chore: scaffold agent harness for Phase 2 eSignature envelope creation
Adds the ralph loop harness artifacts to drive autonomous implementation of Phase 2. State lives in IMPLEMENTATION_PLAN.md so work survives session resets — restart ralph-loop.sh after token refresh and the next agent reads the plan to pick up where it left off. Agent: human Tests: N/A Tests-Added: 0 TypeScript: N/A
This commit is contained in:
parent
c49d127db1
commit
b1c199d21d
|
|
@ -0,0 +1,168 @@
|
||||||
|
# Agent Instructions — Salesforce Appraiser Review Letter
|
||||||
|
|
||||||
|
> Read this file at the start of every iteration. It is the canonical operating guide.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Role
|
||||||
|
|
||||||
|
You are a senior Salesforce developer working autonomously on the appraiser review letter project.
|
||||||
|
You have full access to the codebase and can read, write, and run shell commands.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Loop
|
||||||
|
|
||||||
|
### 1. Orient
|
||||||
|
|
||||||
|
- Read `PROJECT-SPEC.md` — requirements and acceptance criteria for Phase 2
|
||||||
|
- Read `IMPLEMENTATION_PLAN.md` — task list and current status
|
||||||
|
- Read `DECISIONS.md` — architecture decisions already in force
|
||||||
|
- Run `git log --oneline -10` — see what has been done
|
||||||
|
- Check for any prior escalations in `IMPLEMENTATION_PLAN.md`
|
||||||
|
|
||||||
|
### 2. Pick ONE Task
|
||||||
|
|
||||||
|
- Find the first unchecked task `- [ ]` in `IMPLEMENTATION_PLAN.md`
|
||||||
|
- If all tasks are checked, output `<promise>DONE</promise>` and exit
|
||||||
|
- Work only on this one task — do not pick up the next one
|
||||||
|
|
||||||
|
### 3. Implement
|
||||||
|
|
||||||
|
- Follow the existing code patterns in the project
|
||||||
|
- Read the relevant existing classes/LWCs before writing new code
|
||||||
|
- Study the CLM tracking pattern as the model for eSignature tracking
|
||||||
|
|
||||||
|
### 4. Verify — BLOCKING before commit
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Set up the CLI path (system Node v12 is too old — always use nvm Node)
|
||||||
|
NODE=/home/paulh/.nvm/versions/node/v22.22.0/bin/node
|
||||||
|
SF=/home/paulh/.nvm/versions/node/v22.22.0/lib/node_modules/@salesforce/cli/bin/run.js
|
||||||
|
|
||||||
|
# Deploy source to org
|
||||||
|
$NODE $SF project deploy start \
|
||||||
|
--source-dir force-app \
|
||||||
|
--target-org appraiser-dev
|
||||||
|
|
||||||
|
# Run Apex tests (synchronous, human-readable output)
|
||||||
|
$NODE $SF apex run test \
|
||||||
|
--target-org appraiser-dev \
|
||||||
|
--class-names DocusignESignatureServiceTest,CLMAdminServiceTest,AppraiserCasePayloadBuilderTest,CLMDocGenCalloutTest \
|
||||||
|
--result-format human \
|
||||||
|
--synchronous
|
||||||
|
```
|
||||||
|
|
||||||
|
**Do not commit if deploy fails or tests fail.**
|
||||||
|
|
||||||
|
For metadata-only tasks (adding custom fields, no Apex changes): deploy and confirm the object deploys cleanly, but you do not need to run all test classes — run a smoke test against the affected test class only.
|
||||||
|
|
||||||
|
### 5. Commit and Mark Done
|
||||||
|
|
||||||
|
Commit with this exact trailer format:
|
||||||
|
|
||||||
|
```
|
||||||
|
<type>(<scope>): <description>
|
||||||
|
|
||||||
|
<optional body>
|
||||||
|
|
||||||
|
Agent: claude-sonnet-4-6
|
||||||
|
Tests: N/N passing | N/A (metadata-only)
|
||||||
|
Tests-Added: +N | 0
|
||||||
|
TypeScript: N/A (Apex project)
|
||||||
|
```
|
||||||
|
|
||||||
|
Then mark the task `- [x]` in `IMPLEMENTATION_PLAN.md` and commit that update in the same commit.
|
||||||
|
|
||||||
|
### 6. Exit
|
||||||
|
|
||||||
|
Output a brief summary (what you did, test results, what is next) and exit.
|
||||||
|
The loop will restart you with fresh context for the next task.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. **One task per iteration.** Never work on multiple tasks.
|
||||||
|
2. **Read before writing.** Always read the existing class/LWC before modifying it.
|
||||||
|
3. **Follow existing patterns.** The CLM tracking pattern is the model for eSignature tracking.
|
||||||
|
4. **Tests are mandatory for every Apex change.** Add to the existing test classes — do not create new ones unless the spec says to.
|
||||||
|
5. **Never add fields or methods not required by the spec.** Implement what is asked, nothing more.
|
||||||
|
6. **Never modify production data.** All DML in tests uses `@isTest` and `Test.startTest()/stopTest()`.
|
||||||
|
7. **Mock HTTP callouts in tests.** Use `HttpCalloutMock` — same pattern as existing test classes.
|
||||||
|
8. **Deploy before committing.** Never commit code that does not deploy cleanly.
|
||||||
|
9. **Follow DECISIONS.md.** Do not change approaches listed as Accepted without escalating.
|
||||||
|
10. **If stuck, escalate.** Do not invent missing requirements.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Escalation Protocol
|
||||||
|
|
||||||
|
Stop and add this block at the top of `IMPLEMENTATION_PLAN.md`, then output `<promise>STUCK</promise>`:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## ESCALATION REQUIRED
|
||||||
|
- **Task:** [current task]
|
||||||
|
- **Issue:** [what is ambiguous/missing/conflicting]
|
||||||
|
- **What I need:** [specific question or decision]
|
||||||
|
- **What I'd do if I had to guess:** [best guess]
|
||||||
|
```
|
||||||
|
|
||||||
|
Escalate when:
|
||||||
|
- The spec has no FR covering a required decision
|
||||||
|
- A constraint in DECISIONS.md conflicts with the spec
|
||||||
|
- A Salesforce platform limit or API behavior is unclear
|
||||||
|
- A named credential or metadata record is missing from the org
|
||||||
|
- Any task requires deleting or overwriting existing custom fields already in use
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Signals
|
||||||
|
|
||||||
|
- `<promise>PLANNED</promise>` — plan created, ready for build
|
||||||
|
- `<promise>DONE</promise>` — all tasks complete
|
||||||
|
- `<promise>STUCK</promise>` — needs human intervention
|
||||||
|
- `<promise>ERROR</promise>` — unrecoverable error
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
force-app/main/default/
|
||||||
|
├── classes/
|
||||||
|
│ ├── CLMAdminService.cls ← orchestration + persistDocGenResult pattern
|
||||||
|
│ ├── CLMAdminServiceTest.cls ← test pattern to follow
|
||||||
|
│ ├── CLMDocGenCallout.cls
|
||||||
|
│ ├── CLMDocGenCalloutTest.cls
|
||||||
|
│ ├── DocusignESignatureService.cls ← add createEnvelope() here
|
||||||
|
│ ├── DocusignESignatureServiceTest.cls
|
||||||
|
│ ├── AppraiserCasePayloadBuilder.cls
|
||||||
|
│ └── AppraiserCasePayloadBuilderTest.cls
|
||||||
|
├── lwc/
|
||||||
|
│ ├── clmDocGenWorkbench/ ← add Send for Signature button here
|
||||||
|
│ └── docusignEsignWorkbench/ ← read-only browsing (do not modify Phase 2)
|
||||||
|
└── objects/
|
||||||
|
└── Appraiser_Case__c/
|
||||||
|
└── fields/ ← add 5 eSignature tracking fields here
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SF CLI Invocation (Always Use This Pattern)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NODE=/home/paulh/.nvm/versions/node/v22.22.0/bin/node
|
||||||
|
SF=/home/paulh/.nvm/versions/node/v22.22.0/lib/node_modules/@salesforce/cli/bin/run.js
|
||||||
|
$NODE $SF <subcommand> <args>
|
||||||
|
```
|
||||||
|
|
||||||
|
Target org alias: `appraiser-dev`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context Anchor
|
||||||
|
|
||||||
|
- **Phase 1 is complete** — CLM doc generation works end-to-end. Do not modify it.
|
||||||
|
- **Phase 2 goal** — after a CLM doc is generated and attached, enable sending it for eSignature using a DocuSign template (Option A).
|
||||||
|
- **The authoritative plan** is in `NEXT_STEPS_DOCGEN.md` under Phase 2. `IMPLEMENTATION_PLAN.md` is the execution tracker derived from it.
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
# Architecture Decision Records
|
||||||
|
|
||||||
|
> Non-obvious decisions that agents must not undo.
|
||||||
|
> Read during Orient phase every iteration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ADR-001: Option A (template-based envelope) for createEnvelope()
|
||||||
|
**Date:** 2026-04-09
|
||||||
|
**Status:** Accepted
|
||||||
|
|
||||||
|
**Context:**
|
||||||
|
Two approaches were considered for creating a DocuSign eSignature envelope:
|
||||||
|
- Option A: POST with a `templateId` — DocuSign template defines recipients/tabs, Salesforce only passes merge data at send time
|
||||||
|
- Option B: POST with a raw document (download CLM blob, upload as base64) — Salesforce manages all recipient config
|
||||||
|
|
||||||
|
**Decision:**
|
||||||
|
Use Option A. Simpler implementation, no blob download, recipient config lives in the DocuSign template where it belongs.
|
||||||
|
|
||||||
|
**Consequences:**
|
||||||
|
✅ Fewer lines of Apex (no ContentVersion download, no base64 encoding)
|
||||||
|
✅ Recipient/tab configuration lives in DocuSign where it is maintained
|
||||||
|
❌ Requires a matching eSignature template to exist in the DocuSign account
|
||||||
|
❌ If the template does not exist, the API returns a 400 — the operator must set up the template first
|
||||||
|
|
||||||
|
**Alternatives Considered:**
|
||||||
|
Option B was rejected as unnecessary complexity for this proof-of-concept phase.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ADR-002: Named credentials for all DocuSign API calls
|
||||||
|
**Date:** 2026-04-09
|
||||||
|
**Status:** Accepted
|
||||||
|
|
||||||
|
**Context:**
|
||||||
|
All CLM and eSignature API calls must go through Salesforce named credentials. No hardcoded tokens, passwords, or base URLs in Apex.
|
||||||
|
|
||||||
|
**Decision:**
|
||||||
|
Named credential API name is always sourced from `CLM_Account_Setting__mdt`:
|
||||||
|
- CLM calls use `CLM_API_Named_Credential__c`
|
||||||
|
- eSignature calls use `ESignature_Rest_Named_Credential__c`
|
||||||
|
|
||||||
|
The `eSignatureAccountId` is also from `CLM_Account_Setting__mdt.ESignature_Account_Id__c`.
|
||||||
|
|
||||||
|
**Consequences:**
|
||||||
|
✅ No secrets in source code
|
||||||
|
✅ Works with Salesforce's credential management (OAuth token refresh, etc.)
|
||||||
|
❌ Named credentials must be manually configured in each org before the code works
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ADR-003: persistEnvelopeResult follows persistDocGenResult pattern
|
||||||
|
**Date:** 2026-04-09
|
||||||
|
**Status:** Accepted
|
||||||
|
|
||||||
|
**Context:**
|
||||||
|
The project already has `CLMAdminService.persistDocGenResult()` which writes CLM task results back to the case. The eSignature result persistence should follow the same pattern — not a new approach.
|
||||||
|
|
||||||
|
**Decision:**
|
||||||
|
`CLMAdminService.persistEnvelopeResult()` is an `@AuraEnabled` static method that takes structured parameters (not a blob or generic map) and writes specific fields to the case via a SOQL update. Same structure as `persistDocGenResult`.
|
||||||
|
|
||||||
|
**Consequences:**
|
||||||
|
✅ Consistent code pattern, agent can learn from existing method
|
||||||
|
✅ Testable with the same approach as `CLMAdminServiceTest`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ADR-004: eSignature tracking fields belong on Appraiser_Case__c
|
||||||
|
**Date:** 2026-04-09
|
||||||
|
**Status:** Accepted
|
||||||
|
|
||||||
|
**Context:**
|
||||||
|
The case is the central record. CLM tracking fields already live on `Appraiser_Case__c`. The eSignature tracking fields follow the same pattern.
|
||||||
|
|
||||||
|
**Decision:**
|
||||||
|
All five eSignature lifecycle fields go on `Appraiser_Case__c`, not on a child record or a separate object.
|
||||||
|
|
||||||
|
**Consequences:**
|
||||||
|
✅ Single record for operator to view
|
||||||
|
✅ Consistent with existing CLM field placement
|
||||||
|
❌ Only one envelope tracked per case (sufficient for Phase 2)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ADR-005: Operator manually enters template ID in Phase 2
|
||||||
|
**Date:** 2026-04-09
|
||||||
|
**Status:** Accepted
|
||||||
|
|
||||||
|
**Context:**
|
||||||
|
Option: auto-populate the template ID from `CLM_Letter_Definition__mdt` or present a template browser.
|
||||||
|
Both require additional metadata fields or another API call and are out of scope for Phase 2.
|
||||||
|
|
||||||
|
**Decision:**
|
||||||
|
The operator inputs the eSignature template ID directly into a text field in `clmDocGenWorkbench`. No auto-selection, no browser, no additional metadata field.
|
||||||
|
|
||||||
|
**Consequences:**
|
||||||
|
✅ Simpler Phase 2 scope
|
||||||
|
✅ No new metadata record changes needed
|
||||||
|
❌ Operator must know the template ID — acceptable for a proof-of-concept
|
||||||
|
|
||||||
|
**Review after:**
|
||||||
|
Phase 3 could add a `ESignature_Template_Id__c` field to `CLM_Letter_Definition__mdt` and pre-populate the input.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ADR-006: HTTP mocks required for all Apex callout tests
|
||||||
|
**Date:** 2026-04-09
|
||||||
|
**Status:** Accepted
|
||||||
|
|
||||||
|
**Context:**
|
||||||
|
Salesforce requires all HTTP callouts in test classes to use `Test.setMock(HttpCalloutMock.class, ...)`. The existing test classes all follow this pattern.
|
||||||
|
|
||||||
|
**Decision:**
|
||||||
|
All tests for `createEnvelope()` must mock the HTTP response using an inner `MockHttpResponse` class implementing `HttpCalloutMock`. Do not attempt live callouts in test methods.
|
||||||
|
|
||||||
|
**Consequences:**
|
||||||
|
✅ Tests run in any Salesforce org without external connectivity
|
||||||
|
✅ Consistent with existing test class patterns (see `CLMDocGenCalloutTest`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Decisions don't drift if they're written down._
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Implementation Plan — Phase 2: eSignature Envelope Creation
|
||||||
|
|
||||||
|
> This is the live execution tracker. The agent reads and updates this every iteration.
|
||||||
|
> Check off tasks as they are completed (`- [x]`).
|
||||||
|
> Add `ESCALATION REQUIRED` block at the top if stuck.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
- [ ] **Task 1 — Custom fields:** Add 5 eSignature tracking fields to `Appraiser_Case__c` object metadata and update `package.xml` if needed. Deploy to verify. (FR-001)
|
||||||
|
|
||||||
|
- [ ] **Task 2 — EnvelopeCreateResult class + createEnvelope() method:** Add `EnvelopeCreateResult` inner class and `createEnvelope(Id caseId, String accountCode, String templateId)` to `DocusignESignatureService`. (FR-002)
|
||||||
|
|
||||||
|
- [ ] **Task 3 — Tests for createEnvelope():** Add test coverage in `DocusignESignatureServiceTest` — success path (mock 201), failure path (mock 400), blank email guard. (NFR-001, TC-001, TC-002)
|
||||||
|
|
||||||
|
- [ ] **Task 4 — persistEnvelopeResult() + CaseContext extension:** Add `persistEnvelopeResult()` to `CLMAdminService`. Extend `CaseContext` inner class and `getCaseContext()` SOQL to include the 5 eSignature fields. (FR-003, FR-004)
|
||||||
|
|
||||||
|
- [ ] **Task 5 — Tests for persistEnvelopeResult():** Add test coverage in `CLMAdminServiceTest` — verify fields written correctly, `ESignature_Sent_At__c` is set, `ESignature_Completed_At__c` is NOT written on a "sent" result. (NFR-001, TC-003)
|
||||||
|
|
||||||
|
- [ ] **Task 6 — clmDocGenWorkbench UI:** Add "eSignature Template ID" text input and "Send for Signature" button to `clmDocGenWorkbench`. Button calls `createEnvelope()` then `persistEnvelopeResult()`, shows result. (FR-005)
|
||||||
|
|
||||||
|
- [ ] **Task 7 — Full deploy and test run:** Deploy entire change set, run all four test classes, verify all pass. Confirm no regressions. Update `docs/CURRENT_STATUS.md` to reflect Phase 2 completion.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Completion
|
||||||
|
|
||||||
|
When all tasks are checked, output `<promise>DONE</promise>`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Phase 2 plan created: 2026-04-09_
|
||||||
|
|
@ -0,0 +1,327 @@
|
||||||
|
# Project Specification — Phase 2: eSignature Envelope Creation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Project Overview
|
||||||
|
|
||||||
|
### What are we building?
|
||||||
|
|
||||||
|
Connecting the CLM document generation workflow to DocuSign eSignature. After a review letter is generated and attached to an `Appraiser_Case__c` record, the operator can click "Send for Signature" to create a DocuSign envelope (using a pre-built eSignature template), send it, and have the envelope ID, status, and sent timestamp written back to the case.
|
||||||
|
|
||||||
|
### Why does it matter?
|
||||||
|
|
||||||
|
Phase 1 produces the review letter. Phase 2 delivers it for signature. Without this, the operator must manually log into DocuSign to send the envelope — a workflow gap.
|
||||||
|
|
||||||
|
### Success criteria
|
||||||
|
|
||||||
|
- [ ] Five eSignature tracking fields exist on `Appraiser_Case__c` and deploy cleanly
|
||||||
|
- [ ] `DocusignESignatureService.createEnvelope()` calls the eSignature REST API and returns a structured result
|
||||||
|
- [ ] Envelope ID, status, and sent timestamp are persisted to the case after a successful send
|
||||||
|
- [ ] `clmDocGenWorkbench` exposes a "Send for Signature" button that activates after attach succeeds
|
||||||
|
- [ ] All Apex changes are covered by tests in the existing test classes; all tests pass in appraiser-dev
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Technical Foundation
|
||||||
|
|
||||||
|
### Tech stack
|
||||||
|
|
||||||
|
- **Language:** Apex (Salesforce), LWC (JavaScript/HTML)
|
||||||
|
- **Framework:** Salesforce DX / SFDX source format
|
||||||
|
- **Test framework:** Apex Test (deployed and run via sf CLI)
|
||||||
|
- **Package manager:** N/A — Salesforce metadata deployment
|
||||||
|
- **Org alias:** `appraiser-dev`
|
||||||
|
|
||||||
|
### Project structure
|
||||||
|
|
||||||
|
```
|
||||||
|
force-app/main/default/
|
||||||
|
├── classes/
|
||||||
|
│ ├── CLMAdminService.cls
|
||||||
|
│ ├── CLMAdminServiceTest.cls
|
||||||
|
│ ├── DocusignESignatureService.cls ← primary change target
|
||||||
|
│ └── DocusignESignatureServiceTest.cls ← test target
|
||||||
|
├── lwc/
|
||||||
|
│ └── clmDocGenWorkbench/ ← UI change target
|
||||||
|
└── objects/
|
||||||
|
└── Appraiser_Case__c/
|
||||||
|
└── fields/ ← 5 new fields
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build and test commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NODE=/home/paulh/.nvm/versions/node/v22.22.0/bin/node
|
||||||
|
SF=/home/paulh/.nvm/versions/node/v22.22.0/lib/node_modules/@salesforce/cli/bin/run.js
|
||||||
|
|
||||||
|
# Deploy
|
||||||
|
$NODE $SF project deploy start --source-dir force-app --target-org appraiser-dev
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
$NODE $SF apex run test \
|
||||||
|
--target-org appraiser-dev \
|
||||||
|
--class-names DocusignESignatureServiceTest,CLMAdminServiceTest \
|
||||||
|
--result-format human \
|
||||||
|
--synchronous
|
||||||
|
```
|
||||||
|
|
||||||
|
### Coding standards
|
||||||
|
|
||||||
|
- Follow existing Apex class structure — inner DTO classes, `@AuraEnabled` methods, `with sharing`
|
||||||
|
- Follow CLM tracking pattern: service layer method → result object → persist to case in `CLMAdminService`
|
||||||
|
- All HTTP callouts mocked in tests using `HttpCalloutMock` (see `CLMDocGenCalloutTest` for the pattern)
|
||||||
|
- LWC: follow existing `clmDocGenWorkbench` patterns (wire adapters, async/await handlers, tracked properties)
|
||||||
|
- Field API names use `__c` suffix; field-meta.xml follows Salesforce DX format
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Requirements
|
||||||
|
|
||||||
|
### FR-001: eSignature Tracking Fields on Appraiser_Case__c
|
||||||
|
|
||||||
|
**Description:** Five custom fields to track the envelope lifecycle on the case record.
|
||||||
|
|
||||||
|
**Fields:**
|
||||||
|
- `ESignature_Envelope_Id__c` — Text(255), label "eSignature Envelope ID"
|
||||||
|
- `ESignature_Envelope_Status__c` — Text(50), label "eSignature Envelope Status" (values: created / sent / delivered / completed / voided)
|
||||||
|
- `ESignature_Sent_At__c` — DateTime, label "eSignature Sent At"
|
||||||
|
- `ESignature_Completed_At__c` — DateTime, label "eSignature Completed At"
|
||||||
|
- `ESignature_Envelope_Url__c` — URL(255), label "eSignature Envelope URL"
|
||||||
|
|
||||||
|
**Acceptance criteria:**
|
||||||
|
- [ ] All 5 field metadata XML files exist under `objects/Appraiser_Case__c/fields/`
|
||||||
|
- [ ] Fields deploy cleanly to appraiser-dev with no errors
|
||||||
|
- [ ] `package.xml` is updated to include the new fields (or already covers `CustomField` members)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### FR-002: createEnvelope() in DocusignESignatureService
|
||||||
|
|
||||||
|
**Description:** An Apex method that creates a DocuSign eSignature envelope using Option A (template-based). The caller provides a case ID, account code, and template ID. The method resolves the named credential from the account setting, calls `POST /v2.1/accounts/{accountId}/envelopes` with the template ID and a signer role populated from the case's appraiser fields, and returns a structured result.
|
||||||
|
|
||||||
|
**API call shape:**
|
||||||
|
```json
|
||||||
|
POST /v2.1/accounts/{accountId}/envelopes
|
||||||
|
{
|
||||||
|
"templateId": "<templateId>",
|
||||||
|
"templateRoles": [
|
||||||
|
{
|
||||||
|
"email": "<Appraiser_Email__c>",
|
||||||
|
"name": "<Appraiser_Name__c or Appraiser_Salutation__c + Appraiser_Last_Name__c>",
|
||||||
|
"roleName": "Signer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"status": "sent"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Result class:** `EnvelopeCreateResult` — inner class on `DocusignESignatureService`:
|
||||||
|
- `Boolean success`
|
||||||
|
- `String envelopeId`
|
||||||
|
- `String status`
|
||||||
|
- `String envelopeUri`
|
||||||
|
- `String errorMessage`
|
||||||
|
|
||||||
|
**Method signature:**
|
||||||
|
```apex
|
||||||
|
@AuraEnabled(cacheable=false)
|
||||||
|
public static EnvelopeCreateResult createEnvelope(
|
||||||
|
Id caseId,
|
||||||
|
String accountCode,
|
||||||
|
String templateId
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Acceptance criteria:**
|
||||||
|
- [ ] Method is `@AuraEnabled` and callable from LWC
|
||||||
|
- [ ] Resolves `eSignatureAccountId` and `eSignatureRestNamedCredential` from `CLM_Account_Setting__mdt` via `accountCode`
|
||||||
|
- [ ] Queries `Appraiser_Email__c`, `Appraiser_Name__c`, `Appraiser_Last_Name__c`, `Appraiser_Salutation__c` from the case
|
||||||
|
- [ ] POSTs correct JSON body to the eSignature API
|
||||||
|
- [ ] Returns `EnvelopeCreateResult` with `envelopeId` and `status` on success
|
||||||
|
- [ ] Returns `EnvelopeCreateResult` with `success=false` and `errorMessage` on failure (non-2xx or exception)
|
||||||
|
- [ ] Does NOT throw uncaught exceptions — catches and wraps in result
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### FR-003: persistEnvelopeResult() in CLMAdminService
|
||||||
|
|
||||||
|
**Description:** After a successful `createEnvelope()` call, write the result back to `Appraiser_Case__c`. Follows the same pattern as `persistDocGenResult`.
|
||||||
|
|
||||||
|
**Method signature:**
|
||||||
|
```apex
|
||||||
|
@AuraEnabled(cacheable=false)
|
||||||
|
public static void persistEnvelopeResult(
|
||||||
|
Id caseId,
|
||||||
|
String envelopeId,
|
||||||
|
String envelopeStatus,
|
||||||
|
String envelopeUri
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fields written:**
|
||||||
|
- `ESignature_Envelope_Id__c` = envelopeId
|
||||||
|
- `ESignature_Envelope_Status__c` = envelopeStatus
|
||||||
|
- `ESignature_Sent_At__c` = Datetime.now()
|
||||||
|
- `ESignature_Envelope_Url__c` = envelopeUri (if not blank)
|
||||||
|
|
||||||
|
**Acceptance criteria:**
|
||||||
|
- [ ] Method updates the correct case record (by `caseId`)
|
||||||
|
- [ ] Sets `ESignature_Sent_At__c` to current datetime
|
||||||
|
- [ ] Does not overwrite `ESignature_Completed_At__c` (only set when status = completed)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### FR-004: Extend CaseContext with Envelope Fields
|
||||||
|
|
||||||
|
**Description:** The `CaseContext` inner class in `CLMAdminService` (and its `getCaseContext()` query) must include the 5 eSignature tracking fields so the LWC can display current envelope status.
|
||||||
|
|
||||||
|
**Fields to add to CaseContext:**
|
||||||
|
- `String eSignatureEnvelopeId`
|
||||||
|
- `String eSignatureEnvelopeStatus`
|
||||||
|
- `String eSignatureEnvelopeUrl`
|
||||||
|
- `Datetime eSignatureSentAt`
|
||||||
|
- `Datetime eSignatureCompletedAt`
|
||||||
|
|
||||||
|
**Acceptance criteria:**
|
||||||
|
- [ ] `getCaseContext()` SOQL query includes all 5 fields
|
||||||
|
- [ ] `CaseContext` DTO maps all 5 fields with `@AuraEnabled`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### FR-005: "Send for Signature" Button in clmDocGenWorkbench
|
||||||
|
|
||||||
|
**Description:** After the "Attach Generated Document" step succeeds, a "Send for Signature" button becomes enabled. Clicking it calls `createEnvelope()` and then `persistEnvelopeResult()`, and displays the result (envelope ID and status, or error message) in the workbench UI.
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
- Button is disabled until `attachedFileContentDocumentId` is populated (i.e., attach has succeeded)
|
||||||
|
- Button shows a spinner while in progress (same pattern as generate/attach buttons)
|
||||||
|
- On success: display envelope ID and status; update envelope fields displayed from `caseContext`
|
||||||
|
- On error: display error message
|
||||||
|
- Button does NOT automatically select a template — the operator must provide `templateId` via a text input field in the UI
|
||||||
|
|
||||||
|
**New UI elements:**
|
||||||
|
- Text input: "eSignature Template ID" (maps to `templateId` parameter)
|
||||||
|
- Button: "Send for Signature"
|
||||||
|
- Status display: envelope ID, status, sent timestamp (read from refreshed `caseContext`)
|
||||||
|
|
||||||
|
**Acceptance criteria:**
|
||||||
|
- [ ] Button is disabled when `attachedFileContentDocumentId` is blank
|
||||||
|
- [ ] Button calls `DocusignESignatureService.createEnvelope()` with the case ID, account code, and template ID from the input
|
||||||
|
- [ ] On success, calls `CLMAdminService.persistEnvelopeResult()` and refreshes the case context display
|
||||||
|
- [ ] On failure, displays the error message from `EnvelopeCreateResult.errorMessage`
|
||||||
|
- [ ] Template ID input is cleared after a successful send
|
||||||
|
- [ ] Spinner shown during the callout; button disabled during in-flight request
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### NFR-001: Test Coverage
|
||||||
|
|
||||||
|
- [ ] `DocusignESignatureServiceTest` covers `createEnvelope()` — at minimum: success path (mock HTTP 201), failure path (mock HTTP 400), missing template ID guard
|
||||||
|
- [ ] `CLMAdminServiceTest` covers `persistEnvelopeResult()` — at minimum: fields written correctly, `ESignature_Sent_At__c` is set
|
||||||
|
- [ ] All existing tests continue to pass after each change
|
||||||
|
|
||||||
|
### NFR-002: No Hardcoded Credentials
|
||||||
|
|
||||||
|
- [ ] All DocuSign API calls go through named credentials (never hardcoded tokens or passwords)
|
||||||
|
- [ ] Named credential API name is sourced from `CLM_Account_Setting__mdt`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Data Model
|
||||||
|
|
||||||
|
### New fields on Appraiser_Case__c
|
||||||
|
|
||||||
|
| API Name | Type | Length |
|
||||||
|
|----------|------|--------|
|
||||||
|
| ESignature_Envelope_Id__c | Text | 255 |
|
||||||
|
| ESignature_Envelope_Status__c | Text | 50 |
|
||||||
|
| ESignature_Sent_At__c | DateTime | — |
|
||||||
|
| ESignature_Completed_At__c | DateTime | — |
|
||||||
|
| ESignature_Envelope_Url__c | URL | 255 |
|
||||||
|
|
||||||
|
### Existing fields read by createEnvelope()
|
||||||
|
|
||||||
|
| API Name | Used for |
|
||||||
|
|----------|----------|
|
||||||
|
| Appraiser_Email__c | templateRoles[0].email |
|
||||||
|
| Appraiser_Name__c | templateRoles[0].name (prefer this) |
|
||||||
|
| Appraiser_Salutation__c | fallback for name |
|
||||||
|
| Appraiser_Last_Name__c | fallback for name |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Architecture Decisions
|
||||||
|
|
||||||
|
### Constraints
|
||||||
|
|
||||||
|
- **MUST:** Use `CLM_Account_Setting__mdt` to resolve named credentials — never hardcode
|
||||||
|
- **MUST:** Follow the `persistDocGenResult` pattern in `CLMAdminService` for `persistEnvelopeResult`
|
||||||
|
- **MUST:** Mock all HTTP callouts in tests using `HttpCalloutMock`
|
||||||
|
- **MUST NOT:** Modify the CLM document generation flow (Phase 1) — leave it untouched
|
||||||
|
- **MUST NOT:** Create new Apex test classes — add to existing ones
|
||||||
|
- **MUST NOT:** Add eSignature template browsing in this phase — the operator inputs the template ID directly
|
||||||
|
- **PREFER:** Option A (template-based envelope) for `createEnvelope()` — simpler, already decided
|
||||||
|
- **ESCALATE:** If the eSignature named credential does not exist in the org, stop and flag it
|
||||||
|
- **ESCALATE:** If `Appraiser_Email__c` is blank on the queried case, stop — do not silently send with a blank email
|
||||||
|
- **ESCALATE:** If the spec does not cover a required decision — do not fill gaps silently
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- `CLM_Account_Setting__mdt` — provides `ESignature_Account_Id__c`, `ESignature_Rest_Named_Credential__c`
|
||||||
|
- DocuSign eSignature REST API v2.1 — `/v2.1/accounts/{accountId}/envelopes`
|
||||||
|
- Named credential (AcctDemo_NamedCreds or equivalent) — must exist in appraiser-dev
|
||||||
|
|
||||||
|
### Known Challenges
|
||||||
|
|
||||||
|
- The named credential for eSignature REST may differ from the CLM named credential — check `CLM_Account_Setting__mdt` for `ESignature_Rest_Named_Credential__c`
|
||||||
|
- DocuSign template roles: the `roleName` must match the role name defined in the eSignature template — "Signer" is the default, but the actual template may use a different name. If tests fail with a 400 from DocuSign about role name, escalate.
|
||||||
|
- LWC must use `async/await` for imperative Apex calls (not wire adapters) for the send action — matches the existing attach pattern
|
||||||
|
|
||||||
|
### Rejected Approaches
|
||||||
|
|
||||||
|
| Rejected option | Why rejected |
|
||||||
|
|-----------------|-------------|
|
||||||
|
| Option B (upload CLM document as envelope document) | More complex, requires downloading the blob and managing recipients in code. Option A with a matching template is simpler and sufficient. |
|
||||||
|
| Creating a new Apex test class for eSignature | Not needed — `DocusignESignatureServiceTest` already exists and is the right home |
|
||||||
|
| Automatic template selection via API | Out of scope for Phase 2 — operator inputs template ID directly |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Reference Materials
|
||||||
|
|
||||||
|
### Existing code to learn from
|
||||||
|
|
||||||
|
- `CLMAdminService.persistDocGenResult()` — the exact pattern to replicate for `persistEnvelopeResult()`
|
||||||
|
- `CLMAdminService.getCaseContext()` — how case fields are queried and mapped to a DTO
|
||||||
|
- `CLMDocGenCalloutTest` — the HTTP mock pattern to follow in `DocusignESignatureServiceTest`
|
||||||
|
- `clmDocGenWorkbench` — the LWC pattern (async handlers, spinner, button enable/disable) to follow for the send button
|
||||||
|
|
||||||
|
### Anti-patterns
|
||||||
|
|
||||||
|
- Do not use `HttpRequest.setBodyAsBlob()` for the JSON envelope payload — use `setBody()` with `JSON.serialize()`
|
||||||
|
- Do not trust the `status` field in the LWC to determine if all prior steps completed — check the specific field (e.g., `attachedFileContentDocumentId`) that the button should gate on
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Evaluation Design
|
||||||
|
|
||||||
|
### Test cases
|
||||||
|
|
||||||
|
#### TC-001: createEnvelope() success path
|
||||||
|
**Input:** Valid caseId, valid accountCode, valid templateId; mock HTTP returns 201 with `{"envelopeId":"abc-123","status":"sent","uri":"/envelopes/abc-123"}`
|
||||||
|
**Expected output:** `EnvelopeCreateResult.success = true`, `envelopeId = "abc-123"`, `status = "sent"`
|
||||||
|
**Verification:** `System.assertEquals(true, result.success)` in `DocusignESignatureServiceTest`
|
||||||
|
|
||||||
|
#### TC-002: createEnvelope() failure path
|
||||||
|
**Input:** Valid caseId/accountCode/templateId; mock HTTP returns 400 with error body
|
||||||
|
**Expected output:** `EnvelopeCreateResult.success = false`, `errorMessage` is non-blank
|
||||||
|
**Verification:** `System.assertEquals(false, result.success)` and `System.assertNotEquals(null, result.errorMessage)`
|
||||||
|
|
||||||
|
#### TC-003: persistEnvelopeResult() writes correct fields
|
||||||
|
**Input:** Existing case record, envelopeId = "test-env-id", envelopeStatus = "sent", envelopeUri = "/envelopes/test-env-id"
|
||||||
|
**Expected output:** Re-queried case has `ESignature_Envelope_Id__c = "test-env-id"`, `ESignature_Sent_At__c` is non-null
|
||||||
|
**Verification:** Re-query case after call and assert in `CLMAdminServiceTest`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Last updated: 2026-04-09_
|
||||||
|
|
@ -0,0 +1,198 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Ralph Wiggum Loop — Autonomous agent iteration
|
||||||
|
#
|
||||||
|
# Based on Geoffrey Huntley's approach:
|
||||||
|
# - Each iteration spawns a FRESH agent with clean context
|
||||||
|
# - Agent reads the plan, picks ONE task, implements, tests, commits, exits
|
||||||
|
# - Loop restarts until all tasks are done
|
||||||
|
#
|
||||||
|
# No context compaction. No stale reasoning. Just fresh starts.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./ralph-loop.sh # Build mode (default)
|
||||||
|
# ./ralph-loop.sh plan # Planning mode (create IMPLEMENTATION_PLAN.md)
|
||||||
|
# ./ralph-loop.sh --max 20 # Limit to 20 iterations
|
||||||
|
# ./ralph-loop.sh --agent claude # Use claude (default)
|
||||||
|
# ./ralph-loop.sh --agent codex # Use OpenAI Codex CLI
|
||||||
|
# ./ralph-loop.sh --agent aider # Use Aider
|
||||||
|
# ./ralph-loop.sh --agent gemini # Use Gemini CLI
|
||||||
|
# ./ralph-loop.sh --agent custom # Use custom agent (see below)
|
||||||
|
#
|
||||||
|
# Extensibility:
|
||||||
|
# To add support for other AI coding agents (aider, cursor, windsurf, etc.):
|
||||||
|
# 1. Add a new case in the run_agent() function's agent selection block
|
||||||
|
# 2. Format the prompt appropriately for that agent's CLI interface
|
||||||
|
# 3. Ensure the agent outputs to the logfile for promise detection
|
||||||
|
#
|
||||||
|
# Example for Aider:
|
||||||
|
# aider)
|
||||||
|
# aider --message "$prompt" --yes 2>&1 | tee "$logfile"
|
||||||
|
# ;;
|
||||||
|
#
|
||||||
|
# Example for custom script:
|
||||||
|
# custom)
|
||||||
|
# ./my-agent-wrapper.sh "$prompt" 2>&1 | tee "$logfile"
|
||||||
|
# ;;
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MODE="${1:-build}"
|
||||||
|
MAX_ITERATIONS=50
|
||||||
|
AGENT="claude"
|
||||||
|
PLAN_FILE="IMPLEMENTATION_PLAN.md"
|
||||||
|
SPEC_FILE="PROJECT-SPEC.md"
|
||||||
|
AGENT_FILE="AGENT.md"
|
||||||
|
LOG_DIR=".ralph-logs"
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
shift 2>/dev/null || true
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--max) MAX_ITERATIONS="$2"; shift 2 ;;
|
||||||
|
--agent) AGENT="$2"; shift 2 ;;
|
||||||
|
*) echo "Unknown option: $1"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p "$LOG_DIR"
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
log() { echo -e "${BLUE}[ralph]${NC} $1"; }
|
||||||
|
success() { echo -e "${GREEN}[ralph]${NC} $1"; }
|
||||||
|
warn() { echo -e "${YELLOW}[ralph]${NC} $1"; }
|
||||||
|
error() { echo -e "${RED}[ralph]${NC} $1"; }
|
||||||
|
|
||||||
|
# Check prerequisites
|
||||||
|
if [[ ! -f "$SPEC_FILE" ]]; then
|
||||||
|
error "Missing $SPEC_FILE — create your project spec first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$AGENT_FILE" ]]; then
|
||||||
|
warn "No $AGENT_FILE found. Using default agent instructions."
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_agent() {
|
||||||
|
local iteration=$1
|
||||||
|
local mode=$2
|
||||||
|
local logfile="$LOG_DIR/iteration-${iteration}.log"
|
||||||
|
local prompt=""
|
||||||
|
|
||||||
|
if [[ "$mode" == "plan" ]]; then
|
||||||
|
prompt="Read PROJECT-SPEC.md. Decompose the project into discrete, testable tasks ordered by dependency. Write the plan to IMPLEMENTATION_PLAN.md with checkboxes. Output <promise>PLANNED</promise> when done."
|
||||||
|
else
|
||||||
|
prompt="Read AGENT.md (if it exists) for your instructions. Follow the core loop: orient, pick one task, implement, verify, commit, exit."
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Iteration $iteration ($mode mode) — starting fresh agent..."
|
||||||
|
|
||||||
|
# Agent selection block
|
||||||
|
# Extend this case statement to support additional agents
|
||||||
|
case "$AGENT" in
|
||||||
|
claude)
|
||||||
|
echo "$prompt" | claude -p --output-format text 2>&1 | tee "$logfile"
|
||||||
|
;;
|
||||||
|
codex)
|
||||||
|
echo "$prompt" | codex 2>&1 | tee "$logfile"
|
||||||
|
;;
|
||||||
|
aider)
|
||||||
|
# Aider: AI pair programming in your terminal
|
||||||
|
# https://aider.chat
|
||||||
|
aider --message "$prompt" --yes 2>&1 | tee "$logfile"
|
||||||
|
;;
|
||||||
|
gemini)
|
||||||
|
# Google Gemini CLI (if available)
|
||||||
|
# Adjust command based on actual Gemini CLI interface
|
||||||
|
echo "$prompt" | gemini-cli 2>&1 | tee "$logfile"
|
||||||
|
;;
|
||||||
|
custom)
|
||||||
|
# Custom agent integration
|
||||||
|
# Replace this with your own agent wrapper script
|
||||||
|
# The script should:
|
||||||
|
# 1. Accept prompt as first argument or via stdin
|
||||||
|
# 2. Perform the requested work (read files, write code, run tests, commit)
|
||||||
|
# 3. Output promise signals: <promise>PLANNED|DONE|STUCK|ERROR</promise>
|
||||||
|
# 4. Exit with appropriate code
|
||||||
|
if [[ -x "./custom-agent.sh" ]]; then
|
||||||
|
./custom-agent.sh "$prompt" 2>&1 | tee "$logfile"
|
||||||
|
else
|
||||||
|
error "Custom agent selected but ./custom-agent.sh not found or not executable"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
error "Unknown agent: $AGENT"
|
||||||
|
error "Supported agents: claude, codex, aider, gemini, custom"
|
||||||
|
error "To add support for other agents, edit the run_agent() function in this script"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
check_output() {
|
||||||
|
local logfile="$1"
|
||||||
|
|
||||||
|
if grep -q '<promise>DONE</promise>' "$logfile" 2>/dev/null; then
|
||||||
|
return 0 # Done
|
||||||
|
elif grep -q '<promise>STUCK</promise>' "$logfile" 2>/dev/null; then
|
||||||
|
return 2 # Stuck
|
||||||
|
elif grep -q '<promise>ERROR</promise>' "$logfile" 2>/dev/null; then
|
||||||
|
return 3 # Error
|
||||||
|
else
|
||||||
|
return 1 # Continue
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main loop
|
||||||
|
if [[ "$MODE" == "plan" ]]; then
|
||||||
|
log "Planning mode — creating implementation plan..."
|
||||||
|
run_agent 0 plan
|
||||||
|
success "Plan created. Review $PLAN_FILE, then run: ./ralph-loop.sh"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Starting Ralph Wiggum loop (max $MAX_ITERATIONS iterations)"
|
||||||
|
log "Agent: $AGENT"
|
||||||
|
log "Spec: $SPEC_FILE"
|
||||||
|
log "Plan: $PLAN_FILE"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
for i in $(seq 1 "$MAX_ITERATIONS"); do
|
||||||
|
run_agent "$i" build
|
||||||
|
logfile="$LOG_DIR/iteration-${i}.log"
|
||||||
|
|
||||||
|
check_output "$logfile"
|
||||||
|
status=$?
|
||||||
|
|
||||||
|
case $status in
|
||||||
|
0)
|
||||||
|
success "🎉 ALL TASKS COMPLETE after $i iterations!"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
warn "Agent is stuck. Review $logfile and intervene."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
error "Agent encountered an error. Review $logfile."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
1)
|
||||||
|
log "Iteration $i complete. Restarting with fresh context..."
|
||||||
|
echo ""
|
||||||
|
sleep 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
warn "Reached max iterations ($MAX_ITERATIONS). Review progress in $PLAN_FILE."
|
||||||
|
exit 1
|
||||||
Loading…
Reference in New Issue