gsd-pi 2.28.0-dev.e19bf89 → 2.29.0-dev.2ccf3fb

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 (256) hide show
  1. package/README.md +24 -17
  2. package/dist/cli.js +15 -9
  3. package/dist/resource-loader.js +80 -8
  4. package/dist/resources/extensions/bg-shell/process-manager.ts +13 -0
  5. package/dist/resources/extensions/gsd/auto-dashboard.ts +186 -65
  6. package/dist/resources/extensions/gsd/auto-post-unit.ts +14 -6
  7. package/dist/resources/extensions/gsd/auto-recovery.ts +33 -23
  8. package/dist/resources/extensions/gsd/auto-start.ts +25 -10
  9. package/dist/resources/extensions/gsd/auto-verification.ts +41 -7
  10. package/dist/resources/extensions/gsd/auto-worktree-sync.ts +21 -6
  11. package/dist/resources/extensions/gsd/auto.ts +67 -22
  12. package/dist/resources/extensions/gsd/commands-handlers.ts +3 -11
  13. package/dist/resources/extensions/gsd/commands-logs.ts +536 -0
  14. package/dist/resources/extensions/gsd/commands-prefs-wizard.ts +90 -47
  15. package/dist/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
  16. package/dist/resources/extensions/gsd/commands.ts +75 -29
  17. package/dist/resources/extensions/gsd/dashboard-overlay.ts +2 -1
  18. package/dist/resources/extensions/gsd/doctor-types.ts +13 -0
  19. package/dist/resources/extensions/gsd/doctor.ts +2 -6
  20. package/dist/resources/extensions/gsd/export.ts +28 -2
  21. package/dist/resources/extensions/gsd/gsd-db.ts +19 -0
  22. package/dist/resources/extensions/gsd/index.ts +2 -1
  23. package/dist/resources/extensions/gsd/json-persistence.ts +67 -0
  24. package/dist/resources/extensions/gsd/metrics.ts +17 -31
  25. package/dist/resources/extensions/gsd/paths.ts +0 -8
  26. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  27. package/dist/resources/extensions/gsd/prompts/workflow-start.md +28 -0
  28. package/dist/resources/extensions/gsd/queue-order.ts +10 -11
  29. package/dist/resources/extensions/gsd/routing-history.ts +13 -17
  30. package/dist/resources/extensions/gsd/session-lock.ts +284 -0
  31. package/dist/resources/extensions/gsd/session-status-io.ts +23 -41
  32. package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
  33. package/dist/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
  34. package/dist/resources/extensions/gsd/tests/commands-logs.test.ts +241 -0
  35. package/dist/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
  36. package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +1 -1
  37. package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
  38. package/dist/resources/extensions/gsd/tests/session-lock.test.ts +315 -0
  39. package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +55 -0
  40. package/dist/resources/extensions/gsd/tests/verification-evidence.test.ts +26 -24
  41. package/dist/resources/extensions/gsd/tests/verification-gate.test.ts +136 -7
  42. package/dist/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
  43. package/dist/resources/extensions/gsd/types.ts +1 -0
  44. package/dist/resources/extensions/gsd/unit-runtime.ts +16 -13
  45. package/dist/resources/extensions/gsd/verification-evidence.ts +2 -0
  46. package/dist/resources/extensions/gsd/verification-gate.ts +13 -2
  47. package/dist/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
  48. package/dist/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
  49. package/dist/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
  50. package/dist/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
  51. package/dist/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
  52. package/dist/resources/extensions/gsd/workflow-templates/registry.json +85 -0
  53. package/dist/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
  54. package/dist/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
  55. package/dist/resources/extensions/gsd/workflow-templates/spike.md +69 -0
  56. package/dist/resources/extensions/gsd/workflow-templates.ts +241 -0
  57. package/dist/resources/extensions/mcp-client/index.ts +459 -0
  58. package/dist/resources/extensions/remote-questions/discord-adapter.ts +9 -20
  59. package/dist/resources/extensions/remote-questions/http-client.ts +76 -0
  60. package/dist/resources/extensions/remote-questions/notify.ts +1 -2
  61. package/dist/resources/extensions/remote-questions/slack-adapter.ts +11 -18
  62. package/dist/resources/extensions/remote-questions/telegram-adapter.ts +8 -20
  63. package/dist/resources/extensions/remote-questions/types.ts +3 -0
  64. package/dist/resources/extensions/shared/mod.ts +3 -0
  65. package/dist/resources/skills/create-gsd-extension/SKILL.md +87 -0
  66. package/dist/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
  67. package/dist/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
  68. package/dist/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
  69. package/dist/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
  70. package/dist/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
  71. package/dist/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
  72. package/dist/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
  73. package/dist/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
  74. package/dist/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
  75. package/dist/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
  76. package/dist/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
  77. package/dist/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
  78. package/dist/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
  79. package/dist/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
  80. package/dist/resources/skills/create-gsd-extension/references/state-management.md +70 -0
  81. package/dist/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
  82. package/dist/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
  83. package/dist/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
  84. package/dist/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
  85. package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
  86. package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
  87. package/dist/resources/skills/create-skill/SKILL.md +184 -0
  88. package/dist/resources/skills/create-skill/references/api-security.md +226 -0
  89. package/dist/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
  90. package/dist/resources/skills/create-skill/references/common-patterns.md +595 -0
  91. package/dist/resources/skills/create-skill/references/core-principles.md +437 -0
  92. package/dist/resources/skills/create-skill/references/executable-code.md +175 -0
  93. package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
  94. package/dist/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
  95. package/dist/resources/skills/create-skill/references/recommended-structure.md +168 -0
  96. package/dist/resources/skills/create-skill/references/skill-structure.md +372 -0
  97. package/dist/resources/skills/create-skill/references/use-xml-tags.md +466 -0
  98. package/dist/resources/skills/create-skill/references/using-scripts.md +113 -0
  99. package/dist/resources/skills/create-skill/references/using-templates.md +112 -0
  100. package/dist/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
  101. package/dist/resources/skills/create-skill/templates/router-skill.md +73 -0
  102. package/dist/resources/skills/create-skill/templates/simple-skill.md +33 -0
  103. package/dist/resources/skills/create-skill/workflows/add-reference.md +96 -0
  104. package/dist/resources/skills/create-skill/workflows/add-script.md +93 -0
  105. package/dist/resources/skills/create-skill/workflows/add-template.md +74 -0
  106. package/dist/resources/skills/create-skill/workflows/add-workflow.md +120 -0
  107. package/dist/resources/skills/create-skill/workflows/audit-skill.md +148 -0
  108. package/dist/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
  109. package/dist/resources/skills/create-skill/workflows/get-guidance.md +121 -0
  110. package/dist/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
  111. package/dist/resources/skills/create-skill/workflows/verify-skill.md +204 -0
  112. package/package.json +6 -3
  113. package/packages/native/dist/native.d.ts +2 -0
  114. package/packages/native/dist/native.js +19 -5
  115. package/packages/native/src/native.ts +23 -9
  116. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  117. package/packages/pi-coding-agent/dist/core/extensions/loader.js +13 -0
  118. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  120. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
  122. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  124. package/packages/pi-coding-agent/dist/core/system-prompt.js +10 -0
  125. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  126. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -1
  128. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  129. package/packages/pi-coding-agent/package.json +1 -1
  130. package/packages/pi-coding-agent/scripts/copy-assets.cjs +39 -8
  131. package/packages/pi-coding-agent/src/core/extensions/loader.ts +13 -0
  132. package/packages/pi-coding-agent/src/core/settings-manager.ts +11 -0
  133. package/packages/pi-coding-agent/src/core/system-prompt.ts +11 -0
  134. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +4 -1
  135. package/packages/pi-tui/dist/autocomplete.d.ts +3 -0
  136. package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
  137. package/packages/pi-tui/dist/autocomplete.js +14 -0
  138. package/packages/pi-tui/dist/autocomplete.js.map +1 -1
  139. package/packages/pi-tui/src/autocomplete.ts +19 -1
  140. package/pkg/package.json +1 -1
  141. package/src/resources/extensions/bg-shell/process-manager.ts +13 -0
  142. package/src/resources/extensions/gsd/auto-dashboard.ts +186 -65
  143. package/src/resources/extensions/gsd/auto-post-unit.ts +14 -6
  144. package/src/resources/extensions/gsd/auto-recovery.ts +33 -23
  145. package/src/resources/extensions/gsd/auto-start.ts +25 -10
  146. package/src/resources/extensions/gsd/auto-verification.ts +41 -7
  147. package/src/resources/extensions/gsd/auto-worktree-sync.ts +21 -6
  148. package/src/resources/extensions/gsd/auto.ts +67 -22
  149. package/src/resources/extensions/gsd/commands-handlers.ts +3 -11
  150. package/src/resources/extensions/gsd/commands-logs.ts +536 -0
  151. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +90 -47
  152. package/src/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
  153. package/src/resources/extensions/gsd/commands.ts +75 -29
  154. package/src/resources/extensions/gsd/dashboard-overlay.ts +2 -1
  155. package/src/resources/extensions/gsd/doctor-types.ts +13 -0
  156. package/src/resources/extensions/gsd/doctor.ts +2 -6
  157. package/src/resources/extensions/gsd/export.ts +28 -2
  158. package/src/resources/extensions/gsd/gsd-db.ts +19 -0
  159. package/src/resources/extensions/gsd/index.ts +2 -1
  160. package/src/resources/extensions/gsd/json-persistence.ts +67 -0
  161. package/src/resources/extensions/gsd/metrics.ts +17 -31
  162. package/src/resources/extensions/gsd/paths.ts +0 -8
  163. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  164. package/src/resources/extensions/gsd/prompts/workflow-start.md +28 -0
  165. package/src/resources/extensions/gsd/queue-order.ts +10 -11
  166. package/src/resources/extensions/gsd/routing-history.ts +13 -17
  167. package/src/resources/extensions/gsd/session-lock.ts +284 -0
  168. package/src/resources/extensions/gsd/session-status-io.ts +23 -41
  169. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
  170. package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
  171. package/src/resources/extensions/gsd/tests/commands-logs.test.ts +241 -0
  172. package/src/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
  173. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +1 -1
  174. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
  175. package/src/resources/extensions/gsd/tests/session-lock.test.ts +315 -0
  176. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +55 -0
  177. package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +26 -24
  178. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +136 -7
  179. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
  180. package/src/resources/extensions/gsd/types.ts +1 -0
  181. package/src/resources/extensions/gsd/unit-runtime.ts +16 -13
  182. package/src/resources/extensions/gsd/verification-evidence.ts +2 -0
  183. package/src/resources/extensions/gsd/verification-gate.ts +13 -2
  184. package/src/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
  185. package/src/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
  186. package/src/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
  187. package/src/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
  188. package/src/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
  189. package/src/resources/extensions/gsd/workflow-templates/registry.json +85 -0
  190. package/src/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
  191. package/src/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
  192. package/src/resources/extensions/gsd/workflow-templates/spike.md +69 -0
  193. package/src/resources/extensions/gsd/workflow-templates.ts +241 -0
  194. package/src/resources/extensions/mcp-client/index.ts +459 -0
  195. package/src/resources/extensions/remote-questions/discord-adapter.ts +9 -20
  196. package/src/resources/extensions/remote-questions/http-client.ts +76 -0
  197. package/src/resources/extensions/remote-questions/notify.ts +1 -2
  198. package/src/resources/extensions/remote-questions/slack-adapter.ts +11 -18
  199. package/src/resources/extensions/remote-questions/telegram-adapter.ts +8 -20
  200. package/src/resources/extensions/remote-questions/types.ts +3 -0
  201. package/src/resources/extensions/shared/mod.ts +3 -0
  202. package/src/resources/skills/create-gsd-extension/SKILL.md +87 -0
  203. package/src/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
  204. package/src/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
  205. package/src/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
  206. package/src/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
  207. package/src/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
  208. package/src/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
  209. package/src/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
  210. package/src/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
  211. package/src/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
  212. package/src/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
  213. package/src/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
  214. package/src/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
  215. package/src/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
  216. package/src/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
  217. package/src/resources/skills/create-gsd-extension/references/state-management.md +70 -0
  218. package/src/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
  219. package/src/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
  220. package/src/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
  221. package/src/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
  222. package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
  223. package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
  224. package/src/resources/skills/create-skill/SKILL.md +184 -0
  225. package/src/resources/skills/create-skill/references/api-security.md +226 -0
  226. package/src/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
  227. package/src/resources/skills/create-skill/references/common-patterns.md +595 -0
  228. package/src/resources/skills/create-skill/references/core-principles.md +437 -0
  229. package/src/resources/skills/create-skill/references/executable-code.md +175 -0
  230. package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
  231. package/src/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
  232. package/src/resources/skills/create-skill/references/recommended-structure.md +168 -0
  233. package/src/resources/skills/create-skill/references/skill-structure.md +372 -0
  234. package/src/resources/skills/create-skill/references/use-xml-tags.md +466 -0
  235. package/src/resources/skills/create-skill/references/using-scripts.md +113 -0
  236. package/src/resources/skills/create-skill/references/using-templates.md +112 -0
  237. package/src/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
  238. package/src/resources/skills/create-skill/templates/router-skill.md +73 -0
  239. package/src/resources/skills/create-skill/templates/simple-skill.md +33 -0
  240. package/src/resources/skills/create-skill/workflows/add-reference.md +96 -0
  241. package/src/resources/skills/create-skill/workflows/add-script.md +93 -0
  242. package/src/resources/skills/create-skill/workflows/add-template.md +74 -0
  243. package/src/resources/skills/create-skill/workflows/add-workflow.md +120 -0
  244. package/src/resources/skills/create-skill/workflows/audit-skill.md +148 -0
  245. package/src/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
  246. package/src/resources/skills/create-skill/workflows/get-guidance.md +121 -0
  247. package/src/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
  248. package/src/resources/skills/create-skill/workflows/verify-skill.md +204 -0
  249. package/dist/resources/extensions/gsd/preferences-hooks.ts +0 -10
  250. package/dist/resources/extensions/mcporter/index.ts +0 -525
  251. package/dist/resources/extensions/shared/progress-widget.ts +0 -282
  252. package/dist/resources/extensions/shared/thinking-widget.ts +0 -107
  253. package/src/resources/extensions/gsd/preferences-hooks.ts +0 -10
  254. package/src/resources/extensions/mcporter/index.ts +0 -525
  255. package/src/resources/extensions/shared/progress-widget.ts +0 -282
  256. package/src/resources/extensions/shared/thinking-widget.ts +0 -107
@@ -58,6 +58,7 @@ test("verification-evidence: writeVerificationJSON writes correct JSON shape", (
58
58
  stdout: "all good",
59
59
  stderr: "",
60
60
  durationMs: 2340,
61
+ blocking: true,
61
62
  },
62
63
  ],
63
64
  });
@@ -105,9 +106,9 @@ test("verification-evidence: writeVerificationJSON maps exitCode to verdict corr
105
106
  const result = makeResult({
106
107
  passed: false,
107
108
  checks: [
108
- { command: "lint", exitCode: 0, stdout: "", stderr: "", durationMs: 100 },
109
- { command: "test", exitCode: 1, stdout: "", stderr: "fail", durationMs: 200 },
110
- { command: "audit", exitCode: 2, stdout: "", stderr: "err", durationMs: 300 },
109
+ { command: "lint", exitCode: 0, stdout: "", stderr: "", durationMs: 100, blocking: true },
110
+ { command: "test", exitCode: 1, stdout: "", stderr: "fail", durationMs: 200, blocking: true },
111
+ { command: "audit", exitCode: 2, stdout: "", stderr: "err", durationMs: 300, blocking: true },
111
112
  ],
112
113
  });
113
114
 
@@ -133,6 +134,7 @@ test("verification-evidence: writeVerificationJSON excludes stdout/stderr from o
133
134
  stdout: "hello\n",
134
135
  stderr: "some warning",
135
136
  durationMs: 50,
137
+ blocking: true,
136
138
  },
137
139
  ],
138
140
  });
@@ -181,8 +183,8 @@ test("verification-evidence: writeVerificationJSON uses optional unitId when pro
181
183
  test("verification-evidence: formatEvidenceTable returns markdown table with correct columns", () => {
182
184
  const result = makeResult({
183
185
  checks: [
184
- { command: "npm run typecheck", exitCode: 0, stdout: "", stderr: "", durationMs: 2340 },
185
- { command: "npm run lint", exitCode: 1, stdout: "", stderr: "err", durationMs: 1100 },
186
+ { command: "npm run typecheck", exitCode: 0, stdout: "", stderr: "", durationMs: 2340, blocking: true },
187
+ { command: "npm run lint", exitCode: 1, stdout: "", stderr: "err", durationMs: 1100, blocking: true },
186
188
  ],
187
189
  });
188
190
 
@@ -214,9 +216,9 @@ test("verification-evidence: formatEvidenceTable returns no-checks message for e
214
216
  test("verification-evidence: formatEvidenceTable formats duration as seconds with 1 decimal", () => {
215
217
  const result = makeResult({
216
218
  checks: [
217
- { command: "fast", exitCode: 0, stdout: "", stderr: "", durationMs: 150 },
218
- { command: "slow", exitCode: 0, stdout: "", stderr: "", durationMs: 2340 },
219
- { command: "zero", exitCode: 0, stdout: "", stderr: "", durationMs: 0 },
219
+ { command: "fast", exitCode: 0, stdout: "", stderr: "", durationMs: 150, blocking: true },
220
+ { command: "slow", exitCode: 0, stdout: "", stderr: "", durationMs: 2340, blocking: true },
221
+ { command: "zero", exitCode: 0, stdout: "", stderr: "", durationMs: 0, blocking: true },
220
222
  ],
221
223
  });
222
224
 
@@ -230,8 +232,8 @@ test("verification-evidence: formatEvidenceTable uses ✅/❌ emoji for pass/fai
230
232
  const result = makeResult({
231
233
  passed: false,
232
234
  checks: [
233
- { command: "pass-cmd", exitCode: 0, stdout: "", stderr: "", durationMs: 100 },
234
- { command: "fail-cmd", exitCode: 1, stdout: "", stderr: "", durationMs: 200 },
235
+ { command: "pass-cmd", exitCode: 0, stdout: "", stderr: "", durationMs: 100, blocking: true },
236
+ { command: "fail-cmd", exitCode: 1, stdout: "", stderr: "", durationMs: 200, blocking: true },
235
237
  ],
236
238
  });
237
239
 
@@ -335,8 +337,8 @@ test("verification-evidence: integration — VerificationResult → JSON → tab
335
337
  const result = makeResult({
336
338
  passed: false,
337
339
  checks: [
338
- { command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 1500 },
339
- { command: "npm run test:unit", exitCode: 1, stdout: "", stderr: "1 failed", durationMs: 3200 },
340
+ { command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 1500, blocking: true },
341
+ { command: "npm run test:unit", exitCode: 1, stdout: "", stderr: "1 failed", durationMs: 3200, blocking: true },
340
342
  ],
341
343
  discoverySource: "package-json",
342
344
  });
@@ -390,7 +392,7 @@ test("verification-evidence: writeVerificationJSON with retryAttempt and maxRetr
390
392
  const result = makeResult({
391
393
  passed: false,
392
394
  checks: [
393
- { command: "npm run lint", exitCode: 1, stdout: "", stderr: "error", durationMs: 300 },
395
+ { command: "npm run lint", exitCode: 1, stdout: "", stderr: "error", durationMs: 300, blocking: true },
394
396
  ],
395
397
  });
396
398
 
@@ -415,7 +417,7 @@ test("verification-evidence: writeVerificationJSON without retry params omits re
415
417
  const result = makeResult({
416
418
  passed: true,
417
419
  checks: [
418
- { command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100 },
420
+ { command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100, blocking: true },
419
421
  ],
420
422
  });
421
423
 
@@ -441,7 +443,7 @@ test("verification-evidence: writeVerificationJSON includes runtimeErrors when p
441
443
  const result = makeResult({
442
444
  passed: false,
443
445
  checks: [
444
- { command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100 },
446
+ { command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100, blocking: true },
445
447
  ],
446
448
  runtimeErrors: [
447
449
  { source: "bg-shell", severity: "crash", message: "Server crashed", blocking: true },
@@ -473,7 +475,7 @@ test("verification-evidence: writeVerificationJSON omits runtimeErrors when abse
473
475
  const result = makeResult({
474
476
  passed: true,
475
477
  checks: [
476
- { command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 50 },
478
+ { command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 50, blocking: true },
477
479
  ],
478
480
  });
479
481
 
@@ -512,7 +514,7 @@ test("verification-evidence: formatEvidenceTable appends runtime errors section"
512
514
  const result = makeResult({
513
515
  passed: false,
514
516
  checks: [
515
- { command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100 },
517
+ { command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100, blocking: true },
516
518
  ],
517
519
  runtimeErrors: [
518
520
  { source: "bg-shell", severity: "crash", message: "Server crashed with SIGKILL", blocking: true },
@@ -537,7 +539,7 @@ test("verification-evidence: formatEvidenceTable omits runtime errors section wh
537
539
  const result = makeResult({
538
540
  passed: true,
539
541
  checks: [
540
- { command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 200 },
542
+ { command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 200, blocking: true },
541
543
  ],
542
544
  });
543
545
 
@@ -552,7 +554,7 @@ test("verification-evidence: formatEvidenceTable truncates runtime error message
552
554
  const result = makeResult({
553
555
  passed: false,
554
556
  checks: [
555
- { command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100 },
557
+ { command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100, blocking: true },
556
558
  ],
557
559
  runtimeErrors: [
558
560
  { source: "bg-shell", severity: "error", message: longMessage, blocking: false },
@@ -598,7 +600,7 @@ test("verification-evidence: writeVerificationJSON includes auditWarnings when p
598
600
  const result = makeResult({
599
601
  passed: true,
600
602
  checks: [
601
- { command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100 },
603
+ { command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100, blocking: true },
602
604
  ],
603
605
  auditWarnings: SAMPLE_AUDIT_WARNINGS,
604
606
  });
@@ -627,7 +629,7 @@ test("verification-evidence: writeVerificationJSON omits auditWarnings when abse
627
629
  const result = makeResult({
628
630
  passed: true,
629
631
  checks: [
630
- { command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 50 },
632
+ { command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 50, blocking: true },
631
633
  ],
632
634
  });
633
635
 
@@ -666,7 +668,7 @@ test("verification-evidence: formatEvidenceTable appends audit warnings section"
666
668
  const result = makeResult({
667
669
  passed: true,
668
670
  checks: [
669
- { command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100 },
671
+ { command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100, blocking: true },
670
672
  ],
671
673
  auditWarnings: SAMPLE_AUDIT_WARNINGS,
672
674
  });
@@ -689,7 +691,7 @@ test("verification-evidence: formatEvidenceTable omits audit warnings section wh
689
691
  const result = makeResult({
690
692
  passed: true,
691
693
  checks: [
692
- { command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 200 },
694
+ { command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 200, blocking: true },
693
695
  ],
694
696
  });
695
697
 
@@ -705,7 +707,7 @@ test("verification-evidence: integration — VerificationResult with auditWarnin
705
707
  const result = makeResult({
706
708
  passed: true,
707
709
  checks: [
708
- { command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 1500 },
710
+ { command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 1500, blocking: true },
709
711
  ],
710
712
  auditWarnings: [
711
713
  {
@@ -581,7 +581,7 @@ test("formatFailureContext: formats a single failure with command, exit code, st
581
581
  const result: import("../types.ts").VerificationResult = {
582
582
  passed: false,
583
583
  checks: [
584
- { command: "npm run lint", exitCode: 1, stdout: "", stderr: "error: unused var", durationMs: 500 },
584
+ { command: "npm run lint", exitCode: 1, stdout: "", stderr: "error: unused var", durationMs: 500, blocking: true },
585
585
  ],
586
586
  discoverySource: "preference",
587
587
  timestamp: Date.now(),
@@ -598,9 +598,9 @@ test("formatFailureContext: formats multiple failures", () => {
598
598
  const result: import("../types.ts").VerificationResult = {
599
599
  passed: false,
600
600
  checks: [
601
- { command: "npm run lint", exitCode: 1, stdout: "", stderr: "lint error", durationMs: 100 },
602
- { command: "npm run test", exitCode: 2, stdout: "", stderr: "test failure", durationMs: 200 },
603
- { command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 50 },
601
+ { command: "npm run lint", exitCode: 1, stdout: "", stderr: "lint error", durationMs: 100, blocking: true },
602
+ { command: "npm run test", exitCode: 2, stdout: "", stderr: "test failure", durationMs: 200, blocking: true },
603
+ { command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 50, blocking: true },
604
604
  ],
605
605
  discoverySource: "preference",
606
606
  timestamp: Date.now(),
@@ -619,7 +619,7 @@ test("formatFailureContext: truncates stderr longer than 2000 chars", () => {
619
619
  const result: import("../types.ts").VerificationResult = {
620
620
  passed: false,
621
621
  checks: [
622
- { command: "big-err", exitCode: 1, stdout: "", stderr: longStderr, durationMs: 100 },
622
+ { command: "big-err", exitCode: 1, stdout: "", stderr: longStderr, durationMs: 100, blocking: true },
623
623
  ],
624
624
  discoverySource: "preference",
625
625
  timestamp: Date.now(),
@@ -634,8 +634,8 @@ test("formatFailureContext: returns empty string when all checks pass", () => {
634
634
  const result: import("../types.ts").VerificationResult = {
635
635
  passed: true,
636
636
  checks: [
637
- { command: "npm run lint", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100 },
638
- { command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 200 },
637
+ { command: "npm run lint", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100, blocking: true },
638
+ { command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 200, blocking: true },
639
639
  ],
640
640
  discoverySource: "preference",
641
641
  timestamp: Date.now(),
@@ -663,6 +663,7 @@ test("formatFailureContext: caps total output at 10,000 chars", () => {
663
663
  stdout: "",
664
664
  stderr: "e".repeat(1000), // 1000 chars each, 20 * ~1050 (with formatting) > 10,000
665
665
  durationMs: 100,
666
+ blocking: true,
666
667
  });
667
668
  }
668
669
  const result: import("../types.ts").VerificationResult = {
@@ -1077,3 +1078,131 @@ test("dependency-audit: subdirectory package.json does not trigger audit", () =>
1077
1078
  assert.equal(npmAuditCalled, false, "subdirectory dependency files should not trigger audit");
1078
1079
  assert.deepStrictEqual(result, []);
1079
1080
  });
1081
+
1082
+ // ─── Non-Blocking Discovery Tests ────────────────────────────────────────────
1083
+
1084
+ test("non-blocking: package-json discovered commands failing → result.passed is still true", () => {
1085
+ const tmp = makeTempDir("vg-nb-pkg-fail");
1086
+ try {
1087
+ writeFileSync(
1088
+ join(tmp, "package.json"),
1089
+ JSON.stringify({ scripts: { lint: "eslint .", test: "vitest" } }),
1090
+ );
1091
+ // These commands will fail because eslint/vitest don't exist in the temp dir
1092
+ const result = runVerificationGate({
1093
+ basePath: tmp,
1094
+ unitId: "T01",
1095
+ cwd: tmp,
1096
+ // No preference commands — discovery falls through to package.json
1097
+ });
1098
+ assert.equal(result.discoverySource, "package-json");
1099
+ assert.ok(result.checks.length > 0, "should have discovered package.json checks");
1100
+ assert.equal(result.passed, true, "package-json failures should not block the gate");
1101
+ for (const check of result.checks) {
1102
+ assert.equal(check.blocking, false, "package-json checks should be non-blocking");
1103
+ }
1104
+ } finally {
1105
+ rmSync(tmp, { recursive: true, force: true });
1106
+ }
1107
+ });
1108
+
1109
+ test("non-blocking: preference commands failing → result.passed is false", () => {
1110
+ const tmp = makeTempDir("vg-nb-pref-fail");
1111
+ try {
1112
+ const result = runVerificationGate({
1113
+ basePath: tmp,
1114
+ unitId: "T01",
1115
+ cwd: tmp,
1116
+ preferenceCommands: ["sh -c 'exit 1'"],
1117
+ });
1118
+ assert.equal(result.discoverySource, "preference");
1119
+ assert.equal(result.passed, false, "preference failures should block the gate");
1120
+ assert.equal(result.checks[0].blocking, true, "preference checks should be blocking");
1121
+ } finally {
1122
+ rmSync(tmp, { recursive: true, force: true });
1123
+ }
1124
+ });
1125
+
1126
+ test("non-blocking: task-plan commands failing → result.passed is false", () => {
1127
+ const tmp = makeTempDir("vg-nb-tp-fail");
1128
+ try {
1129
+ const result = runVerificationGate({
1130
+ basePath: tmp,
1131
+ unitId: "T01",
1132
+ cwd: tmp,
1133
+ taskPlanVerify: "sh -c 'exit 1'",
1134
+ });
1135
+ assert.equal(result.discoverySource, "task-plan");
1136
+ assert.equal(result.passed, false, "task-plan failures should block the gate");
1137
+ assert.equal(result.checks[0].blocking, true, "task-plan checks should be blocking");
1138
+ } finally {
1139
+ rmSync(tmp, { recursive: true, force: true });
1140
+ }
1141
+ });
1142
+
1143
+ test("non-blocking: blocking field is set correctly based on discovery source", () => {
1144
+ const tmp = makeTempDir("vg-nb-field");
1145
+ try {
1146
+ // preference → blocking
1147
+ const prefResult = runVerificationGate({
1148
+ basePath: tmp,
1149
+ unitId: "T01",
1150
+ cwd: tmp,
1151
+ preferenceCommands: ["echo ok"],
1152
+ });
1153
+ assert.equal(prefResult.checks[0].blocking, true);
1154
+
1155
+ // task-plan → blocking
1156
+ const tpResult = runVerificationGate({
1157
+ basePath: tmp,
1158
+ unitId: "T01",
1159
+ cwd: tmp,
1160
+ taskPlanVerify: "echo ok",
1161
+ });
1162
+ assert.equal(tpResult.checks[0].blocking, true);
1163
+
1164
+ // package-json → non-blocking
1165
+ writeFileSync(
1166
+ join(tmp, "package.json"),
1167
+ JSON.stringify({ scripts: { test: "echo ok" } }),
1168
+ );
1169
+ const pkgResult = runVerificationGate({
1170
+ basePath: tmp,
1171
+ unitId: "T01",
1172
+ cwd: tmp,
1173
+ });
1174
+ assert.equal(pkgResult.checks[0].blocking, false);
1175
+ } finally {
1176
+ rmSync(tmp, { recursive: true, force: true });
1177
+ }
1178
+ });
1179
+
1180
+ test("non-blocking: formatFailureContext only includes blocking failures", () => {
1181
+ const result: import("../types.ts").VerificationResult = {
1182
+ passed: true,
1183
+ checks: [
1184
+ { command: "npm run lint", exitCode: 1, stdout: "", stderr: "lint warning", durationMs: 100, blocking: false },
1185
+ { command: "npm run test", exitCode: 1, stdout: "", stderr: "test error", durationMs: 200, blocking: true },
1186
+ { command: "npm run typecheck", exitCode: 1, stdout: "", stderr: "type error", durationMs: 50, blocking: false },
1187
+ ],
1188
+ discoverySource: "preference",
1189
+ timestamp: Date.now(),
1190
+ };
1191
+ const output = formatFailureContext(result);
1192
+ assert.ok(output.includes("`npm run test`"), "should include blocking failure");
1193
+ assert.ok(!output.includes("npm run lint"), "should not include non-blocking failure");
1194
+ assert.ok(!output.includes("npm run typecheck"), "should not include non-blocking failure");
1195
+ });
1196
+
1197
+ test("non-blocking: formatFailureContext returns empty when only non-blocking failures exist", () => {
1198
+ const result: import("../types.ts").VerificationResult = {
1199
+ passed: true,
1200
+ checks: [
1201
+ { command: "npm run lint", exitCode: 1, stdout: "", stderr: "lint warning", durationMs: 100, blocking: false },
1202
+ { command: "npm run test", exitCode: 1, stdout: "", stderr: "test warning", durationMs: 200, blocking: false },
1203
+ ],
1204
+ discoverySource: "package-json",
1205
+ timestamp: Date.now(),
1206
+ };
1207
+ assert.equal(formatFailureContext(result), "", "should return empty when only non-blocking failures");
1208
+ });
@@ -0,0 +1,173 @@
1
+ // GSD Workflow Templates — Unit Tests
2
+ //
3
+ // Tests registry loading, template resolution, auto-detection, and listing.
4
+
5
+ import { createTestContext } from './test-helpers.ts';
6
+ import {
7
+ loadRegistry,
8
+ resolveByName,
9
+ autoDetect,
10
+ listTemplates,
11
+ getTemplateInfo,
12
+ loadWorkflowTemplate,
13
+ } from '../workflow-templates.ts';
14
+
15
+ const { assertEq, assertTrue, assertMatch, report } = createTestContext();
16
+
17
+ // ═══════════════════════════════════════════════════════════════════════════
18
+ // Registry Loading
19
+ // ═══════════════════════════════════════════════════════════════════════════
20
+
21
+ console.log('\n── Registry Loading ──');
22
+
23
+ {
24
+ const registry = loadRegistry();
25
+ assertTrue(registry !== null, 'Registry should load');
26
+ assertEq(registry.version, 1, 'Registry version should be 1');
27
+ assertTrue(Object.keys(registry.templates).length >= 8, 'Should have at least 8 templates');
28
+
29
+ // Verify required template keys exist
30
+ const expectedIds = ['full-project', 'bugfix', 'small-feature', 'refactor', 'spike', 'hotfix', 'security-audit', 'dep-upgrade'];
31
+ for (const id of expectedIds) {
32
+ assertTrue(id in registry.templates, `Template "${id}" should exist in registry`);
33
+ }
34
+
35
+ // Verify each template has required fields
36
+ for (const [id, entry] of Object.entries(registry.templates)) {
37
+ assertTrue(typeof entry.name === 'string' && entry.name.length > 0, `${id}: name should be non-empty string`);
38
+ assertTrue(typeof entry.description === 'string' && entry.description.length > 0, `${id}: description should be non-empty`);
39
+ assertTrue(typeof entry.file === 'string' && entry.file.endsWith('.md'), `${id}: file should be a .md path`);
40
+ assertTrue(Array.isArray(entry.phases) && entry.phases.length > 0, `${id}: phases should be non-empty array`);
41
+ assertTrue(Array.isArray(entry.triggers) && entry.triggers.length > 0, `${id}: triggers should be non-empty array`);
42
+ }
43
+ }
44
+
45
+ // ═══════════════════════════════════════════════════════════════════════════
46
+ // Resolve by Name
47
+ // ═══════════════════════════════════════════════════════════════════════════
48
+
49
+ console.log('\n── Resolve by Name ──');
50
+
51
+ {
52
+ // Exact match
53
+ const bugfix = resolveByName('bugfix');
54
+ assertTrue(bugfix !== null, 'Should resolve "bugfix"');
55
+ assertEq(bugfix!.id, 'bugfix', 'ID should be "bugfix"');
56
+ assertEq(bugfix!.confidence, 'exact', 'Exact name should have exact confidence');
57
+
58
+ // Case-insensitive name match
59
+ const spike = resolveByName('Research Spike');
60
+ assertTrue(spike !== null, 'Should resolve "Research Spike" by name');
61
+ assertEq(spike!.id, 'spike', 'Should resolve to spike');
62
+
63
+ // Alias match
64
+ const bug = resolveByName('bug');
65
+ assertTrue(bug !== null, 'Should resolve "bug" alias');
66
+ assertEq(bug!.id, 'bugfix', 'Alias "bug" should map to bugfix');
67
+
68
+ const feat = resolveByName('feat');
69
+ assertTrue(feat !== null, 'Should resolve "feat" alias');
70
+ assertEq(feat!.id, 'small-feature', 'Alias "feat" should map to small-feature');
71
+
72
+ const deps = resolveByName('deps');
73
+ assertTrue(deps !== null, 'Should resolve "deps" alias');
74
+ assertEq(deps!.id, 'dep-upgrade', 'Alias "deps" should map to dep-upgrade');
75
+
76
+ // No match
77
+ const missing = resolveByName('nonexistent-template');
78
+ assertTrue(missing === null, 'Should return null for unknown template');
79
+ }
80
+
81
+ // ═══════════════════════════════════════════════════════════════════════════
82
+ // Auto-Detection
83
+ // ═══════════════════════════════════════════════════════════════════════════
84
+
85
+ console.log('\n── Auto-Detection ──');
86
+
87
+ {
88
+ // Should detect bugfix from "fix" keyword
89
+ const fixMatches = autoDetect('fix the login button');
90
+ assertTrue(fixMatches.length > 0, 'Should detect matches for "fix the login button"');
91
+ assertTrue(fixMatches.some(m => m.id === 'bugfix'), 'Should include bugfix in matches');
92
+
93
+ // Should detect spike from "research" keyword
94
+ const researchMatches = autoDetect('research authentication libraries');
95
+ assertTrue(researchMatches.length > 0, 'Should detect matches for "research"');
96
+ assertTrue(researchMatches.some(m => m.id === 'spike'), 'Should include spike in matches');
97
+
98
+ // Should detect hotfix from "urgent" keyword
99
+ const urgentMatches = autoDetect('urgent production is down');
100
+ assertTrue(urgentMatches.length > 0, 'Should detect matches for "urgent"');
101
+ assertTrue(urgentMatches.some(m => m.id === 'hotfix'), 'Should include hotfix in matches');
102
+
103
+ // Should detect dep-upgrade from "upgrade" keyword
104
+ const upgradeMatches = autoDetect('upgrade react to v19');
105
+ assertTrue(upgradeMatches.length > 0, 'Should detect matches for "upgrade"');
106
+ assertTrue(upgradeMatches.some(m => m.id === 'dep-upgrade'), 'Should include dep-upgrade in matches');
107
+
108
+ // Multi-word triggers should score higher
109
+ const projectMatches = autoDetect('create a new project from scratch');
110
+ const projectMatch = projectMatches.find(m => m.id === 'full-project');
111
+ assertTrue(projectMatch !== undefined, 'Should detect full-project for "from scratch"');
112
+
113
+ // Empty input should return no matches
114
+ const emptyMatches = autoDetect('');
115
+ assertEq(emptyMatches.length, 0, 'Empty input should return no matches');
116
+ }
117
+
118
+ // ═══════════════════════════════════════════════════════════════════════════
119
+ // List Templates
120
+ // ═══════════════════════════════════════════════════════════════════════════
121
+
122
+ console.log('\n── List Templates ──');
123
+
124
+ {
125
+ const output = listTemplates();
126
+ assertTrue(output.includes('Workflow Templates'), 'Should have header');
127
+ assertTrue(output.includes('bugfix'), 'Should list bugfix');
128
+ assertTrue(output.includes('spike'), 'Should list spike');
129
+ assertTrue(output.includes('hotfix'), 'Should list hotfix');
130
+ assertTrue(output.includes('/gsd start'), 'Should include usage hint');
131
+ }
132
+
133
+ // ═══════════════════════════════════════════════════════════════════════════
134
+ // Template Info
135
+ // ═══════════════════════════════════════════════════════════════════════════
136
+
137
+ console.log('\n── Template Info ──');
138
+
139
+ {
140
+ const info = getTemplateInfo('bugfix');
141
+ assertTrue(info !== null, 'Should return info for bugfix');
142
+ assertTrue(info!.includes('Bug Fix'), 'Should include template name');
143
+ assertTrue(info!.includes('triage'), 'Should include phase names');
144
+ assertTrue(info!.includes('Triggers'), 'Should include triggers section');
145
+
146
+ const missing = getTemplateInfo('nonexistent');
147
+ assertTrue(missing === null, 'Should return null for unknown template');
148
+ }
149
+
150
+ // ═══════════════════════════════════════════════════════════════════════════
151
+ // Load Workflow Template Content
152
+ // ═══════════════════════════════════════════════════════════════════════════
153
+
154
+ console.log('\n── Load Workflow Template ──');
155
+
156
+ {
157
+ const content = loadWorkflowTemplate('bugfix');
158
+ assertTrue(content !== null, 'Should load bugfix template');
159
+ assertTrue(content!.includes('Bugfix Workflow'), 'Should contain workflow title');
160
+ assertTrue(content!.includes('Phase 1: Triage'), 'Should contain triage phase');
161
+ assertTrue(content!.includes('Phase 4: Ship'), 'Should contain ship phase');
162
+
163
+ const hotfixContent = loadWorkflowTemplate('hotfix');
164
+ assertTrue(hotfixContent !== null, 'Should load hotfix template');
165
+ assertTrue(hotfixContent!.includes('Hotfix Workflow'), 'Should contain hotfix title');
166
+
167
+ const missingContent = loadWorkflowTemplate('nonexistent');
168
+ assertTrue(missingContent === null, 'Should return null for unknown template');
169
+ }
170
+
171
+ // ═══════════════════════════════════════════════════════════════════════════
172
+
173
+ report();
@@ -55,6 +55,7 @@ export interface VerificationCheck {
55
55
  stdout: string;
56
56
  stderr: string;
57
57
  durationMs: number;
58
+ blocking: boolean; // true for preference/task-plan sources, false for package-json (advisory only)
58
59
  }
59
60
 
60
61
  /** A runtime error captured from bg-shell processes or browser console */
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
1
+ import { existsSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import {
4
4
  gsdRoot,
@@ -8,6 +8,7 @@ import {
8
8
  resolveTaskFile,
9
9
  } from "./paths.js";
10
10
  import { loadFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
11
+ import { loadJsonFileOrNull, saveJsonFile } from "./json-persistence.js";
11
12
 
12
13
  export type UnitRuntimePhase =
13
14
  | "dispatched"
@@ -46,13 +47,23 @@ export interface AutoUnitRuntimeRecord {
46
47
  lastRecoveryReason?: "idle" | "hard";
47
48
  }
48
49
 
50
+ function isAutoUnitRuntimeRecord(data: unknown): data is AutoUnitRuntimeRecord {
51
+ return (
52
+ typeof data === "object" &&
53
+ data !== null &&
54
+ (data as AutoUnitRuntimeRecord).version === 1 &&
55
+ typeof (data as AutoUnitRuntimeRecord).unitType === "string" &&
56
+ typeof (data as AutoUnitRuntimeRecord).unitId === "string"
57
+ );
58
+ }
59
+
49
60
  function runtimeDir(basePath: string): string {
50
61
  return join(gsdRoot(basePath), "runtime", "units");
51
62
  }
52
63
 
53
64
  function runtimePath(basePath: string, unitType: string, unitId: string): string {
54
- const sanitizedUnitType = unitType.replace(/[\/]/g, "-");
55
- const sanitizedUnitId = unitId.replace(/[\/]/g, "-");
65
+ const sanitizedUnitType = unitType.replace(/[^a-zA-Z0-9._-]+/g, "-");
66
+ const sanitizedUnitId = unitId.replace(/[^a-zA-Z0-9._-]+/g, "-");
56
67
  return join(runtimeDir(basePath), `${sanitizedUnitType}-${sanitizedUnitId}.json`);
57
68
  }
58
69
 
@@ -63,8 +74,6 @@ export function writeUnitRuntimeRecord(
63
74
  startedAt: number,
64
75
  updates: Partial<AutoUnitRuntimeRecord> = {},
65
76
  ): AutoUnitRuntimeRecord {
66
- const dir = runtimeDir(basePath);
67
- mkdirSync(dir, { recursive: true });
68
77
  const path = runtimePath(basePath, unitType, unitId);
69
78
  const prev = readUnitRuntimeRecord(basePath, unitType, unitId);
70
79
  const next: AutoUnitRuntimeRecord = {
@@ -84,18 +93,12 @@ export function writeUnitRuntimeRecord(
84
93
  recoveryAttempts: updates.recoveryAttempts ?? prev?.recoveryAttempts ?? 0,
85
94
  lastRecoveryReason: updates.lastRecoveryReason ?? prev?.lastRecoveryReason,
86
95
  };
87
- writeFileSync(path, JSON.stringify(next, null, 2) + "\n", "utf-8");
96
+ saveJsonFile(path, next);
88
97
  return next;
89
98
  }
90
99
 
91
100
  export function readUnitRuntimeRecord(basePath: string, unitType: string, unitId: string): AutoUnitRuntimeRecord | null {
92
- const path = runtimePath(basePath, unitType, unitId);
93
- if (!existsSync(path)) return null;
94
- try {
95
- return JSON.parse(readFileSync(path, "utf-8")) as AutoUnitRuntimeRecord;
96
- } catch {
97
- return null;
98
- }
101
+ return loadJsonFileOrNull(runtimePath(basePath, unitType, unitId), isAutoUnitRuntimeRecord);
99
102
  }
100
103
 
101
104
  export function clearUnitRuntimeRecord(basePath: string, unitType: string, unitId: string): void {
@@ -20,6 +20,7 @@ export interface EvidenceCheckJSON {
20
20
  exitCode: number;
21
21
  durationMs: number;
22
22
  verdict: "pass" | "fail";
23
+ blocking: boolean;
23
24
  }
24
25
 
25
26
  export interface RuntimeErrorJSON {
@@ -80,6 +81,7 @@ export function writeVerificationJSON(
80
81
  exitCode: check.exitCode,
81
82
  durationMs: check.durationMs,
82
83
  verdict: check.exitCode === 0 ? "pass" : "fail",
84
+ blocking: check.blocking,
83
85
  })),
84
86
  ...(retryAttempt !== undefined ? { retryAttempt } : {}),
85
87
  ...(maxRetries !== undefined ? { maxRetries } : {}),