From d73b3150a8082f56157f3cfe07f1cc8179eaced4 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 17 Sep 2026 23:27:11 +0000 Subject: [PATCH 1/5] style: drop em dashes and stock phrasing from prose Comments, docs, and user-facing sentences no longer use em dashes or stock AI phrasing. Generated dashboard contracts and the TypeScript SDK were regenerated from the updated descriptions. Dashboard empty-value marks stay as they are. --- .../scripts/diagnose-summary.sh | 6 +- .../scripts/project-analytics.sh | 10 +- .../scripts/friction-scan.sh | 12 +- .claude/skills/using-hotpath/SKILL.md | 6 +- .../using-hotpath/references/hotpath-0.24.md | 2 +- .../references/instrumentation-facilities.md | 4 +- .../scripts/diagnose-summary.sh | 6 +- .../scripts/project-analytics.sh | 10 +- .../scripts/project-analytics.test.sh | 2 +- .../scripts/friction-scan.sh | 12 +- .../scripts/friction-scan.test.sh | 6 +- .codex/skills/using-hotpath/SKILL.md | 6 +- .../using-hotpath/references/hotpath-0.24.md | 2 +- .../references/instrumentation-facilities.md | 4 +- .github/workflows/hawk.yml | 4 +- .github/workflows/hotpath-coverage.yml | 8 +- .github/workflows/pr-run-cleanup.yml | 4 +- .github/workflows/release.yml | 2 +- AGENTS.md | 40 +- CHANGELOG.md | 868 +++++++------- CONTRIBUTING.md | 10 +- Cargo.lock | 10 - SECURITY.md | 24 +- benchmark_data/index-bench/README.md | 8 +- benchmark_data/queries/default.toml | 2 +- benchmark_data/run_benchmarks.py | 8 +- .../cli-exact-query-p95-20260730/README.md | 2 +- .../code-index-restore-20260731/README.md | 14 +- .../README.md | 2 +- .../README.md | 2 +- .../README.md | 2 +- benchmark_data/session-temporal/README.md | 4 +- benchmark_data/tsbench/README.md | 22 +- crates/tracedecay-agent-hosts/Cargo.toml | 1 - crates/tracedecay-agent-hosts/build.rs | 4 +- .../src/agents/antigravity.rs | 6 +- .../src/agents/claude.rs | 38 +- .../src/agents/claude/tests.rs | 4 +- .../src/agents/cline.rs | 2 +- .../src/agents/codex.rs | 50 +- .../src/agents/codex/mcp_registry.rs | 14 +- .../src/agents/codex/plugin_registry.rs | 2 +- .../src/agents/codex/tests.rs | 6 +- .../src/agents/context_scout.rs | 12 +- .../src/agents/context_scout/store.rs | 4 +- .../src/agents/copilot.rs | 14 +- .../src/agents/cursor.rs | 46 +- .../src/agents/cursor_diagnostics.rs | 8 +- .../src/agents/devin.rs | 2 +- .../src/agents/gemini.rs | 46 +- .../src/agents/gemini/extension.rs | 20 +- .../src/agents/gemini/tests.rs | 6 +- .../src/agents/git_post_commit_hook.rs | 6 +- .../src/agents/hermes.rs | 10 +- .../src/agents/hermes/dashboard_wrapper.rs | 8 +- .../agents/hermes/templates/plugin_init.py | 16 +- .../src/agents/hermes/templates/skill.md | 16 +- .../src/agents/host_bundle/component_set.rs | 14 +- .../src/agents/host_bundle/doctor.rs | 12 +- .../src/agents/host_bundle/planner.rs | 8 +- .../src/agents/host_bundle/tests.rs | 10 +- .../src/agents/host_bundle/writer.rs | 4 +- .../src/agents/host_bundle_registry.rs | 10 +- .../src/agents/host_cli.rs | 16 +- .../src/agents/host_component_registration.rs | 10 +- .../src/agents/host_config_io.rs | 10 +- .../tracedecay-agent-hosts/src/agents/kimi.rs | 10 +- .../tracedecay-agent-hosts/src/agents/kiro.rs | 24 +- .../src/agents/kiro/tests.rs | 2 +- .../src/agents/mcp_registration.rs | 12 +- .../tracedecay-agent-hosts/src/agents/mod.rs | 12 +- .../src/agents/opencode.rs | 30 +- .../src/agents/opencode/plugin_cli.rs | 18 +- .../src/agents/plugin_bundle.rs | 28 +- .../src/agents/text_file_transaction.rs | 10 +- .../tracedecay-agent-hosts/src/agents/vibe.rs | 6 +- .../tracedecay-agent-hosts/src/agents/zed.rs | 2 +- .../src/hooks/analytics.rs | 8 +- .../src/hooks/analytics/readiness.rs | 6 +- .../src/hooks/analytics/tests.rs | 4 +- .../src/hooks/cursor.rs | 4 +- .../src/hooks/dispatch.rs | 2 +- .../src/hooks/hint_outcomes.rs | 10 +- .../src/hooks/hint_outcomes/settlement.rs | 8 +- .../tracedecay-agent-hosts/src/hooks/mod.rs | 6 +- .../src/hooks/steering.rs | 6 +- .../src/hooks/store_layout.rs | 4 +- .../src/hooks/tool_hints.rs | 10 +- .../src/hooks/tool_hints/classifiers.rs | 4 +- .../src/hooks/tool_hints/tests.rs | 2 +- crates/tracedecay-agent-hosts/src/lib.rs | 4 +- .../src/native_integration/registry.rs | 4 +- .../src/ports/hook_runtime.rs | 4 +- .../src/ports/mcp_tools.rs | 10 +- .../src/product_version.rs | 8 +- .../src/product_version/root_manifest.rs | 2 +- crates/tracedecay-api/src/observe.rs | 2 +- crates/tracedecay-api/src/read_model.rs | 8 +- crates/tracedecay-api/src/work.rs | 8 +- crates/tracedecay-application/Cargo.toml | 1 - .../benches/hotpath_coverage.rs | 2 +- .../src/advisory/github_runtime.rs | 2 +- .../src/advisory/github_runtime/protocol.rs | 8 +- .../src/analytics_bridge/summary.rs | 4 +- .../src/dashboard_diagnostics.rs | 6 +- crates/tracedecay-application/src/delivery.rs | 6 +- crates/tracedecay-application/src/diagnose.rs | 10 +- .../src/diagnostics_publication.rs | 26 +- .../src/diagnostics_query.rs | 10 +- .../src/diagnostics_store.rs | 6 +- .../src/feedback/cycle_production.rs | 4 +- .../src/feedback/cycle_runtime.rs | 6 +- .../src/git_intelligence.rs | 10 +- .../tracedecay-application/src/git_query.rs | 6 +- crates/tracedecay-application/src/lib.rs | 2 +- .../src/lsp_runtime/semantic.rs | 2 +- .../src/lsp_support/mod.rs | 2 +- .../src/native_integration/authorization.rs | 4 +- .../src/observability/producer.rs | 2 +- .../src/observability/retrieval_emit.rs | 2 +- .../tracedecay-application/src/pr_tracking.rs | 4 +- .../src/pr_tracking/worktrees.rs | 17 +- .../src/primitives/concrete.rs | 4 +- .../production/affected_tests_tests.rs | 2 +- .../production/symbol_graph_snapshot.rs | 10 +- .../src/primitives/runtime.rs | 2 +- .../src/primitives/symbol_graph.rs | 6 +- .../src/project_adoption.rs | 10 +- .../src/tracedecay/mod.rs | 6 +- crates/tracedecay-application/src/work/mod.rs | 2 +- .../src/work/registered.rs | 2 +- .../observability_runtime_contract.rs | 2 +- .../work_service_composition.rs | 2 +- .../tracedecay-automation-runtime/Cargo.toml | 1 - .../src/automation/backend_identity.rs | 18 +- .../src/automation/effect_runtime/journal.rs | 5 +- .../effect_runtime/recovery_index.rs | 4 +- .../automation/effect_runtime/retirement.rs | 6 +- .../effect_runtime/settlement/tests.rs | 15 +- .../src/automation/host_receipts.rs | 3 +- .../src/automation/job_webhook.rs | 6 +- .../src/automation/jobs.rs | 2 +- .../src/automation/jobs/effect_receipt.rs | 2 +- .../src/automation/jobs/scheduler_gate.rs | 6 +- .../src/automation/managed_skills.rs | 5 +- .../src/automation/run_ledger.rs | 8 +- .../src/automation/run_ledger/exact_lookup.rs | 5 +- .../run_ledger/exact_publication.rs | 12 +- .../run_ledger/scheduler_diagnostic.rs | 6 +- .../src/automation/runner.rs | 4 +- .../src/automation/scheduler.rs | 14 +- .../src/automation/session_reflector.rs | 4 +- .../src/automation/skill_materialization.rs | 25 +- .../src/automation/skill_usage/overlap.rs | 8 +- .../src/automation/skill_usage/store.rs | 5 +- .../src/ports/codex_app_server.rs | 8 +- .../src/ports/session_store.rs | 2 +- crates/tracedecay-automation/src/analytics.rs | 2 +- .../src/evidence_budget.rs | 4 +- .../src/skill_frontmatter.rs | 2 +- .../src/claude/canonical.rs | 4 +- crates/tracedecay-capture/src/codex.rs | 4 +- crates/tracedecay-capture/src/cursor.rs | 2 +- .../tracedecay-capture/src/cursor_composer.rs | 2 +- crates/tracedecay-capture/src/parse.rs | 4 +- .../capture_suite/provider_usage_capture.rs | 2 +- .../tests/hotpath_coverage.rs | 4 +- crates/tracedecay-cli/Cargo.toml | 11 +- .../build-support/dashboard_bundle.rs | 2 +- .../build-support/source_provenance.rs | 14 +- crates/tracedecay-cli/build.rs | 6 +- crates/tracedecay-cli/src/agent_cmd.rs | 12 +- crates/tracedecay-cli/src/analytics_cmd.rs | 2 +- crates/tracedecay-cli/src/cli.rs | 16 +- crates/tracedecay-cli/src/cli/help.rs | 26 +- crates/tracedecay-cli/src/cli/parse_tests.rs | 2 +- crates/tracedecay-cli/src/cloud.rs | 8 +- crates/tracedecay-cli/src/commands/branch.rs | 4 +- crates/tracedecay-cli/src/commands/index.rs | 8 +- .../src/commands/profile_storage.rs | 10 +- crates/tracedecay-cli/src/commands/scope.rs | 2 +- .../tracedecay-cli/src/commands/settings.rs | 6 +- crates/tracedecay-cli/src/commands/storage.rs | 12 +- crates/tracedecay-cli/src/display.rs | 4 +- crates/tracedecay-cli/src/global.rs | 8 +- crates/tracedecay-cli/src/main.rs | 10 +- crates/tracedecay-cli/src/product_runtime.rs | 2 +- crates/tracedecay-cli/src/project_cmd.rs | 4 +- crates/tracedecay-cli/src/serve_cmd.rs | 6 +- crates/tracedecay-cli/src/sessions_cmd.rs | 4 +- crates/tracedecay-cli/src/startup_tests.rs | 2 +- crates/tracedecay-cli/src/status_cmd.rs | 6 +- crates/tracedecay-cli/src/tool_command.rs | 30 +- .../tracedecay-cli/src/tool_command/args.rs | 38 +- .../tracedecay-cli/src/tool_command/tests.rs | 4 +- crates/tracedecay-cli/src/update_cmd.rs | 32 +- crates/tracedecay-cli/src/upgrade.rs | 16 +- crates/tracedecay-cli/src/work_cli.rs | 2 +- .../cli_non_interactive_test.rs | 16 +- .../core_cli_suite/dashboard_bundle_test.rs | 2 +- .../observation_reset_recovery_test.rs | 4 +- .../core_cli_suite/source_provenance_test.rs | 10 +- .../tests/core_cli_suite/tool_daemon_test.rs | 2 +- .../tracedecay-cli/tests/work_loop_journey.rs | 6 +- .../tests/work_route_exposure_conformance.rs | 26 +- .../work_evidence.rs | 2 +- .../work_task_session.rs | 22 +- crates/tracedecay-code-extraction/Cargo.toml | 2 +- .../src/astro_extractor.rs | 2 +- .../src/batch_extractor.rs | 4 +- .../src/c_extractor.rs | 2 +- .../src/clojure_extractor.rs | 2 +- .../src/clone_body.rs | 19 +- .../src/cobol_extractor.rs | 4 +- .../src/complexity.rs | 8 +- .../src/cpp_extractor.rs | 2 +- .../src/cpp_extractor/metadata.rs | 2 +- .../src/csharp_extractor.rs | 2 +- .../src/dart_extractor.rs | 2 +- .../src/dockerfile_extractor.rs | 2 +- .../src/gwbasic_extractor.rs | 2 +- .../src/haskell_extractor.rs | 2 +- .../src/hlsl_extractor.rs | 2 +- .../src/java_extractor.rs | 2 +- .../src/julia_extractor.rs | 2 +- .../src/kotlin_extractor.rs | 2 +- .../src/lean_extractor.rs | 14 +- crates/tracedecay-code-extraction/src/lib.rs | 6 +- .../src/markdown_extractor.rs | 12 +- .../src/markdown_structure.rs | 6 +- .../src/msbasic2_extractor.rs | 2 +- .../src/nix_extractor.rs | 2 +- .../src/objc_extractor.rs | 2 +- .../src/ocaml_extractor.rs | 2 +- .../src/parsed_extraction.rs | 4 +- .../src/pascal_extractor.rs | 2 +- .../src/perl_extractor.rs | 8 +- .../src/php_extractor.rs | 2 +- .../src/proto_extractor.rs | 2 +- .../src/python_extractor.rs | 6 +- .../src/qbasic_extractor.rs | 2 +- .../src/quickbasic_extractor.rs | 2 +- .../src/quint_extractor.rs | 2 +- .../src/r_extractor.rs | 4 +- .../src/ruby_extractor.rs | 2 +- .../src/rust_extractor.rs | 26 +- .../src/scala_extractor.rs | 2 +- .../src/source_mask.rs | 6 +- .../src/svelte_extractor.rs | 10 +- .../src/swift_extractor.rs | 2 +- .../src/typescript_extractor.rs | 8 +- .../src/typescript_extractor/test_calls.rs | 6 +- .../src/vbnet_extractor.rs | 2 +- .../src/zig_extractor.rs | 2 +- .../tests/extract_alloc.rs | 4 +- .../tests/main/fixture.rs | 2 +- .../tests/main/incremental_parse.rs | 2 +- .../tests/main/lean.rs | 2 +- .../tests/main/markdown.rs | 4 +- .../tests/main/markdown_modern_grammar.rs | 2 +- .../tests/main/quint.rs | 4 +- .../tests/main/rust.rs | 6 +- .../tests/main/support/docstrings.rs | 2 +- .../tests/main/svelte.rs | 2 +- .../Cargo.toml | 1 - .../src/code_index_generations.rs | 16 +- .../generation_transactions.rs | 4 +- .../src/code_index_generations/journal.rs | 2 +- .../src/code_index_generations/locking.rs | 14 +- .../code_index_generations/receipt_store.rs | 2 +- .../scope_quarantine.rs | 8 +- .../src/code_index_generations/scope_roots.rs | 12 +- .../src/code_index_generations/tests.rs | 2 +- .../src/code_index_executor.rs | 20 +- .../src/code_index_scheduler/activation.rs | 8 +- .../code_index_scheduler/activation_tests.rs | 4 +- .../branch_generations.rs | 32 +- .../src/code_index_scheduler/cadence.rs | 6 +- .../code_index_scheduler/classification.rs | 4 +- .../code_index_scheduler/demand_admission.rs | 4 +- .../code_index_scheduler/freshness_witness.rs | 6 +- .../code_index_scheduler/git_tree_capture.rs | 10 +- .../src/code_index_scheduler/identity.rs | 6 +- .../ignored_dependencies.rs | 12 +- .../src/code_index_scheduler/memory_tests.rs | 6 +- .../code_index_scheduler/publication_store.rs | 14 +- .../src/code_index_scheduler/queries.rs | 2 +- .../src/code_index_scheduler/query_runtime.rs | 6 +- .../src/code_index_scheduler/reconcile.rs | 58 +- .../reconcile_panic_guard.rs | 6 +- .../src/code_index_scheduler/registry.rs | 32 +- .../registry/convergence_park_tests.rs | 16 +- .../code_index_scheduler/registry/mount.rs | 10 +- .../reconcile_failure_isolation_tests.rs | 10 +- .../registry/scope_identity.rs | 2 +- .../registry/serving_reads.rs | 20 +- .../registry/test_gates.rs | 2 +- .../src/code_index_scheduler/serving.rs | 18 +- .../src/code_index_scheduler/tests/mod.rs | 4 +- .../code_index_scheduler/tests/reconcile.rs | 58 +- .../tests/search_permit_release.rs | 12 +- .../src/code_index_scheduler/tests/serving.rs | 32 +- .../src/git_index_transactions/safety.rs | 12 +- .../src/git_transactions/mod.rs | 2 +- .../src/git_transactions/native.rs | 6 +- .../src/git_transactions/service.rs | 2 +- .../src/git_watch.rs | 16 +- .../src/git_watch/admission.rs | 2 +- .../src/git_watch/backstop.rs | 4 +- .../src/git_watch/overflow.rs | 4 +- .../src/git_watch/tests.rs | 32 +- .../src/ports.rs | 4 +- .../project_reads/scope_admission_tests.rs | 6 +- crates/tracedecay-code-index/Cargo.toml | 2 +- .../benches/restore_generation.rs | 4 +- .../src/ast_grep_search.rs | 14 +- .../tracedecay-code-index/src/capabilities.rs | 4 +- crates/tracedecay-code-index/src/chunks.rs | 6 +- crates/tracedecay-code-index/src/clones.rs | 12 +- .../tracedecay-code-index/src/generations.rs | 8 +- .../src/graph_projection.rs | 6 +- .../src/graph_projection/builder.rs | 6 +- .../src/graph_projection/interactive.rs | 10 +- .../graph_projection/interactive/artifact.rs | 2 +- .../src/graph_projection/interactive/tests.rs | 2 +- .../src/hotpath_observe.rs | 2 +- crates/tracedecay-code-index/src/intake.rs | 12 +- crates/tracedecay-code-index/src/languages.rs | 2 +- crates/tracedecay-code-index/src/lineage.rs | 4 +- .../tracedecay-code-index/src/parallelism.rs | 2 +- .../src/production/helpers.rs | 6 +- .../production/lexical_page_source_tests.rs | 2 +- .../src/production/mod.rs | 16 +- .../src/production/partitioned_codec.rs | 26 +- .../src/production/sealed_codec.rs | 16 +- crates/tracedecay-code-index/src/receipts.rs | 10 +- .../tracedecay-code-index/src/source_walk.rs | 2 +- .../src/unmounted_files.rs | 16 +- .../src/unmounted_files/rust.rs | 40 +- .../src/unmounted_files/typescript.rs | 30 +- .../production_orchestration.rs | 20 +- .../parallel_equivalence.rs | 8 +- .../sealed_generation_restore.rs | 4 +- .../fixtures/partitioned_pre_paging/README.md | 2 +- .../src/config/analyzer.rs | 4 +- .../src/config/model.rs | 14 +- .../src/config/model/tests.rs | 6 +- .../src/config/topology.rs | 2 +- crates/tracedecay-contracts/src/clock.rs | 2 +- .../src/code_index_freshness.rs | 4 +- .../tracedecay-contracts/src/configuration.rs | 4 +- crates/tracedecay-contracts/src/context.rs | 4 +- .../src/doctor/adapters.rs | 6 +- crates/tracedecay-contracts/src/doctor/mod.rs | 2 +- .../tracedecay-contracts/src/doctor/report.rs | 4 +- .../src/doctor/sources.rs | 4 +- .../tracedecay-contracts/src/doctor/types.rs | 4 +- .../src/feedback/service.rs | 4 +- .../src/git/native_integration_surface.rs | 2 +- .../tracedecay-contracts/src/git/worktree.rs | 2 +- crates/tracedecay-contracts/src/handlers.rs | 4 +- crates/tracedecay-contracts/src/handoff.rs | 6 +- .../src/handoff_catalog.rs | 4 +- crates/tracedecay-contracts/src/invocation.rs | 2 +- .../src/multi_root/collection.rs | 2 +- .../src/retained_surfaces/evidence.rs | 2 +- .../src/retained_surfaces/service.rs | 4 +- .../src/retrieval/catalog.rs | 2 +- .../src/retrieval/ports.rs | 6 +- .../tracedecay-contracts/src/sdk_catalog.rs | 2 +- .../tracedecay-contracts/src/source_edit.rs | 6 +- .../src/source_edit/invocation.rs | 4 +- .../src/storage/compaction.rs | 4 +- .../src/storage/debris.rs | 2 +- .../src/storage/findings.rs | 12 +- .../src/storage/inventory.rs | 8 +- .../src/storage/telemetry.rs | 4 +- .../src/work_artifact_hydration.rs | 4 +- .../src/work_attempt/product_admission.rs | 2 +- .../src/work_handoff_frontier.rs | 2 +- .../src/work_product/query.rs | 6 +- .../src/work_product/read.rs | 4 +- .../src/work_run_control.rs | 6 +- .../src/work_synthesis.rs | 10 +- .../src/work_topology_view.rs | 2 +- .../src/workflow_admission.rs | 2 +- .../src/workflow_coordination.rs | 10 +- .../tracedecay-contracts/src/workflow_run.rs | 6 +- .../tests/contracts_suite/doctor_report.rs | 2 +- .../contracts_suite/git_read_contract.rs | 2 +- .../contracts_suite/multi_root_scope_set.rs | 2 +- .../work_artifact_hydration_service.rs | 2 +- .../contracts_suite/work_attempt_service.rs | 2 +- .../work_product_application.rs | 2 +- .../contracts_suite/workflow_coordination.rs | 2 +- .../tracedecay-daemon-control/src/service.rs | 12 +- .../src/service/probe.rs | 2 +- .../src/service/runner.rs | 2 +- .../src/service/unit_file.rs | 2 +- crates/tracedecay-daemon-identity/Cargo.toml | 1 - .../src/authority.rs | 3 +- .../src/connection.rs | 4 +- .../src/application_surface.rs | 8 +- .../tracedecay-daemon-protocol/src/client.rs | 12 +- .../src/client/controlled_invocation.rs | 6 +- .../src/client/controlled_invocation_tests.rs | 4 +- .../src/client/lsp_session.rs | 4 +- .../src/connection.rs | 8 +- .../src/contract/mod.rs | 6 +- .../src/handshake.rs | 2 +- crates/tracedecay-daemon-service/Cargo.toml | 1 - .../src/adoption_observation.rs | 4 +- .../application_surface/configuration_wire.rs | 4 +- .../src/application_surface/dispatch.rs | 2 +- .../application_surface/operation_events.rs | 4 +- .../src/application_surface/tests.rs | 2 +- .../src/automation_effect/journal/tests.rs | 5 +- .../src/context_scout_lifecycle/tests.rs | 2 +- .../src/doctor_kernel.rs | 24 +- .../src/invocation/dispatch.rs | 2 +- .../src/invocation/handoff.rs | 2 +- .../src/invocation/lsp.rs | 4 +- .../invocation/lsp/federated_pairing_tests.rs | 4 +- .../src/invocation/lsp_delivery.rs | 2 +- .../src/invocation/native_integration.rs | 12 +- .../src/invocation/primitive.rs | 2 +- .../src/invocation/registrars.rs | 8 +- .../src/invocation/retained.rs | 4 +- .../src/invocation/tests/dispatch_tests.rs | 2 +- .../tests/project_admission_tests.rs | 10 +- .../src/invocation/types.rs | 4 +- .../src/invocation/work_attempt_exec.rs | 6 +- .../src/invocation/work_attempt_exec/tests.rs | 16 +- .../tracedecay-daemon-service/src/logging.rs | 10 +- .../src/profile_host_admission_replay.rs | 6 +- .../src/project_runtime.rs | 8 +- .../src/project_runtime/observability.rs | 14 +- .../src/project_runtime/recovery_tests.rs | 8 +- .../src/project_runtime/shutdown.rs | 12 +- .../profile_refresh_journeys.rs | 2 +- .../session_retained_effect_tests.rs | 2 +- .../src/shutdown/orchestration.rs | 6 +- .../src/shutdown/watchdog.rs | 6 +- .../src/shutdown_coordination.rs | 4 +- .../src/analytics_api.rs | 30 +- .../src/automation_run_api.rs | 2 +- crates/tracedecay-dashboard-api/src/cloud.rs | 6 +- .../src/code_index_freshness_api.rs | 6 +- .../src/delivery_api.rs | 6 +- .../src/doctor_findings_api.rs | 6 +- .../src/events_api.rs | 16 +- .../src/graph_service.rs | 2 +- crates/tracedecay-dashboard-api/src/lib.rs | 16 +- .../tracedecay-dashboard-api/src/loom_api.rs | 2 +- .../src/memory_analysis.rs | 2 +- .../src/memory_analysis/pca.rs | 4 +- .../src/memory_api.rs | 16 +- .../src/multi_root_api.rs | 6 +- .../src/native_integration_api.rs | 2 +- .../tracedecay-dashboard-api/src/observe.rs | 2 +- .../src/remote_status_api.rs | 4 +- .../src/savings_api.rs | 18 +- .../src/settings_api.rs | 8 +- .../src/storage_findings_api.rs | 2 +- .../src/storage_telemetry_api.rs | 6 +- .../src/token_count.rs | 16 +- .../tracedecay-dashboard-api/src/work_api.rs | 2 +- .../tracedecay-domain/src/canonical_text.rs | 10 +- .../src/code_intelligence/graph.rs | 6 +- .../src/code_intelligence/index.rs | 4 +- .../src/code_intelligence/language.rs | 2 +- .../src/code_intelligence/search.rs | 6 +- .../src/code_intelligence/token_grammar.rs | 2 +- crates/tracedecay-domain/src/diagnostics.rs | 2 +- crates/tracedecay-domain/src/errors.rs | 2 +- crates/tracedecay-domain/src/git/hunk.rs | 2 +- .../tracedecay-domain/src/git/read_model.rs | 2 +- crates/tracedecay-domain/src/integration.rs | 6 +- .../src/integration/descriptor.rs | 2 +- crates/tracedecay-domain/src/observation.rs | 8 +- .../src/research/canonical.rs | 2 +- crates/tracedecay-domain/src/research/id.rs | 4 +- crates/tracedecay-domain/src/retrieval.rs | 2 +- .../tracedecay-domain/src/work_placement.rs | 2 +- .../src/work_product_projection.rs | 2 +- crates/tracedecay-framing/src/lib.rs | 4 +- .../src/discovery_queue.rs | 2 +- .../src/git_index_transactions/read.rs | 2 +- crates/tracedecay-global-db/src/lib.rs | 6 +- .../src/observation/refusal_census.rs | 4 +- .../src/observation/reset.rs | 40 +- .../src/observation/reset/tests.rs | 20 +- .../src/observation/retention.rs | 26 +- .../src/observation/schema.rs | 12 +- .../src/observation_adapter.rs | 18 +- .../src/observation_collision_tests.rs | 48 +- .../src/observation_projection/apply.rs | 10 +- .../src/observation_projection/rebuild.rs | 2 +- .../src/observation_projection/state.rs | 4 +- .../src/project_registry.rs | 24 +- .../src/registered_lcm_privacy.rs | 10 +- .../src/registry_maintenance/lifecycle.rs | 8 +- .../src/registry_maintenance/tests.rs | 12 +- .../src/schema_contract/definitions.rs | 2 +- .../src/schema_contract/invariants/audit.rs | 2 +- .../invariants/released_rendering.rs | 26 +- .../src/schema_contract/invariants/repair.rs | 2 +- .../invariants/repair/tests.rs | 2 +- .../src/schema_contract/invariants/rows.rs | 6 +- .../schema_contract/invariants/triggers.rs | 4 +- .../tracedecay-global-db/src/schema_stages.rs | 16 +- .../src/session_temporal_schema/admission.rs | 6 +- .../src/sqlite_persist.rs | 2 +- .../src/store_registration.rs | 4 +- crates/tracedecay-global-db/src/support.rs | 6 +- .../tracedecay-global-db/src/tests/harness.rs | 10 +- .../src/tests/lcm_privacy_rescan.rs | 2 +- .../tests/lcm_schema/lcm_schema_contract.rs | 2 +- .../src/tests/lcm_schema/mod.rs | 2 +- .../lcm_schema/temporal_catalog/admission.rs | 2 +- .../tests/registered_schema_fail_closed.rs | 2 +- crates/tracedecay-graph-db/Cargo.toml | 38 +- .../benches/support/mod.rs | 6 +- crates/tracedecay-graph-db/src/bundle.rs | 8 +- crates/tracedecay-graph-db/src/generation.rs | 33 +- .../src/generation/recovered.rs | 12 +- .../src/generation/replay.rs | 2 +- .../src/generation_runtime.rs | 20 +- .../src/hotpath_observe.rs | 4 +- crates/tracedecay-graph-db/src/lease.rs | 6 +- crates/tracedecay-graph-db/src/location.rs | 4 +- crates/tracedecay-graph-db/src/mutation.rs | 2 +- .../src/projection_identity_index.rs | 2 +- .../src/projection_read.rs | 4 +- crates/tracedecay-graph-db/src/recovery.rs | 2 +- crates/tracedecay-graph-db/src/registry.rs | 8 +- .../src/registry/publication.rs | 20 +- .../src/registry/publication_support.rs | 4 +- crates/tracedecay-graph-db/src/runtime.rs | 8 +- crates/tracedecay-graph-db/src/schema.rs | 8 +- .../tracedecay-graph-db/src/sealed_store.rs | 34 +- crates/tracedecay-graph-db/src/state.rs | 4 +- .../src/store_quarantine.rs | 24 +- crates/tracedecay-graph-db/src/traversal.rs | 4 +- .../tests/at_rest_compact_mutation_probe.rs | 8 +- .../tests/at_rest_grafeo_probe.rs | 6 +- .../tests/at_rest_snapshot.rs | 8 +- .../tests/generation_cycle_rss.rs | 6 +- .../durability_crash_contract.rs | 25 +- .../tests/graph_db_suite/registry_contract.rs | 2 +- .../tests/graph_db_suite/runtime_contract.rs | 2 +- .../verified_generation_contract.rs | 10 +- .../code_graph_layout.rs | 8 +- .../sealed_store.rs | 6 +- .../staging_footprint.rs | 16 +- .../tests/replay_hydrate_probe.rs | 6 +- .../tracedecay-graph-db/tests/support/mod.rs | 2 +- .../tests/tiered_storage_rss.rs | 37 +- .../src/context/markdown_sections.rs | 10 +- .../src/context/read_modes.rs | 22 +- crates/tracedecay-graph-query/src/scc.rs | 8 +- .../src/verified_query.rs | 8 +- .../tracedecay-hooks/src/admission_ledger.rs | 2 +- crates/tracedecay-hooks/src/capture.rs | 6 +- crates/tracedecay-hooks/src/config.rs | 4 +- crates/tracedecay-hooks/src/runtime.rs | 2 +- crates/tracedecay-host-admission/src/lib.rs | 2 +- .../src/projection_drain.rs | 2 +- .../tracedecay-host-admission/src/replay.rs | 6 +- .../tracedecay-host-admission/src/schedule.rs | 2 +- .../host_capture_background_cpu_admission.rs | 2 +- .../src/evidence.rs | 6 +- crates/tracedecay-lcm/src/compression.rs | 6 +- crates/tracedecay-lcm/src/contracts.rs | 8 +- crates/tracedecay-lcm/src/dag.rs | 2 +- crates/tracedecay-lcm/src/gc/tests.rs | 18 +- crates/tracedecay-lcm/src/lib.rs | 4 +- crates/tracedecay-lcm/src/metrics.rs | 4 +- .../src/payload/delete_recovery.rs | 8 +- .../src/payload/filesystem_authority.rs | 4 +- .../filesystem_authority/payload_stream.rs | 4 +- .../src/payload/tombstone_probe_tests.rs | 2 +- crates/tracedecay-lcm/src/query.rs | 6 +- crates/tracedecay-lcm/src/query/expand.rs | 4 +- crates/tracedecay-lcm/src/query/grep.rs | 4 +- crates/tracedecay-lcm/src/query/scope.rs | 4 +- crates/tracedecay-lcm/src/query/status.rs | 12 +- crates/tracedecay-lcm/src/raw.rs | 2 +- crates/tracedecay-lcm/src/retention.rs | 12 +- crates/tracedecay-lcm/src/retention/tests.rs | 2 +- crates/tracedecay-lcm/src/schema.rs | 4 +- .../tracedecay-lcm/src/summary_convergence.rs | 8 +- crates/tracedecay-lcm/src/types.rs | 2 +- .../tracedecay-lcm/tests/hotpath_coverage.rs | 2 +- crates/tracedecay-lsp/src/analyzer/broker.rs | 6 +- .../src/analyzer/broker/semantic_authority.rs | 10 +- .../src/analyzer/broker/shared_client.rs | 2 +- .../src/analyzer/broker/tests.rs | 4 +- crates/tracedecay-lsp/src/analyzer/client.rs | 4 +- .../src/analyzer/host_ownership.rs | 8 +- crates/tracedecay-lsp/src/analyzer/launch.rs | 2 +- .../src/compile_diagnostics/mod.rs | 8 +- .../src/compile_diagnostics/python.rs | 4 +- crates/tracedecay-lsp/src/context.rs | 2 +- crates/tracedecay-lsp/src/diagnostics.rs | 2 +- .../tracedecay-lsp/src/gateway/admission.rs | 4 +- crates/tracedecay-lsp/src/provider.rs | 8 +- .../tests/lsp_suite/analyzer_runtime.rs | 2 +- .../analyzer_runtime/one_analyzer_journey.rs | 2 +- .../tracedecay-maintenance/src/generation.rs | 4 +- crates/tracedecay-maintenance/src/loop_run.rs | 2 +- .../src/profile_backup/tests.rs | 6 +- .../tracedecay-maintenance/src/retention.rs | 4 +- .../src/retention/branch_compaction.rs | 16 +- .../src/retention/incident_debris.rs | 2 +- .../src/retention/orphan_stores.rs | 22 +- .../src/retention/orphan_stores/fence.rs | 2 +- .../src/retention/orphan_stores/pages.rs | 10 +- .../src/retention/orphan_stores/quarantine.rs | 8 +- .../src/retention/orphan_stores/tests.rs | 4 +- .../orphan_stores/tests/quarantine.rs | 4 +- .../src/retention/storage_report.rs | 29 +- .../src/store_maintenance/graph_replay.rs | 8 +- .../src/store_maintenance/mod.rs | 20 +- .../tracedecay-mcp-catalog/src/definitions.rs | 2 +- .../src/definitions/admin.rs | 2 +- .../src/definitions/analysis.rs | 14 +- .../src/definitions/edit.rs | 20 +- .../src/definitions/git.rs | 4 +- .../src/definitions/graph.rs | 18 +- .../src/definitions/session.rs | 2 +- .../src/definitions/testing.rs | 2 +- .../src/definitions/workflow.rs | 2 +- crates/tracedecay-mcp-catalog/src/lib.rs | 4 +- .../src/application_output/view.rs | 2 +- crates/tracedecay-mcp/src/bench.rs | 6 +- .../src/broker_stream_transport.rs | 6 +- crates/tracedecay-mcp/src/context_headings.rs | 2 +- .../src/handlers/analysis/circular.rs | 2 +- .../src/handlers/analysis/constructors.rs | 6 +- .../src/handlers/analysis/dead_code.rs | 2 +- .../src/handlers/analysis/field_sites.rs | 13 +- .../src/handlers/analysis/hotspots.rs | 2 +- .../src/handlers/analysis/recursion.rs | 2 +- .../src/handlers/analysis/unmounted_files.rs | 2 +- .../src/handlers/analysis/unsafe_patterns.rs | 13 +- .../tracedecay-mcp/src/handlers/ast_grep.rs | 4 +- .../src/handlers/dashboard_delivery.rs | 6 +- .../src/handlers/dashboard_git_correlation.rs | 4 +- .../src/handlers/dashboard_lcm.rs | 4 +- crates/tracedecay-mcp/src/handlers/edit.rs | 4 +- .../src/handlers/git/affected.rs | 2 +- .../tracedecay-mcp/src/handlers/git/branch.rs | 2 +- .../src/handlers/git/context.rs | 12 +- crates/tracedecay-mcp/src/handlers/git/mod.rs | 2 +- .../src/handlers/git/pr_context_cursor.rs | 12 +- .../tracedecay-mcp/src/handlers/git/shell.rs | 6 +- .../src/handlers/graph/context_markdown.rs | 4 +- .../src/handlers/graph/navigation.rs | 10 +- .../src/handlers/graph/search.rs | 30 +- .../src/handlers/graph/verified.rs | 2 +- crates/tracedecay-mcp/src/handlers/grep.rs | 4 +- .../tracedecay-mcp/src/handlers/health/dsm.rs | 2 +- .../src/handlers/health/runtime.rs | 2 +- .../src/handlers/hook_runtime/admission.rs | 6 +- .../src/handlers/hook_runtime/ingest.rs | 2 +- .../handlers/hook_runtime/ingest/kernels.rs | 2 +- .../tracedecay-mcp/src/handlers/info/body.rs | 19 +- .../src/handlers/info/config.rs | 2 +- .../tracedecay-mcp/src/handlers/info/files.rs | 4 +- .../tracedecay-mcp/src/handlers/info/mod.rs | 4 +- .../src/handlers/info/outline.rs | 4 +- .../src/handlers/info/port_order.rs | 16 +- .../src/handlers/info/port_status.rs | 4 +- .../tracedecay-mcp/src/handlers/info/read.rs | 2 +- .../src/handlers/info/remote_status.rs | 2 +- .../src/handlers/info/signature_search.rs | 2 +- .../src/handlers/info/status.rs | 4 +- .../tracedecay-mcp/src/handlers/info/todos.rs | 2 +- .../src/handlers/info/type_hierarchy.rs | 2 +- crates/tracedecay-mcp/src/handlers/support.rs | 4 +- .../tracedecay-mcp/src/handlers/workflow.rs | 10 +- .../handlers/workflow/affected_tests_tests.rs | 2 +- crates/tracedecay-mcp/src/hook_events.rs | 2 +- crates/tracedecay-mcp/src/jsonrpc.rs | 4 +- crates/tracedecay-mcp/src/lifecycle.rs | 2 +- crates/tracedecay-mcp/src/scope.rs | 2 +- .../tracedecay-mcp/src/server/connection.rs | 8 +- crates/tracedecay-mcp/src/server/dispatch.rs | 6 +- crates/tracedecay-mcp/src/server/protocol.rs | 4 +- crates/tracedecay-mcp/src/server/rmcp.rs | 6 +- .../tracedecay-mcp/src/server/settlement.rs | 14 +- crates/tracedecay-mcp/src/tool_analytics.rs | 14 +- .../tracedecay-mcp/src/tool_call_deadline.rs | 4 +- crates/tracedecay-mcp/src/tool_context.rs | 16 +- crates/tracedecay-mcp/src/tool_errors.rs | 4 +- crates/tracedecay-mcp/src/tools/binding.rs | 12 +- .../src/tools/dispatch_ceiling.rs | 10 +- crates/tracedecay-mcp/src/tools/mod.rs | 10 +- crates/tracedecay-mcp/src/tools/render.rs | 6 +- crates/tracedecay-mcp/src/transport.rs | 4 +- .../src/workflow/test_identity.rs | 2 +- crates/tracedecay-policy/src/work_loop.rs | 6 +- crates/tracedecay-privacy/src/detect.rs | 6 +- .../tracedecay-privacy/src/detector_kernel.rs | 4 +- .../src/privacy_remediation.rs | 2 +- crates/tracedecay-privacy/src/rules.rs | 24 +- .../src/rules/supplement.toml | 6 +- crates/tracedecay-privacy/src/structured.rs | 2 +- .../src/structured_tests.rs | 2 +- .../tracedecay-privacy/src/structured_text.rs | 20 +- .../src/structured_text_tests.rs | 16 +- .../src/capability_dir.rs | 2 +- .../tracedecay-private-fs/src/framed_log.rs | 2 +- crates/tracedecay-private-fs/src/lib.rs | 2 +- crates/tracedecay-private-fs/src/windows.rs | 4 +- .../tests/atomic_publication.rs | 4 +- crates/tracedecay-project/src/config.rs | 8 +- crates/tracedecay-project/src/config/tests.rs | 4 +- crates/tracedecay-project/src/lib.rs | 2 +- .../tracedecay-project/src/product_runtime.rs | 4 +- crates/tracedecay-project/src/project.rs | 2 +- .../src/project/diagnostics.rs | 8 +- .../src/project/lifecycle/branches.rs | 2 +- .../src/project/lifecycle/identity.rs | 10 +- .../src/project/lifecycle/mod.rs | 2 +- .../tracedecay-project/src/runtime_ports.rs | 4 +- .../src/test_support/host_admission.rs | 8 +- .../host_admission/accounting_test_support.rs | 2 +- crates/tracedecay-project/src/version.rs | 4 +- .../src/bin/tracedecay_search_bench.rs | 2 +- crates/tracedecay-query/src/code_search.rs | 2 +- .../src/retrieval/diversity.rs | 2 +- .../tracedecay-query/src/retrieval/lexical.rs | 6 +- .../src/retrieval/lexical/projection.rs | 10 +- .../retrieval/lexical/projection/artifact.rs | 4 +- .../lexical/projection/artifact/builder.rs | 10 +- .../lexical/projection/artifact/reader.rs | 6 +- .../lexical/projection/artifact/schema.rs | 2 +- .../src/retrieval/observation.rs | 8 +- .../tracedecay-query/src/retrieval/ports.rs | 2 +- .../src/search_quality/candidate_output.rs | 40 +- .../src/search_quality/mod.rs | 4 +- .../candidate_producers.rs | 6 +- crates/tracedecay-runtime-core/Cargo.toml | 1 - crates/tracedecay-runtime-core/src/branch.rs | 7 +- .../src/branch_meta.rs | 14 +- crates/tracedecay-runtime-core/src/config.rs | 6 +- .../tracedecay-runtime-core/src/db/access.rs | 9 +- .../src/db/connection/graph_binding.rs | 4 +- .../src/db/engine/connection.rs | 6 +- .../src/db/external_source.rs | 6 +- .../src/db/migrations/released_shape.rs | 4 +- .../src/db/retrieval_anchor_schema.rs | 2 +- crates/tracedecay-runtime-core/src/db/sql.rs | 6 +- crates/tracedecay-runtime-core/src/git.rs | 2 +- .../src/git_discovery.rs | 12 +- .../src/git_repository.rs | 24 +- crates/tracedecay-runtime-core/src/lib.rs | 4 +- .../src/lifecycle_lease.rs | 49 +- crates/tracedecay-runtime-core/src/logging.rs | 2 +- .../src/path_safety.rs | 24 +- crates/tracedecay-runtime-core/src/ports.rs | 4 +- .../src/profiled_lock.rs | 2 +- .../src/runtime_telemetry.rs | 8 +- .../src/shard_runtime/mod.rs | 4 +- .../src/shard_runtime/registry/retirement.rs | 2 +- .../src/shard_runtime/shard.rs | 2 +- .../src/shard_runtime/verified_graph.rs | 4 +- .../src/sqlite_read_snapshot.rs | 15 +- .../src/storage/layout.rs | 18 +- .../src/storage/paths_and_io.rs | 7 +- crates/tracedecay-runtime-core/src/sync.rs | 2 +- .../tracedecay-runtime-core/src/worktree.rs | 22 +- .../tests/sqlite_foreign_snapshot_cost.rs | 2 +- .../src/connection/mod.rs | 6 +- .../src/content_digest.rs | 2 +- .../src/exact_sql/command.rs | 20 +- .../src/exact_sql/guard.rs | 2 +- .../src/exact_sql/mod.rs | 8 +- .../src/exact_sql/tests/lease.rs | 12 +- .../src/exact_sql/tests/mod.rs | 2 +- .../src/exact_sql/tests/pragma.rs | 2 +- .../src/exact_sql/types.rs | 2 +- .../src/hotpath_observe.rs | 6 +- .../src/reader/pool/lease.rs | 6 +- .../src/reader/pool/mod.rs | 12 +- .../src/reader/tests.rs | 6 +- .../src/reader/worker.rs | 4 +- .../src/remote/status.rs | 2 +- .../src/repository/attachment.rs | 2 +- .../src/repository/diagnostics.rs | 4 +- .../evidence_assembly/anchor_state.rs | 4 +- .../src/repository/evidence_assembly/mod.rs | 2 +- .../src/repository/external_source.rs | 2 +- .../src/repository/external_source/slim.rs | 2 +- .../src/repository/fact/mod.rs | 2 +- .../src/repository/graph_publication/exact.rs | 4 +- .../repository/graph_publication/support.rs | 6 +- .../src/repository/mod.rs | 2 +- .../src/repository/observation/authority.rs | 2 +- .../observation/cursor_authority.rs | 6 +- .../src/repository/observation/mod.rs | 2 +- .../src/repository/support.rs | 8 +- .../src/telemetry.rs | 2 +- .../src/work/schema.rs | 4 +- .../src/work_product.rs | 12 +- .../src/work_product/events.rs | 2 +- .../src/work_product/evidence.rs | 6 +- .../src/work_product/history.rs | 4 +- .../src/work_product/read.rs | 12 +- .../src/workflow/disposition.rs | 4 +- .../src/writer/tests/mod.rs | 4 +- .../src/writer/transaction.rs | 6 +- .../src/writer/worker/ingress.rs | 4 +- .../src/writer/worker/mod.rs | 6 +- .../tests/hotpath_coverage.rs | 4 +- .../rusqlite_suite/handoff_open_storage.rs | 4 +- .../rusqlite_suite/transactional_inbox.rs | 10 +- .../work_product_graph_authority.rs | 10 +- .../work_product_query_authority.rs | 16 +- .../work_registered_store/mod.rs | 2 +- .../work_run_control_storage.rs | 2 +- crates/tracedecay-sdk/src/client.rs | 4 +- crates/tracedecay-sdk/src/codegen.rs | 2 +- crates/tracedecay-sdk/src/observe.rs | 4 +- .../tests/sdk_suite/production_daemon.rs | 2 +- .../src/candidate_output.rs | 2 +- .../src/packaged_assets.rs | 6 +- .../src/fact_store/commit_barrier.rs | 6 +- .../fact_store/fact_response_metadata_test.rs | 6 +- .../src/fact_store/graph.rs | 4 +- .../src/fact_store/graph_manifest.rs | 5 +- .../fact_store/graph_reconciliation_tests.rs | 2 +- .../src/fact_store/mod.rs | 4 +- .../src/fact_store/scoring.rs | 2 +- crates/tracedecay-session-memory/src/lib.rs | 2 +- .../src/memory/diff.rs | 4 +- .../src/memory/entities.rs | 18 +- .../src/memory/hygiene.rs | 4 +- .../src/memory/mod.rs | 2 +- .../src/memory/project_memory.rs | 2 +- .../src/provider_pricing.rs | 2 +- .../src/response_handles.rs | 2 +- .../src/session/tests/privacy.rs | 4 +- .../src/session/types.rs | 4 +- .../src/transcript.rs | 2 +- .../src/user_config.rs | 12 +- .../tests/hotpath_coverage.rs | 2 +- .../src/lcm_authority/mount.rs | 2 +- .../src/lcm_summarization.rs | 4 +- .../provider_capabilities.rs | 6 +- .../src/retained/lcm/output.rs | 6 +- .../src/retained/lcm/retrieval.rs | 2 +- .../src/retained/session.rs | 2 +- .../src/session_queries.rs | 6 +- .../src/session_retrieval/admitted.rs | 6 +- .../src/session_retrieval/lcm.rs | 6 +- .../registry.rs | 4 +- .../worker.rs | 4 +- .../tests/hotpath_coverage.rs | 2 +- .../src/direct.rs | 2 +- .../src/execution.rs | 2 +- .../src/handle.rs | 2 +- .../src/hydration/file_stream_tests.rs | 2 +- .../src/lib.rs | 4 +- .../src/operations/message_anchor.rs | 8 +- .../src/operations/sources.rs | 4 +- .../src/participant_freeze.rs | 6 +- .../src/projection/receipts.rs | 4 +- .../src/projection/tests.rs | 4 +- .../src/registered_lcm_render.rs | 8 +- .../src/relation_projection.rs | 2 +- .../src/relations.rs | 9 +- .../src/retrieval.rs | 2 +- .../src/retrieval/queries.rs | 2 +- .../src/retrieval/records/relations.rs | 2 +- .../src/retrieval/tests.rs | 2 +- .../retrieval/tests/relation_graph_tests.rs | 2 +- .../tests/hotpath_coverage.rs | 2 +- .../src/admission/ingest.rs | 2 +- crates/tracedecay-sessions/src/host_ports.rs | 2 +- crates/tracedecay-sessions/src/lib.rs | 2 +- crates/tracedecay-sessions/src/observation.rs | 4 +- .../src/observation_test.rs | 2 +- .../src/repository_provenance.rs | 2 +- .../src/repository_provenance_test.rs | 2 +- .../src/runtime/git_correlation.rs | 2 +- .../runtime/git_correlation/attribution.rs | 17 +- .../src/runtime/git_correlation/backfill.rs | 8 +- .../git_correlation/graph_view_tests.rs | 4 +- .../git_correlation/publication_outbox.rs | 5 +- .../src/runtime/git_correlation/store.rs | 20 +- .../hosts/claude/canonical_projection.rs | 2 +- .../runtime/hosts/claude/record_metadata.rs | 2 +- .../runtime/hosts/claude/source_records.rs | 4 +- .../hosts/cline_like/observation_tests.rs | 2 +- .../src/runtime/hosts/codex.rs | 22 +- .../src/runtime/hosts/codex/events.rs | 22 +- .../src/runtime/hosts/codex/meta.rs | 2 +- .../src/runtime/hosts/codex/observation.rs | 6 +- .../src/runtime/hosts/codex/records.rs | 6 +- .../src/runtime/hosts/codex/tests.rs | 6 +- .../src/runtime/hosts/codex_app_server.rs | 2 +- .../src/runtime/hosts/cursor.rs | 4 +- .../hosts/cursor/parent_dispatch_index.rs | 8 +- .../src/runtime/hosts/cursor/tests.rs | 2 +- .../src/runtime/hosts/cursor_composer.rs | 12 +- .../runtime/hosts/cursor_composer/ingest.rs | 2 +- .../runtime/hosts/cursor_composer/sqlite.rs | 6 +- .../runtime/hosts/cursor_composer/store.rs | 6 +- .../runtime/hosts/cursor_composer/tests.rs | 4 +- .../src/runtime/hosts/hermes/ingest.rs | 2 +- .../src/runtime/hosts/hermes/state_db.rs | 2 +- .../src/runtime/hosts/hermes/tests.rs | 2 +- .../src/runtime/hosts/kiro.rs | 4 +- .../src/runtime/ingest/failure.rs | 6 +- .../src/runtime/ingest/project.rs | 8 +- .../src/runtime/ingest/tests.rs | 2 +- .../jsonl_observation_admission.rs | 18 +- .../jsonl_observation_admission/tests.rs | 6 +- .../snapshot_observation/admission.rs | 2 +- .../src/runtime/pipeline_metrics.rs | 4 +- .../src/runtime/registered_db.rs | 4 +- .../tracedecay-sessions/src/runtime/shared.rs | 6 +- .../tracedecay-sessions/src/runtime/source.rs | 12 +- .../src/runtime/source/jsonl.rs | 20 +- .../src/runtime/source/tests.rs | 2 +- .../src/runtime/store_access/session_sync.rs | 4 +- .../src/runtime/store_access/transcript.rs | 8 +- .../src/runtime/store_port.rs | 4 +- .../src/runtime/workflow/workflow_ingest.rs | 4 +- .../tests/hotpath_coverage.rs | 4 +- crates/tracedecay-source-edit/src/digest.rs | 6 +- .../src/edits/preview.rs | 8 +- .../src/edits/primitives.rs | 8 +- .../src/edits/reconcile_tests.rs | 2 +- .../src/edits/symbols.rs | 2 +- crates/tracedecay-source-edit/src/execute.rs | 2 +- .../src/move_symbol/hints.rs | 10 +- .../src/move_symbol/mod.rs | 12 +- .../src/move_symbol/rust_paths.rs | 2 +- .../tracedecay-source-edit/src/reconcile.rs | 6 +- .../src/remote_query.rs | 14 +- .../src/session_registry.rs | 6 +- .../src/session_registry/code_graph.rs | 46 +- .../code_graph/graph_attachment.rs | 2 +- .../code_graph/sealed_publication_tests.rs | 14 +- .../session_registry/code_graph_manifest.rs | 34 +- .../graph_shutdown_contract_tests.rs | 2 +- ...ified_graph_runtime_port_contract_tests.rs | 12 +- .../src/store_locator_resolver.rs | 20 +- .../src/writer_gate.rs | 4 +- .../src/canonical_projection.rs | 4 +- .../tracedecay-store/src/diagnostics/codec.rs | 6 +- .../src/memory/project_memory/mod.rs | 2 +- .../tracedecay-store/src/observation/mod.rs | 2 +- crates/tracedecay-store/src/projection.rs | 9 +- .../tracedecay-store/src/projection/tests.rs | 4 +- .../src/provider_descriptor.rs | 4 +- .../tracedecay-store/src/retrieval_anchor.rs | 6 +- .../src/runtime/graph_publication.rs | 2 +- .../src/runtime/repository_read.rs | 4 +- crates/tracedecay-store/src/schema.rs | 4 +- .../test-support/fault_harness.rs | 4 +- .../store_suite/external_source_commit.rs | 4 +- .../src/candidates.rs | 4 +- .../tracedecay-temporal-query/src/cursor.rs | 4 +- .../src/hydration.rs | 2 +- crates/tracedecay-temporal-query/src/lib.rs | 12 +- crates/tracedecay-temporal-query/src/ports.rs | 4 +- .../src/ports/contracts.rs | 2 +- .../src/ports/snapshot.rs | 2 +- .../src/ports/tests.rs | 6 +- .../src/resolution/resolver.rs | 2 +- .../src/resolution/summary.rs | 2 +- crates/tracedecay-temporal-query/src/tests.rs | 2 +- .../tests/hotpath_coverage.rs | 4 +- crates/tracedecay/Cargo.toml | 26 +- crates/tracedecay/benches/large_repos.rs | 8 +- crates/tracedecay/benches/queries.rs | 2 +- crates/tracedecay/src/daemon.rs | 2 +- crates/tracedecay/src/daemon/bootstrap.rs | 4 +- crates/tracedecay/src/daemon/branch_admin.rs | 8 +- .../branch_admin/remote_deletion_lifecycle.rs | 2 +- .../daemon/broker_stream_transport_tests.rs | 4 +- ...de_index_runtime_graph_activation_tests.rs | 2 +- .../src/daemon/connection_serving.rs | 14 +- .../tracedecay/src/daemon/core_admission.rs | 14 +- crates/tracedecay/src/daemon/core_client.rs | 18 +- crates/tracedecay/src/daemon/core_doctor.rs | 2 +- crates/tracedecay/src/daemon/core_hooks.rs | 4 +- crates/tracedecay/src/daemon/core_proxy.rs | 24 +- crates/tracedecay/src/daemon/engine.rs | 6 +- .../tracedecay/src/daemon/engine/shutdown.rs | 2 +- .../src/daemon/invocation_dispatch.rs | 2 +- .../src/daemon/invocation_executor.rs | 2 +- .../tracedecay/src/daemon/invocation_state.rs | 8 +- .../invocation_tests/lsp_lease_tests.rs | 2 +- .../daemon/invocation_tests/types_tests.rs | 6 +- .../src/daemon/invocation_tests/work_tests.rs | 2 +- crates/tracedecay/src/daemon/maintenance.rs | 6 +- crates/tracedecay/src/daemon/pr_autotrack.rs | 4 +- .../src/daemon/pr_autotrack/tests.rs | 2 +- .../src/daemon/production_harness.rs | 4 +- .../journey_test_support.rs | 2 +- .../lcm_preserved_profile_journey_test.rs | 2 +- .../src/daemon/project_composition.rs | 2 +- .../code_index_activation.rs | 10 +- .../src/daemon/project_composition/runtime.rs | 2 +- .../src/daemon/project_open_admission.rs | 2 +- .../src/daemon/project_open_handshake.rs | 2 +- .../src/daemon/project_open_orchestration.rs | 6 +- .../project_open_owners/advisory_runtime.rs | 6 +- .../advisory_runtime/deferred.rs | 6 +- crates/tracedecay/src/daemon/projectless.rs | 2 +- .../src/daemon/retained_test_support.rs | 4 +- crates/tracedecay/src/daemon/scheduler.rs | 6 +- .../src/daemon/scheduler/combined_effect.rs | 7 +- .../src/daemon/store_runtime_tests.rs | 2 +- .../tracedecay/src/daemon/tests/bootstrap.rs | 22 +- .../tracedecay/src/daemon/tests/handshake.rs | 8 +- .../src/daemon/tests/invocation_ownership.rs | 6 +- .../tracedecay/src/daemon/tests/lifecycle.rs | 6 +- .../tracedecay/src/daemon/tests/ownership.rs | 6 +- .../src/daemon/tests/restart_proxy.rs | 8 +- .../tracedecay/src/daemon/tests/rmcp_route.rs | 2 +- .../src/daemon/tests/route_discovery.rs | 2 +- .../src/daemon/tests/runtime_identity.rs | 16 +- .../src/daemon/tests/scheduler_config.rs | 2 +- crates/tracedecay/src/daemon/tests/socket.rs | 2 +- crates/tracedecay/src/dashboard.rs | 6 +- .../src/dashboard/observation_seed.rs | 2 +- crates/tracedecay/src/doctor.rs | 24 +- crates/tracedecay/src/host_admission_test.rs | 2 +- crates/tracedecay/src/mcp/project_route.rs | 4 +- crates/tracedecay/src/mcp/server.rs | 26 +- .../tracedecay/src/mcp/server/connection.rs | 2 +- .../src/mcp/server/freshness_tests.rs | 2 +- .../src/mcp/server/hook_dispatch.rs | 4 +- .../src/mcp/server/host_admission_tests.rs | 2 +- crates/tracedecay/src/mcp/server/ledger.rs | 16 +- crates/tracedecay/src/mcp/server/lifecycle.rs | 22 +- crates/tracedecay/src/mcp/server/requests.rs | 12 +- crates/tracedecay/src/mcp/server/rmcp.rs | 8 +- .../src/mcp/server/writer_test_support.rs | 6 +- .../mcp/tools/handlers/application_surface.rs | 2 +- .../src/mcp/tools/handlers/dashboard.rs | 4 +- .../src/mcp/tools/handlers/dispatch_groups.rs | 6 +- .../tools/handlers/dispatch_test_support.rs | 2 +- .../src/mcp/tools/handlers/dispatch_tests.rs | 8 +- .../handlers/graph_search_dispatch_tests.rs | 2 +- .../src/mcp/tools/handlers/info/status.rs | 2 +- .../tracedecay/src/mcp/tools/handlers/mod.rs | 4 +- .../verified_graph_query_authority_tests.rs | 2 +- crates/tracedecay/src/test_support/git.rs | 2 +- .../src/test_support/host_admission.rs | 4 +- .../agent_suite/claude_plugin_schema_test.rs | 2 +- .../tests/agent_suite/host_io_bundle_test.rs | 2 +- .../plugin_manifest_schema_test.rs | 4 +- .../agent_suite/skill_lint_cursor_test.rs | 6 +- .../agent_suite/skill_materialization_test.rs | 8 +- .../tests/agent_suite/skill_targets_test.rs | 2 +- .../tests/automation_runner_test/backend.rs | 2 +- .../automation_runner_test/combined_review.rs | 2 +- .../tests/automation_runner_test/jobs.rs | 2 +- .../memory_curator/backend_failures.rs | 2 +- .../tests/automation_runner_test/scheduler.rs | 4 +- .../session_reflector.rs | 2 +- .../automatic_fact_receipts.rs | 4 +- .../automation_runner_test/skill_writer.rs | 2 +- .../support/fixtures.rs | 2 +- crates/tracedecay/tests/common/fixture.rs | 12 +- crates/tracedecay/tests/common/mod.rs | 18 +- .../code_index_ignored_dependencies_test.rs | 4 +- .../daemon_suite/indexing_lifecycle_test.rs | 2 +- .../daemon_suite/invocation_observability.rs | 4 +- crates/tracedecay/tests/daemon_suite/main.rs | 2 +- .../stale_client_resilience_test.rs | 2 +- .../daemon_suite/workflow_handoff_test.rs | 14 +- .../tests/dashboard_api_test/analytics.rs | 2 +- .../tests/dashboard_api_test/delivery.rs | 5 +- .../tests/dashboard_api_test/runtime.rs | 2 +- .../tests/dashboard_api_test/savings.rs | 4 +- .../tests/dashboard_api_test/settings.rs | 2 +- .../graph_suite/annotation_helpers_test.rs | 4 +- crates/tracedecay/tests/graph_suite/main.rs | 2 +- .../tests/hermes_suite/lcm_bridge.rs | 12 +- .../hook_lifecycle_lease_test.rs | 2 +- .../tests/hooks_lsp_suite/hook_replay_test.rs | 4 +- .../mcp_suite/context_relevance_eval_test.rs | 4 +- crates/tracedecay/tests/mcp_suite/fixture.rs | 6 +- .../tests/mcp_suite/git_correlation_test.rs | 2 +- .../tests/mcp_suite/mcp_cli_parity_test.rs | 2 +- .../tests/mcp_suite/mcp_cli_serve_test.rs | 4 +- .../mcp_suite/mcp_dashboard_tool_test.rs | 2 +- .../mcp_handler_test/bounded_analysis_test.rs | 2 +- .../mcp_suite/mcp_handler_test/edit_test.rs | 8 +- .../mcp_handler_test/graph_analysis_test.rs | 22 +- .../mcp_handler_test/graph_query_test.rs | 4 +- .../mcp_suite/mcp_handler_test/lcm_test.rs | 14 +- .../mcp_handler_test/memory_facts_test.rs | 2 +- .../mcp_handler_test/move_symbol_test.rs | 10 +- .../mcp_handler_test/rename_symbol_test.rs | 4 +- .../mcp_suite/mcp_handler_test/schema_test.rs | 2 +- .../mcp_handler_test/session_search_test.rs | 4 +- .../mcp_server_test/analytics_test.rs | 2 +- .../mcp_server_test/hooks_branch_test.rs | 4 +- .../mcp_server_test/protocol_test.rs | 4 +- .../mcp_suite/mcp_server_test/support.rs | 4 +- crates/tracedecay/tests/mcp_suite/support.rs | 8 +- .../tests/memory_suite/memory_eval_test.rs | 2 +- .../git_intelligence_regression.rs | 6 +- .../host_bundle_acceptance.rs | 6 +- .../product_surface_suite/work_views_route.rs | 4 +- .../advisory_runtime_acceptance.rs | 8 +- .../application_production_reachability.rs | 14 +- .../grafeo_restart_acceptance.rs | 2 +- .../runtime_surface_acceptance.rs | 20 +- .../session_suite/fact_anchor_authority.rs | 6 +- .../tests/session_suite/git_backfill.rs | 4 +- .../session_suite/lcm_compression/boundary.rs | 4 +- .../lcm_compression/condensation.rs | 2 +- .../session_suite/lcm_compression/mod.rs | 2 +- .../session_suite/message_search_eval_test.rs | 2 +- .../observation_projection/failure_audit.rs | 2 +- .../session_suite/observation_store/mod.rs | 12 +- .../observation_store/retrieval_anchors.rs | 2 +- .../session_runtime/retained_history.rs | 4 +- .../tests/session_suite/transcript_store.rs | 2 +- .../fact_merge_hydration_test.rs | 4 +- .../storage_resolver_test/markers.rs | 2 +- .../working_tree_guard.rs | 4 +- .../tracedecay/tests/storage_suite/support.rs | 20 +- .../worktree_canonical_root_guard_test.rs | 6 +- .../transcript_ingest_suite/cline_like.rs | 4 +- .../tests/transcript_ingest_suite/codex.rs | 2 +- .../codex_compaction.rs | 2 +- .../transcript_ingest_suite/codex_goals.rs | 4 +- .../codex_response_items.rs | 4 +- .../transcript_ingest_suite/codex_usage.rs | 4 +- .../cursor_composer.rs | 6 +- .../transcript_ingest_suite/session_ingest.rs | 2 +- .../tests/transcript_ingest_suite/support.rs | 2 +- .../typed_terminal_restart_acceptance.rs | 8 +- .../transport_boundaries.rs | 12 +- .../v2_surface_mount_conformance.rs | 24 +- dashboard/audit-baselines/README.md | 2 +- .../schemas/dashboard-contracts.schema.json | 30 +- dashboard/codegen/src/generate.ts | 4 +- dashboard/e2e/responsive.ts | 40 +- dashboard/e2e/visibility.dom.test.ts | 2 +- dashboard/e2e/visibility.ts | 6 +- dashboard/hermes-wrapper/plugin_api.py | 18 +- dashboard/package-lock.json | 65 -- dashboard/package.json | 2 - dashboard/rsbuild.config.ts | 4 +- dashboard/src/app/RouteChunkBoundary.tsx | 4 +- dashboard/src/app/channels.ts | 4 +- .../src/app/shell/CommandPalette.dom.test.tsx | 20 +- dashboard/src/app/shell/CommandPalette.tsx | 12 +- dashboard/src/app/shell/NavRail.tsx | 12 +- dashboard/src/app/shell/ScopeBar.tsx | 10 +- dashboard/src/app/shell/Shell.tsx | 4 +- .../src/app/shell/ShellChrome.dom.test.tsx | 4 +- .../src/app/shell/ShellTruth.dom.test.tsx | 30 +- .../src/app/shell/StatusStrip.dom.test.tsx | 6 +- dashboard/src/app/shell/StatusStrip.tsx | 12 +- .../shell/StatusStripRegisters.dom.test.tsx | 2 +- dashboard/src/contracts/generated.ts | 26 +- dashboard/src/data/query/automation.ts | 26 +- dashboard/src/data/query/capabilities.ts | 4 +- dashboard/src/data/query/codeDiagnostics.ts | 18 +- dashboard/src/data/query/envelope.test.ts | 8 +- dashboard/src/data/query/envelope.ts | 10 +- dashboard/src/data/query/memory.ts | 10 +- dashboard/src/data/query/payload.test.ts | 18 +- dashboard/src/data/query/payload.ts | 10 +- .../src/data/query/projectRegistry.test.ts | 4 +- dashboard/src/data/query/projectRegistry.ts | 20 +- dashboard/src/data/query/responseBody.test.ts | 2 +- dashboard/src/data/query/responseBody.ts | 6 +- .../query/schedulerDispatchScope.dom.test.ts | 4 +- .../src/data/query/scopedWrites.dom.test.ts | 6 +- .../data/query/staleScopeReads.dom.test.tsx | 14 +- .../data/query/storageFindings.dom.test.tsx | 2 +- dashboard/src/data/query/storageFindings.ts | 4 +- dashboard/src/data/query/structure.test.ts | 4 +- dashboard/src/data/query/structure.ts | 18 +- dashboard/src/data/query/wireSchema.ts | 6 +- dashboard/src/data/query/work.ts | 13 +- dashboard/src/data/scope/UrlSync.dom.test.tsx | 2 +- dashboard/src/data/scope/UrlSync.tsx | 12 +- dashboard/src/data/scope/store.test.ts | 6 +- dashboard/src/data/scope/store.ts | 44 +- dashboard/src/data/shell/statusRegisters.ts | 2 +- .../src/data/sse/coalescing.dom.test.tsx | 12 +- dashboard/src/data/sse/connect.test.ts | 2 +- dashboard/src/data/sse/connect.ts | 6 +- .../data/sse/providerLifecycle.dom.test.tsx | 4 +- dashboard/src/data/sse/reducer.test.ts | 18 +- dashboard/src/data/sse/reducer.ts | 14 +- dashboard/src/data/sse/reseed.dom.test.tsx | 8 +- dashboard/src/data/sse/throughput.test.ts | 10 +- dashboard/src/data/sse/types.ts | 8 +- dashboard/src/data/sse/useEvents.test.ts | 12 +- dashboard/src/data/sse/useEvents.tsx | 16 +- dashboard/src/test/deliveryFixtures.ts | 6 +- dashboard/src/test/workGraphFixture.ts | 14 +- dashboard/src/theme/fonts.css | 12 +- dashboard/src/theme/tailwind.css | 26 +- dashboard/src/theme/tokens.css | 56 +- dashboard/src/ui/ActivityColumns.dom.test.tsx | 4 +- dashboard/src/ui/ActivityColumns.tsx | 4 +- .../src/ui/CanonicalReadModelSection.tsx | 4 +- dashboard/src/ui/EnvelopeTruth.tsx | 6 +- dashboard/src/ui/EvidenceGrade.tsx | 2 +- dashboard/src/ui/EvidencePattern.tsx | 2 +- dashboard/src/ui/LegacyStates.tsx | 4 +- dashboard/src/ui/OpsLayout.tsx | 2 +- dashboard/src/ui/ReadSection.tsx | 22 +- dashboard/src/ui/StateChip.dom.test.tsx | 8 +- dashboard/src/ui/StateChip.tsx | 8 +- dashboard/src/ui/VirtualList.dom.test.tsx | 6 +- dashboard/src/ui/VirtualList.tsx | 4 +- dashboard/src/ui/archetypes/ExplorerSplit.tsx | 32 +- dashboard/src/ui/archetypes/OverviewGrid.tsx | 2 +- dashboard/src/ui/format.test.ts | 4 +- dashboard/src/ui/format.ts | 8 +- dashboard/src/ui/instrument.tsx | 24 +- dashboard/src/ui/metricModel.ts | 10 +- dashboard/src/ui/rovingRows.ts | 2 +- dashboard/src/ui/search/SearchField.tsx | 2 +- dashboard/src/ui/time.ts | 4 +- dashboard/src/ui/useScrollTabStop.ts | 4 +- dashboard/src/viz/chart/Chart.dom.test.tsx | 6 +- dashboard/src/viz/chart/Chart.tsx | 10 +- dashboard/src/viz/chart/echarts.ts | 4 +- dashboard/src/viz/chart/series.ts | 2 +- .../graph/GraphCanvas.context.dom.test.tsx | 10 +- .../src/viz/graph/GraphCanvas.dom.test.tsx | 6 +- .../GraphCanvas.propagation.dom.test.tsx | 10 +- dashboard/src/viz/graph/GraphCanvas.tsx | 50 +- dashboard/src/viz/graph/activation.test.ts | 2 +- dashboard/src/viz/graph/activation.ts | 8 +- dashboard/src/viz/graph/activationOverlay.ts | 8 +- dashboard/src/viz/graph/adjacency.test.ts | 2 +- dashboard/src/viz/graph/adjacency.ts | 6 +- dashboard/src/viz/graph/emergentField.ts | 4 +- dashboard/src/viz/graph/kindColor.ts | 10 +- dashboard/src/viz/graph/layout.test.ts | 2 +- dashboard/src/viz/graph/layout.ts | 6 +- dashboard/src/viz/graph/measuredField.ts | 4 +- dashboard/src/viz/graph/nodeHover.test.ts | 2 +- dashboard/src/viz/graph/nodeHover.ts | 4 +- dashboard/src/viz/graph/palette.ts | 4 +- dashboard/src/viz/graph/renderer.test.ts | 6 +- dashboard/src/viz/graph/renderer.ts | 14 +- dashboard/src/viz/graph/scene.ts | 4 +- dashboard/src/viz/graph/types.ts | 10 +- dashboard/src/viz/graph/useActivationField.ts | 2 +- dashboard/src/viz/scale.ts | 2 +- dashboard/src/viz/temporal/TemporalScene.tsx | 4 +- dashboard/src/viz/temporal/glyphs.tsx | 4 +- dashboard/src/viz/temporal/journey.ts | 2 +- dashboard/src/viz/temporal/types.ts | 6 +- dashboard/src/viz/trace/model.test.ts | 6 +- dashboard/src/viz/trace/model.ts | 38 +- dashboard/src/viz/trace/palette.ts | 4 +- dashboard/src/viz/trace/readout.ts | 20 +- .../src/viz/trace/reducedMotion.dom.test.ts | 2 +- dashboard/src/viz/trace/render.test.ts | 2 +- dashboard/src/viz/trace/render.ts | 32 +- dashboard/src/viz/trace/sim.test.ts | 14 +- dashboard/src/viz/trace/sim.ts | 18 +- dashboard/src/viz/trace/types.ts | 20 +- .../agents/AgentFailureContext.dom.test.tsx | 2 +- .../workspaces/agents/AgentFailureContext.tsx | 20 +- .../workspaces/agents/AgentHandoffTokens.tsx | 4 +- .../agents/AgentHandoffs.dom.test.tsx | 2 +- .../src/workspaces/agents/AgentHandoffs.tsx | 4 +- .../src/workspaces/agents/AgentInspector.tsx | 12 +- .../agents/AgentTelemetryRegister.tsx | 22 +- .../workspaces/agents/AgentToolActivity.tsx | 12 +- .../workspaces/agents/AgentsPage.dom.test.tsx | 4 +- .../agents/AgentsPage.topology.dom.test.tsx | 2 +- .../agents/AgentsPage.transport.dom.test.tsx | 14 +- .../src/workspaces/agents/AgentsPage.tsx | 10 +- .../workspaces/agents/DelegationTopology.tsx | 8 +- .../src/workspaces/agents/SubagentTree.tsx | 6 +- dashboard/src/workspaces/agents/activity.ts | 8 +- .../src/workspaces/agents/agentInspector.ts | 6 +- .../src/workspaces/agents/agentWorkQuery.ts | 6 +- .../workspaces/agents/delegationTopology.ts | 12 +- dashboard/src/workspaces/agents/failure.ts | 8 +- dashboard/src/workspaces/agents/handoff.ts | 10 +- .../workspaces/agents/handoffTokenQuery.ts | 2 +- .../src/workspaces/agents/handoffTokens.ts | 12 +- .../src/workspaces/agents/subagentTree.ts | 6 +- dashboard/src/workspaces/agents/usage.ts | 8 +- .../AutomationsPage.transport.dom.test.tsx | 2 +- .../automations/AutomationsPage.tsx | 6 +- .../workspaces/automations/LedgerTable.tsx | 4 +- .../src/workspaces/automations/RunHistory.tsx | 2 +- .../workspaces/automations/RunInspector.tsx | 2 +- .../src/workspaces/automations/RunLedger.tsx | 2 +- .../workspaces/automations/SchedulerBay.tsx | 4 +- .../src/workspaces/automations/ledger.ts | 10 +- .../brain/BrainInteraction.dom.test.tsx | 91 +- .../workspaces/brain/BrainPage.dom.test.tsx | 6 +- dashboard/src/workspaces/brain/BrainPage.tsx | 252 +--- .../workspaces/brain/ScopedBrain.dom.test.tsx | 14 +- .../src/workspaces/brain/ScopedBrain.tsx | 18 +- .../workspaces/brain/SignalPanel.dom.test.tsx | 2 +- .../src/workspaces/brain/SignalPanel.tsx | 6 +- .../workspaces/brain/activitySummary.test.ts | 4 +- .../src/workspaces/brain/activitySummary.ts | 10 +- dashboard/src/workspaces/brain/field.test.ts | 6 +- dashboard/src/workspaces/brain/field.ts | 40 +- dashboard/src/workspaces/code/CallChain.tsx | 10 +- .../src/workspaces/code/CodeDiagnostics.tsx | 14 +- .../code/CodePage.chunk.dom.test.tsx | 14 +- .../code/CodePage.cortex.dom.test.tsx | 6 +- .../src/workspaces/code/CodePage.dom.test.tsx | 8 +- .../CodePage.renderer-fallback.dom.test.tsx | 4 +- dashboard/src/workspaces/code/CodePage.tsx | 6 +- dashboard/src/workspaces/code/CompareView.tsx | 10 +- .../src/workspaces/code/CortexCanvas.tsx | 8 +- dashboard/src/workspaces/code/CortexField.tsx | 2 +- .../src/workspaces/code/CortexInspector.tsx | 14 +- .../src/workspaces/code/CortexLedger.tsx | 10 +- .../workspaces/code/CortexRelief.dom.test.tsx | 4 +- .../src/workspaces/code/CortexRelief.tsx | 16 +- .../src/workspaces/code/IndexFreshness.tsx | 96 +- .../src/workspaces/code/NodeEvidence.tsx | 14 +- .../src/workspaces/code/SharedCodeView.tsx | 6 +- dashboard/src/workspaces/code/Strata.tsx | 8 +- .../workspaces/code/SymbolPath.dom.test.tsx | 2 +- dashboard/src/workspaces/code/SymbolPath.tsx | 20 +- dashboard/src/workspaces/code/TraceCanvas.tsx | 6 +- .../workspaces/code/TraceChunkFallback.tsx | 2 +- .../src/workspaces/code/TraceFeltChannels.tsx | 2 +- .../src/workspaces/code/TraceLegend.test.ts | 4 +- dashboard/src/workspaces/code/TraceLegend.tsx | 6 +- dashboard/src/workspaces/code/TraceList.tsx | 4 +- .../src/workspaces/code/TraceReading.tsx | 4 +- .../src/workspaces/code/TraceReadoutStrip.tsx | 6 +- .../workspaces/code/TraceView.dom.test.tsx | 10 +- dashboard/src/workspaces/code/TraceView.tsx | 16 +- .../src/workspaces/code/codeRegisters.test.ts | 4 +- dashboard/src/workspaces/code/codeView.ts | 2 +- .../src/workspaces/code/compareLayout.ts | 2 +- dashboard/src/workspaces/code/cortex.test.ts | 8 +- dashboard/src/workspaces/code/cortex.ts | 6 +- .../src/workspaces/code/cortexRelief.test.ts | 4 +- dashboard/src/workspaces/code/cortexRelief.ts | 22 +- .../src/workspaces/code/cortexRender.test.ts | 4 +- dashboard/src/workspaces/code/cortexRender.ts | 6 +- dashboard/src/workspaces/code/hubs.ts | 14 +- dashboard/src/workspaces/code/sharedCode.ts | 2 +- .../workspaces/code/traceNeighborhood.test.ts | 4 +- .../src/workspaces/code/traceNeighborhood.ts | 10 +- dashboard/src/workspaces/code/traceRanking.ts | 6 +- .../costs/CanonicalCosts.dom.test.tsx | 2 +- .../src/workspaces/costs/CanonicalCosts.tsx | 8 +- .../src/workspaces/costs/CostsInspector.tsx | 2 +- dashboard/src/workspaces/costs/CostsPage.tsx | 6 +- .../src/workspaces/costs/PricingAuthority.tsx | 2 +- .../workspaces/costs/ProviderSpendField.tsx | 6 +- dashboard/src/workspaces/costs/attribution.ts | 8 +- .../delivery/DeliveryPage.dom.test.tsx | 2 +- .../src/workspaces/delivery/DeliveryPage.tsx | 6 +- .../src/workspaces/delivery/JourneyField.tsx | 2 +- .../workspaces/delivery/LocalFirstWing.tsx | 2 +- .../delivery/PullRequestInspector.tsx | 2 +- .../src/workspaces/delivery/UmbrellaField.tsx | 2 +- .../workspaces/delivery/UmbrellaWorkspace.tsx | 2 +- .../workspaces/delivery/deliveryLocation.ts | 4 +- dashboard/src/workspaces/delivery/journey.ts | 4 +- dashboard/src/workspaces/delivery/review.ts | 6 +- dashboard/src/workspaces/delivery/umbrella.ts | 4 +- .../src/workspaces/delivery/umbrellaLayout.ts | 2 +- .../explorer/ExplorerPage.dom.test.tsx | 6 +- .../src/workspaces/explorer/ExplorerPage.tsx | 12 +- .../src/workspaces/explorer/Inspector.tsx | 6 +- dashboard/src/workspaces/explorer/Lane.tsx | 14 +- .../src/workspaces/explorer/absence.test.ts | 2 +- dashboard/src/workspaces/explorer/absence.ts | 2 +- .../src/workspaces/explorer/controller.ts | 20 +- .../explorer/controllerPolling.dom.test.tsx | 8 +- dashboard/src/workspaces/explorer/evidence.ts | 4 +- .../src/workspaces/explorer/laneModel.test.ts | 2 +- .../src/workspaces/explorer/laneModel.ts | 18 +- dashboard/src/workspaces/explorer/model.ts | 8 +- .../knowledge/FactConstellation.tsx | 10 +- .../workspaces/knowledge/FactInspector.tsx | 6 +- .../src/workspaces/knowledge/FactLedger.tsx | 12 +- .../workspaces/knowledge/FactTrustHistory.tsx | 25 +- .../knowledge/KnowledgeFacts.dom.test.tsx | 8 +- .../knowledge/KnowledgeMemory.dom.test.tsx | 6 +- .../knowledge/KnowledgePage.dom.test.tsx | 4 +- .../KnowledgePage.transport.dom.test.tsx | 2 +- .../workspaces/knowledge/KnowledgePage.tsx | 24 +- .../workspaces/knowledge/KnowledgeViews.tsx | 8 +- .../workspaces/knowledge/MemoryGeometry.tsx | 4 +- .../src/workspaces/knowledge/MemoryOplog.tsx | 11 +- .../src/workspaces/knowledge/constellation.ts | 10 +- .../src/workspaces/knowledge/factsAddress.ts | 4 +- .../knowledge/knowledgeRegisters.ts | 8 +- dashboard/src/workspaces/knowledge/ledger.ts | 2 +- .../workspaces/knowledge/memoryModel.test.ts | 2 +- .../src/workspaces/knowledge/memoryModel.ts | 19 +- dashboard/src/workspaces/knowledge/trust.ts | 6 +- .../src/workspaces/loom/LoomPage.dom.test.tsx | 2 +- dashboard/src/workspaces/loom/LoomPage.tsx | 10 +- dashboard/src/workspaces/loom/ThreadChain.tsx | 8 +- dashboard/src/workspaces/loom/chain.test.ts | 2 +- dashboard/src/workspaces/loom/chain.ts | 8 +- dashboard/src/workspaces/loom/loomUrl.ts | 2 +- dashboard/src/workspaces/loom/playback.ts | 2 +- dashboard/src/workspaces/loom/tracks.ts | 6 +- .../src/workspaces/loom/useLoomPlayback.ts | 2 +- .../observatory/AdoptionCoverage.tsx | 6 +- .../observatory/AdoptionOutcomes.tsx | 2 +- .../AnalyticsControls.dom.test.tsx | 4 +- .../observatory/AnalyticsControls.tsx | 6 +- .../CanonicalObservations.dom.test.tsx | 4 +- .../observatory/CanonicalObservations.tsx | 6 +- .../observatory/CodeIndexPipeline.tsx | 6 +- .../observatory/DoctorInspector.dom.test.tsx | 2 +- .../observatory/DoctorInspector.tsx | 8 +- .../observatory/EvidenceInspector.tsx | 4 +- .../workspaces/observatory/EvidencePanel.tsx | 4 +- .../observatory/EvidenceSummaries.tsx | 6 +- .../observatory/HookHints.dom.test.tsx | 2 +- .../src/workspaces/observatory/HookHints.tsx | 6 +- .../observatory/ObservationTimeline.tsx | 2 +- .../ObservatoryOverview.dom.test.tsx | 2 +- .../observatory/ObservatoryPage.dom.test.tsx | 16 +- .../observatory/ObservatoryPage.tsx | 6 +- .../observatory/ObservedFamilyLedger.tsx | 8 +- .../observatory/PerformanceBudgets.tsx | 2 +- .../PerformanceComparisons.dom.test.tsx | 2 +- .../observatory/PlanDimensionCard.tsx | 4 +- .../observatory/RejectedArguments.tsx | 6 +- .../observatory/RetrievalQuality.tsx | 2 +- .../observatory/StorageTelemetry.tsx | 9 +- .../observatory/adoptionCoverage.ts | 2 +- .../observatory/adoptionOutcomes.ts | 4 +- .../observatory/analyticsControls.ts | 6 +- .../src/workspaces/observatory/evidence.ts | 26 +- .../observatory/observatoryReadModel.ts | 6 +- .../observatory/observedFamilies.ts | 8 +- .../observatory/performanceBudgets.test.ts | 4 +- .../observatory/performanceComparisons.ts | 2 +- .../workspaces/observatory/planDimension.ts | 12 +- .../observatory/retrievalQuality.ts | 2 +- .../workspaces/observatory/storageModel.ts | 4 +- .../workspaces/sessions/LcmOverviewPanel.tsx | 4 +- .../sessions/SessionInspector.dom.test.tsx | 2 +- .../workspaces/sessions/SessionInspector.tsx | 30 +- .../src/workspaces/sessions/SessionsPage.tsx | 10 +- .../workspaces/sessions/TranscriptSearch.tsx | 6 +- dashboard/src/workspaces/sessions/model.ts | 8 +- .../src/workspaces/sessions/tokenLabel.ts | 2 +- .../settings/EffectiveConfigTable.tsx | 4 +- .../settings/MultiRootPanel.dom.test.tsx | 4 +- .../workspaces/settings/MultiRootPanel.tsx | 10 +- .../settings/SettingsEditorController.tsx | 2 +- .../workspaces/settings/SettingsInspector.tsx | 8 +- .../settings/SettingsPage.dom.test.tsx | 10 +- .../src/workspaces/settings/SettingsPage.tsx | 16 +- .../settings/SettingsReviewPanel.tsx | 10 +- .../workspaces/settings/SettingsRowEditor.tsx | 2 +- .../workspaces/settings/SettingsValues.tsx | 8 +- .../settings/settingsEditorMachine.test.ts | 10 +- .../settings/settingsEditorMachine.ts | 26 +- .../src/workspaces/settings/settingsGates.ts | 2 +- .../src/workspaces/settings/settingsModel.ts | 30 +- .../workspaces/settings/settingsMutation.ts | 8 +- .../src/workspaces/settings/settingsRows.ts | 10 +- dashboard/src/workspaces/work/WorkBoard.tsx | 2 +- .../work/WorkInspector.dom.test.tsx | 4 +- .../src/workspaces/work/WorkInspector.tsx | 8 +- .../src/workspaces/work/WorkPage.dom.test.tsx | 2 +- dashboard/src/workspaces/work/WorkPage.tsx | 8 +- .../work/WorkTaskActivity.dom.test.tsx | 8 +- .../src/workspaces/work/WorkTaskActivity.tsx | 12 +- .../workspaces/work/views/WorkCausalView.tsx | 24 +- .../workspaces/work/views/WorkDagBoard.tsx | 14 +- .../src/workspaces/work/views/WorkDagView.tsx | 8 +- .../work/views/WorkExecutionRecord.tsx | 6 +- .../work/views/WorkProjectionSwitcher.tsx | 6 +- .../work/views/WorkTimelineView.tsx | 12 +- .../work/views/WorkTopologyAccounting.tsx | 8 +- .../work/views/WorkTopologyView.tsx | 2 +- .../workspaces/work/views/WorkViewChannel.tsx | 4 +- .../work/views/WorkViews.dom.test.tsx | 12 +- .../work/views/WorkWorkloadView.tsx | 18 +- .../workspaces/work/workAccountingCards.ts | 24 +- .../workspaces/work/workAccountingCensus.ts | 8 +- .../workspaces/work/workAccountingMetrics.ts | 4 +- .../workspaces/work/workAccountingModel.ts | 8 +- dashboard/src/workspaces/work/workApi.test.ts | 10 +- dashboard/src/workspaces/work/workApi.ts | 10 +- .../workspaces/work/workAttemptModel.test.ts | 6 +- .../src/workspaces/work/workAttemptModel.ts | 14 +- dashboard/src/workspaces/work/workChannel.ts | 6 +- .../src/workspaces/work/workDagLayout.ts | 12 +- .../workspaces/work/workGraphModel.test.ts | 4 +- .../src/workspaces/work/workGraphModel.ts | 18 +- .../src/workspaces/work/workLaneModel.ts | 4 +- dashboard/src/workspaces/work/workModel.ts | 12 +- .../work/workTopologyAccounting.test.ts | 22 +- .../workspaces/work/workTopologyAccounting.ts | 2 +- .../workspaces/work/workViewsModel.test.ts | 18 +- .../src/workspaces/work/workViewsModel.ts | 48 +- .../src/workspaces/work/workViewsQueries.ts | 14 +- .../workflows/WorkflowDefinitionDetail.tsx | 8 +- .../workflows/WorkflowLifecycle.tsx | 4 +- .../workspaces/workflows/WorkflowRegistry.tsx | 4 +- .../workflows/WorkflowsPage.dom.test.tsx | 4 +- .../workspaces/workflows/WorkflowsPage.tsx | 6 +- .../workflows/workflowLedger.test.ts | 2 +- .../workspaces/workflows/workflowLedger.ts | 6 +- .../workspaces/workflows/workflowQueries.ts | 2 +- .../workspaces/workflows/workflowRoutes.ts | 6 +- dashboard/stories/audit.ts | 20 +- dashboard/stories/fixtures/data.test.ts | 12 +- dashboard/stories/fixtures/data.ts | 152 +-- dashboard/stories/fixtures/handlers.ts | 10 +- dashboard/stories/live-multiproject.ts | 2 +- dashboard/stories/peek.ts | 4 +- dashboard/stories/qa-chrome.ts | 8 +- dashboard/stories/registry.ts | 4 +- dashboard/stories/topography-audit.ts | 26 +- dashboard/vitest.setup.dom.ts | 8 +- docs/AGENT-MEMORY-INTERCEPTION.md | 72 +- docs/DOMAIN-EXTRACTORS.md | 24 +- docs/LCM-PAYLOAD-GC.md | 68 +- docs/LCM-PAYLOAD-LIFECYCLE.md | 48 +- docs/MCP-extensions.md | 72 +- docs/PLUGIN-VALIDATION.md | 26 +- docs/REBRAND-COMPATIBILITY-POLICY.md | 10 +- docs/RELEASE-AUTOMATION.md | 2 +- docs/RETRIEVAL-QUALITY-EVAL.md | 49 +- docs/ROOT-DERIVED-RECORD-READ-DECISION.md | 8 +- docs/ROOT-LEXICAL-CANDIDATE-READ-DECISION.md | 30 +- docs/RUNTIME-BLOCKING-IO-AUDIT.md | 48 +- docs/RUST-PARSER-MIGRATION.md | 4 +- docs/SERVING-PATH-PERFORMANCE.md | 78 +- docs/TEST-MAP-CALIBRATION-DESIGN.md | 54 +- docs/TEST-MAP-INTERPRETATION.md | 36 +- docs/TRACEDECAY-VS-TOKENSAVIOR.md | 56 +- docs/TREESITTERS-RENAME-CONSTRAINTS.md | 42 +- docs/TRUST-DECAY-SEMANTICS.md | 20 +- docs/USER-GUIDE.md | 62 +- docs/dashboard.md | 4 +- .../brain-renderer-qualification.md | 4 +- .../search-quality-direct-evaluation.md | 4 +- docs/graph-at-rest/README.md | 126 +- docs/memory-evals.md | 34 +- ...2-26-codegraph-rust-implementation-plan.md | 52 +- .../2026-02-26-codegraph-rust-port-design.md | 50 +- ...2-26-multi-language-implementation-plan.md | 24 +- ...026-02-26-multi-language-support-design.md | 62 +- ...2026-07-01-macos-launchd-daemon-support.md | 4 +- ...26-07-04-tool-cli-args-agent-ergonomics.md | 176 +-- docs/plans/tracedecay-v2/00-plan-set-index.md | 14 +- docs/plans/tracedecay-v2/03-capture-crate.md | 2 +- docs/plans/tracedecay-v2/05-query-crate.md | 12 +- .../tracedecay-v2/08-tool-catalog-crate.md | 56 +- .../tracedecay-v2/09-application-crate.md | 2 +- .../tracedecay-v2/11-dashboard-frontend.md | 36 +- .../tracedecay-v2/11a-dashboard-design.md | 36 +- .../11b-structure-visualization.md | 81 +- .../11c-work-workspace-design.md | 36 +- ...research-provenance-and-context-anchors.md | 18 +- ...ction-redaction-and-private-data-safety.md | 6 +- ...ask-plan-graph-and-multi-agent-executor.md | 4 +- .../25-code-intelligence-indexing-crate.md | 14 +- .../26-observability-accounting-and-usage.md | 4 +- .../27-cross-host-agent-plugin-bundles.md | 28 +- ...1-native-fastembed-semantic-code-search.md | 8 +- .../tracedecay-v2/34-workspace-refactoring.md | 8 +- ...n-lsp-gateway-and-universal-diagnostics.md | 2 +- ...e-change-context-and-index-transactions.md | 16 +- ...ack-cycle-pr-review-and-agent-proximity.md | 4 +- ...8-storage-retention-size-and-efficiency.md | 40 +- .../39-embedded-grafeo-graph-database.md | 30 +- ...generation-storage-and-zero-copy-replay.md | 30 +- docs/plans/tracedecay-v2/NEXT.md | 6 +- .../audits/ci-triage-plan-2026-08-14.md | 1014 ++++++++--------- .../audits/lane-staleness-2026-08-13.md | 44 +- .../v2-implementation-audit-2026-08-14.md | 12 +- .../plans/2026-07-31-one-shot-crate-split.md | 12 +- .../plans/2026-08-01-test-support-features.md | 62 +- ...8-03-rust-library-maintenance-reduction.md | 2 +- .../plans/2026-08-08-v2-rc-recovery.md | 2 +- .../2026-08-23-pr663-agent-handoff-prompt.md | 4 +- .../2026-08-23-pr663-performance-recovery.md | 4 +- .../superpowers/plans/v2/pr16-remote-brain.md | 2 +- docs/superpowers/plans/v2/pr18-public-sdks.md | 2 +- docs/superpowers/plans/v2/pr20-performance.md | 4 +- evals/agent_adoption/README.md | 38 +- evals/agent_adoption/grade.py | 8 +- evals/agent_adoption/run.sh | 8 +- evals/agent_adoption/selftest.py | 2 +- evals/hermetic/README.md | 28 +- evals/hermetic/run.sh | 4 +- evals/hermetic/score.py | 2 +- evals/hermetic/scorecard.py | 4 +- evals/memory/run_real_model.py | 12 +- mockups/code-topography/NOTES.md | 16 +- mockups/code-topography/README.md | 14 +- mockups/code-topography/core-sample.html | 26 +- mockups/code-topography/cortex.html | 18 +- mockups/code-topography/lens.html | 28 +- mockups/code-topography/prototype/README.md | 32 +- mockups/code-topography/prototype/dataset.js | 20 +- .../code-topography/prototype/qa-drive.mjs | 16 +- mockups/code-topography/prototype/recorder.js | 10 +- mockups/code-topography/prototype/render.js | 10 +- mockups/code-topography/prototype/sim.js | 18 +- .../code-topography/prototype/sim.test.mjs | 22 +- .../code-topography/prototype/trace-live.html | 18 +- mockups/code-topography/shoot.mjs | 2 +- mockups/code-topography/topography.css | 2 +- mockups/code-topography/topography.js | 8 +- mockups/code-topography/trace.html | 28 +- mockups/structure-viz/README.md | 10 +- mockups/structure-viz/call-chain-transit.html | 24 +- mockups/structure-viz/disagreement-field.html | 22 +- .../structure-viz/notes/call-chain-transit.md | 16 +- .../structure-viz/notes/disagreement-field.md | 26 +- mockups/structure-viz/notes/symbol-anatomy.md | 12 +- mockups/structure-viz/shoot.mjs | 2 +- mockups/structure-viz/symbol-anatomy.html | 22 +- mockups/ui-concept-v2/01-brain/README.md | 2 +- .../01-brain/final/04-project-scoped.md | 2 +- .../03-loom/final/01-follow-loaded-tail.md | 2 +- .../03-loom/final/02-temporal-replay.md | 2 +- .../03-loom/final/03-branching-execution.md | 2 +- .../03-loom/final/04-dense-100-agents.md | 2 +- .../final/05-selected-event-evidence.md | 2 +- .../03-loom/final/06-feedback-continuation.md | 4 +- .../03-loom/final/07-evidence-gaps.md | 4 +- .../03-loom/final/08-work-proximity.md | 2 +- mockups/ui-concept-v2/03-loom/final/README.md | 12 +- .../05-agents/final/01-delegation-topology.md | 2 +- .../ui-concept-v2/05-agents/final/README.md | 2 +- mockups/ui-concept-v2/06-code/README.md | 2 +- .../06-code/final/02-structural-comparison.md | 2 +- mockups/ui-concept-v2/07-knowledge/README.md | 2 +- mockups/ui-concept-v2/08-delivery/README.md | 2 +- .../final/07-honest-partial-unknown.md | 2 +- .../10-decision-to-code-pr743-source.html | 2 +- .../ui-concept-v2/09-automations/README.md | 2 +- .../ui-concept-v2/10-observatory/README.md | 2 +- .../final/01-system-evidence-overview.md | 2 +- .../10-observatory/final/README.md | 2 +- mockups/ui-concept-v2/11-costs/README.md | 2 +- .../final/01-provider-spend-attribution.md | 4 +- mockups/ui-concept-v2/12-settings/README.md | 2 +- .../01-effective-configuration-review.md | 2 +- mockups/ui-concept-v2/IMPLEMENTATION.md | 26 +- mockups/ui-concept-v2/INTERACTION-STATES.md | 10 +- mockups/ui-concept-v2/README.md | 4 +- mockups/ui-concept-v2/app/qa/execution.mjs | 2 +- .../ui-concept-v2/app/qa/loom-workspaces.mjs | 2 +- mockups/ui-concept-v2/app/src/App.tsx | 8 +- .../app/src/agents/AgentsPage.tsx | 6 +- .../ui-concept-v2/app/src/agents/agents.css | 8 +- .../app/src/automations/AutomationsPage.tsx | 2 +- .../app/src/automations/automations.css | 2 +- .../app/src/brain/BrainCanvas.tsx | 6 +- .../ui-concept-v2/app/src/brain/particles.ts | 2 +- .../ui-concept-v2/app/src/brain/repoLayout.ts | 2 +- .../app/src/brain/scopedGraph.ts | 4 +- .../app/src/code/semanticData.ts | 2 +- .../app/src/concept/neuronLab/graphScene.ts | 4 +- .../app/src/concept/neuronLab/interior.ts | 2 +- .../app/src/concept/neuronLab/labUtil.ts | 6 +- .../app/src/concept/neuronLab/voxeloScene.ts | 12 +- .../ui-concept-v2/app/src/costs/CostsPage.tsx | 8 +- mockups/ui-concept-v2/app/src/costs/costs.css | 2 +- .../ui-concept-v2/app/src/data/fixtures.ts | 2 +- .../app/src/delivery/constellation.tsx | 10 +- .../ui-concept-v2/app/src/delivery/data.ts | 4 +- .../app/src/delivery/delivery.css | 2 +- .../ui-concept-v2/app/src/delivery/inbox.tsx | 4 +- .../app/src/delivery/journey.tsx | 18 +- .../ui-concept-v2/app/src/delivery/review.tsx | 14 +- .../ui-concept-v2/app/src/delivery/weave.tsx | 2 +- .../app/src/knowledge/KnowledgePage.tsx | 2 +- .../app/src/knowledge/knowledge.css | 2 +- .../app/src/loom/designScenario.ts | 10 +- .../app/src/observatory/ObservatoryPage.tsx | 18 +- .../ui-concept-v2/app/src/observatory/data.ts | 6 +- .../app/src/observatory/observatory.css | 4 +- .../app/src/sessions/SessionsPage.tsx | 18 +- .../ui-concept-v2/app/src/sessions/model.ts | 2 +- .../app/src/sessions/sessions.css | 2 +- .../app/src/settings/settings.css | 6 +- mockups/ui-concept-v2/app/src/work/model.ts | 6 +- .../app/src/workflows/workflows.css | 2 +- plugin/README-claude.md | 2 +- plugin/README-codex.md | 6 +- plugin/README-cursor.md | 8 +- plugin/README-opencode.md | 2 +- plugin/README.md | 4 +- plugin/commands/compare-branches.md | 2 +- .../references/other-branches.md | 6 +- .../references/types-and-traits.md | 4 +- .../investigating-unexpected-changes/SKILL.md | 2 +- scripts/census_attack_the_premise.py | 4 +- scripts/check-distribution-acceptance.sh | 16 +- scripts/check-distribution-feature-wiring.py | 2 +- scripts/check-packaged-mcp-stdio.py | 2 +- scripts/claude_stock_integration.sh | 4 +- .../drop-prefix-restored-rust-artifacts.sh | 2 +- scripts/efficiency-scorecard.py | 10 +- scripts/hermes_plugin_unit_check.py | 2 +- scripts/hermes_stock_integration.sh | 2 +- scripts/hotpath-rustflags.sh | 2 +- scripts/lib/gate-test.sh | 2 +- scripts/lib/portable_process.py | 2 +- scripts/lib/stock-host.sh | 2 +- scripts/linux-test-partitions.py | 4 +- scripts/mcp-conformance-smoke.sh | 2 +- scripts/mcp_probe/README.md | 2 +- scripts/mcp_probe/probe.py | 10 +- scripts/mcp_probe/tools/c.py | 2 +- scripts/mcp_probe/tools/rust.py | 2 +- scripts/opencode_stock_integration.sh | 2 +- scripts/perf-gate.sh | 24 +- scripts/profile-hotpath-os-counters.sh | 2 +- scripts/prune-superseded-actions-caches.py | 2 +- scripts/worktree-gc.sh | 30 +- sdks/typescript/README.md | 4 +- sdks/typescript/src/operations.ts | 34 +- tests/tool_sweep_suite/runner.py | 4 +- 1742 files changed, 8187 insertions(+), 8562 deletions(-) diff --git a/.claude/skills/interpreting-tracedecay-diagnostics/scripts/diagnose-summary.sh b/.claude/skills/interpreting-tracedecay-diagnostics/scripts/diagnose-summary.sh index bf08d7daac..9ecf0c2779 100755 --- a/.claude/skills/interpreting-tracedecay-diagnostics/scripts/diagnose-summary.sh +++ b/.claude/skills/interpreting-tracedecay-diagnostics/scripts/diagnose-summary.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# diagnose-summary.sh — turn TraceDecay diagnostics into a mapped-owner summary. +# diagnose-summary.sh. Turn TraceDecay diagnostics into a mapped-owner summary. # # Runs the graph-aware diagnostic path this skill prescribes and prints: how many # diagnostics were recognized, which symbol/file owns each failure, the callers @@ -63,7 +63,7 @@ print(f"recognized : {parsed} parsed, {returned} returned ({errs} error, {wa print(f"mapped/unmapped: {mapped} mapped to a symbol, {unmapped} UNMAPPED") if d.get("truncated"): print("note : output truncated (raise --max-diagnostics for more)") if not diags: - print("\nclean — no diagnostics with a resolvable file:line span.") + print("\nclean. No diagnostics with a resolvable file:line span.") # Group by mapped owner so shared root causes cluster. from collections import defaultdict @@ -88,7 +88,7 @@ if by_owner: print(f" - {loc} {tag}") if unmapped_hits: - print("\n## UNMAPPED (parse/file-mapping coverage gap — still real errors)") + print("\n## UNMAPPED (parse/file-mapping coverage gap, still real errors)") for loc, tag in unmapped_hits[:10]: print(f" - {loc} {tag}") print(" -> If these own real code, that is a TraceDecay extractor/mapping gap worth an issue.") diff --git a/.claude/skills/introspecting-tracedecay-usage/scripts/project-analytics.sh b/.claude/skills/introspecting-tracedecay-usage/scripts/project-analytics.sh index 1385fdf200..18beacc02c 100755 --- a/.claude/skills/introspecting-tracedecay-usage/scripts/project-analytics.sh +++ b/.claude/skills/introspecting-tracedecay-usage/scripts/project-analytics.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# project-analytics.sh — TraceDecay usage & fact-store adoption snapshot. +# project-analytics.sh. TraceDecay usage & fact-store adoption snapshot. # # Fills the gaps `tracedecay analytics diagnostics` leaves open: a per-tool MCP # call breakdown, and fact-store *adoption* (how often facts are seen vs. rated). @@ -49,7 +49,7 @@ GLOBAL_DB="$TD_HOME/global.db" q() { sqlite3 -noheader -separator ' ' "$1" "$2" 2>/dev/null; } echo "================================================================" -echo " TraceDecay usage & fact-store adoption — $PROJECT_ID" +echo " TraceDecay usage & fact-store adoption, $PROJECT_ID" echo "================================================================" # --- 1. MCP tool adoption (per-tool breakdown; the CLI only groups by kind). -- @@ -92,7 +92,7 @@ if [ "$FB" -gt 0 ]; then printf ' %-26s %s : 1\n' "seen : feedback ratio:" "$(( SEEN / FB ))" RATE=$("$PY" -c "print(f'{100*$FB/max($RETR,1):.2f}%')") printf ' %-26s %s of retrievals\n' "feedback rate:" "$RATE" - echo " signal: feedback loop is ACTIVE but sparse — confirm trust scores are earned, not just seeded." + echo " signal: feedback loop is ACTIVE but sparse. Confirm trust scores are earned, not only seeded." else echo " seen : feedback ratio: ${SEEN} : 0" echo " >> DEAD FEEDBACK LOOP: facts are seen ${SEEN}x but never rated helpful/unhelpful." @@ -101,10 +101,10 @@ fi # --- 3. Feedback ledger (transport-agnostic: CLI + MCP + automation). --------- echo -echo "## Feedback ledger (memory_v2_feedback_history — all transports)" +echo "## Feedback ledger (memory_v2_feedback_history, all transports)" LEDGER="$(q "$SERVING_DB" "SELECT action, datetime(occurred_at,'unixepoch'), COALESCE(source,'unknown'), substr(COALESCE(note,''),1,60) FROM memory_v2_feedback_history ORDER BY occurred_at, event_id;")" -if [ -n "$LEDGER" ]; then printf '%s\n' "$LEDGER" | sed 's/^/ /'; else echo " (none — no fact has ever received feedback)"; fi +if [ -n "$LEDGER" ]; then printf '%s\n' "$LEDGER" | sed 's/^/ /'; else echo " (none, no fact has ever received feedback)"; fi # --- 4. Read vs write activity (oplog is write-side; retrievals are read-side). echo diff --git a/.claude/skills/self-improving-from-usage-logs/scripts/friction-scan.sh b/.claude/skills/self-improving-from-usage-logs/scripts/friction-scan.sh index 52710d9bf9..0901f6cab1 100755 --- a/.claude/skills/self-improving-from-usage-logs/scripts/friction-scan.sh +++ b/.claude/skills/self-improving-from-usage-logs/scripts/friction-scan.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# friction-scan.sh — mine TraceDecay usage logs for friction the self-improving +# friction-scan.sh. Mine TraceDecay usage logs for friction the self-improving # loop should act on: tool error rates, low-adoption tools, dead feedback loops, # and the evidence sessions behind them. Maps directly onto this skill's # "Opportunity Ranking" table. @@ -51,7 +51,7 @@ WHERE="event_kind='mcp_tool_call'" SCOPE=$([ "$ALL" -eq 1 ] && echo "ALL PROJECTS" || echo "$PROJECT_ID") echo "================================================================" -echo " TraceDecay friction scan — $SCOPE" +echo " TraceDecay friction scan, $SCOPE" echo "================================================================" [ -f "$GLOBAL_DB" ] || { echo "(global analytics db not found at $GLOBAL_DB)"; } @@ -77,8 +77,8 @@ q "$GLOBAL_DB" "SELECT tool_name, COUNT(*) c, SUM(outcome='error') e # --- 3. Low-adoption tools: called, but rarely (discovery/trigger gaps). ------ echo -echo "## Least-invoked tools (bottom 12 of those ever called) — candidate discovery gaps" -q "$GLOBAL_DB" "SELECT ' '||tool_name||' — '||COUNT(*)||' call(s)' +echo "## Least-invoked tools (bottom 12 of those ever called), candidate discovery gaps" +q "$GLOBAL_DB" "SELECT ' '||tool_name||', '||COUNT(*)||' call(s)' FROM analytics_events WHERE $WHERE GROUP BY tool_name ORDER BY COUNT(*) ASC LIMIT 12;" echo " (a tool the agents know exists but almost never call is a trigger-text or discoverability gap)" @@ -101,8 +101,8 @@ fi # --- 5. Evidence: sessions carrying the most tool errors. -------------------- echo -echo "## Evidence — sessions with the most tool errors (cite these)" -q "$GLOBAL_DB" "SELECT ' '||COALESCE(NULLIF(session_id,''),'(no session)')||' — '||COUNT(*)||' errors, provider='||provider +echo "## Evidence. Sessions with the most tool errors (cite these)" +q "$GLOBAL_DB" "SELECT ' '||COALESCE(NULLIF(session_id,''),'(no session)')||', '||COUNT(*)||' errors, provider='||provider FROM analytics_events WHERE $WHERE AND outcome='error' GROUP BY session_id, provider ORDER BY COUNT(*) DESC LIMIT 8;" echo diff --git a/.claude/skills/using-hotpath/SKILL.md b/.claude/skills/using-hotpath/SKILL.md index 059a8109cc..044fe6b64d 100644 --- a/.claude/skills/using-hotpath/SKILL.md +++ b/.claude/skills/using-hotpath/SKILL.md @@ -15,8 +15,8 @@ A tripped deadline, admission refusal, memory budget, or backoff ceiling is a measurement arriving through a policy surface. Never raise, remove, or env-override the limit as the fix. Use the lanes below to decompose where the time or memory actually goes, then compare against what the operation should -cost for its inputs. Mis-sized work — an N+1 query storm, an unbatched writer, -a serial phase that should use every core, an inlined mega-future — is the +cost for its inputs. Mis-sized work, an N+1 query storm, an unbatched writer, +a serial phase that should use every core, an inlined mega-future, is the defect; fix it and keep the limit. Change the budget only when the measured cost is genuinely irreducible, in its own commit, with the measurement attached. Overrides that keep an investigation moving are scaffolding: label @@ -55,7 +55,7 @@ does not need the facility catalog. - Record failed/cancelled work too; success-only counters hide the waste being diagnosed. - Use RAII for active/queued/running gauges so cancellation, panic, abort, and shutdown cannot leak them. - Do not wrap tiny getters or inner-loop nodes without measured need. Enabled probes still have event/drain overhead even when timing is sampled out. -- Keep the observability layers distinct. `tracing` events are the always-compiled operator log surface: they cost callsite checks even unsubscribed, are invisible in tests without a subscriber, and typed error mappings may collapse their messages. Hotpath macros are the compile-to-no-op measurement surface. A warn and a gauge on one path serve different consumers — neither replaces the other, and harvesting first-party logs into metrics couples placement decisions to log-field schemas. `eprintln!` is investigation scaffolding; it never merges. +- Keep the observability layers distinct. `tracing` events are the always-compiled operator log surface: they cost callsite checks even unsubscribed, are invisible in tests without a subscriber, and typed error mappings may collapse their messages. Hotpath macros are the compile-to-no-op measurement surface. A warn and a gauge on one path serve different consumers. Neither replaces the other, and harvesting first-party logs into metrics couples placement decisions to log-field schemas. `eprintln!` is investigation scaffolding; it never merges. - Treat Hotpath 0.24 as flat aggregation: it has caller attribution for selected resources, but no parent call tree and no exclusive wall-time subtraction. - For parallel extraction/indexing, report one outer sweep wall span plus per-worker service demand, queue depth, effective worker count, memory reservation, and limiting reason. diff --git a/.claude/skills/using-hotpath/references/hotpath-0.24.md b/.claude/skills/using-hotpath/references/hotpath-0.24.md index 1f20be89f7..a7cb31195e 100644 --- a/.claude/skills/using-hotpath/references/hotpath-0.24.md +++ b/.claude/skills/using-hotpath/references/hotpath-0.24.md @@ -181,7 +181,7 @@ Recommended order: - Async `#[measure]` bridges allocation attribution per poll. A synchronous `measure_block!` spanning `.await` can migrate threads; wall time remains useful but allocation attribution may be unavailable. - Axum/HTTP client durations end at response headers. Measure streamed body/download/decode separately. - Direct rusqlite emits no automatic SQL report. Use TraceDecay's writer/reader/transaction/checkpoint spans and truthful work gauges. -- The `sql` report and `sql_logs` tools are fed only by the crate's third-party front-ends: the `sqlx` feature's `sqlx_tracing_layer()` — a `tracing_subscriber` layer that harvests sqlx's `sqlx::query` completed-query events (sqlx-measured `elapsed`, statement text normalized into parameter-insensitive buckets, attribution to the innermost measured frame via the caller stack) — the `toasty` feature's equivalent layer, and the `diesel` feature's `instrument_diesel_sql`. The layer never times anything itself and holds every other target at `Interest::never`, but a *global* `EnvFilter` runs before per-layer filters and can suppress `sqlx::query` for the whole stack: attach `EnvFilter` per layer. Bridges fit third-party emitters that already pay tracing's cost; first-party code keeps compile-out macros. +- The `sql` report and `sql_logs` tools are fed only by the crate's third-party front-ends: the `sqlx` feature's `sqlx_tracing_layer()`, a `tracing_subscriber` layer that harvests sqlx's `sqlx::query` completed-query events (sqlx-measured `elapsed`, statement text normalized into parameter-insensitive buckets, attribution to the innermost measured frame via the caller stack), the `toasty` feature's equivalent layer, and the `diesel` feature's `instrument_diesel_sql`. The layer never times anything itself and holds every other target at `Interest::never`, but a *global* `EnvFilter` runs before per-layer filters and can suppress `sqlx::query` for the whole stack: attach `EnvFilter` per layer. Bridges fit third-party emitters that already pay tracing's cost; first-party code keeps compile-out macros. - I/O wrapper timing starts on first poll and completes on Ready. Cancellation while Pending is not detected; do not treat it as a full future-lifecycle replacement. - Dynamic HTTP paths, SQL identifiers/comments, debug values, and per-instance `iter = true` can leak or explode cardinality. Keep production keys static and bounded. diff --git a/.claude/skills/using-hotpath/references/instrumentation-facilities.md b/.claude/skills/using-hotpath/references/instrumentation-facilities.md index 7b265ac5ae..d8c99864dc 100644 --- a/.claude/skills/using-hotpath/references/instrumentation-facilities.md +++ b/.claude/skills/using-hotpath/references/instrumentation-facilities.md @@ -1,4 +1,4 @@ -# Hotpath Instrumentation Facilities +# Hotpath instrumentation facilities - Synchronous function or bounded phase: `#[hotpath::measure]` or `hotpath::measure_block!("static.label", expression)`. - Bulk instrumentation of a suspect area: `#[hotpath::measure_all]` on an inline `mod` or `impl` block applies `measure` to every function inside; exclude trivial or noisy functions with `#[hotpath::skip]`. It cannot be a file-level inner attribute, and trait-impl methods get timing/allocation but not CPU-sample attribution. Use it to blanket one investigation target, not the codebase; trim it back per the instrumentation rules before merge. @@ -12,5 +12,5 @@ - Tokio: register the already-built runtime once with `hotpath::tokio_runtime!(runtime.handle())`. - Counts/current state: static `hotpath::gauge!` keys; use additive lifecycle guards for shared state and clean them up in `Drop`. - Debug values: avoid in production unless values are bounded and non-sensitive. -- Direct rusqlite: manual phase/queue/transaction instrumentation; Hotpath 0.24 has no rusqlite adapter. Its `sql` report is fed only by third-party front-ends — `sqlx_tracing_layer()` / `toasty_tracing_layer()` are `tracing_subscriber` layers that harvest those ORMs' completed-query tracing events (emitter-measured elapsed, statements normalized into parameter-insensitive buckets, attributed to the innermost measured frame), and diesel hooks its own instrumentation trait. Use a tracing bridge only for a third-party emitter that already pays tracing's cost; first-party code keeps compile-out macros. Each bridge needs its cargo feature, and a global `EnvFilter` can suppress `sqlx::query` events for the whole stack — attach filters per layer. +- Direct rusqlite: manual phase/queue/transaction instrumentation; Hotpath 0.24 has no rusqlite adapter. Its `sql` report is fed only by third-party front-ends, `sqlx_tracing_layer()` / `toasty_tracing_layer()` are `tracing_subscriber` layers that harvest those ORMs' completed-query tracing events (emitter-measured elapsed, statements normalized into parameter-insensitive buckets, attributed to the innermost measured frame), and diesel hooks its own instrumentation trait. Use a tracing bridge only for a third-party emitter that already pays tracing's cost; first-party code keeps compile-out macros. Each bridge needs its cargo feature, and a global `EnvFilter` can suppress `sqlx::query` events for the whole stack. Attach filters per layer. diff --git a/.codex/skills/interpreting-tracedecay-diagnostics/scripts/diagnose-summary.sh b/.codex/skills/interpreting-tracedecay-diagnostics/scripts/diagnose-summary.sh index bf08d7daac..9ecf0c2779 100755 --- a/.codex/skills/interpreting-tracedecay-diagnostics/scripts/diagnose-summary.sh +++ b/.codex/skills/interpreting-tracedecay-diagnostics/scripts/diagnose-summary.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# diagnose-summary.sh — turn TraceDecay diagnostics into a mapped-owner summary. +# diagnose-summary.sh. Turn TraceDecay diagnostics into a mapped-owner summary. # # Runs the graph-aware diagnostic path this skill prescribes and prints: how many # diagnostics were recognized, which symbol/file owns each failure, the callers @@ -63,7 +63,7 @@ print(f"recognized : {parsed} parsed, {returned} returned ({errs} error, {wa print(f"mapped/unmapped: {mapped} mapped to a symbol, {unmapped} UNMAPPED") if d.get("truncated"): print("note : output truncated (raise --max-diagnostics for more)") if not diags: - print("\nclean — no diagnostics with a resolvable file:line span.") + print("\nclean. No diagnostics with a resolvable file:line span.") # Group by mapped owner so shared root causes cluster. from collections import defaultdict @@ -88,7 +88,7 @@ if by_owner: print(f" - {loc} {tag}") if unmapped_hits: - print("\n## UNMAPPED (parse/file-mapping coverage gap — still real errors)") + print("\n## UNMAPPED (parse/file-mapping coverage gap, still real errors)") for loc, tag in unmapped_hits[:10]: print(f" - {loc} {tag}") print(" -> If these own real code, that is a TraceDecay extractor/mapping gap worth an issue.") diff --git a/.codex/skills/introspecting-tracedecay-usage/scripts/project-analytics.sh b/.codex/skills/introspecting-tracedecay-usage/scripts/project-analytics.sh index 1385fdf200..18beacc02c 100755 --- a/.codex/skills/introspecting-tracedecay-usage/scripts/project-analytics.sh +++ b/.codex/skills/introspecting-tracedecay-usage/scripts/project-analytics.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# project-analytics.sh — TraceDecay usage & fact-store adoption snapshot. +# project-analytics.sh. TraceDecay usage & fact-store adoption snapshot. # # Fills the gaps `tracedecay analytics diagnostics` leaves open: a per-tool MCP # call breakdown, and fact-store *adoption* (how often facts are seen vs. rated). @@ -49,7 +49,7 @@ GLOBAL_DB="$TD_HOME/global.db" q() { sqlite3 -noheader -separator ' ' "$1" "$2" 2>/dev/null; } echo "================================================================" -echo " TraceDecay usage & fact-store adoption — $PROJECT_ID" +echo " TraceDecay usage & fact-store adoption, $PROJECT_ID" echo "================================================================" # --- 1. MCP tool adoption (per-tool breakdown; the CLI only groups by kind). -- @@ -92,7 +92,7 @@ if [ "$FB" -gt 0 ]; then printf ' %-26s %s : 1\n' "seen : feedback ratio:" "$(( SEEN / FB ))" RATE=$("$PY" -c "print(f'{100*$FB/max($RETR,1):.2f}%')") printf ' %-26s %s of retrievals\n' "feedback rate:" "$RATE" - echo " signal: feedback loop is ACTIVE but sparse — confirm trust scores are earned, not just seeded." + echo " signal: feedback loop is ACTIVE but sparse. Confirm trust scores are earned, not only seeded." else echo " seen : feedback ratio: ${SEEN} : 0" echo " >> DEAD FEEDBACK LOOP: facts are seen ${SEEN}x but never rated helpful/unhelpful." @@ -101,10 +101,10 @@ fi # --- 3. Feedback ledger (transport-agnostic: CLI + MCP + automation). --------- echo -echo "## Feedback ledger (memory_v2_feedback_history — all transports)" +echo "## Feedback ledger (memory_v2_feedback_history, all transports)" LEDGER="$(q "$SERVING_DB" "SELECT action, datetime(occurred_at,'unixepoch'), COALESCE(source,'unknown'), substr(COALESCE(note,''),1,60) FROM memory_v2_feedback_history ORDER BY occurred_at, event_id;")" -if [ -n "$LEDGER" ]; then printf '%s\n' "$LEDGER" | sed 's/^/ /'; else echo " (none — no fact has ever received feedback)"; fi +if [ -n "$LEDGER" ]; then printf '%s\n' "$LEDGER" | sed 's/^/ /'; else echo " (none, no fact has ever received feedback)"; fi # --- 4. Read vs write activity (oplog is write-side; retrievals are read-side). echo diff --git a/.codex/skills/introspecting-tracedecay-usage/scripts/project-analytics.test.sh b/.codex/skills/introspecting-tracedecay-usage/scripts/project-analytics.test.sh index 9b6ff3b9bd..439e76a636 100755 --- a/.codex/skills/introspecting-tracedecay-usage/scripts/project-analytics.test.sh +++ b/.codex/skills/introspecting-tracedecay-usage/scripts/project-analytics.test.sh @@ -90,7 +90,7 @@ for scope in project all; do PATH="$FAKE_BIN:$PATH" SERVING_DB="$SERVING_DB" TD_EXPECT_SCOPE="$scope" "$helper" "${args[@]}" } 2>&1)" - assert_contains "$output" "TraceDecay usage & fact-store adoption — proj_current" + assert_contains "$output" "TraceDecay usage & fact-store adoption, proj_current" assert_contains "$output" "serving store: graph.db" assert_contains "$output" "total mcp_tool_call events: 3" assert_contains "$output" "facts stored: 2" diff --git a/.codex/skills/self-improving-from-usage-logs/scripts/friction-scan.sh b/.codex/skills/self-improving-from-usage-logs/scripts/friction-scan.sh index 52710d9bf9..0901f6cab1 100755 --- a/.codex/skills/self-improving-from-usage-logs/scripts/friction-scan.sh +++ b/.codex/skills/self-improving-from-usage-logs/scripts/friction-scan.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# friction-scan.sh — mine TraceDecay usage logs for friction the self-improving +# friction-scan.sh. Mine TraceDecay usage logs for friction the self-improving # loop should act on: tool error rates, low-adoption tools, dead feedback loops, # and the evidence sessions behind them. Maps directly onto this skill's # "Opportunity Ranking" table. @@ -51,7 +51,7 @@ WHERE="event_kind='mcp_tool_call'" SCOPE=$([ "$ALL" -eq 1 ] && echo "ALL PROJECTS" || echo "$PROJECT_ID") echo "================================================================" -echo " TraceDecay friction scan — $SCOPE" +echo " TraceDecay friction scan, $SCOPE" echo "================================================================" [ -f "$GLOBAL_DB" ] || { echo "(global analytics db not found at $GLOBAL_DB)"; } @@ -77,8 +77,8 @@ q "$GLOBAL_DB" "SELECT tool_name, COUNT(*) c, SUM(outcome='error') e # --- 3. Low-adoption tools: called, but rarely (discovery/trigger gaps). ------ echo -echo "## Least-invoked tools (bottom 12 of those ever called) — candidate discovery gaps" -q "$GLOBAL_DB" "SELECT ' '||tool_name||' — '||COUNT(*)||' call(s)' +echo "## Least-invoked tools (bottom 12 of those ever called), candidate discovery gaps" +q "$GLOBAL_DB" "SELECT ' '||tool_name||', '||COUNT(*)||' call(s)' FROM analytics_events WHERE $WHERE GROUP BY tool_name ORDER BY COUNT(*) ASC LIMIT 12;" echo " (a tool the agents know exists but almost never call is a trigger-text or discoverability gap)" @@ -101,8 +101,8 @@ fi # --- 5. Evidence: sessions carrying the most tool errors. -------------------- echo -echo "## Evidence — sessions with the most tool errors (cite these)" -q "$GLOBAL_DB" "SELECT ' '||COALESCE(NULLIF(session_id,''),'(no session)')||' — '||COUNT(*)||' errors, provider='||provider +echo "## Evidence. Sessions with the most tool errors (cite these)" +q "$GLOBAL_DB" "SELECT ' '||COALESCE(NULLIF(session_id,''),'(no session)')||', '||COUNT(*)||' errors, provider='||provider FROM analytics_events WHERE $WHERE AND outcome='error' GROUP BY session_id, provider ORDER BY COUNT(*) DESC LIMIT 8;" echo diff --git a/.codex/skills/self-improving-from-usage-logs/scripts/friction-scan.test.sh b/.codex/skills/self-improving-from-usage-logs/scripts/friction-scan.test.sh index 85fab2f89a..2d42d00a76 100755 --- a/.codex/skills/self-improving-from-usage-logs/scripts/friction-scan.test.sh +++ b/.codex/skills/self-improving-from-usage-logs/scripts/friction-scan.test.sh @@ -49,9 +49,9 @@ case "$query" in ;; *"COUNT(*) FROM analytics_events"*) printf '4\n' ;; *"GROUP BY tool_name HAVING"*) printf 'tracedecay_status|10|2\n' ;; - *"' '||tool_name"*) printf ' tracedecay_status — 10 call(s)\n' ;; + *"' '||tool_name"*) printf ' tracedecay_status, 10 call(s)\n' ;; *"FROM memory_v2_current_facts"*) printf '2 5 2 1 1\n' ;; - *"GROUP BY session_id, provider"*) printf ' session-1 — 2 errors, provider=codex\n' ;; + *"GROUP BY session_id, provider"*) printf ' session-1, 2 errors, provider=codex\n' ;; *) printf 'unexpected query: %s\n' "$query" >&2 exit 90 @@ -71,7 +71,7 @@ for scope in project all; do assert_contains "$output" "TraceDecay friction scan" assert_contains "$output" "hook events: 8 tracedecay tool calls: 4" assert_contains "$output" "memory_v2_current_facts" - assert_contains "$output" "session-1 — 2 errors, provider=codex" + assert_contains "$output" "session-1, 2 errors, provider=codex" done done diff --git a/.codex/skills/using-hotpath/SKILL.md b/.codex/skills/using-hotpath/SKILL.md index 059a8109cc..044fe6b64d 100644 --- a/.codex/skills/using-hotpath/SKILL.md +++ b/.codex/skills/using-hotpath/SKILL.md @@ -15,8 +15,8 @@ A tripped deadline, admission refusal, memory budget, or backoff ceiling is a measurement arriving through a policy surface. Never raise, remove, or env-override the limit as the fix. Use the lanes below to decompose where the time or memory actually goes, then compare against what the operation should -cost for its inputs. Mis-sized work — an N+1 query storm, an unbatched writer, -a serial phase that should use every core, an inlined mega-future — is the +cost for its inputs. Mis-sized work, an N+1 query storm, an unbatched writer, +a serial phase that should use every core, an inlined mega-future, is the defect; fix it and keep the limit. Change the budget only when the measured cost is genuinely irreducible, in its own commit, with the measurement attached. Overrides that keep an investigation moving are scaffolding: label @@ -55,7 +55,7 @@ does not need the facility catalog. - Record failed/cancelled work too; success-only counters hide the waste being diagnosed. - Use RAII for active/queued/running gauges so cancellation, panic, abort, and shutdown cannot leak them. - Do not wrap tiny getters or inner-loop nodes without measured need. Enabled probes still have event/drain overhead even when timing is sampled out. -- Keep the observability layers distinct. `tracing` events are the always-compiled operator log surface: they cost callsite checks even unsubscribed, are invisible in tests without a subscriber, and typed error mappings may collapse their messages. Hotpath macros are the compile-to-no-op measurement surface. A warn and a gauge on one path serve different consumers — neither replaces the other, and harvesting first-party logs into metrics couples placement decisions to log-field schemas. `eprintln!` is investigation scaffolding; it never merges. +- Keep the observability layers distinct. `tracing` events are the always-compiled operator log surface: they cost callsite checks even unsubscribed, are invisible in tests without a subscriber, and typed error mappings may collapse their messages. Hotpath macros are the compile-to-no-op measurement surface. A warn and a gauge on one path serve different consumers. Neither replaces the other, and harvesting first-party logs into metrics couples placement decisions to log-field schemas. `eprintln!` is investigation scaffolding; it never merges. - Treat Hotpath 0.24 as flat aggregation: it has caller attribution for selected resources, but no parent call tree and no exclusive wall-time subtraction. - For parallel extraction/indexing, report one outer sweep wall span plus per-worker service demand, queue depth, effective worker count, memory reservation, and limiting reason. diff --git a/.codex/skills/using-hotpath/references/hotpath-0.24.md b/.codex/skills/using-hotpath/references/hotpath-0.24.md index 1f20be89f7..a7cb31195e 100644 --- a/.codex/skills/using-hotpath/references/hotpath-0.24.md +++ b/.codex/skills/using-hotpath/references/hotpath-0.24.md @@ -181,7 +181,7 @@ Recommended order: - Async `#[measure]` bridges allocation attribution per poll. A synchronous `measure_block!` spanning `.await` can migrate threads; wall time remains useful but allocation attribution may be unavailable. - Axum/HTTP client durations end at response headers. Measure streamed body/download/decode separately. - Direct rusqlite emits no automatic SQL report. Use TraceDecay's writer/reader/transaction/checkpoint spans and truthful work gauges. -- The `sql` report and `sql_logs` tools are fed only by the crate's third-party front-ends: the `sqlx` feature's `sqlx_tracing_layer()` — a `tracing_subscriber` layer that harvests sqlx's `sqlx::query` completed-query events (sqlx-measured `elapsed`, statement text normalized into parameter-insensitive buckets, attribution to the innermost measured frame via the caller stack) — the `toasty` feature's equivalent layer, and the `diesel` feature's `instrument_diesel_sql`. The layer never times anything itself and holds every other target at `Interest::never`, but a *global* `EnvFilter` runs before per-layer filters and can suppress `sqlx::query` for the whole stack: attach `EnvFilter` per layer. Bridges fit third-party emitters that already pay tracing's cost; first-party code keeps compile-out macros. +- The `sql` report and `sql_logs` tools are fed only by the crate's third-party front-ends: the `sqlx` feature's `sqlx_tracing_layer()`, a `tracing_subscriber` layer that harvests sqlx's `sqlx::query` completed-query events (sqlx-measured `elapsed`, statement text normalized into parameter-insensitive buckets, attribution to the innermost measured frame via the caller stack), the `toasty` feature's equivalent layer, and the `diesel` feature's `instrument_diesel_sql`. The layer never times anything itself and holds every other target at `Interest::never`, but a *global* `EnvFilter` runs before per-layer filters and can suppress `sqlx::query` for the whole stack: attach `EnvFilter` per layer. Bridges fit third-party emitters that already pay tracing's cost; first-party code keeps compile-out macros. - I/O wrapper timing starts on first poll and completes on Ready. Cancellation while Pending is not detected; do not treat it as a full future-lifecycle replacement. - Dynamic HTTP paths, SQL identifiers/comments, debug values, and per-instance `iter = true` can leak or explode cardinality. Keep production keys static and bounded. diff --git a/.codex/skills/using-hotpath/references/instrumentation-facilities.md b/.codex/skills/using-hotpath/references/instrumentation-facilities.md index 7b265ac5ae..d8c99864dc 100644 --- a/.codex/skills/using-hotpath/references/instrumentation-facilities.md +++ b/.codex/skills/using-hotpath/references/instrumentation-facilities.md @@ -1,4 +1,4 @@ -# Hotpath Instrumentation Facilities +# Hotpath instrumentation facilities - Synchronous function or bounded phase: `#[hotpath::measure]` or `hotpath::measure_block!("static.label", expression)`. - Bulk instrumentation of a suspect area: `#[hotpath::measure_all]` on an inline `mod` or `impl` block applies `measure` to every function inside; exclude trivial or noisy functions with `#[hotpath::skip]`. It cannot be a file-level inner attribute, and trait-impl methods get timing/allocation but not CPU-sample attribution. Use it to blanket one investigation target, not the codebase; trim it back per the instrumentation rules before merge. @@ -12,5 +12,5 @@ - Tokio: register the already-built runtime once with `hotpath::tokio_runtime!(runtime.handle())`. - Counts/current state: static `hotpath::gauge!` keys; use additive lifecycle guards for shared state and clean them up in `Drop`. - Debug values: avoid in production unless values are bounded and non-sensitive. -- Direct rusqlite: manual phase/queue/transaction instrumentation; Hotpath 0.24 has no rusqlite adapter. Its `sql` report is fed only by third-party front-ends — `sqlx_tracing_layer()` / `toasty_tracing_layer()` are `tracing_subscriber` layers that harvest those ORMs' completed-query tracing events (emitter-measured elapsed, statements normalized into parameter-insensitive buckets, attributed to the innermost measured frame), and diesel hooks its own instrumentation trait. Use a tracing bridge only for a third-party emitter that already pays tracing's cost; first-party code keeps compile-out macros. Each bridge needs its cargo feature, and a global `EnvFilter` can suppress `sqlx::query` events for the whole stack — attach filters per layer. +- Direct rusqlite: manual phase/queue/transaction instrumentation; Hotpath 0.24 has no rusqlite adapter. Its `sql` report is fed only by third-party front-ends, `sqlx_tracing_layer()` / `toasty_tracing_layer()` are `tracing_subscriber` layers that harvest those ORMs' completed-query tracing events (emitter-measured elapsed, statements normalized into parameter-insensitive buckets, attributed to the innermost measured frame), and diesel hooks its own instrumentation trait. Use a tracing bridge only for a third-party emitter that already pays tracing's cost; first-party code keeps compile-out macros. Each bridge needs its cargo feature, and a global `EnvFilter` can suppress `sqlx::query` events for the whole stack. Attach filters per layer. diff --git a/.github/workflows/hawk.yml b/.github/workflows/hawk.yml index f5f3f73765..19a2a52246 100644 --- a/.github/workflows/hawk.yml +++ b/.github/workflows/hawk.yml @@ -1,4 +1,4 @@ -# Hawk visibility lint — explicit optional channel, not PR tip evidence. +# Hawk visibility lint. Explicit optional channel, not PR tip evidence. # # Retrigger note: keep this workflow off pull_request CI until Hawk is clean. # @@ -11,7 +11,7 @@ name: Hawk on: schedule: - # Weekly Monday 06:00 UTC — visibility only; not a merge gate. + # Weekly Monday 06:00 UTC. Visibility only; not a merge gate. - cron: "0 6 * * 1" workflow_dispatch: diff --git a/.github/workflows/hotpath-coverage.yml b/.github/workflows/hotpath-coverage.yml index a30819c354..a6999adff4 100644 --- a/.github/workflows/hotpath-coverage.yml +++ b/.github/workflows/hotpath-coverage.yml @@ -1,15 +1,15 @@ # Coverage lanes for the hotpath instrumentation already present in two # slices: -# * storage — tracedecay-rusqlite-runtime, tracedecay-global-db +# * storage, tracedecay-rusqlite-runtime, tracedecay-global-db # (`slice-tests`) -# * sessions/lcm/capture/temporal-query — tracedecay-sessions, +# * sessions/lcm/capture/temporal-query, tracedecay-sessions, # tracedecay-session-memory, tracedecay-session-runtime, # tracedecay-session-temporal-store, tracedecay-lcm, tracedecay-capture, # tracedecay-temporal-query (`sessions-slice-tests`) # # Each job runs two lanes of the same test set: -# 1. feature off — every `#[hotpath::measure]` macro must stay a no-op; -# 2. `--features /hotpath` — the `hotpath_coverage` tests wrap a +# 1. feature off. Every `#[hotpath::measure]` macro must stay a no-op; +# 2. `--features /hotpath`. The `hotpath_coverage` tests wrap a # real workload in a HotpathGuard and assert the JSON report carries # the crates' existing measure labels, so the lane fails if the # measures stop firing. diff --git a/.github/workflows/pr-run-cleanup.yml b/.github/workflows/pr-run-cleanup.yml index 79f8107044..81883e8146 100644 --- a/.github/workflows/pr-run-cleanup.yml +++ b/.github/workflows/pr-run-cleanup.yml @@ -5,7 +5,7 @@ # branch, and the Rust lanes restore by key prefix: rust-cache picks the # newest `v0-rust---` entry. Everything older on # `refs/pull/N/merge` is unreachable, yet it counts against the repository's -# 10 GB cache budget until GitHub's least-recently-used eviction removes it — +# 10 GB cache budget until GitHub's least-recently-used eviction removes it, # and LRU does not know which entries are dead. Measured 2026-09-08 (PR 707, # run 34231734416, 2.5 hours after the previous push's run): CI alone saved # 9.6 GB per push, so the `ci-dev`, `ci-clippy-full`, optional Hawk, and Windows @@ -18,7 +18,7 @@ # runner queue and their concurrency-group slots, so an afternoon of merges # leaves dozens of runs that can never be acted on ahead of the integration # branch's own run. The same event drops all of the pull request's Actions -# caches, which nothing can restore any more — measured 2026-09-07: 4.6 GB of +# caches, which nothing can restore any more. Measured 2026-09-07: 4.6 GB of # the budget held entries from seven merged child PRs while the Linux test # lane's cache had been evicted, so every one of its runs was a cold build. name: Pull request run hygiene diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a3e2a504b4..9d5f65641d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -915,7 +915,7 @@ jobs: # Authentication is tokenless npm trusted publishing: the reviewed npm # CLI (>= 11.5.1) exchanges this job's GitHub OIDC token itself, and npm # attaches provenance automatically. No token, registry-url, or .npmrc - # auth may exist here — a configured token would shadow the OIDC path. + # auth may exist here. A configured token would shadow the OIDC path. - name: Publish the exact conformance-tested tarball with reviewed npm working-directory: artifact run: | diff --git a/AGENTS.md b/AGENTS.md index 2ec7fd6918..d530666048 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ agent hosts through MCP, hooks, LSP, and an embedded dashboard. Deliver a fully integrated final-V2 product through real production journeys, truthful typed states, maintainable crate/module boundaries, and direct -behavioral evidence—not PR choreography, gate scaffolding, or code that merely +behavioral evidence, not PR choreography, gates built only to be checked, or code that merely compiles. ## Task completion @@ -38,30 +38,30 @@ unauthorized external action after completing independent, authorized work. ## Layout -- The repository root is a **virtual workspace** — it has no package of its +- The repository root is a **virtual workspace**. It has no package of its own. Every crate lives under `crates/`. -- `crates/tracedecay/` — the composition-root library (daemon, MCP tools, +- `crates/tracedecay/`, the composition-root library (daemon, MCP tools, global DB, sessions, code index, application services). Its integration suites are `crates/tracedecay/tests/`, and the ones that use the fixture surface in `tests/common/` require `test-helpers`. Check the selected test target's `required-features` in `Cargo.toml`; `mcp_suite` also requires `test-transport`. -- `crates/tracedecay-cli/` — the package that produces the shipped +- `crates/tracedecay-cli/`, the package that produces the shipped `tracedecay` binary. -- `crates/` — the remaining workspace member crates (`tracedecay-api`, +- `crates/`, the remaining workspace member crates (`tracedecay-api`, `-application`, `-contracts`, `-domain`, `-store`, `-hooks`, `-policy`, `-tool-catalog`, rusqlite parity/runtime crates). -- `dashboard/` — the single embedded dashboard (React + rsbuild + vitest). - `dashboard/src/contracts/` is generated from Rust schemas via schemars — - never hand-edit it; regenerate with the `contracts:generate` script and +- `dashboard/`, the single embedded dashboard (React + rsbuild + vitest). + `dashboard/src/contracts/` is generated from Rust schemas via schemars. + Never hand-edit it; regenerate with the `contracts:generate` script and verify with `contracts:check`. -- `plugin/` — host bundles (Claude, Codex, Cursor, Kimi, opencode). -- `tests/` — shared fixtures, distribution suites, and shell/Python gates that +- `plugin/`, host bundles (Claude, Codex, Cursor, Kimi, opencode). +- `tests/`, shared fixtures, distribution suites, and shell/Python gates that no single crate owns; crate-level integration suites and criterion benches live under that crate's own `tests/` and `benches/`. -- `benchmark_data/` — benchmark fixtures, harnesses, and provenance; - `evals/` — memory, hermetic, and agent-adoption evals; `docs/` — plans and guides. -- `scripts/` — CI/dev gates (commit-msg check, bundle checks, release drift). +- `benchmark_data/`, benchmark fixtures, harnesses, and provenance; + `evals/`, memory, hermetic, and agent-adoption evals; `docs/`, plans and guides. +- `scripts/`. CI/dev gates (commit-msg check, bundle checks, release drift). ## Build & test @@ -70,7 +70,7 @@ unauthorized external action after completing independent, authorized work. - Dashboard: `npm run build` (rsbuild), `npm run typecheck` (`tsc --noEmit`), `npm test` (vitest) from `dashboard/`. - libtest `--exact` requires the full module path and exits 0 when a filter - matches nothing — a vacuous "0 passed" green. For name-filtered runs prefer + matches nothing. That is a vacuous "0 passed" green. For name-filtered runs prefer the ad-hoc anti-vacuity helper `scripts/require-exact-test.sh`; it is not a reason to ossify CI or test names. Otherwise pass the full path (`module::path::test_name`) and confirm the reported count is non-zero before @@ -97,7 +97,7 @@ unauthorized external action after completing independent, authorized work. `.githooks/commit-msg` hook runs it locally via `scripts/install-git-hooks.sh`). - Integration branch is `master` (GitHub: ScriptedAlchemy/tracedecay); CI - lives in `.github/workflows` (hidden — search with `rg --hidden`). + lives in `.github/workflows` (hidden, search with `rg --hidden`). - `.github/`, `.githooks/`, and nested `AGENTS.md` files may carry more specific guidance; the deeper file wins. @@ -111,7 +111,7 @@ unauthorized external action after completing independent, authorized work. overrides before merge. Keep the observability layers distinct: `tracing` events are the always-compiled operator log surface, Hotpath macros the compile-to-no-op measurement surface (tracing bridges exist only - for third-party emitters like sqlx — see the skill), and `eprintln!` + for third-party emitters like sqlx, see the skill), and `eprintln!` scaffolds never merge. - Reuse canonical TraceDecay authorities and maintained libraries first. Custom parsers, cursors, caches, retries, transports, registries, schedulers, @@ -132,12 +132,12 @@ unauthorized external action after completing independent, authorized work. provenance, and `--no-tests=fail`. - Complete cutovers in one delivery slice: migrate every caller and datum, then delete compatibility façades, duplicate routes, old flags, dead aliases, - and superseded scaffolding. + and support code left from the move. - Add a V2/V3 contract, compatibility alias, deprecation path, or data migration only after proving the prior shape shipped on `origin/master`, in a published package, or in a live persisted format. Branch-local and unreleased contracts change in place; a `V1` suffix alone is not release - evidence and does not justify compatibility scaffolding. + evidence and does not justify compatibility shims. - Keep boundaries explicit: use top-level explicit imports/reexports, avoid wildcard parent-child cycles and inline imports, maintain one generated wire authority, and do not hand-write duplicate DTOs. @@ -152,11 +152,11 @@ unauthorized external action after completing independent, authorized work. roadmap precedence; `NEXT.md` records current outcomes only, while historical plans and benchmarks are archival. - Name production modules, APIs, tests, scripts, and CI jobs for durable product - capabilities—not PR numbers, milestones, phases, or temporary gates. Keep + capabilities, not PR numbers, milestones, phases, or temporary gates. Keep PR/milestone labels only in clearly archival plans and benchmark provenance. - Tests must be falsifiable and cover failure, denial, staleness, isolation, cancellation, and rollback where relevant, without duplicating the same - substrate across every host × OS combination. + base across every host × OS combination. ## Shared work diff --git a/CHANGELOG.md b/CHANGELOG.md index b8af07cb42..2362e42c8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -881,7 +881,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * **cli:** require confirmed host adoption ([a275cc5](https://github.com/ScriptedAlchemy/tracedecay/commit/a275cc5b0c63ca2b8df5cde4209d6bb4dd1d2481)) * **cli:** skip install checks for nested inspections ([89c1951](https://github.com/ScriptedAlchemy/tracedecay/commit/89c195195146808ff75a33ea7e330c7a2e21be49)) * **cli:** update explicit reinstall test imports ([6424fd0](https://github.com/ScriptedAlchemy/tracedecay/commit/6424fd02f36dfd1570f69e5173516bdce78c58b5)) -* **code-index:** code-index leftover slice for [#421](https://github.com/ScriptedAlchemy/tracedecay/issues/421) — full-scope dispatch, sealed-branch shard key ([fa37dd9](https://github.com/ScriptedAlchemy/tracedecay/commit/fa37dd9779bb31a3d87d8692ba4885436f7f10aa)) +* **code-index:** code-index leftover slice for [#421](https://github.com/ScriptedAlchemy/tracedecay/issues/421), full-scope dispatch, sealed-branch shard key ([fa37dd9](https://github.com/ScriptedAlchemy/tracedecay/commit/fa37dd9779bb31a3d87d8692ba4885436f7f10aa)) * **code-index:** compile graph policy without test authority ([2dbc2ea](https://github.com/ScriptedAlchemy/tracedecay/commit/2dbc2ead8815fe55d29440e50c4c73e06ca886e0)) * **code-index:** dispatch active generations by exact full scope ([49f8acc](https://github.com/ScriptedAlchemy/tracedecay/commit/49f8acc7c097bb2a68b03ce745c9d58d78112bfe)) * **code-index:** fail closed on late ceiling install ([63b012c](https://github.com/ScriptedAlchemy/tracedecay/commit/63b012ccaf3496532927edade960f364f57e02de)) @@ -1350,7 +1350,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * **automation:** share evidence budget labels ([7002612](https://github.com/ScriptedAlchemy/tracedecay/commit/70026120a4967e1db541a486829066cf32e8c2e5)) * **build:** preserve lean feature compilation ([060046f](https://github.com/ScriptedAlchemy/tracedecay/commit/060046fc1bd2e9f554d17d49114cdd239b6300de)) * **cli:** carry the status deadline into daemon tool calls ([dfa752a](https://github.com/ScriptedAlchemy/tracedecay/commit/dfa752a31f79b8edfbf7d975cbacc5797efac401)) -* **code-index:** code-index leftover slice for [#421](https://github.com/ScriptedAlchemy/tracedecay/issues/421) — full-scope dispatch, sealed-branch shard key ([fa37dd9](https://github.com/ScriptedAlchemy/tracedecay/commit/fa37dd9779bb31a3d87d8692ba4885436f7f10aa)) +* **code-index:** code-index leftover slice for [#421](https://github.com/ScriptedAlchemy/tracedecay/issues/421), full-scope dispatch, sealed-branch shard key ([fa37dd9](https://github.com/ScriptedAlchemy/tracedecay/commit/fa37dd9779bb31a3d87d8692ba4885436f7f10aa)) * **daemon:** mount budget-aware lexical projection build ([906eea1](https://github.com/ScriptedAlchemy/tracedecay/commit/906eea12c5c299b1f57a311c77c9539e9d72ec20)) * **daemon:** Scout P1 follow-up after [#521](https://github.com/ScriptedAlchemy/tracedecay/issues/521) ([d6a273c](https://github.com/ScriptedAlchemy/tracedecay/commit/d6a273c94317274fe3f66d7926b8c38a1e976bf6)) * **global-db:** restore measured WAL reclaim to registered checkpoint ([28135a5](https://github.com/ScriptedAlchemy/tracedecay/commit/28135a5dc50976eb77a3e2e6aae00c4e313dffdc)) @@ -2271,16 +2271,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Claude Code installs now register `SessionStart` and `PostToolUse` lifecycle hooks, matching the freshness/steering coverage Cursor, Codex, and Kiro already had: `SessionStart` reports index freshness and injects the LCM context-recovery hint after compaction; `PostToolUse` notifies the daemon for targeted incremental sync after edits and shell commands. Existing installs pick the hooks up via the post-upgrade backfill or `tracedecay doctor`. -- The CLI-fallback steering ("if MCP fails, use `tracedecay tool ...`") now reaches every host with a prompt-rules surface — Claude Code, Copilot/VS Code, Gemini, OpenCode, Kimi, Vibe, and Kiro — instead of only the Cursor rule and Codex session hook. +- The CLI-fallback steering ("if MCP fails, use `tracedecay tool ...`") now reaches every host with a prompt-rules surface: Claude Code, Copilot/VS Code, Gemini, OpenCode, Kimi, Vibe, and Kiro, instead of only the Cursor rule and Codex session hook. ### Fixed -- **`serve` no longer exits when project resolution fails at startup** — MCP hosts (Cursor especially) never retry a failed server spawn, so one startup exit over a recoverable config problem (uninitialized project, ambiguous global fallback, bad `--path`) turned every later tool call in the session into "Timed out waiting for connection". `serve` now stays alive in a degraded mode: it completes the MCP handshake, lists the real tools, and answers each tool call with an actionable error naming the failure, the fix, and the `tracedecay tool …` CLI fallback. It rechecks the project on every tool call and recovers in-session once `tracedecay init` (or a corrected path) makes resolution succeed — no server toggle or window reload needed. -- **`serve` now tolerates a literal unexpanded `--path ${workspaceFolder}`** — Cursor's headless agent-session MCP scopes spawn the plugin's serve command without expanding the template variable and never retry the failed scope, which surfaced as "Timed out waiting for connection" on every tool call. `serve` now discards an unexpanded `${...}` template value with a stderr warning and falls back to project discovery where possible, requiring a unique registered project when discovery reaches the global registry in this mode. Rationale and details in `cursor-plugin/README.md`. +- **`serve` no longer exits when project resolution fails at startup**. MCP hosts (Cursor especially) never retry a failed server spawn, so one startup exit over a recoverable config problem (uninitialized project, ambiguous global fallback, bad `--path`) turned every later tool call in the session into "Timed out waiting for connection". `serve` now stays alive in a degraded mode: it completes the MCP handshake, lists the real tools, and answers each tool call with an actionable error naming the failure, the fix, and the `tracedecay tool …` CLI fallback. It rechecks the project on every tool call and recovers in-session once `tracedecay init` (or a corrected path) makes resolution succeed, no server toggle or window reload needed. +- **`serve` now tolerates a literal unexpanded `--path ${workspaceFolder}`**. Cursor's headless agent-session MCP scopes spawn the plugin's serve command without expanding the template variable and never retry the failed scope, which surfaced as "Timed out waiting for connection" on every tool call. `serve` now discards an unexpanded `${...}` template value with a stderr warning and falls back to project discovery where possible, requiring a unique registered project when discovery reaches the global registry in this mode. Rationale and details in `cursor-plugin/README.md`. ### Added -- **`tracedecay doctor --agent cursor` now diagnoses dead Cursor MCP scopes** — best-effort scan of Cursor's recent MCP logs for tracedecay spawn failures (literal unexpanded `${workspaceFolder}` paths, `Connection failed: MCP error -32000`, degraded-mode notices) with concrete remediation ("toggle the MCP server in Cursor Settings → MCP or reload the window"), plus a plugin-bundle-version-vs-binary-version staleness check that points at `tracedecay update-plugin`. +- **`tracedecay doctor --agent cursor` now diagnoses dead Cursor MCP scopes**, best-effort scan of Cursor's recent MCP logs for tracedecay spawn failures (literal unexpanded `${workspaceFolder}` paths, `Connection failed: MCP error -32000`, degraded-mode notices) with concrete remediation ("toggle the MCP server in Cursor Settings → MCP or reload the window"), plus a plugin-bundle-version-vs-binary-version staleness check that points at `tracedecay update-plugin`. ## [0.0.23](https://github.com/ScriptedAlchemy/tracedecay/compare/v0.0.22...v0.0.23) - 2026-07-02 @@ -2467,7 +2467,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- **Markdown is now the default MCP tool output format.** Read/list/analysis/context tools (≈70 tools across `search`, `callers`, `callees`, `impact`, `outline`, `body`, `status`, `complexity`, `hotspots`, `health`, `test_map`, `pr_context`, …) now return compact markdown — bullets and GitHub-flavored tables — instead of pretty-printed JSON. Markdown is denser (no per-row key repetition, no brace/indentation overhead), scans better for models, and pushes responses away from the 15K-char truncation cliff. Symbol identifiers (`node_id`, `qualified_name`, `signature`) are preserved inline in backticks so follow-up calls (`body`/`callers`/`callees`) still chain cleanly. `tracedecay_context` was already markdown and is unchanged for the default path. +- **Markdown is now the default MCP tool output format.** Read/list/analysis/context tools (≈70 tools across `search`, `callers`, `callees`, `impact`, `outline`, `body`, `status`, `complexity`, `hotspots`, `health`, `test_map`, `pr_context`, …) now return compact markdown, bullets and GitHub-flavored tables, instead of pretty-printed JSON. Markdown is denser (no per-row key repetition, no brace/indentation overhead), scans better for models, and pushes responses away from the 15K-char truncation cliff. Symbol identifiers (`node_id`, `qualified_name`, `signature`) are preserved inline in backticks so follow-up calls (`body`/`callers`/`callees`) still chain cleanly. `tracedecay_context` was already markdown and is unchanged for the default path. - **New `format` argument** on every markdown-capable tool: pass `format: "json"` to get compact machine-readable JSON (for programmatic consumers); the default is `format: "markdown"`. Unrecognized values fall back to markdown. - **JSON output is never pretty-printed anymore.** Tools that intentionally stay JSON (edit primitives, `dashboard`, `fact_store`/`fact_feedback`, retrieval handles, and the LCM/session lifecycle tools) now emit compact `serde_json::to_string` rather than `to_string_pretty`, a ~30–40% byte reduction with no semantic change. `tracedecay_files` (grouped/flat text) and `tracedecay_type_hierarchy` (text tree) already returned dense text and are unchanged. - Shared `src/mcp/tools/render.rs` module centralizes format selection, format-aware truncation, and the generic JSON→markdown renderer used by tools without a bespoke layout. @@ -2556,32 +2556,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **Local web dashboard (`tracedecay dashboard` + `tracedecay_dashboard` MCP tool).** A self-contained axum server with compile-time-embedded UI assets serving three tabs: **Holographic Memory** (fact/entity/bank inspector, 2D PCA semantic map, association graph, phase-cosine similarity explorer with brushable histogram, and feature-flagged curation), **LCM** (overview, FTS search with role/source/session facets, session/node drilldowns, timeline, compression analytics over the global DB), and **Code Graph** (overview analytics plus a force-directed canvas explorer with search-to-focus, progressive neighbor expansion, callers/callees, filters, and shortest-path mode). CLI flags: `--path`, `--host`, `--port` (0 = auto, parseable URL on stdout), `--open`. The `tracedecay_dashboard` MCP tool starts/stops the same server as a background task and returns the URL. `GET /api/capabilities` advertises feature flags (`memory`, `lcm`, `graph`, `curation`, `llm_curation`) for host/UI feature detection. Dark + light themes, responsive down to ~420px. -- **Memory curation with hard-delete semantics.** `POST /api/plugins/holographic/curate` proposes (dry-run) or applies similarity-based deduplication: the lower-trust fact of each `likely_duplicate` pair is permanently deleted via the canonical store path (FK-cascaded entity links, FTS trigger cleanup, bank dirty-marking) — no archive state, no restore. `POST /curate/apply` exposes a generic delete/merge ops contract for external (e.g. LLM-backed) planners; per-op failures are reported per-op. Migration v13 only cleans up a never-shipped archive-column experiment from local dev databases. +- **Memory curation with hard-delete semantics.** `POST /api/plugins/holographic/curate` proposes (dry-run) or applies similarity-based deduplication: the lower-trust fact of each `likely_duplicate` pair is permanently deleted via the canonical store path (FK-cascaded entity links, FTS trigger cleanup, bank dirty-marking), no archive state, no restore. `POST /curate/apply` exposes a generic delete/merge ops contract for external (e.g. LLM-backed) planners; per-op failures are reported per-op. Migration v13 only cleans up a never-shipped archive-column experiment from local dev databases. - **Hermes wrapper for the dashboard.** `dashboard/hermes-wrapper/` (canonical; deployed to the hermes-agent working tree) reverse-proxies `/holographic/*`, `/lcm/*`, and `/graph/*` to a spawned or externally configured tracedecay dashboard, re-hosts the same UI bundles under the combined "TraceDecay" tab, layers an optional LLM curation planner on the `/curate/apply` contract, and hardens the subprocess lifecycle (stderr drain, parent-death signal, spawn-failure backoff). - **Dashboard build + test infrastructure.** `dashboard/` npm workspace (esbuild) building all bundles, 16 frontend unit tests (`node run-unit-tests.mjs`), Playwright smoke (`npm run smoke -- --expect-lcm=empty|non-empty`), Rust integration suites (`tests/dashboard_api_test.rs`, `dashboard_lcm_fixes_test.rs`, `dashboard_graph_api_test.rs`, `mcp_dashboard_tool_test.rs`), a dashboard CI job, and `build.rs` rerun-if-changed guards so frontend dist changes force re-embedding. - **Curation previews survive dashboard restarts.** The last dry-run curation plan is mirrored to a `.tracedecay/dashboard/curation_preview.json` sidecar and re-hydrated when the server starts; applying curation (or any `/curate/apply` mutation) clears both the in-memory copy and the sidecar. The `GET /curation/preview` API shape is unchanged, and staleness is still recomputed against the live fact count on every read. -- **`tracedecay install --agent hermes` deploys the dashboard plugin page.** The Hermes wrapper (manifest, `plugin_api.py` reverse proxy, and the UI bundles — all embedded in the binary, no source checkout needed) is now written to `/plugins/tracedecay/dashboard/` as part of the default install, where Hermes' dashboard-plugin discovery (stock and forked) picks it up as a "TraceDecay" tab with Memory / LCM / Code Graph / Savings sub-tabs. The deployed proxy bakes in the installing binary path and the profile's pinned `project_root` as spawn-mode defaults (`TRACEDECAY_BIN` / `TRACEDECAY_DASHBOARD_PROJECT` env vars still win); reinstalls preserve the pin, `--no-dashboard` opts out (and removes a previous deploy), and uninstall cleans the page up. The wrapper also gained the Savings sub-tab (`/savings/*` proxy to `/api/plugins/savings/*`). On Hermes versions without dashboard-plugin discovery the deployed directory is inert. -- **Dashboard assets build themselves on fresh checkouts.** When the embedded `dashboard/*/dist` bundles are missing, `build.rs` now runs the frontend build automatically (`npm ci`, falling back to `npm install`, then `npm run build`) with progress reported as build warnings — so `cargo build` / `cargo install --path .` work from a clean clone. If npm is unavailable, the build still fails fast with actionable instructions. `Cargo.toml` switched to an explicit `package.include` whitelist that ships the prebuilt dist bundles inside the crate package, making `cargo package`/`cargo publish` verifiable and letting crates.io/docs.rs builds proceed with no Node.js toolchain. The release workflows (`release.yml` build + publish-crate jobs, `release-beta.yml`) gained the same dashboard prebuild step as CI. +- **`tracedecay install --agent hermes` deploys the dashboard plugin page.** The Hermes wrapper (manifest, `plugin_api.py` reverse proxy, and the UI bundles, all embedded in the binary, no source checkout needed) is now written to `/plugins/tracedecay/dashboard/` as part of the default install, where Hermes' dashboard-plugin discovery (stock and forked) picks it up as a "TraceDecay" tab with Memory / LCM / Code Graph / Savings sub-tabs. The deployed proxy bakes in the installing binary path and the profile's pinned `project_root` as spawn-mode defaults (`TRACEDECAY_BIN` / `TRACEDECAY_DASHBOARD_PROJECT` env vars still win); reinstalls preserve the pin, `--no-dashboard` opts out (and removes a previous deploy), and uninstall cleans the page up. The wrapper also gained the Savings sub-tab (`/savings/*` proxy to `/api/plugins/savings/*`). On Hermes versions without dashboard-plugin discovery the deployed directory is inert. +- **Dashboard assets build themselves on fresh checkouts.** When the embedded `dashboard/*/dist` bundles are missing, `build.rs` now runs the frontend build automatically (`npm ci`, falling back to `npm install`, then `npm run build`) with progress reported as build warnings, so `cargo build` / `cargo install --path .` work from a clean clone. If npm is unavailable, the build still fails fast with actionable instructions. `Cargo.toml` switched to an explicit `package.include` whitelist that ships the prebuilt dist bundles inside the crate package, making `cargo package`/`cargo publish` verifiable and letting crates.io/docs.rs builds proceed with no Node.js toolchain. The release workflows (`release.yml` build + publish-crate jobs, `release-beta.yml`) gained the same dashboard prebuild step as CI. -- **Tokenizer-backed cost tier for the Savings & Cost tab (`token-counting` feature, on by default).** When transcripts carry no usage counters (all Cursor stores — verified to contain none — plus cline/vibe and any Codex/Claude rows without usage), stored message text is now counted with a real BPE tokenizer (tiktoken-rs, `o200k_base`/`cl100k_base`) instead of the chars/4 heuristic: exact for OpenAI-family models, a labeled `≈` approximation for vendors without a public tokenizer (Claude/Gemini). The API gains a third `cost_basis` value `"tokenized"` (between `"actual"` and `"estimated"`; `"mixed"` semantics unchanged), additive `tokenized` token blocks, `tokenized_messages` counts, and per-model `tokenizer` provenance (`{"encoder", "exact"}`); the UI shows tier badges and an updated methodology note. Counts are cached per message (in-process map + a `dashboard_token_counts` sidecar table in the global accounting DB, keyed by message identity with a text-length guard) and pre-warmed in the background at dashboard startup, so 15k+-message stores pay the BPE pass once instead of per request. Disable the feature for a leaner binary (~4 MB embedded vocabularies, lazily decoded) — everything degrades to the chars/4 tier. +- **Tokenizer-backed cost tier for the Savings & Cost tab (`token-counting` feature, on by default).** When transcripts carry no usage counters (all Cursor stores, verified to contain none, plus cline/vibe and any Codex/Claude rows without usage), stored message text is now counted with a real BPE tokenizer (tiktoken-rs, `o200k_base`/`cl100k_base`) instead of the chars/4 heuristic: exact for OpenAI-family models, a labeled `≈` approximation for vendors without a public tokenizer (Claude/Gemini). The API gains a third `cost_basis` value `"tokenized"` (between `"actual"` and `"estimated"`; `"mixed"` semantics unchanged), additive `tokenized` token blocks, `tokenized_messages` counts, and per-model `tokenizer` provenance (`{"encoder", "exact"}`); the UI shows tier badges and an updated methodology note. Counts are cached per message (in-process map + a `dashboard_token_counts` sidecar table in the global accounting DB, keyed by message identity with a text-length guard) and pre-warmed in the background at dashboard startup, so 15k+-message stores pay the BPE pass once instead of per request. Disable the feature for a leaner binary (~4 MB embedded vocabularies, lazily decoded), everything degrades to the chars/4 tier. ### Fixed -- **The savings ledger records by default again — the Savings tab is no longer empty while lifetime counters grow.** The holographic-fact-store commit made the MCP server's global accounting DB opt-in via `TRACEDECAY_ENABLE_GLOBAL_DB`, which silently disabled `savings_ledger` writes (and worldwide-counter flushes) for every default install: tool calls still printed `tracedecay_metrics` lines and CLI paths kept growing `projects.tokens_saved`, but the dashboard showed "ledger calls: 0 / no events yet". Global accounting is now **on by default**; opt out with `TRACEDECAY_DISABLE_GLOBAL_DB=1` (set automatically for cargo-launched processes via `.cargo/config.toml` so test runs stay hermetic) or `TRACEDECAY_ENABLE_GLOBAL_DB=0`, with an explicit `TRACEDECAY_ENABLE_GLOBAL_DB=1` always winning. The dashboard now also surfaces the gate verdict (`savings.recording` in the overview API, a `recording: on/off` badge, and an honest explanation when the ledger is empty — including the "restart your MCP server to pick this up" case) instead of an unconditional "no events yet". Covered by a default-on ledger regression test plus env-precedence unit tests; long-running MCP servers must be restarted/reloaded to pick up the fix. -- **Hermes wrapper spawn mode no longer drops its child server after idle periods.** The wrapper's Linux parent-death guard (`PR_SET_PDEATHSIG`) fires when the *thread* that forked the child exits — and FastAPI sync endpoints run on anyio threadpool workers that are reaped after ~10s idle, so the spawned `tracedecay dashboard` was SIGTERMed shortly after quiet spells (surfacing as intermittent 502 "connection reset by peer" on the next tab click). `plugin_api.py` now spawns from a single long-lived worker thread, binding the child's lifetime to the Hermes host process as intended. +- **The savings ledger records by default again, the Savings tab is no longer empty while lifetime counters grow.** The holographic-fact-store commit made the MCP server's global accounting DB opt-in via `TRACEDECAY_ENABLE_GLOBAL_DB`, which silently disabled `savings_ledger` writes (and worldwide-counter flushes) for every default install: tool calls still printed `tracedecay_metrics` lines and CLI paths kept growing `projects.tokens_saved`, but the dashboard showed "ledger calls: 0 / no events yet". Global accounting is now **on by default**; opt out with `TRACEDECAY_DISABLE_GLOBAL_DB=1` (set automatically for cargo-launched processes via `.cargo/config.toml` so test runs stay hermetic) or `TRACEDECAY_ENABLE_GLOBAL_DB=0`, with an explicit `TRACEDECAY_ENABLE_GLOBAL_DB=1` always winning. The dashboard now also surfaces the gate verdict (`savings.recording` in the overview API, a `recording: on/off` badge, and an honest explanation when the ledger is empty, including the "restart your MCP server to pick this up" case) instead of an unconditional "no events yet". Covered by a default-on ledger regression test plus env-precedence unit tests; long-running MCP servers must be restarted/reloaded to pick up the fix. +- **Hermes wrapper spawn mode no longer drops its child server after idle periods.** The wrapper's Linux parent-death guard (`PR_SET_PDEATHSIG`) fires when the *thread* that forked the child exits, and FastAPI sync endpoints run on anyio threadpool workers that are reaped after ~10s idle, so the spawned `tracedecay dashboard` was SIGTERMed shortly after quiet spells (surfacing as intermittent 502 "connection reset by peer" on the next tab click). `plugin_api.py` now spawns from a single long-lived worker thread, binding the child's lifetime to the Hermes host process as intended. - **Hermes wrapper cold starts no longer 502.** After spawning, the wrapper now waits (bounded, 30s) for the engine's `/api/capabilities` to answer before proxying the first request, returns a clear `503` with `Retry-After` if the engine truly fails to come up, and transparently retries GET proxies once after re-resolving the upstream (which reaps and respawns a dead child). POSTs are never retried so curation applies cannot run twice. - **Fallback branch DBs are now read-only for sync/index writes.** `tracedecay sync`, lazy single-file syncs, and full indexing now refuse to write when the active git branch is being served from an ancestor branch database, preventing branch-only files from being indexed into the fallback DB. -- **`tracedecay install --agent hermes` generates a plugin that loads on newer Hermes hosts.** Four generator/installer fixes: (1) the generated `TraceDecayContextEngine` implements the now-abstract `update_from_response(usage)` method (normalizes `prompt/input`, `completion/output`, and `total` token counts into `last_*_tokens` attributes), so plugin load no longer dies with `Can't instantiate abstract class`; (2) the skill registers under the bare name `tracedecay` — newer Hermes derives the namespace from the plugin and rejects `:` in skill names; (3) the installer now matches the existing indentation of `plugins.enabled`/`plugins.disabled` lists (Hermes writes 2-space items) instead of always inserting 4-space items, which produced unparseable mixed-indent YAML; and (4) flow-style empty lists (`disabled: []`, which Hermes itself writes) are accepted instead of failing with "unsupported Hermes plugins config" — an empty `enabled: []` is rewritten to a block list. The generated context engine additionally honors a `project_root` config key so profiles can pin the indexed project (explicit host kwargs win; the session cwd stays the last fallback). +- **`tracedecay install --agent hermes` generates a plugin that loads on newer Hermes hosts.** Four generator/installer fixes: (1) the generated `TraceDecayContextEngine` implements the now-abstract `update_from_response(usage)` method (normalizes `prompt/input`, `completion/output`, and `total` token counts into `last_*_tokens` attributes), so plugin load no longer dies with `Can't instantiate abstract class`; (2) the skill registers under the bare name `tracedecay`, newer Hermes derives the namespace from the plugin and rejects `:` in skill names; (3) the installer now matches the existing indentation of `plugins.enabled`/`plugins.disabled` lists (Hermes writes 2-space items) instead of always inserting 4-space items, which produced unparseable mixed-indent YAML; and (4) flow-style empty lists (`disabled: []`, which Hermes itself writes) are accepted instead of failing with "unsupported Hermes plugins config", an empty `enabled: []` is rewritten to a block list. The generated context engine additionally honors a `project_root` config key so profiles can pin the indexed project (explicit host kwargs win; the session cwd stays the last fallback). - **`tracedecay install --agent hermes --project-root ` pins a profile's plugin to one project.** The pin is written into the generated plugin (`PINNED_PROJECT_ROOT` in `tools.py`): every plugin tool call then passes `--project ` so memory + LCM stores resolve to `/.tracedecay/` regardless of the Hermes process cwd, and the context engine uses it ahead of cwd inference (kwargs > config > pin > cwd). Reinstalls without the flag preserve an existing pin; the flag is hermes-only, requires an absolute path, and conflicts with `--all-profiles` (pins are per-profile). - **`tracedecay tool` now walks up from subdirectories to the nearest initialised project** when `--project` is not given, matching how `sync`, `status`, `serve`, and `dashboard` resolve project roots. - **Cursor hook hints use the quote-aware shell parser.** `tool_hints` classified search commands with a naive `split_whitespace`, so a quoted pattern like `grep "needle -r" file` leaked a fake `-r` flag and misclassified as a recursive search. It now shares `hooks.rs`'s quote/escape-aware `shell_words` parser (single shared implementation, regression-tested). - **Hermes generated plugin files are written atomically.** `write_text_file` now uses the write-to-`.new`-then-rename pattern (like the config writer), so a mid-write crash can no longer leave a truncated `__init__.py`/`tools.py` behind. Unsupported-config errors during install also name the exact retry command (`tracedecay install --agent hermes`). -- **v13 archive-column cleanup handles generated-column dev databases.** The migration enumerated columns with `PRAGMA table_info`, which hides GENERATED columns — so a dev DB where the abandoned archive revision left `superseded_by` as a generated column referencing `merged_into` skipped that drop and then failed with `no such column: merged_into`. The migration now uses `PRAGMA table_xinfo` and drops the columns in reverse-addition order (dependent generated columns first). Covered by a regression test seeding exactly that odd state. +- **v13 archive-column cleanup handles generated-column dev databases.** The migration enumerated columns with `PRAGMA table_info`, which hides GENERATED columns, so a dev DB where the abandoned archive revision left `superseded_by` as a generated column referencing `merged_into` skipped that drop and then failed with `no such column: merged_into`. The migration now uses `PRAGMA table_xinfo` and drops the columns in reverse-addition order (dependent generated columns first). Covered by a regression test seeding exactly that odd state. - **Archive-semantics purge (policy: deleted memories are permanently hard-deleted).** Removed the last UI remnants of the never-shipped archive feature: the CurationPanel no longer recognizes/renders `archive` or `supersede` ops (neither planner can produce them; the curate ops contract is delete/merge only), the `archive` action field is gone from the frontend types, and stale Hermes-wrapper docstrings naming `archive`/`archive/{fact_id}/restore` routes were corrected. A new store-level test pins the full hard-delete cascade: `MemoryStore::remove_fact` removes the fact row, its FTS mirror row, its entity links, and its feedback events, and marks the fact's banks dirty. ## [6.1.3] - 2026-06-04 ### Fixed -- **Write/exec MCP tools no longer advertise `readOnlyHint: true` (#94).** `tracedecay_replace_symbol`, `tracedecay_insert_at_symbol`, and `tracedecay_run_affected_tests` mutate source files or run a `cargo test` subprocess, but were annotated read-only via the shared `def()` helper — so harnesses that auto-approve read-only tools could edit files or compile and execute project code without prompting. They now use a new `def_rw()` helper that stamps `readOnlyHint: false`, matching the other edit tools. A regression test asserts every write/exec tool is non-read-only. +- **Write/exec MCP tools no longer advertise `readOnlyHint: true` (#94).** `tracedecay_replace_symbol`, `tracedecay_insert_at_symbol`, and `tracedecay_run_affected_tests` mutate source files or run a `cargo test` subprocess, but were annotated read-only via the shared `def()` helper, so harnesses that auto-approve read-only tools could edit files or compile and execute project code without prompting. They now use a new `def_rw()` helper that stamps `readOnlyHint: false`, matching the other edit tools. A regression test asserts every write/exec tool is non-read-only. ## [6.1.2] - 2026-05-30 @@ -2595,13 +2595,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [6.1.1] - 2026-05-26 ### Added -- **Borrowed-worktree detection on every MCP read tool.** When a git worktree is nested inside the main checkout (e.g. agent tooling that puts worktrees under `.claude/worktrees//` or `.worktrees//`), tracedecay's `discover_project_root` walks up and silently resolves the MAIN checkout's `.tracedecay/` — returning results for the wrong branch with no warning, while symbols changed only in the worktree are invisible. New `src/worktree.rs` runs `git rev-parse --show-toplevel` on the caller's CWD and on the resolved index root; when they belong to different working trees, the verbose warning is included in `tracedecay_status` and a one-line notice is prefixed to every read tool response. Detection runs once at server startup (≤2 `git rev-parse` spawns total per session). Ported from codegraph #312. -- **Catch-up sync on MCP connect.** `McpServer::new` now spawns a non-blocking task that runs `find_stale_files` + `sync_if_stale_silent` + `refresh_file_token_map` once at startup, bypassing the 30 s cooldown. Picks up changes made while the server was down — terminal `git pull`, IDE edits before the agent launched, files touched by another tool — so the first tool call sees a fresh index instead of waiting through the cooldown. Ported from codegraph #414. +- **Borrowed-worktree detection on every MCP read tool.** When a git worktree is nested inside the main checkout (e.g. agent tooling that puts worktrees under `.claude/worktrees//` or `.worktrees//`), tracedecay's `discover_project_root` walks up and silently resolves the MAIN checkout's `.tracedecay/`, returning results for the wrong branch with no warning, while symbols changed only in the worktree are invisible. New `src/worktree.rs` runs `git rev-parse --show-toplevel` on the caller's CWD and on the resolved index root; when they belong to different working trees, the verbose warning is included in `tracedecay_status` and a one-line notice is prefixed to every read tool response. Detection runs once at server startup (≤2 `git rev-parse` spawns total per session). Ported from codegraph #312. +- **Catch-up sync on MCP connect.** `McpServer::new` now spawns a non-blocking task that runs `find_stale_files` + `sync_if_stale_silent` + `refresh_file_token_map` once at startup, bypassing the 30 s cooldown. Picks up changes made while the server was down, terminal `git pull`, IDE edits before the agent launched, files touched by another tool, so the first tool call sees a fresh index instead of waiting through the cooldown. Ported from codegraph #414. - **`scripts/prepare-release.py` to auto-promote `[Unreleased]` → `[]` in CHANGELOG.md.** Idempotently renames the `[Unreleased]` block to a dated `[]` block at release time (Case A), or merges into a pre-existing `[]` block by sub-section (Case B). Avoids the codegraph v0.9.5 failure mode where a sparse hand-staged `[]` block silently shadowed the much-larger `[Unreleased]` section above it during release-notes extraction. Ported from codegraph #436. Wire into the release workflow when a version bump lands. ### Changed -- **Embedded MCP watcher replaced with on-demand staleness check (#80).** The `notify-debouncer-full` watcher was the source of severe CPU and memory pressure on large monorepos: top-level-only filtering of `IGNORED_DIRS` meant nested `apps/*/node_modules`, `packages/*/target`, `**/dist` were watched at the OS level, producing event storms and unbounded `RecommendedCache` growth (one user reported the process climbing to 19 GB before being killed). The watcher is now gone — along with the `notify-debouncer-full` dependency. Index freshness is maintained by a lazy `find_stale_files` walk (same gitignore-aware logic `sync()` uses) gated by a 30-second cooldown and invoked at the top of every MCP `tools/call`. Cost: walks on the cold tool call after a quiet window add tens of ms to milliseconds depending on repo size; in exchange, the unbounded-memory class of bug is structurally gone. Reported by @AGiorgetti and @ottob. -- **Per-file staleness banner replaces the binary "STALE INDEX" warning.** Tool responses that referenced files whose in-line sync couldn't refresh now get a focused banner naming exactly those files with their edit ages (e.g. `src/foo.rs (edited 3m ago)`) and an explicit instruction to `Read` those files directly — while telling the agent the rest of the response is authoritative. Replaces the previous all-or-nothing wording that made agents distrust the entire response. The machine-readable `tracedecay_graph_stale` marker is preserved. Ported from codegraph #428. +- **Embedded MCP watcher replaced with on-demand staleness check (#80).** The `notify-debouncer-full` watcher was the source of severe CPU and memory pressure on large monorepos: top-level-only filtering of `IGNORED_DIRS` meant nested `apps/*/node_modules`, `packages/*/target`, `**/dist` were watched at the OS level, producing event storms and unbounded `RecommendedCache` growth (one user reported the process climbing to 19 GB before being killed). The watcher is now gone, along with the `notify-debouncer-full` dependency. Index freshness is maintained by a lazy `find_stale_files` walk (same gitignore-aware logic `sync()` uses) gated by a 30-second cooldown and invoked at the top of every MCP `tools/call`. Cost: walks on the cold tool call after a quiet window add tens of ms to milliseconds depending on repo size; in exchange, the unbounded-memory class of bug is structurally gone. Reported by @AGiorgetti and @ottob. +- **Per-file staleness banner replaces the binary "STALE INDEX" warning.** Tool responses that referenced files whose in-line sync couldn't refresh now get a focused banner naming exactly those files with their edit ages (e.g. `src/foo.rs (edited 3m ago)`) and an explicit instruction to `Read` those files directly, while telling the agent the rest of the response is authoritative. Replaces the previous all-or-nothing wording that made agents distrust the entire response. The machine-readable `tracedecay_graph_stale` marker is preserved. Ported from codegraph #428. - **Kiro steering is loaded as a resource.** The Kiro installer now writes `~/.kiro/steering/tracedecay.md`, loads it from the managed agent's `resources` list with an absolute `file://` URI, leaves the custom-agent `prompt` unset so Kiro's default prompt is preserved, keeps MCP approval policy out of `mcp.json`, and installs permissive `tools: ["*"]` plus `allowedTools: ["@builtin", "@tracedecay"]` defaults for the managed agent. ### Fixed @@ -2610,24 +2610,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [6.1.0] - 2026-05-25 ### Added -- **`tracedecay tool ` — schema-driven CLI dispatcher.** Every MCP tool is now reachable from the command line through a single dynamic subcommand that introspects each tool's JSON schema and coerces `--key value` flags accordingly. `tracedecay tool` (no args) lists tools grouped by category; `tracedecay tool --help` prints schema-derived parameters. Reserved flags: `--json` (raw response), `--project `, `--args `, `-h`/`--help`. Positional args bind to required string properties (e.g. `tracedecay tool search foo`). `@file` values are read from disk for multi-line strings. Replaces seven hand-rolled subcommands (`query`, `context`, `body`, `impact`, `callers`, `files`, `affected`); `query` is kept as an alias for the renamed `search`. New file: `src/tool_command.rs` (~729 LoC). -- **`tracedecay_find_exact_symbol` MCP tool.** Bare-name lookup against `idx_nodes_name` — a single O(log n) index probe with no BM25 ranking, no fuzzy match, no qualified-name suffix walk. Use this when the symbol name is already known; use `tracedecay_search` for relevance-ranked discovery. ~30–200 µs per lookup vs. ~700 µs for the BM25 path. +- **`tracedecay tool `, schema-driven CLI dispatcher.** Every MCP tool is now reachable from the command line through a single dynamic subcommand that introspects each tool's JSON schema and coerces `--key value` flags accordingly. `tracedecay tool` (no args) lists tools grouped by category; `tracedecay tool --help` prints schema-derived parameters. Reserved flags: `--json` (raw response), `--project `, `--args `, `-h`/`--help`. Positional args bind to required string properties (e.g. `tracedecay tool search foo`). `@file` values are read from disk for multi-line strings. Replaces seven hand-rolled subcommands (`query`, `context`, `body`, `impact`, `callers`, `files`, `affected`); `query` is kept as an alias for the renamed `search`. New file: `src/tool_command.rs` (~729 LoC). +- **`tracedecay_find_exact_symbol` MCP tool.** Bare-name lookup against `idx_nodes_name`, a single O(log n) index probe with no BM25 ranking, no fuzzy match, no qualified-name suffix walk. Use this when the symbol name is already known; use `tracedecay_search` for relevance-ranked discovery. ~30–200 µs per lookup vs. ~700 µs for the BM25 path. - **`tracedecay_call_chain` MCP tool.** Finds the shortest *directed* call chain between two node IDs along outgoing `calls` edges only. New `find_path_directed` BFS in `graph/traversal.rs` (the existing `find_path` is bidirectional and wrong for "how does A reach B" questions). Bounded by `max_depth` (default 8, max 20). - **`tracedecay_file_dependents` MCP tool.** Lists every indexed file that imports or otherwise depends on the given file. Thin wrapper around the existing `TraceDecay::get_file_dependents` that was previously only reachable through `tracedecay_affected`'s test-rollup path. - **`tracedecay_replace_symbol` MCP tool.** Symbol-aware body replacement: resolves a name via exact qualified-name match, narrows to callable kinds on ambiguity, and refuses the edit rather than picking the wrong site if more than one callable matches. Reads the file, splices the symbol's `start_line..=end_line` range with `new_source`, writes back, and reindexes the touched file. Plays the role of token-savior's `replace_symbol_source`. -- **`tracedecay_insert_at_symbol` MCP tool.** Inserts content immediately before or after a named symbol's source range — same resolution semantics as `tracedecay_replace_symbol`. `position` is `"before"` or `"after"` (default after). Plays the role of token-savior's `insert_near_symbol`. +- **`tracedecay_insert_at_symbol` MCP tool.** Inserts content immediately before or after a named symbol's source range, same resolution semantics as `tracedecay_replace_symbol`. `position` is `"before"` or `"after"` (default after). Plays the role of token-savior's `insert_near_symbol`. - **`tracedecay serve --timings` flag.** When set, every `tools/call` response gains a `_meta.duration_us` field reporting the handler's pure execution time in microseconds. Lets clients (and benchmarks) attribute latency to actual query work vs. JSON-RPC / stdio / Python-parse overhead. Toggleable at runtime through the new `McpServer::set_timings_enabled` setter so embedders can flip it per-session. -- **Indexer benchmark harness** at `benchmark_data/run_benchmarks.py` — adapts `Mibayy/token-savior`'s `run_benchmarks.py` to drive both tools side-by-side on the same clone of FastAPI, sharing a random symbol sample (seed=42) so per-query rows are directly comparable. tracedecay is driven through a long-lived `tracedecay serve --timings` MCP session for the query column. Latest report (`benchmark_data/comparison-report.md`): cold index 2.9× faster, impact analysis 43× faster than token-savior. -- **tsbench fork** at `benchmark_data/tsbench/` — patch + reproduction README + per-run summary for running `Mibayy/tsbench` (token-savior's own 96-task agent benchmark) against tracedecay. First-attempt untuned result: 184/192 = 95.8% vs. token-savior's audited 97.9%. The harness rewrites `SYSTEM_PROMPT_TS` to map each token-savior tool to its tracedecay equivalent and relaxes the `--disallowedTools` list to allow `Read`/`Edit` fallback on the four task categories tracedecay has no direct tool for. -- **`docs/TRACEDECAY-VS-TOKENSAVIOR.md`** — full capability + performance comparison document covering parsing strategy (regex annotators vs. tree-sitter grammars), the 11 health-analytics tools that have no token-savior equivalent, query-latency numbers (apples-to-apples find / body / impact), the tsbench 184/192 result with per-task failure analysis, and an honest "when to use which" guide. +- **Indexer benchmark harness** at `benchmark_data/run_benchmarks.py`, adapts `Mibayy/token-savior`'s `run_benchmarks.py` to drive both tools side-by-side on the same clone of FastAPI, sharing a random symbol sample (seed=42) so per-query rows are directly comparable. tracedecay is driven through a long-lived `tracedecay serve --timings` MCP session for the query column. Latest report (`benchmark_data/comparison-report.md`): cold index 2.9× faster, impact analysis 43× faster than token-savior. +- **tsbench fork** at `benchmark_data/tsbench/`, patch + reproduction README + per-run summary for running `Mibayy/tsbench` (token-savior's own 96-task agent benchmark) against tracedecay. First-attempt untuned result: 184/192 = 95.8% vs. token-savior's audited 97.9%. The harness rewrites `SYSTEM_PROMPT_TS` to map each token-savior tool to its tracedecay equivalent and relaxes the `--disallowedTools` list to allow `Read`/`Edit` fallback on the four task categories tracedecay has no direct tool for. +- **`docs/TRACEDECAY-VS-TOKENSAVIOR.md`**, full capability + performance comparison document covering parsing strategy (regex annotators vs. tree-sitter grammars), the 11 health-analytics tools that have no token-savior equivalent, query-latency numbers (apples-to-apples find / body / impact), the tsbench 184/192 result with per-task failure analysis, and an honest "when to use which" guide. ### Fixed -- **`tracedecay serve` no longer blocks MCP `initialize` on the watcher's filesystem walk (#84).** Constructing the embedded `notify_debouncer_full` watcher does a synchronous `walkdir` over every registered subtree to seed its file-id map. On a large JS/TS monorepo with multi-gigabyte `node_modules` / `.next` / `dist` trees this can take 30+ seconds — long enough to blow the client's `initialize` timeout. Fix: `ProjectWatcher::new` now runs inside `tokio::task::spawn_blocking` from a detached `tokio::spawn`, so `McpServer::new` returns immediately and the MCP stdio loop can answer `initialize` / `tools/list` in milliseconds. The `CancellationToken` is stored on the server up front so `shutdown` can cancel mid-walk if the agent disconnects before the watcher finishes initialising. Reported by @ottob with a sample-trace and an FSEvents-sandbox repro that left zero ambiguity about root cause. +- **`tracedecay serve` no longer blocks MCP `initialize` on the watcher's filesystem walk (#84).** Constructing the embedded `notify_debouncer_full` watcher does a synchronous `walkdir` over every registered subtree to seed its file-id map. On a large JS/TS monorepo with multi-gigabyte `node_modules` / `.next` / `dist` trees this can take 30+ seconds, long enough to blow the client's `initialize` timeout. Fix: `ProjectWatcher::new` now runs inside `tokio::task::spawn_blocking` from a detached `tokio::spawn`, so `McpServer::new` returns immediately and the MCP stdio loop can answer `initialize` / `tools/list` in milliseconds. The `CancellationToken` is stored on the server up front so `shutdown` can cancel mid-walk if the agent disconnects before the watcher finishes initialising. Reported by @ottob with a sample-trace and an FSEvents-sandbox repro that left zero ambiguity about root cause. - **`tracedecay serve` no longer runs pre-serve maintenance work (#84).** `Commands::Serve` was running `try_flush` (synchronous HTTP round-trip to the worldwide counter), `check_install_stale`, and the silent-reinstall loop over every tracked agent before the MCP stdio loop even started. All three are now gated behind `should_skip_agent_install_maintenance`, alongside `Install` / `Reinstall` / `Uninstall` / `Doctor`. Same maintenance still runs on the user's next interactive `tracedecay …` invocation. - **`tracedecay install --agent antigravity` now registers in both the IDE config and the CLI plugin directory (#85).** Previously only `~/.gemini/antigravity/mcp_config.json` was written, leaving the Antigravity CLI (`agy`) unable to see tracedecay in `/mcp`. New: also writes `~/.gemini/antigravity-cli/plugins/tracedecay.json` with the same `{"mcpServers": {"tracedecay": {...}}}` shape. `uninstall` removes both, `doctor` reports both, `is_detected` triggers on either path. Reported by @ottob. - **MCP `last synced N ago` warning no longer fires after a no-change sync (#86).** The warning was reading `MAX(files.indexed_at)`, which only advances when a file is actually reindexed. On quiet repos a successful `tracedecay sync` (0 added / 0 modified / 0 removed) left `indexed_at` stuck and the warning fired forever. New: the warning is computed from the `last_sync_at` metadata key, which `sync()` writes unconditionally on every successful invocation. Falls back to `MAX(indexed_at)` only when the metadata key is missing (e.g. a freshly-initialised project that has never been synced). New `TraceDecay::last_sync_timestamp()` helper exposes this for embedders. Reported by @uwe-sure. -- **`files_by_language` status output now uses real language names instead of bucketing everything as `Other`.** The SQL `CASE` in `Database::get_stats` only recognised four languages (Rust / Go / Java / Scala) and dumped everything else — Python, TypeScript, C, Swift, Kotlin, etc. — into `"Other"`. Replaced with a Rust-side bucketing helper covering 46 extractor languages; Python files in the FastAPI benchmark now correctly report as `Python` instead of `Other`. Includes special-case basename matching for extensionless `Dockerfile` / `Makefile`. -- **Pre-existing breakage in `tests/mcp_server_test.rs` repaired.** The whole `test-transport`-gated integration suite (31 tests) had been silently failing to compile since `McpServer::new` switched its return type to `Arc` — `setup_server` and `run_server_with_messages` still expected bare `McpServer`. Switched both helpers to `Arc` and bumped the resource-count assertion (`tracedecay://status/files/overview/branches`) from 4 to 5 to include the newer `tracedecay://schema` resource. All 31 tests now pass. +- **`files_by_language` status output now uses real language names instead of bucketing everything as `Other`.** The SQL `CASE` in `Database::get_stats` only recognised four languages (Rust / Go / Java / Scala) and dumped everything else. Python, TypeScript, C, Swift, Kotlin, etc., into `"Other"`. Replaced with a Rust-side bucketing helper covering 46 extractor languages; Python files in the FastAPI benchmark now correctly report as `Python` instead of `Other`. Includes special-case basename matching for extensionless `Dockerfile` / `Makefile`. +- **Pre-existing breakage in `tests/mcp_server_test.rs` repaired.** The whole `test-transport`-gated integration suite (31 tests) had been silently failing to compile since `McpServer::new` switched its return type to `Arc`. `setup_server` and `run_server_with_messages` still expected bare `McpServer`. Switched both helpers to `Arc` and bumped the resource-count assertion (`tracedecay://status/files/overview/branches`) from 4 to 5 to include the newer `tracedecay://schema` resource. All 31 tests now pass. ### Changed - **Seven hand-rolled CLI subcommands replaced by the unified `tracedecay tool ` dispatcher.** `query`, `context`, `body`, `impact`, `callers`, `files`, `affected` were each ~50–150 LoC of clap glue duplicating what the MCP tool already declares in its schema. All seven are gone; the same operations are reached as `tracedecay tool query …`, `tracedecay tool body …`, etc. Drops ~600 LoC of dispatch boilerplate from `src/main.rs`. `query` is kept as an alias for the renamed `search` so muscle memory still works. @@ -2641,19 +2641,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [6.0.0] - 2026-05-25 ### Breaking -- **Daemon mode removed.** The `tracedecay daemon` subcommand, autostart flags (`--enable-autostart` / `--disable-autostart`), foreground mode, and all `daemon-kit`-backed service registration are gone. ~1,100 lines of platform glue (launchd plists, systemd user units, Windows SCM, PID files, UAC elevation) deleted. File-watching now lives inside the MCP server itself — it spawns a `notify`-backed watcher that runs `sync_if_stale_silent` for the duration of the agent session, and exits when the agent disconnects. Multiple MCP peers on the same project converge through the existing per-project sync lock plus `sync_if_stale_silent`'s peer-coordination check; no new primitive was needed. Users with a registered autostart service should unload it manually — see "Migration" in `docs/TRACEDECAY-WHATSNEW.md` §6.0.0. +- **Daemon mode removed.** The `tracedecay daemon` subcommand, autostart flags (`--enable-autostart` / `--disable-autostart`), foreground mode, and all `daemon-kit`-backed service registration are gone. ~1,100 lines of platform glue (launchd plists, systemd user units, Windows SCM, PID files, UAC elevation) deleted. File-watching now lives inside the MCP server itself, it spawns a `notify`-backed watcher that runs `sync_if_stale_silent` for the duration of the agent session, and exits when the agent disconnects. Multiple MCP peers on the same project converge through the existing per-project sync lock plus `sync_if_stale_silent`'s peer-coordination check; no new primitive was needed. Users with a registered autostart service should unload it manually, see "Migration" in `docs/TRACEDECAY-WHATSNEW.md` §6.0.0. - **`UserConfig::daemon_debounce` renamed to `watcher_debounce`.** TOML load is backwards-compatible via `#[serde(alias = "daemon_debounce")]` and any config-mutating command rewrites the file with the new name; Rust struct literals referencing the old name are a compile-time break. - **`McpServer::new` now returns `Arc`.** The embedded watcher task captures a `Weak` so it cannot extend the server's lifetime. Embedders that bound the return value continue to compile; destructuring by value or storing into a non-`Arc` field needs to adapt. - **`tracedecay install --agent claude` writes the modern hook shape `{type, command, args}`.** Legacy single-string `"command": " "` entries are detected by `tracedecay doctor` and auto-rewritten using `current_exe()` as the binary path (issue #81). This is a breaking change for any external tooling that introspects `~/.claude/settings.json` and assumed the legacy concatenated form. - **Beta release channel disabled.** `.github/workflows/release-beta.yml` is gated behind `BETA_CHANNEL_ENABLED=false` and a `workflow_dispatch`-only trigger. The code is preserved for future revival; no `*-beta.*` versions will ship from this commit forward. ### Added -- **`tracedecay_redundancy` MCP tool (#83).** AST-level functional-duplicate detector. Computes four signals per function/method body via tree-sitter — AST shape hash, control-flow-graph hash, ordered call-sequence hash, and a 5-gram token-shingle set — then blends them into a `[0, 1]` composite similarity score (weights 0.40 / 0.25 / 0.20 / 0.15). Pairs are bucketed `definite` / `likely` / `naming_only` and ranked by score. Language-agnostic by design: kind walks use raw tree-sitter strings, so the same code path works for every supported grammar. Computation is lazy — fingerprints land in a new `node_fingerprints` table (schema v10) keyed by `(node_id, body source hash)` and persist across MCP sessions. Pairwise comparison is bucketed by body-token count (±25 % window) so it stays sub-quadratic on large repos. +- **`tracedecay_redundancy` MCP tool (#83).** AST-level functional-duplicate detector. Computes four signals per function/method body via tree-sitter. AST shape hash, control-flow-graph hash, ordered call-sequence hash, and a 5-gram token-shingle set, then blends them into a `[0, 1]` composite similarity score (weights 0.40 / 0.25 / 0.20 / 0.15). Pairs are bucketed `definite` / `likely` / `naming_only` and ranked by score. Language-agnostic by design: kind walks use raw tree-sitter strings, so the same code path works for every supported grammar. Computation is lazy, fingerprints land in a new `node_fingerprints` table (schema v10) keyed by `(node_id, body source hash)` and persist across MCP sessions. Pairwise comparison is bucketed by body-token count (±25 % window) so it stays sub-quadratic on large repos. - **`tracedecay_runtime` MCP tool + `tracedecay status --runtime` flag (#80).** Captures a process + database telemetry snapshot: PID, RSS, virtual size, sustained CPU% sampled over 200 ms, uptime, host CPU count, total system memory, DB / WAL / SHM file sizes, `journal_mode` PRAGMA, total indexed source bytes, node and edge counts, and a derived `db / source` bloat ratio. Lets users hitting unexpected resource pressure attach a structured snapshot to a bug report. Text report mirrors the `tracedecay status` layout; JSON output via `--json` for machine consumption. - **`tracedecay_health` `details=true` sub-score breakdown (#82).** Returns per-dimension `{ score, interpretation, raw_count, source }` objects covering acyclicity (with `edges_in_cycles`), depth (`max_chain` / `ideal_chain`), equality (gini + textual interpretation), redundancy (`dead_count` / `total_fns`), modularity (textual label + components-after-hub-removal), and coverage discipline (`skip_test_coverage_count`). The composite `quality_signal` (geometric mean × 10 000) is preserved as the headline figure. ### Changed -- **File-watcher rewritten around `notify-debouncer-full` 0.8.0-rc.2.** Replaces the DIY tokio debounce timer with the maintained library, which coalesces rename pairs, suppresses redundant modify-after-create, and batches event bursts cross-platform. Drop-in transparent to callers — `ProjectWatcher::new(root, debounce)` signature unchanged. +- **File-watcher rewritten around `notify-debouncer-full` 0.8.0-rc.2.** Replaces the DIY tokio debounce timer with the maintained library, which coalesces rename pairs, suppresses redundant modify-after-create, and batches event bursts cross-platform. Drop-in transparent to callers. `ProjectWatcher::new(root, debounce)` signature unchanged. - **Watcher no longer recursive-watches the project root.** Top-level entries are enumerated at startup; `IGNORED_DIRS` (`target/`, `node_modules/`, `.git/`, …) and dotdirs (`.vscode`, `.idea`, …) are *never registered as watches*, so the kernel never reports events for them. The root itself is watched non-recursively to surface new top-level directories appearing after startup. This is the primary mitigation for the high CPU/RAM symptoms reported in #80 on Windows, where `ReadDirectoryChangesW`'s per-watch buffer could be overwhelmed by churn inside a large `node_modules`. - **Direct `notify` dependency dropped.** Pulled transitively through `notify-debouncer-full`; project_watcher imports types via `notify_debouncer_full::notify` to keep a single crate instance in the resolver graph (debouncer 0.8.0-rc.2 depends on `notify` 9.0.0-rc.4). - **`doctor` auto-repair logic for hook entries.** Modern-shape hooks with the wrong subcommand are fixed in place (preserving the user's bin path); legacy single-string hooks are rewritten using `current_exe()` since the embedded path cannot be parsed unambiguously when it contains spaces. Breaks the doctor → install loop that issue #81 reported on Windows path-with-spaces installs. @@ -2685,11 +2685,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [5.1.1] - 2026-05-16 ### Performance -- **`tracedecay_dead_code` no longer times out on chromium-scale repos.** The pre-4.14.8 form ran the leading-wildcard `LIKE '%::test'` chain inside a correlated `NOT EXISTS` on every dead-code candidate row — fast on scirs (0.097 s, 76 K `annotation_usage`) but timed out at the 25 s probe ceiling on chromium, cascade-poisoning every subsequent MCP tool call via JSON-RPC id reuse. 4.14.8's `WITH test_marker_ids AS (...)` CTE attempt regressed scirs from 0.1 s to >60 s because SQLite inlined the single-reference CTE, so the wildcard scan ran per candidate row instead of once; that attempt was reverted in 4.14.9. A first attempt that put marker ids into a single TEMP table and probed via `e2.source IN (SELECT id FROM temp.test_markers)` ALSO failed on chromium: SQLite picked `idx_edges_unique (source, target, kind)` for the correlated subquery and iterated every marker as the outer driver for every candidate (~13 K markers × ~134 K candidates ≈ 1.7 B probes), >60 s. New shape — **three-step resolve + pre-join + probe via TWO TEMP tables**: +- **`tracedecay_dead_code` no longer times out on chromium-scale repos.** The pre-4.14.8 form ran the leading-wildcard `LIKE '%::test'` chain inside a correlated `NOT EXISTS` on every dead-code candidate row, fast on scirs (0.097 s, 76 K `annotation_usage`) but timed out at the 25 s probe ceiling on chromium, cascade-poisoning every subsequent MCP tool call via JSON-RPC id reuse. 4.14.8's `WITH test_marker_ids AS (...)` CTE attempt regressed scirs from 0.1 s to >60 s because SQLite inlined the single-reference CTE, so the wildcard scan ran per candidate row instead of once; that attempt was reverted in 4.14.9. A first attempt that put marker ids into a single TEMP table and probed via `e2.source IN (SELECT id FROM temp.test_markers)` ALSO failed on chromium: SQLite picked `idx_edges_unique (source, target, kind)` for the correlated subquery and iterated every marker as the outer driver for every candidate (~13 K markers × ~134 K candidates ≈ 1.7 B probes), >60 s. New shape. **three-step resolve + pre-join + probe via TWO TEMP tables**: - `Database::collect_test_marker_ids` runs the marker `SELECT` exactly once over the `kind = 'annotation_usage'` partition (indexed via `idx_nodes_kind`). - `Database::populate_test_marker_temp_table` drops + recreates `temp.test_markers` (with `PRIMARY KEY` on `id` so SQLite builds a real B-tree) and bulk-inserts in 500-id chunks. - `Database::populate_test_annotated_targets_temp_table` joins `edges WHERE kind = 'annotates' AND source IN temp.test_markers` once, materialising "which node ids are annotated by any test marker" into `temp.test_annotated_targets` (PK on `target`). ~15 K rows on chromium. - - `find_dead_code`'s outer SELECT then uses `nodes.id NOT IN (SELECT target FROM temp.test_annotated_targets)` — a single PK probe per candidate against a small indexed lookup table, the optimiser cannot re-shape this into a per-marker iteration. + - `find_dead_code`'s outer SELECT then uses `nodes.id NOT IN (SELECT target FROM temp.test_annotated_targets)`, a single PK probe per candidate against a small indexed lookup table, the optimiser cannot re-shape this into a per-marker iteration. - Both temp tables are unconditionally dropped on the wrap path so a failed query does not leak rows to the next caller on the same connection. Inline comment block on `find_dead_code` documents all three prior pathologies (pre-4.14.8, 4.14.8 CTE, single-temp-table attempt) and a `DO NOT regress this` warning to forestall the next attempt. @@ -2704,7 +2704,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | `dead_code {path: "src"}` | 1.10 s | warm | | `dead_code {path: "lib"}` | 1.09 s | warm | - Was 5/5 TIMEOUT @ 25 s pre-fix, cascade-poisoning every subsequent tool in the probe matrix. Direct `sqlite3` runtime measurement (no MCP layer): 0.75 s end-to-end (markers 42 ms → targets 102 ms → main 600 ms). On scirs (76 K `annotation_usage`): 0.6 s end-to-end — a regression-acceptable trade-off vs. the 0.097 s pre-4.14.8 baseline given that chromium went from >25 s timeout to <1.1 s steady-state. The synthetic regression test `tests/graph_test.rs::dead_code_marker_resolve_is_single_pass` (50 K `annotation_usage` / 5 K functions) runs in 1.3 s release / 3.6 s debug with a 5 s assertion ceiling. + Was 5/5 TIMEOUT @ 25 s pre-fix, cascade-poisoning every subsequent tool in the probe matrix. Direct `sqlite3` runtime measurement (no MCP layer): 0.75 s end-to-end (markers 42 ms → targets 102 ms → main 600 ms). On scirs (76 K `annotation_usage`): 0.6 s end-to-end, a regression-acceptable trade-off vs. the 0.097 s pre-4.14.8 baseline given that chromium went from >25 s timeout to <1.1 s steady-state. The synthetic regression test `tests/graph_test.rs::dead_code_marker_resolve_is_single_pass` (50 K `annotation_usage` / 5 K functions) runs in 1.3 s release / 3.6 s debug with a 5 s assertion ceiling. ## [5.0.0] - 2026-05-16 @@ -2712,21 +2712,21 @@ The largest functional jump since 4.0: nine new MCP tools, a cross-session respo ### Added -- **`tracedecay_read`** — mode-aware file read (`full`, `lines`, `map`, `signatures`) with cross-session cache. `map` and `signatures` are graph-only — no source bytes are touched. A re-call on an unchanged file returns a ~30-token `{"unchanged": true, …}` stub. The cache key folds `last_sync_at` for graph-backed modes so a force-reindex correctly invalidates derived rows. -- **`tracedecay_outline`** — flat list of every top-level symbol in a file, with optional kind filter. The cheapest way to orient before zooming into a large file. -- **`tracedecay_implementations`** — find every type implementing a given trait, or every body of a given method name. Returns method bodies with signatures. -- **`tracedecay_unsafe_patterns`** — surface `.unwrap()` / `.expect()` / `panic!` / `todo!` / `unimplemented!` / `unsafe { }` sites with an `in_test` flag. Word-boundary matching avoids `.unwrap_or` false positives; an `exclude_tests` option skips test-shaped paths. -- **`tracedecay_diagnostics`** — runs the project's compile / type checker (cargo / tsc / pyright) and returns structured errors mapped to graph nodes. Replaces the recurring "shell out → parse text → read file" loop with one structured response. Cargo target dir is forced to `.tracedecay/target/` so it can't race with the user's interactive cargo runs. -- **`tracedecay_config`** — query TOML / JSON config files by dotted key path. Single file (`path`) or glob (`glob`); returns parsed value plus a heuristic line number. DB-free — works on uninitialized projects. -- **`tracedecay_signature_search`** — find functions / methods by signature shape: return type, parameter substring, async flag, path filter. All filters AND-compose. -- **`tracedecay_constructors`** — locate every literal-instantiation site of a struct (`Foo { … }`) and report which fields each site sets — plus `missing_fields` relative to the struct's current definition. The classic "I added a required field, what breaks?" question. String- / char-literal awareness and `match` / `if let` / `while let` pattern filtering keep the result list clean. -- **`tracedecay_field_sites`** — partition every `.` reference into reads and writes. Writes include `=`, compound assignments, and `&mut x.field` borrows; `==` and `=>` correctly count as reads. -- **`tracedecay bench` colored console output** — default `tracedecay bench` is now a fixed-width colored table instead of a markdown dump. Compact `k` / `M` numeric units; savings percentages colored by tier (green ≥80 %, yellow ≥50 %, red <50 %); aggregate footer in the same tier color. `--json` is unchanged. +- **`tracedecay_read`**, mode-aware file read (`full`, `lines`, `map`, `signatures`) with cross-session cache. `map` and `signatures` are graph-only, no source bytes are touched. A re-call on an unchanged file returns a ~30-token `{"unchanged": true, …}` stub. The cache key folds `last_sync_at` for graph-backed modes so a force-reindex correctly invalidates derived rows. +- **`tracedecay_outline`**, flat list of every top-level symbol in a file, with optional kind filter. The cheapest way to orient before zooming into a large file. +- **`tracedecay_implementations`**, find every type implementing a given trait, or every body of a given method name. Returns method bodies with signatures. +- **`tracedecay_unsafe_patterns`**, surface `.unwrap()` / `.expect()` / `panic!` / `todo!` / `unimplemented!` / `unsafe { }` sites with an `in_test` flag. Word-boundary matching avoids `.unwrap_or` false positives; an `exclude_tests` option skips test-shaped paths. +- **`tracedecay_diagnostics`**, runs the project's compile / type checker (cargo / tsc / pyright) and returns structured errors mapped to graph nodes. Replaces the recurring "shell out → parse text → read file" loop with one structured response. Cargo target dir is forced to `.tracedecay/target/` so it can't race with the user's interactive cargo runs. +- **`tracedecay_config`**, query TOML / JSON config files by dotted key path. Single file (`path`) or glob (`glob`); returns parsed value plus a heuristic line number. DB-free, works on uninitialized projects. +- **`tracedecay_signature_search`**, find functions / methods by signature shape: return type, parameter substring, async flag, path filter. All filters AND-compose. +- **`tracedecay_constructors`**, locate every literal-instantiation site of a struct (`Foo { … }`) and report which fields each site sets, plus `missing_fields` relative to the struct's current definition. The classic "I added a required field, what breaks?" question. String- / char-literal awareness and `match` / `if let` / `while let` pattern filtering keep the result list clean. +- **`tracedecay_field_sites`**, partition every `.` reference into reads and writes. Writes include `=`, compound assignments, and `&mut x.field` borrows; `==` and `=>` correctly count as reads. +- **`tracedecay bench` colored console output**, default `tracedecay bench` is now a fixed-width colored table instead of a markdown dump. Compact `k` / `M` numeric units; savings percentages colored by tier (green ≥80 %, yellow ≥50 %, red <50 %); aggregate footer in the same tier color. `--json` is unchanged. ### Changed - **Schema v9: cross-session response cache.** New `read_cache` table keyed by `(project_id, session_id, file_path, mode, args_hash)` with `mtime_ns` for freshness. Backs `tracedecay_read`. -- **Schema v9: `Contains` edges denormalized into `nodes.parent_id`.** The same migration folds containment off the edges table and onto a new column. Cleaner queries — `get_children_of(parent_id)` is one indexed lookup — and the read-only SQL layer no longer has to filter by edge kind for every "find members of this container" question. Extractors keep emitting `Contains` edges as before; the storage layer hoists them into `parent_id` at insert time and skips persisting the row. +- **Schema v9: `Contains` edges denormalized into `nodes.parent_id`.** The same migration folds containment off the edges table and onto a new column. Cleaner queries. `get_children_of(parent_id)` is one indexed lookup, and the read-only SQL layer no longer has to filter by edge kind for every "find members of this container" question. Extractors keep emitting `Contains` edges as before; the storage layer hoists them into `parent_id` at insert time and skips persisting the row. ### Migration notes @@ -2738,64 +2738,64 @@ The largest functional jump since 4.0: nine new MCP tools, a cross-session respo ### Performance - **Same `node.child(i)` O(N²) trap fixed in `batch_extractor.rs`.** `visit_top_level`, `visit_label`, `extract_docstring`, and `extract_label_call_sites` all walked top-level children of the Batch program via `root.child(i)` in an index loop. Refactored: `visit_top_level` materialises children once into a `Vec` via cursor (`collect_children` helper) and downstream helpers take `&[TsNode]` + index instead of `(root, index)`. One O(N) allocation up front, O(1) lookups thereafter, no behavior change. -- **Same trap fixed in `powershell_extractor.rs::find_descendant_by_kind`.** The iterative DFS pushed children with `current.child(i)` in a `for i in (0..N).rev()` loop. Replaced with a `TreeCursor` walk + `stack[start..].reverse()` to preserve first-child-pops-first order — matches the `complexity.rs::push_children` pattern from 4.14.10. +- **Same trap fixed in `powershell_extractor.rs::find_descendant_by_kind`.** The iterative DFS pushed children with `current.child(i)` in a `for i in (0..N).rev()` loop. Replaced with a `TreeCursor` walk + `stack[start..].reverse()` to preserve first-child-pops-first order, matches the `complexity.rs::push_children` pattern from 4.14.10. - **Same trap fixed in `clojure_extractor.rs::extract_calls`.** Top-level form iteration over `list_lit` children used `node.child(i)` indexed loop. Replaced with cursor stepping (with `goto_next_sibling` `skip` times for the `skip` parameter). Particularly relevant on Clojure files with hundreds of top-level forms. -- **Same trap fixed in `cobol_extractor.rs::visit_procedure_division`.** The seed pass that collects PROCEDURE DIVISION children into a `Vec` for multi-pass paragraph grouping used `node.child(i)` in a loop — bites on monolithic COBOL files with many paragraphs. Switched to cursor walk; same O(N) materialisation, O(1) downstream indexing. +- **Same trap fixed in `cobol_extractor.rs::visit_procedure_division`.** The seed pass that collects PROCEDURE DIVISION children into a `Vec` for multi-pass paragraph grouping used `node.child(i)` in a loop, bites on monolithic COBOL files with many paragraphs. Switched to cursor walk; same O(N) materialisation, O(1) downstream indexing. ## [4.14.10] - 2026-05-16 ### Performance -- **`count_complexity` (called from every extractor on every function) no longer hits an O(N²) trap on high-fanout AST nodes.** The body-walk in `src/extraction/complexity.rs` seeded its stack and pushed children with `node.child(i)` inside a `for i in 0..N` loop. Tree-sitter's `node.child(i)` is **O(i)** — it walks the linked sibling chain from the first child — so the seed + per-pop push pair was O(N²) for every node along the way. On `kernel/bpf/verifier.c` (20 K lines, monster switch statements with thousands of cases) a single `tracedecay init` showed the progress bar wedged on that one file long enough that users reported it as "stuck"; chromium had files taking ~3 min individually. New `push_children` helper uses a `TreeCursor` (O(1) per sibling step) and reverses the appended slice so LIFO pop order still produces left-to-right traversal. Same fix applied to `extract_call_name`, `extract_macro_name`, and `rightmost_identifier` — all three did the same O(N²) `child(i)` scan over identifier candidates. Measured on `verifier.c` after the fix: 78 ms end-to-end (file read + parse + extract). Includes `examples/bench_extract.rs` so you can re-measure with `cargo run --release --example bench_extract `. +- **`count_complexity` (called from every extractor on every function) no longer hits an O(N²) trap on high-fanout AST nodes.** The body-walk in `src/extraction/complexity.rs` seeded its stack and pushed children with `node.child(i)` inside a `for i in 0..N` loop. Tree-sitter's `node.child(i)` is **O(i)**, it walks the linked sibling chain from the first child, so the seed + per-pop push pair was O(N²) for every node along the way. On `kernel/bpf/verifier.c` (20 K lines, monster switch statements with thousands of cases) a single `tracedecay init` showed the progress bar wedged on that one file long enough that users reported it as "stuck"; chromium had files taking ~3 min individually. New `push_children` helper uses a `TreeCursor` (O(1) per sibling step) and reverses the appended slice so LIFO pop order still produces left-to-right traversal. Same fix applied to `extract_call_name`, `extract_macro_name`, and `rightmost_identifier`, all three did the same O(N²) `child(i)` scan over identifier candidates. Measured on `verifier.c` after the fix: 78 ms end-to-end (file read + parse + extract). Includes `examples/bench_extract.rs` so you can re-measure with `cargo run --release --example bench_extract `. ## [4.14.9] - 2026-05-16 ### Fixed -- **Revert the 4.14.8 `find_dead_code` CTE refactor — it was a massive regression on real repos.** 4.14.8 moved the test-marker name match into `WITH test_marker_ids AS (...)` thinking that would amortise the leading-wildcard `LIKE`. In practice SQLite does not always materialise a single-reference CTE, and `e2.source IN (SELECT id FROM test_marker_ids)` inside a correlated `NOT EXISTS` degenerated into a per-row scan of the full `annotation_usage` table. On scirs (76 K annotation_usage rows, 153 K annotates edges) `tracedecay_dead_code` went from **0.097 s** (pre-4.14.8) to **>60 s timeout**, which hung the MCP probe matrix — every subsequent tool then appeared to time out because the late response poisoned the JSON-RPC id matching (the cascade caveat in `scripts/mcp_probe/README.md`). The original `JOIN nodes a ON a.id = e2.source` form works because `idx_edges_target_kind` narrows to the (typically 0-3) annotates edges per candidate first, then joins via the nodes PK, so the LIKE only runs on that small per-candidate slice. A `Do NOT lift this into a CTE` comment is left at the call site so future refactors don't repeat the mistake. Other 4.14.8 perf changes (SCC frame clone, multi-source BFS, lines cache, dedup'd FTS terms, file-content cache, cycle-path borrowing, inheritance-depth CTE shape, has_bare_call fast path, placeholder builder) are kept as-is. +- **Revert the 4.14.8 `find_dead_code` CTE refactor, it was a massive regression on real repos.** 4.14.8 moved the test-marker name match into `WITH test_marker_ids AS (...)` thinking that would amortise the leading-wildcard `LIKE`. In practice SQLite does not always materialise a single-reference CTE, and `e2.source IN (SELECT id FROM test_marker_ids)` inside a correlated `NOT EXISTS` degenerated into a per-row scan of the full `annotation_usage` table. On scirs (76 K annotation_usage rows, 153 K annotates edges) `tracedecay_dead_code` went from **0.097 s** (pre-4.14.8) to **>60 s timeout**, which hung the MCP probe matrix, every subsequent tool then appeared to time out because the late response poisoned the JSON-RPC id matching (the cascade caveat in `scripts/mcp_probe/README.md`). The original `JOIN nodes a ON a.id = e2.source` form works because `idx_edges_target_kind` narrows to the (typically 0-3) annotates edges per candidate first, then joins via the nodes PK, so the LIKE only runs on that small per-candidate slice. A `Do NOT lift this into a CTE` comment is left at the call site so future refactors don't repeat the mistake. Other 4.14.8 perf changes (SCC frame clone, multi-source BFS, lines cache, dedup'd FTS terms, file-content cache, cycle-path borrowing, inheritance-depth CTE shape, has_bare_call fast path, placeholder builder) are kept as-is. ## [4.14.8] - 2026-05-16 ### Performance -- **Tarjan SCC no longer clones the entire neighbor list per edge visited.** The iterative DFS in `graph/scc.rs` used `work.last_mut().cloned()` on each loop iteration — that deep-copies the top tuple `(node, neighbors, idx)` including the whole `Vec` of neighbors, once per neighbor visited (so ~`out-degree × visits` full Vec clones during a node's life). Rewrote the loop to peek the top frame with `work.last_mut()` and clone only the two values actually needed (`node` and `next`) before any `work.push(...)`. Every SCC consumer benefits: `tracedecay_circular`, `tracedecay_port_order`, and (since 4.14.7) `tracedecay_recursion`. -- **`tracedecay_diff_context` walks the impact radius once for the union of modified symbols, not once per symbol.** The old loop called `get_impact_radius(node.id, depth)` for every modified node — each call ran an independent BFS from scratch, so any downstream node reachable from K modified ancestors got re-traversed K times. New `GraphTraverser::get_impact_radius_multi(seed_ids, max_depth)` does one BFS seeded with all modified node IDs and a single shared `visited` set; the result has every reachable node visited at most once. Surfaces as `TraceDecay::get_impact_radius_multi`. Particularly impactful on diamond-dependency hotspots (shared utility files reachable from every changed module). -- **`tracedecay_recursion` caches source-file lines instead of re-splitting on every self-edge check.** `is_direct_self_call` was caching the raw `String` source but then doing `let lines: Vec<&str> = source.lines().collect();` on each call — for a 10 k-line file with N self-edges, that's N × 10 k allocations purely to throw away. Cache changed to `HashMap>>` so the line vector is built once per file. +- **Tarjan SCC no longer clones the entire neighbor list per edge visited.** The iterative DFS in `graph/scc.rs` used `work.last_mut().cloned()` on each loop iteration, that deep-copies the top tuple `(node, neighbors, idx)` including the whole `Vec` of neighbors, once per neighbor visited (so ~`out-degree × visits` full Vec clones during a node's life). Rewrote the loop to peek the top frame with `work.last_mut()` and clone only the two values actually needed (`node` and `next`) before any `work.push(...)`. Every SCC consumer benefits: `tracedecay_circular`, `tracedecay_port_order`, and (since 4.14.7) `tracedecay_recursion`. +- **`tracedecay_diff_context` walks the impact radius once for the union of modified symbols, not once per symbol.** The old loop called `get_impact_radius(node.id, depth)` for every modified node, each call ran an independent BFS from scratch, so any downstream node reachable from K modified ancestors got re-traversed K times. New `GraphTraverser::get_impact_radius_multi(seed_ids, max_depth)` does one BFS seeded with all modified node IDs and a single shared `visited` set; the result has every reachable node visited at most once. Surfaces as `TraceDecay::get_impact_radius_multi`. Particularly impactful on diamond-dependency hotspots (shared utility files reachable from every changed module). +- **`tracedecay_recursion` caches source-file lines instead of re-splitting on every self-edge check.** `is_direct_self_call` was caching the raw `String` source but then doing `let lines: Vec<&str> = source.lines().collect();` on each call, for a 10 k-line file with N self-edges, that's N × 10 k allocations purely to throw away. Cache changed to `HashMap>>` so the line vector is built once per file. - **`tracedecay_recursion` cycle-path DFS uses borrowed `&str` and stops once the limit is hit.** `cycle_path_for_scc` / `dfs_cycle_path` previously used `Vec` / `HashSet` on hot paths, allocating a `String` per neighbor visit even though every id already lived in `scc_set`. Switched to `&str` borrows over the SCC's existing storage. The outer loop also sorts SCCs by length first and short-circuits as soon as `cycles.len() == limit`, so we no longer enumerate every cycle in a giant mutually-recursive graph before truncating. - **`tracedecay_inheritance_depth` CTE collapses the hierarchy before joining `nodes`.** The recursive CTE produced one row per (leaf, depth) pair across the full hierarchy; the outer SELECT then ran the `file_path LIKE ?` filter over all of them. Wrapped the hierarchy in a `leaf_depths` CTE that `GROUP BY leaf_id` first, so the path filter and node join only see distinct leaves. -- **`tracedecay_dead_code` resolves the test-marker annotation set in a single CTE pass.** Each candidate dead-code row previously re-evaluated `a.name LIKE '%::test'` (and three more leading-wildcard `LIKE`s) — none of those can use an index, so the cost scaled with `dead-candidates × annotation_usage`. New `WITH test_marker_ids AS (…)` resolves the marker ids once; the dead-code subquery then checks `e2.source IN (SELECT id FROM test_marker_ids)`. -- **`ContextBuilder::find_entry_points` deduplicates FTS terms across the five search rounds.** Full query, extracted symbols, stem variants, and agent-provided extra keywords overlap heavily (e.g. `symbol "foo"` and `keyword "foo"` produce identical FTS results); each duplicate term cost a full DB roundtrip on the single-connection libsql. Terms are now collected into one ordered, deduplicated list before any `search_nodes` calls — original priority preserved (full query → symbols → stems → keywords) so the `cap`-based early exit still favours higher-signal terms first. +- **`tracedecay_dead_code` resolves the test-marker annotation set in a single CTE pass.** Each candidate dead-code row previously re-evaluated `a.name LIKE '%::test'` (and three more leading-wildcard `LIKE`s), none of those can use an index, so the cost scaled with `dead-candidates × annotation_usage`. New `WITH test_marker_ids AS (…)` resolves the marker ids once; the dead-code subquery then checks `e2.source IN (SELECT id FROM test_marker_ids)`. +- **`ContextBuilder::find_entry_points` deduplicates FTS terms across the five search rounds.** Full query, extracted symbols, stem variants, and agent-provided extra keywords overlap heavily (e.g. `symbol "foo"` and `keyword "foo"` produce identical FTS results); each duplicate term cost a full DB roundtrip on the single-connection libsql. Terms are now collected into one ordered, deduplicated list before any `search_nodes` calls, original priority preserved (full query → symbols → stems → keywords) so the `cap`-based early exit still favours higher-signal terms first. - **`ContextBuilder` reads each source file at most once per `build_context`.** Both `extract_code_blocks` and `merge_adjacent_blocks` previously called `get_code(node)` which did its own `fs::read_to_string` per call; merging K adjacent blocks meant K disk reads of the same file. Introduced `get_code_cached(node, file_cache)` that consults a shared `HashMap>`; `build_context` allocates one cache for the request and threads it through both phases. -- **`has_bare_call` short-circuits lines with no `(` and rejects substring matches on both identifier boundaries.** Common short names like `new` / `get` / `len` triggered `line.match_indices(name)` over the full line and then filtered post-hoc — pathological on comment/docstring lines that mention the name without calling it. Added a `line.contains('(')` fast path plus an after-byte identifier-boundary check (so `new` no longer pre-matches inside `newer`). -- **`get_nodes_by_ids` and friends build their `IN (?, ?, …)` placeholder string in one allocation.** Previous `(1..=ids.len()).map(|i| format!("?{i}")).collect()` allocated one `String` per id and an intermediate `Vec` per call — visible on profiles because `traverse_bfs` invokes `get_nodes_by_ids` once per BFS level. New `build_qmark_placeholders(n)` writes `?, ?, ?, …` into a single pre-sized `String`; libsql binds anonymous markers positionally so behaviour is unchanged. Applied at `get_nodes_by_ids`, `batch_incoming_call_counts`, and `search_nodes_by_exact_name`. +- **`has_bare_call` short-circuits lines with no `(` and rejects substring matches on both identifier boundaries.** Common short names like `new` / `get` / `len` triggered `line.match_indices(name)` over the full line and then filtered post-hoc, pathological on comment/docstring lines that mention the name without calling it. Added a `line.contains('(')` fast path plus an after-byte identifier-boundary check (so `new` no longer pre-matches inside `newer`). +- **`get_nodes_by_ids` and friends build their `IN (?, ?, …)` placeholder string in one allocation.** Previous `(1..=ids.len()).map(|i| format!("?{i}")).collect()` allocated one `String` per id and an intermediate `Vec` per call, visible on profiles because `traverse_bfs` invokes `get_nodes_by_ids` once per BFS level. New `build_qmark_placeholders(n)` writes `?, ?, ?, …` into a single pre-sized `String`; libsql binds anonymous markers positionally so behaviour is unchanged. Applied at `get_nodes_by_ids`, `batch_incoming_call_counts`, and `search_nodes_by_exact_name`. ## [4.14.5] - 2026-05-16 ### Fixed -- **`tracedecay_inheritance_depth` no longer explodes on cyclic / near-cyclic trait-bound graphs.** The recursive CTE in `get_inheritance_depth` had a depth bound of 50 but no cycle detection, so any cycle in the `extends` graph (common in Rust workspaces where generic trait bounds form indirect cycles) made the CTE traverse the cycle up to the depth limit from every entry point. On polkadot-sdk (959 `extends` edges) the query took >60 s and timed out; smaller workspaces (sotf 89, scirs 90, sonium 5) were fine. Fix tracks visited node IDs in a path column and skips recursion when the next target is already in the path — query completes in 0.55 s on polkadot end-to-end through MCP. Regression test `test_get_inheritance_depth_terminates_on_cycle` constructs a 3-node graph with an A↔B cycle and a C→A edge, then asserts the query returns in <2 s with all three nodes reported at finite, bounded depth. +- **`tracedecay_inheritance_depth` no longer explodes on cyclic / near-cyclic trait-bound graphs.** The recursive CTE in `get_inheritance_depth` had a depth bound of 50 but no cycle detection, so any cycle in the `extends` graph (common in Rust workspaces where generic trait bounds form indirect cycles) made the CTE traverse the cycle up to the depth limit from every entry point. On polkadot-sdk (959 `extends` edges) the query took >60 s and timed out; smaller workspaces (sotf 89, scirs 90, sonium 5) were fine. Fix tracks visited node IDs in a path column and skips recursion when the next target is already in the path, query completes in 0.55 s on polkadot end-to-end through MCP. Regression test `test_get_inheritance_depth_terminates_on_cycle` constructs a 3-node graph with an A↔B cycle and a C→A edge, then asserts the query returns in <2 s with all three nodes reported at finite, bounded depth. ### Added -- **`scripts/mcp_probe/` — MCP test-matrix harness.** Drives a fresh `tracedecay serve` MCP server over stdio against a configurable set of real repos and exercises every read-only tool with 5 query variants per language, producing a per-tool / per-repo status table that flags tools needing investigation (errors, timeouts, empty results, perf regressions). Same harness doubles as a benchmark — per-call timings are logged, repos serve as a fixed corpus for cross-version perf comparison. Pluggable per-language probe modules under `tools/`; Rust ships included (`tools/rust.py` exercises all 50 MCP tools). `repos.toml` (overridable via `$TRACEDECAY_PROBE_REPOS`) holds the repo set. JSON-RPC ids are strictly matched in `probe.py::McpClient` so a slow call cannot poison subsequent ones; `isolated.py` adds a fresh-server-per-tool retry loop for tools that already showed a real timeout. `build_matrix.py` renders the log to markdown. Used to find and prove this release's `inheritance_depth` cycle bug; same harness verifies the 4.14.4 fixes stay green across the four real repos (sotf, sonium, scirs, polkadot-sdk). +- **`scripts/mcp_probe/`. MCP test-matrix harness.** Drives a fresh `tracedecay serve` MCP server over stdio against a configurable set of real repos and exercises every read-only tool with 5 query variants per language, producing a per-tool / per-repo status table that flags tools needing investigation (errors, timeouts, empty results, perf regressions). Same harness doubles as a benchmark, per-call timings are logged, repos serve as a fixed corpus for cross-version perf comparison. Pluggable per-language probe modules under `tools/`; Rust ships included (`tools/rust.py` exercises all 50 MCP tools). `repos.toml` (overridable via `$TRACEDECAY_PROBE_REPOS`) holds the repo set. JSON-RPC ids are strictly matched in `probe.py::McpClient` so a slow call cannot poison subsequent ones; `isolated.py` adds a fresh-server-per-tool retry loop for tools that already showed a real timeout. `build_matrix.py` renders the log to markdown. Used to find and prove this release's `inheritance_depth` cycle bug; same harness verifies the 4.14.4 fixes stay green across the four real repos (sotf, sonium, scirs, polkadot-sdk). ## [4.14.4] - 2026-05-16 ### Fixed -- **`tracedecay_doc_coverage` reports public fields, enum variants, constants, statics, type aliases, properties, …** The query previously filtered to `kind IN ('function', 'method', 'class', 'interface', 'trait', 'struct', 'enum', 'module')` — so a Rust file full of `pub` undocumented struct fields reported `total_undocumented: 0` even though `tracedecay_module_api` listed dozens of public symbols on the same file. Kind allow-list expanded to include `field`, `enum_variant`, `const`, `static`, `type_alias`, `property`, `csharp_property`, `record`, `data_class`, `sealed_class`, `object`, `case_class`, `kotlin_object`, `inner_class`, `abstract_method`, `constructor`, `struct_method`, `val`, `var`, `mixin`, `extension`, `union`, `typedef`. Excludes `namespace` and `package` — those are aggregators that almost never carry their own doc and would just drown out actionable items. Single `const` deduplicates the prefix and no-prefix branches. Verified end-to-end on real DBs: `biquad.rs` in sotf went from 0 → 23 undocumented public symbols; polkadot-sdk reports ~51 K with a sensible per-kind breakdown. Regression test `test_get_undocumented_public_symbols_includes_fields_and_variants`. +- **`tracedecay_doc_coverage` reports public fields, enum variants, constants, statics, type aliases, properties, …** The query previously filtered to `kind IN ('function', 'method', 'class', 'interface', 'trait', 'struct', 'enum', 'module')`, so a Rust file full of `pub` undocumented struct fields reported `total_undocumented: 0` even though `tracedecay_module_api` listed dozens of public symbols on the same file. Kind allow-list expanded to include `field`, `enum_variant`, `const`, `static`, `type_alias`, `property`, `csharp_property`, `record`, `data_class`, `sealed_class`, `object`, `case_class`, `kotlin_object`, `inner_class`, `abstract_method`, `constructor`, `struct_method`, `val`, `var`, `mixin`, `extension`, `union`, `typedef`. Excludes `namespace` and `package`, those are aggregators that almost never carry their own doc and would just drown out actionable items. Single `const` deduplicates the prefix and no-prefix branches. Verified end-to-end on real DBs: `biquad.rs` in sotf went from 0 → 23 undocumented public symbols; polkadot-sdk reports ~51 K with a sensible per-kind breakdown. Regression test `test_get_undocumented_public_symbols_includes_fields_and_variants`. - **`tracedecay_dead_code` excludes `#[test]`-annotated functions whose name does NOT start with `test`.** The previous filter was name-prefix-only (`name NOT LIKE 'test%'`), so `#[test] fn from_measurement_slope_excludes_lfe()` and similar leaked through. The libtest harness is an implicit caller that never appears as a graph edge, so without this filter most Rust tests with non-`test*` names got misreported as dead. Detection now walks the `annotates` edges and excludes any node whose annotation_usage name is `'test'`, `'…::test'` (covers `tokio::test`, `async_std::test`), `'wasm_bindgen_test'`, or `'…::wasm_bindgen_test'`. The JOIN is constrained to `a.kind = 'annotation_usage'` to avoid accidental matches. Real-DB impact: sotf 1794 → 540 dead functions (-70 %), sonium 778 → 209 (-73 %), scirs 4 839 → 2 469 (-49 %), polkadot-sdk **12 136 → 2 295 (-81 %)**. Manual spot-check on polkadot confirmed every dropped name is unambiguously a `#[test]` function. Regression test `test_find_dead_code_excludes_test_annotated`. -- **`tracedecay_ast_grep_rewrite` surfaces a useful message when `ast-grep` exits non-zero with empty stderr.** ast-grep returns exit 1 with completely silent stdout/stderr when its pattern matches 0 nodes or when the file's language can't be inferred from the extension (`.txt`). The previous error string was `"ast-grep failed: "` — empty trailer, no actionable info. New handler falls back through stderr → stdout → an explicit explanation listing likely causes (pattern matched 0 nodes, language not inferred, invalid pattern), plus the exit code and the file + pattern that failed. Regression test `ast_grep_rewrite_surfaces_useful_error_on_empty_stderr`. -- **`tracedecay_port_status` no longer cross-matches methods that share a name but belong to different parent types.** The match key was `(name.to_lowercase(), kind_compat_group)`, so `Biquad::new` matched `Adaa::new`, `Biquad::process` matched any other `process`, and so on — useless on Rust workspaces where every type has a `new`, `process`, `fmt`, `reset`, etc. Match key now also includes the parent qualifier (stripped of generics — `Biquad` and `Biquad` resolve identically) for kinds that have one (`method`, `field`, `enum_variant`, `struct_method`, `abstract_method`, `constructor`, `csharp_property`, `property`, `val`, `var`); top-level kinds (struct, function, enum, trait) keep name-only matching since their containing context in `qualified_name` is just a file path. Regression tests `port_status_does_not_match_methods_of_different_parents` (Biquad in dir A, Adaa in dir B — must NOT match) and `port_status_matches_methods_with_same_parent_type` (Biquad in both dirs — must match). -- **`tracedecay_branch_diff` returns an empty diff when base == head instead of erroring.** Previous behaviour was `MCP error -32603: base and head are the same branch`, inconsistent with `tracedecay_pr_context` which already handled the same case by returning empty arrays. Same-ref now returns the normal JSON shape with `summary: {added:0, removed:0, changed:0}`, empty `added`/`removed`/`changed` arrays, and a `note` field explaining the equality — so callers can rely on a single response shape. Regression test `branch_diff_returns_empty_when_base_equals_head`. +- **`tracedecay_ast_grep_rewrite` surfaces a useful message when `ast-grep` exits non-zero with empty stderr.** ast-grep returns exit 1 with completely silent stdout/stderr when its pattern matches 0 nodes or when the file's language can't be inferred from the extension (`.txt`). The previous error string was `"ast-grep failed: "`, empty trailer, no actionable info. New handler falls back through stderr → stdout → an explicit explanation listing likely causes (pattern matched 0 nodes, language not inferred, invalid pattern), plus the exit code and the file + pattern that failed. Regression test `ast_grep_rewrite_surfaces_useful_error_on_empty_stderr`. +- **`tracedecay_port_status` no longer cross-matches methods that share a name but belong to different parent types.** The match key was `(name.to_lowercase(), kind_compat_group)`, so `Biquad::new` matched `Adaa::new`, `Biquad::process` matched any other `process`, and so on, useless on Rust workspaces where every type has a `new`, `process`, `fmt`, `reset`, etc. Match key now also includes the parent qualifier (stripped of generics. `Biquad` and `Biquad` resolve identically) for kinds that have one (`method`, `field`, `enum_variant`, `struct_method`, `abstract_method`, `constructor`, `csharp_property`, `property`, `val`, `var`); top-level kinds (struct, function, enum, trait) keep name-only matching since their containing context in `qualified_name` is just a file path. Regression tests `port_status_does_not_match_methods_of_different_parents` (Biquad in dir A, Adaa in dir B, must NOT match) and `port_status_matches_methods_with_same_parent_type` (Biquad in both dirs, must match). +- **`tracedecay_branch_diff` returns an empty diff when base == head instead of erroring.** Previous behaviour was `MCP error -32603: base and head are the same branch`, inconsistent with `tracedecay_pr_context` which already handled the same case by returning empty arrays. Same-ref now returns the normal JSON shape with `summary: {added:0, removed:0, changed:0}`, empty `added`/`removed`/`changed` arrays, and a `note` field explaining the equality, so callers can rely on a single response shape. Regression test `branch_diff_returns_empty_when_base_equals_head`. ## [4.14.1] - 2026-05-15 ### Fixed - **`tracedecay_search` always ranks definitions above `use` re-exports.** BM25 was scoring short `pub use crate::operator::LinearOperator;` rows highly enough that five re-exports outranked the actual `pub trait LinearOperator { … }` definition. Sort now uses a coarse `kind_tier` as the primary key (defs tier 0, impl tier 1, values/members tier 2, modules tier 3, `use`/`include`/annotation usage tier 4); BM25 score is secondary within a tier. Added a per-row exact-name match boost (+10) so a trait named exactly `Foo` beats a `Method` whose qualified name happens to contain `Foo`. Regression test `search_ranks_trait_definition_above_use_reexports` constructs a trait plus five `pub use` re-exports across sibling modules and asserts the trait is hit #1. -- **`tracedecay_dead_code` no longer treats `annotates` / `derives_macro` / `contains` edges as "this function is alive" evidence.** Real-world Rust pervasively uses `#[inline]`, `#[derive(Debug)]`, and similar attributes — every annotation_usage node creates an `annotates` edge pointing at the function, which the previous `kind != 'contains'` filter accepted as a live reference. Result on the sonium codebase: 0 dead functions across 5,715. Narrowed the SQL filter to an explicit allowlist of real-use edges: `calls`, `implements`, `extends`, `type_of`, `returns`, `receives`, `uses`. Regression test `dead_code_flags_unreferenced_fn_with_attribute` exercises the `#[inline]` case. -- **`tracedecay_unused_imports` handles grouped imports (`use std::collections::{HashMap, HashSet};`).** The previous parser treated the literal string `{HashMap, HashSet}` as one identifier and never matched it against the file body, so every grouped import was effectively ignored — explaining the user's "0 unused across 3,404 use nodes" report. A new `identifiers_from_use_path` helper splits grouped/aliased/nested forms (`foo::bar`, `foo::bar as baz`, `foo::{a, b as c}`, `foo::{a, nested::b}`, `foo::{self, bar}`), and the handler now reports one entry per truly-unused identifier with an `unused: ` field. Regression test `unused_imports_handles_grouped_use` verifies the unused half of a grouped use is flagged while the used half is not. -- **`tracedecay_changelog` filters deleted-subtree directory entries.** When an entire subtree was removed in a diff, gix yielded a directory-mode deletion entry whose path was gone from disk by the time the post-hoc `is_dir()` check ran — so directories like `crates/sonium-bem` slipped through as `removed_or_not_indexed`. `git_diff_files` now inspects `entry_mode.is_tree()` on each gix `Change` record (addition/modification/deletion/rewrite) and never pushes a tree entry into the changed-files list. The disk-based `is_dir()` filter is kept as belt-and-suspenders for additions/modifications. Regression test `changelog_filters_deleted_directory_entries` synthesises a `git rm -r crates/` commit and asserts no non-`.rs` paths appear in `changed_files`. -- **`tracedecay_diff_context.modified_symbols` dedupes by node id and dedupes the input `files` array.** Callers that synthesised the file list from upstream tooling (directory walks, multi-source mergers) sometimes passed the same path multiple times — `hmatrix.rs` was reported up to 7× in a row. Added a `modified_seen: HashSet` to guard pushes and an early `files` dedup pass. Regression test `diff_context_dedupes_modified_symbols_on_duplicate_input` passes the same path three times and asserts unique node ids in the output. +- **`tracedecay_dead_code` no longer treats `annotates` / `derives_macro` / `contains` edges as "this function is alive" evidence.** Real-world Rust pervasively uses `#[inline]`, `#[derive(Debug)]`, and similar attributes, every annotation_usage node creates an `annotates` edge pointing at the function, which the previous `kind != 'contains'` filter accepted as a live reference. Result on the sonium codebase: 0 dead functions across 5,715. Narrowed the SQL filter to an explicit allowlist of real-use edges: `calls`, `implements`, `extends`, `type_of`, `returns`, `receives`, `uses`. Regression test `dead_code_flags_unreferenced_fn_with_attribute` exercises the `#[inline]` case. +- **`tracedecay_unused_imports` handles grouped imports (`use std::collections::{HashMap, HashSet};`).** The previous parser treated the literal string `{HashMap, HashSet}` as one identifier and never matched it against the file body, so every grouped import was effectively ignored, explaining the user's "0 unused across 3,404 use nodes" report. A new `identifiers_from_use_path` helper splits grouped/aliased/nested forms (`foo::bar`, `foo::bar as baz`, `foo::{a, b as c}`, `foo::{a, nested::b}`, `foo::{self, bar}`), and the handler now reports one entry per truly-unused identifier with an `unused: ` field. Regression test `unused_imports_handles_grouped_use` verifies the unused half of a grouped use is flagged while the used half is not. +- **`tracedecay_changelog` filters deleted-subtree directory entries.** When an entire subtree was removed in a diff, gix yielded a directory-mode deletion entry whose path was gone from disk by the time the post-hoc `is_dir()` check ran, so directories like `crates/sonium-bem` slipped through as `removed_or_not_indexed`. `git_diff_files` now inspects `entry_mode.is_tree()` on each gix `Change` record (addition/modification/deletion/rewrite) and never pushes a tree entry into the changed-files list. The disk-based `is_dir()` filter is kept as belt-and-suspenders for additions/modifications. Regression test `changelog_filters_deleted_directory_entries` synthesises a `git rm -r crates/` commit and asserts no non-`.rs` paths appear in `changed_files`. +- **`tracedecay_diff_context.modified_symbols` dedupes by node id and dedupes the input `files` array.** Callers that synthesised the file list from upstream tooling (directory walks, multi-source mergers) sometimes passed the same path multiple times. `hmatrix.rs` was reported up to 7× in a row. Added a `modified_seen: HashSet` to guard pushes and an early `files` dedup pass. Regression test `diff_context_dedupes_modified_symbols_on_duplicate_input` passes the same path three times and asserts unique node ids in the output. - **`tracedecay_pr_context` collapses Cargo.toml into a single `config_summary` entry.** Behaviour was already present; added the regression test `pr_context_collapses_cargo_toml_keys` which synthesises a real git history with a 50-dependency Cargo.toml bump and asserts at most one Cargo.toml entry surfaces (kind = `config_summary`). -- **`tracedecay_circular` SCC disjointness stress test.** Added `circular_emits_disjoint_sccs_under_load` — five 3-file cycles connected by non-cyclic DAG-style tails — to guard against any future SCC implementation drift that might let a file leak into more than one cycle entry. +- **`tracedecay_circular` SCC disjointness stress test.** Added `circular_emits_disjoint_sccs_under_load`, five 3-file cycles connected by non-cyclic DAG-style tails, to guard against any future SCC implementation drift that might let a file leak into more than one cycle entry. ### Added -- **`tracedecay_port_order` surfaces intra-cycle ordering signals.** Each cycle entry now reports per-symbol `in_cycle_in_degree` and `in_cycle_out_degree`, a file-level `members_in_cycle` breakdown ranked by member count, an explicit `entry_point` (the SCC member with the smallest in-cycle out-degree — leaf-most, the natural starting point), and a `break_point_candidate` (the highest in-cycle in-degree node, the hub whose call sites are the most-effective refactor target). Replaces the previous flat blob of 200+ symbols with no guidance on where to start. Regression test `port_order_provides_intra_cycle_ordering` wires a 4-node SCC with one obvious hub and asserts `break_point_candidate.name == "h"`. +- **`tracedecay_port_order` surfaces intra-cycle ordering signals.** Each cycle entry now reports per-symbol `in_cycle_in_degree` and `in_cycle_out_degree`, a file-level `members_in_cycle` breakdown ranked by member count, an explicit `entry_point` (the SCC member with the smallest in-cycle out-degree, leaf-most, the natural starting point), and a `break_point_candidate` (the highest in-cycle in-degree node, the hub whose call sites are the most-effective refactor target). Replaces the previous flat blob of 200+ symbols with no guidance on where to start. Regression test `port_order_provides_intra_cycle_ordering` wires a 4-node SCC with one obvious hub and asserts `break_point_candidate.name == "h"`. ### Changed - **`tracedecay_ast_grep_rewrite` is conditionally registered.** The tool is only advertised via `tools/list` when the external `ast-grep` binary is on PATH at server-startup time (cached via `OnceLock` so we don't fork on every `tools/list` request). When the binary is missing, models never see a tool that would immediately return "ast-grep is not installed" on first call. `tracedecay::mcp::tools::ast_grep_available()` is now public; tests in `mcp_handler_test::test_tool_definitions_complete` and `mcp_test::test_tool_definitions_count` branch on it so they pass on hosts with or without the binary installed. @@ -2803,15 +2803,15 @@ The largest functional jump since 4.0: nine new MCP tools, a cross-session respo ## [4.14.0] - 2026-05-15 ### Fixed -- **`tracedecay_run_affected_tests` dispatches directly-changed test files.** Previously the handler only walked callers of every node in `changed_paths` — `#[test]` functions are leaves with no callers, so a PR that only touched `tests/foo.rs` returned "no tests cover the changed paths" and skipped running anything. The handler now also dispatches test functions whose file is itself in `changed_paths` (either via `is_test_file` path heuristic or `#[test]` annotation), with the test recorded as covering itself in `covers_source_ids`. +- **`tracedecay_run_affected_tests` dispatches directly-changed test files.** Previously the handler only walked callers of every node in `changed_paths`. `#[test]` functions are leaves with no callers, so a PR that only touched `tests/foo.rs` returned "no tests cover the changed paths" and skipped running anything. The handler now also dispatches test functions whose file is itself in `changed_paths` (either via `is_test_file` path heuristic or `#[test]` annotation), with the test recorded as covering itself in `covers_source_ids`. - **`parse_derives_in_attr_block` handles rustfmt's multi-line derive blocks.** The previous line-bounded scanner only matched `#[derive(...)]` when the closing `)` was on the same line, so rustfmt's split form (`#[derive(\n Debug,\n Clone,\n)]`) dropped every derive. The parser now joins the attribute-block lines and scans for `#[derive(` ... `)` across the whole region. Two new unit tests (`parses_multiline_derive_attribute`, `parses_multiline_derive_mixed_with_single_line`) cover the split form. - **`tracedecay_diagnose` normalises absolute and backslash paths.** Cargo emits absolute spans when `--manifest-path` points outside cwd, and Windows cargo emits backslash-separated paths; neither matches the indexed forward-slash, project-relative form. `node_at_location` now calls a new `normalize_lookup_path` helper that (1) replaces `\` with `/`, (2) strips the canonicalised project-root prefix for absolutes, and (3) falls back to a raw prefix strip when canonicalisation fails. A diagnostic spanning either form now maps to the correct node. -- **Resolver kind-compatibility filter now applies to the same-file blocklist branches (bug #11 follow-up).** PR8's filter was wired into the main `try_exact_name_match` / `try_qualified_match` paths but not the two `CROSS_FILE_BLOCKLIST` branches in `try_exact_name_match` and `try_exact_name_match_simple`. Common blocklisted names (`new`, `default`, `clone`, …) could still bind a `Calls` reference to a same-file non-callable — a struct or const sharing the name. Both branches now filter candidates through `kind_compatible` before declaring a same-file match. Regression test `resolver_blocklist_branch_respects_kind_filter` reproduces the case (`struct new` + `caller() { let _ = new(); }`) and asserts callees only include callable kinds. +- **Resolver kind-compatibility filter now applies to the same-file blocklist branches (bug #11 follow-up).** PR8's filter was wired into the main `try_exact_name_match` / `try_qualified_match` paths but not the two `CROSS_FILE_BLOCKLIST` branches in `try_exact_name_match` and `try_exact_name_match_simple`. Common blocklisted names (`new`, `default`, `clone`, …) could still bind a `Calls` reference to a same-file non-callable, a struct or const sharing the name. Both branches now filter candidates through `kind_compatible` before declaring a same-file match. Regression test `resolver_blocklist_branch_respects_kind_filter` reproduces the case (`struct new` + `caller() { let _ = new(); }`) and asserts callees only include callable kinds. ## [4.13.0] - 2026-05-15 ### Fixed -- **Resolver kind-compatibility filter (bug #11)** — `tracedecay_rank --edge-kind implements` (and every downstream tool: `tracedecay_impls`, `tracedecay_type_hierarchy`, `tracedecay_callees`'s trait dispatch, …) was poisoned by the resolver fuzzy-binding `impl Default for X` to whatever local node happened to share the name `Default`. The sonium codebase had a parser `Token` enum with a `Default` variant; 150 manual `impl Default for X` blocks all bound to that one `enum_variant`, swamping the rank tool with junk. +- **Resolver kind-compatibility filter (bug #11)**. `tracedecay_rank --edge-kind implements` (and every downstream tool: `tracedecay_impls`, `tracedecay_type_hierarchy`, `tracedecay_callees`'s trait dispatch, …) was poisoned by the resolver fuzzy-binding `impl Default for X` to whatever local node happened to share the name `Default`. The sonium codebase had a parser `Token` enum with a `Default` variant; 150 manual `impl Default for X` blocks all bound to that one `enum_variant`, swamping the rank tool with junk. - New `kind_compatible(ref_kind, target_kind)` helper in `src/resolution/resolver.rs` enforces a structural matrix: - `Implements` / `Extends` / `DerivesMacro` → must target trait/interface/class/abstract-method/sealed-class/annotation/type-alias kinds - `Calls` → must target a callable (function/method/struct-method/constructor/abstract-method/arrow-function/procedure/macro) @@ -2823,153 +2823,153 @@ The largest functional jump since 4.0: nine new MCP tools, a cross-session respo ## [4.12.0] - 2026-05-15 ### Added -- **`src/graph/scc.rs` — Tarjan's strongly-connected-components algorithm.** Iterative (no recursion, no stack-blow risk on deep graphs), generic over node-id type, returns components in reverse-topological order matching what port ranking needs. Used by both `tracedecay_circular` and `tracedecay_port_order`. Five unit tests cover DAGs, two-node cycles, three-cycle-plus-tail, self-loops, and reverse-topo emission order. +- **`src/graph/scc.rs`. Tarjan's strongly-connected-components algorithm.** Iterative (no recursion, no stack-blow risk on deep graphs), generic over node-id type, returns components in reverse-topological order matching what port ranking needs. Used by both `tracedecay_circular` and `tracedecay_port_order`. Five unit tests cover DAGs, two-node cycles, three-cycle-plus-tail, self-loops, and reverse-topo emission order. ### Fixed -- **`tracedecay_circular` reports one entry per SCC, not per DFS walk (bug #10)** — the previous implementation emitted every distinct DFS path through a cycle, producing 73 overlapping cycle entries on the sonium codebase that all shared a long common tail. `find_circular_dependencies` now computes SCCs via Tarjan and emits one entry per genuine mutually-recursive group, filtering out trivial single-node components that don't have self-loops. The legacy `dfs_cycle_detect` helper and `_legacy_walk_cycles` shim were removed. -- **`tracedecay_port_order` exposes per-SCC cycle groups (bug #12)** — previously, every unsorted node after Kahn's topological sort was lumped into a single "Mutual dependency — port together" entry, so two disjoint mutually-recursive pairs `(a,b)` and `(c,d)` would render as one mega-cycle and lose all signal. The handler now runs Tarjan on the subgraph of unsorted nodes and emits one cycle entry per non-trivial SCC, with the `files` set of each cycle surfaced so the user has a concrete "break this edge" target. Each entry carries `symbols`, `files`, `size`, and a refined `note`. +- **`tracedecay_circular` reports one entry per SCC, not per DFS walk (bug #10)**, the previous implementation emitted every distinct DFS path through a cycle, producing 73 overlapping cycle entries on the sonium codebase that all shared a long common tail. `find_circular_dependencies` now computes SCCs via Tarjan and emits one entry per genuine mutually-recursive group, filtering out trivial single-node components that don't have self-loops. The legacy `dfs_cycle_detect` helper and `_legacy_walk_cycles` shim were removed. +- **`tracedecay_port_order` exposes per-SCC cycle groups (bug #12)**, previously, every unsorted node after Kahn's topological sort was lumped into a single "Mutual dependency, port together" entry, so two disjoint mutually-recursive pairs `(a,b)` and `(c,d)` would render as one mega-cycle and lose all signal. The handler now runs Tarjan on the subgraph of unsorted nodes and emits one cycle entry per non-trivial SCC, with the `files` set of each cycle surfaced so the user has a concrete "break this edge" target. Each entry carries `symbols`, `files`, `size`, and a refined `note`. ## [4.11.0] - 2026-05-15 ### Fixed -- **`tracedecay_dependency_depth` no longer follows `implements`/`extends` edges (bug #7)** — the resolver fuzzy-binds `impl Debug for T` and similar across unrelated files, producing chains of spurious file-to-file deps (the report observed a 19-level chain spanning 17 unrelated files terminating in a foreign crate). `build_file_adjacency` now follows only `calls` and `uses` edges. Existing `tracedecay_health` and `tracedecay_circular` callers benefit too — they share the same adjacency builder. -- **`tracedecay_dead_code` no longer reports 0 on `pub`-heavy codebases (bug #8a)** — two fixes: (1) the `NOT EXISTS` subquery now excludes `Contains` edges, which previously masked every node behind its parent's bookkeeping edge; (2) new `include_public: true` argument opts into auditing pub items with no indexed callers, useful for workspace-internal cleanup. Default behaviour (no flag) still excludes pub items as before. -- **`tracedecay_unused_imports` no longer returns 0 on real codebases (bug #8b)** — the previous graph-only check tested `incoming.is_empty()`, but every Use node has at least one Contains edge from its parent, so the predicate never fired. New heuristic reads the source file once (cached per file) and checks whether the imported identifier appears as a whole-word token outside the use statement itself; matches what `cargo`'s own unused-import lint does. `pub use` re-exports, glob imports, and `use self::...` are skipped (intentional aliases / out-of-scope for textual heuristics). Three regression tests cover unused-detection, the dead-code Contains-edge bug, and the new `include_public` opt-in. +- **`tracedecay_dependency_depth` no longer follows `implements`/`extends` edges (bug #7)**, the resolver fuzzy-binds `impl Debug for T` and similar across unrelated files, producing chains of spurious file-to-file deps (the report observed a 19-level chain spanning 17 unrelated files terminating in a foreign crate). `build_file_adjacency` now follows only `calls` and `uses` edges. Existing `tracedecay_health` and `tracedecay_circular` callers benefit too, they share the same adjacency builder. +- **`tracedecay_dead_code` no longer reports 0 on `pub`-heavy codebases (bug #8a)**, two fixes: (1) the `NOT EXISTS` subquery now excludes `Contains` edges, which previously masked every node behind its parent's bookkeeping edge; (2) new `include_public: true` argument opts into auditing pub items with no indexed callers, useful for workspace-internal cleanup. Default behaviour (no flag) still excludes pub items as before. +- **`tracedecay_unused_imports` no longer returns 0 on real codebases (bug #8b)**, the previous graph-only check tested `incoming.is_empty()`, but every Use node has at least one Contains edge from its parent, so the predicate never fired. New heuristic reads the source file once (cached per file) and checks whether the imported identifier appears as a whole-word token outside the use statement itself; matches what `cargo`'s own unused-import lint does. `pub use` re-exports, glob imports, and `use self::...` are skipped (intentional aliases / out-of-scope for textual heuristics). Three regression tests cover unused-detection, the dead-code Contains-edge bug, and the new `include_public` opt-in. ### Changed -- **`TraceDecay::find_dead_code` signature** — gained an `include_public: bool` parameter. Existing callers (`tracedecay_health`, internal tests) updated to pass `false` to preserve previous semantics. +- **`TraceDecay::find_dead_code` signature**, gained an `include_public: bool` parameter. Existing callers (`tracedecay_health`, internal tests) updated to pass `false` to preserve previous semantics. ## [4.10.0] - 2026-05-15 ### Fixed -- **`tracedecay_body` prefers callable kinds over same-named fields (bug #1)** — sonium hit a case where querying `gmres` returned only a struct field literally named `gmres` and missed the obvious `pub fn gmres(...)`. The handler now does an exact-name DB lookup first (via the PR1 suffix-fallback path) so the function isn't buried under BM25 noise, then sorts matches by `body_kind_preference()`: callable (0) > type def (1) > impl (2) > value (3) > field/variant (4) > use (5). -- **`tracedecay_changelog` / `commit_context` / `pr_context` no longer list directories (bug #4)** — gix's `for_each_to_obtain_tree` yields directory-level entries when an entire subtree changes. `git_diff_files` now filters out any path that resolves to a directory on disk, so callers see only file paths. -- **`tracedecay_diff_context.impacted_symbols` dedupes by node id (bug #5)** — diamond dependencies caused the same downstream node to appear 6+ times consecutively. `impacted_seen: HashSet` now guards inserts. -- **`tracedecay_recursion` drops length-1 self-cycles (bug #6)** — single-node cycles are almost always either resolver fuzzy-binding (`self.push()` cross-bound across distinct impls of the same name) or trivial self-recursion. Cycles with `< 2` distinct nodes are now filtered out before being added to the result set. -- **`tracedecay_commit_context` / `tracedecay_pr_context` collapse config-file symbols (bug #3)** — Cargo.toml's 50+ dependency keys used to each enumerate as a separate "modified symbol", blowing past 50K tokens on a real diff. Both handlers now emit a single `{kind: "config_summary", file, config_keys: N}` entry per file with role `config` (`*.toml` / `*.yaml` / `*.json` / `*.ini` / `*.cfg` / `*.lock`). -- **`classify_file_role` no longer flags source files with inline tests as "test" (bug #3 follow-up)** — a `src/foo.rs` with `#[cfg(test)] mod tests` at the bottom keeps role `source`. The "test" bucket is reserved for files that exist purely to host tests (path-based check via `is_test_file`). Three unit tests in `mcp::tools::handlers::git::tests` cover the classification matrix. -- **Rust extractor emits `Extends` edges for supertrait bounds (bug #9)** — `trait Leaf: Middle + Base` now produces unresolved refs with `EdgeKind::Extends` for each bound, so `tracedecay_inheritance_depth`'s recursive CTE walks Rust supertrait chains correctly. Bound extraction handles `type_identifier`, `scoped_type_identifier`, `generic_type`, and `higher_ranked_trait_bound`. Existing DBs need a re-index (`tracedecay sync --force`) to pick up the new edges. +- **`tracedecay_body` prefers callable kinds over same-named fields (bug #1)**, sonium hit a case where querying `gmres` returned only a struct field literally named `gmres` and missed the obvious `pub fn gmres(...)`. The handler now does an exact-name DB lookup first (via the PR1 suffix-fallback path) so the function isn't buried under BM25 noise, then sorts matches by `body_kind_preference()`: callable (0) > type def (1) > impl (2) > value (3) > field/variant (4) > use (5). +- **`tracedecay_changelog` / `commit_context` / `pr_context` no longer list directories (bug #4)**, gix's `for_each_to_obtain_tree` yields directory-level entries when an entire subtree changes. `git_diff_files` now filters out any path that resolves to a directory on disk, so callers see only file paths. +- **`tracedecay_diff_context.impacted_symbols` dedupes by node id (bug #5)**, diamond dependencies caused the same downstream node to appear 6+ times consecutively. `impacted_seen: HashSet` now guards inserts. +- **`tracedecay_recursion` drops length-1 self-cycles (bug #6)**, single-node cycles are almost always either resolver fuzzy-binding (`self.push()` cross-bound across distinct impls of the same name) or trivial self-recursion. Cycles with `< 2` distinct nodes are now filtered out before being added to the result set. +- **`tracedecay_commit_context` / `tracedecay_pr_context` collapse config-file symbols (bug #3)**. Cargo.toml's 50+ dependency keys used to each enumerate as a separate "modified symbol", blowing past 50K tokens on a real diff. Both handlers now emit a single `{kind: "config_summary", file, config_keys: N}` entry per file with role `config` (`*.toml` / `*.yaml` / `*.json` / `*.ini` / `*.cfg` / `*.lock`). +- **`classify_file_role` no longer flags source files with inline tests as "test" (bug #3 follow-up)**, a `src/foo.rs` with `#[cfg(test)] mod tests` at the bottom keeps role `source`. The "test" bucket is reserved for files that exist purely to host tests (path-based check via `is_test_file`). Three unit tests in `mcp::tools::handlers::git::tests` cover the classification matrix. +- **Rust extractor emits `Extends` edges for supertrait bounds (bug #9)**. `trait Leaf: Middle + Base` now produces unresolved refs with `EdgeKind::Extends` for each bound, so `tracedecay_inheritance_depth`'s recursive CTE walks Rust supertrait chains correctly. Bound extraction handles `type_identifier`, `scoped_type_identifier`, `generic_type`, and `higher_ranked_trait_bound`. Existing DBs need a re-index (`tracedecay sync --force`) to pick up the new edges. ## [4.9.0] - 2026-05-15 ### Added -- **`tracedecay_derives` tool** — surfaces the `#[derive(...)]` macros attached to a type plus the trait + method names each one synthesizes. Closes the dead-end-search gap where calls like `.clone()`, `format!("{:?}", x)`, or `serde_json::to_string(&x)` resolve to methods that never appear in the graph (the impl is generated by the proc-macro at compile time). Accepts either `qualified_name` or `node_id`. Well-known derives carry full info (trait path, method list, source crate); unknown / proc-macro derives surface with `well_known: false` and just the derive name. -- **`derive_table` module** (`src/derive_table.rs`) — static knowledge of well-known derives (`Debug`, `Clone`, `Copy`, `Default`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `Hash`, `Serialize`, `Deserialize`, `Display`, `Error`), each mapped to its canonical trait path and method names. Five unit tests cover known + unknown derives and the `enrich` wrapper. -- **`derives` field on `tracedecay_node` output for type nodes** — when the queried node is a `Struct` / `Enum` / `Union` / `Record` / `CaseClass` / `DataClass` / `PascalRecord`, the response now includes a `derives` array so callers don't need a second roundtrip just to learn what derives are present. -- **`TraceDecay::get_derives_for_node(node_id)`** — public helper that re-reads the node's source-file attribute block and parses `#[derive(...)]` directly. The graph's `DerivesMacro` edges are unreliable: the resolver fuzzy-binds std-trait names like `Debug` to nonsense targets (e.g. a `Debug` enum variant in an unrelated test fixture), and the unique constraint on `(source, target, kind, line)` then collapses multiple derives on the same type onto a single edge. Re-parsing from source costs one `fs::read` per node lookup (cheap at typical Rust source sizes) and recovers the full derive list. Five unit tests in `derive_parse_tests` cover single/multi-block derives, qualified paths, mixed attribute kinds, and dedup. +- **`tracedecay_derives` tool**, surfaces the `#[derive(...)]` macros attached to a type plus the trait + method names each one synthesizes. Closes the dead-end-search gap where calls like `.clone()`, `format!("{:?}", x)`, or `serde_json::to_string(&x)` resolve to methods that never appear in the graph (the impl is generated by the proc-macro at compile time). Accepts either `qualified_name` or `node_id`. Well-known derives carry full info (trait path, method list, source crate); unknown / proc-macro derives surface with `well_known: false` and just the derive name. +- **`derive_table` module** (`src/derive_table.rs`), static knowledge of well-known derives (`Debug`, `Clone`, `Copy`, `Default`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `Hash`, `Serialize`, `Deserialize`, `Display`, `Error`), each mapped to its canonical trait path and method names. Five unit tests cover known + unknown derives and the `enrich` wrapper. +- **`derives` field on `tracedecay_node` output for type nodes**, when the queried node is a `Struct` / `Enum` / `Union` / `Record` / `CaseClass` / `DataClass` / `PascalRecord`, the response now includes a `derives` array so callers don't need a second roundtrip just to learn what derives are present. +- **`TraceDecay::get_derives_for_node(node_id)`**, public helper that re-reads the node's source-file attribute block and parses `#[derive(...)]` directly. The graph's `DerivesMacro` edges are unreliable: the resolver fuzzy-binds std-trait names like `Debug` to nonsense targets (e.g. a `Debug` enum variant in an unrelated test fixture), and the unique constraint on `(source, target, kind, line)` then collapses multiple derives on the same type onto a single edge. Re-parsing from source costs one `fs::read` per node lookup (cheap at typical Rust source sizes) and recovers the full derive list. Five unit tests in `derive_parse_tests` cover single/multi-block derives, qualified paths, mixed attribute kinds, and dedup. ### Changed -- **Total MCP tools: 59 → 60** — `tracedecay_derives` added. +- **Total MCP tools: 59 → 60**. `tracedecay_derives` added. ## [4.8.0] - 2026-05-15 ### Added -- **`tracedecay_diagnose` tool** — parses raw `cargo check` / `cargo clippy` / `rustc` stderr into structured diagnostics, then maps each one to the smallest containing graph node and (by default) pre-attaches up to 5 callers. Closes the today-an-agent-hand-parses gap: the response includes severity, optional error code (`E0308`, clippy lint name), message, file/line/column, the owning node (id, kind, qualified_name, span), and the call sites the broken code is reachable from. Diagnostics without a `--> file:line:col` span are dropped — they cannot be located. Accepts a `severity` filter (`error` / `warning` / `all`) and a `max_diagnostics` cap (default 50, hard cap 500). -- **`tracedecay_run_affected_tests` tool** — closes the loop opened by `tracedecay_test_map` / `tracedecay_test_risk`. Given `changed_paths` (or, by default, `git diff --name-only HEAD`), the handler walks the graph to find every test that covers a function/method in those files, then runs `cargo test --no-fail-fast -- ` with `kill_on_drop` and a configurable `timeout_secs` (default 300). Parses libtest stdout into JSON `{ test, passed, covers_source_ids[] }` entries plus pass/fail counts and the cargo exit code; trailing stdout/stderr are tailed at 2 KB each so the response stays in budget. `max_tests` defaults to 100 (hard cap 500) so a refactor touching everything doesn't dispatch an unbounded list. -- **`src/diagnose.rs`** — standalone parser module. Five unit tests cover typed errors (`error[E0308]`), clippy-style headers without codes, summary lines without spans (correctly dropped), multi-diagnostic blocks, and ANSI-prefixed lines. -- **`TraceDecay::node_at_location(file, line_1based)`** — public helper that returns the smallest-span node containing a 1-based source location. Used by `tracedecay_diagnose`; converts to the internal 0-based representation transparently. +- **`tracedecay_diagnose` tool**, parses raw `cargo check` / `cargo clippy` / `rustc` stderr into structured diagnostics, then maps each one to the smallest containing graph node and (by default) pre-attaches up to 5 callers. Closes the today-an-agent-hand-parses gap: the response includes severity, optional error code (`E0308`, clippy lint name), message, file/line/column, the owning node (id, kind, qualified_name, span), and the call sites the broken code is reachable from. Diagnostics without a `--> file:line:col` span are dropped, they cannot be located. Accepts a `severity` filter (`error` / `warning` / `all`) and a `max_diagnostics` cap (default 50, hard cap 500). +- **`tracedecay_run_affected_tests` tool**, closes the loop opened by `tracedecay_test_map` / `tracedecay_test_risk`. Given `changed_paths` (or, by default, `git diff --name-only HEAD`), the handler walks the graph to find every test that covers a function/method in those files, then runs `cargo test --no-fail-fast -- ` with `kill_on_drop` and a configurable `timeout_secs` (default 300). Parses libtest stdout into JSON `{ test, passed, covers_source_ids[] }` entries plus pass/fail counts and the cargo exit code; trailing stdout/stderr are tailed at 2 KB each so the response stays in budget. `max_tests` defaults to 100 (hard cap 500) so a refactor touching everything doesn't dispatch an unbounded list. +- **`src/diagnose.rs`**, standalone parser module. Five unit tests cover typed errors (`error[E0308]`), clippy-style headers without codes, summary lines without spans (correctly dropped), multi-diagnostic blocks, and ANSI-prefixed lines. +- **`TraceDecay::node_at_location(file, line_1based)`**, public helper that returns the smallest-span node containing a 1-based source location. Used by `tracedecay_diagnose`; converts to the internal 0-based representation transparently. ### Changed -- **Total MCP tools: 57 → 59** — `tracedecay_diagnose` and `tracedecay_run_affected_tests` added. -- **New handler module `src/mcp/tools/handlers/workflow.rs`** — keeps cargo/libtest plumbing out of `graph.rs`, which is for code-graph queries. +- **Total MCP tools: 57 → 59**. `tracedecay_diagnose` and `tracedecay_run_affected_tests` added. +- **New handler module `src/mcp/tools/handlers/workflow.rs`**, keeps cargo/libtest plumbing out of `graph.rs`, which is for code-graph queries. ## [4.7.0] - 2026-05-15 ### Added -- **`tracedecay_impls` tool** — index of `impl Trait for Type` blocks. Accepts optional `trait` and `type` filters (both short and qualified names). With neither, returns every impl in the graph. Surfaces information that was previously buried behind the second-class `Implements` edge: which types satisfy a given trait, which traits a type implements, and the impl blocks themselves with their files and signatures. -- **Trait dispatch resolution on `tracedecay_callees`** — when a callee resolves to a method whose enclosing scope is a trait, the handler walks back via `Implements` edges to surface the concrete impl methods reachable through that trait. New entries are tagged `dispatch_via_trait: true` and carry a `dispatch_from` pointer to the trait method. Pass `resolve_dispatch: false` to opt out and get only direct call edges. -- **`TraceDecay::get_impls(trait, type)`** — public helper backing the new tool. -- **`TraceDecay::get_trait_dispatch_targets(method)`** — public helper that returns every impl-method satisfying a given trait method, used by `handle_callees` to surface dispatch targets. +- **`tracedecay_impls` tool**, index of `impl Trait for Type` blocks. Accepts optional `trait` and `type` filters (both short and qualified names). With neither, returns every impl in the graph. Surfaces information that was previously buried behind the second-class `Implements` edge: which types satisfy a given trait, which traits a type implements, and the impl blocks themselves with their files and signatures. +- **Trait dispatch resolution on `tracedecay_callees`**, when a callee resolves to a method whose enclosing scope is a trait, the handler walks back via `Implements` edges to surface the concrete impl methods reachable through that trait. New entries are tagged `dispatch_via_trait: true` and carry a `dispatch_from` pointer to the trait method. Pass `resolve_dispatch: false` to opt out and get only direct call edges. +- **`TraceDecay::get_impls(trait, type)`**, public helper backing the new tool. +- **`TraceDecay::get_trait_dispatch_targets(method)`**, public helper that returns every impl-method satisfying a given trait method, used by `handle_callees` to surface dispatch targets. ### Changed -- **Total MCP tools: 56 → 57** — `tracedecay_impls` added. +- **Total MCP tools: 56 → 57**. `tracedecay_impls` added. - **`tracedecay_callees` description and schema** updated to advertise dispatch resolution and the new `resolve_dispatch` argument. ### Fixed -- **`tracedecay_search` ranks definitions above references (PR1 follow-up)** — BM25 alone was placing `use foo` statements ahead of the actual `pub fn foo()` definition because both score similarly when the symbol name matches. `TraceDecay::search` now over-fetches and re-ranks: every `NodeKind` carries an explicit bonus (callable defs +3.0, type defs / proto defs +2.5, impl blocks +2.0, values / macros / enum variants +1.0, members +0.5, neutral 0.0, container modules -1.5, annotation usages -2.0, `use` / `include` -3.0). The match is exhaustive so adding a new `NodeKind` forces a re-tune here. Result: searching for `gmres` returns the function before its imports. -- **`get_nodes_by_qualified_name` falls back to suffix or bare-name match (PR1 follow-up + user feedback)** — strict equality match remains primary. On empty results: queries with `::` retry as `qualified_name LIKE '%::'` (full scan, `LIMIT 50`); queries without `::` retry as `name = ?` using `idx_nodes_name`. Both forms now resolve, e.g. `get_impls`, `TraceDecay::get_impls`, and the full doubled path all return the same row. `tracedecay_signature` and `tracedecay_by_qualified_name` share the lookup so they agree. -- **Rust extractor no longer doubles the file path in `qualified_name`** — `qualified_prefix()` prepended `self.file_path` even though the file root was already pushed onto `node_stack` at extraction start, producing qnames like `src/foo.rs::src/foo.rs::Type::method`. Now iterates the stack only, yielding `src/foo.rs::Type::method`. Existing DBs will keep the old form until re-indexed (`tracedecay sync --force`). -- **`get_impls` batches the trait lookup (PR2 review follow-up)** — previously one `get_node_by_id` per impl block (N+1). Now collects every Implements-edge target then issues a single `get_nodes_by_ids` to populate the trait map. -- **`graph_stale` insertion asserts on non-object results (PR1 review follow-up)** — `handle_tools_call` now `debug_assert!`s that the wrapped tool result is a JSON object before attaching the `graph_stale` field, matching the "crash hard on unknown value" convention so a future handler returning a non-object is caught immediately instead of silently dropping the structured staleness signal. -- **`cost_to_expand` body heuristic documented as Rust-tuned (PR1 review follow-up)** — the `20 tokens/line` rate over-estimates Haskell/Python by ~2-3x; the doc comment now explicitly says so and notes the single-line floor of 20 tokens, since this number is part of the public tool contract. +- **`tracedecay_search` ranks definitions above references (PR1 follow-up)**. BM25 alone was placing `use foo` statements ahead of the actual `pub fn foo()` definition because both score similarly when the symbol name matches. `TraceDecay::search` now over-fetches and re-ranks: every `NodeKind` carries an explicit bonus (callable defs +3.0, type defs / proto defs +2.5, impl blocks +2.0, values / macros / enum variants +1.0, members +0.5, neutral 0.0, container modules -1.5, annotation usages -2.0, `use` / `include` -3.0). The match is exhaustive so adding a new `NodeKind` forces a re-tune here. Result: searching for `gmres` returns the function before its imports. +- **`get_nodes_by_qualified_name` falls back to suffix or bare-name match (PR1 follow-up + user feedback)**, strict equality match remains primary. On empty results: queries with `::` retry as `qualified_name LIKE '%::'` (full scan, `LIMIT 50`); queries without `::` retry as `name = ?` using `idx_nodes_name`. Both forms now resolve, e.g. `get_impls`, `TraceDecay::get_impls`, and the full doubled path all return the same row. `tracedecay_signature` and `tracedecay_by_qualified_name` share the lookup so they agree. +- **Rust extractor no longer doubles the file path in `qualified_name`**. `qualified_prefix()` prepended `self.file_path` even though the file root was already pushed onto `node_stack` at extraction start, producing qnames like `src/foo.rs::src/foo.rs::Type::method`. Now iterates the stack only, yielding `src/foo.rs::Type::method`. Existing DBs will keep the old form until re-indexed (`tracedecay sync --force`). +- **`get_impls` batches the trait lookup (PR2 review follow-up)**, previously one `get_node_by_id` per impl block (N+1). Now collects every Implements-edge target then issues a single `get_nodes_by_ids` to populate the trait map. +- **`graph_stale` insertion asserts on non-object results (PR1 review follow-up)**. `handle_tools_call` now `debug_assert!`s that the wrapped tool result is a JSON object before attaching the `graph_stale` field, matching the "crash hard on unknown value" convention so a future handler returning a non-object is caught immediately instead of silently dropping the structured staleness signal. +- **`cost_to_expand` body heuristic documented as Rust-tuned (PR1 review follow-up)**, the `20 tokens/line` rate over-estimates Haskell/Python by ~2-3x; the doc comment now explicitly says so and notes the single-line floor of 20 tokens, since this number is part of the public tool contract. ## [4.6.0] - 2026-05-15 ### Added -- **`tracedecay_signature` tool** — signature-only lookup by `qualified_name` or `node_id`. Returns visibility, signature string (generics, params, return type, where clauses), docstring, kind, and async flag for matching nodes. No body content. Replaces most agent `Read` calls when only the public-API surface of a symbol is needed. -- **`graph_stale` field on tool results** — when files referenced by a tool result remain stale after the post-call sync attempt, the JSON-RPC response now carries a top-level `graph_stale: ["path", …]` array plus a machine-parseable `tracedecay_graph_stale: [...]` text marker. The existing human-readable WARNING is preserved. Closes the silent-drift gap where renamed/deleted symbols could return phantom callers/callees without a programmatic signal. -- **`cost_to_expand` annotation on node results** — `tracedecay_node` and `tracedecay_signature` responses now include `cost_to_expand: { body, full_file }` (approximate tokens) so callers can decide whether to set `include_code=true` before re-querying. Body estimate uses ~20 tokens/line; `full_file` uses indexed `files.size / 4`. -- **`tracedecay://schema` MCP resource** — markdown resource documenting the on-disk `.tracedecay/tracedecay.db` schema: tables, columns, indexes, FKs, common query recipes (impl-of-trait, top callers, largest functions), and gotchas (content-hashed IDs, trait dispatch, derive macros). Makes the SQLite escape hatch usable without trial-and-error. -- **`TraceDecay::get_file_size_bytes(path)`** — public helper that returns the indexed byte size of a file (0 when unknown). Backs the `cost_to_expand` full-file estimate. +- **`tracedecay_signature` tool**, signature-only lookup by `qualified_name` or `node_id`. Returns visibility, signature string (generics, params, return type, where clauses), docstring, kind, and async flag for matching nodes. No body content. Replaces most agent `Read` calls when only the public-API surface of a symbol is needed. +- **`graph_stale` field on tool results**, when files referenced by a tool result remain stale after the post-call sync attempt, the JSON-RPC response now carries a top-level `graph_stale: ["path", …]` array plus a machine-parseable `tracedecay_graph_stale: [...]` text marker. The existing human-readable WARNING is preserved. Closes the silent-drift gap where renamed/deleted symbols could return phantom callers/callees without a programmatic signal. +- **`cost_to_expand` annotation on node results**. `tracedecay_node` and `tracedecay_signature` responses now include `cost_to_expand: { body, full_file }` (approximate tokens) so callers can decide whether to set `include_code=true` before re-querying. Body estimate uses ~20 tokens/line; `full_file` uses indexed `files.size / 4`. +- **`tracedecay://schema` MCP resource**, markdown resource documenting the on-disk `.tracedecay/tracedecay.db` schema: tables, columns, indexes, FKs, common query recipes (impl-of-trait, top callers, largest functions), and gotchas (content-hashed IDs, trait dispatch, derive macros). Makes the SQLite escape hatch usable without trial-and-error. +- **`TraceDecay::get_file_size_bytes(path)`**, public helper that returns the indexed byte size of a file (0 when unknown). Backs the `cost_to_expand` full-file estimate. ### Changed -- **Total MCP tools: 55 → 56** — `tracedecay_signature` added; all existing tools unchanged. +- **Total MCP tools: 55 → 56**. `tracedecay_signature` added; all existing tools unchanged. ### Fixed -- **Clippy: project-wide cleanup to restore `-D warnings`** — 43 pre-existing lib errors and 3 bin errors resolved without behavioral change: module doc comments wrap snake_case tool names in backticks; `bench.rs` uses `write!` instead of `format!(..).push_str`; `extraction_worker.rs` converted to `let…else`; redundant closures in `agents/copilot.rs`, `extraction/haskell_extractor.rs`, `mcp/tools/handlers/memory.rs` replaced with method references; `resolution/resolver.rs` merges identical match arms; `serve.rs` uses `sort_by_key`; `upgrade.rs` uses `is_ok_and`; `main.rs` drops a useless `.into()`. +- **Clippy: project-wide cleanup to restore `-D warnings`**. 43 pre-existing lib errors and 3 bin errors resolved without behavioral change: module doc comments wrap snake_case tool names in backticks; `bench.rs` uses `write!` instead of `format!(..).push_str`; `extraction_worker.rs` converted to `let…else`; redundant closures in `agents/copilot.rs`, `extraction/haskell_extractor.rs`, `mcp/tools/handlers/memory.rs` replaced with method references; `resolution/resolver.rs` merges identical match arms; `serve.rs` uses `sort_by_key`; `upgrade.rs` uses `is_ok_and`; `main.rs` drops a useless `.into()`. ## [4.5.1] - 2026-05-15 ### Added -- **`tracedecay monitor` highlights the last 3 updates** — the most recently active (project, tool) pair renders green, second-to-last orange, third-to-last yellow. Re-firing the same tool moves it to the front rather than duplicating. Cleared on Ctrl+R. -- **Welcome banner on fresh installs** — when `tracedecay` is invoked with no subcommand and the global DB has zero registered projects, print a cyan welcome that explicitly suggests `tracedecay init` before the existing "Create one now?" prompt. Returning users see no change. +- **`tracedecay monitor` highlights the last 3 updates**, the most recently active (project, tool) pair renders green, second-to-last orange, third-to-last yellow. Re-firing the same tool moves it to the front rather than duplicating. Cleared on Ctrl+R. +- **Welcome banner on fresh installs**, when `tracedecay` is invoked with no subcommand and the global DB has zero registered projects, print a cyan welcome that explicitly suggests `tracedecay init` before the existing "Create one now?" prompt. Returning users see no change. ### Fixed -- **CI: `clippy::items_after_test_module` denied under Rust 1.95.0** — two test modules from the v4.5.0 work (`gain_tests` in `src/commands.rs`, `gain_format_tests` in `src/display.rs`) were inserted mid-file. Rust 1.95.0 promoted this lint into `clippy::all`, which the project denies project-wide. Both moved to file end. -- **CI: `cargo fmt` drift across 11 files** — accumulated unwrapped one-line `println!` / `match` / struct literals from the v4.5.0 features; re-formatted to match `rustfmt` expectations. +- **CI: `clippy::items_after_test_module` denied under Rust 1.95.0**, two test modules from the v4.5.0 work (`gain_tests` in `src/commands.rs`, `gain_format_tests` in `src/display.rs`) were inserted mid-file. Rust 1.95.0 promoted this lint into `clippy::all`, which the project denies project-wide. Both moved to file end. +- **CI: `cargo fmt` drift across 11 files**, accumulated unwrapped one-line `println!` / `match` / struct literals from the v4.5.0 features; re-formatted to match `rustfmt` expectations. ## [4.5.0] - 2026-05-15 ### Added -- **Cross-session memory primitives (3 new MCP tools)** — `tracedecay_record_decision`, `tracedecay_record_code_area`, and `tracedecay_session_recall` persist agent decisions and worked-on paths in the per-project DB so they survive across sessions. `session_recall` uses FTS5 for fuzzy retrieval. Backed by two new tables and an FTS mirror added in schema migration v8. -- **`tracedecay gain` CLI for the savings ledger** — every MCP tool call now writes an append-only row to a new `savings_ledger` table in the global DB. `tracedecay gain [--all] [--history] [--range 7d] [--json]` reports tokens saved + dollar estimates (Sonnet input pricing, refreshed daily via LiteLLM). -- **`tracedecay bench` reproducible retrieval benchmark** — runs a fixed query set through `tracedecay_context` and reports retrieval savings vs a full-file baseline (CCE-style methodology). Ships with a 10-query generic default set embedded into the binary (no external file dependency); `--queries ` accepts a custom set. Measured **93% mean retrieval savings on tracedecay's own repo** (180K → 3.4K tokens across 10 generic queries). +- **Cross-session memory primitives (3 new MCP tools)**. `tracedecay_record_decision`, `tracedecay_record_code_area`, and `tracedecay_session_recall` persist agent decisions and worked-on paths in the per-project DB so they survive across sessions. `session_recall` uses FTS5 for fuzzy retrieval. Backed by two new tables and an FTS mirror added in schema migration v8. +- **`tracedecay gain` CLI for the savings ledger**, every MCP tool call now writes an append-only row to a new `savings_ledger` table in the global DB. `tracedecay gain [--all] [--history] [--range 7d] [--json]` reports tokens saved + dollar estimates (Sonnet input pricing, refreshed daily via LiteLLM). +- **`tracedecay bench` reproducible retrieval benchmark**, runs a fixed query set through `tracedecay_context` and reports retrieval savings vs a full-file baseline (CCE-style methodology). Ships with a 10-query generic default set embedded into the binary (no external file dependency); `--queries ` accepts a custom set. Measured **93% mean retrieval savings on tracedecay's own repo** (180K → 3.4K tokens across 10 generic queries). ### Changed -- **Schema bumped from v7 to v8** — adds `memory_decisions`, `memory_code_areas`, and the `memory_decisions_fts` virtual table. Existing user DBs upgrade idempotently via `migrate_v8`; fresh installs use the mirrored DDL in `create_schema`. No breaking changes; existing tools and queries continue to work. -- **`GlobalDb::open()` refactored to delegate to `GlobalDb::open_at(path)`** — enables test isolation via `tempfile::TempDir` without process-wide `HOME` mutation. The public `open()` API is unchanged. -- **Total MCP tools: 52 → 55** — three new memory tools added; all existing tools unchanged. +- **Schema bumped from v7 to v8**, adds `memory_decisions`, `memory_code_areas`, and the `memory_decisions_fts` virtual table. Existing user DBs upgrade idempotently via `migrate_v8`; fresh installs use the mirrored DDL in `create_schema`. No breaking changes; existing tools and queries continue to work. +- **`GlobalDb::open()` refactored to delegate to `GlobalDb::open_at(path)`**, enables test isolation via `tempfile::TempDir` without process-wide `HOME` mutation. The public `open()` API is unchanged. +- **Total MCP tools: 52 → 55**, three new memory tools added; all existing tools unchanged. ### Fixed -- **`coverage_discipline` health penalty reduced from 10% to 2% (issue #76)** — annotating genuinely untestable functions with `/// skip-test-coverage` was dropping `quality_signal` despite improving `coverage_pct`, because the penalty had no positive counterbalance (coverage doesn't feed into the composite health score). Max penalty reduced so honest annotation is not punished. +- **`coverage_discipline` health penalty reduced from 10% to 2% (issue #76)**, annotating genuinely untestable functions with `/// skip-test-coverage` was dropping `quality_signal` despite improving `coverage_pct`, because the penalty had no positive counterbalance (coverage doesn't feed into the composite health score). Max penalty reduced so honest annotation is not punished. ## [4.4.0] - 2026-05-14 ### Fixed -- **Rust extractor now detects function calls inside macro invocations (issue #72)** — `assert!(check_count(5))` previously only created a Calls edge to `assert`, missing `check_count`. The extractor now walks into `token_tree` nodes inside macros to find nested call patterns. -- **`test_risk` denominator no longer includes test functions (issue #73)** — functions with `#[test]` annotations and functions inside `::tests::` modules are now excluded from `total_functions` and the coverage percentage denominator. -- **Rust extractor resolves instance method calls (issue #74)** — `instance.method()` now emits an additional unresolved ref with just the method name, allowing the resolver to match it against `impl` method definitions. Previously only associated function calls (`Type::new()`) were resolved. +- **Rust extractor now detects function calls inside macro invocations (issue #72)**. `assert!(check_count(5))` previously only created a Calls edge to `assert`, missing `check_count`. The extractor now walks into `token_tree` nodes inside macros to find nested call patterns. +- **`test_risk` denominator no longer includes test functions (issue #73)**, functions with `#[test]` annotations and functions inside `::tests::` modules are now excluded from `total_functions` and the coverage percentage denominator. +- **Rust extractor resolves instance method calls (issue #74)**. `instance.method()` now emits an additional unresolved ref with just the method name, allowing the resolver to match it against `impl` method definitions. Previously only associated function calls (`Type::new()`) were resolved. ### Added -- **`/// skip-test-coverage` doc comment convention (issue #75)** — mark genuinely untestable functions to exclude them from `test_risk` coverage calculations. The `skipped` count appears in the summary. A `coverage_discipline` health dimension penalises overuse (up to 10% quality signal reduction). -- **VS Code Insiders support for the Copilot installer (issue #69)** — `tracedecay install --agent copilot` now also configures `Code - Insiders/User/settings.json` alongside the regular VS Code path. -- **Copilot prompt instructions (issue #70)** — the Copilot installer now writes `copilot-instructions.md` with tracedecay MCP tool guidance to VS Code (`User/prompts/`), VS Code Insiders, and Copilot CLI (`~/.copilot/`). +- **`/// skip-test-coverage` doc comment convention (issue #75)**, mark genuinely untestable functions to exclude them from `test_risk` coverage calculations. The `skipped` count appears in the summary. A `coverage_discipline` health dimension penalises overuse (up to 10% quality signal reduction). +- **VS Code Insiders support for the Copilot installer (issue #69)**. `tracedecay install --agent copilot` now also configures `Code - Insiders/User/settings.json` alongside the regular VS Code path. +- **Copilot prompt instructions (issue #70)**, the Copilot installer now writes `copilot-instructions.md` with tracedecay MCP tool guidance to VS Code (`User/prompts/`), VS Code Insiders, and Copilot CLI (`~/.copilot/`). ## [4.3.18] - 2026-05-14 ### Fixed -- **Inline `#[cfg(test)]` test modules are now recognized as test coverage** — `test_map`, `health`, `test_risk`, `affected`, `impact`, and `commit_context` previously only detected tests by file path patterns (`tests/`, `_test.`, etc.). Functions annotated with `#[test]` inside inline `#[cfg(test)] mod tests { ... }` blocks in source files (226 such functions in tracedecay's own codebase) were invisible to coverage analysis. The Rust extractor now emits `Annotates` edges from `#[cfg(test)]` to modules, and all test-detection handlers query `#[test]` annotations via the graph in addition to checking file paths. -- **`tracedecay serve` resolves the correct project in multi-folder workspaces (issue #66 reopened)** — when multiple projects are registered in the global DB, the `serve` fallback now picks the project closest to cwd (ancestor match first, then descendant match) instead of failing with an ambiguity error. As a last resort, the server peeks at the MCP `initialize` request's `roots` array to discover the workspace folder the client is working in. +- **Inline `#[cfg(test)]` test modules are now recognized as test coverage**. `test_map`, `health`, `test_risk`, `affected`, `impact`, and `commit_context` previously only detected tests by file path patterns (`tests/`, `_test.`, etc.). Functions annotated with `#[test]` inside inline `#[cfg(test)] mod tests { ... }` blocks in source files (226 such functions in tracedecay's own codebase) were invisible to coverage analysis. The Rust extractor now emits `Annotates` edges from `#[cfg(test)]` to modules, and all test-detection handlers query `#[test]` annotations via the graph in addition to checking file paths. +- **`tracedecay serve` resolves the correct project in multi-folder workspaces (issue #66 reopened)**, when multiple projects are registered in the global DB, the `serve` fallback now picks the project closest to cwd (ancestor match first, then descendant match) instead of failing with an ambiguity error. As a last resort, the server peeks at the MCP `initialize` request's `roots` array to discover the workspace folder the client is working in. ## [4.3.17] - 2026-05-14 ### Fixed -- **`tracedecay upgrade` no longer breaks Homebrew installs (issue #67)** — previously, self-upgrading a Homebrew-managed install mutated the Cellar directly, leaving Homebrew's recorded keg state inconsistent and causing later `brew upgrade` to fail. `tracedecay upgrade` now detects Homebrew installs and delegates to `brew update && brew upgrade tracedecay`. (PR #68, thanks @lesbass) -- **Exclude globs now match nested directories (issue #64)** — the default `node_modules/**` pattern only excluded top-level `node_modules/`, not nested ones like `projectA/node_modules/`. Changed default to `**/node_modules/**`. Also added `is_excluded_dir()` so bare patterns like `**/dist` correctly prune directories during scanning without requiring a trailing `/**`. -- **VS Code multi-folder workspaces can now start the Copilot MCP server (issue #66)** — the Copilot config used `${workspaceFolder}` which VS Code cannot resolve in multi-folder workspaces. Dropped in favour of the serve command's built-in project discovery, matching every other agent integration. +- **`tracedecay upgrade` no longer breaks Homebrew installs (issue #67)**, previously, self-upgrading a Homebrew-managed install mutated the Cellar directly, leaving Homebrew's recorded keg state inconsistent and causing later `brew upgrade` to fail. `tracedecay upgrade` now detects Homebrew installs and delegates to `brew update && brew upgrade tracedecay`. (PR #68, thanks @lesbass) +- **Exclude globs now match nested directories (issue #64)**, the default `node_modules/**` pattern only excluded top-level `node_modules/`, not nested ones like `projectA/node_modules/`. Changed default to `**/node_modules/**`. Also added `is_excluded_dir()` so bare patterns like `**/dist` correctly prune directories during scanning without requiring a trailing `/**`. +- **VS Code multi-folder workspaces can now start the Copilot MCP server (issue #66)**, the Copilot config used `${workspaceFolder}` which VS Code cannot resolve in multi-folder workspaces. Dropped in favour of the serve command's built-in project discovery, matching every other agent integration. ## [4.3.16] - 2026-05-11 ### Fixed -- **Windows CI failure introduced by v4.3.15's zed regression test** — `test_zed_install_preserves_existing_config` seeded `AppData/Roaming/Zed/settings.json` on Windows, but `zed_config_dir` actually uses `.config/zed/settings.json` on every non-macOS platform (Linux *and* Windows). The hand-written `#[cfg(target_os = "windows")]` branch in the test silently diverged from the production helper, so the test wrote the seed to one path and the install wrote to another — backup never appeared at the seeded location and the test failed. The Windows job (Linux passed, since its branch happened to be correct) was the only one to catch the drift. +- **Windows CI failure introduced by v4.3.15's zed regression test**. `test_zed_install_preserves_existing_config` seeded `AppData/Roaming/Zed/settings.json` on Windows, but `zed_config_dir` actually uses `.config/zed/settings.json` on every non-macOS platform (Linux *and* Windows). The hand-written `#[cfg(target_os = "windows")]` branch in the test silently diverged from the production helper, so the test wrote the seed to one path and the install wrote to another, backup never appeared at the seeded location and the test failed. The Windows job (Linux passed, since its branch happened to be correct) was the only one to catch the drift. ### Changed -- **`AgentIntegration::primary_config_path(home) -> Option`** — new trait method that returns the single config file the integration rewrites on install/uninstall. Every agent that goes through `safe_write_json_file` or `write_toml_file` implements it (claude, gemini, cursor, opencode, zed, cline, roo-code, copilot, kilo, antigravity, codex); vibe leaves the default `None` because its TOML config is append-only and has no rewrite path. Regression tests in `tests/agent_test.rs` now call `agent.primary_config_path(home)` instead of duplicating platform-conditional path logic — the production helper is the single source of truth, so a future `zed_config_dir`-style change can't drift between tests and reality. A meta-test (`test_every_tested_agent_advertises_primary_config_path`) walks every integration covered by the install regression suite and asserts the method returns `Some(path)` under the test home, so a new integration added without wiring it up fails fast with a clear message instead of producing a confusing missing-backup panic later. +- **`AgentIntegration::primary_config_path(home) -> Option`**, new trait method that returns the single config file the integration rewrites on install/uninstall. Every agent that goes through `safe_write_json_file` or `write_toml_file` implements it (claude, gemini, cursor, opencode, zed, cline, roo-code, copilot, kilo, antigravity, codex); vibe leaves the default `None` because its TOML config is append-only and has no rewrite path. Regression tests in `tests/agent_test.rs` now call `agent.primary_config_path(home)` instead of duplicating platform-conditional path logic, the production helper is the single source of truth, so a future `zed_config_dir`-style change can't drift between tests and reality. A meta-test (`test_every_tested_agent_advertises_primary_config_path`) walks every integration covered by the install regression suite and asserts the method returns `Some(path)` under the test home, so a new integration added without wiring it up fails fast with a clear message instead of producing a confusing missing-backup panic later. ## [4.3.15] - 2026-05-11 ### Fixed -- **Installing the Codex integration no longer wipes `~/.codex/config.toml` (issue #63)** — `load_toml_file` used `contents.parse::()`, which in the `toml = "1"` crate parses a single TOML *value* rather than a *document*. Any well-formed `config.toml` therefore parsed as an error and silently fell back to an empty table; `install_mcp_server` then serialized that empty-plus-tracedecay table back over the file, erasing every other key the user had set (model, approval_policy, other `[mcp_servers.*]` entries, comments). `load_toml_file` now uses `toml::from_str::` so real documents round-trip, returns `Result` instead of swallowing errors, and refuses to overwrite when an existing file cannot be parsed (so a typo or partial edit leaves the original intact for the user to fix). `doctor_check_config`, `install_mcp_server`, `uninstall_mcp_server`, and `CodexIntegration::has_tracedecay` were updated to handle the `Result` shape — the doctor now reports parse errors as a failed check, and `has_tracedecay` returns `false` on parse error rather than panicking. +- **Installing the Codex integration no longer wipes `~/.codex/config.toml` (issue #63)**. `load_toml_file` used `contents.parse::()`, which in the `toml = "1"` crate parses a single TOML *value* rather than a *document*. Any well-formed `config.toml` therefore parsed as an error and silently fell back to an empty table; `install_mcp_server` then serialized that empty-plus-tracedecay table back over the file, erasing every other key the user had set (model, approval_policy, other `[mcp_servers.*]` entries, comments). `load_toml_file` now uses `toml::from_str::` so real documents round-trip, returns `Result` instead of swallowing errors, and refuses to overwrite when an existing file cannot be parsed (so a typo or partial edit leaves the original intact for the user to fix). `doctor_check_config`, `install_mcp_server`, `uninstall_mcp_server`, and `CodexIntegration::has_tracedecay` were updated to handle the `Result` shape, the doctor now reports parse errors as a failed check, and `has_tracedecay` returns `false` on parse error rather than panicking. ### Changed - **Every config-file write across all agent integrations now leaves a `.bak` copy first.** Previously only install paths went through `backup_config_file`; uninstall paths and `doctor` auto-repair paths called `std::fs::write` directly, so a corrupted serialization or a bug in the rewrite logic could destroy the user's settings with no recovery. A new shared `backup_and_write_json` helper (in `src/agents/mod.rs`) wraps `backup_config_file` + `safe_write_json_file` with best-effort error handling suited to uninstall flows. Every agent's uninstall path (claude, cursor, copilot, cline, zed, kilo, roo-code, opencode, gemini) now goes through this helper, as do the claude `doctor` auto-repair and local-settings-cleanup paths. The Codex TOML write path (`write_toml_file`) also creates a `.bak` before writing for the same reason. Eight per-agent install-side regression tests plus a cursor uninstall-side regression test were added to `tests/agent_test.rs` to guard the new invariant. @@ -2977,152 +2977,152 @@ The largest functional jump since 4.0: nine new MCP tools, a cross-session respo ## [4.3.14] - 2026-05-11 ### Fixed -- **`tracedecay_body` no longer drops the function's outer closing brace (issue #62)** — `handle_body` returned the source spanning `start_line..end_line`, but stored line fields are tree-sitter rows (0-based) while `extract_lines` was written assuming 1-based inclusive inputs. The mismatch meant `lines[start..end_line]` exclusive — one short, lopping off the trailing `}` (or any language's outer block closer sitting on its own line). Inner braces were unaffected because they were never on the boundary. `extract_lines` now treats inputs as 0-based row indices and slices inclusively, so the returned body is byte-exact usable as an `Edit` tool `old_string`. Regression added in `test_body_returns_full_function_source` (`tests/mcp_handler_test.rs`) — verified failing pre-fix with `body: "\nfn format_greeting(name: &str) -> String {\n format!(\"Hello, {}!\", name)"` (closing `}` missing). +- **`tracedecay_body` no longer drops the function's outer closing brace (issue #62)**. `handle_body` returned the source spanning `start_line..end_line`, but stored line fields are tree-sitter rows (0-based) while `extract_lines` was written assuming 1-based inclusive inputs. The mismatch meant `lines[start..end_line]` exclusive, one short, lopping off the trailing `}` (or any language's outer block closer sitting on its own line). Inner braces were unaffected because they were never on the boundary. `extract_lines` now treats inputs as 0-based row indices and slices inclusively, so the returned body is byte-exact usable as an `Edit` tool `old_string`. Regression added in `test_body_returns_full_function_source` (`tests/mcp_handler_test.rs`), verified failing pre-fix with `body: "\nfn format_greeting(name: &str) -> String {\n format!(\"Hello, {}!\", name)"` (closing `}` missing). ### Changed -- **`tracedecay_body` now exposes `start_line` / `end_line` as 1-based file line numbers** — they were previously the raw 0-based tree-sitter row indices, which read as "off by one" against the line numbers any editor or `Edit`-style tool displays. The values now match what users see when they open the file, so the reported `end_line` is the line containing the function's closing brace. The shift is local to `handle_body`; other handlers still expose `node.start_line` as-is. +- **`tracedecay_body` now exposes `start_line` / `end_line` as 1-based file line numbers**, they were previously the raw 0-based tree-sitter row indices, which read as "off by one" against the line numbers any editor or `Edit`-style tool displays. The values now match what users see when they open the file, so the reported `end_line` is the line containing the function's closing brace. The shift is local to `handle_body`; other handlers still expose `node.start_line` as-is. ## [4.3.13] - 2026-05-10 ### Changed -- **Switched to `tree-sitter-grammars/tree-sitter-markdown` (block + inline split parsers)** — the previously-vendored `ikatyang/tree-sitter-markdown` (last updated 2023, GLR-heavy without native frontmatter handling) hung the indexer on otherwise-fine markdown files containing YAML frontmatter. Specifically, the old grammar parsed `---\n…\n---` content as ordinary markdown, where 6/8/10-space-indented YAML lines were simultaneously valid as both deeply-nested list-item continuations and as indented code blocks; tree-sitter's GLR explored all alternatives in parallel, with the surviving-versions count growing exponentially per line. A real-world 18 KB resume.md hung the worker indefinitely; a 4.4 KB minimal reproducer was bisected and is now a regression fixture (`crates/tracedecay-code-extraction/tests/fixtures/markdown_yaml_frontmatter_hang.md`). The new grammar emits an opaque `(minus_metadata)` / `(plus_metadata)` node for frontmatter, so the markdown rules never see the YAML — the same 4.4 KB reproducer parses in ~7 ms, the full 18 KB file in ~16 ms. The markdown extractor was rewritten for the new AST (block parser produces `(atx_heading … heading_content: (inline …))`, headings still become `Module` nodes; the inline parser is run over each `(inline)` byte range via `set_included_ranges` to extract `(inline_link)` for `Uses` edges). All 16 existing markdown extraction tests still pass; 3 new regression tests guard the migration. +- **Switched to `tree-sitter-grammars/tree-sitter-markdown` (block + inline split parsers)**, the previously-vendored `ikatyang/tree-sitter-markdown` (last updated 2023, GLR-heavy without native frontmatter handling) hung the indexer on otherwise-fine markdown files containing YAML frontmatter. Specifically, the old grammar parsed `---\n…\n---` content as ordinary markdown, where 6/8/10-space-indented YAML lines were simultaneously valid as both deeply-nested list-item continuations and as indented code blocks; tree-sitter's GLR explored all alternatives in parallel, with the surviving-versions count growing exponentially per line. A real-world 18 KB resume.md hung the worker indefinitely; a 4.4 KB minimal reproducer was bisected and is now a regression fixture (`crates/tracedecay-code-extraction/tests/fixtures/markdown_yaml_frontmatter_hang.md`). The new grammar emits an opaque `(minus_metadata)` / `(plus_metadata)` node for frontmatter, so the markdown rules never see the YAML, the same 4.4 KB reproducer parses in ~7 ms, the full 18 KB file in ~16 ms. The markdown extractor was rewritten for the new AST (block parser produces `(atx_heading … heading_content: (inline …))`, headings still become `Module` nodes; the inline parser is run over each `(inline)` byte range via `set_included_ranges` to extract `(inline_link)` for `Uses` edges). All 16 existing markdown extraction tests still pass; 3 new regression tests guard the migration. ### Added -- **Per-file extraction timeout** — every extractor round trip is now wrapped in a watchdog (configurable via `extraction_timeout_secs` in `~/.tracedecay/config.toml`, default 60 s). A file whose extractor doesn't respond in time has its worker subprocess killed via `Child::kill()` and is recorded in `SyncResult.skipped_paths` with reason `"extractor timed out (>Ns)"`. Worker crashes (the existing failure path) are now also recorded with reason `"extractor crashed (...)"` instead of disappearing silently. This bounds the worst case for any future grammar pathology — `tracedecay sync` can no longer hang forever on a single malformed file. +- **Per-file extraction timeout**, every extractor round trip is now wrapped in a watchdog (configurable via `extraction_timeout_secs` in `~/.tracedecay/config.toml`, default 60 s). A file whose extractor doesn't respond in time has its worker subprocess killed via `Child::kill()` and is recorded in `SyncResult.skipped_paths` with reason `"extractor timed out (>Ns)"`. Worker crashes (the existing failure path) are now also recorded with reason `"extractor crashed (...)"` instead of disappearing silently. This bounds the worst case for any future grammar pathology. `tracedecay sync` can no longer hang forever on a single malformed file. ## [4.3.12] - 2026-05-09 ### Changed -- **The beta channel is open again** — `tracedecay channel beta` was hard-gated to `"the beta channel is not available at this time"` while the prior 4.5.x beta line was being merged into stable. With v5.0.0-beta.1 published on the prerelease channel, the gate is removed: `switch_channel` now resolves `"beta"` through the same path as `"stable"` and downloads the latest GitHub prerelease. The `unknown channel` error message also lists `beta` as a valid target again. +- **The beta channel is open again**. `tracedecay channel beta` was hard-gated to `"the beta channel is not available at this time"` while the prior 4.5.x beta line was being merged into stable. With v5.0.0-beta.1 published on the prerelease channel, the gate is removed: `switch_channel` now resolves `"beta"` through the same path as `"stable"` and downloads the latest GitHub prerelease. The `unknown channel` error message also lists `beta` as a valid target again. - **Retired the "beta channel has been merged into stable" nudge** in `main.rs`. Beta users (anyone whose binary version contains `-`) used to see the nudge on every invocation; with the channel reopened the nudge is no longer correct. Beta users now stay on beta until they explicitly run `tracedecay channel stable`. ### Fixed -- **`tracedecay wipe` no longer leaks the global DB into the wipe set when `$HOME` is symlinked** — the home `.tracedecay` skip relied on lexical path equality, so a user whose `$HOME` resolves through a symlink (e.g. macOS `/Users/x` vs the canonical `/private/var/...`) could see `~/.tracedecay` show up as a wipe target if the descendant walk reached it via the canonical chain. The skip now canonicalizes both the home path and every candidate before comparing. +- **`tracedecay wipe` no longer leaks the global DB into the wipe set when `$HOME` is symlinked**, the home `.tracedecay` skip relied on lexical path equality, so a user whose `$HOME` resolves through a symlink (e.g. macOS `/Users/x` vs the canonical `/private/var/...`) could see `~/.tracedecay` show up as a wipe target if the descendant walk reached it via the canonical chain. The skip now canonicalizes both the home path and every candidate before comparing. ### Changed (carried forward from the prior unreleased section) -- **Descendant walk for `tracedecay wipe` / `tracedecay list` is now iterative with cycle protection** — `find_descendant_tracedecay` used to recurse, which made deep trees a stack-overflow risk and relied entirely on `file_type()` skipping symlinks for cycle safety. It now uses an explicit worklist plus a canonical-path `visited` set, so the walk is bounded even if a directory cycle slips past the symlink filter (e.g. Windows junctions). -- **`tracedecay doctor` purges stale global-DB entries in batched statements** — purging used to issue one `DELETE` per stale row, which meant N serial round-trips against libsql for a stale-store cleanup (the case that prompted this: 216 deletes). A new `GlobalDb::delete_projects(&[String])` issues one `DELETE … WHERE path IN (…)` per chunk of 256, so the same 216-row purge is now one round-trip. -- **`gather_local_projects_from` is now a separately-exported helper** — extracts the pure discovery logic from the cwd-driven `gather_local_projects` wrapper so the ancestor + descendant walk can be unit-tested without mutating the process's working directory. Backed by 7 new tests covering cwd / ancestor-only / descendant-only / ancestor+descendant dedup / `node_modules` skip / canonical home-skip / empty-dir. -- **Cleared `clippy::map_unwrap_or` warning in `display::shuffle_flags`** — the xorshift seed now uses `map_or` instead of `map(...).unwrap_or(...)`. Behavior unchanged. +- **Descendant walk for `tracedecay wipe` / `tracedecay list` is now iterative with cycle protection**. `find_descendant_tracedecay` used to recurse, which made deep trees a stack-overflow risk and relied entirely on `file_type()` skipping symlinks for cycle safety. It now uses an explicit worklist plus a canonical-path `visited` set, so the walk is bounded even if a directory cycle slips past the symlink filter (e.g. Windows junctions). +- **`tracedecay doctor` purges stale global-DB entries in batched statements**, purging used to issue one `DELETE` per stale row, which meant N serial round-trips against libsql for a stale-store cleanup (the case that prompted this: 216 deletes). A new `GlobalDb::delete_projects(&[String])` issues one `DELETE … WHERE path IN (…)` per chunk of 256, so the same 216-row purge is now one round-trip. +- **`gather_local_projects_from` is now a separately-exported helper**, extracts the pure discovery logic from the cwd-driven `gather_local_projects` wrapper so the ancestor + descendant walk can be unit-tested without mutating the process's working directory. Backed by 7 new tests covering cwd / ancestor-only / descendant-only / ancestor+descendant dedup / `node_modules` skip / canonical home-skip / empty-dir. +- **Cleared `clippy::map_unwrap_or` warning in `display::shuffle_flags`**, the xorshift seed now uses `map_or` instead of `map(...).unwrap_or(...)`. Behavior unchanged. ## [4.3.11] - 2026-05-09 ### Added -- **`tracedecay doctor` now reports stale entries in the global DB and offers to purge them** — projects registered in `~/.tracedecay/global.db` whose `.tracedecay/` directory is gone (deleted, moved, or scratch dirs cleaned up by the OS) are listed under the "Global database" section. Up to 10 paths are shown with an "… and N more" tail. When run interactively, the doctor prompts `Purge N stale row(s) from the global DB? [Y/n]`; on confirmation each stale row is deleted via `GlobalDb::delete_project`. When stdin is not a terminal (CI, piped invocation), the stale list is shown as a warning with a hint to re-run interactively. +- **`tracedecay doctor` now reports stale entries in the global DB and offers to purge them**, projects registered in `~/.tracedecay/global.db` whose `.tracedecay/` directory is gone (deleted, moved, or scratch dirs cleaned up by the OS) are listed under the "Global database" section. Up to 10 paths are shown with an "… and N more" tail. When run interactively, the doctor prompts `Purge N stale row(s) from the global DB? [Y/n]`; on confirmation each stale row is deleted via `GlobalDb::delete_project`. When stdin is not a terminal (CI, piped invocation), the stale list is shown as a warning with a hint to re-run interactively. ### Fixed -- **`tracedecay reinstall` now refreshes every detected agent, not just the first one ever installed** — `migrate_installed_agents` previously returned early as soon as `installed_agents` was non-empty. A user who installed agent A and later configured agent B (e.g. installed Copilot first, then Claude) would have only A in the list, so `reinstall` silently skipped B and its tool permissions never got refreshed when new tools shipped. The migration now scans every agent on each call and additively appends any whose tracedecay config exists on disk but is missing from the tracked list. Side effect: a stale `tracedecay install` warning ("N new tracedecay tool(s) not yet permitted") could persist across reinstalls — that no longer happens. The detection logic is also extracted into a pure `detect_missing_installed_agents` helper covered by a regression test that reproduces the original "claude missing when copilot is tracked" scenario. -- **`tracedecay wipe` warning banner now reaches full width** — the colored title row was 49 visual columns while the `═` rules above and below were 64, producing a short red strip floating between long horizontal lines. The title is now centered and padded with red-background spaces, sandwiched between two blank red rows so the warning reads as a single fixed-width block. +- **`tracedecay reinstall` now refreshes every detected agent, not just the first one ever installed**. `migrate_installed_agents` previously returned early as soon as `installed_agents` was non-empty. A user who installed agent A and later configured agent B (e.g. installed Copilot first, then Claude) would have only A in the list, so `reinstall` silently skipped B and its tool permissions never got refreshed when new tools shipped. The migration now scans every agent on each call and additively appends any whose tracedecay config exists on disk but is missing from the tracked list. Side effect: a stale `tracedecay install` warning ("N new tracedecay tool(s) not yet permitted") could persist across reinstalls, that no longer happens. The detection logic is also extracted into a pure `detect_missing_installed_agents` helper covered by a regression test that reproduces the original "claude missing when copilot is tracked" scenario. +- **`tracedecay wipe` warning banner now reaches full width**, the colored title row was 49 visual columns while the `═` rules above and below were 64, producing a short red strip floating between long horizontal lines. The title is now centered and padded with red-background spaces, sandwiched between two blank red rows so the warning reads as a single fixed-width block. ## [4.3.10] - 2026-05-09 ### Added -- **`tracedecay list` command for inspecting tracked projects** — `list` shows the same projects `wipe` would target (current folder, ancestors, and descendants), with on-disk `.tracedecay/` size and tokens-saved per row, sorted by tokens-saved descending. `tracedecay list --all` (or `-a`) lists every project tracked in `~/.tracedecay/global.db`, marking entries whose `.tracedecay/` directory has been removed as `(stale)`. +- **`tracedecay list` command for inspecting tracked projects**. `list` shows the same projects `wipe` would target (current folder, ancestors, and descendants), with on-disk `.tracedecay/` size and tokens-saved per row, sorted by tokens-saved descending. `tracedecay list --all` (or `-a`) lists every project tracked in `~/.tracedecay/global.db`, marking entries whose `.tracedecay/` directory has been removed as `(stale)`. ### Changed -- **Country flags in `tracedecay status` are now shuffled on every render** — when more flags are tracked than fit on the line, the row used to always show the same prefix and `…` truncate the rest. Each `status` invocation now applies a Fisher-Yates shuffle (xorshift64 seeded from time + PID) before truncation, so a different sample of contributing countries is shown each time. +- **Country flags in `tracedecay status` are now shuffled on every render**, when more flags are tracked than fit on the line, the row used to always show the same prefix and `…` truncate the rest. Each `status` invocation now applies a Fisher-Yates shuffle (xorshift64 seeded from time + PID) before truncation, so a different sample of contributing countries is shown each time. ### Fixed -- **Tool-permission warning now points at `tracedecay reinstall`** — when new tracedecay tools are detected that aren't yet permitted in the agent config, the warning previously said "Run `tracedecay install` to update", which would re-do the full install. The warning now reads "Run `tracedecay reinstall` to update permissions", which is the right command for refreshing permissions on already-installed agents. +- **Tool-permission warning now points at `tracedecay reinstall`**, when new tracedecay tools are detected that aren't yet permitted in the agent config, the warning previously said "Run `tracedecay install` to update", which would re-do the full install. The warning now reads "Run `tracedecay reinstall` to update permissions", which is the right command for refreshing permissions on already-installed agents. ## [4.3.9] - 2026-05-09 ### Added -- **`tracedecay wipe` command for clearing local DBs** — `wipe` finds every `.tracedecay/tracedecay.db` project in the current folder, all its ancestors, and all its descendants (skipping `node_modules`, `target`, `.git`, `vendor`, `dist`, `build`, `.next`, `.venv`, `__pycache__`, and the user-level `~/.tracedecay/`), then prompts for a `go!` confirmation before removing each `.tracedecay/` directory and its row in the global DB. `tracedecay wipe --all` (or `-a`) instead wipes every project tracked in `~/.tracedecay/global.db` and then deletes the global DB itself, leaving it empty. Both flows display a bordered, blinking warning that lists every target before asking for confirmation. +- **`tracedecay wipe` command for clearing local DBs**. `wipe` finds every `.tracedecay/tracedecay.db` project in the current folder, all its ancestors, and all its descendants (skipping `node_modules`, `target`, `.git`, `vendor`, `dist`, `build`, `.next`, `.venv`, `__pycache__`, and the user-level `~/.tracedecay/`), then prompts for a `go!` confirmation before removing each `.tracedecay/` directory and its row in the global DB. `tracedecay wipe --all` (or `-a`) instead wipes every project tracked in `~/.tracedecay/global.db` and then deletes the global DB itself, leaving it empty. Both flows display a bordered, blinking warning that lists every target before asking for confirmation. ## [4.3.8] - 2026-05-06 ### Added -- **`DISABLE_TRACEDECAY=true` environment variable to opt out per-project (#19)** — when set in the MCP server configuration, the `serve` command exits cleanly without initializing. This lets users selectively disable tracedecay for large projects that consume too much RAM, without removing it from their global agent config. +- **`DISABLE_TRACEDECAY=true` environment variable to opt out per-project (#19)**, when set in the MCP server configuration, the `serve` command exits cleanly without initializing. This lets users selectively disable tracedecay for large projects that consume too much RAM, without removing it from their global agent config. ## [4.3.7] - 2026-05-06 ### Fixed -- **Incremental sync no longer aborts on cross-file edge references (#58)** — `insert_edges` now uses a conditional INSERT that silently skips edges whose source or target node does not yet exist in the database. Additionally, both incremental sync loops now insert all nodes first and queue edges for a second pass, so cross-file edges within the same sync batch always find their targets. Previously, `INSERT OR IGNORE` did not suppress FK violations, causing the sync to abort with `FOREIGN KEY constraint failed`. +- **Incremental sync no longer aborts on cross-file edge references (#58)**. `insert_edges` now uses a conditional INSERT that silently skips edges whose source or target node does not yet exist in the database. Both incremental sync loops now insert all nodes first and queue edges for a second pass, so cross-file edges within the same sync batch always find their targets. Previously, `INSERT OR IGNORE` did not suppress FK violations, causing the sync to abort with `FOREIGN KEY constraint failed`. ## [4.3.6] - 2026-05-06 ### Fixed -- **`upgrade` no longer stops the daemon when release assets aren't ready yet** — the preflight asset check now runs before stopping the daemon, so if CI hasn't finished building the release binaries, the command exits cleanly without disrupting the running MCP server. +- **`upgrade` no longer stops the daemon when release assets aren't ready yet**, the preflight asset check now runs before stopping the daemon, so if CI hasn't finished building the release binaries, the command exits cleanly without disrupting the running MCP server. ## [4.3.5] - 2026-05-06 ### Changed -- **Copilot MCP server now passes the workspace folder to `serve`** — both the VS Code (`mcp.servers.tracedecay`) and the Copilot CLI (`mcpServers.tracedecay`) registrations now launch the daemon as `tracedecay serve -p ${workspaceFolder}` instead of plain `tracedecay serve`. This lets the MCP server scope its index to the active workspace automatically without requiring a manual `-p` flag. -- **Copilot agent args validation tightened** — tests for `CopilotIntegration` now verify that `"serve"` is strictly the first argument and that all remaining args are limited to `-p` / `${workspaceFolder}`. This prevents silent regressions where extra or reordered flags could be injected into the MCP server launch command. +- **Copilot MCP server now passes the workspace folder to `serve`**, both the VS Code (`mcp.servers.tracedecay`) and the Copilot CLI (`mcpServers.tracedecay`) registrations now launch the daemon as `tracedecay serve -p ${workspaceFolder}` instead of plain `tracedecay serve`. This lets the MCP server scope its index to the active workspace automatically without requiring a manual `-p` flag. +- **Copilot agent args validation tightened**, tests for `CopilotIntegration` now verify that `"serve"` is strictly the first argument and that all remaining args are limited to `-p` / `${workspaceFolder}`. This prevents silent regressions where extra or reordered flags could be injected into the MCP server launch command. ### Fixed -- **`serve` now falls back to the global project database when CWD discovery fails (#55)** — when VS Code Copilot (or another host) launches `tracedecay serve` with the working directory set to the user's home folder and `${workspaceFolder}` fails to resolve, the server now checks `~/.tracedecay/global.db` for registered projects. If exactly one project is found, it is used automatically; if multiple are found, they are listed on stderr with guidance to pass `-p `. -- **`insert_at` no longer strips the trailing newline from edited files (#57)** — `str::lines()` discards the final `\n`, so the file was silently rewritten without its POSIX-required trailing newline. The join result now re-appends `\n` when the original file ended with one. -- **Clippy CI failures resolved** — fixed 6 `deny`-level clippy errors across extractors (identical `if`/`else` blocks in clojure, redundant `trim()` before `split_whitespace` in haskell, `map_or` → `is_some_and`, `Iterator::last` → `next_back` in SQL, `too_many_arguments` allow in haskell `emit`). -- **Foreign-key violations during incremental sync now point at the recovery path** — when an extractor produces an edge whose source or target is not in the same file's node set, `tracedecay sync` would die with `failed to insert edge: SQLite failure: FOREIGN KEY constraint failed` and no guidance. Full re-index masks this because bulk load disables FK enforcement, so the top-level error handler now detects this specific failure and suggests `tracedecay sync -f`. -- **Spinner no longer leaks on early exit** — added `Drop` for `Spinner` so when `?` propagates an error mid-sync the worker thread is joined, the line is cleared, and the cursor is restored. Previously the cursor stayed hidden after a failed sync. +- **`serve` now falls back to the global project database when CWD discovery fails (#55)**, when VS Code Copilot (or another host) launches `tracedecay serve` with the working directory set to the user's home folder and `${workspaceFolder}` fails to resolve, the server now checks `~/.tracedecay/global.db` for registered projects. If exactly one project is found, it is used automatically; if multiple are found, they are listed on stderr with guidance to pass `-p `. +- **`insert_at` no longer strips the trailing newline from edited files (#57)**. `str::lines()` discards the final `\n`, so the file was silently rewritten without its POSIX-required trailing newline. The join result now re-appends `\n` when the original file ended with one. +- **Clippy CI failures resolved**, fixed 6 `deny`-level clippy errors across extractors (identical `if`/`else` blocks in clojure, redundant `trim()` before `split_whitespace` in haskell, `map_or` → `is_some_and`, `Iterator::last` → `next_back` in SQL, `too_many_arguments` allow in haskell `emit`). +- **Foreign-key violations during incremental sync now point at the recovery path**, when an extractor produces an edge whose source or target is not in the same file's node set, `tracedecay sync` would die with `failed to insert edge: SQLite failure: FOREIGN KEY constraint failed` and no guidance. Full re-index masks this because bulk load disables FK enforcement, so the top-level error handler now detects this specific failure and suggests `tracedecay sync -f`. +- **Spinner no longer leaks on early exit**, added `Drop` for `Spinner` so when `?` propagates an error mid-sync the worker thread is joined, the line is cleared, and the cursor is restored. Previously the cursor stayed hidden after a failed sync. ## [4.3.4] - 2026-05-02 ### Fixed -- **`tracedecay sync` no longer hangs on large monorepos with `node_modules` symlinks** — the directory walker now prunes excluded directories (e.g. `node_modules`, `vendor`, `build`) at the `filter_entry` level before descending into them. Previously, exclusions were only checked per-file after the walker had already entered the directory, so monorepo setups where a package manager creates symlinks inside `node_modules` pointing back into source directories (e.g. `../../api`) could cause the scanner to spin indefinitely. Closes #36. +- **`tracedecay sync` no longer hangs on large monorepos with `node_modules` symlinks**, the directory walker now prunes excluded directories (e.g. `node_modules`, `vendor`, `build`) at the `filter_entry` level before descending into them. Previously, exclusions were only checked per-file after the walker had already entered the directory, so monorepo setups where a package manager creates symlinks inside `node_modules` pointing back into source directories (e.g. `../../api`) could cause the scanner to spin indefinitely. Closes #36. ## [4.3.3] - 2026-05-02 ### Added -- **`tracedecay_body`** — new MCP tool that returns the full source body of a symbol by name (function, struct, const, etc.). Collapses search + node lookup + file read into a single call; returns multiple ranked matches when the name is ambiguous. -- **`tracedecay_todos`** — new MCP tool that finds TODO, FIXME, XXX, HACK, WIP, NOTE, and UNIMPLEMENTED markers across the project. Each result includes the marker kind, file, line, the comment text, and the enclosing symbol name. Filterable by marker kind and path prefix. +- **`tracedecay_body`**, new MCP tool that returns the full source body of a symbol by name (function, struct, const, etc.). Collapses search + node lookup + file read into a single call; returns multiple ranked matches when the name is ambiguous. +- **`tracedecay_todos`**, new MCP tool that finds TODO, FIXME, XXX, HACK, WIP, NOTE, and UNIMPLEMENTED markers across the project. Each result includes the marker kind, file, line, the comment text, and the enclosing symbol name. Filterable by marker kind and path prefix. ### Fixed -- **SQL (and 8 other new-language) files no longer panic during sync** — `tracedecay-large-treesitters 0.4.0` is now published to crates.io and `Cargo.toml` references the registry version instead of a local path. Users who built 4.3.2 via `cargo install` received the old 0.3.2 grammar bundle (no SQL), causing a panic per `.sql` file. Closes #53. +- **SQL (and 8 other new-language) files no longer panic during sync**. `tracedecay-large-treesitters 0.4.0` is now published to crates.io and `Cargo.toml` references the registry version instead of a local path. Users who built 4.3.2 via `cargo install` received the old 0.3.2 grammar bundle (no SQL), causing a panic per `.sql` file. Closes #53. ### Changed -- **`tracedecay-large-treesitters` dependency pinned to published 0.4.0** — switched from a local path dependency to `"0.4.0"` so `cargo install tracedecay` picks up the full grammar set including SQL, R, Julia, Haskell, OCaml, Clojure, Erlang, Elixir, and F#. +- **`tracedecay-large-treesitters` dependency pinned to published 0.4.0**, switched from a local path dependency to `"0.4.0"` so `cargo install tracedecay` picks up the full grammar set including SQL, R, Julia, Haskell, OCaml, Clojure, Erlang, Elixir, and F#. ### Internal -- **Grammar completeness test** — `ts_provider::tests::all_extractor_keys_are_registered` verifies every language key an extractor passes to `ts_provider::language()` is present in the bundled grammar table. CI will catch mismatches before a release ships. +- **Grammar completeness test**. `ts_provider::tests::all_extractor_keys_are_registered` verifies every language key an extractor passes to `ts_provider::language()` is present in the bundled grammar table. CI will catch mismatches before a release ships. ## [4.3.2] - 2026-05-01 ### Added -- **9 new language extractors — R, SQL, Julia, Haskell, OCaml, Clojure, Erlang, Elixir, F#** — closes the gap between tracedecay and sentrux for functional and data-science languages. Each extractor handles the language's primary top-level constructs and is gated behind its own `lang-*` feature flag, all included in `full`: - - **R** (`.r`, `.R`) — function assignments (`foo <- function(...)`), call sites, roxygen2 docstrings. Requires `tracedecay-large-treesitters` ≥ 0.4.0. - - **SQL** (`.sql`) — `CREATE TABLE`, `CREATE VIEW`, `CREATE FUNCTION`, `CREATE PROCEDURE` via `tree-sitter-sequel`. - - **Julia** (`.jl`) — `function`, `macro`, `struct`, `abstract_definition`, `module` definitions; import/using nodes. - - **Haskell** (`.hs`, `.lhs`) — `function`/`bind` declarations, `data_type`/`newtype`, `class`, `instance`, `import` nodes. - - **OCaml** (`.ml`, `.mli`) — top-level `let_binding` (function vs const), `type_definition`, `module_definition`, `class_definition`, `open` nodes. - - **Clojure** (`.clj`, `.cljs`, `.cljc`) — `defn`/`defmacro`, `ns`, `def`/`defonce`, `defprotocol`/`defrecord`/`deftype` via `list_lit` dispatch on the first symbol. - - **Erlang** (`.erl`, `.hrl`) — `fun_decl` with arity-qualified names (`foo/2`), `-module` attribute, `-type`/`-opaque` declarations. - - **Elixir** (`.ex`, `.exs`) — `def`/`defp`, `defmodule`, `defmacro`/`defmacrop`, `defstruct` via `call`-node dispatch on the function head. - - **F#** (`.fs`, `.fsi`, `.fsx`) — `function_or_value_defn`, `type_definition`, `module_defn`, `namespace`, `open_decl` nodes. -- **Complexity configs for all 9 new languages** — `R_COMPLEXITY`, `SQL_COMPLEXITY`, `JULIA_COMPLEXITY`, `HASKELL_COMPLEXITY`, `OCAML_COMPLEXITY`, `CLOJURE_COMPLEXITY`, `ERLANG_COMPLEXITY`, `ELIXIR_COMPLEXITY`, `FSHARP_COMPLEXITY` added to `src/extraction/complexity.rs`. -- **`tracedecay-large-treesitters` 0.4.0** — bundles the 9 new tree-sitter grammars: `tree-sitter-r`, `tree-sitter-sequel`, `tree-sitter-julia`, `tree-sitter-haskell`, `tree-sitter-ocaml`, `tree-sitter-clojure-orchard`, `tree-sitter-erlang`, `tree-sitter-elixir`, `tree-sitter-fsharp`. +- **9 new language extractors. R, SQL, Julia, Haskell, OCaml, Clojure, Erlang, Elixir, F#**, closes the gap between tracedecay and sentrux for functional and data-science languages. Each extractor handles the language's primary top-level constructs and is gated behind its own `lang-*` feature flag, all included in `full`: + - **R** (`.r`, `.R`), function assignments (`foo <- function(...)`), call sites, roxygen2 docstrings. Requires `tracedecay-large-treesitters` ≥ 0.4.0. + - **SQL** (`.sql`). `CREATE TABLE`, `CREATE VIEW`, `CREATE FUNCTION`, `CREATE PROCEDURE` via `tree-sitter-sequel`. + - **Julia** (`.jl`). `function`, `macro`, `struct`, `abstract_definition`, `module` definitions; import/using nodes. + - **Haskell** (`.hs`, `.lhs`). `function`/`bind` declarations, `data_type`/`newtype`, `class`, `instance`, `import` nodes. + - **OCaml** (`.ml`, `.mli`), top-level `let_binding` (function vs const), `type_definition`, `module_definition`, `class_definition`, `open` nodes. + - **Clojure** (`.clj`, `.cljs`, `.cljc`). `defn`/`defmacro`, `ns`, `def`/`defonce`, `defprotocol`/`defrecord`/`deftype` via `list_lit` dispatch on the first symbol. + - **Erlang** (`.erl`, `.hrl`). `fun_decl` with arity-qualified names (`foo/2`), `-module` attribute, `-type`/`-opaque` declarations. + - **Elixir** (`.ex`, `.exs`). `def`/`defp`, `defmodule`, `defmacro`/`defmacrop`, `defstruct` via `call`-node dispatch on the function head. + - **F#** (`.fs`, `.fsi`, `.fsx`). `function_or_value_defn`, `type_definition`, `module_defn`, `namespace`, `open_decl` nodes. +- **Complexity configs for all 9 new languages**. `R_COMPLEXITY`, `SQL_COMPLEXITY`, `JULIA_COMPLEXITY`, `HASKELL_COMPLEXITY`, `OCAML_COMPLEXITY`, `CLOJURE_COMPLEXITY`, `ERLANG_COMPLEXITY`, `ELIXIR_COMPLEXITY`, `FSHARP_COMPLEXITY` added to `src/extraction/complexity.rs`. +- **`tracedecay-large-treesitters` 0.4.0**, bundles the 9 new tree-sitter grammars: `tree-sitter-r`, `tree-sitter-sequel`, `tree-sitter-julia`, `tree-sitter-haskell`, `tree-sitter-ocaml`, `tree-sitter-clojure-orchard`, `tree-sitter-erlang`, `tree-sitter-elixir`, `tree-sitter-fsharp`. ### Fixed -- **`tracedecay monitor` displayed temp directories as projects** — MCP clients that create per-request temp directories (names matching `.tmp…`) were appearing as project entries in the monitor. These are now filtered out at render time; the TOTAL line reflects only real projects. +- **`tracedecay monitor` displayed temp directories as projects**. MCP clients that create per-request temp directories (names matching `.tmp…`) were appearing as project entries in the monitor. These are now filtered out at render time; the TOTAL line reflects only real projects. ### Changed -- **`tracedecay monitor` now supports scrolling** — Up/Down arrows scroll one line at a time; PageUp/PageDown scroll one screen. Scroll offset is clamped to the available content and resets to zero on Ctrl+R. Footer hint updated accordingly. +- **`tracedecay monitor` now supports scrolling**. Up/Down arrows scroll one line at a time; PageUp/PageDown scroll one screen. Scroll offset is clamped to the available content and resets to zero on Ctrl+R. Footer hint updated accordingly. ## [4.3.1] - 2026-05-01 ### Fixed -- **`tracedecay_str_replace`, `tracedecay_multi_str_replace`, and `tracedecay_insert_at` silently mutated files for unsupported types (issue #51)** — all three tools write the file to disk and then call `reindex_file` to update the graph. For file types without a registered extractor (e.g. `.css`, `.html`), `reindex_file` returned `Err("unsupported file type: …")`; the `?` propagated that error to the caller, which reported tool failure — but the write had already been committed. The fix changes `reindex_file` to return `Ok(())` early when no extractor is found, so edits to unsupported file types succeed and the graph simply skips reindexing for those files. +- **`tracedecay_str_replace`, `tracedecay_multi_str_replace`, and `tracedecay_insert_at` silently mutated files for unsupported types (issue #51)**, all three tools write the file to disk and then call `reindex_file` to update the graph. For file types without a registered extractor (e.g. `.css`, `.html`), `reindex_file` returned `Err("unsupported file type: …")`; the `?` propagated that error to the caller, which reported tool failure, but the write had already been committed. The fix changes `reindex_file` to return `Ok(())` early when no extractor is found, so edits to unsupported file types succeed and the graph simply skips reindexing for those files. ### Changed -- **Sync duration is now tracked and displayed** — `GraphStats` gains a `last_sync_duration_ms` field persisted to the metadata store. All three sync paths (full index, `sync_single_files`, `sync_with_progress_verbose`) write this value. The status table's sync row now shows the duration inline: `Last sync 2m ago (1.2s) Full sync 1d ago`. Duration is omitted when the value is unknown (existing databases before this change). +- **Sync duration is now tracked and displayed**. `GraphStats` gains a `last_sync_duration_ms` field persisted to the metadata store. All three sync paths (full index, `sync_single_files`, `sync_with_progress_verbose`) write this value. The status table's sync row now shows the duration inline: `Last sync 2m ago (1.2s) Full sync 1d ago`. Duration is omitted when the value is unknown (existing databases before this change). ## [4.3.0] - 2026-04-30 ### Added -- **Subprocess-isolated extraction** — every file is now parsed inside a short-lived worker process rather than in the sync process itself. If a tree-sitter grammar segfaults, calls `abort()`, or otherwise terminates by a path Rust cannot intercept, only the worker dies; the pool respawns it, the offending file is logged and skipped, and sync continues. This is a stronger guarantee than the v4.2.1 `catch_unwind` defense, which could only catch Rust panics. +- **Subprocess-isolated extraction**, every file is now parsed inside a short-lived worker process rather than in the sync process itself. If a tree-sitter grammar segfaults, calls `abort()`, or otherwise terminates by a path Rust cannot intercept, only the worker dies; the pool respawns it, the offending file is logged and skipped, and sync continues. This is a stronger guarantee than the v4.2.1 `catch_unwind` defense, which could only catch Rust panics. - The worker is exposed via a hidden subcommand (`tracedecay extract-worker`) that authenticates against the parent through a 256-bit per-spawn token: required as both an env var and as the first 32 bytes on stdin. A user invoking the binary directly hits the missing-env check and exits non-zero. The subcommand is also hidden from `--help`. - When `current_exe()` does not point at a real `tracedecay` binary (e.g. under `cargo test`, where the test harness is the running binary), extraction transparently falls back to the in-process path. Tests therefore continue to exercise extractors directly without needing to spawn subprocesses. - Defaults to `available_parallelism()` workers; opt out via `TRACEDECAY_DISABLE_SUBPROCESS=1` if needed. ### Changed -- Single-file extraction (used by the `tracedecay_str_replace`, `tracedecay_insert_at`, etc. edit tools) still runs in-process — the subprocess overhead is unjustified for one-shot operations and these tools are interactive enough that an extractor crash is immediately visible. +- Single-file extraction (used by the `tracedecay_str_replace`, `tracedecay_insert_at`, etc. edit tools) still runs in-process, the subprocess overhead is unjustified for one-shot operations and these tools are interactive enough that an extractor crash is immediately visible. ## [4.2.1] - 2026-04-30 ### Fixed -- **Sync no longer aborts when a tree-sitter grammar hits an internal assertion (issue #49)** — the vendored `tree-sitter-markdown` C++ scanner contains `assert()` calls that, on certain autolink constructs, called `abort()` and killed the entire `tracedecay sync` process (core-dumped on Linux). Two layers of defense: +- **Sync no longer aborts when a tree-sitter grammar hits an internal assertion (issue #49)**, the vendored `tree-sitter-markdown` C++ scanner contains `assert()` calls that, on certain autolink constructs, called `abort()` and killed the entire `tracedecay sync` process (core-dumped on Linux). Two layers of defense: - Added `.cargo/config.toml` with `CFLAGS=-DNDEBUG` and `CXXFLAGS=-DNDEBUG`. `cc-rs` reads these env vars when compiling vendored grammars in `tracedecay-large-treesitters`'s build script, disabling C/C++ assertions in release builds. A failed assertion now degrades to a malformed parse tree (which the extractor handles gracefully) instead of `SIGABRT`. - Added a `safe_extract` helper that wraps every `extractor.extract()` call site with `std::panic::catch_unwind`. A Rust panic from any extractor (malformed input, future bugs) now logs the file path and skips it instead of bringing down the whole sync. - See issue #50 for the broader follow-up: migrating to pure-Rust generated parsers via the `--rust` fork of tree-sitter to eliminate this class of failure entirely. @@ -3130,161 +3130,161 @@ The largest functional jump since 4.0: nine new MCP tools, a cross-session respo ## [4.2.0] - 2026-04-30 ### Added -- **Health & structural analysis tools** — seven new MCP tools that expose quality insights from the existing code graph: - - `tracedecay_health` — composite quality signal (0–10000) from five independent dimensions: acyclicity, depth, equality, redundancy, and modularity. Uses geometric mean so no single dimension can be gamed. Supports `details: true` for per-dimension breakdown. - - `tracedecay_gini` — Gini inequality coefficient for any metric (complexity, lines, fan_in, fan_out, members) across files or symbols. Identifies god files and uneven complexity distribution with interpretive labels and ranked outliers. - - `tracedecay_dependency_depth` — longest file-level dependency chains (Lakos levelization). Shows transitive fragility that direct coupling metrics miss, with full chain reconstruction after cycle-breaking via Tarjan's SCC. - - `tracedecay_dsm` — Design Structure Matrix in three output formats: `stats` (density, cluster count), `clusters` (per-directory edge analysis), and `matrix` (NxN grid with short filenames). Reveals hidden coupling patterns and layering violations. - - `tracedecay_test_risk` — risk-weighted test gap analysis combining complexity, fan-in, test coverage, and git churn (90-day window) into a single score. Answers "where should the next test go?" with `include_tested` option for finding weak-test candidates. - - `tracedecay_session_start` — saves current health metrics as a JSON baseline for later comparison. Call before starting an AI coding session. - - `tracedecay_session_end` — re-computes health and diffs against the session baseline. Reports per-dimension deltas with improved/degraded/unchanged labels, overall pass/fail, and cleans up the baseline file. -- **Git churn integration** — new `src/graph/git.rs` module shells out to `git log` at runtime to compute per-file commit frequency. Used by `tracedecay_test_risk` as a risk multiplier (log2-scaled) without persisting any data to the tracedecay DB. -- **File-level DAG builder** — new `build_file_adjacency` method on `GraphQueryManager` constructs a directed file dependency graph from the existing edge data in a single SQL query. Shared foundation for health, depth, DSM, and modularity computations. +- **Health & structural analysis tools**, seven new MCP tools that expose quality insights from the existing code graph: + - `tracedecay_health`, composite quality signal (0–10000) from five independent dimensions: acyclicity, depth, equality, redundancy, and modularity. Uses geometric mean so no single dimension can be gamed. Supports `details: true` for per-dimension breakdown. + - `tracedecay_gini`. Gini inequality coefficient for any metric (complexity, lines, fan_in, fan_out, members) across files or symbols. Identifies god files and uneven complexity distribution with interpretive labels and ranked outliers. + - `tracedecay_dependency_depth`, longest file-level dependency chains (Lakos levelization). Shows transitive fragility that direct coupling metrics miss, with full chain reconstruction after cycle-breaking via Tarjan's SCC. + - `tracedecay_dsm`. Design Structure Matrix in three output formats: `stats` (density, cluster count), `clusters` (per-directory edge analysis), and `matrix` (NxN grid with short filenames). Reveals hidden coupling patterns and layering violations. + - `tracedecay_test_risk`, risk-weighted test gap analysis combining complexity, fan-in, test coverage, and git churn (90-day window) into a single score. Answers "where should the next test go?" with `include_tested` option for finding weak-test candidates. + - `tracedecay_session_start`, saves current health metrics as a JSON baseline for later comparison. Call before starting an AI coding session. + - `tracedecay_session_end`, re-computes health and diffs against the session baseline. Reports per-dimension deltas with improved/degraded/unchanged labels, overall pass/fail, and cleans up the baseline file. +- **Git churn integration**, new `src/graph/git.rs` module shells out to `git log` at runtime to compute per-file commit frequency. Used by `tracedecay_test_risk` as a risk multiplier (log2-scaled) without persisting any data to the tracedecay DB. +- **File-level DAG builder**, new `build_file_adjacency` method on `GraphQueryManager` constructs a directed file dependency graph from the existing edge data in a single SQL query. Shared foundation for health, depth, DSM, and modularity computations. ## [4.1.8] - 2026-04-30 ### Added -- **`include` config glob** — new `include` field in `.tracedecay/config.json` lets users whitelist hidden (dot-prefixed) paths for indexing. By default, all dot-directories are skipped during sync; paths matching an `include` glob (e.g. `[".github/**"]`) are now walked and indexed. The `exclude` list still applies after inclusion, so `.git/**` and `.tracedecay/**` remain filtered even with broad include patterns. -- **Markdown extraction** — tree-sitter based markdown parser that extracts headers as `Module` nodes with hierarchical `Contains` edges, and code links as `Uses` edges for cross-reference tracking (PR #47) +- **`include` config glob**, new `include` field in `.tracedecay/config.json` lets users whitelist hidden (dot-prefixed) paths for indexing. By default, all dot-directories are skipped during sync; paths matching an `include` glob (e.g. `[".github/**"]`) are now walked and indexed. The `exclude` list still applies after inclusion, so `.git/**` and `.tracedecay/**` remain filtered even with broad include patterns. +- **Markdown extraction**, tree-sitter based markdown parser that extracts headers as `Module` nodes with hierarchical `Contains` edges, and code links as `Uses` edges for cross-reference tracking (PR #47) ## [4.1.7] - 2026-04-29 ### Fixed -- **Nested `.gitignore` files were silently ignored** — `git_ignore(true)` in the `ignore` crate relies on git repository detection (walking up to find `.git`) to build the gitignore rule stack. When the walk root was outside a git repo — or in a subdirectory that the crate couldn't trace back to a `.git` — rules in nested `.gitignore` files were never applied. Added `add_custom_ignore_filename(".gitignore")` to the `WalkBuilder`, which makes the crate read every `.gitignore` it encounters as a standalone ignore source regardless of git repo presence. Five regression tests cover: subdirectory exclusion, scope isolation, negation overrides, deep descendant exclusion, and a direct `ignore`-crate sanity check. +- **Nested `.gitignore` files were silently ignored**. `git_ignore(true)` in the `ignore` crate relies on git repository detection (walking up to find `.git`) to build the gitignore rule stack. When the walk root was outside a git repo, or in a subdirectory that the crate couldn't trace back to a `.git`, rules in nested `.gitignore` files were never applied. Added `add_custom_ignore_filename(".gitignore")` to the `WalkBuilder`, which makes the crate read every `.gitignore` it encounters as a standalone ignore source regardless of git repo presence. Five regression tests cover: subdirectory exclusion, scope isolation, negation overrides, deep descendant exclusion, and a direct `ignore`-crate sanity check. ## [4.1.6] - 2026-04-29 ### Fixed -- **`logging/setLevel` returned MethodNotFound on every session start** — the server correctly advertised the `logging` capability in its `initialize` response (required for the `notifications/message` version-warning feature), but had no handler for the `logging/setLevel` request that MCP clients send immediately after. Every session produced a `-32601` error in the client log. The handler now returns an empty success as required by the MCP spec (RFC 5424 log-level filtering is advisory; the server continues to emit notifications at its own discretion). -- **`java_extraction` panic on empty Javadoc** — parsing a Java file containing a docstring with no content caused a panic (fixes #44). +- **`logging/setLevel` returned MethodNotFound on every session start**, the server correctly advertised the `logging` capability in its `initialize` response (required for the `notifications/message` version-warning feature), but had no handler for the `logging/setLevel` request that MCP clients send immediately after. Every session produced a `-32601` error in the client log. The handler now returns an empty success as required by the MCP spec (RFC 5424 log-level filtering is advisory; the server continues to emit notifications at its own discretion). +- **`java_extraction` panic on empty Javadoc**, parsing a Java file containing a docstring with no content caused a panic (fixes #44). ## [4.1.5] - 2026-04-29 ### Added -- **Edit primitives for code modification** — four new MCP tools enable Claude and friends to edit files without regex or shell quoting hazards (PR #43 by @pierreaubert): - - `tracedecay_str_replace` — replaces a unique `old_str` with `new_str`; fails if 0 or >1 matches, protecting against multi-edit bugs - - `tracedecay_multi_str_replace` — applies N `(old, new)` replacements atomically; all-or-nothing transaction - - `tracedecay_insert_at` — inserts content before or after a unique anchor string or line number - - `tracedecay_ast_grep_rewrite` — structural code rewrite via ast-grep CLI (`--rewrite` mode) -- **Auto re-indexing** — all four edit tools automatically re-index the modified file in the code graph after writing, keeping the graph in sync without manual steps (PR #43 by @pierreaubert) +- **Edit primitives for code modification**, four new MCP tools enable Claude and friends to edit files without regex or shell quoting hazards (PR #43 by @pierreaubert): + - `tracedecay_str_replace`, replaces a unique `old_str` with `new_str`; fails if 0 or >1 matches, protecting against multi-edit bugs + - `tracedecay_multi_str_replace`, applies N `(old, new)` replacements atomically; all-or-nothing transaction + - `tracedecay_insert_at`, inserts content before or after a unique anchor string or line number + - `tracedecay_ast_grep_rewrite`, structural code rewrite via ast-grep CLI (`--rewrite` mode) +- **Auto re-indexing**, all four edit tools automatically re-index the modified file in the code graph after writing, keeping the graph in sync without manual steps (PR #43 by @pierreaubert) ### Performance -- **Fixed N+1 query patterns in graph traversal** — `traverse_bfs`, `traverse_dfs`, `get_callers`, `get_callees`, `get_file_dependencies`, `get_file_dependents`, and `find_dead_code` were each making a separate database query per node, causing excessive CPU usage on large codebases. All methods now batch-fetch nodes using a single `WHERE id IN (...)` query, reducing database roundtrips from O(N) to O(1). (PR #40 by @pierreaubert) +- **Fixed N+1 query patterns in graph traversal**. `traverse_bfs`, `traverse_dfs`, `get_callers`, `get_callees`, `get_file_dependencies`, `get_file_dependents`, and `find_dead_code` were each making a separate database query per node, causing excessive CPU usage on large codebases. All methods now batch-fetch nodes using a single `WHERE id IN (...)` query, reducing database roundtrips from O(N) to O(1). (PR #40 by @pierreaubert) ### Fixed -- **`find_dead_code` hit SQLite variable limit on large codebases** — the query used `IN (?, ?, …)` binds which SQLite caps at 999 variables; replaced with `NOT EXISTS (SELECT 1 FROM edges WHERE …)` to avoid the limit entirely. (PR #43 by @pierreaubert) -- **`tracedecay_test_map` failed to resolve cross-crate qualified calls** — when a reference contained `::` (e.g. `crate_name::func`), a failed qualified-name match returned `None` without falling back to a simple-name lookup, breaking test coverage queries for integration tests that call across crate boundaries. Fixed by removing the early return and adding a simple-name fallback that strips the qualifier before matching. (PR #43 by @pierreaubert) -- **Sync frequency reduced and stale-warning auto-sync added** — sync interval dropped from its previous default to 2 s (configurable); the MCP server now automatically triggers a live sync when an agent receives a stale-graph warning, avoiding a manual `tracedecay sync` round-trip. (PR #43 by @pierreaubert) -- **`TOOL_NAMES` and `EXPECTED_TOOL_PERMS` were static** — `doctor` and `install` would not detect or register newly-introduced MCP tools. Both lists are now built dynamically so adding a tool automatically propagates to health checks and permission installation. (PR #43 by @pierreaubert) -- **`tracedecay monitor` now groups output per project then per tool** — previously all tool calls were listed in a flat stream; entries are now grouped by project path first, then by tool name, making it easier to see which project is driving activity. (PR #43 by @pierreaubert) +- **`find_dead_code` hit SQLite variable limit on large codebases**, the query used `IN (?, ?, …)` binds which SQLite caps at 999 variables; replaced with `NOT EXISTS (SELECT 1 FROM edges WHERE …)` to avoid the limit entirely. (PR #43 by @pierreaubert) +- **`tracedecay_test_map` failed to resolve cross-crate qualified calls**, when a reference contained `::` (e.g. `crate_name::func`), a failed qualified-name match returned `None` without falling back to a simple-name lookup, breaking test coverage queries for integration tests that call across crate boundaries. Fixed by removing the early return and adding a simple-name fallback that strips the qualifier before matching. (PR #43 by @pierreaubert) +- **Sync frequency reduced and stale-warning auto-sync added**, sync interval dropped from its previous default to 2 s (configurable); the MCP server now automatically triggers a live sync when an agent receives a stale-graph warning, avoiding a manual `tracedecay sync` round-trip. (PR #43 by @pierreaubert) +- **`TOOL_NAMES` and `EXPECTED_TOOL_PERMS` were static**. `doctor` and `install` would not detect or register newly-introduced MCP tools. Both lists are now built dynamically so adding a tool automatically propagates to health checks and permission installation. (PR #43 by @pierreaubert) +- **`tracedecay monitor` now groups output per project then per tool**, previously all tool calls were listed in a flat stream; entries are now grouped by project path first, then by tool name, making it easier to see which project is driving activity. (PR #43 by @pierreaubert) ## [4.1.4] - 2026-04-25 ### Fixed -- **`tracedecay monitor` panicked on macOS/Linux with "Cannot start a runtime from within a runtime" (issue #39)** — the previous fix for the Windows panic kept a Unix-only branch that built a new `tokio::runtime` and called `block_on` from inside `#[tokio::main]`, which panics on every platform, not just Windows. `refresh_cost_cache` now uses `block_in_place + Handle::current().block_on` unconditionally, since `monitor::run()` is always invoked from the existing multi-threaded runtime. +- **`tracedecay monitor` panicked on macOS/Linux with "Cannot start a runtime from within a runtime" (issue #39)**, the previous fix for the Windows panic kept a Unix-only branch that built a new `tokio::runtime` and called `block_on` from inside `#[tokio::main]`, which panics on every platform, not just Windows. `refresh_cost_cache` now uses `block_in_place + Handle::current().block_on` unconditionally, since `monitor::run()` is always invoked from the existing multi-threaded runtime. ## [4.1.3] - 2026-04-24 ### Fixed -- **Backslashed Windows hook paths never self-healed (issue #38)** — the v4.0.2 fix for #20 normalized `which_tracedecay()` output but could not rewrite existing settings. `install_single_hook` is idempotent by presence, so when a tracedecay hook already existed with a backslashed path, the silent backfill in `check_install_stale` left it untouched. Additionally, the backfill only scanned `~/.claude/settings.json` — project-level `.claude/settings.json` and `.claude/settings.local.json` were never touched, so opening a previously-configured project could still trigger `bash: C:Usersalkamscoopappstracedecaycurrenttracedecay.exe: command not found`. Fixed with a new `normalize_hook_command_paths` pass that rewrites any backslash-containing tracedecay hook command to forward slashes, and by extending the backfill to the current project's `.claude` directory. +- **Backslashed Windows hook paths never self-healed (issue #38)**, the v4.0.2 fix for #20 normalized `which_tracedecay()` output but could not rewrite existing settings. `install_single_hook` is idempotent by presence, so when a tracedecay hook already existed with a backslashed path, the silent backfill in `check_install_stale` left it untouched. The backfill only scanned `~/.claude/settings.json`, project-level `.claude/settings.json` and `.claude/settings.local.json` were never touched, so opening a previously-configured project could still trigger `bash: C:Usersalkamscoopappstracedecaycurrenttracedecay.exe: command not found`. Fixed with a new `normalize_hook_command_paths` pass that rewrites any backslash-containing tracedecay hook command to forward slashes, and by extending the backfill to the current project's `.claude` directory. ## [4.1.2] - 2026-04-22 ### Added -- **Mistral Vibe agent integration** — `tracedecay install --agent vibe` registers the tracedecay MCP server in Vibe's `~/.vibe/config.toml` as a `[[mcp_servers]]` stdio entry, and appends prompt rules to `~/.vibe/prompts/cli.md`. Supports install, uninstall, and healthcheck. Respects the `VIBE_HOME` environment variable. Closes #37. +- **Mistral Vibe agent integration**. `tracedecay install --agent vibe` registers the tracedecay MCP server in Vibe's `~/.vibe/config.toml` as a `[[mcp_servers]]` stdio entry, and appends prompt rules to `~/.vibe/prompts/cli.md`. Supports install, uninstall, and healthcheck. Respects the `VIBE_HOME` environment variable. Closes #37. ## [4.1.1] - 2026-04-22 ### Added -- **`tracedecay sync --verbose` (`-v`)** — prints per-phase diagnostic lines during sync to help diagnose slow or stuck syncs on large repos. Shows file counts, change breakdowns, and timings for each phase (scan, stat-check, hash, content check, index, resolve, DB write). Also works with `--force` full re-index. Addresses #36. +- **`tracedecay sync --verbose` (`-v`)**, prints per-phase diagnostic lines during sync to help diagnose slow or stuck syncs on large repos. Shows file counts, change breakdowns, and timings for each phase (scan, stat-check, hash, content check, index, resolve, DB write). Also works with `--force` full re-index. Addresses #36. ## [4.1.0] - 2026-04-20 ### Added -- **Walk-up project discovery** — `tracedecay serve`, `tracedecay sync`, and `tracedecay status` now walk up the directory tree to find the nearest `.tracedecay/` database when no `--path` is given. This means you can launch an AI agent from a subdirectory of your project and tracedecay will find the index automatically — similar to how git finds `.git/`. `tracedecay init` is unchanged and always creates a new project at the target directory. -- **Subdirectory scope filtering** — when the MCP server is started from a subdirectory, listing and discovery tools (`tracedecay_files`, `tracedecay_search`, `tracedecay_context`, `tracedecay_dead_code`, `tracedecay_rank`, `tracedecay_largest`, `tracedecay_coupling`, `tracedecay_complexity`, `tracedecay_doc_coverage`, `tracedecay_god_class`, `tracedecay_unused_imports`, `tracedecay_hotspots`, and others) automatically scope results to that subdirectory. Graph traversal tools (`tracedecay_callers`, `tracedecay_callees`, `tracedecay_impact`, `tracedecay_affected`, `tracedecay_type_hierarchy`) remain unscoped so cross-directory relationships are preserved. The user can always override the scope by providing an explicit `path` parameter. `tracedecay_status` reports the active scope prefix when one is in effect. +- **Walk-up project discovery**. `tracedecay serve`, `tracedecay sync`, and `tracedecay status` now walk up the directory tree to find the nearest `.tracedecay/` database when no `--path` is given. This means you can launch an AI agent from a subdirectory of your project and tracedecay will find the index automatically, similar to how git finds `.git/`. `tracedecay init` is unchanged and always creates a new project at the target directory. +- **Subdirectory scope filtering**, when the MCP server is started from a subdirectory, listing and discovery tools (`tracedecay_files`, `tracedecay_search`, `tracedecay_context`, `tracedecay_dead_code`, `tracedecay_rank`, `tracedecay_largest`, `tracedecay_coupling`, `tracedecay_complexity`, `tracedecay_doc_coverage`, `tracedecay_god_class`, `tracedecay_unused_imports`, `tracedecay_hotspots`, and others) automatically scope results to that subdirectory. Graph traversal tools (`tracedecay_callers`, `tracedecay_callees`, `tracedecay_impact`, `tracedecay_affected`, `tracedecay_type_hierarchy`) remain unscoped so cross-directory relationships are preserved. The user can always override the scope by providing an explicit `path` parameter. `tracedecay_status` reports the active scope prefix when one is in effect. ## [4.0.7] - 2026-04-18 ### Fixed -- **Symlinked source directories were not indexed** — both the plain `walkdir` and `.gitignore`-aware `ignore::WalkBuilder` file discovery paths now follow symlinks (`follow_links(true)`), so projects that expose source code through symlinked directories are fully indexed. (PR #34 by @lesbass) +- **Symlinked source directories were not indexed**, both the plain `walkdir` and `.gitignore`-aware `ignore::WalkBuilder` file discovery paths now follow symlinks (`follow_links(true)`), so projects that expose source code through symlinked directories are fully indexed. (PR #34 by @lesbass) ## [4.0.6] - 2026-04-18 ### Added -- **GLSL language support** — new tree-sitter-based extractor for OpenGL shading language files (`.glsl`, `.vert`, `.frag`, `.geom`, `.comp`, `.tesc`, `.tese`). Extracts functions, structs with fields, uniform/in/out/varying declarations, preprocessor defines, call sites, and complexity metrics. Requires `tracedecay-large-treesitters` 0.3.0. Feature-gated as `lang-glsl` in the Full tier. Closes #35. +- **GLSL language support**, new tree-sitter-based extractor for OpenGL shading language files (`.glsl`, `.vert`, `.frag`, `.geom`, `.comp`, `.tesc`, `.tese`). Extracts functions, structs with fields, uniform/in/out/varying declarations, preprocessor defines, call sites, and complexity metrics. Requires `tracedecay-large-treesitters` 0.3.0. Feature-gated as `lang-glsl` in the Full tier. Closes #35. ### Fixed -- **`tracedecay upgrade` fails on Homebrew installs** — `self_replace` failed with `ENOENT` on Homebrew symlinks because it resolved relative symlink targets from CWD instead of the symlink's parent. Now dispatches to install-method-aware replacement: Homebrew bypasses `self_replace` and atomically replaces the binary at the canonical Cellar path, renames the version directory, and updates the symlink + `INSTALL_RECEIPT.json` so `brew` reports the correct version. Scoop updates the version directory, junction, and `manifest.json`. Other symlinked installs get a canonicalization fallback. Supersedes PR #33. +- **`tracedecay upgrade` fails on Homebrew installs**. `self_replace` failed with `ENOENT` on Homebrew symlinks because it resolved relative symlink targets from CWD instead of the symlink's parent. Now dispatches to install-method-aware replacement: Homebrew bypasses `self_replace` and atomically replaces the binary at the canonical Cellar path, renames the version directory, and updates the symlink + `INSTALL_RECEIPT.json` so `brew` reports the correct version. Scoop updates the version directory, junction, and `manifest.json`. Other symlinked installs get a canonicalization fallback. Supersedes PR #33. ## [4.0.5] - 2026-04-17 ### Changed -- **Separate `tracedecay init` from `tracedecay sync`** — previously, `tracedecay sync` silently created a new database if none existed. This was a problem because the global git post-commit hook runs `tracedecay sync` in every repo after each commit, causing phantom `.tracedecay/` databases to appear in projects that never opted in. Now `tracedecay init` handles first-time project setup (creates DB + full index) and errors if already initialized, while `tracedecay sync` only performs incremental updates and errors if the project was never initialized. The git hook (`tracedecay sync >/dev/null 2>&1 &`) now safely exits with an error in non-enrolled repos — no database created. All agent setup messages and documentation updated to reference `tracedecay init` for first-time use. +- **Separate `tracedecay init` from `tracedecay sync`**, previously, `tracedecay sync` silently created a new database if none existed. This was a problem because the global git post-commit hook runs `tracedecay sync` in every repo after each commit, causing phantom `.tracedecay/` databases to appear in projects that never opted in. Now `tracedecay init` handles first-time project setup (creates DB + full index) and errors if already initialized, while `tracedecay sync` only performs incremental updates and errors if the project was never initialized. The git hook (`tracedecay sync >/dev/null 2>&1 &`) now safely exits with an error in non-enrolled repos, no database created. All agent setup messages and documentation updated to reference `tracedecay init` for first-time use. ## [4.0.4] - 2026-04-17 ### Added -- **Google Antigravity support** — new `tracedecay install --agent antigravity` registers the MCP server in `~/.gemini/antigravity/mcp_config.json`. Includes install, uninstall, healthcheck, and auto-detection. Closes #24. -- **Kilo CLI support** — new `tracedecay install --agent kilo` registers the MCP server in `~/.config/kilo/kilo.jsonc` using Kilo's `mcp` key with `type: "local"` format. Includes install, uninstall, healthcheck, and auto-detection. Closes #31. +- **Google Antigravity support**, new `tracedecay install --agent antigravity` registers the MCP server in `~/.gemini/antigravity/mcp_config.json`. Includes install, uninstall, healthcheck, and auto-detection. Closes #24. +- **Kilo CLI support**, new `tracedecay install --agent kilo` registers the MCP server in `~/.config/kilo/kilo.jsonc` using Kilo's `mcp` key with `type: "local"` format. Includes install, uninstall, healthcheck, and auto-detection. Closes #31. ### Changed -- **Simpler install prompts** — `tracedecay install` now asks a Y/n question per detected agent instead of showing a multi-select dialog box. Prints a +/- summary of changes at the end. Removed `dialoguer` dependency. -- **No-op upgrade is no longer an error** — `tracedecay upgrade` when already on the latest version now exits successfully instead of printing a misleading error. Same for `tracedecay channel` when already on the requested channel. (PR #30 by @lesbass) +- **Simpler install prompts**. `tracedecay install` now asks a Y/n question per detected agent instead of showing a multi-select dialog box. Prints a +/- summary of changes at the end. Removed `dialoguer` dependency. +- **No-op upgrade is no longer an error**. `tracedecay upgrade` when already on the latest version now exits successfully instead of printing a misleading error. Same for `tracedecay channel` when already on the requested channel. (PR #30 by @lesbass) ### Fixed -- **Default branch detection wrote `"HEAD"` instead of actual branch name** — `detect_default_branch()` used `reference.name()` on the `refs/remotes/origin/HEAD` symbolic ref, which returns the ref's own name. Now resolves through `reference.follow()` to get the target (e.g. `refs/remotes/origin/master`), then strips the prefix correctly. (PR #26 by @LucioPg) -- **Branch detection in git worktrees** — `current_branch()` read `.git/HEAD` directly as a plain file, which fails in git worktrees where `.git` is a pointer file (not a directory). Fixed with a two-tier approach: `gix::open()` first, then `git symbolic-ref -q HEAD` subprocess fallback. (PR #28 by @LucioPg) -- **Windows monitor nested runtime panic** — `tracedecay monitor` cost cache refresh panicked on Windows due to nested tokio runtimes. Now uses `block_in_place` + `Handle::current()` on Windows. (PR #29 by @LucioPg) -- **Clippy clean** — resolved all clippy errors across the codebase; CI clippy step now passes. +- **Default branch detection wrote `"HEAD"` instead of actual branch name**. `detect_default_branch()` used `reference.name()` on the `refs/remotes/origin/HEAD` symbolic ref, which returns the ref's own name. Now resolves through `reference.follow()` to get the target (e.g. `refs/remotes/origin/master`), then strips the prefix correctly. (PR #26 by @LucioPg) +- **Branch detection in git worktrees**. `current_branch()` read `.git/HEAD` directly as a plain file, which fails in git worktrees where `.git` is a pointer file (not a directory). Fixed with a two-tier approach: `gix::open()` first, then `git symbolic-ref -q HEAD` subprocess fallback. (PR #28 by @LucioPg) +- **Windows monitor nested runtime panic**. `tracedecay monitor` cost cache refresh panicked on Windows due to nested tokio runtimes. Now uses `block_in_place` + `Handle::current()` on Windows. (PR #29 by @LucioPg) +- **Clippy clean**, resolved all clippy errors across the codebase; CI clippy step now passes. ## [4.0.3] - 2026-04-16 ### Fixed -- **Windows daemon nested runtime panic** — `tracedecay daemon` panicked on Windows because `daemon-kit` runs the closure inline (no fork), creating a nested tokio runtime. Now uses `block_in_place` + `Handle::current()` on Windows while keeping `Runtime::new()` on Unix where the forked child genuinely has no runtime. +- **Windows daemon nested runtime panic**. `tracedecay daemon` panicked on Windows because `daemon-kit` runs the closure inline (no fork), creating a nested tokio runtime. Now uses `block_in_place` + `Handle::current()` on Windows while keeping `Runtime::new()` on Unix where the forked child genuinely has no runtime. ## [4.0.2] - 2026-04-14 ### Added -- **Token cost observability** — new `tracedecay cost` command parses Claude Code session transcripts (`~/.claude/projects/**/*.jsonl`), classifies each API turn into 13 task categories (coding, debugging, exploration, ...), and computes dollar cost per model. Supports `--by-model`, `--by-task`, `--export json|csv`, and time ranges (`today`, `7d`, `30d`, `all`). Model pricing is refreshed from LiteLLM every 24 hours and cached at `~/.tracedecay/pricing.json`. Cost data is stored in the existing `~/.tracedecay/global.db`. The `tracedecay status` header now shows today's cost, 7-day cost, and efficiency ratio. The `tracedecay monitor` TUI includes a cost panel. The `hook_stop` handler prints a session cost receipt. Task classification adapted from [AgentSeal/codeburn](https://github.com/AgentSeal/codeburn). -- **`tracedecay status --details`** — the node-kind breakdown table is now opt-in via the `--details` flag. Default status output is more compact. -- **Per-file diversity caps** — `tracedecay_context` now limits how many symbols from a single file appear in results (default: `max_nodes/3`, minimum 3), preventing one large file from dominating context output. Configurable via the new `max_per_file` parameter. -- **Exact name match supplementing** — context search now supplements FTS5 results with exact case-insensitive name lookups, so perfect symbol name matches are never buried by BM25 noise. -- **Stem variant search expansion** — search terms are expanded with suffix-based stem variants (e.g. "authenticate" also finds "authentication", "authenticator") via 13 derivational suffix rules, improving recall for conceptual queries. -- **Co-occurrence boosting** — when a query has multiple terms, symbols where 2+ terms co-locate in name, qualified name, or file path get a multiplicative score boost, improving precision on multi-word searches. -- **Edge recovery after node trimming** — when BFS subgraph expansion trims nodes to fit `max_nodes`, edges are now filtered to retain only those connecting surviving nodes, keeping the returned subgraph consistent. -- **Adaptive SQLite pragmas** — `cache_size` and `mmap_size` now scale to the DB file size instead of using fixed 64 MB / 256 MB values. Small projects (5 MB DB) drop from ~320 MB baseline to ~12 MB; large projects keep the same performance. -- **`tracedecay reinstall` command** — re-runs install for all already-configured agents, refreshing MCP server registration, hooks, permissions, and prompt rules without the interactive picker. +- **Token cost observability**, new `tracedecay cost` command parses Claude Code session transcripts (`~/.claude/projects/**/*.jsonl`), classifies each API turn into 13 task categories (coding, debugging, exploration, ...), and computes dollar cost per model. Supports `--by-model`, `--by-task`, `--export json|csv`, and time ranges (`today`, `7d`, `30d`, `all`). Model pricing is refreshed from LiteLLM every 24 hours and cached at `~/.tracedecay/pricing.json`. Cost data is stored in the existing `~/.tracedecay/global.db`. The `tracedecay status` header now shows today's cost, 7-day cost, and efficiency ratio. The `tracedecay monitor` TUI includes a cost panel. The `hook_stop` handler prints a session cost receipt. Task classification adapted from [AgentSeal/codeburn](https://github.com/AgentSeal/codeburn). +- **`tracedecay status --details`**, the node-kind breakdown table is now opt-in via the `--details` flag. Default status output is more compact. +- **Per-file diversity caps**. `tracedecay_context` now limits how many symbols from a single file appear in results (default: `max_nodes/3`, minimum 3), preventing one large file from dominating context output. Configurable via the new `max_per_file` parameter. +- **Exact name match supplementing**, context search now supplements FTS5 results with exact case-insensitive name lookups, so perfect symbol name matches are never buried by BM25 noise. +- **Stem variant search expansion**, search terms are expanded with suffix-based stem variants (e.g. "authenticate" also finds "authentication", "authenticator") via 13 derivational suffix rules, improving recall for conceptual queries. +- **Co-occurrence boosting**, when a query has multiple terms, symbols where 2+ terms co-locate in name, qualified name, or file path get a multiplicative score boost, improving precision on multi-word searches. +- **Edge recovery after node trimming**, when BFS subgraph expansion trims nodes to fit `max_nodes`, edges are now filtered to retain only those connecting surviving nodes, keeping the returned subgraph consistent. +- **Adaptive SQLite pragmas**. `cache_size` and `mmap_size` now scale to the DB file size instead of using fixed 64 MB / 256 MB values. Small projects (5 MB DB) drop from ~320 MB baseline to ~12 MB; large projects keep the same performance. +- **`tracedecay reinstall` command**, re-runs install for all already-configured agents, refreshing MCP server registration, hooks, permissions, and prompt rules without the interactive picker. ### Removed -- **Graph visualizer** — `tracedecay visualize` command, `src/visualizer.rs`, and the embedded HTML file have been removed. The upstream CodeGraph project also removed its visualizer in the same period. +- **Graph visualizer**. `tracedecay visualize` command, `src/visualizer.rs`, and the embedded HTML file have been removed. The upstream CodeGraph project also removed its visualizer in the same period. ### Fixed -- **Windows path separators in hooks and MCP config** — `which_tracedecay()` now normalizes backslash paths to forward slashes, fixing broken hook command execution on Windows (e.g. Scoop installs). Existing settings with backslash paths are also normalized when read back. +- **Windows path separators in hooks and MCP config**. `which_tracedecay()` now normalizes backslash paths to forward slashes, fixing broken hook command execution on Windows (e.g. Scoop installs). Existing settings with backslash paths are also normalized when read back. ## [4.0.0] - 2026-04-13 ### Added -- **Multi-branch indexing** — opt-in per-branch databases so switching branches never gives stale results. `tracedecay branch add` tracks a branch by copying the nearest ancestor DB and syncing only changed files. `tracedecay branch list`, `tracedecay branch remove`, `tracedecay branch removeall`, and `tracedecay branch gc` manage tracked branches. -- **`tracedecay branch removeall`** — remove all tracked branches except the default in one command, deleting their DB files. -- **`tracedecay_branch_search`** MCP tool — search symbols in another branch's code graph without switching your checkout. -- **`tracedecay_branch_diff`** MCP tool — compare code graphs between two branches: shows symbols added, removed, and changed (signature differs). Supports file and kind filters. -- **`tracedecay_branch_list`** MCP tool and **`tracedecay://branches`** MCP resource — list tracked branches with DB sizes, parent branch, sync times. -- **Branch fallback warnings** — when the MCP server serves from an ancestor branch DB (current branch not tracked), every tool response warns to `tracedecay branch add`. -- **`keywords` parameter for `tracedecay_context`** — agent-driven synonym expansion. Pass extra search terms (e.g. `["login", "session", "token"]` for "authentication") and the context builder searches each keyword independently, bridging conceptual queries to lexically-unrelated symbol names without embedding models. -- **`tracedecay monitor` CLI command** — global live TUI showing MCP tool calls from all projects in real time via a shared memory-mapped ring buffer at `~/.tracedecay/monitor.mmap`. Entries show `prefix - project - tool_name` so multiple tool suites and projects are distinguishable. Uses `memmap2` with file locking for concurrent writer safety. -- **`path` filter on 7 analytics MCP tools** — `tracedecay_god_class`, `tracedecay_largest`, `tracedecay_complexity`, `tracedecay_rank`, `tracedecay_coupling`, `tracedecay_inheritance_depth`, and `tracedecay_recursion` now accept an optional `path` parameter to scope results to a directory (e.g. `"path": "src/main/java"`), preventing large languages from dominating global rankings. -- **Right-click context menu in graph visualizer** — callers, callees, call graph, and impact actions on node right-click. -- **Type annotation references** — TypeScript, Java, and Kotlin type annotation references now tracked as edges in the graph. -- **Graph visualizer** — interactive Cytoscape.js-based code graph visualization served via `tracedecay visualize`. -- **Daemon version mismatch detection** — `tracedecay daemon --status` warns when the daemon version differs from the CLI with a corrective restart command. -- **Parent branch in status output** — `tracedecay status` and `tracedecay_status` now show which branch a tracked branch was seeded from. +- **Multi-branch indexing**, opt-in per-branch databases so switching branches never gives stale results. `tracedecay branch add` tracks a branch by copying the nearest ancestor DB and syncing only changed files. `tracedecay branch list`, `tracedecay branch remove`, `tracedecay branch removeall`, and `tracedecay branch gc` manage tracked branches. +- **`tracedecay branch removeall`**, remove all tracked branches except the default in one command, deleting their DB files. +- **`tracedecay_branch_search`** MCP tool, search symbols in another branch's code graph without switching your checkout. +- **`tracedecay_branch_diff`** MCP tool, compare code graphs between two branches: shows symbols added, removed, and changed (signature differs). Supports file and kind filters. +- **`tracedecay_branch_list`** MCP tool and **`tracedecay://branches`** MCP resource, list tracked branches with DB sizes, parent branch, sync times. +- **Branch fallback warnings**, when the MCP server serves from an ancestor branch DB (current branch not tracked), every tool response warns to `tracedecay branch add`. +- **`keywords` parameter for `tracedecay_context`**, agent-driven synonym expansion. Pass extra search terms (e.g. `["login", "session", "token"]` for "authentication") and the context builder searches each keyword independently, bridging conceptual queries to lexically-unrelated symbol names without embedding models. +- **`tracedecay monitor` CLI command**, global live TUI showing MCP tool calls from all projects in real time via a shared memory-mapped ring buffer at `~/.tracedecay/monitor.mmap`. Entries show `prefix - project - tool_name` so multiple tool suites and projects are distinguishable. Uses `memmap2` with file locking for concurrent writer safety. +- **`path` filter on 7 analytics MCP tools**. `tracedecay_god_class`, `tracedecay_largest`, `tracedecay_complexity`, `tracedecay_rank`, `tracedecay_coupling`, `tracedecay_inheritance_depth`, and `tracedecay_recursion` now accept an optional `path` parameter to scope results to a directory (e.g. `"path": "src/main/java"`), preventing large languages from dominating global rankings. +- **Right-click context menu in graph visualizer**, callers, callees, call graph, and impact actions on node right-click. +- **Type annotation references**. TypeScript, Java, and Kotlin type annotation references now tracked as edges in the graph. +- **Graph visualizer**, interactive Cytoscape.js-based code graph visualization served via `tracedecay visualize`. +- **Daemon version mismatch detection**. `tracedecay daemon --status` warns when the daemon version differs from the CLI with a corrective restart command. +- **Parent branch in status output**. `tracedecay status` and `tracedecay_status` now show which branch a tracked branch was seeded from. ### Removed -- **Vector/embedding module** — removed `src/vectors/`, `enable_embeddings` config field, and `Vector` error variant. The `keywords` parameter on `tracedecay_context` replaces the need for local embedding models. The `vectors` DB table is retained (empty, harmless) to avoid migration issues. +- **Vector/embedding module**, removed `src/vectors/`, `enable_embeddings` config field, and `Vector` error variant. The `keywords` parameter on `tracedecay_context` replaces the need for local embedding models. The `vectors` DB table is retained (empty, harmless) to avoid migration issues. ### Changed -- **Monitor is now global** — moved from per-project (`/.tracedecay/monitor.mmap`) to machine-level (`~/.tracedecay/monitor.mmap`). `tracedecay monitor` no longer takes a `--path` flag. +- **Monitor is now global**, moved from per-project (`/.tracedecay/monitor.mmap`) to machine-level (`~/.tracedecay/monitor.mmap`). `tracedecay monitor` no longer takes a `--path` flag. - Quality improvements to resolution, search, and traversal. - Tool count increased from 34 to 37. @@ -3294,29 +3294,29 @@ The largest functional jump since 4.0: nine new MCP tools, a cross-session respo ## [3.5.1] - 2026-04-13 ### Fixed -- **Doctor validates hook subcommands** — `tracedecay doctor` now checks that each hook event uses the correct tracedecay subcommand (e.g. `hook-prompt-submit` for `UserPromptSubmit`, not an invalid or mismatched command). -- **Doctor auto-repairs broken hooks** — when a hook has a wrong subcommand or is missing entirely, `tracedecay doctor` replaces it with the correct command automatically. +- **Doctor validates hook subcommands**. `tracedecay doctor` now checks that each hook event uses the correct tracedecay subcommand (e.g. `hook-prompt-submit` for `UserPromptSubmit`, not an invalid or mismatched command). +- **Doctor auto-repairs broken hooks**, when a hook has a wrong subcommand or is missing entirely, `tracedecay doctor` replaces it with the correct command automatically. ### Added -- **18 unit tests for Claude hook lifecycle** — install, uninstall, doctor detection, and doctor auto-repair for all three hook events. +- **18 unit tests for Claude hook lifecycle**, install, uninstall, doctor detection, and doctor auto-repair for all three hook events. ## [3.5.0] - 2026-04-13 ### Added -- **Per-call token savings reported inline** — every MCP tool response now appends a `tracedecay_metrics: before=N after=M` line showing how many raw-file tokens were avoided. -- **`UserPromptSubmit` and `Stop` hooks** — `tracedecay install` now registers three hooks (PreToolUse, UserPromptSubmit, Stop) instead of just PreToolUse. Existing installs are silently backfilled on startup. -- **`tracedecay current-counter` / `reset-counter` commands** — expose and reset a per-project local token counter, separate from the lifetime total. +- **Per-call token savings reported inline**, every MCP tool response now appends a `tracedecay_metrics: before=N after=M` line showing how many raw-file tokens were avoided. +- **`UserPromptSubmit` and `Stop` hooks**. `tracedecay install` now registers three hooks (PreToolUse, UserPromptSubmit, Stop) instead of just PreToolUse. Existing installs are silently backfilled on startup. +- **`tracedecay current-counter` / `reset-counter` commands**, expose and reset a per-project local token counter, separate from the lifetime total. - **Respect global gitignore** for `.tracedecay` warning. ### Changed -- **Hook install/uninstall generalized** — `install_hook` and `uninstall_hook` now iterate over all three hook events. -- **Sync uses mtime/size pre-filter** — skips hashing unchanged files, only reads files whose mtime or size changed since last sync. -- **Dependency upgrades** — dialoguer 0.11→0.12, notify 7→8, sha2 0.10→0.11, zip 6→8, windows-sys 0.59→0.61. +- **Hook install/uninstall generalized**. `install_hook` and `uninstall_hook` now iterate over all three hook events. +- **Sync uses mtime/size pre-filter**, skips hashing unchanged files, only reads files whose mtime or size changed since last sync. +- **Dependency upgrades**, dialoguer 0.11→0.12, notify 7→8, sha2 0.10→0.11, zip 6→8, windows-sys 0.59→0.61. ## [3.4.6] - 2026-04-07 ### Fixed -- **SQLite FTS corruption from interrupted sync** — handle UTF-16 encoded files, report unreadable files during sync. +- **SQLite FTS corruption from interrupted sync**, handle UTF-16 encoded files, report unreadable files during sync. ## [3.4.5] - 2026-04-07 @@ -3339,7 +3339,7 @@ The largest functional jump since 4.0: nine new MCP tools, a cross-session respo ## [3.4.2] - 2026-04-07 ### Added -- **`tracedecay channel` command** — show or switch the update channel (stable/beta). +- **`tracedecay channel` command**, show or switch the update channel (stable/beta). ### Fixed - Cross-workflow Homebrew/Scoop failures on wrong release type. @@ -3348,16 +3348,16 @@ The largest functional jump since 4.0: nine new MCP tools, a cross-session respo ## [3.4.1] - 2026-04-07 ### Fixed -- Beta Homebrew bottle 404 — fix bottle archive naming. +- Beta Homebrew bottle 404, fix bottle archive naming. - Update notices now suggest `tracedecay upgrade` instead of platform-specific commands. ## [3.4.0] - 2026-04-07 ### Added -- **`tracedecay upgrade` command** — self-update the binary directly from GitHub releases. Detects the current channel, downloads the correct platform-specific archive, and replaces the running binary. -- **Annotation/attribute extraction for 7 languages** — Rust, Swift, Dart, Scala, PHP, C++, and VB.NET. All create `AnnotationUsage` nodes with `Annotates` edges. Brings annotation support to 12 of 31 languages. -- **McpTransport trait** — zero-cost abstraction for MCP server I/O, enabling in-memory test transports. -- **370+ new tests** — line coverage 71% → 84%. +- **`tracedecay upgrade` command**, self-update the binary directly from GitHub releases. Detects the current channel, downloads the correct platform-specific archive, and replaces the running binary. +- **Annotation/attribute extraction for 7 languages**. Rust, Swift, Dart, Scala, PHP, C++, and VB.NET. All create `AnnotationUsage` nodes with `Annotates` edges. Brings annotation support to 12 of 31 languages. +- **McpTransport trait**, zero-cost abstraction for MCP server I/O, enabling in-memory test transports. +- **370+ new tests**, line coverage 71% → 84%. ## [3.3.3] - 2026-04-05 @@ -3367,20 +3367,20 @@ The largest functional jump since 4.0: nine new MCP tools, a cross-session respo ## [3.3.2] - 2026-04-05 ### Fixed -- **Windows build failure blocking Homebrew/Scoop updates** — `SHELLEXECUTEINFOW` in `windows-sys` 0.59 requires the `Win32_System_Registry` feature flag, which was missing. This caused Windows CI builds to fail since v3.2.0, and because the release workflow used `fail-fast: true`, the failure cascaded to skip the Homebrew tap and Scoop bucket update jobs entirely. Users on Homebrew were stuck on v3.1.0. ([#12](https://github.com/ScriptedAlchemy/tracedecay/issues/12)) -- **`HANDLE` type mismatch on Windows** — `windows-sys` 0.59 changed `HANDLE` from `usize` to `*mut c_void`. The UAC elevation code now uses `std::ptr::null_mut()` and `.is_null()` instead of literal `0`. -- **Release workflow resilience** — changed build matrix to `fail-fast: false` and downstream jobs (`update-homebrew`, `update-scoop`) to `if: !cancelled()`, so a single platform build failure no longer blocks formula/manifest updates for platforms that succeeded. +- **Windows build failure blocking Homebrew/Scoop updates**. `SHELLEXECUTEINFOW` in `windows-sys` 0.59 requires the `Win32_System_Registry` feature flag, which was missing. This caused Windows CI builds to fail since v3.2.0, and because the release workflow used `fail-fast: true`, the failure cascaded to skip the Homebrew tap and Scoop bucket update jobs entirely. Users on Homebrew were stuck on v3.1.0. ([#12](https://github.com/ScriptedAlchemy/tracedecay/issues/12)) +- **`HANDLE` type mismatch on Windows**. `windows-sys` 0.59 changed `HANDLE` from `usize` to `*mut c_void`. The UAC elevation code now uses `std::ptr::null_mut()` and `.is_null()` instead of literal `0`. +- **Release workflow resilience**, changed build matrix to `fail-fast: false` and downstream jobs (`update-homebrew`, `update-scoop`) to `if: !cancelled()`, so a single platform build failure no longer blocks formula/manifest updates for platforms that succeeded. ## [3.3.1] - 2026-04-05 ### Fixed -- **Windows `is_installed()` always returned `false`** — the daemon autostart check via `daemon-kit` used a file-path probe that returns `None` on Windows, so `is_service_installed()` never detected an existing service. This caused `tracedecay install` to re-offer autostart every time. Now dispatches to the Windows SCM query that was already implemented but never wired up. (daemon-kit 0.1.4) -- **Windows `--enable-autostart` failed on reinstall** — running `tracedecay daemon --enable-autostart` twice would error with "service already exists". The installer now stops and removes the old service before re-creating, making the operation idempotent. (daemon-kit 0.1.4) +- **Windows `is_installed()` always returned `false`**, the daemon autostart check via `daemon-kit` used a file-path probe that returns `None` on Windows, so `is_service_installed()` never detected an existing service. This caused `tracedecay install` to re-offer autostart every time. Now dispatches to the Windows SCM query that was already implemented but never wired up. (daemon-kit 0.1.4) +- **Windows `--enable-autostart` failed on reinstall**, running `tracedecay daemon --enable-autostart` twice would error with "service already exists". The installer now stops and removes the old service before re-creating, making the operation idempotent. (daemon-kit 0.1.4) ### Added -- **Upgrade-aware daemon restart** — the background daemon now snapshots its own binary's mtime and size at startup and checks every 60 seconds. When an upgrade is detected (via `brew upgrade`, `cargo install`, `scoop update`, or any package manager), the daemon flushes pending syncs, logs the event, and exits. The service manager (launchd `KeepAlive`, systemd `Restart=on-failure`, Windows SCM failure actions) automatically relaunches with the new binary. Previously the old version ran until the next reboot or manual restart. -- **Windows SCM failure recovery** — the Windows service is now configured with `ServiceFailureActions` (restart after 5s, then 10s) so the SCM relaunches the daemon after upgrade-triggered exits. -- **Daemon version logging** — the daemon startup log now includes the version (`v3.3.1 started, watching N projects`) so log readers can confirm which version is running after an upgrade restart. +- **Upgrade-aware daemon restart**, the background daemon now snapshots its own binary's mtime and size at startup and checks every 60 seconds. When an upgrade is detected (via `brew upgrade`, `cargo install`, `scoop update`, or any package manager), the daemon flushes pending syncs, logs the event, and exits. The service manager (launchd `KeepAlive`, systemd `Restart=on-failure`, Windows SCM failure actions) automatically relaunches with the new binary. Previously the old version ran until the next reboot or manual restart. +- **Windows SCM failure recovery**, the Windows service is now configured with `ServiceFailureActions` (restart after 5s, then 10s) so the SCM relaunches the daemon after upgrade-triggered exits. +- **Daemon version logging**, the daemon startup log now includes the version (`v3.3.1 started, watching N projects`) so log readers can confirm which version is running after an upgrade restart. ### Changed - Bumped `daemon-kit` dependency from 0.1.3 to 0.1.4. @@ -3388,22 +3388,22 @@ The largest functional jump since 4.0: nine new MCP tools, a cross-session respo ## [3.3.0] - 2026-04-05 ### Changed -- **Sync progress now matches full-index display** — `tracedecay sync` now shows `[current/total] syncing file (ETA: Ns)` with the braille spinner and path truncation, matching the progress display used during initial indexing. Previously sync only showed phase names without file counters or ETA. - -### Added -- **MCP tool annotations** — all 34 tools now include `readOnlyHint: true` and a human-friendly `title` in their MCP annotations. Clients that support annotations can run all tracedecay tools concurrently without permission prompts and display cleaner tool names. -- **`_meta["anthropic/alwaysLoad"]`** on core tools — `tracedecay_context`, `tracedecay_search`, and `tracedecay_status` are marked for immediate loading, bypassing the client's tool-search round-trip on first use. -- **Server instructions** — the MCP `initialize` response now includes an `instructions` field guiding the model to start with `tracedecay_context` and noting all tools are read-only and safe to call in parallel. -- **MCP resources** — three resources exposed via `resources/list` and `resources/read`: - - `tracedecay://status` — graph statistics as JSON - - `tracedecay://files` — indexed file tree grouped by directory - - `tracedecay://overview` — project summary with language distribution and symbol kinds -- **`tracedecay_commit_context`** — semantic summary of uncommitted changes for commit message drafting. Returns changed symbols grouped by file role (source/test/config/docs), a suggested commit category, and recent commit subjects for style matching. -- **`tracedecay_pr_context`** — semantic diff between two git refs for pull request descriptions. Returns commit log, symbols added/modified, affected tests, and impacted modules. -- **`tracedecay_simplify_scan`** — quality analysis of changed files: detects symbol duplications, dead code introductions, complexity hotspots, and high-coupling files. -- **`tracedecay_test_map`** — source-to-test mapping at the symbol level. Shows which test functions call which source functions and identifies uncovered symbols. -- **`tracedecay_type_hierarchy`** — recursive type hierarchy tree for traits, interfaces, and classes showing all implementors and extenders with file locations. -- **`tracedecay_context` extended** — new `include_code` parameter includes source code snippets for key symbols (wires through to the existing context builder). New `mode: "plan"` parameter appends extension points (public traits/interfaces with implementor counts) and test coverage for related modules. +- **Sync progress now matches full-index display**. `tracedecay sync` now shows `[current/total] syncing file (ETA: Ns)` with the braille spinner and path truncation, matching the progress display used during initial indexing. Previously sync only showed phase names without file counters or ETA. + +### Added +- **MCP tool annotations**, all 34 tools now include `readOnlyHint: true` and a human-friendly `title` in their MCP annotations. Clients that support annotations can run all tracedecay tools concurrently without permission prompts and display cleaner tool names. +- **`_meta["anthropic/alwaysLoad"]`** on core tools. `tracedecay_context`, `tracedecay_search`, and `tracedecay_status` are marked for immediate loading, bypassing the client's tool-search round-trip on first use. +- **Server instructions**, the MCP `initialize` response now includes an `instructions` field guiding the model to start with `tracedecay_context` and noting all tools are read-only and safe to call in parallel. +- **MCP resources**, three resources exposed via `resources/list` and `resources/read`: + - `tracedecay://status`, graph statistics as JSON + - `tracedecay://files`, indexed file tree grouped by directory + - `tracedecay://overview`, project summary with language distribution and symbol kinds +- **`tracedecay_commit_context`**, semantic summary of uncommitted changes for commit message drafting. Returns changed symbols grouped by file role (source/test/config/docs), a suggested commit category, and recent commit subjects for style matching. +- **`tracedecay_pr_context`**, semantic diff between two git refs for pull request descriptions. Returns commit log, symbols added/modified, affected tests, and impacted modules. +- **`tracedecay_simplify_scan`**, quality analysis of changed files: detects symbol duplications, dead code introductions, complexity hotspots, and high-coupling files. +- **`tracedecay_test_map`**, source-to-test mapping at the symbol level. Shows which test functions call which source functions and identifies uncovered symbols. +- **`tracedecay_type_hierarchy`**, recursive type hierarchy tree for traits, interfaces, and classes showing all implementors and extenders with file locations. +- **`tracedecay_context` extended**, new `include_code` parameter includes source code snippets for key symbols (wires through to the existing context builder). New `mode: "plan"` parameter appends extension points (public traits/interfaces with implementor counts) and test coverage for related modules. ### Changed - Tool count increased from 29 to 34. @@ -3412,64 +3412,64 @@ The largest functional jump since 4.0: nine new MCP tools, a cross-session respo ## [3.2.2] - 2026-04-05 ### Fixed -- **MCP tools no longer warn on patch-only updates** — the `tracedecay_status` MCP tool now uses `is_newer_minor_version` instead of `is_newer_version`, so patch-level releases (e.g. 3.2.0 → 3.2.1) no longer trigger update warnings in MCP tool output. The CLI status command continues to show all available updates. -- **Separate beta/stable update channels** — `is_newer_version` now returns `false` for cross-channel comparisons (beta vs stable). Previously a beta user could be told to upgrade to a stable release, or vice versa. Each channel now only sees updates from its own channel. +- **MCP tools no longer warn on patch-only updates**, the `tracedecay_status` MCP tool now uses `is_newer_minor_version` instead of `is_newer_version`, so patch-level releases (e.g. 3.2.0 → 3.2.1) no longer trigger update warnings in MCP tool output. The CLI status command continues to show all available updates. +- **Separate beta/stable update channels**. `is_newer_version` now returns `false` for cross-channel comparisons (beta vs stable). Previously a beta user could be told to upgrade to a stable release, or vice versa. Each channel now only sees updates from its own channel. ## [3.1.1] - 2026-04-02 ### Fixed -- **Windows daemon service installation** — `tracedecay install` and `tracedecay daemon --enable-autostart` no longer fail on non-elevated Windows terminals. When administrator privileges are required to register the Windows Service, the process now automatically requests UAC elevation for just the service installation step; everything else continues non-elevated. ([#7](https://github.com/ScriptedAlchemy/tracedecay/issues/7)) -- **Quieter version update warnings** — the CLI no longer warns about patch-only releases (e.g. 3.2.0 → 3.2.1); warnings now appear only for minor or major version bumps. The status page (`tracedecay_status` MCP tool) continues to show all available updates. +- **Windows daemon service installation**. `tracedecay install` and `tracedecay daemon --enable-autostart` no longer fail on non-elevated Windows terminals. When administrator privileges are required to register the Windows Service, the process now automatically requests UAC elevation for just the service installation step; everything else continues non-elevated. ([#7](https://github.com/ScriptedAlchemy/tracedecay/issues/7)) +- **Quieter version update warnings**, the CLI no longer warns about patch-only releases (e.g. 3.2.0 → 3.2.1); warnings now appear only for minor or major version bumps. The status page (`tracedecay_status` MCP tool) continues to show all available updates. ## [3.1.0] - 2026-04-01 ### Fixed -- **Edge duplication during incremental sync** — reference resolution was re-resolving ALL unresolved refs on every sync (not just from changed files) and inserting duplicate edges with no deduplication. Over many syncs this caused unbounded DB growth (e.g. 5.1 GB for a 108 MB codebase). A unique index on edges and `INSERT OR IGNORE` now prevent duplicates entirely. A V5 migration automatically deduplicates existing databases on upgrade. ([#5](https://github.com/ScriptedAlchemy/tracedecay/issues/5)) +- **Edge duplication during incremental sync**, reference resolution was re-resolving ALL unresolved refs on every sync (not just from changed files) and inserting duplicate edges with no deduplication. Over many syncs this caused unbounded DB growth (e.g. 5.1 GB for a 108 MB codebase). A unique index on edges and `INSERT OR IGNORE` now prevent duplicates entirely. A V5 migration automatically deduplicates existing databases on upgrade. ([#5](https://github.com/ScriptedAlchemy/tracedecay/issues/5)) ### Added -- **Concurrent sync prevention** — a PID-based lockfile (`.tracedecay/sync.lock`) prevents the CLI and the background daemon from running sync simultaneously. If a sync is already in progress, the second attempt fails immediately with a clear error message. Stale locks from crashed processes are reclaimed automatically. -- **`doctor` database compaction** — `tracedecay doctor` now opens the project database, reports its size, and runs `VACUUM + ANALYZE` to reclaim space. Particularly useful after upgrading from versions affected by edge duplication. -- **Index design documentation** — new `docs/INDEX-DESIGN.md` describes the full indexing pipeline, database schema, extraction process, reference resolution, incremental sync, and how `diff_context` uses the graph. +- **Concurrent sync prevention**, a PID-based lockfile (`.tracedecay/sync.lock`) prevents the CLI and the background daemon from running sync simultaneously. If a sync is already in progress, the second attempt fails immediately with a clear error message. Stale locks from crashed processes are reclaimed automatically. +- **`doctor` database compaction**. `tracedecay doctor` now opens the project database, reports its size, and runs `VACUUM + ANALYZE` to reclaim space. Particularly useful after upgrading from versions affected by edge duplication. +- **Index design documentation**, new `docs/INDEX-DESIGN.md` describes the full indexing pipeline, database schema, extraction process, reference resolution, incremental sync, and how `diff_context` uses the graph. ## [3.0.1] - 2026-04-01 ### Fixed -- **Safe JSON config editing** — `tracedecay install` no longer silently destroys agent config files (e.g. `opencode.json`, `settings.json`) when they contain invalid or unparseable JSON. Previously, a parse failure caused the file to be silently replaced with an empty object plus the tracedecay entry, wiping all existing configuration. +- **Safe JSON config editing**. `tracedecay install` no longer silently destroys agent config files (e.g. `opencode.json`, `settings.json`) when they contain invalid or unparseable JSON. Previously, a parse failure caused the file to be silently replaced with an empty object plus the tracedecay entry, wiping all existing configuration. ### Added -- **Atomic backup before config writes** — a `.bak` copy of the original file is created (via atomic staging) before any modification. If the install fails at any point, the original file is untouched and the backup is preserved. -- **Strict JSON/JSONC loading for edits** — new `load_json_file_strict` and `load_jsonc_file_strict` functions return an error (with a helpful hint) when an existing file cannot be parsed, instead of silently returning `{}`. -- **Atomic config writes** — new content is written to a `.new` sibling file first, then atomically renamed into place via `rename(2)`. The original file is never opened for writing, so a crash or interruption cannot leave it half-written. +- **Atomic backup before config writes**, a `.bak` copy of the original file is created (via atomic staging) before any modification. If the install fails at any point, the original file is untouched and the backup is preserved. +- **Strict JSON/JSONC loading for edits**, new `load_json_file_strict` and `load_jsonc_file_strict` functions return an error (with a helpful hint) when an existing file cannot be parsed, instead of silently returning `{}`. +- **Atomic config writes**, new content is written to a `.new` sibling file first, then atomically renamed into place via `rename(2)`. The original file is never opened for writing, so a crash or interruption cannot leave it half-written. - **20 regression tests** covering backup creation, strict loading, atomic writes, round-trip validation, and the end-to-end install cycle for both valid and corrupt config files. ## [3.0.0] - 2026-03-28 ### Changed -- **Bundled tree-sitter grammars** — all 31 language grammars now come from the `tracedecay-large-treesitters` crate (which includes `tracedecay-medium-treesitters` and `tracedecay-lite-treesitters`). Zero individual `tree-sitter-*` crate dependencies remain in tracedecay itself. The grammar provider (`ts_provider`) is a single `LazyLock` lookup, replacing 100+ lines of per-crate match arms. -- **Removed vendored C grammars** — the Protobuf and COBOL grammars previously compiled from C source via `build.rs` are now vendored inside the bundled crate. tracedecay no longer needs `cc` as a build dependency. -- **Simplified feature flags** — the `lang-*` feature flags still control which extractors are compiled, but no longer pull in individual grammar crate dependencies (all grammars are always present via the bundle). The `ts-ffi`/`ts-rust`/`ts-both` grammar source selection flags have been removed. +- **Bundled tree-sitter grammars**, all 31 language grammars now come from the `tracedecay-large-treesitters` crate (which includes `tracedecay-medium-treesitters` and `tracedecay-lite-treesitters`). Zero individual `tree-sitter-*` crate dependencies remain in tracedecay itself. The grammar provider (`ts_provider`) is a single `LazyLock` lookup, replacing 100+ lines of per-crate match arms. +- **Removed vendored C grammars**, the Protobuf and COBOL grammars previously compiled from C source via `build.rs` are now vendored inside the bundled crate. tracedecay no longer needs `cc` as a build dependency. +- **Simplified feature flags**, the `lang-*` feature flags still control which extractors are compiled, but no longer pull in individual grammar crate dependencies (all grammars are always present via the bundle). The `ts-ffi`/`ts-rust`/`ts-both` grammar source selection flags have been removed. ### Added -- **Daemon install prompt** — `tracedecay install` now offers to install the background daemon as an autostart service (launchd on macOS, systemd on Linux) after agent configuration. Skips silently in non-interactive mode or when the service is already installed. -- **Last sync / Full sync in status** — the status table header now shows a third row with relative timestamps for the most recent incremental sync and the most recent full reindex, stored in the metadata table. +- **Daemon install prompt**. `tracedecay install` now offers to install the background daemon as an autostart service (launchd on macOS, systemd on Linux) after agent configuration. Skips silently in non-interactive mode or when the service is already installed. +- **Last sync / Full sync in status**, the status table header now shows a third row with relative timestamps for the most recent incremental sync and the most recent full reindex, stored in the metadata table. ## [2.4.0] - 2026-03-27 ### Added -- **Daemon mode** — `tracedecay daemon` watches all tracked projects for file changes and runs incremental syncs automatically; debounce configurable via `daemon_debounce` in `~/.tracedecay/config.toml` (default `"15s"`) -- **Daemon management** — `--stop`, `--status`, `--foreground` flags for process control; PID file at `~/.tracedecay/daemon.pid` -- **Autostart service** — `--enable-autostart` / `--disable-autostart` generates and manages a launchd plist (macOS) or systemd user unit (Linux); cross-platform via `daemon-kit` crate -- **Doctor daemon checks** — `tracedecay doctor` now reports daemon running status and autostart configuration -- **`daemon-kit` crate** — new standalone cross-platform daemon/service toolkit published to crates.io, using `daemonize2` on Unix and `windows-service` on Windows +- **Daemon mode**. `tracedecay daemon` watches all tracked projects for file changes and runs incremental syncs automatically; debounce configurable via `daemon_debounce` in `~/.tracedecay/config.toml` (default `"15s"`) +- **Daemon management**. `--stop`, `--status`, `--foreground` flags for process control; PID file at `~/.tracedecay/daemon.pid` +- **Autostart service**. `--enable-autostart` / `--disable-autostart` generates and manages a launchd plist (macOS) or systemd user unit (Linux); cross-platform via `daemon-kit` crate +- **Doctor daemon checks**. `tracedecay doctor` now reports daemon running status and autostart configuration +- **`daemon-kit` crate**, new standalone cross-platform daemon/service toolkit published to crates.io, using `daemonize2` on Unix and `windows-service` on Windows ## [2.3.2] - 2026-03-27 ### Added -- **5 new agent integrations** — Copilot (VS Code), Cursor, Zed, Cline, and Roo Code now supported via `tracedecay install --agent `; each registers the MCP server in the agent's native config format (VS Code `settings.json`, `~/.cursor/mcp.json`, Zed `settings.json`, Cline/Roo Code `cline_mcp_settings.json`) -- **Auto-detect agents** — running `tracedecay install` without `--agent` detects which agents are installed by checking their config directories; if one is found it installs directly, if multiple are found an interactive checkbox selector is shown -- **Installed-agent tracking** — `installed_agents` list in `~/.tracedecay/config.toml` tracks which integrations are active; on upgrade from older versions the list is backfilled by scanning existing configs -- **Uninstall-all** — `tracedecay uninstall` without `--agent` silently removes all tracked integrations -- **JSONC parser** — VS Code and Zed settings files (JSON with comments and trailing commas) are now parsed correctly +- **5 new agent integrations**. Copilot (VS Code), Cursor, Zed, Cline, and Roo Code now supported via `tracedecay install --agent `; each registers the MCP server in the agent's native config format (VS Code `settings.json`, `~/.cursor/mcp.json`, Zed `settings.json`, Cline/Roo Code `cline_mcp_settings.json`) +- **Auto-detect agents**, running `tracedecay install` without `--agent` detects which agents are installed by checking their config directories; if one is found it installs directly, if multiple are found an interactive checkbox selector is shown +- **Installed-agent tracking**. `installed_agents` list in `~/.tracedecay/config.toml` tracks which integrations are active; on upgrade from older versions the list is backfilled by scanning existing configs +- **Uninstall-all**. `tracedecay uninstall` without `--agent` silently removes all tracked integrations +- **JSONC parser**. VS Code and Zed settings files (JSON with comments and trailing commas) are now parsed correctly ### Changed - **Renamed `Agent` trait to `AgentIntegration`** and all struct names from `XxxAgent` to `XxxIntegration` for consistency; functions renamed accordingly (`get_integration`, `all_integrations`, etc.) @@ -3477,13 +3477,13 @@ The largest functional jump since 4.0: nine new MCP tools, a cross-session respo ## [2.3.1] - 2026-03-27 ### Changed -- **Version-update warning suppressed for 15 minutes** — the "Update available" notice shown after `sync` and in MCP tool responses is now suppressed for 15 minutes after it was last displayed, reducing noise for frequent users; `tracedecay status` always shows the warning regardless of suppression +- **Version-update warning suppressed for 15 minutes**, the "Update available" notice shown after `sync` and in MCP tool responses is now suppressed for 15 minutes after it was last displayed, reducing noise for frequent users; `tracedecay status` always shows the warning regardless of suppression ## [2.3.0] - 2026-03-27 ### Added -- **`--skip-folder` flag for sync** — accepts one or more folder names to exclude during indexing (e.g. `tracedecay sync --skip-folder tests benches`); each folder is converted to a `folder/**` glob pattern at runtime -- **ETA during full index** — the progress spinner now shows `[current/total]` file counts and an estimated time remaining (e.g. `[12/150] indexing src/main.rs (ETA: 8s)`) +- **`--skip-folder` flag for sync**, accepts one or more folder names to exclude during indexing (e.g. `tracedecay sync --skip-folder tests benches`); each folder is converted to a `folder/**` glob pattern at runtime +- **ETA during full index**, the progress spinner now shows `[current/total]` file counts and an estimated time remaining (e.g. `[12/150] indexing src/main.rs (ETA: 8s)`) ### Changed - `index_all_with_progress` callback signature now provides `(current, total, path)` for richer progress reporting @@ -3492,37 +3492,37 @@ The largest functional jump since 4.0: nine new MCP tools, a cross-session respo ## [2.2.0] - 2026-03-27 ### Changed -- **Status table title split into two rows** — top row shows version (left) and country flags (right); bottom row shows token counts right-aligned in green -- **Country flags always shown** — removed `--show-flags` option; flags are now fetched automatically and cached for 30 minutes -- **Fixed table width** — cell width capped at 32 columns (max table width 100), with a derived maximum of 25 display flags -- **Upgraded gix to v0.81.0** — from v0.72.1; added explicit `sha1` feature flag and adapted to new `ControlFlow`-based tree diff API +- **Status table title split into two rows**, top row shows version (left) and country flags (right); bottom row shows token counts right-aligned in green +- **Country flags always shown**, removed `--show-flags` option; flags are now fetched automatically and cached for 30 minutes +- **Fixed table width**, cell width capped at 32 columns (max table width 100), with a derived maximum of 25 display flags +- **Upgraded gix to v0.81.0**, from v0.72.1; added explicit `sha1` feature flag and adapted to new `ControlFlow`-based tree diff API ## [2.1.0] - 2026-03-26 ### Added -- **QuickBASIC 4.5 language support** — new `QuickBasicExtractor` handles `.bi` (include) and `.bm` (module) files, sharing the QBasic grammar under the existing `lang-qbasic` feature flag (31 languages total) -- **`gix` for native git operations** — replaced `Command::new("git")` shell-outs with the `gix` crate (minimal features: `revision` + `blob-diff`), removing the runtime dependency on a `git` binary for commit counting and tree diffing -- **Test coverage improvements** — 77 new tests across 6 files: - - `complexity_test.rs` (18 tests) — direct tests for the complexity counting algorithm: branches, loops, nesting, unsafe blocks, unwrap/expect detection, assertion counting - - `rust_extraction_test.rs` (17 tests) — Rust extractor: functions, structs, enums, traits, impls, modules, async, visibility, derive macros, call sites - - `display_test.rs` (10 tests) — formatting functions with boundary values - - `php_extraction_test.rs` (11 tests) — classes, interfaces, traits, namespaces, enums, visibility, inheritance - - `ruby_extraction_test.rs` (9 tests) — classes, modules, methods, inheritance, constants, nested classes - - `quickbasic_extraction_test.rs` (12 tests) — QB4.5-specific parsing (REDIM, SLEEP, ERASE), SUBs, FUNCTIONs, TYPEs, call sites +- **QuickBASIC 4.5 language support**, new `QuickBasicExtractor` handles `.bi` (include) and `.bm` (module) files, sharing the QBasic grammar under the existing `lang-qbasic` feature flag (31 languages total) +- **`gix` for native git operations**, replaced `Command::new("git")` shell-outs with the `gix` crate (minimal features: `revision` + `blob-diff`), removing the runtime dependency on a `git` binary for commit counting and tree diffing +- **Test coverage improvements**. 77 new tests across 6 files: + - `complexity_test.rs` (18 tests), direct tests for the complexity counting algorithm: branches, loops, nesting, unsafe blocks, unwrap/expect detection, assertion counting + - `rust_extraction_test.rs` (17 tests). Rust extractor: functions, structs, enums, traits, impls, modules, async, visibility, derive macros, call sites + - `display_test.rs` (10 tests), formatting functions with boundary values + - `php_extraction_test.rs` (11 tests), classes, interfaces, traits, namespaces, enums, visibility, inheritance + - `ruby_extraction_test.rs` (9 tests), classes, modules, methods, inheritance, constants, nested classes + - `quickbasic_extraction_test.rs` (12 tests). QB4.5-specific parsing (REDIM, SLEEP, ERASE), SUBs, FUNCTIONs, TYPEs, call sites ### Changed -- **Legacy BASIC grammars updated to 0.2.0** — `tree-sitter-qbasic`, `tree-sitter-msbasic2`, and `tree-sitter-gwbasic` bumped from 0.1 to 0.2, adding 27 new AST node types for QuickBasic 4.5 constructs (REDIM, SLEEP, ERASE, SHELL, metacommands, and more) +- **Legacy BASIC grammars updated to 0.2.0**. `tree-sitter-qbasic`, `tree-sitter-msbasic2`, and `tree-sitter-gwbasic` bumped from 0.1 to 0.2, adding 27 new AST node types for QuickBasic 4.5 constructs (REDIM, SLEEP, ERASE, SHELL, metacommands, and more) - `git_commits_since` now uses `gix` revision walk with `ByCommitTimeCutoff` sorting, which is more efficient than the previous `git log` approach as gix stops walking once all queued commits are older than the cutoff - `handle_changelog` tree diff now uses `gix` tree-to-tree comparison with rename tracking, replacing `git diff --name-only` ## [2.0.3] - 2026-03-26 ### Fixed -- **Windows: sync re-adding files** — normalize all relative file paths to forward slashes in the scanner, preventing path mismatch between index and sync on Windows -- **Windows: wrong upgrade command** — detect Scoop installations (`\scoop\` in binary path) and suggest `scoop update tracedecay` instead of `cargo install tracedecay` -- **Windows: git hook backslashes** — write forward slashes in `core.hooksPath` and the post-commit hook snippet, since Git's shell expects `/` separators -- **Scoop bucket structure** — moved manifest to `bucket/` subdirectory for better compatibility with `scoop update` -- **Double-counted token savings** — "Global" total no longer includes the current project's count; display now shows "Project" and "All projects" labels +- **Windows: sync re-adding files**, normalize all relative file paths to forward slashes in the scanner, preventing path mismatch between index and sync on Windows +- **Windows: wrong upgrade command**, detect Scoop installations (`\scoop\` in binary path) and suggest `scoop update tracedecay` instead of `cargo install tracedecay` +- **Windows: git hook backslashes**, write forward slashes in `core.hooksPath` and the post-commit hook snippet, since Git's shell expects `/` separators +- **Scoop bucket structure**, moved manifest to `bucket/` subdirectory for better compatibility with `scoop update` +- **Double-counted token savings**. "Global" total no longer includes the current project's count; display now shows "Project" and "All projects" labels ## [2.0.2] - 2026-03-26 @@ -3534,27 +3534,27 @@ The largest functional jump since 4.0: nine new MCP tools, a cross-session respo ### Added #### 16 new language extractors (15 → 30 languages) -- **Swift** — classes, structs, protocols, enums, extensions, init constructors, async methods, visibility modifiers, inheritance -- **Bash** — functions, `readonly` constants, `source` imports, command call sites, comment docstrings -- **Lua** — functions, colon-methods (OOP via metatables), `require()` imports, LDoc comments, `local` constants -- **Zig** — structs, enums, unions, pub/private visibility, `@import` resolution, `test` blocks as functions, doc comments -- **Protobuf** — `message` → `ProtoMessage`, `service` → `ProtoService`, `rpc` → `ProtoRpc` (new node kinds), enums, fields with type signatures, nested messages, `oneof`, package, imports -- **Nix** — functions, modules (attrsets), constants, `inherit` as imports, `apply_expression` call sites, `#` comments -- **VB.NET** — classes, structures, interfaces, modules, enums, `Sub`/`Function`, `Sub New` constructors, properties, `Inherits`/`Implements`, XML doc comments -- **PowerShell** — functions, typed constants, `Import-Module` / dot-source imports, command call sites, `<# ... #>` block comments -- **Batch/CMD** — labels as functions, `SET` as constants, `CALL :label` as call sites, `REM` docstrings (no complexity counting — too flat) -- **Perl** — `sub` functions/methods, `package` as modules, `use`/`require` imports, `our` constants, method invocations (`->`), `#` comments -- **Objective-C** — `@interface`/`@implementation`/`@protocol`, instance (`-`) and class (`+`) methods, `@property`, `NS_ENUM`, `#import`, message expression call sites, inheritance and protocol conformance -- **Fortran** — `module`, `program`, `subroutine`, `function`, derived `type` with fields, `type extends()` inheritance, `interface`, `parameter` constants, `use` imports, `!` comments -- **COBOL** — `PROGRAM-ID` as module, paragraph labels as functions, `WORKING-STORAGE` data items as fields/constants, `PERFORM` as call sites, `REM` comments (vendored grammar) -- **MS BASIC 2.0** — subroutine synthesis from `REM...RETURN` blocks, `LET` constants, `GOSUB`/`GOTO` call sites -- **GW-BASIC** — `DEF FN` functions, `WHILE/WEND` loops, subroutine synthesis, typed constants -- **QBasic** — `SUB`/`FUNCTION` blocks, `TYPE...END TYPE` as structs with fields, `CONST`, `DIM SHARED`, `CALL` sites, `SELECT CASE` +- **Swift**, classes, structs, protocols, enums, extensions, init constructors, async methods, visibility modifiers, inheritance +- **Bash**, functions, `readonly` constants, `source` imports, command call sites, comment docstrings +- **Lua**, functions, colon-methods (OOP via metatables), `require()` imports, LDoc comments, `local` constants +- **Zig**, structs, enums, unions, pub/private visibility, `@import` resolution, `test` blocks as functions, doc comments +- **Protobuf**. `message` → `ProtoMessage`, `service` → `ProtoService`, `rpc` → `ProtoRpc` (new node kinds), enums, fields with type signatures, nested messages, `oneof`, package, imports +- **Nix**, functions, modules (attrsets), constants, `inherit` as imports, `apply_expression` call sites, `#` comments +- **VB.NET**, classes, structures, interfaces, modules, enums, `Sub`/`Function`, `Sub New` constructors, properties, `Inherits`/`Implements`, XML doc comments +- **PowerShell**, functions, typed constants, `Import-Module` / dot-source imports, command call sites, `<# ... #>` block comments +- **Batch/CMD**, labels as functions, `SET` as constants, `CALL :label` as call sites, `REM` docstrings (no complexity counting, too flat) +- **Perl**. `sub` functions/methods, `package` as modules, `use`/`require` imports, `our` constants, method invocations (`->`), `#` comments +- **Objective-C**. `@interface`/`@implementation`/`@protocol`, instance (`-`) and class (`+`) methods, `@property`, `NS_ENUM`, `#import`, message expression call sites, inheritance and protocol conformance +- **Fortran**. `module`, `program`, `subroutine`, `function`, derived `type` with fields, `type extends()` inheritance, `interface`, `parameter` constants, `use` imports, `!` comments +- **COBOL**. `PROGRAM-ID` as module, paragraph labels as functions, `WORKING-STORAGE` data items as fields/constants, `PERFORM` as call sites, `REM` comments (vendored grammar) +- **MS BASIC 2.0**, subroutine synthesis from `REM...RETURN` blocks, `LET` constants, `GOSUB`/`GOTO` call sites +- **GW-BASIC**. `DEF FN` functions, `WHILE/WEND` loops, subroutine synthesis, typed constants +- **QBasic**. `SUB`/`FUNCTION` blocks, `TYPE...END TYPE` as structs with fields, `CONST`, `DIM SHARED`, `CALL` sites, `SELECT CASE` #### Enhanced Nix extraction -- **Derivation field extraction** — `mkDerivation`, `mkShell`, `buildPythonPackage`, `buildGoModule`, `buildRustPackage`, `buildNpmPackage` calls have their attrset arguments extracted as `Field` nodes (`pname`, `version`, `buildInputs`, `nativeBuildInputs`, `src`, `meta`, etc.) -- **Import path resolution** — `import ./path.nix` creates a `Use` node with a `Uses` unresolved ref, enabling cross-file dependency tracking via `tracedecay_callers` and `tracedecay_impact` -- **Flake output schema awareness** — in `flake.nix` files, standard output attributes (`packages`, `devShells`, `apps`, `nixosModules`, `nixosConfigurations`, `overlays`, `lib`, `checks`, `formatter`) are force-classified as `Module` nodes with recursive child extraction +- **Derivation field extraction**. `mkDerivation`, `mkShell`, `buildPythonPackage`, `buildGoModule`, `buildRustPackage`, `buildNpmPackage` calls have their attrset arguments extracted as `Field` nodes (`pname`, `version`, `buildInputs`, `nativeBuildInputs`, `src`, `meta`, etc.) +- **Import path resolution**. `import ./path.nix` creates a `Use` node with a `Uses` unresolved ref, enabling cross-file dependency tracking via `tracedecay_callers` and `tracedecay_impact` +- **Flake output schema awareness**, in `flake.nix` files, standard output attributes (`packages`, `devShells`, `apps`, `nixosModules`, `nixosConfigurations`, `overlays`, `lib`, `checks`, `formatter`) are force-classified as `Module` nodes with recursive child extraction #### Feature flag tiers - Three compilation tiers via Cargo feature flags to control binary size: @@ -3562,20 +3562,20 @@ The largest functional jump since 4.0: nine new MCP tools, a cross-session respo - **`medium`** (20 languages): lite + Dart, Pascal, PHP, Ruby, Bash, Protobuf, PowerShell, Nix, VB.NET - **`full`** (30 languages, default): medium + Lua, Zig, Objective-C, Perl, Batch/CMD, Fortran, COBOL, MS BASIC 2.0, GW-BASIC, QBasic - Individual `lang-*` feature flags for cherry-picking languages (e.g., `--no-default-features --features lang-nix,lang-bash`) -- `default = ["full"]` — existing users get all 30 languages with no config changes +- `default = ["full"]`, existing users get all 30 languages with no config changes #### New node kinds -- `ProtoMessage` — Protobuf message definitions -- `ProtoService` — Protobuf service definitions -- `ProtoRpc` — Protobuf RPC method definitions +- `ProtoMessage`. Protobuf message definitions +- `ProtoService`. Protobuf service definitions +- `ProtoRpc`. Protobuf RPC method definitions #### Porting assessment tools -- **`tracedecay_port_status`** — compare symbols between source and target directories within the same project to track porting progress; matches by name with cross-language kind compatibility (`class` ↔ `struct`, `interface` ↔ `trait`); reports matched/unmatched/target-only counts and coverage percentage -- **`tracedecay_port_order`** — topological sort of source symbols for porting; uses Kahn's algorithm on the internal dependency graph to produce levels (port leaves first, then dependents); detects and reports dependency cycles +- **`tracedecay_port_status`**, compare symbols between source and target directories within the same project to track porting progress; matches by name with cross-language kind compatibility (`class` ↔ `struct`, `interface` ↔ `trait`); reports matched/unmatched/target-only counts and coverage percentage +- **`tracedecay_port_order`**, topological sort of source symbols for porting; uses Kahn's algorithm on the internal dependency graph to produce levels (port leaves first, then dependents); detects and reports dependency cycles #### Agent prompt improvements -- **SQLite fallback instruction** — agents are told to query `.tracedecay/tracedecay.db` directly via SQL when MCP tools can't answer a code analysis question -- **Improvement feedback loop** — agents propose opening a GitHub issue when they discover an extractor/schema/tool gap, reminding the user to strip sensitive data +- **SQLite fallback instruction**, agents are told to query `.tracedecay/tracedecay.db` directly via SQL when MCP tools can't answer a code analysis question +- **Improvement feedback loop**, agents propose opening a GitHub issue when they discover an extractor/schema/tool gap, reminding the user to strip sensitive data ### Changed - Cargo.toml `description` now lists lite-tier languages with "and many more" instead of all 30 @@ -3584,7 +3584,7 @@ The largest functional jump since 4.0: nine new MCP tools, a cross-session respo ### Breaking - Tree-sitter grammar dependencies for medium/full tier languages are now **optional** behind feature flags. Downstream crates depending on specific extractors must enable the corresponding `lang-*` feature. - `cargo install tracedecay --no-default-features` now builds a **lite** binary (11 languages) instead of the previous 15. To get the old behavior, use `cargo install tracedecay` (default = full, 30 languages). -- Three new `NodeKind` variants (`ProtoMessage`, `ProtoService`, `ProtoRpc`) added — code matching exhaustively on `NodeKind` will need updating. +- Three new `NodeKind` variants (`ProtoMessage`, `ProtoService`, `ProtoRpc`) added, code matching exhaustively on `NodeKind` will need updating. ### Upgrade guide ```bash @@ -3596,8 +3596,8 @@ tracedecay sync --force # re-index to pick up new language extractors ## [1.10.0] - 2026-03-26 ### Added -- **Version update notifications** — the MCP server checks GitHub releases (with a 5-minute cache) and warns users when a newer version is available, via both a `notifications/message` logging notification and a text block prepended to tool responses -- **Global git post-commit hook** — `tracedecay install` now offers to install a global `post-commit` hook that auto-runs `tracedecay sync` after each commit, keeping the index up to date without manual intervention +- **Version update notifications**, the MCP server checks GitHub releases (with a 5-minute cache) and warns users when a newer version is available, via both a `notifications/message` logging notification and a text block prepended to tool responses +- **Global git post-commit hook**. `tracedecay install` now offers to install a global `post-commit` hook that auto-runs `tracedecay sync` after each commit, keeping the index up to date without manual intervention - MCP `logging` capability advertised in `initialize` response - Minimal gitconfig parser for reading `core.hooksPath` from `~/.gitconfig` and `~/.config/git/config` without shelling out to `git` - 12 unit tests for gitconfig parsing, insertion, and tilde expansion @@ -3606,7 +3606,7 @@ tracedecay sync --force # re-index to pick up new language extractors ### Fixed - OpenCode MCP config uses `mcp` key (not `mcpServers`) with `"type": "local"` and `"command": [bin, "serve"]` array format, matching the current OpenCode schema -- Removed legacy `~/.opencode.json` fallback — config always writes to `~/.config/opencode/opencode.json` (or `$XDG_CONFIG_HOME`) +- Removed legacy `~/.opencode.json` fallback, config always writes to `~/.config/opencode/opencode.json` (or `$XDG_CONFIG_HOME`) - Healthcheck validates the `command` array contains `"serve"` instead of checking `args` ## [1.8.2] - 2026-03-26 @@ -3618,21 +3618,21 @@ tracedecay sync --force # re-index to pick up new language extractors ## [1.8.1] - 2026-03-26 ### Added -- **OpenCode agent** (`tracedecay install --agent opencode`) — registers MCP server in `.opencode.json`, appends prompt rules to `OPENCODE.md`; healthcheck validates config and prompt file -- **Codex CLI agent** (`tracedecay install --agent codex`) — registers MCP server in `~/.codex/config.toml` with auto-approval for all 27 tools, appends prompt rules to `~/.codex/AGENTS.md`; healthcheck validates config, tool approval counts, and prompt file +- **OpenCode agent** (`tracedecay install --agent opencode`), registers MCP server in `.opencode.json`, appends prompt rules to `OPENCODE.md`; healthcheck validates config and prompt file +- **Codex CLI agent** (`tracedecay install --agent codex`), registers MCP server in `~/.codex/config.toml` with auto-approval for all 27 tools, appends prompt rules to `~/.codex/AGENTS.md`; healthcheck validates config, tool approval counts, and prompt file - TOML helpers (`load_toml_file`, `write_toml_file`) in agents module for Codex config support - `TOOL_NAMES` constant with bare tool names (without agent-specific prefix) for cross-agent use ### New files -- `src/agents/opencode.rs` — `OpenCodeAgent` implementing `Agent` -- `src/agents/codex.rs` — `CodexAgent` implementing `Agent` +- `src/agents/opencode.rs`. `OpenCodeAgent` implementing `Agent` +- `src/agents/codex.rs`. `CodexAgent` implementing `Agent` ## [1.8.0] - 2026-03-26 ### Added - **Multi-agent architecture** with a trait-based `Agent` abstraction (`install`, `uninstall`, `healthcheck`) to support CLI agents beyond Claude Code -- `tracedecay install [--agent NAME]` replaces `claude-install` — defaults to `claude` when no agent is specified -- `tracedecay uninstall [--agent NAME]` replaces `claude-uninstall` — defaults to `claude` +- `tracedecay install [--agent NAME]` replaces `claude-install`, defaults to `claude` when no agent is specified +- `tracedecay uninstall [--agent NAME]` replaces `claude-uninstall`, defaults to `claude` - `tracedecay doctor [--agent NAME]` now checks all registered agents by default; use `--agent` to narrow to one - Agent registry with `get_agent()`, `all_agents()`, and `available_agents()` for programmatic access - `tracedecay install --agent unknown` returns a clear error listing available agents @@ -3644,8 +3644,8 @@ tracedecay sync --force # re-index to pick up new language extractors - Backward compatibility preserved: `tracedecay claude-install` and `tracedecay claude-uninstall` still work as aliases ### New files -- `src/agents/mod.rs` — `Agent` trait, `InstallContext`, `HealthcheckContext`, `DoctorCounters`, agent registry, shared helpers -- `src/agents/claude.rs` — `ClaudeAgent` implementing `Agent` +- `src/agents/mod.rs`. `Agent` trait, `InstallContext`, `HealthcheckContext`, `DoctorCounters`, agent registry, shared helpers +- `src/agents/claude.rs`. `ClaudeAgent` implementing `Agent` ## [1.7.1] - 2026-03-25 @@ -3666,9 +3666,9 @@ tracedecay sync --force # re-index to pick up new language extractors ### Added - **3 new safety metrics on every function/method node** extracted from the AST during indexing, enabling NASA Power of 10 compliance audits without grep: - - `unsafe_blocks` — counts unsafe blocks/statements (Rust `unsafe {}`, C# `unsafe {}`) - - `unchecked_calls` — counts force-unwrap and unchecked operations (Rust `.unwrap()`/`.expect()`, TypeScript `!`, Kotlin `!!`, Java `.get()` on Optional, Scala `.get()`, Ruby `.fetch()`) - - `assertions` — counts assertion calls per function (Rust `assert!`/`debug_assert!`, Java `assertEquals`, Python `assertEqual`, Go `require`, C++ `EXPECT_EQ`/`ASSERT_TRUE`, and framework-specific variants for all 15 languages) + - `unsafe_blocks`, counts unsafe blocks/statements (Rust `unsafe {}`, C# `unsafe {}`) + - `unchecked_calls`, counts force-unwrap and unchecked operations (Rust `.unwrap()`/`.expect()`, TypeScript `!`, Kotlin `!!`, Java `.get()` on Optional, Scala `.get()`, Ruby `.fetch()`) + - `assertions`, counts assertion calls per function (Rust `assert!`/`debug_assert!`, Java `assertEquals`, Python `assertEqual`, Go `require`, C++ `EXPECT_EQ`/`ASSERT_TRUE`, and framework-specific variants for all 15 languages) - Extended `ComplexityConfig` with 6 new fields (`unsafe_types`, `unchecked_types`, `unchecked_methods`, `call_expression_types`, `call_method_field`, `assertion_names`, `macro_invocation_types`) to support cross-language detection - `count_complexity` now accepts source bytes for method-name and macro-name matching in call expressions - DB migration V4 adds `unsafe_blocks`, `unchecked_calls`, and `assertions` columns to the nodes table @@ -3683,7 +3683,7 @@ tracedecay sync --force # re-index to pick up new language extractors ## [1.6.1] - 2026-03-25 ### Fixed -- `claude-install` now registers all 27 tool permissions — 9 tools added in v1.6.0 (`complexity`, `coupling`, `distribution`, `doc_coverage`, `god_class`, `inheritance_depth`, `largest`, `rank`, `recursion`) were missing from `EXPECTED_TOOL_PERMS`, so `claude-install` didn't grant them and `doctor` didn't flag them +- `claude-install` now registers all 27 tool permissions. 9 tools added in v1.6.0 (`complexity`, `coupling`, `distribution`, `doc_coverage`, `god_class`, `inheritance_depth`, `largest`, `rank`, `recursion`) were missing from `EXPECTED_TOOL_PERMS`, so `claude-install` didn't grant them and `doctor` didn't flag them - README permissions example updated to show all 27 tools (was showing only 9) - README: fixed MCP server location reference (`~/.claude.json`, not `~/.claude/settings.json`) @@ -3691,20 +3691,20 @@ tracedecay sync --force # re-index to pick up new language extractors ### Added - 9 new MCP tools (27 total) for codebase analytics, code quality, and guideline compliance: - - `tracedecay_rank` — rank nodes by relationship count with direction support (incoming/outgoing); answers "most implemented interface", "class that implements the most interfaces", etc. - - `tracedecay_largest` — rank nodes by line count; find largest classes, longest methods - - `tracedecay_coupling` — rank files by fan-in (most depended-on) or fan-out (most dependencies) - - `tracedecay_inheritance_depth` — find deepest class hierarchies via recursive CTE on extends chains - - `tracedecay_distribution` — node kind breakdown per file/directory with summary mode - - `tracedecay_recursion` — detect recursive/mutually-recursive call cycles (NASA Power of 10, Rule 1) - - `tracedecay_complexity` — rank functions by composite complexity score with real cyclomatic complexity from AST - - `tracedecay_doc_coverage` — find public symbols missing documentation (Rust guidelines M-CANONICAL-DOCS) - - `tracedecay_god_class` — find classes with the most members (methods + fields) -- **Complexity metrics on every function/method node** — 4 new columns extracted from the AST during indexing: - - `branches` — branching statements (if, match/switch arms, ternary, catch). CC = branches + 1. - - `loops` — loop constructs (for, while, loop, do). Enables NASA Rule 2 audits. - - `returns` — early exits (return, break, continue, throw). - - `max_nesting` — deepest brace nesting level. Enables NASA Rule 1 (≤4 levels) audits. + - `tracedecay_rank`, rank nodes by relationship count with direction support (incoming/outgoing); answers "most implemented interface", "class that implements the most interfaces", etc. + - `tracedecay_largest`, rank nodes by line count; find largest classes, longest methods + - `tracedecay_coupling`, rank files by fan-in (most depended-on) or fan-out (most dependencies) + - `tracedecay_inheritance_depth`, find deepest class hierarchies via recursive CTE on extends chains + - `tracedecay_distribution`, node kind breakdown per file/directory with summary mode + - `tracedecay_recursion`, detect recursive/mutually-recursive call cycles (NASA Power of 10, Rule 1) + - `tracedecay_complexity`, rank functions by composite complexity score with real cyclomatic complexity from AST + - `tracedecay_doc_coverage`, find public symbols missing documentation (Rust guidelines M-CANONICAL-DOCS) + - `tracedecay_god_class`, find classes with the most members (methods + fields) +- **Complexity metrics on every function/method node**. 4 new columns extracted from the AST during indexing: + - `branches`, branching statements (if, match/switch arms, ternary, catch). CC = branches + 1. + - `loops`, loop constructs (for, while, loop, do). Enables NASA Rule 2 audits. + - `returns`, early exits (return, break, continue, throw). + - `max_nesting`, deepest brace nesting level. Enables NASA Rule 1 (≤4 levels) audits. - Generic `count_complexity()` helper with per-language configs for all 15 supported languages - DB migration V3 adds the 4 complexity columns to the nodes table - All new tools use efficient SQL queries (JOINs, GROUP BY, recursive CTEs) instead of loading all edges into memory @@ -3719,20 +3719,20 @@ tracedecay sync --force # re-index to pick up new language extractors ## [1.5.1] - 2026-03-25 ### Added -- `tracedecay doctor` command — comprehensive health check of binary, project index, global DB, user config, Claude Code integration (MCP server, hook, permissions, CLAUDE.md), and network connectivity +- `tracedecay doctor` command, comprehensive health check of binary, project index, global DB, user config, Claude Code integration (MCP server, hook, permissions, CLAUDE.md), and network connectivity - Stale install warning: automatically detects when `claude-install` needs re-running due to new tool permissions and warns on every CLI command ### Added - 9 new MCP tools (18 total): - - `tracedecay_dead_code` — find unreachable symbols with no incoming edges - - `tracedecay_diff_context` — semantic context for changed files (modified symbols, dependencies, affected tests) - - `tracedecay_module_api` — public API surface of a file or directory - - `tracedecay_circular` — detect circular file dependencies - - `tracedecay_hotspots` — most connected symbols by edge count - - `tracedecay_similar` — find symbols with similar names - - `tracedecay_rename_preview` — all references to a symbol - - `tracedecay_unused_imports` — import statements never referenced - - `tracedecay_changelog` — semantic diff between two git refs + - `tracedecay_dead_code`, find unreachable symbols with no incoming edges + - `tracedecay_diff_context`, semantic context for changed files (modified symbols, dependencies, affected tests) + - `tracedecay_module_api`, public API surface of a file or directory + - `tracedecay_circular`, detect circular file dependencies + - `tracedecay_hotspots`, most connected symbols by edge count + - `tracedecay_similar`, find symbols with similar names + - `tracedecay_rename_preview`, all references to a symbol + - `tracedecay_unused_imports`, import statements never referenced + - `tracedecay_changelog`, semantic diff between two git refs - `get_all_edges()`, `get_nodes_by_file()`, `get_all_nodes()`, `get_incoming_edges()`, `get_outgoing_edges()` delegation methods on `TraceDecay` - `find_circular_dependencies()` graph query for file-level cycle detection - `tracedecay status` prompts to create index if none exists (Y/n) @@ -3741,20 +3741,20 @@ tracedecay sync --force # re-index to pick up new language extractors ## [1.4.3] - 2026-03-25 ### Added -- Country flags row in `tracedecay status` — shows emoji flags of countries where tracedecay is used, centered below the token counters +- Country flags row in `tracedecay status`, shows emoji flags of countries where tracedecay is used, centered below the token counters - `fetch_country_flags()` in cloud module (500ms timeout, best-effort) - Flags truncated with ellipsis if they exceed the available table width ## [1.4.2] - 2026-03-25 ### Added -- PHP language support (`.php`) — functions, classes, methods, traits, interfaces, enums, constants, properties, namespaces, imports, and call sites -- Ruby language support (`.rb`) — methods, classes, modules, constants, inheritance, and call sites +- PHP language support (`.php`), functions, classes, methods, traits, interfaces, enums, constants, properties, namespaces, imports, and call sites +- Ruby language support (`.rb`), methods, classes, modules, constants, inheritance, and call sites ## [1.4.1] - 2026-03-25 ### Added -- Cross-platform release workflow — GitHub Actions builds prebuilt binaries for macOS (ARM), Linux (x86_64, ARM64), and Windows (x86_64) on every release +- Cross-platform release workflow. GitHub Actions builds prebuilt binaries for macOS (ARM), Linux (x86_64, ARM64), and Windows (x86_64) on every release - Scoop package manager support for Windows (`scoop install tracedecay`) - Automated Scoop bucket updates on release - Automated Homebrew formula + bottle updates on release @@ -3765,7 +3765,7 @@ tracedecay sync --force # re-index to pick up new language extractors ## [1.4.0] - 2026-03-25 ### Added -- Worldwide tracedecayd counter — aggregates anonymous token counts across all tracedecay users via Cloudflare Worker + Upstash Redis +- Worldwide tracedecayd counter, aggregates anonymous token counts across all tracedecay users via Cloudflare Worker + Upstash Redis - `tracedecay status` shows three tiers: Local, Global, and Worldwide token counts - `tracedecay disable-upload-counter` / `tracedecay enable-upload-counter` commands to opt out of uploading - All upload state stored transparently in `~/.tracedecay/config.toml` @@ -3801,20 +3801,20 @@ tracedecay sync --force # re-index to pick up new language extractors ## [1.2.0] - 2026-03-24 ### Added -- `claude-install` CLI command — configures Claude Code integration (MCP server, permissions, hook, CLAUDE.md rules) in a single step, replacing the bash `setup.sh` script -- `hook-pre-tool-use` hidden CLI command — cross-platform PreToolUse hook handler written in pure Rust (no bash/jq dependency), blocks Explore agents and exploration-style prompts +- `claude-install` CLI command, configures Claude Code integration (MCP server, permissions, hook, CLAUDE.md rules) in a single step, replacing the bash `setup.sh` script +- `hook-pre-tool-use` hidden CLI command, cross-platform PreToolUse hook handler written in pure Rust (no bash/jq dependency), blocks Explore agents and exploration-style prompts ### Removed -- Embedded bash hook script — the hook is now a native Rust subcommand +- Embedded bash hook script, the hook is now a native Rust subcommand ## [1.1.0] - 2026-03-24 ### Added -- `tracedecay files` CLI command — list indexed files with `--filter` (directory prefix), `--pattern` (glob), and `--json` output -- `tracedecay affected` CLI command — BFS through file dependency graph to find test files impacted by source changes; supports `--stdin` (pipe from `git diff --name-only`), `--depth`, `--filter`, `--json`, `--quiet` -- `tracedecay_files` MCP tool — file listing with path/pattern filtering, flat or grouped-by-directory output -- `tracedecay_affected` MCP tool — find affected test files via file-level dependency traversal -- Graceful shutdown handler for MCP server — persists tokens-saved counter, checkpoints SQLite WAL, and logs session summary on SIGINT/SIGTERM +- `tracedecay files` CLI command, list indexed files with `--filter` (directory prefix), `--pattern` (glob), and `--json` output +- `tracedecay affected` CLI command. BFS through file dependency graph to find test files impacted by source changes; supports `--stdin` (pipe from `git diff --name-only`), `--depth`, `--filter`, `--json`, `--quiet` +- `tracedecay_files` MCP tool, file listing with path/pattern filtering, flat or grouped-by-directory output +- `tracedecay_affected` MCP tool, find affected test files via file-level dependency traversal +- Graceful shutdown handler for MCP server, persists tokens-saved counter, checkpoints SQLite WAL, and logs session summary on SIGINT/SIGTERM - `Database::checkpoint()` method for WAL cleanup on shutdown ## [1.0.1] - 2026-03-24 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8343ccf630..b33af635d1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ is required. See [AGENTS.md](AGENTS.md) for checkout safety and shared-work rule The dashboard bundle at `dashboard/app-dist/` is generated output and is git-ignored, so a fresh clone has none. The CLI build script -(`crates/tracedecay-cli/build.rs`) — the only crate that embeds the bundle — +(`crates/tracedecay-cli/build.rs`), the only crate that embeds the bundle, builds the frontend into an immutable, digest-named copy under its own `OUT_DIR` rather than embedding `app-dist`: `npm ci` runs when `dashboard/node_modules` lacks the marker for the current `package-lock.json`, @@ -44,7 +44,7 @@ Read [the V2 operating-model summary](docs/V2-OPERATING-MODEL.md) before changing storage, retrieval, or host ingestion, then follow the linked authoritative roadmap plans. `tracedecay-graph-db` is the sole final Grafeo boundary; SQLite is relational only. V2 persisted data is reset or recreated -when incompatible—do not add a prior-store reader, conversion, backfill, +when incompatible, do not add a prior-store reader, conversion, backfill, shadow path, or dual write. Tests and local validation must use isolated temporary home, profile, project, @@ -162,7 +162,7 @@ Changes under `plugin/` or `crates/tracedecay-agent-hosts/` are covered by a layered validation system: vendored JSON-schema checks, per-host skill frontmatter contracts, cross-bundle sync/parity tests, and a CI schema-validation workflow. `plugin/skills/` is the shared source of truth for -bundled skills — do not fork host-specific copies. Before submitting, run: +bundled skills, do not fork host-specific copies. Before submitting, run: ```bash cargo nextest run -p tracedecay --features test-helpers --test agent_suite @@ -190,7 +190,7 @@ npm run contracts:check # what CI runs; exits 1 on any drift ``` `contracts:check` is a blocking CI step in the `dashboard` job of `ci.yml`, not -an advisory one. Do not hand-edit the generated files — the check will fail and +an advisory one. Do not hand-edit the generated files. The check will fail and the fix is to regenerate and commit. The generated contract files (and the embedded Cursor extension bundle) are @@ -244,7 +244,7 @@ behavior. - Target `master` for bug fixes and stable features. - Confirm the target branch with the maintainer for release-channel work; do not infer it from an archived plan or PR number. -- Keep PRs focused — one logical change per PR. +- Keep PRs focused. One logical change per PR. - Include test coverage for new behavior. - Do not hand-edit `CHANGELOG.md`; release automation generates it from conventional commit messages. diff --git a/Cargo.lock b/Cargo.lock index 5494c5a697..9904680828 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5330,7 +5330,6 @@ dependencies = [ "criterion", "dirs", "filetime", - "fs2", "futures-util", "gix", "glob", @@ -5416,7 +5415,6 @@ version = "0.1.0" dependencies = [ "cap-fs-ext", "cap-std", - "fs2", "getrandom 0.2.17", "gix", "hex", @@ -5485,7 +5483,6 @@ version = "0.1.0" dependencies = [ "cap-std", "criterion", - "fs2", "getrandom 0.2.17", "gix", "glob", @@ -5551,7 +5548,6 @@ version = "0.1.0" dependencies = [ "cap-fs-ext", "cap-std", - "fs2", "getrandom 0.2.17", "hex", "hotpath", @@ -5608,7 +5604,6 @@ dependencies = [ "crossterm", "dirs", "flate2", - "fs2", "futures-util", "hex", "hotpath", @@ -5721,7 +5716,6 @@ version = "0.1.0" dependencies = [ "cap-fs-ext", "cap-std", - "fs2", "gix", "hotpath", "libc", @@ -5843,7 +5837,6 @@ dependencies = [ name = "tracedecay-daemon-identity" version = "0.1.0" dependencies = [ - "fs2", "getrandom 0.2.17", "hex", "hotpath", @@ -5881,7 +5874,6 @@ name = "tracedecay-daemon-service" version = "0.1.0" dependencies = [ "axum", - "fs2", "getrandom 0.2.17", "hex", "hotpath", @@ -6040,7 +6032,6 @@ name = "tracedecay-graph-db" version = "0.1.0" dependencies = [ "criterion", - "fs2", "grafeo-adapters", "grafeo-common", "grafeo-core", @@ -6421,7 +6412,6 @@ name = "tracedecay-runtime-core" version = "0.1.0" dependencies = [ "criterion", - "fs2", "getrandom 0.2.17", "gix", "hex", diff --git a/SECURITY.md b/SECURITY.md index a2e0f1da88..0f2f179107 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -15,10 +15,10 @@ Only the current major release line is supported. All minor and patch versions w | Version | Supported | |---------|-----------| -| 6.x (current) | Yes — all minor and patch releases | +| 6.x (current) | Yes, all minor and patch releases | | < 6 | No | -When a vulnerability is found, the fix is shipped as a new release — there are no backports to older major versions. Fixes are not applied in place to existing binaries. **If you run tracedecay in production automation (CI pipelines, scheduled agents, server-side MCP deployments), keep it updated to the latest release** so any future fix reaches you immediately via `tracedecay upgrade`. +When a vulnerability is found, the fix is shipped as a new release. There are no backports to older major versions. Fixes are not applied in place to existing binaries. **If you run tracedecay in production automation (CI pipelines, scheduled agents, server-side MCP deployments), keep it updated to the latest release** so any future fix reaches you immediately via `tracedecay upgrade`. ## Security Model @@ -33,7 +33,7 @@ tracedecay builds a **local** code graph stored in the active project store. Rep - Cross-session memory: durable facts, named entities, code-area notes, decisions, and feedback events in the holographic fact store. Those rows are local-only project data. - A response cache for `tracedecay_read` (`read_cache` table): the rendered output served to the agent, stored as a BLOB keyed by file path, mode, and arguments. For full/line-range reads this rendered output contains source text. Rows are freshness-gated by file mtime and swept after a period of inactivity. -Aside from the `read_cache`, the graph itself does **not** persist raw source code — it stores structural metadata only. The active project store is local-only — there is no cloud sync, remote database, or server-side storage. +Aside from the `read_cache`, the graph itself does **not** persist raw source code. It stores structural metadata only. The active project store is local-only. There is no cloud sync, remote database, or server-side storage. The user-level `~/.tracedecay/global.db` tracks indexed projects, aggregate token-savings counts, and cost accounting data parsed from Claude Code session transcripts. Cursor transcript search is stored in the active project's session store (`.tracedecay/sessions.db` for repo-local projects), which contains ingested Cursor user/assistant message text plus transcript paths and metadata for that project. Both stores remain local-only and are not synced to a remote service. @@ -92,14 +92,14 @@ The MCP server exposes **more than 70 tools** (one fewer when the optional `ast- **File-editing tools** (modify source files in your project): -- `tracedecay_str_replace`, `tracedecay_multi_str_replace` — anchored string replacement -- `tracedecay_insert_at`, `tracedecay_insert_at_symbol` — anchored insertion -- `tracedecay_replace_symbol` — replace a symbol's body -- `tracedecay_ast_grep_rewrite` — structural rewrite via the external `ast-grep` binary +- `tracedecay_str_replace`, `tracedecay_multi_str_replace`, anchored string replacement +- `tracedecay_insert_at`, `tracedecay_insert_at_symbol`, anchored insertion +- `tracedecay_replace_symbol`, replace a symbol's body +- `tracedecay_ast_grep_rewrite`, structural rewrite via the external `ast-grep` binary **Local-state tools** (write only inside the active TraceDecay store, never your source): -- `tracedecay_fact_store_add`, `tracedecay_fact_store_update`, `tracedecay_fact_store_remove`, `tracedecay_fact_store_supersede`, and `tracedecay_fact_feedback` — store, remove, or supersede fact text, entity names, feedback events, and trust-score inputs in the local project database. The other exact `tracedecay_fact_store_*` routes and `tracedecay_memory_status` are read-only; repair is daemon-owned background work. +- `tracedecay_fact_store_add`, `tracedecay_fact_store_update`, `tracedecay_fact_store_remove`, `tracedecay_fact_store_supersede`, and `tracedecay_fact_feedback`, store, remove, or supersede fact text, entity names, feedback events, and trust-score inputs in the local project database. The other exact `tracedecay_fact_store_*` routes and `tracedecay_memory_status` are read-only; repair is daemon-owned background work. ### Support bundles and storage diagnostics @@ -109,13 +109,13 @@ Also redact credential-bearing git remotes, database overrides such as `TRACEDEC **Test execution:** -- `tracedecay_run_affected_tests` — compiles and runs the project's own test suite via a `cargo` subprocess (bounded by a configurable wall-clock timeout, default 300 s, and a per-invocation test cap) +- `tracedecay_run_affected_tests`, compiles and runs the project's own test suite via a `cargo` subprocess (bounded by a configurable wall-clock timeout, default 300 s, and a per-invocation test cap) The edit tools target a single file with a unique anchor and re-index in place. They never run shell commands you did not supply. Network-capable operations are limited to the documented release, pricing, semantic-model, telemetry, and configured GitHub review paths above. Every editing and state-mutating tool is -single-file or single-record scoped — there is no bulk-delete or +single-file or single-record scoped. There is no bulk-delete or recursive-write primitive. > Note: file edits are applied by the agent on your behalf through your agent's own tool-approval flow. Treat tracedecay's edit tools with the same caution as your agent's built-in file-write tools. @@ -140,8 +140,8 @@ tracedecay installs **no background daemon, system service, or autostart process The codebase contains minimal `unsafe`, used in two cross-platform places: -- **Memory-mapped monitor ring buffer** (`src/monitor.rs`) — `memmap2` maps `~/.tracedecay/monitor.mmap`, the shared buffer the `tracedecay monitor` TUI reads -- **Tree-sitter FFI** (`crates/tracedecay-code-extraction/src/ts_provider.rs`) — constructing the bundled WGSL grammar from its raw C entry point +- **Memory-mapped monitor ring buffer** (`src/monitor.rs`). `memmap2` maps `~/.tracedecay/monitor.mmap`, the shared buffer the `tracedecay monitor` TUI reads +- **Tree-sitter FFI** (`crates/tracedecay-code-extraction/src/ts_provider.rs`), constructing the bundled WGSL grammar from its raw C entry point The Windows-elevation `unsafe` documented in earlier versions was removed alongside the daemon in 6.0.0. diff --git a/benchmark_data/index-bench/README.md b/benchmark_data/index-bench/README.md index 41a38b1754..f3bd7e5ed8 100644 --- a/benchmark_data/index-bench/README.md +++ b/benchmark_data/index-bench/README.md @@ -1,8 +1,8 @@ # indexing benchmark workload The profiled workload for `.github/workflows/hotpath-profile.yml`. It exists -so a pull request can be asked one question — *did this diff make indexing -slower?* — and get an answer that is about the diff rather than about the +so a pull request can be asked one question. *did this diff make indexing +slower?*, and get an answer that is about the diff rather than about the runner. Entrypoint: `crates/tracedecay-query/src/bin/tracedecay_index_bench.rs`, @@ -20,7 +20,7 @@ HOTPATH_REPORT=functions-timing \ The workload summary goes to stdout as JSON; the Hotpath report goes to `HOTPATH_OUTPUT_PATH`. Built without the `hotpath` feature the binary runs -the identical workload, prints the identical summary, and writes no report — +the identical workload, prints the identical summary, and writes no report, every instrumentation macro compiles to nothing. ## What it measures @@ -80,7 +80,7 @@ things and the timing delta is not a like-for-like comparison. `corpus/` is committed and regenerated by `scripts/generate-index-bench-corpus.py`, which is a pure function of the file -ordinal — no RNG, no clock, no environment. Rerunning it without editing it is +ordinal, no RNG, no clock, no environment. Rerunning it without editing it is a no-op. ```text diff --git a/benchmark_data/queries/default.toml b/benchmark_data/queries/default.toml index 4da35b64d0..40fe11a4fe 100644 --- a/benchmark_data/queries/default.toml +++ b/benchmark_data/queries/default.toml @@ -5,7 +5,7 @@ # and compares to a baseline that reads the full content of every file # referenced by the resulting context. The ratio is the retrieval savings. # -# These queries are intentionally generic — they target patterns present in +# These queries are intentionally generic, they target patterns present in # most application codebases (CLIs, daemons, services) rather than this repo # specifically. For tighter recall numbers on a specific project, write your # own query file and pass it via `tracedecay bench --queries `. diff --git a/benchmark_data/run_benchmarks.py b/benchmark_data/run_benchmarks.py index 196c16e583..c33d4fe6d6 100644 --- a/benchmark_data/run_benchmarks.py +++ b/benchmark_data/run_benchmarks.py @@ -1,4 +1,4 @@ -"""TraceDecay vs Token-Savior — side-by-side benchmark on Python repos. +"""TraceDecay vs Token-Savior, side-by-side benchmark on Python repos. Adapted from token-savior's benchmarks/run_benchmarks.py (https://github.com/Mibayy/token-savior). Runs both tools against the same @@ -188,7 +188,7 @@ class TraceDecayMcp: Spawns a long-lived server so per-call latency reflects actual query work instead of process startup + DB open. Newline-delimited JSON, no Content- - Length framing — see src/mcp/transport.rs. + Length framing, see src/mcp/transport.rs. """ def __init__(self, root: Path, env: dict[str, str] | None = None): @@ -428,11 +428,11 @@ def generate_report(results: list[dict], naive_sizes: dict[str, int]) -> str: "**Memory notes.** token-savior's peak memory is measured with `tracemalloc`", "(Python heap only). tracedecay runs as a subprocess, so its peak is the", "`ru_maxrss` delta from `getrusage(RUSAGE_CHILDREN)` (resident set size).", - "These are *not* identical units — treat them as order-of-magnitude.", + "These are *not* identical units, treat them as order-of-magnitude.", "", "**Query timing.** token-savior is called in-process (pure Python dict", "lookups). tracedecay is driven over MCP via `tracedecay serve --timings`", - "and the per-query column reports the handler's `_meta.duration_us` —", + "and the per-query column reports the handler's `_meta.duration_us`,", "i.e. the time spent inside the Rust handler, with JSON-RPC / stdio /", "Python-parse overhead stripped out. A warm-up call is issued before", "each timed loop. `get_change_impact` for tracedecay sums the handler", diff --git a/benchmark_data/runtime/evidence/cli-exact-query-p95-20260730/README.md b/benchmark_data/runtime/evidence/cli-exact-query-p95-20260730/README.md index 209b802b72..dd854b55f5 100644 --- a/benchmark_data/runtime/evidence/cli-exact-query-p95-20260730/README.md +++ b/benchmark_data/runtime/evidence/cli-exact-query-p95-20260730/README.md @@ -1,6 +1,6 @@ # Integrated CLI exact-query p95 evidence -> **Dated runtime evidence — not acceptance authority.** Preserve these raw +> **Dated runtime evidence, not acceptance authority.** Preserve these raw > samples and their provenance, but do not recreate their exact counts, > snapshots, receipts, attestations, binary/worktree choreography, or gates as > build prerequisites. Current requirements come only from the diff --git a/benchmark_data/runtime/evidence/code-index-restore-20260731/README.md b/benchmark_data/runtime/evidence/code-index-restore-20260731/README.md index 0fe704df3d..a413a2554a 100644 --- a/benchmark_data/runtime/evidence/code-index-restore-20260731/README.md +++ b/benchmark_data/runtime/evidence/code-index-restore-20260731/README.md @@ -1,6 +1,6 @@ # Code-index restore performance evidence (2026-07-31) -> **Dated runtime evidence — not acceptance authority.** Preserve these raw +> **Dated runtime evidence, not acceptance authority.** Preserve these raw > samples and their provenance, but do not recreate their exact counts, > snapshots, receipts, attestations, binary/worktree choreography, or gates as > build prerequisites. Current requirements come only from the @@ -9,8 +9,8 @@ ## Question under test A historical defect from the delivery snapshot (not recorded in-repo) claims -code-index **restore** — loading an already-built index at project open, not -initial indexing — costs about **488.8 s wall / 7.8 GiB peak RSS**. The +code-index **restore**, loading an already-built index at project open, not +initial indexing, costs about **488.8 s wall / 7.8 GiB peak RSS**. The release-readiness investigation requires this to be disproven or fixed. ## Verdict: DISPROVEN at repo-scale workload @@ -55,7 +55,7 @@ directory is the generation script): 1. `benchmark_data/runtime/fixtures.py::prepare_fixture_snapshot(fixture_root=...)` prepared an isolated snapshot (isolated `HOME`, `TRACEDECAY_DATA_DIR`, `TRACEDECAY_DAEMON_SOCKET` all derived by the harness from the snapshot - root — never exported manually) whose fixture project embeds the repo code + root, never exported manually) whose fixture project embeds the repo code tree. Snapshot root `/tmp/td-restore-ev/snap-base` (short path: the daemon socket must fit `SUN_LEN`). 2. `tracedecay init ` ran once, daemon-less, building the full index @@ -75,7 +75,7 @@ directory is the generation script): Every sample invocation wrapped in `/usr/bin/time -v` (parsed into `gnu_time` per JSONL line). 4. Controls: three daemon cold starts over freshly prepared, never-indexed - snapshots (admission 0.04 / 2.91 / 4.83 s — first-open global-DB creation + snapshots (admission 0.04 / 2.91 / 4.83 s, first-open global-DB creation variance; peak RSS 25–31 MiB). Restore-sample admission was uniformly about 0.04 s (one 0.17 s outlier); nearly all restore-path time is the post-admission warming window. @@ -109,9 +109,9 @@ python3 restore_driver.py index --binary target/release/tracedecay \ | 6 | 0.039 | 2.383 | 3 | 2.422 | 125 | 318 | | 7 | 0.041 | 2.877 | 4 | 2.918 | 158 | 338 | -Raw per-sample records: `report.samples.jsonl` (custom explicit line schema — +Raw per-sample records: `report.samples.jsonl` (custom explicit line schema, `kind` in {`index-build-once`, `cold-restore-sample`, -`control-empty-profile-admission`} — not the `run.py` sample schema, whose +`control-empty-profile-admission`}, not the `run.py` sample schema, whose fields do not describe this composed measurement). ## Provenance and environment diff --git a/benchmark_data/runtime/evidence/diagnostic-flood-authority-20260730/README.md b/benchmark_data/runtime/evidence/diagnostic-flood-authority-20260730/README.md index 8122905070..b0ab4da2af 100644 --- a/benchmark_data/runtime/evidence/diagnostic-flood-authority-20260730/README.md +++ b/benchmark_data/runtime/evidence/diagnostic-flood-authority-20260730/README.md @@ -1,6 +1,6 @@ # Diagnostic flood authority evidence -> **Dated runtime evidence — not acceptance authority.** Preserve these raw +> **Dated runtime evidence, not acceptance authority.** Preserve these raw > samples and their provenance, but do not recreate their exact counts, > snapshots, receipts, attestations, or gate choreography as build > prerequisites. Current requirements come only from the diff --git a/benchmark_data/runtime/evidence/missing-daemon-after-shell-p95-20260730/README.md b/benchmark_data/runtime/evidence/missing-daemon-after-shell-p95-20260730/README.md index 9dffda64b6..1c9cd6484a 100644 --- a/benchmark_data/runtime/evidence/missing-daemon-after-shell-p95-20260730/README.md +++ b/benchmark_data/runtime/evidence/missing-daemon-after-shell-p95-20260730/README.md @@ -1,6 +1,6 @@ # Missing-daemon after-shell p95 evidence -> **Dated runtime evidence — not acceptance authority.** Preserve these raw +> **Dated runtime evidence, not acceptance authority.** Preserve these raw > samples and their provenance, but do not recreate their exact counts, > snapshots, receipts, attestations, or gate choreography as build > prerequisites. Current requirements come only from the diff --git a/benchmark_data/runtime/evidence/pinned-audit-revalidation-20260730/README.md b/benchmark_data/runtime/evidence/pinned-audit-revalidation-20260730/README.md index 3ac87ad2d9..413ac55a57 100644 --- a/benchmark_data/runtime/evidence/pinned-audit-revalidation-20260730/README.md +++ b/benchmark_data/runtime/evidence/pinned-audit-revalidation-20260730/README.md @@ -1,6 +1,6 @@ # Pinned-audit runtime revalidation -> **Dated runtime evidence — not acceptance authority.** Preserve these raw +> **Dated runtime evidence, not acceptance authority.** Preserve these raw > samples and their provenance, but do not recreate their exact counts, > snapshots, receipts, attestations, binary/worktree choreography, or gates as > build prerequisites. Current requirements come only from the diff --git a/benchmark_data/session-temporal/README.md b/benchmark_data/session-temporal/README.md index 259d0e7474..930ce9263b 100644 --- a/benchmark_data/session-temporal/README.md +++ b/benchmark_data/session-temporal/README.md @@ -48,8 +48,8 @@ clean source commit, performs that same real measurement without accepting caller-supplied values, and publishes the result before pointing the evidence index at it. The workload manifest is static configuration and is never rewritten by runs. The published result records the clean source commit and -mode — the commit is the content authority for every tracked artifact, so no -per-file hashes are maintained — plus warmups, measured repetitions, and +mode, the commit is the content authority for every tracked artifact, so no +per-file hashes are maintained, plus warmups, measured repetitions, and record counts. ## Observed focused tests diff --git a/benchmark_data/tsbench/README.md b/benchmark_data/tsbench/README.md index 593207f5a4..dff32d1988 100644 --- a/benchmark_data/tsbench/README.md +++ b/benchmark_data/tsbench/README.md @@ -1,4 +1,4 @@ -# tsbench — tracedecay run +# tsbench, tracedecay run Adapts [`Mibayy/tsbench`](https://github.com/Mibayy/tsbench) (the 96-task agent benchmark token-savior uses to publish its 97.9% score) to drive @@ -38,33 +38,33 @@ PY ## What the patch changes (vs. upstream `bench.py`) -- **MCP config** — launches `tracedecay serve -p --timings` instead of +- **MCP config**, launches `tracedecay serve -p --timings` instead of `token_savior.server` over Python stdio. -- **System prompt** — rewrites `SYSTEM_PROMPT_TS` to map each token-savior +- **System prompt**, rewrites `SYSTEM_PROMPT_TS` to map each token-savior tool to its tracedecay equivalent (`find_symbol` → `tracedecay_find_exact_symbol`, `get_function_source` → `tracedecay_body`, `get_full_context` → `tracedecay_context`, etc.). Where no tracedecay equivalent exists (`add_field_to_model`, `move_symbol`, `analyze_config`, `analyze_docker`), the prompt explicitly allows `Read` / `Edit` fallback. -- **`--disallowedTools`** — relaxed from +- **`--disallowedTools`**, relaxed from `["Read","Grep","Glob","Agent"]` to `["Agent"]` only, since the four fallback task categories need text-level tools. -- **Tool-prefix matcher** — `ts_prefixes = ("mcp__tracedecay__",)`. -- **Results path** — `results-tracedecay/raw/` (so a tracedecay run doesn't +- **Tool-prefix matcher**. `ts_prefixes = ("mcp__tracedecay__",)`. +- **Results path**. `results-tracedecay/raw/` (so a tracedecay run doesn't overwrite token-savior's `results/raw/`). -- **Seed-session filename** — `.bench-tracedecay-session-id`. -- **`CLAUDE_PROJECT_ROOT`** env var — set to `ROOT` (the local repo) instead +- **Seed-session filename**. `.bench-tracedecay-session-id`. +- **`CLAUDE_PROJECT_ROOT`** env var, set to `ROOT` (the local repo) instead of the hard-coded `/root/projects/tsbench`. ## Environment -- `TRACEDECAY_BIN` — path to the tracedecay binary. Defaults to the release +- `TRACEDECAY_BIN`, path to the tracedecay binary. Defaults to the release build in the canonical checkout location. -- `TSBENCH_BARE` — set to `0` on macOS / Max OAuth (default is `1`, but +- `TSBENCH_BARE`, set to `0` on macOS / Max OAuth (default is `1`, but `--bare` mode broke OAuth in our environment). On Linux + API key, leave default. -- `TSBENCH_MODEL` — defaults to `claude-opus-4-7`. +- `TSBENCH_MODEL`, defaults to `claude-opus-4-7`. ## License diff --git a/crates/tracedecay-agent-hosts/Cargo.toml b/crates/tracedecay-agent-hosts/Cargo.toml index 62c59ebe49..ca7b493e5c 100644 --- a/crates/tracedecay-agent-hosts/Cargo.toml +++ b/crates/tracedecay-agent-hosts/Cargo.toml @@ -27,7 +27,6 @@ test-transport = [ [dependencies] cap-fs-ext = "4.0.2" cap-std = "4.0.2" -fs2 = "0.4" same-file = "1.0.6" getrandom = "0.2" hex = "0.4" diff --git a/crates/tracedecay-agent-hosts/build.rs b/crates/tracedecay-agent-hosts/build.rs index 112531c877..a7baaa5f4c 100644 --- a/crates/tracedecay-agent-hosts/build.rs +++ b/crates/tracedecay-agent-hosts/build.rs @@ -337,8 +337,8 @@ fn generate_plugin_bundle() { /// `env!("CARGO_PKG_VERSION")` is resolved per compiled crate, so inside this /// library it is this crate's own version rather than the version of the /// `tracedecay` product a user installed. Everything this crate stamps into a -/// place a host can see — plugin manifests, plugin cache paths, staleness -/// warnings, provenance headers — is compared against that product version, so +/// place a host can see, plugin manifests, plugin cache paths, staleness +/// warnings, provenance headers, is compared against that product version, so /// it is read here from the one place that authors it: /// `[workspace.package].version` in the workspace-root `Cargo.toml`. /// diff --git a/crates/tracedecay-agent-hosts/src/agents/antigravity.rs b/crates/tracedecay-agent-hosts/src/agents/antigravity.rs index 71d060a69a..4eee058b31 100644 --- a/crates/tracedecay-agent-hosts/src/agents/antigravity.rs +++ b/crates/tracedecay-agent-hosts/src/agents/antigravity.rs @@ -2,9 +2,9 @@ //! //! Handles registration of the tracedecay MCP server in: //! -//! - `~/.gemini/antigravity/mcp_config.json` — the Antigravity IDE config, +//! - `~/.gemini/antigravity/mcp_config.json`, the Antigravity IDE config, //! shape `{"mcpServers": {"tracedecay": {...}}}`. -//! - `~/.gemini/antigravity-cli/plugins/tracedecay.json` — the Antigravity +//! - `~/.gemini/antigravity-cli/plugins/tracedecay.json`, the Antigravity //! CLI (`agy`) plugin file, same shape. Required because the IDE config //! is not picked up by the CLI (#85). //! @@ -225,7 +225,7 @@ fn doctor_check_registration( ) { if !config.exists() { dc.warn(&format!( - "{} not found — run `tracedecay install --agent antigravity` if you use {}", + "{} not found, run `tracedecay install --agent antigravity` if you use {}", config.display(), product )); diff --git a/crates/tracedecay-agent-hosts/src/agents/claude.rs b/crates/tracedecay-agent-hosts/src/agents/claude.rs index 9cd556b874..ff1d8a2e77 100644 --- a/crates/tracedecay-agent-hosts/src/agents/claude.rs +++ b/crates/tracedecay-agent-hosts/src/agents/claude.rs @@ -509,7 +509,7 @@ fn plugin_deploy_dir(home: &Path) -> PathBuf { home.join(".claude/plugins/marketplaces/tracedecay") } -/// The deployed marketplace manifest — presence signals a plugin install. +/// The deployed marketplace manifest, presence signals a plugin install. fn plugin_marketplace_manifest_path(home: &Path) -> PathBuf { plugin_deploy_dir(home).join(".claude-plugin/marketplace.json") } @@ -779,7 +779,7 @@ fn plugin_perms_satisfied(installed: &[&str]) -> tracedecay_domain::errors::Resu } /// Coverage check against a concrete expected-tool list. An empty expected -/// list must not read as vacuously satisfied — only the wildcard rule can +/// list must not read as vacuously satisfied, only the wildcard rule can /// cover it. fn plugin_perms_covered(installed: &[&str], per_tool: &[String]) -> bool { installed.contains(&plugin_wildcard_perm().as_str()) @@ -808,7 +808,7 @@ const CLAUDE_MD_HISTORICAL_MARKERS: [&str; 3] = [ "No Explore Agents When Codegraph Is Available", ]; /// The one `## ` sub-heading historical blocks owned. A historical block range -/// extends across exactly this heading — never any arbitrary line containing +/// extends across exactly this heading, never any arbitrary line containing /// "tracedecay", which would wrongly absorb a user's own `## …tracedecay…` /// heading on uninstall. const CLAUDE_MD_HISTORICAL_OWNED_SUBHEADING: &str = @@ -941,7 +941,7 @@ fn doctor_check_plugin(dc: &mut DoctorCounters, home: &Path) { let manifest_path = plugin_marketplace_manifest_path(home); if !manifest_path.exists() { dc.warn(&format!( - "{} not found — run `tracedecay install` if you use Claude Code", + "{} not found, run `tracedecay install` if you use Claude Code", manifest_path.display() )); return; @@ -961,7 +961,7 @@ fn doctor_check_plugin(dc: &mut DoctorCounters, home: &Path) { match plugin_manifest.get("version").and_then(|v| v.as_str()) { Some(crate::PRODUCT_VERSION) => dc.pass("Deployed plugin version matches tracedecay"), Some(version) => dc.warn(&format!( - "Deployed plugin version {version} does not match tracedecay {} — run `tracedecay update-plugin`", + "Deployed plugin version {version} does not match tracedecay {}, run `tracedecay update-plugin`", crate::PRODUCT_VERSION )), None => dc.warn("Deployed plugin.json does not contain a version"), @@ -976,7 +976,7 @@ fn doctor_check_plugin(dc: &mut DoctorCounters, home: &Path) { dc.pass(&format!("Plugin {label} present")); } else { dc.fail(&format!( - "Plugin {label} missing in {} — run `tracedecay install`", + "Plugin {label} missing in {}, run `tracedecay install`", deploy_dir.display() )); } @@ -990,7 +990,7 @@ fn doctor_check_plugin(dc: &mut DoctorCounters, home: &Path) { dc.pass(&format!("Plugin {label} present")); } else { dc.fail(&format!( - "Plugin {label} missing in {} — run `tracedecay install`", + "Plugin {label} missing in {}, run `tracedecay install`", deploy_dir.display() )); } @@ -1012,7 +1012,7 @@ fn doctor_check_plugin(dc: &mut DoctorCounters, home: &Path) { }); if registered && !schema_complete { dc.fail(&format!( - "Marketplace entry in {} is missing installLocation/lastUpdated — repair it with Claude Code's native plugin command", + "Marketplace entry in {} is missing installLocation/lastUpdated, repair it with Claude Code's native plugin command", known_marketplaces_path(home).display() )); } else if registered { @@ -1022,7 +1022,7 @@ fn doctor_check_plugin(dc: &mut DoctorCounters, home: &Path) { )); } else { dc.warn(&format!( - "Marketplace not registered in {} — run the native Claude plugin marketplace command", + "Marketplace not registered in {}, run the native Claude plugin marketplace command", known_marketplaces_path(home).display() )); } @@ -1040,7 +1040,7 @@ fn doctor_check_plugin(dc: &mut DoctorCounters, home: &Path) { )); } else { dc.warn(&format!( - "Plugin {PLUGIN_IDENTIFIER} not enabled in settings.json — enable it with Claude Code's native plugin command" + "Plugin {PLUGIN_IDENTIFIER} not enabled in settings.json, enable it with Claude Code's native plugin command" )); } } @@ -1049,7 +1049,7 @@ fn doctor_check_plugin(dc: &mut DoctorCounters, home: &Path) { fn doctor_check_permissions_json(dc: &mut DoctorCounters, home: &Path) { let settings_path = home.join(".claude").join("settings.json"); if !settings_path.exists() { - dc.warn("~/.claude/settings.json not found — configure plugin permissions in Claude Code"); + dc.warn("~/.claude/settings.json not found, configure plugin permissions in Claude Code"); return; } let Some(settings) = std::fs::read_to_string(&settings_path) @@ -1068,7 +1068,7 @@ fn doctor_check_permissions_json(dc: &mut DoctorCounters, home: &Path) { // The plugin-namespace entries are the ones the plugin MCP server actually // matches against; without coverage every call to a tool prompts - // interactively and hard-fails headless/in subagents. Check these first — + // interactively and hard-fails headless/in subagents. Check these first, // this is the real adoption gate. Install/update add the one managed // wildcard while preserving the rest of Claude's host-owned settings. let wildcard = plugin_wildcard_perm(); @@ -1076,7 +1076,7 @@ fn doctor_check_permissions_json(dc: &mut DoctorCounters, home: &Path) { Ok(per_tool) => per_tool, Err(error) => { // An unreadable catalog is a composition failure, never "this host - // advertises no tools" — say so instead of reporting coverage of + // advertises no tools", say so instead of reporting coverage of // an empty set. dc.fail(&format!( "Could not read the advertised tool catalog, so tool permissions cannot be \ @@ -1091,13 +1091,13 @@ fn doctor_check_permissions_json(dc: &mut DoctorCounters, home: &Path) { )); } else if plugin_perms_covered(&installed, &per_tool) { dc.pass(&format!( - "All {} plugin tool permissions granted individually — the single allow rule \ + "All {} plugin tool permissions granted individually, the single allow rule \ \"{wildcard}\" would replace them", per_tool.len() )); } else { dc.fail(&format!( - "Plugin tool calls will prompt interactively — add the single allow rule \ + "Plugin tool calls will prompt interactively, add the single allow rule \ \"{wildcard}\" to `permissions.allow` in {} (or run `/permissions` in Claude Code \ and allow that rule); it covers every tracedecay plugin tool", settings_path.display() @@ -1125,7 +1125,7 @@ fn doctor_check_permissions_json(dc: &mut DoctorCounters, home: &Path) { )); } else { dc.info(&format!( - "{} legacy tool permission(s) not present (harmless — plugin namespace is authoritative)", + "{} legacy tool permission(s) not present (harmless, plugin namespace is authoritative)", missing.len() )); } @@ -1159,7 +1159,7 @@ fn doctor_check_local_config(dc: &mut DoctorCounters, project_path: &Path) { dc.pass("No tracedecay in local config"); } else { dc.warn(&format!( - "TraceDecay entries remain in local config ({}) — leave them or remove them manually; TraceDecay does not rewrite Claude config", + "TraceDecay entries remain in local config ({}), leave them or remove them manually; TraceDecay does not rewrite Claude config", tracedecay_paths.join(", ") )); } @@ -1190,11 +1190,11 @@ fn warn_missing_permissions(settings: &serde_json::Value) { .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect()) .unwrap_or_default(); - // Check the plugin namespace — the entries the plugin MCP server matches. + // Check the plugin namespace, the entries the plugin MCP server matches. // A machine mid-upgrade may carry legacy `mcp__tracedecay__*` entries but // lack coverage of the `mcp__plugin_tracedecay_graph__*` namespace, which // is exactly what causes per-call prompts, so that is the gap worth - // warning about — with the one-rule remedy, not a tool census. + // warning about, with the one-rule remedy, not a tool census. match plugin_perms_satisfied(&installed) { Ok(true) => {} Ok(false) => eprintln!( diff --git a/crates/tracedecay-agent-hosts/src/agents/claude/tests.rs b/crates/tracedecay-agent-hosts/src/agents/claude/tests.rs index 5bc2a7e4eb..bb4382ab1e 100644 --- a/crates/tracedecay-agent-hosts/src/agents/claude/tests.rs +++ b/crates/tracedecay-agent-hosts/src/agents/claude/tests.rs @@ -240,7 +240,7 @@ fn deploy_escapes_special_chars_in_binary_path() { let deploy_dir = deploy_plugin_bundle(home.path(), weird_bin).unwrap(); let hooks_raw = std::fs::read_to_string(deploy_dir.join("hooks/hooks.json")).unwrap(); - // Must parse — a raw replace would have produced invalid JSON here. + // Must parse, a raw replace would have produced invalid JSON here. let hooks: serde_json::Value = serde_json::from_str(&hooks_raw) .expect("hooks.json must stay valid JSON after binary-path substitution"); assert!( @@ -307,7 +307,7 @@ fn deploy_refuses_to_replace_non_tracedecay_dir() { /// The managed-block range must extend across only its own owned /// sub-heading, not a user's own `## …tracedecay…` heading placed after -/// the block — otherwise uninstall would swallow the user's section. +/// the block, otherwise uninstall would swallow the user's section. #[test] fn uninstall_preserves_user_tracedecay_heading_after_block() { let home = tempfile::tempdir().unwrap(); diff --git a/crates/tracedecay-agent-hosts/src/agents/cline.rs b/crates/tracedecay-agent-hosts/src/agents/cline.rs index 78908f8323..d7f0e9059b 100644 --- a/crates/tracedecay-agent-hosts/src/agents/cline.rs +++ b/crates/tracedecay-agent-hosts/src/agents/cline.rs @@ -156,7 +156,7 @@ fn doctor_check_settings(dc: &mut DoctorCounters, home: &Path) { let legacy_path = legacy_cline_mcp_settings_path(home); if settings_have_tracedecay(&legacy_path) { dc.warn(&format!( - "legacy Cline MCP registration found in {} — configure or remove it through Cline's supported flow", + "legacy Cline MCP registration found in {}, configure or remove it through Cline's supported flow", legacy_path.display() )); return; diff --git a/crates/tracedecay-agent-hosts/src/agents/codex.rs b/crates/tracedecay-agent-hosts/src/agents/codex.rs index 6d29c4388e..6141304f00 100644 --- a/crates/tracedecay-agent-hosts/src/agents/codex.rs +++ b/crates/tracedecay-agent-hosts/src/agents/codex.rs @@ -13,7 +13,7 @@ //! different: `codex plugin add` does not record `[hooks.state]` hashes and //! `/hooks` is interactive-only, so activation records trust for TraceDecay's //! own managed hooks ([`sync_codex_hook_trust`]) and deactivation prunes those -//! records again ([`prune_codex_hook_trust_records`]) — both inside the +//! records again ([`prune_codex_hook_trust_records`]), both inside the //! component transaction's rollback boundary. Trust is recorded only for hooks //! whose installed command is byte-for-byte a generated tracedecay command //! ([`codex_hook_command_invokes_tracedecay`]); anything else keeps the manual @@ -265,7 +265,7 @@ impl AgentIntegration for CodexIntegration { && codex_plugin_cached_install_dirs(&ctx.home).is_empty() { dc.warn( - "repo-local Codex bundles ship no lifecycle hooks — run `tracedecay install --agent codex` to add the personal plugin (session hooks, transcript ingest)", + "repo-local Codex bundles ship no lifecycle hooks, run `tracedecay install --agent codex` to add the personal plugin (session hooks, transcript ingest)", ); } } else { @@ -393,7 +393,7 @@ impl AgentIntegration for CodexIntegration { // `~/.codex/agents` is registration surface, not deployed component // assets: `host_component_registration_paths` declares every generated // export plus the ownership manifest for Core. Activation must refresh - // current exports and retire previous-bundle stale ones — otherwise + // current exports and retire previous-bundle stale ones, otherwise // Core install through the receipt-backed lifecycle never writes them // and never retires them (byte-for-byte rollback then fails). tracedecay_automation_runtime::automation::agent_targets::install_codex_managed_agents( @@ -552,7 +552,7 @@ fn codex_plugin_current_cached_install_dir(home: &Path) -> PathBuf { /// cache. `codex plugin add`/`remove` create or delete those files outside /// [`super::safe_write_text_file`], and without a recorded intent /// `restore_registration` treats the live cache as foreign drift (`StalePreview`) -/// and aborts before restoring any other registration path — including the +/// and aborts before restoring any other registration path, including the /// managed-agent ownership manifest that byte-for-byte rollback demands. fn record_codex_cached_plugin_registration_intents(home: &Path) -> Result<()> { let cache_dir = codex_plugin_current_cached_install_dir(home); @@ -805,7 +805,7 @@ impl CodexBundlePolicy { } } - /// Where Codex records trust for this bundle's hooks — `None` for scopes + /// Where Codex records trust for this bundle's hooks, `None` for scopes /// that ship no hooks and therefore have no trust surface. fn hook_trust_config_path(self, home: &Path) -> Option { self.include_hooks().then(|| codex_config_path(home)) @@ -886,8 +886,8 @@ fn write_codex_plugin_files( } /// Canonical rendered global Codex plugin inventory. The registration probe -/// inspects `.codex/plugins/tracedecay` — the directory the receipt-backed -/// first-party host-bundle catalog owns — and requires the managed lifecycle +/// inspects `.codex/plugins/tracedecay`, the directory the receipt-backed +/// first-party host-bundle catalog owns, and requires the managed lifecycle /// hooks to be present, so the catalog must deploy the same rendered content /// the installer produces instead of the raw templates (whose `hooks.json` /// is an empty scaffold rendered only at install time). @@ -1269,7 +1269,7 @@ fn codex_installed_hook_trust_entries(home: &Path) -> Result<(String, Vec hook-codex-session-start && +/// prefix match is unsafe. ` hook-codex-session-start && /// rm -rf ~` starts with our binary token yet smuggles an arbitrary command, so /// it would get silently auto-trusted. Requiring full equality with a generated /// command (`hook_command(bin, subcommand)` for each known subcommand) rejects @@ -1351,7 +1351,7 @@ fn sync_codex_hook_trust(home: &Path, tracedecay_bin: &str) -> Result Result Result CodexHookTrustState { - // A missing [hooks.state] table is just "nothing trusted yet" — treat it + // A missing [hooks.state] table is just "nothing trusted yet", treat it // as empty so one pipeline produces the missing list either way. let empty = toml::value::Table::new(); let state = config @@ -2176,7 +2176,7 @@ fn doctor_check_plugin(dc: &mut DoctorCounters, home: &Path) { let manifest_path = plugin_dir.join(".codex-plugin/plugin.json"); if !manifest_path.exists() { dc.warn(&format!( - "{} not found — run `tracedecay install --agent codex` or `tracedecay update-plugin` to install the Codex plugin bundle", + "{} not found, run `tracedecay install --agent codex` or `tracedecay update-plugin` to install the Codex plugin bundle", manifest_path.display() )); return; @@ -2195,7 +2195,7 @@ fn doctor_check_plugin(dc: &mut DoctorCounters, home: &Path) { /// Codex's own readback of "installed and enabled": the /// `[plugins."tracedecay@…"] enabled = true` activation record in -/// `config.toml` — the state `codex plugin list` reports. Staged source and a +/// `config.toml`, the state `codex plugin list` reports. Staged source and a /// marketplace entry alone never load the plugin's MCP server, skills, or /// hooks, so their presence must not read as an installed integration. fn doctor_check_native_activation(dc: &mut DoctorCounters, home: &Path) { @@ -2207,7 +2207,7 @@ fn doctor_check_native_activation(dc: &mut DoctorCounters, home: &Path) { config_path.display() )), Ok(false) => dc.fail(&format!( - "Codex reports tracedecay@{marketplace_name} not installed — {} has no \ + "Codex reports tracedecay@{marketplace_name} not installed. {} has no \ `[plugins.\"tracedecay@{marketplace_name}\"] enabled = true`, so the MCP server, \ skills, and hooks never load. Run `tracedecay install --agent codex` (drives \ `codex plugin add tracedecay@{marketplace_name}` and auto-trusts the managed hooks)", @@ -2285,7 +2285,7 @@ fn doctor_check_marketplace_entry( )); } else { dc.warn(&format!( - "Codex {label} missing tracedecay in {} — run `{install_command}`", + "Codex {label} missing tracedecay in {}, run `{install_command}`", marketplace_path.display() )); } @@ -2313,7 +2313,7 @@ fn doctor_check_plugin_dir( match manifest.get("version").and_then(|value| value.as_str()) { Some(crate::PRODUCT_VERSION) => dc.pass("Codex plugin version matches tracedecay"), Some(version) => dc.warn(&format!( - "Codex plugin version {version} does not match tracedecay {} — run `tracedecay update-plugin`", + "Codex plugin version {version} does not match tracedecay {}, run `tracedecay update-plugin`", crate::PRODUCT_VERSION )), None => dc.warn("Codex plugin manifest does not contain a version"), @@ -2328,7 +2328,7 @@ fn doctor_check_plugin_dir( )); } else { dc.fail(&format!( - "Codex plugin MCP server missing or has stale timeouts in {} — run `tracedecay update-plugin`", + "Codex plugin MCP server missing or has stale timeouts in {}, run `tracedecay update-plugin`", mcp_path.display() )); } @@ -2344,7 +2344,7 @@ fn doctor_check_plugin_dir( } } else if hooks_path.exists() { dc.warn(&format!( - "repo-local Codex bundle unexpectedly ships lifecycle hooks in {} — run `tracedecay install --local --agent codex` to refresh it", + "repo-local Codex bundle unexpectedly ships lifecycle hooks in {}, run `tracedecay install --local --agent codex` to refresh it", hooks_path.display() )); } @@ -2358,7 +2358,7 @@ fn doctor_check_hooks( ) { if !hooks_path.exists() { dc.warn(&format!( - "{} not found — run `tracedecay install --agent codex` to add lifecycle hooks", + "{} not found, run `tracedecay install --agent codex` to add lifecycle hooks", hooks_path.display() )); return; @@ -2372,7 +2372,7 @@ fn doctor_check_hooks( .collect(); if !missing.is_empty() { dc.warn(&format!( - "tracedecay hook(s) missing for {} in {} — run `tracedecay install --agent codex`", + "tracedecay hook(s) missing for {} in {}, run `tracedecay install --agent codex`", missing.join(", "), hooks_path.display(), )); @@ -2411,18 +2411,18 @@ fn doctor_check_hooks( config_path.display() )), CodexHookTrustState::Missing(missing) => dc.info(&format!( - "Codex skips untrusted command hooks — missing trust for {} in {}; use `/hooks` in Codex to trust the tracedecay hooks", + "Codex skips untrusted command hooks, missing trust for {} in {}; use `/hooks` in Codex to trust the tracedecay hooks", missing.join(", "), config_path.display() )), CodexHookTrustState::Modified(modified) => dc.warn(&format!( - "Codex hook trust is stale for {} in {} — the hook content changed since it was trusted, so Codex now skips it; use `/hooks` in Codex to re-trust the tracedecay hooks", + "Codex hook trust is stale for {} in {}, the hook content changed since it was trusted, so Codex now skips it; use `/hooks` in Codex to re-trust the tracedecay hooks", modified.join(", "), config_path.display() )), }, Err(_) => dc.info( - "Codex skips untrusted command hooks — use `/hooks` in Codex to trust the tracedecay hooks", + "Codex skips untrusted command hooks, use `/hooks` in Codex to trust the tracedecay hooks", ), } } @@ -2431,7 +2431,7 @@ fn doctor_check_hooks( /// fact-store injection is active, so the model does not receive two parallel /// memory systems built from the same sessions. This is advisory only: the /// user's `config.toml` is never edited, and tracedecay never writes into -/// `~/.codex/memories/` — the holographic fact store stays the single source +/// `~/.codex/memories/`, the holographic fact store stays the single source /// of truth and delivery is rendered prompt context only. fn doctor_suggest_native_memories_off(dc: &mut DoctorCounters, home: &Path) { if !crate::hooks::memory_inject::memory_injection_enabled() { diff --git a/crates/tracedecay-agent-hosts/src/agents/codex/mcp_registry.rs b/crates/tracedecay-agent-hosts/src/agents/codex/mcp_registry.rs index a4b1d56611..2928432cea 100644 --- a/crates/tracedecay-agent-hosts/src/agents/codex/mcp_registry.rs +++ b/crates/tracedecay-agent-hosts/src/agents/codex/mcp_registry.rs @@ -24,7 +24,7 @@ //! # Why this exists at all: the MCP-only install mode //! //! In the plugin-bearing install (the default component set, `Core` + -//! `ContextMcp`), the MCP route is *inside* the bundle — the rendered +//! `ContextMcp`), the MCP route is *inside* the bundle, the rendered //! `.mcp.json` under `.codex/plugins/tracedecay/` declares the `graph` server, //! and Codex loads it only once the operator has installed and enabled the //! plugin. TraceDecay must not also register a second, standalone server there: @@ -34,7 +34,7 @@ //! An **MCP-only** component set (`ContextMcp` and/or `OperatorMcp` selected //! *without* `Core`) is the case that had no working registration at all before //! this module. That set deploys the plugin's `.mcp.json` and nothing else, and -//! since the plugin is never installed, the file is inert — the operator ends +//! since the plugin is never installed, the file is inert, the operator ends //! up with a staged file and no MCP server. `codex mcp add` is exactly the //! host-owned command that closes that gap without touching the plugin //! lifecycle, so it is driven for that set and only for that set. See @@ -48,7 +48,7 @@ //! when deciding whether any TraceDecay registration remains. The registration //! transaction is therefore told about exactly that file after the command //! runs, so its existing rollback authority can restore the pre-command -//! document. TraceDecay still never *writes* it — Codex's own CLI does. +//! document. TraceDecay still never *writes* it. Codex's own CLI does. //! //! Because that one file also carries Codex-owned activation and hook-trust //! state, a region guard runs on both sides of the invocation: if the host @@ -93,8 +93,8 @@ const CODEX_HOOKS_KEY: &str = "hooks"; /// Whether this component set is the MCP-only (non-plugin) install. /// /// True exactly when an MCP component is selected and `Core` is not. `Core` -/// carries the Codex plugin bundle — its hooks, skills, and the `.mcp.json` -/// Codex reads once the plugin is installed — so a `Core`-bearing set already +/// carries the Codex plugin bundle, its hooks, skills, and the `.mcp.json` +/// Codex reads once the plugin is installed, so a `Core`-bearing set already /// has an MCP route and must not gain a second, standalone one. Without `Core` /// the staged `.mcp.json` is never loaded by anything, and the host registry is /// the only way the server actually exists. @@ -121,7 +121,7 @@ pub(super) fn require_codex_cli() -> Result { /// `HOME` without mutating the process environment. /// /// The launch contract (`--env` pairs, then `--`, then command and arguments) -/// is built from [`CODEX_MCP_SERVER_ENV`] and [`CODEX_MCP_SERVER_ARGS`] — the +/// is built from [`CODEX_MCP_SERVER_ENV`] and [`CODEX_MCP_SERVER_ARGS`], the /// same constants the plugin bundle's `.mcp.json` writer consumes, so the two /// spellings of the same server cannot drift apart. /// @@ -231,7 +231,7 @@ fn preserved_regions(path: &Path) -> Result { /// Read the config once, returning both its exact bytes (for the rollback /// record) and its preserved regions (for the guard). A missing file is a valid -/// observation — Codex creates the config on first `mcp add`. +/// observation. Codex creates the config on first `mcp add`. fn read_config_observation(path: &Path) -> Result<(Option>, CodexPreservedRegionsV1)> { let bytes = match std::fs::read(path) { Ok(bytes) => bytes, diff --git a/crates/tracedecay-agent-hosts/src/agents/codex/plugin_registry.rs b/crates/tracedecay-agent-hosts/src/agents/codex/plugin_registry.rs index 5fa0bf49ae..f5c93e8337 100644 --- a/crates/tracedecay-agent-hosts/src/agents/codex/plugin_registry.rs +++ b/crates/tracedecay-agent-hosts/src/agents/codex/plugin_registry.rs @@ -16,7 +16,7 @@ //! --json` exits 0 without a TTY, writes `[plugins."tracedecay@personal"] //! enabled = true` into `~/.codex/config.toml`, and copies the staged source //! into `~/.codex/plugins/cache/personal/tracedecay/`. It does **not** -//! write `[hooks.state]` — TraceDecay records trust for its own managed hooks +//! write `[hooks.state]`. TraceDecay records trust for its own managed hooks //! separately (see [`super::sync_codex_hook_trust`] and its safety valve). //! //! That is the same host-capability shape already adopted for `codex mcp add` diff --git a/crates/tracedecay-agent-hosts/src/agents/codex/tests.rs b/crates/tracedecay-agent-hosts/src/agents/codex/tests.rs index f954aeb1b0..b146a5e75e 100644 --- a/crates/tracedecay-agent-hosts/src/agents/codex/tests.rs +++ b/crates/tracedecay-agent-hosts/src/agents/codex/tests.rs @@ -605,7 +605,7 @@ fn codex_hook_command_invokes_tracedecay_is_a_safety_valve() { /// The install-output follow-up must stand until explicit, current trust is /// recorded for every managed hook (normally by the auto-trust sync, or by -/// `/hooks` when the safety valve skipped a hook) — and clear the moment it is. +/// `/hooks` when the safety valve skipped a hook), and clear the moment it is. #[test] fn codex_hook_trust_followup_clears_only_after_explicit_current_trust() { let home = tempfile::tempdir().expect("tempdir"); @@ -960,7 +960,7 @@ fn redeploy_preserves_foreign_discovery_and_support_bytes() { } /// Preflight still reports that the cache is not yet active; activation itself -/// is no longer an interactive deferral — Codex CLI 0.147 drives `plugin add`. +/// is no longer an interactive deferral. Codex CLI 0.147 drives `plugin add`. #[test] fn codex_preflight_reports_inactive_cache_without_interactive_guidance() { let home = tempfile::tempdir().unwrap(); @@ -983,7 +983,7 @@ fn codex_preflight_reports_inactive_cache_without_interactive_guidance() { fn prepare_stages_the_source_and_returns_ready_for_cli_activation() { let home = tempfile::tempdir().unwrap(); // Pre-existing user config: preparation runs before the component - // transaction stages `config.toml`, so it must not write there — hook + // transaction stages `config.toml`, so it must not write there, hook // trust is recorded by activation, inside the rollback boundary. let config_path = codex_config_path(home.path()); std::fs::create_dir_all(config_path.parent().unwrap()).unwrap(); diff --git a/crates/tracedecay-agent-hosts/src/agents/context_scout.rs b/crates/tracedecay-agent-hosts/src/agents/context_scout.rs index ea0e384724..cc3db60238 100644 --- a/crates/tracedecay-agent-hosts/src/agents/context_scout.rs +++ b/crates/tracedecay-agent-hosts/src/agents/context_scout.rs @@ -564,8 +564,8 @@ impl ContextScoutModelExecutionV1 { /// Returns the measured input token count so callers reuse this single /// tokenization. Encoding the serialized request is the most expensive - /// step on a deadline-bounded proposal — a cold BPE table build alone can - /// spend the whole budget — so it must happen exactly once per request. + /// step on a deadline-bounded proposal, a cold BPE table build alone can + /// spend the whole budget, so it must happen exactly once per request. pub fn validate_input( &self, request: &ContextScoutModelRequestV1, @@ -885,7 +885,7 @@ pub(super) fn serialized_token_count(_value: &impl Serialize) -> Option { /// /// The first `serialized_token_count` anywhere in the process builds a /// multi-megabyte BPE table. Measured on one contended Linux core that build -/// runs 0.9s, and 4.4s at a tenth of a core — while a Scout proposal gives +/// runs 0.9s, and 4.4s at a tenth of a core, while a Scout proposal gives /// itself one second for everything, tokenizer included. Paying the build /// inside `propose` therefore spends the whole budget before the backend is /// ever raced, and the proposal reports `DeadlineExceeded` over a denial or a @@ -1460,10 +1460,6 @@ impl ContextScoutDurableRuntimeV1 { } } - pub fn is_current(&self, work: ContextScoutWorkV1) -> bool { - self.coalescer.is_current(work) - } - pub(crate) fn restore_startup( &mut self, startup: &ContextScoutDurableStartupOutcomeV1, @@ -2010,7 +2006,7 @@ mod tests { // Model-assisted selection is only reachable in a `token-counting` build. // Without the BPE tokenizer `serialized_token_count` yields no measurement, // and an unmeasurable request can never be *proven* within the input - // budget — so `validate_input` refuses it as `TokenBudgetExceeded` before + // budget, so `validate_input` refuses it as `TokenBudgetExceeded` before // any assistant is consulted. That refusal is the correct production // behaviour (a build that cannot count tokens must not ship unbounded // input to a model), which means the *typed model outcome* these tests diff --git a/crates/tracedecay-agent-hosts/src/agents/context_scout/store.rs b/crates/tracedecay-agent-hosts/src/agents/context_scout/store.rs index e1746cbd7b..e876f4b89b 100644 --- a/crates/tracedecay-agent-hosts/src/agents/context_scout/store.rs +++ b/crates/tracedecay-agent-hosts/src/agents/context_scout/store.rs @@ -960,7 +960,7 @@ impl ProjectContextScoutDurableStoreV1 { // depend on it unconditionally: a concurrently opening sibling route // holds this project's single writer, so the lane arrives late or the // idle lease expires under it, and the read-shaped startup reports - // `Unavailable` — a durable-state verdict — for lane contention. + // `Unavailable`, a durable-state verdict, for lane contention. // Project open then refuses a route whose durable state is intact. // Decode through the read path first and answer from it whenever the // state already reconciles; the atomic write below stays exactly as it @@ -982,7 +982,7 @@ impl ProjectContextScoutDurableStoreV1 { } /// Decodes the durable state without taking the writer lane. `None` means - /// "read it under the write path instead" — an unreadable, oversized, or + /// "read it under the write path instead", an unreadable, oversized, or /// invalid record is not something this read may decide alone. async fn load_state(&self) -> Option { let encoded = self.database.get_metadata(STORE_KEY_V1).await.ok()?; diff --git a/crates/tracedecay-agent-hosts/src/agents/copilot.rs b/crates/tracedecay-agent-hosts/src/agents/copilot.rs index 7391fb1dc7..ec5b2864c6 100644 --- a/crates/tracedecay-agent-hosts/src/agents/copilot.rs +++ b/crates/tracedecay-agent-hosts/src/agents/copilot.rs @@ -9,7 +9,7 @@ //! commands and never merges that file itself: the host owns the registry, //! and emulating its writes is exactly what the host-capability doctrine //! forbids. The `copilot` binary is therefore a **hard requirement** for this -//! half of the lifecycle, with no config-editing fallback — a half-emulated +//! half of the lifecycle, with no config-editing fallback, a half-emulated //! registration is indistinguishable on disk from a corrupt one. //! * **VS Code's `settings.json`** (`mcp.servers.tracedecay`, plus the //! Insiders profile) has **no host CLI at all**. VS Code exposes no @@ -68,7 +68,7 @@ impl AgentIntegration for CopilotIntegration { /// `~/.copilot/mcp-config.json` (written by `copilot mcp add`) and the /// VS Code user `settings.json`. There is no project-local surface the /// host reads, so offering a local install would mean hand-writing files - /// the adopted CLI lifecycle exists to eliminate — same ruling as Gemini. + /// the adopted CLI lifecycle exists to eliminate, same ruling as Gemini. fn supports_local_install(&self) -> bool { false } @@ -242,7 +242,7 @@ fn vscode_mcp_servers_has_tracedecay(settings_path: &Path) -> bool { // Registration paths // --------------------------------------------------------------------------- -/// VS Code user settings — the TraceDecay-written half. No host CLI writes +/// VS Code user settings, the TraceDecay-written half. No host CLI writes /// this file; see the module documentation. fn vscode_settings_path(home: &Path) -> PathBuf { super::vscode_data_dir(home).join("User/settings.json") @@ -448,7 +448,7 @@ fn doctor_check_vscode_settings(dc: &mut DoctorCounters, vscode_dir: &Path, labe .and_then(|servers| servers.get("tracedecay")) }, &format!( - "{} not found — run `tracedecay install --agent copilot` if you use GitHub Copilot in {label}", + "{} not found, run `tracedecay install --agent copilot` if you use GitHub Copilot in {label}", settings_path.display() ), ); @@ -467,7 +467,7 @@ fn doctor_check_cli_settings(dc: &mut DoctorCounters, home: &Path) { .and_then(|servers| servers.get("tracedecay")) }, &format!( - "{} not found — run `tracedecay install --agent copilot` if you use Copilot CLI", + "{} not found, run `tracedecay install --agent copilot` if you use Copilot CLI", settings_path.display() ), ); @@ -488,7 +488,7 @@ fn doctor_check_mcp_document( let settings = load(settings_path); let Some(server) = lookup(&settings).and_then(serde_json::Value::as_object) else { dc.fail(&format!( - "MCP server NOT registered in {} — run `tracedecay install --agent copilot`", + "MCP server NOT registered in {}, run `tracedecay install --agent copilot`", settings_path.display() )); return; @@ -501,7 +501,7 @@ fn doctor_check_mcp_document( if server_args_are_current(server) { dc.pass("MCP server args include \"serve\""); } else { - dc.fail("MCP server args missing \"serve\" — run `tracedecay install --agent copilot`"); + dc.fail("MCP server args missing \"serve\", run `tracedecay install --agent copilot`"); } } diff --git a/crates/tracedecay-agent-hosts/src/agents/cursor.rs b/crates/tracedecay-agent-hosts/src/agents/cursor.rs index 9a7ed80427..38708661bc 100644 --- a/crates/tracedecay-agent-hosts/src/agents/cursor.rs +++ b/crates/tracedecay-agent-hosts/src/agents/cursor.rs @@ -217,7 +217,7 @@ fn cursor_plugin_manifest_path(home: &Path) -> PathBuf { /// Deploy directory of the native diagnostics extension, versioned exactly /// like every VS Code-family extension install (`publisher.name-version`) and -/// stamped with the real release version — a `0.0.0` directory next to +/// stamped with the real release version, a `0.0.0` directory next to /// otherwise-versioned components was an unstampable literal. pub(super) fn cursor_native_extension_relative_dir() -> String { format!( @@ -252,7 +252,7 @@ fn cursor_native_extension_registration(home: &Path) -> HostBundleRegistrationSt } } -/// Doctor coverage for the deployed native diagnostics extension — the one +/// Doctor coverage for the deployed native diagnostics extension, the one /// Cursor component the plugin-dir checks never touched, so a missing or /// half-deployed extension was invisible. A wholly absent extension is /// informational (the plugin-only install surface never claims it); a @@ -270,14 +270,14 @@ fn doctor_check_native_extension(dc: &mut DoctorCounters, home: &Path) { let stale = stale_native_extension_dirs(home); if stale.is_empty() { dc.info(&format!( - "Cursor native diagnostics extension {} not deployed ({}) — run \ + "Cursor native diagnostics extension {} not deployed ({}), run \ `tracedecay install --agent cursor`", crate::PRODUCT_VERSION, install_dir.display() )); } else { dc.warn(&format!( - "Cursor native diagnostics extension is stale ({}) while {} is current — \ + "Cursor native diagnostics extension is stale ({}) while {} is current. \ run `tracedecay install --agent cursor` to redeploy", stale.join(", "), crate::PRODUCT_VERSION @@ -286,11 +286,11 @@ fn doctor_check_native_extension(dc: &mut DoctorCounters, home: &Path) { } HostBundleRegistrationStateV1::Repairable => dc.warn(&format!( "Cursor native diagnostics extension at {} is incomplete (dist/extension.js \ - missing) — run `tracedecay install --agent cursor`", + missing), run `tracedecay install --agent cursor`", install_dir.display() )), HostBundleRegistrationStateV1::Corrupt => dc.fail(&format!( - "package.json at {} is not the tracedecay cursor-native extension — inspect and \ + "package.json at {} is not the tracedecay cursor-native extension, inspect and \ remove it, then run `tracedecay install --agent cursor`", install_dir.display() )), @@ -466,7 +466,7 @@ fn remove_cursor_plugin_install(install_dir: &Path) -> Result<()> { // The directory is tracedecay-owned. Sweep every skill dir the *current* // bundle no longer ships (retired dispatcher/workflow/memory skills), then // remove the managed skill overlay. Deriving the keep-set from the live - // bundle means a newly retired skill is swept automatically — no + // bundle means a newly retired skill is swept automatically, no // hand-maintained legacy list to fall out of date. User-added files // outside `skills/` (and any non-tracedecay skill dir) are preserved. sweep_retired_bundle_skill_dirs(install_dir)?; @@ -716,7 +716,7 @@ fn legacy_project_cursor_has_tracedecay(cursor_dir: &Path) -> bool { /// and the steering rule into `/.cursor/`; the user-level plugin /// owns all three surfaces now. This is the project-level counterpart of the /// user-level plugin-dir clean replace: detection-gated so projects without -/// legacy artifacts are untouched, and only tracedecay-owned entries are removed — +/// legacy artifacts are untouched, and only tracedecay-owned entries are removed, /// user-authored config (other MCP servers, custom hooks and rules, and /// `permissions.json` allowlists, which the plugin README still recommends /// per-repo) is preserved. @@ -762,7 +762,7 @@ fn sweep_legacy_project_artifacts(project_path: &Path) -> Result<()> { } /// The project directory a cwd-based legacy sweep should target, or `None` -/// when the cwd *is* the home directory — there `.cursor/` is Cursor's +/// when the cwd *is* the home directory, there `.cursor/` is Cursor's /// user-level config tree, not a project workspace. fn cwd_sweep_target(cwd: PathBuf, home: &Path) -> Option { let canonical = |path: &Path| path.canonicalize().unwrap_or_else(|_| path.to_path_buf()); @@ -886,7 +886,7 @@ fn doctor_check_plugin(dc: &mut DoctorCounters, home: &Path) { let manifest_path = cursor_plugin_manifest_path(home); if !manifest_path.exists() { dc.warn(&format!( - "{} not found — run `tracedecay install --agent cursor` if you use Cursor", + "{} not found, run `tracedecay install --agent cursor` if you use Cursor", manifest_path.display() )); if legacy_mcp_has_tracedecay(&home.join(".cursor/mcp.json")) { @@ -925,7 +925,7 @@ fn doctor_check_plugin(dc: &mut DoctorCounters, home: &Path) { fn doctor_check_plugin_mcp(dc: &mut DoctorCounters, mcp_path: &Path) { if !mcp_path.exists() { dc.warn(&format!( - "{} not found — run `tracedecay install --agent cursor`", + "{} not found, run `tracedecay install --agent cursor`", mcp_path.display() )); return; @@ -945,7 +945,7 @@ fn doctor_check_plugin_mcp(dc: &mut DoctorCounters, mcp_path: &Path) { )); } else { dc.fail(&format!( - "Cursor plugin MCP config is incomplete in {} — run `tracedecay install --agent cursor`", + "Cursor plugin MCP config is incomplete in {}, run `tracedecay install --agent cursor`", mcp_path.display() )); } @@ -984,7 +984,7 @@ fn cursor_plugin_hook_expectations() -> Vec<(String, String)> { fn doctor_check_plugin_hooks(dc: &mut DoctorCounters, hooks_path: &Path) { if !hooks_path.exists() { dc.warn(&format!( - "{} not found — run `tracedecay install --agent cursor`", + "{} not found, run `tracedecay install --agent cursor`", hooks_path.display() )); return; @@ -1017,7 +1017,7 @@ fn doctor_check_plugin_hooks(dc: &mut DoctorCounters, hooks_path: &Path) { )); } else { dc.fail(&format!( - "Cursor plugin hook(s) missing for {} — run `tracedecay install --agent cursor`", + "Cursor plugin hook(s) missing for {}, run `tracedecay install --agent cursor`", missing.join(", ") )); } @@ -1118,7 +1118,7 @@ fn report_cursor_session_ingest<'a>( dc.warn(&format!( "Cursor transcript ingest looks stalled: a transcript has {} un-ingested \ byte(s) ({} byte(s) total across {} transcript(s)), exceeding the {} byte \ - per-transcript hook catch-up cap — it will not drain automatically and \ + per-transcript hook catch-up cap, it will not drain automatically and \ session recall is missing those turns. Run `tracedecay sessions import \ --project-path {}` to schedule bounded convergence", health.max_transcript_pending_bytes, @@ -1167,11 +1167,11 @@ fn cursor_plugin_rule_doctor_state(rule_path: &Path) -> CursorPluginRuleDoctorSt fn doctor_check_plugin_rule(dc: &mut DoctorCounters, rule_path: &Path) { match cursor_plugin_rule_doctor_state(rule_path) { CursorPluginRuleDoctorState::Missing => dc.warn(&format!( - "{} not found — run `tracedecay install --agent cursor`", + "{} not found, run `tracedecay install --agent cursor`", rule_path.display() )), CursorPluginRuleDoctorState::Unreadable => dc.fail(&format!( - "Cursor plugin tracedecay rule is unreadable in {} — run `tracedecay install --agent cursor`", + "Cursor plugin tracedecay rule is unreadable in {}, run `tracedecay install --agent cursor`", rule_path.display() )), CursorPluginRuleDoctorState::Active => dc.pass(&format!( @@ -1179,7 +1179,7 @@ fn doctor_check_plugin_rule(dc: &mut DoctorCounters, rule_path: &Path) { rule_path.display() )), CursorPluginRuleDoctorState::Incomplete => dc.fail(&format!( - "Cursor plugin tracedecay rule is incomplete in {} — run `tracedecay install --agent cursor`", + "Cursor plugin tracedecay rule is incomplete in {}, run `tracedecay install --agent cursor`", rule_path.display() )), } @@ -1275,7 +1275,7 @@ mod tests { .exists(), "a representative native slash command should be embedded" ); - // Cursor no longer ships the `tracedecay-*` dispatcher *skills* — those + // Cursor no longer ships the `tracedecay-*` dispatcher *skills*, those // slugs are native commands now. assert!( !install_dir @@ -1589,9 +1589,9 @@ mod tests { ); } - /// A live pre-receipt Cursor bundle — deployed by a release that predates + /// A live pre-receipt Cursor bundle, deployed by a release that predates /// host-bundle receipts, stamped with an older product version and binary - /// path, and recorded by no receipt — must be taken over by the production + /// path, and recorded by no receipt, must be taken over by the production /// component transaction. `Install` (the operator's /// `install --agent cursor`) adopts it, and `Update` (`update-plugin`) /// restamps it to the running binary, instead of refusing with a @@ -1907,7 +1907,7 @@ mod tests { } /// The skill index injected into Cursor `sessionStart` context must match - /// the *model-invocable* skills shipped in the bundle — slash dispatchers + /// the *model-invocable* skills shipped in the bundle, slash dispatchers /// (`disable-model-invocation: true`) are explicit-invoke-only and would /// be noise in steering context. #[test] @@ -2242,7 +2242,7 @@ mod tests { } /// A project whose `.cursor/` only holds user-authored config (no legacy - /// tracedecay artifacts) must come through the sweep byte-identical — no + /// tracedecay artifacts) must come through the sweep byte-identical, no /// rewrites, no backups, no deletions. #[test] fn sweep_is_noop_without_legacy_tracedecay_artifacts() { diff --git a/crates/tracedecay-agent-hosts/src/agents/cursor_diagnostics.rs b/crates/tracedecay-agent-hosts/src/agents/cursor_diagnostics.rs index fef234fa41..3c69490432 100644 --- a/crates/tracedecay-agent-hosts/src/agents/cursor_diagnostics.rs +++ b/crates/tracedecay-agent-hosts/src/agents/cursor_diagnostics.rs @@ -161,7 +161,7 @@ pub(crate) fn report_cursor_mcp_log_findings(dc: &mut DoctorCounters, home: &Pat } if !findings.scanned_any_log { // No Cursor MCP logs on this machine (different platform layout, or - // Cursor has not run) — nothing to report. + // Cursor has not run), nothing to report. return; } if !findings.has_findings() { @@ -177,7 +177,7 @@ pub(crate) fn report_cursor_mcp_log_findings(dc: &mut DoctorCounters, home: &Pat } if findings.connection_failures > 0 { dc.warn(&format!( - "{} failed tracedecay MCP connection(s) in recent Cursor logs — Cursor never \ + "{} failed tracedecay MCP connection(s) in recent Cursor logs. Cursor never \ retries a failed MCP server, so affected sessions report \"Timed out waiting \ for connection\" on every tool call", findings.connection_failures @@ -202,7 +202,7 @@ pub(crate) fn report_cursor_mcp_log_findings(dc: &mut DoctorCounters, home: &Pat } dc.info( " After fixing the cause, toggle the tracedecay MCP server in Cursor Settings → MCP \ - or reload the Cursor window — Cursor does not retry a failed MCP scope on its own.", + or reload the Cursor window. Cursor does not retry a failed MCP scope on its own.", ); for log in findings.affected_logs.iter().take(3) { dc.info(&format!(" log: {}", log.display())); @@ -223,7 +223,7 @@ pub(crate) fn plugin_version_staleness( } Some(format!( "Cursor plugin bundle was rendered by tracedecay {plugin_version} but this binary is \ - {binary_version} — run `tracedecay update-plugin`, then reload Cursor" + {binary_version}, run `tracedecay update-plugin`, then reload Cursor" )) } diff --git a/crates/tracedecay-agent-hosts/src/agents/devin.rs b/crates/tracedecay-agent-hosts/src/agents/devin.rs index 847436849e..31c8ee8f98 100644 --- a/crates/tracedecay-agent-hosts/src/agents/devin.rs +++ b/crates/tracedecay-agent-hosts/src/agents/devin.rs @@ -249,7 +249,7 @@ fn doctor_check_devin_registration( ) { if !config_path.exists() { dc.warn(&format!( - "{} not found — run `tracedecay install --agent {}` if you use {}", + "{} not found, run `tracedecay install --agent {}` if you use {}", config_path.display(), labels.agent_id, labels.product diff --git a/crates/tracedecay-agent-hosts/src/agents/gemini.rs b/crates/tracedecay-agent-hosts/src/agents/gemini.rs index 44fc5d55e5..7b8f0053e6 100644 --- a/crates/tracedecay-agent-hosts/src/agents/gemini.rs +++ b/crates/tracedecay-agent-hosts/src/agents/gemini.rs @@ -1,13 +1,13 @@ //! Gemini CLI agent integration. //! -//! Gemini CLI has a first-party extension lifecycle — `gemini extensions -//! install `, `uninstall `, `list`, `update ` — and an +//! Gemini CLI has a first-party extension lifecycle, `gemini extensions +//! install `, `uninstall `, `list`, `update `, and an //! extension natively bundles everything this integration needs: the MCP //! server entry, a context file, and commands. TraceDecay therefore **adopts //! that lifecycle** instead of configuring Gemini by hand: //! //! 1. TraceDecay stages an extension source it owns outright under -//! `~/.gemini/tracedecay-extension/` — a `gemini-extension.json` manifest +//! `~/.gemini/tracedecay-extension/`, a `gemini-extension.json` manifest //! naming the tracedecay MCP server (`args: ["serve"]`, `trust: true`, the //! resolved binary substituted through a placeholder) plus the extension's //! own `GEMINI.md` context file. @@ -17,7 +17,7 @@ //! What this integration deliberately no longer does is merge //! `~/.gemini/settings.json` or splice a managed block into //! `~/.gemini/GEMINI.md`. The extension supplies both, so writing them as well -//! would register two tracedecay servers and duplicate the rules — and +//! would register two tracedecay servers and duplicate the rules, and //! emulating host-owned registration next to a CLI that owns it is exactly //! what the host-capability doctrine forbids. The `gemini` binary is a hard //! requirement for the lifecycle for the same reason: there is no @@ -27,7 +27,7 @@ //! entry in `~/.gemini/settings.json`, it would now report a defect for the //! correct state. Its checks below were re-pointed at what is actually true //! under the extension model: the staged source, the host's installed -//! extension copy, and — when the binary is present — Gemini's own +//! extension copy, and, when the binary is present, Gemini's own //! `gemini extensions list`. A settings entry is now reported as *legacy //! residue*, not as the required registration. @@ -74,7 +74,7 @@ impl AgentIntegration for GeminiIntegration { /// and is installed by running the host CLI *inside that workspace*, but /// `host_cli::run_host_cli` admits the profile home as the child working /// directory, so this integration cannot target an arbitrary project yet. - /// The honest answer is "not supported" — the alternative would be + /// The honest answer is "not supported", the alternative would be /// hand-writing `/.gemini/settings.json`, which is the emulation /// the adopted lifecycle exists to eliminate. fn supports_local_install(&self) -> bool { @@ -83,7 +83,7 @@ impl AgentIntegration for GeminiIntegration { /// Read-only readiness: has Gemini already adopted an extension matching /// what this version would stage? Nothing is written here, and the host - /// CLI is not required — an absent binary only becomes a hard failure once + /// CLI is not required, an absent binary only becomes a hard failure once /// a lifecycle actually needs to run. fn preflight_non_interactive_install( &self, @@ -139,7 +139,7 @@ impl AgentIntegration for GeminiIntegration { /// bakes the crate version and the resolved binary path). /// /// Gemini owns the installed copy, so refreshing the source alone cannot - /// honestly report an updated extension — the adoption step is reported as + /// honestly report an updated extension, the adoption step is reported as /// a deferred host action instead of silently claimed. fn update_plugin(&self, ctx: &InstallContext) -> Result { if !staged_manifest_path(&ctx.home).exists() { @@ -294,14 +294,14 @@ fn doctor_check_staged_extension(dc: &mut DoctorCounters, home: &Path) { let manifest_path = staged_manifest_path(home); if !manifest_path.exists() { dc.warn(&format!( - "{} not found — run `tracedecay install --agent gemini` if you use Gemini CLI", + "{} not found, run `tracedecay install --agent gemini` if you use Gemini CLI", manifest_path.display() )); return; } if !stage_dir_is_tracedecay(&stage_dir) { dc.fail(&format!( - "{} does not name the tracedecay extension — it is not a TraceDecay-owned source; \ + "{} does not name the tracedecay extension, it is not a TraceDecay-owned source; \ move it aside and run `tracedecay install --agent gemini`", manifest_path.display() )); @@ -316,7 +316,7 @@ fn doctor_check_staged_extension(dc: &mut DoctorCounters, home: &Path) { match manifest.get("version").and_then(|v| v.as_str()) { Some(crate::PRODUCT_VERSION) => dc.pass("Staged extension version matches tracedecay"), Some(version) => dc.warn(&format!( - "Staged extension version {version} does not match tracedecay {} — run `tracedecay update-plugin`", + "Staged extension version {version} does not match tracedecay {}, run `tracedecay update-plugin`", crate::PRODUCT_VERSION )), None => dc.warn("Staged gemini-extension.json does not contain a version"), @@ -332,7 +332,7 @@ fn doctor_check_staged_extension(dc: &mut DoctorCounters, home: &Path) { )); } else { dc.fail(&format!( - "{} is missing or carries no tracedecay rules — run `tracedecay install --agent gemini`", + "{} is missing or carries no tracedecay rules, run `tracedecay install --agent gemini`", context_path.display() )); } @@ -346,12 +346,12 @@ fn doctor_check_installed_extension(dc: &mut DoctorCounters, home: &Path) { let manifest_path = installed_manifest_path(home); match read_installed_extension(home) { InstalledExtensionV1::Missing => dc.warn(&format!( - "No tracedecay extension at {} — `tracedecay install --agent gemini` drives \ + "No tracedecay extension at {}. `tracedecay install --agent gemini` drives \ `gemini extensions install`", manifest_path.display() )), InstalledExtensionV1::Unreadable => dc.fail(&format!( - "{} exists but could not be read as JSON — remove it with \ + "{} exists but could not be read as JSON, remove it with \ `gemini extensions uninstall {EXTENSION_NAME}` and reinstall", manifest_path.display() )), @@ -365,7 +365,7 @@ fn doctor_check_installed_extension(dc: &mut DoctorCounters, home: &Path) { dc.pass("Installed extension version matches tracedecay"); } Some(version) => dc.warn(&format!( - "Installed extension version {version} does not match tracedecay {} — run \ + "Installed extension version {version} does not match tracedecay {}, run \ `gemini extensions update {EXTENSION_NAME}`", crate::PRODUCT_VERSION )), @@ -384,7 +384,7 @@ fn report_manifest_server(dc: &mut DoctorCounters, manifest: &serde_json::Value, .and_then(serde_json::Value::as_object) else { dc.fail(&format!( - "{subject} does not declare the tracedecay MCP server — run \ + "{subject} does not declare the tracedecay MCP server, run \ `tracedecay install --agent gemini`" )); return; @@ -398,7 +398,7 @@ fn report_manifest_server(dc: &mut DoctorCounters, manifest: &serde_json::Value, dc.pass(&format!("{subject} declares an MCP server command")); } else { dc.fail(&format!( - "{subject} MCP server has no command — run `tracedecay install --agent gemini`" + "{subject} MCP server has no command, run `tracedecay install --agent gemini`" )); } @@ -410,7 +410,7 @@ fn report_manifest_server(dc: &mut DoctorCounters, manifest: &serde_json::Value, dc.pass(&format!("{subject} MCP server args include \"serve\"")); } else { dc.fail(&format!( - "{subject} MCP server args missing \"serve\" — run `tracedecay install --agent gemini`" + "{subject} MCP server args missing \"serve\", run `tracedecay install --agent gemini`" )); } @@ -420,7 +420,7 @@ fn report_manifest_server(dc: &mut DoctorCounters, manifest: &serde_json::Value, )); } else { dc.warn(&format!( - "{subject} MCP server missing trust: true — Gemini will prompt for each tool call" + "{subject} MCP server missing trust: true. Gemini will prompt for each tool call" )); } } @@ -434,7 +434,7 @@ fn doctor_check_host_reported_extensions(dc: &mut DoctorCounters, home: &Path) { Ok(Some(outcome)) => outcome, Ok(None) => { dc.info( - "`gemini` is not on PATH — could not ask Gemini CLI which extensions it has \ + "`gemini` is not on PATH, could not ask Gemini CLI which extensions it has \ (the extension lifecycle requires that binary)", ); return; @@ -457,7 +457,7 @@ fn doctor_check_host_reported_extensions(dc: &mut DoctorCounters, home: &Path) { dc.pass("`gemini extensions list` reports the tracedecay extension"); } else { dc.fail( - "`gemini extensions list` does not report a tracedecay extension — run \ + "`gemini extensions list` does not report a tracedecay extension, run \ `tracedecay install --agent gemini`", ); } @@ -465,8 +465,8 @@ fn doctor_check_host_reported_extensions(dc: &mut DoctorCounters, home: &Path) { /// `~/.gemini/settings.json` is no longer where tracedecay is registered: the /// extension supplies the MCP server. A surviving `mcpServers.tracedecay` -/// entry is pre-extension residue, and reporting its *absence* as a failure — -/// as this check once did — would now be a lie. +/// entry is pre-extension residue, and reporting its *absence* as a failure, +/// as this check once did, would now be a lie. fn doctor_check_settings(dc: &mut DoctorCounters, home: &Path) { let settings = settings_path(home); if !settings.exists() { diff --git a/crates/tracedecay-agent-hosts/src/agents/gemini/extension.rs b/crates/tracedecay-agent-hosts/src/agents/gemini/extension.rs index 332866d50a..ed6fd2f95a 100644 --- a/crates/tracedecay-agent-hosts/src/agents/gemini/extension.rs +++ b/crates/tracedecay-agent-hosts/src/agents/gemini/extension.rs @@ -5,7 +5,7 @@ //! install|uninstall|list|update`. TraceDecay therefore does exactly two //! things here: //! -//! 1. **Stage** a complete extension source directory it owns outright — the +//! 1. **Stage** a complete extension source directory it owns outright, the //! `gemini-extension.json` manifest (naming the tracedecay MCP server with //! `args: ["serve"]` and `trust: true`) plus the extension's own context //! file. The manifest carries the resolved tracedecay binary through the @@ -70,7 +70,7 @@ pub(super) use crate::agents::plugin_bundle::TRACEDECAY_BIN_PLACEHOLDER; /// /// `trust: true` is what makes Gemini auto-approve tracedecay tool calls /// instead of prompting per call; `args: ["serve"]` is the MCP transport the -/// binary speaks. Both live in the extension now — not in +/// binary speaks. Both live in the extension now, not in /// `~/.gemini/settings.json`. const EXTENSION_MANIFEST_TEMPLATE: &str = r#"{ "name": "tracedecay", @@ -112,7 +112,7 @@ pub(super) fn extension_stage_dir(home: &Path) -> PathBuf { home.join(GEMINI_STAGED_EXTENSION_RELATIVE) } -/// The staged manifest — presence is the signal that TraceDecay has rendered +/// The staged manifest, presence is the signal that TraceDecay has rendered /// an extension source for this profile. pub(super) fn staged_manifest_path(home: &Path) -> PathBuf { extension_stage_dir(home).join(EXTENSION_MANIFEST_FILE) @@ -134,7 +134,7 @@ pub(super) fn installed_manifest_path(home: &Path) -> PathBuf { } /// Gemini CLI's shared settings file. Host-owned, and under the extension -/// model no longer a TraceDecay write target — only an observation target, so +/// model no longer a TraceDecay write target, only an observation target, so /// a lifecycle transaction can roll back whatever the host CLI changed there. pub(super) fn settings_path(home: &Path) -> PathBuf { gemini_home(home).join("settings.json") @@ -247,8 +247,8 @@ pub(super) fn stage_dir_is_tracedecay(stage_dir: &Path) -> bool { /// Remove the tracedecay-owned staging directory so the next write is a clean /// replace. No-op when it is missing; refuses when it exists but is not -/// tracedecay-owned, so a directory squatting on the path — an operator's own -/// hand-written extension source, say — is never deleted. +/// tracedecay-owned, so a directory squatting on the path, an operator's own +/// hand-written extension source, say, is never deleted. fn clean_replace_owned_stage_dir(stage_dir: &Path) -> Result<()> { if !stage_dir.exists() { return Ok(()); @@ -385,8 +385,8 @@ pub(super) fn require_gemini_cli() -> Result { /// /// When the host already carries an installed tracedecay extension the host's /// own `uninstall` runs first: `gemini extensions install` refuses to install -/// over an existing extension, and removing it through the host — rather than -/// deleting the host-owned directory ourselves — keeps every write to that +/// over an existing extension, and removing it through the host, rather than +/// deleting the host-owned directory ourselves, keeps every write to that /// state on Gemini's side of the boundary. #[hotpath::measure(label = "hosts.agent.gemini.extension_activate")] pub(super) fn gemini_extension_activate_with(gemini: &Path, home: &Path) -> Result<()> { @@ -410,7 +410,7 @@ pub(super) fn gemini_extension_activate_with(gemini: &Path, home: &Path) -> Resu /// /// The staged source is left in place: it is TraceDecay-owned input to the /// host lifecycle, not host registration state, and the deployed-asset -/// lifecycle — not this registration boundary — owns removing it. +/// lifecycle, not this registration boundary, owns removing it. #[hotpath::measure(label = "hosts.agent.gemini.extension_deactivate")] pub(super) fn gemini_extension_deactivate_with(gemini: &Path, home: &Path) -> Result<()> { run_gemini_extension_step(gemini, &["extensions", "uninstall", EXTENSION_NAME], home) @@ -437,7 +437,7 @@ pub(super) fn host_reported_extensions(home: &Path) -> Result {} Some(version) => dc.warn(&format!( - "{} was generated by tracedecay {version} (installed binary is {}) — re-run `tracedecay install --agent hermes` to refresh it", + "{} was generated by tracedecay {version} (installed binary is {}), re-run `tracedecay install --agent hermes` to refresh it", manifest_path.display(), crate::PRODUCT_VERSION, )), None => dc.warn(&format!( - "{} has no manifest version — re-run `tracedecay install --agent hermes` to refresh it", + "{} has no manifest version, re-run `tracedecay install --agent hermes` to refresh it", manifest_path.display(), )), } @@ -664,7 +664,7 @@ mod registration_tests { /// Rendered commit equals provider SHA: the plugin.yaml `generator_commit` /// line and the `__init__.py` provenance header must carry the passed - /// 40-hex commit verbatim — no truncation, no baked-in build identity. + /// 40-hex commit verbatim, no truncation, no baked-in build identity. #[test] fn rendered_provenance_stamps_equal_the_passed_generator_commit() { const FIXTURE_SHA: &str = "0123456789abcdef0123456789abcdef01234567"; diff --git a/crates/tracedecay-agent-hosts/src/agents/hermes/dashboard_wrapper.rs b/crates/tracedecay-agent-hosts/src/agents/hermes/dashboard_wrapper.rs index 69b8167413..7b0306c36e 100644 --- a/crates/tracedecay-agent-hosts/src/agents/hermes/dashboard_wrapper.rs +++ b/crates/tracedecay-agent-hosts/src/agents/hermes/dashboard_wrapper.rs @@ -4,15 +4,15 @@ //! `plugin_api.py`, and one mount entry) into a generated Hermes plugin's //! `dashboard/` subdirectory, where the Hermes web server's dashboard-plugin //! discovery picks it up -//! (`/plugins//dashboard/manifest.json` — both stock and +//! (`/plugins//dashboard/manifest.json`, both stock and //! forked Hermes scan user plugins this way). //! //! Everything is embedded at compile time so installs need no source //! checkout. The adapter starts the standalone `tracedecay dashboard` server //! and mounts its root in Hermes; all UI assets come from the one //! `dashboard/app-dist` build, which the root crate's `dashboard::assets` -//! embeds. Nothing here reaches for those bytes — the wrapper only proxies to -//! the running server — so this is a note about where the assets originate, +//! embeds. Nothing here reaches for those bytes, the wrapper only proxies to +//! the running server, so this is a note about where the assets originate, //! not a dependency. //! //! On hosts whose Hermes predates dashboard-plugin discovery the deployed @@ -189,7 +189,7 @@ fn plugin_api(tracedecay_bin: &str) -> Result { }; if !PLUGIN_API_PY.contains(BIN_PLACEHOLDER) { return Err(TraceDecayError::Config { - message: "embedded plugin_api.py is missing its binary placeholder — \ + message: "embedded plugin_api.py is missing its binary placeholder. \ this is a tracedecay build bug" .to_string(), }); diff --git a/crates/tracedecay-agent-hosts/src/agents/hermes/templates/plugin_init.py b/crates/tracedecay-agent-hosts/src/agents/hermes/templates/plugin_init.py index 027ef50ae9..c5ade411b6 100644 --- a/crates/tracedecay-agent-hosts/src/agents/hermes/templates/plugin_init.py +++ b/crates/tracedecay-agent-hosts/src/agents/hermes/templates/plugin_init.py @@ -49,7 +49,7 @@ class ContextEngine: pass # Hermes' centralized auxiliary LLM facade is the MODULE-LEVEL -# agent.auxiliary_client.call_llm(task=..., messages=..., ...) — AIAgent +# agent.auxiliary_client.call_llm(task=..., messages=..., ...). AIAgent # instances carry no ``auxiliary_client`` attribute and no host call site # hands the plugin an agent object. Guarded so the plugin still degrades # gracefully (deterministic fallback summaries) outside a hermes install. @@ -369,7 +369,7 @@ def _resolve_auxiliary_client(agent=None): # Direct duplicates of the memory provider's own tool surface # (fact_store / fact_feedback / memory_status). Skipped at register() time # when tracedecay is the active memory.provider so the same store is not -# exposed twice per API call. tracedecay_message_search stays registered — +# exposed twice per API call. tracedecay_message_search stays registered, # the provider does not expose transcript search. MEMORY_PROVIDER_TOOLS = frozenset(( *FACT_STORE_EXACT_ROUTES.values(), @@ -454,7 +454,7 @@ def _pre_llm_call(*args, **kwargs): # can hijack the assistant's response and surface tracedecay when the user # did not ask for code work. Keep it first-turn-only for prompt-cache # stability, and skip it entirely when no tracedecay tools registered on - # this host — advertising unregistered tools invites hallucinated calls. + # this host, advertising unregistered tools invites hallucinated calls. if not kwargs.get("is_first_turn"): return None if not _REGISTERED_TOOL_NAMES: @@ -1962,7 +1962,7 @@ def _apply_context_length(self, context_length): run_agent.py logs ``context_length``/``threshold_tokens`` directly and the minimum-context guard in agent/agent_init.py reads - ``context_length`` — leaving them 0 logged a bogus 0-token window and + ``context_length``, leaving them 0 logged a bogus 0-token window and silently bypassed that guard. """ try: @@ -2258,7 +2258,7 @@ def post_setup(self, hermes_home, config): """ if not _tracedecay_binary_available(): print( - f" tracedecay binary not found at {tools.TRACEDECAY_BIN} — " + f" tracedecay binary not found at {tools.TRACEDECAY_BIN}. " "install it (cargo install tracedecay) and re-run `hermes memory setup`." ) return @@ -2283,7 +2283,7 @@ def post_setup(self, hermes_home, config): print(f" tracedecay memory store check failed: {detail}") def system_prompt_block(self): - # Built once per session by the host during system prompt assembly — + # Built once per session by the host during system prompt assembly, # cache-stable, unlike per-turn pre_llm_call injection. return ( "tracedecay memory is active: durable facts live in the holographic " @@ -2690,7 +2690,7 @@ def bound(*args, **kwargs): # - Only the live-ingest LCM verbs whose schemas take the in-memory # ``messages`` list (MESSAGE_DEPENDENT_TOOLS) and the context-engine # native tool mirrors stay gated behind the message-forwarding - # capability flag — without forwarding their ingest piggyback can + # capability flag, without forwarding their ingest piggyback can # never fire (the host still mounts the native LCM tools itself via # context_engine.get_tool_schemas()). register_tool = getattr(ctx, "register_tool", None) @@ -2704,7 +2704,7 @@ def bound(*args, **kwargs): continue if name in MEMORY_PROVIDER_TOOLS and tracedecay_is_memory_provider: # The active memory provider already exposes this store as - # fact_store/fact_feedback/memory_status — registering the + # fact_store/fact_feedback/memory_status, registering the # prefixed twins would double the schema footprint. continue raw_handler = ( diff --git a/crates/tracedecay-agent-hosts/src/agents/hermes/templates/skill.md b/crates/tracedecay-agent-hosts/src/agents/hermes/templates/skill.md index 86bc268764..460908448f 100644 --- a/crates/tracedecay-agent-hosts/src/agents/hermes/templates/skill.md +++ b/crates/tracedecay-agent-hosts/src/agents/hermes/templates/skill.md @@ -12,7 +12,7 @@ call graph traversal, impact analysis, affected files, and architectural navigat If a tracedecay tool invocation fails, times out, or the plugin is unavailable, every tool is also available directly as a shell command: -`tracedecay tool --args ''` — the same JSON arguments object as the +`tracedecay tool --args ''`, the same JSON arguments object as the MCP tool; pipe it via `--args -` (a quoted heredoc) when it contains quotes or newlines (`tracedecay tool` lists all tools, `tracedecay tool --help` shows parameters). Hermes tool calls already run through this CLI under the hood @@ -126,21 +126,21 @@ memory and does not write project LCM data into an arbitrary working directory. ## Memory - **Recall before external search.** Run `fact_search` (and `lcm_grep` for past - conversations) before reaching for web or external search — prior sessions + conversations) before reaching for web or external search. Prior sessions often already answered the question. - **Calibrate trust; don't default everything high.** Aim for a spread across stored facts rather than uniform high trust: - - `>= 0.85` — verified, durable facts (confirmed decisions, observed behavior, + - `>= 0.85`, verified, durable facts (confirmed decisions, observed behavior, user-stated preferences). - - `~ 0.7` — ordinary well-sourced observations. - - `~ 0.5` — plausible but unverified; prefer not storing over storing noise. + - `~ 0.7`, ordinary well-sourced observations. + - `~ 0.5`, plausible but unverified; prefer not storing over storing noise. - **Read the add result's diff report.** `fact_add` returns `diff` / `closest_fact_id` / `similarity` / `reason`: - - `near_duplicate` — a very similar fact exists; prefer `fact_update` on the + - `near_duplicate`, a very similar fact exists; prefer `fact_update` on the existing fact over piling on duplicates. - - `possible_conflict` — a negation/state-change cue suggests supersession; + - `possible_conflict`, a negation/state-change cue suggests supersession; confirm which fact is current and update or remove the stale one. - - `rejected_secret_like` — the content looked like a credential and was NOT + - `rejected_secret_like`, the content looked like a credential and was NOT stored; never try to re-store secrets. - **Never store secrets, transient run output (ports, PIDs, temp paths, run logs), or facts you have not verified.** diff --git a/crates/tracedecay-agent-hosts/src/agents/host_bundle/component_set.rs b/crates/tracedecay-agent-hosts/src/agents/host_bundle/component_set.rs index b57c878fb7..68437f79a9 100644 --- a/crates/tracedecay-agent-hosts/src/agents/host_bundle/component_set.rs +++ b/crates/tracedecay-agent-hosts/src/agents/host_bundle/component_set.rs @@ -766,7 +766,7 @@ impl HostBundleWriterV1 { /// loss. This also closes the crash window between the artifact write /// and the `wrote_new` journal update. /// - /// Anything else — foreign bytes that match neither — stays fail-closed + /// Anything else, foreign bytes that match neither, stays fail-closed /// with `RecoveryRequired`, and the operator resolves it explicitly with /// `tracedecay host-bundle recover`. fn restore_component_set_entry( @@ -835,8 +835,8 @@ impl HostBundleWriterV1 { // rollback wants it gone. `remove_if_digest_matches` already // converges on the two safe outcomes (already absent, or holding // this transaction's cataloged bytes). Foreign bytes at a path this - // transaction created are genuinely ambiguous — removing them could - // destroy another writer's file — so that case stays fail-closed. + // transaction created are genuinely ambiguous, removing them could + // destroy another writer's file, so that case stays fail-closed. remove_if_digest_matches( &parent, &name, @@ -888,7 +888,7 @@ fn component_set_receipt_from_prepared( // Update left untouched while it did real work on a sibling. Two gates bound // this: // - // * The operation is an Update — the only incremental one. Install + // * The operation is an Update, the only incremental one. Install // first-deploys every component, Repair re-asserts ownership of the whole // cataloged set, and Uninstall removes it, so each legitimately stamps its // operation onto every receipt, changed or not. @@ -907,8 +907,8 @@ fn component_set_receipt_from_prepared( let component_receipts = prepared .iter() .map(|component| { - // An unchanged companion — one whose plan writes nothing and whose - // manifest is byte-identical to its durable receipt — keeps its + // An unchanged companion, one whose plan writes nothing and whose + // manifest is byte-identical to its durable receipt, keeps its // original operation provenance. "Writes nothing" must be read from // each mutation's action, not from an empty mutation list: a // component with manifest artifacts always plans one Noop mutation @@ -930,7 +930,7 @@ fn component_set_receipt_from_prepared( .map(|receipt| receipt.rollback_history.clone()) .unwrap_or_default(); // A Repair that overwrites a receipt-owned path whose bytes drifted - // from the catalog backs up genuinely foreign content — a user edit, + // from the catalog backs up genuinely foreign content, a user edit, // never tracedecay's own prior output, because Repair replaces a // path only when its observed digest differs from the cataloged one, // which for an unchanged Repair manifest is also the previously diff --git a/crates/tracedecay-agent-hosts/src/agents/host_bundle/doctor.rs b/crates/tracedecay-agent-hosts/src/agents/host_bundle/doctor.rs index 278a01996e..09eb031ce3 100644 --- a/crates/tracedecay-agent-hosts/src/agents/host_bundle/doctor.rs +++ b/crates/tracedecay-agent-hosts/src/agents/host_bundle/doctor.rs @@ -55,7 +55,7 @@ pub trait HostBundleRegistrationInspectorV1 { /// Read-only classification of one installed component (or one of its /// artifacts). This type is `Serialize`-only and is never persisted into a -/// receipt, journal, or any other durable control file — it exists solely for +/// receipt, journal, or any other durable control file, it exists solely for /// the transient [`HostBundleDoctorReportV1`]. Adding a variant therefore /// widens the doctor's reported vocabulary without making any previously /// written artifact unreadable. @@ -71,7 +71,7 @@ pub enum HostBundleComponentDoctorStateV1 { /// converges without an operator first resolving a foreign claim. Drifted, OwnershipConflict, - /// A `TraceDecay`-named host registration that no install receipt owns — + /// A `TraceDecay`-named host registration that no install receipt owns, /// an uninstall that removed the receipt-owned artifacts but left the host /// still advertising the extension. Reported so the leftover registration /// is visible; repairing it is an explicit operator command. @@ -79,8 +79,8 @@ pub enum HostBundleComponentDoctorStateV1 { /// Every receipt-owned artifact of a component whose host activates only /// through an interactive UI is absent, and the host's staged source bundle /// is present but unactivated. Nothing TraceDecay can drive non-interactively - /// deploys these bytes — the host materialises them when the operator - /// activates the extension — so this is a pending user action rather than + /// deploys these bytes, the host materialises them when the operator + /// activates the extension, so this is a pending user action rather than /// receipt drift. Ranked below `Missing`: a component that still holds SOME /// of its receipt-owned bytes lost the rest after activation, which is real /// drift and stays blocking. @@ -567,7 +567,7 @@ fn receipt_ownership_claims(receipt_paths: &[PathBuf]) -> BTreeMap { if state.artifact_digest == Some(artifact.artifact_digest) { Ok(HostArtifactActionV1::Noop) @@ -433,7 +433,7 @@ pub(super) fn plan_artifact_action( /// ([`HostComponentSetRegistrationV1::receiptless_component_provenance`]), /// e.g. a Cursor plugin directory whose own manifest names tracedecay. /// Live pre-receipt bundles restamp versions and binary paths every -/// release, so they are never byte-identical — provenance is what lets +/// release, so they are never byte-identical, provenance is what lets /// `install`/`update-plugin` converge them without wedging; /// * explicit operator adoption: `--yes --adopt` claimed the path knowingly. /// @@ -473,7 +473,7 @@ fn adopts_pre_receipt_artifact( pub const HOST_BUNDLE_STAGE_ROOT_RELATIVE: &str = ".tracedecay/host-bundle-stage"; /// A deploy path inside TraceDecay's own staging namespace is -/// TraceDecay-staged by construction — it is never host or user config, so a +/// TraceDecay-staged by construction, it is never host or user config, so a /// receiptless divergent file there is a staging left by another TraceDecay /// binary version (its render bakes in the binary path), not a foreign claim. /// Refusing it would wedge the documented native-activation hand-over diff --git a/crates/tracedecay-agent-hosts/src/agents/host_bundle/tests.rs b/crates/tracedecay-agent-hosts/src/agents/host_bundle/tests.rs index 048ae1cf01..c4adf6b45d 100644 --- a/crates/tracedecay-agent-hosts/src/agents/host_bundle/tests.rs +++ b/crates/tracedecay-agent-hosts/src/agents/host_bundle/tests.rs @@ -758,7 +758,7 @@ fn wedge_repair_with_second_writer( } /// Defect: a second writer that left the deployed path holding the exact -/// pre-transaction bytes used to make rollback unconvergeable forever — +/// pre-transaction bytes used to make rollback unconvergeable forever, /// `remove_if_digest_matches` refused to touch a file that no longer /// matched the installed digest, so the journal stayed behind and wedged /// every later host transaction. @@ -1183,8 +1183,8 @@ fn unchanged_companion_receipt_keeps_original_operation_provenance() { // still earns a fresh receipt. The change must keep the set's shared // configuration authority (`configuration_snapshot_id`, // `integration_manifest_digest`, `catalog_digest`) uniform across - // components — `validate_component_set_journal` rejects a set whose - // components disagree on it — so bump a per-component manifest field + // components. `validate_component_set_journal` rejects a set whose + // components disagree on it, so bump a per-component manifest field // (`effective_behavior_digest`) that shifts only the agent's canonical // digest and leaves the core component entirely unchanged. let mut metadata_only_change = core_only_change.clone(); @@ -1738,7 +1738,7 @@ fn receiptless_adoption_requires_provenance_or_explicit_authority() { HostBundleLifecycleOpV1::Repair, ] { // Custom or unowned bytes parked at the cataloged path: refused - // without adoption authority — the path proves nothing. + // without adoption authority, the path proves nothing. let refused = plan_artifact_action( operation, artifact, @@ -2451,7 +2451,7 @@ fn never_activated_interactive_host_component_defers_instead_of_failing() { /// The deferral is scoped to components the host never materialised. Once /// any receipt-owned byte is on disk, an absent sibling is a file that went -/// missing after activation — real drift, and still blocking. +/// missing after activation, real drift, and still blocking. #[test] fn partially_materialised_interactive_host_component_still_fails() { let artifacts = tempfile::tempdir().unwrap(); diff --git a/crates/tracedecay-agent-hosts/src/agents/host_bundle/writer.rs b/crates/tracedecay-agent-hosts/src/agents/host_bundle/writer.rs index 6056811051..6b2daf4ecf 100644 --- a/crates/tracedecay-agent-hosts/src/agents/host_bundle/writer.rs +++ b/crates/tracedecay-agent-hosts/src/agents/host_bundle/writer.rs @@ -10,7 +10,6 @@ use std::sync::atomic::{AtomicU64, Ordering}; use cap_fs_ext::{DirExt, FollowSymlinks, OpenOptionsFollowExt}; use cap_std::ambient_authority; use cap_std::fs::{Dir, OpenOptions as CapOpenOptions}; -use fs2::FileExt; use sha2::{Digest, Sha256}; use tracedecay_domain::canonical_json_bytes; use tracedecay_host_integration::host_bundle_recovery_required; @@ -1312,7 +1311,8 @@ fn open_host_writer_lock( .open_with(&name, &options) .map_err(|_| HostBundleError::UnsafeInstallPath)? .into_std(); - file.try_lock_exclusive() + file.try_lock() + .map_err(std::io::Error::from) .map_err(|_| host_bundle_recovery_required!())?; Ok(HostWriterLock { host, file }) } diff --git a/crates/tracedecay-agent-hosts/src/agents/host_bundle_registry.rs b/crates/tracedecay-agent-hosts/src/agents/host_bundle_registry.rs index e521ea8414..14d570bdb4 100644 --- a/crates/tracedecay-agent-hosts/src/agents/host_bundle_registry.rs +++ b/crates/tracedecay-agent-hosts/src/agents/host_bundle_registry.rs @@ -528,7 +528,7 @@ fn component_assets( // directory, exactly as Claude Code's marketplace source feeds // `claude plugin install`. Render it through the integration's own // renderer so the bytes the transaction deploys are byte-identical to the - // ones staging writes and the doctor reads — two renderers here would let + // ones staging writes and the doctor reads, two renderers here would let // an install and a repair disagree about what was staged. if (host, component) == (HostKindV1::Gemini, HostComponentV1::ContextMcp) { let files = super::gemini::rendered_extension_files(tracedecay_bin) @@ -870,7 +870,7 @@ mod tests { } /// Both writers must agree even when the running binary is not the - /// installed one — `./target/release/tracedecay reinstall` is exactly the + /// installed one. `./target/release/tracedecay reinstall` is exactly the /// case that corrupted the `OpenCode` transaction and wedged the shared /// component-set journal. #[test] @@ -933,7 +933,7 @@ mod tests { } /// Both writers must agree even when the running binary is not the - /// installed one — `./target/release/tracedecay reinstall` is exactly the + /// installed one. `./target/release/tracedecay reinstall` is exactly the /// case that corrupted the Hermes transaction and left a pending /// `component-set-journal.hermes.v1.json` behind. #[test] @@ -1176,7 +1176,7 @@ mod tests { /// descriptor, never Copilot's own `mcp-config.json`: that document is /// written by the host command, and owning it here would make the /// transaction's artifact write race the host's registry merge. Core is - /// refused through the capability matrix, not through a missing arm — the + /// refused through the capability matrix, not through a missing arm, the /// host reports no hook route to install. #[test] fn copilot_packages_only_the_mcp_route_its_host_cli_registers() { @@ -1238,7 +1238,7 @@ mod tests { /// `gemini extensions install` adopts. Core is refused because the staged /// extension declares no hook and the capability matrix reports none, and /// the deployed bytes must be exactly what the integration's own staging - /// writes — otherwise install and repair would stage two different + /// writes, otherwise install and repair would stage two different /// extensions. #[test] fn gemini_packages_the_extension_source_its_host_cli_installs() { diff --git a/crates/tracedecay-agent-hosts/src/agents/host_cli.rs b/crates/tracedecay-agent-hosts/src/agents/host_cli.rs index 4e8444d6c6..8fea71c86c 100644 --- a/crates/tracedecay-agent-hosts/src/agents/host_cli.rs +++ b/crates/tracedecay-agent-hosts/src/agents/host_cli.rs @@ -2,7 +2,7 @@ //! //! Some hosts own their plugin registration, cache, and enabled state //! outright. For those, the canonical way to install or remove TraceDecay is -//! the host's own command — not config surgery on state the host considers +//! the host's own command, not config surgery on state the host considers //! private. This module is the single boundary through which TraceDecay //! invokes such a command. //! @@ -57,8 +57,8 @@ pub(crate) struct HostCliOutcomeV1 { } impl HostCliOutcomeV1 { - /// A clean exit is the only success. Anything else — non-zero, signalled, - /// or timed out — leaves host state unproven and must not be reported as a + /// A clean exit is the only success. Anything else, non-zero, signalled, + /// or timed out, leaves host state unproven and must not be reported as a /// completed lifecycle step. pub(crate) fn succeeded(&self) -> bool { !self.timed_out && self.status == Some(0) @@ -127,15 +127,15 @@ fn require_host_cli_from( /// a fresh write of the executable. /// /// Linux refuses `execve` with `ETXTBSY` while *any* process holds the image -/// open for writing — including a process that merely inherited the descriptor +/// open for writing, including a process that merely inherited the descriptor /// across a `fork` and has not reached its own `exec` yet. A lifecycle that /// drives a host CLI shortly after something installed or updated that binary /// can therefore be refused for a reason that has nothing to do with the host, /// and reporting it would blame the host for a race in its installer. /// /// The retry is deliberately tiny and bounded: the condition clears as soon as -/// the writer's descriptor closes. Every other spawn failure — including a -/// missing or non-executable file — is returned on the first attempt, so no +/// the writer's descriptor closes. Every other spawn failure, including a +/// missing or non-executable file, is returned on the first attempt, so no /// real refusal is delayed or masked. fn spawn_admitting_recent_writes(command: &mut Command) -> std::io::Result { const ATTEMPTS: u32 = 5; @@ -676,7 +676,7 @@ printf '%s' "$HOME" > "$HOME/home" // shell assigns itself a default `PATH` when it starts without one, so // a `#!/bin/sh` probe reports that synthesized default rather than // `` even though `env_clear` did remove the variable. What the - // admission actually promises — and what this asserts — is that the + // admission actually promises, and what this asserts, is that the // *ambient* value did not reach the child. let observed = std::fs::read_to_string(home.path().join("path")).unwrap(); let ambient = std::env::var("PATH").unwrap_or_default(); @@ -755,7 +755,7 @@ exit 0 ); // As above, `` is not observable through a `#!/bin/sh` probe: // the shell synthesizes a default `PATH` when it inherits none. The - // guarantee under test is that neither ambient entry survived — not + // guarantee under test is that neither ambient entry survived, not // the attacker directory, and not even the directory the interpreter // itself was resolved from, because the parent resolves it once and // passes an absolute path rather than letting the child re-resolve. diff --git a/crates/tracedecay-agent-hosts/src/agents/host_component_registration.rs b/crates/tracedecay-agent-hosts/src/agents/host_component_registration.rs index 1afe1ec896..e7a1acdf1b 100644 --- a/crates/tracedecay-agent-hosts/src/agents/host_component_registration.rs +++ b/crates/tracedecay-agent-hosts/src/agents/host_component_registration.rs @@ -199,7 +199,7 @@ impl CatalogHostComponentRegistrationAuthority { } // The transaction error vocabulary is fixed, so surface the // integration's own message here before it is collapsed into the - // generic storage failure — otherwise the actionable cause (for + // generic storage failure, otherwise the actionable cause (for // example a refused symlinked project config) is lost. eprintln!("{error}"); host_bundle_storage_failure!() @@ -521,7 +521,7 @@ impl CatalogHostComponentRegistrationAuthority { /// declared writes held constant. /// /// Some hosts register themselves *through* a file this component set also - /// installs as a managed artifact — Kiro's `~/.kiro/settings/mcp.json` is + /// installs as a managed artifact. Kiro's `~/.kiro/settings/mcp.json` is /// simultaneously the registration path and the `context_mcp` artifact. A /// revision taken over the raw bytes of such a path necessarily changes the /// moment the transaction performs its own declared write, so a post-write @@ -1439,7 +1439,7 @@ impl crate::agents::host_bundle::HostComponentSetRegistrationV1 == crate::agents::host_bundle::HostBundleLifecycleOpV1::Install; self.should_apply = match self.operation { // A registration that is partially present or `Repairable` on - // install is TraceDecay's own residue — staged sources, a + // install is TraceDecay's own residue, staged sources, a // marketplace entry, or a stale native cache left by a prior // install of this same bundle. Reinstall/update over it must // converge by re-activating, exactly as `Update` does; only a @@ -1491,7 +1491,7 @@ impl crate::agents::host_bundle::HostComponentSetRegistrationV1 // Removal is the host's to perform first: stripping a bundle the // host still has registered would leave it resolving a // marketplace that no longer exists. This arm must precede the - // update/activation arms below — both of those remediate as + // update/activation arms below, both of those remediate as // "refresh or activate the plugin", which can never unblock an // uninstall, so routing removal through them makes the host's // integration impossible to remove. Once the operator has run @@ -1918,7 +1918,7 @@ mod tests { /// The live reinstall journey: TraceDecay's own staging residue (a /// personal marketplace entry with no native activation yet) makes every /// Codex component registration read `Repairable`. An install over that - /// self-owned residue must proceed and re-activate — refusing it as an + /// self-owned residue must proceed and re-activate, refusing it as an /// ownership conflict made `tracedecay install --agent codex` fail on /// every reinstall/update of TraceDecay's own prior install. #[test] diff --git a/crates/tracedecay-agent-hosts/src/agents/host_config_io.rs b/crates/tracedecay-agent-hosts/src/agents/host_config_io.rs index d41fd2004c..fd635dbf6d 100644 --- a/crates/tracedecay-agent-hosts/src/agents/host_config_io.rs +++ b/crates/tracedecay-agent-hosts/src/agents/host_config_io.rs @@ -232,7 +232,7 @@ pub(super) fn render_json_config(path: &Path, value: &serde_json::Value) -> Resu return Err(TraceDecayError::Config { message: format!( "internal error: serialized JSON for {} failed re-parse validation.\n \ - This is a bug in tracedecay — please report it.", + This is a bug in tracedecay, please report it.", path.display() ), }); @@ -726,8 +726,8 @@ pub fn write_json_file(path: &Path, value: &serde_json::Value) -> Result<()> { /// The override is honored only when it is non-empty and falls under `home`. /// That keeps isolated-HOME tests from picking up the operator's real /// `KIMI_CODE_HOME` / `VIBE_HOME`, and refuses a host directory that escapes -/// the admitted profile home. Anything else — unset, empty, or outside -/// `home` — uses `home.join(default_relative)`. +/// the admitted profile home. Anything else, unset, empty, or outside +/// `home`, uses `home.join(default_relative)`. pub(crate) fn host_home_override(home: &Path, env_key: &str, default_relative: &str) -> PathBuf { std::env::var_os(env_key) .filter(|value| !value.is_empty()) @@ -1297,11 +1297,11 @@ fn parse_toml_config(path: &Path, contents: &str) -> Result { return Ok(toml::Value::Table(toml::map::Map::new())); } // NOTE: `str.parse::()` parses a single TOML value in toml v1, - // not a document — using it here would treat any well-formed config.toml as + // not a document, using it here would treat any well-formed config.toml as // unparseable and silently drop its contents. Use `toml::from_str` instead. let table: toml::Table = toml::from_str(contents).map_err(|e| TraceDecayError::Config { message: format!( - "failed to parse {} as TOML: {e}. Refusing to overwrite — fix the file or remove it manually.", + "failed to parse {} as TOML: {e}. Refusing to overwrite, fix the file or remove it manually.", path.display() ), })?; diff --git a/crates/tracedecay-agent-hosts/src/agents/kimi.rs b/crates/tracedecay-agent-hosts/src/agents/kimi.rs index 090b5610da..a6bf5ef468 100644 --- a/crates/tracedecay-agent-hosts/src/agents/kimi.rs +++ b/crates/tracedecay-agent-hosts/src/agents/kimi.rs @@ -15,8 +15,8 @@ //! //! **Deferral re-verified 2026-08-08 under the CLI-first policy.** `kimi //! --help` was probed directly: its command set is -//! `export, provider, acp, web, server, login, doctor, vis, migrate, upgrade` -//! — there is no `mcp` subcommand and no plugin subcommand of any kind. The +//! `export, provider, acp, web, server, login, doctor, vis, migrate, upgrade`, +//! there is no `mcp` subcommand and no plugin subcommand of any kind. The //! documented way to add, edit, or delete a server is the in-TUI //! `/mcp-config`. So there is nothing to adopt, and the deferral above is the //! honest lifecycle rather than a preference. See @@ -607,7 +607,7 @@ fn doctor_check_plugin(dc: &mut DoctorCounters, home: &Path, kimi_code_home: &Pa let installed_path = kimi_installed_json_path(kimi_code_home); if !installed_json_has_tracedecay(kimi_code_home) { dc.warn(&format!( - "no tracedecay entry in {} — run `tracedecay install --agent kimi` if you use Kimi Code CLI", + "no tracedecay entry in {}, run `tracedecay install --agent kimi` if you use Kimi Code CLI", installed_path.display() )); return; @@ -620,7 +620,7 @@ fn doctor_check_plugin(dc: &mut DoctorCounters, home: &Path, kimi_code_home: &Pa match kimi_managed_bundle_matches_staged(home, kimi_code_home) { Ok(true) => dc.pass("Kimi Code CLI managed plugin matches its staged source"), Ok(false) => dc.fail( - "Kimi Code CLI managed plugin is stale — run the staged `/plugins install` action", + "Kimi Code CLI managed plugin is stale, run the staged `/plugins install` action", ), Err(error) => dc.fail(&format!( "could not verify Kimi Code CLI managed plugin: {error}" @@ -650,7 +650,7 @@ fn doctor_check_plugin(dc: &mut DoctorCounters, home: &Path, kimi_code_home: &Pa } } else { dc.fail(&format!( - "Kimi Code CLI plugin manifest missing or invalid at {} — run `tracedecay install --agent kimi`", + "Kimi Code CLI plugin manifest missing or invalid at {}, run `tracedecay install --agent kimi`", manifest_path.display() )); } diff --git a/crates/tracedecay-agent-hosts/src/agents/kiro.rs b/crates/tracedecay-agent-hosts/src/agents/kiro.rs index f6d9d093cf..da45d3f855 100644 --- a/crates/tracedecay-agent-hosts/src/agents/kiro.rs +++ b/crates/tracedecay-agent-hosts/src/agents/kiro.rs @@ -49,7 +49,7 @@ const STEERING_SENTINELS: super::prompt_rules::OwnedBlockSentinels = }; /// Heading markers shipped releases (through v0.1.0-beta.37) used as the /// block's identity. An existing install carries one of them, usually closed by -/// the same end sentinel, so update and uninstall must recognize them — +/// the same end sentinel, so update and uninstall must recognize them, /// otherwise a reinstall appends the new block and strands the old one, and /// uninstall never removes it. const HISTORICAL_STEERING_HEADINGS: [&str; 2] = [ @@ -84,7 +84,7 @@ const KIRO_MCP_SERVER_NAME: &str = "tracedecay"; const MCP_SERVER_ARGS: &[&str] = &["serve"]; /// A hook the managed Kiro agent registers. Kiro's documented hook entry -/// schema is `command` plus an optional `matcher` — nothing else, so no +/// schema is `command` plus an optional `matcher`, nothing else, so no /// timeout or other tuning field exists to carry here. struct KiroManagedHook { event: &'static str, @@ -98,7 +98,7 @@ struct KiroManagedHook { /// /// No `stop`/session-end hook is registered. Kiro's documentation describes a /// Stop trigger, so the host-event catalog carries it -/// (`fixtures/host_events/kiro.json`, identity `stop`) — but only at +/// (`fixtures/host_events/kiro.json`, identity `stop`), but only at /// `support: documented_unverified`, because tracedecay has never captured a /// real Kiro stop event or verified Kiro's persisted session format. Until a /// capture verifies it the native decoder rejects the event (see `decode_kiro` @@ -383,14 +383,14 @@ impl AgentIntegration for KiroIntegration { match kiro_doctor_installation_state(&ctx.home) { Ok(KiroDoctorInstallationState::HostAbsent) => { dc.warn(&format!( - "Kiro is not detected at {} — run `tracedecay install --agent kiro` if you use Kiro", + "Kiro is not detected at {}, run `tracedecay install --agent kiro` if you use Kiro", host_home.display() )); return; } Ok(KiroDoctorInstallationState::TraceDecayAbsent) => { dc.warn(&format!( - "Kiro is detected at {}, but TraceDecay is not installed — run `tracedecay install --agent kiro` if you use Kiro", + "Kiro is detected at {}, but TraceDecay is not installed, run `tracedecay install --agent kiro` if you use Kiro", host_home.display() )); // Retired leftovers can exist without an MCP entry (for example @@ -578,7 +578,7 @@ fn require_kiro_cli() -> Result { fn kiro_mcp_add_with(kiro_cli: &Path, home: &Path, tracedecay_bin: &str) -> Result<()> { // Make the global scope explicit. Kiro's CLI also supports a workspace // registry, but this lifecycle owns only the profile-global entry; the - // workspace (`--scope workspace`) form is deliberately not driven here — + // workspace (`--scope workspace`) form is deliberately not driven here, // see `activate_project_host_component_registration`. let mut args = vec![ "mcp", @@ -715,8 +715,8 @@ fn remove_kiro_managed_skill_index(home: &Path, index_path: &Path) -> Result<()> super::remove_managed_skill_prompt_index(home, index_path, SkillInstallTarget::Kiro) } -/// Add or refresh tracedecay's steering resource. Every owned block — the -/// current sentinel-delimited shape or a historical heading-marked one — +/// Add or refresh tracedecay's steering resource. Every owned block, the +/// current sentinel-delimited shape or a historical heading-marked one, /// converges onto exactly one copy of the current block in place; operator /// text around it is preserved. fn install_steering_rules(path: &Path) -> Result<()> { @@ -894,7 +894,7 @@ fn owned_steering_ranges(contents: &str) -> Vec> { /// Earliest owned block at or after `from`. A historical heading block runs to /// the shipped end sentinel when that sentinel closes it before any other /// boundary; otherwise it ends at the next heading, the managed skill index, a -/// current start sentinel, or EOF — the shape the oldest installs wrote. +/// current start sentinel, or EOF, the shape the oldest installs wrote. fn first_owned_steering_range(contents: &str, from: usize) -> Option> { let current = STEERING_SENTINELS.block_range(contents, from); let historical = HISTORICAL_STEERING_HEADINGS @@ -1089,7 +1089,7 @@ fn doctor_advise_retired_steering(dc: &mut DoctorCounters, home: &Path) { } dc.warn(&format!( "migration advisory: retired Kiro global steering still present at {} \ - ({} owned block(s)); global install is MCP-only — remove with \ + ({} owned block(s)); global install is MCP-only, remove with \ `tracedecay uninstall --agent kiro` or delete the owned block(s)", path.display(), ranges.len() @@ -1106,7 +1106,7 @@ fn doctor_advise_retired_managed_agent(dc: &mut DoctorCounters, home: &Path) { } dc.warn(&format!( "migration advisory: retired Kiro managed agent still present at {}; \ - global install is MCP-only — remove with `tracedecay uninstall --agent kiro`", + global install is MCP-only, remove with `tracedecay uninstall --agent kiro`", path.display() )); } @@ -1139,7 +1139,7 @@ fn doctor_advise_retired_default_agent(dc: &mut DoctorCounters, home: &Path) { } dc.warn(&format!( "migration advisory: retired Kiro chat.defaultAgent still points at `{KIRO_AGENT_NAME}` in {}; \ - global install is MCP-only — clear or delete that setting manually \ + global install is MCP-only, clear or delete that setting manually \ (`tracedecay uninstall --agent kiro` does not rewrite cli.json)", path.display() )); diff --git a/crates/tracedecay-agent-hosts/src/agents/kiro/tests.rs b/crates/tracedecay-agent-hosts/src/agents/kiro/tests.rs index c2644d94c0..4d2d03c618 100644 --- a/crates/tracedecay-agent-hosts/src/agents/kiro/tests.rs +++ b/crates/tracedecay-agent-hosts/src/agents/kiro/tests.rs @@ -1175,7 +1175,7 @@ fn cli_lifecycle_leaves_an_ambient_kiro_home_sentinel_untouched() { } /// Kiro's documented hook entry schema is `command` plus an optional -/// `matcher` — an undocumented field (the old `timeout_ms`) is schema noise +/// `matcher`, an undocumented field (the old `timeout_ms`) is schema noise /// Kiro never reads and must not be written. #[test] fn managed_agent_hook_entries_carry_only_documented_fields() { diff --git a/crates/tracedecay-agent-hosts/src/agents/mcp_registration.rs b/crates/tracedecay-agent-hosts/src/agents/mcp_registration.rs index 2411e72509..7e9dacee80 100644 --- a/crates/tracedecay-agent-hosts/src/agents/mcp_registration.rs +++ b/crates/tracedecay-agent-hosts/src/agents/mcp_registration.rs @@ -84,8 +84,8 @@ enum McpUninstallOutcome { /// Remove the tracedecay MCP entry under `root_key` from a host config. /// /// Runs under the host-file write lock like [`install_mcp_server_entry`]. A -/// config that exists but cannot be parsed is a typed error — reporting a -/// clean uninstall over a corrupt config would fabricate state — and callers +/// config that exists but cannot be parsed is a typed error, reporting a +/// clean uninstall over a corrupt config would fabricate state, and callers /// decide whether to keep going across the remaining hosts. Every rewrite or /// removal of the existing file leaves a `.bak` (issue #63) and publishes /// through the durable conditional write/remove shared by every host-file @@ -191,7 +191,7 @@ pub struct McpDoctorLabels<'a> { pub product: &'a str, /// Subject of the pass line, rendered as "{registered} in {path}". pub registered: &'a str, - /// Subject of the fail line, rendered as "{missing} in {path} — run ...". + /// Subject of the fail line, rendered as "{missing} in {path}, run ...". pub missing: &'a str, } @@ -243,7 +243,7 @@ pub fn report_mcp_registration( )); } else { dc.fail(&format!( - "{} in {} — run `tracedecay install --agent {}`", + "{} in {}, run `tracedecay install --agent {}`", labels.missing, config_path.display(), labels.agent_id @@ -264,7 +264,7 @@ pub fn doctor_check_mcp_registration( ) -> Option { if !config_path.exists() { dc.warn(&format!( - "{} not found — run `tracedecay install --agent {}` if you use {}", + "{} not found, run `tracedecay install --agent {}` if you use {}", config_path.display(), labels.agent_id, labels.product @@ -300,7 +300,7 @@ pub(crate) fn doctor_check_prompt_contains_tracedecay( dc.pass(&format!("{subject} contains tracedecay rules")); } else { dc.fail(&format!( - "{subject} missing tracedecay rules — run `tracedecay install --agent {agent_id}`" + "{subject} missing tracedecay rules, run `tracedecay install --agent {agent_id}`" )); } } diff --git a/crates/tracedecay-agent-hosts/src/agents/mod.rs b/crates/tracedecay-agent-hosts/src/agents/mod.rs index 60b0870c1c..7ac2238478 100644 --- a/crates/tracedecay-agent-hosts/src/agents/mod.rs +++ b/crates/tracedecay-agent-hosts/src/agents/mod.rs @@ -269,7 +269,7 @@ pub trait AgentIntegration { /// [`AgentIntegration::prepare_non_interactive_install`] returns: doctor /// needs the same fact without an `InstallContext` and without staging /// anything. Every integration returning `Some` here must also return - /// [`NonInteractiveInstallOutcome::DeferredUserAction`] from preflight — + /// [`NonInteractiveInstallOutcome::DeferredUserAction`] from preflight, /// otherwise doctor would downgrade a state that an unattended reinstall /// could actually have repaired. fn interactive_activation_guidance(&self) -> Option { @@ -282,7 +282,7 @@ pub trait AgentIntegration { /// /// The removal twin of [`AgentIntegration::interactive_activation_guidance`]. /// A host that activates only through an interactive UI also *deactivates* - /// only there, so `Uninstall` must refuse while the registration stands — + /// only there, so `Uninstall` must refuse while the registration stands, /// deleting the receipt-owned artifacts underneath a live registration /// leaves the host resolving a bundle that no longer exists. The refusal /// travels as [`host_bundle::HostBundleError::NativeRemovalRequired`], @@ -304,7 +304,7 @@ pub trait AgentIntegration { /// The default reports [`UpdatePluginOutcome::ConfigOnly`]: most agents /// keep their entire tracedecay integration inside shared config files /// (MCP entries, hook blocks, prompt rules), so there is nothing to - /// refresh that would not be a config write — `tracedecay reinstall` + /// refresh that would not be a config write. `tracedecay reinstall` /// remains the path that reconciles those. fn update_plugin(&self, _ctx: &InstallContext) -> Result { Ok(UpdatePluginOutcome::ConfigOnly) @@ -317,7 +317,7 @@ pub trait AgentIntegration { /// empty list for agents that either do not distribute managed skills /// or have no detected tracedecay installation under `home`. /// - /// Implementors must never create a new installation here — only refresh + /// Implementors must never create a new installation here, only refresh /// artifacts already owned by a catalog receipt. fn export_managed_skills( &self, @@ -568,7 +568,7 @@ pub enum UpdatePluginOutcome { /// Generated artifacts were refreshed at these locations. Refreshed(Vec), /// The integration ships generated artifacts, but none were detected on - /// this machine — nothing was written. + /// this machine, nothing was written. NotInstalled, /// The integration only writes shared config files; there are no /// tracedecay-generated artifacts to refresh without touching config. @@ -792,7 +792,7 @@ impl DoctorCounters { #[macro_export] macro_rules! cli_fallback_args_invocation_lit { () => { - "`tracedecay tool --args ''` — the same JSON arguments object as the MCP tool; \ + "`tracedecay tool --args ''`, the same JSON arguments object as the MCP tool; \ pipe it via `--args -` (a quoted heredoc) when it contains quotes or newlines" }; } diff --git a/crates/tracedecay-agent-hosts/src/agents/opencode.rs b/crates/tracedecay-agent-hosts/src/agents/opencode.rs index 44058fb3f2..b905da3d8c 100644 --- a/crates/tracedecay-agent-hosts/src/agents/opencode.rs +++ b/crates/tracedecay-agent-hosts/src/agents/opencode.rs @@ -9,7 +9,7 @@ //! through the host's own CLI: the plugin deployment already *is* `OpenCode`'s //! own discovery contract, `opencode mcp add` is interactive, and the LSP and //! prompt registrations have no host command at all. [`plugin_cli`] is the -//! decision record — including why driving `opencode plugin ` would +//! decision record, including why driving `opencode plugin ` would //! double-load the plugin and could not be undone. mod plugin_cli; @@ -364,7 +364,7 @@ fn local_config_has_tracedecay(project_root: &Path) -> bool { // Config path resolution // --------------------------------------------------------------------------- -/// Honors an absolute `$XDG_CONFIG_HOME`, including locations outside `HOME` — +/// Honors an absolute `$XDG_CONFIG_HOME`, including locations outside `HOME`, /// but only when `home` *is* this process user's home. See /// [`ambient_xdg_config_home`]. fn opencode_config_path(home: &Path) -> std::path::PathBuf { @@ -375,8 +375,8 @@ fn opencode_config_path(home: &Path) -> std::path::PathBuf { /// /// `$XDG_CONFIG_HOME` names *this process user's* config root, so it only /// answers for a caller that is resolving that same user's home. A caller that -/// names a different root — a per-home sweep, a managed-skill export -/// destination scan, a test sandbox — must stay inside the root it named. +/// names a different root, a per-home sweep, a managed-skill export +/// destination scan, a test sandbox, must stay inside the root it named. /// /// Reading it unconditionally let a lifecycle export sweep that was handed a /// sandbox `home` resolve OpenCode to the operator's real @@ -417,8 +417,8 @@ fn opencode_config_path_for(home: &Path, xdg: Option<&std::ffi::OsStr>) -> std:: /// TraceDecay's own managed artifacts (`plugins/`, `agent/`, `command/`, /// `skills/`), which a component-set transaction writes between the moment the /// registration authority confirms a revision and the moment it applies. Keying -/// on the directory therefore moved this path — and with it the hashed -/// registration path list — mid-transaction, so every apply rechecked against a +/// on the directory therefore moved this path, and with it the hashed +/// registration path list, mid-transaction, so every apply rechecked against a /// different revision and rolled back with `StalePreview`. No managed artifact /// ever writes an `AGENTS.md`, so file existence is stable across a deploy. /// @@ -646,7 +646,7 @@ fn install_mcp_server(config_path: &Path, tracedecay_bin: &str) -> Result<()> { /// /// `plugin` is the one key here that *is* owned by a host command TraceDecay /// declines to drive, so forging its effect is refused on both the install and -/// uninstall paths — see +/// uninstall paths, see /// [`plugin_cli::ensure_host_owned_plugin_registration_untouched`]. #[hotpath::measure(label = "hosts.agent.opencode.registration_install")] fn install_registration_entries( @@ -809,7 +809,7 @@ fn merge_registration_entries( /// /// Stays TraceDecay-written: `OpenCode` has no command that edits instruction /// files, and `AGENTS.md` is operator-editable Markdown discovered by -/// convention — no host-owned state to emulate. The block is marker-delimited +/// convention, no host-owned state to emulate. The block is marker-delimited /// so a refresh replaces exactly what TraceDecay wrote. fn install_prompt_rules(prompt_path: &Path) -> Result<()> { let block = super::prompt_rules::standard_prompt_rules( @@ -907,7 +907,7 @@ fn strip_registration_entries( ) -> Result<(OpenCodeRegistrationRemoval, TextFileMutation)> { let mut config = JsonConfigDialect::Json.parse_for_edit(config_path, existing)?; // Uninstall drops only what TraceDecay wrote. A plugin registration the - // host recorded through `opencode plugin` is not ours to remove — and + // host recorded through `opencode plugin` is not ours to remove, and // OpenCode ships no removal command we could drive instead, which is one // of the reasons that command is not adopted for install either. let host_plugin_before = plugin_cli::host_owned_plugin_registration(&config); @@ -988,7 +988,7 @@ fn doctor_check_config(dc: &mut DoctorCounters, home: &Path) { let config_path = opencode_config_path(home); if !config_path.exists() { dc.warn(&format!( - "{} not found — run `tracedecay install --agent opencode` if you use OpenCode", + "{} not found, run `tracedecay install --agent opencode` if you use OpenCode", config_path.display() )); return; @@ -998,7 +998,7 @@ fn doctor_check_config(dc: &mut DoctorCounters, home: &Path) { let mcp_entry = &config["mcp"]["tracedecay"]; if !mcp_entry.is_object() { dc.fail(&format!( - "MCP server NOT registered in {} — run `tracedecay install --agent opencode`", + "MCP server NOT registered in {}, run `tracedecay install --agent opencode`", config_path.display() )); return; @@ -1013,7 +1013,7 @@ fn doctor_check_config(dc: &mut DoctorCounters, home: &Path) { if has_serve { dc.pass("MCP server args include \"serve\""); } else { - dc.fail("MCP server args missing \"serve\" — run `tracedecay install --agent opencode`"); + dc.fail("MCP server args missing \"serve\", run `tracedecay install --agent opencode`"); } let lsp = &config["lsp"]["tracedecay"]; let lsp_command = lsp["command"].as_array(); @@ -1030,9 +1030,7 @@ fn doctor_check_config(dc: &mut DoctorCounters, home: &Path) { if has_bridge && has_extensions && duplicate_avoidance { dc.pass("custom TraceDecay LSP bridge configured with duplicate-analyzer avoidance"); } else { - dc.fail( - "custom TraceDecay LSP config is stale — run `tracedecay install --agent opencode`", - ); + dc.fail("custom TraceDecay LSP config is stale, run `tracedecay install --agent opencode`"); } } @@ -1057,7 +1055,7 @@ fn doctor_check_plugin(dc: &mut DoctorCounters, home: &Path) { )); } else { dc.fail(&format!( - "native edit/idle plugin missing from {} — run `tracedecay install --agent opencode`", + "native edit/idle plugin missing from {}, run `tracedecay install --agent opencode`", plugin_path.display() )); } diff --git a/crates/tracedecay-agent-hosts/src/agents/opencode/plugin_cli.rs b/crates/tracedecay-agent-hosts/src/agents/opencode/plugin_cli.rs index 9a4ba011a5..464ba60d62 100644 --- a/crates/tracedecay-agent-hosts/src/agents/opencode/plugin_cli.rs +++ b/crates/tracedecay-agent-hosts/src/agents/opencode/plugin_cli.rs @@ -1,4 +1,4 @@ -//! Where OpenCode's own plugin CLI owns TraceDecay's plugin — and where it +//! Where OpenCode's own plugin CLI owns TraceDecay's plugin, and where it //! does not. //! //! Every other host in this crate that owns its plugin lifecycle gets that @@ -32,7 +32,7 @@ //! config dir plus each `.opencode` dir found walking up from the project. //! TraceDecay's deployed `plugins/tracedecay.ts` is therefore loaded by //! OpenCode's *own* discovery contract, with no registration step at all. The -//! file deployment is not emulation of host-private state — it is the host's +//! file deployment is not emulation of host-private state, it is the host's //! documented directory contract, which is precisely the condition under which //! the host-capability doctrine does **not** demand CLI adoption. //! @@ -47,7 +47,7 @@ //! * **There is no removal counterpart.** OpenCode ships `plugin` only; there //! is no `plugin remove`/`uninstall` subcommand. An adopted install would //! leave TraceDecay editing the host-recorded `plugin` array by hand on -//! uninstall — strictly more emulation than today, not less. +//! uninstall, strictly more emulation than today, not less. //! * **The project-local scope cannot be targeted anyway.** `opencode plugin` //! without `--global` resolves its scope from the process working directory, //! and [`super::super::host_cli::run_host_cli`] admits the profile home as @@ -90,7 +90,7 @@ const HOST_PLUGIN_DISCOVERY_EXTENSIONS: &[&str] = &["ts", "js"]; /// /// The host's glob is exactly one level deep, so a file nested in a /// sub-directory (a staged *module*, which is what `opencode plugin` would -/// need) is deliberately **not** discovered — that asymmetry is the whole +/// need) is deliberately **not** discovered, that asymmetry is the whole /// reason the CLI is not adopted here, and the tests below pin it. pub(super) fn is_host_discovered_plugin_path(path: &Path) -> bool { let discovered_extension = path @@ -123,7 +123,7 @@ pub(super) fn host_owned_plugin_registration( /// deliberately does not drive it, and therefore TraceDecay must not write its /// effect either. Emulating the key would be indistinguishable on disk from a /// real `opencode plugin` install while carrying none of the host's own -/// manifest and engine validation — exactly the half-emulated state the +/// manifest and engine validation, exactly the half-emulated state the /// host-capability doctrine forbids. A guard is cheaper than the incident. pub(super) fn ensure_host_owned_plugin_registration_untouched( before: Option<&serde_json::Value>, @@ -154,7 +154,7 @@ mod tests { /// The global deployment path must stay one OpenCode discovers on its own. /// If it ever moves out of `plugins/` or stops being a `.ts` file, the /// host silently stops loading the plugin and nothing else in this crate - /// would notice — the config would still validate. + /// would notice, the config would still validate. #[test] fn the_deployed_global_plugin_path_is_discovered_by_the_hosts_own_loader() { let deployed = Path::new(super::super::OPENCODE_PLUGIN_RELATIVE); @@ -167,8 +167,8 @@ mod tests { } /// The executable form of the adoption decision: a staged *module* - /// directory — the only shape `opencode plugin` accepts, because it needs - /// a `package.json` entrypoint — is NOT discovered by the host's own + /// directory, the only shape `opencode plugin` accepts, because it needs + /// a `package.json` entrypoint, is NOT discovered by the host's own /// loader. Driving the CLI would therefore add a second, distinct plugin /// origin next to the discovered file rather than replacing it. #[test] @@ -282,7 +282,7 @@ mod tests { ); } - /// An untouched key — the steady state — passes. + /// An untouched key, the steady state, passes. #[test] fn an_untouched_plugin_registration_passes_the_guard() { let before = json!(["operator-plugin"]); diff --git a/crates/tracedecay-agent-hosts/src/agents/plugin_bundle.rs b/crates/tracedecay-agent-hosts/src/agents/plugin_bundle.rs index 808f6c2c52..eabfc54480 100644 --- a/crates/tracedecay-agent-hosts/src/agents/plugin_bundle.rs +++ b/crates/tracedecay-agent-hosts/src/agents/plugin_bundle.rs @@ -5,35 +5,35 @@ //! agent format. //! //! Layout of `plugin/`: -//! - `plugin/skills/*/SKILL.md` — the shared model-invocable skills (every +//! - `plugin/skills/*/SKILL.md`, the shared model-invocable skills (every //! `SKILL.md` directory under `plugin/skills/`). All five hosts deploy the //! full set; the workflow dispatcher skills were removed (their behavior //! lives in the native slash commands below), so no host filters the skill //! set today. The `cursor_skill_files` filter is kept as a guard against a //! dispatcher skill being reintroduced. -//! - `plugin/overlays/cursor/commands/tracedecay-*.md` — Cursor 1.6+ native +//! - `plugin/overlays/cursor/commands/tracedecay-*.md`, Cursor 1.6+ native //! slash commands, one per workflow slug, deployed to `commands/.md`. //! These provide the explicit workflow dispatch (no dispatcher *skills*). -//! - `plugin/agents/*.md` — canonical subagents. Claude deploys them verbatim; +//! - `plugin/agents/*.md`, canonical subagents. Claude deploys them verbatim; //! build.rs derives Cursor markdown and Codex TOML adapters from them. -//! - `plugin/commands/*.md` — Claude slash commands. `build.rs` embeds every +//! - `plugin/commands/*.md`, Claude slash commands. `build.rs` embeds every //! file in that directory and the paired Cursor overlay. Adding a command is //! adding the two Markdown files; there is no second list in this module. -//! - `plugin/rules/*.mdc` — Cursor rules. -//! - `plugin/hooks/hooks-.json` — per-host hook wiring; each deploys to +//! - `plugin/rules/*.mdc`, Cursor rules. +//! - `plugin/hooks/hooks-.json`, per-host hook wiring; each deploys to //! `hooks/hooks.json`. //! - `plugin/.claude-plugin/{plugin,marketplace}.json`, //! `plugin/.cursor-plugin/plugin.json`, `plugin/.codex-plugin/plugin.json`, -//! `plugin/.kimi-plugin/plugin.json` — host manifests (deploy to the same +//! `plugin/.kimi-plugin/plugin.json`, host manifests (deploy to the same //! dot-dir path). Kimi's manifest carries hooks inline (`PostToolUse`/ //! `Stop`) and omits MCP: the installer registers `mcpServers.tracedecay` //! in Kimi's session/user `mcp.json` so the host launches from the workspace. -//! - `plugin/opencode/{tracedecay.ts,tracedecay-mcp.ts,opencode.registration.json}` -//! — OpenCode native plugin, MCP companion, and MCP/LSP registration. +//! - `plugin/opencode/{tracedecay.ts,tracedecay-mcp.ts,opencode.registration.json}`, +//! OpenCode native plugin, MCP companion, and MCP/LSP registration. //! OpenCode has no `plugin.json`. -//! - `plugin/.mcp.json` — shared Claude/Codex MCP config (byte-identical); -//! `plugin/mcp-cursor.json` — Cursor MCP config (deploys to `mcp.json`). -//! - `plugin/README-.md` — per-host README (Claude/Cursor/Codex/Kimi +//! - `plugin/.mcp.json`, shared Claude/Codex MCP config (byte-identical); +//! `plugin/mcp-cursor.json`, Cursor MCP config (deploys to `mcp.json`). +//! - `plugin/README-.md`, per-host README (Claude/Cursor/Codex/Kimi //! deploy to `README.md`; OpenCode's README is source documentation). //! //! Composed per-host view = `GENERATED_SKILL_FILES` (recursively embedded from @@ -49,7 +49,7 @@ pub(crate) fn stamp_manifest_version(raw: &str) -> Result { } /// Stamp the version and let the host apply manifest edits on the parsed -/// `Value` before the single serialize — hosts that post-process the manifest +/// `Value` before the single serialize, hosts that post-process the manifest /// (e.g. Codex stripping `hooks` from repo-local bundles) avoid a second /// parse/pretty-print round-trip and cannot drift from this output contract. pub(crate) fn stamp_manifest_version_with( @@ -309,7 +309,7 @@ pub fn cursor_native_extension_files() -> Vec<(&'static str, &'static str)> { /// Files Codex deploys: manifest + every file under `plugin/skills/` /// (`SKILL.md` plus support files). Codex ships no agents/commands/rules. /// The host-bundle catalog deploys the rendered variants of this inventory via -/// `agents::codex::rendered_global_plugin_files` — the raw templates here are +/// `agents::codex::rendered_global_plugin_files`, the raw templates here are /// not directly installable (`hooks/hooks.json` is an empty scaffold). pub fn codex_files() -> Vec<(&'static str, &'static str)> { compose(&[CODEX_MANIFEST_FILES], all_skill_files()) diff --git a/crates/tracedecay-agent-hosts/src/agents/text_file_transaction.rs b/crates/tracedecay-agent-hosts/src/agents/text_file_transaction.rs index 6a84c4fa05..68567e0ae2 100644 --- a/crates/tracedecay-agent-hosts/src/agents/text_file_transaction.rs +++ b/crates/tracedecay-agent-hosts/src/agents/text_file_transaction.rs @@ -9,7 +9,6 @@ use std::path::Path; use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt}; use cap_std::ambient_authority; use cap_std::fs::{Dir, OpenOptions as CapOpenOptions}; -use fs2::FileExt; use same_file::Handle; use tracedecay_domain::canonical_text::sha256_hex; @@ -46,7 +45,7 @@ impl Drop for HostFileWriteLock { "host config lock file could not be unlinked while held" ); } - if let Err(error) = FileExt::unlock(self.handle.as_file()) { + if let Err(error) = self.handle.as_file().unlock() { tracing::warn!( lock_name = %self.lock_name, error = %error, @@ -141,10 +140,9 @@ pub(super) fn lock_host_file_write(path: &Path) -> Result { ), }); } - lock.lock_exclusive() - .map_err(|error| TraceDecayError::Config { - message: format!("failed to lock host config {}: {error}", path.display()), - })?; + lock.lock().map_err(|error| TraceDecayError::Config { + message: format!("failed to lock host config {}: {error}", path.display()), + })?; let locked = Handle::from_file(lock).map_err(|error| TraceDecayError::Config { message: format!( "failed to identify host config lock {}: {error}", diff --git a/crates/tracedecay-agent-hosts/src/agents/vibe.rs b/crates/tracedecay-agent-hosts/src/agents/vibe.rs index 86e1028d36..3ce327b924 100644 --- a/crates/tracedecay-agent-hosts/src/agents/vibe.rs +++ b/crates/tracedecay-agent-hosts/src/agents/vibe.rs @@ -7,7 +7,7 @@ //! **Manual by necessity, not by preference (verified 2026-08-08).** The owner //! policy is CLI-first, so this config write needs a justification. Vibe's //! `vibe mcp add` is genuinely non-interactive and `vibe mcp remove ` -//! exists — but `add` is **remote-transport only** (`--url`, `--transport`, +//! exists, but `add` is **remote-transport only** (`--url`, `--transport`, //! `--header`, `--api-key-*`). It has no `--command`/`--args`, so a local //! stdio server, which is exactly what `tracedecay serve` is, has no //! representation on that command line; Mistral's own documentation registers @@ -368,11 +368,11 @@ fn doctor_check_registration(dc: &mut DoctorCounters, config: &Path, prompt: &Pa dc.pass(&format!("MCP server registered in {}", config.display())) } HostBundleRegistrationStateV1::Missing => dc.warn(&format!( - "{} has no tracedecay MCP server — run `tracedecay install --agent vibe`", + "{} has no tracedecay MCP server, run `tracedecay install --agent vibe`", config.display() )), HostBundleRegistrationStateV1::Repairable => dc.fail(&format!( - "MCP server in {} is foreign-modified — run `tracedecay repair --agent vibe`", + "MCP server in {} is foreign-modified, run `tracedecay repair --agent vibe`", config.display() )), HostBundleRegistrationStateV1::Corrupt => { diff --git a/crates/tracedecay-agent-hosts/src/agents/zed.rs b/crates/tracedecay-agent-hosts/src/agents/zed.rs index f9aa23dace..d040933853 100644 --- a/crates/tracedecay-agent-hosts/src/agents/zed.rs +++ b/crates/tracedecay-agent-hosts/src/agents/zed.rs @@ -248,7 +248,7 @@ fn doctor_check_registration( ) { if !config.exists() { dc.warn(&format!( - "{} not found — run `tracedecay install --agent zed` if you use {}", + "{} not found, run `tracedecay install --agent zed` if you use {}", config.display(), product )); diff --git a/crates/tracedecay-agent-hosts/src/hooks/analytics.rs b/crates/tracedecay-agent-hosts/src/hooks/analytics.rs index f2218d50ec..ade1360cd4 100644 --- a/crates/tracedecay-agent-hosts/src/hooks/analytics.rs +++ b/crates/tracedecay-agent-hosts/src/hooks/analytics.rs @@ -138,9 +138,9 @@ impl HookTimingSpan { // parse legacy configuration, so a daemon-published snapshot is the // only authority consulted here. A hook subprocess starts with an // empty snapshot cache, so treating "no authority" as "off" silenced - // every `hook_completed` row in production while `hook_invoked` — the + // every `hook_completed` row in production while `hook_invoked`, the // other half of the same span, written by the same unconditional - // recorder — kept flowing. That renders every real hook as invoked but + // recorder, kept flowing. That renders every real hook as invoked but // never finished. Only an authority that explicitly says timings are // off suppresses the completion row. let enabled = root @@ -493,7 +493,7 @@ fn record_hook_invoked_named( event_json: &str, parsed: &Value, ) -> HookTimingSpan { - // Length only — never persist event content, prompts, tools, credentials, or paths here. + // Length only, never persist event content, prompts, tools, credentials, or paths here. let payload_bytes = measure_host_event_payload_bytes(event_json); let prompt_category = inferred_prompt_category(parsed); record_hook_analytics( @@ -673,7 +673,7 @@ pub(super) fn record_hook_analytics( /// A hook fires in whatever directory the agent happens to be in, so it must /// not be the thing that decides a directory is a project. When no authority /// already names this checkout, analytics go to the profile-wide file rather -/// than to a store shard minted from the path — writing here used to create +/// than to a store shard minted from the path, writing here used to create /// `projects/proj_/` for directories that never became projects, and /// those shards then outnumbered the real stores. /// diff --git a/crates/tracedecay-agent-hosts/src/hooks/analytics/readiness.rs b/crates/tracedecay-agent-hosts/src/hooks/analytics/readiness.rs index 7206d60bf1..eb960ba60b 100644 --- a/crates/tracedecay-agent-hosts/src/hooks/analytics/readiness.rs +++ b/crates/tracedecay-agent-hosts/src/hooks/analytics/readiness.rs @@ -118,7 +118,7 @@ pub(crate) struct TimeoutOutcomesByHost { pub(crate) host: ReadinessHost, pub(crate) timed_out_true: u64, pub(crate) timed_out_false: u64, - /// `timeout.timed_out` null/missing — distinct from measured false. + /// `timeout.timed_out` null/missing, distinct from measured false. pub(crate) timed_out_unavailable: u64, pub(crate) budget_ms_present: u64, pub(crate) budget_ms_absent: u64, @@ -304,7 +304,7 @@ type MutableDispositionCounts = BTreeMap HookCompletedReadinessDistributions { @@ -340,7 +340,7 @@ pub fn aggregate_hook_completed_readiness(rows: &[Value]) -> HookCompletedReadin rows_folded_to_other_host = rows_folded_to_other_host.saturating_add(1); } - // TRUE host IPC RTT. Null means unavailable — never treat as 0 RTT. + // TRUE host IPC RTT. Null means unavailable, never treat as 0 RTT. let latency = latency_by_host.entry(host).or_default(); latency .wall_time_us diff --git a/crates/tracedecay-agent-hosts/src/hooks/analytics/tests.rs b/crates/tracedecay-agent-hosts/src/hooks/analytics/tests.rs index 24c75fbb88..7c12399f07 100644 --- a/crates/tracedecay-agent-hosts/src/hooks/analytics/tests.rs +++ b/crates/tracedecay-agent-hosts/src/hooks/analytics/tests.rs @@ -102,8 +102,8 @@ fn native_dispatch_dispositions_remain_distinct_in_telemetry() { ); } -/// A hook subprocess never has a published snapshot — nothing in the hook -/// path opens a store or contacts the daemon before the span is built — so +/// A hook subprocess never has a published snapshot, nothing in the hook +/// path opens a store or contacts the daemon before the span is built, so /// treating absence as "timings off" suppressed `hook_completed` for every /// real hook while `hook_invoked` kept being written. Absence must behave /// like the invocation half; only an authority that says off turns it off. diff --git a/crates/tracedecay-agent-hosts/src/hooks/cursor.rs b/crates/tracedecay-agent-hosts/src/hooks/cursor.rs index c4086afadd..0a5cdf9751 100644 --- a/crates/tracedecay-agent-hosts/src/hooks/cursor.rs +++ b/crates/tracedecay-agent-hosts/src/hooks/cursor.rs @@ -45,7 +45,7 @@ const CURSOR_FILE_PATH_FIELDS: &[&str] = &[ /// Emits soft `additional_context` hints steering exploration tools (Grep, /// Glob, Read, semantic search, shell `rg`) toward tracedecay MCP tools. /// Registered on `postToolUse` rather than `preToolUse` because Cursor's -/// documented `preToolUse` output schema has no context-injection field — +/// documented `preToolUse` output schema has no context-injection field, /// `additional_context` is only honored on `postToolUse`. The hook runs /// unmatched (the docs enumerate no matcher value for Cursor's semantic /// search tool) and irrelevant tools fail open with no output. Each hint @@ -180,7 +180,7 @@ pub fn cursor_post_tool_use_decision(runtime: &HookRuntimeV1, event_json: &str) /// once per Cursor session across short-lived hook processes. Hints are also /// suppressed entirely when the workspace has no tracedecay index (suggesting /// tracedecay tools there would be misleading). When no session id is present -/// the hint is emitted as-is — dedupe is impossible but the hint is still +/// the hint is emitted as-is, dedupe is impossible but the hint is still /// useful (fail-open). fn cursor_hint_root( event_json: &str, diff --git a/crates/tracedecay-agent-hosts/src/hooks/dispatch.rs b/crates/tracedecay-agent-hosts/src/hooks/dispatch.rs index 011e4b6200..53ff3b73cd 100644 --- a/crates/tracedecay-agent-hosts/src/hooks/dispatch.rs +++ b/crates/tracedecay-agent-hosts/src/hooks/dispatch.rs @@ -34,7 +34,7 @@ use super::daemon_ports::{ pub(crate) enum HookDispatch { NotApplicable, - /// The native dispatcher recognised the event but could not take ownership of it — no + /// The native dispatcher recognised the event but could not take ownership of it, no /// published binding, an unreadable store layout, or an envelope it could /// not decode. The disposition is still worth recording, but the event has /// not been admitted anywhere, so callers must fall back to their ordinary diff --git a/crates/tracedecay-agent-hosts/src/hooks/hint_outcomes.rs b/crates/tracedecay-agent-hosts/src/hooks/hint_outcomes.rs index 4a5e96f37a..4f0281a9dc 100644 --- a/crates/tracedecay-agent-hosts/src/hooks/hint_outcomes.rs +++ b/crates/tracedecay-agent-hosts/src/hooks/hint_outcomes.rs @@ -3,16 +3,16 @@ //! Hooks record a `hint_emitted` analytics event (carrying a first-class //! `hint_id`, `hint_category`, `session_id`, and `hook_` provider) every //! time a soft hint surfaces. Whether the model *acted* on that hint is not -//! known at emit time — it depends on which tools fire next. This module closes +//! known at emit time, it depends on which tools fire next. This module closes //! that loop after the fact: for each emitted hint that has not yet been //! resolved, it inspects the session's ingested [`session_messages`] activity //! *after* the hint timestamp and appends a new `hint_outcome` analytics event: //! -//! * `acted` — a tracedecay tool matching the hint's category fired inside the +//! * `acted` , a tracedecay tool matching the hint's category fired inside the //! bounded horizon after the hint. -//! * `ignored` — the horizon closed (see below) with post-hint activity but no +//! * `ignored`, the horizon closed (see below) with post-hint activity but no //! matching tool. -//! * *(unresolved)* — the session has no ingested tool activity after the hint +//! * *(unresolved)*, the session has no ingested tool activity after the hint //! yet, so nothing is written and a later pass re-evaluates it. //! //! ## Horizon @@ -62,7 +62,7 @@ const HORIZON_TOOL_STEPS: usize = 25; /// Upper bound on session-message rows fetched per hint when scanning for /// post-hint tool activity. Comfortably exceeds [`HORIZON_TOOL_STEPS`] so the -/// horizon — not this cap — decides the window. +/// horizon, not this cap, decides the window. const SESSION_SCAN_LIMIT: u32 = 256; /// Upper bound on emitted/outcome hint events pulled per correlation pass. diff --git a/crates/tracedecay-agent-hosts/src/hooks/hint_outcomes/settlement.rs b/crates/tracedecay-agent-hosts/src/hooks/hint_outcomes/settlement.rs index 023a0076f7..376b7b8d47 100644 --- a/crates/tracedecay-agent-hosts/src/hooks/hint_outcomes/settlement.rs +++ b/crates/tracedecay-agent-hosts/src/hooks/hint_outcomes/settlement.rs @@ -7,7 +7,7 @@ //! [`settle_project_hint_outcomes`] pass. Settlement imports the hook //! JSONL tail (where hooks record `hint_emitted`) into the durable //! `analytics_events` authority and then correlates outcomes into the same -//! table — the one the `tracedecay_analytics` hints section and the +//! table, the one the `tracedecay_analytics` hints section and the //! dashboard analytics API already read. use std::path::Path; @@ -191,7 +191,7 @@ pub enum HintOutcomeSettlement { impl HintOutcomeSettlement { /// Renders the settlement into the ingest output object. Every state is - /// visible — an unavailable authority or failed pass is reported, not + /// visible, an unavailable authority or failed pass is reported, not /// silently dropped. pub fn as_json(&self) -> Value { match self { @@ -228,7 +228,7 @@ impl HintOutcomeSettlement { /// unresolved hints against the project session store. Callers resolve /// `sources` themselves (production: `analytics_bridge::hook_import_sources`; /// fixtures: isolated temp files) so this pass never touches ambient -/// operator state on its own. Best-effort by contract — failures come back +/// operator state on its own. Best-effort by contract, failures come back /// as typed [`HintOutcomeSettlement`] states and are logged here so every /// caller inherits the same observability. #[hotpath::measure(label = "hosts.hooks.hint_outcomes.settle", future = true)] @@ -313,7 +313,7 @@ pub async fn settle_project_hint_outcomes( /// Every stage carries only what settlement proved: the idempotent /// `hint_outcome` write is the exactly-once terminal ledger, so /// `invoked`/`terminal` count only hints settled this pass and cross-pass -/// sums never double-count. `independently_useful` = `acted` only — the +/// sums never double-count. `independently_useful` = `acted` only, the /// correlator behaviorally verified a category-matching tool fired in the /// independently ingested session activity (never display/self-report). /// `repeat_useful` stays 0 (settlement never verifies repeat use), unresolved diff --git a/crates/tracedecay-agent-hosts/src/hooks/mod.rs b/crates/tracedecay-agent-hosts/src/hooks/mod.rs index 75422076c4..b2dd9bce1b 100644 --- a/crates/tracedecay-agent-hosts/src/hooks/mod.rs +++ b/crates/tracedecay-agent-hosts/src/hooks/mod.rs @@ -85,8 +85,8 @@ pub fn aggregate_hook_completed_readiness(rows: &[Value]) -> HookCompletedReadin /// capture path did not, so each capture-only callback fired silently: the /// project's `hook_analytics.jsonl` gained no row, `tracedecay analytics` /// reported the host as never having invoked a hook, and that is exactly the -/// signal a broken install gives. Attribution follows the host's own event name -/// — read the way the Hermes terminal-receipt handler reads it — so a capture +/// signal a broken install gives. Attribution follows the host's own event name, +/// read the way the Hermes terminal-receipt handler reads it, so a capture /// row is indistinguishable from the response row the same event produces. /// /// `hook_name` overrides that read for the one surface whose payload carries no @@ -988,7 +988,7 @@ fn deduped_project_hint_with_id( }; let mut dedupe = tool_hints::ToolHintDedupe::load_or_default(&path); let decision = dedupe.decide(&session_id, hint.category); - // Every decision — including the suppressed ones — advances the persisted + // Every decision, including the suppressed ones, advances the persisted // budget, so the save is unconditional. let _ = dedupe.save(&path); diff --git a/crates/tracedecay-agent-hosts/src/hooks/steering.rs b/crates/tracedecay-agent-hosts/src/hooks/steering.rs index ab90d989a3..3283685d10 100644 --- a/crates/tracedecay-agent-hosts/src/hooks/steering.rs +++ b/crates/tracedecay-agent-hosts/src/hooks/steering.rs @@ -45,7 +45,7 @@ pub fn build_cursor_session_context( append_tracedecay_bootstrap_context(&mut s); s.push_str("Workflow skills: tracedecay:"); s.push_str(&CURSOR_PLUGIN_SKILLS.join(", ")); - s.push_str(" — each maps a common workflow stage to the right tracedecay tools.\n"); + s.push_str(", each maps a common workflow stage to the right tracedecay tools.\n"); if let Some(saved) = tokens_saved.filter(|saved| *saved > 0) { s.push_str("Tokens saved by tracedecay this session: "); s.push_str(&saved.to_string()); @@ -63,7 +63,7 @@ pub(super) fn index_status_line(initialized: bool, staleness_hint: Option<&str>) None => "tracedecay index status: initialized.\n".to_string(), } } else { - "tracedecay index status: no project index found in this workspace — \ + "tracedecay index status: no project index found in this workspace. \ run `tracedecay init` to enable tracedecay MCP tools.\n" .to_string() } @@ -138,7 +138,7 @@ pub fn build_codex_session_context_for_workspace( } } HookWorkspaceStatus::UnindexedProject => s.push_str( - "Index status: no project index found in this code workspace — \ + "Index status: no project index found in this code workspace. \ run `tracedecay init` to enable tracedecay code-graph tools.\n", ), HookWorkspaceStatus::Generic => {} diff --git a/crates/tracedecay-agent-hosts/src/hooks/store_layout.rs b/crates/tracedecay-agent-hosts/src/hooks/store_layout.rs index 0e031ee526..b2ffc57e39 100644 --- a/crates/tracedecay-agent-hosts/src/hooks/store_layout.rs +++ b/crates/tracedecay-agent-hosts/src/hooks/store_layout.rs @@ -5,14 +5,14 @@ //! `hook_completed` from the timing span's `Drop`), once in the native Hook //! `prepare_bound_hook`, once per surviving hint in the dedupe path, and twice //! more in the memory-injection seen-facts path. Every one of those repeats the -//! same filesystem work — reading the enrollment and repository identity +//! same filesystem work, reading the enrollment and repository identity //! markers, and, for a checkout no authority names yet, a `read_dir` sweep of //! the whole profile's `projects/` directory. //! //! A hook is a one-shot subprocess spawned by `hook_cmd` for a single event, so //! resolution is stable for its entire lifetime and the answer can simply be //! kept. The cache is keyed by (profile root, project root) so a changed -//! `TRACEDECAY_USER_DATA_DIR` — the shape tests and multi-profile runs use — +//! `TRACEDECAY_USER_DATA_DIR`, the shape tests and multi-profile runs use, //! never reads another profile's answer. //! //! Errors collapse to `None`, matching every hook caller, all of which already diff --git a/crates/tracedecay-agent-hosts/src/hooks/tool_hints.rs b/crates/tracedecay-agent-hosts/src/hooks/tool_hints.rs index b2bfea6ac5..32612a0a18 100644 --- a/crates/tracedecay-agent-hosts/src/hooks/tool_hints.rs +++ b/crates/tracedecay-agent-hosts/src/hooks/tool_hints.rs @@ -49,7 +49,7 @@ impl HintCategory { } /// Human-readable name used in the escalation message prefix - /// ("Repeated native