feat: Initial Claude skills for websimple-devops
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
*.bak
|
||||||
|
*.bak.*
|
||||||
102
SKILL.md
Normal file
102
SKILL.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
---
|
||||||
|
name: websimple-devops
|
||||||
|
description: Operate the Websimple WordPress development environment — the local websimple-stack Docker Compose project, WordPress site lifecycle (create, sync, reset), Composer dependencies, Kaliroots child themes, ACF conventions, asset build pipelines, browser/Xdebug debugging, and Gitea metadata lookups. Use whenever the user mentions a Websimple WordPress site, a `wp-sites` Gitea repository, the `ledevsimple.ca` local domain (or another `WEBSIMPLE_STACK_DOMAIN`), `websimple-stack`, `wp-apache2`, Kaliroots, ACF in a Websimple context, or any local WordPress dev task on a developer machine configured for Websimple. Also use when a request implies operations on a Websimple project even without naming it explicitly — for example "spin up a local copy of example.com", "pull production into local for site X", "fix the Composer install for this site", "what's the production URL for client Y".
|
||||||
|
---
|
||||||
|
|
||||||
|
# Websimple DevOps
|
||||||
|
|
||||||
|
Operate Websimple's local WordPress development environment: the `websimple-stack` Docker Compose project, the WordPress sites it serves, the Composer-managed plugins/themes inside them, and the Gitea metadata that holds deployment values.
|
||||||
|
|
||||||
|
This skill packages a router (this file) plus 13 topic-specific reference files under `references/` and a Bash wrapper for Gitea API reads under `scripts/`. The reference files contain the operational detail — read them when a task lands in their area. Don't try to remember their content; resolve the topic, then read the file.
|
||||||
|
|
||||||
|
## Required environment variables
|
||||||
|
|
||||||
|
These must be set in the user's shell before any operation that needs them. Fail fast and ask the user to export them if they're missing — do not guess defaults.
|
||||||
|
|
||||||
|
| Variable | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `WEBSIMPLE_STACK_PATH` | Absolute path to the local `websimple-stack` Docker Compose checkout. |
|
||||||
|
| `WEBSIMPLE_STACK_PROTOCOL` | `http` or `https` — protocol used by local Traefik routing. |
|
||||||
|
| `WEBSIMPLE_STACK_DOMAIN` | Local domain suffix Traefik routes wildcard subdomains for (e.g. `ledevsimple.ca`). |
|
||||||
|
| `WP_LOCAL_ROOT_PATH` | Absolute path to the shared WordPress root path served by the `wp-apache2` service. |
|
||||||
|
| `WEBSIMPLE_GITEA_API_TOKEN` | Read-only Gitea API token for metadata lookups. |
|
||||||
|
|
||||||
|
Treat all of these as sensitive in the sense that the token must never be echoed back in chat. The paths and domain are not secrets, but still read them from the environment rather than hard-coding so the skill stays portable across user setups.
|
||||||
|
|
||||||
|
### Loading the variables (Cowork sessions)
|
||||||
|
|
||||||
|
The Cowork bash sandbox does **not** inherit the user's host `.bashrc`/`.profile` — each bash call starts fresh and only sees env vars injected by the harness. The user keeps a shared env file at `~/.config/cowork/env.sh` on the host (also sourced by their host `.bashrc`, so it's a single source of truth). In Cowork, that path appears under the session's mount as `/sessions/<session-id>/mnt/cowork/env.sh`.
|
||||||
|
|
||||||
|
At the start of any bash call that needs these variables, source the file if it exists. Resolve the session mount path dynamically rather than hard-coding a session ID:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
COWORK_ENV="$(ls /sessions/*/mnt/cowork/env.sh 2>/dev/null | head -1)"
|
||||||
|
[ -n "$COWORK_ENV" ] && . "$COWORK_ENV"
|
||||||
|
```
|
||||||
|
|
||||||
|
If `env.sh` isn't found, fall back to normal behavior: check each variable and, when something's missing, ask the user either to export it or to approve mounting `~/.config/cowork/` (whichever is the actual gap).
|
||||||
|
|
||||||
|
## Reference index — read the file that matches the task
|
||||||
|
|
||||||
|
For any non-trivial task, **read the matching reference file first** before producing commands or making changes. The reference files encode conventions that aren't obvious from the codebase and that the user expects to be followed.
|
||||||
|
|
||||||
|
| Concern | Reference file |
|
||||||
|
|---|---|
|
||||||
|
| Host environment, Docker Compose, Traefik, required executables, MySQL/WP-CLI defaults | `references/websimple-stack.md` |
|
||||||
|
| Websimple Gitea conventions, repository URLs, Actions variables, token usage | `references/websimple-gitea.md` |
|
||||||
|
| WordPress project naming, slug derivation, local/remote URL/DB/path conventions | `references/wp-project.md` |
|
||||||
|
| Local site lifecycle — create, clone, provision from remote, reset, delete | `references/wp-local-site.md` |
|
||||||
|
| Composer dependency management, Satis private repo, plugin/theme integrity | `references/wp-composer.md` |
|
||||||
|
| General WordPress theme PHP conventions (non-Kaliroots) | `references/wp-theme-dev.md` |
|
||||||
|
| Kaliroots parent/child theme conventions and overrides | `references/wp-kaliroots.md` |
|
||||||
|
| ACF field groups, local JSON, options pages, naming, output | `references/wp-acf.md` |
|
||||||
|
| Theme asset source, Vite/Tailwind build, enqueue integration | `references/wp-assets.md` |
|
||||||
|
| Lovable → WordPress conversion (Lovable React export into Websimple theme) | `references/wp-lovable.md` |
|
||||||
|
| Browser-based smoke checks, navigation, SSRF allowlisting | `references/wp-browser.md` |
|
||||||
|
| Autonomous Xdebug runtime debugging | `references/wp-xdebug.md` |
|
||||||
|
| PHPCS/PHPCBF tooling and coding-standard checks | `references/wp-code-style.md` |
|
||||||
|
|
||||||
|
Many tasks touch more than one area. For example, "sync example.com's production database into my local site" needs `wp-project.md` (to resolve names and paths), `websimple-gitea.md` (to read `WP_SITE_URL` and SSH metadata), and `wp-local-site.md` (the synchronize-from-remote workflow). Read all three before starting.
|
||||||
|
|
||||||
|
## Gitea API access — use the bundled script
|
||||||
|
|
||||||
|
Three read-only Gitea operations are available via `scripts/gitea.sh`. They replace what used to be OpenClaw plugin tools and require `WEBSIMPLE_GITEA_API_TOKEN` plus `jq` and `curl`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List repositories in the wp-sites org (default org is wp-sites)
|
||||||
|
bash "${SKILL_ROOT}/scripts/gitea.sh" repos-list
|
||||||
|
|
||||||
|
# List Actions variables for a repository
|
||||||
|
bash "${SKILL_ROOT}/scripts/gitea.sh" vars-list wp-sites example
|
||||||
|
|
||||||
|
# Get one Actions variable
|
||||||
|
bash "${SKILL_ROOT}/scripts/gitea.sh" var-get wp-sites example WP_SITE_URL
|
||||||
|
```
|
||||||
|
|
||||||
|
Each prints a JSON envelope on stdout and exits non-zero with a stderr message on failure. See `references/websimple-gitea.md` for the full operation set and output shapes.
|
||||||
|
|
||||||
|
`${SKILL_ROOT}` here is the directory containing this `SKILL.md`. Resolve it from the path of this file when invoking the script.
|
||||||
|
|
||||||
|
## Safety defaults
|
||||||
|
|
||||||
|
These apply across every reference file. They override anything in a reference that conflicts.
|
||||||
|
|
||||||
|
- **Prefer read-only inspection before changing anything.** Before deleting, dropping a database, overwriting files, or running broad updates, show the resolved values (path, DB name, URL) and ask for confirmation.
|
||||||
|
- **Treat remote hosts as read-only.** Production/staging WordPress sites should only be read from (database export, file rsync) — never written to, never updated, never have plugins deactivated, never have WP-CLI mutations run against them. The only approved remote action shapes are read-only WP-CLI commands (e.g. `wp option get`, `wp db export -`), `rsync` *from* remote *to* local, and SSH connection tests.
|
||||||
|
- **Never echo secrets.** Do not print `WEBSIMPLE_GITEA_API_TOKEN`, admin passwords, or anything from `~/.my.cnf` or `~/.wp-cli/config.yml` back in chat. When using them, reference them by variable name.
|
||||||
|
- **Preserve source-controlled files.** When repairing Composer-managed plugins/themes, never delete directories that are tracked in Git — only ignored/disposable install artifacts. See `references/wp-composer.md` for the integrity-check pattern.
|
||||||
|
- **Use recoverable deletion where possible.** Prefer moving files to the user's trash over `rm -rf` when shelling out, especially for project directories.
|
||||||
|
|
||||||
|
## Workflow shape
|
||||||
|
|
||||||
|
For a typical request:
|
||||||
|
|
||||||
|
1. Identify which reference(s) the task falls under from the table above.
|
||||||
|
2. Read those reference file(s) — fully, not just headings.
|
||||||
|
3. Resolve the WordPress project's slug, paths, domain, and DB name using `references/wp-project.md` conventions and the relevant environment variables.
|
||||||
|
4. If remote metadata is needed (production URL, SSH host/port/user/path), prefer `scripts/gitea.sh var-get` against the matching `wp-sites/<slug>` repository before falling back to SSH inspection.
|
||||||
|
5. Run small read-only checks before any change.
|
||||||
|
6. Make the smallest change that accomplishes the request, asking for confirmation before destructive steps.
|
||||||
|
7. Verify with a small post-change check.
|
||||||
|
|
||||||
|
When in doubt about which reference applies, the simple rule: the file's name describes its concern. `wp-acf.md` is for ACF questions, `wp-xdebug.md` is for Xdebug-driven debugging, and so on.
|
||||||
70
references/websimple-gitea.md
Normal file
70
references/websimple-gitea.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Websimple Gitea
|
||||||
|
|
||||||
|
Use this skill for Websimple Gitea repository conventions and read-only metadata lookup workflows.
|
||||||
|
|
||||||
|
## Base URL
|
||||||
|
|
||||||
|
Websimple Gitea is hosted at:
|
||||||
|
|
||||||
|
- `https://gitea.websimple.com`
|
||||||
|
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
Authenticated Gitea API reads use the `WEBSIMPLE_GITEA_API_TOKEN` environment variable. The bundled `scripts/gitea.sh` wrapper reads this value automatically. Assume the base URL is constant at `https://gitea.websimple.com`.
|
||||||
|
|
||||||
|
If `WEBSIMPLE_GITEA_API_TOKEN` is not set in the user's environment, ask them to export it before proceeding. Never print or echo the token's value back in chat. Use it only for read-only Gitea API requests.
|
||||||
|
|
||||||
|
## Available operations
|
||||||
|
|
||||||
|
The skill bundles a `scripts/gitea.sh` wrapper that exposes three read-only operations. The path is relative to the skill root, so resolve it from the skill directory (typically something like `<skill-root>/scripts/gitea.sh`).
|
||||||
|
|
||||||
|
Each operation prints a JSON envelope to stdout and exits non-zero on failure (with an error message on stderr).
|
||||||
|
|
||||||
|
- `gitea.sh repos-list [--org <org>]` — list repositories for an organization. Default org is `wp-sites`. Maps to `GET /orgs/{org}/repos`.
|
||||||
|
- `gitea.sh vars-list <owner> <repo>` — list Actions variables for a repository. Maps to `GET /repos/{owner}/{repo}/actions/variables`.
|
||||||
|
- `gitea.sh var-get <owner> <repo> <variable-name>` — get one Actions variable. Maps to `GET /repos/{owner}/{repo}/actions/variables/{variablename}`.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash "${SKILL_ROOT}/scripts/gitea.sh" var-get wp-sites example WP_SITE_URL
|
||||||
|
```
|
||||||
|
|
||||||
|
Pipe through `jq` if a specific field is needed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash "${SKILL_ROOT}/scripts/gitea.sh" var-get wp-sites example WP_SITE_URL | jq -r '.variable.data'
|
||||||
|
```
|
||||||
|
|
||||||
|
If the wrapper exits non-zero, surface its stderr message to the user rather than retrying blindly; common causes are a missing/expired token or a typo in owner/repo/variable name.
|
||||||
|
|
||||||
|
## WordPress repositories
|
||||||
|
|
||||||
|
Websimple WordPress site repositories live under the `wp-sites` organization.
|
||||||
|
|
||||||
|
For WordPress project slug `${slug}`:
|
||||||
|
|
||||||
|
- Repository URL: `https://gitea.websimple.com/wp-sites/${slug}`
|
||||||
|
|
||||||
|
## Actions variables
|
||||||
|
|
||||||
|
Use Gitea Actions variables as the source of truth for project deployment and remote environment metadata.
|
||||||
|
|
||||||
|
For WordPress projects, expect these deployment values, usually from repository Actions variables:
|
||||||
|
|
||||||
|
- `WP_SITE_URL`: production WordPress site URL, including protocol, matching the WordPress `siteurl` option concept.
|
||||||
|
- `REMOTE_HOST`: SSH host for the remote environment.
|
||||||
|
- `REMOTE_PORT`: SSH port for the remote environment.
|
||||||
|
- `REMOTE_USER`: SSH user for the remote environment.
|
||||||
|
- `REMOTE_PATH`: WordPress project path on the remote host.
|
||||||
|
|
||||||
|
`wp-sites` repositories may rely on organization/repository fallback variables or secrets for `REMOTE_*` deployment values. If a per-repository `REMOTE_HOST`, `REMOTE_PORT`, `REMOTE_USER`, or `REMOTE_PATH` variable is missing, check the relevant fallback variable/secret source before treating the repository metadata as incomplete.
|
||||||
|
|
||||||
|
Prefer reading Gitea deployment metadata before using SSH. SSH lookups are fallback-only when required metadata is missing after repository values and fallback variables/secrets are considered.
|
||||||
|
|
||||||
|
## Safety
|
||||||
|
|
||||||
|
Treat Gitea metadata reads as read-only. Do not modify repositories, variables, secrets, workflows, branches, tags, issues, releases, or pull requests unless a future workflow explicitly adds an approval path.
|
||||||
|
|
||||||
|
Do not expose tokens or secrets. If authentication is needed beyond `WEBSIMPLE_GITEA_API_TOKEN`, ask the user to set the appropriate environment variable rather than printing or storing secrets inline.
|
||||||
50
references/websimple-stack.md
Normal file
50
references/websimple-stack.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# websimple-stack
|
||||||
|
|
||||||
|
Use this skill for host environment, Docker Compose, Traefik routing, and shared tooling concerns surrounding the Websimple local development environment.
|
||||||
|
|
||||||
|
The Websimple local development Docker stack is maintained in Gitea:
|
||||||
|
|
||||||
|
- https://gitea.websimple.com/docker/websimple-stack
|
||||||
|
|
||||||
|
## Config values used here
|
||||||
|
|
||||||
|
These environment variables drive stack-level operations:
|
||||||
|
|
||||||
|
- `WEBSIMPLE_STACK_PATH`: local path to the `websimple-stack` Docker project checkout.
|
||||||
|
- `WEBSIMPLE_STACK_PROTOCOL`: protocol used by local Traefik routing, `http` or `https`.
|
||||||
|
- `WEBSIMPLE_STACK_DOMAIN`: local domain suffix used by Traefik labels.
|
||||||
|
|
||||||
|
Do not hard-code these values in commands or examples. Read them from the environment. If any are missing when needed, ask the user to export them before proceeding rather than guessing.
|
||||||
|
|
||||||
|
## Environment expectations
|
||||||
|
|
||||||
|
The plugin assumes it is running on a developer machine configured for the Websimple local development stack.
|
||||||
|
|
||||||
|
### Stack and routing
|
||||||
|
|
||||||
|
- The `websimple-stack` Docker Compose project is configured and running locally.
|
||||||
|
- A wildcard `*.${WEBSIMPLE_STACK_DOMAIN}` resolves to the local IP.
|
||||||
|
- Traefik serves local stack services at `${WEBSIMPLE_STACK_PROTOCOL}://*.${WEBSIMPLE_STACK_DOMAIN}` using that wildcard domain routing.
|
||||||
|
|
||||||
|
### Required host executables
|
||||||
|
|
||||||
|
The following executables are expected to be available in the host `$PATH`:
|
||||||
|
|
||||||
|
- `mysql`
|
||||||
|
- `node`
|
||||||
|
- `pnpm`
|
||||||
|
- `php`
|
||||||
|
- `composer`
|
||||||
|
- `wp`
|
||||||
|
|
||||||
|
### MySQL access
|
||||||
|
|
||||||
|
- MySQL credentials are stored in `~/.my.cnf`.
|
||||||
|
- The `mysql` client should connect without prompting for a password.
|
||||||
|
- `/etc/hosts` contains an entry resolving `mysql`, so the DB host resolves the same way on the host as it does inside containers.
|
||||||
|
|
||||||
|
### WP-CLI defaults
|
||||||
|
|
||||||
|
- WP-CLI defaults are configured in `~/.wp-cli/config.yml`.
|
||||||
|
- DB host, user, and password defaults are already set there.
|
||||||
|
- `wp core config` workflows should only need to specify the database name.
|
||||||
271
references/wp-acf.md
Normal file
271
references/wp-acf.md
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
# WP ACF
|
||||||
|
|
||||||
|
Use this skill for Websimple WordPress projects that use Advanced Custom Fields.
|
||||||
|
|
||||||
|
Use `references/wp-theme-dev.md` for general PHP/theme structure, `references/wp-code-style.md` for PHPCS/PHPCBF, `references/wp-browser.md` for visual checks, and `references/wp-xdebug.md` for runtime debugging.
|
||||||
|
|
||||||
|
## Core principles
|
||||||
|
|
||||||
|
- Keep ACF configuration predictable, portable, and version-controlled.
|
||||||
|
- Prefer clear field names that map naturally to template usage.
|
||||||
|
- Keep field registration/ACF hooks under `includes/vendors/acf.php` or the existing ACF vendor/module file.
|
||||||
|
- Keep rendering logic in templates and reusable data helpers under `includes/` when needed.
|
||||||
|
- Avoid changing field keys, field names, return formats, or option names casually; these may affect stored content.
|
||||||
|
|
||||||
|
## Local JSON
|
||||||
|
|
||||||
|
ACF field group JSON definitions should live under the active theme's `acf-json/` directory.
|
||||||
|
|
||||||
|
- Keep ACF JSON files version-controlled.
|
||||||
|
- Preserve the project's existing JSON directory if one exists.
|
||||||
|
- For greenfield projects, use `acf-json/` at the theme root.
|
||||||
|
- Ensure the `acf-json/` directory exists and is writable by WordPress.
|
||||||
|
- Do not delete or regenerate ACF JSON files without checking the diff.
|
||||||
|
|
||||||
|
When local JSON is enabled, saving a field group, post type, taxonomy, or options page in wp-admin automatically creates or updates its JSON file. Do not tell the user to manually re-export JSON after normal dashboard saves; instead, remind them to review and commit the changed JSON file.
|
||||||
|
|
||||||
|
There is no expected per-field-group `json_sync` flag in Websimple conventions. ACF local JSON is controlled by ACF's global JSON setting and save/load paths. ACF defaults local JSON on and uses the active theme's `acf-json/` folder unless customized.
|
||||||
|
|
||||||
|
If PHP hooks customize ACF JSON paths, keep them in the ACF vendor/module file, e.g. `includes/vendors/acf.php`.
|
||||||
|
|
||||||
|
## Field group keys and filenames
|
||||||
|
|
||||||
|
For greenfield field groups, use deterministic field group keys instead of ACF's auto-generated group keys.
|
||||||
|
|
||||||
|
The field group key should match the JSON filename and describe what the group targets:
|
||||||
|
|
||||||
|
```text
|
||||||
|
acf-json/group_post_project.json
|
||||||
|
key: group_post_project
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```text
|
||||||
|
group_post_project # fields for the project custom post type
|
||||||
|
group_page_home # fields for the home page/template
|
||||||
|
group_options_site # site options fields
|
||||||
|
group_taxonomy_sector # fields for the sector taxonomy
|
||||||
|
```
|
||||||
|
|
||||||
|
ACF field keys inside the group may remain auto-generated. The deterministic-key convention applies primarily to field groups so JSON files stay organized and easy to find.
|
||||||
|
|
||||||
|
For existing projects with auto-generated field group keys, preserve them unless the user explicitly approves a cleanup/migration. Do not rename existing field group keys casually because synced JSON, database references, and editor workflows may depend on them.
|
||||||
|
|
||||||
|
## Synchronizing ACF JSON
|
||||||
|
|
||||||
|
For ACF 6.8+ with WP-CLI available, prefer the built-in ACF JSON CLI commands.
|
||||||
|
|
||||||
|
Safe inspection:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wp acf json status
|
||||||
|
wp acf json sync --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
Apply pending local JSON changes to the database:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wp acf json sync
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful scoped variants:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wp acf json sync --type=field-group
|
||||||
|
wp acf json sync --key=group_post_project
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- `wp acf json sync` modifies the database; run `--dry-run` first, especially outside local development.
|
||||||
|
- The command syncs pending local JSON changes for ACF item types such as field groups, post types, taxonomies, and options pages.
|
||||||
|
- If no items are pending, expect an “already in sync” style result.
|
||||||
|
- Do not use a guessed `--all` flag; current ACF CLI sync defaults to all supported item types unless scoped with `--type` or `--key`.
|
||||||
|
|
||||||
|
If the project uses an older ACF/ACF PRO version without these commands:
|
||||||
|
|
||||||
|
1. Check the installed ACF version and whether an update is safe for the project.
|
||||||
|
2. If updating is low-risk and explicitly approved, update ACF/ACF PRO first, then use the CLI workflow.
|
||||||
|
3. Otherwise, use `references/wp-browser.md` to navigate wp-admin and sync from the ACF field group/admin screen.
|
||||||
|
|
||||||
|
For production or staging, ask before updating plugins or syncing database-backed ACF changes.
|
||||||
|
|
||||||
|
## Field group organization
|
||||||
|
|
||||||
|
Use field groups that reflect editorial concepts and template needs.
|
||||||
|
|
||||||
|
Common grouping patterns:
|
||||||
|
|
||||||
|
- page/template-specific fields;
|
||||||
|
- reusable section/component fields;
|
||||||
|
- site options;
|
||||||
|
- post type-specific metadata;
|
||||||
|
- taxonomy/user/menu-item metadata when needed.
|
||||||
|
|
||||||
|
Avoid dumping unrelated fields into a single large group. Prefer labels and instructions that make sense to editors.
|
||||||
|
|
||||||
|
## Field naming
|
||||||
|
|
||||||
|
Use stable, descriptive, snake_case field names.
|
||||||
|
|
||||||
|
Good examples:
|
||||||
|
|
||||||
|
```text
|
||||||
|
hero_title
|
||||||
|
hero_text
|
||||||
|
hero_image
|
||||||
|
cta_link
|
||||||
|
featured_posts
|
||||||
|
background_color
|
||||||
|
```
|
||||||
|
|
||||||
|
Avoid vague names like `title_2`, `content_block`, or `misc` unless the existing project convention requires it.
|
||||||
|
|
||||||
|
Do not rename existing field names unless the user accepts the content migration risk.
|
||||||
|
|
||||||
|
## Return formats
|
||||||
|
|
||||||
|
Respect existing return formats. Changing them can break templates.
|
||||||
|
|
||||||
|
Common preferences:
|
||||||
|
|
||||||
|
- image fields: use the format already expected by the theme; arrays are useful when templates need alt/title/sizes;
|
||||||
|
- link fields: arrays are usually practical for URL/title/target;
|
||||||
|
- post object / relationship fields: know whether the field returns IDs or objects before using it;
|
||||||
|
- true/false fields: handle empty/false explicitly.
|
||||||
|
|
||||||
|
When uncertain, inspect the field group JSON or runtime value before changing template code.
|
||||||
|
|
||||||
|
## Template usage
|
||||||
|
|
||||||
|
Prefer `get_field()` when the value needs escaping, conditionals, fallback handling, or reuse.
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php $title = get_field( 'hero_title' ); ?>
|
||||||
|
|
||||||
|
<?php if ( $title ) : ?>
|
||||||
|
<h1><?= esc_html( $title ); ?></h1>
|
||||||
|
<?php endif; ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
Use compact output tags with escaping, following `references/wp-theme-dev.md`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?= esc_html( get_field( 'eyebrow' ) ); ?>
|
||||||
|
<?= wp_kses_post( get_field( 'intro' ) ); ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
Avoid `the_field()` for dynamic frontend output unless the value is known safe for the exact context. Prefer explicit escaping with `get_field()`.
|
||||||
|
|
||||||
|
For image arrays:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php $image = get_field( 'hero_image' ); ?>
|
||||||
|
|
||||||
|
<?php if ( $image ) : ?>
|
||||||
|
<img
|
||||||
|
src="<?= esc_url( $image['url'] ); ?>"
|
||||||
|
alt="<?= esc_attr( $image['alt'] ?? '' ); ?>"
|
||||||
|
>
|
||||||
|
<?php endif; ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
For link arrays:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php $link = get_field( 'cta_link' ); ?>
|
||||||
|
|
||||||
|
<?php if ( $link ) : ?>
|
||||||
|
<a href="<?= esc_url( $link['url'] ); ?>" target="<?= esc_attr( $link['target'] ?: '_self' ); ?>">
|
||||||
|
<?= esc_html( $link['title'] ); ?>
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options pages
|
||||||
|
|
||||||
|
Keep ACF options-page registration in the ACF vendor/module file.
|
||||||
|
|
||||||
|
Use options pages for true site-wide settings, not content that belongs to a page, post, taxonomy, or theme template.
|
||||||
|
|
||||||
|
When reading option fields, make the option scope explicit:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php $phone = get_field( 'phone_number', 'option' ); ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Flexible content and repeaters
|
||||||
|
|
||||||
|
Keep flexible content layouts readable and component-oriented.
|
||||||
|
|
||||||
|
- Use layout names that map to template partials or sections.
|
||||||
|
- Keep layout fields scoped to what the layout renders.
|
||||||
|
- Avoid deeply nested repeaters/flexible fields unless there is a strong editorial reason.
|
||||||
|
- For complex flexible content rendering, prefer dispatching to template parts instead of one giant template file.
|
||||||
|
|
||||||
|
The dispatch pattern depends on the theme. **Check whether the theme is a Kaliroots child theme first** (see `references/wp-kaliroots.md`) — Kaliroots has its own template lookup helpers and overriding them with generic WP dispatch fragments the resolution path.
|
||||||
|
|
||||||
|
For a **generic (non-Kaliroots) WordPress theme**:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php if ( have_rows( 'sections' ) ) : ?>
|
||||||
|
<?php while ( have_rows( 'sections' ) ) : the_row(); ?>
|
||||||
|
<?php get_template_part( 'templates/content/sections/' . get_row_layout() ); ?>
|
||||||
|
<?php endwhile; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
For a **Kaliroots child theme**, dispatch through the Kaliroots section helper so child/parent override resolution works correctly:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php if ( have_rows( 'sections' ) ) : ?>
|
||||||
|
<?php while ( have_rows( 'sections' ) ) : the_row(); ?>
|
||||||
|
<?= kaliroots_section_template( get_row_layout() ); ?>
|
||||||
|
<?php endwhile; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
In a Kaliroots project, the section partial then lives at `templates/content/sections/{layout-name}.php` and is located via Kaliroots' lookup pipeline — see `references/wp-kaliroots.md` for the helper set and naming rules.
|
||||||
|
|
||||||
|
Verify that the target partial exists before introducing either dispatch into an existing theme. If the project already uses a different established pattern, follow that — don't reorganize.
|
||||||
|
|
||||||
|
Inside the layout partial, use `get_sub_field()` (not `get_field()`) to read layout sub-fields, and escape at output:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
$heading = get_sub_field( 'heading' );
|
||||||
|
$image = get_sub_field( 'image' );
|
||||||
|
?>
|
||||||
|
<?php if ( $heading ) : ?>
|
||||||
|
<h2><?= esc_html( $heading ); ?></h2>
|
||||||
|
<?php endif; ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Relationship and post object fields
|
||||||
|
|
||||||
|
Before using relationship/post object fields, confirm whether they return IDs or post objects.
|
||||||
|
|
||||||
|
- If using objects, restore global post state after setup.
|
||||||
|
- If using IDs, use explicit IDs with WordPress APIs.
|
||||||
|
- Avoid expensive relationship queries inside repeated templates when values can be prepared once.
|
||||||
|
|
||||||
|
## Refactoring workflow
|
||||||
|
|
||||||
|
1. Inspect the active theme, ACF plugin availability, existing ACF JSON path, and ACF-related include files.
|
||||||
|
2. Inspect existing field group JSON before changing field names, keys, return formats, or locations.
|
||||||
|
3. Keep PHP hooks/registration in the existing ACF module, usually `includes/vendors/acf.php`.
|
||||||
|
4. Keep template output escaped explicitly with `get_field()` where practical.
|
||||||
|
5. Make small, reversible changes.
|
||||||
|
6. After wp-admin field group edits, inspect the automatically updated `acf-json/` diff.
|
||||||
|
7. Run PHP lint/style checks via `references/wp-code-style.md` when PHP files changed.
|
||||||
|
8. Smoke-test affected frontend/admin behavior with `references/wp-browser.md` when relevant.
|
||||||
|
9. Inspect the diff, especially ACF JSON changes.
|
||||||
|
|
||||||
|
## Safety
|
||||||
|
|
||||||
|
- Do not rename existing field names or change return formats without calling out content/template impact.
|
||||||
|
- Do not delete ACF JSON files without explicit approval.
|
||||||
|
- Do not expose secrets from options pages or database values.
|
||||||
|
- Do not assume `the_field()` output is safe; escape based on context.
|
||||||
|
- Preserve the project's existing ACF structure when it is coherent.
|
||||||
155
references/wp-assets.md
Normal file
155
references/wp-assets.md
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
# WP Assets
|
||||||
|
|
||||||
|
Use this skill for Websimple WordPress theme asset source, build, and enqueue conventions.
|
||||||
|
|
||||||
|
Use `references/wp-theme-dev.md` for general PHP/theme structure, `references/wp-code-style.md` for PHP linting, and `references/wp-browser.md` for visual smoke tests.
|
||||||
|
|
||||||
|
## Core principles
|
||||||
|
|
||||||
|
- Keep build-time source assets under `src/`.
|
||||||
|
- Do not edit compiled/generated assets unless the user explicitly asks and there is no source available.
|
||||||
|
- Detect the project's actual pipeline before changing files.
|
||||||
|
- Preserve the existing asset toolchain unless the user asks for migration.
|
||||||
|
- Keep PHP enqueue integration aligned with the build output and manifest strategy.
|
||||||
|
|
||||||
|
## Source structure
|
||||||
|
|
||||||
|
Theme source assets should live under `src/` with clear subdirectories for frontend concerns.
|
||||||
|
|
||||||
|
Common structure:
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/
|
||||||
|
styles/ # CSS/SCSS source
|
||||||
|
scripts/ # JavaScript/TypeScript entrypoints and modules
|
||||||
|
components/ # Vue/React or shared frontend components
|
||||||
|
images/ # source images/icons when processed by the build
|
||||||
|
fonts/ # source fonts when processed by the build
|
||||||
|
```
|
||||||
|
|
||||||
|
Follow the existing project structure first. Do not reorganize assets broadly without approval.
|
||||||
|
|
||||||
|
## Detect the pipeline
|
||||||
|
|
||||||
|
Before editing assets, inspect the theme root for signals:
|
||||||
|
|
||||||
|
```text
|
||||||
|
package.json
|
||||||
|
vite.config.*
|
||||||
|
webpack.config.*
|
||||||
|
webpack.mix.js
|
||||||
|
postcss.config.*
|
||||||
|
tailwind.config.*
|
||||||
|
tsconfig.json
|
||||||
|
src/
|
||||||
|
assets/
|
||||||
|
dist/
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `package.json` scripts as the source of truth for build/dev commands.
|
||||||
|
|
||||||
|
Common signals:
|
||||||
|
|
||||||
|
- Vite: `vite.config.ts`, scripts like `dev`, `build`, `preview`.
|
||||||
|
- Legacy webpack/Mix: `webpack.config.js`, `webpack.mix.js`, scripts using `webpack`, `mix`, or older build tooling.
|
||||||
|
- Plain scripts/styles: source under `src/` with minimal bundler config.
|
||||||
|
|
||||||
|
If signals conflict, report the ambiguity before changing build config.
|
||||||
|
|
||||||
|
## Build output
|
||||||
|
|
||||||
|
Treat build output as generated unless the project clearly uses checked-in compiled assets.
|
||||||
|
|
||||||
|
Common output directories:
|
||||||
|
|
||||||
|
```text
|
||||||
|
assets/
|
||||||
|
dist/
|
||||||
|
```
|
||||||
|
|
||||||
|
Compiled/generated assets should normally be excluded from Git. For new or modernized projects, prefer ignoring generated build output and committing only source/config files.
|
||||||
|
|
||||||
|
Legacy projects may already commit compiled assets. Do not remove those generated files or change `.gitignore` behavior without approval. For those projects, keep the committed-build convention and make sure to run the build before committing so generated assets stay in sync with source changes.
|
||||||
|
|
||||||
|
Do not edit minified files, hashed files, generated manifests, compiled CSS/JS, source maps, or copied vendor assets directly when source files exist.
|
||||||
|
|
||||||
|
If compiled assets are committed in the project, update them by running the project build instead of manual edits.
|
||||||
|
|
||||||
|
## Vite conventions
|
||||||
|
|
||||||
|
For Vite-based themes:
|
||||||
|
|
||||||
|
- inspect `vite.config.*` before adding entrypoints;
|
||||||
|
- keep entrypoints under `src/`;
|
||||||
|
- preserve existing manifest/output settings;
|
||||||
|
- use the manifest when PHP enqueue code relies on hashed filenames;
|
||||||
|
- run the project build after changing source assets when verification requires compiled output.
|
||||||
|
|
||||||
|
Do not rewrite Vite config or migrate legacy projects to Vite unless explicitly requested.
|
||||||
|
|
||||||
|
## Legacy webpack conventions
|
||||||
|
|
||||||
|
For legacy webpack/Mix themes:
|
||||||
|
|
||||||
|
- inspect `webpack.config.*` or `webpack.mix.js` first;
|
||||||
|
- preserve existing entrypoint names and output paths;
|
||||||
|
- avoid modernizing syntax or build tooling as part of unrelated changes;
|
||||||
|
- run the existing build script when compiled assets must be refreshed.
|
||||||
|
|
||||||
|
Do not convert webpack/Mix to Vite without an explicit migration request.
|
||||||
|
|
||||||
|
## Enqueue integration
|
||||||
|
|
||||||
|
Asset enqueue logic usually belongs under theme PHP includes, often `includes/core/assets.php` or the existing project equivalent.
|
||||||
|
|
||||||
|
Before changing enqueue code:
|
||||||
|
|
||||||
|
1. Identify the build output path.
|
||||||
|
2. Identify whether filenames are stable or hashed.
|
||||||
|
3. Identify whether the theme uses a manifest.
|
||||||
|
4. Preserve existing handles, dependencies, script type/module settings, and localization/global variables unless changing them is required.
|
||||||
|
|
||||||
|
For stable filenames, filemtime-based cache busting is acceptable when already used by the project:
|
||||||
|
|
||||||
|
```php
|
||||||
|
wp_enqueue_style(
|
||||||
|
'example-theme',
|
||||||
|
get_stylesheet_directory_uri() . '/dist/css/theme.css',
|
||||||
|
[],
|
||||||
|
filemtime( get_stylesheet_directory() . '/dist/css/theme.css' )
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
For hashed filenames, read from the manifest rather than hardcoding generated names.
|
||||||
|
|
||||||
|
## Vue and React source
|
||||||
|
|
||||||
|
Vue/React source should still live under `src/`, typically under `src/components/` or an existing frontend app directory.
|
||||||
|
|
||||||
|
Keep this skill focused on source placement and build integration. For component architecture, state management, app mounting, or framework-specific conventions, use dedicated future `wp-vue` or `wp-react` guidance.
|
||||||
|
|
||||||
|
## Styling conventions
|
||||||
|
|
||||||
|
- Keep styles under `src/styles/` or the existing style source directory.
|
||||||
|
- Preserve the project's CSS methodology and naming conventions.
|
||||||
|
- Avoid broad restyling when the user asked for a targeted fix.
|
||||||
|
- Prefer source-level changes over compiled CSS edits.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. Inspect theme root, `package.json`, build config, source directories, output directories, and enqueue PHP.
|
||||||
|
2. Determine whether the project uses Vite, webpack/Mix, or another pipeline.
|
||||||
|
3. Edit source files under `src/` or the established source directory.
|
||||||
|
4. Do not touch generated output manually.
|
||||||
|
5. Run the smallest relevant check: build, typecheck, lint, or targeted frontend smoke test.
|
||||||
|
6. If compiled assets are committed, run the build before committing and inspect generated diffs.
|
||||||
|
7. Use `references/wp-browser.md` to verify visible frontend changes.
|
||||||
|
8. Report changed source files, generated files, and any build/verification blockers.
|
||||||
|
|
||||||
|
## Safety
|
||||||
|
|
||||||
|
- Do not run package installs or major upgrades without approval.
|
||||||
|
- Do not migrate build tools without explicit request.
|
||||||
|
- Do not edit compiled/minified/generated assets directly when source exists.
|
||||||
|
- Do not remove legacy build config just because a newer tool would be nicer.
|
||||||
|
- Watch for large generated diffs and call them out before committing.
|
||||||
77
references/wp-browser.md
Normal file
77
references/wp-browser.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# WP Browser
|
||||||
|
|
||||||
|
Use this skill for WordPress tasks that require a real browser instead of WP-CLI or file inspection. Prefer `references/wp-project.md` first when you need to resolve a project slug, local URL, production URL, repo, or paths.
|
||||||
|
|
||||||
|
## Safety and scope
|
||||||
|
|
||||||
|
- Treat local sites as safe to inspect and operate, but still avoid destructive admin actions unless explicitly requested.
|
||||||
|
- Ask before changing production content, settings, users, plugins, themes, orders, forms, menus, options, or publishing state.
|
||||||
|
- Prefer read-only checks on remote/production sites: navigate, inspect, screenshot, verify text/layout, check console/network symptoms.
|
||||||
|
- Do not submit public forms, checkout flows, newsletter signups, contact forms, or anything that sends email/external effects unless explicitly requested.
|
||||||
|
- If login is required, use existing browser session/cookies when available. Ask for credentials only if no usable session exists.
|
||||||
|
|
||||||
|
## Choosing the URL
|
||||||
|
|
||||||
|
Resolve the target URL from the request:
|
||||||
|
|
||||||
|
- If the user gives a full URL, use it directly.
|
||||||
|
- If the user gives a project slug, use `references/wp-project.md` conventions to derive the local site URL: `${WEBSIMPLE_STACK_PROTOCOL}://${slug}.${WEBSIMPLE_STACK_DOMAIN}`.
|
||||||
|
- If the user says production/remote, prefer `WP_SITE_URL` from Gitea repository Actions variables via `references/websimple-gitea.md` conventions/tools when available.
|
||||||
|
- If the request is ambiguous between local and production, default to local and state that assumption briefly.
|
||||||
|
|
||||||
|
## Browser environment prerequisite
|
||||||
|
|
||||||
|
Websimple local dev sites may resolve to private network IPs, for example a `*.${WEBSIMPLE_STACK_DOMAIN}` hostname resolving to an IPv6 ULA address like `fd00::/8`. Some browser/MCP harnesses are SSRF-guarded and block private-network targets by default.
|
||||||
|
|
||||||
|
If browser navigation to a local Websimple site fails with a policy-related error (e.g. "navigation blocked by policy"), the browser harness likely needs an allowlist entry for the local Websimple domain plus private-network opt-in. The shape of that configuration depends on the harness in use (Playwright MCP, OpenClaw, etc.), but conceptually:
|
||||||
|
|
||||||
|
- Allow `*.${WEBSIMPLE_STACK_DOMAIN}` (and `${WEBSIMPLE_STACK_DOMAIN}` itself) on the hostname allowlist.
|
||||||
|
- Permit private-network destinations.
|
||||||
|
|
||||||
|
Resolve `${WEBSIMPLE_STACK_DOMAIN}` from the environment; do not hard-code a specific local domain. Prefer an allowlisted form over globally allowing private-network access. Restart/reload the browser harness after changing its SSRF policy.
|
||||||
|
|
||||||
|
## Browser workflow
|
||||||
|
|
||||||
|
1. Open or reuse a browser tab for the target site.
|
||||||
|
2. Take a snapshot before interacting; use semantic/ARIA refs where possible.
|
||||||
|
3. Navigate like a user: click links/buttons, fill inputs, use menus, and wait for reliable UI state instead of arbitrary delays.
|
||||||
|
4. Check the page result with at least one concrete signal: visible text, URL, status page, screenshot, console messages, or DOM state.
|
||||||
|
5. Report concise evidence: URL checked, action performed, result, and any blockers.
|
||||||
|
|
||||||
|
For multi-step browser flows, login checks, stale refs, or tab recovery, follow the general `browser-automation` skill’s browser-control practices.
|
||||||
|
|
||||||
|
## WordPress admin
|
||||||
|
|
||||||
|
Common admin URLs:
|
||||||
|
|
||||||
|
- Dashboard: `/wp-admin/`
|
||||||
|
- Login: `/wp-login.php`
|
||||||
|
- Plugins: `/wp-admin/plugins.php`
|
||||||
|
- Themes: `/wp-admin/themes.php`
|
||||||
|
- Customizer: `/wp-admin/customize.php`
|
||||||
|
- Menus: `/wp-admin/nav-menus.php`
|
||||||
|
- Pages: `/wp-admin/edit.php?post_type=page`
|
||||||
|
- Posts: `/wp-admin/edit.php`
|
||||||
|
- Site Health: `/wp-admin/site-health.php`
|
||||||
|
|
||||||
|
When checking admin screens:
|
||||||
|
|
||||||
|
- Confirm whether the session is authenticated by looking for the admin bar, dashboard, or login form.
|
||||||
|
- Avoid saving forms unless requested.
|
||||||
|
- For settings screens, inspect current values and controls before editing.
|
||||||
|
- For editor screens, avoid autosave-risky content edits unless explicitly requested.
|
||||||
|
- If normal browser `fill` does not populate the WordPress login fields, use a narrow DOM fallback to set `#user_login` and `#user_pass`, dispatch `input` events, then submit. Do not echo credentials back to the user.
|
||||||
|
|
||||||
|
## Frontend checks
|
||||||
|
|
||||||
|
For frontend verification:
|
||||||
|
|
||||||
|
- Check desktop first unless the user asks for mobile/responsive.
|
||||||
|
- Capture a screenshot when visual appearance matters.
|
||||||
|
- Inspect console errors when behavior looks broken or JavaScript-heavy.
|
||||||
|
- Verify canonical symptoms with user-visible evidence, not assumptions from source code alone.
|
||||||
|
|
||||||
|
## Cookies and debugging hooks
|
||||||
|
|
||||||
|
- For Xdebug-specific browser requests, use `references/wp-xdebug.md`.
|
||||||
|
- If needed manually, set or include the `XDEBUG_SESSION=vscode` cookie only for local debugging flows and mention that it may route PHP through `wp-php-xdebug`.
|
||||||
62
references/wp-code-style.md
Normal file
62
references/wp-code-style.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# WP Code Style
|
||||||
|
|
||||||
|
Use this skill for PHP source formatting and coding-standard checks in Websimple WordPress projects.
|
||||||
|
|
||||||
|
## Shared tooling model
|
||||||
|
|
||||||
|
Websimple WordPress projects should rely on shared/global Websimple PHPCS tooling, not per-project Composer installs.
|
||||||
|
|
||||||
|
Project repos should keep only project-specific lint scope in `phpcs.xml`. The PHPCS binary, PHPCBF binary, and `WebsimpleWP` ruleset should come from global Composer tooling.
|
||||||
|
|
||||||
|
Expected global setup, only when explicitly asked to install or repair tooling:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer global config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
|
||||||
|
composer global require squizlabs/php_codesniffer lewebsimple/wp-phpcs-ruleset
|
||||||
|
```
|
||||||
|
|
||||||
|
If global Composer binaries are not on `PATH`, locate them with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer global config bin-dir --absolute --quiet
|
||||||
|
```
|
||||||
|
|
||||||
|
When cleaning an existing project, remove per-project Composer ownership of PHP_CodeSniffer and Websimple coding-standard tooling:
|
||||||
|
|
||||||
|
- remove `squizlabs/php_codesniffer` from `require-dev` when present;
|
||||||
|
- remove `lewebsimple/wp-phpcs-ruleset` from `require-dev` when present;
|
||||||
|
- remove `lint` / `lintfix` Composer scripts when they only wrap project-local PHPCS/PHPCBF, such as `vendor/bin/phpcs` or `vendor/bin/phpcbf`.
|
||||||
|
|
||||||
|
Preserve unrelated Composer dependencies and scripts. If a script does more than run PHPCS/PHPCBF, ask before removing or rewriting it.
|
||||||
|
|
||||||
|
## Required phpcs.xml
|
||||||
|
|
||||||
|
Make sure `phpcs.xml` exists at the project root and only includes `wp-content/mu-plugins/` plus the active/current theme directory:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<ruleset name="wp-code-style">
|
||||||
|
<rule ref="WebsimpleWP"/>
|
||||||
|
<file>wp-content/mu-plugins/</file>
|
||||||
|
<file>wp-content/themes/${theme}/</file>
|
||||||
|
</ruleset>
|
||||||
|
```
|
||||||
|
|
||||||
|
Resolve `${theme}` from the active child/current theme or from the user’s target. Do not lint all themes, WordPress core, vendor, uploads, cache, generated assets, or unrelated directories.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. Inspect `composer.json`, `composer.lock`, and any existing `phpcs.xml` / `phpcs.xml.dist`.
|
||||||
|
2. Resolve the target theme with WP-CLI or source inspection.
|
||||||
|
3. Remove project-local PHPCS/ruleset Composer dependencies and simple `lint` / `lintfix` scripts when present.
|
||||||
|
4. Ensure `phpcs.xml` is present and scoped correctly.
|
||||||
|
5. Run lint with global `phpcs` from the shell, not `vendor/bin/phpcs`.
|
||||||
|
6. Use global `phpcbf` only when the user requested formatting or the fix is clearly safe.
|
||||||
|
7. Re-run lint after fixes and inspect the diff before claiming success.
|
||||||
|
|
||||||
|
## Safety
|
||||||
|
|
||||||
|
- Do not run PHPCBF across broad paths unless the `phpcs.xml` scope is correct.
|
||||||
|
- Do not modify vendor, WordPress core, uploads, caches, or compiled assets.
|
||||||
|
- If PHPCBF changes files, summarize changed files and remaining PHPCS issues.
|
||||||
|
- Do not remove Composer scripts that contain extra behavior beyond straightforward PHPCS/PHPCBF wrappers without asking first.
|
||||||
134
references/wp-composer.md
Normal file
134
references/wp-composer.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# WP Composer
|
||||||
|
|
||||||
|
Use this skill for Composer-based dependency management in Websimple WordPress projects.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Websimple manages WordPress plugins and themes with PHP Composer.
|
||||||
|
|
||||||
|
Private Websimple Composer packages are served by Satis at:
|
||||||
|
|
||||||
|
- `https://satis.ledevsimple.ca`
|
||||||
|
|
||||||
|
For WordPress project conventions such as slug, local path, local URL, database name, and repository URL, use `references/wp-project.md`. For host tooling assumptions, including the `composer` executable being available in `$PATH`, use `references/websimple-stack.md`.
|
||||||
|
|
||||||
|
## Operating guidance
|
||||||
|
|
||||||
|
- Work from the local project root containing `composer.json`.
|
||||||
|
- Prefer read-only inspection commands before making dependency changes.
|
||||||
|
- Inspect `composer.json` and `composer.lock` directly when answering dependency questions.
|
||||||
|
- Use Composer commands through the host `composer` executable unless a task explicitly requires container context.
|
||||||
|
- Treat dependency changes as project code changes: inspect the resulting `composer.json` and `composer.lock`, then run a small verification command.
|
||||||
|
- Ensure `wp-config.php` loads Composer vendor libraries when a project uses Composer.
|
||||||
|
- Composer-managed WordPress plugins/themes should be ignored by Git. Some projects also have project-specific plugins/themes committed with the WordPress project; do not delete or overwrite source-controlled plugins/themes when repairing Composer-managed dependencies.
|
||||||
|
|
||||||
|
## Common read-only checks
|
||||||
|
|
||||||
|
Use these before changing dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer validate --no-check-all
|
||||||
|
composer show
|
||||||
|
composer show vendor/package
|
||||||
|
composer outdated --direct
|
||||||
|
composer why vendor/package
|
||||||
|
composer why-not vendor/package:^1.2
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `composer validate --no-check-all` by default because existing Websimple WordPress projects may intentionally use `"*"` version constraints for WordPress plugins/themes, and plain `composer validate` complains about unbound constraints.
|
||||||
|
|
||||||
|
## Package repositories
|
||||||
|
|
||||||
|
Composer projects should have Composer repository entries for both Satis (private repository) and WPackagist (official WordPress package repository).
|
||||||
|
|
||||||
|
The `repositories` section of `composer.json` should include:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "composer",
|
||||||
|
"url": "https://satis.ledevsimple.ca"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "composer",
|
||||||
|
"url": "https://wpackagist.org"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not assume every project already has these repositories configured. Check `composer.json` first.
|
||||||
|
|
||||||
|
Repository priority order matters: check Websimple Satis first, then WPackagist. Private Websimple packages should win over public package sources when names overlap.
|
||||||
|
|
||||||
|
## Dependency changes
|
||||||
|
|
||||||
|
When asked to add, update, or remove a WordPress plugin/theme package:
|
||||||
|
|
||||||
|
1. Confirm the target project path using `references/wp-project.md` conventions.
|
||||||
|
2. Inspect `composer.json` and `composer.lock`.
|
||||||
|
3. Verify package resolution priority before adding or updating packages: Websimple Satis first, then WPackagist.
|
||||||
|
4. Run the smallest Composer command that performs the requested change.
|
||||||
|
5. Inspect the diff for `composer.json` and `composer.lock`.
|
||||||
|
6. Run `composer validate --no-check-all` after the change.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require vendor/package:^1.2
|
||||||
|
composer update vendor/package --with-dependencies
|
||||||
|
composer remove vendor/package
|
||||||
|
composer validate --no-check-all
|
||||||
|
```
|
||||||
|
|
||||||
|
Ask before running broad updates such as `composer update` without package names.
|
||||||
|
|
||||||
|
## WordPress Composer autoload
|
||||||
|
|
||||||
|
When a WordPress project uses Composer, ensure `wp-config.php` contains this Composer autoloader guard so WordPress can load Composer-managed vendor libraries:
|
||||||
|
|
||||||
|
```php
|
||||||
|
if ( file_exists( __DIR__ . '/vendor/autoload.php' ) ) {
|
||||||
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Add it only if missing, and avoid duplicating equivalent autoload logic.
|
||||||
|
|
||||||
|
Place the guard near the top of `wp-config.php`, after the opening `<?php` and any file header/comments, before WordPress settings/constants and before this line:
|
||||||
|
|
||||||
|
```php
|
||||||
|
require_once ABSPATH . 'wp-settings.php';
|
||||||
|
```
|
||||||
|
|
||||||
|
This keeps Composer classes available to project configuration and WordPress bootstrap code.
|
||||||
|
|
||||||
|
## Plugins/themes integrity check
|
||||||
|
|
||||||
|
Composer-based WordPress projects can drift when a plugin/theme installed under `wp-content/plugins` or `wp-content/themes` no longer matches the version/source recorded in `composer.lock` (for example after a WordPress dashboard update, manual file edits, interrupted install, or broken vendor state).
|
||||||
|
|
||||||
|
When diagnosing plugin/theme drift:
|
||||||
|
|
||||||
|
1. Inspect `composer.lock` to identify packages installed into `wp-content/plugins/*` and `wp-content/themes/*`.
|
||||||
|
2. Check Git status before touching files so source-controlled project-specific plugins/themes are preserved.
|
||||||
|
3. Treat Git-ignored Composer-managed plugin/theme directories as disposable install artifacts.
|
||||||
|
4. Prefer read-only checks first, such as `composer install --dry-run` when useful, `composer show --locked`, and direct inspection of installed package metadata when available.
|
||||||
|
5. If integrity is uncertain, prefer a clean reinstall of Composer-managed plugins/themes from repositories rather than trying to patch files in place.
|
||||||
|
|
||||||
|
Safe repair pattern:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer install --no-interaction --prefer-dist
|
||||||
|
```
|
||||||
|
|
||||||
|
If a fresh install is required, remove only Composer-managed, Git-ignored plugin/theme directories and reinstall from `composer.lock`. Do not remove project-specific plugin/theme directories that are tracked by Git.
|
||||||
|
|
||||||
|
Before deleting any plugin/theme directory, verify both:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git check-ignore -q wp-content/plugins/example-plugin
|
||||||
|
git ls-files --error-unmatch wp-content/plugins/example-plugin >/dev/null 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
Only a path that is ignored and not tracked should be considered disposable. Ask before deleting directories, even when they appear disposable.
|
||||||
249
references/wp-kaliroots.md
Normal file
249
references/wp-kaliroots.md
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
# WP Kaliroots
|
||||||
|
|
||||||
|
Use this skill when a Websimple WordPress site uses the `kaliroots` parent theme or when behavior may come from Kaliroots parent-theme code.
|
||||||
|
|
||||||
|
Kaliroots source of truth: `https://gitea.websimple.com/wp-themes/kaliroots`.
|
||||||
|
|
||||||
|
Use `references/wp-theme-dev.md` for general theme PHP conventions that do not conflict with Kaliroots. Use `references/wp-browser.md` for frontend checks and `references/wp-xdebug.md` when parent/child template resolution or hooks are unclear.
|
||||||
|
|
||||||
|
## Core rule
|
||||||
|
|
||||||
|
For Kaliroots child themes, do not apply generic WordPress template organization blindly. Kaliroots has its own wrapper and template lookup system.
|
||||||
|
|
||||||
|
Prefer child-theme overrides and additions. Treat parent theme changes as higher-risk because they can affect many sites.
|
||||||
|
|
||||||
|
## Confirm Kaliroots usage
|
||||||
|
|
||||||
|
Before applying Kaliroots conventions, confirm the active theme relationship:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wp theme list --status=active
|
||||||
|
wp theme get $(wp option get stylesheet) --field=template
|
||||||
|
```
|
||||||
|
|
||||||
|
Or inspect the child theme `style.css` for a parent declaration like:
|
||||||
|
|
||||||
|
```css
|
||||||
|
Template: kaliroots
|
||||||
|
```
|
||||||
|
|
||||||
|
Inspect the child theme first, then inspect the parent theme only when inherited behavior or helper functions matter.
|
||||||
|
|
||||||
|
## Parent theme structure
|
||||||
|
|
||||||
|
Kaliroots parent uses a compact structure:
|
||||||
|
|
||||||
|
```text
|
||||||
|
functions.php
|
||||||
|
includes/
|
||||||
|
core/
|
||||||
|
purgecss.php
|
||||||
|
theme-setup.php
|
||||||
|
theme-wrapper.php
|
||||||
|
helpers/
|
||||||
|
assets.php
|
||||||
|
attachment.php
|
||||||
|
datetime.php
|
||||||
|
html.php
|
||||||
|
input.php
|
||||||
|
mail.php
|
||||||
|
media.php
|
||||||
|
menu.php
|
||||||
|
meta.php
|
||||||
|
mutex.php
|
||||||
|
query.php
|
||||||
|
shortcodes.php
|
||||||
|
social.php
|
||||||
|
taxonomy.php
|
||||||
|
template.php
|
||||||
|
user.php
|
||||||
|
utilities.php
|
||||||
|
wpfs.php
|
||||||
|
vendors/
|
||||||
|
acf.php
|
||||||
|
gutenberg.php
|
||||||
|
wpbakery.php
|
||||||
|
templates/
|
||||||
|
html/
|
||||||
|
head.php
|
||||||
|
mail.php
|
||||||
|
wrap.php
|
||||||
|
content/
|
||||||
|
main.php
|
||||||
|
```
|
||||||
|
|
||||||
|
The parent `functions.php` is a loader for `includes/core`, `includes/helpers`, and `includes/vendors`.
|
||||||
|
|
||||||
|
Child themes may add their own `includes/`, `templates/`, and asset sources. Preserve existing child-theme conventions unless the user asks for cleanup.
|
||||||
|
|
||||||
|
## Template wrapper model
|
||||||
|
|
||||||
|
Kaliroots wraps base WordPress templates through the `template_include` filter.
|
||||||
|
|
||||||
|
The parent wrapper flow is:
|
||||||
|
|
||||||
|
1. `template_include` calls `kaliroots_wrap_base_template()`.
|
||||||
|
2. The wrapper locates `templates/html/wrap-{base}.php` through `kaliroots_locate_template()`.
|
||||||
|
3. `{base}` is expanded by `kaliroots_base_placeholders()` using WordPress conditional context.
|
||||||
|
4. `locate_template()` gives child themes priority over parent templates.
|
||||||
|
|
||||||
|
Parent default wrapper:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html <?php language_attributes(); ?>>
|
||||||
|
<?= kaliroots_html_template( 'head' ); ?>
|
||||||
|
<body class="<?= join( ' ', get_body_class() ) ?>">
|
||||||
|
<div id="app">
|
||||||
|
<main>
|
||||||
|
<?= kaliroots_generic_template( '{base}' ) ?>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
<?php wp_footer(); ?>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
When changing page layout in a Kaliroots child theme, prefer overriding or adding wrapper/content templates in the child theme instead of replacing standard WordPress top-level templates without checking how Kaliroots resolves them.
|
||||||
|
|
||||||
|
## Template helper functions
|
||||||
|
|
||||||
|
Kaliroots provides template helper functions in `includes/helpers/template.php`.
|
||||||
|
|
||||||
|
Common helpers:
|
||||||
|
|
||||||
|
```php
|
||||||
|
kaliroots_generic_template( $template_path, $vars = [], $args = [] );
|
||||||
|
kaliroots_html_template( $template_name, $vars = [], $args = [] );
|
||||||
|
kaliroots_site_template( $template_name, $vars = [], $args = [] );
|
||||||
|
kaliroots_content_template( $template_name, $vars = [], $args = [] );
|
||||||
|
kaliroots_loop_template( $template_name, $vars = [], $args = [] );
|
||||||
|
kaliroots_section_template( $template_name, $vars = [], $args = [] );
|
||||||
|
kaliroots_shortcode_template( $template_name, $vars = [], $args = [] );
|
||||||
|
kaliroots_widget_template( $template_name, $vars = [], $args = [] );
|
||||||
|
```
|
||||||
|
|
||||||
|
These helpers locate templates through the Kaliroots lookup pipeline, inject `$vars` using `set_query_var()`, buffer template output, and can optionally cache rendered output.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?= kaliroots_content_template( 'hero', [ 'post_id' => get_the_ID() ] ); ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
Inside the located template, read injected values with `get_query_var()` when needed:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php $post_id = get_query_var( 'post_id' ); ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Template naming and lookup
|
||||||
|
|
||||||
|
Kaliroots lookup supports placeholders:
|
||||||
|
|
||||||
|
- `{base}`: expanded using the current WordPress conditional/template context.
|
||||||
|
- `{post_type}`: expanded using `get_post_type()` when available.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```php
|
||||||
|
kaliroots_html_template( 'head' );
|
||||||
|
// looks for templates/html/head-{base}.php, then falls back through base placeholder variants.
|
||||||
|
|
||||||
|
kaliroots_content_template( 'main' );
|
||||||
|
// looks for templates/content/main-{base}.php variants.
|
||||||
|
|
||||||
|
kaliroots_loop_template( 'default' );
|
||||||
|
// looks for templates/content/loops/default-{post_type}.php, then fallback variants.
|
||||||
|
```
|
||||||
|
|
||||||
|
When adding child-theme templates, prefer Kaliroots-compatible paths and names:
|
||||||
|
|
||||||
|
```text
|
||||||
|
templates/html/wrap-front-page.php
|
||||||
|
templates/html/wrap.php
|
||||||
|
templates/content/main-front-page.php
|
||||||
|
templates/content/main-single-project.php
|
||||||
|
templates/content/loops/default-project.php
|
||||||
|
```
|
||||||
|
|
||||||
|
Inspect the actual child theme before adding a new path; some projects may already have established naming.
|
||||||
|
|
||||||
|
## Extending template resolution
|
||||||
|
|
||||||
|
Kaliroots exposes filters for template lookup.
|
||||||
|
|
||||||
|
Use these only when adding a reusable lookup convention is clearer than adding explicit templates:
|
||||||
|
|
||||||
|
```php
|
||||||
|
add_filter( 'kaliroots_locate_templates', 'example_kaliroots_locate_templates' );
|
||||||
|
function example_kaliroots_locate_templates( array $templates ): array {
|
||||||
|
$templates[] = 'templates/content/custom-fallback';
|
||||||
|
|
||||||
|
return $templates;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```php
|
||||||
|
add_filter( 'kaliroots_base_placeholders', 'example_kaliroots_base_placeholders' );
|
||||||
|
function example_kaliroots_base_placeholders( array $templates ): array {
|
||||||
|
if ( is_post_type_archive( 'project' ) ) {
|
||||||
|
array_unshift( $templates, 'archive-project-featured' );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $templates;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep these filters rare and well-named; they affect template resolution globally.
|
||||||
|
|
||||||
|
## Helpers and inherited behavior
|
||||||
|
|
||||||
|
Before reimplementing helper logic in a child theme, inspect Kaliroots helpers under `includes/helpers/`.
|
||||||
|
|
||||||
|
Useful parent helpers include:
|
||||||
|
|
||||||
|
- asset registration with filemtime cache busting;
|
||||||
|
- template loading and pagination;
|
||||||
|
- menu/media/meta/query helpers;
|
||||||
|
- HTML cleaning helpers;
|
||||||
|
- shortcode and utility helpers.
|
||||||
|
|
||||||
|
Do not assume helper behavior from memory. Inspect the parent helper before using or overriding it.
|
||||||
|
|
||||||
|
## Parent vs child changes
|
||||||
|
|
||||||
|
Default to child-theme changes:
|
||||||
|
|
||||||
|
- child template override;
|
||||||
|
- child `includes/` function/hook;
|
||||||
|
- child asset/source change;
|
||||||
|
- child-specific filter/action.
|
||||||
|
|
||||||
|
Only edit Kaliroots parent when the user explicitly asks to change shared parent behavior or the bug truly belongs in the parent.
|
||||||
|
|
||||||
|
Before parent edits, report blast radius:
|
||||||
|
|
||||||
|
- which projects/sites may inherit the change;
|
||||||
|
- whether a child override is safer;
|
||||||
|
- how behavior will be verified.
|
||||||
|
|
||||||
|
## Refactoring workflow
|
||||||
|
|
||||||
|
1. Confirm active child theme and Kaliroots parent.
|
||||||
|
2. Inspect child theme overrides first.
|
||||||
|
3. Inspect relevant Kaliroots parent files: usually `includes/core/theme-wrapper.php` and `includes/helpers/template.php`.
|
||||||
|
4. Choose child override vs parent change deliberately.
|
||||||
|
5. Keep changes small and reversible.
|
||||||
|
6. Run PHP lint/style checks via `references/wp-code-style.md` when PHP files changed.
|
||||||
|
7. Use `references/wp-browser.md` for frontend smoke checks.
|
||||||
|
8. Use `references/wp-xdebug.md` if template resolution/hook flow is unclear.
|
||||||
|
9. Inspect the diff before claiming success.
|
||||||
|
|
||||||
|
## Safety
|
||||||
|
|
||||||
|
- Do not apply generic `references/wp-theme-dev.md` template structure when Kaliroots lookup rules apply.
|
||||||
|
- Do not edit parent Kaliroots casually; prefer child overrides.
|
||||||
|
- Do not mass-reorganize child templates without approval.
|
||||||
|
- Do not touch core, vendor, uploads, caches, or compiled assets.
|
||||||
|
- Preserve existing project conventions when they are coherent.
|
||||||
214
references/wp-local-site.md
Normal file
214
references/wp-local-site.md
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
# WP Local Site
|
||||||
|
|
||||||
|
Use this skill for local WordPress site lifecycle operations. For project naming and derived values, use `references/wp-project.md`. For stack/host environment assumptions, use `references/websimple-stack.md`. For Composer-managed plugins/themes, use `references/wp-composer.md`.
|
||||||
|
|
||||||
|
## Derived values
|
||||||
|
|
||||||
|
For project slug `${slug}`, use `references/wp-project.md` conventions:
|
||||||
|
|
||||||
|
- Local project path: `${WP_LOCAL_ROOT_PATH}/${slug}`
|
||||||
|
- Local database name: `wp_${slug}`
|
||||||
|
- Local site URL: `${WEBSIMPLE_STACK_PROTOCOL}://${slug}.${WEBSIMPLE_STACK_DOMAIN}`
|
||||||
|
- Default local DB table prefix: `wp_`, unless the production/source instance uses a different prefix
|
||||||
|
- Repository URL: `https://gitea.websimple.com/wp-sites/${slug}`
|
||||||
|
|
||||||
|
Resolve project values with `references/wp-project.md`; do not hard-code local paths, protocol, or domain.
|
||||||
|
|
||||||
|
## Safety
|
||||||
|
|
||||||
|
- Prefer read-only checks before creating, deleting, or resetting anything.
|
||||||
|
- Ask before destructive actions such as dropping a database, deleting a project directory, or overwriting local files.
|
||||||
|
- Use recoverable deletion where practical. If shelling out, prefer moving to the user's trash over permanent removal.
|
||||||
|
- Never delete source-controlled project-specific plugins/themes as part of Composer repair; use the `references/wp-composer.md` integrity guidance.
|
||||||
|
- Treat remote hosts as read-only unless a future approved workflow says otherwise.
|
||||||
|
|
||||||
|
## Create local site skeleton
|
||||||
|
|
||||||
|
A local site creation workflow should generally:
|
||||||
|
|
||||||
|
1. Resolve `${slug}` and derived values with `references/wp-project.md`.
|
||||||
|
2. Verify `${WP_LOCAL_ROOT_PATH}` exists.
|
||||||
|
3. Verify `${WP_LOCAL_ROOT_PATH}/${slug}` does not already exist, or ask how to proceed.
|
||||||
|
4. Clone or create the project directory from `https://gitea.websimple.com/wp-sites/${slug}`.
|
||||||
|
5. Create the local database `wp_${slug}` using host `mysql` credentials from `~/.my.cnf`.
|
||||||
|
6. Determine the DB table prefix: use `wp_` by default, or the production/source prefix when provisioning from an existing site.
|
||||||
|
7. Run `wp core config` with only the database name and table prefix when WP-CLI defaults provide DB host/user/password.
|
||||||
|
8. Run Composer install if the project uses Composer.
|
||||||
|
9. Either run `wp core install` for a fresh local site or synchronize/import from an existing remote site.
|
||||||
|
10. Verify local URL and WP-CLI access.
|
||||||
|
|
||||||
|
Example database creation command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mysql -e 'CREATE DATABASE `wp_example` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;'
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the real derived DB name; quote identifiers safely.
|
||||||
|
|
||||||
|
## WP core config
|
||||||
|
|
||||||
|
The host should have WP-CLI defaults in `~/.wp-cli/config.yml` for DB host, user, and password. Local provisioning should normally only specify the DB name:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wp core config --dbname="wp_${slug}" --dbprefix="wp_"
|
||||||
|
```
|
||||||
|
|
||||||
|
Run from the local project path. Do not duplicate DB host/user/password flags unless defaults are missing or the user explicitly asks.
|
||||||
|
|
||||||
|
Use `wp_` as the default table prefix for new local sites. When provisioning from production/staging, detect and preserve the source table prefix if it differs from `wp_`; the imported database and local `wp-config.php` prefix must agree.
|
||||||
|
|
||||||
|
## WP core install
|
||||||
|
|
||||||
|
Use `wp core install` only when creating a fresh local site without importing an existing database. Do not run it before a remote DB import because the import will replace the local tables anyway.
|
||||||
|
|
||||||
|
Example for a new local site:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wp core install \
|
||||||
|
--url="${WEBSIMPLE_STACK_PROTOCOL}://${slug}.${WEBSIMPLE_STACK_DOMAIN}" \
|
||||||
|
--title="${slug}" \
|
||||||
|
--skip-email
|
||||||
|
```
|
||||||
|
|
||||||
|
`~/.wp-cli/config.yml` contains default admin user, email, and password values, so do not pass `--admin_user`, `--admin_email`, or `--admin_password` unless the user asks for custom values. Avoid inventing real production credentials.
|
||||||
|
|
||||||
|
## Standard admin user
|
||||||
|
|
||||||
|
Local sites should have the standard admin user from `~/.wp-cli/config.yml`. After installing or importing a site, check whether that user exists. If missing, create it using the configured default admin values.
|
||||||
|
|
||||||
|
Do not hard-code admin credentials in commands. Do not echo the password in chat or log summaries. Refer to credentials by variable name only.
|
||||||
|
|
||||||
|
The exact YAML shape varies by user setup. Inspect the file before constructing any command, so the keys you read match the keys that actually exist:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Inspect — show keys only, not the password value
|
||||||
|
grep -E '^[[:space:]]*(user_login|user_email|user_pass|role)' ~/.wp-cli/config.yml \
|
||||||
|
| sed 's/\(user_pass:\).*/\1 <redacted>/'
|
||||||
|
```
|
||||||
|
|
||||||
|
Two common shapes:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Shape A — defaults nested under `user create:`
|
||||||
|
user create:
|
||||||
|
user_login: someuser
|
||||||
|
user_email: someone@example.com
|
||||||
|
user_pass: <set>
|
||||||
|
role: administrator
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Shape B — flat top-level keys consumed by custom commands
|
||||||
|
admin_user: someuser
|
||||||
|
admin_email: someone@example.com
|
||||||
|
admin_password: <set>
|
||||||
|
```
|
||||||
|
|
||||||
|
If the file uses Shape A, WP-CLI will already apply the defaults when `wp user create` runs without explicit flags. If it uses Shape B, the values have to be passed explicitly.
|
||||||
|
|
||||||
|
When `yq` is available, prefer it over hand-parsing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ADMIN_USER="$(yq '.["user create"].user_login // .admin_user' ~/.wp-cli/config.yml)"
|
||||||
|
ADMIN_EMAIL="$(yq '.["user create"].user_email // .admin_email' ~/.wp-cli/config.yml)"
|
||||||
|
# Keep ADMIN_PASSWORD only in the subshell that needs it; do not export or print it.
|
||||||
|
```
|
||||||
|
|
||||||
|
If `yq` is not available and the YAML doesn't match a known shape, ask the user for the admin login and email rather than guessing — and have them provide the password via stdin (`read -s`) instead of an argument that would land in shell history.
|
||||||
|
|
||||||
|
Typical flow:
|
||||||
|
|
||||||
|
1. Inspect `~/.wp-cli/config.yml` and identify the admin user/email key names actually used.
|
||||||
|
2. Check whether that user exists locally.
|
||||||
|
3. Create the user only if missing.
|
||||||
|
4. Grant the administrator role if needed.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Step 2–4 — assuming ADMIN_USER, ADMIN_EMAIL, ADMIN_PASSWORD are set in the current shell
|
||||||
|
if ! wp user get "$ADMIN_USER" >/dev/null 2>&1; then
|
||||||
|
wp user create "$ADMIN_USER" "$ADMIN_EMAIL" \
|
||||||
|
--role=administrator \
|
||||||
|
--user_pass="$ADMIN_PASSWORD"
|
||||||
|
fi
|
||||||
|
wp user set-role "$ADMIN_USER" administrator
|
||||||
|
```
|
||||||
|
|
||||||
|
If `wp user create` works without the explicit `--user_pass` flag (Shape A in use), prefer that form so the password never appears on the command line.
|
||||||
|
|
||||||
|
## Synchronize from remote site
|
||||||
|
|
||||||
|
Synchronizing from a remote site means pulling production/staging state into the local site. Keep remote actions read-only: export/read/copy from remote is okay; writing to or mutating remote is not.
|
||||||
|
|
||||||
|
Typical flow:
|
||||||
|
|
||||||
|
1. Resolve local metadata with `references/wp-project.md` and remote metadata with `references/websimple-gitea.md` Actions variables, especially `WP_SITE_URL`.
|
||||||
|
2. Confirm source remote URL/host and destination local path/database.
|
||||||
|
3. Create/reset the local database only after confirmation.
|
||||||
|
4. Detect the remote/source DB table prefix before export/import. Use `wp_` only if the source also uses `wp_` or the source prefix is unknown and no imported DB is involved.
|
||||||
|
5. Export the remote database in a read-only way, preferably streaming to local instead of leaving files on the remote host.
|
||||||
|
6. Import into local DB.
|
||||||
|
7. Ensure local `wp-config.php` uses the same table prefix as the imported DB.
|
||||||
|
8. Search-replace remote URLs to the derived local URL.
|
||||||
|
9. Pull uploads/media from remote unless specified otherwise.
|
||||||
|
10. Verify `siteurl` and `home` locally.
|
||||||
|
|
||||||
|
Preferred DB sync path: stream a compressed remote DB dump over SSH into the local MySQL client. Use this when both remote WP-CLI and local `mysql` are available:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh "$REMOTE_SSH" 'cd "$REMOTE_PATH" && wp db export - --skip-plugins --skip-themes --single-transaction --quick --lock-tables=false | gzip -c' \
|
||||||
|
| gunzip \
|
||||||
|
| mysql "$LOCAL_DB_NAME"
|
||||||
|
```
|
||||||
|
|
||||||
|
After DB import, run URL replacement locally:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wp search-replace "$REMOTE_SITE_URL" "${WEBSIMPLE_STACK_PROTOCOL}://${slug}.${WEBSIMPLE_STACK_DOMAIN}" --all-tables --skip-columns=guid
|
||||||
|
wp option get siteurl
|
||||||
|
wp option get home
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not run `wp search-replace` on the remote site during local synchronization.
|
||||||
|
|
||||||
|
Preferred uploads sync path: use `rsync` over SSH and include `--delete` so local `wp-content/uploads/` matches remote exactly, unless the user asks to preserve extra local files. Websimple SSH usually requires an explicit port, so include the SSH transport option when needed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rsync -az --delete -e "ssh -p $REMOTE_SSH_PORT" "$REMOTE_SSH:$REMOTE_PATH/wp-content/uploads/" "wp-content/uploads/"
|
||||||
|
```
|
||||||
|
|
||||||
|
If the SSH host alias already encodes the port in `~/.ssh/config`, the `-e "ssh -p ..."` option may be omitted. If remote uploads path differs, inspect the project before syncing. Be explicit when `--delete` will remove local-only upload files.
|
||||||
|
|
||||||
|
## Provision from existing project
|
||||||
|
|
||||||
|
When provisioning from an existing local or remote project:
|
||||||
|
|
||||||
|
1. Resolve local and remote metadata using `references/wp-project.md` and `references/websimple-gitea.md`.
|
||||||
|
2. Confirm the source and destination clearly.
|
||||||
|
3. Keep remote actions read-only.
|
||||||
|
4. Import database/content only after confirming the target local DB and path.
|
||||||
|
5. After import, replace remote URLs with the derived local site URL.
|
||||||
|
6. Run small verification checks, such as `wp option get siteurl` and `wp option get home`.
|
||||||
|
|
||||||
|
## Delete/reset local site
|
||||||
|
|
||||||
|
A local delete/reset workflow may include:
|
||||||
|
|
||||||
|
- local project directory `${WP_LOCAL_ROOT_PATH}/${slug}`
|
||||||
|
- local database `wp_${slug}`
|
||||||
|
- generated/cache artifacts
|
||||||
|
|
||||||
|
Before deleting:
|
||||||
|
|
||||||
|
1. Show the resolved path, DB name, and local URL.
|
||||||
|
2. Check whether the path exists and whether it has uncommitted Git changes.
|
||||||
|
3. Ask for confirmation.
|
||||||
|
4. Prefer backup/trash for files where practical.
|
||||||
|
5. Drop the local DB only after explicit confirmation.
|
||||||
|
|
||||||
|
Example DB drop command after confirmation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mysql -e 'DROP DATABASE `wp_example`;'
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the real derived DB name; quote identifiers safely.
|
||||||
199
references/wp-lovable.md
Normal file
199
references/wp-lovable.md
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
# WP Lovable
|
||||||
|
|
||||||
|
Use this skill when turning a Lovable export into a Websimple WordPress project, especially when the output should become a normal WordPress marketing site rather than a detached static React app.
|
||||||
|
|
||||||
|
Use related references as needed:
|
||||||
|
|
||||||
|
- `references/wp-project.md` and `references/wp-local-site.md` for resolving, creating, provisioning, or checking local WordPress sites.
|
||||||
|
- `references/websimple-stack.md` for local stack protocol/domain configuration and Traefik SSL routing.
|
||||||
|
- `references/wp-kaliroots.md` for Kaliroots child-theme structure and template wrapper decisions.
|
||||||
|
- `references/wp-theme-dev.md` for general theme PHP organization.
|
||||||
|
- `references/wp-assets.md` for frontend source, Vite/Tailwind, build output, and enqueue integration.
|
||||||
|
- `references/wp-acf.md` for editable content models, local JSON, options pages, and section data.
|
||||||
|
- `references/wp-browser.md` for frontend/wp-admin smoke tests and visual checks.
|
||||||
|
|
||||||
|
## Core Approach
|
||||||
|
|
||||||
|
Keep WordPress as the application shell and source of truth. Treat the Lovable project as frontend source material to port into the active theme, not as a static app to drop into WordPress unchanged.
|
||||||
|
|
||||||
|
For new Websimple WordPress projects, start from the `wp-boilerplate` template unless the user or project context says otherwise:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://gitea.websimple.com/templates/wp-boilerplate
|
||||||
|
```
|
||||||
|
|
||||||
|
Prefer this shape for a Websimple WordPress conversion:
|
||||||
|
|
||||||
|
```text
|
||||||
|
wp-content/themes/{child-theme}/
|
||||||
|
acf-json/
|
||||||
|
includes/
|
||||||
|
core/
|
||||||
|
assets.php
|
||||||
|
theme-setup.php
|
||||||
|
vendors/
|
||||||
|
acf.php
|
||||||
|
gravityforms.php
|
||||||
|
templates/
|
||||||
|
src/
|
||||||
|
components/
|
||||||
|
hooks/
|
||||||
|
lib/
|
||||||
|
pages/
|
||||||
|
main.tsx
|
||||||
|
index.css
|
||||||
|
dist/
|
||||||
|
package.json
|
||||||
|
vite.config.ts
|
||||||
|
tailwind.config.*
|
||||||
|
postcss.config.*
|
||||||
|
```
|
||||||
|
|
||||||
|
Adapt the paths to the existing theme. Do not reorganize established projects broadly without approval.
|
||||||
|
|
||||||
|
## First Pass
|
||||||
|
|
||||||
|
Before editing, inspect both sides:
|
||||||
|
|
||||||
|
1. Identify the Lovable source project, its framework, routes, components, CSS/Tailwind setup, static assets, forms, and data assumptions.
|
||||||
|
2. Identify the WordPress site, active theme, parent theme, template wrappers, ACF setup, menus, SEO plugin, forms plugin, and asset pipeline.
|
||||||
|
3. Check git status in the WordPress project and avoid touching unrelated dirty files.
|
||||||
|
4. Decide which Lovable pieces are presentation components and which are actually content, navigation, SEO, media, forms, or settings that belong in WordPress.
|
||||||
|
|
||||||
|
Use `rg --files`, `package.json`, `vite.config.*`, `tailwind.config.*`, `src/`, `includes/`, `templates/`, and `acf-json/` as the main inspection points.
|
||||||
|
|
||||||
|
## Porting Rules
|
||||||
|
|
||||||
|
Port reusable React/Tailwind UI into the child theme `src/`. Keep TypeScript path aliases, component names, and Tailwind conventions only when they still make sense inside the theme.
|
||||||
|
|
||||||
|
Move editable site concerns into WordPress:
|
||||||
|
|
||||||
|
- page and section content into pages, ACF fields, flexible content, or options pages;
|
||||||
|
- navigation into WordPress menus;
|
||||||
|
- images and downloadable media into the media library or ACF media fields;
|
||||||
|
- SEO title, canonical, OpenGraph/Twitter, schema, and indexing behavior into the SEO plugin and WordPress-rendered document head;
|
||||||
|
- contact forms into Gravity Forms or the existing WordPress form plugin;
|
||||||
|
- global settings such as phone, address, social links, CTAs, and layout toggles into ACF options when they are truly site-wide.
|
||||||
|
|
||||||
|
React should render serialized WordPress data with safe fallbacks. It should not become the CMS.
|
||||||
|
|
||||||
|
## Routing And SEO
|
||||||
|
|
||||||
|
Avoid pure React Router SPA behavior for normal WordPress marketing pages.
|
||||||
|
|
||||||
|
Prefer WordPress-rendered URLs and document navigation so SEO plugins can output correct per-page titles, canonical URLs, OpenGraph/Twitter tags, JSON-LD, breadcrumbs, and indexing controls.
|
||||||
|
|
||||||
|
Use React routing only when the requested result is an actual app-like interface where client-side routing is intentional. For standard pages, replace internal SPA links with normal document links and let WordPress resolve the route.
|
||||||
|
|
||||||
|
## Bundler Pattern
|
||||||
|
|
||||||
|
For modern Lovable migrations, Vite in the child theme is usually the cleanest fit:
|
||||||
|
|
||||||
|
- keep the app entry at `src/main.tsx` or the existing theme entrypoint;
|
||||||
|
- keep Tailwind/PostCSS config in the theme root;
|
||||||
|
- use an alias such as `@` to point at `src` only if the source already uses it or it improves the port;
|
||||||
|
- output production assets to `dist/`;
|
||||||
|
- generate `dist/.vite/manifest.json`;
|
||||||
|
- set production `base` to `/wp-content/themes/{child-theme}/dist/` when assets are served from the theme;
|
||||||
|
- have PHP enqueue code read the manifest rather than hardcoding hashed filenames;
|
||||||
|
- mark Vite client and app scripts as `type="module"` when needed.
|
||||||
|
|
||||||
|
For local development, bind Vite to the local WordPress host on a fixed port and expose CORS headers so the WordPress page can load the dev server assets.
|
||||||
|
|
||||||
|
Mirror Mooncake's local SSL pattern when the Websimple stack uses HTTPS:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import basicSsl from "@vitejs/plugin-basic-ssl";
|
||||||
|
import { homedir } from "os";
|
||||||
|
import { resolve } from "path";
|
||||||
|
|
||||||
|
const isHttps = process.env.LOCAL_PROTOCOL === "https";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
...(isHttps ? [basicSsl({ certDir: resolve(homedir(), ".local/share/certs") })] : []),
|
||||||
|
],
|
||||||
|
server: {
|
||||||
|
host: `${resolve(__dirname, "../../..").split("/").pop()}.${process.env.TLD || "ledevsimple.ca"}`,
|
||||||
|
port: 3001,
|
||||||
|
strictPort: true,
|
||||||
|
headers: { "Access-Control-Allow-Origin": "*" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Resolve `LOCAL_PROTOCOL` and `TLD` from the local stack configuration whenever possible:
|
||||||
|
|
||||||
|
- `LOCAL_PROTOCOL` should match `$WEBSIMPLE_STACK_PROTOCOL`, usually `https` when Traefik serves local sites with TLS.
|
||||||
|
- `TLD` should match `$WEBSIMPLE_STACK_DOMAIN`, for example `ledevsimple.ca`.
|
||||||
|
- Prefer package scripts, shell environment, or generated local env files that pass those values through to Vite.
|
||||||
|
- If the stack protocol cannot be discovered, inspect `references/websimple-stack.md` config and the current local site URL before choosing `http` or `https`.
|
||||||
|
|
||||||
|
Do not let leftover `dist/.vite/manifest.json` silently force production mode if the local workflow expects the dev server. Prefer an explicit local/dev switch, an environment constant, or a detection strategy that matches the project's generated-output policy.
|
||||||
|
|
||||||
|
## PHP Integration
|
||||||
|
|
||||||
|
Keep PHP integration small and explicit:
|
||||||
|
|
||||||
|
- enqueue assets from `includes/core/assets.php` or the existing asset module;
|
||||||
|
- localize or inline only the WordPress data React needs to render;
|
||||||
|
- keep data preparation in helpers/includes when it becomes non-trivial;
|
||||||
|
- keep template files responsible for mounting the frontend and providing page context;
|
||||||
|
- keep ACF and vendor integrations in the existing vendor/module files.
|
||||||
|
|
||||||
|
For Kaliroots child themes, use Kaliroots wrapper and template conventions instead of inventing top-level WordPress templates.
|
||||||
|
|
||||||
|
## Forms
|
||||||
|
|
||||||
|
Do not rebuild normal WordPress forms in React just because the Lovable export included form markup.
|
||||||
|
|
||||||
|
Prefer a WordPress-native form flow:
|
||||||
|
|
||||||
|
1. Add an ACF field for a form selector or section configuration.
|
||||||
|
2. Render the selected Gravity Form or existing form plugin output in PHP or through a focused React wrapper fed by WordPress data.
|
||||||
|
3. Style the generated form in theme source CSS.
|
||||||
|
4. Verify submit behavior, validation, notifications, spam protection, and accessibility in the browser.
|
||||||
|
|
||||||
|
Only build a custom React form when the project specifically needs custom app behavior and the backend handling is clear.
|
||||||
|
|
||||||
|
## What To Avoid
|
||||||
|
|
||||||
|
- Do not drop the Lovable export into WordPress as a static page dump.
|
||||||
|
- Do not make WordPress serve a one-route SPA for normal marketing content.
|
||||||
|
- Do not hard-code editor-controlled content, layout settings, form IDs, media, menus, or SEO metadata in React.
|
||||||
|
- Do not edit compiled `dist/` assets manually when source exists.
|
||||||
|
- Do not migrate build tools, package managers, or theme architecture as a side effect unless the user approved that scope.
|
||||||
|
- Do not assume Lovable's placeholder copy, mock data, or generated routes are production information.
|
||||||
|
- Do not use manifest existence alone as the only dev/prod signal if generated output may be checked in or left over locally.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Use the smallest checks that cover the migration risk:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm build
|
||||||
|
find wp-content/themes/{child-theme} -name '*.php' -print0 | xargs -0 -n1 php -l
|
||||||
|
```
|
||||||
|
|
||||||
|
Then smoke-test representative URLs in the browser:
|
||||||
|
|
||||||
|
- home page;
|
||||||
|
- at least one inner page;
|
||||||
|
- contact/form page;
|
||||||
|
- mobile viewport;
|
||||||
|
- browser console;
|
||||||
|
- SEO/head output for title, canonical, OpenGraph/Twitter, JSON-LD, and no accidental SPA-only metadata.
|
||||||
|
|
||||||
|
Report changed source files, generated files, existing dirty files left untouched, and any checks that could not run.
|
||||||
|
|
||||||
|
## Reference Pattern
|
||||||
|
|
||||||
|
The first known conversion following this pattern was:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Lovable source: https://github.com/Moonthewhite/chemineeschaleurs
|
||||||
|
WordPress site: https://gitea.websimple.com/wp-sites/chemineeschaleurs
|
||||||
|
Child theme: wp-content/themes/cheminees
|
||||||
|
```
|
||||||
|
|
||||||
|
That project used a Kaliroots child theme, React/Tailwind source under `src/`, Vite output under `dist/`, ACF local JSON under `acf-json/`, PHP enqueueing under `includes/core/assets.php`, WordPress/ACF-owned section layout settings, and Gravity Forms for the contact form.
|
||||||
56
references/wp-project.md
Normal file
56
references/wp-project.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# WP Project
|
||||||
|
|
||||||
|
Use this reference for WordPress-specific Websimple project conventions. For host environment, Docker, Traefik, protocol, or domain assumptions, see `websimple-stack.md`.
|
||||||
|
|
||||||
|
## Config values used here
|
||||||
|
|
||||||
|
These environment variables drive WordPress project value derivation:
|
||||||
|
|
||||||
|
- `WP_LOCAL_ROOT_PATH`: shared local WordPress root path served by `wp-apache2`.
|
||||||
|
- `WEBSIMPLE_STACK_PROTOCOL`: stack-level protocol used to build local WordPress URLs.
|
||||||
|
- `WEBSIMPLE_STACK_DOMAIN`: stack-level domain suffix used to build local WordPress URLs.
|
||||||
|
|
||||||
|
Read these from the environment. Do not hard-code them in commands or examples; if any are missing when needed, ask the user to export them before proceeding.
|
||||||
|
|
||||||
|
## Slug
|
||||||
|
|
||||||
|
A Websimple WordPress project is identified by a unique slug derived from its production domain.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
- `www.example.com` → `example`
|
||||||
|
|
||||||
|
## Local WordPress site
|
||||||
|
|
||||||
|
For a WordPress project `${slug}`:
|
||||||
|
|
||||||
|
- Local project path: `${WP_LOCAL_ROOT_PATH}/${slug}`. In the current websimple-stack this normally maps to `${DOCKER_DATA}/wp/sites/${slug}` on the host and `/var/www/html/${slug}` in containers.
|
||||||
|
- Local database name: `wp_${slug}`
|
||||||
|
- Local site URL: `${WEBSIMPLE_STACK_PROTOCOL}://${slug}.${WEBSIMPLE_STACK_DOMAIN}`
|
||||||
|
- Git repository URL: `https://gitea.websimple.com/wp-sites/${slug}`
|
||||||
|
|
||||||
|
## Remote WordPress site
|
||||||
|
|
||||||
|
Use the corresponding Gitea repository Actions variables as the source of truth for the remote production environment:
|
||||||
|
|
||||||
|
- `WP_SITE_URL`: production site URL source of truth, including protocol.
|
||||||
|
- `REMOTE_HOST`
|
||||||
|
- `REMOTE_PORT`
|
||||||
|
- `REMOTE_USER`
|
||||||
|
- `REMOTE_PATH`
|
||||||
|
|
||||||
|
`wp-sites` repositories may rely on organization/repository fallback variables or secrets for `REMOTE_*` deployment values. If a per-repository `REMOTE_HOST`, `REMOTE_PORT`, `REMOTE_USER`, or `REMOTE_PATH` variable is missing, check the relevant fallback variable/secret source before treating the project metadata as incomplete or attempting to infer SSH values from the public site URL.
|
||||||
|
|
||||||
|
Prefer `WP_SITE_URL` for the production site URL. Reading it from repository variables is cheaper and avoids unnecessary remote SSH work. `WP_SITE_URL` must include the protocol, for example `https://www.example.com`.
|
||||||
|
|
||||||
|
Normally, the local user has SSH access to `${REMOTE_USER}@${REMOTE_HOST}` on `${REMOTE_PORT}` after repository values and fallback variables/secrets are resolved.
|
||||||
|
|
||||||
|
Only perform read-only actions on the remote host. Do not modify files, run updates, change options, write database data, deploy code, or execute destructive commands unless a future workflow explicitly adds a reviewed approval path.
|
||||||
|
|
||||||
|
If `WP_SITE_URL` is missing, determine the production site URL from the remote WordPress environment by running WP-CLI read-only from `${REMOTE_PATH}`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" 'cd "${REMOTE_PATH}" && wp option get siteurl'
|
||||||
|
```
|
||||||
|
|
||||||
|
Treat that `siteurl` value as the fallback production site URL. It should include the protocol.
|
||||||
228
references/wp-theme-dev.md
Normal file
228
references/wp-theme-dev.md
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
# WP Theme Dev
|
||||||
|
|
||||||
|
Use this skill for general Websimple WordPress theme PHP development conventions.
|
||||||
|
|
||||||
|
Do not use this skill for PHPCS tooling setup or formatting commands; use `references/wp-code-style.md`. Use `references/wp-browser.md` for visual/frontend checks and `references/wp-xdebug.md` for runtime PHP debugging.
|
||||||
|
|
||||||
|
## Scope boundaries
|
||||||
|
|
||||||
|
This skill covers:
|
||||||
|
|
||||||
|
- theme PHP file/folder organization;
|
||||||
|
- `functions.php` organization;
|
||||||
|
- actions and filters;
|
||||||
|
- function naming and namespacing/prefixing;
|
||||||
|
- template conventions for regular WordPress themes;
|
||||||
|
- comments/docblocks style;
|
||||||
|
- escaping, sanitization, translation, and preferred output syntax;
|
||||||
|
- safe PHP/theme refactoring workflow.
|
||||||
|
|
||||||
|
Defer specialized areas to dedicated skills when they apply:
|
||||||
|
|
||||||
|
- Kaliroots child themes: use/refine the `references/wp-kaliroots.md` conventions instead of this skill's template guidance.
|
||||||
|
- PHPCS/PHPCBF/tooling: use `references/wp-code-style.md`.
|
||||||
|
- ACF architecture/conventions: use `references/wp-acf.md`.
|
||||||
|
- Asset pipeline details, bundler config, and build commands: use `references/wp-assets.md`.
|
||||||
|
- Vue/React/headless conventions beyond source placement: use dedicated skills when available.
|
||||||
|
|
||||||
|
## Theme structure
|
||||||
|
|
||||||
|
Prefer small, discoverable theme files over large catch-all files.
|
||||||
|
|
||||||
|
- Keep all PHP logic under `includes/`, grouped by category.
|
||||||
|
- Keep `functions.php` as a categorized bootstrap/loader for `includes/` files.
|
||||||
|
- Use category directories such as `includes/admin/`, `includes/core/`, `includes/cpt/`, `includes/taxonomies/`, `includes/forms/`, `includes/roles/`, `includes/schemas/`, `includes/sections/`, `includes/shortcodes/`, `includes/vendors/`, `includes/widgets/`, and `includes/woocommerce/` as applicable.
|
||||||
|
- Keep helpers/utilities separate from hook callback registration when practical.
|
||||||
|
- Use `src/` for CSS/SCSS, JavaScript, TypeScript, Vue, React, and similar source assets that are compiled or bundled.
|
||||||
|
- Preserve the existing project structure unless the user asks for reorganization.
|
||||||
|
- Do not introduce a new architecture into an established theme without approval.
|
||||||
|
|
||||||
|
Preferred `functions.php` pattern: keep it as a categorized loader for files under `includes/`.
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Administration
|
||||||
|
require_once __DIR__ . '/includes/admin/attachment.php';
|
||||||
|
require_once __DIR__ . '/includes/admin/user.php';
|
||||||
|
|
||||||
|
// Core
|
||||||
|
require_once __DIR__ . '/includes/core/assets.php';
|
||||||
|
require_once __DIR__ . '/includes/core/helpers.php';
|
||||||
|
require_once __DIR__ . '/includes/core/theme-setup.php';
|
||||||
|
|
||||||
|
// Custom Post Types
|
||||||
|
require_once __DIR__ . '/includes/cpt/project.php';
|
||||||
|
|
||||||
|
// Custom Taxonomies
|
||||||
|
require_once __DIR__ . '/includes/taxonomies/project-category.php';
|
||||||
|
|
||||||
|
// Roles
|
||||||
|
require_once __DIR__ . '/includes/roles/editor.php';
|
||||||
|
|
||||||
|
// Shortcodes
|
||||||
|
require_once __DIR__ . '/includes/shortcodes/alert.php';
|
||||||
|
|
||||||
|
// Vendors
|
||||||
|
require_once __DIR__ . '/includes/vendors/acf.php';
|
||||||
|
require_once __DIR__ . '/includes/vendors/gravityforms.php';
|
||||||
|
require_once __DIR__ . '/includes/vendors/polylang.php';
|
||||||
|
require_once __DIR__ . '/includes/vendors/tinymce.php';
|
||||||
|
|
||||||
|
// Widgets
|
||||||
|
require_once __DIR__ . '/includes/widgets/contact-info.php';
|
||||||
|
|
||||||
|
// WooCommerce
|
||||||
|
require_once __DIR__ . '/includes/woocommerce/cart.php';
|
||||||
|
require_once __DIR__ . '/includes/woocommerce/checkout.php';
|
||||||
|
require_once __DIR__ . '/includes/woocommerce/product.php';
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep empty category headers when they make the intended structure clearer or match the project convention.
|
||||||
|
|
||||||
|
Avoid modifying WordPress core, vendor directories, uploads, caches, compiled assets, or unrelated themes.
|
||||||
|
|
||||||
|
## Templates and template parts
|
||||||
|
|
||||||
|
For themes that are child themes of Kaliroots, do not apply generic template conventions. Refer to `references/wp-kaliroots.md` instead.
|
||||||
|
|
||||||
|
For regular WordPress themes, templates should live under `templates/` and be grouped by rendering purpose:
|
||||||
|
|
||||||
|
```text
|
||||||
|
templates/
|
||||||
|
html/ # outer high-level templates
|
||||||
|
site/ # global site header/footer and site-wide layout parts
|
||||||
|
content/ # content-related templates
|
||||||
|
loops/ # loop wrappers and loop item templates
|
||||||
|
emails/ # outgoing email templates
|
||||||
|
```
|
||||||
|
|
||||||
|
Each template group may have a `partials/` subdirectory for smaller local partials.
|
||||||
|
|
||||||
|
Use `templates/content/loops/` for loop templates. Prefer paired names where the outer loop and item template share a base name:
|
||||||
|
|
||||||
|
```text
|
||||||
|
templates/content/loops/events.php
|
||||||
|
templates/content/loops/events-item.php
|
||||||
|
templates/content/loops/posts.php
|
||||||
|
templates/content/loops/posts-item.php
|
||||||
|
```
|
||||||
|
|
||||||
|
Additional conventions:
|
||||||
|
|
||||||
|
- Use WordPress template hierarchy intentionally.
|
||||||
|
- Keep template files readable and focused on rendering.
|
||||||
|
- Use template parts for repeated markup or meaningful sections.
|
||||||
|
- Prefer clear names that describe the rendered section or component.
|
||||||
|
- Keep heavy data preparation, custom queries, and complex conditionals out of templates when a helper or setup function would make the template clearer.
|
||||||
|
- Keep markup/output close to templates and reusable data/logic close to `includes/` helpers/modules.
|
||||||
|
|
||||||
|
Do not mass-reorganize template files without explicit approval.
|
||||||
|
|
||||||
|
## Actions and filters
|
||||||
|
|
||||||
|
Register hooks in predictable locations, following the theme's existing organization.
|
||||||
|
|
||||||
|
- Prefer named callbacks over anonymous closures when the callback is reused, non-trivial, or likely to need debugging.
|
||||||
|
- Use clear project/theme-prefixed or namespaced callback names.
|
||||||
|
- Keep callback names tied to the hook purpose.
|
||||||
|
- Avoid hidden side effects in filters unless intentional and documented by context.
|
||||||
|
- Preserve important priorities and accepted-argument counts.
|
||||||
|
- Document or call out non-obvious hook ordering dependencies.
|
||||||
|
|
||||||
|
Example pattern:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Enqueue theme assets.
|
||||||
|
add_action( 'wp_enqueue_scripts', 'example_enqueue_assets' );
|
||||||
|
function example_enqueue_assets(): void {
|
||||||
|
wp_enqueue_style(
|
||||||
|
'example-theme',
|
||||||
|
get_stylesheet_directory_uri() . '/dist/css/theme.css',
|
||||||
|
[],
|
||||||
|
wp_get_theme()->get( 'Version' )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a body class on the front page.
|
||||||
|
add_filter( 'body_class', 'example_add_body_classes' );
|
||||||
|
function example_add_body_classes( array $classes ): array {
|
||||||
|
if ( is_front_page() ) {
|
||||||
|
$classes[] = 'is-front-page';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $classes;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When refactoring hooks, verify behavior with the smallest meaningful check: WP-CLI, browser smoke test, or Xdebug when needed.
|
||||||
|
|
||||||
|
## Function naming
|
||||||
|
|
||||||
|
Follow the existing theme convention first. When adding new global functions, avoid generic names.
|
||||||
|
|
||||||
|
Prefer one of:
|
||||||
|
|
||||||
|
- a theme/project prefix, e.g. `example_get_hero_title()`;
|
||||||
|
- a namespace if the theme already uses namespaces;
|
||||||
|
- a class/static method only when the theme already uses that style or it genuinely improves organization.
|
||||||
|
|
||||||
|
Keep helper names descriptive. Separate hook callbacks from pure/data helpers when it improves readability.
|
||||||
|
|
||||||
|
## Comments and docblocks
|
||||||
|
|
||||||
|
Keep the project's existing commenting style coherent.
|
||||||
|
|
||||||
|
Comments may be explicit and simple, including human-readable descriptions of what a function does. Do not remove comments just because they restate the function name if that style is consistent in the project.
|
||||||
|
|
||||||
|
Use comments/docblocks to improve scanability and clarify intent, inputs, outputs, side effects, or hook context when useful. Avoid stale, misleading, or decorative comments.
|
||||||
|
|
||||||
|
## Escaping, sanitization, translation, and output syntax
|
||||||
|
|
||||||
|
Escape dynamic values at output time unless WordPress or the data source has already produced safe markup for that exact context.
|
||||||
|
|
||||||
|
Common choices:
|
||||||
|
|
||||||
|
- `esc_html()` for plain text;
|
||||||
|
- `esc_attr()` for attribute values;
|
||||||
|
- `esc_url()` for URLs;
|
||||||
|
- `wp_kses_post()` for trusted post-like HTML;
|
||||||
|
- `wp_json_encode()` for JSON output.
|
||||||
|
|
||||||
|
Sanitize input before saving or using it in queries. Use WordPress translation helpers for user-facing theme strings when appropriate.
|
||||||
|
|
||||||
|
Prefer compact PHP output tags for template output:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?= esc_html( $title ); ?>
|
||||||
|
<?= esc_url( $url ); ?>
|
||||||
|
<?= wp_kses_post( $content ); ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not rewrite existing `<?php echo ...; ?>` output solely for style unless the user asked for cleanup or the surrounding file is already being touched.
|
||||||
|
|
||||||
|
## Data and query conventions
|
||||||
|
|
||||||
|
- Use core WordPress APIs where possible.
|
||||||
|
- Reset post data after custom `WP_Query` loops.
|
||||||
|
- Avoid relying on surprising global state.
|
||||||
|
- Avoid expensive queries inside repeated template parts.
|
||||||
|
- Add caching only when justified by a concrete performance issue.
|
||||||
|
|
||||||
|
## Refactoring workflow
|
||||||
|
|
||||||
|
1. Inspect the theme structure, active child/parent theme relationship, and existing conventions.
|
||||||
|
2. If the theme is a Kaliroots child theme, defer template decisions to Kaliroots-specific conventions.
|
||||||
|
3. Make small coherent changes that preserve behavior unless the user requested redesign.
|
||||||
|
4. Avoid mixing formatting-only and behavior changes.
|
||||||
|
5. Run lint/style checks via `references/wp-code-style.md` when PHP files changed.
|
||||||
|
6. Use `references/wp-browser.md` for frontend smoke checks when templates/output changed.
|
||||||
|
7. Use `references/wp-xdebug.md` when runtime behavior is unclear.
|
||||||
|
8. Inspect the diff before claiming success.
|
||||||
|
|
||||||
|
## Safety
|
||||||
|
|
||||||
|
- Do not mass-reorganize a theme without explicit approval.
|
||||||
|
- Do not touch core, vendor, uploads, caches, compiled assets, or unrelated themes.
|
||||||
|
- Do not silently force conventions when the existing project clearly uses a different coherent pattern; call out the tradeoff.
|
||||||
|
- Keep changes reversible and scoped to the user's request.
|
||||||
140
references/wp-xdebug.md
Normal file
140
references/wp-xdebug.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
# WP Xdebug
|
||||||
|
|
||||||
|
Use this skill when the agent should debug local Websimple WordPress PHP execution autonomously. Prefer `references/wp-project.md` first when you need to resolve the site slug, local URL, or project path. Use `references/wp-browser.md` only when browser interaction is needed to understand or reproduce the behavior.
|
||||||
|
|
||||||
|
The expected path is autonomous debugging with `koriym/xdebug-mcp`; do not require the user to have VS Code open or listening for Xdebug connections.
|
||||||
|
|
||||||
|
## Scope and safety
|
||||||
|
|
||||||
|
- Use Xdebug only for local Websimple development sites unless the user explicitly confirms otherwise.
|
||||||
|
- Do not install tools, enable/reconfigure Xdebug, restart services, or modify project files unless requested.
|
||||||
|
- Avoid sending public/production form submissions or external-effect requests while debugging.
|
||||||
|
- Prefer deterministic local reproductions: PHP script, WP-CLI command, PHPUnit command, safe curl/API request, or a minimal reproducer.
|
||||||
|
- If autonomous runtime debugging cannot be performed because `xdebug-mcp` or Xdebug is missing, report that as a blocker and suggest the smallest next setup step.
|
||||||
|
|
||||||
|
## Required autonomous debugger
|
||||||
|
|
||||||
|
Use `koriym/xdebug-mcp` to provide Xdebug-backed tools with structured JSON output for AI analysis:
|
||||||
|
|
||||||
|
- `xtrace`: trace execution flow.
|
||||||
|
- `xstep`: stop at breakpoints and inspect variables.
|
||||||
|
- `xprofile`: collect performance profiling data.
|
||||||
|
- `xcoverage`: collect coverage data.
|
||||||
|
- `xback`: capture call stack/backtrace at a breakpoint.
|
||||||
|
- `xcompare`: compare variable states across runs; CLI-only.
|
||||||
|
|
||||||
|
Check availability before relying on it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
command -v xtrace xstep xprofile xcoverage xback
|
||||||
|
```
|
||||||
|
|
||||||
|
If Composer global binaries are not on `PATH`, locate them with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer global config bin-dir --absolute --quiet
|
||||||
|
```
|
||||||
|
|
||||||
|
Installation, only when explicitly requested:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer global require koriym/xdebug-mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
## Preconditions
|
||||||
|
|
||||||
|
Before triggering a debug run, confirm or infer:
|
||||||
|
|
||||||
|
- The target is local code/site, normally `${WEBSIMPLE_STACK_PROTOCOL}://${slug}.${WEBSIMPLE_STACK_DOMAIN}` and `${WP_LOCAL_ROOT_PATH}/${slug}`.
|
||||||
|
- Xdebug 3.x is installed for the PHP runtime used by the debug command.
|
||||||
|
- `xdebug-mcp` CLI tools are available, or MCP exposes the equivalent tools.
|
||||||
|
- The reproduction command actually executes the PHP code being investigated.
|
||||||
|
|
||||||
|
For Websimple WordPress, remember that the served document root inside the PHP container is normally `/var/www/html`; map that mentally to the local WordPress project root when interpreting file paths.
|
||||||
|
|
||||||
|
## Autonomous workflow
|
||||||
|
|
||||||
|
1. Resolve the project slug, local URL, and local path with `references/wp-project.md` conventions.
|
||||||
|
2. Identify the code path and the safest reproduction strategy:
|
||||||
|
- WP-CLI command when WordPress bootstrap/state is needed.
|
||||||
|
- PHP script or small throwaway reproducer for isolated functions/classes.
|
||||||
|
- PHPUnit or project test command when a test exists.
|
||||||
|
- curl/API request only when it safely and actually triggers the PHP code under debug.
|
||||||
|
- Browser only to discover state, cookies, nonces, or the user flow; then reduce to a deterministic command when possible.
|
||||||
|
3. Pick the xdebug-mcp tool:
|
||||||
|
- Need execution path? Use `xtrace`.
|
||||||
|
- Need variable state at a line? Use `xstep --break='file.php:line'`.
|
||||||
|
- Need caller chain? Use `xback --break='file.php:line'`.
|
||||||
|
- Need slowness data? Use `xprofile --json`.
|
||||||
|
- Need coverage? Use `xcoverage`.
|
||||||
|
4. Run the smallest safe command that captures runtime data.
|
||||||
|
5. Analyze the JSON output and inspect source files as needed.
|
||||||
|
6. Iterate with narrower breakpoints/traces until the cause is clear.
|
||||||
|
7. Report evidence: command shape, breakpoint/trace target, observed runtime data, conclusion, and any blockers.
|
||||||
|
|
||||||
|
## Example commands
|
||||||
|
|
||||||
|
Script or isolated command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xtrace -- php path/to/script.php
|
||||||
|
xstep --break='path/to/file.php:42' -- php path/to/script.php
|
||||||
|
xback --break='path/to/file.php:42' -- php path/to/script.php
|
||||||
|
xprofile --json -- php path/to/script.php
|
||||||
|
```
|
||||||
|
|
||||||
|
WP-CLI reproduction from the local WordPress root. When using the `/usr/local/bin/wp` PHAR, invoke it explicitly through PHP so `xdebug-mcp` can determine the target script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd "${WP_LOCAL_ROOT_PATH}/${slug}"
|
||||||
|
xtrace -- php /usr/local/bin/wp --path="${WP_LOCAL_ROOT_PATH}/${slug}" eval '/* minimal safe reproduction */'
|
||||||
|
xstep --break='wp-content/themes/theme/file.php:42' -- php /usr/local/bin/wp --path="${WP_LOCAL_ROOT_PATH}/${slug}" eval '/* minimal safe reproduction */'
|
||||||
|
xback --break='wp-content/themes/theme/file.php:42' -- php /usr/local/bin/wp --path="${WP_LOCAL_ROOT_PATH}/${slug}" eval '/* minimal safe reproduction */'
|
||||||
|
```
|
||||||
|
|
||||||
|
Test reproduction:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xtrace -- vendor/bin/phpunit --filter 'RelevantTest'
|
||||||
|
xcoverage -- vendor/bin/phpunit --filter 'RelevantTest'
|
||||||
|
```
|
||||||
|
|
||||||
|
Containerized reproduction, if the PHP code must run inside the Websimple stack, should use the actual service names after inspection. Standard PHP is `wp-php`; browser-triggered Xdebug requests are routed to `wp-php-xdebug` when the `XDEBUG_SESSION=vscode` cookie is present.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xtrace -- docker compose exec -T wp-php php /var/www/html/${slug}/path/to/script.php
|
||||||
|
xstep --break='/var/www/html/${slug}/wp-content/themes/theme/file.php:42' -- docker compose exec -T wp-php php /var/www/html/${slug}/path/to/script.php
|
||||||
|
```
|
||||||
|
|
||||||
|
Only use command forms that match the actual Websimple stack project layout after inspecting the project/stack.
|
||||||
|
|
||||||
|
## Browser and HTTP flows
|
||||||
|
|
||||||
|
For browser-only bugs:
|
||||||
|
|
||||||
|
- Use `references/wp-browser.md` to reproduce the UI and inspect the request details.
|
||||||
|
- Avoid relying on a human IDE listener.
|
||||||
|
- Prefer converting the observed request into a safe deterministic command: WP-CLI, script, test, or curl with required cookies/nonces.
|
||||||
|
- Be careful: wrapping `curl` with `xtrace` only debugs PHP if the Xdebug capture is attached to the PHP runtime handling the request. If the PHP executes in Apache/FPM separately, use a supported xdebug-mcp/container strategy or reduce the behavior to WP-CLI/script instead.
|
||||||
|
|
||||||
|
Do not use Xdebug triggers on production/remote URLs.
|
||||||
|
|
||||||
|
## Common WordPress targets
|
||||||
|
|
||||||
|
- Frontend template load: theme template, `template_redirect`, query hooks, block rendering.
|
||||||
|
- wp-admin page: menu page callbacks, list tables, metaboxes, save handlers.
|
||||||
|
- REST API: route callback and permission callback.
|
||||||
|
- admin-ajax: `wp_ajax_*` / `wp_ajax_nopriv_*` handlers.
|
||||||
|
- Forms: validation/submission hooks, but do not submit externally-effectful forms unless explicitly requested.
|
||||||
|
- Cron-like handlers: only trigger explicitly requested local code paths; do not run destructive jobs by accident.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If no runtime data appears:
|
||||||
|
|
||||||
|
- Confirm `xtrace`/`xstep`/`xback` is installed and on `PATH`.
|
||||||
|
- Confirm the command after `--` actually runs PHP and reaches the code path.
|
||||||
|
- Confirm Xdebug 3.x is installed for that PHP binary/container.
|
||||||
|
- Confirm paths in breakpoints match the runtime paths shown in traces, often `/var/www/html/...` inside containers.
|
||||||
|
- Confirm the request/command is local, not production.
|
||||||
|
- If browser navigation is blocked by policy, use `references/wp-browser.md`’s browser SSRF prerequisite for `${WEBSIMPLE_STACK_DOMAIN}` local sites.
|
||||||
219
scripts/gitea.sh
Normal file
219
scripts/gitea.sh
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Websimple Gitea API wrapper — read-only operations.
|
||||||
|
#
|
||||||
|
# Replaces the OpenClaw plugin's three Gitea tools:
|
||||||
|
# - websimple_gitea_orgs_repos_list
|
||||||
|
# - websimple_gitea_repos_actions_variables_list
|
||||||
|
# - websimple_gitea_repos_actions_variables_get
|
||||||
|
#
|
||||||
|
# Requires:
|
||||||
|
# - jq
|
||||||
|
# - curl
|
||||||
|
# - WEBSIMPLE_GITEA_API_TOKEN env var (or --token flag)
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# gitea.sh repos-list [--org wp-sites]
|
||||||
|
# gitea.sh vars-list <owner> <repo>
|
||||||
|
# gitea.sh var-get <owner> <repo> <variable-name>
|
||||||
|
#
|
||||||
|
# Output: JSON to stdout. Errors to stderr. Exit non-zero on failure.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
GITEA_BASE_URL="https://gitea.websimple.com"
|
||||||
|
GITEA_API_BASE="${GITEA_BASE_URL}/api/v1"
|
||||||
|
PAGE_LIMIT=100
|
||||||
|
|
||||||
|
# ----- helpers -----
|
||||||
|
|
||||||
|
die() {
|
||||||
|
printf 'gitea.sh: %s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
require_token() {
|
||||||
|
if [[ -z "${WEBSIMPLE_GITEA_API_TOKEN:-}" ]]; then
|
||||||
|
die "WEBSIMPLE_GITEA_API_TOKEN is not set. Export it before calling this script."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
require_cmd() {
|
||||||
|
command -v "$1" >/dev/null 2>&1 || die "Required command not found: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Issue an authenticated GET. Echos response body, exits non-zero on HTTP error.
|
||||||
|
# Args: $1 = path beginning with /
|
||||||
|
gitea_get() {
|
||||||
|
local path="$1"
|
||||||
|
local url="${GITEA_API_BASE}${path}"
|
||||||
|
local response
|
||||||
|
local http_code
|
||||||
|
|
||||||
|
response="$(curl -sS \
|
||||||
|
--header "Authorization: token ${WEBSIMPLE_GITEA_API_TOKEN}" \
|
||||||
|
--header "Accept: application/json" \
|
||||||
|
--write-out "\n__HTTP_CODE__:%{http_code}" \
|
||||||
|
"${url}")"
|
||||||
|
|
||||||
|
http_code="${response##*__HTTP_CODE__:}"
|
||||||
|
response="${response%$'\n'__HTTP_CODE__:*}"
|
||||||
|
|
||||||
|
if [[ "${http_code}" -lt 200 || "${http_code}" -ge 300 ]]; then
|
||||||
|
die "Gitea API request failed: HTTP ${http_code} for ${url}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s' "${response}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fetch all pages for an endpoint that supports ?page=&limit=.
|
||||||
|
# Args: $1 = base path (without query string); $2 = extra query params (may be empty)
|
||||||
|
# Echoes a single JSON array combining all pages.
|
||||||
|
gitea_get_paginated() {
|
||||||
|
local base_path="$1"
|
||||||
|
local extra_query="${2:-}"
|
||||||
|
local page=1
|
||||||
|
local combined='[]'
|
||||||
|
local page_json
|
||||||
|
local page_count
|
||||||
|
|
||||||
|
while (( page <= 10000 )); do
|
||||||
|
local sep="?"
|
||||||
|
[[ -n "${extra_query}" ]] && sep="?${extra_query}&"
|
||||||
|
local path="${base_path}${sep}page=${page}&limit=${PAGE_LIMIT}"
|
||||||
|
|
||||||
|
# If gitea_get fails (HTTP error), the subshell exits non-zero and the
|
||||||
|
# caller's `|| exit 1` handles propagation.
|
||||||
|
page_json="$(gitea_get "${path}")" || return 1
|
||||||
|
|
||||||
|
# Defensive: empty/null responses become []
|
||||||
|
if [[ -z "${page_json}" || "${page_json}" == "null" ]]; then
|
||||||
|
page_json='[]'
|
||||||
|
fi
|
||||||
|
|
||||||
|
combined="$(jq -c --argjson new "${page_json}" '. + $new' <<<"${combined}")"
|
||||||
|
page_count="$(jq 'length' <<<"${page_json}")"
|
||||||
|
|
||||||
|
if (( page_count < PAGE_LIMIT )); then
|
||||||
|
printf '%s' "${combined}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
page=$(( page + 1 ))
|
||||||
|
done
|
||||||
|
|
||||||
|
die "Pagination safety limit exceeded for ${base_path}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----- subcommands -----
|
||||||
|
|
||||||
|
cmd_repos_list() {
|
||||||
|
local org="wp-sites"
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
--org) org="$2"; shift 2 ;;
|
||||||
|
--org=*) org="${1#--org=}"; shift ;;
|
||||||
|
*) die "Unknown argument to repos-list: $1" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
require_token
|
||||||
|
|
||||||
|
local repos
|
||||||
|
repos="$(gitea_get_paginated "/orgs/${org}/repos" "")" || exit 1
|
||||||
|
|
||||||
|
# Match the OpenClaw tool's output shape: filtered fields + metadata envelope.
|
||||||
|
jq -n \
|
||||||
|
--arg baseUrl "${GITEA_BASE_URL}" \
|
||||||
|
--arg org "${org}" \
|
||||||
|
--argjson pageSize "${PAGE_LIMIT}" \
|
||||||
|
--argjson repos "${repos}" \
|
||||||
|
'{
|
||||||
|
baseUrl: $baseUrl,
|
||||||
|
org: $org,
|
||||||
|
fetchedAll: true,
|
||||||
|
pageSize: $pageSize,
|
||||||
|
count: ($repos | length),
|
||||||
|
repos: ($repos
|
||||||
|
| sort_by((.full_name // .name // ""))
|
||||||
|
| map({
|
||||||
|
id, name, full_name, html_url, clone_url, ssh_url,
|
||||||
|
default_branch, private, archived, updated_at
|
||||||
|
}))
|
||||||
|
}'
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_vars_list() {
|
||||||
|
(( $# == 2 )) || die "Usage: gitea.sh vars-list <owner> <repo>"
|
||||||
|
local owner="$1" repo="$2"
|
||||||
|
require_token
|
||||||
|
|
||||||
|
local variables
|
||||||
|
variables="$(gitea_get_paginated "/repos/${owner}/${repo}/actions/variables" "")" || exit 1
|
||||||
|
|
||||||
|
jq -n \
|
||||||
|
--arg baseUrl "${GITEA_BASE_URL}" \
|
||||||
|
--arg owner "${owner}" \
|
||||||
|
--arg repo "${repo}" \
|
||||||
|
--argjson pageSize "${PAGE_LIMIT}" \
|
||||||
|
--argjson variables "${variables}" \
|
||||||
|
'{
|
||||||
|
baseUrl: $baseUrl,
|
||||||
|
owner: $owner,
|
||||||
|
repo: $repo,
|
||||||
|
fetchedAll: true,
|
||||||
|
pageSize: $pageSize,
|
||||||
|
count: ($variables | length),
|
||||||
|
variables: ($variables | sort_by(.name // ""))
|
||||||
|
}'
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_var_get() {
|
||||||
|
(( $# == 3 )) || die "Usage: gitea.sh var-get <owner> <repo> <variable-name>"
|
||||||
|
local owner="$1" repo="$2" name="$3"
|
||||||
|
require_token
|
||||||
|
|
||||||
|
local variable
|
||||||
|
variable="$(gitea_get "/repos/${owner}/${repo}/actions/variables/${name}")" || exit 1
|
||||||
|
|
||||||
|
jq -n \
|
||||||
|
--arg baseUrl "${GITEA_BASE_URL}" \
|
||||||
|
--arg owner "${owner}" \
|
||||||
|
--arg repo "${repo}" \
|
||||||
|
--argjson variable "${variable}" \
|
||||||
|
'{
|
||||||
|
baseUrl: $baseUrl,
|
||||||
|
owner: $owner,
|
||||||
|
repo: $repo,
|
||||||
|
variable: $variable
|
||||||
|
}'
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat >&2 <<'EOF'
|
||||||
|
Websimple Gitea API wrapper — read-only.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
gitea.sh repos-list [--org <org>] (default org: wp-sites)
|
||||||
|
gitea.sh vars-list <owner> <repo>
|
||||||
|
gitea.sh var-get <owner> <repo> <variable-name>
|
||||||
|
|
||||||
|
Requires WEBSIMPLE_GITEA_API_TOKEN in the environment.
|
||||||
|
EOF
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----- dispatch -----
|
||||||
|
|
||||||
|
require_cmd jq
|
||||||
|
require_cmd curl
|
||||||
|
|
||||||
|
[[ $# -ge 1 ]] || usage
|
||||||
|
|
||||||
|
cmd="$1"; shift
|
||||||
|
case "${cmd}" in
|
||||||
|
repos-list) cmd_repos_list "$@" ;;
|
||||||
|
vars-list) cmd_vars_list "$@" ;;
|
||||||
|
var-get) cmd_var_get "$@" ;;
|
||||||
|
-h|--help|help) usage ;;
|
||||||
|
*) die "Unknown subcommand: ${cmd}" ;;
|
||||||
|
esac
|
||||||
Reference in New Issue
Block a user