# Development Tooling

**Last Updated:** 2026-04-01

Overview of linting, formatting, validation, and automation tools in the Ordio project.

## Python Setup (Required for Pre-push & Rules)

The pre-push hook and `make rules` require Python with PyYAML. Use the project venv:

```bash
# Create venv (once)
python3 -m venv .venv
source .venv/bin/activate

# Install all Python deps (includes PyYAML, requests, Pillow for blog images)
pip install -r requirements.txt
```

**Why:** The pre-push hook runs `generate-rule-metadata.py` when `.cursor/rules/` changes. It uses `.venv/bin/python` when present so GitHub Desktop and other GUI tools use the same env. Without PyYAML, the push fails.

**Alternative:** Run `./setup-python-env.sh` to create `.venv` and install deps.

## Quick Reference

| Command | Purpose |
|---------|---------|
| `make validate` | Run all validators (lint, format, schema, PHPStan, links, lead capture copy, staged `docs/backups/` guard) |
| `make pre-deploy` | Minify, PHP checks, schema validation |
| `make validate-analytics` | GA4 setup validation (API; requires credentials) |
| `make rules` | Cursor rules: regen `METADATA_INDEX.json`, validate metadata/globs/links (glob step skips `docs/backups`, `.cursor/projects`) |
| `make og-audit` | OG tools hub: specs, WebP, registry, Gemini prompts, carousel (`audit-og-publish-readiness.py`) |
| `make og-audit-marketing` | OG: every `get_og_image_for_page` resolution must exist on disk (`audit-og-registry-vs-disk.php`) |
| `php v2/scripts/og-images/audit-og-registry-vs-disk.php` | Same as `og-audit-marketing` without Make |
| `php v2/scripts/dev-helpers/verify-compare-pages-reporting-section.php` | Comparison pages: section-3 uses `compare-reporting-insights-lanxess.php` (no legacy three-card slot) |
| `npm run lint` | ESLint (JS) |
| `npm run lint:fix` | ESLint with auto-fix |
| `npm run format` | Prettier (format JS) |
| `npm run format:check` | Prettier (check only) |
| `npm run validate` | Lint + format check |
| `npm run pre-deploy` | Minify + `composer validate:php` + `composer schema` |
| `npm run check-links` | Markdown link checker (docs/) |
| `npm run check-links:rules` | Markdown link checker (.cursor/rules/) |
| `npm run lighthouse` | Lighthouse CI (requires URL) |
| `npm run css:audit` | CSS audit (sizes, mapping, overlap) |
| `npm run css:coverage` | CSS coverage (Playwright; used/unused % per page) |
| `php v2/scripts/blog/test-css-visual.php` | Blog CSS loading check |
| `npm run validate:schema` | JSON-LD schema validation (pass file path) |
| `composer phpstan` | PHPStan (PHP static analysis) |
| `composer validate:php` | Pre-deployment + PHP extension checks |
| `composer schema` | VideoObject schema validation |
| `php v2/scripts/blog/verify-lead-capture-copy.php --strict` | Lead capture copy: fail if any description contains "dir bei diesem Thema hilft" |
| `python3 v2/scripts/dev-helpers/audit-unversioned-assets.py` | Cache busting audit: find CSS/JS includes without version parameters |

### Marketing / misc FAQ JSON-LD (SSOT)

Run after changing `v2/data/misc-faqs/*.json`, `industry-faqs/*.json`, or pages that use `render-faq-json.php` + `include-marketing-faq-jsonld.php`. See [FAQ_WEBSITE_STANDARD.md](../content/FAQ_WEBSITE_STANDARD.md) § FAQPage JSON-LD.

| Command | Purpose |
|---------|---------|
| `php v2/scripts/dev-helpers/verify-faq-jsonld-parity.php --all-misc-faqs` | Plain-text parity for misc + industry FAQ JSON (skips placeholder-only files) |
| `php v2/scripts/dev-helpers/audit-faq-jsonld-context.php` | Smoke test: standalone FAQ JSON-LD includes `@context` https://schema.org (sample tools + misc + compare) |
| `php v2/scripts/dev-helpers/audit-marketing-faq-ssot.php` | Flags marketing pages with head `FAQPage` vs post-footer include drift |
| `php v2/scripts/dev-helpers/audit-faq-json-internal-links.php '--glob=v2/data/misc-faqs/*.json'` | Validate internal `href`s in FAQ answers (quote glob in zsh) |

### Blog (manual pre-publish)

These are **not** part of `make validate` by default. Run before publishing or when editing outlines/keywords:

| Command | Purpose |
|---------|---------|
| `php v2/scripts/blog/validate-keyword-decision.php --post=SLUG --category=CATEGORY` | `KEYWORD_DECISION.md` table completeness (warnings; `--strict` exits non-zero) |
| `php v2/scripts/blog/propose-secondary-keywords.php --post=SLUG --category=CATEGORY` | Rank secondary candidates from `keywords-sistrix.json` (no API) |

See [docs/content/blog/KEYWORD_RESEARCH_WORKFLOW.md](../content/blog/KEYWORD_RESEARCH_WORKFLOW.md).

### Local blog backup hygiene (gitignored `docs/backups/`)

| Command | Purpose |
|---------|---------|
| `python3 v2/scripts/blog/inventory-docs-backups.py` | Read-only JSON + stdout summary of `docs/backups/` sizes and counts |
| `python3 v2/scripts/blog/cleanup-old-backups.py --dry-run` | Preview snapshot retention; **default** removes superseded dirs (**delete**); add `--archive` to move to `docs/backups/archive/` |
| `python3 v2/scripts/blog/cleanup-old-backups.py --prune-archive --dry-run` | Preview pruning `docs/backups/archive/` (keeps `--keep-archive=5` newest unless set) |
| `python3 v2/scripts/blog/cleanup-seo-meta-sync-backups.py --keep=10` | Prune old `blog-seo-meta-sync-*` dirs from `sync-meta-to-posts.php` |

Canonical doc: [guides/BACKUP_GUIDE.md](../content/blog/guides/BACKUP_GUIDE.md).

## Testing

| Command | Purpose |
|---------|---------|
| `.venv/bin/python scripts/validate-rules.py` | Rule metadata validation |
| `pytest tests/` | Python tests (calculator, rule validation) |
| `node tests/elterngeld_verification*.mjs` | Elterngeld calculator verification |
| `node tests/calculators/` | Calculator test suites (if Jest configured) |
| `php v2/scripts/dev-helpers/test-extension-fallbacks.php` | PHP extension fallback tests |

**Note:** Use `.venv/bin/python` for scripts requiring PyYAML. Run `make rules` for full rule validation.

## ESLint + Prettier

- **Config:** `eslint.config.mjs` (flat config), `.prettierrc`
- **ESLint:** v10+ (requires Node.js 20.19+, 22.13+, or 24+). Use `.nvmrc` (20) for local consistency.
- **Rules:** `no-console` (error), `no-unused-vars` (warn). Some rules downgraded to warn for incremental cleanup.
- **Excluded:** `*.min.js`, `wp-content/`, `node_modules/`, `**/vendor/`, `**/.venv/`
- **Override:** `v2/js/logger.js` and build scripts allow `console` (structured logger, minify output). `scripts/**` uses `no-console: off` (dev/test scripts). `**/*.mjs` gets Node + browser globals.
- **Migration:** See `docs/development/ESLINT_MIGRATION_2026.md` for migration details.

## Pre-commit Hooks (Husky + lint-staged)

On `git commit`, lint-staged runs:

- **JS:** ESLint --fix, Prettier --write
- **Scope:** Only staged files

## Pre-push Hook

On `git push`, before the push completes:

- **If `.cursor/rules/` changed:** Regenerates METADATA_INDEX.json and checks it is committed (same check as CI). Does not run full `make rules` (glob/link validation).
- **Always:** Runs `npm run validate` (lint + format check only; no Composer required). Run `make validate` manually for full checks including schema and PHPStan.

Catches issues locally to avoid unnecessary CI runs. Skip with `git push --no-verify`.

## PHPStan

- **Config:** `phpstan.neon` (includes `phpstan-baseline.neon` for existing level-5 findings)
- **Level:** 5
- **Paths:** `v2/`
- **Install:** `composer require --dev phpstan/phpstan`
- **Run:** `composer phpstan`
- **Baseline:** After fixing reported issues in a file, remove the matching baseline entries (or regenerate: `vendor/bin/phpstan analyse --generate-baseline phpstan-baseline.neon`). Commit baseline changes with the fixes.

## Lighthouse CI

- **Config:** `lighthouserc.js`
- **URLs:** ordio.com, minijob-rechner, compare/planday
- **Assertions:** Performance ≥80%, Accessibility ≥85%
- **CI:** `.github/workflows/lighthouse-ci.yml` (runs on PRs, weekly Mon 09:00 UTC, and manual). Uses `continue-on-error: true` (scores may fluctuate; does not block merge). Does not run on every push to save time.
- **Local:** `npm run lighthouse` (requires `@lhci/cli`). For upload to LHCI server, set `LHCI_GITHUB_APP_TOKEN`.

## Schema Validation

- **Script:** `scripts/validate-schema-jsonld.js`
- **Usage:** `node scripts/validate-schema-jsonld.js <file.json|file.html>`
- **Checks:** @context, @type, FAQPage structure

## GitHub Actions

Push to `master` triggers multiple workflows—each has different path filters. This is expected; they are not duplicates.

**continue-on-error:** Code Quality (PHPStan) and Lighthouse CI use `continue-on-error: true`—legacy PHPStan findings are recorded in `phpstan-baseline.neon` so `composer phpstan` passes locally; CI still uses continue-on-error. Lighthouse scores may fluctuate. Fix incrementally.

| Workflow | Push trigger | Path filter | Runs when |
|----------|--------------|-------------|-----------|
| **Deployment** | `push` to master | `paths-ignore: .cursor/**` | Code or docs change (skips rules-only) |
| **Validate Cursor Rules** | `push` to master | `paths: .cursor/rules/**` | Only when `.cursor/rules/` changes |
| **Code Quality** | `push` to master | `paths-ignore: docs/**, *.md, .cursor/**` | When code changes (excludes docs/rules) |
| **Lighthouse CI** | `pull_request`, `schedule` (Mon 09:00 UTC), `workflow_dispatch` | paths-ignore on PR: docs/**, *.md, .cursor/** | PRs + weekly + manual (not every push) |
| **Documentation Checks** | `push` to master | `paths: .cursor/rules/**, docs/**` | Disabled |

**Concurrency:** All workflows use `cancel-in-progress: true`—new pushes cancel in-progress runs for the same workflow.

**Before pushing `.cursor/rules/` changes:** Run `make rules` and commit `METADATA_INDEX.json`. The Validate Cursor Rules workflow fails if the index is out of sync.

## Dependabot

- **Config:** `.github/dependabot.yml`
- **Scope:** npm, Composer, GitHub Actions
- **Schedule:** Weekly (Monday)

## Dependency Maintenance

**npm:** Run `npm audit` and `npm audit fix` periodically. Safe updates: `npm update`. As of 2026-03-02: Project uses Tailwind v4.2.x, ESLint 10.0.2, lint-staged v16.3.1, postcss-cli v11.0.1. Four low-severity vulnerabilities remain in @lhci/cli dependency chain (inquirer/tmp); `npm audit fix --force` would downgrade @lhci/cli (breaking change)—defer. See `docs/development/DEPENDENCY_UPDATE_2026.md` for complete update log.

**Python:** Use `.venv` (see Python Setup above). Run `pip install -r requirements.txt` for full deps (PyYAML, requests, Pillow, etc.). Updated: jsonschema >=4.26.0 (requires Python >=3.10), pytest >=9.0.0.

**Node.js:** **Node 22 LTS** (updated from Node 20). Node 20 EOL was April 30, 2026. Project migrated to Node 22 LTS for extended support through April 30, 2027. ESLint v10 requires Node 20.19+, 22.13+, or 24+. See `docs/development/DEPENDENCY_UPDATE_2026.md` for migration details.

**Composer:** Run `composer update` when Composer is available. Dependabot opens PRs weekly. **PHP requirement updated to >=8.4** (from >=8.1) for transitive dependencies: maennchen/zipstream-php 3.2.1 (requires PHP ^8.3) and symfony/process v8.0.5 (requires PHP >=8.4). Updated: google/apiclient ^2.19, phpstan/phpstan ^1.12, phpoffice/phpspreadsheet ^5.5 (major update from 1.x - requires testing). **GitHub Actions workflows updated to PHP 8.4** (from 8.2) to match dependency requirements. See `docs/development/DEPENDENCY_UPDATE_2026.md` for breaking changes and deployment fix.

## Makefile

Single entry points for common workflows:

| Target | Purpose |
|--------|---------|
| `make validate` | Run all validators (lint, format check, schema, PHPStan, markdown links, lead capture copy --strict) |
| `make pre-deploy` | Minify assets, run `composer validate:php` and `composer schema` |
| `make validate-analytics` | GA4 setup validation (`ga4-setup-validate.php`; requires API credentials) |
| `make rules` | Cursor rules maintenance (`scripts/maintain-rules.sh`) |

## Composer Scripts

| Script | Purpose |
|--------|---------|
| `composer validate:php` | Pre-deployment check + PHP extension validation |
| `composer schema` | VideoObject schema validation (static_customers_new.php) |
| `composer phpstan` | PHP static analysis |

## VS Code / Cursor Extensions

Recommended extensions (`.vscode/extensions.json`):

- **PHP Intelephense** – PHP IntelliSense
- **ESLint** – JavaScript linting
- **Prettier** – Code formatting
- **markdownlint** – Markdown linting
- **YAML** – YAML support (rules, workflows)

## When to Use What

| When | Command |
|------|---------|
| Before commit | Husky runs automatically on staged JS (ESLint, Prettier) |
| Before PR | `make validate` |
| Before deploy | `make pre-deploy` or `npm run pre-deploy` |
| Rule changes | `make rules` |

## Troubleshooting

- **Composer not in PATH:** Use `php composer.phar` or ensure Composer is installed. CI uses `shivammathur/setup-php` with `tools: composer`.
- **PHPStan memory:** Run with `--memory-limit=512M` (default in `composer phpstan`).
- **check-links on .cursor/rules:** Some `.mdc` files may have internal links that fail. Fix incrementally or run `check-links:rules` separately.
- **Lighthouse CI:** Requires `LHCI_GITHUB_APP_TOKEN` for upload. Local: `npm run lighthouse` uses `lighthouserc.js`.

## CSS Audit and Optimization

- **Audit script:** `npm run css:audit` (or `python3 v2/scripts/dev-helpers/css-audit-analyzer.py`) – Lists CSS files with sizes, maps to pages, detects selector overlap
- **Output:** `docs/systems/css/CSS_AUDIT_REPORT.json`, `CSS_AUDIT_REPORT.md`
- **Blog CSS test:** `php v2/scripts/blog/test-css-visual.php [base_url]` – Verifies blog CSS loads on index, category, post
- **Coverage:** `npm run css:coverage` (Playwright) → `docs/systems/css/CSS_COVERAGE_RESULTS.json`; `python3 v2/scripts/dev-helpers/css-coverage-analyzer.py` → `CSS_OPTIMIZATION_RECOMMENDATIONS.md`. See `docs/systems/css/CSS_COVERAGE_REPORT.md` for representative URLs and manual Chrome DevTools steps
- **Architecture:** `docs/systems/css/CSS_ARCHITECTURE.md` – Shared vs page-specific, build flow
- **Rule:** `.cursor/rules/css-optimization.mdc` – When to add shared vs page-specific, design tokens, minification
- **Pre-deploy (optional):** Run `npm run css:audit` before deploy to verify CSS sizes and mapping

## Lead Capture Copy Validation

- **Script:** `php v2/scripts/blog/verify-lead-capture-copy.php --strict`
- **Purpose:** Ensures no blog post lead capture copy uses the generic phrase "dir bei diesem Thema hilft"
- **Runs in:** `make validate`
- **Audit:** `php v2/scripts/blog/audit-repetitive-lead-capture-copy.php` – lists posts with generic patterns
- **Docs:** `docs/systems/lead-capture/COPY_MANAGEMENT.md`, `docs/systems/lead-capture/LEAD_CAPTURE_COPY_BEST_PRACTICES.md`

## Cache Busting Validation

- **Script:** `python3 v2/scripts/dev-helpers/audit-unversioned-assets.py`
- **Purpose:** Finds CSS/JS includes without version parameters (`?v=`) that could cause caching issues
- **Scans:** `v2/base/`, `v2/pages/`, `v2/components/` directories
- **Output:** Lists files with unversioned includes, line numbers, and URLs
- **Pre-deploy:** Run before deployment to ensure all assets have cache busting
- **Docs:** `docs/development/CACHE_BUSTING_GUIDE.md` (see cache busting guide)

## Related

- `docs/ai/cursor-playbook.md` – MCP prompts, validation workflows
- `docs/development/MCP_INTEGRATION.md` – MCP setup
- `docs/development/PRODUCTION_DEPLOYMENT_CHECKLIST.md` – Pre-deploy checks
