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,895 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""orchestrator.py — Python dispatcher for the dev-team three-phase pipeline.
|
|
3
|
+
|
|
4
|
+
CLI: python3 ${CLAUDE_PLUGIN_ROOT}/scripts/orchestrator.py [--resume] [--skip-llm]
|
|
5
|
+
[--memory-dir <path>] [--classify trivial|standard|complex] [--fail-wave]
|
|
6
|
+
[--dispatch-personas]
|
|
7
|
+
|
|
8
|
+
Flags:
|
|
9
|
+
--resume Skip phases whose state files already exist in memory-dir.
|
|
10
|
+
--skip-llm Use stubs for classify() and all LLM dispatch.
|
|
11
|
+
--memory-dir <path> Where to read/write phase state (default: .claude/memory/ relative to CWD).
|
|
12
|
+
--classify <size> Override classification (trivial|standard|complex). For testing only.
|
|
13
|
+
--fail-wave Simulate a wave barrier failure (for testing).
|
|
14
|
+
--dispatch-personas Dispatch plan-review personas (for testing).
|
|
15
|
+
|
|
16
|
+
Exit codes:
|
|
17
|
+
0 = success
|
|
18
|
+
1 = error (no prior state with --resume, wave barrier failure, etc.)
|
|
19
|
+
|
|
20
|
+
Module split: see ADR 0040.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# Module split (dispatch_primitives / phase_functions): evaluated and
|
|
24
|
+
# declined for now — see ADR 0040
|
|
25
|
+
# (docs/adr/0040-evaluate-splitting-orchestrator-py-no-go.md, issue #1723).
|
|
26
|
+
# The blocker is 58 `patch.object(orch, "dispatch_persona"/"dispatch_personas",
|
|
27
|
+
# ...)` sites in tests/scripts/test_orchestrator.py (multiline-aware count —
|
|
28
|
+
# a single-line grep undercounts to 45) that would stop intercepting dispatch
|
|
29
|
+
# calls if the phase functions imported those names from a separate module.
|
|
30
|
+
# Revisit if dispatch_primitives or phase_functions grows independently.
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import argparse
|
|
35
|
+
import asyncio
|
|
36
|
+
import functools
|
|
37
|
+
import json
|
|
38
|
+
import subprocess
|
|
39
|
+
import sys
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
|
|
42
|
+
SCRIPTS = Path(__file__).resolve().parent
|
|
43
|
+
sys.path.insert(0, str(SCRIPTS))
|
|
44
|
+
from lib.slug import derive_slug
|
|
45
|
+
|
|
46
|
+
# Default personas for plan review — the five plan-review-* critics.
|
|
47
|
+
DEFAULT_PERSONAS = [
|
|
48
|
+
"plan-review-acceptance",
|
|
49
|
+
"plan-review-design",
|
|
50
|
+
"plan-review-ux",
|
|
51
|
+
"plan-review-strategic",
|
|
52
|
+
"plan-review-parallelization",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
# Language-agnostic always-run code-review trio per docs/team-structure.md's
|
|
56
|
+
# review-dispatch fan-out. Conditional language-specific reviewers are out
|
|
57
|
+
# of scope for this iteration.
|
|
58
|
+
CODE_REVIEW_PANEL = ["doc-review", "arch-review", "token-efficiency-review"]
|
|
59
|
+
|
|
60
|
+
# Personas whose --output-format json envelope's "result" field is itself
|
|
61
|
+
# documented structured JSON (per knowledge/review-agent-output-contract.md)
|
|
62
|
+
# and should be parsed rather than stored as freeform prose.
|
|
63
|
+
JSON_CONTRACT_PERSONAS = DEFAULT_PERSONAS + CODE_REVIEW_PANEL
|
|
64
|
+
|
|
65
|
+
# Keyword heuristic for the Research phase's security-engineer dispatch
|
|
66
|
+
# decision. This tuple is the one normative source in CODE for the keyword
|
|
67
|
+
# list — _touches_security() consumes it, it is not duplicated in any other
|
|
68
|
+
# .py module. knowledge/orchestrator-script-implementation.md's "Security
|
|
69
|
+
# Engineer dispatch — script approximation" section (linked from
|
|
70
|
+
# agents/orchestrator.md's phase table) restates the same seven keywords in
|
|
71
|
+
# prose for its own (agent-facing, standalone) audience; a content-guard
|
|
72
|
+
# test (tests/agents/test_orchestrator_security_persona_prose_sync.py, #2067)
|
|
73
|
+
# now asserts the two stay in sync.
|
|
74
|
+
SECURITY_KEYWORDS = (
|
|
75
|
+
"auth",
|
|
76
|
+
"secret",
|
|
77
|
+
"crypto",
|
|
78
|
+
"password",
|
|
79
|
+
"token",
|
|
80
|
+
"credential",
|
|
81
|
+
"encrypt",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Research-phase always-on persona roster (see agents/orchestrator.md §
|
|
85
|
+
# Phase 1: Research). Named module constant, matching the DEFAULT_PERSONAS/
|
|
86
|
+
# CODE_REVIEW_PANEL pattern above, so it has one definition instead of being
|
|
87
|
+
# re-typed at each call/test site. A tuple (like SECURITY_KEYWORDS), not a
|
|
88
|
+
# list: this is a fixed roster, so `list(RESEARCH_PERSONAS)` at its one call
|
|
89
|
+
# site is a genuine type conversion into a mutable working copy, not a
|
|
90
|
+
# defensive copy guarding against accidental in-place mutation of the
|
|
91
|
+
# constant itself.
|
|
92
|
+
RESEARCH_PERSONAS = ("codebase-recon", "architect", "data-flow-tracer")
|
|
93
|
+
|
|
94
|
+
# Plan-phase core-trio roster (see agents/orchestrator.md § Phase 2: Plan).
|
|
95
|
+
# Dispatched first, before the plan-review-* critics in DEFAULT_PERSONAS —
|
|
96
|
+
# see _default_phase_plan below. A tuple, matching RESEARCH_PERSONAS's own
|
|
97
|
+
# convention; unlike RESEARCH_PERSONAS, nothing is ever conditionally
|
|
98
|
+
# appended to this roster, so no defensive-copy note is needed here.
|
|
99
|
+
# knowledge/orchestrator-script-implementation.md's "Plan persona roster"
|
|
100
|
+
# section (linked from agents/orchestrator.md's phase table) restates this
|
|
101
|
+
# same trio (and CRITICS_SKIPPED_ALL_CORE_FAILED's value) in prose; a
|
|
102
|
+
# content-guard test
|
|
103
|
+
# (tests/agents/test_orchestrator_security_persona_prose_sync.py, #2067) now
|
|
104
|
+
# asserts the two stay in sync.
|
|
105
|
+
PLAN_CORE_PERSONAS = ("product-manager", "architect", "qa-engineer")
|
|
106
|
+
|
|
107
|
+
# Persisted-state vocabulary for _default_phase_plan's all-core-failed guard
|
|
108
|
+
# (see below) — named alongside the module's other cross-process vocabulary
|
|
109
|
+
# constants (SECURITY_KEYWORDS, RESEARCH_PERSONAS) so the sentinel has one
|
|
110
|
+
# definition instead of being re-typed at the production site and in tests.
|
|
111
|
+
CRITICS_SKIPPED_ALL_CORE_FAILED = "all_core_personas_failed"
|
|
112
|
+
|
|
113
|
+
# The conditionally-dispatched fourth Research persona (see _touches_security
|
|
114
|
+
# below). Named for the same reason RESEARCH_PERSONAS is: avoid re-typing the
|
|
115
|
+
# literal at each call/test site.
|
|
116
|
+
SECURITY_ENGINEER_PERSONA = "security-engineer"
|
|
117
|
+
|
|
118
|
+
# The Implement-phase wave persona and its post-success doc-verification
|
|
119
|
+
# persona (see _default_phase_implement below). Named for the same reason
|
|
120
|
+
# SECURITY_ENGINEER_PERSONA is: avoid re-typing the literal at each
|
|
121
|
+
# call/test site.
|
|
122
|
+
SOFTWARE_ENGINEER_PERSONA = "software-engineer"
|
|
123
|
+
TECH_WRITER_PERSONA = "tech-writer"
|
|
124
|
+
|
|
125
|
+
# Implement-phase wave slice roster (see _dispatch_implement_wave below). A
|
|
126
|
+
# tuple, matching RESEARCH_PERSONAS/PLAN_CORE_PERSONAS's own convention.
|
|
127
|
+
# Load-bearing, not decorative: persisted into orchestrator-implement.json
|
|
128
|
+
# and printed verbatim in the operator-facing "wave barrier failed on slice
|
|
129
|
+
# '<name>'" message. One definition on the production side (test_orchestrator
|
|
130
|
+
# pins its exact value directly below, alongside SOFTWARE_ENGINEER_PERSONA/
|
|
131
|
+
# TECH_WRITER_PERSONA's own pinning tests) — most test sites deliberately
|
|
132
|
+
# still pin the literal value independently rather than importing this
|
|
133
|
+
# constant, matching how this file's persona constants are pinned rather
|
|
134
|
+
# than merely referenced. A single synthetic slice representing "the whole
|
|
135
|
+
# task" today (see the Script gap in agents/orchestrator.md for why); the
|
|
136
|
+
# --fail-wave simulation branch below deliberately prints a different,
|
|
137
|
+
# unrelated slice name ("slice-1") since it doesn't go through this
|
|
138
|
+
# constant at all.
|
|
139
|
+
IMPLEMENT_WAVE_SLICES = ("implement-1",)
|
|
140
|
+
|
|
141
|
+
# Timeouts (seconds) for the two `claude -p` subprocess dispatch sites below.
|
|
142
|
+
# Unverified placeholders, not measured against a real dispatch — pinned by
|
|
143
|
+
# a direct test (test_orchestrator.py) per follow-up #1716 so an accidental
|
|
144
|
+
# edit fails fast instead of surfacing only as a flaky/slow-CLI symptom;
|
|
145
|
+
# the underlying values themselves remain unverified against real latency.
|
|
146
|
+
CLASSIFY_TIMEOUT_S = 30
|
|
147
|
+
PERSONA_DISPATCH_TIMEOUT_S = 60
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _warn_on_failed_personas(phase_label: str, results: list, fatal: bool = False) -> None:
|
|
151
|
+
"""Print a single stderr WARNING naming every failed persona in results,
|
|
152
|
+
or nothing at all if none failed.
|
|
153
|
+
|
|
154
|
+
Shared by _default_phase_research, _default_phase_plan, and
|
|
155
|
+
_default_phase_implement so the WARNING message has one normative
|
|
156
|
+
formatting/behavior definition instead of independently maintained
|
|
157
|
+
copies. Research/Plan's failures (and Implement's post-success review
|
|
158
|
+
group) are genuinely recorded and non-fatal, which is the default
|
|
159
|
+
wording — but the Implement wave dispatch is a different case: a
|
|
160
|
+
failure there is about to raise WaveError uncaught (the state file is
|
|
161
|
+
never written) and end the process with exit code 1, so `fatal=True`
|
|
162
|
+
selects wording that says so instead of falsely claiming
|
|
163
|
+
"(recorded, non-fatal)".
|
|
164
|
+
"""
|
|
165
|
+
failed_personas = [r["persona"] for r in results if r.get("status") == "failed"]
|
|
166
|
+
if failed_personas:
|
|
167
|
+
suffix = "wave barrier will fail" if fatal else "recorded, non-fatal"
|
|
168
|
+
print(
|
|
169
|
+
f"WARNING: {phase_label} persona dispatch failed ({suffix}): {', '.join(failed_personas)}",
|
|
170
|
+
file=sys.stderr,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _touches_security(request: str) -> bool:
|
|
175
|
+
"""Return True if request case-insensitively contains a security keyword.
|
|
176
|
+
|
|
177
|
+
Heuristic, not a precise classifier: substring matching means false
|
|
178
|
+
positives are expected and accepted (e.g. "cryptocurrency" matches via
|
|
179
|
+
"crypto") per the plan's Risks section.
|
|
180
|
+
"""
|
|
181
|
+
lowered = request.lower()
|
|
182
|
+
return any(keyword in lowered for keyword in SECURITY_KEYWORDS)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# ---------------------------------------------------------------------------
|
|
186
|
+
# Helpers
|
|
187
|
+
# ---------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def phase_state_path(phase: str, memory_dir: Path) -> Path:
|
|
191
|
+
"""Return the canonical path for a phase's state file."""
|
|
192
|
+
return memory_dir / f"orchestrator-{phase}.json"
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def write_progress(phase: str, result: dict, memory_dir: Path) -> None:
|
|
196
|
+
"""Write phase result as JSON to memory_dir/orchestrator-<phase>.json."""
|
|
197
|
+
memory_dir.mkdir(parents=True, exist_ok=True)
|
|
198
|
+
phase_state_path(phase, memory_dir).write_text(json.dumps(result))
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def read_progress(phase: str, memory_dir: Path):
|
|
202
|
+
"""Return the parsed JSON for phase, or None if no state file exists."""
|
|
203
|
+
path = phase_state_path(phase, memory_dir)
|
|
204
|
+
if path.exists():
|
|
205
|
+
return json.loads(path.read_text())
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# ---------------------------------------------------------------------------
|
|
210
|
+
# Classification
|
|
211
|
+
# ---------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
async def classify(request: str, skip_llm: bool = False) -> dict:
|
|
215
|
+
"""Return {size: trivial|standard|complex}. Falls back to standard on failure."""
|
|
216
|
+
if skip_llm:
|
|
217
|
+
return {"size": "standard"}
|
|
218
|
+
try:
|
|
219
|
+
# Offload the blocking call to a thread so an awaiting/gathered caller
|
|
220
|
+
# keeps a free event loop instead of serializing on subprocess.run (#1213).
|
|
221
|
+
loop = asyncio.get_running_loop()
|
|
222
|
+
result = await loop.run_in_executor(
|
|
223
|
+
None,
|
|
224
|
+
functools.partial(
|
|
225
|
+
subprocess.run,
|
|
226
|
+
[
|
|
227
|
+
"claude",
|
|
228
|
+
"-p",
|
|
229
|
+
(
|
|
230
|
+
"Classify this task as exactly one of: trivial, standard, or complex. "
|
|
231
|
+
f"Reply with only one word. Task: {request}"
|
|
232
|
+
),
|
|
233
|
+
],
|
|
234
|
+
capture_output=True,
|
|
235
|
+
text=True,
|
|
236
|
+
timeout=CLASSIFY_TIMEOUT_S,
|
|
237
|
+
),
|
|
238
|
+
)
|
|
239
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
240
|
+
raw = result.stdout.strip().lower()
|
|
241
|
+
for size in ("trivial", "standard", "complex"):
|
|
242
|
+
if size in raw:
|
|
243
|
+
return {"size": size}
|
|
244
|
+
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
|
245
|
+
print(
|
|
246
|
+
"WARNING: LLM classify failed; defaulting to full pipeline",
|
|
247
|
+
file=sys.stderr,
|
|
248
|
+
)
|
|
249
|
+
return {"size": "standard"}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
# ---------------------------------------------------------------------------
|
|
253
|
+
# Research phase
|
|
254
|
+
# ---------------------------------------------------------------------------
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _recon_artifact_path(root: Path) -> Path:
|
|
258
|
+
"""Path to codebase-recon's JSON artifact for the repo at `root`.
|
|
259
|
+
|
|
260
|
+
Per agents/codebase-recon.md's Contract section: always
|
|
261
|
+
`.claude/memory/recon-<slug>.json`. `root` is deliberately the caller's
|
|
262
|
+
own CWD, not a git-root resolution (e.g.
|
|
263
|
+
hooks/lib/artifact_paths.py::memory_dir, used by other scripts in this
|
|
264
|
+
directory for that purpose) — the recon *agent*'s prompt writes this
|
|
265
|
+
path relative to its own CWD, which is orchestrator.py's CWD since
|
|
266
|
+
dispatch_persona's subprocess.run inherits it unchanged. Resolving
|
|
267
|
+
against the git root instead would disagree with the recon agent's own
|
|
268
|
+
write location whenever they differ (e.g. orchestrator.py invoked from
|
|
269
|
+
a subdirectory), which is the opposite of this function's purpose.
|
|
270
|
+
Also independent of orchestrator.py's own (configurable) --memory-dir;
|
|
271
|
+
if that flag points elsewhere, this path and the phase-state directory
|
|
272
|
+
diverge — inherent to the recon agent's contract, not something this
|
|
273
|
+
function can paper over.
|
|
274
|
+
"""
|
|
275
|
+
return root / ".claude" / "memory" / f"recon-{derive_slug(root)}.json"
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
async def _resolve_recon_artifact(personas: list, results: list, cwd: Path) -> str | None:
|
|
279
|
+
"""Link codebase-recon's own artifact (agents/codebase-recon.md's
|
|
280
|
+
Contract — .claude/memory/recon-<slug>.json) into Research state, so a
|
|
281
|
+
Plan-phase consumer doesn't need to independently know that naming
|
|
282
|
+
convention (follow-up #1716). Returns `None` when codebase-recon wasn't
|
|
283
|
+
dispatched, didn't succeed, or its artifact file isn't on disk (e.g.
|
|
284
|
+
--skip-llm, where no real agent ran).
|
|
285
|
+
|
|
286
|
+
`cwd` is captured by the caller before its own `await` rather than read
|
|
287
|
+
here via `Path.cwd()` directly — process-global state should not be
|
|
288
|
+
re-read across an await boundary in case a future concurrent coroutine
|
|
289
|
+
ever changes it.
|
|
290
|
+
"""
|
|
291
|
+
if "codebase-recon" not in personas:
|
|
292
|
+
return None
|
|
293
|
+
recon_result = next((r for r in results if r.get("persona") == "codebase-recon"), None)
|
|
294
|
+
if recon_result is None or recon_result.get("status") != "success":
|
|
295
|
+
return None
|
|
296
|
+
candidate = _recon_artifact_path(cwd)
|
|
297
|
+
# Offload to a thread, matching classify()'s own run_in_executor use for
|
|
298
|
+
# its blocking call — the event loop shouldn't block on a filesystem
|
|
299
|
+
# stat any more than it should on subprocess.run.
|
|
300
|
+
loop = asyncio.get_running_loop()
|
|
301
|
+
if not await loop.run_in_executor(None, candidate.is_file):
|
|
302
|
+
return None
|
|
303
|
+
return str(candidate)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
async def _default_phase_research(request: str, task: dict, skip_llm: bool) -> dict:
|
|
307
|
+
"""Dispatch the Research-phase personas and aggregate their results.
|
|
308
|
+
|
|
309
|
+
Always dispatches RESEARCH_PERSONAS (codebase-recon, architect,
|
|
310
|
+
data-flow-tracer); additionally dispatches security-engineer when the
|
|
311
|
+
request text touches auth/secrets/crypto per _touches_security(). A
|
|
312
|
+
status: "failed" entry among the dispatched results is recorded
|
|
313
|
+
verbatim — reconcile()/WaveError are scoped to the Implement phase's
|
|
314
|
+
wave loop, not Research.
|
|
315
|
+
"""
|
|
316
|
+
# Captured before the await below (see _resolve_recon_artifact's
|
|
317
|
+
# docstring) rather than read via Path.cwd() after it.
|
|
318
|
+
cwd = Path.cwd()
|
|
319
|
+
# RESEARCH_PERSONAS is an immutable tuple; list() converts it into the
|
|
320
|
+
# mutable working copy the conditional security-engineer append below
|
|
321
|
+
# needs (see the constant's own definition for why it's a tuple).
|
|
322
|
+
personas = list(RESEARCH_PERSONAS)
|
|
323
|
+
if _touches_security(request):
|
|
324
|
+
personas.append(SECURITY_ENGINEER_PERSONA)
|
|
325
|
+
# "task" here is the classify() output dict (e.g. {"size": "standard"}),
|
|
326
|
+
# not the request text — kept as a distinct key from "request" so a
|
|
327
|
+
# later Plan-phase slice reading this precedent doesn't conflate them.
|
|
328
|
+
results = await dispatch_personas(
|
|
329
|
+
personas, plan={"task": task, "request": request}, skip_llm=skip_llm
|
|
330
|
+
)
|
|
331
|
+
# Research records failures verbatim and never raises (see docstring
|
|
332
|
+
# above) — but a run where any persona failed must not look identical,
|
|
333
|
+
# on the console, to one that succeeded fully. Mirrors classify()'s own
|
|
334
|
+
# degraded-but-non-fatal WARNING.
|
|
335
|
+
_warn_on_failed_personas("Research", results)
|
|
336
|
+
return {
|
|
337
|
+
"personas": personas,
|
|
338
|
+
"results": results,
|
|
339
|
+
"skip_llm": skip_llm,
|
|
340
|
+
"recon_artifact": await _resolve_recon_artifact(personas, results, cwd),
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
# ---------------------------------------------------------------------------
|
|
345
|
+
# Plan phase
|
|
346
|
+
# ---------------------------------------------------------------------------
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _all_personas_failed(results: list) -> bool:
|
|
350
|
+
"""True if results is non-empty and every entry has status "failed".
|
|
351
|
+
|
|
352
|
+
Deliberately False on an empty list: an empty core_results would make a
|
|
353
|
+
bare all(...) vacuously True and wrongly skip critic dispatch, so the
|
|
354
|
+
emptiness check is load-bearing, not defensive noise — unreachable
|
|
355
|
+
today (dispatch_personas always returns one entry per persona and
|
|
356
|
+
PLAN_CORE_PERSONAS is a fixed 3-tuple), but would matter the moment a
|
|
357
|
+
future slice makes the core roster dynamic.
|
|
358
|
+
"""
|
|
359
|
+
return bool(results) and all(r.get("status") == "failed" for r in results)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
async def _default_phase_plan(
|
|
363
|
+
request: str, task: dict, research_state: dict, skip_llm: bool
|
|
364
|
+
) -> dict:
|
|
365
|
+
"""Dispatch the Plan-phase core trio, then the plan-review-* critics.
|
|
366
|
+
|
|
367
|
+
Two-stage dispatch: PLAN_CORE_PERSONAS (product-manager, architect,
|
|
368
|
+
qa-engineer) drafts a plan using the Research phase's aggregated state
|
|
369
|
+
as context, then DEFAULT_PERSONAS (the five plan-review-* critics)
|
|
370
|
+
critiques that draft — unless every core-trio result has
|
|
371
|
+
status: "failed", in which case critic dispatch is skipped entirely
|
|
372
|
+
(see the all-core-failed guard below). A status: "failed" entry among
|
|
373
|
+
either group's results is recorded verbatim — reconcile()/WaveError
|
|
374
|
+
stay scoped to the Implement phase's wave loop, not Plan. Note this
|
|
375
|
+
phase still dispatches the core trio even when research_state's own
|
|
376
|
+
results are entirely failed: the raw request text is sufficient context
|
|
377
|
+
for the trio to draft from, unlike the critics, which genuinely have
|
|
378
|
+
nothing to critique when the trio itself produced nothing.
|
|
379
|
+
"""
|
|
380
|
+
core_personas = list(PLAN_CORE_PERSONAS)
|
|
381
|
+
core_results = await dispatch_personas(
|
|
382
|
+
core_personas,
|
|
383
|
+
plan={"task": task, "request": request, "research": research_state},
|
|
384
|
+
skip_llm=skip_llm,
|
|
385
|
+
)
|
|
386
|
+
critics_skipped_reason = None
|
|
387
|
+
if _all_personas_failed(core_results):
|
|
388
|
+
# Every core-trio persona failed (most plausibly: the claude CLI is
|
|
389
|
+
# unreachable) — skip the five critic dispatches entirely rather
|
|
390
|
+
# than spend real LLM cost critiquing identical failure stubs.
|
|
391
|
+
critic_results = []
|
|
392
|
+
critics_skipped_reason = CRITICS_SKIPPED_ALL_CORE_FAILED
|
|
393
|
+
print(
|
|
394
|
+
"INFO: all Plan core personas failed — skipping critic dispatch",
|
|
395
|
+
file=sys.stderr,
|
|
396
|
+
)
|
|
397
|
+
else:
|
|
398
|
+
critic_results = await dispatch_personas(
|
|
399
|
+
DEFAULT_PERSONAS,
|
|
400
|
+
plan={"task": task, "request": request, "plan_draft": core_results},
|
|
401
|
+
skip_llm=skip_llm,
|
|
402
|
+
)
|
|
403
|
+
# Exactly one merged WARNING per Plan-phase run, naming every failed
|
|
404
|
+
# persona across both groups — not one line per group.
|
|
405
|
+
_warn_on_failed_personas("Plan", core_results + critic_results)
|
|
406
|
+
return {
|
|
407
|
+
"core_personas": core_personas,
|
|
408
|
+
"core_results": core_results,
|
|
409
|
+
# list(...), not a bare reference: critic_personas is persisted
|
|
410
|
+
# (json.dumps doesn't care, but a future in-memory consumer
|
|
411
|
+
# mutating this list would otherwise corrupt the shared module
|
|
412
|
+
# constant DEFAULT_PERSONAS for the rest of the process).
|
|
413
|
+
"critic_personas": list(DEFAULT_PERSONAS),
|
|
414
|
+
"critic_results": critic_results,
|
|
415
|
+
"critics_skipped_reason": critics_skipped_reason,
|
|
416
|
+
"skip_llm": skip_llm,
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
# ---------------------------------------------------------------------------
|
|
421
|
+
# Implement phase
|
|
422
|
+
# ---------------------------------------------------------------------------
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
async def _dispatch_implement_wave(
|
|
426
|
+
request: str, task: dict, plan_state: dict, skip_llm: bool
|
|
427
|
+
) -> list:
|
|
428
|
+
"""Dispatch the Implement-phase wave and reconcile its results.
|
|
429
|
+
|
|
430
|
+
Dispatches SOFTWARE_ENGINEER_PERSONA once per IMPLEMENT_WAVE_SLICES entry
|
|
431
|
+
via dispatch_personas — reused verbatim rather than a hand-rolled second
|
|
432
|
+
copy of its gather/BaseException-normalization logic. The `* len(...)`
|
|
433
|
+
below is what keeps `personas` index-aligned with IMPLEMENT_WAVE_SLICES
|
|
434
|
+
for the "slice" tagging that follows — a real invariant, not decorative,
|
|
435
|
+
even though both are length 1 today (see IMPLEMENT_WAVE_SLICES's own
|
|
436
|
+
comment for why). reconcile() raises WaveError uncaught (no try/except
|
|
437
|
+
here) on any failed slice, so _run_phase's write_progress call never
|
|
438
|
+
runs for a failed wave — the phase's state file stays absent and a
|
|
439
|
+
subsequent --resume run retries Implement from scratch.
|
|
440
|
+
"""
|
|
441
|
+
results = await dispatch_personas(
|
|
442
|
+
[SOFTWARE_ENGINEER_PERSONA] * len(IMPLEMENT_WAVE_SLICES),
|
|
443
|
+
plan={"task": task, "request": request, "plan_state": plan_state},
|
|
444
|
+
skip_llm=skip_llm,
|
|
445
|
+
)
|
|
446
|
+
for slice_name, result in zip(IMPLEMENT_WAVE_SLICES, results):
|
|
447
|
+
result["slice"] = slice_name
|
|
448
|
+
# fatal=True: this failure is about to raise WaveError uncaught (state
|
|
449
|
+
# never persisted, exit code 1) — the opposite of the "recorded,
|
|
450
|
+
# non-fatal" wording _warn_on_failed_personas defaults to.
|
|
451
|
+
_warn_on_failed_personas("Implement", results, fatal=True)
|
|
452
|
+
await reconcile(results, list(IMPLEMENT_WAVE_SLICES)) # raises WaveError; propagates uncaught
|
|
453
|
+
return results
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
async def _dispatch_implement_verification(
|
|
457
|
+
request: str, task: dict, results: list, skip_llm: bool
|
|
458
|
+
) -> tuple:
|
|
459
|
+
"""Dispatch post-success review-panel + tech-writer verification.
|
|
460
|
+
|
|
461
|
+
Only reached after _dispatch_implement_wave's reconcile() succeeds.
|
|
462
|
+
Both go through dispatch_personas (never a bare dispatch_persona call),
|
|
463
|
+
so an unexpected throwable from either is normalized to a failure stub
|
|
464
|
+
rather than escaping past run_pipeline's `except WaveError` and
|
|
465
|
+
discarding a successful wave's results. A second, independent
|
|
466
|
+
_warn_on_failed_personas call ("Implement review", genuinely non-fatal)
|
|
467
|
+
surfaces a failed member of either dispatch as a stderr WARNING,
|
|
468
|
+
mirroring Research/Plan's own non-fatal-failure-visibility convention.
|
|
469
|
+
"""
|
|
470
|
+
verification_payload = {
|
|
471
|
+
"task": task,
|
|
472
|
+
"request": request,
|
|
473
|
+
"implement_results": results,
|
|
474
|
+
}
|
|
475
|
+
review_results = await dispatch_personas(
|
|
476
|
+
CODE_REVIEW_PANEL, plan=verification_payload, skip_llm=skip_llm
|
|
477
|
+
)
|
|
478
|
+
(tech_writer_result,) = await dispatch_personas(
|
|
479
|
+
[TECH_WRITER_PERSONA],
|
|
480
|
+
plan=verification_payload,
|
|
481
|
+
skip_llm=skip_llm,
|
|
482
|
+
)
|
|
483
|
+
_warn_on_failed_personas("Implement review", review_results + [tech_writer_result])
|
|
484
|
+
return review_results, tech_writer_result
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
async def _default_phase_implement(
|
|
488
|
+
request: str, task: dict, plan_state: dict, skip_llm: bool
|
|
489
|
+
) -> dict:
|
|
490
|
+
"""Dispatch the Implement-phase wave, reconcile it, then verify on success.
|
|
491
|
+
|
|
492
|
+
Composes two independently-changing concerns (see their own docstrings):
|
|
493
|
+
_dispatch_implement_wave (the wave-dispatch/reconcile barrier, whose
|
|
494
|
+
per-step decomposition is tracked future work — see the Script gap in
|
|
495
|
+
agents/orchestrator.md) and _dispatch_implement_verification (a stable
|
|
496
|
+
concern that shouldn't need to move when that lands).
|
|
497
|
+
"""
|
|
498
|
+
results = await _dispatch_implement_wave(request, task, plan_state, skip_llm)
|
|
499
|
+
review_results, tech_writer_result = await _dispatch_implement_verification(
|
|
500
|
+
request, task, results, skip_llm
|
|
501
|
+
)
|
|
502
|
+
return {
|
|
503
|
+
"wave_slices": list(IMPLEMENT_WAVE_SLICES),
|
|
504
|
+
"results": results,
|
|
505
|
+
# list(...), not a bare reference: review_personas is persisted,
|
|
506
|
+
# and a future in-memory consumer mutating this list would
|
|
507
|
+
# otherwise corrupt the shared module constant CODE_REVIEW_PANEL
|
|
508
|
+
# for the rest of the process (same rationale as critic_personas
|
|
509
|
+
# in _default_phase_plan above).
|
|
510
|
+
"review_personas": list(CODE_REVIEW_PANEL),
|
|
511
|
+
"review_results": review_results,
|
|
512
|
+
"tech_writer_result": tech_writer_result,
|
|
513
|
+
"skip_llm": skip_llm,
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
# ---------------------------------------------------------------------------
|
|
518
|
+
# Persona dispatch and wave barrier
|
|
519
|
+
# ---------------------------------------------------------------------------
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
class WaveError(Exception):
|
|
523
|
+
"""Raised when a wave barrier fails (a slice returned status='failed')."""
|
|
524
|
+
|
|
525
|
+
def __init__(self, failing_slice: str, succeeded: list):
|
|
526
|
+
self.failing_slice = failing_slice
|
|
527
|
+
self.succeeded = succeeded
|
|
528
|
+
super().__init__(f"Wave barrier failed on slice '{failing_slice}'")
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _failed_result(persona: str, error: str) -> dict:
|
|
532
|
+
"""Return the canonical dispatch-failure stub shared by every failure site.
|
|
533
|
+
|
|
534
|
+
One normative shape for {persona, status: "failed", error} so a future
|
|
535
|
+
change to the shape (e.g. adding a distinguishing field) touches one
|
|
536
|
+
definition instead of the four call sites that used to hand-construct it
|
|
537
|
+
independently. `error` is required, not defaulted, so a new call site
|
|
538
|
+
must name its cause rather than silently inheriting one that doesn't
|
|
539
|
+
describe it — the four callers today: a malformed dispatch envelope
|
|
540
|
+
("malformed_envelope"), a non-serializable plan payload
|
|
541
|
+
("unserializable_plan"), a subprocess/CLI failure ("llm_unavailable"),
|
|
542
|
+
and an unexpected throwable surfaced by asyncio.gather
|
|
543
|
+
("dispatch_exception").
|
|
544
|
+
"""
|
|
545
|
+
return {"persona": persona, "status": "failed", "error": error}
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def _parse_dispatch_envelope(stdout: str, persona: str) -> dict:
|
|
549
|
+
"""Parse a `claude -p --output-format json` envelope into a dispatch result.
|
|
550
|
+
|
|
551
|
+
Status is derived solely from the envelope's `is_error` field and is
|
|
552
|
+
never overwritten by the parsed payload. For personas in
|
|
553
|
+
JSON_CONTRACT_PERSONAS, the envelope's `result` field is additionally
|
|
554
|
+
parsed as JSON and its keys merged in, except `status` (exposed
|
|
555
|
+
separately as `review_status`, since CODE_REVIEW_PANEL's own contract
|
|
556
|
+
reuses that key name for an unrelated pass/warn/fail/skip vocabulary)
|
|
557
|
+
and `persona` (discarded — already dispatch-owned). For every other
|
|
558
|
+
persona, `result` is always stored verbatim under `output`. A malformed
|
|
559
|
+
or non-object payload — the top-level envelope itself, or (for a
|
|
560
|
+
JSON_CONTRACT_PERSONAS member) the inner `result` — degrades gracefully
|
|
561
|
+
rather than raising: a bad envelope maps to a `"malformed_envelope"`
|
|
562
|
+
failure stub (see `_failed_result`) with no `verdict` key, and a bad
|
|
563
|
+
inner `result` maps to `output` plus a
|
|
564
|
+
`parse_error: True` marker while the already-derived status is left
|
|
565
|
+
untouched.
|
|
566
|
+
"""
|
|
567
|
+
try:
|
|
568
|
+
envelope = json.loads(stdout)
|
|
569
|
+
if not isinstance(envelope, dict):
|
|
570
|
+
raise TypeError("envelope is not a JSON object")
|
|
571
|
+
except (json.JSONDecodeError, TypeError, ValueError):
|
|
572
|
+
return _failed_result(persona, error="malformed_envelope")
|
|
573
|
+
|
|
574
|
+
data = {
|
|
575
|
+
"persona": persona,
|
|
576
|
+
# default True: an envelope that doesn't state whether it errored
|
|
577
|
+
# is not evidence of success.
|
|
578
|
+
"status": "failed" if envelope.get("is_error", True) else "success",
|
|
579
|
+
}
|
|
580
|
+
result_text = envelope.get("result", "")
|
|
581
|
+
if persona in JSON_CONTRACT_PERSONAS:
|
|
582
|
+
try:
|
|
583
|
+
parsed = json.loads(result_text)
|
|
584
|
+
if not isinstance(parsed, dict):
|
|
585
|
+
raise TypeError("parsed result is not a JSON object")
|
|
586
|
+
except (json.JSONDecodeError, TypeError, ValueError):
|
|
587
|
+
data["output"] = result_text
|
|
588
|
+
data["parse_error"] = True
|
|
589
|
+
else:
|
|
590
|
+
# status/persona stay dispatch-owned (AC #3): dispatch status is
|
|
591
|
+
# derived only from the envelope's is_error, never from the
|
|
592
|
+
# parsed payload. CODE_REVIEW_PANEL's own contract reuses the
|
|
593
|
+
# key name "status" for an unrelated pass/warn/fail/skip
|
|
594
|
+
# vocabulary, so expose it separately rather than merge it over.
|
|
595
|
+
for key, value in parsed.items():
|
|
596
|
+
if key == "status":
|
|
597
|
+
data["review_status"] = value
|
|
598
|
+
elif key == "persona":
|
|
599
|
+
continue
|
|
600
|
+
else:
|
|
601
|
+
data[key] = value
|
|
602
|
+
else:
|
|
603
|
+
data["output"] = result_text
|
|
604
|
+
return data
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
async def dispatch_persona(persona: str, plan: dict, skip_llm: bool = False) -> dict:
|
|
608
|
+
"""Dispatch a persona via `claude -p --agent <persona> --output-format json`.
|
|
609
|
+
|
|
610
|
+
In --skip-llm mode, returns a stub success result without invoking the CLI.
|
|
611
|
+
"""
|
|
612
|
+
print(f"INFO: dispatching persona {persona}", file=sys.stderr)
|
|
613
|
+
if skip_llm:
|
|
614
|
+
return {"persona": persona, "status": "success"}
|
|
615
|
+
try:
|
|
616
|
+
# A non-serializable plan value degrades to a failure stub, scoped
|
|
617
|
+
# to this one call, instead of raising out of this coroutine and
|
|
618
|
+
# breaking the asyncio.gather() fan-out in dispatch_personas() for
|
|
619
|
+
# every sibling persona in the same wave. Kept as its own try/except
|
|
620
|
+
# (distinct from the subprocess dispatch below) so a TypeError/
|
|
621
|
+
# ValueError from a genuine bug in the dispatch machinery itself is
|
|
622
|
+
# never mislabeled as this same, narrower serialization failure.
|
|
623
|
+
task_prompt = json.dumps(plan)
|
|
624
|
+
except (TypeError, ValueError):
|
|
625
|
+
return _failed_result(persona, error="unserializable_plan")
|
|
626
|
+
try:
|
|
627
|
+
# Offload to a thread so asyncio.gather over multiple personas actually
|
|
628
|
+
# overlaps instead of blocking the event loop on subprocess.run (#1213).
|
|
629
|
+
loop = asyncio.get_running_loop()
|
|
630
|
+
result = await loop.run_in_executor(
|
|
631
|
+
None,
|
|
632
|
+
functools.partial(
|
|
633
|
+
subprocess.run,
|
|
634
|
+
[
|
|
635
|
+
"claude",
|
|
636
|
+
"-p",
|
|
637
|
+
"--agent",
|
|
638
|
+
persona,
|
|
639
|
+
"--output-format",
|
|
640
|
+
"json",
|
|
641
|
+
task_prompt,
|
|
642
|
+
],
|
|
643
|
+
capture_output=True,
|
|
644
|
+
text=True,
|
|
645
|
+
timeout=PERSONA_DISPATCH_TIMEOUT_S,
|
|
646
|
+
),
|
|
647
|
+
)
|
|
648
|
+
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
|
649
|
+
return _failed_result(persona, error="llm_unavailable")
|
|
650
|
+
|
|
651
|
+
return _parse_dispatch_envelope(result.stdout, persona)
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
async def dispatch_personas(personas: list, plan: dict, skip_llm: bool = False) -> list:
|
|
655
|
+
"""Dispatch all personas concurrently and return their results.
|
|
656
|
+
|
|
657
|
+
return_exceptions=True keeps one persona's unexpected exception (any
|
|
658
|
+
throwable dispatch_persona's own try/except doesn't already convert to a
|
|
659
|
+
failure stub) from cancelling its siblings' in-flight dispatches —
|
|
660
|
+
Research's contract is to aggregate and persist every persona's outcome,
|
|
661
|
+
never to let one bad result silently discard the rest. Matched here on
|
|
662
|
+
BaseException, not Exception: asyncio.CancelledError has subclassed
|
|
663
|
+
BaseException directly (not Exception) since Python 3.8, and a cancelled
|
|
664
|
+
child task's result is exactly what return_exceptions=True aggregates
|
|
665
|
+
here rather than propagates — an Exception-only guard would let it
|
|
666
|
+
through un-normalized and fail JSON serialization downstream.
|
|
667
|
+
"""
|
|
668
|
+
tasks = [dispatch_persona(p, plan, skip_llm) for p in personas]
|
|
669
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
670
|
+
return [
|
|
671
|
+
# Distinct from "llm_unavailable" (a CLI/subprocess-level failure,
|
|
672
|
+
# already handled inside dispatch_persona's own try/except): this
|
|
673
|
+
# branch means something threw out of the coroutine itself — a
|
|
674
|
+
# cancellation or an unforeseen bug — which is not evidence the LLM
|
|
675
|
+
# was unreachable, and the persisted research state must keep the
|
|
676
|
+
# two distinguishable.
|
|
677
|
+
_failed_result(p, error="dispatch_exception") if isinstance(r, BaseException) else r
|
|
678
|
+
for p, r in zip(personas, results)
|
|
679
|
+
]
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
async def reconcile(results: list, wave_slices: list) -> None:
|
|
683
|
+
"""Check wave results; raise WaveError if any slice failed."""
|
|
684
|
+
failed = [r for r in results if r.get("status") == "failed"]
|
|
685
|
+
if failed:
|
|
686
|
+
raise WaveError(
|
|
687
|
+
failing_slice=failed[0]["slice"],
|
|
688
|
+
succeeded=[r["slice"] for r in results if r.get("status") == "success"],
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
def _print_wave_failure(failing_slice: str) -> None:
|
|
693
|
+
"""Print the shared two-line wave-barrier-failure message to stderr.
|
|
694
|
+
|
|
695
|
+
Shared by the --fail-wave simulation branch and the real WaveError-catch
|
|
696
|
+
site in run_pipeline so the resume-hint message has one normative
|
|
697
|
+
definition instead of two independently maintained copies of the same
|
|
698
|
+
two print() calls.
|
|
699
|
+
"""
|
|
700
|
+
print(f"ERROR: wave barrier failed on slice '{failing_slice}'", file=sys.stderr)
|
|
701
|
+
print(f"Resume with: python3 {SCRIPTS / 'orchestrator.py'} --resume", file=sys.stderr)
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
def _resolve_default(fn, default_fn):
|
|
705
|
+
"""Return fn if the caller supplied one, else default_fn.
|
|
706
|
+
|
|
707
|
+
Shared by run_pipeline's phase_research_fn/phase_plan_fn/
|
|
708
|
+
phase_implement_fn injection points so each doesn't add its own
|
|
709
|
+
hand-written `if X_fn is None: X_fn = ...` branch to run_pipeline's own
|
|
710
|
+
body, each one pushing its cyclomatic complexity and parameter count
|
|
711
|
+
higher.
|
|
712
|
+
"""
|
|
713
|
+
return fn if fn is not None else default_fn
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
async def _run_phase(
|
|
717
|
+
phase_name: str, phase_fn, memory_dir: Path, resume: bool, *fn_args
|
|
718
|
+
) -> dict:
|
|
719
|
+
"""Resume-check/dispatch/write a phase's state, shared by every phase.
|
|
720
|
+
|
|
721
|
+
Reads the phase's prior state from disk when resuming; otherwise calls
|
|
722
|
+
phase_fn with fn_args and persists the result via write_progress. This is
|
|
723
|
+
the shape run_pipeline previously hand-inlined once for Research
|
|
724
|
+
(Slice 2) — extracted here so the Plan and Implement call sites reuse
|
|
725
|
+
one definition instead of re-copying it.
|
|
726
|
+
"""
|
|
727
|
+
state = read_progress(phase_name, memory_dir) if resume else None
|
|
728
|
+
if state is None:
|
|
729
|
+
state = await phase_fn(*fn_args)
|
|
730
|
+
write_progress(phase_name, state, memory_dir)
|
|
731
|
+
return state
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
# ---------------------------------------------------------------------------
|
|
735
|
+
# Main pipeline
|
|
736
|
+
# ---------------------------------------------------------------------------
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
async def run_pipeline(
|
|
740
|
+
request: str,
|
|
741
|
+
memory_dir: Path,
|
|
742
|
+
skip_llm: bool = False,
|
|
743
|
+
resume: bool = False,
|
|
744
|
+
classify_fn=None,
|
|
745
|
+
phase_research_fn=None,
|
|
746
|
+
phase_plan_fn=None,
|
|
747
|
+
phase_implement_fn=None,
|
|
748
|
+
fail_wave: bool = False,
|
|
749
|
+
dispatch_personas_flag: bool = False,
|
|
750
|
+
) -> int:
|
|
751
|
+
"""Main orchestration pipeline. Returns exit code (0=success, 1=error)."""
|
|
752
|
+
# Resolve inject-able dependencies
|
|
753
|
+
if classify_fn is None:
|
|
754
|
+
task = await classify(request, skip_llm)
|
|
755
|
+
else:
|
|
756
|
+
task = classify_fn(request)
|
|
757
|
+
if asyncio.iscoroutine(task):
|
|
758
|
+
task = await task
|
|
759
|
+
|
|
760
|
+
phase_research_fn = _resolve_default(phase_research_fn, _default_phase_research)
|
|
761
|
+
phase_plan_fn = _resolve_default(phase_plan_fn, _default_phase_plan)
|
|
762
|
+
phase_implement_fn = _resolve_default(phase_implement_fn, _default_phase_implement)
|
|
763
|
+
|
|
764
|
+
# Fast path for trivial tasks
|
|
765
|
+
if task.get("size") == "trivial":
|
|
766
|
+
print("INFO: trivial task — taking fast path", file=sys.stderr)
|
|
767
|
+
return 0
|
|
768
|
+
|
|
769
|
+
# --resume guard: fail if no state exists at all
|
|
770
|
+
if resume:
|
|
771
|
+
state_files = list(memory_dir.glob("orchestrator-*.json"))
|
|
772
|
+
if not state_files:
|
|
773
|
+
print(
|
|
774
|
+
"ERROR: No prior phase state found; run without --resume to start a new pipeline",
|
|
775
|
+
file=sys.stderr,
|
|
776
|
+
)
|
|
777
|
+
return 1
|
|
778
|
+
|
|
779
|
+
# Wave barrier failure simulation (for testing)
|
|
780
|
+
if fail_wave:
|
|
781
|
+
_print_wave_failure("slice-1")
|
|
782
|
+
return 1
|
|
783
|
+
|
|
784
|
+
# Persona dispatch (for testing --dispatch-personas flag)
|
|
785
|
+
if dispatch_personas_flag:
|
|
786
|
+
await dispatch_personas(
|
|
787
|
+
DEFAULT_PERSONAS,
|
|
788
|
+
plan={"task": task, "request": request},
|
|
789
|
+
skip_llm=skip_llm,
|
|
790
|
+
)
|
|
791
|
+
return 0
|
|
792
|
+
|
|
793
|
+
# Phase 1: Research
|
|
794
|
+
research_state = await _run_phase(
|
|
795
|
+
"research", phase_research_fn, memory_dir, resume, request, task, skip_llm
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
# Phase 2: Plan
|
|
799
|
+
plan_state = await _run_phase(
|
|
800
|
+
"plan", phase_plan_fn, memory_dir, resume, request, task, research_state, skip_llm
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
# Phase 3: Implement
|
|
804
|
+
try:
|
|
805
|
+
await _run_phase(
|
|
806
|
+
"implement", phase_implement_fn, memory_dir, resume, request, task, plan_state, skip_llm
|
|
807
|
+
)
|
|
808
|
+
except WaveError as exc:
|
|
809
|
+
_print_wave_failure(exc.failing_slice)
|
|
810
|
+
return 1
|
|
811
|
+
|
|
812
|
+
return 0
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
# ---------------------------------------------------------------------------
|
|
816
|
+
# Entry point
|
|
817
|
+
# ---------------------------------------------------------------------------
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
def _resolve_request_from_stdin() -> str:
|
|
821
|
+
"""Return the piped stdin request, or "default request" as fallback.
|
|
822
|
+
|
|
823
|
+
Tests stdin CONTENT, not just whether it's a tty: a piped-but-empty
|
|
824
|
+
stdin (e.g. `< /dev/null` in a hook/CI invocation) must still resolve
|
|
825
|
+
to "default request" — an empty request now drives live persona
|
|
826
|
+
dispatch (the Research phase), not just classify().
|
|
827
|
+
"""
|
|
828
|
+
piped_text = sys.stdin.read().strip() if not sys.stdin.isatty() else ""
|
|
829
|
+
return piped_text or "default request"
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
def main(argv=None) -> int:
|
|
833
|
+
ap = argparse.ArgumentParser(description=__doc__)
|
|
834
|
+
ap.add_argument(
|
|
835
|
+
"--resume",
|
|
836
|
+
action="store_true",
|
|
837
|
+
help="Skip phases whose state files already exist",
|
|
838
|
+
)
|
|
839
|
+
ap.add_argument(
|
|
840
|
+
"--skip-llm",
|
|
841
|
+
action="store_true",
|
|
842
|
+
help="Use stubs for classify() and all LLM dispatch",
|
|
843
|
+
)
|
|
844
|
+
ap.add_argument(
|
|
845
|
+
"--memory-dir",
|
|
846
|
+
default=".claude/memory",
|
|
847
|
+
metavar="PATH",
|
|
848
|
+
help="Where to read/write phase state (default: .claude/memory/)",
|
|
849
|
+
)
|
|
850
|
+
ap.add_argument(
|
|
851
|
+
"--classify",
|
|
852
|
+
default=None,
|
|
853
|
+
metavar="SIZE",
|
|
854
|
+
choices=["trivial", "standard", "complex"],
|
|
855
|
+
help="Override classification (for testing)",
|
|
856
|
+
)
|
|
857
|
+
ap.add_argument(
|
|
858
|
+
"--fail-wave",
|
|
859
|
+
action="store_true",
|
|
860
|
+
help="Simulate a wave barrier failure (for testing)",
|
|
861
|
+
)
|
|
862
|
+
ap.add_argument(
|
|
863
|
+
"--dispatch-personas",
|
|
864
|
+
action="store_true",
|
|
865
|
+
help="Dispatch plan-review personas (for testing)",
|
|
866
|
+
)
|
|
867
|
+
args = ap.parse_args(argv)
|
|
868
|
+
|
|
869
|
+
request = _resolve_request_from_stdin()
|
|
870
|
+
memory_dir = Path(args.memory_dir)
|
|
871
|
+
|
|
872
|
+
# Build classify_fn: use CLI override if provided
|
|
873
|
+
classify_fn = None
|
|
874
|
+
if args.classify:
|
|
875
|
+
size = args.classify
|
|
876
|
+
|
|
877
|
+
def classify_fn(req, _size=size):
|
|
878
|
+
return {"size": _size}
|
|
879
|
+
|
|
880
|
+
exit_code = asyncio.run(
|
|
881
|
+
run_pipeline(
|
|
882
|
+
request=request,
|
|
883
|
+
memory_dir=memory_dir,
|
|
884
|
+
skip_llm=args.skip_llm,
|
|
885
|
+
resume=args.resume,
|
|
886
|
+
classify_fn=classify_fn,
|
|
887
|
+
fail_wave=args.fail_wave,
|
|
888
|
+
dispatch_personas_flag=args.dispatch_personas,
|
|
889
|
+
)
|
|
890
|
+
)
|
|
891
|
+
return exit_code
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
if __name__ == "__main__":
|
|
895
|
+
sys.exit(main())
|