gsd-pi 2.10.12 → 2.12.0

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 (504) hide show
  1. package/dist/bundled-extension-paths.d.ts +2 -0
  2. package/dist/bundled-extension-paths.js +10 -0
  3. package/dist/loader.js +45 -28
  4. package/dist/onboarding.js +100 -0
  5. package/dist/resource-loader.js +12 -3
  6. package/dist/resources/GSD-WORKFLOW.md +661 -0
  7. package/dist/resources/agents/researcher.md +29 -0
  8. package/dist/resources/agents/scout.md +56 -0
  9. package/dist/resources/agents/worker.md +31 -0
  10. package/dist/resources/extensions/ask-user-questions.ts +289 -0
  11. package/dist/resources/extensions/async-jobs/async-bash-tool.ts +211 -0
  12. package/dist/resources/extensions/async-jobs/await-tool.ts +101 -0
  13. package/dist/resources/extensions/async-jobs/cancel-job-tool.ts +34 -0
  14. package/dist/resources/extensions/async-jobs/index.ts +133 -0
  15. package/dist/resources/extensions/async-jobs/job-manager.ts +211 -0
  16. package/dist/resources/extensions/bg-shell/index.ts +3179 -0
  17. package/dist/resources/extensions/browser-tools/BROWSER-TOOLS-V2-PROPOSAL.md +1277 -0
  18. package/dist/resources/extensions/browser-tools/capture.ts +165 -0
  19. package/dist/resources/extensions/browser-tools/core.d.ts +205 -0
  20. package/dist/resources/extensions/browser-tools/core.js +1057 -0
  21. package/dist/resources/extensions/browser-tools/evaluate-helpers.ts +184 -0
  22. package/dist/resources/extensions/browser-tools/index.ts +51 -0
  23. package/dist/resources/extensions/browser-tools/lifecycle.ts +270 -0
  24. package/dist/resources/extensions/browser-tools/package.json +24 -0
  25. package/dist/resources/extensions/browser-tools/refs.ts +264 -0
  26. package/dist/resources/extensions/browser-tools/settle.ts +197 -0
  27. package/dist/resources/extensions/browser-tools/state.ts +408 -0
  28. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +652 -0
  29. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +614 -0
  30. package/dist/resources/extensions/browser-tools/tools/assertions.ts +342 -0
  31. package/dist/resources/extensions/browser-tools/tools/forms.ts +801 -0
  32. package/dist/resources/extensions/browser-tools/tools/inspection.ts +492 -0
  33. package/dist/resources/extensions/browser-tools/tools/intent.ts +614 -0
  34. package/dist/resources/extensions/browser-tools/tools/interaction.ts +865 -0
  35. package/dist/resources/extensions/browser-tools/tools/navigation.ts +232 -0
  36. package/dist/resources/extensions/browser-tools/tools/pages.ts +303 -0
  37. package/dist/resources/extensions/browser-tools/tools/refs.ts +541 -0
  38. package/dist/resources/extensions/browser-tools/tools/screenshot.ts +83 -0
  39. package/dist/resources/extensions/browser-tools/tools/session.ts +400 -0
  40. package/dist/resources/extensions/browser-tools/tools/wait.ts +247 -0
  41. package/dist/resources/extensions/browser-tools/utils.ts +660 -0
  42. package/dist/resources/extensions/context7/index.ts +428 -0
  43. package/dist/resources/extensions/context7/package.json +11 -0
  44. package/dist/resources/extensions/get-secrets-from-user.ts +625 -0
  45. package/dist/resources/extensions/google-search/index.ts +336 -0
  46. package/dist/resources/extensions/google-search/package.json +9 -0
  47. package/dist/resources/extensions/gsd/activity-log.ts +116 -0
  48. package/dist/resources/extensions/gsd/auto.ts +3732 -0
  49. package/dist/resources/extensions/gsd/commands.ts +544 -0
  50. package/dist/resources/extensions/gsd/crash-recovery.ts +105 -0
  51. package/dist/resources/extensions/gsd/dashboard-overlay.ts +564 -0
  52. package/dist/resources/extensions/gsd/dispatch-guard.ts +78 -0
  53. package/dist/resources/extensions/gsd/docs/preferences-reference.md +278 -0
  54. package/dist/resources/extensions/gsd/doctor.ts +766 -0
  55. package/dist/resources/extensions/gsd/exit-command.ts +18 -0
  56. package/dist/resources/extensions/gsd/files.ts +857 -0
  57. package/dist/resources/extensions/gsd/git-service.ts +904 -0
  58. package/dist/resources/extensions/gsd/gitignore.ts +200 -0
  59. package/dist/resources/extensions/gsd/guided-flow.ts +1054 -0
  60. package/dist/resources/extensions/gsd/index.ts +735 -0
  61. package/dist/resources/extensions/gsd/metrics.ts +374 -0
  62. package/dist/resources/extensions/gsd/migrate/command.ts +218 -0
  63. package/dist/resources/extensions/gsd/migrate/index.ts +42 -0
  64. package/dist/resources/extensions/gsd/migrate/parser.ts +323 -0
  65. package/dist/resources/extensions/gsd/migrate/parsers.ts +624 -0
  66. package/dist/resources/extensions/gsd/migrate/preview.ts +48 -0
  67. package/dist/resources/extensions/gsd/migrate/transformer.ts +346 -0
  68. package/dist/resources/extensions/gsd/migrate/types.ts +370 -0
  69. package/dist/resources/extensions/gsd/migrate/validator.ts +55 -0
  70. package/dist/resources/extensions/gsd/migrate/writer.ts +539 -0
  71. package/dist/resources/extensions/gsd/native-git-bridge.ts +181 -0
  72. package/dist/resources/extensions/gsd/native-parser-bridge.ts +135 -0
  73. package/dist/resources/extensions/gsd/observability-validator.ts +408 -0
  74. package/dist/resources/extensions/gsd/package.json +11 -0
  75. package/dist/resources/extensions/gsd/paths.ts +339 -0
  76. package/dist/resources/extensions/gsd/post-unit-hooks.ts +449 -0
  77. package/dist/resources/extensions/gsd/preferences.ts +988 -0
  78. package/dist/resources/extensions/gsd/prompt-loader.ts +92 -0
  79. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +25 -0
  80. package/dist/resources/extensions/gsd/prompts/complete-slice.md +27 -0
  81. package/dist/resources/extensions/gsd/prompts/discuss.md +237 -0
  82. package/dist/resources/extensions/gsd/prompts/doctor-heal.md +29 -0
  83. package/dist/resources/extensions/gsd/prompts/execute-task.md +63 -0
  84. package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +3 -0
  85. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +5 -0
  86. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +61 -0
  87. package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +3 -0
  88. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +29 -0
  89. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +3 -0
  90. package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +13 -0
  91. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -0
  92. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +78 -0
  93. package/dist/resources/extensions/gsd/prompts/plan-slice.md +55 -0
  94. package/dist/resources/extensions/gsd/prompts/queue.md +100 -0
  95. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +48 -0
  96. package/dist/resources/extensions/gsd/prompts/replan-slice.md +39 -0
  97. package/dist/resources/extensions/gsd/prompts/research-milestone.md +37 -0
  98. package/dist/resources/extensions/gsd/prompts/research-slice.md +28 -0
  99. package/dist/resources/extensions/gsd/prompts/review-migration.md +66 -0
  100. package/dist/resources/extensions/gsd/prompts/run-uat.md +109 -0
  101. package/dist/resources/extensions/gsd/prompts/system.md +190 -0
  102. package/dist/resources/extensions/gsd/prompts/worktree-merge.md +123 -0
  103. package/dist/resources/extensions/gsd/roadmap-slices.ts +50 -0
  104. package/dist/resources/extensions/gsd/session-forensics.ts +500 -0
  105. package/dist/resources/extensions/gsd/skill-discovery.ts +137 -0
  106. package/dist/resources/extensions/gsd/state.ts +569 -0
  107. package/dist/resources/extensions/gsd/templates/context.md +76 -0
  108. package/dist/resources/extensions/gsd/templates/decisions.md +8 -0
  109. package/dist/resources/extensions/gsd/templates/milestone-summary.md +73 -0
  110. package/dist/resources/extensions/gsd/templates/plan.md +131 -0
  111. package/dist/resources/extensions/gsd/templates/preferences.md +23 -0
  112. package/dist/resources/extensions/gsd/templates/project.md +31 -0
  113. package/dist/resources/extensions/gsd/templates/reassessment.md +28 -0
  114. package/dist/resources/extensions/gsd/templates/requirements.md +81 -0
  115. package/dist/resources/extensions/gsd/templates/research.md +46 -0
  116. package/dist/resources/extensions/gsd/templates/roadmap.md +118 -0
  117. package/dist/resources/extensions/gsd/templates/secrets-manifest.md +22 -0
  118. package/dist/resources/extensions/gsd/templates/slice-context.md +58 -0
  119. package/dist/resources/extensions/gsd/templates/slice-summary.md +99 -0
  120. package/dist/resources/extensions/gsd/templates/state.md +19 -0
  121. package/dist/resources/extensions/gsd/templates/task-plan.md +52 -0
  122. package/dist/resources/extensions/gsd/templates/task-summary.md +57 -0
  123. package/dist/resources/extensions/gsd/templates/uat.md +54 -0
  124. package/dist/resources/extensions/gsd/tests/activity-log-prune.test.ts +297 -0
  125. package/dist/resources/extensions/gsd/tests/activity-log-save.test.ts +127 -0
  126. package/dist/resources/extensions/gsd/tests/auto-draft-pause.test.ts +109 -0
  127. package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +45 -0
  128. package/dist/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +196 -0
  129. package/dist/resources/extensions/gsd/tests/auto-supervisor.test.mjs +53 -0
  130. package/dist/resources/extensions/gsd/tests/collect-from-manifest.test.ts +469 -0
  131. package/dist/resources/extensions/gsd/tests/complete-milestone.test.ts +200 -0
  132. package/dist/resources/extensions/gsd/tests/cost-projection.test.ts +134 -0
  133. package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +312 -0
  134. package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +299 -0
  135. package/dist/resources/extensions/gsd/tests/derive-state.test.ts +660 -0
  136. package/dist/resources/extensions/gsd/tests/discuss-prompt.test.ts +27 -0
  137. package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +91 -0
  138. package/dist/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +170 -0
  139. package/dist/resources/extensions/gsd/tests/doctor.test.ts +594 -0
  140. package/dist/resources/extensions/gsd/tests/draft-promotion.test.ts +169 -0
  141. package/dist/resources/extensions/gsd/tests/exit-command.test.ts +50 -0
  142. package/dist/resources/extensions/gsd/tests/git-service.test.ts +1788 -0
  143. package/dist/resources/extensions/gsd/tests/idle-recovery.test.ts +577 -0
  144. package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +580 -0
  145. package/dist/resources/extensions/gsd/tests/manifest-status.test.ts +283 -0
  146. package/dist/resources/extensions/gsd/tests/metrics-io.test.ts +176 -0
  147. package/dist/resources/extensions/gsd/tests/metrics.test.ts +188 -0
  148. package/dist/resources/extensions/gsd/tests/migrate-command.test.ts +369 -0
  149. package/dist/resources/extensions/gsd/tests/migrate-parser.test.ts +757 -0
  150. package/dist/resources/extensions/gsd/tests/migrate-transformer.test.ts +635 -0
  151. package/dist/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +414 -0
  152. package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +297 -0
  153. package/dist/resources/extensions/gsd/tests/migrate-writer.test.ts +398 -0
  154. package/dist/resources/extensions/gsd/tests/must-have-parser.test.ts +291 -0
  155. package/dist/resources/extensions/gsd/tests/network-error-fallback.test.ts +54 -0
  156. package/dist/resources/extensions/gsd/tests/next-milestone-id.test.ts +66 -0
  157. package/dist/resources/extensions/gsd/tests/orphaned-branch.test.ts +353 -0
  158. package/dist/resources/extensions/gsd/tests/parsers.test.ts +1664 -0
  159. package/dist/resources/extensions/gsd/tests/plan-milestone.test.ts +133 -0
  160. package/dist/resources/extensions/gsd/tests/plan-quality-validator.test.ts +363 -0
  161. package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +297 -0
  162. package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +226 -0
  163. package/dist/resources/extensions/gsd/tests/queue-draft-detection.test.ts +126 -0
  164. package/dist/resources/extensions/gsd/tests/reassess-prompt.test.ts +142 -0
  165. package/dist/resources/extensions/gsd/tests/regex-hardening.test.ts +281 -0
  166. package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +155 -0
  167. package/dist/resources/extensions/gsd/tests/remote-status.test.ts +99 -0
  168. package/dist/resources/extensions/gsd/tests/replan-slice.test.ts +493 -0
  169. package/dist/resources/extensions/gsd/tests/requirements.test.ts +106 -0
  170. package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +35 -0
  171. package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +11 -0
  172. package/dist/resources/extensions/gsd/tests/roadmap-slices.test.ts +41 -0
  173. package/dist/resources/extensions/gsd/tests/run-uat.test.ts +316 -0
  174. package/dist/resources/extensions/gsd/tests/secure-env-collect.test.ts +185 -0
  175. package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +123 -0
  176. package/dist/resources/extensions/gsd/tests/test-helpers.ts +61 -0
  177. package/dist/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +219 -0
  178. package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +234 -0
  179. package/dist/resources/extensions/gsd/tests/workspace-index.test.ts +75 -0
  180. package/dist/resources/extensions/gsd/tests/worktree-integration.test.ts +234 -0
  181. package/dist/resources/extensions/gsd/tests/worktree-manager.test.ts +141 -0
  182. package/dist/resources/extensions/gsd/tests/worktree.test.ts +597 -0
  183. package/dist/resources/extensions/gsd/tests/write-gate.test.ts +122 -0
  184. package/dist/resources/extensions/gsd/types.ts +296 -0
  185. package/dist/resources/extensions/gsd/unit-runtime.ts +184 -0
  186. package/dist/resources/extensions/gsd/workspace-index.ts +189 -0
  187. package/dist/resources/extensions/gsd/worktree-command.ts +845 -0
  188. package/dist/resources/extensions/gsd/worktree-manager.ts +459 -0
  189. package/dist/resources/extensions/gsd/{worktree.js → worktree.ts} +92 -61
  190. package/dist/resources/extensions/mac-tools/index.ts +852 -0
  191. package/dist/resources/extensions/mac-tools/swift-cli/Package.swift +22 -0
  192. package/dist/resources/extensions/mac-tools/swift-cli/Sources/main.swift +1318 -0
  193. package/dist/resources/extensions/mcporter/index.ts +429 -0
  194. package/dist/resources/extensions/remote-questions/config.ts +81 -0
  195. package/dist/resources/extensions/remote-questions/discord-adapter.ts +128 -0
  196. package/dist/resources/extensions/remote-questions/format.ts +163 -0
  197. package/dist/resources/extensions/remote-questions/manager.ts +192 -0
  198. package/dist/resources/extensions/remote-questions/remote-command.ts +361 -0
  199. package/dist/resources/extensions/remote-questions/slack-adapter.ts +92 -0
  200. package/dist/resources/extensions/remote-questions/status.ts +31 -0
  201. package/dist/resources/extensions/remote-questions/store.ts +77 -0
  202. package/dist/resources/extensions/remote-questions/types.ts +75 -0
  203. package/dist/resources/extensions/search-the-web/cache.ts +78 -0
  204. package/dist/resources/extensions/search-the-web/command-search-provider.ts +99 -0
  205. package/dist/resources/extensions/search-the-web/format.ts +258 -0
  206. package/dist/resources/extensions/search-the-web/http.ts +238 -0
  207. package/dist/resources/extensions/search-the-web/index.ts +65 -0
  208. package/dist/resources/extensions/search-the-web/native-search.ts +165 -0
  209. package/dist/resources/extensions/search-the-web/provider.ts +136 -0
  210. package/dist/resources/extensions/search-the-web/tavily.ts +116 -0
  211. package/dist/resources/extensions/search-the-web/tool-fetch-page.ts +581 -0
  212. package/dist/resources/extensions/search-the-web/tool-llm-context.ts +620 -0
  213. package/dist/resources/extensions/search-the-web/tool-search.ts +635 -0
  214. package/dist/resources/extensions/search-the-web/url-utils.ts +91 -0
  215. package/dist/resources/extensions/shared/bundled-extension-paths.ts +11 -0
  216. package/dist/resources/extensions/shared/confirm-ui.ts +126 -0
  217. package/dist/resources/extensions/shared/interview-ui.ts +613 -0
  218. package/dist/resources/extensions/shared/next-action-ui.ts +197 -0
  219. package/dist/resources/extensions/shared/progress-widget.ts +282 -0
  220. package/dist/resources/extensions/shared/terminal.ts +23 -0
  221. package/dist/resources/extensions/shared/thinking-widget.ts +107 -0
  222. package/dist/resources/extensions/shared/ui.ts +400 -0
  223. package/dist/resources/extensions/shared/wizard-ui.ts +551 -0
  224. package/dist/resources/extensions/slash-commands/audit.ts +88 -0
  225. package/dist/resources/extensions/slash-commands/clear.ts +10 -0
  226. package/dist/resources/extensions/slash-commands/create-extension.ts +297 -0
  227. package/dist/resources/extensions/slash-commands/create-slash-command.ts +234 -0
  228. package/dist/resources/extensions/slash-commands/index.ts +12 -0
  229. package/dist/resources/extensions/subagent/agents.ts +126 -0
  230. package/dist/resources/extensions/subagent/index.ts +1072 -0
  231. package/dist/resources/extensions/subagent/isolation.ts +498 -0
  232. package/dist/resources/extensions/ttsr/index.ts +163 -0
  233. package/dist/resources/extensions/ttsr/rule-loader.ts +121 -0
  234. package/dist/resources/extensions/ttsr/ttsr-interrupt.md +6 -0
  235. package/dist/resources/extensions/ttsr/ttsr-manager.ts +430 -0
  236. package/dist/resources/extensions/universal-config/discovery.ts +78 -0
  237. package/dist/resources/extensions/universal-config/format.ts +160 -0
  238. package/dist/resources/extensions/universal-config/index.ts +118 -0
  239. package/dist/resources/extensions/universal-config/package.json +11 -0
  240. package/dist/resources/extensions/universal-config/scanners.ts +579 -0
  241. package/dist/resources/extensions/universal-config/tests/discovery.test.ts +111 -0
  242. package/dist/resources/extensions/universal-config/tests/format.test.ts +111 -0
  243. package/dist/resources/extensions/universal-config/tests/scanners.test.ts +438 -0
  244. package/dist/resources/extensions/universal-config/tools.ts +60 -0
  245. package/dist/resources/extensions/universal-config/types.ts +116 -0
  246. package/dist/resources/extensions/voice/index.ts +271 -0
  247. package/dist/resources/extensions/voice/speech-recognizer.py +504 -0
  248. package/dist/resources/extensions/voice/speech-recognizer.swift +154 -0
  249. package/dist/resources/skills/debug-like-expert/SKILL.md +231 -0
  250. package/dist/resources/skills/debug-like-expert/references/debugging-mindset.md +253 -0
  251. package/dist/resources/skills/debug-like-expert/references/hypothesis-testing.md +373 -0
  252. package/dist/resources/skills/debug-like-expert/references/investigation-techniques.md +337 -0
  253. package/dist/resources/skills/debug-like-expert/references/verification-patterns.md +425 -0
  254. package/dist/resources/skills/debug-like-expert/references/when-to-research.md +361 -0
  255. package/dist/resources/skills/frontend-design/SKILL.md +45 -0
  256. package/dist/resources/skills/github-workflows/SKILL.md +92 -0
  257. package/dist/resources/skills/github-workflows/references/gh/SKILL.md +255 -0
  258. package/dist/resources/skills/github-workflows/references/gh/references/issue-stories.md +204 -0
  259. package/dist/resources/skills/github-workflows/references/gh/references/labels.md +170 -0
  260. package/dist/resources/skills/github-workflows/references/gh/references/milestones.md +158 -0
  261. package/dist/resources/skills/github-workflows/references/gh/references/projects-v2.md +177 -0
  262. package/dist/resources/skills/github-workflows/references/gh/scripts/experiment_cleanup.py +191 -0
  263. package/dist/resources/skills/github-workflows/references/gh/scripts/github_project_setup.py +799 -0
  264. package/dist/resources/skills/github-workflows/references/gh/tests/__init__.py +0 -0
  265. package/dist/resources/skills/github-workflows/references/gh/tests/test_github_project_setup.py +608 -0
  266. package/dist/resources/skills/swiftui/SKILL.md +208 -0
  267. package/dist/resources/skills/swiftui/references/animations.md +921 -0
  268. package/dist/resources/skills/swiftui/references/architecture.md +1561 -0
  269. package/dist/resources/skills/swiftui/references/layout-system.md +1186 -0
  270. package/dist/resources/skills/swiftui/references/navigation.md +1492 -0
  271. package/dist/resources/skills/swiftui/references/networking-async.md +214 -0
  272. package/dist/resources/skills/swiftui/references/performance.md +1706 -0
  273. package/dist/resources/skills/swiftui/references/platform-integration.md +204 -0
  274. package/dist/resources/skills/swiftui/references/state-management.md +1443 -0
  275. package/dist/resources/skills/swiftui/references/swiftdata.md +297 -0
  276. package/dist/resources/skills/swiftui/references/testing-debugging.md +247 -0
  277. package/dist/resources/skills/swiftui/references/uikit-appkit-interop.md +218 -0
  278. package/dist/resources/skills/swiftui/workflows/add-feature.md +191 -0
  279. package/dist/resources/skills/swiftui/workflows/build-new-app.md +311 -0
  280. package/dist/resources/skills/swiftui/workflows/debug-swiftui.md +192 -0
  281. package/dist/resources/skills/swiftui/workflows/optimize-performance.md +197 -0
  282. package/dist/resources/skills/swiftui/workflows/ship-app.md +203 -0
  283. package/dist/resources/skills/swiftui/workflows/write-tests.md +235 -0
  284. package/dist/wizard.js +1 -0
  285. package/package.json +8 -6
  286. package/packages/native/dist/fd/index.d.ts +1 -0
  287. package/packages/native/dist/fd/index.js +1 -0
  288. package/packages/native/dist/grep/index.d.ts +2 -1
  289. package/packages/native/dist/grep/index.js +1 -0
  290. package/packages/native/src/__tests__/fd.test.mjs +35 -0
  291. package/packages/native/src/__tests__/grep.test.mjs +26 -9
  292. package/packages/native/src/fd/index.ts +1 -0
  293. package/packages/native/src/grep/index.ts +3 -2
  294. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  295. package/packages/pi-agent-core/dist/agent-loop.js +169 -55
  296. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  297. package/packages/pi-agent-core/dist/agent.d.ts +13 -1
  298. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  299. package/packages/pi-agent-core/dist/agent.js +16 -0
  300. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  301. package/packages/pi-agent-core/dist/types.d.ts +91 -1
  302. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  303. package/packages/pi-agent-core/dist/types.js.map +1 -1
  304. package/packages/pi-agent-core/src/agent-loop.ts +273 -63
  305. package/packages/pi-agent-core/src/agent.ts +24 -0
  306. package/packages/pi-agent-core/src/types.ts +98 -0
  307. package/packages/pi-ai/dist/env-api-keys.js +2 -0
  308. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  309. package/packages/pi-ai/dist/models.generated.d.ts +314 -0
  310. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  311. package/packages/pi-ai/dist/models.generated.js +236 -0
  312. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  313. package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
  314. package/packages/pi-ai/dist/providers/google-gemini-cli.js +6 -0
  315. package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
  316. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  317. package/packages/pi-ai/dist/providers/openai-completions.js +2 -0
  318. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  319. package/packages/pi-ai/dist/types.d.ts +1 -1
  320. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  321. package/packages/pi-ai/dist/types.js.map +1 -1
  322. package/packages/pi-ai/src/env-api-keys.ts +2 -0
  323. package/packages/pi-ai/src/models.generated.ts +236 -0
  324. package/packages/pi-ai/src/providers/google-gemini-cli.ts +8 -0
  325. package/packages/pi-ai/src/providers/openai-completions.ts +3 -0
  326. package/packages/pi-ai/src/types.ts +2 -1
  327. package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
  328. package/packages/pi-coding-agent/dist/cli/args.js +1 -0
  329. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  330. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +26 -0
  331. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  332. package/packages/pi-coding-agent/dist/core/agent-session.js +137 -22
  333. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  334. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +20 -0
  335. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  336. package/packages/pi-coding-agent/dist/core/auth-storage.js +43 -0
  337. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  338. package/packages/pi-coding-agent/dist/core/fallback-resolver.d.ts +51 -0
  339. package/packages/pi-coding-agent/dist/core/fallback-resolver.d.ts.map +1 -0
  340. package/packages/pi-coding-agent/dist/core/fallback-resolver.js +119 -0
  341. package/packages/pi-coding-agent/dist/core/fallback-resolver.js.map +1 -0
  342. package/packages/pi-coding-agent/dist/core/fallback-resolver.test.d.ts +2 -0
  343. package/packages/pi-coding-agent/dist/core/fallback-resolver.test.d.ts.map +1 -0
  344. package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js +183 -0
  345. package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js.map +1 -0
  346. package/packages/pi-coding-agent/dist/core/index.d.ts +1 -0
  347. package/packages/pi-coding-agent/dist/core/index.d.ts.map +1 -1
  348. package/packages/pi-coding-agent/dist/core/index.js +1 -0
  349. package/packages/pi-coding-agent/dist/core/index.js.map +1 -1
  350. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  351. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  352. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  353. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +12 -1
  354. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  355. package/packages/pi-coding-agent/dist/core/session-manager.js +37 -1
  356. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  357. package/packages/pi-coding-agent/dist/core/session-manager.test.d.ts +2 -0
  358. package/packages/pi-coding-agent/dist/core/session-manager.test.d.ts.map +1 -0
  359. package/packages/pi-coding-agent/dist/core/session-manager.test.js +61 -0
  360. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -0
  361. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
  362. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  363. package/packages/pi-coding-agent/dist/core/settings-manager.js +47 -0
  364. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  365. package/packages/pi-coding-agent/dist/core/tools/find.d.ts.map +1 -1
  366. package/packages/pi-coding-agent/dist/core/tools/find.js +1 -0
  367. package/packages/pi-coding-agent/dist/core/tools/find.js.map +1 -1
  368. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  369. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +6 -15
  370. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  371. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts +18 -1
  372. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  373. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +46 -4
  374. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  375. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  376. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +25 -2
  377. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  378. package/packages/pi-coding-agent/src/cli/args.ts +1 -0
  379. package/packages/pi-coding-agent/src/core/agent-session.ts +167 -22
  380. package/packages/pi-coding-agent/src/core/auth-storage.ts +45 -0
  381. package/packages/pi-coding-agent/src/core/fallback-resolver.test.ts +229 -0
  382. package/packages/pi-coding-agent/src/core/fallback-resolver.ts +165 -0
  383. package/packages/pi-coding-agent/src/core/index.ts +1 -0
  384. package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
  385. package/packages/pi-coding-agent/src/core/session-manager.test.ts +65 -0
  386. package/packages/pi-coding-agent/src/core/session-manager.ts +51 -1
  387. package/packages/pi-coding-agent/src/core/settings-manager.ts +66 -0
  388. package/packages/pi-coding-agent/src/core/tools/find.ts +1 -0
  389. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +6 -16
  390. package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +49 -4
  391. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +28 -2
  392. package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
  393. package/packages/pi-tui/dist/autocomplete.js +3 -4
  394. package/packages/pi-tui/dist/autocomplete.js.map +1 -1
  395. package/packages/pi-tui/dist/components/editor.d.ts +7 -0
  396. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  397. package/packages/pi-tui/dist/components/editor.js +57 -50
  398. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  399. package/packages/pi-tui/src/autocomplete.ts +3 -5
  400. package/packages/pi-tui/src/components/editor.ts +70 -49
  401. package/pkg/dist/core/export-html/ansi-to-html.d.ts +22 -0
  402. package/pkg/dist/core/export-html/ansi-to-html.d.ts.map +1 -0
  403. package/pkg/dist/core/export-html/ansi-to-html.js +249 -0
  404. package/pkg/dist/core/export-html/ansi-to-html.js.map +1 -0
  405. package/pkg/dist/core/export-html/index.d.ts +37 -0
  406. package/pkg/dist/core/export-html/index.d.ts.map +1 -0
  407. package/pkg/dist/core/export-html/index.js +223 -0
  408. package/pkg/dist/core/export-html/index.js.map +1 -0
  409. package/pkg/dist/core/export-html/template.css +971 -0
  410. package/pkg/dist/core/export-html/template.html +54 -0
  411. package/pkg/dist/core/export-html/template.js +1583 -0
  412. package/pkg/dist/core/export-html/tool-renderer.d.ts +38 -0
  413. package/pkg/dist/core/export-html/tool-renderer.d.ts.map +1 -0
  414. package/pkg/dist/core/export-html/tool-renderer.js +70 -0
  415. package/pkg/dist/core/export-html/tool-renderer.js.map +1 -0
  416. package/pkg/dist/core/export-html/vendor/highlight.min.js +1213 -0
  417. package/pkg/dist/core/export-html/vendor/marked.min.js +6 -0
  418. package/scripts/postinstall.js +30 -17
  419. package/src/resources/extensions/bg-shell/index.ts +51 -7
  420. package/src/resources/extensions/gsd/activity-log.ts +61 -14
  421. package/src/resources/extensions/gsd/auto.ts +288 -19
  422. package/src/resources/extensions/gsd/commands.ts +9 -3
  423. package/src/resources/extensions/gsd/dashboard-overlay.ts +55 -12
  424. package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -0
  425. package/src/resources/extensions/gsd/doctor.ts +60 -3
  426. package/src/resources/extensions/gsd/files.ts +45 -1
  427. package/src/resources/extensions/gsd/git-service.ts +46 -41
  428. package/src/resources/extensions/gsd/guided-flow.ts +81 -9
  429. package/src/resources/extensions/gsd/index.ts +62 -1
  430. package/src/resources/extensions/gsd/native-git-bridge.ts +181 -0
  431. package/src/resources/extensions/gsd/paths.ts +35 -4
  432. package/src/resources/extensions/gsd/post-unit-hooks.ts +449 -0
  433. package/src/resources/extensions/gsd/preferences.ts +232 -1
  434. package/src/resources/extensions/gsd/prompt-loader.ts +45 -3
  435. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  436. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -3
  437. package/src/resources/extensions/gsd/prompts/discuss.md +10 -8
  438. package/src/resources/extensions/gsd/prompts/execute-task.md +4 -2
  439. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +3 -1
  440. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -1
  441. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
  442. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +3 -1
  443. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +4 -2
  444. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +3 -1
  445. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +3 -1
  446. package/src/resources/extensions/gsd/prompts/plan-milestone.md +9 -12
  447. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -3
  448. package/src/resources/extensions/gsd/prompts/queue.md +3 -1
  449. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  450. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  451. package/src/resources/extensions/gsd/state.ts +130 -18
  452. package/src/resources/extensions/gsd/templates/context.md +1 -1
  453. package/src/resources/extensions/gsd/templates/state.md +3 -3
  454. package/src/resources/extensions/gsd/tests/activity-log-save.test.ts +127 -0
  455. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +5 -1
  456. package/src/resources/extensions/gsd/tests/doctor.test.ts +115 -1
  457. package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +6 -2
  458. package/src/resources/extensions/gsd/tests/git-service.test.ts +35 -35
  459. package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +8 -8
  460. package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +4 -1
  461. package/src/resources/extensions/gsd/tests/network-error-fallback.test.ts +54 -0
  462. package/src/resources/extensions/gsd/tests/orphaned-branch.test.ts +5 -5
  463. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +297 -0
  464. package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +226 -0
  465. package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +12 -0
  466. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +6 -0
  467. package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +4 -4
  468. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +4 -4
  469. package/src/resources/extensions/gsd/tests/worktree.test.ts +19 -19
  470. package/src/resources/extensions/gsd/types.ts +109 -0
  471. package/src/resources/extensions/gsd/worktree-manager.ts +72 -13
  472. package/src/resources/extensions/gsd/worktree.ts +5 -4
  473. package/src/resources/extensions/search-the-web/command-search-provider.ts +8 -4
  474. package/src/resources/extensions/search-the-web/native-search.ts +32 -23
  475. package/src/resources/extensions/search-the-web/provider.ts +19 -2
  476. package/src/resources/extensions/search-the-web/tool-fetch-page.ts +62 -0
  477. package/src/resources/extensions/search-the-web/tool-llm-context.ts +62 -3
  478. package/src/resources/extensions/search-the-web/tool-search.ts +62 -3
  479. package/src/resources/extensions/shared/bundled-extension-paths.ts +11 -0
  480. package/src/resources/extensions/subagent/index.ts +2 -1
  481. package/src/resources/skills/github-workflows/SKILL.md +5 -0
  482. package/dist/resources/extensions/gsd/git-service.d.ts +0 -225
  483. package/dist/resources/extensions/gsd/git-service.js +0 -756
  484. package/dist/resources/extensions/gsd/preferences.d.ts +0 -122
  485. package/dist/resources/extensions/gsd/preferences.js +0 -616
  486. package/dist/resources/extensions/gsd/worktree.d.ts +0 -106
  487. package/dist/resources/extensions/remote-questions/config.d.ts +0 -14
  488. package/dist/resources/extensions/remote-questions/config.js +0 -68
  489. package/dist/resources/extensions/remote-questions/discord-adapter.d.ts +0 -17
  490. package/dist/resources/extensions/remote-questions/discord-adapter.js +0 -118
  491. package/dist/resources/extensions/remote-questions/format.d.ts +0 -38
  492. package/dist/resources/extensions/remote-questions/format.js +0 -119
  493. package/dist/resources/extensions/remote-questions/manager.d.ts +0 -23
  494. package/dist/resources/extensions/remote-questions/manager.js +0 -161
  495. package/dist/resources/extensions/remote-questions/remote-command.d.ts +0 -6
  496. package/dist/resources/extensions/remote-questions/remote-command.js +0 -358
  497. package/dist/resources/extensions/remote-questions/slack-adapter.d.ts +0 -15
  498. package/dist/resources/extensions/remote-questions/slack-adapter.js +0 -82
  499. package/dist/resources/extensions/remote-questions/status.d.ts +0 -9
  500. package/dist/resources/extensions/remote-questions/status.js +0 -25
  501. package/dist/resources/extensions/remote-questions/store.d.ts +0 -11
  502. package/dist/resources/extensions/remote-questions/store.js +0 -66
  503. package/dist/resources/extensions/remote-questions/types.d.ts +0 -68
  504. package/dist/resources/extensions/remote-questions/types.js +0 -4
@@ -0,0 +1,857 @@
1
+ // GSD Extension - File Parsing and I/O
2
+ // Parsers for roadmap, plan, summary, and continue files.
3
+ // Used by state derivation and the status widget.
4
+ // Pure functions, zero Pi dependencies - uses only Node built-ins.
5
+
6
+ import { promises as fs } from 'node:fs';
7
+ import { dirname, resolve } from 'node:path';
8
+ import { resolveMilestoneFile, relMilestoneFile } from './paths.js';
9
+ import { milestoneIdSort, findMilestoneIds } from './guided-flow.js';
10
+
11
+ import type {
12
+ Roadmap, BoundaryMapEntry,
13
+ SlicePlan, TaskPlanEntry,
14
+ Summary, SummaryFrontmatter, SummaryRequires, FileModified,
15
+ Continue, ContinueFrontmatter, ContinueStatus,
16
+ RequirementCounts,
17
+ SecretsManifest, SecretsManifestEntry, SecretsManifestEntryStatus,
18
+ ManifestStatus,
19
+ } from './types.js';
20
+
21
+ import { checkExistingEnvKeys } from '../get-secrets-from-user.js';
22
+ import { parseRoadmapSlices } from './roadmap-slices.js';
23
+ import { nativeParseRoadmap, nativeExtractSection, NATIVE_UNAVAILABLE } from './native-parser-bridge.js';
24
+
25
+ // ─── Parse Cache ──────────────────────────────────────────────────────────
26
+
27
+ const CACHE_MAX = 50;
28
+
29
+ /** Fast composite key: length + first/last 100 chars. Unique enough for distinct markdown files. */
30
+ function cacheKey(content: string): string {
31
+ const len = content.length;
32
+ const head = content.slice(0, 100);
33
+ const tail = len > 100 ? content.slice(-100) : '';
34
+ return `${len}:${head}:${tail}`;
35
+ }
36
+
37
+ const _parseCache = new Map<string, unknown>();
38
+
39
+ function cachedParse<T>(content: string, tag: string, parseFn: (c: string) => T): T {
40
+ const key = tag + '|' + cacheKey(content);
41
+ if (_parseCache.has(key)) return _parseCache.get(key) as T;
42
+ if (_parseCache.size >= CACHE_MAX) _parseCache.clear();
43
+ const result = parseFn(content);
44
+ _parseCache.set(key, result);
45
+ return result;
46
+ }
47
+
48
+ /** Clear the module-scoped parse cache. Call when files change on disk. */
49
+ export function clearParseCache(): void {
50
+ _parseCache.clear();
51
+ }
52
+
53
+ // ─── Helpers ───────────────────────────────────────────────────────────────
54
+
55
+ /**
56
+ * Split markdown content into frontmatter (YAML-like) and body.
57
+ * Returns [frontmatterLines, body] where frontmatterLines is null if no frontmatter.
58
+ */
59
+ export function splitFrontmatter(content: string): [string[] | null, string] {
60
+ const trimmed = content.trimStart();
61
+ if (!trimmed.startsWith('---')) return [null, content];
62
+
63
+ const afterFirst = trimmed.indexOf('\n');
64
+ if (afterFirst === -1) return [null, content];
65
+
66
+ const rest = trimmed.slice(afterFirst + 1);
67
+ const endIdx = rest.indexOf('\n---');
68
+ if (endIdx === -1) return [null, content];
69
+
70
+ const fmLines = rest.slice(0, endIdx).split('\n');
71
+ const body = rest.slice(endIdx + 4).replace(/^\n+/, '');
72
+ return [fmLines, body];
73
+ }
74
+
75
+ /**
76
+ * Parse YAML-like frontmatter lines into a flat key-value map.
77
+ * Handles simple scalars and arrays (lines starting with " - ").
78
+ * Handles nested objects like requires (lines with " key: value").
79
+ */
80
+ export function parseFrontmatterMap(lines: string[]): Record<string, unknown> {
81
+ const result: Record<string, unknown> = {};
82
+ let currentKey: string | null = null;
83
+ let currentArray: unknown[] | null = null;
84
+ let currentObj: Record<string, string> | null = null;
85
+
86
+ for (const line of lines) {
87
+ // Nested object property (4-space indent with key: value)
88
+ const nestedMatch = line.match(/^ (\w[\w_]*)\s*:\s*(.*)$/);
89
+ if (nestedMatch && currentArray && currentObj) {
90
+ currentObj[nestedMatch[1]] = nestedMatch[2].trim();
91
+ continue;
92
+ }
93
+
94
+ // Array item (2-space indent)
95
+ const arrayMatch = line.match(/^ - (.*)$/);
96
+ if (arrayMatch && currentKey) {
97
+ // If there's a pending nested object, push it
98
+ if (currentObj && Object.keys(currentObj).length > 0) {
99
+ currentArray!.push(currentObj);
100
+ }
101
+ currentObj = null;
102
+
103
+ const val = arrayMatch[1].trim();
104
+ if (!currentArray) currentArray = [];
105
+
106
+ // Check if this array item starts a nested object (e.g. "- slice: S00")
107
+ const nestedStart = val.match(/^(\w[\w_]*)\s*:\s*(.*)$/);
108
+ if (nestedStart) {
109
+ currentObj = { [nestedStart[1]]: nestedStart[2].trim() };
110
+ } else {
111
+ currentArray.push(val);
112
+ }
113
+ continue;
114
+ }
115
+
116
+ // Flush previous key
117
+ if (currentKey) {
118
+ if (currentObj && Object.keys(currentObj).length > 0 && currentArray) {
119
+ currentArray.push(currentObj);
120
+ currentObj = null;
121
+ }
122
+ if (currentArray) {
123
+ result[currentKey] = currentArray;
124
+ }
125
+ currentArray = null;
126
+ }
127
+
128
+ // Top-level key: value
129
+ const kvMatch = line.match(/^(\w[\w_]*)\s*:\s*(.*)$/);
130
+ if (kvMatch) {
131
+ currentKey = kvMatch[1];
132
+ const val = kvMatch[2].trim();
133
+
134
+ if (val === '' || val === '[]') {
135
+ currentArray = [];
136
+ } else if (val.startsWith('[') && val.endsWith(']')) {
137
+ const inner = val.slice(1, -1).trim();
138
+ result[currentKey] = inner ? inner.split(',').map(s => s.trim()) : [];
139
+ currentKey = null;
140
+ } else {
141
+ result[currentKey] = val;
142
+ currentKey = null;
143
+ }
144
+ }
145
+ }
146
+
147
+ // Flush final key
148
+ if (currentKey) {
149
+ if (currentObj && Object.keys(currentObj).length > 0 && currentArray) {
150
+ currentArray.push(currentObj);
151
+ currentObj = null;
152
+ }
153
+ if (currentArray) {
154
+ result[currentKey] = currentArray;
155
+ }
156
+ }
157
+
158
+ return result;
159
+ }
160
+
161
+ /** Extract the text after a heading at a given level, up to the next heading of same or higher level. */
162
+ export function extractSection(body: string, heading: string, level: number = 2): string | null {
163
+ // Try native parser first for better performance on large files
164
+ const nativeResult = nativeExtractSection(body, heading, level);
165
+ if (nativeResult !== NATIVE_UNAVAILABLE) return nativeResult as string | null;
166
+
167
+ const prefix = '#'.repeat(level) + ' ';
168
+ const regex = new RegExp(`^${prefix}${escapeRegex(heading)}\\s*$`, 'm');
169
+ const match = regex.exec(body);
170
+ if (!match) return null;
171
+
172
+ const start = match.index + match[0].length;
173
+ const rest = body.slice(start);
174
+
175
+ const nextHeading = rest.match(new RegExp(`^#{1,${level}} `, 'm'));
176
+ const end = nextHeading ? nextHeading.index! : rest.length;
177
+
178
+ return rest.slice(0, end).trim();
179
+ }
180
+
181
+ /** Extract all sections at a given level, returning heading → content map. */
182
+ export function extractAllSections(body: string, level: number = 2): Map<string, string> {
183
+ const prefix = '#'.repeat(level) + ' ';
184
+ const regex = new RegExp(`^${prefix}(.+)$`, 'gm');
185
+ const sections = new Map<string, string>();
186
+ const matches = [...body.matchAll(regex)];
187
+
188
+ for (let i = 0; i < matches.length; i++) {
189
+ const heading = matches[i][1].trim();
190
+ const start = matches[i].index! + matches[i][0].length;
191
+ const end = i + 1 < matches.length ? matches[i + 1].index! : body.length;
192
+ sections.set(heading, body.slice(start, end).trim());
193
+ }
194
+
195
+ return sections;
196
+ }
197
+
198
+ function escapeRegex(s: string): string {
199
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
200
+ }
201
+
202
+ /** Parse bullet list items from a text block. */
203
+ export function parseBullets(text: string): string[] {
204
+ return text.split('\n')
205
+ .map(l => l.replace(/^\s*[-*]\s+/, '').trim())
206
+ .filter(l => l.length > 0 && !l.startsWith('#'));
207
+ }
208
+
209
+ /** Extract key: value from bold-prefixed lines like "**Key:** Value" */
210
+ export function extractBoldField(text: string, key: string): string | null {
211
+ const regex = new RegExp(`^\\*\\*${escapeRegex(key)}:\\*\\*\\s*(.+)$`, 'm');
212
+ const match = regex.exec(text);
213
+ return match ? match[1].trim() : null;
214
+ }
215
+
216
+ // ─── Roadmap Parser ────────────────────────────────────────────────────────
217
+
218
+ export function parseRoadmap(content: string): Roadmap {
219
+ return cachedParse(content, 'roadmap', _parseRoadmapImpl);
220
+ }
221
+
222
+ function _parseRoadmapImpl(content: string): Roadmap {
223
+ // Try native parser first for better performance
224
+ const nativeResult = nativeParseRoadmap(content);
225
+ if (nativeResult) return nativeResult;
226
+
227
+ const lines = content.split('\n');
228
+
229
+ const h1 = lines.find(l => l.startsWith('# '));
230
+ const title = h1 ? h1.slice(2).trim() : '';
231
+ const vision = extractBoldField(content, 'Vision') || '';
232
+
233
+ const scSection = extractSection(content, 'Success Criteria', 2) ||
234
+ (() => {
235
+ const idx = content.indexOf('**Success Criteria:**');
236
+ if (idx === -1) return '';
237
+ const rest = content.slice(idx);
238
+ const nextSection = rest.indexOf('\n---');
239
+ const block = rest.slice(0, nextSection === -1 ? undefined : nextSection);
240
+ const firstNewline = block.indexOf('\n');
241
+ return firstNewline === -1 ? '' : block.slice(firstNewline + 1);
242
+ })();
243
+ const successCriteria = scSection ? parseBullets(scSection) : [];
244
+
245
+ // Slices
246
+ const slices = parseRoadmapSlices(content);
247
+
248
+ // Boundary map
249
+ const boundaryMap: BoundaryMapEntry[] = [];
250
+ const bmSection = extractSection(content, 'Boundary Map');
251
+
252
+ if (bmSection) {
253
+ const h3Sections = extractAllSections(bmSection, 3);
254
+ for (const [heading, sectionContent] of h3Sections) {
255
+ const arrowMatch = heading.match(/^(\S+)\s*→\s*(\S+)/);
256
+ if (!arrowMatch) continue;
257
+
258
+ const fromSlice = arrowMatch[1];
259
+ const toSlice = arrowMatch[2];
260
+
261
+ let produces = '';
262
+ let consumes = '';
263
+
264
+ const prodMatch = sectionContent.match(/^Produces:\s*\n([\s\S]*?)(?=^Consumes|$)/m);
265
+ if (prodMatch) produces = prodMatch[1].trim();
266
+
267
+ const consMatch = sectionContent.match(/^Consumes[^:]*:\s*\n?([\s\S]*?)$/m);
268
+ if (consMatch) consumes = consMatch[1].trim();
269
+ if (!consumes) {
270
+ const singleCons = sectionContent.match(/^Consumes[^:]*:\s*(.+)$/m);
271
+ if (singleCons) consumes = singleCons[1].trim();
272
+ }
273
+
274
+ boundaryMap.push({ fromSlice, toSlice, produces, consumes });
275
+ }
276
+ }
277
+
278
+ return { title, vision, successCriteria, slices, boundaryMap };
279
+ }
280
+
281
+ // ─── Secrets Manifest Parser ───────────────────────────────────────────────
282
+
283
+ const VALID_STATUSES = new Set<SecretsManifestEntryStatus>(['pending', 'collected', 'skipped']);
284
+
285
+ export function parseSecretsManifest(content: string): SecretsManifest {
286
+ const milestone = extractBoldField(content, 'Milestone') || '';
287
+ const generatedAt = extractBoldField(content, 'Generated') || '';
288
+
289
+ const h3Sections = extractAllSections(content, 3);
290
+ const entries: SecretsManifestEntry[] = [];
291
+
292
+ for (const [heading, sectionContent] of h3Sections) {
293
+ const key = heading.trim();
294
+ if (!key) continue;
295
+
296
+ const service = extractBoldField(sectionContent, 'Service') || '';
297
+ const dashboardUrl = extractBoldField(sectionContent, 'Dashboard') || '';
298
+ const formatHint = extractBoldField(sectionContent, 'Format hint') || '';
299
+ const rawStatus = (extractBoldField(sectionContent, 'Status') || 'pending').toLowerCase().trim() as SecretsManifestEntryStatus;
300
+ const status: SecretsManifestEntryStatus = VALID_STATUSES.has(rawStatus) ? rawStatus : 'pending';
301
+ const destination = extractBoldField(sectionContent, 'Destination') || 'dotenv';
302
+
303
+ // Extract numbered guidance list (lines matching "1. ...", "2. ...", etc.)
304
+ const guidance: string[] = [];
305
+ for (const line of sectionContent.split('\n')) {
306
+ const numMatch = line.match(/^\s*\d+\.\s+(.+)/);
307
+ if (numMatch) {
308
+ guidance.push(numMatch[1].trim());
309
+ }
310
+ }
311
+
312
+ entries.push({ key, service, dashboardUrl, guidance, formatHint, status, destination });
313
+ }
314
+
315
+ return { milestone, generatedAt, entries };
316
+ }
317
+
318
+ // ─── Secrets Manifest Formatter ───────────────────────────────────────────
319
+
320
+ export function formatSecretsManifest(manifest: SecretsManifest): string {
321
+ const lines: string[] = [];
322
+
323
+ lines.push('# Secrets Manifest');
324
+ lines.push('');
325
+ lines.push(`**Milestone:** ${manifest.milestone}`);
326
+ lines.push(`**Generated:** ${manifest.generatedAt}`);
327
+
328
+ for (const entry of manifest.entries) {
329
+ lines.push('');
330
+ lines.push(`### ${entry.key}`);
331
+ lines.push('');
332
+ lines.push(`**Service:** ${entry.service}`);
333
+ if (entry.dashboardUrl) {
334
+ lines.push(`**Dashboard:** ${entry.dashboardUrl}`);
335
+ }
336
+ if (entry.formatHint) {
337
+ lines.push(`**Format hint:** ${entry.formatHint}`);
338
+ }
339
+ lines.push(`**Status:** ${entry.status}`);
340
+ lines.push(`**Destination:** ${entry.destination}`);
341
+ lines.push('');
342
+ for (let i = 0; i < entry.guidance.length; i++) {
343
+ lines.push(`${i + 1}. ${entry.guidance[i]}`);
344
+ }
345
+ }
346
+
347
+ return lines.join('\n') + '\n';
348
+ }
349
+
350
+ // ─── Slice Plan Parser ─────────────────────────────────────────────────────
351
+
352
+ export function parsePlan(content: string): SlicePlan {
353
+ return cachedParse(content, 'plan', _parsePlanImpl);
354
+ }
355
+
356
+ function _parsePlanImpl(content: string): SlicePlan {
357
+ const lines = content.split('\n');
358
+
359
+ const h1 = lines.find(l => l.startsWith('# '));
360
+ let id = '';
361
+ let title = '';
362
+ if (h1) {
363
+ const match = h1.match(/^#\s+(\w+):\s+(.+)/);
364
+ if (match) {
365
+ id = match[1];
366
+ title = match[2].trim();
367
+ } else {
368
+ title = h1.slice(2).trim();
369
+ }
370
+ }
371
+
372
+ const goal = extractBoldField(content, 'Goal') || '';
373
+ const demo = extractBoldField(content, 'Demo') || '';
374
+
375
+ const mhSection = extractSection(content, 'Must-Haves');
376
+ const mustHaves = mhSection ? parseBullets(mhSection) : [];
377
+
378
+ const tasksSection = extractSection(content, 'Tasks');
379
+ const tasks: TaskPlanEntry[] = [];
380
+
381
+ if (tasksSection) {
382
+ const taskLines = tasksSection.split('\n');
383
+ let currentTask: TaskPlanEntry | null = null;
384
+
385
+ for (const line of taskLines) {
386
+ const cbMatch = line.match(/^-\s+\[([ xX])\]\s+\*\*(\w+):\s+(.+?)\*\*\s*(.*)/);
387
+ if (cbMatch) {
388
+ if (currentTask) tasks.push(currentTask);
389
+
390
+ const rest = cbMatch[4] || '';
391
+ const estMatch = rest.match(/`est:([^`]+)`/);
392
+ const estimate = estMatch ? estMatch[1] : '';
393
+
394
+ currentTask = {
395
+ id: cbMatch[2],
396
+ title: cbMatch[3],
397
+ description: '',
398
+ done: cbMatch[1].toLowerCase() === 'x',
399
+ estimate,
400
+ };
401
+ } else if (currentTask && line.match(/^\s*-\s+Files:\s*(.*)/)) {
402
+ const filesMatch = line.match(/^\s*-\s+Files:\s*(.*)/);
403
+ if (filesMatch) {
404
+ currentTask.files = filesMatch[1]
405
+ .split(',')
406
+ .map(f => f.replace(/`/g, '').trim())
407
+ .filter(f => f.length > 0);
408
+ }
409
+ } else if (currentTask && line.match(/^\s*-\s+Verify:\s*(.*)/)) {
410
+ const verifyMatch = line.match(/^\s*-\s+Verify:\s*(.*)/);
411
+ if (verifyMatch) {
412
+ currentTask.verify = verifyMatch[1].trim();
413
+ }
414
+ } else if (currentTask && line.trim() && !line.startsWith('#')) {
415
+ const desc = line.trim();
416
+ if (desc) {
417
+ currentTask.description = currentTask.description
418
+ ? currentTask.description + ' ' + desc
419
+ : desc;
420
+ }
421
+ }
422
+ }
423
+ if (currentTask) tasks.push(currentTask);
424
+ }
425
+
426
+ const filesSection = extractSection(content, 'Files Likely Touched');
427
+ const filesLikelyTouched = filesSection ? parseBullets(filesSection) : [];
428
+
429
+ return { id, title, goal, demo, mustHaves, tasks, filesLikelyTouched };
430
+ }
431
+
432
+ // ─── Summary Parser ────────────────────────────────────────────────────────
433
+
434
+ export function parseSummary(content: string): Summary {
435
+ return cachedParse(content, 'summary', _parseSummaryImpl);
436
+ }
437
+
438
+ function _parseSummaryImpl(content: string): Summary {
439
+ const [fmLines, body] = splitFrontmatter(content);
440
+
441
+ const fm = fmLines ? parseFrontmatterMap(fmLines) : {};
442
+ const asStringArray = (v: unknown): string[] =>
443
+ Array.isArray(v) ? v : (typeof v === 'string' && v ? [v] : []);
444
+ const frontmatter: SummaryFrontmatter = {
445
+ id: (fm.id as string) || '',
446
+ parent: (fm.parent as string) || '',
447
+ milestone: (fm.milestone as string) || '',
448
+ provides: asStringArray(fm.provides),
449
+ requires: ((fm.requires as Array<Record<string, string>>) || []).map(r => ({
450
+ slice: r.slice || '',
451
+ provides: r.provides || '',
452
+ })),
453
+ affects: asStringArray(fm.affects),
454
+ key_files: asStringArray(fm.key_files),
455
+ key_decisions: asStringArray(fm.key_decisions),
456
+ patterns_established: asStringArray(fm.patterns_established),
457
+ drill_down_paths: asStringArray(fm.drill_down_paths),
458
+ observability_surfaces: asStringArray(fm.observability_surfaces),
459
+ duration: (fm.duration as string) || '',
460
+ verification_result: (fm.verification_result as string) || 'untested',
461
+ completed_at: (fm.completed_at as string) || '',
462
+ blocker_discovered: fm.blocker_discovered === 'true' || fm.blocker_discovered === true,
463
+ };
464
+
465
+ const bodyLines = body.split('\n');
466
+ const h1 = bodyLines.find(l => l.startsWith('# '));
467
+ const title = h1 ? h1.slice(2).trim() : '';
468
+
469
+ const h1Idx = bodyLines.indexOf(h1 || '');
470
+ let oneLiner = '';
471
+ for (let i = h1Idx + 1; i < bodyLines.length; i++) {
472
+ const line = bodyLines[i].trim();
473
+ if (!line) continue;
474
+ if (line.startsWith('**') && line.endsWith('**')) {
475
+ oneLiner = line.slice(2, -2);
476
+ }
477
+ break;
478
+ }
479
+
480
+ const whatHappened = extractSection(body, 'What Happened') || '';
481
+ const deviations = extractSection(body, 'Deviations') || '';
482
+
483
+ const filesSection = extractSection(body, 'Files Created/Modified') || extractSection(body, 'Files Modified');
484
+ const filesModified: FileModified[] = [];
485
+ if (filesSection) {
486
+ for (const line of filesSection.split('\n')) {
487
+ const trimmed = line.replace(/^\s*[-*]\s+/, '').trim();
488
+ if (!trimmed || trimmed.startsWith('#')) continue;
489
+
490
+ const fileMatch = trimmed.match(/^`([^`]+)`\s*[—–-]\s*(.+)/);
491
+ if (fileMatch) {
492
+ filesModified.push({ path: fileMatch[1], description: fileMatch[2].trim() });
493
+ }
494
+ }
495
+ }
496
+
497
+ return { frontmatter, title, oneLiner, whatHappened, deviations, filesModified };
498
+ }
499
+
500
+ // ─── Continue Parser ───────────────────────────────────────────────────────
501
+
502
+ export function parseContinue(content: string): Continue {
503
+ return cachedParse(content, 'continue', _parseContinueImpl);
504
+ }
505
+
506
+ function _parseContinueImpl(content: string): Continue {
507
+ const [fmLines, body] = splitFrontmatter(content);
508
+
509
+ const fm = fmLines ? parseFrontmatterMap(fmLines) : {};
510
+ const frontmatter: ContinueFrontmatter = {
511
+ milestone: (fm.milestone as string) || '',
512
+ slice: (fm.slice as string) || '',
513
+ task: (fm.task as string) || '',
514
+ step: typeof fm.step === 'string' ? parseInt(fm.step) : (fm.step as number) || 0,
515
+ totalSteps: typeof fm.total_steps === 'string' ? parseInt(fm.total_steps) : (fm.total_steps as number) ||
516
+ (typeof fm.totalSteps === 'string' ? parseInt(fm.totalSteps) : (fm.totalSteps as number) || 0),
517
+ status: ((fm.status as string) || 'in_progress') as ContinueStatus,
518
+ savedAt: (fm.saved_at as string) || (fm.savedAt as string) || '',
519
+ };
520
+
521
+ const completedWork = extractSection(body, 'Completed Work') || '';
522
+ const remainingWork = extractSection(body, 'Remaining Work') || '';
523
+ const decisions = extractSection(body, 'Decisions Made') || '';
524
+ const context = extractSection(body, 'Context') || '';
525
+ const nextAction = extractSection(body, 'Next Action') || '';
526
+
527
+ return { frontmatter, completedWork, remainingWork, decisions, context, nextAction };
528
+ }
529
+
530
+ // ─── Continue Formatter ────────────────────────────────────────────────────
531
+
532
+ function formatFrontmatter(data: Record<string, unknown>): string {
533
+ const lines: string[] = ['---'];
534
+
535
+ for (const [key, value] of Object.entries(data)) {
536
+ if (value === undefined || value === null) continue;
537
+
538
+ if (Array.isArray(value)) {
539
+ if (value.length === 0) {
540
+ lines.push(`${key}: []`);
541
+ } else if (typeof value[0] === 'object' && value[0] !== null) {
542
+ lines.push(`${key}:`);
543
+ for (const obj of value) {
544
+ const entries = Object.entries(obj as Record<string, unknown>);
545
+ if (entries.length > 0) {
546
+ lines.push(` - ${entries[0][0]}: ${entries[0][1]}`);
547
+ for (let i = 1; i < entries.length; i++) {
548
+ lines.push(` ${entries[i][0]}: ${entries[i][1]}`);
549
+ }
550
+ }
551
+ }
552
+ } else {
553
+ lines.push(`${key}:`);
554
+ for (const item of value) {
555
+ lines.push(` - ${item}`);
556
+ }
557
+ }
558
+ } else {
559
+ lines.push(`${key}: ${value}`);
560
+ }
561
+ }
562
+
563
+ lines.push('---');
564
+ return lines.join('\n');
565
+ }
566
+
567
+ export function formatContinue(cont: Continue): string {
568
+ const fm = cont.frontmatter;
569
+ const fmData: Record<string, unknown> = {
570
+ milestone: fm.milestone,
571
+ slice: fm.slice,
572
+ task: fm.task,
573
+ step: fm.step,
574
+ total_steps: fm.totalSteps,
575
+ status: fm.status,
576
+ saved_at: fm.savedAt,
577
+ };
578
+
579
+ const lines: string[] = [];
580
+ lines.push(formatFrontmatter(fmData));
581
+ lines.push('');
582
+ lines.push('## Completed Work');
583
+ lines.push(cont.completedWork);
584
+ lines.push('');
585
+ lines.push('## Remaining Work');
586
+ lines.push(cont.remainingWork);
587
+ lines.push('');
588
+ lines.push('## Decisions Made');
589
+ lines.push(cont.decisions);
590
+ lines.push('');
591
+ lines.push('## Context');
592
+ lines.push(cont.context);
593
+ lines.push('');
594
+ lines.push('## Next Action');
595
+ lines.push(cont.nextAction);
596
+
597
+ return lines.join('\n');
598
+ }
599
+
600
+ // ─── File I/O ──────────────────────────────────────────────────────────────
601
+
602
+ /**
603
+ * Load a file from disk. Returns content string or null if file doesn't exist.
604
+ */
605
+ export async function loadFile(path: string): Promise<string | null> {
606
+ try {
607
+ return await fs.readFile(path, 'utf-8');
608
+ } catch (err: unknown) {
609
+ if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;
610
+ throw err;
611
+ }
612
+ }
613
+
614
+ /**
615
+ * Save content to a file atomically (write to temp, then rename).
616
+ * Creates parent directories if needed.
617
+ */
618
+ export async function saveFile(path: string, content: string): Promise<void> {
619
+ const dir = dirname(path);
620
+ await fs.mkdir(dir, { recursive: true });
621
+
622
+ const tmpPath = path + '.tmp';
623
+ await fs.writeFile(tmpPath, content, 'utf-8');
624
+ await fs.rename(tmpPath, path);
625
+ }
626
+
627
+ export function parseRequirementCounts(content: string | null): RequirementCounts {
628
+ const counts: RequirementCounts = {
629
+ active: 0,
630
+ validated: 0,
631
+ deferred: 0,
632
+ outOfScope: 0,
633
+ blocked: 0,
634
+ total: 0,
635
+ };
636
+
637
+ if (!content) return counts;
638
+
639
+ const sections = [
640
+ { key: 'active', heading: 'Active' },
641
+ { key: 'validated', heading: 'Validated' },
642
+ { key: 'deferred', heading: 'Deferred' },
643
+ { key: 'outOfScope', heading: 'Out of Scope' },
644
+ ] as const;
645
+
646
+ for (const section of sections) {
647
+ const text = extractSection(content, section.heading, 2);
648
+ if (!text) continue;
649
+ const matches = text.match(/^###\s+[A-Z][\w-]*\d+\s+—/gm);
650
+ counts[section.key] = matches ? matches.length : 0;
651
+ }
652
+
653
+ const blockedMatches = content.match(/^-\s+Status:\s+blocked\s*$/gim);
654
+ counts.blocked = blockedMatches ? blockedMatches.length : 0;
655
+ counts.total = counts.active + counts.validated + counts.deferred + counts.outOfScope;
656
+ return counts;
657
+ }
658
+
659
+ // ─── Task Plan Must-Haves Parser ───────────────────────────────────────────
660
+
661
+ /**
662
+ * Parse must-have items from a task plan's `## Must-Haves` section.
663
+ * Returns structured items with checkbox state. Handles YAML frontmatter,
664
+ * all common checkbox variants (`[ ]`, `[x]`, `[X]`), plain bullets (no checkbox),
665
+ * and indented variants. Returns empty array when the section is missing or empty.
666
+ */
667
+ export function parseTaskPlanMustHaves(content: string): Array<{ text: string; checked: boolean }> {
668
+ const [, body] = splitFrontmatter(content);
669
+ const sectionText = extractSection(body, 'Must-Haves');
670
+ if (!sectionText) return [];
671
+
672
+ const bullets = parseBullets(sectionText);
673
+ if (bullets.length === 0) return [];
674
+
675
+ return bullets.map(line => {
676
+ const cbMatch = line.match(/^\[([xX ])\]\s+(.+)/);
677
+ if (cbMatch) {
678
+ return {
679
+ text: cbMatch[2].trim(),
680
+ checked: cbMatch[1].toLowerCase() === 'x',
681
+ };
682
+ }
683
+ // No checkbox - treat as unchecked with full line as text
684
+ return { text: line.trim(), checked: false };
685
+ });
686
+ }
687
+
688
+ // ─── Must-Have Summary Matching ────────────────────────────────────────────
689
+
690
+ /** Common short words to exclude from substring matching. */
691
+ const COMMON_WORDS = new Set([
692
+ 'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can', 'had', 'her',
693
+ 'was', 'one', 'our', 'out', 'has', 'its', 'let', 'say', 'she', 'too', 'use',
694
+ 'with', 'have', 'from', 'this', 'that', 'they', 'been', 'each', 'when', 'will',
695
+ 'does', 'into', 'also', 'than', 'them', 'then', 'some', 'what', 'only', 'just',
696
+ 'more', 'make', 'like', 'made', 'over', 'such', 'take', 'most', 'very', 'must',
697
+ 'file', 'test', 'tests', 'task', 'new', 'add', 'added', 'existing',
698
+ ]);
699
+
700
+ /**
701
+ * Count how many must-have items are mentioned in a summary.
702
+ *
703
+ * Matching heuristic per must-have:
704
+ * 1. Extract all backtick-enclosed code tokens (e.g. `inspectFoo`).
705
+ * If any code token appears case-insensitively in the summary, count as mentioned.
706
+ * 2. If no code tokens exist, check if any significant word (≥4 chars, not a common word)
707
+ * from the must-have text appears in the summary (case-insensitive).
708
+ *
709
+ * Returns the count of must-haves that had at least one match.
710
+ */
711
+ export function countMustHavesMentionedInSummary(
712
+ mustHaves: Array<{ text: string; checked: boolean }>,
713
+ summaryContent: string,
714
+ ): number {
715
+ if (!summaryContent || mustHaves.length === 0) return 0;
716
+
717
+ const summaryLower = summaryContent.toLowerCase();
718
+ let count = 0;
719
+
720
+ for (const mh of mustHaves) {
721
+ // Extract backtick-enclosed code tokens
722
+ const codeTokens: string[] = [];
723
+ const codeRegex = /`([^`]+)`/g;
724
+ let match: RegExpExecArray | null;
725
+ while ((match = codeRegex.exec(mh.text)) !== null) {
726
+ codeTokens.push(match[1]);
727
+ }
728
+
729
+ if (codeTokens.length > 0) {
730
+ // Strategy 1: any code token found in summary (case-insensitive)
731
+ const found = codeTokens.some(token => summaryLower.includes(token.toLowerCase()));
732
+ if (found) count++;
733
+ } else {
734
+ // Strategy 2: significant substring matching
735
+ // Split into words, keep words ≥4 chars that aren't common
736
+ const words = mh.text.replace(/[^\w\s]/g, ' ').split(/\s+/).filter(w =>
737
+ w.length >= 4 && !COMMON_WORDS.has(w.toLowerCase())
738
+ );
739
+ const found = words.some(word => summaryLower.includes(word.toLowerCase()));
740
+ if (found) count++;
741
+ }
742
+ }
743
+
744
+ return count;
745
+ }
746
+
747
+ // ─── UAT Type Extractor ────────────────────────────────────────────────────
748
+
749
+ /**
750
+ * The four UAT classification types recognised by GSD auto-mode.
751
+ * `undefined` is returned (not this union) when no type can be determined.
752
+ */
753
+ export type UatType = 'artifact-driven' | 'live-runtime' | 'human-experience' | 'mixed';
754
+
755
+ /**
756
+ * Extract the UAT type from a UAT file's raw content.
757
+ *
758
+ * UAT files have no YAML frontmatter - pass raw file content directly.
759
+ * Classification is leading-keyword-only: e.g. `mixed (artifact-driven + live-runtime)` → `'mixed'`.
760
+ *
761
+ * Returns `undefined` when:
762
+ * - the `## UAT Type` section is absent
763
+ * - no `UAT mode:` bullet is found in the section
764
+ * - the value does not start with a recognised keyword
765
+ */
766
+ export function extractUatType(content: string): UatType | undefined {
767
+ const sectionText = extractSection(content, 'UAT Type');
768
+ if (!sectionText) return undefined;
769
+
770
+ const bullets = parseBullets(sectionText);
771
+ const modeBullet = bullets.find(b => b.startsWith('UAT mode:'));
772
+ if (!modeBullet) return undefined;
773
+
774
+ const rawValue = modeBullet.slice('UAT mode:'.length).trim().toLowerCase();
775
+
776
+ if (rawValue.startsWith('artifact-driven')) return 'artifact-driven';
777
+ if (rawValue.startsWith('live-runtime')) return 'live-runtime';
778
+ if (rawValue.startsWith('human-experience')) return 'human-experience';
779
+ if (rawValue.startsWith('mixed')) return 'mixed';
780
+
781
+ return undefined;
782
+ }
783
+
784
+ /**
785
+ * Extract the `depends_on` list from M00x-CONTEXT.md YAML frontmatter.
786
+ * Returns [] when: content is null, no frontmatter block, field absent, or field is empty.
787
+ * Normalizes each dep ID to uppercase (e.g. 'm001' → 'M001').
788
+ */
789
+ export function parseContextDependsOn(content: string | null): string[] {
790
+ if (!content) return [];
791
+ const [fmLines] = splitFrontmatter(content);
792
+ if (!fmLines) return [];
793
+ const fm = parseFrontmatterMap(fmLines);
794
+ const raw = fm['depends_on'];
795
+ if (!Array.isArray(raw) || raw.length === 0) return [];
796
+ return (raw as string[]).map(s => String(s).toUpperCase().trim()).filter(Boolean);
797
+ }
798
+
799
+ /**
800
+ * Inline the prior milestone's SUMMARY.md as context for the current milestone's planning prompt.
801
+ * Returns null when: (1) `mid` is the first milestone, (2) prior milestone has no SUMMARY file.
802
+ *
803
+ * Uses the shared findMilestoneIds to scan the milestones directory.
804
+ */
805
+ export async function inlinePriorMilestoneSummary(mid: string, base: string): Promise<string | null> {
806
+ const sorted = findMilestoneIds(base);
807
+ if (sorted.length === 0) return null;
808
+ const idx = sorted.indexOf(mid);
809
+ if (idx <= 0) return null;
810
+ const prevMid = sorted[idx - 1];
811
+ const absPath = resolveMilestoneFile(base, prevMid, "SUMMARY");
812
+ const relPath = relMilestoneFile(base, prevMid, "SUMMARY");
813
+ const content = absPath ? await loadFile(absPath) : null;
814
+ if (!content) return null;
815
+ return `### Prior Milestone Summary\nSource: \`${relPath}\`\n\n${content.trim()}`;
816
+ }
817
+
818
+ // ─── Manifest Status ──────────────────────────────────────────────────────
819
+
820
+ /**
821
+ * Read a secrets manifest from disk and cross-reference each entry's status
822
+ * with the current environment (.env + process.env).
823
+ *
824
+ * Returns `null` when no manifest file exists (path resolution failure or
825
+ * file not on disk) - callers can distinguish "no manifest" from "empty manifest".
826
+ */
827
+ export async function getManifestStatus(
828
+ base: string, milestoneId: string,
829
+ ): Promise<ManifestStatus | null> {
830
+ const resolvedPath = resolveMilestoneFile(base, milestoneId, 'SECRETS');
831
+ if (!resolvedPath) return null;
832
+
833
+ const content = await loadFile(resolvedPath);
834
+ if (!content) return null;
835
+
836
+ const manifest = parseSecretsManifest(content);
837
+ const keys = manifest.entries.map(e => e.key);
838
+ const existingKeys = await checkExistingEnvKeys(keys, resolve(base, '.env'));
839
+ const existingSet = new Set(existingKeys);
840
+
841
+ const result: ManifestStatus = {
842
+ pending: [],
843
+ collected: [],
844
+ skipped: [],
845
+ existing: [],
846
+ };
847
+
848
+ for (const entry of manifest.entries) {
849
+ if (existingSet.has(entry.key)) {
850
+ result.existing.push(entry.key);
851
+ } else {
852
+ result[entry.status].push(entry.key);
853
+ }
854
+ }
855
+
856
+ return result;
857
+ }