From 566867f59b627c3b472ef1ed9bcbd8df5772d2b1 Mon Sep 17 00:00:00 2001 From: saagpatel <269905221+saagpatel@users.noreply.github.com> Date: Tue, 11 Aug 2026 11:42:12 -0700 Subject: [PATCH 1/2] refactor(packaging): adopt conventional package namespace --- .codex/verify.commands | 4 +- .env.example | 2 +- .github/workflows/README.md | 2 +- .github/workflows/audit.yml | 2 +- .github/workflows/ci.yml | 2 +- AGENTS.md | 2 +- CHANGELOG.md | 42 +- CONTRIBUTING.md | 16 +- DEPLOY.md | 2 +- Dockerfile | 5 +- Makefile | 28 +- README.md | 6 +- config/custom-analyzers/dockerfile_check.py | 6 +- config/project-registry-overrides.json | 2 +- docs/architecture.md | 110 ++--- docs/audit-serve.md | 2 +- docs/ci-coverage-initiative.md | 6 +- docs/codex-restart-packet.md | 4 +- docs/demo-proof/2026-06-07/README.md | 4 +- docs/demo-proof/2026-06-07/proof-package.json | 4 +- docs/demo-proof/public-fixture/SUMMARY.md | 2 +- .../public-fixture/VERIFICATION-NOTES.md | 4 +- .../public-fixture/proof-package.json | 2 +- docs/extending-analyzers.md | 2 +- docs/machine-wide-consolidation.md | 2 +- docs/operator-troubleshooting.md | 2 +- ...29-run-instructions-external-audit-plan.md | 60 +-- ...6-05-29-run-instructions-external-audit.md | 16 +- ...6-14-portfolio-attention-reconciliation.md | 6 +- ...-06-19-notion-projection-reconciliation.md | 4 +- docs/portfolio-decision-contract.md | 2 +- docs/release-gates.md | 62 +-- docs/workbook-template-maintenance.md | 2 +- .../manifest.json | 2 +- .../operator-control-plane-v1/manifest.json | 2 +- .../portfolio-command-center-v1/manifest.json | 2 +- .../public-site-projection-v1/manifest.json | 2 +- pyproject.toml | 34 +- scripts/_bootstrap.py | 7 +- scripts/benchmark_large_portfolio.py | 6 +- scripts/build_codex_restart_packet.py | 4 +- scripts/build_demo_artifacts.py | 16 +- scripts/check_mutation_score.py | 87 ++++ ...generate_evidence_freshness_conformance.py | 7 +- scripts/generate_excel_template.py | 2 +- ...nerate_portfolio_truth_contract_fixture.py | 9 +- scripts/operator_os_seam_linter.py | 4 +- scripts/presence-claims-audit.workflow.js | 4 +- scripts/run-instructions-audit.workflow.js | 4 +- scripts/validate_proof_package.py | 11 +- src/__main__.py | 3 - src/app/__init__.py | 1 - src/{ => github_repo_auditor}/__init__.py | 0 src/github_repo_auditor/__main__.py | 3 + .../action_sync_automation.py | 6 +- .../action_sync_outcomes.py | 6 +- .../action_sync_packets.py | 4 +- .../action_sync_readiness.py | 2 +- .../action_sync_tuning.py | 8 +- .../analyst_views.py | 2 +- .../analyzer_cache.py | 4 +- .../analyzers/__init__.py | 32 +- .../analyzers/activity.py | 6 +- .../analyzers/base.py | 4 +- .../analyzers/cicd.py | 4 +- .../analyzers/code_quality.py | 6 +- .../analyzers/community_profile.py | 6 +- .../analyzers/completeness.py | 4 +- .../analyzers/dependencies.py | 8 +- .../analyzers/description_analyzer.py | 6 +- .../analyzers/interest.py | 6 +- .../analyzers/readme.py | 4 +- .../analyzers/security.py | 6 +- .../analyzers/structure.py | 4 +- .../analyzers/testing.py | 4 +- src/{ => github_repo_auditor}/api_checkout.py | 4 +- src/{ => github_repo_auditor}/api_only.py | 12 +- src/github_repo_auditor/app/__init__.py | 1 + .../app/acknowledgments.py | 12 +- .../app/approval_center.py | 16 +- .../app/auto_apply.py | 16 +- .../app/automation_proposals.py | 16 +- .../app/campaign_workflow.py | 36 +- .../app/control_center.py | 18 +- src/{ => github_repo_auditor}/app/doctor.py | 4 +- .../app/improvement_application.py | 10 +- .../app/initiative_suggestions.py | 22 +- .../app/initiatives.py | 14 +- .../app/portfolio_analysis.py | 12 +- .../app/portfolio_truth.py | 14 +- .../app/pr_head_evidence.py | 2 +- .../app/report_only.py | 6 +- .../app/run_audit.py | 134 +++--- .../app/security_modes.py | 8 +- .../app/semantic_search.py | 6 +- .../app/serve_mode.py | 2 +- .../approval_ledger.py | 6 +- .../archive_candidates.py | 0 .../assets}/excel/analyst-template.xlsx | Bin src/{ => github_repo_auditor}/auto_apply.py | 0 .../automation_executor.py | 4 +- .../automation_proposals.py | 2 +- .../automation_workflow.py | 10 +- src/{ => github_repo_auditor}/badge_export.py | 2 +- src/{ => github_repo_auditor}/badges.py | 2 +- .../baseline_context.py | 0 src/{ => github_repo_auditor}/briefing.py | 32 +- src/{ => github_repo_auditor}/cache.py | 0 .../catalog_validator.py | 2 +- src/{ => github_repo_auditor}/cli.py | 54 +-- .../cli_mode_validation.py | 0 src/{ => github_repo_auditor}/cli_output.py | 0 src/{ => github_repo_auditor}/cloner.py | 2 +- .../codex_restart_packet.py | 4 +- src/{ => github_repo_auditor}/config.py | 0 .../context_quality.py | 4 +- .../control_center_presentation.py | 2 +- .../control_center_report_state.py | 16 +- .../control_center_snapshot.py | 12 +- .../demo_portfolio.py | 22 +- src/{ => github_repo_auditor}/dep_graph.py | 0 src/{ => github_repo_auditor}/diagnostics.py | 10 +- src/{ => github_repo_auditor}/diff.py | 2 +- .../draft_readmes.py | 26 +- .../evidence_freshness_conformance.py | 2 +- .../excel_action_items_helpers.py | 2 +- .../excel_action_sync_readiness_helpers.py | 4 +- .../excel_activity_sheet_helpers.py | 0 .../excel_all_repos_helpers.py | 4 +- .../excel_badges_helpers.py | 0 .../excel_campaign_governance_helpers.py | 0 .../excel_changes_sheet_helpers.py | 0 .../excel_compare_scenario_helpers.py | 0 .../excel_dashboard_helpers.py | 0 .../excel_detail_helpers.py | 4 +- .../excel_executive_summary_helpers.py | 10 +- src/{ => github_repo_auditor}/excel_export.py | 386 +++++++++--------- .../excel_export_registry_helpers.py | 6 +- .../excel_export_runner_helpers.py | 0 .../excel_export_truth_helpers.py | 2 +- .../excel_heatmap_sheet_helpers.py | 0 .../excel_hidden_data_content_helpers.py | 0 .../excel_hidden_data_rows.py | 6 +- .../excel_hidden_sheet_writer.py | 2 +- .../excel_hotspot_sheet_helpers.py | 0 .../excel_initiative_tracker_helpers.py | 4 +- .../excel_ledger_sheet_helpers.py | 0 .../excel_navigation_helpers.py | 12 +- .../excel_operator_closure_helpers.py | 0 .../excel_operator_follow_through_helpers.py | 0 .../excel_operator_hidden_rows.py | 0 .../excel_operator_outcomes_helpers.py | 0 .../excel_operator_queue_helpers.py | 2 +- .../excel_operator_summary_helpers.py | 0 .../excel_portfolio_misc_helpers.py | 0 .../excel_portfolio_sheet_helpers.py | 0 .../excel_print_pack_helpers.py | 2 +- .../excel_print_pack_layout_helpers.py | 0 .../excel_profile_trend_helpers.py | 0 .../excel_quick_wins_helpers.py | 0 .../excel_repo_data_helpers.py | 12 +- .../excel_repo_detail_helpers.py | 2 +- .../excel_report_helpers.py | 0 .../excel_review_queue_helpers.py | 0 .../excel_review_queue_table_helpers.py | 2 +- .../excel_run_changes_helpers.py | 2 +- .../excel_score_explainer_helpers.py | 2 +- .../excel_security_sheet_helpers.py | 0 .../excel_sheet_layout_helpers.py | 2 +- src/{ => github_repo_auditor}/excel_styles.py | 0 .../excel_template.py | 4 +- .../excel_template_info_helpers.py | 0 .../excel_timeline_helpers.py | 2 +- .../excel_workbook_helpers.py | 0 .../excel_workbook_rollups.py | 0 .../ghas_alert_details.py | 2 +- src/{ => github_repo_auditor}/ghas_alerts.py | 4 +- .../github_client.py | 6 +- .../github_client_async.py | 6 +- .../github_projects.py | 2 +- .../github_security_coverage.py | 0 .../governance_activation.py | 4 +- .../graphql_client.py | 0 src/{ => github_repo_auditor}/history.py | 0 .../http_link_header.py | 0 .../implementation_hotspots.py | 8 +- src/{ => github_repo_auditor}/initiatives.py | 4 +- .../intervention_ledger.py | 4 +- .../issue_creator.py | 2 +- src/{ => github_repo_auditor}/libyears.py | 2 +- src/{ => github_repo_auditor}/llm_cost.py | 4 +- .../maturity_tiers.py | 0 src/{ => github_repo_auditor}/models.py | 8 +- src/{ => github_repo_auditor}/narrative.py | 2 +- .../notion_client.py | 0 .../notion_dashboard.py | 2 +- .../notion_export.py | 2 +- .../notion_registry.py | 2 +- src/{ => github_repo_auditor}/notion_sync.py | 4 +- .../operator_acknowledgments.py | 0 .../operator_approval_artifacts.py | 6 +- .../operator_artifact_paths.py | 0 .../operator_control_center.py | 48 +-- .../operator_control_center_artifacts.py | 8 +- .../operator_control_center_rendering.py | 2 +- .../operator_decision_quality.py | 6 +- .../operator_effectiveness.py | 0 .../operator_follow_through.py | 0 .../operator_os_seam_linter.py | 16 +- .../operator_prefs.py | 0 .../operator_resolution_trend.py | 240 +++++------ .../operator_snapshot_packaging.py | 0 .../operator_trend_apply_chain.py | 0 .../operator_trend_class_transitions.py | 0 .../operator_trend_closure_forecast_core.py | 0 ...end_closure_forecast_freshness_controls.py | 2 +- ...closure_forecast_reacquisition_controls.py | 4 +- ...r_trend_closure_forecast_reset_controls.py | 4 +- .../operator_trend_confidence_calibration.py | 2 +- .../operator_trend_decision_memory.py | 0 .../operator_trend_exception_recovery.py | 0 .../operator_trend_pending_debt.py | 0 .../operator_trend_pending_text.py | 0 .../operator_trend_recommendation_drift.py | 0 .../operator_trend_resolution_payload.py | 0 .../operator_trend_run_context.py | 0 .../operator_trend_summary_context.py | 0 .../operator_trend_support.py | 0 .../operator_trend_topline_payload.py | 0 .../operator_trend_transition_closure.py | 0 .../operator_trend_trust_policy_history.py | 0 .../ops_writeback.py | 4 +- .../ossf_scorecard.py | 2 +- src/{ => github_repo_auditor}/pdf_export.py | 0 .../plan_campaign.py | 24 +- .../portfolio_automation.py | 2 +- .../portfolio_catalog.py | 2 +- .../portfolio_checkout_authority.py | 2 +- .../portfolio_context_contract.py | 0 .../portfolio_context_recovery.py | 6 +- .../portfolio_context_triage.py | 2 +- .../portfolio_decision_queue.py | 4 +- .../portfolio_intelligence.py | 4 +- .../portfolio_pathing.py | 0 .../portfolio_readme.py | 4 +- .../portfolio_repository_state.py | 0 .../portfolio_risk.py | 0 .../portfolio_security_gate.py | 2 +- .../portfolio_truth_contract_fixture.py | 8 +- .../portfolio_truth_coverage.py | 2 +- .../portfolio_truth_decisions.py | 2 +- .../portfolio_truth_lineage.py | 0 .../portfolio_truth_metadata.py | 4 +- .../portfolio_truth_precedence.py | 0 .../portfolio_truth_provenance.py | 0 .../portfolio_truth_publish.py | 22 +- .../portfolio_truth_reconcile.py | 30 +- .../portfolio_truth_render.py | 4 +- .../portfolio_truth_sources.py | 10 +- .../portfolio_truth_status.py | 6 +- .../portfolio_truth_trends.py | 0 .../portfolio_truth_types.py | 2 +- .../portfolio_truth_validate.py | 30 +- .../pr_head_evidence.py | 0 .../producer_preflight.py | 0 src/{ => github_repo_auditor}/progress.py | 0 .../project_registry.py | 2 +- src/{ => github_repo_auditor}/quick_wins.py | 4 +- .../readme_suggestions.py | 0 .../recurring_review.py | 8 +- .../registry_parser.py | 2 +- .../repo_improver.py | 2 +- .../report_contracts.py | 4 +- .../report_enrichment.py | 14 +- .../report_operating_paths.py | 4 +- .../report_operator_state.py | 6 +- .../report_portfolio_catalog.py | 6 +- .../report_scorecards.py | 8 +- .../report_shared_artifacts.py | 20 +- src/{ => github_repo_auditor}/report_state.py | 4 +- src/{ => github_repo_auditor}/reporter.py | 12 +- src/{ => github_repo_auditor}/review_pack.py | 6 +- .../run_instructions_audit.py | 6 +- .../scheduled_handoff.py | 4 +- src/{ => github_repo_auditor}/scorecards.py | 0 src/{ => github_repo_auditor}/scorer.py | 18 +- .../scoring_dimensions.py | 0 .../security_admission.py | 0 .../security_burndown.py | 0 .../security_intelligence.py | 4 +- .../semantic_index.py | 2 +- .../serve/__init__.py | 0 src/{ => github_repo_auditor}/serve/api.py | 14 +- src/{ => github_repo_auditor}/serve/app.py | 10 +- .../serve/hosting.py | 2 +- .../serve/redis_store.py | 0 src/{ => github_repo_auditor}/serve/routes.py | 52 +-- src/{ => github_repo_auditor}/serve/runner.py | 2 +- .../serve/static/audit.css | 0 .../serve/templates/approvals.html | 0 .../serve/templates/base.html | 0 .../serve/templates/campaign_plan.html | 0 .../serve/templates/draft_diff.html | 0 .../serve/templates/draft_sections.html | 0 .../serve/templates/index.html | 0 .../serve/templates/initiative_gap.html | 0 .../serve/templates/initiatives.html | 2 +- .../initiatives_dismissal_history.html | 0 .../templates/initiatives_dismissed.html | 0 .../templates/initiatives_suggestions.html | 0 .../serve/templates/new_run.html | 0 .../serve/templates/repo.html | 0 .../serve/templates/runs.html | 0 .../serve/waitlist.py | 0 src/{ => github_repo_auditor}/similarity.py | 0 src/{ => github_repo_auditor}/sparkline.py | 0 .../suggest_initiatives.py | 20 +- src/{ => github_repo_auditor}/terminology.py | 0 .../tier_recalibration.py | 4 +- src/{ => github_repo_auditor}/vuln_check.py | 2 +- src/{ => github_repo_auditor}/warehouse.py | 6 +- src/{ => github_repo_auditor}/watch.py | 0 src/{ => github_repo_auditor}/web_export.py | 14 +- .../weekly_command_center.py | 12 +- .../weekly_packaging.py | 4 +- .../weekly_scheduling_overlay.py | 0 .../workbook_gate.py | 6 +- tests/conftest.py | 2 +- tests/golden/enumerate_composer_contract.py | 2 +- .../enumerate_recovery_state_contract.py | 10 +- .../enumerate_resolution_trend_contract.py | 2 +- .../recovery_state_contract.golden.json | 54 +-- tests/test_action_sync_automation.py | 2 +- tests/test_action_sync_outcomes.py | 2 +- tests/test_action_sync_packets.py | 6 +- tests/test_action_sync_readiness.py | 4 +- tests/test_action_sync_tuning.py | 2 +- tests/test_activity.py | 4 +- tests/test_analyzer_cache.py | 24 +- tests/test_analyzer_staleness_and_releases.py | 6 +- tests/test_analyzers.py | 18 +- tests/test_api_checkout.py | 4 +- tests/test_api_only.py | 12 +- tests/test_app_cli_boundary.py | 14 +- tests/test_approval_ledger.py | 4 +- tests/test_arc_f_surfaces.py | 10 +- tests/test_archive.py | 2 +- tests/test_auto_apply.py | 2 +- tests/test_automation_executor.py | 4 +- tests/test_automation_proposals.py | 4 +- tests/test_automation_workflow.py | 10 +- tests/test_badge_export.py | 2 +- tests/test_badges.py | 4 +- tests/test_briefing.py | 52 +-- tests/test_cache.py | 2 +- tests/test_cache_reconcile.py | 10 +- tests/test_cache_ttl.py | 6 +- tests/test_catalog_validator.py | 2 +- tests/test_cicd.py | 2 +- tests/test_cli_automation_proposals.py | 4 +- tests/test_cli_hardening.py | 108 ++--- tests/test_cli_mode_validation.py | 2 +- tests/test_cli_output.py | 2 +- tests/test_cli_subcommands.py | 2 +- tests/test_cloner.py | 2 +- tests/test_code_quality.py | 4 +- tests/test_codex_restart_packet.py | 4 +- tests/test_community_profile.py | 4 +- tests/test_completeness.py | 2 +- tests/test_composer_golden_contract.py | 2 +- tests/test_config.py | 2 +- tests/test_context_quality.py | 4 +- tests/test_dashboard_risk_kpi.py | 2 +- tests/test_demo_portfolio.py | 20 +- tests/test_dep_graph.py | 4 +- tests/test_dependencies.py | 18 +- tests/test_dependencies_sbom.py | 6 +- tests/test_description_analyzer.py | 14 +- tests/test_diagnostics.py | 8 +- tests/test_diff.py | 2 +- tests/test_dismissal_event_cap.py | 4 +- tests/test_draft_readmes.py | 46 +-- tests/test_evidence_freshness_conformance.py | 2 +- tests/test_excel_enhanced.py | 8 +- tests/test_excel_export_registry_helpers.py | 4 +- tests/test_excel_initiative_tracker.py | 8 +- tests/test_ghas_alerts.py | 12 +- tests/test_github_client.py | 4 +- tests/test_github_client_async.py | 8 +- tests/test_github_projects.py | 2 +- tests/test_github_security_coverage.py | 14 +- tests/test_history.py | 2 +- tests/test_implementation_hotspots.py | 4 +- tests/test_initiatives.py | 6 +- tests/test_initiatives_dismissed_route.py | 2 +- tests/test_initiatives_routes.py | 2 +- tests/test_initiatives_suggestions_route.py | 40 +- tests/test_interest.py | 4 +- tests/test_intervention_ledger.py | 2 +- tests/test_issue_creator.py | 2 +- tests/test_language_trends.py | 2 +- tests/test_llm_cost.py | 12 +- tests/test_load_risk_truth.py | 2 +- tests/test_maturity_tiers.py | 4 +- tests/test_models.py | 2 +- tests/test_mutation_score_gate.py | 38 ++ tests/test_narrative.py | 6 +- tests/test_notion_export.py | 2 +- tests/test_notion_registry.py | 2 +- tests/test_notion_sync_extended.py | 30 +- tests/test_notion_sync_integrity.py | 2 +- tests/test_notion_twoway.py | 6 +- tests/test_operator_acknowledgments.py | 2 +- tests/test_operator_control_center.py | 330 +++++++-------- .../test_operator_control_center_artifacts.py | 2 +- tests/test_operator_decision_quality.py | 2 +- tests/test_operator_effectiveness.py | 2 +- tests/test_operator_os_seam_linter.py | 6 +- tests/test_operator_prefs.py | 8 +- tests/test_operator_snapshot_contract.py | 2 +- tests/test_operator_trend_apply_chain.py | 2 +- .../test_operator_trend_class_transitions.py | 2 +- ..._operator_trend_closure_forecast_events.py | 2 +- ...erator_trend_closure_forecast_freshness.py | 2 +- ...operator_trend_closure_forecast_history.py | 2 +- ...or_trend_closure_forecast_reacquisition.py | 2 +- ...losure_forecast_reacquisition_freshness.py | 2 +- ...losure_forecast_reset_reentry_freshness.py | 2 +- ..._closure_forecast_reset_reentry_rebuild.py | 2 +- ...orecast_reset_reentry_rebuild_freshness.py | 2 +- ...ecast_reset_reentry_rebuild_persistence.py | 2 +- ...entry_restore_rerererestore_persistence.py | 2 +- ...d_reentry_restore_rererestore_freshness.py | 2 +- ...reentry_restore_rererestore_persistence.py | 4 +- ...or_trend_closure_forecast_reset_refresh.py | 2 +- ...ator_trend_closure_forecast_reweighting.py | 2 +- ...t_operator_trend_confidence_calibration.py | 2 +- tests/test_operator_trend_decision_memory.py | 2 +- .../test_operator_trend_exception_recovery.py | 2 +- tests/test_operator_trend_helpers.py | 4 +- tests/test_operator_trend_pending_debt.py | 2 +- ...est_operator_trend_recommendation_drift.py | 2 +- tests/test_operator_trend_run_context.py | 2 +- tests/test_operator_trend_summary_context.py | 2 +- tests/test_operator_trend_topline_payload.py | 2 +- .../test_operator_trend_transition_closure.py | 2 +- ...est_operator_trend_trust_policy_history.py | 2 +- tests/test_ops_writeback.py | 2 +- tests/test_ossf_scorecard.py | 6 +- tests/test_packaging.py | 28 +- tests/test_pdf_export.py | 2 +- tests/test_per_action_approval.py | 32 +- tests/test_per_section_draft_readmes.py | 30 +- tests/test_phase93_approval_surfaces.py | 10 +- tests/test_plan_campaign.py | 76 ++-- tests/test_plugin_loader.py | 8 +- tests/test_portfolio_automation.py | 4 +- tests/test_portfolio_catalog.py | 4 +- tests/test_portfolio_context_contract.py | 2 +- tests/test_portfolio_context_triage.py | 2 +- tests/test_portfolio_decision_queue.py | 9 +- tests/test_portfolio_pathing.py | 2 +- tests/test_portfolio_readme.py | 2 +- tests/test_portfolio_repository_state.py | 6 +- tests/test_portfolio_risk.py | 2 +- tests/test_portfolio_security_gate.py | 4 +- tests/test_portfolio_truth.py | 184 ++++----- .../test_portfolio_truth_contract_fixture.py | 24 +- .../test_portfolio_truth_publish_integrity.py | 2 +- tests/test_portfolio_truth_sources.py | 2 +- tests/test_portfolio_truth_strict_signals.py | 14 +- tests/test_portfolio_truth_trends.py | 4 +- tests/test_pr_head_evidence.py | 2 +- tests/test_pr_head_evidence_cli.py | 4 +- tests/test_producer_preflight.py | 4 +- tests/test_progress.py | 2 +- tests/test_project_registry.py | 2 +- tests/test_quick_wins.py | 6 +- tests/test_readme_staleness_by_age.py | 12 +- tests/test_readme_suggestions.py | 2 +- tests/test_recurring_review.py | 8 +- tests/test_registry_parser.py | 4 +- tests/test_repo_improver.py | 2 +- ...report_enrichment_follow_through_labels.py | 2 +- tests/test_report_enrichment_risk.py | 2 +- tests/test_reporter.py | 6 +- tests/test_review_pack.py | 2 +- tests/test_run_instructions_audit.py | 2 +- tests/test_scheduled_handoff.py | 2 +- tests/test_scorecards.py | 2 +- tests/test_scorer.py | 166 +++++--- tests/test_scoring_profiles.py | 6 +- tests/test_scoring_weights.py | 8 +- tests/test_security.py | 4 +- tests/test_security_admission.py | 2 +- tests/test_security_burndown.py | 8 +- tests/test_security_intelligence.py | 4 +- tests/test_semantic_index.py | 18 +- tests/test_serve.py | 36 +- tests/test_serve_api.py | 46 +-- tests/test_serve_hosting.py | 4 +- tests/test_serve_waitlist.py | 4 +- tests/test_similarity.py | 2 +- tests/test_sparkline.py | 2 +- tests/test_suggest_initiatives.py | 134 +++--- tests/test_testing_analyzer.py | 2 +- tests/test_tier_gaps_export.py | 18 +- tests/test_tier_recalibration.py | 10 +- tests/test_vuln_check.py | 24 +- tests/test_warehouse.py | 18 +- tests/test_watch.py | 2 +- tests/test_web_export.py | 4 +- tests/test_weekly_command_center.py | 2 +- tests/test_weekly_packaging.py | 2 +- tests/test_weekly_scheduling_overlay.py | 2 +- tests/test_weekly_story.py | 10 +- tests/test_workbook_gate.py | 2 +- uv.lock | 2 +- web/README.md | 4 +- web/lib/types.ts | 2 +- 520 files changed, 2578 insertions(+), 2377 deletions(-) create mode 100644 scripts/check_mutation_score.py delete mode 100644 src/__main__.py delete mode 100644 src/app/__init__.py rename src/{ => github_repo_auditor}/__init__.py (100%) create mode 100644 src/github_repo_auditor/__main__.py rename src/{ => github_repo_auditor}/action_sync_automation.py (98%) rename src/{ => github_repo_auditor}/action_sync_outcomes.py (99%) rename src/{ => github_repo_auditor}/action_sync_packets.py (99%) rename src/{ => github_repo_auditor}/action_sync_readiness.py (99%) rename src/{ => github_repo_auditor}/action_sync_tuning.py (98%) rename src/{ => github_repo_auditor}/analyst_views.py (99%) rename src/{ => github_repo_auditor}/analyzer_cache.py (98%) rename src/{ => github_repo_auditor}/analyzers/__init__.py (84%) rename src/{ => github_repo_auditor}/analyzers/activity.py (97%) rename src/{ => github_repo_auditor}/analyzers/base.py (91%) rename src/{ => github_repo_auditor}/analyzers/cicd.py (96%) rename src/{ => github_repo_auditor}/analyzers/code_quality.py (98%) rename src/{ => github_repo_auditor}/analyzers/community_profile.py (95%) rename src/{ => github_repo_auditor}/analyzers/completeness.py (98%) rename src/{ => github_repo_auditor}/analyzers/dependencies.py (97%) rename src/{ => github_repo_auditor}/analyzers/description_analyzer.py (94%) rename src/{ => github_repo_auditor}/analyzers/interest.py (98%) rename src/{ => github_repo_auditor}/analyzers/readme.py (98%) rename src/{ => github_repo_auditor}/analyzers/security.py (98%) rename src/{ => github_repo_auditor}/analyzers/structure.py (97%) rename src/{ => github_repo_auditor}/analyzers/testing.py (97%) rename src/{ => github_repo_auditor}/api_checkout.py (98%) rename src/{ => github_repo_auditor}/api_only.py (95%) create mode 100644 src/github_repo_auditor/app/__init__.py rename src/{ => github_repo_auditor}/app/acknowledgments.py (87%) rename src/{ => github_repo_auditor}/app/approval_center.py (89%) rename src/{ => github_repo_auditor}/app/auto_apply.py (90%) rename src/{ => github_repo_auditor}/app/automation_proposals.py (89%) rename src/{ => github_repo_auditor}/app/campaign_workflow.py (92%) rename src/{ => github_repo_auditor}/app/control_center.py (78%) rename src/{ => github_repo_auditor}/app/doctor.py (89%) rename src/{ => github_repo_auditor}/app/improvement_application.py (96%) rename src/{ => github_repo_auditor}/app/initiative_suggestions.py (86%) rename src/{ => github_repo_auditor}/app/initiatives.py (90%) rename src/{ => github_repo_auditor}/app/portfolio_analysis.py (93%) rename src/{ => github_repo_auditor}/app/portfolio_truth.py (95%) rename src/{ => github_repo_auditor}/app/pr_head_evidence.py (97%) rename src/{ => github_repo_auditor}/app/report_only.py (74%) rename src/{ => github_repo_auditor}/app/run_audit.py (92%) rename src/{ => github_repo_auditor}/app/security_modes.py (92%) rename src/{ => github_repo_auditor}/app/semantic_search.py (85%) rename src/{ => github_repo_auditor}/app/serve_mode.py (89%) rename src/{ => github_repo_auditor}/approval_ledger.py (99%) rename src/{ => github_repo_auditor}/archive_candidates.py (100%) rename {assets => src/github_repo_auditor/assets}/excel/analyst-template.xlsx (100%) rename src/{ => github_repo_auditor}/auto_apply.py (100%) rename src/{ => github_repo_auditor}/automation_executor.py (97%) rename src/{ => github_repo_auditor}/automation_proposals.py (99%) rename src/{ => github_repo_auditor}/automation_workflow.py (96%) rename src/{ => github_repo_auditor}/badge_export.py (99%) rename src/{ => github_repo_auditor}/badges.py (99%) rename src/{ => github_repo_auditor}/baseline_context.py (100%) rename src/{ => github_repo_auditor}/briefing.py (96%) rename src/{ => github_repo_auditor}/cache.py (100%) rename src/{ => github_repo_auditor}/catalog_validator.py (96%) rename src/{ => github_repo_auditor}/cli.py (97%) rename src/{ => github_repo_auditor}/cli_mode_validation.py (100%) rename src/{ => github_repo_auditor}/cli_output.py (100%) rename src/{ => github_repo_auditor}/cloner.py (99%) rename src/{ => github_repo_auditor}/codex_restart_packet.py (98%) rename src/{ => github_repo_auditor}/config.py (100%) rename src/{ => github_repo_auditor}/context_quality.py (94%) rename src/{ => github_repo_auditor}/control_center_presentation.py (99%) rename src/{ => github_repo_auditor}/control_center_report_state.py (73%) rename src/{ => github_repo_auditor}/control_center_snapshot.py (87%) rename src/{ => github_repo_auditor}/demo_portfolio.py (98%) rename src/{ => github_repo_auditor}/dep_graph.py (100%) rename src/{ => github_repo_auditor}/diagnostics.py (98%) rename src/{ => github_repo_auditor}/diff.py (99%) rename src/{ => github_repo_auditor}/draft_readmes.py (96%) rename src/{ => github_repo_auditor}/evidence_freshness_conformance.py (99%) rename src/{ => github_repo_auditor}/excel_action_items_helpers.py (98%) rename src/{ => github_repo_auditor}/excel_action_sync_readiness_helpers.py (96%) rename src/{ => github_repo_auditor}/excel_activity_sheet_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_all_repos_helpers.py (99%) rename src/{ => github_repo_auditor}/excel_badges_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_campaign_governance_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_changes_sheet_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_compare_scenario_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_dashboard_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_detail_helpers.py (95%) rename src/{ => github_repo_auditor}/excel_executive_summary_helpers.py (99%) rename src/{ => github_repo_auditor}/excel_export.py (83%) rename src/{ => github_repo_auditor}/excel_export_registry_helpers.py (96%) rename src/{ => github_repo_auditor}/excel_export_runner_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_export_truth_helpers.py (96%) rename src/{ => github_repo_auditor}/excel_heatmap_sheet_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_hidden_data_content_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_hidden_data_rows.py (98%) rename src/{ => github_repo_auditor}/excel_hidden_sheet_writer.py (99%) rename src/{ => github_repo_auditor}/excel_hotspot_sheet_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_initiative_tracker_helpers.py (98%) rename src/{ => github_repo_auditor}/excel_ledger_sheet_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_navigation_helpers.py (95%) rename src/{ => github_repo_auditor}/excel_operator_closure_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_operator_follow_through_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_operator_hidden_rows.py (100%) rename src/{ => github_repo_auditor}/excel_operator_outcomes_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_operator_queue_helpers.py (97%) rename src/{ => github_repo_auditor}/excel_operator_summary_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_portfolio_misc_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_portfolio_sheet_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_print_pack_helpers.py (98%) rename src/{ => github_repo_auditor}/excel_print_pack_layout_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_profile_trend_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_quick_wins_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_repo_data_helpers.py (97%) rename src/{ => github_repo_auditor}/excel_repo_detail_helpers.py (99%) rename src/{ => github_repo_auditor}/excel_report_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_review_queue_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_review_queue_table_helpers.py (99%) rename src/{ => github_repo_auditor}/excel_run_changes_helpers.py (99%) rename src/{ => github_repo_auditor}/excel_score_explainer_helpers.py (98%) rename src/{ => github_repo_auditor}/excel_security_sheet_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_sheet_layout_helpers.py (98%) rename src/{ => github_repo_auditor}/excel_styles.py (100%) rename src/{ => github_repo_auditor}/excel_template.py (98%) rename src/{ => github_repo_auditor}/excel_template_info_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_timeline_helpers.py (96%) rename src/{ => github_repo_auditor}/excel_workbook_helpers.py (100%) rename src/{ => github_repo_auditor}/excel_workbook_rollups.py (100%) rename src/{ => github_repo_auditor}/ghas_alert_details.py (98%) rename src/{ => github_repo_auditor}/ghas_alerts.py (99%) rename src/{ => github_repo_auditor}/github_client.py (99%) rename src/{ => github_repo_auditor}/github_client_async.py (98%) rename src/{ => github_repo_auditor}/github_projects.py (99%) rename src/{ => github_repo_auditor}/github_security_coverage.py (100%) rename src/{ => github_repo_auditor}/governance_activation.py (99%) rename src/{ => github_repo_auditor}/graphql_client.py (100%) rename src/{ => github_repo_auditor}/history.py (100%) rename src/{ => github_repo_auditor}/http_link_header.py (100%) rename src/{ => github_repo_auditor}/implementation_hotspots.py (98%) rename src/{ => github_repo_auditor}/initiatives.py (98%) rename src/{ => github_repo_auditor}/intervention_ledger.py (99%) rename src/{ => github_repo_auditor}/issue_creator.py (97%) rename src/{ => github_repo_auditor}/libyears.py (99%) rename src/{ => github_repo_auditor}/llm_cost.py (98%) rename src/{ => github_repo_auditor}/maturity_tiers.py (100%) rename src/{ => github_repo_auditor}/models.py (98%) rename src/{ => github_repo_auditor}/narrative.py (99%) rename src/{ => github_repo_auditor}/notion_client.py (100%) rename src/{ => github_repo_auditor}/notion_dashboard.py (98%) rename src/{ => github_repo_auditor}/notion_export.py (99%) rename src/{ => github_repo_auditor}/notion_registry.py (99%) rename src/{ => github_repo_auditor}/notion_sync.py (99%) rename src/{ => github_repo_auditor}/operator_acknowledgments.py (100%) rename src/{ => github_repo_auditor}/operator_approval_artifacts.py (96%) rename src/{ => github_repo_auditor}/operator_artifact_paths.py (100%) rename src/{ => github_repo_auditor}/operator_control_center.py (95%) rename src/{ => github_repo_auditor}/operator_control_center_artifacts.py (93%) rename src/{ => github_repo_auditor}/operator_control_center_rendering.py (99%) rename src/{ => github_repo_auditor}/operator_decision_quality.py (98%) rename src/{ => github_repo_auditor}/operator_effectiveness.py (100%) rename src/{ => github_repo_auditor}/operator_follow_through.py (100%) rename src/{ => github_repo_auditor}/operator_os_seam_linter.py (98%) rename src/{ => github_repo_auditor}/operator_prefs.py (100%) rename src/{ => github_repo_auditor}/operator_resolution_trend.py (98%) rename src/{ => github_repo_auditor}/operator_snapshot_packaging.py (100%) rename src/{ => github_repo_auditor}/operator_trend_apply_chain.py (100%) rename src/{ => github_repo_auditor}/operator_trend_class_transitions.py (100%) rename src/{ => github_repo_auditor}/operator_trend_closure_forecast_core.py (100%) rename src/{ => github_repo_auditor}/operator_trend_closure_forecast_freshness_controls.py (99%) rename src/{ => github_repo_auditor}/operator_trend_closure_forecast_reacquisition_controls.py (99%) rename src/{ => github_repo_auditor}/operator_trend_closure_forecast_reset_controls.py (99%) rename src/{ => github_repo_auditor}/operator_trend_confidence_calibration.py (99%) rename src/{ => github_repo_auditor}/operator_trend_decision_memory.py (100%) rename src/{ => github_repo_auditor}/operator_trend_exception_recovery.py (100%) rename src/{ => github_repo_auditor}/operator_trend_pending_debt.py (100%) rename src/{ => github_repo_auditor}/operator_trend_pending_text.py (100%) rename src/{ => github_repo_auditor}/operator_trend_recommendation_drift.py (100%) rename src/{ => github_repo_auditor}/operator_trend_resolution_payload.py (100%) rename src/{ => github_repo_auditor}/operator_trend_run_context.py (100%) rename src/{ => github_repo_auditor}/operator_trend_summary_context.py (100%) rename src/{ => github_repo_auditor}/operator_trend_support.py (100%) rename src/{ => github_repo_auditor}/operator_trend_topline_payload.py (100%) rename src/{ => github_repo_auditor}/operator_trend_transition_closure.py (100%) rename src/{ => github_repo_auditor}/operator_trend_trust_policy_history.py (100%) rename src/{ => github_repo_auditor}/ops_writeback.py (99%) rename src/{ => github_repo_auditor}/ossf_scorecard.py (99%) rename src/{ => github_repo_auditor}/pdf_export.py (100%) rename src/{ => github_repo_auditor}/plan_campaign.py (96%) rename src/{ => github_repo_auditor}/portfolio_automation.py (98%) rename src/{ => github_repo_auditor}/portfolio_catalog.py (99%) rename src/{ => github_repo_auditor}/portfolio_checkout_authority.py (99%) rename src/{ => github_repo_auditor}/portfolio_context_contract.py (100%) rename src/{ => github_repo_auditor}/portfolio_context_recovery.py (99%) rename src/{ => github_repo_auditor}/portfolio_context_triage.py (98%) rename src/{ => github_repo_auditor}/portfolio_decision_queue.py (99%) rename src/{ => github_repo_auditor}/portfolio_intelligence.py (99%) rename src/{ => github_repo_auditor}/portfolio_pathing.py (100%) rename src/{ => github_repo_auditor}/portfolio_readme.py (96%) rename src/{ => github_repo_auditor}/portfolio_repository_state.py (100%) rename src/{ => github_repo_auditor}/portfolio_risk.py (100%) rename src/{ => github_repo_auditor}/portfolio_security_gate.py (99%) rename src/{ => github_repo_auditor}/portfolio_truth_contract_fixture.py (96%) rename src/{ => github_repo_auditor}/portfolio_truth_coverage.py (98%) rename src/{ => github_repo_auditor}/portfolio_truth_decisions.py (98%) rename src/{ => github_repo_auditor}/portfolio_truth_lineage.py (100%) rename src/{ => github_repo_auditor}/portfolio_truth_metadata.py (98%) rename src/{ => github_repo_auditor}/portfolio_truth_precedence.py (100%) rename src/{ => github_repo_auditor}/portfolio_truth_provenance.py (100%) rename src/{ => github_repo_auditor}/portfolio_truth_publish.py (97%) rename src/{ => github_repo_auditor}/portfolio_truth_reconcile.py (98%) rename src/{ => github_repo_auditor}/portfolio_truth_render.py (99%) rename src/{ => github_repo_auditor}/portfolio_truth_sources.py (99%) rename src/{ => github_repo_auditor}/portfolio_truth_status.py (97%) rename src/{ => github_repo_auditor}/portfolio_truth_trends.py (100%) rename src/{ => github_repo_auditor}/portfolio_truth_types.py (99%) rename src/{ => github_repo_auditor}/portfolio_truth_validate.py (98%) rename src/{ => github_repo_auditor}/pr_head_evidence.py (100%) rename src/{ => github_repo_auditor}/producer_preflight.py (100%) rename src/{ => github_repo_auditor}/progress.py (100%) rename src/{ => github_repo_auditor}/project_registry.py (99%) rename src/{ => github_repo_auditor}/quick_wins.py (97%) rename src/{ => github_repo_auditor}/readme_suggestions.py (100%) rename src/{ => github_repo_auditor}/recurring_review.py (98%) rename src/{ => github_repo_auditor}/registry_parser.py (99%) rename src/{ => github_repo_auditor}/repo_improver.py (99%) rename src/{ => github_repo_auditor}/report_contracts.py (89%) rename src/{ => github_repo_auditor}/report_enrichment.py (99%) rename src/{ => github_repo_auditor}/report_operating_paths.py (95%) rename src/{ => github_repo_auditor}/report_operator_state.py (93%) rename src/{ => github_repo_auditor}/report_portfolio_catalog.py (94%) rename src/{ => github_repo_auditor}/report_scorecards.py (92%) rename src/{ => github_repo_auditor}/report_shared_artifacts.py (78%) rename src/{ => github_repo_auditor}/report_state.py (98%) rename src/{ => github_repo_auditor}/reporter.py (99%) rename src/{ => github_repo_auditor}/review_pack.py (99%) rename src/{ => github_repo_auditor}/run_instructions_audit.py (96%) rename src/{ => github_repo_auditor}/scheduled_handoff.py (99%) rename src/{ => github_repo_auditor}/scorecards.py (100%) rename src/{ => github_repo_auditor}/scorer.py (93%) rename src/{ => github_repo_auditor}/scoring_dimensions.py (100%) rename src/{ => github_repo_auditor}/security_admission.py (100%) rename src/{ => github_repo_auditor}/security_burndown.py (100%) rename src/{ => github_repo_auditor}/security_intelligence.py (99%) rename src/{ => github_repo_auditor}/semantic_index.py (99%) rename src/{ => github_repo_auditor}/serve/__init__.py (100%) rename src/{ => github_repo_auditor}/serve/api.py (94%) rename src/{ => github_repo_auditor}/serve/app.py (87%) rename src/{ => github_repo_auditor}/serve/hosting.py (98%) rename src/{ => github_repo_auditor}/serve/redis_store.py (100%) rename src/{ => github_repo_auditor}/serve/routes.py (94%) rename src/{ => github_repo_auditor}/serve/runner.py (98%) rename src/{ => github_repo_auditor}/serve/static/audit.css (100%) rename src/{ => github_repo_auditor}/serve/templates/approvals.html (100%) rename src/{ => github_repo_auditor}/serve/templates/base.html (100%) rename src/{ => github_repo_auditor}/serve/templates/campaign_plan.html (100%) rename src/{ => github_repo_auditor}/serve/templates/draft_diff.html (100%) rename src/{ => github_repo_auditor}/serve/templates/draft_sections.html (100%) rename src/{ => github_repo_auditor}/serve/templates/index.html (100%) rename src/{ => github_repo_auditor}/serve/templates/initiative_gap.html (100%) rename src/{ => github_repo_auditor}/serve/templates/initiatives.html (95%) rename src/{ => github_repo_auditor}/serve/templates/initiatives_dismissal_history.html (100%) rename src/{ => github_repo_auditor}/serve/templates/initiatives_dismissed.html (100%) rename src/{ => github_repo_auditor}/serve/templates/initiatives_suggestions.html (100%) rename src/{ => github_repo_auditor}/serve/templates/new_run.html (100%) rename src/{ => github_repo_auditor}/serve/templates/repo.html (100%) rename src/{ => github_repo_auditor}/serve/templates/runs.html (100%) rename src/{ => github_repo_auditor}/serve/waitlist.py (100%) rename src/{ => github_repo_auditor}/similarity.py (100%) rename src/{ => github_repo_auditor}/sparkline.py (100%) rename src/{ => github_repo_auditor}/suggest_initiatives.py (98%) rename src/{ => github_repo_auditor}/terminology.py (100%) rename src/{ => github_repo_auditor}/tier_recalibration.py (90%) rename src/{ => github_repo_auditor}/vuln_check.py (98%) rename src/{ => github_repo_auditor}/warehouse.py (99%) rename src/{ => github_repo_auditor}/watch.py (100%) rename src/{ => github_repo_auditor}/web_export.py (99%) rename src/{ => github_repo_auditor}/weekly_command_center.py (98%) rename src/{ => github_repo_auditor}/weekly_packaging.py (99%) rename src/{ => github_repo_auditor}/weekly_scheduling_overlay.py (100%) rename src/{ => github_repo_auditor}/workbook_gate.py (99%) create mode 100644 tests/test_mutation_score_gate.py diff --git a/.codex/verify.commands b/.codex/verify.commands index b1b3e907..dab1a24d 100644 --- a/.codex/verify.commands +++ b/.codex/verify.commands @@ -1,4 +1,4 @@ # codex-os-managed -uv run python -m src.cli report saagpatel --portfolio-truth +PYTHONPATH=src uv run python -m github_repo_auditor.cli report saagpatel --portfolio-truth uv run --extra dev ruff check . -uv run --extra dev --extra serve --extra semantic --extra config pytest -q +PYTHONPATH=src uv run --extra dev --extra serve --extra semantic --extra config pytest -q diff --git a/.env.example b/.env.example index 43ff71c7..69a01167 100644 --- a/.env.example +++ b/.env.example @@ -16,5 +16,5 @@ NOTION_TOKEN= # AUDIT_OUTPUT_DIR=output # Optional: keep this around if you frequently use the flagship workbook path. -# The committed default remains assets/excel/analyst-template.xlsx. +# The committed default remains src/github_repo_auditor/assets/excel/analyst-template.xlsx. # AUDIT_EXCEL_MODE=template diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 8c5ec415..8eb5b7b8 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -59,7 +59,7 @@ Steps: 2. Restore cached audit history and incremental fingerprints. 3. Run the audit in `standard` workbook mode, using incremental mode when a trustworthy cached baseline already exists. 4. Run `audit --control-center` to generate the read-only operator triage artifact. -5. Inspect the canonical scheduled handoff issue state, then run `python3 -m src.scheduled_handoff --output-dir output ...` to build the scheduled handoff JSON + Markdown summary with the right lifecycle action. +5. Inspect the canonical scheduled handoff issue state, then run `python3 -m github_repo_auditor.scheduled_handoff --output-dir output ...` to build the scheduled handoff JSON + Markdown summary with the right lifecycle action. 6. Upload `output/` as the primary artifact output. 7. Open, update, close, or reopen one canonical `scheduled-audit-handoff` issue depending on whether the latest handoff is noisy or quiet. diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index c532d47c..e5cd6dd7 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -95,7 +95,7 @@ jobs: - name: Build scheduled handoff artifact run: | - python3 -m src.scheduled_handoff \ + python3 -m github_repo_auditor.scheduled_handoff \ --output-dir output \ --issue-state "${{ steps.issue-state.outputs.issue_state }}" \ --issue-number "${{ steps.issue-state.outputs.issue_number }}" \ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b647c2f2..cd6967b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,4 +32,4 @@ jobs: - name: Lint with ruff run: ruff check src/ tests/ - name: Type-check operator trend seams - run: python -m mypy src/operator_resolution_trend.py src/operator_trend_support.py src/operator_trend_pending_text.py src/operator_trend_pending_debt.py src/operator_trend_transition_closure.py src/operator_trend_decision_memory.py src/operator_trend_recommendation_drift.py src/operator_trend_confidence_calibration.py src/operator_trend_trust_policy_history.py src/operator_trend_exception_recovery.py src/operator_trend_class_transitions.py src/operator_trend_run_context.py src/operator_trend_summary_context.py src/operator_trend_apply_chain.py src/operator_trend_topline_payload.py + run: python -m mypy src/github_repo_auditor/operator_resolution_trend.py src/github_repo_auditor/operator_trend_support.py src/github_repo_auditor/operator_trend_pending_text.py src/github_repo_auditor/operator_trend_pending_debt.py src/github_repo_auditor/operator_trend_transition_closure.py src/github_repo_auditor/operator_trend_decision_memory.py src/github_repo_auditor/operator_trend_recommendation_drift.py src/github_repo_auditor/operator_trend_confidence_calibration.py src/github_repo_auditor/operator_trend_trust_policy_history.py src/github_repo_auditor/operator_trend_exception_recovery.py src/github_repo_auditor/operator_trend_class_transitions.py src/github_repo_auditor/operator_trend_run_context.py src/github_repo_auditor/operator_trend_summary_context.py src/github_repo_auditor/operator_trend_apply_chain.py src/github_repo_auditor/operator_trend_topline_payload.py diff --git a/AGENTS.md b/AGENTS.md index bd2b3c42..8cb14417 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,7 +21,7 @@ The current machine-readable truth surface is `output/portfolio-truth-latest.jso Refresh and verify the local portfolio truth snapshot: ```sh -uv run python -m src.cli report saagpatel --portfolio-truth +uv run python -m github_repo_auditor.cli report saagpatel --portfolio-truth jq '{generated_at,total:(.projects|length),counts:.source_summary.attention_state_counts}' output/portfolio-truth-latest.json uv run operator-os-seam-linter --truth output/portfolio-truth-latest.json --json ``` diff --git a/CHANGELOG.md b/CHANGELOG.md index 75ac5480..81a91dd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,7 +93,7 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - GitHub Models as alternate narrative provider (`--narrative-provider {anthropic,github-models}`, `--narrative-model`) (Arc F S1.2). - Dependabot/CodeQL/Secret-scanning alerts surfaced in the risk overlay (`--ghas-alerts`) (Arc F S1.3). - README staleness + release-shipped signals (`has_any_release`, `release_count`, `latest_release_age_days`) (Arc F S1.4). -- mutmut pre-release mutation-testing gate on `src/auto_apply.py` + `src/scorer.py` (~93% combined kill rate). Documented in `docs/release-gates.md` (Arc F S1.5). +- mutmut pre-release mutation-testing gate on `src/github_repo_auditor/auto_apply.py` + `src/github_repo_auditor/scorer.py` (~93% combined kill rate). Documented in `docs/release-gates.md` (Arc F S1.5). - Async fetch layer (`--fetch-mode {sync,async}`, `--fetch-workers N`) via httpx + asyncio.Semaphore. 9.7x speedup on the mock benchmark (Arc F S2.1). - Per-(repo, commit-sha, analyzer) cache backed by SQLite with `--no-analyzer-cache` and `--reconcile-cache` controls (Arc F S2.2). - SBOM fetch via GitHub API (`--sbom-source github`) + OSSF Scorecard (`--ossf-scorecard`) (Arc F S2.3). @@ -148,23 +148,23 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [0.14.0] - 2026-03-29 ### Added -- Security surface analyzer (`src/analyzers/security.py`): detects hardcoded secrets, exposed env files, overly-permissive configs -- README improvement suggestions engine (`src/readme_suggestions.py`): per-repo actionable diff +- Security surface analyzer (`src/github_repo_auditor/analyzers/security.py`): detects hardcoded secrets, exposed env files, overly-permissive configs +- README improvement suggestions engine (`src/github_repo_auditor/readme_suggestions.py`): per-repo actionable diff - `--readme-suggest` flag to emit inline suggestions in Markdown report ## [0.13.0] - 2026-03-29 ### Added - Notion external signal integration: pulls star counts, watchers, and topic metadata from Notion -- Portfolio README generator (`src/portfolio_readme.py`): auto-generates a GitHub profile README from audit data +- Portfolio README generator (`src/github_repo_auditor/portfolio_readme.py`): auto-generates a GitHub profile README from audit data ## [0.12.0] - 2026-03-28 ### Added - Rich terminal output with color-coded tier badges, progress bars, and summary panels -- Unicode sparklines for commit activity trends in CLI output (`src/sparkline.py`) +- Unicode sparklines for commit activity trends in CLI output (`src/github_repo_auditor/sparkline.py`) ## [0.11.0] - 2026-03-28 ### Added -- Shields.io badge generation per repo (`src/badge_export.py`) +- Shields.io badge generation per repo (`src/github_repo_auditor/badge_export.py`) - Optional Gist upload for badge URLs via `--badges-gist` flag ## [0.10.0] - 2026-03-28 @@ -186,13 +186,13 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [0.7.0] - 2026-03-25 ### Added -- Flagship Excel dashboard with 10 sheets and a full design system (`src/excel_export.py`, `src/excel_styles.py`) +- Flagship Excel dashboard with 10 sheets and a full design system (`src/github_repo_auditor/excel_export.py`, `src/github_repo_auditor/excel_styles.py`) - Sheets: Summary, All Repos, Tier Breakdown, Top/Bottom 10, Language Distribution, Activity Heatmap, Dimension Radar ## [0.6.0] - 2026-03-25 ### Added -- GraphQL client (`src/graphql_client.py`) for bulk repo queries, reducing API call count -- Library-years (libyears) staleness metric via `src/libyears.py` +- GraphQL client (`src/github_repo_auditor/graphql_client.py`) for bulk repo queries, reducing API call count +- Library-years (libyears) staleness metric via `src/github_repo_auditor/libyears.py` - GitHub Releases detection in activity analyzer - Radon cyclomatic complexity scoring in code quality analyzer - Technology stack summary in report @@ -200,9 +200,9 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [0.5.0] - 2026-03-25 ### Added -- Letter grades A–F for individual repos and portfolio health (`src/scorer.py`) -- Badge system: earned badges per repo based on dimension scores (`src/badges.py`) -- Quick wins: lowest-effort improvements highlighted per repo (`src/quick_wins.py`) +- Letter grades A–F for individual repos and portfolio health (`src/github_repo_auditor/scorer.py`) +- Badge system: earned badges per repo based on dimension scores (`src/github_repo_auditor/badges.py`) +- Quick wins: lowest-effort improvements highlighted per repo (`src/github_repo_auditor/quick_wins.py`) - Commit pattern analysis: message quality, burst detection, solo vs. team commits - Bus factor estimation from contributor stats @@ -210,13 +210,13 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Added - Dual-axis scoring: independent completeness and interest scores per repo - Auto-sync: background re-audit of stale repos on a configurable schedule -- Audit history persistence across runs (`src/history.py`) +- Audit history persistence across runs (`src/github_repo_auditor/history.py`) - Test suite expanded to 82 tests ## [0.3.1] - 2026-03-24 ### Added - Early Excel dashboard with 6 sheets and 6 charts -- Historical diff view: score changes between two audit runs (`src/diff.py`) +- Historical diff view: score changes between two audit runs (`src/github_repo_auditor/diff.py`) - Swift/iOS-specific analyzer tuning - CI pipeline for the auditor itself (GitHub Actions) @@ -224,7 +224,7 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Added - Registry reconciliation: cross-reference GitHub repos against a local `project-registry.md` - `--registry` flag to specify the registry path -- GitHub API response cache (`src/cache.py`) with 1-hour TTL stored in `output/.cache/` +- GitHub API response cache (`src/github_repo_auditor/cache.py`) with 1-hour TTL stored in `output/.cache/` - Summary statistics: most active, most neglected, highest/lowest scored, language distribution ## [0.2.0] - 2026-03-24 @@ -238,11 +238,11 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [0.1.0] - 2026-03-24 ### Added -- Initial release: GitHub API client with pagination and rate-limit handling (`src/github_client.py`) -- Shallow clone pipeline via subprocess (`src/cloner.py`) -- `argparse` CLI entry point (`src/cli.py`) -- `RepoMetadata`, `AnalyzerResult`, `RepoAudit`, `AuditReport` dataclasses (`src/models.py`) +- Initial release: GitHub API client with pagination and rate-limit handling (`src/github_repo_auditor/github_client.py`) +- Shallow clone pipeline via subprocess (`src/github_repo_auditor/cloner.py`) +- `argparse` CLI entry point (`src/github_repo_auditor/cli.py`) +- `RepoMetadata`, `AnalyzerResult`, `RepoAudit`, `AuditReport` dataclasses (`src/github_repo_auditor/models.py`) - 9 completeness analyzers: `readme`, `structure`, `code_quality`, `testing`, `cicd`, `dependencies`, `activity`, `documentation`, `build_readiness` - `InterestAnalyzer` for tech novelty and project ambition scoring -- Weighted composite scorer with completeness tier classification (`src/scorer.py`) -- JSON and Markdown report output (`src/reporter.py`) +- Weighted composite scorer with completeness tier classification (`src/github_repo_auditor/scorer.py`) +- JSON and Markdown report output (`src/github_repo_auditor/reporter.py`) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6cbe2380..636aa5a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -86,11 +86,11 @@ These conventions come from the project's `CLAUDE.md` and must be followed in al ## Adding a New Analyzer -Analyzers live in `src/analyzers/`. Each one scores a single dimension (0.0–1.0) and returns an `AnalyzerResult`. +Analyzers live in `src/github_repo_auditor/analyzers/`. Each one scores a single dimension (0.0–1.0) and returns an `AnalyzerResult`. ### Step 1 — Create the module -Create `src/analyzers/.py` and implement a class that extends `BaseAnalyzer`: +Create `src/github_repo_auditor/analyzers/.py` and implement a class that extends `BaseAnalyzer`: ```python from __future__ import annotations @@ -98,11 +98,11 @@ from __future__ import annotations from pathlib import Path from typing import TYPE_CHECKING -from src.analyzers.base import BaseAnalyzer -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.analyzers.base import BaseAnalyzer +from github_repo_auditor.models import AnalyzerResult, RepoMetadata if TYPE_CHECKING: - from src.github_client import GitHubClient + from github_repo_auditor.github_client import GitHubClient class YourDimensionAnalyzer(BaseAnalyzer): @@ -128,13 +128,13 @@ class YourDimensionAnalyzer(BaseAnalyzer): ### Step 2 — Register the analyzer -Open `src/analyzers/__init__.py` and: +Open `src/github_repo_auditor/analyzers/__init__.py` and: 1. Import your new class at the top. 2. Append an instance to `ALL_ANALYZERS`. ```python -from src.analyzers.your_dimension import YourDimensionAnalyzer +from github_repo_auditor.analyzers.your_dimension import YourDimensionAnalyzer ALL_ANALYZERS = [ ... @@ -144,7 +144,7 @@ ALL_ANALYZERS = [ ### Step 3 — Add a weight in the scorer -Open `src/scorer.py` and add your dimension name to the `WEIGHTS` dict. Weights must sum to `1.0` after adding the new entry, so adjust existing weights proportionally. +Open `src/github_repo_auditor/scorer.py` and add your dimension name to the `WEIGHTS` dict. Weights must sum to `1.0` after adding the new entry, so adjust existing weights proportionally. ### Step 4 — Write tests diff --git a/DEPLOY.md b/DEPLOY.md index 79b86461..09a00432 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -94,5 +94,5 @@ image. Any Redis server version works (the throttle uses plain `EXPIRE`). `SqliteWaitlistStore` needs a sibling implementation behind the existing `WaitlistStore` protocol. - **Local parity:** run the API with - `uv run --extra serve python -m uvicorn --factory src.serve.app:create_app --port 8080` + `uv run --extra serve python -m uvicorn --factory github_repo_auditor.serve.app:create_app --port 8080` and the frontend with `pnpm dev` in `web/`. diff --git a/Dockerfile b/Dockerfile index cddefd9d..79fb6d5b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,9 +8,10 @@ COPY --from=ghcr.io/astral-sh/uv:0.5 /uv /bin/uv WORKDIR /app ENV UV_COMPILE_BYTECODE=1 \ UV_LINK_MODE=copy \ + PYTHONPATH=/app/src \ PYTHONUNBUFFERED=1 -# Install dependencies only (the app runs from the source tree via `src.*` +# Install dependencies only (the app runs from the source tree via `github_repo_auditor.*` # imports, so the project itself isn't packaged). Cached unless deps change. COPY pyproject.toml uv.lock ./ RUN uv sync --frozen --no-install-project --no-dev --extra serve --extra hosting @@ -22,5 +23,5 @@ EXPOSE 8080 # known proxy can opt in with GHRA_TRUST_FORWARDED_FOR and a platform-specific # Uvicorn forwarded-allow-ips override. CMD ["uv", "run", "--no-sync", "python", "-m", "uvicorn", \ - "--factory", "src.serve.app:create_app", \ + "--factory", "github_repo_auditor.serve.app:create_app", \ "--host", "0.0.0.0", "--port", "8080"] diff --git a/Makefile b/Makefile index 2d80bb93..19fd27cb 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ .PHONY: install install-dev doctor audit control-center demo benchmark workbook-gate workbook-signoff test lint format type-check run clean release-gate build shiv dist-check release publish-pypi PYTHON := python3 -CLI := uv run python -m src.cli +SOURCE_ENV := PYTHONPATH=src +CLI := $(SOURCE_ENV) uv run python -m github_repo_auditor.cli USERNAME ?= saagpatel ARGS ?= @@ -27,13 +28,13 @@ benchmark: $(PYTHON) scripts/benchmark_large_portfolio.py workbook-gate: - $(PYTHON) -m src.workbook_gate $(ARGS) + $(SOURCE_ENV) $(PYTHON) -m github_repo_auditor.workbook_gate $(ARGS) workbook-signoff: - $(PYTHON) -m src.workbook_gate --record-signoff $(ARGS) + $(SOURCE_ENV) $(PYTHON) -m github_repo_auditor.workbook_gate --record-signoff $(ARGS) test: - $(PYTHON) -m pytest tests/ -v + $(SOURCE_ENV) $(PYTHON) -m pytest tests/ -v lint: ruff check src/ tests/ @@ -49,24 +50,11 @@ run: release-gate: @echo "=== Running release gate: mutation testing ===" - @echo "Requires: python3.13, mutmut 2.x installed in python3.13 environment" - rm -rf .mutmut-cache mutants/ - python3.13 -m mutmut run + @echo "Requires: Python 3.13 and the locked dev environment" + $(SOURCE_ENV) uv run --extra dev --python 3.13 mutmut run @echo "" @echo "=== Mutation results ===" - python3.13 -c "\ -import sqlite3; \ -conn = sqlite3.connect('.mutmut-cache'); \ -rows = conn.execute('SELECT status, count(*) FROM Mutant GROUP BY status').fetchall(); \ -total = sum(r[1] for r in rows); \ -killed = next((r[1] for r in rows if r[0] == 'ok_killed'), 0); \ -survived = next((r[1] for r in rows if r[0] == 'bad_survived'), 0); \ -timeout = next((r[1] for r in rows if r[0] == 'bad_timeout'), 0); \ -denom = killed + survived; \ -rate = killed / denom if denom > 0 else 0.0; \ -print(f'Total: {total} | Killed: {killed} | Survived: {survived} | Timeout: {timeout}'); \ -print(f'Kill rate: {rate:.1%}'); \ -exit(0 if rate >= 0.85 else 1)" + uv run --no-sync python scripts/check_mutation_score.py --minimum 0.85 clean: rm -rf .pytest_cache __pycache__ dist build *.egg-info src/*.egg-info diff --git a/README.md b/README.md index e51747a3..9e92eeb2 100644 --- a/README.md +++ b/README.md @@ -378,7 +378,7 @@ The portfolio truth layer now has its own dedicated generation path. `--portfoli After regenerating portfolio truth, verify the canonical snapshot instead of copying numbers into handoff or demo docs: ```bash -uv run python -m src.cli report saagpatel --portfolio-truth +PYTHONPATH=src uv run python -m github_repo_auditor.cli report saagpatel --portfolio-truth jq '{generated_at,total:(.projects|length),counts:.source_summary.attention_state_counts}' output/portfolio-truth-latest.json uv run operator-os-seam-linter --truth output/portfolio-truth-latest.json --json ``` @@ -410,7 +410,7 @@ Watch mode now uses that same baseline contract in live execution. Each cycle re The workbook now supports two modes: - `--excel-mode standard` — stable operational workbook path, the CLI default, and the recommended mode for automation and Mac Excel compatibility -- `--excel-mode template` — template-backed workbook path using `assets/excel/analyst-template.xlsx` for controlled template work +- `--excel-mode template` — template-backed workbook path using `src/github_repo_auditor/assets/excel/analyst-template.xlsx` for controlled template work Both modes read from the same report + warehouse facts. Python owns the hidden `Data_*` sheets, stable table names, and workbook facts. The template-backed workbook still owns the template shell, named-range bindings, native sparkline placement, and print layout, but the standard workbook path is now the safest default for automated generation and Excel compatibility. @@ -488,7 +488,7 @@ Common fixes: - Missing GitHub token: set `GITHUB_TOKEN` or pass `--token` for private-repo access, GitHub writeback, metadata apply flows, and other authenticated actions. - Missing or broken Notion config: create or fix `config/notion-config.json` before using `--notion-sync`, `--notion-registry`, or Notion writeback. - Starting from scratch: copy `config/examples/audit-config.example.yaml` to `audit-config.yaml` and `config/examples/notion-config.example.json` to `config/notion-config.json`. -- Missing Excel template: restore `assets/excel/analyst-template.xlsx` or use `--excel-mode standard`. +- Missing Excel template: restore `src/github_repo_auditor/assets/excel/analyst-template.xlsx` or use `--excel-mode standard`. - Missing baseline report: run a full audit before using `--repos`, `--incremental`, or other baseline-dependent workflows. - Config/profile errors: fix `audit-config.yaml` syntax or choose an existing scoring profile under `config/scoring-profiles/`. diff --git a/config/custom-analyzers/dockerfile_check.py b/config/custom-analyzers/dockerfile_check.py index 1164cc81..23c0b541 100644 --- a/config/custom-analyzers/dockerfile_check.py +++ b/config/custom-analyzers/dockerfile_check.py @@ -2,9 +2,9 @@ from pathlib import Path -from src.analyzers.base import BaseAnalyzer -from src.github_client import GitHubClient -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.analyzers.base import BaseAnalyzer +from github_repo_auditor.github_client import GitHubClient +from github_repo_auditor.models import AnalyzerResult, RepoMetadata class DockerfileAnalyzer(BaseAnalyzer): diff --git a/config/project-registry-overrides.json b/config/project-registry-overrides.json index 7b8acae9..e9c4f7a0 100644 --- a/config/project-registry-overrides.json +++ b/config/project-registry-overrides.json @@ -1,5 +1,5 @@ { - "_comment": "Operator-editable enrollment for the canonical project registry. 'overrides' pins hard normalization failures (drifted identifier -> canonical project_key). 'supplementary' enrolls real operator-OS projects the auditor does not track as git repos. 'memory_meta' maps ~/.claude memory note-slugs to their parent project (or '' for pure meta-notes). 'notion_projection_policy_schema_version', 'notion_title_aliases', 'notion_projection_only_rows', and 'notion_truth_shadow_rows' explain Local Portfolio projection differences that are not distinct GithubRepoAuditor truth projects. Consumed by src/project_registry.py and cross-system smoke checks; falls back to built-in defaults if this file is absent.", + "_comment": "Operator-editable enrollment for the canonical project registry. 'overrides' pins hard normalization failures (drifted identifier -> canonical project_key). 'supplementary' enrolls real operator-OS projects the auditor does not track as git repos. 'memory_meta' maps ~/.claude memory note-slugs to their parent project (or '' for pure meta-notes). 'notion_projection_policy_schema_version', 'notion_title_aliases', 'notion_projection_only_rows', and 'notion_truth_shadow_rows' explain Local Portfolio projection differences that are not distinct GithubRepoAuditor truth projects. Consumed by src/github_repo_auditor/project_registry.py and cross-system smoke checks; falls back to built-in defaults if this file is absent.", "overrides": { "jcc": "JobCommandCenter", "jsm_export": "JSMTicketAnalyticsExport", diff --git a/docs/architecture.md b/docs/architecture.md index 074dd85f..4d6116b2 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -10,9 +10,9 @@ GitHub Repo Auditor is now a workbook-first portfolio operating system, not just The same weekly story is rendered across workbook, Markdown, HTML, review-pack, and scheduled handoff. The workbook remains the flagship surface, while the other artifacts mirror the same compressed interpretation so operators do not have to relearn the product by surface. -The weekly packaging seam now has an explicit structured contract, `weekly_story_v1`, finalized through `src/weekly_packaging.py` and exposed by `build_weekly_review_pack(...)`. That contract gives the visible weekly surfaces one shared summary, next-step, section order, and evidence-strip model instead of letting each renderer invent its own condensed story. The current release also adds a bounded approval-aware weekly overlay in `src/weekly_scheduling_overlay.py`, but that overlay still lives inside the same weekly contract instead of creating a second recommendation engine. +The weekly packaging seam now has an explicit structured contract, `weekly_story_v1`, finalized through `src/github_repo_auditor/weekly_packaging.py` and exposed by `build_weekly_review_pack(...)`. That contract gives the visible weekly surfaces one shared summary, next-step, section order, and evidence-strip model instead of letting each renderer invent its own condensed story. The current release also adds a bounded approval-aware weekly overlay in `src/github_repo_auditor/weekly_scheduling_overlay.py`, but that overlay still lives inside the same weekly contract instead of creating a second recommendation engine. -Phase 107 adds one more bounded read model on top of that same weekly seam: `weekly_command_center_digest_v1` in `src/weekly_command_center.py`. The digest is derived from `weekly_story_v1`, the latest operator summary, and the current portfolio-truth snapshot. It is explicitly report-only and workbook-first. Its job is to give a future weekly loop one canonical digest artifact without creating a second weekly authority or widening automation power. +Phase 107 adds one more bounded read model on top of that same weekly seam: `weekly_command_center_digest_v1` in `src/github_repo_auditor/weekly_command_center.py`. The digest is derived from `weekly_story_v1`, the latest operator summary, and the current portfolio-truth snapshot. It is explicitly report-only and workbook-first. Its job is to give a future weekly loop one canonical digest artifact without creating a second weekly authority or widening automation power. The portfolio layer now has its own explicit truth contract too. `--portfolio-truth` builds a versioned machine-readable snapshot for the configured local projects workspace and treats the legacy markdown registry/report files as derived compatibility surfaces rather than as canonical inputs. @@ -36,7 +36,7 @@ That means the architecture is intentionally split between raw state assembly an ### `audit` -`src/cli.py` remains the single entrypoint. It keeps one flag-based command surface and packages the product around four guidance modes: +`src/github_repo_auditor/cli.py` remains the single entrypoint. It keeps one flag-based command surface and packages the product around four guidance modes: - `First Run` - `Weekly Review` @@ -67,57 +67,57 @@ Phase 107 extends that same read-only path. A control-center refresh now also wr The current module boundaries are documented enough to guide work, but they still carry known concentration risk: -- `src/operator_control_center.py` +- `src/github_repo_auditor/operator_control_center.py` Public façade and orchestration layer for control-center snapshot building. -- `src/operator_snapshot_packaging.py` +- `src/github_repo_auditor/operator_snapshot_packaging.py` Operator summary assembly, handoff packaging, and control-center artifact payload shaping. -- `src/operator_decision_quality.py` +- `src/github_repo_auditor/operator_decision_quality.py` Versioned `decision_quality_v1` contract assembly, historical downgrade rules, and the single ownership seam for decision-quality derivation. -- `src/operator_follow_through.py` +- `src/github_repo_auditor/operator_follow_through.py` Follow-through enrichment, projection, and follow-through summary families. -- `src/operator_resolution_trend.py` +- `src/github_repo_auditor/operator_resolution_trend.py` Resolution-trend, trust, closure-forecast, calibration, and queue-history reasoning. -- `src/operator_control_center_rendering.py` +- `src/github_repo_auditor/operator_control_center_rendering.py` Markdown rendering for the control-center artifact. -- `src/weekly_command_center.py` +- `src/github_repo_auditor/weekly_command_center.py` Report-only weekly digest contract and artifact rendering for the bounded command-center loop. -- `src/report_enrichment.py` +- `src/github_repo_auditor/report_enrichment.py` Raw `weekly_pack` assembly plus the compatibility façade that hands off to the extracted weekly packaging seam. -- `src/weekly_packaging.py` +- `src/github_repo_auditor/weekly_packaging.py` Shared weekly contract finalization, compact explainability, and parity layer for workbook, Markdown, HTML, review-pack, and scheduled handoff. -- `src/action_sync_readiness.py` +- `src/github_repo_auditor/action_sync_readiness.py` Readiness-stage logic for deciding whether a campaign should stay local, preview next, apply next, or stop for drift/blockers. -- `src/action_sync_packets.py` +- `src/github_repo_auditor/action_sync_packets.py` Execution handoff logic that turns a campaign into an `Apply Packet` with blockers, rollback posture, and command hints. -- `src/action_sync_outcomes.py` +- `src/github_repo_auditor/action_sync_outcomes.py` Post-apply monitoring logic that judges whether a recent sync is holding, drifting back, reopening, or still needs monitoring. -- `src/action_sync_tuning.py` +- `src/github_repo_auditor/action_sync_tuning.py` Bounded tie-break layer that uses post-apply history to rank tied campaigns without changing stage precedence. -- `src/approval_ledger.py` +- `src/github_repo_auditor/approval_ledger.py` Local approval workflow synthesis, fingerprinting, approval receipts, and approval-center packaging. -- `src/intervention_ledger.py` +- `src/github_repo_auditor/intervention_ledger.py` Cross-run repo intelligence synthesis that connects intervention history, recurring pressure, hotspot persistence, scorecard direction, and campaign aftermath. -- `src/implementation_hotspots.py` +- `src/github_repo_auditor/implementation_hotspots.py` Repo-level implementation pressure and “where to start” guidance. -- `src/warehouse.py` +- `src/github_repo_auditor/warehouse.py` Persistence, history loading, and compatibility handling for regenerated reports and historical trend work. -- `src/portfolio_truth_types.py` +- `src/github_repo_auditor/portfolio_truth_types.py` Versioned public truth contract for workspace projects. -- `src/portfolio_truth_sources.py` +- `src/github_repo_auditor/portfolio_truth_sources.py` Safe local workspace, legacy-registry, and optional Notion source adapters for portfolio truth generation. -- `src/portfolio_truth_reconcile.py` +- `src/github_repo_auditor/portfolio_truth_reconcile.py` Field-by-field precedence, derived status mapping, and truth snapshot assembly. -- `src/portfolio_truth_validate.py` +- `src/github_repo_auditor/portfolio_truth_validate.py` Truth contract, compatibility, and external-path safety validation. -- `src/portfolio_truth_render.py` +- `src/github_repo_auditor/portfolio_truth_render.py` Compatibility rendering for `project-registry.md` and `PORTFOLIO-AUDIT-REPORT.md`. -- `src/portfolio_truth_publish.py` +- `src/github_repo_auditor/portfolio_truth_publish.py` Publish orchestration with temp-file staging, validation, and replace-on-success semantics. -- `src/portfolio_context_contract.py` +- `src/github_repo_auditor/portfolio_context_contract.py` Semantic contract for minimum-viable context, accepted heading aliases, managed context blocks, and context-band classification. -- `src/portfolio_context_recovery.py` +- `src/github_repo_auditor/portfolio_context_recovery.py` Frozen-cohort planning, dirty/temp skip rules, managed context block application, and bounded catalog seed handling for workspace recovery. -- `src/portfolio_pathing.py` +- `src/github_repo_auditor/portfolio_pathing.py` Single owner for normalized operating-path derivation, confidence, override, and rationale assembly. This separation is deliberate, but it is not “finished architecture”: @@ -142,7 +142,7 @@ The architecture rule is now: - declared portfolio metadata stays in the catalog - scorecards still own maturity evaluation - tactical collections remain derived views -- `src/portfolio_pathing.py` normalizes stable path, temporary override, confidence, and rationale +- `src/github_repo_auditor/portfolio_pathing.py` normalizes stable path, temporary override, confidence, and rationale - the truth snapshot is the one machine-facing surface that other renderers consume The normalized path contract is intentionally strict: @@ -157,7 +157,7 @@ This keeps path semantics advisory and portable without reopening queue, approva ## Portfolio Risk Overlay -Phase 108 adds a structured risk overlay on top of the shipped truth, context, path, and trust layers. `src/portfolio_risk.py` is the single owner of risk tier derivation. Risk tiers are `elevated`, `moderate`, `baseline`, and `deferred`. The overlay is advisory-only and derives from already-present truth fields — no new data collection. +Phase 108 adds a structured risk overlay on top of the shipped truth, context, path, and trust layers. `src/github_repo_auditor/portfolio_risk.py` is the single owner of risk tier derivation. Risk tiers are `elevated`, `moderate`, `baseline`, and `deferred`. The overlay is advisory-only and derives from already-present truth fields — no new data collection. Risk factors are accumulated during reconciliation and written into `RiskFields` on each `PortfolioTruthProject`. Compound factor thresholds keep signal gradation useful: `elevated` requires three or more factors, or the specific compound pair `weak-context-active + investigate-override`. Most repos land at `moderate` or `baseline`. Archived and stale-non-maintain repos are short-circuited to `deferred`. @@ -171,7 +171,7 @@ Phase 108 standardizes a minimal doctor/release-check contract for strategic rep The active roadmap already treats two cleanup tracks as real dependencies for later feature work: -- Phase 99 extracted the weekly packaging seam into `src/weekly_packaging.py`, but `src/report_enrichment.py` still remains a broad raw-assembly module that should not absorb new weekly feature growth casually +- Phase 99 extracted the weekly packaging seam into `src/github_repo_auditor/weekly_packaging.py`, but `src/github_repo_auditor/report_enrichment.py` still remains a broad raw-assembly module that should not absorb new weekly feature growth casually - Phase 100 extracted the highest-risk operator subsystems into dedicated modules, but later approval work should still land on those bounded seams instead of rebuilding concentration inside the façade ## Shared Artifact Model @@ -200,13 +200,13 @@ The shared-weekly rule is now explicit: The operator system has two architectural layers: -- raw operator state orchestration in `src/operator_control_center.py` +- raw operator state orchestration in `src/github_repo_auditor/operator_control_center.py` - extracted operator subsystem logic in: - - `src/operator_resolution_trend.py` - - `src/operator_follow_through.py` - - `src/operator_snapshot_packaging.py` - - `src/operator_control_center_rendering.py` -- compressed weekly packaging in `src/weekly_packaging.py` + - `src/github_repo_auditor/operator_resolution_trend.py` + - `src/github_repo_auditor/operator_follow_through.py` + - `src/github_repo_auditor/operator_snapshot_packaging.py` + - `src/github_repo_auditor/operator_control_center_rendering.py` +- compressed weekly packaging in `src/github_repo_auditor/weekly_packaging.py` The operator façade keeps: @@ -256,7 +256,7 @@ These are warehouse-backed descriptive summaries. They inform the weekly read bu Phase 105 adds a bounded trust-contract layer on top of the existing effectiveness and calibration evidence. The goal is not to invent a second recommendation engine. The goal is to make the existing trust posture explicit, reusable, and historically comparable. -The new contract is `decision_quality_v1`. It is assembled in `src/operator_decision_quality.py`, stored inside `operator_summary`, and also persisted as a compact warehouse-backed summary so later runs do not have to scrape prose to compare recommendation quality. +The new contract is `decision_quality_v1`. It is assembled in `src/github_repo_auditor/operator_decision_quality.py`, stored inside `operator_summary`, and also persisted as a compact warehouse-backed summary so later runs do not have to scrape prose to compare recommendation quality. `decision_quality_v1` is intentionally narrow: @@ -268,7 +268,7 @@ The new contract is `decision_quality_v1`. It is assembled in `src/operator_deci The architecture rule for this layer is strict: - raw evidence may come from operator history, calibration history, trend history, and current operator state -- `src/operator_decision_quality.py` is the only owner that turns that evidence into a decision-quality contract +- `src/github_repo_auditor/operator_decision_quality.py` is the only owner that turns that evidence into a decision-quality contract - workbook, Markdown, HTML, review-pack, scheduled handoff, and control-center surfaces consume the same contract through packaged summary fields instead of recomputing trust locally - older warehouse runs that predate the contract are explicitly downgraded to `insufficient-data` rather than guessed into compatibility @@ -359,10 +359,10 @@ Instead it explains whether a repo currently looks: Architecturally, this keeps the split clean: -- `src/portfolio_intelligence.py` remains the current-state portfolio layer -- `src/intervention_ledger.py` owns cross-run historical synthesis -- `src/operator_control_center.py` carries those results into `operator_summary` and queue items -- `src/report_enrichment.py` packages the same historical story across workbook, Markdown, HTML, review-pack, and scheduled handoff +- `src/github_repo_auditor/portfolio_intelligence.py` remains the current-state portfolio layer +- `src/github_repo_auditor/intervention_ledger.py` owns cross-run historical synthesis +- `src/github_repo_auditor/operator_control_center.py` carries those results into `operator_summary` and queue items +- `src/github_repo_auditor/report_enrichment.py` packages the same historical story across workbook, Markdown, HTML, review-pack, and scheduled handoff ## Weekly Story Contract @@ -417,11 +417,11 @@ That layer does not widen write authority. It exists to answer: Architecturally: -- `src/approval_ledger.py` owns approval synthesis and fingerprinting -- `src/approval_ledger.py` now also derives approval freshness from the latest tracked follow-up review event or the original approval timestamp fallback -- `src/operator_control_center.py` carries approval state into the operator summary and queue items -- `src/report_enrichment.py` passes the approval story through the shared weekly/report contracts, and `src/weekly_packaging.py` now carries the final weekly approval packaging across workbook, Markdown, HTML, review-pack, and scheduled handoff artifacts -- `src/warehouse.py` persists approval ledger snapshots, initial approval records, and append-only approval follow-up events while keeping legacy governance approvals readable +- `src/github_repo_auditor/approval_ledger.py` owns approval synthesis and fingerprinting +- `src/github_repo_auditor/approval_ledger.py` now also derives approval freshness from the latest tracked follow-up review event or the original approval timestamp fallback +- `src/github_repo_auditor/operator_control_center.py` carries approval state into the operator summary and queue items +- `src/github_repo_auditor/report_enrichment.py` passes the approval story through the shared weekly/report contracts, and `src/github_repo_auditor/weekly_packaging.py` now carries the final weekly approval packaging across workbook, Markdown, HTML, review-pack, and scheduled handoff artifacts +- `src/github_repo_auditor/warehouse.py` persists approval ledger snapshots, initial approval records, and append-only approval follow-up events while keeping legacy governance approvals readable Approval stays local-authoritative: @@ -506,7 +506,7 @@ template-stamped control modules, and lazy-import cycles. 1. **No new callable-threaded extractions.** When logic is extracted from a large module, the shared primitives it needs move to a leaf support module (for the operator layer, - `src/operator_trend_support.py`) and are imported by the extracted module. Threading the + `src/github_repo_auditor/operator_trend_support.py`) and are imported by the extracted module. Threading the parent's helpers back in as `Callable` parameters is no longer an accepted way to avoid a circular import — fix the dependency direction instead. 2. **No new stamped control modules.** A new closure-forecast or trend control may not be @@ -514,8 +514,8 @@ template-stamped control modules, and lazy-import cycles. `*_recovery_for_target` / `apply_*_control` / `*_hotspots` / `*_summary` / `*_reason`) into a new module. New controls wait for (or drive) the parametrized control core; until that core exists, adding one requires an explicit maintainer decision recorded here. -3. **No new function-level internal imports outside the CLI dispatch layer.** `src/cli.py` - may defer imports for startup latency. Everywhere else, a deferred `from src...` import +3. **No new function-level internal imports outside the CLI dispatch layer.** `src/github_repo_auditor/cli.py` + may defer imports for startup latency. Everywhere else, a deferred `from github_repo_auditor...` import inside a function body marks a layering bug; fix the direction rather than hiding the import. 4. **Overlays merge or die.** An advisory overlay that survives two further phases is either @@ -534,9 +534,9 @@ Its job is narrow: Architecturally: -- `src/action_sync_automation.py` owns the posture classification and safe-command packaging -- `src/operator_control_center.py` carries the posture into `operator_summary` and queue items -- `src/report_enrichment.py` packages the same wording across workbook, Markdown, HTML, review-pack, and scheduled handoff +- `src/github_repo_auditor/action_sync_automation.py` owns the posture classification and safe-command packaging +- `src/github_repo_auditor/operator_control_center.py` carries the posture into `operator_summary` and queue items +- `src/github_repo_auditor/report_enrichment.py` packages the same wording across workbook, Markdown, HTML, review-pack, and scheduled handoff This layer does not widen write authority: diff --git a/docs/audit-serve.md b/docs/audit-serve.md index 69c37609..bb7e8f13 100644 --- a/docs/audit-serve.md +++ b/docs/audit-serve.md @@ -103,7 +103,7 @@ These are intent-log only — they do not call `--writeback-apply`. ## Subprocess safety All flags accepted through the `/runs/new` form are validated against a strict allowlist -(`SAFE_FLAG_NAMES` in `src/serve/runner.py`). Any flag name not in the allowlist is +(`SAFE_FLAG_NAMES` in `src/github_repo_auditor/serve/runner.py`). Any flag name not in the allowlist is rejected before the subprocess is spawned. Flag values are additionally checked against a shell-metacharacter blocklist. The diff --git a/docs/ci-coverage-initiative.md b/docs/ci-coverage-initiative.md index f798f21e..511be7e0 100644 --- a/docs/ci-coverage-initiative.md +++ b/docs/ci-coverage-initiative.md @@ -53,14 +53,14 @@ Re-evaluate if any graduate to wip or functional tier. Three bugs discovered during this work and fixed in `fix/analyzer-bugs`: -1. **`src/analyzers/testing.py`** — `*Tests.swift` / `*Test.swift` missing from +1. **`src/github_repo_auditor/analyzers/testing.py`** — `*Tests.swift` / `*Test.swift` missing from `TEST_PATTERNS`. XCTest file count was always 0, capping Swift repos at 0.7. After fix: all 12 affected repos jumped to testing score 1.0, revealing 101 previously invisible test files across the portfolio. -2. **`src/analyzers/cicd.py`** — `Package.swift`, `Podfile`, `project.yml`/`project.yaml` +2. **`src/github_repo_auditor/analyzers/cicd.py`** — `Package.swift`, `Podfile`, `project.yml`/`project.yaml` not in `_has_build_scripts()`. XcodeGen repos had no build score. -3. **`src/cloner.py`** — hardcoded `/tmp/audit-repos` shared across sessions. +3. **`src/github_repo_auditor/cloner.py`** — hardcoded `/tmp/audit-repos` shared across sessions. Replaced with `tempfile.TemporaryDirectory` — each session gets an isolated dir, eliminating cross-run collisions on batch audits. diff --git a/docs/codex-restart-packet.md b/docs/codex-restart-packet.md index 0d9e7d6d..5f590b38 100644 --- a/docs/codex-restart-packet.md +++ b/docs/codex-restart-packet.md @@ -8,13 +8,13 @@ should not pull operator attention during normal restarts. ## Command ```sh -python -m src.codex_restart_packet --workspace-root /Users/d/Projects +python -m github_repo_auditor.codex_restart_packet --workspace-root "$HOME/Projects" ``` Use JSON when another tool should consume the result: ```sh -python -m src.codex_restart_packet --workspace-root /Users/d/Projects --json +python -m github_repo_auditor.codex_restart_packet --workspace-root "$HOME/Projects" --json ``` For the current generated portfolio truth counts, query the canonical snapshot directly: diff --git a/docs/demo-proof/2026-06-07/README.md b/docs/demo-proof/2026-06-07/README.md index c3fdeb55..57f9477e 100644 --- a/docs/demo-proof/2026-06-07/README.md +++ b/docs/demo-proof/2026-06-07/README.md @@ -50,8 +50,8 @@ The contact sheet is the quickest visual smoke check for all five frames. Refreshed on 2026-06-07 after re-running: ```sh -python -m src.cli --portfolio-truth --portfolio-truth-include-security saagpatel -python -m src.cli triage saagpatel --control-center +python -m github_repo_auditor.cli --portfolio-truth --portfolio-truth-include-security saagpatel +python -m github_repo_auditor.cli triage saagpatel --control-center ``` Use `RECORDING-CHECKLIST.md` for the exact 90-second capture order and diff --git a/docs/demo-proof/2026-06-07/proof-package.json b/docs/demo-proof/2026-06-07/proof-package.json index 2efa7761..f0aa3f66 100644 --- a/docs/demo-proof/2026-06-07/proof-package.json +++ b/docs/demo-proof/2026-06-07/proof-package.json @@ -10,8 +10,8 @@ "repo": "GithubRepoAuditor", "mode": "demo", "commands": [ - "python -m src.cli --portfolio-truth --portfolio-truth-include-security saagpatel", - "python -m src.cli triage saagpatel --control-center", + "python -m github_repo_auditor.cli --portfolio-truth --portfolio-truth-include-security saagpatel", + "python -m github_repo_auditor.cli triage saagpatel --control-center", "pnpm demo:desktop" ] }, diff --git a/docs/demo-proof/public-fixture/SUMMARY.md b/docs/demo-proof/public-fixture/SUMMARY.md index 10929a51..fd55bcad 100644 --- a/docs/demo-proof/public-fixture/SUMMARY.md +++ b/docs/demo-proof/public-fixture/SUMMARY.md @@ -6,7 +6,7 @@ This package establishes the safe public data path for the Operator OS / Portfolio Command Center demo: - fixture input: `fixtures/demo/sample-report.json` for the audit-report lane, - and the closed-pool synthetic portfolio in `src/demo_portfolio.py` for the + and the closed-pool synthetic portfolio in `src/github_repo_auditor/demo_portfolio.py` for the PortfolioCommandCenter lane; - generated artifacts: `output/demo/`, including a 40-project PortfolioCommandCenter `projects` payload at the producer's current truth schema with receipt-backed diff --git a/docs/demo-proof/public-fixture/VERIFICATION-NOTES.md b/docs/demo-proof/public-fixture/VERIFICATION-NOTES.md index b3bbda7c..8896ce8d 100644 --- a/docs/demo-proof/public-fixture/VERIFICATION-NOTES.md +++ b/docs/demo-proof/public-fixture/VERIFICATION-NOTES.md @@ -5,10 +5,10 @@ Date: 2026-08-02 (frames recaptured; supersedes the 2026-06-27 capture) ## Fixture Truth - Fixture input: `fixtures/demo/sample-report.json` (audit-report lane) and - `src/demo_portfolio.py` (PortfolioCommandCenter lane). + `src/github_repo_auditor/demo_portfolio.py` (PortfolioCommandCenter lane). - Generated output directory: `output/demo`. - Portfolio truth schema: sourced from the producer constant - `src.portfolio_truth_types.SCHEMA_VERSION`, never restated in the generator. + `github_repo_auditor.portfolio_truth_types.SCHEMA_VERSION`, never restated in the generator. - Visible project names: closed synthetic codename pool (`Aurora Ledger`, `Basalt Relay`, ... `Nocturne Spar`); no real repository is named. - Visible workspace root: `/demo-workspace`. diff --git a/docs/demo-proof/public-fixture/proof-package.json b/docs/demo-proof/public-fixture/proof-package.json index 97c39373..b02535b2 100644 --- a/docs/demo-proof/public-fixture/proof-package.json +++ b/docs/demo-proof/public-fixture/proof-package.json @@ -170,7 +170,7 @@ { "id": "demo-portfolio-source", "kind": "source", - "path": "../../../src/demo_portfolio.py", + "path": "../../../src/github_repo_auditor/demo_portfolio.py", "description": "Closed-pool synthetic portfolio definition backing the demo truth artifacts.", "required": true }, diff --git a/docs/extending-analyzers.md b/docs/extending-analyzers.md index c699075f..0a5dcf75 100644 --- a/docs/extending-analyzers.md +++ b/docs/extending-analyzers.md @@ -17,7 +17,7 @@ An analyzer should inspect one aspect of a repo and return: ## Practical workflow -1. Add the analyzer under `src/analyzers/`. +1. Add the analyzer under `src/github_repo_auditor/analyzers/`. 2. Make sure it fits the existing result shape used by scoring and report writers. 3. Add tests for both the analyzer output and any score/report behavior it changes. 4. Re-run `pytest` and `make workbook-gate` if workbook-facing summaries change. diff --git a/docs/machine-wide-consolidation.md b/docs/machine-wide-consolidation.md index 46607e3e..a701bf5c 100644 --- a/docs/machine-wide-consolidation.md +++ b/docs/machine-wide-consolidation.md @@ -9,7 +9,7 @@ Run this first in new Codex sessions: ```sh cd /Users/d/Projects/GithubRepoAuditor -python -m src.codex_restart_packet --workspace-root /Users/d/Projects +python -m github_repo_auditor.codex_restart_packet --workspace-root "$HOME/Projects" ``` The packet combines: diff --git a/docs/operator-troubleshooting.md b/docs/operator-troubleshooting.md index efc417b1..414edb07 100644 --- a/docs/operator-troubleshooting.md +++ b/docs/operator-troubleshooting.md @@ -81,7 +81,7 @@ Symptoms: - template-mode preflight failure Fix: -- restore `assets/excel/analyst-template.xlsx` +- restore `src/github_repo_auditor/assets/excel/analyst-template.xlsx` - or run with `--excel-mode standard` `standard` mode is the default and recommended workbook path. diff --git a/docs/plans/2026-05-29-run-instructions-external-audit-plan.md b/docs/plans/2026-05-29-run-instructions-external-audit-plan.md index ab897823..06239337 100644 --- a/docs/plans/2026-05-29-run-instructions-external-audit-plan.md +++ b/docs/plans/2026-05-29-run-instructions-external-audit-plan.md @@ -4,7 +4,7 @@ **Goal:** Independently re-check the snapshot's `run_instructions_present` claim against on-disk ground truth across a stratified pilot of ~19 repos, and produce a discrepancy report. -**Architecture:** A deterministic, read-only Python pre-step (`src/run_instructions_audit.py`, TDD'd) selects the pilot and computes per-repo metadata + a live `tool_today` recompute, emitting compact JSON. A `Workflow` (`scripts/run-instructions-audit.workflow.js`) fans out one Haiku subagent per repo to read the files and judge (blind to the tool's answer), tallies buckets in deterministic JS, and a single Sonnet call writes the markdown report. +**Architecture:** A deterministic, read-only Python pre-step (`src/github_repo_auditor/run_instructions_audit.py`, TDD'd) selects the pilot and computes per-repo metadata + a live `tool_today` recompute, emitting compact JSON. A `Workflow` (`scripts/run-instructions-audit.workflow.js`) fans out one Haiku subagent per repo to read the files and judge (blind to the tool's answer), tallies buckets in deterministic JS, and a single Sonnet call writes the markdown report. **Tech Stack:** Python 3.11+ (pytest), the `Workflow` tool (JS orchestration, Haiku verifiers, Sonnet synthesis), `ctx_execute` to run the pre-step. @@ -16,7 +16,7 @@ | File | Responsibility | |---|---| -| `src/run_instructions_audit.py` (create) | Stage 0 pilot selection + Stage 1 evidence prep + Stage 3 bucket logic (pure fns reused by the workflow's JS mirror). Read-only. | +| `src/github_repo_auditor/run_instructions_audit.py` (create) | Stage 0 pilot selection + Stage 1 evidence prep + Stage 3 bucket logic (pure fns reused by the workflow's JS mirror). Read-only. | | `tests/test_run_instructions_audit.py` (create) | Unit tests for every pure fn + tmp_path tests for the IO fns. | | `scripts/run-instructions-audit.workflow.js` (create) | Stage 2 verifier fan-out + Stage 3 JS tally + Stage 4 synthesis. | | `output/run-instructions-audit-2026-05-29.md` (generated) | The report. Gitignored. | @@ -28,14 +28,14 @@ ## Task 1: Module scaffold + `is_fork_junk` + `assign_bucket` (Stage 3 truth table) **Files:** -- Create: `src/run_instructions_audit.py` +- Create: `src/github_repo_auditor/run_instructions_audit.py` - Test: `tests/test_run_instructions_audit.py` - [ ] **Step 1: Write the failing test** ```python # tests/test_run_instructions_audit.py -from src.run_instructions_audit import assign_bucket, is_fork_junk +from github_repo_auditor.run_instructions_audit import assign_bucket, is_fork_junk def test_is_fork_junk_flags_known_patterns(): @@ -61,12 +61,12 @@ def test_assign_bucket_truth_table(): - [ ] **Step 2: Run test to verify it fails** Run: `python -m pytest tests/test_run_instructions_audit.py -q` -Expected: FAIL — `ModuleNotFoundError: No module named 'src.run_instructions_audit'` +Expected: FAIL — `ModuleNotFoundError: No module named 'github_repo_auditor.run_instructions_audit'` - [ ] **Step 3: Write minimal implementation** ```python -# src/run_instructions_audit.py +# src/github_repo_auditor/run_instructions_audit.py """External audit of the snapshot's run_instructions_present claim (pre-step). Stage 0 (stratified pilot selection) + Stage 1 (evidence prep + live tool_today @@ -82,11 +82,11 @@ import subprocess from datetime import datetime from pathlib import Path -from src.portfolio_context_contract import ( +from github_repo_auditor.portfolio_context_contract import ( analyze_project_context, choose_primary_context_file, ) -from src.portfolio_truth_sources import _collect_context_files +from github_repo_auditor.portfolio_truth_sources import _collect_context_files FORK_JUNK_PATTERNS = (r"-security-fix", r"-cve-", r"-backup-", r"\.bundle$", r"-openssl-") @@ -111,7 +111,7 @@ Expected: PASS (2 passed) - [ ] **Step 5: Commit** ```bash -git add src/run_instructions_audit.py tests/test_run_instructions_audit.py +git add src/github_repo_auditor/run_instructions_audit.py tests/test_run_instructions_audit.py git commit -m "feat: add run-instructions audit pre-step scaffold + bucket logic" ``` @@ -120,13 +120,13 @@ git commit -m "feat: add run-instructions audit pre-step scaffold + bucket logic ## Task 2: `assign_drift_bucket` (snapshot-vs-today drift) **Files:** -- Modify: `src/run_instructions_audit.py` +- Modify: `src/github_repo_auditor/run_instructions_audit.py` - Test: `tests/test_run_instructions_audit.py` - [ ] **Step 1: Write the failing test** ```python -from src.run_instructions_audit import assign_drift_bucket +from github_repo_auditor.run_instructions_audit import assign_drift_bucket def test_assign_drift_bucket(): @@ -146,7 +146,7 @@ Expected: FAIL — `ImportError: cannot import name 'assign_drift_bucket'` - [ ] **Step 3: Write minimal implementation** -Add to `src/run_instructions_audit.py` after `assign_bucket`: +Add to `src/github_repo_auditor/run_instructions_audit.py` after `assign_bucket`: ```python def assign_drift_bucket(snapshot_claim: bool, tool_today: bool, repo_drifted: bool) -> str: @@ -163,7 +163,7 @@ Expected: PASS (3 passed) - [ ] **Step 5: Commit** ```bash -git add src/run_instructions_audit.py tests/test_run_instructions_audit.py +git add src/github_repo_auditor/run_instructions_audit.py tests/test_run_instructions_audit.py git commit -m "feat: add drift bucket logic to run-instructions audit" ``` @@ -172,13 +172,13 @@ git commit -m "feat: add drift bucket logic to run-instructions audit" ## Task 3: `select_pilot` (Stage 0 — stratified, deterministic) **Files:** -- Modify: `src/run_instructions_audit.py` +- Modify: `src/github_repo_auditor/run_instructions_audit.py` - Test: `tests/test_run_instructions_audit.py` - [ ] **Step 1: Write the failing test** ```python -from src.run_instructions_audit import select_pilot +from github_repo_auditor.run_instructions_audit import select_pilot def _project(key, quality, *, status="active", path=None): @@ -261,7 +261,7 @@ Expected: PASS (4 passed) - [ ] **Step 5: Commit** ```bash -git add src/run_instructions_audit.py tests/test_run_instructions_audit.py +git add src/github_repo_auditor/run_instructions_audit.py tests/test_run_instructions_audit.py git commit -m "feat: add stratified pilot selection (Stage 0)" ``` @@ -270,13 +270,13 @@ git commit -m "feat: add stratified pilot selection (Stage 0)" ## Task 4: `build_record` (Stage 1a — compact record from snapshot, pure) **Files:** -- Modify: `src/run_instructions_audit.py` +- Modify: `src/github_repo_auditor/run_instructions_audit.py` - Test: `tests/test_run_instructions_audit.py` - [ ] **Step 1: Write the failing test** ```python -from src.run_instructions_audit import build_record +from github_repo_auditor.run_instructions_audit import build_record def test_build_record_resolves_path_and_primary(): @@ -337,7 +337,7 @@ Expected: PASS (6 passed) - [ ] **Step 5: Commit** ```bash -git add src/run_instructions_audit.py tests/test_run_instructions_audit.py +git add src/github_repo_auditor/run_instructions_audit.py tests/test_run_instructions_audit.py git commit -m "feat: add build_record (Stage 1a) for run-instructions audit" ``` @@ -346,7 +346,7 @@ git commit -m "feat: add build_record (Stage 1a) for run-instructions audit" ## Task 5: IO fns — `is_after`, `compute_drifted`, `compute_tool_today` (Stage 1b/c) **Files:** -- Modify: `src/run_instructions_audit.py` +- Modify: `src/github_repo_auditor/run_instructions_audit.py` - Test: `tests/test_run_instructions_audit.py` - [ ] **Step 1: Write the failing test** @@ -355,7 +355,7 @@ git commit -m "feat: add build_record (Stage 1a) for run-instructions audit" import os import subprocess -from src.run_instructions_audit import compute_drifted, compute_tool_today, is_after +from github_repo_auditor.run_instructions_audit import compute_drifted, compute_tool_today, is_after def test_is_after_compares_tz_aware_iso(): @@ -446,7 +446,7 @@ Expected: PASS (12 passed). If `compute_tool_today` cases fail on heading parsin - [ ] **Step 5: Commit** ```bash -git add src/run_instructions_audit.py tests/test_run_instructions_audit.py +git add src/github_repo_auditor/run_instructions_audit.py tests/test_run_instructions_audit.py git commit -m "feat: add live tool_today recompute + git drift detection (Stage 1)" ``` @@ -455,7 +455,7 @@ git commit -m "feat: add live tool_today recompute + git drift detection (Stage ## Task 6: `prepare_pilot` + `main` (Stage 0+1 orchestration) **Files:** -- Modify: `src/run_instructions_audit.py` +- Modify: `src/github_repo_auditor/run_instructions_audit.py` - Test: `tests/test_run_instructions_audit.py` - [ ] **Step 1: Write the failing test** @@ -463,7 +463,7 @@ git commit -m "feat: add live tool_today recompute + git drift detection (Stage ```python import json -from src.run_instructions_audit import prepare_pilot +from github_repo_auditor.run_instructions_audit import prepare_pilot def test_prepare_pilot_builds_records_and_reports_missing_dirs(tmp_path): @@ -562,13 +562,13 @@ Expected: PASS (13 passed) - [ ] **Step 5: Verify ruff + run against the real snapshot (smoke, read-only)** -Run: `python -m ruff check src/run_instructions_audit.py && python -m src.run_instructions_audit output/portfolio-truth-latest.json | python3 -c "import json,sys; d=json.load(sys.stdin); print('records', len(d['records']), 'errors', len(d['errors']))"` +Run: `python -m ruff check src/github_repo_auditor/run_instructions_audit.py && python -m github_repo_auditor.run_instructions_audit output/portfolio-truth-latest.json | python3 -c "import json,sys; d=json.load(sys.stdin); print('records', len(d['records']), 'errors', len(d['errors']))"` Expected: ruff clean; ~15–20 records, errors listed (not crashed). Confirms real paths resolve. - [ ] **Step 6: Commit** ```bash -git add src/run_instructions_audit.py tests/test_run_instructions_audit.py +git add src/github_repo_auditor/run_instructions_audit.py tests/test_run_instructions_audit.py git commit -m "feat: add prepare_pilot orchestrator + CLI entrypoint (Stage 0+1)" ``` @@ -626,7 +626,7 @@ function verifierPrompt(rec) { ].join('\n') } -// --- Stage 3 tally logic (mirror of src/run_instructions_audit.py) --- +// --- Stage 3 tally logic (mirror of src/github_repo_auditor/run_instructions_audit.py) --- function assignBucket(toolToday, verdict, inPrimary) { if (toolToday === verdict) return verdict ? 'agree_present' : 'agree_absent' if (verdict && !toolToday) return inPrimary ? 'fn_alias_gap' : 'fn_blind_spot' @@ -694,13 +694,13 @@ return { - [ ] **Step 2: Sanity-check the JS bucket logic matches Python** -Confirm by eye that `assignBucket`/`assignDrift` in the JS are line-for-line equivalent to `assign_bucket`/`assign_drift_bucket` in `src/run_instructions_audit.py` (same branch order, same string returns). They are the same six lines. +Confirm by eye that `assignBucket`/`assignDrift` in the JS are line-for-line equivalent to `assign_bucket`/`assign_drift_bucket` in `src/github_repo_auditor/run_instructions_audit.py` (same branch order, same string returns). They are the same six lines. - [ ] **Step 3: 2-repo smoke run (main session)** In the main session: 1. Run the pre-step and capture the JSON payload: - `python -m src.run_instructions_audit output/portfolio-truth-latest.json` + `python -m github_repo_auditor.run_instructions_audit output/portfolio-truth-latest.json` 2. Slice the payload to its first 2 `records` (keep `generated_at`, `workspace_root`, `errors`). 3. Call `Workflow({ scriptPath: "scripts/run-instructions-audit.workflow.js", args: })`. @@ -721,7 +721,7 @@ This task is **main-session only**. No pytest. - [ ] **Step 1: Run the pilot end-to-end** -1. `python -m src.run_instructions_audit output/portfolio-truth-latest.json` → full payload (~19 records). +1. `python -m github_repo_auditor.run_instructions_audit output/portfolio-truth-latest.json` → full payload (~19 records). 2. `Workflow({ scriptPath: "scripts/run-instructions-audit.workflow.js", args: })`. Expected: ~19 Haiku verifiers + 1 Sonnet synthesis; returns `{ report, stats, rows }`. diff --git a/docs/plans/2026-05-29-run-instructions-external-audit.md b/docs/plans/2026-05-29-run-instructions-external-audit.md index 6f6207f2..b2d7b22c 100644 --- a/docs/plans/2026-05-29-run-instructions-external-audit.md +++ b/docs/plans/2026-05-29-run-instructions-external-audit.md @@ -21,11 +21,11 @@ internal schema/consistency). This audits the snapshot's claims against **ground Every `derived` presence-boolean traces through one function: -- `_inspect_project_dir` — `src/portfolio_truth_sources.py:200` -- → `analyze_project_context` — `src/portfolio_context_contract.py:135` -- → `choose_primary_context_file` — `src/portfolio_context_contract.py:128` +- `_inspect_project_dir` — `src/github_repo_auditor/portfolio_truth_sources.py:200` +- → `analyze_project_context` — `src/github_repo_auditor/portfolio_context_contract.py:135` +- → `choose_primary_context_file` — `src/github_repo_auditor/portfolio_context_contract.py:128` (returns `CLAUDE.md`, else `AGENTS.md` — **never `README.md`**) -- → `_section_has_meaningful_content` — `src/portfolio_context_contract.py:265` +- → `_section_has_meaningful_content` — `src/github_repo_auditor/portfolio_context_contract.py:265` (true iff a markdown heading matches a hardcoded alias in `CONTEXT_SECTION_ALIASES` **and** has non-trivial text under it) @@ -101,8 +101,8 @@ For each pilot record, compute and attach: - `primary_file_name` = `choose_primary_context_file(context_files)`. - `tool_today` = live recompute on **today's** files: ```python - from src.portfolio_context_contract import analyze_project_context - from src.portfolio_truth_sources import _collect_context_files + from github_repo_auditor.portfolio_context_contract import analyze_project_context + from github_repo_auditor.portfolio_truth_sources import _collect_context_files tool_today = analyze_project_context( abs_path, _collect_context_files(abs_path) ).run_instructions_present @@ -201,7 +201,7 @@ Main session writes the report to `output/run-instructions-audit-2026-05-29.md`. ## 9. Out of scope (explicit — future widening, not silent cuts) - Verifying the other 5 presence booleans (same subagent file-read; trivial to add later). -- The numeric `context_quality_score` (Arc-H merge gate) — lives in `src/context_quality.py`, a +- The numeric `context_quality_score` (Arc-H merge gate) — lives in `src/github_repo_auditor/context_quality.py`, a separate code path **not** in the snapshot. - Full 132-repo run — invocation #2 after the pilot is hand-validated. - Auditing mechanical claims (`has_tests`, `has_ci`, staleness, …) — a script reproduces those @@ -312,7 +312,7 @@ operator decision. Closed the loop: fixed the README blindness, then used this audit as the regression test. **The fix** wires the dormant `readme_text` parameter of `analyze_project_context` -(`src/portfolio_context_contract.py`): each presence boolean is now "documented in the primary +(`src/github_repo_auditor/portfolio_context_contract.py`): each presence boolean is now "documented in the primary file **OR** the top-level README"; the `context_quality == "none"` gate also considers the README. Primary-file *identity* is unchanged (surgical). Added `tests/test_portfolio_context_contract.py` (5 tests; the function previously had **no** direct diff --git a/docs/plans/2026-06-14-portfolio-attention-reconciliation.md b/docs/plans/2026-06-14-portfolio-attention-reconciliation.md index 7a76b474..d8b53456 100644 --- a/docs/plans/2026-06-14-portfolio-attention-reconciliation.md +++ b/docs/plans/2026-06-14-portfolio-attention-reconciliation.md @@ -20,7 +20,7 @@ This note records the narrow truth update from the 2026-06-14 portfolio attentio The normal publish command should be used when Notion context is available: ```sh -uv run python -m src.cli report saagpatel --portfolio-truth +uv run python -m github_repo_auditor.cli report saagpatel --portfolio-truth ``` Then confirm: @@ -38,7 +38,7 @@ Expected state: ## 2026-06-14 Verification Result -- `uv run python -m src.cli report saagpatel --portfolio-truth` was attempted but refused to publish because `NOTION_TOKEN` was not present and the current `output/portfolio-truth-latest.json` has 137 Notion context rows. The canonical latest snapshot was not replaced. +- `uv run python -m github_repo_auditor.cli report saagpatel --portfolio-truth` was attempted but refused to publish because `NOTION_TOKEN` was not present and the current `output/portfolio-truth-latest.json` has 137 Notion context rows. The canonical latest snapshot was not replaced. - A non-publishing build with `include_notion=False` validated the catalog logic: `bridge-db`, `notification-hub`, and `AIGCCore` all resolved to `category=infrastructure` and `attention_state=active-infra`; snapshot validation reported no warnings. - `uv run pytest -q tests/test_portfolio_truth.py tests/test_catalog_validator.py`: 56 passed, 1 deprecation warning from a legacy CLI invocation test. - `uv run ruff check .`: passed. @@ -46,7 +46,7 @@ Expected state: ## 2026-06-19 Publish Result -- `uv run python -m src.cli report saagpatel --portfolio-truth` was rerun after Notion context became available. +- `uv run python -m github_repo_auditor.cli report saagpatel --portfolio-truth` was rerun after Notion context became available. - The regenerated latest snapshot preserved `source_summary.notion_context_rows=137`, reported `warnings=[]`, and was generated at `2026-06-19T04:36:19.383106+00:00`. - `bridge-db`: `category=infrastructure`, `attention_state=active-infra`, `notion_current_state=Shipped`. - `notification-hub`: `category=infrastructure`, `attention_state=active-infra`, `notion_current_state=Shipped`. diff --git a/docs/plans/2026-06-19-notion-projection-reconciliation.md b/docs/plans/2026-06-19-notion-projection-reconciliation.md index de5145e9..c41e3a09 100644 --- a/docs/plans/2026-06-19-notion-projection-reconciliation.md +++ b/docs/plans/2026-06-19-notion-projection-reconciliation.md @@ -141,7 +141,7 @@ operator decision before truth scope expands. ## Canonical Remote Repair -Implemented in `src/portfolio_truth_sources.py`: +Implemented in `src/github_repo_auditor/portfolio_truth_sources.py`: - Portfolio truth now inspects all configured GitHub fetch remotes instead of only `origin`. @@ -160,7 +160,7 @@ Focused regression tests were added in `tests/test_portfolio_truth.py` for: Verification: -- `uv run ruff check src/portfolio_truth_sources.py tests/test_portfolio_truth.py tests/test_portfolio_truth_sources.py` +- `uv run ruff check src/github_repo_auditor/portfolio_truth_sources.py tests/test_portfolio_truth.py tests/test_portfolio_truth_sources.py` - `PYTHONDONTWRITEBYTECODE=1 uv run pytest tests/test_portfolio_truth.py::test_git_remote_full_name_prefers_canonical_remote tests/test_portfolio_truth.py::test_git_remote_full_name_prefers_matching_public_remote_for_archive_origin tests/test_portfolio_truth.py::test_git_remote_full_name_keeps_normal_origin tests/test_portfolio_truth.py::test_extract_github_full_name_uses_exact_github_host tests/test_portfolio_truth.py::test_git_default_branch_reads_local_origin_head tests/test_portfolio_truth.py::test_git_default_branch_keeps_multi_segment_branch tests/test_portfolio_truth.py::test_git_default_branch_empty_when_origin_head_unset -q` - `PYTHONDONTWRITEBYTECODE=1 uv run pytest tests/test_portfolio_truth.py tests/test_portfolio_truth_sources.py -q` - `PYTHONDONTWRITEBYTECODE=1 python3 scripts/verify.py --run --id cross-system-smoke:contract-conformance --agent codex` diff --git a/docs/portfolio-decision-contract.md b/docs/portfolio-decision-contract.md index 9b80f44d..35c9a5d1 100644 --- a/docs/portfolio-decision-contract.md +++ b/docs/portfolio-decision-contract.md @@ -35,7 +35,7 @@ The machine-readable shape is from the same PortfolioTruth generation: ```bash -python -m src.portfolio_decision_queue \ +python -m github_repo_auditor.portfolio_decision_queue \ --truth output/portfolio-truth-latest.json \ --previous-digest output/portfolio-decision-digest-latest.json \ --format json diff --git a/docs/release-gates.md b/docs/release-gates.md index 28278867..5076eab4 100644 --- a/docs/release-gates.md +++ b/docs/release-gates.md @@ -15,8 +15,8 @@ Mutation testing is scoped to the two files that guard automated actions: | File | Why gated | |------|-----------| -| `src/auto_apply.py` | Trust-bar gating — controls which repos receive automated writes | -| `src/scorer.py` | Scoring/tier logic — drives completeness tiers and portfolio grades | +| `src/github_repo_auditor/auto_apply.py` | Trust-bar gating — controls which repos receive automated writes | +| `src/github_repo_auditor/scorer.py` | Scoring/tier logic — drives completeness tiers and portfolio grades | ### Required threshold @@ -31,34 +31,32 @@ make release-gate Or manually: ```bash -rm -rf .mutmut-cache mutants/ -python3.13 -m mutmut run +PYTHONPATH=src uv run --extra dev --python 3.13 mutmut run +uv run --no-sync python scripts/check_mutation_score.py --minimum 0.85 ``` -Query results directly (the `mutmut results` command crashes on Python 3.13): +The score checker reads mutmut 3's JSON metadata, excludes timeouts from the +denominator, and fails closed on unchecked, skipped, no-test, suspicious, +interrupted, or otherwise unknown results. + +Inspect individual results when needed: ```bash -python3.13 -c " -import sqlite3 -conn = sqlite3.connect('.mutmut-cache') -rows = conn.execute('SELECT status, count(*) FROM Mutant GROUP BY status').fetchall() -for r in rows: print(r) -killed = next(r[1] for r in rows if r[0] == 'ok_killed') -survived = next((r[1] for r in rows if r[0] == 'bad_survived'), 0) -print(f'Kill rate: {killed / (killed + survived):.1%}') -" +PYTHONPATH=src uv run --no-sync mutmut results --all true ``` ### Setup requirements -mutmut 2.x is incompatible with Python 3.14 (pony ORM `deepcopy` crash). Use Python 3.13: +The mutation gate uses the locked mutmut 3.x development dependency and Python +3.13: ```bash -python3.13 -m pip install 'mutmut>=2.0,<3.0' -python3.13 -m pip install -e ".[dev,config]" +uv sync --extra dev --python 3.13 --locked ``` -mutmut 3.x is incompatible with this project's `src.` layout (rejects module names starting with `src.`). The locked version constraint in `pyproject.toml` (`mutmut>=2.5` under `[tool.mutmut]`) documents this. +The project uses a conventional source layout: `src/` is the source root and +`github_repo_auditor` is the import package. This keeps mutmut's path-derived +keys aligned with the module names recorded by the test suite. ### Configuration @@ -66,16 +64,24 @@ mutmut 3.x is incompatible with this project's `src.` layout (rejects module nam ```toml [tool.mutmut] -paths_to_mutate = "src/auto_apply.py,src/scorer.py" -runner = "python3.13 -m pytest -q -p no:cacheprovider -x tests/test_auto_apply.py tests/test_scorer.py" -tests_dir = "tests/" +source_paths = ["src"] +only_mutate = ["src/github_repo_auditor/auto_apply.py", "src/github_repo_auditor/scorer.py"] +pytest_add_cli_args = ["-p", "no:cacheprovider"] +pytest_add_cli_args_test_selection = [ + "tests/test_auto_apply.py", + "tests/test_scorer.py", +] ``` -### Equivalent mutants +### Historical equivalent-mutant notes + +The IDs below came from the earlier mutmut 2.x baseline. Treat them as review +notes, not current identifiers; regenerate and reclassify them after a full +mutmut 3.x gate. The following survivors are confirmed equivalent mutants — behavioral tests cannot distinguish them: -**src/auto_apply.py** +**src/github_repo_auditor/auto_apply.py** | ID | Line | Pattern | Why equivalent | |----|------|---------|----------------| @@ -85,7 +91,7 @@ The following survivors are confirmed equivalent mutants — behavioral tests ca | 75, 80 | 92–93 | `or "XXXX"` in get_approved_manual_campaigns | Mutated default never equals the string being compared | | 106 | 132 | `or "XXXX"` in filter_trusted_repo_actions | Same pattern | -**src/scorer.py** +**src/github_repo_auditor/scorer.py** | ID | Line | Pattern | Why equivalent | |----|------|---------|----------------| @@ -101,12 +107,12 @@ The following survivors are confirmed equivalent mutants — behavioral tests ca | 251 | 136 | `>= 0.5` vs `> 0.5` | Score exactly 0.5 yields "functional" tier anyway (not "shipped"), so cap doesn't fire | | 304 | 213 | `>= 0.3` vs `> 0.3` for mid-tier boundary | Exact 0.3 shipped_ratio is rare in test scenarios | -### Current kill rates (last measured: 2026-05-10) +### Historical kill rates (last measured: 2026-05-10) | File | Mutants | Killed | Survived | Kill Rate | |------|---------|--------|----------|-----------| -| src/auto_apply.py | ~155 | ~146 | ~9 | ~94% | -| src/scorer.py | ~200 | ~182 | ~16 | ~92% | +| src/github_repo_auditor/auto_apply.py | ~155 | ~146 | ~9 | ~94% | +| src/github_repo_auditor/scorer.py | ~200 | ~182 | ~16 | ~92% | | **Combined** | **354** | **328** | **25** | **92.9%** | (1 timeout excluded from denominator; 1 suspicious counted as killed) @@ -170,7 +176,7 @@ checklist. ## Web UI Gate (scope: audit serve) -Run when any change touches `src/serve/` or `tests/test_serve.py`. +Run when any change touches `src/github_repo_auditor/serve/` or `tests/test_serve.py`. ### Steps diff --git a/docs/workbook-template-maintenance.md b/docs/workbook-template-maintenance.md index 4cb6ebc5..e2fdc687 100644 --- a/docs/workbook-template-maintenance.md +++ b/docs/workbook-template-maintenance.md @@ -3,7 +3,7 @@ The Excel workbook now supports two render modes: - `standard`: uses the stable code-generated workbook path, is the CLI default, and is the recommended automation mode -- `template`: hydrates the committed workbook template in `assets/excel/analyst-template.xlsx` +- `template`: hydrates the committed workbook template in `src/github_repo_auditor/assets/excel/analyst-template.xlsx` ## Ownership Boundary diff --git a/fixtures/contracts/evidence-freshness-conformance-v1/manifest.json b/fixtures/contracts/evidence-freshness-conformance-v1/manifest.json index 541d5477..cbdb0dca 100644 --- a/fixtures/contracts/evidence-freshness-conformance-v1/manifest.json +++ b/fixtures/contracts/evidence-freshness-conformance-v1/manifest.json @@ -44,7 +44,7 @@ "owner": { "artifact_path": "fixtures/contracts/evidence-freshness-conformance-v1/vectors.json", "artifact_sha256": "3111e2dd43b12f74481450b4ad07e7b8f285ff9b5e6931fd1ea3ded521fc0e7c", - "generator": "src.evidence_freshness_conformance:build_vectors", + "generator": "github_repo_auditor.evidence_freshness_conformance:build_vectors", "manifest_path": "fixtures/contracts/evidence-freshness-conformance-v1/manifest.json", "repository": "saagpatel/GithubRepoAuditor" }, diff --git a/fixtures/contracts/operator-control-plane-v1/manifest.json b/fixtures/contracts/operator-control-plane-v1/manifest.json index dd8b66db..4065530c 100644 --- a/fixtures/contracts/operator-control-plane-v1/manifest.json +++ b/fixtures/contracts/operator-control-plane-v1/manifest.json @@ -63,7 +63,7 @@ "producer": { "artifact_path": "fixtures/contracts/portable-consumers-v1/portfolio-truth.json", "artifact_sha256": "e79ca6b203ff58b8b6ffbb01e778305887dfba4e4caf66334defea6ccc1713df", - "generator": "src.portfolio_truth_contract_fixture:build_portable_contract_fixture", + "generator": "github_repo_auditor.portfolio_truth_contract_fixture:build_portable_contract_fixture", "manifest_path": "fixtures/contracts/operator-control-plane-v1/manifest.json", "repository": "saagpatel/GithubRepoAuditor" } diff --git a/fixtures/contracts/portfolio-command-center-v1/manifest.json b/fixtures/contracts/portfolio-command-center-v1/manifest.json index e35fd44f..18c5c6d2 100644 --- a/fixtures/contracts/portfolio-command-center-v1/manifest.json +++ b/fixtures/contracts/portfolio-command-center-v1/manifest.json @@ -25,7 +25,7 @@ "producer": { "artifact_path": "fixtures/contracts/portfolio-command-center-v1/portfolio-truth.json", "artifact_sha256": "3838ee7bb814170691bebe5506a9f55fefa1315b9436f4f24c4c041409f0b258", - "generator": "src.portfolio_truth_contract_fixture:build_contract_fixture", + "generator": "github_repo_auditor.portfolio_truth_contract_fixture:build_contract_fixture", "manifest_path": "fixtures/contracts/portfolio-command-center-v1/manifest.json", "repository": "saagpatel/GithubRepoAuditor" } diff --git a/fixtures/contracts/public-site-projection-v1/manifest.json b/fixtures/contracts/public-site-projection-v1/manifest.json index fe80b130..55e1c6ae 100644 --- a/fixtures/contracts/public-site-projection-v1/manifest.json +++ b/fixtures/contracts/public-site-projection-v1/manifest.json @@ -83,7 +83,7 @@ "producer": { "artifact_path": "fixtures/contracts/portable-consumers-v1/portfolio-truth.json", "artifact_sha256": "e79ca6b203ff58b8b6ffbb01e778305887dfba4e4caf66334defea6ccc1713df", - "generator": "src.portfolio_truth_contract_fixture:build_portable_contract_fixture", + "generator": "github_repo_auditor.portfolio_truth_contract_fixture:build_portable_contract_fixture", "manifest_path": "fixtures/contracts/public-site-projection-v1/manifest.json", "repository": "saagpatel/GithubRepoAuditor" } diff --git a/pyproject.toml b/pyproject.toml index e61682f8..938de7fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ dev = [ # is handled as its own cleanup change. "ruff>=0.4,<0.16", "mypy>=1.10", - "mutmut>=2.5", + "mutmut>=3.6,<4", "responses>=0.25", "types-PyYAML>=6.0", ] @@ -81,16 +81,26 @@ Changelog = "https://github.com/saagpatel/GithubRepoAuditor/blob/main/CHANGELOG. Documentation = "https://github.com/saagpatel/GithubRepoAuditor#readme" [project.scripts] -audit = "src.cli:main" -operator-os-seam-linter = "src.operator_os_seam_linter:main" -operator-os-producer-preflight = "src.producer_preflight:main" +audit = "github_repo_auditor.cli:main" +operator-os-seam-linter = "github_repo_auditor.operator_os_seam_linter:main" +operator-os-producer-preflight = "github_repo_auditor.producer_preflight:main" [tool.setuptools.packages.find] -where = ["."] -include = ["src*"] +where = ["src"] +include = ["github_repo_auditor*"] [tool.setuptools.package-data] -"src" = ["*.json", "*.yaml", "*.yml", "*.html", "*.jinja2", "*.j2"] +"github_repo_auditor" = [ + "*.json", + "*.yaml", + "*.yml", + "*.html", + "*.jinja2", + "*.j2", + "assets/excel/*.xlsx", + "serve/static/*.css", + "serve/templates/*.html", +] [tool.pytest.ini_options] testpaths = ["tests"] @@ -99,9 +109,13 @@ python_classes = ["Test*"] python_functions = ["test_*"] [tool.mutmut] -paths_to_mutate = "src/auto_apply.py,src/scorer.py" -runner = "python3.13 -m pytest -q -p no:cacheprovider -x tests/test_auto_apply.py tests/test_scorer.py" -tests_dir = "tests/" +source_paths = ["src"] +only_mutate = ["src/github_repo_auditor/auto_apply.py", "src/github_repo_auditor/scorer.py"] +pytest_add_cli_args = ["-p", "no:cacheprovider"] +pytest_add_cli_args_test_selection = [ + "tests/test_auto_apply.py", + "tests/test_scorer.py", +] [tool.ruff] target-version = "py311" diff --git a/scripts/_bootstrap.py b/scripts/_bootstrap.py index a93e12c0..57793ee9 100644 --- a/scripts/_bootstrap.py +++ b/scripts/_bootstrap.py @@ -4,11 +4,12 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] +SOURCE_ROOT = ROOT / "src" def ensure_project_root() -> Path: """Make repo-local imports work when scripts are run directly.""" - root_text = str(ROOT) - if root_text not in sys.path: - sys.path.insert(0, root_text) + source_root_text = str(SOURCE_ROOT) + if source_root_text not in sys.path: + sys.path.insert(0, source_root_text) return ROOT diff --git a/scripts/benchmark_large_portfolio.py b/scripts/benchmark_large_portfolio.py index 7935feca..58e28113 100644 --- a/scripts/benchmark_large_portfolio.py +++ b/scripts/benchmark_large_portfolio.py @@ -12,9 +12,9 @@ def _load_export_tools() -> tuple[object, object, object]: - from src.excel_export import export_excel - from src.report_enrichment import build_score_explanation - from src.web_export import export_html_dashboard + from github_repo_auditor.excel_export import export_excel + from github_repo_auditor.report_enrichment import build_score_explanation + from github_repo_auditor.web_export import export_html_dashboard return export_excel, build_score_explanation, export_html_dashboard diff --git a/scripts/build_codex_restart_packet.py b/scripts/build_codex_restart_packet.py index 563699c4..e762e780 100644 --- a/scripts/build_codex_restart_packet.py +++ b/scripts/build_codex_restart_packet.py @@ -4,11 +4,11 @@ import sys from pathlib import Path -sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) def _main() -> int: - from src.codex_restart_packet import main + from github_repo_auditor.codex_restart_packet import main return main() diff --git a/scripts/build_demo_artifacts.py b/scripts/build_demo_artifacts.py index 50a9fcc6..7f350193 100644 --- a/scripts/build_demo_artifacts.py +++ b/scripts/build_demo_artifacts.py @@ -11,17 +11,17 @@ def _load_demo_tools() -> tuple[object, object, object, object, object, object, object]: - from src.excel_export import export_excel - from src.operator_control_center import ( + from github_repo_auditor.excel_export import export_excel + from github_repo_auditor.operator_control_center import ( control_center_artifact_payload, render_control_center_markdown, ) - from src.report_enrichment import ( + from github_repo_auditor.report_enrichment import ( build_run_change_counts, build_run_change_summary, build_score_explanation, ) - from src.web_export import export_html_dashboard + from github_repo_auditor.web_export import export_html_dashboard return ( control_center_artifact_payload, @@ -195,7 +195,7 @@ def _write_demo_command_center_artifacts() -> None: consumer needs portfolio-scale breadth and a current, receipt-backed security contract, neither of which a three-repo report can demonstrate. """ - from src.demo_portfolio import ( + from github_repo_auditor.demo_portfolio import ( build_proposals, build_security_burndown, build_snapshot, @@ -203,7 +203,7 @@ def _write_demo_command_center_artifacts() -> None: fixture_generated_at, history_snapshots, ) - from src.portfolio_truth_types import TRUTH_LATEST_FILENAME + from github_repo_auditor.portfolio_truth_types import TRUTH_LATEST_FILENAME # Prefix-discovered artifacts from an earlier generation would still be # picked up by the consumer, so clear them before writing the new set. @@ -224,8 +224,8 @@ def _write_demo_command_center_artifacts() -> None: def _write_demo_warehouse(report_data: dict[str, Any], report_path) -> None: - from src.models import AnalyzerResult, AuditReport, RepoAudit, RepoMetadata - from src.warehouse import WAREHOUSE_FILENAME, write_warehouse_snapshot + from github_repo_auditor.models import AnalyzerResult, AuditReport, RepoAudit, RepoMetadata + from github_repo_auditor.warehouse import WAREHOUSE_FILENAME, write_warehouse_snapshot generated_at = _parse_generated_at(report_data.get("generated_at")) warehouse_path = OUTPUT_DIR / WAREHOUSE_FILENAME diff --git a/scripts/check_mutation_score.py b/scripts/check_mutation_score.py new file mode 100644 index 00000000..4aab9df0 --- /dev/null +++ b/scripts/check_mutation_score.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +"""Fail closed when mutmut 3 results do not satisfy the release threshold.""" + +from __future__ import annotations + +import argparse +from collections import Counter +import json +from pathlib import Path +from typing import Iterable + + +KILLED_EXIT_CODES = {1, 3, 37} +SURVIVED_EXIT_CODES = {0} +TIMEOUT_EXIT_CODES = {-24, 24, 36, 152, 255} + + +def load_exit_codes(mutants_dir: Path) -> list[int | None]: + codes: list[int | None] = [] + for meta_path in sorted(mutants_dir.rglob("*.meta")): + payload = json.loads(meta_path.read_text(encoding="utf-8")) + results = payload.get("exit_code_by_key") + if not isinstance(results, dict): + raise ValueError(f"{meta_path} has no exit_code_by_key object") + for value in results.values(): + if value is not None and not isinstance(value, int): + raise ValueError(f"{meta_path} contains a non-integer exit code") + codes.append(value) + return codes + + +def classify_exit_codes(codes: Iterable[int | None]) -> Counter[str]: + counts: Counter[str] = Counter() + for code in codes: + if code in KILLED_EXIT_CODES: + counts["killed"] += 1 + elif code in SURVIVED_EXIT_CODES: + counts["survived"] += 1 + elif code in TIMEOUT_EXIT_CODES: + counts["timeout"] += 1 + elif code is None: + counts["unchecked"] += 1 + else: + counts["invalid"] += 1 + return counts + + +def evaluate_score(counts: Counter[str], minimum: float) -> tuple[bool, float]: + denominator = counts["killed"] + counts["survived"] + score = counts["killed"] / denominator if denominator else 0.0 + complete = counts["unchecked"] == 0 and counts["invalid"] == 0 + return complete and denominator > 0 and score >= minimum, score + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--mutants-dir", type=Path, default=Path("mutants")) + parser.add_argument("--minimum", type=float, default=0.85) + args = parser.parse_args(argv) + + if not 0.0 <= args.minimum <= 1.0: + parser.error("--minimum must be between 0 and 1") + + try: + codes = load_exit_codes(args.mutants_dir) + except (OSError, ValueError, json.JSONDecodeError) as exc: + print(f"Mutation gate could not read results: {exc}") + return 2 + + if not codes: + print(f"Mutation gate found no results under {args.mutants_dir}") + return 2 + + counts = classify_exit_codes(codes) + passed, score = evaluate_score(counts, args.minimum) + print( + "Mutation gate: " + f"killed={counts['killed']} survived={counts['survived']} " + f"timeouts={counts['timeout']} unchecked={counts['unchecked']} " + f"invalid={counts['invalid']} score={score:.1%} " + f"threshold={args.minimum:.1%}" + ) + return 0 if passed else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/generate_evidence_freshness_conformance.py b/scripts/generate_evidence_freshness_conformance.py index 2e51c6bb..a9a01f87 100644 --- a/scripts/generate_evidence_freshness_conformance.py +++ b/scripts/generate_evidence_freshness_conformance.py @@ -8,10 +8,11 @@ from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] -if str(REPO_ROOT) not in sys.path: - sys.path.insert(0, str(REPO_ROOT)) +SOURCE_ROOT = REPO_ROOT / "src" +if str(SOURCE_ROOT) not in sys.path: + sys.path.insert(0, str(SOURCE_ROOT)) -from src.evidence_freshness_conformance import ( # noqa: E402 +from github_repo_auditor.evidence_freshness_conformance import ( # noqa: E402 MANIFEST_RELATIVE_PATH, VECTORS_RELATIVE_PATH, manifest_bytes, diff --git a/scripts/generate_excel_template.py b/scripts/generate_excel_template.py index 33c75d79..a4433cb0 100644 --- a/scripts/generate_excel_template.py +++ b/scripts/generate_excel_template.py @@ -9,7 +9,7 @@ def _load_template_constants() -> tuple[object, object, object]: - from src.excel_template import DEFAULT_TEMPLATE_PATH, TEMPLATE_INFO_SHEET, TEMPLATE_SHEETS + from github_repo_auditor.excel_template import DEFAULT_TEMPLATE_PATH, TEMPLATE_INFO_SHEET, TEMPLATE_SHEETS return DEFAULT_TEMPLATE_PATH, TEMPLATE_INFO_SHEET, TEMPLATE_SHEETS diff --git a/scripts/generate_portfolio_truth_contract_fixture.py b/scripts/generate_portfolio_truth_contract_fixture.py index e7adfabc..75ce2122 100644 --- a/scripts/generate_portfolio_truth_contract_fixture.py +++ b/scripts/generate_portfolio_truth_contract_fixture.py @@ -8,10 +8,11 @@ from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] -if str(REPO_ROOT) not in sys.path: - sys.path.insert(0, str(REPO_ROOT)) +SOURCE_ROOT = REPO_ROOT / "src" +if str(SOURCE_ROOT) not in sys.path: + sys.path.insert(0, str(SOURCE_ROOT)) -from src.portfolio_truth_contract_fixture import ( # noqa: E402 +from github_repo_auditor.portfolio_truth_contract_fixture import ( # noqa: E402 CONSUMER_PROFILE_MANIFEST_PATHS, FIXTURE_RELATIVE_PATH, MANIFEST_RELATIVE_PATH, @@ -23,7 +24,7 @@ manifest_bytes, portable_fixture_bytes, ) -from src.portfolio_truth_validate import validate_truth_snapshot_payload # noqa: E402 +from github_repo_auditor.portfolio_truth_validate import validate_truth_snapshot_payload # noqa: E402 def _expected_artifacts() -> tuple[tuple[Path, bytes], ...]: diff --git a/scripts/operator_os_seam_linter.py b/scripts/operator_os_seam_linter.py index ed811756..1fae6da5 100644 --- a/scripts/operator_os_seam_linter.py +++ b/scripts/operator_os_seam_linter.py @@ -4,11 +4,11 @@ import sys from pathlib import Path -sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) def _main() -> int: - from src.operator_os_seam_linter import main + from github_repo_auditor.operator_os_seam_linter import main return main() diff --git a/scripts/presence-claims-audit.workflow.js b/scripts/presence-claims-audit.workflow.js index d83bee2d..d08d731a 100644 --- a/scripts/presence-claims-audit.workflow.js +++ b/scripts/presence-claims-audit.workflow.js @@ -2,7 +2,7 @@ // External audit of the snapshot's SIX presence claims against on-disk ground truth. // Stage 2 (verifier fan-out, Haiku — judges all 6 claims per repo in one read) // + Stage 3 (deterministic per-(repo,claim) tally) + Stage 4 (Sonnet synthesis). -// args = output of `python -m src.run_instructions_audit` ({ generated_at, workspace_root, records, errors }), +// args = output of `python -m github_repo_auditor.run_instructions_audit` ({ generated_at, workspace_root, records, errors }), // where each record carries snapshot_claims{} and tool_today{} dicts over the 6 fields. export const meta = { name: "presence-claims-audit", @@ -97,7 +97,7 @@ function verifierPrompt(rec) { ].join("\n"); } -// --- Stage 3 tally logic (mirror of src/run_instructions_audit.py) --- +// --- Stage 3 tally logic (mirror of src/github_repo_auditor/run_instructions_audit.py) --- function assignBucket(toolToday, verdict, inPrimary) { if (toolToday === verdict) return verdict ? "agree_present" : "agree_absent"; if (verdict && !toolToday) diff --git a/scripts/run-instructions-audit.workflow.js b/scripts/run-instructions-audit.workflow.js index 902b6ad3..a539ebc5 100644 --- a/scripts/run-instructions-audit.workflow.js +++ b/scripts/run-instructions-audit.workflow.js @@ -1,7 +1,7 @@ // scripts/run-instructions-audit.workflow.js // External audit of the snapshot's run_instructions_present claim. // Stage 2 (verifier fan-out, Haiku) + Stage 3 (deterministic tally) + Stage 4 (Sonnet synthesis). -// args = output of `python -m src.run_instructions_audit` ({ generated_at, workspace_root, records, errors }). +// args = output of `python -m github_repo_auditor.run_instructions_audit` ({ generated_at, workspace_root, records, errors }). export const meta = { name: "run-instructions-audit", description: @@ -56,7 +56,7 @@ function verifierPrompt(rec) { ].join("\n"); } -// --- Stage 3 tally logic (mirror of src/run_instructions_audit.py) --- +// --- Stage 3 tally logic (mirror of src/github_repo_auditor/run_instructions_audit.py) --- function assignBucket(toolToday, verdict, inPrimary) { if (toolToday === verdict) return verdict ? "agree_present" : "agree_absent"; if (verdict && !toolToday) diff --git a/scripts/validate_proof_package.py b/scripts/validate_proof_package.py index 9a394c12..b0c0b623 100644 --- a/scripts/validate_proof_package.py +++ b/scripts/validate_proof_package.py @@ -15,15 +15,16 @@ from typing import Any # Repo-local imports must work whether this runs as a script or is imported by -# the test suite, so the project root goes on the path before src/ is touched. +# the test suite, so the source root goes on the path first. _PROJECT_ROOT = Path(__file__).resolve().parents[1] -if str(_PROJECT_ROOT) not in sys.path: - sys.path.insert(0, str(_PROJECT_ROOT)) +_SOURCE_ROOT = _PROJECT_ROOT / "src" +if str(_SOURCE_ROOT) not in sys.path: + sys.path.insert(0, str(_SOURCE_ROOT)) -from src.portfolio_truth_types import ( # noqa: E402 +from github_repo_auditor.portfolio_truth_types import ( # noqa: E402 SCHEMA_VERSION as PRODUCER_TRUTH_SCHEMA_VERSION, ) -from src.portfolio_truth_types import TRUTH_LATEST_FILENAME # noqa: E402 +from github_repo_auditor.portfolio_truth_types import TRUTH_LATEST_FILENAME # noqa: E402 ALLOWED_STATUSES = {"passed", "failed", "partial", "stale"} # Portfolio Command Center reads a snapshot older than this as aging, then diff --git a/src/__main__.py b/src/__main__.py deleted file mode 100644 index 679fb5e5..00000000 --- a/src/__main__.py +++ /dev/null @@ -1,3 +0,0 @@ -from src.cli import main - -main() diff --git a/src/app/__init__.py b/src/app/__init__.py deleted file mode 100644 index 282ec44b..00000000 --- a/src/app/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Application-layer mode handlers dispatched by :mod:`src.cli`.""" diff --git a/src/__init__.py b/src/github_repo_auditor/__init__.py similarity index 100% rename from src/__init__.py rename to src/github_repo_auditor/__init__.py diff --git a/src/github_repo_auditor/__main__.py b/src/github_repo_auditor/__main__.py new file mode 100644 index 00000000..28618f94 --- /dev/null +++ b/src/github_repo_auditor/__main__.py @@ -0,0 +1,3 @@ +from github_repo_auditor.cli import main + +main() diff --git a/src/action_sync_automation.py b/src/github_repo_auditor/action_sync_automation.py similarity index 98% rename from src/action_sync_automation.py rename to src/github_repo_auditor/action_sync_automation.py index 07e303f3..c3ef25f9 100644 --- a/src/action_sync_automation.py +++ b/src/github_repo_auditor/action_sync_automation.py @@ -2,9 +2,9 @@ from typing import Any -from src.action_sync_packets import EXECUTION_PRIORITY -from src.action_sync_readiness import CAMPAIGN_DISPLAY_ORDER, READINESS_PRIORITY -from src.terminology import ACTION_SYNC_CANONICAL_LABELS +from github_repo_auditor.action_sync_packets import EXECUTION_PRIORITY +from github_repo_auditor.action_sync_readiness import CAMPAIGN_DISPLAY_ORDER, READINESS_PRIORITY +from github_repo_auditor.terminology import ACTION_SYNC_CANONICAL_LABELS AUTOMATION_PRIORITY = { "approval-first": 0, diff --git a/src/action_sync_outcomes.py b/src/github_repo_auditor/action_sync_outcomes.py similarity index 99% rename from src/action_sync_outcomes.py rename to src/github_repo_auditor/action_sync_outcomes.py index bdf17e84..c54a44fe 100644 --- a/src/action_sync_outcomes.py +++ b/src/github_repo_auditor/action_sync_outcomes.py @@ -2,9 +2,9 @@ from typing import Any -from src.action_sync_readiness import CAMPAIGN_DISPLAY_ORDER -from src.ops_writeback import CAMPAIGN_DEFINITIONS -from src.warehouse import ( +from github_repo_auditor.action_sync_readiness import CAMPAIGN_DISPLAY_ORDER +from github_repo_auditor.ops_writeback import CAMPAIGN_DEFINITIONS +from github_repo_auditor.warehouse import ( load_latest_audit_runs, load_recent_action_runs, load_recent_campaign_drift_events, diff --git a/src/action_sync_packets.py b/src/github_repo_auditor/action_sync_packets.py similarity index 99% rename from src/action_sync_packets.py rename to src/github_repo_auditor/action_sync_packets.py index d86ca29d..74d1a751 100644 --- a/src/action_sync_packets.py +++ b/src/github_repo_auditor/action_sync_packets.py @@ -2,7 +2,7 @@ from typing import Any -from src.action_sync_readiness import ( +from github_repo_auditor.action_sync_readiness import ( CAMPAIGN_DISPLAY_ORDER, QUEUE_CAMPAIGN_PRIORITY, _github_projects_ready, @@ -11,7 +11,7 @@ _infer_campaign, _notion_ready, ) -from src.ops_writeback import build_campaign_bundle +from github_repo_auditor.ops_writeback import build_campaign_bundle EXECUTION_PRIORITY = { "review-drift": 0, diff --git a/src/action_sync_readiness.py b/src/github_repo_auditor/action_sync_readiness.py similarity index 99% rename from src/action_sync_readiness.py rename to src/github_repo_auditor/action_sync_readiness.py index 97d14fed..59ddc8a2 100644 --- a/src/action_sync_readiness.py +++ b/src/github_repo_auditor/action_sync_readiness.py @@ -2,7 +2,7 @@ from typing import Any -from src.ops_writeback import CAMPAIGN_DEFINITIONS, build_campaign_bundle +from github_repo_auditor.ops_writeback import CAMPAIGN_DEFINITIONS, build_campaign_bundle CAMPAIGN_DISPLAY_ORDER = [ "security-review", diff --git a/src/action_sync_tuning.py b/src/github_repo_auditor/action_sync_tuning.py similarity index 98% rename from src/action_sync_tuning.py rename to src/github_repo_auditor/action_sync_tuning.py index cddfe275..74ea7236 100644 --- a/src/action_sync_tuning.py +++ b/src/github_repo_auditor/action_sync_tuning.py @@ -2,10 +2,10 @@ from typing import Any -from src.action_sync_packets import EXECUTION_PRIORITY -from src.action_sync_readiness import CAMPAIGN_DISPLAY_ORDER, READINESS_PRIORITY -from src.ops_writeback import CAMPAIGN_DEFINITIONS -from src.warehouse import ( +from github_repo_auditor.action_sync_packets import EXECUTION_PRIORITY +from github_repo_auditor.action_sync_readiness import CAMPAIGN_DISPLAY_ORDER, READINESS_PRIORITY +from github_repo_auditor.ops_writeback import CAMPAIGN_DEFINITIONS +from github_repo_auditor.warehouse import ( load_campaign_outcomes, load_latest_audit_runs, load_recent_campaign_runs, diff --git a/src/analyst_views.py b/src/github_repo_auditor/analyst_views.py similarity index 99% rename from src/analyst_views.py rename to src/github_repo_auditor/analyst_views.py index 18a26c5d..5e67707d 100644 --- a/src/analyst_views.py +++ b/src/github_repo_auditor/analyst_views.py @@ -5,7 +5,7 @@ from collections import defaultdict from pathlib import Path -from src.warehouse import WAREHOUSE_FILENAME +from github_repo_auditor.warehouse import WAREHOUSE_FILENAME def _normalize_profile_name(report_data: dict, profile_name: str | None) -> str: diff --git a/src/analyzer_cache.py b/src/github_repo_auditor/analyzer_cache.py similarity index 98% rename from src/analyzer_cache.py rename to src/github_repo_auditor/analyzer_cache.py index 01a55428..65917592 100644 --- a/src/analyzer_cache.py +++ b/src/github_repo_auditor/analyzer_cache.py @@ -196,7 +196,7 @@ def reconcile( "ok": bool, } """ - from src.analyzers.base import BaseAnalyzer + from github_repo_auditor.analyzers.base import BaseAnalyzer checked = 0 matched = 0 @@ -237,7 +237,7 @@ def reconcile( for analyzer_name, fresh_dict in fresh_by_name.items(): # Determine inputs_hash for cache lookup. We re-derive it from # the analyzer instance matching by name. - from src.analyzers import ALL_ANALYZERS + from github_repo_auditor.analyzers import ALL_ANALYZERS analyzer_inst: BaseAnalyzer | None = next( (a for a in ALL_ANALYZERS if a.name == analyzer_name), None diff --git a/src/analyzers/__init__.py b/src/github_repo_auditor/analyzers/__init__.py similarity index 84% rename from src/analyzers/__init__.py rename to src/github_repo_auditor/analyzers/__init__.py index 5ac17b9a..e1fd9b53 100644 --- a/src/analyzers/__init__.py +++ b/src/github_repo_auditor/analyzers/__init__.py @@ -9,23 +9,23 @@ from pathlib import Path from typing import TYPE_CHECKING -from src.analyzers.activity import ActivityAnalyzer -from src.analyzers.base import BaseAnalyzer -from src.analyzers.cicd import CicdAnalyzer -from src.analyzers.code_quality import CodeQualityAnalyzer -from src.analyzers.community_profile import CommunityProfileAnalyzer -from src.analyzers.completeness import BuildReadinessAnalyzer, DocumentationAnalyzer -from src.analyzers.dependencies import DependenciesAnalyzer -from src.analyzers.description_analyzer import DescriptionAnalyzer -from src.analyzers.interest import InterestAnalyzer -from src.analyzers.readme import ReadmeAnalyzer -from src.analyzers.security import SecurityAnalyzer -from src.analyzers.structure import StructureAnalyzer -from src.analyzers.testing import TestingAnalyzer -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.analyzers.activity import ActivityAnalyzer +from github_repo_auditor.analyzers.base import BaseAnalyzer +from github_repo_auditor.analyzers.cicd import CicdAnalyzer +from github_repo_auditor.analyzers.code_quality import CodeQualityAnalyzer +from github_repo_auditor.analyzers.community_profile import CommunityProfileAnalyzer +from github_repo_auditor.analyzers.completeness import BuildReadinessAnalyzer, DocumentationAnalyzer +from github_repo_auditor.analyzers.dependencies import DependenciesAnalyzer +from github_repo_auditor.analyzers.description_analyzer import DescriptionAnalyzer +from github_repo_auditor.analyzers.interest import InterestAnalyzer +from github_repo_auditor.analyzers.readme import ReadmeAnalyzer +from github_repo_auditor.analyzers.security import SecurityAnalyzer +from github_repo_auditor.analyzers.structure import StructureAnalyzer +from github_repo_auditor.analyzers.testing import TestingAnalyzer +from github_repo_auditor.models import AnalyzerResult, RepoMetadata if TYPE_CHECKING: - from src.github_client import GitHubClient + from github_repo_auditor.github_client import GitHubClient logger = logging.getLogger(__name__) @@ -103,7 +103,7 @@ def run_with_cache( sbom_source: Forwarded to DependenciesAnalyzer when it is the active analyzer. ``"lockfile"`` (default) or ``"github"``. """ - from src.analyzer_cache import lookup, store + from github_repo_auditor.analyzer_cache import lookup, store inputs_hash: str | None = None if conn is not None: diff --git a/src/analyzers/activity.py b/src/github_repo_auditor/analyzers/activity.py similarity index 97% rename from src/analyzers/activity.py rename to src/github_repo_auditor/analyzers/activity.py index 694b7e5c..ed6957e1 100644 --- a/src/analyzers/activity.py +++ b/src/github_repo_auditor/analyzers/activity.py @@ -4,11 +4,11 @@ from pathlib import Path from typing import TYPE_CHECKING -from src.analyzers.base import BaseAnalyzer -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.analyzers.base import BaseAnalyzer +from github_repo_auditor.models import AnalyzerResult, RepoMetadata if TYPE_CHECKING: - from src.github_client import GitHubClient + from github_repo_auditor.github_client import GitHubClient MONTHS_6 = 180 # days MONTHS_12 = 365 diff --git a/src/analyzers/base.py b/src/github_repo_auditor/analyzers/base.py similarity index 91% rename from src/analyzers/base.py rename to src/github_repo_auditor/analyzers/base.py index e0269bfb..84ad816e 100644 --- a/src/analyzers/base.py +++ b/src/github_repo_auditor/analyzers/base.py @@ -4,10 +4,10 @@ from pathlib import Path from typing import TYPE_CHECKING -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.models import AnalyzerResult, RepoMetadata if TYPE_CHECKING: - from src.github_client import GitHubClient + from github_repo_auditor.github_client import GitHubClient class BaseAnalyzer(ABC): diff --git a/src/analyzers/cicd.py b/src/github_repo_auditor/analyzers/cicd.py similarity index 96% rename from src/analyzers/cicd.py rename to src/github_repo_auditor/analyzers/cicd.py index 71a3efa0..9127b35a 100644 --- a/src/analyzers/cicd.py +++ b/src/github_repo_auditor/analyzers/cicd.py @@ -3,8 +3,8 @@ import json from pathlib import Path -from src.analyzers.base import BaseAnalyzer -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.analyzers.base import BaseAnalyzer +from github_repo_auditor.models import AnalyzerResult, RepoMetadata ALT_CI_FILES = ( ".travis.yml", diff --git a/src/analyzers/code_quality.py b/src/github_repo_auditor/analyzers/code_quality.py similarity index 98% rename from src/analyzers/code_quality.py rename to src/github_repo_auditor/analyzers/code_quality.py index 69dff68f..868b74da 100644 --- a/src/analyzers/code_quality.py +++ b/src/github_repo_auditor/analyzers/code_quality.py @@ -5,11 +5,11 @@ from pathlib import Path from typing import TYPE_CHECKING -from src.analyzers.base import BaseAnalyzer -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.analyzers.base import BaseAnalyzer +from github_repo_auditor.models import AnalyzerResult, RepoMetadata if TYPE_CHECKING: - from src.github_client import GitHubClient + from github_repo_auditor.github_client import GitHubClient # Common entry points by language ENTRY_POINTS: dict[str | None, list[str]] = { diff --git a/src/analyzers/community_profile.py b/src/github_repo_auditor/analyzers/community_profile.py similarity index 95% rename from src/analyzers/community_profile.py rename to src/github_repo_auditor/analyzers/community_profile.py index 331871fc..33c3ca4f 100644 --- a/src/analyzers/community_profile.py +++ b/src/github_repo_auditor/analyzers/community_profile.py @@ -3,11 +3,11 @@ from pathlib import Path from typing import TYPE_CHECKING -from src.analyzers.base import BaseAnalyzer -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.analyzers.base import BaseAnalyzer +from github_repo_auditor.models import AnalyzerResult, RepoMetadata if TYPE_CHECKING: - from src.github_client import GitHubClient + from github_repo_auditor.github_client import GitHubClient # Health files checked by GitHub's community profile endpoint HEALTH_FILES = [ diff --git a/src/analyzers/completeness.py b/src/github_repo_auditor/analyzers/completeness.py similarity index 98% rename from src/analyzers/completeness.py rename to src/github_repo_auditor/analyzers/completeness.py index 336fb54d..aba954cb 100644 --- a/src/analyzers/completeness.py +++ b/src/github_repo_auditor/analyzers/completeness.py @@ -2,8 +2,8 @@ from pathlib import Path -from src.analyzers.base import BaseAnalyzer -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.analyzers.base import BaseAnalyzer +from github_repo_auditor.models import AnalyzerResult, RepoMetadata # Binary file extensions to skip when sampling for comments BINARY_EXTENSIONS = frozenset({ diff --git a/src/analyzers/dependencies.py b/src/github_repo_auditor/analyzers/dependencies.py similarity index 97% rename from src/analyzers/dependencies.py rename to src/github_repo_auditor/analyzers/dependencies.py index b54a599c..d60d6a14 100644 --- a/src/analyzers/dependencies.py +++ b/src/github_repo_auditor/analyzers/dependencies.py @@ -5,8 +5,8 @@ import logging from pathlib import Path -from src.analyzers.base import BaseAnalyzer -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.analyzers.base import BaseAnalyzer +from github_repo_auditor.models import AnalyzerResult, RepoMetadata logger = logging.getLogger(__name__) @@ -165,8 +165,8 @@ def analyze( # Libyears freshness (optional — requires network for registry queries) if found_manifests: try: - from src.cache import ResponseCache - from src.libyears import compute_libyears + from github_repo_auditor.cache import ResponseCache + from github_repo_auditor.libyears import compute_libyears cache = ResponseCache(ttl=86400) # 24hr for registries libyears_data = compute_libyears(repo_path, found_manifests, cache) diff --git a/src/analyzers/description_analyzer.py b/src/github_repo_auditor/analyzers/description_analyzer.py similarity index 94% rename from src/analyzers/description_analyzer.py rename to src/github_repo_auditor/analyzers/description_analyzer.py index c81bea46..111239e2 100644 --- a/src/analyzers/description_analyzer.py +++ b/src/github_repo_auditor/analyzers/description_analyzer.py @@ -1,11 +1,11 @@ -# src/analyzers/description_analyzer.py +# src/github_repo_auditor/analyzers/description_analyzer.py from __future__ import annotations import hashlib from pathlib import Path -from src.analyzers.base import BaseAnalyzer -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.analyzers.base import BaseAnalyzer +from github_repo_auditor.models import AnalyzerResult, RepoMetadata _LANG_KEYWORDS: dict[str, frozenset[str]] = { "Swift": frozenset( diff --git a/src/analyzers/interest.py b/src/github_repo_auditor/analyzers/interest.py similarity index 98% rename from src/analyzers/interest.py rename to src/github_repo_auditor/analyzers/interest.py index 5dbe3ce9..be49742c 100644 --- a/src/analyzers/interest.py +++ b/src/github_repo_auditor/analyzers/interest.py @@ -5,11 +5,11 @@ from pathlib import Path from typing import TYPE_CHECKING -from src.analyzers.base import BaseAnalyzer -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.analyzers.base import BaseAnalyzer +from github_repo_auditor.models import AnalyzerResult, RepoMetadata if TYPE_CHECKING: - from src.github_client import GitHubClient + from github_repo_auditor.github_client import GitHubClient # Languages considered "novel" for a personal portfolio — uncommon enough to signal curiosity NOVEL_LANGUAGES = frozenset({ diff --git a/src/analyzers/readme.py b/src/github_repo_auditor/analyzers/readme.py similarity index 98% rename from src/analyzers/readme.py rename to src/github_repo_auditor/analyzers/readme.py index 0ff53706..beb7cc33 100644 --- a/src/analyzers/readme.py +++ b/src/github_repo_auditor/analyzers/readme.py @@ -7,8 +7,8 @@ import time from pathlib import Path -from src.analyzers.base import BaseAnalyzer -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.analyzers.base import BaseAnalyzer +from github_repo_auditor.models import AnalyzerResult, RepoMetadata logger = logging.getLogger(__name__) diff --git a/src/analyzers/security.py b/src/github_repo_auditor/analyzers/security.py similarity index 98% rename from src/analyzers/security.py rename to src/github_repo_auditor/analyzers/security.py index 6474789e..88451e74 100644 --- a/src/analyzers/security.py +++ b/src/github_repo_auditor/analyzers/security.py @@ -5,11 +5,11 @@ from pathlib import Path from typing import TYPE_CHECKING -from src.analyzers.base import BaseAnalyzer -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.analyzers.base import BaseAnalyzer +from github_repo_auditor.models import AnalyzerResult, RepoMetadata if TYPE_CHECKING: - from src.github_client import GitHubClient + from github_repo_auditor.github_client import GitHubClient # Patterns that strongly suggest exposed secrets SECRET_PATTERNS: list[tuple[str, re.Pattern]] = [ diff --git a/src/analyzers/structure.py b/src/github_repo_auditor/analyzers/structure.py similarity index 97% rename from src/analyzers/structure.py rename to src/github_repo_auditor/analyzers/structure.py index 02af1a15..b85277f7 100644 --- a/src/analyzers/structure.py +++ b/src/github_repo_auditor/analyzers/structure.py @@ -3,8 +3,8 @@ import hashlib from pathlib import Path -from src.analyzers.base import BaseAnalyzer -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.analyzers.base import BaseAnalyzer +from github_repo_auditor.models import AnalyzerResult, RepoMetadata CONFIG_FILES = { "package.json", diff --git a/src/analyzers/testing.py b/src/github_repo_auditor/analyzers/testing.py similarity index 97% rename from src/analyzers/testing.py rename to src/github_repo_auditor/analyzers/testing.py index 15564081..95edd47a 100644 --- a/src/analyzers/testing.py +++ b/src/github_repo_auditor/analyzers/testing.py @@ -3,8 +3,8 @@ import json from pathlib import Path -from src.analyzers.base import BaseAnalyzer -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.analyzers.base import BaseAnalyzer +from github_repo_auditor.models import AnalyzerResult, RepoMetadata TEST_DIRS = ("test", "tests", "__tests__", "spec", "test_suite") diff --git a/src/api_checkout.py b/src/github_repo_auditor/api_checkout.py similarity index 98% rename from src/api_checkout.py rename to src/github_repo_auditor/api_checkout.py index e2f328ce..3166bbe2 100644 --- a/src/api_checkout.py +++ b/src/github_repo_auditor/api_checkout.py @@ -28,10 +28,10 @@ from pathlib import Path from typing import TYPE_CHECKING, Callable, Generator -from src.models import RepoMetadata +from github_repo_auditor.models import RepoMetadata if TYPE_CHECKING: - from src.github_client import GitHubClient + from github_repo_auditor.github_client import GitHubClient logger = logging.getLogger(__name__) diff --git a/src/api_only.py b/src/github_repo_auditor/api_only.py similarity index 95% rename from src/api_only.py rename to src/github_repo_auditor/api_only.py index 0f06eb44..5f57a82e 100644 --- a/src/api_only.py +++ b/src/github_repo_auditor/api_only.py @@ -24,14 +24,14 @@ import requests -from src.analyzers import run_all_analyzers -from src.api_checkout import materialize_api_checkout -from src.graphql_client import bulk_fetch_repos -from src.models import RepoAudit, RepoMetadata -from src.scorer import score_repo +from github_repo_auditor.analyzers import run_all_analyzers +from github_repo_auditor.api_checkout import materialize_api_checkout +from github_repo_auditor.graphql_client import bulk_fetch_repos +from github_repo_auditor.models import RepoAudit, RepoMetadata +from github_repo_auditor.scorer import score_repo if TYPE_CHECKING: - from src.github_client import GitHubClient + from github_repo_auditor.github_client import GitHubClient logger = logging.getLogger(__name__) diff --git a/src/github_repo_auditor/app/__init__.py b/src/github_repo_auditor/app/__init__.py new file mode 100644 index 00000000..d1f13c7f --- /dev/null +++ b/src/github_repo_auditor/app/__init__.py @@ -0,0 +1 @@ +"""Application-layer mode handlers dispatched by :mod:`github_repo_auditor.cli`.""" diff --git a/src/app/acknowledgments.py b/src/github_repo_auditor/app/acknowledgments.py similarity index 87% rename from src/app/acknowledgments.py rename to src/github_repo_auditor/app/acknowledgments.py index bf8fc750..cead14fb 100644 --- a/src/app/acknowledgments.py +++ b/src/github_repo_auditor/app/acknowledgments.py @@ -5,18 +5,18 @@ from pathlib import Path from typing import Any -from src.cli_output import print_info -from src.diff import diff_reports -from src.history import find_previous -from src.operator_acknowledgments import ( +from github_repo_auditor.cli_output import print_info +from github_repo_auditor.diff import diff_reports +from github_repo_auditor.history import find_previous +from github_repo_auditor.operator_acknowledgments import ( build_acknowledgment_record, find_matching_change, find_sibling_changes, load_acknowledgments, save_acknowledgment, ) -from src.recurring_review import MATERIALITY_THRESHOLDS, evaluate_material_changes -from src.report_state import load_latest_report +from github_repo_auditor.recurring_review import MATERIALITY_THRESHOLDS, evaluate_material_changes +from github_repo_auditor.report_state import load_latest_report def run_acknowledgment_capture_mode(args: Any, parser: Any) -> None: diff --git a/src/app/approval_center.py b/src/github_repo_auditor/app/approval_center.py similarity index 89% rename from src/app/approval_center.py rename to src/github_repo_auditor/app/approval_center.py index f3d805ff..4ffdb18a 100644 --- a/src/app/approval_center.py +++ b/src/github_repo_auditor/app/approval_center.py @@ -7,21 +7,21 @@ from pathlib import Path from typing import Any -from src.cli_output import print_info -from src.control_center_report_state import refresh_latest_report_state -from src.operator_approval_artifacts import write_approval_center_artifacts -from src.operator_approval_artifacts import ( +from github_repo_auditor.cli_output import print_info +from github_repo_auditor.control_center_report_state import refresh_latest_report_state +from github_repo_auditor.operator_approval_artifacts import write_approval_center_artifacts +from github_repo_auditor.operator_approval_artifacts import ( write_approval_receipt, write_followup_review_receipt, ) -from src.operator_prefs import load_rejection_events, post_process_approval_session -from src.approval_ledger import ( +from github_repo_auditor.operator_prefs import load_rejection_events, post_process_approval_session +from github_repo_auditor.approval_ledger import ( build_approval_followup_record, build_approval_record, load_approval_ledger_bundle, ) -from src.report_shared_artifacts import refresh_shared_artifacts_from_report -from src.warehouse import save_approval_followup_event, save_approval_record +from github_repo_auditor.report_shared_artifacts import refresh_shared_artifacts_from_report +from github_repo_auditor.warehouse import save_approval_followup_event, save_approval_record def _utcnow() -> datetime: diff --git a/src/app/auto_apply.py b/src/github_repo_auditor/app/auto_apply.py similarity index 90% rename from src/app/auto_apply.py rename to src/github_repo_auditor/app/auto_apply.py index 3fe968bb..95eef68e 100644 --- a/src/app/auto_apply.py +++ b/src/github_repo_auditor/app/auto_apply.py @@ -9,28 +9,28 @@ import json from pathlib import Path -from src.cli_output import print_info -from src.control_center_report_state import refresh_latest_report_state -from src.portfolio_truth_types import truth_latest_path +from github_repo_auditor.cli_output import print_info +from github_repo_auditor.control_center_report_state import refresh_latest_report_state +from github_repo_auditor.portfolio_truth_types import truth_latest_path def run_auto_apply_approved_mode(args, output_dir: Path) -> None: """Apply approved campaign packets for repos that pass the automation trust bar.""" - from src.approval_ledger import load_approval_ledger_bundle - from src.auto_apply import ( + from github_repo_auditor.approval_ledger import load_approval_ledger_bundle + from github_repo_auditor.auto_apply import ( build_trust_bar_index, filter_safe_actions, filter_trusted_repo_actions, get_approved_manual_campaigns, summarize_trust_bar, ) - from src.github_client import GitHubClient - from src.ops_writeback import ( + from github_repo_auditor.github_client import GitHubClient + from github_repo_auditor.ops_writeback import ( apply_github_writeback, build_campaign_bundle, summarize_writeback_results, ) - from src.warehouse import load_latest_campaign_state + from github_repo_auditor.warehouse import load_latest_campaign_state cache = None if getattr(args, "no_cache", False) else None client: GitHubClient | None = ( diff --git a/src/app/automation_proposals.py b/src/github_repo_auditor/app/automation_proposals.py similarity index 89% rename from src/app/automation_proposals.py rename to src/github_repo_auditor/app/automation_proposals.py index 40254126..a70976f8 100644 --- a/src/app/automation_proposals.py +++ b/src/github_repo_auditor/app/automation_proposals.py @@ -8,8 +8,8 @@ from pathlib import Path -from src.cli_output import print_info, print_warning -from src.control_center_report_state import refresh_latest_report_state +from github_repo_auditor.cli_output import print_info, print_warning +from github_repo_auditor.control_center_report_state import refresh_latest_report_state DEFAULT_PORTFOLIO_WORKSPACE = Path.home() / "Projects" @@ -18,7 +18,7 @@ def run_automation_proposals_mode(args) -> None: """Triage the durable bounded-automation proposal queue (Arc D phase 3b).""" from datetime import datetime, timezone - from src.automation_proposals import ( + from github_repo_auditor.automation_proposals import ( ACTION_CATALOG_SEED, ACTION_CONTEXT_PR, ProposalApprovalError, @@ -29,7 +29,7 @@ def run_automation_proposals_mode(args) -> None: reject_proposal, save_proposals, ) - from src.portfolio_automation import select_automation_candidates + from github_repo_auditor.portfolio_automation import select_automation_candidates output_dir = Path(args.output_dir) proposals_path = output_dir / "pending-proposals.json" @@ -67,7 +67,7 @@ def run_automation_proposals_mode(args) -> None: return if getattr(args, "propose_automation", False): - from src.weekly_command_center import load_latest_portfolio_truth + from github_repo_auditor.weekly_command_center import load_latest_portfolio_truth _truth_path, truth = load_latest_portfolio_truth(output_dir) if not truth: @@ -100,9 +100,9 @@ def run_automation_proposals_mode(args) -> None: return if getattr(args, "execute_proposals", False): - from src.automation_proposals import executable_proposals - from src.automation_workflow import execute_approved_proposals - from src.portfolio_truth_reconcile import build_portfolio_truth_snapshot + from github_repo_auditor.automation_proposals import executable_proposals + from github_repo_auditor.automation_workflow import execute_approved_proposals + from github_repo_auditor.portfolio_truth_reconcile import build_portfolio_truth_snapshot if not executable_proposals(load_proposals(proposals_path)): print_info("No approved bounded-automation proposals to execute.") diff --git a/src/app/campaign_workflow.py b/src/github_repo_auditor/app/campaign_workflow.py similarity index 92% rename from src/app/campaign_workflow.py rename to src/github_repo_auditor/app/campaign_workflow.py index 9fddbaa9..c9af7bfc 100644 --- a/src/app/campaign_workflow.py +++ b/src/github_repo_auditor/app/campaign_workflow.py @@ -8,8 +8,8 @@ import json from pathlib import Path -from src.cli_output import print_info -from src.portfolio_truth_types import truth_latest_path +from github_repo_auditor.cli_output import print_info +from github_repo_auditor.portfolio_truth_types import truth_latest_path def _run_campaign_from_ledger_mode(args) -> None: @@ -19,9 +19,9 @@ def _run_campaign_from_ledger_mode(args) -> None: action via the existing apply executor map. Dry-run mode prints what would be executed without calling the GitHub API. """ - from src.cache import ResponseCache - from src.github_client import GitHubClient - from src.plan_campaign import ( + from github_repo_auditor.cache import ResponseCache + from github_repo_auditor.github_client import GitHubClient + from github_repo_auditor.plan_campaign import ( dispatch_action, load_approved_campaign_plans, mark_campaign_applied, @@ -129,12 +129,12 @@ def _run_campaign_from_ledger_mode(args) -> None: def _run_plan_campaign_mode(args) -> None: """Dispatch for --plan-campaign: generate a goal-driven campaign plan packet.""" - from src.approval_ledger import default_approval_reviewer as _default_reviewer - from src.llm_cost import BudgetExceededError, CostTracker - from src.narrative import _resolve_provider - from src.operator_prefs import load_prefs, prefs_path - from src.plan_campaign import generate_plan, narrow_candidates, write_packet_to_ledger - from src.warehouse import WAREHOUSE_FILENAME + from github_repo_auditor.approval_ledger import default_approval_reviewer as _default_reviewer + from github_repo_auditor.llm_cost import BudgetExceededError, CostTracker + from github_repo_auditor.narrative import _resolve_provider + from github_repo_auditor.operator_prefs import load_prefs, prefs_path + from github_repo_auditor.plan_campaign import generate_plan, narrow_candidates, write_packet_to_ledger + from github_repo_auditor.warehouse import WAREHOUSE_FILENAME output_dir = Path(args.output_dir) goal: str = str(args.plan_campaign).strip() @@ -163,7 +163,7 @@ def _run_plan_campaign_mode(args) -> None: warehouse_path = output_dir / WAREHOUSE_FILENAME if warehouse_path.exists(): try: - from src.semantic_index import SemanticIndex + from github_repo_auditor.semantic_index import SemanticIndex semantic_index = SemanticIndex(output_dir) except Exception as exc: # noqa: BLE001 @@ -239,16 +239,16 @@ def _run_draft_readmes_mode(args) -> None: """Dispatch for --draft-readmes: generate LLM-authored README draft packets.""" import sys - from src.approval_ledger import default_approval_reviewer as _default_reviewer - from src.draft_readmes import ( + from github_repo_auditor.approval_ledger import default_approval_reviewer as _default_reviewer + from github_repo_auditor.draft_readmes import ( build_context, generate_draft, qualify_repos, write_packets_to_ledger, ) - from src.llm_cost import BudgetExceededError, CostTracker - from src.narrative import _resolve_provider - from src.operator_prefs import ( + from github_repo_auditor.llm_cost import BudgetExceededError, CostTracker + from github_repo_auditor.narrative import _resolve_provider + from github_repo_auditor.operator_prefs import ( is_suppressed, load_prefs, post_process_approval_session, @@ -305,7 +305,7 @@ def _run_draft_readmes_mode(args) -> None: warehouse_path = output_dir / "portfolio-warehouse.db" if warehouse_path.exists(): try: - from src.semantic_index import SemanticIndex + from github_repo_auditor.semantic_index import SemanticIndex semantic_index = SemanticIndex(output_dir) except Exception as exc: # noqa: BLE001 diff --git a/src/app/control_center.py b/src/github_repo_auditor/app/control_center.py similarity index 78% rename from src/app/control_center.py rename to src/github_repo_auditor/app/control_center.py index 7bac32dd..4e0b40a2 100644 --- a/src/app/control_center.py +++ b/src/github_repo_auditor/app/control_center.py @@ -6,18 +6,18 @@ from pathlib import Path from typing import Any -from src.cli_output import print_info -from src.control_center_presentation import ( +from github_repo_auditor.cli_output import print_info +from github_repo_auditor.control_center_presentation import ( _control_center_next_step_hint, _print_control_center_summary, ) -from src.control_center_snapshot import enrich_control_center_snapshot_from_report -from src.diff import diff_reports -from src.governance_activation import build_governance_summary -from src.history import find_previous -from src.operator_control_center import build_operator_snapshot, normalize_review_state -from src.operator_control_center_artifacts import write_control_center_artifacts -from src.report_state import load_latest_report, parse_iso_datetime, report_artifact_datetime +from github_repo_auditor.control_center_snapshot import enrich_control_center_snapshot_from_report +from github_repo_auditor.diff import diff_reports +from github_repo_auditor.governance_activation import build_governance_summary +from github_repo_auditor.history import find_previous +from github_repo_auditor.operator_control_center import build_operator_snapshot, normalize_review_state +from github_repo_auditor.operator_control_center_artifacts import write_control_center_artifacts +from github_repo_auditor.report_state import load_latest_report, parse_iso_datetime, report_artifact_datetime def run_control_center_mode(args: Any, parser: Any) -> None: diff --git a/src/app/doctor.py b/src/github_repo_auditor/app/doctor.py similarity index 89% rename from src/app/doctor.py rename to src/github_repo_auditor/app/doctor.py index a1b53a18..60e2b16f 100644 --- a/src/app/doctor.py +++ b/src/github_repo_auditor/app/doctor.py @@ -3,8 +3,8 @@ from pathlib import Path from typing import Any -from src.cli_output import print_info -from src.diagnostics import ( +from github_repo_auditor.cli_output import print_info +from github_repo_auditor.diagnostics import ( format_diagnostics_report, run_diagnostics, write_diagnostics_report, diff --git a/src/app/improvement_application.py b/src/github_repo_auditor/app/improvement_application.py similarity index 96% rename from src/app/improvement_application.py rename to src/github_repo_auditor/app/improvement_application.py index 621b4fb0..07c685c8 100644 --- a/src/app/improvement_application.py +++ b/src/github_repo_auditor/app/improvement_application.py @@ -4,13 +4,13 @@ from pathlib import Path -from src.cache import ResponseCache -from src.cli_output import print_info -from src.github_client import GitHubClient +from github_repo_auditor.cache import ResponseCache +from github_repo_auditor.cli_output import print_info +from github_repo_auditor.github_client import GitHubClient def _run_apply_improvements_mode(args, parser) -> None: - from src.repo_improver import ( + from github_repo_auditor.repo_improver import ( apply_metadata_updates, apply_readme_updates, generate_execution_report, @@ -54,7 +54,7 @@ def _run_apply_improvements_mode(args, parser) -> None: readme_updates: list[dict] = list(file_updates) ledger_packets_by_repo: dict[str, object] = {} - from src.draft_readmes import ( + from github_repo_auditor.draft_readmes import ( assemble_readme_from_approved_sections, load_approved_drafts, load_approved_sectioned_packets, diff --git a/src/app/initiative_suggestions.py b/src/github_repo_auditor/app/initiative_suggestions.py similarity index 86% rename from src/app/initiative_suggestions.py rename to src/github_repo_auditor/app/initiative_suggestions.py index 2d4d7af3..cc93ae48 100644 --- a/src/app/initiative_suggestions.py +++ b/src/github_repo_auditor/app/initiative_suggestions.py @@ -10,17 +10,17 @@ import sys from pathlib import Path -from src.cli_output import print_info, print_warning -from src.portfolio_truth_types import truth_latest_path +from github_repo_auditor.cli_output import print_info, print_warning +from github_repo_auditor.portfolio_truth_types import truth_latest_path def _run_suggest_initiatives_mode(args) -> None: """LLM-rank repos closest to qualifying for their next maturity tier (Arc G S8.4).""" from pathlib import Path as _Path - from src.llm_cost import BudgetExceededError - from src.maturity_tiers import tier_name - from src.suggest_initiatives import generate_suggestions + from github_repo_auditor.llm_cost import BudgetExceededError + from github_repo_auditor.maturity_tiers import tier_name + from github_repo_auditor.suggest_initiatives import generate_suggestions truth_path = truth_latest_path(_Path(args.output_dir)) if not truth_path.exists(): @@ -62,7 +62,7 @@ def _run_accept_suggestion_mode(args) -> None: """Accept a suggestion: convert it into a tier-upgrade initiative (Arc G S9.1).""" import sys - from src.suggest_initiatives import accept_suggestion + from github_repo_auditor.suggest_initiatives import accept_suggestion output_dir = Path(args.output_dir) truth_path = truth_latest_path(output_dir) @@ -102,7 +102,7 @@ def _run_dismiss_suggestion_mode(args) -> None: import sys from pathlib import Path - from src.suggest_initiatives import dismiss_suggestion_record, dismissed_path + from github_repo_auditor.suggest_initiatives import dismiss_suggestion_record, dismissed_path output_dir = Path(args.output_dir) try: @@ -126,7 +126,7 @@ def _run_undo_dismiss_mode(args) -> None: """Restore a dismissed repo to the suggestion pool (Arc G S11.4).""" from pathlib import Path - from src.suggest_initiatives import dismissed_path, undo_dismiss + from github_repo_auditor.suggest_initiatives import dismissed_path, undo_dismiss output_dir = Path(args.output_dir) removed = undo_dismiss(dismissed_path(output_dir), args.undo_dismiss) @@ -139,7 +139,7 @@ def _run_list_dismissed_mode(args) -> None: """List all currently dismissed suggestion repos (Arc G S11.4).""" from pathlib import Path - from src.suggest_initiatives import dismissed_path, load_dismissed + from github_repo_auditor.suggest_initiatives import dismissed_path, load_dismissed output_dir = Path(args.output_dir) items = load_dismissed(dismissed_path(output_dir)) @@ -155,7 +155,7 @@ def _run_expire_dismissals_mode(args) -> None: """Remove dismissals whose expiry date has passed (Arc G S12.1).""" from pathlib import Path - from src.suggest_initiatives import dismissed_path, expire_dismissals + from github_repo_auditor.suggest_initiatives import dismissed_path, expire_dismissals output_dir = Path(args.output_dir) expired = expire_dismissals(dismissed_path(output_dir)) @@ -170,7 +170,7 @@ def _run_dismissal_history_mode(args) -> None: """Show audit trail of dismissal events (Arc G S12.1).""" from pathlib import Path - from src.suggest_initiatives import dismissed_path, load_dismissal_events + from github_repo_auditor.suggest_initiatives import dismissed_path, load_dismissal_events output_dir = Path(args.output_dir) events = load_dismissal_events(dismissed_path(output_dir)) diff --git a/src/app/initiatives.py b/src/github_repo_auditor/app/initiatives.py similarity index 90% rename from src/app/initiatives.py rename to src/github_repo_auditor/app/initiatives.py index 6bf815e7..9ca63fa6 100644 --- a/src/app/initiatives.py +++ b/src/github_repo_auditor/app/initiatives.py @@ -7,14 +7,14 @@ from datetime import date, datetime, timezone from pathlib import Path -from src.cli_output import print_info, print_warning -from src.portfolio_truth_types import TRUTH_LATEST_FILENAME, truth_latest_path +from github_repo_auditor.cli_output import print_info, print_warning +from github_repo_auditor.portfolio_truth_types import TRUTH_LATEST_FILENAME, truth_latest_path def run_set_initiative_mode(args) -> None: """Validate and persist a tier-upgrade initiative for a repo.""" - from src.initiatives import Initiative, initiatives_path, operator_identity, upsert_initiative - from src.maturity_tiers import TIER_DEFINITIONS, compute_tier + from github_repo_auditor.initiatives import Initiative, initiatives_path, operator_identity, upsert_initiative + from github_repo_auditor.maturity_tiers import TIER_DEFINITIONS, compute_tier repo_name: str = args.set_initiative target_tier: int | None = getattr(args, "target_tier", None) @@ -70,8 +70,8 @@ def run_set_initiative_mode(args) -> None: def run_list_initiatives_mode(args) -> None: """Print a status table for all initiatives.""" - from src.initiatives import derive_status, initiatives_path, load_initiatives - from src.maturity_tiers import TIER_DEFINITIONS, compute_tier, tier_name + from github_repo_auditor.initiatives import derive_status, initiatives_path, load_initiatives + from github_repo_auditor.maturity_tiers import TIER_DEFINITIONS, compute_tier, tier_name output_dir = Path(args.output_dir) initiatives = load_initiatives(initiatives_path(output_dir)) @@ -118,7 +118,7 @@ def run_list_initiatives_mode(args) -> None: def run_close_initiative_mode(args) -> None: """Close the open initiative for a repo.""" - from src.initiatives import close_initiative, initiatives_path + from github_repo_auditor.initiatives import close_initiative, initiatives_path repo_name: str = args.close_initiative closed = close_initiative(initiatives_path(Path(args.output_dir)), repo_name, reason="met") diff --git a/src/app/portfolio_analysis.py b/src/github_repo_auditor/app/portfolio_analysis.py similarity index 93% rename from src/app/portfolio_analysis.py rename to src/github_repo_auditor/app/portfolio_analysis.py index 717661f8..b26426ed 100644 --- a/src/app/portfolio_analysis.py +++ b/src/github_repo_auditor/app/portfolio_analysis.py @@ -6,8 +6,8 @@ import sys from pathlib import Path -from src.cli_output import print_info, print_warning -from src.portfolio_truth_types import truth_latest_path +from github_repo_auditor.cli_output import print_info, print_warning +from github_repo_auditor.portfolio_truth_types import truth_latest_path def _run_tier_gaps_export_mode(args) -> None: @@ -15,7 +15,7 @@ def _run_tier_gaps_export_mode(args) -> None: from datetime import datetime, timezone from pathlib import Path - from src.maturity_tiers import compute_tier, tier_gap, tier_name + from github_repo_auditor.maturity_tiers import compute_tier, tier_gap, tier_name output_dir = Path(args.output_dir) truth_path = truth_latest_path(output_dir) @@ -92,7 +92,7 @@ def _run_tier_recalibration_report_mode(args) -> None: """Generate tier distribution report and flag bunching (Arc H A4).""" from datetime import date, datetime, timezone - from src.tier_recalibration import tier_distribution_report + from github_repo_auditor.tier_recalibration import tier_distribution_report output_dir = Path(args.output_dir) truth_path = truth_latest_path(output_dir) @@ -130,8 +130,8 @@ def _run_context_triage_mode(args) -> None: """Run context quality triage across the portfolio (Arc H B1).""" from datetime import date, datetime, timezone - from src.catalog_validator import validate_catalog - from src.portfolio_context_triage import run_triage + from github_repo_auditor.catalog_validator import validate_catalog + from github_repo_auditor.portfolio_context_triage import run_triage output_dir = Path(args.output_dir) truth_path = truth_latest_path(output_dir) diff --git a/src/app/portfolio_truth.py b/src/github_repo_auditor/app/portfolio_truth.py similarity index 95% rename from src/app/portfolio_truth.py rename to src/github_repo_auditor/app/portfolio_truth.py index 7e9ce7d6..5c756258 100644 --- a/src/app/portfolio_truth.py +++ b/src/github_repo_auditor/app/portfolio_truth.py @@ -5,26 +5,26 @@ from pathlib import Path from typing import Any -from src.cli_output import print_info -from src.github_security_coverage import ( +from github_repo_auditor.cli_output import print_info +from github_repo_auditor.github_security_coverage import ( DEFAULT_EXPECTED_GITHUB_COHORT_COUNT, SecurityCoverageError, SecurityCoverageReceiptBinding, ) -from src.portfolio_context_recovery import ( +from github_repo_auditor.portfolio_context_recovery import ( apply_context_recovery_plan, build_context_recovery_plan, write_context_recovery_plan_artifacts, ) -from src.portfolio_truth_publish import PortfolioTruthPublishError, publish_portfolio_truth -from src.portfolio_truth_reconcile import build_portfolio_truth_snapshot -from src.portfolio_truth_status import ( +from github_repo_auditor.portfolio_truth_publish import PortfolioTruthPublishError, publish_portfolio_truth +from github_repo_auditor.portfolio_truth_reconcile import build_portfolio_truth_snapshot +from github_repo_auditor.portfolio_truth_status import ( load_live_repo_status_by_name, load_release_count_by_name, load_repo_status_from_audit_by_name, load_security_coverage_by_full_name, ) -from src.producer_preflight import load_producer_evidence +from github_repo_auditor.producer_preflight import load_producer_evidence def run_portfolio_truth_mode(args: Any) -> None: diff --git a/src/app/pr_head_evidence.py b/src/github_repo_auditor/app/pr_head_evidence.py similarity index 97% rename from src/app/pr_head_evidence.py rename to src/github_repo_auditor/app/pr_head_evidence.py index 41d234f9..200b846a 100644 --- a/src/app/pr_head_evidence.py +++ b/src/github_repo_auditor/app/pr_head_evidence.py @@ -6,7 +6,7 @@ from pathlib import Path from typing import Protocol -from src.pr_head_evidence import ( +from github_repo_auditor.pr_head_evidence import ( SnapshotValidationError, evaluate_snapshot, invalid_snapshot_verdict, diff --git a/src/app/report_only.py b/src/github_repo_auditor/app/report_only.py similarity index 74% rename from src/app/report_only.py rename to src/github_repo_auditor/app/report_only.py index 5a4b49cd..afbeaa39 100644 --- a/src/app/report_only.py +++ b/src/github_repo_auditor/app/report_only.py @@ -5,9 +5,9 @@ from pathlib import Path from typing import Any -from src.cli_output import print_info -from src.repo_improver import generate_manifest, write_manifest -from src.report_state import load_latest_report +from github_repo_auditor.cli_output import print_info +from github_repo_auditor.repo_improver import generate_manifest, write_manifest +from github_repo_auditor.report_state import load_latest_report def run_generate_manifest_mode(args: Any, parser: Any) -> None: diff --git a/src/app/run_audit.py b/src/github_repo_auditor/app/run_audit.py similarity index 92% rename from src/app/run_audit.py rename to src/github_repo_auditor/app/run_audit.py index be0b755a..031cdc6e 100644 --- a/src/app/run_audit.py +++ b/src/github_repo_auditor/app/run_audit.py @@ -14,8 +14,8 @@ from datetime import datetime, timezone from pathlib import Path from time import perf_counter -from src.analyzers import run_all_analyzers -from src.baseline_context import ( +from github_repo_auditor.analyzers import run_all_analyzers +from github_repo_auditor.baseline_context import ( build_baseline_context_from_args, build_watch_state, compare_baseline_context, @@ -23,33 +23,33 @@ format_mismatch_value, normalize_scoring_profile, ) -from src.cache import ResponseCache -from src.cli_output import create_progress, print_info, print_status, print_warning -from src.cloner import clone_workspace -from src.control_center_presentation import ( +from github_repo_auditor.cache import ResponseCache +from github_repo_auditor.cli_output import create_progress, print_info, print_status, print_warning +from github_repo_auditor.cloner import clone_workspace +from github_repo_auditor.control_center_presentation import ( _normal_audit_next_step_hint, ) -from src.github_client import GitHubClient -from src.models import AuditReport, RepoAudit, RepoMetadata -from src.recurring_review import FULL_REFRESH_DAYS -from src.report_enrichment import build_run_change_counts, build_run_change_summary -from src.report_state import ( +from github_repo_auditor.github_client import GitHubClient +from github_repo_auditor.models import AuditReport, RepoAudit, RepoMetadata +from github_repo_auditor.recurring_review import FULL_REFRESH_DAYS +from github_repo_auditor.report_enrichment import build_run_change_counts, build_run_change_summary +from github_repo_auditor.report_state import ( audit_from_dict as _audit_from_dict, load_latest_report as _load_latest_report, report_from_dict as _report_from_dict, ) -from src.report_operating_paths import apply_operating_paths as _apply_operating_paths -from src.report_portfolio_catalog import apply_portfolio_catalog as _apply_portfolio_catalog -from src.report_scorecards import apply_scorecards as _apply_scorecards -from src.report_operator_state import enrich_report_with_operator_state as _enrich_report_with_operator_state -from src.reporter import ( +from github_repo_auditor.report_operating_paths import apply_operating_paths as _apply_operating_paths +from github_repo_auditor.report_portfolio_catalog import apply_portfolio_catalog as _apply_portfolio_catalog +from github_repo_auditor.report_scorecards import apply_scorecards as _apply_scorecards +from github_repo_auditor.report_operator_state import enrich_report_with_operator_state as _enrich_report_with_operator_state +from github_repo_auditor.reporter import ( write_json_report, write_markdown_report, write_pcc_export, write_raw_metadata, ) -from src.scorer import score_repo -from src.terminology import ACTION_SYNC_CANONICAL_LABELS +from github_repo_auditor.scorer import score_repo +from github_repo_auditor.terminology import ACTION_SYNC_CANONICAL_LABELS DEFAULT_ANALYSIS_WORKERS = 1 MAX_ANALYSIS_WORKERS = 8 DEFAULT_PORTFOLIO_WORKSPACE = Path.home() / "Projects" @@ -193,7 +193,7 @@ def _normalize_profile_name(profile_name: str | None) -> str: return normalize_scoring_profile(profile_name) def _run_main_audit_cycle(args, config_inspection) -> None: - from src.diagnostics import format_preflight_summary, run_diagnostics, should_block_run + from github_repo_auditor.diagnostics import format_preflight_summary, run_diagnostics, should_block_run if args.preflight_mode != "off": preflight = run_diagnostics(args, config_inspection=config_inspection, full=False) @@ -261,7 +261,7 @@ def _run_main_audit_cycle(args, config_inspection) -> None: resumed_names: set[str] = set() resumed_audits: list[RepoAudit] = [] if getattr(args, "resume", False): - from src.progress import load_progress + from github_repo_auditor.progress import load_progress saved = load_progress(output_dir) if saved: @@ -324,8 +324,8 @@ def _run_main_audit_cycle(args, config_inspection) -> None: ) if getattr(args, "create_issues", False): - from src.issue_creator import create_audit_issues - from src.quick_wins import find_quick_wins + from github_repo_auditor.issue_creator import create_audit_issues + from github_repo_auditor.quick_wins import find_quick_wins quick_wins = find_quick_wins(audits) issue_result = create_audit_issues( @@ -340,8 +340,8 @@ def _run_main_audit_cycle(args, config_inspection) -> None: ) if getattr(args, "summary", False) and outputs.get("json_path"): - from src.diff import diff_reports, print_diff_summary - from src.history import find_previous + from github_repo_auditor.diff import diff_reports, print_diff_summary + from github_repo_auditor.history import find_previous prev_path = find_previous(outputs["json_path"].name) if prev_path: @@ -369,8 +369,8 @@ def _run_main_audit_cycle(args, config_inspection) -> None: ) def _run_watch_mode(args, config_inspection) -> None: - from src.recurring_review import choose_watch_plan - from src.watch import run_watch_loop + from github_repo_auditor.recurring_review import choose_watch_plan + from github_repo_auditor.watch import run_watch_loop def _run_watch_once() -> None: watch_plan = choose_watch_plan( @@ -395,7 +395,7 @@ def _run_watch_once() -> None: def _fetch_repo_metadata(args, client: GitHubClient) -> tuple[list[RepoMetadata], list[dict]]: if args.graphql and args.token: - from src.graphql_client import bulk_fetch_repos + from github_repo_auditor.graphql_client import bulk_fetch_repos print_info("Using GraphQL bulk fetch...") raw_repos = bulk_fetch_repos(args.username, args.token) @@ -497,7 +497,7 @@ def _analyze_repos( custom_weights: dict[str, float] | None, runtime_stats: dict | None = None, ) -> list[RepoAudit]: - from src.analyzers import load_custom_analyzers + from github_repo_auditor.analyzers import load_custom_analyzers if args.skip_clone: print_warning("Audit modes that score repos do not support --skip-clone.") @@ -525,8 +525,8 @@ def _analyze_repos( else: import sqlite3 as _sqlite3 - from src.warehouse import WAREHOUSE_FILENAME - from src.warehouse import _ensure_schema as _warehouse_ensure_schema + from github_repo_auditor.warehouse import WAREHOUSE_FILENAME + from github_repo_auditor.warehouse import _ensure_schema as _warehouse_ensure_schema _output_dir = Path(getattr(args, "output_dir", "output")) _db_path = _output_dir / WAREHOUSE_FILENAME @@ -565,7 +565,7 @@ def _analyze_one(repo_meta: RepoMetadata, repo_path: Path) -> RepoAudit: # ── Optional async enrichment prefetch ─────────────────────────────────── _fetch_mode: str = getattr(args, "fetch_mode", "sync") if _fetch_mode == "async" and repos: - from src.github_client_async import fetch_enrichment_sync as _async_prefetch + from github_repo_auditor.github_client_async import fetch_enrichment_sync as _async_prefetch _fetch_workers: int = max(1, getattr(args, "fetch_workers", 10)) print_info( @@ -682,8 +682,8 @@ def _analyze_one(repo_meta: RepoMetadata, repo_path: Path) -> RepoAudit: # ── Cache reconcile gate ────────────────────────────────────────────── _do_reconcile: bool = getattr(args, "reconcile_cache", False) if _do_reconcile and _warehouse_conn is not None and audits: - from src.analyzer_cache import reconcile as _cache_reconcile - from src.analyzers import run_all_analyzers as _run_all + from github_repo_auditor.analyzer_cache import reconcile as _cache_reconcile + from github_repo_auditor.analyzers import run_all_analyzers as _run_all _repo_path_map: dict[str, object] = { repo_meta.name: cloned.get(repo_meta.name) for repo_meta in repos @@ -750,7 +750,7 @@ def _fresh_run(repo_path, meta, conn=None): def _apply_requested_reconciliation(report: AuditReport, args, audits: list[RepoAudit]) -> None: if args.registry: - from src.registry_parser import parse_registry, reconcile, sync_new_repos + from github_repo_auditor.registry_parser import parse_registry, reconcile, sync_new_repos registry = parse_registry(args.registry) report.reconciliation = reconcile(registry, audits) @@ -772,8 +772,8 @@ def _apply_requested_reconciliation(report: AuditReport, args, audits: list[Repo return if args.notion_registry: - from src.notion_registry import load_notion_registry - from src.registry_parser import reconcile + from github_repo_auditor.notion_registry import load_notion_registry + from github_repo_auditor.registry_parser import reconcile notion_projects = load_notion_registry(Path("config")) if notion_projects: @@ -841,8 +841,8 @@ def _apply_ops_writeback( github_projects_config = None operator_context: dict[str, dict] = {} if getattr(args, "github_projects", False): - from src.github_projects import load_github_projects_config, operator_context_by_repo - from src.operator_control_center import build_operator_snapshot, normalize_review_state + from github_repo_auditor.github_projects import load_github_projects_config, operator_context_by_repo + from github_repo_auditor.operator_control_center import build_operator_snapshot, normalize_review_state github_projects_config = load_github_projects_config( Path(args.github_projects_config) @@ -863,8 +863,8 @@ def _apply_ops_writeback( ) operator_context = operator_context_by_repo(operator_snapshot.get("operator_queue", [])) - from src.notion_sync import sync_campaign_actions - from src.ops_writeback import ( + from github_repo_auditor.notion_sync import sync_campaign_actions + from github_repo_auditor.ops_writeback import ( apply_github_writeback, build_action_runs, build_campaign_bundle, @@ -873,7 +873,7 @@ def _apply_ops_writeback( build_writeback_preview, summarize_writeback_results, ) - from src.warehouse import load_campaign_history, load_latest_campaign_state + from github_repo_auditor.warehouse import load_campaign_history, load_latest_campaign_state previous_state = load_latest_campaign_state(output_dir, args.campaign) campaign_summary, actions = build_campaign_bundle( @@ -968,16 +968,16 @@ def _write_report_outputs( save_fingerprint_data: bool = True, diff_source: Path | None = None, ) -> dict[str, object]: - from src.diff import diff_reports, format_diff_markdown - from src.excel_export import export_excel - from src.history import ( + from github_repo_auditor.diff import diff_reports, format_diff_markdown + from github_repo_auditor.excel_export import export_excel + from github_repo_auditor.history import ( archive_report, find_previous, load_repo_score_history, load_trend_data, save_fingerprints, ) - from src.warehouse import write_warehouse_snapshot + from github_repo_auditor.warehouse import write_warehouse_snapshot output_start = perf_counter() _apply_ops_writeback(report, args, client, output_dir) @@ -1062,7 +1062,7 @@ def _write_report_outputs( badge_info = "" if args.badges: - from src.badge_export import _write_badges_markdown, export_badges, upload_badge_gist + from github_repo_auditor.badge_export import _write_badges_markdown, export_badges, upload_badge_gist badge_result = export_badges(report_data, output_dir) badge_info = ( @@ -1075,9 +1075,9 @@ def _write_report_outputs( notion_info = "" if args.notion: - from src.notion_client import get_notion_token, load_notion_config - from src.notion_export import _load_project_map, export_notion_events - from src.notion_sync import ( + from github_repo_auditor.notion_client import get_notion_token, load_notion_config + from github_repo_auditor.notion_export import _load_project_map, export_notion_events + from github_repo_auditor.notion_sync import ( check_recommendation_followup, create_audit_action_requests, create_audit_history_entry, @@ -1097,8 +1097,8 @@ def _write_report_outputs( sync_token = get_notion_token() sync_config = load_notion_config(Path("config")) if sync_token and sync_config: - from src.notion_dashboard import create_notion_dashboard - from src.quick_wins import find_quick_wins + from github_repo_auditor.notion_dashboard import create_notion_dashboard + from github_repo_auditor.quick_wins import find_quick_wins quick_wins = find_quick_wins(report.audits) project_map = _load_project_map(Path("config")) @@ -1122,21 +1122,21 @@ def _write_report_outputs( readme_info = "" if args.portfolio_readme: - from src.portfolio_readme import export_portfolio_readme + from github_repo_auditor.portfolio_readme import export_portfolio_readme readme_result = export_portfolio_readme(report_data, output_dir) readme_info = f"\n {readme_result['readme_path']}" suggestions_info = "" if args.readme_suggestions: - from src.readme_suggestions import generate_readme_suggestions + from github_repo_auditor.readme_suggestions import generate_readme_suggestions sug_result = generate_readme_suggestions(report_data, output_dir) suggestions_info = f"\n {sug_result['suggestions_path']} ({sug_result['total_suggestions']} suggestions)" html_info = "" if args.html: - from src.web_export import export_html_dashboard + from github_repo_auditor.web_export import export_html_dashboard html_result = export_html_dashboard( report_data, @@ -1151,7 +1151,7 @@ def _write_report_outputs( pdf_info = "" if args.pdf: - from src.pdf_export import export_pdf_report + from github_repo_auditor.pdf_export import export_pdf_report pdf_path = export_pdf_report(report_data, output_dir) if pdf_path: @@ -1159,7 +1159,7 @@ def _write_report_outputs( review_pack_info = "" if args.review_pack: - from src.review_pack import export_review_pack + from github_repo_auditor.review_pack import export_review_pack review_pack_result = export_review_pack( report_data, @@ -1171,7 +1171,7 @@ def _write_report_outputs( review_pack_info = f"\n {review_pack_result['review_pack_path']}" if args.auto_archive: - from src.archive_candidates import export_archive_report, find_archive_candidates + from github_repo_auditor.archive_candidates import export_archive_report, find_archive_candidates candidates = find_archive_candidates(score_history) if candidates: @@ -1181,7 +1181,7 @@ def _write_report_outputs( ) if getattr(args, "vuln_check", False): - from src.vuln_check import check_vulnerabilities, format_vuln_summary + from github_repo_auditor.vuln_check import check_vulnerabilities, format_vuln_summary vulns = check_vulnerabilities(report_data.get("audits", []), cache=cache) print_info(format_vuln_summary(vulns)) @@ -1193,8 +1193,8 @@ def _write_report_outputs( print_info(f"Vulnerability report: {vuln_path}") if getattr(args, "ghas_alerts", False) or getattr(args, "vuln_check", False): - from src.ghas_alert_details import fetch_dependabot_details - from src.ghas_alerts import fetch_ghas_alerts, format_ghas_summary + from github_repo_auditor.ghas_alert_details import fetch_dependabot_details + from github_repo_auditor.ghas_alerts import fetch_ghas_alerts, format_ghas_summary ghas_token: str | None = getattr(args, "token", None) or None ghas_data = fetch_ghas_alerts( @@ -1222,7 +1222,7 @@ def _write_report_outputs( print_info(f"GHAS alerts report: {ghas_path}") if getattr(args, "ossf_scorecard", False): - from src.ossf_scorecard import fetch_ossf_scorecards, format_ossf_summary + from github_repo_auditor.ossf_scorecard import fetch_ossf_scorecards, format_ossf_summary ossf_data = fetch_ossf_scorecards( report_data.get("audits", []), @@ -1250,7 +1250,7 @@ def _write_report_outputs( _uses_llm = args.narrative or getattr(args, "briefing", False) _cost_tracker = None if _uses_llm: - from src.llm_cost import BudgetExceededError, CostTracker + from github_repo_auditor.llm_cost import BudgetExceededError, CostTracker _cost_tracker = CostTracker( budget_usd=getattr(args, "max_llm_spend", None), @@ -1258,7 +1258,7 @@ def _write_report_outputs( ) if args.narrative: - from src.narrative import generate_narrative + from github_repo_auditor.narrative import generate_narrative try: generate_narrative( @@ -1276,7 +1276,7 @@ def _write_report_outputs( sys.exit(1) if getattr(args, "briefing", False): - from src.briefing import generate_briefing + from github_repo_auditor.briefing import generate_briefing try: generate_briefing( @@ -1569,7 +1569,7 @@ def _run_incremental_audit( latest_trusted_baseline: dict | None = None, ) -> None: """Only re-audit repos whose pushed_at changed since last run.""" - from src.history import load_fingerprints + from github_repo_auditor.history import load_fingerprints existing_report_path, existing_report_data = _load_latest_report(output_dir) if not existing_report_path or not existing_report_data: @@ -1579,7 +1579,7 @@ def _run_incremental_audit( fingerprints = load_fingerprints(output_dir / ".audit-fingerprints.json") if not fingerprints: print_warning("No fingerprints found. Run a full audit first.") - print_info(f"Usage: python -m src {args.username}") + print_info(f"Usage: python -m github_repo_auditor {args.username}") return repos = _filter_repos(all_repos, skip_forks=args.skip_forks, skip_archived=args.skip_archived) @@ -1714,7 +1714,7 @@ def _print_dry_run_summary(repos: list[RepoMetadata]) -> None: def _maybe_run_reindex(report: "AuditReport", warehouse_path: Path, args: object) -> None: """Run semantic reindex after audit. Skips gracefully if deps missing.""" - from src.semantic_index import SemanticIndex, _run_reindex + from github_repo_auditor.semantic_index import SemanticIndex, _run_reindex embedder_name: str = getattr(args, "embedder", "voyage") force: bool = getattr(args, "reindex_force", False) diff --git a/src/app/security_modes.py b/src/github_repo_auditor/app/security_modes.py similarity index 92% rename from src/app/security_modes.py rename to src/github_repo_auditor/app/security_modes.py index cd76ecb0..7fe542e7 100644 --- a/src/app/security_modes.py +++ b/src/github_repo_auditor/app/security_modes.py @@ -5,13 +5,13 @@ from pathlib import Path from typing import Any -from src.cli_output import print_info -from src.portfolio_security_gate import ( +from github_repo_auditor.cli_output import print_info +from github_repo_auditor.portfolio_security_gate import ( build_security_gate_report, render_security_gate_markdown, ) -from src.portfolio_truth_types import TRUTH_LATEST_FILENAME -from src.security_burndown import build_security_burndown, render_burndown_markdown +from github_repo_auditor.portfolio_truth_types import TRUTH_LATEST_FILENAME +from github_repo_auditor.security_burndown import build_security_burndown, render_burndown_markdown def run_security_burndown_mode(args: Any) -> None: diff --git a/src/app/semantic_search.py b/src/github_repo_auditor/app/semantic_search.py similarity index 85% rename from src/app/semantic_search.py rename to src/github_repo_auditor/app/semantic_search.py index 0d48c285..beaf9ddf 100644 --- a/src/app/semantic_search.py +++ b/src/github_repo_auditor/app/semantic_search.py @@ -4,13 +4,13 @@ from pathlib import Path -from src.cli_output import print_info, print_warning +from github_repo_auditor.cli_output import print_info, print_warning def run_semantic_search_mode(args: object, query: str) -> None: """Run a standalone semantic search against the existing warehouse index.""" - from src.semantic_index import SemanticIndex, _run_search - from src.warehouse import WAREHOUSE_FILENAME + from github_repo_auditor.semantic_index import SemanticIndex, _run_search + from github_repo_auditor.warehouse import WAREHOUSE_FILENAME output_dir = Path(getattr(args, "output_dir", "output")) warehouse_path = output_dir / WAREHOUSE_FILENAME diff --git a/src/app/serve_mode.py b/src/github_repo_auditor/app/serve_mode.py similarity index 89% rename from src/app/serve_mode.py rename to src/github_repo_auditor/app/serve_mode.py index 8432b6a6..05959efd 100644 --- a/src/app/serve_mode.py +++ b/src/github_repo_auditor/app/serve_mode.py @@ -7,7 +7,7 @@ def run_serve_mode(args: object) -> None: """Launch the local FastAPI + HTMX web UI (requires [serve] extra).""" try: - from src.serve.app import run_serve + from github_repo_auditor.serve.app import run_serve except ImportError: sys.exit("audit serve requires the [serve] extra.\nInstall with: pip install -e '.[serve]'") output_dir = Path(getattr(args, "output_dir", "output")) diff --git a/src/approval_ledger.py b/src/github_repo_auditor/approval_ledger.py similarity index 99% rename from src/approval_ledger.py rename to src/github_repo_auditor/approval_ledger.py index 0ca4dc5a..13025066 100644 --- a/src/approval_ledger.py +++ b/src/github_repo_auditor/approval_ledger.py @@ -8,9 +8,9 @@ from pathlib import Path from typing import Any -from src.governance_activation import SCOPE_TO_KEYS -from src.terminology import ACTION_SYNC_CANONICAL_LABELS -from src.warehouse import ( +from github_repo_auditor.governance_activation import SCOPE_TO_KEYS +from github_repo_auditor.terminology import ACTION_SYNC_CANONICAL_LABELS +from github_repo_auditor.warehouse import ( load_approval_followup_events, load_approval_records, load_recent_action_runs, diff --git a/src/archive_candidates.py b/src/github_repo_auditor/archive_candidates.py similarity index 100% rename from src/archive_candidates.py rename to src/github_repo_auditor/archive_candidates.py diff --git a/assets/excel/analyst-template.xlsx b/src/github_repo_auditor/assets/excel/analyst-template.xlsx similarity index 100% rename from assets/excel/analyst-template.xlsx rename to src/github_repo_auditor/assets/excel/analyst-template.xlsx diff --git a/src/auto_apply.py b/src/github_repo_auditor/auto_apply.py similarity index 100% rename from src/auto_apply.py rename to src/github_repo_auditor/auto_apply.py diff --git a/src/automation_executor.py b/src/github_repo_auditor/automation_executor.py similarity index 97% rename from src/automation_executor.py rename to src/github_repo_auditor/automation_executor.py index b87ee7bb..727de692 100644 --- a/src/automation_executor.py +++ b/src/github_repo_auditor/automation_executor.py @@ -25,7 +25,7 @@ from pathlib import Path from typing import Callable -from src.automation_proposals import AutomationProposal, require_approved +from github_repo_auditor.automation_proposals import AutomationProposal, require_approved CONTRACT_VERSION = "automation_execution_v1" @@ -238,7 +238,7 @@ def execute_catalog_seed( ) if apply_seeds is None: - from src.portfolio_context_recovery import _merge_catalog_seeds + from github_repo_auditor.portfolio_context_recovery import _merge_catalog_seeds apply_seeds = _merge_catalog_seeds apply_seeds(catalog_path, seeds) diff --git a/src/automation_proposals.py b/src/github_repo_auditor/automation_proposals.py similarity index 99% rename from src/automation_proposals.py rename to src/github_repo_auditor/automation_proposals.py index d263b3fd..65b66ab6 100644 --- a/src/automation_proposals.py +++ b/src/github_repo_auditor/automation_proposals.py @@ -19,7 +19,7 @@ from pathlib import Path from typing import Any, Iterable -from src.portfolio_automation import AutomationCandidate +from github_repo_auditor.portfolio_automation import AutomationCandidate CONTRACT_VERSION = "automation_proposals_v1" diff --git a/src/automation_workflow.py b/src/github_repo_auditor/automation_workflow.py similarity index 96% rename from src/automation_workflow.py rename to src/github_repo_auditor/automation_workflow.py index 55369f51..409a7ec8 100644 --- a/src/automation_workflow.py +++ b/src/github_repo_auditor/automation_workflow.py @@ -25,7 +25,7 @@ from functools import partial from pathlib import Path -from src.automation_executor import ( +from github_repo_auditor.automation_executor import ( AutomationExecutionError, CommandRunner, ExecutionPlan, @@ -34,7 +34,7 @@ execute_catalog_seed, execute_context_pr, ) -from src.automation_proposals import ( +from github_repo_auditor.automation_proposals import ( ACTION_CATALOG_SEED, ACTION_CONTEXT_PR, AutomationProposal, @@ -43,16 +43,16 @@ mark_executed, save_proposals, ) -from src.portfolio_context_recovery import ( +from github_repo_auditor.portfolio_context_recovery import ( _catalog_repo_key, _suggested_catalog_seed, write_managed_context_block, ) -from src.portfolio_checkout_authority import ( +from github_repo_auditor.portfolio_checkout_authority import ( checkout_authority_blocker, checkout_authority_path, ) -from src.portfolio_truth_types import PortfolioTruthProject, PortfolioTruthSnapshot +from github_repo_auditor.portfolio_truth_types import PortfolioTruthProject, PortfolioTruthSnapshot CONTRACT_VERSION = "automation_workflow_v1" diff --git a/src/badge_export.py b/src/github_repo_auditor/badge_export.py similarity index 99% rename from src/badge_export.py rename to src/github_repo_auditor/badge_export.py index c75e2aac..1d00241c 100644 --- a/src/badge_export.py +++ b/src/github_repo_auditor/badge_export.py @@ -11,7 +11,7 @@ from pathlib import Path from urllib.parse import quote -from src.scorer import letter_grade +from github_repo_auditor.scorer import letter_grade # ── Color mappings ────────────────────────────────────────────────── diff --git a/src/badges.py b/src/github_repo_auditor/badges.py similarity index 99% rename from src/badges.py rename to src/github_repo_auditor/badges.py index fdc59aab..009a55d0 100644 --- a/src/badges.py +++ b/src/github_repo_auditor/badges.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.models import RepoAudit +from github_repo_auditor.models import RepoAudit # Badge definitions: (name, check_fn, action_hint) # check_fn receives (score_map, details_map, audit) diff --git a/src/baseline_context.py b/src/github_repo_auditor/baseline_context.py similarity index 100% rename from src/baseline_context.py rename to src/github_repo_auditor/baseline_context.py diff --git a/src/briefing.py b/src/github_repo_auditor/briefing.py similarity index 96% rename from src/briefing.py rename to src/github_repo_auditor/briefing.py index 0991d5c5..666cdad9 100644 --- a/src/briefing.py +++ b/src/github_repo_auditor/briefing.py @@ -11,10 +11,10 @@ from pathlib import Path from typing import TYPE_CHECKING -from src.narrative import NarrativeProvider, _resolve_provider +from github_repo_auditor.narrative import NarrativeProvider, _resolve_provider if TYPE_CHECKING: - from src.llm_cost import CostTracker + from github_repo_auditor.llm_cost import CostTracker logger = logging.getLogger(__name__) @@ -228,7 +228,7 @@ def _build_health_delta( } try: - from src.history import load_repo_score_history + from github_repo_auditor.history import load_repo_score_history score_history = load_repo_score_history() if not score_history: @@ -285,7 +285,7 @@ def _build_suggestions( top-N repo summaries are enriched with their nearest neighbors so that the LLM prompt can suggest cross-repo consolidation opportunities. """ - from src.operator_prefs import is_suppressed + from github_repo_auditor.operator_prefs import is_suppressed # Pick top-N by hotspot priority or lowest-scoring actionable repos sorted_audits = sorted(audits, key=lambda a: a.get("overall_score", 1.0)) @@ -420,12 +420,12 @@ def _build_initiatives( if output_dir is None: return [] - from src.initiatives import ( + from github_repo_auditor.initiatives import ( derive_status, initiatives_path, load_initiatives, ) - from src.maturity_tiers import compute_tier + from github_repo_auditor.maturity_tiers import compute_tier path = initiatives_path(output_dir) all_initiatives = load_initiatives(path) @@ -482,7 +482,7 @@ def _build_suggested_initiatives( briefing when the LLM is available to keep the section meaningful). """ try: - from src.suggest_initiatives import generate_suggestions + from github_repo_auditor.suggest_initiatives import generate_suggestions except ImportError: logger.warning("briefing: suggest_initiatives module not available; skipping") return [] @@ -519,7 +519,7 @@ def _build_dismissed_repos( """ if output_dir is None: return [] - from src.suggest_initiatives import dismissed_path, load_dismissed + from github_repo_auditor.suggest_initiatives import dismissed_path, load_dismissed path = dismissed_path(output_dir) if not path.exists(): @@ -571,11 +571,11 @@ def build_briefing( Pass *prefs* (loaded via ``operator_prefs.load_prefs``) to filter out suggestions for actions the operator has repeatedly declined. - Pass *semantic_index* (a :class:`~src.semantic_index.SemanticIndex` instance) + Pass *semantic_index* (a :class:`~github_repo_auditor.semantic_index.SemanticIndex` instance) to enrich the LLM suggestion prompt with cross-repo "related repos" context. If ``None``, behaviour is identical to S3.2 (no enrichment, backward compat). - Pass *cost_tracker* (a :class:`~src.llm_cost.CostTracker` instance) to record + Pass *cost_tracker* (a :class:`~github_repo_auditor.llm_cost.CostTracker` instance) to record LLM spend and enforce an optional budget cap. Pass *output_dir* to load initiative tracker data from @@ -648,7 +648,7 @@ def render_markdown(briefing: Briefing) -> str: lines.append("") today = _date.today() for ini in briefing.initiatives: - from src.maturity_tiers import tier_name as _tier_name + from github_repo_auditor.maturity_tiers import tier_name as _tier_name try: deadline_date = _date.fromisoformat(ini.deadline) @@ -668,7 +668,7 @@ def render_markdown(briefing: Briefing) -> str: # ── Section 0b: Suggested Initiatives ──────────────────────────────────── if briefing.suggested_initiatives: - from src.maturity_tiers import tier_name as _tier_name2 + from github_repo_auditor.maturity_tiers import tier_name as _tier_name2 n = len(briefing.suggested_initiatives) lines.append("## Suggested Initiatives") @@ -903,11 +903,11 @@ def generate_briefing( Generate the weekly operator briefing. Returns a dict with briefing_path (and optionally voice_path), or {skipped, reason}. - Pass *semantic_index* (a :class:`~src.semantic_index.SemanticIndex` instance) to + Pass *semantic_index* (a :class:`~github_repo_auditor.semantic_index.SemanticIndex` instance) to enrich suggestion prompts with cross-repo related-repos context. Defaults to ``None`` (backward-compatible, no enrichment). - Pass *cost_tracker* (a :class:`~src.llm_cost.CostTracker` instance) to record + Pass *cost_tracker* (a :class:`~github_repo_auditor.llm_cost.CostTracker` instance) to record LLM spend and enforce an optional budget cap. """ audits: list[dict] = report_data.get("audits", []) @@ -930,7 +930,7 @@ def generate_briefing( provider_obj, resolved_model_from_result = result # If user didn't specify a model, use cheap Haiku-equivalent instead of Sonnet default if model is None: - from src.narrative import DEFAULT_ANTHROPIC_MODEL, DEFAULT_GITHUB_MODELS_MODEL + from github_repo_auditor.narrative import DEFAULT_ANTHROPIC_MODEL, DEFAULT_GITHUB_MODELS_MODEL if resolved_model_from_result == DEFAULT_ANTHROPIC_MODEL: resolved_model = _ANTHROPIC_BRIEFING_MODEL @@ -947,7 +947,7 @@ def generate_briefing( ) # Load operator prefs to suppress repeatedly-rejected suggestions - from src.operator_prefs import load_prefs, prefs_path + from github_repo_auditor.operator_prefs import load_prefs, prefs_path prefs = load_prefs(prefs_path(Path(output_dir))) diff --git a/src/cache.py b/src/github_repo_auditor/cache.py similarity index 100% rename from src/cache.py rename to src/github_repo_auditor/cache.py diff --git a/src/catalog_validator.py b/src/github_repo_auditor/catalog_validator.py similarity index 96% rename from src/catalog_validator.py rename to src/github_repo_auditor/catalog_validator.py index 4431649b..777309f9 100644 --- a/src/catalog_validator.py +++ b/src/github_repo_auditor/catalog_validator.py @@ -1,4 +1,4 @@ -# src/catalog_validator.py +# src/github_repo_auditor/catalog_validator.py """Catalog completeness validator for portfolio-catalog.yaml entries (Arc H A3).""" from __future__ import annotations diff --git a/src/cli.py b/src/github_repo_auditor/cli.py similarity index 97% rename from src/cli.py rename to src/github_repo_auditor/cli.py index f5d5df11..20b6edae 100644 --- a/src/cli.py +++ b/src/github_repo_auditor/cli.py @@ -22,20 +22,20 @@ from datetime import datetime, timezone from pathlib import Path -from src.cli_mode_validation import validate_cli_mode_args -from src.cli_output import print_info -from src.app.run_audit import ( +from github_repo_auditor.cli_mode_validation import validate_cli_mode_args +from github_repo_auditor.cli_output import print_info +from github_repo_auditor.app.run_audit import ( _run_main_audit_cycle, _run_watch_mode, ) -from src.app.auto_apply import run_auto_apply_approved_mode -from src.app.automation_proposals import run_automation_proposals_mode -from src.app.initiatives import ( +from github_repo_auditor.app.auto_apply import run_auto_apply_approved_mode +from github_repo_auditor.app.automation_proposals import run_automation_proposals_mode +from github_repo_auditor.app.initiatives import ( run_close_initiative_mode, run_list_initiatives_mode, run_set_initiative_mode, ) -from src.app.initiative_suggestions import ( +from github_repo_auditor.app.initiative_suggestions import ( _run_accept_suggestion_mode, _run_dismiss_suggestion_mode, _run_dismissal_history_mode, @@ -44,19 +44,19 @@ _run_suggest_initiatives_mode, _run_undo_dismiss_mode, ) -from src.app.campaign_workflow import ( +from github_repo_auditor.app.campaign_workflow import ( _run_campaign_from_ledger_mode, _run_draft_readmes_mode, _run_plan_campaign_mode, ) -from src.app.portfolio_analysis import ( +from github_repo_auditor.app.portfolio_analysis import ( _run_context_triage_mode, _run_tier_gaps_export_mode, _run_tier_recalibration_report_mode, ) -from src.app.improvement_application import _run_apply_improvements_mode -from src.app.semantic_search import run_semantic_search_mode -from src.github_security_coverage import DEFAULT_EXPECTED_GITHUB_COHORT_COUNT +from github_repo_auditor.app.improvement_application import _run_apply_improvements_mode +from github_repo_auditor.app.semantic_search import run_semantic_search_mode +from github_repo_auditor.github_security_coverage import DEFAULT_EXPECTED_GITHUB_COHORT_COUNT # Emitted at most once per process when legacy flat invocation is used. @@ -1704,7 +1704,7 @@ def build_subcommand_parser() -> argparse.ArgumentParser: def _run_control_center_mode(args, parser) -> None: - from src.app.control_center import run_control_center_mode + from github_repo_auditor.app.control_center import run_control_center_mode run_control_center_mode(args, parser) @@ -1714,7 +1714,7 @@ def _run_control_center_mode(args, parser) -> None: def _run_approval_center_mode(args, parser) -> None: - from src.app.approval_center import run_approval_center_mode + from github_repo_auditor.app.approval_center import run_approval_center_mode run_approval_center_mode(args, parser) @@ -1722,7 +1722,7 @@ def _run_approval_center_mode(args, parser) -> None: def _run_approval_capture_mode(args, parser) -> None: - from src.app.approval_center import run_approval_capture_mode + from github_repo_auditor.app.approval_center import run_approval_capture_mode run_approval_capture_mode(args, parser) @@ -1730,19 +1730,19 @@ def _run_approval_capture_mode(args, parser) -> None: def _run_acknowledgment_capture_mode(args, parser) -> None: - from src.app.acknowledgments import run_acknowledgment_capture_mode + from github_repo_auditor.app.acknowledgments import run_acknowledgment_capture_mode run_acknowledgment_capture_mode(args, parser) def _run_doctor_mode(args, config_inspection) -> None: - from src.app.doctor import run_doctor_mode + from github_repo_auditor.app.doctor import run_doctor_mode run_doctor_mode(args, config_inspection) def _run_generate_manifest_mode(args, parser) -> None: - from src.app.report_only import run_generate_manifest_mode + from github_repo_auditor.app.report_only import run_generate_manifest_mode run_generate_manifest_mode(args, parser) @@ -1801,13 +1801,13 @@ def _run_generate_manifest_mode(args, parser) -> None: def _run_portfolio_truth_mode(args) -> None: - from src.app.portfolio_truth import run_portfolio_truth_mode + from github_repo_auditor.app.portfolio_truth import run_portfolio_truth_mode run_portfolio_truth_mode(args) def _run_portfolio_context_recovery_mode(args) -> None: - from src.app.portfolio_truth import run_portfolio_context_recovery_mode + from github_repo_auditor.app.portfolio_truth import run_portfolio_context_recovery_mode run_portfolio_context_recovery_mode(args) @@ -1850,7 +1850,7 @@ def _run_portfolio_context_recovery_mode(args) -> None: # ── Serve mode ─────────────────────────────────────────────────────────────── def _run_serve_mode(args: object) -> None: - from src.app.serve_mode import run_serve_mode + from github_repo_auditor.app.serve_mode import run_serve_mode run_serve_mode(args) @@ -2027,19 +2027,19 @@ def _rewrite_legacy_argv(argv: list[str]) -> tuple[list[str], bool]: def _run_security_burndown_mode(args) -> None: - from src.app.security_modes import run_security_burndown_mode + from github_repo_auditor.app.security_modes import run_security_burndown_mode run_security_burndown_mode(args) def _run_security_gate_mode(args) -> None: - from src.app.security_modes import run_security_gate_mode + from github_repo_auditor.app.security_modes import run_security_gate_mode run_security_gate_mode(args) def _run_pr_head_evidence_mode(args) -> None: - from src.app.pr_head_evidence import run_pr_head_evidence_mode + from github_repo_auditor.app.pr_head_evidence import run_pr_head_evidence_mode run_pr_head_evidence_mode(args) @@ -2098,7 +2098,7 @@ def main() -> None: parser = legacy_parser is_legacy = False # --serve / --help / empty: not a deprecated invocation - from src.approval_ledger import default_approval_reviewer + from github_repo_auditor.approval_ledger import default_approval_reviewer # ── Subcommand: serve ──────────────────────────────────────────── subcommand = getattr(args, "_subcommand", None) @@ -2107,7 +2107,7 @@ def main() -> None: return # Load config file and merge into args (CLI flags take precedence) - from src.config import inspect_config, merge_config_with_args + from github_repo_auditor.config import inspect_config, merge_config_with_args config_inspection = inspect_config(Path(args.config) if args.config else None) if config_inspection.data: @@ -2132,7 +2132,7 @@ def main() -> None: portfolio_context_recovery_mode = mode_state.portfolio_context_recovery_mode if getattr(args, "reset_prefs", False): - from src.operator_prefs import prefs_path, reset_prefs + from github_repo_auditor.operator_prefs import prefs_path, reset_prefs reset_prefs(prefs_path(Path(args.output_dir))) print_info("Operator prefs reset: suppression hints cleared.") diff --git a/src/cli_mode_validation.py b/src/github_repo_auditor/cli_mode_validation.py similarity index 100% rename from src/cli_mode_validation.py rename to src/github_repo_auditor/cli_mode_validation.py diff --git a/src/cli_output.py b/src/github_repo_auditor/cli_output.py similarity index 100% rename from src/cli_output.py rename to src/github_repo_auditor/cli_output.py diff --git a/src/cloner.py b/src/github_repo_auditor/cloner.py similarity index 99% rename from src/cloner.py rename to src/github_repo_auditor/cloner.py index df8542d9..2612c889 100644 --- a/src/cloner.py +++ b/src/github_repo_auditor/cloner.py @@ -10,7 +10,7 @@ from pathlib import Path from typing import Callable, Generator -from src.models import RepoMetadata +from github_repo_auditor.models import RepoMetadata logger = logging.getLogger(__name__) diff --git a/src/codex_restart_packet.py b/src/github_repo_auditor/codex_restart_packet.py similarity index 98% rename from src/codex_restart_packet.py rename to src/github_repo_auditor/codex_restart_packet.py index a3e991d0..8e05440c 100644 --- a/src/codex_restart_packet.py +++ b/src/github_repo_auditor/codex_restart_packet.py @@ -10,7 +10,7 @@ from pathlib import Path from typing import Any, Iterable -from src.portfolio_truth_types import TRUTH_LATEST_FILENAME +from github_repo_auditor.portfolio_truth_types import TRUTH_LATEST_FILENAME DEFAULT_WORKSPACE_ROOT = Path.home() / "Projects" DEFAULT_OPERATING_REPOS = ( @@ -293,7 +293,7 @@ def build_restart_packet( for repo in active_set ], "next_commands": [ - "python -m src.codex_restart_packet --workspace-root /Users/d/Projects", + 'python -m github_repo_auditor.codex_restart_packet --workspace-root "$HOME/Projects"', "git -C /Users/d/.local/share/personal-ops status --short --branch", "git -C /Users/d/.codex/codexkit status --short --branch", "audit report saagpatel --portfolio-truth --portfolio-truth-include-security", diff --git a/src/config.py b/src/github_repo_auditor/config.py similarity index 100% rename from src/config.py rename to src/github_repo_auditor/config.py diff --git a/src/context_quality.py b/src/github_repo_auditor/context_quality.py similarity index 94% rename from src/context_quality.py rename to src/github_repo_auditor/context_quality.py index bd1f7a55..d0ed73ab 100644 --- a/src/context_quality.py +++ b/src/github_repo_auditor/context_quality.py @@ -1,11 +1,11 @@ -# src/context_quality.py +# src/github_repo_auditor/context_quality.py """Composite context_quality_score computation (Arc H H.4).""" from __future__ import annotations from typing import Any -from src.catalog_validator import score_catalog_entry +from github_repo_auditor.catalog_validator import score_catalog_entry # Weights must sum to 1.0. _WEIGHTS = { diff --git a/src/control_center_presentation.py b/src/github_repo_auditor/control_center_presentation.py similarity index 99% rename from src/control_center_presentation.py rename to src/github_repo_auditor/control_center_presentation.py index c4773a73..8320a4f0 100644 --- a/src/control_center_presentation.py +++ b/src/github_repo_auditor/control_center_presentation.py @@ -2,7 +2,7 @@ from __future__ import annotations -from src.operator_control_center_artifacts import should_print_control_center_item +from github_repo_auditor.operator_control_center_artifacts import should_print_control_center_item def _control_center_next_step_hint() -> str: diff --git a/src/control_center_report_state.py b/src/github_repo_auditor/control_center_report_state.py similarity index 73% rename from src/control_center_report_state.py rename to src/github_repo_auditor/control_center_report_state.py index 4c3a955b..59e3474d 100644 --- a/src/control_center_report_state.py +++ b/src/github_repo_auditor/control_center_report_state.py @@ -5,14 +5,14 @@ from pathlib import Path from typing import Any -from src.diff import diff_reports -from src.governance_activation import build_governance_summary -from src.history import find_previous -from src.models import AuditReport -from src.operator_control_center import normalize_review_state -from src.report_operator_state import enrich_report_with_operator_state -from src.report_portfolio_catalog import apply_portfolio_catalog -from src.report_state import load_latest_report, report_from_dict +from github_repo_auditor.diff import diff_reports +from github_repo_auditor.governance_activation import build_governance_summary +from github_repo_auditor.history import find_previous +from github_repo_auditor.models import AuditReport +from github_repo_auditor.operator_control_center import normalize_review_state +from github_repo_auditor.report_operator_state import enrich_report_with_operator_state +from github_repo_auditor.report_portfolio_catalog import apply_portfolio_catalog +from github_repo_auditor.report_state import load_latest_report, report_from_dict def refresh_latest_report_state( diff --git a/src/control_center_snapshot.py b/src/github_repo_auditor/control_center_snapshot.py similarity index 87% rename from src/control_center_snapshot.py rename to src/github_repo_auditor/control_center_snapshot.py index 9932641c..7acf7323 100644 --- a/src/control_center_snapshot.py +++ b/src/github_repo_auditor/control_center_snapshot.py @@ -5,18 +5,18 @@ from pathlib import Path from typing import Any -from src.portfolio_catalog import ( +from github_repo_auditor.portfolio_catalog import ( DEFAULT_CATALOG_PATH, build_catalog_line, catalog_entry_for_repo, evaluate_intent_alignment, load_portfolio_catalog, ) -from src.report_enrichment import build_operator_focus -from src.report_operating_paths import apply_operating_paths -from src.report_portfolio_catalog import apply_portfolio_catalog -from src.report_scorecards import apply_scorecards -from src.report_state import report_from_dict +from github_repo_auditor.report_enrichment import build_operator_focus +from github_repo_auditor.report_operating_paths import apply_operating_paths +from github_repo_auditor.report_portfolio_catalog import apply_portfolio_catalog +from github_repo_auditor.report_scorecards import apply_scorecards +from github_repo_auditor.report_state import report_from_dict def enrich_control_center_snapshot_from_report( diff --git a/src/demo_portfolio.py b/src/github_repo_auditor/demo_portfolio.py similarity index 98% rename from src/demo_portfolio.py rename to src/github_repo_auditor/demo_portfolio.py index 99560cc9..2328cd6b 100644 --- a/src/demo_portfolio.py +++ b/src/github_repo_auditor/demo_portfolio.py @@ -18,24 +18,24 @@ from datetime import datetime, timedelta, timezone from typing import Any -from src.github_security_coverage import ( +from github_repo_auditor.github_security_coverage import ( GITHUB_SECURITY_RECEIPT_SCHEMA_VERSION, _remote_repository_result, ) -from src.portfolio_repository_state import _observed_result -from src.portfolio_pathing import build_operating_path_entry -from src.portfolio_truth_coverage import build_coverage_envelope -from src.portfolio_truth_decisions import build_project_decision -from src.portfolio_truth_metadata import ( +from github_repo_auditor.portfolio_repository_state import _observed_result +from github_repo_auditor.portfolio_pathing import build_operating_path_entry +from github_repo_auditor.portfolio_truth_coverage import build_coverage_envelope +from github_repo_auditor.portfolio_truth_decisions import build_project_decision +from github_repo_auditor.portfolio_truth_metadata import ( build_exclusions, build_input_envelope, build_source_summary, build_warnings, ) -from src.portfolio_truth_precedence import build_precedence_matrix -from src.portfolio_truth_provenance import REQUIRED_PROJECT_PROVENANCE_KEYS -from src.portfolio_truth_types import DERIVATION_POLICY_VERSION, SCHEMA_VERSION -from src.security_admission import derive_security_admission +from github_repo_auditor.portfolio_truth_precedence import build_precedence_matrix +from github_repo_auditor.portfolio_truth_provenance import REQUIRED_PROJECT_PROVENANCE_KEYS +from github_repo_auditor.portfolio_truth_types import DERIVATION_POLICY_VERSION, SCHEMA_VERSION +from github_repo_auditor.security_admission import derive_security_admission # The demo workspace is deliberately not a real filesystem path. DEMO_WORKSPACE_ROOT = "/demo-workspace" @@ -1064,7 +1064,7 @@ def build_snapshot( ), "projects": projects, } - from src.portfolio_truth_validate import canonicalize_truth_snapshot_payload + from github_repo_auditor.portfolio_truth_validate import canonicalize_truth_snapshot_payload return canonicalize_truth_snapshot_payload( payload, diff --git a/src/dep_graph.py b/src/github_repo_auditor/dep_graph.py similarity index 100% rename from src/dep_graph.py rename to src/github_repo_auditor/dep_graph.py diff --git a/src/diagnostics.py b/src/github_repo_auditor/diagnostics.py similarity index 98% rename from src/diagnostics.py rename to src/github_repo_auditor/diagnostics.py index 0e6862b6..ccaaf570 100644 --- a/src/diagnostics.py +++ b/src/github_repo_auditor/diagnostics.py @@ -8,16 +8,16 @@ from datetime import datetime, timezone from pathlib import Path -from src.baseline_context import ( +from github_repo_auditor.baseline_context import ( build_requested_baseline_context, compare_baseline_context, extract_baseline_context, format_mismatch_value, normalize_scoring_profile, ) -from src.config import ConfigInspection, inspect_config, validate_config_data -from src.excel_template import DEFAULT_TEMPLATE_PATH, TEMPLATE_INFO_SHEET, TEMPLATE_SHEETS -from src.portfolio_intelligence import DEFAULT_PROFILES +from github_repo_auditor.config import ConfigInspection, inspect_config, validate_config_data +from github_repo_auditor.excel_template import DEFAULT_TEMPLATE_PATH, TEMPLATE_INFO_SHEET, TEMPLATE_SHEETS +from github_repo_auditor.portfolio_intelligence import DEFAULT_PROFILES NOTION_CONFIG_PATH = Path("config") / "notion-config.json" FINGERPRINT_FILENAME = ".audit-fingerprints.json" @@ -478,7 +478,7 @@ def _add_excel_checks(checks: list[DiagnosticCheck], args) -> None: status="error", summary="Template workbook is missing.", details=f"Expected template at {template_path}.", - recommended_fix="Restore assets/excel/analyst-template.xlsx or use the stable --excel-mode standard path.", + recommended_fix="Restore src/github_repo_auditor/assets/excel/analyst-template.xlsx or use the stable --excel-mode standard path.", ) ) return diff --git a/src/diff.py b/src/github_repo_auditor/diff.py similarity index 99% rename from src/diff.py rename to src/github_repo_auditor/diff.py index 99922097..362f8f97 100644 --- a/src/diff.py +++ b/src/github_repo_auditor/diff.py @@ -4,7 +4,7 @@ from dataclasses import dataclass, field from pathlib import Path -from src.analyst_views import ( +from github_repo_auditor.analyst_views import ( build_profile_leaderboard, collection_membership_map, summarize_scenario_preview, diff --git a/src/draft_readmes.py b/src/github_repo_auditor/draft_readmes.py similarity index 96% rename from src/draft_readmes.py rename to src/github_repo_auditor/draft_readmes.py index 43fb8cca..c3de215c 100644 --- a/src/draft_readmes.py +++ b/src/github_repo_auditor/draft_readmes.py @@ -15,9 +15,9 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from src.llm_cost import CostTracker - from src.narrative import NarrativeProvider - from src.semantic_index import SemanticIndex + from github_repo_auditor.llm_cost import CostTracker + from github_repo_auditor.narrative import NarrativeProvider + from github_repo_auditor.semantic_index import SemanticIndex logger = logging.getLogger(__name__) @@ -264,7 +264,7 @@ def generate_draft( Catches BudgetExceededError and re-raises with repo context added to the message. """ - from src.llm_cost import BudgetExceededError + from github_repo_auditor.llm_cost import BudgetExceededError repo_name: str = context["repo_name"] prompt = _build_readme_prompt(context) @@ -351,7 +351,7 @@ def write_packets_to_ledger( `ledger_path` is the OUTPUT DIRECTORY (not a file path) because `save_approval_record` from warehouse.py expects the directory. """ - from src.warehouse import save_approval_record + from github_repo_auditor.warehouse import save_approval_record output_dir = Path(ledger_path) @@ -412,7 +412,7 @@ def load_approved_drafts( Hydrates each record's details_json back into a DraftReadmePacket. Skips records older than 30 days (likely stale). """ - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records all_records = load_approval_records(warehouse_path, username or "", limit=500) @@ -488,7 +488,7 @@ def mark_draft_applied( Uses INSERT OR REPLACE semantics — safe to call multiple times. On failure (missing warehouse), logs a warning rather than raising. """ - from src.warehouse import load_approval_records, save_approval_record + from github_repo_auditor.warehouse import load_approval_records, save_approval_record records = load_approval_records(output_dir, "", limit=500) @@ -529,7 +529,7 @@ def record_draft_apply_failure( """ import uuid - from src.warehouse import save_approval_followup_event + from github_repo_auditor.warehouse import save_approval_followup_event event = { "event_id": "af-" + str(uuid.uuid4())[:16], @@ -632,7 +632,7 @@ def write_section_packets_to_ledger( Callers that want the old single-record behaviour should use ``write_packets_to_ledger`` instead. Both can coexist. """ - from src.warehouse import save_approval_record + from github_repo_auditor.warehouse import save_approval_record output_dir = Path(ledger_path) @@ -690,7 +690,7 @@ def approve_section(record_id: str, output_dir: Path) -> None: Raises ``ValueError`` if the record is not found. """ - from src.warehouse import load_approval_records, save_approval_record + from github_repo_auditor.warehouse import load_approval_records, save_approval_record records = load_approval_records(output_dir, "", limit=500) matching = [ @@ -716,7 +716,7 @@ def reject_section(record_id: str, output_dir: Path, reason: str = "") -> None: Raises ``ValueError`` if the record is not found. """ - from src.warehouse import load_approval_records, save_approval_record + from github_repo_auditor.warehouse import load_approval_records, save_approval_record records = load_approval_records(output_dir, "", limit=500) matching = [ @@ -750,7 +750,7 @@ def load_approved_sectioned_packets( callers should check ``assemble_readme_from_approved_sections`` for ``None`` when every section ended up rejected. """ - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records all_records = load_approval_records(warehouse_path, "", limit=500) @@ -801,7 +801,7 @@ def assemble_readme_from_approved_sections(sections: list[dict]) -> str | None: def mark_section_packet_applied(packet_id: str, output_dir: Path) -> None: """Set ``status='applied'`` on every section sub-record sharing *packet_id*.""" - from src.warehouse import load_approval_records, save_approval_record + from github_repo_auditor.warehouse import load_approval_records, save_approval_record records = load_approval_records(output_dir, "", limit=500) for record in records: diff --git a/src/evidence_freshness_conformance.py b/src/github_repo_auditor/evidence_freshness_conformance.py similarity index 99% rename from src/evidence_freshness_conformance.py rename to src/github_repo_auditor/evidence_freshness_conformance.py index da727c41..282e0762 100644 --- a/src/evidence_freshness_conformance.py +++ b/src/github_repo_auditor/evidence_freshness_conformance.py @@ -258,7 +258,7 @@ def build_manifest() -> dict[str, Any]: "repository": OWNER_REPOSITORY, "artifact_path": VECTORS_RELATIVE_PATH, "artifact_sha256": vectors_sha256(), - "generator": "src.evidence_freshness_conformance:build_vectors", + "generator": "github_repo_auditor.evidence_freshness_conformance:build_vectors", "manifest_path": MANIFEST_RELATIVE_PATH, }, "versioning": { diff --git a/src/excel_action_items_helpers.py b/src/github_repo_auditor/excel_action_items_helpers.py similarity index 98% rename from src/excel_action_items_helpers.py rename to src/github_repo_auditor/excel_action_items_helpers.py index 025a5b56..57d33894 100644 --- a/src/excel_action_items_helpers.py +++ b/src/github_repo_auditor/excel_action_items_helpers.py @@ -4,7 +4,7 @@ from typing import Any -from src.scoring_dimensions import display_dimension +from github_repo_auditor.scoring_dimensions import display_dimension ACTION_ITEMS_HEADERS = ["#", "Repo", "Action", "Impact", "Effort", "Dimension"] EFFORT_MAP = { diff --git a/src/excel_action_sync_readiness_helpers.py b/src/github_repo_auditor/excel_action_sync_readiness_helpers.py similarity index 96% rename from src/excel_action_sync_readiness_helpers.py rename to src/github_repo_auditor/excel_action_sync_readiness_helpers.py index 63f40d9f..2788362b 100644 --- a/src/excel_action_sync_readiness_helpers.py +++ b/src/github_repo_auditor/excel_action_sync_readiness_helpers.py @@ -2,8 +2,8 @@ from __future__ import annotations -from src.report_enrichment import build_action_sync_command_hint -from src.terminology import ACTION_SYNC_CANONICAL_LABELS +from github_repo_auditor.report_enrichment import build_action_sync_command_hint +from github_repo_auditor.terminology import ACTION_SYNC_CANONICAL_LABELS def action_sync_readiness_rows(data: dict) -> list[tuple[str, object]]: diff --git a/src/excel_activity_sheet_helpers.py b/src/github_repo_auditor/excel_activity_sheet_helpers.py similarity index 100% rename from src/excel_activity_sheet_helpers.py rename to src/github_repo_auditor/excel_activity_sheet_helpers.py diff --git a/src/excel_all_repos_helpers.py b/src/github_repo_auditor/excel_all_repos_helpers.py similarity index 99% rename from src/excel_all_repos_helpers.py rename to src/github_repo_auditor/excel_all_repos_helpers.py index e840ca61..45f24257 100644 --- a/src/excel_all_repos_helpers.py +++ b/src/github_repo_auditor/excel_all_repos_helpers.py @@ -9,8 +9,8 @@ from openpyxl.styles import Alignment, Font from openpyxl.utils import get_column_letter from openpyxl.worksheet.datavalidation import DataValidation -from src.scoring_dimensions import display_dimension -from src.scorer import PARTIAL_RUN_BASIS_THRESHOLD, WEIGHTS +from github_repo_auditor.scoring_dimensions import display_dimension +from github_repo_auditor.scorer import PARTIAL_RUN_BASIS_THRESHOLD, WEIGHTS ALL_REPOS_HEADERS = [ "Repo", diff --git a/src/excel_badges_helpers.py b/src/github_repo_auditor/excel_badges_helpers.py similarity index 100% rename from src/excel_badges_helpers.py rename to src/github_repo_auditor/excel_badges_helpers.py diff --git a/src/excel_campaign_governance_helpers.py b/src/github_repo_auditor/excel_campaign_governance_helpers.py similarity index 100% rename from src/excel_campaign_governance_helpers.py rename to src/github_repo_auditor/excel_campaign_governance_helpers.py diff --git a/src/excel_changes_sheet_helpers.py b/src/github_repo_auditor/excel_changes_sheet_helpers.py similarity index 100% rename from src/excel_changes_sheet_helpers.py rename to src/github_repo_auditor/excel_changes_sheet_helpers.py diff --git a/src/excel_compare_scenario_helpers.py b/src/github_repo_auditor/excel_compare_scenario_helpers.py similarity index 100% rename from src/excel_compare_scenario_helpers.py rename to src/github_repo_auditor/excel_compare_scenario_helpers.py diff --git a/src/excel_dashboard_helpers.py b/src/github_repo_auditor/excel_dashboard_helpers.py similarity index 100% rename from src/excel_dashboard_helpers.py rename to src/github_repo_auditor/excel_dashboard_helpers.py diff --git a/src/excel_detail_helpers.py b/src/github_repo_auditor/excel_detail_helpers.py similarity index 95% rename from src/excel_detail_helpers.py rename to src/github_repo_auditor/excel_detail_helpers.py index 449935bc..11768ee9 100644 --- a/src/excel_detail_helpers.py +++ b/src/github_repo_auditor/excel_detail_helpers.py @@ -1,14 +1,14 @@ from openpyxl.styles import Font, PatternFill from openpyxl.worksheet.hyperlink import Hyperlink -from src.excel_styles import ( +from github_repo_auditor.excel_styles import ( TEAL, apply_zebra_stripes, auto_width, style_data_cell, style_header_row, ) -from src.excel_workbook_helpers import add_table, sheet_location +from github_repo_auditor.excel_workbook_helpers import add_table, sheet_location REVIEW_QUEUE_LANE_FILLS = { "blocked": "FEE2E2", diff --git a/src/excel_executive_summary_helpers.py b/src/github_repo_auditor/excel_executive_summary_helpers.py similarity index 99% rename from src/excel_executive_summary_helpers.py rename to src/github_repo_auditor/excel_executive_summary_helpers.py index 10462a6d..b0a9f7e8 100644 --- a/src/excel_executive_summary_helpers.py +++ b/src/github_repo_auditor/excel_executive_summary_helpers.py @@ -6,7 +6,7 @@ from openpyxl import Workbook -from src.excel_operator_closure_helpers import ( +from github_repo_auditor.excel_operator_closure_helpers import ( operator_calibration_values, operator_class_memory_values, operator_class_momentum_values, @@ -15,7 +15,7 @@ operator_class_transition_values, operator_transition_closure_values, ) -from src.excel_operator_follow_through_helpers import ( +from github_repo_auditor.excel_operator_follow_through_helpers import ( operator_follow_through_details, operator_follow_through_freshness_details, operator_follow_through_reacquisition_retirement_details, @@ -23,8 +23,8 @@ operator_follow_through_recovery_details, operator_follow_through_revalidation_recovery_details, ) -from src.excel_operator_queue_helpers import operator_focus_snapshot -from src.excel_operator_summary_helpers import ( +from github_repo_auditor.excel_operator_queue_helpers import operator_focus_snapshot +from github_repo_auditor.excel_operator_summary_helpers import ( operator_accountability_values, operator_confidence_values, operator_decision_memory_values, @@ -799,7 +799,7 @@ def build_executive_summary_workbook_sheet( section_font, subheader_font, ) -> None: - from src.analyst_views import build_analyst_context + from github_repo_auditor.analyst_views import build_analyst_context build_executive_summary_sheet( wb, diff --git a/src/excel_export.py b/src/github_repo_auditor/excel_export.py similarity index 83% rename from src/excel_export.py rename to src/github_repo_auditor/excel_export.py index b7c04b29..b239b9ea 100644 --- a/src/excel_export.py +++ b/src/github_repo_auditor/excel_export.py @@ -15,469 +15,469 @@ from openpyxl.formatting.rule import ColorScaleRule, DataBarRule, IconSetRule from openpyxl.utils import get_column_letter -from src.excel_action_items_helpers import ACTION_ITEMS_HEADERS -from src.excel_action_items_helpers import ( +from github_repo_auditor.excel_action_items_helpers import ACTION_ITEMS_HEADERS +from github_repo_auditor.excel_action_items_helpers import ( build_action_items_content as _build_action_items_content, ) -from src.excel_action_items_helpers import ( +from github_repo_auditor.excel_action_items_helpers import ( collect_action_items as _collect_action_items, ) -from src.excel_action_items_helpers import ( +from github_repo_auditor.excel_action_items_helpers import ( write_action_items_sections as _write_action_items_sections, ) -from src.excel_action_sync_readiness_helpers import ( +from github_repo_auditor.excel_action_sync_readiness_helpers import ( action_sync_readiness_rows as _action_sync_readiness_rows, ) -from src.excel_activity_sheet_helpers import ( +from github_repo_auditor.excel_activity_sheet_helpers import ( build_activity_rows as _build_activity_rows, ) -from src.excel_activity_sheet_helpers import ( +from github_repo_auditor.excel_activity_sheet_helpers import ( build_activity_sheet as _build_activity_sheet, ) -from src.excel_activity_sheet_helpers import ( +from github_repo_auditor.excel_activity_sheet_helpers import ( write_activity_table as _write_activity_table, ) -from src.excel_all_repos_helpers import ALL_REPOS_HEADERS -from src.excel_all_repos_helpers import ( +from github_repo_auditor.excel_all_repos_helpers import ALL_REPOS_HEADERS +from github_repo_auditor.excel_all_repos_helpers import ( apply_all_repos_postprocessing as _apply_all_repos_postprocessing, ) -from src.excel_all_repos_helpers import build_all_repo_rows as _build_all_repo_rows -from src.excel_all_repos_helpers import ( +from github_repo_auditor.excel_all_repos_helpers import build_all_repo_rows as _build_all_repo_rows +from github_repo_auditor.excel_all_repos_helpers import ( build_all_repos_workbook_sheet as _build_all_repos_workbook_sheet, ) -from src.excel_all_repos_helpers import ( +from github_repo_auditor.excel_all_repos_helpers import ( configure_all_repos_sheet as _configure_all_repos_sheet, ) -from src.excel_all_repos_helpers import ( +from github_repo_auditor.excel_all_repos_helpers import ( finalize_all_repos_layout as _finalize_all_repos_layout, ) -from src.excel_all_repos_helpers import ( +from github_repo_auditor.excel_all_repos_helpers import ( write_all_repo_rows as _write_all_repo_rows, ) -from src.excel_badges_helpers import ( +from github_repo_auditor.excel_badges_helpers import ( build_badge_distribution_chart as _build_badge_distribution_chart, ) -from src.excel_badges_helpers import ( +from github_repo_auditor.excel_badges_helpers import ( build_badges_sheet as _build_badges_sheet, ) -from src.excel_badges_helpers import ( +from github_repo_auditor.excel_badges_helpers import ( build_badges_sheet_content as _build_badges_sheet_content, ) -from src.excel_badges_helpers import ( +from github_repo_auditor.excel_badges_helpers import ( write_badges_sheet as _write_badges_sheet, ) -from src.excel_campaign_governance_helpers import ( +from github_repo_auditor.excel_campaign_governance_helpers import ( build_campaign_sheet_content as _build_campaign_sheet_content, ) -from src.excel_campaign_governance_helpers import ( +from github_repo_auditor.excel_campaign_governance_helpers import ( build_campaigns_sheet as _build_campaigns_sheet, ) -from src.excel_campaign_governance_helpers import ( +from github_repo_auditor.excel_campaign_governance_helpers import ( build_governance_controls_content as _build_governance_controls_content, ) -from src.excel_campaign_governance_helpers import ( +from github_repo_auditor.excel_campaign_governance_helpers import ( build_governance_controls_sheet as _build_governance_controls_sheet, ) -from src.excel_campaign_governance_helpers import ( +from github_repo_auditor.excel_campaign_governance_helpers import ( build_writeback_audit_content as _build_writeback_audit_content, ) -from src.excel_campaign_governance_helpers import ( +from github_repo_auditor.excel_campaign_governance_helpers import ( build_writeback_audit_sheet as _build_writeback_audit_sheet, ) -from src.excel_campaign_governance_helpers import ( +from github_repo_auditor.excel_campaign_governance_helpers import ( write_campaign_table as _write_campaign_table, ) -from src.excel_campaign_governance_helpers import ( +from github_repo_auditor.excel_campaign_governance_helpers import ( write_governance_controls_table as _write_governance_controls_table, ) -from src.excel_campaign_governance_helpers import ( +from github_repo_auditor.excel_campaign_governance_helpers import ( write_writeback_audit_table as _write_writeback_audit_table, ) -from src.excel_changes_sheet_helpers import ( +from github_repo_auditor.excel_changes_sheet_helpers import ( build_changes_sheet as _build_changes_sheet, ) -from src.excel_changes_sheet_helpers import ( +from github_repo_auditor.excel_changes_sheet_helpers import ( build_changes_sheet_content as _build_changes_sheet_content, ) -from src.excel_changes_sheet_helpers import ( +from github_repo_auditor.excel_changes_sheet_helpers import ( build_changes_sheet_sections as _build_changes_sheet_sections, ) -from src.excel_changes_sheet_helpers import ( +from github_repo_auditor.excel_changes_sheet_helpers import ( write_changes_sheet_sections as _write_changes_sheet_sections, ) -from src.excel_compare_scenario_helpers import ( +from github_repo_auditor.excel_compare_scenario_helpers import ( build_compare_lens_rows as _build_compare_lens_rows, ) -from src.excel_compare_scenario_helpers import ( +from github_repo_auditor.excel_compare_scenario_helpers import ( build_compare_repo_rows as _build_compare_repo_rows, ) -from src.excel_compare_scenario_helpers import ( +from github_repo_auditor.excel_compare_scenario_helpers import ( build_compare_sheet as _build_compare_sheet_helper, ) -from src.excel_compare_scenario_helpers import ( +from github_repo_auditor.excel_compare_scenario_helpers import ( build_scenario_planner_rows as _build_scenario_planner_rows, ) -from src.excel_compare_scenario_helpers import ( +from github_repo_auditor.excel_compare_scenario_helpers import ( build_scenario_planner_sheet as _build_scenario_planner_sheet, ) -from src.excel_compare_scenario_helpers import ( +from github_repo_auditor.excel_compare_scenario_helpers import ( write_compare_sections as _write_compare_sections, ) -from src.excel_compare_scenario_helpers import ( +from github_repo_auditor.excel_compare_scenario_helpers import ( write_scenario_planner_sections as _write_scenario_planner_sections, ) -from src.excel_dashboard_helpers import ( +from github_repo_auditor.excel_dashboard_helpers import ( build_dashboard_bubble_chart as _build_dashboard_bubble_chart, ) -from src.excel_dashboard_helpers import ( +from github_repo_auditor.excel_dashboard_helpers import ( build_dashboard_kpi_specs as _build_dashboard_kpi_specs, ) -from src.excel_dashboard_helpers import ( +from github_repo_auditor.excel_dashboard_helpers import ( build_dashboard_laggard_rows as _build_dashboard_laggard_rows, ) -from src.excel_dashboard_helpers import ( +from github_repo_auditor.excel_dashboard_helpers import ( build_dashboard_portfolio_trend_sparkline as _build_dashboard_portfolio_trend_sparkline, ) -from src.excel_dashboard_helpers import ( +from github_repo_auditor.excel_dashboard_helpers import ( build_dashboard_ranked_content as _build_dashboard_ranked_content, ) -from src.excel_dashboard_helpers import ( +from github_repo_auditor.excel_dashboard_helpers import ( build_dashboard_story_blocks as _build_dashboard_story_blocks, ) -from src.excel_dashboard_helpers import ( +from github_repo_auditor.excel_dashboard_helpers import ( build_dashboard_top_attention_rows as _build_dashboard_top_attention_rows, ) -from src.excel_dashboard_helpers import ( +from github_repo_auditor.excel_dashboard_helpers import ( build_dashboard_top_opportunity_rows as _build_dashboard_top_opportunity_rows, ) -from src.excel_dashboard_helpers import ( +from github_repo_auditor.excel_dashboard_helpers import ( build_dashboard_workbook_sheet as _build_dashboard_workbook_sheet, ) -from src.excel_dashboard_helpers import ( +from github_repo_auditor.excel_dashboard_helpers import ( write_dashboard_header as _write_dashboard_header, ) -from src.excel_dashboard_helpers import ( +from github_repo_auditor.excel_dashboard_helpers import ( write_dashboard_ranked_sections as _write_dashboard_ranked_sections, ) -from src.excel_dashboard_helpers import ( +from github_repo_auditor.excel_dashboard_helpers import ( write_dashboard_story_sections as _write_dashboard_story_sections, ) -from src.excel_dashboard_helpers import ( +from github_repo_auditor.excel_dashboard_helpers import ( write_dashboard_visual_sections as _write_dashboard_visual_sections, ) -from src.excel_detail_helpers import apply_lane_row_fill as _apply_lane_row_fill -from src.excel_executive_summary_helpers import ( +from github_repo_auditor.excel_detail_helpers import apply_lane_row_fill as _apply_lane_row_fill +from github_repo_auditor.excel_executive_summary_helpers import ( build_executive_operator_context as _build_executive_operator_context, ) -from src.excel_executive_summary_helpers import ( +from github_repo_auditor.excel_executive_summary_helpers import ( build_executive_summary_workbook_sheet as _build_executive_summary_workbook_sheet, ) -from src.excel_export_registry_helpers import ( +from github_repo_auditor.excel_export_registry_helpers import ( build_excel_workbook_runtime as _build_excel_workbook_runtime, ) -from src.excel_export_runner_helpers import ( +from github_repo_auditor.excel_export_runner_helpers import ( build_excel_workbook as _build_excel_workbook_runner, ) -from src.excel_export_runner_helpers import ( +from github_repo_auditor.excel_export_runner_helpers import ( build_template_sparkline_specs as _build_template_sparkline_specs_helper, ) -from src.excel_export_runner_helpers import ( +from github_repo_auditor.excel_export_runner_helpers import ( export_excel_workbook as _export_excel_workbook, ) -from src.excel_export_truth_helpers import ( +from github_repo_auditor.excel_export_truth_helpers import ( load_risk_truth as _load_risk_truth, ) -from src.excel_heatmap_sheet_helpers import HEATMAP_HEADERS -from src.excel_heatmap_sheet_helpers import ( +from github_repo_auditor.excel_heatmap_sheet_helpers import HEATMAP_HEADERS +from github_repo_auditor.excel_heatmap_sheet_helpers import ( build_heatmap_rows as _build_heatmap_rows, ) -from src.excel_heatmap_sheet_helpers import ( +from github_repo_auditor.excel_heatmap_sheet_helpers import ( build_heatmap_sheet as _build_heatmap_sheet, ) -from src.excel_heatmap_sheet_helpers import ( +from github_repo_auditor.excel_heatmap_sheet_helpers import ( write_heatmap_table as _write_heatmap_table, ) -from src.excel_hidden_data_content_helpers import ( +from github_repo_auditor.excel_hidden_data_content_helpers import ( build_hidden_data_payload as _build_hidden_data_payload, ) -from src.excel_hidden_data_content_helpers import ( +from github_repo_auditor.excel_hidden_data_content_helpers import ( build_hidden_data_sheets as _build_hidden_data_sheets_helper, ) -from src.excel_hidden_data_rows import build_core_hidden_rows as _build_core_hidden_rows -from src.excel_hidden_sheet_writer import write_hidden_data_tables as _write_hidden_data_tables -from src.excel_hotspot_sheet_helpers import ( +from github_repo_auditor.excel_hidden_data_rows import build_core_hidden_rows as _build_core_hidden_rows +from github_repo_auditor.excel_hidden_sheet_writer import write_hidden_data_tables as _write_hidden_data_tables +from github_repo_auditor.excel_hotspot_sheet_helpers import ( IMPLEMENTATION_HOTSPOTS_HEADERS, ) -from src.excel_hotspot_sheet_helpers import ( +from github_repo_auditor.excel_hotspot_sheet_helpers import ( build_dependency_graph_sheet as _build_dependency_graph_sheet, ) -from src.excel_hotspot_sheet_helpers import ( +from github_repo_auditor.excel_hotspot_sheet_helpers import ( build_hotspot_rows as _build_hotspot_rows, ) -from src.excel_hotspot_sheet_helpers import ( +from github_repo_auditor.excel_hotspot_sheet_helpers import ( build_hotspots_sheet as _build_hotspots_sheet, ) -from src.excel_hotspot_sheet_helpers import ( +from github_repo_auditor.excel_hotspot_sheet_helpers import ( build_implementation_hotspot_rows as _build_implementation_hotspot_rows, ) -from src.excel_hotspot_sheet_helpers import ( +from github_repo_auditor.excel_hotspot_sheet_helpers import ( build_implementation_hotspots_sheet as _build_implementation_hotspots_sheet, ) -from src.excel_hotspot_sheet_helpers import ( +from github_repo_auditor.excel_hotspot_sheet_helpers import ( write_hotspot_table as _write_hotspot_table, ) -from src.excel_initiative_tracker_helpers import ( +from github_repo_auditor.excel_initiative_tracker_helpers import ( build_initiative_tracker_sheet as _build_initiative_tracker_sheet, ) -from src.excel_ledger_sheet_helpers import ( +from github_repo_auditor.excel_ledger_sheet_helpers import ( HISTORICAL_INTELLIGENCE_HEADERS, ) -from src.excel_ledger_sheet_helpers import ( +from github_repo_auditor.excel_ledger_sheet_helpers import ( build_approval_ledger_content as _build_approval_ledger_content, ) -from src.excel_ledger_sheet_helpers import ( +from github_repo_auditor.excel_ledger_sheet_helpers import ( build_approval_ledger_sheet as _build_approval_ledger_sheet, ) -from src.excel_ledger_sheet_helpers import ( +from github_repo_auditor.excel_ledger_sheet_helpers import ( build_governance_audit_rows as _build_governance_audit_rows, ) -from src.excel_ledger_sheet_helpers import ( +from github_repo_auditor.excel_ledger_sheet_helpers import ( build_governance_audit_sheet as _build_governance_audit_sheet, ) -from src.excel_ledger_sheet_helpers import ( +from github_repo_auditor.excel_ledger_sheet_helpers import ( build_historical_intelligence_content as _build_historical_intelligence_content, ) -from src.excel_ledger_sheet_helpers import ( +from github_repo_auditor.excel_ledger_sheet_helpers import ( build_review_history_rows as _build_review_history_rows, ) -from src.excel_ledger_sheet_helpers import ( +from github_repo_auditor.excel_ledger_sheet_helpers import ( build_review_history_sheet as _build_review_history_sheet_helper, ) -from src.excel_ledger_sheet_helpers import ( +from github_repo_auditor.excel_ledger_sheet_helpers import ( write_approval_ledger_sections as _write_approval_ledger_sections, ) -from src.excel_ledger_sheet_helpers import ( +from github_repo_auditor.excel_ledger_sheet_helpers import ( write_governance_audit_table as _write_governance_audit_table, ) -from src.excel_ledger_sheet_helpers import ( +from github_repo_auditor.excel_ledger_sheet_helpers import ( write_historical_intelligence_sheet as _write_historical_intelligence_sheet, ) -from src.excel_ledger_sheet_helpers import ( +from github_repo_auditor.excel_ledger_sheet_helpers import ( write_review_history_table as _write_review_history_table, ) -from src.excel_navigation_helpers import build_navigation as _build_navigation -from src.excel_navigation_helpers import inject_sheet_navigation as _inject_sheet_navigation -from src.excel_operator_hidden_rows import build_operator_hidden_rows as _build_operator_hidden_rows -from src.excel_operator_outcomes_helpers import ( +from github_repo_auditor.excel_navigation_helpers import build_navigation as _build_navigation +from github_repo_auditor.excel_navigation_helpers import inject_sheet_navigation as _inject_sheet_navigation +from github_repo_auditor.excel_operator_hidden_rows import build_operator_hidden_rows as _build_operator_hidden_rows +from github_repo_auditor.excel_operator_outcomes_helpers import ( build_operator_outcomes_content as _build_operator_outcomes_content, ) -from src.excel_operator_outcomes_helpers import ( +from github_repo_auditor.excel_operator_outcomes_helpers import ( write_operator_outcomes_sections as _write_operator_outcomes_sections, ) -from src.excel_operator_queue_helpers import ( +from github_repo_auditor.excel_operator_queue_helpers import ( format_lane_counts as _format_lane_counts, ) -from src.excel_operator_queue_helpers import ( +from github_repo_auditor.excel_operator_queue_helpers import ( format_repo_rollup_counts as _format_repo_rollup_counts, ) -from src.excel_operator_queue_helpers import ( +from github_repo_auditor.excel_operator_queue_helpers import ( operator_counts as _operator_counts, ) -from src.excel_operator_queue_helpers import ( +from github_repo_auditor.excel_operator_queue_helpers import ( ordered_queue_items as _ordered_queue_items, ) -from src.excel_operator_queue_helpers import ( +from github_repo_auditor.excel_operator_queue_helpers import ( summarize_top_actions as _summarize_top_actions, ) -from src.excel_operator_queue_helpers import ( +from github_repo_auditor.excel_operator_queue_helpers import ( summarize_top_issue_families as _summarize_top_issue_families, ) -from src.excel_portfolio_misc_helpers import ( +from github_repo_auditor.excel_portfolio_misc_helpers import ( REPO_PROFILE_RADAR_DIMS, REPO_PROFILE_RADAR_LABELS, RISK_SUMMARY_HEADERS, ) -from src.excel_portfolio_misc_helpers import ( +from github_repo_auditor.excel_portfolio_misc_helpers import ( add_repo_profile_charts as _add_repo_profile_charts, ) -from src.excel_portfolio_misc_helpers import ( +from github_repo_auditor.excel_portfolio_misc_helpers import ( build_registry_content as _build_registry_content, ) -from src.excel_portfolio_misc_helpers import ( +from github_repo_auditor.excel_portfolio_misc_helpers import ( build_registry_sheet as _build_registry_sheet, ) -from src.excel_portfolio_misc_helpers import ( +from github_repo_auditor.excel_portfolio_misc_helpers import ( build_repo_profile_matrix as _build_repo_profile_matrix, ) -from src.excel_portfolio_misc_helpers import ( +from github_repo_auditor.excel_portfolio_misc_helpers import ( build_risk_summary_rows as _build_risk_summary_rows, ) -from src.excel_portfolio_misc_helpers import ( +from github_repo_auditor.excel_portfolio_misc_helpers import ( build_tech_stack_sheet as _build_tech_stack_sheet, ) -from src.excel_portfolio_misc_helpers import ( +from github_repo_auditor.excel_portfolio_misc_helpers import ( build_tier_breakdown_sections as _build_tier_breakdown_sections, ) -from src.excel_portfolio_misc_helpers import ( +from github_repo_auditor.excel_portfolio_misc_helpers import ( build_tier_breakdown_sheet as _build_tier_breakdown_sheet, ) -from src.excel_portfolio_misc_helpers import ( +from github_repo_auditor.excel_portfolio_misc_helpers import ( write_registry_sections as _write_registry_sections, ) -from src.excel_portfolio_misc_helpers import ( +from github_repo_auditor.excel_portfolio_misc_helpers import ( write_repo_profile_matrix as _write_repo_profile_matrix, ) -from src.excel_portfolio_misc_helpers import ( +from github_repo_auditor.excel_portfolio_misc_helpers import ( write_risk_summary_table as _write_risk_summary_table, ) -from src.excel_portfolio_misc_helpers import ( +from github_repo_auditor.excel_portfolio_misc_helpers import ( write_tier_breakdown_sections as _write_tier_breakdown_sections, ) -from src.excel_portfolio_misc_helpers import ( +from github_repo_auditor.excel_portfolio_misc_helpers import ( write_trends_sheet as _write_trends_sheet, ) -from src.excel_portfolio_sheet_helpers import ( +from github_repo_auditor.excel_portfolio_sheet_helpers import ( build_portfolio_catalog_sheet as _build_portfolio_catalog_sheet_helper, ) -from src.excel_portfolio_sheet_helpers import ( +from github_repo_auditor.excel_portfolio_sheet_helpers import ( build_portfolio_explorer_sheet as _build_portfolio_explorer_sheet, ) -from src.excel_portfolio_sheet_helpers import ( +from github_repo_auditor.excel_portfolio_sheet_helpers import ( build_scorecard_table_rows as _build_scorecard_table_rows, ) -from src.excel_portfolio_sheet_helpers import ( +from github_repo_auditor.excel_portfolio_sheet_helpers import ( build_scorecards_sheet as _build_scorecards_sheet_helper, ) -from src.excel_portfolio_sheet_helpers import ( +from github_repo_auditor.excel_portfolio_sheet_helpers import ( write_portfolio_table as _write_portfolio_table, ) -from src.excel_print_pack_helpers import ( +from github_repo_auditor.excel_print_pack_helpers import ( print_pack_workflow_guidance_rows as _print_pack_workflow_guidance_rows, ) -from src.excel_print_pack_layout_helpers import ( +from github_repo_auditor.excel_print_pack_layout_helpers import ( build_print_pack_sheet_content as _build_print_pack_sheet_content, ) -from src.excel_print_pack_layout_helpers import ( +from github_repo_auditor.excel_print_pack_layout_helpers import ( build_print_pack_workbook_sheet as _build_print_pack_workbook_sheet, ) -from src.excel_print_pack_layout_helpers import ( +from github_repo_auditor.excel_print_pack_layout_helpers import ( write_print_pack_sheet as _write_print_pack_sheet, ) -from src.excel_profile_trend_helpers import ( +from github_repo_auditor.excel_profile_trend_helpers import ( build_by_collection_rows as _build_by_collection_rows, ) -from src.excel_profile_trend_helpers import ( +from github_repo_auditor.excel_profile_trend_helpers import ( build_by_collection_sheet as _build_by_collection_sheet, ) -from src.excel_profile_trend_helpers import ( +from github_repo_auditor.excel_profile_trend_helpers import ( build_by_lens_rows as _build_by_lens_rows, ) -from src.excel_profile_trend_helpers import ( +from github_repo_auditor.excel_profile_trend_helpers import ( build_by_lens_sheet as _build_by_lens_sheet, ) -from src.excel_profile_trend_helpers import ( +from github_repo_auditor.excel_profile_trend_helpers import ( build_trend_summary_content as _build_trend_summary_content, ) -from src.excel_profile_trend_helpers import ( +from github_repo_auditor.excel_profile_trend_helpers import ( build_trend_summary_sheet as _build_trend_summary_sheet, ) -from src.excel_profile_trend_helpers import ( +from github_repo_auditor.excel_profile_trend_helpers import ( write_trend_summary_sections as _write_trend_summary_sections, ) -from src.excel_quick_wins_helpers import ( +from github_repo_auditor.excel_quick_wins_helpers import ( build_quick_wins_rows as _build_quick_wins_rows, ) -from src.excel_quick_wins_helpers import ( +from github_repo_auditor.excel_quick_wins_helpers import ( build_quick_wins_sheet as _build_quick_wins_sheet, ) -from src.excel_quick_wins_helpers import ( +from github_repo_auditor.excel_quick_wins_helpers import ( write_quick_wins_sheet as _write_quick_wins_sheet, ) -from src.excel_repo_data_helpers import repo_detail_rows as _repo_detail_rows -from src.excel_repo_data_helpers import run_change_rows as _run_change_rows -from src.excel_repo_detail_helpers import ( +from github_repo_auditor.excel_repo_data_helpers import repo_detail_rows as _repo_detail_rows +from github_repo_auditor.excel_repo_data_helpers import run_change_rows as _run_change_rows +from github_repo_auditor.excel_repo_detail_helpers import ( build_repo_detail_sheet as _build_repo_detail_sheet, ) -from src.excel_report_helpers import display_operator_state as _display_operator_state -from src.excel_report_helpers import generate_narrative as _generate_narrative -from src.excel_review_queue_helpers import ( +from github_repo_auditor.excel_report_helpers import display_operator_state as _display_operator_state +from github_repo_auditor.excel_report_helpers import generate_narrative as _generate_narrative +from github_repo_auditor.excel_review_queue_helpers import ( build_review_queue_sheet_content as _build_review_queue_sheet_content, ) -from src.excel_review_queue_helpers import ( +from github_repo_auditor.excel_review_queue_helpers import ( build_review_queue_workbook_sheet as _build_review_queue_workbook_sheet, ) -from src.excel_review_queue_helpers import ( +from github_repo_auditor.excel_review_queue_helpers import ( write_review_queue_overview as _write_review_queue_overview, ) -from src.excel_review_queue_helpers import ( +from github_repo_auditor.excel_review_queue_helpers import ( write_review_queue_table as _write_review_queue_table, ) -from src.excel_review_queue_table_helpers import ( +from github_repo_auditor.excel_review_queue_table_helpers import ( REVIEW_QUEUE_CENTER_ALIGNED_COLUMNS, REVIEW_QUEUE_GUIDANCE, REVIEW_QUEUE_HEADERS, ) -from src.excel_review_queue_table_helpers import ( +from github_repo_auditor.excel_review_queue_table_helpers import ( build_review_queue_table_rows as _build_review_queue_table_rows, ) -from src.excel_run_changes_helpers import ( +from github_repo_auditor.excel_run_changes_helpers import ( build_run_changes_content as _build_run_changes_content, ) -from src.excel_run_changes_helpers import ( +from github_repo_auditor.excel_run_changes_helpers import ( build_run_changes_sheet as _build_run_changes_sheet, ) -from src.excel_run_changes_helpers import ( +from github_repo_auditor.excel_run_changes_helpers import ( normalize_run_changes_sections as _normalize_run_changes_sections, ) -from src.excel_run_changes_helpers import ( +from github_repo_auditor.excel_run_changes_helpers import ( write_run_changes_sections as _write_run_changes_sections, ) -from src.excel_run_changes_helpers import ( +from github_repo_auditor.excel_run_changes_helpers import ( write_run_changes_summary as _write_run_changes_summary, ) -from src.excel_score_explainer_helpers import ( +from github_repo_auditor.excel_score_explainer_helpers import ( build_score_explainer_content as _build_score_explainer_content, ) -from src.excel_score_explainer_helpers import ( +from github_repo_auditor.excel_score_explainer_helpers import ( build_score_explainer_sheet as _build_score_explainer_sheet, ) -from src.excel_score_explainer_helpers import ( +from github_repo_auditor.excel_score_explainer_helpers import ( write_score_explainer_sections as _write_score_explainer_sections, ) -from src.excel_security_sheet_helpers import ( +from github_repo_auditor.excel_security_sheet_helpers import ( SECURITY_CONTROLS_HEADERS, SECURITY_DEBT_HEADERS, SECURITY_HEADERS, SUPPLY_CHAIN_HEADERS, ) -from src.excel_security_sheet_helpers import ( +from github_repo_auditor.excel_security_sheet_helpers import ( build_security_controls_rows as _build_security_controls_rows, ) -from src.excel_security_sheet_helpers import ( +from github_repo_auditor.excel_security_sheet_helpers import ( build_security_controls_sheet as _build_security_controls_sheet, ) -from src.excel_security_sheet_helpers import ( +from github_repo_auditor.excel_security_sheet_helpers import ( build_security_debt_rows as _build_security_debt_rows, ) -from src.excel_security_sheet_helpers import ( +from github_repo_auditor.excel_security_sheet_helpers import ( build_security_debt_sheet as _build_security_debt_sheet, ) -from src.excel_security_sheet_helpers import build_security_sheet as _build_security_sheet -from src.excel_security_sheet_helpers import ( +from github_repo_auditor.excel_security_sheet_helpers import build_security_sheet as _build_security_sheet +from github_repo_auditor.excel_security_sheet_helpers import ( build_security_sheet_rows as _build_security_sheet_rows, ) -from src.excel_security_sheet_helpers import ( +from github_repo_auditor.excel_security_sheet_helpers import ( build_supply_chain_rows as _build_supply_chain_rows, ) -from src.excel_security_sheet_helpers import build_supply_chain_sheet as _build_supply_chain_sheet -from src.excel_security_sheet_helpers import ( +from github_repo_auditor.excel_security_sheet_helpers import build_supply_chain_sheet as _build_supply_chain_sheet +from github_repo_auditor.excel_security_sheet_helpers import ( write_security_table as _write_security_table, ) -from src.excel_sheet_layout_helpers import set_sheet_header as _set_sheet_header -from src.excel_sheet_layout_helpers import write_instruction_banner as _write_instruction_banner -from src.excel_sheet_layout_helpers import write_key_value_block as _write_key_value_block -from src.excel_sheet_layout_helpers import write_ranked_list as _write_ranked_list -from src.excel_styles import ( +from github_repo_auditor.excel_sheet_layout_helpers import set_sheet_header as _set_sheet_header +from github_repo_auditor.excel_sheet_layout_helpers import write_instruction_banner as _write_instruction_banner +from github_repo_auditor.excel_sheet_layout_helpers import write_key_value_block as _write_key_value_block +from github_repo_auditor.excel_sheet_layout_helpers import write_ranked_list as _write_ranked_list +from github_repo_auditor.excel_styles import ( CENTER, GRADE_COLORS, HEATMAP_AMBER, @@ -505,7 +505,7 @@ style_header_row, write_kpi_card, ) -from src.excel_template import ( +from github_repo_auditor.excel_template import ( DEFAULT_TEMPLATE_PATH, TEMPLATE_INFO_SHEET, TREND_HISTORY_WINDOW, @@ -515,32 +515,32 @@ load_workbook_allowing_native_sparklines, resolve_template_path, ) -from src.excel_template_info_helpers import ( +from github_repo_auditor.excel_template_info_helpers import ( build_dashboard_generated_label as _build_dashboard_generated_label, ) -from src.excel_template_info_helpers import ( +from github_repo_auditor.excel_template_info_helpers import ( build_named_range_targets as _build_named_range_targets, ) -from src.excel_template_info_helpers import ( +from github_repo_auditor.excel_template_info_helpers import ( build_template_info_rows as _build_template_info_rows, ) -from src.excel_timeline_helpers import ( +from github_repo_auditor.excel_timeline_helpers import ( extend_portfolio_trend_with_current as _extend_portfolio_trend_with_current, ) -from src.excel_timeline_helpers import ( +from github_repo_auditor.excel_timeline_helpers import ( extend_score_history_with_current as _extend_score_history_with_current, ) -from src.excel_timeline_helpers import review_status_counts as _review_status_counts -from src.excel_workbook_helpers import add_table as _add_table -from src.excel_workbook_helpers import clear_worksheet as _clear_worksheet -from src.excel_workbook_helpers import configure_sheet_view as _configure_sheet_view -from src.excel_workbook_helpers import get_or_create_sheet as _get_or_create_sheet -from src.excel_workbook_helpers import set_autofilter as _set_autofilter -from src.excel_workbook_helpers import set_defined_name as _set_defined_name -from src.excel_workbook_helpers import sheet_location as _sheet_location -from src.excel_workbook_rollups import build_workbook_rollups as _build_workbook_rollups -from src.history import load_trend_data -from src.report_enrichment import ( +from github_repo_auditor.excel_timeline_helpers import review_status_counts as _review_status_counts +from github_repo_auditor.excel_workbook_helpers import add_table as _add_table +from github_repo_auditor.excel_workbook_helpers import clear_worksheet as _clear_worksheet +from github_repo_auditor.excel_workbook_helpers import configure_sheet_view as _configure_sheet_view +from github_repo_auditor.excel_workbook_helpers import get_or_create_sheet as _get_or_create_sheet +from github_repo_auditor.excel_workbook_helpers import set_autofilter as _set_autofilter +from github_repo_auditor.excel_workbook_helpers import set_defined_name as _set_defined_name +from github_repo_auditor.excel_workbook_helpers import sheet_location as _sheet_location +from github_repo_auditor.excel_workbook_rollups import build_workbook_rollups as _build_workbook_rollups +from github_repo_auditor.history import load_trend_data +from github_repo_auditor.report_enrichment import ( build_maturity_gap_summary, build_operating_paths_summary, build_portfolio_catalog_summary, @@ -553,9 +553,9 @@ build_trust_actionability_summary, build_weekly_review_pack, ) -from src.sparkline import sparkline as render_sparkline -from src.terminology import ACTION_SYNC_CANONICAL_LABELS -from src.weekly_scheduling_overlay import resolve_weekly_story_value +from github_repo_auditor.sparkline import sparkline as render_sparkline +from github_repo_auditor.terminology import ACTION_SYNC_CANONICAL_LABELS +from github_repo_auditor.weekly_scheduling_overlay import resolve_weekly_story_value # Tier display order TIER_ORDER = ["shipped", "functional", "wip", "skeleton", "abandoned"] @@ -840,7 +840,7 @@ def _build_trends(wb: Workbook, data: dict, trend_data: list[dict] | None = None def _build_tier_breakdown(wb: Workbook, data: dict) -> None: from openpyxl.styles import Font as XFont - from src.excel_styles import TIER_FILLS + from github_repo_auditor.excel_styles import TIER_FILLS _build_tier_breakdown_sheet( wb, @@ -911,7 +911,7 @@ def _build_reconciliation(wb: Workbook, data: dict) -> None: def _build_score_explainer(wb: Workbook) -> None: """Static reference sheet explaining the scoring system.""" - from src.scorer import COMPLETENESS_TIERS, GRADE_THRESHOLDS, WEIGHTS + from github_repo_auditor.scorer import COMPLETENESS_TIERS, GRADE_THRESHOLDS, WEIGHTS _build_score_explainer_sheet( wb, @@ -1044,7 +1044,7 @@ def _build_bubble_on_dashboard(ws, data: dict) -> None: def _build_dependency_graph(wb: Workbook, data: dict) -> None: - from src.dep_graph import build_dependency_graph + from github_repo_auditor.dep_graph import build_dependency_graph _build_dependency_graph_sheet( wb, @@ -1311,7 +1311,7 @@ def _build_portfolio_explorer( portfolio_profile: str = "default", collection: str | None = None, ) -> None: - from src.analyst_views import build_analyst_context + from github_repo_auditor.analyst_views import build_analyst_context _build_portfolio_explorer_sheet( wb, @@ -1380,7 +1380,7 @@ def _build_by_lens( portfolio_profile: str = "default", collection: str | None = None, ) -> None: - from src.analyst_views import build_analyst_context + from github_repo_auditor.analyst_views import build_analyst_context _build_by_lens_sheet( wb, @@ -1409,7 +1409,7 @@ def _build_by_collection( *, portfolio_profile: str = "default", ) -> None: - from src.analyst_views import build_analyst_context + from github_repo_auditor.analyst_views import build_analyst_context _build_by_collection_sheet( wb, @@ -1631,7 +1631,7 @@ def _build_scenario_planner( portfolio_profile: str = "default", collection: str | None = None, ) -> None: - from src.analyst_views import build_analyst_context + from github_repo_auditor.analyst_views import build_analyst_context _build_scenario_planner_sheet( wb, diff --git a/src/excel_export_registry_helpers.py b/src/github_repo_auditor/excel_export_registry_helpers.py similarity index 96% rename from src/excel_export_registry_helpers.py rename to src/github_repo_auditor/excel_export_registry_helpers.py index ce4217dd..e9ba198f 100644 --- a/src/excel_export_registry_helpers.py +++ b/src/github_repo_auditor/excel_export_registry_helpers.py @@ -4,14 +4,14 @@ from typing import Any, Callable -from src.excel_workbook_helpers import ( +from github_repo_auditor.excel_workbook_helpers import ( CORE_VISIBLE_SHEETS, DEFAULT_PREFERRED_SHEET_ORDER, ) -from src.excel_workbook_helpers import ( +from github_repo_auditor.excel_workbook_helpers import ( finalize_workbook_structure as default_finalize_workbook_structure, ) -from src.excel_workbook_helpers import ( +from github_repo_auditor.excel_workbook_helpers import ( run_workbook_build_steps as default_run_workbook_build_steps, ) diff --git a/src/excel_export_runner_helpers.py b/src/github_repo_auditor/excel_export_runner_helpers.py similarity index 100% rename from src/excel_export_runner_helpers.py rename to src/github_repo_auditor/excel_export_runner_helpers.py diff --git a/src/excel_export_truth_helpers.py b/src/github_repo_auditor/excel_export_truth_helpers.py similarity index 96% rename from src/excel_export_truth_helpers.py rename to src/github_repo_auditor/excel_export_truth_helpers.py index 39759439..35d35fd3 100644 --- a/src/excel_export_truth_helpers.py +++ b/src/github_repo_auditor/excel_export_truth_helpers.py @@ -5,7 +5,7 @@ import json from pathlib import Path -from src.portfolio_truth_types import truth_latest_path +from github_repo_auditor.portfolio_truth_types import truth_latest_path def load_risk_truth(truth_dir: Path | None) -> tuple[dict[str, str], dict[str, int]]: diff --git a/src/excel_heatmap_sheet_helpers.py b/src/github_repo_auditor/excel_heatmap_sheet_helpers.py similarity index 100% rename from src/excel_heatmap_sheet_helpers.py rename to src/github_repo_auditor/excel_heatmap_sheet_helpers.py diff --git a/src/excel_hidden_data_content_helpers.py b/src/github_repo_auditor/excel_hidden_data_content_helpers.py similarity index 100% rename from src/excel_hidden_data_content_helpers.py rename to src/github_repo_auditor/excel_hidden_data_content_helpers.py diff --git a/src/excel_hidden_data_rows.py b/src/github_repo_auditor/excel_hidden_data_rows.py similarity index 98% rename from src/excel_hidden_data_rows.py rename to src/github_repo_auditor/excel_hidden_data_rows.py index 653cea92..c0ad2d78 100644 --- a/src/excel_hidden_data_rows.py +++ b/src/github_repo_auditor/excel_hidden_data_rows.py @@ -4,9 +4,9 @@ import json -from src.analyst_views import build_analyst_context -from src.excel_report_helpers import collection_memberships -from src.excel_timeline_helpers import ( +from github_repo_auditor.analyst_views import build_analyst_context +from github_repo_auditor.excel_report_helpers import collection_memberships +from github_repo_auditor.excel_timeline_helpers import ( extend_portfolio_trend_with_current, extend_score_history_with_current, ) diff --git a/src/excel_hidden_sheet_writer.py b/src/github_repo_auditor/excel_hidden_sheet_writer.py similarity index 99% rename from src/excel_hidden_sheet_writer.py rename to src/github_repo_auditor/excel_hidden_sheet_writer.py index 38766f1e..ada55201 100644 --- a/src/excel_hidden_sheet_writer.py +++ b/src/github_repo_auditor/excel_hidden_sheet_writer.py @@ -4,7 +4,7 @@ from openpyxl import Workbook -from src.excel_detail_helpers import write_hidden_table_sheet +from github_repo_auditor.excel_detail_helpers import write_hidden_table_sheet def write_hidden_data_tables( diff --git a/src/excel_hotspot_sheet_helpers.py b/src/github_repo_auditor/excel_hotspot_sheet_helpers.py similarity index 100% rename from src/excel_hotspot_sheet_helpers.py rename to src/github_repo_auditor/excel_hotspot_sheet_helpers.py diff --git a/src/excel_initiative_tracker_helpers.py b/src/github_repo_auditor/excel_initiative_tracker_helpers.py similarity index 98% rename from src/excel_initiative_tracker_helpers.py rename to src/github_repo_auditor/excel_initiative_tracker_helpers.py index d00349e9..bae73f21 100644 --- a/src/excel_initiative_tracker_helpers.py +++ b/src/github_repo_auditor/excel_initiative_tracker_helpers.py @@ -15,13 +15,13 @@ from openpyxl.workbook import Workbook -from src.initiatives import ( +from github_repo_auditor.initiatives import ( Initiative, derive_status, initiatives_path, load_initiatives, ) -from src.maturity_tiers import TierGap, compute_tier, tier_gap, tier_name +from github_repo_auditor.maturity_tiers import TierGap, compute_tier, tier_gap, tier_name logger = logging.getLogger(__name__) diff --git a/src/excel_ledger_sheet_helpers.py b/src/github_repo_auditor/excel_ledger_sheet_helpers.py similarity index 100% rename from src/excel_ledger_sheet_helpers.py rename to src/github_repo_auditor/excel_ledger_sheet_helpers.py diff --git a/src/excel_navigation_helpers.py b/src/github_repo_auditor/excel_navigation_helpers.py similarity index 95% rename from src/excel_navigation_helpers.py rename to src/github_repo_auditor/excel_navigation_helpers.py index afd7a24f..421f64db 100644 --- a/src/excel_navigation_helpers.py +++ b/src/github_repo_auditor/excel_navigation_helpers.py @@ -1,11 +1,11 @@ from openpyxl import Workbook from openpyxl.worksheet.hyperlink import Hyperlink -from src.excel_detail_helpers import set_internal_hyperlink -from src.excel_operator_follow_through_helpers import operator_follow_through_details -from src.excel_operator_summary_helpers import operator_handoff_values, operator_watch_values -from src.excel_sheet_layout_helpers import write_instruction_banner -from src.excel_styles import ( +from github_repo_auditor.excel_detail_helpers import set_internal_hyperlink +from github_repo_auditor.excel_operator_follow_through_helpers import operator_follow_through_details +from github_repo_auditor.excel_operator_summary_helpers import operator_handoff_values, operator_watch_values +from github_repo_auditor.excel_sheet_layout_helpers import write_instruction_banner +from github_repo_auditor.excel_styles import ( CENTER, LEFT, NAVY, @@ -20,7 +20,7 @@ auto_width, style_data_cell, ) -from src.excel_workbook_helpers import clear_worksheet, configure_sheet_view, sheet_location +from github_repo_auditor.excel_workbook_helpers import clear_worksheet, configure_sheet_view, sheet_location def inject_sheet_navigation(wb: Workbook) -> None: diff --git a/src/excel_operator_closure_helpers.py b/src/github_repo_auditor/excel_operator_closure_helpers.py similarity index 100% rename from src/excel_operator_closure_helpers.py rename to src/github_repo_auditor/excel_operator_closure_helpers.py diff --git a/src/excel_operator_follow_through_helpers.py b/src/github_repo_auditor/excel_operator_follow_through_helpers.py similarity index 100% rename from src/excel_operator_follow_through_helpers.py rename to src/github_repo_auditor/excel_operator_follow_through_helpers.py diff --git a/src/excel_operator_hidden_rows.py b/src/github_repo_auditor/excel_operator_hidden_rows.py similarity index 100% rename from src/excel_operator_hidden_rows.py rename to src/github_repo_auditor/excel_operator_hidden_rows.py diff --git a/src/excel_operator_outcomes_helpers.py b/src/github_repo_auditor/excel_operator_outcomes_helpers.py similarity index 100% rename from src/excel_operator_outcomes_helpers.py rename to src/github_repo_auditor/excel_operator_outcomes_helpers.py diff --git a/src/excel_operator_queue_helpers.py b/src/github_repo_auditor/excel_operator_queue_helpers.py similarity index 97% rename from src/excel_operator_queue_helpers.py rename to src/github_repo_auditor/excel_operator_queue_helpers.py index 0cef09fb..502adc8e 100644 --- a/src/excel_operator_queue_helpers.py +++ b/src/github_repo_auditor/excel_operator_queue_helpers.py @@ -1,6 +1,6 @@ from collections import Counter -from src.report_enrichment import build_operator_focus, build_operator_focus_line +from github_repo_auditor.report_enrichment import build_operator_focus, build_operator_focus_line def _severity_rank(value: object) -> float: diff --git a/src/excel_operator_summary_helpers.py b/src/github_repo_auditor/excel_operator_summary_helpers.py similarity index 100% rename from src/excel_operator_summary_helpers.py rename to src/github_repo_auditor/excel_operator_summary_helpers.py diff --git a/src/excel_portfolio_misc_helpers.py b/src/github_repo_auditor/excel_portfolio_misc_helpers.py similarity index 100% rename from src/excel_portfolio_misc_helpers.py rename to src/github_repo_auditor/excel_portfolio_misc_helpers.py diff --git a/src/excel_portfolio_sheet_helpers.py b/src/github_repo_auditor/excel_portfolio_sheet_helpers.py similarity index 100% rename from src/excel_portfolio_sheet_helpers.py rename to src/github_repo_auditor/excel_portfolio_sheet_helpers.py diff --git a/src/excel_print_pack_helpers.py b/src/github_repo_auditor/excel_print_pack_helpers.py similarity index 98% rename from src/excel_print_pack_helpers.py rename to src/github_repo_auditor/excel_print_pack_helpers.py index bb207e6b..5675a33b 100644 --- a/src/excel_print_pack_helpers.py +++ b/src/github_repo_auditor/excel_print_pack_helpers.py @@ -2,7 +2,7 @@ from __future__ import annotations -from src.terminology import ACTION_SYNC_CANONICAL_LABELS +from github_repo_auditor.terminology import ACTION_SYNC_CANONICAL_LABELS def print_pack_workflow_guidance_rows( diff --git a/src/excel_print_pack_layout_helpers.py b/src/github_repo_auditor/excel_print_pack_layout_helpers.py similarity index 100% rename from src/excel_print_pack_layout_helpers.py rename to src/github_repo_auditor/excel_print_pack_layout_helpers.py diff --git a/src/excel_profile_trend_helpers.py b/src/github_repo_auditor/excel_profile_trend_helpers.py similarity index 100% rename from src/excel_profile_trend_helpers.py rename to src/github_repo_auditor/excel_profile_trend_helpers.py diff --git a/src/excel_quick_wins_helpers.py b/src/github_repo_auditor/excel_quick_wins_helpers.py similarity index 100% rename from src/excel_quick_wins_helpers.py rename to src/github_repo_auditor/excel_quick_wins_helpers.py diff --git a/src/excel_repo_data_helpers.py b/src/github_repo_auditor/excel_repo_data_helpers.py similarity index 97% rename from src/excel_repo_data_helpers.py rename to src/github_repo_auditor/excel_repo_data_helpers.py index 026d11e5..43fef965 100644 --- a/src/excel_repo_data_helpers.py +++ b/src/github_repo_auditor/excel_repo_data_helpers.py @@ -2,17 +2,17 @@ from __future__ import annotations -from src.excel_detail_helpers import repo_detail_last_movement -from src.excel_report_helpers import collection_memberships, severity_rank -from src.excel_timeline_helpers import extend_score_history_with_current -from src.report_enrichment import ( +from github_repo_auditor.excel_detail_helpers import repo_detail_last_movement +from github_repo_auditor.excel_report_helpers import collection_memberships, severity_rank +from github_repo_auditor.excel_timeline_helpers import extend_score_history_with_current +from github_repo_auditor.report_enrichment import ( build_repo_briefing, build_run_change_counts, build_run_change_summary, build_score_explanation, ) -from src.sparkline import sparkline as render_sparkline -from src.scoring_dimensions import display_dimension +from github_repo_auditor.sparkline import sparkline as render_sparkline +from github_repo_auditor.scoring_dimensions import display_dimension def score_explanation_for_audit(audit: dict) -> dict: diff --git a/src/excel_repo_detail_helpers.py b/src/github_repo_auditor/excel_repo_detail_helpers.py similarity index 99% rename from src/excel_repo_detail_helpers.py rename to src/github_repo_auditor/excel_repo_detail_helpers.py index 3b4fd652..cd65f260 100644 --- a/src/excel_repo_detail_helpers.py +++ b/src/github_repo_auditor/excel_repo_detail_helpers.py @@ -6,7 +6,7 @@ from openpyxl.styles import Font from openpyxl.worksheet.datavalidation import DataValidation -from src.excel_detail_helpers import repo_detail_lookup_formula, set_internal_hyperlink +from github_repo_auditor.excel_detail_helpers import repo_detail_lookup_formula, set_internal_hyperlink REPO_DETAIL_DIMENSION_HEADERS = ["Rank", "Dimension", "Score", "Summary"] diff --git a/src/excel_report_helpers.py b/src/github_repo_auditor/excel_report_helpers.py similarity index 100% rename from src/excel_report_helpers.py rename to src/github_repo_auditor/excel_report_helpers.py diff --git a/src/excel_review_queue_helpers.py b/src/github_repo_auditor/excel_review_queue_helpers.py similarity index 100% rename from src/excel_review_queue_helpers.py rename to src/github_repo_auditor/excel_review_queue_helpers.py diff --git a/src/excel_review_queue_table_helpers.py b/src/github_repo_auditor/excel_review_queue_table_helpers.py similarity index 99% rename from src/excel_review_queue_table_helpers.py rename to src/github_repo_auditor/excel_review_queue_table_helpers.py index eda2177d..713bc84e 100644 --- a/src/excel_review_queue_table_helpers.py +++ b/src/github_repo_auditor/excel_review_queue_table_helpers.py @@ -4,7 +4,7 @@ from typing import Any -from src.report_enrichment import ( +from github_repo_auditor.report_enrichment import ( build_follow_through_checkpoint_status_label, build_follow_through_escalation_status_label, build_follow_through_escalation_summary, diff --git a/src/excel_run_changes_helpers.py b/src/github_repo_auditor/excel_run_changes_helpers.py similarity index 99% rename from src/excel_run_changes_helpers.py rename to src/github_repo_auditor/excel_run_changes_helpers.py index ebd3d89a..d4828146 100644 --- a/src/excel_run_changes_helpers.py +++ b/src/github_repo_auditor/excel_run_changes_helpers.py @@ -2,7 +2,7 @@ from __future__ import annotations -from src.report_enrichment import ( +from github_repo_auditor.report_enrichment import ( build_run_change_counts, build_run_change_summary, no_baseline_summary, diff --git a/src/excel_score_explainer_helpers.py b/src/github_repo_auditor/excel_score_explainer_helpers.py similarity index 98% rename from src/excel_score_explainer_helpers.py rename to src/github_repo_auditor/excel_score_explainer_helpers.py index 2a59360d..f1dbcb02 100644 --- a/src/excel_score_explainer_helpers.py +++ b/src/github_repo_auditor/excel_score_explainer_helpers.py @@ -4,7 +4,7 @@ from typing import Any -from src.scoring_dimensions import UNSCORED_DIMENSIONS, display_dimension +from github_repo_auditor.scoring_dimensions import UNSCORED_DIMENSIONS, display_dimension SCORE_EXPLAINER_HEADERS = ["Dimension", "Weight", "What It Measures", "How to Improve"] diff --git a/src/excel_security_sheet_helpers.py b/src/github_repo_auditor/excel_security_sheet_helpers.py similarity index 100% rename from src/excel_security_sheet_helpers.py rename to src/github_repo_auditor/excel_security_sheet_helpers.py diff --git a/src/excel_sheet_layout_helpers.py b/src/github_repo_auditor/excel_sheet_layout_helpers.py similarity index 98% rename from src/excel_sheet_layout_helpers.py rename to src/github_repo_auditor/excel_sheet_layout_helpers.py index 65d6db30..038cbc18 100644 --- a/src/excel_sheet_layout_helpers.py +++ b/src/github_repo_auditor/excel_sheet_layout_helpers.py @@ -1,6 +1,6 @@ from openpyxl.styles import PatternFill -from src.excel_styles import ( +from github_repo_auditor.excel_styles import ( CENTER, SECTION_FONT, SUBHEADER_FILL, diff --git a/src/excel_styles.py b/src/github_repo_auditor/excel_styles.py similarity index 100% rename from src/excel_styles.py rename to src/github_repo_auditor/excel_styles.py diff --git a/src/excel_template.py b/src/github_repo_auditor/excel_template.py similarity index 98% rename from src/excel_template.py rename to src/github_repo_auditor/excel_template.py index 09a458ba..d6c8da8c 100644 --- a/src/excel_template.py +++ b/src/github_repo_auditor/excel_template.py @@ -18,7 +18,9 @@ SPARKLINE_URI = "{05C60535-1F16-4fd2-B633-F4F36F0B64E0}" SPARKLINE_WARNING_MESSAGE = "Sparkline Group extension is not supported and will be removed" TREND_HISTORY_WINDOW = 12 -DEFAULT_TEMPLATE_PATH = Path(__file__).resolve().parents[1] / "assets" / "excel" / "analyst-template.xlsx" +DEFAULT_TEMPLATE_PATH = ( + Path(__file__).resolve().parent / "assets" / "excel" / "analyst-template.xlsx" +) TEMPLATE_INFO_SHEET = "TemplateInfo" TEMPLATE_SHEETS = [ "Index", diff --git a/src/excel_template_info_helpers.py b/src/github_repo_auditor/excel_template_info_helpers.py similarity index 100% rename from src/excel_template_info_helpers.py rename to src/github_repo_auditor/excel_template_info_helpers.py diff --git a/src/excel_timeline_helpers.py b/src/github_repo_auditor/excel_timeline_helpers.py similarity index 96% rename from src/excel_timeline_helpers.py rename to src/github_repo_auditor/excel_timeline_helpers.py index da47bc74..2da21792 100644 --- a/src/excel_timeline_helpers.py +++ b/src/github_repo_auditor/excel_timeline_helpers.py @@ -1,4 +1,4 @@ -from src.excel_template import TREND_HISTORY_WINDOW +from github_repo_auditor.excel_template import TREND_HISTORY_WINDOW def extend_score_history_with_current( diff --git a/src/excel_workbook_helpers.py b/src/github_repo_auditor/excel_workbook_helpers.py similarity index 100% rename from src/excel_workbook_helpers.py rename to src/github_repo_auditor/excel_workbook_helpers.py diff --git a/src/excel_workbook_rollups.py b/src/github_repo_auditor/excel_workbook_rollups.py similarity index 100% rename from src/excel_workbook_rollups.py rename to src/github_repo_auditor/excel_workbook_rollups.py diff --git a/src/ghas_alert_details.py b/src/github_repo_auditor/ghas_alert_details.py similarity index 98% rename from src/ghas_alert_details.py rename to src/github_repo_auditor/ghas_alert_details.py index bbce4c07..daf3cc7d 100644 --- a/src/ghas_alert_details.py +++ b/src/github_repo_auditor/ghas_alert_details.py @@ -18,7 +18,7 @@ import requests -from src.ghas_alerts import ( +from github_repo_auditor.ghas_alerts import ( _EXPECTED_UNAVAILABLE_STATUSES, GITHUB_API_BASE_URL, _make_session, diff --git a/src/ghas_alerts.py b/src/github_repo_auditor/ghas_alerts.py similarity index 99% rename from src/ghas_alerts.py rename to src/github_repo_auditor/ghas_alerts.py index 3e113d40..9c4ecb99 100644 --- a/src/ghas_alerts.py +++ b/src/github_repo_auditor/ghas_alerts.py @@ -27,8 +27,8 @@ import requests -from src.cache import ResponseCache -from src.http_link_header import next_link_from_header +from github_repo_auditor.cache import ResponseCache +from github_repo_auditor.http_link_header import next_link_from_header logger = logging.getLogger(__name__) diff --git a/src/github_client.py b/src/github_repo_auditor/github_client.py similarity index 99% rename from src/github_client.py rename to src/github_repo_auditor/github_client.py index d77931d2..698f6740 100644 --- a/src/github_client.py +++ b/src/github_repo_auditor/github_client.py @@ -11,9 +11,9 @@ from requests.adapters import HTTPAdapter from urllib3.util import Retry -from src.cache import ResponseCache -from src.http_link_header import next_link_from_header -from src.models import RepoMetadata +from github_repo_auditor.cache import ResponseCache +from github_repo_auditor.http_link_header import next_link_from_header +from github_repo_auditor.models import RepoMetadata logger = logging.getLogger(__name__) diff --git a/src/github_client_async.py b/src/github_repo_auditor/github_client_async.py similarity index 98% rename from src/github_client_async.py rename to src/github_repo_auditor/github_client_async.py index 6aa099da..568409f4 100644 --- a/src/github_client_async.py +++ b/src/github_repo_auditor/github_client_async.py @@ -12,7 +12,7 @@ exponentially on 5xx. 404/403 are treated as "not available" and return None without retry. * Cache integration reuses the same URL+params key scheme as - ``src.cache.ResponseCache`` (SHA-256 hex prefix, file-backed). + ``github_repo_auditor.cache.ResponseCache`` (SHA-256 hex prefix, file-backed). """ from __future__ import annotations @@ -23,10 +23,10 @@ import httpx -from src.github_client import API_BASE, REST_API_VERSION, GitHubClientError +from github_repo_auditor.github_client import API_BASE, REST_API_VERSION, GitHubClientError if TYPE_CHECKING: - from src.cache import ResponseCache + from github_repo_auditor.cache import ResponseCache logger = logging.getLogger(__name__) diff --git a/src/github_projects.py b/src/github_repo_auditor/github_projects.py similarity index 99% rename from src/github_projects.py rename to src/github_repo_auditor/github_projects.py index 82e455a8..68fc19f8 100644 --- a/src/github_projects.py +++ b/src/github_repo_auditor/github_projects.py @@ -3,7 +3,7 @@ from pathlib import Path from typing import Any -from src.report_enrichment import ( +from github_repo_auditor.report_enrichment import ( build_follow_through_checkpoint_status_label, build_follow_through_reacquisition_confidence_retirement_status_label, build_follow_through_reacquisition_revalidation_recovery_status_label, diff --git a/src/github_security_coverage.py b/src/github_repo_auditor/github_security_coverage.py similarity index 100% rename from src/github_security_coverage.py rename to src/github_repo_auditor/github_security_coverage.py diff --git a/src/governance_activation.py b/src/github_repo_auditor/governance_activation.py similarity index 99% rename from src/governance_activation.py rename to src/github_repo_auditor/governance_activation.py index 5859fe38..a7c93c43 100644 --- a/src/governance_activation.py +++ b/src/github_repo_auditor/governance_activation.py @@ -5,8 +5,8 @@ from datetime import datetime, timezone from pathlib import Path -from src.models import AuditReport -from src.warehouse import ( +from github_repo_auditor.models import AuditReport +from github_repo_auditor.warehouse import ( load_audit_report_path, load_governance_approval, load_governance_history, diff --git a/src/graphql_client.py b/src/github_repo_auditor/graphql_client.py similarity index 100% rename from src/graphql_client.py rename to src/github_repo_auditor/graphql_client.py diff --git a/src/history.py b/src/github_repo_auditor/history.py similarity index 100% rename from src/history.py rename to src/github_repo_auditor/history.py diff --git a/src/http_link_header.py b/src/github_repo_auditor/http_link_header.py similarity index 100% rename from src/http_link_header.py rename to src/github_repo_auditor/http_link_header.py diff --git a/src/implementation_hotspots.py b/src/github_repo_auditor/implementation_hotspots.py similarity index 98% rename from src/implementation_hotspots.py rename to src/github_repo_auditor/implementation_hotspots.py index 800db854..02154a53 100644 --- a/src/implementation_hotspots.py +++ b/src/github_repo_auditor/implementation_hotspots.py @@ -4,10 +4,10 @@ from collections import Counter, defaultdict from pathlib import Path -from src.analyzers.code_quality import TODO_PATTERN -from src.analyzers.dependencies import LOCKFILES, MANIFESTS -from src.analyzers.security import _find_dangerous_files, _scan_secrets -from src.models import RepoAudit +from github_repo_auditor.analyzers.code_quality import TODO_PATTERN +from github_repo_auditor.analyzers.dependencies import LOCKFILES, MANIFESTS +from github_repo_auditor.analyzers.security import _find_dangerous_files, _scan_secrets +from github_repo_auditor.models import RepoAudit SKIP_DIRS = { ".git", diff --git a/src/initiatives.py b/src/github_repo_auditor/initiatives.py similarity index 98% rename from src/initiatives.py rename to src/github_repo_auditor/initiatives.py index 8f81306c..9bc0a5a1 100644 --- a/src/initiatives.py +++ b/src/github_repo_auditor/initiatives.py @@ -21,7 +21,7 @@ } Atomic writes use the same tmp-sibling-then-rename pattern as -``src/operator_prefs.py``. +``src/github_repo_auditor/operator_prefs.py``. """ from __future__ import annotations @@ -34,7 +34,7 @@ from datetime import date, datetime, timezone from pathlib import Path -from src.maturity_tiers import compute_tier +from github_repo_auditor.maturity_tiers import compute_tier logger = logging.getLogger(__name__) diff --git a/src/intervention_ledger.py b/src/github_repo_auditor/intervention_ledger.py similarity index 99% rename from src/intervention_ledger.py rename to src/github_repo_auditor/intervention_ledger.py index 23a132f2..1fa4348a 100644 --- a/src/intervention_ledger.py +++ b/src/github_repo_auditor/intervention_ledger.py @@ -3,8 +3,8 @@ from pathlib import Path from typing import Any -from src.history import load_repo_score_history -from src.warehouse import ( +from github_repo_auditor.history import load_repo_score_history +from github_repo_auditor.warehouse import ( load_campaign_outcomes, load_latest_audit_runs, load_operator_state_history, diff --git a/src/issue_creator.py b/src/github_repo_auditor/issue_creator.py similarity index 97% rename from src/issue_creator.py rename to src/github_repo_auditor/issue_creator.py index 42285238..bf897887 100644 --- a/src/issue_creator.py +++ b/src/github_repo_auditor/issue_creator.py @@ -3,7 +3,7 @@ import sys -from src.github_client import GitHubClient +from github_repo_auditor.github_client import GitHubClient def create_audit_issues( diff --git a/src/libyears.py b/src/github_repo_auditor/libyears.py similarity index 99% rename from src/libyears.py rename to src/github_repo_auditor/libyears.py index 732fa8de..1a97e97b 100644 --- a/src/libyears.py +++ b/src/github_repo_auditor/libyears.py @@ -8,7 +8,7 @@ import requests -from src.cache import ResponseCache +from github_repo_auditor.cache import ResponseCache logger = logging.getLogger(__name__) diff --git a/src/llm_cost.py b/src/github_repo_auditor/llm_cost.py similarity index 98% rename from src/llm_cost.py rename to src/github_repo_auditor/llm_cost.py index 0fa7b776..fae9822d 100644 --- a/src/llm_cost.py +++ b/src/github_repo_auditor/llm_cost.py @@ -54,11 +54,11 @@ def _compute_cost(model: str, input_tokens: int, output_tokens: int) -> float: warnings.warn( f"llm_cost: unknown model {model!r} — using fallback rate " f"(${_UNKNOWN_PRICE['input']:.2f}/${_UNKNOWN_PRICE['output']:.2f} per 1M tokens). " - "Update PRICES in src/llm_cost.py.", + "Update PRICES in src/github_repo_auditor/llm_cost.py.", stacklevel=3, ) logger.warning( - "llm_cost: unknown model %r — using fallback rate. Update PRICES in src/llm_cost.py.", + "llm_cost: unknown model %r — using fallback rate. Update PRICES in src/github_repo_auditor/llm_cost.py.", model, ) price = _UNKNOWN_PRICE diff --git a/src/maturity_tiers.py b/src/github_repo_auditor/maturity_tiers.py similarity index 100% rename from src/maturity_tiers.py rename to src/github_repo_auditor/maturity_tiers.py diff --git a/src/models.py b/src/github_repo_auditor/models.py similarity index 98% rename from src/models.py rename to src/github_repo_auditor/models.py index 46b81f23..f6f67ab6 100644 --- a/src/models.py +++ b/src/github_repo_auditor/models.py @@ -114,7 +114,7 @@ class RepoAudit: ossf_scorecard: dict = field(default_factory=dict) def to_dict(self) -> dict: - from src.context_quality import context_quality_score_for_audit + from github_repo_auditor.context_quality import context_quality_score_for_audit return { "metadata": self.metadata.to_dict(), @@ -254,9 +254,9 @@ def from_audits( # AuditReport assembly needs portfolio/scoring modules that depend on these core # dataclasses. Import them dynamically at call time so the model layer stays # import-light and does not form static import cycles. - implementation_hotspots = importlib.import_module("src.implementation_hotspots") - portfolio_intelligence = importlib.import_module("src.portfolio_intelligence") - scorer = importlib.import_module("src.scorer") + implementation_hotspots = importlib.import_module("github_repo_auditor.implementation_hotspots") + portfolio_intelligence = importlib.import_module("github_repo_auditor.portfolio_intelligence") + scorer = importlib.import_module("github_repo_auditor.scorer") # Tier distribution tier_dist: dict[str, int] = {} diff --git a/src/narrative.py b/src/github_repo_auditor/narrative.py similarity index 99% rename from src/narrative.py rename to src/github_repo_auditor/narrative.py index 08381c83..551060c0 100644 --- a/src/narrative.py +++ b/src/github_repo_auditor/narrative.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Protocol if TYPE_CHECKING: - from src.llm_cost import CostTracker + from github_repo_auditor.llm_cost import CostTracker # Allow override via env var for testing / proxies GITHUB_MODELS_BASE_URL = os.environ.get( diff --git a/src/notion_client.py b/src/github_repo_auditor/notion_client.py similarity index 100% rename from src/notion_client.py rename to src/github_repo_auditor/notion_client.py diff --git a/src/notion_dashboard.py b/src/github_repo_auditor/notion_dashboard.py similarity index 98% rename from src/notion_dashboard.py rename to src/github_repo_auditor/notion_dashboard.py index f6c921d1..760de3e2 100644 --- a/src/notion_dashboard.py +++ b/src/github_repo_auditor/notion_dashboard.py @@ -3,7 +3,7 @@ import sys -from src.notion_client import ( +from github_repo_auditor.notion_client import ( DEFAULT_NOTION_VERSION, notion_request, ) diff --git a/src/notion_export.py b/src/github_repo_auditor/notion_export.py similarity index 99% rename from src/notion_export.py rename to src/github_repo_auditor/notion_export.py index e87833c3..a29a8b9e 100644 --- a/src/notion_export.py +++ b/src/github_repo_auditor/notion_export.py @@ -9,7 +9,7 @@ from datetime import datetime, timezone from pathlib import Path -from src.registry_parser import _normalize +from github_repo_auditor.registry_parser import _normalize RAW_EXCERPT_LIMIT = 2000 diff --git a/src/notion_registry.py b/src/github_repo_auditor/notion_registry.py similarity index 99% rename from src/notion_registry.py rename to src/github_repo_auditor/notion_registry.py index b399a3f7..206eaa4e 100644 --- a/src/notion_registry.py +++ b/src/github_repo_auditor/notion_registry.py @@ -5,7 +5,7 @@ import time from pathlib import Path -from src.notion_client import ( +from github_repo_auditor.notion_client import ( DEFAULT_NOTION_VERSION, REQUEST_DELAY, get_notion_token, diff --git a/src/notion_sync.py b/src/github_repo_auditor/notion_sync.py similarity index 99% rename from src/notion_sync.py rename to src/github_repo_auditor/notion_sync.py index f1b1008f..d6e5953c 100644 --- a/src/notion_sync.py +++ b/src/github_repo_auditor/notion_sync.py @@ -10,7 +10,7 @@ import time from pathlib import Path -from src.notion_client import ( +from github_repo_auditor.notion_client import ( DEFAULT_NOTION_VERSION, REQUEST_DELAY, get_notion_token, @@ -338,7 +338,7 @@ def sync_campaign_actions( previous_actions = (previous_state or {}).get("actions", {}) project_map = {} try: - from src.notion_export import _load_project_map + from github_repo_auditor.notion_export import _load_project_map project_map = _load_project_map(config_dir) except Exception: diff --git a/src/operator_acknowledgments.py b/src/github_repo_auditor/operator_acknowledgments.py similarity index 100% rename from src/operator_acknowledgments.py rename to src/github_repo_auditor/operator_acknowledgments.py diff --git a/src/operator_approval_artifacts.py b/src/github_repo_auditor/operator_approval_artifacts.py similarity index 96% rename from src/operator_approval_artifacts.py rename to src/github_repo_auditor/operator_approval_artifacts.py index 796b8200..f5194c6d 100644 --- a/src/operator_approval_artifacts.py +++ b/src/github_repo_auditor/operator_approval_artifacts.py @@ -6,9 +6,9 @@ from datetime import datetime from pathlib import Path -from src.approval_ledger import load_approval_ledger_bundle, render_approval_center_markdown -from src.models import AuditReport -from src.operator_artifact_paths import ( +from github_repo_auditor.approval_ledger import load_approval_ledger_bundle, render_approval_center_markdown +from github_repo_auditor.models import AuditReport +from github_repo_auditor.operator_artifact_paths import ( approval_center_paths, approval_receipt_paths, followup_review_receipt_paths, diff --git a/src/operator_artifact_paths.py b/src/github_repo_auditor/operator_artifact_paths.py similarity index 100% rename from src/operator_artifact_paths.py rename to src/github_repo_auditor/operator_artifact_paths.py diff --git a/src/operator_control_center.py b/src/github_repo_auditor/operator_control_center.py similarity index 95% rename from src/operator_control_center.py rename to src/github_repo_auditor/operator_control_center.py index 7272240c..c7e64cbd 100644 --- a/src/operator_control_center.py +++ b/src/github_repo_auditor/operator_control_center.py @@ -5,14 +5,14 @@ from datetime import datetime, timezone from pathlib import Path -from src.baseline_context import build_watch_guidance -from src.governance_activation import build_governance_summary -from src.intervention_ledger import load_intervention_ledger_bundle -from src.operator_acknowledgments import is_change_acknowledged, load_acknowledgments -from src.operator_effectiveness import build_operator_effectiveness_bundle -from src.portfolio_truth_types import truth_latest_path -from src.recurring_review import build_review_bundle -from src.warehouse import ( +from github_repo_auditor.baseline_context import build_watch_guidance +from github_repo_auditor.governance_activation import build_governance_summary +from github_repo_auditor.intervention_ledger import load_intervention_ledger_bundle +from github_repo_auditor.operator_acknowledgments import is_change_acknowledged, load_acknowledgments +from github_repo_auditor.operator_effectiveness import build_operator_effectiveness_bundle +from github_repo_auditor.portfolio_truth_types import truth_latest_path +from github_repo_auditor.recurring_review import build_review_bundle +from github_repo_auditor.warehouse import ( load_operator_calibration_history, load_operator_state_history, load_recent_campaign_history, @@ -493,7 +493,7 @@ def build_operator_snapshot( # Arc F S3.4: surface cross-repo duplicate groups when a semantic index is available. if semantic_index is not None: try: - from src.semantic_index import DuplicateGroup + from github_repo_auditor.semantic_index import DuplicateGroup dup_groups: list[DuplicateGroup] = semantic_index.find_duplicate_groups() # type: ignore[union-attr] for group in dup_groups: @@ -562,10 +562,10 @@ def build_operator_snapshot( report_data.get("username", ""), limit=CALIBRATION_WINDOW_RUNS, ) - from src import operator_decision_quality as _operator_decision_quality - from src import operator_follow_through as _operator_follow_through - from src import operator_resolution_trend as _operator_resolution_trend - from src import operator_snapshot_packaging as _operator_snapshot_packaging + from github_repo_auditor import operator_decision_quality as _operator_decision_quality + from github_repo_auditor import operator_follow_through as _operator_follow_through + from github_repo_auditor import operator_resolution_trend as _operator_resolution_trend + from github_repo_auditor import operator_snapshot_packaging as _operator_snapshot_packaging confidence_calibration = _operator_decision_quality.build_confidence_calibration( calibration_history @@ -613,12 +613,12 @@ def build_operator_snapshot( resolution_trend=resolution_trend, current_generated_at=report_data.get("generated_at", ""), ) - from src.action_sync_automation import build_action_sync_automation_bundle - from src.action_sync_outcomes import load_action_sync_outcomes_bundle - from src.action_sync_packets import build_action_sync_packets_bundle - from src.action_sync_readiness import build_action_sync_readiness_bundle - from src.action_sync_tuning import load_action_sync_tuning_bundle - from src.approval_ledger import load_approval_ledger_bundle + from github_repo_auditor.action_sync_automation import build_action_sync_automation_bundle + from github_repo_auditor.action_sync_outcomes import load_action_sync_outcomes_bundle + from github_repo_auditor.action_sync_packets import build_action_sync_packets_bundle + from github_repo_auditor.action_sync_readiness import build_action_sync_readiness_bundle + from github_repo_auditor.action_sync_tuning import load_action_sync_tuning_bundle + from github_repo_auditor.approval_ledger import load_approval_ledger_bundle action_sync = build_action_sync_readiness_bundle(report_data, queue) action_sync_packets = build_action_sync_packets_bundle( @@ -780,7 +780,7 @@ def build_operator_snapshot( def render_control_center_markdown(snapshot: dict, username: str, generated_at: str) -> str: - from src.operator_control_center_rendering import ( + from github_repo_auditor.operator_control_center_rendering import ( render_control_center_markdown as _render_control_center_markdown, ) @@ -788,7 +788,7 @@ def render_control_center_markdown(snapshot: dict, username: str, generated_at: def control_center_artifact_payload(report_data: dict, snapshot: dict) -> dict: - from src.operator_snapshot_packaging import ( + from github_repo_auditor.operator_snapshot_packaging import ( control_center_artifact_payload as _control_center_artifact_payload, ) @@ -943,7 +943,7 @@ def _normalize_review_history_item(item: dict) -> dict: def _build_confidence_calibration(history: list[dict]) -> dict: - from src import operator_decision_quality as _operator_decision_quality + from github_repo_auditor import operator_decision_quality as _operator_decision_quality return _operator_decision_quality.build_confidence_calibration(history) @@ -1059,7 +1059,7 @@ def _confidence_validation_status( reopened_recommendation_count: int, reopened_high_count: int, ) -> str: - from src import operator_decision_quality as _operator_decision_quality + from github_repo_auditor import operator_decision_quality as _operator_decision_quality return _operator_decision_quality.confidence_validation_status( judged_count=judged_count, @@ -1078,7 +1078,7 @@ def _confidence_calibration_summary( reopened_recommendation_count: int, judged_count: int, ) -> str: - from src import operator_decision_quality as _operator_decision_quality + from github_repo_auditor import operator_decision_quality as _operator_decision_quality return _operator_decision_quality.confidence_calibration_summary( confidence_validation_status=confidence_validation_status, diff --git a/src/operator_control_center_artifacts.py b/src/github_repo_auditor/operator_control_center_artifacts.py similarity index 93% rename from src/operator_control_center_artifacts.py rename to src/github_repo_auditor/operator_control_center_artifacts.py index e4c71394..bb2dcbc0 100644 --- a/src/operator_control_center_artifacts.py +++ b/src/github_repo_auditor/operator_control_center_artifacts.py @@ -6,13 +6,13 @@ from datetime import datetime from pathlib import Path -from src.cache import contains_sensitive_data -from src.operator_artifact_paths import control_center_paths -from src.operator_control_center import ( +from github_repo_auditor.cache import contains_sensitive_data +from github_repo_auditor.operator_artifact_paths import control_center_paths +from github_repo_auditor.operator_control_center import ( control_center_artifact_payload, render_control_center_markdown, ) -from src.weekly_command_center import ( +from github_repo_auditor.weekly_command_center import ( build_weekly_command_center_digest, load_latest_portfolio_truth, write_weekly_command_center_artifacts, diff --git a/src/operator_control_center_rendering.py b/src/github_repo_auditor/operator_control_center_rendering.py similarity index 99% rename from src/operator_control_center_rendering.py rename to src/github_repo_auditor/operator_control_center_rendering.py index c700df40..72aab972 100644 --- a/src/operator_control_center_rendering.py +++ b/src/github_repo_auditor/operator_control_center_rendering.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.terminology import ACTION_SYNC_CANONICAL_LABELS +from github_repo_auditor.terminology import ACTION_SYNC_CANONICAL_LABELS LANE_LABELS = { "blocked": "Blocked", diff --git a/src/operator_decision_quality.py b/src/github_repo_auditor/operator_decision_quality.py similarity index 98% rename from src/operator_decision_quality.py rename to src/github_repo_auditor/operator_decision_quality.py index 7c7976a9..55b23fa7 100644 --- a/src/operator_decision_quality.py +++ b/src/github_repo_auditor/operator_decision_quality.py @@ -9,7 +9,7 @@ def build_confidence_calibration(history: list[dict[str, Any]]) -> dict[str, Any]: - from src import operator_resolution_trend as _operator_resolution_trend + from github_repo_auditor import operator_resolution_trend as _operator_resolution_trend return _operator_resolution_trend._build_confidence_calibration(history) @@ -21,7 +21,7 @@ def confidence_validation_status( reopened_recommendation_count: int, reopened_high_count: int, ) -> str: - from src import operator_resolution_trend as _operator_resolution_trend + from github_repo_auditor import operator_resolution_trend as _operator_resolution_trend return _operator_resolution_trend._confidence_validation_status( judged_count=judged_count, @@ -40,7 +40,7 @@ def confidence_calibration_summary( reopened_recommendation_count: int, judged_count: int, ) -> str: - from src import operator_resolution_trend as _operator_resolution_trend + from github_repo_auditor import operator_resolution_trend as _operator_resolution_trend return _operator_resolution_trend._confidence_calibration_summary( confidence_validation_status=confidence_validation_status, diff --git a/src/operator_effectiveness.py b/src/github_repo_auditor/operator_effectiveness.py similarity index 100% rename from src/operator_effectiveness.py rename to src/github_repo_auditor/operator_effectiveness.py diff --git a/src/operator_follow_through.py b/src/github_repo_auditor/operator_follow_through.py similarity index 100% rename from src/operator_follow_through.py rename to src/github_repo_auditor/operator_follow_through.py diff --git a/src/operator_os_seam_linter.py b/src/github_repo_auditor/operator_os_seam_linter.py similarity index 98% rename from src/operator_os_seam_linter.py rename to src/github_repo_auditor/operator_os_seam_linter.py index 5e29fb80..a59752d3 100644 --- a/src/operator_os_seam_linter.py +++ b/src/github_repo_auditor/operator_os_seam_linter.py @@ -10,15 +10,15 @@ from pathlib import Path from typing import Any -from src.portfolio_truth_render import GENERATED_MARKDOWN_PROVENANCE_MARKER -from src.portfolio_truth_lineage import resolve_notion_origin -from src.portfolio_truth_types import LEGACY_SCHEMA_VERSIONS, SCHEMA_VERSION, truth_latest_path -from src.portfolio_catalog import load_portfolio_catalog -from src.portfolio_truth_sources import ( +from github_repo_auditor.portfolio_truth_render import GENERATED_MARKDOWN_PROVENANCE_MARKER +from github_repo_auditor.portfolio_truth_lineage import resolve_notion_origin +from github_repo_auditor.portfolio_truth_types import LEGACY_SCHEMA_VERSIONS, SCHEMA_VERSION, truth_latest_path +from github_repo_auditor.portfolio_catalog import load_portfolio_catalog +from github_repo_auditor.portfolio_truth_sources import ( WORKSPACE_DISCOVERY_POLICY_VERSION, workspace_exclusion_reason, ) -from src.project_registry import ( +from github_repo_auditor.project_registry import ( BRIDGE_CANONICAL_KEY_DISAGREEMENTS, DEFAULT_NOTION_PROJECTION_ONLY_ROWS, DEFAULT_NOTION_TITLE_ALIASES, @@ -39,7 +39,7 @@ "~/.local/share/notion-os/project-snapshot.json" ).expanduser() DEFAULT_PROJECT_REGISTRY_OVERRIDES_PATH = ( - Path(__file__).resolve().parents[1] / "config" / "project-registry-overrides.json" + Path(__file__).resolve().parents[2] / "config" / "project-registry-overrides.json" ) HEX_FRAGMENT_RE = re.compile(r"^[0-9a-fA-F]{3}$") @@ -179,7 +179,7 @@ def build_worklist_payload(result: SeamLintResult) -> dict[str, Any]: "target_id": finding.artifact, "created_at": result.generated_at.isoformat(), "suggested_command": ( - "uv run python -m src.operator_os_seam_linter " + "uv run python -m github_repo_auditor.operator_os_seam_linter " "--truth output/portfolio-truth-latest.json" ), "metadata_json": json.dumps(finding.to_dict(), sort_keys=True), diff --git a/src/operator_prefs.py b/src/github_repo_auditor/operator_prefs.py similarity index 100% rename from src/operator_prefs.py rename to src/github_repo_auditor/operator_prefs.py diff --git a/src/operator_resolution_trend.py b/src/github_repo_auditor/operator_resolution_trend.py similarity index 98% rename from src/operator_resolution_trend.py rename to src/github_repo_auditor/operator_resolution_trend.py index 59199656..35b6f187 100644 --- a/src/operator_resolution_trend.py +++ b/src/github_repo_auditor/operator_resolution_trend.py @@ -2,239 +2,239 @@ from typing import NamedTuple -from src.operator_trend_apply_chain import ( +from github_repo_auditor.operator_trend_apply_chain import ( run_resolution_trend_apply_chain as _run_resolution_trend_apply_chain_helper, ) -from src.operator_trend_class_transitions import ( +from github_repo_auditor.operator_trend_class_transitions import ( build_class_reweight_events as _build_class_reweight_events_helper, ) -from src.operator_trend_class_transitions import ( +from github_repo_auditor.operator_trend_class_transitions import ( build_class_transition_events as _build_class_transition_events_helper, ) -from src.operator_trend_class_transitions import ( +from github_repo_auditor.operator_trend_class_transitions import ( consecutive_transition_runs as _consecutive_transition_runs_helper, ) -from src.operator_trend_class_transitions import ( +from github_repo_auditor.operator_trend_class_transitions import ( current_transition_strengthening as _current_transition_strengthening_helper, ) -from src.operator_trend_class_transitions import ( +from github_repo_auditor.operator_trend_class_transitions import ( pending_transition_direction as _pending_transition_direction_helper, ) -from src.operator_trend_class_transitions import ( +from github_repo_auditor.operator_trend_class_transitions import ( target_class_transition_history as _target_class_transition_history_helper, ) -from src.operator_trend_closure_forecast_core import ( +from github_repo_auditor.operator_trend_closure_forecast_core import ( apply_closure_forecast_reweighting_control as _apply_closure_forecast_reweighting_control_helper, ) -from src.operator_trend_closure_forecast_core import ( +from github_repo_auditor.operator_trend_closure_forecast_core import ( class_closure_forecast_events as _class_closure_forecast_events_helper, ) -from src.operator_trend_closure_forecast_core import ( +from github_repo_auditor.operator_trend_closure_forecast_core import ( closure_forecast_hotspots as _closure_forecast_hotspots_helper, ) -from src.operator_trend_closure_forecast_core import ( +from github_repo_auditor.operator_trend_closure_forecast_core import ( pending_debt_freshness_hotspots as _pending_debt_freshness_hotspots_helper, ) -from src.operator_trend_closure_forecast_core import ( +from github_repo_auditor.operator_trend_closure_forecast_core import ( target_closure_forecast_history as _target_closure_forecast_history_helper, ) -from src.operator_trend_closure_forecast_freshness_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_freshness_controls import ( apply_closure_forecast_decay_control as _apply_closure_forecast_decay_control, ) -from src.operator_trend_closure_forecast_freshness_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_freshness_controls import ( closure_forecast_freshness_for_target as _closure_forecast_freshness_for_target, ) -from src.operator_trend_closure_forecast_freshness_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_freshness_controls import ( closure_forecast_freshness_hotspots as _closure_forecast_freshness_hotspots, ) -from src.operator_trend_closure_forecast_freshness_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_freshness_controls import ( closure_forecast_freshness_status as _closure_forecast_freshness_status, ) -from src.operator_trend_closure_forecast_reacquisition_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls import ( apply_closure_forecast_reacquisition_control as _apply_closure_forecast_reacquisition_control, ) -from src.operator_trend_closure_forecast_reacquisition_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls import ( apply_reacquisition_freshness_reset_control as _apply_reacquisition_freshness_reset_control, ) -from src.operator_trend_closure_forecast_reacquisition_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls import ( apply_reacquisition_persistence_and_churn_control as _apply_reacquisition_persistence_and_churn_control, ) -from src.operator_trend_closure_forecast_reacquisition_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls import ( closure_forecast_persistence_reset_summary as _closure_forecast_persistence_reset_summary, ) -from src.operator_trend_closure_forecast_reacquisition_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls import ( closure_forecast_reacquisition_freshness_for_target as _closure_forecast_reacquisition_freshness_for_target, ) -from src.operator_trend_closure_forecast_reacquisition_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls import ( closure_forecast_reacquisition_freshness_hotspots as _closure_forecast_reacquisition_freshness_hotspots, ) -from src.operator_trend_closure_forecast_reacquisition_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls import ( closure_forecast_reacquisition_freshness_summary as _closure_forecast_reacquisition_freshness_summary, ) -from src.operator_trend_closure_forecast_reacquisition_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls import ( closure_forecast_reacquisition_hotspots as _closure_forecast_reacquisition_hotspots, ) -from src.operator_trend_closure_forecast_reacquisition_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls import ( closure_forecast_reacquisition_persistence_for_target as _closure_forecast_reacquisition_persistence_for_target, ) -from src.operator_trend_closure_forecast_reacquisition_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls import ( closure_forecast_reacquisition_persistence_summary as _closure_forecast_reacquisition_persistence_summary, ) -from src.operator_trend_closure_forecast_reacquisition_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls import ( closure_forecast_reacquisition_summary as _closure_forecast_reacquisition_summary, ) -from src.operator_trend_closure_forecast_reacquisition_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls import ( closure_forecast_recovery_churn_for_target as _closure_forecast_recovery_churn_for_target, ) -from src.operator_trend_closure_forecast_reacquisition_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls import ( closure_forecast_recovery_churn_summary as _closure_forecast_recovery_churn_summary, ) -from src.operator_trend_closure_forecast_reacquisition_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls import ( closure_forecast_refresh_hotspots as _closure_forecast_refresh_hotspots, ) -from src.operator_trend_closure_forecast_reacquisition_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls import ( closure_forecast_refresh_recovery_for_target as _closure_forecast_refresh_recovery_for_target, ) -from src.operator_trend_closure_forecast_reacquisition_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls import ( closure_forecast_refresh_recovery_summary as _closure_forecast_refresh_recovery_summary, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( apply_reset_reentry_freshness_reset_control as _apply_reset_reentry_freshness_reset_control, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( apply_reset_reentry_rebuild_freshness_and_reset as _apply_reset_reentry_rebuild_freshness_and_reset_helper, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( apply_reset_reentry_rebuild_persistence_and_churn_control as _apply_reset_reentry_rebuild_persistence_and_churn_control, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( apply_reset_reentry_rebuild_reentry_refresh_recovery_and_restore as _apply_reset_reentry_rebuild_reentry_refresh_recovery_and_restore_helper, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( apply_reset_reentry_rebuild_reentry_restore_rerererestore_persistence_and_churn as _apply_reset_reentry_rebuild_reentry_restore_rerererestore_persistence_and_churn_helper, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( apply_reset_reentry_rebuild_reentry_restore_rererestore_freshness_and_reset as _apply_reset_reentry_rebuild_reentry_restore_rererestore_freshness_and_reset_helper, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( apply_reset_reentry_rebuild_reentry_restore_rererestore_persistence_and_churn as _apply_reset_reentry_rebuild_reentry_restore_rererestore_persistence_and_churn_helper, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( apply_reset_reentry_rebuild_reentry_restore_rererestore_refresh_recovery_and_rerererestore as _apply_reset_reentry_rebuild_reentry_restore_rererestore_refresh_recovery_and_rerererestore_helper, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( apply_reset_reentry_refresh_rebuild_control as _apply_reset_reentry_refresh_rebuild_control, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( apply_reset_refresh_reentry_control as _apply_reset_refresh_reentry_control, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( closure_forecast_reset_reentry_freshness_for_target as _closure_forecast_reset_reentry_freshness_for_target, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( closure_forecast_reset_reentry_freshness_hotspots as _closure_forecast_reset_reentry_freshness_hotspots, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( closure_forecast_reset_reentry_freshness_summary as _closure_forecast_reset_reentry_freshness_summary, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( closure_forecast_reset_reentry_rebuild_churn_for_target as _closure_forecast_reset_reentry_rebuild_churn_for_target, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( closure_forecast_reset_reentry_rebuild_churn_summary as _closure_forecast_reset_reentry_rebuild_churn_summary, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( closure_forecast_reset_reentry_rebuild_hotspots as _closure_forecast_reset_reentry_rebuild_hotspots, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( closure_forecast_reset_reentry_rebuild_persistence_for_target as _closure_forecast_reset_reentry_rebuild_persistence_for_target, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( closure_forecast_reset_reentry_rebuild_persistence_summary as _closure_forecast_reset_reentry_rebuild_persistence_summary, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( closure_forecast_reset_reentry_rebuild_summary as _closure_forecast_reset_reentry_rebuild_summary, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( closure_forecast_reset_reentry_refresh_hotspots as _closure_forecast_reset_reentry_refresh_hotspots, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( closure_forecast_reset_reentry_refresh_recovery_for_target as _closure_forecast_reset_reentry_refresh_recovery_for_target, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( closure_forecast_reset_reentry_refresh_recovery_summary as _closure_forecast_reset_reentry_refresh_recovery_summary, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( closure_forecast_reset_reentry_reset_summary as _closure_forecast_reset_reentry_reset_summary, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( closure_forecast_reset_reentry_summary as _closure_forecast_reset_reentry_summary, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( closure_forecast_reset_refresh_hotspots as _closure_forecast_reset_refresh_hotspots, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( closure_forecast_reset_refresh_recovery_for_target as _closure_forecast_reset_refresh_recovery_for_target, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( closure_forecast_reset_refresh_recovery_summary as _closure_forecast_reset_refresh_recovery_summary, ) -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( closure_forecast_reset_side_from_status as _closure_forecast_reset_side_from_status, ) -from src.operator_trend_confidence_calibration import ( +from github_repo_auditor.operator_trend_confidence_calibration import ( build_confidence_calibration as _build_confidence_calibration_helper, ) -from src.operator_trend_decision_memory import ( +from github_repo_auditor.operator_trend_decision_memory import ( absent_decision_memory as _absent_decision_memory, ) -from src.operator_trend_decision_memory import ( +from github_repo_auditor.operator_trend_decision_memory import ( current_item_last_outcome as _current_item_last_outcome, ) -from src.operator_trend_decision_memory import ( +from github_repo_auditor.operator_trend_decision_memory import ( current_item_resolution_evidence as _current_item_resolution_evidence, ) -from src.operator_trend_decision_memory import ( +from github_repo_auditor.operator_trend_decision_memory import ( recent_interventions as _recent_interventions, ) -from src.operator_trend_decision_memory import ( +from github_repo_auditor.operator_trend_decision_memory import ( summary_decision_memory as _summary_decision_memory, ) -from src.operator_trend_exception_recovery import ( +from github_repo_auditor.operator_trend_exception_recovery import ( exception_pattern_summary as _exception_pattern_summary_helper, ) -from src.operator_trend_exception_recovery import ( +from github_repo_auditor.operator_trend_exception_recovery import ( recovery_pattern_reason as _recovery_pattern_reason_helper, ) -from src.operator_trend_exception_recovery import ( +from github_repo_auditor.operator_trend_exception_recovery import ( trust_recovery_for_target as _trust_recovery_for_target_helper, ) -from src.operator_trend_pending_debt import ( +from github_repo_auditor.operator_trend_pending_debt import ( class_pending_debt_for_target as _class_pending_debt_for_target_helper, ) -from src.operator_trend_pending_debt import ( +from github_repo_auditor.operator_trend_pending_debt import ( class_pending_debt_hotspots as _class_pending_debt_hotspots_helper, ) -from src.operator_trend_pending_debt import ( +from github_repo_auditor.operator_trend_pending_debt import ( closure_forecast_reweight_scores_for_target as _closure_forecast_reweight_scores_for_target_helper, ) -from src.operator_trend_pending_debt import ( +from github_repo_auditor.operator_trend_pending_debt import ( pending_debt_event_outcome as _pending_debt_event_outcome_helper, ) -from src.operator_trend_pending_debt import ( +from github_repo_auditor.operator_trend_pending_debt import ( pending_debt_freshness_for_target as _pending_debt_freshness_for_target_helper, ) -from src.operator_trend_pending_debt import ( +from github_repo_auditor.operator_trend_pending_debt import ( pending_debt_freshness_reason as _pending_debt_freshness_reason_helper, ) -from src.operator_trend_pending_debt import ( +from github_repo_auditor.operator_trend_pending_debt import ( pending_debt_freshness_status as _pending_debt_freshness_status_helper, ) -from src.operator_trend_pending_debt import ( +from github_repo_auditor.operator_trend_pending_debt import ( recent_pending_signal_mix as _recent_pending_signal_mix_helper, ) -from src.operator_trend_pending_text import ( +from github_repo_auditor.operator_trend_pending_text import ( class_pending_debt_summary, class_pending_resolution_summary, closure_forecast_reweighting_summary, @@ -242,43 +242,43 @@ pending_debt_freshness_summary, transition_closure_confidence_summary, ) -from src.operator_trend_recommendation_drift import ( +from github_repo_auditor.operator_trend_recommendation_drift import ( recommendation_drift_status as _recommendation_drift_status, ) -from src.operator_trend_recommendation_drift import ( +from github_repo_auditor.operator_trend_recommendation_drift import ( recommendation_drift_summary as _recommendation_drift_summary, ) -from src.operator_trend_run_context import ( +from github_repo_auditor.operator_trend_run_context import ( build_resolution_run_context as _build_resolution_run_context_helper, ) -from src.operator_trend_summary_context import ( +from github_repo_auditor.operator_trend_summary_context import ( build_trend_summary_context as _build_trend_summary_context_helper, ) -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( class_direction_flip_count as _class_direction_flip_count, ) -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( clamp_round as _clamp_round, ) -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( closure_forecast_direction_majority as _closure_forecast_direction_majority, ) -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( closure_forecast_direction_reversing as _closure_forecast_direction_reversing, ) -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( normalized_closure_forecast_direction as _normalized_closure_forecast_direction, ) -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( target_class_key as _target_class_key, ) -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( target_label as _target_label, ) -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( target_specific_normalization_noise as _target_specific_normalization_noise, ) -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( ATTENTION_LANES, CLASS_CLOSURE_FORECAST_FRESHNESS_WINDOW_RUNS, CLASS_CLOSURE_FORECAST_REFRESH_WINDOW_RUNS, @@ -319,82 +319,82 @@ is_generic_monitor_guidance, is_generic_recommendation, ) -from src.operator_trend_topline_payload import ( +from github_repo_auditor.operator_trend_topline_payload import ( build_resolution_trend_topline_payload as _build_resolution_trend_topline_payload_helper, ) -from src.operator_trend_resolution_payload import build_resolution_trend_payload -from src.operator_trend_transition_closure import ( +from github_repo_auditor.operator_trend_resolution_payload import build_resolution_trend_payload +from github_repo_auditor.operator_trend_transition_closure import ( apply_transition_closure_control as _apply_transition_closure_control_helper, ) -from src.operator_trend_transition_closure import ( +from github_repo_auditor.operator_trend_transition_closure import ( transition_closure_confidence_for_target as _transition_closure_confidence_for_target_helper, ) -from src.operator_trend_trust_policy_history import ( +from github_repo_auditor.operator_trend_trust_policy_history import ( build_trust_exception_events as _build_trust_exception_events_helper, ) -from src.operator_trend_trust_policy_history import ( +from github_repo_auditor.operator_trend_trust_policy_history import ( build_trust_policy_events as _build_trust_policy_events_helper, ) -from src.operator_trend_trust_policy_history import ( +from github_repo_auditor.operator_trend_trust_policy_history import ( exception_case_outcome as _exception_case_outcome_helper, ) -from src.operator_trend_trust_policy_history import ( +from github_repo_auditor.operator_trend_trust_policy_history import ( exception_pattern_for_target as _exception_pattern_for_target_helper, ) -from src.operator_trend_trust_policy_history import ( +from github_repo_auditor.operator_trend_trust_policy_history import ( false_positive_exception_hotspots as _false_positive_exception_hotspots_helper, ) -from src.operator_trend_trust_policy_history import ( +from github_repo_auditor.operator_trend_trust_policy_history import ( group_exception_hotspots as _group_exception_hotspots_helper, ) -from src.operator_trend_trust_policy_history import ( +from github_repo_auditor.operator_trend_trust_policy_history import ( group_policy_hotspots as _group_policy_hotspots_helper, ) -from src.operator_trend_trust_policy_history import ( +from github_repo_auditor.operator_trend_trust_policy_history import ( historical_exception_cases as _historical_exception_cases_helper, ) -from src.operator_trend_trust_policy_history import ( +from github_repo_auditor.operator_trend_trust_policy_history import ( policy_flip_count as _policy_flip_count_helper, ) -from src.operator_trend_trust_policy_history import ( +from github_repo_auditor.operator_trend_trust_policy_history import ( policy_flip_hotspots as _policy_flip_hotspots_helper, ) -from src.operator_trend_trust_policy_history import ( +from github_repo_auditor.operator_trend_trust_policy_history import ( soften_trust_policy as _soften_trust_policy_helper, ) -from src.operator_trend_trust_policy_history import ( +from github_repo_auditor.operator_trend_trust_policy_history import ( strong_policy_failure_count as _strong_policy_failure_count_helper, ) -from src.operator_trend_trust_policy_history import ( +from github_repo_auditor.operator_trend_trust_policy_history import ( target_exception_history as _target_exception_history_helper, ) -from src.operator_trend_trust_policy_history import ( +from github_repo_auditor.operator_trend_trust_policy_history import ( target_policy_history as _target_policy_history_helper, ) -from src.operator_trend_trust_policy_history import ( +from github_repo_auditor.operator_trend_trust_policy_history import ( trust_policy_exception_for_target as _trust_policy_exception_for_target_helper, ) -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( closure_forecast_reset_reentry_side_from_event as _closure_forecast_reset_reentry_side_from_event, ) -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( closure_forecast_reset_reentry_side_from_recovery_status as _closure_forecast_reset_reentry_side_from_recovery_status, ) -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( closure_forecast_reset_reentry_side_from_status as _closure_forecast_reset_reentry_side_from_status, ) -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( ordered_reset_reentry_events_for_target as _ordered_reset_reentry_events_for_target, ) -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( queue_identity as _queue_identity, ) -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( recommendation_bucket as _recommendation_bucket, ) -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( resolve_side as _resolve_side, ) @@ -17558,12 +17558,12 @@ def _was_resolved_then_reopened( def _attach_portfolio_catalog_context( queue: list[dict], report_data: dict ) -> list[dict]: - from src.portfolio_catalog import build_catalog_line, evaluate_intent_alignment - from src.portfolio_pathing import ( + from github_repo_auditor.portfolio_catalog import build_catalog_line, evaluate_intent_alignment + from github_repo_auditor.portfolio_pathing import ( build_operating_path_entry, build_operating_path_line, ) - from src.report_enrichment import ( + from github_repo_auditor.report_enrichment import ( build_maturity_gap_summary, build_operator_focus, build_scorecard_line, diff --git a/src/operator_snapshot_packaging.py b/src/github_repo_auditor/operator_snapshot_packaging.py similarity index 100% rename from src/operator_snapshot_packaging.py rename to src/github_repo_auditor/operator_snapshot_packaging.py diff --git a/src/operator_trend_apply_chain.py b/src/github_repo_auditor/operator_trend_apply_chain.py similarity index 100% rename from src/operator_trend_apply_chain.py rename to src/github_repo_auditor/operator_trend_apply_chain.py diff --git a/src/operator_trend_class_transitions.py b/src/github_repo_auditor/operator_trend_class_transitions.py similarity index 100% rename from src/operator_trend_class_transitions.py rename to src/github_repo_auditor/operator_trend_class_transitions.py diff --git a/src/operator_trend_closure_forecast_core.py b/src/github_repo_auditor/operator_trend_closure_forecast_core.py similarity index 100% rename from src/operator_trend_closure_forecast_core.py rename to src/github_repo_auditor/operator_trend_closure_forecast_core.py diff --git a/src/operator_trend_closure_forecast_freshness_controls.py b/src/github_repo_auditor/operator_trend_closure_forecast_freshness_controls.py similarity index 99% rename from src/operator_trend_closure_forecast_freshness_controls.py rename to src/github_repo_auditor/operator_trend_closure_forecast_freshness_controls.py index 266292e7..db99f179 100644 --- a/src/operator_trend_closure_forecast_freshness_controls.py +++ b/src/github_repo_auditor/operator_trend_closure_forecast_freshness_controls.py @@ -2,7 +2,7 @@ from typing import Any -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( CLASS_CLOSURE_FORECAST_FRESHNESS_WINDOW_RUNS, CLASS_MEMORY_RECENCY_WEIGHTS, HISTORY_WINDOW_RUNS, diff --git a/src/operator_trend_closure_forecast_reacquisition_controls.py b/src/github_repo_auditor/operator_trend_closure_forecast_reacquisition_controls.py similarity index 99% rename from src/operator_trend_closure_forecast_reacquisition_controls.py rename to src/github_repo_auditor/operator_trend_closure_forecast_reacquisition_controls.py index 0454d924..79d2a07d 100644 --- a/src/operator_trend_closure_forecast_reacquisition_controls.py +++ b/src/github_repo_auditor/operator_trend_closure_forecast_reacquisition_controls.py @@ -2,11 +2,11 @@ from typing import Any -from src.operator_trend_closure_forecast_freshness_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_freshness_controls import ( closure_forecast_event_has_evidence, closure_forecast_freshness_status, ) -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( CLASS_CLOSURE_FORECAST_REFRESH_WINDOW_RUNS, CLASS_MEMORY_RECENCY_WEIGHTS, CLASS_REACQUISITION_FRESHNESS_WINDOW_RUNS, diff --git a/src/operator_trend_closure_forecast_reset_controls.py b/src/github_repo_auditor/operator_trend_closure_forecast_reset_controls.py similarity index 99% rename from src/operator_trend_closure_forecast_reset_controls.py rename to src/github_repo_auditor/operator_trend_closure_forecast_reset_controls.py index 41c1a71e..f25dfc8b 100644 --- a/src/operator_trend_closure_forecast_reset_controls.py +++ b/src/github_repo_auditor/operator_trend_closure_forecast_reset_controls.py @@ -2,10 +2,10 @@ from typing import Any, Callable, NamedTuple -from src.operator_trend_closure_forecast_freshness_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_freshness_controls import ( closure_forecast_freshness_status, ) -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( CLASS_MEMORY_RECENCY_WEIGHTS, CLASS_RESET_REENTRY_FRESHNESS_WINDOW_RUNS, CLASS_RESET_REENTRY_REBUILD_FRESHNESS_WINDOW_RUNS, diff --git a/src/operator_trend_confidence_calibration.py b/src/github_repo_auditor/operator_trend_confidence_calibration.py similarity index 99% rename from src/operator_trend_confidence_calibration.py rename to src/github_repo_auditor/operator_trend_confidence_calibration.py index f04dbd52..8fc25f4a 100644 --- a/src/operator_trend_confidence_calibration.py +++ b/src/github_repo_auditor/operator_trend_confidence_calibration.py @@ -2,7 +2,7 @@ from typing import Any, Callable -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( ATTENTION_LANES, CALIBRATION_WINDOW_RUNS, VALIDATION_WINDOW_RUNS, diff --git a/src/operator_trend_decision_memory.py b/src/github_repo_auditor/operator_trend_decision_memory.py similarity index 100% rename from src/operator_trend_decision_memory.py rename to src/github_repo_auditor/operator_trend_decision_memory.py diff --git a/src/operator_trend_exception_recovery.py b/src/github_repo_auditor/operator_trend_exception_recovery.py similarity index 100% rename from src/operator_trend_exception_recovery.py rename to src/github_repo_auditor/operator_trend_exception_recovery.py diff --git a/src/operator_trend_pending_debt.py b/src/github_repo_auditor/operator_trend_pending_debt.py similarity index 100% rename from src/operator_trend_pending_debt.py rename to src/github_repo_auditor/operator_trend_pending_debt.py diff --git a/src/operator_trend_pending_text.py b/src/github_repo_auditor/operator_trend_pending_text.py similarity index 100% rename from src/operator_trend_pending_text.py rename to src/github_repo_auditor/operator_trend_pending_text.py diff --git a/src/operator_trend_recommendation_drift.py b/src/github_repo_auditor/operator_trend_recommendation_drift.py similarity index 100% rename from src/operator_trend_recommendation_drift.py rename to src/github_repo_auditor/operator_trend_recommendation_drift.py diff --git a/src/operator_trend_resolution_payload.py b/src/github_repo_auditor/operator_trend_resolution_payload.py similarity index 100% rename from src/operator_trend_resolution_payload.py rename to src/github_repo_auditor/operator_trend_resolution_payload.py diff --git a/src/operator_trend_run_context.py b/src/github_repo_auditor/operator_trend_run_context.py similarity index 100% rename from src/operator_trend_run_context.py rename to src/github_repo_auditor/operator_trend_run_context.py diff --git a/src/operator_trend_summary_context.py b/src/github_repo_auditor/operator_trend_summary_context.py similarity index 100% rename from src/operator_trend_summary_context.py rename to src/github_repo_auditor/operator_trend_summary_context.py diff --git a/src/operator_trend_support.py b/src/github_repo_auditor/operator_trend_support.py similarity index 100% rename from src/operator_trend_support.py rename to src/github_repo_auditor/operator_trend_support.py diff --git a/src/operator_trend_topline_payload.py b/src/github_repo_auditor/operator_trend_topline_payload.py similarity index 100% rename from src/operator_trend_topline_payload.py rename to src/github_repo_auditor/operator_trend_topline_payload.py diff --git a/src/operator_trend_transition_closure.py b/src/github_repo_auditor/operator_trend_transition_closure.py similarity index 100% rename from src/operator_trend_transition_closure.py rename to src/github_repo_auditor/operator_trend_transition_closure.py diff --git a/src/operator_trend_trust_policy_history.py b/src/github_repo_auditor/operator_trend_trust_policy_history.py similarity index 100% rename from src/operator_trend_trust_policy_history.py rename to src/github_repo_auditor/operator_trend_trust_policy_history.py diff --git a/src/ops_writeback.py b/src/github_repo_auditor/ops_writeback.py similarity index 99% rename from src/ops_writeback.py rename to src/github_repo_auditor/ops_writeback.py index 44035af8..82fdd690 100644 --- a/src/ops_writeback.py +++ b/src/github_repo_auditor/ops_writeback.py @@ -4,8 +4,8 @@ from collections import defaultdict from datetime import datetime, timezone -from src.analyst_views import build_analyst_context -from src.github_projects import ( +from github_repo_auditor.analyst_views import build_analyst_context +from github_repo_auditor.github_projects import ( build_project_field_values, build_project_preview_summary, is_github_projects_config_valid, diff --git a/src/ossf_scorecard.py b/src/github_repo_auditor/ossf_scorecard.py similarity index 99% rename from src/ossf_scorecard.py rename to src/github_repo_auditor/ossf_scorecard.py index 87e650bf..ccb6a4de 100644 --- a/src/ossf_scorecard.py +++ b/src/github_repo_auditor/ossf_scorecard.py @@ -14,7 +14,7 @@ from requests.adapters import HTTPAdapter from urllib3.util import Retry -from src.cache import ResponseCache +from github_repo_auditor.cache import ResponseCache logger = logging.getLogger(__name__) diff --git a/src/pdf_export.py b/src/github_repo_auditor/pdf_export.py similarity index 100% rename from src/pdf_export.py rename to src/github_repo_auditor/pdf_export.py diff --git a/src/plan_campaign.py b/src/github_repo_auditor/plan_campaign.py similarity index 96% rename from src/plan_campaign.py rename to src/github_repo_auditor/plan_campaign.py index e70c2b9e..9da80de8 100644 --- a/src/plan_campaign.py +++ b/src/github_repo_auditor/plan_campaign.py @@ -15,9 +15,9 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from src.llm_cost import CostTracker - from src.narrative import NarrativeProvider - from src.semantic_index import SemanticIndex + from github_repo_auditor.llm_cost import CostTracker + from github_repo_auditor.narrative import NarrativeProvider + from github_repo_auditor.semantic_index import SemanticIndex logger = logging.getLogger(__name__) @@ -206,7 +206,7 @@ def generate_action_for_repo( Robustly handles non-JSON or malformed responses by returning ``None`` and logging a warning. """ - from src.llm_cost import BudgetExceededError + from github_repo_auditor.llm_cost import BudgetExceededError repo_name = str(repo.get("repo_name") or repo.get("name") or "unknown") prompt = _build_action_prompt(repo, goal=goal) @@ -299,7 +299,7 @@ def generate_plan( Respects operator prefs via :func:`is_suppressed` for action_type ``"campaign-plan"`` with the repo name as ``target_context``. """ - from src.operator_prefs import is_suppressed + from github_repo_auditor.operator_prefs import is_suppressed actions: list[CampaignAction] = [] candidate_count = len(candidates) @@ -380,7 +380,7 @@ def write_packet_to_ledger( Returns the ``record_id`` string. """ - from src.warehouse import save_approval_record + from github_repo_auditor.warehouse import save_approval_record record_id = _packet_record_id(packet) subject_key = _goal_subject_key(packet.goal) @@ -437,7 +437,7 @@ def load_approved_campaign_plans(warehouse_path: Path) -> list[CampaignPlanPacke Hydrates the CampaignPlanPacket dataclass from the stored fields. Skips records older than 30 days. """ - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records all_records = load_approval_records(warehouse_path, "", limit=500) cutoff = datetime.now(timezone.utc).replace(microsecond=0) @@ -521,7 +521,7 @@ def load_approved_campaign_plans(warehouse_path: Path) -> list[CampaignPlanPacke def mark_campaign_applied(packet: CampaignPlanPacket, output_dir: Path) -> None: """Update the ledger record state from 'approved-manual' to 'applied'.""" - from src.warehouse import load_approval_records, save_approval_record + from github_repo_auditor.warehouse import load_approval_records, save_approval_record records = load_approval_records(output_dir, "", limit=500) record_id = _packet_record_id(packet) @@ -559,7 +559,7 @@ def approve_action(packet_id: str, action_idx: int, output_dir: Path) -> None: Raises ValueError if the packet is not found. Raises IndexError if action_idx is out of range. """ - from src.warehouse import load_approval_records, save_approval_record + from github_repo_auditor.warehouse import load_approval_records, save_approval_record records = load_approval_records(output_dir, "", limit=500) matching = [ @@ -595,7 +595,7 @@ def reject_action(packet_id: str, action_idx: int, output_dir: Path, reason: str Raises ValueError if the packet is not found. Raises IndexError if action_idx is out of range. """ - from src.warehouse import load_approval_records, save_approval_record + from github_repo_auditor.warehouse import load_approval_records, save_approval_record records = load_approval_records(output_dir, "", limit=500) matching = [ @@ -635,7 +635,7 @@ def record_campaign_apply_failure( """ import uuid - from src.warehouse import save_approval_followup_event + from github_repo_auditor.warehouse import save_approval_followup_event record_id = _packet_record_id(packet) event = { @@ -674,7 +674,7 @@ def dispatch_action( enable_dependabot → (False, "handler not yet implemented") pending_human_action → (False, "manual review required") """ - from src.repo_improver import apply_metadata_updates, apply_readme_updates + from github_repo_auditor.repo_improver import apply_metadata_updates, apply_readme_updates action_type = action.action_type diff --git a/src/portfolio_automation.py b/src/github_repo_auditor/portfolio_automation.py similarity index 98% rename from src/portfolio_automation.py rename to src/github_repo_auditor/portfolio_automation.py index 9c82f09d..d1be3e3f 100644 --- a/src/portfolio_automation.py +++ b/src/github_repo_auditor/portfolio_automation.py @@ -19,7 +19,7 @@ from dataclasses import dataclass from typing import Any -from src.portfolio_checkout_authority import checkout_authority_blocker +from github_repo_auditor.portfolio_checkout_authority import checkout_authority_blocker CONTRACT_VERSION = "automation_candidates_v1" diff --git a/src/portfolio_catalog.py b/src/github_repo_auditor/portfolio_catalog.py similarity index 99% rename from src/portfolio_catalog.py rename to src/github_repo_auditor/portfolio_catalog.py index 4e46cd13..1cde6537 100644 --- a/src/portfolio_catalog.py +++ b/src/github_repo_auditor/portfolio_catalog.py @@ -5,7 +5,7 @@ from pathlib import Path from typing import Any -from src.portfolio_pathing import VALID_OPERATING_PATHS +from github_repo_auditor.portfolio_pathing import VALID_OPERATING_PATHS VALID_LIFECYCLE_STATES = { "active", diff --git a/src/portfolio_checkout_authority.py b/src/github_repo_auditor/portfolio_checkout_authority.py similarity index 99% rename from src/portfolio_checkout_authority.py rename to src/github_repo_auditor/portfolio_checkout_authority.py index 28679249..0934eb15 100644 --- a/src/portfolio_checkout_authority.py +++ b/src/github_repo_auditor/portfolio_checkout_authority.py @@ -6,7 +6,7 @@ from pathlib import Path from typing import Any -from src.portfolio_truth_types import CHECKOUT_COLLISION_SCHEMA_VERSION +from github_repo_auditor.portfolio_truth_types import CHECKOUT_COLLISION_SCHEMA_VERSION @dataclass(frozen=True) diff --git a/src/portfolio_context_contract.py b/src/github_repo_auditor/portfolio_context_contract.py similarity index 100% rename from src/portfolio_context_contract.py rename to src/github_repo_auditor/portfolio_context_contract.py diff --git a/src/portfolio_context_recovery.py b/src/github_repo_auditor/portfolio_context_recovery.py similarity index 99% rename from src/portfolio_context_recovery.py rename to src/github_repo_auditor/portfolio_context_recovery.py index 89b025c7..bc813987 100644 --- a/src/portfolio_context_recovery.py +++ b/src/github_repo_auditor/portfolio_context_recovery.py @@ -8,7 +8,7 @@ from pathlib import Path from typing import Any -from src.portfolio_context_contract import ( +from github_repo_auditor.portfolio_context_contract import ( MANAGED_CONTEXT_END, MANAGED_CONTEXT_START, friendly_missing_fields, @@ -16,11 +16,11 @@ temporary_project_reason, upsert_managed_context_block, ) -from src.portfolio_checkout_authority import ( +from github_repo_auditor.portfolio_checkout_authority import ( checkout_authority_blocker, checkout_authority_path, ) -from src.portfolio_truth_types import ( +from github_repo_auditor.portfolio_truth_types import ( PortfolioTruthProject, PortfolioTruthSnapshot, display_activity_status, diff --git a/src/portfolio_context_triage.py b/src/github_repo_auditor/portfolio_context_triage.py similarity index 98% rename from src/portfolio_context_triage.py rename to src/github_repo_auditor/portfolio_context_triage.py index af0fec8b..1f7429cb 100644 --- a/src/portfolio_context_triage.py +++ b/src/github_repo_auditor/portfolio_context_triage.py @@ -1,4 +1,4 @@ -# src/portfolio_context_triage.py +# src/github_repo_auditor/portfolio_context_triage.py """Context triage runner — B1 of Arc H operational stream.""" from __future__ import annotations diff --git a/src/portfolio_decision_queue.py b/src/github_repo_auditor/portfolio_decision_queue.py similarity index 99% rename from src/portfolio_decision_queue.py rename to src/github_repo_auditor/portfolio_decision_queue.py index 1334fecd..2ba9f852 100644 --- a/src/portfolio_decision_queue.py +++ b/src/github_repo_auditor/portfolio_decision_queue.py @@ -20,12 +20,12 @@ # Keep the documented absolute-path entrypoint portable. When Python executes # this file directly it puts ``src/`` on sys.path, which otherwise makes the -# package-qualified ``src.security_admission`` import impossible outside the +# package-qualified ``github_repo_auditor.security_admission`` import impossible outside the # repository root. Module and test imports already have the repository root. if __package__ in {None, ""}: sys.path.insert(0, str(Path(__file__).resolve().parents[1])) -from src.security_admission import derive_security_admission +from github_repo_auditor.security_admission import derive_security_admission CONTRACT_VERSION = "decision_queue_v2" DIGEST_CONTRACT_VERSION = "portfolio_decision_digest_v2" diff --git a/src/portfolio_intelligence.py b/src/github_repo_auditor/portfolio_intelligence.py similarity index 99% rename from src/portfolio_intelligence.py rename to src/github_repo_auditor/portfolio_intelligence.py index a07b1042..225df20c 100644 --- a/src/portfolio_intelligence.py +++ b/src/github_repo_auditor/portfolio_intelligence.py @@ -3,8 +3,8 @@ from collections import Counter, defaultdict from datetime import datetime, timezone -from src.models import AnalyzerResult, RepoAudit, RepoMetadata -from src.security_intelligence import build_security_governance_preview +from github_repo_auditor.models import AnalyzerResult, RepoAudit, RepoMetadata +from github_repo_auditor.security_intelligence import build_security_governance_preview REPORT_SCHEMA_VERSION = "3.7" diff --git a/src/portfolio_pathing.py b/src/github_repo_auditor/portfolio_pathing.py similarity index 100% rename from src/portfolio_pathing.py rename to src/github_repo_auditor/portfolio_pathing.py diff --git a/src/portfolio_readme.py b/src/github_repo_auditor/portfolio_readme.py similarity index 96% rename from src/portfolio_readme.py rename to src/github_repo_auditor/portfolio_readme.py index 780bcb5b..c4be5813 100644 --- a/src/portfolio_readme.py +++ b/src/github_repo_auditor/portfolio_readme.py @@ -3,7 +3,7 @@ from pathlib import Path -from src.badge_export import GRADE_COLORS, _static_badge_url +from github_repo_auditor.badge_export import GRADE_COLORS, _static_badge_url def _tier_repos(audits: list[dict], tier: str) -> list[dict]: @@ -47,7 +47,7 @@ def export_portfolio_readme(report_data: dict, output_dir: Path) -> dict: shipped_count = tiers.get("shipped", 0) audits = report_data.get("audits", []) - from src.scorer import letter_grade + from github_repo_auditor.scorer import letter_grade avg_grade = letter_grade(avg_score) lines = [ diff --git a/src/portfolio_repository_state.py b/src/github_repo_auditor/portfolio_repository_state.py similarity index 100% rename from src/portfolio_repository_state.py rename to src/github_repo_auditor/portfolio_repository_state.py diff --git a/src/portfolio_risk.py b/src/github_repo_auditor/portfolio_risk.py similarity index 100% rename from src/portfolio_risk.py rename to src/github_repo_auditor/portfolio_risk.py diff --git a/src/portfolio_security_gate.py b/src/github_repo_auditor/portfolio_security_gate.py similarity index 99% rename from src/portfolio_security_gate.py rename to src/github_repo_auditor/portfolio_security_gate.py index f1c2e59b..88cd6baf 100644 --- a/src/portfolio_security_gate.py +++ b/src/github_repo_auditor/portfolio_security_gate.py @@ -12,7 +12,7 @@ from datetime import datetime, timezone from typing import Any -from src.security_admission import derive_security_admission +from github_repo_auditor.security_admission import derive_security_admission @dataclass(frozen=True) diff --git a/src/portfolio_truth_contract_fixture.py b/src/github_repo_auditor/portfolio_truth_contract_fixture.py similarity index 96% rename from src/portfolio_truth_contract_fixture.py rename to src/github_repo_auditor/portfolio_truth_contract_fixture.py index dc903956..2f8043f6 100644 --- a/src/portfolio_truth_contract_fixture.py +++ b/src/github_repo_auditor/portfolio_truth_contract_fixture.py @@ -13,8 +13,8 @@ from datetime import datetime, timedelta, timezone from typing import Any -from src.demo_portfolio import DEMO_PROJECTS, DemoProject, build_snapshot -from src.portfolio_truth_types import SCHEMA_VERSION +from github_repo_auditor.demo_portfolio import DEMO_PROJECTS, DemoProject, build_snapshot +from github_repo_auditor.portfolio_truth_types import SCHEMA_VERSION CONTRACT_VERSION = "ghra-pcc-portfolio-truth.v1" PORTABLE_CONTRACT_VERSION = "ghra-portfolio-truth-portable.v1" @@ -125,7 +125,7 @@ def build_contract_manifest() -> dict[str, Any]: "producer": { "repository": PRODUCER_REPOSITORY, "generator": ( - "src.portfolio_truth_contract_fixture:build_contract_fixture" + "github_repo_auditor.portfolio_truth_contract_fixture:build_contract_fixture" ), "manifest_path": MANIFEST_RELATIVE_PATH, "artifact_path": FIXTURE_RELATIVE_PATH, @@ -252,7 +252,7 @@ def build_consumer_profile_manifest(profile_id: str) -> dict[str, Any]: "producer": { "repository": PRODUCER_REPOSITORY, "generator": ( - "src.portfolio_truth_contract_fixture:" + "github_repo_auditor.portfolio_truth_contract_fixture:" "build_portable_contract_fixture" ), "manifest_path": manifest_path, diff --git a/src/portfolio_truth_coverage.py b/src/github_repo_auditor/portfolio_truth_coverage.py similarity index 98% rename from src/portfolio_truth_coverage.py rename to src/github_repo_auditor/portfolio_truth_coverage.py index 6241b915..c466e4df 100644 --- a/src/portfolio_truth_coverage.py +++ b/src/github_repo_auditor/portfolio_truth_coverage.py @@ -5,7 +5,7 @@ from collections.abc import Mapping, Sequence from typing import Any -from src.portfolio_truth_types import PortfolioTruthProject +from github_repo_auditor.portfolio_truth_types import PortfolioTruthProject ProjectLike = PortfolioTruthProject | Mapping[str, Any] diff --git a/src/portfolio_truth_decisions.py b/src/github_repo_auditor/portfolio_truth_decisions.py similarity index 98% rename from src/portfolio_truth_decisions.py rename to src/github_repo_auditor/portfolio_truth_decisions.py index 91ae7d28..295efaf8 100644 --- a/src/portfolio_truth_decisions.py +++ b/src/github_repo_auditor/portfolio_truth_decisions.py @@ -4,7 +4,7 @@ from typing import Any -from src.portfolio_risk import build_risk_entry +from github_repo_auditor.portfolio_risk import build_risk_entry def derive_attention_state( diff --git a/src/portfolio_truth_lineage.py b/src/github_repo_auditor/portfolio_truth_lineage.py similarity index 100% rename from src/portfolio_truth_lineage.py rename to src/github_repo_auditor/portfolio_truth_lineage.py diff --git a/src/portfolio_truth_metadata.py b/src/github_repo_auditor/portfolio_truth_metadata.py similarity index 98% rename from src/portfolio_truth_metadata.py rename to src/github_repo_auditor/portfolio_truth_metadata.py index 06a6c88f..2bbc7322 100644 --- a/src/portfolio_truth_metadata.py +++ b/src/github_repo_auditor/portfolio_truth_metadata.py @@ -9,8 +9,8 @@ from pathlib import Path from typing import Any -from src.portfolio_truth_coverage import ProjectLike -from src.portfolio_truth_sources import ( +from github_repo_auditor.portfolio_truth_coverage import ProjectLike +from github_repo_auditor.portfolio_truth_sources import ( WORKSPACE_DISCOVERY_POLICY_VERSION, checkout_collision_summary, ) diff --git a/src/portfolio_truth_precedence.py b/src/github_repo_auditor/portfolio_truth_precedence.py similarity index 100% rename from src/portfolio_truth_precedence.py rename to src/github_repo_auditor/portfolio_truth_precedence.py diff --git a/src/portfolio_truth_provenance.py b/src/github_repo_auditor/portfolio_truth_provenance.py similarity index 100% rename from src/portfolio_truth_provenance.py rename to src/github_repo_auditor/portfolio_truth_provenance.py diff --git a/src/portfolio_truth_publish.py b/src/github_repo_auditor/portfolio_truth_publish.py similarity index 97% rename from src/portfolio_truth_publish.py rename to src/github_repo_auditor/portfolio_truth_publish.py index 0548364f..16f253b9 100644 --- a/src/portfolio_truth_publish.py +++ b/src/github_repo_auditor/portfolio_truth_publish.py @@ -12,29 +12,29 @@ from pathlib import Path from typing import Iterable, Iterator -from src.github_security_coverage import ( +from github_repo_auditor.github_security_coverage import ( DEFAULT_ATTENTION_STATES, SecurityCoverageError, SecurityCoverageReceiptBinding, derive_default_attention_cohort, verified_security_coverage_receipt_binding, ) -from src.portfolio_truth_reconcile import ( +from github_repo_auditor.portfolio_truth_reconcile import ( build_portfolio_truth_snapshot, load_prior_notion_context, ) -from src.portfolio_truth_render import render_portfolio_report_markdown, render_registry_markdown -from src.portfolio_truth_lineage import resolve_notion_origin -from src.portfolio_truth_types import truth_latest_path -from src.producer_preflight import ProducerEvidence, verify_evidence_still_current -from src.portfolio_truth_validate import ( +from github_repo_auditor.portfolio_truth_render import render_portfolio_report_markdown, render_registry_markdown +from github_repo_auditor.portfolio_truth_lineage import resolve_notion_origin +from github_repo_auditor.portfolio_truth_types import truth_latest_path +from github_repo_auditor.producer_preflight import ProducerEvidence, verify_evidence_still_current +from github_repo_auditor.portfolio_truth_validate import ( canonicalize_prior_security_truth_payload, validate_portfolio_report_markdown, validate_publish_targets, validate_registry_markdown, validate_truth_snapshot, ) -from src.project_registry import build_project_registry, load_source_paths +from github_repo_auditor.project_registry import build_project_registry, load_source_paths _PORTFOLIO_TRUTH_IN_PROCESS_LOCK = threading.Lock() @@ -252,7 +252,7 @@ def _portfolio_truth_publication_lock(latest_path: Path) -> Iterator[None]: os.close(descriptor) -_REPO_ROOT = Path(__file__).resolve().parents[1] +_REPO_ROOT = Path(__file__).resolve().parents[2] _CONFIG_DIR = _REPO_ROOT / "config" _PUBLISH_JOURNAL_NAME = ".portfolio-truth-publish-journal.json" @@ -269,8 +269,8 @@ def _build_project_registry_json(snapshot, *, include_notion: bool) -> str: scoring_pageids: dict[str, str] = {} if include_notion and sources.get("scoring_data_source_id"): try: - from src.notion_client import get_notion_token - from src.project_registry import fetch_scoring_pageids + from github_repo_auditor.notion_client import get_notion_token + from github_repo_auditor.project_registry import fetch_scoring_pageids token = get_notion_token() if token: diff --git a/src/portfolio_truth_reconcile.py b/src/github_repo_auditor/portfolio_truth_reconcile.py similarity index 98% rename from src/portfolio_truth_reconcile.py rename to src/github_repo_auditor/portfolio_truth_reconcile.py index 763b478f..e12c3ecb 100644 --- a/src/portfolio_truth_reconcile.py +++ b/src/github_repo_auditor/portfolio_truth_reconcile.py @@ -8,34 +8,34 @@ from pathlib import Path from typing import Any -from src.github_security_coverage import ( +from github_repo_auditor.github_security_coverage import ( DEFAULT_ATTENTION_STATES, SecurityCoverageError, derive_default_attention_cohort, ) -from src.portfolio_catalog import ( +from github_repo_auditor.portfolio_catalog import ( catalog_entry_for_repo, group_entry_for_path, load_portfolio_catalog, ) -from src.portfolio_context_contract import has_substantive_readme_support -from src.portfolio_pathing import build_operating_path_entry -from src.portfolio_repository_state import observe_repository_state -from src.portfolio_truth_coverage import build_coverage_envelope -from src.portfolio_truth_decisions import build_project_decision -from src.portfolio_truth_metadata import ( +from github_repo_auditor.portfolio_context_contract import has_substantive_readme_support +from github_repo_auditor.portfolio_pathing import build_operating_path_entry +from github_repo_auditor.portfolio_repository_state import observe_repository_state +from github_repo_auditor.portfolio_truth_coverage import build_coverage_envelope +from github_repo_auditor.portfolio_truth_decisions import build_project_decision +from github_repo_auditor.portfolio_truth_metadata import ( build_exclusions, build_input_envelope, build_source_summary, build_warnings, ) -from src.portfolio_truth_precedence import build_precedence_matrix -from src.portfolio_truth_sources import ( +from github_repo_auditor.portfolio_truth_precedence import build_precedence_matrix +from github_repo_auditor.portfolio_truth_sources import ( discover_workspace_projects, load_legacy_registry_rows, load_safe_notion_project_context, ) -from src.portfolio_truth_types import ( +from github_repo_auditor.portfolio_truth_types import ( DERIVATION_POLICY_VERSION, SCHEMA_VERSION, AdvisoryFields, @@ -48,13 +48,13 @@ SecurityFields, display_activity_status, ) -from src.project_registry import DEFAULT_SUPPLEMENTARY -from src.registry_parser import _normalize -from src.security_admission import derive_security_admission +from github_repo_auditor.project_registry import DEFAULT_SUPPLEMENTARY +from github_repo_auditor.registry_parser import _normalize +from github_repo_auditor.security_admission import derive_security_admission logger = logging.getLogger(__name__) -# ── Strict signal constants (mirror src/analyzers/testing.py and cicd.py) ── +# ── Strict signal constants (mirror src/github_repo_auditor/analyzers/testing.py and cicd.py) ── _TEST_DIRS = frozenset(("test", "tests", "__tests__", "spec", "test_suite")) _TEST_PATTERNS = ( "test_*.py", diff --git a/src/portfolio_truth_render.py b/src/github_repo_auditor/portfolio_truth_render.py similarity index 99% rename from src/portfolio_truth_render.py rename to src/github_repo_auditor/portfolio_truth_render.py index e65fac8f..e40c5f18 100644 --- a/src/portfolio_truth_render.py +++ b/src/github_repo_auditor/portfolio_truth_render.py @@ -4,12 +4,12 @@ from datetime import timezone from typing import Any -from src.portfolio_truth_types import ( +from github_repo_auditor.portfolio_truth_types import ( PortfolioTruthProject, PortfolioTruthSnapshot, display_activity_status, ) -from src.security_admission import derive_security_admission +from github_repo_auditor.security_admission import derive_security_admission def _displayed_status(project: PortfolioTruthProject) -> str: diff --git a/src/portfolio_truth_sources.py b/src/github_repo_auditor/portfolio_truth_sources.py similarity index 99% rename from src/portfolio_truth_sources.py rename to src/github_repo_auditor/portfolio_truth_sources.py index 909b1e5e..30375ab9 100644 --- a/src/portfolio_truth_sources.py +++ b/src/github_repo_auditor/portfolio_truth_sources.py @@ -10,14 +10,14 @@ from typing import Any from urllib.parse import urlparse -from src.notion_registry import load_notion_project_context -from src.portfolio_catalog import group_entry_for_path -from src.portfolio_context_contract import analyze_project_context -from src.portfolio_truth_types import ( +from github_repo_auditor.notion_registry import load_notion_project_context +from github_repo_auditor.portfolio_catalog import group_entry_for_path +from github_repo_auditor.portfolio_context_contract import analyze_project_context +from github_repo_auditor.portfolio_truth_types import ( CHECKOUT_COLLISION_SCHEMA_VERSION, CHECKOUT_COLLISION_SUMMARY_SCHEMA_VERSION, ) -from src.registry_parser import _normalize +from github_repo_auditor.registry_parser import _normalize MAX_CONTEXT_DEPTH = 2 MAX_CONTEXT_BYTES = 32_000 diff --git a/src/portfolio_truth_status.py b/src/github_repo_auditor/portfolio_truth_status.py similarity index 97% rename from src/portfolio_truth_status.py rename to src/github_repo_auditor/portfolio_truth_status.py index bdaf0ab6..2dc69aa8 100644 --- a/src/portfolio_truth_status.py +++ b/src/github_repo_auditor/portfolio_truth_status.py @@ -7,9 +7,9 @@ from datetime import datetime from pathlib import Path -from src.cache import ResponseCache -from src.github_client import GitHubClient -from src.github_security_coverage import ( +from github_repo_auditor.cache import ResponseCache +from github_repo_auditor.github_client import GitHubClient +from github_repo_auditor.github_security_coverage import ( DEFAULT_EXPECTED_GITHUB_COHORT_COUNT, GITHUB_SECURITY_RECEIPT_FILENAME, LoadedSecurityCoverage, diff --git a/src/portfolio_truth_trends.py b/src/github_repo_auditor/portfolio_truth_trends.py similarity index 100% rename from src/portfolio_truth_trends.py rename to src/github_repo_auditor/portfolio_truth_trends.py diff --git a/src/portfolio_truth_types.py b/src/github_repo_auditor/portfolio_truth_types.py similarity index 99% rename from src/portfolio_truth_types.py rename to src/github_repo_auditor/portfolio_truth_types.py index 4a96e95b..c5443ccb 100644 --- a/src/portfolio_truth_types.py +++ b/src/github_repo_auditor/portfolio_truth_types.py @@ -6,7 +6,7 @@ from pathlib import Path from typing import Any -from src.security_admission import ( +from github_repo_auditor.security_admission import ( SECURITY_ADMISSION_SCHEMA_VERSION, derive_security_admission, ) diff --git a/src/portfolio_truth_validate.py b/src/github_repo_auditor/portfolio_truth_validate.py similarity index 98% rename from src/portfolio_truth_validate.py rename to src/github_repo_auditor/portfolio_truth_validate.py index 5c2cd12f..cae5fcde 100644 --- a/src/portfolio_truth_validate.py +++ b/src/github_repo_auditor/portfolio_truth_validate.py @@ -9,7 +9,7 @@ from types import UnionType from typing import Any, Union, get_args, get_origin, get_type_hints -from src.github_security_coverage import ( +from github_repo_auditor.github_security_coverage import ( DEFAULT_COHORT_POLICY, GITHUB_SECURITY_RECEIPT_SCHEMA_VERSION, PROVIDER_NAMES, @@ -20,31 +20,31 @@ _validate_remote_repository, validate_normalized_security_provider, ) -from src.portfolio_checkout_authority import validate_checkout_authority_envelope -from src.portfolio_pathing import ( +from github_repo_auditor.portfolio_checkout_authority import validate_checkout_authority_envelope +from github_repo_auditor.portfolio_pathing import ( VALID_MATURITY_PROGRAMS, VALID_OPERATING_PATHS, VALID_PATH_CONFIDENCE, VALID_PATH_OVERRIDES, ) -from src.portfolio_repository_state import ( +from github_repo_auditor.portfolio_repository_state import ( _local_from_worktree, _select_remote_default_worktree, _selected_worktree, _tracks_nonmatching_branch, ) -from src.portfolio_truth_coverage import build_coverage_envelope -from src.portfolio_truth_decisions import build_project_decision -from src.portfolio_truth_metadata import ( +from github_repo_auditor.portfolio_truth_coverage import build_coverage_envelope +from github_repo_auditor.portfolio_truth_decisions import build_project_decision +from github_repo_auditor.portfolio_truth_metadata import ( build_exclusions, build_source_summary, build_warnings, ) -from src.portfolio_truth_precedence import PRECEDENCE_MATRIX -from src.portfolio_truth_provenance import REQUIRED_PROJECT_PROVENANCE_KEYS -from src.portfolio_truth_render import registry_project_labels -from src.portfolio_truth_sources import WORKSPACE_EXCLUSION_REASONS -from src.portfolio_truth_types import ( +from github_repo_auditor.portfolio_truth_precedence import PRECEDENCE_MATRIX +from github_repo_auditor.portfolio_truth_provenance import REQUIRED_PROJECT_PROVENANCE_KEYS +from github_repo_auditor.portfolio_truth_render import registry_project_labels +from github_repo_auditor.portfolio_truth_sources import WORKSPACE_EXCLUSION_REASONS +from github_repo_auditor.portfolio_truth_types import ( CHECKOUT_COLLISION_SUMMARY_SCHEMA_VERSION, DERIVATION_POLICY_VERSION, SCHEMA_VERSION, @@ -64,9 +64,9 @@ RiskFields, SecurityFields, ) -from src.producer_preflight import ProducerEvidence -from src.registry_parser import _normalize, parse_registry -from src.security_admission import derive_security_admission +from github_repo_auditor.producer_preflight import ProducerEvidence +from github_repo_auditor.registry_parser import _normalize, parse_registry +from github_repo_auditor.security_admission import derive_security_admission def validate_truth_snapshot( diff --git a/src/pr_head_evidence.py b/src/github_repo_auditor/pr_head_evidence.py similarity index 100% rename from src/pr_head_evidence.py rename to src/github_repo_auditor/pr_head_evidence.py diff --git a/src/producer_preflight.py b/src/github_repo_auditor/producer_preflight.py similarity index 100% rename from src/producer_preflight.py rename to src/github_repo_auditor/producer_preflight.py diff --git a/src/progress.py b/src/github_repo_auditor/progress.py similarity index 100% rename from src/progress.py rename to src/github_repo_auditor/progress.py diff --git a/src/project_registry.py b/src/github_repo_auditor/project_registry.py similarity index 99% rename from src/project_registry.py rename to src/github_repo_auditor/project_registry.py index c6696981..057dcd0e 100644 --- a/src/project_registry.py +++ b/src/github_repo_auditor/project_registry.py @@ -656,7 +656,7 @@ def fetch_scoring_pageids(data_source_id: str, token: str) -> dict[str, str]: Uses the auditor's Notion client; paginates the data source. Returns an empty dict on any failure so registry generation never hard-depends on it. """ - from src.notion_client import query_notion_collection + from github_repo_auditor.notion_client import query_notion_collection result: dict[str, str] = {} cursor: str | None = None diff --git a/src/quick_wins.py b/src/github_repo_auditor/quick_wins.py similarity index 97% rename from src/quick_wins.py rename to src/github_repo_auditor/quick_wins.py index e1cf22aa..8f101f45 100644 --- a/src/quick_wins.py +++ b/src/github_repo_auditor/quick_wins.py @@ -1,7 +1,7 @@ from __future__ import annotations -from src.models import RepoAudit -from src.scorer import COMPLETENESS_TIERS +from github_repo_auditor.models import RepoAudit +from github_repo_auditor.scorer import COMPLETENESS_TIERS # Tier thresholds in ascending order for "next tier" lookup _TIER_THRESHOLDS = list(reversed(COMPLETENESS_TIERS)) # abandoned -> shipped diff --git a/src/readme_suggestions.py b/src/github_repo_auditor/readme_suggestions.py similarity index 100% rename from src/readme_suggestions.py rename to src/github_repo_auditor/readme_suggestions.py diff --git a/src/recurring_review.py b/src/github_repo_auditor/recurring_review.py similarity index 98% rename from src/recurring_review.py rename to src/github_repo_auditor/recurring_review.py index 3103016d..39da59b9 100644 --- a/src/recurring_review.py +++ b/src/github_repo_auditor/recurring_review.py @@ -6,18 +6,18 @@ from datetime import datetime, timedelta, timezone from pathlib import Path -from src.analyst_views import build_analyst_context -from src.baseline_context import ( +from github_repo_auditor.analyst_views import build_analyst_context +from github_repo_auditor.baseline_context import ( build_filter_signature_from_args, build_requested_baseline_context, compare_baseline_context, extract_baseline_context, ) -from src.operator_acknowledgments import ( +from github_repo_auditor.operator_acknowledgments import ( is_change_acknowledged, load_acknowledgments, ) -from src.warehouse import ( +from github_repo_auditor.warehouse import ( load_latest_audit_runs, load_review_history, load_watch_checkpoint, diff --git a/src/registry_parser.py b/src/github_repo_auditor/registry_parser.py similarity index 99% rename from src/registry_parser.py rename to src/github_repo_auditor/registry_parser.py index 7ea6a49d..f7177bab 100644 --- a/src/registry_parser.py +++ b/src/github_repo_auditor/registry_parser.py @@ -4,7 +4,7 @@ from dataclasses import dataclass, field from pathlib import Path -from src.models import RepoAudit +from github_repo_auditor.models import RepoAudit VALID_STATUSES = {"active", "recent", "parked", "archived"} diff --git a/src/repo_improver.py b/src/github_repo_auditor/repo_improver.py similarity index 99% rename from src/repo_improver.py rename to src/github_repo_auditor/repo_improver.py index ca31087f..37c05c43 100644 --- a/src/repo_improver.py +++ b/src/github_repo_auditor/repo_improver.py @@ -13,7 +13,7 @@ from typing import TYPE_CHECKING, Any if TYPE_CHECKING: - from src.github_client import GitHubClient + from github_repo_auditor.github_client import GitHubClient logger = logging.getLogger(__name__) diff --git a/src/report_contracts.py b/src/github_repo_auditor/report_contracts.py similarity index 89% rename from src/report_contracts.py rename to src/github_repo_auditor/report_contracts.py index 2d7bbe5a..49283604 100644 --- a/src/report_contracts.py +++ b/src/github_repo_auditor/report_contracts.py @@ -3,8 +3,8 @@ These are the typed forms of the `weekly_story_v1` and risk-posture contracts documented in `docs/architecture.md`, adopted per the 2026-07-10 elegance review to put types at the enrichment boundary. They describe the shapes -produced by `src/weekly_packaging.py` (`_build_weekly_story_v1` and its -evidence-item helpers) and `src/report_enrichment.py` +produced by `src/github_repo_auditor/weekly_packaging.py` (`_build_weekly_story_v1` and its +evidence-item helpers) and `src/github_repo_auditor/report_enrichment.py` (`build_risk_lookup` / `_extract_risk_posture`); they are annotations only and introduce no runtime behavior. """ diff --git a/src/report_enrichment.py b/src/github_repo_auditor/report_enrichment.py similarity index 99% rename from src/report_enrichment.py rename to src/github_repo_auditor/report_enrichment.py index a8ab4681..dddd0d53 100644 --- a/src/report_enrichment.py +++ b/src/github_repo_auditor/report_enrichment.py @@ -5,12 +5,12 @@ from pathlib import Path from typing import Any -from src.portfolio_truth_types import truth_latest_path -from src.report_contracts import RiskLookupEntry, RiskPosture, TopElevatedEntry -from src.scoring_dimensions import display_dimension -from src.terminology import ACTION_SYNC_CANONICAL_LABELS -from src.weekly_packaging import finalize_weekly_pack -from src.weekly_scheduling_overlay import apply_weekly_scheduling_overlay +from github_repo_auditor.portfolio_truth_types import truth_latest_path +from github_repo_auditor.report_contracts import RiskLookupEntry, RiskPosture, TopElevatedEntry +from github_repo_auditor.scoring_dimensions import display_dimension +from github_repo_auditor.terminology import ACTION_SYNC_CANONICAL_LABELS +from github_repo_auditor.weekly_packaging import finalize_weekly_pack +from github_repo_auditor.weekly_scheduling_overlay import apply_weekly_scheduling_overlay COMPLETENESS_THRESHOLDS = [ ("shipped", 0.75), @@ -2141,7 +2141,7 @@ def build_scorecard_line(value: Any) -> str: def build_operating_path_line(value: Any) -> str: - from src.portfolio_pathing import ( + from github_repo_auditor.portfolio_pathing import ( build_operating_path_line as _build_operating_path_line, ) diff --git a/src/report_operating_paths.py b/src/github_repo_auditor/report_operating_paths.py similarity index 95% rename from src/report_operating_paths.py rename to src/github_repo_auditor/report_operating_paths.py index b18b9648..e0d8fe00 100644 --- a/src/report_operating_paths.py +++ b/src/github_repo_auditor/report_operating_paths.py @@ -2,8 +2,8 @@ from __future__ import annotations -from src.models import AuditReport -from src.portfolio_pathing import ( +from github_repo_auditor.models import AuditReport +from github_repo_auditor.portfolio_pathing import ( build_operating_path_entry, build_operating_path_line, build_operating_paths_summary, diff --git a/src/report_operator_state.py b/src/github_repo_auditor/report_operator_state.py similarity index 93% rename from src/report_operator_state.py rename to src/github_repo_auditor/report_operator_state.py index fd5e32d5..7610bf2c 100644 --- a/src/report_operator_state.py +++ b/src/github_repo_auditor/report_operator_state.py @@ -4,9 +4,9 @@ from pathlib import Path -from src.governance_activation import build_governance_summary -from src.models import AuditReport -from src.operator_control_center import build_operator_snapshot, normalize_review_state +from github_repo_auditor.governance_activation import build_governance_summary +from github_repo_auditor.models import AuditReport +from github_repo_auditor.operator_control_center import build_operator_snapshot, normalize_review_state def enrich_report_with_operator_state( diff --git a/src/report_portfolio_catalog.py b/src/github_repo_auditor/report_portfolio_catalog.py similarity index 94% rename from src/report_portfolio_catalog.py rename to src/github_repo_auditor/report_portfolio_catalog.py index 92e4546a..98433360 100644 --- a/src/report_portfolio_catalog.py +++ b/src/github_repo_auditor/report_portfolio_catalog.py @@ -5,8 +5,8 @@ from pathlib import Path from typing import Any -from src.models import AuditReport -from src.portfolio_catalog import ( +from github_repo_auditor.models import AuditReport +from github_repo_auditor.portfolio_catalog import ( DEFAULT_CATALOG_PATH, build_catalog_line, build_intent_alignment_summary, @@ -15,7 +15,7 @@ evaluate_intent_alignment, load_portfolio_catalog, ) -from src.report_enrichment import build_operator_focus +from github_repo_auditor.report_enrichment import build_operator_focus def apply_portfolio_catalog(report: AuditReport, args: Any) -> AuditReport: diff --git a/src/report_scorecards.py b/src/github_repo_auditor/report_scorecards.py similarity index 92% rename from src/report_scorecards.py rename to src/github_repo_auditor/report_scorecards.py index 3207a553..e615e6c0 100644 --- a/src/report_scorecards.py +++ b/src/github_repo_auditor/report_scorecards.py @@ -5,14 +5,14 @@ from pathlib import Path from typing import Any -from src.models import AuditReport -from src.portfolio_catalog import build_intent_alignment_summary, evaluate_intent_alignment -from src.report_enrichment import ( +from github_repo_auditor.models import AuditReport +from github_repo_auditor.portfolio_catalog import build_intent_alignment_summary, evaluate_intent_alignment +from github_repo_auditor.report_enrichment import ( build_maturity_gap_summary, build_operator_focus, build_scorecard_line, ) -from src.scorecards import ( +from github_repo_auditor.scorecards import ( DEFAULT_SCORECARDS_PATH, evaluate_scorecards_for_report, load_scorecards, diff --git a/src/report_shared_artifacts.py b/src/github_repo_auditor/report_shared_artifacts.py similarity index 78% rename from src/report_shared_artifacts.py rename to src/github_repo_auditor/report_shared_artifacts.py index 4e3cbb5a..c953af40 100644 --- a/src/report_shared_artifacts.py +++ b/src/github_repo_auditor/report_shared_artifacts.py @@ -5,21 +5,21 @@ from pathlib import Path from typing import Any -from src.excel_export import export_excel -from src.history import load_repo_score_history, load_trend_data -from src.models import AuditReport -from src.operator_approval_artifacts import write_approval_center_artifacts -from src.operator_control_center_artifacts import write_control_center_artifacts -from src.report_state import report_artifact_datetime -from src.reporter import ( +from github_repo_auditor.excel_export import export_excel +from github_repo_auditor.history import load_repo_score_history, load_trend_data +from github_repo_auditor.models import AuditReport +from github_repo_auditor.operator_approval_artifacts import write_approval_center_artifacts +from github_repo_auditor.operator_control_center_artifacts import write_control_center_artifacts +from github_repo_auditor.report_state import report_artifact_datetime +from github_repo_auditor.reporter import ( write_json_report, write_markdown_report, write_pcc_export, write_raw_metadata, ) -from src.review_pack import export_review_pack -from src.warehouse import write_warehouse_snapshot -from src.web_export import export_html_dashboard +from github_repo_auditor.review_pack import export_review_pack +from github_repo_auditor.warehouse import write_warehouse_snapshot +from github_repo_auditor.web_export import export_html_dashboard def refresh_shared_artifacts_from_report( diff --git a/src/report_state.py b/src/github_repo_auditor/report_state.py similarity index 98% rename from src/report_state.py rename to src/github_repo_auditor/report_state.py index fbb43e9f..1fdeab23 100644 --- a/src/report_state.py +++ b/src/github_repo_auditor/report_state.py @@ -6,8 +6,8 @@ from datetime import datetime, timezone from pathlib import Path -from src.models import AnalyzerResult, AuditReport, RepoAudit, RepoMetadata -from src.registry_parser import RegistryReconciliation +from github_repo_auditor.models import AnalyzerResult, AuditReport, RepoAudit, RepoMetadata +from github_repo_auditor.registry_parser import RegistryReconciliation def parse_iso_datetime(value: str | None) -> datetime | None: diff --git a/src/reporter.py b/src/github_repo_auditor/reporter.py similarity index 99% rename from src/reporter.py rename to src/github_repo_auditor/reporter.py index b96900dd..51f042dc 100644 --- a/src/reporter.py +++ b/src/github_repo_auditor/reporter.py @@ -5,8 +5,8 @@ from datetime import datetime from pathlib import Path -from src.models import AuditReport, RepoAudit -from src.report_enrichment import ( +from github_repo_auditor.models import AuditReport, RepoAudit +from github_repo_auditor.report_enrichment import ( build_follow_through_checkpoint, build_follow_through_checkpoint_status_label, build_follow_through_escalation_status_label, @@ -49,9 +49,9 @@ build_weekly_review_pack, no_linked_artifact_summary, ) -from src.scoring_dimensions import display_dimension -from src.terminology import ACTION_SYNC_CANONICAL_LABELS -from src.weekly_scheduling_overlay import resolve_weekly_story_value +from github_repo_auditor.scoring_dimensions import display_dimension +from github_repo_auditor.terminology import ACTION_SYNC_CANONICAL_LABELS +from github_repo_auditor.weekly_scheduling_overlay import resolve_weekly_story_value TIER_ORDER = ["shipped", "functional", "wip", "skeleton", "abandoned"] @@ -2105,7 +2105,7 @@ def write_markdown_report( _w("") # Quick Wins section - from src.quick_wins import find_quick_wins + from github_repo_auditor.quick_wins import find_quick_wins quick_wins = find_quick_wins(report.audits) if quick_wins: diff --git a/src/review_pack.py b/src/github_repo_auditor/review_pack.py similarity index 99% rename from src/review_pack.py rename to src/github_repo_auditor/review_pack.py index 4e43e836..94c2b500 100644 --- a/src/review_pack.py +++ b/src/github_repo_auditor/review_pack.py @@ -2,9 +2,9 @@ from pathlib import Path -from src.analyst_views import build_analyst_context -from src.report_enrichment import build_weekly_review_pack -from src.terminology import ACTION_SYNC_CANONICAL_LABELS +from github_repo_auditor.analyst_views import build_analyst_context +from github_repo_auditor.report_enrichment import build_weekly_review_pack +from github_repo_auditor.terminology import ACTION_SYNC_CANONICAL_LABELS def _render_weekly_story_sections(_w, weekly_pack: dict) -> None: diff --git a/src/run_instructions_audit.py b/src/github_repo_auditor/run_instructions_audit.py similarity index 96% rename from src/run_instructions_audit.py rename to src/github_repo_auditor/run_instructions_audit.py index 4384b957..52546d15 100644 --- a/src/run_instructions_audit.py +++ b/src/github_repo_auditor/run_instructions_audit.py @@ -14,15 +14,15 @@ from datetime import datetime from pathlib import Path -from src.portfolio_checkout_authority import ( +from github_repo_auditor.portfolio_checkout_authority import ( checkout_authority_blocker, checkout_authority_path, ) -from src.portfolio_context_contract import ( +from github_repo_auditor.portfolio_context_contract import ( analyze_project_context, choose_primary_context_file, ) -from src.portfolio_truth_sources import _collect_context_files +from github_repo_auditor.portfolio_truth_sources import _collect_context_files # The six presence claims the snapshot derives per repo, in logical reading order. # These names match both snapshot `derived.` and ContextAnalysis attributes. diff --git a/src/scheduled_handoff.py b/src/github_repo_auditor/scheduled_handoff.py similarity index 99% rename from src/scheduled_handoff.py rename to src/github_repo_auditor/scheduled_handoff.py index b0e1cc8d..b667523b 100644 --- a/src/scheduled_handoff.py +++ b/src/github_repo_auditor/scheduled_handoff.py @@ -4,8 +4,8 @@ import json from pathlib import Path -from src.report_enrichment import build_weekly_review_pack -from src.terminology import ACTION_SYNC_CANONICAL_LABELS +from github_repo_auditor.report_enrichment import build_weekly_review_pack +from github_repo_auditor.terminology import ACTION_SYNC_CANONICAL_LABELS ISSUE_LABEL = "scheduled-audit-handoff" diff --git a/src/scorecards.py b/src/github_repo_auditor/scorecards.py similarity index 100% rename from src/scorecards.py rename to src/github_repo_auditor/scorecards.py diff --git a/src/scorer.py b/src/github_repo_auditor/scorer.py similarity index 93% rename from src/scorer.py rename to src/github_repo_auditor/scorer.py index a3ca85cb..11a9d762 100644 --- a/src/scorer.py +++ b/src/github_repo_auditor/scorer.py @@ -4,15 +4,15 @@ from pathlib import Path from typing import TYPE_CHECKING, Any -from src.badges import compute_badges, suggest_next_badges -from src.models import AnalyzerResult, RepoAudit, RepoMetadata -from src.scoring_dimensions import ( +from github_repo_auditor.badges import compute_badges, suggest_next_badges +from github_repo_auditor.models import AnalyzerResult, RepoAudit, RepoMetadata +from github_repo_auditor.scoring_dimensions import ( UNSCORED_DIMENSIONS as UNSCORED_DIMENSIONS, display_dimension as display_dimension, ) if TYPE_CHECKING: - from src.github_client import GitHubClient + from github_repo_auditor.github_client import GitHubClient # Rebalanced completeness weights (sum = 1.0) WEIGHTS: dict[str, float] = { @@ -115,7 +115,7 @@ def score_repo( # Portfolio-relative novelty adjustment: reduce novelty for dominant languages if portfolio_lang_freq and metadata.language: - from src.analyzers.interest import NOVEL_LANGUAGES + from github_repo_auditor.analyzers.interest import NOVEL_LANGUAGES if metadata.language in NOVEL_LANGUAGES: freq = portfolio_lang_freq.get(metadata.language, 0.0) @@ -193,14 +193,14 @@ def score_repo( # Compute badges and suggestions audit.badges = compute_badges(audit) audit.next_badges = suggest_next_badges(audit) - from src.implementation_hotspots import build_implementation_hotspots - from src.portfolio_intelligence import ( + from github_repo_auditor.implementation_hotspots import build_implementation_hotspots + from github_repo_auditor.portfolio_intelligence import ( build_action_candidates, build_hotspots, compute_lens_scores, ) - from src.report_enrichment import build_score_explanation - from src.security_intelligence import build_security_posture + from github_repo_auditor.report_enrichment import build_score_explanation + from github_repo_auditor.security_intelligence import build_security_posture audit.security_posture = build_security_posture( metadata, diff --git a/src/scoring_dimensions.py b/src/github_repo_auditor/scoring_dimensions.py similarity index 100% rename from src/scoring_dimensions.py rename to src/github_repo_auditor/scoring_dimensions.py diff --git a/src/security_admission.py b/src/github_repo_auditor/security_admission.py similarity index 100% rename from src/security_admission.py rename to src/github_repo_auditor/security_admission.py diff --git a/src/security_burndown.py b/src/github_repo_auditor/security_burndown.py similarity index 100% rename from src/security_burndown.py rename to src/github_repo_auditor/security_burndown.py diff --git a/src/security_intelligence.py b/src/github_repo_auditor/security_intelligence.py similarity index 99% rename from src/security_intelligence.py rename to src/github_repo_auditor/security_intelligence.py index 04ba73b8..5cbe89b8 100644 --- a/src/security_intelligence.py +++ b/src/github_repo_auditor/security_intelligence.py @@ -4,10 +4,10 @@ import requests -from src.models import AnalyzerResult, RepoAudit, RepoMetadata +from github_repo_auditor.models import AnalyzerResult, RepoAudit, RepoMetadata if TYPE_CHECKING: - from src.github_client import GitHubClient + from github_repo_auditor.github_client import GitHubClient SCORECARD_API_BASE = "https://api.securityscorecards.dev/projects/" SCORECARD_CHECKS = { diff --git a/src/semantic_index.py b/src/github_repo_auditor/semantic_index.py similarity index 99% rename from src/semantic_index.py rename to src/github_repo_auditor/semantic_index.py index 4047f747..0f1a63d1 100644 --- a/src/semantic_index.py +++ b/src/github_repo_auditor/semantic_index.py @@ -24,7 +24,7 @@ from pathlib import Path from typing import Protocol, runtime_checkable -from src.models import RepoAudit +from github_repo_auditor.models import RepoAudit logger = logging.getLogger(__name__) diff --git a/src/serve/__init__.py b/src/github_repo_auditor/serve/__init__.py similarity index 100% rename from src/serve/__init__.py rename to src/github_repo_auditor/serve/__init__.py diff --git a/src/serve/api.py b/src/github_repo_auditor/serve/api.py similarity index 94% rename from src/serve/api.py rename to src/github_repo_auditor/serve/api.py index 0fb1776d..4a85f855 100644 --- a/src/serve/api.py +++ b/src/github_repo_auditor/serve/api.py @@ -3,11 +3,11 @@ Exposes ``GET /api/report/{username}`` returning :meth:`ApiOnlyReport.to_dict` JSON. This is the free "paste your GitHub username" report's backend: it lists a user's repos and scores them from the GitHub API alone (no cloning), via the -existing engine in :mod:`src.api_only`. +existing engine in :mod:`github_repo_auditor.api_only`. The route is defined as a plain ``def`` so FastAPI runs the blocking, network-bound scan in a worker thread rather than on the event loop. The -:class:`~src.github_client.GitHubClient` is supplied through a FastAPI +:class:`~github_repo_auditor.github_client.GitHubClient` is supplied through a FastAPI dependency so tests can override it and a deployment can inject a shared server-side token. """ @@ -21,11 +21,11 @@ from fastapi import APIRouter, Depends, HTTPException, Path, Request from pydantic import BaseModel, Field -from src.api_only import audit_user_api_only -from src.github_client import GitHubClient, GitHubClientError -from src.serve.hosting import RateLimiter, ReportCache -from src.serve.runner import validate_username -from src.serve.waitlist import WaitlistStore, is_valid_email +from github_repo_auditor.api_only import audit_user_api_only +from github_repo_auditor.github_client import GitHubClient, GitHubClientError +from github_repo_auditor.serve.hosting import RateLimiter, ReportCache +from github_repo_auditor.serve.runner import validate_username +from github_repo_auditor.serve.waitlist import WaitlistStore, is_valid_email router = APIRouter(prefix="/api", tags=["report"]) diff --git a/src/serve/app.py b/src/github_repo_auditor/serve/app.py similarity index 87% rename from src/serve/app.py rename to src/github_repo_auditor/serve/app.py index 7a60d533..2c2fb8fa 100644 --- a/src/serve/app.py +++ b/src/github_repo_auditor/serve/app.py @@ -11,15 +11,15 @@ def create_app(output_dir: Path | None = None) -> "FastAPI": # noqa: F821 from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles - from src.serve.api import cors_origins - from src.serve.api import router as api_router - from src.serve.hosting import ( + from github_repo_auditor.serve.api import cors_origins + from github_repo_auditor.serve.api import router as api_router + from github_repo_auditor.serve.hosting import ( build_kv_store, build_rate_limiter, build_report_cache, ) - from src.serve.routes import router - from src.serve.waitlist import build_waitlist_store + from github_repo_auditor.serve.routes import router + from github_repo_auditor.serve.waitlist import build_waitlist_store app = FastAPI( title="Audit Serve", diff --git a/src/serve/hosting.py b/src/github_repo_auditor/serve/hosting.py similarity index 98% rename from src/serve/hosting.py rename to src/github_repo_auditor/serve/hosting.py index ec8ffa1a..95d10d9b 100644 --- a/src/serve/hosting.py +++ b/src/github_repo_auditor/serve/hosting.py @@ -168,7 +168,7 @@ def build_kv_store() -> KVStore: url = os.environ.get(REDIS_URL_ENV_VAR, "").strip() if url: # Lazy import so the redis dependency is only needed when configured. - from src.serve.redis_store import RedisKVStore + from github_repo_auditor.serve.redis_store import RedisKVStore return RedisKVStore(url) return InMemoryKVStore() diff --git a/src/serve/redis_store.py b/src/github_repo_auditor/serve/redis_store.py similarity index 100% rename from src/serve/redis_store.py rename to src/github_repo_auditor/serve/redis_store.py diff --git a/src/serve/routes.py b/src/github_repo_auditor/serve/routes.py similarity index 94% rename from src/serve/routes.py rename to src/github_repo_auditor/serve/routes.py index 9cf4fe09..58e6df66 100644 --- a/src/serve/routes.py +++ b/src/github_repo_auditor/serve/routes.py @@ -14,9 +14,9 @@ from fastapi.responses import HTMLResponse, StreamingResponse from fastapi.templating import Jinja2Templates -from src.portfolio_truth_types import truth_latest_path -from src.scoring_dimensions import display_dimension -from src.serve.runner import SAFE_FLAG_NAMES, get_session, spawn_run, validate_flags +from github_repo_auditor.portfolio_truth_types import truth_latest_path +from github_repo_auditor.scoring_dimensions import display_dimension +from github_repo_auditor.serve.runner import SAFE_FLAG_NAMES, get_session, spawn_run, validate_flags router = APIRouter() @@ -263,7 +263,7 @@ async def approvals(request: Request) -> HTMLResponse: output_dir = _output_dir(request) records: list[dict[str, Any]] = [] try: - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records # username is inferred from output_dir contents — use empty string as sentinel records = load_approval_records(output_dir, username="") @@ -286,7 +286,7 @@ async def draft_diff(request: Request, record_id: str) -> HTMLResponse: output_dir = _output_dir(request) record: dict[str, Any] | None = None try: - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records all_records = load_approval_records(output_dir, username="") record = next( @@ -329,7 +329,7 @@ async def campaign_plan(request: Request, record_id: str) -> HTMLResponse: output_dir = _output_dir(request) record: dict[str, Any] | None = None try: - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records all_records = load_approval_records(output_dir, username="") record = next( @@ -445,8 +445,8 @@ def _render_action_row(packet_id: str, idx: int, action: dict[str, Any]) -> str: @router.post("/approvals/{packet_id}/actions/{idx}/approve", response_class=HTMLResponse) async def approve_campaign_action(request: Request, packet_id: str, idx: int) -> HTMLResponse: """Set action idx to approved; return updated row partial for HTMX swap.""" - from src.plan_campaign import approve_action as _approve_action - from src.warehouse import load_approval_records + from github_repo_auditor.plan_campaign import approve_action as _approve_action + from github_repo_auditor.warehouse import load_approval_records output_dir = _output_dir(request) try: @@ -482,8 +482,8 @@ async def reject_campaign_action( reason: str = Form(""), ) -> HTMLResponse: """Set action idx to rejected; return updated row partial for HTMX swap.""" - from src.plan_campaign import reject_action as _reject_action - from src.warehouse import load_approval_records + from github_repo_auditor.plan_campaign import reject_action as _reject_action + from github_repo_auditor.warehouse import load_approval_records output_dir = _output_dir(request) try: @@ -572,7 +572,7 @@ async def draft_sections(request: Request, packet_id: str) -> HTMLResponse: output_dir = _output_dir(request) sections: list[dict[str, Any]] = [] try: - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records all_records = load_approval_records(output_dir, username="") sections = sorted( @@ -615,8 +615,8 @@ async def draft_sections(request: Request, packet_id: str) -> HTMLResponse: @router.post("/approvals/sections/{record_id}/approve", response_class=HTMLResponse) async def approve_section_route(request: Request, record_id: str) -> HTMLResponse: """Set section record_id to approved; return updated section card for HTMX swap.""" - from src.draft_readmes import approve_section as _approve_section - from src.warehouse import load_approval_records + from github_repo_auditor.draft_readmes import approve_section as _approve_section + from github_repo_auditor.warehouse import load_approval_records output_dir = _output_dir(request) try: @@ -647,8 +647,8 @@ async def reject_section_route( reason: str = Form(""), ) -> HTMLResponse: """Set section record_id to rejected; return updated section card for HTMX swap.""" - from src.draft_readmes import reject_section as _reject_section - from src.warehouse import load_approval_records + from github_repo_auditor.draft_readmes import reject_section as _reject_section + from github_repo_auditor.warehouse import load_approval_records output_dir = _output_dir(request) try: @@ -677,8 +677,8 @@ async def initiatives(request: Request) -> HTMLResponse: """Render the open initiative tracker page.""" output_dir = _output_dir(request) - from src.initiatives import derive_status, initiatives_path, load_initiatives - from src.maturity_tiers import compute_tier, tier_gap, tier_name + from github_repo_auditor.initiatives import derive_status, initiatives_path, load_initiatives + from github_repo_auditor.maturity_tiers import compute_tier, tier_gap, tier_name inits = load_initiatives(initiatives_path(output_dir)) @@ -743,8 +743,8 @@ async def initiatives_suggestions( ) -> HTMLResponse: """Render LLM-ranked suggestions as a page with per-card Accept buttons. `target` query param overrides per-repo next-tier targeting.""" - from src.maturity_tiers import tier_name - from src.suggest_initiatives import default_deadline_for_effort, generate_suggestions + from github_repo_auditor.maturity_tiers import tier_name + from github_repo_auditor.suggest_initiatives import default_deadline_for_effort, generate_suggestions output_dir = _output_dir(request) truth_path = truth_latest_path(output_dir) @@ -817,7 +817,7 @@ async def accept_initiative_route( deadline: str = Form(...), ) -> HTMLResponse: """HTMX endpoint: convert suggestion into initiative. Returns updated card partial.""" - from src.suggest_initiatives import accept_suggestion + from github_repo_auditor.suggest_initiatives import accept_suggestion output_dir = _output_dir(request) truth_path = truth_latest_path(output_dir) @@ -868,7 +868,7 @@ async def dismiss_suggestion_route( reason: str = Form(""), ) -> HTMLResponse: """Dismiss a suggestion. Returns HTMX partial (Arc G S11.4).""" - from src.suggest_initiatives import dismiss_suggestion_record, dismissed_path + from github_repo_auditor.suggest_initiatives import dismiss_suggestion_record, dismissed_path output_dir = _output_dir(request) try: @@ -899,7 +899,7 @@ async def initiatives_dismissed(request: Request) -> HTMLResponse: """ from datetime import date as _date - from src.suggest_initiatives import dismissed_path, load_dismissed + from github_repo_auditor.suggest_initiatives import dismissed_path, load_dismissed output_dir = _output_dir(request) items = load_dismissed(dismissed_path(output_dir)) @@ -930,7 +930,7 @@ async def undo_dismiss_route( repo_name: str = Form(...), ) -> HTMLResponse: """Restore a dismissed repo. HTMX swap-out the row (Arc G S12.2).""" - from src.suggest_initiatives import dismissed_path, undo_dismiss + from github_repo_auditor.suggest_initiatives import dismissed_path, undo_dismiss output_dir = _output_dir(request) removed = undo_dismiss(dismissed_path(output_dir), repo_name) @@ -948,7 +948,7 @@ async def undo_dismiss_route( @router.get("/initiatives/dismissal-history", response_class=HTMLResponse) async def initiatives_dismissal_history(request: Request) -> HTMLResponse: """Show chronological audit trail of dismiss/undo/expire events (Arc G S13.1).""" - from src.suggest_initiatives import dismissed_path, load_dismissal_events + from github_repo_auditor.suggest_initiatives import dismissed_path, load_dismissal_events output_dir = _output_dir(request) events = load_dismissal_events(dismissed_path(output_dir)) @@ -979,13 +979,13 @@ async def initiative_gap(request: Request, repo_name: str, target: int = 0) -> H """Return an HTMX partial listing missing requirements for *repo_name* to reach *target* tier.""" output_dir = _output_dir(request) - from src.maturity_tiers import compute_tier, tier_gap, tier_name + from github_repo_auditor.maturity_tiers import compute_tier, tier_gap, tier_name # Resolve target: use query-string value if provided and valid (1-4), else 0 if target not in (1, 2, 3, 4): # Fall back to looking up the open initiative for this repo try: - from src.initiatives import initiatives_path, load_initiatives + from github_repo_auditor.initiatives import initiatives_path, load_initiatives inits = load_initiatives(initiatives_path(output_dir)) for init in inits: diff --git a/src/serve/runner.py b/src/github_repo_auditor/serve/runner.py similarity index 98% rename from src/serve/runner.py rename to src/github_repo_auditor/serve/runner.py index 9597cff2..d65f7e78 100644 --- a/src/serve/runner.py +++ b/src/github_repo_auditor/serve/runner.py @@ -145,7 +145,7 @@ def spawn_run(username: str, flags: dict[str, str | bool], output_dir: Path) -> safe_username = validate_username(username) flag_args = validate_flags(flags) run_id = uuid.uuid4().hex - cmd = [sys.executable, "-m", "src.cli", safe_username, *flag_args] + cmd = [sys.executable, "-m", "github_repo_auditor.cli", safe_username, *flag_args] session = RunSession(run_id=run_id, cmd=cmd) with _registry_lock: _registry[run_id] = session diff --git a/src/serve/static/audit.css b/src/github_repo_auditor/serve/static/audit.css similarity index 100% rename from src/serve/static/audit.css rename to src/github_repo_auditor/serve/static/audit.css diff --git a/src/serve/templates/approvals.html b/src/github_repo_auditor/serve/templates/approvals.html similarity index 100% rename from src/serve/templates/approvals.html rename to src/github_repo_auditor/serve/templates/approvals.html diff --git a/src/serve/templates/base.html b/src/github_repo_auditor/serve/templates/base.html similarity index 100% rename from src/serve/templates/base.html rename to src/github_repo_auditor/serve/templates/base.html diff --git a/src/serve/templates/campaign_plan.html b/src/github_repo_auditor/serve/templates/campaign_plan.html similarity index 100% rename from src/serve/templates/campaign_plan.html rename to src/github_repo_auditor/serve/templates/campaign_plan.html diff --git a/src/serve/templates/draft_diff.html b/src/github_repo_auditor/serve/templates/draft_diff.html similarity index 100% rename from src/serve/templates/draft_diff.html rename to src/github_repo_auditor/serve/templates/draft_diff.html diff --git a/src/serve/templates/draft_sections.html b/src/github_repo_auditor/serve/templates/draft_sections.html similarity index 100% rename from src/serve/templates/draft_sections.html rename to src/github_repo_auditor/serve/templates/draft_sections.html diff --git a/src/serve/templates/index.html b/src/github_repo_auditor/serve/templates/index.html similarity index 100% rename from src/serve/templates/index.html rename to src/github_repo_auditor/serve/templates/index.html diff --git a/src/serve/templates/initiative_gap.html b/src/github_repo_auditor/serve/templates/initiative_gap.html similarity index 100% rename from src/serve/templates/initiative_gap.html rename to src/github_repo_auditor/serve/templates/initiative_gap.html diff --git a/src/serve/templates/initiatives.html b/src/github_repo_auditor/serve/templates/initiatives.html similarity index 95% rename from src/serve/templates/initiatives.html rename to src/github_repo_auditor/serve/templates/initiatives.html index baa063ad..4b850c10 100644 --- a/src/serve/templates/initiatives.html +++ b/src/github_repo_auditor/serve/templates/initiatives.html @@ -35,7 +35,7 @@

Initiatives

{% if not rows %}

No open initiatives. Set one with:
- python3 -m src.cli --set-initiative <repo> --target-tier 3 --deadline 2026-12-31 + python3 -m github_repo_auditor.cli --set-initiative <repo> --target-tier 3 --deadline 2026-12-31

{% else %} diff --git a/src/serve/templates/initiatives_dismissal_history.html b/src/github_repo_auditor/serve/templates/initiatives_dismissal_history.html similarity index 100% rename from src/serve/templates/initiatives_dismissal_history.html rename to src/github_repo_auditor/serve/templates/initiatives_dismissal_history.html diff --git a/src/serve/templates/initiatives_dismissed.html b/src/github_repo_auditor/serve/templates/initiatives_dismissed.html similarity index 100% rename from src/serve/templates/initiatives_dismissed.html rename to src/github_repo_auditor/serve/templates/initiatives_dismissed.html diff --git a/src/serve/templates/initiatives_suggestions.html b/src/github_repo_auditor/serve/templates/initiatives_suggestions.html similarity index 100% rename from src/serve/templates/initiatives_suggestions.html rename to src/github_repo_auditor/serve/templates/initiatives_suggestions.html diff --git a/src/serve/templates/new_run.html b/src/github_repo_auditor/serve/templates/new_run.html similarity index 100% rename from src/serve/templates/new_run.html rename to src/github_repo_auditor/serve/templates/new_run.html diff --git a/src/serve/templates/repo.html b/src/github_repo_auditor/serve/templates/repo.html similarity index 100% rename from src/serve/templates/repo.html rename to src/github_repo_auditor/serve/templates/repo.html diff --git a/src/serve/templates/runs.html b/src/github_repo_auditor/serve/templates/runs.html similarity index 100% rename from src/serve/templates/runs.html rename to src/github_repo_auditor/serve/templates/runs.html diff --git a/src/serve/waitlist.py b/src/github_repo_auditor/serve/waitlist.py similarity index 100% rename from src/serve/waitlist.py rename to src/github_repo_auditor/serve/waitlist.py diff --git a/src/similarity.py b/src/github_repo_auditor/similarity.py similarity index 100% rename from src/similarity.py rename to src/github_repo_auditor/similarity.py diff --git a/src/sparkline.py b/src/github_repo_auditor/sparkline.py similarity index 100% rename from src/sparkline.py rename to src/github_repo_auditor/sparkline.py diff --git a/src/suggest_initiatives.py b/src/github_repo_auditor/suggest_initiatives.py similarity index 98% rename from src/suggest_initiatives.py rename to src/github_repo_auditor/suggest_initiatives.py index 5573f884..6ca41b4f 100644 --- a/src/suggest_initiatives.py +++ b/src/github_repo_auditor/suggest_initiatives.py @@ -19,7 +19,7 @@ from pathlib import Path from typing import Any -from src.narrative import _resolve_provider +from github_repo_auditor.narrative import _resolve_provider logger = logging.getLogger(__name__) @@ -198,7 +198,7 @@ class DismissedSuggestion: repo_name: str reason: str # operator-provided text, default "" dismissed_at: str # ISO timestamp - dismissed_by: str # operator_identity() from src.initiatives + dismissed_by: str # operator_identity() from github_repo_auditor.initiatives expires_at: str | None = None # ISO date string (YYYY-MM-DD) or None for permanent def to_dict(self) -> dict[str, Any]: @@ -396,7 +396,7 @@ def dismiss_suggestion_record( if not repo_name.strip(): raise ValueError("repo_name must not be blank") - from src.initiatives import operator_identity + from github_repo_auditor.initiatives import operator_identity now = datetime.now(timezone.utc) items = load_dismissed(path) @@ -434,7 +434,7 @@ def undo_dismiss(path: Path, repo_name: str) -> bool: Appends a DismissalEvent of type 'undone' when an entry is successfully removed. """ - from src.initiatives import operator_identity + from github_repo_auditor.initiatives import operator_identity items = load_dismissed(path) events = load_dismissal_events(path) @@ -573,7 +573,7 @@ def narrow_candidates( - If *dismissed* is provided, skip repos whose ``identity.display_name`` is in the set. Pass ``None`` (default) to disable filtering. """ - from src.maturity_tiers import TierGap, compute_tier, tier_gap + from github_repo_auditor.maturity_tiers import TierGap, compute_tier, tier_gap candidates: list[tuple[dict, int, TierGap]] = [] @@ -621,7 +621,7 @@ def build_suggest_prompt( 1-line status blurb). Asks the LLM to return a JSON array sorted by descending leverage, capped at 8 entries. """ - from src.maturity_tiers import tier_name + from github_repo_auditor.maturity_tiers import tier_name blurbs: list[str] = [] for repo, target, gap in candidates: @@ -828,8 +828,8 @@ def accept_suggestion( """ from datetime import datetime, timezone - from src.initiatives import Initiative, initiatives_path, operator_identity, upsert_initiative - from src.maturity_tiers import compute_tier + from github_repo_auditor.initiatives import Initiative, initiatives_path, operator_identity, upsert_initiative + from github_repo_auditor.maturity_tiers import compute_tier # 1. Find project project: dict | None = None @@ -949,7 +949,7 @@ def generate_suggestions( """ import os - from src.llm_cost import BudgetExceededError, CostTracker + from github_repo_auditor.llm_cost import BudgetExceededError, CostTracker # ── Lazy disk load (first call per output_dir) ──────────────────────────── if output_dir is not None and output_dir not in _loaded_from_disk: @@ -998,7 +998,7 @@ def generate_suggestions( if not candidates: return [], 0.0 - # Resolve LLM provider (module-level import so tests can patch src.suggest_initiatives._resolve_provider) + # Resolve LLM provider (module-level import so tests can patch github_repo_auditor.suggest_initiatives._resolve_provider) github_token = os.environ.get("GITHUB_TOKEN", "").strip() or None provider_result = _resolve_provider(None, None, github_token) diff --git a/src/terminology.py b/src/github_repo_auditor/terminology.py similarity index 100% rename from src/terminology.py rename to src/github_repo_auditor/terminology.py diff --git a/src/tier_recalibration.py b/src/github_repo_auditor/tier_recalibration.py similarity index 90% rename from src/tier_recalibration.py rename to src/github_repo_auditor/tier_recalibration.py index c9919080..344dfaa8 100644 --- a/src/tier_recalibration.py +++ b/src/github_repo_auditor/tier_recalibration.py @@ -1,10 +1,10 @@ -# src/tier_recalibration.py +# src/github_repo_auditor/tier_recalibration.py """Tier distribution report for maturity recalibration (Arc H A4).""" from __future__ import annotations from typing import Any -from src.maturity_tiers import compute_tier +from github_repo_auditor.maturity_tiers import compute_tier _TIER_NAMES = {1: "Bronze", 2: "Silver", 3: "Gold", 4: "Platinum"} _BUNCHING_THRESHOLD = 0.60 # flag if any single tier holds > 60% of repos diff --git a/src/vuln_check.py b/src/github_repo_auditor/vuln_check.py similarity index 98% rename from src/vuln_check.py rename to src/github_repo_auditor/vuln_check.py index 0ceb5955..2cb6182e 100644 --- a/src/vuln_check.py +++ b/src/github_repo_auditor/vuln_check.py @@ -6,7 +6,7 @@ import requests -from src.cache import ResponseCache +from github_repo_auditor.cache import ResponseCache OSV_BATCH_URL = "https://api.osv.dev/v1/querybatch" ECOSYSTEM_MAP = {"npm": "npm", "pypi": "PyPI", "crates": "crates.io"} diff --git a/src/warehouse.py b/src/github_repo_auditor/warehouse.py similarity index 99% rename from src/warehouse.py rename to src/github_repo_auditor/warehouse.py index e8ab32f8..8e29bd7f 100644 --- a/src/warehouse.py +++ b/src/github_repo_auditor/warehouse.py @@ -5,7 +5,7 @@ import sqlite3 from pathlib import Path -from src.models import AuditReport +from github_repo_auditor.models import AuditReport WAREHOUSE_FILENAME = "portfolio-warehouse.db" WAREHOUSE_SCHEMA_VERSION = 17 @@ -2130,7 +2130,7 @@ def load_latest_audit_runs(output_dir: Path, username: str, limit: int = 20) -> operator_summary = json.loads(row["operator_summary_json"] or "{}") decision_quality_summary = json.loads(row["decision_quality_summary_json"] or "{}") if not decision_quality_summary: - from src import operator_decision_quality as _operator_decision_quality + from github_repo_auditor import operator_decision_quality as _operator_decision_quality decision_quality_summary = ( _operator_decision_quality.historical_decision_quality_from_summary( @@ -2735,7 +2735,7 @@ def load_latest_operator_state(output_dir: Path, username: str) -> dict | None: operator_summary = json.loads(row["operator_summary_json"] or "{}") decision_quality_summary = json.loads(row["decision_quality_summary_json"] or "{}") if not decision_quality_summary: - from src import operator_decision_quality as _operator_decision_quality + from github_repo_auditor import operator_decision_quality as _operator_decision_quality decision_quality_summary = ( _operator_decision_quality.historical_decision_quality_from_summary(operator_summary) diff --git a/src/watch.py b/src/github_repo_auditor/watch.py similarity index 100% rename from src/watch.py rename to src/github_repo_auditor/watch.py diff --git a/src/web_export.py b/src/github_repo_auditor/web_export.py similarity index 99% rename from src/web_export.py rename to src/github_repo_auditor/web_export.py index 3cacce8c..d112f80a 100644 --- a/src/web_export.py +++ b/src/github_repo_auditor/web_export.py @@ -12,8 +12,8 @@ from pathlib import Path from urllib.parse import urlparse -from src.analyst_views import build_analyst_context -from src.report_enrichment import ( +from github_repo_auditor.analyst_views import build_analyst_context +from github_repo_auditor.report_enrichment import ( build_follow_through_checkpoint, build_follow_through_checkpoint_status_label, build_follow_through_escalation_status_label, @@ -58,10 +58,10 @@ no_baseline_summary, no_linked_artifact_summary, ) -from src.scorer import PARTIAL_RUN_BASIS_THRESHOLD, WEIGHTS -from src.sparkline import sparkline as render_sparkline -from src.terminology import ACTION_SYNC_CANONICAL_LABELS -from src.weekly_scheduling_overlay import resolve_weekly_story_value +from github_repo_auditor.scorer import PARTIAL_RUN_BASIS_THRESHOLD, WEIGHTS +from github_repo_auditor.sparkline import sparkline as render_sparkline +from github_repo_auditor.terminology import ACTION_SYNC_CANONICAL_LABELS +from github_repo_auditor.weekly_scheduling_overlay import resolve_weekly_story_value # ── Color constants (matching Excel design system) ────────────────── @@ -1339,7 +1339,7 @@ def _governance_section(report_data: dict) -> str: # ── HTML section builders ───────────────────────────────────────────── def _tech_radar_section(data: dict, trend_data: list[dict] | None) -> str: - from src.history import load_language_trends + from github_repo_auditor.history import load_language_trends trends = data.get("language_trends") if trends is None: diff --git a/src/weekly_command_center.py b/src/github_repo_auditor/weekly_command_center.py similarity index 98% rename from src/weekly_command_center.py rename to src/github_repo_auditor/weekly_command_center.py index 25337694..c4750ba1 100644 --- a/src/weekly_command_center.py +++ b/src/github_repo_auditor/weekly_command_center.py @@ -5,15 +5,15 @@ from pathlib import Path from typing import Any -from src.portfolio_automation import select_automation_candidates -from src.portfolio_decision_queue import build_decision_digest -from src.portfolio_truth_types import display_activity_status, truth_latest_path -from src.portfolio_truth_trends import ( +from github_repo_auditor.portfolio_automation import select_automation_candidates +from github_repo_auditor.portfolio_decision_queue import build_decision_digest +from github_repo_auditor.portfolio_truth_types import display_activity_status, truth_latest_path +from github_repo_auditor.portfolio_truth_trends import ( build_verdict_transition_ledger, render_movement_summary, ) -from src.report_enrichment import build_weekly_review_pack -from src.security_admission import derive_security_admission +from github_repo_auditor.report_enrichment import build_weekly_review_pack +from github_repo_auditor.security_admission import derive_security_admission CONTRACT_VERSION = "weekly_command_center_digest_v1" AUTHORITY_CAP = "bounded-automation" diff --git a/src/weekly_packaging.py b/src/github_repo_auditor/weekly_packaging.py similarity index 99% rename from src/weekly_packaging.py rename to src/github_repo_auditor/weekly_packaging.py index 51c8d04b..899c40e3 100644 --- a/src/weekly_packaging.py +++ b/src/github_repo_auditor/weekly_packaging.py @@ -2,12 +2,12 @@ from typing import Any -from src.report_contracts import ( +from github_repo_auditor.report_contracts import ( WeeklyStoryEvidenceItem, WeeklyStorySection, WeeklyStoryV1, ) -from src.terminology import ACTION_SYNC_CANONICAL_LABELS +from github_repo_auditor.terminology import ACTION_SYNC_CANONICAL_LABELS NO_FOLLOW_THROUGH_CHECKPOINT = ( "Use the next run or linked artifact to confirm whether the recommendation moved." diff --git a/src/weekly_scheduling_overlay.py b/src/github_repo_auditor/weekly_scheduling_overlay.py similarity index 100% rename from src/weekly_scheduling_overlay.py rename to src/github_repo_auditor/weekly_scheduling_overlay.py diff --git a/src/workbook_gate.py b/src/github_repo_auditor/workbook_gate.py similarity index 99% rename from src/workbook_gate.py rename to src/github_repo_auditor/workbook_gate.py index e6eb35d3..3238ae58 100644 --- a/src/workbook_gate.py +++ b/src/github_repo_auditor/workbook_gate.py @@ -8,9 +8,9 @@ from typing import Any from xml.etree import ElementTree as ET -from src.excel_export import export_excel -from src.excel_template import DEFAULT_TEMPLATE_PATH, load_workbook_allowing_native_sparklines -from src.excel_workbook_helpers import CORE_VISIBLE_SHEETS +from github_repo_auditor.excel_export import export_excel +from github_repo_auditor.excel_template import DEFAULT_TEMPLATE_PATH, load_workbook_allowing_native_sparklines +from github_repo_auditor.excel_workbook_helpers import CORE_VISIBLE_SHEETS DEFAULT_GATE_DIR = Path("output") / "workbook-gate" RESULT_FILENAME = "workbook-gate-result.json" diff --git a/tests/conftest.py b/tests/conftest.py index 8794a1f0..11d1d332 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,7 +5,7 @@ import pytest -from src.models import RepoMetadata +from github_repo_auditor.models import RepoMetadata @pytest.fixture diff --git a/tests/golden/enumerate_composer_contract.py b/tests/golden/enumerate_composer_contract.py index 47ddff3e..3c197a11 100644 --- a/tests/golden/enumerate_composer_contract.py +++ b/tests/golden/enumerate_composer_contract.py @@ -58,7 +58,7 @@ from pathlib import Path from typing import Any -import src.operator_trend_closure_forecast_reset_controls as m +import github_repo_auditor.operator_trend_closure_forecast_reset_controls as m REPO = Path(__file__).resolve().parents[2] GOLDEN_PATH = REPO / "tests" / "golden" / "composer_contract.golden.json" diff --git a/tests/golden/enumerate_recovery_state_contract.py b/tests/golden/enumerate_recovery_state_contract.py index a60c2a50..def86ac0 100644 --- a/tests/golden/enumerate_recovery_state_contract.py +++ b/tests/golden/enumerate_recovery_state_contract.py @@ -20,11 +20,11 @@ from pathlib import Path from types import ModuleType -import src.operator_resolution_trend as m_resolution -import src.operator_snapshot_packaging as m_pkg -import src.operator_trend_closure_forecast_reacquisition_controls as m_reacq -import src.operator_trend_closure_forecast_reset_controls as m_reset -import src.operator_trend_support as m_support # scanned since 2026-07-10: classifiers moved here by the callable-threading unwind +import github_repo_auditor.operator_resolution_trend as m_resolution +import github_repo_auditor.operator_snapshot_packaging as m_pkg +import github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls as m_reacq +import github_repo_auditor.operator_trend_closure_forecast_reset_controls as m_reset +import github_repo_auditor.operator_trend_support as m_support # scanned since 2026-07-10: classifiers moved here by the callable-threading unwind REPO = Path(__file__).resolve().parents[2] GOLDEN_PATH = REPO / "tests" / "golden" / "recovery_state_contract.golden.json" diff --git a/tests/golden/enumerate_resolution_trend_contract.py b/tests/golden/enumerate_resolution_trend_contract.py index 5b7b313e..48ed4836 100644 --- a/tests/golden/enumerate_resolution_trend_contract.py +++ b/tests/golden/enumerate_resolution_trend_contract.py @@ -30,7 +30,7 @@ from pathlib import Path from typing import Any -import src.operator_resolution_trend as m +import github_repo_auditor.operator_resolution_trend as m REPO = Path(__file__).resolve().parents[2] GOLDEN_PATH = REPO / "tests" / "golden" / "resolution_trend_contract.golden.json" diff --git a/tests/golden/recovery_state_contract.golden.json b/tests/golden/recovery_state_contract.golden.json index 06a3a625..b095ef7b 100644 --- a/tests/golden/recovery_state_contract.golden.json +++ b/tests/golden/recovery_state_contract.golden.json @@ -1,5 +1,5 @@ { - "src.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_reentry_restore_rerestore_side_from_persistence_status": { + "github_repo_auditor.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_reentry_restore_rerestore_side_from_persistence_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -270,7 +270,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_reentry_restore_rerestore_side_from_status": { + "github_repo_auditor.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_reentry_restore_rerestore_side_from_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -541,7 +541,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_reentry_side_from_persistence_status": { + "github_repo_auditor.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_reentry_side_from_persistence_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -812,7 +812,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_reentry_side_from_refresh_recovery_status": { + "github_repo_auditor.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_reentry_side_from_refresh_recovery_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -1083,7 +1083,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_reentry_side_from_restore_persistence_status": { + "github_repo_auditor.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_reentry_side_from_restore_persistence_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -1354,7 +1354,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_reentry_side_from_restore_status": { + "github_repo_auditor.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_reentry_side_from_restore_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -1625,7 +1625,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_side_from_reentry_status": { + "github_repo_auditor.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_side_from_reentry_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -1896,7 +1896,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_side_from_refresh_recovery_status": { + "github_repo_auditor.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_side_from_refresh_recovery_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -2167,7 +2167,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_resolution_trend._pending_transition_direction": { + "github_repo_auditor.operator_resolution_trend._pending_transition_direction": { "": "neutral", "act-now": "neutral", "act-with-review": "neutral", @@ -2438,7 +2438,7 @@ "watch": "neutral", "worsening": "neutral" }, - "src.operator_snapshot_packaging._confidence_validation_sentence": { + "github_repo_auditor.operator_snapshot_packaging._confidence_validation_sentence": { "": "The confidence model is still too lightly exercised to judge whether the current signal is earning trust.", "act-now": "The confidence model is still too lightly exercised to judge whether the current signal is earning trust.", "act-with-review": "The confidence model is still too lightly exercised to judge whether the current signal is earning trust.", @@ -2709,7 +2709,7 @@ "watch": "The confidence model is still too lightly exercised to judge whether the current signal is earning trust.", "worsening": "The confidence model is still too lightly exercised to judge whether the current signal is earning trust." }, - "src.operator_trend_closure_forecast_reacquisition_controls.closure_forecast_reacquisition_side_from_status": { + "github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls.closure_forecast_reacquisition_side_from_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -2980,7 +2980,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_trend_closure_forecast_reset_controls._persistence_status_from_rererestore_status": { + "github_repo_auditor.operator_trend_closure_forecast_reset_controls._persistence_status_from_rererestore_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -3251,7 +3251,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_trend_closure_forecast_reset_controls._persistence_status_to_rererestore_status": { + "github_repo_auditor.operator_trend_closure_forecast_reset_controls._persistence_status_to_rererestore_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -3522,7 +3522,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_trend_closure_forecast_reset_controls._refresh_status_to_rererestore_refresh_status": { + "github_repo_auditor.operator_trend_closure_forecast_reset_controls._refresh_status_to_rererestore_refresh_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -3793,7 +3793,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_trend_closure_forecast_reset_controls._rerererestore_text": { + "github_repo_auditor.operator_trend_closure_forecast_reset_controls._rerererestore_text": { "": "", "act-now": "act-now", "act-with-review": "act-with-review", @@ -4064,7 +4064,7 @@ "watch": "watch", "worsening": "worsening" }, - "src.operator_trend_closure_forecast_reset_controls._status_to_rererestore_status": { + "github_repo_auditor.operator_trend_closure_forecast_reset_controls._status_to_rererestore_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -4335,7 +4335,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_trend_closure_forecast_reset_controls.closure_forecast_reset_reentry_rebuild_reentry_restore_rerererestore_text": { + "github_repo_auditor.operator_trend_closure_forecast_reset_controls.closure_forecast_reset_reentry_rebuild_reentry_restore_rerererestore_text": { "": "", "act-now": "act-now", "act-with-review": "act-with-review", @@ -4606,7 +4606,7 @@ "watch": "watch", "worsening": "worsening" }, - "src.operator_trend_closure_forecast_reset_controls.closure_forecast_reset_side_from_status": { + "github_repo_auditor.operator_trend_closure_forecast_reset_controls.closure_forecast_reset_side_from_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -4877,7 +4877,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_trend_support.closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_side_from_persistence_status": { + "github_repo_auditor.operator_trend_support.closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_side_from_persistence_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -5148,7 +5148,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_trend_support.closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_side_from_status": { + "github_repo_auditor.operator_trend_support.closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_side_from_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -5419,7 +5419,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_trend_support.closure_forecast_reset_reentry_rebuild_reentry_restore_rerestore_side_from_refresh_recovery_status": { + "github_repo_auditor.operator_trend_support.closure_forecast_reset_reentry_rebuild_reentry_restore_rerestore_side_from_refresh_recovery_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -5690,7 +5690,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_trend_support.closure_forecast_reset_reentry_rebuild_side_from_persistence_status": { + "github_repo_auditor.operator_trend_support.closure_forecast_reset_reentry_rebuild_side_from_persistence_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -5961,7 +5961,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_trend_support.closure_forecast_reset_reentry_rebuild_side_from_recovery_status": { + "github_repo_auditor.operator_trend_support.closure_forecast_reset_reentry_rebuild_side_from_recovery_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -6232,7 +6232,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_trend_support.closure_forecast_reset_reentry_rebuild_side_from_status": { + "github_repo_auditor.operator_trend_support.closure_forecast_reset_reentry_rebuild_side_from_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -6503,7 +6503,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_trend_support.closure_forecast_reset_reentry_side_from_persistence_status": { + "github_repo_auditor.operator_trend_support.closure_forecast_reset_reentry_side_from_persistence_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -6774,7 +6774,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_trend_support.closure_forecast_reset_reentry_side_from_recovery_status": { + "github_repo_auditor.operator_trend_support.closure_forecast_reset_reentry_side_from_recovery_status": { "": "none", "act-now": "none", "act-with-review": "none", @@ -7045,7 +7045,7 @@ "watch": "none", "worsening": "none" }, - "src.operator_trend_support.closure_forecast_reset_reentry_side_from_status": { + "github_repo_auditor.operator_trend_support.closure_forecast_reset_reentry_side_from_status": { "": "none", "act-now": "none", "act-with-review": "none", diff --git a/tests/test_action_sync_automation.py b/tests/test_action_sync_automation.py index 5f1b7786..c499c24c 100644 --- a/tests/test_action_sync_automation.py +++ b/tests/test_action_sync_automation.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.action_sync_automation import build_action_sync_automation_bundle +from github_repo_auditor.action_sync_automation import build_action_sync_automation_bundle def _readiness_record(campaign_type: str, readiness_stage: str, *, action_count: int = 2) -> dict: diff --git a/tests/test_action_sync_outcomes.py b/tests/test_action_sync_outcomes.py index 7016e5b3..39c9652a 100644 --- a/tests/test_action_sync_outcomes.py +++ b/tests/test_action_sync_outcomes.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.action_sync_outcomes import build_action_sync_outcomes_bundle +from github_repo_auditor.action_sync_outcomes import build_action_sync_outcomes_bundle def _run_snapshot( diff --git a/tests/test_action_sync_packets.py b/tests/test_action_sync_packets.py index c6afbbb4..6c60a2e8 100644 --- a/tests/test_action_sync_packets.py +++ b/tests/test_action_sync_packets.py @@ -1,8 +1,8 @@ from __future__ import annotations -from src.action_sync_packets import build_action_sync_packets_bundle -from src.action_sync_readiness import build_action_sync_readiness_bundle -from src.ops_writeback import build_campaign_bundle +from github_repo_auditor.action_sync_packets import build_action_sync_packets_bundle +from github_repo_auditor.action_sync_readiness import build_action_sync_readiness_bundle +from github_repo_auditor.ops_writeback import build_campaign_bundle def _report_data(**overrides) -> dict: diff --git a/tests/test_action_sync_readiness.py b/tests/test_action_sync_readiness.py index 737f3744..b610f075 100644 --- a/tests/test_action_sync_readiness.py +++ b/tests/test_action_sync_readiness.py @@ -1,7 +1,7 @@ from __future__ import annotations -from src.action_sync_readiness import build_action_sync_readiness_bundle -from src.ops_writeback import build_campaign_bundle +from github_repo_auditor.action_sync_readiness import build_action_sync_readiness_bundle +from github_repo_auditor.ops_writeback import build_campaign_bundle def _report_data(**overrides) -> dict: diff --git a/tests/test_action_sync_tuning.py b/tests/test_action_sync_tuning.py index 91a27f3e..be539bd9 100644 --- a/tests/test_action_sync_tuning.py +++ b/tests/test_action_sync_tuning.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.action_sync_tuning import build_action_sync_tuning_bundle +from github_repo_auditor.action_sync_tuning import build_action_sync_tuning_bundle def _history_row( diff --git a/tests/test_activity.py b/tests/test_activity.py index cf6c7eef..60cb83bf 100644 --- a/tests/test_activity.py +++ b/tests/test_activity.py @@ -6,14 +6,14 @@ import pytest -from src.analyzers.activity import ( +from github_repo_auditor.analyzers.activity import ( ActivityAnalyzer, _classify_commit_pattern, _compute_bus_factor, _count_clusters, _recent_commit_count, ) -from src.models import RepoMetadata +from github_repo_auditor.models import RepoMetadata def _make_metadata(**kwargs) -> RepoMetadata: diff --git a/tests/test_analyzer_cache.py b/tests/test_analyzer_cache.py index 3d141469..0006ebce 100644 --- a/tests/test_analyzer_cache.py +++ b/tests/test_analyzer_cache.py @@ -1,4 +1,4 @@ -"""Tests for the per-(repo, sha, analyzer) cache in src/analyzer_cache.py.""" +"""Tests for the per-(repo, sha, analyzer) cache in src/github_repo_auditor/analyzer_cache.py.""" from __future__ import annotations @@ -7,12 +7,12 @@ from datetime import datetime, timezone from pathlib import Path -from src.analyzer_cache import _deep_equal, _diff_summary, invalidate_repo, lookup, reconcile, stats, store -from src.analyzers import ALL_ANALYZERS, run_all_analyzers, run_with_cache -from src.analyzers.dependencies import DependenciesAnalyzer -from src.analyzers.readme import ReadmeAnalyzer -from src.analyzers.structure import StructureAnalyzer -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.analyzer_cache import _deep_equal, _diff_summary, invalidate_repo, lookup, reconcile, stats, store +from github_repo_auditor.analyzers import ALL_ANALYZERS, run_all_analyzers, run_with_cache +from github_repo_auditor.analyzers.dependencies import DependenciesAnalyzer +from github_repo_auditor.analyzers.readme import ReadmeAnalyzer +from github_repo_auditor.analyzers.structure import StructureAnalyzer +from github_repo_auditor.models import AnalyzerResult, RepoMetadata # ── Helpers ──────────────────────────────────────────────────────────── @@ -20,7 +20,7 @@ def _fresh_db() -> sqlite3.Connection: """In-memory SQLite connection with the analyzer_cache table.""" conn = sqlite3.connect(":memory:") - from src.warehouse import _ensure_schema + from github_repo_auditor.warehouse import _ensure_schema _ensure_schema(conn) return conn @@ -240,7 +240,7 @@ def test_uses_pushed_at_as_sha_when_commit_map_is_missing(self, tmp_path: Path, def _fresh(path, metadata, conn=None): return [result] - monkeypatch.setattr("src.analyzers.ALL_ANALYZERS", [analyzer]) + monkeypatch.setattr("github_repo_auditor.analyzers.ALL_ANALYZERS", [analyzer]) report = reconcile({"repo": repo}, {"repo": meta}, conn, _fresh) assert report["checked"] == 1 @@ -282,7 +282,7 @@ def _fresh(path, metadata, conn=None): raise RuntimeError("boom") return [result] - monkeypatch.setattr("src.analyzers.ALL_ANALYZERS", [analyzer]) + monkeypatch.setattr("github_repo_auditor.analyzers.ALL_ANALYZERS", [analyzer]) report = reconcile( {"bad": bad_repo, "good": good_repo}, {"bad": bad_meta, "good": good_meta}, @@ -307,7 +307,7 @@ def test_cache_inputs_hash_exception_skips_analyzer_pair(self, tmp_path: Path, m def _fresh(path, metadata, conn=None): return [AnalyzerResult("edge", 0.5, 1.0, ["fresh"], {})] - monkeypatch.setattr("src.analyzers.ALL_ANALYZERS", [analyzer]) + monkeypatch.setattr("github_repo_auditor.analyzers.ALL_ANALYZERS", [analyzer]) report = reconcile( {"repo": repo}, {"repo": meta}, @@ -384,7 +384,7 @@ def test_ensure_schema_creates_table(self, tmp_path: Path): db_path = tmp_path / "fresh.db" conn = sqlite3.connect(str(db_path)) # _ensure_schema should create analyzer_cache even on a blank DB. - from src.warehouse import _ensure_schema + from github_repo_auditor.warehouse import _ensure_schema _ensure_schema(conn) # Confirm the table exists by querying it. diff --git a/tests/test_analyzer_staleness_and_releases.py b/tests/test_analyzer_staleness_and_releases.py index e89c248b..408d82fa 100644 --- a/tests/test_analyzer_staleness_and_releases.py +++ b/tests/test_analyzer_staleness_and_releases.py @@ -11,9 +11,9 @@ from pathlib import Path from unittest.mock import MagicMock -from src.analyzers.activity import ActivityAnalyzer -from src.analyzers.readme import ReadmeAnalyzer, _compute_readme_staleness -from src.models import RepoMetadata +from github_repo_auditor.analyzers.activity import ActivityAnalyzer +from github_repo_auditor.analyzers.readme import ReadmeAnalyzer, _compute_readme_staleness +from github_repo_auditor.models import RepoMetadata # --------------------------------------------------------------------------- # Helpers diff --git a/tests/test_analyzers.py b/tests/test_analyzers.py index 935cb7f5..2ca916e3 100644 --- a/tests/test_analyzers.py +++ b/tests/test_analyzers.py @@ -1,13 +1,13 @@ from __future__ import annotations -from src.analyzers.cicd import CicdAnalyzer -from src.analyzers.code_quality import CodeQualityAnalyzer -from src.analyzers.completeness import BuildReadinessAnalyzer, DocumentationAnalyzer -from src.analyzers.dependencies import DependenciesAnalyzer -from src.analyzers.readme import ReadmeAnalyzer -from src.analyzers.structure import StructureAnalyzer -from src.analyzers.testing import TestingAnalyzer -from src.models import RepoMetadata +from github_repo_auditor.analyzers.cicd import CicdAnalyzer +from github_repo_auditor.analyzers.code_quality import CodeQualityAnalyzer +from github_repo_auditor.analyzers.completeness import BuildReadinessAnalyzer, DocumentationAnalyzer +from github_repo_auditor.analyzers.dependencies import DependenciesAnalyzer +from github_repo_auditor.analyzers.readme import ReadmeAnalyzer +from github_repo_auditor.analyzers.structure import StructureAnalyzer +from github_repo_auditor.analyzers.testing import TestingAnalyzer +from github_repo_auditor.models import RepoMetadata class TestReadmeAnalyzer: @@ -176,7 +176,7 @@ def test_workspace_uses_unique_temp_dir(self): """clone_workspace must use TemporaryDirectory, not a hardcoded path.""" import inspect - from src.cloner import clone_workspace + from github_repo_auditor.cloner import clone_workspace src = inspect.getsource(clone_workspace) assert "TemporaryDirectory" in src assert "/tmp/audit-repos" not in src diff --git a/tests/test_api_checkout.py b/tests/test_api_checkout.py index 63e52f73..5df9b58c 100644 --- a/tests/test_api_checkout.py +++ b/tests/test_api_checkout.py @@ -3,8 +3,8 @@ from datetime import datetime, timezone from pathlib import Path -from src.api_checkout import materialize_api_checkout, materialize_api_workspace -from src.models import RepoMetadata +from github_repo_auditor.api_checkout import materialize_api_checkout, materialize_api_workspace +from github_repo_auditor.models import RepoMetadata def _meta(name: str = "demo", full_name: str = "octocat/demo") -> RepoMetadata: diff --git a/tests/test_api_only.py b/tests/test_api_only.py index 6f5816f4..dd1bb460 100644 --- a/tests/test_api_only.py +++ b/tests/test_api_only.py @@ -7,7 +7,7 @@ import pytest import requests -from src.api_only import ( +from github_repo_auditor.api_only import ( API_ONLY_MODE, ApiOnlyReport, _list_user_repos, @@ -15,7 +15,7 @@ audit_user_api_only, score_repos_api_only, ) -from src.models import RepoMetadata +from github_repo_auditor.models import RepoMetadata def _meta_ranked( @@ -73,7 +73,7 @@ def list_repos(self, username: str) -> list[dict]: def test_list_user_repos_prefers_graphql_with_token() -> None: client = _RepoListClient(token="t", rest_repos=[{"name": "rest"}]) gql = [{"name": "graphql"}] - with patch("src.api_only.bulk_fetch_repos", return_value=gql) as mock_gql: + with patch("github_repo_auditor.api_only.bulk_fetch_repos", return_value=gql) as mock_gql: result = _list_user_repos("octocat", client) # type: ignore[arg-type] assert result == gql assert client.list_repos_calls == 0 @@ -82,7 +82,7 @@ def test_list_user_repos_prefers_graphql_with_token() -> None: def test_list_user_repos_uses_rest_without_token() -> None: client = _RepoListClient(token=None, rest_repos=[{"name": "rest"}]) - with patch("src.api_only.bulk_fetch_repos") as mock_gql: + with patch("github_repo_auditor.api_only.bulk_fetch_repos") as mock_gql: result = _list_user_repos("octocat", client) # type: ignore[arg-type] assert result == [{"name": "rest"}] assert client.list_repos_calls == 1 @@ -92,7 +92,7 @@ def test_list_user_repos_uses_rest_without_token() -> None: def test_list_user_repos_falls_back_when_graphql_user_null() -> None: # GraphQL returns user: null → mapping raises TypeError → fall back to REST. client = _RepoListClient(token="t", rest_repos=[{"name": "rest"}]) - with patch("src.api_only.bulk_fetch_repos", side_effect=TypeError("user is None")): + with patch("github_repo_auditor.api_only.bulk_fetch_repos", side_effect=TypeError("user is None")): result = _list_user_repos("ghost", client) # type: ignore[arg-type] assert result == [{"name": "rest"}] assert client.list_repos_calls == 1 @@ -101,7 +101,7 @@ def test_list_user_repos_falls_back_when_graphql_user_null() -> None: def test_list_user_repos_falls_back_on_graphql_http_error() -> None: client = _RepoListClient(token="t", rest_repos=[{"name": "rest"}]) with patch( - "src.api_only.bulk_fetch_repos", + "github_repo_auditor.api_only.bulk_fetch_repos", side_effect=requests.ConnectionError("boom"), ): result = _list_user_repos("octocat", client) # type: ignore[arg-type] diff --git a/tests/test_app_cli_boundary.py b/tests/test_app_cli_boundary.py index da2efcfd..24476dc9 100644 --- a/tests/test_app_cli_boundary.py +++ b/tests/test_app_cli_boundary.py @@ -6,7 +6,7 @@ from pathlib import Path -APP_ROOT = Path(__file__).parent.parent / "src" / "app" +APP_ROOT = Path(__file__).parent.parent / "src" / "github_repo_auditor" / "app" def _cli_imports(path: Path) -> list[str]: @@ -14,12 +14,14 @@ def _cli_imports(path: Path) -> list[str]: violations: list[str] = [] for node in ast.walk(tree): if isinstance(node, ast.Import): - violations.extend(alias.name for alias in node.names if alias.name == "src.cli") + violations.extend(alias.name for alias in node.names if alias.name == "github_repo_auditor.cli") elif isinstance(node, ast.ImportFrom): - if node.module == "src.cli": - violations.append("from src.cli") - elif node.module == "src" and any(alias.name == "cli" for alias in node.names): - violations.append("from src import cli") + if node.module == "github_repo_auditor.cli": + violations.append("from github_repo_auditor.cli") + elif node.module == "github_repo_auditor" and any( + alias.name == "cli" for alias in node.names + ): + violations.append("from github_repo_auditor import cli") return violations diff --git a/tests/test_approval_ledger.py b/tests/test_approval_ledger.py index cb2e6a3a..19aa05e7 100644 --- a/tests/test_approval_ledger.py +++ b/tests/test_approval_ledger.py @@ -2,12 +2,12 @@ from pathlib import Path -from src.approval_ledger import ( +from github_repo_auditor.approval_ledger import ( build_approval_followup_record, build_approval_record, load_approval_ledger_bundle, ) -from src.warehouse import ( +from github_repo_auditor.warehouse import ( load_approval_followup_events, load_approval_records, save_approval_followup_event, diff --git a/tests/test_arc_f_surfaces.py b/tests/test_arc_f_surfaces.py index b9061d05..de29ef69 100644 --- a/tests/test_arc_f_surfaces.py +++ b/tests/test_arc_f_surfaces.py @@ -13,11 +13,11 @@ from openpyxl import Workbook -from src.excel_all_repos_helpers import ALL_REPOS_HEADERS, build_all_repo_rows -from src.excel_export import _build_all_repos, _build_security -from src.excel_repo_data_helpers import _arc_f_detail_cols, repo_detail_rows -from src.excel_security_sheet_helpers import SECURITY_HEADERS, build_security_sheet_rows -from src.operator_control_center import build_operator_snapshot +from github_repo_auditor.excel_all_repos_helpers import ALL_REPOS_HEADERS, build_all_repo_rows +from github_repo_auditor.excel_export import _build_all_repos, _build_security +from github_repo_auditor.excel_repo_data_helpers import _arc_f_detail_cols, repo_detail_rows +from github_repo_auditor.excel_security_sheet_helpers import SECURITY_HEADERS, build_security_sheet_rows +from github_repo_auditor.operator_control_center import build_operator_snapshot # ───────────────────────────────────────────────────────────── # Shared fixture helpers diff --git a/tests/test_archive.py b/tests/test_archive.py index 9e536691..1c8bb060 100644 --- a/tests/test_archive.py +++ b/tests/test_archive.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.archive_candidates import export_archive_report, find_archive_candidates +from github_repo_auditor.archive_candidates import export_archive_report, find_archive_candidates class TestFindArchiveCandidates: diff --git a/tests/test_auto_apply.py b/tests/test_auto_apply.py index cde1d1b2..fe10a0a9 100644 --- a/tests/test_auto_apply.py +++ b/tests/test_auto_apply.py @@ -2,7 +2,7 @@ from __future__ import annotations -from src.auto_apply import ( +from github_repo_auditor.auto_apply import ( SAFE_MUTATION_TARGETS, build_trust_bar_index, filter_safe_actions, diff --git a/tests/test_automation_executor.py b/tests/test_automation_executor.py index fda6e2f3..651e9d83 100644 --- a/tests/test_automation_executor.py +++ b/tests/test_automation_executor.py @@ -12,14 +12,14 @@ import pytest -from src.automation_executor import ( +from github_repo_auditor.automation_executor import ( AutomationExecutionError, CommandResult, ExecutionPlan, execute_catalog_seed, execute_context_pr, ) -from src.automation_proposals import ( +from github_repo_auditor.automation_proposals import ( ACTION_CATALOG_SEED, ACTION_CONTEXT_PR, STATUS_APPROVED, diff --git a/tests/test_automation_proposals.py b/tests/test_automation_proposals.py index 71f70c89..1a5c0f42 100644 --- a/tests/test_automation_proposals.py +++ b/tests/test_automation_proposals.py @@ -12,7 +12,7 @@ import pytest -from src.automation_proposals import ( +from github_repo_auditor.automation_proposals import ( ACTION_CATALOG_SEED, ACTION_CONTEXT_PR, STATUS_APPROVED, @@ -32,7 +32,7 @@ require_approved, save_proposals, ) -from src.portfolio_automation import AutomationCandidate +from github_repo_auditor.portfolio_automation import AutomationCandidate NOW = "2026-04-14T12:00:00+00:00" LATER = "2026-04-15T09:00:00+00:00" diff --git a/tests/test_automation_workflow.py b/tests/test_automation_workflow.py index c2f1bee6..00b79149 100644 --- a/tests/test_automation_workflow.py +++ b/tests/test_automation_workflow.py @@ -18,8 +18,8 @@ import pytest -from src.automation_executor import AutomationExecutionError, CommandResult -from src.automation_proposals import ( +from github_repo_auditor.automation_executor import AutomationExecutionError, CommandResult +from github_repo_auditor.automation_proposals import ( ACTION_CATALOG_SEED, ACTION_CONTEXT_PR, STATUS_APPROVED, @@ -30,13 +30,13 @@ load_proposals, save_proposals, ) -from src.automation_workflow import ( +from github_repo_auditor.automation_workflow import ( build_catalog_seeds_for, build_context_pr_plan, execute_approved_proposals, ) -from src.portfolio_context_contract import MANAGED_CONTEXT_START -from src.portfolio_truth_types import ( +from github_repo_auditor.portfolio_context_contract import MANAGED_CONTEXT_START +from github_repo_auditor.portfolio_truth_types import ( DeclaredFields, DerivedFields, IdentityFields, diff --git a/tests/test_badge_export.py b/tests/test_badge_export.py index 489b37c3..16e87d81 100644 --- a/tests/test_badge_export.py +++ b/tests/test_badge_export.py @@ -3,7 +3,7 @@ import json from urllib.parse import parse_qs, urlparse -from src.badge_export import ( +from github_repo_auditor.badge_export import ( _endpoint_badge_url, _load_gist_id, _make_shield_json, diff --git a/tests/test_badges.py b/tests/test_badges.py index 189a6ae8..1c70b5d5 100644 --- a/tests/test_badges.py +++ b/tests/test_badges.py @@ -2,8 +2,8 @@ from datetime import datetime, timezone -from src.badges import compute_badges, suggest_next_badges -from src.models import AnalyzerResult, RepoAudit, RepoMetadata +from github_repo_auditor.badges import compute_badges, suggest_next_badges +from github_repo_auditor.models import AnalyzerResult, RepoAudit, RepoMetadata def _meta(**overrides) -> RepoMetadata: diff --git a/tests/test_briefing.py b/tests/test_briefing.py index 6c5310cf..90aa4497 100644 --- a/tests/test_briefing.py +++ b/tests/test_briefing.py @@ -7,7 +7,7 @@ import pytest -from src.briefing import ( +from github_repo_auditor.briefing import ( Briefing, InitiativeSuggestionRow, NeedsAttentionRepo, @@ -153,7 +153,7 @@ def test_empty_audits_produces_valid_briefing(self): class TestNoWarehouseHistory: def test_health_delta_graceful_without_history_file(self): audits = [_make_audit("Repo")] - with patch("src.briefing._build_health_delta") as mock_delta: + with patch("github_repo_auditor.briefing._build_health_delta") as mock_delta: mock_delta.return_value = {"up": [], "down": []} briefing = build_briefing(audits, "user", "2026-05-11", use_history=False) assert briefing.health_delta == {"up": [], "down": []} @@ -165,7 +165,7 @@ def test_build_health_delta_returns_empty_when_use_history_false(self): def test_health_delta_graceful_on_load_error(self): audits = [_make_audit("Repo")] - with patch("src.history.load_repo_score_history", side_effect=OSError("no file")): + with patch("github_repo_auditor.history.load_repo_score_history", side_effect=OSError("no file")): result = _build_health_delta(audits, use_history=True) assert result == {"up": [], "down": []} @@ -353,7 +353,7 @@ def test_provider_returns_non_json_no_crash(self): class TestCLIIntegration: def test_briefing_writes_markdown_file(self, tmp_path): report = _make_report([_make_audit("Repo1", pushed_days_ago=2)]) - with patch("src.briefing._resolve_provider", return_value=None): + with patch("github_repo_auditor.briefing._resolve_provider", return_value=None): result = generate_briefing(report, tmp_path, write_voice=False) assert "briefing_path" in result md_path = result["briefing_path"] @@ -362,7 +362,7 @@ def test_briefing_writes_markdown_file(self, tmp_path): def test_briefing_voice_flag_writes_both_files(self, tmp_path): report = _make_report([_make_audit("Repo1", pushed_days_ago=2)]) - with patch("src.briefing._resolve_provider", return_value=None): + with patch("github_repo_auditor.briefing._resolve_provider", return_value=None): result = generate_briefing(report, tmp_path, write_voice=True) assert "briefing_path" in result assert "voice_path" in result @@ -372,13 +372,13 @@ def test_briefing_voice_flag_writes_both_files(self, tmp_path): def test_briefing_uses_output_dir_from_report(self, tmp_path): report = _make_report() subdir = tmp_path / "output" - with patch("src.briefing._resolve_provider", return_value=None): + with patch("github_repo_auditor.briefing._resolve_provider", return_value=None): result = generate_briefing(report, subdir, write_voice=False) assert result["briefing_path"].parent == subdir def test_mutually_exclusive_narrative_and_briefing(self): """argparse rejects --narrative together with --briefing.""" - from src.cli import build_parser + from github_repo_auditor.cli import build_parser parser = build_parser() with pytest.raises(SystemExit): @@ -397,7 +397,7 @@ def __init__(self, neighbor_map: dict[str, list]) -> None: self._neighbor_map = neighbor_map def find_neighbors(self, repo_name: str, k: int = 3): - from src.semantic_index import SearchResult + from github_repo_auditor.semantic_index import SearchResult return [ SearchResult(repo_name=n, score=0.1, snippet=f"repo: {n}") @@ -503,7 +503,7 @@ def _past(self, days: int = 5) -> str: return (date.today() - timedelta(days=days)).isoformat() def _write_initiatives(self, tmp_path, initiatives): - from src.initiatives import initiatives_path, save_initiatives + from github_repo_auditor.initiatives import initiatives_path, save_initiatives save_initiatives(initiatives_path(tmp_path), initiatives) @@ -514,7 +514,7 @@ def _make_initiative( deadline: str | None = None, closed_at: str | None = None, ): - from src.initiatives import Initiative + from github_repo_auditor.initiatives import Initiative return Initiative( repo_name=repo_name, @@ -691,7 +691,7 @@ def test_render_markdown_omits_suggestions_section_when_empty(self): def test_build_briefing_with_include_suggestions_populates_field(self): """build_briefing(include_suggestions=True) with a mock provider populates the field.""" - from src.suggest_initiatives import InitiativeSuggestion + from github_repo_auditor.suggest_initiatives import InitiativeSuggestion fake_suggestion = InitiativeSuggestion( repo_name="Wavelength", @@ -703,7 +703,7 @@ def test_build_briefing_with_include_suggestions_populates_field(self): ) with patch( - "src.suggest_initiatives.generate_suggestions", + "github_repo_auditor.suggest_initiatives.generate_suggestions", return_value=([fake_suggestion], 0.001), ): audits = [_make_audit("Wavelength")] @@ -726,7 +726,7 @@ def _write_dismissed_json(tmp_path, entries: list[dict]) -> None: """Write dismissed-suggestions.json to tmp_path using the canonical versioned format.""" import json - from src.suggest_initiatives import dismissed_path + from github_repo_auditor.suggest_initiatives import dismissed_path payload = {"version": 1, "items": entries} dismissed_path(tmp_path).write_text(json.dumps(payload), encoding="utf-8") @@ -736,18 +736,18 @@ class TestBuildDismissedRepos: """Unit tests for _build_dismissed_repos helper.""" def test_none_output_dir_returns_empty(self): - from src.briefing import _build_dismissed_repos + from github_repo_auditor.briefing import _build_dismissed_repos assert _build_dismissed_repos(None) == [] def test_missing_file_returns_empty(self, tmp_path): - from src.briefing import _build_dismissed_repos + from github_repo_auditor.briefing import _build_dismissed_repos result = _build_dismissed_repos(tmp_path) assert result == [] def test_valid_dismissed_file_returns_rows(self, tmp_path): - from src.briefing import DismissedRepoRow, _build_dismissed_repos + from github_repo_auditor.briefing import DismissedRepoRow, _build_dismissed_repos _write_dismissed_json( tmp_path, @@ -769,7 +769,7 @@ def test_valid_dismissed_file_returns_rows(self, tmp_path): def test_skips_expired_entries(self, tmp_path): from datetime import date - from src.briefing import _build_dismissed_repos + from github_repo_auditor.briefing import _build_dismissed_repos # expires_at is yesterday → should be filtered out _write_dismissed_json( @@ -790,7 +790,7 @@ def test_skips_expired_entries(self, tmp_path): def test_keeps_entries_expiring_today_or_later(self, tmp_path): from datetime import date - from src.briefing import _build_dismissed_repos + from github_repo_auditor.briefing import _build_dismissed_repos _write_dismissed_json( tmp_path, @@ -819,7 +819,7 @@ def test_keeps_entries_expiring_today_or_later(self, tmp_path): def test_keeps_entries_with_malformed_expiry(self, tmp_path): """Malformed expires_at is preserved defensively (not filtered).""" - from src.briefing import DismissedRepoRow, _build_dismissed_repos + from github_repo_auditor.briefing import DismissedRepoRow, _build_dismissed_repos _write_dismissed_json( tmp_path, @@ -844,7 +844,7 @@ class TestBuildBriefingDismissedField: """build_briefing populates dismissed_repos when output_dir is given.""" def test_output_dir_populates_dismissed_repos(self, tmp_path): - from src.briefing import build_briefing + from github_repo_auditor.briefing import build_briefing _write_dismissed_json( tmp_path, @@ -865,7 +865,7 @@ def test_output_dir_populates_dismissed_repos(self, tmp_path): assert briefing.dismissed_repos[0].repo_name == "SilencedRepo" def test_no_output_dir_dismissed_repos_empty(self): - from src.briefing import build_briefing + from github_repo_auditor.briefing import build_briefing audits = [_make_audit("AnyRepo")] briefing = build_briefing(audits, "alice", "2026-05-12", use_history=False) @@ -876,7 +876,7 @@ class TestRenderMarkdownDismissedSection: """render_markdown emits/omits the Currently Dismissed section correctly.""" def test_non_empty_dismissed_repos_renders_section(self): - from src.briefing import DismissedRepoRow + from github_repo_auditor.briefing import DismissedRepoRow briefing = Briefing( username="alice", @@ -899,7 +899,7 @@ def test_empty_dismissed_repos_omits_section(self): assert "## Currently Dismissed" not in md def test_dismissed_count_line_correct(self): - from src.briefing import DismissedRepoRow + from github_repo_auditor.briefing import DismissedRepoRow briefing = Briefing( username="alice", @@ -913,7 +913,7 @@ def test_dismissed_count_line_correct(self): assert "2 repo(s) currently suppressed" in md def test_no_reason_omits_reason_part(self): - from src.briefing import DismissedRepoRow + from github_repo_auditor.briefing import DismissedRepoRow briefing = Briefing( username="alice", @@ -930,7 +930,7 @@ class TestRenderVoiceDismissedLine: """render_voice mentions dismissed count when non-empty.""" def test_non_empty_dismissed_repos_voice_line(self): - from src.briefing import DismissedRepoRow + from github_repo_auditor.briefing import DismissedRepoRow briefing = Briefing( username="alice", @@ -953,7 +953,7 @@ class TestDismissedCrossLink: """render_markdown cross-link footer in the Currently Dismissed section (Arc G S13.3).""" def _dismissed_briefing(self) -> "Briefing": - from src.briefing import DismissedRepoRow + from github_repo_auditor.briefing import DismissedRepoRow return Briefing( username="alice", diff --git a/tests/test_cache.py b/tests/test_cache.py index 0bfcd891..97bac202 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -3,7 +3,7 @@ import json import time -from src.cache import ResponseCache +from github_repo_auditor.cache import ResponseCache class TestResponseCache: diff --git a/tests/test_cache_reconcile.py b/tests/test_cache_reconcile.py index bb488b35..03f8c909 100644 --- a/tests/test_cache_reconcile.py +++ b/tests/test_cache_reconcile.py @@ -19,16 +19,16 @@ from pathlib import Path from unittest.mock import patch -from src.analyzer_cache import _deep_equal, _diff_summary, reconcile, store -from src.analyzers.readme import ReadmeAnalyzer -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.analyzer_cache import _deep_equal, _diff_summary, reconcile, store +from github_repo_auditor.analyzers.readme import ReadmeAnalyzer +from github_repo_auditor.models import AnalyzerResult, RepoMetadata # ── Helpers ──────────────────────────────────────────────────────────── def _fresh_db() -> sqlite3.Connection: conn = sqlite3.connect(":memory:") - from src.warehouse import _ensure_schema + from github_repo_auditor.warehouse import _ensure_schema _ensure_schema(conn) return conn @@ -326,7 +326,7 @@ def _fresh(path, m, conn=None): return [opt_out.analyze(path, m)] # Patch ALL_ANALYZERS in the namespace used by reconcile's inner import. - with patch("src.analyzers.ALL_ANALYZERS", [opt_out]): + with patch("github_repo_auditor.analyzers.ALL_ANALYZERS", [opt_out]): report = reconcile( {"repo": repo_dir}, {"repo": meta}, diff --git a/tests/test_cache_ttl.py b/tests/test_cache_ttl.py index 08e902a3..b51028f3 100644 --- a/tests/test_cache_ttl.py +++ b/tests/test_cache_ttl.py @@ -7,7 +7,7 @@ from datetime import date, datetime, timedelta, timezone from pathlib import Path -from src.suggest_initiatives import ( +from github_repo_auditor.suggest_initiatives import ( _CACHE_TTL_DAYS, InitiativeSuggestion, load_suggestion_cache, @@ -66,7 +66,7 @@ def test_v1_returns_empty(self, tmp_path, caplog): ), encoding="utf-8", ) - with caplog.at_level(logging.INFO, logger="src.suggest_initiatives"): + with caplog.at_level(logging.INFO, logger="github_repo_auditor.suggest_initiatives"): result = load_suggestion_cache(path) assert result == OrderedDict() @@ -80,7 +80,7 @@ def test_v1_logs_legacy_message(self, tmp_path, caplog): json.dumps({"version": 1, "entries": []}), encoding="utf-8", ) - with caplog.at_level(logging.INFO, logger="src.suggest_initiatives"): + with caplog.at_level(logging.INFO, logger="github_repo_auditor.suggest_initiatives"): load_suggestion_cache(path) assert any("v1" in r.message and "dropping" in r.message for r in caplog.records) diff --git a/tests/test_catalog_validator.py b/tests/test_catalog_validator.py index 2bcf2142..862d40c4 100644 --- a/tests/test_catalog_validator.py +++ b/tests/test_catalog_validator.py @@ -3,7 +3,7 @@ import pytest import yaml -from src.catalog_validator import REQUIRED_FIELDS, score_catalog_entry, validate_catalog +from github_repo_auditor.catalog_validator import REQUIRED_FIELDS, score_catalog_entry, validate_catalog def test_full_entry_scores_one(): diff --git a/tests/test_cicd.py b/tests/test_cicd.py index a279b7a8..38839959 100644 --- a/tests/test_cicd.py +++ b/tests/test_cicd.py @@ -2,7 +2,7 @@ import pytest -from src.analyzers.cicd import CicdAnalyzer, _has_build_scripts +from github_repo_auditor.analyzers.cicd import CicdAnalyzer, _has_build_scripts class TestCicdAnalyzerAltCi: diff --git a/tests/test_cli_automation_proposals.py b/tests/test_cli_automation_proposals.py index 55af8718..34653601 100644 --- a/tests/test_cli_automation_proposals.py +++ b/tests/test_cli_automation_proposals.py @@ -14,7 +14,7 @@ import pytest -from src.automation_proposals import ( +from github_repo_auditor.automation_proposals import ( ACTION_CONTEXT_PR, STATUS_APPROVED, STATUS_PENDING, @@ -47,7 +47,7 @@ def _seed(output_dir: Path, status: str = STATUS_PENDING) -> Path: def _run(monkeypatch: pytest.MonkeyPatch, tmp_path: Path, *flags: str) -> None: - from src.cli import main + from github_repo_auditor.cli import main monkeypatch.setattr(sys, "argv", ["audit", "--output-dir", str(tmp_path), "user", *flags]) main() diff --git a/tests/test_cli_hardening.py b/tests/test_cli_hardening.py index 3ba60b93..7cdc089d 100644 --- a/tests/test_cli_hardening.py +++ b/tests/test_cli_hardening.py @@ -8,13 +8,13 @@ import pytest -from src import cli -from src.app import auto_apply, run_audit -from src.baseline_context import build_baseline_context -from src.models import AnalyzerResult, AuditReport, RepoAudit -from src.report_scorecards import apply_scorecards -from src.report_state import audit_from_dict, report_from_dict -from src.scorer import WEIGHTS +from github_repo_auditor import cli +from github_repo_auditor.app import auto_apply, run_audit +from github_repo_auditor.baseline_context import build_baseline_context +from github_repo_auditor.models import AnalyzerResult, AuditReport, RepoAudit +from github_repo_auditor.report_scorecards import apply_scorecards +from github_repo_auditor.report_state import audit_from_dict, report_from_dict +from github_repo_auditor.scorer import WEIGHTS def _make_args(**overrides) -> Namespace: @@ -404,8 +404,8 @@ def test_main_watch_uses_chosen_watch_plan(monkeypatch, sample_metadata, tmp_pat ) monkeypatch.setattr(cli, "build_parser", lambda: FakeParser(args)) - monkeypatch.setattr("src.recurring_review.choose_watch_plan", lambda *_a, **_k: watch_plan) - monkeypatch.setattr("src.watch.run_watch_loop", lambda audit_fn, interval=0: audit_fn()) + monkeypatch.setattr("github_repo_auditor.recurring_review.choose_watch_plan", lambda *_a, **_k: watch_plan) + monkeypatch.setattr("github_repo_auditor.watch.run_watch_loop", lambda audit_fn, interval=0: audit_fn()) monkeypatch.setattr(run_audit, "_load_scoring_profile", lambda name: (None, "default")) monkeypatch.setattr(run_audit, "_fetch_repo_metadata", lambda *_: ([sample_metadata], [])) monkeypatch.setattr(run_audit, "_run_incremental_audit", lambda *a, **k: captured.update(k)) @@ -589,7 +589,7 @@ def test_control_center_snapshot_rehydrates_portfolio_context( "operator_queue": [dict(report.operator_queue[0])], } - from src.control_center_snapshot import enrich_control_center_snapshot_from_report + from github_repo_auditor.control_center_snapshot import enrich_control_center_snapshot_from_report updated = enrich_control_center_snapshot_from_report( report.to_dict(), @@ -648,7 +648,7 @@ def test_control_center_snapshot_repairs_missing_path_basename_catalog_context( ] snapshot = {"operator_summary": {}, "operator_queue": [dict(report.operator_queue[0])]} - from src.control_center_snapshot import enrich_control_center_snapshot_from_report + from github_repo_auditor.control_center_snapshot import enrich_control_center_snapshot_from_report updated = enrich_control_center_snapshot_from_report( report.to_dict(), @@ -671,9 +671,9 @@ class _Result: blocking_errors = 0 monkeypatch.setattr(cli, "build_parser", lambda: FakeParser(args)) - monkeypatch.setattr("src.diagnostics.run_diagnostics", lambda *a, **k: _Result()) - monkeypatch.setattr("src.diagnostics.format_diagnostics_report", lambda result: "doctor ok") - monkeypatch.setattr("src.diagnostics.write_diagnostics_report", lambda result, output_dir, username: artifact_path) + monkeypatch.setattr("github_repo_auditor.diagnostics.run_diagnostics", lambda *a, **k: _Result()) + monkeypatch.setattr("github_repo_auditor.diagnostics.format_diagnostics_report", lambda result: "doctor ok") + monkeypatch.setattr("github_repo_auditor.diagnostics.write_diagnostics_report", lambda result, output_dir, username: artifact_path) cli.main() captured = capsys.readouterr() @@ -703,11 +703,11 @@ def _write_approval_center_artifacts(report_arg, output_dir, *, approval_view): monkeypatch.setattr(cli, "build_parser", lambda: FakeParser(args)) monkeypatch.setattr( - "src.app.approval_center.refresh_latest_report_state", + "github_repo_auditor.app.approval_center.refresh_latest_report_state", lambda _output_dir, _args: (tmp_path / "audit-report-testuser-2026-03-29.json", {}, report), ) monkeypatch.setattr( - "src.app.approval_center.write_approval_center_artifacts", + "github_repo_auditor.app.approval_center.write_approval_center_artifacts", _write_approval_center_artifacts, ) @@ -729,10 +729,10 @@ def test_main_control_center_writes_artifacts_without_audit(monkeypatch, tmp_pat monkeypatch.setattr(cli, "build_parser", lambda: FakeParser(args)) monkeypatch.setattr( - "src.app.control_center.load_latest_report", + "github_repo_auditor.app.control_center.load_latest_report", lambda _output_dir: (report_path, report_data), ) - monkeypatch.setattr("src.app.control_center.find_previous", lambda *_args, **_kwargs: None) + monkeypatch.setattr("github_repo_auditor.app.control_center.find_previous", lambda *_args, **_kwargs: None) cli.main() @@ -768,10 +768,10 @@ def test_main_control_center_suppresses_queue_when_portfolio_truth_is_newer( monkeypatch.setattr(cli, "build_parser", lambda: FakeParser(args)) monkeypatch.setattr( - "src.app.control_center.load_latest_report", + "github_repo_auditor.app.control_center.load_latest_report", lambda _output_dir: (report_path, report_data), ) - monkeypatch.setattr("src.app.control_center.find_previous", lambda *_args, **_kwargs: None) + monkeypatch.setattr("github_repo_auditor.app.control_center.find_previous", lambda *_args, **_kwargs: None) cli.main() @@ -783,7 +783,7 @@ def test_main_control_center_suppresses_queue_when_portfolio_truth_is_newer( def test_control_center_default_print_hides_experiment_items() -> None: - from src.operator_control_center_artifacts import should_print_control_center_item + from github_repo_auditor.operator_control_center_artifacts import should_print_control_center_item assert should_print_control_center_item( { @@ -806,7 +806,7 @@ def test_control_center_default_print_hides_experiment_items() -> None: def test_control_center_default_view_hides_archive_items() -> None: - from src.operator_control_center_artifacts import should_print_control_center_item + from github_repo_auditor.operator_control_center_artifacts import should_print_control_center_item assert not should_print_control_center_item( { @@ -842,7 +842,7 @@ def test_control_center_artifact_filter_drops_archive_queue_items() -> None: ], } - from src.operator_control_center_artifacts import filter_snapshot_for_default_view + from github_repo_auditor.operator_control_center_artifacts import filter_snapshot_for_default_view filter_snapshot_for_default_view(snapshot) @@ -853,7 +853,7 @@ def test_main_control_center_requires_latest_report(monkeypatch): args = _make_args(control_center=True) monkeypatch.setattr(cli, "build_parser", lambda: FakeParser(args)) - monkeypatch.setattr("src.app.control_center.load_latest_report", lambda _output_dir: (None, None)) + monkeypatch.setattr("github_repo_auditor.app.control_center.load_latest_report", lambda _output_dir: (None, None)) with pytest.raises(SystemExit) as exc: cli.main() @@ -904,7 +904,7 @@ def test_auto_apply_dry_run_prints_automation_trust_bar( lambda _output_dir, _args: (tmp_path / "audit-report-testuser-2026-03-29.json", {}, report), ) monkeypatch.setattr( - "src.approval_ledger.load_approval_ledger_bundle", + "github_repo_auditor.approval_ledger.load_approval_ledger_bundle", lambda *_args, **_kwargs: {"approval_ledger": []}, ) @@ -966,7 +966,7 @@ def test_auto_apply_dry_run_does_not_call_github_writeback( lambda _output_dir, _args: (tmp_path / "audit-report-testuser-2026-03-29.json", {}, report), ) monkeypatch.setattr( - "src.approval_ledger.load_approval_ledger_bundle", + "github_repo_auditor.approval_ledger.load_approval_ledger_bundle", lambda *_args, **_kwargs: { "approval_ledger": [ { @@ -980,7 +980,7 @@ def test_auto_apply_dry_run_does_not_call_github_writeback( }, ) monkeypatch.setattr( - "src.ops_writeback.build_campaign_bundle", + "github_repo_auditor.ops_writeback.build_campaign_bundle", lambda *_args, **_kwargs: ( {"campaign_type": "promotion-push"}, [ @@ -1002,7 +1002,7 @@ def test_auto_apply_dry_run_does_not_call_github_writeback( def _apply_github_writeback(*_args, **_kwargs): raise AssertionError("dry-run must not call apply_github_writeback") - monkeypatch.setattr("src.ops_writeback.apply_github_writeback", _apply_github_writeback) + monkeypatch.setattr("github_repo_auditor.ops_writeback.apply_github_writeback", _apply_github_writeback) auto_apply.run_auto_apply_approved_mode(args, tmp_path) @@ -1068,18 +1068,18 @@ def _write_approval_center_artifacts(report_arg, output_dir, *, approval_view): monkeypatch.setattr(cli, "build_parser", lambda: FakeParser(args)) monkeypatch.setattr( - "src.app.approval_center._utcnow", + "github_repo_auditor.app.approval_center._utcnow", lambda: datetime(2026, 4, 17, tzinfo=timezone.utc), ) monkeypatch.setattr( - "src.app.approval_center.refresh_latest_report_state", + "github_repo_auditor.app.approval_center.refresh_latest_report_state", lambda _output_dir, _args: (tmp_path / "audit-report-testuser-2026-03-29.json", {}, report), ) - monkeypatch.setattr("src.app.approval_center.refresh_shared_artifacts_from_report", lambda *_a, **_k: {}) - monkeypatch.setattr("src.app.approval_center.write_approval_center_artifacts", _write_approval_center_artifacts) - monkeypatch.setattr("src.app.approval_center.load_approval_ledger_bundle", _load_approval_ledger_bundle) + monkeypatch.setattr("github_repo_auditor.app.approval_center.refresh_shared_artifacts_from_report", lambda *_a, **_k: {}) + monkeypatch.setattr("github_repo_auditor.app.approval_center.write_approval_center_artifacts", _write_approval_center_artifacts) + monkeypatch.setattr("github_repo_auditor.app.approval_center.load_approval_ledger_bundle", _load_approval_ledger_bundle) monkeypatch.setattr( - "src.app.approval_center.build_approval_record", + "github_repo_auditor.app.approval_center.build_approval_record", lambda ledger_record, *, reviewer, note="": { "approval_id": ledger_record["approval_id"], "approval_subject_type": ledger_record["approval_subject_type"], @@ -1091,7 +1091,7 @@ def _write_approval_center_artifacts(report_arg, output_dir, *, approval_view): "approval_note": note, }, ) - monkeypatch.setattr("src.app.approval_center.save_approval_record", _save_approval_record) + monkeypatch.setattr("github_repo_auditor.app.approval_center.save_approval_record", _save_approval_record) cli.main() @@ -1117,11 +1117,11 @@ def test_main_generate_manifest_writes_artifact(monkeypatch, tmp_path, sample_me monkeypatch.setattr(cli, "build_parser", lambda: FakeParser(args)) monkeypatch.setattr( - "src.app.report_only.load_latest_report", + "github_repo_auditor.app.report_only.load_latest_report", lambda _output_dir: (report_path, report_data), ) monkeypatch.setattr( - "src.app.report_only.generate_manifest", + "github_repo_auditor.app.report_only.generate_manifest", lambda data: [{"repo": "testuser/test-repo"}], ) @@ -1131,7 +1131,7 @@ def _write_manifest(manifest, output_dir): manifest_path.write_text("[]") return manifest_path - monkeypatch.setattr("src.app.report_only.write_manifest", _write_manifest) + monkeypatch.setattr("github_repo_auditor.app.report_only.write_manifest", _write_manifest) cli.main() @@ -1153,7 +1153,7 @@ def test_main_apply_improvements_writes_execution_report(monkeypatch, tmp_path, monkeypatch.setattr(cli, "build_parser", lambda: FakeParser(args)) monkeypatch.setattr( - "src.repo_improver.load_improvements", + "github_repo_auditor.repo_improver.load_improvements", lambda path: { "testuser/test-repo": { "repo": "testuser/test-repo", @@ -1180,9 +1180,9 @@ def _apply_readme_updates(client, owner, updates, *, dry_run=False): readme_calls["dry_run"] = dry_run return [{"repo": "test-repo", "dry_run": True}] - monkeypatch.setattr("src.repo_improver.apply_metadata_updates", _apply_metadata_updates) - monkeypatch.setattr("src.repo_improver.apply_readme_updates", _apply_readme_updates) - monkeypatch.setattr("src.repo_improver.generate_execution_report", lambda results, output_dir: execution_report) + monkeypatch.setattr("github_repo_auditor.repo_improver.apply_metadata_updates", _apply_metadata_updates) + monkeypatch.setattr("github_repo_auditor.repo_improver.apply_readme_updates", _apply_readme_updates) + monkeypatch.setattr("github_repo_auditor.repo_improver.generate_execution_report", lambda results, output_dir: execution_report) cli.main() @@ -1212,7 +1212,7 @@ def test_main_strict_preflight_blocks_on_warnings(monkeypatch): args = _make_args(preflight_mode="strict", token=None) monkeypatch.setattr(cli, "build_parser", lambda: FakeParser(args)) - monkeypatch.setattr("src.diagnostics._resolve_github_token", lambda token: ("", "")) + monkeypatch.setattr("github_repo_auditor.diagnostics._resolve_github_token", lambda token: ("", "")) with pytest.raises(SystemExit) as exc: cli.main() @@ -1327,7 +1327,7 @@ def test_incremental_noop_regenerates_from_latest_report(monkeypatch, tmp_path, monkeypatch.setattr(run_audit, "_load_latest_report", lambda _output_dir: (report_path, report_data)) monkeypatch.setattr( - "src.history.load_fingerprints", + "github_repo_auditor.history.load_fingerprints", lambda *_args, **_kwargs: {sample_metadata.name: {"pushed_at": sample_metadata.pushed_at.isoformat()}}, ) @@ -1449,7 +1449,7 @@ def test_incremental_audit_delegates_changed_repos_to_targeted_path(monkeypatch, monkeypatch.setattr(run_audit, "_load_latest_report", lambda _output_dir: (report_path, report_data)) monkeypatch.setattr( - "src.history.load_fingerprints", + "github_repo_auditor.history.load_fingerprints", lambda *_args, **_kwargs: { changed_repo.name: {"pushed_at": "2026-03-19T00:00:00+00:00"}, sample_metadata.name: {"pushed_at": sample_metadata.pushed_at.isoformat()}, @@ -1665,19 +1665,19 @@ def test_write_report_outputs_forwards_analyst_view_args(monkeypatch, tmp_path, monkeypatch.setattr(run_audit, "write_markdown_report", lambda *a, **k: tmp_path / "audit.md") monkeypatch.setattr(run_audit, "write_pcc_export", lambda *a, **k: tmp_path / "audit-pcc.json") monkeypatch.setattr(run_audit, "write_raw_metadata", lambda *a, **k: tmp_path / "raw.json") - monkeypatch.setattr("src.history.load_trend_data", lambda: []) - monkeypatch.setattr("src.history.load_repo_score_history", lambda: {}) - monkeypatch.setattr("src.history.find_previous", lambda *_: None) - monkeypatch.setattr("src.history.save_fingerprints", lambda *_: None) - monkeypatch.setattr("src.history.archive_report", lambda *_: None) - monkeypatch.setattr("src.warehouse.write_warehouse_snapshot", lambda *a, **k: tmp_path / "warehouse.db") + monkeypatch.setattr("github_repo_auditor.history.load_trend_data", lambda: []) + monkeypatch.setattr("github_repo_auditor.history.load_repo_score_history", lambda: {}) + monkeypatch.setattr("github_repo_auditor.history.find_previous", lambda *_: None) + monkeypatch.setattr("github_repo_auditor.history.save_fingerprints", lambda *_: None) + monkeypatch.setattr("github_repo_auditor.history.archive_report", lambda *_: None) + monkeypatch.setattr("github_repo_auditor.warehouse.write_warehouse_snapshot", lambda *a, **k: tmp_path / "warehouse.db") excel_calls: dict[str, object] = {} def _record_excel(*_args, **kwargs): excel_calls.update(kwargs) return tmp_path / "audit.xlsx" - monkeypatch.setattr("src.excel_export.export_excel", _record_excel) + monkeypatch.setattr("github_repo_auditor.excel_export.export_excel", _record_excel) html_calls: dict[str, object] = {} review_pack_calls: dict[str, object] = {} @@ -1690,8 +1690,8 @@ def _record_review_pack(*_args, **kwargs): review_pack_calls.update(kwargs) return {"review_pack_path": tmp_path / "review-pack.md"} - monkeypatch.setattr("src.web_export.export_html_dashboard", _record_html) - monkeypatch.setattr("src.review_pack.export_review_pack", _record_review_pack) + monkeypatch.setattr("github_repo_auditor.web_export.export_html_dashboard", _record_html) + monkeypatch.setattr("github_repo_auditor.review_pack.export_review_pack", _record_review_pack) outputs = run_audit._write_report_outputs(report, args, tmp_path) diff --git a/tests/test_cli_mode_validation.py b/tests/test_cli_mode_validation.py index 1d1ac232..730443bd 100644 --- a/tests/test_cli_mode_validation.py +++ b/tests/test_cli_mode_validation.py @@ -4,7 +4,7 @@ import pytest -from src.cli_mode_validation import CliModeState, validate_cli_mode_args +from github_repo_auditor.cli_mode_validation import CliModeState, validate_cli_mode_args def _make_args(**overrides) -> Namespace: diff --git a/tests/test_cli_output.py b/tests/test_cli_output.py index a93172c5..14f7dcf0 100644 --- a/tests/test_cli_output.py +++ b/tests/test_cli_output.py @@ -1,4 +1,4 @@ -from src.cli_output import ( +from github_repo_auditor.cli_output import ( HAS_RICH, create_progress, print_info, diff --git a/tests/test_cli_subcommands.py b/tests/test_cli_subcommands.py index b32facf3..2870c7c9 100644 --- a/tests/test_cli_subcommands.py +++ b/tests/test_cli_subcommands.py @@ -17,7 +17,7 @@ import re import warnings -import src.cli as cli_module +import github_repo_auditor.cli as cli_module _infer_subcommand_from_flags = cli_module._infer_subcommand_from_flags _rewrite_legacy_argv = cli_module._rewrite_legacy_argv diff --git a/tests/test_cloner.py b/tests/test_cloner.py index 467b5993..48262df0 100644 --- a/tests/test_cloner.py +++ b/tests/test_cloner.py @@ -6,7 +6,7 @@ import pytest -from src.cloner import clone_repo +from github_repo_auditor.cloner import clone_repo class TestCloneRepoHardening: diff --git a/tests/test_code_quality.py b/tests/test_code_quality.py index 957bee0d..39186939 100644 --- a/tests/test_code_quality.py +++ b/tests/test_code_quality.py @@ -8,7 +8,7 @@ import pytest -from src.analyzers.code_quality import ( +from github_repo_auditor.analyzers.code_quality import ( CodeQualityAnalyzer, _classify_commits, _count_todos, @@ -18,7 +18,7 @@ _radon_analysis, _score_commit_messages, ) -from src.models import RepoMetadata +from github_repo_auditor.models import RepoMetadata def _meta(**overrides) -> RepoMetadata: diff --git a/tests/test_codex_restart_packet.py b/tests/test_codex_restart_packet.py index 45b41cf9..d3cc0f38 100644 --- a/tests/test_codex_restart_packet.py +++ b/tests/test_codex_restart_packet.py @@ -4,12 +4,12 @@ import subprocess from pathlib import Path -from src.codex_restart_packet import ( +from github_repo_auditor.codex_restart_packet import ( build_restart_packet, discover_git_repos, render_markdown, ) -from src.portfolio_truth_types import TRUTH_LATEST_FILENAME +from github_repo_auditor.portfolio_truth_types import TRUTH_LATEST_FILENAME def _run(args: list[str], cwd: Path) -> None: diff --git a/tests/test_community_profile.py b/tests/test_community_profile.py index 68bbfd51..6738322f 100644 --- a/tests/test_community_profile.py +++ b/tests/test_community_profile.py @@ -2,8 +2,8 @@ from pathlib import Path -from src.analyzers.community_profile import CommunityProfileAnalyzer -from src.models import RepoMetadata +from github_repo_auditor.analyzers.community_profile import CommunityProfileAnalyzer +from github_repo_auditor.models import RepoMetadata class _CommunityProfileClient: diff --git a/tests/test_completeness.py b/tests/test_completeness.py index f8997ba5..2dc06436 100644 --- a/tests/test_completeness.py +++ b/tests/test_completeness.py @@ -2,7 +2,7 @@ from pathlib import Path -from src.analyzers.completeness import ( +from github_repo_auditor.analyzers.completeness import ( BuildReadinessAnalyzer, DocumentationAnalyzer, _sample_comment_density, diff --git a/tests/test_composer_golden_contract.py b/tests/test_composer_golden_contract.py index e3cb80dc..23d1a452 100644 --- a/tests/test_composer_golden_contract.py +++ b/tests/test_composer_golden_contract.py @@ -96,7 +96,7 @@ def test_magnitude_floor_unifies_rebuild_and_rererestore_tiers() -> None: # a `key`/`generated_at` that matches the target's `queue_identity`, so # `ordered_reset_reentry_events_for_target` takes its `current_index == 0` shortcut # and returns the two events unchanged. This sidesteps a known pre-existing gap: - # `current_closure_forecast_event_for_target` (src/operator_trend_support.py, + # `current_closure_forecast_event_for_target` (src/github_repo_auditor/operator_trend_support.py, # ~line 210) synthesizes a "current" event from the target dict whose key coverage # stops at the "rerestore" tier and never reaches "rererestore", so when the shared # corpus fixture (which has no matching key/generated_at) falls through to that diff --git a/tests/test_config.py b/tests/test_config.py index a193b0a4..0b6943c6 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2,7 +2,7 @@ import pytest -from src.config import inspect_config, load_config, merge_config_with_args, validate_config_data +from github_repo_auditor.config import inspect_config, load_config, merge_config_with_args, validate_config_data class TestLoadConfig: diff --git a/tests/test_context_quality.py b/tests/test_context_quality.py index bc962107..895c4132 100644 --- a/tests/test_context_quality.py +++ b/tests/test_context_quality.py @@ -2,8 +2,8 @@ """Tests for the composite context_quality_score (Arc H H.4).""" import pytest -from src.context_quality import compute_context_quality_score -from src.models import AnalyzerResult, RepoAudit, RepoMetadata +from github_repo_auditor.context_quality import compute_context_quality_score +from github_repo_auditor.models import AnalyzerResult, RepoAudit, RepoMetadata def test_perfect_repo_scores_one(): diff --git a/tests/test_dashboard_risk_kpi.py b/tests/test_dashboard_risk_kpi.py index 436fd763..a0f7b1a9 100644 --- a/tests/test_dashboard_risk_kpi.py +++ b/tests/test_dashboard_risk_kpi.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.excel_dashboard_helpers import build_dashboard_kpi_specs +from github_repo_auditor.excel_dashboard_helpers import build_dashboard_kpi_specs def test_kpi_specs_include_elevated_risk_when_present() -> None: diff --git a/tests/test_demo_portfolio.py b/tests/test_demo_portfolio.py index 4d4ea8a0..72b3a327 100644 --- a/tests/test_demo_portfolio.py +++ b/tests/test_demo_portfolio.py @@ -12,8 +12,8 @@ from datetime import datetime, timedelta, timezone from pathlib import Path -from src.automation_proposals import VALID_ACTION_TYPES, VALID_STATUSES -from src.demo_portfolio import ( +from github_repo_auditor.automation_proposals import VALID_ACTION_TYPES, VALID_STATUSES +from github_repo_auditor.demo_portfolio import ( DEMO_PROJECTS, FRESH_OFFSET_HOURS, HISTORY_POINTS, @@ -27,18 +27,18 @@ history_snapshots, resolved_coverage_state, ) -from src.github_security_coverage import ( +from github_repo_auditor.github_security_coverage import ( GITHUB_SECURITY_RECEIPT_SCHEMA_VERSION, PROVIDER_NAMES, _provider_result, ) -from src.portfolio_pathing import build_operating_path_entry -from src.portfolio_truth_sources import ( +from github_repo_auditor.portfolio_pathing import build_operating_path_entry +from github_repo_auditor.portfolio_truth_sources import ( WORKSPACE_DISCOVERY_POLICY_VERSION, checkout_collision_summary, ) -from src.portfolio_truth_provenance import REQUIRED_PROJECT_PROVENANCE_KEYS -from src.portfolio_truth_types import ( +from github_repo_auditor.portfolio_truth_provenance import REQUIRED_PROJECT_PROVENANCE_KEYS +from github_repo_auditor.portfolio_truth_types import ( SCHEMA_VERSION, TRUTH_LATEST_FILENAME, VALID_ACTIVITY_STATUS, @@ -46,8 +46,8 @@ VALID_CATEGORY_TAGS, VALID_CONTEXT_QUALITY, ) -from src.portfolio_truth_reconcile import _build_security_fields -from src.portfolio_truth_validate import validate_truth_snapshot_payload +from github_repo_auditor.portfolio_truth_reconcile import _build_security_fields +from github_repo_auditor.portfolio_truth_validate import validate_truth_snapshot_payload # Portfolio Command Center reads anything older than this as no longer fresh. CONSUMER_FRESH_WINDOW_HOURS = 48 @@ -414,7 +414,7 @@ def test_proposals_present_a_mixed_state_triage_queue() -> None: def test_weekly_digest_and_burndown_agree_with_the_snapshot() -> None: - from src.security_admission import derive_security_admission + from github_repo_auditor.security_admission import derive_security_admission snapshot = _snapshot() digest = build_weekly_digest(snapshot) diff --git a/tests/test_dep_graph.py b/tests/test_dep_graph.py index 9dc78a94..3edfbffc 100644 --- a/tests/test_dep_graph.py +++ b/tests/test_dep_graph.py @@ -1,7 +1,7 @@ -"""Tests for src/dep_graph.py — cross-repo dependency graph.""" +"""Tests for src/github_repo_auditor/dep_graph.py — cross-repo dependency graph.""" from __future__ import annotations -from src.dep_graph import build_dependency_graph, find_vulnerability_impact +from github_repo_auditor.dep_graph import build_dependency_graph, find_vulnerability_impact def _make_audit(name: str, dep_names: list[str]) -> dict: diff --git a/tests/test_dependencies.py b/tests/test_dependencies.py index 4ea46da0..19dca50f 100644 --- a/tests/test_dependencies.py +++ b/tests/test_dependencies.py @@ -5,7 +5,7 @@ import sys import types -from src.analyzers.dependencies import DependenciesAnalyzer, _count_dependencies +from github_repo_auditor.analyzers.dependencies import DependenciesAnalyzer, _count_dependencies class TestDependenciesCacheInputsHash: @@ -209,15 +209,15 @@ def test_libyears_merge_preserves_existing_dependency_count( repo.mkdir() (repo / "requirements.txt").write_text("requests\nclick\n") - fake_cache_module = types.ModuleType("src.cache") + fake_cache_module = types.ModuleType("github_repo_auditor.cache") fake_cache_module.ResponseCache = lambda ttl: {"ttl": ttl} - fake_libyears_module = types.ModuleType("src.libyears") + fake_libyears_module = types.ModuleType("github_repo_auditor.libyears") fake_libyears_module.compute_libyears = lambda repo_path, manifests, cache: { "dep_count": 999, "total_libyears": 4.5, } - monkeypatch.setitem(sys.modules, "src.cache", fake_cache_module) - monkeypatch.setitem(sys.modules, "src.libyears", fake_libyears_module) + monkeypatch.setitem(sys.modules, "github_repo_auditor.cache", fake_cache_module) + monkeypatch.setitem(sys.modules, "github_repo_auditor.libyears", fake_libyears_module) result = DependenciesAnalyzer().analyze(repo, sample_metadata) @@ -231,16 +231,16 @@ def test_libyears_failure_is_non_fatal(self, tmp_path, sample_metadata, monkeypa repo.mkdir() (repo / "requirements.txt").write_text("requests\n") - fake_cache_module = types.ModuleType("src.cache") + fake_cache_module = types.ModuleType("github_repo_auditor.cache") fake_cache_module.ResponseCache = lambda ttl: {"ttl": ttl} - fake_libyears_module = types.ModuleType("src.libyears") + fake_libyears_module = types.ModuleType("github_repo_auditor.libyears") def fail_compute_libyears(repo_path, manifests, cache): raise RuntimeError("registry unavailable") fake_libyears_module.compute_libyears = fail_compute_libyears - monkeypatch.setitem(sys.modules, "src.cache", fake_cache_module) - monkeypatch.setitem(sys.modules, "src.libyears", fake_libyears_module) + monkeypatch.setitem(sys.modules, "github_repo_auditor.cache", fake_cache_module) + monkeypatch.setitem(sys.modules, "github_repo_auditor.libyears", fake_libyears_module) result = DependenciesAnalyzer().analyze(repo, sample_metadata) diff --git a/tests/test_dependencies_sbom.py b/tests/test_dependencies_sbom.py index 3a880af5..1ec1d539 100644 --- a/tests/test_dependencies_sbom.py +++ b/tests/test_dependencies_sbom.py @@ -13,9 +13,9 @@ import pytest import requests -from src.analyzers.dependencies import DependenciesAnalyzer -from src.github_client import GitHubClient -from src.models import RepoMetadata +from github_repo_auditor.analyzers.dependencies import DependenciesAnalyzer +from github_repo_auditor.github_client import GitHubClient +from github_repo_auditor.models import RepoMetadata # --------------------------------------------------------------------------- # Fixtures diff --git a/tests/test_description_analyzer.py b/tests/test_description_analyzer.py index b9b69352..bab61488 100644 --- a/tests/test_description_analyzer.py +++ b/tests/test_description_analyzer.py @@ -3,7 +3,7 @@ from datetime import datetime, timezone from pathlib import Path -from src.models import RepoMetadata +from github_repo_auditor.models import RepoMetadata _DT = datetime(2024, 1, 1, tzinfo=timezone.utc) @@ -35,7 +35,7 @@ def _meta(**kwargs) -> RepoMetadata: def test_correct_swift_classification(tmp_path: Path) -> None: - from src.analyzers.description_analyzer import DescriptionAnalyzer + from github_repo_auditor.analyzers.description_analyzer import DescriptionAnalyzer (tmp_path / "MyApp.xcodeproj").mkdir() meta = _meta(description="A SwiftUI iOS app for tracking habits", language="Swift") @@ -46,7 +46,7 @@ def test_correct_swift_classification(tmp_path: Path) -> None: def test_misclassified_ios_repo_with_python_description(tmp_path: Path) -> None: - from src.analyzers.description_analyzer import DescriptionAnalyzer + from github_repo_auditor.analyzers.description_analyzer import DescriptionAnalyzer (tmp_path / "MyApp.xcodeproj").mkdir() meta = _meta(description="A Python CLI script for data processing", language="Swift") @@ -56,7 +56,7 @@ def test_misclassified_ios_repo_with_python_description(tmp_path: Path) -> None: def test_missing_description(tmp_path: Path) -> None: - from src.analyzers.description_analyzer import DescriptionAnalyzer + from github_repo_auditor.analyzers.description_analyzer import DescriptionAnalyzer meta = _meta(description=None, language="Python") result = DescriptionAnalyzer().analyze(tmp_path, meta) @@ -66,7 +66,7 @@ def test_missing_description(tmp_path: Path) -> None: def test_unknown_language_returns_full_score(tmp_path: Path) -> None: - from src.analyzers.description_analyzer import DescriptionAnalyzer + from github_repo_auditor.analyzers.description_analyzer import DescriptionAnalyzer meta = _meta(description="A COBOL mainframe batch processor", language="COBOL") result = DescriptionAnalyzer().analyze(tmp_path, meta) @@ -75,7 +75,7 @@ def test_unknown_language_returns_full_score(tmp_path: Path) -> None: def test_conflict_with_expected_match_gives_partial_score(tmp_path: Path) -> None: - from src.analyzers.description_analyzer import DescriptionAnalyzer + from github_repo_auditor.analyzers.description_analyzer import DescriptionAnalyzer meta = _meta( description="A swift iOS app with some react components", @@ -90,7 +90,7 @@ def test_conflict_with_expected_match_gives_partial_score(tmp_path: Path) -> Non def test_topics_used_as_conflict_signals(tmp_path: Path) -> None: - from src.analyzers.description_analyzer import DescriptionAnalyzer + from github_repo_auditor.analyzers.description_analyzer import DescriptionAnalyzer meta = _meta( description="An xcode project for iOS", diff --git a/tests/test_diagnostics.py b/tests/test_diagnostics.py index 40a88751..2291ea78 100644 --- a/tests/test_diagnostics.py +++ b/tests/test_diagnostics.py @@ -3,9 +3,9 @@ import json from argparse import Namespace -from src.baseline_context import build_baseline_context -from src.config import inspect_config -from src.diagnostics import run_diagnostics +from github_repo_auditor.baseline_context import build_baseline_context +from github_repo_auditor.config import inspect_config +from github_repo_auditor.diagnostics import run_diagnostics def _make_args(tmp_path, **overrides) -> Namespace: @@ -71,7 +71,7 @@ def test_requested_notion_sync_without_token_or_config_is_error(tmp_path, monkey def test_template_mode_missing_asset_is_error(tmp_path, monkeypatch): - monkeypatch.setattr("src.diagnostics.DEFAULT_TEMPLATE_PATH", tmp_path / "missing-template.xlsx") + monkeypatch.setattr("github_repo_auditor.diagnostics.DEFAULT_TEMPLATE_PATH", tmp_path / "missing-template.xlsx") args = _make_args(tmp_path, excel_mode="template") result = run_diagnostics(args, full=False) assert any(check.category == "excel" and check.status == "error" for check in result.checks) diff --git a/tests/test_diff.py b/tests/test_diff.py index bd7aad89..2e103a8d 100644 --- a/tests/test_diff.py +++ b/tests/test_diff.py @@ -2,7 +2,7 @@ import json -from src.diff import diff_reports, format_diff_markdown, print_diff_summary +from github_repo_auditor.diff import diff_reports, format_diff_markdown, print_diff_summary def _make_report(audits: list[dict], avg: float = 0.5, date: str = "2026-03-20") -> dict: diff --git a/tests/test_dismissal_event_cap.py b/tests/test_dismissal_event_cap.py index 3a7c0514..9d589440 100644 --- a/tests/test_dismissal_event_cap.py +++ b/tests/test_dismissal_event_cap.py @@ -4,7 +4,7 @@ from datetime import datetime, timedelta, timezone -from src.suggest_initiatives import ( +from github_repo_auditor.suggest_initiatives import ( _MAX_DISMISSAL_EVENTS, DismissalEvent, _save_dismissed_full, @@ -115,7 +115,7 @@ def test_trim_reason_mentions_dropped_count(self, tmp_path): def test_items_preserved_through_trim(self, tmp_path): """DismissedSuggestion items are unaffected by event log trimming.""" - from src.suggest_initiatives import DismissedSuggestion + from github_repo_auditor.suggest_initiatives import DismissedSuggestion items = [ DismissedSuggestion( diff --git a/tests/test_draft_readmes.py b/tests/test_draft_readmes.py index 696002ee..b0adf094 100644 --- a/tests/test_draft_readmes.py +++ b/tests/test_draft_readmes.py @@ -1,4 +1,4 @@ -"""Tests for src/draft_readmes.py — Arc G Sprint 5.1-5.3.""" +"""Tests for src/github_repo_auditor/draft_readmes.py — Arc G Sprint 5.1-5.3.""" from __future__ import annotations @@ -11,7 +11,7 @@ import pytest -from src.draft_readmes import ( +from github_repo_auditor.draft_readmes import ( DraftReadmePacket, build_context, generate_draft, @@ -21,7 +21,7 @@ record_draft_apply_failure, write_packets_to_ledger, ) -from src.llm_cost import BudgetExceededError +from github_repo_auditor.llm_cost import BudgetExceededError # ── Helpers ─────────────────────────────────────────────────────────────────── @@ -264,7 +264,7 @@ def test_context_repos_captured_in_packet(self) -> None: class TestWritePacketsToLedger: def test_produces_readable_records(self) -> None: """Packets written to ledger can be read back by load_approval_records.""" - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) @@ -320,9 +320,9 @@ def test_audit_report_draft_readmes_repo_calls_dispatch(self) -> None: ] with ( patch("sys.argv", test_argv), - patch("src.cli._run_draft_readmes_mode") as mock_dispatch, + patch("github_repo_auditor.cli._run_draft_readmes_mode") as mock_dispatch, ): - from src.cli import main + from github_repo_auditor.cli import main try: main() @@ -336,7 +336,7 @@ def test_audit_report_draft_readmes_repo_calls_dispatch(self) -> None: def test_parser_accepts_draft_readmes_repo_flag(self) -> None: """build_parser() accepts --draft-readmes and --draft-readmes-repo flags.""" - from src.cli import build_parser + from github_repo_auditor.cli import build_parser parser = build_parser() args = parser.parse_args( @@ -354,7 +354,7 @@ def test_parser_accepts_draft_readmes_repo_flag(self) -> None: def test_legacy_form_accepts_draft_readmes_flags(self) -> None: """Legacy flat invocation accepts --draft-readmes flags.""" - from src.cli import build_parser + from github_repo_auditor.cli import build_parser parser = build_parser() args = parser.parse_args( @@ -382,11 +382,11 @@ def test_legacy_form_emits_deprecation_warning(self) -> None: "/tmp/test-output", ] with patch("sys.argv", ["audit"] + argv): - with patch("src.cli._run_draft_readmes_mode") as mock_fn: + with patch("github_repo_auditor.cli._run_draft_readmes_mode") as mock_fn: with warnings.catch_warnings(record=True) as caught: warnings.simplefilter("always") try: - from src.cli import main + from github_repo_auditor.cli import main main() except SystemExit: @@ -405,7 +405,7 @@ def test_legacy_form_emits_deprecation_warning(self) -> None: def test_infer_subcommand_maps_draft_readmes_to_report(self) -> None: """_infer_subcommand_from_flags returns 'report' when draft_readmes=True.""" - from src.cli import _infer_subcommand_from_flags, build_parser + from github_repo_auditor.cli import _infer_subcommand_from_flags, build_parser parser = build_parser() args = parser.parse_args( @@ -427,8 +427,8 @@ def test_infer_subcommand_maps_draft_readmes_to_report(self) -> None: class TestSuppressionCheck: def test_suppressed_repo_skipped_in_dispatch(self) -> None: """When operator prefs mark a repo as suppressed, generate_draft is not called.""" - with patch("src.draft_readmes.generate_draft") as mock_gen: - from src.operator_prefs import is_suppressed + with patch("github_repo_auditor.draft_readmes.generate_draft") as mock_gen: + from github_repo_auditor.operator_prefs import is_suppressed prefs = { "suppressions": [ @@ -459,7 +459,7 @@ class TestCostGuard: def test_budget_exceeded_aborts_batch_partial_packets_persisted(self) -> None: """With --max-llm-spend=0.0001 and a budget-busting provider, run aborts after the first failure; packets generated before the abort are persisted.""" - from src.llm_cost import CostTracker + from github_repo_auditor.llm_cost import CostTracker with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) @@ -502,7 +502,7 @@ def generate(self, prompt, model, max_tokens, *, cost_tracker=None, feature="x") # Persist whatever was generated before abort write_packets_to_ledger(packets_written, output_dir, reviewer="tester") - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records records = load_approval_records(output_dir, "tester") draft_records = [r for r in records if r.get("approval_subject_type") == "draft-readme"] @@ -670,10 +670,10 @@ def test_no_file_reads_from_ledger(self) -> None: mock_apply = MagicMock(return_value=[{"repo": "target-repo", "ok": True}]) with ( - patch("src.repo_improver.apply_readme_updates", mock_apply), - patch("src.cli.apply_readme_updates", mock_apply, create=True), + patch("github_repo_auditor.repo_improver.apply_readme_updates", mock_apply), + patch("github_repo_auditor.cli.apply_readme_updates", mock_apply, create=True), ): - from src.cli import _run_apply_improvements_mode + from github_repo_auditor.cli import _run_apply_improvements_mode args = self._make_args(str(output_dir), apply_readmes=True, dry_run=False) parser = MagicMock() @@ -727,8 +727,8 @@ def test_both_sources_combined(self) -> None: {"repo": "ledger-repo", "ok": True}, ] ) - with patch("src.repo_improver.apply_readme_updates", mock_apply): - from src.cli import _run_apply_improvements_mode + with patch("github_repo_auditor.repo_improver.apply_readme_updates", mock_apply): + from github_repo_auditor.cli import _run_apply_improvements_mode args = self._make_args( str(output_dir), @@ -775,10 +775,10 @@ def test_dry_run_passes_dry_run_flag_and_skips_state_transitions(self) -> None: mock_apply = MagicMock(return_value=[{"repo": "dry-repo", "dry_run": True}]) mock_mark_applied = MagicMock() with ( - patch("src.repo_improver.apply_readme_updates", mock_apply), - patch("src.draft_readmes.mark_draft_applied", mock_mark_applied), + patch("github_repo_auditor.repo_improver.apply_readme_updates", mock_apply), + patch("github_repo_auditor.draft_readmes.mark_draft_applied", mock_mark_applied), ): - from src.cli import _run_apply_improvements_mode + from github_repo_auditor.cli import _run_apply_improvements_mode args = self._make_args(str(output_dir), apply_readmes=True, dry_run=True) parser = MagicMock() diff --git a/tests/test_evidence_freshness_conformance.py b/tests/test_evidence_freshness_conformance.py index b1d322f9..2d4305d4 100644 --- a/tests/test_evidence_freshness_conformance.py +++ b/tests/test_evidence_freshness_conformance.py @@ -9,7 +9,7 @@ import pytest -from src.evidence_freshness_conformance import ( +from github_repo_auditor.evidence_freshness_conformance import ( AGING_AFTER_MS, CANONICAL_REASONS, CANONICAL_STATES, diff --git a/tests/test_excel_enhanced.py b/tests/test_excel_enhanced.py index 96bcaaef..32f69f28 100644 --- a/tests/test_excel_enhanced.py +++ b/tests/test_excel_enhanced.py @@ -7,7 +7,7 @@ import pytest from openpyxl import Workbook -from src.excel_export import ( +from github_repo_auditor.excel_export import ( RADAR_DIMS, RADAR_LABELS, _build_action_items, @@ -43,12 +43,12 @@ _build_writeback_audit, export_excel, ) -from src.excel_template import ( +from github_repo_auditor.excel_template import ( DEFAULT_TEMPLATE_PATH, TEMPLATE_INFO_SHEET, load_workbook_allowing_native_sparklines, ) -from src.report_enrichment import ( +from github_repo_auditor.report_enrichment import ( build_trust_actionability_summary, build_weekly_review_pack, no_baseline_summary, @@ -1933,7 +1933,7 @@ def test_secondary_visible_sheets_gain_freeze_panes(self): assert wb["Trends"].freeze_panes == "B4" def test_creates_security_phase_sheets(self): - from src.excel_export import ( + from github_repo_auditor.excel_export import ( _build_security_controls, _build_security_debt, _build_supply_chain, diff --git a/tests/test_excel_export_registry_helpers.py b/tests/test_excel_export_registry_helpers.py index 0b687c49..f1db5e2f 100644 --- a/tests/test_excel_export_registry_helpers.py +++ b/tests/test_excel_export_registry_helpers.py @@ -1,7 +1,7 @@ from __future__ import annotations -from src.excel_export_registry_helpers import build_excel_workbook_runtime -from src.excel_workbook_helpers import ( +from github_repo_auditor.excel_export_registry_helpers import build_excel_workbook_runtime +from github_repo_auditor.excel_workbook_helpers import ( CORE_VISIBLE_SHEETS, DEFAULT_PREFERRED_SHEET_ORDER, finalize_workbook_structure, diff --git a/tests/test_excel_initiative_tracker.py b/tests/test_excel_initiative_tracker.py index 3e4defa0..ad2c9dc0 100644 --- a/tests/test_excel_initiative_tracker.py +++ b/tests/test_excel_initiative_tracker.py @@ -7,12 +7,12 @@ from openpyxl import Workbook -from src.excel_initiative_tracker_helpers import ( +from github_repo_auditor.excel_initiative_tracker_helpers import ( _format_missing_requirements, build_initiative_tracker_sheet, ) -from src.initiatives import Initiative, save_initiatives -from src.maturity_tiers import TierGap +from github_repo_auditor.initiatives import Initiative, save_initiatives +from github_repo_auditor.maturity_tiers import TierGap # ── Helpers ─────────────────────────────────────────────────────────────────── @@ -89,7 +89,7 @@ def _build_sheet( # Save initiatives to tmp path output_dir = tmp_path or Path("/tmp/test_initiative_tracker") output_dir.mkdir(parents=True, exist_ok=True) - from src.initiatives import initiatives_path + from github_repo_auditor.initiatives import initiatives_path save_initiatives(initiatives_path(output_dir), initiatives) diff --git a/tests/test_ghas_alerts.py b/tests/test_ghas_alerts.py index cb7bd16f..fc865fc9 100644 --- a/tests/test_ghas_alerts.py +++ b/tests/test_ghas_alerts.py @@ -7,7 +7,7 @@ import requests -from src.ghas_alerts import fetch_ghas_alerts, format_ghas_summary +from github_repo_auditor.ghas_alerts import fetch_ghas_alerts, format_ghas_summary # --------------------------------------------------------------------------- # Helpers @@ -212,7 +212,7 @@ def _get(url: str, params=None, timeout=None): class TestCacheIntegration: def test_cache_hit_skips_api_calls(self, tmp_path: Path) -> None: - from src.cache import ResponseCache + from github_repo_auditor.cache import ResponseCache cache = ResponseCache(cache_dir=tmp_path / ".cache", ttl=21600) audit = _make_audit("cached-repo") @@ -427,7 +427,13 @@ def test_vuln_check_also_triggers_ghas(self) -> None: """When --vuln-check is active, the GHAS block is also triggered (implied flag).""" from pathlib import Path as _Path - audit_flow_source = (_Path(__file__).parent.parent / "src" / "app" / "run_audit.py").read_text() + audit_flow_source = ( + _Path(__file__).parent.parent + / "src" + / "github_repo_auditor" + / "app" + / "run_audit.py" + ).read_text() # Look for the condition that gates GHAS on either flag in the audit flow. assert "ghas_alerts" in audit_flow_source assert "vuln_check" in audit_flow_source diff --git a/tests/test_github_client.py b/tests/test_github_client.py index 85c3419e..d0bc1d38 100644 --- a/tests/test_github_client.py +++ b/tests/test_github_client.py @@ -5,8 +5,8 @@ import pytest import requests -from src.github_client import REST_API_VERSION, GitHubClient -from src.http_link_header import next_link_from_header +from github_repo_auditor.github_client import REST_API_VERSION, GitHubClient +from github_repo_auditor.http_link_header import next_link_from_header class _MemoryCache: diff --git a/tests/test_github_client_async.py b/tests/test_github_client_async.py index e13abb84..96869517 100644 --- a/tests/test_github_client_async.py +++ b/tests/test_github_client_async.py @@ -1,4 +1,4 @@ -"""Tests for src.github_client_async — async parallel enrichment fetcher.""" +"""Tests for github_repo_auditor.github_client_async — async parallel enrichment fetcher.""" from __future__ import annotations @@ -10,8 +10,8 @@ import httpx import pytest -from src.github_client import GitHubClientError -from src.github_client_async import ( +from github_repo_auditor.github_client import GitHubClientError +from github_repo_auditor.github_client_async import ( AsyncGitHubClient, _enrichment_endpoints, fetch_enrichment_sync, @@ -241,7 +241,7 @@ async def _run(): def test_5xx_retry_count_is_bounded(self): """Exactly _MAX_5XX_RETRIES + 1 attempts are made before exhaustion.""" - from src.github_client_async import _MAX_5XX_RETRIES + from github_repo_auditor.github_client_async import _MAX_5XX_RETRIES call_count = 0 diff --git a/tests/test_github_projects.py b/tests/test_github_projects.py index f22ea41a..7c4636c0 100644 --- a/tests/test_github_projects.py +++ b/tests/test_github_projects.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.github_projects import ( +from github_repo_auditor.github_projects import ( build_project_field_values, build_project_preview_summary, load_github_projects_config, diff --git a/tests/test_github_security_coverage.py b/tests/test_github_security_coverage.py index 61f40f8d..ea60cfcf 100644 --- a/tests/test_github_security_coverage.py +++ b/tests/test_github_security_coverage.py @@ -10,7 +10,7 @@ import pytest -from src.github_security_coverage import ( +from github_repo_auditor.github_security_coverage import ( DEFAULT_BASE_REQUEST_LIMIT, DEFAULT_EXPECTED_GITHUB_COHORT_COUNT, GITHUB_SECURITY_RECEIPT_FILENAME, @@ -30,8 +30,8 @@ verified_security_coverage_receipt_binding, write_security_coverage_receipt, ) -from src.portfolio_truth_reconcile import _select_security_entry -from src.portfolio_truth_status import load_security_coverage_by_full_name +from github_repo_auditor.portfolio_truth_reconcile import _select_security_entry +from github_repo_auditor.portfolio_truth_status import load_security_coverage_by_full_name NOW = datetime(2026, 7, 16, 12, tzinfo=timezone.utc) OUTCOME_FIXTURES = json.loads( @@ -656,7 +656,7 @@ def _assert_binding_revalidation_fails_in_child( import json import sys -from src.github_security_coverage import ( +from github_repo_auditor.github_security_coverage import ( SecurityCoverageError, SecurityCoverageReceiptBinding, verified_security_coverage_receipt_binding, @@ -1499,7 +1499,7 @@ def test_collector_cli_holds_writer_intent_before_truth_read_and_replacement( api_base_url="https://api.example.test", ) observed: dict[str, object] = {} - from src import github_security_coverage as coverage_module + from github_repo_auditor import github_security_coverage as coverage_module original_load_json_object = coverage_module._load_json_object @@ -1527,11 +1527,11 @@ def fake_collect_security_coverage( return second monkeypatch.setattr( - "src.github_security_coverage._load_json_object", + "github_repo_auditor.github_security_coverage._load_json_object", load_json_with_truth_read_probe, ) monkeypatch.setattr( - "src.github_security_coverage.collect_security_coverage", + "github_repo_auditor.github_security_coverage.collect_security_coverage", fake_collect_security_coverage, ) monkeypatch.setattr( diff --git a/tests/test_history.py b/tests/test_history.py index 1036ab63..d025cd08 100644 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from src.history import archive_report, find_previous, load_complexity_trends, load_history_index +from github_repo_auditor.history import archive_report, find_previous, load_complexity_trends, load_history_index class TestArchive: diff --git a/tests/test_implementation_hotspots.py b/tests/test_implementation_hotspots.py index db3eff08..8f7a9b06 100644 --- a/tests/test_implementation_hotspots.py +++ b/tests/test_implementation_hotspots.py @@ -3,8 +3,8 @@ from datetime import datetime, timezone from pathlib import Path -from src.implementation_hotspots import build_implementation_hotspots -from src.models import AnalyzerResult, RepoAudit, RepoMetadata +from github_repo_auditor.implementation_hotspots import build_implementation_hotspots +from github_repo_auditor.models import AnalyzerResult, RepoAudit, RepoMetadata def _metadata() -> RepoMetadata: diff --git a/tests/test_initiatives.py b/tests/test_initiatives.py index 8189a025..fb2cad88 100644 --- a/tests/test_initiatives.py +++ b/tests/test_initiatives.py @@ -1,4 +1,4 @@ -"""Tests for src/initiatives.py and CLI wiring — Arc G Sprint 7A.2/7A.3.""" +"""Tests for src/github_repo_auditor/initiatives.py and CLI wiring — Arc G Sprint 7A.2/7A.3.""" from __future__ import annotations @@ -10,7 +10,7 @@ import pytest -from src.initiatives import ( +from github_repo_auditor.initiatives import ( Initiative, close_initiative, derive_status, @@ -355,7 +355,7 @@ def _build_portfolio_truth(tmp_path: Path, repo_name: str = "Wavelength", tier: def _run_cli(argv: list[str]) -> int: """Run the CLI and return exit code (0 = success).""" - from src.cli import main + from github_repo_auditor.cli import main with patch.object(sys, "argv", ["audit"] + argv): try: diff --git a/tests/test_initiatives_dismissed_route.py b/tests/test_initiatives_dismissed_route.py index ccfd85ba..89e00fc9 100644 --- a/tests/test_initiatives_dismissed_route.py +++ b/tests/test_initiatives_dismissed_route.py @@ -14,7 +14,7 @@ from fastapi.testclient import TestClient # noqa: E402 -from src.serve.app import create_app # noqa: E402 +from github_repo_auditor.serve.app import create_app # noqa: E402 # ── Fixtures ───────────────────────────────────────────────────────────────── diff --git a/tests/test_initiatives_routes.py b/tests/test_initiatives_routes.py index 3ce9952d..76b044d5 100644 --- a/tests/test_initiatives_routes.py +++ b/tests/test_initiatives_routes.py @@ -13,7 +13,7 @@ from fastapi.testclient import TestClient # noqa: E402 -from src.serve.app import create_app # noqa: E402 +from github_repo_auditor.serve.app import create_app # noqa: E402 # --------------------------------------------------------------------------- # Fixtures diff --git a/tests/test_initiatives_suggestions_route.py b/tests/test_initiatives_suggestions_route.py index 2a92bbb3..83cb32f6 100644 --- a/tests/test_initiatives_suggestions_route.py +++ b/tests/test_initiatives_suggestions_route.py @@ -14,7 +14,7 @@ from fastapi.testclient import TestClient # noqa: E402 -from src.serve.app import create_app # noqa: E402 +from github_repo_auditor.serve.app import create_app # noqa: E402 # ── Repo fixture helpers (mirror test_initiatives_routes.py) ──────────────── @@ -92,7 +92,7 @@ def test_valid_truth_returns_suggestion_cards( self, output_dir: Path, client: TestClient ) -> None: """GET with valid portfolio-truth → 200 with suggestion cards rendered.""" - from src.suggest_initiatives import InitiativeSuggestion + from github_repo_auditor.suggest_initiatives import InitiativeSuggestion truth = _make_portfolio_truth([_bronze_repo("MyRepo"), _bronze_repo("OtherRepo")]) (output_dir / "portfolio-truth-latest.json").write_text(json.dumps(truth)) @@ -109,7 +109,7 @@ def test_valid_truth_returns_suggestion_cards( ] with patch( - "src.suggest_initiatives.generate_suggestions", + "github_repo_auditor.suggest_initiatives.generate_suggestions", return_value=(fake_suggestions, 0.0042), ): resp = client.get("/initiatives/suggestions") @@ -126,7 +126,7 @@ def test_no_qualifying_repos_shows_empty_state( (output_dir / "portfolio-truth-latest.json").write_text(json.dumps(truth)) with patch( - "src.suggest_initiatives.generate_suggestions", + "github_repo_auditor.suggest_initiatives.generate_suggestions", return_value=([], 0.0), ): resp = client.get("/initiatives/suggestions") @@ -138,7 +138,7 @@ def test_target_query_param_passed_to_generate( self, output_dir: Path, client: TestClient ) -> None: """GET ?target=3 passes target_tier=3 to generate_suggestions.""" - from src.suggest_initiatives import InitiativeSuggestion + from github_repo_auditor.suggest_initiatives import InitiativeSuggestion truth = _make_portfolio_truth([_silver_repo("Repo")]) (output_dir / "portfolio-truth-latest.json").write_text(json.dumps(truth)) @@ -155,7 +155,7 @@ def test_target_query_param_passed_to_generate( ] with patch( - "src.suggest_initiatives.generate_suggestions", + "github_repo_auditor.suggest_initiatives.generate_suggestions", return_value=(fake_suggestions, 0.005), ) as mock_gen: resp = client.get("/initiatives/suggestions?target=3") @@ -183,7 +183,7 @@ def test_happy_path_returns_accepted_fragment( self, output_dir: Path, client: TestClient ) -> None: """Valid form data → 200 HTML fragment containing '✓ Accepted'.""" - from src.initiatives import Initiative + from github_repo_auditor.initiatives import Initiative truth = _make_portfolio_truth([_bronze_repo("TargetRepo")]) (output_dir / "portfolio-truth-latest.json").write_text(json.dumps(truth)) @@ -196,7 +196,7 @@ def test_happy_path_returns_accepted_fragment( set_by="web", ) - with patch("src.suggest_initiatives.accept_suggestion", return_value=fake_initiative): + with patch("github_repo_auditor.suggest_initiatives.accept_suggestion", return_value=fake_initiative): resp = client.post( "/initiatives/accept", data={ @@ -378,7 +378,7 @@ def test_gap_partial_shows_approx_hint_for_proxy_requirement( self, output_dir: Path, client: TestClient ) -> None: """initiative_gap.html renders (approx.) for proxy-sourced requirements.""" - from src.maturity_tiers import TierGap + from github_repo_auditor.maturity_tiers import TierGap truth = _make_portfolio_truth([_bronze_repo("ProxyRepo")]) (output_dir / "portfolio-truth-latest.json").write_text(json.dumps(truth)) @@ -391,7 +391,7 @@ def test_gap_partial_shows_approx_hint_for_proxy_requirement( requirement_sources=["proxy"], ) - with patch("src.maturity_tiers.tier_gap", return_value=fake_gap): + with patch("github_repo_auditor.maturity_tiers.tier_gap", return_value=fake_gap): resp = client.get("/initiatives/ProxyRepo/gap?target=2") assert resp.status_code == 200 @@ -402,7 +402,7 @@ def test_gap_partial_no_approx_hint_for_strict_requirement( self, output_dir: Path, client: TestClient ) -> None: """initiative_gap.html does NOT render (approx.) for strict-sourced requirements.""" - from src.maturity_tiers import TierGap + from github_repo_auditor.maturity_tiers import TierGap truth = _make_portfolio_truth([_bronze_repo("StrictRepo")]) (output_dir / "portfolio-truth-latest.json").write_text(json.dumps(truth)) @@ -415,7 +415,7 @@ def test_gap_partial_no_approx_hint_for_strict_requirement( requirement_sources=["strict"], ) - with patch("src.maturity_tiers.tier_gap", return_value=fake_gap): + with patch("github_repo_auditor.maturity_tiers.tier_gap", return_value=fake_gap): resp = client.get("/initiatives/StrictRepo/gap?target=2") assert resp.status_code == 200 @@ -430,7 +430,7 @@ def test_initiatives_list_proxy_requirement_has_approx_hint( # they load via the HTMX /gap partial. We test the partial directly. # This test verifies the gap partial renders correctly (covers the # requirement_sources field being passed through the route). - from src.maturity_tiers import TierGap + from github_repo_auditor.maturity_tiers import TierGap truth = _make_portfolio_truth([_bronze_repo("HintRepo")]) (output_dir / "portfolio-truth-latest.json").write_text(json.dumps(truth)) @@ -443,7 +443,7 @@ def test_initiatives_list_proxy_requirement_has_approx_hint( requirement_sources=["proxy", "strict"], ) - with patch("src.maturity_tiers.tier_gap", return_value=fake_gap): + with patch("github_repo_auditor.maturity_tiers.tier_gap", return_value=fake_gap): resp = client.get("/initiatives/HintRepo/gap?target=2") assert resp.status_code == 200 @@ -471,7 +471,7 @@ def test_route_passes_cache_key_with_generated_at( We verify the key format rather than cache hit behaviour — the cache semantics are covered in TestSuggestionCache. """ - from src.suggest_initiatives import InitiativeSuggestion + from github_repo_auditor.suggest_initiatives import InitiativeSuggestion truth = _make_portfolio_truth([_bronze_repo("WiredRepo")]) (output_dir / "portfolio-truth-latest.json").write_text(json.dumps(truth)) @@ -493,7 +493,7 @@ def _mock_gen(projects, target_tier=None, budget_usd=0.10, cache_key=None, **kw) received_keys.append(cache_key) return fake_suggestions, 0.001 - with patch("src.suggest_initiatives.generate_suggestions", side_effect=_mock_gen): + with patch("github_repo_auditor.suggest_initiatives.generate_suggestions", side_effect=_mock_gen): resp = client.get("/initiatives/suggestions") assert resp.status_code == 200 @@ -509,7 +509,7 @@ def test_different_generated_at_produces_different_cache_keys( self, output_dir: Path, client: TestClient ) -> None: """Different portfolio-truth generated_at values produce different cache keys.""" - from src.suggest_initiatives import InitiativeSuggestion + from github_repo_auditor.suggest_initiatives import InitiativeSuggestion fake_suggestion = InitiativeSuggestion( repo_name="DiffRepo", @@ -529,7 +529,7 @@ def _mock_gen(projects, target_tier=None, budget_usd=0.10, cache_key=None, **kw) truth_v1 = {"generated_at": "2026-01-01T00:00:00", "projects": [_bronze_repo("DiffRepo")]} truth_v2 = {"generated_at": "2026-02-01T00:00:00", "projects": [_bronze_repo("DiffRepo")]} - with patch("src.suggest_initiatives.generate_suggestions", side_effect=_mock_gen): + with patch("github_repo_auditor.suggest_initiatives.generate_suggestions", side_effect=_mock_gen): (output_dir / "portfolio-truth-latest.json").write_text(json.dumps(truth_v1)) resp1 = client.get("/initiatives/suggestions") @@ -584,7 +584,7 @@ def test_dismiss_then_suggestions_filters_repo( self, output_dir: Path, client: TestClient ) -> None: """After dismissing a repo, GET /initiatives/suggestions excludes it.""" - from src.suggest_initiatives import InitiativeSuggestion + from github_repo_auditor.suggest_initiatives import InitiativeSuggestion truth = _make_portfolio_truth([_bronze_repo("DismissedRepo"), _bronze_repo("KeptRepo")]) (output_dir / "portfolio-truth-latest.json").write_text(json.dumps(truth)) @@ -608,7 +608,7 @@ def test_dismiss_then_suggestions_filters_repo( # Patch generate_suggestions to call the real narrow_candidates so dismissal is applied with patch( - "src.suggest_initiatives.generate_suggestions", + "github_repo_auditor.suggest_initiatives.generate_suggestions", return_value=([fake_kept], 0.001), ): resp_get = client.get("/initiatives/suggestions") diff --git a/tests/test_interest.py b/tests/test_interest.py index cc7ff2ff..825a573c 100644 --- a/tests/test_interest.py +++ b/tests/test_interest.py @@ -2,7 +2,7 @@ import pytest -from src.analyzers.interest import ( +from github_repo_auditor.analyzers.interest import ( InterestAnalyzer, _burst_coefficient, _count_assets, @@ -13,7 +13,7 @@ _score_readme_storytelling, _score_recency, ) -from src.models import RepoMetadata +from github_repo_auditor.models import RepoMetadata def _meta(**overrides) -> RepoMetadata: diff --git a/tests/test_intervention_ledger.py b/tests/test_intervention_ledger.py index 08947a95..f6b89057 100644 --- a/tests/test_intervention_ledger.py +++ b/tests/test_intervention_ledger.py @@ -2,7 +2,7 @@ from pathlib import Path -from src.intervention_ledger import build_intervention_ledger_bundle +from github_repo_auditor.intervention_ledger import build_intervention_ledger_bundle def _queue_item(repo: str, *, lane: str = "ready") -> dict: diff --git a/tests/test_issue_creator.py b/tests/test_issue_creator.py index b2963bdc..859e7d9c 100644 --- a/tests/test_issue_creator.py +++ b/tests/test_issue_creator.py @@ -1,6 +1,6 @@ from unittest.mock import MagicMock -from src.issue_creator import create_audit_issues +from github_repo_auditor.issue_creator import create_audit_issues def _mock_client(existing_issues=None): diff --git a/tests/test_language_trends.py b/tests/test_language_trends.py index acd803a1..8a58ab63 100644 --- a/tests/test_language_trends.py +++ b/tests/test_language_trends.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from src.history import load_language_trends +from github_repo_auditor.history import load_language_trends def _write_report(tmp_path: Path, name: str, audits: list[dict]) -> None: diff --git a/tests/test_llm_cost.py b/tests/test_llm_cost.py index 3ce4071b..711803cd 100644 --- a/tests/test_llm_cost.py +++ b/tests/test_llm_cost.py @@ -1,4 +1,4 @@ -"""Tests for src/llm_cost.py — CostTracker, BudgetExceededError, and provider wiring.""" +"""Tests for src/github_repo_auditor/llm_cost.py — CostTracker, BudgetExceededError, and provider wiring.""" from __future__ import annotations @@ -9,7 +9,7 @@ import pytest -from src.llm_cost import ( +from github_repo_auditor.llm_cost import ( BudgetExceededError, CostTracker, ) @@ -217,7 +217,7 @@ def test_write_telemetry_appends(tmp_path: Path): def test_anthropic_provider_records_cost(): - from src.narrative import AnthropicProvider + from github_repo_auditor.narrative import AnthropicProvider tracker = CostTracker() @@ -258,7 +258,7 @@ def test_anthropic_provider_records_cost(): def test_github_models_provider_records_cost(): - from src.narrative import GitHubModelsProvider + from github_repo_auditor.narrative import GitHubModelsProvider tracker = CostTracker() @@ -291,7 +291,7 @@ def test_github_models_provider_records_cost(): def test_briefing_call_chain_records_feature(): - from src.briefing import _build_suggestions + from github_repo_auditor.briefing import _build_suggestions tracker = CostTracker() @@ -303,7 +303,7 @@ def test_briefing_call_chain_records_feature(): } mock_response.raise_for_status.return_value = None - from src.narrative import GitHubModelsProvider + from github_repo_auditor.narrative import GitHubModelsProvider provider = GitHubModelsProvider(github_token="test-token") diff --git a/tests/test_load_risk_truth.py b/tests/test_load_risk_truth.py index e86a69b3..414473b4 100644 --- a/tests/test_load_risk_truth.py +++ b/tests/test_load_risk_truth.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from src.excel_export_truth_helpers import load_risk_truth +from github_repo_auditor.excel_export_truth_helpers import load_risk_truth def test_load_risk_truth_keys_by_slug_and_display_name(tmp_path: Path) -> None: diff --git a/tests/test_maturity_tiers.py b/tests/test_maturity_tiers.py index e80efa78..3a2cb83c 100644 --- a/tests/test_maturity_tiers.py +++ b/tests/test_maturity_tiers.py @@ -1,4 +1,4 @@ -"""Tests for src/maturity_tiers.py — Arc G Sprint 7A.1 + Sprint 8.3.""" +"""Tests for src/github_repo_auditor/maturity_tiers.py — Arc G Sprint 7A.1 + Sprint 8.3.""" from __future__ import annotations @@ -6,7 +6,7 @@ import pytest -from src.maturity_tiers import ( +from github_repo_auditor.maturity_tiers import ( TIER_DEFINITIONS, TierCriteria, TierGap, diff --git a/tests/test_models.py b/tests/test_models.py index b7efa542..45374402 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -2,7 +2,7 @@ from datetime import datetime, timezone -from src.models import AnalyzerResult, AuditReport, RepoAudit, RepoMetadata +from github_repo_auditor.models import AnalyzerResult, AuditReport, RepoAudit, RepoMetadata class TestRepoMetadata: diff --git a/tests/test_mutation_score_gate.py b/tests/test_mutation_score_gate.py new file mode 100644 index 00000000..ce83a618 --- /dev/null +++ b/tests/test_mutation_score_gate.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +import json +from pathlib import Path + +from scripts.check_mutation_score import classify_exit_codes, evaluate_score, main + + +def _write_meta(root: Path, values: list[int | None]) -> None: + path = root / "src" / "example.py.meta" + path.parent.mkdir(parents=True) + payload = {"exit_code_by_key": {f"mutant_{index}": value for index, value in enumerate(values)}} + path.write_text(json.dumps(payload), encoding="utf-8") + + +def test_evaluate_score_excludes_timeouts() -> None: + counts = classify_exit_codes([1] * 9 + [0, 36]) + + passed, score = evaluate_score(counts, 0.85) + + assert passed is True + assert score == 0.9 + + +def test_main_fails_closed_on_unchecked_results(tmp_path: Path) -> None: + _write_meta(tmp_path, [1, 0, None]) + + assert main(["--mutants-dir", str(tmp_path), "--minimum", "0.5"]) == 1 + + +def test_main_fails_when_no_results_exist(tmp_path: Path) -> None: + assert main(["--mutants-dir", str(tmp_path)]) == 2 + + +def test_main_passes_complete_results_at_threshold(tmp_path: Path) -> None: + _write_meta(tmp_path, [1] * 17 + [0] * 3 + [36]) + + assert main(["--mutants-dir", str(tmp_path), "--minimum", "0.85"]) == 0 diff --git a/tests/test_narrative.py b/tests/test_narrative.py index fe977ede..76f455d8 100644 --- a/tests/test_narrative.py +++ b/tests/test_narrative.py @@ -4,7 +4,7 @@ import pytest -from src.narrative import ( +from github_repo_auditor.narrative import ( AnthropicProvider, GitHubModelsProvider, _build_prompt, @@ -226,7 +226,7 @@ def test_github_models_provider_flag_routes_to_github_models(self, tmp_path, mon def test_cli_parser_accepts_narrative_provider_flag(self): """Parser correctly maps --narrative-provider and --narrative-model.""" - from src.cli import build_parser + from github_repo_auditor.cli import build_parser parser = build_parser() args = parser.parse_args( @@ -238,7 +238,7 @@ def test_cli_parser_accepts_narrative_provider_flag(self): def test_cli_parser_narrative_provider_defaults_to_none(self): """narrative_provider is None by default (auto-detect).""" - from src.cli import build_parser + from github_repo_auditor.cli import build_parser parser = build_parser() args = parser.parse_args(["someuser"]) diff --git a/tests/test_notion_export.py b/tests/test_notion_export.py index 0305a872..34602aba 100644 --- a/tests/test_notion_export.py +++ b/tests/test_notion_export.py @@ -2,7 +2,7 @@ import json -from src.notion_export import ( +from github_repo_auditor.notion_export import ( _build_event_key, _find_biggest_drag, _lookup_project_mapping, diff --git a/tests/test_notion_registry.py b/tests/test_notion_registry.py index c5b2744b..e01ef158 100644 --- a/tests/test_notion_registry.py +++ b/tests/test_notion_registry.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.notion_registry import ( +from github_repo_auditor.notion_registry import ( _extract_first_select, _extract_select, _extract_title, diff --git a/tests/test_notion_sync_extended.py b/tests/test_notion_sync_extended.py index b2f5f5bc..3bdc7778 100644 --- a/tests/test_notion_sync_extended.py +++ b/tests/test_notion_sync_extended.py @@ -2,8 +2,8 @@ from unittest.mock import MagicMock, patch -from src.notion_client import query_notion_collection, query_page_by_title -from src.notion_sync import ( +from github_repo_auditor.notion_client import query_notion_collection, query_page_by_title +from github_repo_auditor.notion_sync import ( ELIGIBLE_TIERS, FLAG_TO_ACTION, _build_weekly_review_section_blocks, @@ -151,7 +151,7 @@ def test_patch_weekly_review_replaces_existing_managed_section(self): append_resp.status_code = 200 with patch( - "src.notion_sync._notion_request", + "github_repo_auditor.notion_sync._notion_request", side_effect=[query_resp, children_resp, archive_resp, archive_resp, archive_resp, append_resp], ) as notion_request: result = patch_weekly_review( @@ -237,7 +237,7 @@ def test_malformed_legacy_payload_fails_soft(self): class TestCampaignActionSync: def test_fails_soft_without_token(self, monkeypatch): - monkeypatch.setattr("src.notion_sync.get_notion_token", lambda: "") + monkeypatch.setattr("github_repo_auditor.notion_sync.get_notion_token", lambda: "") results, refs, drift = sync_campaign_actions([], {"campaign_type": "security-review"}, apply=True) assert results[0]["status"] == "skipped" assert refs == {} @@ -260,7 +260,7 @@ def test_returns_zero_when_no_db_id(self): assert result == {"checked": 0} def test_returns_zero_when_api_fails(self): - with patch("src.notion_sync._notion_request", return_value=None): + with patch("github_repo_auditor.notion_sync._notion_request", return_value=None): result = check_recommendation_followup( self._report(["RepoA"], [0.5]), "token", @@ -273,7 +273,7 @@ def test_returns_zero_when_no_previous_runs(self): mock_resp.status_code = 200 mock_resp.json.return_value = {"results": []} - with patch("src.notion_sync._notion_request", return_value=mock_resp): + with patch("github_repo_auditor.notion_sync._notion_request", return_value=mock_resp): result = check_recommendation_followup( self._report(["RepoA"], [0.5]), "token", @@ -300,7 +300,7 @@ def test_returns_proper_structure_when_blocks_fetched(self): ] } - with patch("src.notion_sync._notion_request", side_effect=[query_resp, blocks_resp]): + with patch("github_repo_auditor.notion_sync._notion_request", side_effect=[query_resp, blocks_resp]): result = check_recommendation_followup( self._report(["RepoA", "RepoB", "RepoC"], [0.6, 0.4, 0.0]), "token", @@ -318,7 +318,7 @@ def test_blocks_api_failure_returns_zero(self): query_resp.status_code = 200 query_resp.json.return_value = {"results": [{"id": "page-abc"}]} - with patch("src.notion_sync._notion_request", side_effect=[query_resp, None]): + with patch("github_repo_auditor.notion_sync._notion_request", side_effect=[query_resp, None]): result = check_recommendation_followup( self._report(["RepoA"], [0.5]), "token", @@ -333,7 +333,7 @@ def test_returns_page_id_when_found(self): mock_resp.status_code = 200 mock_resp.json.return_value = {"results": [{"id": "page-xyz"}]} - with patch("src.notion_client.notion_request", return_value=mock_resp): + with patch("github_repo_auditor.notion_client.notion_request", return_value=mock_resp): result = query_page_by_title("db123", "My Project", "token") assert result == "page-xyz" @@ -343,13 +343,13 @@ def test_returns_none_when_no_results(self): mock_resp.status_code = 200 mock_resp.json.return_value = {"results": []} - with patch("src.notion_client.notion_request", return_value=mock_resp): + with patch("github_repo_auditor.notion_client.notion_request", return_value=mock_resp): result = query_page_by_title("db123", "Nonexistent", "token") assert result is None def test_returns_none_when_api_fails(self): - with patch("src.notion_client.notion_request", return_value=None): + with patch("github_repo_auditor.notion_client.notion_request", return_value=None): result = query_page_by_title("db123", "Any Title", "token") assert result is None @@ -358,7 +358,7 @@ def test_returns_none_on_non_200_status(self): mock_resp = MagicMock() mock_resp.status_code = 404 - with patch("src.notion_client.notion_request", return_value=mock_resp): + with patch("github_repo_auditor.notion_client.notion_request", return_value=mock_resp): result = query_page_by_title("db123", "Any Title", "token") assert result is None @@ -368,7 +368,7 @@ def test_uses_custom_title_property(self): mock_resp.status_code = 200 mock_resp.json.return_value = {"results": [{"id": "page-abc"}]} - with patch("src.notion_client.notion_request", return_value=mock_resp) as mock_req: + with patch("github_repo_auditor.notion_client.notion_request", return_value=mock_resp) as mock_req: query_page_by_title("db123", "My Title", "token", title_property="Title") call_body = mock_req.call_args[0][4] # body is 5th positional arg assert call_body["filter"]["property"] == "Title" @@ -379,7 +379,7 @@ def test_queries_modern_data_source_endpoint_first(self): mock_resp = MagicMock() mock_resp.status_code = 200 - with patch("src.notion_client.notion_request", return_value=mock_resp) as mock_req: + with patch("github_repo_auditor.notion_client.notion_request", return_value=mock_resp) as mock_req: result = query_notion_collection("collection-123", "token", body={"page_size": 1}) assert result is mock_resp @@ -392,7 +392,7 @@ def test_falls_back_to_legacy_database_query_on_404(self): legacy_resp.status_code = 200 with patch( - "src.notion_client.notion_request", + "github_repo_auditor.notion_client.notion_request", side_effect=[missing_resp, legacy_resp], ) as mock_req: result = query_notion_collection("database-123", "token") diff --git a/tests/test_notion_sync_integrity.py b/tests/test_notion_sync_integrity.py index 0dcaa337..c5186b55 100644 --- a/tests/test_notion_sync_integrity.py +++ b/tests/test_notion_sync_integrity.py @@ -4,7 +4,7 @@ import pytest -from src import notion_sync +from github_repo_auditor import notion_sync @dataclass diff --git a/tests/test_notion_twoway.py b/tests/test_notion_twoway.py index 6091797e..a5a81bd1 100644 --- a/tests/test_notion_twoway.py +++ b/tests/test_notion_twoway.py @@ -1,8 +1,8 @@ from __future__ import annotations -from src.notion_dashboard import _bullet_block, _divider_block, _heading_block, _paragraph_block -from src.notion_registry import _extract_rich_text -from src.notion_sync import _render_audit_highlights, check_recommendation_followup +from github_repo_auditor.notion_dashboard import _bullet_block, _divider_block, _heading_block, _paragraph_block +from github_repo_auditor.notion_registry import _extract_rich_text +from github_repo_auditor.notion_sync import _render_audit_highlights, check_recommendation_followup class TestExtractRichText: diff --git a/tests/test_operator_acknowledgments.py b/tests/test_operator_acknowledgments.py index 023b7804..b3a54cf3 100644 --- a/tests/test_operator_acknowledgments.py +++ b/tests/test_operator_acknowledgments.py @@ -2,7 +2,7 @@ import json -from src.operator_acknowledgments import ( +from github_repo_auditor.operator_acknowledgments import ( acknowledgments_path, build_acknowledgment_record, directional_signature, diff --git a/tests/test_operator_control_center.py b/tests/test_operator_control_center.py index bda911df..5e0702a6 100644 --- a/tests/test_operator_control_center.py +++ b/tests/test_operator_control_center.py @@ -3,11 +3,11 @@ import json from pathlib import Path -import src.operator_control_center as operator_control_center -import src.operator_follow_through as operator_follow_through -import src.operator_resolution_trend as operator_resolution_trend -import src.operator_snapshot_packaging as operator_snapshot_packaging -import src.operator_trend_closure_forecast_reset_controls as reset_controls +import github_repo_auditor.operator_control_center as operator_control_center +import github_repo_auditor.operator_follow_through as operator_follow_through +import github_repo_auditor.operator_resolution_trend as operator_resolution_trend +import github_repo_auditor.operator_snapshot_packaging as operator_snapshot_packaging +import github_repo_auditor.operator_trend_closure_forecast_reset_controls as reset_controls build_operator_snapshot = operator_control_center.build_operator_snapshot normalize_review_state = operator_control_center.normalize_review_state @@ -1354,7 +1354,7 @@ def test_operator_snapshot_adds_follow_through_from_recent_history( tmp_path: Path, monkeypatch ): monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "operator_summary": { @@ -2310,7 +2310,7 @@ def test_operator_snapshot_marks_quiet_recovery_as_improving( tmp_path: Path, monkeypatch ): monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "operator_summary": { @@ -2357,7 +2357,7 @@ def test_operator_snapshot_marks_quiet_recovery_as_improving( def test_operator_snapshot_tracks_reopened_attention_items(tmp_path: Path, monkeypatch): monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "operator_summary": { @@ -2404,7 +2404,7 @@ def test_operator_snapshot_prefers_reopened_urgent_over_fresh_urgent( tmp_path: Path, monkeypatch ): monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "operator_summary": { @@ -2455,7 +2455,7 @@ def test_operator_snapshot_marks_chronic_targets_and_longest_persisting_item( tmp_path: Path, monkeypatch ): monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "operator_summary": { @@ -2541,7 +2541,7 @@ def test_operator_snapshot_marks_attempted_when_recent_intervention_exists( tmp_path: Path, monkeypatch ): monkeypatch.setattr( - "src.operator_control_center.load_recent_operator_evidence", + "github_repo_auditor.operator_control_center.load_recent_operator_evidence", lambda *_args, **_kwargs: { "history": [ { @@ -2596,7 +2596,7 @@ def test_operator_snapshot_tracks_confirmed_resolution_and_reopen_evidence( tmp_path: Path, monkeypatch ): monkeypatch.setattr( - "src.operator_control_center.load_recent_operator_evidence", + "github_repo_auditor.operator_control_center.load_recent_operator_evidence", lambda *_args, **_kwargs: { "history": [ { @@ -2653,7 +2653,7 @@ def test_operator_snapshot_tracks_confirmed_resolution_and_reopen_evidence( assert "confirmed resolved" in quiet_summary["resolution_evidence_summary"].lower() monkeypatch.setattr( - "src.operator_control_center.load_recent_operator_evidence", + "github_repo_auditor.operator_control_center.load_recent_operator_evidence", lambda *_args, **_kwargs: { "history": [ { @@ -2794,7 +2794,7 @@ def test_operator_snapshot_calibrates_healthy_confidence_history( tmp_path: Path, monkeypatch ): monkeypatch.setattr( - "src.operator_control_center.load_operator_calibration_history", + "github_repo_auditor.operator_control_center.load_operator_calibration_history", lambda *_args, **_kwargs: [ { "run_id": "run-6", @@ -2909,7 +2909,7 @@ def test_operator_snapshot_marks_noisy_calibration_when_reopens_repeat( tmp_path: Path, monkeypatch ): monkeypatch.setattr( - "src.operator_control_center.load_operator_calibration_history", + "github_repo_auditor.operator_control_center.load_operator_calibration_history", lambda *_args, **_kwargs: [ { "run_id": "run-6", @@ -3038,7 +3038,7 @@ def test_operator_snapshot_tracks_partially_validated_recommendations( tmp_path: Path, monkeypatch ): monkeypatch.setattr( - "src.operator_control_center.load_operator_calibration_history", + "github_repo_auditor.operator_control_center.load_operator_calibration_history", lambda *_args, **_kwargs: [ { "run_id": "run-5", @@ -3177,12 +3177,12 @@ def test_operator_snapshot_healthy_calibration_boosts_urgent_confidence( rollback_preview={}, ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [], ) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "healthy", "confidence_window_runs": 8, @@ -3201,7 +3201,7 @@ def test_operator_snapshot_healthy_calibration_boosts_urgent_confidence( healthy_snapshot = build_operator_snapshot(report, output_dir=tmp_path) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "mixed", "confidence_window_runs": 8, @@ -3252,7 +3252,7 @@ def test_operator_snapshot_uses_verify_first_for_noisy_reopened_targets( rollback_preview={}, ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "operator_summary": { @@ -3277,7 +3277,7 @@ def test_operator_snapshot_uses_verify_first_for_noisy_reopened_targets( ], ) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "noisy", "confidence_window_runs": 8, @@ -3294,7 +3294,7 @@ def test_operator_snapshot_uses_verify_first_for_noisy_reopened_targets( }, ) monkeypatch.setattr( - "src.operator_resolution_trend._was_resolved_then_reopened", + "github_repo_auditor.operator_resolution_trend._was_resolved_then_reopened", lambda *_args, **_kwargs: True, ) @@ -3328,7 +3328,7 @@ def test_operator_snapshot_softens_for_policy_flip_churn(tmp_path: Path, monkeyp rollback_preview={}, ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "generated_at": "2026-04-06T12:00:00+00:00", @@ -3417,7 +3417,7 @@ def test_operator_snapshot_softens_for_policy_flip_churn(tmp_path: Path, monkeyp ], ) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "healthy", "confidence_window_runs": 8, @@ -3434,7 +3434,7 @@ def test_operator_snapshot_softens_for_policy_flip_churn(tmp_path: Path, monkeyp }, ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_policy_exception_for_target", + "github_repo_auditor.operator_resolution_trend._trust_policy_exception_for_target", lambda target, *_args, **_kwargs: ( "softened-for-flip-churn", "Recent trust-policy flips have been bouncing enough that this recommendation should not be treated as fully stable yet.", @@ -3443,7 +3443,7 @@ def test_operator_snapshot_softens_for_policy_flip_churn(tmp_path: Path, monkeyp ), ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_recovery_for_target", + "github_repo_auditor.operator_resolution_trend._trust_recovery_for_target", lambda target, *_args, **_kwargs: ( "candidate", "This target is stabilizing under healthy calibration, but it has not held steady long enough to earn stronger trust yet.", @@ -3466,7 +3466,7 @@ def test_operator_snapshot_never_softens_blocked_setup_below_act_with_review( ): report = _make_report() monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "generated_at": "2026-04-06T12:00:00+00:00", @@ -3554,7 +3554,7 @@ def test_operator_snapshot_never_softens_blocked_setup_below_act_with_review( ], ) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "noisy", "confidence_window_runs": 8, @@ -3571,7 +3571,7 @@ def test_operator_snapshot_never_softens_blocked_setup_below_act_with_review( }, ) monkeypatch.setattr( - "src.operator_resolution_trend._was_resolved_then_reopened", + "github_repo_auditor.operator_resolution_trend._was_resolved_then_reopened", lambda *_args, **_kwargs: True, ) @@ -3614,7 +3614,7 @@ def test_operator_snapshot_recovers_stable_verify_first_target( ], ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "generated_at": "2026-04-06T12:00:00+00:00", @@ -3670,7 +3670,7 @@ def test_operator_snapshot_recovers_stable_verify_first_target( ], ) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "healthy", "confidence_window_runs": 8, @@ -3687,7 +3687,7 @@ def test_operator_snapshot_recovers_stable_verify_first_target( }, ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_policy_exception_for_target", + "github_repo_auditor.operator_resolution_trend._trust_policy_exception_for_target", lambda target, *_args, **_kwargs: ( "softened-for-flip-churn", "Recent trust-policy flips have been bouncing enough that this recommendation should not be treated as fully stable yet.", @@ -3735,7 +3735,7 @@ def test_operator_snapshot_retires_exception_after_stable_window( ], ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "generated_at": "2026-04-06T12:00:00+00:00", @@ -3791,7 +3791,7 @@ def test_operator_snapshot_retires_exception_after_stable_window( ], ) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "healthy", "confidence_window_runs": 8, @@ -3808,7 +3808,7 @@ def test_operator_snapshot_retires_exception_after_stable_window( }, ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_policy_exception_for_target", + "github_repo_auditor.operator_resolution_trend._trust_policy_exception_for_target", lambda target, *_args, **_kwargs: ( "softened-for-flip-churn", "Recent trust-policy flips have been bouncing enough that this recommendation should not be treated as fully stable yet.", @@ -3854,7 +3854,7 @@ def test_operator_snapshot_applies_class_level_normalization_for_healthy_class( ], ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "generated_at": "2026-04-06T12:00:00+00:00", @@ -3922,7 +3922,7 @@ def test_operator_snapshot_applies_class_level_normalization_for_healthy_class( ], ) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "healthy", "confidence_window_runs": 8, @@ -3939,7 +3939,7 @@ def test_operator_snapshot_applies_class_level_normalization_for_healthy_class( }, ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_policy_exception_for_target", + "github_repo_auditor.operator_resolution_trend._trust_policy_exception_for_target", lambda target, *_args, **_kwargs: ( "softened-for-flip-churn", "Recent trust-policy flips have been bouncing enough that this recommendation should not be treated as fully stable yet.", @@ -3985,7 +3985,7 @@ def test_operator_snapshot_keeps_one_off_noise_from_class_normalization( ], ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "generated_at": "2026-04-06T12:00:00+00:00", @@ -4053,7 +4053,7 @@ def test_operator_snapshot_keeps_one_off_noise_from_class_normalization( ], ) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "healthy", "confidence_window_runs": 8, @@ -4070,7 +4070,7 @@ def test_operator_snapshot_keeps_one_off_noise_from_class_normalization( }, ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_policy_exception_for_target", + "github_repo_auditor.operator_resolution_trend._trust_policy_exception_for_target", lambda target, *_args, **_kwargs: ( "softened-for-reopen-risk", "Recent reopen or unresolved behavior softened the recommendation, so confirm closure evidence before overcommitting.", @@ -4079,7 +4079,7 @@ def test_operator_snapshot_keeps_one_off_noise_from_class_normalization( ), ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_recovery_for_target", + "github_repo_auditor.operator_resolution_trend._trust_recovery_for_target", lambda target, *_args, **_kwargs: ( "blocked", "Trust recovery is blocked because this target reopened again inside the recent recovery window.", @@ -4172,11 +4172,11 @@ def test_operator_snapshot_decays_stale_class_normalization( } ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: history, ) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "healthy", "confidence_window_runs": 8, @@ -4193,7 +4193,7 @@ def test_operator_snapshot_decays_stale_class_normalization( }, ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_policy_exception_for_target", + "github_repo_auditor.operator_resolution_trend._trust_policy_exception_for_target", lambda target, *_args, **_kwargs: ( "softened-for-flip-churn", "Recent trust-policy flips have been bouncing enough that this recommendation should not be treated as fully stable yet.", @@ -4202,7 +4202,7 @@ def test_operator_snapshot_decays_stale_class_normalization( ), ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_recovery_for_target", + "github_repo_auditor.operator_resolution_trend._trust_recovery_for_target", lambda target, *_args, **_kwargs: ( "candidate", "This target is stabilizing under healthy calibration, but it has not held steady long enough to earn stronger trust yet.", @@ -4285,11 +4285,11 @@ def test_operator_snapshot_softens_class_debt_when_fresh_sticky_signal_ages_out( } ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: history, ) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "healthy", "confidence_window_runs": 8, @@ -4306,7 +4306,7 @@ def test_operator_snapshot_softens_class_debt_when_fresh_sticky_signal_ages_out( }, ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_policy_exception_for_target", + "github_repo_auditor.operator_resolution_trend._trust_policy_exception_for_target", lambda target, *_args, **_kwargs: ( "softened-for-flip-churn", "Recent trust-policy flips have been bouncing enough that this recommendation should not be treated as fully stable yet.", @@ -4384,11 +4384,11 @@ def test_operator_snapshot_boosts_candidate_normalization_when_fresh_support_cro } ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: history, ) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "healthy", "confidence_window_runs": 8, @@ -4405,7 +4405,7 @@ def test_operator_snapshot_boosts_candidate_normalization_when_fresh_support_cro }, ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_policy_exception_for_target", + "github_repo_auditor.operator_resolution_trend._trust_policy_exception_for_target", lambda target, *_args, **_kwargs: ( "softened-for-flip-churn", "Recent trust-policy flips have been bouncing enough that this recommendation should not be treated as fully stable yet.", @@ -4414,7 +4414,7 @@ def test_operator_snapshot_boosts_candidate_normalization_when_fresh_support_cro ), ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_recovery_for_target", + "github_repo_auditor.operator_resolution_trend._trust_recovery_for_target", lambda target, *_args, **_kwargs: ( "candidate", "This target is stabilizing under healthy calibration, but it has not held steady long enough to earn stronger trust yet.", @@ -4423,7 +4423,7 @@ def test_operator_snapshot_boosts_candidate_normalization_when_fresh_support_cro ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_normalization_for_target", + "github_repo_auditor.operator_resolution_trend._class_normalization_for_target", lambda target, *_args, **_kwargs: ( "candidate", "This class is trending healthier, but the current target has not earned class-level normalization yet.", @@ -4432,7 +4432,7 @@ def test_operator_snapshot_boosts_candidate_normalization_when_fresh_support_cro ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_memory_decay_for_target", + "github_repo_auditor.operator_resolution_trend._class_memory_decay_for_target", lambda target, _history_meta, _calibration, **kwargs: ( "none", "", @@ -4524,11 +4524,11 @@ def test_operator_snapshot_strengthens_watch_into_class_debt_when_fresh_caution_ } ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: history, ) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "healthy", "confidence_window_runs": 8, @@ -4545,7 +4545,7 @@ def test_operator_snapshot_strengthens_watch_into_class_debt_when_fresh_caution_ }, ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_policy_exception_for_target", + "github_repo_auditor.operator_resolution_trend._trust_policy_exception_for_target", lambda target, *_args, **_kwargs: ( "softened-for-reopen-risk", "Recent reopen or unresolved behavior softened the recommendation, so confirm closure evidence before overcommitting.", @@ -4554,7 +4554,7 @@ def test_operator_snapshot_strengthens_watch_into_class_debt_when_fresh_caution_ ), ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_recovery_for_target", + "github_repo_auditor.operator_resolution_trend._trust_recovery_for_target", lambda target, *_args, **_kwargs: ( "blocked", "Trust recovery is blocked because this target reopened again inside the recent recovery window.", @@ -4563,14 +4563,14 @@ def test_operator_snapshot_strengthens_watch_into_class_debt_when_fresh_caution_ ), ) monkeypatch.setattr( - "src.operator_resolution_trend._policy_debt_for_target", + "github_repo_auditor.operator_resolution_trend._policy_debt_for_target", lambda target, _history_meta: ( "watch", "This class has enough recent exception activity to watch for lingering caution, but it is not yet clearly sticky or clearly normalization-friendly.", ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_memory_decay_for_target", + "github_repo_auditor.operator_resolution_trend._class_memory_decay_for_target", lambda target, _history_meta, _calibration, **kwargs: ( "none", "", @@ -4632,11 +4632,11 @@ def test_operator_snapshot_holds_class_normalization_pending_until_support_persi ) history = [] monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: history, ) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "healthy", "confidence_window_runs": 8, @@ -4653,7 +4653,7 @@ def test_operator_snapshot_holds_class_normalization_pending_until_support_persi }, ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_policy_exception_for_target", + "github_repo_auditor.operator_resolution_trend._trust_policy_exception_for_target", lambda target, *_args, **_kwargs: ( "softened-for-flip-churn", "Recent trust-policy flips have been bouncing enough that this recommendation should not be treated as fully stable yet.", @@ -4662,7 +4662,7 @@ def test_operator_snapshot_holds_class_normalization_pending_until_support_persi ), ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_recovery_for_target", + "github_repo_auditor.operator_resolution_trend._trust_recovery_for_target", lambda target, *_args, **_kwargs: ( "candidate", "This target is stabilizing under healthy calibration, but it has not held steady long enough to earn stronger trust yet.", @@ -4671,7 +4671,7 @@ def test_operator_snapshot_holds_class_normalization_pending_until_support_persi ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_memory_decay_for_target", + "github_repo_auditor.operator_resolution_trend._class_memory_decay_for_target", lambda target, _history_meta, _calibration, **kwargs: ( "none", "", @@ -4684,7 +4684,7 @@ def test_operator_snapshot_holds_class_normalization_pending_until_support_persi ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_trust_reweight_for_target", + "github_repo_auditor.operator_resolution_trend._class_trust_reweight_for_target", lambda target, _history_meta, _calibration, **kwargs: ( "normalization-boosted", "Fresh class support crossed the reweight threshold, so this target inherits a stronger act-with-review posture.", @@ -4760,11 +4760,11 @@ def test_operator_snapshot_marks_flat_pending_support_as_holding_then_stalled( } ] monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: history, ) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "healthy", "confidence_window_runs": 8, @@ -4781,7 +4781,7 @@ def test_operator_snapshot_marks_flat_pending_support_as_holding_then_stalled( }, ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_policy_exception_for_target", + "github_repo_auditor.operator_resolution_trend._trust_policy_exception_for_target", lambda target, *_args, **_kwargs: ( "softened-for-flip-churn", "Recent trust-policy flips have been bouncing enough that this recommendation should not be treated as fully stable yet.", @@ -4790,7 +4790,7 @@ def test_operator_snapshot_marks_flat_pending_support_as_holding_then_stalled( ), ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_recovery_for_target", + "github_repo_auditor.operator_resolution_trend._trust_recovery_for_target", lambda target, *_args, **_kwargs: ( "candidate", "This target is stabilizing under healthy calibration, but it has not held steady long enough to earn stronger trust yet.", @@ -4799,7 +4799,7 @@ def test_operator_snapshot_marks_flat_pending_support_as_holding_then_stalled( ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_memory_decay_for_target", + "github_repo_auditor.operator_resolution_trend._class_memory_decay_for_target", lambda target, _history_meta, _calibration, **kwargs: ( "none", "", @@ -4812,7 +4812,7 @@ def test_operator_snapshot_marks_flat_pending_support_as_holding_then_stalled( ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_trust_reweight_for_target", + "github_repo_auditor.operator_resolution_trend._class_trust_reweight_for_target", lambda target, _history_meta, _calibration, **kwargs: ( "normalization-boosted", "Fresh class support crossed the reweight threshold, so this target inherits a stronger act-with-review posture.", @@ -4825,7 +4825,7 @@ def test_operator_snapshot_marks_flat_pending_support_as_holding_then_stalled( ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_trust_reweight_scores_for_target", + "github_repo_auditor.operator_resolution_trend._class_trust_reweight_scores_for_target", lambda target, _history_meta: ( 0.48, 0.24, @@ -4835,7 +4835,7 @@ def test_operator_snapshot_marks_flat_pending_support_as_holding_then_stalled( ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_trust_momentum_for_target", + "github_repo_auditor.operator_resolution_trend._class_trust_momentum_for_target", lambda target, _history_meta, _calibration, **kwargs: ( "pending-support", "The class signal is visible, but it has not stayed strong long enough to confirm broader normalization yet.", @@ -4936,11 +4936,11 @@ def test_operator_snapshot_expires_old_pending_support_when_signal_fades( } ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: history, ) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "healthy", "confidence_window_runs": 8, @@ -4957,7 +4957,7 @@ def test_operator_snapshot_expires_old_pending_support_when_signal_fades( }, ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_policy_exception_for_target", + "github_repo_auditor.operator_resolution_trend._trust_policy_exception_for_target", lambda target, *_args, **_kwargs: ( "softened-for-flip-churn", "Recent trust-policy flips have been bouncing enough that this recommendation should not be treated as fully stable yet.", @@ -4966,7 +4966,7 @@ def test_operator_snapshot_expires_old_pending_support_when_signal_fades( ), ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_recovery_for_target", + "github_repo_auditor.operator_resolution_trend._trust_recovery_for_target", lambda target, *_args, **_kwargs: ( "candidate", "This target is stabilizing under healthy calibration, but it has not held steady long enough to earn stronger trust yet.", @@ -4975,7 +4975,7 @@ def test_operator_snapshot_expires_old_pending_support_when_signal_fades( ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_memory_decay_for_target", + "github_repo_auditor.operator_resolution_trend._class_memory_decay_for_target", lambda target, _history_meta, _calibration, **kwargs: ( "none", "", @@ -4988,11 +4988,11 @@ def test_operator_snapshot_expires_old_pending_support_when_signal_fades( ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_trust_reweight_scores_for_target", + "github_repo_auditor.operator_resolution_trend._class_trust_reweight_scores_for_target", lambda target, _history_meta: (0.05, 0.02, 0.03, "neutral", []), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_trust_reweight_for_target", + "github_repo_auditor.operator_resolution_trend._class_trust_reweight_for_target", lambda target, _history_meta, _calibration, **kwargs: ( "none", "", @@ -5005,7 +5005,7 @@ def test_operator_snapshot_expires_old_pending_support_when_signal_fades( ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_trust_momentum_for_target", + "github_repo_auditor.operator_resolution_trend._class_trust_momentum_for_target", lambda target, _history_meta, _calibration, **kwargs: ( "none", "", @@ -5054,11 +5054,11 @@ def test_operator_snapshot_marks_blocked_pending_support_when_local_noise_overri ], ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [], ) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "healthy", "confidence_window_runs": 8, @@ -5075,7 +5075,7 @@ def test_operator_snapshot_marks_blocked_pending_support_when_local_noise_overri }, ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_policy_exception_for_target", + "github_repo_auditor.operator_resolution_trend._trust_policy_exception_for_target", lambda target, *_args, **_kwargs: ( "softened-for-reopen-risk", "Recent reopen or unresolved behavior softened the recommendation, so confirm closure evidence before overcommitting.", @@ -5084,7 +5084,7 @@ def test_operator_snapshot_marks_blocked_pending_support_when_local_noise_overri ), ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_recovery_for_target", + "github_repo_auditor.operator_resolution_trend._trust_recovery_for_target", lambda target, *_args, **_kwargs: ( "blocked", "Trust recovery is blocked because this target reopened again inside the recent recovery window.", @@ -5093,7 +5093,7 @@ def test_operator_snapshot_marks_blocked_pending_support_when_local_noise_overri ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_memory_decay_for_target", + "github_repo_auditor.operator_resolution_trend._class_memory_decay_for_target", lambda target, _history_meta, _calibration, **kwargs: ( "none", "", @@ -5106,7 +5106,7 @@ def test_operator_snapshot_marks_blocked_pending_support_when_local_noise_overri ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_trust_reweight_for_target", + "github_repo_auditor.operator_resolution_trend._class_trust_reweight_for_target", lambda target, _history_meta, _calibration, **kwargs: ( "normalization-boosted", "Fresh class support crossed the reweight threshold, so this target inherits a stronger act-with-review posture.", @@ -5119,7 +5119,7 @@ def test_operator_snapshot_marks_blocked_pending_support_when_local_noise_overri ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_trust_momentum_for_target", + "github_repo_auditor.operator_resolution_trend._class_trust_momentum_for_target", lambda target, _history_meta, _calibration, **kwargs: ( "blocked", "Positive class strengthening is blocked because local reopen, flip, or blocked-recovery noise still overrides the class signal.", @@ -5192,11 +5192,11 @@ def test_operator_snapshot_scores_pending_support_as_confirm_soon_without_auto_c } ] monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: history, ) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "healthy", "confidence_window_runs": 8, @@ -5213,7 +5213,7 @@ def test_operator_snapshot_scores_pending_support_as_confirm_soon_without_auto_c }, ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_policy_exception_for_target", + "github_repo_auditor.operator_resolution_trend._trust_policy_exception_for_target", lambda target, *_args, **_kwargs: ( "softened-for-flip-churn", "Recent trust-policy flips have been bouncing enough that this recommendation should not be treated as fully stable yet.", @@ -5222,7 +5222,7 @@ def test_operator_snapshot_scores_pending_support_as_confirm_soon_without_auto_c ), ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_recovery_for_target", + "github_repo_auditor.operator_resolution_trend._trust_recovery_for_target", lambda target, *_args, **_kwargs: ( "candidate", "This target is stabilizing under healthy calibration, but it has not held steady long enough to earn stronger trust yet.", @@ -5231,7 +5231,7 @@ def test_operator_snapshot_scores_pending_support_as_confirm_soon_without_auto_c ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_memory_decay_for_target", + "github_repo_auditor.operator_resolution_trend._class_memory_decay_for_target", lambda target, _history_meta, _calibration, **kwargs: ( "none", "", @@ -5244,7 +5244,7 @@ def test_operator_snapshot_scores_pending_support_as_confirm_soon_without_auto_c ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_trust_reweight_scores_for_target", + "github_repo_auditor.operator_resolution_trend._class_trust_reweight_scores_for_target", lambda target, _history_meta: ( 0.55, 0.20, @@ -5254,7 +5254,7 @@ def test_operator_snapshot_scores_pending_support_as_confirm_soon_without_auto_c ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_trust_reweight_for_target", + "github_repo_auditor.operator_resolution_trend._class_trust_reweight_for_target", lambda target, _history_meta, _calibration, **kwargs: ( "normalization-boosted", "Fresh class support crossed the reweight threshold, so this target inherits a stronger act-with-review posture.", @@ -5267,7 +5267,7 @@ def test_operator_snapshot_scores_pending_support_as_confirm_soon_without_auto_c ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_trust_momentum_for_target", + "github_repo_auditor.operator_resolution_trend._class_trust_momentum_for_target", lambda target, _history_meta, _calibration, **kwargs: ( "pending-support", "The class signal is visible, but it has not stayed strong long enough to confirm broader normalization yet.", @@ -5409,11 +5409,11 @@ def test_operator_snapshot_clears_low_confidence_pending_support_with_active_pen }, ] monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: history, ) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "healthy", "confidence_window_runs": 8, @@ -5430,7 +5430,7 @@ def test_operator_snapshot_clears_low_confidence_pending_support_with_active_pen }, ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_policy_exception_for_target", + "github_repo_auditor.operator_resolution_trend._trust_policy_exception_for_target", lambda target, *_args, **_kwargs: ( "softened-for-flip-churn", "Recent trust-policy flips have been bouncing enough that this recommendation should not be treated as fully stable yet.", @@ -5439,7 +5439,7 @@ def test_operator_snapshot_clears_low_confidence_pending_support_with_active_pen ), ) monkeypatch.setattr( - "src.operator_resolution_trend._trust_recovery_for_target", + "github_repo_auditor.operator_resolution_trend._trust_recovery_for_target", lambda target, *_args, **_kwargs: ( "candidate", "This target is stabilizing under healthy calibration, but it has not held steady long enough to earn stronger trust yet.", @@ -5448,7 +5448,7 @@ def test_operator_snapshot_clears_low_confidence_pending_support_with_active_pen ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_memory_decay_for_target", + "github_repo_auditor.operator_resolution_trend._class_memory_decay_for_target", lambda target, _history_meta, _calibration, **kwargs: ( "none", "", @@ -5461,11 +5461,11 @@ def test_operator_snapshot_clears_low_confidence_pending_support_with_active_pen ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_trust_reweight_scores_for_target", + "github_repo_auditor.operator_resolution_trend._class_trust_reweight_scores_for_target", lambda target, _history_meta: (0.08, 0.03, 0.05, "neutral", []), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_trust_reweight_for_target", + "github_repo_auditor.operator_resolution_trend._class_trust_reweight_for_target", lambda target, _history_meta, _calibration, **kwargs: ( "none", "", @@ -5478,7 +5478,7 @@ def test_operator_snapshot_clears_low_confidence_pending_support_with_active_pen ), ) monkeypatch.setattr( - "src.operator_resolution_trend._class_trust_momentum_for_target", + "github_repo_auditor.operator_resolution_trend._class_trust_momentum_for_target", lambda target, _history_meta, _calibration, **kwargs: ( "pending-support", "The class signal is visible, but it has not stayed strong long enough to confirm broader normalization yet.", @@ -5575,7 +5575,7 @@ def test_operator_snapshot_marks_class_pending_debt_as_clearing( }, ] monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: history, ) @@ -5688,7 +5688,7 @@ def test_operator_snapshot_reacquires_confirmation_forecast_after_decay( }, ] monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: history, ) @@ -5734,17 +5734,17 @@ def _phase43_seed( } monkeypatch.setattr( - "src.operator_resolution_trend._apply_closure_forecast_freshness_and_decay", + "github_repo_auditor.operator_resolution_trend._apply_closure_forecast_freshness_and_decay", _phase43_seed, ) monkeypatch.setattr( - "src.operator_resolution_trend._target_specific_normalization_noise", + "github_repo_auditor.operator_resolution_trend._target_specific_normalization_noise", lambda *_args, **_kwargs: False, ) # the satellite reads its own import of target_specific_normalization_noise, # not the god module's reference, so patch it there too monkeypatch.setattr( - "src.operator_trend_closure_forecast_reacquisition_controls.target_specific_normalization_noise", + "github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls.target_specific_normalization_noise", lambda *_args, **_kwargs: False, ) @@ -5864,7 +5864,7 @@ def test_operator_snapshot_reenables_early_clear_when_clearance_is_reacquired( }, ] monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: history, ) @@ -5914,7 +5914,7 @@ def _phase43_seed( } monkeypatch.setattr( - "src.operator_resolution_trend._apply_closure_forecast_freshness_and_decay", + "github_repo_auditor.operator_resolution_trend._apply_closure_forecast_freshness_and_decay", _phase43_seed, ) @@ -5964,7 +5964,7 @@ def test_operator_snapshot_marks_new_confirmation_reacquisition_as_fragile( ], ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [], ) @@ -6012,11 +6012,11 @@ def _phase44_seed( } monkeypatch.setattr( - "src.operator_resolution_trend._apply_closure_forecast_refresh_recovery_and_reacquisition", + "github_repo_auditor.operator_resolution_trend._apply_closure_forecast_refresh_recovery_and_reacquisition", _phase44_seed, ) monkeypatch.setattr( - "src.operator_resolution_trend._target_specific_normalization_noise", + "github_repo_auditor.operator_resolution_trend._target_specific_normalization_noise", lambda *_args, **_kwargs: False, ) @@ -6104,7 +6104,7 @@ def test_operator_snapshot_keeps_confirmation_reacquisition_when_it_is_holding( }, ] monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: history, ) @@ -6152,17 +6152,17 @@ def _phase44_seed( } monkeypatch.setattr( - "src.operator_resolution_trend._apply_closure_forecast_refresh_recovery_and_reacquisition", + "github_repo_auditor.operator_resolution_trend._apply_closure_forecast_refresh_recovery_and_reacquisition", _phase44_seed, ) monkeypatch.setattr( - "src.operator_resolution_trend._target_specific_normalization_noise", + "github_repo_auditor.operator_resolution_trend._target_specific_normalization_noise", lambda *_args, **_kwargs: False, ) # the satellite reads its own import of target_specific_normalization_noise, # not the god module's reference, so patch it there too monkeypatch.setattr( - "src.operator_trend_closure_forecast_reacquisition_controls.target_specific_normalization_noise", + "github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls.target_specific_normalization_noise", lambda *_args, **_kwargs: False, ) @@ -6254,7 +6254,7 @@ def test_operator_snapshot_softens_reacquired_clearance_when_recovery_churns( }, ] monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: history, ) @@ -6302,17 +6302,17 @@ def _phase44_seed( } monkeypatch.setattr( - "src.operator_resolution_trend._apply_closure_forecast_refresh_recovery_and_reacquisition", + "github_repo_auditor.operator_resolution_trend._apply_closure_forecast_refresh_recovery_and_reacquisition", _phase44_seed, ) monkeypatch.setattr( - "src.operator_resolution_trend._target_specific_normalization_noise", + "github_repo_auditor.operator_resolution_trend._target_specific_normalization_noise", lambda *_args, **_kwargs: False, ) # the satellite reads its own import of target_specific_normalization_noise, # not the god module's reference, so patch it there too monkeypatch.setattr( - "src.operator_trend_closure_forecast_reacquisition_controls.target_specific_normalization_noise", + "github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls.target_specific_normalization_noise", lambda *_args, **_kwargs: False, ) @@ -6432,7 +6432,7 @@ def test_operator_snapshot_softens_sustained_reacquisition_when_freshness_turns_ } ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: history, ) @@ -6480,17 +6480,17 @@ def _phase44_seed( } monkeypatch.setattr( - "src.operator_resolution_trend._apply_closure_forecast_refresh_recovery_and_reacquisition", + "github_repo_auditor.operator_resolution_trend._apply_closure_forecast_refresh_recovery_and_reacquisition", _phase44_seed, ) monkeypatch.setattr( - "src.operator_resolution_trend._target_specific_normalization_noise", + "github_repo_auditor.operator_resolution_trend._target_specific_normalization_noise", lambda *_args, **_kwargs: False, ) # the satellite reads its own import of target_specific_normalization_noise, # not the god module's reference, so patch it there too monkeypatch.setattr( - "src.operator_trend_closure_forecast_reacquisition_controls.target_specific_normalization_noise", + "github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls.target_specific_normalization_noise", lambda *_args, **_kwargs: False, ) @@ -6639,7 +6639,7 @@ def test_operator_snapshot_resets_stale_reacquired_clearance_and_restores_pendin } ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: history, ) @@ -6687,17 +6687,17 @@ def _phase44_seed( } monkeypatch.setattr( - "src.operator_resolution_trend._apply_closure_forecast_refresh_recovery_and_reacquisition", + "github_repo_auditor.operator_resolution_trend._apply_closure_forecast_refresh_recovery_and_reacquisition", _phase44_seed, ) monkeypatch.setattr( - "src.operator_resolution_trend._target_specific_normalization_noise", + "github_repo_auditor.operator_resolution_trend._target_specific_normalization_noise", lambda *_args, **_kwargs: False, ) # the satellite reads its own import of target_specific_normalization_noise, # not the god module's reference, so patch it there too monkeypatch.setattr( - "src.operator_trend_closure_forecast_reacquisition_controls.target_specific_normalization_noise", + "github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls.target_specific_normalization_noise", lambda *_args, **_kwargs: False, ) @@ -6752,7 +6752,7 @@ def test_operator_snapshot_sets_pending_confirmation_reentry_after_confirmation_ ], ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "generated_at": "2026-04-06T12:00:00+00:00", @@ -6822,11 +6822,11 @@ def _phase46_seed( } monkeypatch.setattr( - "src.operator_resolution_trend._apply_reacquisition_freshness_and_persistence_reset", + "github_repo_auditor.operator_resolution_trend._apply_reacquisition_freshness_and_persistence_reset", _phase46_seed, ) monkeypatch.setattr( - "src.operator_resolution_trend._target_specific_normalization_noise", + "github_repo_auditor.operator_resolution_trend._target_specific_normalization_noise", lambda *_args, **_kwargs: False, ) @@ -6889,7 +6889,7 @@ def test_operator_snapshot_reenters_confirmation_after_fresh_follow_through( ], ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "generated_at": "2026-04-06T12:00:00+00:00", @@ -6977,11 +6977,11 @@ def _phase46_seed( } monkeypatch.setattr( - "src.operator_resolution_trend._apply_reacquisition_freshness_and_persistence_reset", + "github_repo_auditor.operator_resolution_trend._apply_reacquisition_freshness_and_persistence_reset", _phase46_seed, ) monkeypatch.setattr( - "src.operator_resolution_trend._target_specific_normalization_noise", + "github_repo_auditor.operator_resolution_trend._target_specific_normalization_noise", lambda *_args, **_kwargs: False, ) @@ -7042,7 +7042,7 @@ def test_operator_snapshot_reenters_clearance_after_fresh_follow_through( ], ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "generated_at": "2026-04-06T12:00:00+00:00", @@ -7137,11 +7137,11 @@ def _phase46_seed( } monkeypatch.setattr( - "src.operator_resolution_trend._apply_reacquisition_freshness_and_persistence_reset", + "github_repo_auditor.operator_resolution_trend._apply_reacquisition_freshness_and_persistence_reset", _phase46_seed, ) monkeypatch.setattr( - "src.operator_resolution_trend._target_specific_normalization_noise", + "github_repo_auditor.operator_resolution_trend._target_specific_normalization_noise", lambda *_args, **_kwargs: False, ) @@ -7209,7 +7209,7 @@ def test_operator_snapshot_holds_reset_reentry_when_follow_through_stays_aligned ], ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "generated_at": "2026-04-06T12:00:00+00:00", @@ -7276,11 +7276,11 @@ def _phase47_seed( } monkeypatch.setattr( - "src.operator_resolution_trend._apply_reacquisition_reset_refresh_recovery_and_reentry", + "github_repo_auditor.operator_resolution_trend._apply_reacquisition_reset_refresh_recovery_and_reentry", _phase47_seed, ) monkeypatch.setattr( - "src.operator_resolution_trend._target_specific_normalization_noise", + "github_repo_auditor.operator_resolution_trend._target_specific_normalization_noise", lambda *_args, **_kwargs: False, ) @@ -7329,7 +7329,7 @@ def test_operator_snapshot_softens_reset_reentry_when_reentry_starts_churning( ], ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "generated_at": "2026-04-06T12:00:00+00:00", @@ -7438,11 +7438,11 @@ def _phase47_seed( } monkeypatch.setattr( - "src.operator_resolution_trend._apply_reacquisition_reset_refresh_recovery_and_reentry", + "github_repo_auditor.operator_resolution_trend._apply_reacquisition_reset_refresh_recovery_and_reentry", _phase47_seed, ) monkeypatch.setattr( - "src.operator_resolution_trend._target_specific_normalization_noise", + "github_repo_auditor.operator_resolution_trend._target_specific_normalization_noise", lambda *_args, **_kwargs: False, ) @@ -7487,7 +7487,7 @@ def test_operator_snapshot_starts_pending_confirmation_rebuild_after_reset_reent ], ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "generated_at": "2026-04-06T12:00:00+00:00", @@ -7559,11 +7559,11 @@ def _phase49_seed( } monkeypatch.setattr( - "src.operator_resolution_trend._apply_reset_reentry_freshness_and_reset", + "github_repo_auditor.operator_resolution_trend._apply_reset_reentry_freshness_and_reset", _phase49_seed, ) monkeypatch.setattr( - "src.operator_resolution_trend._target_specific_normalization_noise", + "github_repo_auditor.operator_resolution_trend._target_specific_normalization_noise", lambda *_args, **_kwargs: False, ) @@ -7621,7 +7621,7 @@ def test_operator_snapshot_rebuilds_confirmation_reentry_after_fresh_follow_thro ], ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "generated_at": "2026-04-06T12:00:00+00:00", @@ -7711,11 +7711,11 @@ def _phase49_seed( } monkeypatch.setattr( - "src.operator_resolution_trend._apply_reset_reentry_freshness_and_reset", + "github_repo_auditor.operator_resolution_trend._apply_reset_reentry_freshness_and_reset", _phase49_seed, ) monkeypatch.setattr( - "src.operator_resolution_trend._target_specific_normalization_noise", + "github_repo_auditor.operator_resolution_trend._target_specific_normalization_noise", lambda *_args, **_kwargs: False, ) @@ -7773,7 +7773,7 @@ def test_operator_snapshot_rebuilds_clearance_reentry_after_fresh_follow_through ], ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "generated_at": "2026-04-06T12:00:00+00:00", @@ -7863,11 +7863,11 @@ def _phase49_seed( } monkeypatch.setattr( - "src.operator_resolution_trend._apply_reset_reentry_freshness_and_reset", + "github_repo_auditor.operator_resolution_trend._apply_reset_reentry_freshness_and_reset", _phase49_seed, ) monkeypatch.setattr( - "src.operator_resolution_trend._target_specific_normalization_noise", + "github_repo_auditor.operator_resolution_trend._target_specific_normalization_noise", lambda *_args, **_kwargs: False, ) @@ -7926,7 +7926,7 @@ def test_operator_snapshot_marks_rebuilt_confirmation_as_just_rebuilt( ) monkeypatch.setattr( - "src.operator_resolution_trend._apply_reset_reentry_refresh_recovery_and_rebuild", + "github_repo_auditor.operator_resolution_trend._apply_reset_reentry_refresh_recovery_and_rebuild", lambda resolution_targets, _history, **_kwargs: ( resolution_targets.__setitem__( slice(None), @@ -7960,7 +7960,7 @@ def test_operator_snapshot_marks_rebuilt_confirmation_as_just_rebuilt( ), ) monkeypatch.setattr( - "src.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_persistence_for_target", + "github_repo_auditor.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_persistence_for_target", lambda *_args, **_kwargs: { "closure_forecast_reset_reentry_rebuild_age_runs": 1, "closure_forecast_reset_reentry_rebuild_persistence_score": 0.29, @@ -7970,7 +7970,7 @@ def test_operator_snapshot_marks_rebuilt_confirmation_as_just_rebuilt( }, ) monkeypatch.setattr( - "src.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_churn_for_target", + "github_repo_auditor.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_churn_for_target", lambda *_args, **_kwargs: { "closure_forecast_reset_reentry_rebuild_churn_score": 0.10, "closure_forecast_reset_reentry_rebuild_churn_status": "none", @@ -8028,7 +8028,7 @@ def test_operator_snapshot_softens_rebuilt_clearance_when_rebuild_churn_is_high( ) monkeypatch.setattr( - "src.operator_resolution_trend._apply_reset_reentry_refresh_recovery_and_rebuild", + "github_repo_auditor.operator_resolution_trend._apply_reset_reentry_refresh_recovery_and_rebuild", lambda resolution_targets, _history, **_kwargs: ( resolution_targets.__setitem__( slice(None), @@ -8062,7 +8062,7 @@ def test_operator_snapshot_softens_rebuilt_clearance_when_rebuild_churn_is_high( ), ) monkeypatch.setattr( - "src.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_persistence_for_target", + "github_repo_auditor.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_persistence_for_target", lambda *_args, **_kwargs: { "closure_forecast_reset_reentry_rebuild_age_runs": 2, "closure_forecast_reset_reentry_rebuild_persistence_score": -0.11, @@ -8072,7 +8072,7 @@ def test_operator_snapshot_softens_rebuilt_clearance_when_rebuild_churn_is_high( }, ) monkeypatch.setattr( - "src.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_churn_for_target", + "github_repo_auditor.operator_resolution_trend._closure_forecast_reset_reentry_rebuild_churn_for_target", lambda *_args, **_kwargs: { "closure_forecast_reset_reentry_rebuild_churn_score": 0.52, "closure_forecast_reset_reentry_rebuild_churn_status": "churn", @@ -8881,7 +8881,7 @@ def test_operator_snapshot_learns_when_soft_exception_was_overcautious( ], ) monkeypatch.setattr( - "src.operator_control_center.load_operator_state_history", + "github_repo_auditor.operator_control_center.load_operator_state_history", lambda *_args, **_kwargs: [ { "generated_at": "2026-04-06T12:00:00+00:00", @@ -8954,7 +8954,7 @@ def test_operator_snapshot_learns_when_soft_exception_was_overcautious( ], ) monkeypatch.setattr( - "src.operator_resolution_trend._build_confidence_calibration", + "github_repo_auditor.operator_resolution_trend._build_confidence_calibration", lambda _history: { "confidence_validation_status": "noisy", "confidence_window_runs": 8, @@ -8971,7 +8971,7 @@ def test_operator_snapshot_learns_when_soft_exception_was_overcautious( }, ) monkeypatch.setattr( - "src.operator_resolution_trend._was_resolved_then_reopened", + "github_repo_auditor.operator_resolution_trend._was_resolved_then_reopened", lambda *_args, **_kwargs: True, ) diff --git a/tests/test_operator_control_center_artifacts.py b/tests/test_operator_control_center_artifacts.py index 320ce941..dc0ac453 100644 --- a/tests/test_operator_control_center_artifacts.py +++ b/tests/test_operator_control_center_artifacts.py @@ -4,7 +4,7 @@ import pytest -from src import operator_control_center_artifacts as artifacts +from github_repo_auditor import operator_control_center_artifacts as artifacts def _stub_artifact_dependencies(tmp_path, monkeypatch, *, weekly_digest=None): diff --git a/tests/test_operator_decision_quality.py b/tests/test_operator_decision_quality.py index c3b14f7d..c6987bb8 100644 --- a/tests/test_operator_decision_quality.py +++ b/tests/test_operator_decision_quality.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_decision_quality import ( +from github_repo_auditor.operator_decision_quality import ( build_decision_quality_v1, decision_quality_from_summary, ) diff --git a/tests/test_operator_effectiveness.py b/tests/test_operator_effectiveness.py index 524a713f..b8f9b1b6 100644 --- a/tests/test_operator_effectiveness.py +++ b/tests/test_operator_effectiveness.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_effectiveness import build_operator_effectiveness_bundle +from github_repo_auditor.operator_effectiveness import build_operator_effectiveness_bundle def _state(run_id: str, generated_at: str, blocked: int, urgent: int, *, reopened: int = 0, resolved: int = 0) -> dict: diff --git a/tests/test_operator_os_seam_linter.py b/tests/test_operator_os_seam_linter.py index 25435c31..d33975b3 100644 --- a/tests/test_operator_os_seam_linter.py +++ b/tests/test_operator_os_seam_linter.py @@ -6,13 +6,13 @@ from datetime import UTC, datetime from pathlib import Path -from src.operator_os_seam_linter import ( +from github_repo_auditor.operator_os_seam_linter import ( build_worklist_payload, lint_operator_os_seams, main, ) -from src.portfolio_truth_render import GENERATED_MARKDOWN_PROVENANCE_MARKER -from src.portfolio_truth_types import SCHEMA_VERSION +from github_repo_auditor.portfolio_truth_render import GENERATED_MARKDOWN_PROVENANCE_MARKER +from github_repo_auditor.portfolio_truth_types import SCHEMA_VERSION NOW = datetime(2026, 7, 3, 12, 0, tzinfo=UTC) diff --git a/tests/test_operator_prefs.py b/tests/test_operator_prefs.py index 52582cdc..f6539cbe 100644 --- a/tests/test_operator_prefs.py +++ b/tests/test_operator_prefs.py @@ -1,11 +1,11 @@ -"""Tests for src/operator_prefs.py — suppression hint detection and persistence.""" +"""Tests for src/github_repo_auditor/operator_prefs.py — suppression hint detection and persistence.""" from __future__ import annotations import json import tempfile -from src.operator_prefs import ( +from github_repo_auditor.operator_prefs import ( SuppressionHint, detect_suppressions, is_suppressed, @@ -266,7 +266,7 @@ def test_cli_reset_prefs_clears_file(tmp_path): import sys - from src.cli import main # noqa: PLC0415 + from github_repo_auditor.cli import main # noqa: PLC0415 sys.argv = [ "audit", @@ -316,7 +316,7 @@ def test_post_process_approval_session_updates_prefs(tmp_path): def test_briefing_skips_suppressed_action(): """build_briefing with prefs containing a suppression should skip that action.""" - from src.briefing import build_briefing + from github_repo_auditor.briefing import build_briefing prefs = { "version": 1, diff --git a/tests/test_operator_snapshot_contract.py b/tests/test_operator_snapshot_contract.py index 82648525..7cf2c7cc 100644 --- a/tests/test_operator_snapshot_contract.py +++ b/tests/test_operator_snapshot_contract.py @@ -2,7 +2,7 @@ from pathlib import Path -from src.operator_control_center import build_operator_snapshot, control_center_artifact_payload +from github_repo_auditor.operator_control_center import build_operator_snapshot, control_center_artifact_payload def _make_contract_report(**overrides) -> dict: diff --git a/tests/test_operator_trend_apply_chain.py b/tests/test_operator_trend_apply_chain.py index 9decf038..e7ddd0c2 100644 --- a/tests/test_operator_trend_apply_chain.py +++ b/tests/test_operator_trend_apply_chain.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_apply_chain import run_resolution_trend_apply_chain +from github_repo_auditor.operator_trend_apply_chain import run_resolution_trend_apply_chain def test_run_resolution_trend_apply_chain_returns_named_results() -> None: diff --git a/tests/test_operator_trend_class_transitions.py b/tests/test_operator_trend_class_transitions.py index 747fc0c3..307d7ac9 100644 --- a/tests/test_operator_trend_class_transitions.py +++ b/tests/test_operator_trend_class_transitions.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_class_transitions import ( +from github_repo_auditor.operator_trend_class_transitions import ( build_class_reweight_events, build_class_transition_events, consecutive_transition_runs, diff --git a/tests/test_operator_trend_closure_forecast_events.py b/tests/test_operator_trend_closure_forecast_events.py index 64c447cc..394195ad 100644 --- a/tests/test_operator_trend_closure_forecast_events.py +++ b/tests/test_operator_trend_closure_forecast_events.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_closure_forecast_core import class_closure_forecast_events +from github_repo_auditor.operator_trend_closure_forecast_core import class_closure_forecast_events def _queue_identity(item: dict) -> str: diff --git a/tests/test_operator_trend_closure_forecast_freshness.py b/tests/test_operator_trend_closure_forecast_freshness.py index 56b2a320..754c79c3 100644 --- a/tests/test_operator_trend_closure_forecast_freshness.py +++ b/tests/test_operator_trend_closure_forecast_freshness.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_closure_forecast_freshness_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_freshness_controls import ( apply_closure_forecast_decay_control, closure_forecast_freshness_for_target, closure_forecast_freshness_hotspots, diff --git a/tests/test_operator_trend_closure_forecast_history.py b/tests/test_operator_trend_closure_forecast_history.py index fef6c9f5..a49881bb 100644 --- a/tests/test_operator_trend_closure_forecast_history.py +++ b/tests/test_operator_trend_closure_forecast_history.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_closure_forecast_core import target_closure_forecast_history +from github_repo_auditor.operator_trend_closure_forecast_core import target_closure_forecast_history def _target_class_key(item: dict) -> str: diff --git a/tests/test_operator_trend_closure_forecast_reacquisition.py b/tests/test_operator_trend_closure_forecast_reacquisition.py index 42a3448d..b4a6cbe7 100644 --- a/tests/test_operator_trend_closure_forecast_reacquisition.py +++ b/tests/test_operator_trend_closure_forecast_reacquisition.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_closure_forecast_reacquisition_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls import ( apply_closure_forecast_reacquisition_control, apply_reacquisition_persistence_and_churn_control, closure_forecast_reacquisition_hotspots, diff --git a/tests/test_operator_trend_closure_forecast_reacquisition_freshness.py b/tests/test_operator_trend_closure_forecast_reacquisition_freshness.py index 20ccbe6a..8d32a53d 100644 --- a/tests/test_operator_trend_closure_forecast_reacquisition_freshness.py +++ b/tests/test_operator_trend_closure_forecast_reacquisition_freshness.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_closure_forecast_reacquisition_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reacquisition_controls import ( apply_reacquisition_freshness_reset_control, closure_forecast_persistence_reset_summary, closure_forecast_reacquisition_freshness_for_target, diff --git a/tests/test_operator_trend_closure_forecast_reset_reentry_freshness.py b/tests/test_operator_trend_closure_forecast_reset_reentry_freshness.py index 5a482adb..402a1695 100644 --- a/tests/test_operator_trend_closure_forecast_reset_reentry_freshness.py +++ b/tests/test_operator_trend_closure_forecast_reset_reentry_freshness.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( apply_reset_reentry_freshness_reset_control, closure_forecast_reset_reentry_freshness_for_target, closure_forecast_reset_reentry_freshness_hotspots, diff --git a/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild.py b/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild.py index 18a1688c..360c8bc4 100644 --- a/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild.py +++ b/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( apply_reset_reentry_refresh_rebuild_control, closure_forecast_reset_reentry_rebuild_summary, closure_forecast_reset_reentry_refresh_hotspots, diff --git a/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_freshness.py b/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_freshness.py index 7b759f35..8be15a92 100644 --- a/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_freshness.py +++ b/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_freshness.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( apply_reset_reentry_rebuild_freshness_reset_control, closure_forecast_reset_reentry_rebuild_freshness_for_target, closure_forecast_reset_reentry_rebuild_freshness_hotspots, diff --git a/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_persistence.py b/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_persistence.py index efad60cb..aa8d3933 100644 --- a/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_persistence.py +++ b/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_persistence.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( closure_forecast_reset_reentry_rebuild_churn_for_target, closure_forecast_reset_reentry_rebuild_churn_summary, closure_forecast_reset_reentry_rebuild_hotspots, diff --git a/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_reentry_restore_rerererestore_persistence.py b/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_reentry_restore_rerererestore_persistence.py index b7b1b7a7..8604c811 100644 --- a/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_reentry_restore_rerererestore_persistence.py +++ b/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_reentry_restore_rerererestore_persistence.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( apply_reset_reentry_rebuild_reentry_restore_rerererestore_persistence_and_churn, closure_forecast_reset_reentry_rebuild_reentry_restore_rerererestore_hotspots, closure_forecast_reset_reentry_rebuild_reentry_restore_rerererestore_persistence_for_target, diff --git a/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_freshness.py b/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_freshness.py index 327941d9..ff2ed726 100644 --- a/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_freshness.py +++ b/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_freshness.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( apply_reset_reentry_rebuild_reentry_restore_rererestore_freshness_and_reset, apply_reset_reentry_rebuild_reentry_restore_rererestore_freshness_reset_control, closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_freshness_for_target, diff --git a/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_persistence.py b/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_persistence.py index 3b02f711..24d90c25 100644 --- a/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_persistence.py +++ b/tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_persistence.py @@ -1,12 +1,12 @@ from __future__ import annotations -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( apply_reset_reentry_rebuild_reentry_restore_rererestore_persistence_and_churn, closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_hotspots, closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_persistence_for_target, closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_persistence_summary, ) -from src.operator_trend_support import current_closure_forecast_event_for_target +from github_repo_auditor.operator_trend_support import current_closure_forecast_event_for_target def test_rererestore_persistence_uses_synthesized_current_event() -> None: diff --git a/tests/test_operator_trend_closure_forecast_reset_refresh.py b/tests/test_operator_trend_closure_forecast_reset_refresh.py index 7c9b5c75..274b7c01 100644 --- a/tests/test_operator_trend_closure_forecast_reset_refresh.py +++ b/tests/test_operator_trend_closure_forecast_reset_refresh.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_closure_forecast_reset_controls import ( +from github_repo_auditor.operator_trend_closure_forecast_reset_controls import ( apply_reset_refresh_reentry_control, closure_forecast_reset_refresh_hotspots, closure_forecast_reset_refresh_recovery_for_target, diff --git a/tests/test_operator_trend_closure_forecast_reweighting.py b/tests/test_operator_trend_closure_forecast_reweighting.py index b899e62a..c83aa5af 100644 --- a/tests/test_operator_trend_closure_forecast_reweighting.py +++ b/tests/test_operator_trend_closure_forecast_reweighting.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_closure_forecast_core import ( +from github_repo_auditor.operator_trend_closure_forecast_core import ( apply_closure_forecast_reweighting_control, closure_forecast_hotspots, pending_debt_freshness_hotspots, diff --git a/tests/test_operator_trend_confidence_calibration.py b/tests/test_operator_trend_confidence_calibration.py index 11505ae8..e3dff614 100644 --- a/tests/test_operator_trend_confidence_calibration.py +++ b/tests/test_operator_trend_confidence_calibration.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_confidence_calibration import build_confidence_calibration +from github_repo_auditor.operator_trend_confidence_calibration import build_confidence_calibration def _queue_identity(item: dict) -> str: diff --git a/tests/test_operator_trend_decision_memory.py b/tests/test_operator_trend_decision_memory.py index 977302ee..971ece72 100644 --- a/tests/test_operator_trend_decision_memory.py +++ b/tests/test_operator_trend_decision_memory.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_decision_memory import ( +from github_repo_auditor.operator_trend_decision_memory import ( absent_decision_memory, current_item_last_outcome, current_item_resolution_evidence, diff --git a/tests/test_operator_trend_exception_recovery.py b/tests/test_operator_trend_exception_recovery.py index 73fcf256..c4856e91 100644 --- a/tests/test_operator_trend_exception_recovery.py +++ b/tests/test_operator_trend_exception_recovery.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_exception_recovery import ( +from github_repo_auditor.operator_trend_exception_recovery import ( exception_pattern_summary, recovery_pattern_reason, trust_recovery_for_target, diff --git a/tests/test_operator_trend_helpers.py b/tests/test_operator_trend_helpers.py index 2130f960..40d60550 100644 --- a/tests/test_operator_trend_helpers.py +++ b/tests/test_operator_trend_helpers.py @@ -1,11 +1,11 @@ from __future__ import annotations -from src.operator_trend_pending_text import ( +from github_repo_auditor.operator_trend_pending_text import ( class_pending_debt_summary, pending_debt_freshness_summary, transition_closure_confidence_summary, ) -from src.operator_trend_support import ( +from github_repo_auditor.operator_trend_support import ( is_generic_baseline_guidance, is_generic_monitor_guidance, is_generic_recommendation, diff --git a/tests/test_operator_trend_pending_debt.py b/tests/test_operator_trend_pending_debt.py index a736a5e6..23fef7c2 100644 --- a/tests/test_operator_trend_pending_debt.py +++ b/tests/test_operator_trend_pending_debt.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_pending_debt import ( +from github_repo_auditor.operator_trend_pending_debt import ( class_pending_debt_for_target, class_pending_debt_hotspots, closure_forecast_reweight_scores_for_target, diff --git a/tests/test_operator_trend_recommendation_drift.py b/tests/test_operator_trend_recommendation_drift.py index 44452fe3..450705a4 100644 --- a/tests/test_operator_trend_recommendation_drift.py +++ b/tests/test_operator_trend_recommendation_drift.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_recommendation_drift import ( +from github_repo_auditor.operator_trend_recommendation_drift import ( recommendation_drift_status, recommendation_drift_summary, ) diff --git a/tests/test_operator_trend_run_context.py b/tests/test_operator_trend_run_context.py index 674f0f19..2d27a74a 100644 --- a/tests/test_operator_trend_run_context.py +++ b/tests/test_operator_trend_run_context.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_run_context import build_resolution_run_context +from github_repo_auditor.operator_trend_run_context import build_resolution_run_context def _snapshot_from_queue(queue: list[dict], *, generated_at: str = "") -> dict: diff --git a/tests/test_operator_trend_summary_context.py b/tests/test_operator_trend_summary_context.py index 7c0fc94c..fe84fb67 100644 --- a/tests/test_operator_trend_summary_context.py +++ b/tests/test_operator_trend_summary_context.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_summary_context import build_trend_summary_context +from github_repo_auditor.operator_trend_summary_context import build_trend_summary_context def test_build_trend_summary_context_packages_attention_counts() -> None: diff --git a/tests/test_operator_trend_topline_payload.py b/tests/test_operator_trend_topline_payload.py index be8b7aeb..3045b474 100644 --- a/tests/test_operator_trend_topline_payload.py +++ b/tests/test_operator_trend_topline_payload.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_topline_payload import build_resolution_trend_topline_payload +from github_repo_auditor.operator_trend_topline_payload import build_resolution_trend_topline_payload def test_build_resolution_trend_topline_payload_packages_core_summary() -> None: diff --git a/tests/test_operator_trend_transition_closure.py b/tests/test_operator_trend_transition_closure.py index 11afeda6..fed395b5 100644 --- a/tests/test_operator_trend_transition_closure.py +++ b/tests/test_operator_trend_transition_closure.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_transition_closure import ( +from github_repo_auditor.operator_trend_transition_closure import ( apply_transition_closure_control, transition_closure_confidence_for_target, ) diff --git a/tests/test_operator_trend_trust_policy_history.py b/tests/test_operator_trend_trust_policy_history.py index c211930b..a2a67999 100644 --- a/tests/test_operator_trend_trust_policy_history.py +++ b/tests/test_operator_trend_trust_policy_history.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.operator_trend_trust_policy_history import ( +from github_repo_auditor.operator_trend_trust_policy_history import ( build_trust_policy_events, false_positive_exception_hotspots, policy_flip_count, diff --git a/tests/test_ops_writeback.py b/tests/test_ops_writeback.py index c9640356..47349659 100644 --- a/tests/test_ops_writeback.py +++ b/tests/test_ops_writeback.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.ops_writeback import ( +from github_repo_auditor.ops_writeback import ( apply_github_writeback, build_action_runs, build_campaign_bundle, diff --git a/tests/test_ossf_scorecard.py b/tests/test_ossf_scorecard.py index 506dee1b..5edeedca 100644 --- a/tests/test_ossf_scorecard.py +++ b/tests/test_ossf_scorecard.py @@ -1,4 +1,4 @@ -"""Tests for src/ossf_scorecard.py — OSSF Scorecard integration.""" +"""Tests for src/github_repo_auditor/ossf_scorecard.py — OSSF Scorecard integration.""" from __future__ import annotations @@ -8,8 +8,8 @@ import pytest import requests -from src.cache import ResponseCache -from src.ossf_scorecard import ( +from github_repo_auditor.cache import ResponseCache +from github_repo_auditor.ossf_scorecard import ( _OSSF_CACHE_PARAMS, OSSF_SCORECARD_BASE_URL, _fetch_one, diff --git a/tests/test_packaging.py b/tests/test_packaging.py index 7095127d..1c6874e5 100644 --- a/tests/test_packaging.py +++ b/tests/test_packaging.py @@ -8,7 +8,7 @@ import pytest -from src.config import inspect_config +from github_repo_auditor.config import inspect_config ROOT = Path(__file__).resolve().parents[1] @@ -27,12 +27,27 @@ def _requirements_names() -> set[str]: def test_pyproject_exposes_audit_console_script(): data = tomllib.loads((ROOT / "pyproject.toml").read_text()) - assert data["project"]["scripts"]["audit"] == "src.cli:main" + assert data["project"]["scripts"]["audit"] == "github_repo_auditor.cli:main" + + +def test_import_package_uses_conventional_src_layout(): + data = tomllib.loads((ROOT / "pyproject.toml").read_text()) + + assert data["tool"]["setuptools"]["packages"]["find"] == { + "where": ["src"], + "include": ["github_repo_auditor*"], + } + assert not (ROOT / "src" / "__init__.py").exists() + assert (ROOT / "src" / "github_repo_auditor" / "__init__.py").is_file() + assert data["tool"]["setuptools"]["package-data"]["github_repo_auditor"] + assert "assets/excel/*.xlsx" in data["tool"]["setuptools"]["package-data"][ + "github_repo_auditor" + ] def test_cli_module_executes_help(): result = subprocess.run( - [sys.executable, "-m", "src.cli", "--help"], + [sys.executable, "-m", "github_repo_auditor.cli", "--help"], cwd=ROOT, capture_output=True, text=True, @@ -53,12 +68,13 @@ def test_makefile_includes_operator_entrypoints(): makefile = (ROOT / "Makefile").read_text() for target in ("install:", "install-dev:", "doctor:", "audit:", "control-center:", "workbook-gate:", "workbook-signoff:", "test:"): assert target in makefile - assert "CLI := uv run python -m src.cli" in makefile + assert "SOURCE_ENV := PYTHONPATH=src" in makefile + assert "CLI := $(SOURCE_ENV) uv run python -m github_repo_auditor.cli" in makefile assert "$(CLI) $(USERNAME) --doctor $(ARGS)" in makefile assert "$(CLI) $(USERNAME) --excel-mode standard $(ARGS)" in makefile assert "$(CLI) $(USERNAME) --control-center $(ARGS)" in makefile - assert "$(PYTHON) -m src.workbook_gate $(ARGS)" in makefile - assert "$(PYTHON) -m src.workbook_gate --record-signoff $(ARGS)" in makefile + assert "$(PYTHON) -m github_repo_auditor.workbook_gate $(ARGS)" in makefile + assert "$(PYTHON) -m github_repo_auditor.workbook_gate --record-signoff $(ARGS)" in makefile assert "$(CLI) --help" in makefile diff --git a/tests/test_pdf_export.py b/tests/test_pdf_export.py index a3c1edbc..8e8c7152 100644 --- a/tests/test_pdf_export.py +++ b/tests/test_pdf_export.py @@ -1,4 +1,4 @@ -from src.pdf_export import export_pdf_report +from github_repo_auditor.pdf_export import export_pdf_report def _minimal_report(): diff --git a/tests/test_per_action_approval.py b/tests/test_per_action_approval.py index d6803387..1daa5c9f 100644 --- a/tests/test_per_action_approval.py +++ b/tests/test_per_action_approval.py @@ -9,7 +9,7 @@ import pytest -from src.plan_campaign import ( +from github_repo_auditor.plan_campaign import ( CampaignAction, CampaignPlanPacket, approve_action, @@ -58,7 +58,7 @@ def _make_packet( def _seed_approved_manual(output_dir: Path, goal: str = "add CI to all repos") -> str: """Write a packet with status='approved-manual' and return the record_id.""" - from src.warehouse import load_approval_records, save_approval_record + from github_repo_auditor.warehouse import load_approval_records, save_approval_record packet = _make_packet(goal) record_id = write_packet_to_ledger(packet, output_dir=output_dir, reviewer="tester") @@ -79,7 +79,7 @@ def _seed_approved_manual(output_dir: Path, goal: str = "add CI to all repos") - class TestApproveAction: def test_sets_state_approved_and_persists(self) -> None: - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) @@ -93,7 +93,7 @@ def test_sets_state_approved_and_persists(self) -> None: assert rec["actions"][0]["decided_at"] is not None def test_sets_decided_at_iso_timestamp(self) -> None: - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) @@ -130,7 +130,7 @@ def test_approve_negative_idx_raises_index_error(self) -> None: def test_clears_rejected_reason_on_re_approve(self) -> None: """Re-approving a previously rejected action clears rejected_reason.""" - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) @@ -152,7 +152,7 @@ def test_clears_rejected_reason_on_re_approve(self) -> None: class TestRejectAction: def test_sets_state_rejected_with_reason(self) -> None: - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) @@ -167,7 +167,7 @@ def test_sets_state_rejected_with_reason(self) -> None: assert rec["actions"][0]["decided_at"] is not None def test_sets_state_rejected_empty_reason(self) -> None: - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) @@ -195,7 +195,7 @@ def test_reject_out_of_range_idx_raises_index_error(self) -> None: def test_re_approve_flips_rejected_back_to_approved(self) -> None: """Idempotency: approve after reject → state becomes approved.""" - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) @@ -217,7 +217,7 @@ def test_re_approve_flips_rejected_back_to_approved(self) -> None: class TestBackwardCompat: def test_load_approved_campaign_plans_defaults_missing_state_to_pending(self) -> None: """Pre-7B records without 'state' key → CampaignAction.state == 'pending'.""" - from src.warehouse import save_approval_record + from github_repo_auditor.warehouse import save_approval_record with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) @@ -263,7 +263,7 @@ def test_load_approved_campaign_plans_defaults_missing_state_to_pending(self) -> def test_load_approved_campaign_plans_preserves_existing_state(self) -> None: """Records with 'state' set are hydrated correctly.""" - from src.warehouse import save_approval_record + from github_repo_auditor.warehouse import save_approval_record with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) @@ -324,8 +324,8 @@ def _make_approved_manual_packet( actions: list[CampaignAction], goal: str = "apply gate test", ) -> CampaignPlanPacket: - from src.plan_campaign import _goal_subject_key, _packet_record_id - from src.warehouse import save_approval_record + from github_repo_auditor.plan_campaign import _goal_subject_key, _packet_record_id + from github_repo_auditor.warehouse import save_approval_record generated_at = _recent_generated_at() packet = CampaignPlanPacket( @@ -395,7 +395,7 @@ def test_pending_action_is_skipped_no_dispatch(self) -> None: ] self._make_approved_manual_packet(output_dir, actions) - with patch("src.plan_campaign.dispatch_action") as mock_dispatch: + with patch("github_repo_auditor.plan_campaign.dispatch_action") as mock_dispatch: packets = load_approved_campaign_plans(output_dir) assert len(packets) == 1 # Only approved actions should be dispatched — pending ones skip @@ -496,12 +496,12 @@ def test_packet_with_pending_action_is_not_terminal(self) -> None: from fastapi.testclient import TestClient # noqa: E402 -from src.serve.app import create_app # noqa: E402 +from github_repo_auditor.serve.app import create_app # noqa: E402 def _seed_cp_record_for_routes(output_dir: Path) -> str: """Seed a campaign-plan record with 2 pending actions; return record_id.""" - from src.warehouse import save_approval_record + from github_repo_auditor.warehouse import save_approval_record record_id = "cp-7b-route-test-0001" save_approval_record( @@ -571,7 +571,7 @@ def test_approve_action_returns_200_with_approved_badge(self, tmp_path: Path) -> assert "approved" in resp.text.lower() or "✓" in resp.text def test_approve_action_persists_state(self, tmp_path: Path) -> None: - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records output_dir = self._make_output_dir(tmp_path) record_id = _seed_cp_record_for_routes(output_dir) diff --git a/tests/test_per_section_draft_readmes.py b/tests/test_per_section_draft_readmes.py index 421f16ea..782bbcdc 100644 --- a/tests/test_per_section_draft_readmes.py +++ b/tests/test_per_section_draft_readmes.py @@ -8,7 +8,7 @@ import pytest -from src.draft_readmes import ( +from github_repo_auditor.draft_readmes import ( DraftReadmePacket, approve_section, assemble_readme_from_approved_sections, @@ -48,7 +48,7 @@ def _seed_sections(output_dir: Path, packet: DraftReadmePacket | None = None) -> if packet is None: packet = _make_packet() write_section_packets_to_ledger([packet], output_dir, reviewer="tester") - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records records = load_approval_records(output_dir, "", limit=500) return [ @@ -125,7 +125,7 @@ def test_three_section_packet_writes_three_records(self) -> None: with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) write_section_packets_to_ledger([packet], output_dir, reviewer="t") - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records records = load_approval_records(output_dir, "", limit=500) section_records = [ @@ -138,7 +138,7 @@ def test_all_records_have_correct_subject_type(self) -> None: with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) write_section_packets_to_ledger([packet], output_dir, reviewer="t") - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records records = load_approval_records(output_dir, "", limit=500) section_records = [ @@ -154,7 +154,7 @@ def test_distinct_approval_ids_shared_packet_id(self) -> None: with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) write_section_packets_to_ledger([packet], output_dir, reviewer="t") - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records records = load_approval_records(output_dir, "", limit=500) section_records = [ @@ -179,7 +179,7 @@ def test_rerun_same_repo_preserves_approved_state(self) -> None: # Re-run with the SAME sections (same approval_id derived from heading) write_section_packets_to_ledger([packet], output_dir, reviewer="t") - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records records = load_approval_records(output_dir, "", limit=500) rec = next( @@ -197,7 +197,7 @@ def test_approve_sets_state(self) -> None: output_dir = Path(tmp) record_ids = _seed_sections(output_dir) approve_section(record_ids[0], output_dir) - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records records = load_approval_records(output_dir, "", limit=500) rec = next(r for r in records if r.get("approval_id") == record_ids[0]) @@ -216,7 +216,7 @@ def test_reject_sets_state_and_reason(self) -> None: output_dir = Path(tmp) record_ids = _seed_sections(output_dir) reject_section(record_ids[0], output_dir, reason="too verbose") - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records records = load_approval_records(output_dir, "", limit=500) rec = next(r for r in records if r.get("approval_id") == record_ids[0]) @@ -233,8 +233,8 @@ def test_reject_nonexistent_raises_value_error(self) -> None: class TestLegacyDraftReadmeRecords: def test_legacy_records_load_via_load_approved_drafts(self) -> None: """Pre-Sprint-8 draft-readme records continue to flow through load_approved_drafts.""" - from src.draft_readmes import load_approved_drafts - from src.warehouse import save_approval_record + from github_repo_auditor.draft_readmes import load_approved_drafts + from github_repo_auditor.warehouse import save_approval_record with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) @@ -323,7 +323,7 @@ def test_sets_status_applied_on_all_sub_records(self) -> None: output_dir = Path(tmp) _seed_sections(output_dir, packet) # Get shared packet_id - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records records = load_approval_records(output_dir, "", limit=500) pid = next( @@ -405,7 +405,7 @@ def test_mixed_state_packet_counters(self) -> None: reject_section(record_ids[1], output_dir) # record_ids[2] stays pending - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records records = load_approval_records(output_dir, "", limit=500) sections = [ @@ -428,7 +428,7 @@ class TestDraftSectionsRoutes: def _client(self, output_dir: Path): from fastapi.testclient import TestClient # noqa: E402 - from src.serve.app import create_app # noqa: E402 + from github_repo_auditor.serve.app import create_app # noqa: E402 return TestClient(create_app(output_dir=output_dir), raise_server_exceptions=True) @@ -437,7 +437,7 @@ def test_get_draft_sections_200(self) -> None: with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) _seed_sections(output_dir, packet) - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records records = load_approval_records(output_dir, "", limit=500) pid = next( @@ -497,7 +497,7 @@ def test_post_reject_section_persists_reason(self) -> None: f"/approvals/sections/{record_ids[0]}/reject", data={"reason": "needs work"}, ) - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records records = load_approval_records(output_dir, "", limit=500) rec = next(r for r in records if r.get("approval_id") == record_ids[0]) diff --git a/tests/test_phase93_approval_surfaces.py b/tests/test_phase93_approval_surfaces.py index e23b5e68..c618c07d 100644 --- a/tests/test_phase93_approval_surfaces.py +++ b/tests/test_phase93_approval_surfaces.py @@ -4,11 +4,11 @@ from openpyxl import Workbook -from src.approval_ledger import render_approval_center_markdown -from src.excel_export import _build_approval_ledger, _build_hidden_data_sheets -from src.review_pack import export_review_pack -from src.scheduled_handoff import render_scheduled_handoff_markdown -from src.web_export import _render_html +from github_repo_auditor.approval_ledger import render_approval_center_markdown +from github_repo_auditor.excel_export import _build_approval_ledger, _build_hidden_data_sheets +from github_repo_auditor.review_pack import export_review_pack +from github_repo_auditor.scheduled_handoff import render_scheduled_handoff_markdown +from github_repo_auditor.web_export import _render_html def _report() -> dict: diff --git a/tests/test_plan_campaign.py b/tests/test_plan_campaign.py index 32c101ba..fca7c45b 100644 --- a/tests/test_plan_campaign.py +++ b/tests/test_plan_campaign.py @@ -1,4 +1,4 @@ -"""Tests for src/plan_campaign.py — Arc G Sprint 6.1-6.2 + 6.4 (apply path).""" +"""Tests for src/github_repo_auditor/plan_campaign.py — Arc G Sprint 6.1-6.2 + 6.4 (apply path).""" from __future__ import annotations @@ -11,8 +11,8 @@ import pytest -from src.llm_cost import BudgetExceededError, CostTracker -from src.plan_campaign import ( +from github_repo_auditor.llm_cost import BudgetExceededError, CostTracker +from github_repo_auditor.plan_campaign import ( ACTION_TYPES, CampaignAction, CampaignPlanPacket, @@ -142,7 +142,7 @@ def test_empty_audit_results_returns_empty(self) -> None: assert result == [] def test_with_mock_semantic_index_calls_search(self) -> None: - from src.semantic_index import SearchResult + from github_repo_auditor.semantic_index import SearchResult repos = _make_repos(["alpha", "beta", "gamma"]) @@ -357,7 +357,7 @@ def test_returns_record_id_string(self) -> None: assert record_id.startswith("cp-") def test_record_can_be_read_back_from_ledger(self) -> None: - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) @@ -404,7 +404,7 @@ def _make_truth_file(self, output_dir: Path, repos: list[dict]) -> None: def test_report_subcommand_calls_run_plan_campaign_mode(self) -> None: """audit report --plan-campaign 'goal' someuser → dispatches _run_plan_campaign_mode.""" - with patch("src.cli._run_plan_campaign_mode") as mock_dispatch: + with patch("github_repo_auditor.cli._run_plan_campaign_mode") as mock_dispatch: with patch( "sys.argv", [ @@ -418,7 +418,7 @@ def test_report_subcommand_calls_run_plan_campaign_mode(self) -> None: ], ): try: - from src.cli import main + from github_repo_auditor.cli import main main() except SystemExit: @@ -431,7 +431,7 @@ def test_report_subcommand_calls_run_plan_campaign_mode(self) -> None: def test_legacy_cli_plan_campaign_flag_parsed(self) -> None: """Legacy flat form: audit someuser --plan-campaign 'goal' parses correctly.""" - from src.cli import build_parser + from github_repo_auditor.cli import build_parser parser = build_parser() args = parser.parse_args(["someuser", "--plan-campaign", "my goal"]) @@ -439,8 +439,8 @@ def test_legacy_cli_plan_campaign_flag_parsed(self) -> None: def test_legacy_invocation_emits_deprecation_warning(self) -> None: """audit someuser --plan-campaign 'goal' via legacy path calls _emit_legacy_deprecation_warning.""" - with patch("src.cli._run_plan_campaign_mode"): - with patch("src.cli._emit_legacy_deprecation_warning") as mock_warn: + with patch("github_repo_auditor.cli._run_plan_campaign_mode"): + with patch("github_repo_auditor.cli._emit_legacy_deprecation_warning") as mock_warn: with patch( "sys.argv", [ @@ -453,7 +453,7 @@ def test_legacy_invocation_emits_deprecation_warning(self) -> None: ], ): try: - from src.cli import main + from github_repo_auditor.cli import main main() except SystemExit: @@ -466,7 +466,7 @@ def test_legacy_invocation_emits_deprecation_warning(self) -> None: def test_campaign_from_ledger_flag_parsed(self) -> None: """--campaign-from-ledger is accepted by build_parser.""" - from src.cli import build_parser + from github_repo_auditor.cli import build_parser parser = build_parser() args = parser.parse_args(["someuser", "--campaign-from-ledger", "--output-dir", "/tmp"]) @@ -474,7 +474,7 @@ def test_campaign_from_ledger_flag_parsed(self) -> None: def test_campaign_from_ledger_dispatches_run_mode(self) -> None: """audit report --writeback-apply --campaign-from-ledger someuser dispatches apply mode.""" - with patch("src.cli._run_campaign_from_ledger_mode") as mock_dispatch: + with patch("github_repo_auditor.cli._run_campaign_from_ledger_mode") as mock_dispatch: with patch( "sys.argv", [ @@ -488,7 +488,7 @@ def test_campaign_from_ledger_dispatches_run_mode(self) -> None: ], ): try: - from src.cli import main + from github_repo_auditor.cli import main main() except SystemExit: @@ -506,7 +506,7 @@ def test_boot_no_truth_file_exits_cleanly(self) -> None: # No truth file captured_output: list[str] = [] - with patch("src.cli.print_info", side_effect=lambda msg: captured_output.append(msg)): + with patch("github_repo_auditor.cli.print_info", side_effect=lambda msg: captured_output.append(msg)): with patch( "sys.argv", [ @@ -520,7 +520,7 @@ def test_boot_no_truth_file_exits_cleanly(self) -> None: ], ): try: - from src.cli import main + from github_repo_auditor.cli import main main() except SystemExit: @@ -540,7 +540,7 @@ def _make_approved_packet( status: str = "approved-manual", ) -> tuple[CampaignPlanPacket, dict]: """Return a (packet, ledger_record) pair for test setup.""" - from src.plan_campaign import _goal_subject_key, _packet_record_id + from github_repo_auditor.plan_campaign import _goal_subject_key, _packet_record_id generated_at = generated_at or _recent_generated_at() actions = [ @@ -594,7 +594,7 @@ def _make_approved_packet( class TestLoadApprovedCampaignPlans: def test_returns_only_approved_manual_campaign_plan_records(self) -> None: - from src.warehouse import save_approval_record + from github_repo_auditor.warehouse import save_approval_record with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) @@ -613,7 +613,7 @@ def test_returns_only_approved_manual_campaign_plan_records(self) -> None: assert packets[0].goal == "goal approved" def test_skips_packets_older_than_30_days(self) -> None: - from src.warehouse import save_approval_record + from github_repo_auditor.warehouse import save_approval_record with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) @@ -637,7 +637,7 @@ def test_empty_warehouse_returns_empty_list(self) -> None: assert packets == [] def test_hydrates_actions_into_campaign_action_dataclasses(self) -> None: - from src.warehouse import save_approval_record + from github_repo_auditor.warehouse import save_approval_record with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) @@ -696,7 +696,7 @@ def test_archive_calls_apply_metadata_updates_with_right_shape(self) -> None: action = self._make_action("archive", repo_name="my-repo") mock_client = MagicMock() - with patch("src.repo_improver.apply_metadata_updates") as mock_apply: + with patch("github_repo_auditor.repo_improver.apply_metadata_updates") as mock_apply: mock_apply.return_value = [ {"repo": "my-repo", "actions": [{"type": "archived", "ok": True}]} ] @@ -717,7 +717,7 @@ def test_add_topics_calls_apply_metadata_updates(self) -> None: action = self._make_action("add_topics", target="python cli tool") mock_client = MagicMock() - with patch("src.repo_improver.apply_metadata_updates") as mock_apply: + with patch("github_repo_auditor.repo_improver.apply_metadata_updates") as mock_apply: mock_apply.return_value = [ {"repo": "test-repo", "actions": [{"type": "topics", "ok": True}]} ] @@ -734,7 +734,7 @@ def test_add_topics_calls_apply_metadata_updates(self) -> None: def test_dry_run_does_not_call_executor(self) -> None: action = self._make_action("archive", repo_name="dry-repo") - with patch("src.repo_improver.apply_metadata_updates") as mock_apply: + with patch("github_repo_auditor.repo_improver.apply_metadata_updates") as mock_apply: ok, msg = dispatch_action(action, client=MagicMock(), owner="user", dry_run=True) mock_apply.assert_not_called() @@ -744,7 +744,7 @@ def test_dry_run_does_not_call_executor(self) -> None: def test_dry_run_pending_human_action_still_returns_false(self) -> None: """pending_human_action is always skipped — even in dry-run.""" action = self._make_action("pending_human_action") - with patch("src.repo_improver.apply_metadata_updates") as mock_apply: + with patch("github_repo_auditor.repo_improver.apply_metadata_updates") as mock_apply: ok, msg = dispatch_action(action, client=MagicMock(), owner="user", dry_run=True) mock_apply.assert_not_called() assert ok is False @@ -752,7 +752,7 @@ def test_dry_run_pending_human_action_still_returns_false(self) -> None: def test_apply_readme_calls_apply_readme_updates(self) -> None: action = self._make_action("apply_readme", target="# Hello\nThis is the readme.") - with patch("src.repo_improver.apply_readme_updates") as mock_apply: + with patch("github_repo_auditor.repo_improver.apply_readme_updates") as mock_apply: mock_apply.return_value = [{"repo": "test-repo", "ok": True}] ok, msg = dispatch_action(action, client=MagicMock(), owner="user", dry_run=False) @@ -770,15 +770,15 @@ class TestLedgerStateTransitions: def _write_approved_record( self, output_dir: Path, goal: str = "test goal" ) -> CampaignPlanPacket: - from src.warehouse import save_approval_record + from github_repo_auditor.warehouse import save_approval_record packet, record = _make_approved_packet(goal) save_approval_record(output_dir, record) return packet def test_mark_campaign_applied_updates_status_to_applied(self) -> None: - from src.plan_campaign import _packet_record_id - from src.warehouse import load_approval_records + from github_repo_auditor.plan_campaign import _packet_record_id + from github_repo_auditor.warehouse import load_approval_records with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) @@ -801,7 +801,7 @@ def test_mark_campaign_applied_noop_on_missing_record(self) -> None: mark_campaign_applied(packet, output_dir) # must not raise def test_record_campaign_apply_failure_writes_followup_event(self) -> None: - from src.warehouse import load_approval_records + from github_repo_auditor.warehouse import load_approval_records with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) @@ -810,7 +810,7 @@ def test_record_campaign_apply_failure_writes_followup_event(self) -> None: record_campaign_apply_failure(packet, "some error occurred", output_dir) # Record should still be approved-manual (not applied) - from src.plan_campaign import _packet_record_id + from github_repo_auditor.plan_campaign import _packet_record_id records = load_approval_records(output_dir, "", limit=50) record_id = _packet_record_id(packet) @@ -831,8 +831,8 @@ def _write_packet_with_actions( actions: list[CampaignAction], goal: str = "e2e test goal", ) -> CampaignPlanPacket: - from src.plan_campaign import _goal_subject_key, _packet_record_id - from src.warehouse import save_approval_record + from github_repo_auditor.plan_campaign import _goal_subject_key, _packet_record_id + from github_repo_auditor.warehouse import save_approval_record packet = CampaignPlanPacket( goal=goal, @@ -877,8 +877,8 @@ def _write_packet_with_actions( def test_packet_with_two_supported_and_one_unsupported_marks_applied(self) -> None: """2 archive/topics succeed + 1 add_codeowners (unsupported) → packet marked applied.""" - from src.plan_campaign import _packet_record_id - from src.warehouse import load_approval_records + from github_repo_auditor.plan_campaign import _packet_record_id + from github_repo_auditor.warehouse import load_approval_records with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) @@ -889,7 +889,7 @@ def test_packet_with_two_supported_and_one_unsupported_marks_applied(self) -> No ] packet = self._write_packet_with_actions(output_dir, actions) - with patch("src.repo_improver.apply_metadata_updates") as mock_meta: + with patch("github_repo_auditor.repo_improver.apply_metadata_updates") as mock_meta: mock_meta.return_value = [ {"repo": "repo-a", "actions": [{"type": "archived", "ok": True}]}, ] @@ -933,8 +933,8 @@ def test_packet_with_two_supported_and_one_unsupported_marks_applied(self) -> No def test_packet_with_supported_failure_stays_approved_manual(self) -> None: """When a supported action fails, record_campaign_apply_failure is called, packet stays approved-manual.""" - from src.plan_campaign import _packet_record_id - from src.warehouse import load_approval_records + from github_repo_auditor.plan_campaign import _packet_record_id + from github_repo_auditor.warehouse import load_approval_records with tempfile.TemporaryDirectory() as tmp: output_dir = Path(tmp) @@ -943,7 +943,7 @@ def test_packet_with_supported_failure_stays_approved_manual(self) -> None: ] packet = self._write_packet_with_actions(output_dir, actions, goal="failure e2e test") - with patch("src.repo_improver.apply_metadata_updates") as mock_meta: + with patch("github_repo_auditor.repo_improver.apply_metadata_updates") as mock_meta: mock_meta.return_value = [ {"repo": "repo-a", "actions": [{"ok": False, "error": "API error"}]} ] diff --git a/tests/test_plugin_loader.py b/tests/test_plugin_loader.py index 2b6e4093..42c2a1a0 100644 --- a/tests/test_plugin_loader.py +++ b/tests/test_plugin_loader.py @@ -5,15 +5,15 @@ import pytest -from src.analyzers import load_custom_analyzers -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.analyzers import load_custom_analyzers +from github_repo_auditor.models import AnalyzerResult, RepoMetadata def _make_analyzer_source(name: str = "custom_test", weight: float = 0.5) -> str: return f"""\ from pathlib import Path -from src.analyzers.base import BaseAnalyzer -from src.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.analyzers.base import BaseAnalyzer +from github_repo_auditor.models import AnalyzerResult, RepoMetadata class CustomTestAnalyzer(BaseAnalyzer): name = "{name}" diff --git a/tests/test_portfolio_automation.py b/tests/test_portfolio_automation.py index 7725f91c..0c3b7589 100644 --- a/tests/test_portfolio_automation.py +++ b/tests/test_portfolio_automation.py @@ -10,13 +10,13 @@ from copy import deepcopy -from src.portfolio_automation import ( +from github_repo_auditor.portfolio_automation import ( AutomationCandidate, AutomationEligibility, evaluate_automation_eligibility, select_automation_candidates, ) -from src.weekly_command_center import ( +from github_repo_auditor.weekly_command_center import ( build_weekly_command_center_digest, render_weekly_command_center_markdown, ) diff --git a/tests/test_portfolio_catalog.py b/tests/test_portfolio_catalog.py index ef76840d..2af49310 100644 --- a/tests/test_portfolio_catalog.py +++ b/tests/test_portfolio_catalog.py @@ -5,7 +5,7 @@ from pathlib import Path -from src.portfolio_catalog import ( +from github_repo_auditor.portfolio_catalog import ( build_catalog_line, build_intent_alignment_summary, build_portfolio_catalog_summary, @@ -14,7 +14,7 @@ group_entry_for_path, load_portfolio_catalog, ) -from src.portfolio_pathing import resolve_declared_operating_path +from github_repo_auditor.portfolio_pathing import resolve_declared_operating_path def test_load_portfolio_catalog_accepts_defaults_and_repo_entries(tmp_path: Path): diff --git a/tests/test_portfolio_context_contract.py b/tests/test_portfolio_context_contract.py index 739f5134..56aa50e8 100644 --- a/tests/test_portfolio_context_contract.py +++ b/tests/test_portfolio_context_contract.py @@ -1,6 +1,6 @@ from pathlib import Path -from src.portfolio_context_contract import ( +from github_repo_auditor.portfolio_context_contract import ( analyze_project_context, has_substantive_readme_support, ) diff --git a/tests/test_portfolio_context_triage.py b/tests/test_portfolio_context_triage.py index b1959fa7..96cde629 100644 --- a/tests/test_portfolio_context_triage.py +++ b/tests/test_portfolio_context_triage.py @@ -3,7 +3,7 @@ import json -from src.portfolio_context_triage import ( +from github_repo_auditor.portfolio_context_triage import ( FailureMode, TriageEntry, assess_repo_failure_modes, diff --git a/tests/test_portfolio_decision_queue.py b/tests/test_portfolio_decision_queue.py index ab62ccc4..b0bd9717 100644 --- a/tests/test_portfolio_decision_queue.py +++ b/tests/test_portfolio_decision_queue.py @@ -8,7 +8,7 @@ import pytest -from src.portfolio_decision_queue import ( +from github_repo_auditor.portfolio_decision_queue import ( DIGEST_CONTRACT_VERSION, SECURITY_DECISION_VALIDITY_HOURS, build_decision_digest, @@ -490,7 +490,12 @@ def test_absolute_cli_entrypoint_runs_from_arbitrary_cwd(tmp_path: Path) -> None ), encoding="utf-8", ) - script = Path(__file__).resolve().parents[1] / "src" / "portfolio_decision_queue.py" + script = ( + Path(__file__).resolve().parents[1] + / "src" + / "github_repo_auditor" + / "portfolio_decision_queue.py" + ) unrelated_cwd = tmp_path / "unrelated-cwd" unrelated_cwd.mkdir() diff --git a/tests/test_portfolio_pathing.py b/tests/test_portfolio_pathing.py index 2380dc3b..c8de9d0c 100644 --- a/tests/test_portfolio_pathing.py +++ b/tests/test_portfolio_pathing.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.portfolio_pathing import ( +from github_repo_auditor.portfolio_pathing import ( INVESTIGATE_OVERRIDE, build_operating_path_entry, build_operating_path_line, diff --git a/tests/test_portfolio_readme.py b/tests/test_portfolio_readme.py index 4c9d3499..4cdcafc0 100644 --- a/tests/test_portfolio_readme.py +++ b/tests/test_portfolio_readme.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.portfolio_readme import export_portfolio_readme +from github_repo_auditor.portfolio_readme import export_portfolio_readme def _make_report(**overrides) -> dict: diff --git a/tests/test_portfolio_repository_state.py b/tests/test_portfolio_repository_state.py index 382d1c2e..9ea8c0c2 100644 --- a/tests/test_portfolio_repository_state.py +++ b/tests/test_portfolio_repository_state.py @@ -8,14 +8,14 @@ import pytest -import src.portfolio_repository_state as repository_state -from src.portfolio_repository_state import ( +import github_repo_auditor.portfolio_repository_state as repository_state +from github_repo_auditor.portfolio_repository_state import ( _local_from_worktree, _observed_result, _select_remote_default_worktree, observe_repository_state, ) -from src.portfolio_truth_validate import _validate_repository_state_shape +from github_repo_auditor.portfolio_truth_validate import _validate_repository_state_shape def _git(path: Path, *args: str) -> str: diff --git a/tests/test_portfolio_risk.py b/tests/test_portfolio_risk.py index 47fa3f85..30c7bc07 100644 --- a/tests/test_portfolio_risk.py +++ b/tests/test_portfolio_risk.py @@ -1,4 +1,4 @@ -from src.portfolio_risk import build_portfolio_risk_summary, build_risk_entry +from github_repo_auditor.portfolio_risk import build_portfolio_risk_summary, build_risk_entry def _baseline_kwargs(**overrides): diff --git a/tests/test_portfolio_security_gate.py b/tests/test_portfolio_security_gate.py index fc3a896b..a87b20aa 100644 --- a/tests/test_portfolio_security_gate.py +++ b/tests/test_portfolio_security_gate.py @@ -6,8 +6,8 @@ import pytest -from src.cli import _run_security_gate_mode, build_subcommand_parser -from src.portfolio_security_gate import ( +from github_repo_auditor.cli import _run_security_gate_mode, build_subcommand_parser +from github_repo_auditor.portfolio_security_gate import ( build_security_gate_report, render_security_gate_markdown, ) diff --git a/tests/test_portfolio_truth.py b/tests/test_portfolio_truth.py index cb0dff84..1a4e4820 100644 --- a/tests/test_portfolio_truth.py +++ b/tests/test_portfolio_truth.py @@ -13,8 +13,8 @@ import pytest -from src.cli import main -from src.github_security_coverage import ( +from github_repo_auditor.cli import main +from github_repo_auditor.github_security_coverage import ( GITHUB_SECURITY_RECEIPT_SCHEMA_VERSION, SecurityCoverageReceiptBinding, _provider_result, @@ -24,41 +24,41 @@ load_security_coverage_receipt, write_security_coverage_receipt, ) -from src.portfolio_decision_queue import build_decision_queue -from src.portfolio_context_recovery import ( +from github_repo_auditor.portfolio_decision_queue import build_decision_queue +from github_repo_auditor.portfolio_context_recovery import ( apply_context_recovery_plan, build_context_recovery_plan, ) -from src.portfolio_checkout_authority import ( +from github_repo_auditor.portfolio_checkout_authority import ( checkout_authority_blocker, checkout_authority_path, validate_checkout_authority_envelope, ) -from src.portfolio_truth_publish import ( +from github_repo_auditor.portfolio_truth_publish import ( PortfolioTruthPublishError, publish_portfolio_truth, ) -from src.producer_preflight import ProducerEvidence, producer_evidence_receipt_id -from src.portfolio_truth_reconcile import build_portfolio_truth_snapshot -from src.portfolio_truth_render import ( +from github_repo_auditor.producer_preflight import ProducerEvidence, producer_evidence_receipt_id +from github_repo_auditor.portfolio_truth_reconcile import build_portfolio_truth_snapshot +from github_repo_auditor.portfolio_truth_render import ( render_portfolio_report_markdown, render_registry_markdown, ) -from src.portfolio_truth_provenance import REQUIRED_PROJECT_PROVENANCE_KEYS -from src.portfolio_truth_sources import ( +from github_repo_auditor.portfolio_truth_provenance import REQUIRED_PROJECT_PROVENANCE_KEYS +from github_repo_auditor.portfolio_truth_sources import ( _classify_context_quality, _extract_github_full_name, _git_read, _git_remote_full_name, load_safe_notion_project_context, ) -from src.portfolio_truth_validate import ( +from github_repo_auditor.portfolio_truth_validate import ( canonicalize_prior_security_truth_payload, validate_portfolio_report_markdown, validate_truth_snapshot, ) -from src.project_registry import build_project_registry -from src.registry_parser import parse_registry +from github_repo_auditor.project_registry import build_project_registry +from github_repo_auditor.registry_parser import parse_registry def _write(path: Path, content: str) -> None: @@ -136,7 +136,7 @@ def _security_test_project( tier: str = "elevated", ): """Minimal PortfolioTruthProject for exercising security render helpers directly.""" - from src.portfolio_truth_types import ( + from github_repo_auditor.portfolio_truth_types import ( DeclaredFields, DerivedFields, IdentityFields, @@ -356,7 +356,7 @@ def test_notion_context_uses_configured_title_aliases( ) monkeypatch.setattr( - "src.portfolio_truth_sources.load_notion_project_context", + "github_repo_auditor.portfolio_truth_sources.load_notion_project_context", lambda _config_dir: { "Notion Operating System": { "portfolio_call": "Build Now", @@ -401,7 +401,7 @@ def test_notion_context_uses_fresh_verified_snapshot_when_live_api_unavailable( ) ) monkeypatch.setattr( - "src.portfolio_truth_sources.load_notion_project_context", + "github_repo_auditor.portfolio_truth_sources.load_notion_project_context", lambda _config_dir: None, ) @@ -436,7 +436,7 @@ def test_notion_context_rejects_snapshot_without_verified_live_receipt( ) ) monkeypatch.setattr( - "src.portfolio_truth_sources.load_notion_project_context", + "github_repo_auditor.portfolio_truth_sources.load_notion_project_context", lambda _config_dir: None, ) @@ -454,7 +454,7 @@ def test_notion_context_rejects_non_object_snapshot_json( snapshot_path = tmp_path / "project-snapshot.json" snapshot_path.write_text("[]") monkeypatch.setattr( - "src.portfolio_truth_sources.load_notion_project_context", + "github_repo_auditor.portfolio_truth_sources.load_notion_project_context", lambda _config_dir: None, ) @@ -682,7 +682,7 @@ def test_prior_security_loader_accepts_bounded_legacy_truth( portfolio_catalog: Path, legacy_registry: Path, ) -> None: - import src.portfolio_truth_publish as publish_mod + import github_repo_auditor.portfolio_truth_publish as publish_mod payload, metadata, _ = _legacy_prior_security_payload( portfolio_workspace=portfolio_workspace, @@ -713,7 +713,7 @@ def test_prior_security_loader_allows_same_receipt_truth_generated_after_receipt portfolio_catalog: Path, legacy_registry: Path, ) -> None: - import src.portfolio_truth_publish as publish_mod + import github_repo_auditor.portfolio_truth_publish as publish_mod _, metadata, payload = _legacy_prior_security_payload( portfolio_workspace=portfolio_workspace, @@ -750,7 +750,7 @@ def test_prior_security_loader_refuses_future_truth_from_different_receipt( portfolio_catalog: Path, legacy_registry: Path, ) -> None: - import src.portfolio_truth_publish as publish_mod + import github_repo_auditor.portfolio_truth_publish as publish_mod _, metadata, payload = _legacy_prior_security_payload( portfolio_workspace=portfolio_workspace, @@ -868,7 +868,7 @@ def discover_with_unsorted_collisions( return [] monkeypatch.setattr( - "src.portfolio_truth_reconcile.discover_workspace_projects", + "github_repo_auditor.portfolio_truth_reconcile.discover_workspace_projects", discover_with_unsorted_collisions, ) result = build_portfolio_truth_snapshot( @@ -1220,7 +1220,7 @@ def _timeout_status(project_path: Path, *args: str) -> str: return _git_read(project_path, *args) monkeypatch.setattr( - "src.portfolio_truth_sources._git_read", + "github_repo_auditor.portfolio_truth_sources._git_read", _timeout_status, ) @@ -1377,7 +1377,7 @@ def _timeout_worktree_enumeration(_project_path: Path) -> list[Path]: raise subprocess.TimeoutExpired(["git", "worktree", "list"], timeout=5) monkeypatch.setattr( - "src.portfolio_truth_sources._git_worktree_paths", + "github_repo_auditor.portfolio_truth_sources._git_worktree_paths", _timeout_worktree_enumeration, ) @@ -1709,7 +1709,7 @@ def test_discovered_bare_coordinator_sibling_preserves_identity_and_mutation_pat """ ) monkeypatch.setattr( - "src.portfolio_truth_reconcile.load_safe_notion_project_context", + "github_repo_auditor.portfolio_truth_reconcile.load_safe_notion_project_context", lambda: { "repo": { "portfolio_call": "Maintain", @@ -2250,8 +2250,8 @@ def test_truth_snapshot_operating_path_catalog_field_matches_legacy_disposition( def test_finish_attention_flips_exactly_at_31_day_activity_boundary() -> None: from zoneinfo import ZoneInfo - from src.portfolio_truth_decisions import derive_attention_state - from src.portfolio_truth_reconcile import _activity_status_for + from github_repo_auditor.portfolio_truth_decisions import derive_attention_state + from github_repo_auditor.portfolio_truth_reconcile import _activity_status_for last_activity = datetime(2026, 7, 4, 7, 44, 49, tzinfo=timezone.utc) before_boundary = last_activity + timedelta(days=31) - timedelta(microseconds=1) @@ -2285,8 +2285,8 @@ def attention_at(now: datetime) -> str: def test_attention_state_classifier_separates_activity_from_operator_attention() -> ( None ): - from src.portfolio_truth_decisions import derive_attention_state - from src.portfolio_truth_types import VALID_LIFECYCLE_STATES + from github_repo_auditor.portfolio_truth_decisions import derive_attention_state + from github_repo_auditor.portfolio_truth_types import VALID_LIFECYCLE_STATES assert "manual-only" in VALID_LIFECYCLE_STATES @@ -2485,8 +2485,8 @@ def test_attention_state_uses_resolved_catalog_operating_path( intended_disposition: str, expected_attention: str, ) -> None: - from src.portfolio_pathing import build_operating_path_entry - from src.portfolio_truth_decisions import derive_attention_state + from github_repo_auditor.portfolio_pathing import build_operating_path_entry + from github_repo_auditor.portfolio_truth_decisions import derive_attention_state catalog_entry = build_operating_path_entry( { @@ -2552,7 +2552,7 @@ def test_github_archived_status_reconciles_to_archived_attention( assert alpha.derived.activity_status == "active" assert alpha.derived.archived is True assert alpha.derived.attention_state == "archived" - from src.portfolio_truth_types import display_activity_status + from github_repo_auditor.portfolio_truth_types import display_activity_status assert ( display_activity_status( @@ -2677,7 +2677,7 @@ def test_receipt_archived_state_is_fallback_when_live_status_is_unavailable( def test_build_security_fields_maps_ghas_entry() -> None: - from src.portfolio_truth_reconcile import _build_security_fields + from github_repo_auditor.portfolio_truth_reconcile import _build_security_fields fields = _build_security_fields( { @@ -2704,7 +2704,7 @@ def test_build_security_fields_maps_ghas_entry() -> None: def test_build_security_fields_none_is_unscanned() -> None: - from src.portfolio_truth_reconcile import _build_security_fields + from github_repo_auditor.portfolio_truth_reconcile import _build_security_fields fields = _build_security_fields(None) assert fields.alerts_available is False @@ -2713,7 +2713,7 @@ def test_build_security_fields_none_is_unscanned() -> None: def test_build_security_fields_unavailable_dependabot_is_not_available() -> None: - from src.portfolio_truth_reconcile import _build_security_fields + from github_repo_auditor.portfolio_truth_reconcile import _build_security_fields fields = _build_security_fields( { @@ -2728,7 +2728,7 @@ def test_build_security_fields_unavailable_dependabot_is_not_available() -> None def test_dependabot_only_clean_is_partial_not_combined_security_coverage() -> None: # A clean Dependabot observation must not stand in for combined GitHub # security coverage when code and secret scanning were not observed. - from src.portfolio_truth_reconcile import _build_security_fields + from github_repo_auditor.portfolio_truth_reconcile import _build_security_fields fields = _build_security_fields({"dependabot": {"available": True}}) assert fields.alerts_available is False @@ -3195,7 +3195,7 @@ def test_security_receipt_rejects_same_count_identity_rollover(tmp_path: Path) - def test_security_cohort_identity_skips_repo_less_supplementary() -> None: from types import SimpleNamespace - from src.portfolio_truth_reconcile import ( + from github_repo_auditor.portfolio_truth_reconcile import ( _validate_security_receipt_cohort_identity, ) @@ -3225,7 +3225,7 @@ def test_security_cohort_identity_skips_repo_less_supplementary() -> None: def test_security_cohort_identity_rejects_repo_backed_supplementary() -> None: from types import SimpleNamespace - from src.portfolio_truth_reconcile import ( + from github_repo_auditor.portfolio_truth_reconcile import ( _validate_security_receipt_cohort_identity, ) @@ -3375,7 +3375,7 @@ def test_receipt_backed_snapshot_excludes_repo_less_supplementary_from_cohort( def test_security_cohort_identity_rejects_case_only_drift() -> None: from types import SimpleNamespace - from src.portfolio_truth_reconcile import ( + from github_repo_auditor.portfolio_truth_reconcile import ( _validate_security_receipt_cohort_identity, ) @@ -3408,7 +3408,7 @@ def test_security_cohort_identity_rejects_expansion_and_contraction( ) -> None: from types import SimpleNamespace - from src.portfolio_truth_reconcile import ( + from github_repo_auditor.portfolio_truth_reconcile import ( _validate_security_receipt_cohort_identity, ) @@ -3435,7 +3435,7 @@ def test_security_cohort_identity_rejects_expansion_and_contraction( def test_security_cohort_identity_rejects_missing_repository_name() -> None: from types import SimpleNamespace - from src.portfolio_truth_reconcile import ( + from github_repo_auditor.portfolio_truth_reconcile import ( _validate_security_receipt_cohort_identity, ) @@ -4045,7 +4045,7 @@ def metadata(*, observed_at: datetime, marker: str, count: int = 1) -> dict: second_at = first_at + timedelta(hours=1) write_catalog("manual-only") from contextlib import contextmanager - from src import portfolio_truth_publish as publish_module + from github_repo_auditor import portfolio_truth_publish as publish_module original_lock = publish_module._portfolio_truth_publication_lock original_verify = publish_module._verify_prior_security_evidence_current @@ -4277,7 +4277,7 @@ def test_security_input_rejects_clock_skew_beyond_tolerance( def test_select_security_entry_joins_by_repo_name_when_display_differs() -> None: # GHAS is keyed by repo name ("signal-noise"); the local dir is "Signal & Noise". - from src.portfolio_truth_reconcile import _select_security_entry + from github_repo_auditor.portfolio_truth_reconcile import _select_security_entry entry = {"dependabot": {"high": 9, "available": True}} lookup = {"signal-noise": entry} @@ -4288,7 +4288,7 @@ def test_select_security_entry_joins_by_repo_name_when_display_differs() -> None def test_select_security_entry_falls_back_to_display_name() -> None: - from src.portfolio_truth_reconcile import _select_security_entry + from github_repo_auditor.portfolio_truth_reconcile import _select_security_entry entry = {"dependabot": {"high": 1, "available": True}} # No repo_full_name (local-only repo) → must fall back to display_name. @@ -4296,7 +4296,7 @@ def test_select_security_entry_falls_back_to_display_name() -> None: def test_select_security_entry_prefers_repo_name_over_display() -> None: - from src.portfolio_truth_reconcile import _select_security_entry + from github_repo_auditor.portfolio_truth_reconcile import _select_security_entry by_repo = {"dependabot": {"high": 2, "available": True}} by_display = {"dependabot": {"high": 5, "available": True}} @@ -4305,7 +4305,7 @@ def test_select_security_entry_prefers_repo_name_over_display() -> None: def test_select_security_entry_returns_none_when_unmatched() -> None: - from src.portfolio_truth_reconcile import _select_security_entry + from github_repo_auditor.portfolio_truth_reconcile import _select_security_entry assert _select_security_entry({"other": {}}, "owner/missing", "AlsoMissing") is None @@ -4348,7 +4348,7 @@ def _fake_git_facts(project_path: Path) -> dict[str, object]: return {"has_git": False, "last_commit_at": None, "repo_full_name": ""} monkeypatch.setattr( - "src.portfolio_truth_sources._gather_git_facts", _fake_git_facts + "github_repo_auditor.portfolio_truth_sources._gather_git_facts", _fake_git_facts ) result = build_portfolio_truth_snapshot( @@ -4803,7 +4803,7 @@ def test_portfolio_report_security_posture_scanned_clear( def test_security_attention_items_caps_at_five_and_sorts_critical_first() -> None: - from src.portfolio_truth_render import ( + from github_repo_auditor.portfolio_truth_render import ( MAX_SECURITY_ATTENTION_ITEMS, _security_attention_items, ) @@ -4987,7 +4987,7 @@ def test_publish_uses_bound_security_max_age_for_remote_evidence( from contextlib import nullcontext monkeypatch.setattr( - "src.portfolio_truth_publish.verified_security_coverage_receipt_binding", + "github_repo_auditor.portfolio_truth_publish.verified_security_coverage_receipt_binding", lambda _binding: nullcontext(), ) now = datetime.now(timezone.utc) @@ -5344,7 +5344,7 @@ def _boom(_snapshot, _latest_json_path): raise RuntimeError("renderer exploded") monkeypatch.setattr( - "src.portfolio_truth_publish.render_portfolio_report_markdown", _boom + "github_repo_auditor.portfolio_truth_publish.render_portfolio_report_markdown", _boom ) with pytest.raises(RuntimeError): @@ -5427,7 +5427,7 @@ def test_publish_refuses_receipt_pointer_replacement_after_load( registry_output.write_text("sentinel-registry\n") report_output.write_text("sentinel-report\n") - from src import portfolio_truth_publish as publish_module + from github_repo_auditor import portfolio_truth_publish as publish_module original_stage = publish_module._stage_text replaced = False @@ -5548,7 +5548,7 @@ def test_publish_refuses_prior_truth_pointer_replacement_after_load( "path": "/evidence/github-security-coverage-latest.json", } - from src import portfolio_truth_publish as publish_module + from github_repo_auditor import portfolio_truth_publish as publish_module original_stage = publish_module._stage_text replaced = False @@ -5756,11 +5756,11 @@ def reloaded_guard(_binding: SecurityCoverageReceiptBinding): events.append("guard-exit") monkeypatch.setattr( - "src.portfolio_truth_publish.verify_evidence_still_current", + "github_repo_auditor.portfolio_truth_publish.verify_evidence_still_current", advance_during_final_producer_verification, ) monkeypatch.setattr( - "src.portfolio_truth_publish.verified_security_coverage_receipt_binding", + "github_repo_auditor.portfolio_truth_publish.verified_security_coverage_receipt_binding", reloaded_guard, ) @@ -5880,11 +5880,11 @@ def test_publish_refuses_to_drop_existing_notion_context( report_output = portfolio_workspace / "PORTFOLIO-AUDIT-REPORT.md" monkeypatch.setattr( - "src.portfolio_truth_sources.load_notion_project_context", + "github_repo_auditor.portfolio_truth_sources.load_notion_project_context", lambda _config_dir: None, ) monkeypatch.setattr( - "src.portfolio_truth_publish._notion_project_context_configured", + "github_repo_auditor.portfolio_truth_publish._notion_project_context_configured", lambda: True, ) @@ -5906,8 +5906,8 @@ def test_publish_refuses_to_drop_existing_notion_context( def test_load_prior_notion_context_rebuilds_from_artifact(tmp_path: Path) -> None: - from src.portfolio_truth_reconcile import load_prior_notion_context - from src.registry_parser import _normalize + from github_repo_auditor.portfolio_truth_reconcile import load_prior_notion_context + from github_repo_auditor.registry_parser import _normalize latest_path = tmp_path / "portfolio-truth-latest.json" latest_path.write_text( @@ -5951,7 +5951,7 @@ def test_load_prior_notion_context_rebuilds_from_artifact(tmp_path: Path) -> Non def test_load_prior_notion_context_missing_or_malformed_returns_empty( tmp_path: Path, ) -> None: - from src.portfolio_truth_reconcile import load_prior_notion_context + from github_repo_auditor.portfolio_truth_reconcile import load_prior_notion_context assert load_prior_notion_context(tmp_path / "absent.json") == {} malformed = tmp_path / "malformed.json" @@ -5962,7 +5962,7 @@ def test_load_prior_notion_context_missing_or_malformed_returns_empty( def test_notion_origin_resolution_preserves_oldest_carried_observation( tmp_path: Path, ) -> None: - from src.portfolio_truth_lineage import resolve_notion_origin + from github_repo_auditor.portfolio_truth_lineage import resolve_notion_origin oldest = "2026-07-10T09:00:10+00:00" predecessor_generated = "2026-07-11T03:17:38+00:00" @@ -6001,7 +6001,7 @@ def test_notion_origin_resolution_preserves_oldest_carried_observation( def test_notion_origin_resolution_uses_legacy_generation_time(tmp_path: Path) -> None: - from src.portfolio_truth_lineage import resolve_notion_origin + from github_repo_auditor.portfolio_truth_lineage import resolve_notion_origin artifact = tmp_path / "portfolio-truth-latest.json" artifact.write_text(json.dumps({"generated_at": "2026-07-10T09:00:10+00:00"})) @@ -6023,11 +6023,11 @@ def test_publish_allow_empty_notion_carries_forward_prior_context( # Live Notion unavailable (token lost) - the exact condition that breaks the nightly job. monkeypatch.setattr( - "src.portfolio_truth_sources.load_notion_project_context", + "github_repo_auditor.portfolio_truth_sources.load_notion_project_context", lambda _config_dir: None, ) monkeypatch.setattr( - "src.portfolio_truth_publish._notion_project_context_configured", + "github_repo_auditor.portfolio_truth_publish._notion_project_context_configured", lambda: True, ) @@ -6099,11 +6099,11 @@ def test_publish_without_allow_empty_notion_still_guards( json.dumps({"source_summary": {"notion_context_rows": 137}}) + "\n" ) monkeypatch.setattr( - "src.portfolio_truth_sources.load_notion_project_context", + "github_repo_auditor.portfolio_truth_sources.load_notion_project_context", lambda _config_dir: None, ) monkeypatch.setattr( - "src.portfolio_truth_publish._notion_project_context_configured", + "github_repo_auditor.portfolio_truth_publish._notion_project_context_configured", lambda: True, ) @@ -6123,7 +6123,7 @@ def test_publish_without_allow_empty_notion_still_guards( def test_report_subcommand_parses_allow_empty_notion_flag() -> None: # The nightly job runs `audit report --portfolio-truth`; the new flag # must be accepted on that exact path and default to opt-in off. - from src.cli import build_subcommand_parser + from github_repo_auditor.cli import build_subcommand_parser parser = build_subcommand_parser() enabled = parser.parse_args( @@ -6140,8 +6140,8 @@ def test_report_subcommand_parses_allow_empty_notion_flag() -> None: def test_report_subcommand_parses_security_cohort_count() -> None: - from src.cli import build_subcommand_parser - from src.github_security_coverage import DEFAULT_EXPECTED_GITHUB_COHORT_COUNT + from github_repo_auditor.cli import build_subcommand_parser + from github_repo_auditor.github_security_coverage import DEFAULT_EXPECTED_GITHUB_COHORT_COUNT parser = build_subcommand_parser() explicit = parser.parse_args( @@ -6169,7 +6169,7 @@ def test_portfolio_truth_app_threads_security_cohort_count( ) -> None: from types import SimpleNamespace - from src.app.portfolio_truth import run_portfolio_truth_mode + from github_repo_auditor.app.portfolio_truth import run_portfolio_truth_mode captured: dict[str, object] = {} @@ -6194,12 +6194,12 @@ def fake_publish(**kwargs): ) monkeypatch.setattr( - "src.app.portfolio_truth.load_security_coverage_by_full_name", + "github_repo_auditor.app.portfolio_truth.load_security_coverage_by_full_name", fake_security_loader, ) - monkeypatch.setattr("src.app.portfolio_truth.publish_portfolio_truth", fake_publish) + monkeypatch.setattr("github_repo_auditor.app.portfolio_truth.publish_portfolio_truth", fake_publish) monkeypatch.setattr( - "src.app.portfolio_truth.load_live_repo_status_by_name", + "github_repo_auditor.app.portfolio_truth.load_live_repo_status_by_name", lambda **kwargs: ( captured.setdefault("repo_status_cache", kwargs["cache"]) or {} ), @@ -6238,8 +6238,8 @@ def test_portfolio_truth_app_carries_security_receipt_binding_to_publisher( ) -> None: from types import SimpleNamespace - from src.app.portfolio_truth import run_portfolio_truth_mode - from src.github_security_coverage import SecurityCoverageReceiptBinding + from github_repo_auditor.app.portfolio_truth import run_portfolio_truth_mode + from github_repo_auditor.github_security_coverage import SecurityCoverageReceiptBinding receipt_path = tmp_path / "github-security-coverage-latest.json" binding = SecurityCoverageReceiptBinding( @@ -6268,11 +6268,11 @@ def test_portfolio_truth_app_carries_security_receipt_binding_to_publisher( captured: dict[str, object] = {} monkeypatch.setattr( - "src.app.portfolio_truth.load_security_coverage_by_full_name", + "github_repo_auditor.app.portfolio_truth.load_security_coverage_by_full_name", lambda **_kwargs: loaded, ) monkeypatch.setattr( - "src.app.portfolio_truth.load_live_repo_status_by_name", lambda **_kwargs: {} + "github_repo_auditor.app.portfolio_truth.load_live_repo_status_by_name", lambda **_kwargs: {} ) def fake_publish(**kwargs): @@ -6287,7 +6287,7 @@ def fake_publish(**kwargs): report_changed=False, ) - monkeypatch.setattr("src.app.portfolio_truth.publish_portfolio_truth", fake_publish) + monkeypatch.setattr("github_repo_auditor.app.portfolio_truth.publish_portfolio_truth", fake_publish) monkeypatch.setenv("GHRA_REQUIRE_PRODUCER_EVIDENCE", "0") args = SimpleNamespace( output_dir=str(tmp_path / "output"), @@ -6321,8 +6321,8 @@ def test_canonical_portfolio_truth_refuses_legacy_security_receipt_identity( ) -> None: from types import SimpleNamespace - from src.app.portfolio_truth import run_portfolio_truth_mode - from src.github_security_coverage import SecurityCoverageError + from github_repo_auditor.app.portfolio_truth import run_portfolio_truth_mode + from github_repo_auditor.github_security_coverage import SecurityCoverageError def missing_binding(): raise SecurityCoverageError( @@ -6331,7 +6331,7 @@ def missing_binding(): loaded = SimpleNamespace(binding=missing_binding) monkeypatch.setattr( - "src.app.portfolio_truth.load_security_coverage_by_full_name", + "github_repo_auditor.app.portfolio_truth.load_security_coverage_by_full_name", lambda **_kwargs: loaded, ) monkeypatch.setenv("GHRA_REQUIRE_PRODUCER_EVIDENCE", "1") @@ -6363,8 +6363,8 @@ def test_portfolio_truth_app_passes_validated_producer_receipt_to_publisher( ) -> None: from types import SimpleNamespace - from src.app.portfolio_truth import run_portfolio_truth_mode - from src.producer_preflight import ( + from github_repo_auditor.app.portfolio_truth import run_portfolio_truth_mode + from github_repo_auditor.producer_preflight import ( PREFLIGHT_PASS_CHECKS, PREFLIGHT_SCHEMA_VERSION, producer_evidence_receipt_id, @@ -6414,9 +6414,9 @@ def fake_publish(**kwargs): report_changed=False, ) - monkeypatch.setattr("src.app.portfolio_truth.publish_portfolio_truth", fake_publish) + monkeypatch.setattr("github_repo_auditor.app.portfolio_truth.publish_portfolio_truth", fake_publish) monkeypatch.setattr( - "src.app.portfolio_truth.load_live_repo_status_by_name", lambda **_kwargs: {} + "github_repo_auditor.app.portfolio_truth.load_live_repo_status_by_name", lambda **_kwargs: {} ) monkeypatch.setenv("GHRA_REQUIRE_PRODUCER_EVIDENCE", "1") monkeypatch.setenv("GHRA_PRODUCER_EVIDENCE", str(receipt)) @@ -6483,11 +6483,11 @@ def test_cli_portfolio_truth_allow_empty_notion_carries_forward( + "\n" ) monkeypatch.setattr( - "src.portfolio_truth_sources.load_notion_project_context", + "github_repo_auditor.portfolio_truth_sources.load_notion_project_context", lambda _config_dir: None, ) monkeypatch.setattr( - "src.portfolio_truth_publish._notion_project_context_configured", + "github_repo_auditor.portfolio_truth_publish._notion_project_context_configured", lambda: True, ) argv = [ @@ -6531,11 +6531,11 @@ def test_publish_allow_empty_notion_without_prior_context_publishes_zero( + "\n" ) monkeypatch.setattr( - "src.portfolio_truth_sources.load_notion_project_context", + "github_repo_auditor.portfolio_truth_sources.load_notion_project_context", lambda _config_dir: None, ) monkeypatch.setattr( - "src.portfolio_truth_publish._notion_project_context_configured", + "github_repo_auditor.portfolio_truth_publish._notion_project_context_configured", lambda: True, ) @@ -7295,7 +7295,7 @@ def test_context_recovery_emits_drift_note_when_correcting( def test_git_default_branch_reads_local_origin_head(tmp_path: Path) -> None: - from src.portfolio_truth_sources import _git_default_branch + from github_repo_auditor.portfolio_truth_sources import _git_default_branch repo = tmp_path / "repo" repo.mkdir() @@ -7317,7 +7317,7 @@ def test_git_default_branch_reads_local_origin_head(tmp_path: Path) -> None: def test_git_default_branch_keeps_multi_segment_branch(tmp_path: Path) -> None: - from src.portfolio_truth_sources import _git_default_branch + from github_repo_auditor.portfolio_truth_sources import _git_default_branch repo = tmp_path / "repo" repo.mkdir() @@ -7339,7 +7339,7 @@ def test_git_default_branch_keeps_multi_segment_branch(tmp_path: Path) -> None: def test_git_default_branch_empty_when_origin_head_unset(tmp_path: Path) -> None: - from src.portfolio_truth_sources import _git_default_branch + from github_repo_auditor.portfolio_truth_sources import _git_default_branch repo = tmp_path / "repo" repo.mkdir() diff --git a/tests/test_portfolio_truth_contract_fixture.py b/tests/test_portfolio_truth_contract_fixture.py index 81f7199e..5564e1d4 100644 --- a/tests/test_portfolio_truth_contract_fixture.py +++ b/tests/test_portfolio_truth_contract_fixture.py @@ -11,13 +11,13 @@ import pytest -from src.demo_portfolio import resolved_coverage_state -from src.portfolio_truth_coverage import build_coverage_envelope -from src.portfolio_truth_metadata import build_source_summary, build_warnings -from src.portfolio_pathing import build_operating_path_entry -from src.portfolio_truth_precedence import PRECEDENCE_MATRIX -from src.portfolio_truth_provenance import REQUIRED_PROJECT_PROVENANCE_KEYS -from src.portfolio_truth_contract_fixture import ( +from github_repo_auditor.demo_portfolio import resolved_coverage_state +from github_repo_auditor.portfolio_truth_coverage import build_coverage_envelope +from github_repo_auditor.portfolio_truth_metadata import build_source_summary, build_warnings +from github_repo_auditor.portfolio_pathing import build_operating_path_entry +from github_repo_auditor.portfolio_truth_precedence import PRECEDENCE_MATRIX +from github_repo_auditor.portfolio_truth_provenance import REQUIRED_PROJECT_PROVENANCE_KEYS +from github_repo_auditor.portfolio_truth_contract_fixture import ( CONSUMER_PROFILE_MANIFEST_PATHS, CONTRACT_VERSION, EVALUATED_AT, @@ -36,16 +36,16 @@ manifest_bytes, portable_fixture_bytes, ) -from src.portfolio_truth_reconcile import _build_security_fields -from src.portfolio_truth_sources import WORKSPACE_DISCOVERY_POLICY_VERSION -from src.portfolio_truth_types import SCHEMA_VERSION -from src.portfolio_truth_validate import ( +from github_repo_auditor.portfolio_truth_reconcile import _build_security_fields +from github_repo_auditor.portfolio_truth_sources import WORKSPACE_DISCOVERY_POLICY_VERSION +from github_repo_auditor.portfolio_truth_types import SCHEMA_VERSION +from github_repo_auditor.portfolio_truth_validate import ( _snapshot_from_payload, _validate_security_fields, validate_truth_snapshot, validate_truth_snapshot_payload, ) -from src.producer_preflight import ( +from github_repo_auditor.producer_preflight import ( ProducerEvidence, producer_evidence_receipt_id, ) diff --git a/tests/test_portfolio_truth_publish_integrity.py b/tests/test_portfolio_truth_publish_integrity.py index 0deb2d89..689ce0eb 100644 --- a/tests/test_portfolio_truth_publish_integrity.py +++ b/tests/test_portfolio_truth_publish_integrity.py @@ -4,7 +4,7 @@ import pytest -import src.portfolio_truth_publish as publish_mod +import github_repo_auditor.portfolio_truth_publish as publish_mod @pytest.mark.parametrize("replaced_count", [1, 2, 3, 4, 5]) diff --git a/tests/test_portfolio_truth_sources.py b/tests/test_portfolio_truth_sources.py index ad427f16..a78f54f9 100644 --- a/tests/test_portfolio_truth_sources.py +++ b/tests/test_portfolio_truth_sources.py @@ -12,7 +12,7 @@ from datetime import datetime, timezone from pathlib import Path -from src.portfolio_truth_sources import ( +from github_repo_auditor.portfolio_truth_sources import ( WORKSPACE_EXCLUSION_REASONS, _dedupe_checkouts_by_origin, _is_ignored_project_dir, diff --git a/tests/test_portfolio_truth_strict_signals.py b/tests/test_portfolio_truth_strict_signals.py index 7324a6c8..9dd5ced0 100644 --- a/tests/test_portfolio_truth_strict_signals.py +++ b/tests/test_portfolio_truth_strict_signals.py @@ -11,7 +11,7 @@ import pytest -from src.portfolio_truth_reconcile import ( +from github_repo_auditor.portfolio_truth_reconcile import ( _derive_has_ci, _derive_has_license, _derive_has_tests, @@ -207,7 +207,7 @@ def _make_audit_json(tmp_path: Path, username: str, repo_name: str, release_coun def test_release_count_loaded_from_audit_json(tmp_path: Path) -> None: """--portfolio-truth-include-release-count with valid audit JSON → release_count == 3.""" - from src.portfolio_truth_status import load_release_count_by_name + from github_repo_auditor.portfolio_truth_status import load_release_count_by_name _make_audit_json(tmp_path, username="saagpatel", repo_name="MyRepo", release_count=3) result = load_release_count_by_name(output_dir=tmp_path, username="saagpatel") @@ -219,7 +219,7 @@ def test_release_count_no_audit_json_returns_none( tmp_path: Path, caplog: pytest.LogCaptureFixture ) -> None: """--portfolio-truth-include-release-count with no audit JSON → None returned, warning logged.""" - from src.portfolio_truth_status import load_release_count_by_name + from github_repo_auditor.portfolio_truth_status import load_release_count_by_name with caplog.at_level(logging.WARNING): result = load_release_count_by_name(output_dir=tmp_path, username="saagpatel") @@ -230,7 +230,7 @@ def test_release_count_no_audit_json_returns_none( def test_release_count_absent_for_missing_project(tmp_path: Path) -> None: """Project not in the audit report → release_count key absent from returned dict.""" - from src.portfolio_truth_status import load_release_count_by_name + from github_repo_auditor.portfolio_truth_status import load_release_count_by_name _make_audit_json(tmp_path, username="saagpatel", repo_name="KnownRepo", release_count=5) result = load_release_count_by_name(output_dir=tmp_path, username="saagpatel") @@ -248,7 +248,7 @@ def _make_ghas_json(tmp_path: Path, *, username: str, entries: dict) -> Path: def test_security_alerts_loaded_from_ghas_json(tmp_path: Path) -> None: """--portfolio-truth-include-security with a valid GHAS JSON → name-keyed dict.""" - from src.portfolio_truth_status import load_security_alerts_by_name + from github_repo_auditor.portfolio_truth_status import load_security_alerts_by_name _make_ghas_json( tmp_path, @@ -270,7 +270,7 @@ def test_security_alerts_no_ghas_json_returns_none( tmp_path: Path, caplog: pytest.LogCaptureFixture ) -> None: """--portfolio-truth-include-security with no GHAS JSON → None, warning logged.""" - from src.portfolio_truth_status import load_security_alerts_by_name + from github_repo_auditor.portfolio_truth_status import load_security_alerts_by_name with caplog.at_level(logging.WARNING): result = load_security_alerts_by_name(output_dir=tmp_path, username="saagpatel") @@ -281,7 +281,7 @@ def test_security_alerts_no_ghas_json_returns_none( def test_security_alerts_picks_latest_by_mtime(tmp_path: Path) -> None: """When multiple GHAS files exist, the most recently modified one wins.""" - from src.portfolio_truth_status import load_security_alerts_by_name + from github_repo_auditor.portfolio_truth_status import load_security_alerts_by_name older = tmp_path / "ghas-alerts-saagpatel-2026-05-01.json" older.write_text(json.dumps({"MyRepo": {"dependabot": {"high": 9, "available": True}}})) diff --git a/tests/test_portfolio_truth_trends.py b/tests/test_portfolio_truth_trends.py index 791122d9..4f045a79 100644 --- a/tests/test_portfolio_truth_trends.py +++ b/tests/test_portfolio_truth_trends.py @@ -3,11 +3,11 @@ import json from pathlib import Path -from src.portfolio_truth_trends import ( +from github_repo_auditor.portfolio_truth_trends import ( build_verdict_transition_ledger, render_movement_summary, ) -from src.weekly_command_center import ( +from github_repo_auditor.weekly_command_center import ( build_weekly_command_center_digest, render_weekly_command_center_markdown, ) diff --git a/tests/test_pr_head_evidence.py b/tests/test_pr_head_evidence.py index 3a4dc5bf..d1ae65e3 100644 --- a/tests/test_pr_head_evidence.py +++ b/tests/test_pr_head_evidence.py @@ -7,7 +7,7 @@ import pytest -from src.pr_head_evidence import ( +from github_repo_auditor.pr_head_evidence import ( SnapshotValidationError, evaluate_snapshot, parse_snapshot, diff --git a/tests/test_pr_head_evidence_cli.py b/tests/test_pr_head_evidence_cli.py index dbf793cf..bb524c3e 100644 --- a/tests/test_pr_head_evidence_cli.py +++ b/tests/test_pr_head_evidence_cli.py @@ -7,8 +7,8 @@ import pytest -import src.cli as cli_module -from src.app.pr_head_evidence import run_pr_head_evidence_mode +import github_repo_auditor.cli as cli_module +from github_repo_auditor.app.pr_head_evidence import run_pr_head_evidence_mode FIXTURES = Path(__file__).parent / "fixtures" / "pr_head_evidence" diff --git a/tests/test_producer_preflight.py b/tests/test_producer_preflight.py index 23880dbd..ce907ae9 100644 --- a/tests/test_producer_preflight.py +++ b/tests/test_producer_preflight.py @@ -7,7 +7,7 @@ import pytest -from src.producer_preflight import ( +from github_repo_auditor.producer_preflight import ( PREFLIGHT_SCHEMA_VERSION, PREFLIGHT_PASS_CHECKS, ProducerEvidence, @@ -325,7 +325,7 @@ def test_rewritten_repository_identity_failure_cannot_load_as_pass( def test_rewritten_head_ref_failure_fails_currentness_and_publication( tmp_path: Path, ) -> None: - from src.portfolio_truth_publish import ( + from github_repo_auditor.portfolio_truth_publish import ( PortfolioTruthPublishError, publish_portfolio_truth, ) diff --git a/tests/test_progress.py b/tests/test_progress.py index 3bc1ded9..7a33cccd 100644 --- a/tests/test_progress.py +++ b/tests/test_progress.py @@ -3,7 +3,7 @@ from pathlib import Path -from src.progress import PROGRESS_FILE, clear_progress, load_progress, save_progress +from github_repo_auditor.progress import PROGRESS_FILE, clear_progress, load_progress, save_progress def _sample_audits() -> list[dict]: diff --git a/tests/test_project_registry.py b/tests/test_project_registry.py index dc50ea2f..70b8005e 100644 --- a/tests/test_project_registry.py +++ b/tests/test_project_registry.py @@ -6,7 +6,7 @@ import sqlite3 from pathlib import Path -from src.project_registry import ( +from github_repo_auditor.project_registry import ( build_index, build_project_registry, normalize, diff --git a/tests/test_quick_wins.py b/tests/test_quick_wins.py index a9bf9672..434306d4 100644 --- a/tests/test_quick_wins.py +++ b/tests/test_quick_wins.py @@ -2,9 +2,9 @@ from datetime import datetime, timezone -from src.models import AnalyzerResult, RepoAudit, RepoMetadata -from src.quick_wins import find_quick_wins -from src.scorer import letter_grade +from github_repo_auditor.models import AnalyzerResult, RepoAudit, RepoMetadata +from github_repo_auditor.quick_wins import find_quick_wins +from github_repo_auditor.scorer import letter_grade def _meta(name: str = "test") -> RepoMetadata: diff --git a/tests/test_readme_staleness_by_age.py b/tests/test_readme_staleness_by_age.py index 0665a05d..35327050 100644 --- a/tests/test_readme_staleness_by_age.py +++ b/tests/test_readme_staleness_by_age.py @@ -4,25 +4,25 @@ from pathlib import Path from unittest.mock import patch -from src.analyzers.readme import _compute_readme_staleness +from github_repo_auditor.analyzers.readme import _compute_readme_staleness def test_readme_stale_by_age_when_over_threshold(): now = int(time.time()) - with patch("src.analyzers.readme._git_last_touched_unix", return_value=now - (200 * 86400)): + with patch("github_repo_auditor.analyzers.readme._git_last_touched_unix", return_value=now - (200 * 86400)): result = _compute_readme_staleness(Path("/fake"), "README.md") assert result["readme_stale_by_age"] is True def test_readme_not_stale_by_age_when_under_threshold(): now = int(time.time()) - with patch("src.analyzers.readme._git_last_touched_unix", return_value=now - (100 * 86400)): + with patch("github_repo_auditor.analyzers.readme._git_last_touched_unix", return_value=now - (100 * 86400)): result = _compute_readme_staleness(Path("/fake"), "README.md") assert result["readme_stale_by_age"] is False def test_readme_stale_by_age_none_when_no_git_history(): - with patch("src.analyzers.readme._git_last_touched_unix", return_value=None): + with patch("github_repo_auditor.analyzers.readme._git_last_touched_unix", return_value=None): result = _compute_readme_staleness(Path("/fake"), "README.md") assert result["readme_stale_by_age"] is None @@ -30,13 +30,13 @@ def test_readme_stale_by_age_none_when_no_git_history(): def test_readme_stale_by_age_at_exact_threshold(): """Exactly 180 days is NOT stale (threshold is strict >).""" now = int(time.time()) - with patch("src.analyzers.readme._git_last_touched_unix", return_value=now - (180 * 86400)): + with patch("github_repo_auditor.analyzers.readme._git_last_touched_unix", return_value=now - (180 * 86400)): result = _compute_readme_staleness(Path("/fake"), "README.md") assert result["readme_stale_by_age"] is False def test_readme_stale_by_age_one_day_over_threshold(): now = int(time.time()) - with patch("src.analyzers.readme._git_last_touched_unix", return_value=now - (181 * 86400)): + with patch("github_repo_auditor.analyzers.readme._git_last_touched_unix", return_value=now - (181 * 86400)): result = _compute_readme_staleness(Path("/fake"), "README.md") assert result["readme_stale_by_age"] is True diff --git a/tests/test_readme_suggestions.py b/tests/test_readme_suggestions.py index 6b8a26ea..8e3083cc 100644 --- a/tests/test_readme_suggestions.py +++ b/tests/test_readme_suggestions.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.readme_suggestions import generate_readme_suggestions +from github_repo_auditor.readme_suggestions import generate_readme_suggestions def _make_report(**overrides) -> dict: diff --git a/tests/test_recurring_review.py b/tests/test_recurring_review.py index a6dff301..3e83edc9 100644 --- a/tests/test_recurring_review.py +++ b/tests/test_recurring_review.py @@ -3,14 +3,14 @@ from argparse import Namespace from datetime import datetime, timedelta, timezone -from src.baseline_context import build_baseline_context -from src.models import AuditReport, RepoAudit, RepoMetadata -from src.recurring_review import ( +from github_repo_auditor.baseline_context import build_baseline_context +from github_repo_auditor.models import AuditReport, RepoAudit, RepoMetadata +from github_repo_auditor.recurring_review import ( MATERIALITY_THRESHOLDS, choose_watch_plan, evaluate_material_changes, ) -from src.warehouse import write_warehouse_snapshot +from github_repo_auditor.warehouse import write_warehouse_snapshot def _make_args(tmp_path, **overrides) -> Namespace: diff --git a/tests/test_registry_parser.py b/tests/test_registry_parser.py index 8190dbb9..a7085faf 100644 --- a/tests/test_registry_parser.py +++ b/tests/test_registry_parser.py @@ -2,8 +2,8 @@ from datetime import datetime, timezone -from src.models import RepoAudit, RepoMetadata -from src.registry_parser import _normalize, parse_registry, reconcile +from github_repo_auditor.models import RepoAudit, RepoMetadata +from github_repo_auditor.registry_parser import _normalize, parse_registry, reconcile def _make_audit(name: str, tier: str = "functional", score: float = 0.6) -> RepoAudit: diff --git a/tests/test_repo_improver.py b/tests/test_repo_improver.py index 3b350739..0092ce82 100644 --- a/tests/test_repo_improver.py +++ b/tests/test_repo_improver.py @@ -3,7 +3,7 @@ import base64 import json -from src.repo_improver import ( +from github_repo_auditor.repo_improver import ( apply_file_updates, apply_metadata_updates, apply_readme_updates, diff --git a/tests/test_report_enrichment_follow_through_labels.py b/tests/test_report_enrichment_follow_through_labels.py index b463afcd..4451844a 100644 --- a/tests/test_report_enrichment_follow_through_labels.py +++ b/tests/test_report_enrichment_follow_through_labels.py @@ -13,7 +13,7 @@ import pytest -import src.report_enrichment as RE +import github_repo_auditor.report_enrichment as RE # (builder name -> (mapping key, default status, {status: label})) transcribed # verbatim from the pre-refactor builder bodies. diff --git a/tests/test_report_enrichment_risk.py b/tests/test_report_enrichment_risk.py index 47a2d50e..732225b0 100644 --- a/tests/test_report_enrichment_risk.py +++ b/tests/test_report_enrichment_risk.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from src.report_enrichment import build_risk_lookup, build_weekly_review_pack +from github_repo_auditor.report_enrichment import build_risk_lookup, build_weekly_review_pack def _make_report() -> dict: diff --git a/tests/test_reporter.py b/tests/test_reporter.py index 517abbff..67a84fa9 100644 --- a/tests/test_reporter.py +++ b/tests/test_reporter.py @@ -3,14 +3,14 @@ import json from datetime import datetime, timezone -from src.models import AnalyzerResult, AuditReport, RepoAudit, RepoMetadata -from src.report_enrichment import ( +from github_repo_auditor.models import AnalyzerResult, AuditReport, RepoAudit, RepoMetadata +from github_repo_auditor.report_enrichment import ( build_queue_pressure_summary, build_top_recommendation_summary, build_trust_actionability_summary, no_linked_artifact_summary, ) -from src.reporter import ( +from github_repo_auditor.reporter import ( _sanitize_for_json, write_json_report, write_markdown_report, diff --git a/tests/test_review_pack.py b/tests/test_review_pack.py index 070ae50b..16b5dbb9 100644 --- a/tests/test_review_pack.py +++ b/tests/test_review_pack.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.review_pack import export_review_pack +from github_repo_auditor.review_pack import export_review_pack def _make_report() -> dict: diff --git a/tests/test_run_instructions_audit.py b/tests/test_run_instructions_audit.py index 24aaf6cb..3eafd8d8 100644 --- a/tests/test_run_instructions_audit.py +++ b/tests/test_run_instructions_audit.py @@ -3,7 +3,7 @@ import os import subprocess -from src.run_instructions_audit import ( +from github_repo_auditor.run_instructions_audit import ( CLAIM_FIELDS, assign_bucket, assign_drift_bucket, diff --git a/tests/test_scheduled_handoff.py b/tests/test_scheduled_handoff.py index 6ffa4fe4..ddb822d1 100644 --- a/tests/test_scheduled_handoff.py +++ b/tests/test_scheduled_handoff.py @@ -2,7 +2,7 @@ import json -from src.scheduled_handoff import build_scheduled_handoff +from github_repo_auditor.scheduled_handoff import build_scheduled_handoff def _control_center_payload(*, urgency: str = "urgent") -> dict: diff --git a/tests/test_scorecards.py b/tests/test_scorecards.py index d7063ca9..29325304 100644 --- a/tests/test_scorecards.py +++ b/tests/test_scorecards.py @@ -5,7 +5,7 @@ import pytest -from src.scorecards import ( +from github_repo_auditor.scorecards import ( build_scorecards_summary, evaluate_repo_scorecard, evaluate_scorecards_for_report, diff --git a/tests/test_scorer.py b/tests/test_scorer.py index d8658d51..fe1974ec 100644 --- a/tests/test_scorer.py +++ b/tests/test_scorer.py @@ -2,8 +2,23 @@ from datetime import datetime, timedelta, timezone -from src.models import AnalyzerResult, RepoMetadata -from src.scorer import WEIGHTS, score_repo +import pytest + +from github_repo_auditor.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.scorer import WEIGHTS, score_repo + + +@pytest.fixture(autouse=True) +def _block_scorecard_network(monkeypatch): + def unexpected_scorecard_request(metadata): + raise AssertionError( + f"score_repo unexpectedly enabled live scorecard lookup for {metadata.full_name}" + ) + + monkeypatch.setattr( + "github_repo_auditor.security_intelligence.load_scorecard_security", + unexpected_scorecard_request, + ) def _make_results(scores: dict[str, float]) -> list[AnalyzerResult]: @@ -258,37 +273,37 @@ def test_activity_weight(self): class TestLetterGradeBoundaries: def test_exactly_0_80_is_A(self): - from src.scorer import letter_grade + from github_repo_auditor.scorer import letter_grade assert letter_grade(0.80) == "A" def test_exactly_0_70_is_B(self): - from src.scorer import letter_grade + from github_repo_auditor.scorer import letter_grade assert letter_grade(0.70) == "B" def test_exactly_0_55_is_C(self): - from src.scorer import letter_grade + from github_repo_auditor.scorer import letter_grade assert letter_grade(0.55) == "C" def test_exactly_0_35_is_D(self): - from src.scorer import letter_grade + from github_repo_auditor.scorer import letter_grade assert letter_grade(0.35) == "D" def test_exactly_0_0_is_F(self): - from src.scorer import letter_grade + from github_repo_auditor.scorer import letter_grade assert letter_grade(0.0) == "F" def test_just_below_0_80_is_B(self): - from src.scorer import letter_grade + from github_repo_auditor.scorer import letter_grade assert letter_grade(0.799) == "B" def test_above_1_is_A(self): - from src.scorer import letter_grade + from github_repo_auditor.scorer import letter_grade assert letter_grade(1.0) == "A" @@ -300,7 +315,7 @@ def test_above_1_is_A(self): class TestCompletenessTiers: def test_shipped_threshold(self): - from src.scorer import COMPLETENESS_TIERS + from github_repo_auditor.scorer import COMPLETENESS_TIERS tier_map = dict(COMPLETENESS_TIERS) assert tier_map["shipped"] == 0.75 @@ -336,7 +351,7 @@ def test_abandoned_tier_name(self): class TestInterestTiers: def test_flagship_tier(self): - from src.scorer import INTEREST_TIERS + from github_repo_auditor.scorer import INTEREST_TIERS tier_map = dict(INTEREST_TIERS) assert tier_map["flagship"] == 0.70 @@ -408,7 +423,7 @@ def test_mundane_interest_tier_name(self): class TestStaleness: def test_stale_threshold_days_exact(self): - from src.scorer import STALE_THRESHOLD_DAYS + from github_repo_auditor.scorer import STALE_THRESHOLD_DAYS # Must be exactly 730 (not 731) assert STALE_THRESHOLD_DAYS == 730 @@ -434,13 +449,30 @@ def test_exactly_729_days_is_not_stale(self): class TestScoreRepoDefaults: - def test_scorecard_enabled_default_is_false(self): - # Kills scorecard_enabled: bool = True mutation + def test_scorecard_enabled_default_is_false(self, monkeypatch): + observed: dict[str, bool] = {} + + def build_security_posture( + metadata, + results, + github_client, + *, + scorecard_enabled, + security_offline, + ): + observed["scorecard_enabled"] = scorecard_enabled + return {} + + monkeypatch.setattr( + "github_repo_auditor.security_intelligence.build_security_posture", + build_security_posture, + ) results = _make_results({dim: 0.5 for dim in WEIGHTS}) meta = _make_metadata() - audit = score_repo(meta, results) - # If scorecard_enabled were True, it would attempt live scorecard fetch → error - assert audit is not None + + score_repo(meta, results) + + assert observed == {"scorecard_enabled": False} def test_score_explanation_populated(self): # Kills audit.score_explanation = None mutation @@ -455,7 +487,7 @@ def test_score_explanation_populated(self): class TestComputePortfolioGrade: - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade def _audit(self, score: float, tier: str, language: str = "Python", badges: int = 0): results = _make_results({dim: score for dim in WEIGHTS}) @@ -468,14 +500,14 @@ def _audit(self, score: float, tier: str, language: str = "Python", badges: int return audit def test_empty_audits_returns_F(self): - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade grade, score = compute_portfolio_grade([]) assert grade == "F" assert score == 0.0 def test_single_perfect_audit(self): - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audit = self._audit(1.0, "shipped") grade, score = compute_portfolio_grade([audit]) @@ -484,7 +516,7 @@ def test_single_perfect_audit(self): def test_avg_score_used(self): # Kills * → / mutation: avg = sum * len instead of sum / len - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade a1 = self._audit(0.6, "functional") a2 = self._audit(0.4, "wip") @@ -493,7 +525,7 @@ def test_avg_score_used(self): assert 0.4 <= score <= 0.8 # would be huge if * was used def test_diversity_bonus_applies_with_many_languages(self): - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade # 5 languages → bonus = min(0.10, max(0, (5-3)) * 0.05) = 0.10 audits = [ @@ -505,7 +537,7 @@ def test_diversity_bonus_applies_with_many_languages(self): assert score >= 0.5 def test_diversity_bonus_zero_with_few_languages(self): - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade # 2 languages → bonus = min(0.10, max(0, (2-3)) * 0.05) = 0 audits = [ @@ -523,7 +555,7 @@ def test_diversity_bonus_zero_with_few_languages(self): def test_diversity_bonus_max_10_pct(self): # Kills min(0.10 → 1.10) mutation - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits = [ self._audit(0.5, "functional", lang) @@ -535,7 +567,7 @@ def test_diversity_bonus_max_10_pct(self): def test_diversity_bonus_three_languages_is_zero(self): # Kills (len - 3) → (len + 3) mutation - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits = [self._audit(0.5, "wip", lang) for lang in ["Python", "Rust", "Go"]] _, score3 = compute_portfolio_grade(audits) @@ -547,7 +579,7 @@ def test_diversity_bonus_three_languages_is_zero(self): def test_diversity_bonus_four_languages_is_nonzero(self): # Kills (len - 4) → (len - 3) off-by-one mutation - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits_3 = [self._audit(0.5, "wip", lang) for lang in ["Python", "Rust", "Go"]] audits_4 = [self._audit(0.5, "wip", lang) for lang in ["Python", "Rust", "Go", "Swift"]] @@ -557,7 +589,7 @@ def test_diversity_bonus_four_languages_is_nonzero(self): assert score4 > score3 def test_shipped_ratio_above_50_pct_gives_10_pct_bonus(self): - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade # 2/3 shipped → ratio = 0.67 > 0.5 → bonus = 0.10 audits = [ @@ -573,7 +605,7 @@ def test_shipped_ratio_above_50_pct_gives_10_pct_bonus(self): def test_shipped_bonus_threshold_30_pct(self): # 1/4 shipped = 0.25, not > 0.3, so bonus = 0 # 2/4 shipped = 0.50, not > 0.5, so bonus = 0.05 - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits_25 = [self._audit(0.5, "shipped")] + [self._audit(0.5, "wip")] * 3 audits_50 = [self._audit(0.5, "shipped")] * 2 + [self._audit(0.5, "wip")] * 2 @@ -582,7 +614,7 @@ def test_shipped_bonus_threshold_30_pct(self): assert score_50 > score_25 def test_abandonment_penalty_above_60_pct(self): - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade # 4/5 abandoned → ratio = 0.80 > 0.6 → penalty = -0.10 audits = [self._audit(0.5, "abandoned")] * 4 + [self._audit(0.5, "wip")] @@ -593,7 +625,7 @@ def test_abandonment_penalty_above_60_pct(self): def test_abandonment_penalty_above_40_pct_but_below_60(self): # 3/6 = 0.50 > 0.40 but not > 0.60 → penalty = -0.05 - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits_50 = [self._audit(0.5, "abandoned")] * 3 + [self._audit(0.5, "wip")] * 3 audits_none = [self._audit(0.5, "wip")] * 6 @@ -603,7 +635,7 @@ def test_abandonment_penalty_above_40_pct_but_below_60(self): def test_abandonment_counts_skeleton_tier(self): # Kills "XXskeletonXX" mutation — skeleton must be counted as abandoned-like - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits = [self._audit(0.5, "skeleton")] * 5 audits_wip = [self._audit(0.5, "wip")] * 5 @@ -613,7 +645,7 @@ def test_abandonment_counts_skeleton_tier(self): def test_badge_bonus_above_3_avg(self): # Kills avg_badges = None mutation - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits_many = [self._audit(0.5, "wip", badges=4)] * 3 audits_few = [self._audit(0.5, "wip", badges=0)] * 3 @@ -622,7 +654,7 @@ def test_badge_bonus_above_3_avg(self): assert score_many > score_few def test_health_score_clamped_to_0_1(self): - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade # extreme inputs should still produce 0..1 score audits = [self._audit(1.0, "shipped", badges=10)] * 10 @@ -631,7 +663,7 @@ def test_health_score_clamped_to_0_1(self): def test_health_score_not_none(self): # Kills health_score = None mutation - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits = [self._audit(0.7, "shipped")] grade, score = compute_portfolio_grade(audits) @@ -639,7 +671,7 @@ def test_health_score_not_none(self): assert score > 0.0 def test_returns_letter_grade(self): - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits = [self._audit(0.85, "shipped")] grade, score = compute_portfolio_grade(audits) @@ -647,7 +679,7 @@ def test_returns_letter_grade(self): def test_grade_uses_rounded_score(self): # Kills round(health_score, 3) side-effects - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits = [self._audit(0.5, "wip")] _, score = compute_portfolio_grade(audits) @@ -672,7 +704,7 @@ def test_structure_dimension_triggers_count(self): details={"config_files": ["pyproject.toml"], "source_dirs": ["src"]}, ) ] - from src.scorer import _count_meaningful_files + from github_repo_auditor.scorer import _count_meaningful_files assert _count_meaningful_files(results) == 1 @@ -686,7 +718,7 @@ def test_code_quality_with_entry_point(self): details={"entry_point": "main.py", "total_loc": 0}, ) ] - from src.scorer import _count_meaningful_files + from github_repo_auditor.scorer import _count_meaningful_files assert _count_meaningful_files(results) == 1 @@ -701,7 +733,7 @@ def test_code_quality_with_loc(self): details={"total_loc": 100}, ) ] - from src.scorer import _count_meaningful_files + from github_repo_auditor.scorer import _count_meaningful_files assert _count_meaningful_files(results) == 1 @@ -717,7 +749,7 @@ def test_code_quality_zero_loc_no_entry_point_returns_zero(self): details={"total_loc": 0}, ) ] - from src.scorer import _count_meaningful_files + from github_repo_auditor.scorer import _count_meaningful_files assert _count_meaningful_files(results) == 0 @@ -726,7 +758,7 @@ def test_no_relevant_dimensions_returns_zero(self): results = [ AnalyzerResult(dimension="readme", score=0.5, max_score=1.0, findings=[], details={}) ] - from src.scorer import _count_meaningful_files + from github_repo_auditor.scorer import _count_meaningful_files assert _count_meaningful_files(results) == 0 @@ -741,7 +773,7 @@ def test_structure_returns_1_not_2(self): details={"config_files": ["pyproject.toml"]}, ) ] - from src.scorer import _count_meaningful_files + from github_repo_auditor.scorer import _count_meaningful_files assert _count_meaningful_files(results) == 1 @@ -775,7 +807,7 @@ def test_count_from_source_dirs(self): details={"source_dirs": ["src"]}, ) ] - from src.scorer import _count_meaningful_files + from github_repo_auditor.scorer import _count_meaningful_files assert _count_meaningful_files(results) == 1 @@ -790,7 +822,7 @@ def test_code_quality_total_loc_1_counts(self): details={"total_loc": 1}, ) ] - from src.scorer import _count_meaningful_files + from github_repo_auditor.scorer import _count_meaningful_files assert _count_meaningful_files(results) == 1 @@ -912,7 +944,7 @@ def test_interest_tier_at_boundary(self): def test_grade_F_returned_when_no_threshold_matches(self): # Kills return "XXFXX" mutation for letter_grade fallback # Score -1.0 would fall below all thresholds if letter_grade fallback changed - from src.scorer import letter_grade + from github_repo_auditor.scorer import letter_grade assert letter_grade(0.0) == "F" assert letter_grade(-0.1) == "F" @@ -921,7 +953,7 @@ def test_letter_grade_unreachable_fallback(self): # The literal "F" at the last GRADE_THRESHOLDS entry (0.0) means the fallback # `return "F"` at the end of letter_grade is actually unreachable for valid scores. # The test verifies that changing its value still works correctly via the threshold path. - from src.scorer import GRADE_THRESHOLDS + from github_repo_auditor.scorer import GRADE_THRESHOLDS # The last threshold is (0.0, "F") — anything >= 0.0 returns "F" from the loop assert GRADE_THRESHOLDS[-1] == (0.0, "F") @@ -1035,7 +1067,7 @@ def _simple_audit(self, score: float, tier: str, language: str = "Python", badge return audit def test_grade_F_for_empty(self): - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade grade, score = compute_portfolio_grade([]) assert grade == "F" @@ -1043,7 +1075,7 @@ def test_grade_F_for_empty(self): def test_shipped_ratio_exactly_50_pct(self): # Kills shipped_ratio > 0.5 → >= 0.5 mutation: exactly 50% should NOT give 10% bonus - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits_50 = [self._simple_audit(0.5, "shipped"), self._simple_audit(0.5, "wip")] audits_66 = [ @@ -1058,7 +1090,7 @@ def test_shipped_ratio_exactly_50_pct(self): def test_shipped_ratio_exactly_50_pct_gets_mid_bonus(self): # With ratio=0.5 (> 0.5 is False, but > 0.3 is True) → gets 0.05 bonus, not 0.10 - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits_exactly_50 = [self._simple_audit(0.5, "shipped"), self._simple_audit(0.5, "wip")] audits_all_wip = [self._simple_audit(0.5, "wip"), self._simple_audit(0.5, "wip")] @@ -1069,7 +1101,7 @@ def test_shipped_ratio_exactly_50_pct_gets_mid_bonus(self): def test_shipped_bonus_sum_1_not_2(self): # Kills sum(1 for ...) → sum(2 for ...) mutation: 2 shipped out of 4 = 0.5 ratio # With sum(1): ratio = 2/4 = 0.5; with sum(2): ratio = 4/4 = 1.0 → different bonus tier - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits_2of4 = [self._simple_audit(0.5, "shipped")] * 2 + [ self._simple_audit(0.5, "wip") @@ -1085,7 +1117,7 @@ def test_shipped_bonus_sum_1_not_2(self): def test_abandon_ratio_sum_1_not_2(self): # Kills sum(1 for ...) → sum(2 for ...) mutation: 2 abandoned out of 4 = 0.5 # With sum(2): 4/4 = 1.0 → high penalty; with sum(1): 0.5 → mid penalty - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits_2of4 = [self._simple_audit(0.5, "abandoned")] * 2 + [ self._simple_audit(0.5, "wip") @@ -1097,7 +1129,7 @@ def test_abandon_ratio_sum_1_not_2(self): def test_abandon_ratio_div_not_mul(self): # Kills ) / len(audits) → ) * len(audits) mutation - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade # 1 abandoned out of 5: / → 0.2; * → 1.0 (then penalty would be -0.10 not -0.05) audits = [self._simple_audit(0.5, "abandoned")] + [self._simple_audit(0.5, "wip")] * 4 @@ -1110,7 +1142,7 @@ def test_abandon_ratio_div_not_mul(self): def test_abandon_penalty_high_threshold_0_6(self): # Kills > 0.6 → >= 0.6 and > 1.6 mutations - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade # Exactly 60% abandoned → should get -0.05 (mid penalty), not -0.10 (high penalty) audits_60 = [self._simple_audit(0.5, "abandoned")] * 3 + [ @@ -1124,7 +1156,7 @@ def test_abandon_penalty_high_threshold_0_6(self): def test_abandon_penalty_mid_threshold_0_4(self): # Kills > 0.4 → >= 0.4 mutation for mid-penalty threshold - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade # Exactly 40% abandoned → should NOT get -0.05 penalty (threshold is > 0.4, not >=) audits_40 = [self._simple_audit(0.5, "abandoned")] * 2 + [ @@ -1138,7 +1170,7 @@ def test_abandon_penalty_mid_threshold_0_4(self): def test_badge_bonus_avg_3_not_triggered(self): # Kills > 3 → >= 3 mutation: avg_badges = 3 should NOT get bonus - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits_3badges = [self._simple_audit(0.5, "wip", badges=3)] audits_4badges = [self._simple_audit(0.5, "wip", badges=4)] @@ -1149,7 +1181,7 @@ def test_badge_bonus_avg_3_not_triggered(self): def test_avg_badges_div_not_mul(self): # Kills / len(audits) → * len(audits) mutation for avg_badges - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade # 1 badge across 2 audits: / → 0.5 (no bonus); * → 2 (would trigger bonus) audits = [ @@ -1167,7 +1199,7 @@ def test_avg_badges_div_not_mul(self): def test_diversity_bonus_uses_multiply_0_05(self): # Kills * 0.05 → / 0.05 and * 1.05 mutations - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade # 4 langs: bonus = min(0.10, max(0, 1) * 0.05) = 0.05 # If / 0.05: bonus = min(0.10, max(0, 1) / 0.05) = min(0.10, 20) = 0.10 @@ -1188,7 +1220,7 @@ def test_diversity_bonus_uses_multiply_0_05(self): def test_shipped_bonus_uses_0_10_not_1_1(self): # Kills 0.10 → 1.1 for high shipped ratio - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade # All shipped: ratio = 1.0 > 0.5 → bonus = 0.10 # If 1.1: health_score could exceed 1.0 before clamping @@ -1198,7 +1230,7 @@ def test_shipped_bonus_uses_0_10_not_1_1(self): def test_abandon_penalty_uses_negative_0_10_not_negative_1_1(self): # Kills -0.10 → -1.10 for high abandonment penalty - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits = [self._simple_audit(0.5, "abandoned")] * 4 + [self._simple_audit(0.5, "wip")] _, score = compute_portfolio_grade(audits) @@ -1206,7 +1238,7 @@ def test_abandon_penalty_uses_negative_0_10_not_negative_1_1(self): def test_rounded_to_3_decimal_places(self): # Kills round(health_score, 4) mutation - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits = [self._simple_audit(0.5123456789, "wip")] _, score = compute_portfolio_grade(audits) @@ -1219,7 +1251,7 @@ def test_shipped_bonus_exact_value_0_10(self): # Kills 0.10 → 1.1 mutation: verify exact bonus value (not just clamped max) # avg_score=0.3, ratio=1.0 → health = 0.3 + 0.10 = 0.40 (unclamped) # With 1.1: 0.3 + 1.1 = 1.0 (clamped) → diff detectable - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits = [self._simple_audit(0.3, "shipped")] * 3 _, score = compute_portfolio_grade(audits) @@ -1228,7 +1260,7 @@ def test_shipped_bonus_exact_value_0_10(self): def test_shipped_bonus_mid_tier_exact_0_05(self): # Kills shipped_bonus 0.05 → other mutations at mid-ratio tier # avg_score=0.3, ratio=0.4 (1 shipped, 2 not) → health = 0.3 + 0.05 = 0.35 - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits_mid = [ self._simple_audit(0.3, "shipped"), @@ -1242,7 +1274,7 @@ def test_abandon_penalty_exact_negative_0_10(self): # Kills -0.10 → -1.1 mutation: verify exact penalty value (not just clamped min) # avg_score=0.5, abandon_ratio=0.8 > 0.6 → health = 0.5 - 0.10 = 0.40 # With -1.1: 0.5 - 1.1 = max(0, -0.6) = 0.0 → diff detectable - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits = [self._simple_audit(0.5, "abandoned")] * 4 + [self._simple_audit(0.5, "wip")] _, score = compute_portfolio_grade(audits) @@ -1252,7 +1284,7 @@ def test_badge_bonus_exact_0_05(self): # Kills 0.05 → 1.05 mutation: verify exact bonus when clamping would differ # avg_score=0.3, avg_badges=4 > 3 → health = 0.3 + 0.05 = 0.35 # With 1.05: 0.3 + 1.05 = 1.0 (clamped) → detectable - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits = [self._simple_audit(0.3, "wip", badges=4)] _, score = compute_portfolio_grade(audits) @@ -1261,7 +1293,7 @@ def test_badge_bonus_exact_0_05(self): def test_avg_badges_div_not_mul_two_audits(self): # Kills / len(audits) → * len(audits) mutation with 2 audits # 2 audits each with 2 badges: sum=4, / 2 = 2.0 (< 3, no bonus), * 2 = 8 (bonus) - from src.scorer import compute_portfolio_grade + from github_repo_auditor.scorer import compute_portfolio_grade audits = [ self._simple_audit(0.3, "wip", badges=2), @@ -1280,7 +1312,7 @@ def test_overall_score_partial_dims_uses_division(self): # Kills / weight_sum → * weight_sum when weight_sum != 1.0 # Pass only 2 dims (readme=0.12, structure=0.10, total=0.22) with score 1.0 # / 0.22 = 1.0; * 0.22 = 0.22 → detectably different - from src.models import AnalyzerResult + from github_repo_auditor.models import AnalyzerResult partial = [ AnalyzerResult( @@ -1311,7 +1343,7 @@ def test_fork_activity_reduction_uses_minus_not_plus(self): # correct: activity impact = 0.05; mutant: same (weights["activity"]=0.05 either way) # But other weights differ: correct other_total sums differently # Use a uniform score across dims so impact shows via activity contribution - from src.scorer import WEIGHTS + from github_repo_auditor.scorer import WEIGHTS # activity weight is explicitly set to FORK_ACTIVITY_WEIGHT regardless of -/+; # the difference only manifests via redistribution to other keys. diff --git a/tests/test_scoring_profiles.py b/tests/test_scoring_profiles.py index a1df3579..15f8f5ab 100644 --- a/tests/test_scoring_profiles.py +++ b/tests/test_scoring_profiles.py @@ -4,9 +4,9 @@ from datetime import datetime, timedelta, timezone from pathlib import Path -from src.models import AnalyzerResult, RepoMetadata -from src.app.run_audit import _load_scoring_profile -from src.scorer import WEIGHTS, score_repo +from github_repo_auditor.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.app.run_audit import _load_scoring_profile +from github_repo_auditor.scorer import WEIGHTS, score_repo def _make_results(scores: dict[str, float]) -> list[AnalyzerResult]: diff --git a/tests/test_scoring_weights.py b/tests/test_scoring_weights.py index f254eb4a..c76d7ed7 100644 --- a/tests/test_scoring_weights.py +++ b/tests/test_scoring_weights.py @@ -4,9 +4,9 @@ import inspect import pkgutil -from src.analyzers import ALL_ANALYZERS -from src.analyzers.base import BaseAnalyzer -from src.scorer import WEIGHTS +from github_repo_auditor.analyzers import ALL_ANALYZERS +from github_repo_auditor.analyzers.base import BaseAnalyzer +from github_repo_auditor.scorer import WEIGHTS def test_scoring_weights_sum_to_one() -> None: @@ -32,7 +32,7 @@ def test_scoring_weights_cover_exactly_the_composed_dimensions() -> None: def test_analyzer_classes_do_not_declare_dead_weight_attributes() -> None: - import src.analyzers as analyzers_package + import github_repo_auditor.analyzers as analyzers_package analyzer_classes = [BaseAnalyzer] for module_info in pkgutil.iter_modules(analyzers_package.__path__): diff --git a/tests/test_security.py b/tests/test_security.py index 0873e69b..6d9dd97f 100644 --- a/tests/test_security.py +++ b/tests/test_security.py @@ -3,8 +3,8 @@ from datetime import datetime, timezone from pathlib import Path -from src.analyzers.security import SecurityAnalyzer, _find_dangerous_files, _scan_secrets -from src.models import RepoMetadata +from github_repo_auditor.analyzers.security import SecurityAnalyzer, _find_dangerous_files, _scan_secrets +from github_repo_auditor.models import RepoMetadata def _meta(**overrides) -> RepoMetadata: diff --git a/tests/test_security_admission.py b/tests/test_security_admission.py index b0e029cb..acd342e2 100644 --- a/tests/test_security_admission.py +++ b/tests/test_security_admission.py @@ -4,7 +4,7 @@ import pytest -from src.security_admission import derive_security_admission +from github_repo_auditor.security_admission import derive_security_admission OBSERVED_AT = "2026-08-05T05:47:30+00:00" diff --git a/tests/test_security_burndown.py b/tests/test_security_burndown.py index f80a9a4b..f9d5fe7c 100644 --- a/tests/test_security_burndown.py +++ b/tests/test_security_burndown.py @@ -10,9 +10,9 @@ import pytest import requests -from src.cli import _run_security_burndown_mode -from src.ghas_alert_details import fetch_dependabot_details -from src.security_burndown import ( +from github_repo_auditor.cli import _run_security_burndown_mode +from github_repo_auditor.ghas_alert_details import fetch_dependabot_details +from github_repo_auditor.security_burndown import ( BurndownEntry, BurndownReport, build_security_burndown, @@ -198,7 +198,7 @@ def test_error_handlers_log_no_interpolated_values(self) -> None: """Verify the module source has no format args in the except-handler log calls.""" import inspect - from src import ghas_alert_details + from github_repo_auditor import ghas_alert_details source = inspect.getsource(ghas_alert_details) # Find the except blocks — they should only contain logger calls with diff --git a/tests/test_security_intelligence.py b/tests/test_security_intelligence.py index df41dfa6..9c32c2c1 100644 --- a/tests/test_security_intelligence.py +++ b/tests/test_security_intelligence.py @@ -2,8 +2,8 @@ from datetime import datetime, timezone -from src.models import AnalyzerResult, RepoMetadata -from src.security_intelligence import build_security_posture +from github_repo_auditor.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.security_intelligence import build_security_posture def _make_metadata(**overrides) -> RepoMetadata: diff --git a/tests/test_semantic_index.py b/tests/test_semantic_index.py index 7fc757e3..676854a9 100644 --- a/tests/test_semantic_index.py +++ b/tests/test_semantic_index.py @@ -1,4 +1,4 @@ -"""Tests for src/semantic_index.py — portfolio semantic index (Arc F S3.1).""" +"""Tests for src/github_repo_auditor/semantic_index.py — portfolio semantic index (Arc F S3.1).""" from __future__ import annotations @@ -12,8 +12,8 @@ import pytest import responses as responses_lib -from src.models import AnalyzerResult, RepoAudit, RepoMetadata -from src.semantic_index import ( +from github_repo_auditor.models import AnalyzerResult, RepoAudit, RepoMetadata +from github_repo_auditor.semantic_index import ( VOYAGE_DIM, SearchResult, SemanticIndex, @@ -359,7 +359,7 @@ def test_sends_correct_request_shape(self) -> None: fake_dim = VOYAGE_DIM fake_vecs = [[0.1] * fake_dim] - from src.semantic_index import VOYAGE_API_BASE_URL + from github_repo_auditor.semantic_index import VOYAGE_API_BASE_URL responses_lib.add( responses_lib.POST, @@ -382,7 +382,7 @@ def test_sends_correct_request_shape(self) -> None: def test_auth_header_uses_api_key(self) -> None: fake_key = "vk-auth-header-test" - from src.semantic_index import VOYAGE_API_BASE_URL + from github_repo_auditor.semantic_index import VOYAGE_API_BASE_URL responses_lib.add( responses_lib.POST, @@ -404,7 +404,7 @@ def test_auth_header_uses_api_key(self) -> None: class TestLocalEmbedder: def test_raises_import_error_when_not_installed(self) -> None: with patch.dict("sys.modules", {"sentence_transformers": None}): - from src.semantic_index import LocalEmbedder + from github_repo_auditor.semantic_index import LocalEmbedder with pytest.raises(ImportError, match="sentence-transformers"): LocalEmbedder() @@ -419,7 +419,7 @@ def test_embed_calls_model_encode(self) -> None: mock_st.SentenceTransformer.return_value = mock_model with patch.dict("sys.modules", {"sentence_transformers": mock_st}): - from src.semantic_index import LocalEmbedder # fresh import + from github_repo_auditor.semantic_index import LocalEmbedder # fresh import emb = LocalEmbedder() result = emb.embed(["sample text"]) @@ -481,7 +481,7 @@ def test_reindex_flag_calls_reindex_once(self, tmp_path: Path) -> None: audits = [_make_audit("Repo1"), _make_audit("Repo2")] # Simulate the CLI post-audit hook - from src.semantic_index import _run_reindex + from github_repo_auditor.semantic_index import _run_reindex _run_reindex(mock_idx, audits, force=False) @@ -493,7 +493,7 @@ def test_ask_flag_calls_search_once(self, tmp_path: Path) -> None: SearchResult(repo_name="RustDB", score=0.1, snippet="repo: RustDB") ] - from src.semantic_index import _run_search + from github_repo_auditor.semantic_index import _run_search results = _run_search(mock_idx, "rust database", k=5) diff --git a/tests/test_serve.py b/tests/test_serve.py index d7b21214..1ea24c72 100644 --- a/tests/test_serve.py +++ b/tests/test_serve.py @@ -1,4 +1,4 @@ -"""Tests for src/serve — FastAPI + HTMX local web UI (Arc F S4.1).""" +"""Tests for src/github_repo_auditor/serve — FastAPI + HTMX local web UI (Arc F S4.1).""" from __future__ import annotations @@ -20,8 +20,8 @@ from fastapi.testclient import TestClient # noqa: E402 -from src.serve.app import create_app # noqa: E402 -from src.serve.runner import SAFE_FLAG_NAMES, validate_flags, validate_username # noqa: E402 +from github_repo_auditor.serve.app import create_app # noqa: E402 +from github_repo_auditor.serve.runner import SAFE_FLAG_NAMES, validate_flags, validate_username # noqa: E402 # --------------------------------------------------------------------------- # Fixtures @@ -161,8 +161,8 @@ def test_unknown_repo_returns_404(self, client: TestClient) -> None: assert resp.status_code == 404 def test_known_repo_reads_production_warehouse_schema(self, tmp_path: Path) -> None: - from src.models import AnalyzerResult, AuditReport, RepoAudit, RepoMetadata - from src.warehouse import write_warehouse_snapshot + from github_repo_auditor.models import AnalyzerResult, AuditReport, RepoAudit, RepoMetadata + from github_repo_auditor.warehouse import write_warehouse_snapshot od = tmp_path / "output" od.mkdir() @@ -275,7 +275,7 @@ def test_returns_200_with_mocked_records(self, client: TestClient) -> None: "approval_state": "not-applicable", }, ] - with patch("src.warehouse.load_approval_records", return_value=fake_records): + with patch("github_repo_auditor.warehouse.load_approval_records", return_value=fake_records): resp = client.get("/approvals") assert resp.status_code == 200 assert "appr-001" in resp.text @@ -297,7 +297,7 @@ def test_get_form_returns_200(self, client: TestClient) -> None: assert "username" in resp.text.lower() def test_post_valid_flags_returns_run_id(self, client: TestClient) -> None: - with patch("src.serve.routes.spawn_run", return_value="abc123"): + with patch("github_repo_auditor.serve.routes.spawn_run", return_value="abc123"): resp = client.post( "/runs/new", data={"username": "testuser", "flags": "--portfolio-truth"}, @@ -346,7 +346,7 @@ def _seed_draft_readme_records(output_dir: Path) -> tuple[str, str]: Returns (record_id_1, record_id_2). """ - from src.warehouse import save_approval_record + from github_repo_auditor.warehouse import save_approval_record id1 = "dr-aabbccdd00000001" id2 = "dr-aabbccdd00000002" @@ -428,7 +428,7 @@ def test_draft_diff_returns_200_with_proposed_readme(self, output_dir: Path) -> def test_draft_diff_non_draft_readme_returns_404(self, output_dir: Path) -> None: """A record with a different approval_subject_type should return 404.""" - from src.warehouse import save_approval_record + from github_repo_auditor.warehouse import save_approval_record save_approval_record( output_dir, @@ -510,7 +510,7 @@ def test_unknown_run_id_returns_404(self, client: TestClient) -> None: def test_stream_happy_path(self, client: TestClient, output_dir: Path) -> None: """Spawn a trivial subprocess and read at least one SSE event.""" - from src.serve.runner import spawn_run + from github_repo_auditor.serve.runner import spawn_run # Use python -c "print('hello')" — portable, no shell=True run_id = spawn_run( @@ -519,7 +519,7 @@ def test_stream_happy_path(self, client: TestClient, output_dir: Path) -> None: output_dir=output_dir, ) # Override the session's command to something safe and instant - from src.serve import runner as runner_mod + from github_repo_auditor.serve import runner as runner_mod session = runner_mod.get_session(run_id) assert session is not None @@ -613,7 +613,7 @@ def test_consecutive_hyphens_rejected(self) -> None: class TestHtmxFragmentEscaping: def test_campaign_action_values_are_escaped(self) -> None: - from src.serve.routes import _render_action_row + from github_repo_auditor.serve.routes import _render_action_row html = _render_action_row( 'packet">', @@ -632,7 +632,7 @@ def test_campaign_action_values_are_escaped(self) -> None: assert "<strong>escaping</strong>" in html def test_section_card_values_are_escaped(self) -> None: - from src.serve.routes import _render_section_card + from github_repo_auditor.serve.routes import _render_section_card html = _render_section_card( 'section">', @@ -651,7 +651,7 @@ def test_section_card_values_are_escaped(self) -> None: def test_campaign_action_error_hides_exception_details(self, client: TestClient) -> None: with patch( - "src.plan_campaign.approve_action", + "github_repo_auditor.plan_campaign.approve_action", side_effect=ValueError("internal stack trace /tmp/private.py:99"), ): resp = client.post("/approvals/packet-1/actions/0/approve") @@ -663,7 +663,7 @@ def test_campaign_action_error_hides_exception_details(self, client: TestClient) def test_section_error_hides_exception_details(self, client: TestClient) -> None: with patch( - "src.draft_readmes.approve_section", + "github_repo_auditor.draft_readmes.approve_section", side_effect=ValueError("internal stack trace /tmp/private.py:99"), ): resp = client.post("/approvals/sections/section-1/approve") @@ -681,7 +681,7 @@ def test_section_error_hides_exception_details(self, client: TestClient) -> None class TestCLIServeFlag: def test_serve_flag_in_parser(self) -> None: - from src.cli import build_parser + from github_repo_auditor.cli import build_parser parser = build_parser() # --serve must be a recognised flag (parse with a dummy username) @@ -691,7 +691,7 @@ def test_serve_flag_in_parser(self) -> None: assert args.host == "0.0.0.0" def test_serve_defaults(self) -> None: - from src.cli import build_parser + from github_repo_auditor.cli import build_parser parser = build_parser() args = parser.parse_args(["dummyuser", "--serve"]) @@ -709,7 +709,7 @@ def _seed_campaign_plan_record(output_dir: Path) -> str: Returns the record_id. """ - from src.warehouse import save_approval_record + from github_repo_auditor.warehouse import save_approval_record record_id = "cp-aabbccdd00000001" save_approval_record( diff --git a/tests/test_serve_api.py b/tests/test_serve_api.py index 6f46b309..0a668460 100644 --- a/tests/test_serve_api.py +++ b/tests/test_serve_api.py @@ -1,4 +1,4 @@ -"""Tests for src/serve/api.py — hosted clone-free report JSON endpoint (Phase 2 S1).""" +"""Tests for src/github_repo_auditor/serve/api.py — hosted clone-free report JSON endpoint (Phase 2 S1).""" from __future__ import annotations @@ -15,9 +15,9 @@ import requests # noqa: E402 from fastapi.testclient import TestClient # noqa: E402 -from src.api_only import ApiOnlyReport # noqa: E402 -from src.serve.api import get_github_client # noqa: E402 -from src.serve.app import create_app # noqa: E402 +from github_repo_auditor.api_only import ApiOnlyReport # noqa: E402 +from github_repo_auditor.serve.api import get_github_client # noqa: E402 +from github_repo_auditor.serve.app import create_app # noqa: E402 # --------------------------------------------------------------------------- @@ -70,7 +70,7 @@ def test_health_ok(client: TestClient) -> None: # --------------------------------------------------------------------------- def test_report_returns_serialized_report(client: TestClient) -> None: report = ApiOnlyReport(username="octocat", audits=[]) - with patch("src.serve.api.audit_user_api_only", return_value=report) as mock_audit: + with patch("github_repo_auditor.serve.api.audit_user_api_only", return_value=report) as mock_audit: resp = client.get("/api/report/octocat") assert resp.status_code == 200 @@ -86,7 +86,7 @@ def test_report_returns_serialized_report(client: TestClient) -> None: def test_report_passes_validated_username(client: TestClient) -> None: report = ApiOnlyReport(username="octocat", audits=[]) - with patch("src.serve.api.audit_user_api_only", return_value=report) as mock_audit: + with patch("github_repo_auditor.serve.api.audit_user_api_only", return_value=report) as mock_audit: resp = client.get("/api/report/octocat") assert resp.status_code == 200 @@ -99,7 +99,7 @@ def test_report_passes_validated_username(client: TestClient) -> None: # --------------------------------------------------------------------------- @pytest.mark.parametrize("bad", ["bad--name", "has space", "-leading", "a" * 40]) def test_invalid_username_returns_422(client: TestClient, bad: str) -> None: - with patch("src.serve.api.audit_user_api_only") as mock_audit: + with patch("github_repo_auditor.serve.api.audit_user_api_only") as mock_audit: resp = client.get(f"/api/report/{bad}") assert resp.status_code == 422 mock_audit.assert_not_called() @@ -109,49 +109,49 @@ def test_invalid_username_returns_422(client: TestClient, bad: str) -> None: # Error mapping # --------------------------------------------------------------------------- def test_unknown_user_returns_404(client: TestClient) -> None: - with patch("src.serve.api.audit_user_api_only", side_effect=_http_error(404)): + with patch("github_repo_auditor.serve.api.audit_user_api_only", side_effect=_http_error(404)): resp = client.get("/api/report/ghost") assert resp.status_code == 404 def test_rate_limited_403_with_zero_quota_returns_429(client: TestClient) -> None: err = _http_error(403, headers={"X-RateLimit-Remaining": "0"}) - with patch("src.serve.api.audit_user_api_only", side_effect=err): + with patch("github_repo_auditor.serve.api.audit_user_api_only", side_effect=err): resp = client.get("/api/report/octocat") assert resp.status_code == 429 def test_rate_limited_429_returns_429(client: TestClient) -> None: - with patch("src.serve.api.audit_user_api_only", side_effect=_http_error(429)): + with patch("github_repo_auditor.serve.api.audit_user_api_only", side_effect=_http_error(429)): resp = client.get("/api/report/octocat") assert resp.status_code == 429 def test_forbidden_403_without_quota_header_returns_403(client: TestClient) -> None: # A 403 that is NOT rate-limiting (e.g. private resource) stays a 403, not 429. - with patch("src.serve.api.audit_user_api_only", side_effect=_http_error(403)): + with patch("github_repo_auditor.serve.api.audit_user_api_only", side_effect=_http_error(403)): resp = client.get("/api/report/octocat") assert resp.status_code == 403 def test_upstream_error_returns_502(client: TestClient) -> None: - with patch("src.serve.api.audit_user_api_only", side_effect=_http_error(500)): + with patch("github_repo_auditor.serve.api.audit_user_api_only", side_effect=_http_error(500)): resp = client.get("/api/report/octocat") assert resp.status_code == 502 def test_network_error_returns_502(client: TestClient) -> None: err = requests.ConnectionError("connection reset") - with patch("src.serve.api.audit_user_api_only", side_effect=err): + with patch("github_repo_auditor.serve.api.audit_user_api_only", side_effect=err): resp = client.get("/api/report/octocat") assert resp.status_code == 502 def test_github_client_error_returns_502(client: TestClient) -> None: - from src.github_client import GitHubClientError + from github_repo_auditor.github_client import GitHubClientError with patch( - "src.serve.api.audit_user_api_only", + "github_repo_auditor.serve.api.audit_user_api_only", side_effect=GitHubClientError("graphql failed"), ): resp = client.get("/api/report/octocat") @@ -162,10 +162,10 @@ def test_github_client_error_returns_502(client: TestClient) -> None: # Cost bound # --------------------------------------------------------------------------- def test_scan_is_capped_at_max_repos(client: TestClient) -> None: - from src.serve.api import MAX_REPOS_CAP + from github_repo_auditor.serve.api import MAX_REPOS_CAP report = ApiOnlyReport(username="octocat", audits=[]) - with patch("src.serve.api.audit_user_api_only", return_value=report) as mock_audit: + with patch("github_repo_auditor.serve.api.audit_user_api_only", return_value=report) as mock_audit: # No per-request repo knob — a stray query param is ignored and the # server always bounds the scan at MAX_REPOS_CAP. resp = client.get("/api/report/octocat?max_repos=9999") @@ -180,7 +180,7 @@ def test_scan_is_capped_at_max_repos(client: TestClient) -> None: def test_cors_allows_frontend_origin(client: TestClient) -> None: report = ApiOnlyReport(username="octocat", audits=[]) origin = "http://localhost:3000" - with patch("src.serve.api.audit_user_api_only", return_value=report): + with patch("github_repo_auditor.serve.api.audit_user_api_only", return_value=report): resp = client.get("/api/report/octocat", headers={"Origin": origin}) assert resp.status_code == 200 assert resp.headers.get("access-control-allow-origin") == origin @@ -199,7 +199,7 @@ def test_cors_preflight_allows_waitlist_post(client: TestClient) -> None: def test_cors_origins_reads_env(monkeypatch) -> None: - from src.serve.api import cors_origins + from github_repo_auditor.serve.api import cors_origins monkeypatch.setenv("GHRA_CORS_ORIGINS", "https://a.example, https://b.example") assert cors_origins() == ["https://a.example", "https://b.example"] @@ -212,7 +212,7 @@ def test_cors_origins_reads_env(monkeypatch) -> None: # --------------------------------------------------------------------------- def test_cache_hit_skips_second_scan(client: TestClient) -> None: report = ApiOnlyReport(username="octocat", audits=[]) - with patch("src.serve.api.audit_user_api_only", return_value=report) as mock_audit: + with patch("github_repo_auditor.serve.api.audit_user_api_only", return_value=report) as mock_audit: first = client.get("/api/report/octocat") second = client.get("/api/report/octocat") @@ -229,7 +229,7 @@ def test_rate_limit_returns_429_past_limit(tmp_path, monkeypatch) -> None: local_client = _make_client(tmp_path) report = ApiOnlyReport(username="octocat", audits=[]) - with patch("src.serve.api.audit_user_api_only", return_value=report) as mock_audit: + with patch("github_repo_auditor.serve.api.audit_user_api_only", return_value=report) as mock_audit: codes = [local_client.get("/api/report/octocat").status_code for _ in range(3)] assert codes == [200, 200, 429] # The 2nd request was a cache hit but still consumed throttle budget, so the @@ -246,7 +246,7 @@ def __init__(self, headers: dict[str, str], host: str | None) -> None: def test_client_ip_ignores_forwarded_by_default(monkeypatch) -> None: - from src.serve.api import client_ip + from github_repo_auditor.serve.api import client_ip monkeypatch.delenv("GHRA_TRUST_FORWARDED_FOR", raising=False) req = _FakeRequest({"x-forwarded-for": "9.9.9.9"}, host="1.2.3.4") @@ -255,7 +255,7 @@ def test_client_ip_ignores_forwarded_by_default(monkeypatch) -> None: def test_client_ip_honors_forwarded_when_trusted(monkeypatch) -> None: - from src.serve.api import client_ip + from github_repo_auditor.serve.api import client_ip monkeypatch.setenv("GHRA_TRUST_FORWARDED_FOR", "true") req = _FakeRequest({"x-forwarded-for": "9.9.9.9, 1.2.3.4"}, host="1.2.3.4") diff --git a/tests/test_serve_hosting.py b/tests/test_serve_hosting.py index 7ceb7d2e..455f2aaf 100644 --- a/tests/test_serve_hosting.py +++ b/tests/test_serve_hosting.py @@ -1,10 +1,10 @@ -"""Tests for src/serve/hosting.py — KV store, report cache, per-IP throttle.""" +"""Tests for src/github_repo_auditor/serve/hosting.py — KV store, report cache, per-IP throttle.""" from __future__ import annotations import pytest -from src.serve.hosting import ( +from github_repo_auditor.serve.hosting import ( InMemoryKVStore, RateLimiter, ReportCache, diff --git a/tests/test_serve_waitlist.py b/tests/test_serve_waitlist.py index f7f2bad2..e8bac403 100644 --- a/tests/test_serve_waitlist.py +++ b/tests/test_serve_waitlist.py @@ -1,10 +1,10 @@ -"""Tests for src/serve/waitlist.py — email capture store + validation.""" +"""Tests for src/github_repo_auditor/serve/waitlist.py — email capture store + validation.""" from __future__ import annotations import pytest -from src.serve.waitlist import ( +from github_repo_auditor.serve.waitlist import ( SqliteWaitlistStore, build_waitlist_store, is_valid_email, diff --git a/tests/test_similarity.py b/tests/test_similarity.py index 8ab8f49c..871ce20d 100644 --- a/tests/test_similarity.py +++ b/tests/test_similarity.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.similarity import classify_similarity, compute_file_hashes, find_similar_repos +from github_repo_auditor.similarity import classify_similarity, compute_file_hashes, find_similar_repos class TestComputeFileHashes: diff --git a/tests/test_sparkline.py b/tests/test_sparkline.py index 74b11a1f..78744373 100644 --- a/tests/test_sparkline.py +++ b/tests/test_sparkline.py @@ -1,4 +1,4 @@ -from src.sparkline import sparkline +from github_repo_auditor.sparkline import sparkline class TestSparkline: diff --git a/tests/test_suggest_initiatives.py b/tests/test_suggest_initiatives.py index ce72f3e9..bba8dfe2 100644 --- a/tests/test_suggest_initiatives.py +++ b/tests/test_suggest_initiatives.py @@ -1,4 +1,4 @@ -"""Tests for src/suggest_initiatives.py — Arc G Sprint 8.4 + 9.1 + 10.3 + 10.4 + 11.1 + 11.2 + 11.4 + 12.1.""" +"""Tests for src/github_repo_auditor/suggest_initiatives.py — Arc G Sprint 8.4 + 9.1 + 10.3 + 10.4 + 11.1 + 11.2 + 11.4 + 12.1.""" from __future__ import annotations @@ -8,8 +8,8 @@ import pytest -import src.suggest_initiatives as _si_mod -from src.llm_cost import BudgetExceededError +import github_repo_auditor.suggest_initiatives as _si_mod +from github_repo_auditor.llm_cost import BudgetExceededError DismissedSuggestion = _si_mod.DismissedSuggestion DismissalEvent = _si_mod.DismissalEvent @@ -162,7 +162,7 @@ def test_bronze_repo_with_no_target_returns_target_2(self): def test_platinum_repo_skipped(self): """Repos at tier 4 (Platinum) have no next tier — should be skipped.""" - from src.maturity_tiers import compute_tier + from github_repo_auditor.maturity_tiers import compute_tier repo = _platinum_repo() # Only include if actually Platinum (tier 4) @@ -190,7 +190,7 @@ def test_already_qualifying_repo_skipped(self): """If a repo already qualifies for target, missing_requirements is empty → skip.""" # A Silver-qualifying repo at target=2 has no missing requirements repo = _silver_qualifying_repo() - from src.maturity_tiers import compute_tier, tier_gap + from github_repo_auditor.maturity_tiers import compute_tier, tier_gap current = compute_tier(repo) if current < 2: @@ -370,7 +370,7 @@ class TestGenerateSuggestions: def test_no_provider_returns_deterministic_fallback(self): """When no LLM provider is available, return deterministic ranking.""" repos = [_near_silver_repo("Alpha"), _near_silver_repo("Beta")] - with patch("src.suggest_initiatives._resolve_provider", return_value=None): + with patch("github_repo_auditor.suggest_initiatives._resolve_provider", return_value=None): suggestions, cost = generate_suggestions(repos) assert cost == 0.0 assert isinstance(suggestions, list) @@ -395,7 +395,7 @@ def test_mock_provider_returns_parsed_suggestions(self): ) mock_provider = _MockProvider(canned) with patch( - "src.suggest_initiatives._resolve_provider", return_value=(mock_provider, "test-model") + "github_repo_auditor.suggest_initiatives._resolve_provider", return_value=(mock_provider, "test-model") ): suggestions, cost = generate_suggestions(repos, budget_usd=1.0) assert len(suggestions) >= 1 @@ -408,7 +408,7 @@ def test_budget_exceeded_raises(self): repos = [_near_silver_repo(f"Repo{i}") for i in range(20)] mock_provider = _MockProvider("[]") with patch( - "src.suggest_initiatives._resolve_provider", + "github_repo_auditor.suggest_initiatives._resolve_provider", return_value=(mock_provider, "test-model"), ): # Force a very tiny budget so the pre-call estimate triggers @@ -434,7 +434,7 @@ def test_specific_target_tier_propagates(self): ) mock_provider = _MockProvider(canned) with patch( - "src.suggest_initiatives._resolve_provider", + "github_repo_auditor.suggest_initiatives._resolve_provider", return_value=(mock_provider, "test-model"), ): suggestions, _ = generate_suggestions(repos, target_tier=3, budget_usd=1.0) @@ -461,7 +461,7 @@ def test_no_portfolio_truth_prints_warning(self, tmp_path, capsys): suggest_initiatives=0, llm_budget=None, ) - from src.cli import _run_suggest_initiatives_mode + from github_repo_auditor.cli import _run_suggest_initiatives_mode _run_suggest_initiatives_mode(args) captured = capsys.readouterr() @@ -485,8 +485,8 @@ def test_with_valid_portfolio_truth_prints_table(self, tmp_path, capsys): llm_budget=None, ) - with patch("src.suggest_initiatives._resolve_provider", return_value=None): - from src.cli import _run_suggest_initiatives_mode # noqa: PLC0415 + with patch("github_repo_auditor.suggest_initiatives._resolve_provider", return_value=None): + from github_repo_auditor.cli import _run_suggest_initiatives_mode # noqa: PLC0415 _run_suggest_initiatives_mode(args) @@ -518,8 +518,8 @@ def _mock_gen(projects, target_tier=None, budget_usd=0.10, **kw): # _run_suggest_initiatives_mode imports generate_suggestions locally, so patch # the name in the cli module's namespace after the local import happens - with patch("src.suggest_initiatives.generate_suggestions", _mock_gen): - from src.cli import _run_suggest_initiatives_mode + with patch("github_repo_auditor.suggest_initiatives.generate_suggestions", _mock_gen): + from github_repo_auditor.cli import _run_suggest_initiatives_mode _run_suggest_initiatives_mode(args) @@ -546,8 +546,8 @@ def _mock_gen(projects, target_tier=None, budget_usd=0.10, **kw): captured_budgets.append(budget_usd) return [], 0.0 - with patch("src.suggest_initiatives.generate_suggestions", _mock_gen): - from src.cli import _run_suggest_initiatives_mode + with patch("github_repo_auditor.suggest_initiatives.generate_suggestions", _mock_gen): + from github_repo_auditor.cli import _run_suggest_initiatives_mode _run_suggest_initiatives_mode(args) @@ -557,7 +557,7 @@ def test_parser_suggest_initiatives_const_is_0(self): """--suggest-initiatives with no value → args.suggest_initiatives == 0 (sentinel).""" import argparse - from src.cli import _build_triage_subparser + from github_repo_auditor.cli import _build_triage_subparser sp = argparse.ArgumentParser() subs = sp.add_subparsers(dest="subcommand") @@ -569,7 +569,7 @@ def test_parser_suggest_initiatives_with_value(self): """--suggest-initiatives 4 → args.suggest_initiatives == 4.""" import argparse - from src.cli import _build_triage_subparser + from github_repo_auditor.cli import _build_triage_subparser sp = argparse.ArgumentParser() subs = sp.add_subparsers(dest="subcommand") @@ -599,10 +599,10 @@ def _make_audit(self, name: str = "Repo") -> dict: def test_include_suggestions_false_skips_llm(self): """build_briefing(include_suggestions=False) → suggested_initiatives is empty, no LLM call.""" - from src.briefing import build_briefing + from github_repo_auditor.briefing import build_briefing audits = [self._make_audit()] - with patch("src.narrative._resolve_provider") as mock_resolve: + with patch("github_repo_auditor.narrative._resolve_provider") as mock_resolve: briefing = build_briefing( audits, "user", "2026-05-11", use_history=False, include_suggestions=False ) @@ -612,7 +612,7 @@ def test_include_suggestions_false_skips_llm(self): def test_include_suggestions_true_with_mock_provider_populates_field(self): """build_briefing(include_suggestions=True) with mock provider → suggestions populated.""" - from src.briefing import build_briefing + from github_repo_auditor.briefing import build_briefing # Use near_silver repos which have only 1 missing requirement (passes max_missing=3 filter) audits = [_near_silver_repo(f"Repo{i}") for i in range(2)] @@ -629,7 +629,7 @@ def test_include_suggestions_true_with_mock_provider_populates_field(self): ) mock_provider = _MockProvider(canned) with patch( - "src.suggest_initiatives._resolve_provider", + "github_repo_auditor.suggest_initiatives._resolve_provider", return_value=(mock_provider, "test-model"), ): briefing = build_briefing( @@ -644,7 +644,7 @@ def test_include_suggestions_true_with_mock_provider_populates_field(self): def test_render_markdown_includes_section_when_non_empty(self): """render_markdown includes '## Suggested Initiatives' when field is populated.""" - from src.briefing import Briefing, InitiativeSuggestionRow, render_markdown + from github_repo_auditor.briefing import Briefing, InitiativeSuggestionRow, render_markdown briefing = Briefing( username="user", @@ -667,7 +667,7 @@ def test_render_markdown_includes_section_when_non_empty(self): def test_render_markdown_omits_section_when_empty(self): """render_markdown omits '## Suggested Initiatives' when field is empty.""" - from src.briefing import Briefing, render_markdown + from github_repo_auditor.briefing import Briefing, render_markdown briefing = Briefing(username="user", date="2026-05-11") md = render_markdown(briefing) @@ -755,7 +755,7 @@ def test_happy_path_explicit_deadline_and_tier(self, tmp_path): project = _near_silver_repo("Wavelength") projects = [project] - with patch("src.suggest_initiatives._resolve_provider", return_value=None): + with patch("github_repo_auditor.suggest_initiatives._resolve_provider", return_value=None): initiative = accept_suggestion( repo_name="Wavelength", projects=projects, @@ -781,7 +781,7 @@ def test_defaults_derived_when_no_deadline_or_tier(self, tmp_path): project = _near_silver_repo("AutoRepo") projects = [project] - with patch("src.suggest_initiatives._resolve_provider", return_value=None): + with patch("github_repo_auditor.suggest_initiatives._resolve_provider", return_value=None): initiative = accept_suggestion( repo_name="AutoRepo", projects=projects, @@ -789,7 +789,7 @@ def test_defaults_derived_when_no_deadline_or_tier(self, tmp_path): ) # target = current + 1 (Bronze=1 → Silver=2) - from src.maturity_tiers import compute_tier + from github_repo_auditor.maturity_tiers import compute_tier current = compute_tier(project) assert initiative.target_tier == current + 1 @@ -801,7 +801,7 @@ def test_re_accept_overwrites_prior_initiative(self, tmp_path): project = _near_silver_repo("DupRepo") projects = [project] - with patch("src.suggest_initiatives._resolve_provider", return_value=None): + with patch("github_repo_auditor.suggest_initiatives._resolve_provider", return_value=None): accept_suggestion( repo_name="DupRepo", projects=projects, @@ -826,7 +826,7 @@ def test_override_target_tier_via_re_accept(self, tmp_path): project = _near_silver_repo("TierSwap") projects = [project] - with patch("src.suggest_initiatives._resolve_provider", return_value=None): + with patch("github_repo_auditor.suggest_initiatives._resolve_provider", return_value=None): accept_suggestion( repo_name="TierSwap", projects=projects, @@ -835,7 +835,7 @@ def test_override_target_tier_via_re_accept(self, tmp_path): target_tier=2, ) # Re-accept with target_tier=3 if current allows it - from src.maturity_tiers import compute_tier + from github_repo_auditor.maturity_tiers import compute_tier current = compute_tier(project) if current < 3: @@ -871,7 +871,7 @@ def test_no_git_repo_raises_value_error(self, tmp_path): def test_platinum_repo_raises_value_error(self, tmp_path): """Repo at tier 4 raises ValueError mentioning 'already at Platinum'.""" - from src.maturity_tiers import compute_tier + from github_repo_auditor.maturity_tiers import compute_tier repo = _platinum_repo("MaxedOut") tier = compute_tier(repo) @@ -888,7 +888,7 @@ def test_platinum_repo_raises_value_error(self, tmp_path): def test_target_tier_not_greater_than_current_raises(self, tmp_path): """target_tier <= current_tier raises ValueError mentioning 'must be greater than'.""" project = _near_silver_repo("LowTarget") - from src.maturity_tiers import compute_tier + from github_repo_auditor.maturity_tiers import compute_tier current = compute_tier(project) with pytest.raises(ValueError, match="must be greater than"): @@ -946,7 +946,7 @@ def test_no_portfolio_truth_prints_warning(self, tmp_path, capsys): """Missing portfolio-truth-latest.json → warning printed, exit 0.""" import argparse - from src.cli import _run_accept_suggestion_mode + from github_repo_auditor.cli import _run_accept_suggestion_mode args = argparse.Namespace( output_dir=str(tmp_path), @@ -972,8 +972,8 @@ def test_valid_repo_creates_initiative(self, tmp_path, capsys): target_tier=2, ) - with patch("src.suggest_initiatives._resolve_provider", return_value=None): - from src.cli import _run_accept_suggestion_mode + with patch("github_repo_auditor.suggest_initiatives._resolve_provider", return_value=None): + from github_repo_auditor.cli import _run_accept_suggestion_mode _run_accept_suggestion_mode(args) @@ -997,8 +997,8 @@ def test_repo_not_in_truth_exits_2(self, tmp_path, capsys): target_tier=None, ) - with patch("src.suggest_initiatives._resolve_provider", return_value=None): - from src.cli import _run_accept_suggestion_mode + with patch("github_repo_auditor.suggest_initiatives._resolve_provider", return_value=None): + from github_repo_auditor.cli import _run_accept_suggestion_mode with pytest.raises(SystemExit) as exc_info: _run_accept_suggestion_mode(args) @@ -1019,8 +1019,8 @@ def test_deadline_and_target_tier_overrides_applied(self, tmp_path, capsys): target_tier=2, ) - with patch("src.suggest_initiatives._resolve_provider", return_value=None): - from src.cli import _run_accept_suggestion_mode + with patch("github_repo_auditor.suggest_initiatives._resolve_provider", return_value=None): + from github_repo_auditor.cli import _run_accept_suggestion_mode _run_accept_suggestion_mode(args) @@ -1033,7 +1033,7 @@ def test_parser_accept_suggestion_flag_registered(self): """--accept-suggestion REPO is registered in _build_triage_subparser.""" import argparse - from src.cli import _build_triage_subparser + from github_repo_auditor.cli import _build_triage_subparser sp = argparse.ArgumentParser() subs = sp.add_subparsers(dest="subcommand") @@ -1076,7 +1076,7 @@ def test_same_cache_key_calls_provider_once(self): mock_provider = self._make_mock_provider(repos) with patch( - "src.suggest_initiatives._resolve_provider", + "github_repo_auditor.suggest_initiatives._resolve_provider", return_value=(mock_provider, "test-model"), ): suggestions1, cost1 = generate_suggestions(repos, budget_usd=1.0, cache_key="k1") @@ -1092,7 +1092,7 @@ def test_different_cache_keys_call_provider_twice(self): mock_provider = self._make_mock_provider(repos) with patch( - "src.suggest_initiatives._resolve_provider", + "github_repo_auditor.suggest_initiatives._resolve_provider", return_value=(mock_provider, "test-model"), ): generate_suggestions(repos, budget_usd=1.0, cache_key="key-a") @@ -1106,7 +1106,7 @@ def test_no_cache_key_calls_provider_every_time(self): mock_provider = self._make_mock_provider(repos) with patch( - "src.suggest_initiatives._resolve_provider", + "github_repo_auditor.suggest_initiatives._resolve_provider", return_value=(mock_provider, "test-model"), ): generate_suggestions(repos, budget_usd=1.0) @@ -1120,7 +1120,7 @@ def test_clear_cache_causes_re_invocation(self): mock_provider = self._make_mock_provider(repos) with patch( - "src.suggest_initiatives._resolve_provider", + "github_repo_auditor.suggest_initiatives._resolve_provider", return_value=(mock_provider, "test-model"), ): generate_suggestions(repos, budget_usd=1.0, cache_key="ck") @@ -1135,13 +1135,13 @@ def test_cached_cost_value_is_preserved(self): mock_provider = self._make_mock_provider(repos) with patch( - "src.suggest_initiatives._resolve_provider", + "github_repo_auditor.suggest_initiatives._resolve_provider", return_value=(mock_provider, "test-model"), ): _, cost_first = generate_suggestions(repos, budget_usd=1.0, cache_key="cost-k") with patch( - "src.suggest_initiatives._resolve_provider", + "github_repo_auditor.suggest_initiatives._resolve_provider", return_value=(_MockProvider("[]"), "test-model"), ): _, cost_second = generate_suggestions(repos, budget_usd=1.0, cache_key="cost-k") @@ -1177,7 +1177,7 @@ def _exploding_provider(*args, **kwargs): repos = [_near_silver_repo("NeverCall")] with patch( - "src.suggest_initiatives._resolve_provider", + "github_repo_auditor.suggest_initiatives._resolve_provider", side_effect=_exploding_provider, ): # Should NOT raise despite the exploding mock @@ -1205,7 +1205,7 @@ def _exploding_provider(*args, **kwargs): raise AssertionError("Should not be called on cache hit") with patch( - "src.suggest_initiatives._resolve_provider", + "github_repo_auditor.suggest_initiatives._resolve_provider", side_effect=_exploding_provider, ): suggestions2, cost2 = generate_suggestions( @@ -1227,7 +1227,7 @@ def _exploding_provider(*args, **kwargs): raise AssertionError("accept_suggestion must not call LLM for deadline derivation") with patch( - "src.suggest_initiatives._resolve_provider", + "github_repo_auditor.suggest_initiatives._resolve_provider", side_effect=_exploding_provider, ): initiative = accept_suggestion( @@ -1345,7 +1345,7 @@ def test_load_malformed_json_returns_empty_ordered_dict(self, tmp_path, caplog): bad = tmp_path / "suggestion-cache.json" bad.write_text("not valid json{{{", encoding="utf-8") - with caplog.at_level(logging.WARNING, logger="src.suggest_initiatives"): + with caplog.at_level(logging.WARNING, logger="github_repo_auditor.suggest_initiatives"): result = load_suggestion_cache(bad) assert result == OrderedDict() assert any("could not load cache" in r.message for r in caplog.records) @@ -1374,7 +1374,7 @@ def test_generate_suggestions_writes_to_disk_after_llm_call(self, tmp_path): cache_key = "disk-write-test" with patch( - "src.suggest_initiatives._resolve_provider", + "github_repo_auditor.suggest_initiatives._resolve_provider", return_value=(None, None), ): # No provider → deterministic fallback; still writes to disk when output_dir set @@ -1396,7 +1396,7 @@ def test_generate_suggestions_loads_from_disk_on_new_process(self, tmp_path): # First call — populates disk cache with patch( - "src.suggest_initiatives._resolve_provider", + "github_repo_auditor.suggest_initiatives._resolve_provider", return_value=(None, None), ): suggestions1, cost1 = generate_suggestions( @@ -1411,7 +1411,7 @@ def _exploding_provider(*args, **kwargs): raise AssertionError("LLM must not be called on cache hit from disk") with patch( - "src.suggest_initiatives._resolve_provider", + "github_repo_auditor.suggest_initiatives._resolve_provider", side_effect=_exploding_provider, ): suggestions2, cost2 = generate_suggestions( @@ -1597,7 +1597,7 @@ def test_load_malformed_json_returns_empty_list(self, tmp_path, caplog): path = dismissed_path(tmp_path) path.write_text("not json{{{{", encoding="utf-8") - with caplog.at_level(logging.WARNING, logger="src.suggest_initiatives"): + with caplog.at_level(logging.WARNING, logger="github_repo_auditor.suggest_initiatives"): result = load_dismissed(path) assert result == [] assert any("could not load dismissed" in r.message for r in caplog.records) @@ -1766,7 +1766,7 @@ def test_dismiss_suggestion_writes_file_and_prints_confirmation(self, tmp_path, """--dismiss-suggestion FooRepo writes dismissed file, stdout has confirmation.""" import argparse - from src.cli import _run_dismiss_suggestion_mode + from github_repo_auditor.cli import _run_dismiss_suggestion_mode args = argparse.Namespace( output_dir=str(tmp_path), @@ -1785,7 +1785,7 @@ def test_dismiss_empty_repo_name_exits_2(self, tmp_path, capsys): """--dismiss-suggestion '' exits with code 2.""" import argparse - from src.cli import _run_dismiss_suggestion_mode + from github_repo_auditor.cli import _run_dismiss_suggestion_mode args = argparse.Namespace( output_dir=str(tmp_path), @@ -1800,7 +1800,7 @@ def test_undo_dismiss_removes_and_prints_confirmation(self, tmp_path, capsys): """--undo-dismiss FooRepo after dismissal removes entry and prints confirmation.""" import argparse - from src.cli import _run_undo_dismiss_mode + from github_repo_auditor.cli import _run_undo_dismiss_mode # Pre-dismiss dismiss_suggestion_record(dismissed_path(tmp_path), repo_name="FooRepo") @@ -1818,7 +1818,7 @@ def test_undo_dismiss_unknown_repo_prints_warning_exit_0(self, tmp_path, capsys) """--undo-dismiss UnknownRepo prints a warning but exits 0.""" import argparse - from src.cli import _run_undo_dismiss_mode + from github_repo_auditor.cli import _run_undo_dismiss_mode args = argparse.Namespace(output_dir=str(tmp_path), undo_dismiss="UnknownRepo") _run_undo_dismiss_mode(args) # must not raise @@ -1829,7 +1829,7 @@ def test_list_dismissed_empty_file(self, tmp_path, capsys): """--list-dismissed with no dismissed entries prints 'No dismissed suggestions.'.""" import argparse - from src.cli import _run_list_dismissed_mode + from github_repo_auditor.cli import _run_list_dismissed_mode args = argparse.Namespace(output_dir=str(tmp_path)) _run_list_dismissed_mode(args) @@ -1840,7 +1840,7 @@ def test_list_dismissed_with_entries_prints_each(self, tmp_path, capsys): """--list-dismissed with entries prints each repo on its own line.""" import argparse - from src.cli import _run_list_dismissed_mode + from github_repo_auditor.cli import _run_list_dismissed_mode dismiss_suggestion_record(dismissed_path(tmp_path), repo_name="RepoA", reason="r1") dismiss_suggestion_record(dismissed_path(tmp_path), repo_name="RepoB", reason="r2") @@ -1854,7 +1854,7 @@ def test_list_dismissed_with_entries_prints_each(self, tmp_path, capsys): def test_parser_dismiss_suggestion_flags_registered(self): """--dismiss-suggestion, --reason, --undo-dismiss, --list-dismissed all registered.""" - from src.cli import build_subcommand_parser + from github_repo_auditor.cli import build_subcommand_parser parser = build_subcommand_parser() args = parser.parse_args( @@ -2150,7 +2150,7 @@ def test_dismiss_with_expires_days_sets_expiry(self, tmp_path): import argparse from datetime import timedelta - from src.cli import _run_dismiss_suggestion_mode + from github_repo_auditor.cli import _run_dismiss_suggestion_mode args = argparse.Namespace( output_dir=str(tmp_path), @@ -2168,7 +2168,7 @@ def test_expire_dismissals_no_expired(self, tmp_path, capsys): """--expire-dismissals with nothing expired prints 'No dismissals to expire.'""" import argparse - from src.cli import _run_expire_dismissals_mode + from github_repo_auditor.cli import _run_expire_dismissals_mode args = argparse.Namespace(output_dir=str(tmp_path)) _run_expire_dismissals_mode(args) @@ -2180,7 +2180,7 @@ def test_expire_dismissals_removes_expired_entry(self, tmp_path, capsys): import argparse from datetime import timedelta - from src.cli import _run_expire_dismissals_mode + from github_repo_auditor.cli import _run_expire_dismissals_mode dismiss_suggestion_record(dismissed_path(tmp_path), repo_name="ExpRepo", expires_days=0) p = dismissed_path(tmp_path) @@ -2199,7 +2199,7 @@ def test_dismissal_history_empty(self, tmp_path, capsys): """--dismissal-history with no events prints 'No dismissal history.'""" import argparse - from src.cli import _run_dismissal_history_mode + from github_repo_auditor.cli import _run_dismissal_history_mode args = argparse.Namespace(output_dir=str(tmp_path)) _run_dismissal_history_mode(args) @@ -2210,7 +2210,7 @@ def test_dismissal_history_with_events_prints_each(self, tmp_path, capsys): """--dismissal-history with events prints each event's repo + type.""" import argparse - from src.cli import _run_dismissal_history_mode + from github_repo_auditor.cli import _run_dismissal_history_mode dismiss_suggestion_record(dismissed_path(tmp_path), repo_name="RepoX", reason="noisy") dismiss_suggestion_record(dismissed_path(tmp_path), repo_name="RepoY") @@ -2227,7 +2227,7 @@ def test_dismissal_history_with_events_prints_each(self, tmp_path, capsys): def test_parser_new_flags_registered(self): """--dismiss-expires-days, --expire-dismissals, --dismissal-history all registered.""" - from src.cli import build_subcommand_parser + from github_repo_auditor.cli import build_subcommand_parser parser = build_subcommand_parser() args = parser.parse_args( diff --git a/tests/test_testing_analyzer.py b/tests/test_testing_analyzer.py index eadf798f..602dd42a 100644 --- a/tests/test_testing_analyzer.py +++ b/tests/test_testing_analyzer.py @@ -5,7 +5,7 @@ import pytest -from src.analyzers.testing import TestingAnalyzer +from github_repo_auditor.analyzers.testing import TestingAnalyzer class TestTestingAnalyzer: diff --git a/tests/test_tier_gaps_export.py b/tests/test_tier_gaps_export.py index caf7ec22..8cba264f 100644 --- a/tests/test_tier_gaps_export.py +++ b/tests/test_tier_gaps_export.py @@ -23,8 +23,8 @@ import pytest -from src.app.portfolio_analysis import _print_tier_gaps_markdown, _run_tier_gaps_export_mode -from src.cli import build_subcommand_parser +from github_repo_auditor.app.portfolio_analysis import _print_tier_gaps_markdown, _run_tier_gaps_export_mode +from github_repo_auditor.cli import build_subcommand_parser # ── Fixtures ────────────────────────────────────────────────────────────────── @@ -157,7 +157,7 @@ def test_bronze_included_tier4_and_no_git_skipped(self, tmp_path, capsys): tier_map = {"bronze-repo": 1, "tier4-repo": 4, "no-git-repo": 0} with patch( - "src.maturity_tiers.compute_tier", + "github_repo_auditor.maturity_tiers.compute_tier", side_effect=lambda p: tier_map.get( (p.get("identity") or {}).get("display_name", ""), 1 ), @@ -176,7 +176,7 @@ def test_bronze_included_tier4_and_no_git_skipped(self, tmp_path, capsys): class TestTargetOverride: def test_target_override_3_applied(self, tmp_path, capsys): """--tier-gaps-target 3 → all gaps have target_tier 3.""" - from src.maturity_tiers import compute_tier + from github_repo_auditor.maturity_tiers import compute_tier bronze = _bronze_project("repo-a") if compute_tier(bronze) in (0, 4): @@ -200,7 +200,7 @@ def test_target_override_equal_to_current_skips_repo(self, tmp_path, capsys): _write_truth(tmp_path, [project]) # Mock compute_tier to return 2 (Silver) so target_override==current==2 - with patch("src.maturity_tiers.compute_tier", return_value=2): + with patch("github_repo_auditor.maturity_tiers.compute_tier", return_value=2): args = _make_args(str(tmp_path), tier_gaps_target=2, fmt="json") _run_tier_gaps_export_mode(args) @@ -238,7 +238,7 @@ def test_target_0_exits_2(self, tmp_path): class TestMarkdownFormat: def test_markdown_output_has_table_header(self, tmp_path, capsys): """--format markdown → stdout contains markdown table header.""" - from src.maturity_tiers import compute_tier + from github_repo_auditor.maturity_tiers import compute_tier bronze = _bronze_project("md-repo") if compute_tier(bronze) in (0, 4): @@ -274,7 +274,7 @@ def test_json_envelope_structure(self, tmp_path, capsys): def test_json_gap_fields_present(self, tmp_path, capsys): """Each gap entry has all required fields.""" - from src.maturity_tiers import compute_tier + from github_repo_auditor.maturity_tiers import compute_tier bronze = _bronze_project("field-check") if compute_tier(bronze) in (0, 4): @@ -299,7 +299,7 @@ def test_json_gap_fields_present(self, tmp_path, capsys): class TestRequirementSources: def test_requirement_sources_parallel_to_missing(self, tmp_path, capsys): """missing_requirements and requirement_sources have same length.""" - from src.maturity_tiers import compute_tier + from github_repo_auditor.maturity_tiers import compute_tier bronze = _bronze_project("src-check") if compute_tier(bronze) in (0, 4): @@ -341,7 +341,7 @@ def test_none_display_name_skipped(self, tmp_path, capsys): class TestDefaultTarget: def test_default_target_is_current_plus_one(self, tmp_path, capsys): """Without --tier-gaps-target, each repo's target = current + 1.""" - from src.maturity_tiers import compute_tier + from github_repo_auditor.maturity_tiers import compute_tier bronze = _bronze_project("default-target") current = compute_tier(bronze) diff --git a/tests/test_tier_recalibration.py b/tests/test_tier_recalibration.py index 599456ce..7881cf81 100644 --- a/tests/test_tier_recalibration.py +++ b/tests/test_tier_recalibration.py @@ -1,6 +1,6 @@ # tests/test_tier_recalibration.py """Tests for the tier distribution report (Arc H A4).""" -from src.tier_recalibration import tier_distribution_report +from github_repo_auditor.tier_recalibration import tier_distribution_report def _make_repo(tier: int) -> dict: @@ -8,7 +8,7 @@ def _make_repo(tier: int) -> dict: def test_report_counts_tiers_correctly(monkeypatch): - from src import tier_recalibration + from github_repo_auditor import tier_recalibration monkeypatch.setattr(tier_recalibration, "compute_tier", lambda r: r["_mock_tier"]) @@ -28,7 +28,7 @@ def test_report_counts_tiers_correctly(monkeypatch): def test_report_computes_percentages(monkeypatch): - from src import tier_recalibration + from github_repo_auditor import tier_recalibration monkeypatch.setattr(tier_recalibration, "compute_tier", lambda r: r["_mock_tier"]) @@ -46,7 +46,7 @@ def test_report_empty_repos(): def test_report_flags_bunching_when_bronze_over_60_percent(monkeypatch): - from src import tier_recalibration + from github_repo_auditor import tier_recalibration monkeypatch.setattr(tier_recalibration, "compute_tier", lambda r: r["_mock_tier"]) @@ -56,7 +56,7 @@ def test_report_flags_bunching_when_bronze_over_60_percent(monkeypatch): def test_report_no_bunching_when_distributed(monkeypatch): - from src import tier_recalibration + from github_repo_auditor import tier_recalibration monkeypatch.setattr(tier_recalibration, "compute_tier", lambda r: r["_mock_tier"]) diff --git a/tests/test_vuln_check.py b/tests/test_vuln_check.py index a6e776e4..669c25b8 100644 --- a/tests/test_vuln_check.py +++ b/tests/test_vuln_check.py @@ -4,7 +4,7 @@ from pathlib import Path from unittest.mock import MagicMock, patch -from src.vuln_check import ( +from github_repo_auditor.vuln_check import ( check_vulnerabilities, format_vuln_summary, ) @@ -48,7 +48,7 @@ def test_returns_empty_when_no_dep_versions(self) -> None: {"dimension": "dependencies", "score": 1.0, "details": {}} ], } - with patch("src.vuln_check.requests.post") as mock_post: + with patch("github_repo_auditor.vuln_check.requests.post") as mock_post: result = check_vulnerabilities([audit]) mock_post.assert_not_called() assert result == {} @@ -64,7 +64,7 @@ def test_skips_non_dependency_dimensions(self) -> None: } ], } - with patch("src.vuln_check.requests.post") as mock_post: + with patch("github_repo_auditor.vuln_check.requests.post") as mock_post: result = check_vulnerabilities([audit]) mock_post.assert_not_called() assert result == {} @@ -77,7 +77,7 @@ def test_returns_vulns_for_affected_dep(self) -> None: "severity": [{"type": "CVSS_V3", "score": "9.8"}], } mock_resp = _osv_response([[vuln]]) - with patch("src.vuln_check.requests.post", return_value=mock_resp): + with patch("github_repo_auditor.vuln_check.requests.post", return_value=mock_resp): result = check_vulnerabilities([audit]) assert "my-repo" in result @@ -90,7 +90,7 @@ def test_returns_vulns_for_affected_dep(self) -> None: def test_empty_vulns_list_not_included(self) -> None: audit = _make_audit("clean-repo", [("flask", "2.0.0", "pypi")]) mock_resp = _osv_response([[]]) # no vulns - with patch("src.vuln_check.requests.post", return_value=mock_resp): + with patch("github_repo_auditor.vuln_check.requests.post", return_value=mock_resp): result = check_vulnerabilities([audit]) assert result == {} @@ -107,7 +107,7 @@ def fake_post(url, json, timeout): captured_body.append(json) return _osv_response([[], [], []]) - with patch("src.vuln_check.requests.post", side_effect=fake_post): + with patch("github_repo_auditor.vuln_check.requests.post", side_effect=fake_post): check_vulnerabilities([audit]) assert captured_body @@ -117,7 +117,7 @@ def fake_post(url, json, timeout): def test_api_error_returns_empty(self, capsys) -> None: audit = _make_audit("repo", [("requests", "2.0", "pypi")]) - with patch("src.vuln_check.requests.post", side_effect=Exception("network error")): + with patch("github_repo_auditor.vuln_check.requests.post", side_effect=Exception("network error")): result = check_vulnerabilities([audit]) assert result == {} captured = capsys.readouterr() @@ -127,24 +127,24 @@ def test_non_200_response_skipped(self, capsys) -> None: audit = _make_audit("repo", [("requests", "2.0", "pypi")]) mock_resp = MagicMock() mock_resp.status_code = 500 - with patch("src.vuln_check.requests.post", return_value=mock_resp): + with patch("github_repo_auditor.vuln_check.requests.post", return_value=mock_resp): result = check_vulnerabilities([audit]) assert result == {} def test_uses_cache_on_second_call(self, tmp_path: Path) -> None: - from src.cache import ResponseCache + from github_repo_auditor.cache import ResponseCache cache = ResponseCache(cache_dir=tmp_path / ".cache", ttl=3600) audit = _make_audit("repo", [("requests", "2.0", "pypi")]) vuln = {"id": "CVE-001", "summary": "bug", "severity": []} mock_resp = _osv_response([[vuln]]) - with patch("src.vuln_check.requests.post", return_value=mock_resp) as mock_post: + with patch("github_repo_auditor.vuln_check.requests.post", return_value=mock_resp) as mock_post: result1 = check_vulnerabilities([audit], cache=cache) assert mock_post.call_count == 1 # Second call should use cache — no new HTTP request - with patch("src.vuln_check.requests.post", return_value=mock_resp) as mock_post2: + with patch("github_repo_auditor.vuln_check.requests.post", return_value=mock_resp) as mock_post2: result2 = check_vulnerabilities([audit], cache=cache) assert mock_post2.call_count == 0 assert result2 == result1 @@ -154,7 +154,7 @@ def test_summary_truncated_to_200_chars(self) -> None: audit = _make_audit("repo", [("pkg", "1.0", "pypi")]) vuln = {"id": "CVE-001", "summary": long_summary, "severity": []} mock_resp = _osv_response([[vuln]]) - with patch("src.vuln_check.requests.post", return_value=mock_resp): + with patch("github_repo_auditor.vuln_check.requests.post", return_value=mock_resp): result = check_vulnerabilities([audit]) assert len(result["repo"][0]["summary"]) <= 200 diff --git a/tests/test_warehouse.py b/tests/test_warehouse.py index 75209333..37fcf089 100644 --- a/tests/test_warehouse.py +++ b/tests/test_warehouse.py @@ -3,10 +3,10 @@ import sqlite3 from datetime import datetime, timezone -from src.baseline_context import build_baseline_context -from src.models import AnalyzerResult, RepoMetadata -from src.scorer import WEIGHTS, score_repo -from src.warehouse import ( +from github_repo_auditor.baseline_context import build_baseline_context +from github_repo_auditor.models import AnalyzerResult, RepoMetadata +from github_repo_auditor.scorer import WEIGHTS, score_repo +from github_repo_auditor.warehouse import ( load_action_sync_automation, load_approval_followup_events, load_approval_records, @@ -111,7 +111,7 @@ def test_write_warehouse_snapshot_persists_core_entities(tmp_path): } ] - from src.models import AuditReport + from github_repo_auditor.models import AuditReport report = AuditReport.from_audits("user", [audit], [], 1) report.baseline_context = build_baseline_context( @@ -553,7 +553,7 @@ def test_write_warehouse_snapshot_persists_core_entities(tmp_path): def test_write_warehouse_snapshot_defaults_empty_campaign_outcomes_for_older_style_rows(tmp_path): audit = score_repo(_make_metadata(), _make_results()) - from src.models import AuditReport + from github_repo_auditor.models import AuditReport report = AuditReport.from_audits("user", [audit], [], 1) write_warehouse_snapshot(report, tmp_path) @@ -577,7 +577,7 @@ def test_write_warehouse_snapshot_defaults_empty_campaign_outcomes_for_older_sty def test_write_warehouse_snapshot_expands_multi_action_target_results(tmp_path): audit = score_repo(_make_metadata(), _make_results()) - from src.models import AuditReport + from github_repo_auditor.models import AuditReport report = AuditReport.from_audits("user", [audit], [], 1) report.writeback_results = { @@ -630,7 +630,7 @@ def test_write_warehouse_snapshot_expands_multi_action_target_results(tmp_path): def test_approval_workflow_summary_and_records_round_trip(tmp_path): audit = score_repo(_make_metadata(), _make_results()) - from src.models import AuditReport + from github_repo_auditor.models import AuditReport report = AuditReport.from_audits("user", [audit], [], 1) report.approval_workflow_summary = { @@ -682,7 +682,7 @@ def test_approval_workflow_summary_and_records_round_trip(tmp_path): def test_approval_followup_events_round_trip(tmp_path): audit = score_repo(_make_metadata(), _make_results()) - from src.models import AuditReport + from github_repo_auditor.models import AuditReport report = AuditReport.from_audits("user", [audit], [], 1) write_warehouse_snapshot(report, tmp_path) diff --git a/tests/test_watch.py b/tests/test_watch.py index 9ae892e2..a0ac16af 100644 --- a/tests/test_watch.py +++ b/tests/test_watch.py @@ -2,7 +2,7 @@ import signal from unittest.mock import patch -from src.watch import run_watch_loop +from github_repo_auditor.watch import run_watch_loop class TestWatchLoop: diff --git a/tests/test_web_export.py b/tests/test_web_export.py index aa9b8550..bcb193f2 100644 --- a/tests/test_web_export.py +++ b/tests/test_web_export.py @@ -2,14 +2,14 @@ import json -from src.report_enrichment import ( +from github_repo_auditor.report_enrichment import ( build_queue_pressure_summary, build_top_recommendation_summary, build_weekly_review_pack, no_baseline_summary, no_linked_artifact_summary, ) -from src.web_export import _render_html, _risk_posture_section, export_html_dashboard +from github_repo_auditor.web_export import _render_html, _risk_posture_section, export_html_dashboard def _make_report(**overrides) -> dict: diff --git a/tests/test_weekly_command_center.py b/tests/test_weekly_command_center.py index a3df6da5..f6de8c46 100644 --- a/tests/test_weekly_command_center.py +++ b/tests/test_weekly_command_center.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.weekly_command_center import ( +from github_repo_auditor.weekly_command_center import ( build_weekly_command_center_digest, render_weekly_command_center_markdown, ) diff --git a/tests/test_weekly_packaging.py b/tests/test_weekly_packaging.py index 9800913c..e49f6c6c 100644 --- a/tests/test_weekly_packaging.py +++ b/tests/test_weekly_packaging.py @@ -1,6 +1,6 @@ from __future__ import annotations -from src.weekly_packaging import finalize_weekly_pack +from github_repo_auditor.weekly_packaging import finalize_weekly_pack def test_finalize_weekly_pack_adds_story_and_compact_explainability() -> None: diff --git a/tests/test_weekly_scheduling_overlay.py b/tests/test_weekly_scheduling_overlay.py index 2640c6b4..bd20fbb6 100644 --- a/tests/test_weekly_scheduling_overlay.py +++ b/tests/test_weekly_scheduling_overlay.py @@ -2,7 +2,7 @@ import pytest -from src.weekly_scheduling_overlay import apply_weekly_scheduling_overlay +from github_repo_auditor.weekly_scheduling_overlay import apply_weekly_scheduling_overlay def _base_weekly_pack() -> dict: diff --git a/tests/test_weekly_story.py b/tests/test_weekly_story.py index e444cf61..dfaa0c1e 100644 --- a/tests/test_weekly_story.py +++ b/tests/test_weekly_story.py @@ -4,11 +4,11 @@ from openpyxl import Workbook -from src.excel_export import _build_print_pack -from src.report_enrichment import build_weekly_review_pack -from src.review_pack import export_review_pack -from src.scheduled_handoff import render_scheduled_handoff_markdown -from src.web_export import _render_html +from github_repo_auditor.excel_export import _build_print_pack +from github_repo_auditor.report_enrichment import build_weekly_review_pack +from github_repo_auditor.review_pack import export_review_pack +from github_repo_auditor.scheduled_handoff import render_scheduled_handoff_markdown +from github_repo_auditor.web_export import _render_html def _report() -> dict: diff --git a/tests/test_workbook_gate.py b/tests/test_workbook_gate.py index b7663571..f469cab8 100644 --- a/tests/test_workbook_gate.py +++ b/tests/test_workbook_gate.py @@ -4,7 +4,7 @@ import pytest -from src.workbook_gate import format_gate_result, record_manual_signoff, run_workbook_gate +from github_repo_auditor.workbook_gate import format_gate_result, record_manual_signoff, run_workbook_gate def test_workbook_gate_generates_artifacts_and_validates(tmp_path): diff --git a/uv.lock b/uv.lock index 8616cff3..6339bc4d 100644 --- a/uv.lock +++ b/uv.lock @@ -699,7 +699,7 @@ requires-dist = [ { name = "fpdf2", specifier = ">=2.7.0" }, { name = "httpx", specifier = ">=0.27" }, { name = "jinja2", marker = "extra == 'serve'", specifier = ">=3.1" }, - { name = "mutmut", marker = "extra == 'dev'", specifier = ">=2.5" }, + { name = "mutmut", marker = "extra == 'dev'", specifier = ">=3.6,<4" }, { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.10" }, { name = "openpyxl", specifier = ">=3.1.0" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" }, diff --git a/web/README.md b/web/README.md index 5eb83600..42357eaa 100644 --- a/web/README.md +++ b/web/README.md @@ -2,7 +2,7 @@ Next.js (App Router) paste-a-username frontend for the clone-free portfolio report. The form calls the FastAPI engine's `GET /api/report/{username}` -endpoint (from `src/serve/api.py`) and renders the result with a "top fixes" +endpoint (from `src/github_repo_auditor/serve/api.py`) and renders the result with a "top fixes" framing: grades, repo health, and the highest-leverage actions per repo. ## Develop @@ -12,7 +12,7 @@ Run the Python API and the web app side by side. **1. Start the report API** (from the repo root): ```bash -uv run --extra serve python -m uvicorn --factory src.serve.app:create_app --port 8080 +uv run --extra serve python -m uvicorn --factory github_repo_auditor.serve.app:create_app --port 8080 ``` **2. Start the frontend** (from `web/`): diff --git a/web/lib/types.ts b/web/lib/types.ts index fc91d222..96791e7e 100644 --- a/web/lib/types.ts +++ b/web/lib/types.ts @@ -1,5 +1,5 @@ // Mirrors the JSON shape emitted by the Python engine's -// `ApiOnlyReport.to_dict()` / `RepoAudit.to_dict()` (src/api_only.py, src/models.py). +// `ApiOnlyReport.to_dict()` / `RepoAudit.to_dict()` (src/github_repo_auditor/api_only.py, src/github_repo_auditor/models.py). // Only the fields the UI renders are typed; the payload carries more. export interface RepoMetadata { From b187af1d7a80f9d975e224eb59a2570b67a052ff Mon Sep 17 00:00:00 2001 From: saagpatel <269905221+saagpatel@users.noreply.github.com> Date: Tue, 11 Aug 2026 11:54:16 -0700 Subject: [PATCH 2/2] fix(mutation): reject pytest internal errors --- scripts/check_mutation_score.py | 2 +- tests/test_mutation_score_gate.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/check_mutation_score.py b/scripts/check_mutation_score.py index 4aab9df0..93c8881e 100644 --- a/scripts/check_mutation_score.py +++ b/scripts/check_mutation_score.py @@ -10,7 +10,7 @@ from typing import Iterable -KILLED_EXIT_CODES = {1, 3, 37} +KILLED_EXIT_CODES = {1, 37} SURVIVED_EXIT_CODES = {0} TIMEOUT_EXIT_CODES = {-24, 24, 36, 152, 255} diff --git a/tests/test_mutation_score_gate.py b/tests/test_mutation_score_gate.py index ce83a618..60c17185 100644 --- a/tests/test_mutation_score_gate.py +++ b/tests/test_mutation_score_gate.py @@ -28,6 +28,16 @@ def test_main_fails_closed_on_unchecked_results(tmp_path: Path) -> None: assert main(["--mutants-dir", str(tmp_path), "--minimum", "0.5"]) == 1 +def test_main_fails_closed_on_pytest_internal_error(tmp_path: Path) -> None: + _write_meta(tmp_path, [1] * 9 + [0, 3]) + + counts = classify_exit_codes([3]) + + assert counts["invalid"] == 1 + assert counts["killed"] == 0 + assert main(["--mutants-dir", str(tmp_path), "--minimum", "0.85"]) == 1 + + def test_main_fails_when_no_results_exist(tmp_path: Path) -> None: assert main(["--mutants-dir", str(tmp_path)]) == 2