{"_id":"@cool-ai/eslint-plugin-beach","_rev":"5-39dbedb92a0f9531fd2abfd8f2b4b1fe","name":"@cool-ai/eslint-plugin-beach","dist-tags":{"latest":"0.4.0"},"versions":{"0.1.1":{"name":"@cool-ai/eslint-plugin-beach","version":"0.1.1","license":"Apache-2.0","_id":"@cool-ai/eslint-plugin-beach@0.1.1","maintainers":[{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"}],"homepage":"https://cool-ai.org","bugs":{"url":"https://gitlab.com/johncandrew/beach/-/issues"},"dist":{"shasum":"234aec2c8e068546f4add7ada473a5fcd307fe8a","tarball":"https://registry.npmjs.org/@cool-ai/eslint-plugin-beach/-/eslint-plugin-beach-0.1.1.tgz","fileCount":43,"integrity":"sha512-FnO2XmXyubiGZ/vA3Lc0jiVDYIt413QzUIUOeNEVDF7gpfdTZFjMBFYJEyxrHN6RXIAxfg5R4lxufoiVR+R9OA==","signatures":[{"sig":"MEUCIGm0DSW8MFSf5GnWNRlfK6k30SnJpJLB6HL+qC3FLduDAiEA7qUgJW3i2I+gN6QyXjZs717RhUi7Lga0JhlvuIAl11M=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":58288},"type":"module","_from":"file:cool-ai-eslint-plugin-beach-0.1.1.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"tsc --project tsconfig.json --watch","test":"vitest run","build":"tsc --project tsconfig.json","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"},"_resolved":"/tmp/0ec52412a84215476b64d7472f01946b/cool-ai-eslint-plugin-beach-0.1.1.tgz","_integrity":"sha512-FnO2XmXyubiGZ/vA3Lc0jiVDYIt413QzUIUOeNEVDF7gpfdTZFjMBFYJEyxrHN6RXIAxfg5R4lxufoiVR+R9OA==","repository":{"url":"git+https://gitlab.com/johncandrew/beach.git","type":"git","directory":"packages/eslint-plugin-beach"},"_npmVersion":"10.9.7","description":"ESLint rules that enforce Beach's architectural commitments — channel-blind interior, no geometry tokens in prompts, escalated-bypass justification.","directories":{},"_nodeVersion":"22.22.2","dependencies":{"@typescript-eslint/utils":"^8.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.0.0","vitest":"^2.0.0","typescript":"^5.5.0","@types/node":"^20.0.0","@types/estree":"^1.0.0"},"peerDependencies":{"eslint":"^9.0.0"},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-beach_0.1.1_1778065310269_0.07643931000621818","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@cool-ai/eslint-plugin-beach","version":"0.2.0","license":"Apache-2.0","_id":"@cool-ai/eslint-plugin-beach@0.2.0","maintainers":[{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"}],"homepage":"https://cool-ai.org","bugs":{"url":"https://gitlab.com/johncandrew/beach/-/issues"},"dist":{"shasum":"d14612a8d5df17442677e990d9a113b953c16997","tarball":"https://registry.npmjs.org/@cool-ai/eslint-plugin-beach/-/eslint-plugin-beach-0.2.0.tgz","fileCount":95,"integrity":"sha512-ZHuQ1C02R0vSWw7FSVt/XAMlTlbdYCyL9m3oOsjiIwErQLPFXBw63APoUaDrqdN9tySFms0Kt8EkUJjIw52OfA==","signatures":[{"sig":"MEUCIQDOdbMA63et2YdNoN70QqzDyOUs93RCN6HNPqR52xgtXQIgNv8D9vLxmIr/BCL6rS+HSop4EijNarqneHL1JCgxpQw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":110949},"type":"module","_from":"file:cool-ai-eslint-plugin-beach-0.2.0.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"tsc --project tsconfig.json --watch","test":"vitest run","build":"tsc --project tsconfig.json","coverage":"vitest run --coverage","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"},"_resolved":"/tmp/48e82525118644a3548becca2e51d512/cool-ai-eslint-plugin-beach-0.2.0.tgz","_integrity":"sha512-ZHuQ1C02R0vSWw7FSVt/XAMlTlbdYCyL9m3oOsjiIwErQLPFXBw63APoUaDrqdN9tySFms0Kt8EkUJjIw52OfA==","repository":{"url":"git+https://gitlab.com/johncandrew/beach.git","type":"git","directory":"packages/eslint-plugin-beach"},"_npmVersion":"10.9.7","description":"ESLint rules that enforce Beach's architectural commitments — channel-blind interior, no geometry tokens in prompts, escalated-bypass justification.","directories":{},"_nodeVersion":"22.22.2","dependencies":{"@typescript-eslint/utils":"^8.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.0.0","vitest":"^2.0.0","typescript":"^5.5.0","@types/node":"^20.0.0","@types/estree":"^1.0.0"},"peerDependencies":{"eslint":"^9.0.0"},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-beach_0.2.0_1778459520888_0.3275102364673965","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@cool-ai/eslint-plugin-beach","version":"0.2.1","license":"Apache-2.0","_id":"@cool-ai/eslint-plugin-beach@0.2.1","maintainers":[{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"}],"homepage":"https://cool-ai.org","bugs":{"url":"https://gitlab.com/johncandrew/beach/-/issues"},"dist":{"shasum":"f56ad95b4e05a13e91251370752dd35159a967c0","tarball":"https://registry.npmjs.org/@cool-ai/eslint-plugin-beach/-/eslint-plugin-beach-0.2.1.tgz","fileCount":95,"integrity":"sha512-hG1wfz1rYXD2ZM5Mn6J3mHhnH0Fd3tNjTtBBbqrKzMLGornD1zSlDLKZ8ER8fS4vuRn3WY7kdxWzfULE1HQMSQ==","signatures":[{"sig":"MEYCIQCHxLURwZ5lM+9i+AS99KuW/1hpgEDvDdNB/ssw/Rok/QIhAIB5vVc3G7O7fvLGSAhxAsqILlsn8pJEmNl3M2mhMSC/","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":111960},"type":"module","_from":"file:cool-ai-eslint-plugin-beach-0.2.1.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"tsc --project tsconfig.json --watch","test":"vitest run","build":"tsc --project tsconfig.json","coverage":"vitest run --coverage","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"},"_resolved":"/tmp/0b000076659d51edbf578c18e748ee66/cool-ai-eslint-plugin-beach-0.2.1.tgz","_integrity":"sha512-hG1wfz1rYXD2ZM5Mn6J3mHhnH0Fd3tNjTtBBbqrKzMLGornD1zSlDLKZ8ER8fS4vuRn3WY7kdxWzfULE1HQMSQ==","repository":{"url":"git+https://gitlab.com/johncandrew/beach.git","type":"git","directory":"packages/eslint-plugin-beach"},"_npmVersion":"10.9.7","description":"ESLint rules that enforce Beach's architectural commitments — channel-blind interior, no geometry tokens in prompts, escalated-bypass justification.","directories":{},"_nodeVersion":"22.22.2","dependencies":{"@typescript-eslint/utils":"^8.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.0.0","vitest":"^2.0.0","typescript":"^5.5.0","@types/node":"^20.0.0","@types/estree":"^1.0.0"},"peerDependencies":{"eslint":"^9.0.0"},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-beach_0.2.1_1778466315460_0.9900766510869148","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@cool-ai/eslint-plugin-beach","version":"0.3.0","license":"Apache-2.0","_id":"@cool-ai/eslint-plugin-beach@0.3.0","maintainers":[{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"}],"homepage":"https://cool-ai.org","bugs":{"url":"https://gitlab.com/johncandrew/beach/-/issues"},"dist":{"shasum":"0db11775695129b9646d61e327ad3c3bbd83f7e6","tarball":"https://registry.npmjs.org/@cool-ai/eslint-plugin-beach/-/eslint-plugin-beach-0.3.0.tgz","fileCount":95,"integrity":"sha512-g2qlhzGuE0/QcrEhOD6vnkxJqTSwSq62Jkiusdp0XkkUjlbEf7AqJI1raWbnADxqy4zOpTodtph/Qk6Xc3THtA==","signatures":[{"sig":"MEYCIQDQRKtrVUu/qVLPXibXU6WDFCmg85QGoLsUxR4K7CbBXwIhAI9dd3a0CAEq9Yj6hAd4Kft4tmerMphBDWdHoGWXsF0K","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":115257},"type":"module","_from":"file:cool-ai-eslint-plugin-beach-0.3.0.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"tsc --project tsconfig.json --watch","test":"vitest run","build":"tsc --project tsconfig.json","coverage":"vitest run --coverage","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"},"_resolved":"/tmp/421c6f58a54bb699de355bfef9f63c2f/cool-ai-eslint-plugin-beach-0.3.0.tgz","_integrity":"sha512-g2qlhzGuE0/QcrEhOD6vnkxJqTSwSq62Jkiusdp0XkkUjlbEf7AqJI1raWbnADxqy4zOpTodtph/Qk6Xc3THtA==","repository":{"url":"git+https://gitlab.com/johncandrew/beach.git","type":"git","directory":"packages/eslint-plugin-beach"},"_npmVersion":"10.9.8","description":"ESLint rules that enforce Beach's architectural commitments — channel-blind interior, no geometry tokens in prompts, escalated-bypass justification.","directories":{},"_nodeVersion":"22.22.3","dependencies":{"@typescript-eslint/utils":"^8.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.0.0","vitest":"^2.0.0","typescript":"^5.5.0","@types/node":"^20.0.0","@types/estree":"^1.0.0"},"peerDependencies":{"eslint":"^9.0.0"},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-beach_0.3.0_1780572515745_0.9840923379909474","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@cool-ai/eslint-plugin-beach","version":"0.4.0","description":"ESLint rules that enforce Beach's architectural commitments — channel-blind interior, no geometry tokens in prompts, escalated-bypass justification.","license":"Apache-2.0","repository":{"type":"git","url":"git+https://gitlab.com/johncandrew/beach.git","directory":"packages/eslint-plugin-beach"},"homepage":"https://cool-ai.org","bugs":{"url":"https://gitlab.com/johncandrew/beach/-/issues"},"type":"module","exports":{".":{"import":"./dist/index.js","types":"./dist/index.d.ts"}},"publishConfig":{"access":"public"},"devDependencies":{"@types/estree":"^1.0.0","@types/node":"^20.0.0","eslint":"^9.0.0","typescript":"^5.5.0","vitest":"^2.0.0"},"peerDependencies":{"eslint":"^9.0.0"},"dependencies":{"@typescript-eslint/utils":"^8.0.0"},"scripts":{"build":"tsc --project tsconfig.json","dev":"tsc --project tsconfig.json --watch","test":"vitest run","coverage":"vitest run --coverage","test:watch":"vitest","typecheck":"tsc --noEmit"},"_id":"@cool-ai/eslint-plugin-beach@0.4.0","_integrity":"sha512-A08FA3j8CJNlUYwTeg6Nw/NkfYqNj9Spb/4UGxLtKH4cBrVj3gpJuPk7lT4sZq+1n/9gUh7iGGZyFtoF4ALOsA==","_resolved":"/tmp/9c339d67cf2a03ca249eb1f75f87dbe5/cool-ai-eslint-plugin-beach-0.4.0.tgz","_from":"file:cool-ai-eslint-plugin-beach-0.4.0.tgz","_nodeVersion":"22.22.3","_npmVersion":"10.9.8","dist":{"integrity":"sha512-A08FA3j8CJNlUYwTeg6Nw/NkfYqNj9Spb/4UGxLtKH4cBrVj3gpJuPk7lT4sZq+1n/9gUh7iGGZyFtoF4ALOsA==","shasum":"cf81d73caeb84900ebd25159ba22705a652bc5d8","tarball":"https://registry.npmjs.org/@cool-ai/eslint-plugin-beach/-/eslint-plugin-beach-0.4.0.tgz","fileCount":95,"unpackedSize":115357,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIGIyN1ohFGo5RmmjSrszhqDAv5TgfafnG+Zf9iXvq+IkAiAfoW2I8656PBQ3ASkPQt+RKe1sjqwEE+oov0uFA2rMww=="}]},"_npmUser":{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"},"directories":{},"maintainers":[{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/eslint-plugin-beach_0.4.0_1780677385637_0.3273624870202192"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-06T11:01:50.118Z","modified":"2026-06-05T16:36:25.921Z","0.1.1":"2026-05-06T11:01:50.439Z","0.2.0":"2026-05-11T00:32:01.028Z","0.2.1":"2026-05-11T02:25:15.631Z","0.3.0":"2026-06-04T11:28:36.012Z","0.4.0":"2026-06-05T16:36:25.797Z"},"bugs":{"url":"https://gitlab.com/johncandrew/beach/-/issues"},"license":"Apache-2.0","homepage":"https://cool-ai.org","repository":{"type":"git","url":"git+https://gitlab.com/johncandrew/beach.git","directory":"packages/eslint-plugin-beach"},"description":"ESLint rules that enforce Beach's architectural commitments — channel-blind interior, no geometry tokens in prompts, escalated-bypass justification.","maintainers":[{"name":"johncandrew","email":"johncandrew@yahoo.co.uk"}],"readme":"# @cool-ai/eslint-plugin-beach\n\nESLint rules that enforce Beach's architectural commitments. The rules are mechanical guardrails for the design rules consumers absorb when they integrate Beach — channel-blind interior, surface-agnostic prompts, escalated-bypass justification.\n\n> **Home:** [cool-ai.org](https://cool-ai.org) · **Documentation:** [cool-ai.org/docs](https://cool-ai.org/docs)\n\n## Install\n\n```sh\nnpm install --save-dev @cool-ai/eslint-plugin-beach eslint\n```\n\nESLint v9 (flat config) is required.\n\n## Use the recommended preset\n\n```js\n// eslint.config.js\nimport beach from '@cool-ai/eslint-plugin-beach';\n\nexport default [\n  beach.configs.recommended,\n];\n```\n\nThe preset enables all four rules at `error`. Each rule can also be configured individually if you want to opt in selectively, override defaults, or downgrade severity:\n\n```js\nimport beach from '@cool-ai/eslint-plugin-beach';\n\nexport default [\n  {\n    plugins: { beach },\n    rules: {\n      'beach/no-channel-mode-branching': 'error',\n      'beach/no-channel-types-in-non-adapter-packages': ['error', {\n        channelPackages: ['@cool-ai/beach-channel-whatsapp', /* ... */],\n        adapterPackagePatterns: ['/packages/channel-', '/packages/beach-channel-'],\n      }],\n      'beach/no-geometry-tokens-in-prompts': ['error', {\n        geometryTokens: ['above the fold', /* ... */],\n        promptIdentifiers: ['prompt', 'systemPrompt', /* ... */],\n      }],\n      'beach/escalated-bypass-required': ['error', {\n        bypassPropertyNames: ['bypass', 'rendererBypass'],\n      }],\n    },\n  },\n];\n```\n\n## The rules\n\n### `beach/no-channel-mode-branching`\n\nForbids branching on `session.channelMode` inside handler interior code. The interior is channel-blind by Beach's design.\n\nThe interior is channel-blind by contract. Variation across channels lives in destinations, manifest contributors, completion-trigger configuration, or filter rules — never in `if (channelMode === ...)` branches.\n\n```typescript\n// ✗ violates the rule\nif (session.channelMode === 'batched') { /* ... */ }\nswitch (session.channelMode) { case 'streaming': /* ... */ }\n\n// ✓ instead — express the variation in the wiring\nconst collector = config.channelMode === 'batched' ? new BatchedChannelCollector(...) : null;\n```\n\n### `beach/no-channel-types-in-non-adapter-packages`\n\nForbids importing channel-specific transport types (e.g. `WhatsAppMessage`, `EmailMessage`) in non-adapter packages. Adapter packages — those matching `packages/channel-*`, `packages/beach-channel-*`, `packages/beach-a2ui-renderer-*` — are the only places where channel identity is allowed to leak.\n\n```typescript\n// in packages/session/... — ✗ violates the rule\nimport { WhatsAppMessage } from '@cool-ai/beach-channel-whatsapp';\n\n// in packages/beach-channel-whatsapp/... — ✓ permitted\nimport { WhatsAppMessage } from '@cool-ai/beach-channel-whatsapp';\n```\n\nThe default `channelPackages` list covers the `@cool-ai/beach-channel-*` adaptors (email, whatsapp, sse, a2a, mcp, webhook, cron). Override via the rule's option to add custom channel packages.\n\n### `beach/no-geometry-tokens-in-prompts`\n\nForbids geometry / layout tokens (e.g. \"above the fold\", \"tap the button\", \"scroll down\") in string properties whose key looks like a prompt (`prompt`, `systemPrompt`, `instructions`, `persona`, etc.). The handler produces channel-blind, surface-agnostic content; geometry is expressed in composers, not prompts.\n\n```typescript\n// ✗ violates — the handler must not direct surface geometry\nconst config = { prompt: 'Tell the user to tap the button to confirm.' };\n\n// ✓ — surface-neutral instruction; the composer renders the call-to-action\nconst config = { prompt: 'Confirm the booking with the user.' };\n```\n\nThe default token list is conservative; consumers extend via the `geometryTokens` option.\n\n### `beach/escalated-bypass-required`\n\nRequires that any per-renderer surface-parity bypass declaration set `escalated: true` with a non-empty `reason`. A bypass without escalation indicates the contract was waived without an explicit decision — the rule fails CI to surface it for review.\n\n```typescript\n// ✗ — missing escalation\nconst cfg = { bypass: { reason: 'WhatsApp template constraint' } };\n\n// ✗ — escalated: false is not a valid bypass\nconst cfg = { bypass: { escalated: false, reason: 'temp workaround' } };\n\n// ✓ — explicit escalation + non-empty reason\nconst cfg = { bypass: { escalated: true, reason: 'WhatsApp template constraint forces flat layout' } };\n```\n\nThe default `bypassPropertyNames` covers `bypass`, `rendererBypass`, `surfaceBypass`, `parityBypass`. Override via the rule's option for custom property names.\n\n## Why this exists\n\nBeach's standing rules are absorbed by consumers when they integrate. Without mechanical enforcement, the rules drift. This plugin is the mechanical guardrail — every rule corresponds to a Beach architectural commitment, and CI catches the violation before it lands.\n\nThe plugin is published independently of `@cool-ai/beach-core` so consumers can adopt the rules at their own cadence; Beach's own code is checked by the same plugin via the precommit and prepush gates.\n\n## Licence\n\nApache 2.0 — see [LICENSE](../../LICENSE).\n","readmeFilename":"README.md"}