The taw commandUpdating a site

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"
  }
}
SettingValues
coreminor (every 1.x release), patch (bug fixes only), pinned:<version>
scaffoldauto (replace framework-owned files, and point unedited vite.config.js and phpstan.neon at taw/core's base), off
manifestsadd+bump (what a new taw/core needs; never removals), off
docsframework-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)
checksany of lint, phpstan, test, build, smoke (needs the site running)
deliverpr, pr+merge (merged when checks pass, which deploys), branch
remoteThe 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.

CommandWhat it does
taw upgradeLists pending migrations
taw upgrade --applyRuns 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:

MigrationThemesDoes
1.89.0/site-skillsclassic and blockInstalls or refreshes taw/core's site skills in .claude/skills/
1.91.0/agent-docsclassic and blockPoints AGENTS.md, CLAUDE.md and the Copilot and Windsurf rules at taw/core's copy
1.91.0/configsclassicLoads 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/, puts composer.lock back, and lets the new update do 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 (or taw-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 themeWhere the framework's part lives
CLAUDE.md, AGENTS.md, Copilot and Windsurf rulesvendor/taw/core/resources/agents/. CLAUDE.md imports it; the others point to it. Add your notes under "This site".
vite.config.jsThe classicTheme() preset in taw/core's Vite helper
phpstan.neonvendor/taw/core/resources/phpstan/classic.neon
.github/workflows/ci.yml, framework-sync.ymlShared 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.