gsd-pi 2.63.0-dev.d04bbc5 → 2.64.0-dev.6fe1e44

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 (210) hide show
  1. package/dist/resources/extensions/gsd/auto-dashboard.js +5 -5
  2. package/dist/resources/extensions/gsd/auto-post-unit.js +98 -1
  3. package/dist/resources/extensions/gsd/auto-verification.js +138 -1
  4. package/dist/resources/extensions/gsd/auto.js +5 -0
  5. package/dist/resources/extensions/gsd/bootstrap/notify-interceptor.js +28 -0
  6. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +8 -0
  7. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +15 -0
  8. package/dist/resources/extensions/gsd/commands/catalog.js +7 -1
  9. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
  10. package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +103 -0
  11. package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
  12. package/dist/resources/extensions/gsd/notification-overlay.js +224 -0
  13. package/dist/resources/extensions/gsd/notification-store.js +268 -0
  14. package/dist/resources/extensions/gsd/notification-widget.js +56 -0
  15. package/dist/resources/extensions/gsd/post-execution-checks.js +407 -0
  16. package/dist/resources/extensions/gsd/pre-execution-checks.js +464 -0
  17. package/dist/resources/extensions/gsd/preferences-types.js +4 -0
  18. package/dist/resources/extensions/gsd/preferences-validation.js +33 -0
  19. package/dist/resources/extensions/gsd/preferences.js +4 -0
  20. package/dist/resources/extensions/gsd/verification-evidence.js +18 -0
  21. package/dist/resources/extensions/gsd/workflow-logger.js +8 -0
  22. package/dist/web/standalone/.next/BUILD_ID +1 -1
  23. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -16
  24. package/dist/web/standalone/.next/build-manifest.json +2 -2
  25. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  26. package/dist/web/standalone/.next/required-server-files.json +1 -1
  27. package/dist/web/standalone/.next/routes-manifest.json +6 -0
  28. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  29. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/api/notifications/route.js +3 -0
  45. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -0
  46. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -0
  47. package/dist/web/standalone/.next/server/app/index.html +1 -1
  48. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -16
  55. package/dist/web/standalone/.next/server/functions-config-manifest.json +1 -0
  56. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  57. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  58. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  59. package/dist/web/standalone/.next/static/KPMt-rZBouivKwIKcIral/_buildManifest.js +1 -0
  60. package/dist/web/standalone/.next/static/chunks/app/_global-error/page-8805a20e15762c3c.js +1 -0
  61. package/dist/web/standalone/.next/static/chunks/app/api/boot/route-8805a20e15762c3c.js +1 -0
  62. package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/input/route-8805a20e15762c3c.js +1 -0
  63. package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/resize/route-8805a20e15762c3c.js +1 -0
  64. package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/stream/route-8805a20e15762c3c.js +1 -0
  65. package/dist/web/standalone/.next/static/chunks/app/api/browse-directories/route-8805a20e15762c3c.js +1 -0
  66. package/dist/web/standalone/.next/static/chunks/app/api/captures/route-8805a20e15762c3c.js +1 -0
  67. package/dist/web/standalone/.next/static/chunks/app/api/cleanup/route-8805a20e15762c3c.js +1 -0
  68. package/dist/web/standalone/.next/static/chunks/app/api/dev-mode/route-8805a20e15762c3c.js +1 -0
  69. package/dist/web/standalone/.next/static/chunks/app/api/doctor/route-8805a20e15762c3c.js +1 -0
  70. package/dist/web/standalone/.next/static/chunks/app/api/experimental/route-8805a20e15762c3c.js +1 -0
  71. package/dist/web/standalone/.next/static/chunks/app/api/export-data/route-8805a20e15762c3c.js +1 -0
  72. package/dist/web/standalone/.next/static/chunks/app/api/files/route-8805a20e15762c3c.js +1 -0
  73. package/dist/web/standalone/.next/static/chunks/app/api/forensics/route-8805a20e15762c3c.js +1 -0
  74. package/dist/web/standalone/.next/static/chunks/app/api/git/route-8805a20e15762c3c.js +1 -0
  75. package/dist/web/standalone/.next/static/chunks/app/api/history/route-8805a20e15762c3c.js +1 -0
  76. package/dist/web/standalone/.next/static/chunks/app/api/hooks/route-8805a20e15762c3c.js +1 -0
  77. package/dist/web/standalone/.next/static/chunks/app/api/inspect/route-8805a20e15762c3c.js +1 -0
  78. package/dist/web/standalone/.next/static/chunks/app/api/knowledge/route-8805a20e15762c3c.js +1 -0
  79. package/dist/web/standalone/.next/static/chunks/app/api/live-state/route-8805a20e15762c3c.js +1 -0
  80. package/dist/web/standalone/.next/static/chunks/app/api/notifications/route-8805a20e15762c3c.js +1 -0
  81. package/dist/web/standalone/.next/static/chunks/app/api/onboarding/route-8805a20e15762c3c.js +1 -0
  82. package/dist/web/standalone/.next/static/chunks/app/api/preferences/route-8805a20e15762c3c.js +1 -0
  83. package/dist/web/standalone/.next/static/chunks/app/api/projects/route-8805a20e15762c3c.js +1 -0
  84. package/dist/web/standalone/.next/static/chunks/app/api/recovery/route-8805a20e15762c3c.js +1 -0
  85. package/dist/web/standalone/.next/static/chunks/app/api/remote-questions/route-8805a20e15762c3c.js +1 -0
  86. package/dist/web/standalone/.next/static/chunks/app/api/session/browser/route-8805a20e15762c3c.js +1 -0
  87. package/dist/web/standalone/.next/static/chunks/app/api/session/command/route-8805a20e15762c3c.js +1 -0
  88. package/dist/web/standalone/.next/static/chunks/app/api/session/events/route-8805a20e15762c3c.js +1 -0
  89. package/dist/web/standalone/.next/static/chunks/app/api/session/manage/route-8805a20e15762c3c.js +1 -0
  90. package/dist/web/standalone/.next/static/chunks/app/api/settings-data/route-8805a20e15762c3c.js +1 -0
  91. package/dist/web/standalone/.next/static/chunks/app/api/shutdown/route-8805a20e15762c3c.js +1 -0
  92. package/dist/web/standalone/.next/static/chunks/app/api/skill-health/route-8805a20e15762c3c.js +1 -0
  93. package/dist/web/standalone/.next/static/chunks/app/api/steer/route-8805a20e15762c3c.js +1 -0
  94. package/dist/web/standalone/.next/static/chunks/app/api/switch-root/route-8805a20e15762c3c.js +1 -0
  95. package/dist/web/standalone/.next/static/chunks/app/api/terminal/input/route-8805a20e15762c3c.js +1 -0
  96. package/dist/web/standalone/.next/static/chunks/app/api/terminal/resize/route-8805a20e15762c3c.js +1 -0
  97. package/dist/web/standalone/.next/static/chunks/app/api/terminal/sessions/route-8805a20e15762c3c.js +1 -0
  98. package/dist/web/standalone/.next/static/chunks/app/api/terminal/stream/route-8805a20e15762c3c.js +1 -0
  99. package/dist/web/standalone/.next/static/chunks/app/api/terminal/upload/route-8805a20e15762c3c.js +1 -0
  100. package/dist/web/standalone/.next/static/chunks/app/api/undo/route-8805a20e15762c3c.js +1 -0
  101. package/dist/web/standalone/.next/static/chunks/app/api/update/route-8805a20e15762c3c.js +1 -0
  102. package/dist/web/standalone/.next/static/chunks/app/api/visualizer/route-8805a20e15762c3c.js +1 -0
  103. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/app-error-8805a20e15762c3c.js +1 -0
  104. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/forbidden-8805a20e15762c3c.js +1 -0
  105. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/not-found-8805a20e15762c3c.js +1 -0
  106. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/unauthorized-8805a20e15762c3c.js +1 -0
  107. package/dist/web/standalone/server.js +1 -1
  108. package/package.json +1 -1
  109. package/packages/pi-agent-core/dist/agent-loop.js +26 -9
  110. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  111. package/packages/pi-agent-core/src/agent-loop.test.ts +100 -4
  112. package/packages/pi-agent-core/src/agent-loop.ts +43 -12
  113. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.d.ts +2 -0
  114. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.d.ts.map +1 -0
  115. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +38 -0
  116. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -0
  117. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/agent-session.js +11 -0
  119. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  120. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.d.ts +2 -0
  121. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.d.ts.map +1 -0
  122. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +24 -0
  123. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +1 -0
  124. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/resource-loader.js +4 -1
  126. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  127. package/packages/pi-coding-agent/package.json +1 -1
  128. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +64 -0
  129. package/packages/pi-coding-agent/src/core/agent-session.ts +10 -0
  130. package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +42 -0
  131. package/packages/pi-coding-agent/src/core/resource-loader.ts +5 -1
  132. package/pkg/package.json +1 -1
  133. package/src/resources/extensions/gsd/auto-dashboard.ts +5 -4
  134. package/src/resources/extensions/gsd/auto-post-unit.ts +122 -0
  135. package/src/resources/extensions/gsd/auto-verification.ts +190 -2
  136. package/src/resources/extensions/gsd/auto.ts +4 -0
  137. package/src/resources/extensions/gsd/bootstrap/notify-interceptor.ts +34 -0
  138. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -0
  139. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +19 -0
  140. package/src/resources/extensions/gsd/commands/catalog.ts +7 -1
  141. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
  142. package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +139 -0
  143. package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
  144. package/src/resources/extensions/gsd/notification-overlay.ts +267 -0
  145. package/src/resources/extensions/gsd/notification-store.ts +288 -0
  146. package/src/resources/extensions/gsd/notification-widget.ts +68 -0
  147. package/src/resources/extensions/gsd/post-execution-checks.ts +539 -0
  148. package/src/resources/extensions/gsd/pre-execution-checks.ts +573 -0
  149. package/src/resources/extensions/gsd/preferences-types.ts +28 -0
  150. package/src/resources/extensions/gsd/preferences-validation.ts +33 -0
  151. package/src/resources/extensions/gsd/preferences.ts +4 -0
  152. package/src/resources/extensions/gsd/tests/auto-start-time-persistence.test.ts +50 -0
  153. package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +76 -0
  154. package/src/resources/extensions/gsd/tests/enhanced-verification-integration.test.ts +526 -0
  155. package/src/resources/extensions/gsd/tests/notification-store.test.ts +249 -0
  156. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +312 -0
  157. package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +813 -0
  158. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +999 -0
  159. package/src/resources/extensions/gsd/tests/pre-execution-fail-closed.test.ts +266 -0
  160. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +457 -0
  161. package/src/resources/extensions/gsd/verification-evidence.ts +68 -0
  162. package/src/resources/extensions/gsd/workflow-logger.ts +13 -0
  163. package/dist/web/standalone/.next/static/chunks/app/_global-error/page-c4cc189e7b117ea2.js +0 -1
  164. package/dist/web/standalone/.next/static/chunks/app/api/boot/route-c4cc189e7b117ea2.js +0 -1
  165. package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/input/route-c4cc189e7b117ea2.js +0 -1
  166. package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/resize/route-c4cc189e7b117ea2.js +0 -1
  167. package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/stream/route-c4cc189e7b117ea2.js +0 -1
  168. package/dist/web/standalone/.next/static/chunks/app/api/browse-directories/route-c4cc189e7b117ea2.js +0 -1
  169. package/dist/web/standalone/.next/static/chunks/app/api/captures/route-c4cc189e7b117ea2.js +0 -1
  170. package/dist/web/standalone/.next/static/chunks/app/api/cleanup/route-c4cc189e7b117ea2.js +0 -1
  171. package/dist/web/standalone/.next/static/chunks/app/api/dev-mode/route-c4cc189e7b117ea2.js +0 -1
  172. package/dist/web/standalone/.next/static/chunks/app/api/doctor/route-c4cc189e7b117ea2.js +0 -1
  173. package/dist/web/standalone/.next/static/chunks/app/api/experimental/route-c4cc189e7b117ea2.js +0 -1
  174. package/dist/web/standalone/.next/static/chunks/app/api/export-data/route-c4cc189e7b117ea2.js +0 -1
  175. package/dist/web/standalone/.next/static/chunks/app/api/files/route-c4cc189e7b117ea2.js +0 -1
  176. package/dist/web/standalone/.next/static/chunks/app/api/forensics/route-c4cc189e7b117ea2.js +0 -1
  177. package/dist/web/standalone/.next/static/chunks/app/api/git/route-c4cc189e7b117ea2.js +0 -1
  178. package/dist/web/standalone/.next/static/chunks/app/api/history/route-c4cc189e7b117ea2.js +0 -1
  179. package/dist/web/standalone/.next/static/chunks/app/api/hooks/route-c4cc189e7b117ea2.js +0 -1
  180. package/dist/web/standalone/.next/static/chunks/app/api/inspect/route-c4cc189e7b117ea2.js +0 -1
  181. package/dist/web/standalone/.next/static/chunks/app/api/knowledge/route-c4cc189e7b117ea2.js +0 -1
  182. package/dist/web/standalone/.next/static/chunks/app/api/live-state/route-c4cc189e7b117ea2.js +0 -1
  183. package/dist/web/standalone/.next/static/chunks/app/api/onboarding/route-c4cc189e7b117ea2.js +0 -1
  184. package/dist/web/standalone/.next/static/chunks/app/api/preferences/route-c4cc189e7b117ea2.js +0 -1
  185. package/dist/web/standalone/.next/static/chunks/app/api/projects/route-c4cc189e7b117ea2.js +0 -1
  186. package/dist/web/standalone/.next/static/chunks/app/api/recovery/route-c4cc189e7b117ea2.js +0 -1
  187. package/dist/web/standalone/.next/static/chunks/app/api/remote-questions/route-c4cc189e7b117ea2.js +0 -1
  188. package/dist/web/standalone/.next/static/chunks/app/api/session/browser/route-c4cc189e7b117ea2.js +0 -1
  189. package/dist/web/standalone/.next/static/chunks/app/api/session/command/route-c4cc189e7b117ea2.js +0 -1
  190. package/dist/web/standalone/.next/static/chunks/app/api/session/events/route-c4cc189e7b117ea2.js +0 -1
  191. package/dist/web/standalone/.next/static/chunks/app/api/session/manage/route-c4cc189e7b117ea2.js +0 -1
  192. package/dist/web/standalone/.next/static/chunks/app/api/settings-data/route-c4cc189e7b117ea2.js +0 -1
  193. package/dist/web/standalone/.next/static/chunks/app/api/shutdown/route-c4cc189e7b117ea2.js +0 -1
  194. package/dist/web/standalone/.next/static/chunks/app/api/skill-health/route-c4cc189e7b117ea2.js +0 -1
  195. package/dist/web/standalone/.next/static/chunks/app/api/steer/route-c4cc189e7b117ea2.js +0 -1
  196. package/dist/web/standalone/.next/static/chunks/app/api/switch-root/route-c4cc189e7b117ea2.js +0 -1
  197. package/dist/web/standalone/.next/static/chunks/app/api/terminal/input/route-c4cc189e7b117ea2.js +0 -1
  198. package/dist/web/standalone/.next/static/chunks/app/api/terminal/resize/route-c4cc189e7b117ea2.js +0 -1
  199. package/dist/web/standalone/.next/static/chunks/app/api/terminal/sessions/route-c4cc189e7b117ea2.js +0 -1
  200. package/dist/web/standalone/.next/static/chunks/app/api/terminal/stream/route-c4cc189e7b117ea2.js +0 -1
  201. package/dist/web/standalone/.next/static/chunks/app/api/terminal/upload/route-c4cc189e7b117ea2.js +0 -1
  202. package/dist/web/standalone/.next/static/chunks/app/api/undo/route-c4cc189e7b117ea2.js +0 -1
  203. package/dist/web/standalone/.next/static/chunks/app/api/update/route-c4cc189e7b117ea2.js +0 -1
  204. package/dist/web/standalone/.next/static/chunks/app/api/visualizer/route-c4cc189e7b117ea2.js +0 -1
  205. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/app-error-c4cc189e7b117ea2.js +0 -1
  206. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/forbidden-c4cc189e7b117ea2.js +0 -1
  207. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/not-found-c4cc189e7b117ea2.js +0 -1
  208. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/unauthorized-c4cc189e7b117ea2.js +0 -1
  209. package/dist/web/standalone/.next/static/vIq9fmvRUaFOpguoX5j4W/_buildManifest.js +0 -1
  210. /package/dist/web/standalone/.next/static/{vIq9fmvRUaFOpguoX5j4W → KPMt-rZBouivKwIKcIral}/_ssgManifest.js +0 -0
@@ -472,11 +472,11 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
472
472
  : "";
473
473
  lines.push(rightAlign(headerLeft, headerRight, width));
474
474
  // Worktree/branch right-aligned below header
475
- if (worktreeName && cachedBranch) {
476
- lines.push(rightAlign("", theme.fg("dim", `${worktreeName} (${cachedBranch})`), width));
477
- }
478
- else if (cachedBranch) {
479
- lines.push(rightAlign("", theme.fg("dim", cachedBranch), width));
475
+ const branchLabel = worktreeName && cachedBranch
476
+ ? `${worktreeName} (${cachedBranch})`
477
+ : cachedBranch ?? "";
478
+ if (branchLabel) {
479
+ lines.push(rightAlign("", theme.fg("dim", branchLabel), width));
480
480
  }
481
481
  // Show health signal details when degraded (yellow/red)
482
482
  if (score.level !== "green" && score.signals.length > 0 && widgetMode !== "min") {
@@ -14,7 +14,7 @@ import { deriveState } from "./state.js";
14
14
  import { logWarning, logError } from "./workflow-logger.js";
15
15
  import { loadFile, parseSummary, resolveAllOverrides } from "./files.js";
16
16
  import { loadPrompt } from "./prompt-loader.js";
17
- import { resolveSliceFile, resolveTaskFile, resolveMilestoneFile, resolveTasksDir, buildTaskFileName, } from "./paths.js";
17
+ import { resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveMilestoneFile, resolveTasksDir, buildTaskFileName, } from "./paths.js";
18
18
  import { invalidateAllCaches } from "./cache.js";
19
19
  import { parseUnitId } from "./unit-id.js";
20
20
  import { closeoutUnit } from "./auto-unit-closeout.js";
@@ -36,6 +36,10 @@ import { validateFileChanges } from "./safety/file-change-validator.js";
36
36
  import { validateContent } from "./safety/content-validator.js";
37
37
  import { resolveSafetyHarnessConfig } from "./safety/safety-harness.js";
38
38
  import { resolveExpectedArtifactPath as resolveArtifactForContent } from "./auto-artifact-paths.js";
39
+ import { loadEffectiveGSDPreferences } from "./preferences.js";
40
+ import { getSliceTasks } from "./gsd-db.js";
41
+ import { runPreExecutionChecks } from "./pre-execution-checks.js";
42
+ import { writePreExecutionEvidence } from "./verification-evidence.js";
39
43
  /** Maximum verification retry attempts before escalating to blocker placeholder (#2653). */
40
44
  const MAX_VERIFICATION_RETRIES = 3;
41
45
  /** Enqueue a sidecar item (hook, triage, or quick-task) for the main loop to
@@ -634,6 +638,99 @@ export async function postUnitPostVerification(pctx) {
634
638
  debugLog("postUnit", { phase: "capture-protection-error", error: String(e) });
635
639
  }
636
640
  }
641
+ // ── Pre-execution checks (after plan-slice completes) ──
642
+ if (s.currentUnit &&
643
+ s.currentUnit.type === "plan-slice") {
644
+ let preExecPauseNeeded = false;
645
+ await runSafely("postUnitPostVerification", "pre-execution-checks", async () => {
646
+ try {
647
+ // Check preferences — respect enhanced_verification and enhanced_verification_pre
648
+ const prefs = loadEffectiveGSDPreferences()?.preferences;
649
+ const enhancedEnabled = prefs?.enhanced_verification !== false; // default true
650
+ const preEnabled = prefs?.enhanced_verification_pre !== false; // default true
651
+ if (!enhancedEnabled || !preEnabled) {
652
+ debugLog("postUnitPostVerification", {
653
+ phase: "pre-execution-checks",
654
+ skipped: true,
655
+ reason: "disabled by preferences",
656
+ });
657
+ return;
658
+ }
659
+ // Parse the unit ID to get milestone/slice IDs
660
+ const { milestone: mid, slice: sid } = parseUnitId(s.currentUnit.id);
661
+ if (!mid || !sid) {
662
+ debugLog("postUnitPostVerification", {
663
+ phase: "pre-execution-checks",
664
+ skipped: true,
665
+ reason: "could not parse milestone/slice from unit ID",
666
+ });
667
+ return;
668
+ }
669
+ // Get tasks for this slice from DB
670
+ const tasks = getSliceTasks(mid, sid);
671
+ if (tasks.length === 0) {
672
+ debugLog("postUnitPostVerification", {
673
+ phase: "pre-execution-checks",
674
+ skipped: true,
675
+ reason: "no tasks found for slice",
676
+ });
677
+ return;
678
+ }
679
+ // Run pre-execution checks
680
+ const result = await runPreExecutionChecks(tasks, s.basePath);
681
+ // Log summary to stderr in existing verification output format
682
+ const emoji = result.status === "pass" ? "✅" : result.status === "warn" ? "⚠️" : "❌";
683
+ process.stderr.write(`gsd-pre-exec: ${emoji} Pre-execution checks ${result.status} for ${mid}/${sid} (${result.durationMs}ms)\n`);
684
+ // Log individual check results
685
+ for (const check of result.checks) {
686
+ const checkEmoji = check.passed ? "✓" : check.blocking ? "✗" : "⚠";
687
+ process.stderr.write(`gsd-pre-exec: ${checkEmoji} [${check.category}] ${check.target}: ${check.message}\n`);
688
+ }
689
+ // Write evidence JSON to slice artifacts directory
690
+ const slicePath = resolveSlicePath(s.basePath, mid, sid);
691
+ if (slicePath) {
692
+ writePreExecutionEvidence(result, slicePath, mid, sid);
693
+ }
694
+ // Notify UI
695
+ if (result.status === "fail") {
696
+ const blockingCount = result.checks.filter(c => !c.passed && c.blocking).length;
697
+ ctx.ui.notify(`Pre-execution checks failed: ${blockingCount} blocking issue${blockingCount === 1 ? "" : "s"} found`, "error");
698
+ preExecPauseNeeded = true;
699
+ }
700
+ else if (result.status === "warn") {
701
+ ctx.ui.notify(`Pre-execution checks passed with warnings`, "warning");
702
+ // Strict mode: treat warnings as blocking
703
+ if (prefs?.enhanced_verification_strict === true) {
704
+ preExecPauseNeeded = true;
705
+ }
706
+ }
707
+ debugLog("postUnitPostVerification", {
708
+ phase: "pre-execution-checks",
709
+ status: result.status,
710
+ checkCount: result.checks.length,
711
+ durationMs: result.durationMs,
712
+ });
713
+ }
714
+ catch (preExecError) {
715
+ // Fail-closed: if runPreExecutionChecks throws, pause auto-mode instead of silently continuing
716
+ const errorMessage = preExecError instanceof Error ? preExecError.message : String(preExecError);
717
+ debugLog("postUnitPostVerification", {
718
+ phase: "pre-execution-checks",
719
+ error: errorMessage,
720
+ failClosed: true,
721
+ });
722
+ logError("engine", `gsd-pre-exec: Pre-execution checks threw an error: ${errorMessage}`);
723
+ ctx.ui.notify(`Pre-execution checks error: ${errorMessage} — pausing for human review`, "error");
724
+ preExecPauseNeeded = true;
725
+ }
726
+ });
727
+ // Check for blocking failures after runSafely completes
728
+ if (preExecPauseNeeded) {
729
+ debugLog("postUnitPostVerification", { phase: "pre-execution-checks", pausing: true, reason: "blocking failures detected" });
730
+ await pauseAuto(ctx, pi);
731
+ return "stopped";
732
+ }
733
+ }
637
734
  // ── Triage check ──
638
735
  if (!s.stepMode &&
639
736
  s.currentUnit &&
@@ -9,13 +9,15 @@
9
9
  * value instead of calling return/pauseAuto directly — the caller
10
10
  * checks the result and handles control flow.
11
11
  */
12
+ import { mkdirSync, writeFileSync } from "node:fs";
12
13
  import { resolveSlicePath } from "./paths.js";
13
14
  import { parseUnitId } from "./unit-id.js";
14
- import { isDbAvailable, getTask } from "./gsd-db.js";
15
+ import { isDbAvailable, getTask, getSliceTasks } from "./gsd-db.js";
15
16
  import { loadEffectiveGSDPreferences } from "./preferences.js";
16
17
  import { runVerificationGate, formatFailureContext, captureRuntimeErrors, runDependencyAudit, } from "./verification-gate.js";
17
18
  import { writeVerificationJSON } from "./verification-evidence.js";
18
19
  import { logWarning } from "./workflow-logger.js";
20
+ import { runPostExecutionChecks } from "./post-execution-checks.js";
19
21
  import { join } from "node:path";
20
22
  function isInfraVerificationFailure(stderr) {
21
23
  return /\b(ENOENT|ENOTFOUND|ETIMEDOUT|ECONNRESET|EAI_AGAIN|spawn\s+\S+\s+ENOENT|command not found)\b/i.test(stderr);
@@ -128,12 +130,104 @@ export async function runPostUnitVerification(vctx, pauseAuto) {
128
130
  : "Verification failed due to infrastructure/runtime environment issues — treating as advisory.", "warning");
129
131
  return "continue";
130
132
  }
133
+ // ── Post-execution checks (run after main verification passes for execute-task units) ──
134
+ let postExecChecks;
135
+ let postExecBlockingFailure = false;
136
+ if (result.passed && mid && sid && tid) {
137
+ // Check preferences — respect enhanced_verification and enhanced_verification_post
138
+ const enhancedEnabled = prefs?.enhanced_verification !== false; // default true
139
+ const postEnabled = prefs?.enhanced_verification_post !== false; // default true
140
+ if (enhancedEnabled && postEnabled && isDbAvailable()) {
141
+ try {
142
+ // Get the completed task from DB
143
+ const taskRow = getTask(mid, sid, tid);
144
+ if (taskRow && taskRow.key_files && taskRow.key_files.length > 0) {
145
+ // Get all tasks in the slice
146
+ const allTasks = getSliceTasks(mid, sid);
147
+ // Filter to prior completed tasks (status = 'complete' or 'done', before current task)
148
+ const priorTasks = allTasks.filter((t) => (t.status === "complete" || t.status === "done") &&
149
+ t.id !== tid &&
150
+ t.sequence < taskRow.sequence);
151
+ // Run post-execution checks
152
+ const postExecResult = runPostExecutionChecks(taskRow, priorTasks, s.basePath);
153
+ // Store checks for evidence JSON
154
+ postExecChecks = postExecResult.checks;
155
+ // Log summary to stderr with gsd-post-exec: prefix
156
+ const emoji = postExecResult.status === "pass"
157
+ ? "✅"
158
+ : postExecResult.status === "warn"
159
+ ? "⚠️"
160
+ : "❌";
161
+ process.stderr.write(`gsd-post-exec: ${emoji} Post-execution checks ${postExecResult.status} for ${mid}/${sid}/${tid} (${postExecResult.durationMs}ms)\n`);
162
+ // Log individual check results
163
+ for (const check of postExecResult.checks) {
164
+ const checkEmoji = check.passed
165
+ ? "✓"
166
+ : check.blocking
167
+ ? "✗"
168
+ : "⚠";
169
+ process.stderr.write(`gsd-post-exec: ${checkEmoji} [${check.category}] ${check.target}: ${check.message}\n`);
170
+ }
171
+ // Check for blocking failures
172
+ if (postExecResult.status === "fail") {
173
+ postExecBlockingFailure = true;
174
+ const blockingCount = postExecResult.checks.filter((c) => !c.passed && c.blocking).length;
175
+ ctx.ui.notify(`Post-execution checks failed: ${blockingCount} blocking issue${blockingCount === 1 ? "" : "s"} found`, "error");
176
+ }
177
+ else if (postExecResult.status === "warn") {
178
+ ctx.ui.notify(`Post-execution checks passed with warnings`, "warning");
179
+ // Strict mode: treat warnings as blocking
180
+ if (prefs?.enhanced_verification_strict === true) {
181
+ postExecBlockingFailure = true;
182
+ }
183
+ }
184
+ }
185
+ }
186
+ catch (postExecErr) {
187
+ // Post-execution check errors are non-fatal — log and continue
188
+ logWarning("engine", `gsd-post-exec: error — ${postExecErr.message}`);
189
+ }
190
+ }
191
+ }
192
+ // Re-write verification evidence JSON with post-execution checks
193
+ if (postExecChecks && postExecChecks.length > 0 && mid && sid && tid) {
194
+ try {
195
+ const sDir = resolveSlicePath(s.basePath, mid, sid);
196
+ if (sDir) {
197
+ const tasksDir = join(sDir, "tasks");
198
+ // Add postExecutionChecks to the result for the JSON write
199
+ const resultWithPostExec = {
200
+ ...result,
201
+ // Mark as failed if there was a blocking post-exec failure
202
+ passed: result.passed && !postExecBlockingFailure,
203
+ };
204
+ // Manually write with postExecutionChecks field
205
+ writeVerificationJSONWithPostExec(resultWithPostExec, tasksDir, tid, s.currentUnit.id, postExecChecks, postExecBlockingFailure ? attempt + 1 : undefined, postExecBlockingFailure ? maxRetries : undefined);
206
+ }
207
+ }
208
+ catch (evidenceErr) {
209
+ logWarning("engine", `verification-evidence: post-exec write error — ${evidenceErr.message}`);
210
+ }
211
+ }
212
+ // Update result.passed based on post-execution checks
213
+ if (postExecBlockingFailure) {
214
+ result.passed = false;
215
+ }
131
216
  // ── Auto-fix retry logic ──
132
217
  if (result.passed) {
133
218
  s.verificationRetryCount.delete(s.currentUnit.id);
134
219
  s.pendingVerificationRetry = null;
135
220
  return "continue";
136
221
  }
222
+ else if (postExecBlockingFailure) {
223
+ // Post-execution failures are cross-task consistency issues — retrying the same task won't fix them.
224
+ // Skip retry and pause immediately for human review.
225
+ s.verificationRetryCount.delete(s.currentUnit.id);
226
+ s.pendingVerificationRetry = null;
227
+ ctx.ui.notify(`Post-execution checks failed — cross-task consistency issue detected, pausing for human review`, "error");
228
+ await pauseAuto(ctx, pi);
229
+ return "pause";
230
+ }
137
231
  else if (autoFixEnabled && attempt + 1 <= maxRetries) {
138
232
  const nextAttempt = attempt + 1;
139
233
  s.verificationRetryCount.set(s.currentUnit.id, nextAttempt);
@@ -173,3 +267,46 @@ export async function runPostUnitVerification(vctx, pauseAuto) {
173
267
  return "continue";
174
268
  }
175
269
  }
270
+ /**
271
+ * Write verification evidence JSON with post-execution checks included.
272
+ * This is a variant of writeVerificationJSON that adds the postExecutionChecks field.
273
+ */
274
+ function writeVerificationJSONWithPostExec(result, tasksDir, taskId, unitId, postExecutionChecks, retryAttempt, maxRetries) {
275
+ mkdirSync(tasksDir, { recursive: true });
276
+ const evidence = {
277
+ schemaVersion: 1,
278
+ taskId,
279
+ unitId: unitId ?? taskId,
280
+ timestamp: result.timestamp,
281
+ passed: result.passed,
282
+ discoverySource: result.discoverySource,
283
+ checks: result.checks.map((check) => ({
284
+ command: check.command,
285
+ exitCode: check.exitCode,
286
+ durationMs: check.durationMs,
287
+ verdict: check.exitCode === 0 ? "pass" : "fail",
288
+ })),
289
+ ...(retryAttempt !== undefined ? { retryAttempt } : {}),
290
+ ...(maxRetries !== undefined ? { maxRetries } : {}),
291
+ postExecutionChecks,
292
+ };
293
+ if (result.runtimeErrors && result.runtimeErrors.length > 0) {
294
+ evidence.runtimeErrors = result.runtimeErrors.map(e => ({
295
+ source: e.source,
296
+ severity: e.severity,
297
+ message: e.message,
298
+ blocking: e.blocking,
299
+ }));
300
+ }
301
+ if (result.auditWarnings && result.auditWarnings.length > 0) {
302
+ evidence.auditWarnings = result.auditWarnings.map(w => ({
303
+ name: w.name,
304
+ severity: w.severity,
305
+ title: w.title,
306
+ url: w.url,
307
+ fixAvailable: w.fixAvailable,
308
+ }));
309
+ }
310
+ const filePath = join(tasksDir, `${taskId}-VERIFY.json`);
311
+ writeFileSync(filePath, JSON.stringify(evidence, null, 2) + "\n", "utf-8");
312
+ }
@@ -643,6 +643,7 @@ export async function pauseAuto(ctx, _pi, _errorContext) {
643
643
  sessionFile: s.pausedSessionFile,
644
644
  activeEngineId: s.activeEngineId,
645
645
  activeRunDir: s.activeRunDir,
646
+ autoStartTime: s.autoStartTime,
646
647
  };
647
648
  const runtimeDir = join(gsdRoot(s.originalBasePath || s.basePath), "runtime");
648
649
  mkdirSync(runtimeDir, { recursive: true });
@@ -842,6 +843,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
842
843
  s.activeRunDir = meta.activeRunDir ?? null;
843
844
  s.originalBasePath = meta.originalBasePath || base;
844
845
  s.stepMode = meta.stepMode ?? requestedStepMode;
846
+ s.autoStartTime = meta.autoStartTime || Date.now();
845
847
  s.paused = true;
846
848
  try {
847
849
  unlinkSync(pausedPath);
@@ -869,6 +871,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
869
871
  s.currentMilestoneId = meta.milestoneId;
870
872
  s.originalBasePath = meta.originalBasePath || base;
871
873
  s.stepMode = meta.stepMode ?? requestedStepMode;
874
+ s.autoStartTime = meta.autoStartTime || Date.now();
872
875
  s.paused = true;
873
876
  // Clean up the persisted file — we're consuming it
874
877
  try {
@@ -900,6 +903,8 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
900
903
  s.cmdCtx = ctx;
901
904
  s.basePath = base;
902
905
  setLogBasePath(base);
906
+ if (!s.autoStartTime || s.autoStartTime <= 0)
907
+ s.autoStartTime = Date.now();
903
908
  s.unitDispatchCount.clear();
904
909
  s.unitLifetimeDispatches.clear();
905
910
  if (!getLedger())
@@ -0,0 +1,28 @@
1
+ // GSD Extension — Notify Interceptor
2
+ // Wraps ctx.ui.notify() in-place to persist every notification through the
3
+ // notification store. Uses a WeakSet to prevent double-wrapping and handle
4
+ // UI context replacement on /reload gracefully.
5
+ import { appendNotification } from "../notification-store.js";
6
+ // Track which ui context objects have been wrapped to prevent double-install.
7
+ // WeakSet allows GC to collect replaced uiContext instances after /reload.
8
+ const _wrappedContexts = new WeakSet();
9
+ /**
10
+ * Install the notify interceptor on a context's UI object.
11
+ * Mutates ctx.ui.notify in place — the original is called after persistence.
12
+ * Safe to call multiple times; no-ops if already installed on the same ui object.
13
+ */
14
+ export function installNotifyInterceptor(ctx) {
15
+ if (_wrappedContexts.has(ctx.ui))
16
+ return;
17
+ const originalNotify = ctx.ui.notify.bind(ctx.ui);
18
+ ctx.ui.notify = (message, type) => {
19
+ try {
20
+ appendNotification(message, (type ?? "info"), "notify");
21
+ }
22
+ catch {
23
+ // Non-fatal — never let persistence break the UI
24
+ }
25
+ originalNotify(message, type);
26
+ };
27
+ _wrappedContexts.add(ctx.ui);
28
+ }
@@ -18,6 +18,9 @@ import { resetAskUserQuestionsCache } from "../../ask-user-questions.js";
18
18
  import { recordToolCall as safetyRecordToolCall, recordToolResult as safetyRecordToolResult } from "../safety/evidence-collector.js";
19
19
  import { classifyCommand } from "../safety/destructive-guard.js";
20
20
  import { logWarning as safetyLogWarning } from "../workflow-logger.js";
21
+ import { installNotifyInterceptor } from "./notify-interceptor.js";
22
+ import { initNotificationStore } from "../notification-store.js";
23
+ import { initNotificationWidget } from "../notification-widget.js";
21
24
  // Skip the welcome screen on the very first session_start — cli.ts already
22
25
  // printed it before the TUI launched. Only re-print on /clear (subsequent sessions).
23
26
  let isFirstSession = true;
@@ -27,6 +30,9 @@ async function syncServiceTierStatus(ctx) {
27
30
  }
28
31
  export function registerHooks(pi) {
29
32
  pi.on("session_start", async (_event, ctx) => {
33
+ initNotificationStore(process.cwd());
34
+ installNotifyInterceptor(ctx);
35
+ initNotificationWidget(ctx);
30
36
  resetWriteGateState();
31
37
  resetToolCallLoopGuard();
32
38
  resetAskUserQuestionsCache();
@@ -63,6 +69,8 @@ export function registerHooks(pi) {
63
69
  loadToolApiKeys();
64
70
  });
65
71
  pi.on("session_switch", async (_event, ctx) => {
72
+ initNotificationStore(process.cwd());
73
+ installNotifyInterceptor(ctx);
66
74
  resetWriteGateState();
67
75
  resetToolCallLoopGuard();
68
76
  resetAskUserQuestionsCache();
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { Key } from "@gsd/pi-tui";
4
4
  import { GSDDashboardOverlay } from "../dashboard-overlay.js";
5
+ import { GSDNotificationOverlay } from "../notification-overlay.js";
5
6
  import { ParallelMonitorOverlay } from "../parallel-monitor-overlay.js";
6
7
  import { shortcutDesc } from "../../shared/mod.js";
7
8
  export function registerShortcuts(pi) {
@@ -23,6 +24,20 @@ export function registerShortcuts(pi) {
23
24
  });
24
25
  },
25
26
  });
27
+ pi.registerShortcut(Key.ctrlAlt("n"), {
28
+ description: shortcutDesc("Open notification history", "/gsd notifications"),
29
+ handler: async (ctx) => {
30
+ await ctx.ui.custom((tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done()), {
31
+ overlay: true,
32
+ overlayOptions: {
33
+ width: "80%",
34
+ minWidth: 60,
35
+ maxHeight: "88%",
36
+ anchor: "center",
37
+ },
38
+ });
39
+ },
40
+ });
26
41
  pi.registerShortcut(Key.ctrlAlt("p"), {
27
42
  description: shortcutDesc("Open parallel worker monitor", "/gsd parallel watch"),
28
43
  handler: async (ctx) => {
@@ -4,7 +4,7 @@ import { join } from "node:path";
4
4
  import { loadRegistry } from "../workflow-templates.js";
5
5
  import { resolveProjectRoot } from "../worktree.js";
6
6
  const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
7
- export const GSD_COMMAND_DESCRIPTION = "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update|fast|mcp|rethink|codebase";
7
+ export const GSD_COMMAND_DESCRIPTION = "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update|fast|mcp|rethink|codebase|notifications";
8
8
  export const TOP_LEVEL_SUBCOMMANDS = [
9
9
  { cmd: "help", desc: "Categorized command reference with descriptions" },
10
10
  { cmd: "next", desc: "Explicit step mode (same as /gsd)" },
@@ -36,6 +36,7 @@ export const TOP_LEVEL_SUBCOMMANDS = [
36
36
  { cmd: "hooks", desc: "Show configured post-unit and pre-dispatch hooks" },
37
37
  { cmd: "run-hook", desc: "Manually trigger a specific hook" },
38
38
  { cmd: "skill-health", desc: "Skill lifecycle dashboard" },
39
+ { cmd: "notifications", desc: "View, filter, and clear persistent notification history" },
39
40
  { cmd: "doctor", desc: "Runtime health checks with auto-fix" },
40
41
  { cmd: "logs", desc: "Browse activity logs, debug logs, and metrics" },
41
42
  { cmd: "forensics", desc: "Examine execution logs" },
@@ -97,6 +98,11 @@ const NESTED_COMPLETIONS = {
97
98
  { cmd: "keys", desc: "Manage API keys" },
98
99
  { cmd: "prefs", desc: "Configure global preferences" },
99
100
  ],
101
+ notifications: [
102
+ { cmd: "clear", desc: "Clear all notifications" },
103
+ { cmd: "tail", desc: "Show last N notifications (default: 20)" },
104
+ { cmd: "filter", desc: "Filter by severity (error|warning|info|success)" },
105
+ ],
100
106
  logs: [
101
107
  { cmd: "debug", desc: "List or view debug log files" },
102
108
  { cmd: "tail", desc: "Show last N activity log summaries" },
@@ -25,6 +25,7 @@ export function showHelp(ctx) {
25
25
  " /gsd queue Show queued/dispatched units and execution order",
26
26
  " /gsd history View execution history [--cost] [--phase] [--model] [N]",
27
27
  " /gsd changelog Show categorized release notes [version]",
28
+ " /gsd notifications View persistent notification history [clear|tail|filter] (Ctrl+Alt+N)",
28
29
  "",
29
30
  "COURSE CORRECTION",
30
31
  " /gsd steer <desc> Apply user override to active work",
@@ -0,0 +1,103 @@
1
+ // GSD Extension — /gsd notifications Command Handler
2
+ // View, filter, and clear the persistent notification history.
3
+ import { readNotifications, clearNotifications, getUnreadCount, suppressPersistence, unsuppressPersistence, } from "../../notification-store.js";
4
+ import { GSDNotificationOverlay } from "../../notification-overlay.js";
5
+ function severityIcon(severity) {
6
+ switch (severity) {
7
+ case "error": return "✗";
8
+ case "warning": return "⚠";
9
+ case "success": return "✓";
10
+ case "info":
11
+ default: return "●";
12
+ }
13
+ }
14
+ function formatTimestamp(ts) {
15
+ try {
16
+ const d = new Date(ts);
17
+ return d.toLocaleString("en-US", { hour12: false, month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
18
+ }
19
+ catch {
20
+ return ts.slice(0, 19);
21
+ }
22
+ }
23
+ export async function handleNotificationsCommand(args, ctx, pi) {
24
+ // /gsd notifications clear
25
+ if (args === "clear") {
26
+ clearNotifications();
27
+ // Suppress persistence so the confirmation toast doesn't re-populate the store
28
+ suppressPersistence();
29
+ try {
30
+ ctx.ui.notify("All notifications cleared.", "success");
31
+ }
32
+ finally {
33
+ unsuppressPersistence();
34
+ }
35
+ return true;
36
+ }
37
+ // /gsd notifications tail [N]
38
+ if (args === "tail" || args.startsWith("tail ")) {
39
+ const countStr = args.replace(/^tail\s*/, "").trim();
40
+ const count = countStr ? parseInt(countStr, 10) : 20;
41
+ const n = isNaN(count) || count < 1 ? 20 : Math.min(count, 100);
42
+ const entries = readNotifications().slice(0, n);
43
+ if (entries.length === 0) {
44
+ ctx.ui.notify("No notifications.", "info");
45
+ return true;
46
+ }
47
+ const lines = entries.map((e) => `${severityIcon(e.severity)} [${formatTimestamp(e.ts)}] ${e.message}`);
48
+ ctx.ui.notify(`Last ${entries.length} notification(s):\n${lines.join("\n")}`, "info");
49
+ return true;
50
+ }
51
+ // /gsd notifications filter <severity>
52
+ if (args.startsWith("filter ")) {
53
+ const severity = args.replace(/^filter\s+/, "").trim().toLowerCase();
54
+ if (!["error", "warning", "info", "success"].includes(severity)) {
55
+ ctx.ui.notify("Usage: /gsd notifications filter <error|warning|info|success>", "warning");
56
+ return true;
57
+ }
58
+ const entries = readNotifications().filter((e) => e.severity === severity);
59
+ if (entries.length === 0) {
60
+ ctx.ui.notify(`No ${severity} notifications.`, "info");
61
+ return true;
62
+ }
63
+ const lines = entries.slice(0, 20).map((e) => `${severityIcon(e.severity)} [${formatTimestamp(e.ts)}] ${e.message}`);
64
+ const suffix = entries.length > 20 ? `\n... and ${entries.length - 20} more` : "";
65
+ ctx.ui.notify(`${severity} notifications (${entries.length}):\n${lines.join("\n")}${suffix}`, "info");
66
+ return true;
67
+ }
68
+ // /gsd notifications (no args) — open overlay in TUI, or print summary
69
+ if (args === "" || args === "status") {
70
+ // Try overlay first (TUI mode)
71
+ if (ctx.hasUI) {
72
+ try {
73
+ await ctx.ui.custom((tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done()), {
74
+ overlay: true,
75
+ overlayOptions: {
76
+ width: "80%",
77
+ minWidth: 60,
78
+ maxHeight: "88%",
79
+ anchor: "center",
80
+ },
81
+ });
82
+ return true;
83
+ }
84
+ catch {
85
+ // Fall through to text output if overlay fails
86
+ }
87
+ }
88
+ // Text fallback (RPC/headless mode)
89
+ const unread = getUnreadCount();
90
+ const entries = readNotifications().slice(0, 10);
91
+ if (entries.length === 0) {
92
+ ctx.ui.notify("No notifications.", "info");
93
+ return true;
94
+ }
95
+ const lines = entries.map((e) => `${severityIcon(e.severity)} [${formatTimestamp(e.ts)}] ${e.message}`);
96
+ const header = unread > 0 ? `${unread} unread — ` : "";
97
+ ctx.ui.notify(`${header}Recent notifications:\n${lines.join("\n")}`, "info");
98
+ return true;
99
+ }
100
+ // Unknown subcommand
101
+ ctx.ui.notify("Usage: /gsd notifications [clear|tail [N]|filter <severity>]", "warning");
102
+ return true;
103
+ }
@@ -175,6 +175,11 @@ Examples:
175
175
  await dispatchDirectPhase(ctx, pi, phase, projectRoot());
176
176
  return true;
177
177
  }
178
+ if (trimmed === "notifications" || trimmed.startsWith("notifications ")) {
179
+ const { handleNotificationsCommand } = await import("./notifications-handler.js");
180
+ await handleNotificationsCommand(trimmed.replace(/^notifications\s*/, "").trim(), ctx, pi);
181
+ return true;
182
+ }
178
183
  if (trimmed === "inspect") {
179
184
  await handleInspect(ctx);
180
185
  return true;