gsd-pi 2.80.0-dev.8b3a59f5c → 2.80.0-dev.b2a374d5b

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 (222) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/GSD-WORKFLOW.md +2 -2
  3. package/dist/resources/extensions/gsd/auto/loop.js +32 -1
  4. package/dist/resources/extensions/gsd/auto/phases.js +37 -30
  5. package/dist/resources/extensions/gsd/auto-dispatch.js +10 -0
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +10 -10
  7. package/dist/resources/extensions/gsd/auto-prompts.js +111 -1
  8. package/dist/resources/extensions/gsd/auto-start.js +2 -3
  9. package/dist/resources/extensions/gsd/auto.js +16 -3
  10. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +21 -0
  11. package/dist/resources/extensions/gsd/clean-root-preflight.js +42 -4
  12. package/dist/resources/extensions/gsd/commands/dispatcher.js +5 -0
  13. package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
  14. package/dist/resources/extensions/gsd/detection.js +106 -0
  15. package/dist/resources/extensions/gsd/guided-flow.js +63 -12
  16. package/dist/resources/extensions/gsd/migrate/command.js +40 -1
  17. package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
  18. package/dist/resources/extensions/gsd/planning-path-scope.js +26 -0
  19. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  20. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  21. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  22. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  23. package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
  24. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  25. package/dist/resources/extensions/gsd/quick.js +34 -2
  26. package/dist/resources/extensions/gsd/safety/evidence-collector.js +10 -2
  27. package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
  28. package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
  29. package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
  30. package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
  31. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  32. package/dist/web/standalone/.next/BUILD_ID +1 -1
  33. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  34. package/dist/web/standalone/.next/build-manifest.json +2 -2
  35. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  36. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/index.html +1 -1
  53. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  60. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  61. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  62. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  63. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  64. package/package.json +4 -2
  65. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  66. package/packages/pi-coding-agent/dist/core/agent-session.js +3 -0
  67. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  68. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +32 -0
  69. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  70. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
  71. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  72. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
  73. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  74. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
  75. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
  76. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
  77. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
  78. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts +15 -0
  79. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
  80. package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
  81. package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
  82. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
  83. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
  84. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
  85. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js.map +1 -0
  86. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +3 -0
  87. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  88. package/packages/pi-coding-agent/dist/core/extensions/runner.js +17 -1
  89. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  90. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +99 -0
  91. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  92. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
  93. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  95. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
  96. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  97. package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
  98. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  99. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js +6 -4
  100. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +25 -20
  102. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  103. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +2 -2
  104. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -1
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +2 -1
  109. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +7 -6
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -1
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +26 -13
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  117. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +15 -5
  118. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  119. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
  120. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  122. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
  123. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +36 -27
  126. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +21 -0
  128. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
  130. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
  131. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +10 -0
  132. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
  133. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
  134. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
  135. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
  136. package/packages/pi-coding-agent/src/core/agent-session.ts +3 -0
  137. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +38 -0
  138. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
  139. package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
  140. package/packages/pi-coding-agent/src/core/db-snapshot.test.ts +32 -0
  141. package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -0
  142. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +110 -0
  143. package/packages/pi-coding-agent/src/core/extensions/runner.ts +19 -1
  144. package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
  145. package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
  146. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.ts +7 -5
  147. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +25 -20
  148. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +2 -2
  149. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +1 -0
  150. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +2 -1
  151. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +10 -9
  152. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +33 -13
  153. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
  154. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +24 -7
  155. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
  156. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +36 -27
  157. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +25 -0
  158. package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +14 -0
  159. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
  160. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  161. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  162. package/packages/pi-tui/dist/tui.js +17 -7
  163. package/packages/pi-tui/dist/tui.js.map +1 -1
  164. package/packages/pi-tui/src/tui.ts +19 -7
  165. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  166. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  167. package/pkg/dist/modes/interactive/theme/themes.js +36 -27
  168. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  169. package/src/resources/GSD-WORKFLOW.md +2 -2
  170. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  171. package/src/resources/extensions/gsd/auto/loop.ts +50 -8
  172. package/src/resources/extensions/gsd/auto/phases.ts +42 -28
  173. package/src/resources/extensions/gsd/auto-dispatch.ts +17 -0
  174. package/src/resources/extensions/gsd/auto-post-unit.ts +10 -10
  175. package/src/resources/extensions/gsd/auto-prompts.ts +116 -1
  176. package/src/resources/extensions/gsd/auto-start.ts +7 -6
  177. package/src/resources/extensions/gsd/auto.ts +23 -3
  178. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
  179. package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
  180. package/src/resources/extensions/gsd/commands/dispatcher.ts +6 -0
  181. package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
  182. package/src/resources/extensions/gsd/detection.ts +128 -0
  183. package/src/resources/extensions/gsd/guided-flow.ts +66 -12
  184. package/src/resources/extensions/gsd/migrate/command.ts +47 -1
  185. package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
  186. package/src/resources/extensions/gsd/planning-path-scope.ts +35 -0
  187. package/src/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  188. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  189. package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  190. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  191. package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
  192. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  193. package/src/resources/extensions/gsd/quick.ts +37 -2
  194. package/src/resources/extensions/gsd/safety/evidence-collector.ts +11 -2
  195. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1 -1
  196. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +88 -2
  197. package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +55 -0
  198. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
  199. package/src/resources/extensions/gsd/tests/detection.test.ts +140 -0
  200. package/src/resources/extensions/gsd/tests/has-pending-deep-stage.test.ts +33 -1
  201. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
  202. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
  203. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +50 -0
  204. package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
  205. package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
  206. package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
  207. package/src/resources/extensions/gsd/tests/right-sized-workflow-prompts.test.ts +192 -0
  208. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +29 -0
  209. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +38 -0
  210. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +63 -2
  211. package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
  212. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +7 -0
  213. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
  214. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +37 -6
  215. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +7 -0
  216. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +9 -2
  217. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
  218. package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
  219. package/src/resources/extensions/gsd/unit-runtime.ts +14 -0
  220. package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
  221. /package/dist/web/standalone/.next/static/{k-MDjzPMF62Rg1-FR437h → FU0iL9dhdc2IBzEYBDOlY}/_buildManifest.js +0 -0
  222. /package/dist/web/standalone/.next/static/{k-MDjzPMF62Rg1-FR437h → FU0iL9dhdc2IBzEYBDOlY}/_ssgManifest.js +0 -0
@@ -3,6 +3,8 @@ import assert from "node:assert/strict";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { resolve } from "node:path";
5
5
 
6
+ import { _withDetachedAutoKeepaliveForTest } from "../auto.ts";
7
+
6
8
  const gsdDir = resolve(import.meta.dirname, "..");
7
9
 
8
10
  function readGsdFile(relativePath: string): string {
@@ -94,6 +96,23 @@ test("auto bootstrap validates blocked directories before touching .gsd migratio
94
96
  );
95
97
  });
96
98
 
99
+ test("fresh start registers the auto worker before bootstrap enters worktree flow (#5405)", () => {
100
+ const autoSrc = readGsdFile("auto.ts");
101
+ const startAutoIdx = autoSrc.indexOf("export async function startAuto(");
102
+ const startAutoBody = autoSrc.slice(startAutoIdx);
103
+
104
+ const preBootstrapRegisterIdx = startAutoBody.indexOf("registerAutoWorkerForSession(s, base);");
105
+ const bootstrapCallIdx = startAutoBody.indexOf("const ready = await bootstrapAutoSession(");
106
+
107
+ assert.ok(startAutoIdx > -1, "startAuto should exist");
108
+ assert.ok(preBootstrapRegisterIdx > -1, "startAuto should register worker before bootstrap");
109
+ assert.ok(bootstrapCallIdx > -1, "startAuto should call bootstrapAutoSession");
110
+ assert.ok(
111
+ preBootstrapRegisterIdx < bootstrapCallIdx,
112
+ "worker registration must happen before bootstrap so enterMilestone can claim milestone leases on first entry",
113
+ );
114
+ });
115
+
97
116
  test("startAutoDetached reports failures asynchronously (#3733)", () => {
98
117
  const autoSrc = readGsdFile("auto.ts");
99
118
 
@@ -102,8 +121,8 @@ test("startAutoDetached reports failures asynchronously (#3733)", () => {
102
121
  "auto.ts should export startAutoDetached",
103
122
  );
104
123
  assert.ok(
105
- autoSrc.includes("void startAuto(ctx, pi, base, verboseMode, options).catch"),
106
- "startAutoDetached should launch startAuto without awaiting it",
124
+ autoSrc.includes("void withDetachedAutoKeepalive(startAuto(ctx, pi, base, verboseMode, options)).catch"),
125
+ "startAutoDetached should launch startAuto without awaiting it and keep the process alive",
107
126
  );
108
127
  assert.ok(
109
128
  autoSrc.includes("ctx.ui.notify(`Auto-start failed: ${message}`, \"error\")"),
@@ -111,6 +130,48 @@ test("startAutoDetached reports failures asynchronously (#3733)", () => {
111
130
  );
112
131
  });
113
132
 
133
+ test("detached auto-start keeps a ref'ed handle until the run settles", async () => {
134
+ const originalSetInterval = globalThis.setInterval;
135
+ const originalClearInterval = globalThis.clearInterval;
136
+ let intervalCreated = false;
137
+ let intervalCleared = false;
138
+ let createdHandle: NodeJS.Timeout | undefined;
139
+ let resolveRun!: () => void;
140
+ const run = new Promise<void>((resolve) => {
141
+ resolveRun = resolve;
142
+ });
143
+
144
+ globalThis.setInterval = ((handler: TimerHandler, timeout?: number, ...args: unknown[]) => {
145
+ intervalCreated = true;
146
+ assert.equal(timeout, 30_000);
147
+ void handler;
148
+ void args;
149
+ createdHandle = originalSetInterval(() => {}, 1_000_000);
150
+ assert.equal(createdHandle.hasRef(), true, "detached auto keepalive must be ref'ed");
151
+ return createdHandle;
152
+ }) as unknown as typeof setInterval;
153
+
154
+ globalThis.clearInterval = ((handle?: NodeJS.Timeout | number | string) => {
155
+ if (handle === createdHandle) intervalCleared = true;
156
+ return originalClearInterval(handle);
157
+ }) as unknown as typeof clearInterval;
158
+
159
+ try {
160
+ const heldRun = _withDetachedAutoKeepaliveForTest(run);
161
+ assert.equal(intervalCreated, true, "keepalive interval should start immediately");
162
+ assert.equal(intervalCleared, false, "keepalive should remain active while auto-mode is running");
163
+
164
+ resolveRun();
165
+ await heldRun;
166
+
167
+ assert.equal(intervalCleared, true, "keepalive interval should clear when auto-mode settles");
168
+ } finally {
169
+ if (createdHandle) originalClearInterval(createdHandle);
170
+ globalThis.setInterval = originalSetInterval;
171
+ globalThis.clearInterval = originalClearInterval;
172
+ }
173
+ });
174
+
114
175
  test("detached auto-start preserves milestone lock across pause/stop cleanup (#3733)", () => {
115
176
  const autoSrc = readGsdFile("auto.ts");
116
177
  const sessionSrc = readGsdFile("auto/session.ts");
@@ -44,4 +44,13 @@ describe('status opens DB before deriveState (#3691)', () => {
44
44
  assert.match(quickSrc, /getIsolationMode\(\)\s*!==\s*"none"/,
45
45
  'quick.ts should compare isolation mode against "none"');
46
46
  });
47
+
48
+ test('quick task prompt handles external .gsd without staging quick files', () => {
49
+ assert.match(quickSrc, /isExternalGsdRoot/,
50
+ 'quick.ts should detect whether .gsd resolves outside the project repo');
51
+ assert.match(quickSrc, /do not stage or commit `\.gsd\/quick\/\.\.\.`/,
52
+ 'external-state quick tasks must tell the agent not to stage .gsd/quick files');
53
+ assert.match(quickSrc, /nothing in the project repo to commit/,
54
+ 'external-state quick tasks should allow summary-only work without a git commit');
55
+ });
47
56
  });
@@ -5,6 +5,7 @@ import {
5
5
  clearUnitRuntimeRecord,
6
6
  formatExecuteTaskRecoveryStatus,
7
7
  inspectExecuteTaskDurability,
8
+ isInFlightRuntimePhase,
8
9
  readUnitRuntimeRecord,
9
10
  writeUnitRuntimeRecord,
10
11
  } from "../unit-runtime.ts";
@@ -22,6 +23,12 @@ writeFileSync(
22
23
  "utf-8",
23
24
  );
24
25
 
26
+ console.log("\n=== in-flight runtime phases ===");
27
+ {
28
+ assert.equal(isInFlightRuntimePhase("crashed"), true, "crashed records remain recoverable");
29
+ assert.equal(isInFlightRuntimePhase("finalized"), false, "finalized records are terminal");
30
+ }
31
+
25
32
  console.log("\n=== runtime record write/read/update ===");
26
33
  {
27
34
  const first = writeUnitRuntimeRecord(base, "execute-task", "M100/S02/T09", 1000, { phase: "dispatched" });
@@ -118,7 +118,7 @@ test("guided flow checks pending deep setup before plan-v2 gate", () => {
118
118
  const source = readFileSync(join(gsdDir, "guided-flow.ts"), "utf-8");
119
119
  const showSmartEntryIdx = source.indexOf("export async function showSmartEntry");
120
120
  assert.notEqual(showSmartEntryIdx, -1);
121
- const deepIdx = source.indexOf("hasPendingDeepStage(prefs, basePath)", showSmartEntryIdx);
121
+ const deepIdx = source.indexOf("shouldRunDeepProjectSetup(state, prefs, basePath)", showSmartEntryIdx);
122
122
  const planIdx = source.indexOf("runPlanV2Gate(ctx, basePath, state)", showSmartEntryIdx);
123
123
  assert.ok(
124
124
  deepIdx > -1 && planIdx > -1 && deepIdx < planIdx,
@@ -9,12 +9,12 @@
9
9
 
10
10
  import { describe, test, beforeEach, afterEach } from "node:test";
11
11
  import assert from "node:assert/strict";
12
- import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readdirSync } from "node:fs";
12
+ import { existsSync, mkdtempSync, mkdirSync, readFileSync, writeFileSync, rmSync, readdirSync } from "node:fs";
13
13
  import { join } from "node:path";
14
14
  import { tmpdir } from "node:os";
15
15
  import { execSync } from "node:child_process";
16
16
 
17
- import { PROJECT_FILES } from "../detection.js";
17
+ import { PROJECT_FILES, classifyProject } from "../detection.js";
18
18
 
19
19
  // ─── Helpers ─────────────────────────────────────────────────────────────────
20
20
 
@@ -30,6 +30,14 @@ function createGitRepo(): string {
30
30
  return dir;
31
31
  }
32
32
 
33
+ function createEmptyGitRepo(): string {
34
+ const dir = mkdtempSync(join(tmpdir(), "wt-dispatch-test-empty-"));
35
+ execSync("git init", { cwd: dir, stdio: "ignore" });
36
+ execSync("git config user.email test@test.com", { cwd: dir, stdio: "ignore" });
37
+ execSync("git config user.name Test", { cwd: dir, stdio: "ignore" });
38
+ return dir;
39
+ }
40
+
33
41
  /**
34
42
  * Simulate the health check logic from auto/phases.ts.
35
43
  *
@@ -64,8 +72,6 @@ function hasXcodeBundle(basePath: string): boolean {
64
72
  } catch { return false; }
65
73
  }
66
74
 
67
- import { existsSync } from "node:fs";
68
-
69
75
  // ─── Tests ───────────────────────────────────────────────────────────────────
70
76
 
71
77
  test("PROJECT_FILES is exported and contains expected multi-ecosystem entries", () => {
@@ -80,6 +86,21 @@ test("PROJECT_FILES is exported and contains expected multi-ecosystem entries",
80
86
  assert.ok(PROJECT_FILES.includes("Package.swift"), "includes Swift marker");
81
87
  });
82
88
 
89
+ test("runUnitPhase fails closed when classification returns invalid-repo", () => {
90
+ const source = readFileSync(join(process.cwd(), "src/resources/extensions/gsd/auto/phases.ts"), "utf-8");
91
+ const invalidRepoBranch = source.slice(
92
+ source.indexOf('projectClassification.kind === "invalid-repo"'),
93
+ source.indexOf('projectClassification.kind === "greenfield"'),
94
+ );
95
+
96
+ assert.match(invalidRepoBranch, /projectClassification\.reason === "missing \.git" && hasGit/);
97
+ assert.match(invalidRepoBranch, /project classification could not confirm \.git/);
98
+ assert.match(invalidRepoBranch, /ctx\.ui\.notify\(msg,\s*"error"\)/);
99
+ assert.match(invalidRepoBranch, /await deps\.stopAuto\(ctx,\s*pi,\s*msg\)/);
100
+ assert.match(invalidRepoBranch, /return \{ action: "break", reason: "worktree-invalid" \}/);
101
+ assert.match(invalidRepoBranch, /classified as invalid-repo/);
102
+ });
103
+
83
104
  describe("health check with git repo", () => {
84
105
  let dir: string;
85
106
  beforeEach(() => { dir = createGitRepo(); });
@@ -132,8 +153,18 @@ describe("health check with git repo", () => {
132
153
  });
133
154
 
134
155
  test("health check passes for empty git repo (greenfield project)", () => {
135
- assert.ok(wouldPassHealthCheck(dir, existsSync), "empty git repo should pass health check (greenfield)");
136
- assert.ok(!hasRecognizedProjectFiles(dir, existsSync), "empty git repo has no recognized project files");
156
+ const empty = createEmptyGitRepo();
157
+ try {
158
+ assert.ok(wouldPassHealthCheck(empty, existsSync), "empty git repo should pass health check (greenfield)");
159
+ assert.equal(classifyProject(empty).kind, "greenfield");
160
+ } finally {
161
+ rmSync(empty, { recursive: true, force: true });
162
+ }
163
+ });
164
+
165
+ test("health check classifies README-only repo as untyped existing, not greenfield", () => {
166
+ assert.ok(wouldPassHealthCheck(dir, existsSync), "README-only repo should pass health check");
167
+ assert.equal(classifyProject(dir).kind, "untyped-existing");
137
168
  });
138
169
  });
139
170
 
@@ -235,4 +235,11 @@ describe("removeWorktree — missing worktree", () => {
235
235
  "should not throw when worktree does not exist",
236
236
  );
237
237
  });
238
+
239
+ test("deleteBranch is quiet when the branch is already gone", () => {
240
+ assert.doesNotThrow(
241
+ () => removeWorktree(base, "nonexistent", { deleteBranch: true }),
242
+ "missing branch should be treated as already cleaned up",
243
+ );
244
+ });
238
245
  });
@@ -56,11 +56,11 @@ test("#2616: findNestedGitDirs ignores .git files (worktree pointers)", (t) => {
56
56
  );
57
57
  });
58
58
 
59
- test("#2616: findNestedGitDirs skips excluded directories (node_modules, .gsd, target)", (t) => {
59
+ test("#2616: findNestedGitDirs skips excluded directories (node_modules, .gsd, .bg-shell, target)", (t) => {
60
60
  const root = makeRoot(t);
61
61
 
62
62
  // All three of these contain a .git *directory*, but the scan must skip them.
63
- for (const excluded of ["node_modules", ".gsd", "target"]) {
63
+ for (const excluded of ["node_modules", ".gsd", ".bg-shell", "target"]) {
64
64
  const inside = join(root, excluded, "vendored-pkg");
65
65
  mkdirSync(join(inside, ".git"), { recursive: true });
66
66
  }
@@ -73,6 +73,13 @@ test("#2616: findNestedGitDirs skips excluded directories (node_modules, .gsd, t
73
73
  );
74
74
  });
75
75
 
76
+ test("#2616: findNestedGitDirs ignores normal missing child .git probes", (t) => {
77
+ const root = makeRoot(t);
78
+ mkdirSync(join(root, "plain-dir"), { recursive: true });
79
+
80
+ assert.deepEqual(findNestedGitDirs(root), []);
81
+ });
82
+
76
83
  test("#2616: findNestedGitDirs finds deeply nested repos", (t) => {
77
84
  const root = makeRoot(t);
78
85
  const deep = join(root, "a", "b", "c", "scaffolded");
@@ -18,6 +18,7 @@ import { renderAllProjections } from "../workflow-projections.js";
18
18
  import { writeManifest } from "../workflow-manifest.js";
19
19
  import { appendEvent } from "../workflow-events.js";
20
20
  import { logWarning } from "../workflow-logger.js";
21
+ import { validatePlanningPathScope } from "../planning-path-scope.js";
21
22
 
22
23
  export interface PlanSliceTaskInput {
23
24
  taskId: string;
@@ -141,6 +142,18 @@ export async function handlePlanSlice(
141
142
  return { error: `validation failed: ${(err as Error).message}` };
142
143
  }
143
144
 
145
+ const pathScopeError = validatePlanningPathScope(
146
+ basePath,
147
+ params.tasks.flatMap((task, index) => [
148
+ { field: `tasks[${index}].files`, values: task.files },
149
+ { field: `tasks[${index}].inputs`, values: task.inputs },
150
+ { field: `tasks[${index}].expectedOutput`, values: task.expectedOutput },
151
+ ]),
152
+ );
153
+ if (pathScopeError) {
154
+ return { error: `validation failed: ${pathScopeError}` };
155
+ }
156
+
144
157
  // ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
145
158
  // Guards must be inside the transaction so the state they check cannot
146
159
  // change between the read and the write (#2723).
@@ -8,6 +8,7 @@ import { renderAllProjections } from "../workflow-projections.js";
8
8
  import { writeManifest } from "../workflow-manifest.js";
9
9
  import { appendEvent } from "../workflow-events.js";
10
10
  import { logWarning } from "../workflow-logger.js";
11
+ import { validatePlanningPathScope } from "../planning-path-scope.js";
11
12
 
12
13
  export interface PlanTaskParams {
13
14
  milestoneId: string;
@@ -66,6 +67,15 @@ export async function handlePlanTask(
66
67
  return { error: `validation failed: ${(err as Error).message}` };
67
68
  }
68
69
 
70
+ const pathScopeError = validatePlanningPathScope(basePath, [
71
+ { field: "files", values: params.files },
72
+ { field: "inputs", values: params.inputs },
73
+ { field: "expectedOutput", values: params.expectedOutput },
74
+ ]);
75
+ if (pathScopeError) {
76
+ return { error: `validation failed: ${pathScopeError}` };
77
+ }
78
+
69
79
  // ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
70
80
  // Guards must be inside the transaction so the state they check cannot
71
81
  // change between the read and the write (#2723).
@@ -67,11 +67,25 @@ export type UnitRuntimePhase =
67
67
  | "wrapup-warning-sent"
68
68
  | "timeout"
69
69
  | "finalize-timeout"
70
+ | "crashed"
70
71
  | "recovered"
71
72
  | "finalized"
72
73
  | "paused"
73
74
  | "skipped";
74
75
 
76
+ export const IN_FLIGHT_RUNTIME_PHASES: ReadonlySet<UnitRuntimePhase> = new Set([
77
+ "dispatched",
78
+ "wrapup-warning-sent",
79
+ "timeout",
80
+ "finalize-timeout",
81
+ "crashed",
82
+ "paused",
83
+ ]);
84
+
85
+ export function isInFlightRuntimePhase(phase: UnitRuntimePhase): boolean {
86
+ return IN_FLIGHT_RUNTIME_PHASES.has(phase);
87
+ }
88
+
75
89
  export interface ExecuteTaskRecoveryStatus {
76
90
  planPath: string;
77
91
  summaryPath: string;
@@ -70,6 +70,15 @@ export interface WorktreeDiffSummary {
70
70
  removed: string[];
71
71
  }
72
72
 
73
+ function deleteBranchIfPresent(basePath: string, branch: string, warningPrefix: string): void {
74
+ try {
75
+ if (!nativeBranchExists(basePath, branch)) return;
76
+ nativeBranchDelete(basePath, branch, true);
77
+ } catch (e) {
78
+ logWarning("worktree", `${warningPrefix}: ${(e as Error).message}`);
79
+ }
80
+ }
81
+
73
82
  // ─── Path Helpers ──────────────────────────────────────────────────────────
74
83
 
75
84
  function normalizePathForComparison(path: string): string {
@@ -408,7 +417,7 @@ export function listWorktrees(basePath: string): WorktreeInfo[] {
408
417
 
409
418
  /** Directories to skip when scanning for nested .git dirs. */
410
419
  const NESTED_GIT_SKIP_DIRS = new Set([
411
- ".git", ".gsd", "node_modules", ".next", ".nuxt", "dist", "build",
420
+ ".git", ".gsd", ".bg-shell", "node_modules", ".next", ".nuxt", "dist", "build",
412
421
  "__pycache__", ".tox", ".venv", "venv", "target", "vendor",
413
422
  ]);
414
423
 
@@ -462,7 +471,9 @@ export function findNestedGitDirs(rootPath: string): string[] {
462
471
  continue;
463
472
  }
464
473
  } catch (e) {
465
- logWarning("worktree", `existsSync/.git check failed for ${fullPath}: ${(e as Error).message}`);
474
+ if ((e as NodeJS.ErrnoException).code !== "ENOENT") {
475
+ logWarning("worktree", `existsSync/.git check failed for ${fullPath}: ${(e as Error).message}`);
476
+ }
466
477
  }
467
478
 
468
479
  walk(fullPath, depth + 1);
@@ -535,7 +546,7 @@ export function removeWorktree(
535
546
  if (!existsSync(wtPath)) {
536
547
  nativeWorktreePrune(basePath);
537
548
  if (deleteBranch) {
538
- try { nativeBranchDelete(basePath, branch, true); } catch (e) { logWarning("worktree", `nativeBranchDelete failed: ${(e as Error).message}`); }
549
+ deleteBranchIfPresent(basePath, branch, "nativeBranchDelete failed");
539
550
  }
540
551
  return;
541
552
  }
@@ -670,7 +681,7 @@ export function removeWorktree(
670
681
  nativeWorktreePrune(basePath);
671
682
 
672
683
  if (deleteBranch) {
673
- try { nativeBranchDelete(basePath, branch, true); } catch (e) { logWarning("worktree", `final branch delete failed: ${(e as Error).message}`); }
684
+ deleteBranchIfPresent(basePath, branch, "final branch delete failed");
674
685
  }
675
686
  }
676
687