scdev cleanupnow prunes only truly unused resources - previously the bare command deleted every volume of the current project, and--globaldeleted volumes across every registered project plus orphans, contradicting how "cleanup" is normally understood (cf.docker system prune,npm cache clean) and making the command unsafe to run without thinking. The--globalflag is gone;scdev cleanupnow lists and removes (in one combined confirm): (1) state entries whose project directory is missing from disk, (2) containers carrying thescdev.projectlabel whose project is no longer registered + on disk, (3) volumes not owned by any still-registered project. Orphaned containers are removed before volumes so the old "volume is in use" failure - which hit projects whose state was dropped but whose containers stayed alive - no longer blocks the volume pass. Resources belonging to still-registered projects are never touched; usescdev removefor a full project tear-down.
- Transparent forwarding of colon-namespaced subcommands via
scdev <cmd>- when a justfile declares a recipe literally named after the file (e.g.console *args:inconsole.just), scdev now invokes just asjust -f console.just console <args...>so arguments containing colons (cache:clear,db:migrate,dal:refresh:index) flow through as recipe parameters instead of being parsed as just's module-path separator. Template authors can now wrap CLIs likebin/consoleorartisanwith a single catch-all recipe -scdev console cache:clearforwards straight tobin/console cache:clearinside the container. Without a filename-matching recipe, behavior is unchanged (first arg is the recipe name), so existing templates keep working.
- Fix
scdev execprintingError: exit status Non non-zero shell exit - when an interactive shell started viascdev exec <service> shexited with a non-zero code (Ctrl+D on some shells returns 130, a failed command inside the shell returns its own code), cobra wrapped the child's exit inError: exit status Nand the scdev process itself exited with 1, losing the original exit code. Ctrl+D out of an exec'd shell now returns cleanly to the host shell with no error output, and any non-zero child exit code is propagated verbatim instead of being squashed to 1. Matches howdocker execand standard shells behave.
- Fix auto-update rate-limit death spiral on shared IPs - when the GitHub API call failed (rate limit, 5xx, transient network error),
refreshAndInstallreturned without writing~/.scdev/update-check.json, so the next invocation retried immediately instead of waiting the 24h TTL. On a shared egress IP (office NAT, VPN, CI pool) once any user hit GitHub's 60-req/hour unauthenticated limit, every retrying user kept burning credits faster than the window refilled, starving everyone else's next-day refresh. The cache is now always written on error withLastCheckedbumped and prevETag/LatestTag/InstalledTagpreserved, so a failed attempt counts as "checked recently."
- Fix auto-update never running on fast commands - the background update check was launched as an unjoined goroutine, so Go's runtime killed it when
main()returned. Fast commands likescdev version,scdev list, andscdev statusfinish before the GitHub API round-trip completes, so~/.scdev/update-check.jsonwas never written and auto-update effectively never ran. Made the refresh synchronous: API probe bounded to 3s, download/install bounded to 60s. Cache-hit path (99% of invocations) is unchanged at ~6ms; stale-cache path now blocks for ~200ms on a 304 or ~1-2s when a new release is actually being downloaded. Cost is paid at most once per 24h per machine.
scdev opencommand - opens the current project's URL in the default browser.scdev openuses the project in the working directory;scdev open <name>looks up any registered project by name. Protocol follows the global SSL setting (http/https). Closes the common "I need the URL again, let me runscdev infoand copy-paste" loop.
- Background auto-update with banner - scdev checks for updates in the background (at most once per 24h, conditional GET with ETag) and, if the running binary is in the symlink layout, silently downloads the new release into
~/.scdev/bin/scdev. No sudo, no re-exec. The next invocation prints a one-line banner informing you that you've been upgraded. Legacy installs (plain binary in/usr/local/bin/) still get a "runscdev self-updateto migrate" banner. Opt out withSCDEV_NO_UPDATE_CHECK=1; CI environments are detected automatically. - Checksum-verified installs and updates - every path that downloads an scdev binary (
install.sh,scdev self-update, the background auto-update) now fetcheschecksums.txtalongside the binary and verifies SHA256 before chmod-ing it executable. A compromised release asset or HTTPS MITM via a rogue CA can no longer silently run code on your machine. Releases withoutchecksums.txt(pre-0.6) are rejected rather than falling back to unchecked install.
- Fix stale shared-service containers silently breaking after config changes - if a shared service (Mail, DB UI, Redis Insights, Router) was created while SSL was off and SSL later got flipped on (or image, domain, or dashboard settings changed),
scdev services startwould just start the stale container instead of recreating it, so the new Traefik labels never took effect. Symptoms:https://db.shared.<domain>redirecting to the docs page, HTTPS URLs for other shared services falling through to the 404 catch-all. Shared services now carry the samescdev.config-hashlabel that project services use;startServicecompares it to the hash of the freshly built expected config on every call and recreates the container on drift. The router keeps its "don't recreate just to shrink ports" behavior (other projects' ports would be dropped) via a union-of-ports check before hashing. - Fix lost updates to the state file - every mutating operation on the state manager (
RegisterProject,CreateLink,AddLinkMembers,RenameProject, etc.) used to Load then Save as two independent locked calls, so a concurrent goroutine (like the background update-check doing its own work in parallel withscdev start) could interleave and clobber changes. Operations now hold the manager's lock for the entire read-modify-write cycle via a newMutate(fn)helper. Cross-process concurrency (twoscdevinvocations at once on the same state file) is still unprotected - documented, not a regression. - Fix
scdev self-updatehanging indefinitely on slow GitHub responses - the GitHub API call and binary download now use a 5-minute context timeout, matching what the background update-check was already doing. Previously a stalled GitHub response meant the user had to Ctrl-C.
- Remove unused config fields -
shared.tunnelandshared.observabilitywere defined in config but never wired to any service manager; enabling them did nothing except (for observability) print anobserve.shared.<domain>URL inscdev infothat 404'd.pre_startwas inServiceConfigand documented in the README but no code ever executed the listed commands. All three are gone. Re-implementation tracked as SC-127, SC-128, SC-146. - Remove phantom
persist_on_deletehelp text -scdev down -vandscdev remove -vflag help advertised "respects persist_on_delete" but no such field exists anywhere in config or code. Removed the claim; if anyone wants the feature for real it's tracked as SC-147. - Smaller, safer
cmd/*bootstrap - newwithProject(timeout, fn)/withDocker(timeout, fn)helpers incmd/shared.gocollapse the timeout + Docker-availability + project-load boilerplate that everyrunXxxrepeated. Roughly 150 lines of duplication gone across 15+ commands. - Single shared-service registry - shared service metadata (name, subdomain, container name, start/stop/status/connect/disconnect funcs, per-project opt-in flag) now lives in one place:
services.AllSharedServices(). Adding a new shared service is a single-file change instead of updating two hand-rolled registries (cmd/services.goandinternal/project/shared_services.go) plus hoping they stay in sync. - Other small cleanups -
strings.Join/strconv.Itoafor the router's port CSV label,html.EscapeStringreplaces a hand-written HTML escaper on the docs page, sharedteardownContainershelper betweenProject.DownandProject.Rename, and cleaner early-return control flow inNetworkDisconnect.
scdev stepstyling refinement - the whole header line is now bold cyan (not just the▶), and a trailing blank line separates the header from the command output that follows. Each phase reads as a framed section, not a single colored glyph next to white text.
scdev step <message>subcommand - prints a visually distinct progress marker (two leading blank lines, cyan▶, bold text) for use inside.scdev/commands/*.justrecipes. Duringscdev setup, top-level status messages like "Installing PHP extensions" previously got buried in the wall of apk/composer/npm output;@scdev step "..."replaces@echofor phase headers so they pop against the noise. Auto-plain on non-TTY,NO_COLOR, or global plain mode.scdev start's own "Starting project ..." line now uses the same helper.
- Template authoring docs updated -
templates/README.md,skills/scdev/references/templates.md, and the scdev skill now recommend@scdev stepfor setup.just phase headers and explain why, with the base/Nuxt/Symfony examples rewritten to use it.
- Fix
scdev updateignoring env/image/volume/command changes -serviceNeedsRecreateonly compared Traefik labels and bailed out with "Project is up to date" on any other change, leaving stale containers running. It now compares a deterministicscdev.config-hashlabel stamped on each container, covering image, env, volumes, command, working dir, routing, published ports, and network aliases. Containers created before the label existed are recreated once on first update after upgrade.
- Skill updates for template creation and existing-project setup - expanded
skills/scdev/references (new stack-gotchas reference, richer config examples and template authoring notes, updated SKILL.md with rename/link commands and per-servicerouting.domainguidance) to help agents scaffold new templates and onboard scdev into existing projects. - README and template docs: Symfony/Sylius/Laravel trusted-proxies troubleshooting - documented the mixed-content / stuck debug toolbar / broken admin login failure mode caused by Traefik terminating HTTPS without a trusted-proxy env var, plus PHP template authoring tips (memory_limit drop-in, install-php-extensions, asset-pipeline builds).
- Fix systemcheck reporting "All checks passed" when CA is not trusted -
checkCAonly checked whether CA files exist, not whether the CA is trusted by the system. On a new install where the user skips CA installation, first-run would say "Setup incomplete" but systemcheck would say "All checks passed!" immediately after. Now systemcheck verifies trust status and reports "not trusted by system" with instructions to fix.
- Fix RedisInsights image not resolved on new installs -
${RedisInsightsImage}was missing from the substitution map ingenerateDefaultGlobalConfig(), causing the literal string to be written to~/.scdev/global-config.yamlon first run. Docker then failed with "invalid reference format". Existing installs were unaffected because their config file predated the redis_insights section.
- Bump Go toolchain from 1.25.1 to 1.26.2
- Bump just from 1.40.0 to 1.49.0
- Bump golang.org/x/term from v0.39.0 to v0.42.0
- Bump golang.org/x/sys from v0.40.0 to v0.43.0
- Bump github.com/spf13/pflag from v1.0.9 to v1.0.10
-
scdev linkcommand - create named link networks for direct container-to-container communication between separate projectsscdev link create <name>/scdev link delete <name>- manage named link networksscdev link join <name> <member>.../scdev link leave <name> <member>...- add/remove projects or individual servicesscdev link ls- list all links and their membersscdev link status <name>- show members and connection state- Members can be whole projects (
sec-scan) or specific services (redis-debug.app) - Each link creates a dedicated Docker network (
scdev_link_<name>) for isolation between link groups - Containers resolve each other by container name via Docker's embedded DNS (e.g.,
app.project-b.scdev) - Links persist in global state and auto-reconnect on
scdev start - Validation: link name characters, project/service existence, duplicate prevention
-
scdev info/scdev status/scdev listshow link information - links are displayed alongside services and shared services -
scdev renamecommand - rename a project with full Docker resource migration- Stops containers, migrates volume data (named + Mutagen sync), removes old network
- Updates state file and link memberships atomically
- Rewrites
name:in.scdev/config.yaml(preserves formatting and comments) - Restarts project with new name
- Confirmation prompt (skip with
--force) - Validates new name is DNS-safe and not already taken
- Fix variables in typed config fields -
${VAR}placeholders in int/bool fields (e.g.,port: ${PORT},router: ${ENABLE}) caused "cannot unmarshal !!str into int" errors. The first config parse pass now uses a minimal struct, deferring typed field parsing until after variable substitution.
- Added
ContainerNameFor()standalone helper for building container names without a loaded Project - Added
CopyVolume()to Runtime interface for volume data migration
- Fix shared service hostnames not resolving from project containers -
docker network connectnow passes--aliasflags, so shared services (e.g.,mail,adminer,redis-insights) are resolvable by their short names on project networks, not just on the shared network - Fix
Down()not releasing TCP/UDP host ports -Down()now refreshes the router to drop ports the project was using, preventing port conflicts on subsequent starts (previously only done in CLI commands, not the library method)
- Integration tests restore shared services - tests that tear down shared services now snapshot what's running beforehand and restore it after, preventing silent breakage of the developer's running environment
- Updated docs (README, SKILL.md, templates/README.md) with shared service hostname reference - container-internal hostnames and ports for accessing services from app code vs browser
Down()now handles state unregistration (previously only done incmd/down.go), fixing stale entries left by integration tests- Added integration tests for exec
--separator, config variables in running containers, per-service routing domain, and Down() state cleanup - Added CONTRIBUTING.md with developer guide, architecture decisions, and test strategy
scdev createcommand - scaffold new projects from templates (GitHub repos or local directories)- Template resolution: bare name (
express), full repo (myorg/repo), or local path (./dir) --branchand--tagflags for GitHub templates--auto-startand--auto-setupflags for non-interactive setup- DNS-safe project name validation
- GitHub tarball download with security hardening (symlink validation, size limits, mode masking, path traversal checks)
- Template resolution: bare name (
- Config variables -
variables:section for reusable${VAR}substitution across the config file (not passed to containers). Variables can reference built-in variables like${PROJECTNAME}. - Per-service routing domain -
routing.domainallows individual HTTP/HTTPS services to have custom domains (e.g.api.my-app.scalecommerce.sitefor a backend service) scdev start -q/--quiet- skip project info display after start (useful in scripts and setup.just)- Docker availability check - all Docker-dependent commands now check if Docker is running and show a clear error message instead of confusing Docker errors
scdev exechandles--separator -scdev exec app -- cmdnow works correctly
- Three official templates published:
- Template Authoring Guide at
templates/README.md .setup-completemarker pattern for solving container startup vs setup circular dependency
shared.redis_insightsrenamed toshared.redisin project config (consistent withshared.router,shared.mail,shared.db)buildContainerConfigis now the single source of truth for container configuration (fixes divergence between start and update paths)connectRouteruses shared helper pattern (consistent with mail/db/redis)- Extracted
IsDBServiceByName()to eliminate duplicate DB detection logic - Reduced redundant
GlobalConfigloading in status command - Removed dead
sync_modecode from Mutagen sync - Fixed unsafe
appendin cleanup command - Supply chain security messaging in README
- README: Templates section, multi-service routing, configuration reference tables, supply chain security callout
- Template Authoring Guide: setup lifecycle, scaffolding patterns (in-place vs /tmp), framework-specific notes
- CLAUDE.md: templates docs, Docker check, variables, routing.domain
- scdev skill restructured with progressive disclosure (222-line SKILL.md + references/)
- Sync-ready gate - containers wait for Mutagen sync automatically, no more
while [ ! -f ... ]workarounds - Default protocol -
routing.protocoldefaults tohttpwhenportis set - Default domain -
domaindefaults to{name}.scalecommerce.sitewhen not set - scdev skill - installable agent skill (
npx skills add scalecommerce-dev/scdev) with full CLI reference, config templates for Node/PHP/Python, debugging guides, and setup workflows
- Complete README rewrite with architecture diagram, benchmark data, and coding agents section
- New tagline: "Ever seen a developer and an AI agent fall in love with a dev environment?"
- File sync benchmark: 5x faster cold start vs Docker bind mounts on macOS
- "Standing on the Shoulders of Giants" section crediting all underlying technologies
- TCP/UDP routing, volumes, and custom commands documented in detail
.pnpm-storemust be in Mutagen ignore for pnpm projects (prevents native binary corruption across image changes)
- Shared service registry pattern — adding a new shared service is now one entry instead of editing 6+ locations
- Split
project.gogod file (1400→868 lines) intoshared_services.goandmutagen_sync.go - Single source of truth for global config defaults (
newDefaultGlobalConfig()) — fixes RedisInsights image missing bug - Removed unused
StartRouterWithPortsparameters and unnecessaryConnectToProject/DisconnectFromProjectaliases - Fixed
scdev infonot showing shared services section when onlyredis_insightsis enabled - Consistent display names across all shared service methods
- Auto-discover named volumes from service definitions — no more redundant top-level
volumes:section in project config - Streamlined CLAUDE.md and planning docs — removed stale TODOs, marked completed phases
- Removed TODO comments from code — all tracked as Completo tickets
First public release of scdev - local development environment framework.
- Single-command project startup with
scdev start - Config-driven multi-service containers via
.scdev/config.yaml - Variable substitution (
${PROJECTNAME},${PROJECTDIR},${SCDEV_DOMAIN}) - Project isolation via Docker networks with inter-service DNS
- Named volumes with persistence across restarts
- Project state tracking with
scdev listandscdev info - Shared Traefik router with automatic HTTPS via mkcert
- Shared Mailpit email catcher
- Shared Adminer database UI
- Shared Redis Insights browser
- Custom project commands via justfiles (
.scdev/commands/) - Mutagen file sync for fast macOS development
- Docs page with dynamic project list at
docs.shared.<domain> - Self-update command (
scdev update) - Install script for macOS and Linux