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,1324 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { appendFile, mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { join, relative } from 'node:path';
4
+
5
+ import type { PlanResult, SessionUsage } from './types.js';
6
+ import { extractFrontmatterLeading } from './query/frontmatter.js';
7
+ import { planningPaths, normalizePhaseName, phaseTokenMatches } from './query/helpers.js';
8
+ import { checkVerificationStatus } from './query/check-verification-status.js';
9
+
10
+ export type FailureEventKind =
11
+ | 'session_error'
12
+ | 'summary_issue'
13
+ | 'summary_self_check'
14
+ | 'verification_status'
15
+ | 'blocking_antipattern';
16
+
17
+ export type FailureSeverity = 'advisory' | 'blocking';
18
+
19
+ export interface FailureEvent {
20
+ version: '1.0';
21
+ recorded_at: string;
22
+ kind: FailureEventKind;
23
+ severity: FailureSeverity;
24
+ phase_number?: string;
25
+ phase_name?: string;
26
+ phase_dir?: string;
27
+ step?: string;
28
+ plan_id?: string;
29
+ session_id?: string;
30
+ summary: string;
31
+ details: string[];
32
+ source: {
33
+ type: 'session_result' | 'summary' | 'verification' | 'continue_here';
34
+ path?: string;
35
+ };
36
+ error_subtype?: string;
37
+ status?: string;
38
+ total_cost_usd?: number;
39
+ num_turns?: number;
40
+ usage?: SessionUsage;
41
+ fingerprint: string;
42
+ }
43
+
44
+ export interface FailureMemoryPaths {
45
+ directory: string;
46
+ events: string;
47
+ index: string;
48
+ overview: string;
49
+ }
50
+
51
+ export interface FailureEventContext {
52
+ phaseNumber?: string;
53
+ phaseName?: string;
54
+ phaseDir?: string;
55
+ step?: string;
56
+ planId?: string;
57
+ }
58
+
59
+ export type FailureMemoryStatus = 'candidate' | 'promoted';
60
+
61
+ export interface FailureMemoryEvidence {
62
+ recorded_at: string;
63
+ phase_number?: string;
64
+ plan_id?: string;
65
+ source_path?: string;
66
+ summary: string;
67
+ details: string[];
68
+ }
69
+
70
+ export interface FailureMemoryEntry {
71
+ version: '1.0';
72
+ id: string;
73
+ signature: string;
74
+ title: string;
75
+ kind: FailureEventKind;
76
+ severity: FailureSeverity;
77
+ status: FailureMemoryStatus;
78
+ summary: string;
79
+ evidence_count: number;
80
+ first_seen_at: string;
81
+ last_seen_at: string;
82
+ phase_numbers: string[];
83
+ source_paths: string[];
84
+ prevention_signals: string[];
85
+ promotion_reason?: string;
86
+ evidence: FailureMemoryEvidence[];
87
+ }
88
+
89
+ export interface FailureMemoryIndex {
90
+ version: '1.0';
91
+ updated_at: string;
92
+ entries: FailureMemoryEntry[];
93
+ }
94
+
95
+ export interface FailurePromotionResult {
96
+ paths: FailureMemoryPaths;
97
+ index: FailureMemoryIndex;
98
+ touched_entry_ids: string[];
99
+ promoted_entry_ids: string[];
100
+ candidate_entry_ids: string[];
101
+ }
102
+
103
+ export type FailurePreflightCheckStatus = 'pass' | 'warn' | 'block' | 'skip';
104
+
105
+ export interface FailurePreflightCheck {
106
+ id: string;
107
+ status: FailurePreflightCheckStatus;
108
+ summary: string;
109
+ details: string[];
110
+ memory_ids: string[];
111
+ }
112
+
113
+ export interface FailurePreflightResult {
114
+ checks: FailurePreflightCheck[];
115
+ blockers: FailurePreflightCheck[];
116
+ warnings: FailurePreflightCheck[];
117
+ passed: boolean;
118
+ recommended_package_manager?: string;
119
+ expected_node_version?: string;
120
+ related_memory_ids: string[];
121
+ }
122
+
123
+ interface ContinueHereRow {
124
+ pattern: string;
125
+ description: string;
126
+ severity: string;
127
+ prevention: string;
128
+ }
129
+
130
+ function toRelativeProjectPath(projectDir: string, fullPath: string): string {
131
+ const rel = relative(projectDir, fullPath);
132
+ return rel === '' ? '.' : rel.replaceAll('\\', '/');
133
+ }
134
+
135
+ function failureMemoryPaths(projectDir: string): FailureMemoryPaths {
136
+ const directory = join(planningPaths(projectDir).planning, 'failure-memory');
137
+ return {
138
+ directory,
139
+ events: join(directory, 'events.jsonl'),
140
+ index: join(directory, 'index.json'),
141
+ overview: join(planningPaths(projectDir).planning, 'FAILURE-MEMORY.md'),
142
+ };
143
+ }
144
+
145
+ function failureMemoryEntryPath(projectDir: string, id: string): string {
146
+ return join(failureMemoryPaths(projectDir).directory, `${id}.md`);
147
+ }
148
+
149
+ function buildFingerprint(event: Omit<FailureEvent, 'fingerprint'>): string {
150
+ return [
151
+ event.kind,
152
+ event.phase_number ?? '',
153
+ event.phase_name ?? '',
154
+ event.phase_dir ?? '',
155
+ event.step ?? '',
156
+ event.plan_id ?? '',
157
+ event.session_id ?? '',
158
+ event.source.type,
159
+ event.source.path ?? '',
160
+ event.error_subtype ?? '',
161
+ event.status ?? '',
162
+ event.summary,
163
+ ...event.details,
164
+ ].join('::');
165
+ }
166
+
167
+ function finalizeEvent(event: Omit<FailureEvent, 'fingerprint'>): FailureEvent {
168
+ return {
169
+ ...event,
170
+ fingerprint: buildFingerprint(event),
171
+ };
172
+ }
173
+
174
+ function normalizeLines(text: string): string[] {
175
+ return text
176
+ .split('\n')
177
+ .map(line => line.trim())
178
+ .filter(Boolean)
179
+ .map(line => line.replace(/^[-*+]\s+/, '').trim());
180
+ }
181
+
182
+ function normalizeSignatureText(text: string): string {
183
+ return text
184
+ .toLowerCase()
185
+ .replace(/`/g, '')
186
+ .replace(/\b\d{2}(?:-\d{2})?-summary\.md\b/g, 'summary')
187
+ .replace(/\b\d{2}-verification\.md\b/g, 'verification')
188
+ .replace(/[^\p{L}\p{N}\s-]+/gu, ' ')
189
+ .replace(/\s+/g, ' ')
190
+ .trim();
191
+ }
192
+
193
+ function titleCase(text: string): string {
194
+ if (!text) return text;
195
+ return text
196
+ .split(/\s+/)
197
+ .filter(Boolean)
198
+ .map(token => token[0]?.toUpperCase() + token.slice(1))
199
+ .join(' ');
200
+ }
201
+
202
+ function sentenceCase(text: string): string {
203
+ if (!text) return text;
204
+ return text[0].toUpperCase() + text.slice(1);
205
+ }
206
+
207
+ function uniqueSorted(values: Array<string | undefined>): string[] {
208
+ return [...new Set(values.filter((value): value is string => Boolean(value)))].sort();
209
+ }
210
+
211
+ function limitEvidenceSamples(events: FailureEvent[]): FailureMemoryEvidence[] {
212
+ const sorted = [...events]
213
+ .sort((a, b) => new Date(b.recorded_at).getTime() - new Date(a.recorded_at).getTime())
214
+ .slice(0, 5);
215
+
216
+ return sorted.map(event => ({
217
+ recorded_at: event.recorded_at,
218
+ phase_number: event.phase_number,
219
+ plan_id: event.plan_id,
220
+ source_path: event.source.path,
221
+ summary: event.summary,
222
+ details: event.details.slice(0, 4),
223
+ }));
224
+ }
225
+
226
+ function buildFailureSignature(event: FailureEvent): string {
227
+ switch (event.kind) {
228
+ case 'blocking_antipattern': {
229
+ const pattern = event.summary.replace(/^Blocking anti-pattern:\s*/i, '').trim();
230
+ return `${event.kind}::${normalizeSignatureText(pattern || event.details[0] || event.summary)}`;
231
+ }
232
+ case 'session_error': {
233
+ const detail = event.details[0] || event.summary;
234
+ return `${event.kind}::${event.error_subtype ?? 'unknown'}::${normalizeSignatureText(detail)}`;
235
+ }
236
+ case 'verification_status':
237
+ return `${event.kind}::${event.status ?? 'unknown'}`;
238
+ case 'summary_self_check':
239
+ case 'summary_issue': {
240
+ const detail = event.details[0] || event.summary;
241
+ return `${event.kind}::${normalizeSignatureText(detail)}`;
242
+ }
243
+ default:
244
+ return `${event.kind}::${normalizeSignatureText(event.summary)}`;
245
+ }
246
+ }
247
+
248
+ function buildFailureTitle(event: FailureEvent): string {
249
+ switch (event.kind) {
250
+ case 'blocking_antipattern':
251
+ return event.summary.replace(/^Blocking anti-pattern:\s*/i, '').trim() || 'Blocking Anti-Pattern';
252
+ case 'session_error':
253
+ return `Execution Error: ${titleCase((event.error_subtype ?? 'unknown').replaceAll('_', ' '))}`;
254
+ case 'verification_status':
255
+ return `Verification ${titleCase((event.status ?? 'unknown').replaceAll('_', ' '))}`;
256
+ case 'summary_self_check':
257
+ return 'Summary Self-Check Failure';
258
+ case 'summary_issue':
259
+ return 'Execution Issue';
260
+ default:
261
+ return sentenceCase(event.summary);
262
+ }
263
+ }
264
+
265
+ function buildFailureSummary(event: FailureEvent): string {
266
+ switch (event.kind) {
267
+ case 'blocking_antipattern':
268
+ return sentenceCase(event.details[0] || event.summary);
269
+ case 'session_error':
270
+ return sentenceCase(event.details[0] || event.summary);
271
+ case 'verification_status':
272
+ return `Verification ended with status \`${event.status ?? 'unknown'}\`.`;
273
+ case 'summary_self_check':
274
+ case 'summary_issue':
275
+ return sentenceCase(event.details[0] || event.summary);
276
+ default:
277
+ return sentenceCase(event.summary);
278
+ }
279
+ }
280
+
281
+ function collectPreventionSignals(events: FailureEvent[]): string[] {
282
+ const signals: string[] = [];
283
+ for (const event of events) {
284
+ if (event.kind === 'blocking_antipattern' && event.details[1]) {
285
+ signals.push(event.details[1]);
286
+ }
287
+ }
288
+ return uniqueSorted(signals.map(signal => signal.trim()).filter(Boolean));
289
+ }
290
+
291
+ function determinePromotionStatus(events: FailureEvent[]): { status: FailureMemoryStatus; reason?: string } {
292
+ if (events.some(event => event.kind === 'blocking_antipattern')) {
293
+ return {
294
+ status: 'promoted',
295
+ reason: 'Explicit blocking anti-pattern captured from pause/continue handoff.',
296
+ };
297
+ }
298
+
299
+ if (events.length >= 2) {
300
+ return {
301
+ status: 'promoted',
302
+ reason: `Repeated ${events.length} times across captured failure evidence.`,
303
+ };
304
+ }
305
+
306
+ return { status: 'candidate' };
307
+ }
308
+
309
+ async function readFailureEvents(projectDir: string): Promise<FailureEvent[]> {
310
+ const paths = failureMemoryPaths(projectDir);
311
+ if (!existsSync(paths.events)) return [];
312
+
313
+ let content = '';
314
+ try {
315
+ content = await readFile(paths.events, 'utf-8');
316
+ } catch {
317
+ return [];
318
+ }
319
+
320
+ const seen = new Set<string>();
321
+ const events: FailureEvent[] = [];
322
+ for (const line of content.split('\n').map(row => row.trim()).filter(Boolean)) {
323
+ try {
324
+ const event = JSON.parse(line) as FailureEvent;
325
+ if (!event.fingerprint || seen.has(event.fingerprint)) continue;
326
+ seen.add(event.fingerprint);
327
+ events.push(event);
328
+ } catch {
329
+ // Ignore malformed rows; failure-memory should remain best-effort.
330
+ }
331
+ }
332
+
333
+ return events.sort((a, b) => new Date(a.recorded_at).getTime() - new Date(b.recorded_at).getTime());
334
+ }
335
+
336
+ async function readFailureMemoryIndex(projectDir: string): Promise<FailureMemoryIndex | null> {
337
+ const paths = failureMemoryPaths(projectDir);
338
+ if (!existsSync(paths.index)) return null;
339
+ try {
340
+ return JSON.parse(await readFile(paths.index, 'utf-8')) as FailureMemoryIndex;
341
+ } catch {
342
+ return null;
343
+ }
344
+ }
345
+
346
+ function memorySearchText(entry: FailureMemoryEntry): string {
347
+ return [
348
+ entry.title,
349
+ entry.summary,
350
+ ...entry.prevention_signals,
351
+ ...entry.evidence.flatMap(item => [item.summary, ...item.details]),
352
+ ]
353
+ .join(' ')
354
+ .toLowerCase();
355
+ }
356
+
357
+ function hasAnyNeedle(entry: FailureMemoryEntry, needles: string[]): boolean {
358
+ const haystack = memorySearchText(entry);
359
+ return needles.some(needle => haystack.includes(needle));
360
+ }
361
+
362
+ interface RepoPackageManagerInfo {
363
+ expected?: string;
364
+ source?: string;
365
+ lockfiles: string[];
366
+ }
367
+
368
+ interface RepoNodeVersionInfo {
369
+ expected?: string;
370
+ source?: string;
371
+ current: string;
372
+ matches: boolean;
373
+ }
374
+
375
+ interface RepoScriptsInfo {
376
+ scripts: Record<string, string>;
377
+ source?: string;
378
+ }
379
+
380
+ interface RepoEnvTemplateInfo {
381
+ template_path?: string;
382
+ variable_names: string[];
383
+ configured_variables: string[];
384
+ }
385
+
386
+ function extractPackageManagerName(raw: string | undefined): string | undefined {
387
+ if (!raw) return undefined;
388
+ const trimmed = raw.trim();
389
+ if (!trimmed) return undefined;
390
+ return trimmed.split('@')[0]?.trim() || undefined;
391
+ }
392
+
393
+ function detectLockfiles(projectDir: string): string[] {
394
+ const candidates = [
395
+ 'pnpm-lock.yaml',
396
+ 'package-lock.json',
397
+ 'yarn.lock',
398
+ 'bun.lockb',
399
+ 'bun.lock',
400
+ ];
401
+ return candidates.filter(file => existsSync(join(projectDir, file)));
402
+ }
403
+
404
+ function packageManagerForLockfile(lockfile: string): string | undefined {
405
+ switch (lockfile) {
406
+ case 'pnpm-lock.yaml':
407
+ return 'pnpm';
408
+ case 'package-lock.json':
409
+ return 'npm';
410
+ case 'yarn.lock':
411
+ return 'yarn';
412
+ case 'bun.lock':
413
+ case 'bun.lockb':
414
+ return 'bun';
415
+ default:
416
+ return undefined;
417
+ }
418
+ }
419
+
420
+ async function detectRepoPackageManager(projectDir: string): Promise<RepoPackageManagerInfo> {
421
+ const info: RepoPackageManagerInfo = {
422
+ lockfiles: detectLockfiles(projectDir),
423
+ };
424
+
425
+ const packageJsonPath = join(projectDir, 'package.json');
426
+ if (!existsSync(packageJsonPath)) return info;
427
+
428
+ try {
429
+ const pkg = JSON.parse(await readFile(packageJsonPath, 'utf-8')) as {
430
+ packageManager?: string;
431
+ };
432
+ const expected = extractPackageManagerName(pkg.packageManager);
433
+ if (expected) {
434
+ info.expected = expected;
435
+ info.source = 'package.json#packageManager';
436
+ return info;
437
+ }
438
+ } catch {
439
+ return info;
440
+ }
441
+
442
+ if (info.lockfiles.includes('pnpm-lock.yaml')) return { ...info, expected: 'pnpm', source: 'pnpm-lock.yaml' };
443
+ if (info.lockfiles.includes('package-lock.json')) return { ...info, expected: 'npm', source: 'package-lock.json' };
444
+ if (info.lockfiles.includes('yarn.lock')) return { ...info, expected: 'yarn', source: 'yarn.lock' };
445
+ if (info.lockfiles.includes('bun.lockb') || info.lockfiles.includes('bun.lock')) {
446
+ return { ...info, expected: 'bun', source: info.lockfiles.includes('bun.lockb') ? 'bun.lockb' : 'bun.lock' };
447
+ }
448
+
449
+ return info;
450
+ }
451
+
452
+ async function detectRepoScripts(projectDir: string): Promise<RepoScriptsInfo> {
453
+ const packageJsonPath = join(projectDir, 'package.json');
454
+ if (!existsSync(packageJsonPath)) return { scripts: {} };
455
+
456
+ try {
457
+ const pkg = JSON.parse(await readFile(packageJsonPath, 'utf-8')) as {
458
+ scripts?: Record<string, string>;
459
+ };
460
+ return {
461
+ scripts: pkg.scripts ?? {},
462
+ source: 'package.json#scripts',
463
+ };
464
+ } catch {
465
+ return { scripts: {} };
466
+ }
467
+ }
468
+
469
+ function extractNodeMajor(raw: string | undefined): string | undefined {
470
+ if (!raw) return undefined;
471
+ const match = raw.trim().match(/(\d{1,3})(?:\.\d+){0,2}/);
472
+ return match?.[1];
473
+ }
474
+
475
+ async function detectRepoNodeVersion(projectDir: string): Promise<RepoNodeVersionInfo> {
476
+ const current = process.version;
477
+ const currentMajor = extractNodeMajor(current);
478
+ const files = [
479
+ { path: join(projectDir, '.nvmrc'), source: '.nvmrc' },
480
+ { path: join(projectDir, '.node-version'), source: '.node-version' },
481
+ ];
482
+
483
+ for (const file of files) {
484
+ if (!existsSync(file.path)) continue;
485
+ try {
486
+ const expected = (await readFile(file.path, 'utf-8')).trim();
487
+ const expectedMajor = extractNodeMajor(expected);
488
+ return {
489
+ expected,
490
+ source: file.source,
491
+ current,
492
+ matches: Boolean(expectedMajor && currentMajor && expectedMajor === currentMajor),
493
+ };
494
+ } catch {
495
+ // Ignore parse failures and keep checking.
496
+ }
497
+ }
498
+
499
+ const packageJsonPath = join(projectDir, 'package.json');
500
+ if (existsSync(packageJsonPath)) {
501
+ try {
502
+ const pkg = JSON.parse(await readFile(packageJsonPath, 'utf-8')) as {
503
+ engines?: { node?: string };
504
+ volta?: { node?: string };
505
+ };
506
+ const expected = pkg.volta?.node ?? pkg.engines?.node;
507
+ if (expected) {
508
+ const expectedMajor = extractNodeMajor(expected);
509
+ return {
510
+ expected,
511
+ source: pkg.volta?.node ? 'package.json#volta.node' : 'package.json#engines.node',
512
+ current,
513
+ matches: Boolean(expectedMajor && currentMajor && expectedMajor === currentMajor),
514
+ };
515
+ }
516
+ } catch {
517
+ // Ignore parse failures.
518
+ }
519
+ }
520
+
521
+ return { current, matches: true };
522
+ }
523
+
524
+ function parseDotEnvLines(content: string): string[] {
525
+ return content
526
+ .split('\n')
527
+ .map(line => line.trim())
528
+ .filter(line => line && !line.startsWith('#'))
529
+ .map(line => line.replace(/^export\s+/, ''))
530
+ .map(line => line.split('=')[0]?.trim() ?? '')
531
+ .filter(Boolean);
532
+ }
533
+
534
+ async function detectRepoEnvTemplate(projectDir: string): Promise<RepoEnvTemplateInfo> {
535
+ const templateCandidates = [
536
+ '.env.example',
537
+ '.env.sample',
538
+ '.env.local.example',
539
+ ];
540
+ const configuredCandidates = [
541
+ '.env',
542
+ '.env.local',
543
+ '.env.development',
544
+ '.env.test',
545
+ ];
546
+
547
+ const info: RepoEnvTemplateInfo = {
548
+ variable_names: [],
549
+ configured_variables: [],
550
+ };
551
+
552
+ for (const candidate of configuredCandidates) {
553
+ const fullPath = join(projectDir, candidate);
554
+ if (!existsSync(fullPath)) continue;
555
+ try {
556
+ info.configured_variables.push(...parseDotEnvLines(await readFile(fullPath, 'utf-8')));
557
+ } catch {
558
+ // Ignore parse failures.
559
+ }
560
+ }
561
+ info.configured_variables = uniqueSorted(info.configured_variables);
562
+
563
+ for (const candidate of templateCandidates) {
564
+ const fullPath = join(projectDir, candidate);
565
+ if (!existsSync(fullPath)) continue;
566
+ try {
567
+ info.variable_names = uniqueSorted(parseDotEnvLines(await readFile(fullPath, 'utf-8')));
568
+ info.template_path = candidate;
569
+ return info;
570
+ } catch {
571
+ // Ignore parse failures and keep looking.
572
+ }
573
+ }
574
+
575
+ return info;
576
+ }
577
+
578
+ function collectMemoryTexts(entries: FailureMemoryEntry[]): string[] {
579
+ return entries.flatMap(entry => [
580
+ entry.title,
581
+ entry.summary,
582
+ ...entry.prevention_signals,
583
+ ...entry.evidence.flatMap(item => [item.summary, ...item.details]),
584
+ ]);
585
+ }
586
+
587
+ function extractExpectedScriptNames(entries: FailureMemoryEntry[]): string[] {
588
+ const scriptNames = new Set<string>();
589
+ const patterns = [
590
+ /(?:npm|pnpm|yarn|bun)\s+run\s+([a-zA-Z0-9:_-]+)/gi,
591
+ /missing script:?\s*["']?([a-zA-Z0-9:_-]+)["']?/gi,
592
+ /script(?:\s+entry)?\s+["'`]?([a-zA-Z0-9:_-]+)["'`]?/gi,
593
+ ];
594
+
595
+ for (const text of collectMemoryTexts(entries)) {
596
+ for (const pattern of patterns) {
597
+ for (const match of text.matchAll(pattern)) {
598
+ const script = match[1]?.trim();
599
+ if (script) scriptNames.add(script);
600
+ }
601
+ }
602
+ }
603
+
604
+ return [...scriptNames].sort();
605
+ }
606
+
607
+ function extractExpectedEnvVars(entries: FailureMemoryEntry[]): string[] {
608
+ const envVars = new Set<string>();
609
+ const strongNamePattern = /\b([A-Z][A-Z0-9_]*(?:KEY|TOKEN|SECRET|URL|ID|PASS|PASSWORD|HOST|PORT))\b/g;
610
+ const explicitPattern = /\b([A-Z][A-Z0-9_]{2,})\b/g;
611
+
612
+ for (const text of collectMemoryTexts(entries)) {
613
+ const lower = text.toLowerCase();
614
+ const isEnvText = lower.includes('env') || lower.includes('environment') || lower.includes('api key') || lower.includes('.env');
615
+ if (!isEnvText) continue;
616
+
617
+ for (const match of text.matchAll(strongNamePattern)) {
618
+ envVars.add(match[1]);
619
+ }
620
+
621
+ if (envVars.size === 0) {
622
+ for (const match of text.matchAll(explicitPattern)) {
623
+ const candidate = match[1];
624
+ if (candidate.length >= 5 && candidate !== 'PATH' && candidate !== 'HOME') {
625
+ envVars.add(candidate);
626
+ }
627
+ }
628
+ }
629
+ }
630
+
631
+ return [...envVars].sort();
632
+ }
633
+
634
+ function nextFailureMemoryId(existing: Iterable<string>): string {
635
+ let max = 0;
636
+ for (const id of existing) {
637
+ const match = /^FM-(\d+)$/.exec(id);
638
+ if (!match) continue;
639
+ max = Math.max(max, parseInt(match[1], 10));
640
+ }
641
+ return `FM-${String(max + 1).padStart(3, '0')}`;
642
+ }
643
+
644
+ function renderFailureMemoryOverview(index: FailureMemoryIndex): string {
645
+ const promoted = index.entries.filter(entry => entry.status === 'promoted');
646
+ const candidates = index.entries.filter(entry => entry.status === 'candidate');
647
+
648
+ const renderSection = (entries: FailureMemoryEntry[], empty: string): string => {
649
+ if (entries.length === 0) return `${empty}\n`;
650
+ return entries
651
+ .map(entry => {
652
+ const phases = entry.phase_numbers.length > 0 ? entry.phase_numbers.join(', ') : 'unknown';
653
+ return `- [${entry.id}](failure-memory/${entry.id}.md) — ${entry.title} (${entry.evidence_count} evidence, phases: ${phases})`;
654
+ })
655
+ .join('\n') + '\n';
656
+ };
657
+
658
+ return `# Failure Memory
659
+
660
+ This file summarizes persistent failure patterns captured from \`.planning/failure-memory/events.jsonl\`.
661
+
662
+ ## Promoted Memories
663
+
664
+ ${renderSection(promoted, 'No promoted failure memories yet.')}
665
+ ## Active Candidates
666
+
667
+ ${renderSection(candidates, 'No active candidates yet.')}
668
+ `;
669
+ }
670
+
671
+ function renderFailureMemoryEntry(entry: FailureMemoryEntry): string {
672
+ const phasesYaml = entry.phase_numbers.map(phase => ` - "${phase}"`).join('\n');
673
+ const prevention = entry.prevention_signals.length > 0
674
+ ? entry.prevention_signals.map(signal => `- ${signal}`).join('\n')
675
+ : '- No explicit prevention signal captured yet.';
676
+ const evidence = entry.evidence
677
+ .map(item => {
678
+ const location = [item.phase_number ? `Phase ${item.phase_number}` : null, item.plan_id ? `Plan ${item.plan_id}` : null]
679
+ .filter(Boolean)
680
+ .join(' · ');
681
+ const source = item.source_path ? ` (${item.source_path})` : '';
682
+ const details = item.details.length > 0
683
+ ? `\n Details:\n${item.details.map(detail => ` - ${detail}`).join('\n')}`
684
+ : '';
685
+ return `- ${item.recorded_at}${location ? ` — ${location}` : ''}: ${item.summary}${source}${details}`;
686
+ })
687
+ .join('\n');
688
+
689
+ return `---
690
+ id: ${entry.id}
691
+ status: ${entry.status}
692
+ kind: ${entry.kind}
693
+ severity: ${entry.severity}
694
+ evidence_count: ${entry.evidence_count}
695
+ first_seen: ${entry.first_seen_at}
696
+ last_seen: ${entry.last_seen_at}
697
+ phase_numbers:
698
+ ${phasesYaml || ' - "unknown"'}
699
+ ---
700
+
701
+ # ${entry.id} — ${entry.title}
702
+
703
+ ## Summary
704
+
705
+ ${entry.summary}
706
+
707
+ ## Promotion Status
708
+
709
+ - Status: ${entry.status}
710
+ - Reason: ${entry.promotion_reason ?? 'Not promoted yet.'}
711
+
712
+ ## Prevention Signals
713
+
714
+ ${prevention}
715
+
716
+ ## Evidence
717
+
718
+ ${evidence || '- No evidence samples recorded.'}
719
+ `;
720
+ }
721
+
722
+ function extractSection(content: string, heading: string): string | null {
723
+ const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
724
+ const match = content.match(new RegExp(`(?:^|\\n)##\\s+${escaped}\\s*\\n([\\s\\S]*?)(?=\\n##\\s+|\\n---\\s*(?:\\n|$)|$)`, 'i'));
725
+ return match ? match[1].trim() : null;
726
+ }
727
+
728
+ function hasMeaningfulFailureContent(section: string | null): boolean {
729
+ if (!section) return false;
730
+ const normalized = normalizeLines(section).join(' ').toLowerCase();
731
+ if (!normalized) return false;
732
+ return !(
733
+ normalized === 'none' ||
734
+ normalized === 'none.' ||
735
+ normalized === 'none - no issues encountered' ||
736
+ normalized === 'n/a'
737
+ );
738
+ }
739
+
740
+ function extractSelfCheck(content: string): { status: 'PASSED' | 'FAILED'; details: string[] } | null {
741
+ const match = content.match(/(?:^|\n)##\s+Self-Check:\s*(PASSED|FAILED)\s*\n([\s\S]*?)(?=\n##\s+|\n---\s*(?:\n|$)|$)/i);
742
+ if (!match) return null;
743
+ return {
744
+ status: match[1].toUpperCase() as 'PASSED' | 'FAILED',
745
+ details: normalizeLines(match[2]),
746
+ };
747
+ }
748
+
749
+ function parseTableRows(content: string): Array<{ cells: string[]; raw: string }> {
750
+ return content
751
+ .split('\n')
752
+ .filter(line => {
753
+ const trimmed = line.trim();
754
+ return trimmed.startsWith('|') && trimmed.endsWith('|') && !/^\|[-: |]+\|$/.test(trimmed);
755
+ })
756
+ .map(line => ({
757
+ raw: line.trim(),
758
+ cells: line
759
+ .split('|')
760
+ .slice(1, -1)
761
+ .map(cell => cell.trim()),
762
+ }));
763
+ }
764
+
765
+ function parseBlockingAntiPatterns(content: string): ContinueHereRow[] {
766
+ const section = extractSection(content, 'Critical Anti-Patterns');
767
+ if (!section) return [];
768
+ const rows = parseTableRows(section);
769
+ if (rows.length < 2) return [];
770
+
771
+ const header = rows[0].cells.map(cell => cell.toLowerCase());
772
+ const patternIdx = header.findIndex(cell => cell === 'pattern');
773
+ const descIdx = header.findIndex(cell => cell === 'description');
774
+ const severityIdx = header.findIndex(cell => cell === 'severity');
775
+ const preventionIdx = header.findIndex(cell => cell.includes('prevention'));
776
+ const dataRows = rows.slice(1);
777
+
778
+ return dataRows
779
+ .map(row => ({
780
+ pattern: row.cells[patternIdx] ?? row.cells[0] ?? row.raw,
781
+ description: row.cells[descIdx] ?? '',
782
+ severity: (row.cells[severityIdx] ?? '').toLowerCase(),
783
+ prevention: row.cells[preventionIdx] ?? '',
784
+ }))
785
+ .filter(row => row.severity === 'blocking');
786
+ }
787
+
788
+ async function resolvePhaseDir(projectDir: string, phaseNumber?: string, phaseDir?: string): Promise<string | null> {
789
+ if (phaseDir && existsSync(phaseDir)) return phaseDir;
790
+ if (!phaseNumber) return null;
791
+
792
+ const phasesDir = planningPaths(projectDir).phases;
793
+ try {
794
+ const entries = await readdir(phasesDir, { withFileTypes: true });
795
+ const normalized = normalizePhaseName(phaseNumber);
796
+ const match = entries
797
+ .filter(entry => entry.isDirectory())
798
+ .map(entry => entry.name)
799
+ .find(entry => phaseTokenMatches(entry, normalized));
800
+ return match ? join(phasesDir, match) : null;
801
+ } catch {
802
+ return null;
803
+ }
804
+ }
805
+
806
+ async function listPhaseFiles(phaseDir: string): Promise<string[]> {
807
+ try {
808
+ return await readdir(phaseDir);
809
+ } catch {
810
+ return [];
811
+ }
812
+ }
813
+
814
+ function extractPlanIdFromSummaryFile(filename: string): string | undefined {
815
+ if (!filename.endsWith('-SUMMARY.md') && filename !== 'SUMMARY.md') return undefined;
816
+ return filename === 'SUMMARY.md' ? undefined : filename.replace(/-SUMMARY\.md$/, '');
817
+ }
818
+
819
+ export async function appendFailureEvents(projectDir: string, events: FailureEvent[]): Promise<FailureMemoryPaths> {
820
+ const paths = failureMemoryPaths(projectDir);
821
+ if (events.length === 0) return paths;
822
+
823
+ await mkdir(paths.directory, { recursive: true });
824
+ const existingFingerprints = new Set((await readFailureEvents(projectDir)).map(event => event.fingerprint));
825
+ const uniqueEvents = events.filter(event => {
826
+ if (existingFingerprints.has(event.fingerprint)) return false;
827
+ existingFingerprints.add(event.fingerprint);
828
+ return true;
829
+ });
830
+ if (uniqueEvents.length === 0) return paths;
831
+
832
+ const payload = uniqueEvents.map(event => JSON.stringify(event)).join('\n') + '\n';
833
+ await appendFile(paths.events, payload, 'utf-8');
834
+ return paths;
835
+ }
836
+
837
+ export async function recordPlanResultFailure(
838
+ projectDir: string,
839
+ context: FailureEventContext,
840
+ result: PlanResult,
841
+ ): Promise<FailureEvent[]> {
842
+ if (result.success || !result.error) return [];
843
+ const normalizedPhaseNumber = context.phaseNumber ? normalizePhaseName(context.phaseNumber) : undefined;
844
+
845
+ const event = finalizeEvent({
846
+ version: '1.0',
847
+ recorded_at: new Date().toISOString(),
848
+ kind: 'session_error',
849
+ severity: 'blocking',
850
+ phase_number: normalizedPhaseNumber,
851
+ phase_name: context.phaseName,
852
+ phase_dir: context.phaseDir ? toRelativeProjectPath(projectDir, context.phaseDir) : undefined,
853
+ step: context.step,
854
+ plan_id: context.planId,
855
+ session_id: result.sessionId || undefined,
856
+ summary: `Session error during ${context.step ?? 'execution'}: ${result.error.subtype}`,
857
+ details: result.error.messages,
858
+ source: {
859
+ type: 'session_result',
860
+ },
861
+ error_subtype: result.error.subtype,
862
+ total_cost_usd: result.totalCostUsd,
863
+ num_turns: result.numTurns,
864
+ usage: result.usage,
865
+ });
866
+
867
+ await appendFailureEvents(projectDir, [event]);
868
+ return [event];
869
+ }
870
+
871
+ export async function collectPhaseArtifactFailureEvents(
872
+ projectDir: string,
873
+ context: FailureEventContext,
874
+ ): Promise<FailureEvent[]> {
875
+ const normalizedPhaseNumber = context.phaseNumber ? normalizePhaseName(context.phaseNumber) : undefined;
876
+ const phaseDir = await resolvePhaseDir(projectDir, normalizedPhaseNumber, context.phaseDir);
877
+ if (!phaseDir) return [];
878
+
879
+ const events: FailureEvent[] = [];
880
+ const phaseDirRel = toRelativeProjectPath(projectDir, phaseDir);
881
+ const phaseFiles = await listPhaseFiles(phaseDir);
882
+ const summaryFiles = phaseFiles.filter(file => file.endsWith('-SUMMARY.md') || file === 'SUMMARY.md').sort();
883
+
884
+ for (const summaryFile of summaryFiles) {
885
+ const fullPath = join(phaseDir, summaryFile);
886
+ let content = '';
887
+ try {
888
+ content = await readFile(fullPath, 'utf-8');
889
+ } catch {
890
+ continue;
891
+ }
892
+
893
+ const planId = extractPlanIdFromSummaryFile(summaryFile);
894
+ const issuesSection = extractSection(content, 'Issues Encountered');
895
+ if (hasMeaningfulFailureContent(issuesSection)) {
896
+ const details = normalizeLines(issuesSection ?? '');
897
+ events.push(finalizeEvent({
898
+ version: '1.0',
899
+ recorded_at: new Date().toISOString(),
900
+ kind: 'summary_issue',
901
+ severity: 'advisory',
902
+ phase_number: normalizedPhaseNumber,
903
+ phase_name: context.phaseName,
904
+ phase_dir: phaseDirRel,
905
+ plan_id: planId,
906
+ summary: `Execution issues recorded in ${summaryFile}`,
907
+ details,
908
+ source: {
909
+ type: 'summary',
910
+ path: toRelativeProjectPath(projectDir, fullPath),
911
+ },
912
+ }));
913
+ }
914
+
915
+ const selfCheck = extractSelfCheck(content);
916
+ if (selfCheck?.status === 'FAILED') {
917
+ events.push(finalizeEvent({
918
+ version: '1.0',
919
+ recorded_at: new Date().toISOString(),
920
+ kind: 'summary_self_check',
921
+ severity: 'blocking',
922
+ phase_number: normalizedPhaseNumber,
923
+ phase_name: context.phaseName,
924
+ phase_dir: phaseDirRel,
925
+ plan_id: planId,
926
+ summary: `Self-check failed in ${summaryFile}`,
927
+ details: selfCheck.details,
928
+ source: {
929
+ type: 'summary',
930
+ path: toRelativeProjectPath(projectDir, fullPath),
931
+ },
932
+ }));
933
+ }
934
+ }
935
+
936
+ const verificationFiles = phaseFiles
937
+ .filter(file => file.endsWith('-VERIFICATION.md') || file === 'VERIFICATION.md')
938
+ .sort();
939
+ const verificationFile = verificationFiles[0];
940
+ if (verificationFile && normalizedPhaseNumber) {
941
+ const verificationPath = join(phaseDir, verificationFile);
942
+ try {
943
+ const verification = await checkVerificationStatus([normalizedPhaseNumber], projectDir);
944
+ const verificationData = verification.data as {
945
+ status?: string;
946
+ gaps?: string[];
947
+ human_items?: string[];
948
+ deferred?: string[];
949
+ };
950
+ const status = verificationData.status ?? 'missing';
951
+ if (status !== 'pass' && status !== 'missing') {
952
+ const details = [
953
+ ...(verificationData.gaps ?? []),
954
+ ...(verificationData.human_items ?? []),
955
+ ...(verificationData.deferred ?? []),
956
+ ];
957
+ events.push(finalizeEvent({
958
+ version: '1.0',
959
+ recorded_at: new Date().toISOString(),
960
+ kind: 'verification_status',
961
+ severity: status === 'human_needed' ? 'advisory' : 'blocking',
962
+ phase_number: normalizedPhaseNumber,
963
+ phase_name: context.phaseName,
964
+ phase_dir: phaseDirRel,
965
+ summary: `Verification status is ${status}`,
966
+ details,
967
+ source: {
968
+ type: 'verification',
969
+ path: toRelativeProjectPath(projectDir, verificationPath),
970
+ },
971
+ status,
972
+ }));
973
+ } else {
974
+ const raw = await readFile(verificationPath, 'utf-8');
975
+ const fm = extractFrontmatterLeading(raw) as { status?: string };
976
+ if (fm.status && fm.status !== 'passed') {
977
+ events.push(finalizeEvent({
978
+ version: '1.0',
979
+ recorded_at: new Date().toISOString(),
980
+ kind: 'verification_status',
981
+ severity: fm.status === 'human_needed' ? 'advisory' : 'blocking',
982
+ phase_number: normalizedPhaseNumber,
983
+ phase_name: context.phaseName,
984
+ phase_dir: phaseDirRel,
985
+ summary: `Verification frontmatter status is ${fm.status}`,
986
+ details: [],
987
+ source: {
988
+ type: 'verification',
989
+ path: toRelativeProjectPath(projectDir, verificationPath),
990
+ },
991
+ status: fm.status,
992
+ }));
993
+ }
994
+ }
995
+ } catch {
996
+ // Ignore verification parsing failures — capture should never block the phase flow.
997
+ }
998
+ }
999
+
1000
+ const continueHereCandidates = [
1001
+ join(phaseDir, '.continue-here.md'),
1002
+ join(planningPaths(projectDir).planning, '.continue-here.md'),
1003
+ join(projectDir, '.continue-here.md'),
1004
+ ];
1005
+ const seenContinueHere = new Set<string>();
1006
+
1007
+ for (const candidate of continueHereCandidates) {
1008
+ if (seenContinueHere.has(candidate) || !existsSync(candidate)) continue;
1009
+ seenContinueHere.add(candidate);
1010
+
1011
+ let content = '';
1012
+ try {
1013
+ content = await readFile(candidate, 'utf-8');
1014
+ } catch {
1015
+ continue;
1016
+ }
1017
+
1018
+ const antiPatterns = parseBlockingAntiPatterns(content);
1019
+ for (const antiPattern of antiPatterns) {
1020
+ events.push(finalizeEvent({
1021
+ version: '1.0',
1022
+ recorded_at: new Date().toISOString(),
1023
+ kind: 'blocking_antipattern',
1024
+ severity: 'blocking',
1025
+ phase_number: normalizedPhaseNumber,
1026
+ phase_name: context.phaseName,
1027
+ phase_dir: phaseDirRel,
1028
+ summary: `Blocking anti-pattern: ${antiPattern.pattern}`,
1029
+ details: [antiPattern.description, antiPattern.prevention].filter(Boolean),
1030
+ source: {
1031
+ type: 'continue_here',
1032
+ path: toRelativeProjectPath(projectDir, candidate),
1033
+ },
1034
+ }));
1035
+ }
1036
+ }
1037
+
1038
+ return events;
1039
+ }
1040
+
1041
+ export async function recordPhaseArtifactFailureEvents(
1042
+ projectDir: string,
1043
+ context: FailureEventContext,
1044
+ ): Promise<FailureEvent[]> {
1045
+ const events = await collectPhaseArtifactFailureEvents(projectDir, context);
1046
+ await appendFailureEvents(projectDir, events);
1047
+ return events;
1048
+ }
1049
+
1050
+ export async function promoteFailureMemory(
1051
+ projectDir: string,
1052
+ phaseNumber?: string,
1053
+ ): Promise<FailurePromotionResult> {
1054
+ const paths = failureMemoryPaths(projectDir);
1055
+ await mkdir(paths.directory, { recursive: true });
1056
+
1057
+ const normalizedPhaseNumber = phaseNumber ? normalizePhaseName(phaseNumber) : undefined;
1058
+ const events = await readFailureEvents(projectDir);
1059
+ const existingIndex = await readFailureMemoryIndex(projectDir);
1060
+ const existingIds = new Set(existingIndex?.entries.map(entry => entry.id) ?? []);
1061
+ const idBySignature = new Map<string, string>(
1062
+ (existingIndex?.entries ?? []).map(entry => [entry.signature, entry.id]),
1063
+ );
1064
+
1065
+ const groups = new Map<string, FailureEvent[]>();
1066
+ for (const event of events) {
1067
+ const signature = buildFailureSignature(event);
1068
+ const existing = groups.get(signature) ?? [];
1069
+ existing.push(event);
1070
+ groups.set(signature, existing);
1071
+ }
1072
+
1073
+ const entries = [...groups.entries()]
1074
+ .map(([signature, groupedEvents]) => {
1075
+ const sortedEvents = [...groupedEvents].sort(
1076
+ (a, b) => new Date(a.recorded_at).getTime() - new Date(b.recorded_at).getTime(),
1077
+ );
1078
+ const firstEvent = sortedEvents[0];
1079
+ let id = idBySignature.get(signature);
1080
+ if (!id) {
1081
+ id = nextFailureMemoryId(existingIds);
1082
+ existingIds.add(id);
1083
+ idBySignature.set(signature, id);
1084
+ }
1085
+
1086
+ const promotion = determinePromotionStatus(sortedEvents);
1087
+ return {
1088
+ version: '1.0' as const,
1089
+ id,
1090
+ signature,
1091
+ title: buildFailureTitle(firstEvent),
1092
+ kind: firstEvent.kind,
1093
+ severity: sortedEvents.some(event => event.severity === 'blocking') ? 'blocking' : 'advisory',
1094
+ status: promotion.status,
1095
+ summary: buildFailureSummary(firstEvent),
1096
+ evidence_count: sortedEvents.length,
1097
+ first_seen_at: sortedEvents[0].recorded_at,
1098
+ last_seen_at: sortedEvents[sortedEvents.length - 1].recorded_at,
1099
+ phase_numbers: uniqueSorted(sortedEvents.map(event => event.phase_number)),
1100
+ source_paths: uniqueSorted(sortedEvents.map(event => event.source.path)),
1101
+ prevention_signals: collectPreventionSignals(sortedEvents),
1102
+ promotion_reason: promotion.reason,
1103
+ evidence: limitEvidenceSamples(sortedEvents),
1104
+ } satisfies FailureMemoryEntry;
1105
+ })
1106
+ .sort((a, b) => {
1107
+ if (a.status !== b.status) return a.status === 'promoted' ? -1 : 1;
1108
+ return new Date(b.last_seen_at).getTime() - new Date(a.last_seen_at).getTime();
1109
+ });
1110
+
1111
+ const index: FailureMemoryIndex = {
1112
+ version: '1.0',
1113
+ updated_at: new Date().toISOString(),
1114
+ entries,
1115
+ };
1116
+
1117
+ await writeFile(paths.index, JSON.stringify(index, null, 2) + '\n', 'utf-8');
1118
+ await writeFile(paths.overview, renderFailureMemoryOverview(index), 'utf-8');
1119
+
1120
+ for (const entry of entries) {
1121
+ await writeFile(failureMemoryEntryPath(projectDir, entry.id), renderFailureMemoryEntry(entry), 'utf-8');
1122
+ }
1123
+
1124
+ const touchedEntries = normalizedPhaseNumber
1125
+ ? entries.filter(entry => entry.phase_numbers.includes(normalizedPhaseNumber))
1126
+ : entries;
1127
+
1128
+ return {
1129
+ paths,
1130
+ index,
1131
+ touched_entry_ids: touchedEntries.map(entry => entry.id),
1132
+ promoted_entry_ids: touchedEntries.filter(entry => entry.status === 'promoted').map(entry => entry.id),
1133
+ candidate_entry_ids: touchedEntries.filter(entry => entry.status === 'candidate').map(entry => entry.id),
1134
+ };
1135
+ }
1136
+
1137
+ export async function runFailurePreflight(projectDir: string): Promise<FailurePreflightResult> {
1138
+ const index = await readFailureMemoryIndex(projectDir);
1139
+ if (!index) {
1140
+ return {
1141
+ checks: [],
1142
+ blockers: [],
1143
+ warnings: [],
1144
+ passed: true,
1145
+ related_memory_ids: [],
1146
+ };
1147
+ }
1148
+
1149
+ const promoted = index.entries.filter(entry => entry.status === 'promoted');
1150
+ const checks: FailurePreflightCheck[] = [];
1151
+ const relatedMemoryIds = new Set<string>();
1152
+
1153
+ const packageManagerEntries = promoted.filter(entry =>
1154
+ hasAnyNeedle(entry, ['package manager', 'pnpm', 'npm', 'yarn', 'bun', 'lockfile']),
1155
+ );
1156
+ if (packageManagerEntries.length > 0) {
1157
+ packageManagerEntries.forEach(entry => relatedMemoryIds.add(entry.id));
1158
+ const pkg = await detectRepoPackageManager(projectDir);
1159
+ const details: string[] = [];
1160
+ let status: FailurePreflightCheckStatus = 'warn';
1161
+ let summary = 'Package manager drift was seen before; prefer the repo-declared tool.';
1162
+
1163
+ if (pkg.expected) {
1164
+ details.push(`Expected package manager: ${pkg.expected} (${pkg.source ?? 'detected'})`);
1165
+ summary = `Use ${pkg.expected} for install/test/build commands in this repo.`;
1166
+ status = 'pass';
1167
+ } else {
1168
+ details.push('No explicit package manager detected from package.json or lockfiles.');
1169
+ }
1170
+
1171
+ if (pkg.lockfiles.length > 1) {
1172
+ status = 'warn';
1173
+ details.push(`Multiple lockfiles present: ${pkg.lockfiles.join(', ')}`);
1174
+ } else if (pkg.lockfiles.length === 1) {
1175
+ details.push(`Detected lockfile: ${pkg.lockfiles[0]}`);
1176
+ const lockfileManager = packageManagerForLockfile(pkg.lockfiles[0]);
1177
+ if (pkg.expected && lockfileManager && lockfileManager !== pkg.expected) {
1178
+ status = 'warn';
1179
+ details.push(`Lockfile implies ${lockfileManager}, which conflicts with expected ${pkg.expected}.`);
1180
+ }
1181
+ }
1182
+
1183
+ checks.push({
1184
+ id: 'package_manager_consistency',
1185
+ status,
1186
+ summary,
1187
+ details,
1188
+ memory_ids: packageManagerEntries.map(entry => entry.id),
1189
+ });
1190
+ }
1191
+
1192
+ const nodeEntries = promoted.filter(entry =>
1193
+ hasAnyNeedle(entry, ['node version', 'node mismatch', 'node environment', '.nvmrc', '.node-version', 'engines.node']),
1194
+ );
1195
+ if (nodeEntries.length > 0) {
1196
+ nodeEntries.forEach(entry => relatedMemoryIds.add(entry.id));
1197
+ const node = await detectRepoNodeVersion(projectDir);
1198
+ const details = [`Current Node runtime: ${node.current}`];
1199
+ let status: FailurePreflightCheckStatus = 'warn';
1200
+ let summary = 'Node runtime mismatch was seen before; verify runtime before executing.';
1201
+
1202
+ if (node.expected) {
1203
+ details.push(`Expected Node version: ${node.expected} (${node.source ?? 'detected'})`);
1204
+ if (node.matches) {
1205
+ status = 'pass';
1206
+ summary = `Current Node runtime matches expected version from ${node.source ?? 'repo config'}.`;
1207
+ } else {
1208
+ status = 'block';
1209
+ summary = `Current Node runtime does not match expected version from ${node.source ?? 'repo config'}.`;
1210
+ }
1211
+ } else {
1212
+ details.push('No explicit Node version file or package.json runtime hint detected.');
1213
+ }
1214
+
1215
+ checks.push({
1216
+ id: 'node_runtime_alignment',
1217
+ status,
1218
+ summary,
1219
+ details,
1220
+ memory_ids: nodeEntries.map(entry => entry.id),
1221
+ });
1222
+ }
1223
+
1224
+ const scriptEntries = promoted.filter(entry =>
1225
+ hasAnyNeedle(entry, ['missing script', 'script not found', 'npm run', 'pnpm run', 'yarn run', 'bun run', 'command entry']),
1226
+ );
1227
+ if (scriptEntries.length > 0) {
1228
+ scriptEntries.forEach(entry => relatedMemoryIds.add(entry.id));
1229
+ const scripts = await detectRepoScripts(projectDir);
1230
+ const expectedScripts = extractExpectedScriptNames(scriptEntries);
1231
+ const details: string[] = [];
1232
+ let status: FailurePreflightCheckStatus = 'warn';
1233
+ let summary = 'Command-entry failures were seen before; verify required scripts before execution.';
1234
+
1235
+ if (expectedScripts.length > 0) {
1236
+ const missingScripts = expectedScripts.filter(script => !(script in scripts.scripts));
1237
+ details.push(`Expected scripts from failure memory: ${expectedScripts.join(', ')}`);
1238
+ if (missingScripts.length > 0) {
1239
+ status = 'block';
1240
+ summary = `Missing required package.json scripts: ${missingScripts.join(', ')}.`;
1241
+ details.push(`Missing scripts: ${missingScripts.join(', ')}`);
1242
+ } else {
1243
+ status = 'pass';
1244
+ summary = `Previously missing scripts are present in ${scripts.source ?? 'package.json'}.`;
1245
+ }
1246
+ } else if (Object.keys(scripts.scripts).length > 0) {
1247
+ details.push(`Available scripts: ${Object.keys(scripts.scripts).sort().join(', ')}`);
1248
+ } else {
1249
+ details.push('No package.json scripts detected.');
1250
+ }
1251
+
1252
+ checks.push({
1253
+ id: 'script_entry_readiness',
1254
+ status,
1255
+ summary,
1256
+ details,
1257
+ memory_ids: scriptEntries.map(entry => entry.id),
1258
+ });
1259
+ }
1260
+
1261
+ const envEntries = promoted.filter(entry =>
1262
+ hasAnyNeedle(entry, ['env', 'environment variable', '.env', 'api key', 'missing configuration', 'missing secret']),
1263
+ );
1264
+ if (envEntries.length > 0) {
1265
+ envEntries.forEach(entry => relatedMemoryIds.add(entry.id));
1266
+ const envTemplate = await detectRepoEnvTemplate(projectDir);
1267
+ const expectedEnvVars = uniqueSorted([
1268
+ ...envTemplate.variable_names,
1269
+ ...extractExpectedEnvVars(envEntries),
1270
+ ]);
1271
+ const configured = new Set([
1272
+ ...envTemplate.configured_variables,
1273
+ ...Object.keys(process.env),
1274
+ ]);
1275
+ const details: string[] = [];
1276
+ let status: FailurePreflightCheckStatus = 'warn';
1277
+ let summary = 'Environment setup caused failures before; verify required variables before execution.';
1278
+
1279
+ if (envTemplate.template_path) {
1280
+ details.push(`Environment template detected: ${envTemplate.template_path}`);
1281
+ }
1282
+
1283
+ if (expectedEnvVars.length > 0) {
1284
+ const missingEnvVars = expectedEnvVars.filter(variable => !configured.has(variable));
1285
+ details.push(`Expected env vars: ${expectedEnvVars.join(', ')}`);
1286
+ if (missingEnvVars.length > 0) {
1287
+ summary = `Environment may be incomplete; missing vars: ${missingEnvVars.join(', ')}.`;
1288
+ details.push(`Missing env vars: ${missingEnvVars.join(', ')}`);
1289
+ } else {
1290
+ status = 'pass';
1291
+ summary = 'Previously missing environment variables are present.';
1292
+ }
1293
+ } else {
1294
+ details.push('No concrete env var names could be derived from failure memory.');
1295
+ }
1296
+
1297
+ checks.push({
1298
+ id: 'env_setup_readiness',
1299
+ status,
1300
+ summary,
1301
+ details,
1302
+ memory_ids: envEntries.map(entry => entry.id),
1303
+ });
1304
+ }
1305
+
1306
+ const blockers = checks.filter(check => check.status === 'block');
1307
+ const warnings = checks.filter(check => check.status === 'warn');
1308
+ const packageManagerCheck = checks.find(check => check.id === 'package_manager_consistency');
1309
+ const nodeCheck = checks.find(check => check.id === 'node_runtime_alignment');
1310
+
1311
+ return {
1312
+ checks,
1313
+ blockers,
1314
+ warnings,
1315
+ passed: blockers.length === 0,
1316
+ recommended_package_manager: packageManagerCheck?.summary.startsWith('Use ')
1317
+ ? packageManagerCheck.summary.replace(/^Use\s+(\S+).*/, '$1')
1318
+ : undefined,
1319
+ expected_node_version: nodeCheck?.details.find(detail => detail.startsWith('Expected Node version: '))
1320
+ ?.replace(/^Expected Node version:\s*/, '')
1321
+ .replace(/\s+\(.+\)$/, ''),
1322
+ related_memory_ids: [...relatedMemoryIds],
1323
+ };
1324
+ }