gsd-pi 2.41.0-dev.0acbce9 → 2.41.0-dev.5a170d0

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 (291) hide show
  1. package/README.md +1 -1
  2. package/dist/cli-web-branch.d.ts +6 -0
  3. package/dist/cli-web-branch.js +17 -0
  4. package/dist/onboarding.js +2 -1
  5. package/dist/resources/extensions/gsd/auto/loop.js +89 -1
  6. package/dist/resources/extensions/gsd/auto/phases.js +28 -10
  7. package/dist/resources/extensions/gsd/auto/session.js +6 -0
  8. package/dist/resources/extensions/gsd/auto-dashboard.js +8 -2
  9. package/dist/resources/extensions/gsd/auto-dispatch.js +19 -2
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +7 -0
  11. package/dist/resources/extensions/gsd/auto-recovery.js +12 -4
  12. package/dist/resources/extensions/gsd/auto-start.js +8 -3
  13. package/dist/resources/extensions/gsd/auto-worktree.js +147 -13
  14. package/dist/resources/extensions/gsd/auto.js +64 -2
  15. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +199 -164
  16. package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +62 -0
  17. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  18. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +16 -0
  19. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +7 -2
  20. package/dist/resources/extensions/gsd/commands/catalog.js +40 -1
  21. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
  22. package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
  23. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +146 -0
  24. package/dist/resources/extensions/gsd/context-injector.js +74 -0
  25. package/dist/resources/extensions/gsd/context-store.js +4 -3
  26. package/dist/resources/extensions/gsd/custom-execution-policy.js +47 -0
  27. package/dist/resources/extensions/gsd/custom-verification.js +145 -0
  28. package/dist/resources/extensions/gsd/custom-workflow-engine.js +164 -0
  29. package/dist/resources/extensions/gsd/dashboard-overlay.js +1 -0
  30. package/dist/resources/extensions/gsd/db-writer.js +5 -2
  31. package/dist/resources/extensions/gsd/definition-loader.js +352 -0
  32. package/dist/resources/extensions/gsd/detection.js +1 -1
  33. package/dist/resources/extensions/gsd/dev-execution-policy.js +24 -0
  34. package/dist/resources/extensions/gsd/dev-workflow-engine.js +82 -0
  35. package/dist/resources/extensions/gsd/doctor.js +11 -1
  36. package/dist/resources/extensions/gsd/engine-resolver.js +40 -0
  37. package/dist/resources/extensions/gsd/engine-types.js +8 -0
  38. package/dist/resources/extensions/gsd/execution-policy.js +8 -0
  39. package/dist/resources/extensions/gsd/exit-command.js +12 -2
  40. package/dist/resources/extensions/gsd/export.js +9 -13
  41. package/dist/resources/extensions/gsd/extension-manifest.json +2 -2
  42. package/dist/resources/extensions/gsd/files.js +28 -11
  43. package/dist/resources/extensions/gsd/forensics.js +10 -3
  44. package/dist/resources/extensions/gsd/git-service.js +5 -1
  45. package/dist/resources/extensions/gsd/graph.js +225 -0
  46. package/dist/resources/extensions/gsd/gsd-db.js +25 -8
  47. package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
  48. package/dist/resources/extensions/gsd/guided-flow.js +7 -3
  49. package/dist/resources/extensions/gsd/journal.js +85 -0
  50. package/dist/resources/extensions/gsd/md-importer.js +5 -0
  51. package/dist/resources/extensions/gsd/milestone-ids.js +1 -1
  52. package/dist/resources/extensions/gsd/native-git-bridge.js +2 -2
  53. package/dist/resources/extensions/gsd/post-unit-hooks.js +24 -412
  54. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  55. package/dist/resources/extensions/gsd/preferences.js +1 -0
  56. package/dist/resources/extensions/gsd/prompt-loader.js +34 -4
  57. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +11 -10
  58. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
  59. package/dist/resources/extensions/gsd/prompts/discuss.md +1 -1
  60. package/dist/resources/extensions/gsd/prompts/queue.md +1 -1
  61. package/dist/resources/extensions/gsd/repo-identity.js +46 -2
  62. package/dist/resources/extensions/gsd/rule-registry.js +489 -0
  63. package/dist/resources/extensions/gsd/rule-types.js +6 -0
  64. package/dist/resources/extensions/gsd/run-manager.js +134 -0
  65. package/dist/resources/extensions/gsd/service-tier.js +138 -0
  66. package/dist/resources/extensions/gsd/structured-data-formatter.js +2 -1
  67. package/dist/resources/extensions/gsd/templates/decisions.md +2 -2
  68. package/dist/resources/extensions/gsd/workflow-engine.js +7 -0
  69. package/dist/resources/extensions/gsd/workflow-templates.js +13 -1
  70. package/dist/resources/extensions/gsd/worktree-manager.js +20 -6
  71. package/dist/resources/extensions/gsd/worktree-resolver.js +19 -2
  72. package/dist/resources/extensions/subagent/index.js +7 -3
  73. package/dist/resources/extensions/voice/index.js +4 -4
  74. package/dist/resources/skills/create-workflow/SKILL.md +103 -0
  75. package/dist/resources/skills/create-workflow/references/feature-patterns.md +128 -0
  76. package/dist/resources/skills/create-workflow/references/verification-policies.md +76 -0
  77. package/dist/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
  78. package/dist/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
  79. package/dist/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
  80. package/dist/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
  81. package/dist/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
  82. package/dist/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
  83. package/dist/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
  84. package/dist/web/standalone/.next/BUILD_ID +1 -1
  85. package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
  86. package/dist/web/standalone/.next/build-manifest.json +3 -3
  87. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  88. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  89. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  90. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/index.html +1 -1
  121. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  122. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  123. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  124. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  125. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  126. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  127. package/dist/web/standalone/.next/server/app-paths-manifest.json +16 -16
  128. package/dist/web/standalone/.next/server/chunks/229.js +3 -3
  129. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  132. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  133. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  134. package/dist/web/standalone/.next/static/chunks/4024.c195dc1fdd2adbea.js +9 -0
  135. package/dist/web/standalone/.next/static/chunks/{webpack-9afaaebf6042a1d7.js → webpack-fa307370fcf9fb2c.js} +1 -1
  136. package/dist/web-mode.d.ts +2 -0
  137. package/dist/web-mode.js +29 -7
  138. package/package.json +1 -1
  139. package/packages/native/src/__tests__/text.test.mjs +33 -0
  140. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +3 -1
  141. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -1
  142. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  143. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +10 -7
  144. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  145. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  146. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -0
  147. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  148. package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +4 -2
  149. package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +11 -7
  150. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +5 -0
  151. package/src/resources/extensions/gsd/auto/loop-deps.ts +5 -1
  152. package/src/resources/extensions/gsd/auto/loop.ts +101 -1
  153. package/src/resources/extensions/gsd/auto/phases.ts +30 -10
  154. package/src/resources/extensions/gsd/auto/session.ts +6 -0
  155. package/src/resources/extensions/gsd/auto/types.ts +4 -0
  156. package/src/resources/extensions/gsd/auto-dashboard.ts +9 -2
  157. package/src/resources/extensions/gsd/auto-dispatch.ts +25 -5
  158. package/src/resources/extensions/gsd/auto-post-unit.ts +8 -0
  159. package/src/resources/extensions/gsd/auto-recovery.ts +12 -4
  160. package/src/resources/extensions/gsd/auto-start.ts +8 -3
  161. package/src/resources/extensions/gsd/auto-worktree.ts +162 -18
  162. package/src/resources/extensions/gsd/auto.ts +71 -2
  163. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +209 -162
  164. package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +62 -0
  165. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  166. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +13 -0
  167. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +9 -2
  168. package/src/resources/extensions/gsd/commands/catalog.ts +40 -1
  169. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
  170. package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
  171. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +164 -0
  172. package/src/resources/extensions/gsd/context-injector.ts +100 -0
  173. package/src/resources/extensions/gsd/context-store.ts +4 -3
  174. package/src/resources/extensions/gsd/custom-execution-policy.ts +73 -0
  175. package/src/resources/extensions/gsd/custom-verification.ts +180 -0
  176. package/src/resources/extensions/gsd/custom-workflow-engine.ts +216 -0
  177. package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -0
  178. package/src/resources/extensions/gsd/db-writer.ts +6 -2
  179. package/src/resources/extensions/gsd/definition-loader.ts +462 -0
  180. package/src/resources/extensions/gsd/detection.ts +1 -1
  181. package/src/resources/extensions/gsd/dev-execution-policy.ts +51 -0
  182. package/src/resources/extensions/gsd/dev-workflow-engine.ts +110 -0
  183. package/src/resources/extensions/gsd/doctor.ts +12 -1
  184. package/src/resources/extensions/gsd/engine-resolver.ts +57 -0
  185. package/src/resources/extensions/gsd/engine-types.ts +71 -0
  186. package/src/resources/extensions/gsd/execution-policy.ts +43 -0
  187. package/src/resources/extensions/gsd/exit-command.ts +14 -2
  188. package/src/resources/extensions/gsd/export.ts +8 -15
  189. package/src/resources/extensions/gsd/extension-manifest.json +2 -2
  190. package/src/resources/extensions/gsd/files.ts +29 -12
  191. package/src/resources/extensions/gsd/forensics.ts +9 -3
  192. package/src/resources/extensions/gsd/git-service.ts +5 -4
  193. package/src/resources/extensions/gsd/graph.ts +312 -0
  194. package/src/resources/extensions/gsd/gsd-db.ts +37 -8
  195. package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
  196. package/src/resources/extensions/gsd/guided-flow.ts +7 -3
  197. package/src/resources/extensions/gsd/journal.ts +134 -0
  198. package/src/resources/extensions/gsd/md-importer.ts +6 -0
  199. package/src/resources/extensions/gsd/milestone-ids.ts +1 -1
  200. package/src/resources/extensions/gsd/native-git-bridge.ts +2 -2
  201. package/src/resources/extensions/gsd/post-unit-hooks.ts +24 -462
  202. package/src/resources/extensions/gsd/preferences-types.ts +3 -0
  203. package/src/resources/extensions/gsd/preferences.ts +1 -0
  204. package/src/resources/extensions/gsd/prompt-loader.ts +35 -4
  205. package/src/resources/extensions/gsd/prompts/complete-milestone.md +11 -10
  206. package/src/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
  207. package/src/resources/extensions/gsd/prompts/discuss.md +1 -1
  208. package/src/resources/extensions/gsd/prompts/queue.md +1 -1
  209. package/src/resources/extensions/gsd/repo-identity.ts +47 -2
  210. package/src/resources/extensions/gsd/rule-registry.ts +599 -0
  211. package/src/resources/extensions/gsd/rule-types.ts +68 -0
  212. package/src/resources/extensions/gsd/run-manager.ts +180 -0
  213. package/src/resources/extensions/gsd/service-tier.ts +171 -0
  214. package/src/resources/extensions/gsd/structured-data-formatter.ts +3 -1
  215. package/src/resources/extensions/gsd/templates/decisions.md +2 -2
  216. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +103 -120
  217. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +85 -0
  218. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -2
  219. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +202 -0
  220. package/src/resources/extensions/gsd/tests/bundled-workflow-defs.test.ts +180 -0
  221. package/src/resources/extensions/gsd/tests/captures.test.ts +12 -1
  222. package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +283 -0
  223. package/src/resources/extensions/gsd/tests/context-injector.test.ts +313 -0
  224. package/src/resources/extensions/gsd/tests/context-store.test.ts +10 -5
  225. package/src/resources/extensions/gsd/tests/continue-here.test.ts +20 -20
  226. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +540 -0
  227. package/src/resources/extensions/gsd/tests/custom-verification.test.ts +382 -0
  228. package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +339 -0
  229. package/src/resources/extensions/gsd/tests/dashboard-custom-engine.test.ts +87 -0
  230. package/src/resources/extensions/gsd/tests/db-writer.test.ts +10 -0
  231. package/src/resources/extensions/gsd/tests/definition-loader.test.ts +778 -0
  232. package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +318 -0
  233. package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +15 -10
  234. package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +5 -4
  235. package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +167 -0
  236. package/src/resources/extensions/gsd/tests/doctor-task-done-missing-summary-slice-loop.test.ts +174 -0
  237. package/src/resources/extensions/gsd/tests/e2e-workflow-pipeline-integration.test.ts +476 -0
  238. package/src/resources/extensions/gsd/tests/engine-interfaces-contract.test.ts +271 -0
  239. package/src/resources/extensions/gsd/tests/exit-command.test.ts +55 -0
  240. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +599 -0
  241. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +8 -1
  242. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +7 -7
  243. package/src/resources/extensions/gsd/tests/iterate-engine-integration.test.ts +429 -0
  244. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +513 -0
  245. package/src/resources/extensions/gsd/tests/journal-query-tool.test.ts +147 -0
  246. package/src/resources/extensions/gsd/tests/journal.test.ts +386 -0
  247. package/src/resources/extensions/gsd/tests/md-importer.test.ts +31 -1
  248. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  249. package/src/resources/extensions/gsd/tests/milestone-id-reservation.test.ts +1 -1
  250. package/src/resources/extensions/gsd/tests/parsers.test.ts +110 -0
  251. package/src/resources/extensions/gsd/tests/preferences.test.ts +47 -25
  252. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +3 -1
  253. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +61 -1
  254. package/src/resources/extensions/gsd/tests/routing-history.test.ts +11 -22
  255. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +413 -0
  256. package/src/resources/extensions/gsd/tests/run-manager.test.ts +229 -0
  257. package/src/resources/extensions/gsd/tests/service-tier.test.ts +98 -0
  258. package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +2 -2
  259. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +102 -0
  260. package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +4 -3
  261. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +45 -0
  262. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +117 -0
  263. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -1
  264. package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +99 -0
  265. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -0
  266. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +4 -0
  267. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +178 -0
  268. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +195 -105
  269. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +78 -3
  270. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +140 -0
  271. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +74 -0
  272. package/src/resources/extensions/gsd/types.ts +3 -0
  273. package/src/resources/extensions/gsd/workflow-engine.ts +38 -0
  274. package/src/resources/extensions/gsd/workflow-templates.ts +12 -1
  275. package/src/resources/extensions/gsd/worktree-manager.ts +21 -6
  276. package/src/resources/extensions/gsd/worktree-resolver.ts +30 -9
  277. package/src/resources/extensions/subagent/index.ts +7 -3
  278. package/src/resources/extensions/voice/index.ts +4 -4
  279. package/src/resources/skills/create-workflow/SKILL.md +103 -0
  280. package/src/resources/skills/create-workflow/references/feature-patterns.md +128 -0
  281. package/src/resources/skills/create-workflow/references/verification-policies.md +76 -0
  282. package/src/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
  283. package/src/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
  284. package/src/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
  285. package/src/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
  286. package/src/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
  287. package/src/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
  288. package/src/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
  289. package/dist/web/standalone/.next/static/chunks/4024.279c423e4661ece1.js +0 -9
  290. /package/dist/web/standalone/.next/static/{SwbKZ7JPNFlEmU4f8pKEv → K7GYOOPvQWX6TKYEKhODM}/_buildManifest.js +0 -0
  291. /package/dist/web/standalone/.next/static/{SwbKZ7JPNFlEmU4f8pKEv → K7GYOOPvQWX6TKYEKhODM}/_ssgManifest.js +0 -0
@@ -0,0 +1,386 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import {
4
+ mkdirSync,
5
+ readFileSync,
6
+ existsSync,
7
+ rmSync,
8
+ chmodSync,
9
+ writeFileSync,
10
+ } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { tmpdir } from "node:os";
13
+ import { randomUUID } from "node:crypto";
14
+
15
+ import {
16
+ emitJournalEvent,
17
+ queryJournal,
18
+ type JournalEntry,
19
+ } from "../journal.ts";
20
+
21
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
22
+
23
+ function makeTmpBase(): string {
24
+ const base = join(tmpdir(), `gsd-journal-test-${randomUUID()}`);
25
+ mkdirSync(join(base, ".gsd"), { recursive: true });
26
+ return base;
27
+ }
28
+
29
+ function cleanup(base: string): void {
30
+ try {
31
+ rmSync(base, { recursive: true, force: true });
32
+ } catch {
33
+ /* */
34
+ }
35
+ }
36
+
37
+ function makeEntry(overrides: Partial<JournalEntry> = {}): JournalEntry {
38
+ return {
39
+ ts: "2025-03-21T10:00:00.000Z",
40
+ flowId: "flow-aaa",
41
+ seq: 0,
42
+ eventType: "iteration-start",
43
+ ...overrides,
44
+ };
45
+ }
46
+
47
+ // ─── emitJournalEvent ─────────────────────────────────────────────────────────
48
+
49
+ test("emitJournalEvent creates journal directory and JSONL file", () => {
50
+ const base = makeTmpBase();
51
+ try {
52
+ const entry = makeEntry();
53
+ emitJournalEvent(base, entry);
54
+
55
+ const filePath = join(base, ".gsd", "journal", "2025-03-21.jsonl");
56
+ assert.ok(existsSync(filePath), "JSONL file should exist");
57
+
58
+ const raw = readFileSync(filePath, "utf-8").trim();
59
+ const parsed = JSON.parse(raw);
60
+ assert.equal(parsed.ts, entry.ts);
61
+ assert.equal(parsed.flowId, entry.flowId);
62
+ assert.equal(parsed.seq, entry.seq);
63
+ assert.equal(parsed.eventType, entry.eventType);
64
+ } finally {
65
+ cleanup(base);
66
+ }
67
+ });
68
+
69
+ test("emitJournalEvent appends multiple lines to the same file", () => {
70
+ const base = makeTmpBase();
71
+ try {
72
+ emitJournalEvent(base, makeEntry({ seq: 0 }));
73
+ emitJournalEvent(base, makeEntry({ seq: 1, eventType: "dispatch-match" }));
74
+ emitJournalEvent(base, makeEntry({ seq: 2, eventType: "unit-start" }));
75
+
76
+ const filePath = join(base, ".gsd", "journal", "2025-03-21.jsonl");
77
+ const lines = readFileSync(filePath, "utf-8").trim().split("\n");
78
+ assert.equal(lines.length, 3, "Should have 3 lines");
79
+
80
+ const parsed = lines.map(l => JSON.parse(l));
81
+ assert.equal(parsed[0].seq, 0);
82
+ assert.equal(parsed[1].seq, 1);
83
+ assert.equal(parsed[2].seq, 2);
84
+ assert.equal(parsed[1].eventType, "dispatch-match");
85
+ } finally {
86
+ cleanup(base);
87
+ }
88
+ });
89
+
90
+ test("emitJournalEvent auto-creates nonexistent parent directory", () => {
91
+ const base = join(tmpdir(), `gsd-journal-test-${randomUUID()}`);
92
+ // Don't create .gsd/ — emitJournalEvent should handle it via mkdirSync recursive
93
+ try {
94
+ emitJournalEvent(base, makeEntry());
95
+ const filePath = join(base, ".gsd", "journal", "2025-03-21.jsonl");
96
+ assert.ok(existsSync(filePath), "File should exist even when parent dirs did not");
97
+ } finally {
98
+ cleanup(base);
99
+ }
100
+ });
101
+
102
+ test("emitJournalEvent preserves optional fields (rule, causedBy, data)", () => {
103
+ const base = makeTmpBase();
104
+ try {
105
+ const entry = makeEntry({
106
+ rule: "my-dispatch-rule",
107
+ causedBy: { flowId: "flow-prior", seq: 3 },
108
+ data: { unitId: "M001/S01/T01", status: "ok" },
109
+ });
110
+ emitJournalEvent(base, entry);
111
+
112
+ const filePath = join(base, ".gsd", "journal", "2025-03-21.jsonl");
113
+ const parsed = JSON.parse(readFileSync(filePath, "utf-8").trim());
114
+ assert.equal(parsed.rule, "my-dispatch-rule");
115
+ assert.deepEqual(parsed.causedBy, { flowId: "flow-prior", seq: 3 });
116
+ assert.equal(parsed.data.unitId, "M001/S01/T01");
117
+ assert.equal(parsed.data.status, "ok");
118
+ } finally {
119
+ cleanup(base);
120
+ }
121
+ });
122
+
123
+ test("emitJournalEvent silently catches write errors (no throw)", () => {
124
+ // Use a path that can't be created — null bytes in path
125
+ assert.doesNotThrow(() => {
126
+ emitJournalEvent("/dev/null/impossible\0path", makeEntry());
127
+ });
128
+ });
129
+
130
+ test("emitJournalEvent silently catches read-only directory errors", () => {
131
+ const base = makeTmpBase();
132
+ const journalDir = join(base, ".gsd", "journal");
133
+ mkdirSync(journalDir, { recursive: true });
134
+
135
+ try {
136
+ // Make the journal directory read-only
137
+ chmodSync(journalDir, 0o444);
138
+
139
+ // Should not throw
140
+ assert.doesNotThrow(() => {
141
+ emitJournalEvent(base, makeEntry());
142
+ });
143
+ } finally {
144
+ // Restore permissions for cleanup
145
+ try {
146
+ chmodSync(journalDir, 0o755);
147
+ } catch {
148
+ /* */
149
+ }
150
+ cleanup(base);
151
+ }
152
+ });
153
+
154
+ // ─── Daily Rotation ───────────────────────────────────────────────────────────
155
+
156
+ test("daily rotation: events with different dates go to different files", () => {
157
+ const base = makeTmpBase();
158
+ try {
159
+ emitJournalEvent(base, makeEntry({ ts: "2025-03-20T23:59:59.000Z" }));
160
+ emitJournalEvent(base, makeEntry({ ts: "2025-03-21T00:00:01.000Z" }));
161
+ emitJournalEvent(base, makeEntry({ ts: "2025-03-22T12:00:00.000Z" }));
162
+
163
+ const journalDir = join(base, ".gsd", "journal");
164
+ assert.ok(existsSync(join(journalDir, "2025-03-20.jsonl")));
165
+ assert.ok(existsSync(join(journalDir, "2025-03-21.jsonl")));
166
+ assert.ok(existsSync(join(journalDir, "2025-03-22.jsonl")));
167
+
168
+ // Verify each file has exactly one line
169
+ for (const date of ["2025-03-20", "2025-03-21", "2025-03-22"]) {
170
+ const lines = readFileSync(join(journalDir, `${date}.jsonl`), "utf-8")
171
+ .trim()
172
+ .split("\n");
173
+ assert.equal(lines.length, 1, `${date}.jsonl should have 1 line`);
174
+ }
175
+ } finally {
176
+ cleanup(base);
177
+ }
178
+ });
179
+
180
+ // ─── queryJournal ─────────────────────────────────────────────────────────────
181
+
182
+ test("queryJournal returns all entries when no filters provided", () => {
183
+ const base = makeTmpBase();
184
+ try {
185
+ emitJournalEvent(base, makeEntry({ seq: 0 }));
186
+ emitJournalEvent(base, makeEntry({ seq: 1, eventType: "dispatch-match" }));
187
+
188
+ const results = queryJournal(base);
189
+ assert.equal(results.length, 2);
190
+ assert.equal(results[0].seq, 0);
191
+ assert.equal(results[1].seq, 1);
192
+ } finally {
193
+ cleanup(base);
194
+ }
195
+ });
196
+
197
+ test("queryJournal filters by flowId", () => {
198
+ const base = makeTmpBase();
199
+ try {
200
+ emitJournalEvent(base, makeEntry({ flowId: "flow-aaa", seq: 0 }));
201
+ emitJournalEvent(base, makeEntry({ flowId: "flow-bbb", seq: 1 }));
202
+ emitJournalEvent(base, makeEntry({ flowId: "flow-aaa", seq: 2 }));
203
+
204
+ const results = queryJournal(base, { flowId: "flow-aaa" });
205
+ assert.equal(results.length, 2);
206
+ assert.ok(results.every(e => e.flowId === "flow-aaa"));
207
+ } finally {
208
+ cleanup(base);
209
+ }
210
+ });
211
+
212
+ test("queryJournal filters by eventType", () => {
213
+ const base = makeTmpBase();
214
+ try {
215
+ emitJournalEvent(base, makeEntry({ eventType: "iteration-start", seq: 0 }));
216
+ emitJournalEvent(base, makeEntry({ eventType: "dispatch-match", seq: 1 }));
217
+ emitJournalEvent(base, makeEntry({ eventType: "unit-start", seq: 2 }));
218
+ emitJournalEvent(base, makeEntry({ eventType: "dispatch-match", seq: 3 }));
219
+
220
+ const results = queryJournal(base, { eventType: "dispatch-match" });
221
+ assert.equal(results.length, 2);
222
+ assert.ok(results.every(e => e.eventType === "dispatch-match"));
223
+ } finally {
224
+ cleanup(base);
225
+ }
226
+ });
227
+
228
+ test("queryJournal filters by unitId (from data.unitId)", () => {
229
+ const base = makeTmpBase();
230
+ try {
231
+ emitJournalEvent(
232
+ base,
233
+ makeEntry({ seq: 0, data: { unitId: "M001/S01/T01" } }),
234
+ );
235
+ emitJournalEvent(
236
+ base,
237
+ makeEntry({ seq: 1, data: { unitId: "M001/S01/T02" } }),
238
+ );
239
+ emitJournalEvent(
240
+ base,
241
+ makeEntry({ seq: 2, data: { unitId: "M001/S01/T01" } }),
242
+ );
243
+ emitJournalEvent(base, makeEntry({ seq: 3 })); // no data
244
+
245
+ const results = queryJournal(base, { unitId: "M001/S01/T01" });
246
+ assert.equal(results.length, 2);
247
+ assert.ok(
248
+ results.every(
249
+ e => (e.data as Record<string, unknown>)?.unitId === "M001/S01/T01",
250
+ ),
251
+ );
252
+ } finally {
253
+ cleanup(base);
254
+ }
255
+ });
256
+
257
+ test("queryJournal filters by time range (after/before)", () => {
258
+ const base = makeTmpBase();
259
+ try {
260
+ emitJournalEvent(base, makeEntry({ ts: "2025-03-20T08:00:00.000Z", seq: 0 }));
261
+ emitJournalEvent(base, makeEntry({ ts: "2025-03-21T10:00:00.000Z", seq: 1 }));
262
+ emitJournalEvent(base, makeEntry({ ts: "2025-03-21T15:00:00.000Z", seq: 2 }));
263
+ emitJournalEvent(base, makeEntry({ ts: "2025-03-22T20:00:00.000Z", seq: 3 }));
264
+
265
+ // After only
266
+ const afterResults = queryJournal(base, { after: "2025-03-21T00:00:00.000Z" });
267
+ assert.equal(afterResults.length, 3, "3 entries on or after 2025-03-21");
268
+
269
+ // Before only
270
+ const beforeResults = queryJournal(base, { before: "2025-03-21T12:00:00.000Z" });
271
+ assert.equal(beforeResults.length, 2, "2 entries on or before noon on 03-21");
272
+
273
+ // Both after and before
274
+ const rangeResults = queryJournal(base, {
275
+ after: "2025-03-21T00:00:00.000Z",
276
+ before: "2025-03-21T23:59:59.000Z",
277
+ });
278
+ assert.equal(rangeResults.length, 2, "2 entries within 2025-03-21");
279
+ } finally {
280
+ cleanup(base);
281
+ }
282
+ });
283
+
284
+ test("queryJournal combines multiple filters", () => {
285
+ const base = makeTmpBase();
286
+ try {
287
+ emitJournalEvent(
288
+ base,
289
+ makeEntry({ flowId: "flow-aaa", eventType: "unit-start", seq: 0 }),
290
+ );
291
+ emitJournalEvent(
292
+ base,
293
+ makeEntry({ flowId: "flow-aaa", eventType: "dispatch-match", seq: 1 }),
294
+ );
295
+ emitJournalEvent(
296
+ base,
297
+ makeEntry({ flowId: "flow-bbb", eventType: "unit-start", seq: 2 }),
298
+ );
299
+
300
+ const results = queryJournal(base, {
301
+ flowId: "flow-aaa",
302
+ eventType: "unit-start",
303
+ });
304
+ assert.equal(results.length, 1);
305
+ assert.equal(results[0].flowId, "flow-aaa");
306
+ assert.equal(results[0].eventType, "unit-start");
307
+ } finally {
308
+ cleanup(base);
309
+ }
310
+ });
311
+
312
+ test("queryJournal on nonexistent directory returns empty array", () => {
313
+ const base = join(tmpdir(), `gsd-journal-test-${randomUUID()}`);
314
+ // Don't create anything
315
+ try {
316
+ const results = queryJournal(base);
317
+ assert.deepEqual(results, []);
318
+ } finally {
319
+ cleanup(base);
320
+ }
321
+ });
322
+
323
+ test("queryJournal skips malformed JSON lines gracefully", () => {
324
+ const base = makeTmpBase();
325
+ try {
326
+ const journalDir = join(base, ".gsd", "journal");
327
+ mkdirSync(journalDir, { recursive: true });
328
+
329
+ // Write a file with a mix of valid and invalid lines
330
+ const validEntry = JSON.stringify(makeEntry({ seq: 0 }));
331
+ const content = `${validEntry}\n{not valid json\n${JSON.stringify(makeEntry({ seq: 1 }))}\n`;
332
+ writeFileSync(join(journalDir, "2025-03-21.jsonl"), content);
333
+
334
+ const results = queryJournal(base);
335
+ assert.equal(results.length, 2, "Should skip the malformed line");
336
+ assert.equal(results[0].seq, 0);
337
+ assert.equal(results[1].seq, 1);
338
+ } finally {
339
+ cleanup(base);
340
+ }
341
+ });
342
+
343
+ test("queryJournal reads across multiple daily files", () => {
344
+ const base = makeTmpBase();
345
+ try {
346
+ emitJournalEvent(base, makeEntry({ ts: "2025-03-20T12:00:00.000Z", seq: 0 }));
347
+ emitJournalEvent(base, makeEntry({ ts: "2025-03-21T12:00:00.000Z", seq: 1 }));
348
+ emitJournalEvent(base, makeEntry({ ts: "2025-03-22T12:00:00.000Z", seq: 2 }));
349
+
350
+ const results = queryJournal(base);
351
+ assert.equal(results.length, 3, "Should read from all 3 files");
352
+ // Files are sorted, so order should be chronological
353
+ assert.equal(results[0].ts, "2025-03-20T12:00:00.000Z");
354
+ assert.equal(results[1].ts, "2025-03-21T12:00:00.000Z");
355
+ assert.equal(results[2].ts, "2025-03-22T12:00:00.000Z");
356
+ } finally {
357
+ cleanup(base);
358
+ }
359
+ });
360
+
361
+ test("queryJournal filters by rule", () => {
362
+ const base = makeTmpBase();
363
+ try {
364
+ emitJournalEvent(
365
+ base,
366
+ makeEntry({ seq: 0, eventType: "dispatch-match", rule: "dispatch-task" }),
367
+ );
368
+ emitJournalEvent(
369
+ base,
370
+ makeEntry({ seq: 1, eventType: "post-unit-hook", rule: "post-unit-hook" }),
371
+ );
372
+ emitJournalEvent(
373
+ base,
374
+ makeEntry({ seq: 2, eventType: "dispatch-match", rule: "dispatch-task" }),
375
+ );
376
+
377
+ const results = queryJournal(base, { rule: "dispatch-task" });
378
+ assert.equal(results.length, 2, "Should return only dispatch-task entries");
379
+ assert.ok(
380
+ results.every(e => e.rule === "dispatch-task"),
381
+ "All results should have rule === 'dispatch-task'",
382
+ );
383
+ } finally {
384
+ cleanup(base);
385
+ }
386
+ });
@@ -187,6 +187,36 @@ console.log('=== md-importer: malformed/empty rows skipped ===');
187
187
  assertEq(decisions[1].id, 'D003', 'second valid row (skipping malformed)');
188
188
  }
189
189
 
190
+ console.log('=== md-importer: made_by backward compatibility (old 7-column format) ===');
191
+
192
+ {
193
+ const decisions = parseDecisionsTable(DECISIONS_MD);
194
+ // Old format has no Made By column — should default to 'agent'
195
+ for (const d of decisions) {
196
+ assertEq(d.made_by, 'agent', `${d.id} made_by defaults to agent for legacy format`);
197
+ }
198
+ }
199
+
200
+ console.log('=== md-importer: made_by column parsing (new 8-column format) ===');
201
+
202
+ {
203
+ const newFormatMd = `# Decisions Register
204
+
205
+ | # | When | Scope | Decision | Choice | Rationale | Revisable? | Made By |
206
+ |---|------|-------|----------|--------|-----------|------------|---------|
207
+ | D001 | M001 | library | SQLite library | better-sqlite3 | Sync API | No | human |
208
+ | D002 | M001 | arch | DB location | .gsd/gsd.db | Derived state | No | agent |
209
+ | D003 | M002 | impl | Config format | JSON | Simple | Yes | collaborative |
210
+ | D004 | M002 | impl | Cache strategy | LRU | Predictable | No | bogus |
211
+ `;
212
+ const decisions = parseDecisionsTable(newFormatMd);
213
+ assertEq(decisions.length, 4, 'should parse 4 decisions with new format');
214
+ assertEq(decisions[0].made_by, 'human', 'D001 made_by = human');
215
+ assertEq(decisions[1].made_by, 'agent', 'D002 made_by = agent');
216
+ assertEq(decisions[2].made_by, 'collaborative', 'D003 made_by = collaborative');
217
+ assertEq(decisions[3].made_by, 'agent', 'D004 invalid made_by defaults to agent');
218
+ }
219
+
190
220
  // ═══════════════════════════════════════════════════════════════════════════
191
221
  // md-importer: parseRequirementsSections
192
222
  // ═══════════════════════════════════════════════════════════════════════════
@@ -354,7 +384,7 @@ console.log('=== md-importer: schema v1→v2 migration ===');
354
384
  openDatabase(':memory:');
355
385
  const adapter = _getAdapter();
356
386
  const version = adapter?.prepare('SELECT MAX(version) as v FROM schema_version').get();
357
- assertEq(version?.v, 3, 'new DB should be at schema version 3');
387
+ assertEq(version?.v, 4, 'new DB should be at schema version 4');
358
388
 
359
389
  // Artifacts table should exist
360
390
  const tableCheck = adapter?.prepare("SELECT count(*) as c FROM sqlite_master WHERE type='table' AND name='artifacts'").get();
@@ -335,9 +335,9 @@ console.log('\n=== memory-store: schema includes memories table ===');
335
335
  const viewCount = adapter.prepare('SELECT count(*) as cnt FROM active_memories').get();
336
336
  assertEq(viewCount?.['cnt'], 0, 'active_memories view should exist');
337
337
 
338
- // Verify schema version is 3
338
+ // Verify schema version is 4
339
339
  const version = adapter.prepare('SELECT MAX(version) as v FROM schema_version').get();
340
- assertEq(version?.['v'], 3, 'schema version should be 3');
340
+ assertEq(version?.['v'], 4, 'schema version should be 4');
341
341
 
342
342
  closeDatabase();
343
343
  }
@@ -1,5 +1,5 @@
1
1
  // milestone-id-reservation — Verifies that preview IDs from guided-flow
2
- // match the IDs claimed by gsd_generate_milestone_id via the shared
2
+ // match the IDs claimed by gsd_milestone_generate_id via the shared
3
3
  // reservation mechanism in milestone-ids.ts.
4
4
  //
5
5
  // Regression test for #1569.
@@ -652,6 +652,116 @@ console.log('\n=== parsePlan: new-format task entries with Files and Verify subl
652
652
  assertTrue(p.tasks[0].description.includes('Why: because we need typed plan entries'), 'Why line accumulates into description');
653
653
  }
654
654
 
655
+ console.log('\n=== parsePlan: heading-style task entries (### T01 -- Title) ===');
656
+ {
657
+ const content = `# S11: Heading Style
658
+
659
+ **Goal:** Test heading-style task parsing.
660
+ **Demo:** Parser handles heading-style task entries.
661
+
662
+ ## Tasks
663
+
664
+ ### T01 -- Implement feature
665
+
666
+ - Why: the feature is needed
667
+ - Files: \`src/feature.ts\`
668
+ - Verify: npm test
669
+
670
+ ### T02 -- Write tests \`est:1h\`
671
+
672
+ Some description for the second task.
673
+ `;
674
+
675
+ const p = parsePlan(content);
676
+ assertEq(p.tasks.length, 2, 'heading-style task count');
677
+ assertEq(p.tasks[0].id, 'T01', 'heading T01 id');
678
+ assertEq(p.tasks[0].title, 'Implement feature', 'heading T01 title');
679
+ assertEq(p.tasks[0].done, false, 'heading T01 not done (headings have no checkbox)');
680
+ assertEq(p.tasks[0].files![0], 'src/feature.ts', 'heading T01 files extracted');
681
+ assertEq(p.tasks[0].verify, 'npm test', 'heading T01 verify extracted');
682
+ assertEq(p.tasks[1].id, 'T02', 'heading T02 id');
683
+ assertEq(p.tasks[1].title, 'Write tests', 'heading T02 title');
684
+ assertEq(p.tasks[1].estimate, '1h', 'heading T02 estimate');
685
+ assertTrue(p.tasks[1].description.includes('Some description'), 'heading T02 description');
686
+ }
687
+
688
+ console.log('\n=== parsePlan: heading-style with colon separator (### T01: Title) ===');
689
+ {
690
+ const content = `# S12: Heading Colon Style
691
+
692
+ **Goal:** Test colon-separated heading tasks.
693
+ **Demo:** Parser handles colon separator.
694
+
695
+ ## Tasks
696
+
697
+ ### T01: Setup project
698
+ Basic project setup steps.
699
+
700
+ ### T02: Add CI pipeline \`est:30m\`
701
+ Configure CI.
702
+ `;
703
+
704
+ const p = parsePlan(content);
705
+ assertEq(p.tasks.length, 2, 'colon heading task count');
706
+ assertEq(p.tasks[0].id, 'T01', 'colon heading T01 id');
707
+ assertEq(p.tasks[0].title, 'Setup project', 'colon heading T01 title');
708
+ assertEq(p.tasks[1].id, 'T02', 'colon heading T02 id');
709
+ assertEq(p.tasks[1].title, 'Add CI pipeline', 'colon heading T02 title');
710
+ assertEq(p.tasks[1].estimate, '30m', 'colon heading T02 estimate');
711
+ }
712
+
713
+ console.log('\n=== parsePlan: heading-style with em-dash separator (### T01 — Title) ===');
714
+ {
715
+ const content = `# S13: Em-Dash Style
716
+
717
+ **Goal:** Test em-dash separated heading tasks.
718
+ **Demo:** Parser handles em-dash separator.
719
+
720
+ ## Tasks
721
+
722
+ ### T01 — Build the widget
723
+
724
+ Widget description.
725
+ `;
726
+
727
+ const p = parsePlan(content);
728
+ assertEq(p.tasks.length, 1, 'em-dash heading task count');
729
+ assertEq(p.tasks[0].id, 'T01', 'em-dash heading T01 id');
730
+ assertEq(p.tasks[0].title, 'Build the widget', 'em-dash heading T01 title');
731
+ }
732
+
733
+ console.log('\n=== parsePlan: mixed checkbox and heading-style tasks ===');
734
+ {
735
+ const content = `# S14: Mixed Format
736
+
737
+ **Goal:** Test mixed formats.
738
+ **Demo:** Parser handles both styles in one plan.
739
+
740
+ ## Tasks
741
+
742
+ - [ ] **T01: Checkbox task** \`est:20m\`
743
+ A checkbox-style task.
744
+
745
+ ### T02 -- Heading task \`est:15m\`
746
+
747
+ A heading-style task.
748
+
749
+ - [x] **T03: Done checkbox task** \`est:10m\`
750
+ Already completed.
751
+ `;
752
+
753
+ const p = parsePlan(content);
754
+ assertEq(p.tasks.length, 3, 'mixed format task count');
755
+ assertEq(p.tasks[0].id, 'T01', 'mixed T01 id');
756
+ assertEq(p.tasks[0].done, false, 'mixed T01 not done');
757
+ assertEq(p.tasks[1].id, 'T02', 'mixed T02 id');
758
+ assertEq(p.tasks[1].title, 'Heading task', 'mixed T02 title');
759
+ assertEq(p.tasks[1].estimate, '15m', 'mixed T02 estimate');
760
+ assertEq(p.tasks[1].done, false, 'mixed T02 not done (heading style)');
761
+ assertEq(p.tasks[2].id, 'T03', 'mixed T03 id');
762
+ assertEq(p.tasks[2].done, true, 'mixed T03 done');
763
+ }
764
+
655
765
  // ═══════════════════════════════════════════════════════════════════════════
656
766
  // parseSummary tests
657
767
  // ═══════════════════════════════════════════════════════════════════════════