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,869 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""mutation_kill_loop.py — config-driven, deterministic survivor-kill loop.
|
|
3
|
+
|
|
4
|
+
Migrated from the ACI ``nextgen-test-upgrade-process`` mutation agent and
|
|
5
|
+
de-hardcoded: nothing repo-specific lives here. Project / test-project /
|
|
6
|
+
mutate targets come from ``stryker-config.json``; ``DOTNET_ROOT`` resolution
|
|
7
|
+
and ``.sln`` hide/restore reuse the shipped ``csharp_stryker_net_wrapper``;
|
|
8
|
+
scoring and survivor extraction reuse ``mutation_report``.
|
|
9
|
+
|
|
10
|
+
Generic, stdlib-only, cross-platform (macOS, Linux, Windows). The only
|
|
11
|
+
non-stdlib imports are the sibling plugin modules in this same ``scripts/``
|
|
12
|
+
directory.
|
|
13
|
+
|
|
14
|
+
**Scope (#1562).** This module owns config parsing, the scoped Stryker run,
|
|
15
|
+
verify/commit/revert, and ``run_for_file`` orchestration. Insertion mechanics
|
|
16
|
+
(detect-or-refuse C# source manipulation) live in ``mutation_kill_insert.py``,
|
|
17
|
+
a stdlib-only leaf this module imports from — never the reverse — mirroring
|
|
18
|
+
the wrapper/report split already established by
|
|
19
|
+
``csharp_stryker_net_wrapper.py`` / ``mutation_report.py``. Headless
|
|
20
|
+
generation and the ``--headless`` CLI live in ``mutation_kill_headless.py``,
|
|
21
|
+
which is a different shape: a thin CLI layer built atop this module's public
|
|
22
|
+
API, so it imports back from here at module scope. This module only reaches
|
|
23
|
+
into it lazily, inside ``if __name__ == "__main__":``, specifically to avoid
|
|
24
|
+
the circular import that a module-scope import in both directions would
|
|
25
|
+
create.
|
|
26
|
+
|
|
27
|
+
**Shared mechanics (#1583).** ``_timeout_from_env``, ``git_revert``,
|
|
28
|
+
``git_reset_and_revert``, ``git_commit``, and the "no improvement across
|
|
29
|
+
rounds" stop predicate are imported from ``mutation_kill_shared.py`` rather
|
|
30
|
+
than defined here — they were byte-for-byte duplicated with
|
|
31
|
+
``mutation_kill_loop_python.py`` (the Python/mutmut sibling) before that
|
|
32
|
+
module existed. See its module docstring for the full rationale.
|
|
33
|
+
|
|
34
|
+
**Generation is a seam, not a mechanism.** The loop never decides *what*
|
|
35
|
+
tests to write — a caller supplies a ``generate`` callable that returns the
|
|
36
|
+
new test-method text. The default (interactive) path is agent-driven: the
|
|
37
|
+
``mutation-kill`` agent calls :func:`run_for_file` directly, passing a
|
|
38
|
+
``generate`` hook backed by a live agent turn. A ``--headless`` CLI mode
|
|
39
|
+
(``mutation_kill_headless.py``) shells to ``claude --print`` for unattended
|
|
40
|
+
(CI / shard-pipeline) runs. Invoking the CLI with neither an injected
|
|
41
|
+
generator nor ``--headless`` fails fast at startup — before any Stryker run
|
|
42
|
+
or file mutation.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from __future__ import annotations
|
|
46
|
+
|
|
47
|
+
import json
|
|
48
|
+
import os
|
|
49
|
+
import re
|
|
50
|
+
import subprocess
|
|
51
|
+
import sys
|
|
52
|
+
import tempfile
|
|
53
|
+
from collections.abc import Callable, Sequence
|
|
54
|
+
from dataclasses import dataclass
|
|
55
|
+
from pathlib import Path
|
|
56
|
+
|
|
57
|
+
# The `Generator` alias below is a real runtime expression
|
|
58
|
+
# (mutation_kill_headless imports the name), so `from __future__ import
|
|
59
|
+
# annotations` cannot defer it — it must be subscriptable at import time.
|
|
60
|
+
# collections.abc generics have been since 3.9, which the 3.10 floor
|
|
61
|
+
# (ADR 0031) clears.
|
|
62
|
+
from typing import NamedTuple
|
|
63
|
+
|
|
64
|
+
import csharp_stryker_net_wrapper as wrapper
|
|
65
|
+
import mutation_kill_shared
|
|
66
|
+
import mutation_report
|
|
67
|
+
import mutation_safety_gate
|
|
68
|
+
from mutation_kill_insert import apply_generated_methods, count_methods
|
|
69
|
+
from mutation_kill_shared import (
|
|
70
|
+
GIT_TIMEOUT_S, # noqa: F401 — re-exported for tests (loop.GIT_TIMEOUT_S)
|
|
71
|
+
_timeout_from_env,
|
|
72
|
+
git_commit,
|
|
73
|
+
git_reset_and_revert,
|
|
74
|
+
git_revert,
|
|
75
|
+
stop_reason,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# A generator is any callable the agent (or the headless CLI) supplies: given
|
|
79
|
+
# the source filename, the surviving mutants, the source text, and the
|
|
80
|
+
# current test text, it returns the raw text of new test methods to insert.
|
|
81
|
+
# The loop owns everything *around* this call; the callable owns the single
|
|
82
|
+
# genuinely-LLM step.
|
|
83
|
+
Generator = Callable[[str, list[dict], str, str], str]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class _Timeout(NamedTuple):
|
|
87
|
+
"""A timeout value paired with the env var that overrides it — kept
|
|
88
|
+
together so the two can't drift out of sync at a ``_run_with_timeout``
|
|
89
|
+
call site (#1593 review)."""
|
|
90
|
+
|
|
91
|
+
seconds: float
|
|
92
|
+
env_var_name: str
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _make_timeout(env_var_name: str, default: int) -> _Timeout:
|
|
96
|
+
"""Parse+pair in one step, so the env-var name is written once, not
|
|
97
|
+
once for ``_timeout_from_env`` and again for the ``_Timeout`` it feeds."""
|
|
98
|
+
return _Timeout(_timeout_from_env(env_var_name, default), env_var_name)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# Timeouts for the long-running subprocesses this loop shells out to. Each is
|
|
102
|
+
# overridable via env var since a legitimately slow project (a large solution,
|
|
103
|
+
# a heavyweight test suite) may need a larger budget than these defaults —
|
|
104
|
+
# unlike the 30s used by this codebase's git-shelling helpers, these
|
|
105
|
+
# subprocesses (a Stryker run, a dotnet build/test) routinely run for minutes.
|
|
106
|
+
_STRYKER_TIMEOUT = _make_timeout("DEV_TEAM_MUTATION_STRYKER_TIMEOUT_S", 3600)
|
|
107
|
+
_BUILD_TIMEOUT = _make_timeout("DEV_TEAM_MUTATION_BUILD_TIMEOUT_S", 600)
|
|
108
|
+
_TEST_TIMEOUT = _make_timeout("DEV_TEAM_MUTATION_TEST_TIMEOUT_S", 600)
|
|
109
|
+
# GIT_TIMEOUT_S itself comes from mutation_kill_shared (imported above) —
|
|
110
|
+
# shared verbatim with mutation_kill_loop_python.py (#1583). No local
|
|
111
|
+
# _GIT_TIMEOUT _Timeout pairing exists here anymore: git_revert/git_commit/
|
|
112
|
+
# git_reset_and_revert are themselves imported from that module, so they
|
|
113
|
+
# already close over its own GIT_TIMEOUT_S rather than taking one as a param.
|
|
114
|
+
|
|
115
|
+
STRYKER_RUN_TIMEOUT_S = _STRYKER_TIMEOUT.seconds
|
|
116
|
+
DOTNET_BUILD_TIMEOUT_S = _BUILD_TIMEOUT.seconds
|
|
117
|
+
DOTNET_TEST_TIMEOUT_S = _TEST_TIMEOUT.seconds
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _run_with_timeout(
|
|
121
|
+
argv: list[str],
|
|
122
|
+
timeout_spec: _Timeout,
|
|
123
|
+
*,
|
|
124
|
+
operation_label: str,
|
|
125
|
+
cwd: Path | None = None,
|
|
126
|
+
capture_output: bool = False,
|
|
127
|
+
text: bool = False,
|
|
128
|
+
env: dict[str, str] | None = None,
|
|
129
|
+
) -> subprocess.CompletedProcess | None:
|
|
130
|
+
"""Run ``argv`` with a bounded timeout. On ``TimeoutExpired``, logs to
|
|
131
|
+
stderr and returns ``None`` instead of raising, so ``dotnet_build`` and
|
|
132
|
+
``dotnet_test`` share one timeout/log shape instead of two copies (#1593).
|
|
133
|
+
``git_revert``/``git_commit``/``git_reset_and_revert`` moved to
|
|
134
|
+
``mutation_kill_shared.py`` (#1583) with their own, behaviorally-identical
|
|
135
|
+
inline timeout handling — they no longer call this helper. No
|
|
136
|
+
``shell``/``**kwargs`` passthrough: every ``argv`` runs as a list, never
|
|
137
|
+
through a shell.
|
|
138
|
+
``env`` defaults to ``None`` (inherit the ambient process environment,
|
|
139
|
+
today's behavior) — tests pass a scrubbed env to keep real-git regression
|
|
140
|
+
tests hermetic (#1598/#1584 review, item 4 follow-up)."""
|
|
141
|
+
try:
|
|
142
|
+
return subprocess.run(
|
|
143
|
+
argv,
|
|
144
|
+
cwd=cwd,
|
|
145
|
+
timeout=timeout_spec.seconds,
|
|
146
|
+
capture_output=capture_output,
|
|
147
|
+
text=text,
|
|
148
|
+
check=False,
|
|
149
|
+
env=env,
|
|
150
|
+
)
|
|
151
|
+
except subprocess.TimeoutExpired:
|
|
152
|
+
sys.stderr.write(
|
|
153
|
+
f"error: {operation_label} timed out after {timeout_spec.seconds}s "
|
|
154
|
+
f"(set {timeout_spec.env_var_name} to raise it)\n"
|
|
155
|
+
)
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# =============================================================================
|
|
160
|
+
# Config — everything path-shaped comes from stryker-config.json.
|
|
161
|
+
# =============================================================================
|
|
162
|
+
@dataclass(frozen=True)
|
|
163
|
+
class LoopConfig:
|
|
164
|
+
"""The subset of a ``stryker-config.json`` the loop needs.
|
|
165
|
+
|
|
166
|
+
``project`` is the mutated project (optional — Stryker infers it from the
|
|
167
|
+
solution when omitted). ``test_projects`` drives every ``dotnet build`` and
|
|
168
|
+
``dotnet test`` target — never a hardcoded path. ``solution`` is the main
|
|
169
|
+
``.sln`` hidden during a scoped run.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
project: str | None
|
|
173
|
+
test_projects: list[str]
|
|
174
|
+
mutate: list[str]
|
|
175
|
+
solution: str | None
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def load_loop_config(config_path: Path) -> LoopConfig:
|
|
179
|
+
"""Parse ``stryker-config.json``, tolerating the wrapper shape.
|
|
180
|
+
|
|
181
|
+
Stryker configs may nest everything under a top-level ``"stryker-config"``
|
|
182
|
+
key (the shape the ACI configs use) or place the keys at the top level.
|
|
183
|
+
Both are accepted.
|
|
184
|
+
"""
|
|
185
|
+
raw = json.loads(Path(config_path).read_text(encoding="utf-8"))
|
|
186
|
+
inner = raw.get("stryker-config", raw)
|
|
187
|
+
return LoopConfig(
|
|
188
|
+
project=inner.get("project"),
|
|
189
|
+
test_projects=list(inner.get("test-projects", [])),
|
|
190
|
+
mutate=list(inner.get("mutate", [])),
|
|
191
|
+
solution=inner.get("solution"),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def dotnet_test_project_targets(config: LoopConfig) -> list[str]:
|
|
196
|
+
"""Both the build and test targets are exactly the configured test-projects."""
|
|
197
|
+
return list(config.test_projects)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# =============================================================================
|
|
201
|
+
# Scoped Stryker run — goes THROUGH the wrapper (AC4).
|
|
202
|
+
# =============================================================================
|
|
203
|
+
def make_scoped_config(config: LoopConfig, source_file: str) -> dict:
|
|
204
|
+
"""Build a Stryker config scoped to a single source file.
|
|
205
|
+
|
|
206
|
+
Carries the configured solution / project / test-projects forward and
|
|
207
|
+
narrows ``mutate`` to just the target file with per-test coverage for fast
|
|
208
|
+
feedback. Keys whose configured value is ``None`` are dropped so Stryker
|
|
209
|
+
infers them.
|
|
210
|
+
"""
|
|
211
|
+
scoped = {
|
|
212
|
+
"solution": config.solution,
|
|
213
|
+
"project": config.project,
|
|
214
|
+
"test-projects": config.test_projects,
|
|
215
|
+
"mutate": [f"**/{source_file}"],
|
|
216
|
+
"coverage-analysis": "perTest",
|
|
217
|
+
"reporters": ["json"],
|
|
218
|
+
}
|
|
219
|
+
scoped = {k: v for k, v in scoped.items() if v is not None}
|
|
220
|
+
return {"stryker-config": scoped}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def make_confirm_scoped_config(
|
|
224
|
+
config: LoopConfig,
|
|
225
|
+
source_file: str,
|
|
226
|
+
lines: Sequence[int],
|
|
227
|
+
*,
|
|
228
|
+
total_file_lines: int | None = None,
|
|
229
|
+
degenerate_ratio: float = 0.5,
|
|
230
|
+
) -> dict:
|
|
231
|
+
"""Build a Stryker config scoped to specific SOURCE LINES of one file.
|
|
232
|
+
|
|
233
|
+
#2031: every file that converges pays one full scoped mutation run to
|
|
234
|
+
learn it has converged, and that run generates nothing. The terminal
|
|
235
|
+
round only needs to re-test the handful of mutants that survived the
|
|
236
|
+
previous round, whose lines ``mutation_report.survivor_lines`` already
|
|
237
|
+
computes.
|
|
238
|
+
|
|
239
|
+
**Soundness invariant.** The mutation-kill loop never edits production
|
|
240
|
+
source — every round only ever *adds* test methods to the test file
|
|
241
|
+
(``apply_generated_methods`` / ``run_for_file``; ``mutation_kill_insert``
|
|
242
|
+
refuses anything that looks like a source-file edit). So the set of
|
|
243
|
+
mutation targets in ``source_file`` is fixed for the file's entire loop
|
|
244
|
+
lifetime: no mutant can appear at a line outside the previous round's
|
|
245
|
+
survivor set, because nothing the loop does between rounds can create
|
|
246
|
+
one. That is what makes re-testing only the previous round's survivor
|
|
247
|
+
lines a strictly narrower question with the same answer a full re-scan
|
|
248
|
+
would give for those specific mutants, rather than merely a convenient
|
|
249
|
+
shortcut.
|
|
250
|
+
|
|
251
|
+
**Verified (#2031) — and the verification says this span form is
|
|
252
|
+
WRONG.** Checked against Stryker.NET's own current documentation
|
|
253
|
+
(stryker-mutator.io/docs/stryker-net/configuration/, § ``mutate``,
|
|
254
|
+
fetched live during this work): the ``{start..end}`` span suffix on
|
|
255
|
+
``mutate`` (``dotnet stryker -m "File.cs{10..100}"``) is real and
|
|
256
|
+
supported. But "a span is defined by the indices of the **first
|
|
257
|
+
character** and the **last character**" — i.e. CHARACTER offsets into
|
|
258
|
+
the file, not line numbers. The span form emitted below
|
|
259
|
+
(``"**/File.cs{10..10}"`` for a survivor on line 10) feeds a *line*
|
|
260
|
+
number into a slot Stryker parses as a *character* offset — it points at
|
|
261
|
+
an unrelated, essentially arbitrary character range near the start of
|
|
262
|
+
the file, not the intended line. Wiring this in as-is risks the exact
|
|
263
|
+
"confirm-run that silently mutates more than it claims" failure this
|
|
264
|
+
issue's Risk section warns against: at best the wrong span matches zero
|
|
265
|
+
mutants (caught by ``_score_round``'s #1606 zero-mutants guard — see
|
|
266
|
+
``test_narrowed_confirm_config_resolving_to_nothing_still_trips_the_zero_mutants_guard``
|
|
267
|
+
in ``tests/scripts/test_mutation_kill_loop_orchestration.py``); at worst
|
|
268
|
+
it silently overlaps a *different* mutant than the one being confirmed.
|
|
269
|
+
|
|
270
|
+
Translating a survivor line number into the character-offset span
|
|
271
|
+
Stryker.NET actually expects is mechanically computable (read the file,
|
|
272
|
+
sum preceding line lengths) — but the exact indexing convention (0- vs
|
|
273
|
+
1-based, inclusive/exclusive end, how CRLF line endings count toward the
|
|
274
|
+
offset) is not pinned by the documentation alone, and confirming it needs
|
|
275
|
+
a live ``dotnet``/``dotnet-stryker`` run against a probe file with a
|
|
276
|
+
known survivor line. No such toolchain is available in every environment
|
|
277
|
+
that develops this plugin (checked: ``dotnet`` is not always on ``PATH``).
|
|
278
|
+
Per this issue's own explicit fallback: **this function stays unwired**
|
|
279
|
+
from ``_score_round`` — see
|
|
280
|
+
``test_confirm_scoped_config_is_not_wired_into_the_default_loop_path`` —
|
|
281
|
+
until a pass with a real Stryker.NET verifies the character-offset
|
|
282
|
+
conversion end-to-end. See ``references/languages/csharp-stryker-net.md``
|
|
283
|
+
§ "`--mutate` line-span narrowing" for the full recorded finding and what
|
|
284
|
+
that verification pass needs to do.
|
|
285
|
+
|
|
286
|
+
**Degenerate-narrowing guard (#2031 AC4).** When ``total_file_lines`` is
|
|
287
|
+
supplied, narrowing that would touch ``degenerate_ratio`` or more of the
|
|
288
|
+
file's own lines (default 50%) degrades to :func:`make_scoped_config`
|
|
289
|
+
instead of emitting one span per line — re-testing most of a file one
|
|
290
|
+
line at a time is no cheaper than re-scoping the whole file, and produces
|
|
291
|
+
a far larger ``mutate`` array for no savings. ``total_file_lines=None``
|
|
292
|
+
(the default) skips the comparison, matching today's unconditional
|
|
293
|
+
per-line-span behavior for a caller that doesn't have the file's line
|
|
294
|
+
count handy.
|
|
295
|
+
|
|
296
|
+
Empty ``lines`` returns the ordinary whole-file scoped config, so a caller
|
|
297
|
+
that finds no clustered survivors degrades to today's behavior rather than
|
|
298
|
+
emitting a config that matches nothing.
|
|
299
|
+
"""
|
|
300
|
+
if not lines:
|
|
301
|
+
return make_scoped_config(config, source_file)
|
|
302
|
+
unique_lines = sorted(set(lines))
|
|
303
|
+
if (
|
|
304
|
+
total_file_lines is not None
|
|
305
|
+
and total_file_lines > 0
|
|
306
|
+
and len(unique_lines) / total_file_lines >= degenerate_ratio
|
|
307
|
+
):
|
|
308
|
+
return make_scoped_config(config, source_file)
|
|
309
|
+
spans = [f"**/{source_file}{{{line}..{line}}}" for line in unique_lines]
|
|
310
|
+
scoped = {
|
|
311
|
+
"solution": config.solution,
|
|
312
|
+
"project": config.project,
|
|
313
|
+
"test-projects": config.test_projects,
|
|
314
|
+
"mutate": spans,
|
|
315
|
+
"coverage-analysis": "perTest",
|
|
316
|
+
"reporters": ["json"],
|
|
317
|
+
}
|
|
318
|
+
scoped = {k: v for k, v in scoped.items() if v is not None}
|
|
319
|
+
return {"stryker-config": scoped}
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _write_scoped_config(config: LoopConfig, source_file: str) -> Path:
|
|
323
|
+
with tempfile.NamedTemporaryFile(
|
|
324
|
+
suffix=".json", delete=False, mode="w", prefix="stryker-scoped-"
|
|
325
|
+
) as tmp:
|
|
326
|
+
json.dump(make_scoped_config(config, source_file), tmp)
|
|
327
|
+
path = Path(tmp.name)
|
|
328
|
+
return path
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _validate_solution_path(solution: str, *, cwd: Path | None = None) -> Path:
|
|
332
|
+
"""Reject a ``stryker-config.json`` ``solution`` value that would escape
|
|
333
|
+
the working tree once used to derive ``sln_hidden``, and return the
|
|
334
|
+
resolved, validated absolute path.
|
|
335
|
+
|
|
336
|
+
``solution`` is read verbatim from ``stryker-config.json`` and, before
|
|
337
|
+
this check existed, was concatenated straight into a filename
|
|
338
|
+
(``f"{config.solution}.stryker-hidden"``) with no validation — a
|
|
339
|
+
traversal-shaped value (e.g. ``"../../etc/cron.d/evil"``) would let a
|
|
340
|
+
hostile config target a file outside the repo if the config is ever read
|
|
341
|
+
from an untrusted checkout (#1598). Resolves ``solution`` against
|
|
342
|
+
``cwd`` (or the current working directory) and rejects it unless the
|
|
343
|
+
resolved path stays under that root.
|
|
344
|
+
|
|
345
|
+
Callers must build ``sln``/``sln_hidden`` from THIS function's return
|
|
346
|
+
value, not by independently re-deriving ``Path(config.solution)`` — the
|
|
347
|
+
latter resolves against the *process* working directory when
|
|
348
|
+
``wrapper.hide_sln``/``restore_sln``'s bare ``Path.rename()`` runs, not
|
|
349
|
+
against ``cwd`` (which only ever reaches ``subprocess.run``), so the
|
|
350
|
+
validated path and the actually-renamed path could silently diverge
|
|
351
|
+
whenever a caller passes a ``cwd`` different from the real process cwd
|
|
352
|
+
(caught in #1598/#1584 review).
|
|
353
|
+
|
|
354
|
+
**Requires a relative ``solution``; an absolute value is rejected in
|
|
355
|
+
every practical case (#1598/#1584 review, item 11).** ``pathlib``'s ``/``
|
|
356
|
+
operator silently discards ``base`` outright when the right-hand operand
|
|
357
|
+
is itself absolute (``base / "/etc/passwd" == Path("/etc/passwd")``), so
|
|
358
|
+
``candidate`` becomes that absolute value verbatim — not "``base`` plus
|
|
359
|
+
the absolute path" as the name might suggest. The ``relative_to(base)``
|
|
360
|
+
check below then rejects it as escaping, UNLESS the absolute value
|
|
361
|
+
happens to already lie under ``base`` (a degenerate case with no
|
|
362
|
+
practical use, since a relative value achieves the same result without
|
|
363
|
+
depending on ``base``'s exact prefix). This narrows what a bare
|
|
364
|
+
``Path(config.solution)`` used to accept before this validation existed
|
|
365
|
+
— an absolute ``solution`` is not a supported input; pass a
|
|
366
|
+
repo-relative path.
|
|
367
|
+
"""
|
|
368
|
+
base = (cwd or Path.cwd()).resolve()
|
|
369
|
+
candidate = (base / solution).resolve()
|
|
370
|
+
try:
|
|
371
|
+
candidate.relative_to(base)
|
|
372
|
+
except ValueError:
|
|
373
|
+
raise ValueError(
|
|
374
|
+
f"stryker-config.json 'solution' escapes the working tree: {solution!r}"
|
|
375
|
+
) from None
|
|
376
|
+
return candidate
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def run_scoped_stryker(
|
|
380
|
+
config: LoopConfig,
|
|
381
|
+
source_file: str,
|
|
382
|
+
*,
|
|
383
|
+
output_dir: Path,
|
|
384
|
+
stryker_bin: str = "dotnet",
|
|
385
|
+
cwd: Path | None = None,
|
|
386
|
+
) -> Path:
|
|
387
|
+
"""Run Stryker scoped to one file; return the report path.
|
|
388
|
+
|
|
389
|
+
DOTNET_ROOT resolution and ``.sln`` hide/restore are delegated to
|
|
390
|
+
``csharp_stryker_net_wrapper`` — this function never re-implements either
|
|
391
|
+
(AC4). The ``.sln`` is hidden for the duration of the run and restored on
|
|
392
|
+
every exit path via ``finally``.
|
|
393
|
+
"""
|
|
394
|
+
dotnet_root, err = wrapper.resolve_dotnet_root(
|
|
395
|
+
preset=os.environ.get("DOTNET_ROOT"),
|
|
396
|
+
candidates=wrapper.default_probe_candidates(),
|
|
397
|
+
)
|
|
398
|
+
if err is not None:
|
|
399
|
+
raise RuntimeError(err)
|
|
400
|
+
assert dotnet_root is not None
|
|
401
|
+
env = {**os.environ, "DOTNET_ROOT": dotnet_root}
|
|
402
|
+
|
|
403
|
+
sln: Path | None = None
|
|
404
|
+
if config.solution:
|
|
405
|
+
# Use the validated, resolved path for BOTH the check and the actual
|
|
406
|
+
# rename below — never re-derive a second, independently-resolved
|
|
407
|
+
# Path(config.solution) (#1598/#1584 review: the two could diverge).
|
|
408
|
+
sln = _validate_solution_path(config.solution, cwd=cwd)
|
|
409
|
+
|
|
410
|
+
config_path = _write_scoped_config(config, source_file)
|
|
411
|
+
# Fixed name, no PID suffix. A PID-suffixed name was tried (#1584) to
|
|
412
|
+
# guard two processes scoped-running Stryker against the same solution
|
|
413
|
+
# concurrently, but reverted after review: stryker_shard_pipeline.py
|
|
414
|
+
# runs shards sequentially, not concurrently (see its own module
|
|
415
|
+
# docstring), so that race doesn't exist today — and a PID-suffixed name
|
|
416
|
+
# is invisible to csharp_stryker_net_wrapper.py's check_stale_hidden_sln()
|
|
417
|
+
# crash-recovery, which looks for the exact literal ".stryker-hidden"
|
|
418
|
+
# suffix, so a crashed run would leave an orphaned hidden .sln that
|
|
419
|
+
# existing recovery can no longer find or restore. If true concurrent
|
|
420
|
+
# execution is ever introduced, add uniqueness at the
|
|
421
|
+
# csharp_stryker_net_wrapper level (shared with
|
|
422
|
+
# csharp_stryker_net_slice_runner.py) with a matching update to
|
|
423
|
+
# check_stale_hidden_sln's stale-file scan.
|
|
424
|
+
sln_hidden = Path(f"{sln}.stryker-hidden") if sln is not None else None
|
|
425
|
+
if sln is not None and sln_hidden is not None:
|
|
426
|
+
# Crash-recovery refusal (#1598/#1584 review, item 6): the wrapper's
|
|
427
|
+
# own main() and csharp_stryker_net_slice_runner.py both call this
|
|
428
|
+
# before hide_sln — a stale hidden .sln left over from a prior crash
|
|
429
|
+
# is exactly what the fixed (no-PID) hidden-filename choice above is
|
|
430
|
+
# meant to make findable by this check. This caller never wired it
|
|
431
|
+
# in, silently skipping the recovery it depends on.
|
|
432
|
+
stale_err = wrapper.check_stale_hidden_sln(sln, sln_hidden)
|
|
433
|
+
if stale_err is not None:
|
|
434
|
+
config_path.unlink(missing_ok=True)
|
|
435
|
+
raise RuntimeError(stale_err)
|
|
436
|
+
try:
|
|
437
|
+
if sln is not None and sln_hidden is not None:
|
|
438
|
+
wrapper.hide_sln(sln, sln_hidden)
|
|
439
|
+
try:
|
|
440
|
+
# Deliberately not routed through _run_with_timeout: a Stryker
|
|
441
|
+
# timeout aborts the whole file (raise), it doesn't return a
|
|
442
|
+
# None-means-failure signal for a caller to revert-and-continue
|
|
443
|
+
# like dotnet_build/dotnet_test/git_revert/git_commit do.
|
|
444
|
+
subprocess.run(
|
|
445
|
+
wrapper.build_stryker_argv(
|
|
446
|
+
stryker_bin,
|
|
447
|
+
["--config-file", str(config_path), "--output", str(output_dir)],
|
|
448
|
+
),
|
|
449
|
+
env=env,
|
|
450
|
+
cwd=cwd,
|
|
451
|
+
check=False,
|
|
452
|
+
timeout=_STRYKER_TIMEOUT.seconds,
|
|
453
|
+
)
|
|
454
|
+
except subprocess.TimeoutExpired as exc:
|
|
455
|
+
raise RuntimeError(
|
|
456
|
+
f"error: Stryker run timed out after {_STRYKER_TIMEOUT.seconds}s for "
|
|
457
|
+
f"{source_file} (set {_STRYKER_TIMEOUT.env_var_name} to raise it)"
|
|
458
|
+
) from exc
|
|
459
|
+
finally:
|
|
460
|
+
restored = True
|
|
461
|
+
if sln is not None and sln_hidden is not None:
|
|
462
|
+
restored = wrapper.restore_sln(sln, sln_hidden)
|
|
463
|
+
config_path.unlink(missing_ok=True)
|
|
464
|
+
if not restored:
|
|
465
|
+
# Mirrors #1939's git_revert handling: a failed restore leaves
|
|
466
|
+
# the .sln hidden/renamed, which will break the next build — a
|
|
467
|
+
# fatal, working-tree-possibly-broken outcome, not a silent
|
|
468
|
+
# no-op (#1955).
|
|
469
|
+
raise mutation_kill_shared.RevertFailed(
|
|
470
|
+
f"failed to restore {sln} from {sln_hidden} after the "
|
|
471
|
+
"Stryker run — the solution file is left hidden/renamed, "
|
|
472
|
+
"which will break the next build"
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
return Path(output_dir) / "reports" / "mutation-report.json"
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def extract_survivors(report_path: Path, source_file: str) -> list[dict]:
|
|
479
|
+
"""Return the surviving mutants for one source file (flattened).
|
|
480
|
+
|
|
481
|
+
Delegates grouping/extraction to :func:`mutation_report.survivors_by_mutator`.
|
|
482
|
+
"""
|
|
483
|
+
grouped = mutation_report.survivors_by_mutator(report_path, source_file)
|
|
484
|
+
return [mutant for mutants in grouped.values() for mutant in mutants]
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
# =============================================================================
|
|
488
|
+
# Verify — dotnet build/test go through subprocess here; git_revert/
|
|
489
|
+
# git_reset_and_revert/git_commit are imported from mutation_kill_shared.py
|
|
490
|
+
# (#1583) rather than defined in this section.
|
|
491
|
+
# =============================================================================
|
|
492
|
+
def dotnet_build(targets: Sequence[str], *, cwd: Path | None = None) -> bool:
|
|
493
|
+
"""Build every configured test-project. False if any target fails or
|
|
494
|
+
times out."""
|
|
495
|
+
for target in targets:
|
|
496
|
+
result = _run_with_timeout(
|
|
497
|
+
["dotnet", "build", target, "-c", "Debug", "--nologo"],
|
|
498
|
+
_BUILD_TIMEOUT,
|
|
499
|
+
operation_label=f"dotnet build for {target}",
|
|
500
|
+
capture_output=True,
|
|
501
|
+
text=True,
|
|
502
|
+
cwd=cwd,
|
|
503
|
+
)
|
|
504
|
+
if result is None or result.returncode != 0:
|
|
505
|
+
return False
|
|
506
|
+
return True
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
# Capital-F "Failed:" only — a per-assembly `dotnet test` summary line, NOT
|
|
510
|
+
# the newer SDK's trailing lowercase rollup ("Test summary: ... failed: 0 ...").
|
|
511
|
+
# Kept case-sensitive (no re.IGNORECASE) deliberately: matching the rollup
|
|
512
|
+
# line too would double-count every assembly's failures on top of the
|
|
513
|
+
# rollup's own total (#1598/#1584 review, item 12 — see
|
|
514
|
+
# test_sum_failed_counts_is_case_sensitive_not_double_counting_the_rollup_line
|
|
515
|
+
# for a fixture that actually distinguishes the two, unlike a fixture where
|
|
516
|
+
# both a correct and a buggy (case-insensitive) scan sum to 0).
|
|
517
|
+
_FAILED_COUNT_RE = re.compile(r"Failed:\s*(\d+)")
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def _sum_failed_counts(text: str) -> int:
|
|
521
|
+
"""Sum every capital-F "Failed: N" occurrence in ``text``.
|
|
522
|
+
|
|
523
|
+
Accumulates, not overwrites: a multi-assembly `dotnet test` run prints
|
|
524
|
+
one "Failed: N" line per assembly, and a last-match-wins scan would let
|
|
525
|
+
an early assembly's failures be silently masked by a later, clean
|
|
526
|
+
assembly's "Failed: 0" line (#1598).
|
|
527
|
+
"""
|
|
528
|
+
return sum(int(m.group(1)) for m in _FAILED_COUNT_RE.finditer(text))
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def dotnet_test(
|
|
532
|
+
targets: Sequence[str], test_filter: str, *, cwd: Path | None = None
|
|
533
|
+
) -> bool:
|
|
534
|
+
"""Run the scoped test filter across every test-project. False on any
|
|
535
|
+
non-zero exit, reported failure, or timeout."""
|
|
536
|
+
for target in targets:
|
|
537
|
+
result = _run_with_timeout(
|
|
538
|
+
[
|
|
539
|
+
"dotnet",
|
|
540
|
+
"test",
|
|
541
|
+
target,
|
|
542
|
+
"-c",
|
|
543
|
+
"Debug",
|
|
544
|
+
"--no-build",
|
|
545
|
+
"--filter",
|
|
546
|
+
f"FullyQualifiedName~{test_filter}",
|
|
547
|
+
],
|
|
548
|
+
_TEST_TIMEOUT,
|
|
549
|
+
operation_label=f"dotnet test for {target}",
|
|
550
|
+
capture_output=True,
|
|
551
|
+
text=True,
|
|
552
|
+
cwd=cwd,
|
|
553
|
+
)
|
|
554
|
+
if result is None:
|
|
555
|
+
return False
|
|
556
|
+
if result.returncode != 0 or _sum_failed_counts(result.stdout + result.stderr) > 0:
|
|
557
|
+
return False
|
|
558
|
+
return True
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def _commit_message(
|
|
562
|
+
round_num: int,
|
|
563
|
+
source_file: str,
|
|
564
|
+
survivors: int,
|
|
565
|
+
new_methods: str,
|
|
566
|
+
*,
|
|
567
|
+
generator_label: str | None = None,
|
|
568
|
+
label_override: str | None = None,
|
|
569
|
+
) -> str:
|
|
570
|
+
count = count_methods(new_methods)
|
|
571
|
+
# Whitespace-collapsed the same way append_generator_trailer sanitizes
|
|
572
|
+
# generator_label (#1607): source_file is caller-supplied, and a value
|
|
573
|
+
# containing a newline could otherwise forge an extra "Generator:"
|
|
574
|
+
# trailer line into the commit message.
|
|
575
|
+
safe_source_file = " ".join(str(source_file).split())
|
|
576
|
+
message = (
|
|
577
|
+
f"test(mutation): kill round {round_num} — {safe_source_file}\n\n"
|
|
578
|
+
f"{count} new test method(s) targeting {survivors} surviving mutant(s)"
|
|
579
|
+
)
|
|
580
|
+
return mutation_safety_gate.append_generator_trailer(
|
|
581
|
+
message, generator_label, label_override=label_override
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
# =============================================================================
|
|
586
|
+
# Per-file loop — run → score → check → generate → insert → verify → commit.
|
|
587
|
+
# =============================================================================
|
|
588
|
+
@dataclass(frozen=True)
|
|
589
|
+
class RunContext:
|
|
590
|
+
"""The run-shaped inputs to :func:`run_for_file` — what to run, where, and
|
|
591
|
+
how to report progress.
|
|
592
|
+
|
|
593
|
+
Bundles the clump that already travels together at every call site
|
|
594
|
+
(``main()`` here and the ``_loop_fixture`` test helper), separating "how
|
|
595
|
+
to run this file" from ``run_for_file``'s own ``generate``/``max_rounds``
|
|
596
|
+
controls (#1561). ``generator_label``, when set, is recorded in the
|
|
597
|
+
commit message as an audit trail (e.g. distinguishing an unattended
|
|
598
|
+
``--headless`` commit from an agent-driven one).
|
|
599
|
+
|
|
600
|
+
``label_override_provider``, when set, is called with no arguments
|
|
601
|
+
before building each round's commit message; a non-``None`` result
|
|
602
|
+
replaces ``generator_label`` for that commit AND every subsequent commit
|
|
603
|
+
in this file (#1908 Step 3.2b) — the seam a model-downgrade event uses to
|
|
604
|
+
record itself in the audit trail without mutating this frozen,
|
|
605
|
+
file-level dataclass. The lifetime is sticky, not per-commit: once a
|
|
606
|
+
downgrade fires at round N, the stored label is never cleared, so every
|
|
607
|
+
later commit in this file also carries the downgrade label (with the
|
|
608
|
+
round number frozen at N, not the commit's own round) — intentional,
|
|
609
|
+
since the downgraded model really does stay in use for the rest of the
|
|
610
|
+
file. ``None`` (the default) leaves today's ``generator_label``-only
|
|
611
|
+
behavior unchanged.
|
|
612
|
+
"""
|
|
613
|
+
|
|
614
|
+
config: LoopConfig
|
|
615
|
+
test_file: Path
|
|
616
|
+
source_path: Path
|
|
617
|
+
output_dir: Path
|
|
618
|
+
stryker_bin: str = "dotnet"
|
|
619
|
+
cwd: Path | None = None
|
|
620
|
+
log: Callable[[str], None] = print
|
|
621
|
+
initial_report_path: Path | None = None
|
|
622
|
+
generator_label: str | None = None
|
|
623
|
+
label_override_provider: Callable[[], str | None] | None = None
|
|
624
|
+
# #2030 stop controls, both default-off: with these None the loop's stop
|
|
625
|
+
# behavior is byte-identical to pre-#2030. target_honest_score is the
|
|
626
|
+
# Phase-0 mutation target Phase 8 gates on; min_kills_per_round is the
|
|
627
|
+
# marginal-yield floor (>=1 absolute kills, 0<v<1 a fraction of the
|
|
628
|
+
# round's starting survivors).
|
|
629
|
+
target_honest_score: float | None = None
|
|
630
|
+
min_kills_per_round: float | None = None
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def _score_round(
|
|
634
|
+
round_num: int,
|
|
635
|
+
source_file: str,
|
|
636
|
+
ctx: RunContext,
|
|
637
|
+
*,
|
|
638
|
+
prev_survivor_count: int | None,
|
|
639
|
+
) -> tuple[list[dict], int] | None:
|
|
640
|
+
"""Score one round: baseline-seed-or-scoped-run → survivor extraction →
|
|
641
|
+
log → stop-checks.
|
|
642
|
+
|
|
643
|
+
Returns ``(survivors, survivor_count)`` to continue the round, or
|
|
644
|
+
``None`` when the file is done — no survivors, or no improvement over
|
|
645
|
+
the previous round.
|
|
646
|
+
"""
|
|
647
|
+
if ctx.initial_report_path is not None and round_num == 1:
|
|
648
|
+
report_path = Path(ctx.initial_report_path)
|
|
649
|
+
else:
|
|
650
|
+
report_path = run_scoped_stryker(
|
|
651
|
+
ctx.config,
|
|
652
|
+
source_file,
|
|
653
|
+
output_dir=ctx.output_dir,
|
|
654
|
+
stryker_bin=ctx.stryker_bin,
|
|
655
|
+
cwd=ctx.cwd,
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
survivors = extract_survivors(report_path, source_file)
|
|
659
|
+
survivor_count = len(survivors)
|
|
660
|
+
# File-scoped, not score_report(): a baseline-seeded round 1 report can
|
|
661
|
+
# cover multiple files (#1545) — score_report() would leak another
|
|
662
|
+
# file's score into this line.
|
|
663
|
+
summary = mutation_report.score_report_for_file(report_path, source_file)
|
|
664
|
+
ctx.log(
|
|
665
|
+
f" round {round_num}: honest={summary.honest_score:.1f}% "
|
|
666
|
+
f"survivors={survivor_count}"
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
# Mirrors the Python loop's equivalent guard (#1359/#1606): mutation_report's
|
|
670
|
+
# own documented contract is to return a fully-zeroed ScoreSummary (never
|
|
671
|
+
# raise) for an absent/empty report or an unmatched file key, and
|
|
672
|
+
# run_scoped_stryker runs Stryker with check=False — a crashed Stryker
|
|
673
|
+
# process, or a scoped-config file-key mismatch, can therefore yield
|
|
674
|
+
# survivor_count == 0 here with zero mutants ever actually having run.
|
|
675
|
+
# Without this check that would be indistinguishable from a genuine
|
|
676
|
+
# "no survivors — done" convergence.
|
|
677
|
+
total_mutants = summary.killed + summary.survived + summary.timeout + summary.no_coverage
|
|
678
|
+
if total_mutants == 0:
|
|
679
|
+
ctx.log(
|
|
680
|
+
" zero mutants generated — this is NOT convergence. Stryker "
|
|
681
|
+
"produced no results at all for this file (a crashed run, or a "
|
|
682
|
+
"scoped-config file-key mismatch). Stopping without declaring "
|
|
683
|
+
"survivors == 0 (#1606)."
|
|
684
|
+
)
|
|
685
|
+
return None
|
|
686
|
+
|
|
687
|
+
decision = stop_reason(
|
|
688
|
+
survivor_count,
|
|
689
|
+
prev_survivor_count,
|
|
690
|
+
honest_score=summary.honest_score,
|
|
691
|
+
target_honest_score=ctx.target_honest_score,
|
|
692
|
+
min_kills_per_round=ctx.min_kills_per_round,
|
|
693
|
+
)
|
|
694
|
+
if decision is not None:
|
|
695
|
+
# A non-terminal decision is the #2030 marginal-yield floor: the round
|
|
696
|
+
# DID make progress, the file may still be below target, and whether
|
|
697
|
+
# another round is worth its price is the operator's call. Prefixing it
|
|
698
|
+
# distinctly is what keeps it from reading as a convergence stop in the
|
|
699
|
+
# run log — the agent layer routes a YIELD FLOOR line to Phase 5's
|
|
700
|
+
# existing [c]ontinue / [r]etry / [w]aive / [q]uit prompt.
|
|
701
|
+
ctx.log(f" {decision}" if decision.terminal else f" YIELD FLOOR — {decision}")
|
|
702
|
+
return None
|
|
703
|
+
return survivors, survivor_count
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
def _revert_or_raise(ctx: RunContext, reason: str, *, after_commit: bool = False) -> None:
|
|
707
|
+
"""Revert ``ctx.test_file``, raising if the revert itself fails.
|
|
708
|
+
|
|
709
|
+
``after_commit=True`` routes through :func:`git_reset_and_revert`
|
|
710
|
+
(unstage + checkout), not plain :func:`git_revert` — ``git_commit``
|
|
711
|
+
already staged ``ctx.test_file`` before the commit attempt failed, so a
|
|
712
|
+
plain checkout alone would restore from that still-mutated index, not
|
|
713
|
+
HEAD (#1598/#1584 review). A revert that itself fails is fatal: it
|
|
714
|
+
leaves the working tree in an unknown, possibly-mutated state, so this
|
|
715
|
+
raises rather than letting the loop continue silently.
|
|
716
|
+
"""
|
|
717
|
+
revert_ok = (
|
|
718
|
+
git_reset_and_revert(ctx.test_file, cwd=ctx.cwd)
|
|
719
|
+
if after_commit
|
|
720
|
+
else git_revert(ctx.test_file, cwd=ctx.cwd)
|
|
721
|
+
)
|
|
722
|
+
if not revert_ok:
|
|
723
|
+
raise mutation_kill_shared.RevertFailed(
|
|
724
|
+
f"revert failed for {ctx.test_file} after {reason} — the "
|
|
725
|
+
"working tree is left in an unknown state (mutated test "
|
|
726
|
+
"content may still be on disk, uncommitted)"
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
def _verify_and_commit(
|
|
731
|
+
round_num: int,
|
|
732
|
+
source_file: str,
|
|
733
|
+
survivor_count: int,
|
|
734
|
+
new_methods: str,
|
|
735
|
+
ctx: RunContext,
|
|
736
|
+
) -> int | None:
|
|
737
|
+
"""Build → scoped test → commit-on-green / revert-on-failure.
|
|
738
|
+
|
|
739
|
+
Returns ``survivor_count`` on a successful commit, or ``None`` when the
|
|
740
|
+
round is abandoned (build failure, test failure, or commit failure —
|
|
741
|
+
each reverted via :func:`_revert_or_raise`, which raises if the revert
|
|
742
|
+
itself fails).
|
|
743
|
+
"""
|
|
744
|
+
if not dotnet_build(dotnet_test_project_targets(ctx.config), cwd=ctx.cwd):
|
|
745
|
+
ctx.log(" build failed — reverting")
|
|
746
|
+
_revert_or_raise(ctx, "a failed build")
|
|
747
|
+
return None
|
|
748
|
+
if not dotnet_test(dotnet_test_project_targets(ctx.config), ctx.test_file.stem, cwd=ctx.cwd):
|
|
749
|
+
ctx.log(" tests failed — reverting")
|
|
750
|
+
_revert_or_raise(ctx, "a failed test run")
|
|
751
|
+
return None
|
|
752
|
+
|
|
753
|
+
ctx.log(" green — committing")
|
|
754
|
+
label_override = (
|
|
755
|
+
ctx.label_override_provider() if ctx.label_override_provider is not None else None
|
|
756
|
+
)
|
|
757
|
+
committed = git_commit(
|
|
758
|
+
_commit_message(
|
|
759
|
+
round_num,
|
|
760
|
+
source_file,
|
|
761
|
+
survivor_count,
|
|
762
|
+
new_methods,
|
|
763
|
+
generator_label=ctx.generator_label,
|
|
764
|
+
label_override=label_override,
|
|
765
|
+
),
|
|
766
|
+
ctx.test_file,
|
|
767
|
+
cwd=ctx.cwd,
|
|
768
|
+
)
|
|
769
|
+
if not committed:
|
|
770
|
+
# A failed/timed-out commit is a round failure, not a silent
|
|
771
|
+
# success — without this check the loop would advance to the next
|
|
772
|
+
# round believing this one landed, while the test methods sit
|
|
773
|
+
# uncommitted (and possibly still staged) on disk (#1598).
|
|
774
|
+
ctx.log(" commit failed — reverting")
|
|
775
|
+
_revert_or_raise(ctx, "a failed commit", after_commit=True)
|
|
776
|
+
return None
|
|
777
|
+
return survivor_count
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
def _run_round(
|
|
781
|
+
round_num: int,
|
|
782
|
+
source_file: str,
|
|
783
|
+
ctx: RunContext,
|
|
784
|
+
generate: Generator,
|
|
785
|
+
*,
|
|
786
|
+
prev_survivor_count: int | None,
|
|
787
|
+
) -> int | None:
|
|
788
|
+
"""Run one round: score (via :func:`_score_round`) → generate → insert →
|
|
789
|
+
verify → commit (via :func:`_verify_and_commit`).
|
|
790
|
+
|
|
791
|
+
Returns this round's survivor count (to seed the next round's
|
|
792
|
+
no-improvement check), or ``None`` when the file is done.
|
|
793
|
+
"""
|
|
794
|
+
scored = _score_round(round_num, source_file, ctx, prev_survivor_count=prev_survivor_count)
|
|
795
|
+
if scored is None:
|
|
796
|
+
return None
|
|
797
|
+
survivors, survivor_count = scored
|
|
798
|
+
|
|
799
|
+
# Read once and thread the text through both the generation prompt and
|
|
800
|
+
# the insertion helper below — apply_generated_methods (and, inside it,
|
|
801
|
+
# insert_before_class_close) accept the already-read text instead of
|
|
802
|
+
# re-reading test_file from disk a second and third time (#1584).
|
|
803
|
+
test_text = ctx.test_file.read_text(encoding="utf-8")
|
|
804
|
+
new_methods = generate(
|
|
805
|
+
source_file,
|
|
806
|
+
survivors,
|
|
807
|
+
ctx.source_path.read_text(encoding="utf-8"),
|
|
808
|
+
test_text,
|
|
809
|
+
)
|
|
810
|
+
|
|
811
|
+
# No test_text= here on purpose (#1598/#1584 review, item 7):
|
|
812
|
+
# apply_generated_methods always does its own fresh read now, shared
|
|
813
|
+
# between its duplicate-name check and the write — this pre-generation
|
|
814
|
+
# `test_text` snapshot (read above, before the possibly multi-minute
|
|
815
|
+
# `generate()` call) is only for the generation prompt.
|
|
816
|
+
outcome = apply_generated_methods(ctx.test_file, new_methods)
|
|
817
|
+
if not outcome.inserted:
|
|
818
|
+
ctx.log(f" not inserted ({outcome.reason}) — stopping")
|
|
819
|
+
return None
|
|
820
|
+
|
|
821
|
+
return _verify_and_commit(round_num, source_file, survivor_count, new_methods, ctx)
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def run_for_file(
|
|
825
|
+
source_file: str,
|
|
826
|
+
ctx: RunContext,
|
|
827
|
+
*,
|
|
828
|
+
generate: Generator,
|
|
829
|
+
max_rounds: int = 5,
|
|
830
|
+
) -> None:
|
|
831
|
+
"""Drive the deterministic survivor-kill loop for one source file.
|
|
832
|
+
|
|
833
|
+
``generate`` is the sole non-deterministic step: given survivors + context
|
|
834
|
+
it returns the raw new-method text. Everything else — scoped run, scoring,
|
|
835
|
+
duplicate/insert guards, build/test verification, revert-on-failure,
|
|
836
|
+
commit-on-green, and the no-improvement stop — is mechanical, driven one
|
|
837
|
+
round at a time by :func:`_run_round`.
|
|
838
|
+
|
|
839
|
+
A failed revert (after a build failure, a test failure, or a failed
|
|
840
|
+
commit) is fatal: it raises :class:`mutation_kill_shared.RevertFailed`
|
|
841
|
+
rather than returning silently, because a revert that can't be verified
|
|
842
|
+
as having succeeded means the working tree is left in an unknown,
|
|
843
|
+
possibly-mutated state —
|
|
844
|
+
silently continuing to the next round or file would risk committing
|
|
845
|
+
mutated content later under a false assumption of a clean tree (#1598).
|
|
846
|
+
A failed commit itself is also a round failure, not a silent success: it
|
|
847
|
+
is reverted (unstage + restore, via :func:`git_reset_and_revert`) and the
|
|
848
|
+
round stops without advancing.
|
|
849
|
+
"""
|
|
850
|
+
prev_survivor_count: int | None = None
|
|
851
|
+
for round_num in range(1, max_rounds + 1):
|
|
852
|
+
prev_survivor_count = _run_round(
|
|
853
|
+
round_num, source_file, ctx, generate, prev_survivor_count=prev_survivor_count
|
|
854
|
+
)
|
|
855
|
+
if prev_survivor_count is None:
|
|
856
|
+
return
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
if __name__ == "__main__":
|
|
860
|
+
# Register this already-executing module under its real import name
|
|
861
|
+
# before mutation_kill_headless's `from mutation_kill_loop import ...`
|
|
862
|
+
# runs — otherwise Python can't find "mutation_kill_loop" in sys.modules
|
|
863
|
+
# (this process is running as "__main__") and re-executes this file from
|
|
864
|
+
# disk as a second, distinct module object (two RunContext/LoopConfig
|
|
865
|
+
# class identities). Aliasing first makes the import a cache hit.
|
|
866
|
+
sys.modules.setdefault("mutation_kill_loop", sys.modules[__name__])
|
|
867
|
+
import mutation_kill_headless
|
|
868
|
+
|
|
869
|
+
sys.exit(mutation_kill_headless.main())
|