The tree
shipped 1.0.0The tree is the centre. Every site is a spoke. Direction is measured from the tree, never from the machine.
The tree is your repository as it stands on disk: the committed working
copy. A site is a WordPress install: a database and a filesystem. The DDEV
container on your machine is one of those, and so is dev, and so is prod.
The tree is the only participant with history. It is the only one you can read a diff on and the only one a review can happen to. So it is the only sensible origin for the word direction.
What that makes pull and push mean
pull reads into the tree.
push writes out of it. The environment argument
names which site, and nothing else.
cwp pull dev # dev → the tree
cwp push dev # the tree → dev
One verb each way, carrying everything under management. Tidiness is not the
reason. A partial read would leave the baseline meaning nothing in particular:
the items in it would carry baselines from different days. --only <artifact>
narrows a single run without pretending the rest moved.
The three sites differ in distance and in what a guard lets you do to them. They do not differ in kind.
What the tree holds
Six artifacts. cwp pull writes all of them and
cwp push sends all of them. --only names one.
| In the tree | --only | Read as |
|---|---|---|
content/ | content | one YAML file per post, plus terms, menus and the authors |
| the design system directory | bricks | a tree of JSON, one file per item, plus cwp-options.json |
settings/ | settings | the options a group declares |
widgets/ | widgets | one file per widget area |
roles.yml | roles | roles and the capabilities they grant |
inventory.yml | inventory | core, locale, plugins, themes and versions |
the tracked paths | — | the code you write, sent by the same push |
The tracked paths are the row that is not like the others. They are code,
they have no per-item identity and no baseline, and git is their history. They
travel with the push because an operator shipping a change means the files as
much as the content.
Everything in that table is text. You can diff it and review it the way you review code. The artifacts exist in those formats rather than as a database dump for that one reason.
A family adds no row. It is one feature of a plugin or theme declared as a unit. It expands into the rows above: a settings group, a content group, meta rules on the other groups. What lands in the tree is the same files a group you typed by hand would produce.
What the tree deliberately does not hold
This half matters as much as the first. A tree that quietly lags the site is worse than no tree at all.
-
Images, unless you say which ones.
cwp media pullandpull.uploadsbring a library to your machine, and nothing reaches the tree by that route.What reaches the tree is
media.tracked, and it has three answers.noneis the default.referencedis the files your pulled content points at, enough to rebuild the site’s pages without a database.libraryis every attachment the site holds. Whichever you pick, the capture runs under a file and byte budget and names the files it left when the budget stopped.Only originals travel. WordPress generates the derived sizes from them, and
wp media regeneraterebuilds the set after a push. The budget exists for the reasonnoneis the default: git stores every version of a binary whole and forever, and nobody can repair a history afterwards. -
Customer accounts. No subscriber, no buyer, no order, no address. The scrub anonymises everyone the project did not ask to keep, and nothing puts them in a file.
The people who wrote the content are the exception, and the exception is deliberate. An item records the login that wrote it, and
content/authors.ymlrecords those people: display name, address, roles. A login the other side cannot resolve leaves the item with no author, no archive page and the wrong permissions. They keep their real data through the scrub for the same reason.So a repository carrying content carries its editorial staff by name. Make that decision consciously: git does not forget. An environment that should not have them at all takes
author: it writes every item there under one account. -
Third-party plugins and themes.
.gitignoreexcludeswp-content/plugins/*with your own plugin exempted, andwp-content/themes/*. What travels instead is the list,inventory.yml, so a checkout reproduces the set without vendoring it. -
WordPress core. The image or the host provides it, the inventory records it, and nothing commits it.
-
Secrets. The pull leaves out the design system’s
api-keysandcustom-codetabs unless you ask for them, and a settings group refuses to carry a key it recognises as a credential. -
Everything nothing describes yet. Custom tables, post types a plugin registers at runtime, options nobody declared. This is the largest category and the least visible one.
The tree is not the site, and one command measures the gap
cwp coverage counts what WordPress holds, area by
area, against what the tree describes. It gives each area one of four verdicts:
tracked, excluded, untracked, not-reproducible.
It exists for the failure the section above sets up. A second machine can check
out the repository, build it and produce a site that is not the same one, with
every column of cwp status reading green. status
compares what an artifact describes; coverage asks what no artifact describes
at all.
Measuring is not moving. cwp coverage names a site and reads the tree, and
it transfers nothing between them. So its page carries no direction glyph,
where every pull and push page does.
Where to go next
- Direction, and the tree it is measured from: the bulk rule, the three edges that skip the tree, and why direction lives on the effect rather than on the command’s name.
- Environments and the two-layer config: the spokes, and the three places the git analogy stops.
- Glossary: tree, site, spoke, mode.