gsd-remix 1.0.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 (554) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +939 -0
  3. package/README.zh-CN.md +876 -0
  4. package/agents/gsd-advisor-researcher.md +127 -0
  5. package/agents/gsd-ai-researcher.md +133 -0
  6. package/agents/gsd-assumptions-analyzer.md +105 -0
  7. package/agents/gsd-code-fixer.md +517 -0
  8. package/agents/gsd-code-reviewer.md +371 -0
  9. package/agents/gsd-codebase-mapper.md +781 -0
  10. package/agents/gsd-debug-session-manager.md +314 -0
  11. package/agents/gsd-debugger.md +1452 -0
  12. package/agents/gsd-doc-classifier.md +168 -0
  13. package/agents/gsd-doc-synthesizer.md +204 -0
  14. package/agents/gsd-doc-verifier.md +217 -0
  15. package/agents/gsd-doc-writer.md +615 -0
  16. package/agents/gsd-domain-researcher.md +153 -0
  17. package/agents/gsd-eval-auditor.md +191 -0
  18. package/agents/gsd-eval-planner.md +154 -0
  19. package/agents/gsd-executor.md +603 -0
  20. package/agents/gsd-framework-selector.md +160 -0
  21. package/agents/gsd-integration-checker.md +470 -0
  22. package/agents/gsd-intel-updater.md +334 -0
  23. package/agents/gsd-nyquist-auditor.md +203 -0
  24. package/agents/gsd-pattern-mapper.md +335 -0
  25. package/agents/gsd-phase-researcher.md +841 -0
  26. package/agents/gsd-plan-checker.md +978 -0
  27. package/agents/gsd-planner.md +1251 -0
  28. package/agents/gsd-project-researcher.md +677 -0
  29. package/agents/gsd-research-synthesizer.md +247 -0
  30. package/agents/gsd-roadmapper.md +688 -0
  31. package/agents/gsd-security-auditor.md +155 -0
  32. package/agents/gsd-ui-auditor.md +495 -0
  33. package/agents/gsd-ui-checker.md +309 -0
  34. package/agents/gsd-ui-researcher.md +380 -0
  35. package/agents/gsd-user-profiler.md +171 -0
  36. package/agents/gsd-verifier.md +830 -0
  37. package/bin/install.js +7062 -0
  38. package/commands/gsd/add-backlog.md +79 -0
  39. package/commands/gsd/add-phase.md +43 -0
  40. package/commands/gsd/add-tests.md +41 -0
  41. package/commands/gsd/add-todo.md +47 -0
  42. package/commands/gsd/ai-integration-phase.md +36 -0
  43. package/commands/gsd/analyze-dependencies.md +34 -0
  44. package/commands/gsd/audit-fix.md +33 -0
  45. package/commands/gsd/audit-milestone.md +36 -0
  46. package/commands/gsd/audit-uat.md +24 -0
  47. package/commands/gsd/autonomous.md +46 -0
  48. package/commands/gsd/check-todos.md +45 -0
  49. package/commands/gsd/cleanup.md +23 -0
  50. package/commands/gsd/code-review-fix.md +52 -0
  51. package/commands/gsd/code-review.md +55 -0
  52. package/commands/gsd/complete-milestone.md +136 -0
  53. package/commands/gsd/debug.md +263 -0
  54. package/commands/gsd/discuss-phase.md +69 -0
  55. package/commands/gsd/do.md +30 -0
  56. package/commands/gsd/docs-update.md +48 -0
  57. package/commands/gsd/eval-review.md +32 -0
  58. package/commands/gsd/execute-phase.md +63 -0
  59. package/commands/gsd/explore.md +27 -0
  60. package/commands/gsd/extract_learnings.md +22 -0
  61. package/commands/gsd/fast.md +30 -0
  62. package/commands/gsd/forensics.md +56 -0
  63. package/commands/gsd/from-gsd2.md +47 -0
  64. package/commands/gsd/graphify.md +201 -0
  65. package/commands/gsd/health.md +22 -0
  66. package/commands/gsd/help.md +24 -0
  67. package/commands/gsd/import.md +37 -0
  68. package/commands/gsd/inbox.md +38 -0
  69. package/commands/gsd/ingest-docs.md +42 -0
  70. package/commands/gsd/insert-phase.md +32 -0
  71. package/commands/gsd/intel.md +179 -0
  72. package/commands/gsd/join-discord.md +19 -0
  73. package/commands/gsd/list-phase-assumptions.md +46 -0
  74. package/commands/gsd/list-workspaces.md +19 -0
  75. package/commands/gsd/manager.md +40 -0
  76. package/commands/gsd/map-codebase.md +71 -0
  77. package/commands/gsd/milestone-summary.md +51 -0
  78. package/commands/gsd/new-milestone.md +44 -0
  79. package/commands/gsd/new-project.md +46 -0
  80. package/commands/gsd/new-workspace.md +44 -0
  81. package/commands/gsd/next.md +28 -0
  82. package/commands/gsd/note.md +34 -0
  83. package/commands/gsd/pause-work.md +38 -0
  84. package/commands/gsd/plan-milestone-gaps.md +34 -0
  85. package/commands/gsd/plan-phase.md +52 -0
  86. package/commands/gsd/plan-review-convergence.md +52 -0
  87. package/commands/gsd/plant-seed.md +28 -0
  88. package/commands/gsd/pr-branch.md +25 -0
  89. package/commands/gsd/profile-user.md +46 -0
  90. package/commands/gsd/progress.md +25 -0
  91. package/commands/gsd/quick.md +173 -0
  92. package/commands/gsd/reapply-patches.md +331 -0
  93. package/commands/gsd/remove-phase.md +31 -0
  94. package/commands/gsd/remove-workspace.md +26 -0
  95. package/commands/gsd/research-phase.md +195 -0
  96. package/commands/gsd/resume-work.md +40 -0
  97. package/commands/gsd/review-backlog.md +62 -0
  98. package/commands/gsd/review.md +40 -0
  99. package/commands/gsd/scan.md +26 -0
  100. package/commands/gsd/secure-phase.md +35 -0
  101. package/commands/gsd/session-report.md +19 -0
  102. package/commands/gsd/set-profile.md +12 -0
  103. package/commands/gsd/settings.md +36 -0
  104. package/commands/gsd/ship.md +23 -0
  105. package/commands/gsd/sketch-wrap-up.md +31 -0
  106. package/commands/gsd/sketch.md +49 -0
  107. package/commands/gsd/spec-phase.md +62 -0
  108. package/commands/gsd/spike-wrap-up.md +31 -0
  109. package/commands/gsd/spike.md +46 -0
  110. package/commands/gsd/stats.md +18 -0
  111. package/commands/gsd/sync-skills.md +19 -0
  112. package/commands/gsd/thread.md +227 -0
  113. package/commands/gsd/ui-phase.md +34 -0
  114. package/commands/gsd/ui-review.md +32 -0
  115. package/commands/gsd/ultraplan-phase.md +33 -0
  116. package/commands/gsd/undo.md +34 -0
  117. package/commands/gsd/update.md +37 -0
  118. package/commands/gsd/validate-phase.md +35 -0
  119. package/commands/gsd/verify-work.md +38 -0
  120. package/commands/gsd/workstreams.md +69 -0
  121. package/get-shit-done/bin/gsd-tools.cjs +1263 -0
  122. package/get-shit-done/bin/lib/artifacts.cjs +52 -0
  123. package/get-shit-done/bin/lib/audit.cjs +757 -0
  124. package/get-shit-done/bin/lib/commands.cjs +1023 -0
  125. package/get-shit-done/bin/lib/config-schema.cjs +79 -0
  126. package/get-shit-done/bin/lib/config.cjs +463 -0
  127. package/get-shit-done/bin/lib/core.cjs +1794 -0
  128. package/get-shit-done/bin/lib/docs.cjs +267 -0
  129. package/get-shit-done/bin/lib/frontmatter.cjs +379 -0
  130. package/get-shit-done/bin/lib/graphify.cjs +494 -0
  131. package/get-shit-done/bin/lib/gsd2-import.cjs +511 -0
  132. package/get-shit-done/bin/lib/init.cjs +1878 -0
  133. package/get-shit-done/bin/lib/intel.cjs +639 -0
  134. package/get-shit-done/bin/lib/learnings.cjs +378 -0
  135. package/get-shit-done/bin/lib/milestone.cjs +283 -0
  136. package/get-shit-done/bin/lib/model-profiles.cjs +71 -0
  137. package/get-shit-done/bin/lib/phase.cjs +1058 -0
  138. package/get-shit-done/bin/lib/profile-output.cjs +1080 -0
  139. package/get-shit-done/bin/lib/profile-pipeline.cjs +539 -0
  140. package/get-shit-done/bin/lib/roadmap.cjs +523 -0
  141. package/get-shit-done/bin/lib/schema-detect.cjs +238 -0
  142. package/get-shit-done/bin/lib/security.cjs +504 -0
  143. package/get-shit-done/bin/lib/state.cjs +1649 -0
  144. package/get-shit-done/bin/lib/template.cjs +226 -0
  145. package/get-shit-done/bin/lib/uat.cjs +288 -0
  146. package/get-shit-done/bin/lib/verify.cjs +1184 -0
  147. package/get-shit-done/bin/lib/workstream.cjs +495 -0
  148. package/get-shit-done/bin/repair-sdk.cjs +177 -0
  149. package/get-shit-done/contexts/dev.md +21 -0
  150. package/get-shit-done/contexts/research.md +22 -0
  151. package/get-shit-done/contexts/review.md +22 -0
  152. package/get-shit-done/references/agent-contracts.md +79 -0
  153. package/get-shit-done/references/ai-evals.md +156 -0
  154. package/get-shit-done/references/ai-frameworks.md +186 -0
  155. package/get-shit-done/references/artifact-types.md +131 -0
  156. package/get-shit-done/references/autonomous-smart-discuss.md +277 -0
  157. package/get-shit-done/references/checkpoints.md +808 -0
  158. package/get-shit-done/references/common-bug-patterns.md +114 -0
  159. package/get-shit-done/references/context-budget.md +49 -0
  160. package/get-shit-done/references/continuation-format.md +253 -0
  161. package/get-shit-done/references/debugger-philosophy.md +76 -0
  162. package/get-shit-done/references/decimal-phase-calculation.md +64 -0
  163. package/get-shit-done/references/doc-conflict-engine.md +91 -0
  164. package/get-shit-done/references/domain-probes.md +125 -0
  165. package/get-shit-done/references/executor-examples.md +110 -0
  166. package/get-shit-done/references/few-shot-examples/plan-checker.md +73 -0
  167. package/get-shit-done/references/few-shot-examples/verifier.md +109 -0
  168. package/get-shit-done/references/gate-prompts.md +100 -0
  169. package/get-shit-done/references/gates.md +70 -0
  170. package/get-shit-done/references/git-integration.md +295 -0
  171. package/get-shit-done/references/git-planning-commit.md +40 -0
  172. package/get-shit-done/references/ios-scaffold.md +123 -0
  173. package/get-shit-done/references/mandatory-initial-read.md +2 -0
  174. package/get-shit-done/references/model-profile-resolution.md +38 -0
  175. package/get-shit-done/references/model-profiles.md +145 -0
  176. package/get-shit-done/references/phase-argument-parsing.md +61 -0
  177. package/get-shit-done/references/planner-antipatterns.md +89 -0
  178. package/get-shit-done/references/planner-gap-closure.md +62 -0
  179. package/get-shit-done/references/planner-reviews.md +39 -0
  180. package/get-shit-done/references/planner-revision.md +87 -0
  181. package/get-shit-done/references/planner-source-audit.md +73 -0
  182. package/get-shit-done/references/planning-config.md +460 -0
  183. package/get-shit-done/references/project-skills-discovery.md +19 -0
  184. package/get-shit-done/references/questioning.md +162 -0
  185. package/get-shit-done/references/revision-loop.md +97 -0
  186. package/get-shit-done/references/sketch-interactivity.md +41 -0
  187. package/get-shit-done/references/sketch-theme-system.md +94 -0
  188. package/get-shit-done/references/sketch-tooling.md +45 -0
  189. package/get-shit-done/references/sketch-variant-patterns.md +81 -0
  190. package/get-shit-done/references/tdd.md +330 -0
  191. package/get-shit-done/references/thinking-models-debug.md +44 -0
  192. package/get-shit-done/references/thinking-models-execution.md +50 -0
  193. package/get-shit-done/references/thinking-models-planning.md +62 -0
  194. package/get-shit-done/references/thinking-models-research.md +50 -0
  195. package/get-shit-done/references/thinking-models-verification.md +55 -0
  196. package/get-shit-done/references/thinking-partner.md +96 -0
  197. package/get-shit-done/references/ui-brand.md +160 -0
  198. package/get-shit-done/references/universal-anti-patterns.md +63 -0
  199. package/get-shit-done/references/user-profiling.md +681 -0
  200. package/get-shit-done/references/verification-overrides.md +227 -0
  201. package/get-shit-done/references/verification-patterns.md +612 -0
  202. package/get-shit-done/references/workstream-flag.md +111 -0
  203. package/get-shit-done/templates/AI-SPEC.md +246 -0
  204. package/get-shit-done/templates/DEBUG.md +169 -0
  205. package/get-shit-done/templates/README.md +76 -0
  206. package/get-shit-done/templates/SECURITY.md +61 -0
  207. package/get-shit-done/templates/UAT.md +265 -0
  208. package/get-shit-done/templates/UI-SPEC.md +100 -0
  209. package/get-shit-done/templates/VALIDATION.md +76 -0
  210. package/get-shit-done/templates/claude-md.md +145 -0
  211. package/get-shit-done/templates/codebase/architecture.md +255 -0
  212. package/get-shit-done/templates/codebase/concerns.md +310 -0
  213. package/get-shit-done/templates/codebase/conventions.md +307 -0
  214. package/get-shit-done/templates/codebase/integrations.md +280 -0
  215. package/get-shit-done/templates/codebase/stack.md +186 -0
  216. package/get-shit-done/templates/codebase/structure.md +285 -0
  217. package/get-shit-done/templates/codebase/testing.md +480 -0
  218. package/get-shit-done/templates/config.json +56 -0
  219. package/get-shit-done/templates/context.md +352 -0
  220. package/get-shit-done/templates/continue-here.md +78 -0
  221. package/get-shit-done/templates/copilot-instructions.md +7 -0
  222. package/get-shit-done/templates/debug-subagent-prompt.md +91 -0
  223. package/get-shit-done/templates/dev-preferences.md +21 -0
  224. package/get-shit-done/templates/discovery.md +146 -0
  225. package/get-shit-done/templates/discussion-log.md +63 -0
  226. package/get-shit-done/templates/milestone-archive.md +123 -0
  227. package/get-shit-done/templates/milestone.md +115 -0
  228. package/get-shit-done/templates/phase-prompt.md +610 -0
  229. package/get-shit-done/templates/planner-subagent-prompt.md +117 -0
  230. package/get-shit-done/templates/project.md +186 -0
  231. package/get-shit-done/templates/requirements.md +231 -0
  232. package/get-shit-done/templates/research-project/ARCHITECTURE.md +204 -0
  233. package/get-shit-done/templates/research-project/FEATURES.md +147 -0
  234. package/get-shit-done/templates/research-project/PITFALLS.md +200 -0
  235. package/get-shit-done/templates/research-project/STACK.md +120 -0
  236. package/get-shit-done/templates/research-project/SUMMARY.md +170 -0
  237. package/get-shit-done/templates/research.md +592 -0
  238. package/get-shit-done/templates/retrospective.md +54 -0
  239. package/get-shit-done/templates/roadmap.md +202 -0
  240. package/get-shit-done/templates/spec.md +307 -0
  241. package/get-shit-done/templates/state.md +184 -0
  242. package/get-shit-done/templates/summary-complex.md +59 -0
  243. package/get-shit-done/templates/summary-minimal.md +41 -0
  244. package/get-shit-done/templates/summary-standard.md +48 -0
  245. package/get-shit-done/templates/summary.md +248 -0
  246. package/get-shit-done/templates/user-profile.md +146 -0
  247. package/get-shit-done/templates/user-setup.md +311 -0
  248. package/get-shit-done/templates/verification-report.md +322 -0
  249. package/get-shit-done/workflows/add-phase.md +112 -0
  250. package/get-shit-done/workflows/add-tests.md +354 -0
  251. package/get-shit-done/workflows/add-todo.md +160 -0
  252. package/get-shit-done/workflows/ai-integration-phase.md +284 -0
  253. package/get-shit-done/workflows/analyze-dependencies.md +96 -0
  254. package/get-shit-done/workflows/audit-fix.md +175 -0
  255. package/get-shit-done/workflows/audit-milestone.md +340 -0
  256. package/get-shit-done/workflows/audit-uat.md +109 -0
  257. package/get-shit-done/workflows/autonomous.md +789 -0
  258. package/get-shit-done/workflows/check-todos.md +179 -0
  259. package/get-shit-done/workflows/cleanup.md +154 -0
  260. package/get-shit-done/workflows/code-review-fix.md +497 -0
  261. package/get-shit-done/workflows/code-review.md +515 -0
  262. package/get-shit-done/workflows/complete-milestone.md +847 -0
  263. package/get-shit-done/workflows/diagnose-issues.md +238 -0
  264. package/get-shit-done/workflows/discovery-phase.md +291 -0
  265. package/get-shit-done/workflows/discuss-phase-assumptions.md +670 -0
  266. package/get-shit-done/workflows/discuss-phase-power.md +308 -0
  267. package/get-shit-done/workflows/discuss-phase.md +1378 -0
  268. package/get-shit-done/workflows/do.md +110 -0
  269. package/get-shit-done/workflows/docs-update.md +1155 -0
  270. package/get-shit-done/workflows/eval-review.md +155 -0
  271. package/get-shit-done/workflows/execute-phase.md +1677 -0
  272. package/get-shit-done/workflows/execute-plan.md +533 -0
  273. package/get-shit-done/workflows/explore.md +141 -0
  274. package/get-shit-done/workflows/extract_learnings.md +242 -0
  275. package/get-shit-done/workflows/fast.md +105 -0
  276. package/get-shit-done/workflows/forensics.md +265 -0
  277. package/get-shit-done/workflows/graduation.md +195 -0
  278. package/get-shit-done/workflows/health.md +314 -0
  279. package/get-shit-done/workflows/help.md +667 -0
  280. package/get-shit-done/workflows/import.md +246 -0
  281. package/get-shit-done/workflows/inbox.md +387 -0
  282. package/get-shit-done/workflows/ingest-docs.md +328 -0
  283. package/get-shit-done/workflows/insert-phase.md +130 -0
  284. package/get-shit-done/workflows/list-phase-assumptions.md +178 -0
  285. package/get-shit-done/workflows/list-workspaces.md +56 -0
  286. package/get-shit-done/workflows/manager.md +365 -0
  287. package/get-shit-done/workflows/map-codebase.md +393 -0
  288. package/get-shit-done/workflows/milestone-summary.md +223 -0
  289. package/get-shit-done/workflows/new-milestone.md +611 -0
  290. package/get-shit-done/workflows/new-project.md +1391 -0
  291. package/get-shit-done/workflows/new-workspace.md +239 -0
  292. package/get-shit-done/workflows/next.md +220 -0
  293. package/get-shit-done/workflows/node-repair.md +92 -0
  294. package/get-shit-done/workflows/note.md +158 -0
  295. package/get-shit-done/workflows/pause-work.md +243 -0
  296. package/get-shit-done/workflows/plan-milestone-gaps.md +273 -0
  297. package/get-shit-done/workflows/plan-phase.md +1349 -0
  298. package/get-shit-done/workflows/plan-review-convergence.md +254 -0
  299. package/get-shit-done/workflows/plant-seed.md +172 -0
  300. package/get-shit-done/workflows/pr-branch.md +157 -0
  301. package/get-shit-done/workflows/profile-user.md +452 -0
  302. package/get-shit-done/workflows/progress.md +619 -0
  303. package/get-shit-done/workflows/quick.md +970 -0
  304. package/get-shit-done/workflows/remove-phase.md +155 -0
  305. package/get-shit-done/workflows/remove-workspace.md +92 -0
  306. package/get-shit-done/workflows/research-phase.md +89 -0
  307. package/get-shit-done/workflows/resume-project.md +326 -0
  308. package/get-shit-done/workflows/review.md +344 -0
  309. package/get-shit-done/workflows/scan.md +102 -0
  310. package/get-shit-done/workflows/secure-phase.md +166 -0
  311. package/get-shit-done/workflows/session-report.md +146 -0
  312. package/get-shit-done/workflows/settings.md +319 -0
  313. package/get-shit-done/workflows/ship.md +302 -0
  314. package/get-shit-done/workflows/sketch-wrap-up.md +283 -0
  315. package/get-shit-done/workflows/sketch.md +286 -0
  316. package/get-shit-done/workflows/spec-phase.md +262 -0
  317. package/get-shit-done/workflows/spike-wrap-up.md +281 -0
  318. package/get-shit-done/workflows/spike.md +362 -0
  319. package/get-shit-done/workflows/stats.md +60 -0
  320. package/get-shit-done/workflows/sync-skills.md +182 -0
  321. package/get-shit-done/workflows/transition.md +693 -0
  322. package/get-shit-done/workflows/ui-phase.md +323 -0
  323. package/get-shit-done/workflows/ui-review.md +190 -0
  324. package/get-shit-done/workflows/ultraplan-phase.md +189 -0
  325. package/get-shit-done/workflows/undo.md +314 -0
  326. package/get-shit-done/workflows/update.md +587 -0
  327. package/get-shit-done/workflows/validate-phase.md +176 -0
  328. package/get-shit-done/workflows/verify-phase.md +465 -0
  329. package/get-shit-done/workflows/verify-work.md +740 -0
  330. package/hooks/dist/gsd-check-update-worker.js +108 -0
  331. package/hooks/dist/gsd-check-update.js +64 -0
  332. package/hooks/dist/gsd-context-monitor.js +192 -0
  333. package/hooks/dist/gsd-phase-boundary.sh +28 -0
  334. package/hooks/dist/gsd-prompt-guard.js +97 -0
  335. package/hooks/dist/gsd-read-guard.js +82 -0
  336. package/hooks/dist/gsd-read-injection-scanner.js +152 -0
  337. package/hooks/dist/gsd-session-state.sh +34 -0
  338. package/hooks/dist/gsd-statusline.js +293 -0
  339. package/hooks/dist/gsd-validate-commit.sh +48 -0
  340. package/hooks/dist/gsd-workflow-guard.js +94 -0
  341. package/hooks/gsd-check-update-worker.js +108 -0
  342. package/hooks/gsd-check-update.js +64 -0
  343. package/hooks/gsd-context-monitor.js +192 -0
  344. package/hooks/gsd-phase-boundary.sh +28 -0
  345. package/hooks/gsd-prompt-guard.js +97 -0
  346. package/hooks/gsd-read-guard.js +82 -0
  347. package/hooks/gsd-read-injection-scanner.js +152 -0
  348. package/hooks/gsd-session-state.sh +34 -0
  349. package/hooks/gsd-statusline.js +293 -0
  350. package/hooks/gsd-validate-commit.sh +48 -0
  351. package/hooks/gsd-workflow-guard.js +94 -0
  352. package/package.json +59 -0
  353. package/scripts/base64-scan.sh +262 -0
  354. package/scripts/build-hooks.js +95 -0
  355. package/scripts/gen-inventory-manifest.cjs +109 -0
  356. package/scripts/prompt-injection-scan.sh +201 -0
  357. package/scripts/run-tests.cjs +33 -0
  358. package/scripts/secret-scan.sh +227 -0
  359. package/sdk/package-lock.json +1998 -0
  360. package/sdk/package.json +52 -0
  361. package/sdk/prompts/agents/gsd-executor.md +110 -0
  362. package/sdk/prompts/agents/gsd-phase-researcher.md +158 -0
  363. package/sdk/prompts/agents/gsd-plan-checker.md +160 -0
  364. package/sdk/prompts/agents/gsd-planner.md +214 -0
  365. package/sdk/prompts/agents/gsd-project-researcher.md +323 -0
  366. package/sdk/prompts/agents/gsd-research-synthesizer.md +237 -0
  367. package/sdk/prompts/agents/gsd-roadmapper.md +670 -0
  368. package/sdk/prompts/agents/gsd-verifier.md +159 -0
  369. package/sdk/prompts/templates/project.md +186 -0
  370. package/sdk/prompts/templates/requirements.md +231 -0
  371. package/sdk/prompts/templates/research-project/ARCHITECTURE.md +204 -0
  372. package/sdk/prompts/templates/research-project/FEATURES.md +147 -0
  373. package/sdk/prompts/templates/research-project/PITFALLS.md +200 -0
  374. package/sdk/prompts/templates/research-project/STACK.md +120 -0
  375. package/sdk/prompts/templates/research-project/SUMMARY.md +170 -0
  376. package/sdk/prompts/templates/roadmap.md +202 -0
  377. package/sdk/prompts/templates/state.md +175 -0
  378. package/sdk/prompts/workflows/discuss-phase.md +126 -0
  379. package/sdk/prompts/workflows/execute-plan.md +106 -0
  380. package/sdk/prompts/workflows/plan-phase.md +84 -0
  381. package/sdk/prompts/workflows/research-phase.md +45 -0
  382. package/sdk/prompts/workflows/verify-phase.md +142 -0
  383. package/sdk/src/assembled-prompts.test.ts +349 -0
  384. package/sdk/src/cli-transport.test.ts +388 -0
  385. package/sdk/src/cli-transport.ts +130 -0
  386. package/sdk/src/cli.test.ts +383 -0
  387. package/sdk/src/cli.ts +670 -0
  388. package/sdk/src/config.test.ts +168 -0
  389. package/sdk/src/config.ts +177 -0
  390. package/sdk/src/context-engine.test.ts +295 -0
  391. package/sdk/src/context-engine.ts +170 -0
  392. package/sdk/src/context-truncation.test.ts +163 -0
  393. package/sdk/src/context-truncation.ts +233 -0
  394. package/sdk/src/e2e.integration.test.ts +178 -0
  395. package/sdk/src/errors.ts +72 -0
  396. package/sdk/src/event-stream.test.ts +661 -0
  397. package/sdk/src/event-stream.ts +441 -0
  398. package/sdk/src/failure-memory.test.ts +457 -0
  399. package/sdk/src/failure-memory.ts +1324 -0
  400. package/sdk/src/golden/capture.ts +95 -0
  401. package/sdk/src/golden/fixtures/generate-slug.golden.json +1 -0
  402. package/sdk/src/golden/fixtures/profile-sample-sessions/demo-project/sample.jsonl +3 -0
  403. package/sdk/src/golden/fixtures/summary-extract-sample.md +26 -0
  404. package/sdk/src/golden/fixtures/uat-render-checkpoint-sample.md +15 -0
  405. package/sdk/src/golden/golden-integration-covered.ts +30 -0
  406. package/sdk/src/golden/golden-mutation-covered.ts +7 -0
  407. package/sdk/src/golden/golden-policy.test.ts +8 -0
  408. package/sdk/src/golden/golden-policy.ts +112 -0
  409. package/sdk/src/golden/golden.integration.test.ts +373 -0
  410. package/sdk/src/golden/init-golden-normalize.ts +15 -0
  411. package/sdk/src/golden/read-only-golden-rows.ts +77 -0
  412. package/sdk/src/golden/read-only-parity.integration.test.ts +125 -0
  413. package/sdk/src/golden/registry-canonical-commands.ts +31 -0
  414. package/sdk/src/gsd-tools.test.ts +409 -0
  415. package/sdk/src/gsd-tools.ts +595 -0
  416. package/sdk/src/headless-prompts.test.ts +159 -0
  417. package/sdk/src/index.ts +333 -0
  418. package/sdk/src/init-e2e.integration.test.ts +136 -0
  419. package/sdk/src/init-runner.test.ts +783 -0
  420. package/sdk/src/init-runner.ts +735 -0
  421. package/sdk/src/lifecycle-e2e.integration.test.ts +258 -0
  422. package/sdk/src/logger.test.ts +149 -0
  423. package/sdk/src/logger.ts +113 -0
  424. package/sdk/src/milestone-runner.test.ts +421 -0
  425. package/sdk/src/phase-prompt.test.ts +538 -0
  426. package/sdk/src/phase-prompt.ts +264 -0
  427. package/sdk/src/phase-runner-types.test.ts +421 -0
  428. package/sdk/src/phase-runner.integration.test.ts +377 -0
  429. package/sdk/src/phase-runner.test.ts +2333 -0
  430. package/sdk/src/phase-runner.ts +1203 -0
  431. package/sdk/src/plan-parser.test.ts +528 -0
  432. package/sdk/src/plan-parser.ts +427 -0
  433. package/sdk/src/prompt-builder.test.ts +306 -0
  434. package/sdk/src/prompt-builder.ts +193 -0
  435. package/sdk/src/prompt-sanitizer.test.ts +260 -0
  436. package/sdk/src/prompt-sanitizer.ts +71 -0
  437. package/sdk/src/query/QUERY-HANDLERS.md +317 -0
  438. package/sdk/src/query/audit-open.ts +722 -0
  439. package/sdk/src/query/check-auto-mode.test.ts +77 -0
  440. package/sdk/src/query/check-auto-mode.ts +50 -0
  441. package/sdk/src/query/check-completion.test.ts +113 -0
  442. package/sdk/src/query/check-completion.ts +182 -0
  443. package/sdk/src/query/check-gates.test.ts +103 -0
  444. package/sdk/src/query/check-gates.ts +112 -0
  445. package/sdk/src/query/check-ship-ready.test.ts +77 -0
  446. package/sdk/src/query/check-ship-ready.ts +103 -0
  447. package/sdk/src/query/check-verification-status.test.ts +143 -0
  448. package/sdk/src/query/check-verification-status.ts +160 -0
  449. package/sdk/src/query/commit.test.ts +202 -0
  450. package/sdk/src/query/commit.ts +301 -0
  451. package/sdk/src/query/config-gates.test.ts +89 -0
  452. package/sdk/src/query/config-gates.ts +69 -0
  453. package/sdk/src/query/config-mutation.test.ts +365 -0
  454. package/sdk/src/query/config-mutation.ts +497 -0
  455. package/sdk/src/query/config-query.test.ts +161 -0
  456. package/sdk/src/query/config-query.ts +190 -0
  457. package/sdk/src/query/context-history.test.ts +165 -0
  458. package/sdk/src/query/context-history.ts +467 -0
  459. package/sdk/src/query/decomposed-handlers.test.ts +365 -0
  460. package/sdk/src/query/detect-custom-files.ts +97 -0
  461. package/sdk/src/query/detect-phase-type.test.ts +105 -0
  462. package/sdk/src/query/detect-phase-type.ts +141 -0
  463. package/sdk/src/query/docs-init.ts +257 -0
  464. package/sdk/src/query/failure-capture.ts +58 -0
  465. package/sdk/src/query/frontmatter-array.test.ts +14 -0
  466. package/sdk/src/query/frontmatter-mutation.test.ts +259 -0
  467. package/sdk/src/query/frontmatter-mutation.ts +343 -0
  468. package/sdk/src/query/frontmatter.test.ts +281 -0
  469. package/sdk/src/query/frontmatter.ts +397 -0
  470. package/sdk/src/query/helpers.test.ts +426 -0
  471. package/sdk/src/query/helpers.ts +482 -0
  472. package/sdk/src/query/index.ts +586 -0
  473. package/sdk/src/query/init-complex.test.ts +232 -0
  474. package/sdk/src/query/init-complex.ts +578 -0
  475. package/sdk/src/query/init.test.ts +522 -0
  476. package/sdk/src/query/init.ts +1046 -0
  477. package/sdk/src/query/intel.test.ts +90 -0
  478. package/sdk/src/query/intel.ts +404 -0
  479. package/sdk/src/query/normalize-query-command.test.ts +50 -0
  480. package/sdk/src/query/normalize-query-command.ts +56 -0
  481. package/sdk/src/query/phase-lifecycle.test.ts +1126 -0
  482. package/sdk/src/query/phase-lifecycle.ts +1799 -0
  483. package/sdk/src/query/phase-list-queries.test.ts +88 -0
  484. package/sdk/src/query/phase-list-queries.ts +152 -0
  485. package/sdk/src/query/phase-ready.test.ts +65 -0
  486. package/sdk/src/query/phase-ready.ts +158 -0
  487. package/sdk/src/query/phase.test.ts +307 -0
  488. package/sdk/src/query/phase.ts +340 -0
  489. package/sdk/src/query/pipeline.test.ts +169 -0
  490. package/sdk/src/query/pipeline.ts +243 -0
  491. package/sdk/src/query/plan-execution-route.test.ts +166 -0
  492. package/sdk/src/query/plan-execution-route.ts +209 -0
  493. package/sdk/src/query/plan-task-structure.test.ts +65 -0
  494. package/sdk/src/query/plan-task-structure.ts +63 -0
  495. package/sdk/src/query/profile-extract-messages.ts +247 -0
  496. package/sdk/src/query/profile-output.ts +908 -0
  497. package/sdk/src/query/profile-questionnaire-data.ts +181 -0
  498. package/sdk/src/query/profile-sample.ts +184 -0
  499. package/sdk/src/query/profile-scan-sessions.ts +174 -0
  500. package/sdk/src/query/profile.test.ts +74 -0
  501. package/sdk/src/query/profile.ts +337 -0
  502. package/sdk/src/query/progress.test.ts +156 -0
  503. package/sdk/src/query/progress.ts +566 -0
  504. package/sdk/src/query/registry.test.ts +216 -0
  505. package/sdk/src/query/registry.ts +174 -0
  506. package/sdk/src/query/requirements-extract-from-plans.test.ts +58 -0
  507. package/sdk/src/query/requirements-extract-from-plans.ts +86 -0
  508. package/sdk/src/query/roadmap-update-plan-progress.ts +132 -0
  509. package/sdk/src/query/roadmap.test.ts +359 -0
  510. package/sdk/src/query/roadmap.ts +591 -0
  511. package/sdk/src/query/route-next-action.test.ts +61 -0
  512. package/sdk/src/query/route-next-action.ts +345 -0
  513. package/sdk/src/query/runtime-health.ts +7 -0
  514. package/sdk/src/query/schema-detect.ts +189 -0
  515. package/sdk/src/query/skill-manifest.ts +214 -0
  516. package/sdk/src/query/skills.test.ts +80 -0
  517. package/sdk/src/query/skills.ts +62 -0
  518. package/sdk/src/query/state-mutation.test.ts +450 -0
  519. package/sdk/src/query/state-mutation.ts +1444 -0
  520. package/sdk/src/query/state-project-load.ts +109 -0
  521. package/sdk/src/query/state.test.ts +347 -0
  522. package/sdk/src/query/state.ts +397 -0
  523. package/sdk/src/query/summary.test.ts +95 -0
  524. package/sdk/src/query/summary.ts +296 -0
  525. package/sdk/src/query/template.test.ts +180 -0
  526. package/sdk/src/query/template.ts +242 -0
  527. package/sdk/src/query/uat.test.ts +77 -0
  528. package/sdk/src/query/uat.ts +314 -0
  529. package/sdk/src/query/utils.test.ts +82 -0
  530. package/sdk/src/query/utils.ts +92 -0
  531. package/sdk/src/query/validate.test.ts +656 -0
  532. package/sdk/src/query/validate.ts +807 -0
  533. package/sdk/src/query/verify.test.ts +414 -0
  534. package/sdk/src/query/verify.ts +645 -0
  535. package/sdk/src/query/websearch.test.ts +31 -0
  536. package/sdk/src/query/websearch.ts +82 -0
  537. package/sdk/src/query/workspace.test.ts +119 -0
  538. package/sdk/src/query/workspace.ts +131 -0
  539. package/sdk/src/query/workstream.test.ts +51 -0
  540. package/sdk/src/query/workstream.ts +434 -0
  541. package/sdk/src/research-gate.test.ts +190 -0
  542. package/sdk/src/research-gate.ts +94 -0
  543. package/sdk/src/runtime-health.test.ts +176 -0
  544. package/sdk/src/runtime-health.ts +387 -0
  545. package/sdk/src/session-runner.test.ts +98 -0
  546. package/sdk/src/session-runner.ts +299 -0
  547. package/sdk/src/tool-scoping.test.ts +160 -0
  548. package/sdk/src/tool-scoping.ts +61 -0
  549. package/sdk/src/types.ts +917 -0
  550. package/sdk/src/workstream-utils.ts +33 -0
  551. package/sdk/src/ws-flag.test.ts +285 -0
  552. package/sdk/src/ws-transport.test.ts +161 -0
  553. package/sdk/src/ws-transport.ts +93 -0
  554. package/sdk/tsconfig.json +20 -0
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Unit tests for frontmatter parser and query handler.
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import { mkdtemp, writeFile, rm } from 'node:fs/promises';
7
+ import { join } from 'node:path';
8
+ import { tmpdir } from 'node:os';
9
+ import {
10
+ splitInlineArray,
11
+ extractFrontmatter,
12
+ extractFrontmatterLeading,
13
+ stripFrontmatter,
14
+ frontmatterGet,
15
+ parseMustHavesBlock,
16
+ } from './frontmatter.js';
17
+
18
+ // ─── splitInlineArray ───────────────────────────────────────────────────────
19
+
20
+ describe('splitInlineArray', () => {
21
+ it('splits simple CSV', () => {
22
+ expect(splitInlineArray('a, b, c')).toEqual(['a', 'b', 'c']);
23
+ });
24
+
25
+ it('handles quoted strings with commas', () => {
26
+ expect(splitInlineArray('"a, b", c')).toEqual(['a, b', 'c']);
27
+ });
28
+
29
+ it('handles single-quoted strings', () => {
30
+ expect(splitInlineArray("'a, b', c")).toEqual(['a, b', 'c']);
31
+ });
32
+
33
+ it('trims whitespace', () => {
34
+ expect(splitInlineArray(' a , b ')).toEqual(['a', 'b']);
35
+ });
36
+
37
+ it('returns empty array for empty string', () => {
38
+ expect(splitInlineArray('')).toEqual([]);
39
+ });
40
+ });
41
+
42
+ // ─── extractFrontmatter ─────────────────────────────────────────────────────
43
+
44
+ describe('extractFrontmatter', () => {
45
+ it('parses simple key-value pairs', () => {
46
+ const content = '---\nkey: value\n---\nbody';
47
+ const result = extractFrontmatter(content);
48
+ expect(result).toEqual({ key: 'value' });
49
+ });
50
+
51
+ it('parses nested objects', () => {
52
+ const content = '---\nparent:\n child: value\n---\n';
53
+ const result = extractFrontmatter(content);
54
+ expect(result).toEqual({ parent: { child: 'value' } });
55
+ });
56
+
57
+ it('parses inline arrays', () => {
58
+ const content = '---\ntags: [a, b, c]\n---\n';
59
+ const result = extractFrontmatter(content);
60
+ expect(result).toEqual({ tags: ['a', 'b', 'c'] });
61
+ });
62
+
63
+ it('parses dash arrays', () => {
64
+ const content = '---\nitems:\n - one\n - two\n---\n';
65
+ const result = extractFrontmatter(content);
66
+ expect(result).toEqual({ items: ['one', 'two'] });
67
+ });
68
+
69
+ it('uses the LAST block when multiple stacked blocks exist', () => {
70
+ const content = '---\nold: data\n---\n---\nnew: data\n---\nbody';
71
+ const result = extractFrontmatter(content);
72
+ expect(result).toEqual({ new: 'data' });
73
+ });
74
+
75
+ it('handles empty-object-to-array conversion', () => {
76
+ const content = '---\nlist:\n - item1\n - item2\n---\n';
77
+ const result = extractFrontmatter(content);
78
+ expect(result).toEqual({ list: ['item1', 'item2'] });
79
+ });
80
+
81
+ it('returns empty object when no frontmatter', () => {
82
+ const result = extractFrontmatter('no frontmatter here');
83
+ expect(result).toEqual({});
84
+ });
85
+
86
+ it('strips surrounding quotes from values', () => {
87
+ const content = '---\nkey: "quoted"\n---\n';
88
+ const result = extractFrontmatter(content);
89
+ expect(result).toEqual({ key: 'quoted' });
90
+ });
91
+
92
+ it('handles CRLF line endings', () => {
93
+ const content = '---\r\nkey: value\r\n---\r\nbody';
94
+ const result = extractFrontmatter(content);
95
+ expect(result).toEqual({ key: 'value' });
96
+ });
97
+ });
98
+
99
+ // ─── extractFrontmatterLeading ─────────────────────────────────────────────
100
+
101
+ describe('extractFrontmatterLeading', () => {
102
+ it('parses only the first leading block (gsd-tools.cjs / frontmatter.cjs parity)', () => {
103
+ const content = '---\nfirst: 1\n---\n---\nsecond: 2\n---\nbody';
104
+ expect(extractFrontmatterLeading(content)).toEqual({ first: '1' });
105
+ });
106
+
107
+ it('matches extractFrontmatter when a single block starts the file', () => {
108
+ const content = '---\na: b\n---\n';
109
+ expect(extractFrontmatterLeading(content)).toEqual(extractFrontmatter(content));
110
+ });
111
+ });
112
+
113
+ // ─── stripFrontmatter ───────────────────────────────────────────────────────
114
+
115
+ describe('stripFrontmatter', () => {
116
+ it('strips single frontmatter block', () => {
117
+ const result = stripFrontmatter('---\nk: v\n---\nbody');
118
+ expect(result).toBe('body');
119
+ });
120
+
121
+ it('strips multiple stacked blocks', () => {
122
+ const result = stripFrontmatter('---\na: 1\n---\n---\nb: 2\n---\nbody');
123
+ expect(result).toBe('body');
124
+ });
125
+
126
+ it('returns content unchanged when no frontmatter', () => {
127
+ expect(stripFrontmatter('just body')).toBe('just body');
128
+ });
129
+
130
+ it('handles leading whitespace after strip', () => {
131
+ const result = stripFrontmatter('---\nk: v\n---\n\nbody');
132
+ // After stripping, leading whitespace/newlines may remain
133
+ expect(result.trim()).toBe('body');
134
+ });
135
+ });
136
+
137
+ // ─── frontmatterGet ─────────────────────────────────────────────────────────
138
+
139
+ describe('frontmatterGet', () => {
140
+ let tmpDir: string;
141
+
142
+ beforeEach(async () => {
143
+ tmpDir = await mkdtemp(join(tmpdir(), 'gsd-fm-'));
144
+ });
145
+
146
+ afterEach(async () => {
147
+ await rm(tmpDir, { recursive: true, force: true });
148
+ });
149
+
150
+ it('returns parsed frontmatter from a file', async () => {
151
+ await writeFile(join(tmpDir, 'test.md'), '---\nkey: value\n---\nbody');
152
+ const result = await frontmatterGet(['test.md'], tmpDir);
153
+ expect(result.data).toEqual({ key: 'value' });
154
+ });
155
+
156
+ it('returns single field when field arg provided', async () => {
157
+ await writeFile(join(tmpDir, 'test.md'), '---\nkey: value\n---\nbody');
158
+ const result = await frontmatterGet(['test.md', 'key'], tmpDir);
159
+ expect(result.data).toEqual({ key: 'value' });
160
+ });
161
+
162
+ it('returns error for missing file', async () => {
163
+ const result = await frontmatterGet(['missing.md'], tmpDir);
164
+ expect(result.data).toEqual({ error: 'File not found', path: 'missing.md' });
165
+ });
166
+
167
+ it('throws GSDError for null bytes in path', async () => {
168
+ const { GSDError } = await import('../errors.js');
169
+ await expect(frontmatterGet(['bad\0path.md'], tmpDir)).rejects.toThrow(GSDError);
170
+ });
171
+ });
172
+
173
+ // ─── parseMustHavesBlock ───────────────────────────────────────────────────
174
+
175
+ describe('parseMustHavesBlock', () => {
176
+ it('parses artifacts block with path, provides, min_lines, contains, exports', () => {
177
+ const content = `---
178
+ phase: 12
179
+ must_haves:
180
+ artifacts:
181
+ - path: sdk/src/foo.ts
182
+ provides: Foo handler
183
+ min_lines: 50
184
+ contains: export function foo
185
+ exports:
186
+ - foo
187
+ - bar
188
+ ---
189
+ body`;
190
+ const result = parseMustHavesBlock(content, 'artifacts');
191
+ expect(result.items).toHaveLength(1);
192
+ expect(result.items[0]).toEqual({
193
+ path: 'sdk/src/foo.ts',
194
+ provides: 'Foo handler',
195
+ min_lines: 50,
196
+ contains: 'export function foo',
197
+ exports: ['foo', 'bar'],
198
+ });
199
+ });
200
+
201
+ it('parses key_links block with from, to, via, pattern', () => {
202
+ const content = `---
203
+ phase: 12
204
+ must_haves:
205
+ key_links:
206
+ - from: src/a.ts
207
+ to: src/b.ts
208
+ via: import something
209
+ pattern: import.*something.*from.*b
210
+ ---
211
+ body`;
212
+ const result = parseMustHavesBlock(content, 'key_links');
213
+ expect(result.items).toHaveLength(1);
214
+ expect(result.items[0]).toEqual({
215
+ from: 'src/a.ts',
216
+ to: 'src/b.ts',
217
+ via: 'import something',
218
+ pattern: 'import.*something.*from.*b',
219
+ });
220
+ });
221
+
222
+ it('parses simple string items (truths)', () => {
223
+ const content = `---
224
+ phase: 12
225
+ must_haves:
226
+ truths:
227
+ - Running verify returns valid
228
+ - Running check returns true
229
+ ---
230
+ body`;
231
+ const result = parseMustHavesBlock(content, 'truths');
232
+ expect(result.items).toHaveLength(2);
233
+ expect(result.items[0]).toBe('Running verify returns valid');
234
+ expect(result.items[1]).toBe('Running check returns true');
235
+ });
236
+
237
+ it('preserves nested array values (exports: [a, b])', () => {
238
+ const content = `---
239
+ must_haves:
240
+ artifacts:
241
+ - path: foo.ts
242
+ exports:
243
+ - alpha
244
+ - beta
245
+ ---
246
+ `;
247
+ const result = parseMustHavesBlock(content, 'artifacts');
248
+ expect(result.items[0]).toMatchObject({ exports: ['alpha', 'beta'] });
249
+ });
250
+
251
+ it('returns empty items for missing block', () => {
252
+ const content = `---
253
+ must_haves:
254
+ truths:
255
+ - something
256
+ ---
257
+ `;
258
+ const result = parseMustHavesBlock(content, 'artifacts');
259
+ expect(result.items).toEqual([]);
260
+ expect(result.warnings).toEqual([]);
261
+ });
262
+
263
+ it('returns empty items for no frontmatter', () => {
264
+ const result = parseMustHavesBlock('no frontmatter here', 'artifacts');
265
+ expect(result.items).toEqual([]);
266
+ expect(result.warnings).toEqual([]);
267
+ });
268
+
269
+ it('emits diagnostic warning when content lines exist but 0 items parsed', () => {
270
+ const content = `---
271
+ must_haves:
272
+ artifacts:
273
+ some badly formatted content
274
+ ---
275
+ `;
276
+ const result = parseMustHavesBlock(content, 'artifacts');
277
+ expect(result.items).toEqual([]);
278
+ expect(result.warnings.length).toBeGreaterThan(0);
279
+ expect(result.warnings[0]).toContain('artifacts');
280
+ });
281
+ });
@@ -0,0 +1,397 @@
1
+ /**
2
+ * Frontmatter parser and query handler.
3
+ *
4
+ * Ported from get-shit-done/bin/lib/frontmatter.cjs and state.cjs.
5
+ * Provides YAML frontmatter extraction from .planning/ artifacts.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { extractFrontmatter, frontmatterGet } from './frontmatter.js';
10
+ *
11
+ * const fm = extractFrontmatter('---\nphase: 10\nplan: 01\n---\nbody');
12
+ * // { phase: '10', plan: '01' }
13
+ *
14
+ * const result = await frontmatterGet(['STATE.md'], '/project');
15
+ * // { data: { gsd_state_version: '1.0', milestone: 'v3.0', ... } }
16
+ * ```
17
+ */
18
+
19
+ import { readFile } from 'node:fs/promises';
20
+ import { GSDError, ErrorClassification } from '../errors.js';
21
+ import type { QueryHandler } from './utils.js';
22
+ import { escapeRegex, resolvePathUnderProject } from './helpers.js';
23
+
24
+ // ─── splitInlineArray ───────────────────────────────────────────────────────
25
+
26
+ /**
27
+ * Quote-aware CSV splitting for inline YAML arrays.
28
+ *
29
+ * Handles both single and double quotes, preserving commas inside quotes.
30
+ *
31
+ * @param body - The content inside brackets, e.g. 'a, "b, c", d'
32
+ * @returns Array of trimmed values
33
+ */
34
+ export function splitInlineArray(body: string): string[] {
35
+ const items: string[] = [];
36
+ let current = '';
37
+ let inQuote: string | null = null;
38
+
39
+ for (let i = 0; i < body.length; i++) {
40
+ const ch = body[i];
41
+ if (inQuote) {
42
+ if (ch === inQuote) {
43
+ inQuote = null;
44
+ } else {
45
+ current += ch;
46
+ }
47
+ } else if (ch === '"' || ch === "'") {
48
+ inQuote = ch;
49
+ } else if (ch === ',') {
50
+ const trimmed = current.trim();
51
+ if (trimmed) items.push(trimmed);
52
+ current = '';
53
+ } else {
54
+ current += ch;
55
+ }
56
+ }
57
+ const trimmed = current.trim();
58
+ if (trimmed) items.push(trimmed);
59
+ return items;
60
+ }
61
+
62
+ // ─── parseFrontmatterYamlLines ───────────────────────────────────────────────
63
+
64
+ /**
65
+ * Parse YAML frontmatter body (between `---` fences) using the GSD stack parser.
66
+ * Shared by {@link extractFrontmatterLeading} and {@link extractFrontmatter}.
67
+ */
68
+ function parseFrontmatterYamlLines(yaml: string): Record<string, unknown> {
69
+ const frontmatter: Record<string, unknown> = {};
70
+ const lines = yaml.split(/\r?\n/);
71
+
72
+ // Stack to track nested objects: [{obj, key, indent}]
73
+ const stack: Array<{ obj: Record<string, unknown> | unknown[]; key: string | null; indent: number }> = [
74
+ { obj: frontmatter, key: null, indent: -1 },
75
+ ];
76
+
77
+ for (const line of lines) {
78
+ // Skip empty lines
79
+ if (line.trim() === '') continue;
80
+
81
+ // Calculate indentation (number of leading spaces)
82
+ const indentMatch = line.match(/^(\s*)/);
83
+ const indent = indentMatch ? indentMatch[1].length : 0;
84
+
85
+ // Pop stack back to appropriate level
86
+ while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
87
+ stack.pop();
88
+ }
89
+
90
+ const current = stack[stack.length - 1];
91
+
92
+ // Check for key: value pattern
93
+ const keyMatch = line.match(/^(\s*)([a-zA-Z0-9_-]+):\s*(.*)/);
94
+ if (keyMatch) {
95
+ const key = keyMatch[2];
96
+ const value = keyMatch[3].trim();
97
+
98
+ if (value === '' || value === '[') {
99
+ // Key with no value or opening bracket -- could be nested object or array
100
+ (current.obj as Record<string, unknown>)[key] = value === '[' ? [] : {};
101
+ current.key = null;
102
+ // Push new context for potential nested content
103
+ stack.push({ obj: (current.obj as Record<string, unknown>)[key] as Record<string, unknown>, key: null, indent });
104
+ } else if (value.startsWith('[') && value.endsWith(']')) {
105
+ // Inline array: key: [a, b, c]
106
+ (current.obj as Record<string, unknown>)[key] = splitInlineArray(value.slice(1, -1));
107
+ current.key = null;
108
+ } else {
109
+ // Simple key: value -- strip surrounding quotes
110
+ (current.obj as Record<string, unknown>)[key] = value.replace(/^["']|["']$/g, '');
111
+ current.key = null;
112
+ }
113
+ } else if (line.trim().startsWith('- ')) {
114
+ // Array item
115
+ const afterDash = line.trim().slice(2).trim();
116
+ let itemValue: unknown = afterDash.replace(/^["']|["']$/g, '');
117
+ let isObjItem = false;
118
+
119
+ // Extract key: value within the array item if present
120
+ const kvMatch = afterDash.match(/^([a-zA-Z0-9_-]+):\s*(.*)/);
121
+ if (kvMatch) {
122
+ isObjItem = true;
123
+ const k = kvMatch[1];
124
+ const v = kvMatch[2].trim().replace(/^["']|["']$/g, '');
125
+ itemValue = { [k]: v };
126
+ }
127
+
128
+ // If current context is an empty object, convert to array
129
+ if (typeof current.obj === 'object' && !Array.isArray(current.obj) && Object.keys(current.obj).length === 0) {
130
+ // Find the key in parent that points to this object and convert it
131
+ const parent = stack.length > 1 ? stack[stack.length - 2] : null;
132
+ if (parent && !Array.isArray(parent.obj)) {
133
+ for (const k of Object.keys(parent.obj as Record<string, unknown>)) {
134
+ if ((parent.obj as Record<string, unknown>)[k] === current.obj) {
135
+ (parent.obj as Record<string, unknown>)[k] = [itemValue];
136
+ current.obj = (parent.obj as Record<string, unknown>)[k] as unknown[];
137
+ break;
138
+ }
139
+ }
140
+ }
141
+ } else if (Array.isArray(current.obj)) {
142
+ current.obj.push(itemValue);
143
+ }
144
+
145
+ // Push object context onto stack so subsequent indented properties map to this object
146
+ if (isObjItem && Array.isArray(current.obj)) {
147
+ stack.push({ obj: itemValue as Record<string, unknown>, key: null, indent });
148
+ }
149
+ }
150
+ }
151
+
152
+ return frontmatter;
153
+ }
154
+
155
+ // ─── extractFrontmatterLeading ──────────────────────────────────────────────
156
+
157
+ /**
158
+ * First leading frontmatter block only — parity with `get-shit-done/bin/lib/frontmatter.cjs`
159
+ * `extractFrontmatter` (used by `summary-extract` and `history-digest` in gsd-tools.cjs).
160
+ */
161
+ export function extractFrontmatterLeading(content: string): Record<string, unknown> {
162
+ const match = content.match(/^---\r?\n([\s\S]+?)\r?\n---/);
163
+ if (!match) return {};
164
+ return parseFrontmatterYamlLines(match[1]);
165
+ }
166
+
167
+ // ─── extractFrontmatter ─────────────────────────────────────────────────────
168
+
169
+ /**
170
+ * Parse YAML frontmatter from file content.
171
+ *
172
+ * Full stack-based parser supporting:
173
+ * - Simple key: value pairs
174
+ * - Nested objects via indentation
175
+ * - Inline arrays: key: [a, b, c]
176
+ * - Dash arrays with auto-conversion from empty objects
177
+ * - Multiple stacked blocks (uses the LAST match)
178
+ * - CRLF line endings
179
+ * - Quoted value stripping
180
+ *
181
+ * @param content - File content potentially containing frontmatter
182
+ * @returns Parsed frontmatter as a record, or empty object if none found
183
+ */
184
+ export function extractFrontmatter(content: string): Record<string, unknown> {
185
+ // Find ALL frontmatter blocks. Use the LAST one (corruption recovery).
186
+ const allBlocks = [...content.matchAll(/(?:^|\n)\s*---\r?\n([\s\S]+?)\r?\n---/g)];
187
+ const match = allBlocks.length > 0 ? allBlocks[allBlocks.length - 1] : null;
188
+ if (!match) return {};
189
+
190
+ return parseFrontmatterYamlLines(match[1]);
191
+ }
192
+
193
+ // ─── stripFrontmatter ───────────────────────────────────────────────────────
194
+
195
+ /**
196
+ * Strip all frontmatter blocks from the start of content.
197
+ *
198
+ * Handles CRLF line endings and multiple stacked blocks (corruption recovery).
199
+ * Greedy: keeps stripping ---...--- blocks separated by optional whitespace.
200
+ *
201
+ * @param content - File content with potential frontmatter
202
+ * @returns Content with frontmatter removed
203
+ */
204
+ export function stripFrontmatter(content: string): string {
205
+ let result = content;
206
+ // eslint-disable-next-line no-constant-condition
207
+ while (true) {
208
+ const stripped = result.replace(/^\s*---\r?\n[\s\S]*?\r?\n---\s*/, '');
209
+ if (stripped === result) break;
210
+ result = stripped;
211
+ }
212
+ return result;
213
+ }
214
+
215
+ // ─── parseMustHavesBlock ────────────────────────────────────────────────────
216
+
217
+ /**
218
+ * Result of parsing a must_haves block from frontmatter.
219
+ */
220
+ export interface MustHavesBlockResult {
221
+ items: unknown[];
222
+ warnings: string[];
223
+ }
224
+
225
+ /**
226
+ * Parse a named block from must_haves in raw frontmatter YAML.
227
+ *
228
+ * Port of `parseMustHavesBlock` from `get-shit-done/bin/lib/frontmatter.cjs` lines 195-301.
229
+ * Handles 3-level nesting: `must_haves > blockName > [{key: value, ...}]`.
230
+ * Supports simple string items, structured objects with key-value pairs,
231
+ * and nested arrays within items.
232
+ *
233
+ * @param content - File content with frontmatter
234
+ * @param blockName - Block name under must_haves (e.g. 'artifacts', 'key_links', 'truths')
235
+ * @returns Structured result with items array and warnings
236
+ */
237
+ export function parseMustHavesBlock(content: string, blockName: string): MustHavesBlockResult {
238
+ const warnings: string[] = [];
239
+
240
+ // Extract raw YAML from first ---\n...\n--- block
241
+ const fmMatch = content.match(/^---\r?\n([\s\S]+?)\r?\n---/);
242
+ if (!fmMatch) return { items: [], warnings };
243
+
244
+ const yaml = fmMatch[1];
245
+
246
+ // Find must_haves: at its indentation level
247
+ const mustHavesMatch = yaml.match(/^(\s*)must_haves:\s*$/m);
248
+ if (!mustHavesMatch) return { items: [], warnings };
249
+ const mustHavesIndent = mustHavesMatch[1].length;
250
+
251
+ // Find the block (e.g., "artifacts:", "key_links:") under must_haves
252
+ const blockPattern = new RegExp(`^(\\s+)${escapeRegex(blockName)}:\\s*$`, 'm');
253
+ const blockMatch = yaml.match(blockPattern);
254
+ if (!blockMatch) return { items: [], warnings };
255
+
256
+ const blockIndent = blockMatch[1].length;
257
+ // The block must be nested under must_haves (more indented)
258
+ if (blockIndent <= mustHavesIndent) return { items: [], warnings };
259
+
260
+ // Find where the block starts in the yaml string
261
+ const blockStart = yaml.indexOf(blockMatch[0]);
262
+ if (blockStart === -1) return { items: [], warnings };
263
+
264
+ const afterBlock = yaml.slice(blockStart);
265
+ const blockLines = afterBlock.split(/\r?\n/).slice(1); // skip the header line
266
+
267
+ // List items are indented one level deeper than blockIndent
268
+ // Continuation KVs are indented one level deeper than list items
269
+ const items: unknown[] = [];
270
+ let current: Record<string, unknown> | string | null = null;
271
+ let listItemIndent = -1; // detected from first "- " line
272
+
273
+ for (const line of blockLines) {
274
+ // Skip empty lines
275
+ if (line.trim() === '') continue;
276
+ const indentMatch = line.match(/^(\s*)/);
277
+ const indent = indentMatch ? indentMatch[1].length : 0;
278
+ // Stop at same or lower indent level than the block header
279
+ if (indent <= blockIndent && line.trim() !== '') break;
280
+
281
+ const trimmed = line.trim();
282
+
283
+ if (trimmed.startsWith('- ')) {
284
+ // Detect list item indent from the first occurrence
285
+ if (listItemIndent === -1) listItemIndent = indent;
286
+
287
+ // Only treat as a top-level list item if at the expected indent
288
+ if (indent === listItemIndent) {
289
+ if (current !== null) items.push(current);
290
+ const afterDash = trimmed.slice(2);
291
+ // Check if it's a simple string item (no colon means not a key-value)
292
+ if (!afterDash.includes(':')) {
293
+ current = afterDash.replace(/^["']|["']$/g, '');
294
+ } else {
295
+ // Key-value on same line as dash: "- path: value"
296
+ const kvMatch = afterDash.match(/^(\w+):\s*"?([^"]*)"?\s*$/);
297
+ if (kvMatch) {
298
+ current = {} as Record<string, unknown>;
299
+ current[kvMatch[1]] = kvMatch[2];
300
+ } else {
301
+ current = {} as Record<string, unknown>;
302
+ }
303
+ }
304
+ continue;
305
+ }
306
+ }
307
+
308
+ if (current !== null && typeof current === 'object' && indent > listItemIndent) {
309
+ // Continuation key-value or nested array item
310
+ if (trimmed.startsWith('- ')) {
311
+ // Array item under a key
312
+ const arrVal = trimmed.slice(2).replace(/^["']|["']$/g, '');
313
+ const keys = Object.keys(current);
314
+ const lastKey = keys[keys.length - 1];
315
+ if (lastKey && !Array.isArray(current[lastKey])) {
316
+ current[lastKey] = current[lastKey] ? [current[lastKey]] : [];
317
+ }
318
+ if (lastKey) (current[lastKey] as unknown[]).push(arrVal);
319
+ } else {
320
+ const kvMatch = trimmed.match(/^(\w+):\s*"?([^"]*)"?\s*$/);
321
+ if (kvMatch) {
322
+ const val = kvMatch[2];
323
+ // Try to parse as number
324
+ current[kvMatch[1]] = /^\d+$/.test(val) ? parseInt(val, 10) : val;
325
+ }
326
+ }
327
+ }
328
+ }
329
+ if (current !== null) items.push(current);
330
+
331
+ // Diagnostic warning when block has content lines but parsed 0 items
332
+ if (items.length === 0 && blockLines.length > 0) {
333
+ const nonEmptyLines = blockLines.filter(l => l.trim() !== '').length;
334
+ if (nonEmptyLines > 0) {
335
+ warnings.push(
336
+ `must_haves.${blockName} block has ${nonEmptyLines} content lines but parsed 0 items. ` +
337
+ `Possible YAML formatting issue.`
338
+ );
339
+ }
340
+ }
341
+
342
+ return { items, warnings };
343
+ }
344
+
345
+ // ─── frontmatterGet ─────────────────────────────────────────────────────────
346
+
347
+ /**
348
+ * Query handler for frontmatter.get command.
349
+ *
350
+ * Reads a file, extracts frontmatter, and optionally returns a single field.
351
+ * Rejects null bytes in path (security: path traversal guard).
352
+ *
353
+ * @param args - args[0]: file path, args[1]: optional field name
354
+ * @param projectDir - Project root directory
355
+ * @returns QueryResult with parsed frontmatter or single field value
356
+ */
357
+ export const frontmatterGet: QueryHandler = async (args, projectDir) => {
358
+ const filePath = args[0];
359
+ if (!filePath) {
360
+ throw new GSDError('file path required', ErrorClassification.Validation);
361
+ }
362
+
363
+ // Path traversal guard: reject null bytes
364
+ if (filePath.includes('\0')) {
365
+ throw new GSDError('file path contains null bytes', ErrorClassification.Validation);
366
+ }
367
+
368
+ let fullPath: string;
369
+ try {
370
+ fullPath = await resolvePathUnderProject(projectDir, filePath);
371
+ } catch (err) {
372
+ if (err instanceof GSDError) {
373
+ return { data: { error: err.message, path: filePath } };
374
+ }
375
+ throw err;
376
+ }
377
+
378
+ let content: string;
379
+ try {
380
+ content = await readFile(fullPath, 'utf-8');
381
+ } catch {
382
+ return { data: { error: 'File not found', path: filePath } };
383
+ }
384
+
385
+ const fm = extractFrontmatter(content);
386
+ const field = args[1];
387
+
388
+ if (field) {
389
+ const value = fm[field];
390
+ if (value === undefined) {
391
+ return { data: { error: 'Field not found', field } };
392
+ }
393
+ return { data: { [field]: value } };
394
+ }
395
+
396
+ return { data: fm };
397
+ };