@joshuafolkken/kit 1.1387.0 → 1.1453.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/skills/epic-commands/SKILL.md +1 -1
- package/.claude/skills/epic-commands/epic-bundle.md +5 -0
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +5 -3
- package/.claude/skills/workflow-commands/backlogrun-park.md +2 -0
- package/.claude/skills/workflow-commands/backlogrun-steps.md +13 -10
- package/.claude/skills/workflow-commands/backlogrun.md +3 -2
- package/.claude/skills/workflow-commands/chain-rule.md +4 -2
- package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
- package/.claude/skills/workflow-commands/halfrun.md +1 -1
- package/.claude/skills/workflow-commands/issue-comments.md +6 -0
- package/.claude/skills/workflow-commands/kickoff.md +12 -13
- package/.claude/skills/workflow-commands/observation-filing.md +1 -1
- package/.claude/skills/workflow-commands/observation-ledger.md +3 -2
- package/.claude/skills/workflow-commands/pre-gate-cut.md +5 -3
- package/.claude/skills/workflow-commands/prerequisite.md +6 -0
- package/.claude/skills/workflow-commands/retrospective.md +5 -5
- package/.claude/skills/workflow-commands/split-assessment.md +2 -2
- package/README.md +27 -4
- package/dist/hooks/audit-provision.js +2 -2
- package/dist/hooks/batch-guard.js +7 -7
- package/dist/hooks/{chunk-2YSW3CXH.js → chunk-3C4XRYG6.js} +35 -11
- package/dist/hooks/{chunk-GJ7JPIHN.js → chunk-5C7L6FRC.js} +2704 -190
- package/dist/hooks/{chunk-FZ6FUSBM.js → chunk-723HKZO5.js} +1 -1
- package/dist/hooks/{chunk-STDSEPWU.js → chunk-D3NJXN5M.js} +4 -4
- package/dist/hooks/{chunk-NEC4AT4Z.js → chunk-DGT54IH5.js} +4 -0
- package/dist/hooks/{chunk-LMWBRJIP.js → chunk-EF5GQ6WS.js} +3 -3
- package/dist/hooks/{chunk-JBBMAPC4.js → chunk-IX6TTTI6.js} +4 -0
- package/dist/hooks/{chunk-IYVOLN6A.js → chunk-LBFYEOXJ.js} +3 -3
- package/dist/hooks/{chunk-SL4GQ2XP.js → chunk-MBJYQPEK.js} +106 -23
- package/dist/hooks/{chunk-WNFJLBXI.js → chunk-NO4AALP5.js} +3 -3
- package/dist/hooks/{chunk-J3KSS7II.js → chunk-OLMO72ON.js} +1 -1
- package/dist/hooks/{chunk-DCY2GWHC.js → chunk-SHYUQOIS.js} +5 -5
- package/dist/hooks/{chunk-EAQEGME7.js → chunk-UT7YC3N2.js} +1 -1
- package/dist/hooks/{chunk-EBSHN25W.js → chunk-W5ZKGTSY.js} +4 -4
- package/dist/hooks/{chunk-GNDJ7NJ7.js → chunk-Y3GKIKGW.js} +209 -56
- package/dist/hooks/{chunk-U32VSRNG.js → chunk-Z4XI7CZS.js} +89 -34
- package/dist/hooks/codex-hook-adapter.js +15 -15
- package/dist/hooks/duplicate-read-guard.js +8 -8
- package/dist/hooks/format-edited.js +5 -5
- package/dist/hooks/inputs.json +261 -201
- package/dist/hooks/investigation-guard.js +8 -8
- package/dist/hooks/pretool-guard.js +14 -14
- package/dist/hooks/session-lang.js +69 -16
- package/dist/hooks/stop-guard.js +13 -38
- package/dist/josh.js +95 -19
- package/package.json +4 -3
- package/prompts/collaboration-workflow/issue-template.md +10 -8
- package/prompts/review.md +30 -4
- package/scripts/agent/claude-agent-argv.ts +13 -1
- package/scripts/auth/auth-setup-cli.ts +101 -0
- package/scripts/auth/claude-token-output.ts +78 -0
- package/scripts/auth/claude-token.ts +68 -0
- package/scripts/auto-ok/auto-ok-judge-cli.ts +125 -0
- package/scripts/auto-ok/auto-ok-judge-read.ts +95 -0
- package/scripts/auto-ok/auto-ok-judge-write.ts +109 -0
- package/scripts/auto-ok/auto-ok-judge.ts +67 -0
- package/scripts/backlog/backlog-drive-cli.ts +14 -0
- package/scripts/backlog/backlog-drive-epic.ts +24 -8
- package/scripts/backlog/backlog-drive-launch.ts +3 -0
- package/scripts/backlog/backlog-drive-named-offer.ts +7 -2
- package/scripts/backlog/backlog-drive-named.ts +4 -2
- package/scripts/backlog/backlog-drive-unread.ts +56 -0
- package/scripts/backlog/backlog-drive.ts +38 -11
- package/scripts/backlog/backlog-overlap.ts +22 -5
- package/scripts/backlog/backlog-ready.ts +1 -3
- package/scripts/backlog/backlogrun-args.ts +13 -2
- package/scripts/backlog/backlogrun-cli.ts +91 -19
- package/scripts/backlog/backlogrun-launch.ts +24 -0
- package/scripts/backlog/backlogrun-replace.ts +166 -0
- package/scripts/backlog/backlogrun-stop.ts +178 -0
- package/scripts/behavior/behavior-cli.ts +6 -1
- package/scripts/doctor/doctor-io.ts +60 -9
- package/scripts/doctor/doctor.ts +20 -1
- package/scripts/document/document-byte-budget.ts +3 -3
- package/scripts/epic/epic-bundle-cli.ts +8 -8
- package/scripts/epic/epic-bundle-referenced.ts +2 -5
- package/scripts/epic/epic-bundle.ts +39 -4
- package/scripts/exports/unused-members-cli.ts +2 -1
- package/scripts/followup/followup-checks-event.ts +82 -0
- package/scripts/followup/git-followup-finish.ts +16 -2
- package/scripts/followup/git-followup-workflow.ts +5 -2
- package/scripts/followup/git-pr-followup-checks.ts +18 -4
- package/scripts/followup/git-pr-followup-wrapup.ts +5 -0
- package/scripts/followup/git-pr-followup.ts +4 -1
- package/scripts/fresh-machine/fresh-machine-cli.ts +204 -0
- package/scripts/fresh-machine/fresh-machine-docker.ts +65 -0
- package/scripts/fresh-machine/fresh-machine-image.ts +62 -0
- package/scripts/fresh-machine/fresh-machine-log.ts +61 -0
- package/scripts/fresh-machine/fresh-machine-patterns.ts +175 -0
- package/scripts/fresh-machine/fresh-machine-run.ts +215 -0
- package/scripts/fresh-machine/fresh-machine-script.ts +45 -0
- package/scripts/fresh-machine/fresh-machine-spec.ts +57 -0
- package/scripts/fresh-machine/specs/kit-test-backlogrun-python.json +34 -0
- package/scripts/fresh-machine/specs/kit-test-backlogrun.json +31 -0
- package/scripts/gate/gate-report.ts +70 -15
- package/scripts/gate/project-checks.ts +18 -4
- package/scripts/gate/scoped-green.ts +2 -1
- package/scripts/gate/verification-gate.ts +4 -3
- package/scripts/gh/gh-author-association.ts +14 -0
- package/scripts/gh/git-gh-conditional.ts +66 -0
- package/scripts/gh/git-gh-exec.ts +1 -1
- package/scripts/gh/git-gh-issue-read.ts +6 -3
- package/scripts/gh/git-gh-issue-rest.ts +1 -0
- package/scripts/gh/git-gh-issue-write.ts +5 -1
- package/scripts/gh/git-gh-issue.ts +13 -1
- package/scripts/gh/git-pr-checks-eval.ts +13 -0
- package/scripts/gh/git-pr-checks-parse.ts +8 -1
- package/scripts/gh/git-pr-checks-watch.ts +11 -4
- package/scripts/gh/git-pr-checks.ts +36 -2
- package/scripts/git/git-command.ts +16 -4
- package/scripts/git/git-schemas.ts +1 -0
- package/scripts/git/git-spawn-sync.ts +1 -1
- package/scripts/hooks/pretool-guard.ts +3 -1
- package/scripts/init/hook-command-rewrite.ts +31 -0
- package/scripts/init/init-actions.ts +7 -4
- package/scripts/init/init-ai-copy.ts +34 -11
- package/scripts/init/init-basic.ts +9 -4
- package/scripts/init/init-bootstrap.ts +6 -2
- package/scripts/init/init-dry-run-report.ts +150 -0
- package/scripts/init/init-dry-run.ts +189 -0
- package/scripts/init/init-install.ts +8 -1
- package/scripts/init/init-logic-toolchain.ts +76 -0
- package/scripts/init/init-logic.ts +2 -0
- package/scripts/init/init.ts +62 -42
- package/scripts/init/kit-setup-state.ts +11 -17
- package/scripts/init/own-formatter.ts +21 -0
- package/scripts/init/project-profile.ts +60 -12
- package/scripts/init/start-dry-run.ts +70 -0
- package/scripts/init/start-exec.ts +27 -1
- package/scripts/init/start-plan.ts +23 -3
- package/scripts/init/start-setup-pr.ts +13 -24
- package/scripts/init/start-steps.ts +62 -1
- package/scripts/init/start.ts +38 -17
- package/scripts/init/workflow-mark.ts +35 -0
- package/scripts/init/working-tree.ts +116 -0
- package/scripts/issue/issue-ack-cli.ts +84 -0
- package/scripts/issue/issue-auto-ok.ts +68 -99
- package/scripts/issue/issue-file-cli.ts +17 -17
- package/scripts/issue/issue-file.ts +1 -6
- package/scripts/issue/issue-labels.ts +46 -10
- package/scripts/issue/issue-release-cli.ts +69 -23
- package/scripts/issue/issue-release.ts +3 -1
- package/scripts/issue/issue-state-cli.ts +14 -4
- package/scripts/issue/issue-watch-diff.ts +152 -0
- package/scripts/issue/issue-watch-gate.ts +81 -0
- package/scripts/issue/issue-watch-hook.ts +54 -0
- package/scripts/issue/issue-watch.ts +240 -0
- package/scripts/issue/measure-rerun-cli.ts +3 -4
- package/scripts/josh/josh-command-map.ts +4 -0
- package/scripts/josh/josh-commands-ai.ts +11 -0
- package/scripts/josh/josh-commands-backlog.ts +2 -2
- package/scripts/josh/josh-commands-development.ts +1 -1
- package/scripts/josh/josh-commands-issue.ts +10 -3
- package/scripts/josh/josh-commands-lane.ts +11 -2
- package/scripts/josh/josh-commands-project.ts +26 -2
- package/scripts/josh/josh-verdict.ts +33 -4
- package/scripts/josh/process-identity.ts +32 -6
- package/scripts/lane/claude-lane-broker.ts +142 -0
- package/scripts/lane/lane-await.ts +6 -2
- package/scripts/lane/lane-broker-server.ts +69 -0
- package/scripts/lane/lane-cache.ts +37 -7
- package/scripts/lane/lane-capacity.ts +8 -6
- package/scripts/lane/lane-child-invocation.ts +1 -0
- package/scripts/lane/lane-cli.ts +15 -1
- package/scripts/lane/lane-close.ts +15 -6
- package/scripts/lane/lane-dispatch.ts +36 -10
- package/scripts/lane/lane-environment.ts +1 -1
- package/scripts/lane/lane-launch-cli.ts +13 -4
- package/scripts/lane/lane-ledger.ts +21 -3
- package/scripts/lane/lane-measure-cli.ts +10 -2
- package/scripts/lane/lane-open.ts +9 -1
- package/scripts/lane/lane-preserve.ts +49 -0
- package/scripts/lane/lane-reap.ts +20 -5
- package/scripts/lane/lane-registry.ts +6 -0
- package/scripts/lane/lane-restart-cli.ts +51 -0
- package/scripts/lane/lane-restart.ts +175 -0
- package/scripts/lane/lane-session-broker.ts +363 -0
- package/scripts/lane/lane-spent.ts +195 -0
- package/scripts/lane/lane-stage-stats.ts +27 -3
- package/scripts/lane/lane-stop-stats.ts +96 -0
- package/scripts/lane/openai-lane-supervisor.ts +2 -2
- package/scripts/lane/process-tools.ts +31 -0
- package/scripts/lib/buffered-process.ts +36 -2
- package/scripts/lib/pnpm-reach.ts +27 -0
- package/scripts/lib/status-icons.ts +5 -1
- package/scripts/lint/lint-parallel.ts +19 -2
- package/scripts/observations/observation-ledger-home.ts +1 -0
- package/scripts/observations/observation-ledger-migrate.ts +3 -15
- package/scripts/release/release-issue-close.ts +133 -0
- package/scripts/release/release-publish.ts +12 -1
- package/scripts/repo/repository-labels.ts +11 -1
- package/scripts/review/comment-ranges.ts +199 -0
- package/scripts/review/review-attest-cli.ts +21 -6
- package/scripts/review/review-brief-cli.ts +24 -7
- package/scripts/review/review-code-tree.ts +119 -0
- package/scripts/review/review-comment-round.ts +114 -0
- package/scripts/review/review-record-cli.ts +39 -17
- package/scripts/review/review-round2-cli.ts +16 -5
- package/scripts/review/review-round2.ts +30 -10
- package/scripts/review/review-stamps.ts +22 -1
- package/scripts/rules/implementation-cut.ts +3 -3
- package/scripts/rules/pre-gate-cut.ts +1 -1
- package/scripts/run/board/run-board-checks.ts +42 -0
- package/scripts/run/board/run-board-cli.ts +2 -0
- package/scripts/run/board/run-board-every.ts +3 -1
- package/scripts/run/board/run-board-fit.ts +16 -5
- package/scripts/run/board/run-board-fresh.ts +64 -0
- package/scripts/run/board/run-board-header.ts +129 -53
- package/scripts/run/board/run-board-labels.ts +47 -4
- package/scripts/run/board/run-board-lane-event.ts +66 -0
- package/scripts/run/board/run-board-layout.ts +57 -9
- package/scripts/run/board/run-board-now.ts +234 -0
- package/scripts/run/board/run-board-operation-read.ts +75 -0
- package/scripts/run/board/run-board-operation.ts +150 -0
- package/scripts/run/board/run-board-phase.ts +26 -1
- package/scripts/run/board/run-board-plan.ts +4 -6
- package/scripts/run/board/run-board-read.ts +49 -3
- package/scripts/run/board/run-board-reason.ts +124 -0
- package/scripts/run/board/run-board-render-checks.ts +55 -0
- package/scripts/run/board/run-board-render-legend.ts +12 -3
- package/scripts/run/board/run-board-render-slot.ts +2 -2
- package/scripts/run/board/run-board-render-tail.ts +128 -0
- package/scripts/run/board/run-board-render-thanks.ts +50 -0
- package/scripts/run/board/run-board-render-title.ts +58 -0
- package/scripts/run/board/run-board-render.ts +96 -80
- package/scripts/run/board/run-board-row-yield.ts +32 -0
- package/scripts/run/board/run-board-setup.ts +61 -13
- package/scripts/run/board/run-board-state.ts +10 -0
- package/scripts/run/board/run-board-status.ts +66 -7
- package/scripts/run/board/run-board-thanks-csv.ts +71 -0
- package/scripts/run/board/run-board-thanks-read.ts +112 -0
- package/scripts/run/board/run-board-thanks.ts +53 -0
- package/scripts/run/board/run-board-tick.ts +50 -21
- package/scripts/run/board/run-board-usual.ts +44 -0
- package/scripts/run/carry/run-carry-args.ts +9 -1
- package/scripts/run/carry/run-carry-cli.ts +2 -45
- package/scripts/run/carry/run-carry-end.ts +73 -0
- package/scripts/run/carry/run-carry.ts +8 -1
- package/scripts/run/entry/run-entry-cli.ts +5 -0
- package/scripts/run/event/run-event-checks.ts +54 -0
- package/scripts/run/event/run-event-render.ts +2 -0
- package/scripts/run/event/run-event-scope.ts +1 -0
- package/scripts/run/event/run-event-setup.ts +55 -11
- package/scripts/run/event/run-event-stream.ts +27 -11
- package/scripts/run/merge/run-merge-cli.ts +7 -4
- package/scripts/run/merge/run-merge-ship.ts +61 -0
- package/scripts/run/merge/run-merge-steps.ts +7 -14
- package/scripts/run/merge/run-merge-token.ts +16 -2
- package/scripts/run/run-headless.ts +1 -0
- package/scripts/run/run-step.ts +2 -0
- package/scripts/run/ship/run-ship-cli.ts +22 -2
- package/scripts/run/ship/run-ship-return.ts +31 -13
- package/scripts/run/ship/run-ship-review-steps.ts +17 -4
- package/scripts/run/ship/run-ship-stage.ts +1 -0
- package/scripts/run/wake/run-wake-await.ts +130 -0
- package/scripts/run/wake/run-wake-cli.ts +60 -71
- package/scripts/run/wake/run-wake-describe.ts +34 -2
- package/scripts/run/wake/run-wake-driver.ts +11 -4
- package/scripts/run/wake/run-wake-session.ts +20 -3
- package/scripts/run/wake/run-wake-stop.ts +37 -0
- package/scripts/run/wake/run-wake-supervise.ts +44 -0
- package/scripts/sync/claude-settings-merge.ts +144 -0
- package/scripts/sync/sync-ai-files.ts +28 -3
- package/scripts/sync/sync.ts +15 -0
- package/scripts/test/project-test-script.ts +73 -0
- package/scripts/test/skip-marker.ts +14 -1
- package/scripts/test/test-related.ts +3 -2
- package/scripts/test/test-unit-guard.ts +78 -14
- package/scripts/time-runtime/time-reported-failure.ts +11 -3
- package/scripts/uninstall/distributed-files.ts +92 -0
- package/scripts/uninstall/uninstall-plan.ts +119 -0
- package/scripts/uninstall/uninstall-report.ts +61 -0
- package/scripts/uninstall/uninstall.ts +206 -0
- package/templates/AGENTS.basic.md +4 -0
- package/templates/GEMINI.basic.md +6 -0
- package/dist/hooks/chunk-6V5OPIUU.js +0 -13
- package/scripts/lane/openai-review-broker.ts +0 -262
- package/scripts/run/board/run-board-track.ts +0 -22
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { run_ship_stage } from '#scripts/run/ship/run-ship-stage'
|
|
2
|
+
import { lane_ledger, type LedgerEntry, type StopEntry } from './lane-ledger'
|
|
3
|
+
import { lane_stage_stats } from './lane-stage-stats'
|
|
4
|
+
import { lane_stats, type StatsWindow } from './lane-stats'
|
|
5
|
+
|
|
6
|
+
// How long a lane stood stopped: the ledger's `stop` entries of one period reduced to one row per stop
|
|
7
|
+
// reason — median, maximum, how many stops were resumed and how many never were — printed by
|
|
8
|
+
// `josh lane:stats` under the per-stage table, so the stop reason worth removing next is a number.
|
|
9
|
+
//
|
|
10
|
+
// **The wait is derived, not recorded**: from a stop to the start of the same issue's next ship stage,
|
|
11
|
+
// the way `implement` is. A stop that no ship stage followed — the lane parked, or left stopped — is
|
|
12
|
+
// counted under `not resumed` and kept out of the median and the maximum, whose time has no end.
|
|
13
|
+
//
|
|
14
|
+
// **A re-dispatch of the issue ends the search**: the new child's ship is a fresh lane's work, not the
|
|
15
|
+
// resumption of a stop that may be days old.
|
|
16
|
+
|
|
17
|
+
const COLUMNS = ['stop', 'median (min)', 'max (min)', 'runs', 'not resumed'] as const
|
|
18
|
+
const { STAGE } = run_ship_stage
|
|
19
|
+
const { is_dispatch, is_number, is_stage } = lane_stage_stats
|
|
20
|
+
const REASONS = [
|
|
21
|
+
STAGE.PREFLIGHT,
|
|
22
|
+
STAGE.REVIEW,
|
|
23
|
+
STAGE.SYNC,
|
|
24
|
+
run_ship_stage.CONFLICT,
|
|
25
|
+
STAGE.GATE,
|
|
26
|
+
STAGE.COMMIT,
|
|
27
|
+
STAGE.ROUND_TWO,
|
|
28
|
+
STAGE.FOLLOWUP,
|
|
29
|
+
STAGE.REPORT,
|
|
30
|
+
] as const
|
|
31
|
+
|
|
32
|
+
interface StopWait {
|
|
33
|
+
reason: string
|
|
34
|
+
wait_ms: number | undefined
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function is_stop(entry: LedgerEntry): entry is StopEntry {
|
|
38
|
+
return entry.kind === lane_ledger.KIND.STOP
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function redispatch_ms(stop: StopEntry, entries: ReadonlyArray<LedgerEntry>): number {
|
|
42
|
+
const from_ms = Date.parse(stop.at)
|
|
43
|
+
const later = entries
|
|
44
|
+
.filter((entry) => is_dispatch(entry))
|
|
45
|
+
.filter((entry) => entry.issue === stop.issue)
|
|
46
|
+
.map((entry) => Date.parse(entry.at))
|
|
47
|
+
.filter((at) => at > from_ms)
|
|
48
|
+
|
|
49
|
+
return later.length === 0 ? Infinity : Math.min(...later)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function wait_ms(stop: StopEntry, entries: ReadonlyArray<LedgerEntry>): number | undefined {
|
|
53
|
+
const from_ms = Date.parse(stop.at)
|
|
54
|
+
const until_ms = redispatch_ms(stop, entries)
|
|
55
|
+
const starts = entries
|
|
56
|
+
.filter((entry) => is_stage(entry))
|
|
57
|
+
.filter((stage) => stage.issue === stop.issue)
|
|
58
|
+
.map((stage) => lane_stage_stats.started_ms(stage))
|
|
59
|
+
.filter((start) => start >= from_ms && start < until_ms)
|
|
60
|
+
|
|
61
|
+
return starts.length === 0 ? undefined : Math.min(...starts) - from_ms
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function waits_of(entries: ReadonlyArray<LedgerEntry>): Array<StopWait> {
|
|
65
|
+
return entries
|
|
66
|
+
.filter(is_stop)
|
|
67
|
+
.map((stop) => ({ reason: stop.reason, wait_ms: wait_ms(stop, entries) }))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function reason_row(reason: string, waits: ReadonlyArray<StopWait>): string {
|
|
71
|
+
const of_reason = waits.filter((wait) => wait.reason === reason)
|
|
72
|
+
const resumed = of_reason.map((wait) => wait.wait_ms).filter((value) => is_number(value))
|
|
73
|
+
|
|
74
|
+
return lane_stats.to_row([
|
|
75
|
+
reason,
|
|
76
|
+
lane_stage_stats.cell(lane_stats.median(resumed)),
|
|
77
|
+
lane_stage_stats.cell(lane_stats.peak(resumed)),
|
|
78
|
+
String(resumed.length),
|
|
79
|
+
String(of_reason.length - resumed.length),
|
|
80
|
+
])
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// The period's table: `entries` is the whole ledger, filtered here to `window`.
|
|
84
|
+
function table(entries: ReadonlyArray<LedgerEntry>, window: StatsWindow): string {
|
|
85
|
+
const waits = waits_of(lane_stats.in_window(entries, window))
|
|
86
|
+
|
|
87
|
+
return [
|
|
88
|
+
lane_stats.to_row(COLUMNS),
|
|
89
|
+
lane_stats.to_row(COLUMNS.map(() => '---')),
|
|
90
|
+
...REASONS.map((reason) => reason_row(reason, waits)),
|
|
91
|
+
].join('\n')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const lane_stop_stats = { REASONS, table }
|
|
95
|
+
|
|
96
|
+
export { lane_stop_stats }
|
|
@@ -9,10 +9,10 @@ import { lane_child_invocation } from './lane-child-invocation'
|
|
|
9
9
|
import { lane_child_marker } from './lane-child-marker'
|
|
10
10
|
import { lane_dispatch_log } from './lane-dispatch-log'
|
|
11
11
|
import type { LaneInfo } from './lane-registry'
|
|
12
|
+
import { lane_session_broker } from './lane-session-broker'
|
|
12
13
|
import { openai_lane_ship } from './openai-lane-ship'
|
|
13
14
|
import { openai_lane_supervisor_decision } from './openai-lane-supervisor-decision'
|
|
14
15
|
import { openai_lane_supervisor_owner, type SupervisorOwner } from './openai-lane-supervisor-owner'
|
|
15
|
-
import { openai_review_broker } from './openai-review-broker'
|
|
16
16
|
|
|
17
17
|
const { claim, read_state, release, update } = openai_lane_supervisor_owner
|
|
18
18
|
const CHILD_POLL_MS = 250
|
|
@@ -178,7 +178,7 @@ async function supervise(lane: LaneInfo, profile: AgentProfile, nonce: string):
|
|
|
178
178
|
try {
|
|
179
179
|
if (!(await openai_lane_supervisor_decision.is_approved(lane.directory, nonce))) return 0
|
|
180
180
|
|
|
181
|
-
return await
|
|
181
|
+
return await lane_session_broker.with_server(
|
|
182
182
|
lane,
|
|
183
183
|
async () => await run_claimed(lane, profile, owner),
|
|
184
184
|
)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
// One definition of "the process tools a lane reads are not installed".
|
|
4
|
+
//
|
|
5
|
+
// **`ps` and `pgrep` are a prerequisite, not an optional probe.** The owner check walks the caller's
|
|
6
|
+
// ancestry with `ps`, and lane liveness and reaping match children with `pgrep`. A machine without
|
|
7
|
+
// them (a slim Debian image ships no `procps`) used to read every probe as "nothing there": the
|
|
8
|
+
// ancestry stopped at the caller, `run:step` answered `unknown`, and the parent stopped before its
|
|
9
|
+
// first lane with no word of why. A missing tool is refused by name instead.
|
|
10
|
+
|
|
11
|
+
// What Node reports when the executable to spawn does not exist.
|
|
12
|
+
const NOT_FOUND_CODE = 'ENOENT'
|
|
13
|
+
const PACKAGE_HINT =
|
|
14
|
+
'install the package that provides it (procps on Debian/Ubuntu: `apt-get install procps`)'
|
|
15
|
+
|
|
16
|
+
function is_not_found(error: unknown): boolean {
|
|
17
|
+
return error instanceof Error && 'code' in error && error.code === NOT_FOUND_CODE
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function missing_notice(command: string): string {
|
|
21
|
+
return `\`${path.basename(command)}\` is not installed — kit's lane commands read processes with ps and pgrep; ${PACKAGE_HINT}`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Throw a named refusal when `error` says `command` could not be spawned because it does not exist. */
|
|
25
|
+
function throw_if_missing(command: string, error: unknown): void {
|
|
26
|
+
if (is_not_found(error)) throw new Error(missing_notice(command), { cause: error })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const process_tools = { missing_notice, throw_if_missing }
|
|
30
|
+
|
|
31
|
+
export { process_tools }
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
1
2
|
import { execa } from 'execa'
|
|
3
|
+
import { pnpm_reach } from './pnpm-reach'
|
|
2
4
|
import { SUITE_TIMEOUT_MS } from './timeouts'
|
|
3
5
|
|
|
4
6
|
// One definition of "run a check, keep its output, never throw".
|
|
@@ -9,7 +11,7 @@ import { SUITE_TIMEOUT_MS } from './timeouts'
|
|
|
9
11
|
// (so one failing check does not abort its siblings), and a bound on how long a child may hang.
|
|
10
12
|
// They had the same code twice, which is how a fix to one of them missed the other.
|
|
11
13
|
|
|
12
|
-
const PNPM =
|
|
14
|
+
const { PNPM } = pnpm_reach
|
|
13
15
|
const FORCE_COLOR = '1'
|
|
14
16
|
const FAIL_EXIT_CODE = 1
|
|
15
17
|
|
|
@@ -35,6 +37,38 @@ interface BufferedProcessOptions {
|
|
|
35
37
|
timeout_ms?: number
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
// What execa reports about a child that produced no exit code. Declared here because execa's own
|
|
41
|
+
// result type does not carry `code` or `shortMessage` on the non-rejecting path, though both are
|
|
42
|
+
// there at runtime whenever the child did not exit by itself.
|
|
43
|
+
interface UnfinishedChild {
|
|
44
|
+
exitCode?: number | undefined
|
|
45
|
+
code?: string | undefined
|
|
46
|
+
shortMessage?: string | undefined
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// A missing working directory is reported with the same code as a missing executable, so the code
|
|
50
|
+
// alone would blame pnpm for a lane directory that was removed.
|
|
51
|
+
function is_pnpm_missing(result: UnfinishedChild, cwd: string | undefined): boolean {
|
|
52
|
+
return pnpm_reach.is_not_found(result) && existsSync(cwd ?? process.cwd())
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Why a child has no exit code: it could not be started, it ran past its bound, or a signal ended
|
|
56
|
+
// it. The child cannot say so itself — in the first case there is no child — and without this a
|
|
57
|
+
// check that never ran reads as a check that failed with nothing to report.
|
|
58
|
+
function unfinished_reason(result: UnfinishedChild, cwd: string | undefined): Array<string> {
|
|
59
|
+
if (result.exitCode !== undefined) return []
|
|
60
|
+
const reason = [result.shortMessage ?? '']
|
|
61
|
+
if (is_pnpm_missing(result, cwd)) reason.push(pnpm_reach.NOT_ON_PATH_NOTICE)
|
|
62
|
+
|
|
63
|
+
return reason
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// The reason follows whatever the child wrote before it stopped: a timed-out suite's last lines
|
|
67
|
+
// are the evidence, and the reason is what explains them.
|
|
68
|
+
function collect_output(all: string, reason: ReadonlyArray<string>): string {
|
|
69
|
+
return [all, ...reason].filter((part) => part !== '').join('\n')
|
|
70
|
+
}
|
|
71
|
+
|
|
38
72
|
async function run_buffered_process(
|
|
39
73
|
command_args: ReadonlyArray<string>,
|
|
40
74
|
options: BufferedProcessOptions = {},
|
|
@@ -58,7 +92,7 @@ async function run_buffered_process(
|
|
|
58
92
|
})
|
|
59
93
|
|
|
60
94
|
return {
|
|
61
|
-
output: result.all,
|
|
95
|
+
output: collect_output(result.all, unfinished_reason(result, options.cwd)),
|
|
62
96
|
exit_code: result.exitCode,
|
|
63
97
|
elapsed_ms: performance.now() - started_at,
|
|
64
98
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// One definition of "pnpm is not reachable from this process", for every caller that starts pnpm.
|
|
2
|
+
//
|
|
3
|
+
// `josh` runs its checks as `pnpm josh <check>` children, so a `pnpm` missing from the PATH this
|
|
4
|
+
// process sees fails every one of them before it starts. Nothing in the child's output can say so —
|
|
5
|
+
// there is no child — and the two places that can tell the user (the spawn itself and `josh doctor`)
|
|
6
|
+
// have to say the same thing.
|
|
7
|
+
|
|
8
|
+
const PNPM = 'pnpm'
|
|
9
|
+
// What Node reports when the executable to spawn is not on PATH.
|
|
10
|
+
const NOT_FOUND_CODE = 'ENOENT'
|
|
11
|
+
const PREREQUISITES_URL =
|
|
12
|
+
'https://github.com/joshuafolkken/kit/blob/main/docs/setup/prerequisites.md#2-install-pnpm-and-nodejs'
|
|
13
|
+
const NOT_ON_PATH_NOTICE = [
|
|
14
|
+
'pnpm was not found on the PATH this process sees, so the command could not start.',
|
|
15
|
+
'A shell opened by an editor, an agent or a service can have a different PATH from your terminal.',
|
|
16
|
+
`Install pnpm or add its directory to that PATH: ${PREREQUISITES_URL}`,
|
|
17
|
+
].join('\n')
|
|
18
|
+
|
|
19
|
+
// Narrowed to the one field it reads: execa's declared result type does not carry `code` on the
|
|
20
|
+
// non-rejecting path, though the value is there at runtime whenever the spawn itself failed.
|
|
21
|
+
function is_not_found(result: { code?: string | undefined }): boolean {
|
|
22
|
+
return result.code === NOT_FOUND_CODE
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const pnpm_reach = { PNPM, NOT_ON_PATH_NOTICE, is_not_found }
|
|
26
|
+
|
|
27
|
+
export { pnpm_reach }
|
|
@@ -15,10 +15,14 @@
|
|
|
15
15
|
// `✔ verification gate passed` line to tell a green gate from the third-party warning bodies it
|
|
16
16
|
// forwards, so the pass icon is under the same rule as the failure icon: declared
|
|
17
17
|
// once, imported by the printer and the reader alike.
|
|
18
|
+
//
|
|
19
|
+
// **The warning icon is read as well.** It opens the gate's `not verified` verdict — a gate that ran no
|
|
20
|
+
// check — which `josh-verdict.ts` reads back, so it is declared here for the reason the other two are.
|
|
18
21
|
|
|
19
22
|
const FAIL_ICON = '✗'
|
|
20
23
|
const PASS_ICON = '✔'
|
|
24
|
+
const WARN_ICON = '⚠'
|
|
21
25
|
|
|
22
|
-
const status_icons = { FAIL_ICON, PASS_ICON }
|
|
26
|
+
const status_icons = { FAIL_ICON, PASS_ICON, WARN_ICON }
|
|
23
27
|
|
|
24
28
|
export { status_icons }
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
FAIL_EXIT_CODE,
|
|
9
9
|
type BufferedProcessResult,
|
|
10
10
|
} from '#scripts/lib/buffered-process'
|
|
11
|
+
import { SKIP_MARKER } from '#scripts/test/skip-marker'
|
|
11
12
|
import { cross_file_recheck, type CrossFileRecheckOptions } from './cross-file-recheck'
|
|
12
13
|
|
|
13
14
|
const PRETTIER_ARGS = ['exec', 'prettier', '--check', '.'] as const
|
|
@@ -67,6 +68,22 @@ async function run_checked_eslint(
|
|
|
67
68
|
return await run_eslint(eslint)
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
function is_silent_pass(result: BufferedProcessResult): boolean {
|
|
72
|
+
return result.output === '' && !buffered_process.is_process_failed(result)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Only beside a skipped sibling (`project_checks.ran_notice`): a `lint` that skipped nothing keeps the
|
|
76
|
+
// output it always had.
|
|
77
|
+
function with_ran_notice(
|
|
78
|
+
check: string,
|
|
79
|
+
result: BufferedProcessResult,
|
|
80
|
+
sibling: BufferedProcessResult,
|
|
81
|
+
): BufferedProcessResult {
|
|
82
|
+
if (!sibling.output.includes(SKIP_MARKER) || !is_silent_pass(result)) return result
|
|
83
|
+
|
|
84
|
+
return { ...result, output: `${project_checks.ran_notice(check)}\n` }
|
|
85
|
+
}
|
|
86
|
+
|
|
70
87
|
function lint_exit_code(prettier: BufferedProcessResult, eslint: BufferedProcessResult): number {
|
|
71
88
|
return buffered_process.is_process_failed(prettier) || buffered_process.is_process_failed(eslint)
|
|
72
89
|
? FAIL_EXIT_CODE
|
|
@@ -94,8 +111,8 @@ async function run_lint_checks(
|
|
|
94
111
|
run_checked_eslint(directory, { args: eslint_args, recheck }),
|
|
95
112
|
])
|
|
96
113
|
|
|
97
|
-
write_output(prettier)
|
|
98
|
-
write_output(eslint)
|
|
114
|
+
write_output(with_ran_notice('prettier', prettier, eslint))
|
|
115
|
+
write_output(with_ran_notice('eslint', eslint, prettier))
|
|
99
116
|
|
|
100
117
|
return lint_exit_code(prettier, eslint)
|
|
101
118
|
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
rmSync,
|
|
9
9
|
} from 'node:fs'
|
|
10
10
|
import path from 'node:path'
|
|
11
|
+
import { process_identity } from '#scripts/josh/process-identity'
|
|
11
12
|
import { file_reader } from '#scripts/lib/read-file'
|
|
12
13
|
import {
|
|
13
14
|
LEDGER_FILE_EXTENSION,
|
|
@@ -38,8 +39,6 @@ import { observation_ledger_line } from './observation-ledger-line'
|
|
|
38
39
|
// migration with no await point cannot be interleaved with another write in the same process.
|
|
39
40
|
|
|
40
41
|
const NEWLINE = '\n'
|
|
41
|
-
const NO_SIGNAL = 0
|
|
42
|
-
const NO_SUCH_PROCESS = 'ESRCH'
|
|
43
42
|
|
|
44
43
|
function claim_path(legacy: string): string {
|
|
45
44
|
return `${legacy}.${String(process.pid)}${MIGRATION_CLAIM_SUFFIX}`
|
|
@@ -55,18 +54,6 @@ function claim(from: string, to: string): boolean {
|
|
|
55
54
|
}
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
// Only `ESRCH` means the process is gone; `EPERM` means it runs under another user, and its claim is
|
|
59
|
-
// still its own.
|
|
60
|
-
function is_alive(pid: number): boolean {
|
|
61
|
-
try {
|
|
62
|
-
process.kill(pid, NO_SIGNAL)
|
|
63
|
-
|
|
64
|
-
return true
|
|
65
|
-
} catch (error) {
|
|
66
|
-
return (error as NodeJS.ErrnoException).code !== NO_SUCH_PROCESS
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
57
|
// A claim is named `<old ledger>.<pid>.migrating`, so the pid is what sits between the two.
|
|
71
58
|
function is_stale_claim(name: string, legacy_name: string): boolean {
|
|
72
59
|
const prefix = `${legacy_name}.`
|
|
@@ -75,7 +62,8 @@ function is_stale_claim(name: string, legacy_name: string): boolean {
|
|
|
75
62
|
|
|
76
63
|
const pid = Number(name.slice(prefix.length, -MIGRATION_CLAIM_SUFFIX.length))
|
|
77
64
|
|
|
78
|
-
|
|
65
|
+
// A process under another user still holds its claim (`process_identity.is_present_pid`).
|
|
66
|
+
return Number.isSafeInteger(pid) && !process_identity.is_present_pid(pid)
|
|
79
67
|
}
|
|
80
68
|
|
|
81
69
|
function stale_claims(legacy: string): ReadonlyArray<string> {
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { epic_issue } from '#scripts/epic/epic-issue'
|
|
2
|
+
import { epic_parse } from '#scripts/epic/epic-parse'
|
|
3
|
+
import type { IssueReference } from '#scripts/epic/epic-reference'
|
|
4
|
+
import { git_gh_command } from '#scripts/gh/git-gh-command'
|
|
5
|
+
import { epic_child_schema } from '#scripts/git/git-schemas'
|
|
6
|
+
import { parse_json_object_or_undefined } from '#scripts/git/parse-json-array'
|
|
7
|
+
import { issue_release_cli } from '#scripts/issue/issue-release-cli'
|
|
8
|
+
import { session_cite } from '#scripts/issue/session-cite'
|
|
9
|
+
|
|
10
|
+
// Closes the repository's open release Issue once `pnpm josh release` has published
|
|
11
|
+
// (joshuafolkken/kit#3825). Its body promises that, and an Issue waiting on the release reopens only
|
|
12
|
+
// once it closes; left open, it keeps naming published work as unreleased.
|
|
13
|
+
//
|
|
14
|
+
// **Nothing here throws or changes the release's exit code.** The version is already out by the time
|
|
15
|
+
// this runs, so a close that fails is reported for a person to finish rather than read as a failed
|
|
16
|
+
// release.
|
|
17
|
+
|
|
18
|
+
function published_comment(version: string): string {
|
|
19
|
+
return `Published in \`v${version}\` by \`pnpm josh release\`.`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Resolved through `epic_issue.blocker_references_of`, so a blocker in another repository keeps that
|
|
23
|
+
// repository instead of reading as the same number here.
|
|
24
|
+
function open_references_of(
|
|
25
|
+
json: string | undefined,
|
|
26
|
+
repo: string,
|
|
27
|
+
): Array<IssueReference> | undefined {
|
|
28
|
+
if (json === undefined) return undefined
|
|
29
|
+
const parsed = parse_json_object_or_undefined(json, epic_child_schema)
|
|
30
|
+
|
|
31
|
+
if (parsed === undefined) return undefined
|
|
32
|
+
|
|
33
|
+
return epic_issue
|
|
34
|
+
.blocker_references_of(parsed, repo)
|
|
35
|
+
.filter((reference) => !epic_parse.is_state_closed(reference.state))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// The blockers still open at publish have not merged, so this release did not carry them.
|
|
39
|
+
// `undefined` when the relations are unreadable: closing then could strand a blocker on a closed
|
|
40
|
+
// release Issue that no open one tracks.
|
|
41
|
+
async function open_blockers_of(
|
|
42
|
+
repo: string,
|
|
43
|
+
issue_number: number,
|
|
44
|
+
): Promise<Array<IssueReference> | undefined> {
|
|
45
|
+
const json = await git_gh_command.issue_get_state_and_relations(String(issue_number))
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
return open_references_of(json, repo)
|
|
49
|
+
} catch {
|
|
50
|
+
return undefined
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// A release Issue tracks only its own repository's changes, so a blocker elsewhere is named for a
|
|
55
|
+
// person to carry rather than linked under a number that means a different Issue here.
|
|
56
|
+
function warn_foreign(repo: string, foreign: ReadonlyArray<IssueReference>): void {
|
|
57
|
+
for (const reference of foreign) {
|
|
58
|
+
const cite = session_cite.issue(reference.number, undefined, reference.repo)
|
|
59
|
+
|
|
60
|
+
console.warn(`⚠ ${cite} is not in ${repo}, so it was not carried — carry it by hand`)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Each unmerged blocker joins the next release Issue — the first link files it, the rest find it
|
|
65
|
+
// open — so an open release Issue still names every change no release has published.
|
|
66
|
+
async function carry_forward(repo: string, blockers: ReadonlyArray<IssueReference>): Promise<void> {
|
|
67
|
+
const own = blockers.filter((blocker) => blocker.repo === repo)
|
|
68
|
+
|
|
69
|
+
warn_foreign(
|
|
70
|
+
repo,
|
|
71
|
+
blockers.filter((blocker) => blocker.repo !== repo),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
for (const blocker of own) {
|
|
75
|
+
// eslint-disable-next-line no-await-in-loop -- the first link files the Issue the rest join
|
|
76
|
+
await issue_release_cli.link(blocker.number, repo)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function close_and_carry(
|
|
81
|
+
repo: string,
|
|
82
|
+
issue_number: number,
|
|
83
|
+
version: string,
|
|
84
|
+
blockers: ReadonlyArray<IssueReference>,
|
|
85
|
+
): Promise<void> {
|
|
86
|
+
const cite = `release Issue ${session_cite.issue(issue_number, undefined, repo)}`
|
|
87
|
+
const is_closed = await git_gh_command.issue_close(
|
|
88
|
+
String(issue_number),
|
|
89
|
+
published_comment(version),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if (!is_closed) {
|
|
93
|
+
console.warn(`⚠ ${cite} could not be closed — close it by hand`)
|
|
94
|
+
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.info(`${cite} closed (v${version})`)
|
|
99
|
+
await carry_forward(repo, blockers)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function close_issue(repo: string, issue_number: number, version: string): Promise<void> {
|
|
103
|
+
const blockers = await open_blockers_of(repo, issue_number)
|
|
104
|
+
|
|
105
|
+
if (blockers === undefined) {
|
|
106
|
+
const cite = session_cite.issue(issue_number, undefined, repo)
|
|
107
|
+
|
|
108
|
+
console.warn(`⚠ the blockers of release Issue ${cite} could not be read, so it was left open`)
|
|
109
|
+
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
await close_and_carry(repo, issue_number, version, blockers)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function close_published(version: string): Promise<void> {
|
|
117
|
+
const repo = await git_gh_command.repo_get_name_with_owner()
|
|
118
|
+
|
|
119
|
+
if (repo === undefined) {
|
|
120
|
+
console.warn('⚠ the repository is unreadable, so its release Issue was left open')
|
|
121
|
+
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const open = await issue_release_cli.find_open(repo)
|
|
126
|
+
|
|
127
|
+
if (open === undefined) console.warn(`⚠ the release Issues of ${repo} could not be listed`)
|
|
128
|
+
else if (typeof open === 'number') await close_issue(repo, open, version)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const release_issue_close = { close_published }
|
|
132
|
+
|
|
133
|
+
export { release_issue_close }
|
|
@@ -5,6 +5,7 @@ import { git_remote_branch } from '#scripts/git/git-remote-branch'
|
|
|
5
5
|
import { repository_lock } from '#scripts/git/repository-lock'
|
|
6
6
|
import { write_version } from '#scripts/version/bump-version'
|
|
7
7
|
import { version_targets } from '#scripts/version/version-targets'
|
|
8
|
+
import { release_issue_close } from './release-issue-close'
|
|
8
9
|
import type { ReleasePlan } from './release-plan'
|
|
9
10
|
import { release_progress } from './release-progress'
|
|
10
11
|
import { release_tag } from './release-tag'
|
|
@@ -121,6 +122,14 @@ async function commit_release(plan: ReleasePlan): Promise<string> {
|
|
|
121
122
|
return pr_url
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
// The release Issue is closed only once the distribution answered success — a failed watch leaves it
|
|
126
|
+
// open, still naming the work as unreleased (joshuafolkken/kit#3825).
|
|
127
|
+
async function close_when_published(version: string, exit_code: number): Promise<number> {
|
|
128
|
+
if (exit_code === SUCCESS_EXIT_CODE) await release_issue_close.close_published(version)
|
|
129
|
+
|
|
130
|
+
return exit_code
|
|
131
|
+
}
|
|
132
|
+
|
|
124
133
|
// No tag means nothing downstream ran, so the npm and GitHub Release watches are not started.
|
|
125
134
|
async function merge_and_tag(
|
|
126
135
|
plan: ReleasePlan,
|
|
@@ -136,7 +145,9 @@ async function merge_and_tag(
|
|
|
136
145
|
console.info(release_tag.format_result(plan.next_version, is_tagged))
|
|
137
146
|
if (!is_tagged) return FAILURE_EXIT_CODE
|
|
138
147
|
|
|
139
|
-
|
|
148
|
+
const exit_code = await release_progress.wait_for_distribution(plan.next_version)
|
|
149
|
+
|
|
150
|
+
return await close_when_published(plan.next_version, exit_code)
|
|
140
151
|
}
|
|
141
152
|
|
|
142
153
|
// The whole release happens inside a work tree cut for it, so the root checkout is never touched: the
|
|
@@ -86,6 +86,16 @@ function ensure_labels(repo: string | undefined): ReadonlyArray<string> {
|
|
|
86
86
|
return create_missing(missing_labels(existing), repo)
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
// The label names `ensure_labels` would create, read without creating any — every one when the
|
|
90
|
+
// repository does not exist yet (`josh start --dry-run`). Undefined when the listing cannot be read,
|
|
91
|
+
// so an unread repository is not reported as one with no labels.
|
|
92
|
+
function planned_labels(repo: string | undefined): ReadonlyArray<string> | undefined {
|
|
93
|
+
if (repo === undefined) return label_names(REPOSITORY_LABELS)
|
|
94
|
+
const existing = read_existing(repo)
|
|
95
|
+
|
|
96
|
+
return existing === undefined ? undefined : label_names(missing_labels(existing))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const repository_labels = { ensure_labels, planned_labels }
|
|
90
100
|
|
|
91
101
|
export { repository_labels }
|