patchwork-os 0.2.0-beta.3 → 0.2.0-beta.5

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 (185) hide show
  1. package/README.md +95 -25
  2. package/dist/activityLog.js +2 -1
  3. package/dist/activityLog.js.map +1 -1
  4. package/dist/approvalHttp.js +25 -8
  5. package/dist/approvalHttp.js.map +1 -1
  6. package/dist/approvalQueue.d.ts +44 -1
  7. package/dist/approvalQueue.js +117 -0
  8. package/dist/approvalQueue.js.map +1 -1
  9. package/dist/automation.d.ts +3 -3
  10. package/dist/automation.js +12 -5
  11. package/dist/automation.js.map +1 -1
  12. package/dist/bridge.js +29 -1
  13. package/dist/bridge.js.map +1 -1
  14. package/dist/bridgeLockDiscovery.js +2 -1
  15. package/dist/bridgeLockDiscovery.js.map +1 -1
  16. package/dist/claudeOrchestrator.js +27 -10
  17. package/dist/claudeOrchestrator.js.map +1 -1
  18. package/dist/commands/dashboard.js +8 -1
  19. package/dist/commands/dashboard.js.map +1 -1
  20. package/dist/commands/install.js +3 -0
  21. package/dist/commands/install.js.map +1 -1
  22. package/dist/commands/patchworkInit.js +4 -1
  23. package/dist/commands/patchworkInit.js.map +1 -1
  24. package/dist/commitIssueLinkLog.d.ts +16 -0
  25. package/dist/commitIssueLinkLog.js +87 -4
  26. package/dist/commitIssueLinkLog.js.map +1 -1
  27. package/dist/config.d.ts +20 -1
  28. package/dist/config.js +42 -4
  29. package/dist/config.js.map +1 -1
  30. package/dist/connectorRoutes.js +1 -1
  31. package/dist/connectorRoutes.js.map +1 -1
  32. package/dist/connectors/asana.js +4 -3
  33. package/dist/connectors/asana.js.map +1 -1
  34. package/dist/connectors/confluence.js +35 -0
  35. package/dist/connectors/confluence.js.map +1 -1
  36. package/dist/connectors/datadog.js +33 -4
  37. package/dist/connectors/datadog.js.map +1 -1
  38. package/dist/connectors/discord.js +5 -4
  39. package/dist/connectors/discord.js.map +1 -1
  40. package/dist/connectors/gitlab.js +7 -1
  41. package/dist/connectors/gitlab.js.map +1 -1
  42. package/dist/connectors/mcpOAuth.js +71 -6
  43. package/dist/connectors/mcpOAuth.js.map +1 -1
  44. package/dist/connectors/slack.d.ts +1 -1
  45. package/dist/connectors/slack.js +56 -4
  46. package/dist/connectors/slack.js.map +1 -1
  47. package/dist/connectors/tokenStorage.js +10 -4
  48. package/dist/connectors/tokenStorage.js.map +1 -1
  49. package/dist/decisionTraceLog.d.ts +28 -0
  50. package/dist/decisionTraceLog.js +115 -7
  51. package/dist/decisionTraceLog.js.map +1 -1
  52. package/dist/drivers/claude/subprocess.js +22 -3
  53. package/dist/drivers/claude/subprocess.js.map +1 -1
  54. package/dist/drivers/gemini/index.js +19 -3
  55. package/dist/drivers/gemini/index.js.map +1 -1
  56. package/dist/extensionClient.d.ts +29 -4
  57. package/dist/extensionClient.js +26 -11
  58. package/dist/extensionClient.js.map +1 -1
  59. package/dist/featureFlags.js +18 -32
  60. package/dist/featureFlags.js.map +1 -1
  61. package/dist/fileLockSync.d.ts +67 -0
  62. package/dist/fileLockSync.js +126 -0
  63. package/dist/fileLockSync.js.map +1 -0
  64. package/dist/fp/automationInterpreter.d.ts +6 -0
  65. package/dist/fp/automationInterpreter.js +15 -2
  66. package/dist/fp/automationInterpreter.js.map +1 -1
  67. package/dist/fp/automationState.d.ts +1 -1
  68. package/dist/fp/automationState.js +10 -0
  69. package/dist/fp/automationState.js.map +1 -1
  70. package/dist/fp/commandDescription.js +7 -1
  71. package/dist/fp/commandDescription.js.map +1 -1
  72. package/dist/fsWatchWithFallback.d.ts +36 -0
  73. package/dist/fsWatchWithFallback.js +127 -0
  74. package/dist/fsWatchWithFallback.js.map +1 -0
  75. package/dist/index.js +108 -48
  76. package/dist/index.js.map +1 -1
  77. package/dist/installGuard.js +6 -2
  78. package/dist/installGuard.js.map +1 -1
  79. package/dist/lockfile.js +27 -3
  80. package/dist/lockfile.js.map +1 -1
  81. package/dist/patchworkConfig.js +8 -3
  82. package/dist/patchworkConfig.js.map +1 -1
  83. package/dist/pluginLoader.js +10 -1
  84. package/dist/pluginLoader.js.map +1 -1
  85. package/dist/pluginWatcher.js +6 -13
  86. package/dist/pluginWatcher.js.map +1 -1
  87. package/dist/preToolUseHook.js +3 -2
  88. package/dist/preToolUseHook.js.map +1 -1
  89. package/dist/processTree.d.ts +34 -0
  90. package/dist/processTree.js +105 -0
  91. package/dist/processTree.js.map +1 -0
  92. package/dist/prompts.js +3 -3
  93. package/dist/prompts.js.map +1 -1
  94. package/dist/recipeOrchestration.js +58 -8
  95. package/dist/recipeOrchestration.js.map +1 -1
  96. package/dist/recipeRoutes.d.ts +1 -0
  97. package/dist/recipeRoutes.js +100 -15
  98. package/dist/recipeRoutes.js.map +1 -1
  99. package/dist/recipes/connectorPreflight.js +64 -0
  100. package/dist/recipes/connectorPreflight.js.map +1 -1
  101. package/dist/recipes/idempotencyKey.js +3 -4
  102. package/dist/recipes/idempotencyKey.js.map +1 -1
  103. package/dist/recipes/installer.js +48 -2
  104. package/dist/recipes/installer.js.map +1 -1
  105. package/dist/recipes/parser.js +82 -4
  106. package/dist/recipes/parser.js.map +1 -1
  107. package/dist/recipes/scheduler.d.ts +17 -0
  108. package/dist/recipes/scheduler.js +33 -1
  109. package/dist/recipes/scheduler.js.map +1 -1
  110. package/dist/recipes/yamlRunner.d.ts +4 -1
  111. package/dist/recipes/yamlRunner.js +18 -6
  112. package/dist/recipes/yamlRunner.js.map +1 -1
  113. package/dist/resources.js +21 -13
  114. package/dist/resources.js.map +1 -1
  115. package/dist/runLog.js +14 -3
  116. package/dist/runLog.js.map +1 -1
  117. package/dist/sanitizeParsedJson.d.ts +39 -0
  118. package/dist/sanitizeParsedJson.js +55 -0
  119. package/dist/sanitizeParsedJson.js.map +1 -0
  120. package/dist/server.d.ts +14 -0
  121. package/dist/server.js +105 -33
  122. package/dist/server.js.map +1 -1
  123. package/dist/sessionCheckpoint.d.ts +8 -0
  124. package/dist/sessionCheckpoint.js +18 -2
  125. package/dist/sessionCheckpoint.js.map +1 -1
  126. package/dist/tools/detectUnusedCode.js +9 -7
  127. package/dist/tools/detectUnusedCode.js.map +1 -1
  128. package/dist/tools/editText.js +2 -1
  129. package/dist/tools/editText.js.map +1 -1
  130. package/dist/tools/fileOperations.js +2 -1
  131. package/dist/tools/fileOperations.js.map +1 -1
  132. package/dist/tools/fileWatcher.js +8 -2
  133. package/dist/tools/fileWatcher.js.map +1 -1
  134. package/dist/tools/fixAllLintErrors.js +10 -5
  135. package/dist/tools/fixAllLintErrors.js.map +1 -1
  136. package/dist/tools/formatDocument.js +10 -5
  137. package/dist/tools/formatDocument.js.map +1 -1
  138. package/dist/tools/handoffNote.js +2 -1
  139. package/dist/tools/handoffNote.js.map +1 -1
  140. package/dist/tools/headless/lspClient.js +3 -0
  141. package/dist/tools/headless/lspClient.js.map +1 -1
  142. package/dist/tools/index.js +0 -6
  143. package/dist/tools/index.js.map +1 -1
  144. package/dist/tools/lsp.js +17 -0
  145. package/dist/tools/lsp.js.map +1 -1
  146. package/dist/tools/openDiff.js +4 -1
  147. package/dist/tools/openDiff.js.map +1 -1
  148. package/dist/tools/openFile.js +4 -1
  149. package/dist/tools/openFile.js.map +1 -1
  150. package/dist/tools/organizeImports.js +5 -3
  151. package/dist/tools/organizeImports.js.map +1 -1
  152. package/dist/tools/previewEdit.js +7 -2
  153. package/dist/tools/previewEdit.js.map +1 -1
  154. package/dist/tools/refactorExtractFunction.js +4 -1
  155. package/dist/tools/refactorExtractFunction.js.map +1 -1
  156. package/dist/tools/refactorPreview.js +10 -2
  157. package/dist/tools/refactorPreview.js.map +1 -1
  158. package/dist/tools/replaceBlock.js +2 -1
  159. package/dist/tools/replaceBlock.js.map +1 -1
  160. package/dist/tools/searchAndReplace.js +2 -1
  161. package/dist/tools/searchAndReplace.js.map +1 -1
  162. package/dist/tools/spawnWorkspace.js +15 -7
  163. package/dist/tools/spawnWorkspace.js.map +1 -1
  164. package/dist/tools/transaction.js +4 -1
  165. package/dist/tools/transaction.js.map +1 -1
  166. package/dist/tools/utils.js +62 -5
  167. package/dist/tools/utils.js.map +1 -1
  168. package/dist/transport.d.ts +1 -1
  169. package/dist/transport.js +18 -4
  170. package/dist/transport.js.map +1 -1
  171. package/dist/winShim.d.ts +34 -0
  172. package/dist/winShim.js +94 -0
  173. package/dist/winShim.js.map +1 -0
  174. package/dist/writeFileAtomic.d.ts +23 -0
  175. package/dist/writeFileAtomic.js +94 -0
  176. package/dist/writeFileAtomic.js.map +1 -0
  177. package/package.json +1 -1
  178. package/scripts/postinstall.mjs +18 -5
  179. package/scripts/smoke/run-all.mjs +55 -4
  180. package/scripts/start-all.mjs +60 -1
  181. package/scripts/start-all.ps1 +209 -209
  182. package/scripts/start-orchestrator.ps1 +158 -158
  183. package/dist/tools/ccRoutines.d.ts +0 -221
  184. package/dist/tools/ccRoutines.js +0 -264
  185. package/dist/tools/ccRoutines.js.map +0 -1
package/dist/index.js CHANGED
@@ -41,7 +41,10 @@ import { Bridge } from "./bridge.js";
41
41
  import { isBridgeToolsFileValid, repairBridgeToolsRulesIfStale, } from "./bridgeToolsRules.js";
42
42
  import { findEditor, parseConfig } from "./config.js";
43
43
  import { detectWorkspaceSymlinkInstall, PATCHWORK_PACKAGE_NAME, SYMLINK_INSTALL_FIX, } from "./installGuard.js";
44
+ import { treeKill } from "./processTree.js";
44
45
  import { PACKAGE_VERSION, semverGt } from "./version.js";
46
+ import { ensureCmdShim } from "./winShim.js";
47
+ import { writeFileAtomicSync } from "./writeFileAtomic.js";
45
48
  const __dirnameTop = path.dirname(fileURLToPath(import.meta.url));
46
49
  // Warn when a symlinked global install is detected (`npm install -g .`).
47
50
  // launchctl / sandbox environments can fail through that link with EPERM.
@@ -190,8 +193,12 @@ if (process.argv[2] === "--help" ||
190
193
  ` recipe --help Full recipe subcommand index\n\n` +
191
194
  `Diagnose\n` +
192
195
  ` halts [--window 1h|24h|overnight|7d] Morning summary of recent recipe halts\n` +
196
+ ` judgments [--window ...] [--recipe N] Recent judge-step verdicts across runs\n` +
193
197
  ` traces export Bundle approval / recipe / decision traces\n` +
194
198
  ` print-token [--port N] Print the active bridge auth token\n\n` +
199
+ `Safety\n` +
200
+ ` kill-switch <engage|release|status> Block / resume write-tier tools across bridges\n` +
201
+ ` panic [--reason "..."] Shorthand for kill-switch engage\n\n` +
195
202
  `Daemon (no subcommand)\n` +
196
203
  ` --workspace <dir> Start the bridge in foreground\n` +
197
204
  ` --watch Auto-restart supervisor\n` +
@@ -1760,11 +1767,22 @@ if (process.argv[2] === "kill-switch") {
1760
1767
  // form is `kill-switch engage`; this alias matches it so shell history six
1761
1768
  // months later still makes sense. Does not accept sub-verbs — just runs engage.
1762
1769
  if (process.argv[2] === "panic") {
1770
+ const extra = process.argv.slice(3); // e.g. --reason "..." --force-local
1771
+ // Guard against `panic --help` engaging the kill switch — a real
1772
+ // footgun if you tab-completed the verb to confirm syntax before
1773
+ // committing to the action. `panic` is an alias, so we honor --help
1774
+ // here ourselves rather than forwarding to kill-switch engage.
1775
+ if (extra.includes("--help") || extra.includes("-h")) {
1776
+ console.log('Usage: patchwork panic [--reason "..."] [--force-local]\n\n' +
1777
+ " Alias for `patchwork kill-switch engage` — blocks all write-tier\n" +
1778
+ " tool calls across every running bridge. Use --reason to leave a\n" +
1779
+ " note in the audit trail. Release with `patchwork kill-switch release`.\n");
1780
+ process.exit(0);
1781
+ }
1763
1782
  // Spawn self with kill-switch engage to reuse the full handler without
1764
1783
  // duplicating 200+ LOC. Passes through any flags (--reason, --force-local).
1765
1784
  import("node:child_process").then(({ spawnSync }) => {
1766
1785
  const self = process.argv[1] ?? process.execPath;
1767
- const extra = process.argv.slice(3); // e.g. --reason "..." --force-local
1768
1786
  const result = spawnSync(process.execPath, [self, "kill-switch", "engage", ...extra], { stdio: "inherit" });
1769
1787
  process.exit(result.status ?? 1);
1770
1788
  });
@@ -1831,14 +1849,6 @@ if (process.argv[2] === "halts") {
1831
1849
  process.stderr.write("No running bridge found. Start one with `patchwork start` (or `--driver subprocess`).\n");
1832
1850
  process.exit(2);
1833
1851
  }
1834
- // Single-bridge default: query the first. Multi-bridge users will
1835
- // typically have one orchestrator anyway; expanding to fan-out is a
1836
- // follow-up if needed.
1837
- const lock = liveLocks[0];
1838
- if (!lock) {
1839
- process.stderr.write("No running bridge found.\n");
1840
- process.exit(2);
1841
- }
1842
1852
  const sinceMs = windowSinceMs(window);
1843
1853
  const params = [];
1844
1854
  if (sinceMs != null)
@@ -1846,20 +1856,35 @@ if (process.argv[2] === "halts") {
1846
1856
  if (recipeFilter)
1847
1857
  params.push(`recipe=${encodeURIComponent(recipeFilter)}`);
1848
1858
  const qs = params.length > 0 ? `?${params.join("&")}` : "";
1849
- const controller = new AbortController();
1850
- const timer = setTimeout(() => controller.abort(), 10_000);
1851
- let res;
1852
- try {
1853
- res = await fetch(`http://127.0.0.1:${lock.port}/runs/halt-summary${qs}`, {
1854
- headers: { Authorization: `Bearer ${lock.authToken}` },
1855
- signal: controller.signal,
1856
- });
1857
- }
1858
- finally {
1859
- clearTimeout(timer);
1859
+ // Walk live bridges in order; first responsive one wins. See the
1860
+ // matching block in the `judgments` handler — findAllLiveBridges
1861
+ // can include stale entries when a recycled PID still answers
1862
+ // `kill(pid, 0)` but the lock points at a dead bridge.
1863
+ let res = null;
1864
+ let lastStatus = 0;
1865
+ for (const lock of liveLocks) {
1866
+ const controller = new AbortController();
1867
+ const timer = setTimeout(() => controller.abort(), 10_000);
1868
+ try {
1869
+ const candidate = await fetch(`http://127.0.0.1:${lock.port}/runs/halt-summary${qs}`, {
1870
+ headers: { Authorization: `Bearer ${lock.authToken}` },
1871
+ signal: controller.signal,
1872
+ });
1873
+ if (candidate.ok) {
1874
+ res = candidate;
1875
+ break;
1876
+ }
1877
+ lastStatus = candidate.status;
1878
+ }
1879
+ catch {
1880
+ /* unreachable lock — try next */
1881
+ }
1882
+ finally {
1883
+ clearTimeout(timer);
1884
+ }
1860
1885
  }
1861
- if (!res.ok) {
1862
- process.stderr.write(`Bridge returned ${res.status} for /runs/halt-summary\n`);
1886
+ if (!res) {
1887
+ process.stderr.write(`No live bridge served /runs/halt-summary (last status: ${lastStatus || "unreachable"}).\n`);
1863
1888
  process.exit(1);
1864
1889
  }
1865
1890
  const summary = (await res.json());
@@ -1971,11 +1996,6 @@ if (process.argv[2] === "judgments") {
1971
1996
  process.stderr.write("No running bridge found. Start one with `patchwork start` (or `--driver subprocess`).\n");
1972
1997
  process.exit(2);
1973
1998
  }
1974
- const lock = liveLocks[0];
1975
- if (!lock) {
1976
- process.stderr.write("No running bridge found.\n");
1977
- process.exit(2);
1978
- }
1979
1999
  const sinceMs = windowSinceMs(window);
1980
2000
  const params = [];
1981
2001
  if (sinceMs != null)
@@ -1983,20 +2003,37 @@ if (process.argv[2] === "judgments") {
1983
2003
  if (recipeFilter)
1984
2004
  params.push(`recipe=${encodeURIComponent(recipeFilter)}`);
1985
2005
  const qs = params.length > 0 ? `?${params.join("&")}` : "";
1986
- const controller = new AbortController();
1987
- const timer = setTimeout(() => controller.abort(), 10_000);
1988
- let res;
1989
- try {
1990
- res = await fetch(`http://127.0.0.1:${lock.port}/runs/judge-summary${qs}`, {
1991
- headers: { Authorization: `Bearer ${lock.authToken}` },
1992
- signal: controller.signal,
1993
- });
1994
- }
1995
- finally {
1996
- clearTimeout(timer);
2006
+ // Walk live bridges in order; the first responsive one wins.
2007
+ // findAllLiveBridges uses `kill(pid, 0)` for liveness, which
2008
+ // returns true for any recycled PID — so liveLocks can contain
2009
+ // stale entries from dead bridges. Previously we picked [0]
2010
+ // unconditionally and surfaced a confusing 404; now we try each
2011
+ // and only fall through to the error path when *all* fail.
2012
+ let res = null;
2013
+ let lastStatus = 0;
2014
+ for (const lock of liveLocks) {
2015
+ const controller = new AbortController();
2016
+ const timer = setTimeout(() => controller.abort(), 10_000);
2017
+ try {
2018
+ const candidate = await fetch(`http://127.0.0.1:${lock.port}/runs/judge-summary${qs}`, {
2019
+ headers: { Authorization: `Bearer ${lock.authToken}` },
2020
+ signal: controller.signal,
2021
+ });
2022
+ if (candidate.ok) {
2023
+ res = candidate;
2024
+ break;
2025
+ }
2026
+ lastStatus = candidate.status;
2027
+ }
2028
+ catch {
2029
+ /* unreachable lock — try next */
2030
+ }
2031
+ finally {
2032
+ clearTimeout(timer);
2033
+ }
1997
2034
  }
1998
- if (!res.ok) {
1999
- process.stderr.write(`Bridge returned ${res.status} for /runs/judge-summary\n`);
2035
+ if (!res) {
2036
+ process.stderr.write(`No live bridge served /runs/judge-summary (last status: ${lastStatus || "unreachable"}).\n`);
2000
2037
  process.exit(1);
2001
2038
  }
2002
2039
  const summary = (await res.json());
@@ -2630,9 +2667,12 @@ Steps performed:
2630
2667
  }
2631
2668
  if (extensionArg2) {
2632
2669
  try {
2670
+ // Windows: editor binaries (code/cursor/windsurf) are `.cmd` shims that
2671
+ // Node's execFileSync can't launch without a shell. See bridgeProcess.ts.
2633
2672
  execFileSync(editor, ["--install-extension", extensionArg2], {
2634
2673
  stdio: "pipe",
2635
2674
  timeout: 30000,
2675
+ shell: process.platform === "win32",
2636
2676
  });
2637
2677
  process.stderr.write(` ✓ Extension installed via ${editor}\n\n`);
2638
2678
  }
@@ -2761,13 +2801,18 @@ Steps performed:
2761
2801
  process.stderr.write(` ✓ MCP shim — already registered in ${claudeJsonAbs}\n\n`);
2762
2802
  }
2763
2803
  else {
2804
+ // claude -p spawns the stdio command via Node's child_process, which
2805
+ // can't resolve a bare `.cmd` shim on Windows. Record the `.cmd` form
2806
+ // on win32 so the bridge binary is findable by the spawned process.
2764
2807
  mcpServers["claude-ide-bridge"] = {
2765
- command: "claude-ide-bridge",
2808
+ command: ensureCmdShim("claude-ide-bridge"),
2766
2809
  args: ["shim"],
2767
2810
  type: "stdio",
2768
2811
  };
2769
2812
  claudeJson.mcpServers = mcpServers;
2770
- writeFileSync(claudeJsonAbs, `${JSON.stringify(claudeJson, null, 2)}\n`);
2813
+ // Atomic — `~/.claude.json` holds every MCP server registration on
2814
+ // the machine. A crash mid-write would brick Claude Code globally.
2815
+ writeFileAtomicSync(claudeJsonAbs, `${JSON.stringify(claudeJson, null, 2)}\n`);
2771
2816
  process.stderr.write(` ✓ MCP shim — registered in ${claudeJsonAbs}\n Note: bridge tools are wired via ~/.claude.json (global), not .mcp.json.\n This is intentional — when VS Code/Windsurf/Cursor launches Claude Code it\n injects --mcp-config which overrides any project .mcp.json. Only ~/.claude.json\n is always loaded. You do not need to add anything to .mcp.json.\n\n`);
2772
2817
  }
2773
2818
  }
@@ -2818,7 +2863,9 @@ Steps performed:
2818
2863
  }
2819
2864
  if (added.length > 0 || migrated.length > 0) {
2820
2865
  ccSettings.hooks = ccHooks;
2821
- writeFileSync(ccSettingsPath, `${JSON.stringify(ccSettings, null, 2)}\n`);
2866
+ // Atomic — `~/.claude/settings.json` holds every CC hook entry; a
2867
+ // crash mid-write loses the user's full hook configuration.
2868
+ writeFileAtomicSync(ccSettingsPath, `${JSON.stringify(ccSettings, null, 2)}\n`);
2822
2869
  const addMsg = added.length > 0
2823
2870
  ? ` ✓ CC hooks — wired ${added.length} automation hook(s) in ${ccSettingsPath}\n Added: ${added.join(", ")}\n`
2824
2871
  : "";
@@ -2859,6 +2906,9 @@ Steps performed:
2859
2906
  execFileSync("claude-ide-bridge", ["--version"], {
2860
2907
  stdio: "pipe",
2861
2908
  timeout: 5000,
2909
+ // Windows: global npm bin is a `.cmd` shim that Node's execFileSync
2910
+ // can't launch without a shell. See bridgeProcess.ts for context.
2911
+ shell: process.platform === "win32",
2862
2912
  });
2863
2913
  shimOnPath = true;
2864
2914
  }
@@ -2937,8 +2987,11 @@ Steps performed:
2937
2987
  ? fallbackDocs
2938
2988
  : null;
2939
2989
  if (target) {
2940
- const { exec } = await import("node:child_process");
2941
- exec(`code "${target}"`, { timeout: 3000 }, () => { });
2990
+ // Use execFile with argv (no shell) — exec(`code "${target}"`) was
2991
+ // shell-evaluated and could be injected via `--workspace '"; ...'`
2992
+ // since path.resolve preserves shell metachars. Audit 2026-05-17.
2993
+ const { execFile } = await import("node:child_process");
2994
+ execFile("code", [target], { timeout: 3000 }, () => { });
2942
2995
  }
2943
2996
  }
2944
2997
  // Analytics opt-in prompt — only ask once; skip if preference already set
@@ -3055,9 +3108,11 @@ else if (process.argv[2] === "install-extension") {
3055
3108
  }
3056
3109
  try {
3057
3110
  process.stderr.write(`Installing extension via ${editor}...\n`);
3111
+ // Windows: editor binaries are `.cmd` shims; need shell for resolution.
3058
3112
  execFileSync(editor, ["--install-extension", extensionArg], {
3059
3113
  stdio: "inherit",
3060
3114
  timeout: 30000,
3115
+ shell: process.platform === "win32",
3061
3116
  });
3062
3117
  process.stderr.write("Extension installed successfully.\n");
3063
3118
  }
@@ -3401,7 +3456,12 @@ else {
3401
3456
  for (const sig of ["SIGTERM", "SIGINT"]) {
3402
3457
  process.once(sig, () => {
3403
3458
  stopping = true;
3404
- child.kill(sig);
3459
+ // Use treeKill so grandchildren (recipe runners, claude
3460
+ // subprocesses, extension watchers) are reaped on Windows.
3461
+ // Bare `child.kill(sig)` maps to TerminateProcess on win32
3462
+ // and skips descendants → orphaned processes survive a
3463
+ // supervisor SIGTERM. Audit 2026-05-17.
3464
+ treeKill(child, sig);
3405
3465
  });
3406
3466
  }
3407
3467
  child.on("exit", (code, signal) => {
@@ -3439,12 +3499,12 @@ else {
3439
3499
  if (!isSourceBuild) {
3440
3500
  import("node:child_process")
3441
3501
  .then(({ exec }) => {
3442
- exec("npm view claude-ide-bridge version", { timeout: 5000 }, (err, stdout) => {
3502
+ exec("npm view patchwork-os version", { timeout: 5000 }, (err, stdout) => {
3443
3503
  if (err || !stdout)
3444
3504
  return;
3445
3505
  const latest = stdout.trim();
3446
3506
  if (latest && semverGt(latest, PACKAGE_VERSION)) {
3447
- console.log(`\n Bridge v${latest} available — run: npm update -g claude-ide-bridge\n`);
3507
+ console.log(`\n Patchwork OS v${latest} available — run: npm update -g patchwork-os\n`);
3448
3508
  }
3449
3509
  });
3450
3510
  })