diff --git a/AGENT.md b/AGENT.md new file mode 100644 index 0000000..f04c08e --- /dev/null +++ b/AGENT.md @@ -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 `DONE` 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: + +``` +(): + + + +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 `STUCK`: + +```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 + +- `PLANNED` — plan created, ready for build +- `DONE` — all tasks complete +- `STUCK` — needs human intervention +- `ERROR` — 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 +``` + +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. diff --git a/DECISIONS.md b/DECISIONS.md new file mode 100644 index 0000000..e349851 --- /dev/null +++ b/DECISIONS.md @@ -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._ diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md new file mode 100644 index 0000000..8238301 --- /dev/null +++ b/IMPLEMENTATION_PLAN.md @@ -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 `DONE`. + +--- + +_Phase 2 plan created: 2026-04-09_ diff --git a/PROJECT-SPEC.md b/PROJECT-SPEC.md new file mode 100644 index 0000000..cd7bed7 --- /dev/null +++ b/PROJECT-SPEC.md @@ -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": "", + "templateRoles": [ + { + "email": "", + "name": "", + "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_ diff --git a/ralph-loop.sh b/ralph-loop.sh new file mode 100755 index 0000000..3ac1de7 --- /dev/null +++ b/ralph-loop.sh @@ -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 PLANNED 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: PLANNED|DONE|STUCK|ERROR + # 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 'DONE' "$logfile" 2>/dev/null; then + return 0 # Done + elif grep -q 'STUCK' "$logfile" 2>/dev/null; then + return 2 # Stuck + elif grep -q 'ERROR' "$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