gsd-pi 2.71.0-dev.977c553 → 2.71.0-dev.e17e0ce

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 (258) hide show
  1. package/dist/cli.js +12 -3
  2. package/dist/headless-events.d.ts +2 -0
  3. package/dist/headless-events.js +7 -0
  4. package/dist/headless.js +16 -3
  5. package/dist/mcp-server.js +6 -6
  6. package/dist/provider-migrations.d.ts +10 -0
  7. package/dist/provider-migrations.js +12 -0
  8. package/dist/resource-loader.js +139 -13
  9. package/dist/resources/GSD-WORKFLOW.md +1 -1
  10. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +10 -4
  11. package/dist/resources/extensions/gsd/auto/infra-errors.js +34 -0
  12. package/dist/resources/extensions/gsd/auto/loop.js +32 -1
  13. package/dist/resources/extensions/gsd/auto/phases.js +1 -1
  14. package/dist/resources/extensions/gsd/auto/session.js +11 -0
  15. package/dist/resources/extensions/gsd/auto-dashboard.js +22 -16
  16. package/dist/resources/extensions/gsd/auto-model-selection.js +10 -2
  17. package/dist/resources/extensions/gsd/auto-start.js +13 -6
  18. package/dist/resources/extensions/gsd/auto-tool-tracking.js +1 -1
  19. package/dist/resources/extensions/gsd/auto-worktree.js +1 -1
  20. package/dist/resources/extensions/gsd/auto.js +52 -0
  21. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +2 -0
  22. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +66 -51
  23. package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -0
  24. package/dist/resources/extensions/gsd/commands/context.js +15 -6
  25. package/dist/resources/extensions/gsd/commands/dispatcher.js +12 -2
  26. package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -33
  27. package/dist/resources/extensions/gsd/commands/handlers/core.js +56 -11
  28. package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +15 -6
  29. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +4 -10
  30. package/dist/resources/extensions/gsd/dashboard-overlay.js +8 -3
  31. package/dist/resources/extensions/gsd/dispatch-guard.js +18 -1
  32. package/dist/resources/extensions/gsd/error-classifier.js +1 -1
  33. package/dist/resources/extensions/gsd/forensics.js +19 -6
  34. package/dist/resources/extensions/gsd/guided-flow.js +5 -10
  35. package/dist/resources/extensions/gsd/metrics.js +1 -0
  36. package/dist/resources/extensions/gsd/milestone-actions.js +10 -4
  37. package/dist/resources/extensions/gsd/notification-overlay.js +20 -5
  38. package/dist/resources/extensions/gsd/notification-store.js +51 -1
  39. package/dist/resources/extensions/gsd/notification-widget.js +5 -13
  40. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +8 -3
  41. package/dist/resources/extensions/gsd/pre-execution-checks.js +35 -2
  42. package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -2
  43. package/dist/resources/extensions/gsd/prompts/discuss.md +2 -0
  44. package/dist/resources/extensions/gsd/prompts/execute-task.md +20 -19
  45. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  46. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
  47. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  48. package/dist/resources/extensions/gsd/prompts/queue.md +3 -2
  49. package/dist/resources/extensions/gsd/prompts/system.md +1 -0
  50. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -1
  51. package/dist/resources/extensions/gsd/session-model-override.js +25 -0
  52. package/dist/resources/extensions/gsd/shortcut-defs.js +34 -0
  53. package/dist/resources/skills/create-skill/SKILL.md +2 -0
  54. package/dist/web/standalone/.next/BUILD_ID +1 -1
  55. package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
  56. package/dist/web/standalone/.next/build-manifest.json +2 -2
  57. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  58. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/index.html +1 -1
  75. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
  82. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  84. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  85. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  86. package/package.json +1 -1
  87. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  88. package/packages/mcp-server/dist/workflow-tools.js +21 -11
  89. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  90. package/packages/mcp-server/src/workflow-tools.test.ts +110 -0
  91. package/packages/mcp-server/src/workflow-tools.ts +31 -11
  92. package/packages/pi-ai/dist/providers/amazon-bedrock.js +11 -2
  93. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  94. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts +2 -0
  95. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts.map +1 -0
  96. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +20 -0
  97. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -0
  98. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +4 -1
  99. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  100. package/packages/pi-ai/dist/providers/anthropic-shared.js +8 -3
  101. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  102. package/packages/pi-ai/dist/providers/anthropic-shared.test.js +44 -1
  103. package/packages/pi-ai/dist/providers/anthropic-shared.test.js.map +1 -1
  104. package/packages/pi-ai/dist/providers/anthropic.d.ts +2 -1
  105. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  106. package/packages/pi-ai/dist/providers/anthropic.js +7 -4
  107. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  108. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  109. package/packages/pi-ai/dist/providers/openai-completions.js +11 -0
  110. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  111. package/packages/pi-ai/src/providers/amazon-bedrock.ts +13 -1
  112. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +32 -0
  113. package/packages/pi-ai/src/providers/anthropic-shared.test.ts +55 -1
  114. package/packages/pi-ai/src/providers/anthropic-shared.ts +14 -3
  115. package/packages/pi-ai/src/providers/anthropic.ts +8 -4
  116. package/packages/pi-ai/src/providers/openai-completions.ts +14 -0
  117. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts +2 -0
  118. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts.map +1 -0
  119. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js +61 -0
  120. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js.map +1 -0
  121. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/agent-session.js +2 -1
  123. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +10 -0
  125. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  126. package/packages/pi-coding-agent/dist/core/auth-storage.js +27 -0
  127. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +85 -0
  129. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts +2 -0
  131. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts.map +1 -0
  132. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js +64 -0
  133. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js.map +1 -0
  134. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/model-resolver.js +22 -18
  136. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/core/sdk.d.ts +11 -0
  138. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/sdk.js +38 -5
  140. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts +2 -0
  142. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts.map +1 -0
  143. package/packages/pi-coding-agent/dist/core/sdk.test.js +71 -0
  144. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -0
  145. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  146. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  147. package/packages/pi-coding-agent/dist/index.js +1 -1
  148. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  149. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts +2 -0
  150. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts.map +1 -0
  151. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js +13 -0
  152. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js.map +1 -0
  153. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts +4 -0
  154. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  155. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +24 -2
  156. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  157. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +4 -0
  158. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  159. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +43 -0
  160. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  161. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  162. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +7 -2
  163. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  164. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  165. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -3
  166. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  167. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +4 -2
  168. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  169. package/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts +70 -0
  170. package/packages/pi-coding-agent/src/core/agent-session.ts +2 -1
  171. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +108 -0
  172. package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -0
  173. package/packages/pi-coding-agent/src/core/model-resolver-initial-model-auth.test.ts +78 -0
  174. package/packages/pi-coding-agent/src/core/model-resolver.ts +22 -18
  175. package/packages/pi-coding-agent/src/core/sdk.test.ts +89 -0
  176. package/packages/pi-coding-agent/src/core/sdk.ts +45 -9
  177. package/packages/pi-coding-agent/src/index.ts +1 -0
  178. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/login-dialog.test.ts +24 -0
  179. package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +30 -2
  180. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +47 -0
  181. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +7 -2
  182. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +4 -3
  183. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +4 -2
  184. package/src/resources/GSD-WORKFLOW.md +1 -1
  185. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +13 -5
  186. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +56 -4
  187. package/src/resources/extensions/gsd/auto/infra-errors.ts +38 -0
  188. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -0
  189. package/src/resources/extensions/gsd/auto/loop.ts +45 -1
  190. package/src/resources/extensions/gsd/auto/phases.ts +2 -0
  191. package/src/resources/extensions/gsd/auto/session.ts +11 -0
  192. package/src/resources/extensions/gsd/auto-dashboard.ts +29 -18
  193. package/src/resources/extensions/gsd/auto-model-selection.ts +9 -1
  194. package/src/resources/extensions/gsd/auto-start.ts +13 -6
  195. package/src/resources/extensions/gsd/auto-tool-tracking.ts +1 -1
  196. package/src/resources/extensions/gsd/auto-worktree.ts +1 -1
  197. package/src/resources/extensions/gsd/auto.ts +68 -0
  198. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +2 -0
  199. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +82 -60
  200. package/src/resources/extensions/gsd/bootstrap/system-context.ts +7 -0
  201. package/src/resources/extensions/gsd/commands/context.ts +16 -5
  202. package/src/resources/extensions/gsd/commands/dispatcher.ts +14 -2
  203. package/src/resources/extensions/gsd/commands/handlers/auto.ts +10 -36
  204. package/src/resources/extensions/gsd/commands/handlers/core.ts +58 -11
  205. package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +17 -7
  206. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +4 -10
  207. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -3
  208. package/src/resources/extensions/gsd/dispatch-guard.ts +18 -1
  209. package/src/resources/extensions/gsd/error-classifier.ts +1 -1
  210. package/src/resources/extensions/gsd/forensics.ts +23 -7
  211. package/src/resources/extensions/gsd/guided-flow.ts +5 -10
  212. package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
  213. package/src/resources/extensions/gsd/metrics.ts +12 -1
  214. package/src/resources/extensions/gsd/milestone-actions.ts +10 -3
  215. package/src/resources/extensions/gsd/notification-overlay.ts +24 -7
  216. package/src/resources/extensions/gsd/notification-store.ts +49 -1
  217. package/src/resources/extensions/gsd/notification-widget.ts +5 -14
  218. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -3
  219. package/src/resources/extensions/gsd/pre-execution-checks.ts +39 -2
  220. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -2
  221. package/src/resources/extensions/gsd/prompts/discuss.md +2 -0
  222. package/src/resources/extensions/gsd/prompts/execute-task.md +20 -19
  223. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  224. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
  225. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  226. package/src/resources/extensions/gsd/prompts/queue.md +3 -2
  227. package/src/resources/extensions/gsd/prompts/system.md +1 -0
  228. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -1
  229. package/src/resources/extensions/gsd/session-model-override.ts +36 -0
  230. package/src/resources/extensions/gsd/shortcut-defs.ts +49 -0
  231. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +11 -9
  232. package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +28 -0
  233. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +39 -0
  234. package/src/resources/extensions/gsd/tests/complete-slice-prompt-task-summary-layout.test.ts +18 -0
  235. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +27 -0
  236. package/src/resources/extensions/gsd/tests/execute-task-prompt-existing-artifact-guard.test.ts +33 -0
  237. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +62 -0
  238. package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +15 -0
  239. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +73 -0
  240. package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +180 -0
  241. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +66 -1
  242. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +36 -51
  243. package/src/resources/extensions/gsd/tests/notification-store.test.ts +35 -0
  244. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +26 -0
  245. package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +90 -0
  246. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +1 -0
  247. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +18 -0
  248. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +49 -0
  249. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +19 -0
  250. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +7 -0
  251. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +62 -5
  252. package/src/resources/extensions/gsd/tests/session-model-override.test.ts +35 -0
  253. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +90 -0
  254. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +7 -0
  255. package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +18 -0
  256. package/src/resources/skills/create-skill/SKILL.md +2 -0
  257. /package/dist/web/standalone/.next/static/{4xyaXTn7-shVHaGMcl75o → cYPZv_bAhZk2ms-Pz6vsY}/_buildManifest.js +0 -0
  258. /package/dist/web/standalone/.next/static/{4xyaXTn7-shVHaGMcl75o → cYPZv_bAhZk2ms-Pz6vsY}/_ssgManifest.js +0 -0
@@ -0,0 +1,36 @@
1
+ export interface SessionModelOverride {
2
+ provider: string;
3
+ id: string;
4
+ }
5
+
6
+ const sessionOverrides = new Map<string, SessionModelOverride>();
7
+
8
+ function normalizeSessionId(sessionId: string): string {
9
+ return typeof sessionId === "string" ? sessionId.trim() : "";
10
+ }
11
+
12
+ export function setSessionModelOverride(
13
+ sessionId: string,
14
+ override: SessionModelOverride,
15
+ ): void {
16
+ const key = normalizeSessionId(sessionId);
17
+ if (!key) return;
18
+ sessionOverrides.set(key, {
19
+ provider: override.provider,
20
+ id: override.id,
21
+ });
22
+ }
23
+
24
+ export function getSessionModelOverride(
25
+ sessionId: string,
26
+ ): SessionModelOverride | undefined {
27
+ const key = normalizeSessionId(sessionId);
28
+ if (!key) return undefined;
29
+ return sessionOverrides.get(key);
30
+ }
31
+
32
+ export function clearSessionModelOverride(sessionId: string): void {
33
+ const key = normalizeSessionId(sessionId);
34
+ if (!key) return;
35
+ sessionOverrides.delete(key);
36
+ }
@@ -0,0 +1,49 @@
1
+ // Canonical GSD shortcut definitions used by registration, help text, and overlays.
2
+
3
+ import { formatShortcut } from "./files.js";
4
+
5
+ export type GSDShortcutId = "dashboard" | "notifications" | "parallel";
6
+
7
+ type GSDShortcutDef = {
8
+ key: "g" | "n" | "p";
9
+ action: string;
10
+ command: string;
11
+ };
12
+
13
+ export const GSD_SHORTCUTS: Record<GSDShortcutId, GSDShortcutDef> = {
14
+ dashboard: {
15
+ key: "g",
16
+ action: "Open GSD dashboard",
17
+ command: "/gsd status",
18
+ },
19
+ notifications: {
20
+ key: "n",
21
+ action: "Open notification history",
22
+ command: "/gsd notifications",
23
+ },
24
+ parallel: {
25
+ key: "p",
26
+ action: "Open parallel worker monitor",
27
+ command: "/gsd parallel watch",
28
+ },
29
+ };
30
+
31
+ function combo(prefix: "Ctrl+Alt+" | "Ctrl+Shift+", key: string): string {
32
+ return `${prefix}${key.toUpperCase()}`;
33
+ }
34
+
35
+ export function primaryShortcutCombo(id: GSDShortcutId): string {
36
+ return combo("Ctrl+Alt+", GSD_SHORTCUTS[id].key);
37
+ }
38
+
39
+ export function fallbackShortcutCombo(id: GSDShortcutId): string {
40
+ return combo("Ctrl+Shift+", GSD_SHORTCUTS[id].key);
41
+ }
42
+
43
+ export function shortcutPair(id: GSDShortcutId, formatter: (combo: string) => string = (combo) => combo): string {
44
+ return `${formatter(primaryShortcutCombo(id))} / ${formatter(fallbackShortcutCombo(id))}`;
45
+ }
46
+
47
+ export function formattedShortcutPair(id: GSDShortcutId): string {
48
+ return shortcutPair(id, formatShortcut);
49
+ }
@@ -7,9 +7,8 @@ const sourcePath = join(import.meta.dirname, "..", "auto-start.ts");
7
7
  const source = readFileSync(sourcePath, "utf-8");
8
8
 
9
9
  test("bootstrapAutoSession snapshots ctx.model before guided-flow entry (#2829)", () => {
10
- // #3517 changed the snapshot to prefer GSD preferences, but the ordering
11
- // guarantee still holds: the snapshot must be built before guided-flow.
12
- const snapshotIdx = source.indexOf("const startModelSnapshot = preferredModel");
10
+ // The snapshot ordering guarantee still holds: build snapshot before guided-flow.
11
+ const snapshotIdx = source.indexOf("const startModelSnapshot = manualSessionOverride");
13
12
  assert.ok(snapshotIdx > -1, "auto-start.ts should snapshot model at bootstrap start");
14
13
 
15
14
  const firstDiscussIdx = source.indexOf('await showSmartEntry(ctx, pi, base, { step: requestedStepMode });');
@@ -29,8 +28,11 @@ test("bootstrapAutoSession restores autoModeStartModel from the early snapshot (
29
28
  assert.ok(snapshotRefIdx > -1, "autoModeStartModel should be restored from startModelSnapshot");
30
29
  });
31
30
 
32
- test("bootstrapAutoSession prefers GSD PREFERENCES.md over settings.json for start model (#3517)", () => {
33
- // resolveDefaultSessionModel() should be called before the snapshot is built
31
+ test("bootstrapAutoSession checks manual session override before preferences", () => {
32
+ const manualIdx = source.indexOf("const manualSessionOverride = getSessionModelOverride(");
33
+ assert.ok(manualIdx > -1, "auto-start.ts should read session model override first");
34
+
35
+ // resolveDefaultSessionModel() should still be called for fallback behavior
34
36
  const preferredIdx = source.indexOf("const preferredModel = resolveDefaultSessionModel(");
35
37
  assert.ok(preferredIdx > -1, "auto-start.ts should call resolveDefaultSessionModel()");
36
38
 
@@ -38,11 +40,11 @@ test("bootstrapAutoSession prefers GSD PREFERENCES.md over settings.json for sta
38
40
  const withProviderIdx = source.indexOf("resolveDefaultSessionModel(ctx.model?.provider)");
39
41
  assert.ok(withProviderIdx > -1, "auto-start.ts should pass ctx.model?.provider for bare ID resolution");
40
42
 
41
- const snapshotIdx = source.indexOf("const startModelSnapshot = preferredModel");
42
- assert.ok(snapshotIdx > -1, "startModelSnapshot should use preferredModel when available");
43
+ const snapshotIdx = source.indexOf("const startModelSnapshot = manualSessionOverride");
44
+ assert.ok(snapshotIdx > -1, "startModelSnapshot should prefer manual session override");
43
45
 
44
46
  assert.ok(
45
- preferredIdx < snapshotIdx,
46
- "resolveDefaultSessionModel() must be called before building startModelSnapshot",
47
+ manualIdx < snapshotIdx && preferredIdx < snapshotIdx,
48
+ "manual override and preference fallback must be resolved before building startModelSnapshot",
47
49
  );
48
50
  });
@@ -0,0 +1,28 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import { createTestContext } from "./test-helpers.ts";
5
+
6
+ const { assertTrue, report } = createTestContext();
7
+
8
+ const srcPath = join(import.meta.dirname, "..", "auto-start.ts");
9
+ const src = readFileSync(srcPath, "utf-8");
10
+
11
+ console.log("\n=== #3822: worktree bootstrap uses project DB path ===");
12
+
13
+ const dbLifecycleIdx = src.indexOf("// ── DB lifecycle ──");
14
+ assertTrue(dbLifecycleIdx > 0, "auto-start.ts has a DB lifecycle section");
15
+
16
+ const dbLifecycleRegion = dbLifecycleIdx > 0 ? src.slice(dbLifecycleIdx, dbLifecycleIdx + 600) : "";
17
+
18
+ assertTrue(
19
+ dbLifecycleRegion.includes("const gsdDbPath = resolveProjectRootDbPath(s.basePath);"),
20
+ "DB lifecycle resolves the project-root DB path after worktree entry (#3822)",
21
+ );
22
+
23
+ assertTrue(
24
+ !dbLifecycleRegion.includes('join(s.basePath, ".gsd", "gsd.db")'),
25
+ "DB lifecycle no longer derives gsd.db directly from the worktree path (#3822)",
26
+ );
27
+
28
+ report();
@@ -0,0 +1,39 @@
1
+ import { describe, test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { readFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+
6
+ const systemContextSrc = readFileSync(
7
+ join(import.meta.dirname, "..", "bootstrap", "system-context.ts"),
8
+ "utf-8",
9
+ );
10
+ const registerHooksSrc = readFileSync(
11
+ join(import.meta.dirname, "..", "bootstrap", "register-hooks.ts"),
12
+ "utf-8",
13
+ );
14
+
15
+ describe("bootstrap deriveState DB guards (#3844)", () => {
16
+ test("system-context opens DB before deriveState in resume flows", () => {
17
+ const helperIdx = systemContextSrc.indexOf("const ensureStateDbOpen = async () => {");
18
+ const firstDeriveIdx = systemContextSrc.indexOf("const state = await deriveState(basePath);");
19
+ assert.ok(helperIdx > -1, "system-context should define a DB-open helper for deriveState callers");
20
+ assert.ok(firstDeriveIdx > -1, "system-context should still derive state for resume flows");
21
+ assert.ok(helperIdx < firstDeriveIdx, "system-context should prepare DB opening before deriveState resume calls");
22
+ assert.match(
23
+ systemContextSrc,
24
+ /await ensureStateDbOpen\(\);\s*\n\s*const state = await deriveState\(basePath\);/g,
25
+ "system-context resume flows should open DB before deriveState",
26
+ );
27
+ });
28
+
29
+ test("register-hooks opens DB before deriveState in session_before_compact", () => {
30
+ const compactIdx = registerHooksSrc.indexOf('pi.on("session_before_compact"');
31
+ assert.ok(compactIdx > -1, "register-hooks should define session_before_compact");
32
+ const compactSection = registerHooksSrc.slice(compactIdx, compactIdx + 1600);
33
+ const ensureIdx = compactSection.indexOf("ensureDbOpen()");
34
+ const deriveIdx = compactSection.indexOf("deriveState(basePath)");
35
+ assert.ok(ensureIdx > -1, "session_before_compact should call ensureDbOpen()");
36
+ assert.ok(deriveIdx > -1, "session_before_compact should derive state");
37
+ assert.ok(ensureIdx < deriveIdx, "session_before_compact should open DB before deriveState");
38
+ });
39
+ });
@@ -0,0 +1,18 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { readFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+
6
+ const promptPath = join(process.cwd(), "src/resources/extensions/gsd/prompts/complete-slice.md");
7
+ const prompt = readFileSync(promptPath, "utf-8");
8
+
9
+ test("complete-slice prompt explains the flat task summary layout", () => {
10
+ assert.match(prompt, /flat file layout/i);
11
+ assert.match(prompt, /T01-SUMMARY\.md/);
12
+ assert.match(prompt, /not inside per-task subdirectories like `tasks\/T01\/SUMMARY\.md`/i);
13
+ });
14
+
15
+ test("complete-slice prompt forbids the wrong task summary glob", () => {
16
+ assert.match(prompt, /find .*tasks -name "\*-SUMMARY\.md"/i);
17
+ assert.match(prompt, /Never use `tasks\/\*\/SUMMARY\.md`/);
18
+ });
@@ -145,6 +145,33 @@ test("dispatch guard falls back to positional ordering when no dependencies decl
145
145
  );
146
146
  });
147
147
 
148
+ test("dispatch guard ignores positionally-earlier reverse dependents for zero-dependency slices (#3720)", (t) => {
149
+ const repo = setupRepo();
150
+ t.after(() => teardownRepo(repo));
151
+
152
+ mkdirSync(join(repo, ".gsd", "milestones", "M015"), { recursive: true });
153
+
154
+ insertMilestone({ id: "M015", title: "Reverse dependency fallback" });
155
+ insertSlice({ id: "S03", milestoneId: "M015", title: "Complete prerequisite", status: "complete", depends: [], sequence: 0 });
156
+ insertSlice({ id: "S04", milestoneId: "M015", title: "Depends on S04A", status: "pending", depends: ["S03", "S04A"], sequence: 0 });
157
+ insertSlice({ id: "S04A", milestoneId: "M015", title: "No explicit deps", status: "pending", depends: [], sequence: 0 });
158
+
159
+ writeFileSync(join(repo, ".gsd", "milestones", "M015", "M015-ROADMAP.md"), "# M015\n");
160
+
161
+ // S04A has no declared dependencies and should not be blocked by S04, because
162
+ // S04 itself depends on S04A. With sequence=0, DB ordering falls back to id.
163
+ assert.equal(
164
+ getPriorSliceCompletionBlocker(repo, "main", "execute-task", "M015/S04A/T02"),
165
+ null,
166
+ );
167
+
168
+ // The reverse direction is still blocked normally.
169
+ assert.equal(
170
+ getPriorSliceCompletionBlocker(repo, "main", "execute-task", "M015/S04/T01"),
171
+ "Cannot dispatch execute-task M015/S04/T01: dependency slice M015/S04A is not complete.",
172
+ );
173
+ });
174
+
148
175
  test("dispatch guard allows slice with all declared dependencies complete", (t) => {
149
176
  const repo = setupRepo();
150
177
  t.after(() => teardownRepo(repo));
@@ -0,0 +1,33 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { readFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const promptsDir = join(__dirname, "..", "prompts");
9
+
10
+ test("execute-task prompt requires reading existing artifacts before write", () => {
11
+ const prompt = readFileSync(join(promptsDir, "execute-task.md"), "utf-8");
12
+
13
+ assert.match(
14
+ prompt,
15
+ /Before any `Write` that creates an artifact or output file, check whether that path already exists\./,
16
+ "execute-task prompt should require an existence check before creating artifacts",
17
+ );
18
+ assert.match(
19
+ prompt,
20
+ /If it does, read it first and decide whether the work is already done, should be extended, or truly needs replacement\./,
21
+ "execute-task prompt should require reading existing artifacts before replacement",
22
+ );
23
+ });
24
+
25
+ test("guided resume prompt checks for pre-existing artifacts", () => {
26
+ const prompt = readFileSync(join(promptsDir, "guided-resume-task.md"), "utf-8");
27
+
28
+ assert.match(
29
+ prompt,
30
+ /Before you create any expected artifact or output file, check whether it already exists and read it first/i,
31
+ "guided resume prompt should guard pre-existing artifacts",
32
+ );
33
+ });
@@ -101,3 +101,65 @@ test("#1943 detectStuckLoops ignores watchdog duplicates but flags real re-dispa
101
101
  assert.equal(anomalies.length, 1, `expected 1 anomaly (for the 3x dispatched task), got ${anomalies.length}`);
102
102
  assert.ok(anomalies[0].summary.includes("3 times"));
103
103
  });
104
+
105
+ test("#3760 detectStuckLoops ignores cross-session recovery re-dispatches", () => {
106
+ const anomalies: ForensicAnomaly[] = [];
107
+
108
+ const units: UnitMetrics[] = [
109
+ makeUnit({
110
+ type: "plan-slice",
111
+ id: "M001/S02",
112
+ startedAt: 1000,
113
+ finishedAt: 2000,
114
+ autoSessionKey: "session-a",
115
+ }),
116
+ makeUnit({
117
+ type: "plan-slice",
118
+ id: "M001/S02",
119
+ startedAt: 5000,
120
+ finishedAt: 6000,
121
+ autoSessionKey: "session-b",
122
+ }),
123
+ ];
124
+
125
+ detectStuckLoops(units, anomalies);
126
+
127
+ assert.equal(anomalies.length, 0, "cross-session recovery should not be flagged as a stuck loop");
128
+ });
129
+
130
+ test("#3760 detectStuckLoops still flags repeated dispatches within one auto session", () => {
131
+ const anomalies: ForensicAnomaly[] = [];
132
+
133
+ const units: UnitMetrics[] = [
134
+ makeUnit({
135
+ type: "complete-slice",
136
+ id: "M011/S02",
137
+ startedAt: 1000,
138
+ finishedAt: 2000,
139
+ autoSessionKey: "session-a",
140
+ }),
141
+ makeUnit({
142
+ type: "complete-slice",
143
+ id: "M011/S02",
144
+ startedAt: 5000,
145
+ finishedAt: 6000,
146
+ autoSessionKey: "session-a",
147
+ }),
148
+ makeUnit({
149
+ type: "complete-slice",
150
+ id: "M011/S02",
151
+ startedAt: 9000,
152
+ finishedAt: 10000,
153
+ autoSessionKey: "session-b",
154
+ }),
155
+ ];
156
+
157
+ detectStuckLoops(units, anomalies);
158
+
159
+ assert.equal(anomalies.length, 1, "within-session retries should still be flagged");
160
+ assert.ok(anomalies[0].summary.includes("2 times"), `summary should reflect the worst same-session loop: ${anomalies[0].summary}`);
161
+ assert.ok(
162
+ anomalies[0].details.includes("Cross-session recovery runs are ignored"),
163
+ `details should explain the session-aware rule: ${anomalies[0].details}`,
164
+ );
165
+ });
@@ -4,6 +4,7 @@
4
4
  import test from 'node:test';
5
5
  import assert from 'node:assert/strict';
6
6
  import { formatShortcut } from '../files.ts';
7
+ import { formattedShortcutPair, primaryShortcutCombo, fallbackShortcutCombo } from '../shortcut-defs.ts';
7
8
 
8
9
  // ─── formatShortcut renders per-platform shortcuts ──────────────────────
9
10
 
@@ -67,3 +68,17 @@ test('formatShortcut: passes through plain key names', () => {
67
68
  assert.strictEqual(formatShortcut('Escape'), 'Escape');
68
69
  assert.strictEqual(formatShortcut('Enter'), 'Enter');
69
70
  });
71
+
72
+ test("shortcut-defs: exposes canonical dashboard combos", () => {
73
+ assert.equal(primaryShortcutCombo("dashboard"), "Ctrl+Alt+G");
74
+ assert.equal(fallbackShortcutCombo("dashboard"), "Ctrl+Shift+G");
75
+ });
76
+
77
+ test("shortcut-defs: formats shortcut pair using platform symbols", () => {
78
+ const pair = formattedShortcutPair("notifications");
79
+ if (process.platform === "darwin") {
80
+ assert.equal(pair, "⌃⌥N / ⌃⇧N");
81
+ } else {
82
+ assert.equal(pair, "Ctrl+Alt+N / Ctrl+Shift+N");
83
+ }
84
+ });
@@ -0,0 +1,73 @@
1
+ /**
2
+ * GSDNoProjectError — tests for friendly home-directory error handling.
3
+ *
4
+ * Verifies that GSDNoProjectError is thrown for blocked directories and
5
+ * that the dispatcher catches it with a user-friendly message.
6
+ */
7
+
8
+ import test from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import { readFileSync } from "node:fs";
11
+ import { join, dirname } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+
16
+ const contextSrc = readFileSync(join(__dirname, "..", "commands", "context.ts"), "utf-8");
17
+ const dispatcherSrc = readFileSync(join(__dirname, "..", "commands", "dispatcher.ts"), "utf-8");
18
+
19
+ // ─── GSDNoProjectError class ──────────────────────────────────────────────
20
+
21
+ test("GSDNoProjectError class is exported from context.ts", () => {
22
+ assert.ok(
23
+ contextSrc.includes("export class GSDNoProjectError extends Error"),
24
+ "GSDNoProjectError should be an exported Error subclass",
25
+ );
26
+ });
27
+
28
+ test("GSDNoProjectError sets name property", () => {
29
+ assert.ok(
30
+ contextSrc.includes('this.name = "GSDNoProjectError"'),
31
+ "GSDNoProjectError should set its name for instanceof checks",
32
+ );
33
+ });
34
+
35
+ // ─── projectRoot blocked directory handling ───────────────────────────────
36
+
37
+ test("projectRoot uses validateDirectory and checks for blocked severity", () => {
38
+ assert.ok(
39
+ contextSrc.includes("validateDirectory(pathToCheck)"),
40
+ "projectRoot should call validateDirectory",
41
+ );
42
+ assert.ok(
43
+ contextSrc.includes('result.severity === "blocked"'),
44
+ "projectRoot should check for blocked severity",
45
+ );
46
+ });
47
+
48
+ test("projectRoot throws GSDNoProjectError on blocked directory", () => {
49
+ assert.ok(
50
+ contextSrc.includes("throw new GSDNoProjectError"),
51
+ "projectRoot should throw GSDNoProjectError when directory is blocked",
52
+ );
53
+ });
54
+
55
+ // ─── Dispatcher catch ─────────────────────────────────────────────────────
56
+
57
+ test("dispatcher catches GSDNoProjectError with user-friendly message", () => {
58
+ assert.ok(
59
+ dispatcherSrc.includes("err instanceof GSDNoProjectError"),
60
+ "dispatcher should catch GSDNoProjectError specifically",
61
+ );
62
+ assert.ok(
63
+ dispatcherSrc.includes("cd"),
64
+ "error message should suggest cd-ing into a project directory",
65
+ );
66
+ });
67
+
68
+ test("dispatcher re-throws non-GSDNoProjectError exceptions", () => {
69
+ assert.ok(
70
+ dispatcherSrc.includes("throw err"),
71
+ "dispatcher should re-throw unexpected errors",
72
+ );
73
+ });
@@ -0,0 +1,180 @@
1
+ // gsd / infra-errors cooldown detection tests
2
+ // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
+
4
+ import test, { describe } from "node:test";
5
+ import assert from "node:assert/strict";
6
+
7
+ import {
8
+ isTransientCooldownError,
9
+ getCooldownRetryAfterMs,
10
+ MAX_COOLDOWN_RETRIES,
11
+ COOLDOWN_FALLBACK_WAIT_MS,
12
+ } from "../auto/infra-errors.js";
13
+
14
+ // ─── Constants ────────────────────────────────────────────────────────────────
15
+
16
+ describe("infra-errors cooldown constants", () => {
17
+ test("COOLDOWN_FALLBACK_WAIT_MS is a positive number greater than the 30s rate-limit backoff", () => {
18
+ assert.ok(typeof COOLDOWN_FALLBACK_WAIT_MS === "number");
19
+ assert.ok(COOLDOWN_FALLBACK_WAIT_MS > 30_000, "should exceed the 30s rate-limit window");
20
+ });
21
+
22
+ test("MAX_COOLDOWN_RETRIES is a positive integer", () => {
23
+ assert.ok(typeof MAX_COOLDOWN_RETRIES === "number");
24
+ assert.ok(Number.isInteger(MAX_COOLDOWN_RETRIES));
25
+ assert.ok(MAX_COOLDOWN_RETRIES > 0);
26
+ });
27
+
28
+ test("COOLDOWN_FALLBACK_WAIT_MS is 35_000", () => {
29
+ assert.equal(COOLDOWN_FALLBACK_WAIT_MS, 35_000);
30
+ });
31
+
32
+ test("MAX_COOLDOWN_RETRIES is 5", () => {
33
+ assert.equal(MAX_COOLDOWN_RETRIES, 5);
34
+ });
35
+ });
36
+
37
+ // ─── isTransientCooldownError: structured detection ──────────────────────────
38
+
39
+ describe("isTransientCooldownError — structured code detection", () => {
40
+ test("returns true for an object with code === AUTH_COOLDOWN", () => {
41
+ const err = { code: "AUTH_COOLDOWN", message: "credentials in cooldown" };
42
+ assert.equal(isTransientCooldownError(err), true);
43
+ });
44
+
45
+ test("returns true for a real CredentialCooldownError-shaped error", () => {
46
+ // Simulate CredentialCooldownError without importing sdk.ts (leaf-module rule)
47
+ const err = Object.assign(new Error('All credentials for "anthropic" are in a cooldown window.'), {
48
+ code: "AUTH_COOLDOWN",
49
+ retryAfterMs: 30_000,
50
+ name: "CredentialCooldownError",
51
+ });
52
+ assert.equal(isTransientCooldownError(err), true);
53
+ });
54
+
55
+ test("returns false for an object with a different code", () => {
56
+ const err = { code: "ENOSPC", message: "disk full" };
57
+ assert.equal(isTransientCooldownError(err), false);
58
+ });
59
+
60
+ test("returns false for an object with no code property", () => {
61
+ const err = { message: "some random error" };
62
+ assert.equal(isTransientCooldownError(err), false);
63
+ });
64
+ });
65
+
66
+ // ─── isTransientCooldownError: message fallback ───────────────────────────────
67
+
68
+ describe("isTransientCooldownError — message fallback (cross-process)", () => {
69
+ test("returns true when message contains 'in a cooldown window'", () => {
70
+ const err = new Error('All credentials for "openai" are in a cooldown window. Please wait.');
71
+ assert.equal(isTransientCooldownError(err), true);
72
+ });
73
+
74
+ test("returns true when message matches case-insensitively", () => {
75
+ const err = new Error("credentials IN A COOLDOWN WINDOW");
76
+ assert.equal(isTransientCooldownError(err), true);
77
+ });
78
+
79
+ test("returns true for a plain string containing cooldown window phrase", () => {
80
+ assert.equal(isTransientCooldownError("all keys in a cooldown window"), true);
81
+ });
82
+
83
+ test("returns false for a generic error message", () => {
84
+ const err = new Error("rate limit exceeded");
85
+ assert.equal(isTransientCooldownError(err), false);
86
+ });
87
+
88
+ test("returns false for an error message about auth failure without cooldown phrase", () => {
89
+ const err = new Error("Authentication failed: invalid API key");
90
+ assert.equal(isTransientCooldownError(err), false);
91
+ });
92
+ });
93
+
94
+ // ─── isTransientCooldownError: edge cases ────────────────────────────────────
95
+
96
+ describe("isTransientCooldownError — edge cases", () => {
97
+ test("returns false for null", () => {
98
+ assert.equal(isTransientCooldownError(null), false);
99
+ });
100
+
101
+ test("returns false for undefined", () => {
102
+ assert.equal(isTransientCooldownError(undefined), false);
103
+ });
104
+
105
+ test("returns false for a number", () => {
106
+ assert.equal(isTransientCooldownError(42), false);
107
+ });
108
+
109
+ test("returns false for an empty object", () => {
110
+ assert.equal(isTransientCooldownError({}), false);
111
+ });
112
+
113
+ test("returns false for an object with code === AUTH_COOLDOWN as a non-string", () => {
114
+ // code must be a string matching "AUTH_COOLDOWN" exactly
115
+ const err = { code: 42 };
116
+ assert.equal(isTransientCooldownError(err), false);
117
+ });
118
+ });
119
+
120
+ // ─── getCooldownRetryAfterMs: structured extraction ──────────────────────────
121
+
122
+ describe("getCooldownRetryAfterMs — structured extraction", () => {
123
+ test("returns retryAfterMs when code is AUTH_COOLDOWN and retryAfterMs is set", () => {
124
+ const err = { code: "AUTH_COOLDOWN", retryAfterMs: 30_000 };
125
+ assert.equal(getCooldownRetryAfterMs(err), 30_000);
126
+ });
127
+
128
+ test("returns undefined when code is AUTH_COOLDOWN but retryAfterMs is absent", () => {
129
+ const err = { code: "AUTH_COOLDOWN" };
130
+ assert.equal(getCooldownRetryAfterMs(err), undefined);
131
+ });
132
+
133
+ test("returns 0 when retryAfterMs is explicitly 0", () => {
134
+ const err = { code: "AUTH_COOLDOWN", retryAfterMs: 0 };
135
+ assert.equal(getCooldownRetryAfterMs(err), 0);
136
+ });
137
+
138
+ test("returns undefined for an error with a different code even if retryAfterMs is set", () => {
139
+ const err = { code: "ENOSPC", retryAfterMs: 5_000 };
140
+ assert.equal(getCooldownRetryAfterMs(err), undefined);
141
+ });
142
+
143
+ test("returns undefined for a plain Error with no code property", () => {
144
+ const err = new Error("something went wrong");
145
+ assert.equal(getCooldownRetryAfterMs(err), undefined);
146
+ });
147
+
148
+ test("returns retryAfterMs from a full CredentialCooldownError-shaped object", () => {
149
+ const err = Object.assign(new Error('All credentials for "anthropic" are in a cooldown window.'), {
150
+ code: "AUTH_COOLDOWN",
151
+ retryAfterMs: 15_000,
152
+ name: "CredentialCooldownError",
153
+ });
154
+ assert.equal(getCooldownRetryAfterMs(err), 15_000);
155
+ });
156
+ });
157
+
158
+ // ─── getCooldownRetryAfterMs: edge cases ─────────────────────────────────────
159
+
160
+ describe("getCooldownRetryAfterMs — edge cases", () => {
161
+ test("returns undefined for null", () => {
162
+ assert.equal(getCooldownRetryAfterMs(null), undefined);
163
+ });
164
+
165
+ test("returns undefined for undefined", () => {
166
+ assert.equal(getCooldownRetryAfterMs(undefined), undefined);
167
+ });
168
+
169
+ test("returns undefined for a plain string", () => {
170
+ assert.equal(getCooldownRetryAfterMs("AUTH_COOLDOWN"), undefined);
171
+ });
172
+
173
+ test("returns undefined for an empty object", () => {
174
+ assert.equal(getCooldownRetryAfterMs({}), undefined);
175
+ });
176
+
177
+ test("returns undefined for a number", () => {
178
+ assert.equal(getCooldownRetryAfterMs(42), undefined);
179
+ });
180
+ });