@joshuafolkken/kit 1.1471.0 → 1.1514.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 (267) hide show
  1. package/.claude/settings.json +6 -6
  2. package/.claude/skills/epic-commands/SKILL.md +2 -14
  3. package/.claude/skills/epic-commands/epic-bundle.md +9 -5
  4. package/.claude/skills/epic-commands/execution-waves.md +1 -2
  5. package/.claude/skills/verify-ui/SKILL.md +1 -2
  6. package/.claude/skills/workflow-commands/SKILL.md +7 -9
  7. package/.claude/skills/workflow-commands/background-commands.md +3 -7
  8. package/.claude/skills/workflow-commands/backlogrun-child.md +3 -10
  9. package/.claude/skills/workflow-commands/backlogrun-lanes.md +6 -15
  10. package/.claude/skills/workflow-commands/backlogrun-park.md +2 -8
  11. package/.claude/skills/workflow-commands/backlogrun-progress.md +10 -19
  12. package/.claude/skills/workflow-commands/backlogrun-recovery.md +8 -8
  13. package/.claude/skills/workflow-commands/backlogrun-steps.md +2 -10
  14. package/.claude/skills/workflow-commands/backlogrun.md +31 -50
  15. package/.claude/skills/workflow-commands/chain-rule.md +7 -6
  16. package/.claude/skills/workflow-commands/delegation.md +12 -8
  17. package/.claude/skills/workflow-commands/entry-sequence.md +1 -3
  18. package/.claude/skills/workflow-commands/followup.md +3 -6
  19. package/.claude/skills/workflow-commands/fullrun-steps.md +3 -5
  20. package/.claude/skills/workflow-commands/fullrun.md +4 -6
  21. package/.claude/skills/workflow-commands/halfrun.md +3 -7
  22. package/.claude/skills/workflow-commands/into-target.md +1 -2
  23. package/.claude/skills/workflow-commands/issue-comments.md +3 -3
  24. package/.claude/skills/workflow-commands/issue-scout.md +3 -2
  25. package/.claude/skills/workflow-commands/latest-gate.md +2 -6
  26. package/.claude/skills/workflow-commands/needs-human-review.md +2 -5
  27. package/.claude/skills/workflow-commands/observation-filing.md +0 -14
  28. package/.claude/skills/workflow-commands/observation-ledger.md +4 -9
  29. package/.claude/skills/workflow-commands/pre-gate-cut.md +11 -16
  30. package/.claude/skills/workflow-commands/prerequisite.md +1 -2
  31. package/.claude/skills/workflow-commands/progress-watcher.md +2 -7
  32. package/.claude/skills/workflow-commands/prrun.md +3 -5
  33. package/.claude/skills/workflow-commands/retrospective.md +4 -4
  34. package/.claude/skills/workflow-commands/split-assessment.md +1 -3
  35. package/.claude/skills/workflow-commands/target-repository.md +1 -2
  36. package/.claude/skills/workflow-commands/working-tree-hold.md +2 -4
  37. package/.codex/hooks.json +6 -6
  38. package/.github/actions/setup-node/action.yml +1 -1
  39. package/.github/workflows/ci.yml +1 -1
  40. package/.github/workflows/publish.yml +2 -2
  41. package/CLAUDE.md +1 -1
  42. package/cspell/index.yaml +7 -0
  43. package/dist/CLAUDE.md +1 -1
  44. package/dist/commands/document-section-cli.js +67 -34
  45. package/dist/hooks/batch-guard.js +6 -6
  46. package/dist/hooks/chunk-4KKD2UTK.js +66 -0
  47. package/dist/hooks/{chunk-J47HU5H5.js → chunk-6TS2JD6D.js} +3 -3
  48. package/dist/hooks/chunk-7DSBJVFH.js +50 -0
  49. package/dist/hooks/{chunk-YHBZAJSV.js → chunk-A65Y4YOR.js} +3 -3
  50. package/dist/hooks/{chunk-E7OIKX5Z.js → chunk-APUO6U4J.js} +85 -7
  51. package/dist/hooks/{chunk-IX6TTTI6.js → chunk-EFXE6C37.js} +8 -22
  52. package/dist/hooks/chunk-GP4IEC4O.js +20 -0
  53. package/dist/hooks/{chunk-SHGPANFB.js → chunk-JP45SOCJ.js} +3 -3
  54. package/dist/hooks/{chunk-K4EOONYM.js → chunk-LAOV7NLM.js} +1 -1
  55. package/dist/hooks/chunk-M4HGZV7U.js +83 -0
  56. package/dist/hooks/{chunk-KC5AK2GF.js → chunk-O6GDRNJB.js} +3 -3
  57. package/dist/hooks/{chunk-YYPI7SJZ.js → chunk-PF2NZL2A.js} +5 -5
  58. package/dist/hooks/{chunk-H5HVWYXK.js → chunk-PTICX4OL.js} +3 -4
  59. package/dist/hooks/{chunk-IIGFYXKC.js → chunk-RT27QRJW.js} +581 -1438
  60. package/dist/hooks/{chunk-43ZHT22P.js → chunk-SYOTDYCI.js} +1127 -1089
  61. package/dist/hooks/{chunk-AS74OU3D.js → chunk-TNUMY6NL.js} +1507 -1336
  62. package/dist/hooks/chunk-UPUZF3AN.js +279 -0
  63. package/dist/hooks/{chunk-EUMACUK2.js → chunk-WK6SMBIC.js} +1948 -806
  64. package/dist/hooks/codex-hook-adapter.js +17 -14
  65. package/dist/hooks/duplicate-read-guard.js +7 -7
  66. package/dist/hooks/format-edited.js +5 -4
  67. package/dist/hooks/inputs.json +317 -264
  68. package/dist/hooks/investigation-guard.js +8 -11
  69. package/dist/hooks/pretool-guard.js +16 -13
  70. package/dist/hooks/session-lang.js +370 -25
  71. package/dist/hooks/stop-guard.js +2576 -176
  72. package/dist/josh.js +2124 -277
  73. package/lefthook/base.yml +5 -3
  74. package/package.json +12 -10
  75. package/prompts/collaboration-workflow/file-edits.md +1 -3
  76. package/prompts/collaboration-workflow/gh-rest.md +1 -2
  77. package/prompts/collaboration-workflow/issue-citation.md +2 -4
  78. package/prompts/collaboration-workflow/issue-template.md +10 -2
  79. package/prompts/collaboration-workflow/lane-instructions.md +35 -0
  80. package/prompts/collaboration-workflow/operating-rules.md +2 -6
  81. package/prompts/collaboration-workflow/output-bounds.md +3 -3
  82. package/prompts/collaboration-workflow/overview.md +1 -1
  83. package/prompts/collaboration-workflow/principles.md +14 -8
  84. package/prompts/collaboration-workflow/report-format.md +9 -0
  85. package/prompts/collaboration-workflow/residency.md +12 -8
  86. package/prompts/collaboration-workflow/rule-delivery.md +3 -11
  87. package/prompts/collaboration-workflow/shell-body.md +6 -6
  88. package/prompts/collaboration-workflow/turn-batching.md +5 -5
  89. package/prompts/collaboration-workflow/upstream-interrupt.md +2 -2
  90. package/prompts/collaboration-workflow/wip-cap.md +6 -6
  91. package/prompts/collaboration-workflow.md +1 -0
  92. package/prompts/review.md +4 -5
  93. package/prompts/testing-guide.md +2 -2
  94. package/scripts/agent/agent-argv.ts +20 -4
  95. package/scripts/agent/agent-role-profile.ts +101 -45
  96. package/scripts/agent/agent-safe-text.ts +60 -0
  97. package/scripts/agent/claude-agent-argv.ts +14 -0
  98. package/scripts/agent/codex-agent-argv.ts +2 -0
  99. package/scripts/agent/lane-instructions.ts +144 -0
  100. package/scripts/backlog/backlog-budget.ts +4 -4
  101. package/scripts/backlog/backlog-drive-named.ts +1 -1
  102. package/scripts/backlog/backlog-drive-restore.ts +9 -1
  103. package/scripts/backlog/backlog-occupied.ts +56 -0
  104. package/scripts/backlog/backlog-ready.ts +47 -10
  105. package/scripts/delegation/duplicate-read-guard.ts +2 -2
  106. package/scripts/delegation/investigation-guard.ts +15 -65
  107. package/scripts/delegation/investigation-reads.ts +0 -1
  108. package/scripts/document/document-byte-budget.ts +6 -5
  109. package/scripts/document/document-section-cli.ts +17 -5
  110. package/scripts/document/entry-read-budget.ts +2 -2
  111. package/scripts/document/read-set-cli.ts +4 -1
  112. package/scripts/epic/epic-audit-orphans.ts +5 -8
  113. package/scripts/epic/epic-audit.ts +52 -1
  114. package/scripts/epic/epic-bundle-evidence.ts +49 -26
  115. package/scripts/epic/epic-bundle-referenced.ts +14 -3
  116. package/scripts/epic/epic-bundle.ts +17 -9
  117. package/scripts/followup/git-followup-workflow.ts +19 -1
  118. package/scripts/gate/check-inputs.ts +129 -0
  119. package/scripts/gate/check-reuse.ts +162 -0
  120. package/scripts/gate/core-budget.ts +93 -44
  121. package/scripts/gate/core-wait.ts +130 -0
  122. package/scripts/gate/free-cores.ts +49 -0
  123. package/scripts/gate/gate-ledger.ts +17 -3
  124. package/scripts/gate/gate-plan.ts +23 -1
  125. package/scripts/gate/gate-report.ts +15 -2
  126. package/scripts/gate/gate-run-steps.ts +150 -0
  127. package/scripts/gate/held-mark.ts +55 -0
  128. package/scripts/gate/machine-capacity.ts +70 -11
  129. package/scripts/gate/pre-commit-type-check.ts +14 -1
  130. package/scripts/gate/verification-gate.ts +58 -125
  131. package/scripts/gh/git-gh-issue-rest.ts +1 -0
  132. package/scripts/gh/git-gh-issue-write.ts +24 -2
  133. package/scripts/gh/git-pr.ts +13 -1
  134. package/scripts/git/main-merge-regenerate.ts +78 -0
  135. package/scripts/git/main-merge.ts +14 -5
  136. package/scripts/hooks/batch-guard.ts +5 -8
  137. package/scripts/hooks/pre-commit-lint.ts +91 -0
  138. package/scripts/hooks/pre-push-unit.ts +1 -1
  139. package/scripts/hooks/pretool-guard.ts +27 -52
  140. package/scripts/hooks/reserved-run.ts +1 -0
  141. package/scripts/hooks/run-hook.sh +10 -12
  142. package/scripts/hooks/stop-actions.ts +204 -0
  143. package/scripts/hooks/stop-guard.ts +62 -21
  144. package/scripts/init/hook-launch.ts +12 -7
  145. package/scripts/issue/expected-effect-lint.ts +59 -0
  146. package/scripts/issue/issue-cite-cli.ts +35 -15
  147. package/scripts/issue/issue-classification.ts +1 -1
  148. package/scripts/issue/issue-lint-cli.ts +5 -3
  149. package/scripts/issue/issue-scout-cli.ts +19 -14
  150. package/scripts/issue/markdown-section.ts +19 -5
  151. package/scripts/josh/command-usage.ts +110 -0
  152. package/scripts/josh/josh-command-map.ts +2 -0
  153. package/scripts/josh/josh-command-types.ts +55 -9
  154. package/scripts/josh/josh-commands-ai.ts +13 -0
  155. package/scripts/josh/josh-commands-development.ts +1 -1
  156. package/scripts/josh/josh-commands-hooks.ts +6 -0
  157. package/scripts/josh/josh-commands-lane.ts +10 -1
  158. package/scripts/josh/josh-commands-workflow.ts +5 -0
  159. package/scripts/josh/josh-logic.ts +6 -15
  160. package/scripts/josh/josh-usage-automation.ts +121 -0
  161. package/scripts/josh/josh-usage-backlog.ts +180 -0
  162. package/scripts/josh/josh-usage-developer.ts +230 -0
  163. package/scripts/josh/josh-usage-hooks.ts +38 -0
  164. package/scripts/josh/josh-usage-issue.ts +205 -0
  165. package/scripts/josh/josh-usage-review.ts +101 -0
  166. package/scripts/josh/josh-usage-run.ts +252 -0
  167. package/scripts/josh/josh-usage-session.ts +238 -0
  168. package/scripts/josh/josh-usage-workflow.ts +212 -0
  169. package/scripts/josh/josh.ts +14 -5
  170. package/scripts/josh/package-directory.ts +20 -0
  171. package/scripts/lane/lane-capacity.ts +8 -2
  172. package/scripts/lane/lane-check-stats.ts +56 -0
  173. package/scripts/lane/lane-child-invocation.ts +35 -4
  174. package/scripts/lane/lane-dispatch.ts +3 -3
  175. package/scripts/lane/lane-guard-policy.ts +2 -2
  176. package/scripts/lane/lane-handoff.ts +8 -1
  177. package/scripts/lane/lane-install.ts +12 -4
  178. package/scripts/lane/lane-ledger.ts +43 -1
  179. package/scripts/lane/lane-limit-cli.ts +8 -9
  180. package/scripts/lane/lane-measure-cli.ts +2 -0
  181. package/scripts/lane/lane-preserve.ts +29 -1
  182. package/scripts/lane/lane-registry.ts +9 -7
  183. package/scripts/lane/lane-restart.ts +5 -26
  184. package/scripts/lane/lane-session-action.ts +146 -0
  185. package/scripts/lane/lane-session-broker.ts +57 -138
  186. package/scripts/lane/lane-withdraw-cli.ts +71 -0
  187. package/scripts/lane/lane-withdraw.ts +182 -0
  188. package/scripts/lint/format.ts +4 -4
  189. package/scripts/lint/lint-parallel.ts +10 -3
  190. package/scripts/lint/lint-related-scope.ts +4 -1
  191. package/scripts/notify/telegram-test.ts +10 -5
  192. package/scripts/report/report-format-reference.ts +2 -0
  193. package/scripts/review/acceptance-map-check.ts +61 -0
  194. package/scripts/review/acceptance-map.ts +205 -0
  195. package/scripts/review/review-brief-cli.ts +1 -1
  196. package/scripts/rules/agent-model.ts +59 -0
  197. package/scripts/rules/delivered-rules.ts +11 -2
  198. package/scripts/rules/filing-cap.ts +1 -0
  199. package/scripts/rules/firing-point.ts +6 -5
  200. package/scripts/rules/guard-remedy.ts +117 -0
  201. package/scripts/rules/issue-comments.ts +67 -6
  202. package/scripts/rules/last-prompt.ts +50 -5
  203. package/scripts/rules/oracle-list-cli.ts +7 -0
  204. package/scripts/rules/piped-verification.ts +3 -1
  205. package/scripts/rules/rule-list.ts +4 -1
  206. package/scripts/rules/rule-registry-rows.ts +10 -0
  207. package/scripts/rules/rule-registry.ts +7 -4
  208. package/scripts/rules/rule-rewrite.ts +114 -0
  209. package/scripts/rules/rule-value.ts +7 -1
  210. package/scripts/rules/shell-segments.ts +15 -0
  211. package/scripts/rules/stop-decision.ts +77 -0
  212. package/scripts/rules/stop-rules.ts +147 -47
  213. package/scripts/rules/tail-commands.ts +15 -1
  214. package/scripts/run/board/run-board-cli.ts +2 -0
  215. package/scripts/run/board/run-board-closed.ts +8 -2
  216. package/scripts/run/board/run-board-github.ts +9 -1
  217. package/scripts/run/board/run-board-header.ts +4 -1
  218. package/scripts/run/board/run-board-labels.ts +2 -0
  219. package/scripts/run/board/run-board-layout.ts +1 -1
  220. package/scripts/run/board/run-board-read.ts +16 -0
  221. package/scripts/run/board/run-board-reason.ts +10 -1
  222. package/scripts/run/board/run-board-render-legend.ts +1 -0
  223. package/scripts/run/board/run-board-render-slot.ts +1 -1
  224. package/scripts/run/board/run-board-render-waits.ts +41 -0
  225. package/scripts/run/board/run-board-render.ts +6 -1
  226. package/scripts/run/board/run-board-row-yield.ts +1 -0
  227. package/scripts/run/board/run-board-state.ts +5 -0
  228. package/scripts/run/board/run-board-status.ts +72 -21
  229. package/scripts/run/board/run-board-tick.ts +5 -1
  230. package/scripts/run/cut/run-cut-cli.ts +3 -2
  231. package/scripts/run/cut/run-cut-report.ts +16 -0
  232. package/scripts/run/detached-launch.ts +23 -4
  233. package/scripts/run/entry/run-entry-cli.ts +8 -1
  234. package/scripts/run/event/run-event-render.ts +1 -0
  235. package/scripts/run/event/run-event-scope.ts +19 -1
  236. package/scripts/run/event/run-event-stream-emit.ts +13 -6
  237. package/scripts/run/event/run-event-stream.ts +4 -0
  238. package/scripts/run/hold/run-hold-session.ts +46 -0
  239. package/scripts/run/hold/run-hold.ts +7 -1
  240. package/scripts/run/launch-priority.ts +32 -0
  241. package/scripts/run/merge/run-merge-cli.ts +16 -65
  242. package/scripts/run/merge/run-merge-signals.ts +99 -0
  243. package/scripts/run/merge/run-merge-steps.ts +11 -1
  244. package/scripts/run/merge/run-merge.ts +21 -9
  245. package/scripts/run/run-liveness.ts +1 -1
  246. package/scripts/run/run-poll-hook.ts +157 -0
  247. package/scripts/run/run-step-cli.ts +22 -15
  248. package/scripts/run/run-step-section.ts +85 -0
  249. package/scripts/run/ship/run-ship-cli.ts +5 -12
  250. package/scripts/run/ship/run-ship-detach.ts +9 -0
  251. package/scripts/run/ship/run-ship-next.ts +10 -2
  252. package/scripts/run/ship/run-ship-overlap.ts +195 -0
  253. package/scripts/run/ship/run-ship-pre-detach.ts +33 -15
  254. package/scripts/run/ship/run-ship-preflight.ts +28 -2
  255. package/scripts/run/ship/run-ship-return.ts +31 -7
  256. package/scripts/run/ship/run-ship-review-steps.ts +12 -10
  257. package/scripts/run/ship/run-ship-steps.ts +1 -1
  258. package/scripts/run/ship/run-ship-stop-context.ts +206 -0
  259. package/scripts/run/ship/run-ship-sync.ts +14 -6
  260. package/scripts/test/test-e2e-guard.ts +30 -5
  261. package/scripts/test/test-unit-guard.ts +16 -1
  262. package/scripts/test/unit-worker-share.ts +26 -4
  263. package/scripts/time-runtime/time-shell.ts +29 -4
  264. package/scripts/time-runtime/time-spans.ts +1 -0
  265. package/scripts/time-runtime/time-tool-call.ts +7 -0
  266. package/templates/gitignore +6 -2
  267. package/dist/hooks/chunk-K4LMBQC2.js +0 -68
@@ -0,0 +1,206 @@
1
+ import { stripVTControlCharacters } from 'node:util'
2
+ import { agent_role_profile } from '#scripts/agent/agent-role-profile'
3
+ import { changed_paths } from '#scripts/git/changed-paths'
4
+ import { stamp_file } from '#scripts/josh/stamp-file'
5
+ import { z } from 'zod'
6
+ import { run_ship_next } from './run-ship-next'
7
+ import { run_ship_probe } from './run-ship-probe'
8
+
9
+ // What a relaunched lane child needs in front of it to fix a stopped ship: the tail of the failed
10
+ // stage's output — a red check, or the High/Medium findings a review stage reports — and the branch's
11
+ // changed files. Without them the resumed session opened `josh ship --log` and `git diff` itself before
12
+ // doing anything (joshuafolkken/kit#3923).
13
+ //
14
+ // **Both are bounded**, so a long gate log never becomes the whole prompt. The tail is kept because a
15
+ // check reports its verdict last; the lines left out go to a file whose path and line range the prompt
16
+ // names, so nothing is lost and the session reads only the range it needs.
17
+
18
+ const EXCERPT_LINE_LIMIT = 40
19
+ const EXCERPT_LINE_WIDTH = 240
20
+ const CHANGED_PATH_LIMIT = 30
21
+ const OUTPUT_PREFIX = 'josh-ship-stop-'
22
+ const OUTPUT_SUFFIX = '.log'
23
+ const KEY_SEPARATOR = '-'
24
+ const LINE_BREAK = '\n'
25
+ const CARRIAGE_RETURN = '\r'
26
+
27
+ const omitted_schema = z
28
+ .object({ path: z.string().optional(), last_line: z.number().int() })
29
+ .strict()
30
+ const stop_context_schema = z
31
+ .object({
32
+ excerpt: z.array(z.string()).readonly(),
33
+ omitted: omitted_schema.optional(),
34
+ changed: z.array(z.string()).readonly().optional(),
35
+ more_changed: z.number().int(),
36
+ })
37
+ .strict()
38
+
39
+ type StopContext = z.infer<typeof stop_context_schema>
40
+ type Omitted = z.infer<typeof omitted_schema>
41
+
42
+ // A spinner redraws its line with a carriage return, so only the frame after the last one is what the
43
+ // terminal showed; a CRLF line ends in one and keeps its whole text.
44
+ function shown_frame(line: string): string {
45
+ return line.trimEnd().split(CARRIAGE_RETURN).at(-1) ?? ''
46
+ }
47
+
48
+ function output_lines(output: string): Array<string> {
49
+ return stripVTControlCharacters(output)
50
+ .trimEnd()
51
+ .split(LINE_BREAK)
52
+ .map((line) => agent_role_profile.printable(shown_frame(line)))
53
+ }
54
+
55
+ // The full output is kept beside the ship log, keyed the same way, so the prompt's line range points at
56
+ // text that reads exactly as the excerpt was cut from.
57
+ async function keep_full_output(issue: string, output: string): Promise<string | undefined> {
58
+ const repository = await run_ship_probe.repository_directory()
59
+
60
+ if (repository === undefined) return undefined
61
+
62
+ const target = stamp_file.stamp_path(
63
+ `${OUTPUT_PREFIX}${issue}${KEY_SEPARATOR}`,
64
+ repository,
65
+ OUTPUT_SUFFIX,
66
+ )
67
+
68
+ try {
69
+ return stamp_file.replace_text_stamp(target, output)
70
+ } catch {
71
+ return undefined
72
+ }
73
+ }
74
+
75
+ // A full output that could not be kept still reports the cut, with no path: the prompt then points at
76
+ // the stopped report, which holds the same lines.
77
+ async function omitted_range(issue: string, lines: ReadonlyArray<string>): Promise<Omitted> {
78
+ const path = await keep_full_output(issue, lines.join(LINE_BREAK))
79
+ const last_line = lines.length - EXCERPT_LINE_LIMIT
80
+
81
+ return path === undefined ? { last_line } : { path, last_line }
82
+ }
83
+
84
+ // A line is cut to a width, so one minified or wrapped line cannot spend the prompt's whole budget.
85
+ function clipped(lines: ReadonlyArray<string>): Array<string> {
86
+ return lines.map((line) => line.slice(0, EXCERPT_LINE_WIDTH))
87
+ }
88
+
89
+ async function excerpt_of(
90
+ issue: string,
91
+ output: string,
92
+ ): Promise<Pick<StopContext, 'excerpt' | 'omitted'>> {
93
+ const lines = output.trim() === '' ? [] : output_lines(output)
94
+
95
+ if (lines.length <= EXCERPT_LINE_LIMIT) return { excerpt: clipped(lines) }
96
+
97
+ return {
98
+ excerpt: clipped(lines.slice(-EXCERPT_LINE_LIMIT)),
99
+ omitted: await omitted_range(issue, lines),
100
+ }
101
+ }
102
+
103
+ // A git that cannot answer leaves the list out rather than costing the relaunch — never empty, which
104
+ // would read as a branch with no changes.
105
+ async function read_paths(): Promise<Array<string> | undefined> {
106
+ try {
107
+ return await changed_paths.read_changed_paths(false)
108
+ } catch {
109
+ return undefined
110
+ }
111
+ }
112
+
113
+ async function changed_of(): Promise<Pick<StopContext, 'changed' | 'more_changed'>> {
114
+ const paths = await read_paths()
115
+
116
+ if (paths === undefined) return { more_changed: 0 }
117
+
118
+ return {
119
+ changed: paths.slice(0, CHANGED_PATH_LIMIT),
120
+ more_changed: Math.max(0, paths.length - CHANGED_PATH_LIMIT),
121
+ }
122
+ }
123
+
124
+ async function build(issue: string, output: string): Promise<StopContext> {
125
+ return { ...(await excerpt_of(issue, output)), ...(await changed_of()) }
126
+ }
127
+
128
+ function has_excerpt(context: StopContext | undefined): boolean {
129
+ return context !== undefined && context.excerpt.length > 0
130
+ }
131
+
132
+ function excerpt_heading(issue: string, omitted: Omitted | undefined, shown: number): string {
133
+ if (omitted === undefined) return 'The failed stage output:'
134
+
135
+ const source = omitted.path ?? `the stopped report (\`${run_ship_next.COMMAND.LOG} ${issue}\`)`
136
+ const range = `lines 1-${String(omitted.last_line)} are in ${source}`
137
+
138
+ return `The failed stage output (its last ${String(shown)} lines; ${range}):`
139
+ }
140
+
141
+ // The head lines a budget leaves out join the range the heading names; the file, when one was kept,
142
+ // still holds every line.
143
+ function with_dropped(omitted: Omitted | undefined, dropped: number): Omitted | undefined {
144
+ if (dropped === 0) return omitted
145
+
146
+ return { ...omitted, last_line: (omitted?.last_line ?? 0) + dropped }
147
+ }
148
+
149
+ function excerpt_block(
150
+ issue: string,
151
+ context: StopContext,
152
+ changed: string,
153
+ dropped: number,
154
+ ): string {
155
+ const shown = context.excerpt.slice(dropped)
156
+ const heading = excerpt_heading(issue, with_dropped(context.omitted, dropped), shown.length)
157
+
158
+ return [heading, ...shown, changed].join(LINE_BREAK)
159
+ }
160
+
161
+ function changed_text(context: StopContext): string {
162
+ if (context.changed === undefined) return 'The changed files could not be read from git.'
163
+
164
+ if (context.changed.length === 0) return 'No files changed against the default branch.'
165
+
166
+ const more = context.more_changed > 0 ? ` (and ${String(context.more_changed)} more)` : ''
167
+
168
+ return `Changed files on the branch: ${context.changed.join(', ')}${more}.`
169
+ }
170
+
171
+ // The block the ship-stop prompt carries between the next command and the closing invocation.
172
+ //
173
+ // **It fits the `budget` the prompt leaves it** — the prompt is one argv element, refused past
174
+ // `MAX_VALUE_LENGTH` (joshuafolkken/kit#3964) — by dropping the excerpt's head first, since a check
175
+ // reports its verdict last; the tail line always stays.
176
+ function describe(
177
+ issue: string,
178
+ context: StopContext,
179
+ budget: number = agent_role_profile.MAX_VALUE_LENGTH,
180
+ ): string {
181
+ const changed = changed_text(context)
182
+
183
+ if (!has_excerpt(context)) return changed
184
+
185
+ let dropped = 0
186
+ let block = excerpt_block(issue, context, changed, dropped)
187
+
188
+ while (block.length > budget && dropped < context.excerpt.length - 1) {
189
+ dropped += 1
190
+ block = excerpt_block(issue, context, changed, dropped)
191
+ }
192
+
193
+ return block
194
+ }
195
+
196
+ const run_ship_stop_context = {
197
+ CHANGED_PATH_LIMIT,
198
+ EXCERPT_LINE_LIMIT,
199
+ build,
200
+ describe,
201
+ has_excerpt,
202
+ stop_context_schema,
203
+ }
204
+
205
+ export type { StopContext }
206
+ export { run_ship_stop_context }
@@ -4,6 +4,7 @@ import { git_command } from '#scripts/git/git-command'
4
4
  import { main_merge, type MergeOutcome } from '#scripts/git/main-merge'
5
5
  import { josh_command, type JoshResult } from '#scripts/josh/josh-run'
6
6
  import { error_text } from '#scripts/lib/error-message'
7
+ import { run_ship_overlap } from './run-ship-overlap'
7
8
  import { run_ship_scoped } from './run-ship-scoped'
8
9
 
9
10
  // `josh ship` keeps the branch current with the default branch itself: a default branch that moves
@@ -11,9 +12,9 @@ import { run_ship_scoped } from './run-ship-scoped'
11
12
  // merging the default branch again usually resolves it.
12
13
  //
13
14
  // So the merge happens mechanically: once before the commit (`sync_stage`), and once when
14
- // the followup fails on a pull request GitHub reports `DIRTY` (`followup_stage`). Only a merge git
15
- // itself cannot finish stops the ship, and it stops as `conflict`, naming the unmerged paths so the
16
- // resumed session goes straight to them.
15
+ // the followup fails on a pull request GitHub reports `DIRTY` (`followup_stage`). A conflict only on
16
+ // generated files is regenerated by the merge itself (`main-merge-regenerate.ts`); any other conflict
17
+ // stops the ship as `conflict`, naming the unmerged paths so the resumed session goes straight to them.
17
18
 
18
19
  const should_forward_stderr = true
19
20
  const SUCCESS_EXIT_CODE = 0
@@ -66,12 +67,19 @@ function sync_note(outcome: Exclude<MergeOutcome, { kind: 'conflict' }>): string
66
67
  // touches, and once that work is committed the followup merges again on a clean tree. A merge that changed
67
68
  // the tree is checked by the gate stage that follows, so the commit never carries
68
69
  // an unchecked merge and the gate's record names the merge base the push carries.
69
- async function sync_stage(): Promise<StepResult> {
70
+ //
71
+ // The merge waits first for any lane whose open pull request changes the same paths
72
+ // (`run-ship-overlap.ts`), so it brings that pull request in before the gate rather than meeting it
73
+ // as `DIRTY` after CI.
74
+ async function sync_stage(issue: string): Promise<StepResult> {
75
+ const waited = run_ship_overlap.note(await run_ship_overlap.await_overlap(issue))
70
76
  const outcome = await merge_outcome()
71
77
 
72
- if (outcome.kind === 'conflict') return conflict_result(outcome.files, '')
78
+ if (outcome.kind === 'conflict') return conflict_result(outcome.files, waited)
79
+
80
+ const out = [waited, sync_note(outcome)].filter((line) => line !== '').join('\n')
73
81
 
74
- return { code: SUCCESS_EXIT_CODE, out: sync_note(outcome) }
82
+ return { code: SUCCESS_EXIT_CODE, out }
75
83
  }
76
84
 
77
85
  // Any read that fails answers "not conflicting": the followup failure then stands as it was.
@@ -2,9 +2,13 @@
2
2
  import { existsSync, globSync } from 'node:fs'
3
3
  import path from 'node:path'
4
4
  import { fileURLToPath } from 'node:url'
5
+ import { environment_flags } from '@joshuafolkken/kit/env'
6
+ import { core_budget } from '#scripts/gate/core-budget'
5
7
  import { execa } from 'execa'
6
8
 
7
9
  const PNPM = 'pnpm'
10
+ // `--workers`, `--workers=<n>` and Playwright's `-j` short form.
11
+ const WORKER_FLAGS = /^(?:--workers(?:=|$)|-j)/u
8
12
  const FAIL_EXIT_CODE = 1
9
13
  const ARGV_START = 2
10
14
  const PLAYWRIGHT_PACKAGE = path.join('node_modules', '@playwright', 'test')
@@ -45,15 +49,36 @@ function has_e2e_tests(project_directory: string): boolean {
45
49
  return matches.length > 0
46
50
  }
47
51
 
52
+ // **A local run's worker count is the weight it reserved** (joshuafolkken/kit#3944). Playwright's own
53
+ // default is half the cores, which no ledger entry stood for; CI keeps its config's count, and a
54
+ // count the caller names wins.
55
+ function playwright_arguments(extra_arguments: ReadonlyArray<string>): ReadonlyArray<string> {
56
+ const is_count_named = extra_arguments.some((argument) => WORKER_FLAGS.test(argument))
57
+
58
+ if (is_count_named || environment_flags.is_ci_enabled(process.env['CI'])) return extra_arguments
59
+
60
+ return [`--workers=${String(core_budget.CORE_WEIGHTS.e2e)}`, ...extra_arguments]
61
+ }
62
+
48
63
  async function run_playwright(extra_arguments: ReadonlyArray<string>): Promise<number> {
49
- const result = await execa(PNPM, ['exec', 'playwright', 'test', ...extra_arguments], {
50
- stdio: 'inherit',
51
- reject: false,
52
- })
64
+ const result = await execa(
65
+ PNPM,
66
+ ['exec', 'playwright', 'test', ...playwright_arguments(extra_arguments)],
67
+ { stdio: 'inherit', reject: false },
68
+ )
53
69
 
54
70
  return result.exitCode ?? FAIL_EXIT_CODE
55
71
  }
56
72
 
73
+ // Only a run reserves: a skip starts nothing, and a claim there could wait out a full ledger for it.
74
+ async function run_reserved_playwright(extra_arguments: ReadonlyArray<string>): Promise<number> {
75
+ return await core_budget.with_command_reservation(
76
+ core_budget.CORE_WEIGHTS.e2e,
77
+ async () => await run_playwright(extra_arguments),
78
+ { command: 'test:e2e' },
79
+ )
80
+ }
81
+
57
82
  async function run_guarded_e2e(
58
83
  project_directory: string,
59
84
  extra_arguments: ReadonlyArray<string>,
@@ -61,7 +86,7 @@ async function run_guarded_e2e(
61
86
  const is_installed = is_playwright_installed(project_directory)
62
87
  const has_tests = has_e2e_tests(project_directory)
63
88
  const action = resolve_guard_action(is_installed, has_tests)
64
- if (action === 'run') return await run_playwright(extra_arguments)
89
+ if (action === 'run') return await run_reserved_playwright(extra_arguments)
65
90
 
66
91
  console.info(`josh test:e2e: ${SKIP_REASONS[action]} — skipping Playwright e2e.`)
67
92
 
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { existsSync, globSync } from 'node:fs'
3
+ import { availableParallelism } from 'node:os'
3
4
  import path from 'node:path'
4
5
  import { fileURLToPath } from 'node:url'
5
6
  import { PROJECT_ENVIRONMENT_KEYS } from '#ports'
7
+ import { free_cores } from '#scripts/gate/free-cores'
6
8
  import { project_checks } from '#scripts/gate/project-checks'
7
9
  import { execa } from 'execa'
8
10
  import { project_test_script, type ProjectScriptState } from './project-test-script'
@@ -175,11 +177,24 @@ function isolated_unit_environment(
175
177
  // Reading the share first and claiming afterwards leaves a window in which two hooks firing together
176
178
  // both count "alone" and both run uncapped. Inside the marker the number is identical — this run's own
177
179
  // record is now on disk and `is_nested_run` therefore contributes nothing — and the window is gone.
180
+ //
181
+ // **The machine is read only when no count was typed.** The reading waits out a sample window, and a
182
+ // caller's `--maxWorkers` — the gate's cap among them, already bounded by the same reading — wins
183
+ // whatever it says.
184
+ async function vitest_share(vitest_arguments: ReadonlyArray<string>): Promise<number | undefined> {
185
+ if (unit_worker_share.has_worker_flag(vitest_arguments)) return undefined
186
+
187
+ const available_cores = availableParallelism()
188
+ const free = await free_cores.read({ available_cores })
189
+
190
+ return unit_worker_share.current_share(available_cores, unit_worker_share.live_run_count(), free)
191
+ }
192
+
178
193
  async function run_vitest(vitest_arguments: ReadonlyArray<string>): Promise<number> {
179
194
  return await unit_worker_share.with_run_marker(async () => {
180
195
  const sized = unit_worker_share.worker_arguments(
181
196
  vitest_arguments,
182
- unit_worker_share.current_share(),
197
+ await vitest_share(vitest_arguments),
183
198
  )
184
199
  const result = await execa(PNPM, ['exec', 'vitest', ...vitest_arguments, ...sized], {
185
200
  env: isolated_unit_environment(),
@@ -153,6 +153,22 @@ function resolve_unit_workers(available_cores: number, runs: number): number | u
153
153
  return Math.max(MIN_WORKERS, Math.floor(available_cores / runs))
154
154
  }
155
155
 
156
+ // **The run count divides the machine; the free cores bound what that division hands out.** The
157
+ // share counts only unit runs, so load from outside the core budget's ledger — Claude sessions, other
158
+ // tools — went unseen: 22 of 38 ledger samples at a load average of 20 or more had vitest as their
159
+ // heaviest process, one at 381% over 30 processes. `free_cores` is `free-cores.ts`'s reading, the
160
+ // cores less that outside load. `undefined` — a CI runner, a CPU quota — and a reading at or above
161
+ // the ceiling both leave `workers` as it was, so an unread or quiet machine changes nothing.
162
+ function cap_to_free(
163
+ workers: number | undefined,
164
+ free_cores: number | undefined,
165
+ available_cores: number,
166
+ ): number | undefined {
167
+ if (free_cores === undefined || free_cores >= (workers ?? available_cores)) return workers
168
+
169
+ return Math.max(MIN_WORKERS, Math.floor(free_cores))
170
+ }
171
+
156
172
  // The share this run may take, read from the machine. Separate from `resolve_unit_workers` so that
157
173
  // one stays a pure function of two numbers and every test of it can pass them.
158
174
  //
@@ -163,10 +179,12 @@ function resolve_unit_workers(available_cores: number, runs: number): number | u
163
179
  function current_share(
164
180
  available_cores: number = availableParallelism(),
165
181
  live_runs: number = live_run_count(),
182
+ free_cores?: number,
166
183
  ): number | undefined {
167
184
  const own_run = is_nested_run() ? 0 : SOLO_RUNS
185
+ const share = resolve_unit_workers(available_cores, live_runs + own_run)
168
186
 
169
- return resolve_unit_workers(available_cores, live_runs + own_run)
187
+ return cap_to_free(share, free_cores, available_cores)
170
188
  }
171
189
 
172
190
  function is_worker_flag(argument: string): boolean {
@@ -182,13 +200,15 @@ function is_worker_flag(argument: string): boolean {
182
200
  // would make this function's own answer depend on what happened to be running while it was asked, and
183
201
  // `f(args, undefined)` takes the default rather than the value — so the case that matters most, "this
184
202
  // run is alone", would be the one case no test could express.
203
+ function has_worker_flag(vitest_arguments: ReadonlyArray<string>): boolean {
204
+ return vitest_arguments.some((argument) => is_worker_flag(argument))
205
+ }
206
+
185
207
  function worker_arguments(
186
208
  vitest_arguments: ReadonlyArray<string>,
187
209
  share: number | undefined,
188
210
  ): Array<string> {
189
- if (share === undefined || vitest_arguments.some((argument) => is_worker_flag(argument))) {
190
- return []
191
- }
211
+ if (share === undefined || has_worker_flag(vitest_arguments)) return []
192
212
 
193
213
  return [`${WORKER_FLAG}=${String(share)}`]
194
214
  }
@@ -230,7 +250,9 @@ const unit_worker_share = {
230
250
  NESTED_VALUE,
231
251
  RUN_PREFIX,
232
252
  SOLO_RUNS,
253
+ cap_to_free,
233
254
  current_share,
255
+ has_worker_flag,
234
256
  is_nested_run,
235
257
  live_run_count,
236
258
  marker_path,
@@ -32,8 +32,9 @@ const SEGMENT_PATTERN = /&&|\|\||;|\|/u
32
32
  // last, while `||` keeps the first command's and runs the second only when it failed. `|&` is a pipe
33
33
  // and is cut here, its `&` left in the fragment that follows.
34
34
  const PIPE_PATTERN = /(?<!\|)\|(?!\|)/u
35
- // What `SEGMENT_PATTERN` cuts on minus the pipe, for reading *inside* one pipe segment.
36
- const CHAIN_PATTERN = /&&|\|\||;/u
35
+ // What `SEGMENT_PATTERN` cuts on minus the pipe, for reading *inside* one pipe segment — plus the
36
+ // newline, which ends a command as `;` does, so a line typed before the piped check is not read as it.
37
+ const CHAIN_PATTERN = /&&|\|\||;|\n/u
37
38
  // A quoted span is text, not shell syntax, and it is removed before the pipe reading below. This
38
39
  // repository's issue bodies, comment bodies and commit messages quote command chains constantly, so
39
40
  // `--body "cd x && pnpm josh gate | tail"` is a `gh` call and nothing else — a reader that split its
@@ -299,8 +300,8 @@ function drop_redirections(words: ReadonlyArray<string>): Array<string> {
299
300
  // **The match's own text is what is sliced off, not a fixed word count.** The prefix is two words in
300
301
  // `josh lint`, three in `pnpm josh lint` and four in `pnpm exec josh lint`, so counting words would
301
302
  // leave `josh` itself in the argument list for one of the three spellings.
302
- function josh_arguments(command: string): Array<string> {
303
- const text = command_words(command_segment(command)).join(' ')
303
+ function josh_arguments_of_segment(segment: string): Array<string> {
304
+ const text = command_words(segment).join(' ')
304
305
  const match = JOSH_PATTERN.exec(text)
305
306
 
306
307
  if (match === null) return []
@@ -313,6 +314,29 @@ function josh_arguments(command: string): Array<string> {
313
314
  return drop_redirections(words)
314
315
  }
315
316
 
317
+ function josh_arguments(command: string): Array<string> {
318
+ return josh_arguments_of_segment(command_segment(command))
319
+ }
320
+
321
+ // The josh subcommand one segment runs plus every argument it passes, redirections dropped, or `''`
322
+ // for a segment that runs none. **Flags are kept**: which of them ask nothing new (`run:board --chat`)
323
+ // and which turn a read into a write (`run:carry --cut`) is the reader's rule, not this one's.
324
+ function josh_signature_of_segment(segment: string): string {
325
+ const name = josh_command_of_segment(segment)
326
+
327
+ if (name === '') return ''
328
+
329
+ return [name, ...josh_arguments_of_segment(segment)].join(' ')
330
+ }
331
+
332
+ // Every segment's signature, read over the same unquoted chain `josh_commands_of` walks.
333
+ function josh_signatures_of(command: string): Array<string> {
334
+ return unquoted(command)
335
+ .split(SEGMENT_PATTERN)
336
+ .map((segment) => josh_signature_of_segment(segment))
337
+ .filter((signature) => signature !== '')
338
+ }
339
+
316
340
  const time_shell = {
317
341
  JOSH_PREFIX,
318
342
  bash_command,
@@ -323,6 +347,7 @@ const time_shell = {
323
347
  josh_arguments,
324
348
  josh_command_of,
325
349
  josh_commands_of,
350
+ josh_signatures_of,
326
351
  leading_word,
327
352
  unquoted,
328
353
  }
@@ -334,6 +334,7 @@ function to_spans(events: ReadonlyArray<TimelineEvent>, finished: FinishedAt): A
334
334
  label: event.label,
335
335
  josh_command: event.josh_command,
336
336
  josh_commands: event.josh_commands,
337
+ josh_signatures: event.josh_signatures,
337
338
  check_key: event.check_key,
338
339
  marker: event.marker,
339
340
  is_bundleable: event.is_bundleable,
@@ -55,6 +55,9 @@ interface ToolCall {
55
55
  // A chained `pnpm josh lint:related && pnpm josh test:related` carries both; the count tables expand
56
56
  // it so the ones `josh_command` drops are still counted.
57
57
  josh_commands: ReadonlyArray<string>
58
+ // Each of those with the operands it named, flags dropped — what tells `lane:output 1` from
59
+ // `lane:output 2` once the span has kept no command string. `run-poll-hook.ts` reads it.
60
+ josh_signatures: ReadonlyArray<string>
58
61
  check_key: string
59
62
  marker: PhaseMarker
60
63
  is_bundleable: boolean
@@ -83,6 +86,7 @@ const NO_CALL: ToolCall = {
83
86
  label: '',
84
87
  josh_command: '',
85
88
  josh_commands: [],
89
+ josh_signatures: [],
86
90
  check_key: time_single_check.NO_CHECK,
87
91
  marker: time_markers.NO_MARKER,
88
92
  message_id: NO_MESSAGE_ID,
@@ -95,6 +99,7 @@ const UNKNOWN_CALL: ToolCall = {
95
99
  label: UNKNOWN_TOOL,
96
100
  josh_command: '',
97
101
  josh_commands: [],
102
+ josh_signatures: [],
98
103
  check_key: time_single_check.NO_CHECK,
99
104
  marker: time_markers.NO_MARKER,
100
105
  message_id: NO_MESSAGE_ID,
@@ -111,6 +116,7 @@ function non_bash_call(name: string, input: unknown, message_id: string): ToolCa
111
116
  label: name,
112
117
  josh_command: '',
113
118
  josh_commands: [],
119
+ josh_signatures: [],
114
120
  check_key: time_single_check.NO_CHECK,
115
121
  marker: time_markers.tool_marker(name, input),
116
122
  message_id,
@@ -132,6 +138,7 @@ function bash_call(input: unknown, message_id: string): ToolCall {
132
138
  label: time_shell.bash_label(command),
133
139
  josh_command,
134
140
  josh_commands: time_shell.josh_commands_of(command),
141
+ josh_signatures: time_shell.josh_signatures_of(command),
135
142
  check_key: time_single_check.check_key(josh_command, command),
136
143
  marker: time_markers.bash_marker(command),
137
144
  message_id,
@@ -43,13 +43,17 @@ project.inlang/cache/
43
43
  *storybook.log
44
44
  storybook-static
45
45
 
46
- # Verification gate caches (josh lint / josh check / josh cspell:dot), plus the edit hook's own
47
- # eslint cache (josh format:edited), which is kept apart from the gate's on purpose
46
+ # Verification gate caches (josh lint / josh check / josh cspell:dot), plus the edit hook's, the
47
+ # scoped lint's and the pre-commit hook's own, which are kept apart from the gate's on purpose
48
48
  .eslintcache
49
49
  .eslintcache.edit
50
50
  .eslintcache.related
51
+ .eslintcache.commit
51
52
  .tsbuildinfo
52
53
  .cspellcache
54
+ .cspellcache.commit
55
+ .prettiercache
56
+ .prettiercache.related
53
57
 
54
58
  # Bundle analysis
55
59
  stats-client.html
@@ -1,68 +0,0 @@
1
- import {
2
- investigation_reads
3
- } from "./chunk-H5HVWYXK.js";
4
- import {
5
- lane_guard_policy
6
- } from "./chunk-SHGPANFB.js";
7
- import {
8
- hook_decision
9
- } from "./chunk-J47HU5H5.js";
10
-
11
- // scripts/delegation/investigation-guard.ts
12
- import { text } from "node:stream/consumers";
13
- import { fileURLToPath } from "node:url";
14
- var SWITCH_ENV_KEY = "JOSH_INVESTIGATION_GUARD";
15
- var STAMP_PREFIX = "josh-investigation-guard-";
16
- var NOTICE_STAMP_PREFIX = "josh-investigation-guard-notice-";
17
- function investigation_mode() {
18
- return lane_guard_policy.mode_here("investigation");
19
- }
20
- function should_block(tail, call, refused_at_ms, run) {
21
- if (investigation_mode() !== "refuse") return false;
22
- return investigation_reads.should_block(tail, call, refused_at_ms, run);
23
- }
24
- function should_notify(tail, call, notified_at_ms, run) {
25
- if (investigation_mode() !== "notice") return false;
26
- return investigation_reads.should_block(tail, call, notified_at_ms, run);
27
- }
28
- var NOTICE = `\u{1F4A1} investigation: files read and not edited, or search turns, since the last delegated unit reached the threshold; the read proceeds. Run \`pnpm josh delegate investigation\`, then dispatch the \`${investigation_reads.INVESTIGATOR_AGENT}\` agent (\`kit:${investigation_reads.INVESTIGATOR_AGENT}\` in a consumer); it returns \`file:line\` citations, never the file text. Keep main-line reads to files you will edit.`;
29
- function notice_text(_call, tail) {
30
- const pending = investigation_reads.tally_of(tail).pending.map((absolute) => investigation_reads.repository_relative(absolute));
31
- return pending.length === 0 ? NOTICE : `${NOTICE} Read and not edited so far: ${pending.join(" ")}.`;
32
- }
33
- var GUARD = hook_decision.create_transcript_guard({
34
- prefix: STAMP_PREFIX,
35
- switch_key: SWITCH_ENV_KEY,
36
- is_candidate: investigation_reads.is_refusable_call,
37
- should_block,
38
- reason: investigation_reads.REASON,
39
- // The lane-child notice fires where the refusal is withheld, on a record of its own so it
40
- // never spends the refusal's stamp.
41
- notify: {
42
- prefix: NOTICE_STAMP_PREFIX,
43
- should_notify,
44
- text: notice_text
45
- }
46
- });
47
- var investigation_refusal = GUARD.refusal;
48
- var investigation_outcome = GUARD.outcome;
49
- var { is_enabled, refusal_path } = GUARD;
50
- var investigation_notice_path = hook_decision.create_refusal_stamp(NOTICE_STAMP_PREFIX).path;
51
- var { deny_envelope, load_environment_file, DISABLED_VALUES } = hook_decision;
52
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
53
- if (process.stdin.isTTY) hook_decision.report_no_payload("investigation:guard");
54
- else hook_decision.write_outcome(await text(process.stdin), investigation_outcome);
55
- }
56
-
57
- export {
58
- SWITCH_ENV_KEY,
59
- NOTICE,
60
- investigation_refusal,
61
- investigation_outcome,
62
- is_enabled,
63
- refusal_path,
64
- investigation_notice_path,
65
- deny_envelope,
66
- load_environment_file,
67
- DISABLED_VALUES
68
- };