Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ def show
@auth_instructions = CoPlan.configuration.agent_auth_instructions
@curl = CoPlan.configuration.agent_curl_prefix
@base = request.base_url
@plan_types = PlanType.order(:name)
render layout: false, content_type: "text/markdown", formats: [:text]
end
end
Expand Down
14 changes: 11 additions & 3 deletions engine/app/controllers/coplan/api/v1/plans_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,28 @@ def show
end

def create
if params[:plan_type].present?
plan_type = PlanType.find_by(name: params[:plan_type])
unless plan_type
available = PlanType.order(:name).pluck(:name)
message = "Unknown plan_type \"#{params[:plan_type]}\"."
message += " Available types: #{available.map { |n| "\"#{n}\"" }.join(", ")}." if available.any?
return render json: { error: message }, status: :unprocessable_content
end
end

plan = Plans::Create.call(
title: params[:title],
content: params[:content] || "",
user: current_user,
plan_type_id: params[:plan_type_id].presence
plan_type_id: plan_type&.id
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve plan_type_id support in create API

This line makes POST /api/v1/plans persist only plan_type-by-name lookups and drops params[:plan_type_id] entirely, so existing v1 clients that still send plan_type_id now get a 201 response with the plan created without a type instead of the previously expected typed plan (or a 422 for bad IDs). Because the failure is silent, it can skew downstream behavior that depends on plan type (classification/template workflows) and is likely to break older agents during rollout unless you continue accepting plan_type_id (or explicitly reject it).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional — there are no existing v1 clients using plan_type_id (the field was added in the same development cycle). We're shipping plan_type by name as the only supported path.

)
render json: plan_json(plan).merge(
current_content: plan.current_content,
current_revision: plan.current_revision
), status: :created
rescue ActiveRecord::RecordInvalid => e
render json: { error: e.message }, status: :unprocessable_content
rescue ActiveRecord::InvalidForeignKey
render json: { error: "Invalid plan_type_id" }, status: :unprocessable_content
end

def update
Expand Down
21 changes: 20 additions & 1 deletion engine/app/views/coplan/agent_instructions/show.text.erb
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ Returns: `id`, `title`, `status`, `current_content` (markdown), `current_revisio
```bash
<%= @curl %> -X POST \
-H "Content-Type: application/json" \
-d '{"title": "My Plan", "content": "# My Plan\n\nContent here."}' \
-d '{"title": "My Plan", "content": "# My Plan\n\nContent here.", "plan_type": "general"}' \
"<%= @base %>/api/v1/plans" | jq .
```

Optional fields: `plan_type` (string) — the name of a plan type to use. See [Plan Types](#plan-types) below.

### Update Plan

Update plan metadata (title, status, tags). Only fields included in the request body are changed.
Expand Down Expand Up @@ -80,6 +82,23 @@ Plans move through a lifecycle. **Keep the status current** — update it as the

When you create a plan, it starts as `brainstorm`. Promote it to `considering` when it's ready for review. Move to `developing` when implementation begins. Don't leave plans in stale states.

### Plan Types

Plan types categorize plans and provide default tags. When creating a plan, pass `plan_type` to associate it with a type.
<% if @plan_types.any? %>

**Available plan types:**

| Name | Description |
|------|-------------|
<% @plan_types.each do |pt| %>
| `<%= pt.name %>` | <%= pt.description.present? ? pt.description : "—" %> |
<% end %>
<% else %>

No plan types are currently configured.
<% end %>

### Get Versions

```bash
Expand Down
44 changes: 44 additions & 0 deletions spec/requests/agent_instructions_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
require "rails_helper"

RSpec.describe "Agent Instructions", type: :request do
describe "GET /agent-instructions" do
it "returns markdown content" do
get agent_instructions_path
expect(response).to have_http_status(:success)
expect(response.content_type).to include("text/markdown")
expect(response.body).to include("# CoPlan API")
end

it "includes plan types when they exist" do
create(:plan_type, name: "Design Doc", description: "For design documents")

get agent_instructions_path

expect(response.body).to include("### Plan Types")
expect(response.body).to include("Design Doc")
expect(response.body).to include("For design documents")
end

it "shows message when no plan types are configured" do
get agent_instructions_path

expect(response.body).to include("### Plan Types")
expect(response.body).to include("No plan types are currently configured")
end

it "lists multiple plan types sorted by name" do
create(:plan_type, name: "RFC")
create(:plan_type, name: "Design Doc")

get agent_instructions_path

body = response.body
expect(body.index("Design Doc")).to be < body.index("RFC")
end

it "documents plan_type in create plan section" do
get agent_instructions_path
expect(response.body).to include('"plan_type"')
end
end
end
18 changes: 11 additions & 7 deletions spec/requests/api/v1/plans_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,20 +65,24 @@
expect(body["current_revision"]).to eq(1)
end

it "create with plan_type_id" do
plan_type = create(:plan_type)
post api_v1_plans_path, params: { title: "Typed Plan", content: "# Typed", plan_type_id: plan_type.id }, headers: headers, as: :json
it "create with plan_type by name" do
plan_type = create(:plan_type, name: "design-doc")
post api_v1_plans_path, params: { title: "Typed Plan", content: "# Typed", plan_type: "design-doc" }, headers: headers, as: :json
expect(response).to have_http_status(:created)
body = JSON.parse(response.body)
expect(body["plan_type_id"]).to eq(plan_type.id)
expect(body["plan_type_name"]).to eq(plan_type.name)
expect(body["plan_type_name"]).to eq("design-doc")
end

it "create with invalid plan_type_id returns 422" do
post api_v1_plans_path, params: { title: "Bad Type", plan_type_id: "nonexistent-id" }, headers: headers, as: :json
it "create with unknown plan_type returns 422 with available types" do
create(:plan_type, name: "design-doc")
create(:plan_type, name: "rfc")
post api_v1_plans_path, params: { title: "Bad Type", plan_type: "nope" }, headers: headers, as: :json
expect(response).to have_http_status(:unprocessable_content)
body = JSON.parse(response.body)
expect(body["error"]).to include("plan_type_id")
expect(body["error"]).to include("nope")
expect(body["error"]).to include("design-doc")
expect(body["error"]).to include("rfc")
end

it "create without title fails" do
Expand Down
Loading