@joshuafolkken/kit 1.1094.0 → 1.1136.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 (177) hide show
  1. package/.claude/agents/investigator.md +17 -0
  2. package/.claude/settings.json +1 -0
  3. package/.claude/skills/workflow-commands/SKILL.md +1 -1
  4. package/.claude/skills/workflow-commands/background-commands.md +36 -85
  5. package/.claude/skills/workflow-commands/backlogrun-progress.md +31 -34
  6. package/.claude/skills/workflow-commands/backlogrun-steps.md +2 -2
  7. package/.claude/skills/workflow-commands/delegation.md +5 -1
  8. package/.claude/skills/workflow-commands/fullrun.md +5 -4
  9. package/.claude/skills/workflow-commands/halfrun.md +5 -4
  10. package/.claude/skills/workflow-commands/latest-gate.md +7 -15
  11. package/.claude/skills/workflow-commands/pre-gate-cut.md +4 -8
  12. package/.claude/skills/workflow-commands/split-assessment.md +4 -18
  13. package/.github/actions/setup-node/action.yml +33 -0
  14. package/.github/actions/setup-pnpm/action.yml +4 -4
  15. package/.github/workflows/auto-tag.yml +2 -3
  16. package/.github/workflows/ci.yml +7 -8
  17. package/.github/workflows/dependabot-auto-merge.yml +1 -0
  18. package/.github/workflows/pr-classification.yml +1 -3
  19. package/.github/workflows/production.yml +1 -0
  20. package/.github/workflows/publish.yml +10 -9
  21. package/.github/workflows/sonar-qube.yml +17 -13
  22. package/.pnpmfile.mjs +29 -0
  23. package/AGENTS.md +4 -11
  24. package/CLAUDE.md +24 -17
  25. package/GEMINI.md +4 -13
  26. package/README.md +8 -3
  27. package/SECURITY.md +2 -0
  28. package/cspell/index.yaml +4 -0
  29. package/dist/CLAUDE.basic.md +6 -6
  30. package/dist/CLAUDE.md +24 -17
  31. package/dist/CLAUDE.static.md +6 -6
  32. package/dist/config-merge/index.js +33 -9
  33. package/dist/hooks/batch-guard.js +4 -4
  34. package/dist/hooks/{chunk-OIZUHPJI.js → chunk-FO2I7UT7.js} +1 -1
  35. package/dist/hooks/{chunk-NKOG3XAV.js → chunk-GDU26K3J.js} +11 -3
  36. package/dist/hooks/{chunk-O4LL4HTI.js → chunk-HANSKHES.js} +2 -2
  37. package/dist/hooks/{chunk-3C4YCCEF.js → chunk-HBKZLITF.js} +4 -4
  38. package/dist/hooks/{chunk-UD24EAIL.js → chunk-JKKTVDAG.js} +1984 -146
  39. package/dist/hooks/{chunk-M2XDCUKD.js → chunk-KBXZLMRH.js} +89 -1803
  40. package/dist/hooks/{chunk-IL27YTPH.js → chunk-KOKOAV6D.js} +2 -2
  41. package/dist/hooks/{chunk-7ZPFAVR7.js → chunk-LJBLGHEH.js} +59 -19
  42. package/dist/hooks/{chunk-E54KGFEQ.js → chunk-NFZGFGOR.js} +1 -1
  43. package/dist/hooks/{chunk-KHWHKJOM.js → chunk-RFWWUI5F.js} +4 -4
  44. package/dist/hooks/codex-hook-adapter.js +12 -47
  45. package/dist/hooks/duplicate-read-guard.js +5 -5
  46. package/dist/hooks/format-edited.js +2 -2
  47. package/dist/hooks/inputs.json +175 -173
  48. package/dist/hooks/investigation-guard.js +5 -5
  49. package/dist/hooks/pretool-guard.js +9 -9
  50. package/dist/hooks/stop-guard.js +17 -12
  51. package/dist/josh.js +87 -31
  52. package/dist/version/index.js +36 -20
  53. package/eslint/rules/unicorn.js +3 -2
  54. package/package.json +23 -5
  55. package/pnpm-workspace.yaml +5 -7
  56. package/prompts/collaboration-workflow/glossary.md +17 -0
  57. package/prompts/collaboration-workflow/issue-template.md +1 -1
  58. package/prompts/collaboration-workflow/operating-rules.md +1 -1
  59. package/prompts/collaboration-workflow/principles.md +9 -0
  60. package/prompts/collaboration-workflow.md +1 -0
  61. package/scripts/backlog/backlog-arrival.ts +30 -10
  62. package/scripts/backlog/backlog-budget-cli.ts +15 -16
  63. package/scripts/backlog/backlog-offer-cli.ts +6 -14
  64. package/scripts/backlog/backlog-plan.ts +29 -18
  65. package/scripts/backlog/backlog-ready.ts +20 -7
  66. package/scripts/config-merge/parse-jsonc.ts +33 -3
  67. package/scripts/config-merge/patch-json-key.ts +13 -6
  68. package/scripts/cost-runtime/cost-cli.ts +3 -7
  69. package/scripts/delegation/delegation-policy.ts +9 -1
  70. package/scripts/delegation/investigation-guard.ts +1 -1
  71. package/scripts/delegation/investigation-reads.ts +105 -19
  72. package/scripts/doctor/doctor-consumer.ts +3 -5
  73. package/scripts/doctor/doctor-io.ts +9 -13
  74. package/scripts/document/backlogrun-parent-read-set.ts +12 -1
  75. package/scripts/document/capped-print.ts +74 -0
  76. package/scripts/document/document-byte-budget.ts +1 -0
  77. package/scripts/document/document-section-cli.ts +4 -3
  78. package/scripts/document/entry-read-budget.ts +13 -7
  79. package/scripts/document/entry-read-set.ts +32 -8
  80. package/scripts/document/lane-child-read-set.ts +7 -0
  81. package/scripts/document/read-set-point-of-use.ts +8 -3
  82. package/scripts/document/read-set-trim.ts +3 -1
  83. package/scripts/epic/epic-audit.ts +3 -18
  84. package/scripts/gate/scoped-green.ts +25 -14
  85. package/scripts/gh/git-gh-issue-list.ts +12 -11
  86. package/scripts/gh/git-gh-issue-write.ts +4 -1
  87. package/scripts/gh/git-gh-pr-auto-merge.ts +4 -1
  88. package/scripts/git/git-command.ts +6 -1
  89. package/scripts/git/git-spawn-sync.ts +65 -0
  90. package/scripts/git/git-spawn.ts +5 -4
  91. package/scripts/git/stash/stash-pop-args.ts +1 -6
  92. package/scripts/init/claude-kit-only-settings.ts +29 -0
  93. package/scripts/init/init-copy-content.ts +8 -2
  94. package/scripts/init/init-file-action.ts +54 -0
  95. package/scripts/init/init-logic-migrate.ts +35 -17
  96. package/scripts/init/init-logic-secretlint.ts +1 -1
  97. package/scripts/init/init-logic.ts +6 -22
  98. package/scripts/init/init.ts +11 -68
  99. package/scripts/init/initial-commit.ts +24 -0
  100. package/scripts/init/kit-written-paths.ts +2 -0
  101. package/scripts/init/project-profile.ts +3 -6
  102. package/scripts/init/start-exec.ts +16 -3
  103. package/scripts/init/start-steps.ts +10 -13
  104. package/scripts/issue/defect-rate-cli.ts +61 -20
  105. package/scripts/issue/issue-backlinks.ts +7 -14
  106. package/scripts/issue/issue-cite.ts +12 -6
  107. package/scripts/issue/issue-file.ts +5 -12
  108. package/scripts/issue/issue-read-cli.ts +1 -0
  109. package/scripts/issue/issue-report-failures.ts +6 -2
  110. package/scripts/issue/issue-state-cli.ts +1 -1
  111. package/scripts/issue/markdown-section.ts +21 -6
  112. package/scripts/issue/measure-rerun-cli.ts +73 -14
  113. package/scripts/josh/josh-commands-lane.ts +1 -1
  114. package/scripts/josh/josh-commands-project.ts +8 -1
  115. package/scripts/josh/josh-commands-workflow.ts +1 -1
  116. package/scripts/josh/run-github-prerequisite.ts +3 -6
  117. package/scripts/lane/lane-await.ts +89 -15
  118. package/scripts/lane/lane-cli.ts +11 -13
  119. package/scripts/lane/lane-close.ts +4 -1
  120. package/scripts/lane/lane-launch-cli.ts +10 -16
  121. package/scripts/lane/openai-review-broker.ts +4 -1
  122. package/scripts/lib/cli-flags.ts +31 -2
  123. package/scripts/lib/error-message.ts +12 -1
  124. package/scripts/lines/line-targets.ts +0 -11
  125. package/scripts/lines/lines-command.ts +5 -3
  126. package/scripts/propagate/propagate-git.ts +3 -8
  127. package/scripts/refactor/refactor-scan.ts +2 -1
  128. package/scripts/registry-migration/migrate-logic.ts +28 -19
  129. package/scripts/registry-migration/migrate.ts +6 -5
  130. package/scripts/release/github-release-cli.ts +14 -3
  131. package/scripts/release/github-release-environment.ts +1 -0
  132. package/scripts/release/github-release.ts +103 -17
  133. package/scripts/release/release-publish.ts +36 -2
  134. package/scripts/release/release-worktree.ts +97 -2
  135. package/scripts/review/review-checkout.ts +3 -2
  136. package/scripts/review/review-record-cli.ts +9 -16
  137. package/scripts/rules/early-heartbeat.ts +4 -3
  138. package/scripts/rules/lane-park.ts +9 -0
  139. package/scripts/rules/stop-rules.ts +7 -4
  140. package/scripts/run/run-carry-args.ts +8 -12
  141. package/scripts/run/run-carry-cli.ts +3 -1
  142. package/scripts/run/run-carry-conversation.ts +94 -0
  143. package/scripts/run/run-carry-reclaim.ts +37 -0
  144. package/scripts/run/run-carry.ts +21 -5
  145. package/scripts/run/run-cut-args.ts +1 -6
  146. package/scripts/run/run-ending-cli.ts +1 -5
  147. package/scripts/run/run-entry-cli.ts +40 -9
  148. package/scripts/run/run-entry-stop.ts +104 -0
  149. package/scripts/run/run-event-stream-emit.ts +7 -2
  150. package/scripts/run/run-hold.ts +4 -1
  151. package/scripts/run/run-liveness-cli.ts +1 -5
  152. package/scripts/run/run-merge-cli.ts +2 -10
  153. package/scripts/run/run-merge-steps.ts +4 -1
  154. package/scripts/run/run-parent-cut-hook.ts +4 -1
  155. package/scripts/run/run-prep-cli.ts +4 -1
  156. package/scripts/run/run-progress-args.ts +20 -4
  157. package/scripts/run/run-progress-cli.ts +31 -29
  158. package/scripts/run/run-progress-clock.ts +4 -1
  159. package/scripts/run/run-ship-cli.ts +8 -16
  160. package/scripts/run/run-ship-probe.ts +4 -1
  161. package/scripts/run/run-status-cli.ts +2 -15
  162. package/scripts/run/run-tidy-lanes.ts +4 -1
  163. package/scripts/run/run-wake-cli.ts +2 -10
  164. package/scripts/run/run-watcher-guard.ts +9 -5
  165. package/scripts/safe-chain/pack-hook.ts +92 -0
  166. package/scripts/sync/sync-ai-files.ts +4 -0
  167. package/scripts/test/test-declared-changed.ts +13 -16
  168. package/scripts/test/test-declared.ts +3 -7
  169. package/scripts/test/test-related.ts +1 -6
  170. package/scripts/test/test-unit-guard.ts +21 -7
  171. package/scripts/time-runtime/time-spans.ts +4 -0
  172. package/scripts/version/version-commands.ts +11 -1
  173. package/templates/CLAUDE.basic.md +6 -6
  174. package/templates/workflows/ci.yml +16 -82
  175. package/templates/workflows/dependabot-auto-merge.yml +1 -0
  176. package/templates/workflows/github-release.yml +14 -28
  177. package/tsconfig.sonar.json +4 -22
package/dist/josh.js CHANGED
@@ -4,6 +4,74 @@
4
4
  import { existsSync } from "node:fs";
5
5
  import path from "node:path";
6
6
 
7
+ // scripts/lib/spawn-exit.ts
8
+ var SPAWN_ERROR_EXIT_CODE = 2;
9
+ var SIGNAL_KILL_EXIT_CODE = 1;
10
+ function resolve_spawn_exit(executable, result) {
11
+ if (result.exitCode !== void 0) return result.exitCode;
12
+ if (result.isTerminated === true) return SIGNAL_KILL_EXIT_CODE;
13
+ console.error(`Failed to execute ${executable}: ${result.shortMessage ?? "spawn failed"}`);
14
+ return SPAWN_ERROR_EXIT_CODE;
15
+ }
16
+ function read_spawn_stdout(result) {
17
+ return result.stdout ?? "";
18
+ }
19
+ function read_spawn_stderr(result) {
20
+ return result.stderr ?? "";
21
+ }
22
+
23
+ // scripts/git/git-spawn-sync.ts
24
+ import { execaSync } from "execa";
25
+
26
+ // scripts/git/constants.ts
27
+ import { platform } from "node:os";
28
+ var GIT_COMMAND_UNIX = "/usr/bin/git";
29
+ function get_git_command() {
30
+ if (platform() === "win32") {
31
+ return String.raw`"C:\Program Files\Git\cmd\git.exe"`;
32
+ }
33
+ return GIT_COMMAND_UNIX;
34
+ }
35
+ function get_git_command_for_spawn() {
36
+ if (platform() === "win32") {
37
+ return String.raw`C:\Program Files\Git\cmd\git.exe`;
38
+ }
39
+ return GIT_COMMAND_UNIX;
40
+ }
41
+ var git_utilities = {
42
+ get_git_command,
43
+ get_git_command_for_spawn
44
+ };
45
+
46
+ // scripts/git/git-spawn-sync.ts
47
+ var GIT_TIMEOUT_MS = 1e4;
48
+ var SUCCESS_EXIT_CODE = 0;
49
+ var C_LOCALE = { LC_ALL: "C", LANGUAGE: "C" };
50
+ function run(args, options = {}) {
51
+ const result = execaSync(git_utilities.get_git_command_for_spawn(), args, {
52
+ ...options,
53
+ extendEnv: true,
54
+ reject: false,
55
+ timeout: options.timeout ?? GIT_TIMEOUT_MS
56
+ });
57
+ return {
58
+ exit_code: result.exitCode,
59
+ stdout: read_spawn_stdout(result),
60
+ stderr: read_spawn_stderr(result)
61
+ };
62
+ }
63
+ function read(args, options = {}) {
64
+ const result = run(args, options);
65
+ return result.exit_code === SUCCESS_EXIT_CODE ? result.stdout.trim() : void 0;
66
+ }
67
+ function origin_url(cwd) {
68
+ return read(["config", "--get", "remote.origin.url"], { cwd });
69
+ }
70
+ function toplevel(timeout = GIT_TIMEOUT_MS) {
71
+ return run(["rev-parse", "--show-toplevel"], { env: C_LOCALE, timeout });
72
+ }
73
+ var git_spawn_sync = { GIT_TIMEOUT_MS, C_LOCALE, run, read, origin_url, toplevel };
74
+
7
75
  // scripts/lib/json-value.ts
8
76
  function parse_or_undefined(text) {
9
77
  try {
@@ -43,24 +111,10 @@ function read_if_readable(path5) {
43
111
  }
44
112
  var file_reader = { read_optional, read_file_or_empty, read_if_readable };
45
113
 
46
- // scripts/lib/spawn-exit.ts
47
- var SPAWN_ERROR_EXIT_CODE = 2;
48
- var SIGNAL_KILL_EXIT_CODE = 1;
49
- function resolve_spawn_exit(executable, result) {
50
- if (result.exitCode !== void 0) return result.exitCode;
51
- if (result.isTerminated === true) return SIGNAL_KILL_EXIT_CODE;
52
- console.error(`Failed to execute ${executable}: ${result.shortMessage ?? "spawn failed"}`);
53
- return SPAWN_ERROR_EXIT_CODE;
54
- }
55
- function read_spawn_stdout(result) {
56
- return result.stdout ?? "";
57
- }
58
-
59
114
  // scripts/version/kit-descriptor.ts
60
115
  var KIT_PACKAGE_NAME = "@joshuafolkken/kit";
61
116
 
62
117
  // scripts/doctor/doctor-consumer.ts
63
- import { execaSync } from "execa";
64
118
  import { z } from "zod";
65
119
  var NODE_MODULES = "node_modules";
66
120
  var SETTINGS_PATH = path.join(".claude", "settings.json");
@@ -68,7 +122,7 @@ var CLAUDE_MD = "CLAUDE.md";
68
122
  var PLUGIN_ID = "kit@kit";
69
123
  var CLAUDE_MD_POINTER = `@${NODE_MODULES}/${KIT_PACKAGE_NAME}/dist/${CLAUDE_MD}`;
70
124
  var SECRETLINT_BIN = path.join(NODE_MODULES, ".bin", "secretlint");
71
- var GIT_TIMEOUT_MS = 2e3;
125
+ var GIT_TIMEOUT_MS2 = 2e3;
72
126
  var manifest_schema = z.object({
73
127
  name: z.string().optional(),
74
128
  dependencies: z.record(z.string(), z.string()).optional(),
@@ -102,11 +156,9 @@ function is_plugin_declared(root) {
102
156
  return parsed.success && parsed.data.enabledPlugins?.[PLUGIN_ID] === true;
103
157
  }
104
158
  function configured_hooks_path(root) {
105
- const result = execaSync("git", ["-C", root, "config", "--get", "core.hooksPath"], {
106
- reject: false,
107
- timeout: GIT_TIMEOUT_MS
159
+ const value = git_spawn_sync.read(["-C", root, "config", "--get", "core.hooksPath"], {
160
+ timeout: GIT_TIMEOUT_MS2
108
161
  });
109
- const value = read_spawn_stdout(result).trim();
110
162
  return value === "" ? void 0 : value;
111
163
  }
112
164
  function claude_md_state(root) {
@@ -166,6 +218,7 @@ function report_consumer_setup(root) {
166
218
  var doctor_consumer = {
167
219
  claude_md_line,
168
220
  claude_md_state,
221
+ configured_hooks_path,
169
222
  hooks_path_line,
170
223
  is_kit_consumer,
171
224
  is_plugin_declared,
@@ -246,7 +299,7 @@ function resolve_package_bin(base_directory, package_name, bin_name) {
246
299
  }
247
300
 
248
301
  // scripts/josh/josh-logic.ts
249
- import { execaSync as execaSync3 } from "execa";
302
+ import { execaSync as execaSync2 } from "execa";
250
303
 
251
304
  // scripts/josh/command-suggest.ts
252
305
  var MAX_SUGGESTION_DISTANCE = 3;
@@ -536,7 +589,7 @@ var LANE_COMMANDS = {
536
589
  ...LANE_ARGUMENTS,
537
590
  description: "Block until any of the named in-flight lane children completes; prints which one finished",
538
591
  category: "AI tools",
539
- reference: ["<issue> [<issue>...]", "automation", ["processes"]],
592
+ reference: ["<issue> [<issue>...] [--owner <pid>]", "automation", ["files", "processes"]],
540
593
  default_script_arguments: ["await"]
541
594
  },
542
595
  "lane:launch": {
@@ -1237,6 +1290,13 @@ var PROJECT_COMMANDS = {
1237
1290
  ["files", "git", "network", "processes"]
1238
1291
  ]
1239
1292
  },
1293
+ "dogfood:commit": {
1294
+ script: "scripts/dogfood/dogfood-commit-cli.ts",
1295
+ description: "Make the first commit of a dogfood run's own kit-test-* project",
1296
+ category: "Project",
1297
+ reference: ["<dir>", "automation", ["files", "git"]],
1298
+ is_kit_only: true
1299
+ },
1240
1300
  init: {
1241
1301
  script: "scripts/init/init.ts",
1242
1302
  description: "Initialize config in a new project",
@@ -1255,7 +1315,7 @@ var PROJECT_COMMANDS = {
1255
1315
  },
1256
1316
  "registry:migrate": {
1257
1317
  script: "scripts/registry-migration/migrate.ts",
1258
- description: "Migrate a kit-only project from GitHub Packages to public npm",
1318
+ description: "Migrate a project from GitHub Packages to public npm",
1259
1319
  category: "Project",
1260
1320
  reference: ["", "developer", ["files", "network", "processes"]]
1261
1321
  },
@@ -1441,7 +1501,7 @@ var WORKFLOW_COMMANDS = {
1441
1501
  script: "scripts/issue/measure-rerun-cli.ts",
1442
1502
  description: "Re-run a merged issue\u2019s baseline command and print the before/after pair",
1443
1503
  category: "Workflow",
1444
- reference: ["<path>", "automation", ["processes", "files"]]
1504
+ reference: ["<issue-number>", "automation", ["network", "processes", "files"]]
1445
1505
  },
1446
1506
  // A script rather than an `sh -c` chain, because it has a precondition to enforce: run inside a
1447
1507
  // linked work tree it would hijack the default branch from every other one (joshuafolkken/kit#1535).
@@ -1700,7 +1760,6 @@ var repo_origin = {
1700
1760
  };
1701
1761
 
1702
1762
  // scripts/josh/run-github-prerequisite.ts
1703
- import { execaSync as execaSync2 } from "execa";
1704
1763
  var GITHUB_RUN_COMMANDS = /* @__PURE__ */ new Set([
1705
1764
  "run:entry",
1706
1765
  "run:prep",
@@ -1714,11 +1773,8 @@ function explanation(root, command) {
1714
1773
  if (!existsSync3(path3.join(root, ".git"))) {
1715
1774
  return `${command} requires Git and a GitHub origin. Run git init and add a GitHub origin first.`;
1716
1775
  }
1717
- const result = execaSync2("git", ["config", "--get", "remote.origin.url"], {
1718
- cwd: root,
1719
- reject: false
1720
- });
1721
- if (result.exitCode === 0 && repo_origin.parse_origin_url(result.stdout)) return void 0;
1776
+ const url = git_spawn_sync.origin_url(root);
1777
+ if (url !== void 0 && repo_origin.parse_origin_url(url)) return void 0;
1722
1778
  return `${command} requires a GitHub origin. Add an origin pointing to GitHub first.`;
1723
1779
  }
1724
1780
  var run_github_prerequisite = { explanation };
@@ -1821,7 +1877,7 @@ function should_use_shell(executable) {
1821
1877
  return process.platform === "win32" && executable !== process.execPath;
1822
1878
  }
1823
1879
  function spawn_script(tsx_executable, script_arguments) {
1824
- const result = execaSync3(tsx_executable, script_arguments, {
1880
+ const result = execaSync2(tsx_executable, script_arguments, {
1825
1881
  stdio: "inherit",
1826
1882
  shell: should_use_shell(tsx_executable),
1827
1883
  reject: false
@@ -1830,7 +1886,7 @@ function spawn_script(tsx_executable, script_arguments) {
1830
1886
  }
1831
1887
  function run_shell_command(shell, extra) {
1832
1888
  const [executable = "", ...rest_arguments] = shell;
1833
- const result = execaSync3(executable, [...rest_arguments, ...extra], {
1889
+ const result = execaSync2(executable, [...rest_arguments, ...extra], {
1834
1890
  stdio: "inherit",
1835
1891
  reject: false
1836
1892
  });
@@ -565,7 +565,13 @@ var agent_session_environment = {
565
565
  function message_of(error) {
566
566
  return error instanceof Error ? error.message : String(error);
567
567
  }
568
- var error_text = { message_of };
568
+ var DEBUG_ENV_KEY = "JOSH_DEBUG";
569
+ function trace_swallowed(where, error) {
570
+ if ((process.env[DEBUG_ENV_KEY] ?? "").trim() === "") return;
571
+ process.stderr.write(`josh debug: ${where}: ${message_of(error)}
572
+ `);
573
+ }
574
+ var error_text = { message_of, trace_swallowed };
569
575
 
570
576
  // scripts/gh/git-gh-exec.ts
571
577
  import { execa as execa2, execaSync } from "execa";
@@ -1009,18 +1015,23 @@ function is_package_entry(line) {
1009
1015
  function is_github_tarball_line(line) {
1010
1016
  return line.trimStart().startsWith("tarball: https://npm.pkg.github.com/");
1011
1017
  }
1012
- function clean_kit_line(line, version, integrities) {
1013
- if (!version) return line;
1018
+ function package_id(key) {
1019
+ const name = fix_gh_packages_logic.package_path_from_key(key);
1020
+ return `${name}@${fix_gh_packages_logic.package_version_from_key(key)}`;
1021
+ }
1022
+ function clean_scoped_line(line, id, integrities) {
1023
+ if (!id) return line;
1014
1024
  if (is_github_tarball_line(line)) return void 0;
1015
1025
  const cleaned = remove_flow_tarball(line);
1016
- const pair = integrities.get(version);
1026
+ const pair = integrities.get(id);
1017
1027
  if (pair === void 0) return cleaned;
1018
1028
  return cleaned.split(`integrity: ${pair[0]}`).join(`integrity: ${pair[1]}`);
1019
1029
  }
1020
- function next_kit_version(line, version) {
1021
- if (!is_package_entry(line)) return version;
1022
- const prefix = " '@joshuafolkken/kit@";
1023
- return line.startsWith(prefix) ? line.slice(prefix.length).split("'", 1)[0] ?? "" : "";
1030
+ function next_scoped_id(line, id) {
1031
+ if (!is_package_entry(line)) return id;
1032
+ const prefix = ` '${SCOPE}/`;
1033
+ if (!line.startsWith(prefix)) return "";
1034
+ return package_id(line.slice(" '".length).split("'", 1)[0] ?? "");
1024
1035
  }
1025
1036
  function integrity_pair(old_integrity, npm_integrity) {
1026
1037
  if (old_integrity === void 0 || npm_integrity === void 0) return void 0;
@@ -1029,24 +1040,24 @@ function integrity_pair(old_integrity, npm_integrity) {
1029
1040
  function lock_integrities(lockfile, npm_integrities) {
1030
1041
  const packages = fix_gh_packages_logic.parse_lockfile_packages(lockfile);
1031
1042
  const pairs = /* @__PURE__ */ new Map();
1032
- const kit_entries = Object.entries(packages).filter(([key]) => key.startsWith(`${SCOPE}/kit@`));
1033
- for (const [key, entry] of kit_entries) {
1034
- const version = fix_gh_packages_logic.package_version_from_key(key);
1035
- const pair = integrity_pair(entry.resolution?.integrity, npm_integrities.get(version));
1036
- if (pair !== void 0) pairs.set(version, pair);
1043
+ const scoped_entries = Object.entries(packages).filter(([key]) => key.startsWith(`${SCOPE}/`));
1044
+ for (const [key, entry] of scoped_entries) {
1045
+ const id = package_id(key);
1046
+ const pair = integrity_pair(entry.resolution?.integrity, npm_integrities.get(id));
1047
+ if (pair !== void 0) pairs.set(id, pair);
1037
1048
  }
1038
1049
  return pairs;
1039
1050
  }
1040
1051
  function push_if_defined(lines, cleaned) {
1041
1052
  if (cleaned !== void 0) lines.push(cleaned);
1042
1053
  }
1043
- function rewrite_kit_lockfile(lockfile, npm_integrities) {
1044
- let kit_version = "";
1054
+ function rewrite_scoped_lockfile(lockfile, npm_integrities) {
1055
+ let id = "";
1045
1056
  const lines = [];
1046
1057
  const integrities = lock_integrities(lockfile, npm_integrities);
1047
1058
  for (const line of lockfile.split("\n")) {
1048
- kit_version = next_kit_version(line, kit_version);
1049
- const cleaned = clean_kit_line(line, kit_version, integrities);
1059
+ id = next_scoped_id(line, id);
1060
+ const cleaned = clean_scoped_line(line, id, integrities);
1050
1061
  push_if_defined(lines, cleaned);
1051
1062
  }
1052
1063
  return lines.join("\n");
@@ -1076,7 +1087,7 @@ function previous_registry(project_npmrc, user_npmrc) {
1076
1087
  }
1077
1088
  function plan(project_npmrc, user_npmrc, lockfile) {
1078
1089
  const packages = scoped_packages(lockfile);
1079
- const blocked = packages.filter((name) => name !== `${SCOPE}/kit`);
1090
+ const blocked = [];
1080
1091
  if (has_duplicate_mapping(project_npmrc, user_npmrc)) blocked.push("duplicate registry mappings");
1081
1092
  const unsupported = unsupported_registry(project_npmrc);
1082
1093
  if (unsupported !== void 0) blocked.push(unsupported);
@@ -1088,7 +1099,7 @@ var migrate_logic = {
1088
1099
  plan,
1089
1100
  github_tarballs,
1090
1101
  is_github_tarball,
1091
- rewrite_kit_lockfile,
1102
+ rewrite_scoped_lockfile,
1092
1103
  scoped_versions
1093
1104
  };
1094
1105
 
@@ -1276,6 +1287,7 @@ var version_targets = {
1276
1287
  var FAILURE_EXIT_CODE = 1;
1277
1288
  var ALREADY_UP_TO_DATE = "Already up to date";
1278
1289
  var NO_QUARANTINE_MINUTES2 = 0;
1290
+ var UPGRADE_TIMEOUT_MS = 6e5;
1279
1291
  function read_release_hold(package_config, latest, versions) {
1280
1292
  if (versions.every((version) => version === void 0 || version === latest)) return void 0;
1281
1293
  const minimum_age_minutes = release_age.read_nearest_minimum_release_age(process.cwd());
@@ -1360,7 +1372,11 @@ function run_check(config) {
1360
1372
  );
1361
1373
  }
1362
1374
  function run_upgrade_command(command) {
1363
- const result = execaSync4("sh", ["-c", command], { stdio: "inherit", reject: false });
1375
+ const result = execaSync4("sh", ["-c", command], {
1376
+ stdio: "inherit",
1377
+ reject: false,
1378
+ timeout: UPGRADE_TIMEOUT_MS
1379
+ });
1364
1380
  return result.exitCode ?? FAILURE_EXIT_CODE;
1365
1381
  }
1366
1382
  function run_all_upgrade_commands(commands) {
@@ -80,8 +80,9 @@ export const unicorn_rules = {
80
80
  'unicorn/custom-error-definition': 'error',
81
81
  // throw new Error()の形式を強制
82
82
  'unicorn/throw-new-error': 'error',
83
- // 空の配列をチェック
84
- 'unicorn/no-empty-file': 'error',
83
+ // 空ファイルを禁止する。コメントだけのファイルは許可する: `sv create` の足場 `src/lib/index.ts` は
84
+ // コメント 1 行だけで、手を加えていないプロジェクトで lint が落ちてしまうため(#3069)
85
+ 'unicorn/no-empty-file': ['error', { allowComments: true }],
85
86
  // 静的なみずからのみのクラスを禁止
86
87
  'unicorn/no-static-only-class': 'error',
87
88
  // this別名を禁止(arrow functionを使うべき)
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1094.0",
2
+ "version": "1.1136.0",
3
3
  "name": "@joshuafolkken/kit",
4
4
  "description": "Development toolkit for AI-assisted projects: formatting, AI assistant rules and a Git/GitHub Issue workflow for any repository, plus lint, type check, tests and hooks for Node projects",
5
5
  "keywords": [
@@ -42,6 +42,7 @@
42
42
  "lefthook",
43
43
  ".claude/settings.json",
44
44
  ".claude/skills",
45
+ ".claude/agents",
45
46
  ".claude/.claude-plugin",
46
47
  ".claude-plugin",
47
48
  ".codex",
@@ -59,6 +60,7 @@
59
60
  ".ncurc.json",
60
61
  ".prettierignore",
61
62
  "pnpm-workspace.yaml",
63
+ ".pnpmfile.mjs",
62
64
  "tsconfig.sonar.json",
63
65
  "playwright.config.ts",
64
66
  "!**/*.test.ts",
@@ -71,6 +73,7 @@
71
73
  "!scripts/time",
72
74
  "!scripts/cost",
73
75
  "!scripts/retrospective",
76
+ "!scripts/dogfood",
74
77
  "!scripts/test/classify-isolation.ts",
75
78
  "!scripts/test/http-fault-injection.ts",
76
79
  "!scripts/test/josh-harness*.ts",
@@ -161,6 +164,7 @@
161
164
  "peerDependencies": {
162
165
  "@eslint/compat": "^2.1.1",
163
166
  "@eslint/js": "^10.0.1",
167
+ "@ianvs/prettier-plugin-sort-imports": "^4.7.1",
164
168
  "@playwright/test": ">=1.0.0",
165
169
  "@stylistic/eslint-plugin": "^5.10.0",
166
170
  "eslint": "^10.0.0",
@@ -171,6 +175,9 @@
171
175
  "eslint-plugin-sonarjs": "^4.2.1",
172
176
  "eslint-plugin-unicorn": "^76.0.0",
173
177
  "globals": "^17.12.0",
178
+ "prettier": "^3.9.9",
179
+ "prettier-plugin-svelte": "^4.1.1",
180
+ "prettier-plugin-tailwindcss": "^0.8.1",
174
181
  "typescript": "^6.0.3",
175
182
  "typescript-eslint": "^8.70.1"
176
183
  },
@@ -184,6 +191,9 @@
184
191
  "@eslint/js": {
185
192
  "optional": true
186
193
  },
194
+ "@ianvs/prettier-plugin-sort-imports": {
195
+ "optional": true
196
+ },
187
197
  "@stylistic/eslint-plugin": {
188
198
  "optional": true
189
199
  },
@@ -211,6 +221,15 @@
211
221
  "globals": {
212
222
  "optional": true
213
223
  },
224
+ "prettier": {
225
+ "optional": true
226
+ },
227
+ "prettier-plugin-svelte": {
228
+ "optional": true
229
+ },
230
+ "prettier-plugin-tailwindcss": {
231
+ "optional": true
232
+ },
214
233
  "typescript": {
215
234
  "optional": true
216
235
  },
@@ -224,13 +243,13 @@
224
243
  "@eslint/js": "^10.0.1",
225
244
  "@ianvs/prettier-plugin-sort-imports": "^4.7.1",
226
245
  "@playwright/test": "^1.63.0",
227
- "@secretlint/secretlint-rule-preset-recommend": "^13.0.6",
246
+ "@secretlint/secretlint-rule-preset-recommend": "^13.0.7",
228
247
  "@stylistic/eslint-plugin": "^5.10.0",
229
248
  "@types/node": "^26.6.4",
230
249
  "@types/semver": "^7.8.0",
231
250
  "cspell": "^10.3.6",
232
251
  "esbuild": "^0.28.2",
233
- "eslint": "^10.11.0",
252
+ "eslint": "^10.12.0",
234
253
  "eslint-config-prettier": "^10.1.8",
235
254
  "eslint-import-resolver-typescript": "^4.4.5",
236
255
  "eslint-plugin-import-x": "^4.17.1",
@@ -245,7 +264,7 @@
245
264
  "prettier-plugin-tailwindcss": "^0.8.1",
246
265
  "rollup": "^4.64.0",
247
266
  "rollup-plugin-dts": "^6.5.1",
248
- "secretlint": "^13.0.6",
267
+ "secretlint": "^13.0.7",
249
268
  "typescript": "^6.0.3",
250
269
  "typescript-eslint": "^8.71.0",
251
270
  "vitest": "^5.0.3"
@@ -256,7 +275,6 @@
256
275
  "jsonc-parser": "^3.3.1",
257
276
  "ora": "^9.4.1",
258
277
  "semver": "^7.8.5",
259
- "strip-json-comments": "^5.0.3",
260
278
  "tsx": "^4.23.15",
261
279
  "zod": "^4.6.5"
262
280
  },
@@ -28,11 +28,9 @@ minimumReleaseAgeExclude:
28
28
 
29
29
  overrides:
30
30
  svelte: ^5.55.7
31
- devalue: ^5.8.1
32
- # Indirect deps a within-range re-resolution could not lift past their parents' pins, floored at the
33
- # smallest release clearing each OSV advisory (joshuafolkken/kit#2200; user-approved 2026-09-21 to
34
- # override indirect packages). Carets so `josh latest`'s re-resolution keeps them current within the
35
- # major without reintroducing the advisory.
31
+ # An indirect dep a within-range re-resolution could not lift past its parents' pins, floored at the
32
+ # smallest release clearing its OSV advisory (joshuafolkken/kit#2200; user-approved 2026-09-21 to
33
+ # override indirect packages). A caret so `josh latest`'s re-resolution keeps it current within the
34
+ # major without reintroducing the advisory. The devalue, postcss and nanoid floors were removed once
35
+ # their parents' ranges exceeded them (joshuafolkken/kit#3091).
36
36
  vite: ^8.0.16 # OSV — vite 8.0.8 → 8.0.16
37
- postcss: ^8.5.23 # OSV — postcss 8.5.16 → 8.5.23
38
- nanoid: ^3.3.18 # OSV — nanoid 3.3.15 → 3.3.18
@@ -0,0 +1,17 @@
1
+ # ワークフロー用語集
2
+
3
+ **これは定義集であり、規則を定める場所ではない。** 各語は 1〜2 行の定義と、その語の本文がある単一ソースへのポインタだけを持つ。手順・条件・数値は指し先が持ち、ここでは言い換えない — 言い換えた行は指し先より先に古くなる(joshuafolkken/kit#3080)。
4
+
5
+ `scripts/document/workflow-glossary.test.ts` が、下の主要語が定義されていることと、各ポインタが実在する節に解決することを検査する。
6
+
7
+ - **lane** — `backlogrun` が子 Issue を並行実行するための、専用ブランチ・`.env`・ポートを持つ git worktree。実装・ゲート・レビューは並行し、マージは直列。→ `.claude/skills/workflow-commands/backlogrun-lanes.md` → "Lanes — running more than one child at a time"
8
+ - **cut** — 実行中のプロセスを意図して終わらせ、新しいプロセスが同じ作業を引き継ぐ境界。レーン子のゲート前の cut と、`backlogrun` のセッション cut がある。→ `.claude/skills/workflow-commands/pre-gate-cut.md` → "The pre-gate cut"、`.claude/skills/workflow-commands/backlogrun-progress.md` → "The hand-off"
9
+ - **hold** — 1 つの作業ツリーを 1 つのランが占有しているという記録。`pnpm josh run:hold` が取り、`pnpm josh run:release` が外す。→ `.claude/skills/workflow-commands/working-tree-hold.md` → "The working-tree hold"
10
+ - **park** — `backlogrun` が自分で決められない子を `needs-decision` で止め、バッチ全体は止めずに次の子へ進むこと。→ `.claude/skills/workflow-commands/backlogrun-park.md` → "park and continue"
11
+ - **point of use** — 文書を入口ではなく、その文書に従うべき操作と同じターンで、その操作の直前に読むこと。→ `.claude/skills/workflow-commands/SKILL.md` → "Four documents are read at the point of use, not at the entry"
12
+ - **entry read** — コマンド起動時に読む文書の集合。`pnpm josh read:set` がその集合と費用を答える。→ `.claude/skills/workflow-commands/SKILL.md` → "Which file to read"
13
+ - **oracle** — 機械的に読める入力だけから規則の答えを計算するコマンド。一覧は `pnpm josh oracle:list`。→ `prompts/collaboration-workflow/residency.md` → "第 0 問"
14
+ - **interrupt** — WIP 上限を超えても起票できる、3 条件で決まる発見。別パッケージ起因の上流起票(割り込み Issue)とは別の区分。→ `prompts/collaboration-workflow/wip-cap.md` → "割り込み起票"、`prompts/collaboration-workflow/upstream-interrupt.md` → "別パッケージ起因の問題は割り込み Issue で対応する"
15
+ - **Tier** — 判断点の 3 区分。A は可逆で自分で決める、B は拮抗していて人に聞く、C は不可逆・共有状態で明示指示が要る。→ `CLAUDE.md` → "Decision autonomy"、`prompts/collaboration-workflow/operating-rules.md` → "意思決定の自律ポリシー"
16
+ - **carry** — `backlogrun` の 1 回の起動の予算と進捗を、セッション cut をまたいで引き継ぐ記録(`pnpm josh run:carry`)。→ `.claude/skills/workflow-commands/backlogrun-steps.md` → "The session cut is inside the invocation"
17
+ - **wave** — エピックの子をまとめて並行実行する組。仕組みではなく、依存宣言で表す形。→ `.claude/skills/epic-commands/SKILL.md` → "Execution waves"
@@ -76,7 +76,7 @@ Issue には次の要素を必ず含める。
76
76
  - **互換性を壊す変更は影響を本文で宣言する。** 既存の利用者、API、設定の契約を変える場合は、`## 背景` の直下に `- 互換性: 破壊的変更` を書き、廃止対象と影響を説明する。移行措置や事前案内があっても破壊的変更なら宣言する。この宣言があれば `pnpm josh issue:file` が `breaking-change` ラベルを同じ作成リクエストに付ける。互換性を保つ変更には付けない。影響が曖昧なら起票文案で対象の利用者と契約を明確にしてから分類する
77
77
  - **本文が宣言する分類ラベルは `pnpm josh issue:file` がすべて起票時に付ける。** `bug`、`enhancement`、`breaking-change` は組み合わせて付くことがある。`depth`・`route` などのラベルと併せて 1 回の作成リクエストで付け、宣言が無いとき(`issue:lint` の `labels: none`)は分類ラベルを付けない。Issue が `enhancement` と `breaking-change` の両方を持つとき、PR のリリース分類は `breaking-change` 一つにする。Issue の目的の分類と PR のリリース分類を区別する
78
78
  - **`## 発火点`** には、その規則が破れる瞬間に最初に呼ばれるツールコール名を 1 つ書く。名前は `prompts/collaboration-workflow/rule-delivery.md` の配送表に載る、フックで配送可能なツール(`Bash` / `Edit` / `Read` / `Write` / `AskUserQuestion`)と突き合わせる。**表に無い名前**(そもそもツールコール名でない)と**不一致**(既知のツールだが配送できない)は区別して印字され、joshuafolkken/kit#2201 の「規則は正しいが名指ししたコールが実際に破れるコールの 1 つ後ろ」を起票時に検出する
79
- - **`## ベースライン`** は **「コマンド + 値」** で書く。``- `<測定コマンド>` → <現在の値>`` の形に限り、自然文だけの測定説明は受け付けない(再実行できないため)。マージ後に `pnpm josh measure:rerun` が同じコマンドをやり直し、**before / after の対**を印字する。値が動かなかったとき、反証された前提として `docs/maintainers/observations/` の、その時点のブランチが指す Issue のファイルに 1 行追記される(既存の append-only 台帳を使い、別の台帳は作らない)
79
+ - **`## ベースライン`** は **「コマンド + 値」** で書く。``- `<測定コマンド>` → <現在の値>`` の形に限り、自然文だけの測定説明は受け付けない(再実行できないため)。マージ後に `pnpm josh measure:rerun <N>` が同じコマンドをやり直し(本文は shell として実行されるため、作者が `OWNER` / `MEMBER` / `COLLABORATOR` の Issue に限る)、**before / after の対**を印字する。値が動かなかったとき、反証された前提として `docs/maintainers/observations/` の、その時点のブランチが指す Issue のファイルに 1 行追記される(既存の append-only 台帳を使い、別の台帳は作らない)
80
80
  - **`## 再現`** は **「コマンド + 実際の出力」** で書く。コマンドを backtick、出力をフェンス(``` または ~~~)で囲む。自然文の「確認した」は再実行できないため受け付けない(`## ベースライン` と同じ理由)。読みだけの起票を起票時に弾くためである
81
81
  - **検査は `pnpm josh issue:lint <path>` が行う。** 対象判定・3 見出しの有無・発火点の配送可否・ベースラインが「コマンド + 値」か・再現が「コマンド + 実際の出力」かと、起票時に必要な分類ラベルを、起票前の本文ファイルからまとめて答える(`pnpm josh issue:file` は起票前に同じ検査を行い、問題があれば起票しない)
82
82
 
@@ -27,7 +27,7 @@
27
27
 
28
28
  PR マージ・ブランチ削除・force push・共有ブランチへの push・外部通知の追加送信・リポジトリ設定の変更など、**共有状態に影響する操作はその場でユーザーに明示指示されたものだけ実行する**。
29
29
 
30
- - `fullrun` の auto-merge は `fullrun` の指示自体に含まれるため許可される(本文は [`followup.md`](../../.claude/skills/workflow-commands/followup.md))。それ以外の状況で勝手にマージしてはならない
30
+ - `fullrun` と `backlogrun` の auto-merge は、それぞれの指示自体に含まれるため許可される(本文は [`followup.md`](../../.claude/skills/workflow-commands/followup.md)、`backlogrun` の子への継承は [`backlogrun-child.md`](../../.claude/skills/workflow-commands/backlogrun-child.md))。`prrun` はマージを認可しない。それ以外の状況で勝手にマージしてはならない
31
31
  - `kickoff` / `pnpm josh followup` 単独実行は文書化されたスコープで終了する。PR が OPEN のまま完了したら状態を報告して停止する
32
32
  - 「チェックが全部 green だから次のステップに進む」は承認ではない
33
33
  - **`gh pr merge` の直接実行は kit 配布の `.claude/settings.json` の `permissions.deny` で機械的に遮断されている。** パターンの一覧はそのファイルが一次情報であり、ここでは書き写さない。 `fullrun` の auto-merge は `pnpm josh followup` が node スクリプト内部から gh を起動するため影響を受けない — Bash マッチャに見えるのは `pnpm josh …` だけである。**ただし deny は実装であって規則ではない** — パターンが取りこぼす綴りを禁じているのは本節の規則のほうである
@@ -72,3 +72,12 @@
72
72
  - **導線文書に規則を書き戻さない。** そこに書かれた規則は、次の改訂で更新し忘れる 4 つ目の置き場になる
73
73
  - `scripts/document/ai-document-pointers.test.ts` が機械的に守る。導線文書に規則本体が再出現した場合、導線の一文が消えた場合、`CLAUDE.md` が単一ソースであることの記述が消えた場合に落ちる
74
74
  - 経緯は `docs/maintainers/principles-rationale.md` → "Why AGENTS and GEMINI are pointers"、`@CLAUDE.md` 行と他ツールでの未検証は `docs/maintainers/principles-rationale.md` → "The include line, and what has not been verified"
75
+
76
+ ### Claude Code 以外のエージェントでの読み替え
77
+
78
+ `CLAUDE.md` は Claude Code 向けに書かれており、その機能を名指しする。Codex・Gemini CLI など別のエージェントは次のように読み替える。導線文書が持つのはツール固有の 1 行だけで、共通の読み替えはこの節にしか無い(joshuafolkken/kit#3079)。
79
+
80
+ - **フック**: フックが走らない、または出力が見えないツールでは、フックが届ける規則を自分で適用する。その一覧は手書きせず、[`rule-delivery.md`](./rule-delivery.md) →「配送されている規則」の表を正とする — 表の各行が、そのツールでは自分で守る規則である。表の外でフックが担う 3 つも同じく自分で行う — 最初の実行時コード編集の前に Step 0 の作業サマリを提示する、編集ごとの整形フックに頼らず編集後に `pnpm josh lint:related` と `pnpm josh cspell:dot` を走らせる、`JOSH_SESSION_LANG` は注入を待たずに `.env` から解決する。フックの全体は `.claude/settings.json` の `hooks` が正である
81
+ - **Skill・`/verify-ui`・`/code-review`**: `CLAUDE.md` が skill のロードやサブエージェントでの実行を指示する箇所では、その skill の `SKILL.md` を読み、自分のセッションで従う
82
+ - **`.claude/settings.json` の deny list** は効かない。それが裏付ける規則 — 指示なしのコミット・マージ・ステージングその他の共有状態変更をしない — はそのまま拘束する
83
+ - **`AskUserQuestion`** は、ユーザーに平文で尋ねることを指す
@@ -29,6 +29,7 @@
29
29
  | 引き金つき配送 — 規則を効く瞬間に届ける | [`rule-delivery.md`](./collaboration-workflow/rule-delivery.md) |
30
30
  | コマンド出力が文脈へ持ち込む量の上限 | [`output-bounds.md`](./collaboration-workflow/output-bounds.md) |
31
31
  | 運用ルール | [`operating-rules.md`](./collaboration-workflow/operating-rules.md) |
32
+ | 用語集 — lane・cut・hold・park などの定義と単一ソース | [`glossary.md`](./collaboration-workflow/glossary.md) |
32
33
 
33
34
  **どの 1 本を開けばよいかを決めるのに必要なのは、この索引だけである。** バイト数はここに書かない — 手で保守した数字は本文より先に古くなり、古い数字は無いより悪い。実サイズは `ls -l prompts/collaboration-workflow/` が答える。
34
35
 
@@ -18,10 +18,11 @@ import { backlog_stalled } from './backlog-stalled'
18
18
  //
19
19
  // **"New" is measured against the watcher's own start**, so one issue wakes the parent once: the next
20
20
  // `--wait` the parent starts takes the pool as it stands then — the woken issue included, dispatched or
21
- // not — as its baseline. A reading that fails is not an arrival; the probe says nothing and the
22
- // interval still wakes the parent. A baseline that cannot be read leaves that one wait inert rather than
23
- // reading it as empty — an empty one would wake the parent for the whole pool — and the next `--wait`
24
- // reads it again.
21
+ // not — as its baseline. A reading that fails is not an arrival; the probe says nothing and reads again
22
+ // a minute later. A baseline that cannot be read is never read as empty — an empty one would wake the
23
+ // parent for the whole pool — but it is not given up on either: the scheduled report no longer ends the
24
+ // wait (joshuafolkken/kit#3102), so an inert probe would leave the parent without a wake until `--hours`. The
25
+ // probe reads the baseline again each minute until one answers.
25
26
  //
26
27
  // **Every read is bounded.** The probe is awaited inside the watcher's tick loop, so a `gh` call that
27
28
  // hangs would otherwise freeze the loop — its `--hours` bound, its liveness ping and the exit that is
@@ -116,19 +117,40 @@ async function never_arrives(): Promise<boolean> {
116
117
 
117
118
  const INERT: ArrivalProbe = { has_arrived: never_arrives }
118
119
 
119
- // Only the next-probe instant changes between probes, and it is set before the read is awaited.
120
+ // A probe spent on the baseline reports no arrival, whether or not the baseline answered.
121
+ async function retry_baseline(
122
+ answered: Array<ReadonlySet<string>>,
123
+ ports: ReadyPorts,
124
+ ): Promise<boolean> {
125
+ const late = await read_baseline(ports)
126
+
127
+ if (late !== undefined) answered.push(late)
128
+
129
+ return false
130
+ }
131
+
132
+ // The next-probe instant is set before any read is awaited. A baseline that did not answer is read again
133
+ // at the next probe instead of the reading, and the first one that answers is the baseline from then on
134
+ // — never an arrival itself, since it is the pool as it stands.
120
135
  function probe_of(
121
- baseline: ReadonlySet<string>,
136
+ initial: ReadonlySet<string> | undefined,
122
137
  first_ms: number,
123
138
  ports: ReadyPorts,
124
139
  ): ArrivalProbe {
125
140
  let next_ms = first_ms
141
+ // At most one entry, held in a list so the baseline that answers after an await is added rather than
142
+ // reassigned over the value read before it.
143
+ const answered: Array<ReadonlySet<string>> = initial === undefined ? [] : [initial]
126
144
 
127
145
  async function has_arrived(now_ms: number): Promise<boolean> {
128
146
  if (now_ms < next_ms) return false
129
147
 
130
148
  next_ms = now_ms + PROBE_INTERVAL_MS
131
149
 
150
+ const [baseline] = answered
151
+
152
+ if (baseline === undefined) return await retry_baseline(answered, ports)
153
+
132
154
  const reading = await read_reading(ports)
133
155
 
134
156
  return reading !== undefined && arrivals(baseline, reading).length > NONE
@@ -146,12 +168,10 @@ async function start(
146
168
  ): Promise<ArrivalProbe> {
147
169
  if (!(await is_parent_safely(is_parent))) return INERT
148
170
 
149
- const baseline = await read_baseline(ports)
150
-
151
- return baseline === undefined ? INERT : probe_of(baseline, now_ms + PROBE_INTERVAL_MS, ports)
171
+ return probe_of(await read_baseline(ports), now_ms + PROBE_INTERVAL_MS, ports)
152
172
  }
153
173
 
154
- const backlog_arrival = { INERT, PROBE_INTERVAL_MS, answered_issues, arrivals, start }
174
+ const backlog_arrival = { PROBE_INTERVAL_MS, answered_issues, arrivals, start }
155
175
 
156
176
  export type { ArrivalProbe }
157
177
  export { backlog_arrival }