@joshuafolkken/kit 1.1010.0 → 1.1050.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 +1 -5
- package/.claude/skills/dependency-update/SKILL.md +6 -3
- package/.claude/skills/epic-commands/SKILL.md +46 -17
- package/.claude/skills/verify-ui/SKILL.md +1 -1
- package/.claude/skills/workflow-commands/SKILL.md +10 -25
- package/.claude/skills/workflow-commands/background-commands.md +1 -1
- package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +1 -1
- package/.claude/skills/workflow-commands/backlogrun-park.md +6 -0
- package/.claude/skills/workflow-commands/backlogrun-progress.md +30 -36
- package/.claude/skills/workflow-commands/backlogrun-steps.md +16 -16
- package/.claude/skills/workflow-commands/backlogrun.md +3 -12
- package/.claude/skills/workflow-commands/chain-rule.md +2 -2
- package/.claude/skills/workflow-commands/followup-reference.md +2 -2
- package/.claude/skills/workflow-commands/fullrun-steps.md +4 -3
- package/.claude/skills/workflow-commands/fullrun.md +1 -1
- package/.claude/skills/workflow-commands/halfrun.md +2 -1
- package/.claude/skills/workflow-commands/into-target.md +1 -1
- package/.claude/skills/workflow-commands/issue-comments.md +1 -1
- package/.claude/skills/workflow-commands/observation-filing.md +80 -126
- 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/working-tree-hold.md +2 -2
- package/.codex/hooks.json +1 -5
- package/.github/workflows/auto-tag.yml +21 -0
- package/.github/workflows/publish.yml +9 -10
- package/.github/workflows/sonar-qube.yml +1 -1
- package/AGENTS.md +4 -25
- package/CLAUDE.md +24 -38
- package/GEMINI.md +4 -24
- package/README.md +22 -56
- package/dist/CLAUDE.md +24 -38
- package/dist/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-6PSTVOLH.js → chunk-5BK3K7V4.js} +114 -90
- package/dist/hooks/{chunk-PAXMLGSS.js → chunk-7VPDUBRN.js} +93 -175
- package/dist/hooks/{chunk-KPZN42RT.js → chunk-DMQN45FN.js} +3 -3
- package/dist/hooks/{chunk-WMIR5SNX.js → chunk-EJDX2TOB.js} +13 -3
- package/dist/hooks/{chunk-A2H5I2VD.js → chunk-GQ7KP45M.js} +2 -2
- package/dist/hooks/{chunk-4GDR2YTV.js → chunk-HJIISEAC.js} +4 -4
- package/dist/hooks/{chunk-AWTFKKFW.js → chunk-NAS4C7GP.js} +6 -33
- package/dist/hooks/{chunk-56LGMJJV.js → chunk-NJQOBE6L.js} +2 -2
- package/dist/hooks/{chunk-EZLHXKJ6.js → chunk-RLDX5SOC.js} +1222 -941
- package/dist/hooks/{chunk-GBJUERYK.js → chunk-WQ4OSBSS.js} +10 -3
- package/dist/hooks/codex-hook-adapter.js +10 -10
- package/dist/hooks/duplicate-read-guard.js +5 -5
- package/dist/hooks/format-edited.js +15 -34
- package/dist/hooks/investigation-guard.js +5 -5
- package/dist/hooks/pretool-guard.js +23 -16
- package/dist/hooks/stop-guard.js +21 -200
- package/dist/josh.js +42 -125
- package/dist/version/index.js +466 -42
- package/eslint/base.js +2 -11
- package/package.json +36 -10
- package/prompts/coding-standards.md +15 -290
- package/prompts/collaboration-workflow/cross-repo-epic.md +2 -51
- package/prompts/collaboration-workflow/epic-audit.md +2 -53
- package/prompts/collaboration-workflow/epic-bundle.md +2 -106
- package/prompts/collaboration-workflow/file-edits.md +6 -53
- package/prompts/collaboration-workflow/issue-template.md +4 -4
- package/prompts/collaboration-workflow/operating-rules.md +18 -75
- package/prompts/collaboration-workflow/principles.md +74 -0
- package/prompts/collaboration-workflow/residency.md +55 -15
- package/prompts/collaboration-workflow/rule-delivery.md +5 -46
- 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 +15 -18
- package/prompts/collaboration-workflow.md +22 -28
- package/prompts/sonar-hotspot-handling.md +1 -1
- package/scripts/adopt/adopt-logic.ts +2 -2
- package/scripts/adopt/adopt-toolkits.ts +3 -1
- package/scripts/adopt/adopt.ts +2 -2
- package/scripts/agent/agent-event.ts +2 -9
- package/scripts/agent/agent-exit-record.ts +2 -9
- package/scripts/auto-ok/auto-ok-cli.ts +9 -9
- package/scripts/backlog/backlog-budget-cli.ts +2 -6
- package/scripts/backlog/backlog-drive-cli.ts +20 -10
- package/scripts/backlog/backlog-drive.ts +50 -16
- package/scripts/backlog/backlog-next.ts +24 -8
- package/scripts/backlog/backlog-offer-cli.ts +6 -5
- package/scripts/backlog/backlog-plan-cli.ts +5 -1
- package/scripts/backlog/backlog-plan.ts +1 -1
- package/scripts/backlog/backlog-pool.ts +31 -11
- package/scripts/backlog/backlog-rank.ts +106 -0
- package/scripts/backlog/backlog-waves.ts +42 -29
- package/scripts/build/build-bin.ts +2 -1
- package/scripts/build/build-config-merge.ts +2 -1
- package/scripts/build/build-hooks.ts +31 -6
- package/scripts/build/build-managed-marker.ts +2 -1
- package/scripts/build/build-self-sync-guard.ts +2 -1
- package/scripts/build/build-version.ts +2 -1
- package/scripts/cases/cases-logic.ts +2 -6
- package/scripts/ci/pr-classification.ts +13 -11
- package/scripts/cost-runtime/cost-transcript.ts +4 -11
- package/scripts/delegation/delegation-policy.ts +11 -0
- package/scripts/doctor/doctor-consumer.ts +5 -12
- package/scripts/doctor/doctor-ports.ts +2 -2
- package/scripts/document/document-byte-budget.ts +13 -21
- package/scripts/document/document-section.ts +2 -6
- package/scripts/document/entry-read-budget.ts +6 -5
- package/scripts/{git/git-epic-add-body.ts → epic/epic-add-body.ts} +23 -23
- package/scripts/{git/git-epic-add-plan.ts → epic/epic-add-plan.ts} +23 -26
- package/scripts/{git/git-epic-add.ts → epic/epic-add.ts} +17 -17
- package/scripts/epic/epic-audit-cli.ts +4 -4
- package/scripts/epic/epic-audit-orphans.ts +2 -2
- package/scripts/epic/epic-audit-rationale.ts +1 -1
- package/scripts/epic/epic-audit.ts +1 -1
- package/scripts/{git/git-epic-body.ts → epic/epic-body.ts} +6 -6
- package/scripts/epic/epic-bundle-cli.ts +1 -1
- package/scripts/epic/epic-bundle.ts +2 -2
- package/scripts/epic/epic-candidate-confirm.ts +2 -1
- package/scripts/{git/git-epic-chains.ts → epic/epic-chains.ts} +4 -4
- package/{scripts-ai → scripts/epic}/epic-check.ts +5 -4
- package/{scripts-ai → scripts/epic}/epic-cli.ts +4 -4
- package/scripts/{git/git-epic-close-comment.ts → epic/epic-close-comment.ts} +9 -9
- package/scripts/{git/git-epic-close.ts → epic/epic-close.ts} +23 -19
- package/scripts/epic/epic-cross-repo.ts +21 -38
- package/scripts/{git/git-epic-decision.ts → epic/epic-decision.ts} +12 -14
- package/scripts/epic/epic-fetch.ts +3 -3
- package/scripts/epic/epic-graph.ts +3 -3
- package/scripts/epic/epic-index.ts +2 -2
- package/scripts/epic/epic-issue.ts +1 -1
- package/scripts/epic/epic-lane-offer.ts +11 -22
- package/scripts/epic/epic-next.ts +3 -3
- package/scripts/{git/git-epic-parse.ts → epic/epic-parse.ts} +3 -3
- package/scripts/{git/git-epic-promote.ts → epic/epic-promote.ts} +10 -10
- package/scripts/epic/epic-rank.ts +37 -0
- package/scripts/{git/git-epic-read.ts → epic/epic-read.ts} +9 -9
- package/scripts/{git/git-epic-reconcile.ts → epic/epic-reconcile.ts} +18 -20
- package/scripts/{git/git-epic-reference.ts → epic/epic-reference.ts} +4 -23
- package/scripts/epic/epic-relation-recheck.ts +4 -3
- package/scripts/{git/git-epic-relations.ts → epic/epic-relations.ts} +5 -5
- package/scripts/{git/git-epic-remove-plan.ts → epic/epic-remove-plan.ts} +17 -19
- package/scripts/{git/git-epic-remove.ts → epic/epic-remove.ts} +13 -13
- package/scripts/{git/git-epic-run.ts → epic/epic-run.ts} +27 -27
- package/scripts/{git/git-epic-sections.ts → epic/epic-sections.ts} +6 -6
- package/scripts/{git/git-epic-shape.ts → epic/epic-shape.ts} +7 -7
- package/scripts/epic/epic-solo.ts +29 -28
- package/scripts/{git/git-epic-validate.ts → epic/epic-validate.ts} +13 -13
- package/{scripts-ai → scripts/epic}/epic.ts +17 -16
- package/scripts/gh/fix-gh-packages.ts +2 -1
- package/scripts/gh/gh-spawn.ts +12 -10
- package/scripts/git/constants.ts +1 -1
- package/scripts/git/git-branch.ts +10 -1
- package/scripts/git/git-command.ts +2 -2
- package/scripts/git/git-error.ts +2 -5
- package/scripts/git/git-execa-error.ts +6 -1
- package/{scripts-ai → scripts/git}/git-followup-finish.ts +7 -7
- package/scripts/git/git-followup-flush.ts +74 -48
- package/scripts/git/git-followup-issue-close.ts +2 -2
- package/scripts/git/git-followup-label.ts +1 -1
- package/scripts/git/git-followup-pending.ts +1 -1
- package/{scripts-ai → scripts/git}/git-followup-workflow.ts +23 -11
- package/scripts/git/git-gh-exec.ts +85 -26
- package/scripts/git/git-gh-issue-read.ts +2 -2
- package/scripts/git/git-gh-issue-rest.ts +2 -2
- package/scripts/git/git-gh-issue-write.ts +6 -6
- package/scripts/git/git-gh-pr-read.ts +0 -21
- package/scripts/git/git-gh-pr.ts +2 -23
- package/scripts/git/git-next-issues.ts +34 -5
- package/scripts/git/git-pr-followup-wrapup.ts +2 -6
- package/scripts/git/git-preflight.ts +25 -10
- package/scripts/git/git-push-transport.ts +12 -69
- package/scripts/git/git-spawn.ts +79 -3
- package/scripts/git/git-ssh-keepalive.ts +74 -0
- package/scripts/git/git-staging.ts +21 -9
- package/{scripts-ai → scripts/git}/git-workflow.ts +11 -11
- package/scripts/git/github-issue-url.ts +1 -1
- package/scripts/git/gone-branch.ts +3 -2
- package/scripts/git/issue-labels.ts +13 -1
- package/scripts/git/issue-rank.ts +89 -0
- package/scripts/git/issue-reference.ts +19 -0
- package/scripts/git/main-merge.ts +2 -1
- package/scripts/git/main-sync.ts +3 -2
- package/scripts/git/schemas.ts +1 -1
- package/scripts/git/stash-pop-args.ts +7 -13
- package/scripts/git/stash-pop-cli.ts +2 -1
- package/{scripts-ai → scripts/git}/telegram-test-logic.ts +2 -2
- package/{scripts-ai → scripts/git}/telegram-test.ts +7 -7
- package/scripts/hooks/format-edited-cli.ts +15 -0
- package/scripts/hooks/format-edited-file.ts +2 -9
- package/scripts/hooks/format-rewrite-notice.ts +2 -8
- package/scripts/hooks/pretool-guard-cli.ts +21 -0
- package/scripts/hooks/pretool-guard.ts +14 -15
- package/scripts/init/init-ai-copy.ts +2 -5
- package/scripts/init/init.ts +8 -4
- package/scripts/init/start-exec.ts +7 -1
- package/scripts/init/start-plan.ts +18 -5
- package/scripts/init/start-setup-pr.ts +46 -19
- package/scripts/init/start-steps.ts +43 -10
- package/scripts/init/start.ts +42 -11
- package/scripts/issue/issue-cite.ts +19 -1
- package/scripts/issue/issue-comment-cli.ts +2 -1
- package/scripts/issue/issue-file-cli.ts +2 -1
- package/scripts/issue/issue-fold-cli.ts +9 -16
- package/scripts/issue/issue-scout-cli.ts +8 -13
- package/scripts/issue/measure-rerun-cli.ts +7 -6
- package/scripts/josh/josh-command-map.ts +0 -102
- package/scripts/josh/josh-commands-ai.ts +2 -2
- package/scripts/josh/josh-commands-backlog.ts +1 -1
- package/scripts/josh/josh-commands-development.ts +3 -3
- package/scripts/josh/josh-commands-project.ts +1 -1
- package/scripts/josh/josh-commands-workflow.ts +4 -4
- package/scripts/lane/lane-child-marker.ts +16 -2
- package/scripts/lane/lane-cli.ts +34 -15
- package/scripts/lane/lane-open.ts +3 -2
- package/scripts/lane/lane-registry.ts +3 -6
- package/scripts/lane/lane-report.ts +10 -4
- package/scripts/lib/cli-flags.ts +20 -2
- package/scripts/lib/error-message.ts +9 -0
- package/scripts/lib/read-file.ts +26 -2
- package/scripts/observations/observation-ledger-home.ts +98 -26
- package/scripts/observations/observation-ledger-migrate.ts +26 -21
- package/scripts/observations/observation-ledger-prepare.ts +10 -20
- package/scripts/observations/observation-ledger.ts +58 -25
- package/scripts/observations/observations-flush-cli.ts +6 -23
- package/scripts/observations/observations-flush-landing.ts +2 -1
- package/scripts/observations/observations-flush.ts +29 -20
- package/scripts/package/package-scout-cli.ts +8 -16
- package/scripts/ports/port-command.ts +2 -1
- package/scripts/propagate/propagate-plan.ts +185 -0
- package/scripts/propagate/propagate-publish.ts +6 -7
- package/scripts/propagate/propagate-run.ts +5 -1
- package/scripts/propagate/propagate-steps.ts +18 -2
- package/scripts/propagate/propagate-targets.ts +13 -2
- package/scripts/propagate/propagate.ts +63 -44
- package/scripts/refactor/import-graph.ts +2 -6
- package/scripts/registry-migration/migrate-logic.ts +1 -0
- package/scripts/registry-migration/migrate.ts +3 -2
- package/scripts/registry-migration/user-npmrc.ts +2 -2
- package/scripts/release/github-release.ts +1 -1
- package/scripts/release/release-cli.ts +2 -1
- package/scripts/repo/repo-setting.ts +10 -8
- package/scripts/review/review-attest.ts +44 -0
- package/scripts/review/review-brief-cli.ts +49 -2
- package/scripts/review/review-brief.ts +46 -1
- package/scripts/review/review-finding-ledger.ts +3 -3
- package/scripts/review/review-findings-cli.ts +4 -11
- package/scripts/review/review-record-cli.ts +14 -11
- package/scripts/review/review-record.ts +6 -16
- package/scripts/review/review-stamps.ts +7 -0
- package/scripts/rules/decision-oracle.ts +2 -2
- package/scripts/rules/implementation-cut.ts +1 -1
- package/scripts/rules/issue-fold.ts +1 -2
- package/scripts/rules/lane-carry-conflict.ts +1 -2
- package/scripts/rules/lane-park.ts +1 -2
- package/scripts/rules/pre-gate-cut.ts +2 -2
- package/scripts/rules/rule-body-guard.ts +2 -2
- package/scripts/rules/rule-value-cache.ts +99 -0
- package/scripts/rules/rule-value-cli.ts +10 -9
- package/scripts/rules/rule-value.ts +1 -1
- package/scripts/run/run-carry-args.ts +2 -6
- package/scripts/run/run-carry-cli.ts +5 -7
- package/scripts/run/run-cut-args.ts +7 -13
- package/scripts/run/run-ending-cli.ts +4 -7
- package/scripts/run/run-event-stream.ts +2 -5
- package/scripts/run/run-headless.ts +22 -6
- package/scripts/run/run-liveness-cli.ts +4 -7
- package/scripts/run/run-parent-cut-hook.ts +144 -0
- package/scripts/run/run-progress-args.ts +2 -6
- package/scripts/run/run-progress-cli.ts +2 -1
- package/scripts/run/run-progress-read.ts +2 -1
- package/scripts/run/run-progress.ts +21 -8
- package/scripts/run/run-ship-cli.ts +6 -74
- package/scripts/run/run-ship-preflight.ts +87 -0
- package/scripts/run/run-ship-review-steps.ts +5 -26
- package/scripts/run/run-ship-review.ts +9 -0
- package/scripts/run/run-ship-scoped.ts +44 -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 -0
- package/scripts/run/run-tail-cli.ts +3 -3
- package/scripts/run/run-tidy-stashes.ts +7 -14
- package/scripts/run/run-wake-cli.ts +4 -8
- package/scripts/run/run-wake-describe.ts +19 -1
- package/scripts/run/run-wake-driver.ts +15 -2
- package/scripts/run/run-wake-session.ts +14 -4
- package/scripts/run/run-watcher-hook.ts +1 -1
- package/scripts/safe-chain/project-config.ts +3 -6
- package/scripts/security/security-audit-provision.ts +6 -7
- package/scripts/sonar/sonar-hotspots-cli.ts +2 -1
- package/scripts/sync/directory-copy-guard.ts +2 -1
- package/scripts/sync/sync-ai-files.ts +3 -3
- package/scripts/sync/template-source-logic.ts +3 -4
- package/scripts/test/e2e-retry-check.ts +3 -6
- package/scripts/time-runtime/time-bundle-call.ts +2 -2
- package/scripts/ui/ui-routes-cli.ts +3 -6
- package/scripts/version/bump-version.ts +2 -1
- package/scripts/version/npm-registry.ts +97 -0
- package/scripts/version/version-commands.ts +5 -4
- package/scripts/version/version-remote.ts +38 -33
- package/scripts/version/version-targets.ts +3 -3
- package/.claude/skills/workflow-commands/rule-residency.md +0 -267
- package/evals/scenarios/consult-not-execute.json +0 -20
- package/evals/scenarios/no-implicit-workflow.json +0 -26
- package/evals/scenarios/overrides-both-locations.json +0 -29
- package/evals/scenarios/upstream-interrupt.json +0 -23
- package/evals/scenarios/workflow-skill-loaded.json +0 -16
- package/prompts/agent-lint-protocol.json +0 -79
- package/prompts/agent-rules.md +0 -63
- package/prompts/collaboration-workflow/consultation-vs-execution.md +0 -8
- package/prompts/collaboration-workflow/distributed-docs.md +0 -7
- package/prompts/collaboration-workflow/durable-rules.md +0 -24
- package/prompts/collaboration-workflow/latest-first.md +0 -14
- package/prompts/collaboration-workflow/no-clones.md +0 -11
- package/prompts/collaboration-workflow/simplicity-first.md +0 -9
- package/prompts/collaboration-workflow/single-source-rules.md +0 -13
- package/scripts/build/shim-shell.ts +0 -25
- package/scripts/claude/skill-meta.ts +0 -37
- package/scripts/document/document-scan.ts +0 -108
- package/scripts/document/generate-catalog.ts +0 -69
- package/scripts/document/retired-phrases.ts +0 -143
- package/scripts/document/review-agent-type.ts +0 -46
- package/scripts/document/single-source.ts +0 -73
- package/scripts/git/git-fixture-workspace.ts +0 -88
- package/scripts/hooks/format-edited-file-fixtures.ts +0 -109
- package/scripts/hooks/prompt-hooks.ts +0 -38
- package/scripts/init/hook-command-bootstrap.ts +0 -83
- package/scripts/issue/issue-logic.ts +0 -50
- package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +0 -1227
- package/scripts/josh/josh-script-reader.ts +0 -11
- package/scripts/package/production-dependency-graph.ts +0 -68
- package/scripts/retrospective/retrospective-cli.ts +0 -97
- package/scripts/retrospective/retrospective.ts +0 -127
- package/scripts/rules/delivered-rules-harness.ts +0 -72
- package/scripts/rules/top-level-scripts-guard.ts +0 -47
- package/scripts/run/run-carry-flush.ts +0 -58
- package/scripts/run/run-transcript.golden.txt +0 -57
- package/scripts/run/run-wake-work.ts +0 -108
- package/scripts/sync/dependabot-pin-logic.ts +0 -135
- package/scripts/test/classify-isolation.ts +0 -149
- package/scripts/test/http-fault-injection.ts +0 -79
- package/scripts/test/josh-harness-environment.ts +0 -260
- package/scripts/test/josh-harness.ts +0 -96
- package/scripts/test/pilot-files.ts +0 -452
- package/scripts/test/pilot-measure.ts +0 -78
- package/scripts/test/test-network-guard.ts +0 -453
- package/scripts/test/test-repository-guard.ts +0 -317
- package/scripts/test/test-state-guard.ts +0 -85
- package/scripts/test/test-stdout-guard.ts +0 -54
- package/scripts/test/test-telegram-guard.ts +0 -90
- package/scripts/test/unit-guard-environment.ts +0 -12
- package/scripts/test/unit-projects.ts +0 -139
- package/scripts/test/vitest-include-globs.ts +0 -12
- package/scripts/time-runtime/time-batch-guard-fixtures.ts +0 -66
- package/scripts-ai/completion-gate.ts +0 -13
- package/scripts-ai/environment-loader.ts +0 -9
- /package/{scripts-ai → scripts/epic}/epic-cli-argv.ts +0 -0
- /package/{scripts-ai → scripts/epic}/epic-cli-remove.ts +0 -0
- /package/{scripts-ai → scripts/git}/followup-issue-number.ts +0 -0
|
@@ -22,6 +22,11 @@ function has_timed_out(error: unknown): boolean {
|
|
|
22
22
|
return error.timedOut === true
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
// A timeout has no exit code — the process was killed rather than allowed to exit — so `cause` says
|
|
26
|
+
// so in the slot every other git failure puts a number in. Shared by every git spawn that carries a
|
|
27
|
+
// budget: the push (`git-push-transport.ts`) and the fetch and pull (`git-spawn.ts`).
|
|
28
|
+
const TIMEOUT_EXIT_CODE = 'timeout'
|
|
29
|
+
|
|
25
30
|
// The error a spawned git command fails with. The exit code is carried on `cause` as well as in the
|
|
26
31
|
// message, because callers branch on it rather than parse it: `git_command.push` reads 128 there to
|
|
27
32
|
// decide whether to retry with `--set-upstream`.
|
|
@@ -32,4 +37,4 @@ function create_spawn_error(command: string, exit_code: number | undefined): Err
|
|
|
32
37
|
return new Error(error_message, { cause: { exit_code: exit_code_string } })
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
export { create_spawn_error, get_exit_code, has_timed_out }
|
|
40
|
+
export { create_spawn_error, get_exit_code, has_timed_out, TIMEOUT_EXIT_CODE }
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { review_stamps } from '../scripts/review/review-stamps'
|
|
6
|
-
import { run_hold } from '../scripts/run/run-hold'
|
|
7
|
-
import { run_progress_clock } from '../scripts/run/run-progress-clock'
|
|
1
|
+
import { review_attest } from '#scripts/review/review-attest'
|
|
2
|
+
import { review_stamps } from '#scripts/review/review-stamps'
|
|
3
|
+
import { run_hold } from '#scripts/run/run-hold'
|
|
4
|
+
import { run_progress_clock } from '#scripts/run/run-progress-clock'
|
|
8
5
|
import { parse_completed_issue_number } from './followup-issue-number'
|
|
6
|
+
import { git_followup_cleanup, type CleanupStep } from './git-followup-cleanup'
|
|
7
|
+
import { git_followup_pending } from './git-followup-pending'
|
|
8
|
+
import { git_next_issues } from './git-next-issues'
|
|
9
9
|
|
|
10
10
|
// **The count of unreleased merges, not the project version** (joshuafolkken/kit#1486). This line
|
|
11
11
|
// used to read the local `package.json` and was read as "the version this run just shipped"; children
|
|
@@ -1,62 +1,88 @@
|
|
|
1
1
|
import { observation_ledger } from '#scripts/observations/observation-ledger'
|
|
2
|
-
import {
|
|
2
|
+
import { observation_ledger_prepare } from '#scripts/observations/observation-ledger-prepare'
|
|
3
3
|
import { observations_flush } from '#scripts/observations/observations-flush'
|
|
4
4
|
import { git_command } from './git-command'
|
|
5
|
-
import {
|
|
6
|
-
import { main_sync } from './main-sync'
|
|
5
|
+
import { git_spawn } from './git-spawn'
|
|
7
6
|
|
|
8
7
|
// The residual commit path for the observation ledger (joshuafolkken/kit#1810). A run's appended lines
|
|
9
|
-
// ride its own commit (joshuafolkken/kit#2763), so
|
|
10
|
-
// short-circuits; what it still catches is a line appended after
|
|
11
|
-
// record — which would otherwise stay in the working tree
|
|
12
|
-
// wires the flush into `pnpm josh followup`, the one step that knows the merge has landed.
|
|
8
|
+
// ride its own commit (joshuafolkken/kit#2763), so the ledger is usually clean by now and this
|
|
9
|
+
// short-circuits; what it still catches is a line appended after that commit — a second review round's
|
|
10
|
+
// record — which would otherwise stay in the working tree after the merge.
|
|
13
11
|
//
|
|
14
|
-
// **It
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
async function sync_and_flush(): Promise<void> {
|
|
27
|
-
if ((await main_sync.run([])) !== MAIN_SYNC_SUCCESS) throw new Error(SYNC_FAILURE_MESSAGE)
|
|
28
|
-
|
|
29
|
-
console.info(await observations_flush.flush(new Date()))
|
|
12
|
+
// **It commits onto the pull request's own branch, before the merge** (joshuafolkken/kit#2919). It used
|
|
13
|
+
// to flush after the merge from the default branch, which a lane cannot reach, so a lane's lines
|
|
14
|
+
// waited in the primary checkout for a batch-end flush and were lost there to another run's stash.
|
|
15
|
+
// Committed to the branch instead, the lines merge with the pull request — in a lane as anywhere — and
|
|
16
|
+
// the CI wait `pnpm josh followup` runs next covers the pushed commit like any other push.
|
|
17
|
+
|
|
18
|
+
const COMMIT_MESSAGE_PREFIX = 'Record observation ledger entries'
|
|
19
|
+
|
|
20
|
+
function commit_message(issue_number: string | undefined): string {
|
|
21
|
+
return issue_number === undefined
|
|
22
|
+
? COMMIT_MESSAGE_PREFIX
|
|
23
|
+
: `${COMMIT_MESSAGE_PREFIX} #${issue_number}`
|
|
30
24
|
}
|
|
31
25
|
|
|
32
|
-
// **
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
26
|
+
// **A broken line refuses the merge rather than riding it** (joshuafolkken/kit#2123): the branch's
|
|
27
|
+
// ledger file goes away with the lane, so a line left behind here is a line lost.
|
|
28
|
+
async function assert_grammar(): Promise<void> {
|
|
29
|
+
const broken = await observation_ledger_prepare.prepare(await git_command.repository_root())
|
|
30
|
+
|
|
31
|
+
if (broken.length > 0) throw new Error(observations_flush.broken_lines_message(broken))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// **A push that fails takes its commit back**, leaving the lines staged: the next `pnpm josh followup`
|
|
35
|
+
// then finds them pending and retries. Left committed, they would read as clean and the merge would
|
|
36
|
+
// go ahead without them, the lines stranded in a local commit no reader sees.
|
|
37
|
+
async function push_or_undo_commit(): Promise<void> {
|
|
38
|
+
try {
|
|
39
|
+
await git_command.push()
|
|
40
|
+
} catch (error) {
|
|
41
|
+
await git_spawn.read(['reset', '--soft', 'HEAD~1'])
|
|
42
|
+
throw error
|
|
47
43
|
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Only the ledger paths are staged — whatever else the tree holds is the run's to commit, not this
|
|
47
|
+
// step's — and `-A` takes a migration's deletion of an old path with it.
|
|
48
|
+
async function commit_and_push(
|
|
49
|
+
paths: ReadonlyArray<string>,
|
|
50
|
+
issue_number: string | undefined,
|
|
51
|
+
): Promise<void> {
|
|
52
|
+
await git_spawn.read(['add', '-A', '--', ...paths])
|
|
53
|
+
await git_command.commit(commit_message(issue_number))
|
|
54
|
+
await push_or_undo_commit()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// **Short-circuits when the ledger holds no pending append**, which is every run that recorded nothing
|
|
58
|
+
// after its commit, and on a `--no-merge` run, which has no merge for the lines to ride.
|
|
59
|
+
async function commit_ledger_step(
|
|
60
|
+
should_merge: boolean,
|
|
61
|
+
issue_number: string | undefined,
|
|
62
|
+
): Promise<void> {
|
|
63
|
+
if (!should_merge) return
|
|
64
|
+
|
|
65
|
+
const status_output = await git_command.status()
|
|
66
|
+
|
|
67
|
+
if (!observation_ledger.has_pending_append(status_output)) return
|
|
68
|
+
|
|
69
|
+
await assert_grammar()
|
|
70
|
+
|
|
71
|
+
// Read again: the grammar check migrated any old-path ledger, which changes what is to be staged.
|
|
72
|
+
const paths = observation_ledger
|
|
73
|
+
.ledger_paths(await git_command.status())
|
|
74
|
+
.filter((file_path) => !observation_ledger.is_migration_claim(file_path))
|
|
75
|
+
|
|
76
|
+
// A live run's migration claim alone is pending, not a line: with no path, `git add -A --` would
|
|
77
|
+
// stage the whole tree.
|
|
78
|
+
if (paths.length === 0) return
|
|
48
79
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
await git_followup_cleanup.run_guarded_step(should_merge, {
|
|
54
|
-
label: 'The observation ledger flush',
|
|
55
|
-
recovery: FLUSH_RECOVERY,
|
|
56
|
-
run: sync_and_flush,
|
|
57
|
-
})
|
|
80
|
+
await commit_and_push(paths, issue_number)
|
|
81
|
+
console.info(
|
|
82
|
+
`💡 Committed ${String(paths.length)} observation ledger path(s) to the pull request.`,
|
|
83
|
+
)
|
|
58
84
|
}
|
|
59
85
|
|
|
60
|
-
const git_followup_flush = {
|
|
86
|
+
const git_followup_flush = { COMMIT_MESSAGE_PREFIX, commit_ledger_step }
|
|
61
87
|
|
|
62
88
|
export { git_followup_flush }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { epic_parse } from '#scripts/epic/epic-parse'
|
|
1
2
|
import { poll, type PollOptions } from '#scripts/lib/poll'
|
|
2
3
|
import { z } from 'zod'
|
|
3
|
-
import { git_epic_parse } from './git-epic-parse'
|
|
4
4
|
import { git_gh_command } from './git-gh-command'
|
|
5
5
|
import { parse_json_object_safe } from './parse-json-array'
|
|
6
6
|
|
|
@@ -28,7 +28,7 @@ async function is_issue_closed(issue_number: string): Promise<boolean> {
|
|
|
28
28
|
const raw = await git_gh_command.issue_get_state_and_relations(issue_number)
|
|
29
29
|
const parsed = raw === undefined ? undefined : parse_json_object_safe(raw, state_read_schema)
|
|
30
30
|
|
|
31
|
-
return
|
|
31
|
+
return epic_parse.is_state_closed(parsed?.state)
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const FOLLOWUP_CLOSER = 'pnpm josh followup'
|
|
@@ -46,7 +46,7 @@ async function read_label_names(issue_number: string): Promise<ReadonlyArray<str
|
|
|
46
46
|
// **An issue that does not carry it is never written to**, which is what keeps the step silent on a
|
|
47
47
|
// run whose issue was never labelled — a `404` swallowed into a warning would report a failure where
|
|
48
48
|
// there was nothing to do. The read itself is always paid: one `number,labels,body` call per merged
|
|
49
|
-
// run, which is the read `
|
|
49
|
+
// run, which is the read `epic-read.ts` already makes rather than a field list of its own.
|
|
50
50
|
//
|
|
51
51
|
// A run whose pull request named no issue has nothing to strip, so it returns rather than throwing:
|
|
52
52
|
// the number is recovered from the `closes #N` keyword, and its absence is already reported where
|
|
@@ -21,7 +21,7 @@ import { git_command } from './git-command'
|
|
|
21
21
|
// there cannot include this run's own merge, and a bare number would be understated by exactly one.
|
|
22
22
|
// Saying so is the honest form of the same measurement. The console line printed after the merge
|
|
23
23
|
// passes `false` and carries no note, because by then the merge is on the remote and the fetch below
|
|
24
|
-
// brings it in; `
|
|
24
|
+
// brings it in; `git-followup-finish.ts` is that caller, so **both branches of this flag
|
|
25
25
|
// are live**. What a run *does* about the count is `pnpm josh release:scope`, asked after the merge,
|
|
26
26
|
// which reads `read_pending` below rather than this line (joshuafolkken/kit#1582).
|
|
27
27
|
const MERGE_PENDING_NOTE = "— this run's merge is not counted; it lands next"
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { parseArgs } from 'node:util'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { live_evidence } from '../scripts/review/live-evidence'
|
|
9
|
-
import { review_attest } from '../scripts/review/review-attest'
|
|
10
|
-
import { review_record } from '../scripts/review/review-record'
|
|
11
|
-
import { load_optional_environment } from './environment-loader'
|
|
3
|
+
import { cli_body } from '#scripts/josh/cli-body'
|
|
4
|
+
import { josh_environment_file } from '#scripts/josh/josh-environment-file'
|
|
5
|
+
import { live_evidence } from '#scripts/review/live-evidence'
|
|
6
|
+
import { review_attest } from '#scripts/review/review-attest'
|
|
7
|
+
import { review_record } from '#scripts/review/review-record'
|
|
12
8
|
import { parse_issue_number_from_text } from './followup-issue-number'
|
|
9
|
+
import { git_branch } from './git-branch'
|
|
10
|
+
import { git_error } from './git-error'
|
|
13
11
|
import { git_followup_finish } from './git-followup-finish'
|
|
12
|
+
import { git_followup_flush } from './git-followup-flush'
|
|
13
|
+
import { git_notify, type GitNotifyConfig } from './git-notify'
|
|
14
|
+
import { git_pr_followup } from './git-pr-followup'
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
josh_environment_file.load_environment_file()
|
|
16
17
|
|
|
17
18
|
// cspell:words coderabbit
|
|
18
19
|
|
|
@@ -202,6 +203,17 @@ async function assert_merge_gates(
|
|
|
202
203
|
await assert_live_evidence(should_merge, branch_name)
|
|
203
204
|
}
|
|
204
205
|
|
|
206
|
+
// After the gates, before the CI wait: a line appended since the run's commit rides the pull request
|
|
207
|
+
// itself, so it merges with it — a lane's included (joshuafolkken/kit#2919).
|
|
208
|
+
async function prepare_merge(
|
|
209
|
+
should_merge: boolean,
|
|
210
|
+
issue_number: string | undefined,
|
|
211
|
+
branch_name: string,
|
|
212
|
+
): Promise<void> {
|
|
213
|
+
await assert_merge_gates(should_merge, issue_number, branch_name)
|
|
214
|
+
await git_followup_flush.commit_ledger_step(should_merge, issue_number)
|
|
215
|
+
}
|
|
216
|
+
|
|
205
217
|
async function main(): Promise<void> {
|
|
206
218
|
const cli = parse_cli_arguments()
|
|
207
219
|
|
|
@@ -216,7 +228,7 @@ async function main(): Promise<void> {
|
|
|
216
228
|
const should_merge = is_merge_resolved(cli.values)
|
|
217
229
|
const branch_name = await resolve_branch_name(cli.values.branch)
|
|
218
230
|
|
|
219
|
-
await
|
|
231
|
+
await prepare_merge(should_merge, issue_number, branch_name)
|
|
220
232
|
// **The number the run reports on is the one it used**, which is the number the pull request
|
|
221
233
|
// closes where the invocation named none (joshuafolkken/kit#1539). Recovered inside `run`, so the
|
|
222
234
|
// tail records a run the command line could not identify rather than silently skipping it.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { agent_session_environment } from '#scripts/josh/agent-session-environment'
|
|
2
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
2
3
|
import { execa, execaSync } from 'execa'
|
|
3
4
|
import { has_timed_out } from './git-execa-error'
|
|
4
5
|
import { check_gh_installed } from './git-gh-check'
|
|
@@ -21,7 +22,7 @@ const BODY_FROM_STDIN = '-'
|
|
|
21
22
|
// *sleeps* rather than the wall clock, so each poll's requests sit on top of it. A request allowed
|
|
22
23
|
// to outlast that whole window would make the budget meaningless; half of it keeps one request
|
|
23
24
|
// from dominating the look-ahead.
|
|
24
|
-
// -
|
|
25
|
+
// - the readers that override it ask for 5 seconds (`repo-setting.ts`) and 20
|
|
25
26
|
// (`propagate-publish.ts`, `epic-cross-repo.ts`). Sixty is deliberately the most generous of them,
|
|
26
27
|
// because this entry also serves `--paginate` requests — several HTTP round trips inside one
|
|
27
28
|
// spawn — which a 5-second budget would break.
|
|
@@ -70,10 +71,10 @@ function to_timeout_option(timeout_ms?: number): { timeout: number } {
|
|
|
70
71
|
// Nothing is spread when there is nothing to remove, so a spawn outside such a wrapper keeps its
|
|
71
72
|
// options exactly as they were.
|
|
72
73
|
//
|
|
73
|
-
// **It is exported
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
// a
|
|
74
|
+
// **It is exported for the one spawn that is not a REST request** — `josh start`'s `gh repo create`
|
|
75
|
+
// step (`start-exec.ts`). Every `gh api` request goes through the entries below since
|
|
76
|
+
// joshuafolkken/kit#2901, which `gh-subcommand-guard.test.ts` enforces, and
|
|
77
|
+
// `git-gh-exec-proxy.test.ts` still refuses a `gh api` spawn that does not spread it.
|
|
77
78
|
interface DirectEnvironment {
|
|
78
79
|
env?: Record<string, undefined>
|
|
79
80
|
}
|
|
@@ -84,8 +85,20 @@ function direct_environment(): DirectEnvironment {
|
|
|
84
85
|
return Object.keys(removed).length > 0 ? { env: removed } : {}
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
interface SpawnOptions extends DirectEnvironment {
|
|
89
|
+
timeout: number
|
|
90
|
+
cwd?: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// `cwd` is spread in only when given: `{owner}/{repo}` in a path is resolved from the checkout gh
|
|
94
|
+
// runs in, so a caller pinned to the project root says so, and every other caller keeps inheriting
|
|
95
|
+
// the process's own directory exactly as before.
|
|
96
|
+
function to_spawn_options(timeout_ms?: number, cwd?: string): SpawnOptions {
|
|
97
|
+
return {
|
|
98
|
+
...to_timeout_option(timeout_ms),
|
|
99
|
+
...direct_environment(),
|
|
100
|
+
...(cwd !== undefined && { cwd }),
|
|
101
|
+
}
|
|
89
102
|
}
|
|
90
103
|
|
|
91
104
|
// What a request that ran out of time is labelled with, ahead of whatever gh managed to write.
|
|
@@ -113,10 +126,6 @@ function to_timeout_prefix(error: unknown): string {
|
|
|
113
126
|
return has_timed_out(error) ? `${GH_REQUEST_TIMEOUT_MESSAGE}: ` : ''
|
|
114
127
|
}
|
|
115
128
|
|
|
116
|
-
function to_error_message(error: unknown): string {
|
|
117
|
-
return error instanceof Error ? error.message : String(error)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
129
|
// Surface the gh CLI's stderr as the thrown message when present (matching the previous spawn
|
|
121
130
|
// behavior), otherwise fall back to execa's own message — **and append what gh wrote to stdout**.
|
|
122
131
|
//
|
|
@@ -134,7 +143,7 @@ function to_error_message(error: unknown): string {
|
|
|
134
143
|
function to_gh_error(error: unknown): Error {
|
|
135
144
|
const stderr = has_stderr_field(error) ? error.stderr.trim() : ''
|
|
136
145
|
const stdout = has_stdout_field(error) ? error.stdout.trim() : ''
|
|
137
|
-
const summary = stderr.length > 0 ? stderr :
|
|
146
|
+
const summary = stderr.length > 0 ? stderr : error_text.message_of(error)
|
|
138
147
|
const detail = stdout.length > 0 ? `${summary}\n${stdout}` : summary
|
|
139
148
|
|
|
140
149
|
return gh_failure.attach(
|
|
@@ -143,11 +152,16 @@ function to_gh_error(error: unknown): Error {
|
|
|
143
152
|
)
|
|
144
153
|
}
|
|
145
154
|
|
|
146
|
-
async function exec_gh_command(
|
|
155
|
+
async function exec_gh_command(
|
|
156
|
+
arguments_: Array<string>,
|
|
157
|
+
timeout_ms?: number,
|
|
158
|
+
cwd?: string,
|
|
159
|
+
): Promise<string> {
|
|
147
160
|
await check_gh_installed()
|
|
161
|
+
const options = to_spawn_options(timeout_ms, cwd)
|
|
148
162
|
|
|
149
163
|
try {
|
|
150
|
-
const { stdout } = await execa('gh', arguments_,
|
|
164
|
+
const { stdout } = await execa('gh', arguments_, options) // NOSONAR S8705: execa array args (no shell), trusted dev CLI tooling
|
|
151
165
|
|
|
152
166
|
return stdout.trimEnd()
|
|
153
167
|
} catch (error) {
|
|
@@ -161,12 +175,13 @@ async function exec_gh_command_with_stdin(input: {
|
|
|
161
175
|
// `number | undefined` rather than plain optional: `exec_gh_api` forwards its own optional field,
|
|
162
176
|
// which `exactOptionalPropertyTypes` will not narrow for it.
|
|
163
177
|
timeout_ms?: number | undefined
|
|
178
|
+
cwd?: string | undefined
|
|
164
179
|
}): Promise<string> {
|
|
165
180
|
await check_gh_installed()
|
|
166
181
|
|
|
167
182
|
// Built ahead of the call so the spawn itself stays on one line: `// NOSONAR` suppresses the rule
|
|
168
183
|
// only on the line it sits on, and a wrapped call moves that line away from the reported one.
|
|
169
|
-
const options = { input: input.stdin_body, ...to_spawn_options(input.timeout_ms) }
|
|
184
|
+
const options = { input: input.stdin_body, ...to_spawn_options(input.timeout_ms, input.cwd) }
|
|
170
185
|
|
|
171
186
|
try {
|
|
172
187
|
const { stdout } = await execa('gh', input.args, options) // NOSONAR S8705: execa array args (no shell), trusted dev CLI tooling
|
|
@@ -186,6 +201,12 @@ function parse_status_line(output: string): number | undefined {
|
|
|
186
201
|
return status === undefined ? undefined : Number(status)
|
|
187
202
|
}
|
|
188
203
|
|
|
204
|
+
// What a status probe that exited non-zero reached: `--include` keeps the status line even then,
|
|
205
|
+
// and execa carries it on the error it throws.
|
|
206
|
+
function status_of_failure(error: unknown): number | undefined {
|
|
207
|
+
return has_stdout_field(error) ? parse_status_line(error.stdout) : undefined
|
|
208
|
+
}
|
|
209
|
+
|
|
189
210
|
// The HTTP status of one `gh api` request, read from the status line rather than from `gh`'s error
|
|
190
211
|
// text. A message is prose that can be reworded between releases; the status code is the protocol,
|
|
191
212
|
// so a caller distinguishing "there is nothing at that number" (404) from "the read failed" (403,
|
|
@@ -215,7 +236,24 @@ async function exec_gh_api_status(path: string, timeout_ms?: number): Promise<nu
|
|
|
215
236
|
|
|
216
237
|
return parse_status_line(stdout)
|
|
217
238
|
} catch (error) {
|
|
218
|
-
return
|
|
239
|
+
return status_of_failure(error)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// The synchronous twin of `exec_gh_api_status`, for `epic-cross-repo.ts`, whose resolver is
|
|
244
|
+
// synchronous all the way up to `epic_classify` (joshuafolkken/kit#2901). Same request, same
|
|
245
|
+
// answer: the status line, or `undefined` when no status was reached. It cannot run
|
|
246
|
+
// `check_gh_installed`, which awaits — a missing gh is a spawn that throws without stdout, so it
|
|
247
|
+
// answers `undefined`, which is what that check would have led to anyway.
|
|
248
|
+
function exec_gh_api_status_sync(path: string, timeout_ms?: number): number | undefined {
|
|
249
|
+
const options = to_spawn_options(timeout_ms)
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
const { stdout } = execaSync('gh', ['api', '--include', '--silent', path], options) // NOSONAR S8705: execa array args (no shell), trusted dev CLI tooling
|
|
253
|
+
|
|
254
|
+
return parse_status_line(stdout)
|
|
255
|
+
} catch (error) {
|
|
256
|
+
return status_of_failure(error)
|
|
219
257
|
}
|
|
220
258
|
}
|
|
221
259
|
|
|
@@ -251,8 +289,13 @@ interface GhApiRequest {
|
|
|
251
289
|
should_slurp?: boolean
|
|
252
290
|
jq_filter?: string
|
|
253
291
|
// The budget for this one request, overriding `GH_REQUEST_TIMEOUT_MS`. A field rather than a
|
|
254
|
-
// second argument for the reason recorded beside that constant (joshuafolkken/kit#1065).
|
|
292
|
+
// second argument for the reason recorded beside that constant (joshuafolkken/kit#1065). `0` is
|
|
293
|
+
// execa's own "no budget", kept for the one reader whose caller's writes depend on the answer
|
|
294
|
+
// (`gh-spawn.ts`, joshuafolkken/kit#805).
|
|
255
295
|
timeout_ms?: number
|
|
296
|
+
// The checkout gh runs in, which is what `{owner}/{repo}` in `path` resolves against. Absent means
|
|
297
|
+
// the process's own directory.
|
|
298
|
+
cwd?: string
|
|
256
299
|
}
|
|
257
300
|
|
|
258
301
|
function optional_flag(flag: string, value?: string): Array<string> {
|
|
@@ -277,20 +320,23 @@ function to_gh_api_args(request: GhApiRequest): Array<string> {
|
|
|
277
320
|
|
|
278
321
|
// The response body of one `gh api` request, as text. Every REST caller that can await one goes
|
|
279
322
|
// through it, so the verb, the body and the paging stop being spelled out per call site
|
|
280
|
-
// (joshuafolkken/kit#1023).
|
|
281
|
-
//
|
|
323
|
+
// (joshuafolkken/kit#1023). A caller that cannot await uses `exec_gh_api_sync` or
|
|
324
|
+
// `read_gh_api_sync` below.
|
|
282
325
|
//
|
|
283
326
|
// Failure handling funnels through `to_gh_error` on both paths below, so a failed request throws
|
|
284
327
|
// with gh's stderr summary *and* the JSON error body it wrote to stdout.
|
|
285
328
|
async function exec_gh_api(request: GhApiRequest): Promise<string> {
|
|
286
329
|
const args = to_gh_api_args(request)
|
|
287
330
|
|
|
288
|
-
if (request.body === undefined)
|
|
331
|
+
if (request.body === undefined) {
|
|
332
|
+
return await exec_gh_command(args, request.timeout_ms, request.cwd)
|
|
333
|
+
}
|
|
289
334
|
|
|
290
335
|
return await exec_gh_command_with_stdin({
|
|
291
336
|
args,
|
|
292
337
|
stdin_body: request.body,
|
|
293
338
|
timeout_ms: request.timeout_ms,
|
|
339
|
+
cwd: request.cwd,
|
|
294
340
|
})
|
|
295
341
|
}
|
|
296
342
|
|
|
@@ -300,11 +346,10 @@ async function exec_gh_api(request: GhApiRequest): Promise<string> {
|
|
|
300
346
|
// opens in the consumer is created here rather than by turning the whole runner asynchronous for
|
|
301
347
|
// one request (joshuafolkken/kit#1042).
|
|
302
348
|
//
|
|
303
|
-
// It
|
|
304
|
-
//
|
|
305
|
-
// `
|
|
306
|
-
//
|
|
307
|
-
// were left as they are rather than migrated here in passing.
|
|
349
|
+
// It began as the write side's synchronous entry; since joshuafolkken/kit#2901 the synchronous
|
|
350
|
+
// readers — `gh-spawn.ts`, `epic-cross-repo.ts`, `repo-setting.ts`, `propagate-publish.ts`,
|
|
351
|
+
// `version-remote.ts` — go through it too, rather than each spelling its own `execaSync('gh', …)`
|
|
352
|
+
// with its own error handling, timeout and proxy handling.
|
|
308
353
|
//
|
|
309
354
|
// **It is the same layer, not a second one.** The argument builder and the error translation are
|
|
310
355
|
// shared with the asynchronous path above, so `--input -` for the body, `--jq` for the unwrap, and
|
|
@@ -318,7 +363,7 @@ async function exec_gh_api(request: GhApiRequest): Promise<string> {
|
|
|
318
363
|
// replaced already did.
|
|
319
364
|
function exec_gh_api_sync(request: GhApiRequest): string {
|
|
320
365
|
const body_option = request.body === undefined ? {} : { input: request.body }
|
|
321
|
-
const options = { ...body_option, ...to_spawn_options(request.timeout_ms) }
|
|
366
|
+
const options = { ...body_option, ...to_spawn_options(request.timeout_ms, request.cwd) }
|
|
322
367
|
|
|
323
368
|
try {
|
|
324
369
|
const { stdout } = execaSync('gh', to_gh_api_args(request), options) // NOSONAR S8705: execa array args (no shell), trusted dev CLI tooling
|
|
@@ -329,13 +374,27 @@ function exec_gh_api_sync(request: GhApiRequest): string {
|
|
|
329
374
|
}
|
|
330
375
|
}
|
|
331
376
|
|
|
377
|
+
// `exec_gh_api_sync` for a reader to which a failed request is an answer rather than an error:
|
|
378
|
+
// `undefined` for every failure — a non-zero exit, a timeout, gh missing — and the response text
|
|
379
|
+
// otherwise. The readers that degrade instead of throwing (a report that says `unreadable`, a
|
|
380
|
+
// registry read that retries) share this one catch rather than each writing their own.
|
|
381
|
+
function read_gh_api_sync(request: GhApiRequest): string | undefined {
|
|
382
|
+
try {
|
|
383
|
+
return exec_gh_api_sync(request)
|
|
384
|
+
} catch {
|
|
385
|
+
return undefined
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
332
389
|
const git_gh_exec = {
|
|
333
390
|
direct_environment,
|
|
334
391
|
exec_gh_command,
|
|
335
392
|
exec_gh_command_with_stdin,
|
|
336
393
|
exec_gh_api,
|
|
337
394
|
exec_gh_api_sync,
|
|
395
|
+
read_gh_api_sync,
|
|
338
396
|
exec_gh_api_status,
|
|
397
|
+
exec_gh_api_status_sync,
|
|
339
398
|
parse_status_line,
|
|
340
399
|
}
|
|
341
400
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { IssueReference } from './git-epic-reference'
|
|
2
1
|
import { git_gh_api_path } from './git-gh-api-path'
|
|
3
2
|
import { git_gh_exec } from './git-gh-exec'
|
|
4
3
|
import { gh_failure, type GhFailure } from './git-gh-failure'
|
|
@@ -10,6 +9,7 @@ import {
|
|
|
10
9
|
type RestIssue,
|
|
11
10
|
} from './git-gh-issue-rest'
|
|
12
11
|
import { gh_reachability } from './git-gh-reachability'
|
|
12
|
+
import type { IssueReference } from './issue-reference'
|
|
13
13
|
|
|
14
14
|
// Reading one issue. Split out of `git-gh-issue.ts`, which had grown past the file-length limit
|
|
15
15
|
// while holding both the reads and the writes; the reads are what every epic command goes through,
|
|
@@ -341,7 +341,7 @@ async function issue_get_plan_fields_classified(issue_number: string): Promise<I
|
|
|
341
341
|
return await issue_view_json_classified(issue_number, PLAN_FIELDS)
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
-
// `state` rides along for `
|
|
344
|
+
// `state` rides along for `epic-add.ts`'s closed-epic refusal (joshuafolkken/kit#2337); every
|
|
345
345
|
// other caller reads only the labels and body and ignores it, so it costs no extra request.
|
|
346
346
|
async function issue_get_labels_and_body(issue_number: string): Promise<string | undefined> {
|
|
347
347
|
return await issue_view_json(issue_number, 'number,state,labels,body')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
-
import type { IssueReference } from './git-epic-reference'
|
|
3
2
|
import { MERGED_STATE, to_gh_state } from './git-gh-rest-state'
|
|
3
|
+
import type { IssueReference } from './issue-reference'
|
|
4
4
|
import { parse_json_array_or_undefined, parse_json_object_safe } from './parse-json-array'
|
|
5
5
|
import { blocking_issue_schema } from './schemas'
|
|
6
6
|
|
|
@@ -19,7 +19,7 @@ import { blocking_issue_schema } from './schemas'
|
|
|
19
19
|
//
|
|
20
20
|
// `number` is required so that a 200 carrying something other than an issue — a proxy's HTML error
|
|
21
21
|
// page, an API message object — fails the parse rather than passing as an issue whose every field is
|
|
22
|
-
// missing. `
|
|
22
|
+
// missing. `epic-close` would turn that into `is_closed: false` for every child and report it as
|
|
23
23
|
// fact. The dependency summary is absent on a pull request, which the issue endpoint serves as
|
|
24
24
|
// readily as an issue, so it stays optional and tolerates an explicit null.
|
|
25
25
|
const rest_issue_schema = z.looseObject({
|
|
@@ -24,7 +24,7 @@ const PATCH_METHOD = 'PATCH'
|
|
|
24
24
|
const DELETE_METHOD = 'DELETE'
|
|
25
25
|
|
|
26
26
|
// `gh issue edit`, `gh issue comment` and `gh issue create` each printed one URL, and the callers
|
|
27
|
-
// read it — `
|
|
27
|
+
// read it — `epic-run.ts` parses the epic's number back out of it. REST answers the whole
|
|
28
28
|
// object, so the same value is unwrapped out of it rather than the shape being changed.
|
|
29
29
|
const HTML_URL_FILTER = '.html_url'
|
|
30
30
|
const ID_FILTER = '.id'
|
|
@@ -34,9 +34,9 @@ const COMMENTS_SEGMENT = '/comments'
|
|
|
34
34
|
const CLOSED_STATE = 'closed'
|
|
35
35
|
const COLOR_HASH = '#'
|
|
36
36
|
|
|
37
|
-
// The four writers below answered `boolean` under `gh`, and their callers read it: `
|
|
38
|
-
// only reports the epic label when it was applied, `
|
|
39
|
-
// could not apply, and `
|
|
37
|
+
// The four writers below answered `boolean` under `gh`, and their callers read it: `epic-run.ts`
|
|
38
|
+
// only reports the epic label when it was applied, `epic-relations.ts` counts the relations it
|
|
39
|
+
// could not apply, and `epic-close.ts` prints "close it manually" on a false. One try/catch
|
|
40
40
|
// rather than four copies of it (`CLAUDE.md` → "No clones").
|
|
41
41
|
async function did_write_succeed(write: () => Promise<unknown>): Promise<boolean> {
|
|
42
42
|
try {
|
|
@@ -77,7 +77,7 @@ async function issue_try_comment(issue_number: string, body: string): Promise<bo
|
|
|
77
77
|
// **The comment goes first**, which is what keeps the return value meaning what it meant: a `false`
|
|
78
78
|
// then says "the issue is still open" in *both* failure branches, because the state change is the
|
|
79
79
|
// last thing attempted. Closing first would answer `false` for an issue that is in fact closed, and
|
|
80
|
-
// `
|
|
80
|
+
// `epic-close.ts` prints "close it manually" on that answer.
|
|
81
81
|
//
|
|
82
82
|
// **`comment: undefined` closes without commenting**, and it is what the ordering above costs: a run
|
|
83
83
|
// whose comment landed and whose close was refused leaves the issue open carrying the comment, so
|
|
@@ -168,7 +168,7 @@ function issue_create_request(input: {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
// The browser URL of the created issue, which is what `gh issue create` printed and what
|
|
171
|
-
// `
|
|
171
|
+
// `epic-run.ts` parses the epic's number out of.
|
|
172
172
|
async function issue_create_with_label(input: {
|
|
173
173
|
title: string
|
|
174
174
|
label: string
|
|
@@ -43,9 +43,6 @@ const COMMENTS_QUERY = FULL_PAGE_QUERY
|
|
|
43
43
|
// Written as a constant rather than inline: `{owner}` inside a template literal reads as a broken
|
|
44
44
|
// interpolation.
|
|
45
45
|
const OWNER_PLACEHOLDER = '{owner}'
|
|
46
|
-
const NO_HEAD_REF_MESSAGE = 'gh api answered a pull request with no head branch'
|
|
47
|
-
const FORK_HEAD_MESSAGE =
|
|
48
|
-
'gh api answered a pull request whose head branch is in another repository'
|
|
49
46
|
// What a branch-keyed caller that cannot fold an absence into its own answer throws. Lives here
|
|
50
47
|
// rather than beside either caller: the merge-gate snapshot and the two branch-keyed writes all need
|
|
51
48
|
// it, and three copies of one message is the clone `CLAUDE.md` prohibits (joshuafolkken/kit#1029).
|
|
@@ -236,21 +233,6 @@ async function pr_view(branch_name: string): Promise<string> {
|
|
|
236
233
|
return JSON.stringify(git_gh_pr_rest.to_pr_info(pull))
|
|
237
234
|
}
|
|
238
235
|
|
|
239
|
-
// Already keyed by number, so no resolution is needed. It throws where the others fold to an empty
|
|
240
|
-
// answer, which is the contract `sync-dependabot-pins.ts` relies on: it checks out the branch it is
|
|
241
|
-
// handed, and a guessed one would push template pins onto the wrong PR.
|
|
242
|
-
async function pr_head_reference(pr_number: number): Promise<string> {
|
|
243
|
-
const pull = await read_pull(pr_number)
|
|
244
|
-
const reference = pull.head?.ref
|
|
245
|
-
if (reference === undefined) throw new Error(NO_HEAD_REF_MESSAGE)
|
|
246
|
-
// A fork's head is not on `origin` under this name, and `origin` may well carry a different branch
|
|
247
|
-
// that answers to it. Refusing is the same contract the missing ref has: the caller checks out what
|
|
248
|
-
// it is handed, so a guessed branch pushes template pins onto the wrong pull request.
|
|
249
|
-
if (!git_gh_pr_rest.is_same_repository_head(pull)) throw new Error(FORK_HEAD_MESSAGE)
|
|
250
|
-
|
|
251
|
-
return reference
|
|
252
|
-
}
|
|
253
|
-
|
|
254
236
|
// `undefined` when the listing could not be read — a failed request, or a PR whose number could not
|
|
255
237
|
// be resolved. Not `'[]'`, which every failure used to become.
|
|
256
238
|
//
|
|
@@ -300,7 +282,6 @@ const git_gh_pr_read = {
|
|
|
300
282
|
pr_get_url,
|
|
301
283
|
pr_get_body,
|
|
302
284
|
pr_view,
|
|
303
|
-
pr_head_reference,
|
|
304
285
|
pr_get_comments,
|
|
305
286
|
pr_get_review_comments,
|
|
306
287
|
}
|
|
@@ -315,8 +296,6 @@ export {
|
|
|
315
296
|
read_pull,
|
|
316
297
|
require_pr_number,
|
|
317
298
|
resolve_pr_number,
|
|
318
|
-
FORK_HEAD_MESSAGE,
|
|
319
|
-
NO_HEAD_REF_MESSAGE,
|
|
320
299
|
NO_PULL_REQUEST_MESSAGE,
|
|
321
300
|
UNREADABLE_PULL_REQUEST_MESSAGE,
|
|
322
301
|
}
|