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,958 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Mechanical pre-phase for `agents/test-review.md` (#2169 Step 2.2).
|
|
3
|
+
|
|
4
|
+
`test-review.md` (Step 2.1, #2169) annotates each of its own `## Detect`
|
|
5
|
+
checks as `[MECHANICAL]` (an explicit per-language detection signature or
|
|
6
|
+
grep/threshold rule) or `[JUDGMENT]` (requires semantic reasoning about
|
|
7
|
+
intent). This script computes every `[MECHANICAL]` check that is cheap to
|
|
8
|
+
run as a script, for one test file at a time, so the agent cites raw counts
|
|
9
|
+
instead of re-deriving them in prose:
|
|
10
|
+
|
|
11
|
+
- tests with no assertion call — `error` (matches test-review.md's Severity
|
|
12
|
+
Anchors table)
|
|
13
|
+
- missing `await` on async test bodies (JS/TS/C#/Java) — `warning`
|
|
14
|
+
- mocks/stubs created without a reset/clear call, or a same-shaped
|
|
15
|
+
re-instantiation/re-initialization in `[SetUp]`/`[TestInitialize]`/
|
|
16
|
+
`@BeforeEach`/`@BeforeAll`, in the same file — `warning`
|
|
17
|
+
- unstubbed clock/RNG/timer access (JS/TS/C#/Java), suppressed when a
|
|
18
|
+
fake-timer/injected-clock marker (`jest.useFakeTimers()`, an injected
|
|
19
|
+
`IClock`/`TimeProvider`, `Clock.fixed`, ...) is present in the same
|
|
20
|
+
file — `warning`
|
|
21
|
+
- reflection into private members as primary test strategy (Java/C#/
|
|
22
|
+
Python/JS/TS) — `warning`, NOT promoted to `error` by this migration
|
|
23
|
+
- the Tolerated-Deviation Hunt's >=3-artifact consolidation rule, with a
|
|
24
|
+
cheap same-line/adjacent-line ticket-reference qualifier suppressing the
|
|
25
|
+
disabled-test/aged-marker/suppressed-warning categories — `warning`
|
|
26
|
+
|
|
27
|
+
Test-region boundaries (the `it()`/`test()` call for JS/TS, the annotated
|
|
28
|
+
method for C#/Java) are found by depth-counting over a MASKED copy of the
|
|
29
|
+
file (`_mask_code`) with string/template/char literals and comments blanked
|
|
30
|
+
out first — a stray `)`/`(`/`{`/`}` inside a title, comment, or string body
|
|
31
|
+
can no longer early-close a region or drive the balance past EOF. For JS/TS,
|
|
32
|
+
the no-assertion and missing-await checks additionally never see the
|
|
33
|
+
description-string argument itself — `_js_content_slice` derives a
|
|
34
|
+
callback-only CONTENT slice from the boundary-only region, so a title like
|
|
35
|
+
`'should render'` or `'awaits the response'` can never satisfy the
|
|
36
|
+
assertion/await keyword search on its own words (see `_GATING_CATEGORIES`
|
|
37
|
+
below for why this specifically matters: no-assertion is one of the two
|
|
38
|
+
categories that sets `mechanicalFail`).
|
|
39
|
+
|
|
40
|
+
Internal-collaborator-doubling detection is NOT reimplemented here — it is
|
|
41
|
+
reused via subprocess against `skills/test-design/scripts/
|
|
42
|
+
internal_double_detector.py`, whose own docstring documents `--files` as
|
|
43
|
+
the seam built for exactly this kind of caller (the subprocess call itself
|
|
44
|
+
goes through an injectable `double_detector_runner` parameter so unit tests
|
|
45
|
+
for unrelated checks can stub it out rather than paying real spawn cost).
|
|
46
|
+
That detector has no `severity` field; its findings carry a `verdict` of
|
|
47
|
+
`"high"`, `"informational"`, or `"advisory"`. Only `verdict == "high"` is
|
|
48
|
+
translated here into this script's own `error`-severity finding — never a
|
|
49
|
+
literal `severity == "error"` check against the reused script's raw JSON,
|
|
50
|
+
since no such field exists there.
|
|
51
|
+
|
|
52
|
+
Two disclosure gaps are surfaced explicitly rather than failing open to a
|
|
53
|
+
plain clean bill:
|
|
54
|
+
|
|
55
|
+
- `internal_double_detector.py`'s own `analyze()` only scans files under a
|
|
56
|
+
`test`/`tests` path segment — narrower than this repo's own test-file
|
|
57
|
+
conventions (`*.test.*`, `*.spec.*`, `__tests__/`, see
|
|
58
|
+
`knowledge/test-file-indicators.md`). When the analyzed file's path has
|
|
59
|
+
no `test`/`tests` segment, the doubling check never runs for it; this
|
|
60
|
+
script emits a named `internal-collaborator-doubling-out-of-scope`
|
|
61
|
+
finding instead of silently reporting zero doubling findings.
|
|
62
|
+
- A subprocess spawn/timeout failure or invalid-JSON response from the
|
|
63
|
+
detector is reported as `internal-collaborator-doubling-unavailable` at
|
|
64
|
+
`warning` severity (raised from `suggestion` — a run that never happened
|
|
65
|
+
must not read the same as a clean pass).
|
|
66
|
+
|
|
67
|
+
Both gaps also set the top-level `doublingCheckRan: false` key so a
|
|
68
|
+
downstream consumer can distinguish "mechanically clean except the doubling
|
|
69
|
+
gate, which did not run" from a plain clean bill.
|
|
70
|
+
|
|
71
|
+
`mechanicalFail` is `true` ONLY when a no-assertion-test finding is present
|
|
72
|
+
OR a translated `internal-collaborator-doubling` `error` finding is
|
|
73
|
+
present. Every other finding this script emits is `warning`-severity: it is
|
|
74
|
+
reported, with counts, but never gates the qualitative pass on its own.
|
|
75
|
+
|
|
76
|
+
A file this script cannot decode/parse produces a single `parse-failure`
|
|
77
|
+
finding (no severity that participates in `mechanicalFail`) and falls
|
|
78
|
+
through as mechanically clean — this script never crashes and never
|
|
79
|
+
silently drops a file.
|
|
80
|
+
|
|
81
|
+
This script does NOT own an ambient-API marker table of its own for
|
|
82
|
+
`internal_double_detector.py`'s B2-waiver-evidence purpose — that table
|
|
83
|
+
(`_AMBIENT_API_MARKERS` in that module) stays there; see the REFACTOR note
|
|
84
|
+
at `_CLOCK_RNG_TIMER_MARKERS` below for why this script's own marker table
|
|
85
|
+
is a different list, not a duplicate.
|
|
86
|
+
|
|
87
|
+
The Tolerated-Deviation Hunt (`_check_tolerated_deviation`) runs against
|
|
88
|
+
whichever single file the CLI is given, test or non-test — this script's
|
|
89
|
+
own per-file CLI shape is unopinionated about which files it's called
|
|
90
|
+
with. Step 2.3's actual wiring (`skills/code-review/SKILL.md` step 2b)
|
|
91
|
+
calls this script only for test files in scope, so in practice the Hunt
|
|
92
|
+
currently sees test files, not the "core-flow, non-test source" scope
|
|
93
|
+
`test-review.md`'s prose describes — non-test files get no Phase 0 result
|
|
94
|
+
and fall through to that agent's own manual-judgment fallback. Extending
|
|
95
|
+
the wiring to also cover non-test core-flow files is a separate change,
|
|
96
|
+
tracked at the wiring layer (`test-review.md`'s Hunt section), not here.
|
|
97
|
+
|
|
98
|
+
Stdlib-only (ADR 0014/0015). See docs/python-hook-contract.md.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
from __future__ import annotations
|
|
102
|
+
|
|
103
|
+
import argparse
|
|
104
|
+
import json
|
|
105
|
+
import re
|
|
106
|
+
import subprocess
|
|
107
|
+
import sys
|
|
108
|
+
from collections.abc import Callable
|
|
109
|
+
from pathlib import Path
|
|
110
|
+
|
|
111
|
+
#: `internal_double_detector.py` lives under `skills/test-design/scripts/`;
|
|
112
|
+
#: this script lives under `scripts/` — both are children of the plugin
|
|
113
|
+
#: root (`plugins/dev-team/`), so `parents[1]` from this file reaches it.
|
|
114
|
+
_DETECTOR_PATH = (
|
|
115
|
+
Path(__file__).resolve().parents[1]
|
|
116
|
+
/ "skills"
|
|
117
|
+
/ "test-design"
|
|
118
|
+
/ "scripts"
|
|
119
|
+
/ "internal_double_detector.py"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
LANG_BY_EXT: dict[str, str] = {
|
|
123
|
+
".py": "python",
|
|
124
|
+
".js": "js_ts",
|
|
125
|
+
".jsx": "js_ts",
|
|
126
|
+
".mjs": "js_ts",
|
|
127
|
+
".cjs": "js_ts",
|
|
128
|
+
".ts": "js_ts",
|
|
129
|
+
".tsx": "js_ts",
|
|
130
|
+
".cs": "csharp",
|
|
131
|
+
".java": "java",
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
CONSOLIDATION_THRESHOLD = 3
|
|
135
|
+
|
|
136
|
+
#: "No specific line available" sentinel — used for parse-failure/decode
|
|
137
|
+
#: findings and translated-detector findings with no line info of their own.
|
|
138
|
+
_UNKNOWN_LINE = 1
|
|
139
|
+
|
|
140
|
+
#: Passed as `timeout=` to the `internal_double_detector.py` subprocess call.
|
|
141
|
+
_DOUBLE_DETECTOR_TIMEOUT_SECONDS = 60
|
|
142
|
+
|
|
143
|
+
#: `test`/`tests` path-segment names — mirrors `internal_double_detector.py`'s
|
|
144
|
+
#: own `_TEST_DIR_NAMES`/`_is_test_tree` scoping exactly (this is a
|
|
145
|
+
#: disclosure check on top of that scoping, not a reimplementation of the
|
|
146
|
+
#: detector itself — see the module docstring's out-of-scope paragraph).
|
|
147
|
+
_TEST_DIR_NAMES = frozenset({"test", "tests"})
|
|
148
|
+
|
|
149
|
+
# --- Generic assertion-call detector -----------------------------------------
|
|
150
|
+
|
|
151
|
+
#: "no Assert, expect, should, verify, or equivalent assertion call" —
|
|
152
|
+
#: test-review.md's own generic (language-agnostic) wording for the
|
|
153
|
+
#: no-assertion check.
|
|
154
|
+
_ASSERTION_RE = re.compile(r"\b(assert\w*|expect|should\w*|verify\w*)\b", re.IGNORECASE)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# --- Test-region extraction (brace/paren-balanced, per language) ------------
|
|
158
|
+
|
|
159
|
+
#: Matches a plain `it(`/`test(` call (with optional `.only`/`.skip`) as
|
|
160
|
+
#: well as an `it.each(<table>)(`/`test.each(<table>)(` parameterized-table
|
|
161
|
+
#: call — group 1 always captures the FINAL call's own opening paren (the
|
|
162
|
+
#: description+callback call), never the `.each` table's paren, so
|
|
163
|
+
#: extraction downstream is identical for both forms. The `.each(...)`
|
|
164
|
+
#: table itself may contain one level of nested parens (e.g. a function
|
|
165
|
+
#: call inside the table) but no more — a reasonable approximation of the
|
|
166
|
+
#: common forms, not a full parser. The leading `(?<![.\w$])` (not a plain
|
|
167
|
+
#: `\b`) excludes member-access calls like `pattern.test(value)` or
|
|
168
|
+
#: `/re/.test(s)` — ordinary RegExp usage, not a test declaration. A `\b`
|
|
169
|
+
#: alone still matches there, because `.` and `t` sit on either side of a
|
|
170
|
+
#: word boundary regardless of what the non-word character is.
|
|
171
|
+
_JS_TEST_CALL_RE = re.compile(
|
|
172
|
+
r"(?<![.\w$])(?:it|test)(?:\.only|\.skip)?"
|
|
173
|
+
r"(?:\.each\s*\((?:[^()]|\([^()]*\))*\))?"
|
|
174
|
+
r"\s*(\()"
|
|
175
|
+
)
|
|
176
|
+
_JS_ASYNC_CALLBACK_RE = re.compile(r"^\(\s*['\"`][^'\"`]*['\"`]\s*,\s*async\b")
|
|
177
|
+
|
|
178
|
+
_CSHARP_TEST_ATTR_RE = re.compile(
|
|
179
|
+
r"^[ \t]*\[(?:Test|Fact|TestMethod|Theory|TestCase|TestCaseSource)(?:\([^\]]*\))?\]",
|
|
180
|
+
re.MULTILINE,
|
|
181
|
+
)
|
|
182
|
+
_JAVA_TEST_ANNOT_RE = re.compile(r"^[ \t]*@(?:Test|ParameterizedTest)\b", re.MULTILINE)
|
|
183
|
+
_PY_TEST_DEF_RE = re.compile(r"^([ \t]*)(?:async\s+)?def\s+(test_\w+)\s*\(", re.MULTILINE)
|
|
184
|
+
|
|
185
|
+
#: Setup/init method markers — used only by `_mock_reinitialized` (Fix 4) to
|
|
186
|
+
#: find a `[SetUp]`/`[TestInitialize]`/`@BeforeEach`/`@BeforeAll` method's
|
|
187
|
+
#: own body, never treated as a test method for any other check.
|
|
188
|
+
_CSHARP_SETUP_ATTR_RE = re.compile(r"^[ \t]*\[(?:SetUp|TestInitialize)\]", re.MULTILINE)
|
|
189
|
+
_JAVA_SETUP_ANNOT_RE = re.compile(r"^[ \t]*@(?:BeforeEach|BeforeAll)\b", re.MULTILINE)
|
|
190
|
+
_SETUP_ANNOT_RE: dict[str, re.Pattern] = {"csharp": _CSHARP_SETUP_ATTR_RE, "java": _JAVA_SETUP_ANNOT_RE}
|
|
191
|
+
|
|
192
|
+
_CSHARP_ASYNC_TASK_RE = re.compile(r"\basync\s+Task\b")
|
|
193
|
+
_JAVA_FUTURE_TYPE_RE = re.compile(r"\b(?:Future|CompletableFuture)\b")
|
|
194
|
+
_JAVA_FUTURE_RESOLVED_RE = re.compile(r"\.(?:get|join)\s*\(")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class ParseFailure(Exception):
|
|
198
|
+
"""A test-region boundary (parens/braces) never closed before EOF.
|
|
199
|
+
|
|
200
|
+
Raised internally by the region-extraction helpers and caught once, at
|
|
201
|
+
the top of `analyze_file`, which turns it into a `parse-failure`
|
|
202
|
+
finding — this exception must never propagate out of `analyze_file`."""
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _line_no(text: str, idx: int) -> int:
|
|
206
|
+
return text.count("\n", 0, idx) + 1
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _consume_escape(out: list[str], text: str, j: int) -> int:
|
|
210
|
+
"""Blank a backslash-escape pair (`text[j:j + 2]`, `text[j] == "\\\\"`)
|
|
211
|
+
in `out`, one character at a time so an embedded newline is preserved
|
|
212
|
+
(never blanked) and line numbers stay aligned with `text`. Returns the
|
|
213
|
+
index just past the pair. Split out of `_mask_code`'s quoted-string
|
|
214
|
+
branch to keep that branch's `while` body at one level of nesting."""
|
|
215
|
+
if text[j] != "\n":
|
|
216
|
+
out[j] = " "
|
|
217
|
+
if text[j + 1] != "\n":
|
|
218
|
+
out[j + 1] = " "
|
|
219
|
+
return j + 2
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _mask_code(text: str) -> str:
|
|
223
|
+
"""Same-length copy of `text` with string/template/char literals and
|
|
224
|
+
`//`/`/* */` comments blanked to spaces (newlines preserved), so
|
|
225
|
+
paren/brace depth-counting and top-level-comma scanning never trip on
|
|
226
|
+
a stray `)`/`(`/`{`/`}`/`,` sitting inside a title, comment, or string
|
|
227
|
+
body (Fix 1). Character offsets and line numbers are identical to
|
|
228
|
+
`text` — callers index into the ORIGINAL text using positions computed
|
|
229
|
+
against this masked copy.
|
|
230
|
+
|
|
231
|
+
Limitation: a JS/TS template literal's `${...}` interpolation is
|
|
232
|
+
masked along with the rest of the template, not treated as live code —
|
|
233
|
+
blanket-masking the whole template is the safer default (a stray brace
|
|
234
|
+
inside an interpolation could otherwise miscount), at the cost of not
|
|
235
|
+
recognizing real code inside `${}`. Out of scope for this fix pass."""
|
|
236
|
+
out = list(text)
|
|
237
|
+
i, n = 0, len(text)
|
|
238
|
+
while i < n:
|
|
239
|
+
two = text[i : i + 2]
|
|
240
|
+
if two == "//":
|
|
241
|
+
j = i
|
|
242
|
+
while j < n and text[j] != "\n":
|
|
243
|
+
out[j] = " "
|
|
244
|
+
j += 1
|
|
245
|
+
i = j
|
|
246
|
+
elif two == "/*":
|
|
247
|
+
end = text.find("*/", i + 2)
|
|
248
|
+
j_end = end + 2 if end != -1 else n
|
|
249
|
+
for j in range(i, j_end):
|
|
250
|
+
if text[j] != "\n":
|
|
251
|
+
out[j] = " "
|
|
252
|
+
i = j_end
|
|
253
|
+
elif text[i] in ("'", '"', "`"):
|
|
254
|
+
quote = text[i]
|
|
255
|
+
out[i] = " "
|
|
256
|
+
j = i + 1
|
|
257
|
+
while j < n:
|
|
258
|
+
if text[j] == "\\" and j + 1 < n:
|
|
259
|
+
j = _consume_escape(out, text, j)
|
|
260
|
+
continue
|
|
261
|
+
closing = text[j] == quote
|
|
262
|
+
if text[j] != "\n":
|
|
263
|
+
out[j] = " "
|
|
264
|
+
j += 1
|
|
265
|
+
if closing:
|
|
266
|
+
break
|
|
267
|
+
i = j
|
|
268
|
+
else:
|
|
269
|
+
i += 1
|
|
270
|
+
return "".join(out)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _matching_close_index(text: str, masked: str, open_idx: int, open_ch: str, close_ch: str) -> int | None:
|
|
274
|
+
"""Index into `text` of the character matching `text[open_idx]` (which
|
|
275
|
+
must be `open_ch`), found by depth-counting over `masked` (same length
|
|
276
|
+
as `text`, string/template/char literals and comments already blanked
|
|
277
|
+
there — Fix 1). Returns `None` if depth never returns to 0 before EOF."""
|
|
278
|
+
depth = 0
|
|
279
|
+
i = open_idx
|
|
280
|
+
n = len(masked)
|
|
281
|
+
while i < n:
|
|
282
|
+
c = masked[i]
|
|
283
|
+
if c == open_ch:
|
|
284
|
+
depth += 1
|
|
285
|
+
elif c == close_ch:
|
|
286
|
+
depth -= 1
|
|
287
|
+
if depth == 0:
|
|
288
|
+
return i
|
|
289
|
+
i += 1
|
|
290
|
+
return None
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _js_content_slice(region: str, masked_region: str) -> str:
|
|
294
|
+
"""Slice of `region` (a full `it()`/`test()` call region, its own
|
|
295
|
+
parens included) starting just after the description-string
|
|
296
|
+
argument's closing quote and the following top-level comma — the
|
|
297
|
+
callback argument only, title/description stripped out (Fix 1:
|
|
298
|
+
`_check_no_assertion`/`_check_missing_await` must never match a
|
|
299
|
+
keyword sitting in the title itself, e.g. `'should render'` or
|
|
300
|
+
`'awaits the response'`). Falls back to the full `region` when no
|
|
301
|
+
leading string-literal argument is found (e.g. an `it(name, fn)` form
|
|
302
|
+
using a variable title) or no top-level comma follows it."""
|
|
303
|
+
n = len(region)
|
|
304
|
+
j = 1
|
|
305
|
+
while j < n and region[j] in " \t\r\n":
|
|
306
|
+
j += 1
|
|
307
|
+
if j >= n or region[j] not in "'\"`":
|
|
308
|
+
return region
|
|
309
|
+
quote = region[j]
|
|
310
|
+
end = j + 1
|
|
311
|
+
closed = False
|
|
312
|
+
while end < n:
|
|
313
|
+
if region[end] == "\\" and end + 1 < n:
|
|
314
|
+
end += 2
|
|
315
|
+
continue
|
|
316
|
+
if region[end] == quote:
|
|
317
|
+
end += 1
|
|
318
|
+
closed = True
|
|
319
|
+
break
|
|
320
|
+
end += 1
|
|
321
|
+
if not closed:
|
|
322
|
+
return region
|
|
323
|
+
depth = 0
|
|
324
|
+
k = end
|
|
325
|
+
while k < n:
|
|
326
|
+
c = masked_region[k]
|
|
327
|
+
if c in "([{":
|
|
328
|
+
depth += 1
|
|
329
|
+
elif c in ")]}":
|
|
330
|
+
if depth == 0:
|
|
331
|
+
return region
|
|
332
|
+
depth -= 1
|
|
333
|
+
elif c == "," and depth == 0:
|
|
334
|
+
return region[k + 1 :]
|
|
335
|
+
k += 1
|
|
336
|
+
return region
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _js_ts_test_regions(text: str, masked: str) -> list[dict]:
|
|
340
|
+
"""`{"line", "is_async", "content"}` for each `it()`/`test()` call
|
|
341
|
+
(including `.each` parameterized-table forms) — shared by
|
|
342
|
+
`_check_no_assertion` and `_check_missing_await` (Fix 11) so the
|
|
343
|
+
region/content boundaries are computed exactly once per file.
|
|
344
|
+
`is_async` is read off the FULL region (it needs the title text
|
|
345
|
+
structurally present to match); `content` is the title-stripped
|
|
346
|
+
slice used for keyword search."""
|
|
347
|
+
out = []
|
|
348
|
+
for m in _JS_TEST_CALL_RE.finditer(masked):
|
|
349
|
+
open_idx = m.start(1)
|
|
350
|
+
close_idx = _matching_close_index(text, masked, open_idx, "(", ")")
|
|
351
|
+
if close_idx is None:
|
|
352
|
+
raise ParseFailure("unbalanced parens in an it()/test() call")
|
|
353
|
+
region = text[open_idx : close_idx + 1]
|
|
354
|
+
masked_region = masked[open_idx : close_idx + 1]
|
|
355
|
+
is_async = bool(_JS_ASYNC_CALLBACK_RE.match(region))
|
|
356
|
+
content = _js_content_slice(region, masked_region)
|
|
357
|
+
out.append({"line": _line_no(text, m.start()), "is_async": is_async, "content": content})
|
|
358
|
+
return out
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _annotated_test_regions(text: str, masked: str, annot_re: re.Pattern) -> list[dict]:
|
|
362
|
+
"""`{"line", "signature", "body"}` for each method matching `annot_re`
|
|
363
|
+
(a `[Test]`/`@Test`-style attribute/annotation, or a
|
|
364
|
+
`[SetUp]`/`@BeforeEach`-style one when reused by `_mock_reinitialized`)
|
|
365
|
+
— `signature` is the annotation through the parameter list, `body` is
|
|
366
|
+
the brace-balanced method body. Boundaries are found via `masked`
|
|
367
|
+
(Fix 1) so a stray brace/paren inside a body's own string literal can't
|
|
368
|
+
drive the balance past EOF."""
|
|
369
|
+
out = []
|
|
370
|
+
for m in annot_re.finditer(masked):
|
|
371
|
+
paren_idx = masked.find("(", m.end())
|
|
372
|
+
if paren_idx == -1:
|
|
373
|
+
raise ParseFailure("no parameter list found after a test attribute")
|
|
374
|
+
close_paren = _matching_close_index(text, masked, paren_idx, "(", ")")
|
|
375
|
+
if close_paren is None:
|
|
376
|
+
raise ParseFailure("unbalanced parens in an annotated test method's signature")
|
|
377
|
+
brace_idx = masked.find("{", close_paren)
|
|
378
|
+
if brace_idx == -1:
|
|
379
|
+
raise ParseFailure("no method body found after a test attribute")
|
|
380
|
+
close_brace = _matching_close_index(text, masked, brace_idx, "{", "}")
|
|
381
|
+
if close_brace is None:
|
|
382
|
+
raise ParseFailure("unbalanced braces in an annotated test method's body")
|
|
383
|
+
signature = text[m.start() : brace_idx]
|
|
384
|
+
body = text[brace_idx : close_brace + 1]
|
|
385
|
+
out.append({"line": _line_no(text, m.start()), "signature": signature, "body": body})
|
|
386
|
+
return out
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _extract_regions(text: str, masked: str, lang: str) -> list[dict]:
|
|
390
|
+
"""Single per-language test-region extraction pass, shared by
|
|
391
|
+
`_check_no_assertion` and `_check_missing_await` (Fix 11)."""
|
|
392
|
+
if lang == "js_ts":
|
|
393
|
+
return _js_ts_test_regions(text, masked)
|
|
394
|
+
if lang == "csharp":
|
|
395
|
+
return _annotated_test_regions(text, masked, _CSHARP_TEST_ATTR_RE)
|
|
396
|
+
if lang == "java":
|
|
397
|
+
return _annotated_test_regions(text, masked, _JAVA_TEST_ANNOT_RE)
|
|
398
|
+
return []
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _python_test_regions(text: str, masked: str) -> list[tuple[int, str]]:
|
|
402
|
+
"""`(line_no, body_text)` for each `def test_*(...)`/`async def
|
|
403
|
+
test_*(...)` — body is every line after the parameter list's closing
|
|
404
|
+
`)` line, indented deeper than the `def` line, up to the first dedent
|
|
405
|
+
or EOF (blank lines don't count as a dedent).
|
|
406
|
+
|
|
407
|
+
The closing paren is found by depth-counting over `masked` (like every
|
|
408
|
+
other region helper in this module) rather than assumed to be on the
|
|
409
|
+
`def` line itself — a black-formatted multi-line signature (`def
|
|
410
|
+
test_x(\\n tmp_path, cfg\\n):`) puts the body's first line right
|
|
411
|
+
after a `):` line whose OWN indent equals the `def` line's, which a
|
|
412
|
+
naive "next line" walk would misread as an immediate dedent and return
|
|
413
|
+
an empty body (a false no-assertion finding on a test that does
|
|
414
|
+
assert)."""
|
|
415
|
+
lines = text.split("\n")
|
|
416
|
+
regions = []
|
|
417
|
+
for m in _PY_TEST_DEF_RE.finditer(text):
|
|
418
|
+
indent = m.group(1)
|
|
419
|
+
open_idx = m.end() - 1 # the `(` the regex itself just matched
|
|
420
|
+
close_idx = _matching_close_index(text, masked, open_idx, "(", ")")
|
|
421
|
+
if close_idx is None:
|
|
422
|
+
raise ParseFailure("unbalanced parens in a def test_*(...) signature")
|
|
423
|
+
def_line_idx = text.count("\n", 0, m.start())
|
|
424
|
+
sig_close_line_idx = text.count("\n", 0, close_idx)
|
|
425
|
+
body_lines = []
|
|
426
|
+
for line in lines[sig_close_line_idx + 1 :]:
|
|
427
|
+
if line.strip() == "":
|
|
428
|
+
body_lines.append(line)
|
|
429
|
+
continue
|
|
430
|
+
cur_indent_len = len(line) - len(line.lstrip(" \t"))
|
|
431
|
+
if cur_indent_len <= len(indent):
|
|
432
|
+
break
|
|
433
|
+
body_lines.append(line)
|
|
434
|
+
regions.append((def_line_idx + 1, "\n".join(body_lines)))
|
|
435
|
+
return regions
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
# --- Per-language signature tables (checks c/d/e) ----------------------------
|
|
439
|
+
|
|
440
|
+
#: (a) no-assertion and (c) mock-not-reset are file/test-scanned per
|
|
441
|
+
#: language; JS/TS/C#/Java cover (a)+(b), Python covers (a) only —
|
|
442
|
+
#: test-review.md documents no missing-await/mock-reset/clock-RNG-timer
|
|
443
|
+
#: signatures for Python.
|
|
444
|
+
|
|
445
|
+
_MOCK_CONSTRUCT_MARKERS: dict[str, tuple[re.Pattern, ...]] = {
|
|
446
|
+
"js_ts": (re.compile(r"\bjest\.fn\s*\("), re.compile(r"\bjest\.mock\s*\(")),
|
|
447
|
+
"csharp": (re.compile(r"\bMock<"), re.compile(r"\bSubstitute\.For<")),
|
|
448
|
+
"java": (re.compile(r"\bMockito\.mock\s*\("), re.compile(r"@Mock\b")),
|
|
449
|
+
}
|
|
450
|
+
_MOCK_RESET_MARKERS: dict[str, re.Pattern] = {
|
|
451
|
+
"js_ts": re.compile(r"\bjest\.clearAllMocks\s*\("),
|
|
452
|
+
"csharp": re.compile(r"\.Reset\s*\(\)|\bClearReceivedCalls\s*\("),
|
|
453
|
+
# Mockito-scoped (Fix 4): the previous bare `\breset\s*\(` matched ANY
|
|
454
|
+
# method literally named `reset(` in the file, including production
|
|
455
|
+
# code under test, which could silently suppress a real finding. This
|
|
456
|
+
# narrower form misses an unqualified `reset()` call reached via
|
|
457
|
+
# `import static org.mockito.Mockito.reset;` — a deliberate, documented
|
|
458
|
+
# trade (see the finding this fixes: an over-broad marker in the wrong
|
|
459
|
+
# direction is worse than a narrow miss here).
|
|
460
|
+
"java": re.compile(r"\bMockito\.reset\s*\("),
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
#: Re-instantiation/re-initialization alternative to an explicit reset call
|
|
464
|
+
#: (Fix 4) — test-review.md documents BOTH forms ("Moq `Mock<T>` reused
|
|
465
|
+
#: without `Reset()` OR RE-INSTANTIATION"; "Mockito missing `reset()` OR
|
|
466
|
+
#: `@BeforeEach` RE-INITIALIZATION"), but `_MOCK_RESET_MARKERS` only ever
|
|
467
|
+
#: covered the explicit-call half. `_mock_reinitialized` checks whether a
|
|
468
|
+
#: `[SetUp]`/`[TestInitialize]`/`@BeforeEach`/`@BeforeAll` method's own body
|
|
469
|
+
#: contains a mock-construct marker — re-creating the mock counts the same
|
|
470
|
+
#: as resetting it. JS/TS has no re-instantiation alternative documented in
|
|
471
|
+
#: test-review.md (only `jest.clearAllMocks()`), so it has no entry here.
|
|
472
|
+
|
|
473
|
+
#: REFACTOR (Step 2.2): this table is data-shaped like
|
|
474
|
+
#: `internal_double_detector.py`'s `_AMBIENT_API_MARKERS`, but it is NOT the
|
|
475
|
+
#: same list and does not share a use case with it, so it is kept separate
|
|
476
|
+
#: rather than factored together — see that module's table for the
|
|
477
|
+
#: comparison and this file's own module docstring for the one-line
|
|
478
|
+
#: pointer. `_AMBIENT_API_MARKERS` answers "does this COLLABORATOR's own
|
|
479
|
+
#: declaring file reference ambient state" (evidence for a B2 double
|
|
480
|
+
#: waiver: env/hostname/cwd/locale included, no timer/interval markers).
|
|
481
|
+
#: This table answers "does this TEST body call an unstubbed clock/RNG/
|
|
482
|
+
#: timer API directly" (test-review.md's own non-determinism-sources
|
|
483
|
+
#: signatures: no env/hostname/cwd/locale, but adds setTimeout/
|
|
484
|
+
#: setInterval/setImmediate/Task.Delay/Thread.Sleep, which
|
|
485
|
+
#: `_AMBIENT_API_MARKERS` has no equivalent for). The two tables overlap on
|
|
486
|
+
#: three literal substrings (Date/DateTime/Random-family) because both are
|
|
487
|
+
#: independently describing "the clock and RNG", not because one was
|
|
488
|
+
#: copied from the other — merging them would either strip timer markers
|
|
489
|
+
#: this check needs or leak env/hostname markers into a check
|
|
490
|
+
#: test-review.md never documented for it.
|
|
491
|
+
_CLOCK_RNG_TIMER_MARKERS: dict[str, tuple[re.Pattern, ...]] = {
|
|
492
|
+
"js_ts": (
|
|
493
|
+
re.compile(r"\bDate\.now\s*\("),
|
|
494
|
+
re.compile(r"\bDate\s*\("),
|
|
495
|
+
re.compile(r"\bMath\.random\s*\("),
|
|
496
|
+
re.compile(r"\bsetTimeout\s*\("),
|
|
497
|
+
re.compile(r"\bsetInterval\s*\("),
|
|
498
|
+
re.compile(r"\bsetImmediate\s*\("),
|
|
499
|
+
),
|
|
500
|
+
"csharp": (
|
|
501
|
+
re.compile(r"\bDateTime\.Now\b"),
|
|
502
|
+
re.compile(r"\bDateTime\.UtcNow\b"),
|
|
503
|
+
re.compile(r"\bDateTimeOffset\.Now\b"),
|
|
504
|
+
re.compile(r"\bnew\s+Random\s*\("),
|
|
505
|
+
re.compile(r"\bTask\.Delay\s*\("),
|
|
506
|
+
re.compile(r"\bThread\.Sleep\s*\("),
|
|
507
|
+
),
|
|
508
|
+
"java": (
|
|
509
|
+
re.compile(r"\bnew\s+Date\s*\("),
|
|
510
|
+
re.compile(r"\bLocalDateTime\.now\s*\("),
|
|
511
|
+
re.compile(r"\bInstant\.now\s*\("),
|
|
512
|
+
re.compile(r"\bSystem\.currentTimeMillis\s*\("),
|
|
513
|
+
re.compile(r"\bnew\s+Random\s*\("),
|
|
514
|
+
re.compile(r"\bMath\.random\s*\("),
|
|
515
|
+
re.compile(r"\bThread\.sleep\s*\("),
|
|
516
|
+
),
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
#: Fake-timer/injected-clock markers (Fix 3) — test-review.md's own bullets
|
|
520
|
+
#: exempt stubbed usage ("WITHOUT FAKE TIMERS", "WITHOUT INJECTION"), and
|
|
521
|
+
#: its Severity Anchors table names `jest.useFakeTimers()` as the remedy.
|
|
522
|
+
#: Presence anywhere in the file suppresses the WHOLE
|
|
523
|
+
#: unstubbed-clock-rng-timer finding for that file (the same file-level
|
|
524
|
+
#: granularity `_MOCK_RESET_MARKERS` already uses for mock-not-reset, not a
|
|
525
|
+
#: per-hit check).
|
|
526
|
+
_CLOCK_STUB_MARKERS: dict[str, tuple[re.Pattern, ...]] = {
|
|
527
|
+
"js_ts": (
|
|
528
|
+
re.compile(r"\bjest\.useFakeTimers\s*\("),
|
|
529
|
+
re.compile(r"\bvi\.useFakeTimers\s*\("),
|
|
530
|
+
re.compile(r"\bsinon\.useFakeTimers\s*\("),
|
|
531
|
+
),
|
|
532
|
+
"csharp": (re.compile(r"\bIClock\b"), re.compile(r"\bTimeProvider\b")),
|
|
533
|
+
"java": (re.compile(r"\bClock\.fixed\s*\("),),
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
_REFLECTION_MARKERS: dict[str, tuple[re.Pattern, ...]] = {
|
|
537
|
+
"java": (
|
|
538
|
+
re.compile(r"\bgetDeclaredMethod\b"),
|
|
539
|
+
re.compile(r"\bgetDeclaredField\b"),
|
|
540
|
+
re.compile(r"\.setAccessible\s*\(\s*true\s*\)"),
|
|
541
|
+
re.compile(r"\bMethod\.invoke\b"),
|
|
542
|
+
),
|
|
543
|
+
"csharp": (
|
|
544
|
+
re.compile(r"\.GetMethod\s*\([^)]*BindingFlags\.NonPublic"),
|
|
545
|
+
re.compile(r"\bInvokeMember\s*\("),
|
|
546
|
+
),
|
|
547
|
+
"python": (
|
|
548
|
+
re.compile(r"\bgetattr\s*\([^,]+,\s*['\"]_\w+['\"]"),
|
|
549
|
+
re.compile(r"\bsetattr\s*\([^,]+,\s*['\"]_\w+['\"]"),
|
|
550
|
+
re.compile(r"\bhasattr\s*\([^,]+,\s*['\"]_\w+['\"]"),
|
|
551
|
+
),
|
|
552
|
+
"js_ts": (
|
|
553
|
+
re.compile(r"\[\s*['\"]_\w+['\"]\s*\]"),
|
|
554
|
+
re.compile(r"\bObject\.getOwnPropertyDescriptor\s*\("),
|
|
555
|
+
re.compile(r"\bObject\.defineProperty\s*\("),
|
|
556
|
+
),
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
#: Tolerated-Deviation Hunt categories, sourced from test-review.md's own
|
|
560
|
+
#: grep-pattern prose (language-agnostic — this check has never been
|
|
561
|
+
#: per-language in the agent file). NOT "migrated verbatim" (correction —
|
|
562
|
+
#: see the module docstring's Tolerated-Deviation Hunt paragraph for the
|
|
563
|
+
#: file-scoping disclosure, and below for the qualifier-suppression Fix 8
|
|
564
|
+
#: adds on top of the raw grep patterns): each category in test-review.md
|
|
565
|
+
#: carries a qualifying clause this table only partially implements —
|
|
566
|
+
#: "disabled tests" count only *with no linked issue or expiry*; "aged
|
|
567
|
+
#: markers" only *when there is no linked ticket or follow-up action*;
|
|
568
|
+
#: "suppressed warnings" only *with no explanatory comment naming the
|
|
569
|
+
#: specific approved exception*. For those three categories,
|
|
570
|
+
#: `_line_is_qualified` suppresses a hit whose own line or an immediately
|
|
571
|
+
#: adjacent line carries a ticket/issue reference or explanatory comment —
|
|
572
|
+
#: a cheap same-line-or-adjacent-line approximation, not a real
|
|
573
|
+
#: linked-issue lookup. "Relaxed assertions" has no qualifying clause in
|
|
574
|
+
#: test-review.md (unconditional). "Widened tolerances" (`changed without
|
|
575
|
+
#: a comment explaining the regression`) is inherently diff-based — this
|
|
576
|
+
#: single-file scan cannot tell whether a tolerance constant was recently
|
|
577
|
+
#: *changed* at all, so that category remains an unqualified upper-bound
|
|
578
|
+
#: approximation here; a genuine diff-aware check is out of scope for this
|
|
579
|
+
#: fix pass.
|
|
580
|
+
_DEVIATION_MARKERS: tuple[tuple[str, re.Pattern], ...] = (
|
|
581
|
+
(
|
|
582
|
+
"disabled-test",
|
|
583
|
+
re.compile(
|
|
584
|
+
r"@Ignore\b|@Disabled\b|\bxit\s*\(|\bxdescribe\s*\(|\btest\.skip\s*\("
|
|
585
|
+
r"|\bit\.skip\s*\(|\[Ignore\]|\[Skip\]|pytest\.mark\.skip\b|pytest\.mark\.xfail\b"
|
|
586
|
+
),
|
|
587
|
+
),
|
|
588
|
+
("aged-marker", re.compile(r"\b(?:TODO|FIXME|HACK|XXX)\b")),
|
|
589
|
+
(
|
|
590
|
+
"suppressed-warning",
|
|
591
|
+
re.compile(
|
|
592
|
+
r"@SuppressWarnings\b|#pragma warning disable|#\s*noqa\b|#\s*type:\s*ignore"
|
|
593
|
+
r"|eslint-disable|pylint:\s*disable"
|
|
594
|
+
),
|
|
595
|
+
),
|
|
596
|
+
(
|
|
597
|
+
"relaxed-assertion",
|
|
598
|
+
re.compile(r"either\s+.{0,40}?\s+or\b|\bat least\b|\bapproximately\b|\bdelta\s*=|\bplaces\s*=\s*\d"),
|
|
599
|
+
),
|
|
600
|
+
("widened-tolerance", re.compile(r"\b(?:epsilon|tolerance)\s*=")),
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
#: Categories whose test-review.md wording carries a "no linked
|
|
604
|
+
#: issue/ticket/exception" qualifier (Fix 8) — checked via
|
|
605
|
+
#: `_line_is_qualified`.
|
|
606
|
+
_QUALIFIED_DEVIATION_CATEGORIES = frozenset({"disabled-test", "aged-marker", "suppressed-warning"})
|
|
607
|
+
|
|
608
|
+
#: A ticket/issue reference or explanatory link — `#123`, `PROJ-456`, or a
|
|
609
|
+
#: URL/word naming an issue/ticket tracker.
|
|
610
|
+
_QUALIFIER_RE = re.compile(r"#\d+|\b[A-Za-z]{2,}-\d+\b|\bissue\b|\bticket\b", re.IGNORECASE)
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
def _finding(category: str, severity: str | None, line: int, message: str, count: int = 1) -> dict:
|
|
614
|
+
return {"category": category, "severity": severity, "line": line, "message": message, "count": count}
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
# --- Checks -------------------------------------------------------------------
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def _check_no_assertion(regions: list[dict], lang: str) -> list[dict]:
|
|
621
|
+
key = "content" if lang == "js_ts" else "body"
|
|
622
|
+
findings = []
|
|
623
|
+
for entry in regions:
|
|
624
|
+
if not _ASSERTION_RE.search(entry[key]):
|
|
625
|
+
findings.append(
|
|
626
|
+
_finding("no-assertion", "error", entry["line"], "Test has no assertion call — zero regression protection.")
|
|
627
|
+
)
|
|
628
|
+
return findings
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def _check_no_assertion_python(text: str, masked: str) -> list[dict]:
|
|
632
|
+
findings = []
|
|
633
|
+
for line_no, body in _python_test_regions(text, masked):
|
|
634
|
+
if not _ASSERTION_RE.search(body):
|
|
635
|
+
findings.append(
|
|
636
|
+
_finding("no-assertion", "error", line_no, "Test has no assertion call — zero regression protection.")
|
|
637
|
+
)
|
|
638
|
+
return findings
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def _check_missing_await(regions: list[dict], lang: str) -> list[dict]:
|
|
642
|
+
findings = []
|
|
643
|
+
if lang == "js_ts":
|
|
644
|
+
for entry in regions:
|
|
645
|
+
if entry["is_async"] and "await" not in entry["content"]:
|
|
646
|
+
findings.append(
|
|
647
|
+
_finding(
|
|
648
|
+
"missing-await", "warning", entry["line"], "Async test body has no `await` — likely an unawaited promise."
|
|
649
|
+
)
|
|
650
|
+
)
|
|
651
|
+
elif lang == "csharp":
|
|
652
|
+
for entry in regions:
|
|
653
|
+
if _CSHARP_ASYNC_TASK_RE.search(entry["signature"]) and "await" not in entry["body"]:
|
|
654
|
+
findings.append(
|
|
655
|
+
_finding(
|
|
656
|
+
"missing-await",
|
|
657
|
+
"warning",
|
|
658
|
+
entry["line"],
|
|
659
|
+
"`async Task` test method has no `await` — likely an unresolved Task.",
|
|
660
|
+
)
|
|
661
|
+
)
|
|
662
|
+
elif lang == "java":
|
|
663
|
+
for entry in regions:
|
|
664
|
+
if _JAVA_FUTURE_TYPE_RE.search(entry["body"]) and not _JAVA_FUTURE_RESOLVED_RE.search(entry["body"]):
|
|
665
|
+
findings.append(
|
|
666
|
+
_finding(
|
|
667
|
+
"missing-await",
|
|
668
|
+
"warning",
|
|
669
|
+
entry["line"],
|
|
670
|
+
"Test body references Future/CompletableFuture with no `.get()`/`.join()` resolution.",
|
|
671
|
+
)
|
|
672
|
+
)
|
|
673
|
+
return findings
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def _mock_reinitialized(text: str, masked: str, lang: str) -> bool:
|
|
677
|
+
"""True when a `[SetUp]`/`[TestInitialize]`/`@BeforeEach`/`@BeforeAll`
|
|
678
|
+
method's own body contains a mock-construct marker (Fix 4) — the
|
|
679
|
+
re-instantiation/re-initialization alternative test-review.md
|
|
680
|
+
documents alongside an explicit reset call."""
|
|
681
|
+
setup_re = _SETUP_ANNOT_RE.get(lang)
|
|
682
|
+
construct_markers = _MOCK_CONSTRUCT_MARKERS.get(lang)
|
|
683
|
+
if not setup_re or not construct_markers:
|
|
684
|
+
return False
|
|
685
|
+
for entry in _annotated_test_regions(text, masked, setup_re):
|
|
686
|
+
if any(marker.search(entry["body"]) for marker in construct_markers):
|
|
687
|
+
return True
|
|
688
|
+
return False
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def _check_mock_not_reset(text: str, masked: str, lang: str) -> list[dict]:
|
|
692
|
+
construct_markers = _MOCK_CONSTRUCT_MARKERS.get(lang)
|
|
693
|
+
if not construct_markers:
|
|
694
|
+
return []
|
|
695
|
+
hit_lines = [_line_no(text, m.start()) for marker in construct_markers for m in marker.finditer(text)]
|
|
696
|
+
if not hit_lines:
|
|
697
|
+
return []
|
|
698
|
+
reset_marker = _MOCK_RESET_MARKERS[lang]
|
|
699
|
+
if reset_marker.search(text):
|
|
700
|
+
return []
|
|
701
|
+
if _mock_reinitialized(text, masked, lang):
|
|
702
|
+
return []
|
|
703
|
+
return [
|
|
704
|
+
_finding(
|
|
705
|
+
"mock-not-reset",
|
|
706
|
+
"warning",
|
|
707
|
+
min(hit_lines),
|
|
708
|
+
f"{len(hit_lines)} mock/stub construction site(s) with no reset/clear call found in this file.",
|
|
709
|
+
count=len(hit_lines),
|
|
710
|
+
)
|
|
711
|
+
]
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
def _check_unstubbed_clock_rng_timer(text: str, lang: str) -> list[dict]:
|
|
715
|
+
if any(marker.search(text) for marker in _CLOCK_STUB_MARKERS.get(lang, ())):
|
|
716
|
+
return []
|
|
717
|
+
findings = []
|
|
718
|
+
for marker in _CLOCK_RNG_TIMER_MARKERS.get(lang, ()):
|
|
719
|
+
for m in marker.finditer(text):
|
|
720
|
+
findings.append(
|
|
721
|
+
_finding(
|
|
722
|
+
"unstubbed-clock-rng-timer",
|
|
723
|
+
"warning",
|
|
724
|
+
_line_no(text, m.start()),
|
|
725
|
+
f"Unstubbed clock/RNG/timer access: {m.group(0)!r}.",
|
|
726
|
+
)
|
|
727
|
+
)
|
|
728
|
+
return findings
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
def _check_reflection_primary_strategy(text: str, lang: str) -> list[dict]:
|
|
732
|
+
findings = []
|
|
733
|
+
for marker in _REFLECTION_MARKERS.get(lang, ()):
|
|
734
|
+
for m in marker.finditer(text):
|
|
735
|
+
findings.append(
|
|
736
|
+
_finding(
|
|
737
|
+
"reflection-primary-strategy",
|
|
738
|
+
"warning",
|
|
739
|
+
_line_no(text, m.start()),
|
|
740
|
+
f"Reflection into private members: {m.group(0)!r}. Architecture/encapsulation issue, not a test-hygiene nit.",
|
|
741
|
+
)
|
|
742
|
+
)
|
|
743
|
+
return findings
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
def _line_is_qualified(lines: list[str], line_no: int) -> bool:
|
|
747
|
+
"""True when the marker's own line, or the immediately preceding/
|
|
748
|
+
following line, carries a ticket/issue reference or explanatory
|
|
749
|
+
comment (Fix 8)."""
|
|
750
|
+
for idx in (line_no - 2, line_no - 1, line_no):
|
|
751
|
+
if 0 <= idx < len(lines) and _QUALIFIER_RE.search(lines[idx]):
|
|
752
|
+
return True
|
|
753
|
+
return False
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
def _check_tolerated_deviation(text: str) -> list[dict]:
|
|
757
|
+
lines = text.split("\n")
|
|
758
|
+
hits = []
|
|
759
|
+
for name, marker in _DEVIATION_MARKERS:
|
|
760
|
+
for m in marker.finditer(text):
|
|
761
|
+
line_no = _line_no(text, m.start())
|
|
762
|
+
if name in _QUALIFIED_DEVIATION_CATEGORIES and _line_is_qualified(lines, line_no):
|
|
763
|
+
continue
|
|
764
|
+
hits.append((name, line_no))
|
|
765
|
+
if len(hits) < CONSOLIDATION_THRESHOLD:
|
|
766
|
+
return []
|
|
767
|
+
hits.sort(key=lambda h: h[1])
|
|
768
|
+
listing = ", ".join(f"{name}@L{line}" for name, line in hits)
|
|
769
|
+
return [
|
|
770
|
+
_finding(
|
|
771
|
+
"tolerated-deviation-consolidation",
|
|
772
|
+
"warning",
|
|
773
|
+
hits[0][1],
|
|
774
|
+
f"Fail-safe posture erosion: {len(hits)} tolerated-deviation artifacts co-located in this file ({listing}).",
|
|
775
|
+
count=len(hits),
|
|
776
|
+
)
|
|
777
|
+
]
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
def _is_under_test_dir(root: Path, file_path: Path) -> bool:
|
|
781
|
+
try:
|
|
782
|
+
rel = file_path.resolve().relative_to(root.resolve())
|
|
783
|
+
except ValueError:
|
|
784
|
+
rel = file_path
|
|
785
|
+
return any(part.lower() in _TEST_DIR_NAMES for part in rel.parts)
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
def _translate_double_detector_findings(
|
|
789
|
+
root: Path,
|
|
790
|
+
file_path: Path,
|
|
791
|
+
runner: Callable[..., subprocess.CompletedProcess] = subprocess.run,
|
|
792
|
+
) -> tuple[list[dict], bool]:
|
|
793
|
+
"""Run `internal_double_detector.py --files <file> --json` (via the
|
|
794
|
+
injectable `runner`, defaulting to the real `subprocess.run` — Fix 13)
|
|
795
|
+
and translate each `verdict == "high"` finding into this script's own
|
|
796
|
+
`error`-severity `internal-collaborator-doubling` finding. The reused
|
|
797
|
+
script's raw JSON is never checked for a `severity` field — it doesn't
|
|
798
|
+
have one.
|
|
799
|
+
|
|
800
|
+
Returns `(findings, doubling_check_ran)`. `doubling_check_ran` is
|
|
801
|
+
`False` when the file's path has no `test`/`tests` segment (the
|
|
802
|
+
detector's own `analyze()` never scans it — Fix 6's out-of-scope
|
|
803
|
+
disclosure) or when the subprocess call/JSON parse fails (Fix 5); it
|
|
804
|
+
is `True` only when the detector actually ran and reported."""
|
|
805
|
+
if not _is_under_test_dir(root, file_path):
|
|
806
|
+
return (
|
|
807
|
+
[
|
|
808
|
+
_finding(
|
|
809
|
+
"internal-collaborator-doubling-out-of-scope",
|
|
810
|
+
"warning",
|
|
811
|
+
_UNKNOWN_LINE,
|
|
812
|
+
"internal_double_detector.py only scans files under a "
|
|
813
|
+
"'test'/'tests' path segment; this file's path has no "
|
|
814
|
+
"such segment, so the doubling check did not run for it "
|
|
815
|
+
"— even though this repo's own test-file conventions "
|
|
816
|
+
"(*.test.*, *.spec.*, __tests__/) would still recognize "
|
|
817
|
+
"it as a test file. mechanicalFail is unaffected by the "
|
|
818
|
+
"doubling category specifically for this file.",
|
|
819
|
+
)
|
|
820
|
+
],
|
|
821
|
+
False,
|
|
822
|
+
)
|
|
823
|
+
try:
|
|
824
|
+
completed = runner(
|
|
825
|
+
[sys.executable, str(_DETECTOR_PATH), str(root), "--files", str(file_path), "--json"],
|
|
826
|
+
capture_output=True,
|
|
827
|
+
text=True,
|
|
828
|
+
timeout=_DOUBLE_DETECTOR_TIMEOUT_SECONDS,
|
|
829
|
+
check=False,
|
|
830
|
+
)
|
|
831
|
+
except (OSError, subprocess.TimeoutExpired) as exc:
|
|
832
|
+
return (
|
|
833
|
+
[
|
|
834
|
+
_finding(
|
|
835
|
+
"internal-collaborator-doubling-unavailable",
|
|
836
|
+
"warning",
|
|
837
|
+
_UNKNOWN_LINE,
|
|
838
|
+
f"internal_double_detector.py could not be run: {exc}",
|
|
839
|
+
)
|
|
840
|
+
],
|
|
841
|
+
False,
|
|
842
|
+
)
|
|
843
|
+
try:
|
|
844
|
+
payload = json.loads(completed.stdout)
|
|
845
|
+
except json.JSONDecodeError:
|
|
846
|
+
return (
|
|
847
|
+
[
|
|
848
|
+
_finding(
|
|
849
|
+
"internal-collaborator-doubling-unavailable",
|
|
850
|
+
"warning",
|
|
851
|
+
_UNKNOWN_LINE,
|
|
852
|
+
"internal_double_detector.py did not emit valid JSON: "
|
|
853
|
+
f"exit={completed.returncode} stderr={completed.stderr.strip()!r}",
|
|
854
|
+
)
|
|
855
|
+
],
|
|
856
|
+
False,
|
|
857
|
+
)
|
|
858
|
+
findings = []
|
|
859
|
+
for entry in payload.get("findings", []):
|
|
860
|
+
if entry.get("verdict") == "high":
|
|
861
|
+
findings.append(
|
|
862
|
+
_finding(
|
|
863
|
+
"internal-collaborator-doubling",
|
|
864
|
+
"error",
|
|
865
|
+
entry.get("line", _UNKNOWN_LINE),
|
|
866
|
+
entry.get("message", "internal_double_detector.py reported a high-verdict finding."),
|
|
867
|
+
)
|
|
868
|
+
)
|
|
869
|
+
return findings, True
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
_GATING_CATEGORIES = frozenset({"no-assertion", "internal-collaborator-doubling"})
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
def _parse_failure_result(file_path: Path, message: str) -> dict:
|
|
876
|
+
"""The shared `analyze_file` early-return shape for a file that could
|
|
877
|
+
not be read/decoded or whose test-region boundaries never closed
|
|
878
|
+
before EOF — a `parse-failure` finding, `mechanicalFail: False` (the
|
|
879
|
+
file falls through to the qualitative pass), and the doubling check
|
|
880
|
+
never ran."""
|
|
881
|
+
return {
|
|
882
|
+
"file": str(file_path),
|
|
883
|
+
"mechanicalFail": False,
|
|
884
|
+
"findings": [_finding("parse-failure", None, _UNKNOWN_LINE, message)],
|
|
885
|
+
"skippedQualitative": False,
|
|
886
|
+
"doublingCheckRan": False,
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
def analyze_file(
|
|
891
|
+
root: Path,
|
|
892
|
+
file_path: Path,
|
|
893
|
+
double_detector_runner: Callable[..., subprocess.CompletedProcess] = subprocess.run,
|
|
894
|
+
) -> dict:
|
|
895
|
+
"""Compute the mechanical pre-phase result for `file_path` (scanned
|
|
896
|
+
against `root` for `internal_double_detector.py`'s first-party index).
|
|
897
|
+
`double_detector_runner` is injectable (Fix 13) — defaults to the real
|
|
898
|
+
`subprocess.run`; tests exercising unrelated checks can pass a stub to
|
|
899
|
+
avoid the real subprocess-spawn cost.
|
|
900
|
+
|
|
901
|
+
Returns `{"file", "mechanicalFail", "findings", "skippedQualitative",
|
|
902
|
+
"doublingCheckRan"}`. Never raises — a decode/parse failure becomes a
|
|
903
|
+
`parse-failure` finding with `mechanicalFail: False` (the file falls
|
|
904
|
+
through to the qualitative pass rather than being dropped)."""
|
|
905
|
+
lang = LANG_BY_EXT.get(file_path.suffix)
|
|
906
|
+
|
|
907
|
+
try:
|
|
908
|
+
text = file_path.read_text(encoding="utf-8")
|
|
909
|
+
except (OSError, UnicodeDecodeError) as exc:
|
|
910
|
+
return _parse_failure_result(file_path, f"Could not read/decode file: {exc}")
|
|
911
|
+
|
|
912
|
+
findings: list[dict] = []
|
|
913
|
+
try:
|
|
914
|
+
masked = _mask_code(text)
|
|
915
|
+
regions = _extract_regions(text, masked, lang) if lang else []
|
|
916
|
+
if lang == "python":
|
|
917
|
+
findings += _check_no_assertion_python(text, masked)
|
|
918
|
+
elif lang in ("js_ts", "csharp", "java"):
|
|
919
|
+
findings += _check_no_assertion(regions, lang)
|
|
920
|
+
findings += _check_missing_await(regions, lang)
|
|
921
|
+
if lang:
|
|
922
|
+
findings += _check_mock_not_reset(text, masked, lang)
|
|
923
|
+
findings += _check_unstubbed_clock_rng_timer(text, lang)
|
|
924
|
+
findings += _check_reflection_primary_strategy(text, lang)
|
|
925
|
+
findings += _check_tolerated_deviation(text)
|
|
926
|
+
except ParseFailure as exc:
|
|
927
|
+
return _parse_failure_result(file_path, str(exc))
|
|
928
|
+
|
|
929
|
+
doubling_findings, doubling_check_ran = _translate_double_detector_findings(root, file_path, double_detector_runner)
|
|
930
|
+
findings += doubling_findings
|
|
931
|
+
|
|
932
|
+
mechanical_fail = any(f["category"] in _GATING_CATEGORIES and f["severity"] == "error" for f in findings)
|
|
933
|
+
return {
|
|
934
|
+
"file": str(file_path),
|
|
935
|
+
"mechanicalFail": mechanical_fail,
|
|
936
|
+
"findings": findings,
|
|
937
|
+
"skippedQualitative": mechanical_fail,
|
|
938
|
+
"doublingCheckRan": doubling_check_ran,
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
def main(argv: list[str] | None = None) -> int:
|
|
943
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
944
|
+
parser.add_argument("root", help="Project root (passed through to internal_double_detector.py)")
|
|
945
|
+
parser.add_argument("file", help="Test file to analyze (absolute, or relative to root)")
|
|
946
|
+
args = parser.parse_args(argv)
|
|
947
|
+
|
|
948
|
+
root = Path(args.root)
|
|
949
|
+
file_path = Path(args.file)
|
|
950
|
+
if not file_path.is_absolute():
|
|
951
|
+
file_path = root / file_path
|
|
952
|
+
|
|
953
|
+
print(json.dumps(analyze_file(root, file_path)))
|
|
954
|
+
return 0
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
if __name__ == "__main__":
|
|
958
|
+
raise SystemExit(main(sys.argv[1:]))
|