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,428 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""scan_bash_command_for_banned_scripts.py — real-time PreToolUse denial of new
|
|
3
|
+
shell-script writes under plugins/dev-team/ (#1755).
|
|
4
|
+
|
|
5
|
+
This repo's own CLAUDE.md states the rule ("every shipped
|
|
6
|
+
plugins/dev-team/ script is Python 3.10+ stdlib-only", ADR 0014/0015) and CI
|
|
7
|
+
has a `check-python-only` gate, but neither stops a `.sh`/`.bash`/`.bat`/
|
|
8
|
+
`.cmd`/`.ps1` file from being written *during* a session — the violation was
|
|
9
|
+
only caught later, in code review or CI. This hook closes that gap at the
|
|
10
|
+
point of the `Bash` tool call itself, before the write happens.
|
|
11
|
+
|
|
12
|
+
Heuristic, not exhaustive: it regex/token-scans the command string for
|
|
13
|
+
redirect (`>`, `>>`), `cp`, `mv`, and `tee` write targets. A sufficiently
|
|
14
|
+
obfuscated command (base64-decoded write, a wrapper script, `eval`) can
|
|
15
|
+
evade it — this hook's own scan says so, deliberately, rather than implying
|
|
16
|
+
a guarantee it can't back. `scan_worktree_for_banned_scripts.py` (PostToolUse,
|
|
17
|
+
`*` matcher) is the required backstop: it inspects the real working tree
|
|
18
|
+
after every tool call, catching anything this scan misses regardless of which
|
|
19
|
+
tool wrote it.
|
|
20
|
+
|
|
21
|
+
Contract (docs/python-hook-contract.md):
|
|
22
|
+
Input : PreToolUse JSON on stdin with tool_input.command (Bash matcher)
|
|
23
|
+
Output: exit 0 = allow; exit 2 = block (message on stdout AND stderr)
|
|
24
|
+
|
|
25
|
+
Stdlib-only. See ADR 0014/0015.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import re
|
|
31
|
+
import shlex
|
|
32
|
+
import sys
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
|
|
35
|
+
_SCRIPT_DIR = Path(__file__).resolve().parent
|
|
36
|
+
_LIB_DIR = _SCRIPT_DIR / "lib"
|
|
37
|
+
if str(_LIB_DIR) not in sys.path:
|
|
38
|
+
sys.path.insert(0, str(_LIB_DIR))
|
|
39
|
+
|
|
40
|
+
from artifact_paths import project_root
|
|
41
|
+
from banned_scripts_policy import (
|
|
42
|
+
ALLOWED_RELATIVE_PATHS,
|
|
43
|
+
BANNED_EXTENSIONS,
|
|
44
|
+
SCOPED_PREFIX,
|
|
45
|
+
looks_like_monorepo_checkout,
|
|
46
|
+
)
|
|
47
|
+
from stdin_json import read_stdin_json
|
|
48
|
+
|
|
49
|
+
# `>|` is tolerated between the operator and its target (bash's
|
|
50
|
+
# force-overwrite/noclobber-override redirect) — without it, `|` sits
|
|
51
|
+
# outside the capture class and the whole match silently fails, letting a
|
|
52
|
+
# `>|`-redirected banned write escape `_redirect_targets` entirely.
|
|
53
|
+
_REDIRECT_RE = re.compile(r">>?\|?\s*([^\s;|&<>]+)")
|
|
54
|
+
|
|
55
|
+
# (#1904 item 12) Shell metacharacters always tokenized on their own,
|
|
56
|
+
# regardless of adjacent whitespace, by `_tokenize_command`'s
|
|
57
|
+
# `punctuation_chars` lexer. `\n` is included so a bare newline between two
|
|
58
|
+
# commands is a real segment boundary rather than being swallowed as
|
|
59
|
+
# ordinary whitespace (the default `shlex` behavior, which would silently
|
|
60
|
+
# merge two unrelated commands' tokens into one stream).
|
|
61
|
+
#
|
|
62
|
+
# `|&` (bash shorthand for `2>&1 |`) and `;&` (a case-statement fallthrough
|
|
63
|
+
# terminator) are real command separators too — both group into a single
|
|
64
|
+
# lexer token, same as `&&`/`||`, and omitting them left a command like
|
|
65
|
+
# `true |& cp evil.sh dest.sh` as one un-split segment whose `prog` resolved
|
|
66
|
+
# to `true`, never reaching the cp/mv destination check at all.
|
|
67
|
+
_PUNCTUATION_CHARS = "();<>|&\n"
|
|
68
|
+
_COMMAND_SEPARATORS = frozenset({"&&", "||", ";", "&", "|", "|&", ";&", "\n"})
|
|
69
|
+
_OPERATOR_CHARS = frozenset("();<>|&")
|
|
70
|
+
|
|
71
|
+
# (#1904 item 13) GNU's attached-value form for a short-option cluster
|
|
72
|
+
# ending in `t` (`-tDIR`, `-rtDIR`) — the value is glued directly onto the
|
|
73
|
+
# cluster with no space, distinct from the detached `-t DIR` / `-rt DIR`
|
|
74
|
+
# form handled separately in `_parse_destination_args`.
|
|
75
|
+
_ATTACHED_TARGET_DIR_RE = re.compile(r"^-[^-]*t(?P<value>.+)$")
|
|
76
|
+
_TARGET_DIRECTORY_LONG_OPTION = "--target-directory"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _has_banned_extension(path_str: str) -> bool:
|
|
80
|
+
return path_str.lower().endswith(BANNED_EXTENSIONS)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _strip_quotes(token: str) -> str:
|
|
84
|
+
"""Strip one matching pair of leading/trailing quote characters.
|
|
85
|
+
|
|
86
|
+
The redirect regex's capture group has no notion of shell quoting, so
|
|
87
|
+
`> "evil.sh"` captures the target WITH its surrounding quotes — which
|
|
88
|
+
then fails `.endswith(".sh")` (#1864 sub-claim 3). This normalizes the
|
|
89
|
+
captured text before the extension check, nothing more.
|
|
90
|
+
"""
|
|
91
|
+
if len(token) >= 2 and token[0] == token[-1] and token[0] in ("'", '"'):
|
|
92
|
+
return token[1:-1]
|
|
93
|
+
return token
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _redirect_targets(command: str) -> list[str]:
|
|
97
|
+
targets = []
|
|
98
|
+
for match in _REDIRECT_RE.finditer(command):
|
|
99
|
+
target = _strip_quotes(match.group(1))
|
|
100
|
+
if _has_banned_extension(target):
|
|
101
|
+
targets.append(target)
|
|
102
|
+
return targets
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _match_long_target_directory(args: list[str], i: int, n: int) -> tuple[int, str | None]:
|
|
106
|
+
"""`--target-directory=DIR` or the detached `--target-directory DIR`.
|
|
107
|
+
|
|
108
|
+
Returns `(tokens_consumed, target_dir)` — `(0, None)` on no match, the
|
|
109
|
+
contract every matcher in `_TARGET_DIR_MATCHERS` shares.
|
|
110
|
+
"""
|
|
111
|
+
arg = args[i]
|
|
112
|
+
if arg.startswith("--target-directory="):
|
|
113
|
+
return 1, arg.split("=", 1)[1]
|
|
114
|
+
if arg == "--target-directory" and i + 1 < n:
|
|
115
|
+
return 2, args[i + 1]
|
|
116
|
+
return 0, None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _match_abbreviated_long_option(args: list[str], i: int, n: int) -> tuple[int, str | None]:
|
|
120
|
+
"""An unambiguous abbreviation of `--target-directory=` (e.g.
|
|
121
|
+
`--target-dir=DIR`) — cp/mv define no other `--target-*` long option,
|
|
122
|
+
so any prefix is unambiguous (mirrors GNU getopt_long's own
|
|
123
|
+
abbreviation rule). Checked before the `=` split so a boundary must
|
|
124
|
+
actually exist (#1904 item 13)."""
|
|
125
|
+
del n
|
|
126
|
+
arg = args[i]
|
|
127
|
+
if arg.startswith("--") and "=" in arg:
|
|
128
|
+
flag, _, value = arg.partition("=")
|
|
129
|
+
if len(flag) > 2 and _TARGET_DIRECTORY_LONG_OPTION.startswith(flag):
|
|
130
|
+
return 1, value
|
|
131
|
+
return 0, None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _match_attached_short_option(args: list[str], i: int, n: int) -> tuple[int, str | None]:
|
|
135
|
+
"""GNU's attached-value form: the value is glued directly onto a
|
|
136
|
+
short-option cluster ending in `t`, with no space (`-tDIR`, `-rtDIR`) —
|
|
137
|
+
distinct from the detached `-t DIR` form (#1904 item 13)."""
|
|
138
|
+
del n
|
|
139
|
+
arg = args[i]
|
|
140
|
+
if arg.startswith("-") and not arg.startswith("--"):
|
|
141
|
+
attached = _ATTACHED_TARGET_DIR_RE.match(arg)
|
|
142
|
+
if attached:
|
|
143
|
+
return 1, attached.group("value")
|
|
144
|
+
return 0, None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _match_clustered_short_option(args: list[str], i: int, n: int) -> tuple[int, str | None]:
|
|
148
|
+
"""A single-dash cluster ending in `t` (`-t`, `-rt`, `-at`, `-vt`, ...)
|
|
149
|
+
is GNU coreutils' clustered-short-option form of `-t DIR` — the
|
|
150
|
+
unclustered `-t` alone was the only form recognized before, so
|
|
151
|
+
`cp -rt DIR src.sh` fell through to the (wrong) last-positional
|
|
152
|
+
heuristic and evaded detection."""
|
|
153
|
+
arg = args[i]
|
|
154
|
+
if (
|
|
155
|
+
arg.startswith("-")
|
|
156
|
+
and not arg.startswith("--")
|
|
157
|
+
and len(arg) > 1
|
|
158
|
+
and arg.endswith("t")
|
|
159
|
+
and i + 1 < n
|
|
160
|
+
):
|
|
161
|
+
return 2, args[i + 1]
|
|
162
|
+
return 0, None
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
_TARGET_DIR_MATCHERS = (
|
|
166
|
+
_match_long_target_directory,
|
|
167
|
+
_match_abbreviated_long_option,
|
|
168
|
+
_match_attached_short_option,
|
|
169
|
+
_match_clustered_short_option,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _parse_destination_args(args: list[str]) -> tuple[str | None, list[str]]:
|
|
174
|
+
"""Tokenize `cp`/`mv` arguments (after the program name) into a GNU
|
|
175
|
+
target-directory value (`-t DIR` / `--target-directory=DIR` /
|
|
176
|
+
`--target-directory DIR`, plus the attached/abbreviated/clustered
|
|
177
|
+
variants in `_TARGET_DIR_MATCHERS`) and the remaining positional
|
|
178
|
+
arguments.
|
|
179
|
+
|
|
180
|
+
Returns `(target_dir, positionals)`. `target_dir` is `None` when no
|
|
181
|
+
target-directory form was used, and the caller falls back to treating
|
|
182
|
+
the last positional as the destination (#1875). A dispatch loop over
|
|
183
|
+
`_TARGET_DIR_MATCHERS`, in order — first match wins, mirroring the
|
|
184
|
+
original if/elif precedence.
|
|
185
|
+
"""
|
|
186
|
+
target_dir: str | None = None
|
|
187
|
+
positionals: list[str] = []
|
|
188
|
+
i = 0
|
|
189
|
+
n = len(args)
|
|
190
|
+
while i < n:
|
|
191
|
+
arg = args[i]
|
|
192
|
+
matched = False
|
|
193
|
+
for matcher in _TARGET_DIR_MATCHERS:
|
|
194
|
+
consumed, value = matcher(args, i, n)
|
|
195
|
+
if consumed:
|
|
196
|
+
target_dir = value
|
|
197
|
+
i += consumed
|
|
198
|
+
matched = True
|
|
199
|
+
break
|
|
200
|
+
if matched:
|
|
201
|
+
continue
|
|
202
|
+
if arg.startswith("-") and arg != "-":
|
|
203
|
+
i += 1
|
|
204
|
+
continue
|
|
205
|
+
positionals.append(arg)
|
|
206
|
+
i += 1
|
|
207
|
+
return target_dir, positionals
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _tokenize_command(command: str) -> list[str]:
|
|
211
|
+
"""Tokenize the whole command once, quote-aware, with shell
|
|
212
|
+
metacharacters always returned as their own tokens regardless of
|
|
213
|
+
adjacent whitespace (#1904 item 12b). This is what makes a quoted
|
|
214
|
+
separator or redirect character (`"a&b.sh"`, `"a>b.sh"`) survive as
|
|
215
|
+
literal content of the word it belongs to, instead of desyncing a plain
|
|
216
|
+
string split/regex over the raw command text — the previous approach's
|
|
217
|
+
`re.split` had no notion of quoting, so a separator character inside
|
|
218
|
+
quotes silently broke the word apart mid-token.
|
|
219
|
+
|
|
220
|
+
Raises `ValueError` on malformed quoting; callers must fail open (skip,
|
|
221
|
+
never crash) on that, matching the rest of this hook's contract.
|
|
222
|
+
"""
|
|
223
|
+
lexer = shlex.shlex(command, posix=True, punctuation_chars=_PUNCTUATION_CHARS)
|
|
224
|
+
lexer.whitespace_split = True
|
|
225
|
+
lexer.whitespace = " \t\r"
|
|
226
|
+
return list(lexer)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _segment_simple_commands(tokens: list[str]) -> list[list[str]]:
|
|
230
|
+
"""Split a flat token stream into one list per simple command, breaking
|
|
231
|
+
at command-separator tokens (`&&`, `||`, `;`, `&`, `|`, or a bare
|
|
232
|
+
newline) — the tokenized replacement for the old raw-string
|
|
233
|
+
`re.split(r"&&|\\|\\||[;|&\\n\\r]", command)` (#1904 item 12b)."""
|
|
234
|
+
segments: list[list[str]] = []
|
|
235
|
+
current: list[str] = []
|
|
236
|
+
for tok in tokens:
|
|
237
|
+
if tok in _COMMAND_SEPARATORS:
|
|
238
|
+
if current:
|
|
239
|
+
segments.append(current)
|
|
240
|
+
current = []
|
|
241
|
+
continue
|
|
242
|
+
current.append(tok)
|
|
243
|
+
if current:
|
|
244
|
+
segments.append(current)
|
|
245
|
+
return segments
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _is_operator_token(tok: str) -> bool:
|
|
249
|
+
"""True when every character in `tok` is a shell metacharacter — the
|
|
250
|
+
heuristic signature of an UNQUOTED occurrence.
|
|
251
|
+
|
|
252
|
+
KNOWN LIMITATION: this is imperfect in posix `shlex` mode. A wholly
|
|
253
|
+
quoted metacharacter sequence normalizes to the IDENTICAL token text as
|
|
254
|
+
the real unquoted operator — `shlex` strips quotes before this
|
|
255
|
+
function ever sees the token, so `tee ";" plugins/dev-team/hooks/
|
|
256
|
+
evil.sh` produces the same `";"` token as an actual unquoted `;`
|
|
257
|
+
separator, and `_segment_simple_commands` splits `tee` from its own
|
|
258
|
+
(quoted, literal) destination argument. This lets that destination
|
|
259
|
+
escape detection. A correct fix needs per-token quotedness tracked
|
|
260
|
+
from the lexer itself (subclassing `shlex.shlex.read_token` to record
|
|
261
|
+
whether a quote character was consumed) rather than inferred from the
|
|
262
|
+
token's content after the fact — a larger tokenizer rework than this
|
|
263
|
+
fix; see `test_quoted_semicolon_argument_known_limitation_evades_scan`
|
|
264
|
+
in the test suite for the exact repro, tracked as a follow-up rather
|
|
265
|
+
than silently left unaddressed."""
|
|
266
|
+
return bool(tok) and all(c in _OPERATOR_CHARS for c in tok)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _strip_redirects(tokens: list[str]) -> list[str]:
|
|
270
|
+
"""Drop every unquoted redirect/operator token, the operand
|
|
271
|
+
immediately following one, and a bare file-descriptor digit
|
|
272
|
+
immediately preceding one (`2>err.log`, `2>&1`), from a simple
|
|
273
|
+
command's token list (#1904 item 12a) — a trailing shell redirect
|
|
274
|
+
(`... 2>/dev/null`, `... > /dev/null`) must never be mistaken for a
|
|
275
|
+
`cp`/`mv` positional destination argument.
|
|
276
|
+
|
|
277
|
+
Any operator token CONTAINING `>` consumes its following operand, not
|
|
278
|
+
just the two literal forms `>`/`>>` — `shlex`'s `punctuation_chars`
|
|
279
|
+
lexer groups a run of adjacent metacharacters into one multi-character
|
|
280
|
+
token, so bash's `&>`, `>&`, `>|`, and fd-duplication forms like
|
|
281
|
+
`2>&1` all arrive as a single `>`-containing token distinct from a
|
|
282
|
+
bare `>`/`>>`. Treating only the two literal forms as redirects left
|
|
283
|
+
every one of those evading this heuristic entirely, since their
|
|
284
|
+
trailing operand (e.g. `/dev/null`, a bare fd number) would survive
|
|
285
|
+
into the positional-argument list and get mistaken for the real
|
|
286
|
+
destination.
|
|
287
|
+
"""
|
|
288
|
+
cleaned: list[str] = []
|
|
289
|
+
i = 0
|
|
290
|
+
n = len(tokens)
|
|
291
|
+
while i < n:
|
|
292
|
+
tok = tokens[i]
|
|
293
|
+
if (
|
|
294
|
+
tok.isdigit()
|
|
295
|
+
and i + 1 < n
|
|
296
|
+
and _is_operator_token(tokens[i + 1])
|
|
297
|
+
and ">" in tokens[i + 1]
|
|
298
|
+
):
|
|
299
|
+
i += 3 if i + 2 < n else 2
|
|
300
|
+
continue
|
|
301
|
+
if _is_operator_token(tok) and ">" in tok:
|
|
302
|
+
i += 2 if i + 1 < n else 1
|
|
303
|
+
continue
|
|
304
|
+
if _is_operator_token(tok):
|
|
305
|
+
i += 1
|
|
306
|
+
continue
|
|
307
|
+
cleaned.append(tok)
|
|
308
|
+
i += 1
|
|
309
|
+
return cleaned
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _target_dir_destinations(target_dir: str, positionals: list[str]) -> list[str]:
|
|
313
|
+
"""Expand a GNU target-directory `cp`/`mv` invocation (#1878) into its
|
|
314
|
+
effective per-source write targets: `target_dir/basename(source)` for
|
|
315
|
+
every remaining positional, filtered to banned extensions."""
|
|
316
|
+
destinations: list[str] = []
|
|
317
|
+
for source in positionals:
|
|
318
|
+
dest = f"{target_dir.rstrip('/')}/{Path(source).name}"
|
|
319
|
+
if _has_banned_extension(dest):
|
|
320
|
+
destinations.append(dest)
|
|
321
|
+
return destinations
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _copy_move_tee_targets(command: str) -> list[str]:
|
|
325
|
+
"""Best-effort tokenization to catch `cp`/`mv`/`tee` destinations.
|
|
326
|
+
|
|
327
|
+
Tokenizes the whole command once via `_tokenize_command` and segments
|
|
328
|
+
on the resulting command-separator tokens (#1904 item 12), rather than
|
|
329
|
+
regex-splitting the raw command string first. Each segment then has its
|
|
330
|
+
trailing shell redirect stripped via `_strip_redirects` before the
|
|
331
|
+
last-positional-is-the-destination heuristic runs. Malformed quoting is
|
|
332
|
+
skipped, never crashed on: a scan error must fail open, not block a
|
|
333
|
+
legitimate command it couldn't parse.
|
|
334
|
+
"""
|
|
335
|
+
targets: list[str] = []
|
|
336
|
+
try:
|
|
337
|
+
tokens = _tokenize_command(command)
|
|
338
|
+
except ValueError:
|
|
339
|
+
return targets
|
|
340
|
+
for segment in _segment_simple_commands(tokens):
|
|
341
|
+
segment = _strip_redirects(segment)
|
|
342
|
+
if not segment:
|
|
343
|
+
continue
|
|
344
|
+
prog = Path(segment[0]).name
|
|
345
|
+
if prog == "tee":
|
|
346
|
+
# tee writes to every named file, not just the last.
|
|
347
|
+
args = [t for t in segment[1:] if not t.startswith("-")]
|
|
348
|
+
targets.extend(a for a in args if _has_banned_extension(a))
|
|
349
|
+
elif prog in ("cp", "mv"):
|
|
350
|
+
target_dir, positionals = _parse_destination_args(segment[1:])
|
|
351
|
+
if target_dir is not None:
|
|
352
|
+
# GNU target-directory form (#1878): every remaining
|
|
353
|
+
# positional is a SOURCE being copied/moved INTO
|
|
354
|
+
# target_dir.
|
|
355
|
+
targets.extend(_target_dir_destinations(target_dir, positionals))
|
|
356
|
+
elif positionals and _has_banned_extension(positionals[-1]):
|
|
357
|
+
# Fixed #1875 heuristic: the destination is the actual last
|
|
358
|
+
# positional argument — never an earlier source, even one
|
|
359
|
+
# with a banned extension (e.g. `mv evil.sh good.py`, the
|
|
360
|
+
# correct remediation, must not be flagged).
|
|
361
|
+
targets.append(positionals[-1])
|
|
362
|
+
return targets
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _scoped_relative_path(target: str, cwd: Path, root: Path) -> str | None:
|
|
366
|
+
"""Resolve `target` (as written in the command) against `cwd`, then
|
|
367
|
+
return its path relative to `root` in POSIX form — or ``None`` when it
|
|
368
|
+
resolves outside `root` entirely."""
|
|
369
|
+
candidate = Path(target)
|
|
370
|
+
resolved = candidate if candidate.is_absolute() else (cwd / candidate)
|
|
371
|
+
try:
|
|
372
|
+
rel = resolved.resolve().relative_to(root.resolve())
|
|
373
|
+
except ValueError:
|
|
374
|
+
return None
|
|
375
|
+
return rel.as_posix()
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def find_banned_write(command: str, cwd: Path, root: Path) -> str | None:
|
|
379
|
+
"""Return the first plugins/dev-team/-scoped banned-extension write
|
|
380
|
+
target in `command` (relative to `root`), or ``None``. Skips the two
|
|
381
|
+
documented bootstrap-shim carve-outs."""
|
|
382
|
+
for target in [*_redirect_targets(command), *_copy_move_tee_targets(command)]:
|
|
383
|
+
rel = _scoped_relative_path(target, cwd, root)
|
|
384
|
+
if rel is None or not rel.startswith(SCOPED_PREFIX):
|
|
385
|
+
continue
|
|
386
|
+
if rel in ALLOWED_RELATIVE_PATHS:
|
|
387
|
+
continue
|
|
388
|
+
return rel
|
|
389
|
+
return None
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def main() -> int:
|
|
393
|
+
payload = read_stdin_json() or {}
|
|
394
|
+
|
|
395
|
+
tool_input = payload.get("tool_input")
|
|
396
|
+
command = tool_input.get("command") if isinstance(tool_input, dict) else None
|
|
397
|
+
if not isinstance(command, str) or not command.strip():
|
|
398
|
+
return 0
|
|
399
|
+
|
|
400
|
+
cwd = Path(payload.get("cwd") or ".")
|
|
401
|
+
|
|
402
|
+
# (#1861) Cheap, non-git early exit for any checkout where this
|
|
403
|
+
# monorepo's own plugins/dev-team/ tree doesn't exist — e.g. every
|
|
404
|
+
# downstream install of the plugin. Checked via a plain filesystem walk
|
|
405
|
+
# BEFORE project_root() (which itself shells out to git), not after.
|
|
406
|
+
if not looks_like_monorepo_checkout(cwd):
|
|
407
|
+
return 0
|
|
408
|
+
|
|
409
|
+
root = project_root(cwd)
|
|
410
|
+
hit = find_banned_write(command, cwd, root)
|
|
411
|
+
if hit is None:
|
|
412
|
+
return 0
|
|
413
|
+
|
|
414
|
+
message = (
|
|
415
|
+
f"[BLOCK] {hit} is a shell-script write under plugins/dev-team/. "
|
|
416
|
+
"Every shipped script there must be Python 3.10+ stdlib-only "
|
|
417
|
+
"(ADR 0014/0015; repo CLAUDE.md § Script authoring). Write a "
|
|
418
|
+
".py module instead, or confirm this is genuinely one of the two "
|
|
419
|
+
"documented bootstrap-shim exceptions (plugins/dev-team/install.sh, "
|
|
420
|
+
"plugins/dev-team/hooks/py.sh)."
|
|
421
|
+
)
|
|
422
|
+
sys.stdout.write(message + "\n")
|
|
423
|
+
sys.stderr.write(message + "\n")
|
|
424
|
+
return 2
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
if __name__ == "__main__":
|
|
428
|
+
sys.exit(main())
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""scan_worktree_for_banned_scripts.py — PostToolUse backstop for
|
|
3
|
+
scan_bash_command_for_banned_scripts.py (#1755).
|
|
4
|
+
|
|
5
|
+
The PreToolUse scan only intercepts a *Bash command string*; a banned
|
|
6
|
+
`.sh`/`.bash`/`.bat`/`.cmd`/`.ps1` file written any other way (the `Write` or
|
|
7
|
+
`Edit` tool, or a `Bash` command whose target the PreToolUse heuristic
|
|
8
|
+
missed) still lands under plugins/dev-team/. This hook is the deterministic
|
|
9
|
+
backstop: it runs after EVERY tool call (`*` matcher) and inspects the
|
|
10
|
+
actual working tree via `git status --porcelain`, so it catches a banned
|
|
11
|
+
file regardless of which tool created it.
|
|
12
|
+
|
|
13
|
+
The tool call that wrote the file has already completed by the time this
|
|
14
|
+
hook runs — exit 2 cannot undo that. What it does instead is what
|
|
15
|
+
`docs/python-hook-contract.md`'s block contract is for: it puts the offense
|
|
16
|
+
directly in front of the agent (stdout AND stderr) so the very next thing it
|
|
17
|
+
does is remove or convert the file, rather than the violation surviving
|
|
18
|
+
silently to code review or CI.
|
|
19
|
+
|
|
20
|
+
Contract (docs/python-hook-contract.md):
|
|
21
|
+
Input : PostToolUse JSON on stdin (matcher "*" — any tool_name)
|
|
22
|
+
Output: exit 0 = clean; exit 2 = block (message on stdout AND stderr)
|
|
23
|
+
|
|
24
|
+
Stdlib-only. See ADR 0014/0015.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import subprocess
|
|
30
|
+
import sys
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
_SCRIPT_DIR = Path(__file__).resolve().parent
|
|
34
|
+
_LIB_DIR = _SCRIPT_DIR / "lib"
|
|
35
|
+
if str(_LIB_DIR) not in sys.path:
|
|
36
|
+
sys.path.insert(0, str(_LIB_DIR))
|
|
37
|
+
|
|
38
|
+
from artifact_paths import project_root
|
|
39
|
+
from banned_scripts_policy import (
|
|
40
|
+
ALLOWED_RELATIVE_PATHS,
|
|
41
|
+
BANNED_EXTENSIONS,
|
|
42
|
+
SCOPED_PREFIX,
|
|
43
|
+
looks_like_monorepo_checkout,
|
|
44
|
+
)
|
|
45
|
+
from stdin_json import read_stdin_json
|
|
46
|
+
|
|
47
|
+
_GIT_TIMEOUT_S = 10
|
|
48
|
+
|
|
49
|
+
# Cap on files walked inside a single untracked directory (#1879). A
|
|
50
|
+
# wholly-new directory collapses to one `?? some/new/dir/` porcelain line —
|
|
51
|
+
# it must be recursed into on disk to see individual files — but an
|
|
52
|
+
# unbounded walk could hang on a pathological huge directory. This mirrors
|
|
53
|
+
# _GIT_TIMEOUT_S's intent (never let this scan hang indefinitely) with a
|
|
54
|
+
# count bound instead of a wall-clock one, since `Path.rglob` has no native
|
|
55
|
+
# timeout.
|
|
56
|
+
_MAX_UNTRACKED_DIR_ENTRIES = 5000
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _porcelain_paths(root: Path) -> list[str]:
|
|
60
|
+
"""Every path `git status --porcelain` reports under
|
|
61
|
+
`plugins/dev-team/`, tracked or untracked. Fails open (returns `[]`) on
|
|
62
|
+
any git failure — no repo, git missing, timeout — matching every other
|
|
63
|
+
advisory hook's contract: a scan that can't run must never block.
|
|
64
|
+
|
|
65
|
+
An entry whose path no longer exists on disk is skipped — checked by
|
|
66
|
+
actually stat-ing it, not by inferring "deleted" from a `D` in the
|
|
67
|
+
status code, which is wrong for an unresolved-merge conflict code
|
|
68
|
+
(`DU`/`UD`) where the path IS still present in the working tree. The
|
|
69
|
+
"old -> new" rename/copy arrow is only stripped when the status code
|
|
70
|
+
actually reports a rename or copy (`R`/`C`), not on every entry that
|
|
71
|
+
happens to contain the literal substring "-> ".
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
result = subprocess.run(
|
|
75
|
+
[
|
|
76
|
+
"git",
|
|
77
|
+
"-c",
|
|
78
|
+
"core.quotePath=false",
|
|
79
|
+
"status",
|
|
80
|
+
"--porcelain",
|
|
81
|
+
"--",
|
|
82
|
+
SCOPED_PREFIX.rstrip("/"),
|
|
83
|
+
],
|
|
84
|
+
cwd=str(root),
|
|
85
|
+
capture_output=True,
|
|
86
|
+
text=True,
|
|
87
|
+
check=False,
|
|
88
|
+
timeout=_GIT_TIMEOUT_S,
|
|
89
|
+
)
|
|
90
|
+
except (FileNotFoundError, OSError, subprocess.TimeoutExpired):
|
|
91
|
+
return []
|
|
92
|
+
if result.returncode != 0:
|
|
93
|
+
return []
|
|
94
|
+
|
|
95
|
+
paths = []
|
|
96
|
+
for line in result.stdout.splitlines():
|
|
97
|
+
if len(line) < 3:
|
|
98
|
+
continue
|
|
99
|
+
status = line[:2]
|
|
100
|
+
entry = line[3:]
|
|
101
|
+
if status[0] in ("R", "C") and "-> " in entry:
|
|
102
|
+
entry = entry.split("-> ", 1)[1]
|
|
103
|
+
entry = entry.strip().strip('"')
|
|
104
|
+
if not entry:
|
|
105
|
+
continue
|
|
106
|
+
# Test the actual runtime property ("is this path still present on
|
|
107
|
+
# disk") rather than inferring it from the status code: a plain `D`
|
|
108
|
+
# skip is wrong for an unresolved-merge conflict code (`DU`/`UD`),
|
|
109
|
+
# where the path IS present in the working tree despite carrying a
|
|
110
|
+
# `D` character.
|
|
111
|
+
if not (root / entry).exists():
|
|
112
|
+
continue
|
|
113
|
+
paths.append(entry)
|
|
114
|
+
return paths
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _files_in_untracked_dir(root: Path, rel_dir: str) -> tuple[list[str], bool]:
|
|
118
|
+
"""Recurse into a wholly-new untracked directory (a porcelain entry
|
|
119
|
+
ending in `/`), returning banned-extension files found inside, relative
|
|
120
|
+
to `root` in POSIX form. Bounded by `_MAX_UNTRACKED_DIR_ENTRIES` and
|
|
121
|
+
fails open (returns whatever was found so far) on any OS error.
|
|
122
|
+
|
|
123
|
+
Returns `(hits, truncated)` — `truncated` is `True` when the walk hit
|
|
124
|
+
`_MAX_UNTRACKED_DIR_ENTRIES` before exhausting the directory (#1904 item
|
|
125
|
+
8): a directory with more entries than the cap can hide a banned script
|
|
126
|
+
behind it, so the caller must be able to tell an exhaustive scan apart
|
|
127
|
+
from a capped one instead of treating both as equally complete.
|
|
128
|
+
|
|
129
|
+
Relativizes via the known `root`/`rel_dir` prefix, not `path.resolve()`
|
|
130
|
+
— resolving would follow a symlink named e.g. `evil.sh` to its target,
|
|
131
|
+
which can land outside `root` and raise `ValueError`, silently dropping
|
|
132
|
+
the entry. That's weaker than the sibling non-directory branch in
|
|
133
|
+
`_porcelain_paths`, which flags such a link (it never resolves at all);
|
|
134
|
+
this walk must not be the one gap in an otherwise-consistent scan.
|
|
135
|
+
"""
|
|
136
|
+
hits: list[str] = []
|
|
137
|
+
truncated = False
|
|
138
|
+
try:
|
|
139
|
+
for count, path in enumerate((root / rel_dir).rglob("*")):
|
|
140
|
+
if count >= _MAX_UNTRACKED_DIR_ENTRIES:
|
|
141
|
+
truncated = True
|
|
142
|
+
break
|
|
143
|
+
if not (path.is_file() or path.is_symlink()):
|
|
144
|
+
continue
|
|
145
|
+
if not path.name.lower().endswith(BANNED_EXTENSIONS):
|
|
146
|
+
continue
|
|
147
|
+
try:
|
|
148
|
+
rel = path.relative_to(root).as_posix()
|
|
149
|
+
except ValueError:
|
|
150
|
+
continue
|
|
151
|
+
hits.append(rel)
|
|
152
|
+
except OSError:
|
|
153
|
+
return hits, truncated
|
|
154
|
+
return hits, truncated
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def find_banned_files(root: Path) -> tuple[list[str], list[str]]:
|
|
158
|
+
"""Returns `(hits, truncated_dirs)`. `truncated_dirs` lists every
|
|
159
|
+
untracked directory (relative to `root`, POSIX form) whose recursive
|
|
160
|
+
walk hit `_MAX_UNTRACKED_DIR_ENTRIES` before finishing — a non-empty
|
|
161
|
+
list means `hits` may be incomplete for reasons other than "no banned
|
|
162
|
+
scripts exist" (#1904 item 8)."""
|
|
163
|
+
hits = set()
|
|
164
|
+
truncated_dirs: list[str] = []
|
|
165
|
+
for rel in _porcelain_paths(root):
|
|
166
|
+
if rel.endswith("/"):
|
|
167
|
+
# A brand-new untracked directory — expand it on disk (#1879)
|
|
168
|
+
# rather than checking the directory path string itself.
|
|
169
|
+
if not rel.startswith(SCOPED_PREFIX):
|
|
170
|
+
continue
|
|
171
|
+
found, truncated = _files_in_untracked_dir(root, rel)
|
|
172
|
+
if truncated:
|
|
173
|
+
truncated_dirs.append(rel)
|
|
174
|
+
for f in found:
|
|
175
|
+
if f in ALLOWED_RELATIVE_PATHS:
|
|
176
|
+
continue
|
|
177
|
+
hits.add(f)
|
|
178
|
+
continue
|
|
179
|
+
if not rel.startswith(SCOPED_PREFIX) or rel in ALLOWED_RELATIVE_PATHS:
|
|
180
|
+
continue
|
|
181
|
+
if rel.lower().endswith(BANNED_EXTENSIONS):
|
|
182
|
+
hits.add(rel)
|
|
183
|
+
return sorted(hits), sorted(truncated_dirs)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def main() -> int:
|
|
187
|
+
payload = read_stdin_json() or {}
|
|
188
|
+
cwd = Path(payload.get("cwd") or ".")
|
|
189
|
+
|
|
190
|
+
# (#1861) Cheap, non-git early exit for any checkout where this
|
|
191
|
+
# monorepo's own plugins/dev-team/ tree doesn't exist — e.g. every
|
|
192
|
+
# downstream install of the plugin. This hook runs on the PostToolUse
|
|
193
|
+
# `*` matcher (every tool call, forever), so it must not shell out to
|
|
194
|
+
# `git rev-parse`/`git status` at all when there is nothing under
|
|
195
|
+
# plugins/dev-team/ to scan — checked via a plain filesystem walk
|
|
196
|
+
# BEFORE project_root() (which itself shells out to git), not after.
|
|
197
|
+
if not looks_like_monorepo_checkout(cwd):
|
|
198
|
+
return 0
|
|
199
|
+
|
|
200
|
+
root = project_root(cwd)
|
|
201
|
+
hits, truncated_dirs = find_banned_files(root)
|
|
202
|
+
|
|
203
|
+
truncation_note = None
|
|
204
|
+
if truncated_dirs:
|
|
205
|
+
truncation_note = (
|
|
206
|
+
"[NOTE] scan of "
|
|
207
|
+
+ ", ".join(truncated_dirs)
|
|
208
|
+
+ f" stopped after {_MAX_UNTRACKED_DIR_ENTRIES} entries; "
|
|
209
|
+
"result may be incomplete."
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
if not hits:
|
|
213
|
+
# (#1904 item 8) A truncated scan found nothing WITHIN the entries
|
|
214
|
+
# it walked, but that is not the same claim as "no banned scripts
|
|
215
|
+
# exist" — surface the note even though this path still fails open
|
|
216
|
+
# (exit 0), matching this hook's fail-open contract.
|
|
217
|
+
if truncation_note:
|
|
218
|
+
sys.stdout.write(truncation_note + "\n")
|
|
219
|
+
sys.stderr.write(truncation_note + "\n")
|
|
220
|
+
return 0
|
|
221
|
+
|
|
222
|
+
message = (
|
|
223
|
+
"[BLOCK] Shell script(s) present under plugins/dev-team/ — every "
|
|
224
|
+
"shipped script there must be Python 3.10+ stdlib-only (ADR "
|
|
225
|
+
"0014/0015; repo CLAUDE.md § Script authoring): " + ", ".join(hits) + ". "
|
|
226
|
+
"Remove or convert to .py, or confirm this is genuinely one of the "
|
|
227
|
+
"two documented bootstrap-shim exceptions "
|
|
228
|
+
"(plugins/dev-team/install.sh, plugins/dev-team/hooks/py.sh)."
|
|
229
|
+
)
|
|
230
|
+
if truncation_note:
|
|
231
|
+
message += " " + truncation_note
|
|
232
|
+
sys.stdout.write(message + "\n")
|
|
233
|
+
sys.stderr.write(message + "\n")
|
|
234
|
+
return 2
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
if __name__ == "__main__":
|
|
238
|
+
sys.exit(main())
|