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,720 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""gherkin_feature_merge.py — merge newly-derived scenarios into an existing
|
|
3
|
+
.feature file without destroying prior content (issue #1420).
|
|
4
|
+
|
|
5
|
+
`gherkin-derive` used to overwrite `features/<surface>.feature` unconditionally
|
|
6
|
+
on every run. This module gives it a safe alternative: read the existing file,
|
|
7
|
+
locate the named `Feature:` block, skip any candidate scenario whose title
|
|
8
|
+
already exists there (exact match, trimmed), and append only genuinely-new
|
|
9
|
+
scenarios after the block's last existing unit — never mid-block, never
|
|
10
|
+
rewriting untouched text.
|
|
11
|
+
|
|
12
|
+
Two failure causes are distinguished by construction, not inferred after the
|
|
13
|
+
fact: `feature-not-found` (the named `Feature:` title isn't in the existing
|
|
14
|
+
file at all — most likely a human renamed it) and `malformed-feature-block`
|
|
15
|
+
(the title is found, but the block's structure can't be bounded — a dangling
|
|
16
|
+
`@tag` line with no following `Scenario:`, or a `Scenario Outline:` whose
|
|
17
|
+
`Examples:` keyword has no table rows). Both refuse to write rather than
|
|
18
|
+
silently corrupting or duplicating content.
|
|
19
|
+
|
|
20
|
+
Stdlib-only. (ADR 0014/0015).
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
python3 gherkin_feature_merge.py merge --existing <path> --candidates <path> \
|
|
24
|
+
--feature-title "<title>" [--dry-run] [--json]
|
|
25
|
+
python3 gherkin_feature_merge.py check-stale --existing <path> \
|
|
26
|
+
--observed "<title>=<value>" [--observed "<title>=<value>" ...] [--json]
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import argparse
|
|
32
|
+
import json
|
|
33
|
+
import os
|
|
34
|
+
import sys
|
|
35
|
+
import tempfile
|
|
36
|
+
from dataclasses import dataclass
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
|
|
39
|
+
_HERE = Path(__file__).resolve().parent
|
|
40
|
+
sys.path.insert(0, str(_HERE / "lib"))
|
|
41
|
+
|
|
42
|
+
from _gherkin_text import FEATURE_PREFIX as _FEATURE_PREFIX
|
|
43
|
+
from _gherkin_text import SCENARIO_OUTLINE_PREFIX as _SCENARIO_OUTLINE_PREFIX
|
|
44
|
+
from _gherkin_text import SCENARIO_PREFIX as _SCENARIO_PREFIX
|
|
45
|
+
from _gherkin_text import is_tag_line as _is_tag_line
|
|
46
|
+
from _gherkin_text import safe_for_terminal as _safe_for_terminal
|
|
47
|
+
from _gherkin_text import stripped as _stripped
|
|
48
|
+
from _gherkin_text import trim_trailing_tag_run as _trim_trailing_tag_run
|
|
49
|
+
|
|
50
|
+
_BACKGROUND_PREFIX = "Background:"
|
|
51
|
+
_SCENARIO_PREFIXES = (_SCENARIO_OUTLINE_PREFIX, _SCENARIO_PREFIX)
|
|
52
|
+
_EXAMPLES_PREFIX = "Examples:"
|
|
53
|
+
|
|
54
|
+
# The three error sentinels, named once so every return site shares one
|
|
55
|
+
# source of truth instead of a repeated raw literal. (Test assertions also
|
|
56
|
+
# import these directly rather than repeating the string values.)
|
|
57
|
+
ERROR_FEATURE_NOT_FOUND = "feature-not-found"
|
|
58
|
+
ERROR_MALFORMED_FEATURE_BLOCK = "malformed-feature-block"
|
|
59
|
+
ERROR_UNSAFE_PATH = "unsafe-path"
|
|
60
|
+
# _cmd_merge-only: parse_candidate_units reuses ERROR_MALFORMED_FEATURE_BLOCK
|
|
61
|
+
# internally for a malformed --candidates fragment (it shares _parse_units
|
|
62
|
+
# with the existing-block parser, which has only one malformed-shape error).
|
|
63
|
+
# The CLI remaps it to this distinct code so a --json caller can tell "your
|
|
64
|
+
# scratch candidates file is broken" from "the existing .feature file's
|
|
65
|
+
# structure is broken" — two different files, two different remediations —
|
|
66
|
+
# without parse_candidate_units itself needing a second error value.
|
|
67
|
+
ERROR_MALFORMED_CANDIDATES = "malformed-candidates"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class ScenarioUnit:
|
|
72
|
+
"""One `Scenario:`/`Scenario Outline:` unit — its title plus verbatim text
|
|
73
|
+
(including any preceding `@tag` lines and, for an Outline, its Examples
|
|
74
|
+
table) spanning from its start marker to the next unit or block end."""
|
|
75
|
+
|
|
76
|
+
title: str
|
|
77
|
+
line: int # 1-based line number of the Scenario:/Scenario Outline: line
|
|
78
|
+
text: str
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class FeatureBlock:
|
|
83
|
+
"""A bounded `Feature:` block: header line, optional Background text
|
|
84
|
+
(verbatim), and its ordered scenario units."""
|
|
85
|
+
|
|
86
|
+
header_line: int # 1-based
|
|
87
|
+
end_index: int # 0-based exclusive index (into split lines) where the
|
|
88
|
+
# block's body ends — the boundary `merge_scenarios` splices at.
|
|
89
|
+
# Carried on the parsed block itself so a caller that already has a
|
|
90
|
+
# `FeatureBlock` never needs a second `_find_feature_header`/`_block_end`
|
|
91
|
+
# scan to re-derive where it ends.
|
|
92
|
+
background: str | None
|
|
93
|
+
units: list
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class ParseResult:
|
|
98
|
+
block: FeatureBlock | None
|
|
99
|
+
error: str | None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _split_lines(text: str) -> list:
|
|
103
|
+
"""Split preserving original line endings so reconstruction is byte-exact."""
|
|
104
|
+
return text.splitlines(keepends=True)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _scenario_title(line: str) -> str | None:
|
|
108
|
+
"""Return the title text if `line` is a Scenario:/Scenario Outline: line."""
|
|
109
|
+
stripped = _stripped(line).strip()
|
|
110
|
+
for prefix in _SCENARIO_PREFIXES:
|
|
111
|
+
if stripped.startswith(prefix):
|
|
112
|
+
return stripped[len(prefix) :].strip()
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _is_unit_start(line: str) -> bool:
|
|
117
|
+
return _is_tag_line(line) or _scenario_title(line) is not None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _find_feature_header(lines: list, feature_title: str) -> int | None:
|
|
121
|
+
target = feature_title.strip()
|
|
122
|
+
for i, line in enumerate(lines):
|
|
123
|
+
stripped = _stripped(line).strip()
|
|
124
|
+
if stripped.startswith(_FEATURE_PREFIX):
|
|
125
|
+
title = stripped[len(_FEATURE_PREFIX) :].strip()
|
|
126
|
+
if title == target:
|
|
127
|
+
return i
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _block_end(lines: list, header_index: int) -> int:
|
|
132
|
+
"""Index (exclusive) where the block ends: the next Feature: header
|
|
133
|
+
(any title), or end of file. A `@tag`/blank-line run immediately
|
|
134
|
+
preceding that next header belongs to the *next* block (Gherkin tags
|
|
135
|
+
attach to the declaration that follows them), so the boundary backs up
|
|
136
|
+
before it rather than swallowing it into this block's body — otherwise
|
|
137
|
+
a tagged Feature block anywhere but last in the file makes `_parse_units`
|
|
138
|
+
see a dangling tag line and wrongly refuse the merge as
|
|
139
|
+
malformed-feature-block."""
|
|
140
|
+
for j in range(header_index + 1, len(lines)):
|
|
141
|
+
if _stripped(lines[j]).strip().startswith(_FEATURE_PREFIX):
|
|
142
|
+
return _trim_trailing_tag_run(lines, header_index, j)
|
|
143
|
+
return len(lines)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _find_next_unit_start(body: list, start: int, n: int) -> int:
|
|
147
|
+
"""Return the index of the next unit-start line (a tag line or a
|
|
148
|
+
Scenario:/Scenario Outline: line) at or after `start`, or `n` if none
|
|
149
|
+
remains before the block ends. Shared by `_find_background` (where does
|
|
150
|
+
the Background section end) and `_locate_unit` (where does this unit
|
|
151
|
+
end) — both are the same "scan forward for the next unit marker"
|
|
152
|
+
question."""
|
|
153
|
+
for m in range(start, n):
|
|
154
|
+
if _is_unit_start(body[m]):
|
|
155
|
+
return m
|
|
156
|
+
return n
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _find_background(body: list) -> tuple:
|
|
160
|
+
"""Return (background_text_or_None, body_start_index) for the region
|
|
161
|
+
of `body` before the first scenario unit."""
|
|
162
|
+
n = len(body)
|
|
163
|
+
for k, line in enumerate(body):
|
|
164
|
+
if _stripped(line).strip().startswith(_BACKGROUND_PREFIX):
|
|
165
|
+
bg_end = _find_next_unit_start(body, k + 1, n)
|
|
166
|
+
return "".join(body[k:bg_end]), bg_end
|
|
167
|
+
if _is_unit_start(line):
|
|
168
|
+
return None, k
|
|
169
|
+
return None, n
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _outline_missing_examples_table(body: list, title_offset: int, unit_end: int) -> bool:
|
|
173
|
+
"""True when the Scenario Outline starting at `title_offset` declares an
|
|
174
|
+
`Examples:` keyword but the table has no rows before `unit_end` — the
|
|
175
|
+
one structurally malformed shape a Scenario Outline unit can take.
|
|
176
|
+
(A Scenario Outline with no `Examples:` keyword at all isn't this
|
|
177
|
+
function's concern — that's a content choice, not a structural error.)"""
|
|
178
|
+
examples_idx = None
|
|
179
|
+
for m in range(title_offset + 1, unit_end):
|
|
180
|
+
if _stripped(body[m]).strip().startswith(_EXAMPLES_PREFIX):
|
|
181
|
+
examples_idx = m
|
|
182
|
+
break
|
|
183
|
+
if examples_idx is None:
|
|
184
|
+
return False
|
|
185
|
+
return not any(
|
|
186
|
+
_stripped(body[m]).strip().startswith("|") for m in range(examples_idx + 1, unit_end)
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _locate_unit(body: list, idx: int, n: int) -> tuple:
|
|
191
|
+
"""Find the `(title, title_offset, unit_end)` bounds of the unit
|
|
192
|
+
starting at `idx` — the optional leading `@tag` line(s) through the
|
|
193
|
+
`Scenario:`/`Scenario Outline:` line, up to the next unit-start marker
|
|
194
|
+
or block end. `title` is `None` for a dangling run of tag lines with no
|
|
195
|
+
following Scenario(-Outline) line before the block ends; the caller
|
|
196
|
+
treats that as `malformed-feature-block`."""
|
|
197
|
+
title = None
|
|
198
|
+
title_offset = idx
|
|
199
|
+
for scan in range(idx, n):
|
|
200
|
+
t = _scenario_title(body[scan])
|
|
201
|
+
if t is not None:
|
|
202
|
+
title = t
|
|
203
|
+
title_offset = scan
|
|
204
|
+
break
|
|
205
|
+
|
|
206
|
+
search_from = title_offset + 1 if title is not None else idx + 1
|
|
207
|
+
unit_end = _find_next_unit_start(body, search_from, n)
|
|
208
|
+
return title, title_offset, unit_end
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _parse_units(body: list, body_start: int, header_line_no: int):
|
|
212
|
+
"""Parse ordered ScenarioUnits from `body[body_start:]`.
|
|
213
|
+
|
|
214
|
+
Returns (units, None) on success, or (None, "malformed-feature-block")
|
|
215
|
+
on a dangling tag line or an unterminated Scenario Outline (Examples:
|
|
216
|
+
keyword present with no table rows before the unit/block ends).
|
|
217
|
+
"""
|
|
218
|
+
units = []
|
|
219
|
+
idx = body_start
|
|
220
|
+
n = len(body)
|
|
221
|
+
while idx < n:
|
|
222
|
+
if not _is_unit_start(body[idx]):
|
|
223
|
+
idx += 1
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
unit_start = idx
|
|
227
|
+
title, title_offset, unit_end = _locate_unit(body, idx, n)
|
|
228
|
+
|
|
229
|
+
if title is None:
|
|
230
|
+
# A tag line (or run of tag lines) with no following
|
|
231
|
+
# Scenario:/Scenario Outline: before the block ends.
|
|
232
|
+
return None, ERROR_MALFORMED_FEATURE_BLOCK
|
|
233
|
+
|
|
234
|
+
is_outline = _stripped(body[title_offset]).strip().startswith(_SCENARIO_OUTLINE_PREFIX)
|
|
235
|
+
if is_outline and _outline_missing_examples_table(body, title_offset, unit_end):
|
|
236
|
+
return None, ERROR_MALFORMED_FEATURE_BLOCK
|
|
237
|
+
|
|
238
|
+
unit_text = "".join(body[unit_start:unit_end])
|
|
239
|
+
units.append(ScenarioUnit(title=title, line=header_line_no + title_offset + 1, text=unit_text))
|
|
240
|
+
idx = unit_end
|
|
241
|
+
|
|
242
|
+
return units, None
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def parse_feature_block(text: str, feature_title: str) -> ParseResult:
|
|
246
|
+
"""Locate and bound the `Feature: <feature_title>` block in `text`.
|
|
247
|
+
|
|
248
|
+
The "not found" check runs before any structural parse: if no `Feature:`
|
|
249
|
+
header matches `feature_title` anywhere, `error="feature-not-found"` is
|
|
250
|
+
returned immediately. Only once the header is located does a structural
|
|
251
|
+
failure (a dangling `@tag` line, or a `Scenario Outline:` missing its
|
|
252
|
+
`Examples:` table) yield `error="malformed-feature-block"`.
|
|
253
|
+
"""
|
|
254
|
+
lines = _split_lines(text)
|
|
255
|
+
header_index = _find_feature_header(lines, feature_title)
|
|
256
|
+
if header_index is None:
|
|
257
|
+
return ParseResult(block=None, error=ERROR_FEATURE_NOT_FOUND)
|
|
258
|
+
|
|
259
|
+
end_index = _block_end(lines, header_index)
|
|
260
|
+
body = lines[header_index + 1 : end_index]
|
|
261
|
+
|
|
262
|
+
background, body_start = _find_background(body)
|
|
263
|
+
units, error = _parse_units(body, body_start, header_index + 1)
|
|
264
|
+
if error is not None:
|
|
265
|
+
return ParseResult(block=None, error=error)
|
|
266
|
+
|
|
267
|
+
block = FeatureBlock(
|
|
268
|
+
header_line=header_index + 1, end_index=end_index, background=background, units=units
|
|
269
|
+
)
|
|
270
|
+
return ParseResult(block=block, error=None)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def parse_candidate_units(text: str) -> tuple:
|
|
274
|
+
"""Parse headerless candidate scenario text (no `Feature:` wrapper) —
|
|
275
|
+
the `--candidates` scratch-file shape — into ordered ScenarioUnits.
|
|
276
|
+
Reuses the same unit-splitting logic `parse_feature_block` uses for the
|
|
277
|
+
inside of a block, since a candidates file is exactly a block's body
|
|
278
|
+
with no Background section expected.
|
|
279
|
+
|
|
280
|
+
Returns `(units, error)`, mirroring `parse_feature_block`'s error
|
|
281
|
+
contract: a structurally malformed candidates fragment (a dangling
|
|
282
|
+
`@tag` line, or a `Scenario Outline:` missing its `Examples:` table) is
|
|
283
|
+
reported as `error="malformed-feature-block"` with `units == []`, never
|
|
284
|
+
silently collapsed to an empty, error-less list — a broken
|
|
285
|
+
`--candidates` scratch file must surface as a diagnosable failure, not
|
|
286
|
+
a silent "merged 0 scenarios" success."""
|
|
287
|
+
body = _split_lines(text)
|
|
288
|
+
units, error = _parse_units(body, 0, 0)
|
|
289
|
+
if error is not None:
|
|
290
|
+
return [], error
|
|
291
|
+
return units, None
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@dataclass
|
|
295
|
+
class MergeResult:
|
|
296
|
+
text: str
|
|
297
|
+
added_titles: list
|
|
298
|
+
skipped_duplicate_titles: list
|
|
299
|
+
error: str | None
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _dedupe_candidate_units(units: list) -> tuple:
|
|
303
|
+
"""Return `(deduped_units, self_duplicate_titles)`. Two candidates
|
|
304
|
+
sharing a trimmed title would otherwise both be appended — silently
|
|
305
|
+
making one invisible downstream (`find_then_step_text` keys its map by
|
|
306
|
+
title, last-writer-wins), so a self-collision is dropped and reported
|
|
307
|
+
the same way a collision against an existing title already is."""
|
|
308
|
+
seen = set()
|
|
309
|
+
deduped = []
|
|
310
|
+
duplicates = []
|
|
311
|
+
for unit in units:
|
|
312
|
+
key = unit.title.strip()
|
|
313
|
+
if key in seen:
|
|
314
|
+
duplicates.append(unit.title)
|
|
315
|
+
continue
|
|
316
|
+
seen.add(key)
|
|
317
|
+
deduped.append(unit)
|
|
318
|
+
return deduped, duplicates
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _last_line_ending(lines: list) -> str:
|
|
322
|
+
"""Return the line ending of the last *terminated* line in `lines`
|
|
323
|
+
(scanning from the end), defaulting to `"\\n"` — so a separator
|
|
324
|
+
inserted at the splice point matches the existing file's convention
|
|
325
|
+
instead of always injecting LF into a CRLF file. This is a last-line
|
|
326
|
+
check, not a majority vote across the file (a mixed-ending file's
|
|
327
|
+
splice separator follows whichever ending is nearest the insertion
|
|
328
|
+
point, not whichever is more common) — only the inserted separator is
|
|
329
|
+
normalized this way; a new candidate unit's own interior line endings
|
|
330
|
+
(as authored) are passed through unchanged, so merging into a CRLF file
|
|
331
|
+
can still yield a file with mixed endings overall."""
|
|
332
|
+
for line in reversed(lines):
|
|
333
|
+
if line.endswith("\r\n"):
|
|
334
|
+
return "\r\n"
|
|
335
|
+
if line.endswith("\n"):
|
|
336
|
+
return "\n"
|
|
337
|
+
return "\n"
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _existing_units_use_blank_line_separator(units: list) -> bool:
|
|
341
|
+
"""Infer whether this Feature block's existing scenarios are separated
|
|
342
|
+
by a blank line, from the joins between consecutive existing units —
|
|
343
|
+
excluding the boundary after the very last unit, which sits at the
|
|
344
|
+
block's end and may carry no trailing blank line for a reason unrelated
|
|
345
|
+
to the file's separator convention (there is simply nothing after it to
|
|
346
|
+
separate from). Defaults to `True` when there are fewer than two units
|
|
347
|
+
to compare, matching the blank-line-between-scenarios style most
|
|
348
|
+
existing Gherkin files already use — a majority vote (ties favor
|
|
349
|
+
inserting a blank line) otherwise, so `merge_scenarios` reproduces
|
|
350
|
+
whichever convention this specific file's own scenarios demonstrate
|
|
351
|
+
rather than assuming one."""
|
|
352
|
+
comparable = units[:-1]
|
|
353
|
+
if not comparable:
|
|
354
|
+
return True
|
|
355
|
+
separated = sum(1 for u in comparable if u.text.rstrip(" \t").endswith(("\n\n", "\r\n\r\n")))
|
|
356
|
+
return separated * 2 >= len(comparable)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def merge_scenarios(existing_text: str, feature_title: str, candidate_units: list) -> MergeResult:
|
|
360
|
+
"""Append-only merge of `candidate_units` into the named Feature block.
|
|
361
|
+
|
|
362
|
+
Relays `parse_feature_block`'s error unchanged when the block can't be
|
|
363
|
+
located or bounded — this function never re-derives or guesses the
|
|
364
|
+
cause. When `existing_text` is empty, synthesizes a fresh
|
|
365
|
+
`Feature: <feature_title>` block containing exactly the candidates (no
|
|
366
|
+
parser call needed — nothing to locate).
|
|
367
|
+
"""
|
|
368
|
+
candidate_units, self_duplicate_titles = _dedupe_candidate_units(candidate_units)
|
|
369
|
+
|
|
370
|
+
if not existing_text:
|
|
371
|
+
header = f"{_FEATURE_PREFIX} {feature_title}\n\n"
|
|
372
|
+
body = "".join(unit.text for unit in candidate_units)
|
|
373
|
+
return MergeResult(
|
|
374
|
+
text=header + body,
|
|
375
|
+
added_titles=[unit.title for unit in candidate_units],
|
|
376
|
+
skipped_duplicate_titles=self_duplicate_titles,
|
|
377
|
+
error=None,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
result = parse_feature_block(existing_text, feature_title)
|
|
381
|
+
if result.error is not None:
|
|
382
|
+
return MergeResult(
|
|
383
|
+
text=existing_text,
|
|
384
|
+
added_titles=[],
|
|
385
|
+
skipped_duplicate_titles=[],
|
|
386
|
+
error=result.error,
|
|
387
|
+
)
|
|
388
|
+
# parse_feature_block's contract: block is set whenever error is None.
|
|
389
|
+
assert result.block is not None
|
|
390
|
+
|
|
391
|
+
existing_titles = {unit.title.strip() for unit in result.block.units}
|
|
392
|
+
new_units = [u for u in candidate_units if u.title.strip() not in existing_titles]
|
|
393
|
+
skipped = self_duplicate_titles + [
|
|
394
|
+
u.title for u in candidate_units if u.title.strip() in existing_titles
|
|
395
|
+
]
|
|
396
|
+
|
|
397
|
+
if not new_units:
|
|
398
|
+
return MergeResult(
|
|
399
|
+
text=existing_text, added_titles=[], skipped_duplicate_titles=skipped, error=None
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
# Reuse the boundary `parse_feature_block` already computed above rather
|
|
403
|
+
# than re-deriving it with a second, type-unsafe `_find_feature_header`/
|
|
404
|
+
# `_block_end` pass — `result.block` is guaranteed non-None here since
|
|
405
|
+
# `result.error is None` was just checked.
|
|
406
|
+
lines = _split_lines(existing_text)
|
|
407
|
+
end_index = result.block.end_index
|
|
408
|
+
ending = _last_line_ending(lines)
|
|
409
|
+
|
|
410
|
+
insertion = "".join(unit.text for unit in new_units)
|
|
411
|
+
# Insert right at the block's end boundary, preserving everything before
|
|
412
|
+
# and after byte-for-byte — except that a missing line terminator right
|
|
413
|
+
# at the splice point (existing_text has no trailing newline, or the
|
|
414
|
+
# last new unit's text doesn't end in one) is restored, matching the
|
|
415
|
+
# file's own dominant line ending, rather than left to fuse two Gherkin
|
|
416
|
+
# lines into one. This never rewrites either line's content, only the
|
|
417
|
+
# separator whose absence would otherwise corrupt it.
|
|
418
|
+
prefix_lines = list(lines[:end_index])
|
|
419
|
+
if prefix_lines and not prefix_lines[-1].endswith("\n"):
|
|
420
|
+
prefix_lines[-1] += ending
|
|
421
|
+
if insertion and not insertion.endswith("\n"):
|
|
422
|
+
insertion += ending
|
|
423
|
+
# The last existing unit sits at the block's end with nothing after it
|
|
424
|
+
# to separate from, so its own text alone can't say whether this file
|
|
425
|
+
# puts a blank line between scenarios — infer that from the *other*
|
|
426
|
+
# existing joins instead, and reproduce it here rather than assuming
|
|
427
|
+
# one convention for every repo (issue: a splice landing flush against
|
|
428
|
+
# the last scenario's final line, with no separating blank line, even
|
|
429
|
+
# when every other scenario boundary in the file has one).
|
|
430
|
+
if (
|
|
431
|
+
prefix_lines
|
|
432
|
+
and prefix_lines[-1].strip() != ""
|
|
433
|
+
and _existing_units_use_blank_line_separator(result.block.units)
|
|
434
|
+
):
|
|
435
|
+
prefix_lines.append(ending)
|
|
436
|
+
new_lines = prefix_lines + [insertion] + lines[end_index:]
|
|
437
|
+
merged_text = "".join(new_lines)
|
|
438
|
+
|
|
439
|
+
return MergeResult(
|
|
440
|
+
text=merged_text,
|
|
441
|
+
added_titles=[u.title for u in new_units],
|
|
442
|
+
skipped_duplicate_titles=skipped,
|
|
443
|
+
error=None,
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def find_then_step_text(existing_text: str, feature_title: str) -> dict:
|
|
448
|
+
"""Map each retained scenario's title to `(line, then_text)` — the
|
|
449
|
+
verbatim `Then`/`And`/`But`-continuation step text (joined) plus the
|
|
450
|
+
scenario's 1-based line number, for the stale-scenario check below.
|
|
451
|
+
The line is threaded through (not just the text) so `check-stale` can
|
|
452
|
+
report `<file>:<line>`, the format `gherkin-derive/SKILL.md` Step 6
|
|
453
|
+
mandates — without it the line number `ScenarioUnit.line` already
|
|
454
|
+
computes would be silently dropped on the floor. `But` is included
|
|
455
|
+
alongside `And` because Gherkin permits either as a Then-continuation
|
|
456
|
+
(e.g. `Then the request is rejected / But no partial record is
|
|
457
|
+
written`) — omitting it would silently drop part of the asserted
|
|
458
|
+
behavior from the comparison `is_stale` makes below."""
|
|
459
|
+
result = parse_feature_block(existing_text, feature_title)
|
|
460
|
+
if result.error is not None or result.block is None:
|
|
461
|
+
return {}
|
|
462
|
+
|
|
463
|
+
out = {}
|
|
464
|
+
for unit in result.block.units:
|
|
465
|
+
then_lines = []
|
|
466
|
+
capturing = False
|
|
467
|
+
for line in _split_lines(unit.text):
|
|
468
|
+
stripped = _stripped(line).strip()
|
|
469
|
+
if stripped.startswith("Then "):
|
|
470
|
+
capturing = True
|
|
471
|
+
then_lines.append(line)
|
|
472
|
+
elif capturing and stripped.startswith(("And ", "But ")):
|
|
473
|
+
then_lines.append(line)
|
|
474
|
+
elif capturing:
|
|
475
|
+
capturing = False
|
|
476
|
+
out[unit.title] = (unit.line, "".join(then_lines))
|
|
477
|
+
return out
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def is_stale(then_text: str, observed_value: str) -> bool:
|
|
481
|
+
"""Best-effort, case-insensitive substring containment check: `False`
|
|
482
|
+
(not stale) when `observed_value` appears in `then_text`, `True`
|
|
483
|
+
otherwise. A scenario with no Then step (`then_text == ""`) is never
|
|
484
|
+
reported stale — nothing to compare."""
|
|
485
|
+
if not then_text:
|
|
486
|
+
return False
|
|
487
|
+
return observed_value.lower() not in then_text.lower()
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def _write_error(message: str) -> None:
|
|
491
|
+
sys.stderr.write(message + "\n")
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def _reject_path_traversal(raw: str) -> str | None:
|
|
495
|
+
"""Return an error message if `raw` contains a `..` path component,
|
|
496
|
+
else `None`. `--existing` is not always typed by a human — gherkin-
|
|
497
|
+
derive/SKILL.md Step 2 composes it from a surface name derived from the
|
|
498
|
+
*target repository's* own content (OpenAPI paths, route strings,
|
|
499
|
+
exported symbol names), so a hostile or malformed segment containing
|
|
500
|
+
`../` must not be allowed to steer the write (or the read) outside the
|
|
501
|
+
intended directory."""
|
|
502
|
+
if ".." in Path(raw).parts:
|
|
503
|
+
return f"--existing {raw!r} must not contain a '..' path component"
|
|
504
|
+
return None
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def _merge_payload(written: bool, added=(), skipped=(), error=None) -> dict:
|
|
508
|
+
"""The one JSON shape every `merge` response uses (`check-stale` has its
|
|
509
|
+
own distinct `{findings, unmatched_titles}` shape, built separately in
|
|
510
|
+
`_cmd_check_stale` — not this helper), named once so a future field
|
|
511
|
+
addition or rename to the `merge` response touches this single place
|
|
512
|
+
instead of every `merge`-side call site rebuilding the dict by hand."""
|
|
513
|
+
return {
|
|
514
|
+
"written": written,
|
|
515
|
+
"added_titles": list(added),
|
|
516
|
+
"skipped_duplicate_titles": list(skipped),
|
|
517
|
+
"error": error,
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def _write_atomic(path: Path, text: str) -> None:
|
|
522
|
+
"""Write `text` to `path` via a same-directory temp file + os.replace, so a
|
|
523
|
+
crash or kill mid-write can never leave a truncated/corrupted .feature
|
|
524
|
+
file — the one failure mode this module exists to rule out."""
|
|
525
|
+
fd, tmp_name = tempfile.mkstemp(dir=str(path.parent), prefix=f".{path.name}.", suffix=".tmp")
|
|
526
|
+
try:
|
|
527
|
+
with os.fdopen(fd, "w", encoding="utf-8") as tmp_file:
|
|
528
|
+
tmp_file.write(text)
|
|
529
|
+
os.replace(tmp_name, path)
|
|
530
|
+
except BaseException:
|
|
531
|
+
Path(tmp_name).unlink(missing_ok=True)
|
|
532
|
+
raise
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def _cmd_merge(args: argparse.Namespace) -> int:
|
|
536
|
+
# Known limitation (concurrency-review, issue #1420 follow-up review):
|
|
537
|
+
# read-then-write here is still a TOCTOU race if two `merge` invocations
|
|
538
|
+
# ever target the same --existing path concurrently — whichever writes
|
|
539
|
+
# last silently wins (the atomic write below only rules out a truncated
|
|
540
|
+
# file, not a lost update between two racing invocations). Not fixed:
|
|
541
|
+
# gherkin-derive's own design invokes this once per surface,
|
|
542
|
+
# sequentially, never in parallel against the same file (see
|
|
543
|
+
# gherkin-derive/SKILL.md's Output step), so there is no known live
|
|
544
|
+
# trigger; a cross-platform lock (fcntl vs. msvcrt) would close this
|
|
545
|
+
# remaining gap but is a design call for a human to make, not a
|
|
546
|
+
# mechanical fix — deliberately left to a human decision rather than
|
|
547
|
+
# guessed at here.
|
|
548
|
+
path_error = _reject_path_traversal(args.existing)
|
|
549
|
+
if path_error is not None:
|
|
550
|
+
if args.json:
|
|
551
|
+
print(json.dumps(_merge_payload(written=False, error=ERROR_UNSAFE_PATH)))
|
|
552
|
+
else:
|
|
553
|
+
_write_error(f"gherkin_feature_merge: {path_error} — no scenarios merged")
|
|
554
|
+
return 2
|
|
555
|
+
|
|
556
|
+
existing_path = Path(args.existing)
|
|
557
|
+
existing_text = existing_path.read_text(encoding="utf-8") if existing_path.is_file() else ""
|
|
558
|
+
candidates_text = Path(args.candidates).read_text(encoding="utf-8")
|
|
559
|
+
candidate_units, candidates_error = parse_candidate_units(candidates_text)
|
|
560
|
+
|
|
561
|
+
if candidates_error is not None:
|
|
562
|
+
if args.json:
|
|
563
|
+
print(json.dumps(_merge_payload(written=False, error=ERROR_MALFORMED_CANDIDATES)))
|
|
564
|
+
else:
|
|
565
|
+
_write_error(
|
|
566
|
+
f"gherkin_feature_merge: candidates file {_safe_for_terminal(args.candidates)} "
|
|
567
|
+
f"is malformed (error={candidates_error}) — no scenarios merged, existing file "
|
|
568
|
+
f"left untouched"
|
|
569
|
+
)
|
|
570
|
+
return 2
|
|
571
|
+
|
|
572
|
+
result = merge_scenarios(existing_text, args.feature_title, candidate_units)
|
|
573
|
+
|
|
574
|
+
if result.error is not None:
|
|
575
|
+
if args.json:
|
|
576
|
+
print(json.dumps(_merge_payload(written=False, error=result.error)))
|
|
577
|
+
elif result.error == ERROR_FEATURE_NOT_FOUND:
|
|
578
|
+
_write_error(
|
|
579
|
+
f"gherkin_feature_merge: could not locate Feature: {args.feature_title!r} "
|
|
580
|
+
f"in {_safe_for_terminal(args.existing)} — no scenarios merged, existing file "
|
|
581
|
+
f"left untouched (error={result.error})"
|
|
582
|
+
)
|
|
583
|
+
else:
|
|
584
|
+
_write_error(
|
|
585
|
+
f"gherkin_feature_merge: found Feature: {args.feature_title!r} in "
|
|
586
|
+
f"{_safe_for_terminal(args.existing)} but its structure could not be parsed — "
|
|
587
|
+
f"no scenarios merged, existing file left untouched (error={result.error})"
|
|
588
|
+
)
|
|
589
|
+
return 2
|
|
590
|
+
|
|
591
|
+
if args.dry_run:
|
|
592
|
+
if args.json:
|
|
593
|
+
print(
|
|
594
|
+
json.dumps(
|
|
595
|
+
_merge_payload(
|
|
596
|
+
written=False,
|
|
597
|
+
added=result.added_titles,
|
|
598
|
+
skipped=result.skipped_duplicate_titles,
|
|
599
|
+
)
|
|
600
|
+
)
|
|
601
|
+
)
|
|
602
|
+
else:
|
|
603
|
+
sys.stdout.write(result.text)
|
|
604
|
+
return 0
|
|
605
|
+
|
|
606
|
+
existing_path.parent.mkdir(parents=True, exist_ok=True)
|
|
607
|
+
_write_atomic(existing_path, result.text)
|
|
608
|
+
|
|
609
|
+
if args.json:
|
|
610
|
+
print(
|
|
611
|
+
json.dumps(
|
|
612
|
+
_merge_payload(
|
|
613
|
+
written=True,
|
|
614
|
+
added=result.added_titles,
|
|
615
|
+
skipped=result.skipped_duplicate_titles,
|
|
616
|
+
)
|
|
617
|
+
)
|
|
618
|
+
)
|
|
619
|
+
else:
|
|
620
|
+
print(
|
|
621
|
+
f"OK: merged {len(result.added_titles)} new scenario(s) into "
|
|
622
|
+
f"{_safe_for_terminal(args.existing)}"
|
|
623
|
+
)
|
|
624
|
+
return 0
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def _cmd_check_stale(args: argparse.Namespace) -> int:
|
|
628
|
+
# Same untrusted, SKILL.md-composed provenance as --existing in _cmd_merge
|
|
629
|
+
# (security-review) — the guard applies to both subcommands, not just the
|
|
630
|
+
# one that writes, so a future change that starts echoing parsed content
|
|
631
|
+
# here doesn't quietly reopen the gap.
|
|
632
|
+
path_error = _reject_path_traversal(args.existing)
|
|
633
|
+
if path_error is not None:
|
|
634
|
+
if args.json:
|
|
635
|
+
print(json.dumps({"findings": [], "unmatched_titles": [], "error": ERROR_UNSAFE_PATH}))
|
|
636
|
+
else:
|
|
637
|
+
_write_error(f"gherkin_feature_merge: {path_error}")
|
|
638
|
+
return 2
|
|
639
|
+
|
|
640
|
+
existing_path = Path(args.existing)
|
|
641
|
+
existing_text = existing_path.read_text(encoding="utf-8") if existing_path.is_file() else ""
|
|
642
|
+
|
|
643
|
+
observed = {}
|
|
644
|
+
for raw in args.observed:
|
|
645
|
+
# rpartition (not partition): the title is the untrusted, arbitrary-
|
|
646
|
+
# content half of "<title>=<value>", so a title containing its own
|
|
647
|
+
# "=" (e.g. "Create order fails when qty=0") must not be truncated
|
|
648
|
+
# at the first "=" — split at the last one instead.
|
|
649
|
+
title, _, value = raw.rpartition("=")
|
|
650
|
+
observed[title.strip()] = value
|
|
651
|
+
|
|
652
|
+
then_texts = find_then_step_text(existing_text, args.feature_title)
|
|
653
|
+
|
|
654
|
+
findings = []
|
|
655
|
+
unmatched_titles = []
|
|
656
|
+
for title, value in observed.items():
|
|
657
|
+
entry = then_texts.get(title)
|
|
658
|
+
if entry is None:
|
|
659
|
+
# The observed title isn't an exact key in the retained block —
|
|
660
|
+
# e.g. the caller's title-extraction diverged from the retained
|
|
661
|
+
# scenario's exact text. Report this distinctly rather than
|
|
662
|
+
# silently treating it the same as "nothing to compare"; drift
|
|
663
|
+
# detection is the entire point of this command, so a
|
|
664
|
+
# title-match failure must be diagnosable, not invisible.
|
|
665
|
+
unmatched_titles.append(title)
|
|
666
|
+
continue
|
|
667
|
+
line, then_text = entry
|
|
668
|
+
if is_stale(then_text, value):
|
|
669
|
+
findings.append(
|
|
670
|
+
{
|
|
671
|
+
"title": title,
|
|
672
|
+
"line": line,
|
|
673
|
+
"then_text": then_text.strip(),
|
|
674
|
+
"observed": value,
|
|
675
|
+
}
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
if args.json:
|
|
679
|
+
print(json.dumps({"findings": findings, "unmatched_titles": unmatched_titles}))
|
|
680
|
+
else:
|
|
681
|
+
for entry in findings:
|
|
682
|
+
print(
|
|
683
|
+
f"possibly stale: {_safe_for_terminal(args.existing)}:{entry['line']} "
|
|
684
|
+
f"({entry['title']!r}) — asserts {entry['then_text']!r}, "
|
|
685
|
+
f"code now does {entry['observed']!r}"
|
|
686
|
+
)
|
|
687
|
+
for title in unmatched_titles:
|
|
688
|
+
print(
|
|
689
|
+
f"unmatched: --observed title {title!r} not found among retained scenarios "
|
|
690
|
+
f"in {args.feature_title!r} — check for a title mismatch between the "
|
|
691
|
+
f"observed title and the scenario it should describe"
|
|
692
|
+
)
|
|
693
|
+
return 0
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
def main(argv: list | None = None) -> int:
|
|
697
|
+
parser = argparse.ArgumentParser(prog="gherkin_feature_merge.py")
|
|
698
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
699
|
+
|
|
700
|
+
merge_parser = sub.add_parser("merge")
|
|
701
|
+
merge_parser.add_argument("--existing", required=True)
|
|
702
|
+
merge_parser.add_argument("--candidates", required=True)
|
|
703
|
+
merge_parser.add_argument("--feature-title", required=True)
|
|
704
|
+
merge_parser.add_argument("--dry-run", action="store_true")
|
|
705
|
+
merge_parser.add_argument("--json", action="store_true")
|
|
706
|
+
merge_parser.set_defaults(func=_cmd_merge)
|
|
707
|
+
|
|
708
|
+
stale_parser = sub.add_parser("check-stale")
|
|
709
|
+
stale_parser.add_argument("--existing", required=True)
|
|
710
|
+
stale_parser.add_argument("--feature-title", required=True)
|
|
711
|
+
stale_parser.add_argument("--observed", action="append", default=[])
|
|
712
|
+
stale_parser.add_argument("--json", action="store_true")
|
|
713
|
+
stale_parser.set_defaults(func=_cmd_check_stale)
|
|
714
|
+
|
|
715
|
+
args = parser.parse_args(argv)
|
|
716
|
+
return args.func(args)
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
if __name__ == "__main__": # pragma: no cover
|
|
720
|
+
sys.exit(main())
|