@joshuafolkken/kit 1.1128.0 → 1.1166.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 (192) hide show
  1. package/.claude/agents/investigator.md +17 -0
  2. package/.claude/settings.json +5 -4
  3. package/.claude/skills/epic-commands/SKILL.md +27 -239
  4. package/.claude/skills/epic-commands/epic-bundle.md +115 -0
  5. package/.claude/skills/epic-commands/execution-waves.md +126 -0
  6. package/.claude/skills/workflow-commands/SKILL.md +48 -194
  7. package/.claude/skills/workflow-commands/background-commands.md +1 -1
  8. package/.claude/skills/workflow-commands/backlogrun-child.md +12 -123
  9. package/.claude/skills/workflow-commands/backlogrun-lanes.md +11 -60
  10. package/.claude/skills/workflow-commands/backlogrun-park.md +10 -8
  11. package/.claude/skills/workflow-commands/backlogrun-progress.md +19 -175
  12. package/.claude/skills/workflow-commands/backlogrun-recovery.md +155 -0
  13. package/.claude/skills/workflow-commands/backlogrun-steps.md +64 -144
  14. package/.claude/skills/workflow-commands/backlogrun.md +5 -4
  15. package/.claude/skills/workflow-commands/chain-rule.md +3 -2
  16. package/.claude/skills/workflow-commands/delegation.md +7 -3
  17. package/.claude/skills/workflow-commands/entry-sequence.md +62 -0
  18. package/.claude/skills/workflow-commands/followup.md +104 -181
  19. package/.claude/skills/workflow-commands/fullrun-steps.md +8 -9
  20. package/.claude/skills/workflow-commands/fullrun.md +16 -73
  21. package/.claude/skills/workflow-commands/halfrun.md +10 -36
  22. package/.claude/skills/workflow-commands/into-target.md +4 -4
  23. package/.claude/skills/workflow-commands/issue-comments.md +7 -8
  24. package/.claude/skills/workflow-commands/issue-scout.md +6 -6
  25. package/.claude/skills/workflow-commands/kickoff.md +6 -6
  26. package/.claude/skills/workflow-commands/needs-human-review.md +6 -6
  27. package/.claude/skills/workflow-commands/observation-filing.md +21 -172
  28. package/.claude/skills/workflow-commands/observation-ledger.md +150 -0
  29. package/.claude/skills/workflow-commands/pre-gate-cut.md +33 -93
  30. package/.claude/skills/workflow-commands/prerequisite.md +6 -6
  31. package/.claude/skills/workflow-commands/progress-watcher.md +90 -0
  32. package/.claude/skills/workflow-commands/prrun.md +5 -5
  33. package/.claude/skills/workflow-commands/retrospective.md +9 -9
  34. package/.claude/skills/workflow-commands/split-assessment.md +2 -2
  35. package/.claude/skills/workflow-commands/target-repository.md +2 -2
  36. package/.claude/skills/workflow-commands/working-tree-hold.md +7 -5
  37. package/.codex/hooks.json +4 -4
  38. package/AGENTS.md +1 -6
  39. package/CLAUDE.md +58 -120
  40. package/GEMINI.md +1 -6
  41. package/SECURITY.md +1 -0
  42. package/dist/CLAUDE.md +58 -120
  43. package/dist/hooks/batch-guard.js +4 -4
  44. package/dist/hooks/{chunk-MKVGBQPA.js → chunk-5KTCMCOT.js} +14 -1
  45. package/dist/hooks/{chunk-NXYMNPUQ.js → chunk-A6P7PN7P.js} +59 -21
  46. package/dist/hooks/{chunk-77J7IEQM.js → chunk-ANJ2WGFD.js} +1 -1
  47. package/dist/hooks/{chunk-4JZDP5X5.js → chunk-DYAVAM6S.js} +572 -127
  48. package/dist/hooks/{chunk-7HVB7SZG.js → chunk-ENA7P5FB.js} +4 -4
  49. package/dist/hooks/{chunk-M7QKEKWO.js → chunk-GU5MATVE.js} +214 -85
  50. package/dist/hooks/chunk-GYNGP7HI.js +56 -0
  51. package/dist/hooks/{chunk-5SD2YM7Z.js → chunk-URJA6XY4.js} +4 -4
  52. package/dist/hooks/{chunk-XYUIQOW3.js → chunk-V2BTOZHT.js} +6 -4
  53. package/dist/hooks/{chunk-AIOIOMPX.js → chunk-ZDZ555YH.js} +225 -78
  54. package/dist/hooks/codex-hook-adapter.js +10 -10
  55. package/dist/hooks/duplicate-read-guard.js +5 -5
  56. package/dist/hooks/format-edited.js +2 -2
  57. package/dist/hooks/inputs.json +186 -184
  58. package/dist/hooks/investigation-guard.js +5 -5
  59. package/dist/hooks/pretool-guard.js +11 -10
  60. package/dist/hooks/stop-guard.js +5 -5
  61. package/dist/josh.js +33 -3
  62. package/dist/version/index.js +5 -1
  63. package/package.json +7 -4
  64. package/prompts/collaboration-workflow/file-edits.md +11 -10
  65. package/prompts/collaboration-workflow/gh-rest.md +2 -4
  66. package/prompts/collaboration-workflow/glossary.md +1 -1
  67. package/prompts/collaboration-workflow/issue-citation.md +5 -9
  68. package/prompts/collaboration-workflow/issue-template.md +40 -115
  69. package/prompts/collaboration-workflow/operating-rules.md +4 -5
  70. package/prompts/collaboration-workflow/output-bounds.md +12 -78
  71. package/prompts/collaboration-workflow/overview.md +7 -13
  72. package/prompts/collaboration-workflow/plan-comment.md +11 -39
  73. package/prompts/collaboration-workflow/principles.md +3 -4
  74. package/prompts/collaboration-workflow/report-format.md +28 -74
  75. package/prompts/collaboration-workflow/residency.md +12 -109
  76. package/prompts/collaboration-workflow/rule-delivery.md +98 -97
  77. package/prompts/collaboration-workflow/shell-body.md +3 -13
  78. package/prompts/collaboration-workflow/turn-batching.md +14 -88
  79. package/prompts/collaboration-workflow/upstream-interrupt.md +12 -14
  80. package/prompts/collaboration-workflow/wip-cap.md +6 -45
  81. package/prompts/collaboration-workflow.md +1 -13
  82. package/prompts/review-rubric.md +8 -71
  83. package/prompts/review.md +25 -55
  84. package/prompts/testing-guide.md +18 -67
  85. package/scripts/agent/agent-role-profile.ts +1 -1
  86. package/scripts/agent/codex-hooks.ts +3 -12
  87. package/scripts/backlog/backlog-budget.ts +1 -1
  88. package/scripts/backlog/backlog-drive-cli.ts +4 -3
  89. package/scripts/backlog/backlog-drive-offer-argv.ts +10 -1
  90. package/scripts/backlog/backlog-offer.ts +6 -6
  91. package/scripts/behavior/behavior-rules.ts +11 -4
  92. package/scripts/delegation/delegation-policy.ts +9 -1
  93. package/scripts/delegation/investigation-guard.ts +1 -1
  94. package/scripts/delegation/investigation-reads.ts +109 -23
  95. package/scripts/discovery/repo-discovery.ts +21 -7
  96. package/scripts/discovery/repo-origin.ts +26 -0
  97. package/scripts/discovery/repo-party.ts +42 -1
  98. package/scripts/document/backlogrun-parent-read-set.ts +14 -15
  99. package/scripts/document/capped-print.ts +74 -0
  100. package/scripts/document/document-byte-budget.ts +34 -20
  101. package/scripts/document/document-section-cli.ts +4 -3
  102. package/scripts/document/entry-read-budget.ts +21 -9
  103. package/scripts/document/lane-child-read-set.ts +12 -14
  104. package/scripts/document/read-set-cli.ts +4 -4
  105. package/scripts/document/read-set-point-of-use.ts +10 -2
  106. package/scripts/document/resident-budget.ts +47 -0
  107. package/scripts/epic/epic-shape.ts +10 -13
  108. package/scripts/epic/epic-solo.ts +1 -1
  109. package/scripts/exports/unused-members-cli.ts +7 -3
  110. package/scripts/gate/scoped-green.ts +25 -14
  111. package/scripts/gh/git-gh-api-path.ts +7 -0
  112. package/scripts/hooks/pretool-guard-cli.ts +2 -2
  113. package/scripts/hooks/pretool-guard.ts +45 -2
  114. package/scripts/hooks/run-hook.sh +41 -0
  115. package/scripts/init/claude-kit-only-settings.ts +29 -0
  116. package/scripts/init/hook-command-rewrite.ts +18 -35
  117. package/scripts/init/hook-launch.ts +21 -32
  118. package/scripts/init/init-copy-content.ts +8 -2
  119. package/scripts/init/initial-commit.ts +24 -0
  120. package/scripts/init/start-exec.ts +16 -3
  121. package/scripts/init/start-steps.ts +8 -12
  122. package/scripts/issue/issue-auto-ok.ts +95 -0
  123. package/scripts/issue/issue-file-cli.ts +53 -7
  124. package/scripts/issue/issue-file.ts +28 -5
  125. package/scripts/issue/issue-labels.ts +12 -12
  126. package/scripts/issue/issue-rank.ts +1 -1
  127. package/scripts/issue/issue-read-cli.ts +4 -3
  128. package/scripts/issue/issue-read.ts +2 -2
  129. package/scripts/issue/issue-report-failures.ts +6 -2
  130. package/scripts/issue/issue-scout-cli.ts +2 -2
  131. package/scripts/issue/issue-state-cli.ts +2 -2
  132. package/scripts/issue/issue-state.ts +2 -2
  133. package/scripts/issue/issue-wip.ts +70 -0
  134. package/scripts/josh/hook-decision.ts +17 -0
  135. package/scripts/josh/josh-commands-development.ts +10 -0
  136. package/scripts/josh/josh-commands-issue.ts +2 -2
  137. package/scripts/josh/josh-commands-lane.ts +1 -1
  138. package/scripts/josh/josh-commands-project.ts +7 -0
  139. package/scripts/lane/lane-await.ts +46 -2
  140. package/scripts/lane/lane-cli.ts +11 -13
  141. package/scripts/lines/bytes-command.ts +29 -6
  142. package/scripts/observations/observation-ledger-line.ts +1 -1
  143. package/scripts/release/github-release-environment.ts +1 -0
  144. package/scripts/release/github-release.ts +103 -17
  145. package/scripts/release/release-progress.ts +167 -0
  146. package/scripts/release/release-publish.ts +19 -8
  147. package/scripts/release/release-tag.ts +22 -8
  148. package/scripts/rules/decision-oracle-stage.ts +1 -1
  149. package/scripts/rules/decision-oracle.ts +6 -6
  150. package/scripts/rules/delivered-rules.ts +11 -11
  151. package/scripts/rules/direct-pr-create.ts +110 -0
  152. package/scripts/rules/file-body.ts +98 -42
  153. package/scripts/rules/filing-cap.ts +2 -2
  154. package/scripts/rules/filing-offer.ts +1 -1
  155. package/scripts/rules/gh-api.ts +18 -1
  156. package/scripts/rules/implementation-cut.ts +3 -1
  157. package/scripts/rules/lane-background.ts +67 -10
  158. package/scripts/rules/permission-guards.ts +10 -2
  159. package/scripts/rules/stop-rules.ts +4 -4
  160. package/scripts/rules/test-declared-commit.ts +1 -1
  161. package/scripts/rules/third-party-write.ts +56 -12
  162. package/scripts/run/run-carry-cli.ts +3 -1
  163. package/scripts/run/run-carry-conversation.ts +94 -0
  164. package/scripts/run/run-carry-reclaim.ts +37 -0
  165. package/scripts/run/run-carry.ts +21 -5
  166. package/scripts/run/run-cut-handoff.ts +21 -1
  167. package/scripts/run/run-entry-cli.ts +11 -3
  168. package/scripts/run/run-hold-cli.ts +16 -2
  169. package/scripts/run/run-label.ts +50 -0
  170. package/scripts/run/run-merge-steps.ts +12 -4
  171. package/scripts/run/run-merge.ts +1 -1
  172. package/scripts/run/run-prep-cli.ts +6 -2
  173. package/scripts/run/run-prep.ts +17 -0
  174. package/scripts/run/run-ship-preflight.ts +34 -3
  175. package/scripts/run/run-ship-review-steps.ts +1 -1
  176. package/scripts/run/run-ship-review.ts +26 -5
  177. package/scripts/run/run-step-cli.ts +20 -7
  178. package/scripts/run/run-wake-driver.ts +24 -1
  179. package/scripts/run/run-wake-session.ts +2 -1
  180. package/scripts/test/test-declared-logic.ts +1 -1
  181. package/scripts/test/test-related.ts +1 -6
  182. package/scripts/test/test-unit-guard.ts +21 -7
  183. package/scripts/time-runtime/time-batch-guard.ts +52 -16
  184. package/scripts/time-runtime/time-density-hook.ts +20 -2
  185. package/scripts/time-runtime/time-density.ts +4 -3
  186. package/scripts/time-runtime/time-markers.ts +15 -4
  187. package/scripts/time-runtime/time-spans.ts +4 -0
  188. package/scripts/version/npm-registry.ts +11 -1
  189. package/templates/workflows/ci.yml +9 -1
  190. package/templates/workflows/github-release.yml +13 -25
  191. package/.claude/skills/workflow-commands/followup-reference.md +0 -118
  192. package/dist/hooks/chunk-QVLT52OV.js +0 -464
@@ -7,51 +7,40 @@ import { GIT_LOCATION_VARIABLES } from '#scripts/git/git-location-environment'
7
7
  //
8
8
  // **The command starts from the project root, never from wherever the session happens to be**
9
9
  // (joshuafolkken/kit#2984). Every path after the prefix is relative, so a hook fired from a
10
- // subdirectory would otherwise miss its bundle. The root is git's, with the location variables a git
11
- // hook exports cleared first — the same resolution the consumer rewrite (`hook-command-rewrite.ts`)
12
- // applies, so both sides stay one mechanism for Claude Code and Codex alike.
10
+ // subdirectory would otherwise miss its script. The root is git's, resolved with the location
11
+ // variables a git hook exports cleared first, and resolved with them only when that fails — git
12
+ // metadata outside the work tree. The consumer rewrite (`hook-command-rewrite.ts`) keeps this prefix,
13
+ // so both sides stay one mechanism for Claude Code and Codex alike.
13
14
  //
14
- // **The bundle runs only once the ready gate passes** (`scripts/hooks/hook-bundle-ready.ts`): it
15
- // rebuilds `dist/hooks/` when the source beside it has changed, so an edited guard is never shadowed by
16
- // its old bundle. The gate failing — a checkout that cannot build — drops to the fallback, the live
17
- // source. **An `if`/`else`, never `&&`/`||`**: a guard's refusal is a non-zero exit, and a
18
- // `node … || pnpm josh …` chain would read that refusal as a missing bundle and run the hook twice.
15
+ // **Everything after the root is one script** (`scripts/hooks/run-hook.sh`, joshuafolkken/kit#3184):
16
+ // clearing the location variables for the hook itself, the bundle-ready gate and the live-source
17
+ // fallback live there once, so each hook command names only its hook.
19
18
 
20
19
  const HOOK_DIST_DIR = 'dist/hooks'
20
+ const RUN_HOOK_SCRIPT = 'scripts/hooks/run-hook.sh'
21
21
  const UNSET_OPTIONS = GIT_LOCATION_VARIABLES.map((name) => `-u ${name}`).join(' ')
22
- const PROJECT_ROOT_PREFIX = `if project_root="$(env ${UNSET_OPTIONS} git rev-parse --show-toplevel 2>/dev/null)"; then unset ${GIT_LOCATION_VARIABLES.join(' ')}; else project_root="$(git rev-parse --show-toplevel)" || exit 1; fi; cd "$project_root" && `
23
- // Plain `node` runs the gate's TypeScript directly; the flag keeps Node 22's type-stripping notice off
24
- // every hook's stderr.
25
- const BUNDLE_READY_GATE =
26
- 'node --disable-warning=ExperimentalWarning scripts/hooks/hook-bundle-ready.ts'
22
+ const ROOT_LOOKUP = 'git rev-parse --show-toplevel'
23
+ const PROJECT_ROOT_PREFIX = `project_root="$(env ${UNSET_OPTIONS} ${ROOT_LOOKUP} 2>/dev/null || ${ROOT_LOOKUP})" && cd "$project_root" && `
27
24
 
28
- // Run `primary_command` when `gate` exits 0, else `fallback_command` — both verbatim. Kept generic so
29
- // a test can exercise the branch selection with stub commands rather than real hooks.
30
- function launch_command(gate: string, primary_command: string, fallback_command: string): string {
31
- return `if ${gate}; then ${primary_command}; else ${fallback_command}; fi`
25
+ // `name` is the hook's bundle under `dist/hooks` without `.js` (e.g. `pretool-guard`), followed by any
26
+ // arguments it takes (e.g. `codex-hook-adapter pretool`).
27
+ function hook_launch_command(name: string): string {
28
+ return `${PROJECT_ROOT_PREFIX}sh ${RUN_HOOK_SCRIPT} ${name}`
32
29
  }
33
30
 
34
- // `invocation` is the hook's file under `dist/hooks` plus any arguments (e.g. `pretool-guard.js`);
35
- // `fallback_command` runs the same hook from source.
36
- function bundle_launch_command(invocation: string, fallback_command: string): string {
37
- const primary = `node ${HOOK_DIST_DIR}/${invocation}`
38
-
39
- return `${PROJECT_ROOT_PREFIX}${launch_command(BUNDLE_READY_GATE, primary, fallback_command)}`
40
- }
41
-
42
- // `bundle` is the hook's file under `dist/hooks` (e.g. `pretool-guard.js`); `command` is the josh
43
- // subcommand the fallback runs (e.g. `pretool:guard`).
44
- function hook_launch_command(bundle: string, command: string): string {
45
- return bundle_launch_command(bundle, `pnpm josh ${command}`)
31
+ // The josh subcommand the launcher falls back to for hook `name`: its first `-` becomes `:`
32
+ // (`pretool-guard` → `pretool:guard`). The launcher derives it in shell; this is that rule for the tests
33
+ // that hold every wired hook to a subcommand josh has.
34
+ function fallback_command(name: string): string {
35
+ return name.replace('-', ':')
46
36
  }
47
37
 
48
38
  const hook_launch = {
49
- BUNDLE_READY_GATE,
50
- bundle_launch_command,
39
+ fallback_command,
51
40
  HOOK_DIST_DIR,
52
41
  hook_launch_command,
53
- launch_command,
54
42
  PROJECT_ROOT_PREFIX,
43
+ RUN_HOOK_SCRIPT,
55
44
  }
56
45
 
57
46
  export { hook_launch }
@@ -1,6 +1,7 @@
1
1
  import { managed_marker_logic } from '#scripts/managed-marker/managed-marker-logic'
2
2
  import { workflow_pin_logic } from '#scripts/sync/workflow-pin-logic'
3
3
  import { KIT_PACKAGE_NAME } from '#scripts/version/kit-descriptor'
4
+ import { claude_kit_only_settings } from './claude-kit-only-settings'
4
5
  import { claude_plugin_config } from './claude-plugin-config'
5
6
  import { hook_command_rewrite } from './hook-command-rewrite'
6
7
  import { init_logic } from './init-logic'
@@ -19,7 +20,8 @@ import { init_logic } from './init-logic'
19
20
  * workflow-pin-logic.ts for why the template refs are not authoritative, and
20
21
  * managed-marker-logic.ts for why the stamp is on the file rather than in a list.
21
22
  *
22
- * The consumer's `.claude/settings.json` gets two further passes. The kit plugin's marketplace and
23
+ * The consumer's `.claude/settings.json` gets three further passes. The keys kit sets only for its
24
+ * own sessions are removed (joshuafolkken/kit#3140, see claude-kit-only-settings.ts). The kit plugin's marketplace and
23
25
  * `enabledPlugins` declaration is injected, because the skills that used to be copied now ship as the
24
26
  * `kit` plugin and load from that declaration (joshuafolkken/kit#1879). And its hook commands are
25
27
  * rewritten from `pnpm josh` to the published bundle invoked directly with node
@@ -36,10 +38,14 @@ function transform_copied_content(destination_path: string, content: string): st
36
38
  with_pins,
37
39
  KIT_PACKAGE_NAME,
38
40
  )
39
- const with_plugin = claude_plugin_config.apply_plugin_config_for_destination(
41
+ const with_kit_only_stripped = claude_kit_only_settings.apply_kit_only_strip_for_destination(
40
42
  destination_path,
41
43
  with_marker,
42
44
  )
45
+ const with_plugin = claude_plugin_config.apply_plugin_config_for_destination(
46
+ destination_path,
47
+ with_kit_only_stripped,
48
+ )
43
49
 
44
50
  return hook_command_rewrite.apply_hook_command_rewrite_for_destination(
45
51
  destination_path,
@@ -0,0 +1,24 @@
1
+ import { start_exec } from './start-exec'
2
+
3
+ // The first commit of a new repository, shared by `josh start` and `josh dogfood:commit` so neither
4
+ // keeps a copy of how it is made.
5
+
6
+ const DEFAULT_BRANCH = 'main'
7
+
8
+ function create_repository(root: string): void {
9
+ start_exec.run('git', ['init', `--initial-branch=${DEFAULT_BRANCH}`], root)
10
+ }
11
+
12
+ function commit_all(root: string): void {
13
+ start_exec.run('git', ['add', '--all'], root)
14
+ // The first commit of a new repository has no branch to come from, so the hook that keeps commits
15
+ // off main — installed by the initialize step in a full project — would refuse the only commit
16
+ // that has to land there.
17
+ start_exec.run('git', ['commit', '--no-verify', '--message', 'Initial commit'], root)
18
+ // A repository created by an older `git init` may name its unborn branch `master`.
19
+ start_exec.run('git', ['branch', '--move', '--force', DEFAULT_BRANCH], root)
20
+ }
21
+
22
+ const initial_commit = { create_repository, commit_all }
23
+
24
+ export { initial_commit }
@@ -1,10 +1,21 @@
1
1
  import { git_gh_exec } from '#scripts/gh/git-gh-exec'
2
+ import { git_location_environment } from '#scripts/git/git-location-environment'
2
3
  import { execaSync } from 'execa'
3
4
 
4
5
  // The three spawn shapes every `josh start` step uses, shared by `start-steps.ts`, the setup pull
5
6
  // request step and the `josh init` hint so none of them keeps a copy of its own.
7
+ //
8
+ // The three git-driving shapes below act on `root`, so `cwd` has to mean it: an inherited `GIT_DIR` and friends beat `cwd`
9
+ // and would carry a `git init` or commit into whichever repository a calling hook was firing in
10
+ // (joshuafolkken/kit#3151).
11
+ function root_environment(): Record<string, string | undefined> {
12
+ return git_location_environment.location_free_environment()
13
+ }
14
+
6
15
  function succeeds(command: string, args: ReadonlyArray<string>, root: string): boolean {
7
- return execaSync(command, args, { cwd: root, reject: false }).exitCode === 0
16
+ return (
17
+ execaSync(command, args, { cwd: root, env: root_environment(), reject: false }).exitCode === 0
18
+ )
8
19
  }
9
20
 
10
21
  function read_output(
@@ -12,7 +23,7 @@ function read_output(
12
23
  args: ReadonlyArray<string>,
13
24
  root: string,
14
25
  ): string | undefined {
15
- const result = execaSync(command, args, { cwd: root, reject: false })
26
+ const result = execaSync(command, args, { cwd: root, env: root_environment(), reject: false })
16
27
 
17
28
  return result.exitCode === 0 ? result.stdout.trim() : undefined
18
29
  }
@@ -20,7 +31,9 @@ function read_output(
20
31
  // Every step spawned here dials GitHub directly, past a scanner's loopback proxy, the same as every
21
32
  // other `gh` spawn under scripts/ (joshuafolkken/kit#2436); a local `git` step is unaffected by it.
22
33
  function run(command: string, args: ReadonlyArray<string>, root: string): void {
23
- execaSync(command, args, { ...git_gh_exec.direct_environment(), cwd: root, stdio: 'inherit' })
34
+ const environment = { ...root_environment(), ...git_gh_exec.direct_environment().env }
35
+
36
+ execaSync(command, args, { env: environment, cwd: root, stdio: 'inherit' })
24
37
  }
25
38
 
26
39
  // A caller's own initialize command stands in for the in-process `josh init`, so it keeps the
@@ -6,6 +6,7 @@ import { self_sync_guard } from '#scripts/self-sync-guard/self-sync-guard-logic'
6
6
  import { parseCommandString } from 'execa'
7
7
  import { main as init_main } from './init'
8
8
  import { PACKAGE_DIR } from './init-paths'
9
+ import { initial_commit } from './initial-commit'
9
10
  import { kit_setup_state } from './kit-setup-state'
10
11
  import type { ProjectProfile, ProjectShape } from './project-profile'
11
12
  import { start_exec } from './start-exec'
@@ -23,7 +24,6 @@ interface RunContext extends StepContext {
23
24
  baseline: ReadonlyArray<string> | undefined
24
25
  }
25
26
 
26
- const DEFAULT_BRANCH = 'main'
27
27
  const GH_INSTALL_HINT =
28
28
  'GitHub CLI (gh) is not installed. Install it from https://cli.github.com/, run gh auth login, then run josh start again. Nothing was changed.'
29
29
  const GH_AUTH_HINT =
@@ -70,7 +70,7 @@ function self_run_refusal(root: string): string | undefined {
70
70
  }
71
71
 
72
72
  function create_git_repository(context: StepContext): void {
73
- run('git', ['init', `--initial-branch=${DEFAULT_BRANCH}`], context.root)
73
+ initial_commit.create_repository(context.root)
74
74
  }
75
75
 
76
76
  // The same setup `josh init` runs, called rather than copied, with the profile the user confirmed.
@@ -90,13 +90,7 @@ function initialize(context: StepContext): void {
90
90
  }
91
91
 
92
92
  function commit_all(context: StepContext): void {
93
- run('git', ['add', '--all'], context.root)
94
- // The first commit of a new repository has no branch to come from, so the hook that keeps commits
95
- // off main — installed by the initialize step in a full project — would refuse the only commit
96
- // that has to land there.
97
- run('git', ['commit', '--no-verify', '--message', 'Initial commit'], context.root)
98
- // A repository created by an older `git init` may name its unborn branch `master`.
99
- run('git', ['branch', '--move', '--force', DEFAULT_BRANCH], context.root)
93
+ initial_commit.commit_all(context.root)
100
94
  }
101
95
 
102
96
  function create_github_repository(context: StepContext): void {
@@ -119,13 +113,15 @@ async function open_setup_pull_request(context: RunContext): Promise<void> {
119
113
  await start_setup_pr.open(context.root, context.baseline)
120
114
  }
121
115
 
122
- // What was already changed before a caller's initialize command ran, read only when the setup pull
123
- // request will need to tell that command's files from the user's own (#2872).
116
+ // What was already changed before the setup ran, read whenever a setup pull request will follow, so
117
+ // it can tell the setup's files from the user's own (#2872). Kit's own `josh init` needs it as much as
118
+ // a caller's command: its `josh format` rewrites the project's own sources, which kit cannot name, and
119
+ // leaving them out failed the pull request's Prettier check (joshuafolkken/kit#3136).
124
120
  function baseline_of(
125
121
  steps: ReadonlyArray<StartStep>,
126
122
  context: StepContext,
127
123
  ): ReadonlyArray<string> | undefined {
128
- if (context.init_command === undefined || !steps.includes('setup_pr')) return undefined
124
+ if (!steps.includes('setup_pr')) return undefined
129
125
 
130
126
  return start_setup_pr.changed_paths(context.root)
131
127
  }
@@ -0,0 +1,95 @@
1
+ import { git_branch } from '#scripts/git/git-branch'
2
+ import { git_command } from '#scripts/git/git-command'
3
+ import { run_carry } from '#scripts/run/run-carry'
4
+ import { issue_file } from './issue-file'
5
+ import { AUTO_OK_LABEL, has_label_name } from './issue-labels'
6
+ import { issue_state_cli } from './issue-state-cli'
7
+
8
+ // Whether `josh issue:file` applies `auto-ok` to the Issue it files (joshuafolkken/kit#3213). Work
9
+ // found during an unattended run used to wait in the backlog until a person labelled it, so the
10
+ // filing now computes the answer instead of leaving it to the agent's memory: an Issue filed while a
11
+ // `backlogrun` carry record is live, or from a branch whose Issue carries `auto-ok`, is opted in by
12
+ // default. `--no-auto-ok` is the one exception, declared for an Issue that needs a person's judgement
13
+ // (a Tier B toss-up or a Tier C action). A filing to another repository is never opted in: that
14
+ // repository's own `backlogrun` would implement and merge it though nobody opted that repository in.
15
+
16
+ interface AutoOkSignals {
17
+ is_opted_out: boolean
18
+ is_carried: boolean
19
+ // The labels of the Issue the current branch names; `undefined` when the branch names none or the
20
+ // Issue could not be read.
21
+ branch_labels: ReadonlyArray<string> | undefined
22
+ }
23
+
24
+ interface AutoOkDecision {
25
+ is_applied: boolean
26
+ reason: string
27
+ }
28
+
29
+ const OPTED_OUT: AutoOkDecision = { is_applied: false, reason: '--no-auto-ok declared' }
30
+ const CROSS_REPOSITORY: AutoOkDecision = {
31
+ is_applied: false,
32
+ reason: 'the Issue is filed to another repository',
33
+ }
34
+ const CARRIED: AutoOkDecision = { is_applied: true, reason: 'a backlogrun carry record is live' }
35
+ const BRANCH_OPTED_IN: AutoOkDecision = {
36
+ is_applied: true,
37
+ reason: 'the Issue this branch works on carries auto-ok',
38
+ }
39
+ const NOT_OPTED_IN: AutoOkDecision = {
40
+ is_applied: false,
41
+ reason: 'no backlogrun carry record is live and the branch names no auto-ok Issue',
42
+ }
43
+
44
+ // The opt-out wins over every signal, then the carry record, then the branch's Issue.
45
+ function decide(signals: AutoOkSignals): AutoOkDecision {
46
+ if (signals.is_opted_out) return OPTED_OUT
47
+ if (signals.is_carried) return CARRIED
48
+ if (has_label_name(signals.branch_labels ?? [], AUTO_OK_LABEL)) return BRANCH_OPTED_IN
49
+
50
+ return NOT_OPTED_IN
51
+ }
52
+
53
+ function line_of(decision: AutoOkDecision): string {
54
+ return `auto-ok: ${decision.is_applied ? 'applied' : 'not applied'} — ${decision.reason}`
55
+ }
56
+
57
+ // Read from the common git directory, so a lane sees the record its batch wrote in the main tree.
58
+ async function is_carried(): Promise<boolean> {
59
+ const directory = await run_carry.repository_directory()
60
+
61
+ if (directory === undefined) return false
62
+
63
+ return run_carry.read_carry(run_carry.carry_path(directory)).kind === 'carried'
64
+ }
65
+
66
+ // An unreadable Issue is read as no labels: the filing goes on without `auto-ok` rather than failing.
67
+ // The branch names an Issue of `current`, read there by name: a filing to another repository has
68
+ // already pointed `GH_REPO` at its target, which would otherwise answer with the target's Issue.
69
+ async function branch_labels(current: string): Promise<ReadonlyArray<string> | undefined> {
70
+ const issue_number = git_branch.issue_from_branch(await git_command.branch())
71
+
72
+ if (issue_number === undefined) return undefined
73
+ const read = await issue_state_cli.read_issue(String(issue_number), current)
74
+
75
+ return read.kind === 'state' ? read.state.labels : undefined
76
+ }
77
+
78
+ // Each read is skipped once an earlier signal has already decided the answer; a cross-repository
79
+ // filing is refused before any signal is read.
80
+ async function resolve(
81
+ is_opted_out: boolean,
82
+ target: string,
83
+ current: string,
84
+ ): Promise<AutoOkDecision> {
85
+ if (is_opted_out) return OPTED_OUT
86
+ if (!issue_file.is_same_repository(target, current)) return CROSS_REPOSITORY
87
+ if (await is_carried()) return CARRIED
88
+
89
+ return decide({ is_opted_out, is_carried: false, branch_labels: await branch_labels(current) })
90
+ }
91
+
92
+ const issue_auto_ok = { decide, line_of, resolve }
93
+
94
+ export type { AutoOkDecision, AutoOkSignals }
95
+ export { issue_auto_ok }
@@ -5,24 +5,30 @@ import { repo_party } from '#scripts/discovery/repo-party'
5
5
  import { epic_bundle_cli } from '#scripts/epic/epic-bundle-cli'
6
6
  import { git_gh_command } from '#scripts/gh/git-gh-command'
7
7
  import { git_gh_exec } from '#scripts/gh/git-gh-exec'
8
+ import { git_gh_issue_list, MAX_SCANNED } from '#scripts/gh/git-gh-issue-list'
8
9
  import { git_gh_issue_write } from '#scripts/gh/git-gh-issue-write'
9
10
  import { github_issue_url } from '#scripts/gh/github-issue-url'
10
11
  import { error_text } from '#scripts/lib/error-message'
12
+ import { repository_labels } from '#scripts/repo/repository-labels'
13
+ import { issue_auto_ok } from './issue-auto-ok'
11
14
  import { issue_file, type FileArguments } from './issue-file'
12
15
  import { issue_lint_cli } from './issue-lint-cli'
13
16
  import { issue_scout_cli } from './issue-scout-cli'
17
+ import { issue_wip } from './issue-wip'
14
18
 
15
19
  // `josh issue:file "<title>" --body-file <path> --depth <0|1|2> [--route <route>] [--label <name>]…
16
- // [--repo <owner/repo>] [--distinct <N,…>]` — file an Issue with every filing step run in order
17
- // (joshuafolkken/kit#2808): the third-party refusal, the body lint, the `## Origin` check for another
18
- // repository, the duplicate scout, the create call carrying every label, and `epic:bundle` after it.
20
+ // [--repo <owner/repo>] [--distinct <N,…>] [--over-cap] [--no-auto-ok]` — file an Issue with every
21
+ // filing step run in order (joshuafolkken/kit#2808): the third-party refusal, the body lint, the
22
+ // `## Origin` check for another repository, the WIP cap count (joshuafolkken/kit#3181), the duplicate
23
+ // scout, the missing workflow labels created (joshuafolkken/kit#3176), the `auto-ok` decision
24
+ // (joshuafolkken/kit#3213), the create call carrying every label, and `epic:bundle` after it.
19
25
  // A direct `gh api …/issues` filing is refused by the `direct-filing` delivered rule and pointed here.
20
26
 
21
27
  const SUCCESS_EXIT_CODE = 0
22
28
  const FAILURE_EXIT_CODE = 1
23
29
  const ARGV_OFFSET = 2
24
30
  const USAGE =
25
- 'Usage: josh issue:file "<title>" --body-file <path> --depth <0|1|2> [--route <tier-a|split|interrupt|review-cap>] [--label <name>]… [--repo <owner/repo>] [--distinct <N,…>]'
31
+ 'Usage: josh issue:file "<title>" --body-file <path> --depth <0|1|2> [--route <tier-a|split|interrupt|review-cap>] [--label <name>]… [--repo <owner/repo>] [--distinct <N,…>] [--over-cap] [--no-auto-ok]'
26
32
  const UNKNOWN_REPO_MESSAGE =
27
33
  'Could not read this repository from `git remote`, so the filing has no repository to compare against — check `gh auth status`.'
28
34
  const THIRD_PARTY_MESSAGE =
@@ -68,6 +74,37 @@ function unacknowledged_message(numbers: ReadonlyArray<number>): string {
68
74
  return `✖ duplicate candidate(s) ${listed} not declared separate — read each (\`issue-fold-existing.md\`); fold into a duplicate, or reissue with \`--distinct ${listed}\` when each is a separate deliverable.`
69
75
  }
70
76
 
77
+ // The target's open Issues, pull requests excluded by the listing itself; `undefined` when unreadable.
78
+ async function open_count(target: string): Promise<number | undefined> {
79
+ const outcome = await git_gh_issue_list.issue_list({
80
+ json_fields: 'number',
81
+ limit: MAX_SCANNED,
82
+ repo: target,
83
+ })
84
+
85
+ return outcome.json === undefined ? undefined : issue_wip.count_of(outcome.json)
86
+ }
87
+
88
+ // The count is printed whatever it answers, and the filing is held only past the cap with no
89
+ // exemption declared. An unreadable count warns and goes on: the cap makes growth visible, and a
90
+ // listing outage is not a reason to stop the run a blocked filing exists for.
91
+ async function is_wip_clear(filing: Filing): Promise<boolean> {
92
+ const count = await open_count(filing.target)
93
+
94
+ if (count === undefined) {
95
+ console.error(issue_wip.UNREAD_MESSAGE)
96
+
97
+ return true
98
+ }
99
+
100
+ const verdict = issue_wip.verdict_of(count, filing.args)
101
+
102
+ console.info(issue_wip.count_line(count, filing.target, verdict))
103
+ if (verdict === issue_wip.HELD) console.error(issue_wip.HELD_MESSAGE)
104
+
105
+ return verdict !== issue_wip.HELD
106
+ }
107
+
71
108
  // Set before the scout and left set for `epic:bundle`, so both read the target's backlog.
72
109
  function point_listings_at(filing: Filing): void {
73
110
  if (issue_file.is_same_repository(filing.target, filing.current)) return
@@ -94,12 +131,21 @@ async function is_scout_clear(filing: Filing): Promise<boolean> {
94
131
  }
95
132
 
96
133
  // `undefined` when the create call failed. `gh`'s standard error is captured into the thrown error
97
- // rather than printed, so it is printed here — otherwise the filing fails with no reason given.
134
+ // rather than printed, so it is printed here — otherwise the filing fails with no reason given. A label
135
+ // the create applies is provisioned first, so it never arrives with a generated color.
98
136
  async function create(filing: Filing): Promise<string | undefined> {
137
+ repository_labels.ensure_labels(filing.target)
138
+ const auto_ok = await issue_auto_ok.resolve(
139
+ filing.args.is_auto_ok_opted_out,
140
+ filing.target,
141
+ filing.current,
142
+ )
143
+
144
+ console.info(issue_auto_ok.line_of(auto_ok))
99
145
  const request = git_gh_issue_write.issue_create_request({
100
146
  title: filing.args.title,
101
147
  body: filing.body,
102
- labels: issue_file.labels_of(filing.args, filing.body),
148
+ labels: issue_file.labels_of(filing.args, filing.body, auto_ok.is_applied),
103
149
  repo: filing.target,
104
150
  })
105
151
 
@@ -136,7 +182,7 @@ async function file(filing: Filing): Promise<number> {
136
182
  return FAILURE_EXIT_CODE
137
183
  }
138
184
 
139
- if (!(await is_scout_clear(filing))) return FAILURE_EXIT_CODE
185
+ if (!(await is_wip_clear(filing)) || !(await is_scout_clear(filing))) return FAILURE_EXIT_CODE
140
186
  const url = await create(filing)
141
187
 
142
188
  if (url === undefined) return FAILURE_EXIT_CODE
@@ -4,7 +4,12 @@ import { github_issue_url } from '#scripts/gh/github-issue-url'
4
4
  import { cli_flags } from '#scripts/lib/cli-flags'
5
5
  import { issue_backlinks } from './issue-backlinks'
6
6
  import { issue_classification } from './issue-classification'
7
- import { DEPTH_LABEL_ORDER, FILING_ROUTE_LABELS, has_label_name } from './issue-labels'
7
+ import {
8
+ AUTO_OK_LABEL,
9
+ DEPTH_LABEL_ORDER,
10
+ FILING_ROUTE_LABELS,
11
+ has_label_name,
12
+ } from './issue-labels'
8
13
  import { markdown_section } from './markdown-section'
9
14
 
10
15
  // The decisions behind `josh issue:file` (joshuafolkken/kit#2808), kept apart from the command that
@@ -31,6 +36,10 @@ interface FileArguments {
31
36
  labels: ReadonlyArray<string>
32
37
  repo: string | undefined
33
38
  distinct: ReadonlyArray<number>
39
+ // `--over-cap`: the run is blocked by this filing, so the WIP cap does not hold it (`issue-wip.ts`).
40
+ is_over_cap: boolean
41
+ // `--no-auto-ok`: the Issue needs a person's judgement, so `auto-ok` is not applied (`issue-auto-ok.ts`).
42
+ is_auto_ok_opted_out: boolean
34
43
  }
35
44
 
36
45
  const OPTIONS = {
@@ -40,6 +49,8 @@ const OPTIONS = {
40
49
  label: { type: 'string', multiple: true },
41
50
  repo: { type: 'string' },
42
51
  distinct: { type: 'string', multiple: true },
52
+ 'over-cap': { type: 'boolean' },
53
+ 'no-auto-ok': { type: 'boolean' },
43
54
  } as const
44
55
 
45
56
  // The label a `--depth` / `--route` value names, or `undefined` when it names none — read against the
@@ -101,7 +112,15 @@ function arguments_of(values: ParsedValues, title: string | undefined): FileArgu
101
112
 
102
113
  if (labelled === undefined || !has_text(title) || !has_text(body_file)) return undefined
103
114
 
104
- return { title, body_file, ...labelled, labels: values.label ?? [], repo: values.repo }
115
+ return {
116
+ title,
117
+ body_file,
118
+ ...labelled,
119
+ labels: values.label ?? [],
120
+ repo: values.repo,
121
+ is_over_cap: values['over-cap'] === true,
122
+ is_auto_ok_opted_out: values['no-auto-ok'] === true,
123
+ }
105
124
  }
106
125
 
107
126
  // An unknown flag is `undefined` rather than a throw, so the answer is the usage line, not a stack trace.
@@ -121,12 +140,16 @@ function is_undeclared_classification(label: string, body: string): boolean {
121
140
 
122
141
  // Every label the filing carries: the depth, the route when there is one, the classification labels
123
142
  // the body declares (`issue_classification.labels_for`, which merges case-insensitively), then any
124
- // extra `--label`. One request applies them all, so no label is a separate step to forget.
125
- function labels_of(args: FileArguments, body: string): ReadonlyArray<string> {
143
+ // extra `--label`, then `auto-ok` when the filing opted in (`issue-auto-ok.ts`) and no `--label`
144
+ // already named it. One request applies them all, so no label is a separate step to forget.
145
+ function labels_of(args: FileArguments, body: string, is_auto_ok = false): ReadonlyArray<string> {
126
146
  const extra = args.labels.filter((label) => !is_undeclared_classification(label, body))
127
147
  const declared = [args.depth, ...(args.route === undefined ? [] : [args.route]), ...extra]
148
+ const labels = issue_classification.labels_for(body, declared)
128
149
 
129
- return issue_classification.labels_for(body, declared)
150
+ if (!is_auto_ok || has_label_name(labels, AUTO_OK_LABEL)) return labels
151
+
152
+ return [...labels, AUTO_OK_LABEL]
130
153
  }
131
154
 
132
155
  function is_same_repository(target: string, current: string): boolean {
@@ -16,7 +16,7 @@ const PLANNED_LABEL = 'run:planned'
16
16
  // forever (joshuafolkken/kit#860).
17
17
  const NEEDS_DECISION_LABEL = 'needs-decision'
18
18
  // Marks an issue whose defect reaches the verification path, so it runs with nothing beside it
19
- // (joshuafolkken/kit#2776). A run applies it from the verification-path enumeration in `wip-cap.md`;
19
+ // (joshuafolkken/kit#2776). A run applies it from the verification-path enumeration in `backlogrun-lanes.md`;
20
20
  // `backlog:next` enforces it (`backlog-solo.ts`), so the rule no longer rests on a judgement at
21
21
  // dispatch time.
22
22
  const RUN_SOLO_LABEL = 'run:solo'
@@ -36,11 +36,12 @@ const PRIORITY_HIGH_LABEL = 'priority:high'
36
36
  const AUTO_OK_LABEL = 'auto-ok'
37
37
  // Degrades one issue's run to a `halfrun`-shaped stop: it is implemented and taken through the
38
38
  // verification gate, and then nothing is committed, pushed, opened as a pull request or merged
39
- // (joshuafolkken/kit#1125). **Only a person applies it**, exactly as strongly as `auto-ok` — a mark a
40
- // run could clear for itself is not a mark.
39
+ // (joshuafolkken/kit#1125). **Only a person applies it** — a mark a run could clear for itself is not
40
+ // a mark.
41
41
  //
42
- // It is the opposite of `auto-ok` in what it does and its twin in who may apply it: one widens
43
- // unattended execution past an epic's edge, the other withholds the last step of it. `auto-ok`
42
+ // It is the opposite of `auto-ok` in what it does: one widens unattended execution past an epic's
43
+ // edge, the other withholds the last step of it. Unlike `auto-ok`, which `issue:file` applies by
44
+ // default to work found by opted-in work (joshuafolkken/kit#3213), no run ever applies it. `auto-ok`
44
45
  // answers "may this run at all", this one answers "may its result ship without a person looking".
45
46
  //
46
47
  // **Not `needs-decision`, and the difference is what the two sets below encode.** A parked issue is
@@ -154,7 +155,7 @@ const FILING_ROUTE_LABELS: ReadonlyArray<{
154
155
  //
155
156
  // **The depth is read off the subject, so a run applies it.** Unlike `auto-ok` and
156
157
  // `needs-human-review` this is not a person's judgement about authorization; it is the same reading
157
- // §2i already asks a run to make before it files, and none of the three withholds or widens anything
158
+ // `observation-filing.md` already asks a run to make before it files, and none of the three withholds or widens anything
158
159
  // a run may do.
159
160
  const DEPTH_0_LABEL = 'depth:0'
160
161
  const DEPTH_1_LABEL = 'depth:1'
@@ -171,9 +172,8 @@ const DEPTH_LABEL_ORDER: ReadonlyArray<string> = [DEPTH_0_LABEL, DEPTH_1_LABEL,
171
172
  // description, so provisioning from here is what gives each one a stable color a reader can scan by.
172
173
  // Green, amber and pale blue, so the consumer-facing depth is the one that stands out in a listing.
173
174
  //
174
- // **The provisioning command lives in `observation-filing.md`**, because prose cannot import this module —
175
- // the same split `FILING_ROUTE_LABELS` above lives with. `scripts/rules/issue-depth-label.test.ts` keys
176
- // those three command lines to this array, so a color changed here without the document fails.
175
+ // **`josh issue:file` provisions them** through `repository_labels.ensure_labels` before its create
176
+ // call (joshuafolkken/kit#3176), so no document carries a creation command to keep in step.
177
177
  const DEPTH_LABELS: ReadonlyArray<{
178
178
  name: string
179
179
  color: string
@@ -182,17 +182,17 @@ const DEPTH_LABELS: ReadonlyArray<{
182
182
  {
183
183
  name: DEPTH_0_LABEL,
184
184
  color: '0e8a16',
185
- description: 'Depth 0 — what a consumer of this package touches (SKILL.md §2i)',
185
+ description: 'Depth 0 — what a consumer of this package touches (observation-filing.md)',
186
186
  },
187
187
  {
188
188
  name: DEPTH_1_LABEL,
189
189
  color: 'fbc02d',
190
- description: 'Depth 1 — the run orchestration that executes an Issue (SKILL.md §2i)',
190
+ description: 'Depth 1 — the run orchestration that executes an Issue (observation-filing.md)',
191
191
  },
192
192
  {
193
193
  name: DEPTH_2_LABEL,
194
194
  color: 'c5def5',
195
- description: 'Depth 2 — what measures a run (SKILL.md §2i)',
195
+ description: 'Depth 2 — what measures a run (observation-filing.md)',
196
196
  },
197
197
  ]
198
198
 
@@ -15,7 +15,7 @@ import {
15
15
  //
16
16
  // **`run:solo` alone is not a key.** It means "runs alone", not "runs first": tidying that only touches
17
17
  // the verification path carries it too, and a low-priority one is not owed a place ahead of the queue.
18
- // Only a defect on that path is, because `wip-cap.md` holds a batch until it has merged.
18
+ // Only a defect on that path is, because `backlogrun-lanes.md` holds a batch until it has merged.
19
19
 
20
20
  interface RankRow {
21
21
  labels: ReadonlyArray<string>
@@ -8,7 +8,7 @@ import { issue_report_failures, type ReadFailureKind } from './issue-report-fail
8
8
  // `josh issue:read <N> [<N> ...]` — the body *and* the comments of every issue named, in one call
9
9
  // (joshuafolkken/kit#1715).
10
10
  //
11
- // It replaces the two `gh api` reads `.claude/skills/workflow-commands/SKILL.md` §2g tells an agent
11
+ // It replaces the two `gh api` reads `.claude/skills/workflow-commands/issue-comments.md` tells an agent
12
12
  // to type per issue. Measured over four recorded `backlogrun` parents, `issue bookkeeping` was the
13
13
  // largest single contributor to the parent's turn count — 110 of 414 turns, 26.6% — and one issue
14
14
  // read at a time was its dominant shape: 19 `…/issues/<N>` calls and 5 `…/comments` calls, each its
@@ -23,7 +23,7 @@ import { issue_report_failures, type ReadFailureKind } from './issue-report-fail
23
23
  // prevent.
24
24
  //
25
25
  // **A failed read is never printed as an issue**, and neither is a failed comment listing printed as
26
- // "no comments" — the block says which of the two it holds, because §2g's rule is that the later text
26
+ // "no comments" — the block says which of the two it holds, because `issue-comments.md`'s rule is that the later text
27
27
  // wins, and a comment nobody read cannot win anything.
28
28
 
29
29
  const FAILURE_EXIT_CODE = 1
@@ -88,7 +88,7 @@ async function read_issue(issue_number: string): Promise<NumberResult> {
88
88
 
89
89
  // One issue's block for a caller that wants the text rather than the printing — `run:prep` composes it
90
90
  // with the state block and the dependency-update line. A failed read is returned as its kind, never as
91
- // an empty block, so §2g's "a comment nobody read cannot win anything" holds through the bundle too.
91
+ // an empty block, so `issue-comments.md`'s "a comment nobody read cannot win anything" holds through the bundle too.
92
92
  async function read_block(issue_number: string): Promise<BlockRead> {
93
93
  const result = await read_issue(issue_number)
94
94
 
@@ -129,6 +129,7 @@ async function run(argv: ReadonlyArray<string>): Promise<number> {
129
129
  issue_report_failures.print_blocks(
130
130
  reports.flatMap((report) => issue_blocks(report)),
131
131
  BLOCK_SEPARATOR,
132
+ `issue:read ${issue_numbers.join(' ')}`,
132
133
  )
133
134
 
134
135
  return issue_report_failures.report_failures(reports, FAILURE_TERMS)
@@ -6,7 +6,7 @@ import { z } from 'zod'
6
6
  // (joshuafolkken/kit#1715).
7
7
  //
8
8
  // **It exists because reading one issue costs a parent two turns.** `.claude/skills/workflow-commands/SKILL.md`
9
- // §2g requires the body *and* the comments, since a decision recorded after the body was written
9
+ // `issue-comments.md` requires the body *and* the comments, since a decision recorded after the body was written
10
10
  // lives only in a comment; typed by hand that is `gh api repos/{owner}/{repo}/issues/<N>` followed by
11
11
  // `gh api repos/{owner}/{repo}/issues/<N>/comments`. Measured over four recorded `backlogrun`
12
12
  // parents, `issue bookkeeping` was the single largest contributor to the parent's turn count — 110 of
@@ -15,7 +15,7 @@ import { z } from 'zod'
15
15
  // distribution this collapses.
16
16
  //
17
17
  // **The comments are never silently absent.** A listing that could not be read prints a line saying
18
- // so, because a block showing no comment where the read failed is exactly the misread §2g exists to
18
+ // so, because a block showing no comment where the read failed is exactly the misread `issue-comments.md` exists to
19
19
  // prevent: the agreement in force would be taken from a body a comment had already overturned.
20
20
 
21
21
  const ISSUE_LABEL = 'issue: '