@joshuafolkken/kit 1.953.0 → 1.972.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 (100) hide show
  1. package/.claude/skills/workflow-commands/background-commands.md +3 -0
  2. package/.claude/skills/workflow-commands/backlogrun-child.md +75 -120
  3. package/.claude/skills/workflow-commands/backlogrun-lanes.md +78 -115
  4. package/.claude/skills/workflow-commands/backlogrun-progress.md +82 -146
  5. package/.claude/skills/workflow-commands/backlogrun-steps.md +173 -229
  6. package/.claude/skills/workflow-commands/backlogrun.md +3 -1
  7. package/.claude/skills/workflow-commands/followup.md +3 -0
  8. package/.claude/skills/workflow-commands/fullrun.md +3 -4
  9. package/.claude/skills/workflow-commands/observation-filing.md +31 -25
  10. package/.claude/skills/workflow-commands/pre-gate-cut.md +96 -161
  11. package/.github/workflows/pr-classification.yml +12 -1
  12. package/CLAUDE.md +3 -3
  13. package/README.md +5 -3
  14. package/dist/CLAUDE.md +3 -3
  15. package/dist/hooks/batch-guard.js +4 -4
  16. package/dist/hooks/{chunk-OM5VDM6F.js → chunk-2K52BRWV.js} +2 -2
  17. package/dist/hooks/{chunk-Z5TVZ4AP.js → chunk-BNWIOJFU.js} +29 -16
  18. package/dist/hooks/{chunk-NKVLBB65.js → chunk-BVMSPG3M.js} +3 -3
  19. package/dist/hooks/{chunk-IA7WC4NS.js → chunk-CRGJTD6E.js} +1 -1
  20. package/dist/hooks/{chunk-M4U4YNRE.js → chunk-F6OM6SIG.js} +3 -3
  21. package/dist/hooks/{chunk-O3PEDF3M.js → chunk-KQG4776P.js} +1 -1
  22. package/dist/hooks/{chunk-V7RH56HU.js → chunk-NXL3SBXY.js} +2 -2
  23. package/dist/hooks/{chunk-452OKRQX.js → chunk-WWTMHHZU.js} +120 -18
  24. package/dist/hooks/{chunk-EYFJ74MJ.js → chunk-ZPLXLJ7O.js} +21 -7
  25. package/dist/hooks/{chunk-IBFC25IX.js → chunk-ZYYSBNR6.js} +4 -4
  26. package/dist/hooks/codex-hook-adapter.js +10 -10
  27. package/dist/hooks/duplicate-read-guard.js +5 -5
  28. package/dist/hooks/format-edited.js +2 -2
  29. package/dist/hooks/investigation-guard.js +5 -5
  30. package/dist/hooks/pretool-guard.js +9 -9
  31. package/dist/hooks/stop-guard.js +206 -176
  32. package/dist/josh.js +14 -6
  33. package/package.json +3 -3
  34. package/prompts/collaboration-workflow/wip-cap.md +1 -1
  35. package/scripts/backlog/backlog-arrival.ts +11 -1
  36. package/scripts/backlog/backlog-budget.ts +25 -8
  37. package/scripts/backlog/backlog-drive-cli.ts +5 -1
  38. package/scripts/backlog/backlog-drive-retrospective.ts +20 -0
  39. package/scripts/backlog/backlog-drive.ts +7 -3
  40. package/scripts/backlog/backlog-next.ts +45 -2
  41. package/scripts/backlog/backlog-offer.ts +1 -0
  42. package/scripts/backlog/backlog-plan-cli.ts +66 -27
  43. package/scripts/backlog/backlog-plan.ts +30 -2
  44. package/scripts/backlog/backlog-stalled.ts +9 -0
  45. package/scripts/backlog/backlog-waves.ts +169 -0
  46. package/scripts/document/document-byte-budget.ts +1 -1
  47. package/scripts/document/entry-read-budget.ts +7 -5
  48. package/scripts/epic/epic-lane-offer.ts +69 -4
  49. package/scripts/epic/epic-next.ts +2 -2
  50. package/scripts/epic/epic-solo.ts +147 -0
  51. package/scripts/epic/epic-triage.ts +47 -0
  52. package/scripts/gate/hook-gate-reuse.ts +7 -8
  53. package/scripts/git/git-closes-keyword.ts +15 -0
  54. package/scripts/git/git-followup-flush.ts +5 -6
  55. package/scripts/git/git-followup-issue-close.ts +75 -0
  56. package/scripts/git/git-pr-followup-wrapup.ts +36 -6
  57. package/scripts/git/git-pr-followup.ts +9 -16
  58. package/scripts/git/git-staging.ts +33 -20
  59. package/scripts/git/issue-labels.ts +42 -0
  60. package/scripts/hooks/stop-guard.ts +1 -0
  61. package/scripts/init/init-install.ts +64 -0
  62. package/scripts/init/init.ts +38 -11
  63. package/scripts/init/project-profile.ts +9 -5
  64. package/scripts/init/start-plan.ts +145 -0
  65. package/scripts/init/start-prompt.ts +55 -0
  66. package/scripts/init/start-steps.ts +167 -0
  67. package/scripts/init/start.ts +92 -16
  68. package/scripts/issue/issue-closing-pr.ts +93 -0
  69. package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +4 -2
  70. package/scripts/josh/josh-commands-project.ts +11 -3
  71. package/scripts/josh/josh-commands-workflow.ts +3 -3
  72. package/scripts/observations/observation-ledger-prepare.ts +31 -0
  73. package/scripts/observations/observation-ledger.ts +5 -5
  74. package/scripts/observations/observations-flush.ts +11 -30
  75. package/scripts/rules/implementation-cut.ts +49 -16
  76. package/scripts/rules/lane-background.ts +25 -5
  77. package/scripts/rules/stop-rules.ts +16 -4
  78. package/scripts/run/run-carry-cli.ts +21 -0
  79. package/scripts/run/run-cut-cli.ts +92 -110
  80. package/scripts/run/run-cut-report.ts +167 -0
  81. package/scripts/run/run-cut.ts +23 -7
  82. package/scripts/run/run-entry-cli.ts +36 -2
  83. package/scripts/run/run-headless.ts +1 -0
  84. package/scripts/run/run-hold-cli.ts +36 -15
  85. package/scripts/run/run-hold.ts +15 -4
  86. package/scripts/run/run-merge-cli.ts +28 -14
  87. package/scripts/run/run-merge-steps.ts +38 -2
  88. package/scripts/run/run-merge.ts +41 -3
  89. package/scripts/run/run-prep-cli.ts +8 -2
  90. package/scripts/run/run-prep-locate.ts +165 -0
  91. package/scripts/run/run-prep.ts +12 -1
  92. package/scripts/run/run-retrospective.ts +34 -0
  93. package/scripts/run/run-step-cli.ts +19 -5
  94. package/scripts/run/run-step.ts +31 -19
  95. package/scripts/run/run-tail-cli.ts +4 -2
  96. package/scripts/run/run-wake-work.ts +4 -0
  97. package/scripts/safe-chain/ci-installer-pin.ts +8 -4
  98. package/scripts/time-runtime/time-background.ts +11 -0
  99. package/scripts/time-runtime/time-transcript-line.ts +4 -0
  100. package/templates/pnpm-workspace.static.yaml +2 -0
package/README.md CHANGED
@@ -19,13 +19,15 @@ kit adds no linter or test runner for languages other than JavaScript and TypeSc
19
19
  In your project directory:
20
20
 
21
21
  ```bash
22
- pnpm add -D --allow-build=esbuild @joshuafolkken/kit
22
+ pnpm add -D --allow-build=esbuild --allow-build=unrs-resolver @joshuafolkken/kit
23
23
  pnpm exec josh init
24
- pnpm install
25
- pnpm josh format
26
24
  pnpm josh gate
27
25
  ```
28
26
 
27
+ `josh init` finishes by running `pnpm install` and `josh format` itself; pass `--no-install` to skip both, for example on CI or offline.
28
+
29
+ `josh init` is the entry without GitHub. To start a new project on the GitHub Issue workflow (`kickoff`), run `pnpm exec josh start` instead of `josh init` — it also creates the Git repository, the GitHub repository and the labels ([init.md → `josh init` or `josh start`](./docs/init.md#josh-init-or-josh-start)).
30
+
29
31
  It needs [Node.js](https://nodejs.org/) 22.19.0 or later with [pnpm](https://pnpm.io/), even for a `static` project; [getting-started.md](./docs/getting-started.md) installs both. The global `josh` command ([cli.md](./docs/cli.md)) and the [gh CLI](https://cli.github.com/) for `josh version` and the GitHub Issue workflow are optional.
30
32
 
31
33
  ## Documentation
package/dist/CLAUDE.md CHANGED
@@ -89,7 +89,7 @@ Read from a `.env` file at the project root by the AI scripts, `josh port` and `
89
89
 
90
90
  ### Dependency overrides (`pnpm-workspace.yaml` / `package.json`)
91
91
 
92
- - **Effective overrides live in `pnpm-workspace.yaml`.** pnpm 11 and 12 ignore `pnpm.overrides` in `package.json` (verified with pnpm 12.6.0). Check both files for existing declarations, but never count the ignored package field as an effective override. **An absent or empty `pnpm.overrides` is not evidence that the project has no overrides.**
92
+ - **Effective overrides live in `pnpm-workspace.yaml`.** pnpm 11 and 12 ignore `pnpm.overrides` in `package.json`. Check both files for existing declarations, but never count the ignored package field as an effective override. **An absent or empty `pnpm.overrides` is not evidence that the project has no overrides.**
93
93
  - **NEVER** remove or modify entries in **either** location without explicit user approval.
94
94
  - **NEVER** modify the `devEngines` field in `package.json` without explicit user confirmation. It pins the development toolchain (e.g. pnpm version); silently changing it can break CI or other contributors' environments.
95
95
  - **The check is a command you run, not a conclusion you reach.** After `pnpm update`, `josh latest`, or any dependency-update command, **load the `dependency-update` skill** and follow its procedure before reporting anything about the pins.
@@ -144,7 +144,7 @@ Before every `git commit` (follow-up commits included), self-review against `nod
144
144
 
145
145
  ## Doc Sync Rules
146
146
 
147
- **`CLAUDE.md` is the single source for every agent rule.** `AGENTS.md` and `GEMINI.md` carry no rules — a rule addition, spec change or wording fix is written **once**, here (joshuafolkken/kit#963). The structural test [scripts/document/ai-document-pointers.test.ts](https://github.com/joshuafolkken/kit/blob/main/scripts/document/ai-document-pointers.test.ts) fails if a rule body reappears in either, or if a pointer loses the sentence that sends an agent here.
147
+ **`CLAUDE.md` is the single source for every agent rule.** `AGENTS.md` and `GEMINI.md` carry no rules — a rule addition, spec change or wording fix is written **once**, here (joshuafolkken/kit#963).
148
148
 
149
149
  **docs/ must stay in sync with the package.** When `josh bump` changes the version, review `https://github.com/joshuafolkken/kit/tree/main/docs/` and update any section describing changed behavior (new/renamed commands, `josh init` / `josh sync` behavior, new config files) before committing.
150
150
 
@@ -166,7 +166,7 @@ Before every `git commit` (follow-up commits included), self-review against `nod
166
166
 
167
167
  `kickoff`, `fullrun`, `halfrun` and `backlogrun` are the Issue-driven shorthand commands. **Their procedures are not resident** — they live in the `workflow-commands` skill. **What stays here is decided by one question: must the rule fire on a turn where no skill was loaded?** Explicit invocation, the mid-workflow stop notification, the `overrides` / `devEngines` prohibitions, the UI-verification gate and the three `epic:*` rules below all do; everything a run reaches only after reading the skill is routed to, never restated (`SKILL.md` → "What stays resident, and what is read from here").
168
168
 
169
- **Read the skill before running any part of a command — including the first `gh` call.** Acting from the table below alone is not enough: the table says which command was typed, not how to run it.
169
+ **Read the skill before running any part of a command — including the first `gh` call.**
170
170
 
171
171
  | Typed keyword | What it does | Read first |
172
172
  | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
@@ -9,10 +9,10 @@ import {
9
9
  load_environment_file,
10
10
  notice_refusal_path,
11
11
  refusal_path
12
- } from "./chunk-V7RH56HU.js";
13
- import "./chunk-OM5VDM6F.js";
14
- import "./chunk-O3PEDF3M.js";
15
- import "./chunk-EYFJ74MJ.js";
12
+ } from "./chunk-NXL3SBXY.js";
13
+ import "./chunk-2K52BRWV.js";
14
+ import "./chunk-KQG4776P.js";
15
+ import "./chunk-ZPLXLJ7O.js";
16
16
  import "./chunk-WPALEIRX.js";
17
17
  import "./chunk-RKUAD3I6.js";
18
18
  export {
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  lane_child_marker
3
- } from "./chunk-O3PEDF3M.js";
3
+ } from "./chunk-KQG4776P.js";
4
4
  import {
5
5
  time_bundle_call,
6
6
  time_format,
7
7
  time_round_trips,
8
8
  time_spans,
9
9
  time_transcript_line
10
- } from "./chunk-EYFJ74MJ.js";
10
+ } from "./chunk-ZPLXLJ7O.js";
11
11
 
12
12
  // scripts/lane/lane-guard-policy.ts
13
13
  var DEFAULT_MODE = "refuse";
@@ -35,31 +35,31 @@ import {
35
35
  run_watcher_guard,
36
36
  shell_segments,
37
37
  tail_commands
38
- } from "./chunk-452OKRQX.js";
38
+ } from "./chunk-WWTMHHZU.js";
39
39
  import {
40
40
  find_local_bin_upwards
41
41
  } from "./chunk-3V37IID6.js";
42
42
  import {
43
43
  batch_outcome
44
- } from "./chunk-V7RH56HU.js";
44
+ } from "./chunk-NXL3SBXY.js";
45
45
  import {
46
46
  investigation_refusal
47
- } from "./chunk-NKVLBB65.js";
47
+ } from "./chunk-BVMSPG3M.js";
48
48
  import {
49
49
  duplicate_read_outcome
50
- } from "./chunk-IBFC25IX.js";
50
+ } from "./chunk-ZYYSBNR6.js";
51
51
  import {
52
52
  investigation_reads
53
- } from "./chunk-M4U4YNRE.js";
53
+ } from "./chunk-F6OM6SIG.js";
54
54
  import {
55
55
  lane_guard_policy,
56
56
  time_batch_guard
57
- } from "./chunk-OM5VDM6F.js";
57
+ } from "./chunk-2K52BRWV.js";
58
58
  import {
59
59
  hook_decision,
60
60
  lane_child_marker,
61
61
  run_issue_number
62
- } from "./chunk-O3PEDF3M.js";
62
+ } from "./chunk-KQG4776P.js";
63
63
  import {
64
64
  PLATFORM_TEMP_ROOT,
65
65
  cost_attribute,
@@ -71,7 +71,7 @@ import {
71
71
  stamp_file,
72
72
  time_density_hook,
73
73
  time_shell
74
- } from "./chunk-EYFJ74MJ.js";
74
+ } from "./chunk-ZPLXLJ7O.js";
75
75
 
76
76
  // scripts/hooks/pretool-guard.ts
77
77
  import { text } from "node:stream/consumers";
@@ -1016,24 +1016,37 @@ function current_state() {
1016
1016
  directory: process.cwd(),
1017
1017
  source: process.env,
1018
1018
  carried: run_cut.carried_cut_sync,
1019
+ held_issue: run_cut.held_issue_sync,
1019
1020
  // **Read through the short reuse window** (joshuafolkken/kit#2385). The row now fires per
1020
1021
  // threshold crossing rather than once per run, so this predicate is a candidate on every edit;
1021
1022
  // the window collapses one turn's burst of edits to a single whole-transcript price.
1022
1023
  verdict: () => implementation_cut_verdict.reused_verdict(() => cost_cli.session_verdict())
1023
1024
  };
1024
1025
  }
1025
- function uncut_lane_child_issue(state) {
1026
+ function lane_child_issue(state) {
1026
1027
  if (!lane_child_marker.is_child_of(state.directory, state.source)) return void 0;
1027
- const issue = lane_paths.lane_issue_of(state.directory, { ...state.source });
1028
+ return lane_paths.lane_issue_of(state.directory, { ...state.source });
1029
+ }
1030
+ function held_run_issue(state) {
1031
+ if (lane_paths.lane_issue_of(state.directory, { ...state.source }) !== void 0) return void 0;
1032
+ return state.held_issue();
1033
+ }
1034
+ function uncut_run_issue(state) {
1035
+ const issue = lane_child_issue(state) ?? held_run_issue(state);
1028
1036
  if (issue === void 0) return void 0;
1029
1037
  return state.carried()?.issue === issue ? void 0 : issue;
1030
1038
  }
1039
+ function warrants_the_cut(state) {
1040
+ const verdict = state.verdict();
1041
+ if (lane_child_issue(state) === void 0) return verdict === cost_verdict.OVER_VERDICT;
1042
+ return verdict !== cost_verdict.UNDER_VERDICT;
1043
+ }
1031
1044
  function is_over_threshold_edit(call, state = current_state()) {
1032
- if (!is_edit_tool(call.name) || uncut_lane_child_issue(state) === void 0) return false;
1033
- return state.verdict() !== cost_verdict.UNDER_VERDICT;
1045
+ if (!is_edit_tool(call.name) || uncut_run_issue(state) === void 0) return false;
1046
+ return warrants_the_cut(state);
1034
1047
  }
1035
1048
  var THRESHOLD_TEXT = `${cost_format.format_tokens(CONTEXT_CUT_THRESHOLD)}-token`;
1036
- var IMPLEMENTATION_CUT_REASON = `\u26D4 implementation-phase cut: this checkout is a lane dispatched for this issue and its recent-context cost has crossed the shared ${THRESHOLD_TEXT} threshold mid-implementation, so the thinking accumulated so far is now re-read on every later request. Take the cut before this edit. First write a handoff file with the Write tool \u2014 the user\u2019s instruction verbatim, what you have completed, what remains, and what you deliberately did not touch \u2014 and pass it as \`--handoff <path>\`, so the fresh process resumes on the original instruction rather than the working tree alone (joshuafolkken/kit#2354); a resume that finds no instruction is refused \`incomplete\` rather than continuing blind. \`pnpm josh run:cut --impl <N> --handoff <path>\` ends this process and relaunches a fresh one that resumes back into implementation (\`resume-impl\`), dropping the accumulated context rather than carrying it \u2014 joshuafolkken/kit#1933 built the boundary and joshuafolkken/kit#2310 measured it firing 0 times because the verdict was only ever read at session entry, where the context has not yet grown. The verdict is \`pnpm josh cost --cut\`'s exactly \u2014 the same per-request billed-input statistic against the same threshold, never a second measurement. Issue \`pnpm josh run:cut --impl <N> --handoff <path>\` now and read the verdict: on \`cut\`, **end the turn immediately** \u2014 the fresh process owns the run and continues implementing, so it must not be waited for; on \`not-a-lane\`, \`unready\`, \`busy\`, \`failed\` or \`unknown\`, this process carries the run on and the edit is simply the next call. Never relaunch a second process after \`busy\`. This fired because the dispatch mark names this lane; a person working here carries no mark and sees no refusal, so there is no human-or-child judgement left to make. The procedure is \`.claude/skills/workflow-commands/pre-gate-cut.md\`. Reissue this edit once the cut has answered \u2014 an edit reissued right after this refusal passes, so \`busy\` / \`failed\` cannot wedge the run edit after edit; this fires again on the next threshold crossing rather than once per run.`;
1049
+ var IMPLEMENTATION_CUT_REASON = `\u26D4 implementation-phase cut: this checkout is a lane dispatched for this issue, or a run holds it for this issue (joshuafolkken/kit#2760), and its recent-context cost has crossed the shared ${THRESHOLD_TEXT} threshold mid-implementation, so the thinking accumulated so far is now re-read on every later request. Take the cut before this edit. First write a handoff file with the Write tool \u2014 the user\u2019s instruction verbatim, what you have completed, what remains, and what you deliberately did not touch \u2014 and pass it as \`--handoff <path>\`, so the fresh process resumes on the original instruction rather than the working tree alone (joshuafolkken/kit#2354); a resume that finds no instruction is refused \`incomplete\` rather than continuing blind. \`pnpm josh run:cut --impl <N> --handoff <path>\` ends this process and relaunches a fresh one that resumes back into implementation (\`resume-impl\`), dropping the accumulated context rather than carrying it \u2014 joshuafolkken/kit#1933 built the boundary and joshuafolkken/kit#2310 measured it firing 0 times because the verdict was only ever read at session entry, where the context has not yet grown. The verdict is \`pnpm josh cost --cut\`'s exactly \u2014 the same per-request billed-input statistic against the same threshold, never a second measurement. Issue \`pnpm josh run:cut --impl <N> --handoff <path>\` now and read the verdict: on \`cut\`, **end the turn immediately** \u2014 in a lane the fresh process owns the run and continues implementing, so it must not be waited for; outside a lane nothing is relaunched, so first send the \`confirmation\` Telegram naming the resume command \`fullrun #<N>\` and keep the hold; on \`not-a-lane\`, \`unready\`, \`busy\`, \`failed\` or \`unknown\`, this process carries the run on and the edit is simply the next call. Never relaunch a second process after \`busy\`. This fired because the dispatch mark names this lane or the run hold names this issue; a person working here carries no mark and holds no run, so sees no refusal, so there is no human-or-child judgement left to make. The procedure is \`.claude/skills/workflow-commands/pre-gate-cut.md\`. Reissue this edit once the cut has answered \u2014 an edit reissued right after this refusal passes, so \`busy\` / \`failed\` cannot wedge the run edit after edit; this fires again on the next threshold crossing rather than once per run.`;
1037
1050
  var REISSUE_WINDOW_MS = 9e4;
1038
1051
  function is_reissued_refusal(now_ms, delivered_at_ms) {
1039
1052
  return now_ms - delivered_at_ms < REISSUE_WINDOW_MS;
@@ -1057,7 +1070,7 @@ var implementation_cut = {
1057
1070
  is_over_threshold_edit,
1058
1071
  is_reissued_refusal,
1059
1072
  takes_the_impl_cut,
1060
- uncut_lane_child_issue
1073
+ uncut_run_issue
1061
1074
  };
1062
1075
 
1063
1076
  // scripts/rules/issue-bug-label.ts
@@ -1951,12 +1964,12 @@ function uncut_lane_issue(state) {
1951
1964
  if (state.marked_issue !== issue) return void 0;
1952
1965
  return state.carried()?.issue === issue ? void 0 : issue;
1953
1966
  }
1954
- function warrants_the_cut(verdict) {
1967
+ function warrants_the_cut2(verdict) {
1955
1968
  return verdict !== cost_verdict.UNDER_VERDICT;
1956
1969
  }
1957
1970
  function is_uncut_gate(command, state = current_state2()) {
1958
1971
  if (!runs_the_gate(command) || uncut_lane_issue(state) === void 0) return false;
1959
- return warrants_the_cut(state.context_verdict());
1972
+ return warrants_the_cut2(state.context_verdict());
1960
1973
  }
1961
1974
  var PRE_GATE_CUT_REASON = "\u26D4 pre-gate cut: this checkout is a lane dispatched for this issue and the gate is the boundary to cut at, so take the cut before running it. `pnpm josh run:cut <N>` ends this process and relaunches a fresh one that resumes from the gate, dropping the thinking built up while implementing rather than carrying it on every later request \u2014 joshuafolkken/kit#1839 measured 176K of 204K output riding across that boundary, and joshuafolkken/kit#1864 measured the step itself firing 0 times in 6 lane children while it was carried as prose. Issue `pnpm josh run:cut <N>` now, and read the verdict: on `cut`, **end the turn immediately** \u2014 the fresh process owns the run from there and must not be waited for; on `not-a-lane`, `unready`, `busy`, `failed` or `unknown`, this process carries the run on and the gate is simply the next call. Never relaunch a second process after `busy`. This fired because the dispatch mark names this lane; a person working here carries no mark and sees no refusal, so there is no human-or-child judgement left to make. The procedure is `.claude/skills/workflow-commands/pre-gate-cut.md`. Reissue the gate once the cut has answered \u2014 this fires once per run, so it cannot repeat on the call in hand.";
1962
1975
  function reaches_the_pre_gate_boundary(call, _turn, run2) {
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  investigation_reads
3
- } from "./chunk-M4U4YNRE.js";
3
+ } from "./chunk-F6OM6SIG.js";
4
4
  import {
5
5
  lane_guard_policy
6
- } from "./chunk-OM5VDM6F.js";
6
+ } from "./chunk-2K52BRWV.js";
7
7
  import {
8
8
  hook_decision
9
- } from "./chunk-O3PEDF3M.js";
9
+ } from "./chunk-KQG4776P.js";
10
10
 
11
11
  // scripts/delegation/investigation-guard.ts
12
12
  import { text } from "node:stream/consumers";
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  ESLINT_EDIT_CACHE_FLAGS,
7
7
  time_density_hook
8
- } from "./chunk-EYFJ74MJ.js";
8
+ } from "./chunk-ZPLXLJ7O.js";
9
9
 
10
10
  // scripts/hooks/format-edited-file.ts
11
11
  import { existsSync as existsSync2 } from "node:fs";
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  time_batch_guard
3
- } from "./chunk-OM5VDM6F.js";
3
+ } from "./chunk-2K52BRWV.js";
4
4
  import {
5
5
  hook_decision
6
- } from "./chunk-O3PEDF3M.js";
6
+ } from "./chunk-KQG4776P.js";
7
7
  import {
8
8
  cost_blocks,
9
9
  cost_transcript,
10
10
  time_bundle_call,
11
11
  time_shell,
12
12
  time_spans
13
- } from "./chunk-EYFJ74MJ.js";
13
+ } from "./chunk-ZPLXLJ7O.js";
14
14
 
15
15
  // scripts/delegation/investigation-reads.ts
16
16
  import path from "node:path";
@@ -3,7 +3,7 @@ import {
3
3
  stamp_file,
4
4
  time_density_hook,
5
5
  time_hook_transcript
6
- } from "./chunk-EYFJ74MJ.js";
6
+ } from "./chunk-ZPLXLJ7O.js";
7
7
  import {
8
8
  josh_environment_file
9
9
  } from "./chunk-WPALEIRX.js";
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  lane_guard_policy,
3
3
  time_batch_guard
4
- } from "./chunk-OM5VDM6F.js";
4
+ } from "./chunk-2K52BRWV.js";
5
5
  import {
6
6
  hook_decision
7
- } from "./chunk-O3PEDF3M.js";
7
+ } from "./chunk-KQG4776P.js";
8
8
 
9
9
  // scripts/hooks/batch-guard.ts
10
10
  import { text } from "node:stream/consumers";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  lane_child_marker,
3
3
  run_issue_number
4
- } from "./chunk-O3PEDF3M.js";
4
+ } from "./chunk-KQG4776P.js";
5
5
  import {
6
6
  PLATFORM_TEMP_ROOT,
7
7
  cost_blocks,
@@ -12,7 +12,7 @@ import {
12
12
  time_density_hook,
13
13
  time_shell,
14
14
  time_transcript_line
15
- } from "./chunk-EYFJ74MJ.js";
15
+ } from "./chunk-ZPLXLJ7O.js";
16
16
  import {
17
17
  ENV_FILE_NAME,
18
18
  LANE_SEAT_KEY,
@@ -546,7 +546,8 @@ async function is_linked_worktree() {
546
546
  var run_hold_schema = z.object({
547
547
  issue: z.string(),
548
548
  taken_at: z.string(),
549
- pid: z.number()
549
+ pid: z.number(),
550
+ is_fullrun: z.boolean().optional()
550
551
  });
551
552
  function parse_hold(raw) {
552
553
  try {
@@ -570,16 +571,16 @@ function classify(raw, now) {
570
571
  function read_hold(target, now = /* @__PURE__ */ new Date()) {
571
572
  return classify(stamp_file.read_stamp_text(target), now);
572
573
  }
573
- function build_hold(issue, now) {
574
- return { issue, taken_at: now.toISOString(), pid: process.pid };
574
+ function build_hold(issue, now, is_fullrun) {
575
+ return { issue, taken_at: now.toISOString(), pid: process.pid, is_fullrun };
575
576
  }
576
577
  function write_hold(target, issue, now = /* @__PURE__ */ new Date()) {
577
578
  const hold = build_hold(issue, now);
578
579
  stamp_file.write_stamp(target, hold);
579
580
  return hold;
580
581
  }
581
- function create_hold(target, issue, now = /* @__PURE__ */ new Date()) {
582
- return stamp_file.create_stamp(target, build_hold(issue, now));
582
+ function create_hold(target, issue, now = /* @__PURE__ */ new Date(), is_fullrun) {
583
+ return stamp_file.create_stamp(target, build_hold(issue, now, is_fullrun));
583
584
  }
584
585
  async function is_tree_dirty() {
585
586
  try {
@@ -943,11 +944,13 @@ var IDLE_POLL_MINUTES = 5;
943
944
  var RUN_VERDICT = "run";
944
945
  var WATCH_VERDICT = "watch";
945
946
  var STOP_VERDICT = "stop";
946
- var ANSWERS = ["blocked", "candidates", "exhausted", "parked", "unreadable"];
947
+ var TRIAGE_VERDICT = "triage";
948
+ var ANSWERS = ["blocked", "candidates", "exhausted", "parked", "unreadable", "untriaged"];
947
949
  var WHOLE_RUN_REASON = `The whole-run bound of ${String(WHOLE_RUN_BUDGET_HOURS)} hours has passed. An unattended run that has not finished overnight needs a person, not more waiting.`;
948
950
  var PARKED_REASON = "Nothing opted in can proceed without a person. Report the parked issues and finish.";
949
951
  var UNREADABLE_REASON = "The backlog listing could not be read, so nothing was offered. Report what the command printed and finish \u2014 it is not an empty backlog.";
950
952
  var BLOCKED_REASON = "Everything opted in is blocked or already running, so waiting can still change the answer. Poll again at the polling interval.";
953
+ var TRIAGE_REASON = "An opted-in candidate carries neither `run:solo` nor `run:lane`, so nothing starts until it is judged. Read each issue named above, record its order and one of the two labels with a comment, then ask again.";
951
954
  var NO_IDLE_WATCH_REASON = "The backlog is empty and the idle watch was turned off with `--idle 0`, so the run finishes here.";
952
955
  function to_minutes(duration_ms) {
953
956
  return String(Math.ceil(duration_ms / MS_PER_MINUTE));
@@ -1013,14 +1016,20 @@ function idle_decision(input) {
1013
1016
  function is_finish(input) {
1014
1017
  return stop_reason(input) === void 0 && max_decision(input) === void 0;
1015
1018
  }
1019
+ var FIXED_DECISIONS = {
1020
+ blocked: { verdict: WATCH_VERDICT, reason: BLOCKED_REASON },
1021
+ untriaged: { verdict: TRIAGE_VERDICT, reason: TRIAGE_REASON }
1022
+ };
1023
+ function answer_decision(input) {
1024
+ if (input.answer === "candidates") return { verdict: RUN_VERDICT, reason: run_reason(input) };
1025
+ return FIXED_DECISIONS[input.answer] ?? idle_decision(input);
1026
+ }
1016
1027
  function decide2(input) {
1017
1028
  const reason = stop_reason(input);
1018
1029
  if (reason !== void 0) return stopping(input, reason);
1019
1030
  const capped = max_decision(input);
1020
1031
  if (capped !== void 0) return capped;
1021
- if (input.answer === "candidates") return { verdict: RUN_VERDICT, reason: run_reason(input) };
1022
- if (input.answer === "blocked") return { verdict: WATCH_VERDICT, reason: BLOCKED_REASON };
1023
- return idle_decision(input);
1032
+ return answer_decision(input);
1024
1033
  }
1025
1034
  var backlog_budget = {
1026
1035
  ANSWERS,
@@ -1034,6 +1043,8 @@ var backlog_budget = {
1034
1043
  PARKED_REASON,
1035
1044
  RUN_VERDICT,
1036
1045
  STOP_VERDICT,
1046
+ TRIAGE_REASON,
1047
+ TRIAGE_VERDICT,
1037
1048
  UNREADABLE_REASON,
1038
1049
  WATCH_VERDICT,
1039
1050
  WHOLE_RUN_BUDGET_HOURS,
@@ -1448,16 +1459,22 @@ function adopt_cut(target, cut) {
1448
1459
  function end_cut(target) {
1449
1460
  stamp_file.remove_stamp(target);
1450
1461
  }
1451
- function tree_is_held(directory) {
1452
- if (directory === void 0) return false;
1462
+ function hold_of(directory) {
1463
+ if (directory === void 0) return void 0;
1453
1464
  const read2 = run_hold.read_hold(run_hold.hold_path(directory));
1454
- return read2.kind === "held" || read2.kind === "stale";
1465
+ return read2.kind === "held" || read2.kind === "stale" ? read2.hold : void 0;
1466
+ }
1467
+ function fullrun_issue(hold) {
1468
+ return hold?.is_fullrun === true ? hold.issue : void 0;
1469
+ }
1470
+ function held_issue_sync() {
1471
+ return fullrun_issue(hold_of(worktree_git_directory_sync()));
1455
1472
  }
1456
1473
  async function current_state() {
1457
1474
  const branch2 = await git_command.branch();
1458
1475
  const is_dirty = await run_hold.is_tree_dirty();
1459
- const directory = await worktree_directory2();
1460
- return { branch: branch2, is_dirty, is_held: tree_is_held(directory) };
1476
+ const hold = hold_of(await worktree_directory2());
1477
+ return { branch: branch2, is_dirty, is_held: hold !== void 0, held_issue: fullrun_issue(hold) };
1461
1478
  }
1462
1479
  function describe_cut(cut) {
1463
1480
  return `${cut.invocation} cut at ${cut.cut_at} on ${cut.branch} (${cut.phase})`;
@@ -1499,8 +1516,10 @@ var run_cut = {
1499
1516
  describe_cut,
1500
1517
  end_cut,
1501
1518
  fresh_cut,
1519
+ fullrun_issue,
1502
1520
  handed_off_message,
1503
1521
  has_required_handoff,
1522
+ held_issue_sync,
1504
1523
  incomplete_message,
1505
1524
  invocation_for,
1506
1525
  is_expired,
@@ -1602,12 +1621,19 @@ function is_candidate(call) {
1602
1621
  function is_background_long_run(call, is_lane_child = () => lane_child_marker.is_child_of(process.cwd())) {
1603
1622
  return is_candidate(call) && is_lane_child();
1604
1623
  }
1605
- function pending_background_ids(tail) {
1624
+ function unfinished_ids(tail, launched_ids) {
1606
1625
  const lines = tail.split("\n").map((line) => time_transcript_line.parse_line(line));
1607
1626
  const finished = new Set(lines.map((line) => line?.finished_background ?? ""));
1608
- const launched = lines.flatMap((line) => line?.blocks.map((block) => block.background_id) ?? []);
1627
+ const blocks = lines.flatMap((line) => line?.blocks ?? []);
1628
+ const launched = blocks.flatMap((block) => launched_ids(block));
1609
1629
  return launched.filter((id) => id !== "" && !finished.has(id));
1610
1630
  }
1631
+ function pending_background_ids(tail) {
1632
+ return unfinished_ids(tail, (block) => [block.background_id, block.agent_id]);
1633
+ }
1634
+ function pending_agent_ids(tail) {
1635
+ return unfinished_ids(tail, (block) => [block.agent_id]);
1636
+ }
1611
1637
  var LANE_BACKGROUND_REASON = '\u26D4 lane background: this session is a dispatched lane child (`JOSH_LANE_CHILD`), a headless `claude -p` process whose background Bash tasks are killed when the turn ends \u2014 so a backgrounded `josh gate` / `josh git` / `josh followup` never re-invokes you; it dies at `exit code 143` (joshuafolkken/kit#2704, measured on the #2606 child: implementation, review and live verification done, the run parked instead of merged). Hand the gate-to-merge region to the detached supervisor instead: `pnpm josh ship --detach --review "<title> #<N>"` (add `--cite <N>`), then end the turn on `launched` / `busy` \u2014 `.claude/skills/workflow-commands/chain-rule.md` step 0 is the single source. A single check you must read before continuing runs in the foreground within the tool timeout; the commit-push step does not \u2014 in a child it too belongs to the detached ship, in either spelling. This rule fires on every occurrence, not once per run.';
1612
1638
  var LANE_BACKGROUND_STOP_REASON = "\u26D4 lane background stop: this is a dispatched lane child (`JOSH_LANE_CHILD`) and a background task it launched has not finished \u2014 ending this headless turn kills it, and nothing re-invokes you afterwards (joshuafolkken/kit#2704). Do not send a notification and do not stop: wait for the task in the foreground and act on its result, or re-issue the work through `pnpm josh ship --detach`, whose supervisor outlives the turn (`.claude/skills/workflow-commands/chain-rule.md` step 0). Do not repeat your previous reply.";
1613
1639
  var ROW2 = {
@@ -1621,17 +1647,91 @@ var lane_background = {
1621
1647
  LANE_BACKGROUND_STOP_REASON,
1622
1648
  ROW: ROW2,
1623
1649
  is_background_long_run,
1650
+ pending_agent_ids,
1624
1651
  pending_background_ids
1625
1652
  };
1626
1653
 
1627
1654
  // scripts/git/issue-labels.ts
1655
+ var EPIC_LABEL = "epic";
1628
1656
  var BUG_LABEL = "bug";
1629
1657
  var ENHANCEMENT_LABEL = "enhancement";
1630
1658
  var BREAKING_CHANGE_LABEL = "breaking-change";
1631
1659
  var IN_PROGRESS_LABEL = "in-progress";
1632
1660
  var NEEDS_DECISION_LABEL = "needs-decision";
1661
+ var RUN_SOLO_LABEL = "run:solo";
1662
+ var RUN_LANE_LABEL = "run:lane";
1633
1663
  var NEEDS_HUMAN_REVIEW_LABEL = "needs-human-review";
1634
1664
  var ALREADY_DONE_LABEL = "already-done";
1665
+ var INTERRUPT_ROUTE_LABEL = "route:interrupt";
1666
+ var REVIEW_CAP_ROUTE_LABEL = "route:review-cap";
1667
+ var SPLIT_ROUTE_LABEL = "route:split";
1668
+ var TIER_A_ROUTE_LABEL = "route:tier-a";
1669
+ var FILING_ROUTE_LABELS = [
1670
+ {
1671
+ name: INTERRUPT_ROUTE_LABEL,
1672
+ // Not `5319e7`, which is the `epic` label's purple: in a listing an interrupt would render as
1673
+ // an epic, and the two are read at a glance rather than by name.
1674
+ color: "1d76db",
1675
+ // Keyed to the tests rather than to severity. "A serious defect" is the self-assessment
1676
+ // `wip-cap.md` bans as a criterion, and this string is what a person reads in `gh label list`.
1677
+ description: "Filed past the WIP cap \u2014 meets one of the three interrupt tests (wip-cap.md)"
1678
+ },
1679
+ {
1680
+ name: REVIEW_CAP_ROUTE_LABEL,
1681
+ color: "eab308",
1682
+ description: "Filed by the review round cap's carry-forward (prompts/review.md \u2192 Review round cap)"
1683
+ },
1684
+ {
1685
+ name: SPLIT_ROUTE_LABEL,
1686
+ color: "0e8a16",
1687
+ description: "A child issue created by a split (split-assessment.md)"
1688
+ },
1689
+ {
1690
+ name: TIER_A_ROUTE_LABEL,
1691
+ color: "d93f0b",
1692
+ description: "Filed Tier A during implementation \u2014 an upstream defect or a prerequisite"
1693
+ }
1694
+ ];
1695
+ var DEPTH_0_LABEL = "depth:0";
1696
+ var DEPTH_1_LABEL = "depth:1";
1697
+ var DEPTH_2_LABEL = "depth:2";
1698
+ var DEPTH_LABELS = [
1699
+ {
1700
+ name: DEPTH_0_LABEL,
1701
+ color: "0e8a16",
1702
+ description: "Depth 0 \u2014 what a consumer of this package touches (SKILL.md \xA72i)"
1703
+ },
1704
+ {
1705
+ name: DEPTH_1_LABEL,
1706
+ color: "fbc02d",
1707
+ description: "Depth 1 \u2014 the run orchestration that executes an Issue (SKILL.md \xA72i)"
1708
+ },
1709
+ {
1710
+ name: DEPTH_2_LABEL,
1711
+ color: "c5def5",
1712
+ description: "Depth 2 \u2014 what measures a run (SKILL.md \xA72i)"
1713
+ }
1714
+ ];
1715
+ var WORKFLOW_LABELS = [
1716
+ {
1717
+ name: EPIC_LABEL,
1718
+ color: "5319e7",
1719
+ description: "Tracks a batch of child issues from one split"
1720
+ },
1721
+ { name: IN_PROGRESS_LABEL, color: "0075ca", description: "Work is actively in progress" },
1722
+ {
1723
+ name: RUN_SOLO_LABEL,
1724
+ color: "b60205",
1725
+ description: "Runs alone in a backlogrun: nothing starts beside it"
1726
+ },
1727
+ {
1728
+ name: RUN_LANE_LABEL,
1729
+ color: "0e8a16",
1730
+ description: "Judged safe to run beside others in a backlogrun"
1731
+ },
1732
+ ...FILING_ROUTE_LABELS,
1733
+ ...DEPTH_LABELS
1734
+ ];
1635
1735
  function has_any_label(labels, wanted) {
1636
1736
  return (labels ?? []).some((label) => wanted.has(label.name.toLowerCase()));
1637
1737
  }
@@ -3520,6 +3620,8 @@ export {
3520
3620
  BREAKING_CHANGE_LABEL,
3521
3621
  IN_PROGRESS_LABEL,
3522
3622
  NEEDS_DECISION_LABEL,
3623
+ RUN_SOLO_LABEL,
3624
+ RUN_LANE_LABEL,
3523
3625
  NEEDS_HUMAN_REVIEW_LABEL,
3524
3626
  ALREADY_DONE_LABEL,
3525
3627
  has_any_label,
@@ -1016,15 +1016,23 @@ var PROJECT_COMMANDS = {
1016
1016
  },
1017
1017
  start: {
1018
1018
  script: "scripts/init/start.ts",
1019
- description: "Initialize a project for the GitHub Issue workflow",
1019
+ description: "Create a new project for the GitHub Issue workflow, from git init to labels",
1020
1020
  category: "Project",
1021
- reference: [PROFILE_ARGUMENTS, "developer", ["files", "git", "network"]]
1021
+ reference: [
1022
+ `${PROFILE_ARGUMENTS} [--yes] [--github] [--public]`,
1023
+ "developer",
1024
+ ["files", "git", "network", "processes"]
1025
+ ]
1022
1026
  },
1023
1027
  init: {
1024
1028
  script: "scripts/init/init.ts",
1025
1029
  description: "Initialize config in a new project",
1026
1030
  category: "Project",
1027
- reference: [PROFILE_ARGUMENTS, "developer", ["files", "processes"]]
1031
+ reference: [
1032
+ `${PROFILE_ARGUMENTS} [--no-install]`,
1033
+ "developer",
1034
+ ["files", "network", "processes"]
1035
+ ]
1028
1036
  },
1029
1037
  sync: {
1030
1038
  script: "scripts/sync/sync.ts",
@@ -1165,9 +1173,9 @@ var WORKFLOW_COMMANDS = {
1165
1173
  reference: ["--task-type <type> --body <text>", "automation", ["notifications"]],
1166
1174
  tsx_arguments: OPTIONAL_ENV_FILE_FLAGS
1167
1175
  },
1168
- // The observation ledger's only commit path (joshuafolkken/kit#1756). `pnpm josh git` excludes the
1169
- // ledger from what it stages, so without this command a parent's appended line has no route to the
1170
- // default branch at all — and the recurrence count the promotion rule reads is a count of main.
1176
+ // The observation ledger's commit path for the lines no run's own commit carried
1177
+ // (joshuafolkken/kit#1756, joshuafolkken/kit#2763): a lane's lines sit in the primary checkout, which
1178
+ // its commit cannot see, and the recurrence count the promotion rule reads is a count of main.
1171
1179
  "observations:flush": {
1172
1180
  script: "scripts/observations/observations-flush-cli.ts",
1173
1181
  description: "Commit the observation ledger as a pull request of its own, and merge it",
@@ -1493,6 +1501,7 @@ var time_shell = {
1493
1501
  var NO_BACKGROUND = "";
1494
1502
  var NO_FINISH = 0;
1495
1503
  var LAUNCH_PATTERN = /running in background with ID:\s*([\w-]+)/iu;
1504
+ var AGENT_LAUNCH_PATTERN = /^Async agent launched[\s\S]*?\bagentId:\s*([\w-]+)/u;
1496
1505
  var FINISH_PATTERN = /^<task-notification>[\s\S]*?<task-id>([\w-]+)<\/task-id>/u;
1497
1506
  var OUTPUT_PATTERN = /tasks\/([\w-]+)\.output/u;
1498
1507
  var BASH_ID_KEY = "bash_id";
@@ -1507,6 +1516,9 @@ function read_id_of(input) {
1507
1516
  function launch_id(text) {
1508
1517
  return LAUNCH_PATTERN.exec(text)?.[1] ?? NO_BACKGROUND;
1509
1518
  }
1519
+ function agent_launch_id(text) {
1520
+ return AGENT_LAUNCH_PATTERN.exec(text)?.[1] ?? NO_BACKGROUND;
1521
+ }
1510
1522
  function finished_id(text) {
1511
1523
  return FINISH_PATTERN.exec(text)?.[1] ?? NO_BACKGROUND;
1512
1524
  }
@@ -1571,6 +1583,7 @@ var time_background = {
1571
1583
  NO_BACKGROUND,
1572
1584
  NO_FINISH,
1573
1585
  launch_id,
1586
+ agent_launch_id,
1574
1587
  finished_id,
1575
1588
  finished_at,
1576
1589
  read_id,
@@ -2406,7 +2419,8 @@ function to_block2(raw) {
2406
2419
  followup_stages: time_followup_stage.read_stages(text),
2407
2420
  error_text: is_error === true ? text.slice(0, ERROR_TEXT_LIMIT) : "",
2408
2421
  refusal_guard: is_error === true ? guard_from_refusal(text) : "",
2409
- background_id: time_background.launch_id(text)
2422
+ background_id: time_background.launch_id(text),
2423
+ agent_id: time_background.agent_launch_id(text)
2410
2424
  };
2411
2425
  }
2412
2426
  function to_blocks2(content) {
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  investigation_reads
3
- } from "./chunk-M4U4YNRE.js";
3
+ } from "./chunk-F6OM6SIG.js";
4
4
  import {
5
5
  lane_guard_policy
6
- } from "./chunk-OM5VDM6F.js";
6
+ } from "./chunk-2K52BRWV.js";
7
7
  import {
8
8
  hook_decision
9
- } from "./chunk-O3PEDF3M.js";
9
+ } from "./chunk-KQG4776P.js";
10
10
  import {
11
11
  json_value,
12
12
  time_spans
13
- } from "./chunk-EYFJ74MJ.js";
13
+ } from "./chunk-ZPLXLJ7O.js";
14
14
 
15
15
  // scripts/delegation/duplicate-read-guard.ts
16
16
  import { statSync } from "node:fs";