@refactco/refact-os 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/LICENSE +21 -0
  3. package/README.md +162 -0
  4. package/bin/refact-os.js +154 -0
  5. package/lib/adapters.js +302 -0
  6. package/lib/company.js +76 -0
  7. package/lib/frontmatter.js +30 -0
  8. package/lib/migrate.js +116 -0
  9. package/lib/project-utils.js +179 -0
  10. package/lib/refact-config.js +324 -0
  11. package/lib/scaffold.js +329 -0
  12. package/lib/validate.js +145 -0
  13. package/package.json +46 -0
  14. package/templates/base/AGENTS.md +9 -0
  15. package/templates/base/CLAUDE.md +3 -0
  16. package/templates/base/README.md +54 -0
  17. package/templates/base/agent/AGENTS.md +60 -0
  18. package/templates/base/agent/CLAUDE.md +7 -0
  19. package/templates/base/agent/claude-hooks.json +32 -0
  20. package/templates/base/agent/hooks/claude-sync-transcript.py +236 -0
  21. package/templates/base/agent/hooks/preflight-metadata.mjs +202 -0
  22. package/templates/base/agent/hooks/send-transcript-to-remote-server.py +238 -0
  23. package/templates/base/agent/hooks/sync-chat-transcript.py +188 -0
  24. package/templates/base/agent/hooks.json +29 -0
  25. package/templates/base/agent/scripts/import-project-chat-history.py +196 -0
  26. package/templates/base/agent/scripts/sync-asana.mjs +408 -0
  27. package/templates/base/agent/skills/adopt/SKILL.md +46 -0
  28. package/templates/base/agent/skills/close-ticket/SKILL.md +31 -0
  29. package/templates/base/agent/skills/extract-learnings/SKILL.md +90 -0
  30. package/templates/base/agent/skills/git-it/SKILL.md +138 -0
  31. package/templates/base/agent/skills/import-chat-history/SKILL.md +85 -0
  32. package/templates/base/agent/skills/ingest-input/SKILL.md +43 -0
  33. package/templates/base/agent/skills/open-ticket/SKILL.md +36 -0
  34. package/templates/base/agent/skills/process-docs/SKILL.md +69 -0
  35. package/templates/base/agent/skills/project-status/SKILL.md +35 -0
  36. package/templates/base/agent/skills/project-status/scripts/scan-status.mjs +153 -0
  37. package/templates/base/agent/skills/refact/SKILL.md +139 -0
  38. package/templates/base/agent/skills/setup-project/SKILL.md +140 -0
  39. package/templates/base/agent/skills/sync-asana/SKILL.md +106 -0
  40. package/templates/base/agent/skills/update-canonical-record/SKILL.md +28 -0
  41. package/templates/base/agent/skills/update-package/SKILL.md +51 -0
  42. package/templates/base/docs/context/project.md +30 -0
  43. package/templates/base/docs/decisions.md +22 -0
  44. package/templates/base/docs/index.md +31 -0
  45. package/templates/base/docs/sources/raw/.gitkeep +0 -0
  46. package/templates/base/docs/task/.gitkeep +0 -0
  47. package/templates/base/env.example +14 -0
  48. package/templates/base/gitignore +34 -0
  49. package/templates/overlays/client/agent/skills/create-deliverable/SKILL.md +29 -0
  50. package/templates/overlays/client/docs/deliverables/.gitkeep +0 -0
  51. package/templates/overlays/code/agent/skills/add-codebase/SKILL.md +239 -0
  52. package/templates/overlays/code/agent/skills/code-development/SKILL.md +58 -0
  53. package/templates/overlays/code/agent/skills/code-development/references/gitflow.md +144 -0
  54. package/templates/overlays/nextjs/agent/skills/nextjs-dev/SKILL.md +93 -0
  55. package/templates/overlays/nextjs/agent/skills/setup-netlify-deploy/SKILL.md +143 -0
  56. package/templates/overlays/nextjs/agent/skills/setup-nextjs-app/SKILL.md +118 -0
  57. package/templates/overlays/nextjs/agent/skills/setup-vercel-deploy/SKILL.md +116 -0
  58. package/templates/overlays/wordpress/agent/skills/install-wp-skills/SKILL.md +130 -0
  59. package/templates/overlays/wordpress/agent/skills/setup-kinsta-deploy/SKILL.md +201 -0
  60. package/templates/overlays/wordpress/agent/skills/wp-env/SKILL.md +478 -0
  61. package/templates/overlays/wordpress/wp-cli.yml.example +46 -0
@@ -0,0 +1,478 @@
1
+ ---
2
+ name: wp-env
3
+ description: Manage the local WordPress stack via wp-env — setup, pull plugins/mu-plugins/db from staging, reset, custom local domain.
4
+ pattern: procedure
5
+ when_to_use: /refact wp-env setup | pull [plugins|mu-plugins|db] | reset | domain <host>.
6
+ when_not_to_use: Non-WordPress projects.
7
+ next_skills: []
8
+ sub_agents: []
9
+ ---
10
+
11
+ # wp-env Reference
12
+
13
+ Use this reference when the user invokes any of:
14
+
15
+ - `/refact wp-env setup` — bring up a fresh local WordPress stack with a sample DB.
16
+ - `/refact wp-env pull` — alias for **pull plugins + mu-plugins + db** (staging → local).
17
+ - `/refact wp-env pull plugins`
18
+ - `/refact wp-env pull mu-plugins`
19
+ - `/refact wp-env pull db`
20
+ - `/refact wp-env reset` — destroy containers + volumes and rebuild from scratch.
21
+ - `/refact wp-env domain set <hostname>` — front the local env with a `.local` hostname over HTTPS via Caddy.
22
+ - `/refact wp-env domain clear` — remove the hostname mapping and revert to `http://localhost:8888`.
23
+
24
+ ## What this does
25
+
26
+ Manages the local WordPress development stack for engagements scaffolded with `/refact add codebase wordpress`. Local code lives in `apps/wordpress/wp-content/`, which is the same tree the Kinsta deploy workflows push (see [`kinsta-auto-deploy.md`](./kinsta-auto-deploy.md)). The local env mirrors staging by pulling plugins, mu-plugins, and the DB over SSH using the credentials in `agent/AGENTS.md`.
27
+
28
+ ## Canonical layout
29
+
30
+ ```
31
+ <project-root>/
32
+ ├── .wp-env.json ← wp-env config (created by setup)
33
+ ├── apps/wordpress/
34
+ │ └── wp-content/
35
+ │ ├── plugins/ ← rsync target for `pull plugins`
36
+ │ ├── themes/ ← (not pulled by default — usually in-repo)
37
+ │ └── mu-plugins/ ← rsync target for `pull mu-plugins`
38
+ └── package.json ← gets `wp:*` scripts on setup
39
+ ```
40
+
41
+ `.wp-env.json` maps `wp-content/{plugins,themes,mu-plugins}` from inside `apps/wordpress/wp-content/` so the container sees the same files the deploy workflow ships. **Never** create a parallel `./wp-content/` at the repo root — it splits the source of truth and breaks the Kinsta deploy filter.
42
+
43
+ ## Optional local domain (`<project>.local`)
44
+
45
+ wp-env binds to `127.0.0.1:8888` by default. To use a hostname like `https://website.local` instead, this flow can layer a [Caddy](https://caddyserver.com) reverse proxy on top: Caddy issues a trusted local cert via its own CA, fronts wp-env on `:443`, and you get clean URLs without ports.
46
+
47
+ The hostname is stored at `.refact-os.json` › `wpEnv.localDomain` so every teammate gets the same URL after `/refact wp-env setup`. Per-project Caddy config lives outside the repo at `~/.refact/caddy/<project-slug>.caddyfile`, imported by a global `~/.refact/Caddyfile`. See **Step 4 — `domain set` / `domain clear`** below.
48
+
49
+ ---
50
+
51
+ ## Preflight (always)
52
+
53
+ 1. `.refact-os.json` › `stack` has a `wordpress` entry. If not, stop and ask the user to confirm — this flow is WordPress-specific.
54
+ 2. `apps/wordpress/` exists. If not, delegate to [`add-codebase.md`](./add-codebase.md) Flow B (`/refact add codebase wordpress`), then continue.
55
+ 3. Docker is reachable: `docker info` exits 0. If not, stop and tell the user to start Docker Desktop / Colima.
56
+ 4. Node is **18+**: `node --version`. wp-env requires it.
57
+
58
+ For `pull` flows only — also verify the SSH fields in `agent/AGENTS.md` are filled (not the literal `<ssh-staging-…>` placeholders). If any placeholder remains, stop and tell the user to run `/refact init` first.
59
+
60
+ ---
61
+
62
+ ## Step 1 — `setup`
63
+
64
+ Idempotent. Each sub-step is verified independently; skip silently if already met.
65
+
66
+ ### 1a. Ensure `apps/wordpress/wp-content/` subfolders
67
+
68
+ ```bash
69
+ mkdir -p apps/wordpress/wp-content/plugins
70
+ mkdir -p apps/wordpress/wp-content/themes
71
+ mkdir -p apps/wordpress/wp-content/mu-plugins
72
+ ```
73
+
74
+ ### 1b. Write `.wp-env.json` (skip if present)
75
+
76
+ If `.wp-env.json` already exists, **inspect it**:
77
+
78
+ - If it maps `wp-content/*` from `./apps/wordpress/wp-content/*` — leave the mappings alone. If a Caddy domain is configured, make sure neither `.wp-env.json` nor `.wp-env.override.json` defines `config.WP_HOME` / `config.WP_SITEURL`; see Step 3.
79
+ - If it maps from `./wp-content/*` (the legacy layout) — show the diff and ask the user before rewriting. Some older scaffolds had `wp-content/` at the root; the new convention is `apps/wordpress/wp-content/` so deploy and local share one tree.
80
+
81
+ Resolve the local URL **before** writing the config block:
82
+
83
+ - If `.refact-os.json` › `wpEnv.localDomain` is set → `LOCAL_URL="https://<that-host>"`.
84
+ - Otherwise → `LOCAL_URL="http://localhost:8888"`.
85
+
86
+ Template body when creating:
87
+
88
+ ```json
89
+ {
90
+ "$schema": "https://schemas.wp.org/trunk/wp-env.json",
91
+ "core": null,
92
+ "phpVersion": "8.2",
93
+ "themes": [],
94
+ "plugins": [],
95
+ "mappings": {
96
+ "wp-content/plugins": "./apps/wordpress/wp-content/plugins",
97
+ "wp-content/themes": "./apps/wordpress/wp-content/themes",
98
+ "wp-content/mu-plugins": "./apps/wordpress/wp-content/mu-plugins"
99
+ },
100
+ "config": {
101
+ "WP_DEBUG": true,
102
+ "WP_DEBUG_LOG": true,
103
+ "WP_DEBUG_DISPLAY": false,
104
+ "SCRIPT_DEBUG": true,
105
+ "WP_ENVIRONMENT_TYPE": "local"
106
+ }
107
+ }
108
+ ```
109
+
110
+ Omit `WP_HOME` / `WP_SITEURL` from `.wp-env.json` and `.wp-env.override.json`. wp-env defines its own URL constants and may force its internal `:8888` port into WordPress URLs; when Caddy is in use, keep the public hostname in the DB plus the local-only mu-plugin from Step 3 instead.
111
+
112
+ ### 1c. Install `@wordpress/env`
113
+
114
+ If `@wordpress/env` is missing from `package.json` › `devDependencies`:
115
+
116
+ ```bash
117
+ npm install --save-dev @wordpress/env
118
+ ```
119
+
120
+ ### 1d. Add `wp:*` scripts to `package.json`
121
+
122
+ Add only the ones missing — never overwrite existing entries:
123
+
124
+ | Script | Command |
125
+ |---|---|
126
+ | `wp:start` | `wp-env start` |
127
+ | `wp:stop` | `wp-env stop` |
128
+ | `wp:destroy` | `wp-env destroy` |
129
+ | `wp:clean` | `wp-env clean all` |
130
+ | `wp:logs` | `wp-env logs all` |
131
+ | `wp:cli` | `wp-env run cli wp` |
132
+ | `wp:shell` | `wp-env run cli bash` |
133
+
134
+ ### 1e. Start the stack
135
+
136
+ ```bash
137
+ npx wp-env start
138
+ ```
139
+
140
+ First run downloads images and may take a few minutes. If it fails on `port already in use`, surface the exact error — don't guess at an alternate port without asking.
141
+
142
+ ### 1f. Report
143
+
144
+ Print to the user:
145
+
146
+ - Site URL: `<LOCAL_URL>` (the value resolved in 1b — either `https://<domain>` or `http://localhost:8888`).
147
+ - Admin URL: `<LOCAL_URL>/wp-admin` — login `admin` / `password` (wp-env defaults).
148
+ - Test connectivity: `npm run wp:cli -- --info`.
149
+
150
+ If a domain is configured but Caddy isn't running yet, also remind the user to run `/refact wp-env domain set <hostname>` (or, if already set, `caddy start --config ~/.refact/Caddyfile` to bring the proxy up).
151
+
152
+ ---
153
+
154
+ ## Step 2 — `pull`
155
+
156
+ `/refact wp-env pull` runs **2b → 2c → 2d** in order. The single-target variants (`pull plugins` / `pull mu-plugins` / `pull db`) run just that step. All variants share the preflight in 2a.
157
+
158
+ ### 2a. Resolve SSH target from `agent/AGENTS.md`
159
+
160
+ Parse the **SSH access** section of `agent/AGENTS.md`. The init flow fills these placeholders:
161
+
162
+ | agent/AGENTS.md field | Variable |
163
+ |---|---|
164
+ | `<ssh-staging-user>` | `SSH_USER` |
165
+ | `<ssh-staging-host>` | `SSH_HOST` |
166
+ | `<ssh-staging-port>` | `SSH_PORT` |
167
+ | `<staging-document-root>` | `DOC_ROOT` |
168
+ | `<staging-url>` (from the branch→env table) | `STAGING_URL` |
169
+
170
+ Build the SSH connection string:
171
+
172
+ ```bash
173
+ SSH_TARGET="${SSH_USER}@${SSH_HOST}"
174
+ SSH_OPTS="-p ${SSH_PORT}"
175
+ ```
176
+
177
+ Verify the connection before doing anything destructive:
178
+
179
+ ```bash
180
+ ssh ${SSH_OPTS} "${SSH_TARGET}" "test -d '${DOC_ROOT}/wp-content' && echo ok"
181
+ ```
182
+
183
+ If it doesn't print `ok`, stop. Common causes: wrong port, SSH key not registered with the host, doc root path wrong.
184
+
185
+ > **Production guard.** This flow only ever pulls from **staging**. If the user explicitly asks to pull from production, refuse and explain the safer path: pull staging instead, or have the user export a sanitized DB from prod manually. Production WP-CLI requires owner approval per `agent/AGENTS.md`.
186
+
187
+ ### 2b. Pull `plugins`
188
+
189
+ ```bash
190
+ rsync -avz --delete \
191
+ -e "ssh ${SSH_OPTS}" \
192
+ --exclude='index.php' \
193
+ "${SSH_TARGET}:${DOC_ROOT}/wp-content/plugins/" \
194
+ apps/wordpress/wp-content/plugins/
195
+ ```
196
+
197
+ Notes:
198
+
199
+ - `--delete` mirrors staging exactly. Warn the user once: "this removes any local-only plugins under `apps/wordpress/wp-content/plugins/`. Confirm?" If they decline, drop `--delete`.
200
+ - Some hosts inject plugins that don't belong in the repo (e.g. `kinsta-mu-plugins` lives in `mu-plugins/`, not here, so it shouldn't appear; but check). Don't auto-exclude anything beyond `index.php` without asking.
201
+ - After the rsync, remind the user to inspect `git status` for new tracked paths in `apps/wordpress/wp-content/plugins/`. If something new should reach Kinsta on deploy, the nested `apps/wordpress/.gitignore` needs an explicit `!` exception — see [`kinsta-auto-deploy.md`](./kinsta-auto-deploy.md) § "Adding new tracked files".
202
+
203
+ ### 2c. Pull `mu-plugins`
204
+
205
+ ```bash
206
+ rsync -avz --delete \
207
+ -e "ssh ${SSH_OPTS}" \
208
+ --exclude='index.php' \
209
+ --exclude='00-wp-env-local-url.php' \
210
+ --exclude='kinsta-mu-plugins/' \
211
+ --exclude='kinsta-mu-plugins.php' \
212
+ "${SSH_TARGET}:${DOC_ROOT}/wp-content/mu-plugins/" \
213
+ apps/wordpress/wp-content/mu-plugins/
214
+ ```
215
+
216
+ The Kinsta-injected mu-plugin is excluded because Kinsta manages it server-side; it isn't useful locally and shouldn't end up in git. For other hosts (WP Engine's `wpengine-common`, Pantheon's `pantheon.php`, etc.), apply the same logic — ask the user if you spot one you don't recognize before adding it to the exclude list permanently.
217
+
218
+ `00-wp-env-local-url.php` is a local-only helper created by `domain set`; keep excluding it so `pull mu-plugins --delete` doesn't remove the local Caddy URL fix.
219
+
220
+ ### 2d. Pull `db`
221
+
222
+ The local stack **must be running** for this step. If `npx wp-env run cli wp core is-installed` errors, run `npx wp-env start` first.
223
+
224
+ Resolve `LOCAL_URL` the same way Step 1b does: `https://<wpEnv.localDomain>` if it's set in `.refact-os.json`, otherwise `http://localhost:8888`.
225
+
226
+ ```bash
227
+ # 1. Dump staging DB and pipe straight into the wp-env container.
228
+ ssh ${SSH_OPTS} "${SSH_TARGET}" \
229
+ "cd '${DOC_ROOT}' && wp db export --single-transaction -" \
230
+ | npx wp-env run cli wp db import -
231
+
232
+ # 2. Rewrite URLs from staging → local.
233
+ npx wp-env run cli wp search-replace "${STAGING_URL}" "${LOCAL_URL}" \
234
+ --skip-columns=guid --all-tables
235
+
236
+ # If the staging URL in wp_options has no trailing slash, make sure home/siteurl land exactly.
237
+ npx wp-env run cli wp db query "UPDATE wp_options SET option_value='${LOCAL_URL}' WHERE option_name IN ('home','siteurl')"
238
+
239
+ # 3. Flush caches.
240
+ npx wp-env run cli wp cache flush
241
+ ```
242
+
243
+ Print `STAGING_URL → LOCAL_URL` before running `search-replace` and ask the user to confirm. A bad replace can rewrite half the DB to the wrong host and is painful to undo.
244
+
245
+ After import:
246
+
247
+ - Reset the admin password so the user can log in locally:
248
+
249
+ ```bash
250
+ npx wp-env run cli wp user update admin --user_pass=password --skip-email
251
+ ```
252
+
253
+ (Skip silently if `admin` doesn't exist — some sites use a different super-admin username; surface the existing admin list with `wp user list --role=administrator` and ask the user.)
254
+
255
+ - Remind the user to run `npm run wp:cli -- option get siteurl` to verify the rewrite landed.
256
+ - If a Caddy domain is enabled, also verify `curl -k -I -L --max-redirs 1 "https://<hostname>/wp-admin/"` ends on `https://<hostname>/wp-login.php...`, **not** `https://<hostname>:8888/...`.
257
+
258
+ If the staging host has no `wp-cli` on `$PATH`, **stop** rather than silently falling back — surface the error and the user can decide whether to install wp-cli on the server or pull a manual `mysqldump`. Don't invent a fallback in this flow.
259
+
260
+ ---
261
+
262
+ ## Step 3 — `domain set <hostname>` / `domain clear`
263
+
264
+ Front the wp-env stack with a Caddy reverse proxy so the local site answers on `https://<hostname>` instead of `http://localhost:8888`. The hostname is stored in `.refact-os.json` › `wpEnv.localDomain`, the proxy config lives in `~/.refact/`, and `/etc/hosts` maps the name to `127.0.0.1`.
265
+
266
+ ### 3a. Preflight
267
+
268
+ - `caddy` is on `$PATH`. If missing, stop and tell the user to install it (macOS: `brew install caddy`; Linux: see [caddyserver.com/docs/install](https://caddyserver.com/docs/install)).
269
+ - The hostname ends in `.local`, `.test`, or `.localhost`. Refuse public-looking suffixes (`.com`, `.io`, etc.) — Caddy would try to reach Let's Encrypt and fail, and the user almost certainly meant a local-only name. If they really want a registered TLD, surface the risk and ask again.
270
+ - The hostname is lowercase, kebab-case ASCII (`^[a-z0-9][a-z0-9-]*\.(local|test|localhost)$`).
271
+ - wp-env is running (`docker ps | grep -q wordpress`). If not, run `npx wp-env start` first — Caddy needs the upstream up.
272
+
273
+ ### 3b. `domain set <hostname>`
274
+
275
+ 1. Persist the value:
276
+
277
+ ```jsonc
278
+ // .refact-os.json
279
+ {
280
+ "stack": {
281
+ "wordpress": { "hosting": null, "runtime": null, "environments": {} }
282
+ },
283
+ "wpEnv": {
284
+ "localDomain": "<hostname>"
285
+ }
286
+ }
287
+ ```
288
+
289
+ Read, merge, write back via the same JSON the rest of `/refact` uses — don't hand-edit other keys.
290
+
291
+ 2. Ensure the global Caddy scaffold exists:
292
+
293
+ ```bash
294
+ mkdir -p ~/.refact/caddy
295
+ ```
296
+
297
+ Write `~/.refact/Caddyfile` (only if missing):
298
+
299
+ ```caddyfile
300
+ # Managed by /refact wp-env. Per-project site blocks live in ~/.refact/caddy/.
301
+ {
302
+ # global options
303
+ }
304
+ import caddy/*.caddyfile
305
+ ```
306
+
307
+ 3. Write the per-project site block at `~/.refact/caddy/<project-slug>.caddyfile`. `<project-slug>` is `basename` of the project root, lowercased, with non-alphanumerics replaced by `-`:
308
+
309
+ ```caddyfile
310
+ <hostname> {
311
+ reverse_proxy 127.0.0.1:8888 {
312
+ # wp-env may emit redirects to its internal port. Keep browser traffic on Caddy's HTTPS origin.
313
+ header_down Location "https://<hostname>:8888/" "https://<hostname>/"
314
+ header_down Location "https://localhost:8888/" "https://<hostname>/"
315
+ header_down Location "http://localhost:8888/" "https://<hostname>/"
316
+ }
317
+ }
318
+ ```
319
+
320
+ If the file already exists for this slug, overwrite it — this verb is the source of truth for the project's site block.
321
+
322
+ 4. Trust the Caddy local CA (idempotent — silently noops if already trusted):
323
+
324
+ ```bash
325
+ caddy trust
326
+ ```
327
+
328
+ On macOS this prompts for a sudo password the first time so it can add the root cert to the system keychain. Surface the prompt to the user; do not bypass.
329
+
330
+ 5. Add the `/etc/hosts` entry. This requires `sudo`. Don't run `sudo` directly from the flow — print the exact command and ask the user to run it themselves:
331
+
332
+ ```bash
333
+ # The user runs this:
334
+ echo "127.0.0.1 <hostname>" | sudo tee -a /etc/hosts
335
+ ```
336
+
337
+ Before printing, check whether the entry already exists (`grep -E "^127\.0\.0\.1[[:space:]]+<hostname>$" /etc/hosts`). If it's there, skip this step silently.
338
+
339
+ 6. Start or reload Caddy:
340
+
341
+ ```bash
342
+ # If not already running:
343
+ caddy start --config ~/.refact/Caddyfile --adapter caddyfile
344
+
345
+ # If already running:
346
+ caddy reload --config ~/.refact/Caddyfile --adapter caddyfile
347
+ ```
348
+
349
+ Detect "already running" with `pgrep -x caddy` or `caddy list-modules` exit status. Don't try both — pick one and stick with it.
350
+
351
+ 7. Do **not** set `.wp-env.json` or `.wp-env.override.json` › `config.WP_HOME` / `config.WP_SITEURL` for the Caddy hostname. wp-env rewrites URL constants to include its internal `:8888` port, which causes browsers to follow HTTPS redirects to the plain-HTTP wp-env port and fail with `SSL_ERROR_RX_RECORD_TOO_LONG`. Keep the public URL in the DB instead, and restart the stack after Caddy config changes:
352
+
353
+ ```bash
354
+ npx wp-env stop
355
+ npx wp-env start
356
+ ```
357
+
358
+ If the DB already has the old URL baked in (it usually does — WP stores `siteurl` and `home` in `wp_options`), run a one-shot search-replace:
359
+
360
+ ```bash
361
+ npx wp-env run cli wp search-replace "http://localhost:8888" "https://<hostname>" \
362
+ --skip-columns=guid --all-tables
363
+ npx wp-env run cli wp db query "UPDATE wp_options SET option_value='https://<hostname>' WHERE option_name IN ('home','siteurl')"
364
+ npx wp-env run cli wp cache flush
365
+ ```
366
+
367
+ Confirm the source/target URLs with the user before running.
368
+
369
+ 8. Write a local-only mu-plugin at `apps/wordpress/wp-content/mu-plugins/00-wp-env-local-url.php` (substitute `<hostname>`). This file is intentionally ignored by `apps/wordpress/.gitignore`; do not add a gitignore exception for it.
370
+
371
+ ```php
372
+ <?php
373
+ /**
374
+ * Local wp-env URL overrides.
375
+ *
376
+ * This file is ignored by git via apps/wordpress/.gitignore and should not be
377
+ * deployed. It keeps browser-facing URLs on the Caddy HTTPS origin instead of
378
+ * wp-env's internal :8888 port.
379
+ */
380
+
381
+ if ( defined( 'WP_ENVIRONMENT_TYPE' ) && 'local' === WP_ENVIRONMENT_TYPE ) {
382
+ $local_url = 'https://<hostname>';
383
+
384
+ $rewrite_local_url = static function ( $url ) use ( $local_url ) {
385
+ if ( ! is_string( $url ) ) {
386
+ return $url;
387
+ }
388
+
389
+ return str_replace(
390
+ array(
391
+ 'https://<hostname>:8888',
392
+ 'http://<hostname>:8888',
393
+ 'https://localhost:8888',
394
+ 'http://localhost:8888',
395
+ ),
396
+ $local_url,
397
+ $url
398
+ );
399
+ };
400
+
401
+ add_filter( 'option_home', static fn () => $local_url, PHP_INT_MAX );
402
+ add_filter( 'option_siteurl', static fn () => $local_url, PHP_INT_MAX );
403
+
404
+ foreach ( array( 'home_url', 'site_url', 'admin_url', 'includes_url', 'content_url', 'plugins_url', 'network_site_url' ) as $url_filter ) {
405
+ add_filter( $url_filter, $rewrite_local_url, PHP_INT_MAX );
406
+ }
407
+ }
408
+ ```
409
+
410
+ 9. Verify the browser-facing URLs:
411
+
412
+ ```bash
413
+ npx wp-env run cli wp cache flush
414
+ npm run wp:cli -- option get home
415
+ npm run wp:cli -- option get siteurl
416
+ curl -k -I -L --max-redirs 1 "https://<hostname>/wp-admin/"
417
+ if curl -k -sS "https://<hostname>/wp-login.php" | grep ':8888'; then
418
+ echo "unexpected :8888 URL"
419
+ fi
420
+ ```
421
+
422
+ Expected: `home` and `siteurl` print `https://<hostname>`, `/wp-admin/` redirects to `https://<hostname>/wp-login.php?...`, and the login page contains no `:8888` asset/form URLs. If the browser shows `SSL_ERROR_RX_RECORD_TOO_LONG`, it is almost certainly following an HTTPS URL on `:8888`; inspect the `Location` header and login markup.
423
+
424
+ 10. Report:
425
+
426
+ - Site URL: `https://<hostname>`
427
+ - Admin URL: `https://<hostname>/wp-admin`
428
+ - Caddyfile: `~/.refact/caddy/<project-slug>.caddyfile`
429
+ - Local URL helper: `apps/wordpress/wp-content/mu-plugins/00-wp-env-local-url.php` (gitignored)
430
+ - That the value is persisted in `.refact-os.json` so other teammates pick it up on their next `/refact wp-env setup`.
431
+
432
+ ### 3c. `domain clear`
433
+
434
+ 1. Remove `wpEnv.localDomain` from `.refact-os.json` (delete the key; if `wpEnv` becomes empty, remove that too).
435
+ 2. Delete `~/.refact/caddy/<project-slug>.caddyfile`. If the directory is now empty, leave the directory in place — another project may need it.
436
+ 3. Delete `apps/wordpress/wp-content/mu-plugins/00-wp-env-local-url.php` if it exists.
437
+ 4. Reload Caddy if it's running (`caddy reload …`). Don't stop the global Caddy process — other projects may rely on it.
438
+ 5. Tell the user the `/etc/hosts` entry was **not** removed automatically (it's harmless and removing it would need another sudo prompt). Print the exact `sudo sed -i ''` command they can run if they want it gone.
439
+ 6. Keep `.wp-env.json` and `.wp-env.override.json` free of `WP_HOME` / `WP_SITEURL`, restart wp-env, then run a search-replace from `https://<old-hostname>` back to `http://localhost:8888` so the DB matches.
440
+
441
+ ### 3d. Multi-project caveat
442
+
443
+ Caddy binds `:80` and `:443`, so only one Caddy process can run per machine. The `~/.refact/Caddyfile` `import` pattern handles this: each project owns its own site block under `~/.refact/caddy/`, and the single running Caddy serves all of them. **Don't** scaffold a project-local Caddyfile inside the repo — it would either bind-collide or get accidentally committed.
444
+
445
+ If two projects pick the same hostname, the second `domain set` overwrites the first project's site block. Detect this in 4b step 3 — if the existing file's contents reference a different `127.0.0.1:<port>` upstream than what we're about to write, ask the user to confirm before overwriting.
446
+
447
+ ## Step 4 — `reset`
448
+
449
+ ```bash
450
+ npx wp-env destroy
451
+ npx wp-env start
452
+ ```
453
+
454
+ `destroy` removes the MySQL volume; the next `start` rebuilds with the wp-env default sample install. **Local DB changes are lost** — confirm with the user before running. Files under `apps/wordpress/wp-content/` are not touched (they live on the host, not in the volume).
455
+
456
+ After reset, the user typically wants `/refact wp-env pull db` to restore staging state.
457
+
458
+ ---
459
+
460
+ ## Guardrails
461
+
462
+ - **`apps/wordpress/wp-content/` is the source of truth.** Never write to `./wp-content/` at the repo root — that's the legacy layout. If you find both, ask the user which one is current before any pull.
463
+ - **Never pull from production.** The flow is staging-only. If the user explicitly insists on prod, stop and require explicit owner approval (per `agent/AGENTS.md` § Deployment safety).
464
+ - **Never run `search-replace` without confirming the URLs.** A bad replace can rewrite half the DB to the wrong host and is painful to undo. Always print the source and target URL once before executing.
465
+ - **Never `wp-env destroy` without confirmation.** It nukes the local DB. `reset` confirms; ad-hoc destroy elsewhere should too.
466
+ - **Never invent a fallback** when staging's wp-cli or rsync isn't available. Surface the error, let the user decide.
467
+ - **Never edit `agent/AGENTS.md` to fill missing SSH fields from this flow.** Send the user to `/refact init` so the placeholders are filled in one place.
468
+ - **Never commit pulled DB dumps.** If you write a `.sql` file at any point during this flow, place it in a gitignored path (e.g. `./.wp-env-dumps/`) and delete it after import.
469
+ - **Never write Caddyfiles into the project tree.** Per-project site blocks live under `~/.refact/caddy/`. Project-local files would either bind-collide with another project's Caddy instance or end up committed by accident.
470
+ - **Never accept a public-TLD hostname** for `domain set` (`.com`, `.io`, etc.) without explicit user confirmation — Caddy would try Let's Encrypt and either fail or, worse, attempt ACME against a domain the user doesn't control.
471
+ - **Never run `sudo` from this flow.** `/etc/hosts` edits and `caddy trust` print the command for the user to run; they keep the credential prompt in their own hands.
472
+
473
+ ## When to stop and ask the user
474
+
475
+ - `git status` shows large, unexpected diffs after `pull plugins` (e.g. an entire vendored plugin you didn't know was there) → surface before staging the changes.
476
+ - The staging URL parsed from `agent/AGENTS.md` doesn't match the user's stated staging URL → resolve before running `search-replace`.
477
+ - A pull would `--delete` a local-only plugin that looks like work-in-progress (no matching commit history) → ask first.
478
+ - The user asks to point this flow at a different remote (e.g. "pull from dev instead") → that's an architectural change; confirm whether to extend `agent/AGENTS.md` with a `<dev-*>` block or treat it as a one-off prompt.
@@ -0,0 +1,46 @@
1
+ # wp-cli.yml — remote environment aliases for this engagement.
2
+ #
3
+ # Copy this file to `wp-cli.yml` and fill in the host-specific values for your
4
+ # project. Keep `wp-cli.yml` committed; it is non-secret routing only. SSH keys,
5
+ # tokens, and passwords stay out of this file (1Password, OS keychain, ~/.ssh/).
6
+ #
7
+ # Once configured, agents and engineers run remote WP-CLI like:
8
+ # wp @staging plugin list
9
+ # wp @development cache flush
10
+ #
11
+ # Host-specific setup (SSH config, key registration, dashboards) lives in
12
+ # 1Password and the host's own docs. WP Engine uses an SSH gateway with a
13
+ # /sites/<env>/ layout; Kinsta uses a custom port per site; Pantheon uses
14
+ # Terminus instead of WP-CLI aliases (see the Pantheon block below).
15
+
16
+ # --- WP Engine example ------------------------------------------------------
17
+ # Requires `~/.ssh/config` entries `wpe-staging` and `wpe-development`.
18
+ # @staging:
19
+ # ssh: wpe-staging:/sites/<staging-env>
20
+ # @development:
21
+ # ssh: wpe-development:/sites/<dev-env>
22
+
23
+ # --- Kinsta example ---------------------------------------------------------
24
+ # Requires `~/.ssh/config` entries `kinsta-staging` and `kinsta-development`
25
+ # (each with the custom port from MyKinsta).
26
+ # @staging:
27
+ # ssh: kinsta-staging:~/public
28
+ # @development:
29
+ # ssh: kinsta-development:~/public
30
+
31
+ # --- Generic SSH host -------------------------------------------------------
32
+ # @staging:
33
+ # ssh: deploy@staging.example.com:/var/www/staging/public_html
34
+ # @development:
35
+ # ssh: deploy@dev.example.com:/var/www/dev/public_html
36
+
37
+ # --- Pantheon ---------------------------------------------------------------
38
+ # Pantheon uses Terminus rather than WP-CLI aliases. Do not add `@staging` /
39
+ # `@development` here for Pantheon projects; use:
40
+ # terminus remote:wp <site>.<env> -- <command>
41
+
42
+ # --- Production -------------------------------------------------------------
43
+ # Intentionally NOT defined here. Production WP-CLI requires explicit owner
44
+ # approval and a verified backup (per .cursor/rules/30-security.mdc). When
45
+ # approved, run it manually in a one-off shell — do not commit a
46
+ # `@production` alias.