gsd-pi 2.64.0-dev.f8aad9b → 2.65.0-dev.5c8557b

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 (248) hide show
  1. package/dist/headless.js +3 -1
  2. package/dist/resources/extensions/bg-shell/bg-shell-lifecycle.js +22 -7
  3. package/dist/resources/extensions/bg-shell/process-manager.js +6 -1
  4. package/dist/resources/extensions/gsd/auto-post-unit.js +98 -1
  5. package/dist/resources/extensions/gsd/auto-verification.js +138 -1
  6. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +24 -13
  7. package/dist/resources/extensions/gsd/bootstrap/notify-interceptor.js +28 -0
  8. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +8 -0
  9. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +16 -0
  10. package/dist/resources/extensions/gsd/bootstrap/system-context.js +20 -0
  11. package/dist/resources/extensions/gsd/commands/catalog.js +7 -1
  12. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
  13. package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +104 -0
  14. package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
  15. package/dist/resources/extensions/gsd/notification-overlay.js +256 -0
  16. package/dist/resources/extensions/gsd/notification-store.js +273 -0
  17. package/dist/resources/extensions/gsd/notification-widget.js +56 -0
  18. package/dist/resources/extensions/gsd/post-execution-checks.js +407 -0
  19. package/dist/resources/extensions/gsd/pre-execution-checks.js +464 -0
  20. package/dist/resources/extensions/gsd/preferences-types.js +4 -0
  21. package/dist/resources/extensions/gsd/preferences-validation.js +33 -0
  22. package/dist/resources/extensions/gsd/preferences.js +4 -0
  23. package/dist/resources/extensions/gsd/verification-evidence.js +18 -0
  24. package/dist/resources/extensions/gsd/workflow-logger.js +8 -0
  25. package/dist/web/standalone/.next/BUILD_ID +1 -1
  26. package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -18
  27. package/dist/web/standalone/.next/build-manifest.json +2 -2
  28. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  29. package/dist/web/standalone/.next/routes-manifest.json +6 -0
  30. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  31. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/api/notifications/route.js +3 -0
  47. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -0
  48. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -0
  49. package/dist/web/standalone/.next/server/app/index.html +1 -1
  50. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app-paths-manifest.json +19 -18
  57. package/dist/web/standalone/.next/server/functions-config-manifest.json +1 -0
  58. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  59. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  60. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  61. package/dist/web/standalone/.next/static/chunks/app/_global-error/page-8805a20e15762c3c.js +1 -0
  62. package/dist/web/standalone/.next/static/chunks/app/api/boot/route-8805a20e15762c3c.js +1 -0
  63. package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/input/route-8805a20e15762c3c.js +1 -0
  64. package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/resize/route-8805a20e15762c3c.js +1 -0
  65. package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/stream/route-8805a20e15762c3c.js +1 -0
  66. package/dist/web/standalone/.next/static/chunks/app/api/browse-directories/route-8805a20e15762c3c.js +1 -0
  67. package/dist/web/standalone/.next/static/chunks/app/api/captures/route-8805a20e15762c3c.js +1 -0
  68. package/dist/web/standalone/.next/static/chunks/app/api/cleanup/route-8805a20e15762c3c.js +1 -0
  69. package/dist/web/standalone/.next/static/chunks/app/api/dev-mode/route-8805a20e15762c3c.js +1 -0
  70. package/dist/web/standalone/.next/static/chunks/app/api/doctor/route-8805a20e15762c3c.js +1 -0
  71. package/dist/web/standalone/.next/static/chunks/app/api/experimental/route-8805a20e15762c3c.js +1 -0
  72. package/dist/web/standalone/.next/static/chunks/app/api/export-data/route-8805a20e15762c3c.js +1 -0
  73. package/dist/web/standalone/.next/static/chunks/app/api/files/route-8805a20e15762c3c.js +1 -0
  74. package/dist/web/standalone/.next/static/chunks/app/api/forensics/route-8805a20e15762c3c.js +1 -0
  75. package/dist/web/standalone/.next/static/chunks/app/api/git/route-8805a20e15762c3c.js +1 -0
  76. package/dist/web/standalone/.next/static/chunks/app/api/history/route-8805a20e15762c3c.js +1 -0
  77. package/dist/web/standalone/.next/static/chunks/app/api/hooks/route-8805a20e15762c3c.js +1 -0
  78. package/dist/web/standalone/.next/static/chunks/app/api/inspect/route-8805a20e15762c3c.js +1 -0
  79. package/dist/web/standalone/.next/static/chunks/app/api/knowledge/route-8805a20e15762c3c.js +1 -0
  80. package/dist/web/standalone/.next/static/chunks/app/api/live-state/route-8805a20e15762c3c.js +1 -0
  81. package/dist/web/standalone/.next/static/chunks/app/api/notifications/route-8805a20e15762c3c.js +1 -0
  82. package/dist/web/standalone/.next/static/chunks/app/api/onboarding/route-8805a20e15762c3c.js +1 -0
  83. package/dist/web/standalone/.next/static/chunks/app/api/preferences/route-8805a20e15762c3c.js +1 -0
  84. package/dist/web/standalone/.next/static/chunks/app/api/projects/route-8805a20e15762c3c.js +1 -0
  85. package/dist/web/standalone/.next/static/chunks/app/api/recovery/route-8805a20e15762c3c.js +1 -0
  86. package/dist/web/standalone/.next/static/chunks/app/api/remote-questions/route-8805a20e15762c3c.js +1 -0
  87. package/dist/web/standalone/.next/static/chunks/app/api/session/browser/route-8805a20e15762c3c.js +1 -0
  88. package/dist/web/standalone/.next/static/chunks/app/api/session/command/route-8805a20e15762c3c.js +1 -0
  89. package/dist/web/standalone/.next/static/chunks/app/api/session/events/route-8805a20e15762c3c.js +1 -0
  90. package/dist/web/standalone/.next/static/chunks/app/api/session/manage/route-8805a20e15762c3c.js +1 -0
  91. package/dist/web/standalone/.next/static/chunks/app/api/settings-data/route-8805a20e15762c3c.js +1 -0
  92. package/dist/web/standalone/.next/static/chunks/app/api/shutdown/route-8805a20e15762c3c.js +1 -0
  93. package/dist/web/standalone/.next/static/chunks/app/api/skill-health/route-8805a20e15762c3c.js +1 -0
  94. package/dist/web/standalone/.next/static/chunks/app/api/steer/route-8805a20e15762c3c.js +1 -0
  95. package/dist/web/standalone/.next/static/chunks/app/api/switch-root/route-8805a20e15762c3c.js +1 -0
  96. package/dist/web/standalone/.next/static/chunks/app/api/terminal/input/route-8805a20e15762c3c.js +1 -0
  97. package/dist/web/standalone/.next/static/chunks/app/api/terminal/resize/route-8805a20e15762c3c.js +1 -0
  98. package/dist/web/standalone/.next/static/chunks/app/api/terminal/sessions/route-8805a20e15762c3c.js +1 -0
  99. package/dist/web/standalone/.next/static/chunks/app/api/terminal/stream/route-8805a20e15762c3c.js +1 -0
  100. package/dist/web/standalone/.next/static/chunks/app/api/terminal/upload/route-8805a20e15762c3c.js +1 -0
  101. package/dist/web/standalone/.next/static/chunks/app/api/undo/route-8805a20e15762c3c.js +1 -0
  102. package/dist/web/standalone/.next/static/chunks/app/api/update/route-8805a20e15762c3c.js +1 -0
  103. package/dist/web/standalone/.next/static/chunks/app/api/visualizer/route-8805a20e15762c3c.js +1 -0
  104. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/app-error-8805a20e15762c3c.js +1 -0
  105. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/forbidden-8805a20e15762c3c.js +1 -0
  106. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/not-found-8805a20e15762c3c.js +1 -0
  107. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/unauthorized-8805a20e15762c3c.js +1 -0
  108. package/dist/web/standalone/.next/static/qq3YfHPfyqvh3DIMVmsRH/_buildManifest.js +1 -0
  109. package/package.json +1 -1
  110. package/packages/pi-agent-core/dist/agent-loop.js +26 -9
  111. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  112. package/packages/pi-agent-core/src/agent-loop.test.ts +100 -4
  113. package/packages/pi-agent-core/src/agent-loop.ts +43 -12
  114. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.d.ts +2 -0
  115. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.d.ts.map +1 -0
  116. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +38 -0
  117. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -0
  118. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/agent-session.js +11 -0
  120. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.d.ts +2 -0
  122. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.d.ts.map +1 -0
  123. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +24 -0
  124. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +1 -0
  125. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  126. package/packages/pi-coding-agent/dist/core/resource-loader.js +4 -1
  127. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  128. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  129. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  130. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +8 -0
  131. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  132. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +6 -0
  133. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  134. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +36 -0
  135. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  136. package/packages/pi-coding-agent/package.json +1 -1
  137. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +64 -0
  138. package/packages/pi-coding-agent/src/core/agent-session.ts +10 -0
  139. package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +42 -0
  140. package/packages/pi-coding-agent/src/core/resource-loader.ts +5 -1
  141. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +9 -0
  142. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +33 -0
  143. package/packages/pi-tui/dist/__tests__/overlay-layout.test.d.ts +2 -0
  144. package/packages/pi-tui/dist/__tests__/overlay-layout.test.d.ts.map +1 -0
  145. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +66 -0
  146. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -0
  147. package/packages/pi-tui/dist/components/loader.d.ts +4 -2
  148. package/packages/pi-tui/dist/components/loader.d.ts.map +1 -1
  149. package/packages/pi-tui/dist/components/loader.js +27 -9
  150. package/packages/pi-tui/dist/components/loader.js.map +1 -1
  151. package/packages/pi-tui/dist/components/text.d.ts.map +1 -1
  152. package/packages/pi-tui/dist/components/text.js +2 -0
  153. package/packages/pi-tui/dist/components/text.js.map +1 -1
  154. package/packages/pi-tui/dist/overlay-layout.d.ts.map +1 -1
  155. package/packages/pi-tui/dist/overlay-layout.js +12 -1
  156. package/packages/pi-tui/dist/overlay-layout.js.map +1 -1
  157. package/packages/pi-tui/dist/tui.d.ts +4 -0
  158. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  159. package/packages/pi-tui/dist/tui.js +35 -0
  160. package/packages/pi-tui/dist/tui.js.map +1 -1
  161. package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +82 -0
  162. package/packages/pi-tui/src/components/loader.ts +27 -10
  163. package/packages/pi-tui/src/components/text.ts +1 -0
  164. package/packages/pi-tui/src/overlay-layout.ts +13 -1
  165. package/packages/pi-tui/src/tui.ts +34 -0
  166. package/pkg/package.json +1 -1
  167. package/src/resources/extensions/bg-shell/bg-shell-lifecycle.ts +19 -7
  168. package/src/resources/extensions/bg-shell/process-manager.ts +8 -2
  169. package/src/resources/extensions/gsd/auto-post-unit.ts +122 -0
  170. package/src/resources/extensions/gsd/auto-verification.ts +190 -2
  171. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +25 -13
  172. package/src/resources/extensions/gsd/bootstrap/notify-interceptor.ts +34 -0
  173. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -0
  174. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +20 -0
  175. package/src/resources/extensions/gsd/bootstrap/system-context.ts +28 -0
  176. package/src/resources/extensions/gsd/commands/catalog.ts +7 -1
  177. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
  178. package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +140 -0
  179. package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
  180. package/src/resources/extensions/gsd/notification-overlay.ts +295 -0
  181. package/src/resources/extensions/gsd/notification-store.ts +293 -0
  182. package/src/resources/extensions/gsd/notification-widget.ts +68 -0
  183. package/src/resources/extensions/gsd/post-execution-checks.ts +539 -0
  184. package/src/resources/extensions/gsd/pre-execution-checks.ts +573 -0
  185. package/src/resources/extensions/gsd/preferences-types.ts +28 -0
  186. package/src/resources/extensions/gsd/preferences-validation.ts +33 -0
  187. package/src/resources/extensions/gsd/preferences.ts +4 -0
  188. package/src/resources/extensions/gsd/tests/complete-slice-string-coercion.test.ts +36 -0
  189. package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +76 -0
  190. package/src/resources/extensions/gsd/tests/enhanced-verification-integration.test.ts +526 -0
  191. package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +73 -0
  192. package/src/resources/extensions/gsd/tests/notification-store.test.ts +282 -0
  193. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +312 -0
  194. package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +813 -0
  195. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +999 -0
  196. package/src/resources/extensions/gsd/tests/pre-execution-fail-closed.test.ts +266 -0
  197. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +457 -0
  198. package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +163 -0
  199. package/src/resources/extensions/gsd/verification-evidence.ts +68 -0
  200. package/src/resources/extensions/gsd/workflow-logger.ts +13 -0
  201. package/dist/web/standalone/.next/static/F1mOwzgCW9R8N3Pt1Et87/_buildManifest.js +0 -1
  202. package/dist/web/standalone/.next/static/chunks/app/_global-error/page-c4cc189e7b117ea2.js +0 -1
  203. package/dist/web/standalone/.next/static/chunks/app/api/boot/route-c4cc189e7b117ea2.js +0 -1
  204. package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/input/route-c4cc189e7b117ea2.js +0 -1
  205. package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/resize/route-c4cc189e7b117ea2.js +0 -1
  206. package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/stream/route-c4cc189e7b117ea2.js +0 -1
  207. package/dist/web/standalone/.next/static/chunks/app/api/browse-directories/route-c4cc189e7b117ea2.js +0 -1
  208. package/dist/web/standalone/.next/static/chunks/app/api/captures/route-c4cc189e7b117ea2.js +0 -1
  209. package/dist/web/standalone/.next/static/chunks/app/api/cleanup/route-c4cc189e7b117ea2.js +0 -1
  210. package/dist/web/standalone/.next/static/chunks/app/api/dev-mode/route-c4cc189e7b117ea2.js +0 -1
  211. package/dist/web/standalone/.next/static/chunks/app/api/doctor/route-c4cc189e7b117ea2.js +0 -1
  212. package/dist/web/standalone/.next/static/chunks/app/api/experimental/route-c4cc189e7b117ea2.js +0 -1
  213. package/dist/web/standalone/.next/static/chunks/app/api/export-data/route-c4cc189e7b117ea2.js +0 -1
  214. package/dist/web/standalone/.next/static/chunks/app/api/files/route-c4cc189e7b117ea2.js +0 -1
  215. package/dist/web/standalone/.next/static/chunks/app/api/forensics/route-c4cc189e7b117ea2.js +0 -1
  216. package/dist/web/standalone/.next/static/chunks/app/api/git/route-c4cc189e7b117ea2.js +0 -1
  217. package/dist/web/standalone/.next/static/chunks/app/api/history/route-c4cc189e7b117ea2.js +0 -1
  218. package/dist/web/standalone/.next/static/chunks/app/api/hooks/route-c4cc189e7b117ea2.js +0 -1
  219. package/dist/web/standalone/.next/static/chunks/app/api/inspect/route-c4cc189e7b117ea2.js +0 -1
  220. package/dist/web/standalone/.next/static/chunks/app/api/knowledge/route-c4cc189e7b117ea2.js +0 -1
  221. package/dist/web/standalone/.next/static/chunks/app/api/live-state/route-c4cc189e7b117ea2.js +0 -1
  222. package/dist/web/standalone/.next/static/chunks/app/api/onboarding/route-c4cc189e7b117ea2.js +0 -1
  223. package/dist/web/standalone/.next/static/chunks/app/api/preferences/route-c4cc189e7b117ea2.js +0 -1
  224. package/dist/web/standalone/.next/static/chunks/app/api/projects/route-c4cc189e7b117ea2.js +0 -1
  225. package/dist/web/standalone/.next/static/chunks/app/api/recovery/route-c4cc189e7b117ea2.js +0 -1
  226. package/dist/web/standalone/.next/static/chunks/app/api/remote-questions/route-c4cc189e7b117ea2.js +0 -1
  227. package/dist/web/standalone/.next/static/chunks/app/api/session/browser/route-c4cc189e7b117ea2.js +0 -1
  228. package/dist/web/standalone/.next/static/chunks/app/api/session/command/route-c4cc189e7b117ea2.js +0 -1
  229. package/dist/web/standalone/.next/static/chunks/app/api/session/events/route-c4cc189e7b117ea2.js +0 -1
  230. package/dist/web/standalone/.next/static/chunks/app/api/session/manage/route-c4cc189e7b117ea2.js +0 -1
  231. package/dist/web/standalone/.next/static/chunks/app/api/settings-data/route-c4cc189e7b117ea2.js +0 -1
  232. package/dist/web/standalone/.next/static/chunks/app/api/shutdown/route-c4cc189e7b117ea2.js +0 -1
  233. package/dist/web/standalone/.next/static/chunks/app/api/skill-health/route-c4cc189e7b117ea2.js +0 -1
  234. package/dist/web/standalone/.next/static/chunks/app/api/steer/route-c4cc189e7b117ea2.js +0 -1
  235. package/dist/web/standalone/.next/static/chunks/app/api/switch-root/route-c4cc189e7b117ea2.js +0 -1
  236. package/dist/web/standalone/.next/static/chunks/app/api/terminal/input/route-c4cc189e7b117ea2.js +0 -1
  237. package/dist/web/standalone/.next/static/chunks/app/api/terminal/resize/route-c4cc189e7b117ea2.js +0 -1
  238. package/dist/web/standalone/.next/static/chunks/app/api/terminal/sessions/route-c4cc189e7b117ea2.js +0 -1
  239. package/dist/web/standalone/.next/static/chunks/app/api/terminal/stream/route-c4cc189e7b117ea2.js +0 -1
  240. package/dist/web/standalone/.next/static/chunks/app/api/terminal/upload/route-c4cc189e7b117ea2.js +0 -1
  241. package/dist/web/standalone/.next/static/chunks/app/api/undo/route-c4cc189e7b117ea2.js +0 -1
  242. package/dist/web/standalone/.next/static/chunks/app/api/update/route-c4cc189e7b117ea2.js +0 -1
  243. package/dist/web/standalone/.next/static/chunks/app/api/visualizer/route-c4cc189e7b117ea2.js +0 -1
  244. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/app-error-c4cc189e7b117ea2.js +0 -1
  245. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/forbidden-c4cc189e7b117ea2.js +0 -1
  246. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/not-found-c4cc189e7b117ea2.js +0 -1
  247. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/unauthorized-c4cc189e7b117ea2.js +0 -1
  248. /package/dist/web/standalone/.next/static/{F1mOwzgCW9R8N3Pt1Et87 → qq3YfHPfyqvh3DIMVmsRH}/_ssgManifest.js +0 -0
@@ -0,0 +1,999 @@
1
+ /**
2
+ * pre-execution-checks.test.ts — Unit tests for pre-execution validation checks.
3
+ *
4
+ * Tests all 4 check types:
5
+ * 1. Package existence — npm view mocking, timeout handling
6
+ * 2. File path consistency — files exist vs prior expected_output
7
+ * 3. Task ordering — detect impossible read-before-create
8
+ * 4. Interface contracts — contradictory function signatures
9
+ */
10
+
11
+ import { describe, test, mock } from "node:test";
12
+ import assert from "node:assert/strict";
13
+ import { tmpdir } from "node:os";
14
+ import { mkdirSync, writeFileSync, rmSync } from "node:fs";
15
+ import { join } from "node:path";
16
+
17
+ import {
18
+ extractPackageReferences,
19
+ checkFilePathConsistency,
20
+ checkTaskOrdering,
21
+ checkInterfaceContracts,
22
+ runPreExecutionChecks,
23
+ normalizeFilePath,
24
+ type PreExecutionResult,
25
+ } from "../pre-execution-checks.ts";
26
+ import type { TaskRow } from "../gsd-db.ts";
27
+
28
+ // ─── Test Fixtures ───────────────────────────────────────────────────────────
29
+
30
+ /**
31
+ * Create a minimal TaskRow for testing.
32
+ */
33
+ function createTask(overrides: Partial<TaskRow> = {}): TaskRow {
34
+ return {
35
+ milestone_id: "M001",
36
+ slice_id: "S01",
37
+ id: overrides.id ?? "T01",
38
+ title: "Test Task",
39
+ status: "pending",
40
+ one_liner: "",
41
+ narrative: "",
42
+ verification_result: "",
43
+ duration: "",
44
+ completed_at: null,
45
+ blocker_discovered: false,
46
+ deviations: "",
47
+ known_issues: "",
48
+ key_files: [],
49
+ key_decisions: [],
50
+ full_summary_md: "",
51
+ description: overrides.description ?? "",
52
+ estimate: "",
53
+ files: overrides.files ?? [],
54
+ verify: "",
55
+ inputs: overrides.inputs ?? [],
56
+ expected_output: overrides.expected_output ?? [],
57
+ observability_impact: "",
58
+ full_plan_md: "",
59
+ sequence: overrides.sequence ?? 0,
60
+ ...overrides,
61
+ };
62
+ }
63
+
64
+ // ─── Package Reference Extraction Tests ──────────────────────────────────────
65
+
66
+ describe("extractPackageReferences", () => {
67
+ test("extracts npm install patterns", () => {
68
+ const desc = "Run npm install lodash then npm i axios";
69
+ const packages = extractPackageReferences(desc);
70
+ assert.deepEqual(packages.sort(), ["axios", "lodash"]);
71
+ });
72
+
73
+ test("extracts yarn add patterns", () => {
74
+ const desc = "yarn add react-dom";
75
+ const packages = extractPackageReferences(desc);
76
+ assert.deepEqual(packages, ["react-dom"]);
77
+ });
78
+
79
+ test("extracts scoped packages", () => {
80
+ const desc = "npm install @types/node @babel/core";
81
+ const packages = extractPackageReferences(desc);
82
+ assert.ok(packages.includes("@types/node"));
83
+ assert.ok(packages.includes("@babel/core"));
84
+ });
85
+
86
+ test("extracts require statements from code blocks", () => {
87
+ const desc = `
88
+ \`\`\`javascript
89
+ const fs = require('fs-extra');
90
+ const path = require('path');
91
+ \`\`\`
92
+ `;
93
+ const packages = extractPackageReferences(desc);
94
+ assert.ok(packages.includes("fs-extra"));
95
+ });
96
+
97
+ test("extracts import statements from code blocks", () => {
98
+ const desc = `
99
+ \`\`\`typescript
100
+ import express from 'express';
101
+ import { Router } from 'express';
102
+ import type { Request } from 'express';
103
+ \`\`\`
104
+ `;
105
+ const packages = extractPackageReferences(desc);
106
+ assert.ok(packages.includes("express"));
107
+ });
108
+
109
+ test("ignores relative imports", () => {
110
+ const desc = `import { foo } from './local-file';`;
111
+ const packages = extractPackageReferences(desc);
112
+ assert.deepEqual(packages, []);
113
+ });
114
+
115
+ test("ignores node builtins", () => {
116
+ const desc = `import fs from 'node:fs';`;
117
+ const packages = extractPackageReferences(desc);
118
+ assert.deepEqual(packages, []);
119
+ });
120
+
121
+ test("normalizes package subpaths", () => {
122
+ const desc = "npm install lodash/get";
123
+ const packages = extractPackageReferences(desc);
124
+ assert.deepEqual(packages, ["lodash"]);
125
+ });
126
+
127
+ test("handles empty description", () => {
128
+ const packages = extractPackageReferences("");
129
+ assert.deepEqual(packages, []);
130
+ });
131
+
132
+ test("ignores flags in npm install", () => {
133
+ const desc = "npm install -D typescript";
134
+ const packages = extractPackageReferences(desc);
135
+ assert.ok(packages.includes("typescript"));
136
+ assert.ok(!packages.includes("-D"));
137
+ });
138
+ });
139
+
140
+ // ─── File Path Consistency Tests ─────────────────────────────────────────────
141
+
142
+ describe("checkFilePathConsistency", () => {
143
+ let tempDir: string;
144
+
145
+ test("passes when files exist on disk", () => {
146
+ tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
147
+ mkdirSync(tempDir, { recursive: true });
148
+ writeFileSync(join(tempDir, "existing.ts"), "// content");
149
+
150
+ try {
151
+ const tasks = [
152
+ createTask({
153
+ id: "T01",
154
+ files: ["existing.ts"],
155
+ inputs: [],
156
+ expected_output: [],
157
+ }),
158
+ ];
159
+
160
+ const results = checkFilePathConsistency(tasks, tempDir);
161
+ assert.deepEqual(results, []);
162
+ } finally {
163
+ rmSync(tempDir, { recursive: true, force: true });
164
+ }
165
+ });
166
+
167
+ test("passes when files are in prior expected_output", () => {
168
+ tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
169
+ mkdirSync(tempDir, { recursive: true });
170
+
171
+ try {
172
+ const tasks = [
173
+ createTask({
174
+ id: "T01",
175
+ sequence: 0,
176
+ files: [],
177
+ inputs: [],
178
+ expected_output: ["generated.ts"],
179
+ }),
180
+ createTask({
181
+ id: "T02",
182
+ sequence: 1,
183
+ files: ["generated.ts"],
184
+ inputs: [],
185
+ expected_output: [],
186
+ }),
187
+ ];
188
+
189
+ const results = checkFilePathConsistency(tasks, tempDir);
190
+ assert.deepEqual(results, []);
191
+ } finally {
192
+ rmSync(tempDir, { recursive: true, force: true });
193
+ }
194
+ });
195
+
196
+ test("fails when files don't exist and not in prior outputs", () => {
197
+ tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
198
+ mkdirSync(tempDir, { recursive: true });
199
+
200
+ try {
201
+ const tasks = [
202
+ createTask({
203
+ id: "T01",
204
+ files: ["nonexistent.ts"],
205
+ inputs: [],
206
+ expected_output: [],
207
+ }),
208
+ ];
209
+
210
+ const results = checkFilePathConsistency(tasks, tempDir);
211
+ assert.equal(results.length, 1);
212
+ assert.equal(results[0].category, "file");
213
+ assert.equal(results[0].passed, false);
214
+ assert.equal(results[0].blocking, true);
215
+ assert.ok(results[0].message.includes("nonexistent.ts"));
216
+ } finally {
217
+ rmSync(tempDir, { recursive: true, force: true });
218
+ }
219
+ });
220
+
221
+ test("checks both files and inputs arrays", () => {
222
+ tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
223
+ mkdirSync(tempDir, { recursive: true });
224
+
225
+ try {
226
+ const tasks = [
227
+ createTask({
228
+ id: "T01",
229
+ files: ["missing-file.ts"],
230
+ inputs: ["missing-input.ts"],
231
+ expected_output: [],
232
+ }),
233
+ ];
234
+
235
+ const results = checkFilePathConsistency(tasks, tempDir);
236
+ assert.equal(results.length, 2);
237
+ assert.ok(results.some((r) => r.target === "missing-file.ts"));
238
+ assert.ok(results.some((r) => r.target === "missing-input.ts"));
239
+ } finally {
240
+ rmSync(tempDir, { recursive: true, force: true });
241
+ }
242
+ });
243
+
244
+ test("skips empty file strings", () => {
245
+ tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
246
+ mkdirSync(tempDir, { recursive: true });
247
+
248
+ try {
249
+ const tasks = [
250
+ createTask({
251
+ id: "T01",
252
+ files: ["", " "],
253
+ inputs: [],
254
+ expected_output: [],
255
+ }),
256
+ ];
257
+
258
+ const results = checkFilePathConsistency(tasks, tempDir);
259
+ assert.deepEqual(results, []);
260
+ } finally {
261
+ rmSync(tempDir, { recursive: true, force: true });
262
+ }
263
+ });
264
+ });
265
+
266
+ // ─── Path Normalization Tests ────────────────────────────────────────────────
267
+
268
+ describe("normalizeFilePath", () => {
269
+ test("strips leading ./", () => {
270
+ assert.equal(normalizeFilePath("./src/a.ts"), "src/a.ts");
271
+ assert.equal(normalizeFilePath("././foo.ts"), "foo.ts");
272
+ });
273
+
274
+ test("normalizes backslashes to forward slashes", () => {
275
+ assert.equal(normalizeFilePath("src\\a.ts"), "src/a.ts");
276
+ assert.equal(normalizeFilePath("src\\sub\\file.ts"), "src/sub/file.ts");
277
+ });
278
+
279
+ test("removes duplicate slashes", () => {
280
+ assert.equal(normalizeFilePath("src//a.ts"), "src/a.ts");
281
+ assert.equal(normalizeFilePath("src///sub//file.ts"), "src/sub/file.ts");
282
+ });
283
+
284
+ test("handles empty string", () => {
285
+ assert.equal(normalizeFilePath(""), "");
286
+ });
287
+
288
+ test("removes trailing slash", () => {
289
+ assert.equal(normalizeFilePath("src/"), "src");
290
+ assert.equal(normalizeFilePath("src/sub/"), "src/sub");
291
+ });
292
+
293
+ test("handles paths without any normalization needed", () => {
294
+ assert.equal(normalizeFilePath("src/a.ts"), "src/a.ts");
295
+ assert.equal(normalizeFilePath("index.ts"), "index.ts");
296
+ });
297
+ });
298
+
299
+ describe("checkFilePathConsistency with path normalization", () => {
300
+ let tempDir: string;
301
+
302
+ test("./path matches path in prior expected_output", () => {
303
+ tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
304
+ mkdirSync(tempDir, { recursive: true });
305
+
306
+ try {
307
+ const tasks = [
308
+ createTask({
309
+ id: "T01",
310
+ sequence: 0,
311
+ files: [],
312
+ inputs: [],
313
+ expected_output: ["src/generated.ts"], // Output without ./
314
+ }),
315
+ createTask({
316
+ id: "T02",
317
+ sequence: 1,
318
+ files: ["./src/generated.ts"], // Input with ./
319
+ inputs: [],
320
+ expected_output: [],
321
+ }),
322
+ ];
323
+
324
+ const results = checkFilePathConsistency(tasks, tempDir);
325
+ assert.deepEqual(results, [], "Should pass because ./src/generated.ts matches src/generated.ts");
326
+ } finally {
327
+ rmSync(tempDir, { recursive: true, force: true });
328
+ }
329
+ });
330
+
331
+ test("path matches ./path in prior expected_output", () => {
332
+ tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
333
+ mkdirSync(tempDir, { recursive: true });
334
+
335
+ try {
336
+ const tasks = [
337
+ createTask({
338
+ id: "T01",
339
+ sequence: 0,
340
+ files: [],
341
+ inputs: [],
342
+ expected_output: ["./src/generated.ts"], // Output with ./
343
+ }),
344
+ createTask({
345
+ id: "T02",
346
+ sequence: 1,
347
+ files: ["src/generated.ts"], // Input without ./
348
+ inputs: [],
349
+ expected_output: [],
350
+ }),
351
+ ];
352
+
353
+ const results = checkFilePathConsistency(tasks, tempDir);
354
+ assert.deepEqual(results, [], "Should pass because src/generated.ts matches ./src/generated.ts");
355
+ } finally {
356
+ rmSync(tempDir, { recursive: true, force: true });
357
+ }
358
+ });
359
+
360
+ test("paths with mixed separators match", () => {
361
+ tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
362
+ mkdirSync(tempDir, { recursive: true });
363
+
364
+ try {
365
+ const tasks = [
366
+ createTask({
367
+ id: "T01",
368
+ sequence: 0,
369
+ files: [],
370
+ inputs: [],
371
+ expected_output: ["src/sub/file.ts"],
372
+ }),
373
+ createTask({
374
+ id: "T02",
375
+ sequence: 1,
376
+ files: ["src\\sub\\file.ts"], // Backslash separators
377
+ inputs: [],
378
+ expected_output: [],
379
+ }),
380
+ ];
381
+
382
+ const results = checkFilePathConsistency(tasks, tempDir);
383
+ assert.deepEqual(results, [], "Should pass because backslash paths normalize to forward slash");
384
+ } finally {
385
+ rmSync(tempDir, { recursive: true, force: true });
386
+ }
387
+ });
388
+ });
389
+
390
+ describe("checkTaskOrdering with path normalization", () => {
391
+ test("./path triggers ordering check for path in expected_output", () => {
392
+ const tasks = [
393
+ createTask({
394
+ id: "T01",
395
+ sequence: 0,
396
+ files: ["./generated.ts"], // Reads with ./
397
+ inputs: [],
398
+ expected_output: [],
399
+ }),
400
+ createTask({
401
+ id: "T02",
402
+ sequence: 1,
403
+ files: [],
404
+ inputs: [],
405
+ expected_output: ["generated.ts"], // Creates without ./
406
+ }),
407
+ ];
408
+
409
+ const results = checkTaskOrdering(tasks, "/tmp");
410
+ assert.equal(results.length, 1, "Should detect ordering violation despite ./");
411
+ assert.ok(results[0].message.includes("T01"));
412
+ assert.ok(results[0].message.includes("T02"));
413
+ });
414
+
415
+ test("path triggers ordering check for ./path in expected_output", () => {
416
+ const tasks = [
417
+ createTask({
418
+ id: "T01",
419
+ sequence: 0,
420
+ files: ["generated.ts"], // Reads without ./
421
+ inputs: [],
422
+ expected_output: [],
423
+ }),
424
+ createTask({
425
+ id: "T02",
426
+ sequence: 1,
427
+ files: [],
428
+ inputs: [],
429
+ expected_output: ["./generated.ts"], // Creates with ./
430
+ }),
431
+ ];
432
+
433
+ const results = checkTaskOrdering(tasks, "/tmp");
434
+ assert.equal(results.length, 1, "Should detect ordering violation despite ./ on creator");
435
+ assert.ok(results[0].message.includes("sequence violation"));
436
+ });
437
+
438
+ test("no false positive when correctly ordered with mixed paths", () => {
439
+ const tasks = [
440
+ createTask({
441
+ id: "T01",
442
+ sequence: 0,
443
+ files: [],
444
+ inputs: [],
445
+ expected_output: ["./src/api.ts"],
446
+ }),
447
+ createTask({
448
+ id: "T02",
449
+ sequence: 1,
450
+ files: ["src/api.ts"], // Same file, different notation
451
+ inputs: [],
452
+ expected_output: [],
453
+ }),
454
+ ];
455
+
456
+ const results = checkTaskOrdering(tasks, "/tmp");
457
+ assert.deepEqual(results, [], "Should pass - T02 reads file that T01 already created");
458
+ });
459
+ });
460
+
461
+ // ─── Task Ordering Tests ─────────────────────────────────────────────────────
462
+
463
+ describe("checkTaskOrdering", () => {
464
+ test("passes when tasks are correctly ordered", () => {
465
+ const tasks = [
466
+ createTask({
467
+ id: "T01",
468
+ sequence: 0,
469
+ files: [],
470
+ inputs: [],
471
+ expected_output: ["api.ts"],
472
+ }),
473
+ createTask({
474
+ id: "T02",
475
+ sequence: 1,
476
+ files: ["api.ts"],
477
+ inputs: [],
478
+ expected_output: [],
479
+ }),
480
+ ];
481
+
482
+ const results = checkTaskOrdering(tasks, "/tmp");
483
+ assert.deepEqual(results, []);
484
+ });
485
+
486
+ test("fails when task reads file created by later task", () => {
487
+ const tasks = [
488
+ createTask({
489
+ id: "T01",
490
+ sequence: 0,
491
+ files: ["generated.ts"], // Reads file that doesn't exist yet
492
+ inputs: [],
493
+ expected_output: [],
494
+ }),
495
+ createTask({
496
+ id: "T02",
497
+ sequence: 1,
498
+ files: [],
499
+ inputs: [],
500
+ expected_output: ["generated.ts"], // Creates the file
501
+ }),
502
+ ];
503
+
504
+ const results = checkTaskOrdering(tasks, "/tmp");
505
+ assert.equal(results.length, 1);
506
+ assert.equal(results[0].category, "file");
507
+ assert.equal(results[0].passed, false);
508
+ assert.equal(results[0].blocking, true);
509
+ assert.ok(results[0].message.includes("T01"));
510
+ assert.ok(results[0].message.includes("T02"));
511
+ assert.ok(results[0].message.includes("sequence violation"));
512
+ });
513
+
514
+ test("detects ordering violation in inputs array", () => {
515
+ const tasks = [
516
+ createTask({
517
+ id: "T01",
518
+ sequence: 0,
519
+ files: [],
520
+ inputs: ["schema.json"],
521
+ expected_output: [],
522
+ }),
523
+ createTask({
524
+ id: "T02",
525
+ sequence: 1,
526
+ files: [],
527
+ inputs: [],
528
+ expected_output: ["schema.json"],
529
+ }),
530
+ ];
531
+
532
+ const results = checkTaskOrdering(tasks, "/tmp");
533
+ assert.equal(results.length, 1);
534
+ assert.ok(results[0].message.includes("schema.json"));
535
+ });
536
+
537
+ test("handles multiple ordering violations", () => {
538
+ const tasks = [
539
+ createTask({
540
+ id: "T01",
541
+ sequence: 0,
542
+ files: ["a.ts", "b.ts"],
543
+ inputs: [],
544
+ expected_output: [],
545
+ }),
546
+ createTask({
547
+ id: "T02",
548
+ sequence: 1,
549
+ files: [],
550
+ inputs: [],
551
+ expected_output: ["a.ts"],
552
+ }),
553
+ createTask({
554
+ id: "T03",
555
+ sequence: 2,
556
+ files: [],
557
+ inputs: [],
558
+ expected_output: ["b.ts"],
559
+ }),
560
+ ];
561
+
562
+ const results = checkTaskOrdering(tasks, "/tmp");
563
+ assert.equal(results.length, 2);
564
+ });
565
+
566
+ test("passes when no dependencies between tasks", () => {
567
+ const tasks = [
568
+ createTask({
569
+ id: "T01",
570
+ sequence: 0,
571
+ files: [],
572
+ inputs: [],
573
+ expected_output: ["a.ts"],
574
+ }),
575
+ createTask({
576
+ id: "T02",
577
+ sequence: 1,
578
+ files: [],
579
+ inputs: [],
580
+ expected_output: ["b.ts"],
581
+ }),
582
+ ];
583
+
584
+ const results = checkTaskOrdering(tasks, "/tmp");
585
+ assert.deepEqual(results, []);
586
+ });
587
+ });
588
+
589
+ // ─── Interface Contract Tests ────────────────────────────────────────────────
590
+
591
+ describe("checkInterfaceContracts", () => {
592
+ test("passes when function signatures match", () => {
593
+ const tasks = [
594
+ createTask({
595
+ id: "T01",
596
+ description: `
597
+ \`\`\`typescript
598
+ function processData(input: string): boolean
599
+ \`\`\`
600
+ `,
601
+ }),
602
+ createTask({
603
+ id: "T02",
604
+ description: `
605
+ \`\`\`typescript
606
+ function processData(input: string): boolean
607
+ \`\`\`
608
+ `,
609
+ }),
610
+ ];
611
+
612
+ const results = checkInterfaceContracts(tasks, "/tmp");
613
+ assert.deepEqual(results, []);
614
+ });
615
+
616
+ test("warns on parameter mismatch (non-blocking)", () => {
617
+ const tasks = [
618
+ createTask({
619
+ id: "T01",
620
+ description: `
621
+ \`\`\`typescript
622
+ function saveUser(name: string): void
623
+ \`\`\`
624
+ `,
625
+ }),
626
+ createTask({
627
+ id: "T02",
628
+ description: `
629
+ \`\`\`typescript
630
+ function saveUser(name: string, email: string): void
631
+ \`\`\`
632
+ `,
633
+ }),
634
+ ];
635
+
636
+ const results = checkInterfaceContracts(tasks, "/tmp");
637
+ assert.equal(results.length, 1);
638
+ assert.equal(results[0].category, "schema");
639
+ assert.equal(results[0].target, "saveUser");
640
+ assert.equal(results[0].passed, true); // Warning, not failure
641
+ assert.equal(results[0].blocking, false);
642
+ assert.ok(results[0].message.includes("different parameters"));
643
+ });
644
+
645
+ test("warns on return type mismatch (non-blocking)", () => {
646
+ const tasks = [
647
+ createTask({
648
+ id: "T01",
649
+ description: `
650
+ \`\`\`typescript
651
+ function getData(): string
652
+ \`\`\`
653
+ `,
654
+ }),
655
+ createTask({
656
+ id: "T02",
657
+ description: `
658
+ \`\`\`typescript
659
+ function getData(): number
660
+ \`\`\`
661
+ `,
662
+ }),
663
+ ];
664
+
665
+ const results = checkInterfaceContracts(tasks, "/tmp");
666
+ assert.equal(results.length, 1);
667
+ assert.ok(results[0].message.includes("different return types"));
668
+ });
669
+
670
+ test("handles export function syntax", () => {
671
+ const tasks = [
672
+ createTask({
673
+ id: "T01",
674
+ description: `
675
+ \`\`\`typescript
676
+ export function validate(data: object): boolean
677
+ \`\`\`
678
+ `,
679
+ }),
680
+ createTask({
681
+ id: "T02",
682
+ description: `
683
+ \`\`\`typescript
684
+ export function validate(data: string): boolean
685
+ \`\`\`
686
+ `,
687
+ }),
688
+ ];
689
+
690
+ const results = checkInterfaceContracts(tasks, "/tmp");
691
+ assert.equal(results.length, 1);
692
+ assert.ok(results[0].message.includes("validate"));
693
+ });
694
+
695
+ test("handles async function syntax", () => {
696
+ const tasks = [
697
+ createTask({
698
+ id: "T01",
699
+ description: `
700
+ \`\`\`typescript
701
+ export async function fetchData(): Promise<string>
702
+ \`\`\`
703
+ `,
704
+ }),
705
+ createTask({
706
+ id: "T02",
707
+ description: `
708
+ \`\`\`typescript
709
+ export async function fetchData(): Promise<number>
710
+ \`\`\`
711
+ `,
712
+ }),
713
+ ];
714
+
715
+ const results = checkInterfaceContracts(tasks, "/tmp");
716
+ assert.equal(results.length, 1);
717
+ });
718
+
719
+ test("handles const arrow function syntax", () => {
720
+ const tasks = [
721
+ createTask({
722
+ id: "T01",
723
+ description: `
724
+ \`\`\`typescript
725
+ const handler = (req: Request): Response =>
726
+ \`\`\`
727
+ `,
728
+ }),
729
+ createTask({
730
+ id: "T02",
731
+ description: `
732
+ \`\`\`typescript
733
+ const handler = (req: Request, res: Response): void =>
734
+ \`\`\`
735
+ `,
736
+ }),
737
+ ];
738
+
739
+ const results = checkInterfaceContracts(tasks, "/tmp");
740
+ // Should have 2 results: parameter mismatch AND return type mismatch
741
+ assert.equal(results.length, 2);
742
+ assert.ok(results.some((r) => r.message.includes("handler")));
743
+ assert.ok(results.some((r) => r.message.includes("parameters")));
744
+ assert.ok(results.some((r) => r.message.includes("return types")));
745
+ });
746
+
747
+ test("passes when no code blocks present", () => {
748
+ const tasks = [
749
+ createTask({
750
+ id: "T01",
751
+ description: "Just some text without code blocks",
752
+ }),
753
+ ];
754
+
755
+ const results = checkInterfaceContracts(tasks, "/tmp");
756
+ assert.deepEqual(results, []);
757
+ });
758
+
759
+ test("handles multiple mismatches for same function", () => {
760
+ const tasks = [
761
+ createTask({
762
+ id: "T01",
763
+ description: `
764
+ \`\`\`typescript
765
+ function process(a: string): string
766
+ \`\`\`
767
+ `,
768
+ }),
769
+ createTask({
770
+ id: "T02",
771
+ description: `
772
+ \`\`\`typescript
773
+ function process(a: number): number
774
+ \`\`\`
775
+ `,
776
+ }),
777
+ ];
778
+
779
+ const results = checkInterfaceContracts(tasks, "/tmp");
780
+ // Should have both parameter and return type mismatches
781
+ assert.equal(results.length, 2);
782
+ });
783
+ });
784
+
785
+ // ─── runPreExecutionChecks Integration Tests ─────────────────────────────────
786
+
787
+ describe("runPreExecutionChecks", () => {
788
+ let tempDir: string;
789
+
790
+ test("returns pass status when all checks pass", async () => {
791
+ tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
792
+ mkdirSync(tempDir, { recursive: true });
793
+ writeFileSync(join(tempDir, "existing.ts"), "// content");
794
+
795
+ try {
796
+ const tasks = [
797
+ createTask({
798
+ id: "T01",
799
+ files: ["existing.ts"],
800
+ inputs: [],
801
+ expected_output: ["output.ts"],
802
+ }),
803
+ createTask({
804
+ id: "T02",
805
+ files: ["output.ts"],
806
+ inputs: [],
807
+ expected_output: [],
808
+ }),
809
+ ];
810
+
811
+ const result = await runPreExecutionChecks(tasks, tempDir);
812
+ assert.equal(result.status, "pass");
813
+ assert.equal(result.checks.length, 0);
814
+ assert.ok(result.durationMs >= 0);
815
+ } finally {
816
+ rmSync(tempDir, { recursive: true, force: true });
817
+ }
818
+ });
819
+
820
+ test("returns fail status when blocking failure exists", async () => {
821
+ tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
822
+ mkdirSync(tempDir, { recursive: true });
823
+
824
+ try {
825
+ const tasks = [
826
+ createTask({
827
+ id: "T01",
828
+ files: ["nonexistent.ts"],
829
+ inputs: [],
830
+ expected_output: [],
831
+ }),
832
+ ];
833
+
834
+ const result = await runPreExecutionChecks(tasks, tempDir);
835
+ assert.equal(result.status, "fail");
836
+ assert.ok(result.checks.length > 0);
837
+ assert.ok(result.checks.some((c) => c.blocking === true));
838
+ } finally {
839
+ rmSync(tempDir, { recursive: true, force: true });
840
+ }
841
+ });
842
+
843
+ test("returns warn status for non-blocking issues", async () => {
844
+ tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
845
+ mkdirSync(tempDir, { recursive: true });
846
+
847
+ try {
848
+ // Create tasks with only interface contract warnings
849
+ const tasks = [
850
+ createTask({
851
+ id: "T01",
852
+ files: [],
853
+ inputs: [],
854
+ expected_output: [],
855
+ description: `
856
+ \`\`\`typescript
857
+ function foo(a: string): void
858
+ \`\`\`
859
+ `,
860
+ }),
861
+ createTask({
862
+ id: "T02",
863
+ files: [],
864
+ inputs: [],
865
+ expected_output: [],
866
+ description: `
867
+ \`\`\`typescript
868
+ function foo(a: number): void
869
+ \`\`\`
870
+ `,
871
+ }),
872
+ ];
873
+
874
+ const result = await runPreExecutionChecks(tasks, tempDir);
875
+ assert.equal(result.status, "warn");
876
+ assert.ok(result.checks.some((c) => c.blocking === false));
877
+ } finally {
878
+ rmSync(tempDir, { recursive: true, force: true });
879
+ }
880
+ });
881
+
882
+ test("combines results from all check types", async () => {
883
+ tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
884
+ mkdirSync(tempDir, { recursive: true });
885
+
886
+ try {
887
+ const tasks = [
888
+ createTask({
889
+ id: "T01",
890
+ sequence: 0,
891
+ files: ["will-be-created.ts"], // Ordering violation
892
+ inputs: ["missing.ts"], // Missing file
893
+ expected_output: [],
894
+ description: `
895
+ \`\`\`typescript
896
+ function check(a: string): void
897
+ \`\`\`
898
+ `,
899
+ }),
900
+ createTask({
901
+ id: "T02",
902
+ sequence: 1,
903
+ files: [],
904
+ inputs: [],
905
+ expected_output: ["will-be-created.ts"],
906
+ description: `
907
+ \`\`\`typescript
908
+ function check(a: number): void
909
+ \`\`\`
910
+ `,
911
+ }),
912
+ ];
913
+
914
+ const result = await runPreExecutionChecks(tasks, tempDir);
915
+ assert.equal(result.status, "fail");
916
+
917
+ // Should have multiple types of issues
918
+ const categories = new Set(result.checks.map((c) => c.category));
919
+ assert.ok(categories.has("file")); // From consistency and ordering
920
+ assert.ok(categories.has("schema")); // From interface check
921
+ } finally {
922
+ rmSync(tempDir, { recursive: true, force: true });
923
+ }
924
+ });
925
+
926
+ test("reports duration in milliseconds", async () => {
927
+ tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
928
+ mkdirSync(tempDir, { recursive: true });
929
+
930
+ try {
931
+ const tasks = [createTask({ id: "T01" })];
932
+ const result = await runPreExecutionChecks(tasks, tempDir);
933
+
934
+ assert.ok(typeof result.durationMs === "number");
935
+ assert.ok(result.durationMs >= 0);
936
+ } finally {
937
+ rmSync(tempDir, { recursive: true, force: true });
938
+ }
939
+ });
940
+
941
+ test("handles empty task array", async () => {
942
+ tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
943
+ mkdirSync(tempDir, { recursive: true });
944
+
945
+ try {
946
+ const result = await runPreExecutionChecks([], tempDir);
947
+ assert.equal(result.status, "pass");
948
+ assert.deepEqual(result.checks, []);
949
+ } finally {
950
+ rmSync(tempDir, { recursive: true, force: true });
951
+ }
952
+ });
953
+ });
954
+
955
+ // ─── PreExecutionResult Type Tests ───────────────────────────────────────────
956
+
957
+ describe("PreExecutionResult type", () => {
958
+ test("status is one of pass, warn, fail", async () => {
959
+ const tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
960
+ mkdirSync(tempDir, { recursive: true });
961
+
962
+ try {
963
+ const tasks = [createTask({ id: "T01" })];
964
+ const result = await runPreExecutionChecks(tasks, tempDir);
965
+
966
+ assert.ok(["pass", "warn", "fail"].includes(result.status));
967
+ } finally {
968
+ rmSync(tempDir, { recursive: true, force: true });
969
+ }
970
+ });
971
+
972
+ test("checks array matches PreExecutionCheckJSON schema", async () => {
973
+ const tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
974
+ mkdirSync(tempDir, { recursive: true });
975
+
976
+ try {
977
+ const tasks = [
978
+ createTask({
979
+ id: "T01",
980
+ files: ["missing.ts"],
981
+ }),
982
+ ];
983
+
984
+ const result = await runPreExecutionChecks(tasks, tempDir);
985
+
986
+ for (const check of result.checks) {
987
+ assert.ok(["package", "file", "tool", "endpoint", "schema"].includes(check.category));
988
+ assert.ok(typeof check.target === "string");
989
+ assert.ok(typeof check.passed === "boolean");
990
+ assert.ok(typeof check.message === "string");
991
+ if (check.blocking !== undefined) {
992
+ assert.ok(typeof check.blocking === "boolean");
993
+ }
994
+ }
995
+ } finally {
996
+ rmSync(tempDir, { recursive: true, force: true });
997
+ }
998
+ });
999
+ });