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,631 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Language-neutral internal-double detector with waiver support (#2127).
|
|
3
|
+
|
|
4
|
+
Implements the unconditional rule in `knowledge/internal-collaborator-doubling.md`
|
|
5
|
+
(#2124): a collaborator declared in the project's own first-party source
|
|
6
|
+
stays real; a double is admissible only when the collaborator matches one
|
|
7
|
+
of three named blockers (B1 out-of-process handle, B2 ambient state, B3
|
|
8
|
+
prohibitive real cost), marked with an inline `double-waiver: B<n> — <reason>`
|
|
9
|
+
comment at the double site.
|
|
10
|
+
|
|
11
|
+
**No test-type gate.** The rule is unconditional, so this detector reads no
|
|
12
|
+
test-type declaration (solitary/sociable/unit/component/anything else) —
|
|
13
|
+
that is what closes the undeclared-file and relabel bypasses the epic's
|
|
14
|
+
architecture notes describe. A file is scanned the same way regardless of
|
|
15
|
+
what it calls itself.
|
|
16
|
+
|
|
17
|
+
**Language-neutral by construction.** One first-party declaration index plus
|
|
18
|
+
per-stack *data* tables (`_EXTRACTION_TABLES`, `_AMBIENT_API_MARKERS`) feed a
|
|
19
|
+
single shared pipeline (`extract_by_table`, `analyze`). There is no
|
|
20
|
+
per-language branching logic — a new stack is a new data-table row, not a
|
|
21
|
+
new code path. If this module exceeds ~500 lines, split the extraction
|
|
22
|
+
tables into a submodule rather than growing this file indefinitely (mirrors
|
|
23
|
+
`repo_invariants.py`'s own "start narrow, expand opportunistically"
|
|
24
|
+
convention) — not yet warranted at this size.
|
|
25
|
+
|
|
26
|
+
**What this detector verifies, and what it does not:**
|
|
27
|
+
|
|
28
|
+
- Resolution is **name-based, not fully-qualified-path-based**. Building a
|
|
29
|
+
real cross-file import graph per language is out of scope for a
|
|
30
|
+
stdlib-only script with no per-language parser. A name match against the
|
|
31
|
+
first-party declaration index is treated as resolved. Two same-named
|
|
32
|
+
types in different first-party modules are indistinguishable; a name
|
|
33
|
+
collision with an unrelated third-party type of the same name could
|
|
34
|
+
false-positive. Precision-first per the epic means under-report, not zero
|
|
35
|
+
false positives.
|
|
36
|
+
- **Waiver syntax, not waiver truth**, for B1 and B3. A waiver naming B1 or
|
|
37
|
+
B3 is `informational` once its code is syntactically valid — this
|
|
38
|
+
detector cannot decide whether the claimed out-of-process handle or
|
|
39
|
+
prohibitive cost is real (the epic's own architecture notes: B1 is "not
|
|
40
|
+
decidable", B3 "not statically detectable at all"). `test-smell-review`
|
|
41
|
+
owns that judgment.
|
|
42
|
+
- **B2 gets one mechanical evidence check**, not full verification: the
|
|
43
|
+
epic states B2 (unlike B1/B3) is "detectable against a per-stack table of
|
|
44
|
+
ambient APIs." A B2 waiver is downgraded from `informational` to `high`
|
|
45
|
+
when the doubled collaborator's own declaring file shows no reference to
|
|
46
|
+
any per-stack ambient-API marker (clock, RNG/GUID, env, hostname, cwd,
|
|
47
|
+
locale) — a presence-of-reference check, not a behavioral proof.
|
|
48
|
+
`test-smell-review` still owns the final call even when this check
|
|
49
|
+
passes.
|
|
50
|
+
- Gate-wiring into a blocking hook or CI check is a separate issue (#2128).
|
|
51
|
+
This script is a standalone CLI, proven via direct invocation and its own
|
|
52
|
+
test suite.
|
|
53
|
+
|
|
54
|
+
Stdlib-only. See docs/python-hook-contract.md.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
from __future__ import annotations
|
|
58
|
+
|
|
59
|
+
import argparse
|
|
60
|
+
import json
|
|
61
|
+
import re
|
|
62
|
+
import subprocess
|
|
63
|
+
import sys
|
|
64
|
+
from collections.abc import Callable
|
|
65
|
+
from pathlib import Path
|
|
66
|
+
|
|
67
|
+
# --- First-party type resolution --------------------------------------------
|
|
68
|
+
|
|
69
|
+
#: Directory names excluded from vendored/generated/build-output code for
|
|
70
|
+
#: *both* first-party indexing and double scanning — nothing useful lives
|
|
71
|
+
#: there for either purpose.
|
|
72
|
+
_EXCLUDED_DIR_NAMES = frozenset(
|
|
73
|
+
{
|
|
74
|
+
"__pycache__",
|
|
75
|
+
"node_modules",
|
|
76
|
+
"dist",
|
|
77
|
+
"build",
|
|
78
|
+
"vendor",
|
|
79
|
+
"vendored",
|
|
80
|
+
".git",
|
|
81
|
+
# #2128: a local, uncommitted virtualenv (CLAUDE.md's own
|
|
82
|
+
# documented PEP-668 dev-setup fallback) must never change the
|
|
83
|
+
# required whole-repo CI check's result depending on whether a
|
|
84
|
+
# given contributor's checkout happens to have one.
|
|
85
|
+
".venv",
|
|
86
|
+
"venv",
|
|
87
|
+
".tox",
|
|
88
|
+
"site-packages",
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
#: Test-directory names excluded from first-party resolution ONLY — a type
|
|
93
|
+
#: declared under a test tree isn't a first-party collaborator (#2124's
|
|
94
|
+
#: rule is about the project's own *non-test* source). These directories
|
|
95
|
+
#: are NOT excluded from double-scanning: that's exactly where doubles live.
|
|
96
|
+
_TEST_DIR_NAMES = frozenset({"test", "tests"})
|
|
97
|
+
|
|
98
|
+
#: (stack, compiled_regex, target_group) rows for declaring a first-party
|
|
99
|
+
#: type. One row per declaration *form*, not one combined regex per stack
|
|
100
|
+
#: with post-hoc form selection (design-review fix) — a new form is a new
|
|
101
|
+
#: row, never a new branch.
|
|
102
|
+
_TYPE_DECLARATION_TABLE: tuple[tuple[str, re.Pattern, int], ...] = (
|
|
103
|
+
("python", re.compile(r"^\s*class\s+([A-Za-z_][A-Za-z0-9_]*)"), 1),
|
|
104
|
+
("csharp", re.compile(r"^\s*(?:public|internal|private)?\s*(?:class|interface|record)\s+([A-Za-z_][A-Za-z0-9_]*)"), 1),
|
|
105
|
+
("java", re.compile(r"^\s*(?:public|private|protected)?\s*(?:class|interface|record)\s+([A-Za-z_][A-Za-z0-9_]*)"), 1),
|
|
106
|
+
("js_ts_class", re.compile(r"^\s*(?:export\s+)?class\s+([A-Za-z_][A-Za-z0-9_]*)"), 1),
|
|
107
|
+
("js_ts_interface", re.compile(r"^\s*(?:export\s+)?interface\s+([A-Za-z_][A-Za-z0-9_]*)"), 1),
|
|
108
|
+
("js_ts_type_alias", re.compile(r"^\s*(?:export\s+)?type\s+([A-Za-z_][A-Za-z0-9_]*)\s*="), 1),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _is_vendored_or_build(path: Path) -> bool:
|
|
113
|
+
return any(part.lower() in _EXCLUDED_DIR_NAMES for part in path.parts)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _is_test_tree(path: Path) -> bool:
|
|
117
|
+
return any(part.lower() in _TEST_DIR_NAMES for part in path.parts)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _read_text(path: Path) -> str:
|
|
121
|
+
try:
|
|
122
|
+
return path.read_text(encoding="utf-8")
|
|
123
|
+
except (OSError, UnicodeDecodeError):
|
|
124
|
+
return ""
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
#: Source file extensions this detector reads at all — first-party
|
|
128
|
+
#: resolution and every extraction vector below are scoped to these.
|
|
129
|
+
_SOURCE_EXTENSIONS = frozenset({".py", ".cs", ".java", ".ts", ".tsx", ".js", ".jsx"})
|
|
130
|
+
|
|
131
|
+
#: Which file extensions each stack's patterns are allowed to run against.
|
|
132
|
+
#: Without this, a stack's regex can spuriously match inside another
|
|
133
|
+
#: stack's file (e.g. a C# "class X : Base" pattern's trailing `\s*` can
|
|
134
|
+
#: cross a newline and match text in an unrelated Python file) — extension
|
|
135
|
+
#: gating is what actually keeps the per-stack data tables from bleeding
|
|
136
|
+
#: into each other, not just their labels.
|
|
137
|
+
_STACK_EXTENSIONS: dict[str, frozenset[str]] = {
|
|
138
|
+
"python": frozenset({".py"}),
|
|
139
|
+
"csharp": frozenset({".cs"}),
|
|
140
|
+
"java": frozenset({".java"}),
|
|
141
|
+
"js_ts": frozenset({".ts", ".tsx", ".js", ".jsx"}),
|
|
142
|
+
"js_ts_class": frozenset({".ts", ".tsx", ".js", ".jsx"}),
|
|
143
|
+
"js_ts_interface": frozenset({".ts", ".tsx", ".js", ".jsx"}),
|
|
144
|
+
"js_ts_type_alias": frozenset({".ts", ".tsx", ".js", ".jsx"}),
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def resolve_first_party_types(root: Path) -> dict[str, Path]:
|
|
149
|
+
"""Index every type name declared in non-test, non-vendored,
|
|
150
|
+
non-generated source under `root`. Returns `{type_name: declaring_path}`
|
|
151
|
+
— first declaration wins on a name collision (a recall bound stated in
|
|
152
|
+
the module docstring, not silently hidden).
|
|
153
|
+
"""
|
|
154
|
+
index: dict[str, Path] = {}
|
|
155
|
+
for path in sorted(root.rglob("*")):
|
|
156
|
+
if not path.is_file() or path.suffix not in _SOURCE_EXTENSIONS:
|
|
157
|
+
continue
|
|
158
|
+
rel = path.relative_to(root)
|
|
159
|
+
if _is_vendored_or_build(rel) or _is_test_tree(rel):
|
|
160
|
+
continue
|
|
161
|
+
text = _read_text(path)
|
|
162
|
+
if not text:
|
|
163
|
+
continue
|
|
164
|
+
for stack, pattern, group in _TYPE_DECLARATION_TABLE:
|
|
165
|
+
if path.suffix not in _STACK_EXTENSIONS.get(stack, _SOURCE_EXTENSIONS):
|
|
166
|
+
continue
|
|
167
|
+
for line in text.splitlines():
|
|
168
|
+
m = pattern.match(line)
|
|
169
|
+
if m:
|
|
170
|
+
name = m.group(group)
|
|
171
|
+
index.setdefault(name, path)
|
|
172
|
+
return index
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# --- Extraction tables (doubling constructs, hand-rolled, DI-override) -----
|
|
176
|
+
|
|
177
|
+
def _identity(name: str) -> str:
|
|
178
|
+
return name
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _last_path_segment(module_path: str) -> str:
|
|
182
|
+
"""`./services/SmtpGateway` -> `SmtpGateway` — `vi.mock`/`jest.mock`
|
|
183
|
+
capture a module *path*, not a type name, so the target must be
|
|
184
|
+
normalized to a plausible type-name form before it can be looked up in
|
|
185
|
+
the name-keyed first-party index (correctness-review fix: these rows
|
|
186
|
+
previously could never resolve to anything but `advisory`)."""
|
|
187
|
+
stem = module_path.rsplit("/", 1)[-1]
|
|
188
|
+
for ext in (".ts", ".tsx", ".js", ".jsx"):
|
|
189
|
+
stem = stem.removesuffix(ext)
|
|
190
|
+
return stem
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _last_dotted_segment(dotted: str) -> str:
|
|
194
|
+
"""`billing.SmtpGateway` -> `SmtpGateway` — `vi.spyOn` can capture a
|
|
195
|
+
dotted expression; only the final segment is a plausible type name."""
|
|
196
|
+
return dotted.rsplit(".", 1)[-1]
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
#: One shared schema for every extraction vector: a tuple of
|
|
200
|
+
#: `(stack, compiled_regex, target_group, normalize)` rows, where
|
|
201
|
+
#: `normalize` maps the raw captured string to a plausible type name before
|
|
202
|
+
#: first-party lookup (identity for rows that already capture a bare type
|
|
203
|
+
#: name). `extract_by_table` is the single iteration helper every vector
|
|
204
|
+
#: uses — no vector gets its own bespoke loop (design-review fix).
|
|
205
|
+
_EXTRACTION_TABLES: dict[str, tuple[tuple[str, re.Pattern, int, Callable[[str], str]], ...]] = {
|
|
206
|
+
"mock_construct": (
|
|
207
|
+
("csharp", re.compile(r"\bMock<([A-Za-z_][A-Za-z0-9_]*)>"), 1, _identity),
|
|
208
|
+
("csharp", re.compile(r"\bMock\.Of<([A-Za-z_][A-Za-z0-9_]*)>"), 1, _identity),
|
|
209
|
+
("csharp", re.compile(r"\bSubstitute\.For<([A-Za-z_][A-Za-z0-9_]*)>"), 1, _identity),
|
|
210
|
+
("csharp", re.compile(r"\bA\.Fake<([A-Za-z_][A-Za-z0-9_]*)>"), 1, _identity),
|
|
211
|
+
("java", re.compile(r"\bmock\(([A-Za-z_][A-Za-z0-9_]*)\.class\)"), 1, _identity),
|
|
212
|
+
("java", re.compile(r"@Mock(?:Bean)?\s*\n?\s*(?:private|public)?\s*([A-Za-z_][A-Za-z0-9_]*)\s+\w+;"), 1, _identity),
|
|
213
|
+
("js_ts", re.compile(r"\bvi\.mock\(\s*['\"]([^'\"]+)['\"]"), 1, _last_path_segment),
|
|
214
|
+
("js_ts", re.compile(r"\bjest\.mock\(\s*['\"]([^'\"]+)['\"]"), 1, _last_path_segment),
|
|
215
|
+
("js_ts", re.compile(r"\bvi\.spyOn\(\s*([A-Za-z_][A-Za-z0-9_.]*)"), 1, _last_dotted_segment),
|
|
216
|
+
("python", re.compile(r"\bpatch\(\s*['\"][\w.]*\.([A-Za-z_][A-Za-z0-9_]*)['\"]"), 1, _identity),
|
|
217
|
+
("python", re.compile(r"\bMagicMock\(\s*spec\s*=\s*([A-Za-z_][A-Za-z0-9_]*)"), 1, _identity),
|
|
218
|
+
),
|
|
219
|
+
"hand_rolled": (
|
|
220
|
+
# re.MULTILINE is required (correctness-review fix): extract_by_table
|
|
221
|
+
# runs finditer over the WHOLE file, not line-by-line, so a bare `^`
|
|
222
|
+
# only anchors to offset 0 without it — silently matching nothing
|
|
223
|
+
# in any file with content before the fake class.
|
|
224
|
+
("python", re.compile(r"^\s*class\s+[A-Za-z_][A-Za-z0-9_]*\(([A-Za-z_][A-Za-z0-9_]*)\)", re.MULTILINE), 1, _identity),
|
|
225
|
+
("java", re.compile(r"^\s*class\s+[A-Za-z_][A-Za-z0-9_]*\s+implements\s+([A-Za-z_][A-Za-z0-9_]*)", re.MULTILINE), 1, _identity),
|
|
226
|
+
("js_ts", re.compile(r"^\s*class\s+[A-Za-z_][A-Za-z0-9_]*\s+implements\s+([A-Za-z_][A-Za-z0-9_]*)", re.MULTILINE), 1, _identity),
|
|
227
|
+
("csharp", re.compile(r"^\s*class\s+[A-Za-z_][A-Za-z0-9_]*\s*:\s*([A-Za-z_][A-Za-z0-9_]*)", re.MULTILINE), 1, _identity),
|
|
228
|
+
),
|
|
229
|
+
"di_override": (
|
|
230
|
+
("csharp", re.compile(r"\bRemoveAll<([A-Za-z_][A-Za-z0-9_]*)>"), 1, _identity),
|
|
231
|
+
("csharp", re.compile(r"\bAddSingleton<([A-Za-z_][A-Za-z0-9_]*)>"), 1, _identity),
|
|
232
|
+
("java", re.compile(r"@Bean\s*\n?\s*(?:public|private)?\s*([A-Za-z_][A-Za-z0-9_]*)\s+\w+\("), 1, _identity),
|
|
233
|
+
("python", re.compile(r"\bdependency_overrides\[([A-Za-z_][A-Za-z0-9_]*)\]"), 1, _identity),
|
|
234
|
+
("js_ts", re.compile(r"\boverrideProvider\(([A-Za-z_][A-Za-z0-9_]*)\)"), 1, _identity),
|
|
235
|
+
),
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def extract_by_table(
|
|
240
|
+
text: str,
|
|
241
|
+
table: tuple[tuple[str, re.Pattern, int, Callable[[str], str]], ...],
|
|
242
|
+
file_ext: str | None = None,
|
|
243
|
+
) -> list[tuple[int, str, str]]:
|
|
244
|
+
"""Run every `(stack, regex, group, normalize)` row in `table` against
|
|
245
|
+
`text`, returning `(line_no, stack, normalized_target_name)` for each
|
|
246
|
+
match. Matches over the full text (not line-by-line) so a row's own
|
|
247
|
+
regex controls whether it spans one line or several (e.g. a `@Bean`
|
|
248
|
+
annotation on the line before the method it decorates) — the line
|
|
249
|
+
number is derived from the match's start offset. When `file_ext` is
|
|
250
|
+
given, a row only runs if its stack's extension set includes it — this
|
|
251
|
+
is what stops one stack's pattern from matching inside another stack's
|
|
252
|
+
file (see `_STACK_EXTENSIONS`'s docstring). Shared by every extraction
|
|
253
|
+
vector — mock constructs, hand-rolled subclasses, and DI-override
|
|
254
|
+
bindings alike."""
|
|
255
|
+
out: list[tuple[int, str, str]] = []
|
|
256
|
+
for stack, pattern, group, normalize in table:
|
|
257
|
+
if file_ext is not None and file_ext not in _STACK_EXTENSIONS.get(
|
|
258
|
+
stack, _SOURCE_EXTENSIONS
|
|
259
|
+
):
|
|
260
|
+
continue
|
|
261
|
+
for m in pattern.finditer(text):
|
|
262
|
+
lineno = text.count("\n", 0, m.start()) + 1
|
|
263
|
+
out.append((lineno, stack, normalize(m.group(group))))
|
|
264
|
+
return out
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def find_doubles(text: str, file_ext: str | None = None) -> list[tuple[int, str, str]]:
|
|
268
|
+
return extract_by_table(text, _EXTRACTION_TABLES["mock_construct"], file_ext)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# --- Waiver parsing ----------------------------------------------------------
|
|
272
|
+
|
|
273
|
+
_WAIVER_RE = re.compile(r"double-waiver:\s*([A-Za-z0-9]+)\s*[—-]\s*(.+)")
|
|
274
|
+
_VALID_BLOCKERS = frozenset({"B1", "B2", "B3"})
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def find_waiver(lines: list[str], double_line_no: int) -> tuple[str, str] | None:
|
|
278
|
+
"""Look at the double's own line and the line immediately before it for
|
|
279
|
+
a `double-waiver: B<n> — <reason>` comment. Returns `(code, reason)` or
|
|
280
|
+
`None`."""
|
|
281
|
+
for idx in (double_line_no - 1, double_line_no - 2):
|
|
282
|
+
if 0 <= idx < len(lines):
|
|
283
|
+
m = _WAIVER_RE.search(lines[idx])
|
|
284
|
+
if m:
|
|
285
|
+
return m.group(1), m.group(2).strip()
|
|
286
|
+
return None
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def valid_blocker(code: str) -> bool:
|
|
290
|
+
return code in _VALID_BLOCKERS
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# --- Ambient-API evidence (B2 only) ------------------------------------------
|
|
294
|
+
|
|
295
|
+
#: Per-stack markers whose presence in a collaborator's own declaring file
|
|
296
|
+
#: is evidence it plausibly reads ambient state (clock, RNG/GUID, env,
|
|
297
|
+
#: hostname, cwd, locale). Presence-of-reference only — not a behavioral
|
|
298
|
+
#: proof. B1 and B3 have no equivalent table: the epic states both are
|
|
299
|
+
#: undecidable/not-statically-detectable, so this asymmetry is deliberate.
|
|
300
|
+
_AMBIENT_API_MARKERS: dict[str, tuple[str, ...]] = {
|
|
301
|
+
"python": ("time.", "datetime.", "random.", "uuid.", "os.environ", "socket.gethostname", "os.getcwd", "locale."),
|
|
302
|
+
"csharp": ("DateTime.Now", "Guid.NewGuid", "Environment."),
|
|
303
|
+
"java": ("System.currentTimeMillis", "UUID.randomUUID", "System.getenv"),
|
|
304
|
+
"js_ts": ("Date.now", "Math.random", "process.env"),
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def references_ambient_api(collaborator_source: str, stack: str) -> bool:
|
|
309
|
+
markers = _AMBIENT_API_MARKERS.get(stack, ())
|
|
310
|
+
return any(marker in collaborator_source for marker in markers)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
# --- Verdict computation ------------------------------------------------------
|
|
314
|
+
|
|
315
|
+
#: Findings dict shape: {line, stack, target, vector, verdict, waiver, message}
|
|
316
|
+
Finding = dict
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _verdict_for(
|
|
320
|
+
waiver: tuple[str, str] | None,
|
|
321
|
+
stack: str,
|
|
322
|
+
collaborator_path: Path | None,
|
|
323
|
+
) -> str:
|
|
324
|
+
if waiver is None:
|
|
325
|
+
return "high"
|
|
326
|
+
code, _reason = waiver
|
|
327
|
+
if not valid_blocker(code):
|
|
328
|
+
return "high"
|
|
329
|
+
if code == "B2":
|
|
330
|
+
collaborator_source = _read_text(collaborator_path) if collaborator_path else ""
|
|
331
|
+
if not references_ambient_api(collaborator_source, stack):
|
|
332
|
+
return "high"
|
|
333
|
+
return "informational"
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _remediation_hint(target: str) -> str:
|
|
337
|
+
return (
|
|
338
|
+
f"add a `double-waiver: B<n> — <reason>` comment at the double site "
|
|
339
|
+
f"naming why {target} can't stay real, or use the real collaborator. "
|
|
340
|
+
"See knowledge/internal-collaborator-doubling.md."
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def analyze(root: Path) -> list[Finding]:
|
|
345
|
+
"""Scan every test-tree source file under `root` for doubles of
|
|
346
|
+
first-party collaborators across all three vectors (mock constructs,
|
|
347
|
+
hand-rolled subclasses, DI-overrides), and compute each one's verdict.
|
|
348
|
+
|
|
349
|
+
Scoped to test-tree files (correctness-review fix): the epic's own
|
|
350
|
+
language for both hand-rolled doubles ("a test-tree class implementing
|
|
351
|
+
a first-party abstraction") and DI-overrides ("a type declared under
|
|
352
|
+
the test tree") makes this explicit — a "double" is a test-context
|
|
353
|
+
concept, so ordinary production subclassing of a first-party class is
|
|
354
|
+
not in scope for any of the three vectors, not just these two.
|
|
355
|
+
"""
|
|
356
|
+
first_party = resolve_first_party_types(root)
|
|
357
|
+
findings: list[Finding] = []
|
|
358
|
+
|
|
359
|
+
for path in sorted(root.rglob("*")):
|
|
360
|
+
if not path.is_file() or path.suffix not in _SOURCE_EXTENSIONS:
|
|
361
|
+
continue
|
|
362
|
+
rel_for_scope = path.relative_to(root)
|
|
363
|
+
if _is_vendored_or_build(rel_for_scope) or not _is_test_tree(rel_for_scope):
|
|
364
|
+
continue
|
|
365
|
+
text = _read_text(path)
|
|
366
|
+
if not text:
|
|
367
|
+
continue
|
|
368
|
+
lines = text.splitlines()
|
|
369
|
+
|
|
370
|
+
for vector in ("mock_construct", "hand_rolled", "di_override"):
|
|
371
|
+
matches = extract_by_table(text, _EXTRACTION_TABLES[vector], path.suffix)
|
|
372
|
+
for lineno, stack, target in matches:
|
|
373
|
+
collaborator_path = first_party.get(target)
|
|
374
|
+
if collaborator_path is None:
|
|
375
|
+
verdict = "advisory"
|
|
376
|
+
else:
|
|
377
|
+
waiver = find_waiver(lines, lineno)
|
|
378
|
+
verdict = _verdict_for(waiver, stack, collaborator_path)
|
|
379
|
+
|
|
380
|
+
rel = str(path)
|
|
381
|
+
findings.append(
|
|
382
|
+
{
|
|
383
|
+
"file": rel,
|
|
384
|
+
"line": lineno,
|
|
385
|
+
"stack": stack,
|
|
386
|
+
"vector": vector,
|
|
387
|
+
"target": target,
|
|
388
|
+
"verdict": verdict,
|
|
389
|
+
"message": (
|
|
390
|
+
f"{rel}:{lineno}: {verdict} — {vector} of {target!r}"
|
|
391
|
+
+ (
|
|
392
|
+
f". {_remediation_hint(target)}"
|
|
393
|
+
if verdict == "high"
|
|
394
|
+
else ""
|
|
395
|
+
)
|
|
396
|
+
),
|
|
397
|
+
}
|
|
398
|
+
)
|
|
399
|
+
return findings
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
# --- Changed-file scoping (#2128) --------------------------------------------
|
|
403
|
+
#
|
|
404
|
+
# analyze()'s own signature, full-tree walk, and full-tree first-party
|
|
405
|
+
# resolution are DELIBERATELY untouched by everything below — a first-party
|
|
406
|
+
# class declared in a file the current diff never touched must still resolve.
|
|
407
|
+
# Scoping only ever filters the FINDINGS analyze() already produced; it never
|
|
408
|
+
# narrows what analyze() itself scans.
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def changed_files_since(ref: str, root: Path) -> set[Path] | None:
|
|
412
|
+
"""Absolute paths of files changed vs `ref` (`git diff --name-only
|
|
413
|
+
ref...HEAD`, run with cwd=root). Returns `None` on ANY git failure — not
|
|
414
|
+
a repo, a bad ref, no merge-base (e.g. a shallow clone) — never an empty
|
|
415
|
+
set standing in for "the git call itself failed": an empty set means
|
|
416
|
+
"the diff is genuinely empty", which callers must be able to tell apart
|
|
417
|
+
from "scoping could not be determined at all".
|
|
418
|
+
|
|
419
|
+
`git diff --name-only` always emits paths relative to the repo's TOP
|
|
420
|
+
LEVEL, never relative to the cwd it was run from (there is no implicit
|
|
421
|
+
`--relative`) — so the output must be joined onto the actual top-level
|
|
422
|
+
directory (resolved via `git rev-parse --show-toplevel`), NOT onto
|
|
423
|
+
`root` itself. `root` is legitimately a subdirectory of the repo (e.g.
|
|
424
|
+
a monorepo sub-project's cwd); joining git's repo-root-relative output
|
|
425
|
+
onto that subdirectory would silently double the sub-path and match
|
|
426
|
+
nothing real, which is exactly the false-negative correctness-review
|
|
427
|
+
caught before this landed (#2128). `-c core.quotePath=false` +
|
|
428
|
+
`-z` (NUL-separated, no shell-quoting ambiguity) makes this correct
|
|
429
|
+
for filenames with spaces, quotes, or non-ASCII bytes too."""
|
|
430
|
+
try:
|
|
431
|
+
toplevel_completed = subprocess.run(
|
|
432
|
+
["git", "rev-parse", "--show-toplevel"],
|
|
433
|
+
cwd=str(root),
|
|
434
|
+
capture_output=True,
|
|
435
|
+
text=True,
|
|
436
|
+
timeout=30,
|
|
437
|
+
check=False,
|
|
438
|
+
)
|
|
439
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
440
|
+
return None
|
|
441
|
+
if toplevel_completed.returncode != 0:
|
|
442
|
+
return None
|
|
443
|
+
toplevel = Path(toplevel_completed.stdout.strip()).resolve()
|
|
444
|
+
|
|
445
|
+
try:
|
|
446
|
+
completed = subprocess.run(
|
|
447
|
+
[
|
|
448
|
+
"git",
|
|
449
|
+
"-c",
|
|
450
|
+
"core.quotePath=false",
|
|
451
|
+
"diff",
|
|
452
|
+
"--name-only",
|
|
453
|
+
"-z",
|
|
454
|
+
f"{ref}...HEAD",
|
|
455
|
+
],
|
|
456
|
+
cwd=str(root),
|
|
457
|
+
capture_output=True,
|
|
458
|
+
text=True,
|
|
459
|
+
timeout=30,
|
|
460
|
+
check=False,
|
|
461
|
+
)
|
|
462
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
463
|
+
return None
|
|
464
|
+
if completed.returncode != 0:
|
|
465
|
+
return None
|
|
466
|
+
return {(toplevel / part).resolve() for part in completed.stdout.split("\0") if part}
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _findings_matching_paths(
|
|
470
|
+
findings: list[Finding], target_paths: set[Path], root: Path
|
|
471
|
+
) -> list[Finding]:
|
|
472
|
+
"""Keep only findings whose file resolves into `target_paths`. Both
|
|
473
|
+
sides are resolved to absolute paths before comparison — this function
|
|
474
|
+
resolves `target_paths` itself (never trusts a caller to have done so
|
|
475
|
+
already), so it's correct whether `root` (and therefore every
|
|
476
|
+
finding's `file` string, which inherits root's absolute/relative-ness
|
|
477
|
+
directly from `analyze()`'s own `root.rglob()` walk) was passed in as
|
|
478
|
+
absolute or relative, and regardless of whether `target_paths` arrived
|
|
479
|
+
pre-resolved. Shared by `filter_since` (a git-diff-derived path set)
|
|
480
|
+
and `filter_by_files` (a caller-supplied path list) so the
|
|
481
|
+
path-matching logic exists exactly once."""
|
|
482
|
+
root_abs = root.resolve()
|
|
483
|
+
resolved_targets = {p.resolve() for p in target_paths}
|
|
484
|
+
return [
|
|
485
|
+
f
|
|
486
|
+
for f in findings
|
|
487
|
+
if (root_abs / f["file"]).resolve() in resolved_targets
|
|
488
|
+
or Path(f["file"]).resolve() in resolved_targets
|
|
489
|
+
]
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def filter_since(findings: list[Finding], changed: set[Path], root: Path) -> list[Finding]:
|
|
493
|
+
"""Keep only findings whose file is in `changed`. See
|
|
494
|
+
`_findings_matching_paths` for the matching contract."""
|
|
495
|
+
return _findings_matching_paths(findings, changed, root)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def filter_by_files(findings: list[Finding], files: list[str], root: Path) -> list[Finding]:
|
|
499
|
+
"""Keep only findings whose file is in `files` (repo-relative or
|
|
500
|
+
absolute paths, e.g. from `/code-review`'s own changed-file list —
|
|
501
|
+
the same list `repo_invariants.py --files` already receives). Both a
|
|
502
|
+
root-anchored and a bare interpretation of each entry are offered to
|
|
503
|
+
`_findings_matching_paths` (which resolves everything itself) so a
|
|
504
|
+
relative path resolves correctly whether `root` is the repo root
|
|
505
|
+
(the documented `/code-review` invocation) or a subdirectory. See
|
|
506
|
+
`_findings_matching_paths` for the full matching contract."""
|
|
507
|
+
target_paths = {root / f for f in files} | {Path(f) for f in files}
|
|
508
|
+
return _findings_matching_paths(findings, target_paths, root)
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def analyze_files(root: Path, files: list[str]) -> list[Finding]:
|
|
512
|
+
"""Runs the FULL `analyze(root)` (unchanged full walk, full first-party
|
|
513
|
+
index — same invariant `analyze_since` follows: scoping only ever
|
|
514
|
+
filters findings, never narrows what `analyze()` itself scans), then
|
|
515
|
+
filters to `files`. Unlike `analyze_since`, there is no git-dependent
|
|
516
|
+
failure mode here — `files` is a plain caller-supplied list, so this
|
|
517
|
+
always succeeds."""
|
|
518
|
+
return filter_by_files(analyze(root), files, root)
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def analyze_since(root: Path, ref: str) -> tuple[list[Finding], str | None]:
|
|
522
|
+
"""Runs the FULL `analyze(root)` (unchanged full walk, full first-party
|
|
523
|
+
index), then either filters it to `ref`'s changed-file set or, on a git
|
|
524
|
+
failure, returns the full unfiltered findings alongside a non-None
|
|
525
|
+
advisory reason.
|
|
526
|
+
|
|
527
|
+
The two possible return shapes are NOT interchangeable for a caller:
|
|
528
|
+
`(filtered_findings, None)` means "scoping succeeded, findings are
|
|
529
|
+
accurate to the diff"; `(unfiltered_findings, "<reason>")` means "git
|
|
530
|
+
could not answer the scoping question at all" — a caller that treats a
|
|
531
|
+
non-None reason as just noise and evaluates the findings anyway is
|
|
532
|
+
choosing to fall back to whole-repo behavior, which is the CLI's
|
|
533
|
+
deliberate, disclosed policy (see `main()`'s `--changed-since`) and NOT
|
|
534
|
+
the hook's (see `hooks/internal_double_gate.py`, which always fails open
|
|
535
|
+
to no-block on a non-None reason instead).
|
|
536
|
+
|
|
537
|
+
Disclosed scope note: `analyze(root)` reads the WORKING TREE, while
|
|
538
|
+
`changed_files_since()`'s file list comes from the COMMITTED diff
|
|
539
|
+
(`ref...HEAD`). A finding is kept whenever its FILE is in the committed
|
|
540
|
+
diff, even if the doubling line itself only exists in an uncommitted
|
|
541
|
+
edit to that same file — narrow in practice (`/pr` requires a clean
|
|
542
|
+
tree before opening), and errs toward blocking rather than passing."""
|
|
543
|
+
findings = analyze(root)
|
|
544
|
+
changed = changed_files_since(ref, root)
|
|
545
|
+
if changed is None:
|
|
546
|
+
return findings, f"could not resolve changed files since {ref!r}"
|
|
547
|
+
return filter_since(findings, changed, root), None
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
# --- CLI ----------------------------------------------------------------------
|
|
551
|
+
|
|
552
|
+
_RECALL_BOUNDS_STATEMENT = (
|
|
553
|
+
"Recall bounds: type resolution is name-based, not fully-qualified — a "
|
|
554
|
+
"same-named third-party type can false-positive, and a re-exported/"
|
|
555
|
+
"aliased first-party type can be missed. Waiver checks verify syntax "
|
|
556
|
+
"for B1/B3 (undecidable per the epic) and one mechanical ambient-API "
|
|
557
|
+
"evidence check for B2 only — never full waiver truth. "
|
|
558
|
+
"test-smell-review owns the final judgment call in every case."
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def _summarize(findings: list[Finding]) -> str:
|
|
563
|
+
counts: dict[str, int] = {}
|
|
564
|
+
for f in findings:
|
|
565
|
+
counts[f["verdict"]] = counts.get(f["verdict"], 0) + 1
|
|
566
|
+
if not counts:
|
|
567
|
+
return "0 findings"
|
|
568
|
+
return ", ".join(f"{n} {v}" for v, n in sorted(counts.items()))
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def main(argv: list[str] | None = None) -> int:
|
|
572
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
573
|
+
parser.add_argument("path", help="Directory to scan")
|
|
574
|
+
parser.add_argument(
|
|
575
|
+
"--strict", action="store_true", help="Exit nonzero if any 'high' finding exists"
|
|
576
|
+
)
|
|
577
|
+
parser.add_argument("--json", action="store_true", help="Emit findings as JSON")
|
|
578
|
+
scope_group = parser.add_mutually_exclusive_group()
|
|
579
|
+
scope_group.add_argument(
|
|
580
|
+
"--changed-since",
|
|
581
|
+
metavar="REF",
|
|
582
|
+
help=(
|
|
583
|
+
"Scope findings to files changed vs REF (git diff --name-only "
|
|
584
|
+
"REF...HEAD). On a git failure (bad ref, no merge-base, not a "
|
|
585
|
+
"repo), falls back to the full unfiltered scan with an ADVISORY "
|
|
586
|
+
"line — this fallback is CLI-only; the blocking hook never "
|
|
587
|
+
"adopts it (see hooks/internal_double_gate.py)."
|
|
588
|
+
),
|
|
589
|
+
)
|
|
590
|
+
scope_group.add_argument(
|
|
591
|
+
"--files",
|
|
592
|
+
nargs="*",
|
|
593
|
+
default=None,
|
|
594
|
+
help=(
|
|
595
|
+
"Scope findings to this explicit file list (repo-relative or "
|
|
596
|
+
"absolute) — the same list a caller like /code-review's step 2b "
|
|
597
|
+
"already computed for repo_invariants.py --files. Unlike "
|
|
598
|
+
"--changed-since, this has no git-dependent failure mode."
|
|
599
|
+
),
|
|
600
|
+
)
|
|
601
|
+
args = parser.parse_args(argv)
|
|
602
|
+
|
|
603
|
+
root = Path(args.path)
|
|
604
|
+
if not root.exists():
|
|
605
|
+
print(f"error: path not found: {root}", file=sys.stderr)
|
|
606
|
+
return 2
|
|
607
|
+
|
|
608
|
+
if args.changed_since:
|
|
609
|
+
findings, advisory_reason = analyze_since(root, args.changed_since)
|
|
610
|
+
if advisory_reason:
|
|
611
|
+
print(f"ADVISORY: internal_double_detector: {advisory_reason} — falling back to an unscoped full-tree scan")
|
|
612
|
+
elif args.files is not None:
|
|
613
|
+
findings = analyze_files(root, args.files)
|
|
614
|
+
else:
|
|
615
|
+
findings = analyze(root)
|
|
616
|
+
|
|
617
|
+
if args.json:
|
|
618
|
+
print(json.dumps({"findings": findings}, sort_keys=True))
|
|
619
|
+
else:
|
|
620
|
+
print(_summarize(findings))
|
|
621
|
+
for f in findings:
|
|
622
|
+
print(f["message"])
|
|
623
|
+
print(_RECALL_BOUNDS_STATEMENT)
|
|
624
|
+
|
|
625
|
+
if args.strict and any(f["verdict"] == "high" for f in findings):
|
|
626
|
+
return 1
|
|
627
|
+
return 0
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
if __name__ == "__main__":
|
|
631
|
+
raise SystemExit(main(sys.argv[1:]))
|