From 49ee2b71d34abdbaaeacac1eedeca20b72911df3 Mon Sep 17 00:00:00 2001 From: Suprhimp Date: Fri, 31 Jul 2026 14:23:21 +0900 Subject: [PATCH] fix: make plan artifact updates resilient --- .changeset/reliable-plan-artifact.md | 5 +++++ .claude/agents/duul-planner.md | 10 ++++++---- CLAUDE.md | 12 +++++++++++- src/index.ts | 3 +++ 4 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 .changeset/reliable-plan-artifact.md diff --git a/.changeset/reliable-plan-artifact.md b/.changeset/reliable-plan-artifact.md new file mode 100644 index 0000000..48a81b2 --- /dev/null +++ b/.changeset/reliable-plan-artifact.md @@ -0,0 +1,5 @@ +--- +"@planningo/duul": patch +--- + +Improve plan-file workflow guidance so callers replace and verify each plan revision as one snapshot instead of retrying stale text edits. diff --git a/.claude/agents/duul-planner.md b/.claude/agents/duul-planner.md index d90bb68..9707756 100644 --- a/.claude/agents/duul-planner.md +++ b/.claude/agents/duul-planner.md @@ -27,7 +27,7 @@ You will receive: - Dependencies and imports needed **Write the plan in compressed "caveman" style to save tokens:** drop articles (a/an/the), filler (just/really/basically), and pleasantries; prefer fragments over full sentences; use short synonyms. Keep EXACT: file paths, identifiers, function/type names, code, and the verbatim quote of the user's request. Brevity must never drop a required section or change technical meaning. -3. **Submit the plan via file (preferred for reliability).** Write the full plan markdown to `.duul/plan.md` under the workspace root using the Write tool, THEN call `request_plan_review` with `plan_file: ".duul/plan.md"` (relative path). This avoids the large-argument tool-call failure where a big inline `plan` string collapses to an empty `{}`. For a short plan you may inline `plan` instead. ALWAYS include: +3. **Submit the plan via file (preferred for reliability).** Write the full plan markdown to `.duul/plan.md` under the workspace root using the Write tool, THEN read it back and call `request_plan_review` with `plan_file: ".duul/plan.md"` (relative path). This avoids the large-argument tool-call failure where a big inline `plan` string collapses to an empty `{}`. For a short plan you may inline `plan` instead. ALWAYS include: - `workspace_root`: the workspace root path (required when using `plan_file`) - `plan_file`: `".duul/plan.md"` (relative path) — OR `plan` with the full text inline for short plans - `user_original_request`: the user's verbatim message @@ -68,8 +68,10 @@ A large inline `plan` string is the #1 cause of failed DUUL calls: the model tri **The fix: route the large plan through a file.** -1. Write the full plan markdown to `.duul/plan.md` under the workspace root with the **Write** tool (Write has a tiny, reliable schema — big content goes through fine here). -2. Call `request_plan_review` with a *small* argument object that points at the file: +1. Compose the complete next revision before editing. Use the **Write** tool to replace all of `.duul/plan.md` in one operation; do not build a plan with a chain of exact-text `Edit` calls. Exact-text replacements become stale after the first edit and produce `String to replace not found` errors. +2. Read `.duul/plan.md` back after writing. Only submit the file after confirming it contains the complete intended revision. +3. If Write is unavailable, read the file immediately before one Edit, make one whole-document replacement, and read it back. If that Edit reports a mismatch, stop the edit sequence, re-read the file, and regenerate the full snapshot — never retry the stale replacement. +4. Call `request_plan_review` with a *small* argument object that points at the file: ```json { @@ -80,7 +82,7 @@ A large inline `plan` string is the #1 cause of failed DUUL calls: the model tri } ``` -The server reads `.duul/plan.md` and uses its contents as the plan. `plan_file` must be a **relative** path inside `workspace_root`. +The server reads `.duul/plan.md` and uses its contents as the plan. `plan_file` must be a **relative** path inside `workspace_root`. Apply the same snapshot protocol on every REVISE round; do not edit the plan concurrently with another agent. **Short plans only:** you may instead inline `plan` directly. Exactly one of `plan` or `plan_file` is required. diff --git a/CLAUDE.md b/CLAUDE.md index 0f3f7cb..70d16ff 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,7 +27,17 @@ Only activate when the user mentions **"DUUL"** (or **"두울"**) in their reque 3. If the subagent reports `requires_human_review === true`: pause and ask the user. 4. Extract `approved_plan`, `review_id`, and `git_head_sha` from the subagent's response. -**Fallback:** If the subagent fails or the MCP tool is not accessible from the subagent, fall back to running Phase 1 directly (same as before but in the main agent). +**Fallback:** If the subagent fails or the MCP tool is not accessible from the subagent, fall back to running Phase 1 directly (same as before but in the main agent). Use the plan-artifact protocol below in either path. + +### Plan-artifact protocol (required in Phase 1) + +The plan-file escape hatch only helps if its contents are written reliably. Treat `.duul/plan.md` as an immutable snapshot for each review round: + +1. Compose the complete revision before using a file-edit tool. +2. Use `Write` to replace the entire file once, then read it back and verify the intended plan is present. +3. Do not assemble a revision with several exact-text `Edit` operations. Their search strings are stale as soon as an earlier edit succeeds, causing `String to replace not found` errors. +4. If `Write` is unavailable, read immediately before a single whole-file `Edit`; after any mismatch error, stop, re-read, and write a fresh full snapshot. Never retry the failed replacement text. +5. Only then call `request_plan_review` with `plan_file: ".duul/plan.md"`. One agent owns this artifact at a time. ### Phase 2: Unit-verify Ping-Pong (Opus, start IMMEDIATELY after Phase 1 approval — do NOT ask for confirmation) diff --git a/src/index.ts b/src/index.ts index a82d509..10aa432 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,9 @@ const SERVER_INSTRUCTIONS = ` DUUL — Dual-phase Upfront-plan & Unit-verify Loop. Activate ONLY when user says "DUUL" or "두울". See project CLAUDE.md for full protocol. Key rules: pass workspace_root, pass previous_review_id on each round, never stop between phases. +Plan-file reliability: compose a complete revision before changing .duul/plan.md; write the whole file once, +read it back, then submit plan_file. Do not chain exact-text edits. On an edit mismatch, re-read and write a +fresh snapshot; only one agent may modify the plan artifact at a time. `.trim(); const server = new McpServer(