@wrongstack/core 0.313.0 → 0.313.1

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 (182) hide show
  1. package/dist/chronicle/index.js +84 -6391
  2. package/dist/chronicle/project-server.js +84 -4868
  3. package/dist/chunk-2BUUIMQB.js +225 -0
  4. package/dist/chunk-2FK4WTFF.js +207 -0
  5. package/dist/chunk-2FQL4PDC.js +292 -0
  6. package/dist/chunk-2KKODIQU.js +2256 -0
  7. package/dist/chunk-2YU4WVBW.js +134 -0
  8. package/dist/chunk-3E244UDA.js +356 -0
  9. package/dist/chunk-3XPVB7BA.js +135 -0
  10. package/dist/chunk-4LJJYBAU.js +388 -0
  11. package/dist/chunk-4VWO3ZXL.js +342 -0
  12. package/dist/chunk-4XV6IR37.js +187 -0
  13. package/dist/chunk-4ZHTKJHM.js +183 -0
  14. package/dist/chunk-52RTVH4M.js +361 -0
  15. package/dist/chunk-57GEWK5X.js +26 -0
  16. package/dist/chunk-5DSMEVA4.js +931 -0
  17. package/dist/chunk-5RP3QTRL.js +1223 -0
  18. package/dist/chunk-5S437PRC.js +50 -0
  19. package/dist/chunk-5XDUGTKN.js +511 -0
  20. package/dist/chunk-62V2AFVG.js +422 -0
  21. package/dist/chunk-65RDIOR2.js +2470 -0
  22. package/dist/chunk-6AKOTGLM.js +597 -0
  23. package/dist/chunk-7ZHZE5EO.js +28 -0
  24. package/dist/chunk-A455ATZB.js +140 -0
  25. package/dist/chunk-AD7ZFZCS.js +46 -0
  26. package/dist/chunk-AI4LLQ2U.js +856 -0
  27. package/dist/chunk-ASX6AW36.js +789 -0
  28. package/dist/chunk-AT4V4XGI.js +3522 -0
  29. package/dist/chunk-AWNQ6ED6.js +311 -0
  30. package/dist/chunk-B4AMLZPB.js +9 -0
  31. package/dist/chunk-BAKVJLEV.js +417 -0
  32. package/dist/chunk-BJHVULHO.js +55 -0
  33. package/dist/chunk-CB2HFKTP.js +9 -0
  34. package/dist/chunk-CE4ZOCOI.js +1640 -0
  35. package/dist/chunk-CQQL4ZOQ.js +151 -0
  36. package/dist/chunk-DEIQMBD2.js +65 -0
  37. package/dist/chunk-E6I5RXVK.js +58 -0
  38. package/dist/chunk-EFEHDQGY.js +1589 -0
  39. package/dist/chunk-EODDVJBM.js +82 -0
  40. package/dist/chunk-EPC3GZD4.js +308 -0
  41. package/dist/chunk-FAYRX2AD.js +13 -0
  42. package/dist/chunk-FFSX2QDQ.js +362 -0
  43. package/dist/chunk-FHAMQS7Q.js +5856 -0
  44. package/dist/chunk-FIV4IJL7.js +2263 -0
  45. package/dist/chunk-FNXVT7HF.js +52 -0
  46. package/dist/chunk-FSWCWSTS.js +395 -0
  47. package/dist/chunk-FW5LDDMJ.js +16464 -0
  48. package/dist/chunk-FXQLIX3K.js +211 -0
  49. package/dist/chunk-G5AAKEEZ.js +228 -0
  50. package/dist/chunk-G5S3S5ZN.js +38 -0
  51. package/dist/chunk-GAZEWQEZ.js +231 -0
  52. package/dist/chunk-GFJOMSHG.js +125 -0
  53. package/dist/chunk-GFXZTHNI.js +158 -0
  54. package/dist/chunk-GM54GJBU.js +960 -0
  55. package/dist/chunk-GPQ3NJPR.js +900 -0
  56. package/dist/chunk-GTCU6DZ3.js +1 -0
  57. package/dist/chunk-H5E77KUJ.js +15 -0
  58. package/dist/chunk-H72H4XDU.js +1023 -0
  59. package/dist/chunk-HA4ZYA6T.js +178 -0
  60. package/dist/chunk-I45KAW2N.js +5020 -0
  61. package/dist/chunk-IQ35HMQL.js +1396 -0
  62. package/dist/chunk-IYFBFX34.js +86 -0
  63. package/dist/chunk-JJPMWPB6.js +109 -0
  64. package/dist/chunk-JTHKOYWU.js +663 -0
  65. package/dist/chunk-JVGPR3TS.js +365 -0
  66. package/dist/chunk-JVKB2TBL.js +48 -0
  67. package/dist/chunk-JX7VBZDK.js +31 -0
  68. package/dist/chunk-K3G6JBUC.js +5232 -0
  69. package/dist/chunk-K4YUNKU7.js +589 -0
  70. package/dist/chunk-KBZGE6LN.js +710 -0
  71. package/dist/chunk-KDQJCT2J.js +45 -0
  72. package/dist/chunk-KI4L2DUL.js +560 -0
  73. package/dist/chunk-KJ2ZTQSL.js +47 -0
  74. package/dist/chunk-KSJ5UTP7.js +154 -0
  75. package/dist/chunk-LHOA5DWA.js +28 -0
  76. package/dist/chunk-LX5QWLBL.js +27 -0
  77. package/dist/chunk-LZDDLNCZ.js +279 -0
  78. package/dist/chunk-M2WF6AXG.js +115 -0
  79. package/dist/chunk-M7BS2K6U.js +1 -0
  80. package/dist/chunk-NDBIPFNY.js +102 -0
  81. package/dist/chunk-NEMIWEXC.js +1199 -0
  82. package/dist/chunk-NFAYHPRH.js +323 -0
  83. package/dist/chunk-O37BQ65S.js +21 -0
  84. package/dist/chunk-O74MJHXU.js +187 -0
  85. package/dist/chunk-OJZEX73F.js +112 -0
  86. package/dist/chunk-PAAWLLZX.js +74 -0
  87. package/dist/chunk-PFXFQQRT.js +339 -0
  88. package/dist/chunk-PFZO3C6H.js +14 -0
  89. package/dist/chunk-PHH7LD45.js +113 -0
  90. package/dist/chunk-QRTG4XTI.js +188 -0
  91. package/dist/chunk-QVOVMXJR.js +1351 -0
  92. package/dist/chunk-RCQYT2I4.js +757 -0
  93. package/dist/chunk-RD6DEE36.js +1178 -0
  94. package/dist/chunk-RJIZEJ42.js +30 -0
  95. package/dist/chunk-RPOA7IAE.js +150 -0
  96. package/dist/chunk-SN2DQRL6.js +1172 -0
  97. package/dist/chunk-SQJVFEBV.js +41 -0
  98. package/dist/chunk-SQP64RCS.js +5391 -0
  99. package/dist/chunk-SVT4RNTR.js +49 -0
  100. package/dist/chunk-SWUFN7XM.js +43 -0
  101. package/dist/chunk-SXMVYC5H.js +174 -0
  102. package/dist/chunk-SZ6AYUIZ.js +23 -0
  103. package/dist/chunk-T3KGZBU5.js +3171 -0
  104. package/dist/chunk-TBRL2RCC.js +9 -0
  105. package/dist/chunk-TRT2HRWZ.js +18 -0
  106. package/dist/chunk-TYW2GI5I.js +1077 -0
  107. package/dist/chunk-UIWONWVG.js +4959 -0
  108. package/dist/chunk-VB3H5WWB.js +151 -0
  109. package/dist/chunk-W4A6QA3D.js +258 -0
  110. package/dist/chunk-WBPVKEEE.js +227 -0
  111. package/dist/chunk-WEBMSQP2.js +123 -0
  112. package/dist/chunk-WOEZV3Q4.js +333 -0
  113. package/dist/chunk-WQQON3SV.js +473 -0
  114. package/dist/chunk-WQTOVMGV.js +119 -0
  115. package/dist/chunk-WZO23AWO.js +2779 -0
  116. package/dist/chunk-XBDP4QPW.js +7 -0
  117. package/dist/chunk-XEBHSJLT.js +103 -0
  118. package/dist/chunk-XQ2ECNK3.js +1 -0
  119. package/dist/chunk-XQ2XGIBF.js +2086 -0
  120. package/dist/chunk-Y2GE5TSI.js +355 -0
  121. package/dist/chunk-YBUJCZ57.js +351 -0
  122. package/dist/chunk-YNCVSFGF.js +149 -0
  123. package/dist/chunk-YUPMXOGP.js +282 -0
  124. package/dist/chunk-YY6APYUM.js +49 -0
  125. package/dist/chunk-Z2IJUO45.js +691 -0
  126. package/dist/chunk-ZEHUCJHY.js +129 -0
  127. package/dist/chunk-ZYU2YOJ6.js +1496 -0
  128. package/dist/chunk-ZZ4V3WIZ.js +163 -0
  129. package/dist/coordination/agents/index.js +145 -6260
  130. package/dist/coordination/index.js +365 -34324
  131. package/dist/coordination/mailbox-project-server.js +48 -3276
  132. package/dist/coordination/provider-status-tracker.d.ts +4 -0
  133. package/dist/core/agent.d.ts +14 -1
  134. package/dist/core/index.js +267 -12480
  135. package/dist/core/model-ref.js +5 -23
  136. package/dist/core/provider-runner.d.ts +36 -1
  137. package/dist/design/index.js +50 -1087
  138. package/dist/execution/index.d.ts +1 -1
  139. package/dist/execution/index.js +1599 -21888
  140. package/dist/execution/prompt-enhancer.js +16 -535
  141. package/dist/execution/retry-policy.d.ts +31 -1
  142. package/dist/extension/index.js +7 -311
  143. package/dist/goal/index.js +33 -3435
  144. package/dist/hooks/index.js +14 -1138
  145. package/dist/hq/index.js +112 -5139
  146. package/dist/hq/protocol.js +21 -668
  147. package/dist/index.js +1410 -92656
  148. package/dist/infrastructure/index.js +64 -4811
  149. package/dist/kernel/events/provider-events.d.ts +15 -0
  150. package/dist/kernel/index.js +14 -992
  151. package/dist/models/index.js +40 -1117
  152. package/dist/notifications/index.js +3 -108
  153. package/dist/observability/index.js +17 -279
  154. package/dist/plugin/index.js +584 -14835
  155. package/dist/prompts/index.js +14 -948
  156. package/dist/registry/index.js +9 -822
  157. package/dist/registry-R23UBE7H.js +14 -0
  158. package/dist/replay/index.js +6 -84
  159. package/dist/review-finding-store-DKAV3AS6.js +21 -0
  160. package/dist/review-report-store-OWY3EC3N.js +19 -0
  161. package/dist/security/index.js +114 -2202
  162. package/dist/session-catalog/index.js +36 -2103
  163. package/dist/session-catalog/project-server.js +42 -1435
  164. package/dist/skills/index.js +35 -1578
  165. package/dist/storage/index.js +182 -15600
  166. package/dist/tasking/index.js +10 -676
  167. package/dist/tools/index.js +52 -9590
  168. package/dist/types/config/autonomy.d.ts +5 -0
  169. package/dist/types/config/ui.d.ts +1 -1
  170. package/dist/types/errors.d.ts +18 -0
  171. package/dist/types/index.js +121 -1342
  172. package/dist/types/provider-runner.d.ts +8 -0
  173. package/dist/types/session-markers.js +8 -144
  174. package/dist/utils/error.js +3 -4
  175. package/dist/utils/expect-defined.js +3 -9
  176. package/dist/utils/heap-watchdog.js +6 -706
  177. package/dist/utils/index.js +269 -5685
  178. package/dist/utils/sage-output-block.js +5 -42
  179. package/dist/wiring/proxy-rewrite.d.ts +18 -0
  180. package/dist/wiring/proxy-rewrite.js +18 -121
  181. package/dist/worktree/index.js +6 -887
  182. package/package.json +4 -4
@@ -1,890 +1,9 @@
1
- // src/worktree/worktree-manager.ts
2
- import { mkdir, readFile } from "node:fs/promises";
3
- import { join, resolve as resolve3, sep as sep3 } from "node:path";
4
-
5
- // src/utils/error.ts
6
- function toErrorMessage(err) {
7
- return err instanceof Error ? err.message : String(err);
8
- }
9
-
10
- // src/worktree/worktree-git.ts
11
- import { spawn } from "node:child_process";
12
- import { resolve, sep } from "node:path";
13
-
14
- // src/utils/child-env.ts
15
- var ALLOWED_KEYS = /* @__PURE__ */ new Set([
16
- "PATH",
17
- "HOME",
18
- "USER",
19
- "USERNAME",
20
- "LOGNAME",
21
- "SHELL",
22
- "LANG",
23
- "LC_ALL",
24
- "LC_CTYPE",
25
- "TERM",
26
- "TZ",
27
- "TMPDIR",
28
- "TEMP",
29
- "TMP",
30
- "PWD",
31
- "OLDPWD",
32
- "COMSPEC",
33
- "SYSTEMROOT",
34
- "SYSTEMDRIVE",
35
- "WINDIR",
36
- "PROGRAMFILES",
37
- "PROGRAMFILES(X86)",
38
- "PROGRAMDATA",
39
- "APPDATA",
40
- "LOCALAPPDATA",
41
- "USERPROFILE",
42
- "PUBLIC",
43
- "PATHEXT"
44
- ]);
45
- var SECRET_NAME_PARTS = [
46
- "TOKEN",
47
- "SECRET",
48
- "PASSWORD",
49
- "PASSWD",
50
- // WRONGSTACK_VAULT_PASSPHRASE is the vault KEK. Without this entry it matched
51
- // no rule here and fell through to the `WRONGSTACK_` prefix allow-rule, so the
52
- // key that protects the credential vault was forwarded to every child process
53
- // — including the agent's own exec/bash tools and MCP stdio servers (WS-033).
54
- "PASSPHRASE",
55
- "AUTH",
56
- "CRED",
57
- "BEARER",
58
- "COOKIE",
59
- "PRIVATE"
60
- ];
61
- function looksSecret(name) {
62
- const upper = name.toUpperCase();
63
- for (const p of SECRET_NAME_PARTS) {
64
- if (upper.includes(p)) return true;
65
- }
66
- if (/(?:^|_)KEY(?:$|_|S$)/i.test(upper)) return true;
67
- if (/API[_-]?KEY/i.test(upper)) return true;
68
- if (/ACCESS[_-]?KEY/i.test(upper)) return true;
69
- if (/SESSION[_-]?ID/i.test(upper) === false && /SESSION/i.test(upper)) {
70
- return true;
71
- }
72
- return false;
73
- }
74
- function valueHasEmbeddedCredential(value) {
75
- return /\b[a-z][a-z0-9+.-]*:\/\/[^/\s:@]*:[^/\s@]+@/i.test(value);
76
- }
77
- var NODE_OPTIONS_INJECTION_FLAG = /^(?:--require|-r|--import|--loader|--experimental-loader)$/;
78
- var NODE_OPTIONS_INJECTION_FLAG_EQ = /^(?:--require|-r|--import|--loader|--experimental-loader)=/;
79
- function sanitizeNodeOptions(value) {
80
- const tokens = value.split(/\s+/).filter(Boolean);
81
- const kept = [];
82
- for (let i = 0; i < tokens.length; i++) {
83
- const tok = tokens[i];
84
- if (NODE_OPTIONS_INJECTION_FLAG_EQ.test(tok)) continue;
85
- if (NODE_OPTIONS_INJECTION_FLAG.test(tok)) {
86
- i++;
87
- continue;
88
- }
89
- kept.push(tok);
90
- }
91
- return kept.join(" ");
92
- }
93
- var gitIdentity = null;
94
- function buildChildEnv(optsOrSessionId) {
95
- const opts = typeof optsOrSessionId === "string" ? { sessionId: optsOrSessionId } : optsOrSessionId ?? {};
96
- const hasOwn = Object.hasOwn(process.env, "WRONGSTACK_CHILD_ENV_PASSTHROUGH");
97
- const legacyHasOwn = Object.hasOwn(process.env, "WRONGSTACK_BASH_ENV_PASSTHROUGH");
98
- const passthrough = hasOwn && process.env["WRONGSTACK_CHILD_ENV_PASSTHROUGH"] === "1" || legacyHasOwn && process.env["WRONGSTACK_BASH_ENV_PASSTHROUGH"] === "1";
99
- if (passthrough && !process.env["CI"]) {
100
- console.warn(
101
- "[agent] WARNING: WRONGSTACK_*_ENV_PASSTHROUGH=1 is active \u2014\n all parent env vars (including API keys) forwarded to child processes.\n Do not use on shared or multi-tenant systems."
102
- );
103
- }
104
- const out = {};
105
- const nodeEnvDefaulted = process.env["WRONGSTACK_NODE_ENV_DEFAULTED"] === "1";
106
- for (const [k, v] of Object.entries(process.env)) {
107
- if (v === void 0) continue;
108
- if (nodeEnvDefaulted && (k === "NODE_ENV" || k === "WRONGSTACK_NODE_ENV_DEFAULTED")) continue;
109
- if (passthrough) {
110
- out[k] = v;
111
- continue;
112
- }
113
- const upper = k.toUpperCase();
114
- if (valueHasEmbeddedCredential(v)) continue;
115
- if (ALLOWED_KEYS.has(upper)) {
116
- out[k] = v;
117
- continue;
118
- }
119
- if (looksSecret(upper)) continue;
120
- if (upper === "NODE_OPTIONS") {
121
- const sanitized = sanitizeNodeOptions(v);
122
- if (sanitized) out[k] = sanitized;
123
- continue;
124
- }
125
- if (upper.startsWith("NODE_") || upper.startsWith("NPM_") || upper.startsWith("PNPM_") || upper.startsWith("YARN_") || upper.startsWith("GIT_") || upper.startsWith("CI") || upper.startsWith("XDG_") || // Our own non-secret knobs (WRONGSTACK_HOME, WRONGSTACK_SESSION_ID, …).
126
- // Secrets never live in WRONGSTACK_* env vars (they're in the encrypted
127
- // vault). Forwarding keeps child wstack processes — e.g. ones spawned
128
- // by the test suite — inside the same redirected global root.
129
- upper.startsWith("WRONGSTACK_") || upper === "EDITOR" || upper === "VISUAL" || upper === "PAGER") {
130
- out[k] = v;
131
- }
132
- }
133
- if (gitIdentity) {
134
- if (gitIdentity.name) {
135
- out["GIT_AUTHOR_NAME"] = gitIdentity.name;
136
- out["GIT_COMMITTER_NAME"] = gitIdentity.name;
137
- }
138
- if (gitIdentity.email) {
139
- out["GIT_AUTHOR_EMAIL"] = gitIdentity.email;
140
- out["GIT_COMMITTER_EMAIL"] = gitIdentity.email;
141
- }
142
- }
143
- if (opts.extra) {
144
- Object.assign(out, opts.extra);
145
- }
146
- if (opts.sessionId) out["WRONGSTACK_SESSION_ID"] = opts.sessionId;
147
- return out;
148
- }
149
-
150
- // src/worktree/worktree-git.ts
151
- var MAX_SLUG = 40;
152
- function makeSlug(hint, usedSlugs) {
153
- let base = hint.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^[-.]+/, "").replace(/[-.]+$/, "").slice(0, MAX_SLUG).replace(/[-.]+$/, "");
154
- if (!base) base = "wt";
155
- let slug = `${base}-${crypto.randomUUID().slice(0, 6)}`;
156
- while (usedSlugs.has(slug)) slug = `${base}-${crypto.randomUUID().slice(0, 6)}`;
157
- usedSlugs.add(slug);
158
- return slug;
159
- }
160
- async function collectStats(runGit, dir) {
161
- const sha = (await runGit(["rev-parse", "HEAD"], dir)).stdout.trim();
162
- const numstat = await runGit(["show", "--numstat", "--format=", "HEAD"], dir);
163
- let insertions = 0;
164
- let deletions = 0;
165
- let files = 0;
166
- for (const line of numstat.stdout.split("\n")) {
167
- const m = line.trim().match(/^(\d+|-)\t(\d+|-)\t(.+)$/);
168
- if (!m) continue;
169
- files++;
170
- if (m[1] !== "-") insertions += Number(m[1]);
171
- if (m[2] !== "-") deletions += Number(m[2]);
172
- }
173
- return { insertions, deletions, files, sha };
174
- }
175
- async function identityArgs(runGit, cwd) {
176
- const name = (await runGit(["config", "user.name"], cwd)).stdout.trim();
177
- const email = (await runGit(["config", "user.email"], cwd)).stdout.trim();
178
- if (name && email) return [];
179
- return ["-c", `user.name=${name || "Goal"}`, "-c", `user.email=${email || "goal@agent.local"}`];
180
- }
181
- function defaultRun(gitBin, args, cwd) {
182
- return new Promise((res) => {
183
- let stdout = "";
184
- let stderr = "";
185
- const MAX_GIT_OUTPUT = 1e6;
186
- const child = spawn(gitBin, args, {
187
- cwd,
188
- env: buildChildEnv(),
189
- stdio: ["ignore", "pipe", "pipe"],
190
- signal: AbortSignal.timeout(3e4),
191
- windowsHide: true
192
- });
193
- child.stdout?.on("data", (c) => {
194
- if (stdout.length < MAX_GIT_OUTPUT) stdout += c.toString();
195
- });
196
- child.stderr?.on("data", (c) => {
197
- if (stderr.length < MAX_GIT_OUTPUT) stderr += c.toString();
198
- });
199
- child.on("error", (err) => res({ code: 1, stdout, stderr: err.message }));
200
- child.on("close", (code) => res({ code: code ?? 1, stdout, stderr }));
201
- });
202
- }
203
- function parseConflictPaths(output) {
204
- const paths = /* @__PURE__ */ new Set();
205
- for (const line of output.split("\n")) {
206
- const m = line.match(/^CONFLICT \([^)]*\): Merge conflict in (.+?)\s*$/);
207
- if (m?.[1]) paths.add(m[1]);
208
- }
209
- return [...paths];
210
- }
211
- function assertSafePath(dir, projectRoot) {
212
- const root = resolve(projectRoot);
213
- const abs = resolve(dir);
214
- if (abs !== root && !abs.startsWith(root + sep)) {
215
- throw new Error(`worktree path escapes project root: ${dir}`);
216
- }
217
- }
218
-
219
- // src/worktree/worktree-managed-ops.ts
220
- import { resolve as resolve2, sep as sep2 } from "node:path";
221
- async function cleanupAllManaged(ctx) {
222
- const root = resolve2(ctx.worktreesRoot);
223
- let removed = 0;
224
- try {
225
- const listed = await ctx.runGit(["worktree", "list", "--porcelain"], ctx.projectRoot);
226
- for (const line of listed.stdout.split("\n")) {
227
- const m = line.match(/^worktree\s+(.+?)\s*$/);
228
- if (!m?.[1]) continue;
229
- const dir = resolve2(m[1]);
230
- if (dir !== root && (dir === root || dir.startsWith(root + sep2))) {
231
- const rm = await ctx.runGit(["worktree", "remove", "--force", dir], ctx.projectRoot);
232
- if (rm.code === 0) removed++;
233
- }
234
- }
235
- } catch {
236
- }
237
- try {
238
- const branches = await ctx.runGit(
239
- ["branch", "--list", "--format=%(refname:short)", "wstack/ap/*"],
240
- ctx.projectRoot
241
- );
242
- for (const b of branches.stdout.split("\n").map((s) => s.trim()).filter(Boolean)) {
243
- await ctx.runGit(["branch", "-D", b], ctx.projectRoot);
244
- }
245
- } catch {
246
- }
247
- await ctx.runGit(["worktree", "prune"], ctx.projectRoot).catch(() => void 0);
248
- ctx.handles.clear();
249
- ctx.usedSlugs.clear();
250
- ctx.emit("worktree.released", {
251
- handleId: "cleanup-all",
252
- ownerId: "cleanup-all",
253
- branch: "wstack/ap/*",
254
- kept: false
255
- });
256
- return { removed };
257
- }
258
- async function listManaged(ctx) {
259
- const root = resolve2(ctx.worktreesRoot);
260
- const worktrees = [];
261
- try {
262
- const listed = await ctx.runGit(["worktree", "list", "--porcelain"], ctx.projectRoot);
263
- let curDir = null;
264
- let curBranch;
265
- const flush = () => {
266
- if (curDir) {
267
- const d = resolve2(curDir);
268
- if (d !== root && d.startsWith(root + sep2)) {
269
- worktrees.push({ dir: curDir, branch: curBranch });
270
- }
271
- }
272
- curDir = null;
273
- curBranch = void 0;
274
- };
275
- for (const line of listed.stdout.split("\n")) {
276
- if (line.startsWith("worktree ")) {
277
- flush();
278
- curDir = line.slice("worktree ".length).trim();
279
- } else if (line.startsWith("branch ")) {
280
- curBranch = line.slice("branch ".length).trim().replace(/^refs\/heads\//, "");
281
- } else if (line.trim() === "") {
282
- flush();
283
- }
284
- }
285
- flush();
286
- } catch {
287
- }
288
- let branches = [];
289
- try {
290
- const b = await ctx.runGit(
291
- ["branch", "--list", "--format=%(refname:short)", "wstack/ap/*"],
292
- ctx.projectRoot
293
- );
294
- branches = b.stdout.split("\n").map((s) => s.trim()).filter(Boolean);
295
- } catch {
296
- }
297
- return { worktrees, branches };
298
- }
299
- async function removeOne(ctx, dir, branch) {
300
- const root = resolve2(ctx.worktreesRoot);
301
- const abs = resolve2(dir);
302
- if (abs === root || !abs.startsWith(root + sep2)) {
303
- return { removed: false };
304
- }
305
- let removed = false;
306
- try {
307
- const rm = await ctx.runGit(["worktree", "remove", "--force", abs], ctx.projectRoot);
308
- removed = rm.code === 0;
309
- } catch {
310
- }
311
- if (branch && !branch.startsWith("-")) {
312
- await ctx.runGit(["branch", "-D", "--", branch], ctx.projectRoot).catch(() => void 0);
313
- }
314
- await ctx.runGit(["worktree", "prune"], ctx.projectRoot).catch(() => void 0);
315
- for (const [ownerId, h] of [...ctx.handles]) {
316
- if (resolve2(h.dir) === abs) ctx.handles.delete(ownerId);
317
- }
318
- return { removed };
319
- }
320
- async function cleanupStale(ctx) {
321
- const root = resolve2(ctx.worktreesRoot);
322
- let detected = 0;
323
- try {
324
- const listed = await ctx.runGit(["worktree", "list", "--porcelain"], ctx.projectRoot);
325
- for (const line of listed.stdout.split("\n")) {
326
- const m = line.match(/^worktree\s+(.+?)\s*$/);
327
- if (!m?.[1]) continue;
328
- const dir = resolve2(m[1]);
329
- if (dir !== root && (dir === root || dir.startsWith(root + sep2))) {
330
- detected++;
331
- }
332
- }
333
- } catch {
334
- return { removed: 0, detected: 0 };
335
- }
336
- if (detected === 0) {
337
- try {
338
- const branches = await ctx.runGit(
339
- ["branch", "--list", "--format=%(refname:short)", "wstack/ap/*"],
340
- ctx.projectRoot
341
- );
342
- detected += branches.stdout.split("\n").map((s) => s.trim()).filter(Boolean).length;
343
- } catch {
344
- }
345
- }
346
- if (detected === 0) {
347
- return { removed: 0, detected: 0 };
348
- }
349
- const { removed } = await cleanupAllManaged(ctx);
350
- return { removed, detected };
351
- }
352
-
353
- // src/worktree/worktree-manager.ts
354
- var WorktreeManager = class {
355
- projectRoot;
356
- events;
357
- sessionIdSource;
358
- gitBin;
359
- runGit;
360
- /** Keyed by ownerId. */
361
- handles = /* @__PURE__ */ new Map();
362
- usedSlugs = /* @__PURE__ */ new Set();
363
- constructor(opts) {
364
- this.projectRoot = resolve3(opts.projectRoot);
365
- this.events = opts.events;
366
- this.sessionIdSource = opts.sessionId;
367
- this.gitBin = opts.gitBin ?? "git";
368
- this.runGit = opts.run ?? ((args, cwd) => this.defaultRun(args, cwd));
369
- }
370
- /** Create a fresh worktree + branch forked from the current base branch. */
371
- async allocate(ownerId, opts = {}) {
372
- const existing = this.handles.get(ownerId);
373
- if (existing && (existing.status === "allocating" || existing.status === "active")) {
374
- return existing;
375
- }
376
- const slug = this.makeSlug(opts.slugHint ?? ownerId);
377
- const branch = `wstack/ap/${slug}`;
378
- const dir = join(this.worktreesRoot(), slug);
379
- const absDir = resolve3(dir);
380
- const absRoot = resolve3(this.projectRoot);
381
- if (!absDir.startsWith(absRoot + sep3)) {
382
- throw new Error(`Worktree dir "${absDir}" resolves outside project root`);
383
- }
384
- const baseBranch = opts.baseBranch ?? await this.detectBaseBranch();
385
- const handle = {
386
- id: slug,
387
- ownerId,
388
- ownerLabel: opts.ownerLabel ?? opts.slugHint ?? ownerId,
389
- slug,
390
- dir,
391
- branch,
392
- baseBranch,
393
- status: "allocating",
394
- createdAt: Date.now(),
395
- updatedAt: Date.now(),
396
- insertions: 0,
397
- deletions: 0,
398
- files: 0
399
- };
400
- this.handles.set(ownerId, handle);
401
- try {
402
- await mkdir(this.worktreesRoot(), { recursive: true });
403
- const res = await this.runGit(
404
- ["worktree", "add", "-b", branch, dir, baseBranch],
405
- this.projectRoot
406
- );
407
- if (res.code !== 0) {
408
- return this.fail(handle, res.stderr || "git worktree add failed");
409
- }
410
- } catch (err) {
411
- return this.fail(handle, toErrorMessage(err));
412
- }
413
- this.setStatus(handle, "active");
414
- this.emit("worktree.allocated", {
415
- handleId: handle.id,
416
- ownerId: handle.ownerId,
417
- ownerLabel: handle.ownerLabel,
418
- slug: handle.slug,
419
- dir: handle.dir,
420
- branch: handle.branch,
421
- baseBranch: handle.baseBranch
422
- });
423
- return handle;
424
- }
425
- /** Stage everything and commit inside the worktree. */
426
- async commitAll(handle, message) {
427
- this.setStatus(handle, "committing");
428
- await this.runGit(["add", "-A"], handle.dir);
429
- const staged = await this.runGit(["diff", "--cached", "--quiet"], handle.dir);
430
- if (staged.code === 0) {
431
- this.emitCommitted(handle, false);
432
- return { committed: false };
433
- }
434
- const idArgs = await this.identityArgs(handle.dir);
435
- const committed = await this.runGit([...idArgs, "commit", "-m", message], handle.dir);
436
- if (committed.code !== 0) {
437
- this.fail(handle, committed.stderr || "git commit failed");
438
- return { committed: false };
439
- }
440
- const stats = await this.collectStats(handle.dir);
441
- handle.insertions = stats.insertions;
442
- handle.deletions = stats.deletions;
443
- handle.files = stats.files;
444
- handle.sha = stats.sha;
445
- handle.updatedAt = Date.now();
446
- this.emitCommitted(handle, true);
447
- return { committed: true };
448
- }
449
- /** Merge the worktree branch back into the base branch (squash by default). */
450
- async merge(handle, opts = {}) {
451
- const squash = opts.squash ?? true;
452
- this.setStatus(handle, "merging");
453
- const status = await this.runGit(
454
- ["status", "--porcelain", "--untracked-files=no"],
455
- this.projectRoot
456
- );
457
- if (status.stdout.trim().length > 0) {
458
- const reason = "working tree has uncommitted changes \u2014 commit or stash first";
459
- this.fail(handle, reason);
460
- return { ok: false, stderr: reason };
461
- }
462
- const checkout = await this.runGit(["checkout", handle.baseBranch], this.projectRoot);
463
- if (checkout.code !== 0) {
464
- this.fail(handle, checkout.stderr || `checkout ${handle.baseBranch} failed`);
465
- return { ok: false, stderr: checkout.stderr };
466
- }
467
- const mergeArgs = squash ? ["merge", "--squash", handle.branch] : ["merge", "--no-ff", handle.branch];
468
- const merged = await this.runGit(mergeArgs, this.projectRoot);
469
- const fromOutput = parseConflictPaths(`${merged.stdout}
470
- ${merged.stderr}`);
471
- const fromIndex = await this.unmergedFiles();
472
- const conflictFiles = [.../* @__PURE__ */ new Set([...fromOutput, ...fromIndex])];
473
- if (merged.code !== 0 || conflictFiles.length > 0) {
474
- if (opts.resolve) {
475
- const finalized = await this.tryResolveConflict(handle, conflictFiles, opts);
476
- if (finalized) return finalized;
477
- }
478
- await this.runGit(["reset", "--hard", "HEAD"], this.projectRoot);
479
- handle.conflictFiles = conflictFiles;
480
- this.setStatus(handle, "needs-review", { lastError: merged.stderr });
481
- this.emit("worktree.conflict", {
482
- handleId: handle.id,
483
- ownerId: handle.ownerId,
484
- branch: handle.branch,
485
- conflictFiles
486
- });
487
- return { ok: false, conflict: true, conflictFiles, stderr: merged.stderr };
488
- }
489
- if (squash) {
490
- const msg = opts.message ?? `merge ${handle.branch} (squash)`;
491
- const idArgs = await this.identityArgs(this.projectRoot);
492
- const commit = await this.runGit([...idArgs, "commit", "-m", msg], this.projectRoot);
493
- if (commit.code !== 0 && !/nothing to commit/i.test(commit.stdout + commit.stderr)) {
494
- this.fail(handle, commit.stderr || "squash commit failed");
495
- return { ok: false, stderr: commit.stderr };
496
- }
497
- }
498
- this.setStatus(handle, "merged");
499
- this.emit("worktree.merged", {
500
- handleId: handle.id,
501
- ownerId: handle.ownerId,
502
- branch: handle.branch,
503
- baseBranch: handle.baseBranch,
504
- squash
505
- });
506
- return { ok: true };
507
- }
508
- /**
509
- * Current tip SHA of a handle's base branch (without checking it out). Capture
510
- * this before a merge so a regressed merge can be reverted to exactly this
511
- * commit — unambiguous even when a squash produced no diff. Returns null on
512
- * failure (caller then skips the revert).
513
- */
514
- async baseHead(handle) {
515
- const res = await this.runGit(["rev-parse", handle.baseBranch], this.projectRoot);
516
- const sha = res.stdout.trim();
517
- return res.code === 0 && sha ? sha : null;
518
- }
519
- /**
520
- * Hard-reset the base branch back to `sha` (a value previously returned by
521
- * {@link baseHead}). Used to undo a squash-merge whose integrated result failed
522
- * re-verification, so an auto-resolved-but-broken merge never sticks on base.
523
- * Safe because SDD merges are serialized — no other commit lands in between.
524
- */
525
- async revertBaseTo(handle, sha) {
526
- const status = await this.runGit(["status", "--porcelain"], this.projectRoot);
527
- if (status.code !== 0) return false;
528
- const hasTrackedDirt = status.stdout.split("\n").some((line) => line.trim().length > 0 && !line.startsWith("??"));
529
- if (hasTrackedDirt) return false;
530
- const co = await this.runGit(["checkout", handle.baseBranch], this.projectRoot);
531
- if (co.code !== 0) return false;
532
- const reset = await this.runGit(["reset", "--hard", sha], this.projectRoot);
533
- return reset.code === 0;
534
- }
535
- /**
536
- * Current base branch + tip SHA, captured WITHOUT a handle. The SDD run calls
537
- * this once at start so a later rollback knows which branch the run's squash
538
- * commits landed on. Returns null when not in a usable git state.
539
- */
540
- async currentBase() {
541
- const branch = await this.detectBaseBranch();
542
- const head = await this.runGit(["rev-parse", "HEAD"], this.projectRoot);
543
- const sha = head.stdout.trim();
544
- return head.code === 0 && sha ? { branch, sha } : null;
545
- }
546
- /**
547
- * Force-remove EVERY managed worktree + branch this project owns, without
548
- * relying on the in-memory `handles` map — so it works post-run (a fresh
549
- * manager can clean up a previous run's leftovers). Enumerates
550
- * `git worktree list --porcelain`, removes every checkout living under the
551
- * `.wrongstack/worktrees` root, deletes every `wstack/ap/*` branch, then prunes.
552
- * Returns the number of worktrees removed. Never throws — best-effort cleanup.
553
- */
554
- async cleanupAllManaged() {
555
- return cleanupAllManaged(this.managedOpsContext());
556
- }
557
- /**
558
- * Read-only inventory of this project's managed git artifacts: every worktree
559
- * checkout under the `.wrongstack/worktrees` root (with its branch, when the
560
- * checkout has one) plus every `wstack/ap/*` branch. Unlike `cleanupStale` /
561
- * `cleanupAllManaged` this removes nothing — it powers the WebUI worktree panel
562
- * so the user can SEE orphans before deciding to clean them. Never throws.
563
- */
564
- async listManaged() {
565
- return listManaged(this.managedOpsContext());
566
- }
567
- /**
568
- * Force-remove ONE managed worktree + (optionally) its branch — the targeted
569
- * counterpart to `cleanupAllManaged`. Used by the WebUI worktree panel's
570
- * per-row Remove/Discard. Path-guarded so only checkouts under the project's
571
- * worktrees root can be removed. Best-effort; never throws.
572
- */
573
- async removeOne(dir, branch) {
574
- return removeOne(this.managedOpsContext(), dir, branch);
575
- }
576
- /**
577
- * Squash-merge an arbitrary `wstack/ap/*` branch into the base branch from the
578
- * main checkout — the handle-free counterpart to {@link merge}, used by the
579
- * WebUI worktree panel's per-row "Merge to base". On conflict it hard-resets
580
- * the base tree (never leaves it dirty) and reports the conflicted paths.
581
- * Refuses on a dirty base tree so uncommitted work is never clobbered.
582
- */
583
- async mergeBranch(branch, baseBranch) {
584
- const base = baseBranch ?? await this.detectBaseBranch();
585
- if (branch.startsWith("-") || base.startsWith("-")) {
586
- return { ok: false, reason: "invalid ref" };
587
- }
588
- const status = await this.runGit(["status", "--porcelain"], this.projectRoot);
589
- if (status.stdout.trim().length > 0) {
590
- return { ok: false, reason: "working tree has uncommitted changes \u2014 commit or stash first" };
591
- }
592
- const co = await this.runGit(["checkout", base], this.projectRoot);
593
- if (co.code !== 0) {
594
- return { ok: false, reason: co.stderr || `checkout ${base} failed` };
595
- }
596
- const merged = await this.runGit(["merge", "--squash", branch], this.projectRoot);
597
- if (merged.code !== 0) {
598
- const fromOutput = parseConflictPaths(`${merged.stdout}
599
- ${merged.stderr}`);
600
- const fromIndex = await this.unmergedFiles();
601
- const conflictFiles = [.../* @__PURE__ */ new Set([...fromOutput, ...fromIndex])];
602
- await this.runGit(["reset", "--hard", "HEAD"], this.projectRoot).catch(() => void 0);
603
- return {
604
- ok: false,
605
- conflict: true,
606
- conflictFiles,
607
- reason: merged.stderr || "merge conflict"
608
- };
609
- }
610
- const idArgs = await this.identityArgs(this.projectRoot);
611
- const commit = await this.runGit(
612
- [...idArgs, "commit", "-m", `merge ${branch} (squash)`],
613
- this.projectRoot
614
- );
615
- if (commit.code !== 0 && !/nothing to commit/i.test(commit.stdout + commit.stderr)) {
616
- return { ok: false, reason: commit.stderr || "squash commit failed" };
617
- }
618
- return { ok: true };
619
- }
620
- /**
621
- * Compact change summary for a worktree checkout: working-tree edits
622
- * (numstat vs HEAD) + commit count ahead of `baseBranch`. Powers the panel's
623
- * "View changes" without streaming a full diff. Never throws.
624
- */
625
- async diffSummary(dir, baseBranch) {
626
- const files = [];
627
- let insertions = 0;
628
- let deletions = 0;
629
- let commits = 0;
630
- try {
631
- const numstat = await this.runGit(["diff", "--numstat", "HEAD"], dir);
632
- for (const line of numstat.stdout.split("\n")) {
633
- const m = line.trim().match(/^(\d+|-)\t(\d+|-)\t(.+)$/);
634
- if (!m) continue;
635
- const ins = m[1] === "-" ? 0 : Number(m[1]);
636
- const del = m[2] === "-" ? 0 : Number(m[2]);
637
- files.push({ path: m[3], insertions: ins, deletions: del });
638
- insertions += ins;
639
- deletions += del;
640
- }
641
- } catch {
642
- }
643
- try {
644
- const base = baseBranch ?? await this.detectBaseBranch();
645
- const count = await this.runGit(["rev-list", "--count", `${base}..HEAD`], dir);
646
- commits = Number(count.stdout.trim()) || 0;
647
- } catch {
648
- }
649
- return { files, insertions, deletions, commits };
650
- }
651
- /**
652
- * Detect and clean up stale worktrees left behind by crashed subagents.
653
- *
654
- * P2 #B6 (sprint2 audit): when a subagent crashes (OOM, SIGKILL), its
655
- * worktree checkout and branch remain on disk. `cleanupAllManaged()`
656
- * sweeps everything, but is never called automatically — only via
657
- * `/worktree clean`. This method runs on Director boot (or any other
658
- * entry point) to clean up leftovers from a previous crashed run.
659
- *
660
- * Unlike `cleanupAllManaged()`, this method is a no-op when no stale
661
- * worktrees are detected — it doesn't clear in-memory handles or emit
662
- * events unnecessarily. When stale worktrees ARE found, it delegates
663
- * to `cleanupAllManaged()` for the actual removal.
664
- *
665
- * Returns the number of stale worktrees removed (0 = clean).
666
- */
667
- async cleanupStale() {
668
- return cleanupStale(this.managedOpsContext());
669
- }
670
- /**
671
- * Undo a run's squash commits by reverting each (newest → oldest) on the base
672
- * branch — history-preserving, never a destructive reset. Refuses on a dirty
673
- * working tree (so uncommitted work is never clobbered) and aborts cleanly if a
674
- * revert conflicts, reporting which SHA. `shas` are the run commit SHAs in the
675
- * order they landed; this reverses them. Returns the count reverted.
676
- */
677
- async revertCommits(baseBranch, shas) {
678
- if (shas.length === 0) return { ok: true, reverted: 0, reason: "nothing to revert" };
679
- const status = await this.runGit(["status", "--porcelain"], this.projectRoot);
680
- if (status.stdout.trim().length > 0) {
681
- return {
682
- ok: false,
683
- reverted: 0,
684
- reason: "working tree has uncommitted changes \u2014 commit or stash first"
685
- };
686
- }
687
- const co = await this.runGit(["checkout", baseBranch], this.projectRoot);
688
- if (co.code !== 0) {
689
- return { ok: false, reverted: 0, reason: co.stderr || `checkout ${baseBranch} failed` };
690
- }
691
- const idArgs = await this.identityArgs(this.projectRoot);
692
- let reverted = 0;
693
- for (const sha of [...shas].reverse()) {
694
- const res = await this.runGit([...idArgs, "revert", "--no-edit", sha], this.projectRoot);
695
- if (res.code !== 0) {
696
- await this.runGit(["revert", "--abort"], this.projectRoot).catch(() => void 0);
697
- return {
698
- ok: false,
699
- reverted,
700
- reason: `revert of ${sha.slice(0, 8)} failed: ${(res.stderr || res.stdout).trim().split("\n")[0] ?? "conflict"}`
701
- };
702
- }
703
- reverted++;
704
- }
705
- return { ok: true, reverted };
706
- }
707
- /**
708
- * Run the caller-supplied resolver against a conflicted squash-merge, then
709
- * commit if it cleared every marker. Returns a successful `MergeResult` on a
710
- * clean resolution, or `null` to signal the caller should fall back to the
711
- * abort path. Never leaves the base tree committed-but-dirty: a partial or
712
- * failed resolution returns `null` and the caller hard-resets.
713
- */
714
- async tryResolveConflict(handle, conflictFiles, opts) {
715
- let resolved = false;
716
- try {
717
- resolved = opts.resolve ? await opts.resolve({ conflictFiles, cwd: this.projectRoot }) : false;
718
- } catch {
719
- resolved = false;
720
- }
721
- if (!resolved) return null;
722
- await this.runGit(["add", "-A"], this.projectRoot);
723
- if (await this.hasConflictMarkers(conflictFiles)) return null;
724
- const idArgs = await this.identityArgs(this.projectRoot);
725
- const msg = opts.message ?? `merge ${handle.branch} (squash, conflict resolved)`;
726
- const commit = await this.runGit([...idArgs, "commit", "-m", msg], this.projectRoot);
727
- if (commit.code !== 0 && !/nothing to commit/i.test(commit.stdout + commit.stderr)) {
728
- return null;
729
- }
730
- handle.conflictFiles = conflictFiles;
731
- this.setStatus(handle, "merged");
732
- this.emit("worktree.merged", {
733
- handleId: handle.id,
734
- ownerId: handle.ownerId,
735
- branch: handle.branch,
736
- baseBranch: handle.baseBranch,
737
- squash: true
738
- });
739
- return { ok: true, resolved: true, conflictFiles };
740
- }
741
- /**
742
- * True when staged content still carries conflict markers.
743
- *
744
- * Reads the working-tree files directly and matches a full marker line
745
- * (`<<<<<<< `, `=======`, `>>>>>>> `, `||||||| `) with a lenient regex
746
- * (`{7,}`) that stays platform- and config-independent — unlike the old
747
- * `git diff --check` (only fires when `core.whitespace` opts in) and
748
- * `git grep` (assumes the default 7-char markers).
749
- *
750
- * Scan set: when the caller supplies the conflicted paths (the normal
751
- * path from {@link tryResolveConflict}), ONLY those files are scanned so
752
- * an unrelated `=======` setext-heading underline in a staged document
753
- * can't false-positive and discard valid resolution work. The staged-file
754
- * listing (`git diff --cached --name-only`) is used only as a fallback
755
- * when no paths are given.
756
- *
757
- * CRLF caveat: conflicted files checked out with CRLF (`core.autocrlf=true`,
758
- * `.gitattributes eol=crlf`) carry bare markers as `=======\r\n`. Because
759
- * `$` in multiline mode matches only before `\n`, carriage returns are
760
- * stripped before matching so `\r` never defeats the end-of-line anchor.
761
- */
762
- async hasConflictMarkers(files) {
763
- const listed = files ?? [];
764
- const paths = listed.length > 0 ? listed : (await this.runGit(["diff", "--cached", "--name-only", "-z"], this.projectRoot)).stdout.split("\0").filter(Boolean);
765
- const marker = /^(?:<{7,}(?: |$)|={7,}$|>{7,}(?: |$)|\|{7,}(?: |$))/m;
766
- const startMarker = /^(?:<{7,}(?: |$)|\|{7,}(?: |$))/m;
767
- for (const rel of paths) {
768
- try {
769
- const content = (await readFile(join(this.projectRoot, rel), "utf8")).replace(/\r/g, "");
770
- const lines = content.split("\n");
771
- let seenStart = false;
772
- for (const line of lines) {
773
- if (!marker.test(line)) continue;
774
- if (line.startsWith("=====")) {
775
- if (!seenStart) continue;
776
- } else if (startMarker.test(line)) {
777
- seenStart = true;
778
- }
779
- return true;
780
- }
781
- } catch {
782
- }
783
- }
784
- return false;
785
- }
786
- /**
787
- * Remove the worktree + branch. Conflicted/failed handles (or `keep:true`)
788
- * are left on disk for inspection.
789
- */
790
- async release(handle, opts = {}) {
791
- const keep = opts.keep || handle.status === "needs-review" || handle.status === "failed";
792
- if (!keep) {
793
- await this.runGit(["worktree", "remove", "--force", handle.dir], this.projectRoot);
794
- await this.runGit(["branch", "-D", handle.branch], this.projectRoot);
795
- await this.runGit(["worktree", "prune"], this.projectRoot);
796
- this.handles.delete(handle.ownerId);
797
- }
798
- this.emit("worktree.released", {
799
- handleId: handle.id,
800
- ownerId: handle.ownerId,
801
- branch: handle.branch,
802
- kept: keep
803
- });
804
- }
805
- get(ownerId) {
806
- return this.handles.get(ownerId);
807
- }
808
- list() {
809
- return [...this.handles.values()];
810
- }
811
- // ── internals ────────────────────────────────────────────────────────────
812
- worktreesRoot() {
813
- return join(this.projectRoot, ".wrongstack", "worktrees");
814
- }
815
- async detectBaseBranch() {
816
- const head = await this.runGit(["rev-parse", "--abbrev-ref", "HEAD"], this.projectRoot);
817
- const name = head.stdout.trim();
818
- if (name && name !== "HEAD") return name;
819
- const sha = await this.runGit(["rev-parse", "HEAD"], this.projectRoot);
820
- return sha.stdout.trim() || "HEAD";
821
- }
822
- makeSlug(hint) {
823
- return makeSlug(hint, this.usedSlugs);
824
- }
825
- async collectStats(dir) {
826
- return collectStats(this.runGit, dir);
827
- }
828
- async identityArgs(cwd) {
829
- return identityArgs(this.runGit, cwd);
830
- }
831
- async unmergedFiles() {
832
- const res = await this.runGit(["diff", "--name-only", "--diff-filter=U"], this.projectRoot);
833
- return res.stdout.split("\n").map((s) => s.trim()).filter(Boolean);
834
- }
835
- emitCommitted(handle, committed) {
836
- this.emit("worktree.committed", {
837
- handleId: handle.id,
838
- ownerId: handle.ownerId,
839
- branch: handle.branch,
840
- committed,
841
- insertions: handle.insertions,
842
- deletions: handle.deletions,
843
- files: handle.files,
844
- sha: handle.sha
845
- });
846
- }
847
- fail(handle, error) {
848
- this.setStatus(handle, "failed", { lastError: error });
849
- this.emit("worktree.failed", {
850
- handleId: handle.id,
851
- ownerId: handle.ownerId,
852
- branch: handle.branch,
853
- error
854
- });
855
- return handle;
856
- }
857
- setStatus(handle, status, patch) {
858
- handle.status = status;
859
- handle.updatedAt = Date.now();
860
- if (patch) Object.assign(handle, patch);
861
- }
862
- emit(event, payload) {
863
- const sessionId = this.currentSessionId();
864
- this.events?.emit(
865
- event,
866
- sessionId && payload && typeof payload === "object" ? { ...payload, sessionId } : payload
867
- );
868
- }
869
- currentSessionId() {
870
- const value = typeof this.sessionIdSource === "function" ? this.sessionIdSource() : this.sessionIdSource;
871
- return typeof value === "string" && value.length > 0 ? value : void 0;
872
- }
873
- defaultRun(args, cwd) {
874
- return defaultRun(this.gitBin, args, cwd);
875
- }
876
- /** Bundle of manager state the handle-free managed-artifact ops operate on. */
877
- managedOpsContext() {
878
- return {
879
- runGit: this.runGit,
880
- projectRoot: this.projectRoot,
881
- worktreesRoot: this.worktreesRoot(),
882
- handles: this.handles,
883
- usedSlugs: this.usedSlugs,
884
- emit: (event, payload) => this.emit(event, payload)
885
- };
886
- }
887
- };
1
+ import {
2
+ WorktreeManager,
3
+ assertSafePath
4
+ } from "../chunk-RCQYT2I4.js";
5
+ import "../chunk-VB3H5WWB.js";
6
+ import "../chunk-TBRL2RCC.js";
888
7
  export {
889
8
  WorktreeManager,
890
9
  assertSafePath