Updating a site
Update a TAW theme in one step, the way npm or Composer update a package: a policy file in the theme decides what changes, and a written guide covers anything a person must finish.
A TAW theme updates like a package. taw update updates taw/core, applies the framework's files, runs the migrations and the checks, commits on a new branch, and opens a pull request. It asks no questions: the theme's taw.json already answers them. Available from taw/core 1.91.
taw update
What one update does
Checks it can start safely
taw.json is valid, the theme is a git repository on a branch, the git remote it delivers to exists (unless it only makes a branch), git has no uncommitted changes, and no earlier update is still waiting to be merged. Otherwise nothing changes, and it says what to do first.
Opens a branch
All the work goes on taw/update-<date>-<time>. Your main branch is untouched until you merge.
Updates taw/core
Within the range the policy allows: any 1.x release, patch releases only, or a fixed version.
Applies the framework's files and the migrations
In a classic theme, files the framework owns are replaced, and composer.json and package.json get only what the new taw/core needs, never a removal. A block theme has no scaffold files to replace. Then the migrations run, through the taw/core just installed: each change a release makes to a theme is one migration. A migration that would overwrite your own edits leaves the file alone and gives you a "For you" step instead.
Runs the checks
Lint, PHPStan, tests and the front-end build, as the policy lists them. A check that can't run on this machine, such as the build without node_modules, is marked "not run here", never passed. The smoke check needs WordPress running, so the update leaves it to the pull request's CI.
Commits and delivers
One commit, then, as the policy says: the branch only, a pull request, or a pull request that merges itself when CI passes. For a pull request, the branch is pushed to the policy's remote and the pull request opens there with gh; with branch, nothing is pushed. If the repository has a deploy workflow, merging deploys production, as any merge does. Clear the site's page cache (Hummingbird, WP Rocket, the host's cache) once that deploy has finished, not while it runs: a cache cleared during the deploy is rebuilt from the old code.
A site whose production deploys from another repository than origin, such as an agency's GitHub organization, names that remote: "remote": "origin-agency". Otherwise the pull request opens on the copy, and merging it deploys nothing. git remote -v lists the names.
--plan says what it would do without changing anything. --no-deliver stops at the commit. Every option: update in the command reference.
The policy: taw.json
The theme's taw.json belongs to the site, and updates never change it. Every setting is optional; these are the defaults:
{
"$schema": "./vendor/taw/core/resources/schema/taw-json-1.0.json",
"update": {
"core": "minor",
"scaffold": "auto",
"manifests": "add+bump",
"docs": "framework-sections",
"checks": ["lint", "phpstan", "test", "build"],
"deliver": "pr",
"remote": "origin"
}
}
| Setting | Values |
|---|---|
core | minor (every 1.x release), patch (bug fixes only), pinned:<version> |
scaffold | auto (replace framework-owned files, and point unedited vite.config.js and phpstan.neon at taw/core's base), off |
manifests | add+bump (what a new taw/core needs; never removals), off |
docs | framework-sections (the framework's part of the agent docs comes from taw/core: unedited copies become short files that import or point to it), off (updates leave the agent docs alone) |
checks | any of lint, phpstan, test, build, smoke (needs the site running) |
deliver | pr, pr+merge (merged when checks pass, which deploys), branch |
remote | The git remote the update branch is pushed to; the pull request opens on that remote's GitHub repository. Default origin. Since taw/core 1.92 |
taw policy explains the current settings in words, and --init writes a starter file. If the file is invalid, updates refuse to run until it's fixed, so nothing is ever half applied.
What the policy governs
Migrations follow taw.json wherever they run: in taw update, in the Composer hook below, and in taw upgrade --apply. scaffold: off holds back the site-skills and configs migrations, and docs: off holds back agent-docs. A held-back migration doesn't run, and it says so:
1.91.0/agent-docs is off in taw.json ("docs": "off"), so it didn't run. To do it anyway: vendor/bin/taw upgrade --explain 1.91.0/agent-docs (By hand).
The update report lists it under "Off in taw.json", and upgrade --json under held. With scaffold: off, update also skips the framework files.
The commands for one part of the framework do what you ask, whatever the policy says: sync --apply (Tier 1 files), skills:sync --apply, docs:sync --apply and configs:sync --apply.
The weekly framework-sync.yml workflow reads only part of taw.json. It updates taw/core within the theme's composer.json constraint, applies Tier 1 files, runs the migrations (which follow the policy), runs its own checks, and opens a pull request. Moving it onto vendor/bin/taw update is planned.
When something fails
Nothing is pushed. The work stays on the update branch, and .taw/update-report.md lists:
- what failed, with the command and its output;
- why that kind of failure usually happens, and the steps to fix it;
- how to check the fix, how to finish the update, and how to undo it.
It's written for whoever picks it up. The site owner reads the summary at the top; a developer follows the steps. It's also a complete prompt: to have Claude finish the update, give it this file, or press Fix with Claude in taw-fleet. A person and an agent follow the same guide, so you can always finish an update by hand. When everything passes, the report is the pull request's description.
Plain composer update works too
Both scaffolds list taw/core's Composer hook, so composer update also runs any migrations the theme still needs and taw.json allows, quietly when there are none. It never fails the update: a migration that fails prints how to do it by hand. Review the changes and commit them with composer.lock. composer install doesn't run migrations, because it must reproduce the lock file exactly.
"scripts": {
"post-update-cmd": ["TAW\\CLI\\ComposerScripts::postUpdate"]
}
Turn it off with TAW_NO_UPGRADE=1, or "extra": {"taw": {"upgrade": false}} in composer.json.
| Command | What it does |
|---|---|
taw upgrade | Lists pending migrations |
taw upgrade --apply | Runs them |
taw upgrade --explain <id> | What one does, why, how to do it by hand, how to undo it |
Add the hook only to a theme on taw/core 1.91 or later. On an older taw/core the class doesn't exist, and composer update fails.
The migrations so far:
| Migration | Themes | Does |
|---|---|---|
1.89.0/site-skills | classic and block | Installs or refreshes taw/core's site skills in .claude/skills/ |
1.91.0/agent-docs | classic and block | Points AGENTS.md, CLAUDE.md and the Copilot and Windsurf rules at taw/core's copy |
1.91.0/configs | classic | Loads vite.config.js and phpstan.neon from taw/core's base |
A migration keeps no record of having run: it looks at the theme each time, so running it again changes nothing.
From taw-fleet
In taw-fleet, u on a site (or taw-fleet update <site>) is Update this site. It asks one question, worded from taw.json, such as "Update acme-shop? taw/core, framework files, migrations, checks, then a pull request to merge.", then runs the theme's vendor/bin/taw update with the site's PHP and Local's Composer. A screen shows each step as it runs, and at the end o opens the pull request and r the report.
- A theme on an older taw/core gets one step too: taw-fleet first installs the newest taw/core in
vendor/, putscomposer.lockback, and lets the newupdatedo the rest. - When an update stops, taw-fleet offers Fix with Claude, which starts Claude Code with the report as its prompt (it asks before pushing and never merges), or Do it myself, which opens the report in your editor.
U(ortaw-fleet update --all) updates every client theme that needs it, one after another, each with its own pull request. A theme that stops doesn't stop the others.
Details: Dashboard and keys.
What the framework keeps for you
Since taw/core 1.91, the parts of a theme that only the framework changes ship with taw/core and update with it. The theme keeps short files of its own, so an update never has to merge your edits:
| In the theme | Where the framework's part lives |
|---|---|
CLAUDE.md, AGENTS.md, Copilot and Windsurf rules | vendor/taw/core/resources/agents/. CLAUDE.md imports it; the others point to it. Add your notes under "This site". |
vite.config.js | The classicTheme() preset in taw/core's Vite helper |
phpstan.neon | vendor/taw/core/resources/phpstan/classic.neon |
.github/workflows/ci.yml, framework-sync.yml | Shared workflows in taw/core, at @v1 |
.claude/skills/ | taw/core's site skills; a skill marked owner: site is never replaced |
The migrations convert older themes. A file you edited is left as it is, with a "For you" step that says how to move your changes. To replace an edited agent doc anyway, run taw docs:sync --apply --force, then put your notes back under "This site".
In taw/core 1.91.0 only, the agent-docs migration also replaced copies a site had edited. If a theme updated to 1.91.0 and its AGENTS.md or CLAUDE.md held your own notes, they're in git history: git log -p -- AGENTS.md. Move them under "This site". From 1.91.1 on, edited copies are kept.
Theme CI
A theme's CI is a short stub that calls taw/core's shared workflow, so the checks update with taw/core:
jobs:
ci:
uses: Relmaur/taw-core/.github/workflows/theme-ci.yml@v1
with: { smoke: true, build: false }
It runs only the checks the theme has: PHP lint, the block getData() check, PHPStan and tests, schema:validate, the front-end check or build, and an optional WordPress smoke test. One check always runs (taw/core 1.93): it fails when the repository holds a database dump, a content snapshot (.sync/) or a credentials file (remote.env, .env, wp-config.php, a private key). On a pull request it checks every commit, so a dump added and then removed again still fails. Remove it from the branch's history and change any password it held. Since taw/core 1.94 the same check also runs before each commit on your Mac: the update installs a git pre-commit hook that refuses the commit, so the file never leaves your machine. git commit --no-verify skips the hook, but CI still checks the push. The header of each workflow lists the same steps as commands, so you can run them by hand.