gsd-pi 2.65.0-dev.d0517ff → 2.66.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.
Files changed (188) hide show
  1. package/dist/resources/extensions/gsd/auto/finalize-timeout.js +2 -0
  2. package/dist/resources/extensions/gsd/auto/loop.js +2 -2
  3. package/dist/resources/extensions/gsd/auto/phases.js +48 -5
  4. package/dist/resources/extensions/gsd/auto/types.js +2 -0
  5. package/dist/resources/extensions/gsd/auto-dashboard.js +2 -1
  6. package/dist/resources/extensions/gsd/auto-start.js +134 -2
  7. package/dist/resources/extensions/gsd/bootstrap/system-context.js +3 -1
  8. package/dist/resources/extensions/gsd/commands/handlers/core.js +3 -2
  9. package/dist/resources/extensions/gsd/files.js +17 -0
  10. package/dist/resources/extensions/gsd/notification-overlay.js +1 -1
  11. package/dist/resources/extensions/gsd/notification-widget.js +2 -1
  12. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +1 -1
  13. package/dist/resources/extensions/gsd/pre-execution-checks.js +16 -2
  14. package/dist/resources/extensions/gsd/prompts/system.md +2 -2
  15. package/dist/resources/extensions/subagent/agents.js +19 -5
  16. package/dist/web/standalone/.next/BUILD_ID +1 -1
  17. package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
  18. package/dist/web/standalone/.next/build-manifest.json +3 -3
  19. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  20. package/dist/web/standalone/.next/required-server-files.json +3 -3
  21. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  22. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  23. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  24. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  32. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  33. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  35. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  42. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  43. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  44. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  45. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  46. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  47. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  48. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  49. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  50. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  51. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  53. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  60. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  80. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  90. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  96. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  110. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  112. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  114. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  116. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/index.html +1 -1
  126. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  127. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  128. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  129. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  130. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  131. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  132. package/dist/web/standalone/.next/server/app/page.js +2 -2
  133. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
  135. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  136. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  137. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  138. package/dist/web/standalone/.next/server/middleware.js +2 -2
  139. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  140. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  141. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  142. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  143. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  144. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-f2a7482d42a5614b.js → page-2f24283c162b6ab3.js} +1 -1
  145. package/dist/web/standalone/.next/static/chunks/app/{layout-a16c7a7ecdf0c2cf.js → layout-9ecfd95f343793f0.js} +1 -1
  146. package/dist/web/standalone/.next/static/chunks/app/page-62be3b5fa91e4c8f.js +1 -0
  147. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +1 -0
  148. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +1 -0
  149. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  150. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  151. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  152. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  153. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  154. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  155. package/dist/web/standalone/server.js +1 -1
  156. package/package.json +1 -1
  157. package/packages/pi-coding-agent/package.json +1 -1
  158. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  159. package/packages/pi-tui/dist/tui.js +3 -1
  160. package/packages/pi-tui/dist/tui.js.map +1 -1
  161. package/packages/pi-tui/src/tui.ts +3 -1
  162. package/pkg/package.json +1 -1
  163. package/src/resources/extensions/gsd/auto/finalize-timeout.ts +3 -0
  164. package/src/resources/extensions/gsd/auto/loop.ts +2 -2
  165. package/src/resources/extensions/gsd/auto/phases.ts +68 -3
  166. package/src/resources/extensions/gsd/auto/types.ts +5 -0
  167. package/src/resources/extensions/gsd/auto-dashboard.ts +2 -1
  168. package/src/resources/extensions/gsd/auto-start.ts +143 -0
  169. package/src/resources/extensions/gsd/bootstrap/system-context.ts +3 -1
  170. package/src/resources/extensions/gsd/commands/handlers/core.ts +3 -2
  171. package/src/resources/extensions/gsd/files.ts +19 -0
  172. package/src/resources/extensions/gsd/notification-overlay.ts +1 -1
  173. package/src/resources/extensions/gsd/notification-widget.ts +2 -1
  174. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +1 -1
  175. package/src/resources/extensions/gsd/pre-execution-checks.ts +19 -2
  176. package/src/resources/extensions/gsd/prompts/system.md +2 -2
  177. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +125 -0
  178. package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +69 -0
  179. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +11 -10
  180. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +189 -0
  181. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +66 -0
  182. package/src/resources/extensions/gsd/tests/subagent-agent-discovery.test.ts +47 -0
  183. package/src/resources/extensions/subagent/agents.ts +30 -6
  184. package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +0 -1
  185. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +0 -1
  186. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +0 -1
  187. /package/dist/web/standalone/.next/static/{JwdBI3y1H8vtBKiYvWfEK → Bdk1mnQugYZh7ZxuXUYvc}/_buildManifest.js +0 -0
  188. /package/dist/web/standalone/.next/static/{JwdBI3y1H8vtBKiYvWfEK → Bdk1mnQugYZh7ZxuXUYvc}/_ssgManifest.js +0 -0
@@ -0,0 +1,189 @@
1
+ // GSD2 — Tests for auditOrphanedMilestoneBranches bootstrap audit
2
+ import { describe, test, beforeEach, afterEach } from "node:test";
3
+ import assert from "node:assert/strict";
4
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, realpathSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { tmpdir } from "node:os";
7
+ import { execSync } from "node:child_process";
8
+
9
+ import { auditOrphanedMilestoneBranches } from "../auto-start.ts";
10
+ import { openDatabase, closeDatabase, insertMilestone, updateMilestoneStatus } from "../gsd-db.ts";
11
+
12
+ function run(cmd: string, cwd: string): string {
13
+ return execSync(cmd, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
14
+ }
15
+
16
+ /** Create a temp git repo with .gsd structure and DB. */
17
+ function createRepo(): string {
18
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "orphan-audit-test-")));
19
+ run("git init", dir);
20
+ run("git config user.email test@test.com", dir);
21
+ run("git config user.name Test", dir);
22
+
23
+ writeFileSync(join(dir, "README.md"), "# test\n");
24
+ run("git add .", dir);
25
+ run("git commit -m init", dir);
26
+ run("git branch -M main", dir);
27
+
28
+ // Create .gsd structure on disk (not tracked in git)
29
+ mkdirSync(join(dir, ".gsd", "milestones", "M001"), { recursive: true });
30
+
31
+ return dir;
32
+ }
33
+
34
+ describe("auditOrphanedMilestoneBranches", () => {
35
+ let dir: string;
36
+
37
+ beforeEach(() => {
38
+ dir = createRepo();
39
+ openDatabase(join(dir, ".gsd", "gsd.db"));
40
+ });
41
+
42
+ afterEach(() => {
43
+ closeDatabase();
44
+ rmSync(dir, { recursive: true, force: true });
45
+ });
46
+
47
+ test("no milestone branches → no-op", () => {
48
+ const result = auditOrphanedMilestoneBranches(dir, "worktree");
49
+ assert.deepStrictEqual(result.recovered, []);
50
+ assert.deepStrictEqual(result.warnings, []);
51
+ });
52
+
53
+ test("skips in none isolation mode", () => {
54
+ // Create a milestone branch that would otherwise be detected
55
+ run("git branch milestone/M001", dir);
56
+ insertMilestone({ id: "M001", title: "Test", status: "complete" });
57
+
58
+ const result = auditOrphanedMilestoneBranches(dir, "none");
59
+ assert.deepStrictEqual(result.recovered, []);
60
+ assert.deepStrictEqual(result.warnings, []);
61
+
62
+ // Branch should still exist
63
+ const branches = run("git branch --list milestone/M001", dir);
64
+ assert.ok(branches.includes("milestone/M001"), "branch should be preserved in none mode");
65
+ });
66
+
67
+ test("deletes merged branch for completed milestone", () => {
68
+ // Create milestone branch from main (so it's already merged)
69
+ run("git branch milestone/M001", dir);
70
+ insertMilestone({ id: "M001", title: "Test", status: "complete" });
71
+
72
+ const result = auditOrphanedMilestoneBranches(dir, "worktree");
73
+
74
+ assert.ok(result.recovered.length > 0, "should have recovered actions");
75
+ assert.ok(
76
+ result.recovered.some(r => r.includes("Deleted merged branch milestone/M001")),
77
+ "should report branch deletion",
78
+ );
79
+ assert.deepStrictEqual(result.warnings, []);
80
+
81
+ // Branch should be gone
82
+ const branches = run("git branch --list milestone/M001", dir);
83
+ assert.deepStrictEqual(branches, "", "branch should be deleted");
84
+ });
85
+
86
+ test("warns about unmerged branch for completed milestone", () => {
87
+ // Create milestone branch with divergent commits (not merged into main)
88
+ run("git checkout -b milestone/M001", dir);
89
+ writeFileSync(join(dir, "feature.txt"), "new feature\n");
90
+ run("git add feature.txt", dir);
91
+ run("git commit -m \"add feature on milestone branch\"", dir);
92
+ run("git checkout main", dir);
93
+
94
+ insertMilestone({ id: "M001", title: "Test", status: "complete" });
95
+
96
+ const result = auditOrphanedMilestoneBranches(dir, "worktree");
97
+
98
+ assert.deepStrictEqual(result.recovered, [], "should not delete unmerged branch");
99
+ assert.ok(result.warnings.length > 0, "should have warnings");
100
+ assert.ok(
101
+ result.warnings.some(w => w.includes("NOT merged")),
102
+ "should warn about unmerged branch",
103
+ );
104
+
105
+ // Branch should still exist (data safety)
106
+ const branches = run("git branch --list milestone/M001", dir);
107
+ assert.ok(branches.includes("milestone/M001"), "unmerged branch must be preserved");
108
+ });
109
+
110
+ test("skips active (non-complete) milestone branches", () => {
111
+ run("git branch milestone/M001", dir);
112
+ insertMilestone({ id: "M001", title: "Test", status: "active" });
113
+
114
+ const result = auditOrphanedMilestoneBranches(dir, "worktree");
115
+
116
+ assert.deepStrictEqual(result.recovered, []);
117
+ assert.deepStrictEqual(result.warnings, []);
118
+
119
+ // Branch should still exist
120
+ const branches = run("git branch --list milestone/M001", dir);
121
+ assert.ok(branches.includes("milestone/M001"), "active milestone branch should be preserved");
122
+ });
123
+
124
+ test("cleans up orphaned worktree directory for merged milestone", () => {
125
+ // Create milestone branch (merged — same as main)
126
+ run("git branch milestone/M001", dir);
127
+ insertMilestone({ id: "M001", title: "Test", status: "complete" });
128
+
129
+ // Create orphaned worktree directory
130
+ const wtDir = join(dir, ".gsd", "worktrees", "M001");
131
+ mkdirSync(wtDir, { recursive: true });
132
+ writeFileSync(join(wtDir, "leftover.txt"), "orphaned file\n");
133
+
134
+ const result = auditOrphanedMilestoneBranches(dir, "worktree");
135
+
136
+ assert.ok(result.recovered.length > 0, "should have recovered actions");
137
+ assert.ok(
138
+ result.recovered.some(r => r.includes("worktree directory")),
139
+ "should report worktree cleanup",
140
+ );
141
+
142
+ // Worktree directory should be cleaned up
143
+ assert.ok(!existsSync(wtDir), "orphaned worktree directory should be removed");
144
+ });
145
+
146
+ test("handles multiple milestones with mixed states", () => {
147
+ // M001: complete, branch merged → should clean up
148
+ run("git branch milestone/M001", dir);
149
+ insertMilestone({ id: "M001", title: "First", status: "complete" });
150
+
151
+ // M002: active, branch exists → should skip
152
+ run("git branch milestone/M002", dir);
153
+ insertMilestone({ id: "M002", title: "Second", status: "active" });
154
+
155
+ const result = auditOrphanedMilestoneBranches(dir, "worktree");
156
+
157
+ // M001 should be cleaned up
158
+ assert.ok(
159
+ result.recovered.some(r => r.includes("M001")),
160
+ "should clean up completed M001",
161
+ );
162
+
163
+ // M002 should not be touched
164
+ const branches = run("git branch --list milestone/M002", dir);
165
+ assert.ok(branches.includes("milestone/M002"), "active M002 branch should be preserved");
166
+ });
167
+
168
+ test("works in branch isolation mode", () => {
169
+ run("git branch milestone/M001", dir);
170
+ insertMilestone({ id: "M001", title: "Test", status: "complete" });
171
+
172
+ const result = auditOrphanedMilestoneBranches(dir, "branch");
173
+
174
+ assert.ok(result.recovered.length > 0, "should work in branch mode too");
175
+ assert.ok(
176
+ result.recovered.some(r => r.includes("Deleted merged branch")),
177
+ "should delete branch in branch mode",
178
+ );
179
+ });
180
+
181
+ test("handles milestone in DB but no branch (no-op)", () => {
182
+ insertMilestone({ id: "M001", title: "Test", status: "complete" });
183
+
184
+ const result = auditOrphanedMilestoneBranches(dir, "worktree");
185
+
186
+ assert.deepStrictEqual(result.recovered, []);
187
+ assert.deepStrictEqual(result.warnings, []);
188
+ });
189
+ });
@@ -1083,11 +1083,77 @@ describe("checkTaskOrdering false positive regression (#3677)", () => {
1083
1083
  const results = checkTaskOrdering(tasks, "/tmp");
1084
1084
  assert.equal(results.length, 0, "Normalized task.files path should not trigger a false positive");
1085
1085
  });
1086
+
1087
+ test("annotated inputs still trigger ordering violations against later plain outputs", () => {
1088
+ const tasks = [
1089
+ createTask({
1090
+ id: "T01",
1091
+ sequence: 0,
1092
+ files: [],
1093
+ inputs: ["`later.ts` — needed first"],
1094
+ expected_output: [],
1095
+ }),
1096
+ createTask({
1097
+ id: "T02",
1098
+ sequence: 1,
1099
+ files: [],
1100
+ inputs: [],
1101
+ expected_output: ["later.ts"],
1102
+ }),
1103
+ ];
1104
+
1105
+ const results = checkTaskOrdering(tasks, "/tmp");
1106
+ assert.equal(results.length, 1, "Annotated inputs should still match later plain expected_output entries");
1107
+ assert.equal(results[0].target, "`later.ts` — needed first");
1108
+ assert.ok(results[0].message.includes("sequence violation"));
1109
+ });
1086
1110
  });
1087
1111
 
1088
1112
  // ─── checkFilePathConsistency additional edge cases ──────────────────────────
1089
1113
 
1090
1114
  describe("checkFilePathConsistency additional edge cases", () => {
1115
+ test("annotated inputs match files that already exist on disk", () => {
1116
+ const tempDir = join(tmpdir(), `pre-exec-test-annotated-input-${Date.now()}`);
1117
+ mkdirSync(tempDir, { recursive: true });
1118
+ writeFileSync(join(tempDir, "existing.ts"), "// content");
1119
+
1120
+ try {
1121
+ const tasks = [
1122
+ createTask({
1123
+ id: "T01",
1124
+ files: [],
1125
+ inputs: ["`existing.ts` — file already on disk"],
1126
+ expected_output: [],
1127
+ }),
1128
+ ];
1129
+
1130
+ const results = checkFilePathConsistency(tasks, tempDir);
1131
+ assert.equal(results.length, 0, "Annotated inputs should resolve to the on-disk file path");
1132
+ } finally {
1133
+ rmSync(tempDir, { recursive: true, force: true });
1134
+ }
1135
+ });
1136
+
1137
+ test("plain inputs match prior annotated expected outputs", () => {
1138
+ const tasks = [
1139
+ createTask({
1140
+ id: "T01",
1141
+ files: [],
1142
+ inputs: [],
1143
+ expected_output: ["`generated.ts` — created earlier"],
1144
+ }),
1145
+ createTask({
1146
+ id: "T02",
1147
+ files: [],
1148
+ inputs: ["generated.ts"],
1149
+ expected_output: [],
1150
+ }),
1151
+ ];
1152
+
1153
+ const results = checkFilePathConsistency(tasks, "/tmp");
1154
+ assert.equal(results.length, 0, "Prior annotated expected_output entries should satisfy later plain inputs");
1155
+ });
1156
+
1091
1157
  test("inputs referencing glob-like patterns should not crash", () => {
1092
1158
  // A glob pattern in inputs is unusual but should be handled gracefully.
1093
1159
  // The file won't exist on disk, so it should produce a blocking result.
@@ -42,3 +42,50 @@ test("discoverAgents falls back to legacy .pi/agents when needed", (t) => {
42
42
  assert.equal(discovery.projectAgentsDir, agentsDir);
43
43
  assert.deepEqual(discovery.agents.map((agent) => agent.name), ["ping"]);
44
44
  });
45
+
46
+ test("discoverAgents accepts tools frontmatter as a YAML list", (t) => {
47
+ const root = makeProjectRoot(t);
48
+ const agentsDir = join(root, ".gsd", "agents");
49
+ mkdirSync(agentsDir, { recursive: true });
50
+ writeFileSync(
51
+ join(agentsDir, "reviewer.md"),
52
+ [
53
+ "---",
54
+ "name: reviewer",
55
+ "description: review agent",
56
+ "tools:",
57
+ " - bash",
58
+ " - read",
59
+ "---",
60
+ "Review code",
61
+ "",
62
+ ].join("\n"),
63
+ );
64
+
65
+ const discovery = discoverAgents(root, "project");
66
+
67
+ assert.deepEqual(discovery.agents.map((agent) => agent.name), ["reviewer"]);
68
+ assert.deepEqual(discovery.agents[0]?.tools, ["bash", "read"]);
69
+ });
70
+
71
+ test("discoverAgents still accepts comma-separated tools frontmatter", (t) => {
72
+ const root = makeProjectRoot(t);
73
+ const agentsDir = join(root, ".gsd", "agents");
74
+ mkdirSync(agentsDir, { recursive: true });
75
+ writeFileSync(
76
+ join(agentsDir, "reviewer.md"),
77
+ [
78
+ "---",
79
+ "name: reviewer",
80
+ "description: review agent",
81
+ "tools: bash, read",
82
+ "---",
83
+ "Review code",
84
+ "",
85
+ ].join("\n"),
86
+ );
87
+
88
+ const discovery = discoverAgents(root, "project");
89
+
90
+ assert.deepEqual(discovery.agents[0]?.tools, ["bash", "read"]);
91
+ });
@@ -25,6 +25,33 @@ export interface AgentDiscoveryResult {
25
25
  projectAgentsDir: string | null;
26
26
  }
27
27
 
28
+ interface AgentFrontmatter extends Record<string, unknown> {
29
+ name?: string;
30
+ description?: string;
31
+ tools?: string | string[];
32
+ model?: string;
33
+ }
34
+
35
+ function parseAgentTools(value: string | string[] | undefined): string[] | undefined {
36
+ if (typeof value === "string") {
37
+ const tools = value
38
+ .split(",")
39
+ .map((tool) => tool.trim())
40
+ .filter(Boolean);
41
+ return tools.length > 0 ? tools : undefined;
42
+ }
43
+
44
+ if (Array.isArray(value)) {
45
+ const tools = value
46
+ .flatMap((tool) => typeof tool === "string" ? tool.split(",") : [])
47
+ .map((tool) => tool.trim())
48
+ .filter(Boolean);
49
+ return tools.length > 0 ? tools : undefined;
50
+ }
51
+
52
+ return undefined;
53
+ }
54
+
28
55
  function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig[] {
29
56
  const agents: AgentConfig[] = [];
30
57
 
@@ -51,16 +78,13 @@ function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig
51
78
  continue;
52
79
  }
53
80
 
54
- const { frontmatter, body } = parseFrontmatter<Record<string, string>>(content);
81
+ const { frontmatter, body } = parseFrontmatter<AgentFrontmatter>(content);
55
82
 
56
- if (!frontmatter.name || !frontmatter.description) {
83
+ if (typeof frontmatter.name !== "string" || typeof frontmatter.description !== "string") {
57
84
  continue;
58
85
  }
59
86
 
60
- const tools = frontmatter.tools
61
- ?.split(",")
62
- .map((t: string) => t.trim())
63
- .filter(Boolean);
87
+ const tools = parseAgentTools(frontmatter.tools);
64
88
 
65
89
  agents.push({
66
90
  name: frontmatter.name,
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[8974],{2600:(e,t,n)=>{Promise.resolve().then(n.bind(n,66919))},5214:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"workAsyncStorage",{enumerable:!0,get:function(){return r.workAsyncStorageInstance}});let r=n(17828)},17828:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"workAsyncStorageInstance",{enumerable:!0,get:function(){return r}});let r=(0,n(64054).createAsyncLocalStorage)()},21957:(e,t,n)=>{"use strict";function r({moduleIds:e}){return null}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"PreloadChunks",{enumerable:!0,get:function(){return r}}),n(95155),n(47650),n(5214),n(2451),n(53887)},37206:(e,t,n)=>{"use strict";n.d(t,{default:()=>u.a});var r=n(75707),u=n.n(r)},41112:(e,t,n)=>{"use strict";function r({reason:e,children:t}){return t}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"BailoutToCSR",{enumerable:!0,get:function(){return r}}),n(1980)},64054:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n={bindSnapshot:function(){return s},createAsyncLocalStorage:function(){return a},createSnapshot:function(){return i}};for(var r in n)Object.defineProperty(t,r,{enumerable:!0,get:n[r]});let u=Object.defineProperty(Error("Invariant: AsyncLocalStorage accessed in runtime where it is not available"),"__NEXT_ERROR_CODE",{value:"E504",enumerable:!1,configurable:!0});class l{disable(){throw u}getStore(){}run(){throw u}exit(){throw u}enterWith(){throw u}static bind(e){return e}}let o="u">typeof globalThis&&globalThis.AsyncLocalStorage;function a(){return o?new o:new l}function s(e){return o?o.bind(e):l.bind(e)}function i(){return o?o.snapshot():function(e,...t){return e(...t)}}},66919:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>l});var r=n(95155);let u=(0,n(37206).default)(()=>Promise.all([n.e(1838),n.e(6079),n.e(4986),n.e(2008),n.e(6502)]).then(n.bind(n,46502)).then(e=>e.GSDAppShell),{loadableGenerated:{webpack:()=>[46502]},ssr:!1,loading:()=>(0,r.jsx)("div",{className:"flex h-screen items-center justify-center bg-background text-sm text-muted-foreground",children:"Loading workspace…"})});function l(){return(0,r.jsx)(u,{})}},68635:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return s}});let r=n(95155),u=n(12115),l=n(41112);function o(e){return{default:e&&"default"in e?e.default:e}}n(21957);let a={loader:()=>Promise.resolve(o(()=>null)),loading:null,ssr:!0},s=function(e){let t={...a,...e},n=(0,u.lazy)(()=>t.loader().then(o)),s=t.loading;function i(e){let o=s?(0,r.jsx)(s,{isLoading:!0,pastDelay:!0,error:null}):null,a=!t.ssr||!!t.loading,i=a?u.Suspense:u.Fragment,c=t.ssr?(0,r.jsxs)(r.Fragment,{children:[null,(0,r.jsx)(n,{...e})]}):(0,r.jsx)(l.BailoutToCSR,{reason:"next/dynamic",children:(0,r.jsx)(n,{...e})});return(0,r.jsx)(i,{...a?{fallback:o}:{},children:c})}return i.displayName="LoadableComponent",i}},75707:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return u}});let r=n(73623)._(n(68635));function u(e,t){let n={};"function"==typeof e&&(n.loader=e);let u={...n,...t};return(0,r.default)({...u,modules:u.loadableGenerated?.modules})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)}},e=>{e.O(0,[8441,3794,7358],()=>e(e.s=2600)),_N_E=e.O()}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[7358],{19393:()=>{},55548:(e,s,n)=>{Promise.resolve().then(n.t.bind(n,27123,23)),Promise.resolve().then(n.t.bind(n,61304,23)),Promise.resolve().then(n.t.bind(n,78616,23)),Promise.resolve().then(n.t.bind(n,64777,23)),Promise.resolve().then(n.t.bind(n,57121,23)),Promise.resolve().then(n.t.bind(n,74581,23)),Promise.resolve().then(n.t.bind(n,90484,23)),Promise.resolve().then(n.bind(n,86869))}},e=>{var s=s=>e(e.s=s);e.O(0,[8441,3794],()=>(s(83861),s(55548))),_N_E=e.O()}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[9337],{43946:(e,s,_)=>{Promise.resolve().then(_.t.bind(_,27123,23))}},e=>{e.O(0,[8441,3794,7358],()=>e(e.s=43946)),_N_E=e.O()}]);