@joshuafolkken/kit 1.1383.0 → 1.1435.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-steps.md +4 -3
- package/.claude/skills/workflow-commands/backlogrun.md +1 -0
- package/.claude/skills/workflow-commands/chain-rule.md +4 -2
- package/.claude/skills/workflow-commands/issue-comments.md +6 -0
- package/.claude/skills/workflow-commands/observation-ledger.md +3 -2
- package/.claude/skills/workflow-commands/pre-gate-cut.md +5 -3
- package/README.md +25 -0
- package/dist/hooks/audit-provision.js +2 -2
- package/dist/hooks/batch-guard.js +7 -7
- package/dist/hooks/{chunk-LOJTBU5V.js → chunk-2G5F2YCD.js} +3 -3
- package/dist/hooks/{chunk-4MGHBTOM.js → chunk-3LE6VLSR.js} +25 -8
- package/dist/hooks/{chunk-Y5R47QHU.js → chunk-4BHUPFKF.js} +1 -1
- package/dist/hooks/{chunk-KC6LUZZ2.js → chunk-5SJSSCJG.js} +1 -1
- package/dist/hooks/{chunk-FZ6FUSBM.js → chunk-723HKZO5.js} +1 -1
- package/dist/hooks/{chunk-RYP3TSUJ.js → chunk-7KMN5QB5.js} +3 -3
- package/dist/hooks/{chunk-NEC4AT4Z.js → chunk-DGT54IH5.js} +4 -0
- package/dist/hooks/{chunk-YKUITFT7.js → chunk-EMB55BDF.js} +4 -4
- package/dist/hooks/{chunk-JBBMAPC4.js → chunk-IX6TTTI6.js} +4 -0
- package/dist/hooks/{chunk-6MAW7BCM.js → chunk-JXQBK323.js} +2677 -190
- package/dist/hooks/{chunk-KCF2YE7O.js → chunk-MSYSUSY6.js} +5 -5
- package/dist/hooks/{chunk-LC56R53A.js → chunk-MU4LJFIV.js} +4 -4
- package/dist/hooks/{chunk-7VSZ2N4B.js → chunk-QL6QTPWN.js} +3 -3
- package/dist/hooks/{chunk-TT742SIO.js → chunk-XER6SSIB.js} +92 -19
- package/dist/hooks/{chunk-2X2ART5G.js → chunk-YW452ZAX.js} +500 -29
- package/dist/hooks/{chunk-6AC4C2IO.js → chunk-YZXYOAZ3.js} +187 -443
- 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 +262 -201
- package/dist/hooks/investigation-guard.js +8 -8
- package/dist/hooks/pretool-guard.js +14 -14
- package/dist/hooks/session-lang.js +124 -15
- package/dist/hooks/stop-guard.js +98 -55
- package/dist/josh.js +63 -10
- package/package.json +4 -3
- package/prompts/review.md +22 -0
- 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/backlog/backlog-drive-cli.ts +2 -4
- package/scripts/backlog/backlog-drive-epic.ts +24 -8
- package/scripts/backlog/backlog-drive-launch.ts +5 -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-owner.ts +72 -5
- package/scripts/backlog/backlog-drive-unread.ts +56 -0
- package/scripts/backlog/backlog-drive.ts +16 -8
- 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 +80 -5
- 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 +130 -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 +13 -2
- 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-write.ts +5 -1
- package/scripts/gh/git-gh-issue.ts +4 -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 +4 -1
- package/scripts/init/init-ai-copy.ts +33 -10
- package/scripts/init/init-basic.ts +8 -3
- package/scripts/init/init-logic-toolchain.ts +76 -0
- package/scripts/init/init-logic.ts +2 -0
- package/scripts/init/init.ts +5 -23
- package/scripts/init/project-profile.ts +36 -7
- package/scripts/init/start-exec.ts +27 -1
- package/scripts/init/start-plan.ts +30 -4
- package/scripts/init/start-steps.ts +77 -3
- package/scripts/init/start.ts +21 -13
- package/scripts/init/workflow-mark.ts +35 -0
- package/scripts/issue/issue-ack-cli.ts +84 -0
- package/scripts/issue/issue-labels.ts +40 -8
- 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-backlog.ts +2 -2
- package/scripts/josh/josh-commands-development.ts +1 -1
- package/scripts/josh/josh-commands-issue.ts +7 -0
- package/scripts/josh/josh-commands-lane.ts +11 -2
- package/scripts/josh/josh-commands-project.ts +25 -1
- package/scripts/josh/josh-verdict.ts +33 -4
- package/scripts/lane/claude-lane-broker.ts +142 -0
- package/scripts/lane/lane-await.ts +6 -2
- 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/{openai-review-broker.ts → lane-session-broker.ts} +106 -28
- 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/review/review-attest-cli.ts +21 -6
- package/scripts/review/review-brief-cli.ts +2 -2
- package/scripts/review/review-comment-round.ts +114 -0
- package/scripts/review/review-record-cli.ts +39 -17
- 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 +139 -37
- 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 +58 -5
- 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 +121 -0
- package/scripts/run/board/run-board-span.ts +49 -7
- package/scripts/run/board/run-board-state.ts +10 -0
- package/scripts/run/board/run-board-status.ts +76 -13
- 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 +53 -16
- package/scripts/run/board/run-board-usual.ts +44 -0
- package/scripts/run/carry/run-carry-added.ts +1 -1
- package/scripts/run/carry/run-carry-args.ts +9 -1
- package/scripts/run/carry/run-carry-change.ts +1 -1
- package/scripts/run/carry/run-carry-cli.ts +39 -52
- package/scripts/run/carry/run-carry-end.ts +73 -0
- package/scripts/run/carry/run-carry-ended.ts +9 -1
- package/scripts/run/carry/run-carry.ts +24 -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 +3 -0
- package/scripts/run/event/run-event-scope.ts +1 -0
- package/scripts/run/event/run-event-setup.ts +91 -0
- package/scripts/run/event/run-event-stream.ts +46 -9
- 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/run-stranded-detect.ts +58 -12
- 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 +108 -74
- package/scripts/run/wake/run-wake-describe.ts +36 -2
- package/scripts/run/wake/run-wake-driver.ts +21 -5
- package/scripts/run/wake/run-wake-handoff.ts +1 -1
- 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/run/board/run-board-track.ts +0 -22
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { backlog_drive_restore } from '#scripts/backlog/backlog-drive-restore'
|
|
2
|
+
import { git_gh_issue_read } from '#scripts/gh/git-gh-issue-read'
|
|
3
|
+
import { git_command } from '#scripts/git/git-command'
|
|
4
|
+
import { issue_state } from '#scripts/issue/issue-state'
|
|
5
|
+
import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
|
|
6
|
+
import { run_hold } from '#scripts/run/hold/run-hold'
|
|
7
|
+
import {
|
|
8
|
+
PROCESS_ALIVE,
|
|
9
|
+
PROCESS_NONE,
|
|
10
|
+
PROCESS_UNKNOWN,
|
|
11
|
+
type ProcessTrace,
|
|
12
|
+
} from '#scripts/run/run-liveness'
|
|
13
|
+
import { lane_await } from './lane-await'
|
|
14
|
+
import { lane_registry, type LaneInfo } from './lane-registry'
|
|
15
|
+
|
|
16
|
+
// Whether a lane is in use, and whether it is spent — the one reading `lane:prune` discards a lane
|
|
17
|
+
// on and `josh backlogrun` keeps a watching run on.
|
|
18
|
+
//
|
|
19
|
+
// **In use means a child is working in it, or nobody could tell.** A lane a past run left behind has
|
|
20
|
+
// no process, and counting it as open kept a watching run from being replaced for as long as the lane
|
|
21
|
+
// sat there. An unanswered probe is in use: both callers act on "nobody is here", and the lane a
|
|
22
|
+
// wrong answer costs is one with a child mid-change.
|
|
23
|
+
//
|
|
24
|
+
// **Spent is all five: no child, the issue closed, not in flight, a clean tree, no commit off every
|
|
25
|
+
// remote.** `lane:close` forces the removal, so each condition names something a close cannot give
|
|
26
|
+
// back — and a condition that could not be read keeps the lane, with the reason said.
|
|
27
|
+
//
|
|
28
|
+
// **A lane the running `backlogrun` still has in flight is its `run:merge`'s.** A child that merged
|
|
29
|
+
// while no driver watched leaves a lane that reads spent on every other count; closed here, the run
|
|
30
|
+
// would have no `merge` event, no ledger line and no merged count for it (`run-tidy-lanes.ts`).
|
|
31
|
+
|
|
32
|
+
const STATE_FIELD = 'state'
|
|
33
|
+
const CLOSED_STATE = 'CLOSED'
|
|
34
|
+
|
|
35
|
+
interface SpentProbes {
|
|
36
|
+
process_trace: (issue: string) => ProcessTrace
|
|
37
|
+
issue_state: (issue: string) => Promise<string | undefined>
|
|
38
|
+
// The issues the running `backlogrun` launched and has not settled.
|
|
39
|
+
in_flight: () => Promise<ReadonlySet<string>>
|
|
40
|
+
is_tree_dirty: (directory: string) => Promise<boolean>
|
|
41
|
+
unpushed_commits: (branch: string) => Promise<number | undefined>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface KeptLane {
|
|
45
|
+
issue: string
|
|
46
|
+
reason: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The lanes that are in use, apart: a probe that did not answer is not a child seen working.
|
|
50
|
+
interface LaneUse {
|
|
51
|
+
working: Array<string>
|
|
52
|
+
unread: Array<string>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// One probe answer per lane: a second sample can disagree with the first, and a lane bucketed on
|
|
56
|
+
// two answers can land in neither list.
|
|
57
|
+
type TracedLane = readonly [issue: string, trace: ProcessTrace]
|
|
58
|
+
|
|
59
|
+
interface SpentSort {
|
|
60
|
+
spent: Array<LaneInfo>
|
|
61
|
+
kept: Array<KeptLane>
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type Check = (lane: LaneInfo, probes: SpentProbes) => Promise<string | undefined>
|
|
65
|
+
|
|
66
|
+
async function read_issue_state(issue: string): Promise<string | undefined> {
|
|
67
|
+
const json = await git_gh_issue_read.issue_view_json(issue, STATE_FIELD)
|
|
68
|
+
|
|
69
|
+
return json === undefined ? undefined : issue_state.parse_issue_state(json)?.state
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function read_unpushed_commits(branch: string): Promise<number | undefined> {
|
|
73
|
+
try {
|
|
74
|
+
return await git_command.commit_count_unpushed(branch)
|
|
75
|
+
} catch {
|
|
76
|
+
return undefined
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function read_in_flight(): Promise<ReadonlySet<string>> {
|
|
81
|
+
return backlog_drive_restore.active_issues(await run_event_stream_emit.current_events())
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const LIVE_PROBES: SpentProbes = {
|
|
85
|
+
process_trace: lane_await.process_trace_default,
|
|
86
|
+
issue_state: read_issue_state,
|
|
87
|
+
in_flight: read_in_flight,
|
|
88
|
+
is_tree_dirty: run_hold.is_tree_dirty,
|
|
89
|
+
unpushed_commits: read_unpushed_commits,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// A stranded lane has no work tree for a child to be in, so it is never in use.
|
|
93
|
+
function trace_of(lane: LaneInfo, probes: SpentProbes): ProcessTrace {
|
|
94
|
+
return lane.is_stranded ? PROCESS_NONE : probes.process_trace(lane.issue)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function child_reason(lane: LaneInfo, probes: SpentProbes): Promise<string | undefined> {
|
|
98
|
+
const trace = probes.process_trace(lane.issue)
|
|
99
|
+
|
|
100
|
+
if (trace === PROCESS_NONE) return undefined
|
|
101
|
+
|
|
102
|
+
return trace === PROCESS_ALIVE
|
|
103
|
+
? 'a child is still working in it'
|
|
104
|
+
: 'whether a child is working in it could not be read'
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function issue_reason(lane: LaneInfo, probes: SpentProbes): Promise<string | undefined> {
|
|
108
|
+
const state = await probes.issue_state(lane.issue)
|
|
109
|
+
|
|
110
|
+
if (state === CLOSED_STATE) return undefined
|
|
111
|
+
|
|
112
|
+
return state === undefined ? 'its issue could not be read' : 'its issue is still open'
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function run_reason(lane: LaneInfo, probes: SpentProbes): Promise<string | undefined> {
|
|
116
|
+
const in_flight = await probes.in_flight()
|
|
117
|
+
|
|
118
|
+
return in_flight.has(lane.issue)
|
|
119
|
+
? 'the running backlogrun has not collected it yet (`pnpm josh run:merge` does)'
|
|
120
|
+
: undefined
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// `is_tree_dirty` answers dirty for a status it could not read, which is the side that keeps the lane.
|
|
124
|
+
async function tree_reason(lane: LaneInfo, probes: SpentProbes): Promise<string | undefined> {
|
|
125
|
+
return (await probes.is_tree_dirty(lane.directory)) ? 'it has uncommitted changes' : undefined
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function commit_reason(lane: LaneInfo, probes: SpentProbes): Promise<string | undefined> {
|
|
129
|
+
const count = await probes.unpushed_commits(lane.branch)
|
|
130
|
+
|
|
131
|
+
if (count === 0) return undefined
|
|
132
|
+
|
|
133
|
+
return count === undefined
|
|
134
|
+
? 'its commits could not be counted'
|
|
135
|
+
: `it has ${String(count)} commit(s) that are on no remote`
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// In the order a person acts on them: a running child outranks everything, and an open issue is why
|
|
139
|
+
// the lane exists at all.
|
|
140
|
+
const CHECKS: ReadonlyArray<Check> = [
|
|
141
|
+
child_reason,
|
|
142
|
+
issue_reason,
|
|
143
|
+
run_reason,
|
|
144
|
+
tree_reason,
|
|
145
|
+
commit_reason,
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
// Why the lane is kept, or `undefined` for a spent one.
|
|
149
|
+
async function kept_reason(
|
|
150
|
+
lane: LaneInfo,
|
|
151
|
+
probes: SpentProbes = LIVE_PROBES,
|
|
152
|
+
): Promise<string | undefined> {
|
|
153
|
+
const reasons = await Promise.all(CHECKS.map(async (check) => await check(lane, probes)))
|
|
154
|
+
|
|
155
|
+
return reasons.find((reason) => reason !== undefined)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Every live lane as spent or kept. A stranded lane is neither: `lane:prune` closes it already.
|
|
159
|
+
async function sort_lanes(
|
|
160
|
+
lanes: ReadonlyArray<LaneInfo>,
|
|
161
|
+
probes: SpentProbes = LIVE_PROBES,
|
|
162
|
+
): Promise<SpentSort> {
|
|
163
|
+
const live = lanes.filter((lane) => !lane.is_stranded)
|
|
164
|
+
const reasons = await Promise.all(live.map(async (lane) => await kept_reason(lane, probes)))
|
|
165
|
+
const sorted: SpentSort = { spent: [], kept: [] }
|
|
166
|
+
|
|
167
|
+
for (const [index, lane] of live.entries()) {
|
|
168
|
+
const reason = reasons[index]
|
|
169
|
+
|
|
170
|
+
if (reason === undefined) sorted.spent.push(lane)
|
|
171
|
+
else sorted.kept.push({ issue: lane.issue, reason })
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return sorted
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function issues_traced(traced: ReadonlyArray<TracedLane>, trace: ProcessTrace): Array<string> {
|
|
178
|
+
return traced.filter(([, answer]) => answer === trace).map(([issue]) => issue)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** The lanes in use — what a run being replaced must not be stopped over. */
|
|
182
|
+
async function lanes_in_use(probes: SpentProbes = LIVE_PROBES): Promise<LaneUse> {
|
|
183
|
+
const lanes = await lane_registry.list_lanes()
|
|
184
|
+
const traced = lanes.map((lane): TracedLane => [lane.issue, trace_of(lane, probes)])
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
working: issues_traced(traced, PROCESS_ALIVE),
|
|
188
|
+
unread: issues_traced(traced, PROCESS_UNKNOWN),
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const lane_spent = { kept_reason, lanes_in_use, sort_lanes }
|
|
193
|
+
|
|
194
|
+
export type { KeptLane, LaneUse, SpentProbes, SpentSort }
|
|
195
|
+
export { lane_spent }
|
|
@@ -112,11 +112,15 @@ function stage_row(stage: string, measured: Measured): string {
|
|
|
112
112
|
])
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
function measured_of(entries: ReadonlyArray<LedgerEntry>): Measured {
|
|
116
|
+
const stages = entries.filter(is_stage)
|
|
117
|
+
|
|
118
|
+
return { implement: implement_durations(entries.filter(is_dispatch), stages), stages }
|
|
119
|
+
}
|
|
120
|
+
|
|
115
121
|
// The period's table: `entries` is the whole ledger, filtered here to `window`.
|
|
116
122
|
function table(entries: ReadonlyArray<LedgerEntry>, window: StatsWindow): string {
|
|
117
|
-
const
|
|
118
|
-
const stages = period.filter(is_stage)
|
|
119
|
-
const measured = { implement: implement_durations(period.filter(is_dispatch), stages), stages }
|
|
123
|
+
const measured = measured_of(lane_stats.in_window(entries, window))
|
|
120
124
|
|
|
121
125
|
return [
|
|
122
126
|
lane_stats.to_row(COLUMNS),
|
|
@@ -125,10 +129,30 @@ function table(entries: ReadonlyArray<LedgerEntry>, window: StatsWindow): string
|
|
|
125
129
|
].join('\n')
|
|
126
130
|
}
|
|
127
131
|
|
|
132
|
+
// Each stage's median over every entry given, a stage nothing recorded left out rather than read as
|
|
133
|
+
// `0` — how long a stage usually takes, which `run:board` measures a running child's phase against.
|
|
134
|
+
function medians(entries: ReadonlyArray<LedgerEntry>): ReadonlyMap<string, number> {
|
|
135
|
+
const measured = measured_of(entries)
|
|
136
|
+
|
|
137
|
+
return new Map(
|
|
138
|
+
STAGES.flatMap((stage) => {
|
|
139
|
+
const median = lane_stats.median(durations_of(stage, measured))
|
|
140
|
+
|
|
141
|
+
return median === undefined ? [] : [[stage, median] as const]
|
|
142
|
+
}),
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
128
146
|
const lane_stage_stats = {
|
|
129
147
|
IMPLEMENT,
|
|
130
148
|
NOT_MEASURED,
|
|
131
149
|
STAGES,
|
|
150
|
+
cell,
|
|
151
|
+
is_dispatch,
|
|
152
|
+
is_number,
|
|
153
|
+
is_stage,
|
|
154
|
+
medians,
|
|
155
|
+
started_ms,
|
|
132
156
|
table,
|
|
133
157
|
}
|
|
134
158
|
|
|
@@ -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
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { issue_watch_gate } from '#scripts/issue/issue-watch-gate'
|
|
3
4
|
import { review_attest, type AttestVerdict } from './review-attest'
|
|
4
5
|
import { review_checkout } from './review-checkout'
|
|
5
6
|
|
|
@@ -51,6 +52,22 @@ async function run_attest(nonce: string): Promise<number> {
|
|
|
51
52
|
return FAILURE_EXIT_CODE
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
// An attested review still describes the issue as it read it: a person's update it has not taken in,
|
|
56
|
+
// or one taken in after the brief, makes its verdict stale (`issue-watch-gate.ts`).
|
|
57
|
+
async function report_issue_watch(verdict: AttestVerdict): Promise<number> {
|
|
58
|
+
const refusal = await issue_watch_gate.review_refusal_here()
|
|
59
|
+
|
|
60
|
+
if (refusal !== undefined) {
|
|
61
|
+
console.error(refusal)
|
|
62
|
+
|
|
63
|
+
return FAILURE_EXIT_CODE
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.info(attested_line(verdict))
|
|
67
|
+
|
|
68
|
+
return 0
|
|
69
|
+
}
|
|
70
|
+
|
|
54
71
|
// **`check_here`, not `check`** — the pointer is keyed on the repository root, and `check`'s default
|
|
55
72
|
// is `PROJECT_ROOT`, which is `process.cwd()`. Run from a subdirectory, or from the session checkout
|
|
56
73
|
// rather than the lane's, the two hash different keys: no pointer is found, the command prints "no
|
|
@@ -65,15 +82,13 @@ async function run_check(): Promise<number> {
|
|
|
65
82
|
return 0
|
|
66
83
|
}
|
|
67
84
|
|
|
68
|
-
if (verdict.status
|
|
69
|
-
console.
|
|
85
|
+
if (verdict.status !== 'ok') {
|
|
86
|
+
console.error(review_attest.refusal_message(verdict))
|
|
70
87
|
|
|
71
|
-
return
|
|
88
|
+
return FAILURE_EXIT_CODE
|
|
72
89
|
}
|
|
73
90
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return FAILURE_EXIT_CODE
|
|
91
|
+
return await report_issue_watch(verdict)
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
// Anything unrecognized is a usage error rather than a default. A misspelled flag that fell through
|
|
@@ -12,7 +12,7 @@ import type { FileMapStamp } from '#scripts/josh/file-map-stamp'
|
|
|
12
12
|
import { find_package_directory } from '#scripts/josh/josh-logic'
|
|
13
13
|
import { path_decision } from '#scripts/josh/path-decision'
|
|
14
14
|
import { stamp_file } from '#scripts/josh/stamp-file'
|
|
15
|
-
import {
|
|
15
|
+
import { lane_session_broker } from '#scripts/lane/lane-session-broker'
|
|
16
16
|
import { run_ship_review } from '#scripts/run/ship/run-ship-review'
|
|
17
17
|
import { review_attest } from './review-attest'
|
|
18
18
|
import { review_brief } from './review-brief'
|
|
@@ -150,7 +150,7 @@ function record_round_one(
|
|
|
150
150
|
// was written by the brief the pointer names: one left by an earlier brief is not the tree the
|
|
151
151
|
// finished review read, so the resume reads the whole change instead.
|
|
152
152
|
function attested_tree(checkout: ReviewCheckout): FileMapStamp | undefined {
|
|
153
|
-
const { findings } =
|
|
153
|
+
const { findings } = lane_session_broker.review_paths(PROJECT_ROOT)
|
|
154
154
|
const briefed = review_stamps.briefed_stamp.read()
|
|
155
155
|
|
|
156
156
|
if (briefed === undefined || !review_attest.is_briefed_since(briefed.taken_at, checkout.root)) {
|