This is the canonical instruction file for all coding agents (GitHub Copilot,
Claude Code, Gemini Code Assist, Codex, Aider, and others) working on this
repository. Other agent files (CLAUDE.md, GEMINI.md,
.github/copilot-instructions.md) point here.
Documentate is a WordPress plugin (PHP 8.3, wp-env, Docker) that generates official resolutions and structured administrative documents. It uses:
- Custom post type
documentate_document - Custom taxonomy
documentate_doc_type(template definitions) - OpenTBS for ODT/DOCX template merging
- Collabora Online (server-side) / LibreOffice WASM in the browser
(
@matbee/libreoffice-converter) for optional format conversion - PHPUnit for unit tests, Playwright for E2E tests
- PHPCS with WordPress Coding Standards for PHP linting and formatting (canonical); Mago remains available only as optional secondary tooling
wp-env(Docker) for local WordPress and test environments
Read ARCHITECTURE.md before implementing new features or significant changes.
- Make small, focused diffs. Do not refactor unrelated code.
- Do not rename files, classes, hooks, or public APIs unless the task requires it.
- Preserve all existing features and UI unless explicitly asked to change them.
- Keep documentation and tests aligned with every code change.
- Prefer existing project patterns over introducing new abstractions.
- Follow existing naming, hook, and file-organisation conventions.
- Avoid dead code, speculative abstractions, and broad rewrites.
make up # Start wp-env Docker containers (http://localhost:8989)
make down # Stop containers
make clean # Reset WordPress environmentmake check # Runs: lint -> check-plugin -> test -> check-untranslated -> mo
# (verification only; does not modify source files)| Command | What it does |
|---|---|
make fix |
Auto-fix PHP with PHPCBF / WPCS |
make lint |
Lint PHP with PHPCS / WPCS — always required |
make mago-format |
Optional secondary Mago formatter (may be removed) |
make mago-lint |
Optional secondary Mago lint (may be removed) |
make check-plugin |
Run WordPress plugin-check — always required |
make test |
Run PHPUnit unit tests — always required |
make test-coverage |
PHPUnit with Xdebug coverage (needs --xdebug=coverage) |
make test-e2e |
Run Playwright E2E tests against wp-env |
make test-e2e-visual |
Playwright with interactive UI |
make check-untranslated |
Check all Spanish strings are translated |
Targeted test runs:
make test FILTER=MyTestClass # run tests matching a pattern
make test FILE=tests/unit/Foo.php # run a specific test file| Situation | Required checks |
|---|---|
| Any PHP change | make fix, make lint, make test |
| Any PHP change merged to main | also make check-plugin |
| New or changed user-facing strings | also make check-untranslated |
| UI, admin flows, editor flows, or browser behaviour | also make test-e2e |
| Full pre-merge verification | make check (covers all of the above) |
Before every git push / opening a PR |
at least make lint, make test, and make check-untranslated |
If Docker / wp-env is unavailable, still write code that is designed to pass all checks, and state clearly which checks could not be run locally.
Never push or open a PR without verifying translations. CI runs
make check-untranslated and fails the job if any Spanish msgstr is empty.
Before git push or gh pr create:
- Search the diff for new/changed
__()/_e()/_n()/_x()strings. - Update
languages/documentate-es_ES.poin the same commit (Spanishmsgstrfilled in — not left blank). - Run
make check-untranslatedand confirm it exits 0. - If it fails, fix the empty
msgstrentries (and re-run) before pushing.
Do not treat “tests passed” as enough for a push: PHPUnit does not catch missing
.po entries. Untranslated strings are a CI blocker, same as lint failures.
A task is not complete if any of the following remain:
- Lint errors reported by
make lint - Plugin-check errors reported by
make check-plugin - Untranslated string failures from
make check-untranslated - Failing PHPUnit tests (
make test) - Failing E2E tests relevant to the change (
make test-e2e) - Warnings or errors that would break CI (see
.github/workflows/ci.yml)
- Indentation: tab characters (tab-width = 4), as required by WordPress Coding Standards and
enforced by
.editorconfig. - Naming:
snake_casefor functions/variables,CamelCasefor classes,lowercase-with-hyphensfor file names (e.g.class-documentate-admin.php). - Every function and method must have an English PHPDoc block immediately above it.
- Keep the main plugin file
documentate.phpminimal. - Each class lives in its own file:
class-documentate-component.php. - Admin code ->
admin/, core logic ->includes/, tests ->tests/.
- Escape output:
esc_html(),esc_attr(),esc_url(),wp_kses_post(). - Sanitize input:
sanitize_text_field(),sanitize_textarea_field(),absint(),sanitize_key(). - Unslash superglobals before sanitising (e.g.
wp_unslash( $_POST )). - Use WordPress nonces for all forms and AJAX endpoints.
- Check capabilities with
current_user_can()before privileged operations. - Use
$wpdb->prepare()— never interpolate variables into SQL.
-
All user-facing text must be in Spanish, wrapped in i18n functions (
__(),_e(),_n(),_x()). -
Text domain:
documentate. Strings reused verbatim from WordPress core (e.g.__('Comments', 'default')) may use thedefaultdomain. -
Required (CI fails otherwise): any
__()/_e()/_n()/_x()call whose string contains placeholders (%s,%d,%1$s, …) must have a/* translators: */comment on the line directly above, naming each placeholder. Plugin-check / WPCS reports this asWordPress.WP.I18n.MissingTranslatorsCommentand treats it as an error./* translators: %1$s: old status, %2$s: new status. */ sprintf(__('Cambio de estado: %1$s → %2$s', 'documentate'), $old, $new);
-
Required: every time you add, change, or remove a translatable string, update
languages/documentate-es_ES.po(and any other.pofiles present) in the same commit. A change that touches__()/_e()/_n()/_x()and ships without a.poupdate is incomplete. -
Required before push/PR: run
make check-untranslatedand ensure it passes. Emptymsgstr ""entries after a newmsgidare CI failures — fill the Spanish translation, do not leave placeholders empty. -
Practical workflow when adding a string:
# After adding/changing __() strings in PHP: make check-untranslated # regenerates pot/po and lists untranslated # Edit languages/documentate-es_ES.po: fill msgstr for each new msgid make check-untranslated # must exit 0 before git push
-
Every function and method needs an English PHPDoc block.
-
Align
@param/@returntags so variable names line up, with at least one space after the longest type name. WPCS / plugin-check enforces this asWordPress.Commenting.FunctionComment.SpacingAfterParamType. PHPCBF may not fully fix alignment automatically, so verify by hand./** * @param string $title Document title. * @param int $count Number of revisions. * @param array $extra Optional metadata. * @return WP_Post|WP_Error */
- Keep methods small. PHPMD enforces an NPath complexity threshold of 500 and a cyclomatic complexity threshold of 10 in CI; do not commit code that exceeds them.
- When a method approaches either threshold, extract pure helpers (input parsing, authorization checks, response building) instead of disabling the rule or raising the threshold.
- A long sequence of
if/ternary guards multiplies NPath quickly — split them into focused private methods with descriptive names.
- Use Bootstrap 5 and jQuery for admin UI.
- Enqueue assets via
wp_enqueue_script()/wp_enqueue_style(). - Use minified assets in production.
- Write tests for new behaviour (TDD preferred).
- Tests live in
tests/unit/; use factory classes fromtests/includes/. - Run
make testto execute the PHPUnit suite inside wp-env.
A change is ready when all of the following are true:
make lintpasses with no errors.make check-pluginpasses with no errors.make testpasses with no failures.make check-untranslatedpasses with no empty Spanish translations (always before push/PR — not optional “if you remember strings”).make test-e2epasses for the affected flows (if UI/browser behaviour changed).- PHPDoc is updated for any modified functions or classes.
- No unrelated files, classes, or hooks were renamed or removed.
- No push/PR is opened while
make check-untranslatedis red.
Recurring procedures live as skills in .agents/skills/, the path GitHub
Copilot, Codex and other agents read directly. Claude Code reads
.claude/skills/, which contains symlinks to those same directories, not
copies. When adding a skill, create it in .agents/skills/ and link it from
.claude/skills/; never duplicate a SKILL.md.
| Skill | Read it before | Origin |
|---|---|---|
wp-plugin-development |
Touching hooks, activation/uninstall, the Settings API, options, cron or release packaging | WordPress/agent-skills, GPL-2.0-or-later |
wp-rest-api |
Adding or debugging routes: register_rest_route, permission_callback, schema/args, register_meta, show_in_rest |
idem |
wp-plugin-directory-guidelines |
Editing readme.txt, license headers or plugin naming — this is what make check-plugin enforces |
idem |
blueprint |
Editing blueprint.json or the Playground preview |
idem |
security-audit |
Hunting vulnerabilities and validating findings | cloudflare/security-audit-skill |
All of them are third party and vendored verbatim. Do not reformat or edit them: diverging from upstream makes future updates harder. Fix the problem upstream and re-vendor instead.
skills-lock.json records provenance for skills fetched with a skills
installer; security-audit is the only one so far. The WordPress/agent-skills
set was vendored by hand and is therefore not listed there.
Skills and the agent instruction files are excluded from the release ZIP via
.gitattributes.
includes/autofirma/ adapts the AutoFirma intermediate-server protocol. Two
invariants there look like bugs and are not:
/documentate/v1/autofirma/intermediate/<token>/{storage,retrieve}usespermission_callback => '__return_true'on purpose. AutoFirma is a desktop application; it does not carry the WordPress session cookie, so those routes cannot require a nonce or a capability. What authorises them is the 32-char opaque token, issued only by/autofirma/intermediate-sessions, which does checkedit_posts, and which expires with its transient. Do not "harden" the token routes withcurrent_user_can()or a nonce check — that breaks signing outright.- The protocol itself lives in
erseco/autofirma-intermediate-serverand is copied intoincludes/vendor/autofirma-intermediate-server/by a Composer script. The browser side is@erseco/autofirma-client, bundled bynpm run build:autofirma. Do not reimplement either one in this plugin; fix it upstream and bump the dependency.
Never introduce a fallback that returns the unsigned document when AutoFirma is missing or fails. A file that looks signed but is not is worse than an error. Certificate metadata arriving from JavaScript is untrusted input.
Read ARCHITECTURE.md for details on:
- Data flow and CPT/taxonomy structure
- OpenTBS document generation pipeline
- Conversion engines (Collabora, LibreOffice WASM in the browser)
- Access control and scope filtering
The canonical PHP linter/formatter is PHPCS with WordPress Coding Standards
(.phpcs.xml.dist), installed via Composer:
composer install # installs PHPCS, WPCS, PHPUnit, optional Mago, …
composer phpcs # same as: make lint
composer phpcbf # same as: make fixMago is optional secondary tooling only (not used by CI, make lint,
make fix, or make check). It may be removed later:
composer mago:lint # same as: make mago-lint
composer mago:format # same as: make mago-formatAlways inspect the Makefile to understand exactly what each make target runs.
- Load this file as the conventions file:
/read AGENTS.md. - Use
/askto plan, then/codeor/architectto apply. - Review every diff before accepting, especially in architect mode.