diff --git a/engine/app/controllers/coplan/agent_instructions_controller.rb b/engine/app/controllers/coplan/agent_instructions_controller.rb index 3ec0d739..192128af 100644 --- a/engine/app/controllers/coplan/agent_instructions_controller.rb +++ b/engine/app/controllers/coplan/agent_instructions_controller.rb @@ -5,7 +5,11 @@ class AgentInstructionsController < ApplicationController def show @auth_instructions = CoPlan.configuration.agent_auth_instructions @curl = CoPlan.configuration.agent_curl_prefix - @base = request.base_url + # Includes the engine's mount point — host apps may mount CoPlan under + # a prefix (e.g. /coplan), and request.base_url alone would point every + # curl example at the wrong path. root_path here is the engine's, which + # carries the request's SCRIPT_NAME. + @base = "#{request.base_url}#{root_path.chomp("/")}" @plan_types = PlanType.order(:name) render layout: false, content_type: "text/markdown", formats: [:text] end diff --git a/engine/app/views/coplan/agent_instructions/show.text.erb b/engine/app/views/coplan/agent_instructions/show.text.erb index 47ad9298..ef320b9f 100644 --- a/engine/app/views/coplan/agent_instructions/show.text.erb +++ b/engine/app/views/coplan/agent_instructions/show.text.erb @@ -4,6 +4,19 @@ Interact with the CoPlan app to create, read, edit, and comment on plan document <%= @auth_instructions %> +## Quick Start: The Editing Loop + +Keeping a local Markdown file and syncing whole documents is the intended workflow — you do not need to express edits as granular operations. The canonical loop: + +1. **Read** — `GET <%= @base %>/api/v1/plans/$PLAN_ID/snapshot`. Save `current_content` to a local file and note `current_revision`. +2. **Edit** the local file however you like. +3. **Write** — `PUT <%= @base %>/api/v1/plans/$PLAN_ID/content` with the full new content, `base_revision` set to the revision you read, and a specific `change_summary` (it becomes the version's label in the plan history — "Tightened rollout plan per Sam's feedback", not "Update"). +4. **On `409 Conflict`** someone edited in between: re-read the snapshot, re-apply your changes to the fresh content, and PUT again with the new revision. Never retry with the stale `base_revision`. + +The server diffs your content against the current revision, records granular operations automatically, and preserves comment anchors in unchanged regions. Re-read before every editing session — don't trust a local copy that's more than a few minutes old, and don't keep long-lived local state between tasks. + +See [Editing Plans](#editing-plans-recommended-full-content-replacement) for the full request/response reference, and the lease + operations path only when you need a surgical single-operation edit. + ## API Reference ### List Plans diff --git a/engine/app/views/coplan/llms/show.text.erb b/engine/app/views/coplan/llms/show.text.erb index ee5f1c51..9bc25061 100644 --- a/engine/app/views/coplan/llms/show.text.erb +++ b/engine/app/views/coplan/llms/show.text.erb @@ -4,6 +4,8 @@ CoPlan has a REST API that AI agents can use to create plans, apply edits, and leave review comments. +The editing model is simple: read a plan's content and revision, edit the Markdown locally, and PUT the whole document back with `base_revision` for optimistic concurrency. The server diffs, versions, and preserves comment anchors automatically — no operation-by-operation editing required. + ## API - [Agent Instructions](<%= coplan.agent_instructions_path %>): Full API reference for AI agents, including authentication, endpoints, editing workflow, and commenting. diff --git a/spec/requests/agent_instructions_spec.rb b/spec/requests/agent_instructions_spec.rb index b7f2bbaf..a14ce98c 100644 --- a/spec/requests/agent_instructions_spec.rb +++ b/spec/requests/agent_instructions_spec.rb @@ -41,5 +41,19 @@ get agent_instructions_path expect(response.body).to include('"plan_type"') end + + it "builds example URLs from the request base (root mount)" do + get agent_instructions_path + expect(response.body).to include("http://www.example.com/api/v1/plans") + expect(response.body).not_to include("example.com//api") + end + + it "includes the engine mount prefix in example URLs" do + # Host apps may mount the engine under a prefix (HOST_APP_GUIDE + # documents mount at "/coplan"); examples must include it or agents + # 404 on their first read. + get agent_instructions_path, env: { "SCRIPT_NAME" => "/coplan" } + expect(response.body).to include("http://www.example.com/coplan/api/v1/plans") + end end end