@pify/swarm 0.3.0 → 0.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/README.md CHANGED
@@ -39,3 +39,5 @@ Without it, parallel agents cannot see each other, so two of them cheerfully fix
39
39
  ## License
40
40
 
41
41
  MIT © [Pify maintainers](https://github.com/pifydev)
42
+
43
+ **Isolated runs clean up after themselves** (v0.4): a worktree whose child changed nothing is removed along with its branch — otherwise a read-only step left one of each behind, per run. Anything uncommitted, or any commit the child made, is kept and reported.
@@ -25,7 +25,7 @@ import { Text } from "@earendil-works/pi-tui";
25
25
  import { Type } from "typebox";
26
26
 
27
27
  import { BUILTIN_AGENTS } from "../src/builtin.ts";
28
- import { createIsolationWorktree, isolationNote } from "../src/isolate.ts";
28
+ import { createIsolationWorktree, isolationNote, removeIfUnchanged } from "../src/isolate.ts";
29
29
  import { formatInbox, mailboxDir, mailboxPrompt, postMessage, readInbox } from "../src/mailbox.ts";
30
30
  import { parseAgentFile } from "../src/frontmatter.ts";
31
31
  import { buildReport, buildStatusLine } from "../src/report.ts";
@@ -43,6 +43,8 @@ import { readFileSync, readdirSync } from "node:fs";
43
43
  import { basename, join } from "node:path";
44
44
 
45
45
  const RUN_ENTRY = "swarm-run";
46
+ const CLEAN_WORKTREE_NOTE =
47
+ "Ran isolated in a temporary worktree; it changed nothing, so the worktree was removed.";
46
48
 
47
49
  type UiContext = ExtensionContext;
48
50
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pify/swarm",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Coordinate multiple pi agents in parallel: swarm_run fan-out with per-item auto-routing, concurrency queue, aggregated reports",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -61,8 +61,8 @@
61
61
  }
62
62
  },
63
63
  "devDependencies": {
64
- "@earendil-works/pi-coding-agent": "^0.84.4",
65
- "@earendil-works/pi-tui": "^0.84.4",
64
+ "@earendil-works/pi-coding-agent": "^0.85.1",
65
+ "@earendil-works/pi-tui": "^0.85.1",
66
66
  "@types/node": "^22.10.2",
67
67
  "typebox": "^1.1.38",
68
68
  "typescript": "^5.7.2"
package/src/isolate.ts CHANGED
@@ -79,3 +79,36 @@ export function isolationNote(isolation: Isolation): string {
79
79
  `or inspect: cd "${isolation.path}" && git log --stat`,
80
80
  ].join("\n");
81
81
  }
82
+
83
+ /**
84
+ * Remove a worktree the child left untouched. An isolated run that changed
85
+ * nothing is the common case — a review, a search, a question — and keeping
86
+ * its worktree means a directory and a branch per run accumulate under
87
+ * ~/.worktrees for as long as the machine runs. A worktree with any change,
88
+ * staged or not, committed or not, is kept: that is someone's work.
89
+ *
90
+ * Returns true when it was removed. Never throws: failing to clean up must
91
+ * not fail the run that already succeeded.
92
+ */
93
+ export function removeIfUnchanged(cwd: string, isolation: Isolation): boolean {
94
+ try {
95
+ // Uncommitted work, tracked or not.
96
+ if (git(isolation.path, ["status", "--porcelain"]).trim()) return false;
97
+ // Commits made inside the worktree: the branch moved off the commit it
98
+ // was cut from. (A fresh agent/<slug> branch has no upstream, so asking
99
+ // git for "ahead of upstream" would throw here rather than answer.)
100
+ const head = git(isolation.path, ["rev-parse", "HEAD"]).trim();
101
+ const base = git(cwd, ["rev-parse", "HEAD"]).trim();
102
+ if (!head || head !== base) return false;
103
+ } catch {
104
+ // A worktree we cannot inspect is one we must not delete.
105
+ return false;
106
+ }
107
+ try {
108
+ git(cwd, ["worktree", "remove", "--force", isolation.path]);
109
+ git(cwd, ["branch", "-D", isolation.branch]);
110
+ return true;
111
+ } catch {
112
+ return false;
113
+ }
114
+ }