dartway_cli 0.7.0
dartway_cli: ^0.7.0 copied to clipboard
DartWay command-line tool: print the agent setup brief, check prerequisites, create projects from the canonical template, install the AI toolkit, run convention checks.
Changelog #
0.7.0 #
frameworkRefsDiverged(warning): the framework arriving in halves. An app that consumes DartWay by git statesref: masteron every package, which reads as "all of it from master" and is not what the lock does — a git dependency is pinned to a commit when it is added, and stays there until something upgrades it by name. Add the core in March and the push module in May and the project runs two framework releases against each other, with no version number anywhere to make the gap visible, because a git dependency shows none. The check groups thedartway_*git entries of everypubspec.lockin the project by repository and reports a repository resolved to more than one commit, naming the packages, their commits and the directories to rundart pub upgradein. Different repositories are never compared, and hosted packages are left alone: semver already answers this question for them. A warning rather than an error — the state is wrong but the code is not, and what fixes it is a command rather than an edit.
0.6.0 #
-
dartway checknow verifies that the generated code is committed formatted —generatedCodeUnformatted.serverpod generatewrites its output through thedart_stylebundled with the Serverpod CLI, which is not thedart formatof the project's SDK, and nothing reconciles the two. Left alone, the difference means every generation run rewrites files the change never went near: making one field nullable produced a diff of 29 files and about 1900 lines, in which the two lines that mattered could not be found. The check runsdart format --output=none --set-exit-if-changedover the server'slib/src/generated/and the client'slib/src/protocol/, and it insists on both — a repository that keeps one of them formatted and leaves the other raw has not avoided the diff, it has handed it to whoever next formats the second, which in one project meant 33 unrelated files arriving in someone else's pull request.It is a warning, not an error, and that is a decision rather than a softening: the comparison is against the
dart_styleof whichever SDK ran the check, so a red result can mean "your SDK is newer than the one that formatted this" rather than "you skipped a step". A check that fails the build on that is a check people learn to filter out. So the finding names the files, the exactdart formatcommand with both paths spelled out, and the Dart version it judged against — it has to be actionable by someone who did not write the code and does not know why it went red.The step this holds is documented alongside it, because its position is not the obvious one: the format pass goes after
create-migration, not aftergenerate.create-migrationregenerates in order to diff the schema, so a pass placed between the two is silently undone — which turns a missing step into a loop of generate → format → generate → format again.
0.5.1 #
quickstartno longer tells the agent to run a seed, because the skeleton no longer ships one. The first administrator is now declared per environment —bootstrapAdminIdentifierinconfig/passwords.yaml, which the brief instructs the agent to ask the human for rather than invent, since whoever receives the one-time code on that identifier becomes the admin. The step it replaces was development-only by construction, so staging and production were left with anUPDATEtyped by hand: an operation nobody can read back and nobody can repeat when a new environment goes up. Everything else the seed did was already reachable without it — a plain user registers in half a minute against the OTP printed in the console, and the app name is set on the admin panel's settings screen, which is a better first run than a row put there in advance because it shows the write path and the live update.
0.5.0 #
-
deploy checknow readscompose.override.yml, which nothing did before. The override is the one deploy file a project writes by hand, and the deploy copies it to the server unexamined. The newoverride-web-buildwarns when it carries abuildblock for thewebservice: the deploy builds that image itself and hands itDW_BACKEND_URLfrompublicHost, so an override that builds it too states the API domain a second time with nothing comparing the copies — the build keeps succeeding against yesterday's API. This is not hypothetical: it is what Studio's deployment did, under a comment claiming a check that did not exist. Overridingwebfor a label or a limit stays legitimate, which is why this is a warning and why only the build block trips it. -
/dartway-auditbecomes/dartway-checkup, and looks at the project rather than at the code. The audit judged the Flutter package against the clean-code contract, which left out everything that is not code — and that turned out to be where the worst findings live: a check declared inanalysis_options.yamland executed by no CI step, a test suite excluded months ago with a comment older than its reason, a pin trailing the framework so that a local workaround silently duplicates what upstream now does. The checkup runs the project's own gates before reading anything — those answers are certain and cost a minute — then compares them against what CI actually runs, and measures the distance to the framework. -
Depth is budgeted and remembered, so repeated runs go deeper instead of skimming. Breadth and depth compete for one budget and breadth always wins, so the command reads three to five features properly per run and records them in a coverage table: never-visited first, then whatever changed most since its last pass. A first pass over a feature finds the structural problems; once those are fixed the next one sees the design underneath.
-
A finding a command could confirm is a hypothesis until the command has been run, and is labelled as one. Two real errors motivated the rule: a widget parameter called legacy because a doc comment said so while the code said otherwise, and a folder reported as missing a passport the checker does not in fact demand.
-
dev_notes.md— the second journal.dartway_notes.mdholds what the framework got wrong; this one holds what this project carries and nobody else can fix — CI, pins, configs, tendencies. What belongs to a single feature goes in neither: it is a line in that feature'sknownIssues, next to the code. Installed and git-ignored like its sibling, never overwritten, written bydartway-finishas well as by the checkup, and both journals' open entries are listed when a task ends. Entries are deliberately short — where, what is wrong, what it leads to — because a journal of treatises is a journal nobody reads. -
A retired command is now removed from a project on update rather than lingering as a stale
/slash:managedCommandFileskeeps the old name until no project can still be carrying it.
0.4.0 #
dartway checknow sees the features it had been walking past, and stops contradicting its own exit code. Three findings, one cause: the checker recognised a widget by matching a list of base class names —(Stateless|Stateful|Consumer|HookConsumer|Hook)Widget— which silently missedConsumerStatefulWidget, the class every form and dialog extends. It now asks whether a public class extends anything named*Widget: a shape rather than a memory. Its twin, the newnotAFeature(error), closes the other end — a folder in a zone whose entry point declares no widget is not a feature, and belongs incore/(state several features watch) orshared/(a helper with no story). While only the spec check existed, a provider-only folder passed because it was not a widget; a real project had ten of them, every one graded A. Finally, the verdict line moves out of the Flutter inspector and into the command: the inspector knew nothing of the layout check that ran before it, so a run could print two layout errors, announce "No errors — check passes", and exit 1.
0.3.0 #
-
The deploy now supplies the web build's API address, and the template ships the two Dockerfiles the compose file has always named. The rendered compose file builds
<project>_server/Dockerfileand<project>_flutter/Dockerfilefrom the project root, but neither the template nor the example had ever contained the second one, and the build argument the first version passed —FLUTTER_ENV— was read by nothing in the framework. A project reaching deployment therefore wrote its own image and invented its own way to tell the app which API to talk to, which is a domain written down twice with nothing comparing the copies. The argument is nowDW_BACKEND_URL, rendered frompublicHostin the Serverpod configuration, andmain.dartreads it throughString.fromEnvironmentwith the localhost fallback for local runs. -
Two new local checks.
dockerfiles-presentfails when either image the compose file builds has no Dockerfile — previously that surfaced on the server, after the checkout had already moved.dockerfile-entrypoint-formfails on a shell-formENTRYPOINTin the server image:docker compose run backend … --apply-migrationsappends arguments that this form ignores, so migrations quietly start an ordinary server and the deploy reports success. Settingserver_entrypointdeclares the shell form deliberate and satisfies the check. -
The rendered compose file states the server's run mode as a
command, not only as therunmodeenvironment variable: an exec-form entrypoint takes its arguments from there, anddocker compose runreplaces them wholesale for the migration pass. -
createwritesconfig/passwords.yamlinstead of the template carrying it. The template used to commit the file with throwaway development values so that a new project ran immediately; the values were harmless, the habit was not, anddeploy checkfails on a tracked passwords file — every project therefore started life with an error against it. The committed record is nowpasswords.yaml.example,createcopies it onto the new project's disk, and.gitignorecovers the copy from the first commit. -
invalidTopLevelLayout— the top level of a project is a closed list, and now something checks it. The Flutter package:main.dartand<project>_app.dart, the zonesapp/admin/auth/common/, the layerscore/shared/ui_kit/l10n/. The server package:server.dartandsrc/, and under itapp/crud/dartway/domain/endpoints/generated/models/web/. Anything else is an error, as is a missing fixed name — and so is a top-level name used one level down, which is the case that prompted the check:app/admin/is an ordinary group as far as every other rule can tell, so the admin panel sat outside the checks written for zones without anything noticing. The list had been written down in three places — the docs, the agent toolkit and this checker — and the three had already drifted apart. Now there is one list, in code. -
The checker's zone names are matched exactly rather than by prefix, and
data//domain/are gone from the Flutter side: the data layer isdw.repo, and what is left of Flutter-side domain logic is a helper, so it belongs inshared/. Both folders had been conventional and empty since the beginning. -
--dirskips the layout pass, the same way it already skipsui_kit/: both judge a package as a whole. -
A new toolkit token,
__FLUTTER_APP_FILE__— the app's wiring file, whose name follows the project (my_app_flutter→my_app_app.dart).
0.2.0 #
-
dartway quickstart— the framework's front door, and it is not a plugin. It prints the whole setup brief to stdout: prerequisites, how to create a project, the order the bring-up steps come in and why, the liveness check, how to hand over the sign-in. A human pastes two commands anywhere —dart pub global activate dartway_clianddartway quickstart— and whatever assistant is at hand has the instruction in context. An extension would have tied the way into an open framework to one vendor's format and left everyone else copying prose; a printed text is read by all of them, and by people. The brief is deliberately shell-neutral: it states the step and the reason and lets the agent phrase the command its own platform wants. -
dartway doctor— the failures that are never DartWay's. Dart and Flutter versions, a responding Docker daemon (reported separately from a missing one),serverpod_cliagainst the pin read from the project's own server package, and the pub global bin directory on PATH. Each failure prints the command that fixes it; exit code 1 when something is blocking, so an agent or a CI step can branch on it. These surface late and expensively otherwise — asconnection refusedduring migrations, as generated code that compiles and then misbehaves, asdartway: command not foundright after a successful install. -
dartway create .uses the current empty folder as the project root instead of nesting a directory inside it — the shape people actually start in, an empty folder already open in an editor or an agent. The folder names the project, as influtter create ., converting the separators a directory may carry and a Dart package may not (dartway-demo→dartway_demo); a name that cannot be converted is refused with the reason. An initialized-but-empty git repository is allowed through, since that is how such a folder often arrives, and the initial commit lands in it. -
.claude/settings.jsonis seeded by the installer, only when the project has none and never overwritten afterwards. It pre-approves this stack's build commands so a first run is not a queue of permission prompts, and denies readingconfig/passwords.yaml— a rule the skills stated and nothing enforced. Nothing destructive is on the list. -
Removed dead pubspec rewriting.
createno longer retargetsref: mastertoref: stable: the template stopped carrying git dependencies, and the code had quietly become a no-op that the docs still described.
0.1.3 #
-
The harness now ships the channel back. A project on the framework is where the rules get disproved — but the harness is overwritten on update, so a rule that let you down cannot be fixed where you found it, and the finding used to die in a chat.
setup-aiandcreatenow leave a git-ignoreddartway_notes.mdat the project root (never overwriting an existing one) anddartway-finishlists its open entries at the end of a task.CLAUDE.mdsays when to write there: a rule that does not exist or is too vague, an API the app had to work around, and the moment you are tempted to edit a managed file — that temptation is the note. -
--languagerecords what the project writes its own texts in — feature specs, doc comments, its journal — into the installedCLAUDE.md. Default English; package APIs and error strings are English regardless, since those ship to other people. -
Leftovers of the old shell installer are reported, not removed.
tools/dw_claude_setup/is usually a gitlink with no.gitmodulesentry — invisible togit submodule update, silent ingit status, and an empty folder on disk. The installer names it and prints the two commands that clear it. It does not run them: an installer that edits somebody's git index is a different kind of tool than one that copies files. -
unusedFeatureFile(warning): dead code inside a feature. A file inwidgets//logic/that its own feature never mentions is unreachable — nobody outside may import it — and the analyzer cannot say so, because to it a public class is always possibly used elsewhere. Law 3 is what makes the check possible at all: the search is one folder deep, so the answer is complete rather than a guess. On one real admin panel a single pass found a replaced save-button bar and a stale copy of a moderation list, both compiling and both travelling through every refactor.Two false positives cost the first version four hits out of six, and both are now tested: a type is not how it is called (an extension answers to its member name, a notifier to its provider variable), and dead code keeps dead code alive (a handler nobody calls still calls its own settings file, so the sweep repeats until a pass buries nobody).
-
dartway checktells apart "must be a feature" from "is checked at all". The two used to be one list, so a widget outside a zone was exempt from the passport rule and from every cleanliness and UI-Kit rule at once.lib/shared/— the home for building blocks under Law 3 — is now read for the content rules but never asked for aDwFeatureSpec, which is what made moving blocks out of zones safe to recommend.widgets//logic/are treated as a feature's internals only inside a feature area, soshared/widgets/…is importable rather than being flagged as reaching into somebody's internals.core/,data/anddomain/are still skipped entirely — a known gap, unchanged here.
0.1.2 #
-
dartway deploy— the server without a folder of shell scripts. Three verbs:checkreports whether a deployment would work and changes nothing,runupdates, rebuilds, migrates and restarts,secretmoves credentials between the maintainer'spasswords.yamland a server.The Serverpod configuration is the source of truth. Domains, ports and the database stay in
<project>_server/config/<env>.yamland the CLI reads them;deploy/config.yamlholds only what Serverpod has no concept of. In the two projects this replaced, nine fields existed in both places and a whole script existed to keep them from drifting.checkruns seventeen assertions. The one that pays for the rest is DNS: everypublicHostmust resolve to the deployment host, because a domain that does not reach the box fails certificate issuance and repeated failures hit a rate limit.Secret values travel on stdin, never as arguments, and YAML encoding happens locally so the remote side never escapes anything. Two guards refuse a push that would lose information: keys the server has and the file does not, and values the file would blank.
-
Two more conventions the analyzer cannot see.
barrelFile(error) fires on a file that only re-exports: it reads as convenience and acts as a hole in the feature boundary, because importers name the barrel and reaching into another feature's guts through it looks legitimate — one such file laundered three features' internals until it was deleted.widgetSizesItself(error) fires onExpandedorSizedBox.expandopening abuildbody: the widget claims the parent's space and throws in the first parent that is not a flex, while the analyzer stays silent.SizedBox(width: double.infinity)was tried in the second check and taken back out. Inside a bounded parent it only means "as wide as allowed", so every hit was arguable — and a check whose findings are arguable teaches people to skip the checker. -
forbiddenUiUsagenow catchesTheme.of(context)andcontext.theme. It already flaggedcontext.textThemeandcontext.colorScheme, so the rule was sidesteppable by writing the same thing the long way:Theme.of(context).textTheme.bodySmallreads as ordinary Flutter and passed the checker while the short form did not. Both spellings mean a screen is styling itself. -
Asset paths are checked against the file system.
assetPathMissing(error) fires when a string likeassets/icons/lock.pngnames a file that is not there — nothing else catches it: the code compiles and the screen renders a blank.forbiddenAssetPath(warning) fires on a raw asset path outsideui_kit/, because a path spelled out in a screen survives a renamed file only by accident and cannot be found by search.Together these replace what a code generator used to guarantee. A generated constant could not name a missing file; a hand-written one can — and DartWay projects now write them by hand, since
build_runnerin the edit loop costs minutes per change and punishes the one who forgets to run it with errors about code that is perfectly fine. -
The file-length thresholds are relaxed: nothing below 200 lines, a nudge above it, a warning above 350. Length is the weakest signal the checker has, and a tight limit makes it lie — it flagged files that were long because they were well described. That is not hypothetical: a feature's
DwFeatureSpecnow lives in the file of the feature it describes, and a good description costs twenty lines. A rule that goes off when someone documents their feature properly teaches them to document less. -
New check
featureSpecMissing(warning): a feature whose public file is a widget is expected to declare aDwFeatureSpec— the spec is what error reports, Studio and the agent read. Features whose entry point is an extension or a plain function are left alone: there is nothing there to hang a spec on. -
createstops printing a wall of commands and hands the project to the agent. The old output listed seven commands across two terminals — which is both the first thing a newcomer sees and a contradiction: every new project ships an AI toolkit in.claude/, and the tool that installed it was still telling people to typedocker compose up -dby hand. Nowcreatesays to open the project and runclaude, then ask for it in plain words. The manual sequence has not gone anywhere — it lives in the project'sREADME.md, for people without an agent at hand and for anyone who wants to see what actually happens. -
The toolkit gained the skill that makes this real:
dartway-runknows the order that matters (seeding before migrations fails; a started container is not yet a database accepting connections), the ports (API 8080, dev database 8090, test 9090), where the one-time sign-in code is printed, and how to read the failures people actually hit — Docker not running, port 8090 taken by another DartWay project, a schema that drifted, a model changed withoutserverpod generate, aserverpod_clithat no longer matches the project's pin.
0.1.1 #
create: the printed next steps now actually run in order —dart pub getbefore the server starts,--role maintenanceso migrations apply and exit (leaving the terminal free to seed), and a mention of the VS Code F5 flow. The sign-in hint points at the seeded user.
0.1.0 #
First public release — the DartWay command-line tool.
dartway create — a new project from the DartWay skeleton: server, generated client and Flutter
app, plus the AI toolkit in .claude/. What you get is a skeleton, not somebody's product: phone
auth with one-time codes, a UserProfile with roles, navigation with zone guards, an admin panel,
a UI kit as source you own — and zero domain models, because the domain is the part you write.
dartway check — the conventions, enforced: errors fail the run, warnings and infos are
advisory. File length is a soft signal (over 120 lines an info, over 200 a warning) rather than a
hard rule, because a limit you cannot honestly meet is a limit people learn to ignore.
dartway stats — code size per feature: what actually grew this week.
dartway setup-ai — installs or updates the AI toolkit in an existing project, overwriting only
the files it manages.