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,557 @@
|
|
|
1
|
+
"""atomic_state — shared atomic-write + cross-OS advisory-lock helpers for hooks.
|
|
2
|
+
|
|
3
|
+
Several advisory hooks persist tiny per-session/per-project state with a
|
|
4
|
+
read-modify-write cycle against a shared on-disk file. Two concurrent
|
|
5
|
+
invocations (two Bash tool calls firing close together, or two
|
|
6
|
+
sessions/worktrees touching the same repo) can race and lose an update
|
|
7
|
+
(#1501). This module centralizes the two primitives that close that race:
|
|
8
|
+
|
|
9
|
+
- `atomic_write(path, text)` — write via a same-directory tempfile + rename
|
|
10
|
+
so a concurrent reader never observes a torn/partial file.
|
|
11
|
+
- `locked_state(path)` — a cross-OS exclusive advisory lock held for the
|
|
12
|
+
full read-modify-write cycle so counters increment exactly once per
|
|
13
|
+
invocation instead of clobbering each other.
|
|
14
|
+
|
|
15
|
+
The lock idiom (`fcntl.flock` on POSIX, `msvcrt.locking` on Windows,
|
|
16
|
+
fail-open when neither is available) started as a copy of
|
|
17
|
+
`hooks/code_intelligence_turn_mark.py::_sentinel_lock` — #1888 (below) bounded
|
|
18
|
+
the acquire wait here but deliberately left that sibling's blocking wait
|
|
19
|
+
alone (a PostToolUse nudge sentinel, not a safety-critical guard verdict), so
|
|
20
|
+
the two no longer agree on acquire behavior; only the release-path idiom
|
|
21
|
+
still matches. Everything here is fail-open: any I/O or locking failure runs
|
|
22
|
+
the critical section unlocked / skips the persist rather than raising —
|
|
23
|
+
these are advisory nudges, never gates — unless a caller opts into
|
|
24
|
+
`locked_state(path, strict=True)`, in which case the same failure raises
|
|
25
|
+
`RuntimeError` instead of running unlocked.
|
|
26
|
+
|
|
27
|
+
Lock *acquisition* is bounded, not a blocking wait (#1888): a hung sibling
|
|
28
|
+
process holding the lock must never leave a caller blocked indefinitely,
|
|
29
|
+
since some callers (e.g. `boundary_events.py`, per #1874) sit in front of a
|
|
30
|
+
safety-critical guard hook's verdict. `locked_state` polls a non-blocking
|
|
31
|
+
acquisition for a bounded total budget and falls through to running the
|
|
32
|
+
critical section UNLOCKED once that budget elapses — the same fail-open
|
|
33
|
+
posture as every other failure mode here. This bounds contention on an
|
|
34
|
+
already-held lock specifically; it does NOT bound a stall inside `open()` on
|
|
35
|
+
the lock file itself, or inside a caller's own I/O in the critical section —
|
|
36
|
+
a wedged mount can still block there. Every one of these fail-open points is
|
|
37
|
+
skippable per call: pass `strict=True` to `locked_state` and the same failure
|
|
38
|
+
raises `RuntimeError` instead of running unlocked — for a caller (e.g. a
|
|
39
|
+
read-modify-write whose lost update is a correctness bug, not an advisory
|
|
40
|
+
nudge) that must never silently proceed without the lock.
|
|
41
|
+
|
|
42
|
+
A second use case (#1889) reuses `locked_state` for pure append-serialization
|
|
43
|
+
rather than read-modify-write: several `.claude/metrics/*.jsonl` telemetry
|
|
44
|
+
streams have concurrent writers of their own — sliced-mode code-review's 2-3
|
|
45
|
+
parallel slices, `/build`'s wave-parallel fan-out
|
|
46
|
+
(`DEV_TEAM_MAX_PARALLEL_BUILDS`), or two sessions/worktrees appending to the
|
|
47
|
+
same shared log. Without the lock, two processes' `write()` calls can
|
|
48
|
+
interleave mid-line and corrupt the stream for every downstream consumer.
|
|
49
|
+
`append_line_locked(path, line)` below is the shared helper for that shape.
|
|
50
|
+
|
|
51
|
+
The lock file is opened with `O_NOFOLLOW` (where the platform defines it)
|
|
52
|
+
and created at mode `0600` (security review, #1889): a pre-planted symlink
|
|
53
|
+
at `<path>.lock` would otherwise be followed by a plain `open()`, and a
|
|
54
|
+
world/group-writable lock file would let another local user on a shared
|
|
55
|
+
host acquire and hold it (an `flock`/`msvcrt.locking` caller needs no write
|
|
56
|
+
access to the *data* file to do this — only to the lock file). `O_NOFOLLOW`
|
|
57
|
+
makes `open()` fail closed (OSError) on a symlinked lock path, which this
|
|
58
|
+
function's existing fail-open contract already turns into an unlocked
|
|
59
|
+
critical section — the same outcome as any other lock-open failure.
|
|
60
|
+
|
|
61
|
+
Stdlib-only.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
from __future__ import annotations
|
|
65
|
+
|
|
66
|
+
import contextlib
|
|
67
|
+
import errno
|
|
68
|
+
import os
|
|
69
|
+
import sys
|
|
70
|
+
import tempfile
|
|
71
|
+
import time
|
|
72
|
+
from collections.abc import Iterator
|
|
73
|
+
from pathlib import Path
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
import fcntl # type: ignore[import-not-found]
|
|
77
|
+
except ImportError: # pragma: no cover — Windows has no fcntl
|
|
78
|
+
fcntl = None # type: ignore[assignment]
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
import msvcrt # type: ignore[import-not-found]
|
|
82
|
+
except ImportError: # pragma: no cover — POSIX has no msvcrt
|
|
83
|
+
msvcrt = None # type: ignore[assignment]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# Total time `locked_state` will poll for the lock before giving up and
|
|
87
|
+
# running the critical section unlocked (#1888). Sized comfortably above any
|
|
88
|
+
# legitimate contention chain this module's consumers exercise — the shared
|
|
89
|
+
# concurrency regression test's worst case is 8 workers x 60ms holds (~480ms
|
|
90
|
+
# of legitimate queueing; see test_lock_acquire_budget_exceeds_worst_case_
|
|
91
|
+
# contention, which pins this relationship mechanically) — so normal
|
|
92
|
+
# contention still fully serializes and only a genuinely stalled holder ever
|
|
93
|
+
# trips the give-up path.
|
|
94
|
+
_DEFAULT_LOCK_ACQUIRE_BUDGET_SECONDS = 2.0
|
|
95
|
+
_LOCK_POLL_INTERVAL_SECONDS = 0.01
|
|
96
|
+
|
|
97
|
+
# Hard ceiling on the test-only override below (security review, #1888): the
|
|
98
|
+
# override has a floor to stop a misconfigured `0` from removing the retry,
|
|
99
|
+
# but nothing stopped the OTHER direction — an arbitrarily large value would
|
|
100
|
+
# reinstate exactly the unbounded wait this module exists to remove. No
|
|
101
|
+
# configuration can raise the give-up bound past this.
|
|
102
|
+
_MAX_LOCK_ACQUIRE_BUDGET_SECONDS = 10.0
|
|
103
|
+
|
|
104
|
+
# `_try_acquire`'s non-blocking attempt raises OSError both for ordinary lock
|
|
105
|
+
# contention (retry-worthy) and for conditions retrying can never fix — e.g.
|
|
106
|
+
# ENOLCK/EOPNOTSUPP when flock isn't supported on the underlying filesystem
|
|
107
|
+
# (some NFS/FUSE/CIFS mounts). Without this distinction, `_acquire_bounded`
|
|
108
|
+
# burned the full budget on every single call on such a filesystem, which is
|
|
109
|
+
# strictly worse than the pre-#1888 behavior (an immediate, un-retried
|
|
110
|
+
# failure) it must not regress. Only these errnos — genuine "someone else
|
|
111
|
+
# holds it" signals on POSIX (`flock`) and Windows (`msvcrt.locking`) — are
|
|
112
|
+
# worth retrying; anything else gives up immediately.
|
|
113
|
+
#
|
|
114
|
+
# EDEADLOCK/EDEADLK (#1904 item 9, defensive — no recorded Windows probe):
|
|
115
|
+
# Windows CRT `_locking` (the backend behind `msvcrt.locking`) documents
|
|
116
|
+
# surfacing a deadlock-shaped errno on contention in some circumstances. Both
|
|
117
|
+
# are POSIX-optional attributes (absent on some platforms), hence the same
|
|
118
|
+
# `getattr(..., None)` + filter idiom already used for EWOULDBLOCK above.
|
|
119
|
+
_LOCK_CONTENTION_ERRNOS = frozenset(
|
|
120
|
+
e
|
|
121
|
+
for e in (
|
|
122
|
+
errno.EACCES,
|
|
123
|
+
errno.EAGAIN,
|
|
124
|
+
getattr(errno, "EWOULDBLOCK", None),
|
|
125
|
+
getattr(errno, "EDEADLOCK", None),
|
|
126
|
+
getattr(errno, "EDEADLK", None),
|
|
127
|
+
)
|
|
128
|
+
if e is not None
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class LockAcquisitionError(RuntimeError):
|
|
133
|
+
"""Raised by `locked_state(path, strict=True)` when the lock could not be
|
|
134
|
+
acquired (directory/lock-file creation failure, fdopen failure, or
|
|
135
|
+
acquire-budget exhaustion) — never for a caller-raised exception from
|
|
136
|
+
inside the critical section (#1892 still applies unchanged). Subclasses
|
|
137
|
+
`RuntimeError` so pre-existing `pytest.raises(RuntimeError, ...)`
|
|
138
|
+
assertions against strict-mode callers keep passing; new callers should
|
|
139
|
+
narrow to this type specifically so a broad `except (OSError,
|
|
140
|
+
RuntimeError)` doesn't also swallow an unrelated `RuntimeError` raised by
|
|
141
|
+
the critical section body itself (#1920 correctness review)."""
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _reraise_unless_fail_open(exc: OSError, fail_open: bool) -> None:
|
|
145
|
+
"""Re-raise `exc` unless `fail_open` — the shared decision behind each
|
|
146
|
+
of `atomic_write`'s OSError sites (#1920 structure review: this
|
|
147
|
+
collapses the same duplication `_refuse_or_warn` below was extracted to
|
|
148
|
+
eliminate for `locked_state`'s four fail-open sites). Takes the
|
|
149
|
+
exception explicitly, rather than relying on a bare `raise` re-raising
|
|
150
|
+
the caller's currently-handled exception, so this helper is an ordinary
|
|
151
|
+
function call rather than something that only works from inside a
|
|
152
|
+
lexical `except` block.
|
|
153
|
+
"""
|
|
154
|
+
if not fail_open:
|
|
155
|
+
raise exc
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def atomic_write(path: Path, text: str, *, fail_open: bool = True) -> None:
|
|
159
|
+
"""Write `text` to `path` atomically via tempfile-in-same-dir + rename.
|
|
160
|
+
|
|
161
|
+
A concurrent reader sees either the old file or the fully-written new one,
|
|
162
|
+
never a partial write.
|
|
163
|
+
|
|
164
|
+
`fail_open` (default `True`, matching every pre-existing caller's call
|
|
165
|
+
shape): any OSError — creating the parent directory, obtaining the temp
|
|
166
|
+
file, opening/writing it, or the final `os.replace` — short-circuits to a
|
|
167
|
+
silent no-op; persistence is best-effort for these advisory hooks. Pass
|
|
168
|
+
`fail_open=False` for a caller that must never silently proceed without
|
|
169
|
+
having actually written the file (mirrors `append_line_locked`'s own
|
|
170
|
+
`fail_open` parameter, #1920): the same OSError re-raises instead, after
|
|
171
|
+
the same best-effort tmp-file cleanup runs either way.
|
|
172
|
+
"""
|
|
173
|
+
try:
|
|
174
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
175
|
+
except OSError as exc:
|
|
176
|
+
_reraise_unless_fail_open(exc, fail_open)
|
|
177
|
+
return
|
|
178
|
+
try:
|
|
179
|
+
fd, tmp_name = tempfile.mkstemp(
|
|
180
|
+
prefix=f".{path.name}-", suffix=".tmp", dir=str(path.parent)
|
|
181
|
+
)
|
|
182
|
+
except OSError as exc:
|
|
183
|
+
_reraise_unless_fail_open(exc, fail_open)
|
|
184
|
+
return
|
|
185
|
+
try:
|
|
186
|
+
try:
|
|
187
|
+
handle = os.fdopen(fd, "w", encoding="utf-8")
|
|
188
|
+
except OSError:
|
|
189
|
+
# fdopen() failed without ever wrapping fd in a file object, so
|
|
190
|
+
# nothing else will close it — leaking the fd otherwise (#1920
|
|
191
|
+
# correctness review; mirrors locked_state's own fdopen-failure
|
|
192
|
+
# fd-leak fix).
|
|
193
|
+
with contextlib.suppress(OSError):
|
|
194
|
+
os.close(fd)
|
|
195
|
+
raise
|
|
196
|
+
with handle:
|
|
197
|
+
handle.write(text)
|
|
198
|
+
os.replace(tmp_name, path)
|
|
199
|
+
except OSError as exc:
|
|
200
|
+
with contextlib.suppress(OSError):
|
|
201
|
+
os.unlink(tmp_name)
|
|
202
|
+
_reraise_unless_fail_open(exc, fail_open)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _lock_acquire_budget_seconds() -> float:
|
|
206
|
+
"""Test-only override for the give-up budget, mirroring
|
|
207
|
+
`race_window_delay`'s env-var injection pattern below. Unset in
|
|
208
|
+
production; an unset, negative, or non-integer value (the var is
|
|
209
|
+
documented in whole milliseconds) falls back to the default (fail-open).
|
|
210
|
+
Clamped to `[_LOCK_POLL_INTERVAL_SECONDS,
|
|
211
|
+
_MAX_LOCK_ACQUIRE_BUDGET_SECONDS]`: the floor guarantees a nonzero
|
|
212
|
+
budget (not a guaranteed retry count — a first attempt that itself takes
|
|
213
|
+
longer than the floor can still see zero retries), and the ceiling
|
|
214
|
+
guarantees no override can reinstate an effectively-unbounded wait.
|
|
215
|
+
"""
|
|
216
|
+
raw = os.environ.get("DEV_TEAM_LOCK_ACQUIRE_BUDGET_TEST_MS")
|
|
217
|
+
if not raw:
|
|
218
|
+
return _DEFAULT_LOCK_ACQUIRE_BUDGET_SECONDS
|
|
219
|
+
try:
|
|
220
|
+
value_seconds = int(raw) / 1000
|
|
221
|
+
except ValueError:
|
|
222
|
+
return _DEFAULT_LOCK_ACQUIRE_BUDGET_SECONDS
|
|
223
|
+
if value_seconds < 0:
|
|
224
|
+
return _DEFAULT_LOCK_ACQUIRE_BUDGET_SECONDS
|
|
225
|
+
return min(
|
|
226
|
+
_MAX_LOCK_ACQUIRE_BUDGET_SECONDS,
|
|
227
|
+
max(_LOCK_POLL_INTERVAL_SECONDS, value_seconds),
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _try_acquire(handle) -> bool:
|
|
232
|
+
"""One non-blocking acquisition attempt on `handle`.
|
|
233
|
+
|
|
234
|
+
Returns True on success, False if nothing raised but no lock backend is
|
|
235
|
+
available (fcntl/msvcrt both missing) — this is the one case where
|
|
236
|
+
`_acquire_bounded` below returns without ever polling or sleeping, since
|
|
237
|
+
retrying an operation that can't succeed either way buys nothing. Raises
|
|
238
|
+
OSError when the lock is currently held elsewhere — the caller decides
|
|
239
|
+
whether to retry or give up.
|
|
240
|
+
"""
|
|
241
|
+
if fcntl is not None:
|
|
242
|
+
fcntl.flock(handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
243
|
+
return True
|
|
244
|
+
if msvcrt is not None:
|
|
245
|
+
handle.seek(0)
|
|
246
|
+
msvcrt.locking(handle.fileno(), msvcrt.LK_NBLCK, 1)
|
|
247
|
+
return True
|
|
248
|
+
return False
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _release(handle) -> None:
|
|
252
|
+
"""Release a lock previously acquired via `_try_acquire`, mirroring its
|
|
253
|
+
guard-clause style. Can raise OSError; the caller (`locked_state`) wraps
|
|
254
|
+
this call in its own try/except, matching the fail-open contract."""
|
|
255
|
+
if fcntl is not None:
|
|
256
|
+
fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
|
|
257
|
+
return
|
|
258
|
+
if msvcrt is not None:
|
|
259
|
+
handle.seek(0)
|
|
260
|
+
msvcrt.locking(handle.fileno(), msvcrt.LK_UNLCK, 1)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _acquire_bounded(handle) -> bool:
|
|
264
|
+
"""Poll `_try_acquire` until it succeeds, the acquire budget elapses, or
|
|
265
|
+
(immediately, no polling) `_try_acquire` signals a condition retrying
|
|
266
|
+
can't fix — no lock backend at all, or an OSError that isn't ordinary
|
|
267
|
+
contention (see `_LOCK_CONTENTION_ERRNOS`).
|
|
268
|
+
|
|
269
|
+
Returns True once the lock is held, False if the budget ran out with the
|
|
270
|
+
lock still held elsewhere, no backend exists, or a non-contention
|
|
271
|
+
OSError occurred (the caller runs unlocked in every False case).
|
|
272
|
+
"""
|
|
273
|
+
deadline = time.monotonic() + _lock_acquire_budget_seconds()
|
|
274
|
+
while True:
|
|
275
|
+
try:
|
|
276
|
+
return _try_acquire(handle)
|
|
277
|
+
except OSError as exc:
|
|
278
|
+
if exc.errno not in _LOCK_CONTENTION_ERRNOS:
|
|
279
|
+
return False
|
|
280
|
+
if time.monotonic() >= deadline:
|
|
281
|
+
return False
|
|
282
|
+
time.sleep(_LOCK_POLL_INTERVAL_SECONDS)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _refuse_or_warn(
|
|
286
|
+
reason: str, path: Path, strict: bool, exc: BaseException | None = None
|
|
287
|
+
) -> None:
|
|
288
|
+
"""Shared decision behind each of `locked_state`'s four fail-open
|
|
289
|
+
points (#1892's scoping invariant is unaffected — every call site below
|
|
290
|
+
still runs its own `yield`/`return` immediately after this returns; this
|
|
291
|
+
helper never yields itself). `strict=True` raises
|
|
292
|
+
`LockAcquisitionError`, chaining `exc` as its cause when one is given
|
|
293
|
+
(the budget-exhaustion site has no underlying exception to chain, so it
|
|
294
|
+
omits `exc`). `strict=False` prints the fail-open diagnostic to stderr
|
|
295
|
+
and returns, leaving the caller to fall through to running the critical
|
|
296
|
+
section unlocked.
|
|
297
|
+
"""
|
|
298
|
+
if strict:
|
|
299
|
+
message = (
|
|
300
|
+
f"{reason}; refusing to run the critical section unlocked for {path}"
|
|
301
|
+
)
|
|
302
|
+
if exc is not None:
|
|
303
|
+
raise LockAcquisitionError(message) from exc
|
|
304
|
+
raise LockAcquisitionError(message)
|
|
305
|
+
print(f"{reason}; proceeding WITHOUT lock for {path}", file=sys.stderr)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@contextlib.contextmanager
|
|
309
|
+
def locked_state(path: Path, *, strict: bool = False) -> Iterator[None]:
|
|
310
|
+
"""Hold an exclusive advisory lock for a full read-modify-write cycle.
|
|
311
|
+
|
|
312
|
+
Prevents the lost-update race where two concurrent invocations both read
|
|
313
|
+
the same stale value, both increment it, and one write clobbers the
|
|
314
|
+
other. `fcntl.flock` on POSIX, `msvcrt.locking` on Windows (this repo's
|
|
315
|
+
hooks target both — ADR 0014/0015). The lock file lives beside `path` as
|
|
316
|
+
`<path>.lock`.
|
|
317
|
+
|
|
318
|
+
Acquisition is bounded, not a blocking wait (#1888): a stalled holder
|
|
319
|
+
trips a fail-open give-up after `_lock_acquire_budget_seconds()` of
|
|
320
|
+
polling rather than blocking a caller indefinitely.
|
|
321
|
+
|
|
322
|
+
Fail-open (default, `strict=False`): a missing `fcntl` *and* `msvcrt`, an
|
|
323
|
+
OSError creating the lock file's parent directory or opening the lock
|
|
324
|
+
file, or a lock that can't be acquired within budget, all fall through to
|
|
325
|
+
running the critical section UNLOCKED rather than raising or blocking
|
|
326
|
+
forever. An unlocked lost-update is no worse than the pre-#1501 behavior;
|
|
327
|
+
a crash — or an undelivered guard-hook verdict — would be worse, and
|
|
328
|
+
these hooks are advisory.
|
|
329
|
+
|
|
330
|
+
`strict=True`: every one of the same four fail-open points above raises
|
|
331
|
+
`LockAcquisitionError` (a `RuntimeError` subclass, carrying the same
|
|
332
|
+
diagnostic detail the fail-open path would otherwise print to stderr,
|
|
333
|
+
reworded to say the critical section is being refused rather than run
|
|
334
|
+
unlocked) instead of printing and running the critical section unlocked
|
|
335
|
+
— the critical section body never runs when the lock could not be
|
|
336
|
+
acquired. For a caller where a lost update is a correctness bug rather
|
|
337
|
+
than an advisory best-effort nudge. Every existing caller passes the
|
|
338
|
+
default `False` and is completely unaffected; this is an additive,
|
|
339
|
+
backward-compatible parameter.
|
|
340
|
+
|
|
341
|
+
A caller-raised OSError from inside the critical section propagates
|
|
342
|
+
normally (through the `finally`/release, out of this generator's
|
|
343
|
+
`yield`) rather than being swallowed (#1892): the `except OSError` that
|
|
344
|
+
guards the `os.fdopen()` call is scoped to that call alone, not to the
|
|
345
|
+
`with` block around the `yield` — a prior version wrapped both, which
|
|
346
|
+
meant a caller's OSError landed in that same `except`, fell through to a
|
|
347
|
+
second bare `yield`, and made `contextlib` raise
|
|
348
|
+
`RuntimeError("generator didn't stop after throw()")` instead of the
|
|
349
|
+
OSError the caller actually raised.
|
|
350
|
+
"""
|
|
351
|
+
lock_path = path.parent / (path.name + ".lock")
|
|
352
|
+
try:
|
|
353
|
+
lock_path.parent.mkdir(parents=True, exist_ok=True)
|
|
354
|
+
except OSError as exc:
|
|
355
|
+
reason = (
|
|
356
|
+
f"atomic_state: lock acquisition failed (could not create lock "
|
|
357
|
+
f"directory {lock_path.parent}: {exc})"
|
|
358
|
+
)
|
|
359
|
+
_refuse_or_warn(reason, path, strict, exc)
|
|
360
|
+
yield
|
|
361
|
+
return
|
|
362
|
+
|
|
363
|
+
# os.O_NOFOLLOW is undefined on Windows; getattr(..., 0) makes the flag
|
|
364
|
+
# a no-op there rather than an AttributeError (which isn't an OSError
|
|
365
|
+
# and would escape the except below).
|
|
366
|
+
try:
|
|
367
|
+
lock_fd = os.open(
|
|
368
|
+
str(lock_path), os.O_RDWR | os.O_CREAT | getattr(os, "O_NOFOLLOW", 0), 0o600
|
|
369
|
+
)
|
|
370
|
+
# The mode argument to os.open() only applies when O_CREAT actually
|
|
371
|
+
# creates the file — POSIX ignores it for a path that already
|
|
372
|
+
# exists. Every checkout that already has a pre-#1889 lock file on
|
|
373
|
+
# disk (created at the old, wider default permissions) would
|
|
374
|
+
# otherwise keep it forever. fchmod on the already-open fd (no
|
|
375
|
+
# TOCTOU) enforces 0600 either way (security review, #1889).
|
|
376
|
+
with contextlib.suppress(OSError, AttributeError):
|
|
377
|
+
os.fchmod(lock_fd, 0o600)
|
|
378
|
+
except OSError as exc:
|
|
379
|
+
reason = (
|
|
380
|
+
f"atomic_state: lock acquisition failed (could not open lock "
|
|
381
|
+
f"file {lock_path}: {exc})"
|
|
382
|
+
)
|
|
383
|
+
_refuse_or_warn(reason, path, strict, exc)
|
|
384
|
+
yield
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
# This except is scoped to the fdopen() call ONLY (#1892) — it must NOT
|
|
388
|
+
# also wrap the `with` block below, or a caller-raised OSError from
|
|
389
|
+
# inside the critical section (at the `yield`) would be caught here too
|
|
390
|
+
# and mishandled (see the CONTRACT note above).
|
|
391
|
+
try:
|
|
392
|
+
handle_cm = os.fdopen(lock_fd, "r+")
|
|
393
|
+
except OSError as exc:
|
|
394
|
+
# fdopen() failed without ever wrapping lock_fd in a file object, so
|
|
395
|
+
# nothing else will close it — leaking the fd otherwise (#1920
|
|
396
|
+
# correctness review).
|
|
397
|
+
with contextlib.suppress(OSError):
|
|
398
|
+
os.close(lock_fd)
|
|
399
|
+
reason = (
|
|
400
|
+
f"atomic_state: lock acquisition failed (fdopen on "
|
|
401
|
+
f"{lock_path} failed: {exc})"
|
|
402
|
+
)
|
|
403
|
+
_refuse_or_warn(reason, path, strict, exc)
|
|
404
|
+
yield
|
|
405
|
+
return
|
|
406
|
+
|
|
407
|
+
with handle_cm as handle:
|
|
408
|
+
locked = False
|
|
409
|
+
try:
|
|
410
|
+
try:
|
|
411
|
+
locked = _acquire_bounded(handle)
|
|
412
|
+
except OSError:
|
|
413
|
+
locked = False
|
|
414
|
+
if not locked:
|
|
415
|
+
reason = (
|
|
416
|
+
f"atomic_state: lock acquisition failed (budget exhausted "
|
|
417
|
+
f"or non-contention error acquiring {lock_path})"
|
|
418
|
+
)
|
|
419
|
+
_refuse_or_warn(reason, path, strict)
|
|
420
|
+
yield
|
|
421
|
+
finally:
|
|
422
|
+
if locked:
|
|
423
|
+
try:
|
|
424
|
+
_release(handle)
|
|
425
|
+
except OSError:
|
|
426
|
+
pass
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _write_maybe_delayed(handle, line: str, delay_env_var: str | None) -> None:
|
|
430
|
+
"""Write `line` in one call, unless `delay_env_var` names a set env var.
|
|
431
|
+
|
|
432
|
+
Test-only split path: see `append_line_locked`'s docstring for why a
|
|
433
|
+
single small `write()` needs splitting, not just delaying, to simulate a
|
|
434
|
+
genuinely non-atomic write. A value of ``"0"`` (or anything else that
|
|
435
|
+
parses to <= 0, or fails to parse at all) takes the plain single-write
|
|
436
|
+
path — matching this repo's `DEV_TEAM_*` numeric-knob convention where a
|
|
437
|
+
non-positive value means "disabled" — rather than bare `os.environ.get()`
|
|
438
|
+
truthiness, which would treat the *string* ``"0"`` as "enabled with a
|
|
439
|
+
zero-length delay" and needlessly split the write.
|
|
440
|
+
"""
|
|
441
|
+
delay_ms = 0
|
|
442
|
+
if delay_env_var:
|
|
443
|
+
raw = os.environ.get(delay_env_var)
|
|
444
|
+
if raw:
|
|
445
|
+
try:
|
|
446
|
+
delay_ms = int(raw)
|
|
447
|
+
except ValueError:
|
|
448
|
+
delay_ms = 0
|
|
449
|
+
if delay_ms <= 0:
|
|
450
|
+
handle.write(line)
|
|
451
|
+
return
|
|
452
|
+
mid = len(line) // 2
|
|
453
|
+
handle.write(line[:mid])
|
|
454
|
+
handle.flush()
|
|
455
|
+
race_window_delay(delay_env_var)
|
|
456
|
+
handle.write(line[mid:])
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def append_line_locked(
|
|
460
|
+
path: Path,
|
|
461
|
+
line: str,
|
|
462
|
+
*,
|
|
463
|
+
delay_env_var: str | None = None,
|
|
464
|
+
fail_open: bool = True,
|
|
465
|
+
) -> None:
|
|
466
|
+
"""Append `line` (already newline-terminated) to `path` under `locked_state`.
|
|
467
|
+
|
|
468
|
+
Serializes concurrent appends to a shared JSONL stream: while the lock is
|
|
469
|
+
held (see `locked_state`'s own fail-open fallback above for when it might
|
|
470
|
+
not be), two processes writing at once cannot interleave their bytes
|
|
471
|
+
into one corrupted line (see module docstring, #1889) — the append-only
|
|
472
|
+
counterpart to `locked_state`'s original read-modify-write use case. The
|
|
473
|
+
lock is what provides this, regardless of how many underlying
|
|
474
|
+
`os.write()` syscalls a single logical `.write()` call happens to
|
|
475
|
+
decompose into (e.g. a line long enough to cross the `BufferedWriter`'s
|
|
476
|
+
internal buffer boundary) — a caller does not need to reason about
|
|
477
|
+
syscall-level atomicity separately.
|
|
478
|
+
|
|
479
|
+
`fail_open` (default `True`): OSError from `open()`/`write()` is caught
|
|
480
|
+
*inside* the lock and discarded, matching this repo's fail-open posture
|
|
481
|
+
for advisory telemetry. Pass `fail_open=False` for a caller that needs
|
|
482
|
+
the write failure to propagate instead (e.g. a round-log CLI that must
|
|
483
|
+
crash rather than silently record nothing) — the OSError is still
|
|
484
|
+
caught *inside* the lock (avoiding a caller-raised exception crossing
|
|
485
|
+
the generator's `yield`), just re-raised after the lock releases rather
|
|
486
|
+
than discarded.
|
|
487
|
+
|
|
488
|
+
`delay_env_var`, when set and naming a set env var, splits the write into
|
|
489
|
+
two separate `write()` calls with `race_window_delay` between them,
|
|
490
|
+
instead of the usual single call. This is a test-only hook, needed
|
|
491
|
+
because a single small `write()` to an `O_APPEND`-opened file is
|
|
492
|
+
typically atomic with respect to other processes on mainstream local
|
|
493
|
+
filesystems (not independently verified here — no recorded runtime
|
|
494
|
+
probe), so simply sleeping *before* an unsplit write may not force real
|
|
495
|
+
interleaving in a test. The split instead simulates a genuinely
|
|
496
|
+
non-atomic write directly (long enough to cross a write-buffer
|
|
497
|
+
boundary, or a filesystem, e.g. some NFS configurations, that doesn't
|
|
498
|
+
honor `O_APPEND`'s atomicity guarantee) — the actual failure mode the
|
|
499
|
+
lock protects against regardless of whether any given production write
|
|
500
|
+
happens to be single-syscall-atomic on its own. Unset (the default) in
|
|
501
|
+
production: exactly one `.write()` call at this Python API level,
|
|
502
|
+
unchanged from the code this helper replaced.
|
|
503
|
+
"""
|
|
504
|
+
write_error: OSError | None = None
|
|
505
|
+
with locked_state(path):
|
|
506
|
+
try:
|
|
507
|
+
# O_NOFOLLOW (security review, #1889): the data file sits beside
|
|
508
|
+
# the now-hardened `<path>.lock` in the same predictable,
|
|
509
|
+
# repo-relative directory — a pre-planted symlink here would
|
|
510
|
+
# otherwise redirect every append the same way the pre-fix lock
|
|
511
|
+
# file did. Mode is the historical default (not 0600): unlike
|
|
512
|
+
# the lock file, this file's readers are other tooling that
|
|
513
|
+
# expects normal permissions.
|
|
514
|
+
fd = os.open(
|
|
515
|
+
str(path),
|
|
516
|
+
os.O_WRONLY | os.O_APPEND | os.O_CREAT | getattr(os, "O_NOFOLLOW", 0),
|
|
517
|
+
0o644,
|
|
518
|
+
)
|
|
519
|
+
with os.fdopen(fd, "a", encoding="utf-8") as handle:
|
|
520
|
+
_write_maybe_delayed(handle, line, delay_env_var)
|
|
521
|
+
except OSError as exc:
|
|
522
|
+
if not fail_open:
|
|
523
|
+
write_error = exc
|
|
524
|
+
if write_error is not None:
|
|
525
|
+
raise write_error
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
_MAX_RACE_WINDOW_DELAY_MS = 500
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def race_window_delay(env_var: str) -> None:
|
|
532
|
+
"""Test-only injection point: sleep inside a locked critical section when
|
|
533
|
+
`env_var` names a millisecond delay, widening the race window so a
|
|
534
|
+
concurrency test can deterministically force two invocations to overlap
|
|
535
|
+
rather than relying on timing luck. Unset in production; a bad/missing
|
|
536
|
+
value is a no-op (fail-open). NOT the same implementation as
|
|
537
|
+
`code_intelligence_turn_mark._test_delay` despite the similar shape —
|
|
538
|
+
that sibling still has an uncapped sleep inside its own blocking lock.
|
|
539
|
+
|
|
540
|
+
Capped at `_MAX_RACE_WINDOW_DELAY_MS` (security review, #1889): several
|
|
541
|
+
callers now invoke this from inside a held exclusive lock on a shared,
|
|
542
|
+
frequently-hit stream (`append_line_locked`'s `_write_maybe_delayed`), so
|
|
543
|
+
an unbounded value here would let anything that can influence the process
|
|
544
|
+
environment hold that lock for an attacker-chosen duration. Every real
|
|
545
|
+
test uses 60ms; the cap costs them nothing. This bounds one call, not a
|
|
546
|
+
whole critical section — a caller invoking it more than once, or from
|
|
547
|
+
inside a loop, multiplies the total; today's callers all call it exactly
|
|
548
|
+
once per critical section, but that is a property of the call sites,
|
|
549
|
+
not an invariant this function enforces.
|
|
550
|
+
"""
|
|
551
|
+
raw = os.environ.get(env_var)
|
|
552
|
+
if not raw:
|
|
553
|
+
return
|
|
554
|
+
try:
|
|
555
|
+
time.sleep(min(int(raw), _MAX_RACE_WINDOW_DELAY_MS) / 1000)
|
|
556
|
+
except (ValueError, OSError):
|
|
557
|
+
pass
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Detect whether harness autocompact is configured (#2177, slice 2).
|
|
2
|
+
|
|
3
|
+
Single decider used by both `scripts/set_autocompact_env.py` (the `/setup`
|
|
4
|
+
writer) and `hooks/autocompact_setup_nudge.py`. Import direction is
|
|
5
|
+
scripts -> hooks/lib; this module never imports from `scripts/` or
|
|
6
|
+
`.claude/lib`.
|
|
7
|
+
|
|
8
|
+
The harness env var `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE` is an integer 1-100
|
|
9
|
+
(percent of the auto-compact window). Values above the harness default are
|
|
10
|
+
ignored, so the variable can only lower the threshold. `HARNESS_DEFAULT_PCT`
|
|
11
|
+
(~83) is REPORTED, not confirmed against primary docs.
|
|
12
|
+
|
|
13
|
+
Sources consulted, highest precedence first: the process environment,
|
|
14
|
+
`.claude/settings.local.json`, `.claude/settings.json`, then the user
|
|
15
|
+
settings (`$CLAUDE_CONFIG_DIR/settings.json`, else `~/.claude/settings.json`).
|
|
16
|
+
The first source that defines the key decides. An unreadable or malformed
|
|
17
|
+
settings file is skipped, never raised.
|
|
18
|
+
|
|
19
|
+
Stdlib only.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
import re
|
|
27
|
+
from collections.abc import Mapping
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
from enum import Enum
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
KEY = "CLAUDE_AUTOCOMPACT_PCT_OVERRIDE"
|
|
33
|
+
DEFAULT_PCT = 40
|
|
34
|
+
HARNESS_DEFAULT_PCT = 83 # reported, not verified against primary docs
|
|
35
|
+
|
|
36
|
+
# Plain ASCII integer 1-100: no sign, no whitespace, no decimal point, no leading zero.
|
|
37
|
+
_PCT_RE = re.compile(r"(?:100|[1-9][0-9]?)")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Status(Enum):
|
|
41
|
+
CONFIGURED = "configured"
|
|
42
|
+
INVALID = "invalid"
|
|
43
|
+
ABSENT = "absent"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class Detection:
|
|
48
|
+
status: Status
|
|
49
|
+
raw: object = None
|
|
50
|
+
source: str | None = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def validate_pct(raw: object) -> int | None:
|
|
54
|
+
"""Return the integer percent for a valid string 1-100, else None."""
|
|
55
|
+
if not isinstance(raw, str) or not _PCT_RE.fullmatch(raw):
|
|
56
|
+
return None
|
|
57
|
+
return int(raw)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def user_settings_path(env: Mapping[str, str]) -> Path:
|
|
61
|
+
cfg = env.get("CLAUDE_CONFIG_DIR")
|
|
62
|
+
if cfg:
|
|
63
|
+
return Path(cfg) / "settings.json"
|
|
64
|
+
home = env.get("HOME") or os.path.expanduser("~")
|
|
65
|
+
return Path(home) / ".claude" / "settings.json"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _env_block(path: Path) -> Mapping[str, object] | None:
|
|
69
|
+
try:
|
|
70
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
71
|
+
except (OSError, ValueError, RecursionError, RuntimeError):
|
|
72
|
+
return None
|
|
73
|
+
block = data.get("env") if isinstance(data, dict) else None
|
|
74
|
+
return block if isinstance(block, dict) else None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def detect(project_dir: str | Path, env: Mapping[str, str]) -> Detection:
|
|
78
|
+
"""Find the first source defining the key and classify its value."""
|
|
79
|
+
if KEY in env:
|
|
80
|
+
raw = env[KEY]
|
|
81
|
+
return Detection(
|
|
82
|
+
Status.CONFIGURED if validate_pct(raw) is not None else Status.INVALID,
|
|
83
|
+
raw,
|
|
84
|
+
"process env",
|
|
85
|
+
)
|
|
86
|
+
root = Path(project_dir)
|
|
87
|
+
candidates = (
|
|
88
|
+
("settings.local.json", root / ".claude" / "settings.local.json"),
|
|
89
|
+
("settings.json", root / ".claude" / "settings.json"),
|
|
90
|
+
("user settings.json", user_settings_path(env)),
|
|
91
|
+
)
|
|
92
|
+
for label, path in candidates:
|
|
93
|
+
block = _env_block(path)
|
|
94
|
+
if block is not None and KEY in block:
|
|
95
|
+
raw = block[KEY]
|
|
96
|
+
ok = validate_pct(raw) is not None
|
|
97
|
+
return Detection(Status.CONFIGURED if ok else Status.INVALID, raw, label)
|
|
98
|
+
return Detection(Status.ABSENT)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def autocompact_configured(project_dir: str | Path, env: Mapping[str, str]) -> Status:
|
|
102
|
+
"""CONFIGURED (valid), INVALID (present, bad value) or ABSENT."""
|
|
103
|
+
return detect(project_dir, env).status
|