@joshuafolkken/kit 1.1166.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.
Files changed (61) hide show
  1. package/.claude/skills/workflow-commands/backlogrun-lanes.md +3 -3
  2. package/.claude/skills/workflow-commands/backlogrun-park.md +6 -7
  3. package/.claude/skills/workflow-commands/backlogrun-steps.md +3 -2
  4. package/.claude/skills/workflow-commands/pre-gate-cut.md +4 -4
  5. package/.github/workflows/ci.yml +15 -2
  6. package/dist/hooks/batch-guard.js +4 -4
  7. package/dist/hooks/{chunk-5KTCMCOT.js → chunk-32GQTL4F.js} +1 -1
  8. package/dist/hooks/{chunk-URJA6XY4.js → chunk-DO6NWWHU.js} +4 -4
  9. package/dist/hooks/{chunk-GU5MATVE.js → chunk-G6KRCKEC.js} +11 -19
  10. package/dist/hooks/{chunk-ZDZ555YH.js → chunk-GDS5N2KB.js} +88 -291
  11. package/dist/hooks/{chunk-GYNGP7HI.js → chunk-I4XSL4TT.js} +1 -1
  12. package/dist/hooks/{chunk-DYAVAM6S.js → chunk-S6X6WYKR.js} +324 -49
  13. package/dist/hooks/{chunk-ANJ2WGFD.js → chunk-UAGLLIGZ.js} +1 -1
  14. package/dist/hooks/{chunk-ENA7P5FB.js → chunk-VESKRQOF.js} +3 -3
  15. package/dist/hooks/{chunk-V2BTOZHT.js → chunk-VQLLNFNT.js} +3 -3
  16. package/dist/hooks/{chunk-A6P7PN7P.js → chunk-ZAL3NRGL.js} +2 -2
  17. package/dist/hooks/codex-hook-adapter.js +10 -10
  18. package/dist/hooks/duplicate-read-guard.js +5 -5
  19. package/dist/hooks/format-edited.js +2 -2
  20. package/dist/hooks/inputs.json +192 -192
  21. package/dist/hooks/investigation-guard.js +5 -5
  22. package/dist/hooks/pretool-guard.js +9 -9
  23. package/dist/hooks/stop-guard.js +18 -4
  24. package/dist/josh.js +4 -1
  25. package/package.json +1 -1
  26. package/scripts/backlog/backlog-next.ts +15 -4
  27. package/scripts/backlog/backlog-plan-cli.ts +6 -5
  28. package/scripts/backlog/backlog-rank.ts +18 -9
  29. package/scripts/backlog/backlog-restructure.ts +152 -0
  30. package/scripts/backlog/backlog-waves.ts +15 -17
  31. package/scripts/cost-runtime/context-cut-threshold.ts +1 -9
  32. package/scripts/cost-runtime/cost-verdict.ts +15 -28
  33. package/scripts/followup/git-followup-stages.ts +9 -0
  34. package/scripts/followup/git-pr-followup.ts +1 -1
  35. package/scripts/gh/git-gh-issue.ts +5 -3
  36. package/scripts/gh/git-gh-pr-read.ts +4 -0
  37. package/scripts/gh/git-pr-checks-eval.ts +8 -0
  38. package/scripts/gh/git-pr-checks.ts +11 -1
  39. package/scripts/git/constants.ts +10 -0
  40. package/scripts/git/main-merge.ts +57 -21
  41. package/scripts/git/schemas.ts +2 -0
  42. package/scripts/josh/josh-commands-development.ts +1 -1
  43. package/scripts/lane/lane-launch-cli.ts +21 -3
  44. package/scripts/notify/telegram-notify.ts +26 -1
  45. package/scripts/rules/pre-gate-cut.ts +2 -2
  46. package/scripts/run/run-carry.ts +11 -5
  47. package/scripts/run/run-cut-cli.ts +1 -1
  48. package/scripts/run/run-cut-report.ts +2 -2
  49. package/scripts/run/run-ship-cli.ts +27 -6
  50. package/scripts/run/run-ship-next.ts +13 -0
  51. package/scripts/run/run-ship-pre-detach.ts +66 -0
  52. package/scripts/run/run-ship-return.ts +7 -4
  53. package/scripts/run/run-ship-stage.ts +13 -0
  54. package/scripts/run/run-ship-steps.ts +10 -2
  55. package/scripts/run/run-ship-sync.ts +114 -0
  56. package/scripts/run/run-ship.ts +6 -0
  57. package/scripts/run/run-wake-cli.ts +8 -1
  58. package/scripts/run/run-wake-driver.ts +42 -11
  59. package/scripts/test/test-red-logic.ts +17 -2
  60. package/scripts/test/test-red.ts +8 -7
  61. package/scripts/time-runtime/time-transcript-line.ts +27 -1
@@ -218,8 +218,8 @@ conflict.
218
218
  | When | The lane | Why |
219
219
  | --- | --- | --- |
220
220
  | The child **merged** | `pnpm josh lane:close <N>` | `followup` released the hold and the branch is on `main`; nothing in that tree is wanted |
221
- | The child was **parked before its commit** | `git -C <dir> stash push -u -m "backlogrun: parked #<N>"`, record it on the Issue, then `pnpm josh lane:close <N>` | The stash is a repository-level ref, so it outlives the work tree — and `lane:close` is a **forced** removal that would otherwise take the work. `epic:next` counts a parked child's lane as released, so a lane left open holds a seat the count believes is free |
222
- | The child was **parked after its commit and push** | **Left open**, its directory and held seat recorded on the Issue | The stash step is a **no-op** (tree clean because committed). What `lane:close` would take is the **local branch**, which is the resume path |
221
+ | The child was **parked before its commit** | **Left open**, its uncommitted work in place, its directory and held seat recorded on the Issue | The stash stack is shared by every work tree, so a parallel lane can pop another's entry — the tree itself is the safe place for the work. `lane:close` is a **forced** removal that would take it. `pnpm josh lane:launch <N>` dispatches the released child into the kept lane rather than refusing it as `already-open` — only a lane a child was already dispatched into; a failed-install lane is still refused |
222
+ | The child was **parked after its commit and push** | **Left open**, its directory and held seat recorded on the Issue | The tree is clean because committed. What `lane:close` would take is the **local branch**, which is the resume path |
223
223
  | The child hit a **merge conflict** | **Left open** — the resolution happens in it | The child resolves in place (`backlogrun-recovery.md` → "Conflicts are not predicted"). A park under that section's four conditions takes the row above |
224
224
  | The child stopped on **`needs-human-review`** | **Left open and untouched** | The uncommitted work *is* the artifact a person has to look at. Name the lane directory in the stop report and in the Telegram |
225
225
  | The child **failed** | Whichever of the two parked rows applies, plus the consecutive-failure count | Same reasoning; only the counter differs. **A merge conflict is not this row** — it takes the row above, and it is not counted |
@@ -229,7 +229,7 @@ conflict.
229
229
  **A lane closed after a push is reopened by `pnpm josh lane:open <N>`**, which attaches to a local
230
230
  `<N>-lane` or creates one from `origin/<N>-lane`, saying on standard error which it reused. **The branch
231
231
  still has to have reached the remote for that to hold** — a child parked before its commit has nothing
232
- on `origin`, which is why that row stashes first and the after-push row does not. Rationale:
232
+ on `origin`, which is why that row keeps its lane open rather than closing it. Rationale:
233
233
  `docs/maintainers/backlogrun-lanes-rationale.md` → "Why a committed child's lane is kept".
234
234
 
235
235
  **Which row a child takes is decided by what `followup` printed, not by reading the situation.** **What
@@ -75,12 +75,10 @@ pnpm josh issue:comment <N> --body-file <path> # what needs deciding, and the
75
75
  `needs-decision` precedence over `in-progress`, so the next child is offered normally. Then return to
76
76
  step 1; the other children are unaffected unless they depend on this one.
77
77
 
78
- **What happens to a parked child's lane depends on whether it had committed, and the two answers are
79
- opposite.** **Parked before its commit**, the lane is stashed and closed:
80
- `git -C <dir> stash push -u -m "backlogrun: parked #<N>"`, the stash recorded on the Issue, then
81
- `pnpm josh lane:close <N>` (`epic:next` counts a parked child's lane as released, and `lane:close`
82
- removes the work tree by force). **Parked after its commit and push**, the lane is *kept*: nothing to
83
- stash, and closing would delete the local branch the resume needs. **A lost merge race is not one of
78
+ **A parked child's lane is kept, whether or not it had committed.** **Parked before its commit**, the
79
+ uncommitted work stays in the lane's tree — never in the stash, which every work tree shares — and
80
+ `lane:close` would remove it by force. **Parked after its commit and push**, closing would delete the
81
+ local branch the resume needs. **A lost merge race is not one of
84
82
  these rows** — it resolves in its lane (`backlogrun-recovery.md` → "Conflicts are not predicted"), and parks only under that
85
83
  section's four conditions, which take the after-commit row. Both rows and the `pnpm josh run:release
86
84
  <N>` every parked ending owes are "What happens to a lane" above, the single source.
@@ -101,7 +99,8 @@ gh api -X DELETE repos/{owner}/{repo}/issues/<N>/labels/needs-decision 2>/dev/nu
101
99
 
102
100
  **The released child goes back to a lane, never into the parent's own context.** A parent that is
103
101
  *already* running, and clears a label mid-run because a person just answered, dispatches it the same way
104
- rather than picking up its diff itself — `pnpm josh lane:open <N>` then `pnpm josh lane:dispatch <N>`. A
102
+ rather than picking up its diff itself — `pnpm josh lane:launch <N>`, which dispatches into the lane the
103
+ park kept, or opens one where none is. A
105
104
  parent that implements the released child inline is the failure the orchestrator rule is written against:
106
105
  "The parent orchestrates and never implements a child in its own context" above.
107
106
 
@@ -308,8 +308,9 @@ Rationale: `docs/maintainers/backlogrun-steps-rationale.md` → "Why the plan an
308
308
  whose head is `pnpm josh backlog:offer`: the answer-to-budget mapping, the retry count, the `--exclude`
309
309
  feedback and the lane count are its own (`docs/josh-commands-automation.md` → "`josh backlog:offer`"
310
310
  and "`josh backlog:drive`"). **An AI session reaches this section only when the driver hands a branch
311
- back** — the `Driver result:` line names it, and the session acts on it, then hands the loop back with
312
- `pnpm josh run:carry --cut --owner "$PPID"`:
311
+ back** — the `Driver result:` line names it, and its `Next:` line is the order: claim the record with
312
+ `pnpm josh run:carry --resume "<invocation>" --owner "$PPID"`, act on the branch, then hand the loop
313
+ back with `pnpm josh run:carry --cut --owner "$PPID"`:
313
314
 
314
315
  | Handed back | What the session judges |
315
316
  | --- | --- |
@@ -58,7 +58,7 @@ pnpm josh run:cut <N>
58
58
  - `cut` (0) — record written and handed to the lane's launch owner (OpenAI supervisor starts the
59
59
  successor after this process exits; Anthropic relaunches directly). **End the turn immediately** —
60
60
  the fresh process owns the run.
61
- - `under-threshold` (0) — the recent-window per-request cost is under the shared `CONTEXT_CUT_THRESHOLD`,
61
+ - `under-threshold` (0) — the newest request's billed input is under the shared `CONTEXT_CUT_THRESHOLD`,
62
62
  so nothing was cut; **continue to the gate.**
63
63
  - `not-a-lane` (0) — no open lane for this issue, so not a dispatched child. **Continue to the gate.**
64
64
  - `unready` (1) — the tree is clean or on the default branch, nothing to carry. Continue to the gate.
@@ -79,7 +79,7 @@ carried, handing back the command above. Rationale:
79
79
  `<lane root>/<issue number>` — **and** `JOSH_LANE_CHILD` names that same issue. An interactive
80
80
  `fullrun` carries no mark and runs its gate untouched. `run:cut` uses an open-lane lookup instead, so
81
81
  the guard and the command need not agree byte for byte.
82
- - **It fires only when the recent-window context warrants a cut**. The guard
82
+ - **It fires only when the current context warrants a cut**. The guard
83
83
  reads the same `cost_cli.session_verdict` `run:cut` reads, so an `under`-threshold lane is let through
84
84
  to the gate rather than refused.
85
85
  - **It is silent once the cut is carried**, so the resumed process goes straight to the gate.
@@ -180,8 +180,8 @@ after the fact by `pnpm josh run:ending <N> --output <path>` (joshuafolkken/kit#
180
180
 
181
181
  ## The threshold
182
182
 
183
- `CONTEXT_CUT_THRESHOLD` and the recent-window `cost_verdict.per_request_cost` (the last
184
- `RECENT_REQUEST_WINDOW` requests) are computed by `scripts/cost-runtime/context-cut-payback.ts`, their
183
+ `CONTEXT_CUT_THRESHOLD` and `cost_verdict.per_request_cost` (the newest request's billed input)
184
+ are computed by `scripts/cost-runtime/context-cut-payback.ts`, their
185
185
  single source; both cuts and the parent hand-off read the same verdict, and an unmeasurable session is
186
186
  always cut. Rationale: `docs/maintainers/pre-gate-cut-rationale.md` → "Why the threshold is a
187
187
  break-even". Both cuts end the turn **before** the gate, in the same act that starts the successor, so
@@ -333,10 +333,21 @@ jobs:
333
333
  # No ESLint cache here — nothing in this job lints — and no `--maxWorkers`: `gate-plan.ts` leaves
334
334
  # any machine below its measured 11 cores uncapped, so vitest sizes its own pool from the runner
335
335
  # exactly as it did inside the gate step.
336
+ # **Sharded across runners, because one runner is already saturated** (joshuafolkken/kit#3220).
337
+ # Alone on a 4-core runner vitest takes every core, so the suite can only get faster by spreading
338
+ # its files over more machines. `fail-fast: false` keeps a red shard from cancelling the others,
339
+ # so one run reports every failing file. The `Checks` aggregate below reads `needs.unit.result`,
340
+ # which for a matrix is `success` only when every shard succeeded — no change there is needed.
341
+ # Only kit shards: the distributed template runs the unit suite inside its single `checks` job,
342
+ # where a small consumer suite would pay the duplicated setup and could leave a shard with no file.
336
343
  unit:
337
- name: Unit Tests
344
+ name: Unit Tests (${{ matrix.shard }}/3)
338
345
  needs: reuse-pr-checks
339
346
  if: ${{ !cancelled() && needs.reuse-pr-checks.outputs.reuse != 'true' }}
347
+ strategy:
348
+ fail-fast: false
349
+ matrix:
350
+ shard: [1, 2, 3]
340
351
  runs-on: ubuntu-latest
341
352
  timeout-minutes: 8
342
353
  permissions:
@@ -366,9 +377,11 @@ jobs:
366
377
  # `scripts/ci/ci-yml-unit-job.test.ts` pins the command; the guard itself is
367
378
  # `scripts/test/test-unit-guard.ts`.
368
379
  - name: Run unit tests
369
- run: pnpm josh test:unit
380
+ run: pnpm josh test:unit --shard=${{ matrix.shard }}/3
370
381
 
382
+ # One shard is enough: the check does not depend on which test files ran beside it.
371
383
  - name: Verify optional ESLint consumer install
384
+ if: matrix.shard == 1
372
385
  run: pnpm exec tsx scripts/package/verify-optional-eslint-install.ts
373
386
 
374
387
  # **The required status check. `Checks` is a name the branch ruleset already requires, and this job
@@ -9,10 +9,10 @@ import {
9
9
  load_environment_file,
10
10
  notice_refusal_path,
11
11
  refusal_path
12
- } from "./chunk-V2BTOZHT.js";
13
- import "./chunk-GYNGP7HI.js";
14
- import "./chunk-5KTCMCOT.js";
15
- import "./chunk-DYAVAM6S.js";
12
+ } from "./chunk-VQLLNFNT.js";
13
+ import "./chunk-I4XSL4TT.js";
14
+ import "./chunk-32GQTL4F.js";
15
+ import "./chunk-S6X6WYKR.js";
16
16
  import "./chunk-WPALEIRX.js";
17
17
  import "./chunk-RKUAD3I6.js";
18
18
  export {
@@ -3,7 +3,7 @@ import {
3
3
  stamp_file,
4
4
  time_density_hook,
5
5
  time_hook_transcript
6
- } from "./chunk-DYAVAM6S.js";
6
+ } from "./chunk-S6X6WYKR.js";
7
7
  import {
8
8
  josh_environment_file
9
9
  } from "./chunk-WPALEIRX.js";
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  investigation_reads
3
- } from "./chunk-A6P7PN7P.js";
3
+ } from "./chunk-ZAL3NRGL.js";
4
4
  import {
5
5
  lane_guard_policy
6
- } from "./chunk-GYNGP7HI.js";
6
+ } from "./chunk-I4XSL4TT.js";
7
7
  import {
8
8
  hook_decision
9
- } from "./chunk-5KTCMCOT.js";
9
+ } from "./chunk-32GQTL4F.js";
10
10
  import {
11
11
  json_value,
12
12
  time_spans
13
- } from "./chunk-DYAVAM6S.js";
13
+ } from "./chunk-S6X6WYKR.js";
14
14
 
15
15
  // scripts/delegation/duplicate-read-guard.ts
16
16
  import { statSync } from "node:fs";
@@ -5,7 +5,6 @@ import {
5
5
  NEEDS_DECISION_LABEL,
6
6
  NEEDS_HUMAN_REVIEW_LABEL,
7
7
  PORCELAIN_FLAG,
8
- RECENT_REQUEST_WINDOW,
9
8
  agent_role_profile,
10
9
  bash_triggers,
11
10
  cli_flags,
@@ -22,7 +21,6 @@ import {
22
21
  lane_background,
23
22
  lane_park,
24
23
  lane_registry,
25
- open_issue_schema,
26
24
  own_session,
27
25
  parse_json_object_safe,
28
26
  read_json_listing,
@@ -36,30 +34,30 @@ import {
36
34
  run_tail,
37
35
  shell_segments,
38
36
  tail_commands
39
- } from "./chunk-ZDZ555YH.js";
37
+ } from "./chunk-GDS5N2KB.js";
40
38
  import {
41
39
  find_local_bin_upwards
42
40
  } from "./chunk-3V37IID6.js";
43
41
  import {
44
42
  batch_outcome
45
- } from "./chunk-V2BTOZHT.js";
43
+ } from "./chunk-VQLLNFNT.js";
46
44
  import {
47
45
  investigation_refusal
48
- } from "./chunk-ENA7P5FB.js";
46
+ } from "./chunk-VESKRQOF.js";
49
47
  import {
50
48
  duplicate_read_outcome
51
- } from "./chunk-URJA6XY4.js";
49
+ } from "./chunk-DO6NWWHU.js";
52
50
  import {
53
51
  investigation_reads
54
- } from "./chunk-A6P7PN7P.js";
52
+ } from "./chunk-ZAL3NRGL.js";
55
53
  import {
56
54
  lane_guard_policy
57
- } from "./chunk-GYNGP7HI.js";
55
+ } from "./chunk-I4XSL4TT.js";
58
56
  import {
59
57
  hook_decision,
60
58
  lane_child_marker,
61
59
  run_issue_number
62
- } from "./chunk-5KTCMCOT.js";
60
+ } from "./chunk-32GQTL4F.js";
63
61
  import {
64
62
  PLATFORM_TEMP_ROOT,
65
63
  cost_attribute,
@@ -69,10 +67,11 @@ import {
69
67
  file_reader,
70
68
  json_value,
71
69
  lane_paths,
70
+ open_issue_schema,
72
71
  stamp_file,
73
72
  time_batch_guard,
74
73
  time_shell
75
- } from "./chunk-DYAVAM6S.js";
74
+ } from "./chunk-S6X6WYKR.js";
76
75
 
77
76
  // scripts/rules/direct-filing.ts
78
77
  var DIRECT_FILING_REASON = '\u26D4 direct filing: an Issue is filed with `pnpm josh issue:file "<title>" --body-file <path> --depth <0|1|2> [--route <route>] [--label <name>] [--repo <owner/repo>]`, never with `gh api \u2026/issues` or `gh issue create`. The command runs every filing step in order \u2014 the duplicate scout, the body lint and its classification labels, the depth and `route:` labels, the `## Origin` check when filing into another repository, and `epic:bundle` after the create \u2014 so none of them is a step to remember. When the scout prints candidates it holds the filing: read each, then reissue with `--distinct <N,\u2026>` for the ones that are separate deliverables. Reference: `docs/josh-commands-automation.md` \u2192 "`josh issue:file`". **This rule fires on every occurrence, not once per run.**';
@@ -897,22 +896,15 @@ var cost_corpus = {
897
896
  var FAILURE_EXIT_CODE = 1;
898
897
  var OVER_VERDICT = "over";
899
898
  var UNDER_VERDICT = "under";
900
- function recent_requests(measurement2) {
901
- return measurement2.billed_input_per_request.slice(-RECENT_REQUEST_WINDOW);
902
- }
903
899
  function per_request_cost(measurement2) {
904
- const window = recent_requests(measurement2);
905
- if (window.length === 0) return 0;
906
- const total = window.reduce((sum, billed) => sum + billed, 0);
907
- return Math.round(total / window.length);
900
+ return measurement2.billed_input_per_request.at(-1) ?? 0;
908
901
  }
909
902
  function classify(measurement2, limit) {
910
903
  return per_request_cost(measurement2) > limit ? OVER_VERDICT : UNDER_VERDICT;
911
904
  }
912
905
  function over_summary(measurement2, limit) {
913
906
  const total = measurement2.billed_input_per_request.length;
914
- const window = recent_requests(measurement2).length;
915
- return `${String(per_request_cost(measurement2))} billed input tokens per request over the last ${String(window)} of ${String(total)} request(s); limit ${String(limit)}`;
907
+ return `${String(per_request_cost(measurement2))} billed input tokens per request, on the newest of ${String(total)} request(s); limit ${String(limit)}`;
916
908
  }
917
909
  function report_over(measurement2, limit) {
918
910
  if (measurement2 === void 0 || measurement2.billed_input_per_request.length === 0) {