gsd-pi 2.44.0-dev.62b5d6c → 2.44.0-dev.73f2fd5

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 (326) hide show
  1. package/README.md +30 -12
  2. package/dist/resources/extensions/gsd/auto/infra-errors.js +3 -0
  3. package/dist/resources/extensions/gsd/auto/phases.js +36 -36
  4. package/dist/resources/extensions/gsd/auto-prompts.js +24 -1
  5. package/dist/resources/extensions/gsd/auto-start.js +10 -0
  6. package/dist/resources/extensions/gsd/auto-timers.js +57 -3
  7. package/dist/resources/extensions/gsd/auto-worktree-sync.js +4 -0
  8. package/dist/resources/extensions/gsd/auto-worktree.js +9 -6
  9. package/dist/resources/extensions/gsd/auto.js +30 -3
  10. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +156 -0
  11. package/dist/resources/extensions/gsd/bootstrap/system-context.js +46 -12
  12. package/dist/resources/extensions/gsd/commands/catalog.js +6 -1
  13. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
  14. package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
  15. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +5 -0
  16. package/dist/resources/extensions/gsd/commands-mcp-status.js +187 -0
  17. package/dist/resources/extensions/gsd/db-writer.js +34 -16
  18. package/dist/resources/extensions/gsd/doctor.js +8 -0
  19. package/dist/resources/extensions/gsd/git-service.js +8 -3
  20. package/dist/resources/extensions/gsd/gsd-db.js +12 -1
  21. package/dist/resources/extensions/gsd/markdown-renderer.js +1 -1
  22. package/dist/resources/extensions/gsd/preferences.js +9 -1
  23. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -4
  24. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  25. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -6
  26. package/dist/resources/extensions/gsd/prompts/replan-slice.md +3 -14
  27. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +7 -37
  28. package/dist/resources/extensions/gsd/provider-error-pause.js +7 -0
  29. package/dist/resources/extensions/gsd/state.js +19 -2
  30. package/dist/resources/extensions/gsd/tools/plan-slice.js +1 -0
  31. package/dist/resources/extensions/gsd/tools/plan-task.js +1 -0
  32. package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -0
  33. package/dist/resources/extensions/gsd/tools/validate-milestone.js +88 -0
  34. package/dist/resources/extensions/gsd/worktree-resolver.js +6 -0
  35. package/dist/resources/extensions/mcp-client/index.js +14 -0
  36. package/dist/web/standalone/.next/BUILD_ID +1 -1
  37. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  38. package/dist/web/standalone/.next/build-manifest.json +2 -2
  39. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  40. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  41. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/index.html +1 -1
  57. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  64. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  65. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  66. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  67. package/package.json +1 -1
  68. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +3 -1
  69. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  70. package/packages/pi-coding-agent/dist/core/auth-storage.js +15 -1
  71. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  72. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +6 -8
  73. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  74. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +24 -26
  75. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  76. package/packages/pi-coding-agent/dist/core/fs-utils.test.js +29 -48
  77. package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -1
  78. package/packages/pi-coding-agent/dist/core/local-model-check.d.ts +15 -0
  79. package/packages/pi-coding-agent/dist/core/local-model-check.d.ts.map +1 -0
  80. package/packages/pi-coding-agent/dist/core/local-model-check.js +41 -0
  81. package/packages/pi-coding-agent/dist/core/local-model-check.js.map +1 -0
  82. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +11 -0
  83. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  84. package/packages/pi-coding-agent/dist/core/model-registry.js +20 -1
  85. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  86. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +34 -44
  87. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
  88. package/packages/pi-coding-agent/dist/core/session-manager.test.js +30 -34
  89. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  90. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  91. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  92. package/packages/pi-coding-agent/dist/core/settings-manager.js +6 -0
  93. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +10 -12
  95. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
  96. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  97. package/packages/pi-coding-agent/dist/main.js +17 -0
  98. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  99. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.d.ts +2 -0
  100. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.d.ts.map +1 -0
  101. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js +32 -0
  102. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js.map +1 -0
  103. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  104. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +8 -1
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  108. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  109. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +12 -0
  110. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts +15 -0
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts.map +1 -0
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js +40 -0
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js.map +1 -0
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +4 -1
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +5 -2
  119. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +13 -2
  121. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +17 -8
  124. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  126. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +7 -3
  127. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  128. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +43 -47
  129. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js.map +1 -1
  130. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +7 -7
  131. package/packages/pi-coding-agent/src/core/auth-storage.ts +15 -1
  132. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +26 -26
  133. package/packages/pi-coding-agent/src/core/fs-utils.test.ts +31 -43
  134. package/packages/pi-coding-agent/src/core/local-model-check.ts +45 -0
  135. package/packages/pi-coding-agent/src/core/model-registry.ts +21 -1
  136. package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +40 -45
  137. package/packages/pi-coding-agent/src/core/session-manager.test.ts +33 -33
  138. package/packages/pi-coding-agent/src/core/settings-manager.ts +9 -0
  139. package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +17 -17
  140. package/packages/pi-coding-agent/src/main.ts +19 -0
  141. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/timestamp.test.ts +38 -0
  142. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +10 -0
  143. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +15 -0
  144. package/packages/pi-coding-agent/src/modes/interactive/components/timestamp.ts +48 -0
  145. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +3 -1
  146. package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +18 -3
  147. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +16 -7
  148. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +8 -1
  149. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +74 -74
  150. package/src/resources/extensions/gsd/auto/infra-errors.ts +3 -0
  151. package/src/resources/extensions/gsd/auto/phases.ts +45 -48
  152. package/src/resources/extensions/gsd/auto-prompts.ts +24 -1
  153. package/src/resources/extensions/gsd/auto-start.ts +14 -0
  154. package/src/resources/extensions/gsd/auto-timers.ts +64 -3
  155. package/src/resources/extensions/gsd/auto-worktree-sync.ts +5 -0
  156. package/src/resources/extensions/gsd/auto-worktree.ts +9 -6
  157. package/src/resources/extensions/gsd/auto.ts +37 -3
  158. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +148 -0
  159. package/src/resources/extensions/gsd/bootstrap/system-context.ts +48 -11
  160. package/src/resources/extensions/gsd/commands/catalog.ts +6 -1
  161. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
  162. package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
  163. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +8 -0
  164. package/src/resources/extensions/gsd/commands-mcp-status.ts +247 -0
  165. package/src/resources/extensions/gsd/db-writer.ts +39 -17
  166. package/src/resources/extensions/gsd/doctor.ts +7 -1
  167. package/src/resources/extensions/gsd/git-service.ts +6 -2
  168. package/src/resources/extensions/gsd/gsd-db.ts +16 -1
  169. package/src/resources/extensions/gsd/markdown-renderer.ts +1 -1
  170. package/src/resources/extensions/gsd/preferences.ts +11 -1
  171. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -4
  172. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  173. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -6
  174. package/src/resources/extensions/gsd/prompts/replan-slice.md +3 -14
  175. package/src/resources/extensions/gsd/prompts/validate-milestone.md +7 -37
  176. package/src/resources/extensions/gsd/provider-error-pause.ts +9 -0
  177. package/src/resources/extensions/gsd/state.ts +19 -1
  178. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +99 -99
  179. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +14 -16
  180. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +43 -57
  181. package/src/resources/extensions/gsd/tests/auto-pr-bugs.test.ts +88 -0
  182. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +11 -13
  183. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +465 -523
  184. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +73 -75
  185. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +34 -56
  186. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +533 -656
  187. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +165 -143
  188. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +29 -52
  189. package/src/resources/extensions/gsd/tests/captures.test.ts +148 -176
  190. package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +32 -33
  191. package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +141 -143
  192. package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +25 -25
  193. package/src/resources/extensions/gsd/tests/commands-logs.test.ts +81 -81
  194. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +38 -59
  195. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +228 -263
  196. package/src/resources/extensions/gsd/tests/complete-task.test.ts +250 -302
  197. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +114 -0
  198. package/src/resources/extensions/gsd/tests/context-store.test.ts +354 -367
  199. package/src/resources/extensions/gsd/tests/continue-here.test.ts +68 -72
  200. package/src/resources/extensions/gsd/tests/cost-projection.test.ts +92 -106
  201. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +27 -35
  202. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +220 -237
  203. package/src/resources/extensions/gsd/tests/db-writer.test.ts +465 -416
  204. package/src/resources/extensions/gsd/tests/definition-loader.test.ts +76 -92
  205. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +68 -83
  206. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +210 -181
  207. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +78 -101
  208. package/src/resources/extensions/gsd/tests/derive-state.test.ts +192 -227
  209. package/src/resources/extensions/gsd/tests/detection.test.ts +232 -278
  210. package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +30 -34
  211. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +164 -180
  212. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +43 -49
  213. package/src/resources/extensions/gsd/tests/dispatch-uat-last-completed.test.ts +28 -32
  214. package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +27 -29
  215. package/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts +34 -38
  216. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +54 -75
  217. package/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts +21 -32
  218. package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +72 -97
  219. package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +38 -44
  220. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +104 -145
  221. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +84 -106
  222. package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +54 -60
  223. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +72 -93
  224. package/src/resources/extensions/gsd/tests/doctor.test.ts +104 -134
  225. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +123 -131
  226. package/src/resources/extensions/gsd/tests/est-annotation-timeout.test.ts +120 -0
  227. package/src/resources/extensions/gsd/tests/exit-command.test.ts +20 -24
  228. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +48 -57
  229. package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +5 -7
  230. package/src/resources/extensions/gsd/tests/flag-file-db.test.ts +30 -42
  231. package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +198 -206
  232. package/src/resources/extensions/gsd/tests/git-locale.test.ts +13 -27
  233. package/src/resources/extensions/gsd/tests/git-service.test.ts +285 -388
  234. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +31 -39
  235. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +63 -69
  236. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +255 -264
  237. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +108 -119
  238. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +81 -103
  239. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +229 -262
  240. package/src/resources/extensions/gsd/tests/headless-answers.test.ts +13 -13
  241. package/src/resources/extensions/gsd/tests/health-widget.test.ts +29 -37
  242. package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +81 -102
  243. package/src/resources/extensions/gsd/tests/infra-error.test.ts +20 -2
  244. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +16 -18
  245. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +41 -46
  246. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +42 -53
  247. package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +75 -91
  248. package/src/resources/extensions/gsd/tests/integration-proof.test.ts +18 -18
  249. package/src/resources/extensions/gsd/tests/knowledge.test.ts +89 -0
  250. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +150 -194
  251. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +103 -0
  252. package/src/resources/extensions/gsd/tests/md-importer.test.ts +101 -125
  253. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +45 -54
  254. package/src/resources/extensions/gsd/tests/memory-store.test.ts +80 -93
  255. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +66 -0
  256. package/src/resources/extensions/gsd/tests/migrate-command.test.ts +57 -66
  257. package/src/resources/extensions/gsd/tests/migrate-hierarchy.test.ts +83 -93
  258. package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +161 -170
  259. package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +125 -141
  260. package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +107 -131
  261. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +87 -96
  262. package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +125 -164
  263. package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +81 -94
  264. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +35 -36
  265. package/src/resources/extensions/gsd/tests/overrides.test.ts +99 -106
  266. package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +40 -47
  267. package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +25 -28
  268. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +66 -83
  269. package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +54 -77
  270. package/src/resources/extensions/gsd/tests/park-milestone.test.ts +68 -115
  271. package/src/resources/extensions/gsd/tests/parsers.test.ts +546 -611
  272. package/src/resources/extensions/gsd/tests/paths.test.ts +72 -87
  273. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +77 -117
  274. package/src/resources/extensions/gsd/tests/preferences.test.ts +27 -0
  275. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +11 -7
  276. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +56 -56
  277. package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +93 -119
  278. package/src/resources/extensions/gsd/tests/queue-order.test.ts +70 -82
  279. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +42 -55
  280. package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +100 -0
  281. package/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts +45 -73
  282. package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +28 -38
  283. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +73 -80
  284. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +71 -74
  285. package/src/resources/extensions/gsd/tests/requirements.test.ts +70 -75
  286. package/src/resources/extensions/gsd/tests/retry-state-reset.test.ts +44 -66
  287. package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +114 -181
  288. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +63 -65
  289. package/src/resources/extensions/gsd/tests/run-uat.test.ts +66 -128
  290. package/src/resources/extensions/gsd/tests/session-lock-multipath.test.ts +18 -25
  291. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +37 -44
  292. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +19 -26
  293. package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +63 -0
  294. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +6 -8
  295. package/src/resources/extensions/gsd/tests/stop-auto-merge-back.test.ts +67 -0
  296. package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +22 -28
  297. package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +49 -0
  298. package/src/resources/extensions/gsd/tests/token-savings.test.ts +54 -56
  299. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +23 -25
  300. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +10 -11
  301. package/src/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +66 -82
  302. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +46 -47
  303. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -22
  304. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +84 -86
  305. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +41 -43
  306. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +94 -96
  307. package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +11 -13
  308. package/src/resources/extensions/gsd/tests/worker-registry.test.ts +27 -29
  309. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +50 -52
  310. package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +10 -13
  311. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +14 -18
  312. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +38 -39
  313. package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +17 -21
  314. package/src/resources/extensions/gsd/tests/worktree-health.test.ts +25 -30
  315. package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +30 -37
  316. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +15 -22
  317. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +59 -66
  318. package/src/resources/extensions/gsd/tests/worktree.test.ts +44 -50
  319. package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -0
  320. package/src/resources/extensions/gsd/tools/plan-task.ts +2 -0
  321. package/src/resources/extensions/gsd/tools/replan-slice.ts +3 -0
  322. package/src/resources/extensions/gsd/tools/validate-milestone.ts +127 -0
  323. package/src/resources/extensions/gsd/worktree-resolver.ts +7 -0
  324. package/src/resources/extensions/mcp-client/index.ts +20 -0
  325. /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → kxxAA66bah_yhPYqLBHE2}/_buildManifest.js +0 -0
  326. /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → kxxAA66bah_yhPYqLBHE2}/_ssgManifest.js +0 -0
@@ -1,14 +1,14 @@
1
+ import { describe, test } from 'node:test';
2
+ import assert from 'node:assert/strict';
1
3
  import { parseRoadmap, parsePlan } from '../parsers-legacy.ts';
2
4
  import { parseTaskPlanFile, parseSummary, parseContinue, parseRequirementCounts, parseSecretsManifest, formatSecretsManifest } from '../files.ts';
3
- import { createTestContext } from './test-helpers.ts';
4
-
5
- const { assertEq, assertTrue, report } = createTestContext();
6
5
  // ═══════════════════════════════════════════════════════════════════════════
7
6
  // parseRoadmap tests
8
7
  // ═══════════════════════════════════════════════════════════════════════════
9
8
 
10
- console.log('\n=== parseRoadmap: full roadmap ===');
11
- {
9
+
10
+ describe('parsers', () => {
11
+ test('parseRoadmap: full roadmap', () => {
12
12
  const content = `# M001: GSD Extension — Hierarchical Planning
13
13
 
14
14
  **Vision:** Build a structured planning system for coding agents.
@@ -57,44 +57,43 @@ Consumes from S03:
57
57
 
58
58
  const r = parseRoadmap(content);
59
59
 
60
- assertEq(r.title, 'M001: GSD Extension — Hierarchical Planning', 'roadmap title');
61
- assertEq(r.vision, 'Build a structured planning system for coding agents.', 'roadmap vision');
62
- assertEq(r.successCriteria.length, 3, 'success criteria count');
63
- assertEq(r.successCriteria[0], 'All parsers have test coverage', 'first success criterion');
64
- assertEq(r.successCriteria[2], 'State derivation works correctly', 'third success criterion');
60
+ assert.deepStrictEqual(r.title, 'M001: GSD Extension — Hierarchical Planning', 'roadmap title');
61
+ assert.deepStrictEqual(r.vision, 'Build a structured planning system for coding agents.', 'roadmap vision');
62
+ assert.deepStrictEqual(r.successCriteria.length, 3, 'success criteria count');
63
+ assert.deepStrictEqual(r.successCriteria[0], 'All parsers have test coverage', 'first success criterion');
64
+ assert.deepStrictEqual(r.successCriteria[2], 'State derivation works correctly', 'third success criterion');
65
65
 
66
66
  // Slices
67
- assertEq(r.slices.length, 3, 'slice count');
68
-
69
- assertEq(r.slices[0].id, 'S01', 'S01 id');
70
- assertEq(r.slices[0].title, 'Types + File I/O', 'S01 title');
71
- assertEq(r.slices[0].risk, 'low', 'S01 risk');
72
- assertEq(r.slices[0].depends, [], 'S01 depends');
73
- assertEq(r.slices[0].done, true, 'S01 done');
74
- assertEq(r.slices[0].demo, 'All types defined and parsers work.', 'S01 demo');
75
-
76
- assertEq(r.slices[1].id, 'S02', 'S02 id');
77
- assertEq(r.slices[1].title, 'State Derivation', 'S02 title');
78
- assertEq(r.slices[1].risk, 'medium', 'S02 risk');
79
- assertEq(r.slices[1].depends, ['S01'], 'S02 depends');
80
- assertEq(r.slices[1].done, false, 'S02 done');
81
-
82
- assertEq(r.slices[2].id, 'S03', 'S03 id');
83
- assertEq(r.slices[2].risk, 'high', 'S03 risk');
84
- assertEq(r.slices[2].depends, ['S01', 'S02'], 'S03 depends');
85
- assertEq(r.slices[2].done, false, 'S03 done');
67
+ assert.deepStrictEqual(r.slices.length, 3, 'slice count');
68
+
69
+ assert.deepStrictEqual(r.slices[0].id, 'S01', 'S01 id');
70
+ assert.deepStrictEqual(r.slices[0].title, 'Types + File I/O', 'S01 title');
71
+ assert.deepStrictEqual(r.slices[0].risk, 'low', 'S01 risk');
72
+ assert.deepStrictEqual(r.slices[0].depends, [], 'S01 depends');
73
+ assert.deepStrictEqual(r.slices[0].done, true, 'S01 done');
74
+ assert.deepStrictEqual(r.slices[0].demo, 'All types defined and parsers work.', 'S01 demo');
75
+
76
+ assert.deepStrictEqual(r.slices[1].id, 'S02', 'S02 id');
77
+ assert.deepStrictEqual(r.slices[1].title, 'State Derivation', 'S02 title');
78
+ assert.deepStrictEqual(r.slices[1].risk, 'medium', 'S02 risk');
79
+ assert.deepStrictEqual(r.slices[1].depends, ['S01'], 'S02 depends');
80
+ assert.deepStrictEqual(r.slices[1].done, false, 'S02 done');
81
+
82
+ assert.deepStrictEqual(r.slices[2].id, 'S03', 'S03 id');
83
+ assert.deepStrictEqual(r.slices[2].risk, 'high', 'S03 risk');
84
+ assert.deepStrictEqual(r.slices[2].depends, ['S01', 'S02'], 'S03 depends');
85
+ assert.deepStrictEqual(r.slices[2].done, false, 'S03 done');
86
86
 
87
87
  // Boundary map
88
- assertEq(r.boundaryMap.length, 2, 'boundary map entry count');
89
- assertEq(r.boundaryMap[0].fromSlice, 'S01', 'bm[0] from');
90
- assertEq(r.boundaryMap[0].toSlice, 'S02', 'bm[0] to');
91
- assertTrue(r.boundaryMap[0].produces.includes('types.ts'), 'bm[0] produces mentions types.ts');
92
- assertEq(r.boundaryMap[1].fromSlice, 'S02', 'bm[1] from');
93
- assertEq(r.boundaryMap[1].toSlice, 'S03', 'bm[1] to');
94
- }
95
-
96
- console.log('\n=== parseRoadmap: empty slices section ===');
97
- {
88
+ assert.deepStrictEqual(r.boundaryMap.length, 2, 'boundary map entry count');
89
+ assert.deepStrictEqual(r.boundaryMap[0].fromSlice, 'S01', 'bm[0] from');
90
+ assert.deepStrictEqual(r.boundaryMap[0].toSlice, 'S02', 'bm[0] to');
91
+ assert.ok(r.boundaryMap[0].produces.includes('types.ts'), 'bm[0] produces mentions types.ts');
92
+ assert.deepStrictEqual(r.boundaryMap[1].fromSlice, 'S02', 'bm[1] from');
93
+ assert.deepStrictEqual(r.boundaryMap[1].toSlice, 'S03', 'bm[1] to');
94
+ });
95
+
96
+ test('parseRoadmap: empty slices section', () => {
98
97
  const content = `# M002: Empty Milestone
99
98
 
100
99
  **Vision:** Nothing yet.
@@ -105,13 +104,12 @@ console.log('\n=== parseRoadmap: empty slices section ===');
105
104
  `;
106
105
 
107
106
  const r = parseRoadmap(content);
108
- assertEq(r.title, 'M002: Empty Milestone', 'title with empty slices');
109
- assertEq(r.slices.length, 0, 'no slices parsed');
110
- assertEq(r.boundaryMap.length, 0, 'no boundary map entries');
111
- }
107
+ assert.deepStrictEqual(r.title, 'M002: Empty Milestone', 'title with empty slices');
108
+ assert.deepStrictEqual(r.slices.length, 0, 'no slices parsed');
109
+ assert.deepStrictEqual(r.boundaryMap.length, 0, 'no boundary map entries');
110
+ });
112
111
 
113
- console.log('\n=== parseRoadmap: malformed checkbox lines ===');
114
- {
112
+ test('parseRoadmap: malformed checkbox lines', () => {
115
113
  // Lines that don't match the expected bold pattern should be skipped
116
114
  const content = `# M003: Malformed
117
115
 
@@ -130,15 +128,14 @@ console.log('\n=== parseRoadmap: malformed checkbox lines ===');
130
128
 
131
129
  const r = parseRoadmap(content);
132
130
  // Only S02 and S03 should be parsed (malformed lines without bold markers are skipped)
133
- assertEq(r.slices.length, 2, 'only valid slices parsed from malformed input');
134
- assertEq(r.slices[0].id, 'S02', 'first valid slice is S02');
135
- assertEq(r.slices[0].done, true, 'S02 done');
136
- assertEq(r.slices[1].id, 'S03', 'second valid slice is S03');
137
- assertEq(r.slices[1].depends, ['S02'], 'S03 depends on S02');
138
- }
139
-
140
- console.log('\n=== parseRoadmap: lowercase vs uppercase X for done ===');
141
- {
131
+ assert.deepStrictEqual(r.slices.length, 2, 'only valid slices parsed from malformed input');
132
+ assert.deepStrictEqual(r.slices[0].id, 'S02', 'first valid slice is S02');
133
+ assert.deepStrictEqual(r.slices[0].done, true, 'S02 done');
134
+ assert.deepStrictEqual(r.slices[1].id, 'S03', 'second valid slice is S03');
135
+ assert.deepStrictEqual(r.slices[1].depends, ['S02'], 'S03 depends on S02');
136
+ });
137
+
138
+ test('parseRoadmap: lowercase vs uppercase X for done', () => {
142
139
  const content = `# M004: Case Test
143
140
 
144
141
  **Vision:** Test X case sensitivity.
@@ -156,14 +153,13 @@ console.log('\n=== parseRoadmap: lowercase vs uppercase X for done ===');
156
153
  `;
157
154
 
158
155
  const r = parseRoadmap(content);
159
- assertEq(r.slices.length, 3, 'all three slices parsed');
160
- assertEq(r.slices[0].done, true, 'lowercase x is done');
161
- assertEq(r.slices[1].done, true, 'uppercase X is done');
162
- assertEq(r.slices[2].done, false, 'space is not done');
163
- }
164
-
165
- console.log('\n=== parseRoadmap: missing boundary map ===');
166
- {
156
+ assert.deepStrictEqual(r.slices.length, 3, 'all three slices parsed');
157
+ assert.deepStrictEqual(r.slices[0].done, true, 'lowercase x is done');
158
+ assert.deepStrictEqual(r.slices[1].done, true, 'uppercase X is done');
159
+ assert.deepStrictEqual(r.slices[2].done, false, 'space is not done');
160
+ });
161
+
162
+ test('parseRoadmap: missing boundary map', () => {
167
163
  const content = `# M005: No Boundary Map
168
164
 
169
165
  **Vision:** A roadmap without a boundary map section.
@@ -180,29 +176,27 @@ console.log('\n=== parseRoadmap: missing boundary map ===');
180
176
  `;
181
177
 
182
178
  const r = parseRoadmap(content);
183
- assertEq(r.title, 'M005: No Boundary Map', 'title');
184
- assertEq(r.slices.length, 1, 'one slice');
185
- assertEq(r.boundaryMap.length, 0, 'empty boundary map when section missing');
186
- assertEq(r.successCriteria.length, 1, 'one success criterion');
187
- }
188
-
189
- console.log('\n=== parseRoadmap: no sections at all ===');
190
- {
179
+ assert.deepStrictEqual(r.title, 'M005: No Boundary Map', 'title');
180
+ assert.deepStrictEqual(r.slices.length, 1, 'one slice');
181
+ assert.deepStrictEqual(r.boundaryMap.length, 0, 'empty boundary map when section missing');
182
+ assert.deepStrictEqual(r.successCriteria.length, 1, 'one success criterion');
183
+ });
184
+
185
+ test('parseRoadmap: no sections at all', () => {
191
186
  const content = `# M006: Bare Minimum
192
187
 
193
188
  Just a title and nothing else.
194
189
  `;
195
190
 
196
191
  const r = parseRoadmap(content);
197
- assertEq(r.title, 'M006: Bare Minimum', 'title from bare roadmap');
198
- assertEq(r.vision, '', 'empty vision');
199
- assertEq(r.successCriteria.length, 0, 'no success criteria');
200
- assertEq(r.slices.length, 0, 'no slices');
201
- assertEq(r.boundaryMap.length, 0, 'no boundary map');
202
- }
203
-
204
- console.log('\n=== parseRoadmap: slice with no demo blockquote ===');
205
- {
192
+ assert.deepStrictEqual(r.title, 'M006: Bare Minimum', 'title from bare roadmap');
193
+ assert.deepStrictEqual(r.vision, '', 'empty vision');
194
+ assert.deepStrictEqual(r.successCriteria.length, 0, 'no success criteria');
195
+ assert.deepStrictEqual(r.slices.length, 0, 'no slices');
196
+ assert.deepStrictEqual(r.boundaryMap.length, 0, 'no boundary map');
197
+ });
198
+
199
+ test('parseRoadmap: slice with no demo blockquote', () => {
206
200
  const content = `# M007: No Demo
207
201
 
208
202
  **Vision:** Testing slices without demo lines.
@@ -214,13 +208,12 @@ console.log('\n=== parseRoadmap: slice with no demo blockquote ===');
214
208
  `;
215
209
 
216
210
  const r = parseRoadmap(content);
217
- assertEq(r.slices.length, 2, 'two slices without demos');
218
- assertEq(r.slices[0].demo, '', 'S01 demo empty');
219
- assertEq(r.slices[1].demo, '', 'S02 demo empty');
220
- }
211
+ assert.deepStrictEqual(r.slices.length, 2, 'two slices without demos');
212
+ assert.deepStrictEqual(r.slices[0].demo, '', 'S01 demo empty');
213
+ assert.deepStrictEqual(r.slices[1].demo, '', 'S02 demo empty');
214
+ });
221
215
 
222
- console.log('\n=== parseRoadmap: missing risk defaults to low ===');
223
- {
216
+ test('parseRoadmap: missing risk defaults to low', () => {
224
217
  const content = `# M008: Default Risk
225
218
 
226
219
  **Vision:** Test default risk.
@@ -232,16 +225,14 @@ console.log('\n=== parseRoadmap: missing risk defaults to low ===');
232
225
  `;
233
226
 
234
227
  const r = parseRoadmap(content);
235
- assertEq(r.slices.length, 1, 'one slice');
236
- assertEq(r.slices[0].risk, 'low', 'default risk is low');
237
- }
228
+ assert.deepStrictEqual(r.slices.length, 1, 'one slice');
229
+ assert.deepStrictEqual(r.slices[0].risk, 'low', 'default risk is low');
230
+ });
238
231
 
239
232
  // ═══════════════════════════════════════════════════════════════════════════
240
233
  // parsePlan tests
241
234
  // ═══════════════════════════════════════════════════════════════════════════
242
-
243
- console.log('\n=== parsePlan: full plan ===');
244
- {
235
+ test('parsePlan: full plan', () => {
245
236
  const content = `---
246
237
  estimated_steps: 6
247
238
  estimated_files: 3
@@ -277,42 +268,41 @@ skills_used:
277
268
  `;
278
269
 
279
270
  const taskPlan = parseTaskPlanFile(content);
280
- assertEq(taskPlan.frontmatter.estimated_steps, 6, 'task plan frontmatter estimated_steps');
281
- assertEq(taskPlan.frontmatter.estimated_files, 3, 'task plan frontmatter estimated_files');
282
- assertEq(taskPlan.frontmatter.skills_used.length, 2, 'task plan frontmatter skills_used count');
283
- assertEq(taskPlan.frontmatter.skills_used[0], 'typescript', 'first task plan skill');
284
- assertEq(taskPlan.frontmatter.skills_used[1], 'testing', 'second task plan skill');
271
+ assert.deepStrictEqual(taskPlan.frontmatter.estimated_steps, 6, 'task plan frontmatter estimated_steps');
272
+ assert.deepStrictEqual(taskPlan.frontmatter.estimated_files, 3, 'task plan frontmatter estimated_files');
273
+ assert.deepStrictEqual(taskPlan.frontmatter.skills_used.length, 2, 'task plan frontmatter skills_used count');
274
+ assert.deepStrictEqual(taskPlan.frontmatter.skills_used[0], 'typescript', 'first task plan skill');
275
+ assert.deepStrictEqual(taskPlan.frontmatter.skills_used[1], 'testing', 'second task plan skill');
285
276
 
286
277
  const p = parsePlan(content);
287
278
 
288
- assertEq(p.id, 'S01', 'plan id');
289
- assertEq(p.title, 'Parser Test Suite', 'plan title');
290
- assertEq(p.goal, 'All 5 parsers have test coverage with edge cases.', 'plan goal');
291
- assertEq(p.demo, '`node --test tests/parsers.test.ts` passes with zero failures.', 'plan demo');
279
+ assert.deepStrictEqual(p.id, 'S01', 'plan id');
280
+ assert.deepStrictEqual(p.title, 'Parser Test Suite', 'plan title');
281
+ assert.deepStrictEqual(p.goal, 'All 5 parsers have test coverage with edge cases.', 'plan goal');
282
+ assert.deepStrictEqual(p.demo, '`node --test tests/parsers.test.ts` passes with zero failures.', 'plan demo');
292
283
 
293
284
  // Must-haves
294
- assertEq(p.mustHaves.length, 3, 'must-have count');
295
- assertEq(p.mustHaves[0], 'parseRoadmap tests cover happy path and edge cases', 'first must-have');
285
+ assert.deepStrictEqual(p.mustHaves.length, 3, 'must-have count');
286
+ assert.deepStrictEqual(p.mustHaves[0], 'parseRoadmap tests cover happy path and edge cases', 'first must-have');
296
287
 
297
288
  // Tasks
298
- assertEq(p.tasks.length, 2, 'task count');
289
+ assert.deepStrictEqual(p.tasks.length, 2, 'task count');
299
290
 
300
- assertEq(p.tasks[0].id, 'T01', 'T01 id');
301
- assertEq(p.tasks[0].title, 'Test parseRoadmap and parsePlan', 'T01 title');
302
- assertEq(p.tasks[0].done, false, 'T01 not done');
303
- assertTrue(p.tasks[0].description.includes('comprehensive tests'), 'T01 description content');
291
+ assert.deepStrictEqual(p.tasks[0].id, 'T01', 'T01 id');
292
+ assert.deepStrictEqual(p.tasks[0].title, 'Test parseRoadmap and parsePlan', 'T01 title');
293
+ assert.deepStrictEqual(p.tasks[0].done, false, 'T01 not done');
294
+ assert.ok(p.tasks[0].description.includes('comprehensive tests'), 'T01 description content');
304
295
 
305
- assertEq(p.tasks[1].id, 'T02', 'T02 id');
306
- assertEq(p.tasks[1].title, 'Test parseSummary and parseContinue', 'T02 title');
307
- assertEq(p.tasks[1].done, true, 'T02 done');
296
+ assert.deepStrictEqual(p.tasks[1].id, 'T02', 'T02 id');
297
+ assert.deepStrictEqual(p.tasks[1].title, 'Test parseSummary and parseContinue', 'T02 title');
298
+ assert.deepStrictEqual(p.tasks[1].done, true, 'T02 done');
308
299
 
309
300
  // Files likely touched
310
- assertEq(p.filesLikelyTouched.length, 3, 'files likely touched count');
311
- assertTrue(p.filesLikelyTouched[0].includes('tests/parsers.test.ts'), 'first file');
312
- }
301
+ assert.deepStrictEqual(p.filesLikelyTouched.length, 3, 'files likely touched count');
302
+ assert.ok(p.filesLikelyTouched[0].includes('tests/parsers.test.ts'), 'first file');
303
+ });
313
304
 
314
- console.log('\n=== parseTaskPlanFile: defaults missing frontmatter fields ===');
315
- {
305
+ test('parseTaskPlanFile: defaults missing frontmatter fields', () => {
316
306
  const content = `# T01: Minimal task plan
317
307
 
318
308
  ## Description
@@ -321,13 +311,12 @@ No frontmatter here.
321
311
  `;
322
312
 
323
313
  const taskPlan = parseTaskPlanFile(content);
324
- assertEq(taskPlan.frontmatter.estimated_steps, undefined, 'estimated_steps defaults undefined');
325
- assertEq(taskPlan.frontmatter.estimated_files, undefined, 'estimated_files defaults undefined');
326
- assertEq(taskPlan.frontmatter.skills_used.length, 0, 'skills_used defaults empty array');
327
- }
314
+ assert.deepStrictEqual(taskPlan.frontmatter.estimated_steps, undefined, 'estimated_steps defaults undefined');
315
+ assert.deepStrictEqual(taskPlan.frontmatter.estimated_files, undefined, 'estimated_files defaults undefined');
316
+ assert.deepStrictEqual(taskPlan.frontmatter.skills_used.length, 0, 'skills_used defaults empty array');
317
+ });
328
318
 
329
- console.log('\n=== parseTaskPlanFile: accepts scalar skills_used and numeric strings ===');
330
- {
319
+ test('parseTaskPlanFile: accepts scalar skills_used and numeric strings', () => {
331
320
  const content = `---
332
321
  estimated_steps: "9"
333
322
  estimated_files: "4"
@@ -338,14 +327,13 @@ skills_used: react-best-practices
338
327
  `;
339
328
 
340
329
  const taskPlan = parseTaskPlanFile(content);
341
- assertEq(taskPlan.frontmatter.estimated_steps, 9, 'string estimated_steps parsed');
342
- assertEq(taskPlan.frontmatter.estimated_files, 4, 'string estimated_files parsed');
343
- assertEq(taskPlan.frontmatter.skills_used.length, 1, 'scalar skills_used normalized to array');
344
- assertEq(taskPlan.frontmatter.skills_used[0], 'react-best-practices', 'scalar skill preserved');
345
- }
346
-
347
- console.log('\n=== parseTaskPlanFile: filters blank skills_used items ===');
348
- {
330
+ assert.deepStrictEqual(taskPlan.frontmatter.estimated_steps, 9, 'string estimated_steps parsed');
331
+ assert.deepStrictEqual(taskPlan.frontmatter.estimated_files, 4, 'string estimated_files parsed');
332
+ assert.deepStrictEqual(taskPlan.frontmatter.skills_used.length, 1, 'scalar skills_used normalized to array');
333
+ assert.deepStrictEqual(taskPlan.frontmatter.skills_used[0], 'react-best-practices', 'scalar skill preserved');
334
+ });
335
+
336
+ test('parseTaskPlanFile: filters blank skills_used items', () => {
349
337
  const content = `---
350
338
  skills_used:
351
339
  - react
@@ -357,13 +345,12 @@ skills_used:
357
345
  `;
358
346
 
359
347
  const taskPlan = parseTaskPlanFile(content);
360
- assertEq(taskPlan.frontmatter.skills_used.length, 2, 'blank skill entries removed');
361
- assertEq(taskPlan.frontmatter.skills_used[0], 'react', 'first remaining skill');
362
- assertEq(taskPlan.frontmatter.skills_used[1], 'testing', 'second remaining skill');
363
- }
348
+ assert.deepStrictEqual(taskPlan.frontmatter.skills_used.length, 2, 'blank skill entries removed');
349
+ assert.deepStrictEqual(taskPlan.frontmatter.skills_used[0], 'react', 'first remaining skill');
350
+ assert.deepStrictEqual(taskPlan.frontmatter.skills_used[1], 'testing', 'second remaining skill');
351
+ });
364
352
 
365
- console.log('\n=== parseTaskPlanFile: invalid numeric frontmatter ignored ===');
366
- {
353
+ test('parseTaskPlanFile: invalid numeric frontmatter ignored', () => {
367
354
  const content = `---
368
355
  estimated_steps: many
369
356
  estimated_files: unknown
@@ -373,12 +360,11 @@ estimated_files: unknown
373
360
  `;
374
361
 
375
362
  const taskPlan = parseTaskPlanFile(content);
376
- assertEq(taskPlan.frontmatter.estimated_steps, undefined, 'invalid estimated_steps ignored');
377
- assertEq(taskPlan.frontmatter.estimated_files, undefined, 'invalid estimated_files ignored');
378
- }
363
+ assert.deepStrictEqual(taskPlan.frontmatter.estimated_steps, undefined, 'invalid estimated_steps ignored');
364
+ assert.deepStrictEqual(taskPlan.frontmatter.estimated_files, undefined, 'invalid estimated_files ignored');
365
+ });
379
366
 
380
- console.log('\n=== parseTaskPlanFile: parsePlan ignores task-plan frontmatter ===');
381
- {
367
+ test('parseTaskPlanFile: parsePlan ignores task-plan frontmatter', () => {
382
368
  const content = `---
383
369
  estimated_steps: 2
384
370
  estimated_files: 1
@@ -398,12 +384,11 @@ skills_used:
398
384
  `;
399
385
 
400
386
  const p = parsePlan(content);
401
- assertEq(p.id, 'S11', 'plan id still parsed with frontmatter');
402
- assertEq(p.tasks.length, 1, 'task still parsed with frontmatter');
403
- }
387
+ assert.deepStrictEqual(p.id, 'S11', 'plan id still parsed with frontmatter');
388
+ assert.deepStrictEqual(p.tasks.length, 1, 'task still parsed with frontmatter');
389
+ });
404
390
 
405
- console.log('\n=== parsePlan: multi-line task description concatenation ===');
406
- {
391
+ test('parsePlan: multi-line task description concatenation', () => {
407
392
  const content = `# S02: Multi-line Test
408
393
 
409
394
  **Goal:** Test multi-line descriptions.
@@ -430,16 +415,15 @@ console.log('\n=== parsePlan: multi-line task description concatenation ===');
430
415
 
431
416
  const p = parsePlan(content);
432
417
 
433
- assertEq(p.tasks.length, 2, 'two tasks');
434
- assertTrue(p.tasks[0].description.includes('First line'), 'T01 desc has first line');
435
- assertTrue(p.tasks[0].description.includes('Second line'), 'T01 desc has second line');
436
- assertTrue(p.tasks[0].description.includes('Third line'), 'T01 desc has third line');
437
- assertTrue(p.tasks[0].description.includes('description. Second'), 'lines joined with space');
438
- assertEq(p.tasks[1].description, 'Just one line.', 'T02 single-line desc');
439
- }
418
+ assert.deepStrictEqual(p.tasks.length, 2, 'two tasks');
419
+ assert.ok(p.tasks[0].description.includes('First line'), 'T01 desc has first line');
420
+ assert.ok(p.tasks[0].description.includes('Second line'), 'T01 desc has second line');
421
+ assert.ok(p.tasks[0].description.includes('Third line'), 'T01 desc has third line');
422
+ assert.ok(p.tasks[0].description.includes('description. Second'), 'lines joined with space');
423
+ assert.deepStrictEqual(p.tasks[1].description, 'Just one line.', 'T02 single-line desc');
424
+ });
440
425
 
441
- console.log('\n=== parsePlan: frontmatter does not pollute task descriptions ===');
442
- {
426
+ test('parsePlan: frontmatter does not pollute task descriptions', () => {
443
427
  const content = `---
444
428
  estimated_steps: 2
445
429
  estimated_files: 1
@@ -457,12 +441,11 @@ skills_used:
457
441
  `;
458
442
 
459
443
  const p = parsePlan(content);
460
- assertEq(p.tasks.length, 1, 'one task parsed with frontmatter');
461
- assertEq(p.tasks[0].description, 'First line of description. Second line of description.', 'frontmatter excluded from description');
462
- }
444
+ assert.deepStrictEqual(p.tasks.length, 1, 'one task parsed with frontmatter');
445
+ assert.deepStrictEqual(p.tasks[0].description, 'First line of description. Second line of description.', 'frontmatter excluded from description');
446
+ });
463
447
 
464
- console.log('\n=== parsePlan: task with missing estimate ===');
465
- {
448
+ test('parsePlan: task with missing estimate', () => {
466
449
  const content = `# S03: No Estimate
467
450
 
468
451
  **Goal:** Handle tasks without estimates.
@@ -478,15 +461,14 @@ console.log('\n=== parsePlan: task with missing estimate ===');
478
461
  `;
479
462
 
480
463
  const p = parsePlan(content);
481
- assertEq(p.tasks.length, 2, 'two tasks parsed');
482
- assertEq(p.tasks[0].id, 'T01', 'T01 id');
483
- assertEq(p.tasks[0].title, 'No Estimate Task', 'T01 title without estimate');
484
- assertEq(p.tasks[0].done, false, 'T01 not done');
485
- assertEq(p.tasks[1].id, 'T02', 'T02 id');
486
- }
487
-
488
- console.log('\n=== parsePlan: empty tasks section ===');
489
- {
464
+ assert.deepStrictEqual(p.tasks.length, 2, 'two tasks parsed');
465
+ assert.deepStrictEqual(p.tasks[0].id, 'T01', 'T01 id');
466
+ assert.deepStrictEqual(p.tasks[0].title, 'No Estimate Task', 'T01 title without estimate');
467
+ assert.deepStrictEqual(p.tasks[0].done, false, 'T01 not done');
468
+ assert.deepStrictEqual(p.tasks[1].id, 'T02', 'T02 id');
469
+ });
470
+
471
+ test('parsePlan: empty tasks section', () => {
490
472
  const content = `# S04: Empty Tasks
491
473
 
492
474
  **Goal:** No tasks yet.
@@ -504,14 +486,13 @@ console.log('\n=== parsePlan: empty tasks section ===');
504
486
  `;
505
487
 
506
488
  const p = parsePlan(content);
507
- assertEq(p.id, 'S04', 'plan id with empty tasks');
508
- assertEq(p.tasks.length, 0, 'no tasks');
509
- assertEq(p.mustHaves.length, 1, 'one must-have');
510
- assertEq(p.filesLikelyTouched.length, 1, 'one file');
511
- }
512
-
513
- console.log('\n=== parsePlan: no H1 ===');
514
- {
489
+ assert.deepStrictEqual(p.id, 'S04', 'plan id with empty tasks');
490
+ assert.deepStrictEqual(p.tasks.length, 0, 'no tasks');
491
+ assert.deepStrictEqual(p.mustHaves.length, 1, 'one must-have');
492
+ assert.deepStrictEqual(p.filesLikelyTouched.length, 1, 'one file');
493
+ });
494
+
495
+ test('parsePlan: no H1', () => {
515
496
  const content = `**Goal:** A plan without a heading.
516
497
  **Demo:** Still parses.
517
498
 
@@ -522,15 +503,14 @@ console.log('\n=== parsePlan: no H1 ===');
522
503
  `;
523
504
 
524
505
  const p = parsePlan(content);
525
- assertEq(p.id, '', 'empty id without H1');
526
- assertEq(p.title, '', 'empty title without H1');
527
- assertEq(p.goal, 'A plan without a heading.', 'goal still parsed');
528
- assertEq(p.tasks.length, 1, 'task still parsed');
529
- assertEq(p.tasks[0].id, 'T01', 'task id');
530
- }
531
-
532
- console.log('\n=== parsePlan: task estimate backtick in description ===');
533
- {
506
+ assert.deepStrictEqual(p.id, '', 'empty id without H1');
507
+ assert.deepStrictEqual(p.title, '', 'empty title without H1');
508
+ assert.deepStrictEqual(p.goal, 'A plan without a heading.', 'goal still parsed');
509
+ assert.deepStrictEqual(p.tasks.length, 1, 'task still parsed');
510
+ assert.deepStrictEqual(p.tasks[0].id, 'T01', 'task id');
511
+ });
512
+
513
+ test('parsePlan: task estimate backtick in description', () => {
534
514
  const content = `# S05: Estimate Handling
535
515
 
536
516
  **Goal:** Test estimate text handling.
@@ -543,14 +523,13 @@ console.log('\n=== parsePlan: task estimate backtick in description ===');
543
523
  `;
544
524
 
545
525
  const p = parsePlan(content);
546
- assertEq(p.tasks.length, 1, 'one task');
547
- assertEq(p.tasks[0].id, 'T01', 'task id');
548
- assertEq(p.tasks[0].title, 'With Estimate', 'title excludes estimate');
549
- assertTrue(p.tasks[0].description.includes('Main description'), 'description from continuation line');
550
- }
551
-
552
- console.log('\n=== parsePlan: uppercase X for done ===');
553
- {
526
+ assert.deepStrictEqual(p.tasks.length, 1, 'one task');
527
+ assert.deepStrictEqual(p.tasks[0].id, 'T01', 'task id');
528
+ assert.deepStrictEqual(p.tasks[0].title, 'With Estimate', 'title excludes estimate');
529
+ assert.ok(p.tasks[0].description.includes('Main description'), 'description from continuation line');
530
+ });
531
+
532
+ test('parsePlan: uppercase X for done', () => {
554
533
  const content = `# S06: Case Test
555
534
 
556
535
  **Goal:** Test case.
@@ -566,12 +545,11 @@ console.log('\n=== parsePlan: uppercase X for done ===');
566
545
  `;
567
546
 
568
547
  const p = parsePlan(content);
569
- assertEq(p.tasks[0].done, true, 'uppercase X is done');
570
- assertEq(p.tasks[1].done, true, 'lowercase x is done');
571
- }
548
+ assert.deepStrictEqual(p.tasks[0].done, true, 'uppercase X is done');
549
+ assert.deepStrictEqual(p.tasks[1].done, true, 'lowercase x is done');
550
+ });
572
551
 
573
- console.log('\n=== parsePlan: no Must-Haves section ===');
574
- {
552
+ test('parsePlan: no Must-Haves section', () => {
575
553
  const content = `# S07: No Must-Haves
576
554
 
577
555
  **Goal:** Test missing must-haves.
@@ -584,12 +562,11 @@ console.log('\n=== parsePlan: no Must-Haves section ===');
584
562
  `;
585
563
 
586
564
  const p = parsePlan(content);
587
- assertEq(p.mustHaves.length, 0, 'empty must-haves');
588
- assertEq(p.tasks.length, 1, 'task still parsed');
589
- }
565
+ assert.deepStrictEqual(p.mustHaves.length, 0, 'empty must-haves');
566
+ assert.deepStrictEqual(p.tasks.length, 1, 'task still parsed');
567
+ });
590
568
 
591
- console.log('\n=== parsePlan: no Files Likely Touched section ===');
592
- {
569
+ test('parsePlan: no Files Likely Touched section', () => {
593
570
  const content = `# S08: No Files
594
571
 
595
572
  **Goal:** Test missing files section.
@@ -602,11 +579,10 @@ console.log('\n=== parsePlan: no Files Likely Touched section ===');
602
579
  `;
603
580
 
604
581
  const p = parsePlan(content);
605
- assertEq(p.filesLikelyTouched.length, 0, 'empty files likely touched');
606
- }
582
+ assert.deepStrictEqual(p.filesLikelyTouched.length, 0, 'empty files likely touched');
583
+ });
607
584
 
608
- console.log('\n=== parsePlan: old-format task entries (no sublines) ===');
609
- {
585
+ test('parsePlan: old-format task entries (no sublines)', () => {
610
586
  const content = `# S09: Old Format
611
587
 
612
588
  **Goal:** Test old-format compatibility.
@@ -619,16 +595,15 @@ console.log('\n=== parsePlan: old-format task entries (no sublines) ===');
619
595
  `;
620
596
 
621
597
  const p = parsePlan(content);
622
- assertEq(p.tasks.length, 1, 'one task parsed');
623
- assertEq(p.tasks[0].id, 'T01', 'task id');
624
- assertEq(p.tasks[0].title, 'Classic Task', 'task title');
625
- assertEq(p.tasks[0].done, false, 'task not done');
626
- assertEq(p.tasks[0].files, undefined, 'files is undefined for old-format entry');
627
- assertEq(p.tasks[0].verify, undefined, 'verify is undefined for old-format entry');
628
- }
629
-
630
- console.log('\n=== parsePlan: new-format task entries with Files and Verify sublines ===');
631
- {
598
+ assert.deepStrictEqual(p.tasks.length, 1, 'one task parsed');
599
+ assert.deepStrictEqual(p.tasks[0].id, 'T01', 'task id');
600
+ assert.deepStrictEqual(p.tasks[0].title, 'Classic Task', 'task title');
601
+ assert.deepStrictEqual(p.tasks[0].done, false, 'task not done');
602
+ assert.deepStrictEqual(p.tasks[0].files, undefined, 'files is undefined for old-format entry');
603
+ assert.deepStrictEqual(p.tasks[0].verify, undefined, 'verify is undefined for old-format entry');
604
+ });
605
+
606
+ test('parsePlan: new-format task entries with Files and Verify sublines', () => {
632
607
  const content = `# S10: New Format
633
608
 
634
609
  **Goal:** Test new-format subline extraction.
@@ -643,18 +618,17 @@ console.log('\n=== parsePlan: new-format task entries with Files and Verify subl
643
618
  `;
644
619
 
645
620
  const p = parsePlan(content);
646
- assertEq(p.tasks.length, 1, 'one task parsed');
647
- assertEq(p.tasks[0].id, 'T01', 'task id');
648
- assertTrue(Array.isArray(p.tasks[0].files), 'files is an array');
649
- assertEq(p.tasks[0].files!.length, 2, 'files array has two entries');
650
- assertEq(p.tasks[0].files![0], 'types.ts', 'first file is types.ts');
651
- assertEq(p.tasks[0].files![1], 'files.ts', 'second file is files.ts');
652
- assertEq(p.tasks[0].verify, 'run the test suite', 'verify string extracted correctly');
653
- assertTrue(p.tasks[0].description.includes('Why: because we need typed plan entries'), 'Why line accumulates into description');
654
- }
655
-
656
- console.log('\n=== parsePlan: heading-style task entries (### T01 -- Title) ===');
657
- {
621
+ assert.deepStrictEqual(p.tasks.length, 1, 'one task parsed');
622
+ assert.deepStrictEqual(p.tasks[0].id, 'T01', 'task id');
623
+ assert.ok(Array.isArray(p.tasks[0].files), 'files is an array');
624
+ assert.deepStrictEqual(p.tasks[0].files!.length, 2, 'files array has two entries');
625
+ assert.deepStrictEqual(p.tasks[0].files![0], 'types.ts', 'first file is types.ts');
626
+ assert.deepStrictEqual(p.tasks[0].files![1], 'files.ts', 'second file is files.ts');
627
+ assert.deepStrictEqual(p.tasks[0].verify, 'run the test suite', 'verify string extracted correctly');
628
+ assert.ok(p.tasks[0].description.includes('Why: because we need typed plan entries'), 'Why line accumulates into description');
629
+ });
630
+
631
+ test('parsePlan: heading-style task entries (### T01 -- Title)', () => {
658
632
  const content = `# S11: Heading Style
659
633
 
660
634
  **Goal:** Test heading-style task parsing.
@@ -674,20 +648,19 @@ Some description for the second task.
674
648
  `;
675
649
 
676
650
  const p = parsePlan(content);
677
- assertEq(p.tasks.length, 2, 'heading-style task count');
678
- assertEq(p.tasks[0].id, 'T01', 'heading T01 id');
679
- assertEq(p.tasks[0].title, 'Implement feature', 'heading T01 title');
680
- assertEq(p.tasks[0].done, false, 'heading T01 not done (headings have no checkbox)');
681
- assertEq(p.tasks[0].files![0], 'src/feature.ts', 'heading T01 files extracted');
682
- assertEq(p.tasks[0].verify, 'npm test', 'heading T01 verify extracted');
683
- assertEq(p.tasks[1].id, 'T02', 'heading T02 id');
684
- assertEq(p.tasks[1].title, 'Write tests', 'heading T02 title');
685
- assertEq(p.tasks[1].estimate, '1h', 'heading T02 estimate');
686
- assertTrue(p.tasks[1].description.includes('Some description'), 'heading T02 description');
687
- }
688
-
689
- console.log('\n=== parsePlan: heading-style with colon separator (### T01: Title) ===');
690
- {
651
+ assert.deepStrictEqual(p.tasks.length, 2, 'heading-style task count');
652
+ assert.deepStrictEqual(p.tasks[0].id, 'T01', 'heading T01 id');
653
+ assert.deepStrictEqual(p.tasks[0].title, 'Implement feature', 'heading T01 title');
654
+ assert.deepStrictEqual(p.tasks[0].done, false, 'heading T01 not done (headings have no checkbox)');
655
+ assert.deepStrictEqual(p.tasks[0].files![0], 'src/feature.ts', 'heading T01 files extracted');
656
+ assert.deepStrictEqual(p.tasks[0].verify, 'npm test', 'heading T01 verify extracted');
657
+ assert.deepStrictEqual(p.tasks[1].id, 'T02', 'heading T02 id');
658
+ assert.deepStrictEqual(p.tasks[1].title, 'Write tests', 'heading T02 title');
659
+ assert.deepStrictEqual(p.tasks[1].estimate, '1h', 'heading T02 estimate');
660
+ assert.ok(p.tasks[1].description.includes('Some description'), 'heading T02 description');
661
+ });
662
+
663
+ test('parsePlan: heading-style with colon separator (### T01: Title)', () => {
691
664
  const content = `# S12: Heading Colon Style
692
665
 
693
666
  **Goal:** Test colon-separated heading tasks.
@@ -703,16 +676,15 @@ console.log('\n=== parsePlan: heading-style with colon separator (### T01: Title
703
676
  `;
704
677
 
705
678
  const p = parsePlan(content);
706
- assertEq(p.tasks.length, 2, 'colon heading task count');
707
- assertEq(p.tasks[0].id, 'T01', 'colon heading T01 id');
708
- assertEq(p.tasks[0].title, 'Setup project', 'colon heading T01 title');
709
- assertEq(p.tasks[1].id, 'T02', 'colon heading T02 id');
710
- assertEq(p.tasks[1].title, 'Add CI pipeline', 'colon heading T02 title');
711
- assertEq(p.tasks[1].estimate, '30m', 'colon heading T02 estimate');
712
- }
713
-
714
- console.log('\n=== parsePlan: heading-style with em-dash separator (### T01 — Title) ===');
715
- {
679
+ assert.deepStrictEqual(p.tasks.length, 2, 'colon heading task count');
680
+ assert.deepStrictEqual(p.tasks[0].id, 'T01', 'colon heading T01 id');
681
+ assert.deepStrictEqual(p.tasks[0].title, 'Setup project', 'colon heading T01 title');
682
+ assert.deepStrictEqual(p.tasks[1].id, 'T02', 'colon heading T02 id');
683
+ assert.deepStrictEqual(p.tasks[1].title, 'Add CI pipeline', 'colon heading T02 title');
684
+ assert.deepStrictEqual(p.tasks[1].estimate, '30m', 'colon heading T02 estimate');
685
+ });
686
+
687
+ test('parsePlan: heading-style with em-dash separator (### T01 — Title)', () => {
716
688
  const content = `# S13: Em-Dash Style
717
689
 
718
690
  **Goal:** Test em-dash separated heading tasks.
@@ -726,13 +698,12 @@ Widget description.
726
698
  `;
727
699
 
728
700
  const p = parsePlan(content);
729
- assertEq(p.tasks.length, 1, 'em-dash heading task count');
730
- assertEq(p.tasks[0].id, 'T01', 'em-dash heading T01 id');
731
- assertEq(p.tasks[0].title, 'Build the widget', 'em-dash heading T01 title');
732
- }
701
+ assert.deepStrictEqual(p.tasks.length, 1, 'em-dash heading task count');
702
+ assert.deepStrictEqual(p.tasks[0].id, 'T01', 'em-dash heading T01 id');
703
+ assert.deepStrictEqual(p.tasks[0].title, 'Build the widget', 'em-dash heading T01 title');
704
+ });
733
705
 
734
- console.log('\n=== parsePlan: mixed checkbox and heading-style tasks ===');
735
- {
706
+ test('parsePlan: mixed checkbox and heading-style tasks', () => {
736
707
  const content = `# S14: Mixed Format
737
708
 
738
709
  **Goal:** Test mixed formats.
@@ -752,23 +723,21 @@ A heading-style task.
752
723
  `;
753
724
 
754
725
  const p = parsePlan(content);
755
- assertEq(p.tasks.length, 3, 'mixed format task count');
756
- assertEq(p.tasks[0].id, 'T01', 'mixed T01 id');
757
- assertEq(p.tasks[0].done, false, 'mixed T01 not done');
758
- assertEq(p.tasks[1].id, 'T02', 'mixed T02 id');
759
- assertEq(p.tasks[1].title, 'Heading task', 'mixed T02 title');
760
- assertEq(p.tasks[1].estimate, '15m', 'mixed T02 estimate');
761
- assertEq(p.tasks[1].done, false, 'mixed T02 not done (heading style)');
762
- assertEq(p.tasks[2].id, 'T03', 'mixed T03 id');
763
- assertEq(p.tasks[2].done, true, 'mixed T03 done');
764
- }
726
+ assert.deepStrictEqual(p.tasks.length, 3, 'mixed format task count');
727
+ assert.deepStrictEqual(p.tasks[0].id, 'T01', 'mixed T01 id');
728
+ assert.deepStrictEqual(p.tasks[0].done, false, 'mixed T01 not done');
729
+ assert.deepStrictEqual(p.tasks[1].id, 'T02', 'mixed T02 id');
730
+ assert.deepStrictEqual(p.tasks[1].title, 'Heading task', 'mixed T02 title');
731
+ assert.deepStrictEqual(p.tasks[1].estimate, '15m', 'mixed T02 estimate');
732
+ assert.deepStrictEqual(p.tasks[1].done, false, 'mixed T02 not done (heading style)');
733
+ assert.deepStrictEqual(p.tasks[2].id, 'T03', 'mixed T03 id');
734
+ assert.deepStrictEqual(p.tasks[2].done, true, 'mixed T03 done');
735
+ });
765
736
 
766
737
  // ═══════════════════════════════════════════════════════════════════════════
767
738
  // parseSummary tests
768
739
  // ═══════════════════════════════════════════════════════════════════════════
769
-
770
- console.log('\n=== parseSummary: full summary with all frontmatter fields ===');
771
- {
740
+ test('parseSummary: full summary with all frontmatter fields', () => {
772
741
  const content = `---
773
742
  id: T01
774
743
  parent: S01
@@ -823,52 +792,51 @@ None.
823
792
  const s = parseSummary(content);
824
793
 
825
794
  // Frontmatter fields
826
- assertEq(s.frontmatter.id, 'T01', 'summary id');
827
- assertEq(s.frontmatter.parent, 'S01', 'summary parent');
828
- assertEq(s.frontmatter.milestone, 'M001', 'summary milestone');
829
- assertEq(s.frontmatter.provides.length, 2, 'provides count');
830
- assertEq(s.frontmatter.provides[0], 'parseRoadmap test coverage', 'first provides');
831
- assertEq(s.frontmatter.provides[1], 'parsePlan test coverage', 'second provides');
795
+ assert.deepStrictEqual(s.frontmatter.id, 'T01', 'summary id');
796
+ assert.deepStrictEqual(s.frontmatter.parent, 'S01', 'summary parent');
797
+ assert.deepStrictEqual(s.frontmatter.milestone, 'M001', 'summary milestone');
798
+ assert.deepStrictEqual(s.frontmatter.provides.length, 2, 'provides count');
799
+ assert.deepStrictEqual(s.frontmatter.provides[0], 'parseRoadmap test coverage', 'first provides');
800
+ assert.deepStrictEqual(s.frontmatter.provides[1], 'parsePlan test coverage', 'second provides');
832
801
 
833
802
  // requires (nested objects)
834
- assertEq(s.frontmatter.requires.length, 2, 'requires count');
835
- assertEq(s.frontmatter.requires[0].slice, 'S00', 'first requires slice');
836
- assertEq(s.frontmatter.requires[0].provides, 'type definitions', 'first requires provides');
837
- assertEq(s.frontmatter.requires[1].slice, 'S02', 'second requires slice');
838
- assertEq(s.frontmatter.requires[1].provides, 'state derivation', 'second requires provides');
839
-
840
- assertEq(s.frontmatter.affects.length, 1, 'affects count');
841
- assertEq(s.frontmatter.affects[0], 'auto-mode dispatch', 'affects value');
842
- assertEq(s.frontmatter.key_files.length, 2, 'key_files count');
843
- assertEq(s.frontmatter.key_decisions.length, 1, 'key_decisions count');
844
- assertEq(s.frontmatter.patterns_established.length, 1, 'patterns_established count');
845
- assertEq(s.frontmatter.drill_down_paths.length, 1, 'drill_down_paths count');
803
+ assert.deepStrictEqual(s.frontmatter.requires.length, 2, 'requires count');
804
+ assert.deepStrictEqual(s.frontmatter.requires[0].slice, 'S00', 'first requires slice');
805
+ assert.deepStrictEqual(s.frontmatter.requires[0].provides, 'type definitions', 'first requires provides');
806
+ assert.deepStrictEqual(s.frontmatter.requires[1].slice, 'S02', 'second requires slice');
807
+ assert.deepStrictEqual(s.frontmatter.requires[1].provides, 'state derivation', 'second requires provides');
808
+
809
+ assert.deepStrictEqual(s.frontmatter.affects.length, 1, 'affects count');
810
+ assert.deepStrictEqual(s.frontmatter.affects[0], 'auto-mode dispatch', 'affects value');
811
+ assert.deepStrictEqual(s.frontmatter.key_files.length, 2, 'key_files count');
812
+ assert.deepStrictEqual(s.frontmatter.key_decisions.length, 1, 'key_decisions count');
813
+ assert.deepStrictEqual(s.frontmatter.patterns_established.length, 1, 'patterns_established count');
814
+ assert.deepStrictEqual(s.frontmatter.drill_down_paths.length, 1, 'drill_down_paths count');
846
815
 
847
816
  // observability_surfaces extraction
848
- assertEq(s.frontmatter.observability_surfaces.length, 2, 'observability_surfaces count');
849
- assertEq(s.frontmatter.observability_surfaces[0], 'test pass/fail output from node --test', 'first observability surface');
850
- assertEq(s.frontmatter.observability_surfaces[1], 'exit code 1 on failure', 'second observability surface');
817
+ assert.deepStrictEqual(s.frontmatter.observability_surfaces.length, 2, 'observability_surfaces count');
818
+ assert.deepStrictEqual(s.frontmatter.observability_surfaces[0], 'test pass/fail output from node --test', 'first observability surface');
819
+ assert.deepStrictEqual(s.frontmatter.observability_surfaces[1], 'exit code 1 on failure', 'second observability surface');
851
820
 
852
- assertEq(s.frontmatter.duration, '23min', 'duration');
853
- assertEq(s.frontmatter.verification_result, 'pass', 'verification_result');
854
- assertEq(s.frontmatter.completed_at, '2025-03-10T08:00:00Z', 'completed_at');
821
+ assert.deepStrictEqual(s.frontmatter.duration, '23min', 'duration');
822
+ assert.deepStrictEqual(s.frontmatter.verification_result, 'pass', 'verification_result');
823
+ assert.deepStrictEqual(s.frontmatter.completed_at, '2025-03-10T08:00:00Z', 'completed_at');
855
824
 
856
825
  // Body fields
857
- assertEq(s.title, 'T01: Test parseRoadmap and parsePlan', 'summary title');
858
- assertEq(s.oneLiner, 'Created parsers.test.ts with 98 assertions across 16 test groups.', 'one-liner');
859
- assertTrue(s.whatHappened.includes('comprehensive tests'), 'whatHappened content');
860
- assertEq(s.deviations, 'None.', 'deviations');
826
+ assert.deepStrictEqual(s.title, 'T01: Test parseRoadmap and parsePlan', 'summary title');
827
+ assert.deepStrictEqual(s.oneLiner, 'Created parsers.test.ts with 98 assertions across 16 test groups.', 'one-liner');
828
+ assert.ok(s.whatHappened.includes('comprehensive tests'), 'whatHappened content');
829
+ assert.deepStrictEqual(s.deviations, 'None.', 'deviations');
861
830
 
862
831
  // Files modified
863
- assertEq(s.filesModified.length, 3, 'filesModified count');
864
- assertEq(s.filesModified[0].path, 'tests/parsers.test.ts', 'first file path');
865
- assertTrue(s.filesModified[0].description.includes('98 assertions'), 'first file description');
866
- assertEq(s.filesModified[1].path, 'types.ts', 'second file path');
867
- assertEq(s.filesModified[2].path, 'files.ts', 'third file path');
868
- }
869
-
870
- console.log('\n=== parseSummary: one-liner extraction (bold-wrapped line after H1) ===');
871
- {
832
+ assert.deepStrictEqual(s.filesModified.length, 3, 'filesModified count');
833
+ assert.deepStrictEqual(s.filesModified[0].path, 'tests/parsers.test.ts', 'first file path');
834
+ assert.ok(s.filesModified[0].description.includes('98 assertions'), 'first file description');
835
+ assert.deepStrictEqual(s.filesModified[1].path, 'types.ts', 'second file path');
836
+ assert.deepStrictEqual(s.filesModified[2].path, 'files.ts', 'third file path');
837
+ });
838
+
839
+ test('parseSummary: one-liner extraction (bold-wrapped line after H1)', () => {
872
840
  const content = `# S01: Parser Test Suite
873
841
 
874
842
  **All 5 parsers have test coverage with edge cases.**
@@ -879,12 +847,11 @@ Things happened.
879
847
  `;
880
848
 
881
849
  const s = parseSummary(content);
882
- assertEq(s.title, 'S01: Parser Test Suite', 'title');
883
- assertEq(s.oneLiner, 'All 5 parsers have test coverage with edge cases.', 'bold one-liner');
884
- }
850
+ assert.deepStrictEqual(s.title, 'S01: Parser Test Suite', 'title');
851
+ assert.deepStrictEqual(s.oneLiner, 'All 5 parsers have test coverage with edge cases.', 'bold one-liner');
852
+ });
885
853
 
886
- console.log('\n=== parseSummary: non-bold paragraph after H1 (empty one-liner) ===');
887
- {
854
+ test('parseSummary: non-bold paragraph after H1 (empty one-liner)', () => {
888
855
  const content = `# T02: Some Task
889
856
 
890
857
  This is just a regular paragraph, not bold.
@@ -895,12 +862,11 @@ Did stuff.
895
862
  `;
896
863
 
897
864
  const s = parseSummary(content);
898
- assertEq(s.title, 'T02: Some Task', 'title');
899
- assertEq(s.oneLiner, '', 'non-bold line results in empty one-liner');
900
- }
865
+ assert.deepStrictEqual(s.title, 'T02: Some Task', 'title');
866
+ assert.deepStrictEqual(s.oneLiner, '', 'non-bold line results in empty one-liner');
867
+ });
901
868
 
902
- console.log('\n=== parseSummary: files-modified parsing (backtick path — description format) ===');
903
- {
869
+ test('parseSummary: files-modified parsing (backtick path — description format)', () => {
904
870
  const content = `# T03: File Changes
905
871
 
906
872
  **One-liner.**
@@ -913,15 +879,14 @@ console.log('\n=== parseSummary: files-modified parsing (backtick path — descr
913
879
  `;
914
880
 
915
881
  const s = parseSummary(content);
916
- assertEq(s.filesModified.length, 3, 'three files');
917
- assertEq(s.filesModified[0].path, 'src/index.ts', 'first path');
918
- assertEq(s.filesModified[0].description, 'main entry point', 'first description');
919
- assertEq(s.filesModified[1].path, 'src/utils.ts', 'second path');
920
- assertEq(s.filesModified[2].path, 'README.md', 'third path');
921
- }
922
-
923
- console.log('\n=== parseSummary: missing frontmatter (safe defaults) ===');
924
- {
882
+ assert.deepStrictEqual(s.filesModified.length, 3, 'three files');
883
+ assert.deepStrictEqual(s.filesModified[0].path, 'src/index.ts', 'first path');
884
+ assert.deepStrictEqual(s.filesModified[0].description, 'main entry point', 'first description');
885
+ assert.deepStrictEqual(s.filesModified[1].path, 'src/utils.ts', 'second path');
886
+ assert.deepStrictEqual(s.filesModified[2].path, 'README.md', 'third path');
887
+ });
888
+
889
+ test('parseSummary: missing frontmatter (safe defaults)', () => {
925
890
  const content = `# T04: No Frontmatter
926
891
 
927
892
  **Did something.**
@@ -932,26 +897,25 @@ No frontmatter at all.
932
897
  `;
933
898
 
934
899
  const s = parseSummary(content);
935
- assertEq(s.frontmatter.id, '', 'default id empty');
936
- assertEq(s.frontmatter.parent, '', 'default parent empty');
937
- assertEq(s.frontmatter.milestone, '', 'default milestone empty');
938
- assertEq(s.frontmatter.provides.length, 0, 'default provides empty');
939
- assertEq(s.frontmatter.requires.length, 0, 'default requires empty');
940
- assertEq(s.frontmatter.affects.length, 0, 'default affects empty');
941
- assertEq(s.frontmatter.key_files.length, 0, 'default key_files empty');
942
- assertEq(s.frontmatter.key_decisions.length, 0, 'default key_decisions empty');
943
- assertEq(s.frontmatter.patterns_established.length, 0, 'default patterns_established empty');
944
- assertEq(s.frontmatter.drill_down_paths.length, 0, 'default drill_down_paths empty');
945
- assertEq(s.frontmatter.observability_surfaces.length, 0, 'default observability_surfaces empty');
946
- assertEq(s.frontmatter.duration, '', 'default duration empty');
947
- assertEq(s.frontmatter.verification_result, 'untested', 'default verification_result');
948
- assertEq(s.frontmatter.completed_at, '', 'default completed_at empty');
949
- assertEq(s.title, 'T04: No Frontmatter', 'title still parsed');
950
- assertEq(s.oneLiner, 'Did something.', 'one-liner still parsed');
951
- }
952
-
953
- console.log('\n=== parseSummary: empty body ===');
954
- {
900
+ assert.deepStrictEqual(s.frontmatter.id, '', 'default id empty');
901
+ assert.deepStrictEqual(s.frontmatter.parent, '', 'default parent empty');
902
+ assert.deepStrictEqual(s.frontmatter.milestone, '', 'default milestone empty');
903
+ assert.deepStrictEqual(s.frontmatter.provides.length, 0, 'default provides empty');
904
+ assert.deepStrictEqual(s.frontmatter.requires.length, 0, 'default requires empty');
905
+ assert.deepStrictEqual(s.frontmatter.affects.length, 0, 'default affects empty');
906
+ assert.deepStrictEqual(s.frontmatter.key_files.length, 0, 'default key_files empty');
907
+ assert.deepStrictEqual(s.frontmatter.key_decisions.length, 0, 'default key_decisions empty');
908
+ assert.deepStrictEqual(s.frontmatter.patterns_established.length, 0, 'default patterns_established empty');
909
+ assert.deepStrictEqual(s.frontmatter.drill_down_paths.length, 0, 'default drill_down_paths empty');
910
+ assert.deepStrictEqual(s.frontmatter.observability_surfaces.length, 0, 'default observability_surfaces empty');
911
+ assert.deepStrictEqual(s.frontmatter.duration, '', 'default duration empty');
912
+ assert.deepStrictEqual(s.frontmatter.verification_result, 'untested', 'default verification_result');
913
+ assert.deepStrictEqual(s.frontmatter.completed_at, '', 'default completed_at empty');
914
+ assert.deepStrictEqual(s.title, 'T04: No Frontmatter', 'title still parsed');
915
+ assert.deepStrictEqual(s.oneLiner, 'Did something.', 'one-liner still parsed');
916
+ });
917
+
918
+ test('parseSummary: empty body', () => {
955
919
  const content = `---
956
920
  id: T05
957
921
  parent: S01
@@ -960,16 +924,15 @@ milestone: M001
960
924
  `;
961
925
 
962
926
  const s = parseSummary(content);
963
- assertEq(s.frontmatter.id, 'T05', 'id from frontmatter');
964
- assertEq(s.title, '', 'empty title');
965
- assertEq(s.oneLiner, '', 'empty one-liner');
966
- assertEq(s.whatHappened, '', 'empty whatHappened');
967
- assertEq(s.deviations, '', 'empty deviations');
968
- assertEq(s.filesModified.length, 0, 'no files modified');
969
- }
970
-
971
- console.log('\n=== parseSummary: summary with requires array (nested objects) ===');
972
- {
927
+ assert.deepStrictEqual(s.frontmatter.id, 'T05', 'id from frontmatter');
928
+ assert.deepStrictEqual(s.title, '', 'empty title');
929
+ assert.deepStrictEqual(s.oneLiner, '', 'empty one-liner');
930
+ assert.deepStrictEqual(s.whatHappened, '', 'empty whatHappened');
931
+ assert.deepStrictEqual(s.deviations, '', 'empty deviations');
932
+ assert.deepStrictEqual(s.filesModified.length, 0, 'no files modified');
933
+ });
934
+
935
+ test('parseSummary: summary with requires array (nested objects)', () => {
973
936
  const content = `---
974
937
  id: T06
975
938
  parent: S02
@@ -1004,20 +967,18 @@ Tested.
1004
967
  `;
1005
968
 
1006
969
  const s = parseSummary(content);
1007
- assertEq(s.frontmatter.requires.length, 3, 'three requires entries');
1008
- assertEq(s.frontmatter.requires[0].slice, 'S01', 'first requires slice');
1009
- assertEq(s.frontmatter.requires[0].provides, 'parser functions', 'first requires provides');
1010
- assertEq(s.frontmatter.requires[1].slice, 'S00', 'second requires slice');
1011
- assertEq(s.frontmatter.requires[2].slice, 'S03', 'third requires slice');
1012
- assertEq(s.frontmatter.requires[2].provides, 'state engine', 'third requires provides');
1013
- }
970
+ assert.deepStrictEqual(s.frontmatter.requires.length, 3, 'three requires entries');
971
+ assert.deepStrictEqual(s.frontmatter.requires[0].slice, 'S01', 'first requires slice');
972
+ assert.deepStrictEqual(s.frontmatter.requires[0].provides, 'parser functions', 'first requires provides');
973
+ assert.deepStrictEqual(s.frontmatter.requires[1].slice, 'S00', 'second requires slice');
974
+ assert.deepStrictEqual(s.frontmatter.requires[2].slice, 'S03', 'third requires slice');
975
+ assert.deepStrictEqual(s.frontmatter.requires[2].provides, 'state engine', 'third requires provides');
976
+ });
1014
977
 
1015
978
  // ═══════════════════════════════════════════════════════════════════════════
1016
979
  // parseContinue tests
1017
980
  // ═══════════════════════════════════════════════════════════════════════════
1018
-
1019
- console.log('\n=== parseContinue: full continue file with all frontmatter fields ===');
1020
- {
981
+ test('parseContinue: full continue file with all frontmatter fields', () => {
1021
982
  const content = `---
1022
983
  milestone: M001
1023
984
  slice: S01
@@ -1052,24 +1013,23 @@ Run the full test suite with node --test.
1052
1013
  const c = parseContinue(content);
1053
1014
 
1054
1015
  // Frontmatter
1055
- assertEq(c.frontmatter.milestone, 'M001', 'continue milestone');
1056
- assertEq(c.frontmatter.slice, 'S01', 'continue slice');
1057
- assertEq(c.frontmatter.task, 'T02', 'continue task');
1058
- assertEq(c.frontmatter.step, 3, 'continue step');
1059
- assertEq(c.frontmatter.totalSteps, 5, 'continue totalSteps');
1060
- assertEq(c.frontmatter.status, 'in_progress', 'continue status');
1061
- assertEq(c.frontmatter.savedAt, '2025-03-10T08:30:00Z', 'continue savedAt');
1016
+ assert.deepStrictEqual(c.frontmatter.milestone, 'M001', 'continue milestone');
1017
+ assert.deepStrictEqual(c.frontmatter.slice, 'S01', 'continue slice');
1018
+ assert.deepStrictEqual(c.frontmatter.task, 'T02', 'continue task');
1019
+ assert.deepStrictEqual(c.frontmatter.step, 3, 'continue step');
1020
+ assert.deepStrictEqual(c.frontmatter.totalSteps, 5, 'continue totalSteps');
1021
+ assert.deepStrictEqual(c.frontmatter.status, 'in_progress', 'continue status');
1022
+ assert.deepStrictEqual(c.frontmatter.savedAt, '2025-03-10T08:30:00Z', 'continue savedAt');
1062
1023
 
1063
1024
  // Body sections
1064
- assertTrue(c.completedWork.includes('Steps 1-3 are done'), 'completedWork content');
1065
- assertTrue(c.remainingWork.includes('Steps 4-5'), 'remainingWork content');
1066
- assertTrue(c.decisions.includes('manual assert pattern'), 'decisions content');
1067
- assertTrue(c.context.includes('gsd-s01 worktree'), 'context content');
1068
- assertTrue(c.nextAction.includes('node --test'), 'nextAction content');
1069
- }
1070
-
1071
- console.log('\n=== parseContinue: string step/totalSteps parsed as integers ===');
1072
- {
1025
+ assert.ok(c.completedWork.includes('Steps 1-3 are done'), 'completedWork content');
1026
+ assert.ok(c.remainingWork.includes('Steps 4-5'), 'remainingWork content');
1027
+ assert.ok(c.decisions.includes('manual assert pattern'), 'decisions content');
1028
+ assert.ok(c.context.includes('gsd-s01 worktree'), 'context content');
1029
+ assert.ok(c.nextAction.includes('node --test'), 'nextAction content');
1030
+ });
1031
+
1032
+ test('parseContinue: string step/totalSteps parsed as integers', () => {
1073
1033
  const content = `---
1074
1034
  milestone: M002
1075
1035
  slice: S03
@@ -1102,14 +1062,13 @@ Continue.
1102
1062
  `;
1103
1063
 
1104
1064
  const c = parseContinue(content);
1105
- assertEq(c.frontmatter.step, 7, 'step parsed as integer 7');
1106
- assertEq(c.frontmatter.totalSteps, 12, 'totalSteps parsed as integer 12');
1107
- assertEq(typeof c.frontmatter.step, 'number', 'step is number type');
1108
- assertEq(typeof c.frontmatter.totalSteps, 'number', 'totalSteps is number type');
1109
- }
1110
-
1111
- console.log('\n=== parseContinue: NaN step values (non-numeric strings) ===');
1112
- {
1065
+ assert.deepStrictEqual(c.frontmatter.step, 7, 'step parsed as integer 7');
1066
+ assert.deepStrictEqual(c.frontmatter.totalSteps, 12, 'totalSteps parsed as integer 12');
1067
+ assert.deepStrictEqual(typeof c.frontmatter.step, 'number', 'step is number type');
1068
+ assert.deepStrictEqual(typeof c.frontmatter.totalSteps, 'number', 'totalSteps is number type');
1069
+ });
1070
+
1071
+ test('parseContinue: NaN step values (non-numeric strings)', () => {
1113
1072
  const content = `---
1114
1073
  milestone: M001
1115
1074
  slice: S01
@@ -1151,12 +1110,11 @@ Do things.
1151
1110
  const totalIsNaN = Number.isNaN(c.frontmatter.totalSteps);
1152
1111
  // The parser does parseInt which returns NaN for non-numeric strings
1153
1112
  // There's no || 0 fallback on the parseInt path, so NaN is expected
1154
- assertTrue(stepIsNaN, 'NaN step when non-numeric string');
1155
- assertTrue(totalIsNaN, 'NaN totalSteps when non-numeric string');
1156
- }
1113
+ assert.ok(stepIsNaN, 'NaN step when non-numeric string');
1114
+ assert.ok(totalIsNaN, 'NaN totalSteps when non-numeric string');
1115
+ });
1157
1116
 
1158
- console.log('\n=== parseContinue: all three status variants ===');
1159
- {
1117
+ test('parseContinue: all three status variants', () => {
1160
1118
  for (const status of ['in_progress', 'interrupted', 'compacted'] as const) {
1161
1119
  const content = `---
1162
1120
  milestone: M001
@@ -1174,12 +1132,11 @@ Work.
1174
1132
  `;
1175
1133
 
1176
1134
  const c = parseContinue(content);
1177
- assertEq(c.frontmatter.status, status, `status variant: ${status}`);
1135
+ assert.deepStrictEqual(c.frontmatter.status, status, `status variant: ${status}`);
1178
1136
  }
1179
- }
1137
+ });
1180
1138
 
1181
- console.log('\n=== parseContinue: missing frontmatter ===');
1182
- {
1139
+ test('parseContinue: missing frontmatter', () => {
1183
1140
  const content = `## Completed Work
1184
1141
 
1185
1142
  Some work done.
@@ -1202,24 +1159,23 @@ Next thing.
1202
1159
  `;
1203
1160
 
1204
1161
  const c = parseContinue(content);
1205
- assertEq(c.frontmatter.milestone, '', 'default milestone empty');
1206
- assertEq(c.frontmatter.slice, '', 'default slice empty');
1207
- assertEq(c.frontmatter.task, '', 'default task empty');
1208
- assertEq(c.frontmatter.step, 0, 'default step 0');
1209
- assertEq(c.frontmatter.totalSteps, 0, 'default totalSteps 0');
1210
- assertEq(c.frontmatter.status, 'in_progress', 'default status in_progress');
1211
- assertEq(c.frontmatter.savedAt, '', 'default savedAt empty');
1162
+ assert.deepStrictEqual(c.frontmatter.milestone, '', 'default milestone empty');
1163
+ assert.deepStrictEqual(c.frontmatter.slice, '', 'default slice empty');
1164
+ assert.deepStrictEqual(c.frontmatter.task, '', 'default task empty');
1165
+ assert.deepStrictEqual(c.frontmatter.step, 0, 'default step 0');
1166
+ assert.deepStrictEqual(c.frontmatter.totalSteps, 0, 'default totalSteps 0');
1167
+ assert.deepStrictEqual(c.frontmatter.status, 'in_progress', 'default status in_progress');
1168
+ assert.deepStrictEqual(c.frontmatter.savedAt, '', 'default savedAt empty');
1212
1169
 
1213
1170
  // Body sections still parse
1214
- assertTrue(c.completedWork.includes('Some work done'), 'completedWork without frontmatter');
1215
- assertTrue(c.remainingWork.includes('More to do'), 'remainingWork without frontmatter');
1216
- assertTrue(c.decisions.includes('A decision'), 'decisions without frontmatter');
1217
- assertTrue(c.context.includes('Some context'), 'context without frontmatter');
1218
- assertTrue(c.nextAction.includes('Next thing'), 'nextAction without frontmatter');
1219
- }
1220
-
1221
- console.log('\n=== parseContinue: body section extraction ===');
1222
- {
1171
+ assert.ok(c.completedWork.includes('Some work done'), 'completedWork without frontmatter');
1172
+ assert.ok(c.remainingWork.includes('More to do'), 'remainingWork without frontmatter');
1173
+ assert.ok(c.decisions.includes('A decision'), 'decisions without frontmatter');
1174
+ assert.ok(c.context.includes('Some context'), 'context without frontmatter');
1175
+ assert.ok(c.nextAction.includes('Next thing'), 'nextAction without frontmatter');
1176
+ });
1177
+
1178
+ test('parseContinue: body section extraction', () => {
1223
1179
  const content = `---
1224
1180
  milestone: M001
1225
1181
  slice: S01
@@ -1253,16 +1209,15 @@ Pick up at step 3: run the integration tests.
1253
1209
  `;
1254
1210
 
1255
1211
  const c = parseContinue(content);
1256
- assertTrue(c.completedWork.includes('First paragraph'), 'completedWork first paragraph');
1257
- assertTrue(c.completedWork.includes('Second paragraph'), 'completedWork second paragraph');
1258
- assertTrue(c.remainingWork.includes('step 3 and step 4'), 'remainingWork detail');
1259
- assertTrue(c.decisions.includes('approach A over approach B'), 'decisions detail');
1260
- assertTrue(c.context.includes('Node 22 required'), 'context detail');
1261
- assertTrue(c.nextAction.includes('step 3: run the integration tests'), 'nextAction detail');
1262
- }
1263
-
1264
- console.log('\n=== parseContinue: total_steps vs totalSteps key support ===');
1265
- {
1212
+ assert.ok(c.completedWork.includes('First paragraph'), 'completedWork first paragraph');
1213
+ assert.ok(c.completedWork.includes('Second paragraph'), 'completedWork second paragraph');
1214
+ assert.ok(c.remainingWork.includes('step 3 and step 4'), 'remainingWork detail');
1215
+ assert.ok(c.decisions.includes('approach A over approach B'), 'decisions detail');
1216
+ assert.ok(c.context.includes('Node 22 required'), 'context detail');
1217
+ assert.ok(c.nextAction.includes('step 3: run the integration tests'), 'nextAction detail');
1218
+ });
1219
+
1220
+ test('parseContinue: total_steps vs totalSteps key support', () => {
1266
1221
  // Test total_steps (snake_case) — the primary format
1267
1222
  const content1 = `---
1268
1223
  milestone: M001
@@ -1280,7 +1235,7 @@ Work.
1280
1235
  `;
1281
1236
 
1282
1237
  const c1 = parseContinue(content1);
1283
- assertEq(c1.frontmatter.totalSteps, 8, 'total_steps snake_case works');
1238
+ assert.deepStrictEqual(c1.frontmatter.totalSteps, 8, 'total_steps snake_case works');
1284
1239
 
1285
1240
  // Test totalSteps (camelCase) — the fallback
1286
1241
  const content2 = `---
@@ -1299,15 +1254,13 @@ Work.
1299
1254
  `;
1300
1255
 
1301
1256
  const c2 = parseContinue(content2);
1302
- assertEq(c2.frontmatter.totalSteps, 6, 'totalSteps camelCase works');
1303
- }
1257
+ assert.deepStrictEqual(c2.frontmatter.totalSteps, 6, 'totalSteps camelCase works');
1258
+ });
1304
1259
 
1305
1260
  // ═══════════════════════════════════════════════════════════════════════════
1306
1261
  // parseRequirementCounts tests
1307
1262
  // ═══════════════════════════════════════════════════════════════════════════
1308
-
1309
- console.log('\n=== parseRequirementCounts: full requirements file ===');
1310
- {
1263
+ test('parseRequirementCounts: full requirements file', () => {
1311
1264
  const content = `# Requirements
1312
1265
 
1313
1266
  ## Active
@@ -1344,27 +1297,25 @@ console.log('\n=== parseRequirementCounts: full requirements file ===');
1344
1297
  `;
1345
1298
 
1346
1299
  const counts = parseRequirementCounts(content);
1347
- assertEq(counts.active, 3, 'active count');
1348
- assertEq(counts.validated, 2, 'validated count');
1349
- assertEq(counts.deferred, 1, 'deferred count');
1350
- assertEq(counts.outOfScope, 2, 'outOfScope count');
1351
- assertEq(counts.blocked, 1, 'blocked count');
1352
- assertEq(counts.total, 8, 'total is sum of active+validated+deferred+outOfScope');
1353
- }
1354
-
1355
- console.log('\n=== parseRequirementCounts: null input returns all zeros ===');
1356
- {
1300
+ assert.deepStrictEqual(counts.active, 3, 'active count');
1301
+ assert.deepStrictEqual(counts.validated, 2, 'validated count');
1302
+ assert.deepStrictEqual(counts.deferred, 1, 'deferred count');
1303
+ assert.deepStrictEqual(counts.outOfScope, 2, 'outOfScope count');
1304
+ assert.deepStrictEqual(counts.blocked, 1, 'blocked count');
1305
+ assert.deepStrictEqual(counts.total, 8, 'total is sum of active+validated+deferred+outOfScope');
1306
+ });
1307
+
1308
+ test('parseRequirementCounts: null input returns all zeros', () => {
1357
1309
  const counts = parseRequirementCounts(null);
1358
- assertEq(counts.active, 0, 'null active');
1359
- assertEq(counts.validated, 0, 'null validated');
1360
- assertEq(counts.deferred, 0, 'null deferred');
1361
- assertEq(counts.outOfScope, 0, 'null outOfScope');
1362
- assertEq(counts.blocked, 0, 'null blocked');
1363
- assertEq(counts.total, 0, 'null total');
1364
- }
1365
-
1366
- console.log('\n=== parseRequirementCounts: empty sections return zero counts ===');
1367
- {
1310
+ assert.deepStrictEqual(counts.active, 0, 'null active');
1311
+ assert.deepStrictEqual(counts.validated, 0, 'null validated');
1312
+ assert.deepStrictEqual(counts.deferred, 0, 'null deferred');
1313
+ assert.deepStrictEqual(counts.outOfScope, 0, 'null outOfScope');
1314
+ assert.deepStrictEqual(counts.blocked, 0, 'null blocked');
1315
+ assert.deepStrictEqual(counts.total, 0, 'null total');
1316
+ });
1317
+
1318
+ test('parseRequirementCounts: empty sections return zero counts', () => {
1368
1319
  const content = `# Requirements
1369
1320
 
1370
1321
  ## Active
@@ -1377,16 +1328,15 @@ console.log('\n=== parseRequirementCounts: empty sections return zero counts ===
1377
1328
  `;
1378
1329
 
1379
1330
  const counts = parseRequirementCounts(content);
1380
- assertEq(counts.active, 0, 'empty active');
1381
- assertEq(counts.validated, 0, 'empty validated');
1382
- assertEq(counts.deferred, 0, 'empty deferred');
1383
- assertEq(counts.outOfScope, 0, 'empty outOfScope');
1384
- assertEq(counts.blocked, 0, 'empty blocked');
1385
- assertEq(counts.total, 0, 'empty total');
1386
- }
1387
-
1388
- console.log('\n=== parseRequirementCounts: blocked status counting ===');
1389
- {
1331
+ assert.deepStrictEqual(counts.active, 0, 'empty active');
1332
+ assert.deepStrictEqual(counts.validated, 0, 'empty validated');
1333
+ assert.deepStrictEqual(counts.deferred, 0, 'empty deferred');
1334
+ assert.deepStrictEqual(counts.outOfScope, 0, 'empty outOfScope');
1335
+ assert.deepStrictEqual(counts.blocked, 0, 'empty blocked');
1336
+ assert.deepStrictEqual(counts.total, 0, 'empty total');
1337
+ });
1338
+
1339
+ test('parseRequirementCounts: blocked status counting', () => {
1390
1340
  const content = `# Requirements
1391
1341
 
1392
1342
  ## Active
@@ -1411,13 +1361,12 @@ console.log('\n=== parseRequirementCounts: blocked status counting ===');
1411
1361
  `;
1412
1362
 
1413
1363
  const counts = parseRequirementCounts(content);
1414
- assertEq(counts.active, 3, 'active includes blocked items in Active section');
1415
- assertEq(counts.blocked, 3, 'blocked counts all blocked statuses across sections');
1416
- assertEq(counts.deferred, 1, 'deferred section count');
1417
- }
1364
+ assert.deepStrictEqual(counts.active, 3, 'active includes blocked items in Active section');
1365
+ assert.deepStrictEqual(counts.blocked, 3, 'blocked counts all blocked statuses across sections');
1366
+ assert.deepStrictEqual(counts.deferred, 1, 'deferred section count');
1367
+ });
1418
1368
 
1419
- console.log('\n=== parseRequirementCounts: total is sum of all section counts ===');
1420
- {
1369
+ test('parseRequirementCounts: total is sum of all section counts', () => {
1421
1370
  const content = `# Requirements
1422
1371
 
1423
1372
  ## Active
@@ -1451,20 +1400,18 @@ console.log('\n=== parseRequirementCounts: total is sum of all section counts ==
1451
1400
  `;
1452
1401
 
1453
1402
  const counts = parseRequirementCounts(content);
1454
- assertEq(counts.active, 1, 'one active');
1455
- assertEq(counts.validated, 2, 'two validated');
1456
- assertEq(counts.deferred, 3, 'three deferred');
1457
- assertEq(counts.outOfScope, 1, 'one outOfScope');
1458
- assertEq(counts.total, 7, 'total = 1 + 2 + 3 + 1');
1459
- assertEq(counts.total, counts.active + counts.validated + counts.deferred + counts.outOfScope, 'total is exact sum');
1460
- }
1403
+ assert.deepStrictEqual(counts.active, 1, 'one active');
1404
+ assert.deepStrictEqual(counts.validated, 2, 'two validated');
1405
+ assert.deepStrictEqual(counts.deferred, 3, 'three deferred');
1406
+ assert.deepStrictEqual(counts.outOfScope, 1, 'one outOfScope');
1407
+ assert.deepStrictEqual(counts.total, 7, 'total = 1 + 2 + 3 + 1');
1408
+ assert.deepStrictEqual(counts.total, counts.active + counts.validated + counts.deferred + counts.outOfScope, 'total is exact sum');
1409
+ });
1461
1410
 
1462
1411
  // ═══════════════════════════════════════════════════════════════════════════
1463
1412
  // parseSecretsManifest / formatSecretsManifest tests
1464
1413
  // ═══════════════════════════════════════════════════════════════════════════
1465
-
1466
- console.log('\n=== parseSecretsManifest: full manifest with 3 keys ===');
1467
- {
1414
+ test('parseSecretsManifest: full manifest with 3 keys', () => {
1468
1415
  const content = `# Secrets Manifest
1469
1416
 
1470
1417
  **Milestone:** M003
@@ -1508,37 +1455,36 @@ console.log('\n=== parseSecretsManifest: full manifest with 3 keys ===');
1508
1455
 
1509
1456
  const m = parseSecretsManifest(content);
1510
1457
 
1511
- assertEq(m.milestone, 'M003', 'manifest milestone');
1512
- assertEq(m.generatedAt, '2025-06-15T10:00:00Z', 'manifest generatedAt');
1513
- assertEq(m.entries.length, 3, 'three entries');
1458
+ assert.deepStrictEqual(m.milestone, 'M003', 'manifest milestone');
1459
+ assert.deepStrictEqual(m.generatedAt, '2025-06-15T10:00:00Z', 'manifest generatedAt');
1460
+ assert.deepStrictEqual(m.entries.length, 3, 'three entries');
1514
1461
 
1515
1462
  // First entry
1516
- assertEq(m.entries[0].key, 'OPENAI_API_KEY', 'entry 0 key');
1517
- assertEq(m.entries[0].service, 'OpenAI', 'entry 0 service');
1518
- assertEq(m.entries[0].dashboardUrl, 'https://platform.openai.com/api-keys', 'entry 0 dashboardUrl');
1519
- assertEq(m.entries[0].formatHint, 'starts with sk-', 'entry 0 formatHint');
1520
- assertEq(m.entries[0].status, 'pending', 'entry 0 status');
1521
- assertEq(m.entries[0].destination, 'dotenv', 'entry 0 destination');
1522
- assertEq(m.entries[0].guidance.length, 3, 'entry 0 guidance count');
1523
- assertEq(m.entries[0].guidance[0], 'Go to https://platform.openai.com/api-keys', 'entry 0 guidance[0]');
1524
- assertEq(m.entries[0].guidance[2], 'Copy the key immediately — it won\'t be shown again', 'entry 0 guidance[2]');
1463
+ assert.deepStrictEqual(m.entries[0].key, 'OPENAI_API_KEY', 'entry 0 key');
1464
+ assert.deepStrictEqual(m.entries[0].service, 'OpenAI', 'entry 0 service');
1465
+ assert.deepStrictEqual(m.entries[0].dashboardUrl, 'https://platform.openai.com/api-keys', 'entry 0 dashboardUrl');
1466
+ assert.deepStrictEqual(m.entries[0].formatHint, 'starts with sk-', 'entry 0 formatHint');
1467
+ assert.deepStrictEqual(m.entries[0].status, 'pending', 'entry 0 status');
1468
+ assert.deepStrictEqual(m.entries[0].destination, 'dotenv', 'entry 0 destination');
1469
+ assert.deepStrictEqual(m.entries[0].guidance.length, 3, 'entry 0 guidance count');
1470
+ assert.deepStrictEqual(m.entries[0].guidance[0], 'Go to https://platform.openai.com/api-keys', 'entry 0 guidance[0]');
1471
+ assert.deepStrictEqual(m.entries[0].guidance[2], 'Copy the key immediately — it won\'t be shown again', 'entry 0 guidance[2]');
1525
1472
 
1526
1473
  // Second entry
1527
- assertEq(m.entries[1].key, 'STRIPE_SECRET_KEY', 'entry 1 key');
1528
- assertEq(m.entries[1].service, 'Stripe', 'entry 1 service');
1529
- assertEq(m.entries[1].status, 'collected', 'entry 1 status');
1530
- assertEq(m.entries[1].formatHint, 'starts with sk_test_ or sk_live_', 'entry 1 formatHint');
1531
- assertEq(m.entries[1].guidance.length, 3, 'entry 1 guidance count');
1474
+ assert.deepStrictEqual(m.entries[1].key, 'STRIPE_SECRET_KEY', 'entry 1 key');
1475
+ assert.deepStrictEqual(m.entries[1].service, 'Stripe', 'entry 1 service');
1476
+ assert.deepStrictEqual(m.entries[1].status, 'collected', 'entry 1 status');
1477
+ assert.deepStrictEqual(m.entries[1].formatHint, 'starts with sk_test_ or sk_live_', 'entry 1 formatHint');
1478
+ assert.deepStrictEqual(m.entries[1].guidance.length, 3, 'entry 1 guidance count');
1532
1479
 
1533
1480
  // Third entry
1534
- assertEq(m.entries[2].key, 'SUPABASE_URL', 'entry 2 key');
1535
- assertEq(m.entries[2].status, 'skipped', 'entry 2 status');
1536
- assertEq(m.entries[2].destination, 'vercel', 'entry 2 destination');
1537
- assertEq(m.entries[2].guidance.length, 2, 'entry 2 guidance count');
1538
- }
1539
-
1540
- console.log('\n=== parseSecretsManifest: single-key manifest ===');
1541
- {
1481
+ assert.deepStrictEqual(m.entries[2].key, 'SUPABASE_URL', 'entry 2 key');
1482
+ assert.deepStrictEqual(m.entries[2].status, 'skipped', 'entry 2 status');
1483
+ assert.deepStrictEqual(m.entries[2].destination, 'vercel', 'entry 2 destination');
1484
+ assert.deepStrictEqual(m.entries[2].guidance.length, 2, 'entry 2 guidance count');
1485
+ });
1486
+
1487
+ test('parseSecretsManifest: single-key manifest', () => {
1542
1488
  const content = `# Secrets Manifest
1543
1489
 
1544
1490
  **Milestone:** M001
@@ -1557,15 +1503,14 @@ console.log('\n=== parseSecretsManifest: single-key manifest ===');
1557
1503
  `;
1558
1504
 
1559
1505
  const m = parseSecretsManifest(content);
1560
- assertEq(m.milestone, 'M001', 'single-key milestone');
1561
- assertEq(m.entries.length, 1, 'single entry');
1562
- assertEq(m.entries[0].key, 'DATABASE_URL', 'single entry key');
1563
- assertEq(m.entries[0].service, 'PostgreSQL', 'single entry service');
1564
- assertEq(m.entries[0].guidance.length, 2, 'single entry guidance count');
1565
- }
1566
-
1567
- console.log('\n=== parseSecretsManifest: empty/no-secrets manifest ===');
1568
- {
1506
+ assert.deepStrictEqual(m.milestone, 'M001', 'single-key milestone');
1507
+ assert.deepStrictEqual(m.entries.length, 1, 'single entry');
1508
+ assert.deepStrictEqual(m.entries[0].key, 'DATABASE_URL', 'single entry key');
1509
+ assert.deepStrictEqual(m.entries[0].service, 'PostgreSQL', 'single entry service');
1510
+ assert.deepStrictEqual(m.entries[0].guidance.length, 2, 'single entry guidance count');
1511
+ });
1512
+
1513
+ test('parseSecretsManifest: empty/no-secrets manifest', () => {
1569
1514
  const content = `# Secrets Manifest
1570
1515
 
1571
1516
  **Milestone:** M002
@@ -1573,13 +1518,12 @@ console.log('\n=== parseSecretsManifest: empty/no-secrets manifest ===');
1573
1518
  `;
1574
1519
 
1575
1520
  const m = parseSecretsManifest(content);
1576
- assertEq(m.milestone, 'M002', 'empty manifest milestone');
1577
- assertEq(m.generatedAt, '2025-06-15T14:00:00Z', 'empty manifest generatedAt');
1578
- assertEq(m.entries.length, 0, 'no entries in empty manifest');
1579
- }
1521
+ assert.deepStrictEqual(m.milestone, 'M002', 'empty manifest milestone');
1522
+ assert.deepStrictEqual(m.generatedAt, '2025-06-15T14:00:00Z', 'empty manifest generatedAt');
1523
+ assert.deepStrictEqual(m.entries.length, 0, 'no entries in empty manifest');
1524
+ });
1580
1525
 
1581
- console.log('\n=== parseSecretsManifest: missing optional fields default correctly ===');
1582
- {
1526
+ test('parseSecretsManifest: missing optional fields default correctly', () => {
1583
1527
  const content = `# Secrets Manifest
1584
1528
 
1585
1529
  **Milestone:** M004
@@ -1593,18 +1537,17 @@ console.log('\n=== parseSecretsManifest: missing optional fields default correct
1593
1537
  `;
1594
1538
 
1595
1539
  const m = parseSecretsManifest(content);
1596
- assertEq(m.entries.length, 1, 'one entry with missing fields');
1597
- assertEq(m.entries[0].key, 'SOME_API_KEY', 'key parsed');
1598
- assertEq(m.entries[0].service, 'SomeService', 'service parsed');
1599
- assertEq(m.entries[0].dashboardUrl, '', 'missing dashboardUrl defaults to empty string');
1600
- assertEq(m.entries[0].formatHint, '', 'missing formatHint defaults to empty string');
1601
- assertEq(m.entries[0].status, 'pending', 'missing status defaults to pending');
1602
- assertEq(m.entries[0].destination, 'dotenv', 'missing destination defaults to dotenv');
1603
- assertEq(m.entries[0].guidance.length, 1, 'guidance still parsed');
1604
- }
1605
-
1606
- console.log('\n=== parseSecretsManifest: all three status values parse ===');
1607
- {
1540
+ assert.deepStrictEqual(m.entries.length, 1, 'one entry with missing fields');
1541
+ assert.deepStrictEqual(m.entries[0].key, 'SOME_API_KEY', 'key parsed');
1542
+ assert.deepStrictEqual(m.entries[0].service, 'SomeService', 'service parsed');
1543
+ assert.deepStrictEqual(m.entries[0].dashboardUrl, '', 'missing dashboardUrl defaults to empty string');
1544
+ assert.deepStrictEqual(m.entries[0].formatHint, '', 'missing formatHint defaults to empty string');
1545
+ assert.deepStrictEqual(m.entries[0].status, 'pending', 'missing status defaults to pending');
1546
+ assert.deepStrictEqual(m.entries[0].destination, 'dotenv', 'missing destination defaults to dotenv');
1547
+ assert.deepStrictEqual(m.entries[0].guidance.length, 1, 'guidance still parsed');
1548
+ });
1549
+
1550
+ test('parseSecretsManifest: all three status values parse', () => {
1608
1551
  for (const status of ['pending', 'collected', 'skipped'] as const) {
1609
1552
  const content = `# Secrets Manifest
1610
1553
 
@@ -1620,12 +1563,11 @@ console.log('\n=== parseSecretsManifest: all three status values parse ===');
1620
1563
  `;
1621
1564
 
1622
1565
  const m = parseSecretsManifest(content);
1623
- assertEq(m.entries[0].status, status, `status variant: ${status}`);
1566
+ assert.deepStrictEqual(m.entries[0].status, status, `status variant: ${status}`);
1624
1567
  }
1625
- }
1568
+ });
1626
1569
 
1627
- console.log('\n=== parseSecretsManifest: invalid status defaults to pending ===');
1628
- {
1570
+ test('parseSecretsManifest: invalid status defaults to pending', () => {
1629
1571
  const content = `# Secrets Manifest
1630
1572
 
1631
1573
  **Milestone:** M006
@@ -1640,11 +1582,10 @@ console.log('\n=== parseSecretsManifest: invalid status defaults to pending ==='
1640
1582
  `;
1641
1583
 
1642
1584
  const m = parseSecretsManifest(content);
1643
- assertEq(m.entries[0].status, 'pending', 'invalid status defaults to pending');
1644
- }
1585
+ assert.deepStrictEqual(m.entries[0].status, 'pending', 'invalid status defaults to pending');
1586
+ });
1645
1587
 
1646
- console.log('\n=== parseSecretsManifest + formatSecretsManifest: round-trip ===');
1647
- {
1588
+ test('parseSecretsManifest + formatSecretsManifest: round-trip', () => {
1648
1589
  const original = `# Secrets Manifest
1649
1590
 
1650
1591
  **Milestone:** M007
@@ -1679,32 +1620,30 @@ console.log('\n=== parseSecretsManifest + formatSecretsManifest: round-trip ==='
1679
1620
  const parsed2 = parseSecretsManifest(formatted);
1680
1621
 
1681
1622
  // Verify semantic equality after round-trip
1682
- assertEq(parsed2.milestone, parsed1.milestone, 'round-trip milestone');
1683
- assertEq(parsed2.generatedAt, parsed1.generatedAt, 'round-trip generatedAt');
1684
- assertEq(parsed2.entries.length, parsed1.entries.length, 'round-trip entry count');
1623
+ assert.deepStrictEqual(parsed2.milestone, parsed1.milestone, 'round-trip milestone');
1624
+ assert.deepStrictEqual(parsed2.generatedAt, parsed1.generatedAt, 'round-trip generatedAt');
1625
+ assert.deepStrictEqual(parsed2.entries.length, parsed1.entries.length, 'round-trip entry count');
1685
1626
 
1686
1627
  for (let i = 0; i < parsed1.entries.length; i++) {
1687
1628
  const e1 = parsed1.entries[i];
1688
1629
  const e2 = parsed2.entries[i];
1689
- assertEq(e2.key, e1.key, `round-trip entry ${i} key`);
1690
- assertEq(e2.service, e1.service, `round-trip entry ${i} service`);
1691
- assertEq(e2.dashboardUrl, e1.dashboardUrl, `round-trip entry ${i} dashboardUrl`);
1692
- assertEq(e2.formatHint, e1.formatHint, `round-trip entry ${i} formatHint`);
1693
- assertEq(e2.status, e1.status, `round-trip entry ${i} status`);
1694
- assertEq(e2.destination, e1.destination, `round-trip entry ${i} destination`);
1695
- assertEq(e2.guidance.length, e1.guidance.length, `round-trip entry ${i} guidance length`);
1630
+ assert.deepStrictEqual(e2.key, e1.key, `round-trip entry ${i} key`);
1631
+ assert.deepStrictEqual(e2.service, e1.service, `round-trip entry ${i} service`);
1632
+ assert.deepStrictEqual(e2.dashboardUrl, e1.dashboardUrl, `round-trip entry ${i} dashboardUrl`);
1633
+ assert.deepStrictEqual(e2.formatHint, e1.formatHint, `round-trip entry ${i} formatHint`);
1634
+ assert.deepStrictEqual(e2.status, e1.status, `round-trip entry ${i} status`);
1635
+ assert.deepStrictEqual(e2.destination, e1.destination, `round-trip entry ${i} destination`);
1636
+ assert.deepStrictEqual(e2.guidance.length, e1.guidance.length, `round-trip entry ${i} guidance length`);
1696
1637
  for (let j = 0; j < e1.guidance.length; j++) {
1697
- assertEq(e2.guidance[j], e1.guidance[j], `round-trip entry ${i} guidance[${j}]`);
1638
+ assert.deepStrictEqual(e2.guidance[j], e1.guidance[j], `round-trip entry ${i} guidance[${j}]`);
1698
1639
  }
1699
1640
  }
1700
- }
1641
+ });
1701
1642
 
1702
1643
  // ═══════════════════════════════════════════════════════════════════════════
1703
1644
  // LLM-style round-trip tests — realistic manifest variations
1704
1645
  // ═══════════════════════════════════════════════════════════════════════════
1705
-
1706
- console.log('\n=== LLM round-trip: extra whitespace ===');
1707
- {
1646
+ test('LLM round-trip: extra whitespace', () => {
1708
1647
  // LLMs often produce inconsistent indentation and trailing spaces
1709
1648
  const messy = `# Secrets Manifest
1710
1649
 
@@ -1735,34 +1674,33 @@ console.log('\n=== LLM round-trip: extra whitespace ===');
1735
1674
  const formatted = formatSecretsManifest(parsed1);
1736
1675
  const parsed2 = parseSecretsManifest(formatted);
1737
1676
 
1738
- assertEq(parsed2.milestone, parsed1.milestone, 'whitespace round-trip milestone');
1739
- assertEq(parsed2.generatedAt, parsed1.generatedAt, 'whitespace round-trip generatedAt');
1740
- assertEq(parsed2.entries.length, parsed1.entries.length, 'whitespace round-trip entry count');
1741
- assertEq(parsed2.entries.length, 2, 'whitespace: two entries parsed');
1677
+ assert.deepStrictEqual(parsed2.milestone, parsed1.milestone, 'whitespace round-trip milestone');
1678
+ assert.deepStrictEqual(parsed2.generatedAt, parsed1.generatedAt, 'whitespace round-trip generatedAt');
1679
+ assert.deepStrictEqual(parsed2.entries.length, parsed1.entries.length, 'whitespace round-trip entry count');
1680
+ assert.deepStrictEqual(parsed2.entries.length, 2, 'whitespace: two entries parsed');
1742
1681
 
1743
1682
  for (let i = 0; i < parsed1.entries.length; i++) {
1744
1683
  const e1 = parsed1.entries[i];
1745
1684
  const e2 = parsed2.entries[i];
1746
- assertEq(e2.key, e1.key, `whitespace round-trip entry ${i} key`);
1747
- assertEq(e2.service, e1.service, `whitespace round-trip entry ${i} service`);
1748
- assertEq(e2.dashboardUrl, e1.dashboardUrl, `whitespace round-trip entry ${i} dashboardUrl`);
1749
- assertEq(e2.formatHint, e1.formatHint, `whitespace round-trip entry ${i} formatHint`);
1750
- assertEq(e2.status, e1.status, `whitespace round-trip entry ${i} status`);
1751
- assertEq(e2.destination, e1.destination, `whitespace round-trip entry ${i} destination`);
1752
- assertEq(e2.guidance.length, e1.guidance.length, `whitespace round-trip entry ${i} guidance length`);
1685
+ assert.deepStrictEqual(e2.key, e1.key, `whitespace round-trip entry ${i} key`);
1686
+ assert.deepStrictEqual(e2.service, e1.service, `whitespace round-trip entry ${i} service`);
1687
+ assert.deepStrictEqual(e2.dashboardUrl, e1.dashboardUrl, `whitespace round-trip entry ${i} dashboardUrl`);
1688
+ assert.deepStrictEqual(e2.formatHint, e1.formatHint, `whitespace round-trip entry ${i} formatHint`);
1689
+ assert.deepStrictEqual(e2.status, e1.status, `whitespace round-trip entry ${i} status`);
1690
+ assert.deepStrictEqual(e2.destination, e1.destination, `whitespace round-trip entry ${i} destination`);
1691
+ assert.deepStrictEqual(e2.guidance.length, e1.guidance.length, `whitespace round-trip entry ${i} guidance length`);
1753
1692
  for (let j = 0; j < e1.guidance.length; j++) {
1754
- assertEq(e2.guidance[j], e1.guidance[j], `whitespace round-trip entry ${i} guidance[${j}]`);
1693
+ assert.deepStrictEqual(e2.guidance[j], e1.guidance[j], `whitespace round-trip entry ${i} guidance[${j}]`);
1755
1694
  }
1756
1695
  }
1757
1696
 
1758
1697
  // Verify the parser correctly stripped trailing whitespace
1759
- assertEq(parsed1.milestone, 'M010', 'whitespace: milestone trimmed');
1760
- assertEq(parsed1.entries[0].key, 'OPENAI_API_KEY', 'whitespace: key trimmed');
1761
- assertEq(parsed1.entries[0].service, 'OpenAI', 'whitespace: service trimmed');
1762
- }
1698
+ assert.deepStrictEqual(parsed1.milestone, 'M010', 'whitespace: milestone trimmed');
1699
+ assert.deepStrictEqual(parsed1.entries[0].key, 'OPENAI_API_KEY', 'whitespace: key trimmed');
1700
+ assert.deepStrictEqual(parsed1.entries[0].service, 'OpenAI', 'whitespace: service trimmed');
1701
+ });
1763
1702
 
1764
- console.log('\n=== LLM round-trip: missing optional fields ===');
1765
- {
1703
+ test('LLM round-trip: missing optional fields', () => {
1766
1704
  // LLMs may omit Dashboard and Format hint lines entirely
1767
1705
  const minimal = `# Secrets Manifest
1768
1706
 
@@ -1790,32 +1728,31 @@ console.log('\n=== LLM round-trip: missing optional fields ===');
1790
1728
  const parsed1 = parseSecretsManifest(minimal);
1791
1729
 
1792
1730
  // Verify missing optional fields get defaults
1793
- assertEq(parsed1.entries[0].dashboardUrl, '', 'missing-optional: no dashboard → empty string');
1794
- assertEq(parsed1.entries[0].formatHint, '', 'missing-optional: no format hint → empty string');
1795
- assertEq(parsed1.entries[1].dashboardUrl, '', 'missing-optional: entry 2 no dashboard → empty string');
1796
- assertEq(parsed1.entries[1].formatHint, '', 'missing-optional: entry 2 no format hint → empty string');
1731
+ assert.deepStrictEqual(parsed1.entries[0].dashboardUrl, '', 'missing-optional: no dashboard → empty string');
1732
+ assert.deepStrictEqual(parsed1.entries[0].formatHint, '', 'missing-optional: no format hint → empty string');
1733
+ assert.deepStrictEqual(parsed1.entries[1].dashboardUrl, '', 'missing-optional: entry 2 no dashboard → empty string');
1734
+ assert.deepStrictEqual(parsed1.entries[1].formatHint, '', 'missing-optional: entry 2 no format hint → empty string');
1797
1735
 
1798
1736
  // Round-trip: formatter omits empty optional fields, re-parse preserves defaults
1799
1737
  const formatted = formatSecretsManifest(parsed1);
1800
1738
  const parsed2 = parseSecretsManifest(formatted);
1801
1739
 
1802
- assertEq(parsed2.entries.length, parsed1.entries.length, 'missing-optional round-trip entry count');
1740
+ assert.deepStrictEqual(parsed2.entries.length, parsed1.entries.length, 'missing-optional round-trip entry count');
1803
1741
 
1804
1742
  for (let i = 0; i < parsed1.entries.length; i++) {
1805
1743
  const e1 = parsed1.entries[i];
1806
1744
  const e2 = parsed2.entries[i];
1807
- assertEq(e2.key, e1.key, `missing-optional round-trip entry ${i} key`);
1808
- assertEq(e2.service, e1.service, `missing-optional round-trip entry ${i} service`);
1809
- assertEq(e2.dashboardUrl, e1.dashboardUrl, `missing-optional round-trip entry ${i} dashboardUrl`);
1810
- assertEq(e2.formatHint, e1.formatHint, `missing-optional round-trip entry ${i} formatHint`);
1811
- assertEq(e2.status, e1.status, `missing-optional round-trip entry ${i} status`);
1812
- assertEq(e2.destination, e1.destination, `missing-optional round-trip entry ${i} destination`);
1813
- assertEq(e2.guidance.length, e1.guidance.length, `missing-optional round-trip entry ${i} guidance length`);
1745
+ assert.deepStrictEqual(e2.key, e1.key, `missing-optional round-trip entry ${i} key`);
1746
+ assert.deepStrictEqual(e2.service, e1.service, `missing-optional round-trip entry ${i} service`);
1747
+ assert.deepStrictEqual(e2.dashboardUrl, e1.dashboardUrl, `missing-optional round-trip entry ${i} dashboardUrl`);
1748
+ assert.deepStrictEqual(e2.formatHint, e1.formatHint, `missing-optional round-trip entry ${i} formatHint`);
1749
+ assert.deepStrictEqual(e2.status, e1.status, `missing-optional round-trip entry ${i} status`);
1750
+ assert.deepStrictEqual(e2.destination, e1.destination, `missing-optional round-trip entry ${i} destination`);
1751
+ assert.deepStrictEqual(e2.guidance.length, e1.guidance.length, `missing-optional round-trip entry ${i} guidance length`);
1814
1752
  }
1815
- }
1753
+ });
1816
1754
 
1817
- console.log('\n=== LLM round-trip: extra blank lines ===');
1818
- {
1755
+ test('LLM round-trip: extra blank lines', () => {
1819
1756
  // LLMs sometimes insert excessive blank lines between sections
1820
1757
  const blanky = `# Secrets Manifest
1821
1758
 
@@ -1859,42 +1796,40 @@ console.log('\n=== LLM round-trip: extra blank lines ===');
1859
1796
 
1860
1797
  const parsed1 = parseSecretsManifest(blanky);
1861
1798
 
1862
- assertEq(parsed1.entries.length, 2, 'blank-lines: two entries parsed');
1863
- assertEq(parsed1.milestone, 'M012', 'blank-lines: milestone parsed');
1864
- assertEq(parsed1.entries[0].key, 'API_KEY_ONE', 'blank-lines: first key');
1865
- assertEq(parsed1.entries[0].guidance.length, 2, 'blank-lines: first entry guidance count');
1866
- assertEq(parsed1.entries[1].key, 'API_KEY_TWO', 'blank-lines: second key');
1867
- assertEq(parsed1.entries[1].status, 'skipped', 'blank-lines: second entry status');
1799
+ assert.deepStrictEqual(parsed1.entries.length, 2, 'blank-lines: two entries parsed');
1800
+ assert.deepStrictEqual(parsed1.milestone, 'M012', 'blank-lines: milestone parsed');
1801
+ assert.deepStrictEqual(parsed1.entries[0].key, 'API_KEY_ONE', 'blank-lines: first key');
1802
+ assert.deepStrictEqual(parsed1.entries[0].guidance.length, 2, 'blank-lines: first entry guidance count');
1803
+ assert.deepStrictEqual(parsed1.entries[1].key, 'API_KEY_TWO', 'blank-lines: second key');
1804
+ assert.deepStrictEqual(parsed1.entries[1].status, 'skipped', 'blank-lines: second entry status');
1868
1805
 
1869
1806
  // Round-trip produces clean output
1870
1807
  const formatted = formatSecretsManifest(parsed1);
1871
1808
  const parsed2 = parseSecretsManifest(formatted);
1872
1809
 
1873
- assertEq(parsed2.entries.length, parsed1.entries.length, 'blank-lines round-trip entry count');
1810
+ assert.deepStrictEqual(parsed2.entries.length, parsed1.entries.length, 'blank-lines round-trip entry count');
1874
1811
 
1875
1812
  for (let i = 0; i < parsed1.entries.length; i++) {
1876
1813
  const e1 = parsed1.entries[i];
1877
1814
  const e2 = parsed2.entries[i];
1878
- assertEq(e2.key, e1.key, `blank-lines round-trip entry ${i} key`);
1879
- assertEq(e2.service, e1.service, `blank-lines round-trip entry ${i} service`);
1880
- assertEq(e2.dashboardUrl, e1.dashboardUrl, `blank-lines round-trip entry ${i} dashboardUrl`);
1881
- assertEq(e2.formatHint, e1.formatHint, `blank-lines round-trip entry ${i} formatHint`);
1882
- assertEq(e2.status, e1.status, `blank-lines round-trip entry ${i} status`);
1883
- assertEq(e2.destination, e1.destination, `blank-lines round-trip entry ${i} destination`);
1884
- assertEq(e2.guidance.length, e1.guidance.length, `blank-lines round-trip entry ${i} guidance length`);
1815
+ assert.deepStrictEqual(e2.key, e1.key, `blank-lines round-trip entry ${i} key`);
1816
+ assert.deepStrictEqual(e2.service, e1.service, `blank-lines round-trip entry ${i} service`);
1817
+ assert.deepStrictEqual(e2.dashboardUrl, e1.dashboardUrl, `blank-lines round-trip entry ${i} dashboardUrl`);
1818
+ assert.deepStrictEqual(e2.formatHint, e1.formatHint, `blank-lines round-trip entry ${i} formatHint`);
1819
+ assert.deepStrictEqual(e2.status, e1.status, `blank-lines round-trip entry ${i} status`);
1820
+ assert.deepStrictEqual(e2.destination, e1.destination, `blank-lines round-trip entry ${i} destination`);
1821
+ assert.deepStrictEqual(e2.guidance.length, e1.guidance.length, `blank-lines round-trip entry ${i} guidance length`);
1885
1822
  }
1886
1823
 
1887
1824
  // Verify the formatted output is cleaner (fewer consecutive blank lines)
1888
1825
  const consecutiveBlanks = formatted.match(/\n{4,}/g);
1889
- assertTrue(consecutiveBlanks === null, 'blank-lines: formatted output has no 4+ consecutive newlines');
1890
- }
1826
+ assert.ok(consecutiveBlanks === null, 'blank-lines: formatted output has no 4+ consecutive newlines');
1827
+ });
1891
1828
 
1892
1829
  // ═══════════════════════════════════════════════════════════════════════════
1893
1830
  // parseRoadmap: boundary map with embedded code fences (#468)
1894
1831
  // ═══════════════════════════════════════════════════════════════════════════
1895
-
1896
- console.log('\n=== parseRoadmap: boundary map with code fences (#468) ===');
1897
- {
1832
+ test('parseRoadmap: boundary map with code fences (#468)', () => {
1898
1833
  const content = `# M001: Test
1899
1834
 
1900
1835
  **Vision:** Test
@@ -1923,10 +1858,10 @@ Consumes: nothing
1923
1858
  const r = parseRoadmap(content);
1924
1859
  const elapsed = Date.now() - start;
1925
1860
 
1926
- assertTrue(elapsed < 1000, `boundary map with code fences parsed in ${elapsed}ms (should be < 1s)`);
1927
- assertEq(r.slices.length, 2, 'code-fence roadmap: slice count');
1861
+ assert.ok(elapsed < 1000, `boundary map with code fences parsed in ${elapsed}ms (should be < 1s)`);
1862
+ assert.deepStrictEqual(r.slices.length, 2, 'code-fence roadmap: slice count');
1928
1863
  // Boundary map should still parse (may not capture perfectly with code fences, but must not hang)
1929
- assertTrue(r.boundaryMap.length >= 0, 'code-fence roadmap: boundary map parsed without hanging');
1930
- }
1864
+ assert.ok(r.boundaryMap.length >= 0, 'code-fence roadmap: boundary map parsed without hanging');
1865
+ });
1931
1866
 
1932
- report();
1867
+ });