@joshuafolkken/kit 1.1005.0 → 1.1035.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/workflow-commands/SKILL.md +8 -23
- package/.claude/skills/workflow-commands/backlogrun-child.md +4 -2
- package/.claude/skills/workflow-commands/backlogrun.md +3 -12
- 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/observation-filing.md +42 -43
- package/.claude/skills/workflow-commands/prerequisite.md +1 -1
- package/.codex/hooks.json +1 -5
- 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 -58
- package/dist/CLAUDE.md +24 -38
- package/dist/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-WMIR5SNX.js → chunk-D67KVGGY.js} +3 -3
- package/dist/hooks/{chunk-EZLHXKJ6.js → chunk-EIFCAJRB.js} +70 -27
- package/dist/hooks/{chunk-KPZN42RT.js → chunk-GJT4BNVE.js} +3 -3
- package/dist/hooks/{chunk-PAXMLGSS.js → chunk-GLBDBJOK.js} +44 -24
- package/dist/hooks/{chunk-GBJUERYK.js → chunk-HOYJU5RM.js} +1 -1
- package/dist/hooks/{chunk-6PSTVOLH.js → chunk-HU66PWWL.js} +33 -57
- package/dist/hooks/{chunk-A2H5I2VD.js → chunk-KG6PZD72.js} +2 -2
- package/dist/hooks/{chunk-AWTFKKFW.js → chunk-XPLP27WA.js} +3 -8
- package/dist/hooks/{chunk-4GDR2YTV.js → chunk-Y27M6A7X.js} +4 -4
- package/dist/hooks/{chunk-56LGMJJV.js → chunk-ZXNE6NY3.js} +2 -2
- package/dist/hooks/codex-hook-adapter.js +10 -10
- package/dist/hooks/duplicate-read-guard.js +5 -5
- package/dist/hooks/format-edited.js +2 -2
- package/dist/hooks/investigation-guard.js +5 -5
- package/dist/hooks/pretool-guard.js +9 -9
- package/dist/hooks/stop-guard.js +11 -15
- package/dist/josh.js +38 -19
- package/dist/version/index.js +93 -41
- package/eslint/base.js +2 -11
- package/package.json +32 -6
- 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/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 +54 -14
- package/prompts/collaboration-workflow/rule-delivery.md +1 -11
- package/prompts/collaboration-workflow/upstream-interrupt.md +1 -1
- package/prompts/collaboration-workflow.md +22 -28
- package/scripts/adopt/adopt-logic.ts +2 -2
- package/scripts/adopt/adopt-toolkits.ts +1 -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/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-offer-cli.ts +6 -5
- package/scripts/backlog/backlog-plan.ts +1 -1
- package/scripts/build/build-bin.ts +2 -1
- package/scripts/build/build-config-merge.ts +2 -1
- package/scripts/build/build-hooks.ts +24 -4
- 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/doctor/doctor-consumer.ts +5 -12
- package/scripts/doctor/doctor-ports.ts +2 -2
- package/scripts/document/document-byte-budget.ts +8 -16
- package/scripts/document/document-section.ts +2 -6
- package/scripts/document/entry-read-budget.ts +1 -1
- 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-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/{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/{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 +1 -1
- package/scripts/git/git-error.ts +2 -5
- 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-pr-followup-wrapup.ts +2 -6
- package/scripts/git/git-preflight.ts +3 -6
- 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/issue-labels.ts +1 -1
- 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-rewrite-notice.ts +2 -8
- package/scripts/init/init-ai-copy.ts +23 -14
- package/scripts/init/init.ts +8 -4
- package/scripts/init/start-steps.ts +2 -1
- package/scripts/init/start.ts +2 -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-commands-ai.ts +2 -2
- package/scripts/josh/josh-commands-backlog.ts +1 -1
- package/scripts/josh/josh-commands-development.ts +1 -1
- package/scripts/josh/josh-commands-workflow.ts +4 -4
- package/scripts/lane/lane-cli.ts +2 -1
- package/scripts/lane/lane-open.ts +3 -2
- package/scripts/lane/lane-registry.ts +3 -6
- 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-publish.ts +6 -7
- package/scripts/propagate/propagate-steps.ts +2 -1
- package/scripts/propagate/propagate.ts +3 -2
- package/scripts/refactor/import-graph.ts +2 -6
- package/scripts/registry-migration/migrate.ts +3 -2
- package/scripts/registry-migration/user-npmrc.ts +2 -2
- package/scripts/release/release-cli.ts +2 -1
- package/scripts/repo/repo-setting.ts +10 -8
- 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/rules/decision-oracle.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-liveness-cli.ts +4 -7
- package/scripts/run/run-progress-args.ts +2 -6
- package/scripts/run/run-progress-cli.ts +2 -1
- 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 +2 -6
- package/scripts/run/run-wake-driver.ts +15 -2
- package/scripts/run/run-wake-session.ts +14 -4
- 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/ui/ui-routes-cli.ts +3 -6
- package/scripts/version/bump-version.ts +2 -1
- package/scripts/version/version-remote.ts +18 -27
- 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
|
@@ -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
|
}
|
package/scripts/git/git-gh-pr.ts
CHANGED
|
@@ -21,7 +21,8 @@ import { git_gh_pr_snapshot } from './git-gh-pr-snapshot'
|
|
|
21
21
|
// **Every path here was measured against the live API before it was written**, on a throwaway pull
|
|
22
22
|
// request opened between two throwaway branches so that neither `main` nor any real pull request was
|
|
23
23
|
// touched. Two of the four did not behave as reading the documentation would suggest: see
|
|
24
|
-
// `pr_create` for the duplicate-head response
|
|
24
|
+
// `pr_create` for the duplicate-head response. The other, `pr_checkout`, is gone with its one caller,
|
|
25
|
+
// `sync-dependabot-pins` (joshuafolkken/kit#2887).
|
|
25
26
|
//
|
|
26
27
|
// `pr_checks` — a `gh pr checks <branch>` wrapper — is gone rather than converted. Nothing called it:
|
|
27
28
|
// the merge gate reads the rollup out of the snapshot and `git-pr-checks-watch.ts` runs the watching
|
|
@@ -139,27 +140,6 @@ async function pr_create(
|
|
|
139
140
|
return url
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
// **`gh pr checkout` does use GraphQL** — one `POST /graphql` to resolve the pull request, measured
|
|
143
|
-
// with `GH_DEBUG=api` on the throwaway pull request (joshuafolkken/kit#1029). Everything after that
|
|
144
|
-
// is git, so only the resolution had to move: `pr_head_reference` is the REST read that already
|
|
145
|
-
// replaced it for this file's one caller, `sync-dependabot-pins.ts`.
|
|
146
|
-
//
|
|
147
|
-
// `fetch_branch` updates `refs/remotes/origin/<branch>`, which is what lets the plain `checkout`
|
|
148
|
-
// resolve a branch that exists only on the remote — the tracking branch `gh` left behind, by the
|
|
149
|
-
// operations `git-command.ts` already owns rather than a new wrapper. The fast-forward is the third
|
|
150
|
-
// thing the CLI did and the one a fetch-and-checkout pair silently drops: a branch that is already
|
|
151
|
-
// local is checked out at whatever commit the last run left it on.
|
|
152
|
-
//
|
|
153
|
-
// A fork's head is refused by `pr_head_reference` rather than fetched from `origin` under a name
|
|
154
|
-
// that repository may use for something else.
|
|
155
|
-
async function pr_checkout(pr_number: number): Promise<void> {
|
|
156
|
-
const branch_name = await git_gh_pr_read.pr_head_reference(pr_number)
|
|
157
|
-
|
|
158
|
-
await git_command.fetch_branch(branch_name)
|
|
159
|
-
await git_command.checkout(branch_name)
|
|
160
|
-
await git_command.merge_fast_forward(branch_name)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
143
|
// A pull request's conversation comment is an **issue** comment: `POST issues/{N}/comments` is the
|
|
164
144
|
// endpoint, and it answers the same `html_url` for a pull request as for an issue (measured on the
|
|
165
145
|
// throwaway pull request). `git-gh-issue-write.ts` already posts to it, so this resolves the number
|
|
@@ -270,7 +250,6 @@ const git_gh_pr = {
|
|
|
270
250
|
pr_create,
|
|
271
251
|
pr_get_classification,
|
|
272
252
|
pr_ensure_classification,
|
|
273
|
-
pr_checkout,
|
|
274
253
|
pr_comment,
|
|
275
254
|
pr_merge,
|
|
276
255
|
pr_update_body,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { epic_close } from '#scripts/epic/epic-close'
|
|
2
2
|
import { git_followup_cleanup } from './git-followup-cleanup'
|
|
3
|
-
import { git_followup_flush } from './git-followup-flush'
|
|
4
3
|
import { git_followup_issue_close } from './git-followup-issue-close'
|
|
5
4
|
import { git_followup_label } from './git-followup-label'
|
|
6
5
|
import { git_followup_stages, type StageLog } from './git-followup-stages'
|
|
@@ -137,7 +136,7 @@ async function epic_close_step(input: WrapupInput): Promise<void> {
|
|
|
137
136
|
label: 'The epic auto-close',
|
|
138
137
|
recovery: undefined,
|
|
139
138
|
run: async () => {
|
|
140
|
-
await
|
|
139
|
+
await epic_close.close_completed_epics({
|
|
141
140
|
issue_number: input.issue_number,
|
|
142
141
|
is_merged: input.should_merge,
|
|
143
142
|
})
|
|
@@ -234,9 +233,6 @@ async function run_wrapup(input: WrapupInput, log: StageLog): Promise<void> {
|
|
|
234
233
|
}
|
|
235
234
|
|
|
236
235
|
await run_tail_steps(input)
|
|
237
|
-
// After the merge tail and before the hold is released (`git-followup-finish.ts`): the ledger
|
|
238
|
-
// flush switches branches, so it must land while this run still holds the tree (kit#1810).
|
|
239
|
-
await git_followup_flush.flush_ledger_step(input.should_merge)
|
|
240
236
|
|
|
241
237
|
lap(log, STAGE.completion_and_epic_close)
|
|
242
238
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
1
2
|
import { live_evidence } from '#scripts/review/live-evidence'
|
|
2
3
|
import { changed_paths } from './changed-paths'
|
|
3
4
|
import { git_branch } from './git-branch'
|
|
@@ -28,10 +29,6 @@ interface Resolution {
|
|
|
28
29
|
problems: Array<string>
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
function error_message(error: unknown): string {
|
|
32
|
-
return error instanceof Error ? error.message : String(error)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
32
|
function resolve_from_branch(current_branch: string): Resolution {
|
|
36
33
|
const number = git_issue.branch_number(current_branch)
|
|
37
34
|
|
|
@@ -71,7 +68,7 @@ function resolve_from_title(
|
|
|
71
68
|
try {
|
|
72
69
|
return resolve_parsed(git_issue.parse(cli_input), current_branch, default_branch)
|
|
73
70
|
} catch (error) {
|
|
74
|
-
return { target: undefined, problems: [
|
|
71
|
+
return { target: undefined, problems: [error_text.message_of(error)] }
|
|
75
72
|
}
|
|
76
73
|
}
|
|
77
74
|
|
|
@@ -89,7 +86,7 @@ async function classification_problems(target: Target): Promise<Array<string>> {
|
|
|
89
86
|
|
|
90
87
|
return []
|
|
91
88
|
} catch (error) {
|
|
92
|
-
return [
|
|
89
|
+
return [error_text.message_of(error)]
|
|
93
90
|
}
|
|
94
91
|
}
|
|
95
92
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
LEGACY_OBSERVATION_LEDGER_PATHS,
|
|
2
3
|
observation_ledger,
|
|
3
|
-
|
|
4
|
+
OBSERVATION_LEDGER_DIRECTORY,
|
|
4
5
|
} from '#scripts/observations/observation-ledger'
|
|
5
6
|
import { observation_ledger_prepare } from '#scripts/observations/observation-ledger-prepare'
|
|
6
7
|
import { git_command } from './git-command'
|
|
@@ -77,17 +78,28 @@ async function stage_untracked_files(files: ReadonlyArray<string>): Promise<void
|
|
|
77
78
|
// **A line that breaks the grammar keeps the old exclusion** (joshuafolkken/kit#2123): the commit
|
|
78
79
|
// goes ahead without the ledger, and `pnpm josh observations:flush` — which refuses the same line and
|
|
79
80
|
// names it — is where it is repaired. The migration is run first, as the flush runs it, so a line
|
|
80
|
-
// still on
|
|
81
|
-
//
|
|
81
|
+
// still on an old path (joshuafolkken/kit#2724) is committed in the directory. **A lane carries its
|
|
82
|
+
// own lines** (joshuafolkken/kit#2919): its writers append to its own tree's issue file, so this is
|
|
83
|
+
// the step that takes them to the default branch.
|
|
84
|
+
const LEDGER_PATHSPECS: ReadonlyArray<string> = [
|
|
85
|
+
OBSERVATION_LEDGER_DIRECTORY,
|
|
86
|
+
...LEGACY_OBSERVATION_LEDGER_PATHS,
|
|
87
|
+
]
|
|
88
|
+
|
|
82
89
|
async function ledger_exclusions(): Promise<ReadonlyArray<string>> {
|
|
83
90
|
const broken = await observation_ledger_prepare.prepare(await git_command.repository_root())
|
|
84
91
|
|
|
85
|
-
return broken.length > 0 ?
|
|
92
|
+
return broken.length > 0 ? LEDGER_PATHSPECS : []
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// An excluded pathspec covers what sits under it, as git reads it — the directory's files included.
|
|
96
|
+
function is_excluded(file_path: string, excluded: ReadonlyArray<string>): boolean {
|
|
97
|
+
return excluded.some((spec) => file_path === spec || file_path.startsWith(`${spec}/`))
|
|
86
98
|
}
|
|
87
99
|
|
|
88
100
|
// A migration claim is a copy mid-move, never the ledger itself, so it is never staged.
|
|
89
101
|
function is_stageable(file_path: string, excluded: ReadonlyArray<string>): boolean {
|
|
90
|
-
return !observation_ledger.is_migration_claim(file_path) && !
|
|
102
|
+
return !observation_ledger.is_migration_claim(file_path) && !is_excluded(file_path, excluded)
|
|
91
103
|
}
|
|
92
104
|
|
|
93
105
|
// **Said out loud, because the alternative is an unexplained failure.** With the ledger the only
|
|
@@ -95,15 +107,15 @@ function is_stageable(file_path: string, excluded: ReadonlyArray<string>): boole
|
|
|
95
107
|
// step dies on git's empty index with `Failed to commit changes` — a message naming nothing that
|
|
96
108
|
// caused it. This line is what turns that into a diagnosis.
|
|
97
109
|
//
|
|
98
|
-
// **The paths are parsed rather than matched as substrings**: `docs/maintainers/observations.md
|
|
99
|
-
// ledger's path, and a hint naming a file the exclusion never touched is a hint that
|
|
100
|
-
// reader to ignore it.
|
|
110
|
+
// **The paths are parsed rather than matched as substrings**: `docs/maintainers/observations-old/x.md`
|
|
111
|
+
// contains the ledger's path, and a hint naming a file the exclusion never touched is a hint that
|
|
112
|
+
// teaches the reader to ignore it.
|
|
101
113
|
function report_excluded_paths(status_output: string, excluded: ReadonlyArray<string>): void {
|
|
102
114
|
const left_out = status_output
|
|
103
115
|
.split('\n')
|
|
104
116
|
.filter((line) => line.trim().length > 0)
|
|
105
117
|
.map((line) => observation_ledger.status_path(line))
|
|
106
|
-
.filter((file_path) =>
|
|
118
|
+
.filter((file_path) => is_excluded(file_path, excluded))
|
|
107
119
|
|
|
108
120
|
for (const file_path of left_out) {
|
|
109
121
|
console.info(
|
|
@@ -1,16 +1,16 @@
|
|
|
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 {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
3
|
+
import { cli_body } from '#scripts/josh/cli-body'
|
|
4
|
+
import { test_red_commit } from '#scripts/test/test-red-commit'
|
|
5
|
+
import { git_branch } from './git-branch'
|
|
6
|
+
import { git_commit } from './git-commit'
|
|
7
|
+
import { git_error } from './git-error'
|
|
8
|
+
import { git_issue, type IssueInfo } from './git-issue'
|
|
9
|
+
import { git_pr } from './git-pr'
|
|
10
|
+
import { git_preflight } from './git-preflight'
|
|
11
|
+
import { git_prompt, type WorkflowConfirmations } from './git-prompt'
|
|
12
|
+
import { git_push } from './git-push'
|
|
13
|
+
import { git_staging } from './git-staging'
|
|
14
14
|
|
|
15
15
|
const SKIP_MESSAGES = {
|
|
16
16
|
commit: '💡 Commit skipped.',
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// notification carrying only a pull-request URL until joshuafolkken/kit#994 added the second form
|
|
7
7
|
// here rather than beside it.
|
|
8
8
|
//
|
|
9
|
-
// Two other modules read a URL for something narrower and keep their own patterns. `
|
|
9
|
+
// Two other modules read a URL for something narrower and keep their own patterns. `epic-parse`
|
|
10
10
|
// scans an epic body for task-list rows, so its regex is anchored to the row marker and runs
|
|
11
11
|
// globally over a document; `propagate-steps` takes the trailing number of whatever URL
|
|
12
12
|
// `gh issue create` just printed, where the repository is already known. This one is for a caller
|
|
@@ -282,7 +282,7 @@ function label_name_of(labels: ReadonlyArray<string>, wanted: string): string |
|
|
|
282
282
|
|
|
283
283
|
// The same comparison for a caller holding label *names* rather than listing rows. `EpicChild.labels`
|
|
284
284
|
// is an array of strings (`scripts/epic/epic-graph.ts`), which is the one shape `has_any_label` cannot
|
|
285
|
-
// take — so `epic-classify.ts` and `
|
|
285
|
+
// take — so `epic-classify.ts` and `epic-validate.ts` each grew a raw case-sensitive
|
|
286
286
|
// `Array.includes` instead, and an `Epic`-cased label walked past both. Kept here beside the rule it
|
|
287
287
|
// implements rather than at either call site, for the reason the comment above gives.
|
|
288
288
|
//
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// An issue named from outside any one graph — a task-list row, a citation in prose, a blocker
|
|
2
|
+
// relation, a child that could not be read. Repository **and** number, because a number alone cannot
|
|
3
|
+
// identify one: issue numbers are unique per repository, so the same `#40` names two different issues
|
|
4
|
+
// depending on who wrote it (joshuafolkken/kit#1014).
|
|
5
|
+
//
|
|
6
|
+
// It lives in the git layer because the `blocked-by` relations are read here and carry a repository
|
|
7
|
+
// of their own (joshuafolkken/kit#1126); the epic domain builds on this layer, never the other way
|
|
8
|
+
// round (joshuafolkken/kit#2904). `epic-reference` re-exports it, so every epic importer is unchanged.
|
|
9
|
+
//
|
|
10
|
+
// `state` is present only on a reference read from a `blocked-by` relation whose response carried it
|
|
11
|
+
// (joshuafolkken/kit#1943). The classifier needs it for a blocker no named graph tracks, where there is
|
|
12
|
+
// no child record to read the state from; every other reference leaves it out.
|
|
13
|
+
interface IssueReference {
|
|
14
|
+
repo: string
|
|
15
|
+
number: number
|
|
16
|
+
state?: 'OPEN' | 'CLOSED'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type { IssueReference }
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
3
|
import { extract_issue_number } from '#scripts/hooks/check-commit-message'
|
|
4
4
|
import { composite_arguments, USAGE_ERROR_EXIT_CODE } from '#scripts/josh/josh-composite-arguments'
|
|
5
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
5
6
|
import { git_command } from './git-command'
|
|
6
7
|
import { main_merge_guard } from './main-merge-guard'
|
|
7
8
|
|
|
@@ -103,7 +104,7 @@ async function run(argv: ReadonlyArray<string>): Promise<number> {
|
|
|
103
104
|
try {
|
|
104
105
|
return await dispatch(argv)
|
|
105
106
|
} catch (error) {
|
|
106
|
-
console.error(
|
|
107
|
+
console.error(error_text.message_of(error))
|
|
107
108
|
|
|
108
109
|
return FAILURE_EXIT_CODE
|
|
109
110
|
}
|