@joshuafolkken/kit 1.1136.0 → 1.1178.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 +4 -4
- package/.claude/skills/epic-commands/SKILL.md +27 -239
- package/.claude/skills/epic-commands/epic-bundle.md +115 -0
- package/.claude/skills/epic-commands/execution-waves.md +126 -0
- package/.claude/skills/workflow-commands/SKILL.md +48 -194
- package/.claude/skills/workflow-commands/background-commands.md +1 -1
- package/.claude/skills/workflow-commands/backlogrun-child.md +12 -123
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +14 -63
- package/.claude/skills/workflow-commands/backlogrun-park.md +16 -15
- package/.claude/skills/workflow-commands/backlogrun-progress.md +17 -173
- package/.claude/skills/workflow-commands/backlogrun-recovery.md +155 -0
- package/.claude/skills/workflow-commands/backlogrun-steps.md +65 -144
- package/.claude/skills/workflow-commands/backlogrun.md +5 -4
- package/.claude/skills/workflow-commands/chain-rule.md +3 -2
- package/.claude/skills/workflow-commands/delegation.md +2 -2
- package/.claude/skills/workflow-commands/entry-sequence.md +62 -0
- package/.claude/skills/workflow-commands/followup.md +104 -181
- package/.claude/skills/workflow-commands/fullrun-steps.md +8 -9
- package/.claude/skills/workflow-commands/fullrun.md +16 -73
- package/.claude/skills/workflow-commands/halfrun.md +10 -36
- package/.claude/skills/workflow-commands/into-target.md +4 -4
- package/.claude/skills/workflow-commands/issue-comments.md +7 -8
- package/.claude/skills/workflow-commands/issue-scout.md +6 -6
- package/.claude/skills/workflow-commands/kickoff.md +6 -6
- package/.claude/skills/workflow-commands/needs-human-review.md +6 -6
- package/.claude/skills/workflow-commands/observation-filing.md +21 -172
- package/.claude/skills/workflow-commands/observation-ledger.md +150 -0
- package/.claude/skills/workflow-commands/pre-gate-cut.md +35 -95
- package/.claude/skills/workflow-commands/prerequisite.md +6 -6
- package/.claude/skills/workflow-commands/progress-watcher.md +90 -0
- package/.claude/skills/workflow-commands/prrun.md +5 -5
- package/.claude/skills/workflow-commands/retrospective.md +9 -9
- package/.claude/skills/workflow-commands/split-assessment.md +2 -2
- package/.claude/skills/workflow-commands/target-repository.md +2 -2
- package/.claude/skills/workflow-commands/working-tree-hold.md +7 -5
- package/.codex/hooks.json +4 -4
- package/.github/workflows/ci.yml +15 -2
- package/AGENTS.md +1 -6
- package/CLAUDE.md +58 -120
- package/GEMINI.md +1 -6
- package/dist/CLAUDE.md +58 -120
- package/dist/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-NFZGFGOR.js → chunk-32GQTL4F.js} +14 -1
- package/dist/hooks/{chunk-HBKZLITF.js → chunk-DO6NWWHU.js} +4 -4
- package/dist/hooks/{chunk-KBXZLMRH.js → chunk-G6KRCKEC.js} +216 -76
- package/dist/hooks/{chunk-JKKTVDAG.js → chunk-GDS5N2KB.js} +186 -309
- package/dist/hooks/chunk-I4XSL4TT.js +56 -0
- package/dist/hooks/{chunk-GDU26K3J.js → chunk-S6X6WYKR.js} +886 -174
- package/dist/hooks/{chunk-FO2I7UT7.js → chunk-UAGLLIGZ.js} +1 -1
- package/dist/hooks/{chunk-RFWWUI5F.js → chunk-VESKRQOF.js} +3 -3
- package/dist/hooks/{chunk-KOKOAV6D.js → chunk-VQLLNFNT.js} +6 -4
- package/dist/hooks/{chunk-LJBLGHEH.js → chunk-ZAL3NRGL.js} +4 -6
- 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/inputs.json +178 -177
- package/dist/hooks/investigation-guard.js +5 -5
- package/dist/hooks/pretool-guard.js +11 -10
- package/dist/hooks/stop-guard.js +20 -6
- package/dist/josh.js +29 -3
- package/dist/version/index.js +5 -1
- package/package.json +2 -1
- package/prompts/collaboration-workflow/file-edits.md +11 -10
- package/prompts/collaboration-workflow/gh-rest.md +2 -4
- package/prompts/collaboration-workflow/glossary.md +1 -1
- package/prompts/collaboration-workflow/issue-citation.md +5 -9
- package/prompts/collaboration-workflow/issue-template.md +40 -115
- package/prompts/collaboration-workflow/operating-rules.md +4 -5
- package/prompts/collaboration-workflow/output-bounds.md +12 -78
- package/prompts/collaboration-workflow/overview.md +7 -13
- package/prompts/collaboration-workflow/plan-comment.md +11 -39
- package/prompts/collaboration-workflow/principles.md +3 -4
- package/prompts/collaboration-workflow/report-format.md +28 -74
- package/prompts/collaboration-workflow/residency.md +12 -109
- package/prompts/collaboration-workflow/rule-delivery.md +98 -97
- package/prompts/collaboration-workflow/shell-body.md +3 -13
- package/prompts/collaboration-workflow/turn-batching.md +14 -88
- package/prompts/collaboration-workflow/upstream-interrupt.md +12 -14
- package/prompts/collaboration-workflow/wip-cap.md +6 -45
- package/prompts/collaboration-workflow.md +1 -13
- package/prompts/review-rubric.md +8 -71
- package/prompts/review.md +25 -55
- package/prompts/testing-guide.md +18 -67
- package/scripts/agent/agent-role-profile.ts +1 -1
- package/scripts/agent/codex-hooks.ts +3 -12
- package/scripts/backlog/backlog-budget.ts +1 -1
- package/scripts/backlog/backlog-drive-cli.ts +4 -3
- package/scripts/backlog/backlog-drive-offer-argv.ts +10 -1
- package/scripts/backlog/backlog-next.ts +15 -4
- package/scripts/backlog/backlog-offer.ts +6 -6
- package/scripts/backlog/backlog-plan-cli.ts +6 -5
- package/scripts/backlog/backlog-rank.ts +18 -9
- package/scripts/backlog/backlog-restructure.ts +152 -0
- package/scripts/backlog/backlog-waves.ts +15 -17
- package/scripts/behavior/behavior-rules.ts +11 -4
- package/scripts/cost-runtime/context-cut-threshold.ts +1 -9
- package/scripts/cost-runtime/cost-verdict.ts +15 -28
- package/scripts/delegation/investigation-reads.ts +5 -5
- package/scripts/discovery/repo-discovery.ts +21 -7
- package/scripts/discovery/repo-origin.ts +26 -0
- package/scripts/discovery/repo-party.ts +42 -1
- package/scripts/document/backlogrun-parent-read-set.ts +14 -15
- package/scripts/document/document-byte-budget.ts +34 -20
- package/scripts/document/entry-read-budget.ts +21 -9
- package/scripts/document/lane-child-read-set.ts +12 -14
- package/scripts/document/read-set-cli.ts +4 -4
- package/scripts/document/read-set-point-of-use.ts +10 -2
- package/scripts/document/resident-budget.ts +47 -0
- package/scripts/epic/epic-shape.ts +10 -13
- package/scripts/epic/epic-solo.ts +1 -1
- package/scripts/exports/unused-members-cli.ts +7 -3
- package/scripts/followup/git-followup-stages.ts +9 -0
- package/scripts/followup/git-pr-followup.ts +1 -1
- package/scripts/gh/git-gh-api-path.ts +7 -0
- package/scripts/gh/git-gh-issue.ts +5 -3
- package/scripts/gh/git-gh-pr-read.ts +4 -0
- package/scripts/gh/git-pr-checks-eval.ts +8 -0
- package/scripts/gh/git-pr-checks.ts +11 -1
- package/scripts/git/constants.ts +10 -0
- package/scripts/git/main-merge.ts +57 -21
- package/scripts/git/schemas.ts +2 -0
- package/scripts/hooks/pretool-guard-cli.ts +2 -2
- package/scripts/hooks/pretool-guard.ts +45 -2
- package/scripts/hooks/run-hook.sh +41 -0
- package/scripts/init/hook-command-rewrite.ts +18 -35
- package/scripts/init/hook-launch.ts +21 -32
- package/scripts/issue/issue-auto-ok.ts +95 -0
- package/scripts/issue/issue-file-cli.ts +53 -7
- package/scripts/issue/issue-file.ts +28 -5
- package/scripts/issue/issue-labels.ts +12 -12
- package/scripts/issue/issue-rank.ts +1 -1
- package/scripts/issue/issue-read-cli.ts +3 -3
- package/scripts/issue/issue-read.ts +2 -2
- package/scripts/issue/issue-scout-cli.ts +2 -2
- package/scripts/issue/issue-state-cli.ts +1 -1
- package/scripts/issue/issue-state.ts +2 -2
- package/scripts/issue/issue-wip.ts +70 -0
- package/scripts/josh/hook-decision.ts +17 -0
- package/scripts/josh/josh-commands-development.ts +11 -1
- package/scripts/josh/josh-commands-issue.ts +2 -2
- package/scripts/lane/lane-launch-cli.ts +21 -3
- package/scripts/lines/bytes-command.ts +29 -6
- package/scripts/notify/telegram-notify.ts +26 -1
- package/scripts/observations/observation-ledger-line.ts +1 -1
- package/scripts/release/release-progress.ts +167 -0
- package/scripts/release/release-publish.ts +19 -8
- package/scripts/release/release-tag.ts +22 -8
- package/scripts/rules/decision-oracle-stage.ts +1 -1
- package/scripts/rules/decision-oracle.ts +6 -6
- package/scripts/rules/delivered-rules.ts +11 -11
- package/scripts/rules/direct-pr-create.ts +110 -0
- package/scripts/rules/file-body.ts +98 -42
- package/scripts/rules/filing-cap.ts +2 -2
- package/scripts/rules/filing-offer.ts +1 -1
- package/scripts/rules/gh-api.ts +18 -1
- package/scripts/rules/implementation-cut.ts +3 -1
- package/scripts/rules/lane-background.ts +67 -10
- package/scripts/rules/permission-guards.ts +10 -2
- package/scripts/rules/pre-gate-cut.ts +2 -2
- package/scripts/rules/stop-rules.ts +4 -4
- package/scripts/rules/test-declared-commit.ts +1 -1
- package/scripts/rules/third-party-write.ts +56 -12
- package/scripts/run/run-carry.ts +11 -5
- package/scripts/run/run-cut-cli.ts +1 -1
- package/scripts/run/run-cut-handoff.ts +21 -1
- package/scripts/run/run-cut-report.ts +2 -2
- package/scripts/run/run-entry-cli.ts +11 -3
- package/scripts/run/run-hold-cli.ts +16 -2
- package/scripts/run/run-label.ts +50 -0
- package/scripts/run/run-merge-steps.ts +8 -3
- package/scripts/run/run-merge.ts +1 -1
- package/scripts/run/run-prep-cli.ts +6 -2
- package/scripts/run/run-prep.ts +17 -0
- package/scripts/run/run-ship-cli.ts +27 -6
- package/scripts/run/run-ship-next.ts +13 -0
- package/scripts/run/run-ship-pre-detach.ts +66 -0
- package/scripts/run/run-ship-preflight.ts +34 -3
- package/scripts/run/run-ship-return.ts +7 -4
- package/scripts/run/run-ship-review-steps.ts +1 -1
- package/scripts/run/run-ship-review.ts +26 -5
- package/scripts/run/run-ship-stage.ts +13 -0
- package/scripts/run/run-ship-steps.ts +10 -2
- package/scripts/run/run-ship-sync.ts +114 -0
- package/scripts/run/run-ship.ts +6 -0
- package/scripts/run/run-step-cli.ts +20 -7
- package/scripts/run/run-wake-cli.ts +8 -1
- package/scripts/run/run-wake-driver.ts +62 -8
- package/scripts/run/run-wake-session.ts +2 -1
- package/scripts/test/test-declared-logic.ts +1 -1
- package/scripts/test/test-red-logic.ts +17 -2
- package/scripts/test/test-red.ts +8 -7
- package/scripts/time-runtime/time-batch-guard.ts +52 -16
- package/scripts/time-runtime/time-density-hook.ts +20 -2
- package/scripts/time-runtime/time-density.ts +4 -3
- package/scripts/time-runtime/time-markers.ts +15 -4
- package/scripts/time-runtime/time-transcript-line.ts +27 -1
- package/scripts/version/npm-registry.ts +11 -1
- package/templates/workflows/ci.yml +9 -1
- package/.claude/skills/workflow-commands/followup-reference.md +0 -118
- package/dist/hooks/chunk-HANSKHES.js +0 -464
package/prompts/review.md
CHANGED
|
@@ -15,17 +15,10 @@ points to them rather than restating them:
|
|
|
15
15
|
|
|
16
16
|
## When to run
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
/ `halfrun` / `backlogrun`, driven by the brief `pnpm josh review:brief` prints. Round 1 runs before the
|
|
23
|
-
commit — scope: `git diff main`. Round 2 runs after it, beside the CI the commit started
|
|
24
|
-
(`.claude/skills/workflow-commands/chain-rule.md` → "The pull request opens between the rounds, so CI
|
|
25
|
-
runs beside round 2").
|
|
26
|
-
|
|
27
|
-
The workflow review uses the `reviewer` role profile printed by `pnpm josh review:brief`. Pass its
|
|
28
|
-
model and effort explicitly to the review subagent; do not substitute the review level for effort.
|
|
18
|
+
When a review runs is `CLAUDE.md` → "Pre-commit Self-Review"; inside a workflow, round 1 runs before
|
|
19
|
+
the commit and round 2 beside CI (`.claude/skills/workflow-commands/chain-rule.md`). Pass the
|
|
20
|
+
`reviewer` profile's model and effort that `pnpm josh review:brief` prints to the review subagent;
|
|
21
|
+
do not substitute the review level for effort.
|
|
29
22
|
|
|
30
23
|
Re-run after applying fixes until **no high or medium findings remain — or until the round cap below is
|
|
31
24
|
reached, the first review included — whichever comes first.** The cap below is not optional. The second round
|
|
@@ -46,10 +39,8 @@ pnpm josh review:brief --level-only # the branch diff
|
|
|
46
39
|
pnpm josh review:brief --level-only --staged # the staged diff
|
|
47
40
|
```
|
|
48
41
|
|
|
49
|
-
**The level is decided from the changed paths and nothing else
|
|
50
|
-
|
|
51
|
-
likely to be shipped. A rule an agent applies from memory is a rule an agent can talk itself out of;
|
|
52
|
-
one it has to run answers the same way every time.
|
|
42
|
+
**The level is decided from the changed paths and nothing else** — never "this one is small"
|
|
43
|
+
(`docs/maintainers/review-history.md`).
|
|
53
44
|
|
|
54
45
|
| Every changed path is… | Level | Rounds |
|
|
55
46
|
| ---------------------------------------------------------------------------------------- | -------- | ----------------------- |
|
|
@@ -64,13 +55,8 @@ would hand a reduced level to a caller that failed to read the diff.
|
|
|
64
55
|
in `package.json`'s `files` and are written into every consumer project by `josh init` / `josh sync`,
|
|
65
56
|
so a defect in one reaches a consumer and is reviewed at `medium` like any other shipped file.
|
|
66
57
|
|
|
67
|
-
**Documentation is not inert either
|
|
68
|
-
|
|
69
|
-
them from _testing_ — which asks whether an automated test could have caught the defect — while this
|
|
70
|
-
asks whether a human reading the diff is the only thing that can. Measured on joshuafolkken/kit#963 and
|
|
71
|
-
#965, both documentation-only by that classification: a `medium` review found ten real defects in each
|
|
72
|
-
— pointers into sections that had been removed, and citations naming the wrong file, in artifacts
|
|
73
|
-
distributed to every consumer — that nothing else would have caught.
|
|
58
|
+
**Documentation is not inert either.** `CLAUDE.md`, `prompts/**`, `.claude/**` and `docs/**` are all
|
|
59
|
+
reviewed at `medium`, though exempt from _testing_: a reader is the only detector a prose defect has.
|
|
74
60
|
|
|
75
61
|
**A confirmed High blocks regardless of round count**, and the round cap below does not change that.
|
|
76
62
|
|
|
@@ -78,26 +64,19 @@ distributed to every consumer — that nothing else would have caught.
|
|
|
78
64
|
|
|
79
65
|
## Review round cap (2 rounds)
|
|
80
66
|
|
|
81
|
-
The severity rule in the rubric is not a stopping condition on its own
|
|
82
|
-
|
|
83
|
-
new code the fixes produce, which is unbounded.
|
|
67
|
+
The severity rule in the rubric is not a stopping condition on its own: every fix creates new surface a
|
|
68
|
+
further round would find something in.
|
|
84
69
|
|
|
85
70
|
**Two rounds is the ceiling, not the schedule.** Whether the second one is due at all is
|
|
86
71
|
`pnpm josh review:round2`'s answer — "When round 2 is skipped entirely, and when it is not" below is
|
|
87
|
-
the single source of that condition
|
|
88
|
-
|
|
89
|
-
This is measured, not theorized. On joshuafolkken/kit#854 four rounds produced 18 findings; on #855
|
|
90
|
-
two rounds produced 19. Almost none of them was a repeat: each round found new things, and many of
|
|
91
|
-
those were about code the **previous round's fix** had just written. Two rounds of that is diligence; a
|
|
92
|
-
third is the review chasing its own tail.
|
|
72
|
+
the single source of that condition.
|
|
93
73
|
|
|
94
74
|
### A merge-conflict resolution review is not one of the two
|
|
95
75
|
|
|
96
76
|
**When `pnpm josh followup` reports `PR checks failed (merge conflict)`, the child resolves the conflict
|
|
97
|
-
in its own lane and reviews the resolution — and that round does not count against the cap
|
|
98
|
-
(joshuafolkken/kit#1623). The cap bounds re-reading _the change under review_; a resolution review reads
|
|
77
|
+
in its own lane and reviews the resolution — and that round does not count against the cap**. The cap bounds re-reading _the change under review_; a resolution review reads
|
|
99
78
|
a different subject — not the change, but what merging a moved `main` into it did.
|
|
100
|
-
`.claude/skills/workflow-commands/backlogrun-
|
|
79
|
+
`.claude/skills/workflow-commands/backlogrun-recovery.md` → "Conflicts are not predicted" is the single source of
|
|
101
80
|
the procedure and of the four conditions under which the run steps back instead of resolving.
|
|
102
81
|
`pnpm josh review:attest --check` must answer `ok` before the merge is re-issued, and a confirmed High
|
|
103
82
|
parks the child rather than buying it a further round.
|
|
@@ -105,7 +84,7 @@ parks the child rather than buying it a further round.
|
|
|
105
84
|
### When round 2 is skipped entirely, and when it is not
|
|
106
85
|
|
|
107
86
|
**Two rounds is the ceiling; whether the second round runs at all is a command's answer, not a reading
|
|
108
|
-
of a list
|
|
87
|
+
of a list**:
|
|
109
88
|
|
|
110
89
|
```bash
|
|
111
90
|
pnpm josh review:round2 --round-1-closed # → required | skip ; the reason on stderr
|
|
@@ -116,8 +95,7 @@ round-1 High/Medium finding actually closed — by a fix in this working tree, o
|
|
|
116
95
|
positive — and none was filed or deferred. **Without the flag the answer is `required`**, which is also
|
|
117
96
|
what the command answers to every uncertainty it meets, a missing round-1 snapshot included.
|
|
118
97
|
|
|
119
|
-
**`skip` has exactly two arms, and both are states in which round 1's fix code needs no review
|
|
120
|
-
(joshuafolkken/kit#1222 — the round exists because round 1's fix code is otherwise unreviewed):
|
|
98
|
+
**`skip` has exactly two arms, and both are states in which round 1's fix code needs no review**:
|
|
121
99
|
|
|
122
100
|
| Arm | What it is |
|
|
123
101
|
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -126,9 +104,7 @@ what the command answers to every uncertainty it meets, a missing round-1 snapsh
|
|
|
126
104
|
|
|
127
105
|
**Neither arm weakens the standard.** A round-1 High/Medium that did not close is not a closed finding,
|
|
128
106
|
so the flag is not passable; the cap is still two rounds; and a confirmed High still blocks the merge.
|
|
129
|
-
|
|
130
|
-
is not adopted**: a documentation-only diff is exactly where the review is the only detector (the #963 /
|
|
131
|
-
#965 measurement above), so a prompt fix answers `required`.
|
|
107
|
+
A documentation-only fix delta is not inert, so a prompt fix answers `required`.
|
|
132
108
|
|
|
133
109
|
**A skipped round is recorded on the Issue**, so the condition stays auditable and can be withdrawn if
|
|
134
110
|
a defect is later traced to a skipped delta:
|
|
@@ -142,7 +118,7 @@ a defect is later traced to a skipped delta:
|
|
|
142
118
|
### Three-way disposition after the cap
|
|
143
119
|
|
|
144
120
|
**After the second round, place every remaining non-High finding in exactly one of three exits, decided
|
|
145
|
-
from what the finding is rather than from the filer's discretion
|
|
121
|
+
from what the finding is rather than from the filer's discretion**. **The
|
|
146
122
|
default exit is branch 3, and branch 2 has to be earned**. Read them in order — branch 1 where the fix is
|
|
147
123
|
trivial and local, branch 2 only where the finding clears the bar it names, branch 3 for everything
|
|
148
124
|
left, which is most of them. "It might matter later" is branch 3.
|
|
@@ -167,12 +143,6 @@ left, which is most of them. "It might matter later" is branch 3.
|
|
|
167
143
|
suspicion included. **The note is not optional**: one line naming the finding and why it was dropped
|
|
168
144
|
is what keeps a dropped finding auditable rather than invisible.
|
|
169
145
|
|
|
170
|
-
**What it costs, and why that is the right trade.** A dropped finding that later turns out to matter is
|
|
171
|
-
re-found by the next review of that code, at the price of one round it would have paid anyway. A filed
|
|
172
|
-
finding that never mattered is carried forever — placed in an epic, offered by `epic:next`, read by
|
|
173
|
-
everyone who scans the backlog. The two errors are not symmetric, and the old default was on the
|
|
174
|
-
expensive side of them.
|
|
175
|
-
|
|
176
146
|
**Findings that reduce to one root judgement are filed as one Issue, not several** — a single follow-up
|
|
177
147
|
Issue with a section per symptom (`## 現象 1` / `## 現象 2`).
|
|
178
148
|
|
|
@@ -187,7 +157,7 @@ Only branch 2 files an Issue. What follows applies to that branch.
|
|
|
187
157
|
the commit.** Where the run opens a pull request — `fullrun`, `backlogrun` — they go after
|
|
188
158
|
`pnpm josh git -y` and before `pnpm josh followup`; where it does not — `halfrun`, or a standalone
|
|
189
159
|
pre-commit self-review — they run as soon as the disposition is decided. **The chain may run in a
|
|
190
|
-
delegated unit** — `pnpm josh delegate followup-filing
|
|
160
|
+
delegated unit** — `pnpm josh delegate followup-filing`.
|
|
191
161
|
- **When the round files a second follow-up, run `pnpm josh issue:fold` first.** Several findings from
|
|
192
162
|
one review fold into one Issue by default — the filing-time counterpart to the split assessment,
|
|
193
163
|
reading the same two questions (`.claude/skills/workflow-commands/split-assessment.md` → "The same
|
|
@@ -204,13 +174,13 @@ Only branch 2 files an Issue. What follows applies to that branch.
|
|
|
204
174
|
write command yourself — never a hand edit of the epic body, which leaves the task list and the
|
|
205
175
|
`blocked-by` relations disagreeing and `epic:next` returning `error`.
|
|
206
176
|
|
|
207
|
-
| Answer | Do
|
|
208
|
-
| ------------------------------------------------------------------------------------------------- |
|
|
209
|
-
| `add_to_epic` | `pnpm josh epic --add <E> <new>` — add `--before <M>` / `--after <M>` when the relation carries an order
|
|
210
|
-
| `create_epic` | `pnpm josh epic "<title>" <new> <other> [--ordered]`
|
|
211
|
-
| `ask` — candidates spread across two or more epics | **Choose the epic you recommend, run its write command, and record the decision** — what was taken, what was rejected, why — on both the new Issue and that epic's `## Decisions`. This does not stop a run and does not park a child
|
|
212
|
-
| `none` | Nothing
|
|
213
|
-
| **The command could not answer** — non-zero exit, or **any** ⚠ warning above `Nothing to bundle.` | Stop and report, naming what it said. **A `none` printed after such a warning is not "nothing to bundle"** — the search was incomplete
|
|
177
|
+
| Answer | Do | Tier |
|
|
178
|
+
| ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
|
|
179
|
+
| `add_to_epic` | `pnpm josh epic --add <E> <new>` — add `--before <M>` / `--after <M>` when the relation carries an order | **A — no confirmation** |
|
|
180
|
+
| `create_epic` | `pnpm josh epic "<title>" <new> <other> [--ordered]` | **A — no confirmation** |
|
|
181
|
+
| `ask` — candidates spread across two or more epics | **Choose the epic you recommend, run its write command, and record the decision** — what was taken, what was rejected, why — on both the new Issue and that epic's `## Decisions`. This does not stop a run and does not park a child; stop only where the two epics are genuinely too close to separate | **A — no confirmation** |
|
|
182
|
+
| `none` | Nothing | — |
|
|
183
|
+
| **The command could not answer** — non-zero exit, or **any** ⚠ warning above `Nothing to bundle.` | Stop and report, naming what it said. **A `none` printed after such a warning is not "nothing to bundle"** — the search was incomplete | — |
|
|
214
184
|
|
|
215
185
|
- **A confirmed High is never deferred.** A real defect does not ship because a round counter ran out,
|
|
216
186
|
so a standing High blocks the merge — never fixed-in-place, filed, or dropped as one of the three
|
package/prompts/testing-guide.md
CHANGED
|
@@ -22,7 +22,8 @@ Req N: ...
|
|
|
22
22
|
### Rules
|
|
23
23
|
|
|
24
24
|
- **Every user-facing behavior change** (UI interaction, keyboard shortcut, visible state change) must have a corresponding E2E test.
|
|
25
|
-
- **Every logic/utility change** (pure functions, filters, transforms) must have a unit test.
|
|
25
|
+
- **Every logic/utility change** (pure functions, filters, transforms) must have a unit test. A bug fix takes a regression test; a refactor takes tests pinning the existing behavior BEFORE the change (`prompts/refactoring.md`).
|
|
26
|
+
- **Non-runtime updates (pre-approved manual-only exception):** a change touching no executable runtime code path — docs, non-executable config, editor/IDE files, cosmetic asset swaps with no selector/path change — may be verified by hand; declare it in Step 0 with the reason. The mechanically exempt (`*.md`, `.editorconfig`, `.idea/**`, `.vscode/**`, `prompts/**`) need no declaration. A runtime change whose test is infeasible needs the reason and user approval.
|
|
26
27
|
- **basic で kit がテストできない言語だけの変更:** `package.json` に `josh.profile: basic`(改名前の `static` も同じ)が記録され、変更が HTML/CSS や Lua などのソースだけなら自動テストは不要。HTML/CSS はブラウザーで対象ページを開き、想定する画面幅でレイアウト・リンク・HTML 内の操作を確認する。そのほかのソースは変更した処理を実際に動かす。どちらも結果を記録する。同じ変更に JavaScript または TypeScript のファイルが含まれれば自動テストを行う。
|
|
27
28
|
- If a test is genuinely infeasible (e.g., native OS date-picker popup cannot be driven by Playwright), write a comment in the test file explaining why and test the closest observable behavior instead (e.g., that the editor remains open after the `change` event fires).
|
|
28
29
|
- Do **not** report a requirement as done if its test is missing.
|
|
@@ -59,7 +60,7 @@ There is exactly **one** convention for every test file — never choose between
|
|
|
59
60
|
- **The `.svelte.` infix is required for component/browser tests and must be preserved.** `*.svelte.test.ts` routes the file to the **browser/component** vitest project (`include: src/**/*.svelte.{test,spec}.{js,ts}`); plain `*.test.ts` routes to the **node/unit** project (`include: src/**/*.{test,spec}.{js,ts}`). Renaming `Foo.svelte.test.ts` → `Foo.test.ts` silently moves it to the wrong project — do not drop the `.svelte.` infix.
|
|
60
61
|
- **Colocate every test next to the code it exercises.** A top-level `tests/` directory is **not used** — place `foo.test.ts` beside `foo.ts`, and E2E specs under the relevant `src/routes/**` path. The top-level `tests/` ban is **lint-enforced** by the same rule (`eslint/rules/test-filename.js`). Note: `playwright.config.ts` discovers E2E via `testMatch: '**/*.e2e.{ts,js}'`, so an `*.e2e.ts` placed outside `src/routes/**` would still run — that specific `src/routes/**` placement is enforced by **convention/review, not config**, and must be upheld manually.
|
|
61
62
|
|
|
62
|
-
> **Enforcement:** the `*.spec.ts` ban and the top-level `tests/` ban are enforced by the test-filename rule (`eslint/rules/test-filename.js`), wired into the shared `eslint/base.js`, so they fail loudly in `josh lint` for kit itself, for every consumer of that base config, and for every AI tool
|
|
63
|
+
> **Enforcement:** the `*.spec.ts` ban and the top-level `tests/` ban are enforced by the test-filename rule (`eslint/rules/test-filename.js`), wired into the shared `eslint/base.js`, so they fail loudly in `josh lint` for kit itself, for every consumer of that base config, and for every AI tool (`docs/maintainers/testing-guide-history.md`).
|
|
63
64
|
|
|
64
65
|
---
|
|
65
66
|
|
|
@@ -177,19 +178,8 @@ test('returns expected values in sequence', () => {
|
|
|
177
178
|
|
|
178
179
|
## 6. Closing the E2E gate without a human run
|
|
179
180
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
replacement is **not** a relaxation — nothing that a red local run stopped is allowed through.
|
|
183
|
-
|
|
184
|
-
### Why it existed, and why it no longer does
|
|
185
|
-
|
|
186
|
-
The reason was never that a human reads E2E output better. It was that `followup` waited 180
|
|
187
|
-
seconds for CI, which no suite containing E2E can finish in — so the command exited red beside a CI
|
|
188
|
-
that was still running, and the only reliable E2E signal left was a person's terminal. That budget
|
|
189
|
-
is now derived from the distributed `templates/workflows/ci.yml` rather than picked as a round
|
|
190
|
-
number: 1920 seconds, the longest run its `needs` graph permits plus runner-queue headroom, with a
|
|
191
|
-
unit test that fails if a declared job budget outgrows it (joshuafolkken/kit#851). CI can now be
|
|
192
|
-
waited on, so it can be relied on.
|
|
181
|
+
No human E2E run closes the gate, and nothing a red local run stopped is let through
|
|
182
|
+
(`docs/maintainers/testing-guide-history.md`).
|
|
193
183
|
|
|
194
184
|
### Which result closes the gate
|
|
195
185
|
|
|
@@ -219,60 +209,21 @@ a **defined** outcome, not an accident: a project with no E2E suite has nothing
|
|
|
219
209
|
read, and the run says so out loud. **A skip you did not see printed is not one** — it is an unread
|
|
220
210
|
result, and it falls under the previous paragraph.
|
|
221
211
|
|
|
222
|
-
**CI decides the same question
|
|
212
|
+
**CI decides the same question by the same rule.** The `e2e-detect` job in
|
|
223
213
|
`templates/workflows/ci.yml` enables the `e2e` job when any `*.e2e.{ts,js}` exists outside
|
|
224
|
-
`node_modules` — the guard's own glob
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
"no E2E" in CI while `josh test:e2e` ran it locally: the job's `if:` went false, GitHub recorded it
|
|
229
|
-
as `skipped`, the rollup parser counted the skip as passing exactly as it does for every other
|
|
230
|
-
conditional job, and the first row of the table above closed the gate on a suite nobody ran.
|
|
231
|
-
**Where the specs live was a precondition on the CI signal; it is now a property of the workflow.**
|
|
232
|
-
The agreement is executed rather than described — `scripts/ci/ci-yml-e2e-detect.test.ts` runs the
|
|
233
|
-
workflow's own script against each layout and compares its verdict to the guard's, and asserts over
|
|
234
|
-
the whole matrix that no layout containing specs can yield `enabled=false`. § 1's placement
|
|
235
|
-
convention still stands on its own merits (`eslint/rules/test-filename.js` bans a top-level
|
|
236
|
-
`tests/` directory outright), but the CI signal no longer depends on following it.
|
|
237
|
-
|
|
238
|
-
**One difference from the guard remains, and it is deliberate and one-sided.** The guard also skips
|
|
239
|
-
when `@playwright/test` is not installed, which is what keeps that optional peer optional for the
|
|
240
|
-
pre-push hook; CI enables the job on the spec alone, so a spec that cannot run ends red rather than
|
|
241
|
-
silently green. Enabling more than the guard costs a failed job, enabling less costs a merge — so
|
|
242
|
-
the difference may widen in that direction and never narrow.
|
|
214
|
+
`node_modules` — the guard's own glob — whatever the config's filename;
|
|
215
|
+
`scripts/ci/ci-yml-e2e-detect.test.ts` pins the agreement. CI enables the job on the spec alone, even
|
|
216
|
+
where the guard skips for a missing `@playwright/test`, so a spec that cannot run ends red rather than
|
|
217
|
+
silently green.
|
|
243
218
|
|
|
244
219
|
### The gate is not weakened
|
|
245
220
|
|
|
246
|
-
Two properties carry the verification the human step used to carry, and both are asserted by tests
|
|
247
|
-
rather than left to prose:
|
|
248
|
-
|
|
249
221
|
- **A non-passing check keeps the merge closed.** `evaluate_pr_state` returns `success` only when
|
|
250
|
-
GitHub reports the pull request `CLEAN` **and** every required check passes
|
|
251
|
-
|
|
252
|
-
the
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
**`E2E` stays off the required-check list, and that is a decision rather than an oversight**
|
|
259
|
-
(joshuafolkken/kit#991). A required check is satisfied by a _skipped_ job — the rollup parser maps
|
|
260
|
-
`SKIPPED` to `pass` — so requiring `E2E` would have passed on exactly the skipped job described
|
|
261
|
-
above, which is the failure it looks like it would have caught. It adds nothing to the failure path
|
|
262
|
-
either: a red E2E already ends the wait on the poll that sees it. What it would add is a cost —
|
|
263
|
-
a repository whose CI reports no `E2E` context at all resolves the required entry to `missing`,
|
|
264
|
-
which is never `pass`, so the evaluation stays `pending` until the 32-minute wait times out with
|
|
265
|
-
nothing actually wrong. What guarantees the job runs is the detection rule above, not the required
|
|
266
|
-
list. A project that wants it required opts in through `JOSH_REQUIRED_CHECKS`, which
|
|
267
|
-
`scripts/gh/git-pr-checks-eval.test.ts` covers; the decision itself — `E2E` off the default list,
|
|
268
|
-
and a repository reporting no `E2E` check still mergeable — is pinned in
|
|
269
|
-
`scripts/gh/git-pr-checks-e2e-gate.test.ts`.
|
|
270
|
-
|
|
271
|
-
**A red E2E is reported as soon as it goes red, and by name.** `E2E` is not on the required list, so
|
|
272
|
-
until joshuafolkken/kit#990 nothing ended the wait when it failed: the rollup read as pending,
|
|
273
|
-
`followup` ran out its 32-minute budget, and the run ended in `Timed out while waiting for PR checks
|
|
274
|
-
to complete.` — a message naming neither the job nor the cause. Any failing check now ends the wait
|
|
275
|
-
on the poll that sees it, and the error names it (`PR checks failed (failed checks: E2E).`).
|
|
276
|
-
The poll loop keeps CodeRabbit exempt under the temporary kit#753 policy. **This is a change to how fast the gate
|
|
277
|
-
reports, never to what it lets through** — no failing check gained a path to `success`, which
|
|
278
|
-
`scripts/gh/git-pr-checks-e2e-gate.test.ts` continues to assert.
|
|
222
|
+
GitHub reports the pull request `CLEAN` **and** every required check passes; the only exception is a
|
|
223
|
+
non-passing set made entirely of CodeRabbit checks (the temporary kit#753 policy). A failing check
|
|
224
|
+
ends the wait on the poll that sees it, by name (`PR checks failed (failed checks: E2E).`).
|
|
225
|
+
- **Filtering, narrowing or reinterpreting E2E output to get past it is a workaround** —
|
|
226
|
+
`CLAUDE.md` → "Cross-package problems"; reporting the filtered result honestly does not make it
|
|
227
|
+
compliant.
|
|
228
|
+
- **`E2E` stays off the default required-check list**; a project opts in through
|
|
229
|
+
`JOSH_REQUIRED_CHECKS`. `scripts/gh/git-pr-checks-e2e-gate.test.ts` pins both properties.
|
|
@@ -74,7 +74,7 @@ const PROVIDER_PROFILES = { anthropic: DEFAULT_PROFILES, openai: OPENAI_PROFILES
|
|
|
74
74
|
|
|
75
75
|
// **Effort as a function of the run phase, not the role alone** (joshuafolkken/kit#2382). For the first
|
|
76
76
|
// merge only the mechanical ship/bookkeeping region is lowered: the pre-gate resume drives the gate,
|
|
77
|
-
// commit, PR and merge, applying fixes the gate has already named — work
|
|
77
|
+
// commit, PR and merge, applying fixes the gate has already named — work delegation.md calls the opposite
|
|
78
78
|
// of judgement. The design-judgment implementation phases keep the role default, as does any role/phase
|
|
79
79
|
// with no entry, and the review's own judgement is the reviewer role's (`high`), untouched by lowering
|
|
80
80
|
// the worker here. The investigation phase — highest output density — is left for a later merge, once
|
|
@@ -33,22 +33,13 @@ const UNMATCHED_EVENTS: ReadonlySet<string> = new Set(['UserPromptSubmit'])
|
|
|
33
33
|
const JSON_INDENT = '\t'
|
|
34
34
|
|
|
35
35
|
function adapter_command(mode: string): string {
|
|
36
|
-
return hook_launch.
|
|
37
|
-
`codex-hook-adapter.js ${mode}`,
|
|
38
|
-
`pnpm exec tsx scripts/hooks/codex-hook-adapter.ts ${mode}`,
|
|
39
|
-
)
|
|
36
|
+
return hook_launch.hook_launch_command(`codex-hook-adapter ${mode}`)
|
|
40
37
|
}
|
|
41
38
|
|
|
42
39
|
// Each Claude-side guard command the adapter stands in for, keyed by that command verbatim.
|
|
43
40
|
const ADAPTED_COMMANDS: ReadonlyMap<string, string> = new Map([
|
|
44
|
-
[
|
|
45
|
-
|
|
46
|
-
adapter_command('pretool'),
|
|
47
|
-
],
|
|
48
|
-
[
|
|
49
|
-
hook_launch.hook_launch_command('format-edited.js', 'format:edited'),
|
|
50
|
-
adapter_command('posttool'),
|
|
51
|
-
],
|
|
41
|
+
[hook_launch.hook_launch_command('pretool-guard'), adapter_command('pretool')],
|
|
42
|
+
[hook_launch.hook_launch_command('format-edited'), adapter_command('posttool')],
|
|
52
43
|
])
|
|
53
44
|
|
|
54
45
|
function codex_handler(handler: HookHandler): HookHandler {
|
|
@@ -59,7 +59,7 @@ type BudgetVerdict =
|
|
|
59
59
|
typeof RUN_VERDICT | typeof WATCH_VERDICT | typeof STOP_VERDICT | typeof TRIAGE_VERDICT
|
|
60
60
|
|
|
61
61
|
// What `backlog:next` answered, in the words this decision needs. The mapping is mechanical and is
|
|
62
|
-
// written once, in `
|
|
62
|
+
// written once, in `docs/josh-commands-automation.md` → "`josh backlog:offer`": numbers are `candidates`, `wait` is `blocked` while
|
|
63
63
|
// this run has children in flight and `exhausted` when it does not, `none` is `exhausted`, `stop` is
|
|
64
64
|
// `parked`, `triage` is `untriaged`, and `error` or a failed listing is `unreadable`.
|
|
65
65
|
const ANSWERS = ['blocked', 'candidates', 'exhausted', 'parked', 'unreadable', 'untriaged'] as const
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { setTimeout as sleep } from 'node:timers/promises'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
|
-
import { CONTEXT_CUT_THRESHOLD } from '#scripts/cost-runtime/context-cut-threshold'
|
|
5
4
|
import { hook_decision } from '#scripts/josh/hook-decision'
|
|
6
5
|
import { josh_command } from '#scripts/josh/josh-run'
|
|
7
6
|
import { lane_await, type AwaitState } from '#scripts/lane/lane-await'
|
|
@@ -217,7 +216,8 @@ function merge_token(out: string, code: number): string {
|
|
|
217
216
|
}
|
|
218
217
|
|
|
219
218
|
// The child's transcript, where its lane recorded one, so `run:merge` can tell an API outage from a
|
|
220
|
-
// failure exactly as it does for the parent that passes `--output` by hand.
|
|
219
|
+
// failure exactly as it does for the parent that passes `--output` by hand. No hand-off threshold: the
|
|
220
|
+
// supervisor runs this loop, and a process has no context to cut (joshuafolkken/kit#3156).
|
|
221
221
|
async function merge(issue: string, owner: string): Promise<MergeResult> {
|
|
222
222
|
await backlog_drive_owner.assert_current(owner)
|
|
223
223
|
const lane = await lane_registry.find_open_lane(issue)
|
|
@@ -225,7 +225,7 @@ async function merge(issue: string, owner: string): Promise<MergeResult> {
|
|
|
225
225
|
child: issue,
|
|
226
226
|
epic: undefined,
|
|
227
227
|
repo: undefined,
|
|
228
|
-
over:
|
|
228
|
+
over: undefined,
|
|
229
229
|
owner: run_carry.owner_of(Number(owner)),
|
|
230
230
|
output: lane?.output,
|
|
231
231
|
})
|
|
@@ -381,6 +381,7 @@ async function main(argv: ReadonlyArray<string>): Promise<void> {
|
|
|
381
381
|
const backlog_drive_cli = {
|
|
382
382
|
end_line,
|
|
383
383
|
finish: backlog_drive_finish.finish,
|
|
384
|
+
merge,
|
|
384
385
|
merge_token,
|
|
385
386
|
offer_argv: backlog_drive_offer_argv.offer_argv,
|
|
386
387
|
parse,
|
|
@@ -3,11 +3,20 @@ import type { DriveState } from './backlog-drive'
|
|
|
3
3
|
|
|
4
4
|
const LIST_SEPARATOR = ','
|
|
5
5
|
|
|
6
|
+
// **A child in flight is never offered** (joshuafolkken/kit#3156). A lane child merges its own PR and
|
|
7
|
+
// drops `in-progress` before GitHub closes the issue and before the loop collects it, so `backlog:next`
|
|
8
|
+
// can offer it again; launching it then is refused by its still-open lane and handed to an AI session.
|
|
9
|
+
function excluded(state: DriveState): ReadonlyArray<string> {
|
|
10
|
+
return [...new Set([...state.exclude, ...state.in_flight])]
|
|
11
|
+
}
|
|
12
|
+
|
|
6
13
|
function offer_argv(
|
|
7
14
|
state: DriveState,
|
|
8
15
|
carry: RunCarry,
|
|
9
16
|
forwarded: ReadonlyArray<string>,
|
|
10
17
|
): ReadonlyArray<string> {
|
|
18
|
+
const exclude = excluded(state)
|
|
19
|
+
|
|
11
20
|
return [
|
|
12
21
|
'backlog:offer',
|
|
13
22
|
'--json',
|
|
@@ -21,7 +30,7 @@ function offer_argv(
|
|
|
21
30
|
String(state.in_flight.length),
|
|
22
31
|
'--retries',
|
|
23
32
|
String(state.retries),
|
|
24
|
-
...(
|
|
33
|
+
...(exclude.length === 0 ? [] : ['--exclude', exclude.join(LIST_SEPARATOR)]),
|
|
25
34
|
...forwarded,
|
|
26
35
|
]
|
|
27
36
|
}
|
|
@@ -16,7 +16,8 @@ import { PROJECT_ROOT } from '#scripts/init/init-paths'
|
|
|
16
16
|
import { issue_citation } from '#scripts/rules/issue-citation'
|
|
17
17
|
import { backlog_defect_priority } from './backlog-defect-priority'
|
|
18
18
|
import { backlog_pool } from './backlog-pool'
|
|
19
|
-
import { backlog_rank } from './backlog-rank'
|
|
19
|
+
import { backlog_rank, type GateScope } from './backlog-rank'
|
|
20
|
+
import { backlog_restructure } from './backlog-restructure'
|
|
20
21
|
|
|
21
22
|
// `josh backlog:next` — what the whole opted-in backlog may run next (joshuafolkken/kit#1630).
|
|
22
23
|
//
|
|
@@ -278,7 +279,7 @@ function report_retry(context: PoolContext): number {
|
|
|
278
279
|
return SUCCESS_EXIT_CODE
|
|
279
280
|
}
|
|
280
281
|
|
|
281
|
-
// The rows the offer's cap bounds — the standalone half
|
|
282
|
+
// The rows the offer's cap bounds — the standalone half.
|
|
282
283
|
function standalone_keys(context: PoolContext): ReadonlySet<string> {
|
|
283
284
|
return backlog_pool.standalone_keys(context.opted_in.issues, {
|
|
284
285
|
tracked: epic_index.withheld_children(context.tracking.index, context.opted_in.issues),
|
|
@@ -287,6 +288,16 @@ function standalone_keys(context: PoolContext): ReadonlySet<string> {
|
|
|
287
288
|
})
|
|
288
289
|
}
|
|
289
290
|
|
|
291
|
+
// The running lanes still carry the pickup label, so the opted-in listing holds their bodies too —
|
|
292
|
+
// which is what lets a candidate be held back from a restructure a running lane already makes
|
|
293
|
+
// (joshuafolkken/kit#3221). `backlog:plan --waves` reads the same scope, so its first wave is this offer.
|
|
294
|
+
function gate_scope(context: PoolContext): GateScope {
|
|
295
|
+
return {
|
|
296
|
+
standalone: standalone_keys(context),
|
|
297
|
+
declared: backlog_restructure.declared_of(context.opted_in.issues),
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
290
301
|
// The `run:solo` gate is applied here rather than in `resolve`, because `backlog:plan` shares
|
|
291
302
|
// `resolve` and a plan made before the run lists everything (joshuafolkken/kit#2776). The holders are
|
|
292
303
|
// read only on a `run` answer — the one answer the gate can change. The cap follows the gate
|
|
@@ -302,7 +313,7 @@ async function gate_solo(result: EpicNextResult, context: PoolContext): Promise<
|
|
|
302
313
|
|
|
303
314
|
if (notice !== undefined) console.error(notice)
|
|
304
315
|
|
|
305
|
-
const gated = backlog_rank.gate(result, read, repo,
|
|
316
|
+
const gated = backlog_rank.gate(result, read, repo, gate_scope(context))
|
|
306
317
|
|
|
307
318
|
if (gated.notice !== undefined) console.error(gated.notice)
|
|
308
319
|
|
|
@@ -418,7 +429,7 @@ const backlog_next = {
|
|
|
418
429
|
READ_FAILURES,
|
|
419
430
|
VERDICT_TOKENS,
|
|
420
431
|
resolve,
|
|
421
|
-
|
|
432
|
+
gate_scope,
|
|
422
433
|
context_of,
|
|
423
434
|
run,
|
|
424
435
|
}
|
|
@@ -5,15 +5,15 @@ import { backlog_next } from './backlog-next'
|
|
|
5
5
|
// answered — its stdout tokens and its exit code — into the one word `backlog:budget` is then asked
|
|
6
6
|
// with, plus the issue numbers to start and the consecutive-retry count carried to the next ask.
|
|
7
7
|
//
|
|
8
|
-
// **The mapping is `
|
|
9
|
-
//
|
|
10
|
-
// branches an agent
|
|
11
|
-
// children in flight and `exhausted` when it has none, and `retry` is `blocked` until the third
|
|
8
|
+
// **The mapping is `docs/josh-commands-automation.md` → "`josh backlog:offer`", held here so it cannot
|
|
9
|
+
// drift.** It fixes which `backlog:next` answer becomes which `backlog:budget` word, and it carries two
|
|
10
|
+
// context branches an agent once applied by hand every iteration: `wait` is `blocked` while this run
|
|
11
|
+
// has children in flight and `exhausted` when it has none, and `retry` is `blocked` until the third
|
|
12
12
|
// consecutive one, which is `unreadable`. Both are decided from a count here rather than from a
|
|
13
|
-
// judgement.
|
|
13
|
+
// judgement (joshuafolkken/kit#3175 dropped the prose copy from `backlogrun-steps.md`).
|
|
14
14
|
|
|
15
15
|
// Three consecutive `retry` answers end the run: below that a transport hiccup is re-asked, at it the
|
|
16
|
-
// outage is not a hiccup. `
|
|
16
|
+
// outage is not a hiccup. "`josh backlog:offer`" is the single source of the count.
|
|
17
17
|
const RETRY_LIMIT = 3
|
|
18
18
|
const FAILURE_EXIT_CODE = 1
|
|
19
19
|
const NO_RETRIES = 0
|
|
@@ -10,6 +10,7 @@ import { run_invocation } from '#scripts/run/run-invocation'
|
|
|
10
10
|
import { backlog_named } from './backlog-named'
|
|
11
11
|
import { backlog_next, type OptedIn } from './backlog-next'
|
|
12
12
|
import { backlog_plan, type NamedPlan, type PlanContext } from './backlog-plan'
|
|
13
|
+
import type { GateScope } from './backlog-rank'
|
|
13
14
|
import { backlog_scope } from './backlog-scope'
|
|
14
15
|
import { backlog_waves } from './backlog-waves'
|
|
15
16
|
|
|
@@ -104,9 +105,9 @@ interface Plan {
|
|
|
104
105
|
// past the listing cap. Carrying the *whole* index here instead would move that misreport rather
|
|
105
106
|
// than remove it: the sentence would name an epic that is withholding nothing.
|
|
106
107
|
tracked: ReadonlyMap<number, number>
|
|
107
|
-
// The rows the offer's cap bounds, so `--waves` cuts each
|
|
108
|
-
// (joshuafolkken/kit#2928).
|
|
109
|
-
|
|
108
|
+
// The rows the offer's cap bounds and the paths each issue restructures, so `--waves` cuts each
|
|
109
|
+
// wave where `backlog:next` does (joshuafolkken/kit#2928, joshuafolkken/kit#3221).
|
|
110
|
+
scope: GateScope
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
// The listing plus whether it was cut. Carried together because the rows alone cannot say whether
|
|
@@ -141,7 +142,7 @@ async function classify(
|
|
|
141
142
|
repo: context.repo,
|
|
142
143
|
exclude,
|
|
143
144
|
tracked: epic_index.withheld_children(context.tracking.index, context.opted_in.issues),
|
|
144
|
-
|
|
145
|
+
scope: backlog_next.gate_scope(context),
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
148
|
|
|
@@ -172,7 +173,7 @@ function print_plan(plan: Plan, listing: OpenListing, view: PlanView): void {
|
|
|
172
173
|
const context = context_of(plan, listing)
|
|
173
174
|
|
|
174
175
|
if (view.is_waves) {
|
|
175
|
-
console.info(backlog_waves.format_waves(plan.result, context, plan.
|
|
176
|
+
console.info(backlog_waves.format_waves(plan.result, context, plan.scope))
|
|
176
177
|
|
|
177
178
|
return
|
|
178
179
|
}
|
|
@@ -4,6 +4,7 @@ import { epic_rank } from '#scripts/epic/epic-rank'
|
|
|
4
4
|
import type { EpicNextResult } from '#scripts/epic/epic-report'
|
|
5
5
|
import { epic_solo, type SoloGate, type SoloSelection } from '#scripts/epic/epic-solo'
|
|
6
6
|
import { git_next_issues } from '#scripts/issue/git-next-issues'
|
|
7
|
+
import { backlog_restructure, type Declared } from './backlog-restructure'
|
|
7
8
|
|
|
8
9
|
// What the backlog offers, in the order it is decided (joshuafolkken/kit#2928): rank, then the
|
|
9
10
|
// `run:solo` gate, then the cap. `backlog:next` and `backlog:plan --waves` both call `select`, so the
|
|
@@ -78,29 +79,37 @@ interface OfferInput {
|
|
|
78
79
|
repo: string
|
|
79
80
|
// The keys of the standalone rows — the ones the cap bounds.
|
|
80
81
|
standalone: ReadonlySet<string>
|
|
82
|
+
// The paths each issue restructures; two candidates claiming one are not offered together
|
|
83
|
+
// (joshuafolkken/kit#3221). Absent, nothing is separated.
|
|
84
|
+
declared?: Declared
|
|
81
85
|
}
|
|
82
86
|
|
|
87
|
+
// The restructure separation runs before the cap, for the reason the gate does: the cap only ever
|
|
88
|
+
// removes what would otherwise be offered.
|
|
83
89
|
function select(input: OfferInput): SoloSelection {
|
|
84
90
|
const ranked = rank(input.candidates, input.pool)
|
|
91
|
+
const { read, repo } = input
|
|
92
|
+
const gated = epic_solo.select(ranked, read, repo)
|
|
93
|
+
const separated = backlog_restructure.separate(gated, input.declared ?? new Map(), { read, repo })
|
|
85
94
|
|
|
86
|
-
return cap(
|
|
95
|
+
return cap(separated, input.standalone)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface GateScope {
|
|
99
|
+
standalone: ReadonlySet<string>
|
|
100
|
+
declared: Declared
|
|
87
101
|
}
|
|
88
102
|
|
|
89
103
|
// `backlog:next`'s gate: `epic_solo.gate` with this selection in place of its bare one.
|
|
90
|
-
function gate(
|
|
91
|
-
result: EpicNextResult,
|
|
92
|
-
read: BusyRead,
|
|
93
|
-
repo: string,
|
|
94
|
-
standalone: ReadonlySet<string>,
|
|
95
|
-
): SoloGate {
|
|
104
|
+
function gate(result: EpicNextResult, read: BusyRead, repo: string, scope: GateScope): SoloGate {
|
|
96
105
|
const pool = everything_in(result)
|
|
97
106
|
|
|
98
107
|
return epic_solo.gate(result, read, repo, (candidates) =>
|
|
99
|
-
select({ candidates, pool, read, repo,
|
|
108
|
+
select({ candidates, pool, read, repo, ...scope }),
|
|
100
109
|
)
|
|
101
110
|
}
|
|
102
111
|
|
|
103
112
|
const backlog_rank = { everything_in, gate, rank_result, select }
|
|
104
113
|
|
|
105
114
|
export { backlog_rank }
|
|
106
|
-
export type { OfferInput }
|
|
115
|
+
export type { GateScope, OfferInput }
|