Upgrades
How improvements reach a project that was generated months ago.
Most starters are a snapshot. You buy the code, it becomes yours, and whatever the author fixes next year happens in a repository you no longer share. The honest version of "lifetime updates" in that model is "a changelog you may read and a diff you may apply by hand."
This one can do better, and the reason is narrow enough to state plainly:
Because your project has a known shape, we can tell which files are still ours — and only touch those.
The four kinds of file
| what it is | what an upgrade does | |
|---|---|---|
| Packages | @launchquickly/* — the CLI, the lint preset | pnpm up, like any dependency |
| Managed | the action client, tenancy primitives, adapters, the app components | replaced if you have not edited it |
| Agent surface | .claude/ — the skills, slash commands and the check hook | replaced if you have not edited it |
| Yours | schema, pages, features, business logic | never touched |
Managed files carry a marker at the top:
/**
* @lq-managed core/action-client
*
* Edit freely — this is your copy. `lq upgrade` checks whether it still matches
* what shipped: untouched, it is replaced with the new version; edited, you get a
* diff to apply yourself rather than losing your work.
*/Your schema, your pages and your features carry no marker, because those encode decisions no release should overrule.
.claude/ needs no marker. The whole directory is ours by construction —
skills, commands and the hook are the product, not a starting point you fill in
— so it is managed by location instead. A skill you write yourself is simply
absent from the template, and an upgrade never touches a file it does not ship.
That directory was excluded from upgrades until recently, which meant a project bought before a skill existed could never receive it. Since the skills are the thing that keeps an agent on the rails, that was the least defensible gap in "lifetime updates" — and it is the reason the design guides reach projects generated months ago.
How "untouched" is decided
.lq/manifest.json records a hash of every managed file as it shipped. That
hash is written once and never updated in your project, which is what makes it
useful: if the file still hashes to it, you have not touched it and a new version
can go straight in. If it does not, you have — and you get a diff instead of a
surprise.
lq doctortells you where you stand: template version, which generated files you have edited, which managed files have drifted.
Drift is not an error. Editing your own vendored code is the point of vendoring it. Drift is information the upgrade path needs, and a tool that scolded you for it would be training you not to touch your own project.
Running an upgrade
lq upgrade --channel stable --key lq_live_… # fetch the latest release
lq upgrade --template ./path/to/release # or point at a copy you have--channel redeems your license, downloads the release and unpacks it to a
temporary directory; --template skips all of that. The engine behind them is
the same. The key can also come from the LQ_LICENSE_KEY environment
variable, and --channel dev rides the canary train — when no dev build is
published it serves stable and says so.
It will: replace managed files you have not edited, add plumbing the release
introduces, add dependencies you are missing, and update the vendored tooling
— the CLI, the lint rules and the codemods that live in vendor/*.tgz.
That last one matters more than it sounds. A release can be entirely a
generator or lint fix and touch no template source at all; the tooling is
where those live. It is replaced unconditionally rather than diffed, because
a tarball is ours outright and nobody hand-edits one — and pnpm install
(which the command tells you to run) is what actually delivers it.
It will not: overwrite a file you have edited, delete anything, or change a dependency version you already chose. Those are reported and left to you.
Afterwards:
pnpm install
lq checkThe upgrade is not finished until lq check passes. That is not advice — it
is the same sentence the command prints when it hands control back.
When you delete part of a generated feature
Deleting some of a slice is ordinary work — a comments feature generated with
its own routes, when comments really belong inside the parent page. The
manifest still records the files you removed, so lq check names them:
lq manifest prune # drops entries whose files are gone
lq manifest prune --dry-runIt only drops entries whose FILE is absent. A file you edited keeps its
recorded hash, because that hash is exactly what tells lq upgrade you
changed it — dropping it would make the next upgrade treat your work as ours.
Codemods
When an API changes between releases, an upgrade can also rewrite your code: codemods run before the managed files are replaced, against the version you are leaving. They emit positioned text edits located with the TypeScript parser — a reviewable diff, not a reprinted file.
The codemod registry is currently empty on purpose: nothing between the released versions has needed one yet. The engine is real and tested; the first breaking release will ship with its codemod, and an upgrade that runs one tells you exactly which files it touched.