roam-code 12.27__tar.gz → 12.28__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {roam_code-12.27/src/roam_code.egg-info → roam_code-12.28}/PKG-INFO +5 -4
- {roam_code-12.27 → roam_code-12.28}/README.md +4 -3
- {roam_code-12.27 → roam_code-12.28}/pyproject.toml +1 -1
- {roam_code-12.27 → roam_code-12.28}/src/roam/catalog/detectors.py +298 -7
- {roam_code-12.27 → roam_code-12.28}/src/roam/cli.py +2 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_auth_gaps.py +90 -11
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_math.py +34 -6
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_missing_index.py +82 -3
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_over_fetch.py +86 -17
- roam_code-12.28/src/roam/commands/cmd_suppress.py +138 -0
- roam_code-12.28/src/roam/commands/finding_suppress.py +258 -0
- {roam_code-12.27 → roam_code-12.28/src/roam_code.egg-info}/PKG-INFO +5 -4
- {roam_code-12.27 → roam_code-12.28}/src/roam_code.egg-info/SOURCES.txt +5 -0
- roam_code-12.28/tests/test_finding_suppress.py +220 -0
- roam_code-12.28/tests/test_laravel_fp_fixes.py +169 -0
- roam_code-12.28/tests/test_math_fp_fixes.py +243 -0
- {roam_code-12.27 → roam_code-12.28}/LICENSE +0 -0
- {roam_code-12.27 → roam_code-12.28}/setup.cfg +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/__main__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/analysis/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/analysis/effects.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/analysis/taint.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/api.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/ask/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/ask/classifier.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/ask/recipes.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/ask/runner.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/ask/workflow.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/attest/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/attest/cga.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/bridges/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/bridges/base.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/bridges/bridge_config.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/bridges/bridge_django.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/bridges/bridge_protobuf.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/bridges/bridge_rest_api.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/bridges/bridge_salesforce.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/bridges/bridge_template.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/bridges/registry.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/catalog/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/catalog/fixes.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/catalog/python_idioms.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/catalog/smells.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/catalog/tasks.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/audit_trail_helpers.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/changed_files.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_adrs.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_adversarial.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_affected.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_affected_tests.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_agent_context.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_agent_export.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_agent_plan.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_ai_ratio.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_ai_readiness.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_alerts.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_annotate.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_api.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_api_changes.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_api_drift.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_ask.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_attest.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_audit.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_audit_trail_conformance.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_audit_trail_export.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_audit_trail_verify.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_bisect.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_breaking.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_budget.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_bus_factor.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_capsule.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_cga.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_changelog.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_check_rules.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_ci_setup.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_clean.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_clones.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_closure.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_clusters.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_codeowners.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_complexity.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_config.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_congestion.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_context.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_conventions.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_coupling.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_coverage_gaps.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_critique.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_cut.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_dark_matter.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_dashboard.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_dead.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_debt.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_deps.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_describe.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_dev_profile.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_diagnose.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_diff.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_disambiguate.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_doc_staleness.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_docs_coverage.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_doctor.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_dogfood.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_drift.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_duplicates.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_effects.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_endpoints.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_entry_points.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_eval_retrieve.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_exit_codes.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_fan.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_file.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_fingerprint.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_fitness.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_flag_dead.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_fleet.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_fn_coupling.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_forecast.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_graph_export.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_graph_stats.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_grep.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_guard.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_health.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_help_search.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_hooks.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_hotspots.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_hover.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_impact.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_index.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_index_bundle.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_index_stats.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_ingest_trace.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_init.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_intent.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_invariants.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_layers.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_map.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_mcp_setup.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_mcp_status.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_metrics.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_metrics_push.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_migration_safety.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_minimap.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_module.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_mutate.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_n1.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_oracle.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_orchestrate.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_orphan_imports.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_orphan_routes.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_owner.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_partition.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_path_coverage.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_patterns.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_plan.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_plan_refactor.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_plugins.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_pr_analyze.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_pr_comment_render.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_pr_diff.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_pr_prep.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_pr_risk.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_pre_commit.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_preflight.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_py_modern.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_py_types.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_pytest_fixtures.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_recipes.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_recommend.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_relate.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_report.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_reset.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_retrieve.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_risk.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_rules.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_rules_validate.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_safe_delete.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_safe_zones.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_sbom.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_schema.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_search.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_search_semantic.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_secrets.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_semantic_diff.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_simulate.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_simulate_departure.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_sketch.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_smells.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_spectral.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_split.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_stats.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_suggest_refactoring.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_suggest_reviewers.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_supply_chain.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_symbol.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_syntax_check.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_taint.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_telemetry.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_test_gaps.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_test_impact.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_test_pyramid.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_test_scaffold.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_testmap.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_timeline.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_tour.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_trace.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_trends.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_triage.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_understand.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_uses.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_verify.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_verify_imports.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_version.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_vibe_check.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_visualize.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_vuln_map.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_vuln_reach.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_vulns.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_watch.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_weather.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_why.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_why_fail.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_workflow.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_ws.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_xlang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/codeowners_helpers.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/context_helpers.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/gate_presets.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/git_helpers.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/graph_helpers.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/metrics_history.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/next_steps.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/resolve.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/commands/suppression.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/competitor_site_data.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/config.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/coverage_reports.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/critique/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/critique/aggregator.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/critique/checks.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/db/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/db/connection.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/db/queries.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/db/schema.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/eval/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/eval/harness.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/exit_codes.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/fleet/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/fleet/adapters.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/fleet/manifest.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/git_utils.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/graph/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/graph/anomaly.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/graph/builder.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/graph/clone_detect.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/graph/clusters.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/graph/cycles.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/graph/dark_matter.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/graph/diff.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/graph/fingerprint.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/graph/layers.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/graph/pagerank.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/graph/partition.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/graph/pathfinding.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/graph/propagation.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/graph/simulate.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/graph/spectral.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/graph/stats.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/index/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/index/complexity.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/index/discovery.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/index/django_post.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/index/file_roles.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/index/git_stats.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/index/gitignore.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/index/incremental.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/index/indexer.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/index/parser.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/index/pytest_fixtures.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/index/registry_dispatch.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/index/relations.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/index/symbols.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/index/test_conventions.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/apex_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/aura_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/base.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/c_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/csharp_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/extractor_schema.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/extractors/kotlin.yaml +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/foxpro_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/generic_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/go_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/hcl_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/java_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/javascript_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/kotlin_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/php_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/python_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/query_engine.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/registry.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/ruby_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/rust_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/scala_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/sfxml_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/sql_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/swift_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/typescript_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/visualforce_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/languages/yaml_lang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/mcp-server-card.json +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/mcp_extras/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/mcp_extras/completions.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/mcp_extras/concurrency.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/mcp_extras/progress.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/mcp_extras/sampling.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/mcp_extras/session.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/mcp_extras/watcher.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/mcp_server.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/observability.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/output/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/output/confidence.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/output/errors.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/output/file_role_hints.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/output/formatter.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/output/framework_filter.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/output/mermaid.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/output/project_shape.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/output/sarif.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/output/schema_registry.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/plugins.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/refactor/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/refactor/codegen.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/refactor/transforms.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/retrieve/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/retrieve/learned_ranker.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/retrieve/pipeline.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/retrieve/rerank.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/retrieve/seeds.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/retrieve/semantic.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/rules/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/rules/ast_match.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/rules/builtin.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/rules/dataflow.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/rules/engine.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/runtime/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/runtime/daemon.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/runtime/graph_backend.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/runtime/hotspots.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/runtime/lock_modes.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/runtime/lockmgr.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/runtime/trace_ingest.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/search/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/search/framework_packs.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/search/index_embeddings.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/search/onnx_embeddings.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/search/tfidf.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/aibom_extension.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_classifier.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_engine.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/api_error_leak.yaml +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/java_fileupload_path_traversal.yaml +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/js_insecure_jwt_decode.yaml +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/js_localstorage_secrets.yaml +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/js_prototype_pollution.yaml +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/js_ssrf.yaml +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/js_xss.yaml +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/python_basic.yaml +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/python_deserialization.yaml +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/python_path_traversal.yaml +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/python_socketio_remote_source.yaml +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/python_sqli.yaml +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/python_urllib_open_redirect.yaml +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/vue_v_html.yaml +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/vuln_reach.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/security/vuln_store.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/surface_counts.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/telemetry.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/templates/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/templates/ci/Jenkinsfile +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/templates/ci/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/templates/ci/agent-review.yml +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/templates/ci/azure-pipelines.yml +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/templates/ci/bitbucket-pipelines.yml +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/templates/ci/gitlab-ci.yml +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/workspace/__init__.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/workspace/aggregator.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/workspace/api_scanner.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/workspace/config.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam/workspace/db.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam_code.egg-info/dependency_links.txt +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam_code.egg-info/entry_points.txt +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam_code.egg-info/requires.txt +0 -0
- {roam_code-12.27 → roam_code-12.28}/src/roam_code.egg-info/top_level.txt +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_adrs.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_adversarial.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_affected.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_agent_export.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_agent_mode.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_agent_plan_context.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_ai_ratio.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_ai_readiness.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_alerts_cmd.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_annotations.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_anomaly.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_api_changes.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_api_drift.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_ask.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_attest.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_audit_trail_aggregate.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_audit_trail_conformance.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_audit_trail_sequence.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_audit_trail_verify.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_auth_gaps.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_backend_fixes_round2.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_backend_fixes_round3.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_basic.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_batch_mcp.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_bisect.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_bridge_django.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_bridges.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_bridges_extended.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_budget.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_budget_flag.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_budget_phase2.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_bus_factor.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_capsule.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_cga.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_check_rules.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_ci_gate_eval.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_ci_sarif_guard.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_ci_setup.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_clones.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_closure.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_codeowners.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_commands_architecture.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_commands_exploration.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_commands_health.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_commands_refactoring.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_commands_workflow.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_competitor_site_data.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_comprehensive.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_config.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_congestion.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_context_propagation.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_conventions_cmd.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_coverage_gaps_cmd.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_coverage_ingestion.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_critique.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_cut.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_dark_matter.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_dark_matter_helpers.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_dashboard.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_dataflow_dead.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_dead_aging.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_defer_loading.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_demo_gif_asset.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_describe.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_detail_flag_hints.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_detector_precision.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_deterministic_output.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_dev_profile.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_difficulty_scoring.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_doc_consistency.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_doc_staleness.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_docker_assets.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_docs_coverage.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_docs_site_quality.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_doctor.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_dogfood.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_drift.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_drift_by_team.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_duplicates.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_effects.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_effects_propagation.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_endpoints.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_entry_points_cmd.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_eval_retrieve.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_except_pass_narrow.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_exclude_patterns.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_exit_codes.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_extractor_grammar_drift.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_fallback_contracts.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_file_roles.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_fingerprint.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_fixes.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_flag_dead.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_fleet.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_fn_coupling.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_forecast.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_formatters.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_foxpro.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_framework_detection.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_gate_presets.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_git_helpers.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_git_utils.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_guard.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_health_gate.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_hooks.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_hotspots.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_hover.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_index.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_index_bundle.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_init_cmd.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_install_check.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_intent.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_invariants.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_json_contracts.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_json_error_envelope.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_kotlin_swift_extractors.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_language_corpus.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_languages.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_library_api.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_math.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_math_tips.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_mcp_extras.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_mcp_server.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_mcp_setup.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_mermaid.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_metrics_cmd.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_metrics_push.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_migration_safety.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_minimap.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_missing_index.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_mutate.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_n1.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_next_steps.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_onboard.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_oracle.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_orchestrate.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_orphan_routes.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_oss_bench_harness.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_over_fetch.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_pagerank_truncation.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_partition.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_path_coverage.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_patterns_cmd.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_performance.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_personalized_pagerank.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_plan.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_plugin_discovery.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_pr_analyze.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_pr_analyze_cache.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_pr_analyze_edge_cases.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_pr_analyze_helpers.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_pr_analyze_v2_signals.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_pr_comment_render.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_pr_comment_script.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_pr_diff.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_pr_risk_author.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_progress.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_progressive_disclosure.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_properties.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_pytest_fixtures.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_python_extractor_v2.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_python_idioms_e2e.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_python_pivot.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_readme_surface_consistency.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_realworld_feedback.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_refactoring_intelligence.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_registry_dispatch.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_relate.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_report.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_reset_clean.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_resolve.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_retrieve.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_retrieve_cross_repo.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_retrieve_seeds.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_risk.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_ruby.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_rule_profiles.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_rules.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_rules_ast_match.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_rules_community_pack.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_rules_dataflow.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_rules_symbol_requirements.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_rules_validate.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_runtime.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_runtime_score.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_salesforce.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_sarif_flag.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_sbom.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_scala.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_schema_versioning.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_search_explain.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_secrets.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_secrets_v2.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_semantic_diff.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_semantic_onnx.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_semantic_search.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_simulate.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_simulate_departure.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_sketch.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_smells.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_smoke.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_sna_metrics.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_spectral.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_split_cmd.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_sql.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_suggest_reviewers.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_supply_chain.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_surface_counts.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_syntax_check.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_taint.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_taint_analysis.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_taint_classifier.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_taint_intraprocedural.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_test_conventions.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_test_gaps.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_test_scaffold.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_testmap.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_top_flag_consistency.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_tour_cmd.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_trends.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_trends_cohort.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_triage.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_uses_cmd.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_v1215_passes.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_v1216_passes.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_v1216_passes_41_50.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_v1216_passes_51_60.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_v1217_passes_61_80.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_v1218_passes_81_90.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_v1219_passes_91_100.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_v1220_passes_101_110.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_v1221_query_timeout.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_v1221_untested_commands.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_v12_2.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_v2_edge_cases.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_v2_integration.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_v6_features.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_v71_features.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_v7_features.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_v82_features.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_verify.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_verify_imports.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_vibe_check.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_visualize.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_vuln.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_vulns_cmd.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_watch.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_why.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_workspace.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_ws_resolve_fixes.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_xlang.py +0 -0
- {roam_code-12.27 → roam_code-12.28}/tests/test_yaml_hcl.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: roam-code
|
|
3
|
-
Version: 12.
|
|
3
|
+
Version: 12.28
|
|
4
4
|
Summary: Instant codebase comprehension for AI coding agents
|
|
5
5
|
Author: CosmoHac
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -63,9 +63,9 @@ Dynamic: license-file
|
|
|
63
63
|
|
|
64
64
|
**Architectural sight for AI coding agents — before they edit.**
|
|
65
65
|
|
|
66
|
-
A local code graph (SQLite + tree-sitter + git history) that gives any agent — Claude Code, Cursor, Aider, Continue, your own — five high-leverage verbs: `understand`, `retrieve`, `context`, `preflight`, `critique`. The other
|
|
66
|
+
A local code graph (SQLite + tree-sitter + git history) that gives any agent — Claude Code, Cursor, Aider, Continue, your own — five high-leverage verbs: `understand`, `retrieve`, `context`, `preflight`, `critique`. The other 182 specialised commands are advanced surface for specialised workflows.
|
|
67
67
|
|
|
68
|
-
*
|
|
68
|
+
*187 commands · 136 MCP tools · 27 languages · 100% local · zero API keys*
|
|
69
69
|
|
|
70
70
|
[](https://pypi.org/project/roam-code/)
|
|
71
71
|
[](https://github.com/Cranot/roam-code/stargazers)
|
|
@@ -333,7 +333,7 @@ roam health
|
|
|
333
333
|
|
|
334
334
|
## Commands
|
|
335
335
|
|
|
336
|
-
**Lead with the 5 verbs.** The [5 core commands](#core-commands) cover ~80% of agent workflows: `understand`, `context`, `retrieve`, `preflight`, `critique`. The remaining
|
|
336
|
+
**Lead with the 5 verbs.** The [5 core commands](#core-commands) cover ~80% of agent workflows: `understand`, `context`, `retrieve`, `preflight`, `critique`. The remaining 182 commands are detail surface for specialised workflows (taint, fleet, cga, oracle, eval, …) — they're called by agents on demand, not memorised. This is intentional design; under the hood the canonical surface is **187 commands organised into 7 categories** (plus 6 aliases for muscle memory: `algo` → `math`, `weather` → `churn`, `digest` / `snapshot` / `trend` → `trends`, `onboard` → `understand`), but you don't need to know that to start.
|
|
337
337
|
|
|
338
338
|
<details>
|
|
339
339
|
<summary><strong>Full command reference</strong></summary>
|
|
@@ -383,6 +383,7 @@ roam health
|
|
|
383
383
|
| `roam audit-trail-conformance-check [--retention-days N] [--gate]` | Score the audit trail against an EU AI Act Article 12 checklist (chain integrity, timestamps, actors, reproducibility, retention) |
|
|
384
384
|
| `roam rules-validate [PATH] [--against DIFF] [--strict] [--gate] [--explain]` | Lint a `.roam/rules.yml` for typos, schema mistakes, unknown patterns, duplicate IDs; optional dry-run against a sample diff |
|
|
385
385
|
| `roam dogfood [--no-audit] [--no-pr-analyze] [--no-audit-trail]` | One-shot v2 stack runner: audit + pr-analyze + audit-trail + Article 12 conformance — first-touch demo for any repo |
|
|
386
|
+
| `roam suppress <finding-id> --reason "…"` | Suppress a math / over-fetch / missing-index / auth-gaps false positive with audit-trail-friendly record (`.roam/suppressions.json`); `--list` / `--remove` complete the workflow |
|
|
386
387
|
| `roam why-fail <test>` | Find recently-changed symbols transitively reachable from a failing test |
|
|
387
388
|
| `roam recommend <symbol>` | Surface related symbols using call-graph + co-change + clone signals |
|
|
388
389
|
| `roam graph-stats` | Graph-level invariants: density, weak components, non-trivial cycles, top inbound symbols |
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
**Architectural sight for AI coding agents — before they edit.**
|
|
6
6
|
|
|
7
|
-
A local code graph (SQLite + tree-sitter + git history) that gives any agent — Claude Code, Cursor, Aider, Continue, your own — five high-leverage verbs: `understand`, `retrieve`, `context`, `preflight`, `critique`. The other
|
|
7
|
+
A local code graph (SQLite + tree-sitter + git history) that gives any agent — Claude Code, Cursor, Aider, Continue, your own — five high-leverage verbs: `understand`, `retrieve`, `context`, `preflight`, `critique`. The other 182 specialised commands are advanced surface for specialised workflows.
|
|
8
8
|
|
|
9
|
-
*
|
|
9
|
+
*187 commands · 136 MCP tools · 27 languages · 100% local · zero API keys*
|
|
10
10
|
|
|
11
11
|
[](https://pypi.org/project/roam-code/)
|
|
12
12
|
[](https://github.com/Cranot/roam-code/stargazers)
|
|
@@ -274,7 +274,7 @@ roam health
|
|
|
274
274
|
|
|
275
275
|
## Commands
|
|
276
276
|
|
|
277
|
-
**Lead with the 5 verbs.** The [5 core commands](#core-commands) cover ~80% of agent workflows: `understand`, `context`, `retrieve`, `preflight`, `critique`. The remaining
|
|
277
|
+
**Lead with the 5 verbs.** The [5 core commands](#core-commands) cover ~80% of agent workflows: `understand`, `context`, `retrieve`, `preflight`, `critique`. The remaining 182 commands are detail surface for specialised workflows (taint, fleet, cga, oracle, eval, …) — they're called by agents on demand, not memorised. This is intentional design; under the hood the canonical surface is **187 commands organised into 7 categories** (plus 6 aliases for muscle memory: `algo` → `math`, `weather` → `churn`, `digest` / `snapshot` / `trend` → `trends`, `onboard` → `understand`), but you don't need to know that to start.
|
|
278
278
|
|
|
279
279
|
<details>
|
|
280
280
|
<summary><strong>Full command reference</strong></summary>
|
|
@@ -324,6 +324,7 @@ roam health
|
|
|
324
324
|
| `roam audit-trail-conformance-check [--retention-days N] [--gate]` | Score the audit trail against an EU AI Act Article 12 checklist (chain integrity, timestamps, actors, reproducibility, retention) |
|
|
325
325
|
| `roam rules-validate [PATH] [--against DIFF] [--strict] [--gate] [--explain]` | Lint a `.roam/rules.yml` for typos, schema mistakes, unknown patterns, duplicate IDs; optional dry-run against a sample diff |
|
|
326
326
|
| `roam dogfood [--no-audit] [--no-pr-analyze] [--no-audit-trail]` | One-shot v2 stack runner: audit + pr-analyze + audit-trail + Article 12 conformance — first-touch demo for any repo |
|
|
327
|
+
| `roam suppress <finding-id> --reason "…"` | Suppress a math / over-fetch / missing-index / auth-gaps false positive with audit-trail-friendly record (`.roam/suppressions.json`); `--list` / `--remove` complete the workflow |
|
|
327
328
|
| `roam why-fail <test>` | Find recently-changed symbols transitively reachable from a failing test |
|
|
328
329
|
| `roam recommend <symbol>` | Surface related symbols using call-graph + co-change + clone signals |
|
|
329
330
|
| `roam graph-stats` | Graph-level invariants: density, weak components, non-trivial cycles, top inbound symbols |
|
|
@@ -48,8 +48,19 @@ def _finding(
|
|
|
48
48
|
*,
|
|
49
49
|
evidence=None,
|
|
50
50
|
fix=None,
|
|
51
|
+
match_line=None,
|
|
51
52
|
):
|
|
53
|
+
"""Build a finding dict.
|
|
54
|
+
|
|
55
|
+
M1 (2026-05-06): when ``match_line`` is supplied, the finding's
|
|
56
|
+
``location`` field points at the exact AST node where the pattern
|
|
57
|
+
matched (e.g. the line containing the .sort() call) — not the
|
|
58
|
+
enclosing function declaration. The function-start line is
|
|
59
|
+
preserved as ``symbol_line`` so callers needing both have access.
|
|
60
|
+
"""
|
|
52
61
|
bw = best_way(task_id)
|
|
62
|
+
sym_line = sym["line_start"]
|
|
63
|
+
actual_line = match_line if match_line is not None else sym_line
|
|
53
64
|
finding = {
|
|
54
65
|
"task_id": task_id,
|
|
55
66
|
"detected_way": detected_way,
|
|
@@ -57,7 +68,8 @@ def _finding(
|
|
|
57
68
|
"symbol_id": sym["id"],
|
|
58
69
|
"symbol_name": sym["qualified_name"] or sym["name"],
|
|
59
70
|
"kind": sym["kind"],
|
|
60
|
-
"location": _loc(sym["file_path"],
|
|
71
|
+
"location": _loc(sym["file_path"], actual_line),
|
|
72
|
+
"symbol_line": sym_line,
|
|
61
73
|
"confidence": confidence,
|
|
62
74
|
"reason": reason,
|
|
63
75
|
}
|
|
@@ -68,6 +80,78 @@ def _finding(
|
|
|
68
80
|
return finding
|
|
69
81
|
|
|
70
82
|
|
|
83
|
+
def _find_match_line(snippet: str, pattern, sym_line_start: int | None) -> int | None:
|
|
84
|
+
"""Walk the snippet line by line to find the first line matching ``pattern``.
|
|
85
|
+
|
|
86
|
+
Returns the absolute line number (sym_line_start + offset). When
|
|
87
|
+
snippet doesn't contain the match, returns sym_line_start unchanged
|
|
88
|
+
so callers can blindly substitute.
|
|
89
|
+
|
|
90
|
+
Used by sort-to-select / IO-in-loop / regex-in-loop detectors to
|
|
91
|
+
pin findings at the exact match site.
|
|
92
|
+
"""
|
|
93
|
+
if not snippet or sym_line_start is None:
|
|
94
|
+
return sym_line_start
|
|
95
|
+
for offset, line in enumerate(snippet.splitlines()):
|
|
96
|
+
if pattern.search(line):
|
|
97
|
+
return sym_line_start + offset
|
|
98
|
+
return sym_line_start
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# M4 — recognise DEV-only / DEBUG-only gates so production-impact
|
|
102
|
+
# detectors don't fire on code that's stripped from production builds.
|
|
103
|
+
# Real-world examples (from the prior round of FPs):
|
|
104
|
+
# - if (import.meta.env.DEV) { ... heavy diagnostics ... }
|
|
105
|
+
# - if (process.env.NODE_ENV !== 'production') { ... }
|
|
106
|
+
# - if (__DEV__) { ... }
|
|
107
|
+
# - if (DEBUG) { ... }
|
|
108
|
+
# - console.assert(...) — short-circuits in production
|
|
109
|
+
_DEV_GATE_RE = re.compile(
|
|
110
|
+
# No trailing \b — alternatives ending in `'production'` or `__DEV__` have
|
|
111
|
+
# non-word chars after them, so the global `\b` would never fire there.
|
|
112
|
+
r"\bif\s*\([^)]*?\b(?:"
|
|
113
|
+
r"import\.meta\.env\.(?:DEV\b|MODE\s*[!=]==?\s*['\"]production['\"])|"
|
|
114
|
+
r"process\.env\.NODE_ENV\s*[!=]==?\s*['\"]production['\"]|"
|
|
115
|
+
r"__DEV__|"
|
|
116
|
+
r"DEBUG\b"
|
|
117
|
+
r")",
|
|
118
|
+
re.IGNORECASE,
|
|
119
|
+
)
|
|
120
|
+
_CONSOLE_ASSERT_RE = re.compile(r"\bconsole\.assert\s*\(")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _is_dev_only_block(snippet: str, match_line_offset: int | None = None) -> bool:
|
|
124
|
+
"""Heuristic: does the snippet (or the lines around the match) sit inside
|
|
125
|
+
a DEV-only conditional?
|
|
126
|
+
|
|
127
|
+
Conservative matcher: returns True only when an obvious DEV gate is
|
|
128
|
+
visible *before* the match line in the snippet. Won't catch every
|
|
129
|
+
case (e.g. a flag set in a parent function), but catches the common
|
|
130
|
+
Vue 3 / React / Next.js / Vite patterns where heavy diagnostics live
|
|
131
|
+
behind import.meta.env.DEV.
|
|
132
|
+
"""
|
|
133
|
+
if not snippet:
|
|
134
|
+
return False
|
|
135
|
+
if _DEV_GATE_RE.search(snippet) or _CONSOLE_ASSERT_RE.search(snippet):
|
|
136
|
+
return True
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _find_first_keyword_line(snippet: str, keywords: tuple[str, ...], sym_line_start: int | None) -> int | None:
|
|
141
|
+
"""First line containing any of the keywords (case-insensitive substring).
|
|
142
|
+
|
|
143
|
+
Cheaper than regex; used for self-call detection in branching recursion.
|
|
144
|
+
"""
|
|
145
|
+
if not snippet or sym_line_start is None:
|
|
146
|
+
return sym_line_start
|
|
147
|
+
lows = [k.lower() for k in keywords]
|
|
148
|
+
for offset, line in enumerate(snippet.splitlines()):
|
|
149
|
+
ll = line.lower()
|
|
150
|
+
if any(k in ll for k in lows):
|
|
151
|
+
return sym_line_start + offset
|
|
152
|
+
return sym_line_start
|
|
153
|
+
|
|
154
|
+
|
|
71
155
|
def _row_value(row, key, default=None):
|
|
72
156
|
"""Safely read a sqlite row key with a fallback."""
|
|
73
157
|
try:
|
|
@@ -1125,25 +1209,62 @@ _IO_RECEIVER_HINTS = {
|
|
|
1125
1209
|
"requests",
|
|
1126
1210
|
"urllib",
|
|
1127
1211
|
}
|
|
1212
|
+
# M3 expansion: when a call is IN this set OR matches one of the IN_MEMORY_LEAVES
|
|
1213
|
+
# attached to a recognised receiver, treat as an in-memory cache read NOT I/O.
|
|
1214
|
+
# Real-world FP that drove the expansion: roam math flagged
|
|
1215
|
+
# `queryClient.getQueryData` inside a TanStack Query factory as N+1
|
|
1216
|
+
# round-trips when those are sync cache reads.
|
|
1128
1217
|
_IN_MEMORY_EXACT = {
|
|
1129
1218
|
"queryclient.setquerydata",
|
|
1130
1219
|
"queryclient.getquerydata",
|
|
1131
1220
|
"queryclient.getqueriesdata",
|
|
1132
1221
|
"queryclient.setqueriesdata",
|
|
1222
|
+
"queryclient.invalidatequeries",
|
|
1223
|
+
"queryclient.removequeries",
|
|
1224
|
+
"queryclient.cancelqueries",
|
|
1225
|
+
"queryclient.refetchqueries",
|
|
1133
1226
|
"qc.setquerydata",
|
|
1134
1227
|
"qc.getquerydata",
|
|
1135
1228
|
"qc.getqueriesdata",
|
|
1136
1229
|
"qc.setqueriesdata",
|
|
1230
|
+
"qc.invalidatequeries",
|
|
1137
1231
|
"redux.dispatch",
|
|
1138
1232
|
"store.dispatch",
|
|
1233
|
+
# SWR + RTK Query + Apollo cache equivalents
|
|
1234
|
+
"mutate", # SWR's mutate() (cache-only)
|
|
1235
|
+
"cache.read",
|
|
1236
|
+
"cache.write",
|
|
1237
|
+
"cache.evict",
|
|
1238
|
+
"cache.modify", # Apollo
|
|
1239
|
+
"client.readquery", # Apollo
|
|
1240
|
+
"client.writequery", # Apollo
|
|
1241
|
+
"client.cache.evict", # Apollo
|
|
1139
1242
|
}
|
|
1140
1243
|
_IN_MEMORY_LEAVES = {
|
|
1141
1244
|
"setquerydata",
|
|
1142
1245
|
"getquerydata",
|
|
1143
1246
|
"setqueriesdata",
|
|
1144
1247
|
"getqueriesdata",
|
|
1248
|
+
"invalidatequeries",
|
|
1249
|
+
"removequeries",
|
|
1250
|
+
"cancelqueries",
|
|
1251
|
+
"refetchqueries",
|
|
1145
1252
|
"dispatch",
|
|
1146
1253
|
"setstate",
|
|
1254
|
+
# M3 — generic Map/Set/WeakMap operations are NOT I/O even in loops.
|
|
1255
|
+
# Without these, `mapInst.get(k)` inside a loop falsely fires as N+1.
|
|
1256
|
+
"has",
|
|
1257
|
+
"set",
|
|
1258
|
+
"delete",
|
|
1259
|
+
"clear",
|
|
1260
|
+
"peek",
|
|
1261
|
+
# SWR + Apollo
|
|
1262
|
+
"readquery",
|
|
1263
|
+
"writequery",
|
|
1264
|
+
"writefragment",
|
|
1265
|
+
"readfragment",
|
|
1266
|
+
"evict",
|
|
1267
|
+
"modify",
|
|
1147
1268
|
}
|
|
1148
1269
|
_IN_MEMORY_RECEIVER_HINTS = {
|
|
1149
1270
|
"queryclient",
|
|
@@ -1153,6 +1274,17 @@ _IN_MEMORY_RECEIVER_HINTS = {
|
|
|
1153
1274
|
"cache",
|
|
1154
1275
|
"state",
|
|
1155
1276
|
"router",
|
|
1277
|
+
# M3 expansion: more cache-library + native-collection receivers
|
|
1278
|
+
"map",
|
|
1279
|
+
"set",
|
|
1280
|
+
"weakmap",
|
|
1281
|
+
"weakset",
|
|
1282
|
+
"dict",
|
|
1283
|
+
"lookup",
|
|
1284
|
+
"registry",
|
|
1285
|
+
"client", # Apollo client.cache.evict
|
|
1286
|
+
"session",
|
|
1287
|
+
"pinia", # Vue 3 store
|
|
1156
1288
|
}
|
|
1157
1289
|
_IO_WRAPPER_NAMES = {
|
|
1158
1290
|
"batch",
|
|
@@ -1264,9 +1396,41 @@ def _io_emit_finding(
|
|
|
1264
1396
|
guard_applies: bool,
|
|
1265
1397
|
evidence_io_calls: list[str],
|
|
1266
1398
|
r,
|
|
1399
|
+
*,
|
|
1400
|
+
dev_gated: bool = False,
|
|
1401
|
+
match_line: int | None = None,
|
|
1402
|
+
snippet: str | None = None,
|
|
1267
1403
|
) -> dict:
|
|
1268
|
-
"""Build the finding dict for one of high/medium/ambiguous result branches.
|
|
1404
|
+
"""Build the finding dict for one of high/medium/ambiguous result branches.
|
|
1405
|
+
|
|
1406
|
+
M1 (match_line): pin location at the first I/O call site in the snippet
|
|
1407
|
+
rather than the function declaration.
|
|
1408
|
+
M4 (dev_gated): note when the body sits inside a DEV gate.
|
|
1409
|
+
M6 (suppress hint): every emitted finding carries `to_suppress` evidence.
|
|
1410
|
+
"""
|
|
1269
1411
|
fix_text = "; ".join(sorted(fixes)) if fixes else None
|
|
1412
|
+
# M1: try to find the line of the first I/O call inside the snippet.
|
|
1413
|
+
derived_match_line = match_line
|
|
1414
|
+
if derived_match_line is None and snippet and (high_calls or medium_calls or ambiguous_calls):
|
|
1415
|
+
first_call = (high_calls + medium_calls + ambiguous_calls)[0]
|
|
1416
|
+
leaf = _call_leaf(first_call) or first_call
|
|
1417
|
+
# Use a loose substring match — call shapes vary across extractors.
|
|
1418
|
+
if leaf:
|
|
1419
|
+
for offset, line in enumerate(snippet.splitlines()):
|
|
1420
|
+
if leaf in line:
|
|
1421
|
+
derived_match_line = (r["line_start"] or 0) + offset
|
|
1422
|
+
break
|
|
1423
|
+
common_evidence_extras = {}
|
|
1424
|
+
if dev_gated:
|
|
1425
|
+
common_evidence_extras["dev_gated"] = True
|
|
1426
|
+
common_evidence_extras["dev_gated_note"] = (
|
|
1427
|
+
"loop body sits inside a DEV-only conditional (import.meta.env.DEV / __DEV__ / "
|
|
1428
|
+
"process.env.NODE_ENV); production-stripped, so the N+1 cost is not paid in prod"
|
|
1429
|
+
)
|
|
1430
|
+
suppress_hint = (
|
|
1431
|
+
"wrap the loop in a batch/eager guard (e.g. `with()` or `map()`+`Promise.all`), OR "
|
|
1432
|
+
"add `# roam: ignore-math[io-in-loop]` on the function line if the call is intentional"
|
|
1433
|
+
)
|
|
1270
1434
|
if level == "high":
|
|
1271
1435
|
reason_calls = _dedupe(high_calls)[:2]
|
|
1272
1436
|
reason_suffix = f"; frameworks: {', '.join(sorted(frameworks))}" if frameworks else ""
|
|
@@ -1284,8 +1448,11 @@ def _io_emit_finding(
|
|
|
1284
1448
|
"io_calls": evidence_io_calls,
|
|
1285
1449
|
"frameworks": sorted(frameworks),
|
|
1286
1450
|
"guard_hints": guard_hints,
|
|
1451
|
+
"to_suppress": suppress_hint,
|
|
1452
|
+
**common_evidence_extras,
|
|
1287
1453
|
},
|
|
1288
1454
|
fix=fix_text,
|
|
1455
|
+
match_line=derived_match_line,
|
|
1289
1456
|
)
|
|
1290
1457
|
if level == "medium":
|
|
1291
1458
|
reason_calls = _dedupe(medium_calls)[:2]
|
|
@@ -1304,8 +1471,11 @@ def _io_emit_finding(
|
|
|
1304
1471
|
"io_calls": evidence_io_calls,
|
|
1305
1472
|
"frameworks": sorted(frameworks),
|
|
1306
1473
|
"guard_hints": guard_hints,
|
|
1474
|
+
"to_suppress": suppress_hint,
|
|
1475
|
+
**common_evidence_extras,
|
|
1307
1476
|
},
|
|
1308
1477
|
fix=fix_text,
|
|
1478
|
+
match_line=derived_match_line,
|
|
1309
1479
|
)
|
|
1310
1480
|
# Ambiguous bare calls (get/find/query) — weak evidence, low confidence.
|
|
1311
1481
|
reason_calls = _dedupe(ambiguous_calls)[:2]
|
|
@@ -1321,8 +1491,11 @@ def _io_emit_finding(
|
|
|
1321
1491
|
"ambiguous_io_only": True,
|
|
1322
1492
|
"frameworks": sorted(frameworks),
|
|
1323
1493
|
"guard_hints": guard_hints,
|
|
1494
|
+
"to_suppress": suppress_hint,
|
|
1495
|
+
**common_evidence_extras,
|
|
1324
1496
|
},
|
|
1325
1497
|
fix=fix_text,
|
|
1498
|
+
match_line=derived_match_line,
|
|
1326
1499
|
)
|
|
1327
1500
|
finding["precision"] = "low"
|
|
1328
1501
|
return finding
|
|
@@ -1397,6 +1570,10 @@ def detect_io_in_loop(conn: sqlite3.Connection) -> list[dict]:
|
|
|
1397
1570
|
if any(kw in name_lower for kw in _IO_WRAPPER_NAMES):
|
|
1398
1571
|
continue
|
|
1399
1572
|
|
|
1573
|
+
# M4: skip DEV-gated bodies — production stripped, so loop overhead
|
|
1574
|
+
# is irrelevant. (Conservative: only skip when the gate is *literally*
|
|
1575
|
+
# in this function's body.)
|
|
1576
|
+
dev_gated = _is_dev_only_block(snippet)
|
|
1400
1577
|
guard_applies = bool(frameworks and guard_hints)
|
|
1401
1578
|
evidence_io_calls = _dedupe(high_calls + medium_calls + ambiguous_calls)[:6]
|
|
1402
1579
|
if high_calls:
|
|
@@ -1405,6 +1582,14 @@ def detect_io_in_loop(conn: sqlite3.Connection) -> list[dict]:
|
|
|
1405
1582
|
level = "medium"
|
|
1406
1583
|
else:
|
|
1407
1584
|
level = "ambiguous"
|
|
1585
|
+
if dev_gated:
|
|
1586
|
+
# Don't drop entirely — a real production-bound issue could still
|
|
1587
|
+
# exist outside the gate. But demote two tiers so it sinks to the
|
|
1588
|
+
# bottom of the verdict list.
|
|
1589
|
+
if level == "high":
|
|
1590
|
+
level = "medium"
|
|
1591
|
+
elif level == "medium":
|
|
1592
|
+
level = "ambiguous"
|
|
1408
1593
|
results.append(
|
|
1409
1594
|
_io_emit_finding(
|
|
1410
1595
|
level,
|
|
@@ -1417,6 +1602,8 @@ def detect_io_in_loop(conn: sqlite3.Connection) -> list[dict]:
|
|
|
1417
1602
|
guard_applies,
|
|
1418
1603
|
evidence_io_calls,
|
|
1419
1604
|
r,
|
|
1605
|
+
dev_gated=dev_gated,
|
|
1606
|
+
snippet=snippet,
|
|
1420
1607
|
)
|
|
1421
1608
|
)
|
|
1422
1609
|
return results
|
|
@@ -1512,6 +1699,15 @@ def detect_sort_to_select(conn: sqlite3.Connection) -> list[dict]:
|
|
|
1512
1699
|
r"\bsort(?:ed)?\s*\([^)]*\).*?\[\s*(?:-?\d+|:\s*[^]\n]+)\s*\]",
|
|
1513
1700
|
re.DOTALL,
|
|
1514
1701
|
)
|
|
1702
|
+
# M1: per-line sort detector for pinpointing the match line
|
|
1703
|
+
sort_call_line_re = re.compile(r"\bsort(?:ed)?\s*\(")
|
|
1704
|
+
|
|
1705
|
+
# M5 — fallback false-positive guard. Skip when the sort result is also
|
|
1706
|
+
# iterated/returned in full (sorted then map/forEach/return — display order,
|
|
1707
|
+
# not a min/max selection).
|
|
1708
|
+
# The check is conservative: if we see ANY iteration of the sort target
|
|
1709
|
+
# alongside the index access, demote the finding rather than skip outright.
|
|
1710
|
+
iteration_after_sort_re = re.compile(r"\bsort(?:ed)?\s*\(.*?\b(map|forEach|filter|reduce|return)\b", re.DOTALL)
|
|
1515
1711
|
|
|
1516
1712
|
results = []
|
|
1517
1713
|
for r in rows:
|
|
@@ -1521,28 +1717,44 @@ def detect_sort_to_select(conn: sqlite3.Connection) -> list[dict]:
|
|
|
1521
1717
|
if not snippet:
|
|
1522
1718
|
continue
|
|
1523
1719
|
|
|
1720
|
+
match_line = _find_match_line(snippet, sort_call_line_re, r["line_start"])
|
|
1721
|
+
sort_iterated = bool(iteration_after_sort_re.search(snippet))
|
|
1722
|
+
|
|
1524
1723
|
# Strong patterns: sorted(...)[0], sorted(... )[:k], arr.sort(); arr[0]
|
|
1525
1724
|
if sorted_index_re.search(snippet) or inplace_sort_index_re.search(snippet):
|
|
1725
|
+
# M5: when the sort result is also iterated, downgrade — the
|
|
1726
|
+
# subscript may be incidental (e.g. logging the first item of a
|
|
1727
|
+
# display-ordered list).
|
|
1728
|
+
confidence = "medium" if sort_iterated else "high"
|
|
1729
|
+
reason = "Sort used only for first/last/top-k selection"
|
|
1730
|
+
if sort_iterated:
|
|
1731
|
+
reason += " (note: result is also iterated — may be incidental subscript)"
|
|
1526
1732
|
results.append(
|
|
1527
1733
|
_finding(
|
|
1528
1734
|
"sort-to-select",
|
|
1529
1735
|
"full-sort",
|
|
1530
1736
|
r,
|
|
1531
|
-
|
|
1532
|
-
|
|
1737
|
+
reason,
|
|
1738
|
+
confidence,
|
|
1739
|
+
match_line=match_line,
|
|
1533
1740
|
)
|
|
1534
1741
|
)
|
|
1535
1742
|
continue
|
|
1536
1743
|
|
|
1537
1744
|
# Fallback pattern for other languages (sort(...) then index/slice).
|
|
1538
1745
|
if generic_sort_index_re.search(snippet):
|
|
1746
|
+
confidence = "low" if sort_iterated else "medium"
|
|
1747
|
+
reason = "Potential full sort followed by index/slice selection"
|
|
1748
|
+
if sort_iterated:
|
|
1749
|
+
reason += " (note: result is also iterated — may be incidental subscript)"
|
|
1539
1750
|
results.append(
|
|
1540
1751
|
_finding(
|
|
1541
1752
|
"sort-to-select",
|
|
1542
1753
|
"full-sort",
|
|
1543
1754
|
r,
|
|
1544
|
-
|
|
1545
|
-
|
|
1755
|
+
reason,
|
|
1756
|
+
confidence,
|
|
1757
|
+
match_line=match_line,
|
|
1546
1758
|
)
|
|
1547
1759
|
)
|
|
1548
1760
|
return results
|
|
@@ -1646,6 +1858,11 @@ def detect_branching_recursion(conn: sqlite3.Connection) -> list[dict]:
|
|
|
1646
1858
|
|
|
1647
1859
|
results = []
|
|
1648
1860
|
|
|
1861
|
+
# M2 fix: detect the SECOND form of depth guard — early-return when
|
|
1862
|
+
# depth EXCEEDS limit ("if depth > 10 return"). The original regex only
|
|
1863
|
+
# recognised "depth < limit ⇒ continue" patterns; the negation form was
|
|
1864
|
+
# silently mis-flagged as O(2^n). Real-world FP: deepEqual flagged
|
|
1865
|
+
# despite line+2 having `if (depth > 10) return false`.
|
|
1649
1866
|
def _has_explicit_depth_guard(language: str | None, snippet: str) -> bool:
|
|
1650
1867
|
"""Return True for bounded-recursion guards that cap traversal depth."""
|
|
1651
1868
|
if not snippet:
|
|
@@ -1656,9 +1873,30 @@ def detect_branching_recursion(conn: sqlite3.Connection) -> list[dict]:
|
|
|
1656
1873
|
# Python: len(path.split(".")) < 5
|
|
1657
1874
|
if re.search(r"len\s*\(\s*[^)]*\.split\s*\([^)]*\)\s*\)\s*(?:<|<=)\s*\d+", snippet):
|
|
1658
1875
|
return True
|
|
1876
|
+
# Form 1: continue if depth/level/budget BELOW limit.
|
|
1659
1877
|
# Common explicit counters: depth < maxDepth, level <= 4, etc.
|
|
1660
1878
|
if re.search(
|
|
1661
|
-
r"\b(?:depth|level|currentDepth|current_depth)\b
|
|
1879
|
+
r"\b(?:depth|level|budget|remaining|hops|currentDepth|current_depth|max_depth|maxDepth)\b"
|
|
1880
|
+
r"\s*(?:<|<=)\s*(?:\d+|maxDepth|max_depth|MAX_DEPTH|max_recursion|MAX_RECURSION)",
|
|
1881
|
+
snippet,
|
|
1882
|
+
):
|
|
1883
|
+
return True
|
|
1884
|
+
# M2 — Form 2: early-return if depth/level/budget EXCEEDS limit.
|
|
1885
|
+
# Matches `if (depth > 10) return ...`, `if (level >= MAX_DEPTH) raise`,
|
|
1886
|
+
# `if (budget < 1) return`, `if (--budget <= 0) return`.
|
|
1887
|
+
# Generous on the "return / raise / throw / break" side since
|
|
1888
|
+
# short-circuit-and-bail is the universal early-exit shape.
|
|
1889
|
+
if re.search(
|
|
1890
|
+
r"\b(?:depth|level|budget|remaining|hops|recursion_count|recursionDepth)\b"
|
|
1891
|
+
r"\s*(?:>|>=|<|<=)\s*(?:\d+|maxDepth|max_depth|MAX_DEPTH|max_recursion|MAX_RECURSION|0)\s*\)?\s*"
|
|
1892
|
+
r"\s*[:{]?\s*(?:return|raise|throw|break)",
|
|
1893
|
+
snippet,
|
|
1894
|
+
):
|
|
1895
|
+
return True
|
|
1896
|
+
# Decrement-then-check: `if (--budget <= 0) return`
|
|
1897
|
+
if re.search(
|
|
1898
|
+
r"--?\s*\b(?:depth|budget|remaining|hops)\b\s*[<>]=?\s*\d+\s*\)?\s*[:{]?\s*"
|
|
1899
|
+
r"(?:return|raise|throw|break)",
|
|
1662
1900
|
snippet,
|
|
1663
1901
|
):
|
|
1664
1902
|
return True
|
|
@@ -1673,6 +1911,22 @@ def detect_branching_recursion(conn: sqlite3.Connection) -> list[dict]:
|
|
|
1673
1911
|
return True
|
|
1674
1912
|
return False
|
|
1675
1913
|
|
|
1914
|
+
# M2 — Set/Map/WeakSet/WeakMap parameter or local: signals the function
|
|
1915
|
+
# already implements its own memoization / cycle-tracking, so the
|
|
1916
|
+
# branching-recursion warning would be a FP.
|
|
1917
|
+
def _has_memo_collection(snippet: str) -> bool:
|
|
1918
|
+
if not snippet:
|
|
1919
|
+
return False
|
|
1920
|
+
if re.search(r"\b(?:Set|Map|WeakSet|WeakMap)\s*<", snippet): # TS generic
|
|
1921
|
+
return True
|
|
1922
|
+
if re.search(r"\bnew\s+(?:Set|Map|WeakSet|WeakMap)\b", snippet): # JS literal
|
|
1923
|
+
return True
|
|
1924
|
+
if re.search(r"\b(?:visited|seen|memo|cache|memoised|memoized)\b\s*[:=]", snippet):
|
|
1925
|
+
return True
|
|
1926
|
+
if re.search(r"@(?:lru_cache|cache|memoize|memoise|functools\.lru_cache)\b", snippet):
|
|
1927
|
+
return True
|
|
1928
|
+
return False
|
|
1929
|
+
|
|
1676
1930
|
for r in rows:
|
|
1677
1931
|
if _is_test_path(r["file_path"]):
|
|
1678
1932
|
continue
|
|
@@ -1715,6 +1969,14 @@ def detect_branching_recursion(conn: sqlite3.Connection) -> list[dict]:
|
|
|
1715
1969
|
)
|
|
1716
1970
|
if _has_explicit_depth_guard(_row_value(r, "language", ""), snippet):
|
|
1717
1971
|
continue
|
|
1972
|
+
if _has_memo_collection(snippet):
|
|
1973
|
+
# M2: function carries its own Set/Map/WeakSet — already memoised.
|
|
1974
|
+
continue
|
|
1975
|
+
# M1: pin the location at the first self-call line if we can find it,
|
|
1976
|
+
# not the function declaration. The detector flagged because
|
|
1977
|
+
# self_call_count >= 2; the first occurrence of `name(` (or shorthand
|
|
1978
|
+
# `name(`) inside the body is the most informative anchor.
|
|
1979
|
+
match_line = _find_first_keyword_line(snippet, (r["name"] + "(",), r["line_start"])
|
|
1718
1980
|
results.append(
|
|
1719
1981
|
_finding(
|
|
1720
1982
|
"branching-recursion",
|
|
@@ -1722,6 +1984,16 @@ def detect_branching_recursion(conn: sqlite3.Connection) -> list[dict]:
|
|
|
1722
1984
|
r,
|
|
1723
1985
|
f"Branching recursion ({r['self_call_count']} self-calls) without memoization",
|
|
1724
1986
|
"high",
|
|
1987
|
+
evidence={
|
|
1988
|
+
"self_call_count": r["self_call_count"],
|
|
1989
|
+
"guard_check": "no depth/budget guard found in body, no memo Set/Map detected",
|
|
1990
|
+
"to_suppress": (
|
|
1991
|
+
"add `if (depth > N) return` early-return guard, OR pass a "
|
|
1992
|
+
"Set/Map/WeakSet for memoisation/cycle-tracking, OR add "
|
|
1993
|
+
"`# roam: ignore-math[branching-recursion]` to the function line"
|
|
1994
|
+
),
|
|
1995
|
+
},
|
|
1996
|
+
match_line=match_line,
|
|
1725
1997
|
)
|
|
1726
1998
|
)
|
|
1727
1999
|
return results
|
|
@@ -2332,6 +2604,25 @@ def _calibrate_finding(finding: dict, context: dict | None) -> dict:
|
|
|
2332
2604
|
finding["confidence"] = _lower_confidence(finding["confidence"])
|
|
2333
2605
|
finding["reason"] += " (bounded loop)"
|
|
2334
2606
|
|
|
2607
|
+
# M8 — confidence calibration floor.
|
|
2608
|
+
# Categories where the FP-fix is heuristic-only (no AST-level proof)
|
|
2609
|
+
# cap at 'medium' regardless of caller-count or runtime boost. Real-world
|
|
2610
|
+
# calibration on union-web showed "high confidence" branching-recursion
|
|
2611
|
+
# was 0/1 true positive; same pattern likely on sort-then-subscript when
|
|
2612
|
+
# the result is also iterated.
|
|
2613
|
+
_MEDIUM_FLOOR_TASKS = {"branching-recursion", "sort-to-select"}
|
|
2614
|
+
if finding.get("task_id") in _MEDIUM_FLOOR_TASKS and finding.get("confidence") == "high":
|
|
2615
|
+
# Only floor when there's no strong runtime signal — runtime-hot code
|
|
2616
|
+
# earns the high-confidence boost.
|
|
2617
|
+
if not runtime_hot:
|
|
2618
|
+
finding["confidence"] = "medium"
|
|
2619
|
+
evidence_dict = finding.setdefault("evidence", {}) if isinstance(finding.get("evidence"), dict) else {}
|
|
2620
|
+
if isinstance(evidence_dict, dict):
|
|
2621
|
+
evidence_dict["calibration_floor"] = (
|
|
2622
|
+
"category capped at 'medium' — heuristic-only detection has high FP rate; "
|
|
2623
|
+
"use --confidence high to skip these"
|
|
2624
|
+
)
|
|
2625
|
+
|
|
2335
2626
|
return finding
|
|
2336
2627
|
|
|
2337
2628
|
|
|
@@ -131,6 +131,7 @@ _COMMANDS = {
|
|
|
131
131
|
),
|
|
132
132
|
"rules-validate": ("roam.commands.cmd_rules_validate", "rules_validate"),
|
|
133
133
|
"dogfood": ("roam.commands.cmd_dogfood", "dogfood"),
|
|
134
|
+
"suppress": ("roam.commands.cmd_suppress", "suppress"),
|
|
134
135
|
"stats": ("roam.commands.cmd_stats", "stats"),
|
|
135
136
|
"why-fail": ("roam.commands.cmd_why_fail", "why_fail"),
|
|
136
137
|
"recommend": ("roam.commands.cmd_recommend", "recommend"),
|
|
@@ -284,6 +285,7 @@ _CATEGORIES = {
|
|
|
284
285
|
"audit-trail-export",
|
|
285
286
|
"audit-trail-conformance-check",
|
|
286
287
|
"dogfood",
|
|
288
|
+
"suppress",
|
|
287
289
|
"pr-diff",
|
|
288
290
|
"api-changes",
|
|
289
291
|
"semantic-diff",
|