@1aboveio/skills 0.0.0-stage → 1.0.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/README.md +30 -2
- package/bin/cli.mjs +14 -0
- package/bin/select-core.mjs +172 -0
- package/package.json +146 -4
- package/skills/app-dev/airflow/LICENSE +3 -0
- package/skills/app-dev/airflow/SKILL.md +199 -0
- package/skills/app-dev/airflow/release-check.mjs +93 -0
- package/skills/app-dev/android-foundation/LICENSE +3 -0
- package/skills/app-dev/android-foundation/SKILL.md +192 -0
- package/skills/app-dev/android-foundation/assets/android-ci-job.yml +217 -0
- package/skills/app-dev/android-foundation/references/ci.md +87 -0
- package/skills/app-dev/android-foundation/references/gotchas.md +221 -0
- package/skills/app-dev/android-foundation/references/setup.md +164 -0
- package/skills/app-dev/android-foundation/release-check.mjs +93 -0
- package/skills/app-dev/android-foundation/scripts/device-run.sh +176 -0
- package/skills/app-dev/android-foundation/scripts/device-run.test.mjs +172 -0
- package/skills/app-dev/app-debug/LICENSE +3 -0
- package/skills/app-dev/app-debug/SKILL.md +110 -0
- package/skills/app-dev/app-debug/references/common-errors.md +128 -0
- package/skills/app-dev/app-debug/release-check.mjs +93 -0
- package/skills/app-dev/better-auth/LICENSE +3 -0
- package/skills/app-dev/better-auth/SKILL.md +598 -0
- package/skills/app-dev/better-auth/references/feishu-api.md +270 -0
- package/skills/app-dev/better-auth/release-check.mjs +93 -0
- package/skills/app-dev/data-catalog/LICENSE +3 -0
- package/skills/app-dev/data-catalog/SKILL.md +204 -0
- package/skills/app-dev/data-catalog/agents/openai.yaml +3 -0
- package/skills/app-dev/data-catalog/references/data-catalog-conventions.md +75 -0
- package/skills/app-dev/data-catalog/references/field-definitions.md +116 -0
- package/skills/app-dev/data-catalog/references/format.md +121 -0
- package/skills/app-dev/data-catalog/release-check.mjs +93 -0
- package/skills/app-dev/data-catalog/scripts/data-catalog.test.mjs +36 -0
- package/skills/app-dev/data-catalog/scripts/data_catalog.py +1392 -0
- package/skills/app-dev/data-catalog/scripts/persist-conventions-core.mjs +117 -0
- package/skills/app-dev/data-catalog/scripts/persist-conventions.mjs +17 -0
- package/skills/app-dev/data-catalog/scripts/test_data_catalog.py +571 -0
- package/skills/app-dev/ios-foundation/LICENSE +3 -0
- package/skills/app-dev/ios-foundation/SKILL.md +184 -0
- package/skills/app-dev/ios-foundation/references/setup.md +243 -0
- package/skills/app-dev/ios-foundation/release-check.mjs +93 -0
- package/skills/app-dev/monorepo/LICENSE +3 -0
- package/skills/app-dev/monorepo/SKILL.md +462 -0
- package/skills/app-dev/monorepo/release-check.mjs +93 -0
- package/skills/app-dev/nextjs-fullstack/LICENSE +3 -0
- package/skills/app-dev/nextjs-fullstack/SKILL.md +211 -0
- package/skills/app-dev/nextjs-fullstack/conventions.md +318 -0
- package/skills/app-dev/nextjs-fullstack/frontend-conventions.md +61 -0
- package/skills/app-dev/nextjs-fullstack/nextjs16.md +287 -0
- package/skills/app-dev/nextjs-fullstack/release-check.mjs +93 -0
- package/skills/app-dev/nextjs-fullstack/server-actions.md +409 -0
- package/skills/app-dev/prisma-setup/LICENSE +3 -0
- package/skills/app-dev/prisma-setup/SKILL.md +181 -0
- package/skills/app-dev/prisma-setup/nextjs.md +258 -0
- package/skills/app-dev/prisma-setup/release-check.mjs +93 -0
- package/skills/app-dev/prisma-setup/turborepo.md +301 -0
- package/skills/app-dev/pyspark/LICENSE +3 -0
- package/skills/app-dev/pyspark/SKILL.md +127 -0
- package/skills/app-dev/pyspark/assets/templates/etl.py +748 -0
- package/skills/app-dev/pyspark/assets/templates/utils/__init__.py +1 -0
- package/skills/app-dev/pyspark/assets/templates/utils/hudi_metadata.py +14 -0
- package/skills/app-dev/pyspark/references/diagnosis-and-profiling.md +169 -0
- package/skills/app-dev/pyspark/references/etl-contract.md +129 -0
- package/skills/app-dev/pyspark/references/production-validation.md +131 -0
- package/skills/app-dev/pyspark/references/reconciliation.md +38 -0
- package/skills/app-dev/pyspark/references/transformation-design.md +190 -0
- package/skills/app-dev/pyspark/references/velocity-feature-calculation.md +193 -0
- package/skills/app-dev/pyspark/release-check.mjs +93 -0
- package/skills/app-dev/pyspark/scripts/spark_eventlog_summary.py +223 -0
- package/skills/app-dev/python-backend/LICENSE +3 -0
- package/skills/app-dev/python-backend/SKILL.md +325 -0
- package/skills/app-dev/python-backend/assets/uv-install.sh +48 -0
- package/skills/app-dev/python-backend/references/uv-installs.md +83 -0
- package/skills/app-dev/python-backend/release-check.mjs +93 -0
- package/skills/app-dev/shadcn/LICENSE +3 -0
- package/skills/app-dev/shadcn/SKILL.md +120 -0
- package/skills/app-dev/shadcn/assets/shadcn-small.png +0 -0
- package/skills/app-dev/shadcn/assets/shadcn.png +0 -0
- package/skills/app-dev/shadcn/cli.md +411 -0
- package/skills/app-dev/shadcn/customization.md +224 -0
- package/skills/app-dev/shadcn/mcp.md +101 -0
- package/skills/app-dev/shadcn/release-check.mjs +93 -0
- package/skills/app-dev/shadcn/rules/base-vs-radix.md +323 -0
- package/skills/app-dev/shadcn/rules/component-selection.md +67 -0
- package/skills/app-dev/shadcn/rules/composition.md +195 -0
- package/skills/app-dev/shadcn/rules/data-table.md +201 -0
- package/skills/app-dev/shadcn/rules/forms.md +255 -0
- package/skills/app-dev/shadcn/rules/icons.md +103 -0
- package/skills/app-dev/shadcn/rules/styling.md +167 -0
- package/skills/app-dev/zod-v4/LICENSE +3 -0
- package/skills/app-dev/zod-v4/SKILL.md +286 -0
- package/skills/app-dev/zod-v4/release-check.mjs +93 -0
- package/skills/cicd/cloud-build/LICENSE +3 -0
- package/skills/cicd/cloud-build/SKILL.md +707 -0
- package/skills/cicd/cloud-build/release-check.mjs +93 -0
- package/skills/cicd/cloud-debug/LICENSE +3 -0
- package/skills/cicd/cloud-debug/SKILL.md +315 -0
- package/skills/cicd/cloud-debug/references/build-failures.md +238 -0
- package/skills/cicd/cloud-debug/references/deploy-failures.md +376 -0
- package/skills/cicd/cloud-debug/references/pipeline-failures.md +378 -0
- package/skills/cicd/cloud-debug/release-check.mjs +93 -0
- package/skills/cicd/cloud-deploy/LICENSE +3 -0
- package/skills/cicd/cloud-deploy/SKILL.md +230 -0
- package/skills/cicd/cloud-deploy/references/config-templates.md +257 -0
- package/skills/cicd/cloud-deploy/release-check.mjs +93 -0
- package/skills/cicd/docker/LICENSE +3 -0
- package/skills/cicd/docker/SKILL.md +151 -0
- package/skills/cicd/docker/release-check.mjs +93 -0
- package/skills/cicd/github-actions/LICENSE +3 -0
- package/skills/cicd/github-actions/SKILL.md +111 -0
- package/skills/cicd/github-actions/references/audit-profile-optimize.md +150 -0
- package/skills/cicd/github-actions/references/ci-case-studies.md +141 -0
- package/skills/cicd/github-actions/references/conditional-ci.md +214 -0
- package/skills/cicd/github-actions/references/debugging.md +13 -0
- package/skills/cicd/github-actions/references/pipeline.md +87 -0
- package/skills/cicd/github-actions/references/post-deploy-and-publishing.md +33 -0
- package/skills/cicd/github-actions/references/runners.md +253 -0
- package/skills/cicd/github-actions/references/security.md +29 -0
- package/skills/cicd/github-actions/references/speed-and-audit.md +115 -0
- package/skills/cicd/github-actions/references/two-pass-reading.md +92 -0
- package/skills/cicd/github-actions/release-check.mjs +93 -0
- package/skills/cicd/github-actions/scripts/ci-run-audit-core.mjs +612 -0
- package/skills/cicd/github-actions/scripts/ci-run-audit.mjs +4 -0
- package/skills/cicd/github-actions/scripts/ci-run-audit.test.mjs +637 -0
- package/skills/cicd/github-flow/LICENSE +3 -0
- package/skills/cicd/github-flow/SKILL.md +123 -0
- package/skills/cicd/github-flow/references/ci-conventions.md +14 -0
- package/skills/cicd/github-flow/release-check.mjs +93 -0
- package/skills/cicd/github-flow/scripts/flow-core.mjs +105 -0
- package/skills/cicd/github-flow/scripts/flow.mjs +81 -0
- package/skills/cicd/github-flow/scripts/flow.test.mjs +114 -0
- package/skills/cicd/github-flow/scripts/persist-conventions-core.mjs +117 -0
- package/skills/cicd/github-flow/scripts/persist-conventions.mjs +49 -0
- package/skills/cicd/github-flow/scripts/persist-conventions.test.mjs +108 -0
- package/skills/cicd/google-cloud/LICENSE +3 -0
- package/skills/cicd/google-cloud/SKILL.md +119 -0
- package/skills/cicd/google-cloud/references/gcs.md +469 -0
- package/skills/cicd/google-cloud/references/iam.md +451 -0
- package/skills/cicd/google-cloud/references/project.md +349 -0
- package/skills/cicd/google-cloud/references/secrets.md +336 -0
- package/skills/cicd/google-cloud/references/vpc.md +312 -0
- package/skills/cicd/google-cloud/release-check.mjs +93 -0
- package/skills/cicd/google-cloud/scripts/create-sa.sh +36 -0
- package/skills/cicd/google-cloud/scripts/gcp-config.sh +31 -0
- package/skills/cicd/google-cloud/scripts/grant-iap.sh +41 -0
- package/skills/cicd/google-cloud/scripts/setup-secrets.sh +48 -0
- package/skills/cicd/mergify/LICENSE +3 -0
- package/skills/cicd/mergify/SKILL.md +102 -0
- package/skills/cicd/mergify/assets/templates/mergify.yml +215 -0
- package/skills/cicd/mergify/assets/templates/ruleset.json +46 -0
- package/skills/cicd/mergify/references/branch-protection.md +282 -0
- package/skills/cicd/mergify/references/configuration.md +274 -0
- package/skills/cicd/mergify/references/diagnosis.md +109 -0
- package/skills/cicd/mergify/references/stacks.md +84 -0
- package/skills/cicd/mergify/references/traps.md +119 -0
- package/skills/cicd/mergify/references/watch-contract.md +274 -0
- package/skills/cicd/mergify/release-check.mjs +93 -0
- package/skills/cicd/mergify/scripts/audit-core.mjs +208 -0
- package/skills/cicd/mergify/scripts/audit.mjs +4 -0
- package/skills/cicd/mergify/scripts/audit.test.mjs +201 -0
- package/skills/cicd/mergify/scripts/entrypoints.test.mjs +106 -0
- package/skills/cicd/mergify/scripts/pr-620-mergify-comments.fixture.json +30 -0
- package/skills/cicd/mergify/scripts/stack-push-core.mjs +258 -0
- package/skills/cicd/mergify/scripts/stack-push.mjs +4 -0
- package/skills/cicd/mergify/scripts/stack-push.test.mjs +233 -0
- package/skills/cicd/mergify/scripts/watch-pr-delivery-core.mjs +1065 -0
- package/skills/cicd/mergify/scripts/watch-pr-delivery.mjs +4 -0
- package/skills/cicd/mergify/scripts/watch-pr-delivery.test.mjs +1826 -0
- package/skills/cicd/podman/LICENSE +3 -0
- package/skills/cicd/podman/SKILL.md +71 -0
- package/skills/cicd/podman/agents/openai.yaml +4 -0
- package/skills/cicd/podman/assets/templates/podman-compose-socket-directory.yml +6 -0
- package/skills/cicd/podman/assets/templates/podman-service-override.conf +3 -0
- package/skills/cicd/podman/references/compose-compatibility.md +70 -0
- package/skills/cicd/podman/references/networking-and-ports.md +74 -0
- package/skills/cicd/podman/references/rootless-services-and-sockets.md +156 -0
- package/skills/cicd/podman/references/troubleshooting.md +98 -0
- package/skills/cicd/podman/release-check.mjs +93 -0
- package/skills/content/article-review/LICENSE +3 -0
- package/skills/content/article-review/SKILL.md +66 -0
- package/skills/content/article-review/references/report-format.md +68 -0
- package/skills/content/article-review/references/template-guide.md +79 -0
- package/skills/content/article-review/references/template-wechat-payments.md +125 -0
- package/skills/content/article-review/release-check.mjs +93 -0
- package/skills/content/article-to-image/LICENSE +3 -0
- package/skills/content/article-to-image/SKILL.md +91 -0
- package/skills/content/article-to-image/references/formats.md +53 -0
- package/skills/content/article-to-image/release-check.mjs +93 -0
- package/skills/content/flowchart-svg/LICENSE +3 -0
- package/skills/content/flowchart-svg/SKILL.md +43 -0
- package/skills/content/flowchart-svg/release-check.mjs +93 -0
- package/skills/content/flowchart-svg/scripts/export_png.sh +8 -0
- package/skills/content/image-gen/LICENSE +3 -0
- package/skills/content/image-gen/SKILL.md +192 -0
- package/skills/content/image-gen/release-check.mjs +93 -0
- package/skills/content/image-gen/scripts/credentials.mjs +217 -0
- package/skills/content/image-gen/scripts/credentials.test.mjs +212 -0
- package/skills/content/image-gen/scripts/generate.mjs +240 -0
- package/skills/content/image-gen/scripts/options.mjs +293 -0
- package/skills/content/image-gen/scripts/options.test.mjs +245 -0
- package/skills/content/mermaid/LICENSE +3 -0
- package/skills/content/mermaid/SKILL.md +635 -0
- package/skills/content/mermaid/release-check.mjs +93 -0
- package/skills/content/wechat-article-writer/LICENSE +3 -0
- package/skills/content/wechat-article-writer/SKILL.md +92 -0
- package/skills/content/wechat-article-writer/references/style-selector.md +133 -0
- package/skills/content/wechat-article-writer/references/writing-rules.md +152 -0
- package/skills/content/wechat-article-writer/release-check.mjs +93 -0
- package/skills/content/wechat-article-writer/templates/wechat-comparison.md +52 -0
- package/skills/content/wechat-article-writer/templates/wechat-howto.md +51 -0
- package/skills/content/wechat-article-writer/templates/wechat-insight.md +50 -0
- package/skills/content/wechat-article-writer/templates/wechat-pitfall.md +52 -0
- package/skills/content/wechat-publisher/LICENSE +3 -0
- package/skills/content/wechat-publisher/SKILL.md +171 -0
- package/skills/content/wechat-publisher/references/api.md +105 -0
- package/skills/content/wechat-publisher/references/layout-rules.md +57 -0
- package/skills/content/wechat-publisher/references/wechat-safe-template.html +86 -0
- package/skills/content/wechat-publisher/release-check.mjs +93 -0
- package/skills/content/wechat-publisher/scripts/md_to_wechat_html.py +194 -0
- package/skills/content/wechat-publisher/scripts/publish.py +239 -0
- package/skills/content/wechat-search/LICENSE +3 -0
- package/skills/content/wechat-search/SKILL.md +211 -0
- package/skills/content/wechat-search/release-check.mjs +93 -0
- package/skills/content/wechat-search/scripts/start-chrome.sh +83 -0
- package/skills/content/xai-search/LICENSE +3 -0
- package/skills/content/xai-search/SKILL.md +79 -0
- package/skills/content/xai-search/release-check.mjs +93 -0
- package/skills/content/xai-search/scripts/xai_search.sh +187 -0
- package/skills/content/xai-search/tests/smoke_test.sh +36 -0
- package/skills/core/distill-writing/LICENSE +3 -0
- package/skills/core/distill-writing/SKILL.md +74 -0
- package/skills/core/distill-writing/references/extraction.md +95 -0
- package/skills/core/distill-writing/references/updating.md +46 -0
- package/skills/core/distill-writing/release-check.mjs +93 -0
- package/skills/core/doc-to-md/LICENSE +3 -0
- package/skills/core/doc-to-md/SKILL.md +78 -0
- package/skills/core/doc-to-md/release-check.mjs +93 -0
- package/skills/core/doc-to-md/scripts/convert.py +157 -0
- package/skills/core/editorial-review/LICENSE +3 -0
- package/skills/core/editorial-review/SKILL.md +79 -0
- package/skills/core/editorial-review/references/checklist.md +94 -0
- package/skills/core/editorial-review/release-check.mjs +93 -0
- package/skills/core/house-style/LICENSE +3 -0
- package/skills/core/house-style/SKILL.md +33 -0
- package/skills/core/house-style/references/defaults.md +66 -0
- package/skills/core/house-style/references/rules.md +267 -0
- package/skills/core/house-style/references/sources.md +27 -0
- package/skills/core/house-style/references/style-en.md +131 -0
- package/skills/core/house-style/references/style-zh.md +141 -0
- package/skills/core/house-style/references/terminology-zh.md +223 -0
- package/skills/core/house-style/release-check.mjs +93 -0
- package/skills/core/house-style/templates/brd.md +103 -0
- package/skills/core/house-style/templates/commercial-proposal.md +93 -0
- package/skills/core/house-style/templates/compliance-policy.md +109 -0
- package/skills/core/house-style/templates/gap-analysis.md +105 -0
- package/skills/core/house-style/templates/general.md +63 -0
- package/skills/core/house-style/templates/integration-guide.md +98 -0
- package/skills/core/house-style/templates/prd.md +81 -0
- package/skills/core/house-style/templates/risk-operations-procedure.md +94 -0
- package/skills/core/house-style/templates/technical-spec.md +96 -0
- package/skills/core/polish-cn/LICENSE +3 -0
- package/skills/core/polish-cn/SKILL.md +102 -0
- package/skills/core/polish-cn/references/brief.md +93 -0
- package/skills/core/polish-cn/references/models.md +76 -0
- package/skills/core/polish-cn/release-check.mjs +93 -0
- package/skills/core/polish-cn/scripts/polish-cn.test.mjs +244 -0
- package/skills/core/polish-cn/scripts/polish_blocks.py +489 -0
- package/skills/core/reg-parser/LICENSE +3 -0
- package/skills/core/reg-parser/SKILL.md +212 -0
- package/skills/core/reg-parser/references/checklist-schema.md +223 -0
- package/skills/core/reg-parser/references/checklists/hk-amlo-checklist.json +1306 -0
- package/skills/core/reg-parser/references/checklists/hk-amlo-checklist.md +469 -0
- package/skills/core/reg-parser/references/checklists/hk-fi-corporate-onboarding-cdd-checklist.json +634 -0
- package/skills/core/reg-parser/references/checklists/hk-fi-corporate-onboarding-cdd-checklist.md +233 -0
- package/skills/core/reg-parser/references/checklists/hk-mso-guideline-checklist.json +2194 -0
- package/skills/core/reg-parser/references/checklists/hk-mso-guideline-checklist.md +771 -0
- package/skills/core/reg-parser/references/checklists/kyc-aml-checklist.md +258 -0
- package/skills/core/reg-parser/references/checklists/visa-vars-checklist.md +1058 -0
- package/skills/core/reg-parser/references/mandates/hk-amlo-cap615.md +389 -0
- package/skills/core/reg-parser/references/mandates/visa-vars.md +205 -0
- package/skills/core/reg-parser/release-check.mjs +93 -0
- package/skills/core/reg-parser/scripts/compare_checklist.py +230 -0
- package/skills/core/reg-parser/scripts/dump_checklist.py +361 -0
- package/skills/core/reg-parser/scripts/preprocess-mandate.sh +187 -0
- package/skills/core/reg-parser/scripts/reg-parser.test.mjs +165 -0
- package/skills/core/reg-parser/scripts/test_quarantine.sh +105 -0
- package/skills/core/reg-parser/scripts/validate_checklist.py +563 -0
- package/skills/core/reg-parser/scripts/validate_mandate.py +273 -0
- package/skills/core/terminology/LICENSE +3 -0
- package/skills/core/terminology/SKILL.md +62 -0
- package/skills/core/terminology/references/format.md +50 -0
- package/skills/core/terminology/references/init.md +32 -0
- package/skills/core/terminology/release-check.mjs +93 -0
- package/skills/core/write-doc/LICENSE +3 -0
- package/skills/core/write-doc/SKILL.md +141 -0
- package/skills/core/write-doc/references/bilingual.md +60 -0
- package/skills/core/write-doc/release-check.mjs +93 -0
- package/skills/engineering/better-review/LICENSE +3 -0
- package/skills/engineering/better-review/SKILL.md +249 -0
- package/skills/engineering/better-review/agents/openai.yaml +9 -0
- package/skills/engineering/better-review/references/domain-hazards.md +53 -0
- package/skills/engineering/better-review/references/passes.md +147 -0
- package/skills/engineering/better-review/references/result.md +151 -0
- package/skills/engineering/better-review/release-check.mjs +93 -0
- package/skills/engineering/better-review/scripts/pr-context.mjs +212 -0
- package/skills/engineering/better-review/scripts/pr-context.test.mjs +168 -0
- package/skills/engineering/better-review/scripts/rereview-scope.mjs +503 -0
- package/skills/engineering/better-review/scripts/rereview-scope.test.mjs +587 -0
- package/skills/engineering/better-review/scripts/scan-diff.mjs +365 -0
- package/skills/engineering/better-review/scripts/scan-diff.test.mjs +414 -0
- package/skills/engineering/better-review/scripts/vendor-sync.test.mjs +96 -0
- package/skills/engineering/e2e-test/LICENSE +3 -0
- package/skills/engineering/e2e-test/SKILL.md +98 -0
- package/skills/engineering/e2e-test/assets/ci-gates.cloudbuild.yaml +24 -0
- package/skills/engineering/e2e-test/references/authoring/auth-flows.md +159 -0
- package/skills/engineering/e2e-test/references/authoring/playwright-config.md +73 -0
- package/skills/engineering/e2e-test/references/authoring/playwright-patterns.md +144 -0
- package/skills/engineering/e2e-test/references/ci-integration.md +25 -0
- package/skills/engineering/e2e-test/references/ci-playwright-container.md +290 -0
- package/skills/engineering/e2e-test/references/presentation-sweep.md +104 -0
- package/skills/engineering/e2e-test/references/reviewing.md +41 -0
- package/skills/engineering/e2e-test/references/route-manifest.md +51 -0
- package/skills/engineering/e2e-test/release-check.mjs +93 -0
- package/skills/engineering/e2e-test/scripts/detect-routes-nextjs.mjs +200 -0
- package/skills/engineering/e2e-test/scripts/presentation-checks.mjs +171 -0
- package/skills/engineering/e2e-test/scripts/presentation-checks.test.mjs +106 -0
- package/skills/engineering/e2e-test/scripts/presentation-perceivability.mjs +179 -0
- package/skills/engineering/e2e-test/scripts/presentation-perceivability.test.mjs +117 -0
- package/skills/engineering/e2e-test/scripts/presentation-reachability.mjs +154 -0
- package/skills/engineering/e2e-test/scripts/presentation-reachability.test.mjs +69 -0
- package/skills/engineering/e2e-test/scripts/presentation-render-health.mjs +141 -0
- package/skills/engineering/e2e-test/scripts/presentation-render-health.test.mjs +98 -0
- package/skills/engineering/e2e-test/scripts/presentation-sweep.mjs +147 -0
- package/skills/engineering/e2e-test/scripts/presentation-sweep.test.mjs +71 -0
- package/skills/engineering/e2e-test/scripts/presentation-temporal.mjs +127 -0
- package/skills/engineering/e2e-test/scripts/presentation-temporal.test.mjs +59 -0
- package/skills/engineering/e2e-test/scripts/project-route-manifest.mjs +82 -0
- package/skills/engineering/e2e-test/scripts/project-route-manifest.test.mjs +76 -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 +11 -0
- package/skills/engineering/engineering-runtime/agents/openai.yaml +6 -0
- package/skills/engineering/engineering-runtime/coherence/workflow.json +597 -0
- package/skills/engineering/engineering-runtime/engineering-runtime.test.mjs +68 -0
- package/skills/engineering/engineering-runtime/release-check.mjs +93 -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/invocation-policy.mjs +187 -0
- package/skills/engineering/engineering-runtime/scripts/invocation-policy.test.mjs +95 -0
- package/skills/engineering/engineering-runtime/scripts/main-module.mjs +79 -0
- package/skills/engineering/engineering-runtime/scripts/main-module.test.mjs +288 -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/required-check-plan.test.mjs +96 -0
- package/skills/engineering/engineering-runtime/scripts/review-completion.mjs +56 -0
- package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +595 -0
- package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +189 -0
- package/skills/engineering/engineering-runtime/scripts/yaml-mapping.mjs +18 -0
- package/skills/engineering/ensure-coverage/LICENSE +3 -0
- package/skills/engineering/ensure-coverage/SKILL.md +129 -0
- package/skills/engineering/ensure-coverage/references/breadth/coverage-ledger.md +92 -0
- package/skills/engineering/ensure-coverage/references/breadth/inventory-contract.md +85 -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 +19 -0
- package/skills/engineering/ensure-coverage/references/depth/grading.md +35 -0
- package/skills/engineering/ensure-coverage/references/depth/mock-policy.md +57 -0
- package/skills/engineering/ensure-coverage/references/depth/test-smells.md +39 -0
- package/skills/engineering/ensure-coverage/references/enforcement/ci-contract.md +44 -0
- package/skills/engineering/ensure-coverage/references/examples/coverage-ledger.md +110 -0
- package/skills/engineering/ensure-coverage/references/examples/refund-flow.md +35 -0
- package/skills/engineering/ensure-coverage/references/presentation/axis.md +64 -0
- package/skills/engineering/ensure-coverage/references/process/audit-mode.md +27 -0
- package/skills/engineering/ensure-coverage/references/process/output-template.md +139 -0
- package/skills/engineering/ensure-coverage/references/process/test-strategy.md +56 -0
- package/skills/engineering/ensure-coverage/release-check.mjs +93 -0
- package/skills/engineering/ensure-coverage/schemas/coverage-config.schema.json +67 -0
- package/skills/engineering/ensure-coverage/schemas/coverage-file.schema.json +140 -0
- package/skills/engineering/ensure-coverage/scripts/README.md +99 -0
- package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.mjs +182 -0
- package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.test.mjs +96 -0
- package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.mjs +105 -0
- package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.test.mjs +47 -0
- package/skills/engineering/ensure-coverage/scripts/ci-audit.mjs +137 -0
- package/skills/engineering/ensure-coverage/scripts/ci-audit.test.mjs +86 -0
- package/skills/engineering/ensure-coverage/scripts/coverage-ledger.mjs +674 -0
- package/skills/engineering/ensure-coverage/scripts/coverage-ledger.test.mjs +447 -0
- package/skills/engineering/ensure-coverage/scripts/design-parity.mjs +591 -0
- package/skills/engineering/ensure-coverage/scripts/design-parity.test.mjs +552 -0
- package/skills/engineering/ensure-coverage/scripts/lint-tests.mjs +282 -0
- package/skills/engineering/ensure-coverage/scripts/lint-tests.test.mjs +289 -0
- package/skills/engineering/ensure-coverage/scripts/mock-policy-config.mjs +176 -0
- package/skills/engineering/ensure-coverage/scripts/mock-policy-config.test.mjs +119 -0
- package/skills/engineering/ensure-coverage/scripts/package.json +19 -0
- package/skills/engineering/golden-path/LICENSE +3 -0
- package/skills/engineering/golden-path/SKILL.md +230 -0
- package/skills/engineering/golden-path/agents/openai.yaml +3 -0
- package/skills/engineering/golden-path/assets/golden-paths.example.json +57 -0
- package/skills/engineering/golden-path/references/contract.md +239 -0
- package/skills/engineering/golden-path/references/migration.md +111 -0
- package/skills/engineering/golden-path/release-check.mjs +93 -0
- package/skills/engineering/golden-path/scripts/golden-path-core.mjs +1779 -0
- package/skills/engineering/golden-path/scripts/golden-path.mjs +6 -0
- package/skills/engineering/golden-path/scripts/golden-path.test.mjs +1457 -0
- package/skills/engineering/harness-runtime/LICENSE +3 -0
- package/skills/engineering/harness-runtime/SKILL.md +19 -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/discover-models.test.mjs +363 -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/harness-runtime.test.mjs +967 -0
- package/skills/engineering/harness-runtime/index.mjs +767 -0
- package/skills/engineering/harness-runtime/model-catalog-seed.test.mjs +829 -0
- package/skills/engineering/harness-runtime/model-catalog.mjs +787 -0
- package/skills/engineering/harness-runtime/model-catalog.test.mjs +366 -0
- package/skills/engineering/harness-runtime/native-question-contracts.md +37 -0
- package/skills/engineering/harness-runtime/references/model-catalog-seed.json +408 -0
- package/skills/engineering/harness-runtime/references/model-catalog.md +57 -0
- package/skills/engineering/harness-runtime/release-check.mjs +93 -0
- package/skills/engineering/resolve-issues/LICENSE +3 -0
- package/skills/engineering/resolve-issues/SKILL.md +250 -0
- package/skills/engineering/resolve-issues/agents/openai.yaml +9 -0
- package/skills/engineering/resolve-issues/plugin-hooks.json +17 -0
- package/skills/engineering/resolve-issues/references/canary.md +28 -0
- package/skills/engineering/resolve-issues/references/ci.md +105 -0
- package/skills/engineering/resolve-issues/references/done.md +25 -0
- package/skills/engineering/resolve-issues/references/dropbox.md +90 -0
- package/skills/engineering/resolve-issues/references/expire.md +318 -0
- package/skills/engineering/resolve-issues/references/exploration.md +202 -0
- package/skills/engineering/resolve-issues/references/implementation.md +303 -0
- package/skills/engineering/resolve-issues/references/preflight.md +445 -0
- package/skills/engineering/resolve-issues/references/review.md +228 -0
- package/skills/engineering/resolve-issues/references/shared-modules.md +44 -0
- package/skills/engineering/resolve-issues/references/task-plan.md +264 -0
- package/skills/engineering/resolve-issues/release-check.mjs +93 -0
- package/skills/engineering/resolve-issues/scripts/cleanup-runs.mjs +364 -0
- package/skills/engineering/resolve-issues/scripts/cleanup-runs.test.mjs +280 -0
- package/skills/engineering/resolve-issues/scripts/discover-models.mjs +9 -0
- package/skills/engineering/resolve-issues/scripts/dropbox.mjs +473 -0
- package/skills/engineering/resolve-issues/scripts/dropbox.test.mjs +346 -0
- package/skills/engineering/resolve-issues/scripts/findings-reuse.mjs +98 -0
- package/skills/engineering/resolve-issues/scripts/findings-reuse.test.mjs +88 -0
- package/skills/engineering/resolve-issues/scripts/github-reviews.mjs +384 -0
- package/skills/engineering/resolve-issues/scripts/github-reviews.test.mjs +264 -0
- package/skills/engineering/resolve-issues/scripts/install-hooks.mjs +135 -0
- package/skills/engineering/resolve-issues/scripts/install-hooks.test.mjs +79 -0
- package/skills/engineering/resolve-issues/scripts/issue-tree.mjs +162 -0
- package/skills/engineering/resolve-issues/scripts/issue-tree.test.mjs +107 -0
- package/skills/engineering/resolve-issues/scripts/manual-invocation.test.mjs +14 -0
- package/skills/engineering/resolve-issues/scripts/model-catalog.mjs +9 -0
- package/skills/engineering/resolve-issues/scripts/next-actions-capacity.test.mjs +149 -0
- package/skills/engineering/resolve-issues/scripts/next-actions.mjs +856 -0
- package/skills/engineering/resolve-issues/scripts/next-actions.test.mjs +915 -0
- package/skills/engineering/resolve-issues/scripts/plan-sync.mjs +269 -0
- package/skills/engineering/resolve-issues/scripts/plan-sync.test.mjs +156 -0
- package/skills/engineering/resolve-issues/scripts/preflight-models.mjs +736 -0
- package/skills/engineering/resolve-issues/scripts/preflight-models.test.mjs +448 -0
- package/skills/engineering/resolve-issues/scripts/ready-check.mjs +185 -0
- package/skills/engineering/resolve-issues/scripts/ready-check.test.mjs +217 -0
- package/skills/engineering/resolve-issues/scripts/rehydrate.mjs +98 -0
- package/skills/engineering/resolve-issues/scripts/rehydrate.test.mjs +91 -0
- package/skills/engineering/resolve-issues/scripts/review-verdict.mjs +509 -0
- package/skills/engineering/resolve-issues/scripts/review-verdict.test.mjs +1060 -0
- package/skills/engineering/resolve-issues/scripts/size-check.mjs +492 -0
- package/skills/engineering/resolve-issues/scripts/size-check.test.mjs +458 -0
- package/skills/engineering/resolve-release/LICENSE +3 -0
- package/skills/engineering/resolve-release/SKILL.md +215 -0
- package/skills/engineering/resolve-release/agents/openai.yaml +8 -0
- package/skills/engineering/resolve-release/flows/cloud-run-canary.md +13 -0
- package/skills/engineering/resolve-release/flows/cloud-run.md +7 -0
- package/skills/engineering/resolve-release/flows/root-tag-npm.md +13 -0
- package/skills/engineering/resolve-release/flows/workspace-npm.md +22 -0
- package/skills/engineering/resolve-release/references/canary.md +51 -0
- package/skills/engineering/resolve-release/references/candidate.md +41 -0
- package/skills/engineering/resolve-release/references/cut.md +131 -0
- package/skills/engineering/resolve-release/references/dark-launch.md +66 -0
- package/skills/engineering/resolve-release/references/flows.md +150 -0
- package/skills/engineering/resolve-release/references/preflight.md +154 -0
- package/skills/engineering/resolve-release/references/prepared-metadata.md +260 -0
- package/skills/engineering/resolve-release/references/promotion.md +82 -0
- package/skills/engineering/resolve-release/references/publish.md +232 -0
- package/skills/engineering/resolve-release/references/workspace-npm.md +103 -0
- package/skills/engineering/resolve-release/release-check.mjs +93 -0
- package/skills/engineering/resolve-release/scripts/apply.mjs +212 -0
- package/skills/engineering/resolve-release/scripts/apply.test.mjs +99 -0
- package/skills/engineering/resolve-release/scripts/dark-flags.mjs +134 -0
- package/skills/engineering/resolve-release/scripts/dark-flags.test.mjs +100 -0
- package/skills/engineering/resolve-release/scripts/flow.mjs +500 -0
- package/skills/engineering/resolve-release/scripts/flow.test.mjs +340 -0
- package/skills/engineering/resolve-release/scripts/green-head.mjs +231 -0
- package/skills/engineering/resolve-release/scripts/green-head.test.mjs +101 -0
- package/skills/engineering/resolve-release/scripts/npm-version-absence.mjs +138 -0
- package/skills/engineering/resolve-release/scripts/npm-version-absence.test.mjs +426 -0
- package/skills/engineering/resolve-release/scripts/plan-npm.test.mjs +659 -0
- package/skills/engineering/resolve-release/scripts/plan-toml-boundary.test.mjs +317 -0
- package/skills/engineering/resolve-release/scripts/plan-uv.test.mjs +291 -0
- package/skills/engineering/resolve-release/scripts/plan.mjs +806 -0
- package/skills/engineering/resolve-release/scripts/plan.test.mjs +428 -0
- package/skills/engineering/rush-issues/LICENSE +3 -0
- package/skills/engineering/rush-issues/SKILL.md +58 -0
- package/skills/engineering/rush-issues/agents/openai.yaml +8 -0
- package/skills/engineering/rush-issues/rehydrate.mjs +10 -0
- package/skills/engineering/rush-issues/release-check.mjs +93 -0
- package/skills/engineering/rush-release/LICENSE +3 -0
- package/skills/engineering/rush-release/SKILL.md +35 -0
- package/skills/engineering/rush-release/agents/openai.yaml +8 -0
- package/skills/engineering/rush-release/release-check.mjs +93 -0
- package/skills/engineering/test-quality/LICENSE +3 -0
- package/skills/engineering/test-quality/SKILL.md +79 -0
- package/skills/engineering/test-quality/references/audit.md +80 -0
- package/skills/engineering/test-quality/references/lint.md +24 -0
- package/skills/engineering/test-quality/references/principles.md +143 -0
- package/skills/engineering/test-quality/references/testing-conventions.md +9 -0
- package/skills/engineering/test-quality/release-check.mjs +93 -0
- package/skills/engineering/test-quality/scripts/lint-tests.mjs +574 -0
- package/skills/engineering/test-quality/scripts/lint-tests.test.mjs +591 -0
- package/skills/engineering/test-quality/scripts/mock-policy-config.mjs +176 -0
- package/skills/engineering/test-quality/scripts/mock-policy-config.test.mjs +119 -0
- package/skills/engineering/test-quality/scripts/persist-conventions-core.mjs +117 -0
- package/skills/engineering/test-quality/scripts/persist-conventions.mjs +17 -0
- package/skills/engineering/test-quality/scripts/persist-conventions.test.mjs +104 -0
- package/skills/risk-compliance/compliance-audit/LICENSE +3 -0
- package/skills/risk-compliance/compliance-audit/SKILL.md +439 -0
- package/skills/risk-compliance/compliance-audit/references/findings-schema.md +293 -0
- package/skills/risk-compliance/compliance-audit/references/normalization-rules.md +45 -0
- package/skills/risk-compliance/compliance-audit/references/vars-checklist.md +1068 -0
- package/skills/risk-compliance/compliance-audit/references/vars-findings-schema.md +398 -0
- package/skills/risk-compliance/compliance-audit/release-check.mjs +93 -0
- package/skills/risk-compliance/compliance-audit/scripts/compliance-audit.test.mjs +14 -0
- package/skills/risk-compliance/compliance-audit/scripts/export_tracker_xlsx.py +221 -0
- package/skills/risk-compliance/compliance-audit/scripts/reconcile_tracker.py +458 -0
- package/skills/risk-compliance/compliance-audit/scripts/test_export_tracker_xlsx.py +97 -0
- package/skills/risk-compliance/compliance-audit/scripts/test_validate_tracker.py +157 -0
- package/skills/risk-compliance/compliance-audit/scripts/validate_tracker.py +829 -0
- package/skills/risk-compliance/contract-compare/LICENSE +3 -0
- package/skills/risk-compliance/contract-compare/SKILL.md +199 -0
- package/skills/risk-compliance/contract-compare/references/comparison-dimensions.md +209 -0
- package/skills/risk-compliance/contract-compare/release-check.mjs +93 -0
- package/skills/risk-compliance/contract-review/LICENSE +3 -0
- package/skills/risk-compliance/contract-review/SKILL.md +240 -0
- package/skills/risk-compliance/contract-review/references/review-criteria.md +223 -0
- package/skills/risk-compliance/contract-review/release-check.mjs +93 -0
- package/skills/risk-compliance/doc-authenticity/LICENSE +3 -0
- package/skills/risk-compliance/doc-authenticity/SKILL.md +160 -0
- package/skills/risk-compliance/doc-authenticity/agents/openai.yaml +8 -0
- package/skills/risk-compliance/doc-authenticity/manual-invocation.test.mjs +12 -0
- package/skills/risk-compliance/doc-authenticity/references/agreement.md +66 -0
- package/skills/risk-compliance/doc-authenticity/references/bank-statement.md +150 -0
- package/skills/risk-compliance/doc-authenticity/references/forensic-signals.md +157 -0
- package/skills/risk-compliance/doc-authenticity/references/image-signals.md +114 -0
- package/skills/risk-compliance/doc-authenticity/references/invoice.md +66 -0
- package/skills/risk-compliance/doc-authenticity/references/issuer-profiles.md +86 -0
- package/skills/risk-compliance/doc-authenticity/references/verdict-and-report.md +95 -0
- package/skills/risk-compliance/doc-authenticity/references/verdict.schema.json +81 -0
- package/skills/risk-compliance/doc-authenticity/release-check.mjs +93 -0
- package/skills/risk-compliance/doc-authenticity/scripts/image_forensics.py +294 -0
- package/skills/risk-compliance/doc-authenticity/scripts/pdf_forensics.py +401 -0
- package/skills/risk-compliance/doc-authenticity/scripts/validate_codes.py +224 -0
- package/skills/risk-compliance/doc-authenticity/scripts/validate_verdict.py +308 -0
- package/skills/risk-compliance/doc-authenticity/scripts/validate_verdict.test.mjs +212 -0
- package/skills/risk-compliance/fraud-analysis/LICENSE +3 -0
- package/skills/risk-compliance/fraud-analysis/SKILL.md +114 -0
- package/skills/risk-compliance/fraud-analysis/references/archetypes/authorized-payment-scam.md +41 -0
- package/skills/risk-compliance/fraud-analysis/references/archetypes/first-party-fraud.md +44 -0
- package/skills/risk-compliance/fraud-analysis/references/archetypes/third-party-fraud.md +27 -0
- package/skills/risk-compliance/fraud-analysis/references/contexts/bank-transfer.md +24 -0
- package/skills/risk-compliance/fraud-analysis/references/contexts/card-payment.md +30 -0
- package/skills/risk-compliance/fraud-analysis/references/contexts/payment-collection.md +20 -0
- package/skills/risk-compliance/fraud-analysis/references/contexts/payout.md +20 -0
- package/skills/risk-compliance/fraud-analysis/references/feature-engineering.md +158 -0
- package/skills/risk-compliance/fraud-analysis/references/mechanisms/account-takeover.md +36 -0
- package/skills/risk-compliance/fraud-analysis/references/report-rationale.md +45 -0
- package/skills/risk-compliance/fraud-analysis/references/report-template.md +190 -0
- package/skills/risk-compliance/fraud-analysis/references/review-checklist.md +175 -0
- package/skills/risk-compliance/fraud-analysis/references/taxonomy.md +79 -0
- package/skills/risk-compliance/fraud-analysis/references/terminology.md +108 -0
- package/skills/risk-compliance/fraud-analysis/references/workflow.md +175 -0
- package/skills/risk-compliance/fraud-analysis/release-check.mjs +93 -0
- package/skills/risk-compliance/payment-analysis/LICENSE +3 -0
- package/skills/risk-compliance/payment-analysis/SKILL.md +180 -0
- package/skills/risk-compliance/payment-analysis/references/auth-rate-actions.md +30 -0
- package/skills/risk-compliance/payment-analysis/references/chargebacks.md +208 -0
- package/skills/risk-compliance/payment-analysis/references/chinese-polish.md +51 -0
- package/skills/risk-compliance/payment-analysis/references/event-layers.md +127 -0
- package/skills/risk-compliance/payment-analysis/references/fx.md +59 -0
- package/skills/risk-compliance/payment-analysis/references/journey.md +79 -0
- package/skills/risk-compliance/payment-analysis/references/metrics.md +67 -0
- package/skills/risk-compliance/payment-analysis/references/report-template.md +119 -0
- package/skills/risk-compliance/payment-analysis/references/terminology.md +95 -0
- package/skills/risk-compliance/payment-analysis/references/visualization.md +56 -0
- package/skills/risk-compliance/payment-analysis/release-check.mjs +93 -0
- package/skills/risk-compliance/report-writing/LICENSE +3 -0
- package/skills/risk-compliance/report-writing/SKILL.md +220 -0
- package/skills/risk-compliance/report-writing/references/audit-report-template.md +121 -0
- package/skills/risk-compliance/report-writing/references/external-assessment-template.md +167 -0
- package/skills/risk-compliance/report-writing/references/management-report-template.md +145 -0
- package/skills/risk-compliance/report-writing/references/vars-report-template.md +224 -0
- package/skills/risk-compliance/report-writing/release-check.mjs +93 -0
- package/skills/risk-compliance/similar-domain/LICENSE +3 -0
- package/skills/risk-compliance/similar-domain/SKILL.md +203 -0
- package/skills/risk-compliance/similar-domain/references/entities.md +67 -0
- package/skills/risk-compliance/similar-domain/references/fingerprints.md +134 -0
- package/skills/risk-compliance/similar-domain/references/pivots.md +82 -0
- package/skills/risk-compliance/similar-domain/references/report-template.md +93 -0
- package/skills/risk-compliance/similar-domain/references/scoring.md +130 -0
- package/skills/risk-compliance/similar-domain/references/terminology.md +41 -0
- package/skills/risk-compliance/similar-domain/references/workflow.md +85 -0
- package/skills/risk-compliance/similar-domain/release-check.mjs +93 -0
- package/skills/risk-compliance/tld-detection/LICENSE +3 -0
- package/skills/risk-compliance/tld-detection/SKILL.md +245 -0
- package/skills/risk-compliance/tld-detection/agents/openai.yaml +9 -0
- package/skills/risk-compliance/tld-detection/references/chinese-polish.md +55 -0
- package/skills/risk-compliance/tld-detection/references/clean.md +51 -0
- package/skills/risk-compliance/tld-detection/references/clustering.md +235 -0
- package/skills/risk-compliance/tld-detection/references/decision.md +189 -0
- package/skills/risk-compliance/tld-detection/references/fx-conversion.md +232 -0
- package/skills/risk-compliance/tld-detection/references/report-template.md +272 -0
- package/skills/risk-compliance/tld-detection/references/streams.md +141 -0
- package/skills/risk-compliance/tld-detection/references/terminology.md +95 -0
- package/skills/risk-compliance/tld-detection/references/workflow.md +95 -0
- package/skills/risk-compliance/tld-detection/release-check.mjs +93 -0
- package/skills/risk-compliance/tld-detection/scripts/detect_ladders.py +390 -0
- package/skills/risk-compliance/tld-detection/scripts/detect_ladders.test.mjs +14 -0
- package/skills/risk-compliance/tld-detection/scripts/detect_streams.py +650 -0
- package/skills/risk-compliance/tld-detection/scripts/detect_streams.test.mjs +14 -0
- package/skills/risk-compliance/tld-detection/tests/test_detect_ladders.py +134 -0
- package/skills/risk-compliance/tld-detection/tests/test_detect_streams.py +520 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Bring reviews posted on the PR on GitHub into a resolve-issues review round.
|
|
3
|
+
//
|
|
4
|
+
// github-reviews.mjs merge --pr <owner/name#N> --head <sha> --review <run>/reviews/<unit>-r<N>.json
|
|
5
|
+
// [--prior <reviews/<unit>-r<N-1>.json>]
|
|
6
|
+
// github-reviews.mjs structured --pr <owner/name#N> --head <sha> --out <run>/reviews/<unit>-r<N>.json
|
|
7
|
+
// --unit <id> --round <N> --fix-round <F>
|
|
8
|
+
//
|
|
9
|
+
// Only reviews and comments from an account with write access to the repository, other
|
|
10
|
+
// than the PR author, count; the rest are listed under `external.ignored`.
|
|
11
|
+
//
|
|
12
|
+
// merge: add the PR's blocking GitHub feedback to the round's result file, in place, before
|
|
13
|
+
// review-verdict.mjs reads it. Rerunning it on the same file replaces what it added.
|
|
14
|
+
// - A reviewer whose latest review requests changes is a P1 finding `gh-review-<id>`.
|
|
15
|
+
// Once a fix brief has carried it, it is `awaiting` that reviewer's re-review instead.
|
|
16
|
+
// - An inline thread, review body, or PR comment whose first line is tagged P0 or P1
|
|
17
|
+
// (`P1:`, `**P1**`, `[P1]`, a `![P1 Badge]` image, also inside `<sub>`) is a finding `gh-comment-<id>`, until
|
|
18
|
+
// its thread is resolved or a fix brief has carried it once. P2/P3 tags stay on GitHub.
|
|
19
|
+
// - A GitHub finding is handled once the prior round's fix brief (`<prior>.fix.json`,
|
|
20
|
+
// written only on a `fix` verdict) carried it; `external.handled` carries the set from
|
|
21
|
+
// round to round. An incomplete or handed-back round hands nothing on.
|
|
22
|
+
// - Every `gh-review-<n>`/`gh-comment-<n>` finding is this script's: it removes each one
|
|
23
|
+
// the file holds, the reviewer's carried-forward copies included, and adds the current
|
|
24
|
+
// set, each with a trigger, rule, and open path like any blocking finding.
|
|
25
|
+
// Every review, thread and comment is read, page by page. A permission lookup that fails
|
|
26
|
+
// for any reason but "not a user" stops the merge (exit 2) rather than drop feedback.
|
|
27
|
+
// Exit 0 merged · 2 bad command or unreadable file or GitHub failure.
|
|
28
|
+
//
|
|
29
|
+
// structured: write a GitHub-posted better-review result for --head as this round's result
|
|
30
|
+
// file, so no reviewer is spawned. The result is a comment or review body holding
|
|
31
|
+
// STRUCTURED_MARKER and one ```json block; its `head` must be --head, and it must be
|
|
32
|
+
// complete by the reviewer's own rules (complete, every pass, checks run). The file takes
|
|
33
|
+
// --unit, --round and --fix-round from the caller and records its `source`.
|
|
34
|
+
// Exit 0 whenever it ran (`found: false` when none is found) · 2 bad command or GitHub failure.
|
|
35
|
+
|
|
36
|
+
import { execFileSync } from 'node:child_process'
|
|
37
|
+
import { readFileSync, writeFileSync } from 'node:fs'
|
|
38
|
+
import { basename, dirname, join } from 'node:path'
|
|
39
|
+
|
|
40
|
+
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
41
|
+
import { reviewCompletionProblems } from '../../engineering-runtime/scripts/review-completion.mjs'
|
|
42
|
+
|
|
43
|
+
export const STRUCTURED_MARKER = '<!-- better-review-result -->'
|
|
44
|
+
// GitHub's base access level; `role_name` may be a custom role's name.
|
|
45
|
+
export const WRITE_PERMISSIONS = Object.freeze(['admin', 'write'])
|
|
46
|
+
export const GITHUB_ID = /^gh-(?:review|comment)-\d+$/
|
|
47
|
+
const BLOCKING_TAG = /^(?:[\s*_[(!]|<[^>\n]+>)*(P[0-3])\b/
|
|
48
|
+
|
|
49
|
+
export class GithubReviewsError extends Error {}
|
|
50
|
+
|
|
51
|
+
const isText = (value) => typeof value === 'string' && value.trim() !== ''
|
|
52
|
+
const firstLine = (body) => String(body ?? '').trim().split('\n', 1)[0]
|
|
53
|
+
|
|
54
|
+
// The P0-P3 tag a comment's first line opens with, or null.
|
|
55
|
+
export function severityTag(body) {
|
|
56
|
+
const m = firstLine(body).match(BLOCKING_TAG)
|
|
57
|
+
return m ? m[1] : null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function parsePr(text) {
|
|
61
|
+
const m = String(text ?? '').match(/^([\w.-]+)\/([\w.-]+)#(\d+)$/)
|
|
62
|
+
if (!m) throw new GithubReviewsError(`--pr must be <owner/name#N>, got ${JSON.stringify(text)}`)
|
|
63
|
+
return { owner: m[1], name: m[2], number: Number(m[3]) }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Split the PR's authors into trusted (write access, not the PR author) and ignored.
|
|
67
|
+
function trustOf(login, { prAuthor, permissionOf }) {
|
|
68
|
+
if (!isText(login)) return 'no author'
|
|
69
|
+
if (login === prAuthor) return 'PR author'
|
|
70
|
+
const permission = permissionOf(login)
|
|
71
|
+
return WRITE_PERMISSIONS.includes(permission) ? null : `permission ${permission ?? 'none'}`
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// The result a body posts: the marker and a JSON block shaped like a result (a HEAD and a
|
|
75
|
+
// findings array); null otherwise, so any other comment is read as feedback.
|
|
76
|
+
function structuredJson(body) {
|
|
77
|
+
const text = String(body ?? '')
|
|
78
|
+
if (!text.includes(STRUCTURED_MARKER)) return null
|
|
79
|
+
const block = text.match(/```json\s*\n([\s\S]*?)\n```/)
|
|
80
|
+
if (!block) return null
|
|
81
|
+
let result
|
|
82
|
+
try { result = JSON.parse(block[1]) } catch { return null }
|
|
83
|
+
return isText(result?.head) && Array.isArray(result?.findings) ? result : null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// The blocking-finding shape review-verdict.mjs requires of every P0/P1.
|
|
87
|
+
function shaped(finding, what, where) {
|
|
88
|
+
return {
|
|
89
|
+
...finding,
|
|
90
|
+
trigger: `${finding.author}'s ${what} on GitHub: ${finding.url}`,
|
|
91
|
+
rule: 'blocking review feedback on the PR is resolved before merge',
|
|
92
|
+
paths: [{ path: where || finding.url || 'the PR', status: 'open' }],
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function summaryOf(body, fallback) {
|
|
97
|
+
const line = firstLine(body).replace(/!\[[^\]]*\]\([^)]*\)/g, '').replace(/<[^>]+>/g, '').replace(/[*_[\]]/g, '')
|
|
98
|
+
.replace(/^\s*P[0-3]\b[:\s-]*/, '').trim()
|
|
99
|
+
return line || fallback
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The PR's blocking GitHub feedback for --head. `pr` is the GraphQL pullRequest node;
|
|
104
|
+
* `handled` holds the ids a fix brief has already carried.
|
|
105
|
+
*/
|
|
106
|
+
export function collectExternal(pr, head, { permissionOf, handled = new Set() }) {
|
|
107
|
+
const prAuthor = pr?.author?.login ?? null
|
|
108
|
+
const trustCache = new Map()
|
|
109
|
+
const trusted = (login) => {
|
|
110
|
+
if (!trustCache.has(login)) trustCache.set(login, trustOf(login, { prAuthor, permissionOf }))
|
|
111
|
+
return trustCache.get(login)
|
|
112
|
+
}
|
|
113
|
+
const findings = []
|
|
114
|
+
const awaiting = []
|
|
115
|
+
const ignored = []
|
|
116
|
+
const ignore = (login, url, reason) => ignored.push({ author: login ?? null, url: url ?? null, reason })
|
|
117
|
+
|
|
118
|
+
// A reviewer's latest decisive review is their standing state, as GitHub counts it.
|
|
119
|
+
const latest = new Map()
|
|
120
|
+
for (const review of pr?.reviews?.nodes ?? []) {
|
|
121
|
+
const login = review?.author?.login
|
|
122
|
+
if (!['APPROVED', 'CHANGES_REQUESTED', 'DISMISSED'].includes(review?.state)) continue
|
|
123
|
+
const before = latest.get(login)
|
|
124
|
+
if (!before || String(review.submittedAt) >= String(before.submittedAt)) latest.set(login, review)
|
|
125
|
+
}
|
|
126
|
+
for (const [login, review] of latest) {
|
|
127
|
+
if (review.state !== 'CHANGES_REQUESTED') continue
|
|
128
|
+
const reason = trusted(login)
|
|
129
|
+
if (reason) { ignore(login, review.url, reason); continue }
|
|
130
|
+
const id = `gh-review-${review.databaseId}`
|
|
131
|
+
if (handled.has(id)) {
|
|
132
|
+
awaiting.push({ id, author: login, url: review.url, commit: review.commit?.oid ?? null })
|
|
133
|
+
continue
|
|
134
|
+
}
|
|
135
|
+
findings.push(shaped({
|
|
136
|
+
id, severity: 'P1', pass: 'external', source: 'github', author: login, url: review.url,
|
|
137
|
+
commit: review.commit?.oid ?? null,
|
|
138
|
+
summary: summaryOf(review.body, `${login} requested changes`),
|
|
139
|
+
...(isText(review.body) ? { detail: review.body.trim() } : {}),
|
|
140
|
+
}, 'changes request'))
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const tagged = (node, extra = {}) => {
|
|
144
|
+
const body = node?.body
|
|
145
|
+
if (structuredJson(body)) return
|
|
146
|
+
const severity = severityTag(body)
|
|
147
|
+
if (severity !== 'P0' && severity !== 'P1') return
|
|
148
|
+
const login = node?.author?.login
|
|
149
|
+
const reason = trusted(login)
|
|
150
|
+
if (reason) { ignore(login, node.url, reason); return }
|
|
151
|
+
const id = `gh-comment-${node.databaseId}`
|
|
152
|
+
if (handled.has(id)) return
|
|
153
|
+
const where = isText(extra.file) ? `${extra.file}${extra.line ? `:${extra.line}` : ''}` : null
|
|
154
|
+
findings.push(shaped({
|
|
155
|
+
id, severity, pass: 'external', source: 'github', author: login, url: node.url,
|
|
156
|
+
summary: summaryOf(body, `${login} flagged ${severity}`), detail: String(body).trim(), ...extra,
|
|
157
|
+
}, `${severity} comment`, where))
|
|
158
|
+
}
|
|
159
|
+
for (const thread of pr?.reviewThreads?.nodes ?? []) {
|
|
160
|
+
if (thread?.isResolved) continue
|
|
161
|
+
const comment = thread?.comments?.nodes?.[0]
|
|
162
|
+
if (!comment) continue
|
|
163
|
+
tagged(comment, {
|
|
164
|
+
...(isText(comment.path) ? { file: comment.path } : {}),
|
|
165
|
+
...(Number.isSafeInteger(comment.line ?? comment.originalLine) ? { line: comment.line ?? comment.originalLine } : {}),
|
|
166
|
+
commit: comment.originalCommit?.oid ?? comment.commit?.oid ?? null,
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
// A changes-requested review is already its reviewer's finding.
|
|
170
|
+
for (const review of pr?.reviews?.nodes ?? []) {
|
|
171
|
+
if (review?.state !== 'CHANGES_REQUESTED') tagged(review, { commit: review?.commit?.oid ?? null })
|
|
172
|
+
}
|
|
173
|
+
for (const comment of pr?.comments?.nodes ?? []) tagged(comment)
|
|
174
|
+
|
|
175
|
+
return { prAuthor, head, findings, awaiting, ignored }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Ids a fix brief has carried: the prior round's ledger plus the GitHub findings in the
|
|
179
|
+
// prior round's fix brief, which exists only when its verdict sent them to a fix worker.
|
|
180
|
+
export function handledIds(prior, priorFixBrief = null) {
|
|
181
|
+
const ids = new Set(Array.isArray(prior?.external?.handled) ? prior.external.handled.filter((id) => GITHUB_ID.test(id)) : [])
|
|
182
|
+
for (const f of Array.isArray(priorFixBrief?.fix) ? priorFixBrief.fix : []) {
|
|
183
|
+
if (isText(f?.id) && GITHUB_ID.test(f.id)) ids.add(f.id)
|
|
184
|
+
}
|
|
185
|
+
return ids
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** The review file with this round's GitHub feedback in place of any added before. */
|
|
189
|
+
export function mergeExternal(review, external, handled) {
|
|
190
|
+
// GitHub is the authority on its own findings, so drop any copy the reviewer carried.
|
|
191
|
+
const own = (Array.isArray(review?.findings) ? review.findings : []).filter((f) => !GITHUB_ID.test(String(f?.id)))
|
|
192
|
+
return {
|
|
193
|
+
...review,
|
|
194
|
+
findings: [...own, ...external.findings],
|
|
195
|
+
external: {
|
|
196
|
+
added: external.findings.map((f) => f.id),
|
|
197
|
+
head: external.head,
|
|
198
|
+
prAuthor: external.prAuthor,
|
|
199
|
+
awaiting: external.awaiting,
|
|
200
|
+
handled: [...handled].sort(),
|
|
201
|
+
ignored: external.ignored,
|
|
202
|
+
},
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* The newest trusted GitHub-posted better-review result for --head, or null. Bodies
|
|
208
|
+
* without the marker, with no JSON block, or of another HEAD are passed over.
|
|
209
|
+
*/
|
|
210
|
+
export function findStructured(pr, head, { permissionOf }) {
|
|
211
|
+
const prAuthor = pr?.author?.login ?? null
|
|
212
|
+
const bodies = [
|
|
213
|
+
...(pr?.reviews?.nodes ?? []).map((n) => ({ ...n, at: n?.submittedAt })),
|
|
214
|
+
...(pr?.comments?.nodes ?? []).map((n) => ({ ...n, at: n?.createdAt })),
|
|
215
|
+
].filter((n) => String(n?.body ?? '').includes(STRUCTURED_MARKER))
|
|
216
|
+
.sort((a, b) => String(b.at).localeCompare(String(a.at)))
|
|
217
|
+
for (const node of bodies) {
|
|
218
|
+
const result = structuredJson(node.body)
|
|
219
|
+
if (result?.head !== head) continue
|
|
220
|
+
// Unlike a legacy local record, a posted result must say it is complete.
|
|
221
|
+
if (result.complete !== true || reviewCompletionProblems(result, { requirePasses: true }).length) continue
|
|
222
|
+
if (trustOf(node?.author?.login, { prAuthor, permissionOf })) continue
|
|
223
|
+
return { result, source: { kind: 'github', author: node.author.login, url: node.url ?? null } }
|
|
224
|
+
}
|
|
225
|
+
return null
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const CONNECTIONS = {
|
|
229
|
+
reviews: 'databaseId author { login } state body submittedAt url commit { oid }',
|
|
230
|
+
reviewThreads: `isResolved isOutdated comments(first: 1) {
|
|
231
|
+
nodes { databaseId author { login } body path line originalLine url commit { oid } originalCommit { oid } } }`,
|
|
232
|
+
comments: 'databaseId author { login } body createdAt url',
|
|
233
|
+
}
|
|
234
|
+
const pageQuery = (field) => `query($owner: String!, $name: String!, $number: Int!, $after: String) {
|
|
235
|
+
repository(owner: $owner, name: $name) {
|
|
236
|
+
pullRequest(number: $number) {
|
|
237
|
+
author { login }
|
|
238
|
+
${field}(first: 100, after: $after) { pageInfo { hasNextPage endCursor } nodes { ${CONNECTIONS[field]} } }
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}`
|
|
242
|
+
|
|
243
|
+
function gh(args) {
|
|
244
|
+
try {
|
|
245
|
+
return execFileSync('gh', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] })
|
|
246
|
+
} catch (error) {
|
|
247
|
+
throw new GithubReviewsError(`gh ${args[0]} ${args[1] ?? ''} failed: ${String(error.stderr || error.message).trim()}`)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Every node of each connection, page by page; `query` runs one GraphQL page.
|
|
252
|
+
export function fetchPr({ owner, name, number }, query = ghGraphql) {
|
|
253
|
+
const pr = {}
|
|
254
|
+
for (const field of Object.keys(CONNECTIONS)) {
|
|
255
|
+
const nodes = []
|
|
256
|
+
let after = null
|
|
257
|
+
do {
|
|
258
|
+
const page = query(pageQuery(field), { owner, name, number, after })?.data?.repository?.pullRequest
|
|
259
|
+
if (!page) throw new GithubReviewsError(`no pull request ${owner}/${name}#${number}`)
|
|
260
|
+
pr.author = page.author
|
|
261
|
+
const connection = page[field]
|
|
262
|
+
if (!Array.isArray(connection?.nodes)) throw new GithubReviewsError(`no ${field} in the GitHub response`)
|
|
263
|
+
nodes.push(...connection.nodes)
|
|
264
|
+
after = connection.pageInfo?.hasNextPage ? connection.pageInfo.endCursor : null
|
|
265
|
+
if (connection.pageInfo?.hasNextPage && !isText(after)) throw new GithubReviewsError(`${field}: a next page without a cursor`)
|
|
266
|
+
} while (after)
|
|
267
|
+
pr[field] = { nodes }
|
|
268
|
+
}
|
|
269
|
+
return pr
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function ghGraphql(query, { owner, name, number, after }) {
|
|
273
|
+
const args = ['api', 'graphql', '-f', `query=${query}`, '-F', `owner=${owner}`, '-F', `name=${name}`, '-F', `number=${number}`]
|
|
274
|
+
if (after) args.push('-f', `after=${after}`)
|
|
275
|
+
return JSON.parse(gh(args))
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// A bot answers 200 with permission "none"; a login that is no user answers 404. Any other
|
|
279
|
+
// failure is unknown access, so it stops the merge.
|
|
280
|
+
export function permissionFetcher({ owner, name }, api = (path) => gh(['api', path])) {
|
|
281
|
+
return (login) => {
|
|
282
|
+
let text
|
|
283
|
+
try {
|
|
284
|
+
text = api(`repos/${owner}/${name}/collaborators/${encodeURIComponent(login)}/permission`)
|
|
285
|
+
} catch (error) {
|
|
286
|
+
if (/\(HTTP 404\)/.test(error.message)) return 'none'
|
|
287
|
+
throw new GithubReviewsError(`cannot tell ${login}'s access: ${error.message}`)
|
|
288
|
+
}
|
|
289
|
+
const out = JSON.parse(text)
|
|
290
|
+
return out?.permission || 'none'
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const USAGE = `usage:
|
|
295
|
+
github-reviews.mjs merge --pr <owner/name#N> --head <sha> --review <reviews/<unit>-r<N>.json> [--prior <file>]
|
|
296
|
+
github-reviews.mjs structured --pr <owner/name#N> --head <sha> --out <file> --unit <id> --round <N> --fix-round <F>`
|
|
297
|
+
|
|
298
|
+
function parseArgs(argv) {
|
|
299
|
+
const [mode, ...rest] = argv
|
|
300
|
+
const opts = { mode }
|
|
301
|
+
for (let i = 0; i < rest.length; i += 2) {
|
|
302
|
+
if (!rest[i]?.startsWith('--') || rest[i + 1] === undefined) throw new GithubReviewsError(USAGE)
|
|
303
|
+
opts[rest[i].slice(2)] = rest[i + 1]
|
|
304
|
+
}
|
|
305
|
+
const need = { merge: ['pr', 'head', 'review'], structured: ['pr', 'head', 'out', 'unit', 'round', 'fix-round'] }[mode]
|
|
306
|
+
if (!need || need.some((k) => !isText(opts[k]))) throw new GithubReviewsError(USAGE)
|
|
307
|
+
return opts
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function readJson(path) {
|
|
311
|
+
try {
|
|
312
|
+
return JSON.parse(readFileSync(path, 'utf8'))
|
|
313
|
+
} catch (error) {
|
|
314
|
+
throw new GithubReviewsError(`cannot read ${path}: ${error.message}`)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function priorPath(opts) {
|
|
319
|
+
if (opts.prior) return opts.prior
|
|
320
|
+
const m = basename(opts.review).match(/^(.*-r)(\d+)\.json$/)
|
|
321
|
+
return m && Number(m[2]) >= 2 ? join(dirname(opts.review), `${m[1]}${Number(m[2]) - 1}.json`) : null
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function counter(text, flag) {
|
|
325
|
+
const n = Number(text)
|
|
326
|
+
if (!Number.isSafeInteger(n) || n < 1) throw new GithubReviewsError(`--${flag} must be a positive integer`)
|
|
327
|
+
return n
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function run(argv, { fetch = fetchPr, permissions = permissionFetcher } = {}) {
|
|
331
|
+
const opts = parseArgs(argv)
|
|
332
|
+
const target = parsePr(opts.pr)
|
|
333
|
+
const pr = fetch(target)
|
|
334
|
+
const permissionOf = permissions(target)
|
|
335
|
+
if (opts.mode === 'structured') {
|
|
336
|
+
const found = findStructured(pr, opts.head, { permissionOf })
|
|
337
|
+
if (!found) return { out: { found: false, head: opts.head } }
|
|
338
|
+
const result = {
|
|
339
|
+
...found.result,
|
|
340
|
+
unit: opts.unit,
|
|
341
|
+
round: counter(opts.round, 'round'),
|
|
342
|
+
fixRound: counter(opts['fix-round'], 'fix-round'),
|
|
343
|
+
source: found.source,
|
|
344
|
+
}
|
|
345
|
+
writeFileSync(opts.out, `${JSON.stringify(result, null, 2)}\n`)
|
|
346
|
+
return { out: { found: true, path: opts.out, source: found.source } }
|
|
347
|
+
}
|
|
348
|
+
const review = readJson(opts.review)
|
|
349
|
+
const path = priorPath(opts)
|
|
350
|
+
let prior = null
|
|
351
|
+
if (path) {
|
|
352
|
+
try { prior = JSON.parse(readFileSync(path, 'utf8')) } catch (error) {
|
|
353
|
+
if (opts.prior || error.code !== 'ENOENT') throw new GithubReviewsError(`cannot read prior ${path}: ${error.message}`)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
let priorFixBrief = null
|
|
357
|
+
if (path) {
|
|
358
|
+
const briefPath = join(dirname(path), `${basename(path, '.json')}.fix.json`)
|
|
359
|
+
try { priorFixBrief = JSON.parse(readFileSync(briefPath, 'utf8')) } catch (error) {
|
|
360
|
+
if (error.code !== 'ENOENT') throw new GithubReviewsError(`cannot read ${briefPath}: ${error.message}`)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const handled = handledIds(prior, priorFixBrief)
|
|
364
|
+
const external = collectExternal(pr, opts.head, { permissionOf, handled })
|
|
365
|
+
const merged = mergeExternal(review, external, handled)
|
|
366
|
+
writeFileSync(opts.review, `${JSON.stringify(merged, null, 2)}\n`)
|
|
367
|
+
return {
|
|
368
|
+
out: {
|
|
369
|
+
findings: external.findings.map(({ id, severity, author }) => ({ id, severity, author })),
|
|
370
|
+
awaiting: external.awaiting,
|
|
371
|
+
ignored: external.ignored.length,
|
|
372
|
+
},
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (isMainModule(import.meta.url)) {
|
|
377
|
+
try {
|
|
378
|
+
const { out } = run(process.argv.slice(2))
|
|
379
|
+
process.stdout.write(`${JSON.stringify(out, null, 2)}\n`)
|
|
380
|
+
} catch (error) {
|
|
381
|
+
process.stderr.write(`${error instanceof GithubReviewsError ? error.message : error.stack}\n`)
|
|
382
|
+
process.exitCode = 2
|
|
383
|
+
}
|
|
384
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
collectExternal,
|
|
9
|
+
fetchPr,
|
|
10
|
+
permissionFetcher,
|
|
11
|
+
findStructured,
|
|
12
|
+
handledIds,
|
|
13
|
+
mergeExternal,
|
|
14
|
+
parsePr,
|
|
15
|
+
run,
|
|
16
|
+
severityTag,
|
|
17
|
+
STRUCTURED_MARKER,
|
|
18
|
+
} from './github-reviews.mjs'
|
|
19
|
+
|
|
20
|
+
const HEAD = 'abc1234'
|
|
21
|
+
const ROLES = { vera932: 'write', alo335: 'admin', reader: 'read' }
|
|
22
|
+
const permissionOf = (login) => ROLES[login] ?? 'none'
|
|
23
|
+
const review = (id, login, state, body = '', at = '2026-10-08T10:00:00Z', commit = HEAD) => ({
|
|
24
|
+
databaseId: id, author: { login }, state, body, submittedAt: at, url: `https://github.com/o/r/pull/1#pullrequestreview-${id}`, commit: { oid: commit },
|
|
25
|
+
})
|
|
26
|
+
const thread = (id, login, body, extra = {}) => ({
|
|
27
|
+
isResolved: false, isOutdated: false, ...extra,
|
|
28
|
+
comments: { nodes: [{ databaseId: id, author: { login }, body, path: 'src/a.ts', line: 12, url: `https://github.com/o/r/pull/1#discussion_r${id}`, originalCommit: { oid: HEAD } }] },
|
|
29
|
+
})
|
|
30
|
+
const pr = (parts = {}) => ({
|
|
31
|
+
author: { login: 'cory-bot' },
|
|
32
|
+
reviews: { nodes: parts.reviews ?? [] },
|
|
33
|
+
reviewThreads: { nodes: parts.threads ?? [] },
|
|
34
|
+
comments: { nodes: parts.comments ?? [] },
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('severity tags are read from the first line in the usual forms only', () => {
|
|
38
|
+
for (const [body, tag] of [
|
|
39
|
+
['P1: refund sign flips', 'P1'], ['**P0** token leaks', 'P0'], ['[P1] missing check', 'P1'],
|
|
40
|
+
[' Wrong total', 'P1'], ['P2: rename', 'P2'],
|
|
41
|
+
['**<sub><sub></sub></sub> Keep the lock**', 'P1'],
|
|
42
|
+
['<!-- codex-pull-request-review-summary -->', null],
|
|
43
|
+
['Pretty sure this is fine', null], ['P10 items', null], ['nit\nP1: on line two', null], ['', null],
|
|
44
|
+
]) assert.equal(severityTag(body), tag, body)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('a trusted changes request is a P1 finding; approvals, dismissals and later approvals clear it', () => {
|
|
48
|
+
const blocked = collectExternal(pr({ reviews: [review(11, 'vera932', 'CHANGES_REQUESTED', 'Refund rounds the wrong way\nmore')] }), HEAD, { permissionOf })
|
|
49
|
+
assert.deepEqual(blocked.findings.map((f) => [f.id, f.severity, f.summary, f.author]), [['gh-review-11', 'P1', 'Refund rounds the wrong way', 'vera932']])
|
|
50
|
+
assert.match(blocked.findings[0].detail, /more/)
|
|
51
|
+
const cleared = collectExternal(pr({ reviews: [
|
|
52
|
+
review(11, 'vera932', 'CHANGES_REQUESTED', 'x', '2026-10-08T10:00:00Z'),
|
|
53
|
+
review(12, 'vera932', 'COMMENTED', 'thanks', '2026-10-08T11:00:00Z'),
|
|
54
|
+
review(13, 'vera932', 'APPROVED', '', '2026-10-08T12:00:00Z'),
|
|
55
|
+
] }), HEAD, { permissionOf })
|
|
56
|
+
assert.deepEqual(cleared.findings, [])
|
|
57
|
+
const dismissed = collectExternal(pr({ reviews: [review(11, 'vera932', 'CHANGES_REQUESTED', 'x'), review(14, 'vera932', 'DISMISSED', '', '2026-10-08T12:00:00Z')] }), HEAD, { permissionOf })
|
|
58
|
+
assert.deepEqual(dismissed.findings, [])
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('only write access counts, and never the PR author', () => {
|
|
62
|
+
const external = collectExternal(pr({
|
|
63
|
+
reviews: [review(21, 'reader', 'CHANGES_REQUESTED', 'no'), review(22, 'cory-bot', 'CHANGES_REQUESTED', 'self')],
|
|
64
|
+
threads: [thread(31, 'codex-bot', 'P1: bot says'), thread(32, 'alo335', 'P1: maintainer says')],
|
|
65
|
+
}), HEAD, { permissionOf })
|
|
66
|
+
assert.deepEqual(external.findings.map((f) => f.id), ['gh-comment-32'])
|
|
67
|
+
assert.deepEqual(external.ignored.map((i) => [i.author, i.reason]).sort(), [
|
|
68
|
+
['codex-bot', 'permission none'], ['cory-bot', 'PR author'], ['reader', 'permission read'],
|
|
69
|
+
])
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('tagged comments block until resolved; P2/P3 and untagged comments never do', () => {
|
|
73
|
+
const external = collectExternal(pr({
|
|
74
|
+
threads: [
|
|
75
|
+
thread(41, 'vera932', '**P0** tenant check missing'),
|
|
76
|
+
thread(42, 'vera932', 'P1: resolved already', { isResolved: true }),
|
|
77
|
+
thread(43, 'vera932', 'P2: rename'),
|
|
78
|
+
thread(44, 'vera932', 'looks off to me'),
|
|
79
|
+
],
|
|
80
|
+
reviews: [review(45, 'vera932', 'COMMENTED', '[P1] the export drops currency')],
|
|
81
|
+
comments: [{ databaseId: 46, author: { login: 'alo335' }, body: 'P1: migration not reversible', createdAt: '2026-10-08T10:00:00Z', url: 'u46' }],
|
|
82
|
+
}), HEAD, { permissionOf })
|
|
83
|
+
assert.deepEqual(external.findings.map((f) => [f.id, f.severity]), [['gh-comment-41', 'P0'], ['gh-comment-45', 'P1'], ['gh-comment-46', 'P1']])
|
|
84
|
+
assert.equal(external.findings[0].file, 'src/a.ts')
|
|
85
|
+
assert.equal(external.findings[0].line, 12)
|
|
86
|
+
const badge = collectExternal(pr({ threads: [thread(49, 'vera932', '**<sub><sub></sub></sub> Keep the lock**')] }), HEAD, { permissionOf })
|
|
87
|
+
assert.equal(badge.findings[0].summary, 'Keep the lock')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('a changes request whose body is tagged counts once', () => {
|
|
91
|
+
const external = collectExternal(pr({ reviews: [review(47, 'vera932', 'CHANGES_REQUESTED', 'P1: wrong total')] }), HEAD, { permissionOf })
|
|
92
|
+
assert.deepEqual(external.findings.map((f) => f.id), ['gh-review-47'])
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('after a fix brief carried them, a standing changes request is awaited and a comment is dropped', () => {
|
|
96
|
+
const handled = new Set(['gh-review-11', 'gh-comment-41'])
|
|
97
|
+
const external = collectExternal(pr({
|
|
98
|
+
reviews: [review(11, 'vera932', 'CHANGES_REQUESTED', 'x', '2026-10-08T10:00:00Z', 'old1234')],
|
|
99
|
+
threads: [thread(41, 'vera932', 'P1: still unresolved'), thread(48, 'vera932', 'P1: new one')],
|
|
100
|
+
}), HEAD, { permissionOf, handled })
|
|
101
|
+
assert.deepEqual(external.findings.map((f) => f.id), ['gh-comment-48'])
|
|
102
|
+
assert.deepEqual(external.awaiting.map((a) => [a.id, a.author, a.commit]), [['gh-review-11', 'vera932', 'old1234']])
|
|
103
|
+
const rerequested = collectExternal(pr({ reviews: [
|
|
104
|
+
review(11, 'vera932', 'CHANGES_REQUESTED', 'x', '2026-10-08T10:00:00Z', 'old1234'),
|
|
105
|
+
review(15, 'vera932', 'CHANGES_REQUESTED', 'still wrong', '2026-10-08T13:00:00Z'),
|
|
106
|
+
] }), HEAD, { permissionOf, handled })
|
|
107
|
+
assert.deepEqual(rerequested.findings.map((f) => f.id), ['gh-review-15'], 'a new changes request is a new finding')
|
|
108
|
+
assert.deepEqual(rerequested.awaiting, [])
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test('the handled ledger carries forward and merge replaces what it added before', () => {
|
|
112
|
+
const prior = { findings: [{ id: 'gh-review-11', severity: 'P1' }, { id: 'R1', severity: 'P1' }], external: { added: ['gh-review-11'], handled: ['gh-comment-41'] } }
|
|
113
|
+
const brief = { fix: [{ id: 'gh-review-11' }, { id: 'R1' }] }
|
|
114
|
+
assert.deepEqual([...handledIds(prior, brief)].sort(), ['gh-comment-41', 'gh-review-11'])
|
|
115
|
+
assert.deepEqual([...handledIds(prior, null)], ['gh-comment-41'], 'no fix brief: the prior round handed nothing on')
|
|
116
|
+
assert.deepEqual([...handledIds(null)], [])
|
|
117
|
+
const own = { id: 'R2', severity: 'P1' }
|
|
118
|
+
const stale = { id: 'gh-comment-1', severity: 'P1', source: 'github' }
|
|
119
|
+
const fresh = { findings: [{ id: 'gh-comment-2', severity: 'P1', source: 'github' }], awaiting: [], ignored: [], head: HEAD, prAuthor: 'cory-bot' }
|
|
120
|
+
const merged = mergeExternal({ version: 1, findings: [own, stale], external: { added: ['gh-comment-1'] } }, fresh, new Set(['gh-review-11']))
|
|
121
|
+
assert.deepEqual(merged.findings.map((f) => f.id), ['R2', 'gh-comment-2'])
|
|
122
|
+
assert.deepEqual(merged.external.added, ['gh-comment-2'])
|
|
123
|
+
assert.deepEqual(merged.external.handled, ['gh-review-11'])
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
const PASSES = { spec: 'done', correctness: 'done', tests: 'done', standards: 'done' }
|
|
127
|
+
const structuredBody = (head, extra = {}) => `${STRUCTURED_MARKER}\nReview by Agentier\n\n\`\`\`json\n${JSON.stringify({ version: 1, head, complete: true, passes: PASSES, findings: [], commands: ['npm test'], ...extra })}\n\`\`\`\n`
|
|
128
|
+
|
|
129
|
+
test('a structured result stands in only when trusted and of this HEAD, newest first', () => {
|
|
130
|
+
const comments = [
|
|
131
|
+
{ databaseId: 51, author: { login: 'alo335' }, body: structuredBody(HEAD, { commands: ['older'] }), createdAt: '2026-10-08T10:00:00Z', url: 'u51' },
|
|
132
|
+
{ databaseId: 52, author: { login: 'alo335' }, body: structuredBody(HEAD, { commands: ['newer'] }), createdAt: '2026-10-08T11:00:00Z', url: 'u52' },
|
|
133
|
+
{ databaseId: 53, author: { login: 'alo335' }, body: structuredBody('other99'), createdAt: '2026-10-08T12:00:00Z', url: 'u53' },
|
|
134
|
+
{ databaseId: 54, author: { login: 'reader' }, body: structuredBody(HEAD, { commands: ['untrusted'] }), createdAt: '2026-10-08T13:00:00Z', url: 'u54' },
|
|
135
|
+
{ databaseId: 55, author: { login: 'cory-bot' }, body: structuredBody(HEAD, { commands: ['self'] }), createdAt: '2026-10-08T14:00:00Z', url: 'u55' },
|
|
136
|
+
{ databaseId: 56, author: { login: 'alo335' }, body: `${STRUCTURED_MARKER}\n\`\`\`json\n{not json\n\`\`\``, createdAt: '2026-10-08T15:00:00Z', url: 'u56' },
|
|
137
|
+
]
|
|
138
|
+
const found = findStructured(pr({ comments }), HEAD, { permissionOf })
|
|
139
|
+
assert.deepEqual(found.result.commands, ['newer'])
|
|
140
|
+
assert.deepEqual(found.source, { kind: 'github', author: 'alo335', url: 'u52' })
|
|
141
|
+
assert.equal(findStructured(pr({ comments: comments.slice(2) }), HEAD, { permissionOf }), null)
|
|
142
|
+
const tagged = collectExternal(pr({ comments: [{ ...comments[0], body: structuredBody(HEAD, { findings: [{ id: 'R1', severity: 'P1' }] }).replace('Review by', 'P1 Review by') }] }), HEAD, { permissionOf })
|
|
143
|
+
assert.deepEqual(tagged.findings, [], 'a structured result is never read as a tagged comment')
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test('parsePr takes owner/name#N only', () => {
|
|
147
|
+
assert.deepEqual(parsePr('1aboveio/ai#1545'), { owner: '1aboveio', name: 'ai', number: 1545 })
|
|
148
|
+
assert.throws(() => parsePr('1545'), /owner\/name#N/)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test('run merges into the round file using the prior beside it, and writes a structured result with the caller\'s counters', (t) => {
|
|
152
|
+
const dir = mkdtempSync(join(tmpdir(), 'github-reviews-'))
|
|
153
|
+
t.after(() => rmSync(dir, { recursive: true, force: true }))
|
|
154
|
+
const deps = (node) => ({ fetch: () => node, permissions: () => permissionOf })
|
|
155
|
+
writeFileSync(join(dir, '7-r1.json'), JSON.stringify({ findings: [{ id: 'gh-review-11', severity: 'P1', source: 'github' }], external: { added: ['gh-review-11'] } }))
|
|
156
|
+
writeFileSync(join(dir, '7-r1.fix.json'), JSON.stringify({ fix: [{ id: 'gh-review-11' }] }))
|
|
157
|
+
const path = join(dir, '7-r2.json')
|
|
158
|
+
writeFileSync(path, JSON.stringify({ version: 1, unit: '7', round: 2, head: HEAD, findings: [] }))
|
|
159
|
+
const node = pr({ reviews: [review(11, 'vera932', 'CHANGES_REQUESTED', 'x', '2026-10-08T10:00:00Z', 'old1234')], threads: [thread(61, 'vera932', 'P1: new')] })
|
|
160
|
+
const merged = run(['merge', '--pr', 'o/r#1', '--head', HEAD, '--review', path], deps(node))
|
|
161
|
+
assert.deepEqual(merged.out.awaiting.map((a) => a.id), ['gh-review-11'])
|
|
162
|
+
const file = JSON.parse(readFileSync(path, 'utf8'))
|
|
163
|
+
assert.deepEqual(file.findings.map((f) => f.id), ['gh-comment-61'])
|
|
164
|
+
assert.deepEqual(file.external.awaiting.map((a) => a.id), ['gh-review-11'])
|
|
165
|
+
assert.deepEqual(file.external.handled, ['gh-review-11'])
|
|
166
|
+
|
|
167
|
+
const out = join(dir, '7-r3.json')
|
|
168
|
+
const none = run(['structured', '--pr', 'o/r#1', '--head', HEAD, '--out', out, '--unit', '7', '--round', '3', '--fix-round', '2'], deps(pr()))
|
|
169
|
+
assert.equal(none.out.found, false)
|
|
170
|
+
const withResult = pr({ comments: [{ databaseId: 71, author: { login: 'alo335' }, body: structuredBody(HEAD, { unit: 'theirs', round: 9, fixRound: 9 }), createdAt: '2026-10-08T10:00:00Z', url: 'u71' }] })
|
|
171
|
+
const written = run(['structured', '--pr', 'o/r#1', '--head', HEAD, '--out', out, '--unit', '7', '--round', '3', '--fix-round', '2'], deps(withResult))
|
|
172
|
+
assert.equal(written.out.found, true)
|
|
173
|
+
const result = JSON.parse(readFileSync(out, 'utf8'))
|
|
174
|
+
assert.deepEqual([result.unit, result.round, result.fixRound, result.source.author], ['7', 3, 2, 'alo335'])
|
|
175
|
+
assert.throws(() => run(['merge', '--pr', 'o/r#1', '--head', HEAD], deps(node)), /usage/)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
test('a GitHub finding from an incomplete or handed-back round is not handled: it still blocks', (t) => {
|
|
179
|
+
const dir = mkdtempSync(join(tmpdir(), 'github-reviews-nofix-'))
|
|
180
|
+
t.after(() => rmSync(dir, { recursive: true, force: true }))
|
|
181
|
+
// round 1 was incomplete (or handed back): the verdict wrote no fix brief
|
|
182
|
+
writeFileSync(join(dir, '7-r1.json'), JSON.stringify({ complete: false, findings: [{ id: 'gh-comment-1', severity: 'P1', source: 'github' }], external: { added: ['gh-comment-1'] } }))
|
|
183
|
+
const path = join(dir, '7-r2.json')
|
|
184
|
+
writeFileSync(path, JSON.stringify({ version: 1, unit: '7', round: 2, head: HEAD, findings: [] }))
|
|
185
|
+
run(['merge', '--pr', 'o/r#1', '--head', HEAD, '--review', path], { fetch: () => pr({ threads: [thread(1, 'vera932', 'P1: still broken')] }), permissions: () => permissionOf })
|
|
186
|
+
assert.deepEqual(JSON.parse(readFileSync(path, 'utf8')).findings.map((f) => f.id), ['gh-comment-1'])
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('merge never removes a reviewer finding that only claims github provenance', () => {
|
|
190
|
+
const forged = { id: 'R1', severity: 'P1', summary: 'real local blocker', source: 'github' }
|
|
191
|
+
const merged = mergeExternal({ version: 1, findings: [forged] }, { findings: [], awaiting: [], ignored: [], head: HEAD }, new Set())
|
|
192
|
+
assert.deepEqual(merged.findings, [forged])
|
|
193
|
+
const faked = mergeExternal({ version: 1, findings: [forged], external: { added: ['R1'] } }, { findings: [], awaiting: [], ignored: [], head: HEAD }, new Set())
|
|
194
|
+
assert.deepEqual(faked.findings, [forged], 'listing a reviewer id under external.added does not make it removable')
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
test('every page is read: a standing changes request behind 100 comments still blocks', () => {
|
|
198
|
+
const standing = review(1, 'vera932', 'CHANGES_REQUESTED', 'no', '2026-10-08T09:00:00Z')
|
|
199
|
+
const chatter = Array.from({ length: 150 }, (_, i) => review(100 + i, 'alo335', 'COMMENTED', 'ok', '2026-10-08T10:00:00Z'))
|
|
200
|
+
const all = { reviews: [standing, ...chatter], reviewThreads: [], comments: [] }
|
|
201
|
+
const calls = []
|
|
202
|
+
const query = (text, { after }) => {
|
|
203
|
+
const field = Object.keys(all).find((f) => text.includes(`${f}(first: 100`))
|
|
204
|
+
calls.push([field, after])
|
|
205
|
+
const start = after ? Number(after) : 0
|
|
206
|
+
const nodes = all[field].slice(start, start + 100)
|
|
207
|
+
const next = start + 100 < all[field].length
|
|
208
|
+
return { data: { repository: { pullRequest: { author: { login: 'cory-bot' }, [field]: { nodes, pageInfo: { hasNextPage: next, endCursor: next ? String(start + 100) : null } } } } } }
|
|
209
|
+
}
|
|
210
|
+
const fetched = fetchPr({ owner: 'o', name: 'r', number: 1 }, query)
|
|
211
|
+
assert.equal(fetched.reviews.nodes.length, 151)
|
|
212
|
+
assert.deepEqual(calls.filter(([f]) => f === 'reviews'), [['reviews', null], ['reviews', '100']])
|
|
213
|
+
assert.deepEqual(collectExternal(fetched, HEAD, { permissionOf }).findings.map((f) => f.id), ['gh-review-1'])
|
|
214
|
+
assert.throws(() => fetchPr({ owner: 'o', name: 'r', number: 1 }, () => ({ data: { repository: { pullRequest: { reviews: { nodes: [], pageInfo: { hasNextPage: true, endCursor: null } } } } } })), /without a cursor/)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
test('an access lookup that fails stops the merge; only "not a user" means no access', () => {
|
|
218
|
+
const failing = (message) => permissionFetcher({ owner: 'o', name: 'r' }, () => { throw new Error(message) })
|
|
219
|
+
assert.throws(() => failing('gh api failed: connection reset')('vera932'), /cannot tell vera932's access/)
|
|
220
|
+
assert.equal(failing('gh: x is not a user (HTTP 404)')('x'), 'none')
|
|
221
|
+
const answering = (body) => permissionFetcher({ owner: 'o', name: 'r' }, () => JSON.stringify(body))
|
|
222
|
+
assert.equal(answering({ permission: 'none', role_name: '' })('codex[bot]'), 'none', 'a bot answers 200 with no access')
|
|
223
|
+
assert.equal(answering({ permission: 'write', role_name: 'maintain' })('alo335'), 'write')
|
|
224
|
+
assert.equal(answering({ permission: 'write', role_name: 'Security reviewer' })('sec'), 'write', 'a custom role keeps its base access')
|
|
225
|
+
const blocked = () => collectExternal(pr({ reviews: [review(1, 'alo335', 'CHANGES_REQUESTED', 'no')] }), HEAD, { permissionOf: failing('HTTP 502') })
|
|
226
|
+
assert.throws(blocked, /cannot tell alo335's access/)
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
test('a GitHub finding the reviewer carried forward is replaced, not doubled or refused', () => {
|
|
230
|
+
const carried = { id: 'gh-comment-1', severity: 'P1', summary: 'carried by the reviewer', trigger: 't', rule: 'r', paths: [{ path: 'a', status: 'open' }] }
|
|
231
|
+
const current = collectExternal(pr({ threads: [thread(1, 'vera932', 'P1: still broken')] }), HEAD, { permissionOf })
|
|
232
|
+
const merged = mergeExternal({ version: 1, findings: [carried, { id: 'R2', severity: 'P3' }] }, current, new Set())
|
|
233
|
+
assert.deepEqual(merged.findings.map((f) => [f.id, f.summary]), [['R2', undefined], ['gh-comment-1', 'still broken']])
|
|
234
|
+
const resolved = mergeExternal({ version: 1, findings: [carried] }, collectExternal(pr(), HEAD, { permissionOf }), new Set())
|
|
235
|
+
assert.deepEqual(resolved.findings, [], 'GitHub decides whether its finding is still open')
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
test('GitHub findings carry the blocking shape, so the verdict needs no exemption', () => {
|
|
239
|
+
const [finding] = collectExternal(pr({ threads: [thread(1, 'vera932', 'P1: still broken')] }), HEAD, { permissionOf }).findings
|
|
240
|
+
assert.match(finding.trigger, /vera932's P1 comment on GitHub/)
|
|
241
|
+
assert.ok(finding.rule)
|
|
242
|
+
assert.deepEqual(finding.paths, [{ path: 'src/a.ts:12', status: 'open' }])
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
test('a comment that only mentions the marker is still read as feedback', () => {
|
|
246
|
+
for (const body of [
|
|
247
|
+
`P1: Parser drops feedback containing ${STRUCTURED_MARKER}`,
|
|
248
|
+
`P1: Parser drops feedback containing ${STRUCTURED_MARKER}\n\n\`\`\`json\n{}\n\`\`\``,
|
|
249
|
+
`P1: wrong\n${STRUCTURED_MARKER}\n\`\`\`json\n{"head":"${HEAD}"}\n\`\`\``,
|
|
250
|
+
]) {
|
|
251
|
+
const external = collectExternal(pr({ threads: [thread(1, 'vera932', body)] }), HEAD, { permissionOf })
|
|
252
|
+
assert.deepEqual(external.findings.map((f) => f.id), ['gh-comment-1'], body)
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
test('a posted result must be complete by the reviewer\'s own rules', () => {
|
|
257
|
+
const posted = (extra) => pr({ comments: [{ databaseId: 1, author: { login: 'alo335' }, body: structuredBody(HEAD, extra), createdAt: '2026-10-08T10:00:00Z', url: 'u1' }] })
|
|
258
|
+
assert.ok(findStructured(posted({}), HEAD, { permissionOf }))
|
|
259
|
+
assert.equal(findStructured(posted({ complete: undefined, passes: undefined }), HEAD, { permissionOf }), null)
|
|
260
|
+
assert.equal(findStructured(posted({ passes: { ...PASSES, tests: 'partial' } }), HEAD, { permissionOf }), null)
|
|
261
|
+
assert.equal(findStructured(posted({ commands: [] }), HEAD, { permissionOf }), null)
|
|
262
|
+
assert.equal(findStructured(posted({ findings: undefined }), HEAD, { permissionOf }), null)
|
|
263
|
+
assert.equal(findStructured(posted({ complete: undefined }), HEAD, { permissionOf }), null, 'all passes done but no complete: true')
|
|
264
|
+
})
|