@joshuafolkken/kit 1.1387.0 → 1.1453.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 (278) hide show
  1. package/.claude/skills/epic-commands/SKILL.md +1 -1
  2. package/.claude/skills/epic-commands/epic-bundle.md +5 -0
  3. package/.claude/skills/workflow-commands/backlogrun-lanes.md +5 -3
  4. package/.claude/skills/workflow-commands/backlogrun-park.md +2 -0
  5. package/.claude/skills/workflow-commands/backlogrun-steps.md +13 -10
  6. package/.claude/skills/workflow-commands/backlogrun.md +3 -2
  7. package/.claude/skills/workflow-commands/chain-rule.md +4 -2
  8. package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
  9. package/.claude/skills/workflow-commands/halfrun.md +1 -1
  10. package/.claude/skills/workflow-commands/issue-comments.md +6 -0
  11. package/.claude/skills/workflow-commands/kickoff.md +12 -13
  12. package/.claude/skills/workflow-commands/observation-filing.md +1 -1
  13. package/.claude/skills/workflow-commands/observation-ledger.md +3 -2
  14. package/.claude/skills/workflow-commands/pre-gate-cut.md +5 -3
  15. package/.claude/skills/workflow-commands/prerequisite.md +6 -0
  16. package/.claude/skills/workflow-commands/retrospective.md +5 -5
  17. package/.claude/skills/workflow-commands/split-assessment.md +2 -2
  18. package/README.md +27 -4
  19. package/dist/hooks/audit-provision.js +2 -2
  20. package/dist/hooks/batch-guard.js +7 -7
  21. package/dist/hooks/{chunk-2YSW3CXH.js → chunk-3C4XRYG6.js} +35 -11
  22. package/dist/hooks/{chunk-GJ7JPIHN.js → chunk-5C7L6FRC.js} +2704 -190
  23. package/dist/hooks/{chunk-FZ6FUSBM.js → chunk-723HKZO5.js} +1 -1
  24. package/dist/hooks/{chunk-STDSEPWU.js → chunk-D3NJXN5M.js} +4 -4
  25. package/dist/hooks/{chunk-NEC4AT4Z.js → chunk-DGT54IH5.js} +4 -0
  26. package/dist/hooks/{chunk-LMWBRJIP.js → chunk-EF5GQ6WS.js} +3 -3
  27. package/dist/hooks/{chunk-JBBMAPC4.js → chunk-IX6TTTI6.js} +4 -0
  28. package/dist/hooks/{chunk-IYVOLN6A.js → chunk-LBFYEOXJ.js} +3 -3
  29. package/dist/hooks/{chunk-SL4GQ2XP.js → chunk-MBJYQPEK.js} +106 -23
  30. package/dist/hooks/{chunk-WNFJLBXI.js → chunk-NO4AALP5.js} +3 -3
  31. package/dist/hooks/{chunk-J3KSS7II.js → chunk-OLMO72ON.js} +1 -1
  32. package/dist/hooks/{chunk-DCY2GWHC.js → chunk-SHYUQOIS.js} +5 -5
  33. package/dist/hooks/{chunk-EAQEGME7.js → chunk-UT7YC3N2.js} +1 -1
  34. package/dist/hooks/{chunk-EBSHN25W.js → chunk-W5ZKGTSY.js} +4 -4
  35. package/dist/hooks/{chunk-GNDJ7NJ7.js → chunk-Y3GKIKGW.js} +209 -56
  36. package/dist/hooks/{chunk-U32VSRNG.js → chunk-Z4XI7CZS.js} +89 -34
  37. package/dist/hooks/codex-hook-adapter.js +15 -15
  38. package/dist/hooks/duplicate-read-guard.js +8 -8
  39. package/dist/hooks/format-edited.js +5 -5
  40. package/dist/hooks/inputs.json +261 -201
  41. package/dist/hooks/investigation-guard.js +8 -8
  42. package/dist/hooks/pretool-guard.js +14 -14
  43. package/dist/hooks/session-lang.js +69 -16
  44. package/dist/hooks/stop-guard.js +13 -38
  45. package/dist/josh.js +95 -19
  46. package/package.json +4 -3
  47. package/prompts/collaboration-workflow/issue-template.md +10 -8
  48. package/prompts/review.md +30 -4
  49. package/scripts/agent/claude-agent-argv.ts +13 -1
  50. package/scripts/auth/auth-setup-cli.ts +101 -0
  51. package/scripts/auth/claude-token-output.ts +78 -0
  52. package/scripts/auth/claude-token.ts +68 -0
  53. package/scripts/auto-ok/auto-ok-judge-cli.ts +125 -0
  54. package/scripts/auto-ok/auto-ok-judge-read.ts +95 -0
  55. package/scripts/auto-ok/auto-ok-judge-write.ts +109 -0
  56. package/scripts/auto-ok/auto-ok-judge.ts +67 -0
  57. package/scripts/backlog/backlog-drive-cli.ts +14 -0
  58. package/scripts/backlog/backlog-drive-epic.ts +24 -8
  59. package/scripts/backlog/backlog-drive-launch.ts +3 -0
  60. package/scripts/backlog/backlog-drive-named-offer.ts +7 -2
  61. package/scripts/backlog/backlog-drive-named.ts +4 -2
  62. package/scripts/backlog/backlog-drive-unread.ts +56 -0
  63. package/scripts/backlog/backlog-drive.ts +38 -11
  64. package/scripts/backlog/backlog-overlap.ts +22 -5
  65. package/scripts/backlog/backlog-ready.ts +1 -3
  66. package/scripts/backlog/backlogrun-args.ts +13 -2
  67. package/scripts/backlog/backlogrun-cli.ts +91 -19
  68. package/scripts/backlog/backlogrun-launch.ts +24 -0
  69. package/scripts/backlog/backlogrun-replace.ts +166 -0
  70. package/scripts/backlog/backlogrun-stop.ts +178 -0
  71. package/scripts/behavior/behavior-cli.ts +6 -1
  72. package/scripts/doctor/doctor-io.ts +60 -9
  73. package/scripts/doctor/doctor.ts +20 -1
  74. package/scripts/document/document-byte-budget.ts +3 -3
  75. package/scripts/epic/epic-bundle-cli.ts +8 -8
  76. package/scripts/epic/epic-bundle-referenced.ts +2 -5
  77. package/scripts/epic/epic-bundle.ts +39 -4
  78. package/scripts/exports/unused-members-cli.ts +2 -1
  79. package/scripts/followup/followup-checks-event.ts +82 -0
  80. package/scripts/followup/git-followup-finish.ts +16 -2
  81. package/scripts/followup/git-followup-workflow.ts +5 -2
  82. package/scripts/followup/git-pr-followup-checks.ts +18 -4
  83. package/scripts/followup/git-pr-followup-wrapup.ts +5 -0
  84. package/scripts/followup/git-pr-followup.ts +4 -1
  85. package/scripts/fresh-machine/fresh-machine-cli.ts +204 -0
  86. package/scripts/fresh-machine/fresh-machine-docker.ts +65 -0
  87. package/scripts/fresh-machine/fresh-machine-image.ts +62 -0
  88. package/scripts/fresh-machine/fresh-machine-log.ts +61 -0
  89. package/scripts/fresh-machine/fresh-machine-patterns.ts +175 -0
  90. package/scripts/fresh-machine/fresh-machine-run.ts +215 -0
  91. package/scripts/fresh-machine/fresh-machine-script.ts +45 -0
  92. package/scripts/fresh-machine/fresh-machine-spec.ts +57 -0
  93. package/scripts/fresh-machine/specs/kit-test-backlogrun-python.json +34 -0
  94. package/scripts/fresh-machine/specs/kit-test-backlogrun.json +31 -0
  95. package/scripts/gate/gate-report.ts +70 -15
  96. package/scripts/gate/project-checks.ts +18 -4
  97. package/scripts/gate/scoped-green.ts +2 -1
  98. package/scripts/gate/verification-gate.ts +4 -3
  99. package/scripts/gh/gh-author-association.ts +14 -0
  100. package/scripts/gh/git-gh-conditional.ts +66 -0
  101. package/scripts/gh/git-gh-exec.ts +1 -1
  102. package/scripts/gh/git-gh-issue-read.ts +6 -3
  103. package/scripts/gh/git-gh-issue-rest.ts +1 -0
  104. package/scripts/gh/git-gh-issue-write.ts +5 -1
  105. package/scripts/gh/git-gh-issue.ts +13 -1
  106. package/scripts/gh/git-pr-checks-eval.ts +13 -0
  107. package/scripts/gh/git-pr-checks-parse.ts +8 -1
  108. package/scripts/gh/git-pr-checks-watch.ts +11 -4
  109. package/scripts/gh/git-pr-checks.ts +36 -2
  110. package/scripts/git/git-command.ts +16 -4
  111. package/scripts/git/git-schemas.ts +1 -0
  112. package/scripts/git/git-spawn-sync.ts +1 -1
  113. package/scripts/hooks/pretool-guard.ts +3 -1
  114. package/scripts/init/hook-command-rewrite.ts +31 -0
  115. package/scripts/init/init-actions.ts +7 -4
  116. package/scripts/init/init-ai-copy.ts +34 -11
  117. package/scripts/init/init-basic.ts +9 -4
  118. package/scripts/init/init-bootstrap.ts +6 -2
  119. package/scripts/init/init-dry-run-report.ts +150 -0
  120. package/scripts/init/init-dry-run.ts +189 -0
  121. package/scripts/init/init-install.ts +8 -1
  122. package/scripts/init/init-logic-toolchain.ts +76 -0
  123. package/scripts/init/init-logic.ts +2 -0
  124. package/scripts/init/init.ts +62 -42
  125. package/scripts/init/kit-setup-state.ts +11 -17
  126. package/scripts/init/own-formatter.ts +21 -0
  127. package/scripts/init/project-profile.ts +60 -12
  128. package/scripts/init/start-dry-run.ts +70 -0
  129. package/scripts/init/start-exec.ts +27 -1
  130. package/scripts/init/start-plan.ts +23 -3
  131. package/scripts/init/start-setup-pr.ts +13 -24
  132. package/scripts/init/start-steps.ts +62 -1
  133. package/scripts/init/start.ts +38 -17
  134. package/scripts/init/workflow-mark.ts +35 -0
  135. package/scripts/init/working-tree.ts +116 -0
  136. package/scripts/issue/issue-ack-cli.ts +84 -0
  137. package/scripts/issue/issue-auto-ok.ts +68 -99
  138. package/scripts/issue/issue-file-cli.ts +17 -17
  139. package/scripts/issue/issue-file.ts +1 -6
  140. package/scripts/issue/issue-labels.ts +46 -10
  141. package/scripts/issue/issue-release-cli.ts +69 -23
  142. package/scripts/issue/issue-release.ts +3 -1
  143. package/scripts/issue/issue-state-cli.ts +14 -4
  144. package/scripts/issue/issue-watch-diff.ts +152 -0
  145. package/scripts/issue/issue-watch-gate.ts +81 -0
  146. package/scripts/issue/issue-watch-hook.ts +54 -0
  147. package/scripts/issue/issue-watch.ts +240 -0
  148. package/scripts/issue/measure-rerun-cli.ts +3 -4
  149. package/scripts/josh/josh-command-map.ts +4 -0
  150. package/scripts/josh/josh-commands-ai.ts +11 -0
  151. package/scripts/josh/josh-commands-backlog.ts +2 -2
  152. package/scripts/josh/josh-commands-development.ts +1 -1
  153. package/scripts/josh/josh-commands-issue.ts +10 -3
  154. package/scripts/josh/josh-commands-lane.ts +11 -2
  155. package/scripts/josh/josh-commands-project.ts +26 -2
  156. package/scripts/josh/josh-verdict.ts +33 -4
  157. package/scripts/josh/process-identity.ts +32 -6
  158. package/scripts/lane/claude-lane-broker.ts +142 -0
  159. package/scripts/lane/lane-await.ts +6 -2
  160. package/scripts/lane/lane-broker-server.ts +69 -0
  161. package/scripts/lane/lane-cache.ts +37 -7
  162. package/scripts/lane/lane-capacity.ts +8 -6
  163. package/scripts/lane/lane-child-invocation.ts +1 -0
  164. package/scripts/lane/lane-cli.ts +15 -1
  165. package/scripts/lane/lane-close.ts +15 -6
  166. package/scripts/lane/lane-dispatch.ts +36 -10
  167. package/scripts/lane/lane-environment.ts +1 -1
  168. package/scripts/lane/lane-launch-cli.ts +13 -4
  169. package/scripts/lane/lane-ledger.ts +21 -3
  170. package/scripts/lane/lane-measure-cli.ts +10 -2
  171. package/scripts/lane/lane-open.ts +9 -1
  172. package/scripts/lane/lane-preserve.ts +49 -0
  173. package/scripts/lane/lane-reap.ts +20 -5
  174. package/scripts/lane/lane-registry.ts +6 -0
  175. package/scripts/lane/lane-restart-cli.ts +51 -0
  176. package/scripts/lane/lane-restart.ts +175 -0
  177. package/scripts/lane/lane-session-broker.ts +363 -0
  178. package/scripts/lane/lane-spent.ts +195 -0
  179. package/scripts/lane/lane-stage-stats.ts +27 -3
  180. package/scripts/lane/lane-stop-stats.ts +96 -0
  181. package/scripts/lane/openai-lane-supervisor.ts +2 -2
  182. package/scripts/lane/process-tools.ts +31 -0
  183. package/scripts/lib/buffered-process.ts +36 -2
  184. package/scripts/lib/pnpm-reach.ts +27 -0
  185. package/scripts/lib/status-icons.ts +5 -1
  186. package/scripts/lint/lint-parallel.ts +19 -2
  187. package/scripts/observations/observation-ledger-home.ts +1 -0
  188. package/scripts/observations/observation-ledger-migrate.ts +3 -15
  189. package/scripts/release/release-issue-close.ts +133 -0
  190. package/scripts/release/release-publish.ts +12 -1
  191. package/scripts/repo/repository-labels.ts +11 -1
  192. package/scripts/review/comment-ranges.ts +199 -0
  193. package/scripts/review/review-attest-cli.ts +21 -6
  194. package/scripts/review/review-brief-cli.ts +24 -7
  195. package/scripts/review/review-code-tree.ts +119 -0
  196. package/scripts/review/review-comment-round.ts +114 -0
  197. package/scripts/review/review-record-cli.ts +39 -17
  198. package/scripts/review/review-round2-cli.ts +16 -5
  199. package/scripts/review/review-round2.ts +30 -10
  200. package/scripts/review/review-stamps.ts +22 -1
  201. package/scripts/rules/implementation-cut.ts +3 -3
  202. package/scripts/rules/pre-gate-cut.ts +1 -1
  203. package/scripts/run/board/run-board-checks.ts +42 -0
  204. package/scripts/run/board/run-board-cli.ts +2 -0
  205. package/scripts/run/board/run-board-every.ts +3 -1
  206. package/scripts/run/board/run-board-fit.ts +16 -5
  207. package/scripts/run/board/run-board-fresh.ts +64 -0
  208. package/scripts/run/board/run-board-header.ts +129 -53
  209. package/scripts/run/board/run-board-labels.ts +47 -4
  210. package/scripts/run/board/run-board-lane-event.ts +66 -0
  211. package/scripts/run/board/run-board-layout.ts +57 -9
  212. package/scripts/run/board/run-board-now.ts +234 -0
  213. package/scripts/run/board/run-board-operation-read.ts +75 -0
  214. package/scripts/run/board/run-board-operation.ts +150 -0
  215. package/scripts/run/board/run-board-phase.ts +26 -1
  216. package/scripts/run/board/run-board-plan.ts +4 -6
  217. package/scripts/run/board/run-board-read.ts +49 -3
  218. package/scripts/run/board/run-board-reason.ts +124 -0
  219. package/scripts/run/board/run-board-render-checks.ts +55 -0
  220. package/scripts/run/board/run-board-render-legend.ts +12 -3
  221. package/scripts/run/board/run-board-render-slot.ts +2 -2
  222. package/scripts/run/board/run-board-render-tail.ts +128 -0
  223. package/scripts/run/board/run-board-render-thanks.ts +50 -0
  224. package/scripts/run/board/run-board-render-title.ts +58 -0
  225. package/scripts/run/board/run-board-render.ts +96 -80
  226. package/scripts/run/board/run-board-row-yield.ts +32 -0
  227. package/scripts/run/board/run-board-setup.ts +61 -13
  228. package/scripts/run/board/run-board-state.ts +10 -0
  229. package/scripts/run/board/run-board-status.ts +66 -7
  230. package/scripts/run/board/run-board-thanks-csv.ts +71 -0
  231. package/scripts/run/board/run-board-thanks-read.ts +112 -0
  232. package/scripts/run/board/run-board-thanks.ts +53 -0
  233. package/scripts/run/board/run-board-tick.ts +50 -21
  234. package/scripts/run/board/run-board-usual.ts +44 -0
  235. package/scripts/run/carry/run-carry-args.ts +9 -1
  236. package/scripts/run/carry/run-carry-cli.ts +2 -45
  237. package/scripts/run/carry/run-carry-end.ts +73 -0
  238. package/scripts/run/carry/run-carry.ts +8 -1
  239. package/scripts/run/entry/run-entry-cli.ts +5 -0
  240. package/scripts/run/event/run-event-checks.ts +54 -0
  241. package/scripts/run/event/run-event-render.ts +2 -0
  242. package/scripts/run/event/run-event-scope.ts +1 -0
  243. package/scripts/run/event/run-event-setup.ts +55 -11
  244. package/scripts/run/event/run-event-stream.ts +27 -11
  245. package/scripts/run/merge/run-merge-cli.ts +7 -4
  246. package/scripts/run/merge/run-merge-ship.ts +61 -0
  247. package/scripts/run/merge/run-merge-steps.ts +7 -14
  248. package/scripts/run/merge/run-merge-token.ts +16 -2
  249. package/scripts/run/run-headless.ts +1 -0
  250. package/scripts/run/run-step.ts +2 -0
  251. package/scripts/run/ship/run-ship-cli.ts +22 -2
  252. package/scripts/run/ship/run-ship-return.ts +31 -13
  253. package/scripts/run/ship/run-ship-review-steps.ts +17 -4
  254. package/scripts/run/ship/run-ship-stage.ts +1 -0
  255. package/scripts/run/wake/run-wake-await.ts +130 -0
  256. package/scripts/run/wake/run-wake-cli.ts +60 -71
  257. package/scripts/run/wake/run-wake-describe.ts +34 -2
  258. package/scripts/run/wake/run-wake-driver.ts +11 -4
  259. package/scripts/run/wake/run-wake-session.ts +20 -3
  260. package/scripts/run/wake/run-wake-stop.ts +37 -0
  261. package/scripts/run/wake/run-wake-supervise.ts +44 -0
  262. package/scripts/sync/claude-settings-merge.ts +144 -0
  263. package/scripts/sync/sync-ai-files.ts +28 -3
  264. package/scripts/sync/sync.ts +15 -0
  265. package/scripts/test/project-test-script.ts +73 -0
  266. package/scripts/test/skip-marker.ts +14 -1
  267. package/scripts/test/test-related.ts +3 -2
  268. package/scripts/test/test-unit-guard.ts +78 -14
  269. package/scripts/time-runtime/time-reported-failure.ts +11 -3
  270. package/scripts/uninstall/distributed-files.ts +92 -0
  271. package/scripts/uninstall/uninstall-plan.ts +119 -0
  272. package/scripts/uninstall/uninstall-report.ts +61 -0
  273. package/scripts/uninstall/uninstall.ts +206 -0
  274. package/templates/AGENTS.basic.md +4 -0
  275. package/templates/GEMINI.basic.md +6 -0
  276. package/dist/hooks/chunk-6V5OPIUU.js +0 -13
  277. package/scripts/lane/openai-review-broker.ts +0 -262
  278. package/scripts/run/board/run-board-track.ts +0 -22
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env tsx
2
+ import { fileURLToPath } from 'node:url'
3
+ import { git_command } from '#scripts/git/git-command'
4
+ import { issue_number_shape } from '#scripts/issue/issue-number-shape'
5
+ import { session_cite } from '#scripts/issue/session-cite'
6
+ import { issue_watch, type Acknowledged } from './issue-watch'
7
+
8
+ // `josh issue:ack <N>` — record that the run has read a person's update to its issue, so the hook,
9
+ // the review attestation and `josh followup` stop raising it. It prints the update it acknowledges:
10
+ // the same read becomes the new baseline (`issue-watch.ts` → `acknowledge`), so nothing that arrived
11
+ // after the hook spoke can be acknowledged without being printed here.
12
+ //
13
+ // **The number is asked for, though the record already carries it**, so an acknowledgement typed for
14
+ // one issue cannot silently clear the watch on another.
15
+
16
+ const SUCCESS_EXIT_CODE = 0
17
+ const FAILURE_EXIT_CODE = 1
18
+ const ARGV_OFFSET = 2
19
+ const USAGE = 'Usage: josh issue:ack <issue-number>'
20
+ const NO_WATCH_LINE =
21
+ 'No run recorded a baseline for that issue in this checkout (`josh run:entry` writes it), or the issue could not be read — nothing was acknowledged.'
22
+ const NOTHING_NEW_LINE = 'No person has updated the issue since the run last read it.'
23
+
24
+ function parse_number(argv: ReadonlyArray<string>): string | undefined {
25
+ const [first] = argv
26
+
27
+ if (first === undefined || argv.length !== 1) return undefined
28
+
29
+ return issue_number_shape.ISSUE_NUMBER_PATTERN.test(first) ? first : undefined
30
+ }
31
+
32
+ function report(acknowledged: Acknowledged): void {
33
+ const heading = `Acknowledged issue ${session_cite.issue(acknowledged.issue)}.`
34
+
35
+ console.info(
36
+ acknowledged.diff === undefined
37
+ ? `${heading} ${NOTHING_NEW_LINE}`
38
+ : `${heading}\n\n${acknowledged.diff}`,
39
+ )
40
+ }
41
+
42
+ async function acknowledge(issue_number: string): Promise<number> {
43
+ const root = await git_command.repository_root()
44
+
45
+ if (issue_watch.read_record(root)?.issue !== issue_number) {
46
+ console.error(NO_WATCH_LINE)
47
+
48
+ return FAILURE_EXIT_CODE
49
+ }
50
+
51
+ const acknowledged = await issue_watch.acknowledge(root)
52
+
53
+ if (acknowledged === undefined) {
54
+ console.error(NO_WATCH_LINE)
55
+
56
+ return FAILURE_EXIT_CODE
57
+ }
58
+
59
+ report(acknowledged)
60
+
61
+ return SUCCESS_EXIT_CODE
62
+ }
63
+
64
+ async function run(argv: ReadonlyArray<string>): Promise<number> {
65
+ const issue_number = parse_number(argv)
66
+
67
+ if (issue_number === undefined) {
68
+ console.error(USAGE)
69
+
70
+ return FAILURE_EXIT_CODE
71
+ }
72
+
73
+ return await acknowledge(issue_number)
74
+ }
75
+
76
+ async function main(argv: ReadonlyArray<string>): Promise<void> {
77
+ process.exitCode = await run(argv)
78
+ }
79
+
80
+ const issue_ack_cli = { run }
81
+
82
+ if (process.argv[1] === fileURLToPath(import.meta.url)) await main(process.argv.slice(ARGV_OFFSET))
83
+
84
+ export { issue_ack_cli }
@@ -1,34 +1,30 @@
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/carry/run-carry'
4
- import { issue_file } from './issue-file'
5
- import { AUTO_OK_LABEL, has_label_name, RELEASE_LABEL } 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. So that work found during an
9
- // unattended run does not wait in the backlog until a person labels it, the filing computes the
10
- // 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
- // A `release` Issue is never opted in either: a release is Tier C, so only a
16
- // person applies `auto-ok` to it, whatever the carry record or the branch's Issue says.
17
- // `--requested` withholds the default the same way: a person asked for this filing (a `new` entry, or
18
- // a request in conversation), so the run's own opt-in does not speak for them — `auto-ok` comes only
19
- // from their own `--label auto-ok`. A filing without the declaration — an observation, a
20
- // retrospective, a routed filing — keeps the default above.
21
-
22
- interface AutoOkSignals {
1
+ import {
2
+ BREAKING_CHANGE_LABEL,
3
+ DEFERRED_LABEL,
4
+ EPIC_LABEL,
5
+ has_label_name,
6
+ NEEDS_HUMAN_REVIEW_LABEL,
7
+ RELEASE_LABEL,
8
+ } from './issue-labels'
9
+ import { markdown_section } from './markdown-section'
10
+
11
+ // Whether `josh issue:file` applies `auto-ok` to the Issue it files, judged against the criteria
12
+ // `docs/how-to/run-backlog.md` records rather than the context the filing happens in, so a filing a
13
+ // person asked for (`kickoff new`) is judged the same as one an agent makes on its own. The answer is
14
+ // a pure reading of the filing — its labels, its body and its target — so `backlogrun`'s judgement
15
+ // of an Issue already open reuses `exclusion_of` rather than a second list (joshuafolkken/kit#3854).
16
+ // The order: `--no-auto-ok`, a filing to another repository (whose own `backlogrun` would implement
17
+ // and merge it though nobody opted that repository in), a label that keeps a person in the loop, and
18
+ // last the body's judgement declaration (`JUDGEMENT_NEEDED` / `JUDGEMENT_NOT_NEEDED`): whether a
19
+ // policy decision, an external account or a human visual check is needed.
20
+
21
+ interface AutoOkFiling {
22
+ // `--no-auto-ok`: the filer declared the Issue needs a person's judgement.
23
23
  is_opted_out: boolean
24
- // `--requested`: a person asked for the filing, so neither the carry record nor the branch speaks.
25
- is_requested: boolean
26
- // The filing carries the `release` label.
27
- is_release: boolean
28
- is_carried: boolean
29
- // The labels of the Issue the current branch names; `undefined` when the branch names none or the
30
- // Issue could not be read.
31
- branch_labels: ReadonlyArray<string> | undefined
24
+ is_cross_repository: boolean
25
+ // Every label the filing carries, including the ones the body's declarations add.
26
+ labels: ReadonlyArray<string>
27
+ body: string
32
28
  }
33
29
 
34
30
  interface AutoOkDecision {
@@ -36,98 +32,71 @@ interface AutoOkDecision {
36
32
  reason: string
37
33
  }
38
34
 
35
+ const BACKGROUND_HEADING = '## 背景'
36
+ const JUDGEMENT_NOT_NEEDED = '- 人の判断: 不要'
37
+ const JUDGEMENT_NEEDED = '- 人の判断: 要'
38
+ const DECLARATION_PROBLEM = `declare under \`${BACKGROUND_HEADING}\` whether a person's judgement (a policy decision, an external account, a human visual check) is needed: \`${JUDGEMENT_NOT_NEEDED}\` or \`${JUDGEMENT_NEEDED}(<which one>)\` — exactly one (\`issue-template.md\`)`
39
+
39
40
  const OPTED_OUT: AutoOkDecision = { is_applied: false, reason: '--no-auto-ok declared' }
40
41
  const CROSS_REPOSITORY: AutoOkDecision = {
41
42
  is_applied: false,
42
43
  reason: 'the Issue is filed to another repository',
43
44
  }
44
- const RELEASE: AutoOkDecision = {
45
- is_applied: false,
46
- reason: `a ${RELEASE_LABEL} Issue is opted in by a person only`,
47
- }
48
- const REQUESTED: AutoOkDecision = {
45
+ const JUDGEMENT: AutoOkDecision = {
49
46
  is_applied: false,
50
- reason: 'a person asked for this filing; auto-ok only when they declare it',
47
+ reason: `the body declares \`${JUDGEMENT_NEEDED}\``,
51
48
  }
52
- const CARRIED: AutoOkDecision = { is_applied: true, reason: 'a backlogrun carry record is live' }
53
- const BRANCH_OPTED_IN: AutoOkDecision = {
49
+ const APPLIED: AutoOkDecision = {
54
50
  is_applied: true,
55
- reason: 'the Issue this branch works on carries auto-ok',
56
- }
57
- const NOT_OPTED_IN: AutoOkDecision = {
58
- is_applied: false,
59
- reason: 'no backlogrun carry record is live and the branch names no auto-ok Issue',
60
- }
61
-
62
- // What the filing itself declares: `--no-auto-ok` and `--requested`.
63
- type AutoOkDeclared = Pick<AutoOkSignals, 'is_opted_out' | 'is_requested'>
64
- type Withholding = AutoOkDeclared & Pick<AutoOkSignals, 'is_release'>
65
-
66
- // The three signals that withhold `auto-ok` before any other is read, or `undefined` when none holds.
67
- function withheld(signals: Withholding): AutoOkDecision | undefined {
68
- if (signals.is_opted_out) return OPTED_OUT
69
- if (signals.is_release) return RELEASE
70
- if (signals.is_requested) return REQUESTED
71
-
72
- return undefined
51
+ reason: 'no exclusion holds and the body declares no judgement is needed',
73
52
  }
74
53
 
75
- // The opt-out, the `release` label and `--requested` win over every signal, then the carry record,
76
- // then the branch's Issue.
77
- function decide(signals: AutoOkSignals): AutoOkDecision {
78
- const decision = withheld(signals)
54
+ // The labels that keep a person in the loop, each with the reason it withholds `auto-ok`. A
55
+ // `breaking-change` declared in the body arrives here as its label, added before the decision.
56
+ const EXCLUDED_LABELS: ReadonlyArray<readonly [string, string]> = [
57
+ [RELEASE_LABEL, 'a release is Tier C'],
58
+ [BREAKING_CHANGE_LABEL, 'a breaking change needs a person to accept the migration'],
59
+ [DEFERRED_LABEL, 'the Issue is deferred'],
60
+ [NEEDS_HUMAN_REVIEW_LABEL, 'a person reviews the change before it lands'],
61
+ [EPIC_LABEL, 'an epic is opted in by a person, and that opts in every descendant'],
62
+ ]
79
63
 
80
- if (decision !== undefined) return decision
81
- if (signals.is_carried) return CARRIED
82
- if (has_label_name(signals.branch_labels ?? [], AUTO_OK_LABEL)) return BRANCH_OPTED_IN
64
+ // The first label that withholds `auto-ok`, or `undefined` when none of them is present.
65
+ function exclusion_of(labels: ReadonlyArray<string>): AutoOkDecision | undefined {
66
+ const found = EXCLUDED_LABELS.find(([label]) => has_label_name(labels, label))
83
67
 
84
- return NOT_OPTED_IN
85
- }
68
+ if (found === undefined) return undefined
86
69
 
87
- function line_of(decision: AutoOkDecision): string {
88
- return `auto-ok: ${decision.is_applied ? 'applied' : 'not applied'} — ${decision.reason}`
70
+ return { is_applied: false, reason: `a \`${found[0]}\` Issue — ${found[1]}` }
89
71
  }
90
72
 
91
- // Read from the common git directory, so a lane sees the record its batch wrote in the main tree.
92
- async function is_carried(): Promise<boolean> {
93
- const directory = await run_carry.repository_directory()
73
+ // `true` / `false` for a declared need, `undefined` when the background declares neither or both.
74
+ function judgement_of(body: string): boolean | undefined {
75
+ const background = markdown_section.section_lines(body, BACKGROUND_HEADING).join('\n')
76
+ const lines = markdown_section.unfenced_lines(background).map((line) => line.trim())
77
+ const is_not_needed = lines.some((line) => line.startsWith(JUDGEMENT_NOT_NEEDED))
78
+ const is_needed = lines.some((line) => line.startsWith(JUDGEMENT_NEEDED))
94
79
 
95
- if (directory === undefined) return false
96
-
97
- return run_carry.read_carry(run_carry.carry_path(directory)).kind === 'carried'
80
+ return is_not_needed === is_needed ? undefined : is_needed
98
81
  }
99
82
 
100
- // An unreadable Issue is read as no labels: the filing goes on without `auto-ok` rather than failing.
101
- // The branch names an Issue of `current`, read there by name: a filing to another repository has
102
- // already pointed `GH_REPO` at its target, which would otherwise answer with the target's Issue.
103
- async function branch_labels(current: string): Promise<ReadonlyArray<string> | undefined> {
104
- const issue_number = git_branch.issue_from_branch(await git_command.branch())
83
+ // Why the filing is refused for its declaration, or `undefined` when it declares exactly one answer.
84
+ function declaration_problem(body: string): string | undefined {
85
+ return judgement_of(body) === undefined ? DECLARATION_PROBLEM : undefined
86
+ }
105
87
 
106
- if (issue_number === undefined) return undefined
107
- const read = await issue_state_cli.read_issue(String(issue_number), current)
88
+ function decide(filing: AutoOkFiling): AutoOkDecision {
89
+ if (filing.is_opted_out) return OPTED_OUT
90
+ if (filing.is_cross_repository) return CROSS_REPOSITORY
108
91
 
109
- return read.kind === 'state' ? read.state.labels : undefined
92
+ return exclusion_of(filing.labels) ?? (judgement_of(filing.body) === false ? APPLIED : JUDGEMENT)
110
93
  }
111
94
 
112
- // Each read is skipped once an earlier signal has already decided the answer; a cross-repository
113
- // filing is refused before any signal is read. `labels` are the ones the filing declares.
114
- async function resolve(
115
- declared: AutoOkDeclared,
116
- target: string,
117
- current: string,
118
- labels: ReadonlyArray<string> = [],
119
- ): Promise<AutoOkDecision> {
120
- const withholding = { ...declared, is_release: has_label_name(labels, RELEASE_LABEL) }
121
- const decision = withheld(withholding)
122
-
123
- if (decision !== undefined) return decision
124
- if (!issue_file.is_same_repository(target, current)) return CROSS_REPOSITORY
125
- if (await is_carried()) return CARRIED
126
-
127
- return decide({ ...withholding, is_carried: false, branch_labels: await branch_labels(current) })
95
+ function line_of(decision: AutoOkDecision): string {
96
+ return `auto-ok: ${decision.is_applied ? 'applied' : 'not applied'} — ${decision.reason}`
128
97
  }
129
98
 
130
- const issue_auto_ok = { decide, line_of, resolve }
99
+ const issue_auto_ok = { declaration_problem, decide, exclusion_of, line_of }
131
100
 
132
- export type { AutoOkDeclared, AutoOkDecision, AutoOkSignals }
101
+ export type { AutoOkDecision, AutoOkFiling }
133
102
  export { issue_auto_ok }
@@ -24,7 +24,7 @@ import { issue_scout_cli } from './issue-scout-cli'
24
24
  import { issue_wip } from './issue-wip'
25
25
 
26
26
  // `josh issue:file "<title>" --body-file <path> --depth <0|1|2> [--route <route>] [--label <name>]…
27
- // [--repo <owner/repo>] [--distinct <N,…>] [--over-cap] [--no-auto-ok] [--requested] [--release]` — file an Issue
27
+ // [--repo <owner/repo>] [--distinct <N,…>] [--over-cap] [--no-auto-ok] [--release]` — file an Issue
28
28
  // with every filing step run in order: the third-party refusal, the body
29
29
  // lint, the `## Origin` check for another repository, the `auto-ok` decision
30
30
  // with the run label it owes, the fold question against the run's earlier
@@ -37,7 +37,7 @@ const SUCCESS_EXIT_CODE = 0
37
37
  const FAILURE_EXIT_CODE = 1
38
38
  const ARGV_OFFSET = 2
39
39
  const USAGE =
40
- '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] [--requested] [--release]'
40
+ '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] [--release]'
41
41
  const UNKNOWN_REPO_MESSAGE =
42
42
  'Could not read this repository from `git remote`, so the filing has no repository to compare against — check `gh auth status`.'
43
43
  const THIRD_PARTY_MESSAGE =
@@ -74,10 +74,13 @@ function is_third_party(target: string): boolean {
74
74
  // the filing may go on to the scout.
75
75
  function refusals_of(filing: Filing): ReadonlyArray<string> {
76
76
  if (is_third_party(filing.target)) return [THIRD_PARTY_MESSAGE]
77
- const problems = issue_lint_cli.problems(filing.body).map((problem) => `✖ ${problem}`)
78
- const origin = issue_file.origin_problem(filing.body, filing.target, filing.current)
77
+ const problems = [
78
+ ...issue_lint_cli.problems(filing.body),
79
+ issue_auto_ok.declaration_problem(filing.body),
80
+ issue_file.origin_problem(filing.body, filing.target, filing.current),
81
+ ]
79
82
 
80
- return origin === undefined ? problems : [...problems, `✖ ${origin}`]
83
+ return problems.filter((problem) => problem !== undefined).map((problem) => `✖ ${problem}`)
81
84
  }
82
85
 
83
86
  function unacknowledged_message(numbers: ReadonlyArray<number>): string {
@@ -143,18 +146,15 @@ async function is_scout_clear(filing: Filing): Promise<boolean> {
143
146
  }
144
147
 
145
148
  // Every label the create call carries, or `undefined` when an `auto-ok` filing names neither run label.
146
- // Decided before the WIP count and the scout, so a refusal costs no listing.
147
- async function labels_of(filing: Filing): Promise<ReadonlyArray<string> | undefined> {
148
- const declared = {
149
+ // Decided before the WIP count and the scout, so a refusal costs no listing. The decision reads the
150
+ // labels the body's declarations add, so a declared breaking change is excluded like its label.
151
+ function labels_of(filing: Filing): ReadonlyArray<string> | undefined {
152
+ const auto_ok = issue_auto_ok.decide({
149
153
  is_opted_out: filing.args.is_auto_ok_opted_out,
150
- is_requested: filing.args.is_requested,
151
- }
152
- const auto_ok = await issue_auto_ok.resolve(
153
- declared,
154
- filing.target,
155
- filing.current,
156
- filing.args.labels,
157
- )
154
+ is_cross_repository: !issue_file.is_same_repository(filing.target, filing.current),
155
+ labels: issue_file.labels_of(filing.args, filing.body),
156
+ body: filing.body,
157
+ })
158
158
 
159
159
  console.info(issue_auto_ok.line_of(auto_ok))
160
160
  const labels = issue_file.labels_of(filing.args, filing.body, auto_ok.is_applied)
@@ -271,7 +271,7 @@ async function is_clear(filing: Filing): Promise<boolean> {
271
271
 
272
272
  async function file(filing: Filing): Promise<number> {
273
273
  if (!is_admitted(filing)) return FAILURE_EXIT_CODE
274
- const labels = await labels_of(filing)
274
+ const labels = labels_of(filing)
275
275
 
276
276
  if (labels === undefined || !(await is_clear(filing))) return FAILURE_EXIT_CODE
277
277
 
@@ -41,9 +41,6 @@ interface FileArguments {
41
41
  is_over_cap: boolean
42
42
  // `--no-auto-ok`: the Issue needs a person's judgement, so `auto-ok` is not applied (`issue-auto-ok.ts`).
43
43
  is_auto_ok_opted_out: boolean
44
- // `--requested`: a person asked for this filing, so `auto-ok` is applied only when a `--label` names
45
- // it (`issue-auto-ok.ts`).
46
- is_requested: boolean
47
44
  // `--release`: the change reaches a consumer only once published, so the Issue is linked to the
48
45
  // repository's release Issue as a blocker (`issue-release-cli.ts`).
49
46
  is_release: boolean
@@ -58,7 +55,6 @@ const OPTIONS = {
58
55
  distinct: { type: 'string', multiple: true },
59
56
  'over-cap': { type: 'boolean' },
60
57
  'no-auto-ok': { type: 'boolean' },
61
- requested: { type: 'boolean' },
62
58
  release: { type: 'boolean' },
63
59
  } as const
64
60
 
@@ -129,7 +125,6 @@ function arguments_of(values: ParsedValues, title: string | undefined): FileArgu
129
125
  repo: values.repo,
130
126
  is_over_cap: values['over-cap'] === true,
131
127
  is_auto_ok_opted_out: values['no-auto-ok'] === true,
132
- is_requested: values.requested === true,
133
128
  is_release: values.release === true,
134
129
  }
135
130
  }
@@ -151,7 +146,7 @@ function is_undeclared_classification(label: string, body: string): boolean {
151
146
 
152
147
  // Every label the filing carries: the depth, the route when there is one, the classification labels
153
148
  // the body declares (`issue_classification.labels_for`, which merges case-insensitively), then any
154
- // extra `--label`, then `auto-ok` when the filing opted in (`issue-auto-ok.ts`) and no `--label`
149
+ // extra `--label`, then `auto-ok` when the filing is judged in (`issue-auto-ok.ts`) and no `--label`
155
150
  // already named it. One request applies them all, so no label is a separate step to forget.
156
151
  function labels_of(args: FileArguments, body: string, is_auto_ok = false): ReadonlyArray<string> {
157
152
  const extra = args.labels.filter((label) => !is_undeclared_classification(label, body))
@@ -67,9 +67,15 @@ const ALREADY_DONE_LABEL = 'already-done'
67
67
  // Marks the Issue that tracks a repository's next release. An Issue whose
68
68
  // change reaches a consumer only once published is linked to it as a `blocked_by` blocker
69
69
  // (`josh issue:release`), so "merged but not yet released" is visible in the backlog. A run applies
70
- // it; **`auto-ok` never rides on it** — `issue-auto-ok.ts` refuses the default for it on every path,
71
- // since a release is Tier C — and only a person opts it into unattended execution.
70
+ // it; **`auto-ok` never rides on it** — `issue-auto-ok.ts` excludes it, since a release is Tier C —
71
+ // and only a person opts it into unattended execution.
72
72
  const RELEASE_LABEL = 'release'
73
+ // A person's mark for work set aside for now; `issue-auto-ok.ts` reads it as an exclusion. Applied by
74
+ // hand and never provisioned, so it is not in `REPOSITORY_LABELS`.
75
+ const DEFERRED_LABEL = 'deferred'
76
+ // Applied to an Issue whose second review round was skipped, so the condition stays auditable
77
+ // (`prompts/review.md` → "When round 2 is skipped entirely, and when it is not").
78
+ const REVIEW_ROUND2_SKIPPED_LABEL = 'review-round2-skipped'
73
79
 
74
80
  // The four labels that mean an open issue must not be handed to a run as it stands: an `epic`
75
81
  // tracks a batch and is never run directly (its children are), `in-progress` is already claimed by
@@ -205,8 +211,11 @@ interface LabelDefinition {
205
211
  }
206
212
 
207
213
  // What `josh start` provisions on a new repository so the first `kickoff` finds every label a run
208
- // applies with its intended color. The `epic` and `in-progress` metadata is
209
- // the one the workflow documents create them with; the labels only a person applies are left out.
214
+ // applies with its intended color. The metadata is the one the workflow documents create each label
215
+ // with. **The labels only a person applies are provisioned too**: a person cannot apply a label the
216
+ // repository does not have, so leaving one out made the first `auto-ok` a manual creation step.
217
+ // `bug` is GitHub's default label, kept because `issue:file` applies it to a bug fix and a
218
+ // repository may have been created without the defaults.
210
219
  const WORKFLOW_LABELS: ReadonlyArray<LabelDefinition> = [
211
220
  {
212
221
  name: EPIC_LABEL,
@@ -235,6 +244,32 @@ const WORKFLOW_LABELS: ReadonlyArray<LabelDefinition> = [
235
244
  color: 'fef2c0',
236
245
  description: 'Tracks the next release; its blockers are the merged work it publishes',
237
246
  },
247
+ {
248
+ name: AUTO_OK_LABEL,
249
+ color: '0e8a16',
250
+ description: 'Opted in to unattended execution outside an epic',
251
+ },
252
+ {
253
+ name: NEEDS_DECISION_LABEL,
254
+ color: 'd93f0b',
255
+ description: 'Parked: needs a decision before it can run',
256
+ },
257
+ {
258
+ name: NEEDS_HUMAN_REVIEW_LABEL,
259
+ color: 'd93f0b',
260
+ description: 'Implement and verify, but stop before committing so a person can look',
261
+ },
262
+ {
263
+ name: ALREADY_DONE_LABEL,
264
+ color: '6f42c1',
265
+ description: 'Verified already merged — a person closes it',
266
+ },
267
+ {
268
+ name: REVIEW_ROUND2_SKIPPED_LABEL,
269
+ color: 'c5def5',
270
+ description: 'Review round 2 was skipped under the condition in prompts/review.md',
271
+ },
272
+ { name: BUG_LABEL, color: 'd73a4a', description: "Something isn't working" },
238
273
  ...FILING_ROUTE_LABELS,
239
274
  ...DEPTH_LABELS,
240
275
  ]
@@ -329,12 +364,6 @@ function depth_label_of(labels: ReadonlyArray<LabelReference> | undefined): stri
329
364
  // document operates on a label name that does not exist here — a stale or mistyped `labels[]=…`
330
365
  // otherwise fails silently at run time. One source, so a label added above joins the scan without a
331
366
  // second edit.
332
- // Applied to an Issue whose second review round was skipped, so the condition stays auditable
333
- // (`prompts/review.md` → "When round 2 is skipped entirely, and when it is not"). Provisioned inline
334
- // by the run that applies it rather than through `FILING_ROUTE_LABELS`, but still a managed label the
335
- // documents operate on, so the label-reference scan has to know it.
336
- const REVIEW_ROUND2_SKIPPED_LABEL = 'review-round2-skipped'
337
-
338
367
  const ALL_LABELS: ReadonlySet<string> = new Set([
339
368
  EPIC_LABEL,
340
369
  BUG_LABEL,
@@ -363,6 +392,11 @@ const ALL_LABELS: ReadonlySet<string> = new Set([
363
392
  IGNORE_FOR_RELEASE_LABEL,
364
393
  ])
365
394
 
395
+ // The managed labels `REPOSITORY_LABELS` deliberately does not provision, each with its reason. Every
396
+ // other member of `ALL_LABELS` is provisioned — `issue-labels.test.ts` fails on one that is in
397
+ // neither list, so a label added above cannot silently become one a repository lacks. Empty today.
398
+ const EXEMPT_FROM_PROVISIONING: ReadonlyMap<string, string> = new Map<string, string>()
399
+
366
400
  export type { LabelDefinition, ReleaseClassification }
367
401
  export {
368
402
  ALL_LABELS,
@@ -371,6 +405,7 @@ export {
371
405
  BUG_LABEL,
372
406
  BUGFIX_LABEL,
373
407
  BREAKING_CHANGE_LABEL,
408
+ DEFERRED_LABEL,
374
409
  depth_label_of,
375
410
  DEPTH_0_LABEL,
376
411
  DEPTH_1_LABEL,
@@ -379,6 +414,7 @@ export {
379
414
  DEPTH_LABELS,
380
415
  ENHANCEMENT_LABEL,
381
416
  EPIC_LABEL,
417
+ EXEMPT_FROM_PROVISIONING,
382
418
  FILING_ROUTE_LABELS,
383
419
  has_any_label,
384
420
  has_label_name,