roam-code 12.31__tar.gz → 12.32__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.31 → roam_code-12.32}/PKG-INFO +1 -1
- {roam_code-12.31 → roam_code-12.32}/pyproject.toml +1 -1
- {roam_code-12.31 → roam_code-12.32}/src/roam/catalog/detectors.py +198 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/catalog/tasks.py +69 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_debt.py +8 -1
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_math.py +38 -1
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_pr_comment_render.py +11 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/finding_suppress.py +52 -2
- {roam_code-12.31 → roam_code-12.32}/src/roam/competitor_site_data.py +2 -2
- {roam_code-12.31 → roam_code-12.32}/src/roam/mcp-server-card.json +1 -1
- {roam_code-12.31 → roam_code-12.32}/src/roam_code.egg-info/PKG-INFO +1 -1
- {roam_code-12.31 → roam_code-12.32}/tests/test_math.py +79 -2
- {roam_code-12.31 → roam_code-12.32}/tests/test_mcp_server.py +12 -2
- {roam_code-12.31 → roam_code-12.32}/LICENSE +0 -0
- {roam_code-12.31 → roam_code-12.32}/README.md +0 -0
- {roam_code-12.31 → roam_code-12.32}/setup.cfg +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/__main__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/analysis/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/analysis/effects.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/analysis/taint.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/api.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/ask/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/ask/classifier.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/ask/recipes.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/ask/runner.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/ask/workflow.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/attest/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/attest/cga.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/bridges/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/bridges/base.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/bridges/bridge_config.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/bridges/bridge_django.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/bridges/bridge_protobuf.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/bridges/bridge_rest_api.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/bridges/bridge_salesforce.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/bridges/bridge_template.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/bridges/registry.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/catalog/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/catalog/fixes.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/catalog/python_idioms.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/catalog/smells.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/cli.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/audit_trail_helpers.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/changed_files.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_adrs.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_adversarial.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_affected.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_affected_tests.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_agent_context.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_agent_export.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_agent_plan.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_ai_ratio.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_ai_readiness.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_alerts.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_annotate.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_api.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_api_changes.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_api_drift.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_ask.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_attest.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_audit.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_audit_trail_conformance.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_audit_trail_export.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_audit_trail_verify.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_auth_gaps.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_bisect.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_breaking.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_budget.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_bus_factor.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_capsule.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_cga.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_changelog.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_check_rules.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_ci_setup.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_clean.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_clones.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_closure.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_clusters.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_codeowners.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_complexity.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_config.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_congestion.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_context.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_conventions.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_coupling.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_coverage_gaps.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_critique.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_cut.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_dark_matter.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_dashboard.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_dead.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_deps.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_describe.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_dev_profile.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_diagnose.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_diff.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_disambiguate.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_doc_staleness.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_docs_coverage.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_doctor.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_dogfood.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_drift.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_duplicates.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_effects.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_endpoints.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_entry_points.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_eval_retrieve.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_exit_codes.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_fan.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_file.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_fingerprint.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_fitness.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_flag_dead.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_fleet.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_fn_coupling.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_forecast.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_graph_export.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_graph_stats.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_grep.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_guard.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_health.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_help_search.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_hooks.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_hotspots.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_hover.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_impact.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_index.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_index_bundle.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_index_stats.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_ingest_trace.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_init.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_intent.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_invariants.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_layers.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_map.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_mcp_setup.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_mcp_status.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_metrics.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_metrics_push.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_migration_safety.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_minimap.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_missing_index.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_module.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_mutate.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_n1.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_oracle.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_orchestrate.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_orphan_imports.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_orphan_routes.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_over_fetch.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_owner.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_partition.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_path_coverage.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_patterns.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_plan.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_plan_refactor.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_plugins.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_pr_analyze.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_pr_diff.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_pr_prep.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_pr_risk.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_pre_commit.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_preflight.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_py_modern.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_py_types.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_pytest_fixtures.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_recipes.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_recommend.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_relate.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_report.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_reset.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_retrieve.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_risk.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_rules.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_rules_validate.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_safe_delete.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_safe_zones.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_sbom.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_schema.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_search.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_search_semantic.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_secrets.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_semantic_diff.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_simulate.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_simulate_departure.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_sketch.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_smells.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_spectral.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_split.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_stats.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_suggest_refactoring.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_suggest_reviewers.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_supply_chain.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_suppress.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_symbol.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_syntax_check.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_taint.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_telemetry.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_test_gaps.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_test_impact.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_test_pyramid.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_test_scaffold.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_testmap.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_timeline.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_tour.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_trace.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_trends.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_triage.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_understand.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_uses.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_verify.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_verify_imports.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_version.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_vibe_check.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_visualize.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_vuln_map.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_vuln_reach.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_vulns.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_watch.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_weather.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_why.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_why_fail.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_workflow.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_ws.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_xlang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/codeowners_helpers.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/context_helpers.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/gate_presets.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/git_helpers.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/graph_helpers.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/metrics_history.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/next_steps.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/pr_analyze/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/pr_analyze/audit_trail.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/pr_analyze/cache.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/pr_analyze/rules.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/resolve.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/commands/suppression.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/config.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/coverage_reports.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/critique/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/critique/aggregator.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/critique/checks.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/db/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/db/connection.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/db/queries.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/db/schema.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/eval/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/eval/harness.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/exit_codes.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/fleet/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/fleet/adapters.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/fleet/manifest.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/git_utils.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/graph/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/graph/anomaly.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/graph/builder.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/graph/clone_detect.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/graph/clusters.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/graph/cycles.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/graph/dark_matter.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/graph/diff.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/graph/fingerprint.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/graph/layers.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/graph/pagerank.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/graph/partition.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/graph/pathfinding.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/graph/propagation.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/graph/simulate.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/graph/spectral.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/graph/stats.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/index/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/index/complexity.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/index/discovery.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/index/django_post.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/index/file_roles.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/index/git_stats.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/index/gitignore.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/index/incremental.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/index/indexer.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/index/parser.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/index/pytest_fixtures.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/index/registry_dispatch.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/index/relations.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/index/symbols.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/index/test_conventions.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/apex_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/aura_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/base.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/c_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/csharp_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/extractor_schema.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/extractors/kotlin.yaml +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/foxpro_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/generic_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/go_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/hcl_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/java_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/javascript_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/kotlin_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/php_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/python_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/query_engine.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/registry.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/ruby_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/rust_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/scala_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/sfxml_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/sql_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/swift_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/typescript_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/visualforce_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/languages/yaml_lang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/mcp_extras/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/mcp_extras/completions.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/mcp_extras/concurrency.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/mcp_extras/progress.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/mcp_extras/sampling.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/mcp_extras/session.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/mcp_extras/watcher.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/mcp_server.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/observability.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/output/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/output/confidence.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/output/errors.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/output/file_role_hints.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/output/formatter.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/output/framework_filter.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/output/mermaid.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/output/project_shape.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/output/sarif.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/output/schema_registry.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/plugins.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/refactor/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/refactor/codegen.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/refactor/transforms.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/retrieve/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/retrieve/learned_ranker.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/retrieve/pipeline.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/retrieve/rerank.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/retrieve/seeds.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/retrieve/semantic.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/rules/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/rules/ast_match.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/rules/builtin.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/rules/dataflow.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/rules/engine.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/runtime/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/runtime/daemon.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/runtime/graph_backend.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/runtime/hotspots.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/runtime/lock_modes.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/runtime/lockmgr.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/runtime/trace_ingest.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/search/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/search/framework_packs.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/search/index_embeddings.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/search/onnx_embeddings.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/search/tfidf.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/aibom_extension.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_classifier.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_engine.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/api_error_leak.yaml +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/java_fileupload_path_traversal.yaml +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/js_insecure_jwt_decode.yaml +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/js_localstorage_secrets.yaml +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/js_prototype_pollution.yaml +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/js_ssrf.yaml +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/js_xss.yaml +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/python_basic.yaml +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/python_deserialization.yaml +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/python_path_traversal.yaml +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/python_socketio_remote_source.yaml +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/python_sqli.yaml +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/python_urllib_open_redirect.yaml +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/vue_v_html.yaml +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/vuln_reach.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/security/vuln_store.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/surface_counts.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/telemetry.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/templates/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/templates/ci/Jenkinsfile +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/templates/ci/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/templates/ci/agent-review.yml +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/templates/ci/azure-pipelines.yml +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/templates/ci/bitbucket-pipelines.yml +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/templates/ci/gitlab-ci.yml +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/workspace/__init__.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/workspace/aggregator.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/workspace/api_scanner.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/workspace/config.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam/workspace/db.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam_code.egg-info/SOURCES.txt +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam_code.egg-info/dependency_links.txt +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam_code.egg-info/entry_points.txt +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam_code.egg-info/requires.txt +0 -0
- {roam_code-12.31 → roam_code-12.32}/src/roam_code.egg-info/top_level.txt +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_adrs.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_adversarial.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_affected.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_agent_export.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_agent_mode.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_agent_plan_context.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_ai_ratio.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_ai_readiness.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_alerts_cmd.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_annotations.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_anomaly.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_api_changes.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_api_drift.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_ask.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_attest.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_audit_trail_aggregate.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_audit_trail_conformance.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_audit_trail_sequence.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_audit_trail_verify.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_auth_gaps.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_backend_fixes_round2.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_backend_fixes_round3.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_basic.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_batch_mcp.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_bisect.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_bridge_django.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_bridges.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_bridges_extended.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_budget.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_budget_flag.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_budget_phase2.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_bus_factor.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_capsule.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_cga.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_check_rules.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_ci_gate_eval.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_ci_sarif_guard.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_ci_setup.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_clones.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_closure.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_codeowners.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_commands_architecture.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_commands_exploration.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_commands_health.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_commands_refactoring.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_commands_workflow.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_competitor_site_data.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_comprehensive.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_config.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_congestion.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_context_propagation.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_conventions_cmd.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_coverage_gaps_cmd.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_coverage_ingestion.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_critique.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_cut.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_dark_matter.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_dark_matter_helpers.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_dashboard.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_dataflow_dead.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_dead_aging.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_defer_loading.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_demo_gif_asset.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_describe.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_detail_flag_hints.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_detector_precision.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_deterministic_output.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_dev_profile.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_difficulty_scoring.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_doc_consistency.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_doc_staleness.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_docker_assets.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_docs_coverage.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_docs_site_quality.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_doctor.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_dogfood.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_drift.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_drift_by_team.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_duplicates.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_effects.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_effects_propagation.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_endpoints.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_entry_points_cmd.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_eval_retrieve.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_except_pass_narrow.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_exclude_patterns.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_exit_codes.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_extractor_grammar_drift.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_fallback_contracts.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_file_roles.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_finding_suppress.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_fingerprint.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_fixes.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_flag_dead.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_fleet.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_fn_coupling.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_forecast.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_formatters.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_foxpro.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_framework_detection.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_gate_presets.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_git_helpers.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_git_utils.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_guard.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_health_gate.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_hooks.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_hotspots.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_hover.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_index.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_index_bundle.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_init_cmd.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_install_check.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_intent.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_invariants.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_json_contracts.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_json_error_envelope.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_kotlin_swift_extractors.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_language_corpus.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_languages.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_laravel_fp_fixes.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_library_api.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_math_fp_fixes.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_math_tips.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_mcp_extras.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_mcp_setup.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_mermaid.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_metrics_cmd.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_metrics_push.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_migration_safety.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_minimap.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_missing_index.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_mutate.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_n1.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_next_steps.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_onboard.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_oracle.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_orchestrate.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_orphan_routes.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_oss_bench_harness.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_over_fetch.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_pagerank_truncation.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_partition.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_path_coverage.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_patterns_cmd.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_performance.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_personalized_pagerank.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_plan.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_plugin_discovery.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_pr_analyze.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_pr_analyze_cache.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_pr_analyze_edge_cases.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_pr_analyze_helpers.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_pr_analyze_v2_signals.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_pr_comment_render.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_pr_comment_script.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_pr_diff.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_pr_risk_author.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_progress.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_progressive_disclosure.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_properties.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_pytest_fixtures.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_python_extractor_v2.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_python_idioms_e2e.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_python_pivot.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_readme_surface_consistency.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_realworld_feedback.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_refactoring_intelligence.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_registry_dispatch.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_regression_fp_corpus.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_relate.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_report.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_reset_clean.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_resolve.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_retrieve.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_retrieve_cross_repo.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_retrieve_seeds.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_risk.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_ruby.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_rule_profiles.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_rules.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_rules_ast_match.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_rules_community_pack.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_rules_dataflow.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_rules_symbol_requirements.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_rules_validate.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_runtime.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_runtime_score.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_salesforce.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_sarif_flag.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_sbom.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_scala.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_schema_versioning.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_search_explain.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_secrets.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_secrets_v2.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_semantic_diff.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_semantic_onnx.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_semantic_search.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_simulate.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_simulate_departure.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_sketch.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_smells.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_smoke.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_sna_metrics.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_spectral.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_split_cmd.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_sql.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_suggest_reviewers.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_supply_chain.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_surface_counts.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_syntax_check.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_taint.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_taint_analysis.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_taint_classifier.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_taint_intraprocedural.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_test_conventions.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_test_gaps.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_test_scaffold.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_testmap.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_top_flag_consistency.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_tour_cmd.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_trends.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_trends_cohort.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_triage.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_uses_cmd.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_v1215_passes.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_v1216_passes.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_v1216_passes_41_50.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_v1216_passes_51_60.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_v1217_passes_61_80.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_v1218_passes_81_90.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_v1219_passes_91_100.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_v1220_passes_101_110.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_v1221_query_timeout.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_v1221_untested_commands.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_v12_2.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_v2_edge_cases.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_v2_integration.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_v6_features.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_v71_features.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_v7_features.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_v82_features.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_verify.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_verify_imports.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_vibe_check.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_visualize.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_vuln.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_vulns_cmd.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_watch.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_why.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_workspace.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_ws_resolve_fixes.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_xlang.py +0 -0
- {roam_code-12.31 → roam_code-12.32}/tests/test_yaml_hcl.py +0 -0
|
@@ -1723,6 +1723,10 @@ def autodetect_framework_profile() -> str | None:
|
|
|
1723
1723
|
tanstack = "@tanstack/vue-query" in deps or "@tanstack/query-core" in deps
|
|
1724
1724
|
if tanstack and (vue_ver.startswith("^3") or vue_ver.startswith("3") or vue_ver.startswith("~3")):
|
|
1725
1725
|
return "vue3-tanstack"
|
|
1726
|
+
# Z6 (2026-05-06) — Express/Koa/Fastify don't have framework
|
|
1727
|
+
# profiles yet (they're light frameworks; the I/O patterns are
|
|
1728
|
+
# generic Node fs / http). Return None for now but flag in meta
|
|
1729
|
+
# so future versions can add a profile when one becomes useful.
|
|
1726
1730
|
|
|
1727
1731
|
composer = _read_json(_os.path.join(cwd, "composer.json"))
|
|
1728
1732
|
if composer:
|
|
@@ -2766,6 +2770,197 @@ def detect_chained_collection_walks(conn: sqlite3.Connection) -> list[dict]:
|
|
|
2766
2770
|
return results
|
|
2767
2771
|
|
|
2768
2772
|
|
|
2773
|
+
# Z1 (2026-05-06) — React: `useEffect(() => { ... })` without a dependency
|
|
2774
|
+
# array runs on EVERY render. Almost always a bug — the dev forgot the
|
|
2775
|
+
# second argument. The fix is `useEffect(() => { ... }, [deps])` or
|
|
2776
|
+
# `useEffect(() => { ... }, [])` for mount-only.
|
|
2777
|
+
_RE_USEEFFECT_NO_DEPS = re.compile(
|
|
2778
|
+
r"\buseEffect\s*\(\s*(?:async\s+)?(?:\(\s*\)|function\s*\(\s*\))\s*=>\s*\{[^}]*?\}\s*\)",
|
|
2779
|
+
re.DOTALL,
|
|
2780
|
+
)
|
|
2781
|
+
_RE_USEEFFECT_WITH_DEPS = re.compile(
|
|
2782
|
+
r"\buseEffect\s*\(\s*[^,]+,\s*\[",
|
|
2783
|
+
)
|
|
2784
|
+
|
|
2785
|
+
|
|
2786
|
+
def detect_useeffect_missing_deps(conn: sqlite3.Connection) -> list[dict]:
|
|
2787
|
+
"""React: `useEffect(() => {...})` without a dependency array."""
|
|
2788
|
+
try:
|
|
2789
|
+
rows = conn.execute(
|
|
2790
|
+
"SELECT s.id, s.name, s.qualified_name, s.kind, f.path AS file_path, "
|
|
2791
|
+
"s.line_start, s.line_end "
|
|
2792
|
+
"FROM symbols s "
|
|
2793
|
+
"JOIN files f ON s.file_id = f.id "
|
|
2794
|
+
"WHERE s.kind IN ('function', 'method') "
|
|
2795
|
+
"AND f.language IN ('javascript', 'typescript', 'tsx', 'jsx')"
|
|
2796
|
+
).fetchall()
|
|
2797
|
+
except Exception:
|
|
2798
|
+
return []
|
|
2799
|
+
results = []
|
|
2800
|
+
for r in rows:
|
|
2801
|
+
if _is_test_path(r["file_path"]):
|
|
2802
|
+
continue
|
|
2803
|
+
snippet = _read_symbol_source(r["file_path"], r["line_start"], r["line_end"])
|
|
2804
|
+
if not snippet or "useEffect" not in snippet:
|
|
2805
|
+
continue
|
|
2806
|
+
# Skip if every useEffect call also has a deps array.
|
|
2807
|
+
no_deps = _RE_USEEFFECT_NO_DEPS.search(snippet)
|
|
2808
|
+
if not no_deps:
|
|
2809
|
+
continue
|
|
2810
|
+
# Verify the SAME call doesn't have a deps array (regex catches the
|
|
2811
|
+
# no-deps shape but a more elaborate body could trick it).
|
|
2812
|
+
# Conservative: only fire when there's NO useEffect-with-deps in body.
|
|
2813
|
+
if _RE_USEEFFECT_WITH_DEPS.search(snippet):
|
|
2814
|
+
# Mixed bag — at least one useEffect has deps, can't reliably
|
|
2815
|
+
# tell which one is the problem without a real parser. Skip.
|
|
2816
|
+
continue
|
|
2817
|
+
line_offset = snippet[: no_deps.start()].count("\n")
|
|
2818
|
+
match_line = (r["line_start"] or 1) + line_offset
|
|
2819
|
+
results.append(
|
|
2820
|
+
_finding(
|
|
2821
|
+
"useeffect-missing-deps",
|
|
2822
|
+
"no-deps-array",
|
|
2823
|
+
r,
|
|
2824
|
+
"useEffect without dependency array — runs on every render",
|
|
2825
|
+
"high",
|
|
2826
|
+
match_line=match_line,
|
|
2827
|
+
snippet=snippet,
|
|
2828
|
+
matched_patterns=["useEffect call", "no second-arg dep array"],
|
|
2829
|
+
)
|
|
2830
|
+
)
|
|
2831
|
+
return results
|
|
2832
|
+
|
|
2833
|
+
|
|
2834
|
+
# Z2 (2026-05-06) — `eval()` / `exec()` / `new Function()` / `setTimeout(string)`
|
|
2835
|
+
# in production source. These are arbitrary code execution sinks if the
|
|
2836
|
+
# input is user-derived. Even when "safe" (literal string), they trip
|
|
2837
|
+
# CSP rules and bundler optimisations. Suppress when test path.
|
|
2838
|
+
_RE_EVAL_CALLS = re.compile(
|
|
2839
|
+
r"\b(?:eval|exec|execfile|compile)\s*\("
|
|
2840
|
+
r"|\bnew\s+Function\s*\("
|
|
2841
|
+
r"|\bsetTimeout\s*\(\s*['\"]"
|
|
2842
|
+
r"|\bsetInterval\s*\(\s*['\"]",
|
|
2843
|
+
)
|
|
2844
|
+
|
|
2845
|
+
|
|
2846
|
+
def detect_dangerous_eval(conn: sqlite3.Connection) -> list[dict]:
|
|
2847
|
+
"""Detect `eval`, `exec`, `new Function(...)`, `setTimeout(string)` — code-injection sinks."""
|
|
2848
|
+
try:
|
|
2849
|
+
rows = conn.execute(
|
|
2850
|
+
"SELECT s.id, s.name, s.qualified_name, s.kind, f.path AS file_path, "
|
|
2851
|
+
"f.language AS language, s.line_start, s.line_end "
|
|
2852
|
+
"FROM symbols s "
|
|
2853
|
+
"JOIN files f ON s.file_id = f.id "
|
|
2854
|
+
"WHERE s.kind IN ('function', 'method')"
|
|
2855
|
+
).fetchall()
|
|
2856
|
+
except Exception:
|
|
2857
|
+
return []
|
|
2858
|
+
results = []
|
|
2859
|
+
for r in rows:
|
|
2860
|
+
if _is_test_path(r["file_path"]):
|
|
2861
|
+
continue
|
|
2862
|
+
# Skip non-source roles when we can tell.
|
|
2863
|
+
path = (r["file_path"] or "").replace("\\", "/").lower()
|
|
2864
|
+
if "/migration" in path or "/script" in path or "/cli" in path:
|
|
2865
|
+
# CLI / migration scripts often legitimately use exec/eval.
|
|
2866
|
+
continue
|
|
2867
|
+
snippet = _read_symbol_source(r["file_path"], r["line_start"], r["line_end"])
|
|
2868
|
+
if not snippet:
|
|
2869
|
+
continue
|
|
2870
|
+
m = _RE_EVAL_CALLS.search(snippet)
|
|
2871
|
+
if not m:
|
|
2872
|
+
continue
|
|
2873
|
+
# Skip ast.literal_eval (safe), regex.compile (different "compile"
|
|
2874
|
+
# — won't actually match because it ends in `.compile(` with a dot
|
|
2875
|
+
# before, so prefix isn't word-boundary — but be defensive).
|
|
2876
|
+
if "literal_eval" in snippet[max(0, m.start() - 20) : m.end()]:
|
|
2877
|
+
continue
|
|
2878
|
+
if ".compile(" in snippet[max(0, m.start() - 5) : m.end() + 1]:
|
|
2879
|
+
continue
|
|
2880
|
+
line_offset = snippet[: m.start()].count("\n")
|
|
2881
|
+
match_line = (r["line_start"] or 1) + line_offset
|
|
2882
|
+
called = m.group(0).rstrip("(")
|
|
2883
|
+
results.append(
|
|
2884
|
+
_finding(
|
|
2885
|
+
"dangerous-eval",
|
|
2886
|
+
"eval-or-exec",
|
|
2887
|
+
r,
|
|
2888
|
+
f"Dangerous dynamic execution sink ({called}) — code-injection risk if input is user-derived",
|
|
2889
|
+
"high",
|
|
2890
|
+
match_line=match_line,
|
|
2891
|
+
snippet=snippet,
|
|
2892
|
+
matched_patterns=[f"call: {called}", "not in test/migration/script path"],
|
|
2893
|
+
)
|
|
2894
|
+
)
|
|
2895
|
+
return results
|
|
2896
|
+
|
|
2897
|
+
|
|
2898
|
+
# Z5 (2026-05-06) — JS/TS DOM listener leak: `addEventListener` without
|
|
2899
|
+
# a paired `removeEventListener` keeps references alive after the
|
|
2900
|
+
# component unmounts. Detect ONLY when the function looks like a
|
|
2901
|
+
# component lifecycle (useEffect / componentDidMount / connectedCallback /
|
|
2902
|
+
# constructor) and addEventListener appears without remove.
|
|
2903
|
+
_RE_ADD_LISTENER = re.compile(r"\baddEventListener\s*\(")
|
|
2904
|
+
_RE_REMOVE_LISTENER = re.compile(r"\bremoveEventListener\s*\(")
|
|
2905
|
+
_RE_LIFECYCLE = re.compile(
|
|
2906
|
+
r"\b(?:useEffect|componentDidMount|componentWillMount|connectedCallback|constructor)\b",
|
|
2907
|
+
)
|
|
2908
|
+
|
|
2909
|
+
|
|
2910
|
+
def detect_unremoved_event_listener(conn: sqlite3.Connection) -> list[dict]:
|
|
2911
|
+
"""JS/TS: `addEventListener` in a lifecycle without paired `removeEventListener`."""
|
|
2912
|
+
try:
|
|
2913
|
+
rows = conn.execute(
|
|
2914
|
+
"SELECT s.id, s.name, s.qualified_name, s.kind, f.path AS file_path, "
|
|
2915
|
+
"s.line_start, s.line_end "
|
|
2916
|
+
"FROM symbols s "
|
|
2917
|
+
"JOIN files f ON s.file_id = f.id "
|
|
2918
|
+
"WHERE s.kind IN ('function', 'method') "
|
|
2919
|
+
"AND f.language IN ('javascript', 'typescript', 'tsx', 'jsx')"
|
|
2920
|
+
).fetchall()
|
|
2921
|
+
except Exception:
|
|
2922
|
+
return []
|
|
2923
|
+
results = []
|
|
2924
|
+
for r in rows:
|
|
2925
|
+
if _is_test_path(r["file_path"]):
|
|
2926
|
+
continue
|
|
2927
|
+
snippet = _read_symbol_source(r["file_path"], r["line_start"], r["line_end"])
|
|
2928
|
+
if not snippet:
|
|
2929
|
+
continue
|
|
2930
|
+
if not _RE_ADD_LISTENER.search(snippet):
|
|
2931
|
+
continue
|
|
2932
|
+
# Only fire for lifecycle-ish bodies — outside of components,
|
|
2933
|
+
# listeners are often global and intentionally never removed.
|
|
2934
|
+
if not _RE_LIFECYCLE.search(snippet):
|
|
2935
|
+
continue
|
|
2936
|
+
# Already paired: presence of removeEventListener anywhere in body.
|
|
2937
|
+
if _RE_REMOVE_LISTENER.search(snippet):
|
|
2938
|
+
continue
|
|
2939
|
+
# `useEffect` should also return a cleanup function. Check for one.
|
|
2940
|
+
if "useEffect" in snippet and re.search(r"return\s+(?:\(\s*\)|function)", snippet):
|
|
2941
|
+
continue
|
|
2942
|
+
m = _RE_ADD_LISTENER.search(snippet)
|
|
2943
|
+
line_offset = snippet[: m.start()].count("\n")
|
|
2944
|
+
match_line = (r["line_start"] or 1) + line_offset
|
|
2945
|
+
results.append(
|
|
2946
|
+
_finding(
|
|
2947
|
+
"unremoved-event-listener",
|
|
2948
|
+
"no-cleanup",
|
|
2949
|
+
r,
|
|
2950
|
+
"addEventListener in component lifecycle without removeEventListener — memory leak",
|
|
2951
|
+
"high",
|
|
2952
|
+
match_line=match_line,
|
|
2953
|
+
snippet=snippet,
|
|
2954
|
+
matched_patterns=[
|
|
2955
|
+
"addEventListener call",
|
|
2956
|
+
"lifecycle context (useEffect / componentDidMount / etc.)",
|
|
2957
|
+
"no paired removeEventListener",
|
|
2958
|
+
],
|
|
2959
|
+
)
|
|
2960
|
+
)
|
|
2961
|
+
return results
|
|
2962
|
+
|
|
2963
|
+
|
|
2769
2964
|
def detect_loop_lookup(conn: sqlite3.Connection) -> list[dict]:
|
|
2770
2965
|
""".index(), .indexOf(), .contains(), .includes() called inside a loop.
|
|
2771
2966
|
|
|
@@ -3736,6 +3931,9 @@ _MATH_DETECTORS = [
|
|
|
3736
3931
|
("spread-accumulator", "spread-rebind", detect_spread_accumulator),
|
|
3737
3932
|
("defer-in-loop", "loop-defer", detect_defer_in_loop),
|
|
3738
3933
|
("chained-collection-walk", "two-pass-walk", detect_chained_collection_walks),
|
|
3934
|
+
("useeffect-missing-deps", "no-deps-array", detect_useeffect_missing_deps),
|
|
3935
|
+
("dangerous-eval", "eval-or-exec", detect_dangerous_eval),
|
|
3936
|
+
("unremoved-event-listener", "no-cleanup", detect_unremoved_event_listener),
|
|
3739
3937
|
("list-prepend", "insert-front", detect_list_prepend),
|
|
3740
3938
|
("sort-to-select", "full-sort", detect_sort_to_select),
|
|
3741
3939
|
("loop-lookup", "method-scan", detect_loop_lookup),
|
|
@@ -400,6 +400,75 @@ CATALOG: dict[str, dict] = {
|
|
|
400
400
|
},
|
|
401
401
|
],
|
|
402
402
|
},
|
|
403
|
+
"unremoved-event-listener": {
|
|
404
|
+
"name": "addEventListener without paired removeEventListener (memory leak)",
|
|
405
|
+
"category": "concurrency",
|
|
406
|
+
"kind": "idiom",
|
|
407
|
+
"ways": [
|
|
408
|
+
{
|
|
409
|
+
"id": "with-cleanup",
|
|
410
|
+
"name": "useEffect cleanup / removeEventListener",
|
|
411
|
+
"time": "n/a",
|
|
412
|
+
"space": "n/a",
|
|
413
|
+
"rank": 1,
|
|
414
|
+
"tip": "Return a cleanup function from useEffect: `useEffect(() => { window.addEventListener('x', h); return () => window.removeEventListener('x', h); }, [])`. For class components, pair in componentWillUnmount.",
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
"id": "no-cleanup",
|
|
418
|
+
"name": "addEventListener with no cleanup",
|
|
419
|
+
"time": "n/a",
|
|
420
|
+
"space": "n/a",
|
|
421
|
+
"rank": 10,
|
|
422
|
+
"tip": "",
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
},
|
|
426
|
+
"dangerous-eval": {
|
|
427
|
+
"name": "Dynamic execution sink (eval / exec / new Function)",
|
|
428
|
+
"category": "error-handling",
|
|
429
|
+
"kind": "idiom",
|
|
430
|
+
"ways": [
|
|
431
|
+
{
|
|
432
|
+
"id": "narrow-or-remove",
|
|
433
|
+
"name": "Use a parser / template / safer dispatch",
|
|
434
|
+
"time": "n/a",
|
|
435
|
+
"space": "n/a",
|
|
436
|
+
"rank": 1,
|
|
437
|
+
"tip": "Prefer ast.literal_eval, JSON.parse, a real template engine, or a switch/dispatch dict over arbitrary-string execution. Tightens CSP; eliminates injection surface.",
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
"id": "eval-or-exec",
|
|
441
|
+
"name": "eval / exec / new Function with dynamic input",
|
|
442
|
+
"time": "n/a",
|
|
443
|
+
"space": "n/a",
|
|
444
|
+
"rank": 10,
|
|
445
|
+
"tip": "",
|
|
446
|
+
},
|
|
447
|
+
],
|
|
448
|
+
},
|
|
449
|
+
"useeffect-missing-deps": {
|
|
450
|
+
"name": "React useEffect without dependency array",
|
|
451
|
+
"category": "concurrency",
|
|
452
|
+
"kind": "idiom",
|
|
453
|
+
"ways": [
|
|
454
|
+
{
|
|
455
|
+
"id": "with-deps",
|
|
456
|
+
"name": "useEffect with explicit dep array",
|
|
457
|
+
"time": "n/a",
|
|
458
|
+
"space": "n/a",
|
|
459
|
+
"rank": 1,
|
|
460
|
+
"tip": "Add the dependency array as the second argument: `useEffect(() => {...}, [deps])`. Use `[]` for mount-only effects.",
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
"id": "no-deps-array",
|
|
464
|
+
"name": "useEffect without deps (runs every render)",
|
|
465
|
+
"time": "n/a",
|
|
466
|
+
"space": "n/a",
|
|
467
|
+
"rank": 10,
|
|
468
|
+
"tip": "",
|
|
469
|
+
},
|
|
470
|
+
],
|
|
471
|
+
},
|
|
403
472
|
"chained-collection-walk": {
|
|
404
473
|
"name": "Chained collection walk (filter+find / map+find / filter+length)",
|
|
405
474
|
"category": "collections",
|
|
@@ -562,10 +562,17 @@ def debt(ctx, limit, by_kind, threshold, roi):
|
|
|
562
562
|
_debt_label = (
|
|
563
563
|
"low debt" if stats["mean_debt"] < 0.1 else "moderate debt" if stats["mean_debt"] < 0.3 else "high debt"
|
|
564
564
|
)
|
|
565
|
+
# Z14 (2026-05-06) — append top-1 hotspot to the verdict so the
|
|
566
|
+
# one-line summary tells you WHERE to look first, not just IF
|
|
567
|
+
# there's debt.
|
|
568
|
+
top_hint = ""
|
|
569
|
+
if all_items:
|
|
570
|
+
top1 = all_items[0]
|
|
571
|
+
top_hint = f" — top hotspot: {top1['path']} (score={top1['debt_score']})"
|
|
565
572
|
_debt_verdict = (
|
|
566
573
|
f"{_debt_label}: {_n_cycles} cycle files, "
|
|
567
574
|
f"{_n_gods} god components, {_n_hotspots} hotspots "
|
|
568
|
-
f"across {stats['total_files']} files"
|
|
575
|
+
f"across {stats['total_files']} files{top_hint}"
|
|
569
576
|
)
|
|
570
577
|
|
|
571
578
|
roi_summary, roi_by_path = ({}, {})
|
|
@@ -147,6 +147,23 @@ def math_cmd(
|
|
|
147
147
|
click.echo(name)
|
|
148
148
|
return
|
|
149
149
|
|
|
150
|
+
# Z7 (2026-05-06) — validate --task against the catalog; on typo, show
|
|
151
|
+
# the closest matches by edit distance instead of running 49 detectors
|
|
152
|
+
# silently to find zero results.
|
|
153
|
+
if task_filter:
|
|
154
|
+
from roam.catalog.tasks import CATALOG
|
|
155
|
+
|
|
156
|
+
if task_filter not in CATALOG:
|
|
157
|
+
import difflib
|
|
158
|
+
|
|
159
|
+
close = difflib.get_close_matches(task_filter, list(CATALOG.keys()), n=3, cutoff=0.4)
|
|
160
|
+
hint = f" Did you mean: {', '.join(close)}?" if close else ""
|
|
161
|
+
click.echo(
|
|
162
|
+
f"NOTE: --task '{task_filter}' is not a known task id."
|
|
163
|
+
f" Run `roam math --json` then look at distinct `task_id` values." + hint,
|
|
164
|
+
err=True,
|
|
165
|
+
)
|
|
166
|
+
|
|
150
167
|
ensure_index()
|
|
151
168
|
|
|
152
169
|
from roam.catalog.detectors import autodetect_framework_profile, run_detectors
|
|
@@ -289,7 +306,18 @@ def math_cmd(
|
|
|
289
306
|
if top_n >= max(3, total // 2):
|
|
290
307
|
category_hint = f"; mostly: {top_cat}"
|
|
291
308
|
if total == 0:
|
|
292
|
-
|
|
309
|
+
# Z3 (2026-05-06) — informative zero-state. When 0 findings,
|
|
310
|
+
# tell the user (a) which profile filter was active, (b) how
|
|
311
|
+
# many detectors ran, (c) what to try next.
|
|
312
|
+
profile_note = ""
|
|
313
|
+
if profile != "balanced":
|
|
314
|
+
profile_note = f" (profile={profile} may be too strict; try --profile balanced)"
|
|
315
|
+
verdict = (
|
|
316
|
+
f"No algorithmic issues detected{profile_note} — "
|
|
317
|
+
f"{detector_meta.get('detectors_executed', 0)} detector(s) ran cleanly. "
|
|
318
|
+
f"Try `roam math --profile aggressive` for more candidates "
|
|
319
|
+
f"or `roam debt --top 10` for refactoring ROI hotspots."
|
|
320
|
+
)
|
|
293
321
|
elif suppressed_count > 0:
|
|
294
322
|
verdict = (
|
|
295
323
|
f"{unsuppressed_total} unsuppressed candidate{'s' if unsuppressed_total != 1 else ''} "
|
|
@@ -341,6 +369,15 @@ def math_cmd(
|
|
|
341
369
|
[float(f.get("impact_score", 0.0) or 0.0) for f in findings],
|
|
342
370
|
default=0.0,
|
|
343
371
|
),
|
|
372
|
+
# Z13 (2026-05-06) — top_tasks_by_count helps CI
|
|
373
|
+
# dashboards / agents prioritise without iterating
|
|
374
|
+
# every finding. Format: [{task_id, count}, ...].
|
|
375
|
+
"top_tasks_by_count": [
|
|
376
|
+
{"task_id": tid, "count": n}
|
|
377
|
+
for tid, n in __import__("collections")
|
|
378
|
+
.Counter(f.get("task_id", "?") for f in findings)
|
|
379
|
+
.most_common(3)
|
|
380
|
+
],
|
|
344
381
|
},
|
|
345
382
|
findings=findings,
|
|
346
383
|
)
|
|
@@ -287,6 +287,13 @@ def _section_rule_violations(rule_violations: list[dict]) -> list[str]:
|
|
|
287
287
|
out = ["### Architecture rule violations", ""]
|
|
288
288
|
block_v = [v for v in rule_violations if v.get("severity") == "BLOCK"]
|
|
289
289
|
warn_v = [v for v in rule_violations if v.get("severity") in ("WARN", "WARNING")]
|
|
290
|
+
# Z4 (2026-05-06) — when total violations is huge, wrap the per-item
|
|
291
|
+
# detail in a collapsible <details> block so the comment doesn't
|
|
292
|
+
# dominate the PR thread on noisy diffs.
|
|
293
|
+
use_details = len(rule_violations) >= 12
|
|
294
|
+
if use_details:
|
|
295
|
+
out.append(f"<details><summary>{len(rule_violations)} violation(s) — expand for detail</summary>")
|
|
296
|
+
out.append("")
|
|
290
297
|
for v in block_v[:5]:
|
|
291
298
|
out.append(f"- **BLOCK** `{v['rule_id']}`: `{v['file']}` -> `{v['matched_import']}`")
|
|
292
299
|
if v.get("description"):
|
|
@@ -309,6 +316,10 @@ def _section_rule_violations(rule_violations: list[dict]) -> list[str]:
|
|
|
309
316
|
chunks = [f"`{rid}` x{n} ({sev})" for (sev, rid), n in counts.most_common(5)]
|
|
310
317
|
more_total = extra_block + extra_warn
|
|
311
318
|
out.append(f"- _...{more_total} more violation(s): " + ", ".join(chunks) + "._")
|
|
319
|
+
# Z4 — close the collapsible block when we opened one.
|
|
320
|
+
if use_details:
|
|
321
|
+
out.append("")
|
|
322
|
+
out.append("</details>")
|
|
312
323
|
out.append("")
|
|
313
324
|
return out
|
|
314
325
|
|
|
@@ -91,6 +91,52 @@ def _inline_match(line_text: str, command: str, task_id: str) -> bool:
|
|
|
91
91
|
return False
|
|
92
92
|
|
|
93
93
|
|
|
94
|
+
def _parse_simple_ignore_findings_yaml(text: str) -> dict:
|
|
95
|
+
"""Minimal YAML parser for .roamignore-findings — no PyYAML required.
|
|
96
|
+
|
|
97
|
+
Handles the documented shape only:
|
|
98
|
+
|
|
99
|
+
rules:
|
|
100
|
+
- task_id: io-in-loop
|
|
101
|
+
path_glob: "src/composables/**/*.ts"
|
|
102
|
+
reason: "..."
|
|
103
|
+
- task_id: branching-recursion
|
|
104
|
+
path_glob: "src/utils/object-diff.ts"
|
|
105
|
+
|
|
106
|
+
Anything more complex (anchors, multi-line strings, nested lists)
|
|
107
|
+
needs real PyYAML. Returns ``{}`` on shapes we can't recognise so
|
|
108
|
+
callers fall through to a clean empty-rules state.
|
|
109
|
+
"""
|
|
110
|
+
rules: list[dict] = []
|
|
111
|
+
current: dict | None = None
|
|
112
|
+
in_rules_block = False
|
|
113
|
+
for raw in text.splitlines():
|
|
114
|
+
line = raw.rstrip()
|
|
115
|
+
stripped = line.strip()
|
|
116
|
+
if not stripped or stripped.startswith("#"):
|
|
117
|
+
continue
|
|
118
|
+
if stripped == "rules:" or stripped.startswith("rules:"):
|
|
119
|
+
in_rules_block = True
|
|
120
|
+
continue
|
|
121
|
+
if not in_rules_block:
|
|
122
|
+
continue
|
|
123
|
+
if stripped.startswith("- "):
|
|
124
|
+
if current:
|
|
125
|
+
rules.append(current)
|
|
126
|
+
current = {}
|
|
127
|
+
stripped = stripped[2:].strip()
|
|
128
|
+
# First key on the same line as `-` is the common shape.
|
|
129
|
+
if ":" in stripped:
|
|
130
|
+
k, _, v = stripped.partition(":")
|
|
131
|
+
current[k.strip()] = v.strip().strip('"').strip("'")
|
|
132
|
+
elif current is not None and ":" in stripped:
|
|
133
|
+
k, _, v = stripped.partition(":")
|
|
134
|
+
current[k.strip()] = v.strip().strip('"').strip("'")
|
|
135
|
+
if current:
|
|
136
|
+
rules.append(current)
|
|
137
|
+
return {"rules": rules} if rules else {}
|
|
138
|
+
|
|
139
|
+
|
|
94
140
|
def _load_ignore_findings_file(path: Path) -> list[dict]:
|
|
95
141
|
"""Load `.roamignore-findings` from ``path``. Returns ``[]`` on any error.
|
|
96
142
|
|
|
@@ -116,11 +162,15 @@ def _load_ignore_findings_file(path: Path) -> list[dict]:
|
|
|
116
162
|
|
|
117
163
|
data = yaml.safe_load(text) or {}
|
|
118
164
|
except ImportError:
|
|
119
|
-
# No PyYAML:
|
|
165
|
+
# No PyYAML: try strict JSON first, then a minimal-YAML fallback so
|
|
166
|
+
# the .roamignore-findings format works on Python 3.9 / installs
|
|
167
|
+
# without PyYAML (PyYAML is not a project dependency).
|
|
120
168
|
try:
|
|
121
169
|
data = _json.loads(text)
|
|
122
170
|
except _json.JSONDecodeError:
|
|
123
|
-
|
|
171
|
+
data = _parse_simple_ignore_findings_yaml(text)
|
|
172
|
+
if not data:
|
|
173
|
+
return []
|
|
124
174
|
except Exception: # noqa: BLE001 — malformed YAML never crashes the analyser
|
|
125
175
|
return []
|
|
126
176
|
rules = data.get("rules") if isinstance(data, dict) else []
|
|
@@ -1362,8 +1362,8 @@ MAP_METADATA: dict[str, dict[str, object]] = {
|
|
|
1362
1362
|
"relationship": "self",
|
|
1363
1363
|
"peer": True,
|
|
1364
1364
|
"graph": "PageRank + Tarjan + Louvain + layers",
|
|
1365
|
-
"note": "Graph algorithms (PageRank, SCC, Louvain, Fiedler) on tree-sitter ASTs fused with git history in SQLite. 136 MCP tools, 187 CLI commands. 19 Python idiom detectors (v12.7+).
|
|
1366
|
-
"version_evaluated": "12.
|
|
1365
|
+
"note": "Graph algorithms (PageRank, SCC, Louvain, Fiedler) on tree-sitter ASTs fused with git history in SQLite. 136 MCP tools, 187 CLI commands. 19 Python idiom detectors (v12.7+). 54 algo detectors (12.32).",
|
|
1366
|
+
"version_evaluated": "12.32",
|
|
1367
1367
|
"repo_url": "https://github.com/Cranot/roam-code",
|
|
1368
1368
|
},
|
|
1369
1369
|
"CKB/CodeMCP": {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://schemas.modelcontextprotocol.io/server-card/v1",
|
|
3
3
|
"name": "roam-code",
|
|
4
4
|
"display_name": "roam — instant codebase intelligence",
|
|
5
|
-
"version": "12.
|
|
5
|
+
"version": "12.32",
|
|
6
6
|
"description": "Architectural sight for AI agents before they edit. Pre-indexes symbols, call graphs, dependencies, architecture layers, and git history into a local SQLite DB. 136 MCP tools, 10 resources, 5 prompts, 27 languages. 100% local, zero API keys.",
|
|
7
7
|
"vendor": {
|
|
8
8
|
"name": "Cranot",
|
|
@@ -29,8 +29,9 @@ class TestCatalog:
|
|
|
29
29
|
|
|
30
30
|
# T3 added serial-await-loop; X1-X5 added async-blocking-sleep,
|
|
31
31
|
# broad-except-swallow, spread-accumulator, defer-in-loop;
|
|
32
|
-
# X13 added chained-collection-walk
|
|
33
|
-
|
|
32
|
+
# X13 added chained-collection-walk; Z1/Z2/Z5 added useeffect-
|
|
33
|
+
# missing-deps, dangerous-eval, unremoved-event-listener → 32.
|
|
34
|
+
assert len(CATALOG) == 32, f"Expected 32 tasks, got {len(CATALOG)}"
|
|
34
35
|
|
|
35
36
|
def test_detector_registry_covers_catalog(self):
|
|
36
37
|
from roam.catalog.detectors import _MATH_DETECTORS
|
|
@@ -1166,6 +1167,82 @@ class TestBroadExceptSwallow:
|
|
|
1166
1167
|
assert hits == []
|
|
1167
1168
|
|
|
1168
1169
|
|
|
1170
|
+
class TestUseEffectMissingDeps:
|
|
1171
|
+
"""Z1 (2026-05-06) — React useEffect without deps array."""
|
|
1172
|
+
|
|
1173
|
+
def test_detects_missing_deps(self, project_factory, monkeypatch):
|
|
1174
|
+
proj = project_factory(
|
|
1175
|
+
{
|
|
1176
|
+
"Comp.tsx": (
|
|
1177
|
+
"import { useEffect } from 'react';\n"
|
|
1178
|
+
"export function Comp() {\n"
|
|
1179
|
+
" useEffect(() => { console.log('hi'); });\n"
|
|
1180
|
+
" return null;\n"
|
|
1181
|
+
"}\n"
|
|
1182
|
+
),
|
|
1183
|
+
}
|
|
1184
|
+
)
|
|
1185
|
+
monkeypatch.chdir(proj)
|
|
1186
|
+
from roam.catalog.detectors import detect_useeffect_missing_deps
|
|
1187
|
+
from roam.db.connection import open_db
|
|
1188
|
+
|
|
1189
|
+
with open_db(readonly=True, project_root=proj) as conn:
|
|
1190
|
+
hits = detect_useeffect_missing_deps(conn)
|
|
1191
|
+
assert len(hits) >= 1
|
|
1192
|
+
|
|
1193
|
+
def test_skips_when_deps_present(self, project_factory, monkeypatch):
|
|
1194
|
+
proj = project_factory(
|
|
1195
|
+
{
|
|
1196
|
+
"Comp.tsx": (
|
|
1197
|
+
"import { useEffect } from 'react';\n"
|
|
1198
|
+
"export function Comp(props: { id: string }) {\n"
|
|
1199
|
+
" useEffect(() => { fetch(props.id); }, [props.id]);\n"
|
|
1200
|
+
" return null;\n"
|
|
1201
|
+
"}\n"
|
|
1202
|
+
),
|
|
1203
|
+
}
|
|
1204
|
+
)
|
|
1205
|
+
monkeypatch.chdir(proj)
|
|
1206
|
+
from roam.catalog.detectors import detect_useeffect_missing_deps
|
|
1207
|
+
from roam.db.connection import open_db
|
|
1208
|
+
|
|
1209
|
+
with open_db(readonly=True, project_root=proj) as conn:
|
|
1210
|
+
hits = detect_useeffect_missing_deps(conn)
|
|
1211
|
+
assert hits == []
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
class TestDangerousEval:
|
|
1215
|
+
"""Z2 (2026-05-06) — eval / exec / new Function in production source."""
|
|
1216
|
+
|
|
1217
|
+
def test_detects_eval(self, project_factory, monkeypatch):
|
|
1218
|
+
proj = project_factory(
|
|
1219
|
+
{
|
|
1220
|
+
"service.py": ("def evaluate(expr):\n return eval(expr)\n"),
|
|
1221
|
+
}
|
|
1222
|
+
)
|
|
1223
|
+
monkeypatch.chdir(proj)
|
|
1224
|
+
from roam.catalog.detectors import detect_dangerous_eval
|
|
1225
|
+
from roam.db.connection import open_db
|
|
1226
|
+
|
|
1227
|
+
with open_db(readonly=True, project_root=proj) as conn:
|
|
1228
|
+
hits = detect_dangerous_eval(conn)
|
|
1229
|
+
assert len(hits) >= 1
|
|
1230
|
+
|
|
1231
|
+
def test_skips_test_paths(self, project_factory, monkeypatch):
|
|
1232
|
+
proj = project_factory(
|
|
1233
|
+
{
|
|
1234
|
+
"tests/test_eval.py": ("def test_eval():\n return eval('1+1')\n"),
|
|
1235
|
+
}
|
|
1236
|
+
)
|
|
1237
|
+
monkeypatch.chdir(proj)
|
|
1238
|
+
from roam.catalog.detectors import detect_dangerous_eval
|
|
1239
|
+
from roam.db.connection import open_db
|
|
1240
|
+
|
|
1241
|
+
with open_db(readonly=True, project_root=proj) as conn:
|
|
1242
|
+
hits = detect_dangerous_eval(conn)
|
|
1243
|
+
assert hits == []
|
|
1244
|
+
|
|
1245
|
+
|
|
1169
1246
|
class TestBatchIterationGuard:
|
|
1170
1247
|
"""DF2 (2026-05-06) — `for chunk in _chunked(ids):` is not N+1."""
|
|
1171
1248
|
|
|
@@ -308,14 +308,24 @@ class TestToolDecorator:
|
|
|
308
308
|
"roam_test_impact",
|
|
309
309
|
"roam_disambiguate",
|
|
310
310
|
"roam_why_fail",
|
|
311
|
+
# v12.27 / v12.28 — Roam Agent Review v2 layer (8)
|
|
312
|
+
"roam_pr_analyze",
|
|
313
|
+
"roam_pr_comment_render",
|
|
314
|
+
"roam_rules_validate",
|
|
315
|
+
"roam_audit_trail_export",
|
|
316
|
+
"roam_audit_trail_verify",
|
|
317
|
+
"roam_audit_trail_conformance_check",
|
|
318
|
+
"roam_dogfood",
|
|
319
|
+
"roam_metrics_push",
|
|
311
320
|
}
|
|
312
321
|
assert _CORE_TOOLS == expected
|
|
313
322
|
|
|
314
323
|
def test_core_tools_count(self):
|
|
315
324
|
from roam.mcp_server import _CORE_TOOLS
|
|
316
325
|
|
|
317
|
-
# v12.1: 27 + 6 = 33; v12.6: +2 Python-pivot = 35; v12.16: +1 catalog = 36
|
|
318
|
-
|
|
326
|
+
# v12.1: 27 + 6 = 33; v12.6: +2 Python-pivot = 35; v12.16: +1 catalog = 36;
|
|
327
|
+
# v12.19: +5 agent wrappers = 41; v12.28: +8 Agent Review v2 = 49.
|
|
328
|
+
assert len(_CORE_TOOLS) == 49
|
|
319
329
|
|
|
320
330
|
def test_required_task_tools_declared(self):
|
|
321
331
|
from roam.mcp_server import _TASK_REQUIRED_TOOLS
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|