Plan and Build: Feature Request to EARS Spec and Tested Code

Workflow

Turns a feature request into an EARS behavioral spec with testable requirements, stops with clarifying questions when anything is unresolved, then implements it in a developer/evaluator loop whose independent evaluator re-runs the checks, proves every requirement's test fails on the old code and passes on the new, and rejects tampered tests before summarizing the change.

Usage

echo "<your request>" | octomind workflow plan-and-build

Reads your request from stdin. Add --dry-run to validate and print the plan without running any steps.

Pipeline

  1. 1 spec Sequential developer:spec

    <request> {{input}} </request> This is an AUTONOMOUS, non-interactive run — no human can answer questions. Write the spec for the request above, scoped to the change actually requested, then output the complete spec.md …

  2. 2 blocked Sequential developer:brief

    <spec> {{spec}} </spec> The spec above ended NEEDS CLARIFICATION — no implementation was attempted. Extract its open questions into a short numbered list the user can answer to unblock the build, then stop. Start with e…

  3. 3 build Loop max 3×
    • developer developer:general continue

      <spec> {{spec}} </spec> Implement the spec above end to end in the current project. Touch only what it requires, treat its Guardrails as hard constraints, and stop and report if a Guardrail would have to break. Prove ev…

    • brief developer:brief fresh

      <spec> {{spec}} </spec> Read the spec above and the current unstaged git changes, then brief what the diff actually does versus what each FR asks for. Output only the brief.

    • evaluator developer:general fresh

      <spec> {{spec}} </spec> <evidence> {{developer}} </evidence> <brief> {{brief}} </brief> You are the independent evaluator, in a fresh session. Trust nothing you did not run yourself: the developer's evidence and the bri…

  4. 4 summary Sequential developer:brief

    <spec> {{spec}} </spec> <last_evaluation> {{evaluator}} </last_evaluation> Read the spec above and the implementation in the current unstaged git changes, then produce a concise spec-versus-diff briefing for the human r…

  5. 5 stalled Sequential developer:brief

    <spec> {{spec}} </spec> <last_evaluation> {{evaluator}} </last_evaluation> The build loop ended WITHOUT evaluator approval — do not present this as success. Read the spec and the current unstaged changes, then report fo…

Definition

# Title: Plan and Build: Feature Request to EARS Spec and Tested Code
#
# Public workflow: turn a request into a behavioral spec, then implement that
# spec through a developer/evaluator loop that proves every requirement with a
# named, passing test. Graph routing: a spec that still needs clarification
# never reaches the build.
#
# Shape:
# spec (developer:spec, autonomous, spec-authoring skill preloaded — its
#   validator gates the spec when [skills] auto_validation is on) ->
#   SPEC STATUS: NEEDS CLARIFICATION -> blocked (questions for the human) -> end
#   SPEC STATUS: READY -> build loop (developer -> brief -> evaluator, max 3) ->
#     VERDICT: APPROVED -> summary -> end
#     cap reached or VERDICT: BLOCKED -> stalled (report for a human) -> end
#
# Requirement contract: the spec gives every requirement a stable ID (FR-001…)
# and proves each in its Acceptance section. The developer maps each FR to the
# test that proves it. The evaluator trusts nothing it did not run: it re-runs
# the verification itself, runs the mapped tests against the pre-change code
# (each must fail there — fail-to-pass), scans the test diff for tampering,
# checks the Guardrails mechanically against the diff, and sends back every
# residual risk from the brief that is real and fixable in scope. The brief's
# Exposure level (what the change touches) is reported, never looped on.
#
# Revert contract: the fail-to-pass check swaps changed source files back to
# HEAD in place, so the evaluator never uses git checkout/restore/reset/stash
# (the tree holds uncommitted work). It copies each file to a scratch directory
# first, restores from that copy, and compares sha256 hashes; a mismatch ends the
# loop with VERDICT: BLOCKED. The evaluator has no timeout and no retries on
# purpose: killing or re-running it mid-swap could leave old code behind.
#
# Tip: set `model` on the evaluator to a different provider than the developer
# — LLM judges favor output from their own model. Public roles only.

name            = "plan-and-build"
description     = "Turns a feature request into an EARS behavioral spec with testable requirements, stops with clarifying questions when anything is unresolved, then implements it in a developer/evaluator loop whose independent evaluator re-runs the checks, proves every requirement's test fails on the old code and passes on the new, and rejects tampered tests before summarizing the change."
entry           = "spec"
max_transitions = 4

# ── 1. Spec: request → behavioral spec ───────────────────────────────────────
[[steps]]
name    = "spec"
role    = "developer:spec"
session = "fresh"
skills  = ["spec-authoring"]
prompt  = """
<request>
{{input}}
</request>

This is an AUTONOMOUS, non-interactive run — no human can answer questions.
Write the spec for the request above, scoped to the change actually requested,
then output the complete spec.md content, ending with its SPEC STATUS line.
"""

# ── 2a. Blocked: the spec still needs a human ────────────────────────────────
[[steps]]
name    = "blocked"
role    = "developer:brief"
session = "fresh"
prompt  = """
<spec>
{{spec}}
</spec>

The spec above ended NEEDS CLARIFICATION — no implementation was attempted.
Extract its open questions into a short numbered list the user can answer to
unblock the build, then stop.

Start with exactly: `BLOCKED ON CLARIFICATION — no changes made.`
"""

# ── 2b. Build loop: developer ↔ evaluator ────────────────────────────────────
[[steps]]
name           = "build"
loop           = true
max_iterations = 3
exit_when      = { output = "evaluator", matches = '(?m)^VERDICT: (APPROVED|BLOCKED)' }

  [[steps.run]]
  name    = "developer"
  role    = "developer:general"
  session = "continue"
  prompt  = """
<spec>
{{spec}}
</spec>

Implement the spec above end to end in the current project. Touch only what it
requires, treat its Guardrails as hard constraints, and stop and report if a
Guardrail would have to break. Prove every requirement with a test: each FR and
each Acceptance item maps to a test that exercises it. An independent evaluator
will run every mapped test against the pre-change code, where each test for new
or changed behavior must fail — so assert the behavior itself, never the
implementation's own output. Never skip, delete, or loosen a test to get green.

Run the spec's verification command and iterate until it passes. End with:
- a requirement map, one line per FR: `FR-001 → <test name>` (or `manual: <check>`
  only where the spec's Verification says no command can prove it), plus one
  line per residual risk the evaluator returned and you closed: `R1 → <test name>`;
- the exact command you ran and its output as evidence.

Output only the work summary, the map, and the evidence — no preamble.
"""

  [[steps.run]]
  name    = "brief"
  role    = "developer:brief"
  session = "fresh"
  prompt  = """
<spec>
{{spec}}
</spec>

Read the spec above and the current unstaged git changes, then brief what the
diff actually does versus what each FR asks for. Output only the brief.
"""

  [[steps.run]]
  name    = "evaluator"
  role    = "developer:general"
  session = "fresh"
  prompt  = """
<spec>
{{spec}}
</spec>

<evidence>
{{developer}}
</evidence>

<brief>
{{brief}}
</brief>

You are the independent evaluator, in a fresh session. Trust nothing you did not
run yourself: the developer's evidence and the brief are leads, not proof. Run
these checks in order, by requirement ID.

A. VERIFY — run the spec's verification command yourself, in full. It must pass
   with no newly skipped or ignored tests. If it fails, report it and skip C.
B. MAP — every FR and every Acceptance item (Scenario, Property, Check) maps to a
   test in the developer's map, and that test exists and exercises the stated
   behavior. `manual:` is acceptable only where the spec's Verification says no
   command can prove it.
C. FAIL-TO-PASS — prove the mapped tests have teeth against the pre-change code:
   1. Record `git status --porcelain` and the sha256 of every modified or added
      non-test file; copy each one to a scratch directory (`mktemp -d`).
   2. Swap in the pre-change code, leaving test files alone: overwrite each
      modified file with `git show HEAD:<path>`, and move each added non-test
      file into the scratch directory. Never use git checkout, restore, reset, or
      stash.
   3. Run only the mapped tests. Each test for an ADDED or MODIFIED requirement
      must fail — an assertion failure, or a compile error because the behavior
      is missing. Each invariant test must still pass. Name every test that
      passes on the old code: it proves nothing.
   4. Restore every file from the scratch copy, then confirm the sha256 values
      and `git status --porcelain` equal step 1. If they do not, restore again;
      if they still do not, stop with `REVERT-FAILED: <path>` and VERDICT: BLOCKED.
D. TAMPERING — read the test-file diff. Fail on deleted or renamed-away tests;
   new skip, ignore, or only markers; assertions removed or loosened; the unit
   under test mocked out; expected values copied from the implementation's own
   output; and any source branch that recognizes a test environment or specific
   test inputs.
E. GUARDRAILS — compare `git status --porcelain` and `git diff --stat` against
   the spec: no file outside its scope, no new dependency in a manifest or
   lockfile when the spec forbids one, no protected surface changed.
F. RESIDUAL — for each open residual risk (R1, R2…) in the brief, open the code
   it cites and decide. Real (a reachable failure path with no guard and no test)
   and fixable inside the spec's scope → fail F, naming the ID, the path, and the
   guard plus test that would close it. Real, but closing it needs a change the
   spec or its Guardrails rule out → `ACCEPTED: R<n> — <reason>`, not a failure.
   Guarded, unreachable, or misread by the brief → `DROPPED: R<n> — <reason>`.
   The brief's Exposure level never fails F — it measures what the change
   touches, not what is left to fix.

List every failing check with specifics; this goes straight to the developer. If
A–F all pass, approve — do not invent criteria the spec never stated. A revert
failure is BLOCKED, never REVISE.

End with exactly one line: `VERDICT: APPROVED`, `VERDICT: REVISE`, or
`VERDICT: BLOCKED`. Nothing after it.
"""

# ── 3a. Summary: approved ────────────────────────────────────────────────────
[[steps]]
name    = "summary"
role    = "developer:brief"
session = "fresh"
prompt  = """
<spec>
{{spec}}
</spec>

<last_evaluation>
{{evaluator}}
</last_evaluation>

Read the spec above and the implementation in the current unstaged git changes,
then produce a concise spec-versus-diff briefing for the human reviewer. Add each
`ACCEPTED: R<n>` line from the last evaluation to QUESTIONS — a known risk left
open on purpose. If your Exposure is 🔴 HIGH, start with exactly:
`HIGH EXPOSURE — human review required before merge.`
Output only the briefing.
"""

# ── 3b. Stalled: loop hit its cap without approval ───────────────────────────
[[steps]]
name    = "stalled"
role    = "developer:brief"
session = "fresh"
prompt  = """
<spec>
{{spec}}
</spec>

<last_evaluation>
{{evaluator}}
</last_evaluation>

The build loop ended WITHOUT evaluator approval — do not present this as success.
Read the spec and the current unstaged changes, then report for a human decision:
what was implemented and verified, and every gap the evaluator still flags.

If the last evaluation ends with `VERDICT: BLOCKED`, start with exactly:
`BLOCKED — source files may not match the developer's last state; check git status before anything else.`
Otherwise start with exactly: `DID NOT CONVERGE — human review required.`
"""

# ── Graph routes ─────────────────────────────────────────────────────────────
# Anything other than an explicit READY falls through to `blocked` — fail safe.
[[edges]]
from = "spec"
to   = "build"
when = { matches = '(?m)^SPEC STATUS: READY' }

[[edges]]
from = "spec"
to   = "blocked"

[[edges]]
from = "blocked"
to   = "$end"

[[edges]]
from = "build"
to   = "summary"
when = { output = "evaluator", matches = '(?m)^VERDICT: APPROVED' }

[[edges]]
from = "build"
to   = "stalled"

[[edges]]
from = "summary"
to   = "$end"

[[edges]]
from = "stalled"
to   = "$end"