launch-quickly

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 iswhat an upgrade does
Packages@launchquickly/* — the CLI, the lint presetpnpm up, like any dependency
Managedthe action client, tenancy primitives, adapters, the app componentsreplaced if you have not edited it
Agent surface.claude/ — the skills, slash commands and the check hookreplaced if you have not edited it
Yoursschema, pages, features, business logicnever 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 doctor

tells 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 check

The 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-run

It 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.

On this page