gsd-pi 2.63.0-dev.026d309 → 2.63.0-dev.786f0ff

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 (350) hide show
  1. package/README.md +46 -134
  2. package/dist/cli.js +48 -6
  3. package/dist/headless-query.js +11 -1
  4. package/dist/help-text.js +4 -1
  5. package/dist/onboarding.js +15 -8
  6. package/dist/resource-loader.js +18 -3
  7. package/dist/resources/extensions/cmux/index.js +21 -12
  8. package/dist/resources/extensions/gsd/auto/detect-stuck.js +27 -0
  9. package/dist/resources/extensions/gsd/auto/finalize-timeout.js +40 -0
  10. package/dist/resources/extensions/gsd/auto/loop.js +4 -0
  11. package/dist/resources/extensions/gsd/auto/phases.js +157 -22
  12. package/dist/resources/extensions/gsd/auto/session.js +12 -0
  13. package/dist/resources/extensions/gsd/auto-dashboard.js +9 -3
  14. package/dist/resources/extensions/gsd/auto-model-selection.js +32 -0
  15. package/dist/resources/extensions/gsd/auto-post-unit.js +124 -10
  16. package/dist/resources/extensions/gsd/auto-prompts.js +25 -0
  17. package/dist/resources/extensions/gsd/auto-recovery.js +15 -7
  18. package/dist/resources/extensions/gsd/auto-start.js +10 -21
  19. package/dist/resources/extensions/gsd/auto-timers.js +2 -1
  20. package/dist/resources/extensions/gsd/auto-tool-tracking.js +17 -0
  21. package/dist/resources/extensions/gsd/auto-worktree.js +13 -7
  22. package/dist/resources/extensions/gsd/auto.js +19 -2
  23. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +147 -75
  24. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +13 -0
  25. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +85 -0
  26. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +3 -0
  27. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +32 -1
  28. package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +54 -0
  29. package/dist/resources/extensions/gsd/bootstrap/system-context.js +30 -2
  30. package/dist/resources/extensions/gsd/commands-handlers.js +9 -4
  31. package/dist/resources/extensions/gsd/constants.js +42 -0
  32. package/dist/resources/extensions/gsd/db-writer.js +72 -4
  33. package/dist/resources/extensions/gsd/forensics.js +20 -4
  34. package/dist/resources/extensions/gsd/gsd-db.js +64 -17
  35. package/dist/resources/extensions/gsd/guided-flow.js +19 -0
  36. package/dist/resources/extensions/gsd/metrics.js +27 -1
  37. package/dist/resources/extensions/gsd/native-git-bridge.js +5 -3
  38. package/dist/resources/extensions/gsd/preferences-types.js +2 -0
  39. package/dist/resources/extensions/gsd/preferences.js +7 -2
  40. package/dist/resources/extensions/gsd/prompt-loader.js +7 -0
  41. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  42. package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  43. package/dist/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  44. package/dist/resources/extensions/gsd/prompts/forensics.md +2 -0
  45. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  46. package/dist/resources/extensions/gsd/prompts/system.md +4 -7
  47. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  48. package/dist/resources/extensions/gsd/roadmap-mutations.js +1 -1
  49. package/dist/resources/extensions/gsd/roadmap-slices.js +9 -5
  50. package/dist/resources/extensions/gsd/safety/content-validator.js +73 -0
  51. package/dist/resources/extensions/gsd/safety/destructive-guard.js +34 -0
  52. package/dist/resources/extensions/gsd/safety/evidence-collector.js +109 -0
  53. package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +83 -0
  54. package/dist/resources/extensions/gsd/safety/file-change-validator.js +71 -0
  55. package/dist/resources/extensions/gsd/safety/git-checkpoint.js +91 -0
  56. package/dist/resources/extensions/gsd/safety/safety-harness.js +64 -0
  57. package/dist/resources/extensions/gsd/slice-parallel-conflict.js +67 -0
  58. package/dist/resources/extensions/gsd/slice-parallel-eligibility.js +51 -0
  59. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +378 -0
  60. package/dist/resources/extensions/gsd/state.js +74 -14
  61. package/dist/resources/extensions/gsd/status-guards.js +11 -0
  62. package/dist/resources/extensions/gsd/tools/complete-milestone.js +17 -12
  63. package/dist/resources/extensions/gsd/tools/complete-slice.js +40 -26
  64. package/dist/resources/extensions/gsd/tools/complete-task.js +12 -12
  65. package/dist/resources/extensions/gsd/tools/plan-milestone.js +33 -25
  66. package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -8
  67. package/dist/resources/extensions/gsd/workflow-projections.js +21 -5
  68. package/dist/resources/extensions/gsd/worktree-manager.js +82 -29
  69. package/dist/resources/extensions/gsd/worktree-resolver.js +4 -3
  70. package/dist/resources/extensions/mcp-client/auth.js +101 -0
  71. package/dist/resources/extensions/mcp-client/index.js +10 -1
  72. package/dist/resources/extensions/ollama/index.js +28 -22
  73. package/dist/resources/extensions/ollama/model-capabilities.js +37 -34
  74. package/dist/resources/extensions/ollama/ndjson-stream.js +54 -0
  75. package/dist/resources/extensions/ollama/ollama-chat-provider.js +380 -0
  76. package/dist/resources/extensions/ollama/ollama-client.js +23 -32
  77. package/dist/resources/extensions/ollama/ollama-discovery.js +2 -7
  78. package/dist/resources/extensions/ollama/ollama-tool.js +62 -0
  79. package/dist/resources/extensions/ollama/thinking-parser.js +104 -0
  80. package/dist/update-cmd.js +4 -2
  81. package/dist/web/standalone/.next/BUILD_ID +1 -1
  82. package/dist/web/standalone/.next/app-path-routes-manifest.json +20 -20
  83. package/dist/web/standalone/.next/build-manifest.json +2 -2
  84. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  85. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  86. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  94. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  103. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  105. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  107. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  108. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  109. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  111. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  113. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  115. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  117. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  119. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  121. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  123. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  125. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  127. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  129. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  131. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  133. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  135. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  137. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  139. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  141. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  143. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  144. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  145. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  147. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  149. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  151. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  153. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  155. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  156. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  157. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
  158. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  159. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  161. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  163. package/dist/web/standalone/.next/server/app/index.html +1 -1
  164. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  165. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  166. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  167. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  168. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  169. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  170. package/dist/web/standalone/.next/server/app-paths-manifest.json +20 -20
  171. package/dist/web/standalone/.next/server/chunks/6897.js +12 -0
  172. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  173. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  174. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  175. package/package.json +1 -1
  176. package/packages/pi-agent-core/dist/agent-loop.d.ts +8 -0
  177. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  178. package/packages/pi-agent-core/dist/agent-loop.js +50 -0
  179. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  180. package/packages/pi-agent-core/src/agent-loop.test.ts +221 -5
  181. package/packages/pi-agent-core/src/agent-loop.ts +53 -0
  182. package/packages/pi-ai/dist/types.d.ts +16 -1
  183. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  184. package/packages/pi-ai/dist/types.js.map +1 -1
  185. package/packages/pi-ai/src/types.ts +18 -1
  186. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +9 -0
  187. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  188. package/packages/pi-coding-agent/dist/core/auth-storage.js +50 -1
  189. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  190. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +41 -0
  191. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  192. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +7 -0
  193. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  194. package/packages/pi-coding-agent/dist/core/extensions/loader.js +31 -4
  195. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  196. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +28 -1
  197. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -1
  198. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts +2 -0
  199. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts.map +1 -0
  200. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js +46 -0
  201. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js.map +1 -0
  202. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
  203. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  204. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  205. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -0
  206. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  207. package/packages/pi-coding-agent/dist/core/model-registry.js +12 -0
  208. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  209. package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -3
  210. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  211. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +23 -1
  212. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  213. package/packages/pi-coding-agent/dist/core/resource-loader.js +80 -56
  214. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  215. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  216. package/packages/pi-coding-agent/dist/core/sdk.js +9 -0
  217. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  218. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +53 -0
  219. package/packages/pi-coding-agent/src/core/auth-storage.ts +66 -1
  220. package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +39 -1
  221. package/packages/pi-coding-agent/src/core/extensions/loader.ts +34 -4
  222. package/packages/pi-coding-agent/src/core/extensions/provider-registration.test.ts +81 -0
  223. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
  224. package/packages/pi-coding-agent/src/core/model-registry.ts +14 -0
  225. package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -3
  226. package/packages/pi-coding-agent/src/core/resource-loader.ts +89 -56
  227. package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
  228. package/src/resources/extensions/cmux/index.ts +18 -12
  229. package/src/resources/extensions/gsd/auto/detect-stuck.ts +27 -0
  230. package/src/resources/extensions/gsd/auto/finalize-timeout.ts +46 -0
  231. package/src/resources/extensions/gsd/auto/loop.ts +5 -0
  232. package/src/resources/extensions/gsd/auto/phases.ts +194 -33
  233. package/src/resources/extensions/gsd/auto/session.ts +14 -0
  234. package/src/resources/extensions/gsd/auto-dashboard.ts +11 -3
  235. package/src/resources/extensions/gsd/auto-model-selection.ts +36 -0
  236. package/src/resources/extensions/gsd/auto-post-unit.ts +141 -12
  237. package/src/resources/extensions/gsd/auto-prompts.ts +21 -0
  238. package/src/resources/extensions/gsd/auto-recovery.ts +9 -8
  239. package/src/resources/extensions/gsd/auto-start.ts +11 -20
  240. package/src/resources/extensions/gsd/auto-timers.ts +2 -1
  241. package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
  242. package/src/resources/extensions/gsd/auto-worktree.ts +14 -6
  243. package/src/resources/extensions/gsd/auto.ts +22 -1
  244. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +160 -88
  245. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +15 -0
  246. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +98 -0
  247. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -0
  248. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +36 -1
  249. package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
  250. package/src/resources/extensions/gsd/bootstrap/system-context.ts +31 -2
  251. package/src/resources/extensions/gsd/commands-handlers.ts +10 -4
  252. package/src/resources/extensions/gsd/constants.ts +44 -0
  253. package/src/resources/extensions/gsd/db-writer.ts +78 -4
  254. package/src/resources/extensions/gsd/forensics.ts +21 -5
  255. package/src/resources/extensions/gsd/gsd-db.ts +64 -17
  256. package/src/resources/extensions/gsd/guided-flow.ts +22 -0
  257. package/src/resources/extensions/gsd/metrics.ts +28 -1
  258. package/src/resources/extensions/gsd/native-git-bridge.ts +5 -3
  259. package/src/resources/extensions/gsd/preferences-types.ts +16 -0
  260. package/src/resources/extensions/gsd/preferences.ts +9 -2
  261. package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
  262. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  263. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  264. package/src/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  265. package/src/resources/extensions/gsd/prompts/forensics.md +2 -0
  266. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  267. package/src/resources/extensions/gsd/prompts/system.md +4 -7
  268. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  269. package/src/resources/extensions/gsd/roadmap-mutations.ts +1 -1
  270. package/src/resources/extensions/gsd/roadmap-slices.ts +10 -5
  271. package/src/resources/extensions/gsd/safety/content-validator.ts +98 -0
  272. package/src/resources/extensions/gsd/safety/destructive-guard.ts +49 -0
  273. package/src/resources/extensions/gsd/safety/evidence-collector.ts +151 -0
  274. package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +120 -0
  275. package/src/resources/extensions/gsd/safety/file-change-validator.ts +108 -0
  276. package/src/resources/extensions/gsd/safety/git-checkpoint.ts +106 -0
  277. package/src/resources/extensions/gsd/safety/safety-harness.ts +105 -0
  278. package/src/resources/extensions/gsd/slice-parallel-conflict.ts +86 -0
  279. package/src/resources/extensions/gsd/slice-parallel-eligibility.ts +73 -0
  280. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +477 -0
  281. package/src/resources/extensions/gsd/state.ts +67 -12
  282. package/src/resources/extensions/gsd/status-guards.ts +13 -0
  283. package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +288 -0
  284. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +34 -13
  285. package/src/resources/extensions/gsd/tests/cmux.test.ts +58 -0
  286. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +51 -0
  287. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
  288. package/src/resources/extensions/gsd/tests/complete-slice-string-coercion.test.ts +211 -0
  289. package/src/resources/extensions/gsd/tests/complete-task.test.ts +39 -0
  290. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +107 -0
  291. package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +109 -0
  292. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +13 -9
  293. package/src/resources/extensions/gsd/tests/db-writer.test.ts +134 -0
  294. package/src/resources/extensions/gsd/tests/deferred-slice-dispatch.test.ts +203 -0
  295. package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +130 -0
  296. package/src/resources/extensions/gsd/tests/doctor-fix-flag.test.ts +92 -0
  297. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +116 -0
  298. package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +50 -0
  299. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +103 -0
  300. package/src/resources/extensions/gsd/tests/git-checkpoint.test.ts +94 -0
  301. package/src/resources/extensions/gsd/tests/insert-slice-no-wipe.test.ts +88 -0
  302. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +27 -7
  303. package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +34 -0
  304. package/src/resources/extensions/gsd/tests/metrics.test.ts +116 -1
  305. package/src/resources/extensions/gsd/tests/milestone-status-tool.test.ts +201 -0
  306. package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +2 -1
  307. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +82 -18
  308. package/src/resources/extensions/gsd/tests/preferences.test.ts +10 -0
  309. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +25 -0
  310. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +69 -0
  311. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +30 -0
  312. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +50 -0
  313. package/src/resources/extensions/gsd/tests/slice-parallel-conflict.test.ts +92 -0
  314. package/src/resources/extensions/gsd/tests/slice-parallel-eligibility.test.ts +95 -0
  315. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +83 -0
  316. package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +42 -0
  317. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +103 -0
  318. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +349 -0
  319. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -2
  320. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +73 -0
  321. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +34 -0
  322. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +1 -1
  323. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +148 -0
  324. package/src/resources/extensions/gsd/tools/complete-milestone.ts +34 -20
  325. package/src/resources/extensions/gsd/tools/complete-slice.ts +41 -26
  326. package/src/resources/extensions/gsd/tools/complete-task.ts +12 -12
  327. package/src/resources/extensions/gsd/tools/plan-milestone.ts +55 -30
  328. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -8
  329. package/src/resources/extensions/gsd/types.ts +44 -22
  330. package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
  331. package/src/resources/extensions/gsd/workflow-projections.ts +23 -5
  332. package/src/resources/extensions/gsd/worktree-manager.ts +76 -28
  333. package/src/resources/extensions/gsd/worktree-resolver.ts +4 -3
  334. package/src/resources/extensions/mcp-client/auth.ts +149 -0
  335. package/src/resources/extensions/mcp-client/index.ts +16 -1
  336. package/src/resources/extensions/ollama/index.ts +26 -25
  337. package/src/resources/extensions/ollama/model-capabilities.ts +41 -34
  338. package/src/resources/extensions/ollama/ndjson-stream.ts +63 -0
  339. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +20 -0
  340. package/src/resources/extensions/ollama/ollama-chat-provider.ts +459 -0
  341. package/src/resources/extensions/ollama/ollama-client.ts +30 -30
  342. package/src/resources/extensions/ollama/ollama-discovery.ts +5 -8
  343. package/src/resources/extensions/ollama/ollama-tool.ts +69 -0
  344. package/src/resources/extensions/ollama/tests/ollama-chat-provider-stream.test.ts +82 -0
  345. package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -27
  346. package/src/resources/extensions/ollama/thinking-parser.ts +116 -0
  347. package/src/resources/extensions/ollama/types.ts +23 -0
  348. package/dist/web/standalone/.next/server/chunks/2229.js +0 -12
  349. /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → SDB1T-4NqkMjYirjjqQhr}/_buildManifest.js +0 -0
  350. /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → SDB1T-4NqkMjYirjjqQhr}/_ssgManifest.js +0 -0
@@ -24,13 +24,9 @@ Leave the project in a state where the next agent can immediately understand wha
24
24
 
25
25
  ## Skills
26
26
 
27
- GSD ships with bundled skills. Load the relevant skill file with the `read` tool before starting work when the task matches.
27
+ GSD ships with bundled skills. Load the relevant skill file with the `read` tool before starting work when the task matches. Use bare skill names — GSD resolves them to the correct path automatically.
28
28
 
29
- | Trigger | Skill to load |
30
- |---|---|
31
- | Frontend UI - web components, pages, landing pages, dashboards, React/HTML/CSS, styling | `~/.gsd/agent/skills/frontend-design/SKILL.md` |
32
- | macOS or iOS apps - SwiftUI, Xcode, App Store | `~/.gsd/agent/skills/swiftui/SKILL.md` |
33
- | Debugging - complex bugs, failing tests, root-cause investigation after standard approaches fail | `~/.gsd/agent/skills/debug-like-expert/SKILL.md` |
29
+ {{bundledSkillsTable}}
34
30
 
35
31
  ## Hard Rules
36
32
 
@@ -119,7 +115,7 @@ In all modes, slices commit sequentially on the active branch; there are no per-
119
115
  ### Artifact Templates
120
116
 
121
117
  Templates showing the expected format for each artifact type are in:
122
- `~/.gsd/agent/extensions/gsd/templates/`
118
+ `{{templatesDir}}`
123
119
 
124
120
  **Always read the relevant template before writing an artifact** to match the expected structure exactly. The parsers that read these files depend on specific formatting:
125
121
 
@@ -175,6 +171,7 @@ Templates showing the expected format for each artifact type are in:
175
171
  - Never guess at library APIs from training data — use `get_library_docs`.
176
172
  - Never ask the user to run a command, set a variable, or check something you can check yourself.
177
173
  - Never await stale async jobs after editing source — `cancel_job` them first, then re-run.
174
+ - Never query `.gsd/gsd.db` directly via `sqlite3`, `better-sqlite3`, or `node -e require('better-sqlite3')` — the database uses a single-writer WAL connection managed by the engine. Direct access causes reader/writer conflicts and bypasses validation logic. Use `gsd_milestone_status`, `gsd_journal_query`, or other `gsd_*` tools exclusively for all DB reads and writes.
178
175
 
179
176
  ### Ask vs infer
180
177
 
@@ -38,6 +38,8 @@ All relevant context has been preloaded below — the roadmap, all slice summari
38
38
 
39
39
  **Persist validation results through `gsd_validate_milestone`.** Call it with: `milestoneId`, `verdict`, `remediationRound`, `successCriteriaChecklist`, `sliceDeliveryAudit`, `crossSliceIntegration`, `requirementCoverage`, `verificationClasses` (when non-empty), `verdictRationale`, and `remediationPlan` (if verdict is `needs-remediation`). The tool writes the validation to the DB and renders VALIDATION.md to disk.
40
40
 
41
+ **DB access safety:** Do NOT query `.gsd/gsd.db` directly via `sqlite3` or `node -e require('better-sqlite3')` — the engine owns the WAL connection. Use `gsd_milestone_status` to read milestone and slice state. All data you need is already inlined in the context above or accessible via the `gsd_*` tools. Direct DB access corrupts the WAL and bypasses tool-level validation.
42
+
41
43
  If verdict is `needs-remediation`:
42
44
  - After calling `gsd_validate_milestone`, use `gsd_reassess_roadmap` to add remediation slices. Pass `milestoneId`, a synthetic `completedSliceId` (e.g. "VALIDATION"), `verdict: "roadmap-adjusted"`, `assessment` text, and `sliceChanges` with the new slices in the `added` array. The tool persists the changes to the DB and re-renders ROADMAP.md.
43
45
  - These remediation slices will be planned and executed before validation re-runs.
@@ -39,7 +39,7 @@ export function markSliceDoneInRoadmap(basePath: string, mid: string, sid: strin
39
39
  new RegExp(`^(#{1,4}\\s+(?:\\*{0,2})(?:Slice\\s+)?${sid}\\*{0,2}[:\\s.\\u2014\\u2013-]+\\s*)(.+)`, "m"),
40
40
  (match, prefix, title) => {
41
41
  // Already marked done — no-op
42
- if (/^\u2713/.test(title) || /\(Complete\)\s*$/i.test(title)) return match;
42
+ if (/^[\u2713\u2705]/.test(title) || /[\u2705]\s*$/.test(title) || /\(Complete\)\s*$/i.test(title)) return match;
43
43
  return `${prefix}\u2713 ${title}`;
44
44
  },
45
45
  );
@@ -82,7 +82,7 @@ function parseTableSlices(section: string): RoadmapSliceEntry[] {
82
82
  const fullRow = line.toLowerCase();
83
83
  const done =
84
84
  /\[x\]/i.test(line) ||
85
- /[✅☑✓]/.test(line) ||
85
+ /[✅☑✓✔]/.test(line) ||
86
86
  /\bdone\b/.test(fullRow) ||
87
87
  /\bcomplete(?:d)?\b/.test(fullRow);
88
88
 
@@ -222,11 +222,11 @@ function parseProseSliceHeaders(content: string): RoadmapSliceEntry[] {
222
222
  // numeric prefixes (e.g., "1.", "(1)"), bracketed IDs (e.g., "[S01]"),
223
223
  // optional checkmark completion marker, and optional leading indentation.
224
224
  // Separator after the ID is flexible: colon, dash, em/en dash, dot, or just whitespace.
225
- const headerPattern = /^\s*#{1,4}\s+\*{0,2}(?:\u2713\s+)?(?:\d+[.)]\s+)?(?:\(\d+\)\s+)?(?:Slice\s+)?\[?(S\d+)\]?\*{0,2}[:\s.\u2014\u2013-]*\s*(.+)/gm;
225
+ const headerPattern = /^\s*#{1,4}\s+\*{0,2}(?:[\u2713\u2705]\s+)?(?:\d+[.)]\s+)?(?:\(\d+\)\s+)?(?:Slice\s+)?\[?(S\d+)\]?\*{0,2}[:\s.\u2014\u2013-]*\s*(.+)/gm;
226
226
  let match: RegExpExecArray | null;
227
227
 
228
228
  // Check for checkmark before the slice ID (e.g., "## checkmark S01: Title")
229
- const prefixCheckPattern = /^\s*#{1,4}\s+\*{0,2}\u2713\s+/;
229
+ const prefixCheckPattern = /^\s*#{1,4}\s+\*{0,2}[\u2713\u2705]\s+/;
230
230
 
231
231
  while ((match = headerPattern.exec(content)) !== null) {
232
232
  const id = match[1]!;
@@ -240,9 +240,14 @@ function parseProseSliceHeaders(content: string): RoadmapSliceEntry[] {
240
240
  const line = match[0];
241
241
  let done = prefixCheckPattern.test(line);
242
242
 
243
- if (!done && title.startsWith("\u2713")) {
243
+ if (!done && /^[\u2713\u2705]/.test(title)) {
244
244
  done = true;
245
- title = title.replace(/^\u2713\s*/, "");
245
+ title = title.replace(/^[\u2713\u2705]\s*/, "");
246
+ }
247
+
248
+ if (!done && /[\u2705]\s*$/.test(title)) {
249
+ done = true;
250
+ title = title.replace(/\s*[\u2705]\s*$/, "");
246
251
  }
247
252
 
248
253
  if (!done && /\(Complete\)\s*$/i.test(title)) {
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Lightweight content validator for auto-mode safety harness.
3
+ * Validates that high-value unit outputs contain minimum expected content.
4
+ *
5
+ * Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
6
+ */
7
+
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { logWarning } from "../workflow-logger.js";
10
+
11
+ // ─── Types ──────────────────────────────────────────────────────────────────
12
+
13
+ export interface ContentViolation {
14
+ severity: "warning";
15
+ reason: string;
16
+ }
17
+
18
+ // ─── Public API ─────────────────────────────────────────────────────────────
19
+
20
+ /**
21
+ * Validate content quality for a completed unit.
22
+ * Returns an array of violations. Empty array = content looks acceptable.
23
+ *
24
+ * @param unitType - The type of unit that completed (e.g. "plan-slice")
25
+ * @param artifactPath - Absolute path to the primary artifact file
26
+ */
27
+ export function validateContent(
28
+ unitType: string,
29
+ artifactPath: string | null,
30
+ ): ContentViolation[] {
31
+ if (!artifactPath || !existsSync(artifactPath)) return [];
32
+
33
+ const validator = VALIDATORS[unitType];
34
+ if (!validator) return [];
35
+
36
+ try {
37
+ const content = readFileSync(artifactPath, "utf-8");
38
+ return validator(content);
39
+ } catch (e) {
40
+ logWarning("safety", `content validation read failed: ${(e as Error).message}`);
41
+ return [];
42
+ }
43
+ }
44
+
45
+ // ─── Validators ─────────────────────────────────────────────────────────────
46
+
47
+ type ContentValidatorFn = (content: string) => ContentViolation[];
48
+
49
+ const VALIDATORS: Record<string, ContentValidatorFn> = {
50
+ "plan-slice": validatePlanSlice,
51
+ "plan-milestone": validatePlanMilestone,
52
+ };
53
+
54
+ function validatePlanSlice(content: string): ContentViolation[] {
55
+ const violations: ContentViolation[] = [];
56
+
57
+ // Must have at least 2 task entries (checkbox pattern)
58
+ const taskCount = (content.match(/- \[[ x]\] \*\*T\d+/g) || []).length;
59
+ if (taskCount < 2) {
60
+ violations.push({
61
+ severity: "warning",
62
+ reason: `Slice plan has only ${taskCount} task(s) — expected at least 2`,
63
+ });
64
+ }
65
+
66
+ // Should have a Files Likely Touched section
67
+ if (!content.includes("## Files Likely Touched") && !content.includes("## Files")) {
68
+ violations.push({
69
+ severity: "warning",
70
+ reason: "Slice plan missing 'Files Likely Touched' section",
71
+ });
72
+ }
73
+
74
+ // Should have a verification section
75
+ if (!content.includes("Verify") && !content.includes("verify")) {
76
+ violations.push({
77
+ severity: "warning",
78
+ reason: "Slice plan has no verification instructions",
79
+ });
80
+ }
81
+
82
+ return violations;
83
+ }
84
+
85
+ function validatePlanMilestone(content: string): ContentViolation[] {
86
+ const violations: ContentViolation[] = [];
87
+
88
+ // Must have at least 1 slice entry
89
+ const sliceCount = (content.match(/##\s+S\d+/g) || []).length;
90
+ if (sliceCount < 1) {
91
+ violations.push({
92
+ severity: "warning",
93
+ reason: `Milestone roadmap has ${sliceCount} slice(s) — expected at least 1`,
94
+ });
95
+ }
96
+
97
+ return violations;
98
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Destructive command classifier for auto-mode safety harness.
3
+ * Classifies bash commands and warns on potentially destructive operations.
4
+ * Does NOT block — only classifies for logging/notification.
5
+ *
6
+ * Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
7
+ */
8
+
9
+ // ─── Pattern Definitions ────────────────────────────────────────────────────
10
+
11
+ interface DestructivePattern {
12
+ pattern: RegExp;
13
+ label: string;
14
+ }
15
+
16
+ const DESTRUCTIVE_PATTERNS: readonly DestructivePattern[] = [
17
+ { pattern: /\brm\s+(-[^\s]*[rfRF][^\s]*\s+|.*\s+-[^\s]*[rfRF])/, label: "recursive delete" },
18
+ { pattern: /\bgit\s+push\s+.*--force/, label: "force push" },
19
+ { pattern: /\bgit\s+push\s+-f\b/, label: "force push" },
20
+ { pattern: /\bgit\s+reset\s+--hard/, label: "hard reset" },
21
+ { pattern: /\bgit\s+clean\s+-[^\s]*[fdxFDX]/, label: "git clean" },
22
+ { pattern: /\bgit\s+checkout\s+--\s+\./, label: "discard all changes" },
23
+ { pattern: /\bdrop\s+(database|table|index)\b/i, label: "SQL drop" },
24
+ { pattern: /\btruncate\s+table\b/i, label: "SQL truncate" },
25
+ { pattern: /\bchmod\s+777\b/, label: "world-writable permissions" },
26
+ { pattern: /\bcurl\s.*\|\s*(bash|sh|zsh)\b/, label: "pipe to shell" },
27
+ ];
28
+
29
+ // ─── Public API ─────────────────────────────────────────────────────────────
30
+
31
+ export interface CommandClassification {
32
+ destructive: boolean;
33
+ labels: string[];
34
+ }
35
+
36
+ /**
37
+ * Classify a bash command for destructive operations.
38
+ * Returns the list of matched destructive pattern labels.
39
+ */
40
+ export function classifyCommand(command: string): CommandClassification {
41
+ const labels: string[] = [];
42
+ for (const { pattern, label } of DESTRUCTIVE_PATTERNS) {
43
+ if (pattern.test(command)) {
44
+ // Deduplicate labels (e.g., two force-push patterns)
45
+ if (!labels.includes(label)) labels.push(label);
46
+ }
47
+ }
48
+ return { destructive: labels.length > 0, labels };
49
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Real-time tool call evidence collector for auto-mode safety harness.
3
+ * Tracks every bash command, file write, and file edit during a unit execution.
4
+ * Evidence is compared against LLM completion claims in evidence-cross-ref.ts.
5
+ *
6
+ * Follows the same module-level Map pattern as auto-tool-tracking.ts.
7
+ * Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
8
+ */
9
+
10
+ // ─── Types ──────────────────────────────────────────────────────────────────
11
+
12
+ export interface BashEvidence {
13
+ kind: "bash";
14
+ toolCallId: string;
15
+ command: string;
16
+ exitCode: number;
17
+ outputSnippet: string;
18
+ timestamp: number;
19
+ }
20
+
21
+ export interface FileWriteEvidence {
22
+ kind: "write";
23
+ toolCallId: string;
24
+ path: string;
25
+ timestamp: number;
26
+ }
27
+
28
+ export interface FileEditEvidence {
29
+ kind: "edit";
30
+ toolCallId: string;
31
+ path: string;
32
+ timestamp: number;
33
+ }
34
+
35
+ export type EvidenceEntry = BashEvidence | FileWriteEvidence | FileEditEvidence;
36
+
37
+ // ─── Module State ───────────────────────────────────────────────────────────
38
+
39
+ let unitEvidence: EvidenceEntry[] = [];
40
+
41
+ // ─── Public API ─────────────────────────────────────────────────────────────
42
+
43
+ /** Reset all evidence for a new unit. Call at unit start. */
44
+ export function resetEvidence(): void {
45
+ unitEvidence = [];
46
+ }
47
+
48
+ /** Get a read-only view of all evidence collected for the current unit. */
49
+ export function getEvidence(): readonly EvidenceEntry[] {
50
+ return unitEvidence;
51
+ }
52
+
53
+ /** Get only bash evidence entries. */
54
+ export function getBashEvidence(): readonly BashEvidence[] {
55
+ return unitEvidence.filter((e): e is BashEvidence => e.kind === "bash");
56
+ }
57
+
58
+ /** Get all file paths touched (write + edit). */
59
+ export function getFilePaths(): string[] {
60
+ return unitEvidence
61
+ .filter((e): e is FileWriteEvidence | FileEditEvidence => e.kind === "write" || e.kind === "edit")
62
+ .map(e => e.path);
63
+ }
64
+
65
+ // ─── Recording (called from register-hooks.ts) ─────────────────────────────
66
+
67
+ /**
68
+ * Record a tool call at dispatch time (before execution).
69
+ * Exit codes and output are filled in by recordToolResult after execution.
70
+ */
71
+ export function recordToolCall(toolName: string, input: Record<string, unknown>): void {
72
+ if (toolName === "bash" || toolName === "Bash") {
73
+ unitEvidence.push({
74
+ kind: "bash",
75
+ toolCallId: "",
76
+ command: String(input.command ?? ""),
77
+ exitCode: -1,
78
+ outputSnippet: "",
79
+ timestamp: Date.now(),
80
+ });
81
+ } else if (toolName === "write" || toolName === "Write") {
82
+ unitEvidence.push({
83
+ kind: "write",
84
+ toolCallId: "",
85
+ path: String(input.file_path ?? input.path ?? ""),
86
+ timestamp: Date.now(),
87
+ });
88
+ } else if (toolName === "edit" || toolName === "Edit") {
89
+ unitEvidence.push({
90
+ kind: "edit",
91
+ toolCallId: "",
92
+ path: String(input.file_path ?? input.path ?? ""),
93
+ timestamp: Date.now(),
94
+ });
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Record a tool execution result. Matches the most recent unresolved entry
100
+ * of the same kind and fills in the toolCallId, exit code, and output.
101
+ */
102
+ export function recordToolResult(
103
+ toolCallId: string,
104
+ toolName: string,
105
+ result: unknown,
106
+ isError: boolean,
107
+ ): void {
108
+ const normalizedName = toolName.toLowerCase();
109
+
110
+ if (normalizedName === "bash") {
111
+ const entry = findLastUnresolved("bash") as BashEvidence | undefined;
112
+ if (entry) {
113
+ entry.toolCallId = toolCallId;
114
+ const text = extractResultText(result);
115
+ entry.outputSnippet = text.slice(0, 500);
116
+ const exitMatch = text.match(/Command exited with code (\d+)/);
117
+ entry.exitCode = exitMatch ? Number(exitMatch[1]) : (isError ? 1 : 0);
118
+ }
119
+ } else if (normalizedName === "write" || normalizedName === "edit") {
120
+ const entry = findLastUnresolved(normalizedName as "write" | "edit");
121
+ if (entry) {
122
+ entry.toolCallId = toolCallId;
123
+ }
124
+ }
125
+ }
126
+
127
+ // ─── Internals ──────────────────────────────────────────────────────────────
128
+
129
+ function findLastUnresolved(kind: string): EvidenceEntry | undefined {
130
+ for (let i = unitEvidence.length - 1; i >= 0; i--) {
131
+ if (unitEvidence[i].kind === kind && unitEvidence[i].toolCallId === "") {
132
+ return unitEvidence[i];
133
+ }
134
+ }
135
+ return undefined;
136
+ }
137
+
138
+ function extractResultText(result: unknown): string {
139
+ if (typeof result === "string") return result;
140
+ if (result && typeof result === "object") {
141
+ const r = result as Record<string, unknown>;
142
+ if (Array.isArray(r.content)) {
143
+ const textBlock = r.content.find(
144
+ (c: unknown) => typeof c === "object" && c !== null && (c as Record<string, unknown>).type === "text",
145
+ ) as Record<string, unknown> | undefined;
146
+ if (textBlock && typeof textBlock.text === "string") return textBlock.text;
147
+ }
148
+ if (typeof r.text === "string") return r.text;
149
+ }
150
+ return String(result ?? "");
151
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Evidence cross-reference for auto-mode safety harness.
3
+ * Compares the LLM's claimed verification evidence (command + exitCode)
4
+ * against actual bash tool calls recorded by the evidence collector.
5
+ *
6
+ * Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
7
+ */
8
+
9
+ import type { BashEvidence, EvidenceEntry } from "./evidence-collector.js";
10
+
11
+ // ─── Types ──────────────────────────────────────────────────────────────────
12
+
13
+ export interface ClaimedEvidence {
14
+ command: string;
15
+ exitCode: number;
16
+ verdict: string;
17
+ }
18
+
19
+ export interface EvidenceMismatch {
20
+ severity: "warning" | "error";
21
+ claimed: ClaimedEvidence;
22
+ actual: BashEvidence | null;
23
+ reason: string;
24
+ }
25
+
26
+ // ─── Public API ─────────────────────────────────────────────────────────────
27
+
28
+ /**
29
+ * Cross-reference claimed verification evidence against actual bash tool calls.
30
+ *
31
+ * Returns an array of mismatches. Empty array = all claims verified.
32
+ * Skips entries that were coerced from strings (already flagged by db-tools.ts).
33
+ */
34
+ export function crossReferenceEvidence(
35
+ claimedEvidence: readonly ClaimedEvidence[],
36
+ actualEvidence: readonly EvidenceEntry[],
37
+ ): EvidenceMismatch[] {
38
+ const bashCalls = actualEvidence.filter(
39
+ (e): e is BashEvidence => e.kind === "bash",
40
+ );
41
+ const mismatches: EvidenceMismatch[] = [];
42
+
43
+ for (const claimed of claimedEvidence) {
44
+ // Skip coerced entries — they're already flagged with exitCode: -1
45
+ // and verdict: "unknown (coerced from string)" by db-tools.ts
46
+ if (claimed.verdict?.includes("coerced from string")) continue;
47
+ if (claimed.exitCode === -1) continue;
48
+
49
+ // Skip entries with empty or generic commands
50
+ if (!claimed.command || claimed.command.length < 3) continue;
51
+
52
+ // Find matching bash call by command substring match
53
+ const match = findBestMatch(claimed.command, bashCalls);
54
+
55
+ if (!match) {
56
+ mismatches.push({
57
+ severity: "warning",
58
+ claimed,
59
+ actual: null,
60
+ reason: `No bash tool call found matching "${claimed.command.slice(0, 80)}"`,
61
+ });
62
+ continue;
63
+ }
64
+
65
+ // Exit code mismatch: LLM claims success but actual command failed
66
+ if (claimed.exitCode === 0 && match.exitCode !== 0) {
67
+ mismatches.push({
68
+ severity: "error",
69
+ claimed,
70
+ actual: match,
71
+ reason: `Claimed exitCode=0 but actual exitCode=${match.exitCode}`,
72
+ });
73
+ }
74
+ }
75
+
76
+ return mismatches;
77
+ }
78
+
79
+ // ─── Internals ──────────────────────────────────────────────────────────────
80
+
81
+ /**
82
+ * Find the best matching bash evidence entry for a claimed command.
83
+ * Uses substring matching — the claimed command may be a shortened version
84
+ * of the actual command, or vice versa.
85
+ */
86
+ function findBestMatch(
87
+ claimedCommand: string,
88
+ bashCalls: readonly BashEvidence[],
89
+ ): BashEvidence | null {
90
+ const normalized = claimedCommand.trim();
91
+
92
+ // Exact match first
93
+ const exact = bashCalls.find(b => b.command.trim() === normalized);
94
+ if (exact) return exact;
95
+
96
+ // Substring match: claimed is contained in actual or actual in claimed
97
+ const substring = bashCalls.find(
98
+ b => b.command.includes(normalized) || normalized.includes(b.command),
99
+ );
100
+ if (substring) return substring;
101
+
102
+ // Token match: split on whitespace and check significant overlap
103
+ const claimedTokens = normalized.split(/\s+/).filter(t => t.length > 2);
104
+ if (claimedTokens.length === 0) return null;
105
+
106
+ let bestMatch: BashEvidence | null = null;
107
+ let bestScore = 0;
108
+
109
+ for (const call of bashCalls) {
110
+ const callTokens = new Set(call.command.split(/\s+/));
111
+ const matchCount = claimedTokens.filter(t => callTokens.has(t)).length;
112
+ const score = matchCount / claimedTokens.length;
113
+ if (score > bestScore && score >= 0.5) {
114
+ bestScore = score;
115
+ bestMatch = call;
116
+ }
117
+ }
118
+
119
+ return bestMatch;
120
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Post-unit file change validator for auto-mode safety harness.
3
+ * Compares actual git diff against the task plan's expected output files.
4
+ *
5
+ * Uses tasks.expected_output (DB column, populated from per-task ## Expected Output)
6
+ * and tasks.files (from slice PLAN.md - Files: subline) as the expected set.
7
+ * Compares against git diff HEAD~1 --name-only after auto-commit.
8
+ *
9
+ * Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
10
+ */
11
+
12
+ import { execFileSync } from "node:child_process";
13
+ import { logWarning } from "../workflow-logger.js";
14
+
15
+ // ─── Types ──────────────────────────────────────────────────────────────────
16
+
17
+ export interface FileViolation {
18
+ severity: "info" | "warning";
19
+ file: string;
20
+ reason: string;
21
+ }
22
+
23
+ export interface FileChangeAudit {
24
+ expectedFiles: string[];
25
+ actualFiles: string[];
26
+ unexpectedFiles: string[];
27
+ missingFiles: string[];
28
+ violations: FileViolation[];
29
+ }
30
+
31
+ // ─── Public API ─────────────────────────────────────────────────────────────
32
+
33
+ /**
34
+ * Validate file changes after auto-commit for an execute-task unit.
35
+ * Returns null if task data is unavailable or DB is not loaded.
36
+ *
37
+ * @param basePath - Working directory (worktree or project root)
38
+ * @param expectedOutput - JSON array from tasks.expected_output DB column
39
+ * @param plannedFiles - JSON array from tasks.files DB column
40
+ */
41
+ export function validateFileChanges(
42
+ basePath: string,
43
+ expectedOutput: string[],
44
+ plannedFiles: string[],
45
+ ): FileChangeAudit | null {
46
+ const allExpected = new Set([...expectedOutput, ...plannedFiles]);
47
+
48
+ // If no expected files were planned, skip validation
49
+ if (allExpected.size === 0) return null;
50
+
51
+ // Get actual changed files from last commit
52
+ const actualFiles = getChangedFilesFromLastCommit(basePath);
53
+ if (!actualFiles) return null;
54
+
55
+ // Filter out .gsd/ internal files — only validate project source files
56
+ const projectFiles = actualFiles.filter(f => !f.startsWith(".gsd/") && !f.startsWith(".gsd\\"));
57
+
58
+ // Normalize expected paths (strip leading ./ or /)
59
+ const normalizedExpected = new Set(
60
+ [...allExpected].map(f => f.replace(/^\.\//, "").replace(/^\//, "")),
61
+ );
62
+
63
+ // Compute symmetric difference
64
+ const unexpectedFiles = projectFiles.filter(f => !normalizedExpected.has(f));
65
+ const missingFiles = [...normalizedExpected].filter(f => !projectFiles.includes(f));
66
+
67
+ const violations: FileViolation[] = [];
68
+
69
+ for (const f of unexpectedFiles) {
70
+ violations.push({
71
+ severity: "warning",
72
+ file: f,
73
+ reason: "Modified but not in task plan's expected output",
74
+ });
75
+ }
76
+
77
+ for (const f of missingFiles) {
78
+ violations.push({
79
+ severity: "info",
80
+ file: f,
81
+ reason: "Listed in task plan but not modified",
82
+ });
83
+ }
84
+
85
+ return {
86
+ expectedFiles: [...normalizedExpected],
87
+ actualFiles: projectFiles,
88
+ unexpectedFiles,
89
+ missingFiles,
90
+ violations,
91
+ };
92
+ }
93
+
94
+ // ─── Internals ──────────────────────────────────────────────────────────────
95
+
96
+ function getChangedFilesFromLastCommit(basePath: string): string[] | null {
97
+ try {
98
+ const result = execFileSync(
99
+ "git",
100
+ ["diff", "--name-only", "HEAD~1", "HEAD"],
101
+ { cwd: basePath, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" },
102
+ ).trim();
103
+ return result ? result.split("\n").filter(Boolean) : [];
104
+ } catch (e) {
105
+ logWarning("safety", `git diff failed in file-change-validator: ${(e as Error).message}`);
106
+ return null;
107
+ }
108
+ }