feat(template): rename lez-framework to spel; delegate scaffolding to spel init#199
feat(template): rename lez-framework to spel; delegate scaffolding to spel init#199vpavlin wants to merge 7 commits into
Conversation
Updates the two pinned defaults in `constants.rs`: - DEFAULT_LEZ: v0.2.0-rc1 (35d8df0) → v0.1.2 (cf3639d), the first published LEZ release. - DEFAULT_SPEL: v0.2.0-rc.5 (1db7c5f) → v0.5.0 (73fc462), released today and aligned with LEZ v0.1.2. Circuits version (0.4.1) is unchanged — LEZ v0.1.2's flake.lock pins the same circuits commit (d6cf41f) as before. Closes logos-co/ecosystem#128 (upgrade spel + scaffold to pin LEZ v0.1.2). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…el init ## What changed - `lgs new --template spel` (new) calls `spel init <name> --lez-tag <DEFAULT_LEZ.tag>` to scaffold the project, then layers scaffold.toml and AI skills on top. Scaffold no longer owns any SPEL template files. - `lgs new --template lez-framework` prints a deprecation warning and maps to `spel`. Existing projects with `framework.kind = "lez-framework"` in scaffold.toml continue to work unchanged. - `FRAMEWORK_KIND_SPEL = "spel"` added to constants. - `lgs build idl` for spel projects delegates to the vendored spel binary (`spel generate-idl`) instead of scaffold's internal IDL pipeline. - `lgs build client` for spel projects delegates to `spel ffi-gen`. - Deploy-cache IDL hashing treats `spel` projects the same as `lez-framework` (IDL is a required deploy artifact). - `templates/lez-framework/` removed entirely — spel init is the authoritative scaffolder for framework projects. - `skills/lez-framework-template/` renamed to `skills/spel-template/` with updated content using spel vocabulary (#[spel_program], spel.toml, make targets, spel CLI commands). ## Why The `lez-framework` template in scaffold was broken (still referenced jimmy-claw/lez-framework) and duplicated template logic that belongs to spel. Scaffold's role is now minimal: scaffold.toml, AI skills, and version pinning — program scaffolding is owned by spel. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Replaces the broken in-tree lez-framework template with a thin delegation to the external spel CLI for scaffolding framework projects. Scaffold now only owns version pinning, scaffold.toml, and AI skills; program scaffolding (Cargo workspace, IDL gen, FFI gen) is handled by spel init/spel generate-idl/spel ffi-gen. The legacy lez-framework template name remains accepted but emits a deprecation warning and is silently mapped to spel.
Changes:
- New
--template spel(and deprecation aliaslez-framework) shells out tospel initand layers scaffold state on top; LEZ pin downgraded tov0.1.2, spel pin bumped tov0.5.0. lgs build idlandlgs build clientdelegate tospel generate-idl/spel ffi-genfor spel projects; deploy-cache hashing now requires an IDL for bothlez-frameworkandspelkinds.- Deleted the entire
templates/lez-framework/tree and renamedskills/lez-framework-template/→skills/spel-template/with rewritten content.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/commands/new.rs | Splits cmd_new_inner into cmd_new_spel (delegates to spel init) and cmd_new_default; adds find_spel_on_path and build_scaffold_config helpers. |
| src/commands/build.rs | Adds FRAMEWORK_KIND_SPEL branch that runs IDL gen but skips client gen (left to project Makefile). |
| src/commands/idl.rs | Routes spel projects to spel generate-idl; updates error message to mention spel. |
| src/commands/client.rs | Routes spel projects to spel ffi-gen and std::process::exit(0)s mid-helper; updates error message. |
| src/commands/run_state.rs | Treats both lez-framework and spel projects as IDL-required when computing program hashes. |
| src/commands/init.rs | Test updated to expect spel-template skill in place of lez-framework-template. |
| src/constants.rs | Adds FRAMEWORK_KIND_SPEL; bumps DEFAULT_SPEL to v0.5.0 and changes DEFAULT_LEZ to v0.1.2. |
| src/template/project.rs | Removes the lez-framework-specific overlay test and drops the variant from the no-self-patch test. |
| src/template/skills.rs | Test updated for renamed skill directory. |
| templates/lez-framework/** | Entire template tree deleted. |
| skills/lez-framework-template/SKILL.md | Deleted. |
| skills/spel-template/SKILL.md | New skill describing the spel-based workflow. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| FRAMEWORK_KIND_SPEL => { | ||
| // For spel projects, FFI/client gen is handled by `spel ffi-gen` | ||
| // (or the project Makefile's `make ffi`). Delegate directly. | ||
| cmd_spel(vec!["ffi-gen".to_string()])?; | ||
| std::process::exit(0); | ||
| } |
Addressed Copilot review comment: `std::process::exit(0)` inside a helper returning `DynResult<Project>` was surprising and bypassed caller cleanup. Spel dispatch now happens at call-site in `build_clients_for_current_project` and `generate_clients_from_current_idl` before reaching the lez-framework-only `require_lez_framework_project` helper. Also fixes two rustfmt failures flagged by CI on this branch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three CLI tests expected old `lez-framework`-only error strings and the old `lez-framework-template` skill name. Updated to match the new error messages (which now mention both `spel` and `lez-framework`) and the renamed `spel-template` skill. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| let cwd = env::current_dir()?; | ||
| let status = std::process::Command::new(&spel_bin) | ||
| .arg("init") | ||
| .arg(&cmd.name) | ||
| .arg("--lez-tag") | ||
| .arg(DEFAULT_LEZ.tag) | ||
| .current_dir(&cwd) | ||
| .status() | ||
| .context("failed to launch spel init")?; | ||
| if !status.success() { | ||
| anyhow::bail!("spel init failed"); | ||
| } |
| fn find_spel_on_path() -> anyhow::Result<std::path::PathBuf> { | ||
| let path_var = std::env::var_os("PATH").unwrap_or_default(); | ||
| for dir in std::env::split_paths(&path_var) { | ||
| let candidate = dir.join("spel"); | ||
| if candidate.is_file() { | ||
| return Ok(candidate); | ||
| } | ||
| } | ||
|
|
||
| Ok(()) | ||
| anyhow::bail!("spel not found on PATH") | ||
| } |
| other => { | ||
| bail!("unsupported template `{other}`. Expected `default` or `lez-framework`.") | ||
| bail!("unsupported template `{other}`. Expected `default` or `spel`.") | ||
| } |
| // spel init created `target/`; layer scaffold state on top. | ||
| fs::create_dir_all(target.join(".scaffold/state"))?; | ||
| fs::create_dir_all(target.join(".scaffold/logs"))?; | ||
|
|
||
| let cfg = build_scaffold_config(cmd, FRAMEWORK_KIND_SPEL, bootstrap_cache); | ||
| write_text(&target.join("scaffold.toml"), &serialize_config(&cfg)?)?; | ||
| ensure_scaffold_in_gitignore(target)?; | ||
| apply_skills(target)?; |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Error message for unknown template now mentions the deprecated `lez-framework` alias so users know it exists. - `--lez-path` combined with `--template spel` now fails with a clear message (was silently recorded in scaffold.toml but never forwarded to `spel init`). - `--vendor-deps` combined with `--template spel` now fails with a clear message (was silently writing non-existent paths into scaffold.toml). - `find_spel_on_path` now also probes `spel.exe` on Windows. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| let template_variant = match cmd.template.as_str() { | ||
| FRAMEWORK_KIND_DEFAULT | FRAMEWORK_KIND_LEZ_FRAMEWORK => cmd.template.clone(), | ||
| FRAMEWORK_KIND_DEFAULT => cmd.template.clone(), | ||
| FRAMEWORK_KIND_SPEL => cmd.template.clone(), | ||
| FRAMEWORK_KIND_LEZ_FRAMEWORK => { | ||
| eprintln!( | ||
| "warning: template `lez-framework` is deprecated; use `--template spel` instead." | ||
| ); | ||
| FRAMEWORK_KIND_SPEL.to_string() | ||
| } | ||
| other => { | ||
| bail!("unsupported template `{other}`. Expected `default` or `lez-framework`.") | ||
| bail!( | ||
| "unsupported template `{other}`. \ | ||
| Expected `default` or `spel` (or the deprecated alias `lez-framework`)." | ||
| ) | ||
| } |
| let spel_bin = find_spel_on_path().context( | ||
| "spel binary not found on PATH.\n\ | ||
| Install it first:\n \ | ||
| cargo install --git https://github.com/logos-co/spel.git --tag v0.5.0 spel", | ||
| )?; |
| fn find_spel_on_path() -> anyhow::Result<std::path::PathBuf> { | ||
| let path_var = std::env::var_os("PATH").unwrap_or_default(); | ||
| for dir in std::env::split_paths(&path_var) { | ||
| let candidate = dir.join("spel"); | ||
| if candidate.is_file() { | ||
| return Ok(candidate); | ||
| } | ||
| // On Windows executables carry a .exe suffix. | ||
| #[cfg(target_os = "windows")] | ||
| { | ||
| let candidate_exe = dir.join("spel.exe"); | ||
| if candidate_exe.is_file() { | ||
| return Ok(candidate_exe); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Ok(()) | ||
| anyhow::bail!("spel not found on PATH") |
| // Both lez-framework and spel projects require an IDL file at deploy time. | ||
| let is_lez_framework = matches!( | ||
| project.config.framework.kind.as_str(), | ||
| FRAMEWORK_KIND_LEZ_FRAMEWORK | FRAMEWORK_KIND_SPEL | ||
| ); |
- Install hint in find_spel_on_path error now uses DEFAULT_SPEL.tag instead of a hard-coded version string that would drift on bumps. - check_spel_version() warns when the installed spel version does not match DEFAULT_SPEL.tag, so mismatches surface before `lgs setup`. - available_templates() now includes "spel" explicitly; after removing the lez-framework template directory it was no longer discoverable from disk, so --template help advertised only "default". - Renamed is_lez_framework -> requires_idl in run_state.rs; the old name implied the flag only covered lez-framework, but it also matches spel. Updated the inline comment at the use site to match. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| let stdout = String::from_utf8_lossy(&output.stdout); | ||
| if !stdout.contains(DEFAULT_SPEL.tag) { | ||
| eprintln!( | ||
| "warning: installed spel version ({}) does not match the expected {} pinned by scaffold.\n\ | ||
| This may cause unexpected behaviour. Install the pinned version with:\n \ | ||
| cargo install --git https://github.com/logos-co/spel.git --tag {} spel", | ||
| stdout.trim(), | ||
| DEFAULT_SPEL.tag, | ||
| DEFAULT_SPEL.tag, | ||
| ); | ||
| } |
| let spel_bin = find_spel_on_path().with_context(|| { | ||
| format!( | ||
| "spel binary not found on PATH.\n\ | ||
| Install it first:\n \ | ||
| cargo install --git https://github.com/logos-co/spel.git --tag {} spel", | ||
| DEFAULT_SPEL.tag | ||
| ) | ||
| })?; | ||
| check_spel_version(&spel_bin); | ||
|
|
||
| if cmd.lez_path.is_some() { | ||
| anyhow::bail!( | ||
| "`--lez-path` is not supported with `--template spel`.\n\ | ||
| `spel init` fetches LEZ via `--lez-tag`; a local path override is not forwarded.\n\ | ||
| Use `--template default` if you need a local LEZ checkout." | ||
| ); | ||
| } | ||
| if cmd.vendor_deps { | ||
| anyhow::bail!( | ||
| "`--vendor-deps` is not supported with `--template spel`.\n\ | ||
| Vendoring is managed by `spel init` and `lgs setup`, not by scaffold directly.\n\ | ||
| Use `--template default` if you need vendored deps." | ||
| ); | ||
| } |
| anyhow::bail!("spel init failed"); | ||
| } | ||
|
|
||
| // spel init created `target/`; layer scaffold state on top. |
|
@vpavlin wow, copilot really tried hard to review your PR |
|
is it ready to review? quickly tried:
|
Review + hands-on dogfoodingReviewed the diff and then actually ran the affected flows end-to-end (built the CLI, installed 🔴 Blocker 1 —
|
| Producer / consumer | Path |
|---|---|
scaffold scaffold.toml framework.idl.path |
idl/ → expects idl/<stem>.json |
deploy-cache hashing (run_state.rs ~L118, tightened in this PR) |
reads idl/<stem>.json, bails if missing for spel |
spel spel.toml / make idl |
<root>/spelproj-idl.json |
spel generate-idl (what lgs build idl invokes) |
prints IDL to stdout, writes no file |
Reproduced in a generated spel project (vendored spel binary in place):
lgs build idl→ runsspel generate-idl, dumps JSON to stdout, creates no file, exits 0.src/commands/idl.rsreturnscmd_spel(["generate-idl"]), andcmd_speluses.status()(inherits stdout) — it never redirects toidl/<stem>.jsonthe way the lez-framework branch writes its IDL.lgs build client→spel ffi-gen→Error reading IDL '…/spelproj-idl.json': No such file or directory, exit 1.lgs deploy/lgs run→compute_program_hashesrequiresidl/<stem>.json→ would bail: "expected IDL file …/idl/spelproj.json … is missing; runlgs build idlfirst" — andlgs build idlnever produces it. Dead end.
So the Test Plan item "lgs build idl … calls spel generate-idl" is true but functionally useless: nothing is persisted, and the deploy gate this PR adds then makes deploy impossible. Fix: capture spel generate-idl stdout → write idl/<stem>.json (as the lez-framework branch does), or point framework.idl.path at spel's <root>/spelproj-idl.json convention and teach run_state to read that name.
🔴 Blocker 2 — circuits pin (0.4.1) is incompatible with the bumped spel/LEZ pins (they need 0.4.2)
constants.rs bumps DEFAULT_LEZ → v0.1.2 and DEFAULT_SPEL → v0.5.0 but leaves DEFAULT_CIRCUITS_VERSION = "0.4.1".
- Both LEZ v0.1.2's and spel v0.5.0's
Cargo.lockpulllogos-blockchainrev1da154c…, whose build script asserts circuitsv0.4.2. - Reproduced:
cargo install --git …/spel.git --tag v0.5.0 spelagainst circuits 0.4.1 → panic: "The logos-blockchain-circuits directory … is version 'v0.4.1', but version 'v0.4.2' is expected." setup.rsprovisions circuits at the 0.4.1 pin and uses it for the LEZ-sequencer, wallet and vendored-spel builds.lgs buildwas observed downloading…/v0.4.1/…before the (long) sequencer build — it hits the same assertion.
The doc comment derives 0.4.1 from LEZ's flake.lock (nix), but scaffold builds via Cargo, which needs 0.4.2. Net effect: lgs setup / lgs build fail on a clean machine for both default and spel projects. The comment even says "bump this in lock-step with DEFAULT_LB_PIN / DEFAULT_LEZ." Fix: DEFAULT_CIRCUITS_VERSION = "0.4.2" and verify a from-scratch lgs setup.
🟠 Medium
3. check_spel_version warns on every spel project, even when correct. spel --version (v0.5.0) prints usage to stderr and exits 1 with empty stdout; new.rs greps output.stdout for "v0.5.0" → always false. Live output during lgs new --template spel:
warning: installed spel version () does not match the expected v0.5.0 pinned by scaffold.
Note the empty (). Read stderr too (or call a real version subcommand) before deciding to warn.
4. cache_root portability regression — affects default too. The build_scaffold_config extraction changed the no---cache-root default from String::new() to the absolute bootstrap path (new.rs ~L292). Confirmed: a generated default project's scaffold.toml now contains cache_root = "/root/.cache/logos-scaffold". The deleted comment explicitly kept it empty "so scaffold.toml stays portable." This machine-specific path now ships in every committed config, not just spel.
🟡 Low
5. Flag-validation ordering. --vendor-deps / --lez-path with --template spel report "spel not found on PATH" instead of the intended "flag not supported" error, because find_spel_on_path() / check_spel_version run before the flag checks in cmd_new_spel. Validate args first (fail fast regardless of environment).
6. Generated project's spel deps don't match the recorded pin. spel init (called with only --lez-tag) emits spelproj_ffi → spel-framework-core tag="v0.4.0" and examples → branch="main", while scaffold.toml records spel v0.5.0. branch="main" is a moving target that defeats the reproducible pinning that's scaffold's job. Consider passing --spel-tag to match. (Partly upstream, but it's the seam this PR introduces.)
7. No test coverage for the new path. There are no new-command integration tests at all, and cmd_new_spel / the deprecation mapping / the flag-bail paths are untested; the removed lez_framework_overlay unit test wasn't replaced.
8. Doc/UX nits. skills/spel-template/SKILL.md L116-118 uses bare spel -- initialize … (the -- is the lgs passthrough separator), contradicting its own gotcha to use lgs spel -- …; L105 says "lgs build runs make build" (it runs scaffold's workspace build, not make). lgs new --template spel prints two separate "Next steps" blocks (spel's + scaffold's).
Flows exercised
new --template default ✅ (cache_root regression) · new --template spel ✅ delegates (warning #3, IDL #1, deps #6) · new --template lez-framework ✅ deprecation→spel · bad template ✅ correct error · spel + --vendor-deps/--lez-path default, spel · build idl 🔴 stdout, no file · build client 🔴 missing IDL · build/setup 🔴 circuits skew · deploy-cache requires IDL 🔴 would bail · init skills→spel-template ✅ · 342 unit + changed integration tests ✅.
Bottom line: the headline happy path (lgs new --template spel → buildable, deployable project) doesn't work today — it breaks at lgs setup (circuits 0.4.1 vs 0.4.2) and again at lgs build idl/deploy (IDL location). Both look like small, targeted fixes.
🤖 Reviewed via hands-on dogfooding in a fresh environment. Generated with Claude Code.
danisharora099
left a comment
There was a problem hiding this comment.
LGTM on the direction - spel owns scaffolding, scaffold owns pins/orchestration. Agree with @weboko: IDL output doesn’t land where deploy expects, and circuits 0.4.1 vs 0.4.2 blocks setup on a clean box. Fix those two (+ cache_root / version warning if easy)
Summary
lgs new --template spel(new default for framework projects) callsspel init <name> --lez-tag <lez-tag>, then layersscaffold.tomland AI skills on top. Scaffold owns no SPEL template files.lgs new --template lez-frameworkprints a deprecation warning and maps tospel. Existingscaffold.tomlfiles withframework.kind = "lez-framework"continue to work.FRAMEWORK_KIND_SPEL = "spel"added to constants.lgs build idlon spel projects delegates tospel generate-idl(vendored binary).lgs build clienton spel projects delegates tospel ffi-gen.lez-frameworkandspelprojects.templates/lez-framework/deleted — the template referencedjimmy-claw/lez-framework(broken) and duplicated logic that belongs in spel.skills/lez-framework-template/renamed →skills/spel-template/with updated content (spel vocabulary:#[spel_program],spel.toml,maketargets, spel CLI).Why
The
lez-frameworktemplate was broken (wrong repo, old macros) and maintained a parallel scaffolding path to whatspel initalready does correctly. This change makes scaffold's role minimal: version pinning,scaffold.toml, and AI skills — program scaffolding belongs to spel.Closes logos-co/ecosystem#128 (scaffold now uses released spel for framework projects).
Test plan
lgs new my-project --template spelsucceeds withspelon PATH and produces a buildable projectlgs new my-project --template lez-frameworkprints deprecation warning and produces same resultlgs new my-project(default) still uses thedefaulttemplate (bare LEZ, no change)lgs build idlin a spel project callsspel generate-idlvia the vendored binarycargo test --lib)🤖 Generated with Claude Code