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
85 changes: 74 additions & 11 deletions examples/implement-enhanced.sc
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
//> using dep "org.virtuslab::orca:0.0.11"
//> using jvm 21

/** Persistent planning + coding flow, enhanced with a plan review and a shared
* codebase brief.
/** Persistent planning + coding flow that lands the work on its own branch and
* opens a pull request.
*
* Same backbone as `implement.sc` (autonomous planning → persistent
* `.orca/plan-<hash>.md` → per-task implement + review-and-fix loop), with two
* steps chained onto planning — both on the planner's read-only session, so
* they cost no extra exploration:
* `.orca/plan-<hash>.md` → per-task implement + review-and-fix loop), enhanced
* the same way as before — both on the planner's read-only session, so they
* cost no extra exploration:
*
* 1. **`.reviewed(claude)`** — the planner critiques its own draft and
* returns an improved plan (missing/duplicated tasks, ordering, vague
Expand All @@ -17,24 +17,67 @@
* `plan.taskPrompt(task)` prepends it to every task so the cold-starting
* coding agents don't re-discover what the planner already learned.
*
* The brief rides in the plan file (a trailing `## Brief` section), so
* `recoverOrCreate` / `implementTaskLoop` persist, reuse, and remove it with
* the file — no sidecar.
* On top of that, like `issue-pr-bugfix.sc` but prompt-driven and with no
* failing-test/CI stages, the flow runs on a fresh branch and finishes with a
* PR:
*
* Swap to `.briefed(claude).reviewed(claude)` to also review the brief; both
* are well-typed.
* 1. On a fresh run it stashes any WIP, has haiku name a branch from the
* prompt, and switches to it.
* 1. Plans + implements all tasks (each committed on the branch).
* 1. Pushes and opens a PR with a haiku-generated title + description from
* the full branch diff. A human picks the PR up from there.
*
* Resume: an interrupted run leaves you on the orca branch with the committed
* plan file, so re-running continues the loop in place (the `recover` guard
* skips the stash/name/checkout). A resumed run ends on the orca branch — it
* can't recover the original starting branch; a fresh run returns to where it
* started. Re-running a finished flow is a no-op: `createPr` reports
* `PrAlreadyExists`, which the flow tolerates.
*
* ```bash
* scala-cli run implement-enhanced.sc -- "Add a multiply function to the calculator crate"
* ```
*
* Requires `claude` logged in and `cargo` on PATH.
* Requires `claude` logged in, `cargo` on PATH, and `gh` authenticated.
*/

import orca.{*, given}
// Not in the `orca.*` export wildcard; imported by name to tolerate a re-run
// against a branch whose PR a prior run already opened.
import orca.tools.PrAlreadyExists

/** Structured branch-name suggestion — a single kebab-case slug from haiku. */
case class BranchName(name: String) derives JsonData

flow(OrcaArgs(args)):
val planFile = Plan.defaultPath(userPrompt)
// Captured before any branch switch so the flow can return here at the end.
val startBranch = git.currentBranch()

// Fresh run only: stash WIP, name a branch, switch to it. On resume the plan
// file is already committed on the orca branch we're sitting on, so `recover`
// short-circuits this block and the loop below continues in place — naming a
// branch here would otherwise create a second one (haiku isn't deterministic).
if Plan.recover(planFile).isEmpty then
// Stash pre-existing edits before switching branches, or they'd ride onto
// the orca branch. `ensureClean` emits a Step the user can act on
// (`git stash pop`) once the flow finishes.
val _ = git.ensureClean("orca: pre-implement stash")
val branchName = stage("Name a branch"):
claude.haiku
.resultAs[BranchName]
.autonomous
.run(
s"""Suggest a git branch name for the task below. Use a short,
|kebab-case slug prefixed with `orca/` (e.g.
|`orca/add-multiply-fn`). Output the name only.
|
|Task: $userPrompt""".stripMargin
)
._2
.name
stage(s"Create branch $branchName"):
git.checkoutOrCreate(branchName)

// Plan → review → brief, all on one read-only planner session. On resume the
// persisted plan (with its brief) is reused without re-planning.
Expand Down Expand Up @@ -65,3 +108,23 @@ flow(OrcaArgs(args)):
lintCommand = Some("cargo check --tests"),
lintLlm = Some(claude.haiku)
)

// The task loop has removed the plan file and committed that removal, so the
// branch now holds only the implementation. Push it and open the PR from the
// full branch diff.
stage("Push branch"):
git.push().orThrow

val prSum = stage("Generate PR title and description"):
summarisePr(llm = claude.haiku, diff = git.diffVsBase(git.defaultBase()))

stage("Open PR"):
gh.createPr(title = prSum.title, body = prSum.body) match
case Left(_: PrAlreadyExists) => () // a prior run already opened it
case Left(e) => throw e
case Right(_) => () // the tool logs the URL

// Return to the start branch so any stashed WIP pops back onto it. On a
// resumed run this is the orca branch itself, so it's a no-op.
stage(s"Return to $startBranch"):
git.checkout(startBranch).orThrow
33 changes: 33 additions & 0 deletions runner/src/test/scala/flowtests/FlowCompilesTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ package flowtests
// regressed. Fix the API, not the test.

import orca.{*, given}
// Deliberately NOT in the `orca.*` export wildcard: a recoverable `createPr`
// failure, referenced by name in `examples/implement-enhanced.sc`. Pinning it
// here keeps the "import it explicitly" requirement honest.
import orca.tools.PrAlreadyExists

case class PlanTask(branchName: String, description: String) derives JsonData
case class FlowPlan(tasks: List[PlanTask]) derives JsonData
case class BranchSlug(name: String) derives JsonData

object FlowCanary:

Expand Down Expand Up @@ -134,6 +139,34 @@ object FlowCanary:
gh.writeComment(pr, "pr comment")
gh.updatePr(pr, "new title", "new body")

/** Branch + PR surface — exercised by `examples/implement-enhanced.sc`. Pins
* the resumable-branch ops (`recover` guard, `ensureClean`,
* `checkoutOrCreate`, `currentBranch`, `checkout`), the `createPr` `Either`
* with its recoverable `PrAlreadyExists`, and the merge-base diff that feeds
* `summarisePr`. A signature drift surfaces here instead of at the next live
* run.
*/
def branchAndPrSurface(): Unit =
flow(OrcaArgs()):
val planFile = Plan.defaultPath(userPrompt)
val startBranch = git.currentBranch()
if Plan.recover(planFile).isEmpty then
val _ = git.ensureClean("orca: pre-implement stash")
val branch =
claude.haiku.resultAs[BranchSlug].autonomous.run(userPrompt)._2.name
git.checkoutOrCreate(branch)
stage("pr"):
git.push().orThrow
val summary = summarisePr(
llm = claude.haiku,
diff = git.diffVsBase(git.defaultBase())
)
gh.createPr(title = summary.title, body = summary.body) match
case Left(_: PrAlreadyExists) => ()
case Left(e) => throw e
case Right(_) => ()
git.checkout(startBranch).orThrow

/** Planning grid surface; exercised across `examples/`. Pins the full `mode ×
* operation` grid: every cell returns `Sessioned[B, <result>]` where the
* result is `Plan` (`from`), `Verdict[Plan]` (`assessThenPlan`), or `Triage`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ class TerminalEventListenerTest extends munit.FunSuite:
val ps = new PrintStream(buf)
val output =
new TerminalOutputState(ps, useColor = false, animated = animated)
val listener = new TerminalEventListener(output, useColor = listenerUseColor)
val listener =
new TerminalEventListener(output, useColor = listenerUseColor)
events.foreach(listener.onEvent)
buf.toString

Expand Down
Loading