bmalph 1.0.0 → 2.2.1

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 (455) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +398 -217
  3. package/bmad/bmm/agents/analyst.agent.yaml +43 -36
  4. package/bmad/bmm/agents/architect.agent.yaml +29 -28
  5. package/bmad/bmm/agents/dev.agent.yaml +38 -38
  6. package/bmad/bmm/agents/pm.agent.yaml +44 -46
  7. package/bmad/bmm/agents/qa.agent.yaml +58 -0
  8. package/bmad/bmm/agents/quick-flow-solo-dev.agent.yaml +32 -32
  9. package/bmad/bmm/agents/sm.agent.yaml +37 -36
  10. package/bmad/bmm/agents/tech-writer/tech-writer-sidecar/documentation-standards.md +223 -223
  11. package/bmad/bmm/agents/tech-writer/tech-writer.agent.yaml +46 -45
  12. package/bmad/bmm/agents/ux-designer.agent.yaml +27 -26
  13. package/bmad/bmm/data/project-context-template.md +26 -26
  14. package/bmad/bmm/module-help.csv +31 -31
  15. package/bmad/bmm/module.yaml +50 -44
  16. package/bmad/bmm/teams/default-party.csv +20 -21
  17. package/bmad/bmm/teams/team-fullstack.yaml +12 -12
  18. package/bmad/bmm/workflows/1-analysis/create-product-brief/product-brief.template.md +10 -10
  19. package/bmad/bmm/workflows/1-analysis/create-product-brief/steps/step-01-init.md +177 -177
  20. package/bmad/bmm/workflows/1-analysis/create-product-brief/steps/step-01b-continue.md +161 -161
  21. package/bmad/bmm/workflows/1-analysis/create-product-brief/steps/step-02-vision.md +199 -199
  22. package/bmad/bmm/workflows/1-analysis/create-product-brief/steps/step-03-users.md +202 -202
  23. package/bmad/bmm/workflows/1-analysis/create-product-brief/steps/step-04-metrics.md +205 -205
  24. package/bmad/bmm/workflows/1-analysis/create-product-brief/steps/step-05-scope.md +219 -219
  25. package/bmad/bmm/workflows/1-analysis/create-product-brief/steps/step-06-complete.md +162 -162
  26. package/bmad/bmm/workflows/1-analysis/create-product-brief/workflow.md +57 -58
  27. package/bmad/bmm/workflows/1-analysis/research/domain-steps/step-01-init.md +137 -137
  28. package/bmad/bmm/workflows/1-analysis/research/domain-steps/step-02-domain-analysis.md +229 -229
  29. package/bmad/bmm/workflows/1-analysis/research/domain-steps/step-03-competitive-landscape.md +238 -238
  30. package/bmad/bmm/workflows/1-analysis/research/domain-steps/step-04-regulatory-focus.md +206 -206
  31. package/bmad/bmm/workflows/1-analysis/research/domain-steps/step-05-technical-trends.md +234 -234
  32. package/bmad/bmm/workflows/1-analysis/research/domain-steps/step-06-research-synthesis.md +443 -443
  33. package/bmad/bmm/workflows/1-analysis/research/market-steps/step-01-init.md +182 -182
  34. package/bmad/bmm/workflows/1-analysis/research/market-steps/step-02-customer-behavior.md +237 -237
  35. package/bmad/bmm/workflows/1-analysis/research/market-steps/step-03-customer-pain-points.md +249 -249
  36. package/bmad/bmm/workflows/1-analysis/research/market-steps/step-04-customer-decisions.md +259 -259
  37. package/bmad/bmm/workflows/1-analysis/research/market-steps/step-05-competitive-analysis.md +177 -177
  38. package/bmad/bmm/workflows/1-analysis/research/market-steps/step-06-research-completion.md +475 -475
  39. package/bmad/bmm/workflows/1-analysis/research/research.template.md +29 -29
  40. package/bmad/bmm/workflows/1-analysis/research/technical-steps/step-01-init.md +137 -137
  41. package/bmad/bmm/workflows/1-analysis/research/technical-steps/step-02-technical-overview.md +239 -239
  42. package/bmad/bmm/workflows/1-analysis/research/technical-steps/step-03-integration-patterns.md +248 -248
  43. package/bmad/bmm/workflows/1-analysis/research/technical-steps/step-04-architectural-patterns.md +202 -202
  44. package/bmad/bmm/workflows/1-analysis/research/technical-steps/step-05-implementation-research.md +233 -239
  45. package/bmad/bmm/workflows/1-analysis/research/technical-steps/step-06-research-synthesis.md +486 -486
  46. package/bmad/bmm/workflows/1-analysis/research/workflow-domain-research.md +54 -0
  47. package/bmad/bmm/workflows/1-analysis/research/workflow-market-research.md +54 -0
  48. package/bmad/bmm/workflows/1-analysis/research/workflow-technical-research.md +54 -0
  49. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/data/domain-complexity.csv +14 -12
  50. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/data/prd-purpose.md +197 -197
  51. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/data/project-types.csv +10 -10
  52. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-c/step-01-init.md +191 -191
  53. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-c/step-01b-continue.md +153 -153
  54. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-c/step-02-discovery.md +224 -224
  55. package/bmad/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02b-vision.md +154 -0
  56. package/bmad/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02c-executive-summary.md +170 -0
  57. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-c/step-03-success.md +226 -226
  58. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-c/step-04-journeys.md +213 -213
  59. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-c/step-05-domain.md +207 -207
  60. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-c/step-06-innovation.md +226 -226
  61. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-c/step-07-project-type.md +237 -237
  62. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-c/step-08-scoping.md +228 -228
  63. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-c/step-09-functional.md +231 -231
  64. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-c/step-10-nonfunctional.md +242 -242
  65. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-c/step-11-polish.md +217 -217
  66. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-c/step-12-complete.md +124 -124
  67. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-e/step-e-01-discovery.md +247 -247
  68. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-e/step-e-01b-legacy-conversion.md +208 -208
  69. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-e/step-e-02-review.md +249 -249
  70. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-e/step-e-03-edit.md +253 -253
  71. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-e/step-e-04-complete.md +168 -168
  72. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-v/step-v-01-discovery.md +226 -218
  73. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-v/step-v-02-format-detection.md +191 -191
  74. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-v/step-v-02b-parity-check.md +209 -209
  75. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-v/step-v-03-density-validation.md +174 -174
  76. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-v/step-v-04-brief-coverage-validation.md +214 -214
  77. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-v/step-v-05-measurability-validation.md +228 -228
  78. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-v/step-v-06-traceability-validation.md +217 -217
  79. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-v/step-v-07-implementation-leakage-validation.md +205 -205
  80. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-v/step-v-08-domain-compliance-validation.md +243 -243
  81. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-v/step-v-09-project-type-validation.md +263 -263
  82. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-v/step-v-10-smart-validation.md +209 -209
  83. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-v/step-v-11-holistic-quality-validation.md +264 -264
  84. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-v/step-v-12-completeness-validation.md +242 -242
  85. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/steps-v/step-v-13-report-complete.md +231 -231
  86. package/bmad/bmm/workflows/2-plan-workflows/{prd → create-prd}/templates/prd-template.md +10 -10
  87. package/bmad/bmm/workflows/2-plan-workflows/create-prd/workflow-create-prd.md +63 -0
  88. package/bmad/bmm/workflows/2-plan-workflows/create-prd/workflow-edit-prd.md +65 -0
  89. package/bmad/bmm/workflows/2-plan-workflows/create-prd/workflow-validate-prd.md +63 -0
  90. package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-01-init.md +135 -135
  91. package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-01b-continue.md +127 -127
  92. package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-02-discovery.md +190 -190
  93. package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-03-core-experience.md +216 -216
  94. package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-04-emotional-response.md +219 -219
  95. package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-05-inspiration.md +234 -234
  96. package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-06-design-system.md +252 -252
  97. package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-07-defining-experience.md +254 -254
  98. package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-08-visual-foundation.md +224 -224
  99. package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-09-design-directions.md +224 -224
  100. package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-10-user-journeys.md +241 -241
  101. package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-11-component-strategy.md +248 -248
  102. package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-12-ux-patterns.md +237 -237
  103. package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-13-responsive-accessibility.md +264 -264
  104. package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-14-complete.md +171 -171
  105. package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/ux-design-template.md +13 -13
  106. package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md +42 -43
  107. package/bmad/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-01-document-discovery.md +184 -190
  108. package/bmad/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-02-prd-analysis.md +172 -178
  109. package/bmad/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-03-epic-coverage-validation.md +173 -179
  110. package/bmad/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-04-ux-alignment.md +133 -139
  111. package/bmad/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-05-epic-quality-review.md +245 -252
  112. package/bmad/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-06-final-assessment.md +129 -135
  113. package/bmad/bmm/workflows/3-solutioning/check-implementation-readiness/templates/readiness-report-template.md +4 -4
  114. package/bmad/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md +54 -55
  115. package/bmad/bmm/workflows/3-solutioning/create-architecture/architecture-decision-template.md +12 -12
  116. package/bmad/bmm/workflows/3-solutioning/create-architecture/data/domain-complexity.csv +12 -10
  117. package/bmad/bmm/workflows/3-solutioning/create-architecture/data/project-types.csv +6 -6
  118. package/bmad/bmm/workflows/3-solutioning/create-architecture/steps/step-01-init.md +153 -153
  119. package/bmad/bmm/workflows/3-solutioning/create-architecture/steps/step-01b-continue.md +164 -164
  120. package/bmad/bmm/workflows/3-solutioning/create-architecture/steps/step-02-context.md +224 -224
  121. package/bmad/bmm/workflows/3-solutioning/create-architecture/steps/step-03-starter.md +331 -331
  122. package/bmad/bmm/workflows/3-solutioning/create-architecture/steps/step-04-decisions.md +318 -318
  123. package/bmad/bmm/workflows/3-solutioning/create-architecture/steps/step-05-patterns.md +359 -359
  124. package/bmad/bmm/workflows/3-solutioning/create-architecture/steps/step-06-structure.md +379 -379
  125. package/bmad/bmm/workflows/3-solutioning/create-architecture/steps/step-07-validation.md +359 -359
  126. package/bmad/bmm/workflows/3-solutioning/create-architecture/steps/step-08-complete.md +76 -76
  127. package/bmad/bmm/workflows/3-solutioning/create-architecture/workflow.md +49 -50
  128. package/bmad/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-01-validate-prerequisites.md +259 -259
  129. package/bmad/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-02-design-epics.md +233 -233
  130. package/bmad/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-03-create-stories.md +272 -272
  131. package/bmad/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-04-final-validation.md +149 -149
  132. package/bmad/bmm/workflows/3-solutioning/create-epics-and-stories/templates/epics-template.md +57 -57
  133. package/bmad/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md +58 -59
  134. package/bmad/bmm/workflows/4-implementation/code-review/checklist.md +23 -23
  135. package/bmad/bmm/workflows/4-implementation/code-review/instructions.xml +226 -226
  136. package/bmad/bmm/workflows/4-implementation/code-review/workflow.yaml +44 -51
  137. package/bmad/bmm/workflows/4-implementation/correct-course/checklist.md +288 -288
  138. package/bmad/bmm/workflows/4-implementation/correct-course/instructions.md +207 -206
  139. package/bmad/bmm/workflows/4-implementation/correct-course/workflow.yaml +54 -60
  140. package/bmad/bmm/workflows/4-implementation/create-story/checklist.md +358 -358
  141. package/bmad/bmm/workflows/4-implementation/create-story/instructions.xml +346 -345
  142. package/bmad/bmm/workflows/4-implementation/create-story/template.md +49 -49
  143. package/bmad/bmm/workflows/4-implementation/create-story/workflow.yaml +53 -61
  144. package/bmad/bmm/workflows/4-implementation/dev-story/checklist.md +80 -80
  145. package/bmad/bmm/workflows/4-implementation/dev-story/instructions.xml +410 -410
  146. package/bmad/bmm/workflows/4-implementation/dev-story/workflow.yaml +21 -27
  147. package/bmad/bmm/workflows/4-implementation/retrospective/instructions.md +1444 -1443
  148. package/bmad/bmm/workflows/4-implementation/retrospective/workflow.yaml +53 -58
  149. package/bmad/bmm/workflows/4-implementation/sprint-planning/checklist.md +33 -33
  150. package/bmad/bmm/workflows/4-implementation/sprint-planning/instructions.md +226 -225
  151. package/bmad/bmm/workflows/4-implementation/sprint-planning/sprint-status-template.yaml +55 -55
  152. package/bmad/bmm/workflows/4-implementation/sprint-planning/workflow.yaml +47 -54
  153. package/bmad/bmm/workflows/4-implementation/sprint-status/instructions.md +230 -229
  154. package/bmad/bmm/workflows/4-implementation/sprint-status/workflow.yaml +25 -36
  155. package/bmad/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-01-mode-detection.md +174 -156
  156. package/bmad/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-02-context-gathering.md +118 -120
  157. package/bmad/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-03-execute.md +111 -113
  158. package/bmad/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-04-self-check.md +111 -113
  159. package/bmad/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-05-adversarial-review.md +104 -106
  160. package/bmad/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-06-resolve-findings.md +146 -140
  161. package/bmad/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md +50 -50
  162. package/bmad/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-01-understand.md +191 -189
  163. package/bmad/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-02-investigate.md +144 -144
  164. package/bmad/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-03-generate.md +127 -128
  165. package/bmad/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-04-review.md +200 -191
  166. package/bmad/bmm/workflows/bmad-quick-flow/quick-spec/tech-spec-template.md +74 -74
  167. package/bmad/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md +79 -79
  168. package/bmad/bmm/workflows/document-project/checklist.md +245 -245
  169. package/bmad/bmm/workflows/document-project/documentation-requirements.csv +12 -12
  170. package/bmad/bmm/workflows/document-project/instructions.md +130 -221
  171. package/bmad/bmm/workflows/document-project/templates/deep-dive-template.md +345 -345
  172. package/bmad/bmm/workflows/document-project/templates/index-template.md +169 -169
  173. package/bmad/bmm/workflows/document-project/templates/project-overview-template.md +103 -103
  174. package/bmad/bmm/workflows/document-project/templates/project-scan-report-schema.json +160 -160
  175. package/bmad/bmm/workflows/document-project/templates/source-tree-template.md +135 -135
  176. package/bmad/bmm/workflows/document-project/workflow.yaml +22 -30
  177. package/bmad/bmm/workflows/document-project/workflows/deep-dive-instructions.md +298 -298
  178. package/bmad/bmm/workflows/document-project/workflows/deep-dive.yaml +31 -31
  179. package/bmad/bmm/workflows/document-project/workflows/full-scan-instructions.md +1106 -1106
  180. package/bmad/bmm/workflows/document-project/workflows/full-scan.yaml +31 -31
  181. package/bmad/bmm/workflows/generate-project-context/project-context-template.md +21 -0
  182. package/bmad/bmm/workflows/generate-project-context/steps/step-01-discover.md +184 -0
  183. package/bmad/bmm/workflows/generate-project-context/steps/step-02-generate.md +318 -0
  184. package/bmad/bmm/workflows/generate-project-context/steps/step-03-complete.md +278 -0
  185. package/bmad/bmm/workflows/generate-project-context/workflow.md +49 -0
  186. package/bmad/bmm/workflows/qa/automate/checklist.md +33 -0
  187. package/bmad/bmm/workflows/qa/automate/instructions.md +110 -0
  188. package/bmad/bmm/workflows/qa/automate/workflow.yaml +44 -0
  189. package/bmad/core/agents/bmad-master.agent.yaml +30 -30
  190. package/bmad/core/module-help.csv +9 -11
  191. package/bmad/core/module.yaml +25 -25
  192. package/bmad/core/tasks/editorial-review-prose.xml +102 -91
  193. package/bmad/core/tasks/editorial-review-structure.xml +209 -198
  194. package/bmad/core/tasks/help.md +85 -0
  195. package/bmad/core/tasks/index-docs.xml +64 -64
  196. package/bmad/core/tasks/review-adversarial-general.xml +48 -48
  197. package/bmad/core/tasks/shard-doc.xml +107 -108
  198. package/bmad/core/tasks/workflow.xml +234 -234
  199. package/bmad/core/workflows/advanced-elicitation/methods.csv +51 -51
  200. package/bmad/core/workflows/advanced-elicitation/workflow.xml +116 -116
  201. package/bmad/core/workflows/brainstorming/brain-methods.csv +61 -61
  202. package/bmad/core/workflows/brainstorming/steps/step-01-session-setup.md +197 -197
  203. package/bmad/core/workflows/brainstorming/steps/step-01b-continue.md +122 -122
  204. package/bmad/core/workflows/brainstorming/steps/step-02a-user-selected.md +225 -225
  205. package/bmad/core/workflows/brainstorming/steps/step-02b-ai-recommended.md +237 -237
  206. package/bmad/core/workflows/brainstorming/steps/step-02c-random-selection.md +209 -209
  207. package/bmad/core/workflows/brainstorming/steps/step-02d-progressive-flow.md +264 -264
  208. package/bmad/core/workflows/brainstorming/steps/step-03-technique-execution.md +399 -399
  209. package/bmad/core/workflows/brainstorming/steps/step-04-idea-organization.md +303 -303
  210. package/bmad/core/workflows/brainstorming/template.md +15 -15
  211. package/bmad/core/workflows/brainstorming/workflow.md +58 -58
  212. package/bmad/core/workflows/party-mode/steps/step-01-agent-loading.md +138 -138
  213. package/bmad/core/workflows/party-mode/steps/step-02-discussion-orchestration.md +187 -187
  214. package/bmad/core/workflows/party-mode/steps/step-03-graceful-exit.md +168 -157
  215. package/bmad/core/workflows/party-mode/workflow.md +194 -194
  216. package/bundled-versions.json +3 -0
  217. package/dist/cli.js +61 -6
  218. package/dist/commands/check-updates.d.ts +5 -0
  219. package/dist/commands/check-updates.js +63 -0
  220. package/dist/commands/doctor.d.ts +39 -1
  221. package/dist/commands/doctor.js +348 -79
  222. package/dist/commands/init.d.ts +2 -0
  223. package/dist/commands/init.js +41 -15
  224. package/dist/commands/status.d.ts +7 -1
  225. package/dist/commands/status.js +111 -42
  226. package/dist/commands/upgrade.d.ts +7 -1
  227. package/dist/commands/upgrade.js +43 -12
  228. package/dist/installer.d.ts +19 -2
  229. package/dist/installer.js +305 -66
  230. package/dist/transition/artifacts.d.ts +2 -0
  231. package/dist/transition/artifacts.js +46 -0
  232. package/dist/transition/context.d.ts +19 -0
  233. package/dist/transition/context.js +261 -0
  234. package/dist/transition/fix-plan.d.ts +15 -0
  235. package/dist/transition/fix-plan.js +94 -0
  236. package/dist/transition/index.d.ts +9 -0
  237. package/dist/transition/index.js +16 -0
  238. package/dist/transition/orchestration.d.ts +2 -0
  239. package/dist/transition/orchestration.js +243 -0
  240. package/dist/transition/specs-changelog.d.ts +3 -0
  241. package/dist/transition/specs-changelog.js +75 -0
  242. package/dist/transition/specs-index.d.ts +22 -0
  243. package/dist/transition/specs-index.js +157 -0
  244. package/dist/transition/story-parsing.d.ts +7 -0
  245. package/dist/transition/story-parsing.js +124 -0
  246. package/dist/transition/tech-stack.d.ts +3 -0
  247. package/dist/transition/tech-stack.js +79 -0
  248. package/dist/transition/types.d.ts +60 -0
  249. package/dist/transition/types.js +1 -0
  250. package/dist/utils/config.d.ts +4 -0
  251. package/dist/utils/config.js +14 -4
  252. package/dist/utils/constants.d.ts +70 -0
  253. package/dist/utils/constants.js +97 -0
  254. package/dist/utils/dryrun.d.ts +7 -0
  255. package/dist/utils/dryrun.js +48 -0
  256. package/dist/utils/errors.d.ts +63 -0
  257. package/dist/utils/errors.js +86 -0
  258. package/dist/utils/file-system.d.ts +24 -0
  259. package/dist/utils/file-system.js +99 -0
  260. package/dist/utils/github.d.ts +83 -0
  261. package/dist/utils/github.js +230 -0
  262. package/dist/utils/json.js +3 -3
  263. package/dist/utils/logger.d.ts +6 -0
  264. package/dist/utils/logger.js +27 -0
  265. package/dist/utils/state.d.ts +4 -7
  266. package/dist/utils/state.js +147 -26
  267. package/dist/utils/validate.d.ts +40 -0
  268. package/dist/utils/validate.js +175 -1
  269. package/package.json +75 -59
  270. package/ralph/RALPH-REFERENCE.md +412 -0
  271. package/ralph/lib/circuit_breaker.sh +463 -330
  272. package/ralph/lib/date_utils.sh +104 -53
  273. package/ralph/lib/enable_core.sh +815 -0
  274. package/ralph/lib/response_analyzer.sh +884 -768
  275. package/ralph/lib/task_sources.sh +577 -0
  276. package/ralph/lib/timeout_utils.sh +145 -145
  277. package/ralph/lib/wizard_utils.sh +547 -0
  278. package/ralph/ralph_import.sh +636 -0
  279. package/ralph/ralph_loop.sh +1793 -1391
  280. package/ralph/ralph_monitor.sh +125 -0
  281. package/ralph/templates/AGENT.md +158 -158
  282. package/ralph/templates/PROMPT.md +285 -292
  283. package/ralph/templates/fix_plan.md +27 -27
  284. package/ralph/templates/ralphrc.template +102 -0
  285. package/ralph/templates/specs/.gitkeep +1 -1
  286. package/slash-commands/advanced-elicitation.md +1 -1
  287. package/slash-commands/adversarial-review.md +1 -1
  288. package/slash-commands/analyst.md +1 -1
  289. package/slash-commands/architect.md +1 -1
  290. package/slash-commands/bmad-help.md +1 -1
  291. package/slash-commands/bmalph-implement.md +152 -152
  292. package/slash-commands/brainstorm-project.md +1 -1
  293. package/slash-commands/brainstorming.md +1 -1
  294. package/slash-commands/correct-course.md +1 -1
  295. package/slash-commands/create-architecture.md +1 -1
  296. package/slash-commands/create-brief.md +1 -1
  297. package/slash-commands/create-epics-stories.md +1 -1
  298. package/slash-commands/create-prd.md +1 -1
  299. package/slash-commands/create-story.md +1 -1
  300. package/slash-commands/create-ux.md +1 -1
  301. package/slash-commands/dev.md +1 -1
  302. package/slash-commands/document-project.md +1 -1
  303. package/slash-commands/domain-research.md +1 -1
  304. package/slash-commands/editorial-prose.md +1 -1
  305. package/slash-commands/editorial-structure.md +1 -1
  306. package/slash-commands/execute-workflow.md +1 -1
  307. package/slash-commands/generate-project-context.md +1 -0
  308. package/slash-commands/implementation-readiness.md +1 -1
  309. package/slash-commands/index-docs.md +1 -1
  310. package/slash-commands/market-research.md +1 -1
  311. package/slash-commands/party-mode.md +1 -1
  312. package/slash-commands/pm.md +1 -1
  313. package/slash-commands/qa-automate.md +1 -0
  314. package/slash-commands/qa.md +1 -0
  315. package/slash-commands/quick-dev.md +1 -1
  316. package/slash-commands/quick-flow-solo-dev.md +1 -1
  317. package/slash-commands/retrospective.md +1 -1
  318. package/slash-commands/shard-doc.md +1 -1
  319. package/slash-commands/sm.md +1 -1
  320. package/slash-commands/sprint-planning.md +1 -1
  321. package/slash-commands/sprint-status.md +1 -1
  322. package/slash-commands/tech-spec.md +1 -1
  323. package/slash-commands/tech-writer.md +1 -0
  324. package/slash-commands/technical-research.md +1 -1
  325. package/slash-commands/ux-designer.md +1 -1
  326. package/slash-commands/validate-architecture.md +1 -1
  327. package/slash-commands/validate-brief.md +1 -1
  328. package/slash-commands/validate-epics-stories.md +1 -1
  329. package/slash-commands/validate-prd.md +1 -1
  330. package/slash-commands/validate-story.md +1 -1
  331. package/slash-commands/validate-ux.md +1 -1
  332. package/bmad/bmm/agents/tea.agent.yaml +0 -63
  333. package/bmad/bmm/sub-modules/claude-code/config.yaml +0 -4
  334. package/bmad/bmm/sub-modules/claude-code/injections.yaml +0 -242
  335. package/bmad/bmm/sub-modules/claude-code/readme.md +0 -87
  336. package/bmad/bmm/testarch/knowledge/adr-quality-readiness-checklist.md +0 -350
  337. package/bmad/bmm/testarch/knowledge/api-request.md +0 -442
  338. package/bmad/bmm/testarch/knowledge/api-testing-patterns.md +0 -843
  339. package/bmad/bmm/testarch/knowledge/auth-session.md +0 -552
  340. package/bmad/bmm/testarch/knowledge/burn-in.md +0 -273
  341. package/bmad/bmm/testarch/knowledge/ci-burn-in.md +0 -675
  342. package/bmad/bmm/testarch/knowledge/component-tdd.md +0 -486
  343. package/bmad/bmm/testarch/knowledge/contract-testing.md +0 -957
  344. package/bmad/bmm/testarch/knowledge/data-factories.md +0 -500
  345. package/bmad/bmm/testarch/knowledge/email-auth.md +0 -721
  346. package/bmad/bmm/testarch/knowledge/error-handling.md +0 -725
  347. package/bmad/bmm/testarch/knowledge/feature-flags.md +0 -750
  348. package/bmad/bmm/testarch/knowledge/file-utils.md +0 -463
  349. package/bmad/bmm/testarch/knowledge/fixture-architecture.md +0 -401
  350. package/bmad/bmm/testarch/knowledge/fixtures-composition.md +0 -382
  351. package/bmad/bmm/testarch/knowledge/intercept-network-call.md +0 -430
  352. package/bmad/bmm/testarch/knowledge/log.md +0 -429
  353. package/bmad/bmm/testarch/knowledge/network-error-monitor.md +0 -405
  354. package/bmad/bmm/testarch/knowledge/network-first.md +0 -486
  355. package/bmad/bmm/testarch/knowledge/network-recorder.md +0 -527
  356. package/bmad/bmm/testarch/knowledge/nfr-criteria.md +0 -670
  357. package/bmad/bmm/testarch/knowledge/overview.md +0 -286
  358. package/bmad/bmm/testarch/knowledge/playwright-config.md +0 -730
  359. package/bmad/bmm/testarch/knowledge/probability-impact.md +0 -601
  360. package/bmad/bmm/testarch/knowledge/recurse.md +0 -421
  361. package/bmad/bmm/testarch/knowledge/risk-governance.md +0 -615
  362. package/bmad/bmm/testarch/knowledge/selective-testing.md +0 -732
  363. package/bmad/bmm/testarch/knowledge/selector-resilience.md +0 -527
  364. package/bmad/bmm/testarch/knowledge/test-healing-patterns.md +0 -644
  365. package/bmad/bmm/testarch/knowledge/test-levels-framework.md +0 -473
  366. package/bmad/bmm/testarch/knowledge/test-priorities-matrix.md +0 -373
  367. package/bmad/bmm/testarch/knowledge/test-quality.md +0 -664
  368. package/bmad/bmm/testarch/knowledge/timing-debugging.md +0 -372
  369. package/bmad/bmm/testarch/knowledge/visual-debugging.md +0 -524
  370. package/bmad/bmm/testarch/tea-index.csv +0 -35
  371. package/bmad/bmm/workflows/1-analysis/research/market-steps/step-02-customer-insights.md +0 -200
  372. package/bmad/bmm/workflows/1-analysis/research/workflow.md +0 -173
  373. package/bmad/bmm/workflows/2-plan-workflows/prd/validation-report-prd-workflow.md +0 -433
  374. package/bmad/bmm/workflows/2-plan-workflows/prd/workflow.md +0 -150
  375. package/bmad/bmm/workflows/bmad-quick-flow/quick-dev/data/project-levels.yaml +0 -59
  376. package/bmad/bmm/workflows/excalidraw-diagrams/_shared/excalidraw-library.json +0 -90
  377. package/bmad/bmm/workflows/excalidraw-diagrams/_shared/excalidraw-templates.yaml +0 -127
  378. package/bmad/bmm/workflows/excalidraw-diagrams/create-dataflow/checklist.md +0 -39
  379. package/bmad/bmm/workflows/excalidraw-diagrams/create-dataflow/instructions.md +0 -130
  380. package/bmad/bmm/workflows/excalidraw-diagrams/create-dataflow/workflow.yaml +0 -27
  381. package/bmad/bmm/workflows/excalidraw-diagrams/create-diagram/checklist.md +0 -43
  382. package/bmad/bmm/workflows/excalidraw-diagrams/create-diagram/instructions.md +0 -141
  383. package/bmad/bmm/workflows/excalidraw-diagrams/create-diagram/workflow.yaml +0 -27
  384. package/bmad/bmm/workflows/excalidraw-diagrams/create-flowchart/checklist.md +0 -49
  385. package/bmad/bmm/workflows/excalidraw-diagrams/create-flowchart/instructions.md +0 -241
  386. package/bmad/bmm/workflows/excalidraw-diagrams/create-flowchart/workflow.yaml +0 -27
  387. package/bmad/bmm/workflows/excalidraw-diagrams/create-wireframe/checklist.md +0 -38
  388. package/bmad/bmm/workflows/excalidraw-diagrams/create-wireframe/instructions.md +0 -133
  389. package/bmad/bmm/workflows/excalidraw-diagrams/create-wireframe/workflow.yaml +0 -27
  390. package/bmad/bmm/workflows/testarch/atdd/atdd-checklist-template.md +0 -363
  391. package/bmad/bmm/workflows/testarch/atdd/checklist.md +0 -374
  392. package/bmad/bmm/workflows/testarch/atdd/instructions.md +0 -806
  393. package/bmad/bmm/workflows/testarch/atdd/workflow.yaml +0 -47
  394. package/bmad/bmm/workflows/testarch/automate/checklist.md +0 -582
  395. package/bmad/bmm/workflows/testarch/automate/instructions.md +0 -1324
  396. package/bmad/bmm/workflows/testarch/automate/workflow.yaml +0 -54
  397. package/bmad/bmm/workflows/testarch/ci/checklist.md +0 -247
  398. package/bmad/bmm/workflows/testarch/ci/github-actions-template.yaml +0 -198
  399. package/bmad/bmm/workflows/testarch/ci/gitlab-ci-template.yaml +0 -149
  400. package/bmad/bmm/workflows/testarch/ci/instructions.md +0 -536
  401. package/bmad/bmm/workflows/testarch/ci/workflow.yaml +0 -47
  402. package/bmad/bmm/workflows/testarch/framework/checklist.md +0 -320
  403. package/bmad/bmm/workflows/testarch/framework/instructions.md +0 -481
  404. package/bmad/bmm/workflows/testarch/framework/workflow.yaml +0 -49
  405. package/bmad/bmm/workflows/testarch/nfr-assess/checklist.md +0 -407
  406. package/bmad/bmm/workflows/testarch/nfr-assess/instructions.md +0 -726
  407. package/bmad/bmm/workflows/testarch/nfr-assess/nfr-report-template.md +0 -461
  408. package/bmad/bmm/workflows/testarch/nfr-assess/workflow.yaml +0 -49
  409. package/bmad/bmm/workflows/testarch/test-design/checklist.md +0 -407
  410. package/bmad/bmm/workflows/testarch/test-design/instructions.md +0 -1158
  411. package/bmad/bmm/workflows/testarch/test-design/test-design-architecture-template.md +0 -213
  412. package/bmad/bmm/workflows/testarch/test-design/test-design-qa-template.md +0 -286
  413. package/bmad/bmm/workflows/testarch/test-design/test-design-template.md +0 -294
  414. package/bmad/bmm/workflows/testarch/test-design/workflow.yaml +0 -71
  415. package/bmad/bmm/workflows/testarch/test-review/checklist.md +0 -472
  416. package/bmad/bmm/workflows/testarch/test-review/instructions.md +0 -628
  417. package/bmad/bmm/workflows/testarch/test-review/test-review-template.md +0 -390
  418. package/bmad/bmm/workflows/testarch/test-review/workflow.yaml +0 -48
  419. package/bmad/bmm/workflows/testarch/trace/checklist.md +0 -642
  420. package/bmad/bmm/workflows/testarch/trace/instructions.md +0 -1030
  421. package/bmad/bmm/workflows/testarch/trace/trace-template.md +0 -675
  422. package/bmad/bmm/workflows/testarch/trace/workflow.yaml +0 -57
  423. package/bmad/core/resources/excalidraw/README.md +0 -160
  424. package/bmad/core/resources/excalidraw/excalidraw-helpers.md +0 -127
  425. package/bmad/core/resources/excalidraw/library-loader.md +0 -50
  426. package/bmad/core/resources/excalidraw/validate-json-instructions.md +0 -79
  427. package/bmad/core/tasks/bmad-help.md +0 -62
  428. package/dist/commands/guide.d.ts +0 -1
  429. package/dist/commands/guide.js +0 -19
  430. package/dist/commands/implement.d.ts +0 -1
  431. package/dist/commands/implement.js +0 -83
  432. package/dist/commands/plan.d.ts +0 -5
  433. package/dist/commands/plan.js +0 -44
  434. package/dist/commands/reset.d.ts +0 -5
  435. package/dist/commands/reset.js +0 -35
  436. package/dist/commands/resume.d.ts +0 -1
  437. package/dist/commands/resume.js +0 -44
  438. package/dist/commands/start.d.ts +0 -5
  439. package/dist/commands/start.js +0 -54
  440. package/dist/transition.d.ts +0 -52
  441. package/dist/transition.js +0 -656
  442. package/slash-commands/atdd.md +0 -1
  443. package/slash-commands/continuous-integration.md +0 -1
  444. package/slash-commands/create-dataflow.md +0 -1
  445. package/slash-commands/create-diagram.md +0 -1
  446. package/slash-commands/create-flowchart.md +0 -1
  447. package/slash-commands/create-wireframe.md +0 -1
  448. package/slash-commands/nfr-assess.md +0 -1
  449. package/slash-commands/tea.md +0 -1
  450. package/slash-commands/test-automate.md +0 -1
  451. package/slash-commands/test-design.md +0 -1
  452. package/slash-commands/test-framework.md +0 -1
  453. package/slash-commands/test-review.md +0 -1
  454. package/slash-commands/test-trace.md +0 -1
  455. package/slash-commands/validate-test-design.md +0 -1
@@ -1,768 +1,884 @@
1
- #!/bin/bash
2
- # Response Analyzer Component for Ralph
3
- # Analyzes Claude Code output to detect completion signals, test-only loops, and progress
4
-
5
- # Source date utilities for cross-platform compatibility
6
- source "$(dirname "${BASH_SOURCE[0]}")/date_utils.sh"
7
-
8
- # Response Analysis Functions
9
- # Based on expert recommendations from Martin Fowler, Michael Nygard, Sam Newman
10
-
11
- # Colors for output
12
- RED='\033[0;31m'
13
- GREEN='\033[0;32m'
14
- YELLOW='\033[1;33m'
15
- BLUE='\033[0;34m'
16
- NC='\033[0m'
17
-
18
- # Use RALPH_DIR if set by main script, otherwise default to .ralph
19
- RALPH_DIR="${RALPH_DIR:-.ralph}"
20
-
21
- # Analysis configuration
22
- COMPLETION_KEYWORDS=("done" "complete" "finished" "all tasks complete" "project complete" "ready for review")
23
- TEST_ONLY_PATTERNS=("npm test" "bats" "pytest" "jest" "cargo test" "go test" "running tests")
24
- NO_WORK_PATTERNS=("nothing to do" "no changes" "already implemented" "up to date")
25
-
26
- # =============================================================================
27
- # JSON OUTPUT FORMAT DETECTION AND PARSING
28
- # =============================================================================
29
-
30
- # Detect output format (json or text)
31
- # Returns: "json" if valid JSON, "text" otherwise
32
- detect_output_format() {
33
- local output_file=$1
34
-
35
- if [[ ! -f "$output_file" ]] || [[ ! -s "$output_file" ]]; then
36
- echo "text"
37
- return
38
- fi
39
-
40
- # Check if file starts with { or [ (JSON indicators)
41
- local first_char=$(head -c 1 "$output_file" 2>/dev/null | tr -d '[:space:]')
42
-
43
- if [[ "$first_char" != "{" && "$first_char" != "[" ]]; then
44
- echo "text"
45
- return
46
- fi
47
-
48
- # Validate as JSON using jq
49
- if jq empty "$output_file" 2>/dev/null; then
50
- echo "json"
51
- else
52
- echo "text"
53
- fi
54
- }
55
-
56
- # Parse JSON response and extract structured fields
57
- # Creates .ralph/.json_parse_result with normalized analysis data
58
- # Supports THREE JSON formats:
59
- # 1. Flat format: { status, exit_signal, work_type, files_modified, ... }
60
- # 2. Claude CLI object format: { result, sessionId, metadata: { files_changed, has_errors, completion_status, ... } }
61
- # 3. Claude CLI array format: [ {type: "system", ...}, {type: "assistant", ...}, {type: "result", ...} ]
62
- parse_json_response() {
63
- local output_file=$1
64
- local result_file="${2:-$RALPH_DIR/.json_parse_result}"
65
- local normalized_file=""
66
-
67
- if [[ ! -f "$output_file" ]]; then
68
- echo "ERROR: Output file not found: $output_file" >&2
69
- return 1
70
- fi
71
-
72
- # Validate JSON first
73
- if ! jq empty "$output_file" 2>/dev/null; then
74
- echo "ERROR: Invalid JSON in output file" >&2
75
- return 1
76
- fi
77
-
78
- # Check if JSON is an array (Claude CLI array format)
79
- # Claude CLI outputs: [{type: "system", ...}, {type: "assistant", ...}, {type: "result", ...}]
80
- if jq -e 'type == "array"' "$output_file" >/dev/null 2>&1; then
81
- normalized_file=$(mktemp)
82
-
83
- # Extract the "result" type message from the array (usually the last entry)
84
- # This contains: result, session_id, is_error, duration_ms, etc.
85
- local result_obj=$(jq '[.[] | select(.type == "result")] | .[-1] // {}' "$output_file" 2>/dev/null)
86
-
87
- # Guard against empty result_obj if jq fails (review fix: Macroscope)
88
- [[ -z "$result_obj" ]] && result_obj="{}"
89
-
90
- # Extract session_id from init message as fallback
91
- local init_session_id=$(jq -r '.[] | select(.type == "system" and .subtype == "init") | .session_id // empty' "$output_file" 2>/dev/null | head -1)
92
-
93
- # Prioritize result object's own session_id, then fall back to init message (review fix: CodeRabbit)
94
- # This prevents session ID loss when arrays lack an init message with session_id
95
- local effective_session_id
96
- effective_session_id=$(echo "$result_obj" | jq -r '.sessionId // .session_id // empty' 2>/dev/null)
97
- if [[ -z "$effective_session_id" || "$effective_session_id" == "null" ]]; then
98
- effective_session_id="$init_session_id"
99
- fi
100
-
101
- # Build normalized object merging result with effective session_id
102
- if [[ -n "$effective_session_id" && "$effective_session_id" != "null" ]]; then
103
- echo "$result_obj" | jq --arg sid "$effective_session_id" '. + {sessionId: $sid} | del(.session_id)' > "$normalized_file"
104
- else
105
- echo "$result_obj" | jq 'del(.session_id)' > "$normalized_file"
106
- fi
107
-
108
- # Use normalized file for subsequent parsing
109
- output_file="$normalized_file"
110
- fi
111
-
112
- # Detect JSON format by checking for Claude CLI fields
113
- local has_result_field=$(jq -r 'has("result")' "$output_file" 2>/dev/null)
114
-
115
- # Extract fields - support both flat format and Claude CLI format
116
- # Priority: Claude CLI fields first, then flat format fields
117
-
118
- # Status: from flat format OR derived from metadata.completion_status
119
- local status=$(jq -r '.status // "UNKNOWN"' "$output_file" 2>/dev/null)
120
- local completion_status=$(jq -r '.metadata.completion_status // ""' "$output_file" 2>/dev/null)
121
- if [[ "$completion_status" == "complete" || "$completion_status" == "COMPLETE" ]]; then
122
- status="COMPLETE"
123
- fi
124
-
125
- # Exit signal: from flat format OR derived from completion_status
126
- local exit_signal=$(jq -r '.exit_signal // false' "$output_file" 2>/dev/null)
127
-
128
- # Bug #1 Fix: If exit_signal is still false, check for RALPH_STATUS block in .result field
129
- # Claude CLI JSON format embeds the RALPH_STATUS block within the .result text field
130
- if [[ "$exit_signal" == "false" && "$has_result_field" == "true" ]]; then
131
- local result_text=$(jq -r '.result // ""' "$output_file" 2>/dev/null)
132
- if [[ -n "$result_text" ]] && echo "$result_text" | grep -q -- "---RALPH_STATUS---"; then
133
- # Extract EXIT_SIGNAL value from RALPH_STATUS block within result text
134
- local embedded_exit_sig=$(echo "$result_text" | grep "EXIT_SIGNAL:" | cut -d: -f2 | xargs)
135
- if [[ "$embedded_exit_sig" == "true" ]]; then
136
- exit_signal="true"
137
- [[ "${VERBOSE_PROGRESS:-}" == "true" ]] && echo "DEBUG: Extracted EXIT_SIGNAL=true from .result RALPH_STATUS block" >&2
138
- fi
139
- # Also check STATUS field as fallback
140
- local embedded_status=$(echo "$result_text" | grep "STATUS:" | cut -d: -f2 | xargs)
141
- if [[ "$embedded_status" == "COMPLETE" && "$exit_signal" != "true" ]]; then
142
- # STATUS: COMPLETE without explicit EXIT_SIGNAL implies completion
143
- exit_signal="true"
144
- [[ "${VERBOSE_PROGRESS:-}" == "true" ]] && echo "DEBUG: Inferred EXIT_SIGNAL=true from .result STATUS=COMPLETE" >&2
145
- fi
146
- fi
147
- fi
148
-
149
- # Work type: from flat format
150
- local work_type=$(jq -r '.work_type // "UNKNOWN"' "$output_file" 2>/dev/null)
151
-
152
- # Files modified: from flat format OR from metadata.files_changed
153
- local files_modified=$(jq -r '.metadata.files_changed // .files_modified // 0' "$output_file" 2>/dev/null)
154
-
155
- # Error count: from flat format OR derived from metadata.has_errors
156
- # Note: When only has_errors=true is present (without explicit error_count),
157
- # we set error_count=1 as a minimum. This is defensive programming since
158
- # the stuck detection threshold is >5 errors, so 1 error won't trigger it.
159
- # Actual error count may be higher, but precise count isn't critical for our logic.
160
- local error_count=$(jq -r '.error_count // 0' "$output_file" 2>/dev/null)
161
- local has_errors=$(jq -r '.metadata.has_errors // false' "$output_file" 2>/dev/null)
162
- if [[ "$has_errors" == "true" && "$error_count" == "0" ]]; then
163
- error_count=1 # At least one error if has_errors is true
164
- fi
165
-
166
- # Summary: from flat format OR from result field (Claude CLI format)
167
- local summary=$(jq -r '.result // .summary // ""' "$output_file" 2>/dev/null)
168
-
169
- # Session ID: from Claude CLI format (sessionId) OR from metadata.session_id
170
- local session_id=$(jq -r '.sessionId // .metadata.session_id // ""' "$output_file" 2>/dev/null)
171
-
172
- # Loop number: from metadata
173
- local loop_number=$(jq -r '.metadata.loop_number // .loop_number // 0' "$output_file" 2>/dev/null)
174
-
175
- # Confidence: from flat format
176
- local confidence=$(jq -r '.confidence // 0' "$output_file" 2>/dev/null)
177
-
178
- # Progress indicators: from Claude CLI metadata (optional)
179
- local progress_count=$(jq -r '.metadata.progress_indicators | if . then length else 0 end' "$output_file" 2>/dev/null)
180
-
181
- # Normalize values
182
- # Convert exit_signal to boolean string
183
- if [[ "$exit_signal" == "true" || "$status" == "COMPLETE" || "$completion_status" == "complete" || "$completion_status" == "COMPLETE" ]]; then
184
- exit_signal="true"
185
- else
186
- exit_signal="false"
187
- fi
188
-
189
- # Determine is_test_only from work_type
190
- local is_test_only="false"
191
- if [[ "$work_type" == "TEST_ONLY" ]]; then
192
- is_test_only="true"
193
- fi
194
-
195
- # Determine is_stuck from error_count (threshold >5)
196
- local is_stuck="false"
197
- error_count=$((error_count + 0)) # Ensure integer
198
- if [[ $error_count -gt 5 ]]; then
199
- is_stuck="true"
200
- fi
201
-
202
- # Ensure files_modified is integer
203
- files_modified=$((files_modified + 0))
204
-
205
- # Ensure progress_count is integer
206
- progress_count=$((progress_count + 0))
207
-
208
- # Calculate has_completion_signal
209
- local has_completion_signal="false"
210
- if [[ "$status" == "COMPLETE" || "$exit_signal" == "true" ]]; then
211
- has_completion_signal="true"
212
- fi
213
-
214
- # Boost confidence based on structured data availability
215
- if [[ "$has_result_field" == "true" ]]; then
216
- confidence=$((confidence + 20)) # Structured response boost
217
- fi
218
- if [[ $progress_count -gt 0 ]]; then
219
- confidence=$((confidence + progress_count * 5)) # Progress indicators boost
220
- fi
221
-
222
- # Write normalized result using jq for safe JSON construction
223
- # String fields use --arg (auto-escapes), numeric/boolean use --argjson
224
- jq -n \
225
- --arg status "$status" \
226
- --argjson exit_signal "$exit_signal" \
227
- --argjson is_test_only "$is_test_only" \
228
- --argjson is_stuck "$is_stuck" \
229
- --argjson has_completion_signal "$has_completion_signal" \
230
- --argjson files_modified "$files_modified" \
231
- --argjson error_count "$error_count" \
232
- --arg summary "$summary" \
233
- --argjson loop_number "$loop_number" \
234
- --arg session_id "$session_id" \
235
- --argjson confidence "$confidence" \
236
- '{
237
- status: $status,
238
- exit_signal: $exit_signal,
239
- is_test_only: $is_test_only,
240
- is_stuck: $is_stuck,
241
- has_completion_signal: $has_completion_signal,
242
- files_modified: $files_modified,
243
- error_count: $error_count,
244
- summary: $summary,
245
- loop_number: $loop_number,
246
- session_id: $session_id,
247
- confidence: $confidence,
248
- metadata: {
249
- loop_number: $loop_number,
250
- session_id: $session_id
251
- }
252
- }' > "$result_file"
253
-
254
- # Cleanup temporary normalized file if created (for array format handling)
255
- if [[ -n "$normalized_file" && -f "$normalized_file" ]]; then
256
- rm -f "$normalized_file"
257
- fi
258
-
259
- return 0
260
- }
261
-
262
- # Analyze Claude Code response and extract signals
263
- analyze_response() {
264
- local output_file=$1
265
- local loop_number=$2
266
- local analysis_result_file=${3:-"$RALPH_DIR/.response_analysis"}
267
-
268
- # Initialize analysis result
269
- local has_completion_signal=false
270
- local is_test_only=false
271
- local is_stuck=false
272
- local has_progress=false
273
- local confidence_score=0
274
- local exit_signal=false
275
- local work_summary=""
276
- local files_modified=0
277
-
278
- # Read output file
279
- if [[ ! -f "$output_file" ]]; then
280
- echo "ERROR: Output file not found: $output_file"
281
- return 1
282
- fi
283
-
284
- local output_content=$(cat "$output_file")
285
- local output_length=${#output_content}
286
-
287
- # Detect output format and try JSON parsing first
288
- local output_format=$(detect_output_format "$output_file")
289
-
290
- if [[ "$output_format" == "json" ]]; then
291
- # Try JSON parsing
292
- if parse_json_response "$output_file" "$RALPH_DIR/.json_parse_result" 2>/dev/null; then
293
- # Extract values from JSON parse result
294
- has_completion_signal=$(jq -r '.has_completion_signal' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "false")
295
- exit_signal=$(jq -r '.exit_signal' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "false")
296
- is_test_only=$(jq -r '.is_test_only' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "false")
297
- is_stuck=$(jq -r '.is_stuck' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "false")
298
- work_summary=$(jq -r '.summary' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "")
299
- files_modified=$(jq -r '.files_modified' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "0")
300
- local json_confidence=$(jq -r '.confidence' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "0")
301
- local session_id=$(jq -r '.session_id' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "")
302
-
303
- # Persist session ID if present (for session continuity across loop iterations)
304
- if [[ -n "$session_id" && "$session_id" != "null" ]]; then
305
- store_session_id "$session_id"
306
- [[ "${VERBOSE_PROGRESS:-}" == "true" ]] && echo "DEBUG: Persisted session ID: $session_id" >&2
307
- fi
308
-
309
- # JSON parsing provides high confidence
310
- if [[ "$exit_signal" == "true" ]]; then
311
- confidence_score=100
312
- else
313
- confidence_score=$((json_confidence + 50))
314
- fi
315
-
316
- # Check for file changes via git (supplements JSON data)
317
- if command -v git &>/dev/null && git rev-parse --git-dir >/dev/null 2>&1; then
318
- local git_files=$(git diff --name-only 2>/dev/null | wc -l)
319
- if [[ $git_files -gt 0 ]]; then
320
- has_progress=true
321
- files_modified=$git_files
322
- fi
323
- fi
324
-
325
- # Write analysis results for JSON path using jq for safe construction
326
- jq -n \
327
- --argjson loop_number "$loop_number" \
328
- --arg timestamp "$(get_iso_timestamp)" \
329
- --arg output_file "$output_file" \
330
- --arg output_format "json" \
331
- --argjson has_completion_signal "$has_completion_signal" \
332
- --argjson is_test_only "$is_test_only" \
333
- --argjson is_stuck "$is_stuck" \
334
- --argjson has_progress "$has_progress" \
335
- --argjson files_modified "$files_modified" \
336
- --argjson confidence_score "$confidence_score" \
337
- --argjson exit_signal "$exit_signal" \
338
- --arg work_summary "$work_summary" \
339
- --argjson output_length "$output_length" \
340
- '{
341
- loop_number: $loop_number,
342
- timestamp: $timestamp,
343
- output_file: $output_file,
344
- output_format: $output_format,
345
- analysis: {
346
- has_completion_signal: $has_completion_signal,
347
- is_test_only: $is_test_only,
348
- is_stuck: $is_stuck,
349
- has_progress: $has_progress,
350
- files_modified: $files_modified,
351
- confidence_score: $confidence_score,
352
- exit_signal: $exit_signal,
353
- work_summary: $work_summary,
354
- output_length: $output_length
355
- }
356
- }' > "$analysis_result_file"
357
- rm -f "$RALPH_DIR/.json_parse_result"
358
- return 0
359
- fi
360
- # If JSON parsing failed, fall through to text parsing
361
- fi
362
-
363
- # Text parsing fallback (original logic)
364
-
365
- # Track whether an explicit EXIT_SIGNAL was found in RALPH_STATUS block
366
- # If explicit signal found, heuristics should NOT override Claude's intent
367
- local explicit_exit_signal_found=false
368
-
369
- # 1. Check for explicit structured output (if Claude follows schema)
370
- if grep -q -- "---RALPH_STATUS---" "$output_file"; then
371
- # Parse structured output
372
- local status=$(grep "STATUS:" "$output_file" | cut -d: -f2 | xargs)
373
- local exit_sig=$(grep "EXIT_SIGNAL:" "$output_file" | cut -d: -f2 | xargs)
374
-
375
- # If EXIT_SIGNAL is explicitly provided, respect it
376
- if [[ -n "$exit_sig" ]]; then
377
- explicit_exit_signal_found=true
378
- if [[ "$exit_sig" == "true" ]]; then
379
- has_completion_signal=true
380
- exit_signal=true
381
- confidence_score=100
382
- else
383
- # Explicit EXIT_SIGNAL: false - Claude says to continue
384
- exit_signal=false
385
- fi
386
- elif [[ "$status" == "COMPLETE" ]]; then
387
- # No explicit EXIT_SIGNAL but STATUS is COMPLETE
388
- has_completion_signal=true
389
- exit_signal=true
390
- confidence_score=100
391
- fi
392
- fi
393
-
394
- # 2. Detect completion keywords in natural language output
395
- for keyword in "${COMPLETION_KEYWORDS[@]}"; do
396
- if grep -qi "$keyword" "$output_file"; then
397
- has_completion_signal=true
398
- ((confidence_score+=10))
399
- break
400
- fi
401
- done
402
-
403
- # 3. Detect test-only loops
404
- local test_command_count=0
405
- local implementation_count=0
406
- local error_count=0
407
-
408
- test_command_count=$(grep -c -i "running tests\|npm test\|bats\|pytest\|jest" "$output_file" 2>/dev/null | head -1 || echo "0")
409
- implementation_count=$(grep -c -i "implementing\|creating\|writing\|adding\|function\|class" "$output_file" 2>/dev/null | head -1 || echo "0")
410
-
411
- # Strip whitespace and ensure it's a number
412
- test_command_count=$(echo "$test_command_count" | tr -d '[:space:]')
413
- implementation_count=$(echo "$implementation_count" | tr -d '[:space:]')
414
-
415
- # Convert to integers with default fallback
416
- test_command_count=${test_command_count:-0}
417
- implementation_count=${implementation_count:-0}
418
- test_command_count=$((test_command_count + 0))
419
- implementation_count=$((implementation_count + 0))
420
-
421
- if [[ $test_command_count -gt 0 ]] && [[ $implementation_count -eq 0 ]]; then
422
- is_test_only=true
423
- work_summary="Test execution only, no implementation"
424
- fi
425
-
426
- # 4. Detect stuck/error loops
427
- # Use two-stage filtering to avoid counting JSON field names as errors
428
- # Stage 1: Filter out JSON field patterns like "is_error": false
429
- # Stage 2: Count actual error messages in specific contexts
430
- # Pattern aligned with ralph_loop.sh to ensure consistent behavior
431
- error_count=$(grep -v '"[^"]*error[^"]*":' "$output_file" 2>/dev/null | \
432
- grep -cE '(^Error:|^ERROR:|^error:|\]: error|Link: error|Error occurred|failed with error|[Ee]xception|Fatal|FATAL)' \
433
- 2>/dev/null || echo "0")
434
- error_count=$(echo "$error_count" | tr -d '[:space:]')
435
- error_count=${error_count:-0}
436
- error_count=$((error_count + 0))
437
-
438
- if [[ $error_count -gt 5 ]]; then
439
- is_stuck=true
440
- fi
441
-
442
- # 5. Detect "nothing to do" patterns
443
- for pattern in "${NO_WORK_PATTERNS[@]}"; do
444
- if grep -qi "$pattern" "$output_file"; then
445
- has_completion_signal=true
446
- ((confidence_score+=15))
447
- work_summary="No work remaining"
448
- break
449
- fi
450
- done
451
-
452
- # 6. Check for file changes (git integration)
453
- if command -v git &>/dev/null && git rev-parse --git-dir >/dev/null 2>&1; then
454
- files_modified=$(git diff --name-only 2>/dev/null | wc -l)
455
- if [[ $files_modified -gt 0 ]]; then
456
- has_progress=true
457
- ((confidence_score+=20))
458
- fi
459
- fi
460
-
461
- # 7. Analyze output length trends (detect declining engagement)
462
- if [[ -f "$RALPH_DIR/.last_output_length" ]]; then
463
- local last_length=$(cat "$RALPH_DIR/.last_output_length")
464
- local length_ratio=$((output_length * 100 / last_length))
465
-
466
- if [[ $length_ratio -lt 50 ]]; then
467
- # Output is less than 50% of previous - possible completion
468
- ((confidence_score+=10))
469
- fi
470
- fi
471
- echo "$output_length" > "$RALPH_DIR/.last_output_length"
472
-
473
- # 8. Extract work summary from output
474
- if [[ -z "$work_summary" ]]; then
475
- # Try to find summary in output
476
- work_summary=$(grep -i "summary\|completed\|implemented" "$output_file" | head -1 | cut -c 1-100)
477
- if [[ -z "$work_summary" ]]; then
478
- work_summary="Output analyzed, no explicit summary found"
479
- fi
480
- fi
481
-
482
- # 9. Determine exit signal based on confidence (heuristic)
483
- # IMPORTANT: Only apply heuristics if no explicit EXIT_SIGNAL was found in RALPH_STATUS
484
- # Claude's explicit intent takes precedence over natural language pattern matching
485
- if [[ "$explicit_exit_signal_found" != "true" ]]; then
486
- if [[ $confidence_score -ge 40 || "$has_completion_signal" == "true" ]]; then
487
- exit_signal=true
488
- fi
489
- fi
490
-
491
- # Write analysis results to file (text parsing path) using jq for safe construction
492
- jq -n \
493
- --argjson loop_number "$loop_number" \
494
- --arg timestamp "$(get_iso_timestamp)" \
495
- --arg output_file "$output_file" \
496
- --arg output_format "text" \
497
- --argjson has_completion_signal "$has_completion_signal" \
498
- --argjson is_test_only "$is_test_only" \
499
- --argjson is_stuck "$is_stuck" \
500
- --argjson has_progress "$has_progress" \
501
- --argjson files_modified "$files_modified" \
502
- --argjson confidence_score "$confidence_score" \
503
- --argjson exit_signal "$exit_signal" \
504
- --arg work_summary "$work_summary" \
505
- --argjson output_length "$output_length" \
506
- '{
507
- loop_number: $loop_number,
508
- timestamp: $timestamp,
509
- output_file: $output_file,
510
- output_format: $output_format,
511
- analysis: {
512
- has_completion_signal: $has_completion_signal,
513
- is_test_only: $is_test_only,
514
- is_stuck: $is_stuck,
515
- has_progress: $has_progress,
516
- files_modified: $files_modified,
517
- confidence_score: $confidence_score,
518
- exit_signal: $exit_signal,
519
- work_summary: $work_summary,
520
- output_length: $output_length
521
- }
522
- }' > "$analysis_result_file"
523
-
524
- # Always return 0 (success) - callers should check the JSON result file
525
- # Returning non-zero would cause issues with set -e and test frameworks
526
- return 0
527
- }
528
-
529
- # Update exit signals file based on analysis
530
- update_exit_signals() {
531
- local analysis_file=${1:-"$RALPH_DIR/.response_analysis"}
532
- local exit_signals_file=${2:-"$RALPH_DIR/.exit_signals"}
533
-
534
- if [[ ! -f "$analysis_file" ]]; then
535
- echo "ERROR: Analysis file not found: $analysis_file"
536
- return 1
537
- fi
538
-
539
- # Read analysis results
540
- local is_test_only=$(jq -r '.analysis.is_test_only' "$analysis_file")
541
- local has_completion_signal=$(jq -r '.analysis.has_completion_signal' "$analysis_file")
542
- local loop_number=$(jq -r '.loop_number' "$analysis_file")
543
- local has_progress=$(jq -r '.analysis.has_progress' "$analysis_file")
544
-
545
- # Read current exit signals
546
- local signals=$(cat "$exit_signals_file" 2>/dev/null || echo '{"test_only_loops": [], "done_signals": [], "completion_indicators": []}')
547
-
548
- # Update test_only_loops array
549
- if [[ "$is_test_only" == "true" ]]; then
550
- signals=$(echo "$signals" | jq ".test_only_loops += [$loop_number]")
551
- else
552
- # Clear test_only_loops if we had implementation
553
- if [[ "$has_progress" == "true" ]]; then
554
- signals=$(echo "$signals" | jq '.test_only_loops = []')
555
- fi
556
- fi
557
-
558
- # Update done_signals array
559
- if [[ "$has_completion_signal" == "true" ]]; then
560
- signals=$(echo "$signals" | jq ".done_signals += [$loop_number]")
561
- fi
562
-
563
- # Update completion_indicators array (strong signals)
564
- local confidence=$(jq -r '.analysis.confidence_score' "$analysis_file")
565
- if [[ $confidence -ge 60 ]]; then
566
- signals=$(echo "$signals" | jq ".completion_indicators += [$loop_number]")
567
- fi
568
-
569
- # Keep only last 5 signals (rolling window)
570
- signals=$(echo "$signals" | jq '.test_only_loops = .test_only_loops[-5:]')
571
- signals=$(echo "$signals" | jq '.done_signals = .done_signals[-5:]')
572
- signals=$(echo "$signals" | jq '.completion_indicators = .completion_indicators[-5:]')
573
-
574
- # Write updated signals
575
- echo "$signals" > "$exit_signals_file"
576
-
577
- return 0
578
- }
579
-
580
- # Log analysis results in human-readable format
581
- log_analysis_summary() {
582
- local analysis_file=${1:-"$RALPH_DIR/.response_analysis"}
583
-
584
- if [[ ! -f "$analysis_file" ]]; then
585
- return 1
586
- fi
587
-
588
- local loop=$(jq -r '.loop_number' "$analysis_file")
589
- local exit_sig=$(jq -r '.analysis.exit_signal' "$analysis_file")
590
- local confidence=$(jq -r '.analysis.confidence_score' "$analysis_file")
591
- local test_only=$(jq -r '.analysis.is_test_only' "$analysis_file")
592
- local files_changed=$(jq -r '.analysis.files_modified' "$analysis_file")
593
- local summary=$(jq -r '.analysis.work_summary' "$analysis_file")
594
-
595
- echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${NC}"
596
- echo -e "${BLUE}║ Response Analysis - Loop #$loop ║${NC}"
597
- echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}"
598
- echo -e "${YELLOW}Exit Signal:${NC} $exit_sig"
599
- echo -e "${YELLOW}Confidence:${NC} $confidence%"
600
- echo -e "${YELLOW}Test Only:${NC} $test_only"
601
- echo -e "${YELLOW}Files Changed:${NC} $files_changed"
602
- echo -e "${YELLOW}Summary:${NC} $summary"
603
- echo ""
604
- }
605
-
606
- # Detect if Claude is stuck (repeating same errors)
607
- detect_stuck_loop() {
608
- local current_output=$1
609
- local history_dir=${2:-"$RALPH_DIR/logs"}
610
-
611
- # Get last 3 output files
612
- local recent_outputs=$(ls -t "$history_dir"/claude_output_*.log 2>/dev/null | head -3)
613
-
614
- if [[ -z "$recent_outputs" ]]; then
615
- return 1 # Not enough history
616
- fi
617
-
618
- # Extract key errors from current output using two-stage filtering
619
- # Stage 1: Filter out JSON field patterns to avoid false positives
620
- # Stage 2: Extract actual error messages
621
- local current_errors=$(grep -v '"[^"]*error[^"]*":' "$current_output" 2>/dev/null | \
622
- grep -E '(^Error:|^ERROR:|^error:|\]: error|Link: error|Error occurred|failed with error|[Ee]xception|Fatal|FATAL)' 2>/dev/null | \
623
- sort | uniq)
624
-
625
- if [[ -z "$current_errors" ]]; then
626
- return 1 # No errors
627
- fi
628
-
629
- # Check if same errors appear in all recent outputs
630
- # For multi-line errors, verify ALL error lines appear in ALL history files
631
- local all_files_match=true
632
- while IFS= read -r output_file; do
633
- local file_matches_all=true
634
- while IFS= read -r error_line; do
635
- # Use -F for literal fixed-string matching (not regex)
636
- if ! grep -qF "$error_line" "$output_file" 2>/dev/null; then
637
- file_matches_all=false
638
- break
639
- fi
640
- done <<< "$current_errors"
641
-
642
- if [[ "$file_matches_all" != "true" ]]; then
643
- all_files_match=false
644
- break
645
- fi
646
- done <<< "$recent_outputs"
647
-
648
- if [[ "$all_files_match" == "true" ]]; then
649
- return 0 # Stuck on same error(s)
650
- else
651
- return 1 # Making progress or different errors
652
- fi
653
- }
654
-
655
- # =============================================================================
656
- # SESSION MANAGEMENT FUNCTIONS
657
- # =============================================================================
658
-
659
- # Session file location - standardized across ralph_loop.sh and response_analyzer.sh
660
- SESSION_FILE="$RALPH_DIR/.claude_session_id"
661
- # Session expiration time in seconds (24 hours)
662
- SESSION_EXPIRATION_SECONDS=86400
663
-
664
- # Store session ID to file with timestamp
665
- # Usage: store_session_id "session-uuid-123"
666
- store_session_id() {
667
- local session_id=$1
668
-
669
- if [[ -z "$session_id" ]]; then
670
- return 1
671
- fi
672
-
673
- # Write session with timestamp using jq for safe JSON construction
674
- jq -n \
675
- --arg session_id "$session_id" \
676
- --arg timestamp "$(get_iso_timestamp)" \
677
- '{
678
- session_id: $session_id,
679
- timestamp: $timestamp
680
- }' > "$SESSION_FILE"
681
-
682
- return 0
683
- }
684
-
685
- # Get the last stored session ID
686
- # Returns: session ID string or empty if not found
687
- get_last_session_id() {
688
- if [[ ! -f "$SESSION_FILE" ]]; then
689
- echo ""
690
- return 0
691
- fi
692
-
693
- # Extract session_id from JSON file
694
- local session_id=$(jq -r '.session_id // ""' "$SESSION_FILE" 2>/dev/null)
695
- echo "$session_id"
696
- return 0
697
- }
698
-
699
- # Check if the stored session should be resumed
700
- # Returns: 0 (true) if session is valid and recent, 1 (false) otherwise
701
- should_resume_session() {
702
- if [[ ! -f "$SESSION_FILE" ]]; then
703
- echo "false"
704
- return 1
705
- fi
706
-
707
- # Get session timestamp
708
- local timestamp=$(jq -r '.timestamp // ""' "$SESSION_FILE" 2>/dev/null)
709
-
710
- if [[ -z "$timestamp" ]]; then
711
- echo "false"
712
- return 1
713
- fi
714
-
715
- # Calculate session age using date utilities
716
- local now=$(get_epoch_seconds)
717
- local session_time
718
-
719
- # Parse ISO timestamp to epoch - try multiple formats for cross-platform compatibility
720
- # Strip milliseconds if present (e.g., 2026-01-09T10:30:00.123+00:00 → 2026-01-09T10:30:00+00:00)
721
- local clean_timestamp="${timestamp}"
722
- if [[ "$timestamp" =~ \.[0-9]+[+-Z] ]]; then
723
- clean_timestamp=$(echo "$timestamp" | sed 's/\.[0-9]*\([+-Z]\)/\1/')
724
- fi
725
-
726
- if command -v gdate &>/dev/null; then
727
- # macOS with coreutils
728
- session_time=$(gdate -d "$clean_timestamp" +%s 2>/dev/null)
729
- elif date --version 2>&1 | grep -q GNU; then
730
- # GNU date (Linux)
731
- session_time=$(date -d "$clean_timestamp" +%s 2>/dev/null)
732
- else
733
- # BSD date (macOS without coreutils) - try parsing ISO format
734
- # Format: 2026-01-09T10:30:00+00:00 or 2026-01-09T10:30:00Z
735
- # Strip timezone suffix for BSD date parsing
736
- local date_only="${clean_timestamp%[+-Z]*}"
737
- session_time=$(date -j -f "%Y-%m-%dT%H:%M:%S" "$date_only" +%s 2>/dev/null)
738
- fi
739
-
740
- # If we couldn't parse the timestamp, consider session expired
741
- if [[ -z "$session_time" || ! "$session_time" =~ ^[0-9]+$ ]]; then
742
- echo "false"
743
- return 1
744
- fi
745
-
746
- # Calculate age in seconds
747
- local age=$((now - session_time))
748
-
749
- # Check if session is still valid (less than expiration time)
750
- if [[ $age -lt $SESSION_EXPIRATION_SECONDS ]]; then
751
- echo "true"
752
- return 0
753
- else
754
- echo "false"
755
- return 1
756
- fi
757
- }
758
-
759
- # Export functions for use in ralph_loop.sh
760
- export -f detect_output_format
761
- export -f parse_json_response
762
- export -f analyze_response
763
- export -f update_exit_signals
764
- export -f log_analysis_summary
765
- export -f detect_stuck_loop
766
- export -f store_session_id
767
- export -f get_last_session_id
768
- export -f should_resume_session
1
+ #!/bin/bash
2
+ # Response Analyzer Component for Ralph
3
+ # Analyzes Claude Code output to detect completion signals, test-only loops, and progress
4
+
5
+ # Source date utilities for cross-platform compatibility
6
+ source "$(dirname "${BASH_SOURCE[0]}")/date_utils.sh"
7
+
8
+ # Response Analysis Functions
9
+ # Based on expert recommendations from Martin Fowler, Michael Nygard, Sam Newman
10
+
11
+ # Colors for output
12
+ RED='\033[0;31m'
13
+ GREEN='\033[0;32m'
14
+ YELLOW='\033[1;33m'
15
+ BLUE='\033[0;34m'
16
+ NC='\033[0m'
17
+
18
+ # Use RALPH_DIR if set by main script, otherwise default to .ralph
19
+ RALPH_DIR="${RALPH_DIR:-.ralph}"
20
+
21
+ # Analysis configuration
22
+ COMPLETION_KEYWORDS=("done" "complete" "finished" "all tasks complete" "project complete" "ready for review")
23
+ TEST_ONLY_PATTERNS=("npm test" "bats" "pytest" "jest" "cargo test" "go test" "running tests")
24
+ NO_WORK_PATTERNS=("nothing to do" "no changes" "already implemented" "up to date")
25
+
26
+ # =============================================================================
27
+ # JSON OUTPUT FORMAT DETECTION AND PARSING
28
+ # =============================================================================
29
+
30
+ # Detect output format (json or text)
31
+ # Returns: "json" if valid JSON, "text" otherwise
32
+ detect_output_format() {
33
+ local output_file=$1
34
+
35
+ if [[ ! -f "$output_file" ]] || [[ ! -s "$output_file" ]]; then
36
+ echo "text"
37
+ return
38
+ fi
39
+
40
+ # Check if file starts with { or [ (JSON indicators)
41
+ local first_char=$(head -c 1 "$output_file" 2>/dev/null | tr -d '[:space:]')
42
+
43
+ if [[ "$first_char" != "{" && "$first_char" != "[" ]]; then
44
+ echo "text"
45
+ return
46
+ fi
47
+
48
+ # Validate as JSON using jq
49
+ if jq empty "$output_file" 2>/dev/null; then
50
+ echo "json"
51
+ else
52
+ echo "text"
53
+ fi
54
+ }
55
+
56
+ # Parse JSON response and extract structured fields
57
+ # Creates .ralph/.json_parse_result with normalized analysis data
58
+ # Supports THREE JSON formats:
59
+ # 1. Flat format: { status, exit_signal, work_type, files_modified, ... }
60
+ # 2. Claude CLI object format: { result, sessionId, metadata: { files_changed, has_errors, completion_status, ... } }
61
+ # 3. Claude CLI array format: [ {type: "system", ...}, {type: "assistant", ...}, {type: "result", ...} ]
62
+ parse_json_response() {
63
+ local output_file=$1
64
+ local result_file="${2:-$RALPH_DIR/.json_parse_result}"
65
+ local normalized_file=""
66
+
67
+ if [[ ! -f "$output_file" ]]; then
68
+ echo "ERROR: Output file not found: $output_file" >&2
69
+ return 1
70
+ fi
71
+
72
+ # Validate JSON first
73
+ if ! jq empty "$output_file" 2>/dev/null; then
74
+ echo "ERROR: Invalid JSON in output file" >&2
75
+ return 1
76
+ fi
77
+
78
+ # Check if JSON is an array (Claude CLI array format)
79
+ # Claude CLI outputs: [{type: "system", ...}, {type: "assistant", ...}, {type: "result", ...}]
80
+ if jq -e 'type == "array"' "$output_file" >/dev/null 2>&1; then
81
+ normalized_file=$(mktemp)
82
+
83
+ # Extract the "result" type message from the array (usually the last entry)
84
+ # This contains: result, session_id, is_error, duration_ms, etc.
85
+ local result_obj=$(jq '[.[] | select(.type == "result")] | .[-1] // {}' "$output_file" 2>/dev/null)
86
+
87
+ # Guard against empty result_obj if jq fails (review fix: Macroscope)
88
+ [[ -z "$result_obj" ]] && result_obj="{}"
89
+
90
+ # Extract session_id from init message as fallback
91
+ local init_session_id=$(jq -r '.[] | select(.type == "system" and .subtype == "init") | .session_id // empty' "$output_file" 2>/dev/null | head -1)
92
+
93
+ # Prioritize result object's own session_id, then fall back to init message (review fix: CodeRabbit)
94
+ # This prevents session ID loss when arrays lack an init message with session_id
95
+ local effective_session_id
96
+ effective_session_id=$(echo "$result_obj" | jq -r '.sessionId // .session_id // empty' 2>/dev/null)
97
+ if [[ -z "$effective_session_id" || "$effective_session_id" == "null" ]]; then
98
+ effective_session_id="$init_session_id"
99
+ fi
100
+
101
+ # Build normalized object merging result with effective session_id
102
+ if [[ -n "$effective_session_id" && "$effective_session_id" != "null" ]]; then
103
+ echo "$result_obj" | jq --arg sid "$effective_session_id" '. + {sessionId: $sid} | del(.session_id)' > "$normalized_file"
104
+ else
105
+ echo "$result_obj" | jq 'del(.session_id)' > "$normalized_file"
106
+ fi
107
+
108
+ # Use normalized file for subsequent parsing
109
+ output_file="$normalized_file"
110
+ fi
111
+
112
+ # Detect JSON format by checking for Claude CLI fields
113
+ local has_result_field=$(jq -r 'has("result")' "$output_file" 2>/dev/null)
114
+
115
+ # Extract fields - support both flat format and Claude CLI format
116
+ # Priority: Claude CLI fields first, then flat format fields
117
+
118
+ # Status: from flat format OR derived from metadata.completion_status
119
+ local status=$(jq -r '.status // "UNKNOWN"' "$output_file" 2>/dev/null)
120
+ local completion_status=$(jq -r '.metadata.completion_status // ""' "$output_file" 2>/dev/null)
121
+ if [[ "$completion_status" == "complete" || "$completion_status" == "COMPLETE" ]]; then
122
+ status="COMPLETE"
123
+ fi
124
+
125
+ # Exit signal: from flat format OR derived from completion_status
126
+ # Track whether EXIT_SIGNAL was explicitly provided (vs inferred from STATUS)
127
+ local exit_signal=$(jq -r '.exit_signal // false' "$output_file" 2>/dev/null)
128
+ local explicit_exit_signal_found=$(jq -r 'has("exit_signal")' "$output_file" 2>/dev/null)
129
+
130
+ # Bug #1 Fix: If exit_signal is still false, check for RALPH_STATUS block in .result field
131
+ # Claude CLI JSON format embeds the RALPH_STATUS block within the .result text field
132
+ if [[ "$exit_signal" == "false" && "$has_result_field" == "true" ]]; then
133
+ local result_text=$(jq -r '.result // ""' "$output_file" 2>/dev/null)
134
+ if [[ -n "$result_text" ]] && echo "$result_text" | grep -q -- "---RALPH_STATUS---"; then
135
+ # Extract EXIT_SIGNAL value from RALPH_STATUS block within result text
136
+ local embedded_exit_sig
137
+ embedded_exit_sig=$(echo "$result_text" | grep "EXIT_SIGNAL:" | cut -d: -f2 | xargs)
138
+ if [[ -n "$embedded_exit_sig" ]]; then
139
+ # Explicit EXIT_SIGNAL found in RALPH_STATUS block
140
+ explicit_exit_signal_found="true"
141
+ if [[ "$embedded_exit_sig" == "true" ]]; then
142
+ exit_signal="true"
143
+ [[ "${VERBOSE_PROGRESS:-}" == "true" ]] && echo "DEBUG: Extracted EXIT_SIGNAL=true from .result RALPH_STATUS block" >&2
144
+ else
145
+ exit_signal="false"
146
+ [[ "${VERBOSE_PROGRESS:-}" == "true" ]] && echo "DEBUG: Extracted EXIT_SIGNAL=false from .result RALPH_STATUS block (respecting explicit intent)" >&2
147
+ fi
148
+ fi
149
+ # Also check STATUS field as fallback ONLY when EXIT_SIGNAL was not specified
150
+ # This respects explicit EXIT_SIGNAL: false which means "task complete, continue working"
151
+ local embedded_status
152
+ embedded_status=$(echo "$result_text" | grep "STATUS:" | cut -d: -f2 | xargs)
153
+ if [[ "$embedded_status" == "COMPLETE" && "$explicit_exit_signal_found" != "true" ]]; then
154
+ # STATUS: COMPLETE without any EXIT_SIGNAL field implies completion
155
+ exit_signal="true"
156
+ [[ "${VERBOSE_PROGRESS:-}" == "true" ]] && echo "DEBUG: Inferred EXIT_SIGNAL=true from .result STATUS=COMPLETE (no explicit EXIT_SIGNAL found)" >&2
157
+ fi
158
+ fi
159
+ fi
160
+
161
+ # Work type: from flat format
162
+ local work_type=$(jq -r '.work_type // "UNKNOWN"' "$output_file" 2>/dev/null)
163
+
164
+ # Files modified: from flat format OR from metadata.files_changed
165
+ local files_modified=$(jq -r '.metadata.files_changed // .files_modified // 0' "$output_file" 2>/dev/null)
166
+
167
+ # Error count: from flat format OR derived from metadata.has_errors
168
+ # Note: When only has_errors=true is present (without explicit error_count),
169
+ # we set error_count=1 as a minimum. This is defensive programming since
170
+ # the stuck detection threshold is >5 errors, so 1 error won't trigger it.
171
+ # Actual error count may be higher, but precise count isn't critical for our logic.
172
+ local error_count=$(jq -r '.error_count // 0' "$output_file" 2>/dev/null)
173
+ local has_errors=$(jq -r '.metadata.has_errors // false' "$output_file" 2>/dev/null)
174
+ if [[ "$has_errors" == "true" && "$error_count" == "0" ]]; then
175
+ error_count=1 # At least one error if has_errors is true
176
+ fi
177
+
178
+ # Summary: from flat format OR from result field (Claude CLI format)
179
+ local summary=$(jq -r '.result // .summary // ""' "$output_file" 2>/dev/null)
180
+
181
+ # Session ID: from Claude CLI format (sessionId) OR from metadata.session_id
182
+ local session_id=$(jq -r '.sessionId // .metadata.session_id // ""' "$output_file" 2>/dev/null)
183
+
184
+ # Loop number: from metadata
185
+ local loop_number=$(jq -r '.metadata.loop_number // .loop_number // 0' "$output_file" 2>/dev/null)
186
+
187
+ # Confidence: from flat format
188
+ local confidence=$(jq -r '.confidence // 0' "$output_file" 2>/dev/null)
189
+
190
+ # Progress indicators: from Claude CLI metadata (optional)
191
+ local progress_count=$(jq -r '.metadata.progress_indicators | if . then length else 0 end' "$output_file" 2>/dev/null)
192
+
193
+ # Permission denials: from Claude Code output (Issue #101)
194
+ # When Claude Code is denied permission to run commands, it outputs a permission_denials array
195
+ local permission_denial_count=$(jq -r '.permission_denials | if . then length else 0 end' "$output_file" 2>/dev/null)
196
+ permission_denial_count=$((permission_denial_count + 0)) # Ensure integer
197
+
198
+ local has_permission_denials="false"
199
+ if [[ $permission_denial_count -gt 0 ]]; then
200
+ has_permission_denials="true"
201
+ fi
202
+
203
+ # Extract denied tool names and commands for logging/display
204
+ # Shows tool_name for non-Bash tools, and for Bash tools shows the command that was denied
205
+ # This handles both cases: AskUserQuestion denial shows "AskUserQuestion",
206
+ # while Bash denial shows "Bash(git commit -m ...)" with truncated command
207
+ local denied_commands_json="[]"
208
+ if [[ $permission_denial_count -gt 0 ]]; then
209
+ denied_commands_json=$(jq -r '[.permission_denials[] | if .tool_name == "Bash" then "Bash(\(.tool_input.command // "?" | split("\n")[0] | .[0:60]))" else .tool_name // "unknown" end]' "$output_file" 2>/dev/null || echo "[]")
210
+ fi
211
+
212
+ # Normalize values
213
+ # Convert exit_signal to boolean string
214
+ # Only infer from status/completion_status if no explicit EXIT_SIGNAL was provided
215
+ if [[ "$explicit_exit_signal_found" == "true" ]]; then
216
+ # Respect explicit EXIT_SIGNAL value (already set above)
217
+ [[ "$exit_signal" == "true" ]] && exit_signal="true" || exit_signal="false"
218
+ elif [[ "$exit_signal" == "true" || "$status" == "COMPLETE" || "$completion_status" == "complete" || "$completion_status" == "COMPLETE" ]]; then
219
+ exit_signal="true"
220
+ else
221
+ exit_signal="false"
222
+ fi
223
+
224
+ # Determine is_test_only from work_type
225
+ local is_test_only="false"
226
+ if [[ "$work_type" == "TEST_ONLY" ]]; then
227
+ is_test_only="true"
228
+ fi
229
+
230
+ # Determine is_stuck from error_count (threshold >5)
231
+ local is_stuck="false"
232
+ error_count=$((error_count + 0)) # Ensure integer
233
+ if [[ $error_count -gt 5 ]]; then
234
+ is_stuck="true"
235
+ fi
236
+
237
+ # Ensure files_modified is integer
238
+ files_modified=$((files_modified + 0))
239
+
240
+ # Ensure progress_count is integer
241
+ progress_count=$((progress_count + 0))
242
+
243
+ # Calculate has_completion_signal
244
+ local has_completion_signal="false"
245
+ if [[ "$status" == "COMPLETE" || "$exit_signal" == "true" ]]; then
246
+ has_completion_signal="true"
247
+ fi
248
+
249
+ # Boost confidence based on structured data availability
250
+ if [[ "$has_result_field" == "true" ]]; then
251
+ confidence=$((confidence + 20)) # Structured response boost
252
+ fi
253
+ if [[ $progress_count -gt 0 ]]; then
254
+ confidence=$((confidence + progress_count * 5)) # Progress indicators boost
255
+ fi
256
+
257
+ # Write normalized result using jq for safe JSON construction
258
+ # String fields use --arg (auto-escapes), numeric/boolean use --argjson
259
+ jq -n \
260
+ --arg status "$status" \
261
+ --argjson exit_signal "$exit_signal" \
262
+ --argjson is_test_only "$is_test_only" \
263
+ --argjson is_stuck "$is_stuck" \
264
+ --argjson has_completion_signal "$has_completion_signal" \
265
+ --argjson files_modified "$files_modified" \
266
+ --argjson error_count "$error_count" \
267
+ --arg summary "$summary" \
268
+ --argjson loop_number "$loop_number" \
269
+ --arg session_id "$session_id" \
270
+ --argjson confidence "$confidence" \
271
+ --argjson has_permission_denials "$has_permission_denials" \
272
+ --argjson permission_denial_count "$permission_denial_count" \
273
+ --argjson denied_commands "$denied_commands_json" \
274
+ '{
275
+ status: $status,
276
+ exit_signal: $exit_signal,
277
+ is_test_only: $is_test_only,
278
+ is_stuck: $is_stuck,
279
+ has_completion_signal: $has_completion_signal,
280
+ files_modified: $files_modified,
281
+ error_count: $error_count,
282
+ summary: $summary,
283
+ loop_number: $loop_number,
284
+ session_id: $session_id,
285
+ confidence: $confidence,
286
+ has_permission_denials: $has_permission_denials,
287
+ permission_denial_count: $permission_denial_count,
288
+ denied_commands: $denied_commands,
289
+ metadata: {
290
+ loop_number: $loop_number,
291
+ session_id: $session_id
292
+ }
293
+ }' > "$result_file"
294
+
295
+ # Cleanup temporary normalized file if created (for array format handling)
296
+ if [[ -n "$normalized_file" && -f "$normalized_file" ]]; then
297
+ rm -f "$normalized_file"
298
+ fi
299
+
300
+ return 0
301
+ }
302
+
303
+ # Analyze Claude Code response and extract signals
304
+ analyze_response() {
305
+ local output_file=$1
306
+ local loop_number=$2
307
+ local analysis_result_file=${3:-"$RALPH_DIR/.response_analysis"}
308
+
309
+ # Initialize analysis result
310
+ local has_completion_signal=false
311
+ local is_test_only=false
312
+ local is_stuck=false
313
+ local has_progress=false
314
+ local confidence_score=0
315
+ local exit_signal=false
316
+ local work_summary=""
317
+ local files_modified=0
318
+
319
+ # Read output file
320
+ if [[ ! -f "$output_file" ]]; then
321
+ echo "ERROR: Output file not found: $output_file"
322
+ return 1
323
+ fi
324
+
325
+ local output_content=$(cat "$output_file")
326
+ local output_length=${#output_content}
327
+
328
+ # Detect output format and try JSON parsing first
329
+ local output_format=$(detect_output_format "$output_file")
330
+
331
+ if [[ "$output_format" == "json" ]]; then
332
+ # Try JSON parsing
333
+ if parse_json_response "$output_file" "$RALPH_DIR/.json_parse_result" 2>/dev/null; then
334
+ # Extract values from JSON parse result
335
+ has_completion_signal=$(jq -r '.has_completion_signal' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "false")
336
+ exit_signal=$(jq -r '.exit_signal' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "false")
337
+ is_test_only=$(jq -r '.is_test_only' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "false")
338
+ is_stuck=$(jq -r '.is_stuck' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "false")
339
+ work_summary=$(jq -r '.summary' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "")
340
+ files_modified=$(jq -r '.files_modified' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "0")
341
+ local json_confidence=$(jq -r '.confidence' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "0")
342
+ local session_id=$(jq -r '.session_id' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "")
343
+
344
+ # Extract permission denial fields (Issue #101)
345
+ local has_permission_denials=$(jq -r '.has_permission_denials' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "false")
346
+ local permission_denial_count=$(jq -r '.permission_denial_count' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "0")
347
+ local denied_commands_json=$(jq -r '.denied_commands' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "[]")
348
+
349
+ # Persist session ID if present (for session continuity across loop iterations)
350
+ if [[ -n "$session_id" && "$session_id" != "null" ]]; then
351
+ store_session_id "$session_id"
352
+ [[ "${VERBOSE_PROGRESS:-}" == "true" ]] && echo "DEBUG: Persisted session ID: $session_id" >&2
353
+ fi
354
+
355
+ # JSON parsing provides high confidence
356
+ if [[ "$exit_signal" == "true" ]]; then
357
+ confidence_score=100
358
+ else
359
+ confidence_score=$((json_confidence + 50))
360
+ fi
361
+
362
+ # Check for file changes via git (supplements JSON data)
363
+ # Fix #141: Detect both uncommitted changes AND committed changes
364
+ if command -v git &>/dev/null && git rev-parse --git-dir >/dev/null 2>&1; then
365
+ local git_files=0
366
+ local loop_start_sha=""
367
+ local current_sha=""
368
+
369
+ if [[ -f "$RALPH_DIR/.loop_start_sha" ]]; then
370
+ loop_start_sha=$(cat "$RALPH_DIR/.loop_start_sha" 2>/dev/null || echo "")
371
+ fi
372
+ current_sha=$(git rev-parse HEAD 2>/dev/null || echo "")
373
+
374
+ # Check if commits were made (HEAD changed)
375
+ if [[ -n "$loop_start_sha" && -n "$current_sha" && "$loop_start_sha" != "$current_sha" ]]; then
376
+ # Commits were made - count union of committed files AND working tree changes
377
+ git_files=$(
378
+ {
379
+ git diff --name-only "$loop_start_sha" "$current_sha" 2>/dev/null
380
+ git diff --name-only HEAD 2>/dev/null # unstaged changes
381
+ git diff --name-only --cached 2>/dev/null # staged changes
382
+ } | sort -u | wc -l
383
+ )
384
+ else
385
+ # No commits - check for uncommitted changes (staged + unstaged)
386
+ git_files=$(
387
+ {
388
+ git diff --name-only 2>/dev/null # unstaged changes
389
+ git diff --name-only --cached 2>/dev/null # staged changes
390
+ } | sort -u | wc -l
391
+ )
392
+ fi
393
+
394
+ if [[ $git_files -gt 0 ]]; then
395
+ has_progress=true
396
+ files_modified=$git_files
397
+ fi
398
+ fi
399
+
400
+ # Write analysis results for JSON path using jq for safe construction
401
+ jq -n \
402
+ --argjson loop_number "$loop_number" \
403
+ --arg timestamp "$(get_iso_timestamp)" \
404
+ --arg output_file "$output_file" \
405
+ --arg output_format "json" \
406
+ --argjson has_completion_signal "$has_completion_signal" \
407
+ --argjson is_test_only "$is_test_only" \
408
+ --argjson is_stuck "$is_stuck" \
409
+ --argjson has_progress "$has_progress" \
410
+ --argjson files_modified "$files_modified" \
411
+ --argjson confidence_score "$confidence_score" \
412
+ --argjson exit_signal "$exit_signal" \
413
+ --arg work_summary "$work_summary" \
414
+ --argjson output_length "$output_length" \
415
+ --argjson has_permission_denials "$has_permission_denials" \
416
+ --argjson permission_denial_count "$permission_denial_count" \
417
+ --argjson denied_commands "$denied_commands_json" \
418
+ '{
419
+ loop_number: $loop_number,
420
+ timestamp: $timestamp,
421
+ output_file: $output_file,
422
+ output_format: $output_format,
423
+ analysis: {
424
+ has_completion_signal: $has_completion_signal,
425
+ is_test_only: $is_test_only,
426
+ is_stuck: $is_stuck,
427
+ has_progress: $has_progress,
428
+ files_modified: $files_modified,
429
+ confidence_score: $confidence_score,
430
+ exit_signal: $exit_signal,
431
+ work_summary: $work_summary,
432
+ output_length: $output_length,
433
+ has_permission_denials: $has_permission_denials,
434
+ permission_denial_count: $permission_denial_count,
435
+ denied_commands: $denied_commands
436
+ }
437
+ }' > "$analysis_result_file"
438
+ rm -f "$RALPH_DIR/.json_parse_result"
439
+ return 0
440
+ fi
441
+ # If JSON parsing failed, fall through to text parsing
442
+ fi
443
+
444
+ # Text parsing fallback (original logic)
445
+
446
+ # Track whether an explicit EXIT_SIGNAL was found in RALPH_STATUS block
447
+ # If explicit signal found, heuristics should NOT override Claude's intent
448
+ local explicit_exit_signal_found=false
449
+
450
+ # 1. Check for explicit structured output (if Claude follows schema)
451
+ if grep -q -- "---RALPH_STATUS---" "$output_file"; then
452
+ # Parse structured output
453
+ local status=$(grep "STATUS:" "$output_file" | cut -d: -f2 | xargs)
454
+ local exit_sig=$(grep "EXIT_SIGNAL:" "$output_file" | cut -d: -f2 | xargs)
455
+
456
+ # If EXIT_SIGNAL is explicitly provided, respect it
457
+ if [[ -n "$exit_sig" ]]; then
458
+ explicit_exit_signal_found=true
459
+ if [[ "$exit_sig" == "true" ]]; then
460
+ has_completion_signal=true
461
+ exit_signal=true
462
+ confidence_score=100
463
+ else
464
+ # Explicit EXIT_SIGNAL: false - Claude says to continue
465
+ exit_signal=false
466
+ fi
467
+ elif [[ "$status" == "COMPLETE" ]]; then
468
+ # No explicit EXIT_SIGNAL but STATUS is COMPLETE
469
+ has_completion_signal=true
470
+ exit_signal=true
471
+ confidence_score=100
472
+ fi
473
+ fi
474
+
475
+ # 2. Detect completion keywords in natural language output
476
+ for keyword in "${COMPLETION_KEYWORDS[@]}"; do
477
+ if grep -qi "$keyword" "$output_file"; then
478
+ has_completion_signal=true
479
+ ((confidence_score+=10))
480
+ break
481
+ fi
482
+ done
483
+
484
+ # 3. Detect test-only loops
485
+ local test_command_count=0
486
+ local implementation_count=0
487
+ local error_count=0
488
+
489
+ test_command_count=$(grep -c -i "running tests\|npm test\|bats\|pytest\|jest" "$output_file" 2>/dev/null | head -1 || echo "0")
490
+ implementation_count=$(grep -c -i "implementing\|creating\|writing\|adding\|function\|class" "$output_file" 2>/dev/null | head -1 || echo "0")
491
+
492
+ # Strip whitespace and ensure it's a number
493
+ test_command_count=$(echo "$test_command_count" | tr -d '[:space:]')
494
+ implementation_count=$(echo "$implementation_count" | tr -d '[:space:]')
495
+
496
+ # Convert to integers with default fallback
497
+ test_command_count=${test_command_count:-0}
498
+ implementation_count=${implementation_count:-0}
499
+ test_command_count=$((test_command_count + 0))
500
+ implementation_count=$((implementation_count + 0))
501
+
502
+ if [[ $test_command_count -gt 0 ]] && [[ $implementation_count -eq 0 ]]; then
503
+ is_test_only=true
504
+ work_summary="Test execution only, no implementation"
505
+ fi
506
+
507
+ # 4. Detect stuck/error loops
508
+ # Use two-stage filtering to avoid counting JSON field names as errors
509
+ # Stage 1: Filter out JSON field patterns like "is_error": false
510
+ # Stage 2: Count actual error messages in specific contexts
511
+ # Pattern aligned with ralph_loop.sh to ensure consistent behavior
512
+ error_count=$(grep -v '"[^"]*error[^"]*":' "$output_file" 2>/dev/null | \
513
+ grep -cE '(^Error:|^ERROR:|^error:|\]: error|Link: error|Error occurred|failed with error|[Ee]xception|Fatal|FATAL)' \
514
+ 2>/dev/null || echo "0")
515
+ error_count=$(echo "$error_count" | tr -d '[:space:]')
516
+ error_count=${error_count:-0}
517
+ error_count=$((error_count + 0))
518
+
519
+ if [[ $error_count -gt 5 ]]; then
520
+ is_stuck=true
521
+ fi
522
+
523
+ # 5. Detect "nothing to do" patterns
524
+ for pattern in "${NO_WORK_PATTERNS[@]}"; do
525
+ if grep -qi "$pattern" "$output_file"; then
526
+ has_completion_signal=true
527
+ ((confidence_score+=15))
528
+ work_summary="No work remaining"
529
+ break
530
+ fi
531
+ done
532
+
533
+ # 6. Check for file changes (git integration)
534
+ # Fix #141: Detect both uncommitted changes AND committed changes
535
+ if command -v git &>/dev/null && git rev-parse --git-dir >/dev/null 2>&1; then
536
+ local loop_start_sha=""
537
+ local current_sha=""
538
+
539
+ if [[ -f "$RALPH_DIR/.loop_start_sha" ]]; then
540
+ loop_start_sha=$(cat "$RALPH_DIR/.loop_start_sha" 2>/dev/null || echo "")
541
+ fi
542
+ current_sha=$(git rev-parse HEAD 2>/dev/null || echo "")
543
+
544
+ # Check if commits were made (HEAD changed)
545
+ if [[ -n "$loop_start_sha" && -n "$current_sha" && "$loop_start_sha" != "$current_sha" ]]; then
546
+ # Commits were made - count union of committed files AND working tree changes
547
+ files_modified=$(
548
+ {
549
+ git diff --name-only "$loop_start_sha" "$current_sha" 2>/dev/null
550
+ git diff --name-only HEAD 2>/dev/null # unstaged changes
551
+ git diff --name-only --cached 2>/dev/null # staged changes
552
+ } | sort -u | wc -l
553
+ )
554
+ else
555
+ # No commits - check for uncommitted changes (staged + unstaged)
556
+ files_modified=$(
557
+ {
558
+ git diff --name-only 2>/dev/null # unstaged changes
559
+ git diff --name-only --cached 2>/dev/null # staged changes
560
+ } | sort -u | wc -l
561
+ )
562
+ fi
563
+
564
+ if [[ $files_modified -gt 0 ]]; then
565
+ has_progress=true
566
+ ((confidence_score+=20))
567
+ fi
568
+ fi
569
+
570
+ # 7. Analyze output length trends (detect declining engagement)
571
+ if [[ -f "$RALPH_DIR/.last_output_length" ]]; then
572
+ local last_length=$(cat "$RALPH_DIR/.last_output_length")
573
+ local length_ratio=$((output_length * 100 / last_length))
574
+
575
+ if [[ $length_ratio -lt 50 ]]; then
576
+ # Output is less than 50% of previous - possible completion
577
+ ((confidence_score+=10))
578
+ fi
579
+ fi
580
+ echo "$output_length" > "$RALPH_DIR/.last_output_length"
581
+
582
+ # 8. Extract work summary from output
583
+ if [[ -z "$work_summary" ]]; then
584
+ # Try to find summary in output
585
+ work_summary=$(grep -i "summary\|completed\|implemented" "$output_file" | head -1 | cut -c 1-100)
586
+ if [[ -z "$work_summary" ]]; then
587
+ work_summary="Output analyzed, no explicit summary found"
588
+ fi
589
+ fi
590
+
591
+ # 9. Determine exit signal based on confidence (heuristic)
592
+ # IMPORTANT: Only apply heuristics if no explicit EXIT_SIGNAL was found in RALPH_STATUS
593
+ # Claude's explicit intent takes precedence over natural language pattern matching
594
+ if [[ "$explicit_exit_signal_found" != "true" ]]; then
595
+ if [[ $confidence_score -ge 40 || "$has_completion_signal" == "true" ]]; then
596
+ exit_signal=true
597
+ fi
598
+ fi
599
+
600
+ # Write analysis results to file (text parsing path) using jq for safe construction
601
+ # Note: Permission denial fields default to false/0 since text output doesn't include this data
602
+ jq -n \
603
+ --argjson loop_number "$loop_number" \
604
+ --arg timestamp "$(get_iso_timestamp)" \
605
+ --arg output_file "$output_file" \
606
+ --arg output_format "text" \
607
+ --argjson has_completion_signal "$has_completion_signal" \
608
+ --argjson is_test_only "$is_test_only" \
609
+ --argjson is_stuck "$is_stuck" \
610
+ --argjson has_progress "$has_progress" \
611
+ --argjson files_modified "$files_modified" \
612
+ --argjson confidence_score "$confidence_score" \
613
+ --argjson exit_signal "$exit_signal" \
614
+ --arg work_summary "$work_summary" \
615
+ --argjson output_length "$output_length" \
616
+ '{
617
+ loop_number: $loop_number,
618
+ timestamp: $timestamp,
619
+ output_file: $output_file,
620
+ output_format: $output_format,
621
+ analysis: {
622
+ has_completion_signal: $has_completion_signal,
623
+ is_test_only: $is_test_only,
624
+ is_stuck: $is_stuck,
625
+ has_progress: $has_progress,
626
+ files_modified: $files_modified,
627
+ confidence_score: $confidence_score,
628
+ exit_signal: $exit_signal,
629
+ work_summary: $work_summary,
630
+ output_length: $output_length,
631
+ has_permission_denials: false,
632
+ permission_denial_count: 0,
633
+ denied_commands: []
634
+ }
635
+ }' > "$analysis_result_file"
636
+
637
+ # Always return 0 (success) - callers should check the JSON result file
638
+ # Returning non-zero would cause issues with set -e and test frameworks
639
+ return 0
640
+ }
641
+
642
+ # Update exit signals file based on analysis
643
+ update_exit_signals() {
644
+ local analysis_file=${1:-"$RALPH_DIR/.response_analysis"}
645
+ local exit_signals_file=${2:-"$RALPH_DIR/.exit_signals"}
646
+
647
+ if [[ ! -f "$analysis_file" ]]; then
648
+ echo "ERROR: Analysis file not found: $analysis_file"
649
+ return 1
650
+ fi
651
+
652
+ # Read analysis results
653
+ local is_test_only=$(jq -r '.analysis.is_test_only' "$analysis_file")
654
+ local has_completion_signal=$(jq -r '.analysis.has_completion_signal' "$analysis_file")
655
+ local loop_number=$(jq -r '.loop_number' "$analysis_file")
656
+ local has_progress=$(jq -r '.analysis.has_progress' "$analysis_file")
657
+
658
+ # Read current exit signals
659
+ local signals=$(cat "$exit_signals_file" 2>/dev/null || echo '{"test_only_loops": [], "done_signals": [], "completion_indicators": []}')
660
+
661
+ # Update test_only_loops array
662
+ if [[ "$is_test_only" == "true" ]]; then
663
+ signals=$(echo "$signals" | jq ".test_only_loops += [$loop_number]")
664
+ else
665
+ # Clear test_only_loops if we had implementation
666
+ if [[ "$has_progress" == "true" ]]; then
667
+ signals=$(echo "$signals" | jq '.test_only_loops = []')
668
+ fi
669
+ fi
670
+
671
+ # Update done_signals array
672
+ if [[ "$has_completion_signal" == "true" ]]; then
673
+ signals=$(echo "$signals" | jq ".done_signals += [$loop_number]")
674
+ fi
675
+
676
+ # Update completion_indicators array (only when Claude explicitly signals exit)
677
+ # Note: Previously used confidence >= 60, but JSON mode always has confidence >= 70
678
+ # due to deterministic scoring (+50 for JSON format, +20 for result field).
679
+ # This caused premature exits after 5 loops. Now we respect Claude's explicit intent.
680
+ local exit_signal=$(jq -r '.analysis.exit_signal // false' "$analysis_file")
681
+ if [[ "$exit_signal" == "true" ]]; then
682
+ signals=$(echo "$signals" | jq ".completion_indicators += [$loop_number]")
683
+ fi
684
+
685
+ # Keep only last 5 signals (rolling window)
686
+ signals=$(echo "$signals" | jq '.test_only_loops = .test_only_loops[-5:]')
687
+ signals=$(echo "$signals" | jq '.done_signals = .done_signals[-5:]')
688
+ signals=$(echo "$signals" | jq '.completion_indicators = .completion_indicators[-5:]')
689
+
690
+ # Write updated signals
691
+ echo "$signals" > "$exit_signals_file"
692
+
693
+ return 0
694
+ }
695
+
696
+ # Log analysis results in human-readable format
697
+ log_analysis_summary() {
698
+ local analysis_file=${1:-"$RALPH_DIR/.response_analysis"}
699
+
700
+ if [[ ! -f "$analysis_file" ]]; then
701
+ return 1
702
+ fi
703
+
704
+ local loop=$(jq -r '.loop_number' "$analysis_file")
705
+ local exit_sig=$(jq -r '.analysis.exit_signal' "$analysis_file")
706
+ local confidence=$(jq -r '.analysis.confidence_score' "$analysis_file")
707
+ local test_only=$(jq -r '.analysis.is_test_only' "$analysis_file")
708
+ local files_changed=$(jq -r '.analysis.files_modified' "$analysis_file")
709
+ local summary=$(jq -r '.analysis.work_summary' "$analysis_file")
710
+
711
+ echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${NC}"
712
+ echo -e "${BLUE}║ Response Analysis - Loop #$loop ║${NC}"
713
+ echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}"
714
+ echo -e "${YELLOW}Exit Signal:${NC} $exit_sig"
715
+ echo -e "${YELLOW}Confidence:${NC} $confidence%"
716
+ echo -e "${YELLOW}Test Only:${NC} $test_only"
717
+ echo -e "${YELLOW}Files Changed:${NC} $files_changed"
718
+ echo -e "${YELLOW}Summary:${NC} $summary"
719
+ echo ""
720
+ }
721
+
722
+ # Detect if Claude is stuck (repeating same errors)
723
+ detect_stuck_loop() {
724
+ local current_output=$1
725
+ local history_dir=${2:-"$RALPH_DIR/logs"}
726
+
727
+ # Get last 3 output files
728
+ local recent_outputs=$(ls -t "$history_dir"/claude_output_*.log 2>/dev/null | head -3)
729
+
730
+ if [[ -z "$recent_outputs" ]]; then
731
+ return 1 # Not enough history
732
+ fi
733
+
734
+ # Extract key errors from current output using two-stage filtering
735
+ # Stage 1: Filter out JSON field patterns to avoid false positives
736
+ # Stage 2: Extract actual error messages
737
+ local current_errors=$(grep -v '"[^"]*error[^"]*":' "$current_output" 2>/dev/null | \
738
+ grep -E '(^Error:|^ERROR:|^error:|\]: error|Link: error|Error occurred|failed with error|[Ee]xception|Fatal|FATAL)' 2>/dev/null | \
739
+ sort | uniq)
740
+
741
+ if [[ -z "$current_errors" ]]; then
742
+ return 1 # No errors
743
+ fi
744
+
745
+ # Check if same errors appear in all recent outputs
746
+ # For multi-line errors, verify ALL error lines appear in ALL history files
747
+ local all_files_match=true
748
+ while IFS= read -r output_file; do
749
+ local file_matches_all=true
750
+ while IFS= read -r error_line; do
751
+ # Use -F for literal fixed-string matching (not regex)
752
+ if ! grep -qF "$error_line" "$output_file" 2>/dev/null; then
753
+ file_matches_all=false
754
+ break
755
+ fi
756
+ done <<< "$current_errors"
757
+
758
+ if [[ "$file_matches_all" != "true" ]]; then
759
+ all_files_match=false
760
+ break
761
+ fi
762
+ done <<< "$recent_outputs"
763
+
764
+ if [[ "$all_files_match" == "true" ]]; then
765
+ return 0 # Stuck on same error(s)
766
+ else
767
+ return 1 # Making progress or different errors
768
+ fi
769
+ }
770
+
771
+ # =============================================================================
772
+ # SESSION MANAGEMENT FUNCTIONS
773
+ # =============================================================================
774
+
775
+ # Session file location - standardized across ralph_loop.sh and response_analyzer.sh
776
+ SESSION_FILE="$RALPH_DIR/.claude_session_id"
777
+ # Session expiration time in seconds (24 hours)
778
+ SESSION_EXPIRATION_SECONDS=86400
779
+
780
+ # Store session ID to file with timestamp
781
+ # Usage: store_session_id "session-uuid-123"
782
+ store_session_id() {
783
+ local session_id=$1
784
+
785
+ if [[ -z "$session_id" ]]; then
786
+ return 1
787
+ fi
788
+
789
+ # Write session with timestamp using jq for safe JSON construction
790
+ jq -n \
791
+ --arg session_id "$session_id" \
792
+ --arg timestamp "$(get_iso_timestamp)" \
793
+ '{
794
+ session_id: $session_id,
795
+ timestamp: $timestamp
796
+ }' > "$SESSION_FILE"
797
+
798
+ return 0
799
+ }
800
+
801
+ # Get the last stored session ID
802
+ # Returns: session ID string or empty if not found
803
+ get_last_session_id() {
804
+ if [[ ! -f "$SESSION_FILE" ]]; then
805
+ echo ""
806
+ return 0
807
+ fi
808
+
809
+ # Extract session_id from JSON file
810
+ local session_id=$(jq -r '.session_id // ""' "$SESSION_FILE" 2>/dev/null)
811
+ echo "$session_id"
812
+ return 0
813
+ }
814
+
815
+ # Check if the stored session should be resumed
816
+ # Returns: 0 (true) if session is valid and recent, 1 (false) otherwise
817
+ should_resume_session() {
818
+ if [[ ! -f "$SESSION_FILE" ]]; then
819
+ echo "false"
820
+ return 1
821
+ fi
822
+
823
+ # Get session timestamp
824
+ local timestamp=$(jq -r '.timestamp // ""' "$SESSION_FILE" 2>/dev/null)
825
+
826
+ if [[ -z "$timestamp" ]]; then
827
+ echo "false"
828
+ return 1
829
+ fi
830
+
831
+ # Calculate session age using date utilities
832
+ local now=$(get_epoch_seconds)
833
+ local session_time
834
+
835
+ # Parse ISO timestamp to epoch - try multiple formats for cross-platform compatibility
836
+ # Strip milliseconds if present (e.g., 2026-01-09T10:30:00.123+00:00 → 2026-01-09T10:30:00+00:00)
837
+ local clean_timestamp="${timestamp}"
838
+ if [[ "$timestamp" =~ \.[0-9]+[+-Z] ]]; then
839
+ clean_timestamp=$(echo "$timestamp" | sed 's/\.[0-9]*\([+-Z]\)/\1/')
840
+ fi
841
+
842
+ if command -v gdate &>/dev/null; then
843
+ # macOS with coreutils
844
+ session_time=$(gdate -d "$clean_timestamp" +%s 2>/dev/null)
845
+ elif date --version 2>&1 | grep -q GNU; then
846
+ # GNU date (Linux)
847
+ session_time=$(date -d "$clean_timestamp" +%s 2>/dev/null)
848
+ else
849
+ # BSD date (macOS without coreutils) - try parsing ISO format
850
+ # Format: 2026-01-09T10:30:00+00:00 or 2026-01-09T10:30:00Z
851
+ # Strip timezone suffix for BSD date parsing
852
+ local date_only="${clean_timestamp%[+-Z]*}"
853
+ session_time=$(date -j -f "%Y-%m-%dT%H:%M:%S" "$date_only" +%s 2>/dev/null)
854
+ fi
855
+
856
+ # If we couldn't parse the timestamp, consider session expired
857
+ if [[ -z "$session_time" || ! "$session_time" =~ ^[0-9]+$ ]]; then
858
+ echo "false"
859
+ return 1
860
+ fi
861
+
862
+ # Calculate age in seconds
863
+ local age=$((now - session_time))
864
+
865
+ # Check if session is still valid (less than expiration time)
866
+ if [[ $age -lt $SESSION_EXPIRATION_SECONDS ]]; then
867
+ echo "true"
868
+ return 0
869
+ else
870
+ echo "false"
871
+ return 1
872
+ fi
873
+ }
874
+
875
+ # Export functions for use in ralph_loop.sh
876
+ export -f detect_output_format
877
+ export -f parse_json_response
878
+ export -f analyze_response
879
+ export -f update_exit_signals
880
+ export -f log_analysis_summary
881
+ export -f detect_stuck_loop
882
+ export -f store_session_id
883
+ export -f get_last_session_id
884
+ export -f should_resume_session