opencode-swarm 7.144.3 → 7.144.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 (127) hide show
  1. package/.opencode/skills/swarm-pr-review/SKILL.md +3 -1
  2. package/.opencode/skills/swarm-pr-review/references/lane-output-recoverability.md +28 -0
  3. package/.opencode/skills/swarm-pr-review/references/prompt-templates.md +2 -0
  4. package/README.md +3 -1
  5. package/dist/background/pending-delegations.d.ts +14 -0
  6. package/dist/background/workspace-snapshot.d.ts +7 -0
  7. package/dist/cli/{capability-probe-f1paszww.js → capability-probe-976tt4ba.js} +6 -2
  8. package/dist/cli/{config-doctor-pkr9w2nv.js → config-doctor-m005xfdt.js} +3 -3
  9. package/dist/cli/{core-7pymdx0b.js → core-6vdtfb8s.js} +4 -3
  10. package/dist/cli/{curation-policy-y1pqpztb.js → curation-policy-a511r0qz.js} +6 -6
  11. package/dist/cli/{curator-wbzcjd0q.js → curator-0t87anp5.js} +35 -34
  12. package/dist/cli/{curator-drift-2jbjv03a.js → curator-drift-ckjm272w.js} +2 -2
  13. package/dist/cli/curator-llm-factory-j8tk9rxm.js +63 -0
  14. package/dist/cli/{dispatch-1y1rnv1h.js → dispatch-ndxbsk98.js} +2 -2
  15. package/dist/cli/{evidence-summary-service-rcgrdm52.js → evidence-summary-service-c22gahjc.js} +15 -14
  16. package/dist/cli/{gate-evidence-jmzctnhg.js → gate-evidence-hh8kc9wt.js} +9 -8
  17. package/dist/cli/guardrail-explain-9gjw2dsd.js +64 -0
  18. package/dist/cli/{guardrail-log-k1s9xf3h.js → guardrail-log-ejf5zjac.js} +5 -5
  19. package/dist/cli/{hive-promoter-vcj5h0e3.js → hive-promoter-gkbfpdtv.js} +35 -34
  20. package/dist/cli/{index-s2rcz51a.js → index-1jsmmx7y.js} +66 -34
  21. package/dist/cli/{index-qj22wyw2.js → index-1pxqkvvq.js} +3 -3
  22. package/dist/cli/{index-p863y7xx.js → index-3qdzpv91.js} +1 -1
  23. package/dist/cli/{index-8hkjk37c.js → index-58cvm3dw.js} +36 -1
  24. package/dist/cli/{index-ghzvxj3g.js → index-5wenr2vs.js} +40 -37
  25. package/dist/cli/{index-wck434f8.js → index-63tgh0cn.js} +15 -4
  26. package/dist/cli/{index-xwpmxpb7.js → index-641yjpk8.js} +6 -6
  27. package/dist/cli/{index-zwrqe065.js → index-6z5jtf7d.js} +2 -2
  28. package/dist/cli/{index-j6mfyy0a.js → index-7e4q41nb.js} +3 -3
  29. package/dist/cli/{index-6s2q144w.js → index-855hseq4.js} +43 -22
  30. package/dist/cli/index-9e6fdz2h.js +282 -0
  31. package/dist/cli/{index-zswmbm15.js → index-9s8drsf9.js} +2 -2
  32. package/dist/cli/{index-x68tw7v3.js → index-a1hxejxc.js} +168 -36
  33. package/dist/cli/{index-b6e016tv.js → index-abtrcs16.js} +1 -1
  34. package/dist/cli/{index-8fwhhayc.js → index-b44qqqt2.js} +27 -18
  35. package/dist/cli/{index-k3cxyk66.js → index-cwtpqm5f.js} +1 -1
  36. package/dist/cli/{index-2en8s72h.js → index-dc04v786.js} +30 -2
  37. package/dist/cli/{index-7554tecy.js → index-dtbzyr3s.js} +2 -2
  38. package/dist/cli/{index-eya357bk.js → index-ea09xax6.js} +5 -5
  39. package/dist/cli/{index-74hpm6sh.js → index-f36ybbsv.js} +12 -6
  40. package/dist/cli/{index-wd24zn2e.js → index-f9vvg5xb.js} +1 -1
  41. package/dist/cli/{index-rn0eq85g.js → index-fv0s19g9.js} +1 -1
  42. package/dist/cli/{index-ep14xc5z.js → index-fv2pzwnp.js} +6 -5
  43. package/dist/cli/{index-dpfphnmy.js → index-h1p08s3n.js} +1 -1
  44. package/dist/cli/{index-584grrwn.js → index-hkdgkkhb.js} +2 -2
  45. package/dist/cli/{index-1brb14xc.js → index-kdak6s63.js} +12 -6
  46. package/dist/cli/{index-satc0vs6.js → index-kh0jt9pg.js} +300 -1181
  47. package/dist/cli/{index-p7hbphaq.js → index-mkmcz6x8.js} +4 -4
  48. package/dist/cli/{index-krxcxkdq.js → index-n4hwcsbd.js} +20 -5
  49. package/dist/cli/{index-06cn7haa.js → index-rdgtfj74.js} +2 -2
  50. package/dist/cli/{index-b227mpnk.js → index-rnyt30tm.js} +4 -4
  51. package/dist/cli/{index-whezv7vc.js → index-v3y3qg48.js} +1 -1
  52. package/dist/cli/{index-0fgwdtvp.js → index-vdha477y.js} +1 -1
  53. package/dist/cli/{index-pr8vhj6j.js → index-wbrqvm2g.js} +8 -8
  54. package/dist/cli/{index-8214dd7j.js → index-wknz5c8q.js} +2 -2
  55. package/dist/cli/{index-pag8nsb1.js → index-wm6f7t7w.js} +3 -3
  56. package/dist/cli/{index-247wf23m.js → index-wmsehjq6.js} +2847 -1791
  57. package/dist/cli/{index-4nt52yr9.js → index-zj6t9zgx.js} +173 -25
  58. package/dist/cli/index-zy7f84cs.js +1210 -0
  59. package/dist/cli/index.d.ts +11 -1
  60. package/dist/cli/index.js +64 -42
  61. package/dist/cli/{knowledge-escalator-x6jrcrwj.js → knowledge-escalator-1d79m1tr.js} +14 -13
  62. package/dist/cli/{knowledge-events-aw22r72m.js → knowledge-events-ces29ncg.js} +12 -11
  63. package/dist/cli/{knowledge-link-bntppte4.js → knowledge-link-4cg4776a.js} +5 -5
  64. package/dist/cli/{knowledge-store-19xg1rw1.js → knowledge-store-pe8webc2.js} +6 -6
  65. package/dist/cli/{knowledge-validator-8wh82z0a.js → knowledge-validator-4yjf1f7k.js} +8 -8
  66. package/dist/cli/{pending-delegations-1y303j3k.js → pending-delegations-9h1jmtw1.js} +5 -5
  67. package/dist/cli/{pr-subscriptions-wvd041rp.js → pr-subscriptions-8pg34pev.js} +5 -5
  68. package/dist/cli/{runner-jn55k8xb.js → runner-cqecp1na.js} +6 -6
  69. package/dist/cli/{scan-cursor-429r5db9.js → scan-cursor-d6exjmqj.js} +9 -7
  70. package/dist/cli/{schema-jdwjcby0.js → schema-2esybebd.js} +4 -2
  71. package/dist/cli/{scope-persistence-s0s1bz15.js → scope-persistence-ej59x0yc.js} +9 -8
  72. package/dist/cli/{skill-generator-ffbj0ye5.js → skill-generator-yqta3yrg.js} +16 -15
  73. package/dist/cli/{telemetry-xyfb5vm8.js → telemetry-cybx6cfz.js} +1 -1
  74. package/dist/cli/{workspace-snapshot-w3tg4ne5.js → workspace-snapshot-d89zm6wm.js} +5 -2
  75. package/dist/cli/{worktree-collision-ownership-z5ftwjz3.js → worktree-collision-ownership-vy0kxa98.js} +28 -21
  76. package/dist/cli/{worktree-isolation-y2p93438.js → worktree-isolation-rwq0kke7.js} +35 -34
  77. package/dist/cli/{worktree-provisioning-owner-g24xd417.js → worktree-provisioning-owner-zzkjycp1.js} +3 -3
  78. package/dist/commands/ci-simulate.d.ts +2 -0
  79. package/dist/commands/index.d.ts +1 -0
  80. package/dist/commands/recover.d.ts +17 -0
  81. package/dist/commands/registry.d.ts +9 -1
  82. package/dist/commands/reset-session.d.ts +2 -0
  83. package/dist/config/cache-paths.d.ts +57 -0
  84. package/dist/config/evidence-schema.d.ts +3 -3
  85. package/dist/config/schema.d.ts +9 -0
  86. package/dist/git/branch.d.ts +2 -0
  87. package/dist/git/pr.d.ts +2 -0
  88. package/dist/hooks/context-budget.d.ts +4 -0
  89. package/dist/hooks/context-usage.d.ts +48 -0
  90. package/dist/hooks/delegation-gate/worktree-isolation.d.ts +7 -0
  91. package/dist/hooks/diff-scope.d.ts +7 -0
  92. package/dist/hooks/pr-workflow-gate.d.ts +29 -2
  93. package/dist/hooks/review-receipt-scope.d.ts +7 -0
  94. package/dist/hooks/reviewer-scope-lifecycle.d.ts +9 -0
  95. package/dist/hooks/semantic-diff-injection.d.ts +8 -0
  96. package/dist/index.js +607 -591
  97. package/dist/mutation/engine.d.ts +11 -0
  98. package/dist/observability/catalog.d.ts +8 -6
  99. package/dist/review/diff-source.d.ts +2 -0
  100. package/dist/sandbox/capability-probe.d.ts +22 -0
  101. package/dist/sandbox/executor.d.ts +15 -0
  102. package/dist/sandbox/linux/bubblewrap-executor.d.ts +6 -0
  103. package/dist/sandbox/macos/sandbox-exec-executor.d.ts +41 -0
  104. package/dist/sast/semgrep.d.ts +3 -2
  105. package/dist/telemetry.d.ts +17 -1
  106. package/dist/tools/checkpoint.d.ts +11 -0
  107. package/dist/tools/complexity-hotspots.d.ts +15 -0
  108. package/dist/tools/context-status.d.ts +9 -3
  109. package/dist/tools/diff-summary.d.ts +12 -0
  110. package/dist/tools/dispatch-lanes.d.ts +35 -2
  111. package/dist/tools/gh-evidence.d.ts +17 -1
  112. package/dist/tools/knowledge-receipt.d.ts +1 -1
  113. package/dist/tools/pkg-audit.d.ts +13 -0
  114. package/dist/tools/pre-check-batch.d.ts +9 -0
  115. package/dist/tools/prepare-pr-workflow-checkout.d.ts +1 -0
  116. package/dist/tools/sast-scan.d.ts +3 -0
  117. package/dist/utils/bun-compat.d.ts +80 -3
  118. package/dist/utils/git-binary-missing-error.d.ts +80 -2
  119. package/dist/utils/git-executable.d.ts +179 -0
  120. package/dist/utils/gitignore-warning.d.ts +10 -0
  121. package/dist/workflow/coder-settlement.d.ts +92 -0
  122. package/dist/worktree/core.d.ts +8 -0
  123. package/dist/worktree/merge.d.ts +33 -0
  124. package/package.json +3 -2
  125. package/dist/cli/curator-llm-factory-7are9wjh.js +0 -62
  126. package/dist/cli/guardrail-explain-brgw4sve.js +0 -63
  127. package/dist/cli/index-q1exe2b3.js +0 -256
@@ -25,6 +25,7 @@
25
25
  * present and falls back to a stable djb2-derived 32-bit hash on Node.
26
26
  */
27
27
  import { spawn as nodeSpawn } from 'node:child_process';
28
+ import * as fsSync from 'node:fs';
28
29
  /**
29
30
  * Whether the current runtime is Bun. Cached at first call — every subsequent
30
31
  * call is a single property access.
@@ -118,9 +119,16 @@ export interface BunCompatSubprocess {
118
119
  exitCode: number | null;
119
120
  readonly signalCode?: NodeJS.Signals | null;
120
121
  /**
121
- * Asynchronous process-creation failure reported by Node's ChildProcess
122
- * `error` event (for example ENOENT). Bun reports equivalent failures by
123
- * throwing from `Bun.spawn`, so this is populated only by the Node fallback.
122
+ * Process-creation failure (for example ENOENT), reported as a **value**
123
+ * on every runtime.
124
+ *
125
+ * The Node fallback populates it from ChildProcess's `error` event. Bun
126
+ * reports the same failures by throwing synchronously from `Bun.spawn`;
127
+ * issue #2236 showed that throw escaping uncaught to a tool boundary and
128
+ * surfacing a raw `ENOENT ... posix_spawn 'git'` to the user. The Bun path
129
+ * is now normalised *down* to this contract — the throw is caught and
130
+ * exposed here — so process-creation failures have no exit code, are
131
+ * described by `spawnError`, and `bunSpawn` never throws.
124
132
  */
125
133
  readonly spawnError?: Error | null;
126
134
  kill(signal?: NodeJS.Signals | number): void;
@@ -133,7 +141,76 @@ export declare const _internals: {
133
141
  cwd: () => string;
134
142
  spawnTaskkill: (args: string[], options: Parameters<typeof nodeSpawn>[2]) => import("node:child_process").ChildProcess;
135
143
  createProcessTreeKiller: typeof createProcessTreeKiller;
144
+ /**
145
+ * Test seam for the spawn-`cwd` probe. Lets a test drive the `EACCES`
146
+ * branch on hosts (Windows) where an unreadable directory cannot be created
147
+ * without elevation, instead of skipping the case.
148
+ */
149
+ statSync: (target: string) => fsSync.Stats;
136
150
  };
151
+ /** Why a spawn `cwd` was rejected before the child was ever created. */
152
+ export type SpawnCwdFailureReason = 'missing' | 'not-directory';
153
+ /**
154
+ * Raised as a **value**, never thrown: `bunSpawn`/`bunSpawnSync` surface it
155
+ * through the runtime's existing failure contract (`spawnError` plus a
156
+ * non-zero `exited`), and `runGit` converts it into its typed failure result.
157
+ * Throwing it would escalate previously-safe call sites into unhandled
158
+ * synchronous throws — the exact leak class this change removes.
159
+ */
160
+ export declare class SpawnCwdMissingError extends Error {
161
+ readonly name = "SpawnCwdMissingError";
162
+ /** Kept ENOENT so existing `code`-sniffing call sites keep working. */
163
+ readonly code = "ENOENT";
164
+ readonly cwd: string;
165
+ readonly reason: SpawnCwdFailureReason;
166
+ readonly executable: string;
167
+ constructor(cwd: string, reason: SpawnCwdFailureReason, executable: string, options?: {
168
+ cause?: unknown;
169
+ });
170
+ }
171
+ /** Observable state of a spawn `cwd`. */
172
+ export type SpawnCwdState =
173
+ /** Present and a directory — a spawn ENOENT here really is the binary. */
174
+ 'directory'
175
+ /** Definitely absent. */
176
+ | 'missing'
177
+ /** Present but not a directory (a file, for example). */
178
+ | 'not-directory'
179
+ /** `stat` refused (`EACCES`/`EPERM`) — we cannot tell. */
180
+ | 'unreadable'
181
+ /** `stat` failed for some other reason — we cannot tell. */
182
+ | 'unknown';
183
+ /**
184
+ * Inspects a spawn `cwd` with `statSync(...).isDirectory()`.
185
+ *
186
+ * `existsSync` is deliberately NOT used: it returns `true` for a `cwd` that is
187
+ * a regular *file* (the spawn then fails `ENOTDIR` on POSIX / `ENOENT` on
188
+ * Windows) and `false` when `stat` fails with `EACCES` (the directory is in
189
+ * fact present). Both would be misclassified.
190
+ *
191
+ * Verified rather than assumed: a **relative** `cwd` is safe (the stat and the
192
+ * child resolve against the same parent cwd), and a **symlinked** `cwd` is
193
+ * safe (`statSync` follows the link, as does the child).
194
+ */
195
+ export declare function inspectSpawnCwd(cwd: string): SpawnCwdState;
196
+ /**
197
+ * Returns a typed error when `cwd` is *definitely* unusable as a spawn working
198
+ * directory, `null` otherwise.
199
+ *
200
+ * An `unreadable`/`unknown` `cwd` deliberately returns `null`: refusing to
201
+ * spawn on "I cannot tell" would convert a stat permission quirk into a hard
202
+ * failure. The spawn proceeds and the OS decides; the classifiers in
203
+ * `git-binary-missing-error.ts` then report the honest third state.
204
+ */
205
+ export declare function describeSpawnCwdFailure(cwd: string, executable: string, cause?: unknown): SpawnCwdMissingError | null;
206
+ /** Mutually exclusive classification of a spawn failure. */
207
+ export type SpawnFailureClass = 'binary-missing' | 'cwd-missing' | 'cwd-unreadable' | 'other';
208
+ /**
209
+ * Single source of truth for the three-way split (issue #2236).
210
+ * `git-binary-missing-error.ts` re-exports this and its predicates delegate
211
+ * here, so the wrap below and every caller-side check can never disagree.
212
+ */
213
+ export declare function classifySpawnFailure(err: unknown, cwd?: string): SpawnFailureClass;
137
214
  /**
138
215
  * Merges `envOverrides` into the base env (or `process.env` if no base env
139
216
  * is given) and returns the resulting record.
@@ -1,9 +1,87 @@
1
- export declare class GitBinaryMissingError extends Error {
1
+ /**
2
+ * Spawn-failure classification for git (and any other external binary).
3
+ *
4
+ * Issue #2236: a spawn that fails with `ENOENT` was previously read as
5
+ * "the git binary is not on PATH". That is only one of the reasons a spawn
6
+ * can produce `ENOENT`. When the `cwd` handed to the spawn no longer exists,
7
+ * libuv reports the *same* code — `ENOENT: no such file or directory,
8
+ * posix_spawn 'git'` (`uv_spawn` on Windows) — and the misclassification sent
9
+ * the reported bug down a four-day PATH investigation while git was present
10
+ * the whole time.
11
+ *
12
+ * The discriminator is the `cwd` the caller already holds. Classification is
13
+ * three-way, never two-way: "I cannot tell" gets its own state and is never
14
+ * folded into either definite bucket.
15
+ *
16
+ * The `cwd` probe itself (`inspectSpawnCwd`, `SpawnCwdMissingError`) lives in
17
+ * `bun-compat.ts` — that file must stay loadable by bare Node, so it cannot
18
+ * import from here. It is re-exported below so callers still have one
19
+ * classification surface.
20
+ */
21
+ export { classifySpawnFailure, describeSpawnCwdFailure, inspectSpawnCwd, type SpawnCwdFailureReason, SpawnCwdMissingError, type SpawnCwdState, type SpawnFailureClass, } from './bun-compat.js';
22
+ /**
23
+ * Base type for "git never ran".
24
+ *
25
+ * Both subclasses mean the child process was never created, so **nothing may
26
+ * be inferred from its absence of output**. That distinction is the whole
27
+ * point: a caller that reads "git produced no result" as a fact about the
28
+ * repository (for example "this path is not in HEAD") is only entitled to do
29
+ * so when git actually executed and reported. Callers therefore discriminate
30
+ * on this base, not on either subclass — a two-way `instanceof` check against
31
+ * a three-way classification is what re-introduces the #2236 misdiagnosis one
32
+ * state at a time.
33
+ */
34
+ export declare class GitUnavailableError extends Error {
35
+ }
36
+ export declare class GitBinaryMissingError extends GitUnavailableError {
2
37
  readonly name = "GitBinaryMissingError";
3
38
  constructor(message?: string, options?: {
4
39
  cause?: unknown;
5
40
  });
6
41
  }
7
- export declare function isGitBinaryMissing(err: unknown): err is {
42
+ /** Which of the two `cwd` fault states produced a {@link GitSpawnCwdError}. */
43
+ export type GitSpawnCwdFault = 'missing' | 'unreadable';
44
+ /**
45
+ * git could not start because its `cwd` was unusable — the #2236 shape.
46
+ *
47
+ * Deliberately a *sibling* of {@link GitBinaryMissingError}, never a subclass:
48
+ * "the working directory is gone" and "git is not installed" are different
49
+ * facts with different remediations, and the #2236 regression suites assert
50
+ * that a cwd fault is `not.toBeInstanceOf(GitBinaryMissingError)`.
51
+ *
52
+ * The message text is rendered here rather than at each throw site so all four
53
+ * `execGit`-style call sites (`git/branch.ts`, `tools/checkpoint.ts`,
54
+ * `hooks/semantic-diff-injection.ts`, `tools/diff-summary.ts`) cannot drift.
55
+ */
56
+ export declare class GitSpawnCwdError extends GitUnavailableError {
57
+ readonly name = "GitSpawnCwdError";
58
+ readonly cwd: string;
59
+ readonly fault: GitSpawnCwdFault;
60
+ constructor(cwd: string, fault: GitSpawnCwdFault, options?: {
61
+ cause?: unknown;
62
+ });
63
+ }
64
+ /**
65
+ * True when the spawn failed because the executable itself could not be
66
+ * resolved. Passing the `cwd` the caller already holds is what makes this
67
+ * answer trustworthy; omitting it preserves the pre-#2236 ENOENT-only
68
+ * behaviour for call sites that have no cwd to offer.
69
+ */
70
+ export declare function isGitBinaryMissing(err: unknown, cwd?: string): err is {
71
+ code?: string;
72
+ };
73
+ /**
74
+ * True when the spawn failed because `cwd` is gone, or exists but is not a
75
+ * directory. This is the #2236 failure shape.
76
+ */
77
+ export declare function isSpawnCwdMissing(err: unknown, cwd: string): err is {
78
+ code?: string;
79
+ };
80
+ /**
81
+ * True when `cwd` could not be inspected (`EACCES`/`EPERM`, or an unexpected
82
+ * stat failure). Never folded into either definite bucket — callers must fail
83
+ * closed on it rather than assume the directory is gone.
84
+ */
85
+ export declare function isSpawnCwdUnreadable(err: unknown, cwd: string): err is {
8
86
  code?: string;
9
87
  };
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Single source of truth for "what absolute path do we invoke for git."
3
+ *
4
+ * Issue #2236 hardening (F1/F4/F5) — this module is NOT the fix for #2236
5
+ * (that was a stale worktree `cwd`, fixed elsewhere). It closes a real
6
+ * latent defect class the issue also asked for: `gitExec()`-style call
7
+ * sites spawn the slash-less name `git` while passing an explicit `env`
8
+ * object, and on POSIX there is no absolute-path candidate that would make
9
+ * PATH resolution unnecessary. See
10
+ * `.agents/issue-traces/2236-gitexec-enoent-posix-spawn/07-approved-plan.md`
11
+ * sections F1, F4, F5.
12
+ *
13
+ * Candidate order (built as ONE list and probed in a single pass — do NOT
14
+ * rely on a first-match-only PATH scan to order anything; on a darwin host
15
+ * with `PATH=/usr/bin:/bin` that would return the xcode-select
16
+ * `/usr/bin/git` shim before the homebrew candidates that exist precisely
17
+ * to avoid it):
18
+ *
19
+ * 1. explicit override — `OPENCODE_SWARM_GIT_BINARY` env var, else the
20
+ * value registered via `setGitBinaryOverride` (the USER-LEVEL config
21
+ * `git.binary`; a project/repo-supplied value is refused before it ever
22
+ * reaches this module — see `enforceGitBinaryProvenance` in
23
+ * `src/config/loader.ts`);
24
+ * 2. platform absolute candidates (darwin/linux/win32 — see
25
+ * `platformCandidates`);
26
+ * 3. ALL PATH matches for `git` (every PATH entry is scanned, not just
27
+ * the first hit);
28
+ * 4. bare `'git'` LAST, as an unprobed terminal fallback so a host that
29
+ * works today via plain PATH resolution does not regress.
30
+ *
31
+ * Each non-bare candidate is validated once with `git --version` (bounded
32
+ * timeout, explicit cwd) before being accepted — this rejects a macOS
33
+ * `/usr/bin/git` xcode-select shim that exists on disk but fails with
34
+ * `xcrun: error: invalid active developer path`, and it compensates for
35
+ * `isExecutableFile`'s missing `X_OK` check
36
+ * (`src/utils/external-tool-runner.ts`). Exit 0 alone does NOT accept a
37
+ * candidate: the output must match git's own `git version <n>.<n>` format
38
+ * (`GIT_VERSION_PATTERN`), so a non-git program that merely exits 0 is not
39
+ * mistaken for git BY ACCIDENT. That format check is not an anti-tampering
40
+ * control — see `GIT_VERSION_PATTERN` below for what it can and cannot do;
41
+ * the control over which binaries may be NAMED at all is
42
+ * `enforceGitBinaryProvenance` in `src/config/loader.ts`.
43
+ *
44
+ * Bounded probing (AGENTS.md invariant 1 — plugin init is fast and
45
+ * side-effect-minimal): per-probe timeout 250ms, total first-resolution
46
+ * budget 1000ms.
47
+ *
48
+ * This resolver NEVER throws. Both non-accepting outcomes — "the budget was
49
+ * exhausted before every candidate had been tried" and "every candidate we
50
+ * could enumerate was probed and rejected" — return the bare name `'git'`
51
+ * (candidate 4 above), unprobed.
52
+ *
53
+ * An earlier revision threw `GitBinaryMissingError` on the all-rejected
54
+ * outcome, justified by "a bare `'git'` spawn would hit the exact same PATH
55
+ * entries and fail identically." That justification is refuted by this
56
+ * issue's own root-cause analysis: a slash-less spawn does NOT resolve
57
+ * against the PATH this module enumerates. `pathCandidates()` reads
58
+ * `_internals.env().PATH`, whereas the runtime resolves a bare name against
59
+ * libuv's `_PATH_DEFPATH` (`/usr/bin:/bin`) when the child env carries no
60
+ * PATH, and under Bun against a snapshot taken at PROCESS START rather than
61
+ * the live `process.env` (oven-sh/bun#29237). The two sets genuinely differ,
62
+ * so throwing reintroduced issue #2236's own defect class — reporting git as
63
+ * unavailable on a host where git works — precisely in the degraded-PATH
64
+ * state #2236 nominates as the reporter's candidate trigger. Measured:
65
+ * platform `linux` with `PATH=/usr/bin:/bin` threw after ZERO probes (all
66
+ * three POSIX candidates stat-reject in microseconds, so the budget never
67
+ * trips) and stayed cached for 60s.
68
+ *
69
+ * The actionable diagnostic is not lost by falling through — it moves one
70
+ * layer up, to the caller that actually observes a failed spawn:
71
+ * `gitExec()` (`src/git/branch.ts`) renders `describeGitResolution()` into
72
+ * the `GitBinaryMissingError` it throws when the spawn itself fails.
73
+ *
74
+ * Caching: a successful resolution is memoized for the process lifetime. A
75
+ * bare-fallback outcome is memoized with a 60s TTL, then re-probed. Without
76
+ * any negative cache a git-less host would pay the full probe budget on
77
+ * every call; a permanent negative cache would mean a host that installs
78
+ * git mid-session stays broken until restart.
79
+ */
80
+ import { spawnSync } from 'node:child_process';
81
+ /** Env var escape hatch — a blocked user can set this without editing files. */
82
+ export declare const GIT_BINARY_ENV_VAR = "OPENCODE_SWARM_GIT_BINARY";
83
+ export type GitResolutionCandidateSource = 'override' | 'platform' | 'path';
84
+ export interface GitResolutionAttempt {
85
+ source: GitResolutionCandidateSource;
86
+ candidate: string;
87
+ accepted: boolean;
88
+ /** Rejection reason; absent when `accepted` is true. */
89
+ reason?: string;
90
+ }
91
+ export interface GitResolutionDescription {
92
+ /**
93
+ * Whether the most recent probe cycle produced a VALIDATED ABSOLUTE PATH.
94
+ *
95
+ * This is deliberately narrower than "resolution returned something
96
+ * usable": `resolveGitExecutable()` returns the unprobed bare `'git'` on
97
+ * the fallback path, and `resolved` stays `false` there because nothing was
98
+ * validated. Consumers rendering a diagnostic want exactly that
99
+ * distinction — `resolvedPath` is likewise `undefined` on the fallback.
100
+ */
101
+ resolved: boolean;
102
+ resolvedPath?: string;
103
+ attempts: GitResolutionAttempt[];
104
+ overrideValue?: string;
105
+ overrideSource?: 'env' | 'config';
106
+ }
107
+ /**
108
+ * Resolve the git executable to invoke. Lazy and memoized on first call —
109
+ * nothing is probed at module load. Synchronous; bounded by
110
+ * `TOTAL_BUDGET_MS`. Prefer `resolveGitExecutableAsync()` on any path that
111
+ * runs before plugin init resolves (AGENTS.md invariant 1).
112
+ */
113
+ export declare function resolveGitExecutable(): string;
114
+ /**
115
+ * Async counterpart. Shares one in-flight resolution across concurrent
116
+ * callers (the `semgrepAvailabilityProbe` precedent in
117
+ * `src/sast/semgrep.ts`) and yields to the event loop between candidate
118
+ * probes so a slow probe budget never blocks the event loop for the full
119
+ * 1000ms in one tick.
120
+ */
121
+ export declare function resolveGitExecutableAsync(): Promise<string>;
122
+ /**
123
+ * Exported for tests and called by `setGitBinaryOverride` on change.
124
+ *
125
+ * Bumping `cacheGeneration` is load-bearing, not bookkeeping: clearing
126
+ * `inFlightAsync` detaches an in-flight async probe but cannot cancel it, so
127
+ * without the bump that abandoned probe still commits its pre-reset result
128
+ * over the state this reset just established.
129
+ */
130
+ export declare function resetGitExecutableCache(): void;
131
+ /**
132
+ * TEST-ONLY seam (issue #2236 FR-006 ratchet fix) — NOT a production API, do
133
+ * not call from `src/`. Pre-seeds the resolver cache with an explicit
134
+ * "success" entry so `resolveGitExecutable()` / `resolveGitExecutableAsync()`
135
+ * return `value` immediately with zero probe spawns. Used exclusively by
136
+ * `tests/preload/executable-resolver-pin.ts` so pre-existing test files that
137
+ * assert `cmd[0] === 'git'` (or mock `node:child_process` to assert exact
138
+ * spawnSync call counts/args) do not need a per-file resolver stub. A test
139
+ * that needs the REAL probing behavior calls `resetGitExecutableCache()`
140
+ * first — that clears this seed exactly like any other cache entry, since it
141
+ * is stored in the same `cache` variable.
142
+ */
143
+ export declare function __seedGitExecutableForTests(value: string): void;
144
+ /**
145
+ * Registers the config-sourced override (`git.binary`). The environment
146
+ * variable `OPENCODE_SWARM_GIT_BINARY` always takes precedence when set —
147
+ * this only registers the config fallback. Resets the cache ONLY when the
148
+ * value actually changes, so a repeat/multi-swarm init does not discard an
149
+ * already-memoized good resolution.
150
+ *
151
+ * SECURITY: callers must pass only a USER-LEVEL value. `git.binary` selects
152
+ * an executable this process spawns, so a value read from a repository's
153
+ * `.opencode/opencode-swarm.json` would be attacker-controlled code
154
+ * selection (CWE-427). The config loader strips a project-supplied value
155
+ * before the merged config is built (`enforceGitBinaryProvenance` in
156
+ * `src/config/loader.ts`), which is why the sole production caller
157
+ * (`src/index.ts`) can pass `config.git?.binary` directly.
158
+ */
159
+ export declare function setGitBinaryOverride(value?: string): void;
160
+ /**
161
+ * Diagnostic surface: which candidates were tried in the most recent probe
162
+ * cycle and why each was rejected. Safe to call before any resolution has
163
+ * happened (returns an empty attempts list) — it never triggers a probe
164
+ * itself.
165
+ */
166
+ export declare function describeGitResolution(): GitResolutionDescription;
167
+ /**
168
+ * DI seam for testability (repo convention — see `src/sast/semgrep.ts`).
169
+ * `platform`/`env` let tests drive every platform branch regardless of the
170
+ * host OS running the test. `now`/`yieldToEventLoop` make the TTL, budget,
171
+ * and async-yield behavior deterministically testable without real sleeps.
172
+ */
173
+ export declare const _internals: {
174
+ spawnSync: typeof spawnSync;
175
+ platform: () => NodeJS.Platform;
176
+ env: () => NodeJS.ProcessEnv;
177
+ now: () => number;
178
+ yieldToEventLoop: () => Promise<void>;
179
+ };
@@ -1,4 +1,5 @@
1
1
  import { bunSpawn } from './bun-compat';
2
+ import { resolveGitExecutableAsync } from './git-executable.js';
2
3
  /**
3
4
  * Test-only dependency-injection seam. Production code calls
4
5
  * `_internals.bunSpawn(...)` so tests can replace the function on this object
@@ -9,6 +10,15 @@ import { bunSpawn } from './bun-compat';
9
10
  */
10
11
  export declare const _internals: {
11
12
  bunSpawn: typeof bunSpawn;
13
+ /**
14
+ * Test seam for async git binary resolution (issue #2236 hardening, lane
15
+ * C1b) — see `src/utils/git-executable.ts`. `ensureSwarmGitExcluded` runs
16
+ * on the plugin init path (AGENTS.md invariant 1), so it MUST use the
17
+ * async resolver, never the synchronous one — a synchronous probe loop
18
+ * here would be exactly the kind of unbounded-feeling init-path work the
19
+ * invariant forbids.
20
+ */
21
+ resolveGitExecutableAsync: typeof resolveGitExecutableAsync;
12
22
  };
13
23
  /**
14
24
  * Module-level flag so the warning fires at most once per process.
@@ -1,6 +1,7 @@
1
1
  import type { BackgroundTaskChangeContext, BackgroundWorktreeDescriptor } from '../background/pending-delegations.js';
2
2
  import { type TaskEvidence } from '../gate-evidence.js';
3
3
  import type { MergeOperationProvenance } from '../worktree/merge.js';
4
+ import type { CoderSettlementWal } from './workflow-wal-schema.js';
4
5
  export interface CoderSettlementResult {
5
6
  evidence: TaskEvidence;
6
7
  accepted: boolean;
@@ -60,10 +61,101 @@ export declare function recordCoderMergeProvenance(options: {
60
61
  provenance: MergeOperationProvenance;
61
62
  observedFiles: string[];
62
63
  }): Promise<void>;
64
+ /**
65
+ * Does the lane branch still exist in the primary repository?
66
+ *
67
+ * Throws rather than guessing when git cannot answer: an INDETERMINATE result
68
+ * must never be read as "gone". The #2236 self-heal decides whether to mark a
69
+ * settlement terminal on this answer, and marking terminal while the branch
70
+ * survives strands the coder's commits on an orphan branch.
71
+ */
72
+ declare function probeBranchExists(directory: string, branchName: string): boolean;
63
73
  export declare function completeCoderSettlementCleanup(directory: string, taskId: string, transitionId: string): Promise<void>;
64
74
  export declare function recoverCoderSettlement(directory: string, taskId: string): Promise<CoderSettlementResult | null>;
65
75
  export declare function releaseCoderDispatchOwnership(directory: string, taskId: string, transitionId: string): void;
66
76
  export declare function assertNoUnsettledCoderDispatch(directory: string, taskId: string): Promise<void>;
77
+ export interface CoderSettlementWalState {
78
+ taskId: string;
79
+ state: CoderSettlementWal['state'] | 'unreadable';
80
+ transitionId?: string;
81
+ processId?: number;
82
+ recordedAt?: string;
83
+ /** The in-process ownership registry still holds this dispatch (toolAfter never drained it). */
84
+ ownedInProcess: boolean;
85
+ /** A different, still-alive host process owns the dispatch. Never force-releasable from here. */
86
+ ownedByLiveForeignPid: boolean;
87
+ }
88
+ /**
89
+ * Bounded inventory of durable coder-settlement WALs for diagnostics and
90
+ * recovery surfaces (/swarm diagnose, /swarm recover, /swarm reset-session).
91
+ * Ownership predicates mirror recoverCoderSettlement's refusal guard exactly:
92
+ * `ownedInProcess` means this process's liveDispatches registry holds the
93
+ * dispatch (its completion may still be pending or was lost); a foreign live
94
+ * pid can never be released from this process by construction.
95
+ *
96
+ * `truncated` is true when the directory held more matching files than
97
+ * MAX_SETTLEMENT_WAL_SCAN — consumers MUST surface that so a wedge sorted
98
+ * past the cap is not silently invisible (issue #2268 review, PRR-011).
99
+ */
100
+ export declare function listCoderSettlementWalStates(directory: string): Promise<{
101
+ states: CoderSettlementWalState[];
102
+ truncated: boolean;
103
+ }>;
104
+ export type StaleSettlementRecoveryOutcome = {
105
+ taskId: string;
106
+ outcome: 'recovered';
107
+ accepted: boolean;
108
+ forced: boolean;
109
+ } | {
110
+ taskId: string;
111
+ outcome: 'already_terminal';
112
+ state: 'ABORTED' | 'COMMITTED';
113
+ } | {
114
+ taskId: string;
115
+ outcome: 'owned_in_process';
116
+ transitionId: string;
117
+ } | {
118
+ taskId: string;
119
+ outcome: 'owned_by_live_foreign_pid';
120
+ processId: number;
121
+ } | {
122
+ taskId: string;
123
+ outcome: 'unreadable_wal';
124
+ } | {
125
+ taskId: string;
126
+ outcome: 'error';
127
+ message: string;
128
+ };
129
+ /**
130
+ * Issue #2268: the user-facing settlement recovery entry point. Settles every
131
+ * non-terminal coder-settlement WAL via the same recoverCoderSettlement
132
+ * transition the internal sinks (update_task_status, /swarm close preflight,
133
+ * Stage-B ingestion) already use — no new states, no schema change.
134
+ *
135
+ * `force` releases THIS process's ownership registry keys before recovery.
136
+ * That is safe only as an explicit operator assertion (human-only /swarm
137
+ * recover --force, /swarm reset-session): if the dispatch was genuinely still
138
+ * in flight, its late completion will fail settlement with
139
+ * CODER_SETTLEMENT_IDEMPOTENCY_CONFLICT against the already-recovered WAL.
140
+ * A foreign live pid is NEVER forced — its dispatch lives in another process.
141
+ */
142
+ export declare function recoverStaleCoderSettlements(directory: string, options?: {
143
+ taskIds?: string[];
144
+ force?: boolean;
145
+ }): Promise<{
146
+ results: StaleSettlementRecoveryOutcome[];
147
+ truncated: boolean;
148
+ }>;
67
149
  export declare const _internals: {
68
150
  liveDispatches: Set<string>;
151
+ /**
152
+ * Test seam for the lane-branch existence probe. Production reads it here,
153
+ * so the #2236 fail-closed invariants — never mark a WAL terminal while the
154
+ * branch exists, and never mark it terminal on an INDETERMINATE answer —
155
+ * are drivable without a second git fixture whose `.git` is unusable (which
156
+ * would break every other git call and never reach the guard).
157
+ */
158
+ branchExists: typeof probeBranchExists;
159
+ recoverCoderSettlement: typeof recoverCoderSettlement;
69
160
  };
161
+ export {};
@@ -11,6 +11,7 @@
11
11
  import * as fs from 'node:fs';
12
12
  import * as fsPromises from 'node:fs/promises';
13
13
  import { bunSpawn } from '../utils/bun-compat';
14
+ import { resolveGitExecutable } from '../utils/git-executable.js';
14
15
  import type { WorktreeHandle, WorktreeOptions, WorktreeProvisionResult } from './types';
15
16
  /**
16
17
  * Test-only dependency-injection seam. Production code calls
@@ -58,6 +59,13 @@ export declare const _internals: {
58
59
  removeLaneProfileFromDisk: (worktreePath: string, laneIndex: number) => Promise<void>;
59
60
  /** Test seam for removeWorktree — allows tests to intercept worktree removal calls. */
60
61
  removeWorktree: typeof removeWorktree;
62
+ /**
63
+ * Test seam for the git binary resolution (issue #2236 hardening, lane
64
+ * C1b) — see `src/utils/git-executable.ts`. Allows tests to stub a
65
+ * deterministic value instead of exercising the real filesystem-probing
66
+ * resolver against a mocked `bunSpawn`.
67
+ */
68
+ resolveGitExecutable: typeof resolveGitExecutable;
61
69
  };
62
70
  /**
63
71
  * FR-201 SC-124: Writes the lane runtime profile as `.swarm/lanes/{laneIndex}.env`.
@@ -10,6 +10,7 @@
10
10
  * @module merge-back
11
11
  */
12
12
  import { bunSpawn } from '../utils/bun-compat';
13
+ import { resolveGitExecutable } from '../utils/git-executable.js';
13
14
  import type { MergeStrategy } from './types';
14
15
  /**
15
16
  * Test-only dependency-injection seam. Production code calls
@@ -31,7 +32,39 @@ export declare const _internals: {
31
32
  startupOrphanRecovery: typeof startupOrphanRecovery;
32
33
  /** FR-001b: exposes extractSessionId for lane ownership validation. */
33
34
  extractSessionId: typeof extractSessionId;
35
+ /**
36
+ * Issue #2236 hardening (F1/F4/F5) — resolves the absolute git executable
37
+ * path instead of spawning the bare `'git'` name. Exposed for test
38
+ * injection following the `src/worktree/core.ts` convention.
39
+ */
40
+ resolveGitExecutable: typeof resolveGitExecutable;
34
41
  };
42
+ /**
43
+ * Why a git child process never started (issue #2236).
44
+ *
45
+ * `cwd-missing` is the reported bug: a lane worktree path read from durable
46
+ * WAL state whose directory has already been torn down. `cwd-unreadable` is
47
+ * the deliberate third state — "cannot tell" must never be treated as "gone",
48
+ * because the recovery path decides whether to discard a branch on it.
49
+ */
50
+ export type GitSpawnFailureKind = 'cwd-missing' | 'cwd-unreadable' | 'spawn-failed';
51
+ export interface GitSpawnFailure {
52
+ kind: GitSpawnFailureKind;
53
+ cwd: string;
54
+ message: string;
55
+ }
56
+ /**
57
+ * Stage reported when the lane worktree directory is gone AND the lane branch
58
+ * no longer exists — nothing is recoverable. The coder-settlement recovery
59
+ * path keys its self-heal on this stage, then re-verifies branch absence
60
+ * itself before touching the WAL.
61
+ */
62
+ export declare const SOURCE_WORKTREE_GONE_STAGE = "source-worktree-gone";
63
+ /**
64
+ * Stage reported when the lane worktree directory is gone but branch existence
65
+ * could not be determined. Fails closed: callers must never self-heal on it.
66
+ */
67
+ export declare const SOURCE_WORKTREE_UNCERTAIN_STAGE = "source-worktree-uncertain";
35
68
  export interface MergeSuccess {
36
69
  merged: true;
37
70
  strategy: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.144.3",
3
+ "version": "7.144.5",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -109,7 +109,8 @@
109
109
  "repro:1873": "bun build scripts/repro-1873-entry.ts --outdir dist-build-test/repro-1873 --target node --format esm && node scripts/repro-1873.mjs",
110
110
  "check:events": "bun run scripts/check-event-contract.ts",
111
111
  "check:test-file-cap": "bun run scripts/check-test-file-cap.ts",
112
- "check:gate-portability": "bun run scripts/check-gate-portability.ts"
112
+ "check:gate-portability": "bun run scripts/check-gate-portability.ts",
113
+ "check:bare-spawn": "bun run scripts/check-bare-executable-spawn.ts"
113
114
  },
114
115
  "dependencies": {
115
116
  "@opencode-ai/plugin": "^1.18.3",
@@ -1,62 +0,0 @@
1
- // @bun
2
- import {
3
- createCuratorLLMDelegate
4
- } from "./index-247wf23m.js";
5
- import"./index-7554tecy.js";
6
- import"./index-2en8s72h.js";
7
- import"./index-satc0vs6.js";
8
- import"./index-134d35c1.js";
9
- import"./index-b6e016tv.js";
10
- import"./index-zwrqe065.js";
11
- import"./index-8214dd7j.js";
12
- import"./index-y8552snf.js";
13
- import"./index-wck434f8.js";
14
- import"./index-70ha9e09.js";
15
- import"./index-c8s9a3zh.js";
16
- import"./index-9ss2m4rs.js";
17
- import"./index-74hpm6sh.js";
18
- import"./index-584grrwn.js";
19
- import"./index-pag8nsb1.js";
20
- import"./index-eya357bk.js";
21
- import"./index-b227mpnk.js";
22
- import"./index-0fgwdtvp.js";
23
- import"./index-rtry5xyf.js";
24
- import"./index-zsrtt42s.js";
25
- import"./index-krxcxkdq.js";
26
- import"./index-k3cxyk66.js";
27
- import"./index-x68tw7v3.js";
28
- import"./index-6s2q144w.js";
29
- import"./index-p7hbphaq.js";
30
- import"./index-j6mfyy0a.js";
31
- import"./index-556ja8an.js";
32
- import"./index-7sy3aa9m.js";
33
- import"./index-wx8zdh4a.js";
34
- import"./index-8fwhhayc.js";
35
- import"./index-q1exe2b3.js";
36
- import"./index-xwpmxpb7.js";
37
- import"./index-pr8vhj6j.js";
38
- import"./index-qj22wyw2.js";
39
- import"./index-gprxjmm7.js";
40
- import"./index-n832052r.js";
41
- import"./index-rn0eq85g.js";
42
- import"./index-z6xqpmqg.js";
43
- import"./index-8hkjk37c.js";
44
- import"./index-1brb14xc.js";
45
- import"./index-4rhhvd1a.js";
46
- import"./index-whezv7vc.js";
47
- import"./index-zjygnfay.js";
48
- import"./index-ae75rja9.js";
49
- import"./index-dpfphnmy.js";
50
- import"./index-zswmbm15.js";
51
- import"./index-ed5ykebp.js";
52
- import"./index-bk5tah7q.js";
53
- import"./index-wd24zn2e.js";
54
- import"./index-s2rcz51a.js";
55
- import"./index-7g4c7s5r.js";
56
- import"./index-bpmtbmy9.js";
57
- import"./index-4nt52yr9.js";
58
- import"./index-zgwm4ryv.js";
59
- import"./index-a76rekgs.js";
60
- export {
61
- createCuratorLLMDelegate
62
- };