Slice 1 — Walking skeleton: shared core + planner lane, driven end-to-end from nibble #2
Labels
No labels
epic
in-review
ready-for-agent
ready-for-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
weiwen/sandcastle#2
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Parent
weiwen/sandcastle#1
What to build
The walking skeleton for the extracted sandcastle core, proven end-to-end through the read-only planner lane driven from
nibble. This slice establishes all shared infrastructure and the first consumer wiring; slices #2 and #3 add the write lanes on top without touching the infra again.Shared core (
weiwen/sandcastle):flake.nixin the shared shape (flake-utils.eachDefaultSystem+rust-overlay), inputsnixpkgs,flake-utils,rust-overlay,llm-agents(re-exported).lib.${system}.mkSandcastleImage { name, tag, extraContents ? [], extraEnv ? [], forgeUrl }— wrapsdockerTools.fakeNss+streamLayeredImageover the base package set +extraContents; sets PATH/HOME/cert/locale env plusextraEnv; entrypointsleep infinity, workdir/home/agent.sandcastlePackages(bash, coreutils, curl, findutils, gawk, git, gnugrep, gnused, jq, nix, nodejs, openssl, pkg-config, which,tea,cacert) +claude-code+ the three bakedwriteShellScriptBinhelpers:configure-tea(parameterized byforgeUrl, writes~/.config/tea/config.ymlfromFORGEJO_TOKEN/FORGEJO_USERat runtime),fmt(cargo clippy --fix --allow-dirty --allow-staged -- -D warnings && cargo fmt),check(cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test).apps.${system}.sandcastle— orchestrator as a self-containedbuildNpmPackagenode app bundling@ai-hero/sandcastle+zod+ the plan prompt; committedpackage-lock.json+npmDepsHash.loadConfig(tomlString) → ResolvedConfigstep producing{ repo, forgeHost, imageName, maxIterations, maxConcurrency, models: { planner, implementer, reviewer, prFixer } }, applying defaults and throwing on missingrepo/forgeHost. The loop runs the planner only this slice: reads config, builds a sandbox fromimageNamewith theconfigure-teaonSandboxReadyhook, runs the planner (model frommodels.planner), and parses the<plan>JSON via the schema. No execute phase yet.--repodropped from allteacalls (slug auto-derived from the sandbox remote); branch formatsandcastle/issue-{id}and label lifecycle hardcoded.First consumer (
nibble):flake.nixrewritten to the shared shape; adds thesandcastleinput (git+https://forge.weiwen.dev/weiwen/sandcastle.git); definespackages.sandcastleImage = sandcastle.lib.${system}.mkSandcastleImage { name = "sandcastle"; tag = "nibble"; extraContents = [ rustToolchain pkg-config libjxl libheif ]; forgeUrl = "https://forge.weiwen.dev"; }; re-exposesapps.sandcastle; addschecks.sandcastleImage; preserves the existing Playwright dev shell..sandcastle/withconfig.toml(schema below),.env(Anthropic + Forgejo secrets only),CODING_STANDARDS.md,.gitignore(.env,logs/,worktrees/). Nomain.mts,package.json,node_modules, orJustfile.originremote (git + jj) realigned from the internal SSH host tossh://forgejo@forge.weiwen.dev/weiwen/nibble.git.config.tomlschema (from PRD):Acceptance criteria
nix flake checkin the shared repo passes, including aloadConfigunit test covering: valid TOML → expectedResolvedConfig; omitted optional fields → documented defaults; missingrepoorforgeHost→ throws.nix run .#sandcastleImage | docker loadinnibblebuilds and loads thesandcastle:nibbleimage;claude-code,tea,configure-tea,fmt,checkare all onPATHinside it.nix flake checkinnibblepasses, includingchecks.sandcastleImage.nix run .#sandcastleinnibbleruns the planner and emits a valid<plan>JSON validated against the schema, with no--repoflags in anyteacall.nibble'sorigin(git and jj) resolves toforge.weiwen.dev;teaslug auto-derivation works from within a sandbox built off that remote.nibble's Playwright dev shell still functions (scripts/preview.mjsrunnable).nibble's.sandcastle/contains onlyconfig.toml,.env,CODING_STANDARDS.md,.gitignore— no JS or lockfiles.Blocked by
None - can start immediately.
Slice 1 implemented — walking skeleton + planner lane
Shared core pushed to
main(50532720); nibble wired and committed locally.sandcastle core: shared-shape
flake.nix(flake-utils + rust-overlay + llm-agents) exposinglib.${system}.mkSandcastleImage,apps.${system}.sandcastle(self-containedbuildNpmPackage),checks.${system}.sandcastle, re-exportedclaude-code. Base package set + bakedconfigure-tea/fmt/check. Orchestrator has a pureloadConfig(tomlString) → ResolvedConfig(defaults applied, throws on missingrepo/forgeHost) and runs the planner lane only. Plan prompt genericized (--repodropped everywhere; no hardcoded slug/URL).nibble: flake rewritten to the shared shape consuming
git+https://forge.weiwen.dev/weiwen/sandcastle.git;packages.sandcastleImage, re-exposedapps.sandcastle,checks.sandcastleImage; Playwright dev shell preserved;.sandcastle/= {config.toml, .env, CODING_STANDARDS.md, .gitignore}; git+jj origin realigned to forge.weiwen.dev.Acceptance criteria
nix flake checkpasses, incl. loadConfig unit tests (valid→config, defaults, missing→throws)nix run .#sandcastleImage | docker loadbuilds/loadssandcastle:nibble;claude,tea,configure-tea,fmt,checkall on PATHnix flake checkpasses, incl.checks.sandcastleImage(verified clean-room, no override)weiwen/nibble, failed only on a dummy token)scripts/preview.mjsrunnable).sandcastle/contains only config.toml, .env, CODING_STANDARDS.md, .gitignorenix run .#sandcastlefull planner run — pending maintainer secrets: needs realANTHROPIC_API_KEY/OAuth +FORGEJO_TOKEN/FORGEJO_USERin.sandcastle/.env(a real Claude API call). Verified up to that boundary: config loads, bundled prompt resolves, planner/sandbox start.Notes
fmtcollided with coreutils'fmton PATH (evie avoided this viajust fmt). The shared flake now ordersextraContents+ baked helpers ahead of the base set sofmt/checkresolve to our scripts.checks.clippydeferred on nibble: nibble has pre-existing clippy debt (type_complexity,too_many_arguments,manual_contains,collapsible_if,useless_vec,items_after_test_module). Gating flake check on it would fail. Left for a separate cleanup — required before the write lanes (which runcheck) land.