From b460c4e9086c094a80830e0bd658a37d188960bd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:25:52 +0000 Subject: [PATCH 1/5] Initial plan From ed0de950d0da1e5d9799de977a85f403bbff4936 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:42:44 +0000 Subject: [PATCH 2/5] Add complete WordPress block plugin starter repo scaffold Agent-Logs-Url: https://github.com/lightspeedwp/ls-starter-plugin/sessions/5b4a8a5c-337d-41eb-b6c4-d22d5fcc07b0 Co-authored-by: ashleyshaw <1805352+ashleyshaw@users.noreply.github.com> --- .agents/README.md | 39 ++ .agents/agents/README.md | 27 ++ .agents/agents/plugin-architect.md | 50 ++ .agents/skills/README.md | 46 ++ .agents/skills/block-plugin-audit/SKILL.md | 87 ++++ .coderabbit.yml | 69 +++ .editorconfig | 29 ++ .gitattributes | 37 ++ .github/README.md | 33 ++ .github/copilot-instructions.md | 86 ++++ .github/instructions/README.md | 26 ++ .github/instructions/assets.instructions.md | 50 ++ .github/instructions/blocks.instructions.md | 96 ++++ .github/instructions/php.instructions.md | 61 +++ .../plugin-structure.instructions.md | 56 +++ .../instructions/workflows.instructions.md | 54 +++ .github/prompts/README.md | 41 ++ .github/prompts/audit-plugin.prompt.md | 40 ++ .github/prompts/cleanup.prompt.md | 25 + .github/prompts/new-block.prompt.md | 37 ++ .github/prompts/release.prompt.md | 49 ++ .github/reports/.gitkeep | 0 .github/reports/README.md | 66 +++ .github/tasks/.gitkeep | 0 .github/tasks/README.md | 53 +++ .github/tasks/task-list.md | 24 + .github/workflows/ci.yml | 37 ++ .github/workflows/code-quality.yml | 32 ++ .github/workflows/release.yml | 46 ++ .gitignore | 99 ++-- .lintstagedrc.json | 7 + .nvmrc | 1 + AGENTS.md | 227 +++++++++ CHANGELOG.md | 40 ++ CLAUDE.md | 5 + CODEOWNERS | 4 + README.md | 192 +++++++- assets/css/.gitkeep | 0 assets/icons/.gitkeep | 0 assets/images/.gitkeep | 0 assets/js/.gitkeep | 0 blocks/.gitkeep | 0 composer.json | 31 ++ docs/README.md | 36 ++ inc/.gitkeep | 0 languages/.gitkeep | 0 package.json | 27 ++ patterns/.gitkeep | 0 plugin-utils.mjs | 433 ++++++++++++++++++ readme.txt | 26 ++ src/.gitkeep | 0 src/blocks/.gitkeep | 0 src/css/.gitkeep | 0 src/js/.gitkeep | 0 templates/.gitkeep | 0 uninstall.php | 30 ++ {{PLUGIN_SLUG}}.php | 49 ++ 57 files changed, 2456 insertions(+), 47 deletions(-) create mode 100644 .agents/README.md create mode 100644 .agents/agents/README.md create mode 100644 .agents/agents/plugin-architect.md create mode 100644 .agents/skills/README.md create mode 100644 .agents/skills/block-plugin-audit/SKILL.md create mode 100644 .coderabbit.yml create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/README.md create mode 100644 .github/copilot-instructions.md create mode 100644 .github/instructions/README.md create mode 100644 .github/instructions/assets.instructions.md create mode 100644 .github/instructions/blocks.instructions.md create mode 100644 .github/instructions/php.instructions.md create mode 100644 .github/instructions/plugin-structure.instructions.md create mode 100644 .github/instructions/workflows.instructions.md create mode 100644 .github/prompts/README.md create mode 100644 .github/prompts/audit-plugin.prompt.md create mode 100644 .github/prompts/cleanup.prompt.md create mode 100644 .github/prompts/new-block.prompt.md create mode 100644 .github/prompts/release.prompt.md create mode 100644 .github/reports/.gitkeep create mode 100644 .github/reports/README.md create mode 100644 .github/tasks/.gitkeep create mode 100644 .github/tasks/README.md create mode 100644 .github/tasks/task-list.md create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/code-quality.yml create mode 100644 .github/workflows/release.yml create mode 100644 .lintstagedrc.json create mode 100644 .nvmrc create mode 100644 AGENTS.md create mode 100644 CHANGELOG.md create mode 100644 CLAUDE.md create mode 100644 CODEOWNERS create mode 100644 assets/css/.gitkeep create mode 100644 assets/icons/.gitkeep create mode 100644 assets/images/.gitkeep create mode 100644 assets/js/.gitkeep create mode 100644 blocks/.gitkeep create mode 100644 composer.json create mode 100644 docs/README.md create mode 100644 inc/.gitkeep create mode 100644 languages/.gitkeep create mode 100644 package.json create mode 100644 patterns/.gitkeep create mode 100644 plugin-utils.mjs create mode 100644 readme.txt create mode 100644 src/.gitkeep create mode 100644 src/blocks/.gitkeep create mode 100644 src/css/.gitkeep create mode 100644 src/js/.gitkeep create mode 100644 templates/.gitkeep create mode 100644 uninstall.php create mode 100644 {{PLUGIN_SLUG}}.php diff --git a/.agents/README.md b/.agents/README.md new file mode 100644 index 0000000..84922a5 --- /dev/null +++ b/.agents/README.md @@ -0,0 +1,39 @@ +# .agents + +This folder contains portable agent-specific assets for {{PLUGIN_NAME}}. +These assets are designed to be reusable across different AI systems and tools. + +--- + +## Structure + +| Folder | Purpose | +|---|---| +| `skills/` | Portable, reusable agent skills — focused tasks agents can perform | +| `agents/` | Agent persona definitions — describe specialist roles agents can adopt | + +--- + +## Difference between `.github/` and `.agents/` + +| `.github/` | `.agents/` | +|---|---| +| GitHub-specific (Copilot, Actions, instructions) | Portable across AI systems | +| Workflows, prompts, reports, tasks | Skills and personas | +| Tied to GitHub platform features | Usable in any AI tool or agent framework | + +--- + +## Usage + +- AI agents should read `AGENTS.md` first, then consult relevant skills and personas here. +- Skills describe *how* to perform a specific task in this repo context. +- Agents describe *who* the AI should act as for a given type of work. + +--- + +## Adding skills and agents + +- Add new skills as folders under `.agents/skills/`, each with a `SKILL.md` file. +- Add new agent personas as Markdown files under `.agents/agents/`. +- Keep skills focused — one skill, one task type. diff --git a/.agents/agents/README.md b/.agents/agents/README.md new file mode 100644 index 0000000..b819dc8 --- /dev/null +++ b/.agents/agents/README.md @@ -0,0 +1,27 @@ +# .agents/agents + +This folder contains agent persona definitions for {{PLUGIN_NAME}}. + +--- + +## What is an agent persona? + +An agent persona describes the role, expertise, and behaviour an AI agent should adopt for a specific type of work in this repository. +Personas are not skills — they describe *who the agent is*, not *what it does*. + +--- + +## Available agents + +| Agent | Description | +|---|---| +| `plugin-architect.md` | WordPress plugin architecture specialist | + +--- + +## Adding new agents + +1. Create a file named `{role}.md` in this folder. +2. Describe the agent's role, expertise, and working style. +3. Reference specific skills from `.agents/skills/` where relevant. +4. Add it to the table in this README. diff --git a/.agents/agents/plugin-architect.md b/.agents/agents/plugin-architect.md new file mode 100644 index 0000000..2f4be4d --- /dev/null +++ b/.agents/agents/plugin-architect.md @@ -0,0 +1,50 @@ +# Agent: Plugin Architect + +## Role + +You are a senior WordPress plugin architect at LightSpeed. +You specialise in building maintainable, secure, accessible WordPress plugins — with a strong bias toward Gutenberg block development. + +--- + +## Expertise + +- WordPress plugin architecture and best practices +- Gutenberg block development (`block.json`, `@wordpress/scripts`, `register_block_type`) +- PHP coding standards (WordPress Coding Standards, escaping, sanitisation) +- JavaScript and React for the block editor +- Accessibility (WCAG 2.1 AA, semantic HTML, ARIA) +- Security (output escaping, input sanitisation, nonce verification, capability checks) +- Performance (lean asset loading, no unnecessary dependencies) +- LightSpeed plugin and theme scaffold conventions +- AI-assisted development workflows + +--- + +## Working style + +- Prefer small, precise diffs over large rewrites. +- Keep the plugin lean — do not add dependencies or infrastructure that is not needed. +- Follow WordPress core conventions before reaching for abstraction. +- Always escape PHP output. Always sanitise PHP input. No exceptions. +- Use `block.json` for block registration rather than bespoke PHP registration logic. +- Reference `AGENTS.md` before making any changes to the repo. +- Write reports to `.github/reports/` and tasks to `.github/tasks/`. +- Do not modify unrelated files. +- Explain your reasoning clearly when making architectural decisions. + +--- + +## Available skills + +- [Block Plugin Audit](../.agents/skills/block-plugin-audit/SKILL.md) + +--- + +## Guiding principles + +1. **WordPress-first** — use stable WordPress APIs before adding abstraction. +2. **Security by default** — escape, sanitise, validate everywhere. +3. **Accessible** — semantic HTML, ARIA, keyboard support in every block. +4. **Lean** — every file, dependency, and line of code should earn its place. +5. **Documented** — leave the repo in a better state than you found it. diff --git a/.agents/skills/README.md b/.agents/skills/README.md new file mode 100644 index 0000000..1f92aa0 --- /dev/null +++ b/.agents/skills/README.md @@ -0,0 +1,46 @@ +# .agents/skills + +This folder contains portable, reusable agent skills for {{PLUGIN_NAME}}. + +--- + +## What is a skill? + +A skill is a focused, self-contained description of how an AI agent should perform a specific task in this repository. +Skills are not personas — they describe *what to do*, not *who to be*. + +--- + +## Structure + +Each skill lives in its own folder: + +``` +skills/ +└── {skill-name}/ + └── SKILL.md +``` + +The `SKILL.md` file describes: +- What the skill does +- When to use it +- Step-by-step instructions +- Expected inputs and outputs +- Quality criteria + +--- + +## Available skills + +| Skill | Description | +|---|---| +| `block-plugin-audit/` | Audit a WordPress block plugin for quality, security, and accessibility | + +--- + +## Adding new skills + +1. Create a folder under `.agents/skills/` with a descriptive name. +2. Add a `SKILL.md` file following the structure above. +3. Add it to the table in this README. +4. Reference it from `AGENTS.md` if it is commonly needed. diff --git a/.agents/skills/block-plugin-audit/SKILL.md b/.agents/skills/block-plugin-audit/SKILL.md new file mode 100644 index 0000000..f99d71d --- /dev/null +++ b/.agents/skills/block-plugin-audit/SKILL.md @@ -0,0 +1,87 @@ +# SKILL: Block Plugin Audit + +Audit a WordPress block plugin for code quality, security, and accessibility. + +--- + +## When to use this skill + +Use this skill when: +- Reviewing a plugin before a release. +- Onboarding onto an existing plugin codebase. +- Requested to produce a plugin audit report. +- Running the `audit-plugin.prompt.md` prompt. + +--- + +## Inputs + +- The plugin repository root directory. +- Optional: specific files or folders to focus on. + +--- + +## Steps + +### 1. Review plugin structure + +- Confirm the main plugin file exists and has a valid WordPress plugin header. +- Confirm `defined( 'ABSPATH' )` direct access protection is present. +- Confirm plugin constants are defined correctly. +- Confirm includes are loaded via `plugins_loaded`. +- Confirm the text domain matches the plugin slug. + +### 2. PHP security review + +- Scan all PHP files for unescaped output. +- Check for unsanitised `$_GET`, `$_POST`, `$_REQUEST`, `$_COOKIE` usage. +- Check for missing nonce verification on form submissions. +- Check for missing capability checks before privileged operations. +- Check for unsafe database queries (missing `$wpdb->prepare()`). +- Flag any use of `eval()`, `exec()`, `system()`, or `shell_exec()`. + +### 3. Block quality review (if blocks present) + +- Confirm each block has a valid `block.json`. +- Confirm blocks are registered using `register_block_type()` with `block.json`. +- Confirm PHP render callbacks escape all dynamic output. +- Confirm block patterns use safe escaping. +- Confirm `block.json` files have `$schema`, `name`, `title`, `category`, and `textdomain`. + +### 4. Translation review + +- Confirm all user-facing strings use translation functions. +- Confirm the correct text domain is used in all translation function calls. + +### 5. Accessibility review + +- Review block edit and save components for: + - Semantic HTML elements + - ARIA attributes where needed + - Keyboard navigation support + - Sufficient colour contrast (flag for manual review) +- Review frontend-rendered output for accessibility markers. + +### 6. General quality + +- Check for unreplaced `{{PLACEHOLDER}}` tokens. +- Check that `CHANGELOG.md` is up to date. +- Check for unused files or stale commented-out code. + +--- + +## Outputs + +1. Write an audit report to `.github/reports/audit-YYYY-MM-DD.md`. +2. Create or update `.github/tasks/task-list.md` with actionable tasks from findings. + +--- + +## Quality criteria + +A passing audit has: +- No unescaped output in PHP files. +- No unsanitised input used directly. +- Valid `block.json` for every registered block. +- No unreplaced placeholder tokens. +- `CHANGELOG.md` reflects the current state of the plugin. diff --git a/.coderabbit.yml b/.coderabbit.yml new file mode 100644 index 0000000..0d7cef0 --- /dev/null +++ b/.coderabbit.yml @@ -0,0 +1,69 @@ +version: 2 + +# Reviews are enabled and set to a draft-aware, plugin-focused review profile. +reviews: + request_changes_workflow: false + high_level_summary: true + poem: false + review_status: true + collapse_walkthrough: false + +# Language and tone +language: en-US +tone_instructions: "Be direct and professional. Focus on practical WordPress plugin quality." + +# Path-level review instructions +path_instructions: + - path: "{{PLUGIN_SLUG}}.php" + instructions: | + Review the main plugin bootstrap file. + Check for: valid plugin header with all required fields, direct access protection (ABSPATH check), + correct version constant, safe loading of includes, no business logic in the bootstrap. + Confirm text domain matches the plugin slug. + + - path: "inc/**/*.php" + instructions: | + Review PHP include files. + Check for: correct escaping (esc_html__, esc_attr__, wp_kses_post, etc.), + sanitisation and validation of input, translation function usage, no direct database queries without $wpdb, + correct use of WordPress hooks and filters. + + - path: "src/**/*.js" + instructions: | + Review JavaScript source files. + Check for: correct use of wp.element or vanilla JS, no jQuery dependency unless justified, + no hardcoded strings (use wp.i18n), accessibility-aware DOM interactions. + + - path: "src/**/*.css" + instructions: | + Review CSS source files. + Check for: no use of !important unless justified, responsive-first approach, + use of CSS custom properties where appropriate, accessible colour contrast considerations. + + - path: "blocks/**/block.json" + instructions: | + Review block registration JSON files. + Check for: valid schema reference, correct category and icon, editorScript/style/viewScript paths, + meaningful attributes, supports declarations, correct textdomain in titles and descriptions. + + - path: "patterns/*.php" + instructions: | + Review block pattern PHP files. + Check for: correct escaping of any dynamic output, translation function usage, + pattern header comments (Title, Slug, Categories), clean markup. + + - path: "AGENTS.md" + instructions: | + Review the AI agent guidance document. + Check for: clarity, completeness, placeholder consistency, accurate command references, + correct folder references for prompts, reports, tasks, docs, skills, and agents. + + - path: "README.md" + instructions: | + Review the root README. + Check for: clear quick start, accurate command table, correct placeholder references, + sensible repo map, correct folder descriptions. + +# What CodeRabbit should focus on for this repo +chat: + auto_reply: true diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2eb2ebe --- /dev/null +++ b/.editorconfig @@ -0,0 +1,29 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.php] +indent_style = tab +indent_size = 4 + +[*.{js,mjs,cjs,ts,jsx,tsx}] +indent_style = space +indent_size = 2 + +[*.{css,scss}] +indent_style = space +indent_size = 2 + +[*.{json,yml,yaml}] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6b2edd3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,37 @@ +# Normalise line endings to LF on checkout +* text=auto eol=lf + +# Binary files — do not modify +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.svg binary +*.woff binary +*.woff2 binary +*.ttf binary +*.eot binary +*.zip binary + +# Export-ignore — exclude from dist archives +.git export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.editorconfig export-ignore +.nvmrc export-ignore +.coderabbit.yml export-ignore +.lintstagedrc.json export-ignore +node_modules/ export-ignore +vendor/ export-ignore +src/ export-ignore +.github/ export-ignore +.agents/ export-ignore +package.json export-ignore +package-lock.json export-ignore +composer.json export-ignore +composer.lock export-ignore +plugin-utils.mjs export-ignore +AGENTS.md export-ignore +CLAUDE.md export-ignore +CODEOWNERS export-ignore diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000..d24d085 --- /dev/null +++ b/.github/README.md @@ -0,0 +1,33 @@ +# .github + +This folder contains GitHub-native AI and repository workflow infrastructure for {{PLUGIN_NAME}}. + +--- + +## Folder structure + +| Folder / File | Purpose | +|---|---| +| `copilot-instructions.md` | Primary GitHub Copilot instruction file for this repo | +| `instructions/` | File-type-specific Copilot guidance files | +| `prompts/` | Reusable GitHub Copilot prompt files for repeatable workflows | +| `reports/` | Developer and AI-generated reports (not end-user docs) | +| `tasks/` | Task lists and AI-maintained work tracking | +| `workflows/` | GitHub Actions CI/CD workflows | + +--- + +## Usage + +- AI agents: read `AGENTS.md` first, then `.github/copilot-instructions.md`. +- Developers: see `prompts/` for useful starting prompts for common tasks. +- Reports and task lists: see `reports/README.md` and `tasks/README.md`. +- Workflows are triggered automatically via GitHub Actions. + +--- + +## What does NOT belong in `.github/` + +- End-user documentation → `docs/` +- Plugin source code → `src/`, `inc/`, `blocks/` +- Static assets → `assets/` diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..6b506e1 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,86 @@ +# GitHub Copilot Instructions + +This file configures GitHub Copilot for the {{PLUGIN_NAME}} repository. +Read [`AGENTS.md`](../AGENTS.md) for full agent guidance. + +--- + +## Repo overview + +This is a LightSpeed WordPress block plugin starter. +- One plugin, one repo. +- Block-ready but not locked into a large block framework. +- Uses placeholder tokens (`{{DOUBLE_BRACES}}`) throughout — replace before production. + +--- + +## Key conventions + +- PHP: tabs for indentation, WordPress coding standards. +- JS/CSS: spaces (2), follow `@wordpress/scripts` defaults. +- Text domain must match the plugin slug everywhere. +- All PHP output must be escaped. All input must be sanitised. +- Use `esc_html__()`, `esc_attr__()`, `esc_url()`, `wp_kses_post()`. + +--- + +## Where things live + +| What | Where | +|---|---| +| Main plugin bootstrap | `{{PLUGIN_SLUG}}.php` | +| PHP includes | `inc/` | +| Block source files | `src/blocks/` | +| Built block assets | `blocks/` | +| Static assets | `assets/` | +| Block patterns | `patterns/` | +| Translation files | `languages/` | +| End-user docs | `docs/` | +| Copilot prompts | `.github/prompts/` | +| Developer reports | `.github/reports/` | +| Task lists | `.github/tasks/` | +| Portable skills | `.agents/skills/` | +| Agent personas | `.agents/agents/` | + +--- + +## File-type guidance + +See `.github/instructions/` for detailed guidance: + +- [`php.instructions.md`](instructions/php.instructions.md) — PHP coding standards and escaping +- [`blocks.instructions.md`](instructions/blocks.instructions.md) — Block development conventions +- [`plugin-structure.instructions.md`](instructions/plugin-structure.instructions.md) — Plugin structure rules +- [`assets.instructions.md`](instructions/assets.instructions.md) — Asset conventions +- [`workflows.instructions.md`](instructions/workflows.instructions.md) — CI/CD and workflow guidance + +--- + +## Security reminders + +- Never echo unescaped dynamic values. +- Always sanitise `$_GET`, `$_POST`, `$_REQUEST`, and `$_COOKIE`. +- Use `$wpdb->prepare()` for database queries. +- Validate nonces on form submissions. +- Use capability checks before sensitive operations. + +--- + +## Validation and linting + +```bash +npm run plugin:validate # Validate plugin structure +npm run security:scan # PHP security scan +composer run phpcs # PHP coding standards +npm run lint # JS + CSS + JSON linting +``` + +--- + +## Accessibility + +- Use semantic HTML elements. +- Add ARIA attributes where native semantics are insufficient. +- Ensure sufficient colour contrast. +- Support keyboard navigation in interactive blocks. +- Use `aria-label` or `aria-labelledby` on interactive controls without visible labels. diff --git a/.github/instructions/README.md b/.github/instructions/README.md new file mode 100644 index 0000000..5551e5c --- /dev/null +++ b/.github/instructions/README.md @@ -0,0 +1,26 @@ +# .github/instructions + +This folder contains file-type-specific GitHub Copilot instruction files for {{PLUGIN_NAME}}. + +--- + +## Files + +| File | Purpose | +|---|---| +| `php.instructions.md` | PHP coding standards, escaping, and sanitisation | +| `blocks.instructions.md` | WordPress block development conventions | +| `plugin-structure.instructions.md` | Plugin folder structure and architecture rules | +| `assets.instructions.md` | Asset organisation and build conventions | +| `workflows.instructions.md` | CI/CD and GitHub Actions guidance | + +--- + +## How these files work + +GitHub Copilot reads these files to understand context for specific file types or workflows. +Reference them from `.github/copilot-instructions.md` using relative links. + +To add a new instruction file: +1. Create a file named `{topic}.instructions.md` in this folder. +2. Add a link to it from `.github/copilot-instructions.md`. diff --git a/.github/instructions/assets.instructions.md b/.github/instructions/assets.instructions.md new file mode 100644 index 0000000..2255193 --- /dev/null +++ b/.github/instructions/assets.instructions.md @@ -0,0 +1,50 @@ +--- +applyTo: "assets/**,src/**" +--- + +# Assets Instructions + +## Asset organisation + +| Folder | Content | +|---|---| +| `assets/css/` | Static (pre-built) CSS files for frontend or admin | +| `assets/js/` | Static (pre-built) JS files not managed by block build | +| `assets/images/` | Plugin images (logos, backgrounds, etc.) | +| `assets/icons/` | SVG or PNG icons | +| `src/blocks/` | Block source files — compiled by `@wordpress/scripts` | +| `src/css/` | Non-block CSS source files | +| `src/js/` | Non-block JS source files | +| `blocks/` | Built block assets — output of `npm run build` | + +## Rules + +- Do not mix source and built files in the same folder. +- `src/` contains files that need compilation. +- `assets/` contains files that are already production-ready. +- `blocks/` contains built block output from `@wordpress/scripts`. +- Do not commit compiled output from `src/` — use `npm run build` to generate it. + +## Enqueuing assets in PHP + +Use `wp_enqueue_style()` and `wp_enqueue_script()` with versioning: + +```php +wp_enqueue_style( + '{{PLUGIN_SLUG}}-frontend', + {{NAMESPACE}}_PLUGIN_URL . 'assets/css/frontend.css', + [], + {{NAMESPACE}}_VERSION +); +``` + +## Block assets + +Block assets (editor and frontend CSS/JS) are declared in `block.json` and enqueued automatically by `register_block_type()`. +Do not manually enqueue block scripts — let `block.json` handle it. + +## Image and icon guidelines + +- Optimise all images before committing. +- Use SVG for icons where possible. +- Do not commit large image files to the repository. diff --git a/.github/instructions/blocks.instructions.md b/.github/instructions/blocks.instructions.md new file mode 100644 index 0000000..6349a69 --- /dev/null +++ b/.github/instructions/blocks.instructions.md @@ -0,0 +1,96 @@ +--- +applyTo: "**/{blocks,src/blocks}/**" +--- + +# Blocks Instructions + +## Block structure + +Each block lives in its own directory under `src/blocks/{{block-name}}/`. +Built assets are output to `blocks/{{block-name}}/`. + +Typical block directory: + +``` +src/blocks/my-block/ +├── block.json Block registration metadata +├── edit.js Block editor component +├── index.js Block registration entry point +├── editor.css Editor-only styles (optional) +└── style.css Frontend styles (optional) +``` + +## block.json + +Every block must have a `block.json` file with: + +- `$schema`: reference the WordPress block schema +- `apiVersion`: use `3` +- `name`: `{{PLUGIN_SLUG}}/block-name` +- `title`, `description`, `category`, `icon` +- `textdomain`: `{{TEXT_DOMAIN}}` +- `editorScript`, `style`, `viewScript` as appropriate +- `supports` and `attributes` declarations + +Example: + +```json +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "{{PLUGIN_SLUG}}/my-block", + "title": "My Block", + "category": "text", + "icon": "block-default", + "description": "A short block description.", + "textdomain": "{{TEXT_DOMAIN}}", + "editorScript": "file:./index.js", + "style": "file:./style.css", + "supports": { + "html": false, + "color": { "background": true, "text": true } + } +} +``` + +## Registration in PHP + +Register blocks in the main plugin file or a dedicated `inc/` file: + +```php +register_block_type( {{NAMESPACE}}_PLUGIN_DIR . 'blocks/my-block' ); +``` + +Do not manually register scripts and styles when `block.json` handles it. + +## PHP-rendered blocks + +For `render_callback` blocks: + +```php +function {{PLUGIN_SLUG}}_render_my_block( $attributes, $content, $block ) { + $title = isset( $attributes['title'] ) ? sanitize_text_field( $attributes['title'] ) : ''; + return '
' . esc_html( $title ) . '
'; +} +``` + +Always escape output. Always sanitise attributes before use. + +## Editor vs frontend assets + +- Editor CSS: `editorStyle` in `block.json` +- Frontend CSS: `style` in `block.json` +- Editor JS: `editorScript` in `block.json` +- Frontend JS (interactive): `viewScript` in `block.json` + +## Build tooling + +Use `@wordpress/scripts` for building block assets: + +```bash +npm run build # Production build +npm run start # Development watch +``` + +Block source: `src/blocks/` +Block output: `blocks/` diff --git a/.github/instructions/php.instructions.md b/.github/instructions/php.instructions.md new file mode 100644 index 0000000..1caa965 --- /dev/null +++ b/.github/instructions/php.instructions.md @@ -0,0 +1,61 @@ +--- +applyTo: "**/*.php" +--- + +# PHP Instructions + +## Coding standards + +- Follow [WordPress PHP Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/php/). +- Use **tabs** for indentation (not spaces). +- Opening braces go on the same line for control structures. +- Use `true`, `false`, `null` (lowercase). + +## Escaping output + +Always escape before output. Match the escaping function to the context: + +```php +echo esc_html( $text ); // Plain text output +echo esc_attr( $attribute ); // HTML attribute values +echo esc_url( $url ); // URLs in href/src +echo esc_html__( 'String', '{{TEXT_DOMAIN}}' ); // Translated plain text +echo wp_kses_post( $html ); // HTML content (limited tags) +``` + +Never echo unescaped variables or user input. + +## Sanitising input + +Sanitise all external input before storing or using it: + +```php +$text = sanitize_text_field( wp_unslash( $_POST['field'] ) ); +$url = esc_url_raw( wp_unslash( $_POST['url'] ) ); +$int = absint( $_POST['number'] ); +$email = sanitize_email( $_POST['email'] ); +$html = wp_kses_post( wp_unslash( $_POST['content'] ) ); +``` + +## Translation + +Wrap all user-facing strings: + +```php +esc_html__( 'String', '{{TEXT_DOMAIN}}' ) +esc_html_e( 'String', '{{TEXT_DOMAIN}}' ) +esc_attr__( 'String', '{{TEXT_DOMAIN}}' ) +``` + +## Security + +- Check nonces with `check_ajax_referer()` or `wp_verify_nonce()` before processing form submissions. +- Check capabilities with `current_user_can()` before privileged operations. +- Use `$wpdb->prepare()` for all database queries with dynamic values. +- Never use `eval()`. + +## File structure + +- PHP includes go in `inc/`. +- Class files: `inc/class-{{plugin-slug}}-name.php`. +- Load includes from the main plugin file via `plugins_loaded`. diff --git a/.github/instructions/plugin-structure.instructions.md b/.github/instructions/plugin-structure.instructions.md new file mode 100644 index 0000000..d2c07cb --- /dev/null +++ b/.github/instructions/plugin-structure.instructions.md @@ -0,0 +1,56 @@ +--- +applyTo: "**" +--- + +# Plugin Structure Instructions + +## Root files + +| File | Purpose | +|---|---| +| `{{PLUGIN_SLUG}}.php` | Main plugin bootstrap — valid WordPress plugin header required | +| `uninstall.php` | Plugin uninstall handler — keep conservative, no data deletion by default | +| `plugin-utils.mjs` | Plugin validation and utility CLI | +| `package.json` | Node scripts and dev dependencies | +| `composer.json` | PHP quality tooling | +| `AGENTS.md` | AI agent guidance — read this first | +| `CHANGELOG.md` | Version history — follow Keep a Changelog + SemVer | +| `README.md` | Human-readable root documentation | + +## Main plugin file rules + +- Must contain a valid WordPress plugin header. +- Must include `defined( 'ABSPATH' ) || exit;` direct access protection. +- Define plugin constants: `{{NAMESPACE}}_VERSION`, `{{NAMESPACE}}_PLUGIN_DIR`, `{{NAMESPACE}}_PLUGIN_URL`. +- Load includes via `plugins_loaded` — not at the top level. +- Keep the bootstrap lean — no business logic. + +## Directory roles + +| Directory | Purpose | +|---|---| +| `inc/` | Optional PHP include files — loaded from main plugin file | +| `src/` | Source files for compilation (blocks, CSS, JS) | +| `blocks/` | Built block assets — output of `npm run build` | +| `assets/` | Static (pre-built) CSS, JS, images, icons | +| `patterns/` | WordPress block patterns (PHP with header comments) | +| `templates/` | Optional block templates and template parts | +| `languages/` | Translation files (.pot, .po, .mo) | +| `docs/` | End-user documentation only | +| `.github/` | GitHub-native workflows, instructions, prompts, reports, tasks | +| `.agents/` | Portable agent skills and personas | + +## Placeholder consistency + +All `{{PLACEHOLDER}}` tokens must be replaced consistently: +- Text domain = plugin slug everywhere. +- Namespace = consistent uppercase constant prefix. +- Package name = consistent in package.json and composer.json. + +## What NOT to do + +- Do not put developer reports in `docs/`. +- Do not put built assets in `src/`. +- Do not add Playwright, Storybook, Docker, webpack config, or Vite. +- Do not add a PHP autoloader unless there is a genuine reason. +- Do not add issue templates or pull request templates. diff --git a/.github/instructions/workflows.instructions.md b/.github/instructions/workflows.instructions.md new file mode 100644 index 0000000..6bc9de8 --- /dev/null +++ b/.github/instructions/workflows.instructions.md @@ -0,0 +1,54 @@ +--- +applyTo: ".github/workflows/**" +--- + +# Workflows Instructions + +## GitHub Actions workflows + +| Workflow | Purpose | +|---|---| +| `ci.yml` | Install dependencies, validate plugin, lint | +| `code-quality.yml` | PHP coding standards and lint checks | +| `release.yml` | Validate changelog on release tags | + +## Rules + +- Keep workflows focused and fast. +- Install Node dependencies with `npm ci`. +- Install Composer dependencies with `composer install --no-interaction`. +- Run `npm run plugin:validate` as part of CI. +- Run `composer run phpcs` as part of CI. +- Do not add Docker, Playwright, or E2E tests unless explicitly required. + +## Workflow triggers + +- `ci.yml` and `code-quality.yml` should run on `push` and `pull_request` to `main`. +- `release.yml` should run on `push` of tags matching `v*`. + +## Secrets + +- Do not hardcode credentials in workflow files. +- Use GitHub Actions secrets for any tokens or keys. + +## Node setup + +Use the `actions/setup-node` action with `.nvmrc` for the Node version: + +```yaml +- uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' +``` + +## Composer setup + +Use the `shivammathur/setup-php` action for PHP: + +```yaml +- uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + tools: composer +``` diff --git a/.github/prompts/README.md b/.github/prompts/README.md new file mode 100644 index 0000000..bf44e26 --- /dev/null +++ b/.github/prompts/README.md @@ -0,0 +1,41 @@ +# .github/prompts + +This folder contains GitHub Copilot prompt files for {{PLUGIN_NAME}}. +These are reusable starting points for common development workflows. + +--- + +## Files + +| File | Purpose | +|---|---| +| `cleanup.prompt.md` | Clean up and review a PHP or JS file | +| `audit-plugin.prompt.md` | Audit the full plugin for quality issues | +| `new-block.prompt.md` | Scaffold a new Gutenberg block | +| `release.prompt.md` | Prepare a plugin release | + +--- + +## How to use prompts + +1. Open GitHub Copilot Chat in VS Code. +2. Type `/` to see available prompt files, or reference them directly. +3. Provide any additional context the prompt asks for. + +--- + +## Adding new prompts + +1. Create a file named `{workflow}.prompt.md` in this folder. +2. Use Markdown with a clear title and instructions. +3. Reference specific files or patterns relevant to this plugin. +4. Add it to the table above. + +--- + +## Notes + +- Prompts are GitHub-specific and live in `.github/prompts/`. +- Portable, reusable skills live in `.agents/skills/`. +- Reports generated by prompts should go in `.github/reports/`. +- Task lists generated by prompts should go in `.github/tasks/`. diff --git a/.github/prompts/audit-plugin.prompt.md b/.github/prompts/audit-plugin.prompt.md new file mode 100644 index 0000000..586943f --- /dev/null +++ b/.github/prompts/audit-plugin.prompt.md @@ -0,0 +1,40 @@ +# Audit Plugin Prompt + +Perform a quality and security audit of this WordPress plugin. + +## Instructions + +Review the following areas and produce a structured report: + +### 1. Plugin structure +- Is the main plugin file correctly structured with a valid header? +- Is ABSPATH protection present? +- Are constants defined correctly? +- Are includes loaded correctly via `plugins_loaded`? + +### 2. PHP quality +- Is all output correctly escaped? +- Is all input correctly sanitised and validated? +- Are nonces used on form submissions? +- Are capability checks in place before privileged operations? +- Are translation functions used for all user-facing strings? + +### 3. Block quality (if blocks are present) +- Is each block registered with a valid `block.json`? +- Is `block.json` used for asset declaration instead of manual enqueuing? +- Are PHP-rendered block callbacks escaping all output? +- Are block patterns using safe escaping? + +### 4. JavaScript quality +- Are there any hardcoded strings that should be translated? +- Are there any obvious accessibility issues? + +### 5. General +- Are any `{{PLACEHOLDER}}` tokens still unreplaced? +- Is the `CHANGELOG.md` up to date? +- Are there any unused files or folders? + +## Output format + +Write the audit report to `.github/reports/audit-YYYY-MM-DD.md`. +Create a task list in `.github/tasks/task-list.md` based on findings. diff --git a/.github/prompts/cleanup.prompt.md b/.github/prompts/cleanup.prompt.md new file mode 100644 index 0000000..5638e4e --- /dev/null +++ b/.github/prompts/cleanup.prompt.md @@ -0,0 +1,25 @@ +# Cleanup Prompt + +Review the specified file and clean it up. + +## Instructions + +1. Review the file for: + - Unused variables, functions, or imports + - Dead or commented-out code that should be removed + - Inconsistent indentation or formatting + - Missing or outdated doc comments + - Strings not wrapped in translation functions + - PHP output that is not escaped + +2. Apply fixes using the project's coding standards: + - PHP: WordPress Coding Standards (tabs, WordPress escaping, translation) + - JS: `@wordpress/scripts` eslint config + - CSS: `@wordpress/scripts` stylelint config + +3. Do not change logic — only clean up formatting, escaping, and documentation. + +## File to review + +> Replace this line with the path to the file you want to clean up. +> Example: `inc/class-my-plugin-example.php` diff --git a/.github/prompts/new-block.prompt.md b/.github/prompts/new-block.prompt.md new file mode 100644 index 0000000..cabccf7 --- /dev/null +++ b/.github/prompts/new-block.prompt.md @@ -0,0 +1,37 @@ +# New Block Prompt + +Scaffold a new Gutenberg block for this plugin. + +## Instructions + +Create a new block with the following details: + +- **Block slug**: (replace with your block name, e.g. `my-block`) +- **Block title**: (replace with a human-readable title) +- **Block description**: (replace with a short description) +- **Block category**: (e.g. `text`, `media`, `design`, `theme`, `widgets`) +- **Block type**: static (uses Save component) or dynamic (uses PHP render callback) + +## What to create + +1. `src/blocks/{{block-slug}}/block.json` — block registration metadata +2. `src/blocks/{{block-slug}}/index.js` — block registration entry point +3. `src/blocks/{{block-slug}}/edit.js` — block editor component +4. `src/blocks/{{block-slug}}/save.js` — block save function (for static blocks) +5. `src/blocks/{{block-slug}}/style.css` — frontend styles +6. For dynamic blocks: PHP render callback registered in `inc/` or the main plugin file + +## Rules + +- Use `block.json` for all asset declarations — no manual `wp_enqueue_*` for block assets. +- Escape all PHP output in render callbacks. +- Use `{{TEXT_DOMAIN}}` as the text domain for all translated strings. +- Block name must be `{{PLUGIN_SLUG}}/{{block-slug}}`. +- Run `npm run build` after creating source files. +- Register the block in the main plugin file using `register_block_type()`. + +## After creation + +- Run `npm run schema:validate` to validate `block.json`. +- Run `npm run build` to compile block assets. +- Update `CHANGELOG.md` under `[Unreleased]`. diff --git a/.github/prompts/release.prompt.md b/.github/prompts/release.prompt.md new file mode 100644 index 0000000..7e0f326 --- /dev/null +++ b/.github/prompts/release.prompt.md @@ -0,0 +1,49 @@ +# Release Prompt + +Prepare a new plugin release. + +## Instructions + +Perform the following steps to prepare a release: + +### 1. Review changes +- Review `git log` or `git diff` since the last release. +- Identify all significant changes (features, fixes, security updates). + +### 2. Update CHANGELOG.md +- Move all entries from `[Unreleased]` to a new versioned section. +- Use the format `## [X.Y.Z] - YYYY-MM-DD`. +- Follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) conventions. +- Add a compare link at the bottom of `CHANGELOG.md`. + +### 3. Update version numbers +Update the version number in: +- `{{PLUGIN_SLUG}}.php` — `Version:` header field +- `{{PLUGIN_SLUG}}.php` — `{{NAMESPACE}}_VERSION` constant +- `package.json` — `version` field +- `readme.txt` — `Stable tag:` field + +### 4. Validate +Run: +```bash +npm run plugin:validate +npm run security:scan +composer run phpcs +npm run lint +``` + +Fix any issues before tagging. + +### 5. Commit and tag +```bash +git add . +git commit -m "Release v{VERSION}" +git tag v{VERSION} +git push && git push --tags +``` + +## Notes + +- Follow [SemVer](https://semver.org/): MAJOR.MINOR.PATCH. +- Security fixes should be PATCH or MINOR releases with a `Security` changelog entry. +- Do not release with unreplaced `{{PLACEHOLDER}}` tokens. diff --git a/.github/reports/.gitkeep b/.github/reports/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.github/reports/README.md b/.github/reports/README.md new file mode 100644 index 0000000..d4ef122 --- /dev/null +++ b/.github/reports/README.md @@ -0,0 +1,66 @@ +# .github/reports + +This folder contains **developer and AI-generated reports** for {{PLUGIN_NAME}}. + +--- + +## What belongs here + +- Plugin audit reports +- Security scan reports +- Code quality reports +- AI-generated analysis outputs +- Accessibility audit reports + +## What does NOT belong here + +- End-user documentation → `docs/` +- Task lists → `.github/tasks/` + +--- + +## Naming conventions + +Use the following format for report filenames: + +``` +{type}-YYYY-MM-DD.md +``` + +Examples: +- `audit-2025-06-01.md` +- `security-scan-2025-06-01.md` +- `accessibility-audit-2025-07-15.md` + +For multiple reports of the same type in one period, append a short descriptor: + +``` +audit-2025-06-01-blocks.md +audit-2025-06-01-php.md +``` + +--- + +## Creating reports + +When an AI agent runs a prompt from `.github/prompts/audit-plugin.prompt.md` or similar: +1. Write the report to this folder using the naming convention above. +2. Summarise findings clearly under headings. +3. Create follow-up tasks in `.github/tasks/task-list.md`. + +--- + +## Subfolders (optional) + +For larger repos, reports may be organised by month or category: + +``` +reports/ +├── 2025-06/ +│ ├── audit-2025-06-01.md +│ └── security-scan-2025-06-15.md +└── 2025-07/ + └── audit-2025-07-01.md +``` + +This is optional — keep it flat if the volume is low. diff --git a/.github/tasks/.gitkeep b/.github/tasks/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.github/tasks/README.md b/.github/tasks/README.md new file mode 100644 index 0000000..970553d --- /dev/null +++ b/.github/tasks/README.md @@ -0,0 +1,53 @@ +# .github/tasks + +This folder contains task lists and AI-maintained work tracking for {{PLUGIN_NAME}}. + +--- + +## Files + +| File | Purpose | +|---|---| +| `task-list.md` | Current task list — update as work progresses | +| `.gitkeep` | Keeps the folder tracked by Git when empty | + +--- + +## Naming conventions + +| File type | Format | Example | +|---|---|---| +| Main task list | `task-list.md` | `task-list.md` | +| Milestone task list | `tasks-{milestone}.md` | `tasks-v1.0.0.md` | +| Sprint task list | `tasks-{date}.md` | `tasks-2025-06.md` | + +--- + +## When to create task lists + +- After running an audit prompt — create tasks from findings. +- When planning a new feature or milestone. +- When an AI agent identifies work that requires human review. + +--- + +## How AI agents should use this folder + +- After generating a report in `.github/reports/`, create or update `task-list.md`. +- Mark tasks as `[ ]` (pending) or `[x]` (completed). +- Add a reference to the report that generated each task. +- Do not delete completed tasks — mark them done so there is a history. + +--- + +## How tasks relate to reports + +- Reports live in `.github/reports/`. +- Tasks live in `.github/tasks/`. +- A task entry should reference the report it came from where applicable. + +Example: + +```markdown +- [ ] Fix unescaped output in `inc/class-example.php:42` — see `reports/audit-2025-06-01.md` +``` diff --git a/.github/tasks/task-list.md b/.github/tasks/task-list.md new file mode 100644 index 0000000..d6f46f5 --- /dev/null +++ b/.github/tasks/task-list.md @@ -0,0 +1,24 @@ +# Task List + +Active tasks for {{PLUGIN_NAME}}. +Update this file as work progresses. + +--- + +## In progress + + + +## Pending + +- [ ] Replace all `{{PLACEHOLDER}}` tokens with real values +- [ ] Rename `{{PLUGIN_SLUG}}.php` to match the actual plugin slug +- [ ] Update `CHANGELOG.md` with the actual release date +- [ ] Update `CODEOWNERS` with real GitHub usernames +- [ ] Update `docs/README.md` with the plugin's documentation structure +- [ ] Run `npm run plugin:validate` and resolve any warnings +- [ ] Install dependencies: `npm install && composer install` + +## Completed + + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f98367a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + validate: + name: Validate plugin + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install Node dependencies + run: npm ci + + - name: Validate plugin structure + run: npm run plugin:validate + + - name: Validate schema + run: npm run schema:validate + + - name: Security scan + run: npm run security:scan + + - name: Lint JS and CSS + run: npm run lint diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml new file mode 100644 index 0000000..bbaa1f5 --- /dev/null +++ b/.github/workflows/code-quality.yml @@ -0,0 +1,32 @@ +name: Code Quality + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + php-quality: + name: PHP code quality + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + tools: composer + coverage: none + + - name: Install Composer dependencies + run: composer install --no-interaction --prefer-dist --no-progress + + - name: PHP syntax check + run: composer run phplint + + - name: PHP coding standards + run: composer run phpcs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..69888d5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,46 @@ +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + release: + name: Validate release + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install Node dependencies + run: npm ci + + - name: Validate plugin structure + run: npm run plugin:validate + + - name: Security scan + run: npm run security:scan + + - name: Check for unreplaced placeholders + run: node plugin-utils.mjs scan-placeholders + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + tools: composer + coverage: none + + - name: Install Composer dependencies + run: composer install --no-interaction --prefer-dist --no-progress + + - name: PHP coding standards + run: composer run phpcs diff --git a/.gitignore b/.gitignore index 5469669..05901a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,48 +1,55 @@ -# Wordpress - ignore core, configuration, examples, uploads and logs. -# https://github.com/github/gitignore/blob/main/WordPress.gitignore - -# Core -# -# Note: if you want to stage/commit WP core files -# you can delete this whole section/until Configuration. -/wp-admin/ -/wp-content/index.php -/wp-content/languages -/wp-content/plugins/index.php -/wp-content/themes/index.php -/wp-includes/ -/index.php -/license.txt -/readme.html -/wp-*.php -/xmlrpc.php - -# Configuration -wp-config.php - -# Example themes -/wp-content/themes/twenty*/ - -# Example plugin -/wp-content/plugins/hello.php - -# Uploads -/wp-content/uploads/ - -# Log files -*.log - -# htaccess -/.htaccess +# Dependencies +node_modules/ +vendor/ -# All plugins -# -# Note: If you wish to whitelist plugins, -# uncomment the next line -#/wp-content/plugins +# Build output +build/ +dist/ -# All themes -# -# Note: If you wish to whitelist themes, -# uncomment the next line -#/wp-content/themes \ No newline at end of file +# Logs +*.log +logs/ + +# OS +.DS_Store +Thumbs.db + +# Editor +.vscode/settings.json +.idea/ +*.swp +*.swo +*~ + +# Environment +.env +.env.local +.env.*.local + +# Archives +*.zip +*.tar.gz +*.tgz + +# PHP +.php-cs-fixer.cache +.phpunit.cache/ +.phpunit.result.cache + +# Node +.npm/ +.eslintcache +.stylelintcache + +# Temp +tmp/ +temp/ + +# AI / local cache +.cache/ +.ai-cache/ + +# Lock files +# package-lock.json is committed to lock Node dependencies. +# composer.lock is NOT committed — this is a plugin, not a deployment artefact. +composer.lock \ No newline at end of file diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 0000000..2637421 --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,7 @@ +{ + "*.php": ["composer run phpcs --", "composer run phplint --"], + "src/**/*.js": ["npm run lint:js --"], + "src/**/*.css": ["npm run lint:css --"], + "blocks/**/block.json": ["npm run lint:json --"], + "*.json": ["npm run lint:json --"] +} diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..efa3784 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,227 @@ +# AGENTS.md — AI Agent Guidance + +This is the primary guidance document for AI agents working in this repository. +Read this file first before making any changes. + +--- + +## Repo Purpose + +This is a LightSpeed WordPress block plugin starter repository. +It provides a clean, lean scaffold for building custom WordPress plugins — with a bias toward Gutenberg block development. + +It is **not** a WordPress.org submission starter. +It is **not** a monorepo. +It is a **single plugin in a single repo**. + +--- + +## Repo Structure + +``` +/ +├── {{PLUGIN_SLUG}}.php Main plugin bootstrap — rename to match your slug +├── uninstall.php Plugin uninstall stub — keep conservative +├── plugin-utils.mjs Node CLI for validation, schema checks, and security scanning +├── package.json Node scripts and dev dependencies +├── composer.json PHP quality tooling +├── AGENTS.md This file — start here +├── CLAUDE.md Claude agent pointer +├── CHANGELOG.md Version history — follow Keep a Changelog + SemVer +├── README.md Human-readable root documentation +├── readme.txt Lightweight WordPress distribution placeholder +├── inc/ PHP include files (optional, loaded from main plugin file) +├── src/ Source files: src/blocks/, src/css/, src/js/ +├── blocks/ Built or registered block asset directories +├── assets/ Static assets: assets/css/, assets/js/, assets/images/, assets/icons/ +├── patterns/ WordPress block patterns (PHP files with header comments) +├── templates/ Optional block templates and template parts +├── languages/ Translation files (.pot, .po, .mo) +├── docs/ End-user documentation — NOT developer reports +├── .github/ GitHub-native workflows, Copilot instructions, prompts, tasks, reports +└── .agents/ Portable agent skills and personas +``` + +--- + +## Plugin-First Development + +- This is a **plugin repo** — not a theme, not a monorepo. +- One plugin, one repo. Do not add multi-plugin infrastructure. +- The main plugin file must maintain a valid WordPress plugin header. +- Keep the bootstrap file lean — load includes via `plugins_loaded`, not inline. +- Do not invent a plugin framework. Use WordPress core APIs. + +--- + +## Placeholder Conventions + +All placeholder tokens use `{{DOUBLE_BRACES}}` format. +These must be replaced before the plugin is used in production. + +| Placeholder | Purpose | +|---|---| +| `{{PLUGIN_NAME}}` | Human-readable plugin name | +| `{{PLUGIN_SLUG}}` | URL-safe slug (lowercase, hyphens) | +| `{{TEXT_DOMAIN}}` | WordPress text domain — must match slug | +| `{{PLUGIN_URI}}` | Plugin URI | +| `{{PLUGIN_DESCRIPTION}}` | One-sentence plugin description | +| `{{AUTHOR_NAME}}` | Author or organisation name | +| `{{AUTHOR_URI}}` | Author website URL | +| `{{NAMESPACE}}` | PHP constant namespace prefix (uppercase, underscores) | +| `{{PACKAGE_NAME}}` | Composer/npm package name | +| `{{REPO_NAME}}` | GitHub repository name | +| `{{GITHUB_ORG}}` | GitHub organisation name | + +**Rules:** +- Text domain and slug must always match. +- Namespace must be uppercase and underscore-separated. +- Do not leave required metadata blank — use placeholders. +- Keep placeholders consistent across all files. + +--- + +## PHP Architecture + +- Keep PHP minimal unless there is a clear need. +- PHP include files go in `inc/`. +- Load includes from `{{PLUGIN_SLUG}}_init()` via `plugins_loaded`. +- Use WordPress coding standards (tabs for indentation in PHP). +- Do not add a PHP autoloader unless genuinely needed. +- Class files go in `inc/` — name them `class-{{plugin-slug}}-name.php`. + +### Escaping output (required) + +Always escape output before rendering: + +```php +echo esc_html( $variable ); +echo esc_attr( $attribute ); +echo esc_url( $url ); +echo wp_kses_post( $html ); +``` + +Never echo unescaped dynamic content. + +### Sanitising input (required) + +Sanitise all user input before using it: + +```php +$value = sanitize_text_field( wp_unslash( $_POST['field'] ) ); +$url = esc_url_raw( wp_unslash( $_POST['url'] ) ); +$int = absint( $_POST['number'] ); +``` + +### Translation + +Always wrap strings for translation: + +```php +esc_html__( 'String', '{{TEXT_DOMAIN}}' ) +esc_html_e( 'String', '{{TEXT_DOMAIN}}' ) +``` + +--- + +## Block Development + +- Source block files go in `src/blocks/{{block-name}}/`. +- Built block assets go in `blocks/{{block-name}}/`. +- Each block must have a valid `block.json` file. +- Register blocks using `register_block_type()` with the path to `block.json`. +- Use `@wordpress/scripts` for building block assets. +- Block editor assets should be separate from frontend assets. +- PHP-rendered blocks must escape all dynamic output. +- Use `block.json` `supports` and `attributes` rather than bespoke registration logic. + +Example block registration: + +```php +register_block_type( {{NAMESPACE}}_PLUGIN_DIR . 'blocks/my-block' ); +``` + +--- + +## Asset Conventions + +- **Static assets** (ready-to-use CSS, JS, images, icons): `assets/` +- **Source files** (need compilation): `src/` +- **Built block assets**: `blocks/` +- Do not mix source and built assets in the same folder. +- Frontend CSS and editor CSS should be separate files where appropriate. + +--- + +## Validation and Linting Commands + +Run these before committing: + +```bash +# Node validation +npm run plugin:validate # Validate plugin structure and headers +npm run schema:validate # Validate block.json and JSON files +npm run security:scan # Scan for risky PHP patterns +npm run lint # Run all JS, CSS, and JSON linters + +# PHP quality +composer run phpcs # Check coding standards +composer run phpcbf # Auto-fix coding standards +composer run phplint # Check PHP syntax +``` + +--- + +## Changelog Expectations + +- Follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +- Follow [Semantic Versioning](https://semver.org/). +- Every version bump must include a `CHANGELOG.md` entry. +- Keep an `[Unreleased]` section at the top. +- Use sections: `Added`, `Changed`, `Deprecated`, `Removed`, `Fixed`, `Security`. + +--- + +## Documentation Expectations + +- `docs/` is for **end-user documentation** — installation guides, editor guides, client notes. +- Do **not** put developer reports in `docs/`. +- Developer reports go in `.github/reports/`. +- Task lists go in `.github/tasks/`. + +--- + +## AI Folder Conventions + +| Folder | Purpose | +|---|---| +| `.github/prompts/` | GitHub Copilot prompt files for repeatable workflows | +| `.github/reports/` | Developer and AI-generated reports for this repo | +| `.github/tasks/` | Task lists and AI-maintained work tracking | +| `.github/instructions/` | File-type-specific Copilot guidance | +| `.agents/skills/` | Portable, reusable agent skills | +| `.agents/agents/` | Agent persona definitions | + +**Rules for AI agents:** +- Store reports in `.github/reports/` — never in `docs/` or the repo root. +- Store task lists in `.github/tasks/` — update `task-list.md` when work is tracked. +- Store GitHub prompt files in `.github/prompts/`. +- Store portable skills in `.agents/skills/`. +- Store agent personas in `.agents/agents/`. +- Keep prompts focused and short — they are wrappers for repeatable workflows. + +--- + +## Code Quality Rules For AI Agents + +- Prefer **small diffs** — avoid large rewrites unless clearly justified. +- Avoid adding new dependencies unless strictly necessary. +- Do not invent a build pipeline — use `@wordpress/scripts` if builds are needed. +- Keep the plugin lean — do not add infrastructure that is not used. +- Do not add Playwright, Storybook, Docker, webpack config, or Vite unless there is a strong reason. +- Escape all PHP output correctly. +- Sanitise and validate all inputs. +- Review block render callbacks and patterns carefully before modifying. +- Do not hardcode plugin-specific values — use constants and placeholders. +- Follow WordPress coding standards. +- Follow accessibility best practices in blocks and templates. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0f5c3ed --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,40 @@ +# Changelog + +All notable changes to this plugin will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +--- + +## [Unreleased] + +### Added + +### Changed + +### Deprecated + +### Removed + +### Fixed + +### Security + +--- + +## [0.1.0] - YYYY-MM-DD + +### Added +- Initial plugin scaffold with placeholder structure. +- Block-ready `src/blocks/` and `blocks/` folder layout. +- `plugin-utils.mjs` for plugin validation, schema checks, and security scanning. +- Composer-based PHP quality tooling (PHPCS, PHPCBF, PHP lint). +- `.github/` folder with Copilot instructions, prompts, reports, tasks, and workflows. +- `.agents/` folder with portable skills and agent personas. +- `docs/` folder for end-user documentation. + +--- + +[Unreleased]: https://github.com/{{GITHUB_ORG}}/{{REPO_NAME}}/compare/v0.1.0...HEAD +[0.1.0]: https://github.com/{{GITHUB_ORG}}/{{REPO_NAME}}/releases/tag/v0.1.0 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..c577915 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +# CLAUDE.md + +This repository uses `AGENTS.md` as the primary AI agent guidance document. + +Please read [`AGENTS.md`](./AGENTS.md) before making any changes to this repository. diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..7076ddb --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,4 @@ +# {{GITHUB_ORG}}/{{REPO_NAME}} + +# Default code owners — update with real GitHub usernames. +* @{{GITHUB_ORG}}/{{REPO_NAME}}-team diff --git a/README.md b/README.md index 91dc910..b13cecd 100644 --- a/README.md +++ b/README.md @@ -1 +1,191 @@ -# ls-starter-plugin \ No newline at end of file +# {{PLUGIN_NAME}} + +> LightSpeed WordPress block plugin starter — lean, block-ready, AI-workflow-aware. + +--- + +## What this repo is + +A production-aware starter template for building custom WordPress plugins at LightSpeed. +It is **not** a WordPress.org submission starter and does not include submission-specific bureaucracy. + +It is intentionally lighter than `block-plugin-scaffold` while still providing: + +- a valid WordPress plugin skeleton +- a block-ready folder structure +- practical PHP and JS linting and validation +- AI-ready workflows with prompts, reports, tasks, and agent personas +- clear placeholder conventions throughout + +--- + +## Who it is for + +LightSpeed developers building custom WordPress plugins — for client and commercial work. +Also suitable for use by AI agents working within a structured, well-documented repo. + +--- + +## What it includes + +| Area | Description | +|---|---| +| `{{PLUGIN_SLUG}}.php` | Main plugin bootstrap file | +| `uninstall.php` | Safe plugin uninstall stub | +| `inc/` | Optional PHP include files | +| `src/` | Source files for block and plugin assets | +| `blocks/` | Built or registered block asset directories | +| `assets/` | Static CSS, JS, images, and icons | +| `patterns/` | WordPress block patterns | +| `templates/` | Optional block templates | +| `languages/` | Translation files | +| `docs/` | End-user documentation | +| `.github/` | GitHub-native AI and workflow infrastructure | +| `.agents/` | Portable agent skills and personas | + +--- + +## Requirements + +- PHP 8.0+ +- WordPress 6.4+ +- Node.js 20+ (see `.nvmrc`) +- Composer + +--- + +## Quick start + +```bash +# 1. Use this template via GitHub (recommended): +# Click "Use this template" on the GitHub repo page, +# or clone and replace placeholders manually: +git clone https://github.com/{{GITHUB_ORG}}/{{REPO_NAME}}.git my-plugin +cd my-plugin + +# 2. Replace all {{PLACEHOLDER}} tokens (see Customise placeholders below) +# Rename {{PLUGIN_SLUG}}.php to match your actual plugin slug. + +# 3. Install Node dependencies +npm install + +# 4. Install Composer dependencies +composer install + +# 5. Validate the plugin scaffold +npm run plugin:validate + +# 6. Lint your code +npm run lint +composer run phpcs +``` + +--- + +## Customise placeholders + +Search and replace these tokens across the entire repo before starting work: + +| Placeholder | Example value | +|---|---| +| `{{PLUGIN_NAME}}` | `My Awesome Plugin` | +| `{{PLUGIN_SLUG}}` | `my-awesome-plugin` | +| `{{TEXT_DOMAIN}}` | `my-awesome-plugin` | +| `{{PLUGIN_URI}}` | `https://example.com/plugins/my-awesome-plugin` | +| `{{PLUGIN_DESCRIPTION}}` | `A useful WordPress plugin.` | +| `{{AUTHOR_NAME}}` | `LightSpeed` | +| `{{AUTHOR_URI}}` | `https://lightspeedwp.agency` | +| `{{NAMESPACE}}` | `MY_AWESOME_PLUGIN` | +| `{{PACKAGE_NAME}}` | `lightspeedwp/my-awesome-plugin` | +| `{{REPO_NAME}}` | `my-awesome-plugin` | +| `{{GITHUB_ORG}}` | `lightspeedwp` | + +Also rename `{{PLUGIN_SLUG}}.php` to match your actual plugin slug. + +--- + +## Repo map + +``` +/ +├── {{PLUGIN_SLUG}}.php Main plugin bootstrap +├── uninstall.php Plugin uninstall handler +├── plugin-utils.mjs Plugin validation and utility CLI +├── package.json Node tooling and scripts +├── composer.json PHP tooling and standards +├── AGENTS.md AI agent guidance (start here for AI) +├── CLAUDE.md Claude-specific pointer +├── CHANGELOG.md Version history +├── docs/ End-user documentation +├── inc/ PHP include files +├── src/ Source files (blocks, CSS, JS) +├── blocks/ Built/registered block assets +├── assets/ Static assets (css, js, images, icons) +├── patterns/ WordPress block patterns +├── templates/ Block templates (optional) +├── languages/ Translation files +├── .github/ GitHub workflows, prompts, reports, tasks +└── .agents/ Portable agent skills and personas +``` + +--- + +## Available commands + +### Node + +| Command | Description | +|---|---| +| `npm run plugin:validate` | Validate plugin structure and headers | +| `npm run schema:validate` | Validate block.json and JSON files | +| `npm run security:scan` | Scan PHP files for risky patterns | +| `npm run lint` | Run all JS, CSS, and JSON linters | +| `npm run lint:js` | Lint JS source files | +| `npm run lint:css` | Lint CSS source files | +| `npm run lint:json` | Lint JSON files | +| `npm run build` | Build block assets | +| `npm run start` | Watch and build block assets | +| `npm run i18n` | Generate translation POT file | + +### Composer + +| Command | Description | +|---|---| +| `composer run phpcs` | Check PHP coding standards | +| `composer run phpcbf` | Auto-fix PHP coding standards | +| `composer run phplint` | Check PHP syntax | + +--- + +## What to edit first + +1. Replace all placeholders (see table above). +2. Rename `{{PLUGIN_SLUG}}.php` to your plugin slug. +3. Update `CHANGELOG.md` with your real start date. +4. Update `docs/README.md` with your plugin's documentation structure. +5. Update `CODEOWNERS` with real GitHub usernames. +6. Add your PHP includes to `inc/` and load them from the main plugin file. +7. Add blocks to `src/blocks/` and register them in the main plugin file. + +--- + +## AI workflows + +| Folder | Purpose | +|---|---| +| `AGENTS.md` | Primary AI agent guidance for this repo | +| `.github/copilot-instructions.md` | GitHub Copilot instructions | +| `.github/instructions/` | File-type-specific Copilot guidance | +| `.github/prompts/` | Reusable GitHub Copilot prompt files | +| `.github/reports/` | Developer and AI-generated reports | +| `.github/tasks/` | Task lists and AI work tracking | +| `.agents/skills/` | Portable agent skills | +| `.agents/agents/` | Agent persona definitions | + +--- + +## Notes + +- This repo is **not** packaged specifically for WordPress.org submission. +- `composer.lock` is not committed (this is a plugin, not a deployment artefact). +- `package-lock.json` is committed to pin Node dependencies. \ No newline at end of file diff --git a/assets/css/.gitkeep b/assets/css/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/assets/icons/.gitkeep b/assets/icons/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/assets/images/.gitkeep b/assets/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/assets/js/.gitkeep b/assets/js/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/blocks/.gitkeep b/blocks/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..1d6e5c0 --- /dev/null +++ b/composer.json @@ -0,0 +1,31 @@ +{ + "name": "{{PACKAGE_NAME}}", + "type": "wordpress-plugin", + "license": "GPL-2.0-or-later", + "authors": [ + { + "name": "{{AUTHOR_NAME}}", + "homepage": "{{AUTHOR_URI}}" + } + ], + "require": { + "php": ">=8.0" + }, + "require-dev": { + "squizlabs/php_codesniffer": "^3.10", + "wp-coding-standards/wpcs": "^3.1", + "phpcompatibility/phpcompatibility-wp": "^2.1", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0" + }, + "scripts": { + "phpcs": "phpcs --standard=WordPress .", + "phpcbf": "phpcbf --standard=WordPress .", + "phplint": "find . -name '*.php' -not -path './vendor/*' -not -path './node_modules/*' | xargs php -l" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + }, + "optimize-autoloader": true + } +} diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..54313ba --- /dev/null +++ b/docs/README.md @@ -0,0 +1,36 @@ +# docs + +This folder contains **end-user documentation** for {{PLUGIN_NAME}}. + +--- + +## What belongs here + +- Installation and setup guides +- Editor and block usage guides +- Client-facing usage notes +- Screenshots and annotated walkthroughs +- FAQs and troubleshooting notes + +## What does NOT belong here + +- Developer reports → `.github/reports/` +- Task lists → `.github/tasks/` +- AI prompts → `.github/prompts/` +- Developer onboarding notes → `README.md` or `AGENTS.md` + +--- + +## Suggested structure + +As the plugin grows, organise docs by audience or topic: + +``` +docs/ +├── README.md This file — overview of docs structure +├── installation.md How to install and activate the plugin +├── usage.md How to use the plugin's blocks and features +└── faq.md Common questions and answers +``` + +Add screenshots and images to `docs/` as needed. diff --git a/inc/.gitkeep b/inc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/languages/.gitkeep b/languages/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json new file mode 100644 index 0000000..c09d198 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "{{PACKAGE_NAME}}", + "version": "0.1.0", + "description": "{{PLUGIN_DESCRIPTION}}", + "type": "module", + "private": true, + "scripts": { + "build": "wp-scripts build", + "start": "wp-scripts start", + "plugin:validate": "node plugin-utils.mjs validate-plugin", + "schema:validate": "node plugin-utils.mjs validate-schema", + "security:scan": "node plugin-utils.mjs security-scan", + "lint": "npm run lint:js && npm run lint:css && npm run lint:json", + "lint:js": "wp-scripts lint-js src", + "lint:css": "wp-scripts lint-style src", + "lint:json": "wp-scripts lint-md-docs . --ext=.json || true", + "i18n": "wp-scripts makepot", + "packages-update": "wp-scripts packages-update", + "help": "node plugin-utils.mjs help" + }, + "engines": { + "node": ">=20.0.0" + }, + "devDependencies": { + "@wordpress/scripts": "^30.0.0" + } +} diff --git a/patterns/.gitkeep b/patterns/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/plugin-utils.mjs b/plugin-utils.mjs new file mode 100644 index 0000000..bcfb94f --- /dev/null +++ b/plugin-utils.mjs @@ -0,0 +1,433 @@ +#!/usr/bin/env node +/** + * plugin-utils.mjs + * + * Plugin validation and utility CLI for {{PLUGIN_NAME}}. + * Run with: node plugin-utils.mjs + * + * Commands: + * validate-plugin Validate plugin structure and headers + * validate-schema Validate block.json and JSON schema files + * security-scan Scan PHP files for risky patterns + * scan-placeholders Check for unreplaced placeholder tokens + * help Show available commands + */ + +import { readFileSync, readdirSync, existsSync, statSync } from 'fs'; +import { join, relative } from 'path'; + +// ─── Helpers ──────────────────────────────────────────────────────────────── + +const ROOT = process.cwd(); + +const colours = { + reset: '\x1b[0m', + red: '\x1b[31m', + yellow: '\x1b[33m', + green: '\x1b[32m', + cyan: '\x1b[36m', + bold: '\x1b[1m', +}; + +const log = { + info: ( msg ) => console.log( ` ${msg}` ), + ok: ( msg ) => console.log( `${colours.green} ✓${colours.reset} ${msg}` ), + warn: ( msg ) => console.log( `${colours.yellow} ⚠ ${msg}${colours.reset}` ), + error: ( msg ) => console.log( `${colours.red} ✗ ${msg}${colours.reset}` ), + heading: ( msg ) => console.log( `\n${colours.bold}${colours.cyan}${msg}${colours.reset}\n` ), +}; + +/** + * Recursively find files matching a predicate. + * + * @param {string} dir + * @param {Function} predicate + * @param {string[]} results + * @returns {string[]} + */ +function findFiles( dir, predicate, results = [] ) { + if ( ! existsSync( dir ) ) return results; + + for ( const entry of readdirSync( dir ) ) { + const fullPath = join( dir, entry ); + + // Skip common large directories. + if ( [ 'node_modules', 'vendor', '.git', 'build', 'dist' ].includes( entry ) ) { + continue; + } + + const stat = statSync( fullPath ); + if ( stat.isDirectory() ) { + findFiles( fullPath, predicate, results ); + } else if ( predicate( entry, fullPath ) ) { + results.push( fullPath ); + } + } + + return results; +} + +/** + * Read a file as a string, return empty string on failure. + * + * @param {string} filePath + * @returns {string} + */ +function readFileSafe( filePath ) { + try { + return readFileSync( filePath, 'utf8' ); + } catch { + return ''; + } +} + +// ─── Commands ──────────────────────────────────────────────────────────────── + +/** + * validate-plugin + * Check that required plugin files exist and the main plugin header is valid. + */ +function validatePlugin() { + log.heading( 'Validating plugin structure…' ); + + let errors = 0; + let warnings = 0; + + // Required files. + const required = [ + 'uninstall.php', + 'README.md', + 'CHANGELOG.md', + 'package.json', + 'composer.json', + 'plugin-utils.mjs', + ]; + + for ( const file of required ) { + if ( existsSync( join( ROOT, file ) ) ) { + log.ok( `${file} exists` ); + } else { + log.error( `Missing required file: ${file}` ); + errors++; + } + } + + // Find the main plugin file (a PHP file in root with a plugin header). + const phpFiles = readdirSync( ROOT ).filter( + ( f ) => f.endsWith( '.php' ) && f !== 'uninstall.php' + ); + + const mainPluginFile = phpFiles.find( ( file ) => { + const content = readFileSafe( join( ROOT, file ) ); + return content.includes( 'Plugin Name:' ); + } ); + + if ( mainPluginFile ) { + log.ok( `Main plugin file found: ${mainPluginFile}` ); + + const content = readFileSafe( join( ROOT, mainPluginFile ) ); + + // Check for ABSPATH protection. + if ( content.includes( "defined( 'ABSPATH' )" ) || content.includes( "defined('ABSPATH')" ) ) { + log.ok( 'Direct access protection (ABSPATH check) present' ); + } else { + log.error( 'Missing ABSPATH direct access protection in main plugin file' ); + errors++; + } + + // Check for Text Domain header. + const textDomainMatch = content.match( /Text Domain:\s*(.+)/i ); + if ( textDomainMatch ) { + const textDomain = textDomainMatch[ 1 ].trim(); + log.ok( `Text Domain: ${textDomain}` ); + + // Warn if text domain still contains a placeholder. + if ( textDomain.includes( '{{' ) ) { + log.warn( 'Text Domain still contains a placeholder token — replace before use' ); + warnings++; + } + } else { + log.warn( 'Text Domain header not found in main plugin file' ); + warnings++; + } + + // Check plugin name. + const pluginNameMatch = content.match( /Plugin Name:\s*(.+)/i ); + if ( pluginNameMatch ) { + const pluginName = pluginNameMatch[ 1 ].trim(); + if ( pluginName.includes( '{{' ) ) { + log.warn( `Plugin Name still contains a placeholder: ${pluginName}` ); + warnings++; + } else { + log.ok( `Plugin Name: ${pluginName}` ); + } + } + } else { + log.error( 'No main plugin file found in root (a PHP file with "Plugin Name:" header)' ); + errors++; + } + + // Check required folders. + const requiredDirs = [ 'inc', 'src', 'blocks', 'assets', 'docs', 'languages' ]; + for ( const dir of requiredDirs ) { + if ( existsSync( join( ROOT, dir ) ) ) { + log.ok( `Directory exists: ${dir}/` ); + } else { + log.warn( `Directory missing: ${dir}/ — create it when needed` ); + warnings++; + } + } + + // Summary. + console.log( '' ); + if ( errors > 0 ) { + log.error( `Validation failed with ${errors} error(s) and ${warnings} warning(s).` ); + process.exit( 1 ); + } else if ( warnings > 0 ) { + log.warn( `Validation passed with ${warnings} warning(s). Review before release.` ); + } else { + log.ok( 'Plugin structure looks good.' ); + } +} + +/** + * validate-schema + * Validate block.json files and other plugin JSON files. + */ +function validateSchema() { + log.heading( 'Validating JSON and block.json files…' ); + + const jsonFiles = findFiles( ROOT, ( name ) => name.endsWith( '.json' ) ); + + if ( jsonFiles.length === 0 ) { + log.info( 'No JSON files found.' ); + return; + } + + let errors = 0; + + for ( const filePath of jsonFiles ) { + const rel = relative( ROOT, filePath ); + const content = readFileSafe( filePath ); + + try { + const parsed = JSON.parse( content ); + + // For block.json files, check for key fields. + if ( filePath.endsWith( 'block.json' ) ) { + const missing = []; + if ( ! parsed.name ) missing.push( 'name' ); + if ( ! parsed.title ) missing.push( 'title' ); + if ( ! parsed.category ) missing.push( 'category' ); + if ( ! parsed[ '$schema' ] ) missing.push( '$schema' ); + + if ( missing.length > 0 ) { + log.warn( `${rel}: block.json missing recommended fields: ${missing.join( ', ' )}` ); + } else { + log.ok( `${rel}: valid block.json` ); + } + } else { + log.ok( `${rel}: valid JSON` ); + } + } catch ( err ) { + log.error( `${rel}: invalid JSON — ${err.message}` ); + errors++; + } + } + + console.log( '' ); + if ( errors > 0 ) { + log.error( `Schema validation failed with ${errors} error(s).` ); + process.exit( 1 ); + } else { + log.ok( 'All JSON files are valid.' ); + } +} + +/** + * security-scan + * Scan PHP files for common risky patterns. + * This is a practical, lightweight scan — not a replacement for a proper SAST tool. + */ +function securityScan() { + log.heading( 'Scanning PHP files for risky patterns…' ); + + // Patterns that suggest risky code. + const riskyPatterns = [ + { + label: 'Unescaped echo of $_GET/$_POST/$_REQUEST/$_COOKIE', + regex: /echo\s+\$_(GET|POST|REQUEST|COOKIE)\[/, + severity: 'error', + }, + { + // Flag direct assignment of superglobals to a variable without an obvious + // sanitisation wrapper on the same line. Common safe wrappers are excluded + // to reduce false positives, but manual review is still recommended. + label: 'Possible unsanitised superglobal assignment — verify sanitisation', + regex: /=\s*\$_(GET|POST|REQUEST|COOKIE)\[/, + // Exclude lines that already call a known sanitisation function on the same line. + exclude: /sanitize_|esc_url_raw|absint|intval|wp_unslash/, + severity: 'warn', + note: 'Confirm input is sanitised, e.g. sanitize_text_field( wp_unslash( $_POST["field"] ) )', + }, + { + label: 'eval() usage', + regex: /\beval\s*\(/, + severity: 'error', + }, + { + label: 'base64_decode used — review for obfuscated payloads', + regex: /base64_decode\s*\(/, + severity: 'warn', + }, + { + label: 'Potential SQL injection — unescaped $wpdb->query with variable', + regex: /\$wpdb->(query|get_results|get_row|get_var)\s*\(\s*["']?\s*\$/, + severity: 'warn', + note: 'Use $wpdb->prepare() or verify this is safe', + }, + { + label: 'Direct file inclusion with variable', + regex: /(include|require)(_once)?\s*\(\s*\$/, + severity: 'warn', + note: 'Ensure path is validated and sanitised', + }, + { + label: 'system/exec/shell_exec/passthru usage', + regex: /\b(system|exec|shell_exec|passthru)\s*\(/, + severity: 'error', + }, + ]; + + const phpFiles = findFiles( ROOT, ( name ) => name.endsWith( '.php' ) ); + + if ( phpFiles.length === 0 ) { + log.info( 'No PHP files found.' ); + return; + } + + let totalIssues = 0; + + for ( const filePath of phpFiles ) { + const rel = relative( ROOT, filePath ); + const lines = readFileSafe( filePath ).split( '\n' ); + + for ( let i = 0; i < lines.length; i++ ) { + const line = lines[ i ]; + + for ( const pattern of riskyPatterns ) { + if ( pattern.regex.test( line ) ) { + // Skip if the line also matches an exclusion pattern (indicates safe usage). + if ( pattern.exclude && pattern.exclude.test( line ) ) { + continue; + } + const lineNum = i + 1; + if ( pattern.severity === 'error' ) { + log.error( `${rel}:${lineNum} — ${pattern.label}` ); + } else { + log.warn( `${rel}:${lineNum} — ${pattern.label}` ); + } + if ( pattern.note ) { + log.info( ` Note: ${pattern.note}` ); + } + totalIssues++; + } + } + } + } + + console.log( '' ); + if ( totalIssues === 0 ) { + log.ok( `Scanned ${phpFiles.length} PHP file(s) — no obvious risky patterns found.` ); + } else { + log.warn( + `Scanned ${phpFiles.length} PHP file(s) — ${totalIssues} potential issue(s) found. Review carefully.` + ); + } +} + +/** + * scan-placeholders + * Check for unreplaced {{PLACEHOLDER}} tokens. + */ +function scanPlaceholders() { + log.heading( 'Scanning for unreplaced placeholder tokens…' ); + + const scanExtensions = [ '.php', '.json', '.md', '.txt', '.yml', '.yaml', '.mjs', '.js' ]; + const placeholderRegex = /\{\{[A-Z_]+\}\}/g; + + const files = findFiles( + ROOT, + ( name ) => scanExtensions.some( ( ext ) => name.endsWith( ext ) ) + ); + + let found = 0; + + for ( const filePath of files ) { + const rel = relative( ROOT, filePath ); + const content = readFileSafe( filePath ); + const matches = content.match( placeholderRegex ); + + if ( matches ) { + const unique = [ ...new Set( matches ) ]; + log.warn( `${rel}: ${unique.join( ', ' )}` ); + found += unique.length; + } + } + + console.log( '' ); + if ( found === 0 ) { + log.ok( 'No unreplaced placeholder tokens found.' ); + } else { + log.warn( `Found ${found} placeholder token(s) across scanned files. Replace before deploying.` ); + } +} + +/** + * help + * Display available commands. + */ +function showHelp() { + console.log( ` +${colours.bold}plugin-utils.mjs — {{PLUGIN_NAME}} validation and utility CLI${colours.reset} + +Usage: + node plugin-utils.mjs + +Commands: + ${colours.cyan}validate-plugin${colours.reset} Validate plugin structure and headers + ${colours.cyan}validate-schema${colours.reset} Validate block.json and JSON files + ${colours.cyan}security-scan${colours.reset} Scan PHP files for risky patterns + ${colours.cyan}scan-placeholders${colours.reset} Check for unreplaced {{PLACEHOLDER}} tokens + ${colours.cyan}help${colours.reset} Show this help message +` ); +} + +// ─── Entrypoint ────────────────────────────────────────────────────────────── + +const command = process.argv[ 2 ]; + +switch ( command ) { + case 'validate-plugin': + validatePlugin(); + break; + case 'validate-schema': + validateSchema(); + break; + case 'security-scan': + securityScan(); + break; + case 'scan-placeholders': + scanPlaceholders(); + break; + case 'help': + case '--help': + case '-h': + case undefined: + showHelp(); + break; + default: + log.error( `Unknown command: ${command}` ); + showHelp(); + process.exit( 1 ); +} diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..3a4a69e --- /dev/null +++ b/readme.txt @@ -0,0 +1,26 @@ +=== {{PLUGIN_NAME}} === +Contributors: {{AUTHOR_NAME}} +Tags: blocks, gutenberg, {{PLUGIN_SLUG}} +Requires at least: 6.4 +Tested up to: 6.7 +Requires PHP: 8.0 +Stable tag: 0.1.0 +License: GPL-2.0-or-later +License URI: https://www.gnu.org/licenses/gpl-2.0.html + +{{PLUGIN_DESCRIPTION}} + +== Description == + +{{PLUGIN_DESCRIPTION}} + +== Installation == + +1. Upload the plugin folder to `/wp-content/plugins/`. +2. Activate the plugin through the Plugins screen in WordPress. +3. See `docs/` for further usage notes. + +== Changelog == + += 0.1.0 = +* Initial release. diff --git a/src/.gitkeep b/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/blocks/.gitkeep b/src/blocks/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/css/.gitkeep b/src/css/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/js/.gitkeep b/src/js/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/templates/.gitkeep b/templates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/uninstall.php b/uninstall.php new file mode 100644 index 0000000..9a81097 --- /dev/null +++ b/uninstall.php @@ -0,0 +1,30 @@ +query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '{{PLUGIN_SLUG}}_%'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery + +// Example: Remove a custom database table. +// global $wpdb; +// $table_name = $wpdb->prefix . '{{PLUGIN_SLUG}}_data'; +// $wpdb->query( "DROP TABLE IF EXISTS {$table_name}" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery diff --git a/{{PLUGIN_SLUG}}.php b/{{PLUGIN_SLUG}}.php new file mode 100644 index 0000000..b0a13f7 --- /dev/null +++ b/{{PLUGIN_SLUG}}.php @@ -0,0 +1,49 @@ + Date: Mon, 30 Mar 2026 15:43:55 +0000 Subject: [PATCH 3/5] Fix: add explicit permissions to GitHub Actions workflows Agent-Logs-Url: https://github.com/lightspeedwp/ls-starter-plugin/sessions/5b4a8a5c-337d-41eb-b6c4-d22d5fcc07b0 Co-authored-by: ashleyshaw <1805352+ashleyshaw@users.noreply.github.com> --- .github/workflows/ci.yml | 3 +++ .github/workflows/code-quality.yml | 3 +++ .github/workflows/release.yml | 3 +++ 3 files changed, 9 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f98367a..3d9a7c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [main] +permissions: + contents: read + jobs: validate: name: Validate plugin diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index bbaa1f5..8c684ac 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [main] +permissions: + contents: read + jobs: php-quality: name: PHP code quality diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 69888d5..27c3cea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,9 @@ on: tags: - 'v*' +permissions: + contents: read + jobs: release: name: Validate release From 76cc08572dad137a28db2264b0e1d76dbcd7da86 Mon Sep 17 00:00:00 2001 From: Ash Shaw Date: Mon, 30 Mar 2026 18:07:45 +0200 Subject: [PATCH 4/5] Update .github/instructions/workflows.instructions.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Ash Shaw --- .github/instructions/workflows.instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/instructions/workflows.instructions.md b/.github/instructions/workflows.instructions.md index 6bc9de8..8d8013f 100644 --- a/.github/instructions/workflows.instructions.md +++ b/.github/instructions/workflows.instructions.md @@ -10,7 +10,7 @@ applyTo: ".github/workflows/**" |---|---| | `ci.yml` | Install dependencies, validate plugin, lint | | `code-quality.yml` | PHP coding standards and lint checks | -| `release.yml` | Validate changelog on release tags | +| `release.yml` | Validate plugin scaffold (plugin:validate, security scan, placeholder scan, PHPCS) on release tags | ## Rules From a7be5c58364d0e079edf14dbade4331220e12dc0 Mon Sep 17 00:00:00 2001 From: Ash Shaw Date: Mon, 30 Mar 2026 18:08:13 +0200 Subject: [PATCH 5/5] Update .agents/agents/plugin-architect.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Ash Shaw --- .agents/agents/plugin-architect.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.agents/agents/plugin-architect.md b/.agents/agents/plugin-architect.md index 2f4be4d..ae5262d 100644 --- a/.agents/agents/plugin-architect.md +++ b/.agents/agents/plugin-architect.md @@ -37,7 +37,7 @@ You specialise in building maintainable, secure, accessible WordPress plugins ## Available skills -- [Block Plugin Audit](../.agents/skills/block-plugin-audit/SKILL.md) +- [Block Plugin Audit](../skills/block-plugin-audit/SKILL.md) ---