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,888 @@
|
|
|
1
|
+
"""progress_guardian.py — Python plan validator for the progress-guardian agent.
|
|
2
|
+
|
|
3
|
+
Validates plan step completion, commit discipline, and scope creep.
|
|
4
|
+
|
|
5
|
+
Exit codes:
|
|
6
|
+
0 = pass (all checks clean)
|
|
7
|
+
1 = fail (hard errors: missing commits, uncommitted work, incomplete steps)
|
|
8
|
+
2 = warn (warnings only or LLM check skipped)
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/progress_guardian.py --plan <path> [--pre-pr] [--skip-llm]
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import fnmatch
|
|
18
|
+
import json
|
|
19
|
+
import re
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
26
|
+
from lib.review_result import build_result, main_exit, make_issue, skipped_llm_warning
|
|
27
|
+
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
# Module constants (REFACTOR: extracted for easy maintenance)
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
# Matches: - [x] Step 1.1: Header text OR - [ ] Just a header
|
|
33
|
+
STEP_PATTERN = re.compile(r"^-\s+\[( |x|X)\]\s+(?:Step\s+[\d.]+:\s+)?(.+)$")
|
|
34
|
+
|
|
35
|
+
# Matches backtick-quoted paths in plan text (declared file references)
|
|
36
|
+
BACKTICK_PATH_RE = re.compile(r"`([^`\s]+\.[a-zA-Z0-9_]+)`")
|
|
37
|
+
|
|
38
|
+
# Matches backtick-quoted directory declarations (trailing '/'), e.g.
|
|
39
|
+
# `plugins/dev-team/skills/new-feature/`. Kept separate from
|
|
40
|
+
# BACKTICK_PATH_RE so extension-less, non-path backtick tokens (e.g.
|
|
41
|
+
# `runHook()`, `SomeClass`) are never mistaken for declared paths — see
|
|
42
|
+
# issue #713.
|
|
43
|
+
BACKTICK_DIR_RE = re.compile(r"`([^`\s]+/)`")
|
|
44
|
+
|
|
45
|
+
# Matches a slice's `**Files:** ...` declaration line. Captures the
|
|
46
|
+
# remainder of the line so BACKTICK_PATH_RE/BACKTICK_DIR_RE can extract
|
|
47
|
+
# the paths from it.
|
|
48
|
+
SLICE_FILES_RE = re.compile(r"^\*\*Files:\*\*\s*(.+)$")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _extract_declared_paths(text: str) -> list[str]:
|
|
52
|
+
"""Return backtick-quoted file paths and directory paths declared in
|
|
53
|
+
text: exact file paths (ending in a dot-extension) plus directory
|
|
54
|
+
declarations (ending in '/'). A directory declaration means "this
|
|
55
|
+
slice touches things under here" — matched with startswith, not
|
|
56
|
+
equality, by callers. Bare backtick-quoted tokens with no extension
|
|
57
|
+
and no trailing slash (e.g. `` `runHook()` ``) are not paths and are
|
|
58
|
+
intentionally excluded (see issue #713).
|
|
59
|
+
"""
|
|
60
|
+
return BACKTICK_PATH_RE.findall(text) + BACKTICK_DIR_RE.findall(text)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _is_path_declared(file_path: str, declared_paths) -> bool:
|
|
64
|
+
"""True if file_path exactly matches a declared file, falls under
|
|
65
|
+
a declared directory (trailing '/') prefix, or matches a declared glob
|
|
66
|
+
pattern (fnmatch, stdlib — issue #865 AC9, e.g. `src/auth/**`). Single
|
|
67
|
+
source of truth shared by check_commit_discipline, check_scope, and
|
|
68
|
+
check_declared_scope_adherence so these checks cannot drift on what
|
|
69
|
+
counts as "declared" (see issue #525/#713/#865).
|
|
70
|
+
"""
|
|
71
|
+
for declared in declared_paths:
|
|
72
|
+
if declared.endswith("/"):
|
|
73
|
+
if file_path.startswith(declared):
|
|
74
|
+
return True
|
|
75
|
+
elif file_path == declared or any(ch in declared for ch in "*?[") and fnmatch.fnmatch(
|
|
76
|
+
file_path, declared
|
|
77
|
+
):
|
|
78
|
+
return True
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
# Data model
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class Step:
|
|
89
|
+
done: bool
|
|
90
|
+
header: str
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
# Helpers
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def run_git(args: list[str], cwd: str) -> str:
|
|
99
|
+
"""Run a git command in cwd, return stdout. Returns '' on failure."""
|
|
100
|
+
try:
|
|
101
|
+
result = subprocess.run(
|
|
102
|
+
["git"] + args,
|
|
103
|
+
capture_output=True,
|
|
104
|
+
text=True,
|
|
105
|
+
cwd=cwd,
|
|
106
|
+
check=False,
|
|
107
|
+
)
|
|
108
|
+
return result.stdout
|
|
109
|
+
except (FileNotFoundError, OSError):
|
|
110
|
+
return ""
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
# Core checks
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def parse_plan(path: Path) -> tuple[list[Step], list[dict]]:
|
|
119
|
+
"""Parse plan file and return (steps, errors).
|
|
120
|
+
|
|
121
|
+
Scopes checkbox parsing with two flags when a Build Progress section
|
|
122
|
+
exists (issue #525):
|
|
123
|
+
|
|
124
|
+
- `in_build_progress` (outer scope) — when the plan contains a
|
|
125
|
+
`## Build Progress` heading, only lines between that heading and the
|
|
126
|
+
next `## ` H2 are eligible for step parsing. This prevents the
|
|
127
|
+
top-level `## Acceptance Criteria` section's checkboxes from being
|
|
128
|
+
treated as Build Progress steps.
|
|
129
|
+
- `in_acceptance` (inner skip) — within the Build Progress section,
|
|
130
|
+
the `/plan` skill renders a documentation mirror of the top-level
|
|
131
|
+
AC list under a `### Acceptance Criteria` H3 sub-heading. Those
|
|
132
|
+
mirror items lack work-tracking semantics (no `### Slice` heading,
|
|
133
|
+
no `**Files:**` line, no commit subject anchor). They are skipped
|
|
134
|
+
so the pre-PR gate doesn't demand a commit for each one. The
|
|
135
|
+
structural problem behind the workaround is tracked in #526.
|
|
136
|
+
|
|
137
|
+
When no `## Build Progress` heading is present (legacy/minimal plans),
|
|
138
|
+
falls back to whole-file scanning — preserves backward compatibility
|
|
139
|
+
with the pre-existing bats fixtures that construct minimal plans.
|
|
140
|
+
|
|
141
|
+
Returns an error finding (naming the file) if no checkboxes found.
|
|
142
|
+
"""
|
|
143
|
+
text = path.read_text(encoding="utf-8")
|
|
144
|
+
|
|
145
|
+
# First, see whether the plan has a `## Build Progress` heading. If it
|
|
146
|
+
# does, narrow the scan to that section only. Otherwise, scan the whole
|
|
147
|
+
# file (preserves backward compatibility with plans that don't carry
|
|
148
|
+
# the section structure).
|
|
149
|
+
lines = text.splitlines()
|
|
150
|
+
has_build_progress = any(
|
|
151
|
+
line.rstrip().startswith("## Build Progress") for line in lines
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
steps: list[Step] = []
|
|
155
|
+
in_build_progress = not has_build_progress # legacy plans: scan everything
|
|
156
|
+
in_acceptance = False
|
|
157
|
+
for raw in lines:
|
|
158
|
+
line = raw.rstrip()
|
|
159
|
+
if has_build_progress:
|
|
160
|
+
if line.startswith("## Build Progress"):
|
|
161
|
+
in_build_progress = True
|
|
162
|
+
in_acceptance = False
|
|
163
|
+
continue
|
|
164
|
+
# Any other H2 closes the section.
|
|
165
|
+
if in_build_progress and line.startswith("## "):
|
|
166
|
+
in_build_progress = False
|
|
167
|
+
continue
|
|
168
|
+
# The `### Acceptance Criteria` subheading inside Build Progress
|
|
169
|
+
# is a documentation mirror — the same items appear in the
|
|
170
|
+
# top-level `## Acceptance Criteria`. Skip its checkboxes so
|
|
171
|
+
# they don't demand commits of their own. The structural
|
|
172
|
+
# redesign of where ACs live is tracked in #526; this PR
|
|
173
|
+
# ships the workaround so the gate stops false-positiving.
|
|
174
|
+
if in_build_progress and line.startswith("### Acceptance Criteria"):
|
|
175
|
+
in_acceptance = True
|
|
176
|
+
continue
|
|
177
|
+
# Any other H3 within Build Progress exits the AC subsection.
|
|
178
|
+
if in_acceptance and line.startswith("### "):
|
|
179
|
+
in_acceptance = False
|
|
180
|
+
if not in_build_progress or in_acceptance:
|
|
181
|
+
continue
|
|
182
|
+
m = STEP_PATTERN.match(line)
|
|
183
|
+
if m:
|
|
184
|
+
flag, header = m.group(1), m.group(2).strip()
|
|
185
|
+
steps.append(Step(done=(flag.lower() == "x"), header=header))
|
|
186
|
+
|
|
187
|
+
if not steps:
|
|
188
|
+
return [], [
|
|
189
|
+
make_issue(
|
|
190
|
+
"error",
|
|
191
|
+
message=f"No checkbox steps found in plan file: {path.name}",
|
|
192
|
+
file=str(path),
|
|
193
|
+
suggested_fix="Add steps with '- [ ] Step N.M: description' format.",
|
|
194
|
+
)
|
|
195
|
+
]
|
|
196
|
+
return steps, []
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _parse_slice_files(plan_text: str, slice_header: str) -> list[str]:
|
|
200
|
+
"""Return backtick-quoted paths from the `**Files:**` line declared
|
|
201
|
+
under the slice identified by `slice_header`. Empty list when not found.
|
|
202
|
+
|
|
203
|
+
Two layouts are supported:
|
|
204
|
+
1. **Heading-anchored** (the /plan skill convention): the slice is
|
|
205
|
+
declared with `### Slice N: title` and `**Files:** ...` lives in
|
|
206
|
+
that section's body. Build Progress checkboxes mirror the heading
|
|
207
|
+
text — we match the heading and scan forward.
|
|
208
|
+
2. **Inline** (minimal plans): `- [x] Slice N: title` followed by a
|
|
209
|
+
`**Files:** ...` line in the same checkbox block. Scan forward
|
|
210
|
+
from the checkbox until another checkbox or a heading.
|
|
211
|
+
|
|
212
|
+
Heading-anchored is tried first because real plans always use it. The
|
|
213
|
+
inline form preserves a smaller fixture surface for tests and the
|
|
214
|
+
minimal plans the existing bats fixtures construct.
|
|
215
|
+
"""
|
|
216
|
+
lines = plan_text.splitlines()
|
|
217
|
+
|
|
218
|
+
# Try heading-anchored layout first.
|
|
219
|
+
in_block = False
|
|
220
|
+
for raw in lines:
|
|
221
|
+
line = raw.rstrip()
|
|
222
|
+
if line.startswith("### ") and line[4:].strip() == slice_header:
|
|
223
|
+
in_block = True
|
|
224
|
+
continue
|
|
225
|
+
if not in_block:
|
|
226
|
+
continue
|
|
227
|
+
if line.startswith(("## ", "### ")):
|
|
228
|
+
break
|
|
229
|
+
fm = SLICE_FILES_RE.match(line)
|
|
230
|
+
if fm:
|
|
231
|
+
return _extract_declared_paths(fm.group(1))
|
|
232
|
+
|
|
233
|
+
# Fall back to inline layout: `**Files:**` line near a `- [x] <header>`.
|
|
234
|
+
in_block = False
|
|
235
|
+
for raw in lines:
|
|
236
|
+
line = raw.rstrip()
|
|
237
|
+
m = STEP_PATTERN.match(line)
|
|
238
|
+
if m:
|
|
239
|
+
header = m.group(2).strip()
|
|
240
|
+
if header == slice_header:
|
|
241
|
+
in_block = True
|
|
242
|
+
continue
|
|
243
|
+
if in_block:
|
|
244
|
+
# A different checkbox closes the block.
|
|
245
|
+
break
|
|
246
|
+
if not in_block:
|
|
247
|
+
continue
|
|
248
|
+
if line.startswith(("## ", "### ")):
|
|
249
|
+
break
|
|
250
|
+
fm = SLICE_FILES_RE.match(line)
|
|
251
|
+
if fm:
|
|
252
|
+
return _extract_declared_paths(fm.group(1))
|
|
253
|
+
return []
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _branch_base_sha(repo_root: str) -> str | None:
|
|
257
|
+
"""Resolve the branch base SHA (the merge-base with trunk).
|
|
258
|
+
|
|
259
|
+
Prefers remote tracking refs over local refs — local main lags
|
|
260
|
+
origin/main the moment any work begins. Returns None when no
|
|
261
|
+
trunk-like ref resolves to a divergence point; callers should
|
|
262
|
+
treat None as "no meaningful branch base — use whole history."
|
|
263
|
+
See issue #525.
|
|
264
|
+
"""
|
|
265
|
+
head_sha = run_git(["rev-parse", "HEAD"], repo_root).strip()
|
|
266
|
+
for branch in ("origin/main", "origin/master", "main", "master"):
|
|
267
|
+
out = run_git(["merge-base", "HEAD", branch], repo_root).strip()
|
|
268
|
+
if out and out != head_sha:
|
|
269
|
+
return out
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def check_commit_discipline(
|
|
274
|
+
steps: list[Step], repo_root: str, plan_text: str = ""
|
|
275
|
+
) -> list[dict]:
|
|
276
|
+
"""For each done step, verify a matching commit exists on this branch.
|
|
277
|
+
|
|
278
|
+
Two strategies, tried in order per step:
|
|
279
|
+
1. **File-path match (preferred).** When the slice declares
|
|
280
|
+
`**Files:** ...`, pass when any commit since the branch base
|
|
281
|
+
touched one of the declared files. This decouples the gate
|
|
282
|
+
from commit-subject wording so Conventional Commits work.
|
|
283
|
+
2. **Substring fallback.** When the slice has no `**Files:**`
|
|
284
|
+
line (legacy plans), pass when any commit subject since the
|
|
285
|
+
branch base contains the slice header. Same matcher as before,
|
|
286
|
+
but scoped to `<base>..HEAD` so both strategies see the same
|
|
287
|
+
commit range (cannot diverge — see issue #525).
|
|
288
|
+
|
|
289
|
+
Returns error findings for any done step with no matching commit.
|
|
290
|
+
"""
|
|
291
|
+
done_steps = [s for s in steps if s.done]
|
|
292
|
+
if not done_steps:
|
|
293
|
+
return []
|
|
294
|
+
|
|
295
|
+
base_sha = _branch_base_sha(repo_root)
|
|
296
|
+
|
|
297
|
+
# Build the commit range. When no base resolves (orphan HEAD), fall
|
|
298
|
+
# back to whole-history matching so legacy behavior is preserved.
|
|
299
|
+
if base_sha:
|
|
300
|
+
range_arg = f"{base_sha}..HEAD"
|
|
301
|
+
log_subjects_out = run_git(
|
|
302
|
+
["log", "--no-merges", "--format=%s", range_arg], repo_root
|
|
303
|
+
)
|
|
304
|
+
else:
|
|
305
|
+
range_arg = "HEAD"
|
|
306
|
+
log_subjects_out = run_git(
|
|
307
|
+
["log", "--no-merges", "--format=%s", "HEAD"], repo_root
|
|
308
|
+
)
|
|
309
|
+
if not log_subjects_out.strip():
|
|
310
|
+
# Zero commits on this branch — emit a warning, not a hard error.
|
|
311
|
+
return [
|
|
312
|
+
make_issue(
|
|
313
|
+
"warning",
|
|
314
|
+
message="No commits found on this branch — cannot verify commit discipline.",
|
|
315
|
+
suggested_fix="Commit work before marking steps done.",
|
|
316
|
+
)
|
|
317
|
+
]
|
|
318
|
+
commit_subjects = [
|
|
319
|
+
s.lower() for s in log_subjects_out.strip().splitlines() if s.strip()
|
|
320
|
+
]
|
|
321
|
+
|
|
322
|
+
# Collect file paths touched by commits in the range (file-path path).
|
|
323
|
+
if base_sha:
|
|
324
|
+
diff_out = run_git(["diff", "--name-only", range_arg], repo_root)
|
|
325
|
+
else:
|
|
326
|
+
diff_out = run_git(
|
|
327
|
+
["log", "--name-only", "--pretty=format:", "HEAD"], repo_root
|
|
328
|
+
)
|
|
329
|
+
branch_files = {p for p in diff_out.strip().splitlines() if p.strip()}
|
|
330
|
+
|
|
331
|
+
errors: list[dict] = []
|
|
332
|
+
for step in done_steps:
|
|
333
|
+
declared = _parse_slice_files(plan_text, step.header) if plan_text else []
|
|
334
|
+
if declared:
|
|
335
|
+
# File-path path: pass when any declared file was touched on
|
|
336
|
+
# this branch, or any branch file falls under a declared
|
|
337
|
+
# directory (see issue #713).
|
|
338
|
+
if any(_is_path_declared(bf, declared) for bf in branch_files):
|
|
339
|
+
continue
|
|
340
|
+
errors.append(
|
|
341
|
+
make_issue(
|
|
342
|
+
"error",
|
|
343
|
+
message=(
|
|
344
|
+
f"Done step '{step.header}' has no matching commit in git log. "
|
|
345
|
+
f"Expected a commit on this branch that touched one of: "
|
|
346
|
+
f"{', '.join(declared)}"
|
|
347
|
+
),
|
|
348
|
+
suggested_fix=(
|
|
349
|
+
f"Commit your work touching one of the declared files "
|
|
350
|
+
f"({', '.join(declared)}) before marking '{step.header}' done."
|
|
351
|
+
),
|
|
352
|
+
)
|
|
353
|
+
)
|
|
354
|
+
continue
|
|
355
|
+
# Substring fallback: no `**Files:**` declared.
|
|
356
|
+
header_lower = step.header.lower()
|
|
357
|
+
if not any(header_lower in subject for subject in commit_subjects):
|
|
358
|
+
errors.append(
|
|
359
|
+
make_issue(
|
|
360
|
+
"error",
|
|
361
|
+
message=(
|
|
362
|
+
f"Done step '{step.header}' has no matching commit in git log. "
|
|
363
|
+
f"Expected a commit whose subject contains: '{step.header}'"
|
|
364
|
+
),
|
|
365
|
+
suggested_fix=(
|
|
366
|
+
f"Commit your work with a message containing '{step.header}' "
|
|
367
|
+
f"before marking this step done."
|
|
368
|
+
),
|
|
369
|
+
)
|
|
370
|
+
)
|
|
371
|
+
return errors
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def check_uncommitted(
|
|
375
|
+
repo_root: str, exclude_paths: list[str] | None = None
|
|
376
|
+
) -> list[dict]:
|
|
377
|
+
"""Check for staged or unstaged changes. Returns an error if any exist.
|
|
378
|
+
|
|
379
|
+
exclude_paths: relative or absolute paths to ignore (e.g. the plan file itself).
|
|
380
|
+
"""
|
|
381
|
+
status_output = run_git(["status", "--porcelain"], repo_root)
|
|
382
|
+
if not status_output.strip():
|
|
383
|
+
return []
|
|
384
|
+
|
|
385
|
+
# Filter out excluded paths
|
|
386
|
+
excluded = set(exclude_paths or [])
|
|
387
|
+
non_excluded_lines = []
|
|
388
|
+
for line in status_output.strip().splitlines():
|
|
389
|
+
if not line.strip():
|
|
390
|
+
continue
|
|
391
|
+
parts = line.strip().split(None, 1)
|
|
392
|
+
file_path = parts[1].strip() if len(parts) == 2 else ""
|
|
393
|
+
# Normalize: check both basename and full relative path
|
|
394
|
+
if file_path and file_path not in excluded:
|
|
395
|
+
import os
|
|
396
|
+
|
|
397
|
+
basename = os.path.basename(file_path)
|
|
398
|
+
if basename not in excluded:
|
|
399
|
+
non_excluded_lines.append(line)
|
|
400
|
+
|
|
401
|
+
if non_excluded_lines:
|
|
402
|
+
return [
|
|
403
|
+
make_issue(
|
|
404
|
+
"error",
|
|
405
|
+
message=(
|
|
406
|
+
"Uncommitted changes detected. All work must be committed "
|
|
407
|
+
"before plan steps can be considered complete."
|
|
408
|
+
),
|
|
409
|
+
suggested_fix="Run 'git add' and 'git commit' to commit outstanding work.",
|
|
410
|
+
)
|
|
411
|
+
]
|
|
412
|
+
return []
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def check_pre_pr(steps: list[Step]) -> list[dict]:
|
|
416
|
+
"""Assert all steps are done (checked). Returns error for each undone step."""
|
|
417
|
+
errors: list[dict] = []
|
|
418
|
+
for step in steps:
|
|
419
|
+
if not step.done:
|
|
420
|
+
errors.append(
|
|
421
|
+
make_issue(
|
|
422
|
+
"error",
|
|
423
|
+
message=(
|
|
424
|
+
f"Pre-PR gate: step '{step.header}' is not done ([ ] unchecked). "
|
|
425
|
+
f"All plan steps must be complete before opening a PR."
|
|
426
|
+
),
|
|
427
|
+
suggested_fix=f"Complete and commit work for '{step.header}', then mark it [x].",
|
|
428
|
+
)
|
|
429
|
+
)
|
|
430
|
+
return errors
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
VERIFY_LOG_PATH = "metrics/verify-log.jsonl"
|
|
434
|
+
|
|
435
|
+
# Test-file indicators — kept in sync with knowledge/test-file-indicators.md
|
|
436
|
+
# so this Python check and the /build skill's prose classification agree on
|
|
437
|
+
# what counts as "a test" (see issue #727).
|
|
438
|
+
_TEST_NAME_RE = re.compile(r"\.(test|spec)\.[^./]+$")
|
|
439
|
+
_TEST_DIR_FRAGMENTS = ("__tests__/",)
|
|
440
|
+
_STEP_DEFINITION_RE = re.compile(
|
|
441
|
+
r"(\.steps\.[^./]+$|StepDefinitions\.[^./]+$|Steps\.[^./]+$)", re.IGNORECASE
|
|
442
|
+
)
|
|
443
|
+
_TEST_CLASS_NAME_RE = re.compile(r"(Test|Tests|TestCase|Spec)\.java$")
|
|
444
|
+
_TEST_ATTRIBUTE_RE = re.compile(
|
|
445
|
+
r"\[Fact\]|\[Theory\]|\[TestCase\]|\[TestMethod\]|\[TestClass\]|"
|
|
446
|
+
r"(?<!Parameterized)(?<!Factory)\[Test\]|@Test\b|@ParameterizedTest|@TestFactory"
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
# Docs/config files never carry a runtime surface — no source ever executes
|
|
450
|
+
# from them, so a diff touching only these (plus tests) has nothing for
|
|
451
|
+
# /verify to drive.
|
|
452
|
+
_DOC_CONFIG_SUFFIXES = {
|
|
453
|
+
".md",
|
|
454
|
+
".mdx",
|
|
455
|
+
".txt",
|
|
456
|
+
".rst",
|
|
457
|
+
".adoc",
|
|
458
|
+
".yml",
|
|
459
|
+
".yaml",
|
|
460
|
+
".toml",
|
|
461
|
+
".ini",
|
|
462
|
+
".cfg",
|
|
463
|
+
".editorconfig",
|
|
464
|
+
}
|
|
465
|
+
_DOC_CONFIG_BASENAMES = {
|
|
466
|
+
"LICENSE",
|
|
467
|
+
"LICENSE.txt",
|
|
468
|
+
".gitignore",
|
|
469
|
+
".gitattributes",
|
|
470
|
+
"CODEOWNERS",
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def _is_test_file(path: str, repo_root: str) -> bool:
|
|
475
|
+
"""True when `path` looks like a test per knowledge/test-file-indicators.md."""
|
|
476
|
+
if path.endswith(".feature"):
|
|
477
|
+
return True
|
|
478
|
+
if _TEST_NAME_RE.search(path):
|
|
479
|
+
return True
|
|
480
|
+
if any(frag in path for frag in _TEST_DIR_FRAGMENTS):
|
|
481
|
+
return True
|
|
482
|
+
if _STEP_DEFINITION_RE.search(path):
|
|
483
|
+
return True
|
|
484
|
+
if _TEST_CLASS_NAME_RE.search(path):
|
|
485
|
+
return True
|
|
486
|
+
suffix = Path(path).suffix.lower()
|
|
487
|
+
if suffix in (".cs", ".java"):
|
|
488
|
+
# Attribute-based test detection needs the file's content — best
|
|
489
|
+
# effort only, and only when the file still exists on disk.
|
|
490
|
+
try:
|
|
491
|
+
content = (Path(repo_root) / path).read_text(
|
|
492
|
+
encoding="utf-8", errors="replace"
|
|
493
|
+
)
|
|
494
|
+
except OSError:
|
|
495
|
+
return False
|
|
496
|
+
if _TEST_ATTRIBUTE_RE.search(content):
|
|
497
|
+
return True
|
|
498
|
+
return False
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def _is_doc_or_config_file(path: str) -> bool:
|
|
502
|
+
if path.startswith("metrics/") and path.endswith(".jsonl"):
|
|
503
|
+
return True
|
|
504
|
+
if Path(path).name in _DOC_CONFIG_BASENAMES:
|
|
505
|
+
return True
|
|
506
|
+
return Path(path).suffix.lower() in _DOC_CONFIG_SUFFIXES
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def has_runtime_surface(changed_files: list[str], repo_root: str) -> bool:
|
|
510
|
+
"""True when at least one changed file is neither a test file nor a
|
|
511
|
+
docs/config file — i.e. there is something for /verify to exercise.
|
|
512
|
+
"""
|
|
513
|
+
return any(
|
|
514
|
+
not _is_test_file(f, repo_root) and not _is_doc_or_config_file(f)
|
|
515
|
+
for f in changed_files
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def _collect_branch_changed_files(repo_root: str) -> list[str]:
|
|
520
|
+
"""All files changed on this branch: the committed diff since the
|
|
521
|
+
branch base, plus any currently uncommitted (staged/unstaged) files.
|
|
522
|
+
Mirrors check_scope's branch-scope resolution (issue #727).
|
|
523
|
+
"""
|
|
524
|
+
base_ref = _branch_base_sha(repo_root)
|
|
525
|
+
if not base_ref:
|
|
526
|
+
base_ref = run_git(["rev-list", "--max-parents=0", "HEAD"], repo_root).strip()
|
|
527
|
+
|
|
528
|
+
diff_output = (
|
|
529
|
+
run_git(["diff", "--name-only", base_ref + "..HEAD"], repo_root)
|
|
530
|
+
if base_ref
|
|
531
|
+
else ""
|
|
532
|
+
)
|
|
533
|
+
changed_files = (
|
|
534
|
+
set(diff_output.strip().splitlines()) if diff_output.strip() else set()
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
status_output = run_git(["status", "--porcelain"], repo_root)
|
|
538
|
+
for line in status_output.strip().splitlines():
|
|
539
|
+
if line.strip():
|
|
540
|
+
parts = line.strip().split(None, 1)
|
|
541
|
+
if len(parts) == 2:
|
|
542
|
+
changed_files.add(parts[1].strip())
|
|
543
|
+
|
|
544
|
+
return sorted(changed_files)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def check_verify_log(repo_root: str, changed_files: list[str]) -> list[dict]:
|
|
548
|
+
"""Pre-PR gate (issue #727): if the branch touches any runtime-surface
|
|
549
|
+
file (not exclusively tests/docs/config), require at least one entry
|
|
550
|
+
in metrics/verify-log.jsonl for the current branch — evidence that
|
|
551
|
+
`/verify` ran (or was explicitly skipped with a reason) before the
|
|
552
|
+
slice was marked done. Fails closed, the same way an incomplete step
|
|
553
|
+
or a missing commit does.
|
|
554
|
+
"""
|
|
555
|
+
if not changed_files or not has_runtime_surface(changed_files, repo_root):
|
|
556
|
+
return []
|
|
557
|
+
|
|
558
|
+
branch = run_git(["rev-parse", "--abbrev-ref", "HEAD"], repo_root).strip()
|
|
559
|
+
log_path = Path(repo_root) / VERIFY_LOG_PATH
|
|
560
|
+
|
|
561
|
+
entries: list[dict] = []
|
|
562
|
+
if log_path.exists():
|
|
563
|
+
for raw_line in log_path.read_text(encoding="utf-8").splitlines():
|
|
564
|
+
raw_line = raw_line.strip()
|
|
565
|
+
if not raw_line:
|
|
566
|
+
continue
|
|
567
|
+
try:
|
|
568
|
+
entries.append(json.loads(raw_line))
|
|
569
|
+
except ValueError:
|
|
570
|
+
continue
|
|
571
|
+
|
|
572
|
+
if any(entry.get("branch") == branch for entry in entries):
|
|
573
|
+
return []
|
|
574
|
+
|
|
575
|
+
return [
|
|
576
|
+
make_issue(
|
|
577
|
+
"error",
|
|
578
|
+
message=(
|
|
579
|
+
"Pre-PR gate: this branch touches runtime-surface file(s) but no "
|
|
580
|
+
f"entry in {VERIFY_LOG_PATH} matches branch '{branch}'. `/verify` "
|
|
581
|
+
"(or an explicit skip-with-reason) is required before a PR can be "
|
|
582
|
+
"opened."
|
|
583
|
+
),
|
|
584
|
+
suggested_fix=(
|
|
585
|
+
"Run /verify against the branch's changed runtime files (or record "
|
|
586
|
+
"a skipped:<reason> entry if the diff genuinely has no runtime "
|
|
587
|
+
f"surface to drive), then append the outcome to {VERIFY_LOG_PATH}."
|
|
588
|
+
),
|
|
589
|
+
)
|
|
590
|
+
]
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def _all_slice_headers(steps: list[Step]) -> list[str]:
|
|
594
|
+
"""Return the subset of Build Progress checkbox headers that are slice
|
|
595
|
+
headers (`"Slice N: <title>"`), not step headers — the shape
|
|
596
|
+
`_parse_slice_files` expects (issue #865 AC8)."""
|
|
597
|
+
return [s.header for s in steps if s.header.startswith("Slice ")]
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def check_declared_scope_adherence(
|
|
601
|
+
steps: list[Step], plan_path: Path, repo_root: str, plan_text: str
|
|
602
|
+
) -> list[dict]:
|
|
603
|
+
"""Issue #865 AC8: compare the branch diff against the union of every
|
|
604
|
+
slice's declared `**Files:**` scope (fnmatch-aware, glob-capable).
|
|
605
|
+
|
|
606
|
+
This is a **named WARNING only** — never a pre-PR gate failure. When a
|
|
607
|
+
plan opts into `Scope enforcement: freeze` (see `build_slice_scope.py`),
|
|
608
|
+
freeze itself is the real enforcement mechanism (a blocked Write/Edit);
|
|
609
|
+
this check exists for the *unfrozen* case, where out-of-scope edits are
|
|
610
|
+
otherwise invisible until `/code-review` or a human catches them.
|
|
611
|
+
|
|
612
|
+
Deterministic — no LLM call, unlike `check_scope` (which reasons about
|
|
613
|
+
*all* backtick-quoted paths in the plan prose). A plan with no slice
|
|
614
|
+
declaring `**Files:**` has nothing to compare against and returns `[]`.
|
|
615
|
+
"""
|
|
616
|
+
declared: list[str] = []
|
|
617
|
+
for header in _all_slice_headers(steps):
|
|
618
|
+
declared.extend(_parse_slice_files(plan_text, header))
|
|
619
|
+
if not declared:
|
|
620
|
+
return []
|
|
621
|
+
|
|
622
|
+
changed_files = _collect_branch_changed_files(repo_root)
|
|
623
|
+
plan_name = plan_path.name
|
|
624
|
+
changed_files = [
|
|
625
|
+
f
|
|
626
|
+
for f in changed_files
|
|
627
|
+
if f != plan_name
|
|
628
|
+
and not f.endswith(plan_name)
|
|
629
|
+
and not (f.startswith("metrics/") and f.endswith(".jsonl"))
|
|
630
|
+
and not f.startswith("memory/")
|
|
631
|
+
]
|
|
632
|
+
if not changed_files:
|
|
633
|
+
return []
|
|
634
|
+
|
|
635
|
+
out_of_scope = sorted(f for f in changed_files if not _is_path_declared(f, declared))
|
|
636
|
+
if not out_of_scope:
|
|
637
|
+
return []
|
|
638
|
+
|
|
639
|
+
return [
|
|
640
|
+
make_issue(
|
|
641
|
+
"warning",
|
|
642
|
+
message=(
|
|
643
|
+
"Scope adherence: out-of-scope edit(s) without freeze: "
|
|
644
|
+
f"{', '.join(out_of_scope)} not covered by any slice's declared "
|
|
645
|
+
f"Files ({', '.join(sorted(set(declared)))})."
|
|
646
|
+
),
|
|
647
|
+
suggested_fix=(
|
|
648
|
+
"Reconcile the plan's Files declarations, or engage scope "
|
|
649
|
+
"enforcement (`**Scope enforcement:** freeze`) so out-of-scope "
|
|
650
|
+
"writes are blocked rather than merely warned about."
|
|
651
|
+
),
|
|
652
|
+
rule_id="declared-scope-warning",
|
|
653
|
+
)
|
|
654
|
+
]
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
def check_scope(plan_path: Path, repo_root: str, skip_llm: bool) -> list[dict]:
|
|
658
|
+
"""Detect files in the branch diff that aren't declared in the plan.
|
|
659
|
+
|
|
660
|
+
Declared paths are backtick-quoted filenames in the plan text.
|
|
661
|
+
Compares all files touched in commits since the branch diverged from its
|
|
662
|
+
base (merge-base with HEAD~N or the root commit) plus any uncommitted files.
|
|
663
|
+
|
|
664
|
+
With --skip-llm: adds llm-skipped warning instead of calling LLM.
|
|
665
|
+
LLM unavailability always produces a warning, never an error.
|
|
666
|
+
"""
|
|
667
|
+
text = plan_path.read_text(encoding="utf-8")
|
|
668
|
+
declared_paths = set(_extract_declared_paths(text))
|
|
669
|
+
|
|
670
|
+
# Single source of truth for base-ref resolution — shared with
|
|
671
|
+
# check_commit_discipline so the two checks cannot drift on what
|
|
672
|
+
# counts as "trunk" (see issue #525). When no meaningful divergence
|
|
673
|
+
# point exists (no remote, no local trunk), fall back to the root
|
|
674
|
+
# commit so check_scope can still flag every file touched in the repo.
|
|
675
|
+
base_ref = _branch_base_sha(repo_root)
|
|
676
|
+
if not base_ref:
|
|
677
|
+
base_ref = run_git(["rev-list", "--max-parents=0", "HEAD"], repo_root).strip()
|
|
678
|
+
|
|
679
|
+
# Collect files changed in commits since base_ref
|
|
680
|
+
if base_ref:
|
|
681
|
+
diff_output = run_git(["diff", "--name-only", base_ref + "..HEAD"], repo_root)
|
|
682
|
+
else:
|
|
683
|
+
diff_output = ""
|
|
684
|
+
changed_files = (
|
|
685
|
+
set(diff_output.strip().splitlines()) if diff_output.strip() else set()
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
# Also collect uncommitted files from status
|
|
689
|
+
status_output = run_git(["status", "--porcelain"], repo_root)
|
|
690
|
+
for line in status_output.strip().splitlines():
|
|
691
|
+
if line.strip():
|
|
692
|
+
# porcelain format: XY <path>
|
|
693
|
+
parts = line.strip().split(None, 1)
|
|
694
|
+
if len(parts) == 2:
|
|
695
|
+
changed_files.add(parts[1].strip())
|
|
696
|
+
|
|
697
|
+
# Exclude the plan file itself from scope check, along with append-only
|
|
698
|
+
# metrics artifacts (e.g. review-value.jsonl, verify-log.jsonl) — these
|
|
699
|
+
# are pipeline instrumentation the plan never declares by name, not
|
|
700
|
+
# plan-scoped work product (issue #727).
|
|
701
|
+
plan_name = plan_path.name
|
|
702
|
+
plan_rel = str(plan_path)
|
|
703
|
+
changed_files = {
|
|
704
|
+
f
|
|
705
|
+
for f in changed_files
|
|
706
|
+
if f != plan_name
|
|
707
|
+
and f != plan_rel
|
|
708
|
+
and not f.endswith(plan_name)
|
|
709
|
+
and not (f.startswith("metrics/") and f.endswith(".jsonl"))
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if not changed_files:
|
|
713
|
+
return []
|
|
714
|
+
|
|
715
|
+
# If no declared paths, any changed file is potentially out-of-plan.
|
|
716
|
+
# Otherwise a file is "declared" (not out-of-plan) if it exactly
|
|
717
|
+
# matches a declared file or falls under a declared directory prefix
|
|
718
|
+
# (see issue #713).
|
|
719
|
+
out_of_plan = (
|
|
720
|
+
changed_files
|
|
721
|
+
if not declared_paths
|
|
722
|
+
else {f for f in changed_files if not _is_path_declared(f, declared_paths)}
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
if not out_of_plan:
|
|
726
|
+
return []
|
|
727
|
+
|
|
728
|
+
# We have out-of-plan files — check with LLM or emit skipped warning
|
|
729
|
+
if skip_llm:
|
|
730
|
+
return [
|
|
731
|
+
skipped_llm_warning(
|
|
732
|
+
message=(
|
|
733
|
+
f"LLM check skipped (--skip-llm): {len(out_of_plan)} file(s) not declared "
|
|
734
|
+
f"in plan may represent scope creep — manual review required."
|
|
735
|
+
)
|
|
736
|
+
)
|
|
737
|
+
]
|
|
738
|
+
|
|
739
|
+
# Call LLM for scope assessment
|
|
740
|
+
file_list = ", ".join(sorted(out_of_plan))
|
|
741
|
+
prompt = (
|
|
742
|
+
f"These files were changed but are not declared in the plan '{plan_path.name}': "
|
|
743
|
+
f"{file_list}. "
|
|
744
|
+
f"Is this likely scope creep or acceptable collateral changes? "
|
|
745
|
+
f'Reply with a brief JSON assessment: {{"scope_creep": true/false, "reason": "..."}}.'
|
|
746
|
+
)
|
|
747
|
+
try:
|
|
748
|
+
result = subprocess.run(
|
|
749
|
+
["claude", "-p", prompt],
|
|
750
|
+
capture_output=True,
|
|
751
|
+
text=True,
|
|
752
|
+
timeout=60,
|
|
753
|
+
check=False,
|
|
754
|
+
)
|
|
755
|
+
if result.returncode != 0 or not result.stdout.strip():
|
|
756
|
+
return [
|
|
757
|
+
make_issue(
|
|
758
|
+
"warning",
|
|
759
|
+
message=(
|
|
760
|
+
f"LLM scope check unavailable: {len(out_of_plan)} file(s) not in plan "
|
|
761
|
+
f"({file_list}) — manual scope review recommended."
|
|
762
|
+
),
|
|
763
|
+
)
|
|
764
|
+
]
|
|
765
|
+
return [
|
|
766
|
+
make_issue(
|
|
767
|
+
"warning",
|
|
768
|
+
message=(
|
|
769
|
+
f"Scope check (LLM): {len(out_of_plan)} out-of-plan file(s): "
|
|
770
|
+
f"{file_list}. LLM assessment: {result.stdout.strip()[:300]}"
|
|
771
|
+
),
|
|
772
|
+
)
|
|
773
|
+
]
|
|
774
|
+
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
|
775
|
+
return [
|
|
776
|
+
make_issue(
|
|
777
|
+
"warning",
|
|
778
|
+
message=(
|
|
779
|
+
f"LLM scope check unavailable (claude not on PATH or timed out): "
|
|
780
|
+
f"{len(out_of_plan)} file(s) not declared in plan — manual review needed."
|
|
781
|
+
),
|
|
782
|
+
)
|
|
783
|
+
]
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
# ---------------------------------------------------------------------------
|
|
787
|
+
# Main
|
|
788
|
+
# ---------------------------------------------------------------------------
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
def main(argv: list[str] | None = None) -> int:
|
|
792
|
+
parser = argparse.ArgumentParser(
|
|
793
|
+
description="Validate plan step completion and commit discipline."
|
|
794
|
+
)
|
|
795
|
+
parser.add_argument("--plan", required=True, help="Path to the plan markdown file.")
|
|
796
|
+
parser.add_argument(
|
|
797
|
+
"--pre-pr",
|
|
798
|
+
action="store_true",
|
|
799
|
+
help="Assert all plan steps are complete (pre-PR gate).",
|
|
800
|
+
)
|
|
801
|
+
parser.add_argument(
|
|
802
|
+
"--skip-llm",
|
|
803
|
+
action="store_true",
|
|
804
|
+
help="Skip LLM scope check and emit a warning finding instead.",
|
|
805
|
+
)
|
|
806
|
+
args = parser.parse_args(argv)
|
|
807
|
+
|
|
808
|
+
plan_path = Path(args.plan).resolve()
|
|
809
|
+
if not plan_path.exists():
|
|
810
|
+
result = build_result(
|
|
811
|
+
errors=[
|
|
812
|
+
make_issue(
|
|
813
|
+
"error",
|
|
814
|
+
message=f"Plan file not found: {args.plan}",
|
|
815
|
+
file=args.plan,
|
|
816
|
+
)
|
|
817
|
+
],
|
|
818
|
+
warnings=[],
|
|
819
|
+
)
|
|
820
|
+
return main_exit(result)
|
|
821
|
+
|
|
822
|
+
# Resolve the git repo root from the plan file's directory.
|
|
823
|
+
# Falls back to the plan file's parent if not in a git repo.
|
|
824
|
+
repo_root_output = run_git(
|
|
825
|
+
["rev-parse", "--show-toplevel"], str(plan_path.parent)
|
|
826
|
+
).strip()
|
|
827
|
+
repo_root = repo_root_output if repo_root_output else str(plan_path.parent)
|
|
828
|
+
|
|
829
|
+
errors: list[dict] = []
|
|
830
|
+
warnings: list[dict] = []
|
|
831
|
+
|
|
832
|
+
# 1. Parse plan checkboxes
|
|
833
|
+
steps, parse_errors = parse_plan(plan_path)
|
|
834
|
+
errors.extend(parse_errors)
|
|
835
|
+
|
|
836
|
+
# Pre-read the plan text once so check_commit_discipline can locate
|
|
837
|
+
# per-slice `**Files:**` declarations (file-path matcher path).
|
|
838
|
+
plan_text = plan_path.read_text(encoding="utf-8")
|
|
839
|
+
if not parse_errors:
|
|
840
|
+
# 2. Uncommitted change check (always runs; ignore the plan file itself)
|
|
841
|
+
uncommitted_issues = check_uncommitted(
|
|
842
|
+
repo_root, exclude_paths=[plan_path.name]
|
|
843
|
+
)
|
|
844
|
+
errors.extend(uncommitted_issues)
|
|
845
|
+
|
|
846
|
+
# 3. Commit discipline (runs regardless of uncommitted state — both issues matter)
|
|
847
|
+
commit_issues = check_commit_discipline(steps, repo_root, plan_text)
|
|
848
|
+
for issue in commit_issues:
|
|
849
|
+
if issue["severity"] == "error":
|
|
850
|
+
errors.append(issue)
|
|
851
|
+
else:
|
|
852
|
+
warnings.append(issue)
|
|
853
|
+
|
|
854
|
+
# 4. Scope creep check (only when tree is clean — avoids double-counting uncommitted)
|
|
855
|
+
if not uncommitted_issues:
|
|
856
|
+
scope_issues = check_scope(plan_path, repo_root, args.skip_llm)
|
|
857
|
+
for issue in scope_issues:
|
|
858
|
+
if issue["severity"] == "error":
|
|
859
|
+
errors.append(issue)
|
|
860
|
+
else:
|
|
861
|
+
warnings.append(issue)
|
|
862
|
+
|
|
863
|
+
# 5. Pre-PR gate (runs regardless of uncommitted state)
|
|
864
|
+
if args.pre_pr:
|
|
865
|
+
pre_pr_issues = check_pre_pr(steps)
|
|
866
|
+
errors.extend(pre_pr_issues)
|
|
867
|
+
|
|
868
|
+
# 6. Verify-log gate (issue #727): runtime-surface changes need
|
|
869
|
+
# evidence /verify ran before the PR can be opened.
|
|
870
|
+
verify_changed_files = _collect_branch_changed_files(repo_root)
|
|
871
|
+
verify_issues = check_verify_log(repo_root, verify_changed_files)
|
|
872
|
+
errors.extend(verify_issues)
|
|
873
|
+
|
|
874
|
+
# 7. Declared-scope adherence (issue #865 AC8): named warning
|
|
875
|
+
# only, never a gate failure — freeze (when engaged) is the
|
|
876
|
+
# real enforcement mechanism.
|
|
877
|
+
warnings.extend(
|
|
878
|
+
check_declared_scope_adherence(
|
|
879
|
+
steps, plan_path, repo_root, plan_text
|
|
880
|
+
)
|
|
881
|
+
)
|
|
882
|
+
|
|
883
|
+
result = build_result(errors, warnings)
|
|
884
|
+
return main_exit(result)
|
|
885
|
+
|
|
886
|
+
|
|
887
|
+
if __name__ == "__main__":
|
|
888
|
+
sys.exit(main())
|