Skip to content
Download PDF

The Sandbox ​

The sandbox is a one-command, real-scenario tour of the entire forge lifecycle. It builds the actual forge binary, stages a realistic sample project, and drives it through import, scoring, every enforced gate, and the dashboard build — asserting at each step that the gates fire exactly the way they do in production. It is both the fastest way to see forge work end to end and the repository's end-to-end safety net.

Runs from a source checkout

The sandbox lives in the forge source tree (scripts/sandbox/, wired to make targets), not in an installed project. Clone the repository to run it. Installed users get the same behavior through the individual commands documented across this site — the sandbox just wires them into one realistic run.

Two entry points ​

Both drive the same 9-step scenario against the same fixtures, so they never drift:

CommandWhat it is
make sandboxHuman-drivable. Builds the binary, runs the full lifecycle, and leaves the artifacts under scripts/sandbox/out/work/ for you to open and inspect.
make sandbox-cleanThe same run with a wiped output directory first (run.sh --clean). Use it when a previous run left stale state.
make e2eThe hermetic, CI-gated form: a tag-gated Go test (go test -tags e2e ./e2e/...) that runs the identical scenario in a temp directory with hard assertions and no persisted artifacts.

The script runs every step even if an earlier one failed, prints one PASS/FAIL line per assertion, and exits non-zero if any assertion failed — so a single run reports every problem rather than stopping at the first.

The 9-step lifecycle ​

Each step asserts a specific, observable outcome. The gate steps are the point: they deliberately expect a non-zero exit to prove the gate blocks.

  1. build — compile the forge binary to out/bin/forge.
  2. stage — copy the acme-shop fixture into a fresh work directory.
  3. import — map the .specify/ project into .forge/ and assert the change directories were created. See Migration Importer.
  4. analyze — run the portfolio health rollup and a single-spec score, asserting the quality score is non-trivial.
  5. secscan — scan the planted secret and assert the guardrails gate BLOCKS (exit non-zero).
  6. secscan waive + rescan — waive the finding as a human would, and assert the rescan is now clean.
  7. budget — accumulate spend, then cross the ceiling and assert the spend budget reports BUDGET EXCEEDED (exit non-zero).
  8. verify breaker — force a verify failure so the circuit-breaker opens (exit non-zero), then --reset-breaker and assert it clears.
  9. trace + docs — assert the session timeline aggregated the gate events, then build the offline dashboard and assert index.html was written.

The gates are supposed to fail

Steps 5, 7, and 8 assert a non-zero exit. That is the sandbox proving the enforced gates actually stop the line — a secret is blocked, a budget ceiling is enforced, a failing verify escalates. A sandbox where every step returns 0 would mean the gates are asleep.

The sample project ​

The fixture is a small but realistic .specify/ project, acme-shop, committed under scripts/sandbox/fixtures/:

text
acme-shop/
  .specify/
    memory/constitution.md            # a few real principles
    specs/001-checkout/{spec,plan,tasks}.md
    specs/002-auth/{spec,plan,tasks}.md
  src/
    checkout.js                       # secret-free
    config.js                         # a planted, clearly-fake AWS key

src/config.js contains an intentionally-fake AWS-style key. It is the trigger for step 5 — the thing the guardrails gate must catch. Everything else is authored so analyze produces a real, non-trivial quality score.

Inspecting the artifacts ​

make sandbox leaves a fully-populated .forge/ behind so you can explore the exact state the gates produced:

text
scripts/sandbox/out/work/
  .forge/
    changes/001-checkout/
      budget.yaml            # the spend ledger
      secscan.yaml           # the waiver from step 6
      session-log.jsonl      # the attributed timeline
      verify-attempts.yaml   # the circuit-breaker state
    site/index.html          # the generated dashboard

The run prints the path to the generated dashboard at the end — open it in a browser to see the same spec-viewer a real project produces.

Sandbox, selftest, and e2e ​

Forge ships three overlapping checks. They are complementary, not redundant:

CheckCommandScope
Selftestforge doctor --selftestFast in-process smoke test that the core commands are wired, using tiny synthetic stubs.
Sandboxmake sandboxDrives the real binary through the full lifecycle against a realistic project, asserting each gate. Leaves artifacts to inspect.
e2emake e2eThe sandbox scenario as a hermetic, CI-gated Go test — same assertions, no persisted state.

Reach for the selftest to check the plumbing, the sandbox to see and inspect the experience, and make e2e in CI.

Extending it ​

The fixture and the driver are plain, versioned files:

  • Add a spec or feature under scripts/sandbox/fixtures/acme-shop/.specify/.
  • Add or adjust a step in scripts/sandbox/run.sh (each step asserts an exit code and records PASS/FAIL).
  • Mirror any new assertion in e2e/sandbox_test.go so CI covers it too.

Keep the two entry points in lockstep: whatever the human sandbox demonstrates, the hermetic e2e test should assert.

Next steps ​