pi-dev-team 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/PORTING.md +134 -0
- package/README.md +207 -0
- package/UPSTREAM.json +64 -0
- package/agents/Explore.md +15 -0
- package/agents/a11y-review.md +118 -0
- package/agents/adr-author.md +70 -0
- package/agents/ai-provenance-review.md +120 -0
- package/agents/angular-reactivity-review.md +95 -0
- package/agents/arch-review.md +135 -0
- package/agents/architect.md +78 -0
- package/agents/autoship-batch-proposer.md +69 -0
- package/agents/claude-setup-review.md +136 -0
- package/agents/codebase-recon.md +184 -0
- package/agents/component-architecture-review.md +119 -0
- package/agents/concurrency-review.md +109 -0
- package/agents/correctness-review.md +290 -0
- package/agents/data-flow-tracer.md +120 -0
- package/agents/doc-review.md +165 -0
- package/agents/domain-review.md +136 -0
- package/agents/general-purpose.md +10 -0
- package/agents/gherkin-quality-critic.md +113 -0
- package/agents/js-fp-review.md +114 -0
- package/agents/mutation-kill.md +684 -0
- package/agents/naming-review.md +142 -0
- package/agents/orchestrator.md +339 -0
- package/agents/performance-review.md +105 -0
- package/agents/plan-review-acceptance.md +115 -0
- package/agents/plan-review-design.md +90 -0
- package/agents/plan-review-parallelization.md +84 -0
- package/agents/plan-review-strategic.md +96 -0
- package/agents/plan-review-ux.md +110 -0
- package/agents/platform-engineer.md +64 -0
- package/agents/product-manager.md +68 -0
- package/agents/progress-guardian.md +79 -0
- package/agents/qa-engineer.md +289 -0
- package/agents/quality-reviewer.md +132 -0
- package/agents/react-reactivity-review.md +102 -0
- package/agents/refactor-opportunity-review.md +128 -0
- package/agents/security-engineer.md +60 -0
- package/agents/security-review.md +218 -0
- package/agents/session-analysis.md +95 -0
- package/agents/software-engineer.md +105 -0
- package/agents/spec-compliance-review.md +100 -0
- package/agents/spec-reviewer.md +114 -0
- package/agents/structure-review.md +146 -0
- package/agents/tech-writer.md +84 -0
- package/agents/test-review.md +246 -0
- package/agents/test-smell-review.md +188 -0
- package/agents/token-efficiency-review.md +139 -0
- package/agents/ui-ux-designer.md +54 -0
- package/agents/vue-reactivity-review.md +95 -0
- package/bin/__pycache__/claudecpython-314.pyc +0 -0
- package/bin/claude +258 -0
- package/docs/upstream/.pages +1 -0
- package/docs/upstream/CHANGELOG.md +2586 -0
- package/docs/upstream/README.md +155 -0
- package/docs/upstream/agent-architecture.md +214 -0
- package/docs/upstream/agent_info.md +187 -0
- package/docs/upstream/artifact-migration.md +124 -0
- package/docs/upstream/code-intelligence-nudge.md +149 -0
- package/docs/upstream/code-review-process.md +294 -0
- package/docs/upstream/concurrent-use.md +73 -0
- package/docs/upstream/context-management.md +111 -0
- package/docs/upstream/developer-notes.md +280 -0
- package/docs/upstream/diagrams/architecture-overview.svg +101 -0
- package/docs/upstream/diagrams/review-dispatch.svg +139 -0
- package/docs/upstream/diagrams/team-agents.svg +128 -0
- package/docs/upstream/diagrams/test-improve-flow.svg +166 -0
- package/docs/upstream/diagrams/workflow-linear.svg +66 -0
- package/docs/upstream/diagrams/workflow-three-phase.svg +200 -0
- package/docs/upstream/eval-maintenance.md +95 -0
- package/docs/upstream/eval-running-guide.md +147 -0
- package/docs/upstream/eval-system.md +291 -0
- package/docs/upstream/session-review-oss-complements.md +75 -0
- package/docs/upstream/session-review.md +212 -0
- package/docs/upstream/skills.md +188 -0
- package/docs/upstream/team-structure.md +21 -0
- package/docs/upstream/telemetry-ci-access.md +129 -0
- package/docs/upstream/telemetry-repo-security.md +120 -0
- package/docs/upstream/test-evaluation.md +277 -0
- package/docs/upstream/test-improve.md +154 -0
- package/docs/upstream/triage-workflow.md +282 -0
- package/docs/upstream/workflows.md +289 -0
- package/extensions/dev-team/index.ts +539 -0
- package/extensions/dev-team/lib/agents.ts +272 -0
- package/extensions/dev-team/lib/ai-credits.ts +92 -0
- package/extensions/dev-team/lib/autocompact.ts +81 -0
- package/extensions/dev-team/lib/child-run.ts +102 -0
- package/extensions/dev-team/lib/config.ts +236 -0
- package/extensions/dev-team/lib/gh-command.ts +103 -0
- package/extensions/dev-team/lib/github-style.ts +307 -0
- package/extensions/dev-team/lib/hooks.ts +350 -0
- package/extensions/dev-team/lib/metrics.ts +115 -0
- package/extensions/dev-team/lib/safe-read.ts +49 -0
- package/extensions/dev-team/lib/session-files.ts +57 -0
- package/extensions/dev-team/lib/session-spend.ts +123 -0
- package/extensions/dev-team/lib/shell-scan.ts +205 -0
- package/extensions/dev-team/lib/skills.ts +213 -0
- package/extensions/dev-team/lib/subagent-render.ts +245 -0
- package/extensions/dev-team/lib/subagent-types.ts +164 -0
- package/extensions/dev-team/lib/subagent.ts +596 -0
- package/extensions/dev-team/lib/terminal-text.ts +54 -0
- package/extensions/dev-team/lib/tools-misc.ts +152 -0
- package/extensions/dev-team/lib/transcript.ts +110 -0
- package/extensions/dev-team/lib/trust.ts +52 -0
- package/extensions/dev-team/lib/usage-breakdown.ts +176 -0
- package/extensions/dev-team/lib/usage-chart.ts +153 -0
- package/extensions/dev-team/lib/usage-command.ts +107 -0
- package/extensions/dev-team/lib/usage-history.ts +203 -0
- package/extensions/dev-team/lib/usage-render.ts +225 -0
- package/extensions/dev-team/lib/usage-split-bar.ts +127 -0
- package/extensions/dev-team/lib/usage-state.ts +116 -0
- package/extensions/dev-team/lib/usage-text.ts +159 -0
- package/extensions/dev-team/lib/usage-view.ts +109 -0
- package/hooks/__pycache__/refactor_test_freeze_guard.cpython-314.pyc +0 -0
- package/hooks/agent_dispatch_ledger.py +190 -0
- package/hooks/autocompact_setup_nudge.py +99 -0
- package/hooks/bash_retry_guard.py +228 -0
- package/hooks/boundary_events_write_guard.py +352 -0
- package/hooks/code_intelligence_nudge.py +293 -0
- package/hooks/code_intelligence_turn_mark.py +317 -0
- package/hooks/codegraph_bootstrap.py +139 -0
- package/hooks/contract_version_guard.py +362 -0
- package/hooks/cost_meter.py +106 -0
- package/hooks/destructive-commands.json +62 -0
- package/hooks/destructive_guard.py +477 -0
- package/hooks/eval_compliance_check.py +440 -0
- package/hooks/guards.json +17 -0
- package/hooks/hooks.json +323 -0
- package/hooks/internal_double_gate.py +296 -0
- package/hooks/js_fp_review.py +212 -0
- package/hooks/knowledge_index.py +119 -0
- package/hooks/lib/__pycache__/artifact_paths.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/atomic_state.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/autocompact_config.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/boundary_events.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/doc_classification.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/gh_pr_create_detect.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/git_safe_diff.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/instrument_log.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/metrics_query.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/plugin_version.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/pre_commit_doc_classifier.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/review_agent_registry.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/review_gate_corroboration.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/review_gate_hash.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/review_verdicts.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/stdin_json.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/stryker_invocation.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/telemetry_consent.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/test_file_classify.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/token_efficiency_limits.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/verify_guard_state.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/xunit_v3_operator_gate.cpython-314.pyc +0 -0
- package/hooks/lib/agent_skill_hints.py +74 -0
- package/hooks/lib/artifact_paths.py +263 -0
- package/hooks/lib/atomic_state.py +557 -0
- package/hooks/lib/autocompact_config.py +103 -0
- package/hooks/lib/autoship_log.py +106 -0
- package/hooks/lib/banned_scripts_policy.py +51 -0
- package/hooks/lib/boundary_events.py +436 -0
- package/hooks/lib/build_knowledge_index.py +504 -0
- package/hooks/lib/build_skills_index.py +361 -0
- package/hooks/lib/build_state.py +116 -0
- package/hooks/lib/classify_ship_outcome.py +126 -0
- package/hooks/lib/config_changelog_schema.py +115 -0
- package/hooks/lib/cost_meter.py +955 -0
- package/hooks/lib/doc_classification.py +116 -0
- package/hooks/lib/gh_pr_create_detect.py +136 -0
- package/hooks/lib/git_safe_diff.py +123 -0
- package/hooks/lib/instrument_log.py +66 -0
- package/hooks/lib/iteration_journal_gate.py +197 -0
- package/hooks/lib/knowledge_index_paths.py +88 -0
- package/hooks/lib/mcp_json_repowise.py +177 -0
- package/hooks/lib/metrics_query.py +202 -0
- package/hooks/lib/minimal_yaml.py +434 -0
- package/hooks/lib/plugin_version.py +142 -0
- package/hooks/lib/pre_commit_detect.py +537 -0
- package/hooks/lib/pre_commit_doc_classifier.py +126 -0
- package/hooks/lib/pricing.py +118 -0
- package/hooks/lib/report_pdf.py +371 -0
- package/hooks/lib/review_agent_registry.py +142 -0
- package/hooks/lib/review_dispatch_ledger.py +101 -0
- package/hooks/lib/review_gate_corroboration.py +521 -0
- package/hooks/lib/review_gate_hash.py +252 -0
- package/hooks/lib/review_gate_normalized_hash.py +1115 -0
- package/hooks/lib/review_verdicts.py +301 -0
- package/hooks/lib/run_report.py +160 -0
- package/hooks/lib/skill_categories.yaml +125 -0
- package/hooks/lib/stdin_json.py +57 -0
- package/hooks/lib/stryker_invocation.py +102 -0
- package/hooks/lib/telemetry_consent.py +41 -0
- package/hooks/lib/telemetry_report.py +108 -0
- package/hooks/lib/test_file_classify.py +160 -0
- package/hooks/lib/token_efficiency_limits.py +51 -0
- package/hooks/lib/turn_identity.py +77 -0
- package/hooks/lib/verify_guard_state.py +110 -0
- package/hooks/lib/workflow_state.py +206 -0
- package/hooks/lib/xunit_v3_operator_gate.py +596 -0
- package/hooks/mcp_json_repowise_nudge.py +74 -0
- package/hooks/mutation_adapters/__init__.py +7 -0
- package/hooks/mutation_adapters/__pycache__/__init__.cpython-314.pyc +0 -0
- package/hooks/mutation_adapters/__pycache__/lib.cpython-314.pyc +0 -0
- package/hooks/mutation_adapters/__pycache__/mutmut.cpython-314.pyc +0 -0
- package/hooks/mutation_adapters/__pycache__/pitest.cpython-314.pyc +0 -0
- package/hooks/mutation_adapters/__pycache__/stryker.cpython-314.pyc +0 -0
- package/hooks/mutation_adapters/__pycache__/stryker_net.cpython-314.pyc +0 -0
- package/hooks/mutation_adapters/lib.py +478 -0
- package/hooks/mutation_adapters/mutmut.py +188 -0
- package/hooks/mutation_adapters/pitest.py +266 -0
- package/hooks/mutation_adapters/stryker.py +157 -0
- package/hooks/mutation_adapters/stryker_net.py +264 -0
- package/hooks/mutation_gate.py +193 -0
- package/hooks/mutation_testing_smoke_gate.py +371 -0
- package/hooks/pending_review_notify.py +121 -0
- package/hooks/phase_marker.py +138 -0
- package/hooks/post_compact_state_reinject.py +180 -0
- package/hooks/post_format.py +115 -0
- package/hooks/pre_commit_knowledge_index.py +128 -0
- package/hooks/pre_commit_review.py +66 -0
- package/hooks/pre_pr_review.py +694 -0
- package/hooks/pre_tool_guard.py +405 -0
- package/hooks/py.sh +73 -0
- package/hooks/refactor-bash-write-patterns.json +29 -0
- package/hooks/refactor_test_bash_guard.py +253 -0
- package/hooks/refactor_test_freeze_guard.py +139 -0
- package/hooks/refactor_test_revert_guard.py +186 -0
- package/hooks/repo_review_nudge.py +287 -0
- package/hooks/review_verdict_recorder.py +464 -0
- package/hooks/scan_bash_command_for_banned_scripts.py +428 -0
- package/hooks/scan_worktree_for_banned_scripts.py +238 -0
- package/hooks/session_learning_trigger.py +248 -0
- package/hooks/skills_index.py +126 -0
- package/hooks/stryker_xunit_shim_guard.py +571 -0
- package/hooks/subagent_completion_guard.py +309 -0
- package/hooks/subagent_skill_context.py +139 -0
- package/hooks/task_completion_metrics.py +216 -0
- package/hooks/tdd_guard.py +229 -0
- package/hooks/telemetry.py +341 -0
- package/hooks/token_efficiency_review.py +194 -0
- package/hooks/verify_guard.py +183 -0
- package/hooks/verify_guard_edit_marker.py +73 -0
- package/hooks/version_check.py +173 -0
- package/knowledge/accepted-risks-schema.md +98 -0
- package/knowledge/adr-decision-criteria.md +64 -0
- package/knowledge/adversarial-review-protocol.md +139 -0
- package/knowledge/agent-registry.md +228 -0
- package/knowledge/agent-review-methodology.md +80 -0
- package/knowledge/ai-friendly-repo-guidelines.md +67 -0
- package/knowledge/architecture-assessment.md +96 -0
- package/knowledge/artifact-lifecycle.md +57 -0
- package/knowledge/cd-maturity-model.md +82 -0
- package/knowledge/cd-test-architecture.md +190 -0
- package/knowledge/ci-cd-file-scope.md +24 -0
- package/knowledge/codegraph-vs-graphify.md +192 -0
- package/knowledge/component-test-patterns.md +139 -0
- package/knowledge/database-change-management.md +80 -0
- package/knowledge/database-test-patterns.md +79 -0
- package/knowledge/decision-defaults.md +88 -0
- package/knowledge/dependency-breaking-techniques.md +116 -0
- package/knowledge/deployment-pipeline.md +86 -0
- package/knowledge/design-smells.md +122 -0
- package/knowledge/directory-enumeration.md +38 -0
- package/knowledge/domain-modeling.md +123 -0
- package/knowledge/evidence-bundle.md +90 -0
- package/knowledge/exploratory-testing-field-guide.md +122 -0
- package/knowledge/failure-routing.md +28 -0
- package/knowledge/fixture-construction.md +56 -0
- package/knowledge/frontend-component-architecture.md +139 -0
- package/knowledge/gherkin-quality-review-dispatch.md +135 -0
- package/knowledge/index.json +6766 -0
- package/knowledge/internal-collaborator-doubling.md +101 -0
- package/knowledge/legacy-test-strategy.md +71 -0
- package/knowledge/long-run-waiting.md +66 -0
- package/knowledge/microservice-testing.md +71 -0
- package/knowledge/model-pricing.json +23 -0
- package/knowledge/mutation-score-formulas.md +60 -0
- package/knowledge/object-calisthenics.md +147 -0
- package/knowledge/oracle-provenance.md +94 -0
- package/knowledge/orchestrator-script-implementation.md +185 -0
- package/knowledge/owasp-detection.md +148 -0
- package/knowledge/plan-review-rubric.md +56 -0
- package/knowledge/proxy-connectivity.md +62 -0
- package/knowledge/reactive-effect-patterns.md +73 -0
- package/knowledge/recon-inventory-excludes.txt +32 -0
- package/knowledge/references/bdd-value-guide.md +61 -0
- package/knowledge/references/csharp-http-client-testing.md +264 -0
- package/knowledge/release-strategies.md +74 -0
- package/knowledge/report-output-location.md +117 -0
- package/knowledge/report-pdf-integration.md +63 -0
- package/knowledge/report-print.css +129 -0
- package/knowledge/report-template.md +114 -0
- package/knowledge/report-to-pdf.md +69 -0
- package/knowledge/request-processing-flow.md +63 -0
- package/knowledge/result-verification.md +52 -0
- package/knowledge/review-agent-output-contract.md +121 -0
- package/knowledge/review-lens-classification.md +113 -0
- package/knowledge/review-rubric.md +62 -0
- package/knowledge/review-template.md +104 -0
- package/knowledge/rule-fixtures/A02.insecure-random-js/negative.js +1 -0
- package/knowledge/rule-fixtures/A02.insecure-random-js/positive.js +1 -0
- package/knowledge/rule-fixtures/A02.weak-hashing-md5/negative.py +1 -0
- package/knowledge/rule-fixtures/A02.weak-hashing-md5/positive.py +1 -0
- package/knowledge/rule-fixtures/A03.command-injection/negative.js +1 -0
- package/knowledge/rule-fixtures/A03.command-injection/positive.js +1 -0
- package/knowledge/rule-fixtures/A03.sql-injection/negative.js +1 -0
- package/knowledge/rule-fixtures/A03.sql-injection/positive.js +1 -0
- package/knowledge/rule-fixtures/A03.xss-innerhtml/negative.js +1 -0
- package/knowledge/rule-fixtures/A03.xss-innerhtml/positive.js +1 -0
- package/knowledge/rule-fixtures/A05.cors-wildcard/negative.js +1 -0
- package/knowledge/rule-fixtures/A05.cors-wildcard/positive.js +1 -0
- package/knowledge/rule-fixtures/A05.default-credentials/negative.js +1 -0
- package/knowledge/rule-fixtures/A05.default-credentials/positive.js +1 -0
- package/knowledge/rule-fixtures/A07.jwt-alg-none/negative.js +1 -0
- package/knowledge/rule-fixtures/A07.jwt-alg-none/positive.js +1 -0
- package/knowledge/rule-fixtures/A08.binary-formatter/negative.cs +1 -0
- package/knowledge/rule-fixtures/A08.binary-formatter/positive.cs +1 -0
- package/knowledge/rule-fixtures/A08.js-eval/negative.js +1 -0
- package/knowledge/rule-fixtures/A08.js-eval/positive.js +1 -0
- package/knowledge/rule-fixtures/A08.object-input-stream/negative.java +1 -0
- package/knowledge/rule-fixtures/A08.object-input-stream/positive.java +1 -0
- package/knowledge/schemas/disposition-register-v1.json +65 -0
- package/knowledge/schemas/recon-envelope-v1.json +198 -0
- package/knowledge/schemas/unified-finding-v1.json +72 -0
- package/knowledge/security-primitives-contract.md +301 -0
- package/knowledge/security-review-rule-map.yaml +107 -0
- package/knowledge/skills-registry.md +72 -0
- package/knowledge/task-size-classifier.md +103 -0
- package/knowledge/telemetry-schema.md +881 -0
- package/knowledge/test-automation-maturity.md +56 -0
- package/knowledge/test-automation-principles.md +71 -0
- package/knowledge/test-cadence-tradeoffs.md +68 -0
- package/knowledge/test-doubles.md +105 -0
- package/knowledge/test-file-indicators.md +22 -0
- package/knowledge/test-layer-gates.md +35 -0
- package/knowledge/test-matrix-examples/django-batch.md +24 -0
- package/knowledge/test-matrix-examples/dotnet-grpc-fronting-api.md +90 -0
- package/knowledge/test-matrix-examples/dotnet-http-consumer.md +131 -0
- package/knowledge/test-matrix-examples/react-node-spa.md +24 -0
- package/knowledge/test-matrix-examples/spring-boot-service.md +25 -0
- package/knowledge/test-matrix-examples/ssr-htmx.md +24 -0
- package/knowledge/test-organization.md +70 -0
- package/knowledge/test-pyramid.md +84 -0
- package/knowledge/test-refactoring.md +67 -0
- package/knowledge/test-review-division-of-labor.md +85 -0
- package/knowledge/test-smells.md +80 -0
- package/knowledge/test-stack-profiles/bdd-frameworks.md +235 -0
- package/knowledge/test-stack-profiles/django.md +13 -0
- package/knowledge/test-stack-profiles/dotnet.md +18 -0
- package/knowledge/test-stack-profiles/go.md +16 -0
- package/knowledge/test-stack-profiles/node.md +16 -0
- package/knowledge/test-stack-profiles/react.md +12 -0
- package/knowledge/test-stack-profiles/spring-boot.md +16 -0
- package/knowledge/test-stack-profiles/ssr-htmx.md +14 -0
- package/knowledge/test-stack-profiles/vue.md +12 -0
- package/knowledge/test-strategy.md +70 -0
- package/knowledge/testability-patterns.md +240 -0
- package/knowledge/testing-quadrants.md +44 -0
- package/knowledge/testing-techniques/approval.md +15 -0
- package/knowledge/testing-techniques/chaos.md +17 -0
- package/knowledge/testing-techniques/fuzz.md +15 -0
- package/knowledge/testing-techniques/property-based.md +15 -0
- package/knowledge/testing-techniques/schema-validation.md +15 -0
- package/knowledge/testing-techniques/screenshot.md +15 -0
- package/knowledge/three-phase-workflow.md +198 -0
- package/knowledge/value-patterns.md +55 -0
- package/knowledge/verification-mode.md +116 -0
- package/knowledge/virtual-service-libraries.md +75 -0
- package/knowledge/wave-consolidation-guidance.md +21 -0
- package/overrides/agents/Explore.md +15 -0
- package/overrides/agents/general-purpose.md +10 -0
- package/overrides/notes/autoship.md +6 -0
- package/overrides/notes/issues-from-assessment.md +3 -0
- package/overrides/notes/issues-from-plan.md +3 -0
- package/overrides/notes/mutation-night-watch.md +3 -0
- package/overrides/notes/mutation-testing.md +3 -0
- package/overrides/notes/pr.md +7 -0
- package/overrides/notes/project-init.md +6 -0
- package/overrides/notes/setup.md +13 -0
- package/overrides/notes/specs.md +3 -0
- package/overrides/skills/headless-run/SKILL.md +45 -0
- package/overrides/skills/upgrade/SKILL.md +30 -0
- package/overrides/skills/version/SKILL.md +25 -0
- package/package.json +36 -0
- package/scripts/authoring_digest.py +93 -0
- package/scripts/autoship_discover.py +121 -0
- package/scripts/autoship_group.py +409 -0
- package/scripts/autoship_proposals.py +494 -0
- package/scripts/autoship_queue.py +291 -0
- package/scripts/autoship_reclaim.py +495 -0
- package/scripts/build_jobs.py +108 -0
- package/scripts/build_rollback_point.py +240 -0
- package/scripts/build_slice_scope.py +157 -0
- package/scripts/build_wave.py +109 -0
- package/scripts/build_wave_reconcile.py +252 -0
- package/scripts/build_worktree_baseref.py +113 -0
- package/scripts/check_agent_scope.py +117 -0
- package/scripts/check_agent_tool_mapping.py +213 -0
- package/scripts/check_review_agent_mcp_tools.py +317 -0
- package/scripts/check_security_assessment_mcp_tools.py +165 -0
- package/scripts/checkpoint_abort.py +502 -0
- package/scripts/claude_setup_review.py +438 -0
- package/scripts/codebase_recon.py +556 -0
- package/scripts/coverage_config.py +623 -0
- package/scripts/coverage_delta_steering.py +330 -0
- package/scripts/coverage_discovery_dotnet.py +315 -0
- package/scripts/coverage_discovery_java.py +742 -0
- package/scripts/coverage_discovery_js.py +546 -0
- package/scripts/coverage_gap_ranking.py +556 -0
- package/scripts/coverage_readiness.py +455 -0
- package/scripts/coverage_report_parse.py +521 -0
- package/scripts/detect_bdd_convention.py +252 -0
- package/scripts/eval_ablation.py +376 -0
- package/scripts/gherkin_analysis_coverage_gate.py +306 -0
- package/scripts/gherkin_cross_feature_duplicate_titles_gate.py +173 -0
- package/scripts/gherkin_effectiveness_rollup.py +238 -0
- package/scripts/gherkin_failure_path_gate.py +206 -0
- package/scripts/gherkin_feature_merge.py +720 -0
- package/scripts/gherkin_stub_gate.py +163 -0
- package/scripts/gherkin_stub_merge.py +479 -0
- package/scripts/git_origin_host.py +88 -0
- package/scripts/install-java-static-analysis.py +110 -0
- package/scripts/issue_deps.py +74 -0
- package/scripts/lib/_bdd_markers.py +28 -0
- package/scripts/lib/_gherkin_text.py +93 -0
- package/scripts/lib/_vendored_tree.py +70 -0
- package/scripts/lib/autoship_state.py +397 -0
- package/scripts/lib/claude_md_guard.py +226 -0
- package/scripts/lib/deterministic_recon.py +446 -0
- package/scripts/lib/mcp_tool_grants.py +211 -0
- package/scripts/lib/plan_parse.py +386 -0
- package/scripts/lib/review_result.py +84 -0
- package/scripts/lib/review_roster.py +86 -0
- package/scripts/lib/session_log/__init__.py +34 -0
- package/scripts/lib/session_log/__pycache__/__init__.cpython-314.pyc +0 -0
- package/scripts/lib/session_log/__pycache__/records.cpython-314.pyc +0 -0
- package/scripts/lib/session_log/classify.py +231 -0
- package/scripts/lib/session_log/corrections.py +194 -0
- package/scripts/lib/session_log/discovery.py +108 -0
- package/scripts/lib/session_log/records.py +218 -0
- package/scripts/lib/session_log/redact.py +76 -0
- package/scripts/lib/session_log/signals.py +373 -0
- package/scripts/lib/session_report_downstream.py +614 -0
- package/scripts/lib/session_report_maintainer.py +1273 -0
- package/scripts/lib/session_report_shared.py +262 -0
- package/scripts/lib/settings_hook_guard.py +157 -0
- package/scripts/lib/slug.py +33 -0
- package/scripts/lib/stub_extractors/__init__.py +82 -0
- package/scripts/lib/stub_extractors/_common.py +328 -0
- package/scripts/lib/stub_extractors/csharp.py +19 -0
- package/scripts/lib/stub_extractors/go.py +173 -0
- package/scripts/lib/stub_extractors/java.py +18 -0
- package/scripts/lib/stub_extractors/jsts.py +126 -0
- package/scripts/mutation_stack_sections.py +149 -0
- package/scripts/mutation_yield_steering.py +345 -0
- package/scripts/orchestrator.py +895 -0
- package/scripts/plan_gherkin_export.py +227 -0
- package/scripts/plan_waves.py +208 -0
- package/scripts/pr_close_keyword_lint.py +108 -0
- package/scripts/progress_guardian.py +888 -0
- package/scripts/recon_inventory.py +273 -0
- package/scripts/review_findings_log.py +93 -0
- package/scripts/run_invariants.py +124 -0
- package/scripts/select_lenses.py +640 -0
- package/scripts/session_report.py +486 -0
- package/scripts/set_autocompact_env.py +221 -0
- package/scripts/ship_resume_guard.py +135 -0
- package/scripts/ship_review_gate.py +63 -0
- package/scripts/specs_convention_marker.py +103 -0
- package/scripts/test_improve_resume.py +277 -0
- package/scripts/test_review_mechanics.py +958 -0
- package/scripts/token_efficiency_review.py +322 -0
- package/scripts/verdict_scope.py +285 -0
- package/scripts/verify_gherkin_quality_critic_isolation.py +296 -0
- package/scripts/verify_tier.py +157 -0
- package/skills/adr-tools/SKILL.md +118 -0
- package/skills/agent-readiness/SKILL.md +105 -0
- package/skills/agent-readiness/ai_friendly_analyzers.py +326 -0
- package/skills/agent-readiness/scanner.py +441 -0
- package/skills/agent-readiness/scorecard.yaml +88 -0
- package/skills/api-design/SKILL.md +115 -0
- package/skills/apply-fixes/SKILL.md +171 -0
- package/skills/apply-test-doubles/SKILL.md +321 -0
- package/skills/artifact-lifecycle/SKILL.md +127 -0
- package/skills/autoship/SKILL.md +1124 -0
- package/skills/benchmark/SKILL.md +105 -0
- package/skills/branch-workflow/SKILL.md +89 -0
- package/skills/browse/SKILL.md +184 -0
- package/skills/browser-testing/SKILL.md +62 -0
- package/skills/browser-testing/references/playwright-patterns.md +216 -0
- package/skills/build/SKILL.md +422 -0
- package/skills/build/references/static-self-heal.md +245 -0
- package/skills/careful/SKILL.md +72 -0
- package/skills/cd-test-architecture/SKILL.md +371 -0
- package/skills/ci-debugging/SKILL.md +105 -0
- package/skills/co-evolution-audit/SKILL.md +269 -0
- package/skills/code-review/SKILL.md +1015 -0
- package/skills/code-review/examples/aggregated-sample.json +56 -0
- package/skills/code-review/examples/sample-report.md +41 -0
- package/skills/code-review/output-format.md +478 -0
- package/skills/code-review/scripts/activation.py +86 -0
- package/skills/code-review/scripts/change_impact.py +357 -0
- package/skills/code-review/scripts/change_shape.py +372 -0
- package/skills/code-review/scripts/change_size.py +212 -0
- package/skills/code-review/scripts/changed_file_list.py +141 -0
- package/skills/code-review/scripts/closing_pass.py +187 -0
- package/skills/code-review/scripts/consolidate.py +277 -0
- package/skills/code-review/scripts/contract_failure_report.py +185 -0
- package/skills/code-review/scripts/dispatch_reconcile.py +66 -0
- package/skills/code-review/scripts/dispatch_waves.py +164 -0
- package/skills/code-review/scripts/finding_signature.py +446 -0
- package/skills/code-review/scripts/ledger.py +283 -0
- package/skills/code-review/scripts/partition.py +169 -0
- package/skills/code-review/scripts/render_tiered_findings.py +274 -0
- package/skills/code-review/scripts/repo_invariants.py +1066 -0
- package/skills/code-review/scripts/review_context_pack.py +306 -0
- package/skills/code-review/scripts/review_round_log.py +345 -0
- package/skills/code-review/scripts/review_value_coverage.py +297 -0
- package/skills/code-review/scripts/validate_review_output.py +467 -0
- package/skills/code-review/sliced-mode.md +205 -0
- package/skills/competitive-analysis/SKILL.md +191 -0
- package/skills/context-loading-protocol/SKILL.md +157 -0
- package/skills/continue/SKILL.md +90 -0
- package/skills/cost-report/SKILL.md +178 -0
- package/skills/coverage-baseline/SKILL.md +335 -0
- package/skills/coverage-baseline/references/multi-project-discovery.md +202 -0
- package/skills/coverage-delta/SKILL.md +181 -0
- package/skills/coverage-delta/references/mutation-gate.md +70 -0
- package/skills/design-doc/SKILL.md +95 -0
- package/skills/design-interrogation/SKILL.md +89 -0
- package/skills/design-it-twice/SKILL.md +91 -0
- package/skills/docker-image-audit/SKILL.md +108 -0
- package/skills/docker-image-audit/references/install-guide.md +64 -0
- package/skills/docker-image-audit/references/report-template.md +73 -0
- package/skills/docker-image-create/SKILL.md +185 -0
- package/skills/domain-analysis/SKILL.md +183 -0
- package/skills/domain-driven-design/SKILL.md +194 -0
- package/skills/exploratory-testing/SKILL.md +108 -0
- package/skills/explore/SKILL.md +51 -0
- package/skills/farley-score/SKILL.md +165 -0
- package/skills/feature-file-validation/SKILL.md +78 -0
- package/skills/feature-file-validation/references/validation-rules.md +115 -0
- package/skills/feedback-learning/SKILL.md +414 -0
- package/skills/fix/SKILL.md +450 -0
- package/skills/freeze/SKILL.md +68 -0
- package/skills/frontend-architecture/SKILL.md +113 -0
- package/skills/gherkin-derive/SKILL.md +630 -0
- package/skills/gherkin-public/SKILL.md +266 -0
- package/skills/governance-compliance/SKILL.md +150 -0
- package/skills/guard/SKILL.md +75 -0
- package/skills/handoff/SKILL.md +139 -0
- package/skills/handoff/references/summary-templates.md +242 -0
- package/skills/harness-audit/SKILL.md +751 -0
- package/skills/harness-audit/scripts/lesson_validate.py +386 -0
- package/skills/harness-audit/scripts/redundancy_criterion.py +188 -0
- package/skills/headless-run/SKILL.md +45 -0
- package/skills/headless-run/scripts/isolated_dispatch.py +381 -0
- package/skills/help/SKILL.md +72 -0
- package/skills/hexagonal-architecture/SKILL.md +85 -0
- package/skills/human-oversight-protocol/SKILL.md +224 -0
- package/skills/issues-from-assessment/SKILL.md +223 -0
- package/skills/issues-from-plan/SKILL.md +133 -0
- package/skills/legacy-code/SKILL.md +132 -0
- package/skills/mermaid-diagramming/SKILL.md +120 -0
- package/skills/mutation-night-watch/SKILL.md +154 -0
- package/skills/mutation-night-watch/references/scheduling.md +135 -0
- package/skills/mutation-testing/SKILL.md +396 -0
- package/skills/mutation-testing/references/languages/csharp-stryker-net.md +676 -0
- package/skills/mutation-testing/references/languages/go-go-mutesting.md +95 -0
- package/skills/mutation-testing/references/languages/java-pitest.md +77 -0
- package/skills/mutation-testing/references/languages/javascript-stryker.md +188 -0
- package/skills/mutation-testing/references/languages/python-mutmut.md +97 -0
- package/skills/mutation-testing/references/time-estimation.md +34 -0
- package/skills/mutation-testing/references/tool-detection.md +15 -0
- package/skills/mutation-testing/references/workflow-callers.md +23 -0
- package/skills/mutation-testing/scripts/__pycache__/xunit_v3_feature_detector.cpython-314.pyc +0 -0
- package/skills/mutation-testing/scripts/csharp_stryker_net_slice_runner.py +635 -0
- package/skills/mutation-testing/scripts/csharp_stryker_net_status_loop.py +525 -0
- package/skills/mutation-testing/scripts/csharp_stryker_net_wrapper.py +681 -0
- package/skills/mutation-testing/scripts/mutation_baseline_reuse.py +292 -0
- package/skills/mutation-testing/scripts/mutation_exclude_policy.py +268 -0
- package/skills/mutation-testing/scripts/mutation_feasibility_gate.py +463 -0
- package/skills/mutation-testing/scripts/mutation_kill_headless.py +331 -0
- package/skills/mutation-testing/scripts/mutation_kill_insert.py +199 -0
- package/skills/mutation-testing/scripts/mutation_kill_insert_python.py +150 -0
- package/skills/mutation-testing/scripts/mutation_kill_loop.py +869 -0
- package/skills/mutation-testing/scripts/mutation_kill_loop_python.py +949 -0
- package/skills/mutation-testing/scripts/mutation_kill_retry.py +592 -0
- package/skills/mutation-testing/scripts/mutation_kill_shared.py +620 -0
- package/skills/mutation-testing/scripts/mutation_nightwatch.py +462 -0
- package/skills/mutation-testing/scripts/mutation_nightwatch_stacks.py +425 -0
- package/skills/mutation-testing/scripts/mutation_report.py +743 -0
- package/skills/mutation-testing/scripts/mutation_report_cli.py +175 -0
- package/skills/mutation-testing/scripts/mutation_safety_gate.py +69 -0
- package/skills/mutation-testing/scripts/stryker_shard_pipeline.py +847 -0
- package/skills/mutation-testing/scripts/stryker_shard_setup.py +440 -0
- package/skills/mutation-testing/scripts/stryker_timeout_retry.py +142 -0
- package/skills/mutation-testing/scripts/xunit_v3_feature_detector.py +341 -0
- package/skills/performance-benchmark/SKILL.md +174 -0
- package/skills/performance-benchmark/examples/report-format.md +43 -0
- package/skills/performance-benchmark/references/benchmark-script.md +169 -0
- package/skills/performance-metrics/SKILL.md +265 -0
- package/skills/plan/SKILL.md +199 -0
- package/skills/plan/references/gherkin-persistence.md +43 -0
- package/skills/plan/references/plan-template.md +182 -0
- package/skills/pr/SKILL.md +289 -0
- package/skills/pr/scripts/gate_retry_state.py +368 -0
- package/skills/project-init/README.md +141 -0
- package/skills/project-init/SKILL.md +1197 -0
- package/skills/project-init/evals/evals.json +200 -0
- package/skills/project-init/references/capability-tools.md +55 -0
- package/skills/project-init/references/configs.md +221 -0
- package/skills/property-based-testing/SKILL.md +121 -0
- package/skills/property-based-testing/fixtures/invariant_fixture.py +15 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/README.md +42 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/LICENSE +21 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/README.md +263 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/cjs/fast-check.d.ts +5165 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/cjs/fast-check.js +12147 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/cjs/package.json +3 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/cjs/types57/fast-check.d.ts +5165 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/fast-check.d.ts +5165 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/fast-check.js +12011 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/rolldown-runtime-D7D4PA-g.js +13 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/types57/fast-check.d.ts +5165 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/package.json +94 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/LICENSE +21 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/README.md +168 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/RandomGenerator-DcXj09Ch.d.ts +14 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformBigInt.d.ts +15 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformBigInt.js +38 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformFloat32.d.ts +15 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformFloat32.js +18 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformFloat64.d.ts +15 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformFloat64.js +22 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformInt.d.ts +15 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformInt.js +134 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/RandomGenerator-DcXj09Ch.d.ts +14 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformBigInt.d.ts +15 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformBigInt.js +37 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformFloat32.d.ts +15 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformFloat32.js +17 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformFloat64.d.ts +15 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformFloat64.js +21 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformInt.d.ts +15 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformInt.js +133 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/congruential32.d.ts +7 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/congruential32.js +44 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/mersenne.d.ts +7 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/mersenne.js +90 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/xoroshiro128plus.d.ts +7 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/xoroshiro128plus.js +80 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/xorshift128plus.d.ts +7 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/xorshift128plus.js +78 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/package.json +3 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/types/JumpableRandomGenerator.d.ts +16 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/types/JumpableRandomGenerator.js +0 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/types/RandomGenerator.d.ts +2 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/types/RandomGenerator.js +0 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/utils/generateN.d.ts +6 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/utils/generateN.js +8 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/utils/purify.d.ts +12 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/utils/purify.js +9 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/utils/skipN.d.ts +6 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/utils/skipN.js +6 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/congruential32.d.ts +7 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/congruential32.js +46 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/mersenne.d.ts +7 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/mersenne.js +92 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/xoroshiro128plus.d.ts +7 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/xoroshiro128plus.js +82 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/xorshift128plus.d.ts +7 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/xorshift128plus.js +80 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/types/JumpableRandomGenerator.d.ts +16 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/types/JumpableRandomGenerator.js +0 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/types/RandomGenerator.d.ts +2 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/types/RandomGenerator.js +0 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/utils/generateN.d.ts +6 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/utils/generateN.js +9 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/utils/purify.d.ts +12 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/utils/purify.js +10 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/utils/skipN.d.ts +6 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/utils/skipN.js +7 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/package.json +133 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/package-lock.json +1179 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/package.json +14 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/roundtrip.js +29 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/roundtrip.properties.test.js +16 -0
- package/skills/property-based-testing/fixtures/no_property_fixture.py +10 -0
- package/skills/property-based-testing/fixtures/roundtrip_fixture.py +16 -0
- package/skills/property-based-testing/references/languages/javascript.md +54 -0
- package/skills/property-based-testing/scripts/detect_and_dispatch.py +80 -0
- package/skills/property-based-testing/scripts/hypothesis_scaffold.py +276 -0
- package/skills/proxy-resilience/SKILL.md +84 -0
- package/skills/quality-gate-pipeline/SKILL.md +184 -0
- package/skills/quality-targets-converge/SKILL.md +254 -0
- package/skills/repo-review/SKILL.md +159 -0
- package/skills/report-pdf/SKILL.md +66 -0
- package/skills/review/SKILL.md +47 -0
- package/skills/review-agent/SKILL.md +152 -0
- package/skills/review-summary/SKILL.md +73 -0
- package/skills/run-report/SKILL.md +70 -0
- package/skills/semantic-duplication-scan/SKILL.md +337 -0
- package/skills/semantic-scan/SKILL.md +53 -0
- package/skills/semgrep-analyze/SKILL.md +139 -0
- package/skills/setup/SKILL.md +1122 -0
- package/skills/ship/SKILL.md +240 -0
- package/skills/source-verification/SKILL.md +210 -0
- package/skills/source-verification/scripts/claim_extractor.py +155 -0
- package/skills/specs/.size-baseline.json +4 -0
- package/skills/specs/SKILL.md +243 -0
- package/skills/specs/references/completeness-checklist.md +83 -0
- package/skills/specs/references/extraction.md +58 -0
- package/skills/specs/references/glossary.md +59 -0
- package/skills/specs/references/persistence.md +115 -0
- package/skills/specs/references/predictability-check.md +77 -0
- package/skills/static-analysis-integration/SKILL.md +235 -0
- package/skills/static-analysis-integration/adapters/_envelope.py +26 -0
- package/skills/static-analysis-integration/adapters/jscpd-adapter.py +66 -0
- package/skills/static-analysis-integration/adapters/lizard-adapter.py +81 -0
- package/skills/static-analysis-integration/adapters/mypy-adapter.py +50 -0
- package/skills/static-analysis-integration/adapters/mypy-src-layout.py +93 -0
- package/skills/static-analysis-integration/adapters/security-review-adapter.py +212 -0
- package/skills/static-analysis-integration/maintenance.md +23 -0
- package/skills/static-analysis-integration/references/language-setup.md +228 -0
- package/skills/static-analysis-integration/references/sarif-parser.md +124 -0
- package/skills/static-analysis-integration/references/security-review-adapter.md +118 -0
- package/skills/static-analysis-integration/references/tool-configs.md +617 -0
- package/skills/static-analysis-integration/rulesets/pmd-quickstart.xml +24 -0
- package/skills/stryker-xunit-v2-shim/SKILL.md +274 -0
- package/skills/stryker-xunit-v2-shim/references/shim-howto.md +256 -0
- package/skills/stryker-xunit-v2-shim/scripts/generate_shim.py +143 -0
- package/skills/systematic-debugging/SKILL.md +130 -0
- package/skills/telemetry/SKILL.md +75 -0
- package/skills/test-audit-disable/SKILL.md +129 -0
- package/skills/test-design/SKILL.md +177 -0
- package/skills/test-design/scripts/__pycache__/internal_double_detector.cpython-314.pyc +0 -0
- package/skills/test-design/scripts/internal_double_detector.py +631 -0
- package/skills/test-design-advisor/SKILL.md +166 -0
- package/skills/test-driven-development/SKILL.md +169 -0
- package/skills/test-health/SKILL.md +262 -0
- package/skills/test-improve/SKILL.md +239 -0
- package/skills/test-improve/references/phase-0-approach-contract.md +228 -0
- package/skills/test-improve/references/phase-1-analyze.md +131 -0
- package/skills/test-improve/references/phase-2-baseline.md +121 -0
- package/skills/test-improve/references/phase-3-derive-gherkin.md +53 -0
- package/skills/test-improve/references/phase-4-plan-fixes.md +34 -0
- package/skills/test-improve/references/phase-5-improve.md +215 -0
- package/skills/test-improve/references/phase-6-refactor-decision.md +45 -0
- package/skills/test-improve/references/phase-7-refactor.md +44 -0
- package/skills/test-improve/references/phase-8-validate.md +66 -0
- package/skills/test-improve/references/phase-9-close-out-prompt.md +11 -0
- package/skills/test-improve/references/phase-9-report.md +62 -0
- package/skills/test-improve/references/review-loop.md +92 -0
- package/skills/test-improve/templates/executive-summary.md +123 -0
- package/skills/threat-modeling/SKILL.md +108 -0
- package/skills/triage/SKILL.md +211 -0
- package/skills/ubiquitous-language/SKILL.md +192 -0
- package/skills/ubiquitous-language/scripts/collect_domain_signals.py +300 -0
- package/skills/unfreeze/SKILL.md +37 -0
- package/skills/upgrade/SKILL.md +31 -0
- package/skills/upgrade/scripts/check_version_drift.py +113 -0
- package/skills/upgrade/scripts/enable_autoupdate.py +149 -0
- package/skills/version/SKILL.md +25 -0
- package/sync/__pycache__/sync_upstream.cpython-314.pyc +0 -0
- package/sync/sync_upstream.py +293 -0
- package/templates/ACCEPTED-RISKS.md.tmpl +46 -0
- package/templates/agents/agent-template.md +151 -0
- package/templates/agents/angular-testing.md +66 -0
- package/templates/agents/csharp-quality.md +63 -0
- package/templates/agents/esm-enforcer.md +52 -0
- package/templates/agents/front-end-testing.md +65 -0
- package/templates/agents/go-quality.md +65 -0
- package/templates/agents/python-quality.md +62 -0
- package/templates/agents/react-testing.md +61 -0
- package/templates/agents/ts-enforcer.md +60 -0
- package/templates/agents/twelve-factor-audit.md +49 -0
- package/tools/entropy-check.py +250 -0
- package/tools/model-hash-verify.py +213 -0
|
@@ -0,0 +1,949 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""mutation_kill_loop_python.py — deterministic survivor-kill loop for
|
|
3
|
+
Python/mutmut, the Python counterpart to ``mutation_kill_loop.py`` (#1357).
|
|
4
|
+
|
|
5
|
+
mutmut has no project/solution structure to load from a config file the way
|
|
6
|
+
Stryker.NET does — a scoped run only needs the source file and a test
|
|
7
|
+
command, so there is no config-file abstraction here (unlike
|
|
8
|
+
``mutation_kill_loop.py``'s ``LoopConfig``/``stryker-config.json``). Scoring
|
|
9
|
+
and survivor extraction reuse ``mutation_report``'s mutmut-junitxml support
|
|
10
|
+
(#1357).
|
|
11
|
+
|
|
12
|
+
**Generation is a seam, not a mechanism** (same contract as the C# loop):
|
|
13
|
+
the loop never decides *what* tests to write — a caller supplies a
|
|
14
|
+
``generate`` callable that returns the new pytest function text. The
|
|
15
|
+
default (interactive) path is agent-driven: the ``mutation-kill`` agent
|
|
16
|
+
calls :func:`run_for_file` directly, passing a ``generate`` hook backed by a
|
|
17
|
+
live agent turn. A ``--headless`` CLI mode shells to ``claude --print`` for
|
|
18
|
+
unattended (CI) runs.
|
|
19
|
+
|
|
20
|
+
**Scope (#1583).** This module owns the scoped ``mutmut run``,
|
|
21
|
+
verify/commit/revert, and ``run_for_file`` orchestration — mirroring
|
|
22
|
+
``mutation_kill_loop.py``'s post-#1562 scope exactly. Insertion mechanics
|
|
23
|
+
(detect-or-refuse end-of-file test-function appending) live in
|
|
24
|
+
``mutation_kill_insert_python.py``, a stdlib-only leaf this module imports
|
|
25
|
+
from — never the reverse — mirroring the C# split
|
|
26
|
+
(``mutation_kill_insert.py``). Headless generation's shared ``claude --print``
|
|
27
|
+
invocation glue and the generic (non-language-specific) helpers
|
|
28
|
+
(``strip_code_fences``, ``resolve_model``, ``claude_cli_available``,
|
|
29
|
+
``CLAUDE_CLI``, ``run_claude_headless``) live in ``mutation_kill_shared.py``
|
|
30
|
+
(#1601) and are reused, not duplicated, here — only the Python-flavored
|
|
31
|
+
prompt (``build_generation_prompt``/``build_survivor_summary``) and the
|
|
32
|
+
``--headless`` CLI argument parsing for THIS loop stay local, since mutmut's
|
|
33
|
+
CLI args (``--test-command``, no ``--config``/``--stryker-bin``) genuinely
|
|
34
|
+
differ from the C# loop's.
|
|
35
|
+
|
|
36
|
+
**Shared mechanics (#1583).** ``_timeout_from_env``, ``git_revert``,
|
|
37
|
+
``git_reset_and_revert``, ``git_commit``, and the "no improvement across
|
|
38
|
+
rounds" stop predicate are imported from ``mutation_kill_shared.py`` rather
|
|
39
|
+
than defined here — they were byte-for-byte duplicated with
|
|
40
|
+
``mutation_kill_loop.py`` before that module existed.
|
|
41
|
+
|
|
42
|
+
**Import boundary (#1601).** ``resolve_model``, ``claude_cli_available``,
|
|
43
|
+
``CLAUDE_CLI``, and ``run_claude_headless`` are imported directly from
|
|
44
|
+
``mutation_kill_shared`` — never from ``mutation_kill_headless``, which is
|
|
45
|
+
the C#/Stryker.NET CLI module (it imports ``mutation_kill_loop`` at module
|
|
46
|
+
scope and owns the C#-only ``--config``/``--stryker-bin`` CLI surface).
|
|
47
|
+
(``strip_code_fences`` also moved to ``mutation_kill_shared`` but isn't
|
|
48
|
+
imported here directly — this module only reaches it indirectly, through
|
|
49
|
+
``run_claude_headless``'s own internal call.) Reaching into
|
|
50
|
+
``mutation_kill_headless`` for these language-neutral names used to
|
|
51
|
+
transitively pull the entire C# stack into this Python-only loop; importing
|
|
52
|
+
them from the neutral ``mutation_kill_shared`` module instead removes that
|
|
53
|
+
coupling entirely.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
from __future__ import annotations
|
|
57
|
+
|
|
58
|
+
import argparse
|
|
59
|
+
import contextlib
|
|
60
|
+
import shutil
|
|
61
|
+
import subprocess
|
|
62
|
+
import sys
|
|
63
|
+
import time
|
|
64
|
+
from collections.abc import Callable, Sequence
|
|
65
|
+
from dataclasses import dataclass
|
|
66
|
+
from pathlib import Path
|
|
67
|
+
|
|
68
|
+
# typing, not collections.abc: the `Generator` alias below is a real runtime
|
|
69
|
+
# expression, so `from __future__ import annotations` cannot defer it — it
|
|
70
|
+
# must be subscriptable at import time. collections.abc generics have been
|
|
71
|
+
# since 3.9, which the 3.10 floor (ADR 0031) clears.
|
|
72
|
+
import mutation_kill_shared
|
|
73
|
+
import mutation_report
|
|
74
|
+
import mutation_safety_gate
|
|
75
|
+
from mutation_kill_insert_python import apply_generated_tests, count_tests
|
|
76
|
+
from mutation_kill_retry import (
|
|
77
|
+
EXIT_GENERATION_EXHAUSTED,
|
|
78
|
+
EXIT_REVERT_FAILED,
|
|
79
|
+
DowngradeEvent,
|
|
80
|
+
GenerationExhausted,
|
|
81
|
+
make_downgrade_audit_hook,
|
|
82
|
+
make_retrying_headless_call,
|
|
83
|
+
)
|
|
84
|
+
from mutation_kill_shared import (
|
|
85
|
+
CLAUDE_CLI,
|
|
86
|
+
GIT_TIMEOUT_S, # noqa: F401 — re-exported for tests (loop.GIT_TIMEOUT_S), matching the C# sibling's export name
|
|
87
|
+
_timeout_from_env,
|
|
88
|
+
claude_cli_available,
|
|
89
|
+
git_commit,
|
|
90
|
+
git_reset_and_revert,
|
|
91
|
+
git_revert,
|
|
92
|
+
resolve_model,
|
|
93
|
+
run_claude_headless, # noqa: F401 — re-exported for tests (loop.run_claude_headless identity check); make_headless_generator now calls it indirectly via make_retrying_headless_call (#1908)
|
|
94
|
+
stop_reason,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
Generator = Callable[[str, list[dict], str, str], str]
|
|
98
|
+
|
|
99
|
+
# Mirrors mutation_kill_headless.NO_GENERATOR_MESSAGE — pinned so a contract test
|
|
100
|
+
# can assert it verbatim.
|
|
101
|
+
NO_GENERATOR_MESSAGE = (
|
|
102
|
+
"no test generator available — invoke via the mutation-kill agent "
|
|
103
|
+
"or pass --headless"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
MISSING_CLAUDE_MESSAGE = (
|
|
107
|
+
f"--headless requires the Claude CLI but '{CLAUDE_CLI}' is not available. "
|
|
108
|
+
"Install Claude Code (`npm install -g @anthropic-ai/claude-code`) and "
|
|
109
|
+
"authenticate it (run `claude` once to log in, or set ANTHROPIC_API_KEY) — "
|
|
110
|
+
"or set CLAUDE_BIN to the CLI's path."
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# =============================================================================
|
|
115
|
+
# Scoped mutmut run — mutmut has no native JSON report; junitxml is it.
|
|
116
|
+
# =============================================================================
|
|
117
|
+
def _mutmut_argv() -> list[str]:
|
|
118
|
+
"""Return the argv prefix for invoking mutmut — `mutmut` or `python3 -m mutmut`."""
|
|
119
|
+
if shutil.which("mutmut") is not None:
|
|
120
|
+
return ["mutmut"]
|
|
121
|
+
return [sys.executable, "-m", "mutmut"]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# Bounds how long a caller waits to acquire the `.mutmut-cache` lock before
|
|
125
|
+
# giving up loudly rather than hanging forever behind a stuck/crashed holder.
|
|
126
|
+
# An override is legitimate for a large repo whose mutmut-cache lock is held
|
|
127
|
+
# longer than 300s by a slow, in-flight concurrent run.
|
|
128
|
+
_MUTMUT_CACHE_LOCK_TIMEOUT_S = _timeout_from_env(
|
|
129
|
+
"DEV_TEAM_MUTATION_MUTMUT_LOCK_TIMEOUT_S", 300
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# How often _acquire_mutmut_cache_lock re-checks the lock directory while
|
|
133
|
+
# waiting. Small enough to notice a release promptly; large enough not to
|
|
134
|
+
# busy-loop.
|
|
135
|
+
_MUTMUT_CACHE_LOCK_POLL_INTERVAL_S = 0.1
|
|
136
|
+
|
|
137
|
+
# Timeouts for the mutmut subprocesses themselves (#1605) — previously
|
|
138
|
+
# unbounded, unlike the C# loop's DOTNET_BUILD_TIMEOUT_S/DOTNET_TEST_TIMEOUT_S
|
|
139
|
+
# equivalents. The scoped `mutmut run` mutates and re-tests every mutant for
|
|
140
|
+
# one file, so its budget mirrors STRYKER_RUN_TIMEOUT_S's order of magnitude;
|
|
141
|
+
# `mutmut junitxml` only reformats already-computed results, so it gets a
|
|
142
|
+
# short budget instead.
|
|
143
|
+
_MUTMUT_RUN_TIMEOUT_S = _timeout_from_env("DEV_TEAM_MUTATION_MUTMUT_TIMEOUT_S", 3600)
|
|
144
|
+
_MUTMUT_JUNITXML_TIMEOUT_S = _timeout_from_env(
|
|
145
|
+
"DEV_TEAM_MUTATION_MUTMUT_JUNITXML_TIMEOUT_S", 60
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _acquire_mutmut_cache_lock(root: Path, *, timeout: float = _MUTMUT_CACHE_LOCK_TIMEOUT_S) -> Path:
|
|
150
|
+
"""Acquire a simple, cross-platform mutex directory guarding
|
|
151
|
+
``.mutmut-cache`` for the duration of one scoped mutmut run (#1584).
|
|
152
|
+
|
|
153
|
+
Two concurrent ``run_scoped_mutmut`` invocations sharing the same repo
|
|
154
|
+
race on the single, fixed ``.mutmut-cache`` path — one run's cache
|
|
155
|
+
delete/mutmut-run/revert sequence can corrupt or invalidate another's
|
|
156
|
+
in-flight run. ``Path.mkdir()`` is atomic on both POSIX and Windows,
|
|
157
|
+
unlike ``fcntl``/``msvcrt`` file locks (only one of which is available on
|
|
158
|
+
any given platform), so a lock *directory* — created, then removed on
|
|
159
|
+
release — is the portable, stdlib-only mutex.
|
|
160
|
+
"""
|
|
161
|
+
lock_dir = root / ".mutmut-cache.lock"
|
|
162
|
+
deadline = time.monotonic() + timeout
|
|
163
|
+
while True:
|
|
164
|
+
try:
|
|
165
|
+
lock_dir.mkdir()
|
|
166
|
+
return lock_dir
|
|
167
|
+
except FileExistsError:
|
|
168
|
+
if time.monotonic() >= deadline:
|
|
169
|
+
raise RuntimeError(
|
|
170
|
+
f"timed out after {timeout}s waiting for the .mutmut-cache "
|
|
171
|
+
f"lock at {lock_dir} — a concurrent run may be stuck "
|
|
172
|
+
"holding it (remove the directory manually to recover if "
|
|
173
|
+
"no run is actually in flight)"
|
|
174
|
+
) from None
|
|
175
|
+
time.sleep(_MUTMUT_CACHE_LOCK_POLL_INTERVAL_S)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _release_mutmut_cache_lock(lock_dir: Path) -> None:
|
|
179
|
+
"""Release the lock directory acquired by :func:`_acquire_mutmut_cache_lock`.
|
|
180
|
+
|
|
181
|
+
Called from a ``finally`` block — swallows ``OSError``/``FileNotFoundError``
|
|
182
|
+
(e.g. the directory was already removed) so a release-time failure never
|
|
183
|
+
masks whatever exception the run itself was raising (#1598/#1584 review).
|
|
184
|
+
"""
|
|
185
|
+
with contextlib.suppress(OSError, FileNotFoundError):
|
|
186
|
+
lock_dir.rmdir()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _revert_file_for_cleanup(path: Path, *, cwd: Path | None) -> bool:
|
|
190
|
+
"""Revert ``path`` for :func:`run_scoped_mutmut`'s cleanup ``finally``.
|
|
191
|
+
|
|
192
|
+
``git_revert`` only converts ``subprocess.TimeoutExpired`` to False; any
|
|
193
|
+
other unexpected exception (e.g. ``FileNotFoundError`` if git isn't on
|
|
194
|
+
PATH) would otherwise propagate straight out of the caller's ``finally``
|
|
195
|
+
block, skipping the second revert attempt entirely. Treat it the same as
|
|
196
|
+
a False return so both reverts are genuinely attempted unconditionally.
|
|
197
|
+
"""
|
|
198
|
+
try:
|
|
199
|
+
return git_revert(path, cwd=cwd)
|
|
200
|
+
except OSError:
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def run_scoped_mutmut(
|
|
205
|
+
source_file: str,
|
|
206
|
+
*,
|
|
207
|
+
test_command: str,
|
|
208
|
+
test_file: Path | None = None,
|
|
209
|
+
cwd: Path | None = None,
|
|
210
|
+
) -> str:
|
|
211
|
+
"""Run mutmut scoped to one file; return the ``mutmut junitxml`` output.
|
|
212
|
+
|
|
213
|
+
Clears any stale ``.mutmut-cache`` first — a cache from a *different*
|
|
214
|
+
scope (a prior run against another file, or a stale run from before this
|
|
215
|
+
file changed) is silently reused otherwise, which was a real trap hit
|
|
216
|
+
manually while dogfooding this loop by hand (#1354): every run must see
|
|
217
|
+
its own fresh baseline, not a leftover one.
|
|
218
|
+
|
|
219
|
+
**Always reverts ``source_file`` (and ``test_file``, when given) in a
|
|
220
|
+
``finally``.** mutmut mutates the real source file on disk for the
|
|
221
|
+
duration of each mutant's test run and restores it when that mutant
|
|
222
|
+
finishes — but an internal mutmut crash (a real, reproducible one: mutmut
|
|
223
|
+
2.5.1's own cache layer raises ``AssertionError``/``ValueError`` on some
|
|
224
|
+
files, confirmed while dogfooding this exact function against
|
|
225
|
+
``hooks/mutation_adapters/mutmut.py`` — see #1357) skips that restore
|
|
226
|
+
and leaves the mutated content on disk. Unlike Stryker.NET (which
|
|
227
|
+
instruments a separate build, never the real file), mutmut's crash
|
|
228
|
+
failure mode is "corrupt the file under test," so every scoped run must
|
|
229
|
+
unconditionally `git checkout --` it afterward — succeeding, failing, or
|
|
230
|
+
raising.
|
|
231
|
+
|
|
232
|
+
The **test file** the ``--runner`` command exercises is exposed to the
|
|
233
|
+
same failure mode — mutmut 2.5.1 has also been observed to truncate the
|
|
234
|
+
runner's test file to empty via a crashed ``.bak``-restore (#1359),
|
|
235
|
+
which silently breaks the *next* round's baseline (mutmut then reports
|
|
236
|
+
zero mutants — a false "converged" positive, not real coverage). Passing
|
|
237
|
+
``test_file`` reverts it alongside ``source_file`` in the same
|
|
238
|
+
``finally``; each round's ``git checkout --`` restores exactly the
|
|
239
|
+
state committed at the end of the previous round, which is always the
|
|
240
|
+
correct baseline for the round about to run.
|
|
241
|
+
|
|
242
|
+
**Lock-guarded end to end** (#1584): the cache delete, the mutmut run
|
|
243
|
+
itself, and the revert are all held under ``.mutmut-cache.lock`` — not
|
|
244
|
+
just the delete — because mutmut's cache is shared, fixed-path state for
|
|
245
|
+
the whole repo; a second concurrent invocation reading/writing it
|
|
246
|
+
mid-run is exactly as corrupting as racing the delete alone.
|
|
247
|
+
|
|
248
|
+
Raises :class:`mutation_kill_shared.RevertFailed` when either cleanup
|
|
249
|
+
revert fails. Both cleanup reverts are attempted first regardless of
|
|
250
|
+
which one fails; the raised message names every file that failed to
|
|
251
|
+
revert.
|
|
252
|
+
"""
|
|
253
|
+
root = cwd or Path(".")
|
|
254
|
+
lock_dir = _acquire_mutmut_cache_lock(root)
|
|
255
|
+
try:
|
|
256
|
+
(root / ".mutmut-cache").unlink(missing_ok=True)
|
|
257
|
+
|
|
258
|
+
prefix = _mutmut_argv()
|
|
259
|
+
argv = [
|
|
260
|
+
*prefix,
|
|
261
|
+
"run",
|
|
262
|
+
f"--paths-to-mutate={source_file}",
|
|
263
|
+
"--runner",
|
|
264
|
+
test_command,
|
|
265
|
+
"--no-progress",
|
|
266
|
+
"--simple-output",
|
|
267
|
+
]
|
|
268
|
+
try:
|
|
269
|
+
try:
|
|
270
|
+
subprocess.run(
|
|
271
|
+
argv,
|
|
272
|
+
cwd=cwd,
|
|
273
|
+
capture_output=True,
|
|
274
|
+
text=True,
|
|
275
|
+
check=False,
|
|
276
|
+
timeout=_MUTMUT_RUN_TIMEOUT_S,
|
|
277
|
+
)
|
|
278
|
+
except (FileNotFoundError, OSError) as exc:
|
|
279
|
+
raise RuntimeError(f"mutmut run failed to start: {exc}") from exc
|
|
280
|
+
except subprocess.TimeoutExpired as exc:
|
|
281
|
+
raise RuntimeError(
|
|
282
|
+
f"mutmut run timed out after {_MUTMUT_RUN_TIMEOUT_S}s for "
|
|
283
|
+
f"{source_file} (set DEV_TEAM_MUTATION_MUTMUT_TIMEOUT_S to "
|
|
284
|
+
"raise it)"
|
|
285
|
+
) from exc
|
|
286
|
+
|
|
287
|
+
try:
|
|
288
|
+
junit = subprocess.run(
|
|
289
|
+
[*prefix, "junitxml"],
|
|
290
|
+
cwd=cwd,
|
|
291
|
+
capture_output=True,
|
|
292
|
+
text=True,
|
|
293
|
+
check=False,
|
|
294
|
+
timeout=_MUTMUT_JUNITXML_TIMEOUT_S,
|
|
295
|
+
)
|
|
296
|
+
except subprocess.TimeoutExpired as exc:
|
|
297
|
+
raise RuntimeError(
|
|
298
|
+
"mutmut junitxml extraction timed out after "
|
|
299
|
+
f"{_MUTMUT_JUNITXML_TIMEOUT_S}s (set "
|
|
300
|
+
"DEV_TEAM_MUTATION_MUTMUT_JUNITXML_TIMEOUT_S to raise it)"
|
|
301
|
+
) from exc
|
|
302
|
+
return junit.stdout or ""
|
|
303
|
+
finally:
|
|
304
|
+
source_reverted = _revert_file_for_cleanup(Path(source_file), cwd=cwd)
|
|
305
|
+
test_reverted = True
|
|
306
|
+
if test_file is not None:
|
|
307
|
+
test_reverted = _revert_file_for_cleanup(test_file, cwd=cwd)
|
|
308
|
+
failed = [
|
|
309
|
+
str(p)
|
|
310
|
+
for p, ok in (
|
|
311
|
+
(source_file, source_reverted),
|
|
312
|
+
(test_file, test_reverted),
|
|
313
|
+
)
|
|
314
|
+
if p is not None and not ok
|
|
315
|
+
]
|
|
316
|
+
if failed:
|
|
317
|
+
raise mutation_kill_shared.RevertFailed(
|
|
318
|
+
"cleanup revert failed for "
|
|
319
|
+
f"{', '.join(failed)} after run_scoped_mutmut — the "
|
|
320
|
+
"working tree is left in an unknown state (mutated "
|
|
321
|
+
"content may still be on disk, uncommitted)"
|
|
322
|
+
)
|
|
323
|
+
finally:
|
|
324
|
+
_release_mutmut_cache_lock(lock_dir)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def extract_survivors(junitxml_text: str, source_file: str) -> list[dict]:
|
|
328
|
+
"""Return the surviving mutants for one source file (flattened).
|
|
329
|
+
|
|
330
|
+
Delegates parsing to :func:`mutation_report.survivors_from_mutmut_junitxml`
|
|
331
|
+
— mutmut names no per-mutation operator, so every survivor's
|
|
332
|
+
``mutatorName`` is the fixed literal ``"mutmut"`` (a single group).
|
|
333
|
+
"""
|
|
334
|
+
grouped = mutation_report.survivors_from_mutmut_junitxml(
|
|
335
|
+
junitxml_text, source_file
|
|
336
|
+
)
|
|
337
|
+
return [mutant for mutants in grouped.values() for mutant in mutants]
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
# =============================================================================
|
|
341
|
+
# Verify — python_compiles/run_scoped_pytest go through subprocess here;
|
|
342
|
+
# git_revert/git_reset_and_revert/git_commit are imported from
|
|
343
|
+
# mutation_kill_shared.py (#1583) rather than defined in this section.
|
|
344
|
+
# =============================================================================
|
|
345
|
+
# Timeouts for the compile-check and scoped-test subprocesses (#1605) —
|
|
346
|
+
# previously unbounded, unlike the C# loop's DOTNET_BUILD_TIMEOUT_S/
|
|
347
|
+
# DOTNET_TEST_TIMEOUT_S equivalents.
|
|
348
|
+
_PYTHON_COMPILE_TIMEOUT_S = _timeout_from_env(
|
|
349
|
+
"DEV_TEAM_MUTATION_PYTHON_COMPILE_TIMEOUT_S", 600
|
|
350
|
+
)
|
|
351
|
+
_PYTEST_TIMEOUT_S = _timeout_from_env("DEV_TEAM_MUTATION_PYTEST_TIMEOUT_S", 600)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _neutralize_leading_dash(path: Path) -> str:
|
|
355
|
+
"""Return ``str(path)`` guarded against being parsed as a CLI flag
|
|
356
|
+
instead of a positional filename (#1607) — a ``test_file`` value like
|
|
357
|
+
``-p`` would otherwise let pytest interpret it as ``-p <plugin>`` rather
|
|
358
|
+
than a (nonexistent) file. A ``--`` end-of-options marker is the usual
|
|
359
|
+
fix (and is what ``py_compile``'s own ``argparse``-based CLI honors),
|
|
360
|
+
but pytest's own argument parser does NOT treat ``--`` as ending option
|
|
361
|
+
parsing — a flag placed after it is still parsed, not treated as a bare
|
|
362
|
+
positional — so prefixing a relative, dash-leading path with ``./``
|
|
363
|
+
instead, which works regardless of a tool's own ``--`` support.
|
|
364
|
+
"""
|
|
365
|
+
text = str(path)
|
|
366
|
+
return f"./{text}" if text.startswith("-") else text
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def python_compiles(test_file: Path, *, cwd: Path | None = None) -> bool:
|
|
370
|
+
"""Syntax-check the test file — Python's equivalent of a build step.
|
|
371
|
+
|
|
372
|
+
False (not raised) on a timeout, matching this function's existing
|
|
373
|
+
"non-zero returncode -> False" contract for a plain compile failure.
|
|
374
|
+
"""
|
|
375
|
+
try:
|
|
376
|
+
rc = subprocess.run(
|
|
377
|
+
[sys.executable, "-m", "py_compile", _neutralize_leading_dash(test_file)],
|
|
378
|
+
capture_output=True,
|
|
379
|
+
text=True,
|
|
380
|
+
cwd=cwd,
|
|
381
|
+
check=False,
|
|
382
|
+
timeout=_PYTHON_COMPILE_TIMEOUT_S,
|
|
383
|
+
).returncode
|
|
384
|
+
except subprocess.TimeoutExpired:
|
|
385
|
+
return False
|
|
386
|
+
return rc == 0
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def run_scoped_pytest(test_file: Path, *, cwd: Path | None = None) -> bool:
|
|
390
|
+
"""Run the test file under pytest. False on any non-zero exit or timeout."""
|
|
391
|
+
try:
|
|
392
|
+
rc = subprocess.run(
|
|
393
|
+
[sys.executable, "-m", "pytest", "-q", _neutralize_leading_dash(test_file)],
|
|
394
|
+
capture_output=True,
|
|
395
|
+
text=True,
|
|
396
|
+
cwd=cwd,
|
|
397
|
+
check=False,
|
|
398
|
+
timeout=_PYTEST_TIMEOUT_S,
|
|
399
|
+
).returncode
|
|
400
|
+
except subprocess.TimeoutExpired:
|
|
401
|
+
return False
|
|
402
|
+
return rc == 0
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def _commit_message(
|
|
406
|
+
round_num: int,
|
|
407
|
+
source_file: str,
|
|
408
|
+
survivors: int,
|
|
409
|
+
new_tests: str,
|
|
410
|
+
*,
|
|
411
|
+
generator_label: str | None = None,
|
|
412
|
+
label_override: str | None = None,
|
|
413
|
+
) -> str:
|
|
414
|
+
count = count_tests(new_tests)
|
|
415
|
+
# Whitespace-collapsed the same way append_generator_trailer sanitizes
|
|
416
|
+
# generator_label (#1607): source_file is caller-supplied, and a value
|
|
417
|
+
# containing a newline could otherwise forge an extra "Generator:"
|
|
418
|
+
# trailer line into the commit message.
|
|
419
|
+
safe_source_file = " ".join(str(source_file).split())
|
|
420
|
+
message = (
|
|
421
|
+
f"test(mutation): kill round {round_num} — {safe_source_file}\n\n"
|
|
422
|
+
f"{count} new test(s) targeting {survivors} surviving mutant(s)"
|
|
423
|
+
)
|
|
424
|
+
return mutation_safety_gate.append_generator_trailer(
|
|
425
|
+
message, generator_label, label_override=label_override
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
# =============================================================================
|
|
430
|
+
# Per-file loop — run → score → check → generate → insert → verify → commit.
|
|
431
|
+
# =============================================================================
|
|
432
|
+
@dataclass(frozen=True)
|
|
433
|
+
class RunContext:
|
|
434
|
+
"""The run-shaped inputs to :func:`run_for_file` — what to run, where, and
|
|
435
|
+
how to report progress.
|
|
436
|
+
|
|
437
|
+
Bundles the clump that already travels together at every call site
|
|
438
|
+
(``main()`` here and the ``mutation-kill`` agent's own driving code),
|
|
439
|
+
separating "how to run this file" from ``run_for_file``'s own
|
|
440
|
+
``generate``/``max_rounds`` controls — mirrors
|
|
441
|
+
``mutation_kill_loop.py``'s ``RunContext`` (#1561/#1583).
|
|
442
|
+
``generator_label``, when set, is recorded in the commit message as an
|
|
443
|
+
audit trail (e.g. distinguishing an unattended ``--headless`` commit from
|
|
444
|
+
an agent-driven one).
|
|
445
|
+
|
|
446
|
+
``label_override_provider``, when set, is called with no arguments
|
|
447
|
+
before building each round's commit message; a non-``None`` result
|
|
448
|
+
replaces ``generator_label`` for that commit AND every subsequent commit
|
|
449
|
+
in this file (#1908 Step 3.2b) — the seam a model-downgrade event uses to
|
|
450
|
+
record itself in the audit trail without mutating this frozen,
|
|
451
|
+
file-level dataclass. The lifetime is sticky, not per-commit: once a
|
|
452
|
+
downgrade fires at round N, the stored label is never cleared, so every
|
|
453
|
+
later commit in this file also carries the downgrade label (with the
|
|
454
|
+
round number frozen at N, not the commit's own round) — intentional,
|
|
455
|
+
since the downgraded model really does stay in use for the rest of the
|
|
456
|
+
file. ``None`` (the default) leaves today's ``generator_label``-only
|
|
457
|
+
behavior unchanged.
|
|
458
|
+
"""
|
|
459
|
+
|
|
460
|
+
test_file: Path
|
|
461
|
+
source_path: Path
|
|
462
|
+
test_command: str
|
|
463
|
+
cwd: Path | None = None
|
|
464
|
+
log: Callable[[str], None] = print
|
|
465
|
+
initial_junitxml: str | None = None
|
|
466
|
+
generator_label: str | None = None
|
|
467
|
+
label_override_provider: Callable[[], str | None] | None = None
|
|
468
|
+
# #2030 stop controls, both default-off: with these None the loop's stop
|
|
469
|
+
# behavior is byte-identical to pre-#2030. target_honest_score is the
|
|
470
|
+
# Phase-0 mutation target Phase 8 gates on; min_kills_per_round is the
|
|
471
|
+
# marginal-yield floor (>=1 absolute kills, 0<v<1 a fraction of the
|
|
472
|
+
# round's starting survivors).
|
|
473
|
+
target_honest_score: float | None = None
|
|
474
|
+
min_kills_per_round: float | None = None
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _score_round(
|
|
478
|
+
round_num: int,
|
|
479
|
+
source_file: str,
|
|
480
|
+
ctx: RunContext,
|
|
481
|
+
*,
|
|
482
|
+
prev_survivor_count: int | None,
|
|
483
|
+
) -> tuple[list[dict], int] | None:
|
|
484
|
+
"""Score one round: scoped-run-or-seeded-report → survivor extraction →
|
|
485
|
+
log → stop-checks.
|
|
486
|
+
|
|
487
|
+
Returns ``(survivors, survivor_count)`` to continue the round, or
|
|
488
|
+
``None`` when the file is done — zero mutants generated (not
|
|
489
|
+
convergence — see below), no survivors, or no improvement over the
|
|
490
|
+
previous round.
|
|
491
|
+
"""
|
|
492
|
+
if ctx.initial_junitxml is not None and round_num == 1:
|
|
493
|
+
junitxml_text = ctx.initial_junitxml
|
|
494
|
+
else:
|
|
495
|
+
junitxml_text = run_scoped_mutmut(
|
|
496
|
+
source_file, test_command=ctx.test_command, test_file=ctx.test_file, cwd=ctx.cwd
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
survivors = extract_survivors(junitxml_text, source_file)
|
|
500
|
+
survivor_count = len(survivors)
|
|
501
|
+
summary = mutation_report.score_mutmut_junitxml(junitxml_text)
|
|
502
|
+
ctx.log(
|
|
503
|
+
f" round {round_num}: honest={summary.honest_score:.1f}% "
|
|
504
|
+
f"survivors={survivor_count}"
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
total_mutants = (
|
|
508
|
+
summary.killed + summary.survived + summary.timeout + summary.no_coverage
|
|
509
|
+
)
|
|
510
|
+
if total_mutants == 0:
|
|
511
|
+
ctx.log(
|
|
512
|
+
" zero mutants generated — this is NOT convergence. mutmut "
|
|
513
|
+
"produced no results at all (a real internal crash — e.g. "
|
|
514
|
+
"the known Python 3.13+ pickle incompatibility, 'TypeError: "
|
|
515
|
+
"cannot pickle itertools.count object' — or a file with no "
|
|
516
|
+
"executable statements). Stopping without declaring "
|
|
517
|
+
"survivors == 0 (#1359)."
|
|
518
|
+
)
|
|
519
|
+
return None
|
|
520
|
+
|
|
521
|
+
decision = stop_reason(
|
|
522
|
+
survivor_count,
|
|
523
|
+
prev_survivor_count,
|
|
524
|
+
honest_score=summary.honest_score,
|
|
525
|
+
target_honest_score=ctx.target_honest_score,
|
|
526
|
+
min_kills_per_round=ctx.min_kills_per_round,
|
|
527
|
+
)
|
|
528
|
+
if decision is not None:
|
|
529
|
+
# A non-terminal decision is the #2030 marginal-yield floor: the round
|
|
530
|
+
# DID make progress, the file may still be below target, and whether
|
|
531
|
+
# another round is worth its price is the operator's call. Prefixing it
|
|
532
|
+
# distinctly is what keeps it from reading as a convergence stop in the
|
|
533
|
+
# run log — the agent layer routes a YIELD FLOOR line to Phase 5's
|
|
534
|
+
# existing [c]ontinue / [r]etry / [w]aive / [q]uit prompt.
|
|
535
|
+
ctx.log(f" {decision}" if decision.terminal else f" YIELD FLOOR — {decision}")
|
|
536
|
+
return None
|
|
537
|
+
|
|
538
|
+
return survivors, survivor_count
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _revert_or_raise(ctx: RunContext, reason: str, *, after_commit: bool = False) -> None:
|
|
542
|
+
"""Revert ``ctx.test_file``, raising if the revert itself fails.
|
|
543
|
+
|
|
544
|
+
``after_commit=True`` routes through :func:`git_reset_and_revert`
|
|
545
|
+
(unstage + checkout), not plain :func:`git_revert` — ``git_commit``
|
|
546
|
+
already staged ``ctx.test_file`` before the commit attempt failed, so a
|
|
547
|
+
plain checkout alone would restore from that still-mutated index, not
|
|
548
|
+
HEAD (#1598/#1584 review). A revert that itself fails is fatal: it
|
|
549
|
+
leaves the working tree in an unknown, possibly-mutated state, so this
|
|
550
|
+
raises rather than letting the loop continue silently.
|
|
551
|
+
|
|
552
|
+
Explicit params (not a closure) — mirrors ``mutation_kill_loop.py``'s
|
|
553
|
+
``_revert_or_raise`` shape and makes this independently
|
|
554
|
+
testable/monkeypatchable (#1598/#1584 review, item 3).
|
|
555
|
+
"""
|
|
556
|
+
revert_ok = (
|
|
557
|
+
git_reset_and_revert(ctx.test_file, cwd=ctx.cwd)
|
|
558
|
+
if after_commit
|
|
559
|
+
else git_revert(ctx.test_file, cwd=ctx.cwd)
|
|
560
|
+
)
|
|
561
|
+
if not revert_ok:
|
|
562
|
+
raise mutation_kill_shared.RevertFailed(
|
|
563
|
+
f"revert failed for {ctx.test_file} after {reason} — the "
|
|
564
|
+
"working tree is left in an unknown state (mutated test "
|
|
565
|
+
"content may still be on disk, uncommitted)"
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def _verify_and_commit(
|
|
570
|
+
round_num: int,
|
|
571
|
+
source_file: str,
|
|
572
|
+
survivor_count: int,
|
|
573
|
+
new_tests: str,
|
|
574
|
+
ctx: RunContext,
|
|
575
|
+
) -> int | None:
|
|
576
|
+
"""Compile-check → scoped pytest → commit-on-green / revert-on-failure.
|
|
577
|
+
|
|
578
|
+
Returns ``survivor_count`` on a successful commit, or ``None`` when the
|
|
579
|
+
round is abandoned (compile failure, test failure, or commit failure —
|
|
580
|
+
each reverted via :func:`_revert_or_raise`, which raises if the revert
|
|
581
|
+
itself fails).
|
|
582
|
+
"""
|
|
583
|
+
if not python_compiles(ctx.test_file, cwd=ctx.cwd):
|
|
584
|
+
ctx.log(" compile check failed — reverting")
|
|
585
|
+
_revert_or_raise(ctx, "a failed compile check")
|
|
586
|
+
return None
|
|
587
|
+
if not run_scoped_pytest(ctx.test_file, cwd=ctx.cwd):
|
|
588
|
+
ctx.log(" tests failed — reverting")
|
|
589
|
+
_revert_or_raise(ctx, "a failed test run")
|
|
590
|
+
return None
|
|
591
|
+
|
|
592
|
+
ctx.log(" green — committing")
|
|
593
|
+
label_override = (
|
|
594
|
+
ctx.label_override_provider() if ctx.label_override_provider is not None else None
|
|
595
|
+
)
|
|
596
|
+
committed = git_commit(
|
|
597
|
+
_commit_message(
|
|
598
|
+
round_num,
|
|
599
|
+
source_file,
|
|
600
|
+
survivor_count,
|
|
601
|
+
new_tests,
|
|
602
|
+
generator_label=ctx.generator_label,
|
|
603
|
+
label_override=label_override,
|
|
604
|
+
),
|
|
605
|
+
ctx.test_file,
|
|
606
|
+
cwd=ctx.cwd,
|
|
607
|
+
)
|
|
608
|
+
if not committed:
|
|
609
|
+
# A failed commit is a round failure, not a silent success —
|
|
610
|
+
# without this check the loop would advance believing this
|
|
611
|
+
# round landed, while the new tests sit uncommitted (and
|
|
612
|
+
# possibly still staged) on disk (#1598).
|
|
613
|
+
ctx.log(" commit failed — reverting")
|
|
614
|
+
_revert_or_raise(ctx, "a failed commit", after_commit=True)
|
|
615
|
+
return None
|
|
616
|
+
return survivor_count
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
def _run_round(
|
|
620
|
+
round_num: int,
|
|
621
|
+
source_file: str,
|
|
622
|
+
ctx: RunContext,
|
|
623
|
+
generate: Generator,
|
|
624
|
+
*,
|
|
625
|
+
prev_survivor_count: int | None,
|
|
626
|
+
) -> int | None:
|
|
627
|
+
"""Run one round: score (via :func:`_score_round`) → generate → insert →
|
|
628
|
+
verify → commit (via :func:`_verify_and_commit`).
|
|
629
|
+
|
|
630
|
+
Returns this round's survivor count (to seed the next round's
|
|
631
|
+
no-improvement check), or ``None`` when the file is done.
|
|
632
|
+
"""
|
|
633
|
+
scored = _score_round(round_num, source_file, ctx, prev_survivor_count=prev_survivor_count)
|
|
634
|
+
if scored is None:
|
|
635
|
+
return None
|
|
636
|
+
survivors, survivor_count = scored
|
|
637
|
+
|
|
638
|
+
# Read once and thread the text through the generation prompt below —
|
|
639
|
+
# apply_generated_tests reads the file again, fresh, immediately before
|
|
640
|
+
# its own duplicate check and write (see its docstring): using a
|
|
641
|
+
# pre-generation snapshot there would widen a microseconds-wide
|
|
642
|
+
# read-before-write race into a multi-minute one, since generate() is an
|
|
643
|
+
# LLM call that can run for minutes (#1598/#1584 review).
|
|
644
|
+
test_text = ctx.test_file.read_text(encoding="utf-8")
|
|
645
|
+
new_tests = generate(
|
|
646
|
+
source_file,
|
|
647
|
+
survivors,
|
|
648
|
+
ctx.source_path.read_text(encoding="utf-8"),
|
|
649
|
+
test_text,
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
outcome = apply_generated_tests(ctx.test_file, new_tests)
|
|
653
|
+
if not outcome.inserted:
|
|
654
|
+
ctx.log(f" not inserted ({outcome.reason}) — stopping")
|
|
655
|
+
return None
|
|
656
|
+
|
|
657
|
+
return _verify_and_commit(round_num, source_file, survivor_count, new_tests, ctx)
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def run_for_file(
|
|
661
|
+
source_file: str,
|
|
662
|
+
ctx: RunContext,
|
|
663
|
+
*,
|
|
664
|
+
generate: Generator,
|
|
665
|
+
max_rounds: int = 5,
|
|
666
|
+
) -> None:
|
|
667
|
+
"""Drive the deterministic survivor-kill loop for one Python source file.
|
|
668
|
+
|
|
669
|
+
``generate`` is the sole non-deterministic step: given survivors +
|
|
670
|
+
context it returns the raw new-test text. Everything else — scoped run,
|
|
671
|
+
scoring, duplicate/insert guards, compile/test verification,
|
|
672
|
+
revert-on-failure, commit-on-green, and the no-improvement stop — is
|
|
673
|
+
mechanical, driven one round at a time by :func:`_run_round` — mirroring
|
|
674
|
+
:func:`mutation_kill_loop.run_for_file`'s contract exactly.
|
|
675
|
+
|
|
676
|
+
A failed revert (after a compile failure, a test failure, a failed
|
|
677
|
+
commit, or a failed mutmut cleanup revert inside
|
|
678
|
+
:func:`run_scoped_mutmut`) is fatal: it raises
|
|
679
|
+
:class:`mutation_kill_shared.RevertFailed` rather than returning
|
|
680
|
+
silently, because a revert that can't be verified as having succeeded
|
|
681
|
+
means the working tree is left in an unknown, possibly-mutated state
|
|
682
|
+
(#1598). A failed commit itself is also a round failure, not a silent
|
|
683
|
+
success: it is reverted (unstage + restore, via
|
|
684
|
+
:func:`git_reset_and_revert`) and the round stops without advancing.
|
|
685
|
+
"""
|
|
686
|
+
prev_survivor_count: int | None = None
|
|
687
|
+
for round_num in range(1, max_rounds + 1):
|
|
688
|
+
prev_survivor_count = _run_round(
|
|
689
|
+
round_num, source_file, ctx, generate, prev_survivor_count=prev_survivor_count
|
|
690
|
+
)
|
|
691
|
+
if prev_survivor_count is None:
|
|
692
|
+
return
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
# =============================================================================
|
|
696
|
+
# Headless generation — shell to `claude --print` for unattended runs.
|
|
697
|
+
# =============================================================================
|
|
698
|
+
def build_survivor_summary(survivors: list[dict], *, limit: int = 40) -> str:
|
|
699
|
+
"""Render surviving mutants as a compact list."""
|
|
700
|
+
lines = []
|
|
701
|
+
for mutant in survivors[:limit]:
|
|
702
|
+
line = mutant.get("location", {}).get("start", {}).get("line", "?")
|
|
703
|
+
lines.append(f"- L{line}")
|
|
704
|
+
if len(survivors) > limit:
|
|
705
|
+
lines.append(f"- … and {len(survivors) - limit} more")
|
|
706
|
+
return "\n".join(lines)
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
def build_generation_prompt(
|
|
710
|
+
source_file: str,
|
|
711
|
+
survivors: list[dict],
|
|
712
|
+
source_text: str,
|
|
713
|
+
test_text: str,
|
|
714
|
+
*,
|
|
715
|
+
source_limit: int = 8000,
|
|
716
|
+
) -> str:
|
|
717
|
+
"""Build the generation prompt.
|
|
718
|
+
|
|
719
|
+
The existing test file is the *only* pattern — assertion style and
|
|
720
|
+
fixture usage are inferred from it, never hardcoded here (mirrors
|
|
721
|
+
``mutation_kill_headless.build_generation_prompt``, adapted for pytest's flat
|
|
722
|
+
``def test_*():`` convention rather than a class/namespace-wrapped one).
|
|
723
|
+
"""
|
|
724
|
+
return (
|
|
725
|
+
f"You are adding new pytest test functions that KILL surviving "
|
|
726
|
+
f"mutations in {source_file}.\n\n"
|
|
727
|
+
"Match the existing test file exactly: its imports, assertion style "
|
|
728
|
+
"(plain `assert`, pytest.approx, monkeypatch, etc.), fixtures, and "
|
|
729
|
+
"naming conventions are the pattern to follow. Do not introduce any "
|
|
730
|
+
"library, helper, or convention that does not already appear in it.\n\n"
|
|
731
|
+
f"## Surviving mutations ({len(survivors)})\n"
|
|
732
|
+
f"{build_survivor_summary(survivors)}\n\n"
|
|
733
|
+
f"## Source under test\n{source_text[:source_limit]}\n\n"
|
|
734
|
+
f"## Existing test file (the pattern to match)\n{test_text}\n\n"
|
|
735
|
+
"## Rules\n"
|
|
736
|
+
"1. Return ONLY the new top-level `def test_*():` function(s) — no "
|
|
737
|
+
"class wrapper, no imports, no module-level fixtures.\n"
|
|
738
|
+
"2. Each must run against the helpers/fixtures already in the "
|
|
739
|
+
"existing test file.\n"
|
|
740
|
+
"3. Reuse the existing file's assertion and fixture patterns exactly.\n"
|
|
741
|
+
"4. Match the existing naming convention.\n"
|
|
742
|
+
"5. Do not redeclare fixtures or helpers already present.\n"
|
|
743
|
+
"6. Every assertion must check a specific value — not just that a "
|
|
744
|
+
"call didn't raise.\n"
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
def make_headless_generator(
|
|
749
|
+
model: str | None = None,
|
|
750
|
+
*,
|
|
751
|
+
cwd: Path | None = None,
|
|
752
|
+
log: Callable[[str], None] = print,
|
|
753
|
+
on_downgrade: Callable[[DowngradeEvent], None] | None = None,
|
|
754
|
+
sleep: Callable[[float], None] = time.sleep,
|
|
755
|
+
) -> Generator:
|
|
756
|
+
"""Return a :data:`Generator` that shells to ``claude --print``.
|
|
757
|
+
|
|
758
|
+
Builds the Python-flavored prompt above, then delegates everything else
|
|
759
|
+
to :func:`mutation_kill_retry.make_retrying_headless_call` (#1908) —
|
|
760
|
+
the 3-consecutive-gateway-class-failures/1-same-model-retry/at-most-
|
|
761
|
+
once-per-file-downgrade wrapper around
|
|
762
|
+
:func:`mutation_kill_shared.run_claude_headless` (#1583, relocated from
|
|
763
|
+
``mutation_kill_headless`` in #1601).
|
|
764
|
+
|
|
765
|
+
The retry/downgrade state (consecutive-failure counter, model in use,
|
|
766
|
+
whether this file already spent its one downgrade) lives in the
|
|
767
|
+
``retrying_call`` closure below — constructed once per file, here, never
|
|
768
|
+
at module scope — so a new file's generator always starts fresh at the
|
|
769
|
+
top of the ladder regardless of a prior file's downgrade, and concurrent
|
|
770
|
+
files under ``--all --concurrency`` (each with their own closure) never
|
|
771
|
+
leak state to one another. ``round_num`` is derived from how many times
|
|
772
|
+
THIS closure has been invoked (``_run_round`` calls ``generate`` once per
|
|
773
|
+
round), since the shared :data:`Generator` signature carries no round
|
|
774
|
+
number of its own.
|
|
775
|
+
|
|
776
|
+
``on_downgrade``, when given, is passed straight through to
|
|
777
|
+
:func:`mutation_kill_retry.make_retrying_headless_call`. Building the
|
|
778
|
+
``on_downgrade``/``get_label_override`` audit-trail pair
|
|
779
|
+
(:func:`mutation_kill_retry.make_downgrade_audit_hook`) is this
|
|
780
|
+
module's own ``main()``'s job now (#1908 review) — this function no
|
|
781
|
+
longer constructs one internally or attaches a
|
|
782
|
+
``label_override_provider`` attribute to the returned ``generate``;
|
|
783
|
+
``main()`` has ``get_label_override`` directly in scope and wires it
|
|
784
|
+
into :class:`RunContext` itself, so no attribute-smuggling is needed.
|
|
785
|
+
"""
|
|
786
|
+
retrying_call = make_retrying_headless_call(
|
|
787
|
+
initial_model=model, cwd=cwd, log=log, on_downgrade=on_downgrade, sleep=sleep
|
|
788
|
+
)
|
|
789
|
+
round_counter = {"n": 0}
|
|
790
|
+
|
|
791
|
+
def generate(
|
|
792
|
+
source_file: str,
|
|
793
|
+
survivors: list[dict],
|
|
794
|
+
source_text: str,
|
|
795
|
+
test_text: str,
|
|
796
|
+
) -> str:
|
|
797
|
+
round_counter["n"] += 1
|
|
798
|
+
prompt = build_generation_prompt(source_file, survivors, source_text, test_text)
|
|
799
|
+
return retrying_call(prompt, source_file, round_counter["n"])
|
|
800
|
+
|
|
801
|
+
return generate
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
# =============================================================================
|
|
805
|
+
# CLI — startup preflight + --headless generation.
|
|
806
|
+
# =============================================================================
|
|
807
|
+
def parse_args(argv: Sequence[str]) -> argparse.Namespace:
|
|
808
|
+
p = argparse.ArgumentParser(
|
|
809
|
+
prog="mutation_kill_loop_python.py",
|
|
810
|
+
description=(
|
|
811
|
+
"Deterministic survivor-kill loop for Python/mutmut. Agent-driven "
|
|
812
|
+
"by default; --headless enables unattended generation via the "
|
|
813
|
+
"Claude CLI."
|
|
814
|
+
),
|
|
815
|
+
)
|
|
816
|
+
p.add_argument("--file", required=False, help="Source file to target")
|
|
817
|
+
p.add_argument(
|
|
818
|
+
"--test-command",
|
|
819
|
+
default=None,
|
|
820
|
+
help="Scoped pytest command mutmut runs per mutant (required)",
|
|
821
|
+
)
|
|
822
|
+
p.add_argument("--max-rounds", type=int, default=5, help="Max rounds per file")
|
|
823
|
+
p.add_argument(
|
|
824
|
+
"--headless",
|
|
825
|
+
action="store_true",
|
|
826
|
+
help="Unattended generation via `claude --print` (CI runs).",
|
|
827
|
+
)
|
|
828
|
+
p.add_argument(
|
|
829
|
+
"--model",
|
|
830
|
+
help=(
|
|
831
|
+
"Generation model for --headless. Default: DEV_TEAM_MUTATION_MODEL "
|
|
832
|
+
"env var, else omitted so `claude --print` uses its own default."
|
|
833
|
+
),
|
|
834
|
+
)
|
|
835
|
+
p.add_argument("--test-file", help="Test file to extend (required with --headless)")
|
|
836
|
+
p.add_argument("--source-path", help="Source file under test (required with --headless)")
|
|
837
|
+
p.add_argument(
|
|
838
|
+
"--target-honest-score",
|
|
839
|
+
type=float,
|
|
840
|
+
default=None,
|
|
841
|
+
help=(
|
|
842
|
+
"Phase-0 mutation target (percent). Stop a file once its honest "
|
|
843
|
+
"score reaches this, since work past the threshold cannot change "
|
|
844
|
+
"the Phase-8 verdict. Default off — unset reproduces pre-#2030 "
|
|
845
|
+
"behavior exactly."
|
|
846
|
+
),
|
|
847
|
+
)
|
|
848
|
+
p.add_argument(
|
|
849
|
+
"--min-kills-per-round",
|
|
850
|
+
type=float,
|
|
851
|
+
default=None,
|
|
852
|
+
help=(
|
|
853
|
+
"Marginal-yield floor. >=1 is an absolute kill count; 0<v<1 is a "
|
|
854
|
+
"fraction of the round's starting survivors. A round below the "
|
|
855
|
+
"floor while still under target is surfaced to the operator "
|
|
856
|
+
"([c]ontinue / [r]etry / [w]aive / [q]uit), never stopped "
|
|
857
|
+
"silently. Default off."
|
|
858
|
+
),
|
|
859
|
+
)
|
|
860
|
+
return p.parse_args(list(argv))
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
864
|
+
"""CLI entry point — see :func:`mutation_kill_headless.main` for the contract
|
|
865
|
+
this mirrors."""
|
|
866
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
867
|
+
args = parse_args(argv)
|
|
868
|
+
|
|
869
|
+
if not args.headless:
|
|
870
|
+
sys.stderr.write(f"error: {NO_GENERATOR_MESSAGE}\n")
|
|
871
|
+
return 1
|
|
872
|
+
|
|
873
|
+
model = resolve_model(args.model)
|
|
874
|
+
|
|
875
|
+
if not claude_cli_available():
|
|
876
|
+
sys.stderr.write(f"error: {MISSING_CLAUDE_MESSAGE}\n")
|
|
877
|
+
return 3
|
|
878
|
+
|
|
879
|
+
if not (args.file and args.test_file and args.source_path and args.test_command):
|
|
880
|
+
sys.stderr.write(
|
|
881
|
+
"error: --headless requires --file, --test-file, --source-path, "
|
|
882
|
+
"and --test-command\n"
|
|
883
|
+
)
|
|
884
|
+
return 2
|
|
885
|
+
|
|
886
|
+
on_downgrade, get_label_override = make_downgrade_audit_hook()
|
|
887
|
+
generate = make_headless_generator(model, on_downgrade=on_downgrade)
|
|
888
|
+
try:
|
|
889
|
+
run_for_file(
|
|
890
|
+
args.file,
|
|
891
|
+
RunContext(
|
|
892
|
+
test_file=Path(args.test_file),
|
|
893
|
+
source_path=Path(args.source_path),
|
|
894
|
+
test_command=args.test_command,
|
|
895
|
+
generator_label=f"headless ({model or 'default'})",
|
|
896
|
+
label_override_provider=get_label_override,
|
|
897
|
+
target_honest_score=args.target_honest_score,
|
|
898
|
+
min_kills_per_round=args.min_kills_per_round,
|
|
899
|
+
),
|
|
900
|
+
generate=generate,
|
|
901
|
+
max_rounds=args.max_rounds,
|
|
902
|
+
)
|
|
903
|
+
except GenerationExhausted as exc:
|
|
904
|
+
# This file's retry-then-downgrade budget is fully spent (3
|
|
905
|
+
# consecutive gateway-class failures + 1 same-model retry, at the
|
|
906
|
+
# original model AND at most one fallback tier) — distinct from
|
|
907
|
+
# RevertFailed below (exit 4, working tree possibly mutated) and
|
|
908
|
+
# from the generic RuntimeError case below it (exit 5, clean but
|
|
909
|
+
# not exhausted — e.g. a non-gateway-class generation timeout). A
|
|
910
|
+
# clean exhaustion mutates nothing in the paths this covers:
|
|
911
|
+
# generation precedes insertion within a round, and a prior round's
|
|
912
|
+
# own insertion-revert failure (compile/test/commit paths, via
|
|
913
|
+
# _revert_or_raise) is itself fatal — raised as RevertFailed, never
|
|
914
|
+
# swallowed. run_scoped_mutmut's post-mutmut-crash cleanup revert
|
|
915
|
+
# (its own ``finally``) is also checked (#1928/#1939) and raises
|
|
916
|
+
# RevertFailed on its own failure. What isn't independently
|
|
917
|
+
# re-verified here is that a revert git reports as successful
|
|
918
|
+
# actually left the tree clean (#1955) — so callers
|
|
919
|
+
# (stryker_shard_pipeline.py's shard driver) can log this file as
|
|
920
|
+
# unfixed and continue to the next file without affecting the run's
|
|
921
|
+
# exit status, instead of aborting the whole shard (#1908 review).
|
|
922
|
+
sys.stderr.write(f"error: {exc}\n")
|
|
923
|
+
return EXIT_GENERATION_EXHAUSTED
|
|
924
|
+
except mutation_kill_shared.RevertFailed as exc:
|
|
925
|
+
# A failed revert (or a failed-commit round-abandonment's own
|
|
926
|
+
# revert) leaves the working tree in an unknown, possibly-mutated
|
|
927
|
+
# state (#1930) — narrower and more urgent than the generic
|
|
928
|
+
# RuntimeError case below: this is the only case that can't be
|
|
929
|
+
# trusted as clean.
|
|
930
|
+
sys.stderr.write(f"error: {exc}\n")
|
|
931
|
+
return EXIT_REVERT_FAILED
|
|
932
|
+
except RuntimeError as exc:
|
|
933
|
+
# Every other RuntimeError this loop raises (a mutmut-run timeout,
|
|
934
|
+
# a mutmut-start/junitxml-extraction failure, etc.) is clean:
|
|
935
|
+
# run_scoped_mutmut's cleanup-revert gap is closed (#1928/#1939) —
|
|
936
|
+
# a failed cleanup revert now raises RevertFailed instead of being
|
|
937
|
+
# silently discarded, so reaching this branch means the cleanup
|
|
938
|
+
# revert itself succeeded, same as the GenerationExhausted case
|
|
939
|
+
# above. Not a retry-budget exhaustion — reuses exit 5 (#1956: this
|
|
940
|
+
# is the OUTCOME class, not a specific cause) because the shard
|
|
941
|
+
# driver only distinguishes "fatal, stop" (4) from "clean, continue"
|
|
942
|
+
# (5), not why a file wasn't fixed.
|
|
943
|
+
sys.stderr.write(f"error: {exc} — generation failed cleanly, continuing\n")
|
|
944
|
+
return EXIT_GENERATION_EXHAUSTED
|
|
945
|
+
return 0
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
if __name__ == "__main__":
|
|
949
|
+
sys.exit(main())
|