bmalph 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +217 -0
- package/bin/bmalph.js +14 -0
- package/bmad/bmm/agents/analyst.agent.yaml +36 -0
- package/bmad/bmm/agents/architect.agent.yaml +28 -0
- package/bmad/bmm/agents/dev.agent.yaml +38 -0
- package/bmad/bmm/agents/pm.agent.yaml +46 -0
- package/bmad/bmm/agents/quick-flow-solo-dev.agent.yaml +32 -0
- package/bmad/bmm/agents/sm.agent.yaml +36 -0
- package/bmad/bmm/agents/tea.agent.yaml +63 -0
- package/bmad/bmm/agents/tech-writer/tech-writer-sidecar/documentation-standards.md +224 -0
- package/bmad/bmm/agents/tech-writer/tech-writer.agent.yaml +45 -0
- package/bmad/bmm/agents/ux-designer.agent.yaml +26 -0
- package/bmad/bmm/data/project-context-template.md +26 -0
- package/bmad/bmm/module-help.csv +31 -0
- package/bmad/bmm/module.yaml +44 -0
- package/bmad/bmm/sub-modules/claude-code/config.yaml +4 -0
- package/bmad/bmm/sub-modules/claude-code/injections.yaml +242 -0
- package/bmad/bmm/sub-modules/claude-code/readme.md +87 -0
- package/bmad/bmm/teams/default-party.csv +21 -0
- package/bmad/bmm/teams/team-fullstack.yaml +12 -0
- package/bmad/bmm/testarch/knowledge/adr-quality-readiness-checklist.md +350 -0
- package/bmad/bmm/testarch/knowledge/api-request.md +442 -0
- package/bmad/bmm/testarch/knowledge/api-testing-patterns.md +843 -0
- package/bmad/bmm/testarch/knowledge/auth-session.md +552 -0
- package/bmad/bmm/testarch/knowledge/burn-in.md +273 -0
- package/bmad/bmm/testarch/knowledge/ci-burn-in.md +675 -0
- package/bmad/bmm/testarch/knowledge/component-tdd.md +486 -0
- package/bmad/bmm/testarch/knowledge/contract-testing.md +957 -0
- package/bmad/bmm/testarch/knowledge/data-factories.md +500 -0
- package/bmad/bmm/testarch/knowledge/email-auth.md +721 -0
- package/bmad/bmm/testarch/knowledge/error-handling.md +725 -0
- package/bmad/bmm/testarch/knowledge/feature-flags.md +750 -0
- package/bmad/bmm/testarch/knowledge/file-utils.md +463 -0
- package/bmad/bmm/testarch/knowledge/fixture-architecture.md +401 -0
- package/bmad/bmm/testarch/knowledge/fixtures-composition.md +382 -0
- package/bmad/bmm/testarch/knowledge/intercept-network-call.md +430 -0
- package/bmad/bmm/testarch/knowledge/log.md +429 -0
- package/bmad/bmm/testarch/knowledge/network-error-monitor.md +405 -0
- package/bmad/bmm/testarch/knowledge/network-first.md +486 -0
- package/bmad/bmm/testarch/knowledge/network-recorder.md +527 -0
- package/bmad/bmm/testarch/knowledge/nfr-criteria.md +670 -0
- package/bmad/bmm/testarch/knowledge/overview.md +286 -0
- package/bmad/bmm/testarch/knowledge/playwright-config.md +730 -0
- package/bmad/bmm/testarch/knowledge/probability-impact.md +601 -0
- package/bmad/bmm/testarch/knowledge/recurse.md +421 -0
- package/bmad/bmm/testarch/knowledge/risk-governance.md +615 -0
- package/bmad/bmm/testarch/knowledge/selective-testing.md +732 -0
- package/bmad/bmm/testarch/knowledge/selector-resilience.md +527 -0
- package/bmad/bmm/testarch/knowledge/test-healing-patterns.md +644 -0
- package/bmad/bmm/testarch/knowledge/test-levels-framework.md +473 -0
- package/bmad/bmm/testarch/knowledge/test-priorities-matrix.md +373 -0
- package/bmad/bmm/testarch/knowledge/test-quality.md +664 -0
- package/bmad/bmm/testarch/knowledge/timing-debugging.md +372 -0
- package/bmad/bmm/testarch/knowledge/visual-debugging.md +524 -0
- package/bmad/bmm/testarch/tea-index.csv +35 -0
- package/bmad/bmm/workflows/1-analysis/create-product-brief/product-brief.template.md +10 -0
- package/bmad/bmm/workflows/1-analysis/create-product-brief/steps/step-01-init.md +177 -0
- package/bmad/bmm/workflows/1-analysis/create-product-brief/steps/step-01b-continue.md +161 -0
- package/bmad/bmm/workflows/1-analysis/create-product-brief/steps/step-02-vision.md +199 -0
- package/bmad/bmm/workflows/1-analysis/create-product-brief/steps/step-03-users.md +202 -0
- package/bmad/bmm/workflows/1-analysis/create-product-brief/steps/step-04-metrics.md +205 -0
- package/bmad/bmm/workflows/1-analysis/create-product-brief/steps/step-05-scope.md +219 -0
- package/bmad/bmm/workflows/1-analysis/create-product-brief/steps/step-06-complete.md +162 -0
- package/bmad/bmm/workflows/1-analysis/create-product-brief/workflow.md +58 -0
- package/bmad/bmm/workflows/1-analysis/research/domain-steps/step-01-init.md +137 -0
- package/bmad/bmm/workflows/1-analysis/research/domain-steps/step-02-domain-analysis.md +229 -0
- package/bmad/bmm/workflows/1-analysis/research/domain-steps/step-03-competitive-landscape.md +238 -0
- package/bmad/bmm/workflows/1-analysis/research/domain-steps/step-04-regulatory-focus.md +206 -0
- package/bmad/bmm/workflows/1-analysis/research/domain-steps/step-05-technical-trends.md +234 -0
- package/bmad/bmm/workflows/1-analysis/research/domain-steps/step-06-research-synthesis.md +443 -0
- package/bmad/bmm/workflows/1-analysis/research/market-steps/step-01-init.md +182 -0
- package/bmad/bmm/workflows/1-analysis/research/market-steps/step-02-customer-behavior.md +237 -0
- package/bmad/bmm/workflows/1-analysis/research/market-steps/step-02-customer-insights.md +200 -0
- package/bmad/bmm/workflows/1-analysis/research/market-steps/step-03-customer-pain-points.md +249 -0
- package/bmad/bmm/workflows/1-analysis/research/market-steps/step-04-customer-decisions.md +259 -0
- package/bmad/bmm/workflows/1-analysis/research/market-steps/step-05-competitive-analysis.md +177 -0
- package/bmad/bmm/workflows/1-analysis/research/market-steps/step-06-research-completion.md +475 -0
- package/bmad/bmm/workflows/1-analysis/research/research.template.md +29 -0
- package/bmad/bmm/workflows/1-analysis/research/technical-steps/step-01-init.md +137 -0
- package/bmad/bmm/workflows/1-analysis/research/technical-steps/step-02-technical-overview.md +239 -0
- package/bmad/bmm/workflows/1-analysis/research/technical-steps/step-03-integration-patterns.md +248 -0
- package/bmad/bmm/workflows/1-analysis/research/technical-steps/step-04-architectural-patterns.md +202 -0
- package/bmad/bmm/workflows/1-analysis/research/technical-steps/step-05-implementation-research.md +239 -0
- package/bmad/bmm/workflows/1-analysis/research/technical-steps/step-06-research-synthesis.md +486 -0
- package/bmad/bmm/workflows/1-analysis/research/workflow.md +173 -0
- package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-01-init.md +135 -0
- package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-01b-continue.md +127 -0
- package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-02-discovery.md +190 -0
- package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-03-core-experience.md +216 -0
- package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-04-emotional-response.md +219 -0
- package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-05-inspiration.md +234 -0
- package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-06-design-system.md +252 -0
- package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-07-defining-experience.md +254 -0
- package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-08-visual-foundation.md +224 -0
- package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-09-design-directions.md +224 -0
- package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-10-user-journeys.md +241 -0
- package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-11-component-strategy.md +248 -0
- package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-12-ux-patterns.md +237 -0
- package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-13-responsive-accessibility.md +264 -0
- package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-14-complete.md +171 -0
- package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/ux-design-template.md +13 -0
- package/bmad/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md +43 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/data/domain-complexity.csv +13 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/data/prd-purpose.md +197 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/data/project-types.csv +11 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-c/step-01-init.md +191 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-c/step-01b-continue.md +153 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-c/step-02-discovery.md +224 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-c/step-03-success.md +226 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-c/step-04-journeys.md +213 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-c/step-05-domain.md +207 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-c/step-06-innovation.md +226 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-c/step-07-project-type.md +237 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-c/step-08-scoping.md +228 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-c/step-09-functional.md +231 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-c/step-10-nonfunctional.md +242 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-c/step-11-polish.md +217 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-c/step-12-complete.md +124 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-e/step-e-01-discovery.md +247 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-e/step-e-01b-legacy-conversion.md +208 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-e/step-e-02-review.md +249 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-e/step-e-03-edit.md +253 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-e/step-e-04-complete.md +168 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-01-discovery.md +218 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-02-format-detection.md +191 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-02b-parity-check.md +209 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-03-density-validation.md +174 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-04-brief-coverage-validation.md +214 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-05-measurability-validation.md +228 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-06-traceability-validation.md +217 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-07-implementation-leakage-validation.md +205 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-08-domain-compliance-validation.md +243 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-09-project-type-validation.md +263 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-10-smart-validation.md +209 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-11-holistic-quality-validation.md +264 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-12-completeness-validation.md +242 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-13-report-complete.md +231 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/templates/prd-template.md +10 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/validation-report-prd-workflow.md +433 -0
- package/bmad/bmm/workflows/2-plan-workflows/prd/workflow.md +150 -0
- package/bmad/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-01-document-discovery.md +190 -0
- package/bmad/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-02-prd-analysis.md +178 -0
- package/bmad/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-03-epic-coverage-validation.md +179 -0
- package/bmad/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-04-ux-alignment.md +139 -0
- package/bmad/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-05-epic-quality-review.md +252 -0
- package/bmad/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-06-final-assessment.md +135 -0
- package/bmad/bmm/workflows/3-solutioning/check-implementation-readiness/templates/readiness-report-template.md +4 -0
- package/bmad/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md +55 -0
- package/bmad/bmm/workflows/3-solutioning/create-architecture/architecture-decision-template.md +12 -0
- package/bmad/bmm/workflows/3-solutioning/create-architecture/data/domain-complexity.csv +11 -0
- package/bmad/bmm/workflows/3-solutioning/create-architecture/data/project-types.csv +7 -0
- package/bmad/bmm/workflows/3-solutioning/create-architecture/steps/step-01-init.md +153 -0
- package/bmad/bmm/workflows/3-solutioning/create-architecture/steps/step-01b-continue.md +164 -0
- package/bmad/bmm/workflows/3-solutioning/create-architecture/steps/step-02-context.md +224 -0
- package/bmad/bmm/workflows/3-solutioning/create-architecture/steps/step-03-starter.md +331 -0
- package/bmad/bmm/workflows/3-solutioning/create-architecture/steps/step-04-decisions.md +318 -0
- package/bmad/bmm/workflows/3-solutioning/create-architecture/steps/step-05-patterns.md +359 -0
- package/bmad/bmm/workflows/3-solutioning/create-architecture/steps/step-06-structure.md +379 -0
- package/bmad/bmm/workflows/3-solutioning/create-architecture/steps/step-07-validation.md +359 -0
- package/bmad/bmm/workflows/3-solutioning/create-architecture/steps/step-08-complete.md +76 -0
- package/bmad/bmm/workflows/3-solutioning/create-architecture/workflow.md +50 -0
- package/bmad/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-01-validate-prerequisites.md +259 -0
- package/bmad/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-02-design-epics.md +233 -0
- package/bmad/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-03-create-stories.md +272 -0
- package/bmad/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-04-final-validation.md +149 -0
- package/bmad/bmm/workflows/3-solutioning/create-epics-and-stories/templates/epics-template.md +57 -0
- package/bmad/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md +59 -0
- package/bmad/bmm/workflows/4-implementation/code-review/checklist.md +23 -0
- package/bmad/bmm/workflows/4-implementation/code-review/instructions.xml +227 -0
- package/bmad/bmm/workflows/4-implementation/code-review/workflow.yaml +51 -0
- package/bmad/bmm/workflows/4-implementation/correct-course/checklist.md +288 -0
- package/bmad/bmm/workflows/4-implementation/correct-course/instructions.md +206 -0
- package/bmad/bmm/workflows/4-implementation/correct-course/workflow.yaml +60 -0
- package/bmad/bmm/workflows/4-implementation/create-story/checklist.md +358 -0
- package/bmad/bmm/workflows/4-implementation/create-story/instructions.xml +345 -0
- package/bmad/bmm/workflows/4-implementation/create-story/template.md +49 -0
- package/bmad/bmm/workflows/4-implementation/create-story/workflow.yaml +61 -0
- package/bmad/bmm/workflows/4-implementation/dev-story/checklist.md +80 -0
- package/bmad/bmm/workflows/4-implementation/dev-story/instructions.xml +410 -0
- package/bmad/bmm/workflows/4-implementation/dev-story/workflow.yaml +27 -0
- package/bmad/bmm/workflows/4-implementation/retrospective/instructions.md +1443 -0
- package/bmad/bmm/workflows/4-implementation/retrospective/workflow.yaml +58 -0
- package/bmad/bmm/workflows/4-implementation/sprint-planning/checklist.md +33 -0
- package/bmad/bmm/workflows/4-implementation/sprint-planning/instructions.md +225 -0
- package/bmad/bmm/workflows/4-implementation/sprint-planning/sprint-status-template.yaml +55 -0
- package/bmad/bmm/workflows/4-implementation/sprint-planning/workflow.yaml +54 -0
- package/bmad/bmm/workflows/4-implementation/sprint-status/instructions.md +229 -0
- package/bmad/bmm/workflows/4-implementation/sprint-status/workflow.yaml +36 -0
- package/bmad/bmm/workflows/bmad-quick-flow/quick-dev/data/project-levels.yaml +59 -0
- package/bmad/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-01-mode-detection.md +156 -0
- package/bmad/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-02-context-gathering.md +120 -0
- package/bmad/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-03-execute.md +113 -0
- package/bmad/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-04-self-check.md +113 -0
- package/bmad/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-05-adversarial-review.md +106 -0
- package/bmad/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-06-resolve-findings.md +140 -0
- package/bmad/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md +50 -0
- package/bmad/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-01-understand.md +189 -0
- package/bmad/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-02-investigate.md +144 -0
- package/bmad/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-03-generate.md +128 -0
- package/bmad/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-04-review.md +191 -0
- package/bmad/bmm/workflows/bmad-quick-flow/quick-spec/tech-spec-template.md +74 -0
- package/bmad/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md +79 -0
- package/bmad/bmm/workflows/document-project/checklist.md +245 -0
- package/bmad/bmm/workflows/document-project/documentation-requirements.csv +12 -0
- package/bmad/bmm/workflows/document-project/instructions.md +221 -0
- package/bmad/bmm/workflows/document-project/templates/deep-dive-template.md +345 -0
- package/bmad/bmm/workflows/document-project/templates/index-template.md +169 -0
- package/bmad/bmm/workflows/document-project/templates/project-overview-template.md +103 -0
- package/bmad/bmm/workflows/document-project/templates/project-scan-report-schema.json +160 -0
- package/bmad/bmm/workflows/document-project/templates/source-tree-template.md +135 -0
- package/bmad/bmm/workflows/document-project/workflow.yaml +30 -0
- package/bmad/bmm/workflows/document-project/workflows/deep-dive-instructions.md +298 -0
- package/bmad/bmm/workflows/document-project/workflows/deep-dive.yaml +31 -0
- package/bmad/bmm/workflows/document-project/workflows/full-scan-instructions.md +1106 -0
- package/bmad/bmm/workflows/document-project/workflows/full-scan.yaml +31 -0
- package/bmad/bmm/workflows/excalidraw-diagrams/_shared/excalidraw-library.json +90 -0
- package/bmad/bmm/workflows/excalidraw-diagrams/_shared/excalidraw-templates.yaml +127 -0
- package/bmad/bmm/workflows/excalidraw-diagrams/create-dataflow/checklist.md +39 -0
- package/bmad/bmm/workflows/excalidraw-diagrams/create-dataflow/instructions.md +130 -0
- package/bmad/bmm/workflows/excalidraw-diagrams/create-dataflow/workflow.yaml +27 -0
- package/bmad/bmm/workflows/excalidraw-diagrams/create-diagram/checklist.md +43 -0
- package/bmad/bmm/workflows/excalidraw-diagrams/create-diagram/instructions.md +141 -0
- package/bmad/bmm/workflows/excalidraw-diagrams/create-diagram/workflow.yaml +27 -0
- package/bmad/bmm/workflows/excalidraw-diagrams/create-flowchart/checklist.md +49 -0
- package/bmad/bmm/workflows/excalidraw-diagrams/create-flowchart/instructions.md +241 -0
- package/bmad/bmm/workflows/excalidraw-diagrams/create-flowchart/workflow.yaml +27 -0
- package/bmad/bmm/workflows/excalidraw-diagrams/create-wireframe/checklist.md +38 -0
- package/bmad/bmm/workflows/excalidraw-diagrams/create-wireframe/instructions.md +133 -0
- package/bmad/bmm/workflows/excalidraw-diagrams/create-wireframe/workflow.yaml +27 -0
- package/bmad/bmm/workflows/testarch/atdd/atdd-checklist-template.md +363 -0
- package/bmad/bmm/workflows/testarch/atdd/checklist.md +374 -0
- package/bmad/bmm/workflows/testarch/atdd/instructions.md +806 -0
- package/bmad/bmm/workflows/testarch/atdd/workflow.yaml +47 -0
- package/bmad/bmm/workflows/testarch/automate/checklist.md +582 -0
- package/bmad/bmm/workflows/testarch/automate/instructions.md +1324 -0
- package/bmad/bmm/workflows/testarch/automate/workflow.yaml +54 -0
- package/bmad/bmm/workflows/testarch/ci/checklist.md +247 -0
- package/bmad/bmm/workflows/testarch/ci/github-actions-template.yaml +198 -0
- package/bmad/bmm/workflows/testarch/ci/gitlab-ci-template.yaml +149 -0
- package/bmad/bmm/workflows/testarch/ci/instructions.md +536 -0
- package/bmad/bmm/workflows/testarch/ci/workflow.yaml +47 -0
- package/bmad/bmm/workflows/testarch/framework/checklist.md +320 -0
- package/bmad/bmm/workflows/testarch/framework/instructions.md +481 -0
- package/bmad/bmm/workflows/testarch/framework/workflow.yaml +49 -0
- package/bmad/bmm/workflows/testarch/nfr-assess/checklist.md +407 -0
- package/bmad/bmm/workflows/testarch/nfr-assess/instructions.md +726 -0
- package/bmad/bmm/workflows/testarch/nfr-assess/nfr-report-template.md +461 -0
- package/bmad/bmm/workflows/testarch/nfr-assess/workflow.yaml +49 -0
- package/bmad/bmm/workflows/testarch/test-design/checklist.md +407 -0
- package/bmad/bmm/workflows/testarch/test-design/instructions.md +1158 -0
- package/bmad/bmm/workflows/testarch/test-design/test-design-architecture-template.md +213 -0
- package/bmad/bmm/workflows/testarch/test-design/test-design-qa-template.md +286 -0
- package/bmad/bmm/workflows/testarch/test-design/test-design-template.md +294 -0
- package/bmad/bmm/workflows/testarch/test-design/workflow.yaml +71 -0
- package/bmad/bmm/workflows/testarch/test-review/checklist.md +472 -0
- package/bmad/bmm/workflows/testarch/test-review/instructions.md +628 -0
- package/bmad/bmm/workflows/testarch/test-review/test-review-template.md +390 -0
- package/bmad/bmm/workflows/testarch/test-review/workflow.yaml +48 -0
- package/bmad/bmm/workflows/testarch/trace/checklist.md +642 -0
- package/bmad/bmm/workflows/testarch/trace/instructions.md +1030 -0
- package/bmad/bmm/workflows/testarch/trace/trace-template.md +675 -0
- package/bmad/bmm/workflows/testarch/trace/workflow.yaml +57 -0
- package/bmad/core/agents/bmad-master.agent.yaml +30 -0
- package/bmad/core/module-help.csv +11 -0
- package/bmad/core/module.yaml +25 -0
- package/bmad/core/resources/excalidraw/README.md +160 -0
- package/bmad/core/resources/excalidraw/excalidraw-helpers.md +127 -0
- package/bmad/core/resources/excalidraw/library-loader.md +50 -0
- package/bmad/core/resources/excalidraw/validate-json-instructions.md +79 -0
- package/bmad/core/tasks/bmad-help.md +62 -0
- package/bmad/core/tasks/editorial-review-prose.xml +91 -0
- package/bmad/core/tasks/editorial-review-structure.xml +198 -0
- package/bmad/core/tasks/index-docs.xml +65 -0
- package/bmad/core/tasks/review-adversarial-general.xml +48 -0
- package/bmad/core/tasks/shard-doc.xml +109 -0
- package/bmad/core/tasks/workflow.xml +235 -0
- package/bmad/core/workflows/advanced-elicitation/methods.csv +51 -0
- package/bmad/core/workflows/advanced-elicitation/workflow.xml +117 -0
- package/bmad/core/workflows/brainstorming/brain-methods.csv +62 -0
- package/bmad/core/workflows/brainstorming/steps/step-01-session-setup.md +197 -0
- package/bmad/core/workflows/brainstorming/steps/step-01b-continue.md +122 -0
- package/bmad/core/workflows/brainstorming/steps/step-02a-user-selected.md +225 -0
- package/bmad/core/workflows/brainstorming/steps/step-02b-ai-recommended.md +237 -0
- package/bmad/core/workflows/brainstorming/steps/step-02c-random-selection.md +209 -0
- package/bmad/core/workflows/brainstorming/steps/step-02d-progressive-flow.md +264 -0
- package/bmad/core/workflows/brainstorming/steps/step-03-technique-execution.md +399 -0
- package/bmad/core/workflows/brainstorming/steps/step-04-idea-organization.md +303 -0
- package/bmad/core/workflows/brainstorming/template.md +15 -0
- package/bmad/core/workflows/brainstorming/workflow.md +58 -0
- package/bmad/core/workflows/party-mode/steps/step-01-agent-loading.md +138 -0
- package/bmad/core/workflows/party-mode/steps/step-02-discussion-orchestration.md +187 -0
- package/bmad/core/workflows/party-mode/steps/step-03-graceful-exit.md +157 -0
- package/bmad/core/workflows/party-mode/workflow.md +194 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +26 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +168 -0
- package/dist/commands/guide.d.ts +1 -0
- package/dist/commands/guide.js +19 -0
- package/dist/commands/implement.d.ts +1 -0
- package/dist/commands/implement.js +83 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.js +67 -0
- package/dist/commands/plan.d.ts +5 -0
- package/dist/commands/plan.js +44 -0
- package/dist/commands/reset.d.ts +5 -0
- package/dist/commands/reset.js +35 -0
- package/dist/commands/resume.d.ts +1 -0
- package/dist/commands/resume.js +44 -0
- package/dist/commands/start.d.ts +5 -0
- package/dist/commands/start.js +54 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +53 -0
- package/dist/commands/upgrade.d.ts +1 -0
- package/dist/commands/upgrade.js +34 -0
- package/dist/installer.d.ts +11 -0
- package/dist/installer.js +219 -0
- package/dist/transition.d.ts +52 -0
- package/dist/transition.js +656 -0
- package/dist/utils/config.d.ts +7 -0
- package/dist/utils/config.js +14 -0
- package/dist/utils/json.d.ts +7 -0
- package/dist/utils/json.js +26 -0
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.js +13 -0
- package/dist/utils/state.d.ts +29 -0
- package/dist/utils/state.js +78 -0
- package/dist/utils/validate.d.ts +4 -0
- package/dist/utils/validate.js +42 -0
- package/package.json +59 -0
- package/ralph/lib/circuit_breaker.sh +330 -0
- package/ralph/lib/date_utils.sh +53 -0
- package/ralph/lib/response_analyzer.sh +768 -0
- package/ralph/lib/timeout_utils.sh +145 -0
- package/ralph/ralph_loop.sh +1391 -0
- package/ralph/templates/AGENT.md +158 -0
- package/ralph/templates/PROMPT.md +292 -0
- package/ralph/templates/fix_plan.md +27 -0
- package/ralph/templates/specs/.gitkeep +2 -0
- package/slash-commands/advanced-elicitation.md +1 -0
- package/slash-commands/adversarial-review.md +1 -0
- package/slash-commands/analyst.md +1 -0
- package/slash-commands/architect.md +1 -0
- package/slash-commands/atdd.md +1 -0
- package/slash-commands/bmad-help.md +1 -0
- package/slash-commands/bmalph-implement.md +152 -0
- package/slash-commands/bmalph.md +1 -0
- package/slash-commands/brainstorm-project.md +1 -0
- package/slash-commands/brainstorming.md +1 -0
- package/slash-commands/continuous-integration.md +1 -0
- package/slash-commands/correct-course.md +1 -0
- package/slash-commands/create-architecture.md +1 -0
- package/slash-commands/create-brief.md +1 -0
- package/slash-commands/create-dataflow.md +1 -0
- package/slash-commands/create-diagram.md +1 -0
- package/slash-commands/create-epics-stories.md +1 -0
- package/slash-commands/create-flowchart.md +1 -0
- package/slash-commands/create-prd.md +1 -0
- package/slash-commands/create-story.md +1 -0
- package/slash-commands/create-ux.md +1 -0
- package/slash-commands/create-wireframe.md +1 -0
- package/slash-commands/dev.md +1 -0
- package/slash-commands/document-project.md +1 -0
- package/slash-commands/domain-research.md +1 -0
- package/slash-commands/editorial-prose.md +1 -0
- package/slash-commands/editorial-structure.md +1 -0
- package/slash-commands/execute-workflow.md +1 -0
- package/slash-commands/implementation-readiness.md +1 -0
- package/slash-commands/index-docs.md +1 -0
- package/slash-commands/market-research.md +1 -0
- package/slash-commands/nfr-assess.md +1 -0
- package/slash-commands/party-mode.md +1 -0
- package/slash-commands/pm.md +1 -0
- package/slash-commands/quick-dev.md +1 -0
- package/slash-commands/quick-flow-solo-dev.md +1 -0
- package/slash-commands/retrospective.md +1 -0
- package/slash-commands/shard-doc.md +1 -0
- package/slash-commands/sm.md +1 -0
- package/slash-commands/sprint-planning.md +1 -0
- package/slash-commands/sprint-status.md +1 -0
- package/slash-commands/tea.md +1 -0
- package/slash-commands/tech-spec.md +1 -0
- package/slash-commands/technical-research.md +1 -0
- package/slash-commands/test-automate.md +1 -0
- package/slash-commands/test-design.md +1 -0
- package/slash-commands/test-framework.md +1 -0
- package/slash-commands/test-review.md +1 -0
- package/slash-commands/test-trace.md +1 -0
- package/slash-commands/ux-designer.md +1 -0
- package/slash-commands/validate-architecture.md +1 -0
- package/slash-commands/validate-brief.md +1 -0
- package/slash-commands/validate-epics-stories.md +1 -0
- package/slash-commands/validate-prd.md +1 -0
- package/slash-commands/validate-story.md +1 -0
- package/slash-commands/validate-test-design.md +1 -0
- package/slash-commands/validate-ux.md +1 -0
|
@@ -0,0 +1,1391 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Claude Code Ralph Loop with Rate Limiting and Documentation
|
|
4
|
+
# Adaptation of the Ralph technique for Claude Code with usage management
|
|
5
|
+
|
|
6
|
+
set -e # Exit on any error
|
|
7
|
+
|
|
8
|
+
# Source library components
|
|
9
|
+
SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
|
|
10
|
+
source "$SCRIPT_DIR/lib/date_utils.sh"
|
|
11
|
+
source "$SCRIPT_DIR/lib/timeout_utils.sh"
|
|
12
|
+
source "$SCRIPT_DIR/lib/response_analyzer.sh"
|
|
13
|
+
source "$SCRIPT_DIR/lib/circuit_breaker.sh"
|
|
14
|
+
|
|
15
|
+
# Configuration
|
|
16
|
+
# Ralph-specific files live in .ralph/ subfolder
|
|
17
|
+
RALPH_DIR=".ralph"
|
|
18
|
+
PROMPT_FILE="$RALPH_DIR/PROMPT.md"
|
|
19
|
+
LOG_DIR="$RALPH_DIR/logs"
|
|
20
|
+
DOCS_DIR="$RALPH_DIR/docs/generated"
|
|
21
|
+
STATUS_FILE="$RALPH_DIR/status.json"
|
|
22
|
+
PROGRESS_FILE="$RALPH_DIR/progress.json"
|
|
23
|
+
CLAUDE_CODE_CMD="claude"
|
|
24
|
+
MAX_CALLS_PER_HOUR=100 # Adjust based on your plan
|
|
25
|
+
VERBOSE_PROGRESS=false # Default: no verbose progress updates
|
|
26
|
+
CLAUDE_TIMEOUT_MINUTES=20 # Default: 20 minutes timeout for Claude Code execution
|
|
27
|
+
SLEEP_DURATION=3600 # 1 hour in seconds
|
|
28
|
+
CALL_COUNT_FILE="$RALPH_DIR/.call_count"
|
|
29
|
+
TIMESTAMP_FILE="$RALPH_DIR/.last_reset"
|
|
30
|
+
USE_TMUX=false
|
|
31
|
+
|
|
32
|
+
# Modern Claude CLI configuration (Phase 1.1)
|
|
33
|
+
CLAUDE_OUTPUT_FORMAT="json" # Options: json, text
|
|
34
|
+
CLAUDE_ALLOWED_TOOLS="Write,Bash(git *),Read" # Comma-separated list of allowed tools
|
|
35
|
+
CLAUDE_USE_CONTINUE=true # Enable session continuity
|
|
36
|
+
CLAUDE_SESSION_FILE="$RALPH_DIR/.claude_session_id" # Session ID persistence file
|
|
37
|
+
CLAUDE_MIN_VERSION="2.0.76" # Minimum required Claude CLI version
|
|
38
|
+
|
|
39
|
+
# Session management configuration (Phase 1.2)
|
|
40
|
+
# Note: SESSION_EXPIRATION_SECONDS is defined in lib/response_analyzer.sh (86400 = 24 hours)
|
|
41
|
+
RALPH_SESSION_FILE="$RALPH_DIR/.ralph_session" # Ralph-specific session tracking (lifecycle)
|
|
42
|
+
RALPH_SESSION_HISTORY_FILE="$RALPH_DIR/.ralph_session_history" # Session transition history
|
|
43
|
+
# Session expiration: 24 hours default balances project continuity with fresh context
|
|
44
|
+
# Too short = frequent context loss; Too long = stale context causes unpredictable behavior
|
|
45
|
+
CLAUDE_SESSION_EXPIRY_HOURS=${CLAUDE_SESSION_EXPIRY_HOURS:-24}
|
|
46
|
+
|
|
47
|
+
# Valid tool patterns for --allowed-tools validation
|
|
48
|
+
# Tools can be exact matches or pattern matches with wildcards in parentheses
|
|
49
|
+
VALID_TOOL_PATTERNS=(
|
|
50
|
+
"Write"
|
|
51
|
+
"Read"
|
|
52
|
+
"Edit"
|
|
53
|
+
"MultiEdit"
|
|
54
|
+
"Glob"
|
|
55
|
+
"Grep"
|
|
56
|
+
"Task"
|
|
57
|
+
"TodoWrite"
|
|
58
|
+
"WebFetch"
|
|
59
|
+
"WebSearch"
|
|
60
|
+
"Bash"
|
|
61
|
+
"Bash(git *)"
|
|
62
|
+
"Bash(npm *)"
|
|
63
|
+
"Bash(bats *)"
|
|
64
|
+
"Bash(python *)"
|
|
65
|
+
"Bash(node *)"
|
|
66
|
+
"NotebookEdit"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Exit detection configuration
|
|
70
|
+
EXIT_SIGNALS_FILE="$RALPH_DIR/.exit_signals"
|
|
71
|
+
RESPONSE_ANALYSIS_FILE="$RALPH_DIR/.response_analysis"
|
|
72
|
+
MAX_CONSECUTIVE_TEST_LOOPS=3
|
|
73
|
+
MAX_CONSECUTIVE_DONE_SIGNALS=2
|
|
74
|
+
TEST_PERCENTAGE_THRESHOLD=30 # If more than 30% of recent loops are test-only, flag it
|
|
75
|
+
|
|
76
|
+
# Colors for terminal output
|
|
77
|
+
RED='\033[0;31m'
|
|
78
|
+
GREEN='\033[0;32m'
|
|
79
|
+
YELLOW='\033[1;33m'
|
|
80
|
+
BLUE='\033[0;34m'
|
|
81
|
+
PURPLE='\033[0;35m'
|
|
82
|
+
NC='\033[0m' # No Color
|
|
83
|
+
|
|
84
|
+
# Initialize directories
|
|
85
|
+
mkdir -p "$LOG_DIR" "$DOCS_DIR"
|
|
86
|
+
|
|
87
|
+
# Check if tmux is available
|
|
88
|
+
check_tmux_available() {
|
|
89
|
+
if ! command -v tmux &> /dev/null; then
|
|
90
|
+
log_status "ERROR" "tmux is not installed. Please install tmux or run without --monitor flag."
|
|
91
|
+
echo "Install tmux:"
|
|
92
|
+
echo " Ubuntu/Debian: sudo apt-get install tmux"
|
|
93
|
+
echo " macOS: brew install tmux"
|
|
94
|
+
echo " CentOS/RHEL: sudo yum install tmux"
|
|
95
|
+
exit 1
|
|
96
|
+
fi
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Setup tmux session with monitor
|
|
100
|
+
setup_tmux_session() {
|
|
101
|
+
local session_name="ralph-$(date +%s)"
|
|
102
|
+
local ralph_home="${RALPH_HOME:-$HOME/.ralph}"
|
|
103
|
+
|
|
104
|
+
log_status "INFO" "Setting up tmux session: $session_name"
|
|
105
|
+
|
|
106
|
+
# Create new tmux session detached
|
|
107
|
+
tmux new-session -d -s "$session_name" -c "$(pwd)"
|
|
108
|
+
|
|
109
|
+
# Split window vertically to create monitor pane on the right
|
|
110
|
+
tmux split-window -h -t "$session_name" -c "$(pwd)"
|
|
111
|
+
|
|
112
|
+
# Start monitor in the right pane
|
|
113
|
+
if command -v ralph-monitor &> /dev/null; then
|
|
114
|
+
tmux send-keys -t "$session_name:0.1" "ralph-monitor" Enter
|
|
115
|
+
else
|
|
116
|
+
tmux send-keys -t "$session_name:0.1" "'$ralph_home/ralph_monitor.sh'" Enter
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# Start ralph loop in the left pane (exclude tmux flag to avoid recursion)
|
|
120
|
+
local ralph_cmd
|
|
121
|
+
if command -v ralph &> /dev/null; then
|
|
122
|
+
ralph_cmd="ralph"
|
|
123
|
+
else
|
|
124
|
+
ralph_cmd="'$ralph_home/ralph_loop.sh'"
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
if [[ "$MAX_CALLS_PER_HOUR" != "100" ]]; then
|
|
128
|
+
ralph_cmd="$ralph_cmd --calls $MAX_CALLS_PER_HOUR"
|
|
129
|
+
fi
|
|
130
|
+
if [[ "$PROMPT_FILE" != "$RALPH_DIR/PROMPT.md" ]]; then
|
|
131
|
+
ralph_cmd="$ralph_cmd --prompt '$PROMPT_FILE'"
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
tmux send-keys -t "$session_name:0.0" "$ralph_cmd" Enter
|
|
135
|
+
|
|
136
|
+
# Focus on left pane (main ralph loop)
|
|
137
|
+
tmux select-pane -t "$session_name:0.0"
|
|
138
|
+
|
|
139
|
+
# Set window title
|
|
140
|
+
tmux rename-window -t "$session_name:0" "Ralph: Loop | Monitor"
|
|
141
|
+
|
|
142
|
+
log_status "SUCCESS" "Tmux session created. Attaching to session..."
|
|
143
|
+
log_status "INFO" "Use Ctrl+B then D to detach from session"
|
|
144
|
+
log_status "INFO" "Use 'tmux attach -t $session_name' to reattach"
|
|
145
|
+
|
|
146
|
+
# Attach to session (this will block until session ends)
|
|
147
|
+
tmux attach-session -t "$session_name"
|
|
148
|
+
|
|
149
|
+
exit 0
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
# Initialize call tracking
|
|
153
|
+
init_call_tracking() {
|
|
154
|
+
log_status "INFO" "DEBUG: Entered init_call_tracking..."
|
|
155
|
+
local current_hour=$(date +%Y%m%d%H)
|
|
156
|
+
local last_reset_hour=""
|
|
157
|
+
|
|
158
|
+
if [[ -f "$TIMESTAMP_FILE" ]]; then
|
|
159
|
+
last_reset_hour=$(cat "$TIMESTAMP_FILE")
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
# Reset counter if it's a new hour
|
|
163
|
+
if [[ "$current_hour" != "$last_reset_hour" ]]; then
|
|
164
|
+
echo "0" > "$CALL_COUNT_FILE"
|
|
165
|
+
echo "$current_hour" > "$TIMESTAMP_FILE"
|
|
166
|
+
log_status "INFO" "Call counter reset for new hour: $current_hour"
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
# Initialize exit signals tracking if it doesn't exist
|
|
170
|
+
if [[ ! -f "$EXIT_SIGNALS_FILE" ]]; then
|
|
171
|
+
echo '{"test_only_loops": [], "done_signals": [], "completion_indicators": []}' > "$EXIT_SIGNALS_FILE"
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
# Initialize circuit breaker
|
|
175
|
+
init_circuit_breaker
|
|
176
|
+
|
|
177
|
+
log_status "INFO" "DEBUG: Completed init_call_tracking successfully"
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# Log function with timestamps and colors
|
|
181
|
+
log_status() {
|
|
182
|
+
local level=$1
|
|
183
|
+
local message=$2
|
|
184
|
+
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
|
185
|
+
local color=""
|
|
186
|
+
|
|
187
|
+
case $level in
|
|
188
|
+
"INFO") color=$BLUE ;;
|
|
189
|
+
"WARN") color=$YELLOW ;;
|
|
190
|
+
"ERROR") color=$RED ;;
|
|
191
|
+
"SUCCESS") color=$GREEN ;;
|
|
192
|
+
"LOOP") color=$PURPLE ;;
|
|
193
|
+
esac
|
|
194
|
+
|
|
195
|
+
echo -e "${color}[$timestamp] [$level] $message${NC}"
|
|
196
|
+
echo "[$timestamp] [$level] $message" >> "$LOG_DIR/ralph.log"
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
# Update status JSON for external monitoring
|
|
200
|
+
update_status() {
|
|
201
|
+
local loop_count=$1
|
|
202
|
+
local calls_made=$2
|
|
203
|
+
local last_action=$3
|
|
204
|
+
local status=$4
|
|
205
|
+
local exit_reason=${5:-""}
|
|
206
|
+
|
|
207
|
+
cat > "$STATUS_FILE" << STATUSEOF
|
|
208
|
+
{
|
|
209
|
+
"timestamp": "$(get_iso_timestamp)",
|
|
210
|
+
"loop_count": $loop_count,
|
|
211
|
+
"calls_made_this_hour": $calls_made,
|
|
212
|
+
"max_calls_per_hour": $MAX_CALLS_PER_HOUR,
|
|
213
|
+
"last_action": "$last_action",
|
|
214
|
+
"status": "$status",
|
|
215
|
+
"exit_reason": "$exit_reason",
|
|
216
|
+
"next_reset": "$(get_next_hour_time)"
|
|
217
|
+
}
|
|
218
|
+
STATUSEOF
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
# Check if we can make another call
|
|
222
|
+
can_make_call() {
|
|
223
|
+
local calls_made=0
|
|
224
|
+
if [[ -f "$CALL_COUNT_FILE" ]]; then
|
|
225
|
+
calls_made=$(cat "$CALL_COUNT_FILE")
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
if [[ $calls_made -ge $MAX_CALLS_PER_HOUR ]]; then
|
|
229
|
+
return 1 # Cannot make call
|
|
230
|
+
else
|
|
231
|
+
return 0 # Can make call
|
|
232
|
+
fi
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
# Increment call counter
|
|
236
|
+
increment_call_counter() {
|
|
237
|
+
local calls_made=0
|
|
238
|
+
if [[ -f "$CALL_COUNT_FILE" ]]; then
|
|
239
|
+
calls_made=$(cat "$CALL_COUNT_FILE")
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
((calls_made++))
|
|
243
|
+
echo "$calls_made" > "$CALL_COUNT_FILE"
|
|
244
|
+
echo "$calls_made"
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
# Wait for rate limit reset with countdown
|
|
248
|
+
wait_for_reset() {
|
|
249
|
+
local calls_made=$(cat "$CALL_COUNT_FILE" 2>/dev/null || echo "0")
|
|
250
|
+
log_status "WARN" "Rate limit reached ($calls_made/$MAX_CALLS_PER_HOUR). Waiting for reset..."
|
|
251
|
+
|
|
252
|
+
# Calculate time until next hour
|
|
253
|
+
local current_minute=$(date +%M)
|
|
254
|
+
local current_second=$(date +%S)
|
|
255
|
+
local wait_time=$(((60 - current_minute - 1) * 60 + (60 - current_second)))
|
|
256
|
+
|
|
257
|
+
log_status "INFO" "Sleeping for $wait_time seconds until next hour..."
|
|
258
|
+
|
|
259
|
+
# Countdown display
|
|
260
|
+
while [[ $wait_time -gt 0 ]]; do
|
|
261
|
+
local hours=$((wait_time / 3600))
|
|
262
|
+
local minutes=$(((wait_time % 3600) / 60))
|
|
263
|
+
local seconds=$((wait_time % 60))
|
|
264
|
+
|
|
265
|
+
printf "\r${YELLOW}Time until reset: %02d:%02d:%02d${NC}" $hours $minutes $seconds
|
|
266
|
+
sleep 1
|
|
267
|
+
((wait_time--))
|
|
268
|
+
done
|
|
269
|
+
printf "\n"
|
|
270
|
+
|
|
271
|
+
# Reset counter
|
|
272
|
+
echo "0" > "$CALL_COUNT_FILE"
|
|
273
|
+
echo "$(date +%Y%m%d%H)" > "$TIMESTAMP_FILE"
|
|
274
|
+
log_status "SUCCESS" "Rate limit reset! Ready for new calls."
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# Check if we should gracefully exit
|
|
278
|
+
should_exit_gracefully() {
|
|
279
|
+
log_status "INFO" "DEBUG: Checking exit conditions..." >&2
|
|
280
|
+
|
|
281
|
+
if [[ ! -f "$EXIT_SIGNALS_FILE" ]]; then
|
|
282
|
+
log_status "INFO" "DEBUG: No exit signals file found, continuing..." >&2
|
|
283
|
+
return 1 # Don't exit, file doesn't exist
|
|
284
|
+
fi
|
|
285
|
+
|
|
286
|
+
local signals=$(cat "$EXIT_SIGNALS_FILE")
|
|
287
|
+
log_status "INFO" "DEBUG: Exit signals content: $signals" >&2
|
|
288
|
+
|
|
289
|
+
# Count recent signals (last 5 loops) - with error handling
|
|
290
|
+
local recent_test_loops
|
|
291
|
+
local recent_done_signals
|
|
292
|
+
local recent_completion_indicators
|
|
293
|
+
|
|
294
|
+
recent_test_loops=$(echo "$signals" | jq '.test_only_loops | length' 2>/dev/null || echo "0")
|
|
295
|
+
recent_done_signals=$(echo "$signals" | jq '.done_signals | length' 2>/dev/null || echo "0")
|
|
296
|
+
recent_completion_indicators=$(echo "$signals" | jq '.completion_indicators | length' 2>/dev/null || echo "0")
|
|
297
|
+
|
|
298
|
+
log_status "INFO" "DEBUG: Exit counts - test_loops:$recent_test_loops, done_signals:$recent_done_signals, completion:$recent_completion_indicators" >&2
|
|
299
|
+
|
|
300
|
+
# Check for exit conditions
|
|
301
|
+
|
|
302
|
+
# 1. Too many consecutive test-only loops
|
|
303
|
+
if [[ $recent_test_loops -ge $MAX_CONSECUTIVE_TEST_LOOPS ]]; then
|
|
304
|
+
log_status "WARN" "Exit condition: Too many test-focused loops ($recent_test_loops >= $MAX_CONSECUTIVE_TEST_LOOPS)"
|
|
305
|
+
echo "test_saturation"
|
|
306
|
+
return 0
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
# 2. Multiple "done" signals
|
|
310
|
+
if [[ $recent_done_signals -ge $MAX_CONSECUTIVE_DONE_SIGNALS ]]; then
|
|
311
|
+
log_status "WARN" "Exit condition: Multiple completion signals ($recent_done_signals >= $MAX_CONSECUTIVE_DONE_SIGNALS)"
|
|
312
|
+
echo "completion_signals"
|
|
313
|
+
return 0
|
|
314
|
+
fi
|
|
315
|
+
|
|
316
|
+
# 3. Safety circuit breaker - force exit after 5 consecutive completion indicators
|
|
317
|
+
# Bug #2 Fix: Prevents infinite loops when EXIT_SIGNAL is not explicitly set
|
|
318
|
+
# but completion patterns clearly indicate work is done. Threshold of 5 is higher
|
|
319
|
+
# than normal threshold (2) to avoid false positives while preventing API waste.
|
|
320
|
+
if [[ $recent_completion_indicators -ge 5 ]]; then
|
|
321
|
+
log_status "WARN" "🚨 SAFETY CIRCUIT BREAKER: Force exit after 5 consecutive completion indicators ($recent_completion_indicators)" >&2
|
|
322
|
+
echo "safety_circuit_breaker"
|
|
323
|
+
return 0
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
# 4. Strong completion indicators (only if Claude's EXIT_SIGNAL is true)
|
|
327
|
+
# This prevents premature exits when heuristics detect completion patterns
|
|
328
|
+
# but Claude explicitly indicates work is still in progress via RALPH_STATUS block.
|
|
329
|
+
# The exit_signal in .response_analysis represents Claude's explicit intent.
|
|
330
|
+
local claude_exit_signal="false"
|
|
331
|
+
if [[ -f "$RESPONSE_ANALYSIS_FILE" ]]; then
|
|
332
|
+
claude_exit_signal=$(jq -r '.analysis.exit_signal // false' "$RESPONSE_ANALYSIS_FILE" 2>/dev/null || echo "false")
|
|
333
|
+
fi
|
|
334
|
+
|
|
335
|
+
if [[ $recent_completion_indicators -ge 2 ]] && [[ "$claude_exit_signal" == "true" ]]; then
|
|
336
|
+
log_status "WARN" "Exit condition: Strong completion indicators ($recent_completion_indicators) with EXIT_SIGNAL=true" >&2
|
|
337
|
+
echo "project_complete"
|
|
338
|
+
return 0
|
|
339
|
+
elif [[ $recent_completion_indicators -ge 2 ]]; then
|
|
340
|
+
log_status "INFO" "DEBUG: Completion indicators ($recent_completion_indicators) present but EXIT_SIGNAL=false, continuing..." >&2
|
|
341
|
+
fi
|
|
342
|
+
|
|
343
|
+
# 5. Check fix_plan.md for completion
|
|
344
|
+
# Bug #3 Fix: Support indented markdown checkboxes with [[:space:]]* pattern
|
|
345
|
+
if [[ -f "$RALPH_DIR/@fix_plan.md" ]]; then
|
|
346
|
+
local total_items=$(grep -cE "^[[:space:]]*- \[" "$RALPH_DIR/@fix_plan.md" 2>/dev/null)
|
|
347
|
+
local completed_items=$(grep -cE "^[[:space:]]*- \[x\]" "$RALPH_DIR/@fix_plan.md" 2>/dev/null)
|
|
348
|
+
|
|
349
|
+
# Handle case where grep returns no matches (exit code 1)
|
|
350
|
+
[[ -z "$total_items" ]] && total_items=0
|
|
351
|
+
[[ -z "$completed_items" ]] && completed_items=0
|
|
352
|
+
|
|
353
|
+
log_status "INFO" "DEBUG: .ralph/@fix_plan.md check - total_items:$total_items, completed_items:$completed_items" >&2
|
|
354
|
+
|
|
355
|
+
if [[ $total_items -gt 0 ]] && [[ $completed_items -eq $total_items ]]; then
|
|
356
|
+
log_status "WARN" "Exit condition: All fix_plan.md items completed ($completed_items/$total_items)" >&2
|
|
357
|
+
echo "plan_complete"
|
|
358
|
+
return 0
|
|
359
|
+
fi
|
|
360
|
+
else
|
|
361
|
+
log_status "INFO" "DEBUG: .ralph/@fix_plan.md file not found" >&2
|
|
362
|
+
fi
|
|
363
|
+
|
|
364
|
+
log_status "INFO" "DEBUG: No exit conditions met, continuing loop" >&2
|
|
365
|
+
echo "" # Return empty string instead of using return code
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
# =============================================================================
|
|
369
|
+
# MODERN CLI HELPER FUNCTIONS (Phase 1.1)
|
|
370
|
+
# =============================================================================
|
|
371
|
+
|
|
372
|
+
# Check Claude CLI version for compatibility with modern flags
|
|
373
|
+
check_claude_version() {
|
|
374
|
+
local version=$($CLAUDE_CODE_CMD --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
|
375
|
+
|
|
376
|
+
if [[ -z "$version" ]]; then
|
|
377
|
+
log_status "WARN" "Cannot detect Claude CLI version, assuming compatible"
|
|
378
|
+
return 0
|
|
379
|
+
fi
|
|
380
|
+
|
|
381
|
+
# Compare versions (simplified semver comparison)
|
|
382
|
+
local required="$CLAUDE_MIN_VERSION"
|
|
383
|
+
|
|
384
|
+
# Convert to comparable integers (major * 10000 + minor * 100 + patch)
|
|
385
|
+
local ver_parts=(${version//./ })
|
|
386
|
+
local req_parts=(${required//./ })
|
|
387
|
+
|
|
388
|
+
local ver_num=$((${ver_parts[0]:-0} * 10000 + ${ver_parts[1]:-0} * 100 + ${ver_parts[2]:-0}))
|
|
389
|
+
local req_num=$((${req_parts[0]:-0} * 10000 + ${req_parts[1]:-0} * 100 + ${req_parts[2]:-0}))
|
|
390
|
+
|
|
391
|
+
if [[ $ver_num -lt $req_num ]]; then
|
|
392
|
+
log_status "WARN" "Claude CLI version $version < $required. Some modern features may not work."
|
|
393
|
+
log_status "WARN" "Consider upgrading: npm update -g @anthropic-ai/claude-code"
|
|
394
|
+
return 1
|
|
395
|
+
fi
|
|
396
|
+
|
|
397
|
+
log_status "INFO" "Claude CLI version $version (>= $required) - modern features enabled"
|
|
398
|
+
return 0
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
# Validate allowed tools against whitelist
|
|
402
|
+
# Returns 0 if valid, 1 if invalid with error message
|
|
403
|
+
validate_allowed_tools() {
|
|
404
|
+
local tools_input=$1
|
|
405
|
+
|
|
406
|
+
if [[ -z "$tools_input" ]]; then
|
|
407
|
+
return 0 # Empty is valid (uses defaults)
|
|
408
|
+
fi
|
|
409
|
+
|
|
410
|
+
# Split by comma
|
|
411
|
+
local IFS=','
|
|
412
|
+
read -ra tools <<< "$tools_input"
|
|
413
|
+
|
|
414
|
+
for tool in "${tools[@]}"; do
|
|
415
|
+
# Trim whitespace
|
|
416
|
+
tool=$(echo "$tool" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
417
|
+
|
|
418
|
+
if [[ -z "$tool" ]]; then
|
|
419
|
+
continue
|
|
420
|
+
fi
|
|
421
|
+
|
|
422
|
+
local valid=false
|
|
423
|
+
|
|
424
|
+
# Check against valid patterns
|
|
425
|
+
for pattern in "${VALID_TOOL_PATTERNS[@]}"; do
|
|
426
|
+
if [[ "$tool" == "$pattern" ]]; then
|
|
427
|
+
valid=true
|
|
428
|
+
break
|
|
429
|
+
fi
|
|
430
|
+
|
|
431
|
+
# Check for Bash(*) pattern - any Bash with parentheses is allowed
|
|
432
|
+
if [[ "$tool" =~ ^Bash\(.+\)$ ]]; then
|
|
433
|
+
valid=true
|
|
434
|
+
break
|
|
435
|
+
fi
|
|
436
|
+
done
|
|
437
|
+
|
|
438
|
+
if [[ "$valid" == "false" ]]; then
|
|
439
|
+
echo "Error: Invalid tool in --allowed-tools: '$tool'"
|
|
440
|
+
echo "Valid tools: ${VALID_TOOL_PATTERNS[*]}"
|
|
441
|
+
echo "Note: Bash(...) patterns with any content are allowed (e.g., 'Bash(git *)')"
|
|
442
|
+
return 1
|
|
443
|
+
fi
|
|
444
|
+
done
|
|
445
|
+
|
|
446
|
+
return 0
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
# Build loop context for Claude Code session
|
|
450
|
+
# Provides loop-specific context via --append-system-prompt
|
|
451
|
+
build_loop_context() {
|
|
452
|
+
local loop_count=$1
|
|
453
|
+
local context=""
|
|
454
|
+
|
|
455
|
+
# Add loop number
|
|
456
|
+
context="Loop #${loop_count}. "
|
|
457
|
+
|
|
458
|
+
# Extract incomplete tasks from @fix_plan.md
|
|
459
|
+
# Bug #3 Fix: Support indented markdown checkboxes with [[:space:]]* pattern
|
|
460
|
+
if [[ -f "$RALPH_DIR/@fix_plan.md" ]]; then
|
|
461
|
+
local incomplete_tasks=$(grep -cE "^[[:space:]]*- \[ \]" "$RALPH_DIR/@fix_plan.md" 2>/dev/null || echo "0")
|
|
462
|
+
context+="Remaining tasks: ${incomplete_tasks}. "
|
|
463
|
+
fi
|
|
464
|
+
|
|
465
|
+
# Add circuit breaker state
|
|
466
|
+
if [[ -f "$RALPH_DIR/.circuit_breaker_state" ]]; then
|
|
467
|
+
local cb_state=$(jq -r '.state // "UNKNOWN"' "$RALPH_DIR/.circuit_breaker_state" 2>/dev/null)
|
|
468
|
+
if [[ "$cb_state" != "CLOSED" && "$cb_state" != "null" && -n "$cb_state" ]]; then
|
|
469
|
+
context+="Circuit breaker: ${cb_state}. "
|
|
470
|
+
fi
|
|
471
|
+
fi
|
|
472
|
+
|
|
473
|
+
# Add previous loop summary (truncated)
|
|
474
|
+
if [[ -f "$RESPONSE_ANALYSIS_FILE" ]]; then
|
|
475
|
+
local prev_summary=$(jq -r '.analysis.work_summary // ""' "$RESPONSE_ANALYSIS_FILE" 2>/dev/null | head -c 200)
|
|
476
|
+
if [[ -n "$prev_summary" && "$prev_summary" != "null" ]]; then
|
|
477
|
+
context+="Previous: ${prev_summary}"
|
|
478
|
+
fi
|
|
479
|
+
fi
|
|
480
|
+
|
|
481
|
+
# Limit total length to ~500 chars
|
|
482
|
+
echo "${context:0:500}"
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
# Get session file age in hours (cross-platform)
|
|
486
|
+
# Returns: age in hours on stdout, or -1 if stat fails
|
|
487
|
+
# Note: Returns 0 for files less than 1 hour old
|
|
488
|
+
get_session_file_age_hours() {
|
|
489
|
+
local file=$1
|
|
490
|
+
|
|
491
|
+
if [[ ! -f "$file" ]]; then
|
|
492
|
+
echo "0"
|
|
493
|
+
return
|
|
494
|
+
fi
|
|
495
|
+
|
|
496
|
+
local os_type
|
|
497
|
+
os_type=$(uname)
|
|
498
|
+
|
|
499
|
+
local file_mtime
|
|
500
|
+
if [[ "$os_type" == "Darwin" ]]; then
|
|
501
|
+
# macOS (BSD stat)
|
|
502
|
+
file_mtime=$(stat -f %m "$file" 2>/dev/null)
|
|
503
|
+
else
|
|
504
|
+
# Linux (GNU stat)
|
|
505
|
+
file_mtime=$(stat -c %Y "$file" 2>/dev/null)
|
|
506
|
+
fi
|
|
507
|
+
|
|
508
|
+
# Handle stat failure - return -1 to indicate error
|
|
509
|
+
# This prevents false expiration when stat fails
|
|
510
|
+
if [[ -z "$file_mtime" || "$file_mtime" == "0" ]]; then
|
|
511
|
+
echo "-1"
|
|
512
|
+
return
|
|
513
|
+
fi
|
|
514
|
+
|
|
515
|
+
local current_time
|
|
516
|
+
current_time=$(date +%s)
|
|
517
|
+
|
|
518
|
+
local age_seconds=$((current_time - file_mtime))
|
|
519
|
+
local age_hours=$((age_seconds / 3600))
|
|
520
|
+
|
|
521
|
+
echo "$age_hours"
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
# Initialize or resume Claude session (with expiration check)
|
|
525
|
+
#
|
|
526
|
+
# Session Expiration Strategy:
|
|
527
|
+
# - Default expiration: 24 hours (configurable via CLAUDE_SESSION_EXPIRY_HOURS)
|
|
528
|
+
# - 24 hours chosen because: long enough for multi-day projects, short enough
|
|
529
|
+
# to prevent stale context from causing unpredictable behavior
|
|
530
|
+
# - Sessions auto-expire to ensure Claude starts fresh periodically
|
|
531
|
+
#
|
|
532
|
+
# Returns (stdout):
|
|
533
|
+
# - Session ID string: when resuming a valid, non-expired session
|
|
534
|
+
# - Empty string: when starting new session (no file, expired, or stat error)
|
|
535
|
+
#
|
|
536
|
+
# Return codes:
|
|
537
|
+
# - 0: Always returns success (caller should check stdout for session ID)
|
|
538
|
+
#
|
|
539
|
+
init_claude_session() {
|
|
540
|
+
if [[ -f "$CLAUDE_SESSION_FILE" ]]; then
|
|
541
|
+
# Check session age
|
|
542
|
+
local age_hours
|
|
543
|
+
age_hours=$(get_session_file_age_hours "$CLAUDE_SESSION_FILE")
|
|
544
|
+
|
|
545
|
+
# Handle stat failure (-1) - treat as needing new session
|
|
546
|
+
# Don't expire sessions when we can't determine age
|
|
547
|
+
if [[ $age_hours -eq -1 ]]; then
|
|
548
|
+
log_status "WARN" "Could not determine session age, starting new session"
|
|
549
|
+
rm -f "$CLAUDE_SESSION_FILE"
|
|
550
|
+
echo ""
|
|
551
|
+
return 0
|
|
552
|
+
fi
|
|
553
|
+
|
|
554
|
+
# Check if session has expired
|
|
555
|
+
if [[ $age_hours -ge $CLAUDE_SESSION_EXPIRY_HOURS ]]; then
|
|
556
|
+
log_status "INFO" "Session expired (${age_hours}h old, max ${CLAUDE_SESSION_EXPIRY_HOURS}h), starting new session"
|
|
557
|
+
rm -f "$CLAUDE_SESSION_FILE"
|
|
558
|
+
echo ""
|
|
559
|
+
return 0
|
|
560
|
+
fi
|
|
561
|
+
|
|
562
|
+
# Session is valid, try to read it
|
|
563
|
+
local session_id=$(cat "$CLAUDE_SESSION_FILE" 2>/dev/null)
|
|
564
|
+
if [[ -n "$session_id" ]]; then
|
|
565
|
+
log_status "INFO" "Resuming Claude session: ${session_id:0:20}... (${age_hours}h old)"
|
|
566
|
+
echo "$session_id"
|
|
567
|
+
return 0
|
|
568
|
+
fi
|
|
569
|
+
fi
|
|
570
|
+
|
|
571
|
+
log_status "INFO" "Starting new Claude session"
|
|
572
|
+
echo ""
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
# Save session ID after successful execution
|
|
576
|
+
save_claude_session() {
|
|
577
|
+
local output_file=$1
|
|
578
|
+
|
|
579
|
+
# Try to extract session ID from JSON output
|
|
580
|
+
if [[ -f "$output_file" ]]; then
|
|
581
|
+
local session_id=$(jq -r '.metadata.session_id // .session_id // empty' "$output_file" 2>/dev/null)
|
|
582
|
+
if [[ -n "$session_id" && "$session_id" != "null" ]]; then
|
|
583
|
+
echo "$session_id" > "$CLAUDE_SESSION_FILE"
|
|
584
|
+
log_status "INFO" "Saved Claude session: ${session_id:0:20}..."
|
|
585
|
+
fi
|
|
586
|
+
fi
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
# =============================================================================
|
|
590
|
+
# SESSION LIFECYCLE MANAGEMENT FUNCTIONS (Phase 1.2)
|
|
591
|
+
# =============================================================================
|
|
592
|
+
|
|
593
|
+
# Get current session ID from Ralph session file
|
|
594
|
+
# Returns: session ID string or empty if not found
|
|
595
|
+
get_session_id() {
|
|
596
|
+
if [[ ! -f "$RALPH_SESSION_FILE" ]]; then
|
|
597
|
+
echo ""
|
|
598
|
+
return 0
|
|
599
|
+
fi
|
|
600
|
+
|
|
601
|
+
# Extract session_id from JSON file (SC2155: separate declare from assign)
|
|
602
|
+
local session_id
|
|
603
|
+
session_id=$(jq -r '.session_id // ""' "$RALPH_SESSION_FILE" 2>/dev/null)
|
|
604
|
+
local jq_status=$?
|
|
605
|
+
|
|
606
|
+
# Handle jq failure or null/empty results
|
|
607
|
+
if [[ $jq_status -ne 0 || -z "$session_id" || "$session_id" == "null" ]]; then
|
|
608
|
+
session_id=""
|
|
609
|
+
fi
|
|
610
|
+
echo "$session_id"
|
|
611
|
+
return 0
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
# Reset session with reason logging
|
|
615
|
+
# Usage: reset_session "reason_for_reset"
|
|
616
|
+
reset_session() {
|
|
617
|
+
local reason=${1:-"manual_reset"}
|
|
618
|
+
|
|
619
|
+
# Get current timestamp
|
|
620
|
+
local reset_timestamp
|
|
621
|
+
reset_timestamp=$(get_iso_timestamp)
|
|
622
|
+
|
|
623
|
+
# Always create/overwrite the session file using jq for safe JSON escaping
|
|
624
|
+
jq -n \
|
|
625
|
+
--arg session_id "" \
|
|
626
|
+
--arg created_at "" \
|
|
627
|
+
--arg last_used "" \
|
|
628
|
+
--arg reset_at "$reset_timestamp" \
|
|
629
|
+
--arg reset_reason "$reason" \
|
|
630
|
+
'{
|
|
631
|
+
session_id: $session_id,
|
|
632
|
+
created_at: $created_at,
|
|
633
|
+
last_used: $last_used,
|
|
634
|
+
reset_at: $reset_at,
|
|
635
|
+
reset_reason: $reset_reason
|
|
636
|
+
}' > "$RALPH_SESSION_FILE"
|
|
637
|
+
|
|
638
|
+
# Also clear the Claude session file for consistency
|
|
639
|
+
rm -f "$CLAUDE_SESSION_FILE" 2>/dev/null
|
|
640
|
+
|
|
641
|
+
# Clear exit signals to prevent stale completion indicators from causing premature exit (issue #91)
|
|
642
|
+
# This ensures a fresh start without leftover state from previous sessions
|
|
643
|
+
if [[ -f "$EXIT_SIGNALS_FILE" ]]; then
|
|
644
|
+
echo '{"test_only_loops": [], "done_signals": [], "completion_indicators": []}' > "$EXIT_SIGNALS_FILE"
|
|
645
|
+
[[ "${VERBOSE_PROGRESS:-}" == "true" ]] && log_status "INFO" "Cleared exit signals file"
|
|
646
|
+
fi
|
|
647
|
+
|
|
648
|
+
# Clear response analysis to prevent stale EXIT_SIGNAL from previous session
|
|
649
|
+
rm -f "$RESPONSE_ANALYSIS_FILE" 2>/dev/null
|
|
650
|
+
|
|
651
|
+
# Log the session transition (non-fatal to prevent script exit under set -e)
|
|
652
|
+
log_session_transition "active" "reset" "$reason" "${loop_count:-0}" || true
|
|
653
|
+
|
|
654
|
+
log_status "INFO" "Session reset: $reason"
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
# Log session state transitions to history file
|
|
658
|
+
# Usage: log_session_transition from_state to_state reason loop_number
|
|
659
|
+
log_session_transition() {
|
|
660
|
+
local from_state=$1
|
|
661
|
+
local to_state=$2
|
|
662
|
+
local reason=$3
|
|
663
|
+
local loop_number=${4:-0}
|
|
664
|
+
|
|
665
|
+
# Get timestamp once (SC2155: separate declare from assign)
|
|
666
|
+
local ts
|
|
667
|
+
ts=$(get_iso_timestamp)
|
|
668
|
+
|
|
669
|
+
# Create transition entry using jq for safe JSON (SC2155: separate declare from assign)
|
|
670
|
+
local transition
|
|
671
|
+
transition=$(jq -n -c \
|
|
672
|
+
--arg timestamp "$ts" \
|
|
673
|
+
--arg from_state "$from_state" \
|
|
674
|
+
--arg to_state "$to_state" \
|
|
675
|
+
--arg reason "$reason" \
|
|
676
|
+
--argjson loop_number "$loop_number" \
|
|
677
|
+
'{
|
|
678
|
+
timestamp: $timestamp,
|
|
679
|
+
from_state: $from_state,
|
|
680
|
+
to_state: $to_state,
|
|
681
|
+
reason: $reason,
|
|
682
|
+
loop_number: $loop_number
|
|
683
|
+
}')
|
|
684
|
+
|
|
685
|
+
# Read history file defensively - fallback to empty array on any failure
|
|
686
|
+
local history
|
|
687
|
+
if [[ -f "$RALPH_SESSION_HISTORY_FILE" ]]; then
|
|
688
|
+
history=$(cat "$RALPH_SESSION_HISTORY_FILE" 2>/dev/null)
|
|
689
|
+
# Validate JSON, fallback to empty array if corrupted
|
|
690
|
+
if ! echo "$history" | jq empty 2>/dev/null; then
|
|
691
|
+
history='[]'
|
|
692
|
+
fi
|
|
693
|
+
else
|
|
694
|
+
history='[]'
|
|
695
|
+
fi
|
|
696
|
+
|
|
697
|
+
# Append transition and keep only last 50 entries
|
|
698
|
+
local updated_history
|
|
699
|
+
updated_history=$(echo "$history" | jq ". += [$transition] | .[-50:]" 2>/dev/null)
|
|
700
|
+
local jq_status=$?
|
|
701
|
+
|
|
702
|
+
# Only write if jq succeeded
|
|
703
|
+
if [[ $jq_status -eq 0 && -n "$updated_history" ]]; then
|
|
704
|
+
echo "$updated_history" > "$RALPH_SESSION_HISTORY_FILE"
|
|
705
|
+
else
|
|
706
|
+
# Fallback: start fresh with just this transition
|
|
707
|
+
echo "[$transition]" > "$RALPH_SESSION_HISTORY_FILE"
|
|
708
|
+
fi
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
# Generate a unique session ID using timestamp and random component
|
|
712
|
+
generate_session_id() {
|
|
713
|
+
local ts
|
|
714
|
+
ts=$(date +%s)
|
|
715
|
+
local rand
|
|
716
|
+
rand=$RANDOM
|
|
717
|
+
echo "ralph-${ts}-${rand}"
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
# Initialize session tracking (called at loop start)
|
|
721
|
+
init_session_tracking() {
|
|
722
|
+
local ts
|
|
723
|
+
ts=$(get_iso_timestamp)
|
|
724
|
+
|
|
725
|
+
# Create session file if it doesn't exist
|
|
726
|
+
if [[ ! -f "$RALPH_SESSION_FILE" ]]; then
|
|
727
|
+
local new_session_id
|
|
728
|
+
new_session_id=$(generate_session_id)
|
|
729
|
+
|
|
730
|
+
jq -n \
|
|
731
|
+
--arg session_id "$new_session_id" \
|
|
732
|
+
--arg created_at "$ts" \
|
|
733
|
+
--arg last_used "$ts" \
|
|
734
|
+
--arg reset_at "" \
|
|
735
|
+
--arg reset_reason "" \
|
|
736
|
+
'{
|
|
737
|
+
session_id: $session_id,
|
|
738
|
+
created_at: $created_at,
|
|
739
|
+
last_used: $last_used,
|
|
740
|
+
reset_at: $reset_at,
|
|
741
|
+
reset_reason: $reset_reason
|
|
742
|
+
}' > "$RALPH_SESSION_FILE"
|
|
743
|
+
|
|
744
|
+
log_status "INFO" "Initialized session tracking (session: $new_session_id)"
|
|
745
|
+
return 0
|
|
746
|
+
fi
|
|
747
|
+
|
|
748
|
+
# Validate existing session file
|
|
749
|
+
if ! jq empty "$RALPH_SESSION_FILE" 2>/dev/null; then
|
|
750
|
+
log_status "WARN" "Corrupted session file detected, recreating..."
|
|
751
|
+
local new_session_id
|
|
752
|
+
new_session_id=$(generate_session_id)
|
|
753
|
+
|
|
754
|
+
jq -n \
|
|
755
|
+
--arg session_id "$new_session_id" \
|
|
756
|
+
--arg created_at "$ts" \
|
|
757
|
+
--arg last_used "$ts" \
|
|
758
|
+
--arg reset_at "$ts" \
|
|
759
|
+
--arg reset_reason "corrupted_file_recovery" \
|
|
760
|
+
'{
|
|
761
|
+
session_id: $session_id,
|
|
762
|
+
created_at: $created_at,
|
|
763
|
+
last_used: $last_used,
|
|
764
|
+
reset_at: $reset_at,
|
|
765
|
+
reset_reason: $reset_reason
|
|
766
|
+
}' > "$RALPH_SESSION_FILE"
|
|
767
|
+
fi
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
# Update last_used timestamp in session file (called on each loop iteration)
|
|
771
|
+
update_session_last_used() {
|
|
772
|
+
if [[ ! -f "$RALPH_SESSION_FILE" ]]; then
|
|
773
|
+
return 0
|
|
774
|
+
fi
|
|
775
|
+
|
|
776
|
+
local ts
|
|
777
|
+
ts=$(get_iso_timestamp)
|
|
778
|
+
|
|
779
|
+
# Update last_used in existing session file
|
|
780
|
+
local updated
|
|
781
|
+
updated=$(jq --arg last_used "$ts" '.last_used = $last_used' "$RALPH_SESSION_FILE" 2>/dev/null)
|
|
782
|
+
local jq_status=$?
|
|
783
|
+
|
|
784
|
+
if [[ $jq_status -eq 0 && -n "$updated" ]]; then
|
|
785
|
+
echo "$updated" > "$RALPH_SESSION_FILE"
|
|
786
|
+
fi
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
# Global array for Claude command arguments (avoids shell injection)
|
|
790
|
+
declare -a CLAUDE_CMD_ARGS=()
|
|
791
|
+
|
|
792
|
+
# Build Claude CLI command with modern flags using array (shell-injection safe)
|
|
793
|
+
# Populates global CLAUDE_CMD_ARGS array for direct execution
|
|
794
|
+
# Uses -p flag with prompt content (Claude CLI does not have --prompt-file)
|
|
795
|
+
build_claude_command() {
|
|
796
|
+
local prompt_file=$1
|
|
797
|
+
local loop_context=$2
|
|
798
|
+
local session_id=$3
|
|
799
|
+
|
|
800
|
+
# Reset global array
|
|
801
|
+
CLAUDE_CMD_ARGS=("$CLAUDE_CODE_CMD")
|
|
802
|
+
|
|
803
|
+
# Check if prompt file exists
|
|
804
|
+
if [[ ! -f "$prompt_file" ]]; then
|
|
805
|
+
log_status "ERROR" "Prompt file not found: $prompt_file"
|
|
806
|
+
return 1
|
|
807
|
+
fi
|
|
808
|
+
|
|
809
|
+
# Add output format flag
|
|
810
|
+
if [[ "$CLAUDE_OUTPUT_FORMAT" == "json" ]]; then
|
|
811
|
+
CLAUDE_CMD_ARGS+=("--output-format" "json")
|
|
812
|
+
fi
|
|
813
|
+
|
|
814
|
+
# Add allowed tools (each tool as separate array element)
|
|
815
|
+
if [[ -n "$CLAUDE_ALLOWED_TOOLS" ]]; then
|
|
816
|
+
CLAUDE_CMD_ARGS+=("--allowedTools")
|
|
817
|
+
# Split by comma and add each tool
|
|
818
|
+
local IFS=','
|
|
819
|
+
read -ra tools_array <<< "$CLAUDE_ALLOWED_TOOLS"
|
|
820
|
+
for tool in "${tools_array[@]}"; do
|
|
821
|
+
# Trim whitespace
|
|
822
|
+
tool=$(echo "$tool" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
823
|
+
if [[ -n "$tool" ]]; then
|
|
824
|
+
CLAUDE_CMD_ARGS+=("$tool")
|
|
825
|
+
fi
|
|
826
|
+
done
|
|
827
|
+
fi
|
|
828
|
+
|
|
829
|
+
# Add session continuity flag
|
|
830
|
+
if [[ "$CLAUDE_USE_CONTINUE" == "true" ]]; then
|
|
831
|
+
CLAUDE_CMD_ARGS+=("--continue")
|
|
832
|
+
fi
|
|
833
|
+
|
|
834
|
+
# Add loop context as system prompt (no escaping needed - array handles it)
|
|
835
|
+
if [[ -n "$loop_context" ]]; then
|
|
836
|
+
CLAUDE_CMD_ARGS+=("--append-system-prompt" "$loop_context")
|
|
837
|
+
fi
|
|
838
|
+
|
|
839
|
+
# Read prompt file content and use -p flag
|
|
840
|
+
# Note: Claude CLI uses -p for prompts, not --prompt-file (which doesn't exist)
|
|
841
|
+
# Array-based approach maintains shell injection safety
|
|
842
|
+
local prompt_content
|
|
843
|
+
prompt_content=$(cat "$prompt_file")
|
|
844
|
+
CLAUDE_CMD_ARGS+=("-p" "$prompt_content")
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
# Main execution function
|
|
848
|
+
execute_claude_code() {
|
|
849
|
+
local timestamp=$(date '+%Y-%m-%d_%H-%M-%S')
|
|
850
|
+
local output_file="$LOG_DIR/claude_output_${timestamp}.log"
|
|
851
|
+
local loop_count=$1
|
|
852
|
+
local calls_made=$(cat "$CALL_COUNT_FILE" 2>/dev/null || echo "0")
|
|
853
|
+
calls_made=$((calls_made + 1))
|
|
854
|
+
|
|
855
|
+
log_status "LOOP" "Executing Claude Code (Call $calls_made/$MAX_CALLS_PER_HOUR)"
|
|
856
|
+
local timeout_seconds=$((CLAUDE_TIMEOUT_MINUTES * 60))
|
|
857
|
+
log_status "INFO" "⏳ Starting Claude Code execution... (timeout: ${CLAUDE_TIMEOUT_MINUTES}m)"
|
|
858
|
+
|
|
859
|
+
# Build loop context for session continuity
|
|
860
|
+
local loop_context=""
|
|
861
|
+
if [[ "$CLAUDE_USE_CONTINUE" == "true" ]]; then
|
|
862
|
+
loop_context=$(build_loop_context "$loop_count")
|
|
863
|
+
if [[ -n "$loop_context" && "$VERBOSE_PROGRESS" == "true" ]]; then
|
|
864
|
+
log_status "INFO" "Loop context: $loop_context"
|
|
865
|
+
fi
|
|
866
|
+
fi
|
|
867
|
+
|
|
868
|
+
# Initialize or resume session
|
|
869
|
+
local session_id=""
|
|
870
|
+
if [[ "$CLAUDE_USE_CONTINUE" == "true" ]]; then
|
|
871
|
+
session_id=$(init_claude_session)
|
|
872
|
+
fi
|
|
873
|
+
|
|
874
|
+
# Build the Claude CLI command with modern flags
|
|
875
|
+
# Note: We use the modern CLI with -p flag when CLAUDE_OUTPUT_FORMAT is "json"
|
|
876
|
+
# For backward compatibility, fall back to stdin piping for text mode
|
|
877
|
+
local use_modern_cli=false
|
|
878
|
+
|
|
879
|
+
if [[ "$CLAUDE_OUTPUT_FORMAT" == "json" ]]; then
|
|
880
|
+
# Modern approach: use CLI flags (builds CLAUDE_CMD_ARGS array)
|
|
881
|
+
if build_claude_command "$PROMPT_FILE" "$loop_context" "$session_id"; then
|
|
882
|
+
use_modern_cli=true
|
|
883
|
+
log_status "INFO" "Using modern CLI mode (JSON output)"
|
|
884
|
+
else
|
|
885
|
+
log_status "WARN" "Failed to build modern CLI command, falling back to legacy mode"
|
|
886
|
+
fi
|
|
887
|
+
else
|
|
888
|
+
log_status "INFO" "Using legacy CLI mode (text output)"
|
|
889
|
+
fi
|
|
890
|
+
|
|
891
|
+
# Execute Claude Code
|
|
892
|
+
if [[ "$use_modern_cli" == "true" ]]; then
|
|
893
|
+
# Modern execution with command array (shell-injection safe)
|
|
894
|
+
# Execute array directly without bash -c to prevent shell metacharacter interpretation
|
|
895
|
+
if portable_timeout ${timeout_seconds}s "${CLAUDE_CMD_ARGS[@]}" > "$output_file" 2>&1 &
|
|
896
|
+
then
|
|
897
|
+
: # Continue to wait loop
|
|
898
|
+
else
|
|
899
|
+
log_status "ERROR" "❌ Failed to start Claude Code process (modern mode)"
|
|
900
|
+
# Fall back to legacy mode
|
|
901
|
+
log_status "INFO" "Falling back to legacy mode..."
|
|
902
|
+
use_modern_cli=false
|
|
903
|
+
fi
|
|
904
|
+
fi
|
|
905
|
+
|
|
906
|
+
# Fall back to legacy stdin piping if modern mode failed or not enabled
|
|
907
|
+
if [[ "$use_modern_cli" == "false" ]]; then
|
|
908
|
+
if portable_timeout ${timeout_seconds}s $CLAUDE_CODE_CMD < "$PROMPT_FILE" > "$output_file" 2>&1 &
|
|
909
|
+
then
|
|
910
|
+
: # Continue to wait loop
|
|
911
|
+
else
|
|
912
|
+
log_status "ERROR" "❌ Failed to start Claude Code process"
|
|
913
|
+
return 1
|
|
914
|
+
fi
|
|
915
|
+
fi
|
|
916
|
+
|
|
917
|
+
# Get PID and monitor progress
|
|
918
|
+
local claude_pid=$!
|
|
919
|
+
local progress_counter=0
|
|
920
|
+
|
|
921
|
+
# Show progress while Claude Code is running
|
|
922
|
+
while kill -0 $claude_pid 2>/dev/null; do
|
|
923
|
+
progress_counter=$((progress_counter + 1))
|
|
924
|
+
case $((progress_counter % 4)) in
|
|
925
|
+
1) progress_indicator="⠋" ;;
|
|
926
|
+
2) progress_indicator="⠙" ;;
|
|
927
|
+
3) progress_indicator="⠹" ;;
|
|
928
|
+
0) progress_indicator="⠸" ;;
|
|
929
|
+
esac
|
|
930
|
+
|
|
931
|
+
# Get last line from output if available
|
|
932
|
+
local last_line=""
|
|
933
|
+
if [[ -f "$output_file" && -s "$output_file" ]]; then
|
|
934
|
+
last_line=$(tail -1 "$output_file" 2>/dev/null | head -c 80)
|
|
935
|
+
fi
|
|
936
|
+
|
|
937
|
+
# Update progress file for monitor
|
|
938
|
+
cat > "$PROGRESS_FILE" << EOF
|
|
939
|
+
{
|
|
940
|
+
"status": "executing",
|
|
941
|
+
"indicator": "$progress_indicator",
|
|
942
|
+
"elapsed_seconds": $((progress_counter * 10)),
|
|
943
|
+
"last_output": "$last_line",
|
|
944
|
+
"timestamp": "$(date '+%Y-%m-%d %H:%M:%S')"
|
|
945
|
+
}
|
|
946
|
+
EOF
|
|
947
|
+
|
|
948
|
+
# Only log if verbose mode is enabled
|
|
949
|
+
if [[ "$VERBOSE_PROGRESS" == "true" ]]; then
|
|
950
|
+
if [[ -n "$last_line" ]]; then
|
|
951
|
+
log_status "INFO" "$progress_indicator Claude Code: $last_line... (${progress_counter}0s)"
|
|
952
|
+
else
|
|
953
|
+
log_status "INFO" "$progress_indicator Claude Code working... (${progress_counter}0s elapsed)"
|
|
954
|
+
fi
|
|
955
|
+
fi
|
|
956
|
+
|
|
957
|
+
sleep 10
|
|
958
|
+
done
|
|
959
|
+
|
|
960
|
+
# Wait for the process to finish and get exit code
|
|
961
|
+
# Use || to prevent set -e from killing the script on non-zero (e.g. timeout 124)
|
|
962
|
+
local exit_code=0
|
|
963
|
+
wait $claude_pid || exit_code=$?
|
|
964
|
+
|
|
965
|
+
if [ $exit_code -eq 124 ]; then
|
|
966
|
+
# Timeout - not a failure, just took too long
|
|
967
|
+
log_status "WARN" "⏰ Claude Code timed out after ${CLAUDE_TIMEOUT_MINUTES} minutes"
|
|
968
|
+
log_status "INFO" "Session will be reset and retried in next loop"
|
|
969
|
+
rm -f "$CLAUDE_SESSION_FILE"
|
|
970
|
+
echo '{"status": "timeout", "timestamp": "'$(date '+%Y-%m-%d %H:%M:%S')'"}' > "$PROGRESS_FILE"
|
|
971
|
+
return 4 # Specific return code for timeout
|
|
972
|
+
elif [ $exit_code -eq 0 ]; then
|
|
973
|
+
# Only increment counter on successful execution
|
|
974
|
+
echo "$calls_made" > "$CALL_COUNT_FILE"
|
|
975
|
+
|
|
976
|
+
# Clear progress file
|
|
977
|
+
echo '{"status": "completed", "timestamp": "'$(date '+%Y-%m-%d %H:%M:%S')'"}' > "$PROGRESS_FILE"
|
|
978
|
+
|
|
979
|
+
log_status "SUCCESS" "✅ Claude Code execution completed successfully"
|
|
980
|
+
|
|
981
|
+
# Save session ID from JSON output (Phase 1.1)
|
|
982
|
+
if [[ "$CLAUDE_USE_CONTINUE" == "true" ]]; then
|
|
983
|
+
save_claude_session "$output_file"
|
|
984
|
+
fi
|
|
985
|
+
|
|
986
|
+
# Analyze the response
|
|
987
|
+
log_status "INFO" "🔍 Analyzing Claude Code response..."
|
|
988
|
+
analyze_response "$output_file" "$loop_count"
|
|
989
|
+
local analysis_exit_code=$?
|
|
990
|
+
|
|
991
|
+
# Update exit signals based on analysis
|
|
992
|
+
update_exit_signals
|
|
993
|
+
|
|
994
|
+
# Log analysis summary
|
|
995
|
+
log_analysis_summary
|
|
996
|
+
|
|
997
|
+
# Get file change count for circuit breaker
|
|
998
|
+
local files_changed=$(git diff --name-only 2>/dev/null | wc -l || echo 0)
|
|
999
|
+
local has_errors="false"
|
|
1000
|
+
|
|
1001
|
+
# Two-stage error detection to avoid JSON field false positives
|
|
1002
|
+
# Stage 1: Filter out JSON field patterns like "is_error": false
|
|
1003
|
+
# Stage 2: Look for actual error messages in specific contexts
|
|
1004
|
+
# Avoid type annotations like "error: Error" by requiring lowercase after ": error"
|
|
1005
|
+
if grep -v '"[^"]*error[^"]*":' "$output_file" 2>/dev/null | \
|
|
1006
|
+
grep -qE '(^Error:|^ERROR:|^error:|\]: error|Link: error|Error occurred|failed with error|[Ee]xception|Fatal|FATAL)'; then
|
|
1007
|
+
has_errors="true"
|
|
1008
|
+
|
|
1009
|
+
# Debug logging: show what triggered error detection
|
|
1010
|
+
if [[ "$VERBOSE_PROGRESS" == "true" ]]; then
|
|
1011
|
+
log_status "DEBUG" "Error patterns found:"
|
|
1012
|
+
grep -v '"[^"]*error[^"]*":' "$output_file" 2>/dev/null | \
|
|
1013
|
+
grep -nE '(^Error:|^ERROR:|^error:|\]: error|Link: error|Error occurred|failed with error|[Ee]xception|Fatal|FATAL)' | \
|
|
1014
|
+
head -3 | while IFS= read -r line; do
|
|
1015
|
+
log_status "DEBUG" " $line"
|
|
1016
|
+
done
|
|
1017
|
+
fi
|
|
1018
|
+
|
|
1019
|
+
log_status "WARN" "Errors detected in output, check: $output_file"
|
|
1020
|
+
fi
|
|
1021
|
+
local output_length=$(wc -c < "$output_file" 2>/dev/null || echo 0)
|
|
1022
|
+
|
|
1023
|
+
# Record result in circuit breaker
|
|
1024
|
+
record_loop_result "$loop_count" "$files_changed" "$has_errors" "$output_length"
|
|
1025
|
+
local circuit_result=$?
|
|
1026
|
+
|
|
1027
|
+
if [[ $circuit_result -ne 0 ]]; then
|
|
1028
|
+
log_status "WARN" "Circuit breaker opened - halting execution"
|
|
1029
|
+
return 3 # Special code for circuit breaker trip
|
|
1030
|
+
fi
|
|
1031
|
+
|
|
1032
|
+
return 0
|
|
1033
|
+
else
|
|
1034
|
+
# Clear progress file on failure
|
|
1035
|
+
echo '{"status": "failed", "timestamp": "'$(date '+%Y-%m-%d %H:%M:%S')'"}' > "$PROGRESS_FILE"
|
|
1036
|
+
|
|
1037
|
+
# Check if the failure is due to API 5-hour limit
|
|
1038
|
+
if grep -qi "5.*hour.*limit\|limit.*reached.*try.*back\|usage.*limit.*reached" "$output_file"; then
|
|
1039
|
+
log_status "ERROR" "🚫 Claude API 5-hour usage limit reached"
|
|
1040
|
+
return 2 # Special return code for API limit
|
|
1041
|
+
else
|
|
1042
|
+
log_status "ERROR" "❌ Claude Code execution failed, check: $output_file"
|
|
1043
|
+
return 1
|
|
1044
|
+
fi
|
|
1045
|
+
fi
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
# Cleanup function
|
|
1049
|
+
cleanup() {
|
|
1050
|
+
log_status "INFO" "Ralph loop interrupted. Cleaning up..."
|
|
1051
|
+
reset_session "manual_interrupt"
|
|
1052
|
+
update_status "$loop_count" "$(cat "$CALL_COUNT_FILE" 2>/dev/null || echo "0")" "interrupted" "stopped"
|
|
1053
|
+
exit 0
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
# Set up signal handlers
|
|
1057
|
+
trap cleanup SIGINT SIGTERM
|
|
1058
|
+
|
|
1059
|
+
# Global variable for loop count (needed by cleanup function)
|
|
1060
|
+
loop_count=0
|
|
1061
|
+
|
|
1062
|
+
# Main loop
|
|
1063
|
+
main() {
|
|
1064
|
+
|
|
1065
|
+
log_status "SUCCESS" "🚀 Ralph loop starting with Claude Code"
|
|
1066
|
+
log_status "INFO" "Max calls per hour: $MAX_CALLS_PER_HOUR"
|
|
1067
|
+
log_status "INFO" "Logs: $LOG_DIR/ | Docs: $DOCS_DIR/ | Status: $STATUS_FILE"
|
|
1068
|
+
|
|
1069
|
+
# Check if project uses old flat structure and needs migration
|
|
1070
|
+
if [[ -f "PROMPT.md" ]] && [[ ! -d ".ralph" ]]; then
|
|
1071
|
+
log_status "ERROR" "This project uses the old flat structure."
|
|
1072
|
+
echo ""
|
|
1073
|
+
echo "Ralph v0.10.0+ uses a .ralph/ subfolder to keep your project root clean."
|
|
1074
|
+
echo ""
|
|
1075
|
+
echo "To upgrade your project, run:"
|
|
1076
|
+
echo " ralph-migrate"
|
|
1077
|
+
echo ""
|
|
1078
|
+
echo "This will move Ralph-specific files to .ralph/ while preserving src/ at root."
|
|
1079
|
+
echo "A backup will be created before migration."
|
|
1080
|
+
exit 1
|
|
1081
|
+
fi
|
|
1082
|
+
|
|
1083
|
+
# Check if this is a Ralph project directory
|
|
1084
|
+
if [[ ! -f "$PROMPT_FILE" ]]; then
|
|
1085
|
+
log_status "ERROR" "Prompt file '$PROMPT_FILE' not found!"
|
|
1086
|
+
echo ""
|
|
1087
|
+
|
|
1088
|
+
# Check if this looks like a partial Ralph project
|
|
1089
|
+
if [[ -f "$RALPH_DIR/@fix_plan.md" ]] || [[ -d "$RALPH_DIR/specs" ]] || [[ -f "$RALPH_DIR/@AGENT.md" ]]; then
|
|
1090
|
+
echo "This appears to be a Ralph project but is missing .ralph/PROMPT.md."
|
|
1091
|
+
echo "You may need to create or restore the PROMPT.md file."
|
|
1092
|
+
else
|
|
1093
|
+
echo "This directory is not a Ralph project."
|
|
1094
|
+
fi
|
|
1095
|
+
|
|
1096
|
+
echo ""
|
|
1097
|
+
echo "To fix this:"
|
|
1098
|
+
echo " 1. Create a new project: ralph-setup my-project"
|
|
1099
|
+
echo " 2. Import existing requirements: ralph-import requirements.md"
|
|
1100
|
+
echo " 3. Navigate to an existing Ralph project directory"
|
|
1101
|
+
echo " 4. Or create .ralph/PROMPT.md manually in this directory"
|
|
1102
|
+
echo ""
|
|
1103
|
+
echo "Ralph projects should contain: .ralph/PROMPT.md, .ralph/@fix_plan.md, .ralph/specs/, src/, etc."
|
|
1104
|
+
exit 1
|
|
1105
|
+
fi
|
|
1106
|
+
|
|
1107
|
+
# Initialize session tracking before entering the loop
|
|
1108
|
+
init_session_tracking
|
|
1109
|
+
|
|
1110
|
+
log_status "INFO" "Starting main loop..."
|
|
1111
|
+
log_status "INFO" "DEBUG: About to enter while loop, loop_count=$loop_count"
|
|
1112
|
+
|
|
1113
|
+
while true; do
|
|
1114
|
+
loop_count=$((loop_count + 1))
|
|
1115
|
+
log_status "INFO" "DEBUG: Successfully incremented loop_count to $loop_count"
|
|
1116
|
+
|
|
1117
|
+
# Update session last_used timestamp
|
|
1118
|
+
update_session_last_used
|
|
1119
|
+
|
|
1120
|
+
log_status "INFO" "Loop #$loop_count - calling init_call_tracking..."
|
|
1121
|
+
init_call_tracking
|
|
1122
|
+
|
|
1123
|
+
log_status "LOOP" "=== Starting Loop #$loop_count ==="
|
|
1124
|
+
|
|
1125
|
+
# Check circuit breaker before attempting execution
|
|
1126
|
+
if should_halt_execution; then
|
|
1127
|
+
reset_session "circuit_breaker_open"
|
|
1128
|
+
update_status "$loop_count" "$(cat "$CALL_COUNT_FILE")" "circuit_breaker_open" "halted" "stagnation_detected"
|
|
1129
|
+
log_status "ERROR" "🛑 Circuit breaker has opened - execution halted"
|
|
1130
|
+
break
|
|
1131
|
+
fi
|
|
1132
|
+
|
|
1133
|
+
# Check rate limits
|
|
1134
|
+
if ! can_make_call; then
|
|
1135
|
+
wait_for_reset
|
|
1136
|
+
continue
|
|
1137
|
+
fi
|
|
1138
|
+
|
|
1139
|
+
# Check for graceful exit conditions
|
|
1140
|
+
local exit_reason=$(should_exit_gracefully)
|
|
1141
|
+
if [[ "$exit_reason" != "" ]]; then
|
|
1142
|
+
log_status "SUCCESS" "🏁 Graceful exit triggered: $exit_reason"
|
|
1143
|
+
reset_session "project_complete"
|
|
1144
|
+
update_status "$loop_count" "$(cat "$CALL_COUNT_FILE")" "graceful_exit" "completed" "$exit_reason"
|
|
1145
|
+
|
|
1146
|
+
log_status "SUCCESS" "🎉 Ralph has completed the project! Final stats:"
|
|
1147
|
+
log_status "INFO" " - Total loops: $loop_count"
|
|
1148
|
+
log_status "INFO" " - API calls used: $(cat "$CALL_COUNT_FILE")"
|
|
1149
|
+
log_status "INFO" " - Exit reason: $exit_reason"
|
|
1150
|
+
|
|
1151
|
+
break
|
|
1152
|
+
fi
|
|
1153
|
+
|
|
1154
|
+
# Update status
|
|
1155
|
+
local calls_made=$(cat "$CALL_COUNT_FILE" 2>/dev/null || echo "0")
|
|
1156
|
+
update_status "$loop_count" "$calls_made" "executing" "running"
|
|
1157
|
+
|
|
1158
|
+
# Execute Claude Code
|
|
1159
|
+
local exec_result=0
|
|
1160
|
+
execute_claude_code "$loop_count" || exec_result=$?
|
|
1161
|
+
|
|
1162
|
+
if [ $exec_result -eq 0 ]; then
|
|
1163
|
+
update_status "$loop_count" "$(cat "$CALL_COUNT_FILE")" "completed" "success"
|
|
1164
|
+
|
|
1165
|
+
# Brief pause between successful executions
|
|
1166
|
+
sleep 5
|
|
1167
|
+
elif [ $exec_result -eq 3 ]; then
|
|
1168
|
+
# Circuit breaker opened
|
|
1169
|
+
reset_session "circuit_breaker_trip"
|
|
1170
|
+
update_status "$loop_count" "$(cat "$CALL_COUNT_FILE")" "circuit_breaker_open" "halted" "stagnation_detected"
|
|
1171
|
+
log_status "ERROR" "🛑 Circuit breaker has opened - halting loop"
|
|
1172
|
+
log_status "INFO" "Run 'ralph --reset-circuit' to reset the circuit breaker after addressing issues"
|
|
1173
|
+
break
|
|
1174
|
+
elif [ $exec_result -eq 2 ]; then
|
|
1175
|
+
# API 5-hour limit reached - handle specially
|
|
1176
|
+
update_status "$loop_count" "$(cat "$CALL_COUNT_FILE")" "api_limit" "paused"
|
|
1177
|
+
log_status "WARN" "🛑 Claude API 5-hour limit reached!"
|
|
1178
|
+
|
|
1179
|
+
# Ask user whether to wait or exit
|
|
1180
|
+
echo -e "\n${YELLOW}The Claude API 5-hour usage limit has been reached.${NC}"
|
|
1181
|
+
echo -e "${YELLOW}You can either:${NC}"
|
|
1182
|
+
echo -e " ${GREEN}1)${NC} Wait for the limit to reset (usually within an hour)"
|
|
1183
|
+
echo -e " ${GREEN}2)${NC} Exit the loop and try again later"
|
|
1184
|
+
echo -e "\n${BLUE}Choose an option (1 or 2):${NC} "
|
|
1185
|
+
|
|
1186
|
+
# Read user input with timeout
|
|
1187
|
+
read -t 30 -n 1 user_choice
|
|
1188
|
+
echo # New line after input
|
|
1189
|
+
|
|
1190
|
+
if [[ "$user_choice" == "2" ]] || [[ -z "$user_choice" ]]; then
|
|
1191
|
+
log_status "INFO" "User chose to exit (or timed out). Exiting loop..."
|
|
1192
|
+
update_status "$loop_count" "$(cat "$CALL_COUNT_FILE")" "api_limit_exit" "stopped" "api_5hour_limit"
|
|
1193
|
+
break
|
|
1194
|
+
else
|
|
1195
|
+
log_status "INFO" "User chose to wait. Waiting for API limit reset..."
|
|
1196
|
+
# Wait for longer period when API limit is hit
|
|
1197
|
+
local wait_minutes=60
|
|
1198
|
+
log_status "INFO" "Waiting $wait_minutes minutes before retrying..."
|
|
1199
|
+
|
|
1200
|
+
# Countdown display
|
|
1201
|
+
local wait_seconds=$((wait_minutes * 60))
|
|
1202
|
+
while [[ $wait_seconds -gt 0 ]]; do
|
|
1203
|
+
local minutes=$((wait_seconds / 60))
|
|
1204
|
+
local seconds=$((wait_seconds % 60))
|
|
1205
|
+
printf "\r${YELLOW}Time until retry: %02d:%02d${NC}" $minutes $seconds
|
|
1206
|
+
sleep 1
|
|
1207
|
+
((wait_seconds--))
|
|
1208
|
+
done
|
|
1209
|
+
printf "\n"
|
|
1210
|
+
fi
|
|
1211
|
+
elif [ $exec_result -eq 4 ]; then
|
|
1212
|
+
# Timeout - non-fatal, session already reset in execute_claude_code
|
|
1213
|
+
update_status "$loop_count" "$(cat "$CALL_COUNT_FILE")" "timeout" "retrying"
|
|
1214
|
+
log_status "INFO" "Retrying with fresh session in 10 seconds..."
|
|
1215
|
+
sleep 10
|
|
1216
|
+
else
|
|
1217
|
+
update_status "$loop_count" "$(cat "$CALL_COUNT_FILE")" "failed" "error"
|
|
1218
|
+
log_status "WARN" "Execution failed, waiting 30 seconds before retry..."
|
|
1219
|
+
sleep 30
|
|
1220
|
+
fi
|
|
1221
|
+
|
|
1222
|
+
log_status "LOOP" "=== Completed Loop #$loop_count ==="
|
|
1223
|
+
done
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
# Help function
|
|
1227
|
+
show_help() {
|
|
1228
|
+
cat << HELPEOF
|
|
1229
|
+
Ralph Loop for Claude Code
|
|
1230
|
+
|
|
1231
|
+
Usage: $0 [OPTIONS]
|
|
1232
|
+
|
|
1233
|
+
IMPORTANT: This command must be run from a Ralph project directory.
|
|
1234
|
+
Use 'ralph-setup project-name' to create a new project first.
|
|
1235
|
+
|
|
1236
|
+
Options:
|
|
1237
|
+
-h, --help Show this help message
|
|
1238
|
+
-c, --calls NUM Set max calls per hour (default: $MAX_CALLS_PER_HOUR)
|
|
1239
|
+
-p, --prompt FILE Set prompt file (default: $PROMPT_FILE)
|
|
1240
|
+
-s, --status Show current status and exit
|
|
1241
|
+
-m, --monitor Start with tmux session and live monitor (requires tmux)
|
|
1242
|
+
-v, --verbose Show detailed progress updates during execution
|
|
1243
|
+
-t, --timeout MIN Set Claude Code execution timeout in minutes (default: $CLAUDE_TIMEOUT_MINUTES)
|
|
1244
|
+
--reset-circuit Reset circuit breaker to CLOSED state
|
|
1245
|
+
--circuit-status Show circuit breaker status and exit
|
|
1246
|
+
--reset-session Reset session state and exit (clears session continuity)
|
|
1247
|
+
|
|
1248
|
+
Modern CLI Options (Phase 1.1):
|
|
1249
|
+
--output-format FORMAT Set Claude output format: json or text (default: $CLAUDE_OUTPUT_FORMAT)
|
|
1250
|
+
--allowed-tools TOOLS Comma-separated list of allowed tools (default: $CLAUDE_ALLOWED_TOOLS)
|
|
1251
|
+
--no-continue Disable session continuity across loops
|
|
1252
|
+
--session-expiry HOURS Set session expiration time in hours (default: $CLAUDE_SESSION_EXPIRY_HOURS)
|
|
1253
|
+
|
|
1254
|
+
Files created:
|
|
1255
|
+
- $LOG_DIR/: All execution logs
|
|
1256
|
+
- $DOCS_DIR/: Generated documentation
|
|
1257
|
+
- $STATUS_FILE: Current status (JSON)
|
|
1258
|
+
- .ralph/.ralph_session: Session lifecycle tracking
|
|
1259
|
+
- .ralph/.ralph_session_history: Session transition history (last 50)
|
|
1260
|
+
- .ralph/.call_count: API call counter for rate limiting
|
|
1261
|
+
- .ralph/.last_reset: Timestamp of last rate limit reset
|
|
1262
|
+
|
|
1263
|
+
Example workflow:
|
|
1264
|
+
ralph-setup my-project # Create project
|
|
1265
|
+
cd my-project # Enter project directory
|
|
1266
|
+
$0 --monitor # Start Ralph with monitoring
|
|
1267
|
+
|
|
1268
|
+
Examples:
|
|
1269
|
+
$0 --calls 50 --prompt my_prompt.md
|
|
1270
|
+
$0 --monitor # Start with integrated tmux monitoring
|
|
1271
|
+
$0 --monitor --timeout 30 # 30-minute timeout for complex tasks
|
|
1272
|
+
$0 --verbose --timeout 5 # 5-minute timeout with detailed progress
|
|
1273
|
+
$0 --output-format text # Use legacy text output format
|
|
1274
|
+
$0 --no-continue # Disable session continuity
|
|
1275
|
+
$0 --session-expiry 48 # 48-hour session expiration
|
|
1276
|
+
|
|
1277
|
+
HELPEOF
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
# Parse command line arguments
|
|
1281
|
+
while [[ $# -gt 0 ]]; do
|
|
1282
|
+
case $1 in
|
|
1283
|
+
-h|--help)
|
|
1284
|
+
show_help
|
|
1285
|
+
exit 0
|
|
1286
|
+
;;
|
|
1287
|
+
-c|--calls)
|
|
1288
|
+
MAX_CALLS_PER_HOUR="$2"
|
|
1289
|
+
shift 2
|
|
1290
|
+
;;
|
|
1291
|
+
-p|--prompt)
|
|
1292
|
+
PROMPT_FILE="$2"
|
|
1293
|
+
shift 2
|
|
1294
|
+
;;
|
|
1295
|
+
-s|--status)
|
|
1296
|
+
if [[ -f "$STATUS_FILE" ]]; then
|
|
1297
|
+
echo "Current Status:"
|
|
1298
|
+
cat "$STATUS_FILE" | jq . 2>/dev/null || cat "$STATUS_FILE"
|
|
1299
|
+
else
|
|
1300
|
+
echo "No status file found. Ralph may not be running."
|
|
1301
|
+
fi
|
|
1302
|
+
exit 0
|
|
1303
|
+
;;
|
|
1304
|
+
-m|--monitor)
|
|
1305
|
+
USE_TMUX=true
|
|
1306
|
+
shift
|
|
1307
|
+
;;
|
|
1308
|
+
-v|--verbose)
|
|
1309
|
+
VERBOSE_PROGRESS=true
|
|
1310
|
+
shift
|
|
1311
|
+
;;
|
|
1312
|
+
-t|--timeout)
|
|
1313
|
+
if [[ "$2" =~ ^[1-9][0-9]*$ ]] && [[ "$2" -le 120 ]]; then
|
|
1314
|
+
CLAUDE_TIMEOUT_MINUTES="$2"
|
|
1315
|
+
else
|
|
1316
|
+
echo "Error: Timeout must be a positive integer between 1 and 120 minutes"
|
|
1317
|
+
exit 1
|
|
1318
|
+
fi
|
|
1319
|
+
shift 2
|
|
1320
|
+
;;
|
|
1321
|
+
--reset-circuit)
|
|
1322
|
+
# Source the circuit breaker library
|
|
1323
|
+
SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
|
|
1324
|
+
source "$SCRIPT_DIR/lib/circuit_breaker.sh"
|
|
1325
|
+
source "$SCRIPT_DIR/lib/date_utils.sh"
|
|
1326
|
+
reset_circuit_breaker "Manual reset via command line"
|
|
1327
|
+
reset_session "manual_circuit_reset"
|
|
1328
|
+
exit 0
|
|
1329
|
+
;;
|
|
1330
|
+
--reset-session)
|
|
1331
|
+
# Reset session state only
|
|
1332
|
+
SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
|
|
1333
|
+
source "$SCRIPT_DIR/lib/date_utils.sh"
|
|
1334
|
+
reset_session "manual_reset_flag"
|
|
1335
|
+
echo -e "\033[0;32m✅ Session state reset successfully\033[0m"
|
|
1336
|
+
exit 0
|
|
1337
|
+
;;
|
|
1338
|
+
--circuit-status)
|
|
1339
|
+
# Source the circuit breaker library
|
|
1340
|
+
SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
|
|
1341
|
+
source "$SCRIPT_DIR/lib/circuit_breaker.sh"
|
|
1342
|
+
show_circuit_status
|
|
1343
|
+
exit 0
|
|
1344
|
+
;;
|
|
1345
|
+
--output-format)
|
|
1346
|
+
if [[ "$2" == "json" || "$2" == "text" ]]; then
|
|
1347
|
+
CLAUDE_OUTPUT_FORMAT="$2"
|
|
1348
|
+
else
|
|
1349
|
+
echo "Error: --output-format must be 'json' or 'text'"
|
|
1350
|
+
exit 1
|
|
1351
|
+
fi
|
|
1352
|
+
shift 2
|
|
1353
|
+
;;
|
|
1354
|
+
--allowed-tools)
|
|
1355
|
+
if ! validate_allowed_tools "$2"; then
|
|
1356
|
+
exit 1
|
|
1357
|
+
fi
|
|
1358
|
+
CLAUDE_ALLOWED_TOOLS="$2"
|
|
1359
|
+
shift 2
|
|
1360
|
+
;;
|
|
1361
|
+
--no-continue)
|
|
1362
|
+
CLAUDE_USE_CONTINUE=false
|
|
1363
|
+
shift
|
|
1364
|
+
;;
|
|
1365
|
+
--session-expiry)
|
|
1366
|
+
if [[ -z "$2" || ! "$2" =~ ^[1-9][0-9]*$ ]]; then
|
|
1367
|
+
echo "Error: --session-expiry requires a positive integer (hours)"
|
|
1368
|
+
exit 1
|
|
1369
|
+
fi
|
|
1370
|
+
CLAUDE_SESSION_EXPIRY_HOURS="$2"
|
|
1371
|
+
shift 2
|
|
1372
|
+
;;
|
|
1373
|
+
*)
|
|
1374
|
+
echo "Unknown option: $1"
|
|
1375
|
+
show_help
|
|
1376
|
+
exit 1
|
|
1377
|
+
;;
|
|
1378
|
+
esac
|
|
1379
|
+
done
|
|
1380
|
+
|
|
1381
|
+
# Only execute when run directly, not when sourced
|
|
1382
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
1383
|
+
# If tmux mode requested, set it up
|
|
1384
|
+
if [[ "$USE_TMUX" == "true" ]]; then
|
|
1385
|
+
check_tmux_available
|
|
1386
|
+
setup_tmux_session
|
|
1387
|
+
fi
|
|
1388
|
+
|
|
1389
|
+
# Start the main loop
|
|
1390
|
+
main
|
|
1391
|
+
fi
|