{
  "no-inline-css": [
    {
      "id": "no-wp-add-inline-style",
      "title": "wp_add_inline_style() call",
      "category": "convention",
      "severity": "critical",
      "pattern": "wp_add_inline_style\\s*\\(",
      "description": "Monorepo CLAUDE.md \u00a74 bans inline CSS: wp_add_inline_style() is forbidden — all styles belong in SCSS/CSS files.",
      "resolution": "Move the style into the plugin's SCSS build and enqueue the compiled stylesheet instead.",
      "excludeGlobs": [
        "**/EmailTemplates/**"
      ],
      "canary": "wp_add_inline_style('handle', $css);"
    },
    {
      "id": "no-inline-style-tag",
      "title": "<style> block",
      "category": "convention",
      "severity": "critical",
      "flags": "i",
      "pattern": "<style[\\s>]",
      "description": "Monorepo CLAUDE.md \u00a74 bans inline CSS: raw <style> blocks are forbidden outside transactional email markup.",
      "resolution": "Move the rules into a SCSS/CSS file. Email HTML (includes/Workflow/EmailTemplates/**) is the sole documented exception — mail clients require inline/embedded CSS.",
      "excludeGlobs": [
        "**/EmailTemplates/**"
      ],
      "canary": "<style>.my-class { color: red; }</style>"
    },
    {
      "id": "no-inline-style-attribute",
      "title": "style=\"...\" attribute",
      "category": "convention",
      "severity": "critical",
      "pattern": "\\bstyle\\s*=\\s*[\"']",
      "description": "Monorepo CLAUDE.md \u00a74 bans inline CSS: style=\"...\" attributes are forbidden outside transactional email markup.",
      "resolution": "Use a CSS class + SCSS rule, or a data attribute driven by CSS. Email HTML (includes/Workflow/EmailTemplates/**) is the sole documented exception — mail clients strip <style> and external stylesheets.",
      "excludeGlobs": [
        "**/EmailTemplates/**"
      ],
      "canary": "<div style=\"margin-top: 10px;\">"
    }
  ],
  "no-license-strings": [
    {
      "id": "no-license-identifier",
      "title": "\"license\"/\"licence\"-shaped identifier, path, or string",
      "category": "convention",
      "severity": "critical",
      "flags": "i",
      "pattern": "licen[sc]",
      "description": "Product decision: license keys are retired from this plugin (.claude/skills/backend-integration.md:118-119). No identifier, option name, path, or user-visible string may reference licensing — the plugin activates through the connect surface instead. Deliberately unanchored (no \\b) so it also catches identifiers like ipz_license_key or LICENSE_KEY where underscores suppress a word boundary.",
      "resolution": "Remove the license-shaped identifier/string. If this is legacy-option cleanup code (e.g. deleting a deprecated 'ipz_license_key' option), that call already lives at plugin root (international-press-zone.php), which is outside this rule's scanned roots (includes/, admin/) — it does not need an exception here.",
      "canary": "define('IPZ_LICENSE_KEY', get_option('license_key'));",
      "negativeCanary": [
        "// Author URI: https://press.zone"
      ]
    }
  ],
  "no-api-key-exposure": [
    {
      "id": "no-api-key-log-php",
      "title": "API key variable passed to a PHP logging/dump function",
      "category": "security",
      "severity": "critical",
      "pattern": "(?:error_log|var_dump|print_r|wp_die)\\s*\\([^)]*\\$\\w*[Aa]pi[_-]?[Kk]ey\\b",
      "description": "Product decision: the per-site API key must never be logged (.claude/skills/settings-management.md:207, api-integration.md:187).",
      "resolution": "Redact the key before logging (e.g. log only whether a key is present, or its last 4 characters) — never pass the raw key variable to error_log/var_dump/print_r/wp_die.",
      "canary": "error_log('Connect failed for key: ' . $api_key);"
    },
    {
      "id": "no-api-key-log-js",
      "title": "API key variable passed to a JS console function",
      "category": "security",
      "severity": "critical",
      "pattern": "console\\.(?:log|debug|info|warn|error)\\s*\\([^)]*\\b\\w*[Aa]pi[_-]?[Kk]ey\\b",
      "description": "Product decision: the per-site API key must never be logged (.claude/skills/settings-management.md:207, api-integration.md:187).",
      "resolution": "Remove the console call or log a redacted placeholder — never the raw key value.",
      "canary": "console.log('key', apiKey);"
    },
    {
      "id": "no-api-key-render-jsx",
      "title": "API key value interpolated into rendered JSX",
      "category": "security",
      "severity": "critical",
      "pattern": "\\{[^{}]*\\b\\w*[Aa]pi[_-]?[Kk]ey\\b[^{}]*\\}",
      "description": "Product decision: the per-site API key must never be rendered in the UI (.claude/skills/settings-management.md:56-68 — the connect flow never surfaces the raw key to the user).",
      "resolution": "Render connection status (connected/not connected) instead of the key value. If a masked/last-4 representation is genuinely needed, name the variable to make the masking obvious (e.g. apiKeyMasked) so this rule does not need weakening.",
      "excludeGlobs": [
        "**/*.md"
      ],
      "canary": "<span>{apiKey}</span>"
    }
  ]
}
