@joshuafolkken/kit 1.978.0 → 1.983.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 (69) hide show
  1. package/.claude/skills/epic-commands/SKILL.md +6 -5
  2. package/.claude/skills/workflow-commands/SKILL.md +9 -9
  3. package/.claude/skills/workflow-commands/background-commands.md +1 -1
  4. package/.claude/skills/workflow-commands/fullrun-steps.md +8 -8
  5. package/.claude/skills/workflow-commands/halfrun.md +4 -5
  6. package/.claude/skills/workflow-commands/issue-comments.md +1 -1
  7. package/.claude/skills/workflow-commands/issue-scout.md +21 -22
  8. package/.claude/skills/workflow-commands/kickoff.md +8 -9
  9. package/.claude/skills/workflow-commands/observation-filing.md +9 -10
  10. package/.claude/skills/workflow-commands/prerequisite.md +7 -6
  11. package/.claude/skills/workflow-commands/retrospective.md +5 -5
  12. package/.claude/skills/workflow-commands/rule-residency.md +2 -2
  13. package/.claude/skills/workflow-commands/split-assessment.md +4 -3
  14. package/.claude/skills/workflow-commands/target-repository.md +7 -6
  15. package/dist/hooks/batch-guard.js +4 -4
  16. package/dist/hooks/{chunk-ZPLXLJ7O.js → chunk-47SPMGOD.js} +16 -1
  17. package/dist/hooks/{chunk-BVMSPG3M.js → chunk-5JX6HXDX.js} +3 -3
  18. package/dist/hooks/{chunk-KQG4776P.js → chunk-APVOACVT.js} +1 -1
  19. package/dist/hooks/{chunk-2K52BRWV.js → chunk-ASUZGZFT.js} +2 -2
  20. package/dist/hooks/{chunk-CRGJTD6E.js → chunk-ERXMJJOV.js} +1 -1
  21. package/dist/hooks/{chunk-Q7LE77CO.js → chunk-IM3KM25Y.js} +31 -29
  22. package/dist/hooks/{chunk-BNGGEY3Z.js → chunk-LBXQHXMC.js} +129 -342
  23. package/dist/hooks/{chunk-NXL3SBXY.js → chunk-P3UX7SFN.js} +2 -2
  24. package/dist/hooks/{chunk-F6OM6SIG.js → chunk-RFPHKU7C.js} +12 -12
  25. package/dist/hooks/{chunk-ZYYSBNR6.js → chunk-ZT65LR3U.js} +4 -4
  26. package/dist/hooks/codex-hook-adapter.js +10 -10
  27. package/dist/hooks/duplicate-read-guard.js +5 -5
  28. package/dist/hooks/format-edited.js +2 -2
  29. package/dist/hooks/investigation-guard.js +5 -5
  30. package/dist/hooks/pretool-guard.js +9 -9
  31. package/dist/hooks/stop-guard.js +4 -4
  32. package/dist/josh.js +16 -1
  33. package/package.json +1 -1
  34. package/prompts/collaboration-workflow/epic-bundle.md +8 -8
  35. package/prompts/collaboration-workflow/issue-template.md +5 -5
  36. package/prompts/collaboration-workflow/rule-delivery.md +43 -30
  37. package/prompts/collaboration-workflow/upstream-interrupt.md +1 -1
  38. package/prompts/collaboration-workflow/wip-cap.md +3 -3
  39. package/prompts/review.md +11 -9
  40. package/scripts/delegation/delegation-policy.ts +9 -9
  41. package/scripts/document/document-byte-budget.ts +1 -1
  42. package/scripts/issue/issue-file-cli.ts +188 -0
  43. package/scripts/issue/issue-file.ts +192 -0
  44. package/scripts/issue/issue-lint-cli.ts +1 -1
  45. package/scripts/issue/issue-scout-cli.ts +41 -97
  46. package/scripts/issue/issue-scout-closed.ts +102 -0
  47. package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +9 -1
  48. package/scripts/josh/josh-command-map.ts +1 -0
  49. package/scripts/josh/josh-commands-issue.ts +11 -0
  50. package/scripts/josh/josh-commands-project.ts +5 -1
  51. package/scripts/propagate/propagate-run.ts +1 -0
  52. package/scripts/propagate/propagate-select.ts +72 -0
  53. package/scripts/propagate/propagate-targets.ts +5 -3
  54. package/scripts/propagate/propagate.ts +86 -24
  55. package/scripts/rules/bash-triggers.ts +19 -6
  56. package/scripts/rules/delivered-rules.ts +28 -32
  57. package/scripts/rules/direct-filing.ts +38 -0
  58. package/scripts/rules/filing-cap.ts +11 -8
  59. package/scripts/rules/issue-fold.ts +4 -4
  60. package/scripts/rules/oracle-consulted.ts +4 -4
  61. package/scripts/rules/oracle-firing.ts +13 -16
  62. package/scripts/rules/rule-body-guard.ts +6 -9
  63. package/scripts/rules/stop-rules.ts +3 -2
  64. package/scripts/test/pilot-files.ts +0 -1
  65. package/scripts/version/fresh-metadata-cache.ts +36 -0
  66. package/scripts/version/latest-update.ts +2 -1
  67. package/scripts/rules/issue-bug-label.ts +0 -85
  68. package/scripts/rules/issue-filing-args.ts +0 -50
  69. package/scripts/rules/issue-scout.ts +0 -64
@@ -2,7 +2,6 @@ import {
2
2
  ALREADY_DONE_LABEL,
3
3
  BREAKING_CHANGE_LABEL,
4
4
  BUGFIX_LABEL,
5
- BUG_LABEL,
6
5
  CONTEXT_CUT_THRESHOLD,
7
6
  ENHANCEMENT_LABEL,
8
7
  FULL_PAGE_QUERY,
@@ -20,7 +19,6 @@ import {
20
19
  git_gh_api_path,
21
20
  git_gh_exec,
22
21
  has_any_label,
23
- has_label_name,
24
22
  has_stderr_field,
25
23
  lane_background,
26
24
  lane_park,
@@ -37,31 +35,31 @@ import {
37
35
  run_watcher_guard,
38
36
  shell_segments,
39
37
  tail_commands
40
- } from "./chunk-Q7LE77CO.js";
38
+ } from "./chunk-IM3KM25Y.js";
41
39
  import {
42
40
  find_local_bin_upwards
43
41
  } from "./chunk-3V37IID6.js";
44
42
  import {
45
43
  batch_outcome
46
- } from "./chunk-NXL3SBXY.js";
44
+ } from "./chunk-P3UX7SFN.js";
47
45
  import {
48
46
  investigation_refusal
49
- } from "./chunk-BVMSPG3M.js";
47
+ } from "./chunk-5JX6HXDX.js";
50
48
  import {
51
49
  duplicate_read_outcome
52
- } from "./chunk-ZYYSBNR6.js";
50
+ } from "./chunk-ZT65LR3U.js";
53
51
  import {
54
52
  investigation_reads
55
- } from "./chunk-F6OM6SIG.js";
53
+ } from "./chunk-RFPHKU7C.js";
56
54
  import {
57
55
  lane_guard_policy,
58
56
  time_batch_guard
59
- } from "./chunk-2K52BRWV.js";
57
+ } from "./chunk-ASUZGZFT.js";
60
58
  import {
61
59
  hook_decision,
62
60
  lane_child_marker,
63
61
  run_issue_number
64
- } from "./chunk-KQG4776P.js";
62
+ } from "./chunk-APVOACVT.js";
65
63
  import {
66
64
  PLATFORM_TEMP_ROOT,
67
65
  cost_attribute,
@@ -71,14 +69,28 @@ import {
71
69
  json_value,
72
70
  lane_paths,
73
71
  stamp_file,
74
- time_density_hook,
75
72
  time_shell
76
- } from "./chunk-ZPLXLJ7O.js";
73
+ } from "./chunk-47SPMGOD.js";
77
74
 
78
75
  // scripts/hooks/pretool-guard.ts
79
76
  import { text } from "node:stream/consumers";
80
77
  import { fileURLToPath as fileURLToPath3 } from "node:url";
81
78
 
79
+ // scripts/rules/direct-filing.ts
80
+ var DIRECT_FILING_REASON = '\u26D4 direct filing: an Issue is filed with `pnpm josh issue:file "<title>" --body-file <path> --depth <0|1|2> [--route <route>] [--label <name>] [--repo <owner/repo>]`, never with `gh api \u2026/issues` or `gh issue create`. The command runs every filing step in order \u2014 the duplicate scout, the body lint and its classification labels, the depth and `route:` labels, the `## Origin` check when filing into another repository, and `epic:bundle` after the create \u2014 so none of them is a step to remember. When the scout prints candidates it holds the filing: read each, then reissue with `--distinct <N,\u2026>` for the ones that are separate deliverables. Reference: `docs/josh-commands.md` \u2192 "`josh issue:file`". **This rule fires on every occurrence, not once per run.**';
81
+ function is_any_filing(command) {
82
+ return bash_triggers.is_direct_filing(command) || bash_triggers.is_issue_filing(command);
83
+ }
84
+ var ROW = {
85
+ id: "direct-filing",
86
+ is_trigger: bash_triggers.on_bash_command(bash_triggers.is_direct_filing),
87
+ reason: DIRECT_FILING_REASON,
88
+ decide: () => true,
89
+ keeps: bash_triggers.on_bash_command(bash_triggers.is_issue_filing),
90
+ reaches: bash_triggers.on_bash_command(is_any_filing)
91
+ };
92
+ var direct_filing = { DIRECT_FILING_REASON, ROW };
93
+
82
94
  // scripts/run/run-progress.ts
83
95
  var DEFAULT_INTERVAL_MINUTES = 20;
84
96
  var INTERVAL_KEY = "JOSH_PROGRESS_INTERVAL_MINUTES";
@@ -382,7 +394,7 @@ function carries_a_file_body(command, has_file = existsSync) {
382
394
  return writes_existing_file(command, has_file) || has_body_interpreter(command);
383
395
  }
384
396
  var FILE_BODY_REASON = "\u26D4 file body in a shell command: this carries a file's new text inline \u2014 a heredoc rewriting an existing file, an interpreter (`python3 - <<`, `node -e`) whose body writes, or an in-place `perl -0pi -e`. `CLAUDE.md` forbids it because the command body is re-read on every later request of the run, so an editing script written early is billed for the rest of it (`file-edits.md` measured one run at ~2m45s spent retyping existing lines). Edit a region with the Edit tool (old / new), or pass a body by path (`--field body=@<path>`, `--body-file <path>`). A new-file creation (`cat > <new-file> <<EOF`), a read-only heredoc (`cat <<EOF`), and a short `sed -i` stay allowed. **This rule fires on every occurrence, not once per run.**";
385
- var ROW = {
397
+ var ROW2 = {
386
398
  id: "file-body",
387
399
  is_trigger: bash_triggers.on_bash_command((command) => carries_a_file_body(command)),
388
400
  reason: FILE_BODY_REASON,
@@ -390,7 +402,7 @@ var ROW = {
390
402
  };
391
403
  var file_body = {
392
404
  FILE_BODY_REASON,
393
- ROW,
405
+ ROW: ROW2,
394
406
  carries_a_file_body,
395
407
  is_in_place_perl,
396
408
  is_interpreter_write,
@@ -518,7 +530,7 @@ function is_force_or_delete(command) {
518
530
  return shell_segments.segments_of(command).some((segment) => is_destructive_segment(segment));
519
531
  }
520
532
  var GIT_FORCE_REASON = "\u26D4 force push or branch delete: this rewrites or removes shared history, which is Tier C and needs explicit current-turn user instruction \u2014 never an AI decision, even to fix a problem you caused (`prompts/collaboration-workflow/operating-rules.md`). The `.claude/settings.json` deny list matches globs, so it misses this spelling: a combined short cluster (`git push -uf`), a `git -C <path>` prefix, and the colon-form delete (`git push origin :branch`) all pass it. This row reads the argv instead, so the spelling does not matter. For an ordinary push use `pnpm josh git`; a force push, a `git push --delete` / `:branch`, or a `git branch -d` / `-D` is the user's to run in their own terminal. **This rule fires on every occurrence, not once per run.**";
521
- var ROW2 = {
533
+ var ROW3 = {
522
534
  id: "git-force",
523
535
  is_trigger: bash_triggers.on_bash_command(is_force_or_delete),
524
536
  reason: GIT_FORCE_REASON,
@@ -526,7 +538,7 @@ var ROW2 = {
526
538
  };
527
539
  var git_force = {
528
540
  GIT_FORCE_REASON,
529
- ROW: ROW2,
541
+ ROW: ROW3,
530
542
  is_branch_delete,
531
543
  is_force_or_delete,
532
544
  is_force_or_delete_push
@@ -815,8 +827,8 @@ function selected_threshold(values) {
815
827
  function threshold_option(over) {
816
828
  return over === void 0 ? {} : { over };
817
829
  }
818
- function path_option(path7) {
819
- return path7 === void 0 ? {} : { path: path7 };
830
+ function path_option(path6) {
831
+ return path6 === void 0 ? {} : { path: path6 };
820
832
  }
821
833
  function to_options(values) {
822
834
  if (values.cut === true && values.over !== void 0) return void 0;
@@ -1056,7 +1068,7 @@ function is_reissued_refusal(now_ms, delivered_at_ms) {
1056
1068
  function decide2(_call, run2, _can_record, delivered_at_ms) {
1057
1069
  return !is_reissued_refusal(run2.now_ms, delivered_at_ms);
1058
1070
  }
1059
- var ROW3 = {
1071
+ var ROW4 = {
1060
1072
  id: "implementation-cut",
1061
1073
  is_trigger: is_over_threshold_edit,
1062
1074
  reason: IMPLEMENTATION_CUT_REASON,
@@ -1066,7 +1078,7 @@ var ROW3 = {
1066
1078
  var implementation_cut = {
1067
1079
  IMPLEMENTATION_CUT_REASON,
1068
1080
  REISSUE_WINDOW_MS,
1069
- ROW: ROW3,
1081
+ ROW: ROW4,
1070
1082
  decide: decide2,
1071
1083
  is_edit_tool,
1072
1084
  is_over_threshold_edit,
@@ -1075,201 +1087,6 @@ var implementation_cut = {
1075
1087
  uncut_run_issue
1076
1088
  };
1077
1089
 
1078
- // scripts/rules/issue-bug-label.ts
1079
- import { readFileSync as readFileSync3 } from "node:fs";
1080
- import path4 from "node:path";
1081
-
1082
- // scripts/issue/markdown-section.ts
1083
- var HEADING_PREFIX = "## ";
1084
- function is_heading(line) {
1085
- return line.trimStart().startsWith(HEADING_PREFIX);
1086
- }
1087
- function is_the_heading(line, heading) {
1088
- return line.trim() === heading;
1089
- }
1090
- function next_fence(line, fence) {
1091
- const marker = /^ {0,3}(`{3,}|~{3,})/u.exec(line)?.[1];
1092
- if (fence === "") return marker;
1093
- const closing = new RegExp(
1094
- String.raw`^ {0,3}${fence.charAt(0)}{${String(fence.length)},}\s*$`,
1095
- "u"
1096
- );
1097
- if (closing.test(line)) return "";
1098
- return void 0;
1099
- }
1100
- function unfenced_lines(body) {
1101
- let fence = "";
1102
- const visible = [];
1103
- for (const line of body.split("\n")) {
1104
- const next = next_fence(line, fence);
1105
- const visible_line = line.replace(/^(?: {4}|\t).*/u, "");
1106
- visible.push(next === void 0 && fence === "" ? visible_line : "");
1107
- fence = next ?? fence;
1108
- }
1109
- return visible;
1110
- }
1111
- function has_unfenced_line(body, text2) {
1112
- return unfenced_lines(body).some((line) => line.trim() === text2);
1113
- }
1114
- function has_line(body, text2) {
1115
- return has_unfenced_line(body, text2);
1116
- }
1117
- function section_lines(body, heading) {
1118
- const lines = body.split("\n");
1119
- const visible = unfenced_lines(body);
1120
- const start = visible.findIndex((line) => is_the_heading(line, heading));
1121
- if (start === -1) return [];
1122
- const rest = lines.slice(start + 1);
1123
- const end = visible.slice(start + 1).findIndex((line) => is_heading(line));
1124
- return end === -1 ? rest : rest.slice(0, end);
1125
- }
1126
- var markdown_section = { section_lines, is_heading, has_line, has_unfenced_line };
1127
-
1128
- // scripts/issue/issue-bug-label.ts
1129
- var BUG_DECLARATION_LINE = "- \u7A2E\u5225: \u4E0D\u5177\u5408";
1130
- function is_bug_fix(body) {
1131
- const background = markdown_section.section_lines(body, "## \u80CC\u666F").join("\n");
1132
- return markdown_section.has_unfenced_line(background, BUG_DECLARATION_LINE);
1133
- }
1134
- var issue_bug_label = { BUG_DECLARATION_LINE, is_bug_fix };
1135
-
1136
- // scripts/issue/issue-classification.ts
1137
- var FEATURE_DECLARATIONS = ["- \u76EE\u7684: \u6A5F\u80FD\u8FFD\u52A0", "- \u76EE\u7684: \u6A5F\u80FD\u6539\u5584"];
1138
- var BREAKING_DECLARATION = "- \u4E92\u63DB\u6027: \u7834\u58CA\u7684\u5909\u66F4";
1139
- var NON_BUG_DECLARATION = "- \u7A2E\u5225: \u975E\u4E0D\u5177\u5408";
1140
- var CLASSIFICATION_LABELS = [BUG_LABEL, ENHANCEMENT_LABEL, BREAKING_CHANGE_LABEL];
1141
- var CONFLICT = "bug and enhancement declarations cannot be combined";
1142
- var MISSING_BUG_CLASSIFICATION = "missing bug classification";
1143
- var CONFLICTING_BUG_CLASSIFICATIONS = "conflicting bug classifications";
1144
- function required_labels(body) {
1145
- const labels = [];
1146
- const background = markdown_section.section_lines(body, "## \u80CC\u666F").join("\n");
1147
- if (issue_bug_label.is_bug_fix(body)) labels.push(BUG_LABEL);
1148
- if (FEATURE_DECLARATIONS.some((line) => markdown_section.has_unfenced_line(background, line))) {
1149
- labels.push(ENHANCEMENT_LABEL);
1150
- }
1151
- if (markdown_section.has_unfenced_line(background, BREAKING_DECLARATION)) {
1152
- labels.push(BREAKING_CHANGE_LABEL);
1153
- }
1154
- return labels;
1155
- }
1156
- function labels_for(body, existing) {
1157
- const missing = required_labels(body).filter((label) => !has_label_name(existing, label));
1158
- return [...existing, ...missing];
1159
- }
1160
- function bug_classification_problem(body) {
1161
- const background = markdown_section.section_lines(body, "## \u80CC\u666F").join("\n");
1162
- const has_bug = issue_bug_label.is_bug_fix(body);
1163
- const has_non_bug = markdown_section.has_unfenced_line(background, NON_BUG_DECLARATION);
1164
- if (!has_bug && !has_non_bug) return MISSING_BUG_CLASSIFICATION;
1165
- if (has_bug && has_non_bug) return CONFLICTING_BUG_CLASSIFICATIONS;
1166
- return void 0;
1167
- }
1168
- function problems(body) {
1169
- const classification_problem = bug_classification_problem(body);
1170
- const found = [];
1171
- if (classification_problem !== void 0) found.push(classification_problem);
1172
- const labels = required_labels(body);
1173
- if (labels.includes(BUG_LABEL) && labels.includes(ENHANCEMENT_LABEL)) found.push(CONFLICT);
1174
- return found;
1175
- }
1176
- var issue_classification = {
1177
- BREAKING_DECLARATION,
1178
- CLASSIFICATION_LABELS,
1179
- FEATURE_DECLARATIONS,
1180
- NON_BUG_DECLARATION,
1181
- labels_for,
1182
- problems,
1183
- required_labels
1184
- };
1185
-
1186
- // scripts/rules/issue-filing-args.ts
1187
- var SHELL_WORD = /(?:[^\s'"]|'[^']*'|"[^"]*")+/gu;
1188
- var QUOTE = /['"]/gu;
1189
- var RAW_FIELDS = /* @__PURE__ */ new Set(["-f", "--raw-field"]);
1190
- var FILE_FIELDS = /* @__PURE__ */ new Set(["-F", "--field"]);
1191
- var LABEL_OPTIONS = /* @__PURE__ */ new Set(["-l", "--label"]);
1192
- var BODY_FILE_OPTIONS = /* @__PURE__ */ new Set(["--body-file"]);
1193
- var INLINE_BODY_OPTIONS = /* @__PURE__ */ new Set(["-b", "--body"]);
1194
- var BODY_FILE_PREFIX = "body=@";
1195
- function words_of2(command) {
1196
- return [...command.matchAll(SHELL_WORD)].map((match) => match[0].replaceAll(QUOTE, ""));
1197
- }
1198
- function values_after(words, flags) {
1199
- return words.flatMap((word, index) => {
1200
- const value = words[index + 1];
1201
- return value !== void 0 && flags.has(word) ? [value] : [];
1202
- });
1203
- }
1204
- function has_inline_body(words) {
1205
- const has_raw = values_after(words, RAW_FIELDS).some((value) => value.startsWith("body="));
1206
- return has_raw || values_after(words, INLINE_BODY_OPTIONS).length > 0;
1207
- }
1208
- function body_file(command) {
1209
- const words = words_of2(command);
1210
- const fields = values_after(words, FILE_FIELDS).filter((value) => value.startsWith("body="));
1211
- const options = values_after(words, BODY_FILE_OPTIONS);
1212
- if (has_inline_body(words) || fields.length + options.length !== 1) return void 0;
1213
- return fields[0]?.startsWith(BODY_FILE_PREFIX) ? fields[0].slice(BODY_FILE_PREFIX.length) : options[0];
1214
- }
1215
- function has_label(command, label) {
1216
- const words = words_of2(command);
1217
- const fields = [...values_after(words, RAW_FIELDS), ...values_after(words, FILE_FIELDS)];
1218
- const options = values_after(words, LABEL_OPTIONS).flatMap((value) => value.split(","));
1219
- return fields.includes(`labels[]=${label}`) || options.includes(label);
1220
- }
1221
- var issue_filing_args = { body_file, has_label, words_of: words_of2 };
1222
-
1223
- // scripts/rules/issue-bug-label.ts
1224
- var LINT_NAMES = /* @__PURE__ */ new Set(["issue:lint"]);
1225
- var LINT_PATH = /\b(?:issue:lint|iln)\s+(?:'([^']+)'|"([^"]+)"|(\S+))/u;
1226
- function lint_argument(segment) {
1227
- const [, single_quoted, double_quoted, bare] = LINT_PATH.exec(segment) ?? [];
1228
- return single_quoted ?? double_quoted ?? bare;
1229
- }
1230
- function latest_lint_path(tail) {
1231
- const segments = tail_commands.prior_bash_commands(tail).flatMap((command) => shell_segments.segments_of(command));
1232
- const lint = segments.findLast((segment) => shell_segments.is_josh_command(segment, LINT_NAMES));
1233
- return lint === void 0 ? void 0 : lint_argument(lint);
1234
- }
1235
- function linted_body(body_path) {
1236
- try {
1237
- return readFileSync3(body_path, "utf8");
1238
- } catch {
1239
- return void 0;
1240
- }
1241
- }
1242
- function matches_linted_file(command, lint_path) {
1243
- const filing_path = issue_filing_args.body_file(command);
1244
- return filing_path !== void 0 && path4.resolve(filing_path) === path4.resolve(lint_path);
1245
- }
1246
- function needs_classification_labels(command, tail) {
1247
- const lint_path = latest_lint_path(tail);
1248
- if (lint_path === void 0) return false;
1249
- const body = linted_body(lint_path);
1250
- if (body === void 0) return false;
1251
- if (!matches_linted_file(command, lint_path) || issue_classification.problems(body).length > 0) {
1252
- return true;
1253
- }
1254
- const expected = issue_classification.required_labels(body);
1255
- return issue_classification.CLASSIFICATION_LABELS.some(
1256
- (label) => expected.includes(label) !== issue_filing_args.has_label(command, label)
1257
- );
1258
- }
1259
- function decide3(call, run2) {
1260
- const command = time_shell.bash_command(call.input);
1261
- const tail = time_density_hook.read_tail(run2.transcript);
1262
- return needs_classification_labels(command, tail);
1263
- }
1264
- var ISSUE_CLASSIFICATION_REASON = "\u26D4 issue classification mismatch: use the exact body file checked by `pnpm josh issue:lint` in this creation call (`-F body=@<path>` or `gh issue create --body-file <path>`) and apply exactly the `labels:` it reports (`bug`, `enhancement`, `breaking-change`). See `prompts/collaboration-workflow/issue-template.md`.";
1265
- var ROW4 = {
1266
- id: "issue-bug-label",
1267
- is_trigger: bash_triggers.on_bash_command(bash_triggers.is_issue_filing),
1268
- reason: ISSUE_CLASSIFICATION_REASON,
1269
- decide: decide3
1270
- };
1271
- var issue_bug_label_rule = { ROW: ROW4, latest_lint_path, needs_classification_labels };
1272
-
1273
1090
  // scripts/rules/issue-fold.ts
1274
1091
  var FOLD_NAMES = /* @__PURE__ */ new Set(["issue:fold"]);
1275
1092
  function runs_the_fold(command) {
@@ -1279,7 +1096,7 @@ function already_folded(tail, _call) {
1279
1096
  if (filing_cap.prior_filing_count(tail) === 0) return true;
1280
1097
  return tail_commands.prior_bash_commands(tail).some((command) => runs_the_fold(command));
1281
1098
  }
1282
- var ISSUE_FOLD_REASON = '\u26D4 issue fold: this run has filed an Issue before and is filing another with no `pnpm josh issue:fold` between them. Several findings from one session fold into one Issue by default \u2014 the filing-time counterpart to the split assessment, whose two questions it reuses: are the findings separable, and does the whole clearly exceed one verification gate (the size half is `split:assess`, called not recomputed). Reissue after folding: `pnpm josh issue:fold "<title>" "<title>" \u2026` \u2014 `fold` means file one Issue covering them, `separate` means file them apart (separable findings whose combined size clears the split guide), and the default is `fold`. Add `--not-separable` when they are really one deliverable. The procedure is `.claude/skills/workflow-commands/split-assessment.md` \u2192 "The question". It fires once per run and cannot repeat on the call in hand.';
1099
+ var ISSUE_FOLD_REASON = '\u26D4 issue fold: this run has filed an Issue before and is filing another with no `pnpm josh issue:fold` between them. Several findings from one session fold into one Issue by default \u2014 the filing-time counterpart to the split assessment, whose two questions it reuses: are the findings separable, and does the whole clearly exceed one verification gate (the size half is `split:assess`, called not recomputed). Reissue after folding: `pnpm josh issue:fold "<title>" "<title>" \u2026` \u2014 `fold` means file one Issue covering them, `separate` means file them apart (separable findings whose combined size clears the split guide), and the default is `fold`. Add `--not-separable` when they are really one deliverable. The procedure is `.claude/skills/workflow-commands/split-assessment.md` \u2192 "The question". Every later filing is refused until the fold is on the transcript.';
1283
1100
  var ROW5 = {
1284
1101
  id: "issue-fold",
1285
1102
  is_trigger: bash_triggers.on_bash_command(bash_triggers.is_issue_filing),
@@ -1289,24 +1106,6 @@ var ROW5 = {
1289
1106
  };
1290
1107
  var issue_fold = { ISSUE_FOLD_REASON, ROW: ROW5, already_folded, runs_the_fold };
1291
1108
 
1292
- // scripts/rules/issue-scout.ts
1293
- var SCOUT_NAMES = /* @__PURE__ */ new Set(["issue:scout"]);
1294
- function runs_the_scout(command) {
1295
- return shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, SCOUT_NAMES));
1296
- }
1297
- function already_scouted(tail, _call) {
1298
- return tail_commands.prior_bash_commands(tail).some((command) => runs_the_scout(command));
1299
- }
1300
- var ISSUE_SCOUT_REASON = '\u26D4 issue scout: this files an Issue with no `pnpm josh issue:scout` earlier in the run. A duplicate is likeliest for the work that just finished, which is exactly where self-restraint is weakest. Reissue after scouting: `pnpm josh issue:scout "<title>"` (add `--body "<one-line summary, citing #N where the work follows one>"` so the epic half can answer), then read its `Duplicates:` \u2014 an **open** candidate covering the same work stops the run rather than filing a second Issue, and a `(closed)` one means the work is already done \u2014 before reissuing this filing. The procedure is `.claude/skills/workflow-commands/SKILL.md` \u2192 \xA72e. It fires once per run and cannot repeat on the call in hand.';
1301
- var ROW6 = {
1302
- id: "issue-scout",
1303
- is_trigger: bash_triggers.on_bash_command(bash_triggers.is_issue_filing),
1304
- reason: ISSUE_SCOUT_REASON,
1305
- already_satisfied: already_scouted,
1306
- keeps: bash_triggers.on_bash_command(runs_the_scout)
1307
- };
1308
- var issue_scout = { ISSUE_SCOUT_REASON, ROW: ROW6, already_scouted, runs_the_scout };
1309
-
1310
1109
  // scripts/rules/josh-git-bare.ts
1311
1110
  var GIT_COMMAND2 = /* @__PURE__ */ new Set(["git"]);
1312
1111
  var CONFIRM_FLAGS = /* @__PURE__ */ new Set(["-y", "--yes"]);
@@ -1321,7 +1120,7 @@ function is_bare_josh_git(command) {
1321
1120
  return shell_segments.segments_of(command).some((segment) => is_bare_segment(segment));
1322
1121
  }
1323
1122
  var JOSH_GIT_BARE_REASON = '\u26D4 bare `pnpm josh git`: this prompts to confirm the staging, and with no TTY the prompt falls to its cancel branch \u2014 `Checking unstaged files... Found` then `Operation cancelled.` \u2014 so the call throws away the time it takes to fail (about 31 seconds in one measured lane) and you reissue it with `-y` anyway (joshuafolkken/kit#2297). Run it unattended from the start: `pnpm josh git -y "<title> #<N>"`. The recovery form that opens the PR for a push that already landed is `pnpm josh git -y --skip-commit --skip-push`. This rule fires on every occurrence, not once per run.';
1324
- var ROW7 = {
1123
+ var ROW6 = {
1325
1124
  id: "josh-git-bare",
1326
1125
  is_trigger: bash_triggers.on_bash_command(is_bare_josh_git),
1327
1126
  reason: JOSH_GIT_BARE_REASON,
@@ -1329,7 +1128,7 @@ var ROW7 = {
1329
1128
  };
1330
1129
  var josh_git_bare = {
1331
1130
  JOSH_GIT_BARE_REASON,
1332
- ROW: ROW7,
1131
+ ROW: ROW6,
1333
1132
  is_bare_josh_git
1334
1133
  };
1335
1134
 
@@ -1343,7 +1142,7 @@ function is_carry_conflict(command, is_lane_child = lane_child_marker.is_child_o
1343
1142
  return is_lane_child;
1344
1143
  }
1345
1144
  var LANE_CARRY_CONFLICT_REASON = '\u26D4 lane child carry conflict: this session is a dispatched lane child, and `pnpm josh run:merge` / `pnpm josh run:carry` are the parent backlogrun budget commands \u2014 a child must never run them. The live carry record here belongs to the parent that launched this child, not to a competing run: the parent holds the one run budget for the repository, and every lane shares the common git directory it is keyed on, so a live owner a lane child reads is always its parent (joshuafolkken/kit#2267, measured 2026-09-21 in backlogrun #2252 \u2014 a child read run:merge `busy` as a competitor and parked #2258 unimplemented). Do not run this command, and do not treat it as a conflict: no `needs-decision`, no `confirmation` Telegram, no question to a person \u2014 a dispatched child cannot ask, and there is nothing to decide. Continue implementing this Issue; the parent owns the budget and counts the outcome at the return. The procedure is `.claude/skills/workflow-commands/pre-gate-cut.md` and `backlogrun-progress.md` \u2192 "The hand-off". This rule fires on every occurrence, not once per run.';
1346
- var ROW8 = {
1145
+ var ROW7 = {
1347
1146
  id: "lane-carry-conflict",
1348
1147
  is_trigger: bash_triggers.on_bash_command(is_carry_conflict),
1349
1148
  reason: LANE_CARRY_CONFLICT_REASON,
@@ -1351,7 +1150,7 @@ var ROW8 = {
1351
1150
  };
1352
1151
  var lane_carry_conflict = {
1353
1152
  LANE_CARRY_CONFLICT_REASON,
1354
- ROW: ROW8,
1153
+ ROW: ROW7,
1355
1154
  invokes_budget_command,
1356
1155
  is_carry_conflict
1357
1156
  };
@@ -1404,13 +1203,13 @@ function is_interactive_ask(call, is_lane_child = lane_child_marker.is_child_of(
1404
1203
  return is_lane_child;
1405
1204
  }
1406
1205
  var LANE_INTERACTIVE_ASK_REASON = '\u26D4 lane child interactive ask: this session is a dispatched lane child and cannot ask a person, so this call is refused. Left to run it would end the turn with nothing on the Issue \u2014 the parent then finds an OPEN Issue with `in-progress` and no question (joshuafolkken/kit#2201; joshuafolkken/kit#2034 placed the same rule one tool-call too late). Do not ask: park the decision instead. Apply `needs-decision` and post a comment carrying the question, the options and whether work was stashed \u2014 `gh api repos/{owner}/{repo}/issues/<N>/labels -f \'labels[]=needs-decision\'` then `pnpm josh issue:comment <N> --body-file <path>` \u2014 then send the `confirmation` Telegram and stop. The procedure is `.claude/skills/workflow-commands/pre-gate-cut.md` \u2192 "A lane child records its park before it stops", and the park itself is `backlogrun-park.md` \u2192 "park and continue". This rule fires on every occurrence, not once per run.';
1407
- var ROW9 = {
1206
+ var ROW8 = {
1408
1207
  id: "lane-interactive-ask",
1409
1208
  is_trigger: is_interactive_ask,
1410
1209
  reason: LANE_INTERACTIVE_ASK_REASON,
1411
1210
  decide: () => true
1412
1211
  };
1413
- var lane_interactive_ask = { LANE_INTERACTIVE_ASK_REASON, ROW: ROW9, is_interactive_ask };
1212
+ var lane_interactive_ask = { LANE_INTERACTIVE_ASK_REASON, ROW: ROW8, is_interactive_ask };
1414
1213
 
1415
1214
  // scripts/rules/lane-switch-main.ts
1416
1215
  var SWITCH_SUBCOMMAND = "switch";
@@ -1441,7 +1240,7 @@ function is_switch_away_from_lane(command, is_lane_child = lane_child_marker.is_
1441
1240
  return switch_targets(command).some((target) => target !== own_branch);
1442
1241
  }
1443
1242
  var LANE_SWITCH_MAIN_REASON = "\u26D4 lane child on `git switch main`: this session is a dispatched lane child, and switching to the default branch here always fails \u2014 a lane is a linked work tree, the primary checkout holds the default branch, and git allows one branch in one work tree at a time (`fatal: main is already used by worktree at <the primary checkout>`, measured 11 times in the 2026-09-21 backlogrun). Do not switch: the lane is already on its own issue branch, branched from a fresh default before it opened, and the latest default is brought in during the gate by `pnpm josh main:merge`. So skip this step and keep implementing on the current branch \u2014 no `git switch`, no `git pull`. `pnpm josh main:sync` (`josh ms`) is refused in a lane for the same reason, and a lane is finished with `pnpm josh lane:close <issue-number>`. The parent runs `git switch main && git pull` in the primary checkout before each lane opens, never the child \u2014 `backlogrun-lanes.md` and `backlogrun-child.md` are the single sources. This rule fires on every occurrence, not once per run.";
1444
- var ROW10 = {
1243
+ var ROW9 = {
1445
1244
  id: "lane-switch-main",
1446
1245
  is_trigger: bash_triggers.on_bash_command(is_switch_away_from_lane),
1447
1246
  reason: LANE_SWITCH_MAIN_REASON,
@@ -1449,7 +1248,7 @@ var ROW10 = {
1449
1248
  };
1450
1249
  var lane_switch_main = {
1451
1250
  LANE_SWITCH_MAIN_REASON,
1452
- ROW: ROW10,
1251
+ ROW: ROW9,
1453
1252
  is_switch_away_from_lane,
1454
1253
  switch_target,
1455
1254
  switch_targets
@@ -1752,20 +1551,13 @@ var FIRING = /* @__PURE__ */ new Map([
1752
1551
  describes: "a package-add command (`pnpm add \u2026`, `npm install <pkg>`)",
1753
1552
  governs: is_package_add
1754
1553
  }
1755
- ],
1756
- [
1757
- "issue:lint",
1758
- {
1759
- describes: "an Issue-filing call (`gh api \u2026/issues`, `gh issue create`)",
1760
- governs: bash_triggers.is_issue_filing
1761
- }
1762
1554
  ]
1763
1555
  ]);
1764
1556
  var PHASE = "the act it governs is a phase of a run, not a single shell call a trigger can anchor on";
1765
1557
  var READ = "it answers a status question and governs no action of its own to gate before";
1766
1558
  var SELF = "the command is itself the governed act; there is no earlier call to gate before it";
1767
1559
  var LATEST_SCOPE_REASON = "the run it governs (`pnpm josh latest`) is already gated at its point of use by the latest-gate; a firing here would double-gate it";
1768
- var EPIC_BUNDLE_REASON = "it runs after a filing to place it, and that filing is already gated by `issue-scout`; it has no earlier call of its own";
1560
+ var FILING_STEP_REASON = "`pnpm josh issue:file` runs it as one of its filing steps, and a direct filing is refused by the `direct-filing` delivered rule; there is no separate call left to gate";
1769
1561
  var RELEASE_SCOPE_REASON = "it reads the release owed *after* `pnpm josh followup` has merged (`git_followup_pending`), so it trails the merge rather than gating it; a firing on `followup` would gate a check that must run once the merge is done, reading a pre-merge state that has not yet arrived";
1770
1562
  var NOT_NAMED = /* @__PURE__ */ new Map([
1771
1563
  ["release:scope", RELEASE_SCOPE_REASON],
@@ -1777,8 +1569,9 @@ var NOT_NAMED = /* @__PURE__ */ new Map([
1777
1569
  ["latest:scope", LATEST_SCOPE_REASON],
1778
1570
  ["run:hold", SELF],
1779
1571
  ["cost:cut", READ],
1780
- ["epic:bundle", EPIC_BUNDLE_REASON],
1781
- ["issue:scout", "the filing it governs is already gated by the `issue-scout` delivered rule"],
1572
+ ["epic:bundle", FILING_STEP_REASON],
1573
+ ["issue:scout", FILING_STEP_REASON],
1574
+ ["issue:lint", FILING_STEP_REASON],
1782
1575
  [
1783
1576
  "issue:fold",
1784
1577
  "the second filing it governs is already gated by the `issue-fold` delivered rule"
@@ -1834,7 +1627,7 @@ function command_line(oracle) {
1834
1627
  return oracle.args ? `pnpm josh ${command} ${oracle.args}` : `pnpm josh ${command}`;
1835
1628
  }
1836
1629
  function reason_for(oracle, firing_point) {
1837
- return `\u26D4 decision oracle not consulted: this is ${firing_point.describes}, but the run has not run \`${command_line(oracle)}\` first \u2014 the oracle that decides ${oracle.decision.toLowerCase()}. It answers ${oracle.vocabulary.join(" | ")}; single source ${oracle.single_source}. Reissue after running it and reading its verdict. It fires once per run and cannot repeat on the call in hand.`;
1630
+ return `\u26D4 decision oracle not consulted: this is ${firing_point.describes}, but the run has not run \`${command_line(oracle)}\` first \u2014 the oracle that decides ${oracle.decision.toLowerCase()}. It answers ${oracle.vocabulary.join(" | ")}; single source ${oracle.single_source}. Reissue after running it and reading its verdict. Every such call is refused until that command is on the transcript.`;
1838
1631
  }
1839
1632
  function row_for(oracle, firing_point) {
1840
1633
  const names = command_names(oracle);
@@ -1917,7 +1710,7 @@ function is_poll_loop(command) {
1917
1710
  return LOOP_KEYWORD2.test(plain) && SLEEP_COMMAND.test(plain);
1918
1711
  }
1919
1712
  var POLL_LOOP_REASON = '\u26D4 output poll loop: this hand-writes a `while`/`until` wait loop over a command\'s output, a marker file or a process \u2014 with a `sleep` or, worse, without one \u2014 and the loop does not know when the command finished. A regex that never matches waits to the harness block limit; one that matches a mid-run line exits too early \u2014 either way the command exit is not what ends the wait, and each pass spends a full-context round trip (joshuafolkken/kit#2371 measured about 45 minutes, 13% of six lanes, lost to exactly this). Do not poll the output file. A command known to take minutes \u2014 `pnpm josh gate`, `pnpm josh followup`, `pnpm josh format` \u2014 is started detached from the first with `run_in_background` set, and the harness re-invokes you when it exits: its completion notification is what ends the wait, on the task exit rather than a regex or the block limit. So issue the long command in the background and end the turn \u2014 do not arm a loop; the wake-up brings you back. To glance at interim output, `Read` the output file once, never in a sleep loop. If you are waiting on CI, `pnpm josh followup` waits on it for you. The procedure is `.claude/skills/workflow-commands/background-commands.md`, "A command that can take minutes is issued in the background". **This rule fires on every occurrence, not once per run.**';
1920
- var ROW11 = {
1713
+ var ROW10 = {
1921
1714
  id: "poll-loop",
1922
1715
  is_trigger: bash_triggers.on_bash_command(is_poll_loop),
1923
1716
  reason: POLL_LOOP_REASON,
@@ -1925,7 +1718,7 @@ var ROW11 = {
1925
1718
  };
1926
1719
  var poll_loop = {
1927
1720
  POLL_LOOP_REASON,
1928
- ROW: ROW11,
1721
+ ROW: ROW10,
1929
1722
  is_poll_loop
1930
1723
  };
1931
1724
 
@@ -2045,16 +1838,16 @@ function posts_at_path_literally(command) {
2045
1838
  return RAW_FIELD_BODY_AT.test(command);
2046
1839
  }
2047
1840
  var RAW_FIELD_BODY_REASON = "\u26D4 literal @path body: this passes `body=@<path>` to `gh api` with a raw field flag (`-f` / `--raw-field`), which sends the value verbatim \u2014 the string `@<path>` is posted as the comment, not the file it points at. `-F` / `--field` is the flag that reads the file, and the two spellings differ by one character while `gh` exits 0 and returns a comment URL either way, so the break is invisible until a person reads the issue (joshuafolkken/kit#2304 lost two park comments this way). Post an issue or PR comment with `pnpm josh issue:comment <N> --body-file <path>` \u2014 the one spelling, which cannot take the wrong flag. The rule and the safe forms are in `prompts/collaboration-workflow/shell-body.md`. This rule fires on every occurrence, not once per run.";
2048
- var ROW12 = {
1841
+ var ROW11 = {
2049
1842
  id: "raw-field-body",
2050
1843
  is_trigger: bash_triggers.on_bash_command(posts_at_path_literally),
2051
1844
  reason: RAW_FIELD_BODY_REASON,
2052
1845
  decide: () => true
2053
1846
  };
2054
- var raw_field_body = { RAW_FIELD_BODY_REASON, ROW: ROW12, posts_at_path_literally };
1847
+ var raw_field_body = { RAW_FIELD_BODY_REASON, ROW: ROW11, posts_at_path_literally };
2055
1848
 
2056
1849
  // scripts/rules/rule-body-guard.ts
2057
- import { existsSync as existsSync2, readFileSync as readFileSync4 } from "node:fs";
1850
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
2058
1851
  var EDIT_TOOL = "Edit";
2059
1852
  var WRITE_TOOL = "Write";
2060
1853
  var CLAUDE_DOCUMENT = /(?:^|\/)CLAUDE\.md$/u;
@@ -2077,7 +1870,7 @@ function string_field(input, key) {
2077
1870
  return typeof value === "string" ? value : "";
2078
1871
  }
2079
1872
  function read_existing(file_path) {
2080
- return existsSync2(file_path) ? readFileSync4(file_path, "utf8") : "";
1873
+ return existsSync2(file_path) ? readFileSync3(file_path, "utf8") : "";
2081
1874
  }
2082
1875
  function texts_of(name, input, read2) {
2083
1876
  if (name === WRITE_TOOL) {
@@ -2104,7 +1897,7 @@ function writes_rule_prose(call, read2 = read_existing) {
2104
1897
  const { old_text, new_text } = texts_of(call.name, input, read2);
2105
1898
  return adds_rule_prose(old_text, new_text);
2106
1899
  }
2107
- var RULE_BODY_REASON = "\u26D4 rule written into prose: this edit adds rule text to CLAUDE.md, a `prompts/**` doc or a skill body. Before it lands, run the residency questions the addition skips. A rule whose answer is computable from mechanically readable inputs should be a decision oracle, not prose \u2014 question 0; `pnpm josh oracle:list` lists the existing oracles a new one joins (single source `scripts/rules/decision-oracle.ts`). A rule that decides *when* or *in what order* to act belongs in the run driver, not prose \u2014 the ordering question; `pnpm josh run:step` is the state machine a sequencing rule moves into. Only a rule that is neither \u2014 not computable, not ordering \u2014 stays prose, and then as a trigger-plus-pointer line rather than a second copy of its procedure. The criterion is `prompts/collaboration-workflow/residency.md` (question 0 and the ordering question); the delivery enumeration is `prompts/collaboration-workflow/rule-delivery.md`. This fires once per run \u2014 once you have confirmed the rule can be neither an oracle nor an ordering step, reissue this edit and it will go through.";
1900
+ var RULE_BODY_REASON = "\u26D4 rule written into prose: this edit adds rule text to CLAUDE.md, a `prompts/**` doc or a skill body. Before it lands, run the residency questions the addition skips. A rule whose answer is computable from mechanically readable inputs should be a decision oracle, not prose \u2014 question 0; `pnpm josh oracle:list` lists the existing oracles a new one joins (single source `scripts/rules/decision-oracle.ts`). A rule that decides *when* or *in what order* to act belongs in the run driver, not prose \u2014 the ordering question; `pnpm josh run:step` is the state machine a sequencing rule moves into. Only a rule that is neither \u2014 not computable, not ordering \u2014 stays prose, and then as a trigger-plus-pointer line rather than a second copy of its procedure. The criterion is `prompts/collaboration-workflow/residency.md` (question 0 and the ordering question); the delivery enumeration is `prompts/collaboration-workflow/rule-delivery.md`. Every such edit is refused until both commands are on the transcript \u2014 run them, confirm the rule can be neither an oracle nor an ordering step, then reissue this edit.";
2108
1901
  var PLACEMENT_ORACLES = [
2109
1902
  /* @__PURE__ */ new Set(["oracle:list"]),
2110
1903
  /* @__PURE__ */ new Set(["run:step", "run:next"])
@@ -2123,7 +1916,7 @@ function runs_placement_oracle(command) {
2123
1916
  (names) => shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, names))
2124
1917
  );
2125
1918
  }
2126
- var ROW13 = {
1919
+ var ROW12 = {
2127
1920
  id: "rule-body",
2128
1921
  is_trigger: writes_rule_prose,
2129
1922
  reason: RULE_BODY_REASON,
@@ -2132,7 +1925,7 @@ var ROW13 = {
2132
1925
  };
2133
1926
  var rule_body_guard = {
2134
1927
  RULE_BODY_REASON,
2135
- ROW: ROW13,
1928
+ ROW: ROW12,
2136
1929
  adds_rule_prose,
2137
1930
  answered_placement,
2138
1931
  is_rule_document,
@@ -2216,8 +2009,8 @@ var shell_body_trigger = {
2216
2009
  import { execFileSync } from "node:child_process";
2217
2010
 
2218
2011
  // scripts/gate/project-checks.ts
2219
- import { existsSync as existsSync3, globSync as globSync2, readFileSync as readFileSync5 } from "node:fs";
2220
- import path5 from "node:path";
2012
+ import { existsSync as existsSync3, globSync as globSync2, readFileSync as readFileSync4 } from "node:fs";
2013
+ import path4 from "node:path";
2221
2014
 
2222
2015
  // scripts/test/skip-marker.ts
2223
2016
  var SKIP_MARKER = "\u2014 skipping";
@@ -2261,8 +2054,8 @@ var DEPENDENCY_FIELDS = ["dependencies", "devDependencies"];
2261
2054
  var object_schema = z5.record(z5.string(), z5.unknown());
2262
2055
  function project_root(directory) {
2263
2056
  let current = directory;
2264
- while (!existsSync3(path5.join(current, PACKAGE_JSON))) {
2265
- const parent = path5.dirname(current);
2057
+ while (!existsSync3(path4.join(current, PACKAGE_JSON))) {
2058
+ const parent = path4.dirname(current);
2266
2059
  if (parent === current) return directory;
2267
2060
  current = parent;
2268
2061
  }
@@ -2275,7 +2068,7 @@ function has_files(directory, pattern) {
2275
2068
  return globSync2(pattern, { cwd: project_root(directory), exclude: is_ignored }).length > 0;
2276
2069
  }
2277
2070
  function has_config(directory, names) {
2278
- return names.some((name) => existsSync3(path5.join(project_root(directory), name)));
2071
+ return names.some((name) => existsSync3(path4.join(project_root(directory), name)));
2279
2072
  }
2280
2073
  function has_bin(directory, bin_name) {
2281
2074
  return find_local_bin_upwards(directory, bin_name) !== void 0;
@@ -2291,8 +2084,8 @@ function is_static_manifest(manifest) {
2291
2084
  }
2292
2085
  function read_manifest(directory) {
2293
2086
  try {
2294
- const manifest_path = path5.join(project_root(directory), PACKAGE_JSON);
2295
- return JSON.parse(readFileSync5(manifest_path, "utf8"));
2087
+ const manifest_path = path4.join(project_root(directory), PACKAGE_JSON);
2088
+ return JSON.parse(readFileSync4(manifest_path, "utf8"));
2296
2089
  } catch {
2297
2090
  return void 0;
2298
2091
  }
@@ -2357,32 +2150,32 @@ var EXEMPT_PREFIXES = [".idea/", ".vscode/", "prompts/"];
2357
2150
  var EXEMPT_SUFFIXES = [".md"];
2358
2151
  var STATIC_VISUAL_SUFFIXES = [".html", ".css"];
2359
2152
  function normalize(paths) {
2360
- return paths.map((path7) => path7.trim()).filter((path7) => path7 !== "");
2153
+ return paths.map((path6) => path6.trim()).filter((path6) => path6 !== "");
2361
2154
  }
2362
- function is_test_file(path7) {
2363
- return TEST_SUFFIXES.some((suffix) => path7.endsWith(suffix));
2155
+ function is_test_file(path6) {
2156
+ return TEST_SUFFIXES.some((suffix) => path6.endsWith(suffix));
2364
2157
  }
2365
- function is_static_visual(path7, is_static2) {
2366
- return is_static2 && STATIC_VISUAL_SUFFIXES.some((suffix) => path7.endsWith(suffix));
2158
+ function is_static_visual(path6, is_static2) {
2159
+ return is_static2 && STATIC_VISUAL_SUFFIXES.some((suffix) => path6.endsWith(suffix));
2367
2160
  }
2368
- function is_exempt(path7, is_static2 = false) {
2369
- if (EXEMPT_PATHS.includes(path7) || is_static_visual(path7, is_static2)) return true;
2370
- return EXEMPT_SUFFIXES.some((suffix) => path7.endsWith(suffix)) || EXEMPT_PREFIXES.some((prefix) => path7.startsWith(prefix));
2161
+ function is_exempt(path6, is_static2 = false) {
2162
+ if (EXEMPT_PATHS.includes(path6) || is_static_visual(path6, is_static2)) return true;
2163
+ return EXEMPT_SUFFIXES.some((suffix) => path6.endsWith(suffix)) || EXEMPT_PREFIXES.some((prefix) => path6.startsWith(prefix));
2371
2164
  }
2372
- function is_runtime(path7, is_static2 = false) {
2373
- return !is_test_file(path7) && !is_exempt(path7, is_static2);
2165
+ function is_runtime(path6, is_static2 = false) {
2166
+ return !is_test_file(path6) && !is_exempt(path6, is_static2);
2374
2167
  }
2375
2168
  function verdict_for(paths, is_static2 = false) {
2376
2169
  const changed = normalize(paths);
2377
- if (changed.some((path7) => is_test_file(path7))) return "satisfied";
2378
- if (changed.some((path7) => is_runtime(path7, is_static2))) return "required";
2170
+ if (changed.some((path6) => is_test_file(path6))) return "satisfied";
2171
+ if (changed.some((path6) => is_runtime(path6, is_static2))) return "required";
2379
2172
  return "exempt";
2380
2173
  }
2381
2174
  function runtime_files(paths, is_static2 = false) {
2382
- return normalize(paths).filter((path7) => is_runtime(path7, is_static2));
2175
+ return normalize(paths).filter((path6) => is_runtime(path6, is_static2));
2383
2176
  }
2384
2177
  function exempt_files(paths, is_static2 = false) {
2385
- return normalize(paths).filter((path7) => is_exempt(path7, is_static2));
2178
+ return normalize(paths).filter((path6) => is_exempt(path6, is_static2));
2386
2179
  }
2387
2180
  var test_declared_logic = {
2388
2181
  EXEMPT_PATHS,
@@ -2447,8 +2240,8 @@ function is_untested_commit(call) {
2447
2240
  return test_declared_changed.current_verdict() === "required";
2448
2241
  }
2449
2242
  var REASON = '\u26D4 test declared: this commits a runtime code change with no test beside it \u2014 `pnpm josh test:declared` reads the working tree as `required` (a changed file that is neither a test \u2014 `*.test.ts` / `*.e2e.ts` \u2014 nor a mechanically-exempt non-runtime path). `CLAUDE.md` \u2192 Code Change Rules requires a test for ALL code changes: a bug fix gets a regression test, a refactor gets tests pinning behavior first, logic gets a unit test, an observable UI/timing change gets an E2E. Add the declared test and reissue this commit. **If the change genuinely touches no executable runtime path** it is the pre-approved non-runtime exception \u2014 declare it in your Step 0 work summary with the reason it is exempt (non-executable config, a cosmetic asset swap) and reissue; the exception is a presentation, not a stop. Run `pnpm josh test:declared` to see the files it names. The procedure is `prompts/collaboration-workflow/rule-delivery.md` \u2192 "\u914D\u9001\u3055\u308C\u3066\u3044\u308B\u898F\u5247". This fires once per run, so reissuing will not repeat on the call in hand.';
2450
- var ROW14 = { id: "test-declared", is_trigger: is_untested_commit, reason: REASON };
2451
- var test_declared_commit = { REASON, ROW: ROW14, is_untested_commit };
2243
+ var ROW13 = { id: "test-declared", is_trigger: is_untested_commit, reason: REASON };
2244
+ var test_declared_commit = { REASON, ROW: ROW13, is_untested_commit };
2452
2245
 
2453
2246
  // scripts/rules/third-party-write.ts
2454
2247
  function is_third_party_write_segment(segment, session_owner) {
@@ -2464,14 +2257,14 @@ function writes_third_party(command, resolve_owner = repo_party.current_owner) {
2464
2257
  return targets.some((segment) => is_third_party_write_segment(segment, session_owner));
2465
2258
  }
2466
2259
  var THIRD_PARTY_WRITE_REASON = '\u26D4 third-party write: this `gh api` call writes to a repository whose owner is not this session \u2014 a tracker we do not own is Tier C, and every write there (Issue, comment, PR, Discussion, review) needs explicit current-turn user instruction; typing the target, or holding a correct diagnosis, is not that instruction (`CLAUDE.md` \u2192 "Third-party repositories are Tier C", `prompts/collaboration-workflow/upstream-interrupt.md`). Confirm the party with `pnpm josh repo:party <owner/repo>` \u2014 owner equality, computed not judged. A read (`-X GET`, or no field flag) passes untouched; only a write (`-f`/`-F`/`--field`/`--raw-field`/`--input`, or `-X POST|PATCH|PUT|DELETE`) is refused. Record the finding on our own Issue and draft the artifact for a person to post \u2014 do not publish it yourself. **This rule fires on every occurrence, not once per run.**';
2467
- var ROW15 = {
2260
+ var ROW14 = {
2468
2261
  id: "third-party-write",
2469
2262
  is_trigger: bash_triggers.on_bash_command((command) => writes_third_party(command)),
2470
2263
  reason: THIRD_PARTY_WRITE_REASON,
2471
2264
  decide: () => true
2472
2265
  };
2473
2266
  var third_party_write = {
2474
- ROW: ROW15,
2267
+ ROW: ROW14,
2475
2268
  THIRD_PARTY_WRITE_REASON,
2476
2269
  is_third_party_write_segment,
2477
2270
  writes_third_party
@@ -2525,14 +2318,14 @@ function is_unauthorized_worktree_change(command) {
2525
2318
  return shell_segments.segments_of(command).some((segment) => is_unauthorized_segment(segment));
2526
2319
  }
2527
2320
  var WORKTREE_MUTATION_REASON = '\u26D4 unauthorized working-tree change: `git checkout -- <path>`, `git restore <path>` and an unauthorized `git stash` mutate the working tree or the repository-wide stash every work tree shares, and none is on the `.claude/settings.json` deny list (`operating-rules.md`). Do not run them on your own judgement. A commit goes through `pnpm josh git`; a stash pop goes through `pnpm josh stash:pop "<message>"`, never a positional `git stash pop` a shared stack lets another lane divert. A stash push is authorized only inside a documented flow and only with a findable message \u2014 `git stash push -u -m "<message>"` \u2014 which this row allows; a bare `git stash`, a message-less push, or `pop` / `apply` / `drop` is refused. **This rule fires on every occurrence, not once per run.**';
2528
- var ROW16 = {
2321
+ var ROW15 = {
2529
2322
  id: "worktree-mutation",
2530
2323
  is_trigger: bash_triggers.on_bash_command(is_unauthorized_worktree_change),
2531
2324
  reason: WORKTREE_MUTATION_REASON,
2532
2325
  decide: () => true
2533
2326
  };
2534
2327
  var worktree_guard = {
2535
- ROW: ROW16,
2328
+ ROW: ROW15,
2536
2329
  WORKTREE_MUTATION_REASON,
2537
2330
  is_unauthorized_stash,
2538
2331
  is_unauthorized_worktree_change,
@@ -2569,7 +2362,7 @@ function is_body_only_issue_read(command) {
2569
2362
  if (segments.some((segment) => fetches_issue_comments(segment))) return false;
2570
2363
  return segments.some((segment) => is_body_read_segment(segment));
2571
2364
  }
2572
- var ISSUE_COMMENTS_REASON = "\u26D4 an Issue's comments are part of the Issue: read them before implementing, not only the body. A decision recorded after the body was written lives only in a comment \u2014 a corrected diagnosis (joshuafolkken/kit#1537), a changed default and an added acceptance criterion (joshuafolkken/kit#1520), a scope handed to another Issue (joshuafolkken/kit#1304) \u2014 and nothing in the body says it was superseded, so a body-only reader builds the wrong thing and sees no contradiction. Reissue this read with the comments included: `gh api repos/{owner}/{repo}/issues/<N>/comments --jq '.[] | {user: .user.login, created_at, body}'` beside the body read, or `gh issue view <N> --comments` where GraphQL is reachable. Then the later text is the agreement in force \u2014 a comment supersedes the body it contradicts \u2014 except for two answers that are not the run's to make: work a comment reassigns to another Issue is out of scope and is not implemented, and a comment saying the Issue no longer has a reason to exist stops the run with a `confirmation` Telegram. The procedure is `.claude/skills/workflow-commands/SKILL.md` \u2192 \"An Issue's comments are part of the Issue\". It fires once per run and cannot repeat on the call in hand.";
2365
+ var ISSUE_COMMENTS_REASON = "\u26D4 an Issue's comments are part of the Issue: read them before implementing, not only the body. A decision recorded after the body was written lives only in a comment \u2014 a corrected diagnosis (joshuafolkken/kit#1537), a changed default and an added acceptance criterion (joshuafolkken/kit#1520), a scope handed to another Issue (joshuafolkken/kit#1304) \u2014 and nothing in the body says it was superseded, so a body-only reader builds the wrong thing and sees no contradiction. Reissue this read with the comments included: `gh api repos/{owner}/{repo}/issues/<N>/comments --jq '.[] | {user: .user.login, created_at, body}'` beside the body read, or `gh issue view <N> --comments` where GraphQL is reachable. Then the later text is the agreement in force \u2014 a comment supersedes the body it contradicts \u2014 except for two answers that are not the run's to make: work a comment reassigns to another Issue is out of scope and is not implemented, and a comment saying the Issue no longer has a reason to exist stops the run with a `confirmation` Telegram. The procedure is `.claude/skills/workflow-commands/SKILL.md` \u2192 \"An Issue's comments are part of the Issue\". Every body-only read is refused until the comments are read.";
2573
2366
  var { SHELL_BODY_REASON: SHELL_BODY_REASON2, carries_a_body: carries_a_body2, is_shell_evaluated_body: is_shell_evaluated_body2, keeps_body_safe: keeps_body_safe2 } = shell_body_trigger;
2574
2367
  var ISSUE_LISTING = /^gh\s+(?:-{1,2}[\w-]+(?:[= ]\S+)?\s+)*issue\s+list\b/u;
2575
2368
  var ISSUES_QUERY = /repos\/[^\s'"]*\/issues\?[^\s'"]*state=open/u;
@@ -2593,26 +2386,24 @@ var DELIVERED_RULES = [
2593
2386
  // before any of those speak; on a first-party filing it is silent (the owners match), so `wip-cap`
2594
2387
  // still speaks first, exactly as before. It fires on every occurrence (`git-force.ts`).
2595
2388
  third_party_write.ROW,
2389
+ // **A hand-built filing is refused on every occurrence** (joshuafolkken/kit#2808) and pointed at
2390
+ // `josh issue:file`, which runs the scout, the lint, the labels, the `## Origin` check and
2391
+ // `epic:bundle` itself. It claims the direct call only, so the rows below — which trigger on the
2392
+ // `issue:file` call — never overlap it.
2393
+ direct_filing.ROW,
2596
2394
  {
2597
2395
  id: "wip-cap",
2598
2396
  is_trigger: on_bash_command(is_issue_filing),
2599
2397
  reason: WIP_CAP_REASON,
2600
2398
  keeps: on_bash_command(counts_open_issues)
2601
2399
  },
2602
- // **Three rows share the filing trigger** (joshuafolkken/kit#2119), listed after `wip-cap` so the
2603
- // backlog count still speaks first: `issue-scout` refuses a filing the run has not scouted (once per
2604
- // run, stood down once the scout is on the tail), and `filing-cap` refuses every filing past the
2605
- // per-run ceiling. Each is a real instance of the admissible overlap the comment on `delivery`
2606
- // describes — the losing rule's delivery is still correct one reissue later — so a scout-less,
2607
- // over-cap filing is delivered `wip-cap`, then `issue-scout`, then `filing-cap` across its reissues.
2608
- issue_scout.ROW,
2400
+ // **Two more rows share the filing trigger** (joshuafolkken/kit#2119, joshuafolkken/kit#2213), listed
2401
+ // after `wip-cap` so the backlog count still speaks first: `filing-cap` refuses every filing past
2402
+ // the per-run ceiling, and `issue-fold` refuses a *second* filing the run has not folded. Each is a
2403
+ // real instance of the admissible overlap the comment on `delivery` describes — the losing rule's
2404
+ // delivery is still correct one reissue later — so an unfolded, over-cap second filing is delivered
2405
+ // `wip-cap`, then `filing-cap`, then `issue-fold` across its reissues.
2609
2406
  filing_cap.ROW,
2610
- // **`issue-fold` shares the same filing trigger** (joshuafolkken/kit#2213), listed after
2611
- // `filing-cap`: it refuses a *second* filing the run has not folded, once per run, stood down off
2612
- // the tail when the run has already folded or has filed nothing earlier. It is another instance of
2613
- // the admissible overlap — the losing rule's delivery is still correct one reissue later — so a
2614
- // scout-less, unfolded, over-cap second filing is delivered `wip-cap`, then `issue-scout`, then
2615
- // `filing-cap`, then `issue-fold` across its reissues.
2616
2407
  issue_fold.ROW,
2617
2408
  {
2618
2409
  id: "issue-comments",
@@ -2839,13 +2630,9 @@ var DELIVERED_RULES = [
2839
2630
  rule_body_guard.ROW,
2840
2631
  // **The generic oracle-consulted rows, one per oracle that declared a firing point**
2841
2632
  // (joshuafolkken/kit#2324). Each refuses the action the oracle governs when the run has not run that
2842
- // oracle's command first, stood down once it has — `issue-scout`'s shape read from the registry
2843
- // rather than hand-written. Listed last so an oracle whose firing point overlaps a filing
2844
- // (`issue:lint`) delivers on the reissue, after `wip-cap` / `issue-scout` / `filing-cap` /
2845
- // `issue-fold` have each had their turn. The non-overlapping firing point (`pkg:scout` on a package
2846
- // add) claims a command no row above matches.
2847
- ...oracle_consulted.ROWS,
2848
- issue_bug_label_rule.ROW
2633
+ // oracle's command first, stood down once it has. Listed last; the one firing point left
2634
+ // (`pkg:scout` on a package add) claims a command no row above matches.
2635
+ ...oracle_consulted.ROWS
2849
2636
  ];
2850
2637
  var ALONE_IN_TURN = 1;
2851
2638
  function batches_the_turn(call, turn) {
@@ -2879,12 +2666,12 @@ function is_first_delivery(tail, call, delivered_at_ms, run2) {
2879
2666
  return delivered_at_ms === hook_decision.NEVER_MS && !will_batch_guard_refuse(tail, call, run2);
2880
2667
  }
2881
2668
  function delivery_decision(rule) {
2882
- const { decide: decide6, already_satisfied } = rule;
2883
- if (decide6 === void 0 && already_satisfied === void 0) return is_first_delivery;
2669
+ const { decide: decide5, already_satisfied } = rule;
2670
+ if (decide5 === void 0 && already_satisfied === void 0) return is_first_delivery;
2884
2671
  return function should_block(tail, call, delivered_at_ms, run2) {
2885
2672
  if (already_satisfied?.(tail, call) === true) return false;
2886
- if (decide6 === void 0) return is_first_delivery(tail, call, delivered_at_ms, run2);
2887
- return decide6(call, run2, !will_batch_guard_refuse(tail, call, run2), delivered_at_ms);
2673
+ if (decide5 === void 0) return true;
2674
+ return decide5(call, run2, !will_batch_guard_refuse(tail, call, run2), delivered_at_ms);
2888
2675
  };
2889
2676
  }
2890
2677
  function guard_of(rule) {
@@ -2914,11 +2701,11 @@ var delivered_rules = {
2914
2701
  DELIVERED_RULES,
2915
2702
  EARLY_HEARTBEAT_REASON: early_heartbeat.EARLY_HEARTBEAT_REASON,
2916
2703
  FILE_BODY_REASON: file_body.FILE_BODY_REASON,
2704
+ DIRECT_FILING_REASON: direct_filing.DIRECT_FILING_REASON,
2917
2705
  FILING_CAP_REASON: filing_cap.FILING_CAP_REASON,
2918
2706
  GIT_FORCE_REASON: git_force.GIT_FORCE_REASON,
2919
2707
  ISSUE_COMMENTS_REASON,
2920
2708
  ISSUE_FOLD_REASON: issue_fold.ISSUE_FOLD_REASON,
2921
- ISSUE_SCOUT_REASON: issue_scout.ISSUE_SCOUT_REASON,
2922
2709
  LANE_INTERACTIVE_ASK_REASON: lane_interactive_ask.LANE_INTERACTIVE_ASK_REASON,
2923
2710
  LANE_PARK_REASON: lane_park.LANE_PARK_REASON,
2924
2711
  MEASURED_RULES,
@@ -3269,9 +3056,9 @@ async function issue_get_labels_and_body(issue_number) {
3269
3056
  return await issue_view_json(issue_number, "number,state,labels,body");
3270
3057
  }
3271
3058
  async function issue_list_comments(issue_number) {
3272
- const path7 = `${git_gh_api_path.issue_comments_api_path(issue_number)}${git_gh_api_path.FULL_PAGE_QUERY}`;
3059
+ const path6 = `${git_gh_api_path.issue_comments_api_path(issue_number)}${git_gh_api_path.FULL_PAGE_QUERY}`;
3273
3060
  try {
3274
- return await git_gh_exec.exec_gh_api({ path: path7, should_paginate: true });
3061
+ return await git_gh_exec.exec_gh_api({ path: path6, should_paginate: true });
3275
3062
  } catch {
3276
3063
  return void 0;
3277
3064
  }
@@ -3346,8 +3133,8 @@ function is_selected(rest, request) {
3346
3133
  return !git_gh_issue_rest.is_pull_request(rest) && matches_body(rest, request.body_term);
3347
3134
  }
3348
3135
  async function fetch_page(request, page) {
3349
- const path7 = listing_path(request, page);
3350
- const rows = git_gh_issue_rest.parse_rest_issues(await git_gh_exec.exec_gh_api({ path: path7 }));
3136
+ const path6 = listing_path(request, page);
3137
+ const rows = git_gh_issue_rest.parse_rest_issues(await git_gh_exec.exec_gh_api({ path: path6 }));
3351
3138
  return {
3352
3139
  selected: rows.filter((row) => is_selected(row, request)),
3353
3140
  is_full: rows.length >= PER_PAGE
@@ -3588,7 +3375,7 @@ var git_gh_issue = {
3588
3375
  };
3589
3376
 
3590
3377
  // scripts/ci/pr-classification.ts
3591
- import { readFileSync as readFileSync6 } from "node:fs";
3378
+ import { readFileSync as readFileSync5 } from "node:fs";
3592
3379
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3593
3380
  import { execaSync } from "execa";
3594
3381
  import { z as z8 } from "zod";
@@ -3655,7 +3442,7 @@ function fetch_current_labels(repository, pull_number) {
3655
3442
  return result.stdout.split("\n").filter((name) => name !== "");
3656
3443
  }
3657
3444
  function check_event(event_path, read_labels = fetch_current_labels) {
3658
- const event = EVENT.parse(JSON.parse(readFileSync6(event_path, "utf8")));
3445
+ const event = EVENT.parse(JSON.parse(readFileSync5(event_path, "utf8")));
3659
3446
  const labels = read_labels(event.repository.full_name, event.pull_request.number);
3660
3447
  return classification_error(labels, event.pull_request.user.login);
3661
3448
  }
@@ -3888,9 +3675,9 @@ async function pr_head_reference(pr_number) {
3888
3675
  async function read_comments(branch_name, to_path, to_json) {
3889
3676
  const pr_number = await resolve_pr_number(branch_name);
3890
3677
  if (pr_number === void 0) return void 0;
3891
- const path7 = `${to_path(String(pr_number))}${COMMENTS_QUERY}`;
3678
+ const path6 = `${to_path(String(pr_number))}${COMMENTS_QUERY}`;
3892
3679
  try {
3893
- return to_json(await git_gh_exec.exec_gh_api({ path: path7, should_paginate: true }));
3680
+ return to_json(await git_gh_exec.exec_gh_api({ path: path6, should_paginate: true }));
3894
3681
  } catch {
3895
3682
  return void 0;
3896
3683
  }
@@ -4038,16 +3825,16 @@ var git_gh_pr_rollup = {
4038
3825
 
4039
3826
  // scripts/git/git-gh-pr-snapshot.ts
4040
3827
  var NO_HEAD_SHA_MESSAGE = "gh api answered a pull request with no head commit";
4041
- async function read_object_pages(path7) {
3828
+ async function read_object_pages(path6) {
4042
3829
  return await git_gh_exec.exec_gh_api({
4043
- path: `${path7}${FULL_PAGE_QUERY}`,
3830
+ path: `${path6}${FULL_PAGE_QUERY}`,
4044
3831
  should_paginate: true,
4045
3832
  should_slurp: true
4046
3833
  });
4047
3834
  }
4048
- async function read_array_pages(path7) {
3835
+ async function read_array_pages(path6) {
4049
3836
  return await git_gh_exec.exec_gh_api({
4050
- path: `${path7}${FULL_PAGE_QUERY}`,
3837
+ path: `${path6}${FULL_PAGE_QUERY}`,
4051
3838
  should_paginate: true
4052
3839
  });
4053
3840
  }
@@ -4064,8 +3851,8 @@ async function read_status_check_rollup(commit_sha) {
4064
3851
  return git_gh_pr_rollup.to_status_check_rollup({ check_runs_json, status_json });
4065
3852
  }
4066
3853
  async function pr_get_review_decision(pr_number) {
4067
- const path7 = git_gh_api_path.pull_reviews_api_path(String(pr_number));
4068
- return git_gh_pr_review.to_review_decision(await read_array_pages(path7));
3854
+ const path6 = git_gh_api_path.pull_reviews_api_path(String(pr_number));
3855
+ return git_gh_pr_review.to_review_decision(await read_array_pages(path6));
4069
3856
  }
4070
3857
  async function build_checks_fields(pull) {
4071
3858
  return {
@@ -4690,9 +4477,9 @@ var epic_busy = {
4690
4477
  };
4691
4478
 
4692
4479
  // scripts/run/run-liveness.ts
4693
- import { readFileSync as readFileSync7, realpathSync, statSync } from "node:fs";
4480
+ import { readFileSync as readFileSync6, realpathSync, statSync } from "node:fs";
4694
4481
  import { homedir, tmpdir } from "node:os";
4695
- import path6 from "node:path";
4482
+ import path5 from "node:path";
4696
4483
  import { setTimeout as sleep2 } from "node:timers/promises";
4697
4484
 
4698
4485
  // scripts/agent/agent-event.ts
@@ -4984,7 +4771,7 @@ function to_advice(verdict, has_work_to_stash) {
4984
4771
  if (verdict === UNDETERMINED_VERDICT) return UNDETERMINED_ADVICE;
4985
4772
  return has_work_to_stash ? STOPPED_DIRTY_ADVICE : STOPPED_CLEAN_ADVICE;
4986
4773
  }
4987
- function decide4(traces) {
4774
+ function decide3(traces) {
4988
4775
  const verdict = to_verdict(traces);
4989
4776
  const has_work_to_stash = verdict === STOPPED_VERDICT && traces.is_tree_dirty;
4990
4777
  return {
@@ -5007,12 +4794,12 @@ var ALLOWED_ROOTS = [
5007
4794
  )
5008
4795
  ];
5009
4796
  function is_within(candidate, root) {
5010
- const relative = path6.relative(root, candidate);
5011
- return relative !== "" && !relative.startsWith("..") && !path6.isAbsolute(relative);
4797
+ const relative = path5.relative(root, candidate);
4798
+ return relative !== "" && !relative.startsWith("..") && !path5.isAbsolute(relative);
5012
4799
  }
5013
4800
  function to_safe_path(output_path) {
5014
- const normalized2 = path6.normalize(output_path);
5015
- if (!path6.isAbsolute(normalized2)) return void 0;
4801
+ const normalized2 = path5.normalize(output_path);
4802
+ if (!path5.isAbsolute(normalized2)) return void 0;
5016
4803
  return ALLOWED_ROOTS.some((root) => is_within(normalized2, root)) ? normalized2 : void 0;
5017
4804
  }
5018
4805
  function sample_output(output_path) {
@@ -5026,7 +4813,7 @@ function read_agent_state(output_path) {
5026
4813
  const safe_path = to_safe_path(output_path);
5027
4814
  if (safe_path === void 0) return void 0;
5028
4815
  try {
5029
- const state = agent_event.read(readFileSync7(safe_path, "utf8"));
4816
+ const state = agent_event.read(readFileSync6(safe_path, "utf8"));
5030
4817
  return state.provider === void 0 ? void 0 : state;
5031
4818
  } catch {
5032
4819
  return void 0;
@@ -5083,11 +4870,11 @@ async function read_traces(request) {
5083
4870
  }
5084
4871
  async function check(request) {
5085
4872
  run_issue_number.require_issue_number(request.issue);
5086
- return decide4(await read_traces(request));
4873
+ return decide3(await read_traces(request));
5087
4874
  }
5088
4875
  var run_liveness = {
5089
4876
  check,
5090
- decide: decide4,
4877
+ decide: decide3,
5091
4878
  describe_agent_state,
5092
4879
  read_child_settled,
5093
4880
  read_agent_state,
@@ -5237,7 +5024,7 @@ function to_advice2(verdict, issue, tree) {
5237
5024
  if (verdict === PARK_VERDICT) return PARK_ADVICE;
5238
5025
  return CLEAN_ADVICE;
5239
5026
  }
5240
- function decide5(tree, child, issue) {
5027
+ function decide4(tree, child, issue) {
5241
5028
  const verdict = to_verdict2(tree, child);
5242
5029
  return {
5243
5030
  advice: to_advice2(verdict, issue, tree),
@@ -5306,8 +5093,8 @@ async function read_child_state(issue) {
5306
5093
  async function check2(issue) {
5307
5094
  require_issue_number(issue);
5308
5095
  const tree = await read_tree_state();
5309
- if (needs_reclaim(tree)) return decide5(tree, NO_CHILD_WORK, issue);
5310
- return decide5(tree, await read_child_state(issue), issue);
5096
+ if (needs_reclaim(tree)) return decide4(tree, NO_CHILD_WORK, issue);
5097
+ return decide4(tree, await read_child_state(issue), issue);
5311
5098
  }
5312
5099
  var run_preflight = {
5313
5100
  CLEAN_ADVICE,
@@ -5322,7 +5109,7 @@ var run_preflight = {
5322
5109
  MERGED_PR,
5323
5110
  STASH_LABEL_PREFIX,
5324
5111
  check: check2,
5325
- decide: decide5,
5112
+ decide: decide4,
5326
5113
  read_child_state,
5327
5114
  read_pr_state: read_pr_state2,
5328
5115
  to_pr_state: to_pr_state2