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
107 changes: 107 additions & 0 deletions docs/CHEAT_SHEET.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Talking to Claude — a cheat sheet

The global Claude in the dashboard (open it with **⌘K**, or the **Ask Claude**
button) is a *conductor*: you talk to it in plain English and it drives Corral for
you — starting projects, reviewing PRs, building images, running flows.

This page is a set of **things you can just say**, grouped by what you want to get
done. They're examples, not magic words — say them your own way. The repo names
here (`project-planner`, `invoice-service`, …) are made up; swap in yours.

> Tip: the more specific you are, the better. "Review PR #212 and post the
> findings as a comment" beats "look at that PR".

---

## Reviewing a pull request

Corral has two *different* things it can do to a PR. Knowing which you want gets
you the right result:

- **Full review** — Claude *reads* the PR (the diff, the risk analysis, the hot
spots) and writes up a review. It never runs the code. Fast.
- **Sandbox verify** — Claude spins up a throwaway sandbox on the PR's branch and
actually *runs* it: builds it, runs the tests, tries the app. Slower, but it
proves the change works.

You can ask for either — or **both at once**.

| Say this | What happens |
|---|---|
| *"Do a full review of PR #212 in `project-planner`."* | Full review only. Reads + writes a review. |
| *"Review PR #212 and post the findings as a comment."* | Full review, then drafts a PR comment for you to approve. |
| *"Verify PR #212 works in a sandbox."* | Sandbox verify only. Runs the branch in a container. |
| *"Do a full review of PR #212 **and** verify it works in a sandbox."* | Both — the review runs on the host while the sandbox builds and tests. They run at the same time and report back separately. |
| *"What's the risk on PR #212?"* | Runs the risk analysis and summarizes it. |

---

## Starting work in a sandbox

Sandboxes are isolated containers where Claude does actual code work — editing,
building, running — without touching your real machine.

| Say this | What happens |
|---|---|
| *"Start a project on `invoice-service` and add PDF export to invoices."* | Creates a sandbox on the repo, hands Claude the task, and starts it. |
| *"Create a project using both `invoice-service` and `project-planner`, and wire the planner to read invoices from the invoice API."* | A single sandbox with **both** repos checked out, working the cross-repo task. |
| *"Spin up a sandbox on the `feature/webhooks` branch of `invoice-service` and get the tests passing."* | Same, but on a specific branch. |
| *"Work GitHub issue #88 in `project-planner`."* | Creates a sandbox seeded from the issue and starts on it. |

> "Create a project" and "start a project" go together — Claude creates it *and*
> starts it, then hands your task to the sandbox's own Claude and supervises.

---

## Getting an app running to look at

| Say this | What happens |
|---|---|
| *"Get `project-planner` running so I can see it."* | Boots the app in a sandbox and points the **Live View** tab at the right port. |
| *"The image for `invoice-service` is missing — build it."* | Builds the repo's Docker image inside a sandbox and saves it as the repo's baseline so future projects reuse it. |
| *"Verify the Live View actually renders, don't just assume."* | Drives a real browser at the Live View to confirm it's not a blank page. |

---

## Checking on things

| Say this | What happens |
|---|---|
| *"What projects are running right now?"* | Lists projects and whether each is working, waiting, or idle. |
| *"Is the `project-planner` sandbox stuck?"* | Reads that sandbox's conversation and tells you what it's doing. |
| *"Show me the open PRs across my repos."* | The cross-repo PR inbox. |
| *"What flows do I have, and when did the triage one last run?"* | Lists automations/flows and recent runs. |
| *"Pull up the recent AI activity."* | Recent entries from the activity log. |

---

## GitHub housekeeping

| Say this | What happens |
|---|---|
| *"Create an issue in `invoice-service`: 'Refunds double-charge on retry', and start work on it."* | Files the issue, then spins up a sandbox to work it. |
| *"Merge PR #212 in `project-planner` with squash."* | Merges via your configured strategy (waits for CI, no force). |
| *"Leave a note on PR #212: watch the migration in 0012."* | A private, local note — **not** posted to GitHub. |
| *"Comment on PR #212 with the review."* | Posts a comment to GitHub (this one *is* public). |

---

## Doing several at once

The conductor is happy to fan out. You can stack asks in one sentence:

- *"Do a full review of PR #212, verify it in a sandbox, and while that runs, get
`project-planner` up in Live View."*
- *"Start projects on `invoice-service` and `project-planner`, and in each, run the
test suite and tell me what fails."*

It kicks each off, keeps an eye on them, and reports back as results land.

---

### Two quick reminders

- **The global Claude runs on your machine, not in a sandbox.** It orchestrates;
the *code work* happens inside sandboxes it creates. That's the safety boundary.
- **Nothing goes to GitHub unless you say so.** Reviews, notes, and analysis stay
local until you explicitly ask to comment, merge, or push.
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ you a dashboard to drive them. For quick help while you're in the app, press

## Start here

- [Cheat sheet](CHEAT_SHEET.md) — things you can just *say* to the dashboard's Claude.
- [Using Corral](usage.md) — install, the CLI, and the end-to-end workflow.
- [Security model](security.md) — the trust boundary, in detail.
- [Architecture](architecture.md) — how the pieces fit (proxy, firewall, container).
Expand Down
43 changes: 43 additions & 0 deletions host/skills/corral-api/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,49 @@ corral api POST /api/repos/<repoId>/analyze # git-only, synchronous
corral api GET /api/repos/<repoId>/analysis-status # analyzed / up-to-date?
```

### Full review of a PR (the "do a full review" ask)

A **full review** is a single workflow that ties the above together: it auto-runs
the block/heuristics + risk analysis if they're missing, then feeds those findings
plus the PR description and diff into the editable **`pr.review`** prompt (host
Claude, **read-only**) and stores a markdown review. Also fire-and-return:

```
corral api POST /api/prs/<prId>/review # start the full review (background)
corral api GET /api/prs/<prId>/analysis # poll → the "review" field {status}
corral api GET /api/prs/<prId>/review # read the markdown once status=done
# or, from the CLI, do all of that in one call:
corral pr review <prId> --wait # starts, waits, prints the review
```

To **post the review as a PR comment** (this writes to GitHub), take the markdown
from `/review` and send it:

```
corral api POST /api/prs/<prId>/comment -d '{"body":"<the review markdown>"}'
```

**Full review ≠ sandbox verify — know which the user wants.** These are two
different things and they are INDEPENDENT (you can run one, the other, or both at
once):

- **Full review** (this endpoint) — host, read-only. It READS the PR (diff +
prior analysis) and WRITES a review. It does NOT run the code, build, or test.
This is what "do a full review of PR #N" means.
- **Sandbox verify** — spins up a sandbox project on the PR's branch and actually
RUNS the change (build, tests, the app) inside a container. This is the
`POST /projects/create` (PR branch + a verify prompt) → `POST /p/<id>/start`
flow above. This is what "verify it works" / "run it in a sandbox" means.

Map the ask to the right action(s):

- *"Do a full review of PR #14."* → just `POST /api/prs/<prId>/review`.
- *"Verify PR #14 works in a sandbox."* → just the create+start sandbox verify.
- *"Do a full review AND verify PR #14 in a sandbox."* → do BOTH, and you can
kick them off at the SAME TIME — the review runs on the host while the sandbox
builds/tests. They don't depend on each other; start both, then report each
result as it lands.

## PR notes (private, local)

Notes are **local annotations stored only in Corral** — never posted to GitHub
Expand Down
57 changes: 57 additions & 0 deletions internal/dashboard/api_pr_review_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package dashboard

import (
"encoding/json"
"net/http"
"strings"
"testing"
)

// TestAPIPRReviewRoutes exercises the conductor-facing review surface: the stored
// review is null before a run, the analysis status carries a "review" field, and
// the start route is reachable (returns started, or 502 when claude is absent —
// either proves the route is wired, which is what we're testing here).
func TestAPIPRReviewRoutes(t *testing.T) {
srv, _, prID := apiTestServer(t)
defer srv.Close()
base := "/api/prs/" + itoa(prID)

// GET review before any run → {"review": null}.
code, body := apiReq(t, srv, http.MethodGet, base+"/review", "")
if code != http.StatusOK {
t.Fatalf("GET review = %d, want 200 (%s)", code, body)
}
var got struct {
Review *struct {
Markdown string `json:"markdown"`
} `json:"review"`
}
if err := json.Unmarshal([]byte(body), &got); err != nil {
t.Fatalf("decode review: %v (%s)", err, body)
}
if got.Review != nil {
t.Errorf("expected null review before a run, got %+v", got.Review)
}

// The analysis status must expose a "review" job alongside enrich + risk, so a
// conductor can poll it.
code, body = apiReq(t, srv, http.MethodGet, base+"/analysis", "")
if code != http.StatusOK {
t.Fatalf("GET analysis = %d, want 200", code)
}
for _, want := range []string{`"review"`, `"enrich"`, `"risk"`} {
if !strings.Contains(body, want) {
t.Errorf("analysis status missing %s: %s", want, body)
}
}

// POST review starts the job (200 started) or 502 if the claude CLI isn't on
// this machine — both mean the route is wired. A 404 would mean it isn't.
code, body = apiReq(t, srv, http.MethodPost, base+"/review", "{}")
if code != http.StatusOK && code != http.StatusBadGateway {
t.Fatalf("POST review = %d, want 200 or 502 (%s)", code, body)
}
if code == http.StatusOK && !strings.Contains(body, `"review"`) {
t.Errorf("started review response should name the kind: %s", body)
}
}
8 changes: 8 additions & 0 deletions internal/dashboard/automations_endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,14 @@ func (d *dashboardServer) handleAPIPRItem(w http.ResponseWriter, r *http.Request
d.handleAPIPREnrich(w, r, prID)
case action == "analyze" && r.Method == http.MethodPost:
d.handleAPIPRRiskStart(w, r, prID)
case action == "review" && r.Method == http.MethodPost:
d.handleAPIPRReviewStart(w, r, prID)
case action == "review" && r.Method == http.MethodGet:
d.handlePRReviewGet(w, r, prID)
case action == "comment" && r.Method == http.MethodPost:
// Post a comment on the PR (posts to GitHub via gh). Used to publish a
// full-review result: read GET .../review, then POST it here as {body}.
d.handlePRAction(w, r, prID, "comment")
case action == "analysis" && r.Method == http.MethodGet:
d.handleAPIPRAnalysisStatus(w, r, prID)
case action == "blocks" && r.Method == http.MethodGet:
Expand Down
107 changes: 107 additions & 0 deletions internal/dashboard/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2431,6 +2431,113 @@
}
}
},
"/api/prs/{id}/review": {
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"post": {
"summary": "Start a FULL REVIEW of a PR — fire and return",
"description": "Runs the full-review WORKFLOW in the BACKGROUND (returns immediately): auto-runs the block/heuristics + risk analysis if missing, then feeds those findings + the PR description + diff into the editable pr.review prompt (host Claude, read-only) and stores the markdown. This is a REVIEW ONLY — it reads the PR and writes a review; it does NOT run the code. To also run the change (build/tests) in an isolated container, ALSO start a sandbox verify (POST /projects/create with the PR branch + a verify prompt, then POST /p/{id}/start); the two are independent and can run at the same time. Poll GET /api/prs/{id}/analysis (the \"review\" field) for completion, then read the result from GET /api/prs/{id}/review.",
"responses": {
"200": {
"description": "Started",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"ok": {
"type": "boolean"
},
"kind": {
"type": "string"
},
"status": {
"type": "string"
}
}
}
}
}
},
"502": {
"description": "claude CLI unavailable"
}
}
},
"get": {
"summary": "Read a PR's stored full review",
"description": "The last computed full-review markdown (null if never run). Read after the review job finishes (poll GET /api/prs/{id}/analysis). The markdown can be posted as a PR comment via POST /api/prs/{id}/comment.",
"responses": {
"200": {
"description": "Review",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"review": {
"type": "object",
"nullable": true
}
}
}
}
}
}
}
}
},
"/api/prs/{id}/comment": {
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"post": {
"summary": "Post a comment on the PR (to GitHub)",
"description": "Posts a comment on the PR via gh. Use it to PUBLISH a full-review result: read GET /api/prs/{id}/review, then POST its markdown here as {body}. This writes to GitHub, so it is subject to the API-writes gate.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"body": {
"type": "string",
"description": "The comment markdown to post."
}
},
"required": [
"body"
]
}
}
}
},
"responses": {
"200": {
"description": "Posted"
},
"400": {
"description": "Not a GitHub remote"
}
}
}
},
"/api/prs/{id}/stack": {
"parameters": [
{
Expand Down
Loading
Loading