@gethmy/harness 1.2.0 → 1.2.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.
package/dist/cli.js CHANGED
@@ -81,6 +81,11 @@ var init_constants = __esm(() => {
81
81
  QUERY_GC_TIME: 1000 * 60 * 60 * 24
82
82
  };
83
83
  });
84
+ // ../harmony-shared/dist/fanoutSource.js
85
+ var FANOUT_KEY_MARKER = "harmony:fanout-item", FANOUT_KEY_RE;
86
+ var init_fanoutSource = __esm(() => {
87
+ FANOUT_KEY_RE = new RegExp(`^\\[${FANOUT_KEY_MARKER}\\]:\\s*#(\\S+)\\s*$`, "m");
88
+ });
84
89
  // ../harmony-shared/dist/gateConfigError.js
85
90
  function gateConfigErrorReason(evaluation) {
86
91
  if (!evaluation || evaluation.passed)
@@ -496,6 +501,7 @@ var init_dist = __esm(() => {
496
501
  init_columnSort();
497
502
  init_commentSerializer();
498
503
  init_constants();
504
+ init_fanoutSource();
499
505
  init_gateConfigError();
500
506
  init_gateEvaluate();
501
507
  init_logger();
package/dist/index.js CHANGED
@@ -164,6 +164,11 @@ var init_constants = __esm(() => {
164
164
  QUERY_GC_TIME: 1000 * 60 * 60 * 24
165
165
  };
166
166
  });
167
+ // ../harmony-shared/dist/fanoutSource.js
168
+ var FANOUT_KEY_MARKER = "harmony:fanout-item", FANOUT_KEY_RE;
169
+ var init_fanoutSource = __esm(() => {
170
+ FANOUT_KEY_RE = new RegExp(`^\\[${FANOUT_KEY_MARKER}\\]:\\s*#(\\S+)\\s*$`, "m");
171
+ });
167
172
  // ../harmony-shared/dist/gateConfigError.js
168
173
  function gateConfigErrorReason(evaluation) {
169
174
  if (!evaluation || evaluation.passed)
@@ -579,6 +584,7 @@ var init_dist = __esm(() => {
579
584
  init_columnSort();
580
585
  init_commentSerializer();
581
586
  init_constants();
587
+ init_fanoutSource();
582
588
  init_gateConfigError();
583
589
  init_gateEvaluate();
584
590
  init_logger();
@@ -3647,7 +3653,7 @@ async function runStage(request, deps) {
3647
3653
  // src/worktree.ts
3648
3654
  init_log();
3649
3655
  import { execFileSync as execFileSync7, execSync } from "node:child_process";
3650
- import { existsSync as existsSync3, rmSync } from "node:fs";
3656
+ import { existsSync as existsSync3, readdirSync as readdirSync2, rmSync } from "node:fs";
3651
3657
  import { resolve as resolve3 } from "node:path";
3652
3658
  var TAG13 = "worktree";
3653
3659
 
@@ -3781,11 +3787,25 @@ function createWorktree(basePath, baseBranch, branchName, opts = {}) {
3781
3787
  }
3782
3788
  return worktreeDir;
3783
3789
  }
3790
+ function containsForeignWorktrees(dir) {
3791
+ if (existsSync3(resolve3(dir, ".git")))
3792
+ return false;
3793
+ let children;
3794
+ try {
3795
+ children = readdirSync2(dir);
3796
+ } catch {
3797
+ return false;
3798
+ }
3799
+ return children.some((child) => existsSync3(resolve3(dir, child, ".git")));
3800
+ }
3784
3801
  function cleanupWorktree(worktreePath, branchName) {
3785
3802
  const repoRoot = execFileSync7("git", ["rev-parse", "--show-toplevel"], {
3786
3803
  encoding: "utf-8"
3787
3804
  }).trim();
3788
3805
  if (existsSync3(worktreePath)) {
3806
+ if (containsForeignWorktrees(worktreePath)) {
3807
+ throw new Error(`Refusing to remove ${worktreePath}: it is not a git worktree itself, ` + `but git worktrees live directly beneath it. Removing it would ` + `destroy them — pass the individual worktree paths instead (#928).`);
3808
+ }
3789
3809
  try {
3790
3810
  execFileSync7("git", ["worktree", "remove", worktreePath, "--force"], {
3791
3811
  cwd: repoRoot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gethmy/harness",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
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",
package/src/worktree.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execFileSync, execSync } from "node:child_process";
2
- import { existsSync, rmSync } from "node:fs";
2
+ import { existsSync, readdirSync, rmSync } from "node:fs";
3
3
  import { resolve } from "node:path";
4
4
  import { log } from "./log.js";
5
5
  import { installCommand } from "./pm.js";
@@ -356,6 +356,25 @@ export function createWorktree(
356
356
  return worktreeDir;
357
357
  }
358
358
 
359
+ /**
360
+ * True when `dir` is not itself a git worktree or repository (no `.git`
361
+ * entry) while a direct child directory carries one — i.e. `dir` is a
362
+ * CONTAINER of worktrees, like `.harmony-worktrees/agent-attempts/`.
363
+ * Force-removing such a directory destroys every worktree beneath it, live
364
+ * runs included (#928), so `cleanupWorktree` refuses it outright instead of
365
+ * escalating to `rmSync`.
366
+ */
367
+ function containsForeignWorktrees(dir: string): boolean {
368
+ if (existsSync(resolve(dir, ".git"))) return false;
369
+ let children: string[];
370
+ try {
371
+ children = readdirSync(dir);
372
+ } catch {
373
+ return false;
374
+ }
375
+ return children.some((child) => existsSync(resolve(dir, child, ".git")));
376
+ }
377
+
359
378
  /**
360
379
  * Remove a git worktree and its branch.
361
380
  */
@@ -368,6 +387,16 @@ export function cleanupWorktree(
368
387
  }).trim();
369
388
 
370
389
  if (existsSync(worktreePath)) {
390
+ // Never escalate on a container: `git worktree remove` would fail on it
391
+ // (it is not a worktree), and the old rmSync fallback then deleted every
392
+ // worktree underneath — including live ones with uncommitted work (#928).
393
+ if (containsForeignWorktrees(worktreePath)) {
394
+ throw new Error(
395
+ `Refusing to remove ${worktreePath}: it is not a git worktree itself, ` +
396
+ `but git worktrees live directly beneath it. Removing it would ` +
397
+ `destroy them — pass the individual worktree paths instead (#928).`,
398
+ );
399
+ }
371
400
  try {
372
401
  execFileSync("git", ["worktree", "remove", worktreePath, "--force"], {
373
402
  cwd: repoRoot,