package main import ( "os" "path/filepath" "regexp" "strings" ) type frontendViolation struct { file string line int rule string snippet string } // Future enhancement (FUTURE-WO-012-ENFORCE): Add a lint rule that requires // every string literal in a JSX text node to be wrapped in t(). This was // intentionally omitted from WO-012 to avoid breaking all in-flight WOs that // have untranslated strings. The scaffolding (i18next init, en.json catalog, // t() + useTranslation exports) was shipped in WO-012; strict enforcement is // deferred to a follow-up work order. // Files/dirs where fetch() and manual clients are allowed. // Each entry has a documented reason — add new entries only with justification. var allowedFrontendFiles = map[string]bool{ "lib/transport.ts": true, // Global transport setup "lib/api/transport.ts": true, // Orchestrator global transport setup "lib/token-refresh.ts": true, // Auth bootstrap client (avoids interceptor loop) "lib/sync/sync-engine.ts": true, // Jotai sync — lives outside React, can't use hooks "components/media-library/media-upload-area.tsx": true, // Multipart file upload via FormData "components/html-editor/preview-iframe.tsx": true, // Block HTML preview (returns HTML, not proto) "lib/editor-bridge/iframe-script.ts": true, // Editor iframe preview "player/human-proof/index.ts": true, // Standalone player bundle — no React, fetches public REST endpoint // createClient exceptions — each has a technical reason why useQuery/useMutation can't be used: // use-entity-search fans one debounced query out to 10 RPCs across 8 services // via useQueries (single combined loading state + result blending for the // command palette); generated per-RPC hooks can't express this without 10 // independent hook instances and manual aggregation. "hooks/use-entity-search.ts": true, "routes/admin/plugins.tsx": true, // Server-streaming (for await...of) — useMutation doesn't support streaming "hooks/use-restart-operation.ts": true, // Server-streaming + raw Connect envelope for cross-origin orchestrator stream "routes/admin/menus.tsx": true, // Imperative async callback in useCallback "components/data-platform/tables/creation-wizard.tsx": true, // useMutation has serialization bug with oneof fields // SiteAgentService.SendMessage is a Connect server-streaming RPC consumed // with `for await` — generated Connect Query hooks are unary-only and // cannot express a streaming turn (same reason as use-restart-operation). "components/site-agent/use-site-agent-stream.ts": true, // Registry browse/detail call the ORCHESTRATOR's public Connect endpoints // (PluginRegistryService/PluginReviewService) on a different origin. The // CMS transport and generated hooks only cover the CMS's own proto surface, // so these are plain JSON POSTs to the registry URL from GetAuthConfig // (same reason routes/admin/plugins.tsx carried before the extraction). "components/plugins/browse-registry-tab.tsx": true, "components/plugins/registry-detail.tsx": true, } // Plugin web files that are allowed to call plugin-owned REST handlers because // there is no ConnectRPC surface for them yet. Keep this list narrow. var allowedPluginRESTFiles = map[string]bool{ // Suffix-matched (see pluginRESTFileAllowed), so these cover calcomblock both // as the standalone plugin repo (plugins/calcomblock/web/...) and the legacy // bundled path (backend/internal/plugins/calcomblock/web/...). "plugins/calcomblock/web/settings.tsx": true, // Plugin settings/test endpoints are mounted via HTTPHandler; no generated hooks exist yet "plugins/calcomblock/web/editor.tsx": true, // Block editor reads plugin /settings + /event-types via HTTPHandler routes } // Specific fetch paths that are non-proto REST endpoints (no ConnectRPC equivalent) // These get a WARN, not a FAIL var knownNonProtoFetches = map[string]bool{ "/api/plugins/": true, // Plugin REST APIs "/api/mcp/": true, // MCP device auth flow "/api/lists/subscribe": true, // Public subscribe endpoint "/api/helpdesk/": true, // Helpdesk attachment multipart upload/download (ConnectRPC doesn't support multipart) "/preview/": true, // Preview HTML endpoints "/api/ai/chat/stream": true, // AI chat SSE streaming (EventSource/fetch — ConnectRPC doesn't support SSE) "/api/support/": true, // CMS helpdesk widget: multipart attachment upload to the Chi proxy — connect-query can't send FormData "/api/helpdesk/upload": true, // Orchestrator helpdesk: multipart attachment upload — same FormData limitation "/.well-known/skills/": true, // Public well-known skill discovery index (instance-derived skill name) — no proto service } var ( // Anti-pattern: importing axios reAxios = regexp.MustCompile(`(?:import|require).*['"]axios['"]`) // Anti-pattern: useQueryClient (should use refetch() instead) reUseQueryClient = regexp.MustCompile(`useQueryClient\s*\(`) // Anti-pattern in plugins: QueryClientProvider is already mounted by core reQueryClientProvider = regexp.MustCompile(`\bQueryClientProvider\b`) // Anti-pattern: createClient from connectrpc (should use generated hooks) reCreateClient = regexp.MustCompile(`createClient\s*\(`) // Anti-pattern: fetch() to /api/ or proto paths (should use ConnectRPC) reFetchAPI = regexp.MustCompile(`fetch\s*\(\s*['\x60"/]`) // Anti-pattern: createConnectTransport (only allowed in transport.ts) reCreateTransport = regexp.MustCompile(`createConnectTransport\s*\(`) // Anti-pattern: new XMLHttpRequest reXHR = regexp.MustCompile(`new\s+XMLHttpRequest`) // Anti-pattern: importing from @connectrpc/connect-web directly to create clients reConnectWebImport = regexp.MustCompile(`from\s+['"]@connectrpc/connect-web['"]`) // Anti-pattern: browser confirm() instead of the shared confirm dialog reWindowConfirm = regexp.MustCompile(`\bwindow\.confirm\s*\(`) reBareConfirm = regexp.MustCompile(`(?:^|[^\w.])confirm\s*\(`) // Anti-pattern: raw