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,743 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""mutation_report.py — parse a native mutation report and compute scores.
|
|
3
|
+
|
|
4
|
+
Generic, stdlib-only, cross-platform (macOS, Linux, Windows). Carries no repo-specific literal —
|
|
5
|
+
project names, controller names, and test-library names live in the report
|
|
6
|
+
being parsed, never in this module.
|
|
7
|
+
|
|
8
|
+
Two native report shapes are supported, normalized to the same internal
|
|
9
|
+
``{"files": {"<path>": {"mutants": [...]}}}`` dict before scoring:
|
|
10
|
+
|
|
11
|
+
- Stryker / Stryker.NET's own ``mutation-report.json`` — read directly via
|
|
12
|
+
the path-based functions (``score_report``, ``survivors_by_mutator``, …).
|
|
13
|
+
- mutmut's ``mutmut junitxml`` output — mutmut has no JSON report of its
|
|
14
|
+
own; ``parse_mutmut_junitxml`` converts it into the same internal shape so
|
|
15
|
+
every downstream function (scoring, survivor extraction, file discovery)
|
|
16
|
+
works identically regardless of which tool produced the data.
|
|
17
|
+
|
|
18
|
+
Two scores are computed side by side:
|
|
19
|
+
|
|
20
|
+
- **honest score** = Killed / (Killed + Survived + NoCoverage)
|
|
21
|
+
A Timeout is *not* a demonstrated kill — the test never asserted anything;
|
|
22
|
+
the mutant merely ran too long. Counting Timeouts as kills inflates the
|
|
23
|
+
score, so the honest score excludes them from the numerator entirely.
|
|
24
|
+
|
|
25
|
+
- **reported score** = (Killed + Timeout) / (Killed + Survived + Timeout + NoCoverage)
|
|
26
|
+
The score Stryker itself reports, kept alongside for parity so the gap
|
|
27
|
+
between "what the tool claims" and "what the tests actually prove" is
|
|
28
|
+
visible rather than hidden.
|
|
29
|
+
|
|
30
|
+
Both scores are expressed as percentages in [0, 100]. An absent or empty
|
|
31
|
+
report yields zeroed scores rather than raising.
|
|
32
|
+
|
|
33
|
+
Stryker.NET mutant statuses: Killed, Survived, Timeout, NoCoverage, Ignored,
|
|
34
|
+
CompileError. Only the first four participate in scoring; Ignored and
|
|
35
|
+
CompileError are excluded from both numerator and denominator.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
import json
|
|
41
|
+
import xml.etree.ElementTree as ET
|
|
42
|
+
from dataclasses import dataclass
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
|
|
45
|
+
# Stryker.NET mutant-status vocabulary — the single source of truth for these
|
|
46
|
+
# literals across the whole pipeline. Sibling modules import these constants
|
|
47
|
+
# rather than re-typing the raw strings, so the status vocabulary lives in one
|
|
48
|
+
# place (AC4). Only these four participate in the score denominators.
|
|
49
|
+
STATUS_KILLED = "Killed"
|
|
50
|
+
STATUS_SURVIVED = "Survived"
|
|
51
|
+
STATUS_TIMEOUT = "Timeout"
|
|
52
|
+
STATUS_NO_COVERAGE = "NoCoverage"
|
|
53
|
+
|
|
54
|
+
# Mutant dict key carrying Stryker's "this mutant sits in module-init code,
|
|
55
|
+
# not per-test code" flag. Centralized here alongside the status vocabulary
|
|
56
|
+
# above so the literal isn't repeated across the functions that read it.
|
|
57
|
+
MUTANT_STATIC_KEY = "static"
|
|
58
|
+
|
|
59
|
+
# Fixed reason string attached to every accepted-static-survivor entry
|
|
60
|
+
# (see ``accepted_static_survivors``) — a static-flagged Survived mutant is
|
|
61
|
+
# not equivalent and not fixed; it is deliberately deferred because killing
|
|
62
|
+
# it would require a full-suite re-run rather than a single-mutant one.
|
|
63
|
+
ACCEPTED_STATIC_REASON = "static — verification requires a full-suite re-run"
|
|
64
|
+
|
|
65
|
+
# ``status`` value for every accepted-static-survivor entry — a sibling
|
|
66
|
+
# constant to ``ACCEPTED_STATIC_REASON`` so the entry's ``"status"`` field is
|
|
67
|
+
# never a bare string literal, matching every other status value in this
|
|
68
|
+
# module (``STATUS_KILLED`` etc. above).
|
|
69
|
+
ACCEPTED_STATIC_ENTRY_STATUS = "accepted"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(frozen=True)
|
|
73
|
+
class ScoreSummary:
|
|
74
|
+
"""Both scores plus the two counts that distinguish them.
|
|
75
|
+
|
|
76
|
+
``honest_score`` and ``reported_score`` are percentages in [0, 100].
|
|
77
|
+
``timeout`` and ``no_coverage`` are raw mutant counts, surfaced so a
|
|
78
|
+
caller can show *why* the two scores diverge.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
killed: int
|
|
82
|
+
survived: int
|
|
83
|
+
timeout: int
|
|
84
|
+
no_coverage: int
|
|
85
|
+
honest_score: float
|
|
86
|
+
reported_score: float
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def load_report(report_path: Path) -> dict:
|
|
90
|
+
"""Return the parsed report, or ``{}`` when the file is absent, empty,
|
|
91
|
+
malformed JSON, or valid JSON that isn't a dict (e.g. a report file
|
|
92
|
+
containing ``[]`` or ``42``) — never raises."""
|
|
93
|
+
path = Path(report_path)
|
|
94
|
+
if not path.exists():
|
|
95
|
+
return {}
|
|
96
|
+
text = path.read_text(encoding="utf-8").strip()
|
|
97
|
+
if not text:
|
|
98
|
+
return {}
|
|
99
|
+
try:
|
|
100
|
+
parsed = json.loads(text)
|
|
101
|
+
except json.JSONDecodeError:
|
|
102
|
+
return {}
|
|
103
|
+
return parsed if isinstance(parsed, dict) else {}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _iter_mutants(data: dict):
|
|
107
|
+
"""Yield every mutant dict across all files in a parsed report."""
|
|
108
|
+
for info in data.get("files", {}).values():
|
|
109
|
+
yield from info.get("mutants", [])
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _find_file_entry(data: dict, file_path: str) -> tuple[str, dict] | None:
|
|
113
|
+
"""Return the matched ``(report_key, info)`` pair for one file from an
|
|
114
|
+
already-parsed report dict.
|
|
115
|
+
|
|
116
|
+
Matches ``file_path`` against report keys by exact match first, then by
|
|
117
|
+
basename, so a caller can pass either the full report key or just the
|
|
118
|
+
filename. Returns ``None`` when no file matches. ``report_key`` is the
|
|
119
|
+
key exactly as the report emits it — a caller that only passed a
|
|
120
|
+
basename (or an absolute path) gets back the matched report key, not an
|
|
121
|
+
echo of its own input.
|
|
122
|
+
"""
|
|
123
|
+
files = data.get("files", {})
|
|
124
|
+
|
|
125
|
+
info = files.get(file_path)
|
|
126
|
+
if info is not None:
|
|
127
|
+
return file_path, info
|
|
128
|
+
|
|
129
|
+
target = Path(file_path).name
|
|
130
|
+
for key, value in files.items():
|
|
131
|
+
if Path(key).name == target:
|
|
132
|
+
return key, value
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _find_file_info(data: dict, file_path: str) -> dict | None:
|
|
137
|
+
"""Return one file's report entry (the ``{"mutants": [...]}`` dict) from
|
|
138
|
+
an already-parsed report dict.
|
|
139
|
+
|
|
140
|
+
Matches ``file_path`` against report keys by exact match first, then by
|
|
141
|
+
basename, so a caller can pass either the full report key or just the
|
|
142
|
+
filename. Returns ``None`` when no file matches. Thin wrapper around
|
|
143
|
+
``_find_file_entry`` that discards the matched key — callers that need
|
|
144
|
+
the matched key too (e.g. ``_accepted_static_survivors_from_data``) call
|
|
145
|
+
``_find_file_entry`` directly instead.
|
|
146
|
+
"""
|
|
147
|
+
entry = _find_file_entry(data, file_path)
|
|
148
|
+
return entry[1] if entry is not None else None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _tally(mutants: list[dict]) -> ScoreSummary:
|
|
152
|
+
"""Compute the honest and reported scores for a flat list of mutant
|
|
153
|
+
dicts (one file's ``mutants`` list, or every file's combined)."""
|
|
154
|
+
killed = survived = timeout = no_coverage = 0
|
|
155
|
+
for mutant in mutants:
|
|
156
|
+
status = mutant.get("status")
|
|
157
|
+
if status == STATUS_KILLED:
|
|
158
|
+
killed += 1
|
|
159
|
+
elif status == STATUS_SURVIVED:
|
|
160
|
+
survived += 1
|
|
161
|
+
elif status == STATUS_TIMEOUT:
|
|
162
|
+
timeout += 1
|
|
163
|
+
elif status == STATUS_NO_COVERAGE:
|
|
164
|
+
no_coverage += 1
|
|
165
|
+
|
|
166
|
+
honest_denom = killed + survived + no_coverage
|
|
167
|
+
reported_denom = killed + survived + timeout + no_coverage
|
|
168
|
+
|
|
169
|
+
honest_score = (killed / honest_denom * 100) if honest_denom else 0.0
|
|
170
|
+
reported_score = (
|
|
171
|
+
(killed + timeout) / reported_denom * 100 if reported_denom else 0.0
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
return ScoreSummary(
|
|
175
|
+
killed=killed,
|
|
176
|
+
survived=survived,
|
|
177
|
+
timeout=timeout,
|
|
178
|
+
no_coverage=no_coverage,
|
|
179
|
+
honest_score=honest_score,
|
|
180
|
+
reported_score=reported_score,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _score_data(data: dict) -> ScoreSummary:
|
|
185
|
+
"""Compute the honest and reported scores for an already-parsed report
|
|
186
|
+
dict (the ``{"files": {...}}`` shape shared by every supported tool)."""
|
|
187
|
+
return _tally(list(_iter_mutants(data)))
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def score_report(report_path: Path) -> ScoreSummary:
|
|
191
|
+
"""Compute the honest and reported scores for a Stryker-shaped mutation
|
|
192
|
+
report on disk.
|
|
193
|
+
|
|
194
|
+
Returns a fully zeroed :class:`ScoreSummary` when the report is absent
|
|
195
|
+
or empty — never raises for a missing file.
|
|
196
|
+
"""
|
|
197
|
+
return _score_data(load_report(report_path))
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _score_data_for_file(data: dict, file_path: str) -> ScoreSummary:
|
|
201
|
+
"""Compute the honest and reported scores for one file within an
|
|
202
|
+
already-parsed report dict.
|
|
203
|
+
|
|
204
|
+
Matches ``file_path`` the same way ``_find_file_info`` does (exact key,
|
|
205
|
+
then basename). Returns a fully zeroed :class:`ScoreSummary` when the
|
|
206
|
+
file is not matched — never raises.
|
|
207
|
+
"""
|
|
208
|
+
info = _find_file_info(data, file_path)
|
|
209
|
+
if info is None:
|
|
210
|
+
return _tally([])
|
|
211
|
+
return _tally(info.get("mutants", []))
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def score_report_for_file(report_path: Path, file_path: str) -> ScoreSummary:
|
|
215
|
+
"""Compute the honest and reported scores for one file within a
|
|
216
|
+
Stryker-shaped mutation report on disk.
|
|
217
|
+
|
|
218
|
+
Matches ``file_path`` against report keys by exact match first, then by
|
|
219
|
+
basename (same rule as ``survivors_by_mutator``). Returns a fully zeroed
|
|
220
|
+
:class:`ScoreSummary` when the report is absent/empty or the file is not
|
|
221
|
+
matched — never raises.
|
|
222
|
+
"""
|
|
223
|
+
return _score_data_for_file(load_report(report_path), file_path)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _survivors_from_data(
|
|
227
|
+
data: dict, file_path: str, skip_static: bool = False
|
|
228
|
+
) -> dict[str, list[dict]]:
|
|
229
|
+
"""Return the Survived mutants for one source file, grouped by mutator
|
|
230
|
+
name, from an already-parsed report dict.
|
|
231
|
+
|
|
232
|
+
Matches ``file_path`` against report keys by exact match first, then by
|
|
233
|
+
basename, so a caller can pass either the full report key or just the
|
|
234
|
+
filename. Only ``Survived`` mutants are returned — killed, timed-out, and
|
|
235
|
+
uncovered mutants are not survivors. Returns ``{}`` when the file is not
|
|
236
|
+
in the report or has no survivors.
|
|
237
|
+
|
|
238
|
+
When ``skip_static`` is ``True``, a mutant is excluded only when it
|
|
239
|
+
carries ``static: true``; absence of the key (or ``static: false``)
|
|
240
|
+
never excludes it. This function stays a pure data transform — the CLI
|
|
241
|
+
wrapper owns any user-facing "skip is inapplicable" notice.
|
|
242
|
+
"""
|
|
243
|
+
info = _find_file_info(data, file_path)
|
|
244
|
+
if info is None:
|
|
245
|
+
return {}
|
|
246
|
+
|
|
247
|
+
mutants = info.get("mutants", [])
|
|
248
|
+
if skip_static:
|
|
249
|
+
mutants = [
|
|
250
|
+
mutant for mutant in mutants if mutant.get(MUTANT_STATIC_KEY) is not True
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
grouped: dict[str, list[dict]] = {}
|
|
254
|
+
for mutant in mutants:
|
|
255
|
+
if mutant.get("status") != STATUS_SURVIVED:
|
|
256
|
+
continue
|
|
257
|
+
mutator = mutant.get("mutatorName", "")
|
|
258
|
+
grouped.setdefault(mutator, []).append(mutant)
|
|
259
|
+
return grouped
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def survivors_by_mutator(
|
|
263
|
+
report_path: Path, file_path: str, skip_static: bool = False
|
|
264
|
+
) -> dict[str, list[dict]]:
|
|
265
|
+
"""Return the Survived mutants for one source file, grouped by mutator
|
|
266
|
+
name, from a Stryker-shaped mutation report on disk.
|
|
267
|
+
|
|
268
|
+
See ``_survivors_from_data`` for the ``skip_static`` behavior.
|
|
269
|
+
"""
|
|
270
|
+
return _survivors_from_data(
|
|
271
|
+
load_report(report_path), file_path, skip_static=skip_static
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def survivors_by_mutator_from_data(
|
|
276
|
+
data: dict, file_path: str, skip_static: bool = False
|
|
277
|
+
) -> dict[str, list[dict]]:
|
|
278
|
+
"""Return the Survived mutants for one source file, grouped by mutator
|
|
279
|
+
name, from an already-parsed report dict.
|
|
280
|
+
|
|
281
|
+
Thin public wrapper around ``_survivors_from_data`` — lets a caller that
|
|
282
|
+
already holds a parsed report (e.g. the CLI's ``--skip-static`` path,
|
|
283
|
+
which loads the report once to run ``has_static_field``/
|
|
284
|
+
``is_file_in_report`` diagnostics before computing survivors) reuse that
|
|
285
|
+
dict instead of triggering a second, redundant ``load_report`` call. See
|
|
286
|
+
``_survivors_from_data`` for the ``skip_static`` behavior.
|
|
287
|
+
"""
|
|
288
|
+
return _survivors_from_data(data, file_path, skip_static=skip_static)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def has_static_field(data: dict, file_path: str) -> bool:
|
|
292
|
+
"""Return whether any mutant in the matched file's list carries a
|
|
293
|
+
``"static"`` key, from an already-parsed report dict.
|
|
294
|
+
|
|
295
|
+
Matches ``file_path`` the same way ``_find_file_info`` does (exact key,
|
|
296
|
+
then basename). Returns ``False`` uniformly both when the matched file
|
|
297
|
+
has no mutant carrying a ``"static"`` key and when ``file_path`` isn't
|
|
298
|
+
present in the report at all (``_find_file_info`` returns ``None``) —
|
|
299
|
+
the two cases are not distinguished by this function's return value; use
|
|
300
|
+
``is_file_in_report`` alongside it to tell them apart.
|
|
301
|
+
|
|
302
|
+
Data-based (takes an already-parsed ``data`` dict) rather than
|
|
303
|
+
``Path``-based. This module's public API is not uniformly ``Path``-based
|
|
304
|
+
— ``parse_mutmut_junitxml``/``score_mutmut_junitxml``/
|
|
305
|
+
``survivors_from_mutmut_junitxml`` already take raw input — so this is
|
|
306
|
+
an established shape, not a new one. It exists to serve the CLI's
|
|
307
|
+
pre-dispatch diagnostic, which already holds the parsed report; a
|
|
308
|
+
``Path``-based signature would force the ``--skip-static`` path to
|
|
309
|
+
parse the report an extra, redundant time instead of reusing the dict
|
|
310
|
+
it already loaded once (see ``survivors_by_mutator_from_data``, which
|
|
311
|
+
the same CLI path reuses this dict with, for the same reason).
|
|
312
|
+
"""
|
|
313
|
+
info = _find_file_info(data, file_path)
|
|
314
|
+
if info is None:
|
|
315
|
+
return False
|
|
316
|
+
return any(MUTANT_STATIC_KEY in mutant for mutant in info.get("mutants", []))
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def is_file_in_report(data: dict, file_path: str) -> bool:
|
|
320
|
+
"""Return whether ``file_path`` matches any report key, from an
|
|
321
|
+
already-parsed report dict.
|
|
322
|
+
|
|
323
|
+
Matches the same way ``_find_file_info`` does (exact key, then
|
|
324
|
+
basename). Lets a caller distinguish "file matched but has no static
|
|
325
|
+
field" from "file never matched any report key" — the two causes
|
|
326
|
+
``has_static_field`` collapses into a single ``False`` return value.
|
|
327
|
+
"""
|
|
328
|
+
return _find_file_info(data, file_path) is not None
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _resolve_survivor_line(mutant: dict) -> int | None:
|
|
332
|
+
"""Resolve one mutant's source line from its ``location.start.line``
|
|
333
|
+
field, or ``None`` when that shape is missing or malformed.
|
|
334
|
+
|
|
335
|
+
Never raises: a missing/non-dict ``location``, a missing/non-dict
|
|
336
|
+
``start``, or a ``line`` that isn't a plain ``int`` (``None``, a
|
|
337
|
+
non-int value, or a ``bool`` — ``bool`` is an ``int`` subclass in
|
|
338
|
+
Python but is never a real line number) all resolve to ``None``. This
|
|
339
|
+
matches the module's never-raise-on-bad-input posture (see the module
|
|
340
|
+
docstring, ``load_report``'s "never raises" note, and
|
|
341
|
+
``parse_mutmut_junitxml``'s "malformed input returns empty rather than
|
|
342
|
+
raising"). Extracted from ``_survivors_by_line_from_data``'s original
|
|
343
|
+
inline unwrap so ``_accepted_static_survivors_from_data`` can reuse the
|
|
344
|
+
same hardened logic instead of duplicating it.
|
|
345
|
+
"""
|
|
346
|
+
loc = mutant.get("location")
|
|
347
|
+
loc = loc if isinstance(loc, dict) else {}
|
|
348
|
+
start = loc.get("start")
|
|
349
|
+
start = start if isinstance(start, dict) else {}
|
|
350
|
+
line = start.get("line")
|
|
351
|
+
if not isinstance(line, int) or isinstance(line, bool):
|
|
352
|
+
return None
|
|
353
|
+
return line
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _survivors_by_line_from_data(
|
|
357
|
+
data: dict, file_path: str, skip_static: bool = False
|
|
358
|
+
) -> dict:
|
|
359
|
+
"""Return the Survived mutants for one source file, clustered by source
|
|
360
|
+
line, from an already-parsed report dict.
|
|
361
|
+
|
|
362
|
+
Matches ``file_path`` against report keys the same way
|
|
363
|
+
``_survivors_from_data`` does (via ``_find_file_info`` — exact key, then
|
|
364
|
+
basename). Only ``Survived`` mutants participate; a mutant of any other
|
|
365
|
+
status (Killed, Timeout, NoCoverage, ...) sharing a line with survivors
|
|
366
|
+
never appears in the result.
|
|
367
|
+
|
|
368
|
+
``skip_static`` is accepted for API symmetry with ``_survivors_from_data``
|
|
369
|
+
and so a caller (the CLI wrapper) can pass ``--skip-static
|
|
370
|
+
--survivors-by-line`` without being rejected — but it is a **no-op** on
|
|
371
|
+
the returned clusters (#1948, Option A): a static-flagged survivor still
|
|
372
|
+
counts toward its line's cluster weight and ranking exactly as it would
|
|
373
|
+
without the flag. A static mutant is still real evidence about that
|
|
374
|
+
line's mutation density, even when generation (a decision made
|
|
375
|
+
downstream, not here) skips writing a test for it — the two are
|
|
376
|
+
deliberately decoupled. A caller that needs to know whether a specific
|
|
377
|
+
survivor within a cluster should be skipped for generation checks that
|
|
378
|
+
survivor's own ``static`` field directly (already present on the raw
|
|
379
|
+
mutant dict); this function does not filter or annotate it further.
|
|
380
|
+
|
|
381
|
+
Return shape: ``{"clusters": [{"line": int, "survivors": [dict, ...]}, ...],
|
|
382
|
+
"unclustered": [dict, ...]}``.
|
|
383
|
+
|
|
384
|
+
- ``clusters`` groups survivors by ``mutant["location"]["start"]["line"]``
|
|
385
|
+
and is sorted by ``len(survivors)`` descending; ties are broken by
|
|
386
|
+
``line`` ascending.
|
|
387
|
+
- ``unclustered`` holds every survivor whose line isn't a resolvable
|
|
388
|
+
plain ``int`` — ``None`` (e.g. mutmut's no-resolvable-line case), a
|
|
389
|
+
non-int value, a ``bool`` (``bool`` is an ``int`` subclass in Python
|
|
390
|
+
but is never a real line number), or a missing/non-dict ``location``/
|
|
391
|
+
``start`` value of any shape — such a survivor never forms or joins a
|
|
392
|
+
cluster. This matches the module's never-raise-on-bad-input posture
|
|
393
|
+
(see the module docstring, ``load_report``'s "never raises" note, and
|
|
394
|
+
``parse_mutmut_junitxml``'s "malformed input returns empty rather
|
|
395
|
+
than raising"): no ``location``/``start``/``line`` shape, however
|
|
396
|
+
malformed, raises ``AttributeError``/``TypeError`` here.
|
|
397
|
+
- Returns ``{"clusters": [], "unclustered": []}`` when the file is not in
|
|
398
|
+
the report or has no survivors.
|
|
399
|
+
"""
|
|
400
|
+
# skip_static is deliberately never read below — see the docstring's
|
|
401
|
+
# "no-op on the returned clusters" note. It is accepted purely so the
|
|
402
|
+
# CLI can pass it through instead of rejecting the combination.
|
|
403
|
+
info = _find_file_info(data, file_path)
|
|
404
|
+
if info is None:
|
|
405
|
+
return {"clusters": [], "unclustered": []}
|
|
406
|
+
|
|
407
|
+
by_line: dict[int, list[dict]] = {}
|
|
408
|
+
unclustered: list[dict] = []
|
|
409
|
+
for mutant in info.get("mutants", []):
|
|
410
|
+
if mutant.get("status") != STATUS_SURVIVED:
|
|
411
|
+
continue
|
|
412
|
+
line = _resolve_survivor_line(mutant)
|
|
413
|
+
if line is None:
|
|
414
|
+
unclustered.append(mutant)
|
|
415
|
+
continue
|
|
416
|
+
by_line.setdefault(line, []).append(mutant)
|
|
417
|
+
|
|
418
|
+
clusters = [
|
|
419
|
+
{"line": line, "survivors": survivors} for line, survivors in by_line.items()
|
|
420
|
+
]
|
|
421
|
+
clusters.sort(key=lambda cluster: (-len(cluster["survivors"]), cluster["line"]))
|
|
422
|
+
|
|
423
|
+
return {"clusters": clusters, "unclustered": unclustered}
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def survivors_by_line(
|
|
427
|
+
report_path: Path, file_path: str, skip_static: bool = False
|
|
428
|
+
) -> dict:
|
|
429
|
+
"""Return the Survived mutants for one source file, clustered by source
|
|
430
|
+
line, from a Stryker-shaped mutation report on disk.
|
|
431
|
+
|
|
432
|
+
See ``_survivors_by_line_from_data`` for the return shape, ordering rule,
|
|
433
|
+
and the ``skip_static`` no-op-on-clustering behavior (#1948).
|
|
434
|
+
"""
|
|
435
|
+
return _survivors_by_line_from_data(
|
|
436
|
+
load_report(report_path), file_path, skip_static=skip_static
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def survivor_lines(report_path: Path, file_path: str) -> list[int]:
|
|
441
|
+
"""Return the sorted, de-duplicated source lines carrying a Survived
|
|
442
|
+
mutant for one file — the set a #2031 confirm run needs to re-test.
|
|
443
|
+
|
|
444
|
+
Built on :func:`survivors_by_line` rather than re-deriving the clustering,
|
|
445
|
+
so the confirm scope and the generation targets can never disagree about
|
|
446
|
+
which lines had survivors. ``unclustered`` survivors (no resolvable line)
|
|
447
|
+
are deliberately excluded: they cannot be expressed as a line-scoped
|
|
448
|
+
mutate span, and a caller that cannot scope them must fall back to a full
|
|
449
|
+
scoped run rather than silently dropping them — see
|
|
450
|
+
:func:`confirm_scope_is_complete`.
|
|
451
|
+
"""
|
|
452
|
+
clustered = survivors_by_line(report_path, file_path)
|
|
453
|
+
return sorted({c["line"] for c in clustered.get("clusters", [])})
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def confirm_scope_is_complete(report_path: Path, file_path: str) -> bool:
|
|
457
|
+
"""Whether every Survived mutant for ``file_path`` has a resolvable line.
|
|
458
|
+
|
|
459
|
+
A confirm run narrowed to line spans can only re-test mutants whose line
|
|
460
|
+
is known. If any survivor is ``unclustered``, narrowing would silently
|
|
461
|
+
stop re-testing it, and its absence from the confirm result would read as
|
|
462
|
+
"killed" once spliced. Callers must run the ordinary full scoped round in
|
|
463
|
+
that case: correctness first, saving second.
|
|
464
|
+
"""
|
|
465
|
+
clustered = survivors_by_line(report_path, file_path)
|
|
466
|
+
return not clustered.get("unclustered")
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def splice_confirm_over_prior(prior: dict, confirm: dict, file_path: str) -> dict:
|
|
470
|
+
"""Overlay a survivor-scoped confirm result onto the prior round's report.
|
|
471
|
+
|
|
472
|
+
A confirm run mutates only the lines that had survivors last round, so its
|
|
473
|
+
report is a strict subset of the file's mutant set. Reporting it alone
|
|
474
|
+
would understate the file (every mutant outside the confirm scope would
|
|
475
|
+
vanish, inflating or deflating the score depending on their status). This
|
|
476
|
+
splices instead — the same pattern Phase 8 (``/quality-targets-converge``,
|
|
477
|
+
#1208) uses to report a whole-repo score from freshly-measured changed
|
|
478
|
+
files over a persisted baseline.
|
|
479
|
+
|
|
480
|
+
Mutants are matched by ``id`` where both sides carry one, falling back to
|
|
481
|
+
``(line, mutatorName, replacement)`` — Stryker ids are stable within a run
|
|
482
|
+
but not guaranteed across runs. A confirm mutant replaces its prior
|
|
483
|
+
counterpart; a prior mutant the confirm run did not cover is carried
|
|
484
|
+
forward unchanged; a confirm mutant with no prior counterpart is added.
|
|
485
|
+
|
|
486
|
+
Never raises on malformed input, matching this module's standing posture:
|
|
487
|
+
an unusable side is treated as absent rather than fatal.
|
|
488
|
+
"""
|
|
489
|
+
|
|
490
|
+
def _key(mutant: dict) -> tuple:
|
|
491
|
+
if not isinstance(mutant, dict):
|
|
492
|
+
return ("", "", "", "")
|
|
493
|
+
mid = mutant.get("id")
|
|
494
|
+
if isinstance(mid, (str, int)) and not isinstance(mid, bool):
|
|
495
|
+
return ("id", str(mid))
|
|
496
|
+
return (
|
|
497
|
+
"shape",
|
|
498
|
+
str(_resolve_survivor_line(mutant)),
|
|
499
|
+
str(mutant.get("mutatorName")),
|
|
500
|
+
str(mutant.get("replacement")),
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
prior_info = _find_file_info(prior if isinstance(prior, dict) else {}, file_path)
|
|
504
|
+
confirm_info = _find_file_info(
|
|
505
|
+
confirm if isinstance(confirm, dict) else {}, file_path
|
|
506
|
+
)
|
|
507
|
+
if not isinstance(prior_info, dict):
|
|
508
|
+
return confirm if isinstance(confirm, dict) else {}
|
|
509
|
+
prior_mutants = prior_info.get("mutants")
|
|
510
|
+
prior_mutants = prior_mutants if isinstance(prior_mutants, list) else []
|
|
511
|
+
confirm_mutants = (
|
|
512
|
+
confirm_info.get("mutants") if isinstance(confirm_info, dict) else None
|
|
513
|
+
)
|
|
514
|
+
confirm_mutants = confirm_mutants if isinstance(confirm_mutants, list) else []
|
|
515
|
+
|
|
516
|
+
merged = {_key(m): m for m in prior_mutants if isinstance(m, dict)}
|
|
517
|
+
for mutant in confirm_mutants:
|
|
518
|
+
if isinstance(mutant, dict):
|
|
519
|
+
merged[_key(mutant)] = mutant
|
|
520
|
+
|
|
521
|
+
spliced = json.loads(json.dumps(prior))
|
|
522
|
+
spliced_info = _find_file_info(spliced, file_path)
|
|
523
|
+
if isinstance(spliced_info, dict):
|
|
524
|
+
spliced_info["mutants"] = list(merged.values())
|
|
525
|
+
return spliced
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def _accepted_static_survivors_from_data(
|
|
529
|
+
data: dict, file_path: str, *, skip_static_active: bool
|
|
530
|
+
) -> list[dict]:
|
|
531
|
+
"""Return each Survived mutant carrying ``static: true`` for one source
|
|
532
|
+
file as an accepted-survivor entry, from an already-parsed report dict.
|
|
533
|
+
|
|
534
|
+
``skip_static_active`` is the required evidence that a static-flagged
|
|
535
|
+
survivor was *deliberately* deferred, not merely present. Stryker's
|
|
536
|
+
``static: true`` flag alone proves nothing about operator intent — the
|
|
537
|
+
deliberateness lives one layer up, in whether the CLI's own
|
|
538
|
+
``--skip-static`` flag was active for this run. When
|
|
539
|
+
``skip_static_active`` is ``False``, this function returns ``[]``
|
|
540
|
+
immediately, before any other computation: a run where the skip was
|
|
541
|
+
never active has no accepted survivors to report, regardless of how many
|
|
542
|
+
mutants carry ``static: true``. When ``True``, behavior matches the
|
|
543
|
+
unconditional version this function used to be.
|
|
544
|
+
|
|
545
|
+
Matches ``file_path`` against report keys the same way
|
|
546
|
+
``_survivors_from_data`` does (via ``_find_file_entry`` — exact key, then
|
|
547
|
+
basename). Only a mutant with ``status == STATUS_SURVIVED`` *and*
|
|
548
|
+
``mutant.get(MUTANT_STATIC_KEY) is True`` qualifies — a
|
|
549
|
+
Killed/Timeout/NoCoverage/CompileError mutant carrying ``static: true``
|
|
550
|
+
is never returned, and a Survived mutant without the static flag is
|
|
551
|
+
never returned either.
|
|
552
|
+
|
|
553
|
+
Each entry has the shape ``{"id": ..., "file": str, "line": int | None,
|
|
554
|
+
"operator": str, "status": ACCEPTED_STATIC_ENTRY_STATUS, "reason":
|
|
555
|
+
ACCEPTED_STATIC_REASON}``. ``id`` is the mutant's own ``"id"`` field
|
|
556
|
+
(``None`` when absent) — stable mutant identity, not just a file/line
|
|
557
|
+
pair. ``file`` is the *matched report key* (from ``_find_file_entry``),
|
|
558
|
+
not an echo of the caller's ``file_path`` argument — a caller that
|
|
559
|
+
passed a basename gets back the full report key. ``line`` is resolved
|
|
560
|
+
via ``_resolve_survivor_line`` (``None`` for any missing/malformed
|
|
561
|
+
location/start/line shape — never raises).
|
|
562
|
+
|
|
563
|
+
Returns ``[]`` when the file is not in the report or has no matching
|
|
564
|
+
survivors.
|
|
565
|
+
"""
|
|
566
|
+
if not skip_static_active:
|
|
567
|
+
return []
|
|
568
|
+
|
|
569
|
+
entry = _find_file_entry(data, file_path)
|
|
570
|
+
if entry is None:
|
|
571
|
+
return []
|
|
572
|
+
matched_key, info = entry
|
|
573
|
+
|
|
574
|
+
return [
|
|
575
|
+
{
|
|
576
|
+
"id": mutant.get("id"),
|
|
577
|
+
"file": matched_key,
|
|
578
|
+
"line": _resolve_survivor_line(mutant),
|
|
579
|
+
"operator": mutant.get("mutatorName", ""),
|
|
580
|
+
"status": ACCEPTED_STATIC_ENTRY_STATUS,
|
|
581
|
+
"reason": ACCEPTED_STATIC_REASON,
|
|
582
|
+
}
|
|
583
|
+
for mutant in info.get("mutants", [])
|
|
584
|
+
if mutant.get("status") == STATUS_SURVIVED
|
|
585
|
+
and mutant.get(MUTANT_STATIC_KEY) is True
|
|
586
|
+
]
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def accepted_static_survivors(
|
|
590
|
+
report_path: Path, file_path: str, *, skip_static_active: bool
|
|
591
|
+
) -> list[dict]:
|
|
592
|
+
"""Return each Survived mutant carrying ``static: true`` for one source
|
|
593
|
+
file, as accepted-survivor entries, from a Stryker-shaped mutation
|
|
594
|
+
report on disk.
|
|
595
|
+
|
|
596
|
+
See ``_accepted_static_survivors_from_data`` for the return shape,
|
|
597
|
+
matching rule, and the required ``skip_static_active`` gating.
|
|
598
|
+
"""
|
|
599
|
+
return _accepted_static_survivors_from_data(
|
|
600
|
+
load_report(report_path), file_path, skip_static_active=skip_static_active
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def accepted_static_survivors_from_data(
|
|
605
|
+
data: dict, file_path: str, *, skip_static_active: bool
|
|
606
|
+
) -> list[dict]:
|
|
607
|
+
"""Return each Survived mutant carrying ``static: true`` for one source
|
|
608
|
+
file, as accepted-survivor entries, from an already-parsed report dict.
|
|
609
|
+
|
|
610
|
+
Thin public wrapper around ``_accepted_static_survivors_from_data``,
|
|
611
|
+
mirroring the established pair-per-function convention set by
|
|
612
|
+
``survivors_by_mutator``/``survivors_by_mutator_from_data``. Called by
|
|
613
|
+
the CLI's ``--accepted-static-survivors`` branch, which already holds a
|
|
614
|
+
parsed report (loaded once to run
|
|
615
|
+
``_maybe_warn_skip_static_inapplicable`` first) and reuses that dict
|
|
616
|
+
here instead of triggering a second, redundant ``load_report`` call.
|
|
617
|
+
"""
|
|
618
|
+
return _accepted_static_survivors_from_data(
|
|
619
|
+
data, file_path, skip_static_active=skip_static_active
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def _files_with_status_from_data(data: dict, status: str) -> list[str]:
|
|
624
|
+
"""Return the sorted report file keys having >= 1 mutant of ``status``,
|
|
625
|
+
from an already-parsed report dict.
|
|
626
|
+
|
|
627
|
+
The report keys are the file identifiers exactly as the tool emits them
|
|
628
|
+
(relative source paths); the caller decides how to interpret them.
|
|
629
|
+
"""
|
|
630
|
+
return sorted(
|
|
631
|
+
key
|
|
632
|
+
for key, info in data.get("files", {}).items()
|
|
633
|
+
if any(m.get("status") == status for m in info.get("mutants", []))
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def _files_with_status(report_path: Path, status: str) -> list[str]:
|
|
638
|
+
"""Return the sorted report file keys having >= 1 mutant of ``status``,
|
|
639
|
+
from a Stryker-shaped mutation report on disk. Returns ``[]`` for an
|
|
640
|
+
absent/empty report — never raises."""
|
|
641
|
+
return _files_with_status_from_data(load_report(report_path), status)
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def files_with_survivors(report_path: Path) -> list[str]:
|
|
645
|
+
"""Return the sorted report file keys that have >= 1 Survived mutant.
|
|
646
|
+
|
|
647
|
+
This is the single source of truth for survivor-file discovery — sibling
|
|
648
|
+
modules call it instead of re-walking the report (AC4).
|
|
649
|
+
"""
|
|
650
|
+
return _files_with_status(report_path, STATUS_SURVIVED)
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def files_with_timeouts(report_path: Path) -> list[str]:
|
|
654
|
+
"""Return the sorted report file keys that have >= 1 Timeout mutant.
|
|
655
|
+
|
|
656
|
+
This is the single source of truth for timeout-file discovery — sibling
|
|
657
|
+
modules call it instead of re-walking the report (AC4).
|
|
658
|
+
"""
|
|
659
|
+
return _files_with_status(report_path, STATUS_TIMEOUT)
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
# =============================================================================
|
|
663
|
+
# mutmut — no native JSON report; junitxml is its only structured output.
|
|
664
|
+
# =============================================================================
|
|
665
|
+
def _testcase_to_entry(testcase) -> tuple[str, dict] | None:
|
|
666
|
+
"""Derive the status and build the mutant dict for one ``<testcase>``
|
|
667
|
+
element from ``mutmut junitxml`` output.
|
|
668
|
+
|
|
669
|
+
Returns ``None`` when the testcase carries no ``file`` attribute — there
|
|
670
|
+
is nothing to key it into the ``files`` dict by, so it is skipped.
|
|
671
|
+
|
|
672
|
+
mutmut marks a survived mutant with a ``<failure>`` child
|
|
673
|
+
(``message="bad_survived"``) and a timed-out mutant with a distinct
|
|
674
|
+
``<error>`` child (``message="bad_timeout"``, ``error_type="timeout"``) —
|
|
675
|
+
the two are never conflated, so a real Timeout is never miscounted as a
|
|
676
|
+
Survived. Every other ``<testcase>`` — including a suspicious-but-ignored
|
|
677
|
+
mutant under mutmut's default ``suspicious_policy="ignore"`` — is Killed;
|
|
678
|
+
mutmut's default reporting granularity cannot distinguish "cleanly
|
|
679
|
+
killed" from "suspicious but ignored" any further than that.
|
|
680
|
+
"""
|
|
681
|
+
file_key = testcase.get("file")
|
|
682
|
+
if not file_key:
|
|
683
|
+
return None
|
|
684
|
+
if testcase.find("failure") is not None:
|
|
685
|
+
status = STATUS_SURVIVED
|
|
686
|
+
elif testcase.find("error") is not None:
|
|
687
|
+
status = STATUS_TIMEOUT
|
|
688
|
+
else:
|
|
689
|
+
status = STATUS_KILLED
|
|
690
|
+
line = testcase.get("line")
|
|
691
|
+
mutant = {
|
|
692
|
+
"id": testcase.get("name", "?"),
|
|
693
|
+
"mutatorName": "mutmut",
|
|
694
|
+
"status": status,
|
|
695
|
+
"location": {
|
|
696
|
+
"start": {"line": int(line) if line and line.isdigit() else None}
|
|
697
|
+
},
|
|
698
|
+
"replacement": "",
|
|
699
|
+
}
|
|
700
|
+
return file_key, mutant
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
def parse_mutmut_junitxml(xml_text: str) -> dict:
|
|
704
|
+
"""Convert ``mutmut junitxml`` output into the internal ``{"files": {...}}``
|
|
705
|
+
shape every scoring/survivor function above already consumes.
|
|
706
|
+
|
|
707
|
+
mutmut has no NoCoverage concept (it always runs the full scoped test
|
|
708
|
+
command against every mutant), so that count is always zero here.
|
|
709
|
+
|
|
710
|
+
Malformed or empty input returns an empty ``{"files": {}}`` rather than
|
|
711
|
+
raising — callers see it as an empty report, same as a missing file.
|
|
712
|
+
"""
|
|
713
|
+
files: dict[str, dict] = {}
|
|
714
|
+
if not xml_text.strip():
|
|
715
|
+
return {"files": files}
|
|
716
|
+
try:
|
|
717
|
+
root = ET.fromstring(xml_text)
|
|
718
|
+
except ET.ParseError:
|
|
719
|
+
return {"files": files}
|
|
720
|
+
|
|
721
|
+
for testcase in root.iter("testcase"):
|
|
722
|
+
entry = _testcase_to_entry(testcase)
|
|
723
|
+
if entry is None:
|
|
724
|
+
continue
|
|
725
|
+
file_key, mutant = entry
|
|
726
|
+
files.setdefault(file_key, {"mutants": []})["mutants"].append(mutant)
|
|
727
|
+
|
|
728
|
+
return {"files": files}
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
def score_mutmut_junitxml(xml_text: str) -> ScoreSummary:
|
|
732
|
+
"""Compute the honest and reported scores directly from ``mutmut
|
|
733
|
+
junitxml`` output (no report file on disk needed)."""
|
|
734
|
+
return _score_data(parse_mutmut_junitxml(xml_text))
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
def survivors_from_mutmut_junitxml(
|
|
738
|
+
xml_text: str, file_path: str
|
|
739
|
+
) -> dict[str, list[dict]]:
|
|
740
|
+
"""Return the Survived mutants for one file from ``mutmut junitxml``
|
|
741
|
+
output, grouped by mutator name (always ``"mutmut"`` — the tool names no
|
|
742
|
+
per-mutation operator the way Stryker/pitest do)."""
|
|
743
|
+
return _survivors_from_data(parse_mutmut_junitxml(xml_text), file_path)
|