// acceptancePatternsExtended.ts — S-P3-1: split da acceptancePatterns.ts // Sezioni: GraphQL / Subscriptions, WebSocket / Real-time, Pagination, // i18n / Localization, E-commerce / Cart, Social features, // Map / Geolocation, Calendar / Booking, OAuth / Social Login, // Caching / Redis, CLI / Command-line import type { GoalPattern } from './acceptancePatternsCore'; export const EXTENDED_PATTERNS: GoalPattern[] = [ // ── S412: GraphQL / Subscriptions ─────────────────────────────────────────── { re: /graphql|apollo|subscri(?:ption|zioni)|gql|query.*mutation|mutation.*query/i, tests: [ { requirement: "Schema GraphQL o client Apollo presente", critical: true, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasSchema = /type Query|type Mutation|type Subscription|typeDefs|gql\`|makeExecutableSchema/i.test(allCode); const hasClient = /ApolloClient|useQuery|useMutation|useSubscription|graphql-request/i.test(allCode); const hasFile = files.some(f => /\\.graphql$|\\.gql$|schema\\.(ts|js)|resolvers/i.test(f.path)); const pass = hasSchema || hasClient || hasFile; return { pass, detail: pass ? "GraphQL trovato: " + [hasSchema && "schema/typeDefs", hasClient && "Apollo client/hooks", hasFile && "file .graphql"].filter(Boolean).join(", ") : "Manca schema GraphQL, Apollo client o file .graphql/.gql" }; `, }, ], }, // ── S412: WebSocket / Real-time / Chat ────────────────────────────────────── { re: /websocket|socket\.io|real.?time|live.*(?:chat|update|feed)|pusher|ably|supabase.*realtime|broadcast/i, tests: [ { requirement: "Connessione real-time / WebSocket implementata", critical: true, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasWS = /new WebSocket|socket\.io|io\(|useSocket|ws\\.on|ws\\.emit/i.test(allCode); const hasPusher = /pusher|ably|centrifuge|phoenix\.socket/i.test(allCode); const hasRealtime= /supabase.*realtime|\.on\\(.*message|\.subscribe\\(.*channel/i.test(allCode); const pass = hasWS || hasPusher || hasRealtime; return { pass, detail: pass ? "Real-time trovato: " + [hasWS && "WebSocket/socket.io", hasPusher && "Pusher/Ably", hasRealtime && "Supabase Realtime"].filter(Boolean).join(", ") : "Manca connessione WebSocket, socket.io, Pusher o Supabase Realtime" }; `, }, ], }, // ── S412: Pagination / Infinite Scroll ────────────────────────────────────── { re: /paginaz|pagination|pagina.*\d+|infinite.*scroll|load.*more|cursor.*based|offset.*limit/i, tests: [ { requirement: "Sistema paginazione implementato", critical: true, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasOffset = /offset|skip|LIMIT.*OFFSET|page.*size|pageSize|per_page/i.test(allCode); const hasCursor = /cursor|after|before|hasNextPage|endCursor|nextCursor/i.test(allCode); const hasInfinite= /IntersectionObserver|useInfiniteQuery|loadMore|fetchNextPage|onEndReached/i.test(allCode); const pass = hasOffset || hasCursor || hasInfinite; return { pass, detail: pass ? "Paginazione trovata: " + [hasOffset && "offset/limit", hasCursor && "cursor-based", hasInfinite && "infinite scroll"].filter(Boolean).join(", ") : "Manca paginazione (nessun offset/limit, cursor, useInfiniteQuery o IntersectionObserver)" }; `, }, ], }, // ── S412: i18n / Localization / Multi-language ────────────────────────────── { re: /i18n|internazionalizzaz|localizz|multi.?language|multilingual|traduzion|react.?intl|next.?intl|i18next/i, tests: [ { requirement: "Sistema internazionalizzazione (i18n) presente", critical: true, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasLib = /i18next|react-intl|next-intl|lingui|vue-i18n|python-babel|gettext/i.test(allCode); const hasT = /useTranslation|t\\("|__\\("|_\\("|formatMessage| /locale|translations?|messages?|lang/.test(f.path) && /\\.(?:json|yaml|po|mo)$/.test(f.path)); const pass = (hasLib || hasT) && hasLocale; return { pass, detail: pass ? "i18n trovato: " + [hasLib && "libreria i18n", hasT && "hook t()/useTranslation", hasLocale && "file traduzioni"].filter(Boolean).join(", ") : "Manca: " + (!hasLib && !hasT ? "libreria i18n o hook t(); " : "") + (!hasLocale ? "file di traduzione (.json/.yaml/.po)" : "") }; `, }, ], }, // ── S412: E-commerce / Cart / Orders ──────────────────────────────────────── { re: /e.?commerce|negozio.*online|shop|carrello|cart|ordini?|checkout|acquisto|prodotto.*prezzo|catalogo.*prodotti/i, tests: [ { requirement: "Carrello acquisti implementato", critical: true, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasCart = /cart|carrello|basket|shopping_cart|addToCart|removeFromCart/i.test(allCode); const hasOrder = /order|ordine|checkout|purchase|acquisto/i.test(allCode); const hasPrice = /price|prezzo|cost|costo|amount|total|subtotal/i.test(allCode); const pass = hasCart && hasOrder && hasPrice; return { pass, detail: pass ? "E-commerce trovato: carrello + ordini + prezzi" : "Manca: " + (!hasCart ? "gestione carrello; " : "") + (!hasOrder ? "gestione ordini; " : "") + (!hasPrice ? "campo prezzo/total" : "") }; `, }, { requirement: "Stato carrello persiste (store o DB)", critical: false, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasStore = /useCart|cartStore|cartContext|useShoppingCart|localStorage.*cart|sessionStorage.*cart/i.test(allCode); const hasDB = /cart.*table|orders.*table|supabase.*cart|insert.*cart/i.test(allCode); return { pass: hasStore || hasDB, detail: (hasStore || hasDB) ? "Persistenza carrello trovata: " + [hasStore && "store/context", hasDB && "DB"].filter(Boolean).join(", ") : "Carrello non persiste (manca store, localStorage o DB)" }; `, }, ], }, // ── S412: Social features / Comments / Likes ──────────────────────────────── { re: /commenti?|comment|like|reazione|reaction|follow|follower|condividi|share|social.*feature|feed.*social/i, tests: [ { requirement: "Feature social (commenti / like / follow) implementata", critical: true, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasComment = /comment|commento|reply|risposta/i.test(allCode); const hasLike = /like|unlike|reaction|upvote|downvote|heart/i.test(allCode); const hasFollow = /follow|unfollow|subscribe|following|follower/i.test(allCode); const pass = hasComment || hasLike || hasFollow; return { pass, detail: pass ? "Social trovato: " + [hasComment && "commenti", hasLike && "like/reaction", hasFollow && "follow"].filter(Boolean).join(", ") : "Manca: commenti, like/reaction e follow" }; `, }, { requirement: "Tabella commenti o likes nel DB", critical: false, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasTable = /create table.*comment|comments.*table|likes.*table|reactions.*table|comment.*schema|like.*schema/i.test(allCode); return { pass: hasTable, detail: hasTable ? "Schema DB commenti/likes trovato" : "Manca schema DB per commenti/likes" }; `, }, ], }, // ── S412: Map / Geolocation ───────────────────────────────────────────────── { re: /mappa|map(?:box|libre)?|google.*maps|leaflet|geolocalizzaz|coordinate|gps.*track|marker|geocod/i, tests: [ { requirement: "Componente mappa / geolocalizzazione presente", critical: true, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasLib = /leaflet|mapbox|google.*maps|maplibre|react-map-gl|folium|geopandas/i.test(allCode); const hasCoords = /latitude|longitude|lat(?:itude)?\\s*:|lng\\s*:|coordinates|LatLng/i.test(allCode); const hasGeo = /navigator\\.geolocation|getCurrentPosition|watchPosition|geocode/i.test(allCode); const pass = hasLib || (hasCoords && hasGeo); return { pass, detail: pass ? "Mappa trovata: " + [hasLib && "libreria mappa", hasCoords && "coordinate", hasGeo && "geolocalizzazione"].filter(Boolean).join(", ") : "Manca libreria mappa (Leaflet/Mapbox/Google Maps) o navigator.geolocation" }; `, }, ], }, // ── S412: Calendar / Booking / Scheduling ─────────────────────────────────── { re: /calendario?|calendar|prenotaz|booking|appuntament|schedul|event.*date|date.*picker|slot.*orar/i, tests: [ { requirement: "Sistema calendario / prenotazioni implementato", critical: true, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasCal = /FullCalendar|react-calendar|date-fns|dayjs|moment|Calendar|DatePicker/i.test(allCode); const hasEvent = /event|evento|appuntamento|appointment|booking|prenotazione|slot/i.test(allCode); const hasDate = /start_date|end_date|startDate|endDate|date_from|date_to|scheduled_at/i.test(allCode); const pass = (hasCal || hasEvent) && hasDate; return { pass, detail: pass ? "Calendario trovato: " + [hasCal && "libreria calendario", hasEvent && "eventi/prenotazioni", hasDate && "date start/end"].filter(Boolean).join(", ") : "Manca: " + (!hasCal && !hasEvent ? "componente calendario; " : "") + (!hasDate ? "campi data start/end" : "") }; `, }, ], }, // ── S412: OAuth / Social Login ────────────────────────────────────────────── { re: /oauth|google.*sign.?in|sign.?in.*google|github.*auth|login.*(?:google|github|facebook|apple|microsoft)|sso|openid/i, tests: [ { requirement: "OAuth / social login implementato", critical: true, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasOAuth = /oauth|openid|supabase.*auth.*provider|next-auth|passport\\.use|google.*strategy|github.*strategy/i.test(allCode); const hasButton = /LoginWithGoogle|SignInWithGithub|OAuthButton|SocialLogin|provider.*google|provider.*github/i.test(allCode); const hasConfig = /GOOGLE_CLIENT_ID|GITHUB_CLIENT_ID|OAUTH_CLIENT|AUTH_PROVIDER|clientId.*google/i.test(allCode); const pass = hasOAuth && (hasButton || hasConfig); return { pass, detail: pass ? "OAuth trovato: strategia/provider + configurazione client" : "Manca: " + (!hasOAuth ? "libreria OAuth (next-auth/passport/supabase auth); " : "") + (!hasButton && !hasConfig ? "pulsante login o config CLIENT_ID" : "") }; `, }, ], }, // ── S412: Caching / Redis / Memoization ──────────────────────────────────── { re: /cach(?:e|ing)|redis|memcach|cdn.*cache|stale.?while.?revalidate|memoiz|lru.*cache|cache.*invalidat/i, tests: [ { requirement: "Strategia di caching implementata", critical: true, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasRedis = /redis|ioredis|upstash|Redis\\.createClient/i.test(allCode); const hasMemo = /useMemo|useCallback|React\\.memo|lru-cache|memoize|functools\\.lru_cache/i.test(allCode); const hasHTTP = /Cache-Control|ETag|stale-while-revalidate|max-age=|s-maxage/i.test(allCode); const hasNext = /revalidate|unstable_cache|next.*cache|SWR|useQuery.*staleTime/i.test(allCode); const pass = hasRedis || hasMemo || hasHTTP || hasNext; return { pass, detail: pass ? "Caching trovato: " + [hasRedis && "Redis/Upstash", hasMemo && "memoization", hasHTTP && "HTTP Cache-Control", hasNext && "SWR/React Query/Next cache"].filter(Boolean).join(", ") : "Nessuna strategia di caching trovata (Redis, useMemo, Cache-Control, SWR)" }; `, }, ], }, // ── S412: CLI / Command-line tool ─────────────────────────────────────────── { re: /cli|command.?line|terminal.*app|console.*app|argparse|commander|typer|click.*python|yargs/i, tests: [ { requirement: "CLI / tool a riga di comando implementato", critical: true, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasArgs = /argparse|ArgumentParser|commander|yargs|typer|click|process\\.argv|sys\\.argv/i.test(allCode); const hasBin = files.some(f => f.path.includes("bin/") || f.path.endsWith("/cli.ts") || f.path.endsWith("/cli.js") || f.path.endsWith("/cli.py")); const hasMain = /if\\s+__name__.*main|cli\\(\\)|program\\.parse|app\\(\\)/i.test(allCode); const pass = hasArgs || hasBin || hasMain; return { pass, detail: pass ? "CLI trovato: " + [hasArgs && "parser argomenti", hasBin && "file bin/cli", hasMain && "entry point main"].filter(Boolean).join(", ") : "Manca: argparse/commander/yargs, file bin/ o entry point CLI" }; `, }, ], }, ];