@gethmy/harness 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -473,6 +473,13 @@ var init_playbookStage = __esm(() => {
473
473
 
474
474
  // ../harmony-shared/dist/projectTemplates.js
475
475
  var init_projectTemplates = () => {};
476
+
477
+ // ../harmony-shared/dist/realtimeChannel.js
478
+ var inFlightDetach;
479
+ var init_realtimeChannel = __esm(() => {
480
+ init_logger();
481
+ inFlightDetach = new WeakMap;
482
+ });
476
483
  // ../harmony-shared/dist/reviewTools.js
477
484
  var REVIEW_DISALLOWED_TOOLS;
478
485
  var init_reviewTools = __esm(() => {
@@ -514,6 +521,7 @@ var init_dist = __esm(() => {
514
521
  init_playbookCatalog();
515
522
  init_playbookStage();
516
523
  init_projectTemplates();
524
+ init_realtimeChannel();
517
525
  init_reviewTools();
518
526
  init_stageHandoff();
519
527
  init_types();
package/dist/index.js CHANGED
@@ -556,6 +556,13 @@ var init_playbookStage = __esm(() => {
556
556
 
557
557
  // ../harmony-shared/dist/projectTemplates.js
558
558
  var init_projectTemplates = () => {};
559
+
560
+ // ../harmony-shared/dist/realtimeChannel.js
561
+ var inFlightDetach;
562
+ var init_realtimeChannel = __esm(() => {
563
+ init_logger();
564
+ inFlightDetach = new WeakMap;
565
+ });
559
566
  // ../harmony-shared/dist/reviewTools.js
560
567
  var REVIEW_DISALLOWED_TOOLS;
561
568
  var init_reviewTools = __esm(() => {
@@ -597,6 +604,7 @@ var init_dist = __esm(() => {
597
604
  init_playbookCatalog();
598
605
  init_playbookStage();
599
606
  init_projectTemplates();
607
+ init_realtimeChannel();
600
608
  init_reviewTools();
601
609
  init_stageHandoff();
602
610
  init_types();
@@ -4480,6 +4488,33 @@ function resolveWorktreeStartRef(baseBranch, branchName, continueExisting, branc
4480
4488
  }
4481
4489
  return `origin/${baseBranch}`;
4482
4490
  }
4491
+ function resolveContinuationTarget(branchName, continueRequested, failedBranchPrefix, approvedBranchPrefix, branchExistsOnOrigin) {
4492
+ if (branchExistsOnOrigin(branchName)) {
4493
+ return {
4494
+ branchName,
4495
+ continueExisting: true,
4496
+ existsOnOrigin: true,
4497
+ reason: continueRequested ? "requested" : "exists_on_origin"
4498
+ };
4499
+ }
4500
+ if (failedBranchPrefix && approvedBranchPrefix && branchName.startsWith(failedBranchPrefix)) {
4501
+ const sibling = approvedBranchPrefix + branchName.slice(failedBranchPrefix.length);
4502
+ if (branchExistsOnOrigin(sibling)) {
4503
+ return {
4504
+ branchName: sibling,
4505
+ continueExisting: true,
4506
+ existsOnOrigin: true,
4507
+ reason: "approved_rename"
4508
+ };
4509
+ }
4510
+ }
4511
+ return {
4512
+ branchName,
4513
+ continueExisting: continueRequested,
4514
+ existsOnOrigin: false,
4515
+ reason: "fresh"
4516
+ };
4517
+ }
4483
4518
  function fetchExistingBranch(repoRoot, branchName, attempts = 3, lsRemoteImpl = (root, branch) => execFileSync8("git", ["ls-remote", "--exit-code", "origin", `refs/heads/${branch}`], { cwd: root, stdio: "pipe" }), fetchImpl = (root, branch) => execFileSync8("git", [
4484
4519
  "fetch",
4485
4520
  "origin",
@@ -4538,7 +4573,7 @@ function createWorktree(basePath, baseBranch, branchName, opts = {}) {
4538
4573
  });
4539
4574
  } catch {}
4540
4575
  fetchBaseBranch(repoRoot, baseBranch);
4541
- const startRef = resolveWorktreeStartRef(baseBranch, branchName, opts.continueExisting ?? false, () => fetchExistingBranch(repoRoot, branchName));
4576
+ const startRef = resolveWorktreeStartRef(baseBranch, branchName, opts.continueExisting ?? false, () => opts.branchExistsOnOrigin ?? fetchExistingBranch(repoRoot, branchName));
4542
4577
  log.info(TAG16, `Creating worktree: ${worktreeDir} (branch: ${branchName}, base: ${startRef})`);
4543
4578
  try {
4544
4579
  execFileSync8("git", ["worktree", "add", "-B", branchName, worktreeDir, startRef], { cwd: repoRoot, stdio: "pipe" });
@@ -4787,6 +4822,7 @@ export {
4787
4822
  resolvePrUrl,
4788
4823
  resolvePrHeadBranch,
4789
4824
  resolveOracleRunner,
4825
+ resolveContinuationTarget,
4790
4826
  rescueUnpushedBranch,
4791
4827
  rerunFailedJobs,
4792
4828
  requestIndependentReview,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gethmy/harness",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Execution motor for Harmony playbook stages. Runs exactly one stage per invocation: worktree, role-separated subagents, held oracle, gate evidence. It never routes, never judges, never pushes.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -55,7 +55,7 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "@anthropic-ai/claude-agent-sdk": "^0.3.178",
58
- "@gethmy/mcp": "3.0.0",
58
+ "@gethmy/mcp": "3.1.0",
59
59
  "@supabase/supabase-js": "2.95.3"
60
60
  },
61
61
  "devDependencies": {
package/src/worktree.ts CHANGED
@@ -114,6 +114,102 @@ export function resolveWorktreeStartRef(
114
114
  return `origin/${baseBranch}`;
115
115
  }
116
116
 
117
+ /** The branch a run should actually build on, decided against origin (#930). */
118
+ export interface ContinuationTarget {
119
+ /** The branch the run must use — possibly the approved-rename sibling. */
120
+ branchName: string;
121
+ /** Whether `createWorktree` should continue the branch's own pushed tip. */
122
+ continueExisting: boolean;
123
+ /**
124
+ * The probe's answer for `branchName`: does it exist on origin? Hand it to
125
+ * `createWorktree` (`opts.branchExistsOnOrigin`) so the same ref is not
126
+ * ls-remote'd + fetched a second time inside `resolveWorktreeStartRef` on
127
+ * every continued run. When true, the probe (`fetchExistingBranch`) has
128
+ * already fetched the ref, so `refs/remotes/origin/<branchName>` resolves
129
+ * locally — the promise `createWorktree` needs to skip its own probe.
130
+ */
131
+ existsOnOrigin: boolean;
132
+ /** Why — one legible word for the worker's log line. */
133
+ reason: "requested" | "exists_on_origin" | "approved_rename" | "fresh";
134
+ }
135
+
136
+ /**
137
+ * Final continuation decision for an implement run, consulted against origin
138
+ * right before `createWorktree` (#930).
139
+ *
140
+ * The description-based guard (`recordedBranchForCard` in @harmony/shared)
141
+ * answers from what the card RECORDS — and the record has two known failure
142
+ * modes, both in the data-loss class: a verify-failed run pushes its branch
143
+ * but dies before `postSummary` writes the record (or a human deletes the
144
+ * auto-appended block as routine cleanup), and a review approval renames the
145
+ * ref on origin while the record write can fail or predate the rewrite. So
146
+ * this helper asks origin itself:
147
+ *
148
+ * - The branch EXISTS on origin → continue it, whatever the description says.
149
+ * `git worktree add -B` would reset it and completion would force-push over
150
+ * it; the guard's contract is "failing safe costs a redundant continuation;
151
+ * failing open costs the user's work" — this makes that hold even when the
152
+ * record is gone.
153
+ * - The branch is GONE but its approved-rename sibling
154
+ * (`<approvedPrefix><rest>` for a `<failedPrefix><rest>` name) exists →
155
+ * continue the sibling. That is the ref the approval moved the work to;
156
+ * rebuilding `<failedPrefix><rest>` from origin/<base> would reimplement
157
+ * from scratch and the next approval would force-rename over the reviewed
158
+ * branch behind its open PR.
159
+ * - Neither exists → nothing on origin to lose; pass `continueRequested`
160
+ * through unchanged, and `createWorktree` falls back to a fresh branch
161
+ * exactly as before.
162
+ *
163
+ * The probe's answer travels with the target (`existsOnOrigin`) so the caller
164
+ * can hand it to `createWorktree` and origin is asked exactly ONCE per run —
165
+ * without it, a continued run ls-remote'd + fetched the same ref a second time
166
+ * inside `resolveWorktreeStartRef` (#930 review).
167
+ *
168
+ * `branchExistsOnOrigin` is a thunk for the same reason as
169
+ * `resolveWorktreeStartRef`'s — the decision is unit-testable without git.
170
+ * Callers pass `fetchExistingBranch`, which fails CLOSED (throws
171
+ * `WorktreeBaseError` on an unprobeable remote) — a transient network error
172
+ * must requeue the run, never read as "branch absent" (#637 follow-up).
173
+ */
174
+ export function resolveContinuationTarget(
175
+ branchName: string,
176
+ continueRequested: boolean,
177
+ failedBranchPrefix: string,
178
+ approvedBranchPrefix: string,
179
+ branchExistsOnOrigin: (ref: string) => boolean,
180
+ ): ContinuationTarget {
181
+ if (branchExistsOnOrigin(branchName)) {
182
+ return {
183
+ branchName,
184
+ continueExisting: true,
185
+ existsOnOrigin: true,
186
+ reason: continueRequested ? "requested" : "exists_on_origin",
187
+ };
188
+ }
189
+ if (
190
+ failedBranchPrefix &&
191
+ approvedBranchPrefix &&
192
+ branchName.startsWith(failedBranchPrefix)
193
+ ) {
194
+ const sibling =
195
+ approvedBranchPrefix + branchName.slice(failedBranchPrefix.length);
196
+ if (branchExistsOnOrigin(sibling)) {
197
+ return {
198
+ branchName: sibling,
199
+ continueExisting: true,
200
+ existsOnOrigin: true,
201
+ reason: "approved_rename",
202
+ };
203
+ }
204
+ }
205
+ return {
206
+ branchName,
207
+ continueExisting: continueRequested,
208
+ existsOnOrigin: false,
209
+ reason: "fresh",
210
+ };
211
+ }
212
+
117
213
  /**
118
214
  * Fetch a branch from origin so `origin/<branchName>` resolves locally, and
119
215
  * report whether the branch exists on the remote.
@@ -209,6 +305,17 @@ export interface CreateWorktreeOptions {
209
305
  * when the branch isn't on origin yet.
210
306
  */
211
307
  continueExisting?: boolean;
308
+ /**
309
+ * Pre-answered origin probe for `branchName`, set when the caller already
310
+ * ran the probe (`resolveContinuationTarget` → `fetchExistingBranch`, #930):
311
+ * `createWorktree` then skips its own ls-remote + fetch of the same ref.
312
+ * `true` carries a promise, not just an answer — the ref must have been
313
+ * FETCHED so `refs/remotes/origin/<branchName>` resolves locally.
314
+ * `fetchExistingBranch` does both; a bare ls-remote does not, so never pass
315
+ * its answer here. Leave undefined and `createWorktree` probes itself
316
+ * (unchanged behaviour).
317
+ */
318
+ branchExistsOnOrigin?: boolean;
212
319
  }
213
320
 
214
321
  /**
@@ -278,12 +385,17 @@ export function createWorktree(
278
385
  // Pick the base ref. Normally a fresh per-attempt branch from
279
386
  // origin/<baseBranch>; for a stage continuation, the branch's own pushed tip
280
387
  // so a prior stage's commits survive into this run (#561). `-B` resets the
281
- // local branch to whichever ref we chose.
388
+ // local branch to whichever ref we chose. A caller that already probed
389
+ // origin (resolveContinuationTarget, #930) passes the answer in
390
+ // `opts.branchExistsOnOrigin` — its probe also fetched the ref, so
391
+ // `origin/<branchName>` resolves locally and asking origin again would be a
392
+ // redundant second ls-remote + fetch of the same ref.
282
393
  const startRef = resolveWorktreeStartRef(
283
394
  baseBranch,
284
395
  branchName,
285
396
  opts.continueExisting ?? false,
286
- () => fetchExistingBranch(repoRoot, branchName),
397
+ () =>
398
+ opts.branchExistsOnOrigin ?? fetchExistingBranch(repoRoot, branchName),
287
399
  );
288
400
 
289
401
  log.info(