@1aboveio/skills 0.10.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 +3 -0
- package/README.md +90 -0
- package/bin/1aboveio-skills.mjs +18 -0
- package/package.json +28 -0
- package/runtime/skills/distribution/generated/recipes.json +1189 -0
- package/runtime/skills/distribution/scripts/bundles.mjs +280 -0
- package/runtime/skills/engineering/engineering-runtime/scripts/main-module.mjs +80 -0
- package/skills/backend/airflow-dag-develop/LICENSE +3 -0
- package/skills/backend/airflow-dag-develop/SKILL.md +111 -0
- package/skills/backend/app-debug/LICENSE +3 -0
- package/skills/backend/app-debug/SKILL.md +109 -0
- package/skills/backend/app-debug/references/common-errors.md +128 -0
- package/skills/backend/python-backend/LICENSE +3 -0
- package/skills/backend/python-backend/SKILL.md +326 -0
- package/skills/cicd-pipeline/cloud-build/LICENSE +3 -0
- package/skills/cicd-pipeline/cloud-build/SKILL.md +707 -0
- package/skills/cicd-pipeline/cloud-debug/LICENSE +3 -0
- package/skills/cicd-pipeline/cloud-debug/SKILL.md +316 -0
- package/skills/cicd-pipeline/cloud-debug/references/build-failures.md +238 -0
- package/skills/cicd-pipeline/cloud-debug/references/deploy-failures.md +376 -0
- package/skills/cicd-pipeline/cloud-debug/references/pipeline-failures.md +378 -0
- package/skills/cicd-pipeline/cloud-deploy/LICENSE +3 -0
- package/skills/cicd-pipeline/cloud-deploy/SKILL.md +229 -0
- package/skills/cicd-pipeline/cloud-deploy/references/config-templates.md +257 -0
- package/skills/cicd-pipeline/docker/LICENSE +3 -0
- package/skills/cicd-pipeline/docker/SKILL.md +126 -0
- package/skills/cicd-pipeline/google-cloud/LICENSE +3 -0
- package/skills/cicd-pipeline/google-cloud/SKILL.md +118 -0
- package/skills/cicd-pipeline/google-cloud/references/gcs.md +469 -0
- package/skills/cicd-pipeline/google-cloud/references/iam.md +451 -0
- package/skills/cicd-pipeline/google-cloud/references/project.md +349 -0
- package/skills/cicd-pipeline/google-cloud/references/secrets.md +336 -0
- package/skills/cicd-pipeline/google-cloud/references/vpc.md +312 -0
- package/skills/cicd-pipeline/google-cloud/scripts/create-sa.sh +36 -0
- package/skills/cicd-pipeline/google-cloud/scripts/gcp-config.sh +31 -0
- package/skills/cicd-pipeline/google-cloud/scripts/grant-iap.sh +41 -0
- package/skills/cicd-pipeline/google-cloud/scripts/setup-secrets.sh +48 -0
- package/skills/cicd-pipeline/mergify/LICENSE +3 -0
- package/skills/cicd-pipeline/mergify/SKILL.md +138 -0
- package/skills/cicd-pipeline/mergify/assets/templates/mergify.yml +237 -0
- package/skills/cicd-pipeline/mergify/assets/templates/ruleset.json +46 -0
- package/skills/cicd-pipeline/mergify/references/branch-protection.md +277 -0
- package/skills/cicd-pipeline/mergify/references/configuration.md +183 -0
- package/skills/cicd-pipeline/mergify/references/diagnosis.md +73 -0
- package/skills/cicd-pipeline/mergify/references/traps.md +78 -0
- package/skills/cicd-pipeline/mergify/references/watch-contract.md +218 -0
- package/skills/cicd-pipeline/mergify/scripts/audit-core.mjs +131 -0
- package/skills/cicd-pipeline/mergify/scripts/audit.mjs +4 -0
- package/skills/cicd-pipeline/mergify/scripts/watch-pr-delivery-core.mjs +663 -0
- package/skills/cicd-pipeline/mergify/scripts/watch-pr-delivery.mjs +4 -0
- package/skills/cicd-pipeline/podman/LICENSE +3 -0
- package/skills/cicd-pipeline/podman/SKILL.md +70 -0
- package/skills/cicd-pipeline/podman/agents/openai.yaml +4 -0
- package/skills/cicd-pipeline/podman/assets/templates/podman-compose-socket-directory.yml +6 -0
- package/skills/cicd-pipeline/podman/assets/templates/podman-service-override.conf +3 -0
- package/skills/cicd-pipeline/podman/references/compose-compatibility.md +70 -0
- package/skills/cicd-pipeline/podman/references/networking-and-ports.md +74 -0
- package/skills/cicd-pipeline/podman/references/rootless-services-and-sockets.md +156 -0
- package/skills/cicd-pipeline/podman/references/troubleshooting.md +98 -0
- package/skills/engineering/e2e-test/LICENSE +3 -0
- package/skills/engineering/e2e-test/SKILL.md +156 -0
- package/skills/engineering/e2e-test/assets/ci-gates.cloudbuild.yaml +272 -0
- package/skills/engineering/e2e-test/assets/ci-gates.github.yml +451 -0
- package/skills/engineering/e2e-test/assets/e2e-workflow.yml +282 -0
- package/skills/engineering/e2e-test/references/authoring/auth-flows.md +159 -0
- package/skills/engineering/e2e-test/references/authoring/playwright-config.md +71 -0
- package/skills/engineering/e2e-test/references/authoring/playwright-patterns.md +219 -0
- package/skills/engineering/e2e-test/references/authoring/test-skipping.md +44 -0
- package/skills/engineering/e2e-test/references/ci-integration.md +121 -0
- package/skills/engineering/e2e-test/references/ci-playwright-container.md +280 -0
- package/skills/engineering/e2e-test/references/debugging.md +36 -0
- package/skills/engineering/e2e-test/references/presentation-sweep.md +131 -0
- package/skills/engineering/e2e-test/references/reviewing.md +39 -0
- package/skills/engineering/e2e-test/references/route-discovery.md +50 -0
- package/skills/engineering/e2e-test/references/route-manifest.md +44 -0
- package/skills/engineering/e2e-test/scripts/detect-routes-fastapi.py +290 -0
- package/skills/engineering/e2e-test/scripts/detect-routes-nextjs.mjs +200 -0
- package/skills/engineering/e2e-test/scripts/post-visual-evidence.mjs +158 -0
- package/skills/engineering/e2e-test/scripts/presentation-checks.mjs +171 -0
- package/skills/engineering/e2e-test/scripts/presentation-perceivability.mjs +179 -0
- package/skills/engineering/e2e-test/scripts/presentation-reachability.mjs +154 -0
- package/skills/engineering/e2e-test/scripts/presentation-render-health.mjs +141 -0
- package/skills/engineering/e2e-test/scripts/presentation-sweep.mjs +148 -0
- package/skills/engineering/e2e-test/scripts/presentation-temporal.mjs +127 -0
- package/skills/engineering/e2e-test/scripts/presentation-visual.mjs +84 -0
- package/skills/engineering/e2e-test/scripts/project-route-manifest.mjs +75 -0
- package/skills/engineering/e2e-test/scripts/validate-manifest.mjs +106 -0
- package/skills/engineering/engineering-runtime/LICENSE +3 -0
- package/skills/engineering/engineering-runtime/SKILL.md +10 -0
- package/skills/engineering/engineering-runtime/agents/openai.yaml +6 -0
- package/skills/engineering/engineering-runtime/coherence/workflow.json +553 -0
- package/skills/engineering/engineering-runtime/scripts/exact-head-artifact.mjs +131 -0
- package/skills/engineering/engineering-runtime/scripts/head-check-set.mjs +398 -0
- package/skills/engineering/engineering-runtime/scripts/main-module.mjs +80 -0
- package/skills/engineering/engineering-runtime/scripts/mergify-yaml.mjs +11 -0
- package/skills/engineering/engineering-runtime/scripts/package-lock.json +43 -0
- package/skills/engineering/engineering-runtime/scripts/package.json +10 -0
- package/skills/engineering/engineering-runtime/scripts/required-check-plan.mjs +223 -0
- package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +576 -0
- package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +166 -0
- package/skills/engineering/ensure-coverage/LICENSE +3 -0
- package/skills/engineering/ensure-coverage/SKILL.md +136 -0
- package/skills/engineering/ensure-coverage/evals/evals.json +125 -0
- package/skills/engineering/ensure-coverage/references/breadth/coverage-ledger.md +91 -0
- package/skills/engineering/ensure-coverage/references/breadth/inventory-contract.md +83 -0
- package/skills/engineering/ensure-coverage/references/breadth/surface-baseline.md +44 -0
- package/skills/engineering/ensure-coverage/references/breadth/surface-discovery.md +16 -0
- package/skills/engineering/ensure-coverage/references/depth/characterization.md +68 -0
- package/skills/engineering/ensure-coverage/references/depth/coverage.config.example.json +25 -0
- package/skills/engineering/ensure-coverage/references/depth/grading.md +35 -0
- package/skills/engineering/ensure-coverage/references/depth/mock-policy.md +87 -0
- package/skills/engineering/ensure-coverage/references/depth/test-smells.md +23 -0
- package/skills/engineering/ensure-coverage/references/enforcement/ci-contract.md +164 -0
- package/skills/engineering/ensure-coverage/references/enforcement/hooks.md +85 -0
- package/skills/engineering/ensure-coverage/references/examples/coverage-ledger.md +109 -0
- package/skills/engineering/ensure-coverage/references/examples/refund-flow.md +33 -0
- package/skills/engineering/ensure-coverage/references/presentation/axis.md +78 -0
- package/skills/engineering/ensure-coverage/references/presentation/runner-contract.md +74 -0
- package/skills/engineering/ensure-coverage/references/process/audit-mode.md +33 -0
- package/skills/engineering/ensure-coverage/references/process/output-template.md +139 -0
- package/skills/engineering/ensure-coverage/references/process/review-contract-template.md +119 -0
- package/skills/engineering/ensure-coverage/references/process/scope-class.md +178 -0
- package/skills/engineering/ensure-coverage/references/process/test-strategy.md +55 -0
- package/skills/engineering/ensure-coverage/schemas/coverage-config.schema.json +45 -0
- package/skills/engineering/ensure-coverage/schemas/coverage-file.schema.json +93 -0
- package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.mjs +178 -0
- package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.mjs +101 -0
- package/skills/engineering/ensure-coverage/scripts/ci-audit.mjs +358 -0
- package/skills/engineering/ensure-coverage/scripts/coverage-checklist.mjs +494 -0
- package/skills/engineering/ensure-coverage/scripts/coverage-ledger.mjs +663 -0
- package/skills/engineering/ensure-coverage/scripts/design-parity.mjs +591 -0
- package/skills/engineering/ensure-coverage/scripts/evidence-block.mjs +367 -0
- package/skills/engineering/ensure-coverage/scripts/lint-tests.mjs +269 -0
- package/skills/engineering/ensure-coverage/scripts/mock-policy-config.mjs +176 -0
- package/skills/engineering/ensure-coverage/scripts/package-lock.json +76 -0
- package/skills/engineering/ensure-coverage/scripts/package.json +19 -0
- package/skills/engineering/ensure-coverage/scripts/scope-class.mjs +554 -0
- package/skills/engineering/harness-runtime/LICENSE +3 -0
- package/skills/engineering/harness-runtime/SKILL.md +18 -0
- package/skills/engineering/harness-runtime/agents/openai.yaml +6 -0
- package/skills/engineering/harness-runtime/bin/discover-models.mjs +4 -0
- package/skills/engineering/harness-runtime/bin/model-catalog.mjs +4 -0
- package/skills/engineering/harness-runtime/contracts.md +15 -0
- package/skills/engineering/harness-runtime/discover-models.mjs +392 -0
- package/skills/engineering/harness-runtime/fixtures/native-question-schemas.json +33 -0
- package/skills/engineering/harness-runtime/fixtures/question-responses.json +54 -0
- package/skills/engineering/harness-runtime/index.mjs +767 -0
- package/skills/engineering/harness-runtime/model-catalog.mjs +787 -0
- package/skills/engineering/harness-runtime/native-question-contracts.md +37 -0
- package/skills/engineering/harness-runtime/references/model-catalog-seed.json +159 -0
- package/skills/engineering/harness-runtime/references/model-catalog.md +57 -0
- package/skills/engineering/implement-and-pr/LICENSE +3 -0
- package/skills/engineering/implement-and-pr/SKILL.md +176 -0
- package/skills/engineering/implement-and-pr/references/ci-iteration.md +10 -0
- package/skills/engineering/implement-and-pr/references/closeout.md +27 -0
- package/skills/engineering/implement-and-pr/references/contract-complete-fix-rounds.md +34 -0
- package/skills/engineering/implement-and-pr/references/evidence-rules.md +39 -0
- package/skills/engineering/implement-and-pr/references/incremental-plan.md +16 -0
- package/skills/engineering/implement-and-pr/references/self-review.md +23 -0
- package/skills/engineering/implement-and-pr/references/tdd-mode.md +18 -0
- package/skills/engineering/resolve-issues/LICENSE +3 -0
- package/skills/engineering/resolve-issues/SKILL.md +167 -0
- package/skills/engineering/resolve-issues/generated/workflow-repair-policy.json +448 -0
- package/skills/engineering/resolve-issues/references/breaker.md +82 -0
- package/skills/engineering/resolve-issues/references/deliverables.md +27 -0
- package/skills/engineering/resolve-issues/references/delivery.md +108 -0
- package/skills/engineering/resolve-issues/references/evidence-lane.md +21 -0
- package/skills/engineering/resolve-issues/references/exact-head-ci.md +287 -0
- package/skills/engineering/resolve-issues/references/fan-out.md +33 -0
- package/skills/engineering/resolve-issues/references/finalization.md +68 -0
- package/skills/engineering/resolve-issues/references/guarantees.md +10 -0
- package/skills/engineering/resolve-issues/references/high-risk.md +29 -0
- package/skills/engineering/resolve-issues/references/incidents/848/README.md +156 -0
- package/skills/engineering/resolve-issues/references/intake.md +86 -0
- package/skills/engineering/resolve-issues/references/integration-gate.md +53 -0
- package/skills/engineering/resolve-issues/references/interference.md +87 -0
- package/skills/engineering/resolve-issues/references/loop.md +134 -0
- package/skills/engineering/resolve-issues/references/model-catalog.md +9 -0
- package/skills/engineering/resolve-issues/references/postmortem.md +27 -0
- package/skills/engineering/resolve-issues/references/pre-flight-model-slots.md +41 -0
- package/skills/engineering/resolve-issues/references/pre-flight-recording-and-checkout.md +48 -0
- package/skills/engineering/resolve-issues/references/pre-flight.md +41 -0
- package/skills/engineering/resolve-issues/references/regression-checklist.md +26 -0
- package/skills/engineering/resolve-issues/references/run-state.md +288 -0
- package/skills/engineering/resolve-issues/references/sandboxed-testing.md +48 -0
- package/skills/engineering/resolve-issues/references/spawn-contract.md +96 -0
- package/skills/engineering/resolve-issues/references/terminal-evidence-journal.md +40 -0
- package/skills/engineering/resolve-issues/references/why.md +653 -0
- package/skills/engineering/resolve-issues/schemas/fix-round.schema.json +49 -0
- package/skills/engineering/resolve-issues/scripts/combine-and-verify.mjs +721 -0
- package/skills/engineering/resolve-issues/scripts/component-candidate.mjs +962 -0
- package/skills/engineering/resolve-issues/scripts/contract-revision.mjs +220 -0
- package/skills/engineering/resolve-issues/scripts/detect-delivery-mode.mjs +420 -0
- package/skills/engineering/resolve-issues/scripts/detect-target-branch.mjs +256 -0
- package/skills/engineering/resolve-issues/scripts/detect-workspace-mode.mjs +168 -0
- package/skills/engineering/resolve-issues/scripts/discover-models.mjs +9 -0
- package/skills/engineering/resolve-issues/scripts/doctrine.mjs +62 -0
- package/skills/engineering/resolve-issues/scripts/evidence-lifecycle-contract.mjs +191 -0
- package/skills/engineering/resolve-issues/scripts/exact-head-ci.mjs +413 -0
- package/skills/engineering/resolve-issues/scripts/exact-head-github-provider.mjs +332 -0
- package/skills/engineering/resolve-issues/scripts/finalize.mjs +488 -0
- package/skills/engineering/resolve-issues/scripts/fix-rounds.mjs +3307 -0
- package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-circular-1001.json +16 -0
- package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-valid-sequencing.json +51 -0
- package/skills/engineering/resolve-issues/scripts/fixtures/fmm-express-830-component-candidate.json +17 -0
- package/skills/engineering/resolve-issues/scripts/fixtures/head-check-set-1081.json +166 -0
- package/skills/engineering/resolve-issues/scripts/gate-value-series.mjs +92 -0
- package/skills/engineering/resolve-issues/scripts/guide-index.mjs +73 -0
- package/skills/engineering/resolve-issues/scripts/head-check-set.mjs +159 -0
- package/skills/engineering/resolve-issues/scripts/interference.mjs +427 -0
- package/skills/engineering/resolve-issues/scripts/model-catalog.mjs +9 -0
- package/skills/engineering/resolve-issues/scripts/next-operations.mjs +419 -0
- package/skills/engineering/resolve-issues/scripts/postmortem.mjs +909 -0
- package/skills/engineering/resolve-issues/scripts/preflight-questions.mjs +322 -0
- package/skills/engineering/resolve-issues/scripts/reconcile-contained-unit-prs.mjs +415 -0
- package/skills/engineering/resolve-issues/scripts/release-state-contract.mjs +697 -0
- package/skills/engineering/resolve-issues/scripts/report.mjs +494 -0
- package/skills/engineering/resolve-issues/scripts/required-check-plan.mjs +172 -0
- package/skills/engineering/resolve-issues/scripts/round-metadata.mjs +79 -0
- package/skills/engineering/resolve-issues/scripts/run-state-review6-cases.mjs +334 -0
- package/skills/engineering/resolve-issues/scripts/run-state.mjs +4784 -0
- package/skills/engineering/resolve-issues/scripts/sandbox-selftest.mjs +395 -0
- package/skills/engineering/resolve-issues/scripts/spawn-contract.mjs +290 -0
- package/skills/engineering/resolve-issues/scripts/terminal-dispositions.mjs +170 -0
- package/skills/engineering/resolve-issues/scripts/terminal-evidence-journal.mjs +293 -0
- package/skills/engineering/resolve-issues/scripts/unit-kind.mjs +197 -0
- package/skills/engineering/resolve-issues/scripts/unit-lifecycle.mjs +127 -0
- package/skills/engineering/resolve-issues/scripts/watch-delivery.mjs +893 -0
- package/skills/engineering/resolve-issues/scripts/workspaces.mjs +829 -0
- package/skills/engineering/resolve-issues/workflows/independent-review.workflow.js +290 -0
- package/skills/engineering/resolve-issues/workflows/prior-art-scan.workflow.js +80 -0
- package/skills/engineering/resolve-issues/workflows/workflow-smoke.mjs +102 -0
- package/skills/engineering/resolve-release/LICENSE +3 -0
- package/skills/engineering/resolve-release/SKILL.md +112 -0
- package/skills/engineering/resolve-release/references/assembly.md +137 -0
- package/skills/engineering/resolve-release/references/auto-when-green.md +56 -0
- package/skills/engineering/resolve-release/references/candidate.md +167 -0
- package/skills/engineering/resolve-release/references/exposure.md +178 -0
- package/skills/engineering/resolve-release/references/handoff.md +24 -0
- package/skills/engineering/resolve-release/references/postmortem.md +230 -0
- package/skills/engineering/resolve-release/references/preflight.md +207 -0
- package/skills/engineering/resolve-release/references/principles.md +94 -0
- package/skills/engineering/resolve-release/references/regression-checklist.md +36 -0
- package/skills/engineering/resolve-release/references/related-skills.md +12 -0
- package/skills/engineering/resolve-release/references/routing.md +149 -0
- package/skills/engineering/resolve-release/references/verified-sha-github-flow.md +285 -0
- package/skills/engineering/resolve-release/references/versioning.md +202 -0
- package/skills/engineering/resolve-release/references/why.md +53 -0
- package/skills/engineering/resolve-release/scripts/adapter-completion-artifact.mjs +389 -0
- package/skills/engineering/resolve-release/scripts/build-changes.mjs +209 -0
- package/skills/engineering/resolve-release/scripts/candidate-hygiene.mjs +407 -0
- package/skills/engineering/resolve-release/scripts/candidate-identity.mjs +904 -0
- package/skills/engineering/resolve-release/scripts/candidate-traffic.mjs +81 -0
- package/skills/engineering/resolve-release/scripts/checked-adapter-loader.mjs +612 -0
- package/skills/engineering/resolve-release/scripts/close-attempt.mjs +135 -0
- package/skills/engineering/resolve-release/scripts/closeout-release.mjs +161 -0
- package/skills/engineering/resolve-release/scripts/doctrine.mjs +106 -0
- package/skills/engineering/resolve-release/scripts/durable-processing.mjs +522 -0
- package/skills/engineering/resolve-release/scripts/ensure-target-green.mjs +659 -0
- package/skills/engineering/resolve-release/scripts/evidence-bundle.mjs +1014 -0
- package/skills/engineering/resolve-release/scripts/finalize-release.mjs +526 -0
- package/skills/engineering/resolve-release/scripts/fixtures/durable-processing-adapter.mjs +169 -0
- package/skills/engineering/resolve-release/scripts/green-gate.mjs +599 -0
- package/skills/engineering/resolve-release/scripts/isolated-adapter-evaluator.mjs +752 -0
- package/skills/engineering/resolve-release/scripts/metadata-pr-status.mjs +56 -0
- package/skills/engineering/resolve-release/scripts/metadata-sync.mjs +1538 -0
- package/skills/engineering/resolve-release/scripts/postmortem.mjs +381 -0
- package/skills/engineering/resolve-release/scripts/preflight-probes.mjs +498 -0
- package/skills/engineering/resolve-release/scripts/production-endpoints.mjs +326 -0
- package/skills/engineering/resolve-release/scripts/rc-circuit-breaker.mjs +272 -0
- package/skills/engineering/resolve-release/scripts/report.mjs +417 -0
- package/skills/engineering/resolve-release/scripts/reprobe-credentials.mjs +114 -0
- package/skills/engineering/resolve-release/scripts/revalidate-candidate.mjs +238 -0
- package/skills/engineering/resolve-release/scripts/review-packet.mjs +503 -0
- package/skills/engineering/resolve-release/scripts/rollback-floor.mjs +263 -0
- package/skills/engineering/resolve-release/scripts/version-assert.mjs +339 -0
- package/skills/engineering/resolve-release/scripts/version-postmortem.mjs +485 -0
- package/skills/engineering/resolve-release/scripts/version.mjs +1199 -0
- package/skills/engineering/resolve-release/scripts/watch-candidate-delivery.mjs +449 -0
- package/skills/engineering/resolve-release/vendor/ACORN-LICENSE +21 -0
- package/skills/engineering/resolve-release/vendor/README.md +60 -0
- package/skills/engineering/resolve-release/vendor/acorn.mjs +6233 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/LICENSE +21 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/README.md +341 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/README.template.md +70 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs +7 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.mts +2033 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.ts +2033 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js +7 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/package.json +49 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/LICENSE +21 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/README.md +5 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.mts +549 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.ts +549 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/package.json +36 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/LICENSE +47 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/README.md +82 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.d.ts +11 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.mjs +22 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cjs +21 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.cjs +21 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.d.ts +11 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.d.ts +11 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.mjs +25 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.wasm +0 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.mts +85 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.ts +85 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.mts +20 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.ts +20 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/package.json +61 -0
- package/skills/engineering/review-pr/LICENSE +3 -0
- package/skills/engineering/review-pr/SKILL.md +123 -0
- package/skills/engineering/review-pr/references/adversarial-reviewer-prompt.md +42 -0
- package/skills/engineering/review-pr/references/code-correctness.md +5 -0
- package/skills/engineering/review-pr/references/contract-freshness.md +9 -0
- package/skills/engineering/review-pr/references/coordination.md +18 -0
- package/skills/engineering/review-pr/references/domain-hazards.md +123 -0
- package/skills/engineering/review-pr/references/finding-themes.md +7 -0
- package/skills/engineering/review-pr/references/github-posting.md +98 -0
- package/skills/engineering/review-pr/references/golden-path-smoke.md +5 -0
- package/skills/engineering/review-pr/references/incremental-output.md +16 -0
- package/skills/engineering/review-pr/references/inputs-and-discovery.md +31 -0
- package/skills/engineering/review-pr/references/output-format.md +99 -0
- package/skills/engineering/review-pr/references/over-mock-screen.md +7 -0
- package/skills/engineering/review-pr/references/promotion-prs.md +16 -0
- package/skills/engineering/review-pr/references/re-review.md +18 -0
- package/skills/engineering/review-pr/references/review-method.md +199 -0
- package/skills/engineering/review-pr/references/review-mode.md +30 -0
- package/skills/engineering/review-pr/references/review-posture.md +53 -0
- package/skills/engineering/review-pr/references/round1-depth.md +62 -0
- package/skills/engineering/review-pr/references/scripts.md +17 -0
- package/skills/engineering/review-pr/references/workflow.md +16 -0
- package/skills/engineering/review-pr/schemas/findings.schema.json +282 -0
- package/skills/engineering/review-pr/scripts/finding-contract.mjs +285 -0
- package/skills/engineering/review-pr/scripts/post-review.mjs +405 -0
- package/skills/engineering/review-pr/scripts/pr-context.mjs +207 -0
- package/skills/engineering/review-pr/scripts/scan-diff.mjs +365 -0
- package/skills/engineering/review-pr/scripts/theme-contract.mjs +57 -0
- package/skills/engineering/smoke/LICENSE +3 -0
- package/skills/engineering/smoke/SKILL.md +131 -0
- package/skills/engineering/smoke/assets/smoke.manifest.example.json +53 -0
- package/skills/engineering/smoke/references/manifest.md +192 -0
- package/skills/engineering/smoke/scripts/smoke.mjs +713 -0
- package/skills/fullstack/better-auth/LICENSE +3 -0
- package/skills/fullstack/better-auth/SKILL.md +601 -0
- package/skills/fullstack/better-auth/references/feishu-api.md +270 -0
- package/skills/fullstack/monorepo/LICENSE +3 -0
- package/skills/fullstack/monorepo/SKILL.md +465 -0
- package/skills/fullstack/nextjs-fullstack/LICENSE +3 -0
- package/skills/fullstack/nextjs-fullstack/SKILL.md +210 -0
- package/skills/fullstack/nextjs-fullstack/conventions.md +318 -0
- package/skills/fullstack/nextjs-fullstack/frontend-conventions.md +61 -0
- package/skills/fullstack/nextjs-fullstack/nextjs16.md +287 -0
- package/skills/fullstack/nextjs-fullstack/server-actions.md +409 -0
- package/skills/fullstack/prisma-setup/LICENSE +3 -0
- package/skills/fullstack/prisma-setup/SKILL.md +180 -0
- package/skills/fullstack/prisma-setup/nextjs.md +258 -0
- package/skills/fullstack/prisma-setup/turborepo.md +301 -0
- package/skills/fullstack/shadcn/LICENSE +3 -0
- package/skills/fullstack/shadcn/SKILL.md +119 -0
- package/skills/fullstack/shadcn/assets/shadcn-small.png +0 -0
- package/skills/fullstack/shadcn/assets/shadcn.png +0 -0
- package/skills/fullstack/shadcn/cli.md +411 -0
- package/skills/fullstack/shadcn/customization.md +224 -0
- package/skills/fullstack/shadcn/evals/evals.json +90 -0
- package/skills/fullstack/shadcn/mcp.md +101 -0
- package/skills/fullstack/shadcn/rules/base-vs-radix.md +323 -0
- package/skills/fullstack/shadcn/rules/component-selection.md +67 -0
- package/skills/fullstack/shadcn/rules/composition.md +195 -0
- package/skills/fullstack/shadcn/rules/data-table.md +201 -0
- package/skills/fullstack/shadcn/rules/forms.md +255 -0
- package/skills/fullstack/shadcn/rules/icons.md +103 -0
- package/skills/fullstack/shadcn/rules/styling.md +167 -0
- package/skills/fullstack/zod-v4/LICENSE +3 -0
- package/skills/fullstack/zod-v4/SKILL.md +287 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
// Post a review-pr verdict + findings to GitHub as ONE review (inline comments +
|
|
2
|
+
// summary + verdict event), handling the self-authored-PR downgrade. The fiddly,
|
|
3
|
+
// error-prone glue that should be code, not prose. Pure core is unit-tested; the
|
|
4
|
+
// `gh` calls live only in the CLI.
|
|
5
|
+
//
|
|
6
|
+
// node post-review.mjs --findings review.json --pr 482 [--repo owner/name]
|
|
7
|
+
// [--pr-context pr-context.json] [--dry-run] [--file-issues]
|
|
8
|
+
//
|
|
9
|
+
// `review.json` matches schemas/findings.schema.json: { verdict, summary, findings[] }.
|
|
10
|
+
// Verification is enforced upstream by that schema — a finding with no file/line is
|
|
11
|
+
// invalid and this script refuses to post it. The BLOCK-n/NOTE-n label and the
|
|
12
|
+
// verdict are DERIVED here from each finding's {priority, in_scope}; the reviewer
|
|
13
|
+
// never writes them. Non-blocking notes and out-of-scope criticals route to tracked
|
|
14
|
+
// issues — but ONLY under --file-issues (default OFF prints what it WOULD open),
|
|
15
|
+
// because opening an issue is an outward action. --pr-context reuses pr-context.mjs's
|
|
16
|
+
// own changed-file list to catch a finding whose path is not actually in the diff
|
|
17
|
+
// (in_scope alone is not a reliable proxy for that — #635); never anchored inline
|
|
18
|
+
// either way, only where the detail lands (comment vs. review-body section) changes.
|
|
19
|
+
|
|
20
|
+
import fs from 'node:fs'
|
|
21
|
+
import { execFileSync } from 'node:child_process'
|
|
22
|
+
import {
|
|
23
|
+
validateFinding,
|
|
24
|
+
validateReviewDocument,
|
|
25
|
+
reviewDocumentWarnings,
|
|
26
|
+
isBlockingFinding,
|
|
27
|
+
isOutOfScopeCritical,
|
|
28
|
+
findingInScope,
|
|
29
|
+
} from './finding-contract.mjs'
|
|
30
|
+
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
31
|
+
|
|
32
|
+
// ---- pure core ----
|
|
33
|
+
|
|
34
|
+
export const VERDICT_EVENT = {
|
|
35
|
+
PASS: 'APPROVE',
|
|
36
|
+
NEEDS_CHANGES: 'REQUEST_CHANGES',
|
|
37
|
+
FAIL: 'REQUEST_CHANGES',
|
|
38
|
+
BLOCKED: 'COMMENT',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Map verdict -> GitHub review event, downgrading to COMMENT on a self-authored PR
|
|
42
|
+
// (GitHub forbids APPROVE/REQUEST_CHANGES on your own PR).
|
|
43
|
+
export function resolveEvent(verdict, { selfAuthored = false } = {}) {
|
|
44
|
+
const base = VERDICT_EVENT[verdict]
|
|
45
|
+
if (!base) throw new Error(`unknown verdict: ${verdict}`)
|
|
46
|
+
if (selfAuthored && (base === 'APPROVE' || base === 'REQUEST_CHANGES')) {
|
|
47
|
+
return { event: 'COMMENT', downgraded: true, intended: base }
|
|
48
|
+
}
|
|
49
|
+
return { event: base, downgraded: false, intended: base }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Enforce the finding contract (the part the schema guarantees, re-checked here so a
|
|
53
|
+
// hand-built file can't post a location-less "finding").
|
|
54
|
+
export function validateFindings(findings) {
|
|
55
|
+
if (!Array.isArray(findings)) return ['findings is not an array']
|
|
56
|
+
return findings.flatMap((finding, index) => validateFinding(finding, index))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { validateReviewDocument }
|
|
60
|
+
|
|
61
|
+
// ---- rendered contract: label, verdict, and issue routing are DERIVED here ----
|
|
62
|
+
//
|
|
63
|
+
// The reviewer model returns JSON only ({priority, in_scope, ...}); it never
|
|
64
|
+
// writes a label prefix or the final verdict. post-review.mjs renders both, so
|
|
65
|
+
// any reviewer model maps to one output (#399). `blocking` is not a stored field:
|
|
66
|
+
// it is `in_scope && priority <= 1`, computed by isBlockingFinding.
|
|
67
|
+
|
|
68
|
+
// Assign a rendered label to each finding, sequenced per class:
|
|
69
|
+
// in_scope && priority<=1 → BLOCK-n (holds the PR)
|
|
70
|
+
// everything else → NOTE-n
|
|
71
|
+
// An out-of-scope critical (!in_scope && priority<=1) is a NOTE-n label but is
|
|
72
|
+
// surfaced loudly (its own issue + a warning), never buried — see planIssues.
|
|
73
|
+
export function renderFindings(findings) {
|
|
74
|
+
let blockN = 0
|
|
75
|
+
let noteN = 0
|
|
76
|
+
return findings.map((finding) => {
|
|
77
|
+
const blocking = isBlockingFinding(finding)
|
|
78
|
+
const outOfScopeCritical = isOutOfScopeCritical(finding)
|
|
79
|
+
const label = blocking ? `BLOCK-${++blockN}` : `NOTE-${++noteN}`
|
|
80
|
+
return { finding, label, blocking, outOfScopeCritical }
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Verdict is mechanized: NEEDS_CHANGES iff ≥1 BLOCK-n, else PASS. FAIL/BLOCKED are
|
|
85
|
+
// structural reviewer calls (unusable contract / unreachable evidence) and pass
|
|
86
|
+
// through untouched.
|
|
87
|
+
export function deriveVerdict(declaredVerdict, findings) {
|
|
88
|
+
if (declaredVerdict === 'FAIL' || declaredVerdict === 'BLOCKED') return declaredVerdict
|
|
89
|
+
return findings.some((finding) => isBlockingFinding(finding)) ? 'NEEDS_CHANGES' : 'PASS'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function prLink({ pr, repo } = {}) {
|
|
93
|
+
if (repo && pr != null) return `https://github.com/${repo}/pull/${pr}`
|
|
94
|
+
if (pr != null) return `#${pr}`
|
|
95
|
+
return 'this PR'
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// The loud, free warning: prints regardless of any flag, and rides in the review
|
|
99
|
+
// body + handoff. An out-of-scope critical does not block the PR, so nothing else
|
|
100
|
+
// forces a human to look at it — the warning is that forcing function.
|
|
101
|
+
export function outOfScopeCriticalWarnings(findings, context = {}) {
|
|
102
|
+
return renderFindings(findings)
|
|
103
|
+
.filter(({ outOfScopeCritical }) => outOfScopeCritical)
|
|
104
|
+
.map(({ finding: f, label }) =>
|
|
105
|
+
`> ⚠️ **OUT-OF-SCOPE CRITICAL — ${label} (does NOT block this PR):** ${f.title} — \`${f.file}:${f.line_start}\` ` +
|
|
106
|
+
`(priority ${f.priority}, confidence ${f.confidence}). A dedicated critical/security issue is owed; do not fold it into the refactor-later batch.`)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Plan the tracked issues a set of findings should open — pure, returns intended
|
|
110
|
+
// payloads (never shells out). The CLI decides whether to create them (--file-issues)
|
|
111
|
+
// or print them (default). Two kinds:
|
|
112
|
+
// refactor-later — ONE batched issue for every priority>=2 note (both scopes).
|
|
113
|
+
// out-of-scope-critical — ONE dedicated issue per !in_scope && priority<=1 finding.
|
|
114
|
+
export function planIssues(findings, context = {}) {
|
|
115
|
+
const rendered = renderFindings(findings)
|
|
116
|
+
const link = prLink(context)
|
|
117
|
+
const notes = rendered.filter(({ finding }) => Number.isInteger(finding.priority) && finding.priority >= 2)
|
|
118
|
+
const criticals = rendered.filter(({ outOfScopeCritical }) => outOfScopeCritical)
|
|
119
|
+
const issues = []
|
|
120
|
+
if (notes.length) {
|
|
121
|
+
const rows = notes.map(({ finding: f, label }) =>
|
|
122
|
+
`- **${label}** ${f.in_scope === false ? '(out-of-scope) ' : ''}${f.title} — \`${f.file}:${f.line_start}\`` +
|
|
123
|
+
(f.recommendation ? ` — ${f.recommendation}` : ''))
|
|
124
|
+
issues.push({
|
|
125
|
+
kind: 'refactor-later',
|
|
126
|
+
labels: ['refactor-later'],
|
|
127
|
+
title: `refactor-later: ${notes.length} deferred note${notes.length === 1 ? '' : 's'} from ${link}`,
|
|
128
|
+
body: `Deferred non-blocking notes batched from ${link} — not merge-blocking, tracked so they are neither lost nor left holding attention on the PR.\n\n${rows.join('\n')}`,
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
for (const { finding: f, label } of criticals) {
|
|
132
|
+
issues.push({
|
|
133
|
+
kind: 'out-of-scope-critical',
|
|
134
|
+
labels: ['critical', 'security'],
|
|
135
|
+
title: `out-of-scope critical: ${f.title}`,
|
|
136
|
+
body: `Surfaced as ${label} from ${link} — a priority-${f.priority} issue in code the PR did not change, so it does NOT block that PR but must not be buried in a refactor batch.\n\n` +
|
|
137
|
+
`**Location:** \`${f.file}:${f.line_start}\`\n**Why:** ${f.why}` +
|
|
138
|
+
(f.consequence ? `\n**Consequence:** ${f.consequence}` : '') +
|
|
139
|
+
(f.recommendation ? `\n**Fix:** ${f.recommendation}` : ''),
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
return issues
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function commentBody(f, label) {
|
|
146
|
+
const prefix = label ? `${label}: ` : ''
|
|
147
|
+
let body = `**${prefix}${f.title}** _(priority ${f.priority}, confidence ${f.confidence})_` +
|
|
148
|
+
(f.theme?.contract ? `\n\n**Governing contract:** ${f.theme.contract}` : '') +
|
|
149
|
+
`\n\n${f.why}` +
|
|
150
|
+
(f.consequence ? `\n\n**Consequence:** ${f.consequence}` : '') +
|
|
151
|
+
(f.recommendation ? `\n\n**Fix:** ${f.recommendation}` : '')
|
|
152
|
+
// A domain-semantic blocker's hazard_candidate is the ready-to-paste repo-hazards row the
|
|
153
|
+
// fix commit owes (docs/domain-hazards.md). The posted review is the durable form the fix
|
|
154
|
+
// agent works from — dropping the row here would force the fixer to reinvent it and leave
|
|
155
|
+
// the re-reviewer nothing to verify the written row against.
|
|
156
|
+
const h = f.hazard_candidate
|
|
157
|
+
if (h && (h.covered_by || h.rule)) {
|
|
158
|
+
body += h.covered_by
|
|
159
|
+
? `\n\n**Hazard row:** covered by \`${h.covered_by}\` in \`docs/domain-hazards.md\` — no new row owed.`
|
|
160
|
+
: `\n\n**Hazard row owed** (\`docs/domain-hazards.md\` → ${h.section || 'cross-cutting'}):\n\n\`\`\`markdown\n| ${h.id || '<id>'} | ${h.rule} | ${h.prove || '<what to prove>'} | <this PR> |\n\`\`\``
|
|
161
|
+
}
|
|
162
|
+
return body
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ---- #635: is a finding's file actually IN the diff? ----
|
|
166
|
+
//
|
|
167
|
+
// `pr-context.mjs` computes the changed-file list once, at review-establishment
|
|
168
|
+
// time, as `changedFiles: [{status, path}]` (see its --base CLI). Reuse that same
|
|
169
|
+
// list here rather than recomputing it — post-review.mjs never shells out to git
|
|
170
|
+
// or `gh` to rediscover the diff; the CLI accepts it via `--pr-context <file>`
|
|
171
|
+
// (pr-context.mjs's own JSON output) so the two steps of one review share one
|
|
172
|
+
// computation instead of drifting.
|
|
173
|
+
|
|
174
|
+
// Normalize a path for changed-file/finding-file comparison. The diff list and a
|
|
175
|
+
// finding's `file` can name the same path while disagreeing on a leading `./`,
|
|
176
|
+
// backslashes, a leading `/` (repo-relative vs. an absolute path the reviewer
|
|
177
|
+
// pasted), or case (a case-insensitive checkout vs. GitHub's committed case).
|
|
178
|
+
export function normalizeDiffPath(p) {
|
|
179
|
+
if (!p) return ''
|
|
180
|
+
return String(p).replace(/\\/g, '/').replace(/^(\.\/)+/, '').replace(/^\/+/, '').toLowerCase()
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Build the changed-file set from pr-context.mjs's own `changedFiles` shape
|
|
184
|
+
// ([{status, path}]), or a bare array of path strings. `null` (not an array at
|
|
185
|
+
// all) means "the diff is unknown" — distinct from an empty Set ("known: nothing
|
|
186
|
+
// changed") — so `anchorable` below can tell "can't verify" from "verified: not
|
|
187
|
+
// in the diff".
|
|
188
|
+
export function changedFileSet(changedFiles) {
|
|
189
|
+
if (!Array.isArray(changedFiles)) return null
|
|
190
|
+
const set = new Set()
|
|
191
|
+
for (const entry of changedFiles) {
|
|
192
|
+
const p = typeof entry === 'string' ? entry : entry?.path
|
|
193
|
+
if (p) set.add(normalizeDiffPath(p))
|
|
194
|
+
}
|
|
195
|
+
return set
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Can this finding be anchored as an inline comment? Two independent gates:
|
|
199
|
+
// - in_scope (the reviewer's judgment — unchanged from #628/#399), and
|
|
200
|
+
// - the file is actually in the diff, when the diff is known (#635) — an
|
|
201
|
+
// in_scope finding earned by tracing into unchanged code to prove impact can
|
|
202
|
+
// still name a file the diff never touched, and GitHub 422s on that path
|
|
203
|
+
// exactly like it does for an out-of-scope one.
|
|
204
|
+
// `knownChangedFiles === null` means the caller could not establish the diff (no
|
|
205
|
+
// `--pr-context`, an unreadable/malformed file, a git/gh error upstream of it) —
|
|
206
|
+
// fail TOWARD POSTING: keep the pre-#635 permissive behavior (in_scope alone
|
|
207
|
+
// decides) rather than guessing either way about a diff this run cannot see. A
|
|
208
|
+
// finding is never dropped either way — undecidable-but-in-scope still anchors
|
|
209
|
+
// (old behavior); anything that fails the check lands in the body instead.
|
|
210
|
+
export function anchorable(finding, knownChangedFiles) {
|
|
211
|
+
if (!findingInScope(finding)) return false
|
|
212
|
+
if (knownChangedFiles === null) return true
|
|
213
|
+
return knownChangedFiles.has(normalizeDiffPath(finding.file))
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// An out-of-scope finding's file is by definition absent from the diff — GitHub
|
|
217
|
+
// refuses to anchor a review comment there and 422s the ENTIRE review, losing the
|
|
218
|
+
// verdict, the summary, and every in-scope comment with it (#628). So an
|
|
219
|
+
// out-of-scope finding never reaches `comments`; this renders the detail an
|
|
220
|
+
// inline comment would have carried (why/consequence/fix) into the review body
|
|
221
|
+
// instead — the only place left for it, since a `NOTE-n` label alone would bury
|
|
222
|
+
// it and, for a non-critical note, `--file-issues` is opt-in and OFF by default.
|
|
223
|
+
// An out-of-scope CRITICAL still gets this in addition to its existing loud
|
|
224
|
+
// warning above (`outOfScopeCriticalWarnings`) — that warning alone omits
|
|
225
|
+
// why/consequence/recommendation, so without this section a critical's full
|
|
226
|
+
// reasoning would vanish along with its inline comment.
|
|
227
|
+
//
|
|
228
|
+
// #635: `in_scope` was only ever a proxy for "this file is in the diff", and the
|
|
229
|
+
// proxy is inexact in the direction that still 422s the whole review — review-pr's
|
|
230
|
+
// own doctrine (SKILL.md § Review Posture) permits tracing into UNCHANGED code to
|
|
231
|
+
// *prove* impact, so a finding can be `in_scope: true` and still name a file the
|
|
232
|
+
// diff never touched. So this section now also catches any finding — in scope or
|
|
233
|
+
// not — whose `file` isn't in the known changed-file list, subsuming the in_scope
|
|
234
|
+
// check rather than sitting beside it: the question is no longer "is this in
|
|
235
|
+
// scope?" (a judgment) but "is this path in the diff?" (a fact `changedFiles`,
|
|
236
|
+
// when known, settles). `in_scope`'s own effect is unchanged either way.
|
|
237
|
+
export function outOfScopeFindingsBody(rendered, { changedFiles } = {}) {
|
|
238
|
+
const knownChangedFiles = changedFileSet(changedFiles)
|
|
239
|
+
const diverted = rendered.filter(({ finding: f }) => !anchorable(f, knownChangedFiles))
|
|
240
|
+
if (!diverted.length) return ''
|
|
241
|
+
const blocks = diverted.map(({ finding: f, label }) => {
|
|
242
|
+
const reason = !findingInScope(f) ? 'out-of-scope — not anchored inline' : 'file not in diff — not anchored inline'
|
|
243
|
+
return `**${label} — \`${f.file}:${f.line_start}\`** _(${reason})_\n\n${commentBody(f, null)}`
|
|
244
|
+
})
|
|
245
|
+
return `## Findings not anchored inline\n\n${blocks.join('\n\n---\n\n')}`
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Build the single-review payload for POST /repos/{o}/{r}/pulls/{n}/reviews.
|
|
249
|
+
// The verdict is DERIVED from the findings ({priority, in_scope}), not taken from
|
|
250
|
+
// the reviewer's declared field; each inline comment carries its rendered
|
|
251
|
+
// BLOCK-n/NOTE-n label; out-of-scope criticals ride a loud warning at the top.
|
|
252
|
+
// `changedFiles` is the diff's changed-file list (pr-context.mjs's own shape,
|
|
253
|
+
// [{status, path}]) — optional, and never part of the reviewer's document (like
|
|
254
|
+
// `pr`/`repo`, it's a posting-time input, not something the model emits).
|
|
255
|
+
export function buildReviewPayload({ verdict, summary = '', findings = [], selfAuthored = false, pr, repo, changedFiles, ...metadata }) {
|
|
256
|
+
const documentErrors = validateReviewDocument({ verdict, summary, findings, ...metadata })
|
|
257
|
+
if (documentErrors.length) throw new Error(`invalid review document: ${documentErrors.join('; ')}`)
|
|
258
|
+
const derivedVerdict = deriveVerdict(verdict, findings)
|
|
259
|
+
const { event, downgraded, intended } = resolveEvent(derivedVerdict, { selfAuthored })
|
|
260
|
+
const rendered = renderFindings(findings)
|
|
261
|
+
const knownChangedFiles = changedFileSet(changedFiles)
|
|
262
|
+
let body = summary || `Review verdict: ${derivedVerdict}`
|
|
263
|
+
if (downgraded) {
|
|
264
|
+
const kind = intended === 'APPROVE' ? 'approve' : 'request changes on'
|
|
265
|
+
body = `> **Intended verdict: ${derivedVerdict}** — posted as a comment because the reviewer authored this PR and GitHub does not allow you to ${kind} your own PR. Treat the findings below as binding.\n\n${body}`
|
|
266
|
+
}
|
|
267
|
+
const warnings = outOfScopeCriticalWarnings(findings, { pr, repo })
|
|
268
|
+
if (warnings.length) body = `${warnings.join('\n\n')}\n\n${body}`
|
|
269
|
+
const oosBody = outOfScopeFindingsBody(rendered, { changedFiles })
|
|
270
|
+
if (oosBody) body = `${body}\n\n${oosBody}`
|
|
271
|
+
const comments = rendered
|
|
272
|
+
.filter(({ finding: f }) => anchorable(f, knownChangedFiles)) // never anchor out-of-scope or off-diff — see outOfScopeFindingsBody
|
|
273
|
+
.map(({ finding: f, label }) => {
|
|
274
|
+
const lineCount = f.line_count ?? 1 // line_count is optional; a bare line_start anchors one line
|
|
275
|
+
return {
|
|
276
|
+
path: f.file,
|
|
277
|
+
line: f.line_start + lineCount - 1,
|
|
278
|
+
side: 'RIGHT',
|
|
279
|
+
...(lineCount > 1
|
|
280
|
+
? { start_line: f.line_start, start_side: 'RIGHT' }
|
|
281
|
+
: {}),
|
|
282
|
+
body: commentBody(f, label),
|
|
283
|
+
}
|
|
284
|
+
})
|
|
285
|
+
// #941 / epic-876: self-authored downgrade is a first-class posting fact for guarantee 2.
|
|
286
|
+
// Orchestrators must record reviewPostedAs on the unit when downgraded — COMMENT + intended
|
|
287
|
+
// PASS + model read-back is the proof, not a GitHub APPROVE event (which GitHub forbids).
|
|
288
|
+
return {
|
|
289
|
+
event,
|
|
290
|
+
body,
|
|
291
|
+
comments,
|
|
292
|
+
verdict: derivedVerdict,
|
|
293
|
+
reviewPostedAs: downgraded ? 'comment-self-authored' : (event === 'COMMENT' ? 'comment' : 'review-event'),
|
|
294
|
+
selfAuthored: !!selfAuthored,
|
|
295
|
+
intendedEvent: intended,
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ---- CLI (gh side effects) ----
|
|
300
|
+
|
|
301
|
+
function gh(args, opts = {}) {
|
|
302
|
+
return execFileSync('gh', args, { encoding: 'utf8', ...opts }).trim()
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function parseArgs(argv) {
|
|
306
|
+
const f = {}
|
|
307
|
+
for (let i = 2; i < argv.length; i++) {
|
|
308
|
+
const a = argv[i]
|
|
309
|
+
if (a === '--dry-run') f.dryRun = true
|
|
310
|
+
else if (a === '--no-self-check') f.noSelfCheck = true
|
|
311
|
+
else if (a === '--file-issues') f.fileIssues = true
|
|
312
|
+
else if (a.startsWith('--')) f[a.slice(2)] = argv[++i]
|
|
313
|
+
}
|
|
314
|
+
return f
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Load the changed-file list from a saved `pr-context.mjs` JSON output (its own
|
|
318
|
+
// `--base <target>` invocation, piped to a file at review-establishment time — see
|
|
319
|
+
// SKILL.md § Review Workflow step 1). Never throws: a missing/unreadable/malformed
|
|
320
|
+
// file is exactly the "unavailable" case `anchorable` already handles by failing
|
|
321
|
+
// TOWARD POSTING, so this warns and returns `undefined` rather than crashing a run
|
|
322
|
+
// that would otherwise post cleanly.
|
|
323
|
+
function loadChangedFiles(prContextPath) {
|
|
324
|
+
if (!prContextPath) return undefined
|
|
325
|
+
try {
|
|
326
|
+
const ctx = JSON.parse(fs.readFileSync(prContextPath, 'utf8'))
|
|
327
|
+
if (Array.isArray(ctx.changedFiles)) return ctx.changedFiles
|
|
328
|
+
console.error(`warning: --pr-context ${prContextPath} has no changedFiles array; not validating comment paths against the diff`)
|
|
329
|
+
} catch (e) {
|
|
330
|
+
console.error(`warning: could not read --pr-context ${prContextPath} (${e.message}); not validating comment paths against the diff`)
|
|
331
|
+
}
|
|
332
|
+
return undefined
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Print, or actually open, the tracked issues these findings route to. Issue
|
|
336
|
+
// creation is an OUTWARD action, so it is gated behind --file-issues and DEFAULTS
|
|
337
|
+
// OFF: without the flag we only print what WOULD open. The loud out-of-scope
|
|
338
|
+
// warnings print in EITHER mode — a warning is free; opening an issue is not.
|
|
339
|
+
function routeIssues(findings, { pr, repo, fileIssues, ghImpl = gh }) {
|
|
340
|
+
for (const warning of outOfScopeCriticalWarnings(findings, { pr, repo })) console.error(warning)
|
|
341
|
+
const issues = planIssues(findings, { pr, repo })
|
|
342
|
+
if (!issues.length) return
|
|
343
|
+
if (!fileIssues) {
|
|
344
|
+
console.log(`\n--file-issues not set (default) — would open ${issues.length} issue(s); pass --file-issues to create them:`)
|
|
345
|
+
for (const issue of issues) console.log(` [${issue.kind}] ${issue.title} labels: ${issue.labels.join(',')}`)
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
for (const issue of issues) {
|
|
349
|
+
const args = ['issue', 'create', '--title', issue.title, '--body', issue.body]
|
|
350
|
+
if (repo) args.push('--repo', repo)
|
|
351
|
+
for (const label of issue.labels) args.push('--label', label)
|
|
352
|
+
const url = ghImpl(args)
|
|
353
|
+
console.log(`opened [${issue.kind}] ${url}`)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function main() {
|
|
358
|
+
const a = parseArgs(process.argv)
|
|
359
|
+
if (!a.findings || !a.pr) {
|
|
360
|
+
console.error('usage: post-review.mjs --findings <file.json> --pr <number> [--repo owner/name] [--pr-context <pr-context.json>] [--dry-run] [--no-self-check] [--file-issues]')
|
|
361
|
+
process.exit(2)
|
|
362
|
+
}
|
|
363
|
+
const data = JSON.parse(fs.readFileSync(a.findings, 'utf8'))
|
|
364
|
+
const errs = validateReviewDocument(data)
|
|
365
|
+
if (errs.length) {
|
|
366
|
+
console.error('Refusing to post — invalid findings:\n ' + errs.join('\n '))
|
|
367
|
+
process.exit(2)
|
|
368
|
+
}
|
|
369
|
+
// Read-tolerance: a finding still carrying a removed field (e.g. blocking) warns
|
|
370
|
+
// but never blocks posting; the derived value is used.
|
|
371
|
+
for (const w of reviewDocumentWarnings(data)) console.error(`warn: ${w}`)
|
|
372
|
+
let selfAuthored = false
|
|
373
|
+
if (!a.noSelfCheck) {
|
|
374
|
+
try {
|
|
375
|
+
const me = gh(['api', 'user', '--jq', '.login'])
|
|
376
|
+
const author = gh(['pr', 'view', String(a.pr), '--json', 'author', '--jq', '.author.login'])
|
|
377
|
+
selfAuthored = me === author
|
|
378
|
+
} catch {
|
|
379
|
+
console.error('warning: could not determine reviewer vs author; not downgrading. Pass --no-self-check to silence.')
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// --repo is optional for the review post and for opening issues (resolved from
|
|
383
|
+
// gh when actually posting); --dry-run resolves nothing (PR links fall back to #n).
|
|
384
|
+
const repo = a.repo || (a.dryRun ? undefined : gh(['repo', 'view', '--json', 'nameWithOwner', '--jq', '.nameWithOwner']))
|
|
385
|
+
// --pr-context reuses the changed-file list pr-context.mjs already computed at
|
|
386
|
+
// review-establishment time (its own JSON stdout, saved to a file) — this script
|
|
387
|
+
// never recomputes the diff itself. Omitted/unreadable => undefined => anchorable()
|
|
388
|
+
// falls back to the pre-#635 in_scope-only check (see its doc comment).
|
|
389
|
+
const changedFiles = loadChangedFiles(a['pr-context'])
|
|
390
|
+
const { verdict, reviewPostedAs, selfAuthored: postedSelf, intendedEvent, ...payload } = buildReviewPayload({ ...data, selfAuthored, pr: a.pr, repo, changedFiles })
|
|
391
|
+
if (a.dryRun) {
|
|
392
|
+
// --dry-run touches nothing: it never posts the review and never opens an
|
|
393
|
+
// issue, even with --file-issues — it prints what both would do.
|
|
394
|
+
console.log(JSON.stringify({ ...payload, verdict, reviewPostedAs, selfAuthored: postedSelf, intendedEvent }, null, 2))
|
|
395
|
+
console.log(`\nDerived verdict: ${verdict} (from ${data.findings.length} finding(s)); reviewPostedAs=${reviewPostedAs}`)
|
|
396
|
+
routeIssues(data.findings, { pr: a.pr, repo, fileIssues: false })
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
// Strip orchestrator-only fields before the GitHub reviews API body.
|
|
400
|
+
execFileSync('gh', ['api', `repos/${repo}/pulls/${a.pr}/reviews`, '--method', 'POST', '--input', '-'], { input: JSON.stringify(payload), stdio: ['pipe', 'inherit', 'inherit'] })
|
|
401
|
+
console.log(`posted ${payload.event} (verdict ${verdict}; reviewPostedAs=${reviewPostedAs})${postedSelf ? ' (self-authored → downgraded)' : ''} with ${payload.comments.length} inline comment(s)`)
|
|
402
|
+
routeIssues(data.findings, { pr: a.pr, repo, fileIssues: a.fileIssues })
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (isMainModule(import.meta.url)) main()
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// Establish the review SUBJECT correctly, every time: the net three-dot diff from
|
|
2
|
+
// the merge-base, the changed files, and the commit list (for the history scan).
|
|
3
|
+
// Pure git — language-agnostic. Pure parsers are unit-tested; git calls live in
|
|
4
|
+
// the CLI. Removes the classic diff-discipline footgun (two-dot vs three-dot, wrong
|
|
5
|
+
// base) so the reviewer reasons over the right change-set.
|
|
6
|
+
//
|
|
7
|
+
// node pr-context.mjs --base main [--head HEAD] [--head-ref <branch-name>]
|
|
8
|
+
// # prints JSON review context
|
|
9
|
+
//
|
|
10
|
+
// The range it emits is `<merge-base>...HEAD` (three-dot = what would merge), which
|
|
11
|
+
// is what review-method.md requires.
|
|
12
|
+
//
|
|
13
|
+
// PROMOTION SIGNAL. `promotion: true` marks a release/integration branch being merged
|
|
14
|
+
// into production (review as a release: fast-forward gate + readiness checklist, not a
|
|
15
|
+
// line-by-line re-review). It requires BOTH halves: the BASE is a production branch
|
|
16
|
+
// (main/master/production/prod) AND the HEAD is a recognized integration/release branch
|
|
17
|
+
// (dev, develop, release/*, hotfix/*, staging, staging/*). A base check alone is wrong
|
|
18
|
+
// in a trunk-based repo, where every feature PR targets `main` — those are feature PRs,
|
|
19
|
+
// not promotions (see issue #392).
|
|
20
|
+
//
|
|
21
|
+
// The head is classified by its BRANCH NAME. `--head` is a rev (default HEAD); the name
|
|
22
|
+
// is resolved from it (`git rev-parse --abbrev-ref`), or you may pass it explicitly with
|
|
23
|
+
// `--head-ref`. When the name cannot be resolved (a detached HEAD or a bare SHA — nothing
|
|
24
|
+
// to classify), the SAFE DEFAULT is `promotion: false`: treating a real promotion as a
|
|
25
|
+
// feature PR just runs the normal review; the reverse (issue #392's bug) mis-routes it.
|
|
26
|
+
|
|
27
|
+
import { execFileSync } from 'node:child_process'
|
|
28
|
+
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
29
|
+
|
|
30
|
+
// ---- pure core ----
|
|
31
|
+
|
|
32
|
+
export function threeDotRange(base, head = 'HEAD') {
|
|
33
|
+
return `${base}...${head}`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Parse `git diff --name-status <range>` → [{status, path}] (handles rename "R100\told\tnew").
|
|
37
|
+
export function parseNameStatus(text) {
|
|
38
|
+
return (text || '').split(/\r?\n/).filter(Boolean).map((line) => {
|
|
39
|
+
const parts = line.split('\t')
|
|
40
|
+
const status = parts[0]
|
|
41
|
+
const path = status.startsWith('R') || status.startsWith('C') ? parts[parts.length - 1] : parts[1]
|
|
42
|
+
return { status, path }
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Parse `git log --oneline <base>..HEAD` → [{sha, subject}] (two-dot here is correct:
|
|
47
|
+
// the commits unique to the PR head).
|
|
48
|
+
export function parseLog(text) {
|
|
49
|
+
return (text || '').split(/\r?\n/).filter(Boolean).map((line) => {
|
|
50
|
+
const sp = line.indexOf(' ')
|
|
51
|
+
return sp === -1 ? { sha: line, subject: '' } : { sha: line.slice(0, sp), subject: line.slice(sp + 1) }
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// A promotion (e.g. dev→main) must FAST-FORWARD: the base must be an ancestor of
|
|
56
|
+
// HEAD, i.e. no commits live on base that HEAD lacks. A non-empty list = base has
|
|
57
|
+
// commits (usually hotfixes) the promotion would REVERT — hold and back-merge first.
|
|
58
|
+
export function fastForwardable(commitsOnBaseNotInHead) {
|
|
59
|
+
return (commitsOnBaseNotInHead || []).length === 0
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// A promotion TARGET: the base is a production branch. Necessary but NOT sufficient —
|
|
63
|
+
// a promotion also needs an integration/release HEAD (see isPromotion). Keying on this
|
|
64
|
+
// alone flags every feature PR in a trunk-based repo as a promotion (issue #392).
|
|
65
|
+
export function isPromotionTarget(baseRef) {
|
|
66
|
+
return /(^|\/)(main|master|production|prod)$/.test(baseRef || '')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Conventional remote names recognized in the SHORT remote-tracking form
|
|
70
|
+
// (`origin/dev`). The full `refs/remotes/<remote>/…` form strips any remote segment
|
|
71
|
+
// because the namespace is explicit and unambiguous; the short form is ambiguous with
|
|
72
|
+
// an ordinary namespaced branch (`jsmith/dev`), so we only strip a KNOWN remote — never
|
|
73
|
+
// an arbitrary first segment. This is normalization, not part of the integration
|
|
74
|
+
// allowlist.
|
|
75
|
+
const KNOWN_REMOTES = ['origin', 'upstream']
|
|
76
|
+
|
|
77
|
+
// Normalize a head ref to a bare branch name by stripping ONLY known ref prefixes, so
|
|
78
|
+
// the integration/release name (if any) lands at the START of the string. Handles the
|
|
79
|
+
// three git ref shapes: `refs/heads/<name>`, `refs/remotes/<remote>/<name>`, and the
|
|
80
|
+
// short remote-tracking `<known-remote>/<name>`. It NEVER strips an arbitrary leading
|
|
81
|
+
// path segment — that is exactly the any-prefix hole (issue #392) this closes: a
|
|
82
|
+
// namespaced feature branch like `feature/release/notes` or `jsmith/dev` keeps its
|
|
83
|
+
// namespace and so cannot masquerade as an integration branch.
|
|
84
|
+
export function normalizeHeadName(headRef) {
|
|
85
|
+
const name = headRef || ''
|
|
86
|
+
if (name.startsWith('refs/heads/')) return name.slice('refs/heads/'.length)
|
|
87
|
+
if (name.startsWith('refs/remotes/')) {
|
|
88
|
+
// drop `refs/remotes/` + exactly ONE remote segment
|
|
89
|
+
return name.slice('refs/remotes/'.length).replace(/^[^/]+\//, '')
|
|
90
|
+
}
|
|
91
|
+
for (const remote of KNOWN_REMOTES) {
|
|
92
|
+
if (name.startsWith(`${remote}/`)) return name.slice(remote.length + 1)
|
|
93
|
+
}
|
|
94
|
+
return name
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// A promotion HEAD: an integration or release branch, by NAME. A small, documented
|
|
98
|
+
// allowlist of integration/release shapes — dev/develop, staging, and release/hotfix/
|
|
99
|
+
// staging/*. After normalizing away known ref prefixes (`refs/heads/`, `refs/remotes/`,
|
|
100
|
+
// and short `origin/`-style remotes), the integration name is matched ANCHORED AT THE
|
|
101
|
+
// START (`^`): the exact whole-name shapes (`dev`/`develop`/`staging`) and the path
|
|
102
|
+
// shapes (`release/`/`hotfix/`/`staging/`). The name must OPEN the branch, never be
|
|
103
|
+
// buried under a namespace — so `origin/dev` and `refs/heads/develop` resolve, but a
|
|
104
|
+
// namespaced feature branch like `feature/release/notes` or `jsmith/dev` does not.
|
|
105
|
+
// Anything else (the common case: an ordinary feature branch, or an unresolvable bare
|
|
106
|
+
// SHA) → false.
|
|
107
|
+
export function isIntegrationBranch(headRef) {
|
|
108
|
+
const name = normalizeHeadName(headRef)
|
|
109
|
+
// exact whole-name integration branches: dev, develop, staging
|
|
110
|
+
if (/^(dev|develop|staging)$/.test(name)) return true
|
|
111
|
+
// release-family branches with a version/name suffix, name at the START: release/*, hotfix/*, staging/*
|
|
112
|
+
if (/^(release|hotfix|staging)\//.test(name)) return true
|
|
113
|
+
return false
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// A PROMOTION = a production BASE receiving an integration/release HEAD. Both halves
|
|
117
|
+
// required. Review it as a release (fast-forward gate + readiness checklist), not a
|
|
118
|
+
// line-by-line re-review of feature code already merged & green on the integration
|
|
119
|
+
// branch. An unresolvable head (bare SHA → not integration-shaped) yields false, the
|
|
120
|
+
// documented safe default.
|
|
121
|
+
export function isPromotion(baseRef, headRef) {
|
|
122
|
+
return isPromotionTarget(baseRef) && isIntegrationBranch(headRef)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// The reviewer-facing note for the emitted context. Pure so the promotion branches are
|
|
126
|
+
// unit-testable without git. The PROMOTION + NOT fast-forwardable hold-and-back-merge
|
|
127
|
+
// guidance is unchanged — only the trigger (isPromotion) is corrected.
|
|
128
|
+
export function reviewNote({ promotion, fastForwardable: ff, base, head, commitsOnBaseNotInHead }) {
|
|
129
|
+
if (promotion && !ff) {
|
|
130
|
+
const n = (commitsOnBaseNotInHead || []).length
|
|
131
|
+
return `PROMOTION + NOT fast-forwardable: ${base} has ${n} commit(s) not in ${head} (likely hotfixes). HOLD — back-merge ${base} → ${head} first; promoting would revert them. Then apply the release-readiness checklist (migrations/secrets/flag-defaults/version/rollback).`
|
|
132
|
+
}
|
|
133
|
+
if (promotion) {
|
|
134
|
+
return 'PROMOTION (fast-forwardable): do not re-review feature code already merged & green on the integration branch. Run only the release-readiness checklist on the content: migrations/backfills, secrets & config, feature-flag defaults, version/changelog, rollback.'
|
|
135
|
+
}
|
|
136
|
+
return 'Review the NET diff (range above), not commit-by-commit. Scope findings to what this range introduces; pre-existing issues (present at mergeBase) are notes, not blockers. Scan `commits` for secrets added-then-removed / unsafe intermediate states.'
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ---- CLI ----
|
|
140
|
+
|
|
141
|
+
function git(args) {
|
|
142
|
+
return execFileSync('git', args, { encoding: 'utf8' }).trim()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function parseArgs(argv) {
|
|
146
|
+
const f = { head: 'HEAD' }
|
|
147
|
+
for (let i = 2; i < argv.length; i++) {
|
|
148
|
+
const a = argv[i]
|
|
149
|
+
if (a.startsWith('--')) f[a.slice(2)] = argv[++i]
|
|
150
|
+
}
|
|
151
|
+
return f
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Resolve the head's symbolic BRANCH NAME (what isIntegrationBranch classifies), or null
|
|
155
|
+
// when there is nothing to classify. Prefer an explicit --head-ref; otherwise ask git for
|
|
156
|
+
// the symbolic name of --head. A detached HEAD (`git rev-parse --abbrev-ref` → "HEAD") or a
|
|
157
|
+
// bare SHA (echoed back unchanged) has no branch name → null → promotion defaults false.
|
|
158
|
+
function resolveHeadName(a) {
|
|
159
|
+
if (a['head-ref']) return a['head-ref']
|
|
160
|
+
const head = a.head || 'HEAD'
|
|
161
|
+
let name
|
|
162
|
+
try {
|
|
163
|
+
name = git(['rev-parse', '--abbrev-ref', head])
|
|
164
|
+
} catch {
|
|
165
|
+
return null
|
|
166
|
+
}
|
|
167
|
+
if (!name || name === 'HEAD' || /^[0-9a-f]{7,40}$/i.test(name)) return null
|
|
168
|
+
return name
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function main() {
|
|
172
|
+
const a = parseArgs(process.argv)
|
|
173
|
+
if (!a.base) {
|
|
174
|
+
console.error('usage: pr-context.mjs --base <target-branch-or-ref> [--head HEAD] [--head-ref <branch-name>]')
|
|
175
|
+
process.exit(2)
|
|
176
|
+
}
|
|
177
|
+
const base = git(['merge-base', a.base, a.head])
|
|
178
|
+
const range = threeDotRange(base, a.head)
|
|
179
|
+
const files = parseNameStatus(git(['diff', '--name-status', range]))
|
|
180
|
+
const commits = parseLog(git(['log', '--oneline', `${base}..${a.head}`]))
|
|
181
|
+
const diffstat = git(['diff', '--shortstat', range])
|
|
182
|
+
// commits on the base that HEAD lacks (in base, not in head) — the fast-forward / drift signal
|
|
183
|
+
const commitsOnBaseNotInHead = parseLog(git(['log', '--oneline', `${a.head}..${a.base}`]))
|
|
184
|
+
const ff = fastForwardable(commitsOnBaseNotInHead)
|
|
185
|
+
// Promotion requires BOTH a production base AND an integration/release head — a base
|
|
186
|
+
// check alone flags every feature→main PR (issue #392). headRef is the resolved head
|
|
187
|
+
// branch name (null when unresolvable → not integration-shaped → promotion false).
|
|
188
|
+
const headRef = resolveHeadName(a)
|
|
189
|
+
const promotion = isPromotion(a.base, headRef)
|
|
190
|
+
const ctx = {
|
|
191
|
+
targetRef: a.base,
|
|
192
|
+
headRef,
|
|
193
|
+
mergeBase: base,
|
|
194
|
+
range,
|
|
195
|
+
diffCommand: `git diff ${range}`,
|
|
196
|
+
changedFiles: files,
|
|
197
|
+
commits,
|
|
198
|
+
diffstat,
|
|
199
|
+
promotion,
|
|
200
|
+
fastForwardable: ff,
|
|
201
|
+
commitsOnBaseNotInHead,
|
|
202
|
+
note: reviewNote({ promotion, fastForwardable: ff, base: a.base, head: a.head, commitsOnBaseNotInHead }),
|
|
203
|
+
}
|
|
204
|
+
console.log(JSON.stringify(ctx, null, 2))
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (isMainModule(import.meta.url)) main()
|