@joshuafolkken/kit 1.1035.0 → 1.1094.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/.claude/settings.json +9 -8
- package/.claude/skills/epic-commands/SKILL.md +5 -7
- package/.claude/skills/verify-ui/SKILL.md +1 -1
- package/.claude/skills/workflow-commands/SKILL.md +21 -13
- package/.claude/skills/workflow-commands/background-commands.md +5 -4
- package/.claude/skills/workflow-commands/backlogrun-child.md +3 -3
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +9 -7
- package/.claude/skills/workflow-commands/backlogrun-park.md +6 -0
- package/.claude/skills/workflow-commands/backlogrun-progress.md +31 -37
- package/.claude/skills/workflow-commands/backlogrun-steps.md +25 -25
- package/.claude/skills/workflow-commands/backlogrun.md +2 -2
- package/.claude/skills/workflow-commands/chain-rule.md +6 -4
- package/.claude/skills/workflow-commands/delegation.md +3 -3
- package/.claude/skills/workflow-commands/followup-reference.md +3 -3
- package/.claude/skills/workflow-commands/followup.md +7 -3
- package/.claude/skills/workflow-commands/fullrun-steps.md +3 -2
- package/.claude/skills/workflow-commands/fullrun.md +3 -2
- package/.claude/skills/workflow-commands/halfrun.md +16 -19
- package/.claude/skills/workflow-commands/issue-comments.md +2 -2
- package/.claude/skills/workflow-commands/issue-fold-existing.md +10 -10
- package/.claude/skills/workflow-commands/issue-scout.md +1 -1
- package/.claude/skills/workflow-commands/kickoff.md +9 -4
- package/.claude/skills/workflow-commands/latest-gate.md +1 -1
- package/.claude/skills/workflow-commands/needs-human-review.md +1 -1
- package/.claude/skills/workflow-commands/observation-filing.md +44 -89
- package/.claude/skills/workflow-commands/pre-gate-cut.md +33 -36
- package/.claude/skills/workflow-commands/prerequisite.md +1 -1
- package/.claude/skills/workflow-commands/prrun.md +36 -0
- package/.claude/skills/workflow-commands/retrospective.md +1 -1
- package/.claude/skills/workflow-commands/working-tree-hold.md +24 -4
- package/.codex/hooks.json +16 -20
- package/.github/actions/setup-pnpm/action.yml +93 -0
- package/.github/dependabot.yml +5 -1
- package/.github/workflows/auto-tag.yml +21 -0
- package/.github/workflows/ci.yml +26 -128
- package/.github/workflows/pr-classification.yml +5 -41
- package/.github/workflows/publish.yml +42 -48
- package/.github/workflows/sonar-qube.yml +37 -4
- package/AGENTS.md +12 -2
- package/CLAUDE.md +15 -16
- package/GEMINI.md +14 -2
- package/README.md +59 -18
- package/SECURITY.md +36 -6
- package/cspell/index.yaml +2 -1
- package/dist/CLAUDE.md +15 -16
- package/dist/config-merge/index.js +15 -3
- package/dist/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-Y27M6A7X.js → chunk-3C4YCCEF.js} +5 -13
- package/dist/hooks/{chunk-D67KVGGY.js → chunk-7ZPFAVR7.js} +17 -88
- package/dist/hooks/{chunk-HOYJU5RM.js → chunk-E54KGFEQ.js} +11 -4
- package/dist/hooks/{chunk-KG6PZD72.js → chunk-IL27YTPH.js} +2 -2
- package/dist/hooks/{chunk-GJT4BNVE.js → chunk-KHWHKJOM.js} +3 -3
- package/dist/hooks/{chunk-HU66PWWL.js → chunk-M2XDCUKD.js} +1076 -509
- package/dist/hooks/{chunk-GLBDBJOK.js → chunk-NKOG3XAV.js} +199 -304
- package/dist/hooks/{chunk-ZXNE6NY3.js → chunk-O4LL4HTI.js} +2 -7
- package/dist/hooks/{chunk-XPLP27WA.js → chunk-OIZUHPJI.js} +5 -27
- package/dist/hooks/{chunk-EIFCAJRB.js → chunk-UD24EAIL.js} +2546 -1636
- package/dist/hooks/codex-hook-adapter.js +10 -11
- package/dist/hooks/duplicate-read-guard.js +5 -5
- package/dist/hooks/format-edited.js +15 -34
- package/dist/hooks/inputs.json +279 -0
- package/dist/hooks/investigation-guard.js +5 -5
- package/dist/hooks/pretool-guard.js +23 -16
- package/dist/hooks/session-lang.js +0 -1
- package/dist/hooks/stop-guard.js +188 -509
- package/dist/josh.js +166 -225
- package/dist/managed-marker/index.js +2 -1
- package/dist/version/index.js +412 -64
- package/eslint/rules/promise.js +5 -0
- package/package.json +13 -8
- package/prompts/collaboration-workflow/file-edits.md +6 -53
- package/prompts/collaboration-workflow/issue-citation.md +5 -5
- package/prompts/collaboration-workflow/operating-rules.md +4 -4
- package/prompts/collaboration-workflow/overview.md +13 -1
- package/prompts/collaboration-workflow/report-format.md +1 -1
- package/prompts/collaboration-workflow/residency.md +4 -4
- package/prompts/collaboration-workflow/rule-delivery.md +5 -36
- package/prompts/collaboration-workflow/shell-body.md +7 -9
- package/prompts/collaboration-workflow/turn-batching.md +21 -37
- package/prompts/collaboration-workflow/upstream-interrupt.md +1 -1
- package/prompts/collaboration-workflow/wip-cap.md +36 -36
- package/prompts/collaboration-workflow.md +5 -8
- package/prompts/review-rubric.md +1 -1
- package/prompts/review.md +3 -3
- package/prompts/sonar-hotspot-handling.md +12 -4
- package/prompts/testing-guide.md +3 -3
- package/scripts/adopt/adopt-toolkits.ts +2 -2
- package/scripts/adopt/adopt.ts +0 -2
- package/scripts/agent/agent-argv.ts +0 -2
- package/scripts/agent/agent-diagnostics.ts +1 -1
- package/scripts/agent/agent-role-profile.ts +0 -7
- package/scripts/agent/claude-agent-argv.ts +0 -2
- package/scripts/agent/codex-agent-argv.ts +1 -1
- package/scripts/agent/codex-hooks.ts +92 -0
- package/scripts/agent/interactive-ask.ts +1 -1
- package/scripts/auto-ok/auto-ok-cli.ts +15 -25
- package/scripts/backlog/backlog-budget.ts +0 -2
- package/scripts/backlog/backlog-defect-priority.ts +2 -2
- package/scripts/backlog/backlog-drive-cli.ts +10 -8
- package/scripts/backlog/backlog-drive-named-offer.ts +16 -3
- package/scripts/backlog/backlog-drive-named.ts +2 -1
- package/scripts/backlog/backlog-drive.ts +76 -16
- package/scripts/backlog/backlog-named.ts +0 -3
- package/scripts/backlog/backlog-next.ts +32 -22
- package/scripts/backlog/backlog-offer-cli.ts +0 -5
- package/scripts/backlog/backlog-plan-cli.ts +6 -8
- package/scripts/backlog/backlog-plan.ts +1 -1
- package/scripts/backlog/backlog-pool.ts +34 -17
- package/scripts/backlog/backlog-rank.ts +106 -0
- package/scripts/backlog/backlog-ready.ts +19 -3
- package/scripts/backlog/backlog-scope.ts +1 -3
- package/scripts/backlog/backlog-stalled-detect.ts +2 -2
- package/scripts/backlog/backlog-stalled.ts +0 -1
- package/scripts/backlog/backlog-waves.ts +47 -31
- package/scripts/behavior/behavior-cli.ts +1 -1
- package/scripts/build/build-codex-hooks.ts +26 -0
- package/scripts/build/build-hooks.ts +92 -11
- package/scripts/build/hook-bundle-stamp.ts +111 -0
- package/scripts/cases/cases-cli.ts +1 -1
- package/scripts/cases/cases-logic.ts +1 -1
- package/scripts/ci/composite-actions.ts +30 -0
- package/scripts/ci/pr-classification.ts +3 -3
- package/scripts/claude/workflow-destination.ts +10 -1
- package/scripts/clone/clone-fingerprint.ts +0 -1
- package/scripts/clone/clone-scan-cli.ts +1 -1
- package/scripts/clone/clone-scan.ts +1 -1
- package/scripts/config-merge/json-format.ts +0 -1
- package/scripts/config-merge/patch-yaml-list.ts +1 -1
- package/scripts/cost-runtime/codex-usage.ts +2 -9
- package/scripts/cost-runtime/cost-attribute.ts +0 -1
- package/scripts/cost-runtime/cost-cli.ts +0 -3
- package/scripts/cost-runtime/cost-corpus.ts +0 -2
- package/scripts/cost-runtime/cost-format.ts +0 -3
- package/scripts/cost-runtime/cost-pricing.ts +0 -1
- package/scripts/cost-runtime/cost-tokens.ts +0 -1
- package/scripts/delegation/delegation-cli.ts +1 -1
- package/scripts/delegation/delegation-policy.ts +11 -1
- package/scripts/delegation/duplicate-read-guard.ts +0 -8
- package/scripts/delegation/fanout-cli.ts +1 -1
- package/scripts/delegation/fanout.ts +0 -2
- package/scripts/delegation/investigation-reads.ts +1 -164
- package/scripts/discovery/repo-map-logic.ts +0 -1
- package/scripts/discovery/repo-party-cli.ts +1 -1
- package/scripts/doctor/doctor-consumer.ts +4 -6
- package/scripts/doctor/doctor-logic.ts +0 -4
- package/scripts/doctor/doctor.ts +14 -9
- package/scripts/document/document-byte-budget.ts +8 -9
- package/scripts/document/document-byte-check.ts +0 -1
- package/scripts/document/document-read-cli.ts +0 -2
- package/scripts/document/document-section-cli.ts +1 -1
- package/scripts/document/document-section.ts +0 -3
- package/scripts/document/edit-files-cli.ts +0 -1
- package/scripts/document/entry-read-budget.ts +13 -4
- package/scripts/document/lane-child-read-set.ts +1 -6
- package/scripts/document/read-files-cli.ts +0 -4
- package/scripts/document/read-set-cli.ts +0 -2
- package/scripts/document/read-set-point-of-use.ts +1 -0
- package/scripts/document/read-set-pricing.ts +0 -1
- package/scripts/document/read-set-trim.ts +1 -1
- package/scripts/epic/epic-add.ts +1 -1
- package/scripts/epic/epic-audit-cli.ts +1 -4
- package/scripts/epic/epic-audit-orphans.ts +1 -3
- package/scripts/epic/epic-audit-rationale.ts +0 -1
- package/scripts/epic/epic-audit-report.ts +0 -2
- package/scripts/epic/epic-audit.ts +5 -4
- package/scripts/epic/epic-bundle-cli.ts +1 -1
- package/scripts/epic/epic-bundle-evidence.ts +0 -1
- package/scripts/epic/epic-bundle-referenced.ts +1 -5
- package/scripts/epic/epic-bundle.ts +0 -4
- package/scripts/epic/epic-busy.ts +4 -5
- package/scripts/epic/epic-candidate-confirm.ts +0 -5
- package/scripts/epic/epic-classify.ts +2 -2
- package/scripts/epic/epic-cli-argv.ts +0 -1
- package/scripts/epic/epic-close-comment.ts +1 -1
- package/scripts/epic/epic-close.ts +5 -2
- package/scripts/epic/epic-cross-repo.ts +2 -4
- package/scripts/epic/epic-decision.ts +0 -1
- package/scripts/epic/epic-fetch.ts +2 -2
- package/scripts/epic/epic-index.ts +2 -3
- package/scripts/epic/epic-issue.ts +1 -15
- package/scripts/epic/epic-lane-offer.ts +20 -30
- package/scripts/epic/epic-nested.ts +1 -1
- package/scripts/epic/epic-next-read.ts +1 -3
- package/scripts/epic/epic-next-views.ts +0 -1
- package/scripts/epic/epic-next.ts +1 -10
- package/scripts/epic/epic-parse.ts +0 -1
- package/scripts/epic/epic-promote.ts +0 -9
- package/scripts/epic/epic-rank.ts +37 -0
- package/scripts/epic/epic-read.ts +1 -2
- package/scripts/epic/epic-reconcile.ts +1 -1
- package/scripts/epic/epic-reference.ts +1 -13
- package/scripts/epic/epic-relation-recheck.ts +1 -1
- package/scripts/epic/epic-relations.ts +1 -1
- package/scripts/epic/epic-remove.ts +1 -1
- package/scripts/epic/epic-report.ts +0 -1
- package/scripts/epic/epic-run.ts +2 -3
- package/scripts/epic/epic-shape.ts +1 -1
- package/scripts/epic/epic-solo-stale.ts +107 -0
- package/scripts/epic/epic-solo.ts +32 -30
- package/scripts/epic/epic-triage.ts +1 -1
- package/scripts/epic/epic-validate.ts +1 -1
- package/scripts/epic/epic.ts +1 -1
- package/scripts/exports/unused-members-cli.ts +53 -0
- package/scripts/{git → followup}/followup-issue-number.ts +1 -6
- package/scripts/{git → followup}/git-followup-cleanup.ts +2 -1
- package/scripts/{git → followup}/git-followup-finish.ts +1 -3
- package/scripts/{git → followup}/git-followup-flush.ts +2 -2
- package/scripts/{git → followup}/git-followup-issue-close.ts +2 -2
- package/scripts/{git → followup}/git-followup-label.ts +4 -4
- package/scripts/followup/git-followup-merged.ts +112 -0
- package/scripts/{git → followup}/git-followup-pending.ts +1 -1
- package/scripts/{git → followup}/git-followup-workflow.ts +19 -11
- package/scripts/{git → followup}/git-pr-followup-wrapup.ts +9 -3
- package/scripts/{git → followup}/git-pr-followup.ts +53 -16
- package/scripts/gate/core-budget.ts +3 -9
- package/scripts/gate/gate-plan.ts +7 -4
- package/scripts/gate/gate-report.ts +0 -6
- package/scripts/gate/gate-tree.ts +1 -1
- package/scripts/gate/pre-commit-type-check.ts +0 -6
- package/scripts/gate/project-checks.ts +7 -8
- package/scripts/gate/scoped-green.ts +0 -1
- package/scripts/gate/type-check-step.ts +1 -1
- package/scripts/gate/verification-gate.ts +0 -2
- package/scripts/gh/fix-gh-packages.ts +15 -12
- package/scripts/gh/gh-spawn.ts +2 -2
- package/scripts/{git → gh}/git-ai-review-scan.ts +1 -5
- package/scripts/{git → gh}/git-gh-check.ts +2 -1
- package/scripts/{git → gh}/git-gh-exec.ts +1 -1
- package/scripts/{git → gh}/git-gh-failure.ts +1 -1
- package/scripts/{git → gh}/git-gh-helpers.ts +0 -1
- package/scripts/{git → gh}/git-gh-issue-list.ts +2 -0
- package/scripts/{git → gh}/git-gh-issue-read.ts +1 -1
- package/scripts/{git → gh}/git-gh-issue-rest.ts +6 -3
- package/scripts/{git → gh}/git-gh-pr-read.ts +24 -0
- package/scripts/{git → gh}/git-gh-pr-rest.ts +5 -2
- package/scripts/{git → gh}/git-gh-pr-review.ts +2 -2
- package/scripts/{git → gh}/git-gh-pr-rollup.ts +1 -3
- package/scripts/{git → gh}/git-gh-pr.ts +3 -1
- package/scripts/{git → gh}/git-pr-ai-review.ts +2 -5
- package/scripts/{git → gh}/git-pr-checks-eval.ts +0 -10
- package/scripts/{git → gh}/git-pr-checks-parse.ts +4 -20
- package/scripts/{git → gh}/git-pr-checks.ts +2 -1
- package/scripts/{git → gh}/git-pr-coderabbit.ts +2 -5
- package/scripts/{git → gh}/git-pr-confirmation.ts +1 -3
- package/scripts/{git → gh}/git-pr-managed-config.ts +2 -3
- package/scripts/{git → gh}/git-pr.ts +3 -3
- package/scripts/git/changed-file-scope.ts +0 -1
- package/scripts/git/git-branch.ts +3 -6
- package/scripts/git/git-command.ts +1 -1
- package/scripts/git/git-commit.ts +1 -1
- package/scripts/git/git-common-directory.ts +80 -24
- package/scripts/git/git-error.ts +66 -9
- package/scripts/git/git-execa-error.ts +6 -1
- package/scripts/git/git-issue.ts +1 -1
- package/scripts/git/git-preflight.ts +24 -6
- package/scripts/git/git-prompt.ts +6 -31
- package/scripts/git/git-push-transport.ts +12 -69
- package/scripts/git/git-push.ts +1 -1
- package/scripts/git/git-spawn.ts +79 -3
- package/scripts/git/git-ssh-keepalive.ts +74 -0
- package/scripts/git/git-staging.ts +1 -0
- package/scripts/git/git-status.ts +1 -1
- package/scripts/git/git-workflow.ts +1 -1
- package/scripts/git/gone-branch.ts +4 -2
- package/scripts/git/listing-cutoff.ts +1 -1
- package/scripts/git/main-merge.ts +1 -1
- package/scripts/git/main-sync.ts +19 -1
- package/scripts/git/parse-json-array.ts +0 -3
- package/scripts/git/repository-lock.ts +16 -16
- package/scripts/git/{git-stash.ts → stash/git-stash.ts} +1 -1
- package/scripts/git/{stash-pop-args.ts → stash/stash-pop-args.ts} +1 -1
- package/scripts/git/{stash-pop-cli.ts → stash/stash-pop-cli.ts} +0 -1
- package/scripts/git/{stash-sweep-lock.ts → stash/stash-sweep-lock.ts} +3 -3
- package/scripts/hooks/codex-hook-adapter.ts +0 -1
- package/scripts/hooks/edited-cspell.ts +1 -1
- package/scripts/hooks/format-edited-cli.ts +15 -0
- package/scripts/hooks/format-edited-file.ts +4 -9
- package/scripts/hooks/pre-push-unit.ts +0 -4
- package/scripts/hooks/pretool-guard-cli.ts +21 -0
- package/scripts/hooks/pretool-guard.ts +36 -19
- package/scripts/hooks/reserved-run.ts +1 -1
- package/scripts/hooks/step-zero-notice.ts +76 -0
- package/scripts/hooks/stop-guard.ts +47 -21
- package/scripts/init/claude-plugin-config.ts +4 -5
- package/scripts/init/hook-command-rewrite.ts +16 -8
- package/scripts/init/hook-launch.ts +41 -15
- package/scripts/init/init-basic.ts +2 -5
- package/scripts/init/init-logic.ts +7 -0
- package/scripts/init/init.ts +0 -3
- package/scripts/init/project-profile.ts +6 -11
- package/scripts/init/start-exec.ts +8 -2
- package/scripts/init/start-plan.ts +18 -5
- package/scripts/init/start-setup-pr.ts +52 -25
- package/scripts/init/start-steps.ts +42 -9
- package/scripts/init/start.ts +40 -10
- package/scripts/issue/behavior-change-lint.ts +0 -1
- package/scripts/issue/defect-rate-cli.ts +3 -3
- package/scripts/issue/defect-rate.ts +1 -2
- package/scripts/{git → issue}/git-next-issues.ts +37 -8
- package/scripts/issue/issue-backlinks-cli.ts +1 -5
- package/scripts/issue/issue-cite-cli.ts +2 -6
- package/scripts/issue/issue-cite.ts +19 -3
- package/scripts/issue/issue-classification.ts +1 -9
- package/scripts/issue/issue-closing-pr.ts +4 -4
- package/scripts/issue/issue-comment-cli.ts +2 -2
- package/scripts/issue/issue-file-cli.ts +5 -5
- package/scripts/issue/issue-file.ts +2 -2
- package/scripts/issue/issue-fold-cli.ts +1 -1
- package/scripts/issue/issue-fold-existing-cli.ts +1 -1
- package/scripts/{git → issue}/issue-labels.ts +20 -0
- package/scripts/issue/issue-lint-cli.ts +1 -1
- package/scripts/issue/issue-merged.ts +2 -2
- package/scripts/issue/issue-rank.ts +89 -0
- package/scripts/issue/issue-read-cli.ts +8 -47
- package/scripts/issue/issue-read.ts +0 -2
- package/scripts/issue/issue-report-failures.ts +80 -0
- package/scripts/issue/issue-scout-cli.ts +1 -7
- package/scripts/issue/issue-scout-closed.ts +2 -2
- package/scripts/issue/issue-scout.ts +0 -2
- package/scripts/issue/issue-state-cli.ts +5 -46
- package/scripts/issue/issue-state.ts +1 -5
- package/scripts/issue/markdown-section.ts +1 -1
- package/scripts/issue/measure-rerun-cli.ts +1 -1
- package/scripts/issue/reproduction-measure.ts +1 -1
- package/scripts/josh/cli-body.ts +1 -1
- package/scripts/josh/file-map-stamp.ts +0 -3
- package/scripts/josh/josh-command-map.ts +2 -102
- package/scripts/josh/josh-commands-ai.ts +9 -102
- package/scripts/josh/josh-commands-backlog.ts +5 -1
- package/scripts/josh/josh-commands-development.ts +10 -3
- package/scripts/josh/josh-commands-issue.ts +2 -2
- package/scripts/josh/josh-commands-maintenance.ts +7 -0
- package/scripts/josh/josh-commands-project.ts +7 -1
- package/scripts/josh/josh-commands-run.ts +114 -0
- package/scripts/josh/josh-commands-versioning.ts +8 -0
- package/scripts/josh/josh-commands-workflow.ts +2 -2
- package/scripts/josh/path-decision.ts +0 -3
- package/scripts/josh/process-owner.ts +16 -0
- package/scripts/josh/session-language-cli.ts +0 -1
- package/scripts/lane/lane-await.ts +3 -6
- package/scripts/lane/lane-capacity.ts +6 -3
- package/scripts/lane/lane-child-invocation.ts +20 -7
- package/scripts/lane/lane-child-marker.ts +16 -2
- package/scripts/lane/lane-cli.ts +33 -18
- package/scripts/lane/lane-close.ts +1 -3
- package/scripts/lane/lane-dispatch.ts +3 -4
- package/scripts/lane/lane-environment.ts +0 -9
- package/scripts/lane/lane-handoff.ts +38 -0
- package/scripts/lane/lane-launch-cli.ts +21 -11
- package/scripts/lane/lane-open.ts +0 -3
- package/scripts/lane/lane-output.ts +0 -1
- package/scripts/lane/lane-registry.ts +9 -0
- package/scripts/lane/lane-report.ts +10 -4
- package/scripts/lane/openai-lane-ship.ts +2 -0
- package/scripts/lane/openai-lane-supervisor-decision.ts +2 -1
- package/scripts/lane/openai-lane-supervisor.ts +6 -4
- package/scripts/lane/openai-review-broker.ts +3 -0
- package/scripts/lib/bounded-pool.ts +1 -0
- package/scripts/lib/json-value.ts +5 -4
- package/scripts/lib/poll.ts +3 -1
- package/scripts/lib/timed-fetch.ts +15 -0
- package/scripts/lib/timeouts.ts +13 -0
- package/scripts/{yaml → lib}/yaml-document.ts +3 -1
- package/scripts/{bytes → lines}/bytes-command.ts +0 -2
- package/scripts/lines/lines-command.ts +0 -1
- package/scripts/lint/lint-related-scope.ts +0 -1
- package/scripts/lint/lint-related.ts +1 -1
- package/scripts/{git → notify}/telegram-notify.ts +2 -2
- package/scripts/{git → notify}/telegram-test.ts +4 -4
- package/scripts/observations/observation-ledger-home.ts +0 -1
- package/scripts/observations/observation-ledger-line.ts +0 -1
- package/scripts/observations/observation-ledger-migrate.ts +1 -1
- package/scripts/observations/observations-flush-landing.ts +3 -3
- package/scripts/observations/observations-flush.ts +2 -2
- package/scripts/overrides/lockfile-overrides.ts +1 -1
- package/scripts/overrides/overrides-logic.ts +1 -1
- package/scripts/package/package-scout-cli.ts +2 -7
- package/scripts/package/package-scout.ts +0 -2
- package/scripts/propagate/propagate-git-failure.ts +0 -1
- package/scripts/propagate/propagate-git.ts +0 -2
- package/scripts/propagate/propagate-plan.ts +185 -0
- package/scripts/propagate/propagate-publish.ts +2 -3
- package/scripts/propagate/propagate-run.ts +5 -3
- package/scripts/propagate/propagate-steps.ts +19 -6
- package/scripts/propagate/propagate-targets.ts +13 -6
- package/scripts/propagate/propagate.ts +60 -46
- package/scripts/refactor/import-graph.ts +0 -1
- package/scripts/refactor/refactor-lint.ts +0 -2
- package/scripts/refactor/refactor-scan.ts +0 -1
- package/scripts/refactor/refactor-targets.ts +0 -1
- package/scripts/registry-migration/migrate-logic.ts +1 -2
- package/scripts/registry-migration/migrate.ts +19 -9
- package/scripts/release/github-release-cli.ts +8 -6
- package/scripts/release/github-release-environment.ts +46 -0
- package/scripts/release/github-release.ts +151 -94
- package/scripts/release/release-history.ts +0 -1
- package/scripts/release/release-plan.ts +0 -1
- package/scripts/release/release-publish.ts +2 -3
- package/scripts/release/release-scope-cli.ts +1 -2
- package/scripts/release/release-tag.ts +2 -4
- package/scripts/repo/auto-merge-setting-logic.ts +1 -4
- package/scripts/repo/repo-setting.ts +1 -1
- package/scripts/repo/repository-labels.ts +3 -3
- package/scripts/repo/required-checks-logic.ts +206 -0
- package/scripts/repo/required-checks-report.ts +71 -0
- package/scripts/repo/ruleset-check-cli.ts +53 -0
- package/scripts/repo/ruleset-check.ts +147 -0
- package/scripts/report/report-lint-cli.ts +0 -4
- package/scripts/review/disposition-cli.ts +1 -1
- package/scripts/review/live-evidence.ts +1 -1
- package/scripts/review/review-attest-cli.ts +0 -6
- package/scripts/review/review-attest.ts +48 -16
- package/scripts/review/review-brief-cli.ts +72 -8
- package/scripts/review/review-brief.ts +67 -9
- package/scripts/review/review-diff-parts.ts +89 -0
- package/scripts/review/review-diff-write.ts +97 -0
- package/scripts/review/review-finding-ledger.ts +0 -1
- package/scripts/review/review-record-cli.ts +1 -1
- package/scripts/review/review-record.ts +0 -1
- package/scripts/review/review-round2-cli.ts +1 -1
- package/scripts/review/review-stamps.ts +6 -2
- package/scripts/review/review-tree.ts +14 -2
- package/scripts/rules/decision-oracle-stage.ts +18 -0
- package/scripts/rules/decision-oracle.ts +10 -7
- package/scripts/rules/delivered-rules.ts +9 -1
- package/scripts/rules/destructive-command.ts +119 -0
- package/scripts/rules/direct-filing.ts +1 -1
- package/scripts/rules/file-body.ts +0 -3
- package/scripts/rules/filing-cap.ts +0 -2
- package/scripts/rules/gh-api.ts +7 -2
- package/scripts/rules/git-argv.ts +18 -7
- package/scripts/rules/git-force.ts +0 -2
- package/scripts/rules/implementation-cut.ts +1 -2
- package/scripts/rules/index-guard.ts +71 -0
- package/scripts/rules/issue-citation.ts +50 -25
- package/scripts/rules/issue-fold.ts +1 -2
- package/scripts/rules/lane-background.ts +15 -7
- package/scripts/rules/lane-carry-conflict.ts +1 -2
- package/scripts/rules/lane-park.ts +14 -8
- package/scripts/rules/last-prompt.ts +1 -0
- package/scripts/rules/oracle-consulted.ts +1 -1
- package/scripts/rules/oracle-firing.ts +1 -0
- package/scripts/rules/oracle-list-cli.ts +0 -3
- package/scripts/rules/permission-guards.ts +14 -0
- package/scripts/rules/pre-gate-cut.ts +2 -2
- package/scripts/rules/protected-files.ts +74 -0
- package/scripts/rules/reply-language.ts +1 -1
- package/scripts/rules/rule-body-guard.ts +0 -3
- package/scripts/rules/rule-value-cli.ts +0 -1
- package/scripts/rules/shell-body-trigger.ts +0 -1
- package/scripts/rules/shell-segments.ts +0 -1
- package/scripts/rules/stop-rules.ts +13 -5
- package/scripts/rules/tail-commands.ts +1 -1
- package/scripts/rules/test-declared-commit.ts +1 -1
- package/scripts/rules/third-party-write.ts +0 -1
- package/scripts/rules/worktree-guard.ts +26 -9
- package/scripts/run/run-carry-cli.ts +0 -2
- package/scripts/run/run-carry-stash.ts +2 -2
- package/scripts/run/run-carry-streak.ts +1 -1
- package/scripts/run/run-cut-cli.ts +0 -7
- package/scripts/run/run-cut.ts +8 -54
- package/scripts/run/run-ending-cli.ts +1 -1
- package/scripts/run/run-ending.ts +1 -4
- package/scripts/run/run-entry-cli.ts +101 -24
- package/scripts/run/run-entry.ts +0 -1
- package/scripts/run/run-event-cli.ts +0 -2
- package/scripts/run/run-event-follow.ts +1 -0
- package/scripts/run/run-event-render.ts +1 -1
- package/scripts/run/run-event-scope.ts +53 -4
- package/scripts/run/run-event-stream.ts +0 -3
- package/scripts/run/run-event-watch.ts +1 -0
- package/scripts/run/run-halfrun-resume.ts +20 -7
- package/scripts/run/run-headless.ts +24 -8
- package/scripts/run/run-hold-cli.ts +33 -10
- package/scripts/run/run-hold.ts +25 -5
- package/scripts/run/run-issue-number.ts +1 -1
- package/scripts/run/run-liveness-cli.ts +1 -1
- package/scripts/run/run-liveness.ts +2 -3
- package/scripts/run/run-merge-cli.ts +5 -13
- package/scripts/run/run-merge-steps.ts +5 -5
- package/scripts/run/run-merge.ts +1 -1
- package/scripts/run/run-next-cli.ts +1 -1
- package/scripts/run/run-parent-cut-hook.ts +143 -0
- package/scripts/run/run-preflight.ts +2 -9
- package/scripts/run/run-prep-cli.ts +2 -2
- package/scripts/run/run-prep-locate.ts +1 -1
- package/scripts/run/run-progress-cli.ts +4 -7
- package/scripts/run/run-progress-clock.ts +6 -34
- package/scripts/run/run-progress-config.ts +0 -4
- package/scripts/run/run-progress-read.ts +3 -3
- package/scripts/run/run-progress.ts +22 -10
- package/scripts/run/run-prrun-resume.ts +117 -0
- package/scripts/run/run-report-cli.ts +1 -2
- package/scripts/run/run-report.ts +0 -1
- package/scripts/run/run-retrospective.ts +1 -1
- package/scripts/run/run-review-cli.ts +0 -3
- package/scripts/run/run-review-steps.ts +2 -0
- package/scripts/run/run-review.ts +0 -2
- package/scripts/run/run-ship-cli.ts +27 -77
- package/scripts/run/run-ship-detach.ts +1 -0
- package/scripts/run/run-ship-next.ts +106 -0
- package/scripts/run/run-ship-preflight.ts +87 -0
- package/scripts/run/run-ship-return.ts +8 -3
- package/scripts/run/run-ship-review-steps.ts +23 -30
- package/scripts/run/run-ship-review.ts +20 -0
- package/scripts/run/run-ship-scoped.ts +45 -0
- package/scripts/run/run-ship-stage.ts +5 -1
- package/scripts/run/run-ship-steps.ts +105 -0
- package/scripts/run/run-ship.ts +2 -1
- package/scripts/run/run-stage-read.ts +62 -0
- package/scripts/run/run-stage.ts +160 -0
- package/scripts/run/run-status-cli.ts +1 -1
- package/scripts/run/run-step-cli.ts +21 -14
- package/scripts/run/run-step.ts +1 -1
- package/scripts/run/run-stop-notify.ts +1 -3
- package/scripts/run/run-stranded-cli.ts +0 -4
- package/scripts/run/run-stranded-detect.ts +2 -2
- package/scripts/run/run-tail-cli.ts +17 -11
- package/scripts/run/run-tail.ts +2 -0
- package/scripts/run/run-tidy-lanes.ts +2 -1
- package/scripts/run/run-tidy-stashes.ts +12 -12
- package/scripts/run/run-wake-cli.ts +4 -5
- package/scripts/run/run-wake-describe.ts +19 -1
- package/scripts/run/run-wake-loop.ts +2 -2
- package/scripts/run/run-wake.ts +2 -4
- package/scripts/run/run-watcher-guard-cli.ts +0 -4
- package/scripts/run/run-watcher-guard.ts +9 -7
- package/scripts/run/run-watcher-hook.ts +1 -1
- package/scripts/safe-chain/ci-installer-pin.ts +11 -5
- package/scripts/safe-chain/project-config.ts +1 -1
- package/scripts/security/security-audit-provision-logic.ts +0 -1
- package/scripts/security/security-audit-provision.ts +3 -2
- package/scripts/security/security-audit.ts +0 -1
- package/scripts/security/security-updates-logic.ts +0 -1
- package/scripts/sonar/sonar-hotspots-cli.ts +10 -52
- package/scripts/sonar/sonar-hotspots.ts +0 -1
- package/scripts/sonar/sonar-new-code-cli.ts +150 -0
- package/scripts/sonar/sonar-new-code.ts +79 -0
- package/scripts/sonar/sonar-project.ts +97 -0
- package/scripts/split/split-assess-cli.ts +1 -1
- package/scripts/split/split-assess.ts +2 -5
- package/scripts/sync/managed-config-scope-cli.ts +1 -1
- package/scripts/sync/skill-migration.ts +0 -1
- package/scripts/sync/sync-hook-safety.ts +2 -3
- package/scripts/sync/sync.ts +1 -8
- package/scripts/sync/template-source-logic.ts +0 -1
- package/scripts/sync/workflow-pin-logic.ts +16 -28
- package/scripts/test/test-declared-logic.ts +0 -1
- package/scripts/test/test-declared.ts +0 -2
- package/scripts/test/test-red-commit.ts +2 -2
- package/scripts/test/test-red.ts +14 -7
- package/scripts/test/test-type-logic.ts +1 -1
- package/scripts/test/unit-worker-share.ts +7 -16
- package/scripts/time-runtime/time-batch-guard.ts +1 -2
- package/scripts/time-runtime/time-bundle-call.ts +2 -3
- package/scripts/time-runtime/time-bundles.ts +0 -4
- package/scripts/time-runtime/time-density-hook.ts +0 -2
- package/scripts/time-runtime/time-followup-stage.ts +1 -1
- package/scripts/time-runtime/time-format.ts +0 -73
- package/scripts/time-runtime/time-round-trips.ts +0 -4
- package/scripts/time-runtime/time-single-check.ts +1 -1
- package/scripts/time-runtime/time-spans.ts +1 -1
- package/scripts/time-runtime/time-transcript-line.ts +1 -2
- package/scripts/ui/ui-routes-cli.ts +0 -5
- package/scripts/ui/ui-routes.ts +0 -5
- package/scripts/version/effective-upstream.ts +2 -2
- package/scripts/version/latest-corepack.ts +7 -9
- package/scripts/version/latest-scope-cli.ts +0 -2
- package/scripts/version/latest-update.ts +0 -2
- package/scripts/version/npm-registry.ts +97 -0
- package/scripts/version/publishable-range-check.ts +1 -1
- package/scripts/version/release-hold.ts +0 -4
- package/scripts/version/running-binary.ts +2 -2
- package/scripts/version/version-check-logic.ts +0 -2
- package/scripts/version/version-commands.ts +5 -4
- package/scripts/version/version-remote.ts +28 -14
- package/scripts/version/version-targets.ts +4 -4
- package/templates/sonar-project.properties +28 -1
- package/templates/workflows/dependabot-auto-merge.yml +5 -3
- package/templates/workflows/github-release.yml +81 -0
- package/prompts/collaboration-workflow/cross-repo-epic.md +0 -5
- package/prompts/collaboration-workflow/epic-audit.md +0 -5
- package/prompts/collaboration-workflow/epic-bundle.md +0 -5
- package/scripts/init/plugin-install-hint.ts +0 -47
- package/scripts/version/parse-json.ts +0 -13
- /package/scripts/{git → followup}/git-followup-stages.ts +0 -0
- /package/scripts/{git → gh}/git-gh-api-path.ts +0 -0
- /package/scripts/{git → gh}/git-gh-command.ts +0 -0
- /package/scripts/{git → gh}/git-gh-issue-write.ts +0 -0
- /package/scripts/{git → gh}/git-gh-issue.ts +0 -0
- /package/scripts/{git → gh}/git-gh-pr-auto-merge.ts +0 -0
- /package/scripts/{git → gh}/git-gh-pr-snapshot.ts +0 -0
- /package/scripts/{git → gh}/git-gh-reachability.ts +0 -0
- /package/scripts/{git → gh}/git-gh-repo.ts +0 -0
- /package/scripts/{git → gh}/git-gh-rest-state.ts +0 -0
- /package/scripts/{git → gh}/git-pr-checks-watch.ts +0 -0
- /package/scripts/{git → gh}/git-pr-error.ts +0 -0
- /package/scripts/{git → gh}/git-pr-messages.ts +0 -0
- /package/scripts/{git → gh}/github-issue-url.ts +0 -0
- /package/scripts/git/{stash-orphans.ts → stash/stash-orphans.ts} +0 -0
- /package/scripts/{git → issue}/issue-reference.ts +0 -0
- /package/scripts/{git → lib}/animation-helpers.ts +0 -0
- /package/scripts/{git → lib}/git-animation.ts +0 -0
- /package/scripts/{git → notify}/git-notify.ts +0 -0
- /package/scripts/{git → notify}/telegram-test-logic.ts +0 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { yaml_document } from '#scripts/lib/yaml-document'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
// The status checks kit's distributed workflows report, keyed by the workflow that reports them —
|
|
5
|
+
// the single source `josh ruleset:check` and `josh doctor` compare a repository's required status
|
|
6
|
+
// checks against (joshuafolkken/kit#3012). Each name is the job's `name:`, the string a ruleset's
|
|
7
|
+
// required check is matched by; `required-checks-logic.test.ts` reads it back off the workflows.
|
|
8
|
+
//
|
|
9
|
+
// `E2E` belongs here even though `josh followup` deliberately does not wait on it
|
|
10
|
+
// (joshuafolkken/kit#991): a ruleset counts a skipped job as passing, and the job is skipped when the
|
|
11
|
+
// project has no E2E suite, so requiring it never blocks a merge — it only refuses a red one.
|
|
12
|
+
interface WorkflowChecks {
|
|
13
|
+
workflow: string
|
|
14
|
+
checks: ReadonlyArray<string>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const CI_WORKFLOW = '.github/workflows/ci.yml'
|
|
18
|
+
const SONAR_WORKFLOW = '.github/workflows/sonar-qube.yml'
|
|
19
|
+
const CLASSIFICATION_WORKFLOW = '.github/workflows/pr-classification.yml'
|
|
20
|
+
const REQUIRED_STATUS_CHECKS = 'required_status_checks'
|
|
21
|
+
const NOT_FOUND_STATUS = 404
|
|
22
|
+
|
|
23
|
+
const DISTRIBUTED_CHECKS: ReadonlyArray<WorkflowChecks> = [
|
|
24
|
+
{ workflow: CI_WORKFLOW, checks: ['Checks', 'Detect E2E', 'E2E', 'Security Audit'] },
|
|
25
|
+
{ workflow: SONAR_WORKFLOW, checks: ['SonarQube'] },
|
|
26
|
+
{ workflow: CLASSIFICATION_WORKFLOW, checks: ['Release classification'] },
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
// Where the repository's required checks live. A ruleset is the source `--apply` writes to; classic
|
|
30
|
+
// branch protection is read beside it, and is the source for a repository that never moved to rulesets.
|
|
31
|
+
type RequiredSource =
|
|
32
|
+
| { kind: 'ruleset'; ruleset_id: number; contexts: ReadonlyArray<string> }
|
|
33
|
+
| { kind: 'protection'; contexts: ReadonlyArray<string> }
|
|
34
|
+
| { kind: 'none' }
|
|
35
|
+
| { kind: 'unreadable' }
|
|
36
|
+
|
|
37
|
+
const status_check_schema = z.looseObject({ context: z.string() })
|
|
38
|
+
const branch_rule_schema = z.looseObject({
|
|
39
|
+
type: z.literal(REQUIRED_STATUS_CHECKS),
|
|
40
|
+
ruleset_id: z.number(),
|
|
41
|
+
parameters: z.looseObject({ required_status_checks: z.array(status_check_schema) }),
|
|
42
|
+
})
|
|
43
|
+
const protection_schema = z.looseObject({ contexts: z.array(z.string()) })
|
|
44
|
+
const ruleset_rule_schema = z.looseObject({
|
|
45
|
+
type: z.string(),
|
|
46
|
+
parameters: z
|
|
47
|
+
.looseObject({ required_status_checks: z.array(status_check_schema).optional() })
|
|
48
|
+
.optional(),
|
|
49
|
+
})
|
|
50
|
+
const ruleset_schema = z.looseObject({ rules: z.array(ruleset_rule_schema) })
|
|
51
|
+
const workflow_job_schema = z.looseObject({ name: z.string().optional() })
|
|
52
|
+
const workflow_schema = z.looseObject({ jobs: z.record(z.string(), workflow_job_schema) })
|
|
53
|
+
|
|
54
|
+
// One `gh api` read: the body it answered, or the HTTP status it failed with (`undefined` when
|
|
55
|
+
// nothing answered at all).
|
|
56
|
+
type ApiRead = { kind: 'body'; stdout: string } | { kind: 'failed'; status: number | undefined }
|
|
57
|
+
|
|
58
|
+
type BranchRule = z.infer<typeof branch_rule_schema>
|
|
59
|
+
type RulesetRule = z.infer<typeof ruleset_rule_schema>
|
|
60
|
+
|
|
61
|
+
function parse_yaml_safe(content: string): unknown {
|
|
62
|
+
try {
|
|
63
|
+
return yaml_document.parse_yaml(content)
|
|
64
|
+
} catch {
|
|
65
|
+
return undefined
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// The job `name:`s a workflow defines — the contexts it reports. A file that does not parse, or has
|
|
70
|
+
// no `jobs` mapping, reports nothing.
|
|
71
|
+
function workflow_job_names(content: string): Array<string> {
|
|
72
|
+
const parsed = workflow_schema.safeParse(parse_yaml_safe(content))
|
|
73
|
+
if (!parsed.success) return []
|
|
74
|
+
|
|
75
|
+
return Object.values(parsed.data.jobs).flatMap((job) =>
|
|
76
|
+
job.name === undefined ? [] : [job.name],
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// The checks this repository should require: only those a workflow it has actually reports. A file
|
|
81
|
+
// of the same name is not enough — a project's own `ci.yml` with a `build` job is never told to
|
|
82
|
+
// require `Checks`, which nothing there would ever report.
|
|
83
|
+
function expected_checks(read_workflow: (workflow: string) => string | undefined): Array<string> {
|
|
84
|
+
return DISTRIBUTED_CHECKS.flatMap((entry) => {
|
|
85
|
+
const content = read_workflow(entry.workflow)
|
|
86
|
+
if (content === undefined) return []
|
|
87
|
+
const reported = workflow_job_names(content)
|
|
88
|
+
|
|
89
|
+
return entry.checks.filter((check) => reported.includes(check))
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function parse_json(stdout: string): unknown {
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(stdout)
|
|
96
|
+
} catch {
|
|
97
|
+
return undefined
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function status_check_rules(payload: unknown): Array<BranchRule> {
|
|
102
|
+
if (!Array.isArray(payload)) return []
|
|
103
|
+
|
|
104
|
+
return payload.flatMap((entry: unknown) => {
|
|
105
|
+
const parsed = branch_rule_schema.safeParse(entry)
|
|
106
|
+
|
|
107
|
+
return parsed.success ? [parsed.data] : []
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// `GET repos/{owner}/{repo}/rules/branches/{branch}` — every rule in force on the branch, across
|
|
112
|
+
// rulesets. The contexts of all of them are combined, since each is enforced; the first ruleset is the
|
|
113
|
+
// one `--apply` writes to. No `required_status_checks` rule at all reads as `none`.
|
|
114
|
+
function parse_branch_rules(stdout: string | undefined): RequiredSource {
|
|
115
|
+
const payload = stdout === undefined ? undefined : parse_json(stdout)
|
|
116
|
+
if (!Array.isArray(payload)) return { kind: 'unreadable' }
|
|
117
|
+
const rules = status_check_rules(payload)
|
|
118
|
+
const [first] = rules
|
|
119
|
+
if (first === undefined) return { kind: 'none' }
|
|
120
|
+
const contexts = rules.flatMap((rule) =>
|
|
121
|
+
rule.parameters.required_status_checks.map((check) => check.context),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return { kind: 'ruleset', ruleset_id: first.ruleset_id, contexts }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// `GET …/branches/{branch}/protection/required_status_checks`. GitHub answers a branch without
|
|
128
|
+
// protection (or without required checks) with a 404, which alone reads as `none`. Any other
|
|
129
|
+
// failure — a 403 for a caller without admin access, a 5xx, a timeout — or a body that is not the
|
|
130
|
+
// expected document reads as `unreadable`, so a check that is in fact required is never reported
|
|
131
|
+
// missing.
|
|
132
|
+
function parse_protection(read: ApiRead): RequiredSource {
|
|
133
|
+
if (read.kind === 'failed') {
|
|
134
|
+
return { kind: read.status === NOT_FOUND_STATUS ? 'none' : 'unreadable' }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const parsed = protection_schema.safeParse(parse_json(read.stdout))
|
|
138
|
+
|
|
139
|
+
return parsed.success
|
|
140
|
+
? { kind: 'protection', contexts: parsed.data.contexts }
|
|
141
|
+
: { kind: 'unreadable' }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// GitHub enforces rulesets and classic protection together, so a check either one requires is
|
|
145
|
+
// required. The ruleset stays the source `--apply` writes to; a protection read that failed for any
|
|
146
|
+
// reason but a 404 leaves the whole answer `unreadable`, as a lone protection read would.
|
|
147
|
+
function combine_sources(rules: RequiredSource, protection: RequiredSource): RequiredSource {
|
|
148
|
+
if (rules.kind !== 'ruleset') return protection
|
|
149
|
+
if (protection.kind === 'none') return rules
|
|
150
|
+
if (protection.kind !== 'protection') return { kind: 'unreadable' }
|
|
151
|
+
|
|
152
|
+
return { ...rules, contexts: [...new Set([...rules.contexts, ...protection.contexts])] }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function missing_checks(
|
|
156
|
+
expected: ReadonlyArray<string>,
|
|
157
|
+
contexts: ReadonlyArray<string>,
|
|
158
|
+
): Array<string> {
|
|
159
|
+
return expected.filter((check) => !contexts.includes(check))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function with_added_checks(rule: RulesetRule, missing: ReadonlyArray<string>): RulesetRule {
|
|
163
|
+
if (rule.type !== REQUIRED_STATUS_CHECKS || rule.parameters === undefined) return rule
|
|
164
|
+
const current = rule.parameters.required_status_checks ?? []
|
|
165
|
+
const added = missing.map((context) => ({ context }))
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
...rule,
|
|
169
|
+
parameters: { ...rule.parameters, required_status_checks: [...current, ...added] },
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// The `PUT repos/{owner}/{repo}/rulesets/{id}` body: the ruleset's own rules, unchanged but for the
|
|
174
|
+
// missing contexts appended to its required-checks rule. `rules` replaces the whole list, so every
|
|
175
|
+
// other rule is carried over as read rather than dropped.
|
|
176
|
+
function ruleset_update_body(
|
|
177
|
+
ruleset_stdout: string,
|
|
178
|
+
missing: ReadonlyArray<string>,
|
|
179
|
+
): string | undefined {
|
|
180
|
+
const parsed = ruleset_schema.safeParse(parse_json(ruleset_stdout))
|
|
181
|
+
if (!parsed.success) return undefined
|
|
182
|
+
|
|
183
|
+
return JSON.stringify({
|
|
184
|
+
rules: parsed.data.rules.map((rule) => with_added_checks(rule, missing)),
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// The `POST …/protection/required_status_checks/contexts` body, which appends rather than replaces.
|
|
189
|
+
function protection_update_body(missing: ReadonlyArray<string>): string {
|
|
190
|
+
return JSON.stringify({ contexts: missing })
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const required_checks_logic = {
|
|
194
|
+
DISTRIBUTED_CHECKS,
|
|
195
|
+
workflow_job_names,
|
|
196
|
+
expected_checks,
|
|
197
|
+
parse_branch_rules,
|
|
198
|
+
parse_protection,
|
|
199
|
+
combine_sources,
|
|
200
|
+
missing_checks,
|
|
201
|
+
ruleset_update_body,
|
|
202
|
+
protection_update_body,
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export type { ApiRead, RequiredSource, WorkflowChecks }
|
|
206
|
+
export { required_checks_logic }
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { repo_setting } from './repo-setting'
|
|
2
|
+
import type { RequiredSource } from './required-checks-logic'
|
|
3
|
+
|
|
4
|
+
// What one read of a repository's required status checks found, against what kit's distributed
|
|
5
|
+
// workflows report (joshuafolkken/kit#3012).
|
|
6
|
+
interface RequiredChecksReport {
|
|
7
|
+
repo: string | undefined
|
|
8
|
+
branch: string | undefined
|
|
9
|
+
source: RequiredSource
|
|
10
|
+
expected: ReadonlyArray<string>
|
|
11
|
+
missing: ReadonlyArray<string>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const LABEL = 'Required status checks'
|
|
15
|
+
const APPLY_COMMAND = 'pnpm josh ruleset:check --apply'
|
|
16
|
+
|
|
17
|
+
function source_label(source: RequiredSource): string {
|
|
18
|
+
return source.kind === 'ruleset' ? `ruleset ${String(source.ruleset_id)}` : 'branch protection'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// An unreadable answer is never reported as a missing one — the same rule every repository-setting
|
|
22
|
+
// report follows — so it names the possible causes and no remediation.
|
|
23
|
+
function format_unreadable(report: RequiredChecksReport): Array<string> {
|
|
24
|
+
return [
|
|
25
|
+
` ⚠ ${LABEL}: could not be read (${repo_setting.report_target(report.repo)}) — not checked, not necessarily missing`,
|
|
26
|
+
' `gh` is missing or unauthenticated, or the request failed (offline, timed out).',
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// No ruleset or protection requires anything. `--apply` adds to an existing rule and never creates
|
|
31
|
+
// one — which branches a ruleset targets and who may bypass it are the maintainer's choices.
|
|
32
|
+
function format_none(report: RequiredChecksReport, target: string): Array<string> {
|
|
33
|
+
return [
|
|
34
|
+
` ⚠ ${LABEL}: none required on ${String(report.branch)} (${target})`,
|
|
35
|
+
` Expected: ${report.expected.join(', ')}`,
|
|
36
|
+
' Create a branch ruleset (Settings → Rules → Rulesets) that requires them.',
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function format_found(report: RequiredChecksReport, target: string): Array<string> {
|
|
41
|
+
const where = `${String(report.branch)} (${target}, ${source_label(report.source)})`
|
|
42
|
+
|
|
43
|
+
if (report.missing.length === 0) {
|
|
44
|
+
return [` ✔ ${LABEL}: all ${String(report.expected.length)} kit checks required on ${where}`]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return [
|
|
48
|
+
` ⚠ ${LABEL}: ${report.missing.join(', ')} not required on ${where}`,
|
|
49
|
+
` Add: ${APPLY_COMMAND}`,
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function format_report(report: RequiredChecksReport): Array<string> {
|
|
54
|
+
const target = repo_setting.report_target(report.repo)
|
|
55
|
+
if (report.source.kind === 'unreadable') return format_unreadable(report)
|
|
56
|
+
if (report.source.kind === 'none') return format_none(report, target)
|
|
57
|
+
|
|
58
|
+
return format_found(report, target)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Whether nothing is left to do: the checks were read and every expected one is required.
|
|
62
|
+
function is_complete(report: RequiredChecksReport): boolean {
|
|
63
|
+
const is_read = report.source.kind === 'ruleset' || report.source.kind === 'protection'
|
|
64
|
+
|
|
65
|
+
return is_read && report.missing.length === 0
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const required_checks_report = { format_report, is_complete }
|
|
69
|
+
|
|
70
|
+
export type { RequiredChecksReport }
|
|
71
|
+
export { required_checks_report }
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { gh_spawn } from '#scripts/gh/gh-spawn'
|
|
4
|
+
import { PROJECT_ROOT } from '#scripts/init/init-paths'
|
|
5
|
+
import { repo_setting } from './repo-setting'
|
|
6
|
+
import { required_checks_report, type RequiredChecksReport } from './required-checks-report'
|
|
7
|
+
import { ruleset_check } from './ruleset-check'
|
|
8
|
+
|
|
9
|
+
// `josh ruleset:check [--apply]` — say whether the default branch requires every status check kit's
|
|
10
|
+
// distributed workflows report, and with `--apply` add the missing ones (joshuafolkken/kit#3012).
|
|
11
|
+
// Changing a repository setting is outward-facing, so the write happens only on the flag a person
|
|
12
|
+
// typed; `josh sync` and `josh doctor` never pass it. Exits non-zero while anything is left to do.
|
|
13
|
+
|
|
14
|
+
const ARGV_OFFSET = 2
|
|
15
|
+
const APPLY_FLAG = '--apply'
|
|
16
|
+
const FAILURE_EXIT_CODE = 1
|
|
17
|
+
|
|
18
|
+
function exit_code_for(report: RequiredChecksReport): number {
|
|
19
|
+
return required_checks_report.is_complete(report) ? 0 : FAILURE_EXIT_CODE
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function apply(report: RequiredChecksReport): number {
|
|
23
|
+
try {
|
|
24
|
+
if (!ruleset_check.apply_missing(report)) return FAILURE_EXIT_CODE
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error(`Could not add ${report.missing.join(', ')}: ${String(error)}`)
|
|
27
|
+
|
|
28
|
+
return FAILURE_EXIT_CODE
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.info(` ✔ Added: ${report.missing.join(', ')}`)
|
|
32
|
+
|
|
33
|
+
return 0
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function run(argv: ReadonlyArray<string>): number {
|
|
37
|
+
const report = ruleset_check.inspect(gh_spawn.get_repo_name_with_owner(), PROJECT_ROOT)
|
|
38
|
+
|
|
39
|
+
repo_setting.print_section(required_checks_report.format_report(report))
|
|
40
|
+
const is_writable = argv.includes(APPLY_FLAG) && report.missing.length > 0
|
|
41
|
+
|
|
42
|
+
return is_writable ? apply(report) : exit_code_for(report)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function main(argv: ReadonlyArray<string>): void {
|
|
46
|
+
process.exitCode = run(argv)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const ruleset_check_cli = { APPLY_FLAG, run }
|
|
50
|
+
|
|
51
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) main(process.argv.slice(ARGV_OFFSET))
|
|
52
|
+
|
|
53
|
+
export { ruleset_check_cli }
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { git_gh_exec } from '#scripts/gh/git-gh-exec'
|
|
3
|
+
import { gh_failure } from '#scripts/gh/git-gh-failure'
|
|
4
|
+
import { PROJECT_ROOT } from '#scripts/init/init-paths'
|
|
5
|
+
import { file_reader } from '#scripts/lib/read-file'
|
|
6
|
+
import { repo_setting } from './repo-setting'
|
|
7
|
+
import { required_checks_logic, type ApiRead, type RequiredSource } from './required-checks-logic'
|
|
8
|
+
import { required_checks_report, type RequiredChecksReport } from './required-checks-report'
|
|
9
|
+
|
|
10
|
+
// Read a repository's required status checks on its default branch and compare them with the checks
|
|
11
|
+
// kit's distributed workflows report (joshuafolkken/kit#3012). Every read degrades to `unreadable`
|
|
12
|
+
// rather than throwing; the one write, `apply_missing`, runs only on `josh ruleset:check --apply`.
|
|
13
|
+
|
|
14
|
+
function read_api(api_path: string): string | undefined {
|
|
15
|
+
return git_gh_exec.read_gh_api_sync({
|
|
16
|
+
path: api_path,
|
|
17
|
+
cwd: PROJECT_ROOT,
|
|
18
|
+
timeout_ms: repo_setting.GH_TIMEOUT_MS,
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function read_default_branch(repo: string): string | undefined {
|
|
23
|
+
const branch = git_gh_exec
|
|
24
|
+
.read_gh_api_sync({
|
|
25
|
+
path: `repos/${repo}`,
|
|
26
|
+
jq_filter: '.default_branch',
|
|
27
|
+
cwd: PROJECT_ROOT,
|
|
28
|
+
timeout_ms: repo_setting.GH_TIMEOUT_MS,
|
|
29
|
+
})
|
|
30
|
+
?.trim()
|
|
31
|
+
|
|
32
|
+
return branch === '' ? undefined : branch
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Unlike `read_api`, keeps the failure's HTTP status: a 404 here means "no protection", which is an
|
|
36
|
+
// answer, while any other failure is not.
|
|
37
|
+
function read_protection(repo: string, branch: string): ApiRead {
|
|
38
|
+
try {
|
|
39
|
+
const stdout = git_gh_exec.exec_gh_api_sync({
|
|
40
|
+
path: `repos/${repo}/branches/${branch}/protection/required_status_checks`,
|
|
41
|
+
cwd: PROJECT_ROOT,
|
|
42
|
+
timeout_ms: repo_setting.GH_TIMEOUT_MS,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return { kind: 'body', stdout }
|
|
46
|
+
} catch (error) {
|
|
47
|
+
return { kind: 'failed', status: gh_failure.failure_of(error)?.status }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Rulesets and classic branch protection both, since GitHub enforces both; unreadable rules stop
|
|
52
|
+
// there rather than letting protection alone pass for the whole answer.
|
|
53
|
+
function read_source(repo: string, branch: string): RequiredSource {
|
|
54
|
+
const from_rules = required_checks_logic.parse_branch_rules(
|
|
55
|
+
read_api(`repos/${repo}/rules/branches/${branch}`),
|
|
56
|
+
)
|
|
57
|
+
if (from_rules.kind === 'unreadable') return from_rules
|
|
58
|
+
const from_protection = required_checks_logic.parse_protection(read_protection(repo, branch))
|
|
59
|
+
|
|
60
|
+
return required_checks_logic.combine_sources(from_rules, from_protection)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// The checks `root` should require, decided by the jobs its distributed workflows define on disk.
|
|
64
|
+
function expected_in(root: string): Array<string> {
|
|
65
|
+
return required_checks_logic.expected_checks((workflow) =>
|
|
66
|
+
file_reader.read_if_readable(path.join(root, workflow)),
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function contexts_of(source: RequiredSource): ReadonlyArray<string> {
|
|
71
|
+
return source.kind === 'ruleset' || source.kind === 'protection' ? source.contexts : []
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function inspect(repo: string | undefined, root: string): RequiredChecksReport {
|
|
75
|
+
const expected = expected_in(root)
|
|
76
|
+
const branch = repo === undefined ? undefined : read_default_branch(repo)
|
|
77
|
+
const source: RequiredSource =
|
|
78
|
+
repo === undefined || branch === undefined ? { kind: 'unreadable' } : read_source(repo, branch)
|
|
79
|
+
const missing =
|
|
80
|
+
source.kind === 'unreadable'
|
|
81
|
+
? []
|
|
82
|
+
: required_checks_logic.missing_checks(expected, contexts_of(source))
|
|
83
|
+
|
|
84
|
+
return { repo, branch, source, expected, missing }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function apply_to_ruleset(repo: string, ruleset_id: number, missing: ReadonlyArray<string>): void {
|
|
88
|
+
const api_path = `repos/${repo}/rulesets/${String(ruleset_id)}`
|
|
89
|
+
const current = git_gh_exec.exec_gh_api_sync({ path: api_path, cwd: PROJECT_ROOT })
|
|
90
|
+
const body = required_checks_logic.ruleset_update_body(current, missing)
|
|
91
|
+
if (body === undefined) throw new Error(`Unexpected ruleset response from ${api_path}`)
|
|
92
|
+
|
|
93
|
+
git_gh_exec.exec_gh_api_sync({ path: api_path, method: 'PUT', body, cwd: PROJECT_ROOT })
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function apply_to_protection(repo: string, branch: string, missing: ReadonlyArray<string>): void {
|
|
97
|
+
git_gh_exec.exec_gh_api_sync({
|
|
98
|
+
path: `repos/${repo}/branches/${branch}/protection/required_status_checks/contexts`,
|
|
99
|
+
method: 'POST',
|
|
100
|
+
body: required_checks_logic.protection_update_body(missing),
|
|
101
|
+
cwd: PROJECT_ROOT,
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function apply_to_source(
|
|
106
|
+
repo: string,
|
|
107
|
+
branch: string,
|
|
108
|
+
source: RequiredSource,
|
|
109
|
+
missing: ReadonlyArray<string>,
|
|
110
|
+
): boolean {
|
|
111
|
+
if (source.kind === 'ruleset') {
|
|
112
|
+
apply_to_ruleset(repo, source.ruleset_id, missing)
|
|
113
|
+
|
|
114
|
+
return true
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (source.kind !== 'protection') return false
|
|
118
|
+
apply_to_protection(repo, branch, missing)
|
|
119
|
+
|
|
120
|
+
return true
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// The one write: append the missing checks to the rule that already requires checks. A repository
|
|
124
|
+
// with neither a ruleset rule nor protection gets nothing created for it, which `format_report`
|
|
125
|
+
// explains. Throws on a failed request so the caller can report it; `false` when nothing was written.
|
|
126
|
+
function apply_missing(report: RequiredChecksReport): boolean {
|
|
127
|
+
const { repo, branch, source, missing } = report
|
|
128
|
+
if (repo === undefined || branch === undefined || missing.length === 0) return false
|
|
129
|
+
|
|
130
|
+
return apply_to_source(repo, branch, source, missing)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// The `josh doctor` section: read, never write.
|
|
134
|
+
function report_required_checks_section(
|
|
135
|
+
repo: string | undefined,
|
|
136
|
+
root: string,
|
|
137
|
+
): RequiredChecksReport {
|
|
138
|
+
const report = inspect(repo, root)
|
|
139
|
+
|
|
140
|
+
repo_setting.print_section(required_checks_report.format_report(report))
|
|
141
|
+
|
|
142
|
+
return report
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const ruleset_check = { expected_in, inspect, apply_missing, report_required_checks_section }
|
|
146
|
+
|
|
147
|
+
export { ruleset_check }
|
|
@@ -37,7 +37,7 @@ function main(argv: ReadonlyArray<string>): void {
|
|
|
37
37
|
process.exitCode = run(argv)
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
const disposition_cli = { USAGE,
|
|
40
|
+
const disposition_cli = { USAGE, reason_line, run }
|
|
41
41
|
|
|
42
42
|
if (process.argv[1] === fileURLToPath(import.meta.url)) main(process.argv.slice(ARGV_OFFSET))
|
|
43
43
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { git_gh_command } from '#scripts/gh/git-gh-command'
|
|
1
2
|
import { git_command } from '#scripts/git/git-command'
|
|
2
|
-
import { git_gh_command } from '#scripts/git/git-gh-command'
|
|
3
3
|
import { reproduction_measure } from '#scripts/issue/reproduction-measure'
|
|
4
4
|
import { test_declared_logic, type Verdict } from '#scripts/test/test-declared-logic'
|
|
5
5
|
|
|
@@ -101,13 +101,7 @@ async function main(argv: ReadonlyArray<string>): Promise<void> {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
const review_attest_cli = {
|
|
104
|
-
CHECK_FLAG,
|
|
105
|
-
main,
|
|
106
|
-
NOT_REQUIRED_LINE,
|
|
107
104
|
run,
|
|
108
|
-
run_check,
|
|
109
|
-
UNKNOWN_NONCE_LINE,
|
|
110
|
-
USAGE,
|
|
111
105
|
}
|
|
112
106
|
|
|
113
107
|
if (process.argv[1] === fileURLToPath(import.meta.url)) await main(process.argv.slice(ARGV_OFFSET))
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto'
|
|
2
|
+
import { lstatSync } from 'node:fs'
|
|
2
3
|
import { git_command } from '#scripts/git/git-command'
|
|
3
4
|
import { PROJECT_ROOT } from '#scripts/init/init-paths'
|
|
4
5
|
import { stamp_file } from '#scripts/josh/stamp-file'
|
|
6
|
+
import { json_value } from '#scripts/lib/json-value'
|
|
5
7
|
import { review_checkout, type ReviewCheckout } from './review-checkout'
|
|
6
8
|
|
|
7
9
|
// The record that says which checkout a `/code-review` actually read (joshuafolkken/kit#1522).
|
|
@@ -80,17 +82,7 @@ function attest_path(nonce: string): string {
|
|
|
80
82
|
function read_json(source: string): unknown {
|
|
81
83
|
const raw = stamp_file.read_stamp_text(source)
|
|
82
84
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
return JSON.parse(raw)
|
|
87
|
-
} catch {
|
|
88
|
-
return undefined
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function is_record(value: unknown): value is Record<string, unknown> {
|
|
93
|
-
return typeof value === 'object' && value !== null
|
|
85
|
+
return raw === undefined ? undefined : json_value.parse_or_undefined(raw)
|
|
94
86
|
}
|
|
95
87
|
|
|
96
88
|
function string_field(value: Record<string, unknown>, key: string): string | undefined {
|
|
@@ -100,7 +92,7 @@ function string_field(value: Record<string, unknown>, key: string): string | und
|
|
|
100
92
|
}
|
|
101
93
|
|
|
102
94
|
function to_checkout(value: unknown): ReviewCheckout | undefined {
|
|
103
|
-
if (!is_record(value)) return undefined
|
|
95
|
+
if (!json_value.is_record(value)) return undefined
|
|
104
96
|
|
|
105
97
|
const root = string_field(value, 'root')
|
|
106
98
|
const branch = string_field(value, 'branch')
|
|
@@ -112,7 +104,7 @@ function to_checkout(value: unknown): ReviewCheckout | undefined {
|
|
|
112
104
|
}
|
|
113
105
|
|
|
114
106
|
function to_pointer(value: unknown): PointerRecord | undefined {
|
|
115
|
-
if (!is_record(value)) return undefined
|
|
107
|
+
if (!json_value.is_record(value)) return undefined
|
|
116
108
|
|
|
117
109
|
const nonce = string_field(value, 'nonce')
|
|
118
110
|
const taken_at = string_field(value, 'taken_at')
|
|
@@ -240,6 +232,47 @@ function check(now: number = Date.now(), root?: string): AttestVerdict {
|
|
|
240
232
|
return verdict
|
|
241
233
|
}
|
|
242
234
|
|
|
235
|
+
function is_written_since(source: string, taken_at: string): boolean {
|
|
236
|
+
try {
|
|
237
|
+
const stats = lstatSync(source)
|
|
238
|
+
|
|
239
|
+
return stats.isFile() && stats.mtimeMs >= Date.parse(taken_at)
|
|
240
|
+
} catch {
|
|
241
|
+
return false
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// **Whether the last briefed review of this branch finished** (joshuafolkken/kit#2945). A ship resumed
|
|
246
|
+
// after a stop re-issues round 1, and without this answer it briefed the whole change again — the
|
|
247
|
+
// files the finished review had just read, re-read at full cost. It is asked before `record_target`
|
|
248
|
+
// mints the next nonce, since that call drops the record it reads. The branch must match: the pointer
|
|
249
|
+
// is keyed on the root, and a lane root outlives the branch a previous run reviewed there.
|
|
250
|
+
//
|
|
251
|
+
// **An attestation alone is not a finished review.** The reviewer may attest and then die before it
|
|
252
|
+
// reports — a timeout, a usage limit — and narrowing on that would call files nobody read "already
|
|
253
|
+
// read". So the findings file, written only once the review has its verdict, must postdate the brief
|
|
254
|
+
// this pointer names; one left from an earlier review is older than that brief and does not count.
|
|
255
|
+
function is_reviewed_on(checkout: ReviewCheckout, findings_path: string, root?: string): boolean {
|
|
256
|
+
const pointer = read_pointer(root)
|
|
257
|
+
|
|
258
|
+
if (pointer === undefined) return false
|
|
259
|
+
|
|
260
|
+
const verdict = verdict_for(pointer.nonce)
|
|
261
|
+
|
|
262
|
+
if (verdict.status !== 'ok' || verdict.expected?.branch !== checkout.branch) return false
|
|
263
|
+
|
|
264
|
+
return is_written_since(findings_path, pointer.taken_at)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Whether a record written at `written_at` belongs to the brief the pointer names rather than an earlier
|
|
268
|
+
// one (joshuafolkken/kit#2945): that brief writes it right after minting the pointer. ISO timestamps
|
|
269
|
+
// order as strings.
|
|
270
|
+
function is_briefed_since(written_at: string, root?: string): boolean {
|
|
271
|
+
const pointer = read_pointer(root)
|
|
272
|
+
|
|
273
|
+
return pointer !== undefined && written_at >= pointer.taken_at
|
|
274
|
+
}
|
|
275
|
+
|
|
243
276
|
// The pointer is keyed on the repository root rather than on `process.cwd()`, which is what
|
|
244
277
|
// `PROJECT_ROOT` is. `josh review:brief` records against the root it just asked git for, so the check
|
|
245
278
|
// has to ask the same question: run the two from different directories — one from `scripts/`, one
|
|
@@ -294,18 +327,17 @@ function refusal_message(verdict: AttestVerdict): string {
|
|
|
294
327
|
}
|
|
295
328
|
|
|
296
329
|
const review_attest = {
|
|
297
|
-
ATTEST_PREFIX,
|
|
298
330
|
attest,
|
|
299
331
|
attest_path,
|
|
300
332
|
check,
|
|
301
333
|
check_here,
|
|
302
334
|
clear,
|
|
303
335
|
clear_here,
|
|
304
|
-
EXPECT_PREFIX,
|
|
305
336
|
expect_path,
|
|
337
|
+
is_briefed_since,
|
|
338
|
+
is_reviewed_on,
|
|
306
339
|
MISMATCH_VERDICT,
|
|
307
340
|
MISSING_REASON,
|
|
308
|
-
POINTER_PREFIX,
|
|
309
341
|
pointer_path,
|
|
310
342
|
read_attestation,
|
|
311
343
|
read_expectation,
|