@vibecodiq/cli 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +12 -12
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2,23 +2,23 @@
2
2
  import{createHash as Cn}from"crypto";import Ts from"path";var Pe={id:"ARCH-01",name:"Business logic in domains/",module:"architecture",layer:"L1",priority:"P0",description:"Supabase/DB calls in pages or components indicate business logic outside domains/",fixCost:250,fixSize:"L",async run(e){let t=/supabase\.(from|auth|rpc|storage)\b|\.from\(\s*['"`]/,i=[],s=e.files.filter(n=>(n.includes("/pages/")||n.includes("/app/"))&&!n.includes("/api/")&&!n.includes("route.")&&!n.includes("layout.")&&!n.includes("globals.")&&(n.endsWith(".ts")||n.endsWith(".tsx")||n.endsWith(".js")||n.endsWith(".jsx"))),r=e.files.filter(n=>n.includes("/components/")&&(n.endsWith(".ts")||n.endsWith(".tsx")||n.endsWith(".js")||n.endsWith(".jsx")));for(let n of[...s,...r]){let o;try{o=await e.readFile(n)}catch{continue}t.test(o)&&i.push(n)}return i.length>0?{result:"FAIL",message:`Supabase/DB calls found in pages/components (${i.length} file${i.length>1?"s":""})`,evidence:i.slice(0,5)}:{result:"PASS",message:"No business logic detected in pages/components"}}};var we={id:"ARCH-02",name:"domains/ directory exists",module:"architecture",layer:"L1",priority:"P0",description:"Projects with >5 source files need a domains/ directory for organized business logic",fixCost:250,fixSize:"L",async run(e){let t=e.files.filter(r=>(r.endsWith(".ts")||r.endsWith(".tsx")||r.endsWith(".js")||r.endsWith(".jsx"))&&!r.includes("node_modules"));if(t.length<=5)return{result:"PASS",message:"Project is small \u2014 domains/ not yet required"};if(!e.files.some(r=>r.includes("domains/")||r.includes("src/domains/")))return{result:"FAIL",message:`${t.length} source files but no domains/ directory`,evidence:["Create domains/<domain>/<slice>/ for business logic"]};let s=new Set;for(let r of e.files){let n=r.match(/(?:src\/)?domains\/([^/]+)\//);n&&s.add(n[1])}return s.size===0?{result:"FAIL",message:"domains/ exists but is empty"}:{result:"PASS",message:`${s.size} domain${s.size>1?"s":""} found`}}};var Re={id:"ARCH-03",name:"No cross-domain imports",module:"architecture",layer:"L1",priority:"P1",description:"Files in one domain must not import from another domain \u2014 use shared/ for cross-domain communication",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(r=>r.includes("domains/"));if(t.length===0)return{result:"PASS",message:"No domains/ directory yet"};let i=new Set;for(let r of t){let n=r.match(/(?:src\/)?domains\/([^/]+)\//);n&&i.add(n[1])}let s=[];for(let r of t){if(!r.endsWith(".ts")&&!r.endsWith(".tsx")&&!r.endsWith(".js")&&!r.endsWith(".jsx"))continue;let n=r.match(/(?:src\/)?domains\/([^/]+)\//)?.[1];if(!n)continue;let o;try{o=await e.readFile(r)}catch{continue}for(let a of i){if(a===n)continue;new RegExp(`from.*domains/${a}|import.*domains/${a}`).test(o)&&s.push(`${r} \u2192 imports from ${a}/`)}}return s.length>0?{result:"FAIL",message:`${s.length} cross-domain import${s.length>1?"s":""} found`,evidence:s.slice(0,5)}:{result:"PASS",message:"No cross-domain imports detected"}}};var Le={id:"ARCH-04",name:"Pages are thin wrappers",module:"architecture",layer:"L1",priority:"P1",description:"Page files should be thin wrappers (<80 lines) that compose domain components",fixCost:250,fixSize:"L",async run(e){let t=e.files.filter(s=>(s.includes("/pages/")||s.match(/app\/.*\/page\.(ts|tsx|js|jsx)$/))&&(s.endsWith(".tsx")||s.endsWith(".jsx")));if(t.length===0)return{result:"PASS",message:"No page files found"};let i=[];for(let s of t){let r;try{r=await e.readFile(s)}catch{continue}let n=r.split(`
3
3
  `).length;n>80&&i.push(`${s} (${n} lines)`)}return i.length>0?{result:"FAIL",message:`${i.length} page${i.length>1?"s":""} over 80 lines \u2014 extract logic to domains/`,evidence:i.slice(0,5)}:{result:"PASS",message:`All ${t.length} pages are thin wrappers`}}};var Ne={id:"ARCH-05",name:"shared/ has no business logic",module:"architecture",layer:"L1",priority:"P1",description:"shared/ should contain only infrastructure (DB clients, auth helpers) \u2014 not domain-specific components",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>(n.includes("shared/")||n.includes("src/shared/"))&&(n.endsWith(".ts")||n.endsWith(".tsx")||n.endsWith(".js")||n.endsWith(".jsx")));if(t.length===0)return{result:"PASS",message:"No shared/ directory yet"};let i=/supabase\.(from|rpc)\(|\.insert\(|\.update\(|\.delete\(|\.select\(/,s=/TaskList|TaskForm|PricingCard|AdminUser|LoginForm|RegisterForm|InvoiceTable|OrderList|ProductCard/,r=[];for(let n of t){let o;try{o=await e.readFile(n)}catch{continue}s.test(o)?r.push(`${n} \u2014 business component name detected`):i.test(o)&&!n.includes("/db/")&&!n.includes("/auth/")&&!n.includes("/billing/")&&r.push(`${n} \u2014 direct DB operations outside infrastructure`)}return r.length>0?{result:"FAIL",message:`Business logic found in shared/ (${r.length} file${r.length>1?"s":""})`,evidence:r.slice(0,5)}:{result:"PASS",message:`shared/ contains only infrastructure (${t.length} files)`}}};var Fe={id:"ARCH-06",name:"File size limit",module:"architecture",layer:"L1",priority:"P1",description:"Source files over 500 lines indicate god components \u2014 split into smaller slices",fixCost:250,fixSize:"L",async run(e){let t=e.files.filter(s=>(s.endsWith(".ts")||s.endsWith(".tsx")||s.endsWith(".js")||s.endsWith(".jsx"))&&!s.includes("node_modules")&&!s.includes(".next")&&!s.includes("dist/")&&!s.includes(".generated."));if(t.length===0)return{result:"PASS",message:"No source files found"};let i=[];for(let s of t){let r;try{r=await e.readFile(s)}catch{continue}let n=r.split(`
4
4
  `).length;n>500&&i.push(`${s} (${n} lines)`)}return i.length>0?{result:"FAIL",message:`${i.length} file${i.length>1?"s":""} over 500 lines \u2014 split into smaller modules`,evidence:i.slice(0,10)}:{result:"PASS",message:`All ${t.length} source files under 500 lines`}}};var xe={id:"STR-01",name:"CI/CD pipeline exists",module:"architecture",layer:"L1",priority:"P1",description:"A CI/CD pipeline (GitHub Actions, Vercel, etc.) should exist to enforce checks on every deploy",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(s=>s.includes(".github/workflows/")&&(s.endsWith(".yml")||s.endsWith(".yaml")));if(t.length>0)return{result:"PASS",message:`CI/CD pipeline found: ${t.length} GitHub Actions workflow${t.length>1?"s":""}`,evidence:t.slice(0,5)};let i=e.files.filter(s=>s.includes(".circleci/config.yml")||s.includes(".gitlab-ci.yml")||s.includes("Jenkinsfile")||s.includes(".travis.yml")||s.includes("bitbucket-pipelines.yml")||s.includes("vercel.json"));return i.length>0?{result:"PASS",message:"CI/CD configuration found",evidence:i.slice(0,3)}:{result:"FAIL",message:"No CI/CD pipeline found \u2014 code goes from developer to production unchecked"}}};var Ee={id:"STR-02",name:"Test files exist",module:"architecture",layer:"L1",priority:"P1",description:"At least one test file should exist \u2014 no tests means no regression safety net",fixCost:250,fixSize:"L",async run(e){let t=e.files.filter(s=>!s.includes("node_modules")&&!s.includes(".next")&&(s.endsWith(".test.ts")||s.endsWith(".test.tsx")||s.endsWith(".test.js")||s.endsWith(".test.jsx")||s.endsWith(".spec.ts")||s.endsWith(".spec.tsx")||s.endsWith(".spec.js")||s.endsWith(".spec.jsx")||s.includes("__tests__/"))),i=e.files.filter(s=>!s.includes("node_modules")&&(s.includes("vitest.config")||s.includes("jest.config")||s.includes("playwright.config")||s.includes("cypress.config")));return t.length>0?{result:"PASS",message:`${t.length} test file${t.length>1?"s":""} found`,evidence:t.slice(0,5)}:i.length>0?{result:"PASS",message:"Test configuration found but no test files yet",evidence:i.slice(0,3)}:{result:"FAIL",message:"No test files found \u2014 no regression safety net for AI-generated changes"}}};async function Te(e,t,i){let s=[],r=e.files;i&&i.length>0&&(r=r.filter(n=>i.some(o=>De(n,o))));for(let n of r){let o;try{o=await e.readFile(n)}catch{continue}let a=o.split(`
5
- `);for(let c=0;c<a.length;c++)t.test(a[c])&&s.push({file:n,line:c+1,content:a[c].trim()})}return s}function De(e,t){if(t.startsWith("!"))return!De(e,t.slice(1));if(t.startsWith("**/")){let i=t.slice(3);return e.includes(i)}if(t.endsWith("/**")){let i=t.slice(0,-3);return e.startsWith(i)}return t.includes("*")?new RegExp("^"+t.replace(/\*/g,".*")+"$").test(e):e===t||e.startsWith(t+"/")}function w(e){return e.startsWith("src/")||e.startsWith("components/")||e.startsWith("app/")||e.startsWith("pages/")||e.includes("/components/")||e.includes("/hooks/")||e.includes("/contexts/")}function x(e){return e.includes("/api/")||e.includes("route.ts")||e.includes("route.js")||e.includes("server/")||e.includes("actions.ts")||e.includes("actions.js")}function v(e){return e.includes("migration")||e.includes("supabase/")||e.endsWith(".sql")}function U(e){return(e.split("/").pop()||"").startsWith(".env")}var Me={id:"AUTH-01",name:"service_role key not in client code",module:"auth",layer:"L2",priority:"P0",aliases:["ADM-17"],description:"service_role bypasses all RLS \u2014 must never appear in client-side code or NEXT_PUBLIC_ vars",fixCost:100,fixSize:"M",async run(e){let t=/service_role|SERVICE_ROLE|SUPABASE_SERVICE_ROLE|sb_secret_/i,i=await e.grepFiles(t),s=i.filter(c=>c.content.includes("NEXT_PUBLIC_")?!0:c.file.includes("use client")?!1:!!(w(c.file)&&!c.file.includes("/api/")&&!c.file.includes("route."))),r=i.filter(c=>c.content.includes("NEXT_PUBLIC_")&&/service_role|SERVICE_ROLE/i.test(c.content)),n=[...s,...r],o=[...new Map(n.map(c=>[`${c.file}:${c.line}`,c])).values()];return o.length>0?{result:"FAIL",message:`service_role key found in client-accessible code (${o.length} location${o.length>1?"s":""})`,evidence:o.map(c=>`${c.file}:${c.line} \u2192 ${c.content.substring(0,120)}`)}:i.filter(c=>!c.content.startsWith("//")&&!c.content.startsWith("#")&&!c.content.startsWith("*")).length>0?{result:"PASS",message:"service_role key found only in server-side code"}:{result:"UNKNOWN",message:"No service_role references found \u2014 Supabase may not be used"}}};var Ue={id:"AUTH-02",name:"RLS enabled on all tables",module:"auth",layer:"L2",priority:"P0",description:"Every table must have Row Level Security enabled. Without RLS, anon key = full DB access.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(v);if(t.length===0)return{result:"UNKNOWN",message:"No SQL migration files found"};let i=/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:public\.)?(\w+)/gi,s=/ALTER\s+TABLE\s+(?:public\.)?(\w+)\s+ENABLE\s+ROW\s+LEVEL\s+SECURITY/gi,r=new Set(["schema_migrations","_prisma_migrations","migrations"]),n=new Set,o=new Set;for(let c of t){let l;try{l=await e.readFile(c)}catch{continue}let d,f=new RegExp(i.source,"gi");for(;(d=f.exec(l))!==null;){let p=d[1].toLowerCase();r.has(p)||n.add(p)}let m=new RegExp(s.source,"gi");for(;(d=m.exec(l))!==null;)o.add(d[1].toLowerCase())}if(n.size===0)return{result:"UNKNOWN",message:"No CREATE TABLE statements found in migrations"};let a=[...n].filter(c=>!o.has(c));return a.length>0?{result:"FAIL",message:`${a.length} table${a.length>1?"s":""} without RLS (of ${n.size} total)`,evidence:a.map(c=>`Table "${c}" \u2014 missing ENABLE ROW LEVEL SECURITY`)}:{result:"PASS",message:`All ${n.size} tables have RLS enabled`}}};var je={id:"AUTH-03",name:"RLS policies have WITH CHECK",module:"auth",layer:"L2",priority:"P0",description:"INSERT/UPDATE policies need WITH CHECK clause. USING alone lets users insert data owned by others.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(v);if(t.length===0)return{result:"UNKNOWN",message:"No SQL migration files found"};let i=[],s=[];for(let a of t){let c;try{c=await e.readFile(a)}catch{continue}let l=/CREATE\s+POLICY\s+"?(\w+)"?\s+ON\s+(?:public\.)?(\w+)\s+(?:AS\s+\w+\s+)?FOR\s+(INSERT|UPDATE|ALL)\b([\s\S]*?)(?=CREATE\s+POLICY|$)/gi,d;for(;(d=l.exec(c))!==null;){let f=d[1],m=d[2],p=d[3].toUpperCase(),y=d[4];(p==="INSERT"||p==="UPDATE"||p==="ALL")&&(/WITH\s+CHECK\s*\(\s*true\s*\)/i.test(y)?i.push(`${a}: policy "${f}" on ${m} \u2014 WITH CHECK (true) is permissive`):/WITH\s+CHECK/i.test(y)||s.push(`${a}: policy "${f}" on ${m} (${p}) \u2014 missing WITH CHECK`))}}let r=[...i,...s];if(r.length>0)return{result:"FAIL",message:`${r.length} RLS policy issue${r.length>1?"s":""} found`,evidence:r.slice(0,5)};let n=t.some(a=>{try{let c=e.files.includes(a)?a:"";return/CREATE\s+POLICY/i.test(c)}catch{return!1}});return(await e.grepFiles(/CREATE\s+POLICY/i)).length===0?{result:"UNKNOWN",message:"No RLS policies found in migrations"}:{result:"PASS",message:"All INSERT/UPDATE RLS policies have proper WITH CHECK clauses"}}};var Oe={id:"AUTH-04",name:"Server-side auth on protected routes",module:"auth",layer:"L2",priority:"P0",description:"Every protected API route/server action must verify auth server-side. Middleware-only auth is bypassable (CVE-2025-29927).",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>(n.includes("route.")||n.includes("actions."))&&x(n));if(t.length===0)return{result:"UNKNOWN",message:"No API route handlers found"};let i=/getUser|getSession|auth\(\)|verifyToken|requireAuth|requireAdmin|checkPermission|requireRole|currentUser|validateSession/i,s=/webhook|health|status|public|og|sitemap|robots|favicon|_next/i,r=[];for(let n of t){if(s.test(n))continue;let o;try{o=await e.readFile(n)}catch{continue}i.test(o)||r.push(n)}return r.length>0?{result:"FAIL",message:`${r.length} API route${r.length>1?"s":""} without server-side auth check`,evidence:r.slice(0,5).map(n=>`${n} \u2014 no auth verification detected`)}:{result:"PASS",message:`All ${t.length} API routes have server-side auth checks`}}};var We={id:"AUTH-05",name:"No secrets with NEXT_PUBLIC_ prefix",module:"auth",layer:"L2",priority:"P0",description:"NEXT_PUBLIC_ vars are bundled in the browser. Secrets must never use this prefix.",fixCost:100,fixSize:"M",async run(e){let t=[/NEXT_PUBLIC_.*SERVICE_ROLE/i,/NEXT_PUBLIC_.*SECRET/i,/NEXT_PUBLIC_.*DATABASE_URL/i,/NEXT_PUBLIC_.*DB_URL/i,/NEXT_PUBLIC_.*PRIVATE/i,/NEXT_PUBLIC_.*PASSWORD/i,/NEXT_PUBLIC_.*JWT_SECRET/i,/VITE_.*SERVICE_ROLE/i,/VITE_.*SECRET_KEY/i,/VITE_.*DATABASE_URL/i,/EXPO_PUBLIC_.*SERVICE_ROLE/i,/EXPO_PUBLIC_.*SECRET/i],i=e.files.filter(U),s=[];for(let r of i){let n;try{n=await e.readFile(r)}catch{continue}let o=n.split(`
6
- `);for(let a=0;a<o.length;a++){let c=o[a].trim();if(!(c.startsWith("#")||!c)){for(let l of t)if(l.test(c)){s.push({file:r,line:a+1,content:c.split("=")[0]});break}}}}return s.length>0?{result:"FAIL",message:`Secret keys exposed via public prefix (${s.length} found)`,evidence:s.map(r=>`${r.file}:${r.line} \u2192 ${r.content}`)}:i.length===0?{result:"UNKNOWN",message:"No .env files found to check"}:{result:"PASS",message:"No secrets found with NEXT_PUBLIC_/VITE_/EXPO_PUBLIC_ prefix"}}};var Be={id:"AUTH-06",name:"Protected routes redirect unauthenticated users",module:"auth",layer:"L2",priority:"P1",description:"Middleware or route guard must redirect unauthenticated users to /login for protected pages.",fixCost:100,fixSize:"M",async run(e){let t=e.files.find(s=>/^middleware\.(ts|js)$/.test(s));if(!t)return(await e.grepFiles(/redirect.*login|redirect.*auth|redirect.*sign/i)).length>0?{result:"PASS",message:"Route guards with login redirect detected"}:{result:"FAIL",message:"No middleware.ts and no route guards found \u2014 unauthenticated users can access protected pages",evidence:["Missing: middleware.ts or per-page auth redirect"]};let i;try{i=await e.readFile(t)}catch{return{result:"UNKNOWN",message:"Could not read middleware file"}}return/redirect.*login|redirect.*auth|NextResponse.*redirect/i.test(i)?{result:"PASS",message:"Middleware redirects unauthenticated users to login"}:{result:"FAIL",message:"Middleware exists but no login redirect detected",evidence:[`${t} \u2014 no redirect to /login or /auth`]}}};var He={id:"AUTH-07",name:"Session tokens in httpOnly cookies",module:"auth",layer:"L2",priority:"P2",description:"Tokens in localStorage are accessible via XSS. httpOnly cookies prevent JavaScript access to session tokens.",fixCost:100,fixSize:"M",async run(e){let t=/localStorage.*(?:token|session|jwt|access_token)|sessionStorage.*(?:token|session|jwt)/i,i=/httpOnly|cookie.*session|supabase.*ssr|@supabase\/ssr/i,s=await e.grepFiles(t),r=await e.grepFiles(i),n=s.filter(o=>!o.content.trimStart().startsWith("//"));return n.length>0&&r.length===0?{result:"FAIL",message:`Session tokens stored in localStorage (${n.length} location${n.length>1?"s":""})`,evidence:n.slice(0,3).map(o=>`${o.file}:${o.line} \u2192 ${o.content.substring(0,120)}`)}:r.length>0?{result:"PASS",message:"httpOnly cookies or @supabase/ssr detected for session management"}:{result:"UNKNOWN",message:"No explicit token storage pattern detected"}}};var ze={id:"AUTH-08",name:"Password hashing (if custom auth)",module:"auth",layer:"L2",priority:"P0",description:"Passwords must be hashed with bcrypt/argon2/scrypt. N/A if using Supabase Auth (handles hashing internally).",fixCost:100,fixSize:"M",async run(e){let t=/supabase|@supabase|createBrowserClient|createServerClient/i;if((await e.grepFiles(t)).length>0)return{result:"N/A",message:"Supabase Auth handles password hashing internally (bcrypt/Argon2)"};let s=/bcrypt|argon2|scrypt|pbkdf2/i,r=/password.*=.*req\.body|password.*=.*body\.|plaintext|md5.*password|sha1.*password/i,n=await e.grepFiles(s),o=await e.grepFiles(r);return o.length>0&&n.length===0?{result:"FAIL",message:"Password handling without secure hashing detected",evidence:o.slice(0,3).map(a=>`${a.file}:${a.line} \u2192 ${a.content.substring(0,120)}`)}:n.length>0?{result:"PASS",message:"Secure password hashing detected (bcrypt/argon2/scrypt)"}:{result:"UNKNOWN",message:"No custom auth password handling detected"}}};var qe={id:"AUTH-09",name:"Rate limiting on auth endpoints",module:"auth",layer:"L2",priority:"P1",description:"Login/register endpoints without rate limiting are vulnerable to brute force attacks.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(o=>/auth|login|register|sign/i.test(o)),i=/rateLimit|rate.limit|throttle|limiter|too.many.requests|429|upstash/i;return t.length===0?{result:"UNKNOWN",message:"No auth endpoint files found"}:(await e.grepFiles(i,t)).length>0?{result:"PASS",message:"Rate limiting detected on auth endpoints"}:(await e.grepFiles(i)).length>0?{result:"PASS",message:"Rate limiting detected in codebase (verify it covers auth endpoints)"}:(await e.grepFiles(/supabase.*auth|@supabase/i)).length>0?{result:"PASS",message:"Supabase Auth has built-in rate limiting for auth endpoints"}:{result:"FAIL",message:"No rate limiting found on auth endpoints",evidence:["Missing: rate limiting middleware on login/register routes"]}}};var Ke={id:"AUTH-10",name:"Profile sync trigger with safe search_path",module:"auth",layer:"L2",priority:"P1",description:"handle_new_user() trigger must use SECURITY DEFINER with restricted search_path to prevent privilege escalation.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(v);if(t.length===0)return{result:"UNKNOWN",message:"No SQL migration files found"};let i=/handle_new_user|on_auth_user_created|AFTER\s+INSERT\s+ON\s+auth\.users/i,s=/SECURITY\s+DEFINER/i,r=/search_path|SET\s+search_path/i;if((await e.grepFiles(i)).length===0)return{result:"FAIL",message:"No profile sync trigger found (handle_new_user or equivalent)",evidence:["Missing trigger: new auth.users rows won't sync to profiles table"]};for(let o of t){let a;try{a=await e.readFile(o)}catch{continue}if(i.test(a)){let c=s.test(a),l=r.test(a);return c&&!l?{result:"FAIL",message:"Profile trigger uses SECURITY DEFINER without restricted search_path",evidence:[`${o} \u2014 SECURITY DEFINER without SET search_path = '' (privilege escalation risk)`]}:c&&l?{result:"PASS",message:"Profile sync trigger found with SECURITY DEFINER and restricted search_path"}:{result:"PASS",message:"Profile sync trigger found"}}}return{result:"PASS",message:"Profile sync trigger detected"}}};var Ge={id:"AUTH-11",name:"Client/server auth separation",module:"auth",layer:"L2",priority:"P0",description:"Separate Supabase clients for browser and server. One shared client leaks service_role to browser or uses anon key on server.",fixCost:100,fixSize:"M",async run(e){let t=/createBrowserClient|createClient.*browser/i,i=/createServerClient|createClient.*server/i,s=/createClient\s*\(/,r=await e.grepFiles(t),n=await e.grepFiles(i),o=await e.grepFiles(s);if(r.length>0&&n.length>0)return{result:"PASS",message:"Separate browser and server Supabase clients detected"};if(r.length>0||n.length>0)return{result:"PASS",message:"Dedicated Supabase client pattern detected (@supabase/ssr)"};if(o.length>0){let a=o.filter(c=>/supabase|createClient/.test(c.content));if(a.length>0)return{result:"FAIL",message:"Single generic createClient() used \u2014 no client/server separation",evidence:a.slice(0,3).map(c=>`${c.file}:${c.line} \u2192 ${c.content.substring(0,120)}`)}}return{result:"UNKNOWN",message:"No Supabase client initialization found"}}};var Ve={id:"AUTH-12",name:"Auth environment variables present",module:"auth",layer:"L2",priority:"P2",description:"Missing auth env vars = hardcoded keys or broken auth in production.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(U);if(t.length===0)return{result:"UNKNOWN",message:"No .env files found"};let i=["SUPABASE_URL","SUPABASE_ANON_KEY"],s=[];for(let o of t){let a;try{a=await e.readFile(o)}catch{continue}for(let c of i)a.includes(c)&&!s.includes(c)&&s.push(c)}if((await e.grepFiles(/supabase/i)).length===0)return{result:"UNKNOWN",message:"No Supabase references found"};let n=i.filter(o=>!s.includes(o));return n.length>0?{result:"FAIL",message:`Missing auth env vars: ${n.join(", ")}`,evidence:n.map(o=>`${o} not found in any .env file`)}:{result:"PASS",message:"Auth environment variables configured"}}};var Ye={id:"AUTH-13",name:"getUser() not getSession() for server-side auth",module:"auth",layer:"L2",priority:"P0",description:"getSession() reads JWT without server verification. Use getUser() for auth decisions.",fixCost:100,fixSize:"M",async run(e){let t=/\.auth\.getSession\s*\(/,i=/\.auth\.getUser\s*\(/,s=await e.grepFiles(t),r=await e.grepFiles(i);if(s.length===0&&r.length===0)return{result:"UNKNOWN",message:"No getSession/getUser calls found \u2014 Supabase Auth may not be used"};let n=s.filter(l=>x(l.file)),o=r.filter(l=>x(l.file));if(n.length>0&&o.length===0)return{result:"FAIL",message:`Server-side code uses getSession() without getUser() (${n.length} location${n.length>1?"s":""})`,evidence:n.map(l=>`${l.file}:${l.line} \u2192 ${l.content.substring(0,120)}`)};if(n.length>0&&o.length>0)return{result:"FAIL",message:"Server code uses both getSession() and getUser() \u2014 getSession() in server context is insecure",evidence:n.map(l=>`${l.file}:${l.line} \u2192 ${l.content.substring(0,120)}`)};let a=s.filter(l=>!x(l.file)&&!/supabase\/functions\//i.test(l.file)),c=r.filter(l=>!x(l.file)&&!/supabase\/functions\//i.test(l.file));return a.length>0&&c.length===0&&o.length===0?{result:"FAIL",message:`Client code uses getSession() (${a.length} location${a.length>1?"s":""}) but getUser() never called \u2014 auth relies on unverified JWT`,evidence:a.slice(0,3).map(l=>`${l.file}:${l.line} \u2192 ${l.content.substring(0,120)}`)}:o.length>0||c.length>0?{result:"PASS",message:"Auth uses getUser() for verification"}:{result:"PASS",message:"getUser() is used for auth verification"}}};var Xe={id:"AUTH-14",name:"No eval() or dangerouslySetInnerHTML with user data",module:"auth",layer:"L2",priority:"P0",description:"eval() and dangerouslySetInnerHTML enable XSS attacks \u2014 session theft and account takeover.",fixCost:100,fixSize:"M",async run(e){let t=/\beval\s*\(/,i=/dangerouslySetInnerHTML/,s=await e.grepFiles(t),r=await e.grepFiles(i),n=s.filter(c=>!(c.content.trimStart().startsWith("//")||c.content.trimStart().startsWith("*")||c.file.includes("node_modules"))),o=r.filter(c=>!(c.content.trimStart().startsWith("//")||c.file.includes("node_modules"))),a=[...n,...o];return a.length>0?{result:"FAIL",message:`Unsafe code patterns found (${n.length} eval, ${o.length} dangerouslySetInnerHTML)`,evidence:a.slice(0,5).map(c=>`${c.file}:${c.line} \u2192 ${c.content.substring(0,120)}`)}:{result:"PASS",message:"No eval() or dangerouslySetInnerHTML found"}}};var Je={id:"AUTH-15",name:"CORS configuration",module:"auth",layer:"L2",priority:"P2",description:"Access-Control-Allow-Origin: * with credentials allows any website to read auth cookies and data.",fixCost:100,fixSize:"M",async run(e){let t=/Access-Control-Allow-Origin.*\*|cors.*origin.*\*|origin:\s*['"]?\*/i,i=/credentials.*true|allowCredentials|Access-Control-Allow-Credentials/i,s=await e.grepFiles(t),r=await e.grepFiles(i);return s.length>0&&r.length>0?{result:"FAIL",message:"CORS wildcard (*) used with credentials \u2014 any website can read auth data",evidence:s.slice(0,3).map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,120)}`)}:s.length>0?{result:"FAIL",message:"CORS wildcard (*) detected \u2014 consider restricting to specific origins",evidence:s.slice(0,3).map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,120)}`)}:{result:"PASS",message:"No dangerous CORS wildcard configuration detected"}}};var Qe={id:"AUTH-16",name:"Token/session expiration configured",module:"auth",layer:"L2",priority:"P2",description:"Infinite sessions mean a stolen token works forever. Configure JWT expiration and session timeouts.",fixCost:100,fixSize:"M",async run(e){let t=/expiresIn|maxAge|session.*expir|jwt.*expir|SESSION_EXPIRY|JWT_EXPIRY|token.*expir/i;return(await e.grepFiles(t)).length>0?{result:"PASS",message:"Token/session expiration configured"}:(await e.grepFiles(/supabase/i)).length>0?{result:"PASS",message:"Supabase Auth has default JWT expiry (3600s)"}:{result:"FAIL",message:"No token/session expiration configuration found",evidence:["Missing: expiresIn, maxAge, or session expiry configuration"]}}};var Ze={id:"AUTH-17",name:"Storage bucket RLS",module:"auth",layer:"L2",priority:"P0",description:"Supabase storage buckets must have RLS on storage.objects. Without it, all files are publicly accessible.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(v);if(t.length===0)return{result:"UNKNOWN",message:"No SQL migration files found"};let i=/storage\.buckets|create.*bucket|INSERT.*storage\.buckets/i,s=/storage\.objects.*ENABLE.*ROW.*LEVEL|RLS.*storage\.objects|POLICY.*storage\.objects/i,r=await e.grepFiles(i,t),n=await e.grepFiles(s,t);return r.length===0?(await e.grepFiles(/supabase.*storage|storage\.from\(/i)).length===0?{result:"N/A",message:"No Supabase storage usage detected"}:{result:"UNKNOWN",message:"Storage used in code but no bucket creation in migrations"}:n.length>0?{result:"PASS",message:"Storage bucket RLS policies detected"}:{result:"FAIL",message:"Storage buckets created without RLS on storage.objects \u2014 files may be publicly accessible",evidence:r.slice(0,3).map(o=>`${o.file}:${o.line} \u2192 ${o.content.substring(0,120)}`)}}};var et={id:"AUTH-18",name:"RBAC in app_metadata not user_metadata",module:"auth",layer:"L2",priority:"P0",description:"Roles in user_metadata are user-editable. Use app_metadata for RBAC (server-only).",fixCost:100,fixSize:"M",async run(e){let t=/user_meta_?data.*role|raw_user_meta_?data.*role|user\.user_metadata.*role/i,i=/app_meta_?data.*role|user\.app_metadata.*role/i,s=/updateUser\s*\(\s*\{[\s\S]*?data\s*:\s*\{[\s\S]*?role/,r=await e.grepFiles(t),n=await e.grepFiles(i),o=await e.grepFiles(s);return r.length>0?{result:"FAIL",message:`Role stored in user_metadata (user-editable) \u2014 ${r.length} location${r.length>1?"s":""}`,evidence:r.map(a=>`${a.file}:${a.line} \u2192 ${a.content.substring(0,120)}`)}:o.length>0?{result:"FAIL",message:"Role set via updateUser() data field (user-editable user_metadata)",evidence:o.map(a=>`${a.file}:${a.line} \u2192 ${a.content.substring(0,120)}`)}:n.length>0?{result:"PASS",message:"Role stored in app_metadata (server-only, not user-editable)"}:{result:"UNKNOWN",message:"No role/RBAC references found in codebase"}}};var tt={id:"AUTH-19",name:"Multi-tenancy data isolation",module:"auth",layer:"L2",priority:"P1",description:"RLS policies must include tenant_id or organization_id to prevent cross-tenant data access.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(v),i=/tenant_id|organization_id|org_id|team_id|workspace_id/i,s=await e.grepFiles(i,t.length>0?t:void 0),r=await e.grepFiles(i);return s.length===0&&r.length===0?{result:"N/A",message:"No multi-tenancy pattern detected (single-tenant app)"}:(await e.grepFiles(/POLICY[\s\S]*?tenant_id|POLICY[\s\S]*?org_id|POLICY[\s\S]*?organization_id/i,t)).length>0?{result:"PASS",message:"RLS policies include tenant isolation"}:{result:"FAIL",message:"Multi-tenant schema detected but RLS policies don't include tenant_id filtering",evidence:["Tenant columns exist but RLS policies may allow cross-tenant data access"]}}};var st={id:"AUTH-20",name:"OAuth domain restriction",module:"auth",layer:"L2",priority:"P1",description:"Social login (Google, GitHub) should restrict allowed email domains to prevent unauthorized access.",fixCost:100,fixSize:"M",async run(e){let t=/signInWithOAuth|signIn.*provider|google|github.*login|oauth/i,i=/allowedDomain|domain.*restrict|email.*domain|hd=|hosted_domain/i,s=await e.grepFiles(t);return s.length===0?{result:"N/A",message:"No OAuth/social login detected"}:(await e.grepFiles(i)).length>0?{result:"PASS",message:"OAuth domain restriction detected"}:{result:"FAIL",message:"OAuth login enabled without domain restriction \u2014 any Google/GitHub account can sign in",evidence:s.slice(0,3).map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,120)}`)}}};var it={id:"AUTH-21",name:"Force dynamic on auth routes",module:"auth",layer:"L2",priority:"P1",description:"Auth routes must use force-dynamic to prevent ISR cache serving wrong user data.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>/auth|login|register|signup|sign-in|sign-up/i.test(n)&&(n.includes("page.")||n.includes("route."))&&(n.endsWith(".ts")||n.endsWith(".tsx")||n.endsWith(".js")||n.endsWith(".jsx")));if(t.length===0)return{result:"UNKNOWN",message:"No auth route/page files found"};let i=/export\s+const\s+dynamic\s*=\s*['"]force-dynamic['"]/,s=/unstable_noStore|noStore|revalidate\s*=\s*0/,r=[];for(let n of t){let o;try{o=await e.readFile(n)}catch{continue}!i.test(o)&&!s.test(o)&&n.includes("page.")&&r.push(n)}return r.length>0?{result:"FAIL",message:`${r.length} auth page${r.length>1?"s":""} without force-dynamic \u2014 may serve cached auth state`,evidence:r.slice(0,5).map(n=>`${n} \u2014 missing export const dynamic = 'force-dynamic'`)}:{result:"PASS",message:"Auth routes use force-dynamic or equivalent"}}};var nt={id:"AUTH-22",name:"CSRF protection on Route Handlers",module:"auth",layer:"L2",priority:"P2",description:"Server Actions have built-in CSRF protection (Next.js 14+). Route Handlers do NOT \u2014 they need explicit CSRF tokens or SameSite cookies.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(o=>/route\.(ts|js)$/i.test(o));if(t.length===0)return{result:"UNKNOWN",message:"No Route Handler files found"};let i=[];for(let o of t){let a;try{a=await e.readFile(o)}catch{continue}/export\s+(?:async\s+)?function\s+(?:POST|PUT|PATCH|DELETE)/i.test(a)&&i.push(o)}if(i.length===0)return{result:"PASS",message:"No mutation Route Handlers found (only GET)"};let s=/csrf|csurf|csrfToken|SameSite|x-csrf|anti.?forgery/i;return(await e.grepFiles(s)).length>0?{result:"PASS",message:"CSRF protection detected"}:(await e.grepFiles(/["']use server["']/i)).length>0&&i.length<=2?{result:"PASS",message:"Mutations use Server Actions (built-in CSRF protection)"}:{result:"FAIL",message:`${i.length} mutation Route Handler${i.length>1?"s":""} without CSRF protection`,evidence:i.slice(0,3).map(o=>`${o} \u2014 POST/PUT/PATCH/DELETE without CSRF token`)}}};var rt={id:"AUTH-23",name:"Email verification required",module:"auth",layer:"L2",priority:"P2",description:"Without email verification, anyone can sign up with any email \u2014 spam accounts and impersonation.",fixCost:100,fixSize:"M",async run(e){let t=/email_confirmed_at|emailConfirmed|verifyEmail|confirmEmail|email.*verif|verification.*email/i;return(await e.grepFiles(t)).length>0?{result:"PASS",message:"Email verification check detected"}:(await e.grepFiles(/supabase/i)).length>0?{result:"PASS",message:"Supabase Auth has configurable email verification (check dashboard settings)"}:{result:"FAIL",message:"No email verification enforcement found",evidence:["Missing: email_confirmed_at check or equivalent verification flow"]}}};var ot={id:"AUTH-24",name:"Account enumeration prevention",module:"auth",layer:"L2",priority:"P2",description:"Login/register error messages must not reveal whether an email exists. Consistent messages prevent user enumeration.",fixCost:100,fixSize:"M",async run(e){let t=/email.*not.*found|user.*not.*found|no.*account.*with|email.*already.*registered|email.*already.*exists|account.*already.*exists/i,s=(await e.grepFiles(t)).filter(r=>!r.content.trimStart().startsWith("//")&&!r.content.trimStart().startsWith("*"));return s.length>0?{result:"FAIL",message:`Account enumeration possible \u2014 error messages reveal email existence (${s.length} location${s.length>1?"s":""})`,evidence:s.slice(0,3).map(r=>`${r.file}:${r.line} \u2192 ${r.content.substring(0,120)}`)}:{result:"PASS",message:"No account enumeration patterns detected in error messages"}}};var at={id:"AUTH-25",name:"Refresh token reuse detection",module:"auth",layer:"L2",priority:"P2",description:"Token rotation prevents stolen refresh tokens from being reused. Supabase supports this via config.",fixCost:100,fixSize:"M",async run(e){let t=/token.*rotation|refresh.*token.*reuse|reuse.*detection|GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL/i;return(await e.grepFiles(t)).length>0?{result:"PASS",message:"Refresh token rotation/reuse detection configured"}:(await e.grepFiles(/supabase/i)).length>0?{result:"PASS",message:"Supabase Auth has built-in refresh token rotation (check dashboard config)"}:{result:"UNKNOWN",message:"No refresh token configuration detected"}}};var ct={id:"AUTH-26",name:"Sign-out revokes server session",module:"auth",layer:"L2",priority:"P2",description:"Sign-out must revoke the server session (scope: 'global'), not just clear client cookies.",fixCost:100,fixSize:"M",async run(e){let t=/signOut|sign_out|logout|log_out/i,i=/scope.*global|global.*scope/i,s=await e.grepFiles(t);return s.length===0?{result:"UNKNOWN",message:"No sign-out implementation found"}:(await e.grepFiles(i)).length>0?{result:"PASS",message:"Sign-out uses global scope (revokes all sessions)"}:{result:"FAIL",message:"Sign-out found but no global scope \u2014 sessions may persist on other devices",evidence:s.slice(0,2).map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,120)}`)}}};var lt={id:"AUTH-27",name:"Email link poisoning mitigation",module:"auth",layer:"L2",priority:"P2",description:"SITE_URL and REDIRECT_ALLOW_LIST must be configured to prevent email link redirect attacks.",fixCost:100,fixSize:"M",async run(e){let t=/SITE_URL|GOTRUE_SITE_URL/i,i=/REDIRECT_ALLOW_LIST|ADDITIONAL_REDIRECT_URLS|redirect.*allow/i,s=await e.grepFiles(t),r=await e.grepFiles(i);return s.length>0&&r.length>0?{result:"PASS",message:"SITE_URL and REDIRECT_ALLOW_LIST configured"}:s.length>0?{result:"PASS",message:"SITE_URL configured (check REDIRECT_ALLOW_LIST in Supabase dashboard)"}:(await e.grepFiles(/supabase/i)).length===0?{result:"N/A",message:"No Supabase usage detected"}:{result:"FAIL",message:"No SITE_URL or REDIRECT_ALLOW_LIST found \u2014 email magic links may redirect to attacker domains",evidence:["Missing: SITE_URL and REDIRECT_ALLOW_LIST in env configuration"]}}};var ut={id:"AUTH-28",name:"Realtime presence authorization",module:"auth",layer:"L2",priority:"P2",description:"Supabase Realtime Presence channels must have authorization. Without it, any user can see who's online.",fixCost:100,fixSize:"M",async run(e){let t=/realtime|presence|channel.*subscribe|supabase.*channel/i,i=await e.grepFiles(t);if(i.length===0)return{result:"N/A",message:"No Supabase Realtime/Presence usage detected"};let s=/authorized|RLS.*realtime|realtime.*auth|channel.*auth/i;return(await e.grepFiles(s)).length>0?{result:"PASS",message:"Realtime channel authorization detected"}:{result:"FAIL",message:"Realtime/Presence channels found without authorization",evidence:i.slice(0,3).map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,120)}`)}}};var dt={id:"BIL-01",name:"Stripe secret key not in client code",module:"billing",layer:"L2",priority:"P0",description:"sk_live_/sk_test_ in client code = full Stripe API access for any visitor",fixCost:100,fixSize:"M",async run(e){let t=/sk_live_[a-zA-Z0-9]{20,}|sk_test_[a-zA-Z0-9]{20,}/,i=/STRIPE_SECRET|NEXT_PUBLIC_.*STRIPE.*SECRET|VITE_.*STRIPE.*SECRET/i,r=(await e.grepFiles(t)).filter(l=>!U(l.file)&&!l.content.trimStart().startsWith("#")&&!l.content.trimStart().startsWith("//"));if(r.length>0)return{result:"FAIL",message:`Hardcoded Stripe secret key found in source code (${r.length} location${r.length>1?"s":""})`,evidence:r.map(l=>`${l.file}:${l.line} \u2192 sk_***_[REDACTED]`)};let n=await e.grepFiles(i),o=n.filter(l=>/NEXT_PUBLIC_|VITE_|EXPO_PUBLIC_/i.test(l.content)&&/STRIPE.*SECRET/i.test(l.content));if(o.length>0)return{result:"FAIL",message:"Stripe secret key exposed via NEXT_PUBLIC_/VITE_ prefix",evidence:o.map(l=>`${l.file}:${l.line} \u2192 ${l.content.split("=")[0]}`)};let a=n.filter(l=>w(l.file)&&/STRIPE_SECRET/i.test(l.content)&&!l.file.includes("/api/")&&!l.file.includes("route."));return a.length>0?{result:"FAIL",message:"Stripe secret key referenced in client-side file",evidence:a.map(l=>`${l.file}:${l.line} \u2192 ${l.content.substring(0,120)}`)}:n.filter(l=>/STRIPE_SECRET/i.test(l.content)).length>0?{result:"PASS",message:"Stripe secret key found only in server-side/env files"}:{result:"UNKNOWN",message:"No Stripe secret key references found \u2014 Stripe may not be used"}}};var pt={id:"BIL-02",name:"Webhook signature verification",module:"billing",layer:"L2",priority:"P0",aliases:["ADM-15"],description:"Stripe webhook handler must verify signature via constructEvent(). Without it, anyone can send fake webhooks.",fixCost:100,fixSize:"M",async run(e){let t=/webhook/i,i=e.files.filter(c=>t.test(c));if(i.length===0)return{result:"UNKNOWN",message:"No webhook handler files found"};let s=/constructEvent|constructEventAsync|webhooks\.construct/,r=/stripe-signature|Stripe-Signature|STRIPE_WEBHOOK_SECRET|webhook.*secret/i,n=await e.grepFiles(s,i),o=await e.grepFiles(r,i);if(n.length>0){for(let c of i){let l;try{l=await e.readFile(c)}catch{continue}let d=/constructEvent|constructEventAsync|webhooks\.construct/.test(l),f=/JSON\.parse\s*\(.*body/i.test(l),m=/if\s*\(\s*(?:webhook_?[Ss]ecret|STRIPE_WEBHOOK_SECRET|secret)/i.test(l)||/if\s*\(\s*!?\s*webhook_?[Ss]ecret/i.test(l);if(d&&f&&m)return{result:"FAIL",message:"Webhook signature verification is conditional \u2014 JSON.parse fallback bypasses constructEvent() when secret is missing",evidence:[`${c} \u2014 constructEvent() inside conditional, JSON.parse(body) fallback allows unverified webhooks`]}}return{result:"PASS",message:"Webhook handler uses constructEvent() for signature verification"}}return o.length>0?{result:"PASS",message:"Webhook handler references signature verification"}:(await e.grepFiles(/stripe|Stripe/,i)).length>0?{result:"FAIL",message:"Stripe webhook handler found WITHOUT signature verification",evidence:i.map(c=>`${c} \u2014 no constructEvent() or signature check`)}:{result:"UNKNOWN",message:"Webhook files found but no Stripe references \u2014 may not be Stripe webhooks"}}};var ft={id:"BIL-03",name:"Raw body preservation in webhook",module:"billing",layer:"L2",priority:"P0",description:"Webhook signature verification requires raw request body. req.json() breaks it \u2014 use req.text() or bodyParser: false.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(a=>/webhook/i.test(a));if(t.length===0)return{result:"UNKNOWN",message:"No webhook handler files found"};let i=/request\.text\(\)|req\.text\(\)|bodyParser\s*:\s*false|getRawBody|raw\s*:\s*true|rawBody/,s=/request\.json\(\)|req\.body(?!\s*Parser)|JSON\.parse/,r=await e.grepFiles(i,t),n=await e.grepFiles(s,t);return r.length>0?{result:"PASS",message:"Webhook handler preserves raw body for signature verification"}:(await e.grepFiles(/stripe|constructEvent/i,t)).length===0?{result:"UNKNOWN",message:"Webhook files found but no Stripe references"}:n.length>0?{result:"FAIL",message:"Webhook handler uses req.json()/req.body instead of raw body \u2014 signature verification will fail",evidence:n.slice(0,3).map(a=>`${a.file}:${a.line} \u2192 ${a.content.substring(0,120)}`)}:{result:"FAIL",message:"Stripe webhook handler found but no raw body preservation detected",evidence:t.map(a=>`${a} \u2014 missing request.text() or bodyParser: false`)}}};var mt={id:"BIL-04",name:"Idempotent webhook processing",module:"billing",layer:"L2",priority:"P1",description:"Stripe retries webhooks by design. Without idempotency checks, duplicate credits and double subscriptions occur.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>/webhook/i.test(n));if(t.length===0)return{result:"UNKNOWN",message:"No webhook handler files found"};let i=/event\.id|event_id|idempoten|UNIQUE.*event|duplicate.*check|processed.*events|already.*processed/i,s=await e.grepFiles(i,t);return(await e.grepFiles(/stripe|constructEvent/i,t)).length===0?{result:"UNKNOWN",message:"No Stripe webhook handler found"}:s.length>0?{result:"PASS",message:"Webhook idempotency check detected"}:{result:"FAIL",message:"Stripe webhook handler has no idempotency check \u2014 duplicate processing possible",evidence:["No event.id tracking or duplicate check in webhook handler"]}}};var gt={id:"BIL-05",name:"Subscription state machine",module:"billing",layer:"L2",priority:"P1",description:"Stripe has 8 subscription states. Handling only active/canceled causes access issues for past_due, trialing, incomplete, unpaid.",fixCost:100,fixSize:"M",async run(e){let t=/past_due|trialing|incomplete|unpaid/i,i=/active|canceled/i,s=await e.grepFiles(t),r=await e.grepFiles(i);return s.length>=2?{result:"PASS",message:"Multiple subscription states handled beyond active/canceled"}:r.length>0&&s.length===0?{result:"FAIL",message:"Only active/canceled states handled \u2014 missing past_due, trialing, incomplete, unpaid",evidence:["Subscription state machine is incomplete \u2014 users may lose access incorrectly"]}:(await e.grepFiles(/subscription|stripe/i)).length===0?{result:"UNKNOWN",message:"No subscription handling found"}:{result:"FAIL",message:"Subscription code found but no explicit state handling",evidence:["Missing: past_due, trialing, incomplete, unpaid state handling"]}}};var ht={id:"BIL-06",name:"Entitlement/plan limit checking",module:"billing",layer:"L2",priority:"P1",description:"Stripe doesn't track usage limits. Without app-side entitlement checks, free users access paid features.",fixCost:100,fixSize:"M",async run(e){let t=/checkPlan|checkEntitle|planLimit|featureGate|subscription.*check|plan.*limit|canAccess|hasFeature|isSubscribed|entitlement/i;return(await e.grepFiles(t)).length>0?{result:"PASS",message:"Entitlement/plan limit checking detected"}:(await e.grepFiles(/subscription|stripe.*plan|pricing/i)).length===0?{result:"UNKNOWN",message:"No subscription/pricing code found"}:{result:"FAIL",message:"Subscription code exists but no entitlement/plan limit checks found",evidence:["Missing: checkPlanLimit, checkEntitlement, featureGate, or equivalent"]}}};var St={id:"BIL-07",name:"Customer \u2194 User sync",module:"billing",layer:"L2",priority:"P1",description:"Every user must map to exactly one Stripe customer. Missing sync = orphaned customers, broken subscription lookups.",fixCost:100,fixSize:"M",async run(e){let t=/stripe_customer_id|stripeCustomerId|customer_id.*stripe/i,i=/customers\.create|createCustomer|stripe.*customer/i,s=e.files.filter(v),r=await e.grepFiles(t,s.length>0?s:void 0),n=await e.grepFiles(i);return r.length>0?{result:"PASS",message:"stripe_customer_id found in database schema \u2014 user-customer sync exists"}:n.length>0?{result:"PASS",message:"Stripe customer creation found in code"}:(await e.grepFiles(/stripe/i)).length>0?{result:"FAIL",message:"Stripe is used but no stripe_customer_id in schema and no customer creation logic",evidence:["Missing: stripe_customer_id column in users/profiles table"]}:{result:"UNKNOWN",message:"No Stripe references found"}}};var yt={id:"BIL-08",name:"Webhook returns 200 for unknown events",module:"billing",layer:"L2",priority:"P1",description:"Returning 400/500 for unhandled events triggers Stripe retry loops and eventual endpoint deactivation.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(o=>/webhook/i.test(o));if(t.length===0)return{result:"UNKNOWN",message:"No webhook handler files found"};let i=/else\s*\{[\s\S]*?(?:return.*(?:4\d\d|5\d\d)|throw|NextResponse.*(?:4\d\d|5\d\d))/i,s=/default\s*:[\s\S]*?(?:200|ok|return\s+new\s+Response)/i,r=await e.grepFiles(i,t),n=await e.grepFiles(s,t);return r.length>0?{result:"FAIL",message:"Webhook handler returns error for unknown events \u2014 will trigger Stripe retry loop",evidence:r.slice(0,3).map(o=>`${o.file}:${o.line} \u2192 ${o.content.substring(0,120)}`)}:n.length>0?{result:"PASS",message:"Webhook handler returns 200 for unhandled events"}:{result:"UNKNOWN",message:"Could not determine webhook default response behavior"}}};var $t={id:"BIL-09",name:"No client-side billing state as source of truth",module:"billing",layer:"L2",priority:"P1",description:"Subscription state in localStorage/React state can be manipulated. Server must be source of truth.",fixCost:100,fixSize:"M",async run(e){let t=[],i=/localStorage.*(?:subscription|plan|billing)|sessionStorage.*(?:subscription|plan)|useState.*(?:subscription|isPro|isPaid|plan)/i,r=(await e.grepFiles(i)).filter(d=>w(d.file));for(let d of r)t.push(`${d.file}:${d.line} \u2192 ${d.content.substring(0,120)}`);let n=/\.from\s*\(\s*['"](?:subscriptions|plans|credits|billing)['"]\s*\)\s*\.\s*select/i,a=(await e.grepFiles(n)).filter(d=>w(d.file));for(let d of a)t.push(`${d.file}:${d.line} \u2192 ${d.content.substring(0,120)}`);let c=/(?:useSubscription|useBilling|usePlan)\b/,l=e.files.filter(d=>c.test(d)||/hook/i.test(d));for(let d of l){let f;try{f=await e.readFile(d)}catch{continue}let m=/from\s*\(\s*['"](?:subscriptions|plans|credits)['"]\s*\)/.test(f),p=/isPro|isPaid|plan\s*===|plan\s*!==|plan\s*==|plan\s*!=/.test(f);m&&p&&t.push(`${d} \u2192 Client hook reads billing table and derives plan/isPro locally`)}return t.length>0?{result:"FAIL",message:`Client-side billing state as source of truth (${t.length} location${t.length>1?"s":""})`,evidence:t.slice(0,5)}:{result:"PASS",message:"No client-side billing state as source of truth detected"}}};var At={id:"BIL-10",name:"Reconciliation mechanism",module:"billing",layer:"L2",priority:"P2",description:"Even good webhook handling drifts 1-2x/month. A reconciliation job comparing Stripe vs DB prevents silent revenue loss.",fixCost:100,fixSize:"M",async run(e){let t=/reconcil|sync.*stripe|stripe.*sync|cron.*billing|billing.*cron|verify.*subscription|subscription.*verify/i,i=e.files.filter(n=>/reconcil/i.test(n)),s=await e.grepFiles(t);return i.length>0||s.length>0?{result:"PASS",message:"Reconciliation mechanism detected"}:(await e.grepFiles(/stripe/i)).length===0?{result:"UNKNOWN",message:"No Stripe references found"}:{result:"FAIL",message:"No billing reconciliation mechanism found",evidence:["Missing: reconciliation script/job to compare Stripe state vs DB state"]}}};var bt={id:"BIL-11",name:"Cancellation handling",module:"billing",layer:"L2",priority:"P1",description:"Explicit cancel flow: server-side cancellation + webhook processing + DB update + access revocation.",fixCost:100,fixSize:"M",async run(e){let t=/cancel.*subscription|subscription.*cancel|customer\.subscription\.deleted|cancelAt|cancel_at_period_end/i,i=await e.grepFiles(t);return i.length>=2?{result:"PASS",message:"Cancellation handling detected in multiple locations"}:i.length===1?{result:"PASS",message:"Cancellation handling detected"}:(await e.grepFiles(/subscription|stripe/i)).length===0?{result:"UNKNOWN",message:"No subscription code found"}:{result:"FAIL",message:"No cancellation handling found \u2014 users may retain access after canceling or be charged after canceling",evidence:["Missing: cancel subscription flow, subscription.deleted webhook handler"]}}};var Ct={id:"BIL-12",name:"Stripe env vars configured",module:"billing",layer:"L2",priority:"P2",description:"Missing Stripe env vars = billing won't work in production or keys get hardcoded.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(U);if(t.length===0)return{result:"UNKNOWN",message:"No .env files found"};let i=["STRIPE_SECRET_KEY","STRIPE_WEBHOOK_SECRET","STRIPE_PUBLISHABLE_KEY"],s=[],r=[];for(let o of t){let a;try{a=await e.readFile(o)}catch{continue}for(let c of i)a.includes(c)&&!s.includes(c)&&s.push(c)}for(let o of i)s.includes(o)||r.push(o);return(await e.grepFiles(/stripe/i)).length===0?{result:"UNKNOWN",message:"No Stripe references found"}:r.length>0?{result:"FAIL",message:`Missing Stripe env vars: ${r.join(", ")}`,evidence:r.map(o=>`${o} not found in any .env file`)}:{result:"PASS",message:"All required Stripe env vars configured"}}};var vt={id:"BIL-13",name:"Error handling in payment flows",module:"billing",layer:"L2",priority:"P2",description:"Missing error handling around Stripe API calls = white screen on payment failure, no diagnostics.",fixCost:100,fixSize:"M",async run(e){let t=/stripe\.\w+\.\w+\(|checkout\.sessions\.create|subscriptions\.create|customers\.create/i,i=/try\s*\{|\.catch\s*\(|catch\s*\(/,s=e.files.filter(o=>/api|route|action|server/i.test(o)),r=await e.grepFiles(t,s.length>0?s:void 0);return r.length===0?{result:"UNKNOWN",message:"No Stripe API calls found"}:(await e.grepFiles(i,s.length>0?s:void 0)).length>0?{result:"PASS",message:"Error handling found in payment flow files"}:{result:"FAIL",message:"Stripe API calls found without error handling",evidence:r.slice(0,3).map(o=>`${o.file}:${o.line} \u2192 ${o.content.substring(0,120)}`)}}};var kt={id:"BIL-14",name:"Checkout flow is server-initiated",module:"billing",layer:"L2",priority:"P0",description:"Checkout session must be created on the server. Client-side creation requires secret key in browser.",fixCost:100,fixSize:"M",async run(e){let t=/checkout\.sessions\.create|createCheckoutSession/,i=await e.grepFiles(t);if(i.length===0)return{result:"UNKNOWN",message:"No Checkout session creation found"};let s=i.filter(n=>w(n.file)&&!x(n.file));for(let n of s){let o;try{o=await e.readFile(n.file)}catch{continue}if(/["']use client["']/.test(o))return{result:"FAIL",message:"Checkout session created in client component \u2014 secret key required in browser",evidence:[`${n.file}:${n.line} \u2192 ${n.content.substring(0,120)}`]}}return i.filter(n=>x(n.file)).length>0?{result:"PASS",message:"Checkout session created server-side"}:{result:"PASS",message:"Checkout session creation found (not in client component)"}}};var _t={id:"BIL-15",name:"Stripe Price ID tampering prevention",module:"billing",layer:"L2",priority:"P0",description:"Price ID from client request must be validated server-side against an allowlist. Client can send any price_id.",fixCost:100,fixSize:"M",async run(e){let t=/checkout\.sessions\.create|createCheckoutSession/;if((await e.grepFiles(t)).length===0)return{result:"UNKNOWN",message:"No Stripe Checkout session creation found"};let s=/req\.body.*price|req\.json.*price|request\.json.*price|body\.price|priceId|price_id/i,r=/allowedPrices|ALLOWED_PRICES|validPrices|PRICE_IDS|priceWhitelist|priceLookup|PLANS\[|plans\[|PRICES\[|prices\./i,n=await e.grepFiles(s),o=await e.grepFiles(r);return n.length>0&&o.length===0?{result:"FAIL",message:"Price ID accepted from client without server-side validation",evidence:n.slice(0,3).map(a=>`${a.file}:${a.line} \u2192 ${a.content.substring(0,120)}`)}:o.length>0?{result:"PASS",message:"Price ID validation/allowlist detected"}:{result:"PASS",message:"Checkout session creation found with no client price input detected"}}};var It={id:"BIL-16",name:"Never fulfill on success_url",module:"billing",layer:"L2",priority:"P0",description:"Fulfillment (DB writes, access grants) must happen via webhook, not on the success redirect page.",fixCost:100,fixSize:"M",async run(e){let t=[/success/i,/thank/i,/payment.*confirm/i],i=e.files.filter(n=>(n.includes("page.")||n.includes("index."))&&t.some(a=>a.test(n)));if(i.length===0)return{result:"UNKNOWN",message:"No success/thank-you pages found"};let s=/\.insert\(|\.update\(|\.upsert\(|createSubscription|grantAccess|activateUser|fulfillOrder|UPDATE.*SET|INSERT.*INTO/i,r=[];for(let n of i){let o;try{o=await e.readFile(n)}catch{continue}let a=o.split(`
7
- `);for(let c=0;c<a.length;c++)s.test(a[c])&&r.push({file:n,line:c+1,content:a[c].trim()})}return r.length>0?{result:"FAIL",message:`Fulfillment logic found in success page (${r.length} location${r.length>1?"s":""}) \u2014 must use webhook instead`,evidence:r.map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,120)}`)}:{result:"PASS",message:"Success pages contain no fulfillment logic"}}};var Pt={id:"BIL-17",name:"PCI raw card data safety",module:"billing",layer:"L2",priority:"P0",description:"Raw card numbers, CVV, expiration must never touch your server. Use Stripe Elements or Checkout.",fixCost:100,fixSize:"M",async run(e){let t=/card\s*\[\s*number\s*\]|cardNumber|card_number|cvv|cvc|expiry.*month|card.*expir/i,i=/<input[^>]*(?:card|cvv|cvc|expir)/i,s=/CardElement|PaymentElement|useStripe|useElements|@stripe\/react-stripe-js|stripe\.elements/i,r=await e.grepFiles(t),n=await e.grepFiles(i),o=[...r,...n].filter(c=>!(c.content.trimStart().startsWith("//")||c.content.trimStart().startsWith("*")||c.content.trimStart().startsWith("#")||c.file.includes("test")||c.file.includes("spec")||c.file.includes(".md"))),a=await e.grepFiles(s);return o.length>0&&a.length===0?{result:"FAIL",message:`Raw card data handling detected without Stripe Elements (${o.length} location${o.length>1?"s":""})`,evidence:o.slice(0,5).map(c=>`${c.file}:${c.line} \u2192 ${c.content.substring(0,120)}`)}:a.length>0?{result:"PASS",message:"Stripe Elements/Checkout detected \u2014 card data handled by Stripe"}:{result:"UNKNOWN",message:"No card data handling or Stripe Elements detected"}}};var wt={id:"BIL-18",name:"Refund/dispute handling",module:"billing",layer:"L2",priority:"P1",description:"Webhook must handle charge.refunded and charge.dispute.created to revoke access and update billing state.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(a=>/webhook/i.test(a));if(t.length===0)return{result:"UNKNOWN",message:"No webhook handler files found"};let i=/charge\.refunded|refund/i,s=/charge\.dispute|dispute/i,r=await e.grepFiles(i,t),n=await e.grepFiles(s,t);return r.length>0&&n.length>0?{result:"PASS",message:"Both refund and dispute handling detected in webhook"}:r.length>0||n.length>0?{result:"PASS",message:"Partial refund/dispute handling detected"}:(await e.grepFiles(/stripe/i,t)).length===0?{result:"UNKNOWN",message:"No Stripe webhook handler found"}:{result:"FAIL",message:"No refund or dispute handling in webhook \u2014 access may persist after refund",evidence:["Missing: charge.refunded and charge.dispute.created event handlers"]}}};var Rt={id:"BIL-19",name:"Stripe API version pinning",module:"billing",layer:"L2",priority:"P2",description:"Pin Stripe API version to avoid breaking changes from automatic upgrades.",fixCost:100,fixSize:"M",async run(e){let t=/apiVersion|api_version/i;if((await e.grepFiles(t)).length>0)return{result:"PASS",message:"Stripe API version pinning detected"};let s=await e.grepFiles(/new\s+Stripe\s*\(|Stripe\s*\(/i);return s.length===0?{result:"UNKNOWN",message:"No Stripe initialization found"}:{result:"FAIL",message:"Stripe initialized without explicit API version \u2014 may break on Stripe upgrades",evidence:s.slice(0,2).map(r=>`${r.file}:${r.line} \u2192 ${r.content.substring(0,120)}`)}}};var Lt={id:"BIL-20",name:"Portal session auth",module:"billing",layer:"L2",priority:"P1",description:"Billing portal session must use customer ID from authenticated user, not from client request.",fixCost:100,fixSize:"M",async run(e){let t=/billingPortal|billing_portal|customer_portal/i,i=await e.grepFiles(t);if(i.length===0)return{result:"UNKNOWN",message:"No billing portal usage found"};let s=/getUser|getSession|auth\(\)|requireAuth/i,r=[...new Set(i.map(o=>o.file))];return(await e.grepFiles(s,r)).length>0?{result:"PASS",message:"Billing portal session created with authenticated customer ID"}:{result:"FAIL",message:"Billing portal session created without verifying authenticated user",evidence:i.slice(0,3).map(o=>`${o.file}:${o.line} \u2192 ${o.content.substring(0,120)}`)}}};var Nt={id:"BIL-21",name:"Webhook event coverage",module:"billing",layer:"L2",priority:"P1",description:"Webhook handler must process at minimum 3 core subscription events. Missing events = state drift.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>/webhook/i.test(n));if(t.length===0)return{result:"UNKNOWN",message:"No webhook handler files found"};let i=[{pattern:/checkout\.session\.completed/i,name:"checkout.session.completed"},{pattern:/customer\.subscription\.updated/i,name:"customer.subscription.updated"},{pattern:/customer\.subscription\.deleted/i,name:"customer.subscription.deleted"},{pattern:/invoice\.payment_succeeded/i,name:"invoice.payment_succeeded"},{pattern:/invoice\.payment_failed/i,name:"invoice.payment_failed"}],s=[],r=[];for(let n of i)(await e.grepFiles(n.pattern,t)).length>0?s.push(n.name):r.push(n.name);return s.length===0?(await e.grepFiles(/stripe/i,t)).length===0?{result:"UNKNOWN",message:"Webhook files found but no Stripe event handling"}:{result:"FAIL",message:"Stripe webhook handler found but no subscription events handled",evidence:["Missing: checkout.session.completed, subscription.updated, subscription.deleted"]}:s.length<3?{result:"FAIL",message:`Only ${s.length}/5 core webhook events handled`,evidence:[`Handled: ${s.join(", ")}`,`Missing: ${r.join(", ")}`]}:{result:"PASS",message:`${s.length}/5 core webhook events handled`}}};var Ft={id:"BIL-22",name:"Trial period handling",module:"billing",layer:"L2",priority:"P2",description:"If trials are offered, handle trial_will_end webhook and trial-to-paid conversion properly.",fixCost:100,fixSize:"M",async run(e){let t=/trial_period_days|trial_end|trialing|trial_will_end|trialDays/i,i=await e.grepFiles(t);return i.length>=2?{result:"PASS",message:"Trial period handling detected"}:i.length===1?{result:"PASS",message:"Trial reference found (verify trial_will_end webhook is handled)"}:(await e.grepFiles(/subscription|stripe/i)).length===0?{result:"UNKNOWN",message:"No subscription code found"}:{result:"N/A",message:"No trial period usage detected"}}};var xt={id:"BIL-23",name:"Card testing protection",module:"billing",layer:"L2",priority:"P2",description:"Rate limiting on payment endpoints prevents card testing attacks (automated validation of stolen cards).",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>/checkout|payment|billing|subscribe/i.test(n));if(t.length===0)return{result:"UNKNOWN",message:"No payment endpoint files found"};let i=/rateLimit|rate.limit|throttle|limiter|too.many.requests|429|upstash/i;return(await e.grepFiles(i,t)).length>0?{result:"PASS",message:"Rate limiting detected on payment endpoints"}:(await e.grepFiles(i)).length>0?{result:"PASS",message:"Rate limiting detected in codebase (verify it covers payment endpoints)"}:{result:"FAIL",message:"No rate limiting found on payment endpoints \u2014 vulnerable to card testing",evidence:["Missing: rate limiting middleware on checkout/payment routes"]}}};var Et={id:"BIL-24",name:"Metered billing usage reporting",module:"billing",layer:"L2",priority:"P2",description:"If using metered pricing, usage must be reported to Stripe. Missing reporting = customers never billed for usage.",fixCost:100,fixSize:"M",async run(e){let t=/metered|usage_type|usageRecord|usage.*report|createUsageRecord|meter/i,i=await e.grepFiles(t);return i.length>=2?{result:"PASS",message:"Metered billing usage reporting detected"}:i.length===1?{result:"PASS",message:"Metered billing reference found"}:{result:"N/A",message:"No metered billing usage detected"}}};var Tt={id:"BIL-25",name:"Subscription table user-writable",module:"billing",layer:"L2",priority:"P0",description:"RLS policies on billing tables (subscriptions, plans, credits) must not allow authenticated users to UPDATE or INSERT plan/status columns. User-writable billing state enables subscription fraud (Free \u2192 Pro without payment).",fixCost:250,fixSize:"L",async run(e){let t=["subscriptions","plans","credits","billing"],i=/create\s+table\s+(?:public\.)?(subscriptions|plans|credits|billing)/i,s=e.files.filter(l=>/\.sql$/i.test(l)||/supabase.*migration/i.test(l)),r=await e.grepFiles(i,s.length>0?s:void 0);if(r.length===0){let l=await e.grepFiles(i);if(l.length===0)return{result:"PASS",message:"No billing tables (subscriptions/plans/credits) found"};r.push(...l)}let n=new Set(r.map(l=>{let d=l.content.match(/(?:public\.)?(subscriptions|plans|credits|billing)/i);return d?d[1].toLowerCase():null}).filter(Boolean)),o=[];for(let l of n)for(let d of s.length>0?s:e.files.filter(f=>/\.sql$/i.test(f))){let f;try{f=await e.readFile(d)}catch{continue}let m=new RegExp(`create\\s+policy[^;]*?on\\s+(?:public\\.)?${l}\\s+for\\s+(update|insert|all)`,"gis"),p;for(;(p=m.exec(f))!==null;){let y=Math.max(0,p.index),M=f.substring(y,f.indexOf(";",p.index)+1||y+500),$=/auth\.uid\(\)/i.test(M),F=/with\s+check\s*\([^)]*(?:false|service_role|is_admin)/i.test(M);if($&&!F){let ie=f.substring(0,p.index).split(`
5
+ `);for(let c=0;c<a.length;c++)t.test(a[c])&&s.push({file:n,line:c+1,content:a[c].trim()})}return s}function De(e,t){if(t.startsWith("!"))return!De(e,t.slice(1));if(t.startsWith("**/")){let i=t.slice(3);return e.includes(i)}if(t.endsWith("/**")){let i=t.slice(0,-3);return e.startsWith(i)}return t.includes("*")?new RegExp("^"+t.replace(/\*/g,".*")+"$").test(e):e===t||e.startsWith(t+"/")}function w(e){return e.startsWith("src/")||e.startsWith("components/")||e.startsWith("app/")||e.startsWith("pages/")||e.includes("/components/")||e.includes("/hooks/")||e.includes("/contexts/")}function x(e){return e.includes("/api/")||e.includes("route.ts")||e.includes("route.js")||e.includes("server/")||e.includes("actions.ts")||e.includes("actions.js")}function v(e){return e.includes("migration")||e.includes("supabase/")||e.endsWith(".sql")}function U(e){return(e.split("/").pop()||"").startsWith(".env")}var Me={id:"AUTH-01",name:"service_role key not in client code",module:"auth",layer:"L2",priority:"P0",aliases:["ADM-17"],description:"service_role bypasses all RLS \u2014 must never appear in client-side code or NEXT_PUBLIC_ vars",fixCost:100,fixSize:"M",async run(e){let t=/service_role|SERVICE_ROLE|SUPABASE_SERVICE_ROLE|sb_secret_/i,i=await e.grepFiles(t),s=i.filter(c=>c.content.includes("NEXT_PUBLIC_")?!0:c.file.includes("use client")?!1:!!(w(c.file)&&!c.file.includes("/api/")&&!c.file.includes("route."))),r=i.filter(c=>c.content.includes("NEXT_PUBLIC_")&&/service_role|SERVICE_ROLE/i.test(c.content)),n=[...s,...r],o=[...new Map(n.map(c=>[`${c.file}:${c.line}`,c])).values()];return o.length>0?{result:"FAIL",message:`service_role key found in client-accessible code (${o.length} location${o.length>1?"s":""})`,evidence:o.map(c=>`${c.file}:${c.line} \u2192 ${c.content.substring(0,120)}`)}:i.filter(c=>!c.content.startsWith("//")&&!c.content.startsWith("#")&&!c.content.startsWith("*")).length>0?{result:"PASS",message:"service_role key found only in server-side code"}:{result:"UNKNOWN",message:"No service_role references found \u2014 Supabase may not be used"}}};var Ue={id:"AUTH-02",name:"RLS enabled on all tables",module:"auth",layer:"L2",priority:"P0",description:"Every table must have Row Level Security enabled. Without RLS, anon key = full DB access.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(v);if(t.length===0)return{result:"UNKNOWN",message:"No SQL migration files found"};let i=/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:public\.)?(\w+)/gi,s=/ALTER\s+TABLE\s+(?:public\.)?(\w+)\s+ENABLE\s+ROW\s+LEVEL\s+SECURITY/gi,r=new Set(["schema_migrations","_prisma_migrations","migrations"]),n=new Set,o=new Set;for(let c of t){let l;try{l=await e.readFile(c)}catch{continue}let d,f=new RegExp(i.source,"gi");for(;(d=f.exec(l))!==null;){let p=d[1].toLowerCase();r.has(p)||n.add(p)}let m=new RegExp(s.source,"gi");for(;(d=m.exec(l))!==null;)o.add(d[1].toLowerCase())}if(n.size===0)return{result:"UNKNOWN",message:"No CREATE TABLE statements found in migrations"};let a=[...n].filter(c=>!o.has(c));return a.length>0?{result:"FAIL",message:`${a.length} table${a.length>1?"s":""} without RLS (of ${n.size} total)`,evidence:a.map(c=>`Table "${c}" \u2014 missing ENABLE ROW LEVEL SECURITY`)}:{result:"PASS",message:`All ${n.size} tables have RLS enabled`}}};var je={id:"AUTH-03",name:"RLS policies have WITH CHECK",module:"auth",layer:"L2",priority:"P0",description:"INSERT/UPDATE policies need WITH CHECK clause. USING alone lets users insert data owned by others.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(v);if(t.length===0)return{result:"UNKNOWN",message:"No SQL migration files found"};let i=[],s=[];for(let a of t){let c;try{c=await e.readFile(a)}catch{continue}let l=/CREATE\s+POLICY\s+"?(\w+)"?\s+ON\s+(?:public\.)?(\w+)\s+(?:AS\s+\w+\s+)?FOR\s+(INSERT|UPDATE|ALL)\b([\s\S]*?)(?=CREATE\s+POLICY|$)/gi,d;for(;(d=l.exec(c))!==null;){let f=d[1],m=d[2],p=d[3].toUpperCase(),S=d[4];(p==="INSERT"||p==="UPDATE"||p==="ALL")&&(/WITH\s+CHECK\s*\(\s*true\s*\)/i.test(S)?i.push(`${a}: policy "${f}" on ${m} \u2014 WITH CHECK (true) is permissive`):/WITH\s+CHECK/i.test(S)||s.push(`${a}: policy "${f}" on ${m} (${p}) \u2014 missing WITH CHECK`))}}let r=[...i,...s];if(r.length>0)return{result:"FAIL",message:`${r.length} RLS policy issue${r.length>1?"s":""} found`,evidence:r.slice(0,5)};let n=t.some(a=>{try{let c=e.files.includes(a)?a:"";return/CREATE\s+POLICY/i.test(c)}catch{return!1}});return(await e.grepFiles(/CREATE\s+POLICY/i)).length===0?{result:"UNKNOWN",message:"No RLS policies found in migrations"}:{result:"PASS",message:"All INSERT/UPDATE RLS policies have proper WITH CHECK clauses"}}};var Oe={id:"AUTH-04",name:"Server-side auth on protected routes",module:"auth",layer:"L2",priority:"P0",description:"Every protected API route/server action must verify auth server-side. Middleware-only auth is bypassable (CVE-2025-29927).",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>(n.includes("route.")||n.includes("actions."))&&x(n));if(t.length===0)return{result:"UNKNOWN",message:"No API route handlers found"};let i=/getUser|getSession|auth\(\)|verifyToken|requireAuth|requireAdmin|checkPermission|requireRole|currentUser|validateSession/i,s=/webhook|health|status|public|og|sitemap|robots|favicon|_next/i,r=[];for(let n of t){if(s.test(n))continue;let o;try{o=await e.readFile(n)}catch{continue}i.test(o)||r.push(n)}return r.length>0?{result:"FAIL",message:`${r.length} API route${r.length>1?"s":""} without server-side auth check`,evidence:r.slice(0,5).map(n=>`${n} \u2014 no auth verification detected`)}:{result:"PASS",message:`All ${t.length} API routes have server-side auth checks`}}};var We={id:"AUTH-05",name:"No secrets with NEXT_PUBLIC_ prefix",module:"auth",layer:"L2",priority:"P0",description:"NEXT_PUBLIC_ vars are bundled in the browser. Secrets must never use this prefix.",fixCost:100,fixSize:"M",async run(e){let t=[/NEXT_PUBLIC_.*SERVICE_ROLE/i,/NEXT_PUBLIC_.*SECRET/i,/NEXT_PUBLIC_.*DATABASE_URL/i,/NEXT_PUBLIC_.*DB_URL/i,/NEXT_PUBLIC_.*PRIVATE/i,/NEXT_PUBLIC_.*PASSWORD/i,/NEXT_PUBLIC_.*JWT_SECRET/i,/VITE_.*SERVICE_ROLE/i,/VITE_.*SECRET_KEY/i,/VITE_.*DATABASE_URL/i,/EXPO_PUBLIC_.*SERVICE_ROLE/i,/EXPO_PUBLIC_.*SECRET/i],i=e.files.filter(U),s=[];for(let r of i){let n;try{n=await e.readFile(r)}catch{continue}let o=n.split(`
6
+ `);for(let a=0;a<o.length;a++){let c=o[a].trim();if(!(c.startsWith("#")||!c)){for(let l of t)if(l.test(c)){s.push({file:r,line:a+1,content:c.split("=")[0]});break}}}}return s.length>0?{result:"FAIL",message:`Secret keys exposed via public prefix (${s.length} found)`,evidence:s.map(r=>`${r.file}:${r.line} \u2192 ${r.content}`)}:i.length===0?{result:"UNKNOWN",message:"No .env files found to check"}:{result:"PASS",message:"No secrets found with NEXT_PUBLIC_/VITE_/EXPO_PUBLIC_ prefix"}}};var Be={id:"AUTH-06",name:"Protected routes redirect unauthenticated users",module:"auth",layer:"L2",priority:"P1",description:"Middleware or route guard must redirect unauthenticated users to /login for protected pages.",fixCost:100,fixSize:"M",async run(e){let t=e.files.find(s=>/^middleware\.(ts|js)$/.test(s));if(!t)return(await e.grepFiles(/redirect.*login|redirect.*auth|redirect.*sign/i)).length>0?{result:"PASS",message:"Route guards with login redirect detected"}:{result:"FAIL",message:"No middleware.ts and no route guards found \u2014 unauthenticated users can access protected pages",evidence:["Missing: middleware.ts or per-page auth redirect"]};let i;try{i=await e.readFile(t)}catch{return{result:"UNKNOWN",message:"Could not read middleware file"}}return/redirect.*login|redirect.*auth|NextResponse.*redirect/i.test(i)?{result:"PASS",message:"Middleware redirects unauthenticated users to login"}:{result:"FAIL",message:"Middleware exists but no login redirect detected",evidence:[`${t} \u2014 no redirect to /login or /auth`]}}};var He={id:"AUTH-07",name:"Session tokens in httpOnly cookies",module:"auth",layer:"L2",priority:"P2",description:"Tokens in localStorage are accessible via XSS. httpOnly cookies prevent JavaScript access to session tokens.",fixCost:100,fixSize:"M",async run(e){let t=/localStorage.*(?:token|session|jwt|access_token)|sessionStorage.*(?:token|session|jwt)/i,i=/httpOnly|cookie.*session|supabase.*ssr|@supabase\/ssr/i,s=await e.grepFiles(t),r=await e.grepFiles(i),n=s.filter(o=>!o.content.trimStart().startsWith("//"));return n.length>0&&r.length===0?{result:"FAIL",message:`Session tokens stored in localStorage (${n.length} location${n.length>1?"s":""})`,evidence:n.slice(0,3).map(o=>`${o.file}:${o.line} \u2192 ${o.content.substring(0,120)}`)}:r.length>0?{result:"PASS",message:"httpOnly cookies or @supabase/ssr detected for session management"}:{result:"UNKNOWN",message:"No explicit token storage pattern detected"}}};var ze={id:"AUTH-08",name:"Password hashing (if custom auth)",module:"auth",layer:"L2",priority:"P0",description:"Passwords must be hashed with bcrypt/argon2/scrypt. N/A if using Supabase Auth (handles hashing internally).",fixCost:100,fixSize:"M",async run(e){let t=/supabase|@supabase|createBrowserClient|createServerClient/i;if((await e.grepFiles(t)).length>0)return{result:"N/A",message:"Supabase Auth handles password hashing internally (bcrypt/Argon2)"};let s=/bcrypt|argon2|scrypt|pbkdf2/i,r=/password.*=.*req\.body|password.*=.*body\.|plaintext|md5.*password|sha1.*password/i,n=await e.grepFiles(s),o=await e.grepFiles(r);return o.length>0&&n.length===0?{result:"FAIL",message:"Password handling without secure hashing detected",evidence:o.slice(0,3).map(a=>`${a.file}:${a.line} \u2192 ${a.content.substring(0,120)}`)}:n.length>0?{result:"PASS",message:"Secure password hashing detected (bcrypt/argon2/scrypt)"}:{result:"UNKNOWN",message:"No custom auth password handling detected"}}};var qe={id:"AUTH-09",name:"Rate limiting on auth endpoints",module:"auth",layer:"L2",priority:"P1",description:"Login/register endpoints without rate limiting are vulnerable to brute force attacks.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(o=>/auth|login|register|sign/i.test(o)),i=/rateLimit|rate.limit|throttle|limiter|too.many.requests|429|upstash/i;return t.length===0?{result:"UNKNOWN",message:"No auth endpoint files found"}:(await e.grepFiles(i,t)).length>0?{result:"PASS",message:"Rate limiting detected on auth endpoints"}:(await e.grepFiles(i)).length>0?{result:"PASS",message:"Rate limiting detected in codebase (verify it covers auth endpoints)"}:(await e.grepFiles(/supabase.*auth|@supabase/i)).length>0?{result:"PASS",message:"Supabase Auth has built-in rate limiting for auth endpoints"}:{result:"FAIL",message:"No rate limiting found on auth endpoints",evidence:["Missing: rate limiting middleware on login/register routes"]}}};var Ke={id:"AUTH-10",name:"Profile sync trigger with safe search_path",module:"auth",layer:"L2",priority:"P1",description:"handle_new_user() trigger must use SECURITY DEFINER with restricted search_path to prevent privilege escalation.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(v);if(t.length===0)return{result:"UNKNOWN",message:"No SQL migration files found"};let i=/handle_new_user|on_auth_user_created|AFTER\s+INSERT\s+ON\s+auth\.users/i,s=/SECURITY\s+DEFINER/i,r=/search_path|SET\s+search_path/i;if((await e.grepFiles(i)).length===0)return{result:"FAIL",message:"No profile sync trigger found (handle_new_user or equivalent)",evidence:["Missing trigger: new auth.users rows won't sync to profiles table"]};for(let o of t){let a;try{a=await e.readFile(o)}catch{continue}if(i.test(a)){let c=s.test(a),l=r.test(a);return c&&!l?{result:"FAIL",message:"Profile trigger uses SECURITY DEFINER without restricted search_path",evidence:[`${o} \u2014 SECURITY DEFINER without SET search_path = '' (privilege escalation risk)`]}:c&&l?{result:"PASS",message:"Profile sync trigger found with SECURITY DEFINER and restricted search_path"}:{result:"PASS",message:"Profile sync trigger found"}}}return{result:"PASS",message:"Profile sync trigger detected"}}};var Ge={id:"AUTH-11",name:"Client/server auth separation",module:"auth",layer:"L2",priority:"P0",description:"Separate Supabase clients for browser and server. One shared client leaks service_role to browser or uses anon key on server.",fixCost:100,fixSize:"M",async run(e){let t=/createBrowserClient|createClient.*browser/i,i=/createServerClient|createClient.*server/i,s=/createClient\s*\(/,r=await e.grepFiles(t),n=await e.grepFiles(i),o=await e.grepFiles(s);if(r.length>0&&n.length>0)return{result:"PASS",message:"Separate browser and server Supabase clients detected"};if(r.length>0||n.length>0)return{result:"PASS",message:"Dedicated Supabase client pattern detected (@supabase/ssr)"};if(o.length>0){let a=o.filter(c=>/supabase|createClient/.test(c.content));if(a.length>0)return{result:"FAIL",message:"Single generic createClient() used \u2014 no client/server separation",evidence:a.slice(0,3).map(c=>`${c.file}:${c.line} \u2192 ${c.content.substring(0,120)}`)}}return{result:"UNKNOWN",message:"No Supabase client initialization found"}}};var Ve={id:"AUTH-12",name:"Auth environment variables present",module:"auth",layer:"L2",priority:"P2",description:"Missing auth env vars = hardcoded keys or broken auth in production.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(U);if(t.length===0)return{result:"UNKNOWN",message:"No .env files found"};let i=["SUPABASE_URL","SUPABASE_ANON_KEY"],s=[];for(let o of t){let a;try{a=await e.readFile(o)}catch{continue}for(let c of i)a.includes(c)&&!s.includes(c)&&s.push(c)}if((await e.grepFiles(/supabase/i)).length===0)return{result:"UNKNOWN",message:"No Supabase references found"};let n=i.filter(o=>!s.includes(o));return n.length>0?{result:"FAIL",message:`Missing auth env vars: ${n.join(", ")}`,evidence:n.map(o=>`${o} not found in any .env file`)}:{result:"PASS",message:"Auth environment variables configured"}}};var Ye={id:"AUTH-13",name:"getUser() not getSession() for server-side auth",module:"auth",layer:"L2",priority:"P0",description:"getSession() reads JWT without server verification. Use getUser() for auth decisions.",fixCost:100,fixSize:"M",async run(e){let t=/\.auth\.getSession\s*\(/,i=/\.auth\.getUser\s*\(/,s=await e.grepFiles(t),r=await e.grepFiles(i);if(s.length===0&&r.length===0)return{result:"UNKNOWN",message:"No getSession/getUser calls found \u2014 Supabase Auth may not be used"};let n=s.filter(l=>x(l.file)),o=r.filter(l=>x(l.file));if(n.length>0&&o.length===0)return{result:"FAIL",message:`Server-side code uses getSession() without getUser() (${n.length} location${n.length>1?"s":""})`,evidence:n.map(l=>`${l.file}:${l.line} \u2192 ${l.content.substring(0,120)}`)};if(n.length>0&&o.length>0)return{result:"FAIL",message:"Server code uses both getSession() and getUser() \u2014 getSession() in server context is insecure",evidence:n.map(l=>`${l.file}:${l.line} \u2192 ${l.content.substring(0,120)}`)};let a=s.filter(l=>!x(l.file)&&!/supabase\/functions\//i.test(l.file)),c=r.filter(l=>!x(l.file)&&!/supabase\/functions\//i.test(l.file));return a.length>0&&c.length===0&&o.length===0?{result:"FAIL",message:`Client code uses getSession() (${a.length} location${a.length>1?"s":""}) but getUser() never called \u2014 auth relies on unverified JWT`,evidence:a.slice(0,3).map(l=>`${l.file}:${l.line} \u2192 ${l.content.substring(0,120)}`)}:o.length>0||c.length>0?{result:"PASS",message:"Auth uses getUser() for verification"}:{result:"PASS",message:"getUser() is used for auth verification"}}};var Xe={id:"AUTH-14",name:"No eval() or dangerouslySetInnerHTML with user data",module:"auth",layer:"L2",priority:"P0",description:"eval() and dangerouslySetInnerHTML enable XSS attacks \u2014 session theft and account takeover.",fixCost:100,fixSize:"M",async run(e){let t=/\beval\s*\(/,i=/dangerouslySetInnerHTML/,s=await e.grepFiles(t),r=await e.grepFiles(i),n=s.filter(c=>!(c.content.trimStart().startsWith("//")||c.content.trimStart().startsWith("*")||c.file.includes("node_modules"))),o=r.filter(c=>!(c.content.trimStart().startsWith("//")||c.file.includes("node_modules"))),a=[...n,...o];return a.length>0?{result:"FAIL",message:`Unsafe code patterns found (${n.length} eval, ${o.length} dangerouslySetInnerHTML)`,evidence:a.slice(0,5).map(c=>`${c.file}:${c.line} \u2192 ${c.content.substring(0,120)}`)}:{result:"PASS",message:"No eval() or dangerouslySetInnerHTML found"}}};var Je={id:"AUTH-15",name:"CORS configuration",module:"auth",layer:"L2",priority:"P2",description:"Access-Control-Allow-Origin: * with credentials allows any website to read auth cookies and data.",fixCost:100,fixSize:"M",async run(e){let t=/Access-Control-Allow-Origin.*\*|cors.*origin.*\*|origin:\s*['"]?\*/i,i=/credentials.*true|allowCredentials|Access-Control-Allow-Credentials/i,s=await e.grepFiles(t),r=await e.grepFiles(i);return s.length>0&&r.length>0?{result:"FAIL",message:"CORS wildcard (*) used with credentials \u2014 any website can read auth data",evidence:s.slice(0,3).map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,120)}`)}:s.length>0?{result:"FAIL",message:"CORS wildcard (*) detected \u2014 consider restricting to specific origins",evidence:s.slice(0,3).map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,120)}`)}:{result:"PASS",message:"No dangerous CORS wildcard configuration detected"}}};var Qe={id:"AUTH-16",name:"Token/session expiration configured",module:"auth",layer:"L2",priority:"P2",description:"Infinite sessions mean a stolen token works forever. Configure JWT expiration and session timeouts.",fixCost:100,fixSize:"M",async run(e){let t=/expiresIn|maxAge|session.*expir|jwt.*expir|SESSION_EXPIRY|JWT_EXPIRY|token.*expir/i;return(await e.grepFiles(t)).length>0?{result:"PASS",message:"Token/session expiration configured"}:(await e.grepFiles(/supabase/i)).length>0?{result:"PASS",message:"Supabase Auth has default JWT expiry (3600s)"}:{result:"FAIL",message:"No token/session expiration configuration found",evidence:["Missing: expiresIn, maxAge, or session expiry configuration"]}}};var Ze={id:"AUTH-17",name:"Storage bucket RLS",module:"auth",layer:"L2",priority:"P0",description:"Supabase storage buckets must have RLS on storage.objects. Without it, all files are publicly accessible.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(v);if(t.length===0)return{result:"UNKNOWN",message:"No SQL migration files found"};let i=/storage\.buckets|create.*bucket|INSERT.*storage\.buckets/i,s=/storage\.objects.*ENABLE.*ROW.*LEVEL|RLS.*storage\.objects|POLICY.*storage\.objects/i,r=await e.grepFiles(i,t),n=await e.grepFiles(s,t);return r.length===0?(await e.grepFiles(/supabase.*storage|storage\.from\(/i)).length===0?{result:"N/A",message:"No Supabase storage usage detected"}:{result:"UNKNOWN",message:"Storage used in code but no bucket creation in migrations"}:n.length>0?{result:"PASS",message:"Storage bucket RLS policies detected"}:{result:"FAIL",message:"Storage buckets created without RLS on storage.objects \u2014 files may be publicly accessible",evidence:r.slice(0,3).map(o=>`${o.file}:${o.line} \u2192 ${o.content.substring(0,120)}`)}}};var et={id:"AUTH-18",name:"RBAC in app_metadata not user_metadata",module:"auth",layer:"L2",priority:"P0",description:"Roles in user_metadata are user-editable. Use app_metadata for RBAC (server-only).",fixCost:100,fixSize:"M",async run(e){let t=/user_meta_?data.*role|raw_user_meta_?data.*role|user\.user_metadata.*role/i,i=/app_meta_?data.*role|user\.app_metadata.*role/i,s=/updateUser\s*\(\s*\{[\s\S]*?data\s*:\s*\{[\s\S]*?role/,r=await e.grepFiles(t),n=await e.grepFiles(i),o=await e.grepFiles(s);return r.length>0?{result:"FAIL",message:`Role stored in user_metadata (user-editable) \u2014 ${r.length} location${r.length>1?"s":""}`,evidence:r.map(a=>`${a.file}:${a.line} \u2192 ${a.content.substring(0,120)}`)}:o.length>0?{result:"FAIL",message:"Role set via updateUser() data field (user-editable user_metadata)",evidence:o.map(a=>`${a.file}:${a.line} \u2192 ${a.content.substring(0,120)}`)}:n.length>0?{result:"PASS",message:"Role stored in app_metadata (server-only, not user-editable)"}:{result:"UNKNOWN",message:"No role/RBAC references found in codebase"}}};var tt={id:"AUTH-19",name:"Multi-tenancy data isolation",module:"auth",layer:"L2",priority:"P1",description:"RLS policies must include tenant_id or organization_id to prevent cross-tenant data access.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(v),i=/tenant_id|organization_id|org_id|team_id|workspace_id/i,s=await e.grepFiles(i,t.length>0?t:void 0),r=await e.grepFiles(i);return s.length===0&&r.length===0?{result:"N/A",message:"No multi-tenancy pattern detected (single-tenant app)"}:(await e.grepFiles(/POLICY[\s\S]*?tenant_id|POLICY[\s\S]*?org_id|POLICY[\s\S]*?organization_id/i,t)).length>0?{result:"PASS",message:"RLS policies include tenant isolation"}:{result:"FAIL",message:"Multi-tenant schema detected but RLS policies don't include tenant_id filtering",evidence:["Tenant columns exist but RLS policies may allow cross-tenant data access"]}}};var st={id:"AUTH-20",name:"OAuth domain restriction",module:"auth",layer:"L2",priority:"P1",description:"Social login (Google, GitHub) should restrict allowed email domains to prevent unauthorized access.",fixCost:100,fixSize:"M",async run(e){let t=/signInWithOAuth|signIn.*provider|google|github.*login|oauth/i,i=/allowedDomain|domain.*restrict|email.*domain|hd=|hosted_domain/i,s=await e.grepFiles(t);return s.length===0?{result:"N/A",message:"No OAuth/social login detected"}:(await e.grepFiles(i)).length>0?{result:"PASS",message:"OAuth domain restriction detected"}:{result:"FAIL",message:"OAuth login enabled without domain restriction \u2014 any Google/GitHub account can sign in",evidence:s.slice(0,3).map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,120)}`)}}};var it={id:"AUTH-21",name:"Force dynamic on auth routes",module:"auth",layer:"L2",priority:"P1",description:"Auth routes must use force-dynamic to prevent ISR cache serving wrong user data.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>/auth|login|register|signup|sign-in|sign-up/i.test(n)&&(n.includes("page.")||n.includes("route."))&&(n.endsWith(".ts")||n.endsWith(".tsx")||n.endsWith(".js")||n.endsWith(".jsx")));if(t.length===0)return{result:"UNKNOWN",message:"No auth route/page files found"};let i=/export\s+const\s+dynamic\s*=\s*['"]force-dynamic['"]/,s=/unstable_noStore|noStore|revalidate\s*=\s*0/,r=[];for(let n of t){let o;try{o=await e.readFile(n)}catch{continue}!i.test(o)&&!s.test(o)&&n.includes("page.")&&r.push(n)}return r.length>0?{result:"FAIL",message:`${r.length} auth page${r.length>1?"s":""} without force-dynamic \u2014 may serve cached auth state`,evidence:r.slice(0,5).map(n=>`${n} \u2014 missing export const dynamic = 'force-dynamic'`)}:{result:"PASS",message:"Auth routes use force-dynamic or equivalent"}}};var nt={id:"AUTH-22",name:"CSRF protection on Route Handlers",module:"auth",layer:"L2",priority:"P2",description:"Server Actions have built-in CSRF protection (Next.js 14+). Route Handlers do NOT \u2014 they need explicit CSRF tokens or SameSite cookies.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(o=>/route\.(ts|js)$/i.test(o));if(t.length===0)return{result:"UNKNOWN",message:"No Route Handler files found"};let i=[];for(let o of t){let a;try{a=await e.readFile(o)}catch{continue}/export\s+(?:async\s+)?function\s+(?:POST|PUT|PATCH|DELETE)/i.test(a)&&i.push(o)}if(i.length===0)return{result:"PASS",message:"No mutation Route Handlers found (only GET)"};let s=/csrf|csurf|csrfToken|SameSite|x-csrf|anti.?forgery/i;return(await e.grepFiles(s)).length>0?{result:"PASS",message:"CSRF protection detected"}:(await e.grepFiles(/["']use server["']/i)).length>0&&i.length<=2?{result:"PASS",message:"Mutations use Server Actions (built-in CSRF protection)"}:{result:"FAIL",message:`${i.length} mutation Route Handler${i.length>1?"s":""} without CSRF protection`,evidence:i.slice(0,3).map(o=>`${o} \u2014 POST/PUT/PATCH/DELETE without CSRF token`)}}};var rt={id:"AUTH-23",name:"Email verification required",module:"auth",layer:"L2",priority:"P2",description:"Without email verification, anyone can sign up with any email \u2014 spam accounts and impersonation.",fixCost:100,fixSize:"M",async run(e){let t=/email_confirmed_at|emailConfirmed|verifyEmail|confirmEmail|email.*verif|verification.*email/i;return(await e.grepFiles(t)).length>0?{result:"PASS",message:"Email verification check detected"}:(await e.grepFiles(/supabase/i)).length>0?{result:"PASS",message:"Supabase Auth has configurable email verification (check dashboard settings)"}:{result:"FAIL",message:"No email verification enforcement found",evidence:["Missing: email_confirmed_at check or equivalent verification flow"]}}};var ot={id:"AUTH-24",name:"Account enumeration prevention",module:"auth",layer:"L2",priority:"P2",description:"Login/register error messages must not reveal whether an email exists. Consistent messages prevent user enumeration.",fixCost:100,fixSize:"M",async run(e){let t=/email.*not.*found|user.*not.*found|no.*account.*with|email.*already.*registered|email.*already.*exists|account.*already.*exists/i,s=(await e.grepFiles(t)).filter(r=>!r.content.trimStart().startsWith("//")&&!r.content.trimStart().startsWith("*"));return s.length>0?{result:"FAIL",message:`Account enumeration possible \u2014 error messages reveal email existence (${s.length} location${s.length>1?"s":""})`,evidence:s.slice(0,3).map(r=>`${r.file}:${r.line} \u2192 ${r.content.substring(0,120)}`)}:{result:"PASS",message:"No account enumeration patterns detected in error messages"}}};var at={id:"AUTH-25",name:"Refresh token reuse detection",module:"auth",layer:"L2",priority:"P2",description:"Token rotation prevents stolen refresh tokens from being reused. Supabase supports this via config.",fixCost:100,fixSize:"M",async run(e){let t=/token.*rotation|refresh.*token.*reuse|reuse.*detection|GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL/i;return(await e.grepFiles(t)).length>0?{result:"PASS",message:"Refresh token rotation/reuse detection configured"}:(await e.grepFiles(/supabase/i)).length>0?{result:"PASS",message:"Supabase Auth has built-in refresh token rotation (check dashboard config)"}:{result:"UNKNOWN",message:"No refresh token configuration detected"}}};var ct={id:"AUTH-26",name:"Sign-out revokes server session",module:"auth",layer:"L2",priority:"P2",description:"Sign-out must revoke the server session (scope: 'global'), not just clear client cookies.",fixCost:100,fixSize:"M",async run(e){let t=/signOut|sign_out|logout|log_out/i,i=/scope.*global|global.*scope/i,s=await e.grepFiles(t);return s.length===0?{result:"UNKNOWN",message:"No sign-out implementation found"}:(await e.grepFiles(i)).length>0?{result:"PASS",message:"Sign-out uses global scope (revokes all sessions)"}:{result:"FAIL",message:"Sign-out found but no global scope \u2014 sessions may persist on other devices",evidence:s.slice(0,2).map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,120)}`)}}};var lt={id:"AUTH-27",name:"Email link poisoning mitigation",module:"auth",layer:"L2",priority:"P2",description:"SITE_URL and REDIRECT_ALLOW_LIST must be configured to prevent email link redirect attacks.",fixCost:100,fixSize:"M",async run(e){let t=/SITE_URL|GOTRUE_SITE_URL/i,i=/REDIRECT_ALLOW_LIST|ADDITIONAL_REDIRECT_URLS|redirect.*allow/i,s=await e.grepFiles(t),r=await e.grepFiles(i);return s.length>0&&r.length>0?{result:"PASS",message:"SITE_URL and REDIRECT_ALLOW_LIST configured"}:s.length>0?{result:"PASS",message:"SITE_URL configured (check REDIRECT_ALLOW_LIST in Supabase dashboard)"}:(await e.grepFiles(/supabase/i)).length===0?{result:"N/A",message:"No Supabase usage detected"}:{result:"FAIL",message:"No SITE_URL or REDIRECT_ALLOW_LIST found \u2014 email magic links may redirect to attacker domains",evidence:["Missing: SITE_URL and REDIRECT_ALLOW_LIST in env configuration"]}}};var ut={id:"AUTH-28",name:"Realtime presence authorization",module:"auth",layer:"L2",priority:"P2",description:"Supabase Realtime Presence channels must have authorization. Without it, any user can see who's online.",fixCost:100,fixSize:"M",async run(e){let t=/realtime|presence|channel.*subscribe|supabase.*channel/i,i=await e.grepFiles(t);if(i.length===0)return{result:"N/A",message:"No Supabase Realtime/Presence usage detected"};let s=/authorized|RLS.*realtime|realtime.*auth|channel.*auth/i;return(await e.grepFiles(s)).length>0?{result:"PASS",message:"Realtime channel authorization detected"}:{result:"FAIL",message:"Realtime/Presence channels found without authorization",evidence:i.slice(0,3).map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,120)}`)}}};var dt={id:"BIL-01",name:"Stripe secret key not in client code",module:"billing",layer:"L2",priority:"P0",description:"sk_live_/sk_test_ in client code = full Stripe API access for any visitor",fixCost:100,fixSize:"M",async run(e){let t=/sk_live_[a-zA-Z0-9]{20,}|sk_test_[a-zA-Z0-9]{20,}/,i=/STRIPE_SECRET|NEXT_PUBLIC_.*STRIPE.*SECRET|VITE_.*STRIPE.*SECRET/i,r=(await e.grepFiles(t)).filter(l=>!U(l.file)&&!l.content.trimStart().startsWith("#")&&!l.content.trimStart().startsWith("//"));if(r.length>0)return{result:"FAIL",message:`Hardcoded Stripe secret key found in source code (${r.length} location${r.length>1?"s":""})`,evidence:r.map(l=>`${l.file}:${l.line} \u2192 sk_***_[REDACTED]`)};let n=await e.grepFiles(i),o=n.filter(l=>/NEXT_PUBLIC_|VITE_|EXPO_PUBLIC_/i.test(l.content)&&/STRIPE.*SECRET/i.test(l.content));if(o.length>0)return{result:"FAIL",message:"Stripe secret key exposed via NEXT_PUBLIC_/VITE_ prefix",evidence:o.map(l=>`${l.file}:${l.line} \u2192 ${l.content.split("=")[0]}`)};let a=n.filter(l=>w(l.file)&&/STRIPE_SECRET/i.test(l.content)&&!l.file.includes("/api/")&&!l.file.includes("route."));return a.length>0?{result:"FAIL",message:"Stripe secret key referenced in client-side file",evidence:a.map(l=>`${l.file}:${l.line} \u2192 ${l.content.substring(0,120)}`)}:n.filter(l=>/STRIPE_SECRET/i.test(l.content)).length>0?{result:"PASS",message:"Stripe secret key found only in server-side/env files"}:{result:"UNKNOWN",message:"No Stripe secret key references found \u2014 Stripe may not be used"}}};var pt={id:"BIL-02",name:"Webhook signature verification",module:"billing",layer:"L2",priority:"P0",aliases:["ADM-15"],description:"Stripe webhook handler must verify signature via constructEvent(). Without it, anyone can send fake webhooks.",fixCost:100,fixSize:"M",async run(e){let t=/webhook/i,i=e.files.filter(c=>t.test(c));if(i.length===0)return{result:"UNKNOWN",message:"No webhook handler files found"};let s=/constructEvent|constructEventAsync|webhooks\.construct/,r=/stripe-signature|Stripe-Signature|STRIPE_WEBHOOK_SECRET|webhook.*secret/i,n=await e.grepFiles(s,i),o=await e.grepFiles(r,i);if(n.length>0){for(let c of i){let l;try{l=await e.readFile(c)}catch{continue}let d=/constructEvent|constructEventAsync|webhooks\.construct/.test(l),f=/JSON\.parse\s*\(.*body/i.test(l),m=/if\s*\(\s*(?:webhook_?[Ss]ecret|STRIPE_WEBHOOK_SECRET|secret)/i.test(l)||/if\s*\(\s*!?\s*webhook_?[Ss]ecret/i.test(l);if(d&&f&&m)return{result:"FAIL",message:"Webhook signature verification is conditional \u2014 JSON.parse fallback bypasses constructEvent() when secret is missing",evidence:[`${c} \u2014 constructEvent() inside conditional, JSON.parse(body) fallback allows unverified webhooks`]}}return{result:"PASS",message:"Webhook handler uses constructEvent() for signature verification"}}return o.length>0?{result:"PASS",message:"Webhook handler references signature verification"}:(await e.grepFiles(/stripe|Stripe/,i)).length>0?{result:"FAIL",message:"Stripe webhook handler found WITHOUT signature verification",evidence:i.map(c=>`${c} \u2014 no constructEvent() or signature check`)}:{result:"UNKNOWN",message:"Webhook files found but no Stripe references \u2014 may not be Stripe webhooks"}}};var ft={id:"BIL-03",name:"Raw body preservation in webhook",module:"billing",layer:"L2",priority:"P0",description:"Webhook signature verification requires raw request body. req.json() breaks it \u2014 use req.text() or bodyParser: false.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(a=>/webhook/i.test(a));if(t.length===0)return{result:"UNKNOWN",message:"No webhook handler files found"};let i=/request\.text\(\)|req\.text\(\)|bodyParser\s*:\s*false|getRawBody|raw\s*:\s*true|rawBody/,s=/request\.json\(\)|req\.body(?!\s*Parser)|JSON\.parse/,r=await e.grepFiles(i,t),n=await e.grepFiles(s,t);return r.length>0?{result:"PASS",message:"Webhook handler preserves raw body for signature verification"}:(await e.grepFiles(/stripe|constructEvent/i,t)).length===0?{result:"UNKNOWN",message:"Webhook files found but no Stripe references"}:n.length>0?{result:"FAIL",message:"Webhook handler uses req.json()/req.body instead of raw body \u2014 signature verification will fail",evidence:n.slice(0,3).map(a=>`${a.file}:${a.line} \u2192 ${a.content.substring(0,120)}`)}:{result:"FAIL",message:"Stripe webhook handler found but no raw body preservation detected",evidence:t.map(a=>`${a} \u2014 missing request.text() or bodyParser: false`)}}};var mt={id:"BIL-04",name:"Idempotent webhook processing",module:"billing",layer:"L2",priority:"P1",description:"Stripe retries webhooks by design. Without idempotency checks, duplicate credits and double subscriptions occur.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>/webhook/i.test(n));if(t.length===0)return{result:"UNKNOWN",message:"No webhook handler files found"};let i=/event\.id|event_id|idempoten|UNIQUE.*event|duplicate.*check|processed.*events|already.*processed/i,s=await e.grepFiles(i,t);return(await e.grepFiles(/stripe|constructEvent/i,t)).length===0?{result:"UNKNOWN",message:"No Stripe webhook handler found"}:s.length>0?{result:"PASS",message:"Webhook idempotency check detected"}:{result:"FAIL",message:"Stripe webhook handler has no idempotency check \u2014 duplicate processing possible",evidence:["No event.id tracking or duplicate check in webhook handler"]}}};var gt={id:"BIL-05",name:"Subscription state machine",module:"billing",layer:"L2",priority:"P1",description:"Stripe has 8 subscription states. Handling only active/canceled causes access issues for past_due, trialing, incomplete, unpaid.",fixCost:100,fixSize:"M",async run(e){let t=/past_due|trialing|incomplete|unpaid/i,i=/active|canceled/i,s=await e.grepFiles(t),r=await e.grepFiles(i);return s.length>=2?{result:"PASS",message:"Multiple subscription states handled beyond active/canceled"}:r.length>0&&s.length===0?{result:"FAIL",message:"Only active/canceled states handled \u2014 missing past_due, trialing, incomplete, unpaid",evidence:["Subscription state machine is incomplete \u2014 users may lose access incorrectly"]}:(await e.grepFiles(/subscription|stripe/i)).length===0?{result:"UNKNOWN",message:"No subscription handling found"}:{result:"FAIL",message:"Subscription code found but no explicit state handling",evidence:["Missing: past_due, trialing, incomplete, unpaid state handling"]}}};var ht={id:"BIL-06",name:"Entitlement/plan limit checking",module:"billing",layer:"L2",priority:"P1",description:"Stripe doesn't track usage limits. Without app-side entitlement checks, free users access paid features.",fixCost:100,fixSize:"M",async run(e){let t=/checkPlan|checkEntitle|planLimit|featureGate|subscription.*check|plan.*limit|canAccess|hasFeature|isSubscribed|entitlement/i;return(await e.grepFiles(t)).length>0?{result:"PASS",message:"Entitlement/plan limit checking detected"}:(await e.grepFiles(/subscription|stripe.*plan|pricing/i)).length===0?{result:"UNKNOWN",message:"No subscription/pricing code found"}:{result:"FAIL",message:"Subscription code exists but no entitlement/plan limit checks found",evidence:["Missing: checkPlanLimit, checkEntitlement, featureGate, or equivalent"]}}};var yt={id:"BIL-07",name:"Customer \u2194 User sync",module:"billing",layer:"L2",priority:"P1",description:"Every user must map to exactly one Stripe customer. Missing sync = orphaned customers, broken subscription lookups.",fixCost:100,fixSize:"M",async run(e){let t=/stripe_customer_id|stripeCustomerId|customer_id.*stripe/i,i=/customers\.create|createCustomer|stripe.*customer/i,s=e.files.filter(v),r=await e.grepFiles(t,s.length>0?s:void 0),n=await e.grepFiles(i);return r.length>0?{result:"PASS",message:"stripe_customer_id found in database schema \u2014 user-customer sync exists"}:n.length>0?{result:"PASS",message:"Stripe customer creation found in code"}:(await e.grepFiles(/stripe/i)).length>0?{result:"FAIL",message:"Stripe is used but no stripe_customer_id in schema and no customer creation logic",evidence:["Missing: stripe_customer_id column in users/profiles table"]}:{result:"UNKNOWN",message:"No Stripe references found"}}};var St={id:"BIL-08",name:"Webhook returns 200 for unknown events",module:"billing",layer:"L2",priority:"P1",description:"Returning 400/500 for unhandled events triggers Stripe retry loops and eventual endpoint deactivation.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(o=>/webhook/i.test(o));if(t.length===0)return{result:"UNKNOWN",message:"No webhook handler files found"};let i=/else\s*\{[\s\S]*?(?:return.*(?:4\d\d|5\d\d)|throw|NextResponse.*(?:4\d\d|5\d\d))/i,s=/default\s*:[\s\S]*?(?:200|ok|return\s+new\s+Response)/i,r=await e.grepFiles(i,t),n=await e.grepFiles(s,t);return r.length>0?{result:"FAIL",message:"Webhook handler returns error for unknown events \u2014 will trigger Stripe retry loop",evidence:r.slice(0,3).map(o=>`${o.file}:${o.line} \u2192 ${o.content.substring(0,120)}`)}:n.length>0?{result:"PASS",message:"Webhook handler returns 200 for unhandled events"}:{result:"UNKNOWN",message:"Could not determine webhook default response behavior"}}};var $t={id:"BIL-09",name:"No client-side billing state as source of truth",module:"billing",layer:"L2",priority:"P1",description:"Subscription state in localStorage/React state can be manipulated. Server must be source of truth.",fixCost:100,fixSize:"M",async run(e){let t=[],i=/localStorage.*(?:subscription|plan|billing)|sessionStorage.*(?:subscription|plan)|useState.*(?:subscription|isPro|isPaid|plan)/i,r=(await e.grepFiles(i)).filter(d=>w(d.file));for(let d of r)t.push(`${d.file}:${d.line} \u2192 ${d.content.substring(0,120)}`);let n=/\.from\s*\(\s*['"](?:subscriptions|plans|credits|billing)['"]\s*\)\s*\.\s*select/i,a=(await e.grepFiles(n)).filter(d=>w(d.file));for(let d of a)t.push(`${d.file}:${d.line} \u2192 ${d.content.substring(0,120)}`);let c=/(?:useSubscription|useBilling|usePlan)\b/,l=e.files.filter(d=>c.test(d)||/hook/i.test(d));for(let d of l){let f;try{f=await e.readFile(d)}catch{continue}let m=/from\s*\(\s*['"](?:subscriptions|plans|credits)['"]\s*\)/.test(f),p=/isPro|isPaid|plan\s*===|plan\s*!==|plan\s*==|plan\s*!=/.test(f);m&&p&&t.push(`${d} \u2192 Client hook reads billing table and derives plan/isPro locally`)}return t.length>0?{result:"FAIL",message:`Client-side billing state as source of truth (${t.length} location${t.length>1?"s":""})`,evidence:t.slice(0,5)}:{result:"PASS",message:"No client-side billing state as source of truth detected"}}};var At={id:"BIL-10",name:"Reconciliation mechanism",module:"billing",layer:"L2",priority:"P2",description:"Even good webhook handling drifts 1-2x/month. A reconciliation job comparing Stripe vs DB prevents silent revenue loss.",fixCost:100,fixSize:"M",async run(e){let t=/reconcil|sync.*stripe|stripe.*sync|cron.*billing|billing.*cron|verify.*subscription|subscription.*verify/i,i=e.files.filter(n=>/reconcil/i.test(n)),s=await e.grepFiles(t);return i.length>0||s.length>0?{result:"PASS",message:"Reconciliation mechanism detected"}:(await e.grepFiles(/stripe/i)).length===0?{result:"UNKNOWN",message:"No Stripe references found"}:{result:"FAIL",message:"No billing reconciliation mechanism found",evidence:["Missing: reconciliation script/job to compare Stripe state vs DB state"]}}};var bt={id:"BIL-11",name:"Cancellation handling",module:"billing",layer:"L2",priority:"P1",description:"Explicit cancel flow: server-side cancellation + webhook processing + DB update + access revocation.",fixCost:100,fixSize:"M",async run(e){let t=/cancel.*subscription|subscription.*cancel|customer\.subscription\.deleted|cancelAt|cancel_at_period_end/i,i=await e.grepFiles(t);return i.length>=2?{result:"PASS",message:"Cancellation handling detected in multiple locations"}:i.length===1?{result:"PASS",message:"Cancellation handling detected"}:(await e.grepFiles(/subscription|stripe/i)).length===0?{result:"UNKNOWN",message:"No subscription code found"}:{result:"FAIL",message:"No cancellation handling found \u2014 users may retain access after canceling or be charged after canceling",evidence:["Missing: cancel subscription flow, subscription.deleted webhook handler"]}}};var Ct={id:"BIL-12",name:"Stripe env vars configured",module:"billing",layer:"L2",priority:"P2",description:"Missing Stripe env vars = billing won't work in production or keys get hardcoded.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(U);if(t.length===0)return{result:"UNKNOWN",message:"No .env files found"};let i=["STRIPE_SECRET_KEY","STRIPE_WEBHOOK_SECRET","STRIPE_PUBLISHABLE_KEY"],s=[],r=[];for(let o of t){let a;try{a=await e.readFile(o)}catch{continue}for(let c of i)a.includes(c)&&!s.includes(c)&&s.push(c)}for(let o of i)s.includes(o)||r.push(o);return(await e.grepFiles(/stripe/i)).length===0?{result:"UNKNOWN",message:"No Stripe references found"}:r.length>0?{result:"FAIL",message:`Missing Stripe env vars: ${r.join(", ")}`,evidence:r.map(o=>`${o} not found in any .env file`)}:{result:"PASS",message:"All required Stripe env vars configured"}}};var vt={id:"BIL-13",name:"Error handling in payment flows",module:"billing",layer:"L2",priority:"P2",description:"Missing error handling around Stripe API calls = white screen on payment failure, no diagnostics.",fixCost:100,fixSize:"M",async run(e){let t=/stripe\.\w+\.\w+\(|checkout\.sessions\.create|subscriptions\.create|customers\.create/i,i=/try\s*\{|\.catch\s*\(|catch\s*\(/,s=e.files.filter(o=>/api|route|action|server/i.test(o)),r=await e.grepFiles(t,s.length>0?s:void 0);return r.length===0?{result:"UNKNOWN",message:"No Stripe API calls found"}:(await e.grepFiles(i,s.length>0?s:void 0)).length>0?{result:"PASS",message:"Error handling found in payment flow files"}:{result:"FAIL",message:"Stripe API calls found without error handling",evidence:r.slice(0,3).map(o=>`${o.file}:${o.line} \u2192 ${o.content.substring(0,120)}`)}}};var kt={id:"BIL-14",name:"Checkout flow is server-initiated",module:"billing",layer:"L2",priority:"P0",description:"Checkout session must be created on the server. Client-side creation requires secret key in browser.",fixCost:100,fixSize:"M",async run(e){let t=/checkout\.sessions\.create|createCheckoutSession/,i=await e.grepFiles(t);if(i.length===0)return{result:"UNKNOWN",message:"No Checkout session creation found"};let s=i.filter(n=>w(n.file)&&!x(n.file));for(let n of s){let o;try{o=await e.readFile(n.file)}catch{continue}if(/["']use client["']/.test(o))return{result:"FAIL",message:"Checkout session created in client component \u2014 secret key required in browser",evidence:[`${n.file}:${n.line} \u2192 ${n.content.substring(0,120)}`]}}return i.filter(n=>x(n.file)).length>0?{result:"PASS",message:"Checkout session created server-side"}:{result:"PASS",message:"Checkout session creation found (not in client component)"}}};var _t={id:"BIL-15",name:"Stripe Price ID tampering prevention",module:"billing",layer:"L2",priority:"P0",description:"Price ID from client request must be validated server-side against an allowlist. Client can send any price_id.",fixCost:100,fixSize:"M",async run(e){let t=/checkout\.sessions\.create|createCheckoutSession/;if((await e.grepFiles(t)).length===0)return{result:"UNKNOWN",message:"No Stripe Checkout session creation found"};let s=/req\.body.*price|req\.json.*price|request\.json.*price|body\.price|priceId|price_id/i,r=/allowedPrices|ALLOWED_PRICES|validPrices|PRICE_IDS|priceWhitelist|priceLookup|PLANS\[|plans\[|PRICES\[|prices\./i,n=await e.grepFiles(s),o=await e.grepFiles(r);return n.length>0&&o.length===0?{result:"FAIL",message:"Price ID accepted from client without server-side validation",evidence:n.slice(0,3).map(a=>`${a.file}:${a.line} \u2192 ${a.content.substring(0,120)}`)}:o.length>0?{result:"PASS",message:"Price ID validation/allowlist detected"}:{result:"PASS",message:"Checkout session creation found with no client price input detected"}}};var It={id:"BIL-16",name:"Never fulfill on success_url",module:"billing",layer:"L2",priority:"P0",description:"Fulfillment (DB writes, access grants) must happen via webhook, not on the success redirect page.",fixCost:100,fixSize:"M",async run(e){let t=[/success/i,/thank/i,/payment.*confirm/i],i=e.files.filter(n=>(n.includes("page.")||n.includes("index."))&&t.some(a=>a.test(n)));if(i.length===0)return{result:"UNKNOWN",message:"No success/thank-you pages found"};let s=/\.insert\(|\.update\(|\.upsert\(|createSubscription|grantAccess|activateUser|fulfillOrder|UPDATE.*SET|INSERT.*INTO/i,r=[];for(let n of i){let o;try{o=await e.readFile(n)}catch{continue}let a=o.split(`
7
+ `);for(let c=0;c<a.length;c++)s.test(a[c])&&r.push({file:n,line:c+1,content:a[c].trim()})}return r.length>0?{result:"FAIL",message:`Fulfillment logic found in success page (${r.length} location${r.length>1?"s":""}) \u2014 must use webhook instead`,evidence:r.map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,120)}`)}:{result:"PASS",message:"Success pages contain no fulfillment logic"}}};var Pt={id:"BIL-17",name:"PCI raw card data safety",module:"billing",layer:"L2",priority:"P0",description:"Raw card numbers, CVV, expiration must never touch your server. Use Stripe Elements or Checkout.",fixCost:100,fixSize:"M",async run(e){let t=/card\s*\[\s*number\s*\]|cardNumber|card_number|cvv|cvc|expiry.*month|card.*expir/i,i=/<input[^>]*(?:card|cvv|cvc|expir)/i,s=/CardElement|PaymentElement|useStripe|useElements|@stripe\/react-stripe-js|stripe\.elements/i,r=await e.grepFiles(t),n=await e.grepFiles(i),o=[...r,...n].filter(c=>!(c.content.trimStart().startsWith("//")||c.content.trimStart().startsWith("*")||c.content.trimStart().startsWith("#")||c.file.includes("test")||c.file.includes("spec")||c.file.includes(".md"))),a=await e.grepFiles(s);return o.length>0&&a.length===0?{result:"FAIL",message:`Raw card data handling detected without Stripe Elements (${o.length} location${o.length>1?"s":""})`,evidence:o.slice(0,5).map(c=>`${c.file}:${c.line} \u2192 ${c.content.substring(0,120)}`)}:a.length>0?{result:"PASS",message:"Stripe Elements/Checkout detected \u2014 card data handled by Stripe"}:{result:"UNKNOWN",message:"No card data handling or Stripe Elements detected"}}};var wt={id:"BIL-18",name:"Refund/dispute handling",module:"billing",layer:"L2",priority:"P1",description:"Webhook must handle charge.refunded and charge.dispute.created to revoke access and update billing state.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(a=>/webhook/i.test(a));if(t.length===0)return{result:"UNKNOWN",message:"No webhook handler files found"};let i=/charge\.refunded|refund/i,s=/charge\.dispute|dispute/i,r=await e.grepFiles(i,t),n=await e.grepFiles(s,t);return r.length>0&&n.length>0?{result:"PASS",message:"Both refund and dispute handling detected in webhook"}:r.length>0||n.length>0?{result:"PASS",message:"Partial refund/dispute handling detected"}:(await e.grepFiles(/stripe/i,t)).length===0?{result:"UNKNOWN",message:"No Stripe webhook handler found"}:{result:"FAIL",message:"No refund or dispute handling in webhook \u2014 access may persist after refund",evidence:["Missing: charge.refunded and charge.dispute.created event handlers"]}}};var Rt={id:"BIL-19",name:"Stripe API version pinning",module:"billing",layer:"L2",priority:"P2",description:"Pin Stripe API version to avoid breaking changes from automatic upgrades.",fixCost:100,fixSize:"M",async run(e){let t=/apiVersion|api_version/i;if((await e.grepFiles(t)).length>0)return{result:"PASS",message:"Stripe API version pinning detected"};let s=await e.grepFiles(/new\s+Stripe\s*\(|Stripe\s*\(/i);return s.length===0?{result:"UNKNOWN",message:"No Stripe initialization found"}:{result:"FAIL",message:"Stripe initialized without explicit API version \u2014 may break on Stripe upgrades",evidence:s.slice(0,2).map(r=>`${r.file}:${r.line} \u2192 ${r.content.substring(0,120)}`)}}};var Lt={id:"BIL-20",name:"Portal session auth",module:"billing",layer:"L2",priority:"P1",description:"Billing portal session must use customer ID from authenticated user, not from client request.",fixCost:100,fixSize:"M",async run(e){let t=/billingPortal|billing_portal|customer_portal/i,i=await e.grepFiles(t);if(i.length===0)return{result:"UNKNOWN",message:"No billing portal usage found"};let s=/getUser|getSession|auth\(\)|requireAuth/i,r=[...new Set(i.map(o=>o.file))];return(await e.grepFiles(s,r)).length>0?{result:"PASS",message:"Billing portal session created with authenticated customer ID"}:{result:"FAIL",message:"Billing portal session created without verifying authenticated user",evidence:i.slice(0,3).map(o=>`${o.file}:${o.line} \u2192 ${o.content.substring(0,120)}`)}}};var Nt={id:"BIL-21",name:"Webhook event coverage",module:"billing",layer:"L2",priority:"P1",description:"Webhook handler must process at minimum 3 core subscription events. Missing events = state drift.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>/webhook/i.test(n));if(t.length===0)return{result:"UNKNOWN",message:"No webhook handler files found"};let i=[{pattern:/checkout\.session\.completed/i,name:"checkout.session.completed"},{pattern:/customer\.subscription\.updated/i,name:"customer.subscription.updated"},{pattern:/customer\.subscription\.deleted/i,name:"customer.subscription.deleted"},{pattern:/invoice\.payment_succeeded/i,name:"invoice.payment_succeeded"},{pattern:/invoice\.payment_failed/i,name:"invoice.payment_failed"}],s=[],r=[];for(let n of i)(await e.grepFiles(n.pattern,t)).length>0?s.push(n.name):r.push(n.name);return s.length===0?(await e.grepFiles(/stripe/i,t)).length===0?{result:"UNKNOWN",message:"Webhook files found but no Stripe event handling"}:{result:"FAIL",message:"Stripe webhook handler found but no subscription events handled",evidence:["Missing: checkout.session.completed, subscription.updated, subscription.deleted"]}:s.length<3?{result:"FAIL",message:`Only ${s.length}/5 core webhook events handled`,evidence:[`Handled: ${s.join(", ")}`,`Missing: ${r.join(", ")}`]}:{result:"PASS",message:`${s.length}/5 core webhook events handled`}}};var Ft={id:"BIL-22",name:"Trial period handling",module:"billing",layer:"L2",priority:"P2",description:"If trials are offered, handle trial_will_end webhook and trial-to-paid conversion properly.",fixCost:100,fixSize:"M",async run(e){let t=/trial_period_days|trial_end|trialing|trial_will_end|trialDays/i,i=await e.grepFiles(t);return i.length>=2?{result:"PASS",message:"Trial period handling detected"}:i.length===1?{result:"PASS",message:"Trial reference found (verify trial_will_end webhook is handled)"}:(await e.grepFiles(/subscription|stripe/i)).length===0?{result:"UNKNOWN",message:"No subscription code found"}:{result:"N/A",message:"No trial period usage detected"}}};var xt={id:"BIL-23",name:"Card testing protection",module:"billing",layer:"L2",priority:"P2",description:"Rate limiting on payment endpoints prevents card testing attacks (automated validation of stolen cards).",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>/checkout|payment|billing|subscribe/i.test(n));if(t.length===0)return{result:"UNKNOWN",message:"No payment endpoint files found"};let i=/rateLimit|rate.limit|throttle|limiter|too.many.requests|429|upstash/i;return(await e.grepFiles(i,t)).length>0?{result:"PASS",message:"Rate limiting detected on payment endpoints"}:(await e.grepFiles(i)).length>0?{result:"PASS",message:"Rate limiting detected in codebase (verify it covers payment endpoints)"}:{result:"FAIL",message:"No rate limiting found on payment endpoints \u2014 vulnerable to card testing",evidence:["Missing: rate limiting middleware on checkout/payment routes"]}}};var Et={id:"BIL-24",name:"Metered billing usage reporting",module:"billing",layer:"L2",priority:"P2",description:"If using metered pricing, usage must be reported to Stripe. Missing reporting = customers never billed for usage.",fixCost:100,fixSize:"M",async run(e){let t=/metered|usage_type|usageRecord|usage.*report|createUsageRecord|meter/i,i=await e.grepFiles(t);return i.length>=2?{result:"PASS",message:"Metered billing usage reporting detected"}:i.length===1?{result:"PASS",message:"Metered billing reference found"}:{result:"N/A",message:"No metered billing usage detected"}}};var Tt={id:"BIL-25",name:"Subscription table user-writable",module:"billing",layer:"L2",priority:"P0",description:"RLS policies on billing tables (subscriptions, plans, credits) must not allow authenticated users to UPDATE or INSERT plan/status columns. User-writable billing state enables subscription fraud (Free \u2192 Pro without payment).",fixCost:250,fixSize:"L",async run(e){let t=["subscriptions","plans","credits","billing"],i=/create\s+table\s+(?:public\.)?(subscriptions|plans|credits|billing)/i,s=e.files.filter(l=>/\.sql$/i.test(l)||/supabase.*migration/i.test(l)),r=await e.grepFiles(i,s.length>0?s:void 0);if(r.length===0){let l=await e.grepFiles(i);if(l.length===0)return{result:"PASS",message:"No billing tables (subscriptions/plans/credits) found"};r.push(...l)}let n=new Set(r.map(l=>{let d=l.content.match(/(?:public\.)?(subscriptions|plans|credits|billing)/i);return d?d[1].toLowerCase():null}).filter(Boolean)),o=[];for(let l of n)for(let d of s.length>0?s:e.files.filter(f=>/\.sql$/i.test(f))){let f;try{f=await e.readFile(d)}catch{continue}let m=new RegExp(`create\\s+policy[^;]*?on\\s+(?:public\\.)?${l}\\s+for\\s+(update|insert|all)`,"gis"),p;for(;(p=m.exec(f))!==null;){let S=Math.max(0,p.index),M=f.substring(S,f.indexOf(";",p.index)+1||S+500),$=/auth\.uid\(\)/i.test(M),F=/with\s+check\s*\([^)]*(?:false|service_role|is_admin)/i.test(M);if($&&!F){let ie=f.substring(0,p.index).split(`
8
8
  `).length,fe=p[1].toUpperCase();o.push(`${d}:${ie} \u2192 RLS ${fe} policy on ${l} allows user writes (auth.uid() = user_id)`)}}}if(o.length>0)return{result:"FAIL",message:`Billing table${n.size>1?"s":""} (${[...n].join(", ")}) allow user writes via RLS \u2014 subscription fraud risk`,evidence:o.slice(0,5)};let a=/\.from\s*\(\s*['"](?:subscriptions|plans|credits|billing)['"]\s*\)\s*\.\s*(?:update|insert|upsert)/i,c=await e.grepFiles(a);return c.length>0?{result:"FAIL",message:`Client-side writes to billing table detected (${c.length} location${c.length>1?"s":""})`,evidence:c.slice(0,3).map(l=>`${l.file}:${l.line} \u2192 ${l.content.substring(0,120)}`)}:{result:"PASS",message:"No user-writable billing tables detected"}}};var Dt={id:"ADM-01",name:"Admin endpoints have server-side auth",module:"admin",layer:"L2",priority:"P0",description:"Every /admin or /api/admin endpoint must verify admin role server-side. Server Actions are public POST endpoints.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(r=>/api\/admin|app\/api\/admin/i.test(r)&&(r.endsWith(".ts")||r.endsWith(".js")));if(t.length===0)return{result:"UNKNOWN",message:"No admin API route handlers found"};let i=/requireAdmin|role.*admin|isAdmin|checkPermission|requireRole|app_metadata.*admin|admin.*guard/i,s=[];for(let r of t){let n;try{n=await e.readFile(r)}catch{continue}i.test(n)||s.push(r)}return s.length>0?{result:"FAIL",message:`${s.length} admin API route${s.length>1?"s":""} without admin role verification`,evidence:s.slice(0,5).map(r=>`${r} \u2014 no admin role check detected`)}:{result:"PASS",message:`All ${t.length} admin API routes have admin role verification`}}};var Mt={id:"ADM-02",name:"Admin routes not accessible without auth",module:"admin",layer:"L2",priority:"P0",description:"Admin pages and API must return 401/403 without valid admin token. Middleware alone is insufficient (CVE-2025-29927).",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(o=>/app\/admin\/.*page\.|pages\/admin\//i.test(o)||/(?:pages|views|routes)\/Admin[A-Z][^/]*\.(tsx|ts|jsx|js)$/.test(o)||/(?:pages|views|routes)\/admin-[^/]*\.(tsx|ts|jsx|js)$/.test(o));if(t.length===0)return{result:"UNKNOWN",message:"No admin pages found"};let i=/getUser|getSession|requireAuth|requireAdmin|redirect.*login|redirect.*auth|middleware|auth\(\)|checkPermission/i,s=[];for(let o of t){let a;try{a=await e.readFile(o)}catch{continue}i.test(a)||s.push(o)}let r=e.files.some(o=>/middleware\.(ts|js)$/i.test(o)),n=!1;if(r){let o=e.files.find(a=>/middleware\.(ts|js)$/i.test(a));if(o)try{let a=await e.readFile(o);n=/admin/i.test(a)}catch{}}return s.length>0&&!n?{result:"FAIL",message:`${s.length} admin page${s.length>1?"s":""} without auth guard (no middleware coverage either)`,evidence:s.slice(0,5).map(o=>`${o} \u2014 no auth guard detected`)}:s.length>0&&n?{result:"PASS",message:`Admin pages protected via middleware (${t.length} pages). Note: add per-route auth for defense-in-depth.`}:{result:"PASS",message:`All ${t.length} admin pages have auth guards`}}};var Ut={id:"ADM-03",name:"No client-side-only role checks",module:"admin",layer:"L2",priority:"P1",description:"Role checks in JSX ({isAdmin && <Panel/>}) without server-side enforcement are bypassable via dev tools.",fixCost:100,fixSize:"M",async run(e){let t=/isAdmin|is_admin|role.*admin|admin.*role/i,i=/requireAdmin|requireRole|checkPermission|app_metadata.*admin/i,r=(await e.grepFiles(t)).filter(o=>w(o.file)&&!x(o.file));return r.length===0?{result:"PASS",message:"No client-side-only role checks detected"}:(await e.grepFiles(i)).length>0?{result:"PASS",message:"Client-side role checks backed by server-side enforcement"}:{result:"FAIL",message:`Client-side role checks found without server-side enforcement (${r.length} location${r.length>1?"s":""})`,evidence:r.slice(0,3).map(o=>`${o.file}:${o.line} \u2192 ${o.content.substring(0,120)}`)}}};var jt={id:"ADM-04",name:"Audit log for admin actions",module:"admin",layer:"L2",priority:"P1",description:"Admin operations (delete user, change role, modify data) must be logged. Required for SOC 2 compliance.",fixCost:100,fixSize:"M",async run(e){let t=/audit.*log|admin.*log|action.*log|createAuditEntry|logAdminAction|activity.*log/i,i=/audit_log|admin_log|activity_log/i,s=await e.grepFiles(t),r=e.files.filter(v),n=await e.grepFiles(i,r.length>0?r:void 0);return s.length>0||n.length>0?{result:"PASS",message:"Audit logging detected for admin actions"}:(await e.grepFiles(/admin/i)).length===0?{result:"UNKNOWN",message:"No admin functionality detected"}:{result:"FAIL",message:"No audit logging found for admin actions",evidence:["Missing: audit_log table or logAdminAction function"]}}};var Ot={id:"ADM-05",name:"RBAC beyond binary admin",module:"admin",layer:"L2",priority:"P2",description:"Binary admin/user model means every admin can do everything. Granular roles limit blast radius.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(v),i=/RBAC|permission|capability|role.*check|roles.*table|user_roles/i,s=/isAdmin|is_admin|boolean.*admin/i,r=await e.grepFiles(i),n=await e.grepFiles(/role.*enum|roles.*table|permissions.*table/i,t.length>0?t:void 0);if(r.length>0||n.length>0)return{result:"PASS",message:"RBAC or granular permissions model detected"};let o=await e.grepFiles(s);return o.length>0?{result:"FAIL",message:"Binary admin model (isAdmin boolean) \u2014 no granular permissions",evidence:o.slice(0,3).map(a=>`${a.file}:${a.line} \u2192 ${a.content.substring(0,120)}`)}:{result:"UNKNOWN",message:"No admin role model detected"}}};var Wt={id:"ADM-06",name:"Safe impersonation (if exists)",module:"admin",layer:"L2",priority:"P1",description:"If admin can 'login as user', the action must be logged with admin ID preserved. Replacing admin session = invisible abuse.",fixCost:100,fixSize:"M",async run(e){let t=/impersonat|loginAs|actAs|switchUser|act_as|login_as/i,i=await e.grepFiles(t);if(i.length===0)return{result:"N/A",message:"No impersonation functionality detected"};let s=/audit|log.*admin|admin.*log|logAction|track/i;return(await e.grepFiles(s)).length>0?{result:"PASS",message:"Impersonation exists with audit logging"}:{result:"FAIL",message:"Impersonation found without audit logging \u2014 admin actions will appear as user actions",evidence:i.slice(0,3).map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,120)}`)}}};var Bt={id:"ADM-07",name:"UUIDs not sequential IDs",module:"admin",layer:"L2",priority:"P1",description:"Sequential integer IDs enable enumeration attacks (IDOR). Use UUIDs for all user-facing primary keys.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(v);if(t.length===0)return{result:"UNKNOWN",message:"No SQL migration files found"};let i=/(?:SERIAL|BIGSERIAL|INTEGER\s+PRIMARY\s+KEY\s+(?:AUTO_INCREMENT|GENERATED))/i,s=/UUID\s+(?:PRIMARY\s+KEY\s+)?DEFAULT\s+(?:gen_random_uuid|uuid_generate)/i,r=await e.grepFiles(i,t),n=await e.grepFiles(s,t),o=r.filter(a=>!/migration|schema_migration|_prisma/i.test(a.content));return o.length>0&&n.length===0?{result:"FAIL",message:`Sequential IDs found without UUID usage (${o.length} table${o.length>1?"s":""})`,evidence:o.slice(0,5).map(a=>`${a.file}:${a.line} \u2192 ${a.content.substring(0,120)}`)}:n.length>0?{result:"PASS",message:"UUID primary keys detected in schema"}:{result:"UNKNOWN",message:"No primary key definitions found in migrations"}}};var Ht={id:"ADM-08",name:"No unprotected debug/admin routes",module:"admin",layer:"L2",priority:"P0",description:"Debug, internal, and admin routes must have auth guards. Hidden URLs are not security.",fixCost:100,fixSize:"M",async run(e){let t=[/app\/admin\//,/pages\/admin\//,/app\/internal\//,/app\/debug\//,/api\/admin\//,/api\/debug\//,/api\/internal\//,/api\/graphql/,/api\/seed/,/api\/reset/,/api\/test/,/supabase\/functions\/.*(?:seed|admin|debug|reset|test)/],i=e.files.filter(n=>t.some(o=>o.test(n)));if(i.length===0)return{result:"PASS",message:"No admin/debug/internal routes detected"};let s=/requireAdmin|requireAuth|getUser|getSession|verifyToken|isAdmin|checkPermission|requireRole|auth\(\)|middleware/i,r=[];for(let n of i){if(!n.endsWith(".ts")&&!n.endsWith(".tsx")&&!n.endsWith(".js")&&!n.endsWith(".jsx"))continue;let o;try{o=await e.readFile(n)}catch{continue}s.test(o)||r.push(n)}return r.length>0?{result:"FAIL",message:`${r.length} admin/debug route${r.length>1?"s":""} without auth check`,evidence:r.map(n=>`${n} \u2014 no auth guard detected`)}:{result:"PASS",message:`All ${i.length} admin/debug routes have auth checks`}}};var zt={id:"ADM-09",name:"Destructive ops require extra authorization",module:"admin",layer:"L2",priority:"P2",description:"Bulk delete, data wipe, billing override should require confirmation step or elevated permission.",fixCost:100,fixSize:"M",async run(e){let t=/deleteAll|bulkDelete|wipeData|truncate|DROP\s+TABLE|removeAll|destroyAll|purge/i,i=/confirm|double.*auth|re.?authenticate|verification.*step|two.*step/i,r=(await e.grepFiles(t)).filter(o=>/admin/i.test(o.file));return r.length===0?{result:"N/A",message:"No bulk destructive admin operations detected"}:(await e.grepFiles(i)).length>0?{result:"PASS",message:"Confirmation/extra auth detected for destructive operations"}:{result:"FAIL",message:"Destructive admin operations without extra authorization",evidence:r.slice(0,3).map(o=>`${o.file}:${o.line} \u2192 ${o.content.substring(0,120)}`)}}};var qt={id:"ADM-10",name:"Admin code separated from user app",module:"admin",layer:"L2",priority:"P2",description:"Admin code in separate directories prevents user app bugs from affecting admin, and vice versa.",fixCost:100,fixSize:"M",async run(e){let t=e.files.some(s=>/^app\/admin\/|^domains\/admin\/|^src\/admin\//i.test(s)),i=e.files.filter(s=>/admin/i.test(s)&&!/(app|domains|src)\/admin\//i.test(s));return t?{result:"PASS",message:"Admin code in dedicated directory (app/admin/ or domains/admin/)"}:i.length>0?{result:"FAIL",message:"Admin code scattered across user-facing directories",evidence:i.slice(0,5).map(s=>s)}:{result:"N/A",message:"No admin code detected"}}};var Kt={id:"ADM-11",name:"No hardcoded admin credentials",module:"admin",layer:"L2",priority:"P0",description:"Hardcoded admin passwords, tokens, or emails in source code = anyone with repo access is admin",fixCost:100,fixSize:"M",async run(e){let t=[/admin.*password\s*[:=]\s*["']/i,/admin.*token\s*[:=]\s*["']/i,/admin.*secret\s*[:=]\s*["']/i,/password\s*[:=]\s*["'](?:admin|password|123456|secret)/i,/DEFAULT_ADMIN_PASSWORD/i,/ADMIN_PASSWORD\s*[:=]/i,/seed.*admin.*password/i],i=[];for(let r of t){let o=(await e.grepFiles(r)).filter(a=>!(U(a.file)||a.content.trimStart().startsWith("//")||a.content.trimStart().startsWith("#")||a.content.trimStart().startsWith("*")||a.file.includes("test")||a.file.includes("spec")));i.push(...o)}let s=[...new Map(i.map(r=>[`${r.file}:${r.line}`,r])).values()];return s.length>0?{result:"FAIL",message:`Hardcoded admin credentials found (${s.length} location${s.length>1?"s":""})`,evidence:s.map(r=>`${r.file}:${r.line} \u2192 ${r.content.substring(0,80).replace(/["'][^"']{8,}["']/g,'"[REDACTED]"')}`)}:{result:"PASS",message:"No hardcoded admin credentials found in source code"}}};var Gt={id:"ADM-12",name:"Admin error handling",module:"admin",layer:"L2",priority:"P2",description:"Admin API errors must not leak stack traces, SQL queries, or internal schema details.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(o=>/admin.*route|api.*admin/i.test(o));if(t.length===0)return{result:"UNKNOWN",message:"No admin API routes found"};let i=/try\s*\{|\.catch\s*\(|catch\s*\(/,s=/stack|trace|SQL|query.*error|\.message/i,r=!1,n=[];for(let o of t){let a;try{a=await e.readFile(o)}catch{continue}i.test(a)&&(r=!0);let c=a.split(`
9
- `);for(let l=0;l<c.length;l++)/error\.stack|error\.message|err\.stack|JSON\.stringify.*error/i.test(c[l])&&n.push({file:o,line:l+1,content:c[l].trim()})}return n.length>0?{result:"FAIL",message:"Admin API may leak error details (stack traces, error messages)",evidence:n.slice(0,3).map(o=>`${o.file}:${o.line} \u2192 ${o.content.substring(0,120)}`)}:r?{result:"PASS",message:"Admin error handling present without obvious information leaks"}:{result:"FAIL",message:"No error handling in admin API routes",evidence:t.slice(0,3).map(o=>`${o} \u2014 no try/catch`)}}};var Vt={id:"ADM-13",name:"MFA requirement for admin roles",module:"admin",layer:"L2",priority:"P0",description:"Admin auth must require MFA/AAL2. Compromised admin password without MFA = total takeover.",fixCost:100,fixSize:"M",async run(e){let t=/mfa|aal2|getAuthenticatorAssuranceLevel|totp|authenticator|multi.?factor|two.?factor/i,i=e.files.filter(n=>/admin/i.test(n));if(i.length===0)return{result:"UNKNOWN",message:"No admin files found"};let s=await e.grepFiles(t,i),r=await e.grepFiles(t);return s.length>0?{result:"PASS",message:"MFA/AAL2 enforcement detected in admin code"}:r.length>0?{result:"PASS",message:"MFA implementation detected in codebase (verify it covers admin routes)"}:{result:"FAIL",message:"No MFA/AAL2 enforcement found for admin roles",evidence:["No references to mfa, aal2, totp, or authenticator in admin code"]}}};var Yt={id:"ADM-14",name:"Rate limiting on admin endpoints",module:"admin",layer:"L2",priority:"P1",description:"Admin API endpoints without rate limiting are vulnerable to brute force and DoS attacks.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>/api.*admin|admin.*route/i.test(n));if(t.length===0)return{result:"UNKNOWN",message:"No admin API endpoints found"};let i=/rateLimit|rate.limit|throttle|limiter|upstash|redis.*limit|too.many.requests|429/i;return(await e.grepFiles(i,t)).length>0?{result:"PASS",message:"Rate limiting detected on admin endpoints"}:(await e.grepFiles(i)).length>0?{result:"PASS",message:"Rate limiting detected in codebase (verify it covers admin endpoints)"}:{result:"FAIL",message:"No rate limiting found on admin endpoints",evidence:["Missing: rate limiting middleware on admin API routes"]}}};var Xt={id:"ADM-16",name:"Separate admin session timeouts",module:"admin",layer:"L2",priority:"P1",description:"Admin sessions should have shorter timeouts than user sessions to limit session hijacking window.",fixCost:100,fixSize:"M",async run(e){let t=/admin.*timeout|admin.*expir|admin.*maxAge|admin.*session.*duration|session.*admin.*short/i;return(await e.grepFiles(t)).length>0?{result:"PASS",message:"Differentiated admin session timeout detected"}:(await e.grepFiles(/admin/i)).length===0?{result:"UNKNOWN",message:"No admin functionality detected"}:{result:"FAIL",message:"No separate admin session timeout \u2014 admin sessions use same duration as user sessions",evidence:["Missing: shorter session timeout for admin roles"]}}};var Jt={id:"ADM-18",name:"Admin action notification/alerting",module:"admin",layer:"L2",priority:"P2",description:"Critical admin actions should trigger notifications (Slack, email) so compromised admin accounts are detected quickly.",fixCost:100,fixSize:"M",async run(e){let t=/slack.*webhook|sendSlack|sendEmail.*admin|notify.*admin|alert.*admin|admin.*notify|admin.*alert|webhook.*notify/i,i=e.files.filter(n=>/admin/i.test(n));if(i.length===0)return{result:"UNKNOWN",message:"No admin functionality detected"};let s=await e.grepFiles(t,i),r=await e.grepFiles(t);return s.length>0||r.length>0?{result:"PASS",message:"Admin action notifications/alerting detected"}:{result:"FAIL",message:"No notification/alerting for admin actions \u2014 compromised admin goes undetected",evidence:["Missing: Slack webhook, email alert, or notification for critical admin actions"]}}};var Qt={id:"ADM-19",name:"CSRF protection for admin mutations",module:"admin",layer:"L2",priority:"P1",description:"Admin mutation endpoints need CSRF protection. If victim is admin, CSRF compromises entire app.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>/admin.*route\.(ts|js)$|api.*admin/i.test(n));if(t.length===0)return{result:"UNKNOWN",message:"No admin route handlers found"};let i=/csrf|csurf|csrfToken|SameSite|x-csrf|anti.?forgery/i;return(await e.grepFiles(i)).length>0?{result:"PASS",message:"CSRF protection detected"}:(await e.grepFiles(/["']use server["']/i)).length>0?{result:"PASS",message:"Server Actions used (built-in CSRF protection in Next.js 14+)"}:{result:"FAIL",message:"No CSRF protection on admin mutation routes",evidence:t.slice(0,3).map(n=>`${n} \u2014 no CSRF token or SameSite cookie`)}}};var Zt={id:"ADM-20",name:"Data export controls",module:"admin",layer:"L2",priority:"P1",description:"Admin bulk export/download must have authorization and logging. Compromised admin can exfiltrate all user data.",fixCost:100,fixSize:"M",async run(e){let t=/export.*csv|downloadCSV|bulk.*fetch|dump.*data|export.*users|export.*data/i,i=await e.grepFiles(t);if(i.length===0)return{result:"N/A",message:"No bulk data export functionality detected"};let s=/requireAdmin|requireAuth|getUser|checkPermission/i,r=/audit|log.*export|log.*download/i,n=[...new Set(i.map(c=>c.file))],o=await e.grepFiles(s,n),a=await e.grepFiles(r,n);return o.length>0&&a.length>0?{result:"PASS",message:"Data export has auth and logging"}:o.length>0?{result:"PASS",message:"Data export has auth (consider adding export logging)"}:{result:"FAIL",message:"Data export without auth/logging \u2014 PII exfiltration risk",evidence:i.slice(0,3).map(c=>`${c.file}:${c.line} \u2192 ${c.content.substring(0,120)}`)}}};var es={id:"ADM-21",name:"Admin provisioning control",module:"admin",layer:"L2",priority:"P1",description:"Admin role must not be self-assignable. AI tools often generate 'isFirstUser \u2192 admin' or open role selection on signup.",fixCost:100,fixSize:"M",async run(e){let t=/role\s*=\s*['"]admin['"]|isFirstUser|first.*user.*admin|role.*select|self.*assign.*admin/i,i=e.files.filter(a=>/signup|register|onboard/i.test(a)),s=await e.grepFiles(t),n=[...i.length>0?await e.grepFiles(t,i):[],...s.filter(a=>/signup|register|onboard/i.test(a.file))],o=[...new Map(n.map(a=>[`${a.file}:${a.line}`,a])).values()];return o.length>0?{result:"FAIL",message:"Self-assign admin pattern detected in signup/onboarding flow",evidence:o.slice(0,3).map(a=>`${a.file}:${a.line} \u2192 ${a.content.substring(0,120)}`)}:{result:"PASS",message:"No self-assign admin pattern detected in signup flow"}}};var ts={id:"ADM-22",name:"Edge Function authentication",module:"admin",layer:"L2",priority:"P0",description:"Supabase Edge Functions must verify the Authorization header or user session. Unprotected Edge Functions can be called by anyone with the public anon key, enabling privilege escalation (e.g. seed-admin).",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(r=>/supabase\/functions\/[^/]+\/index\.(ts|js)$/i.test(r));if(t.length===0)return{result:"PASS",message:"No Supabase Edge Functions found"};let i=/auth\.getUser|authorization.*header|req\.headers\.get\s*\(\s*['"]authorization['"]\)|verifyJWT|supabaseClient\.auth|createClient.*serviceRole/i,s=[];for(let r of t){let n=await e.readFile(r);i.test(n)||s.push(r)}return s.length>0?{result:"FAIL",message:`${s.length} Edge Function${s.length>1?"s":""} without auth verification`,evidence:s.slice(0,5).map(r=>`${r} \u2192 No Authorization header check or auth.getUser() call`)}:{result:"PASS",message:`All ${t.length} Edge Functions have auth checks`}}};var ui=["/auth/","middleware.ts","middleware.js","/domains/auth/"],di=/\b(getUser|createServerClient|supabase\.auth|currentUser|clerkMiddleware|useUser|getServerSession|useSession|authOptions|getSession)\b/;function pi(e){return ui.some(t=>e.includes(t))}function fi(e){return e.includes("node_modules")||e.includes(".next/")||e.includes("test")||e.includes("spec")||e.includes(".d.ts")||e.includes("types.ts")||e.includes("types.js")}var ss={id:"DRIFT-AUTH-01",name:"Auth logic spreading outside auth directory",module:"auth",layer:"L2",priority:"P1",category:"drift",description:"Auth patterns (getUser, createServerClient, etc.) found outside auth directories indicate module boundary drift.",fixCost:100,fixSize:"M",async run(e){let i=(await e.grepFiles(di)).filter(s=>!(fi(s.file)||s.content.trimStart().startsWith("//")||s.content.trimStart().startsWith("*")||pi(s.file)));return i.length>3?{result:"FAIL",message:`Auth logic found in ${i.length} locations outside auth directories \u2014 module boundary is leaking`,evidence:i.slice(0,5).map(s=>`${s.file}:${s.line} \u2192 ${s.content.substring(0,100)}`)}:i.length>0?{result:"PASS",message:`Minor auth references outside auth dir (${i.length}) \u2014 within acceptable range`}:{result:"PASS",message:"Auth logic is contained within auth directories"}}};var mi=/\b(getUser|createServerClient|supabase\.auth|currentUser|clerkMiddleware|auth\(\)|getServerSession|NextAuth)\b/,is={id:"DRIFT-AUTH-02",name:"Duplicate auth middleware files",module:"auth",layer:"L2",priority:"P1",category:"drift",description:"Multiple middleware files with auth logic indicate fragmented auth \u2014 a common AI generation pattern.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(s=>(s.includes("middleware.ts")||s.includes("middleware.js"))&&!s.includes("node_modules")&&!s.includes(".next/")),i=[];for(let s of t)try{let r=await e.readFile(s);mi.test(r)&&i.push(s)}catch{continue}return i.length>1?{result:"FAIL",message:`${i.length} middleware files contain auth logic \u2014 auth should have a single entry point`,evidence:i.map(s=>s)}:i.length===1?{result:"PASS",message:`Single auth middleware found: ${i[0]}`}:{result:"UNKNOWN",message:"No middleware with auth logic found"}}};var gi=/\b(getUser|createServerClient|supabase\.auth|currentUser|auth\(\)|getServerSession|requireAuth|withAuth|isAuthenticated)\b/,ns={id:"DRIFT-AUTH-03",name:"New API routes without auth checks",module:"auth",layer:"L2",priority:"P0",category:"drift",description:"API route handlers without auth verification \u2014 any visitor can call unprotected endpoints.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(s=>s.includes("/api/")&&(s.endsWith("route.ts")||s.endsWith("route.js"))&&!s.includes("node_modules")&&!s.includes(".next/")&&!s.includes("/api/auth/")&&!s.includes("/api/webhook")&&!s.includes("/api/health")&&!s.includes("/api/public"));if(t.length===0)return{result:"UNKNOWN",message:"No API route files found"};let i=[];for(let s of t)try{let r=await e.readFile(s);gi.test(r)||i.push(s)}catch{continue}return i.length>0?{result:"FAIL",message:`${i.length} of ${t.length} API routes have no auth verification`,evidence:i.slice(0,5).map(s=>s)}:{result:"PASS",message:`All ${t.length} API routes have auth checks`}}};var hi=/\b(isAdmin|role\s*===?\s*['"`]admin['"`]|user\.role|userRole|hasRole)\b/,Si=/\b(getUser|createServerClient|supabase\.auth|currentUser|getServerSession|requireAdmin|checkAdmin)\b/,rs={id:"DRIFT-AUTH-04",name:"Client-side auth bypass \u2014 role check without server verification",module:"auth",layer:"L2",priority:"P1",category:"drift",description:"Role checks in JSX/client code without server-side verification \u2014 UI-only gates can be bypassed.",fixCost:100,fixSize:"M",async run(e){let i=(await e.grepFiles(hi)).filter(n=>n.content.trimStart().startsWith("//")||n.file.includes("node_modules")||n.file.includes(".next/")?!1:w(n.file));if(i.length===0)return{result:"PASS",message:"No client-side role checks found"};let r=(await e.grepFiles(Si,["**/api/**","**/server/**","**/actions.*"])).some(n=>!n.file.includes("node_modules"));return i.length>0&&!r?{result:"FAIL",message:`${i.length} client-side role checks found but no server-side role verification \u2014 admin UI can be bypassed`,evidence:i.slice(0,5).map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,100)}`)}:{result:"PASS",message:`Client-side role checks (${i.length}) backed by server-side verification`}}};var yi=/localStorage\.(getItem|setItem|removeItem)\s*\(\s*['"`](token|jwt|session|auth|access_token|refresh_token|user)/,os={id:"DRIFT-AUTH-05",name:"Auth tokens stored in localStorage",module:"auth",layer:"L2",priority:"P1",category:"drift",description:"localStorage for auth tokens is vulnerable to XSS \u2014 use httpOnly cookies or secure session management.",fixCost:100,fixSize:"M",async run(e){let i=(await e.grepFiles(yi)).filter(s=>!(s.content.trimStart().startsWith("//")||s.file.includes("node_modules")||s.file.includes(".next/")));return i.length>0?{result:"FAIL",message:`Auth tokens stored in localStorage (${i.length} location${i.length>1?"s":""}) \u2014 vulnerable to XSS`,evidence:i.slice(0,5).map(s=>`${s.file}:${s.line} \u2192 ${s.content.substring(0,100)}`)}:{result:"PASS",message:"No localStorage auth token usage found"}}};var $i=["/billing/","/stripe/","/webhook/","/payment/","/subscription/","/domains/billing/"],Ai=/\b(stripe|Stripe|createCheckout|price_id|priceId|subscription_id|subscriptionId|checkout\.sessions|customer\.subscriptions)\b/;function bi(e){return $i.some(t=>e.includes(t))}function Ci(e){return e.includes("node_modules")||e.includes(".next/")||e.includes("test")||e.includes("spec")||e.includes(".d.ts")||e.includes("types.ts")||e.includes("types.js")||e.includes("package.json")||e.includes("package-lock")||e.includes("pnpm-lock")||e.includes(".env")}var as={id:"DRIFT-BIL-01",name:"Billing logic spreading outside billing directory",module:"billing",layer:"L2",priority:"P1",category:"drift",description:"Stripe/payment imports outside billing directories indicate module boundary drift.",fixCost:100,fixSize:"M",async run(e){let i=(await e.grepFiles(Ai)).filter(s=>!(Ci(s.file)||s.content.trimStart().startsWith("//")||s.content.trimStart().startsWith("*")||s.content.trimStart().startsWith("#")||bi(s.file)));return i.length>3?{result:"FAIL",message:`Billing/Stripe logic found in ${i.length} locations outside billing directories \u2014 module boundary is leaking`,evidence:i.slice(0,5).map(s=>`${s.file}:${s.line} \u2192 ${s.content.substring(0,100)}`)}:i.length>0?{result:"PASS",message:`Minor billing references outside billing dir (${i.length}) \u2014 within acceptable range`}:{result:"PASS",message:"Billing logic is contained within billing directories"}}};var vi=/\b(webhook|stripe.*event|event\.type|constructEvent)\b/i,cs={id:"DRIFT-BIL-02",name:"Duplicate webhook handler endpoints",module:"billing",layer:"L2",priority:"P0",category:"drift",description:"Multiple webhook endpoints create race conditions and duplicate event processing.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(s=>!s.includes("node_modules")&&!s.includes(".next/")&&!s.includes("test")&&(s.includes("webhook")||s.includes("stripe"))&&(s.endsWith(".ts")||s.endsWith(".js")||s.endsWith(".py"))),i=[];for(let s of t)try{let r=await e.readFile(s);vi.test(r)&&i.push(s)}catch{continue}return i.length>1?{result:"FAIL",message:`${i.length} webhook handler files found \u2014 should be exactly 1`,evidence:i}:i.length===1?{result:"PASS",message:`Single webhook handler: ${i[0]}`}:{result:"UNKNOWN",message:"No webhook handler files found \u2014 Stripe webhooks may not be configured"}}};var ki=/\b(subscription|plan|isPro|isFreeTier|isPaid|currentPlan|userPlan|hasSubscription)\b/,ls={id:"DRIFT-BIL-03",name:"Client-side subscription check without server verification",module:"billing",layer:"L2",priority:"P1",category:"drift",description:"Plan/subscription checks in client code without server-side verification \u2014 users can bypass paywalls.",fixCost:100,fixSize:"M",async run(e){let i=(await e.grepFiles(ki)).filter(n=>n.content.trimStart().startsWith("//")||n.file.includes("node_modules")||n.file.includes(".next/")||n.file.includes("test")||n.file.includes("types")?!1:w(n.file)&&!n.file.includes("/api/"));if(i.length===0)return{result:"PASS",message:"No client-side subscription checks found"};let r=(await e.grepFiles(/\b(subscription|check.*plan|check.*limit|entitlement|getSubscription)\b/i,["**/api/**","**/server/**","**/actions.*","**/billing/**"])).some(n=>!n.file.includes("node_modules"));return i.length>0&&!r?{result:"FAIL",message:`${i.length} client-side subscription checks but no server-side plan verification \u2014 paywall can be bypassed`,evidence:i.slice(0,5).map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,100)}`)}:{result:"PASS",message:`Client-side subscription checks (${i.length}) backed by server-side verification`}}};var _i=/(?:price|amount|cost|fee)\s*[:=]\s*(\d{2,}(?:\.\d{2})?|['"`]\$?\d+)/i,Ii=/(?:amount|price|unit_amount)\s*[:=]\s*\d{3,}/,us={id:"DRIFT-BIL-04",name:"Hardcoded prices instead of plan configuration",module:"billing",layer:"L2",priority:"P2",category:"drift",description:"Hardcoded dollar amounts or price values should come from plan configuration or Stripe metadata.",fixCost:100,fixSize:"M",async run(e){let t=await e.grepFiles(_i),i=await e.grepFiles(Ii),r=[...t,...i].filter(n=>!(n.content.trimStart().startsWith("//")||n.content.trimStart().startsWith("*")||n.file.includes("node_modules")||n.file.includes(".next/")||n.file.includes("test")||n.file.includes("package.json")||n.file.includes(".lock")||n.file.includes(".css")||n.file.includes("migration")));return r.length>3?{result:"FAIL",message:`${r.length} hardcoded price/amount values found \u2014 prices should come from plan config or Stripe`,evidence:r.slice(0,5).map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,100)}`)}:r.length>0?{result:"PASS",message:`Minor hardcoded amounts found (${r.length}) \u2014 review recommended`}:{result:"PASS",message:"No hardcoded price values detected"}}};var Pi=/\b(canAccess|hasFeature|featureEnabled|isEnabled|featureFlag|canUse)\b/,wi=/\b(checkLimit|checkSubscription|checkPlan|entitlement|billingGuard|requirePlan|checkQuota)\b/,ds={id:"DRIFT-BIL-05",name:"Feature access without billing verification",module:"billing",layer:"L2",priority:"P2",category:"drift",description:"New features with access gates but no billing/entitlement check \u2014 free users can access paid features.",fixCost:100,fixSize:"M",async run(e){let i=(await e.grepFiles(Pi)).filter(n=>!(n.content.trimStart().startsWith("//")||n.file.includes("node_modules")||n.file.includes(".next/")||n.file.includes("test")));if(i.length===0)return{result:"UNKNOWN",message:"No feature gate patterns found \u2014 may not use feature flags"};let r=(await e.grepFiles(wi)).some(n=>!n.file.includes("node_modules")&&!n.file.includes(".next/"));return i.length>0&&!r?{result:"FAIL",message:`${i.length} feature gate(s) found but no billing/entitlement verification \u2014 paid features may be accessible for free`,evidence:i.slice(0,5).map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,100)}`)}:{result:"PASS",message:`Feature gates (${i.length}) backed by billing verification`}}};var Ri=["/admin/","/domains/admin/"],Li=/\b(isAdmin|requireAdmin|checkAdmin|adminGuard|role\s*===?\s*['"`]admin['"`]|user_role|superAdmin|isSuperAdmin)\b/;function Ni(e){return Ri.some(t=>e.includes(t))}function Fi(e){return e.includes("node_modules")||e.includes(".next/")||e.includes("test")||e.includes("spec")||e.includes(".d.ts")||e.includes("types.ts")||e.includes("types.js")||e.includes("migration")||e.includes(".sql")}var ps={id:"DRIFT-ADM-01",name:"Admin logic spreading outside admin directory",module:"admin",layer:"L2",priority:"P1",category:"drift",description:"Admin permission patterns found outside admin directories indicate module boundary drift.",fixCost:100,fixSize:"M",async run(e){let i=(await e.grepFiles(Li)).filter(s=>!(Fi(s.file)||s.content.trimStart().startsWith("//")||s.content.trimStart().startsWith("*")||Ni(s.file)));return i.length>3?{result:"FAIL",message:`Admin logic found in ${i.length} locations outside admin directories \u2014 module boundary is leaking`,evidence:i.slice(0,5).map(s=>`${s.file}:${s.line} \u2192 ${s.content.substring(0,100)}`)}:i.length>0?{result:"PASS",message:`Minor admin references outside admin dir (${i.length}) \u2014 within acceptable range`}:{result:"PASS",message:"Admin logic is contained within admin directories"}}};var xi=/\b(requireAdmin|checkAdmin|adminGuard|isAdmin|role\s*===?\s*['"`]admin['"`]|isSuperAdmin)\b/,fs={id:"DRIFT-ADM-02",name:"New admin routes without permission guards",module:"admin",layer:"L2",priority:"P0",category:"drift",description:"Admin route handlers without permission checks \u2014 any authenticated user can access admin features.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(s=>s.includes("/admin/")&&!s.includes("node_modules")&&!s.includes(".next/")&&!s.includes("test")&&(s.endsWith("route.ts")||s.endsWith("route.js")||s.endsWith("page.tsx")||s.endsWith("page.jsx")||s.endsWith("page.ts")));if(t.length===0)return{result:"UNKNOWN",message:"No admin route files found"};let i=[];for(let s of t)try{let r=await e.readFile(s);xi.test(r)||i.push(s)}catch{continue}return i.length>0?{result:"FAIL",message:`${i.length} of ${t.length} admin routes have no permission guard`,evidence:i.slice(0,5).map(s=>s)}:{result:"PASS",message:`All ${t.length} admin routes have permission guards`}}};var ms=/\b(logAuditEvent|auditLog|audit_log|createAuditEntry|logAdminAction|insertAuditLog)\b/,gs=/\b(DELETE|PUT|PATCH|POST)\b/,hs={id:"DRIFT-ADM-03",name:"Admin mutations without audit logging",module:"admin",layer:"L2",priority:"P1",category:"drift",description:"Admin mutation endpoints (POST/PUT/DELETE) without audit log calls \u2014 no trail of admin actions.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(s=>s.includes("/admin/")&&s.includes("/api/")&&!s.includes("node_modules")&&!s.includes(".next/")&&!s.includes("test")&&(s.endsWith("route.ts")||s.endsWith("route.js")));if(t.length===0){let s=e.files.filter(n=>n.includes("/admin/")&&!n.includes("node_modules")&&!n.includes(".next/")&&(n.includes("action")||n.includes("service")||n.includes("handler"))&&(n.endsWith(".ts")||n.endsWith(".js")||n.endsWith(".py")));if(s.length===0)return{result:"UNKNOWN",message:"No admin API routes or action files found"};let r=[];for(let n of s)try{let o=await e.readFile(n);gs.test(o)&&!ms.test(o)&&r.push(n)}catch{continue}return r.length>0?{result:"FAIL",message:`${r.length} admin action file(s) with mutations but no audit logging`,evidence:r.slice(0,5)}:{result:"PASS",message:"Admin action files have audit logging"}}let i=[];for(let s of t)try{let r=await e.readFile(s);gs.test(r)&&!ms.test(r)&&i.push(s)}catch{continue}return i.length>0?{result:"FAIL",message:`${i.length} of ${t.length} admin API routes have mutations without audit logging`,evidence:i.slice(0,5)}:{result:"PASS",message:`All ${t.length} admin API routes have audit logging`}}};var Ss={id:"ENV-01",name:".env.example exists",module:"foundation",layer:"L3",priority:"P1",description:"Project should have .env.example documenting required environment variables",fixCost:50,fixSize:"S",async run(e){return e.files.some(s=>s===".env.example"||s.endsWith("/.env.example"))?{result:"PASS",message:".env.example found"}:e.files.some(s=>s===".env"||s===".env.local"||s.endsWith("/.env")||s.endsWith("/.env.local"))?{result:"FAIL",message:"Project uses .env files but has no .env.example for documentation",evidence:["Create .env.example listing all required vars (without secret values)"]}:{result:"UNKNOWN",message:"No .env files found \u2014 project may not use environment variables"}}};var ys={id:"ENV-02",name:"No secrets in committed .env",module:"foundation",layer:"L3",priority:"P0",description:"Committed .env files must not contain actual secret values \u2014 only .env.example with placeholders",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>{let o=n.split("/").pop()||"";return(o===".env"||o===".env.local"||o===".env.production")&&!o.includes("example")});if(t.length===0)return{result:"PASS",message:"No committed .env files found"};let i=/^[A-Z_]+=(?:sk_live_|sk_test_|whsec_|sbp_|eyJ|ghp_|gho_|AKIA|supabase.*service_role)/m,s=/^[A-Z_]+=.{20,}/m,r=[];for(let n of t){let o;try{o=await e.readFile(n)}catch{continue}if(i.test(o))r.push(`${n} \u2014 contains secret key patterns`);else if(s.test(o)){let a=o.split(`
9
+ `);for(let l=0;l<c.length;l++)/error\.stack|error\.message|err\.stack|JSON\.stringify.*error/i.test(c[l])&&n.push({file:o,line:l+1,content:c[l].trim()})}return n.length>0?{result:"FAIL",message:"Admin API may leak error details (stack traces, error messages)",evidence:n.slice(0,3).map(o=>`${o.file}:${o.line} \u2192 ${o.content.substring(0,120)}`)}:r?{result:"PASS",message:"Admin error handling present without obvious information leaks"}:{result:"FAIL",message:"No error handling in admin API routes",evidence:t.slice(0,3).map(o=>`${o} \u2014 no try/catch`)}}};var Vt={id:"ADM-13",name:"MFA requirement for admin roles",module:"admin",layer:"L2",priority:"P0",description:"Admin auth must require MFA/AAL2. Compromised admin password without MFA = total takeover.",fixCost:100,fixSize:"M",async run(e){let t=/mfa|aal2|getAuthenticatorAssuranceLevel|totp|authenticator|multi.?factor|two.?factor/i,i=e.files.filter(n=>/admin/i.test(n));if(i.length===0)return{result:"UNKNOWN",message:"No admin files found"};let s=await e.grepFiles(t,i),r=await e.grepFiles(t);return s.length>0?{result:"PASS",message:"MFA/AAL2 enforcement detected in admin code"}:r.length>0?{result:"PASS",message:"MFA implementation detected in codebase (verify it covers admin routes)"}:{result:"FAIL",message:"No MFA/AAL2 enforcement found for admin roles",evidence:["No references to mfa, aal2, totp, or authenticator in admin code"]}}};var Yt={id:"ADM-14",name:"Rate limiting on admin endpoints",module:"admin",layer:"L2",priority:"P1",description:"Admin API endpoints without rate limiting are vulnerable to brute force and DoS attacks.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>/api.*admin|admin.*route/i.test(n));if(t.length===0)return{result:"UNKNOWN",message:"No admin API endpoints found"};let i=/rateLimit|rate.limit|throttle|limiter|upstash|redis.*limit|too.many.requests|429/i;return(await e.grepFiles(i,t)).length>0?{result:"PASS",message:"Rate limiting detected on admin endpoints"}:(await e.grepFiles(i)).length>0?{result:"PASS",message:"Rate limiting detected in codebase (verify it covers admin endpoints)"}:{result:"FAIL",message:"No rate limiting found on admin endpoints",evidence:["Missing: rate limiting middleware on admin API routes"]}}};var Xt={id:"ADM-16",name:"Separate admin session timeouts",module:"admin",layer:"L2",priority:"P1",description:"Admin sessions should have shorter timeouts than user sessions to limit session hijacking window.",fixCost:100,fixSize:"M",async run(e){let t=/admin.*timeout|admin.*expir|admin.*maxAge|admin.*session.*duration|session.*admin.*short/i;return(await e.grepFiles(t)).length>0?{result:"PASS",message:"Differentiated admin session timeout detected"}:(await e.grepFiles(/admin/i)).length===0?{result:"UNKNOWN",message:"No admin functionality detected"}:{result:"FAIL",message:"No separate admin session timeout \u2014 admin sessions use same duration as user sessions",evidence:["Missing: shorter session timeout for admin roles"]}}};var Jt={id:"ADM-18",name:"Admin action notification/alerting",module:"admin",layer:"L2",priority:"P2",description:"Critical admin actions should trigger notifications (Slack, email) so compromised admin accounts are detected quickly.",fixCost:100,fixSize:"M",async run(e){let t=/slack.*webhook|sendSlack|sendEmail.*admin|notify.*admin|alert.*admin|admin.*notify|admin.*alert|webhook.*notify/i,i=e.files.filter(n=>/admin/i.test(n));if(i.length===0)return{result:"UNKNOWN",message:"No admin functionality detected"};let s=await e.grepFiles(t,i),r=await e.grepFiles(t);return s.length>0||r.length>0?{result:"PASS",message:"Admin action notifications/alerting detected"}:{result:"FAIL",message:"No notification/alerting for admin actions \u2014 compromised admin goes undetected",evidence:["Missing: Slack webhook, email alert, or notification for critical admin actions"]}}};var Qt={id:"ADM-19",name:"CSRF protection for admin mutations",module:"admin",layer:"L2",priority:"P1",description:"Admin mutation endpoints need CSRF protection. If victim is admin, CSRF compromises entire app.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>/admin.*route\.(ts|js)$|api.*admin/i.test(n));if(t.length===0)return{result:"UNKNOWN",message:"No admin route handlers found"};let i=/csrf|csurf|csrfToken|SameSite|x-csrf|anti.?forgery/i;return(await e.grepFiles(i)).length>0?{result:"PASS",message:"CSRF protection detected"}:(await e.grepFiles(/["']use server["']/i)).length>0?{result:"PASS",message:"Server Actions used (built-in CSRF protection in Next.js 14+)"}:{result:"FAIL",message:"No CSRF protection on admin mutation routes",evidence:t.slice(0,3).map(n=>`${n} \u2014 no CSRF token or SameSite cookie`)}}};var Zt={id:"ADM-20",name:"Data export controls",module:"admin",layer:"L2",priority:"P1",description:"Admin bulk export/download must have authorization and logging. Compromised admin can exfiltrate all user data.",fixCost:100,fixSize:"M",async run(e){let t=/export.*csv|downloadCSV|bulk.*fetch|dump.*data|export.*users|export.*data/i,i=await e.grepFiles(t);if(i.length===0)return{result:"N/A",message:"No bulk data export functionality detected"};let s=/requireAdmin|requireAuth|getUser|checkPermission/i,r=/audit|log.*export|log.*download/i,n=[...new Set(i.map(c=>c.file))],o=await e.grepFiles(s,n),a=await e.grepFiles(r,n);return o.length>0&&a.length>0?{result:"PASS",message:"Data export has auth and logging"}:o.length>0?{result:"PASS",message:"Data export has auth (consider adding export logging)"}:{result:"FAIL",message:"Data export without auth/logging \u2014 PII exfiltration risk",evidence:i.slice(0,3).map(c=>`${c.file}:${c.line} \u2192 ${c.content.substring(0,120)}`)}}};var es={id:"ADM-21",name:"Admin provisioning control",module:"admin",layer:"L2",priority:"P1",description:"Admin role must not be self-assignable. AI tools often generate 'isFirstUser \u2192 admin' or open role selection on signup.",fixCost:100,fixSize:"M",async run(e){let t=/role\s*=\s*['"]admin['"]|isFirstUser|first.*user.*admin|role.*select|self.*assign.*admin/i,i=e.files.filter(a=>/signup|register|onboard/i.test(a)),s=await e.grepFiles(t),n=[...i.length>0?await e.grepFiles(t,i):[],...s.filter(a=>/signup|register|onboard/i.test(a.file))],o=[...new Map(n.map(a=>[`${a.file}:${a.line}`,a])).values()];return o.length>0?{result:"FAIL",message:"Self-assign admin pattern detected in signup/onboarding flow",evidence:o.slice(0,3).map(a=>`${a.file}:${a.line} \u2192 ${a.content.substring(0,120)}`)}:{result:"PASS",message:"No self-assign admin pattern detected in signup flow"}}};var ts={id:"ADM-22",name:"Edge Function authentication",module:"admin",layer:"L2",priority:"P0",description:"Supabase Edge Functions must verify the Authorization header or user session. Unprotected Edge Functions can be called by anyone with the public anon key, enabling privilege escalation (e.g. seed-admin).",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(r=>/supabase\/functions\/[^/]+\/index\.(ts|js)$/i.test(r));if(t.length===0)return{result:"PASS",message:"No Supabase Edge Functions found"};let i=/auth\.getUser|authorization.*header|req\.headers\.get\s*\(\s*['"]authorization['"]\)|verifyJWT|supabaseClient\.auth|createClient.*serviceRole/i,s=[];for(let r of t){let n=await e.readFile(r);i.test(n)||s.push(r)}return s.length>0?{result:"FAIL",message:`${s.length} Edge Function${s.length>1?"s":""} without auth verification`,evidence:s.slice(0,5).map(r=>`${r} \u2192 No Authorization header check or auth.getUser() call`)}:{result:"PASS",message:`All ${t.length} Edge Functions have auth checks`}}};var ui=["/auth/","middleware.ts","middleware.js","/domains/auth/"],di=/\b(getUser|createServerClient|supabase\.auth|currentUser|clerkMiddleware|useUser|getServerSession|useSession|authOptions|getSession)\b/;function pi(e){return ui.some(t=>e.includes(t))}function fi(e){return e.includes("node_modules")||e.includes(".next/")||e.includes("test")||e.includes("spec")||e.includes(".d.ts")||e.includes("types.ts")||e.includes("types.js")}var ss={id:"DRIFT-AUTH-01",name:"Auth logic spreading outside auth directory",module:"auth",layer:"L2",priority:"P1",category:"drift",description:"Auth patterns (getUser, createServerClient, etc.) found outside auth directories indicate module boundary drift.",fixCost:100,fixSize:"M",async run(e){let i=(await e.grepFiles(di)).filter(s=>!(fi(s.file)||s.content.trimStart().startsWith("//")||s.content.trimStart().startsWith("*")||pi(s.file)));return i.length>3?{result:"FAIL",message:`Auth logic found in ${i.length} locations outside auth directories \u2014 module boundary is leaking`,evidence:i.slice(0,5).map(s=>`${s.file}:${s.line} \u2192 ${s.content.substring(0,100)}`)}:i.length>0?{result:"PASS",message:`Minor auth references outside auth dir (${i.length}) \u2014 within acceptable range`}:{result:"PASS",message:"Auth logic is contained within auth directories"}}};var mi=/\b(getUser|createServerClient|supabase\.auth|currentUser|clerkMiddleware|auth\(\)|getServerSession|NextAuth)\b/,is={id:"DRIFT-AUTH-02",name:"Duplicate auth middleware files",module:"auth",layer:"L2",priority:"P1",category:"drift",description:"Multiple middleware files with auth logic indicate fragmented auth \u2014 a common AI generation pattern.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(s=>(s.includes("middleware.ts")||s.includes("middleware.js"))&&!s.includes("node_modules")&&!s.includes(".next/")),i=[];for(let s of t)try{let r=await e.readFile(s);mi.test(r)&&i.push(s)}catch{continue}return i.length>1?{result:"FAIL",message:`${i.length} middleware files contain auth logic \u2014 auth should have a single entry point`,evidence:i.map(s=>s)}:i.length===1?{result:"PASS",message:`Single auth middleware found: ${i[0]}`}:{result:"UNKNOWN",message:"No middleware with auth logic found"}}};var gi=/\b(getUser|createServerClient|supabase\.auth|currentUser|auth\(\)|getServerSession|requireAuth|withAuth|isAuthenticated)\b/,ns={id:"DRIFT-AUTH-03",name:"New API routes without auth checks",module:"auth",layer:"L2",priority:"P0",category:"drift",description:"API route handlers without auth verification \u2014 any visitor can call unprotected endpoints.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(s=>s.includes("/api/")&&(s.endsWith("route.ts")||s.endsWith("route.js"))&&!s.includes("node_modules")&&!s.includes(".next/")&&!s.includes("/api/auth/")&&!s.includes("/api/webhook")&&!s.includes("/api/health")&&!s.includes("/api/public"));if(t.length===0)return{result:"UNKNOWN",message:"No API route files found"};let i=[];for(let s of t)try{let r=await e.readFile(s);gi.test(r)||i.push(s)}catch{continue}return i.length>0?{result:"FAIL",message:`${i.length} of ${t.length} API routes have no auth verification`,evidence:i.slice(0,5).map(s=>s)}:{result:"PASS",message:`All ${t.length} API routes have auth checks`}}};var hi=/\b(isAdmin|role\s*===?\s*['"`]admin['"`]|user\.role|userRole|hasRole)\b/,yi=/\b(getUser|createServerClient|supabase\.auth|currentUser|getServerSession|requireAdmin|checkAdmin)\b/,rs={id:"DRIFT-AUTH-04",name:"Client-side auth bypass \u2014 role check without server verification",module:"auth",layer:"L2",priority:"P1",category:"drift",description:"Role checks in JSX/client code without server-side verification \u2014 UI-only gates can be bypassed.",fixCost:100,fixSize:"M",async run(e){let i=(await e.grepFiles(hi)).filter(n=>n.content.trimStart().startsWith("//")||n.file.includes("node_modules")||n.file.includes(".next/")?!1:w(n.file));if(i.length===0)return{result:"PASS",message:"No client-side role checks found"};let r=(await e.grepFiles(yi,["**/api/**","**/server/**","**/actions.*"])).some(n=>!n.file.includes("node_modules"));return i.length>0&&!r?{result:"FAIL",message:`${i.length} client-side role checks found but no server-side role verification \u2014 admin UI can be bypassed`,evidence:i.slice(0,5).map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,100)}`)}:{result:"PASS",message:`Client-side role checks (${i.length}) backed by server-side verification`}}};var Si=/localStorage\.(getItem|setItem|removeItem)\s*\(\s*['"`](token|jwt|session|auth|access_token|refresh_token|user)/,os={id:"DRIFT-AUTH-05",name:"Auth tokens stored in localStorage",module:"auth",layer:"L2",priority:"P1",category:"drift",description:"localStorage for auth tokens is vulnerable to XSS \u2014 use httpOnly cookies or secure session management.",fixCost:100,fixSize:"M",async run(e){let i=(await e.grepFiles(Si)).filter(s=>!(s.content.trimStart().startsWith("//")||s.file.includes("node_modules")||s.file.includes(".next/")));return i.length>0?{result:"FAIL",message:`Auth tokens stored in localStorage (${i.length} location${i.length>1?"s":""}) \u2014 vulnerable to XSS`,evidence:i.slice(0,5).map(s=>`${s.file}:${s.line} \u2192 ${s.content.substring(0,100)}`)}:{result:"PASS",message:"No localStorage auth token usage found"}}};var $i=["/billing/","/stripe/","/webhook/","/payment/","/subscription/","/domains/billing/"],Ai=/\b(stripe|Stripe|createCheckout|price_id|priceId|subscription_id|subscriptionId|checkout\.sessions|customer\.subscriptions)\b/;function bi(e){return $i.some(t=>e.includes(t))}function Ci(e){return e.includes("node_modules")||e.includes(".next/")||e.includes("test")||e.includes("spec")||e.includes(".d.ts")||e.includes("types.ts")||e.includes("types.js")||e.includes("package.json")||e.includes("package-lock")||e.includes("pnpm-lock")||e.includes(".env")}var as={id:"DRIFT-BIL-01",name:"Billing logic spreading outside billing directory",module:"billing",layer:"L2",priority:"P1",category:"drift",description:"Stripe/payment imports outside billing directories indicate module boundary drift.",fixCost:100,fixSize:"M",async run(e){let i=(await e.grepFiles(Ai)).filter(s=>!(Ci(s.file)||s.content.trimStart().startsWith("//")||s.content.trimStart().startsWith("*")||s.content.trimStart().startsWith("#")||bi(s.file)));return i.length>3?{result:"FAIL",message:`Billing/Stripe logic found in ${i.length} locations outside billing directories \u2014 module boundary is leaking`,evidence:i.slice(0,5).map(s=>`${s.file}:${s.line} \u2192 ${s.content.substring(0,100)}`)}:i.length>0?{result:"PASS",message:`Minor billing references outside billing dir (${i.length}) \u2014 within acceptable range`}:{result:"PASS",message:"Billing logic is contained within billing directories"}}};var vi=/\b(webhook|stripe.*event|event\.type|constructEvent)\b/i,cs={id:"DRIFT-BIL-02",name:"Duplicate webhook handler endpoints",module:"billing",layer:"L2",priority:"P0",category:"drift",description:"Multiple webhook endpoints create race conditions and duplicate event processing.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(s=>!s.includes("node_modules")&&!s.includes(".next/")&&!s.includes("test")&&(s.includes("webhook")||s.includes("stripe"))&&(s.endsWith(".ts")||s.endsWith(".js")||s.endsWith(".py"))),i=[];for(let s of t)try{let r=await e.readFile(s);vi.test(r)&&i.push(s)}catch{continue}return i.length>1?{result:"FAIL",message:`${i.length} webhook handler files found \u2014 should be exactly 1`,evidence:i}:i.length===1?{result:"PASS",message:`Single webhook handler: ${i[0]}`}:{result:"UNKNOWN",message:"No webhook handler files found \u2014 Stripe webhooks may not be configured"}}};var ki=/\b(subscription|plan|isPro|isFreeTier|isPaid|currentPlan|userPlan|hasSubscription)\b/,ls={id:"DRIFT-BIL-03",name:"Client-side subscription check without server verification",module:"billing",layer:"L2",priority:"P1",category:"drift",description:"Plan/subscription checks in client code without server-side verification \u2014 users can bypass paywalls.",fixCost:100,fixSize:"M",async run(e){let i=(await e.grepFiles(ki)).filter(n=>n.content.trimStart().startsWith("//")||n.file.includes("node_modules")||n.file.includes(".next/")||n.file.includes("test")||n.file.includes("types")?!1:w(n.file)&&!n.file.includes("/api/"));if(i.length===0)return{result:"PASS",message:"No client-side subscription checks found"};let r=(await e.grepFiles(/\b(subscription|check.*plan|check.*limit|entitlement|getSubscription)\b/i,["**/api/**","**/server/**","**/actions.*","**/billing/**"])).some(n=>!n.file.includes("node_modules"));return i.length>0&&!r?{result:"FAIL",message:`${i.length} client-side subscription checks but no server-side plan verification \u2014 paywall can be bypassed`,evidence:i.slice(0,5).map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,100)}`)}:{result:"PASS",message:`Client-side subscription checks (${i.length}) backed by server-side verification`}}};var _i=/(?:price|amount|cost|fee)\s*[:=]\s*(\d{2,}(?:\.\d{2})?|['"`]\$?\d+)/i,Ii=/(?:amount|price|unit_amount)\s*[:=]\s*\d{3,}/,us={id:"DRIFT-BIL-04",name:"Hardcoded prices instead of plan configuration",module:"billing",layer:"L2",priority:"P2",category:"drift",description:"Hardcoded dollar amounts or price values should come from plan configuration or Stripe metadata.",fixCost:100,fixSize:"M",async run(e){let t=await e.grepFiles(_i),i=await e.grepFiles(Ii),r=[...t,...i].filter(n=>!(n.content.trimStart().startsWith("//")||n.content.trimStart().startsWith("*")||n.file.includes("node_modules")||n.file.includes(".next/")||n.file.includes("test")||n.file.includes("package.json")||n.file.includes(".lock")||n.file.includes(".css")||n.file.includes("migration")));return r.length>3?{result:"FAIL",message:`${r.length} hardcoded price/amount values found \u2014 prices should come from plan config or Stripe`,evidence:r.slice(0,5).map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,100)}`)}:r.length>0?{result:"PASS",message:`Minor hardcoded amounts found (${r.length}) \u2014 review recommended`}:{result:"PASS",message:"No hardcoded price values detected"}}};var Pi=/\b(canAccess|hasFeature|featureEnabled|isEnabled|featureFlag|canUse)\b/,wi=/\b(checkLimit|checkSubscription|checkPlan|entitlement|billingGuard|requirePlan|checkQuota)\b/,ds={id:"DRIFT-BIL-05",name:"Feature access without billing verification",module:"billing",layer:"L2",priority:"P2",category:"drift",description:"New features with access gates but no billing/entitlement check \u2014 free users can access paid features.",fixCost:100,fixSize:"M",async run(e){let i=(await e.grepFiles(Pi)).filter(n=>!(n.content.trimStart().startsWith("//")||n.file.includes("node_modules")||n.file.includes(".next/")||n.file.includes("test")));if(i.length===0)return{result:"UNKNOWN",message:"No feature gate patterns found \u2014 may not use feature flags"};let r=(await e.grepFiles(wi)).some(n=>!n.file.includes("node_modules")&&!n.file.includes(".next/"));return i.length>0&&!r?{result:"FAIL",message:`${i.length} feature gate(s) found but no billing/entitlement verification \u2014 paid features may be accessible for free`,evidence:i.slice(0,5).map(n=>`${n.file}:${n.line} \u2192 ${n.content.substring(0,100)}`)}:{result:"PASS",message:`Feature gates (${i.length}) backed by billing verification`}}};var Ri=["/admin/","/domains/admin/"],Li=/\b(isAdmin|requireAdmin|checkAdmin|adminGuard|role\s*===?\s*['"`]admin['"`]|user_role|superAdmin|isSuperAdmin)\b/;function Ni(e){return Ri.some(t=>e.includes(t))}function Fi(e){return e.includes("node_modules")||e.includes(".next/")||e.includes("test")||e.includes("spec")||e.includes(".d.ts")||e.includes("types.ts")||e.includes("types.js")||e.includes("migration")||e.includes(".sql")}var ps={id:"DRIFT-ADM-01",name:"Admin logic spreading outside admin directory",module:"admin",layer:"L2",priority:"P1",category:"drift",description:"Admin permission patterns found outside admin directories indicate module boundary drift.",fixCost:100,fixSize:"M",async run(e){let i=(await e.grepFiles(Li)).filter(s=>!(Fi(s.file)||s.content.trimStart().startsWith("//")||s.content.trimStart().startsWith("*")||Ni(s.file)));return i.length>3?{result:"FAIL",message:`Admin logic found in ${i.length} locations outside admin directories \u2014 module boundary is leaking`,evidence:i.slice(0,5).map(s=>`${s.file}:${s.line} \u2192 ${s.content.substring(0,100)}`)}:i.length>0?{result:"PASS",message:`Minor admin references outside admin dir (${i.length}) \u2014 within acceptable range`}:{result:"PASS",message:"Admin logic is contained within admin directories"}}};var xi=/\b(requireAdmin|checkAdmin|adminGuard|isAdmin|role\s*===?\s*['"`]admin['"`]|isSuperAdmin)\b/,fs={id:"DRIFT-ADM-02",name:"New admin routes without permission guards",module:"admin",layer:"L2",priority:"P0",category:"drift",description:"Admin route handlers without permission checks \u2014 any authenticated user can access admin features.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(s=>s.includes("/admin/")&&!s.includes("node_modules")&&!s.includes(".next/")&&!s.includes("test")&&(s.endsWith("route.ts")||s.endsWith("route.js")||s.endsWith("page.tsx")||s.endsWith("page.jsx")||s.endsWith("page.ts")));if(t.length===0)return{result:"UNKNOWN",message:"No admin route files found"};let i=[];for(let s of t)try{let r=await e.readFile(s);xi.test(r)||i.push(s)}catch{continue}return i.length>0?{result:"FAIL",message:`${i.length} of ${t.length} admin routes have no permission guard`,evidence:i.slice(0,5).map(s=>s)}:{result:"PASS",message:`All ${t.length} admin routes have permission guards`}}};var ms=/\b(logAuditEvent|auditLog|audit_log|createAuditEntry|logAdminAction|insertAuditLog)\b/,gs=/\b(DELETE|PUT|PATCH|POST)\b/,hs={id:"DRIFT-ADM-03",name:"Admin mutations without audit logging",module:"admin",layer:"L2",priority:"P1",category:"drift",description:"Admin mutation endpoints (POST/PUT/DELETE) without audit log calls \u2014 no trail of admin actions.",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(s=>s.includes("/admin/")&&s.includes("/api/")&&!s.includes("node_modules")&&!s.includes(".next/")&&!s.includes("test")&&(s.endsWith("route.ts")||s.endsWith("route.js")));if(t.length===0){let s=e.files.filter(n=>n.includes("/admin/")&&!n.includes("node_modules")&&!n.includes(".next/")&&(n.includes("action")||n.includes("service")||n.includes("handler"))&&(n.endsWith(".ts")||n.endsWith(".js")||n.endsWith(".py")));if(s.length===0)return{result:"UNKNOWN",message:"No admin API routes or action files found"};let r=[];for(let n of s)try{let o=await e.readFile(n);gs.test(o)&&!ms.test(o)&&r.push(n)}catch{continue}return r.length>0?{result:"FAIL",message:`${r.length} admin action file(s) with mutations but no audit logging`,evidence:r.slice(0,5)}:{result:"PASS",message:"Admin action files have audit logging"}}let i=[];for(let s of t)try{let r=await e.readFile(s);gs.test(r)&&!ms.test(r)&&i.push(s)}catch{continue}return i.length>0?{result:"FAIL",message:`${i.length} of ${t.length} admin API routes have mutations without audit logging`,evidence:i.slice(0,5)}:{result:"PASS",message:`All ${t.length} admin API routes have audit logging`}}};var ys={id:"ENV-01",name:".env.example exists",module:"foundation",layer:"L3",priority:"P1",description:"Project should have .env.example documenting required environment variables",fixCost:50,fixSize:"S",async run(e){return e.files.some(s=>s===".env.example"||s.endsWith("/.env.example"))?{result:"PASS",message:".env.example found"}:e.files.some(s=>s===".env"||s===".env.local"||s.endsWith("/.env")||s.endsWith("/.env.local"))?{result:"FAIL",message:"Project uses .env files but has no .env.example for documentation",evidence:["Create .env.example listing all required vars (without secret values)"]}:{result:"UNKNOWN",message:"No .env files found \u2014 project may not use environment variables"}}};var Ss={id:"ENV-02",name:"No secrets in committed .env",module:"foundation",layer:"L3",priority:"P0",description:"Committed .env files must not contain actual secret values \u2014 only .env.example with placeholders",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>{let o=n.split("/").pop()||"";return(o===".env"||o===".env.local"||o===".env.production")&&!o.includes("example")});if(t.length===0)return{result:"PASS",message:"No committed .env files found"};let i=/^[A-Z_]+=(?:sk_live_|sk_test_|whsec_|sbp_|eyJ|ghp_|gho_|AKIA|supabase.*service_role)/m,s=/^[A-Z_]+=.{20,}/m,r=[];for(let n of t){let o;try{o=await e.readFile(n)}catch{continue}if(i.test(o))r.push(`${n} \u2014 contains secret key patterns`);else if(s.test(o)){let a=o.split(`
10
10
  `).filter(c=>/^[A-Z_]+=.{20,}/.test(c)&&!c.startsWith("#")&&!c.includes("your_")&&!c.includes("xxx"));a.length>2&&r.push(`${n} \u2014 appears to contain real values (${a.length} long values)`)}}return r.length>0?{result:"FAIL",message:`Secrets found in committed .env file${r.length>1?"s":""}`,evidence:r}:{result:"FAIL",message:`.env file${t.length>1?"s":""} committed to git \u2014 should be in .gitignore`,evidence:t.map(n=>`${n} \u2014 add to .gitignore`)}}};var $s={id:"CFG-01",name:"TypeScript strict mode",module:"foundation",layer:"L3",priority:"P1",description:"tsconfig.json should have strict: true to catch type errors at build time",fixCost:250,fixSize:"L",async run(e){let t;try{t=await e.readFile("tsconfig.json")}catch{return{result:"UNKNOWN",message:"No tsconfig.json found \u2014 project may not use TypeScript"}}return/"strict"\s*:\s*true/.test(t)?{result:"PASS",message:"TypeScript strict mode is enabled"}:/"strict"\s*:\s*false/.test(t)?{result:"FAIL",message:"TypeScript strict mode is explicitly disabled",evidence:['tsconfig.json \u2014 set "strict": true']}:{result:"FAIL",message:"TypeScript strict mode not configured (defaults to false)",evidence:['tsconfig.json \u2014 add "strict": true to compilerOptions']}}};var As={id:"ERR-01",name:"Global error boundary exists",module:"foundation",layer:"L3",priority:"P1",description:"React apps should have a top-level error boundary to prevent full white-screen crashes",fixCost:100,fixSize:"M",async run(e){if(e.files.filter(s=>s.endsWith(".tsx")||s.endsWith(".jsx")).length===0)return{result:"UNKNOWN",message:"No React files found"};let i=/ErrorBoundary|componentDidCatch|getDerivedStateFromError|error\.tsx|error\.jsx/;for(let s of e.files){if(!s.endsWith(".ts")&&!s.endsWith(".tsx")&&!s.endsWith(".js")&&!s.endsWith(".jsx"))continue;let r;try{r=await e.readFile(s)}catch{continue}if(i.test(r))return{result:"PASS",message:`Error boundary found in ${s}`}}return{result:"FAIL",message:"No error boundary component found \u2014 unhandled errors will crash the entire app",evidence:["Add an ErrorBoundary component wrapping your app root or use Next.js error.tsx"]}}};var bs={id:"ERR-03",name:"Unhandled promise rejection handling",module:"foundation",layer:"L3",priority:"P1",description:"Application should handle unhandled promise rejections to prevent silent failures",fixCost:50,fixSize:"S",async run(e){let t=e.files.filter(r=>(r.includes("/api/")||r.includes("route.ts")||r.includes("route.js")||r.includes("actions.ts")||r.includes("actions.js"))&&(r.endsWith(".ts")||r.endsWith(".js")));if(t.length===0)return{result:"UNKNOWN",message:"No API routes or server actions found"};let i=/try\s*\{/,s=[];for(let r of t){let n;try{n=await e.readFile(r)}catch{continue}/async\s+function|async\s*\(/.test(n)&&!i.test(n)&&s.push(r)}return s.length>0?{result:"FAIL",message:`${s.length} async API route${s.length>1?"s":""} without try/catch error handling`,evidence:s.slice(0,5).map(r=>`${r} \u2014 add try/catch around async operations`)}:{result:"PASS",message:`All ${t.length} API routes have error handling`}}};var Cs={id:"SCH-01",name:"Database migrations exist",module:"schema",layer:"L4",priority:"P1",description:"Project should have migration files (Supabase, Prisma, or Drizzle) for reproducible schema changes",fixCost:100,fixSize:"M",async run(e){let t=[/supabase\/migrations\//,/prisma\/migrations\//,/drizzle\/migrations\//,/migrations\/.*\.sql$/,/schema\.prisma$/],i=e.files.filter(o=>t.some(a=>a.test(o)));if(i.length>0)return{result:"PASS",message:`${i.length} migration file${i.length>1?"s":""} found`};let s=e.files.some(o=>o.includes("supabase")||o.includes("@supabase")),r=e.files.some(o=>o.includes("prisma")),n=e.files.some(o=>o.includes("drizzle"));return s||r||n?{result:"FAIL",message:"Database ORM/client detected but no migration files found",evidence:["Create migration files to track schema changes reproducibly"]}:{result:"UNKNOWN",message:"No database usage detected"}}};var vs={id:"SCH-02",name:"No raw SQL in application code",module:"schema",layer:"L4",priority:"P2",description:"Inline SQL in route handlers/components instead of ORM/query builder is fragile and injection-prone",fixCost:250,fixSize:"L",async run(e){let t=e.files.filter(r=>!r.includes("migration")&&!r.endsWith(".sql")&&!r.includes("seed")&&!r.includes("supabase/")&&!r.includes("prisma/")&&!r.includes("drizzle/")&&(r.endsWith(".ts")||r.endsWith(".tsx")||r.endsWith(".js")||r.endsWith(".jsx"))),i=/\b(SELECT|INSERT INTO|UPDATE\s+\w+\s+SET|DELETE FROM|CREATE TABLE|ALTER TABLE|DROP TABLE)\b/,s=[];for(let r of t){let n;try{n=await e.readFile(r)}catch{continue}let o=n.split(`
11
- `);for(let a=0;a<o.length;a++){let c=o[a].trim();c.startsWith("//")||c.startsWith("*")||c.startsWith("#")||i.test(c)&&s.push(`${r}:${a+1} \u2192 ${c.substring(0,100)}`)}}return s.length>0?{result:"FAIL",message:`Raw SQL found in ${s.length} location${s.length>1?"s":""} in application code`,evidence:s.slice(0,5)}:{result:"PASS",message:"No raw SQL detected in application code"}}};var ks={id:"SCH-04",name:"Soft delete pattern consistency",module:"schema",layer:"L4",priority:"P2",description:"If soft delete is used, all delete operations should use the same pattern (deleted_at column)",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>(n.endsWith(".ts")||n.endsWith(".tsx")||n.endsWith(".js")||n.endsWith(".jsx"))&&!n.includes("node_modules")),i=!1,s=!1,r=[];for(let n of t){let o;try{o=await e.readFile(n)}catch{continue}/deleted_at|deletedAt|is_deleted|isDeleted|soft.?delete/i.test(o)&&(i=!0),/\.delete\(\)|\.remove\(\)|DELETE FROM/i.test(o)&&!o.includes("deleted_at")&&(s=!0,r.push(n))}return!i&&!s?{result:"UNKNOWN",message:"No delete operations detected"}:i&&s?{result:"FAIL",message:"Mixed soft delete and hard delete patterns \u2014 inconsistent data deletion strategy",evidence:r.slice(0,5).map(n=>`${n} \u2014 uses hard delete while project has soft delete pattern`)}:i?{result:"PASS",message:"Consistent soft delete pattern used"}:{result:"PASS",message:"Consistent hard delete pattern (no soft delete used)"}}};var _s={id:"SCH-09",name:"N+1 query pattern detection",module:"schema",layer:"L4",priority:"P2",description:"Detects fetch-list-then-fetch-each-item patterns that cause performance collapse under load",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(o=>(o.endsWith(".ts")||o.endsWith(".tsx")||o.endsWith(".js")||o.endsWith(".jsx"))&&!o.includes("node_modules")&&!o.includes("migration")),i=/\.(from|select)\(.*\)(?:(?!\.single\(\)).)*$/,s=/for\s*\(.*\)\s*\{[^}]*(?:\.from\(|\.select\(|\.rpc\(|supabase\.|fetch\()/s,r=/\.map\(\s*(?:async\s*)?\([^)]*\)\s*=>\s*(?:{[^}]*)?(?:\.from\(|\.select\(|\.rpc\(|supabase\.|fetch\()/s,n=[];for(let o of t){let a;try{a=await e.readFile(o)}catch{continue}s.test(a)?n.push(`${o} \u2014 DB/API call inside for loop`):r.test(a)&&n.push(`${o} \u2014 DB/API call inside .map()`)}return n.length>0?{result:"FAIL",message:`N+1 query pattern detected in ${n.length} file${n.length>1?"s":""}`,evidence:n.slice(0,5)}:{result:"PASS",message:"No N+1 query patterns detected"}}};var Is={id:"SCH-10",name:"Unbounded query guard",module:"schema",layer:"L4",priority:"P2",description:"Queries without LIMIT/pagination can exhaust memory under real data volumes",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>(n.endsWith(".ts")||n.endsWith(".tsx")||n.endsWith(".js")||n.endsWith(".jsx"))&&!n.includes("node_modules")&&!n.includes("migration")&&!n.includes("seed")),i=/\.from\(['"`]\w+['"`]\)\s*\.select\(/,s=/\.limit\(|\.range\(|\.single\(|\.maybeSingle\(|\.eq\(.*id|pagination|paginate|page\s*[=:]/i,r=[];for(let n of t){let o;try{o=await e.readFile(n)}catch{continue}i.test(o)&&!s.test(o)&&r.push(`${n} \u2014 .from().select() without .limit()/.range()/.single()`)}return r.length>0?{result:"FAIL",message:`Unbounded queries found in ${r.length} file${r.length>1?"s":""}`,evidence:r.slice(0,5)}:{result:"PASS",message:"All queries appear to have bounds (limit/range/single)"}}};var Ei=new Set(["ARCH-01","ARCH-02","ARCH-03","ARCH-04","ARCH-05","ARCH-06","STR-01","STR-02","AUTH-01","AUTH-02","AUTH-03","AUTH-05","AUTH-06","AUTH-11","AUTH-13","AUTH-14","BIL-01","BIL-02","BIL-03","BIL-04","BIL-09","BIL-14","BIL-16","BIL-17","ADM-01","ADM-02","ADM-08","ADM-11","ENV-01","ENV-02","CFG-01","ERR-01"]),ws=[Pe,we,Re,Le,Ne,Fe,xe,Ee,Me,Ue,je,Oe,We,Be,He,ze,qe,Ke,Ge,Ve,Ye,Xe,Je,Qe,Ze,et,tt,st,it,nt,rt,ot,at,ct,lt,ut,dt,pt,ft,mt,gt,ht,St,yt,$t,At,bt,Ct,vt,kt,_t,It,Pt,wt,Rt,Lt,Nt,Ft,xt,Et,Tt,Dt,Mt,Ut,jt,Ot,Wt,Bt,Ht,zt,qt,Kt,Gt,Vt,Yt,Xt,Jt,Qt,Zt,es,ts,ss,is,ns,rs,os,as,cs,ls,us,ds,ps,fs,hs,Ss,ys,$s,As,bs,Cs,vs,ks,_s,Is],Ps={"AUTH-01":"supabase","AUTH-02":"supabase","AUTH-03":"supabase","AUTH-10":"supabase","AUTH-11":"supabase","AUTH-13":"supabase","AUTH-17":"supabase","AUTH-18":"supabase","AUTH-27":"supabase","AUTH-28":"supabase","BIL-01":"stripe","BIL-02":"stripe","BIL-03":"stripe","BIL-05":"stripe","BIL-12":"stripe","BIL-15":"stripe","BIL-19":"stripe","BIL-20":"stripe","BIL-22":"stripe"};for(let e of ws)e.phase=Ei.has(e.id)?1:2,Ps[e.id]&&(e.vendor=Ps[e.id]);var Rs=ws;import Ns from"fs/promises";import Q from"path";var Ti=new Set(["node_modules",".git",".next","dist","build",".vercel",".turbo","coverage",".nyc_output","__pycache__",".svelte-kit"]),Ls=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".sql",".env",".env.local",".env.example"]);async function Fs(e){let t=[];return await xs(e,e,t),t}async function xs(e,t,i){let s;try{s=await Ns.readdir(e,{withFileTypes:!0})}catch{return}for(let r of s)if(!(r.name.startsWith(".")&&r.name!==".env"&&r.name!==".env.local"&&r.name!==".env.example"&&r.isDirectory()))if(r.isDirectory()){if(Ti.has(r.name))continue;await xs(Q.join(e,r.name),t,i)}else{let n=Q.extname(r.name),o=r.name;(Ls.has(n)||Ls.has(o))&&i.push(Q.relative(t,Q.join(e,r.name)))}}async function ne(e,t){let i=Q.resolve(e,t);return Ns.readFile(i,"utf-8")}import Di from"fs/promises";import Mi from"path";async function Es(e,t,i){let s={hasStripe:!1,hasSupabase:!1,detectedVendors:[]},r=Mi.join(e,"package.json");try{let n=await Di.readFile(r,"utf-8"),o=JSON.parse(n),a={...o.dependencies,...o.devDependencies};(a.stripe||a["@stripe/stripe-js"]||a["@stripe/react-stripe-js"])&&(s.hasStripe=!0),(a["@supabase/supabase-js"]||a["@supabase/ssr"]||a["@supabase/auth-helpers-nextjs"])&&(s.hasSupabase=!0)}catch{}if((!s.hasStripe||!s.hasSupabase)&&t&&i){let n=/from\s+["']stripe["']|new\s+Stripe\s*\(|stripe[_-]webhook|STRIPE_SECRET|loadStripe\s*\(/,o=/from\s+["']@supabase|createClient\s*\(|SUPABASE_URL|supabase\.auth\.|supabase\.from\s*\(/,a=t.filter(f=>f.endsWith(".ts")||f.endsWith(".tsx")||f.endsWith(".js")||f.endsWith(".jsx")),c=a.filter(f=>/stripe|billing|payment|webhook|checkout|supabase|auth/i.test(f)),l=a.filter(f=>!/stripe|billing|payment|webhook|checkout|supabase|auth/i.test(f)),d=[...c,...l.slice(0,30)];for(let f of d){if(s.hasStripe&&s.hasSupabase)break;try{let m=await i(f);!s.hasStripe&&n.test(m)&&(s.hasStripe=!0),!s.hasSupabase&&o.test(m)&&(s.hasSupabase=!0)}catch{continue}}}return s.hasStripe&&s.detectedVendors.push("stripe"),s.hasSupabase&&s.detectedVendors.push("supabase"),s}var Ui={L1:"Architecture",L2:"Safety",L3:"Foundation",L4:"Schema"},ji={P0:5,P1:3,P2:2,P3:1};function Oi(e){let i=e.filter(o=>o.layer==="L2"||o.layer==="L3"||o.layer==="L4").filter(o=>o.result==="FAIL"),s=0;for(let o of i)s+=ji[o.priority]??1;let r=Math.max(0,Math.min(100,100-s)),n;return r>=90?n="A":r>=75?n="B":r>=60?n="C":r>=40?n="D":n="F",{grade:n,score:r}}var Wi={"ARCH-01":{points:20,rc:"RC01",rcName:"Architecture Drift"},"ARCH-04":{points:10,rc:"RC01",rcName:"Architecture Drift"},"ARCH-06":{points:10,rc:"RC01",rcName:"Architecture Drift"},"ARCH-03":{points:20,rc:"RC02",rcName:"Dependency Corruption"},"ARCH-02":{points:10,rc:"RC03",rcName:"Structural Entropy"},"ARCH-05":{points:10,rc:"RC03",rcName:"Structural Entropy"},"STR-02":{points:10,rc:"RC04",rcName:"Test Infrastructure"},"STR-01":{points:10,rc:"RC05",rcName:"Deployment Safety"}},me=["RC01","RC02","RC03","RC04","RC05"],Bi={RC01:"Architecture Drift",RC02:"Dependency Corruption",RC03:"Structural Entropy",RC04:"Test Infrastructure",RC05:"Deployment Safety"},ge={RC01:40,RC02:20,RC03:20,RC04:10,RC05:10};function Hi(e){let t={};for(let a of me)t[a]=0;for(let a of e){if(a.result!=="FAIL")continue;let c=Wi[a.id];c&&(t[c.rc]+=c.points)}let i=Object.values(t).reduce((a,c)=>a+c,0),s;i<=20?s="Minimal":i<=40?s="Low":i<=60?s="Moderate":i<=80?s="High":s="Critical";let r=me.map(a=>({code:a,name:Bi[a],score:t[a],maxScore:ge[a]})),n="RC01",o=0;for(let a of me){let c=ge[a]>0?t[a]/ge[a]:0;c>o&&(o=c,n=a)}return{score:i,band:s,breakdown:r,primaryVector:n}}var re=class{evaluate(t,i,s){let r=this.computeLayerScores(t),n={project:s,timestamp:new Date().toISOString(),mode:i,checks:t,layers:r};return(i==="trust-score"||i==="all")&&(n.trustScore=Oi(t)),(i==="architecture"||i==="all")&&(n.chaosIndex=Hi(t)),n}computeLayerScores(t){let i={},s=new Set;for(let r of t)s.add(r.layer);for(let r of s){let o=t.filter(f=>f.layer===r).filter(f=>f.result!=="N/A"&&f.result!=="UNKNOWN"&&f.result!=="NOT_APPLICABLE"),a=o.filter(f=>f.result==="PASS").length,c=o.filter(f=>f.result==="FAIL").length,l=o.length,d=l>0?Math.round(a/l*10):0;i[r]={name:Ui[r]??r,passed:a,failed:c,total:l,score:d}}return i}};var zi={"trust-score":["L2","L3","L4"],architecture:["L1"],all:["L1","L2","L3","L4"]};async function Ds(e,t={}){let{mode:i="trust-score",engine:s=new re,allChecks:r=!1,disabledChecks:n}=t,o=Ts.resolve(e),a=await Fs(o),c=zi[i],d=await Es(o,a,$=>ne(o,$)),f={rootDir:o,files:a,fingerprint:d,readFile:$=>ne(o,$),grepFiles:($,F)=>Te({rootDir:o,files:a,readFile:ie=>ne(o,ie)},$,F)},m=n??qi(),p=Rs.filter($=>c.includes($.layer)&&(r||$.phase===1)),y=[];for(let $ of p)if(!m.has($.id)){if(Ki($,d)){y.push({id:$.id,name:$.name,module:$.module,layer:$.layer,priority:$.priority,category:$.category,result:"NOT_APPLICABLE",message:Gi($)});continue}try{let F=await $.run(f);y.push({id:$.id,name:$.name,module:$.module,layer:$.layer,priority:$.priority,category:$.category,result:F.result,message:F.message,evidence:F.evidence,shadow:$.shadow})}catch(F){y.push({id:$.id,name:$.name,module:$.module,layer:$.layer,priority:$.priority,category:$.category,result:"UNKNOWN",message:`Check failed with error: ${F instanceof Error?F.message:String(F)}`,shadow:$.shadow})}}let M=Ts.basename(o);return s.evaluate(y,i,M)}function qi(){let e=process.env.VIBECODIQ_DISABLED_CHECKS||"";return e.trim()?new Set(e.split(",").map(t=>t.trim().toUpperCase()).filter(Boolean)):new Set}function Ki(e,t){return e.vendor==="stripe"&&!t.hasStripe||e.vendor==="supabase"&&!t.hasSupabase}function Gi(e){return e.vendor==="stripe"?"No Stripe integration detected \u2014 check not applicable":e.vendor==="supabase"?"No Supabase integration detected \u2014 check not applicable":"Prerequisite not detected \u2014 check not applicable"}var u="\x1B[0m",g="\x1B[1m",h="\x1B[2m",k="\x1B[31m",_="\x1B[32m",L="\x1B[33m";var he="\x1B[37m",Vi="\x1B[41m",Yi="\x1B[43m",Ms="\x1B[35m";function js(e,t=10){let i=t>0?Math.round(e/t*10):0;return`${i>=7?_:i>=4?L:k}${"\u2588".repeat(i)}${h}${"\u2591".repeat(10-i)}${u}`}function V(e){return e==="A"||e==="B"?_:e==="C"?L:k}function H(e){switch(e){case"PASS":return`${_}\u2713${u}`;case"FAIL":return`${k}\u2717${u}`;case"REVIEW":return`${L}\u26A0${u}`;case"UNKNOWN":return`${L}?${u}`;default:return`${h}\u25CB${u}`}}function Os(e){switch(e){case"P0":return`${Vi}${he}${g} P0 ${u}`;case"P1":return`${Yi}${he}${g} P1 ${u}`;default:return`${h} ${e} ${u}`}}var Us={auth:"Auth",billing:"Billing",admin:"Admin",architecture:"Architecture",foundation:"Foundation",schema:"Schema"};function Ws(e){let t=new Map;for(let r of e){let n=r.module||"other";t.has(n)||t.set(n,[]),t.get(n).push(r)}let i=["auth","billing","admin","architecture","foundation","schema"],s=[];for(let r of i)t.has(r)&&(s.push([Us[r]||r,t.get(r)]),t.delete(r));for(let[r,n]of t)s.push([Us[r]||r,n]);return s}function Xi(){let e=t=>process.stdout.write(t+`
11
+ `);for(let a=0;a<o.length;a++){let c=o[a].trim();c.startsWith("//")||c.startsWith("*")||c.startsWith("#")||i.test(c)&&s.push(`${r}:${a+1} \u2192 ${c.substring(0,100)}`)}}return s.length>0?{result:"FAIL",message:`Raw SQL found in ${s.length} location${s.length>1?"s":""} in application code`,evidence:s.slice(0,5)}:{result:"PASS",message:"No raw SQL detected in application code"}}};var ks={id:"SCH-04",name:"Soft delete pattern consistency",module:"schema",layer:"L4",priority:"P2",description:"If soft delete is used, all delete operations should use the same pattern (deleted_at column)",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>(n.endsWith(".ts")||n.endsWith(".tsx")||n.endsWith(".js")||n.endsWith(".jsx"))&&!n.includes("node_modules")),i=!1,s=!1,r=[];for(let n of t){let o;try{o=await e.readFile(n)}catch{continue}/deleted_at|deletedAt|is_deleted|isDeleted|soft.?delete/i.test(o)&&(i=!0),/\.delete\(\)|\.remove\(\)|DELETE FROM/i.test(o)&&!o.includes("deleted_at")&&(s=!0,r.push(n))}return!i&&!s?{result:"UNKNOWN",message:"No delete operations detected"}:i&&s?{result:"FAIL",message:"Mixed soft delete and hard delete patterns \u2014 inconsistent data deletion strategy",evidence:r.slice(0,5).map(n=>`${n} \u2014 uses hard delete while project has soft delete pattern`)}:i?{result:"PASS",message:"Consistent soft delete pattern used"}:{result:"PASS",message:"Consistent hard delete pattern (no soft delete used)"}}};var _s={id:"SCH-09",name:"N+1 query pattern detection",module:"schema",layer:"L4",priority:"P2",description:"Detects fetch-list-then-fetch-each-item patterns that cause performance collapse under load",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(o=>(o.endsWith(".ts")||o.endsWith(".tsx")||o.endsWith(".js")||o.endsWith(".jsx"))&&!o.includes("node_modules")&&!o.includes("migration")),i=/\.(from|select)\(.*\)(?:(?!\.single\(\)).)*$/,s=/for\s*\(.*\)\s*\{[^}]*(?:\.from\(|\.select\(|\.rpc\(|supabase\.|fetch\()/s,r=/\.map\(\s*(?:async\s*)?\([^)]*\)\s*=>\s*(?:{[^}]*)?(?:\.from\(|\.select\(|\.rpc\(|supabase\.|fetch\()/s,n=[];for(let o of t){let a;try{a=await e.readFile(o)}catch{continue}s.test(a)?n.push(`${o} \u2014 DB/API call inside for loop`):r.test(a)&&n.push(`${o} \u2014 DB/API call inside .map()`)}return n.length>0?{result:"FAIL",message:`N+1 query pattern detected in ${n.length} file${n.length>1?"s":""}`,evidence:n.slice(0,5)}:{result:"PASS",message:"No N+1 query patterns detected"}}};var Is={id:"SCH-10",name:"Unbounded query guard",module:"schema",layer:"L4",priority:"P2",description:"Queries without LIMIT/pagination can exhaust memory under real data volumes",fixCost:100,fixSize:"M",async run(e){let t=e.files.filter(n=>(n.endsWith(".ts")||n.endsWith(".tsx")||n.endsWith(".js")||n.endsWith(".jsx"))&&!n.includes("node_modules")&&!n.includes("migration")&&!n.includes("seed")),i=/\.from\(['"`]\w+['"`]\)\s*\.select\(/,s=/\.limit\(|\.range\(|\.single\(|\.maybeSingle\(|\.eq\(.*id|pagination|paginate|page\s*[=:]/i,r=[];for(let n of t){let o;try{o=await e.readFile(n)}catch{continue}i.test(o)&&!s.test(o)&&r.push(`${n} \u2014 .from().select() without .limit()/.range()/.single()`)}return r.length>0?{result:"FAIL",message:`Unbounded queries found in ${r.length} file${r.length>1?"s":""}`,evidence:r.slice(0,5)}:{result:"PASS",message:"All queries appear to have bounds (limit/range/single)"}}};var Ei=new Set(["ARCH-01","ARCH-02","ARCH-03","ARCH-04","ARCH-05","ARCH-06","STR-01","STR-02","AUTH-01","AUTH-02","AUTH-03","AUTH-05","AUTH-06","AUTH-11","AUTH-13","AUTH-14","BIL-01","BIL-02","BIL-03","BIL-04","BIL-09","BIL-14","BIL-16","BIL-17","ADM-01","ADM-02","ADM-08","ADM-11","ENV-01","ENV-02","CFG-01","ERR-01"]),ws=[Pe,we,Re,Le,Ne,Fe,xe,Ee,Me,Ue,je,Oe,We,Be,He,ze,qe,Ke,Ge,Ve,Ye,Xe,Je,Qe,Ze,et,tt,st,it,nt,rt,ot,at,ct,lt,ut,dt,pt,ft,mt,gt,ht,yt,St,$t,At,bt,Ct,vt,kt,_t,It,Pt,wt,Rt,Lt,Nt,Ft,xt,Et,Tt,Dt,Mt,Ut,jt,Ot,Wt,Bt,Ht,zt,qt,Kt,Gt,Vt,Yt,Xt,Jt,Qt,Zt,es,ts,ss,is,ns,rs,os,as,cs,ls,us,ds,ps,fs,hs,ys,Ss,$s,As,bs,Cs,vs,ks,_s,Is],Ps={"AUTH-01":"supabase","AUTH-02":"supabase","AUTH-03":"supabase","AUTH-10":"supabase","AUTH-11":"supabase","AUTH-13":"supabase","AUTH-17":"supabase","AUTH-18":"supabase","AUTH-27":"supabase","AUTH-28":"supabase","BIL-01":"stripe","BIL-02":"stripe","BIL-03":"stripe","BIL-05":"stripe","BIL-12":"stripe","BIL-15":"stripe","BIL-19":"stripe","BIL-20":"stripe","BIL-22":"stripe"};for(let e of ws)e.phase=Ei.has(e.id)?1:2,Ps[e.id]&&(e.vendor=Ps[e.id]);var Rs=ws;import Ns from"fs/promises";import Q from"path";var Ti=new Set(["node_modules",".git",".next","dist","build",".vercel",".turbo","coverage",".nyc_output","__pycache__",".svelte-kit"]),Ls=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".sql",".env",".env.local",".env.example"]);async function Fs(e){let t=[];return await xs(e,e,t),t}async function xs(e,t,i){let s;try{s=await Ns.readdir(e,{withFileTypes:!0})}catch{return}for(let r of s)if(!(r.name.startsWith(".")&&r.name!==".env"&&r.name!==".env.local"&&r.name!==".env.example"&&r.isDirectory()))if(r.isDirectory()){if(Ti.has(r.name))continue;await xs(Q.join(e,r.name),t,i)}else{let n=Q.extname(r.name),o=r.name;(Ls.has(n)||Ls.has(o))&&i.push(Q.relative(t,Q.join(e,r.name)))}}async function ne(e,t){let i=Q.resolve(e,t);return Ns.readFile(i,"utf-8")}import Di from"fs/promises";import Mi from"path";async function Es(e,t,i){let s={hasStripe:!1,hasSupabase:!1,detectedVendors:[]},r=Mi.join(e,"package.json");try{let n=await Di.readFile(r,"utf-8"),o=JSON.parse(n),a={...o.dependencies,...o.devDependencies};(a.stripe||a["@stripe/stripe-js"]||a["@stripe/react-stripe-js"])&&(s.hasStripe=!0),(a["@supabase/supabase-js"]||a["@supabase/ssr"]||a["@supabase/auth-helpers-nextjs"])&&(s.hasSupabase=!0)}catch{}if((!s.hasStripe||!s.hasSupabase)&&t&&i){let n=/from\s+["']stripe["']|new\s+Stripe\s*\(|stripe[_-]webhook|STRIPE_SECRET|loadStripe\s*\(/,o=/from\s+["']@supabase|createClient\s*\(|SUPABASE_URL|supabase\.auth\.|supabase\.from\s*\(/,a=t.filter(f=>f.endsWith(".ts")||f.endsWith(".tsx")||f.endsWith(".js")||f.endsWith(".jsx")),c=a.filter(f=>/stripe|billing|payment|webhook|checkout|supabase|auth/i.test(f)),l=a.filter(f=>!/stripe|billing|payment|webhook|checkout|supabase|auth/i.test(f)),d=[...c,...l.slice(0,30)];for(let f of d){if(s.hasStripe&&s.hasSupabase)break;try{let m=await i(f);!s.hasStripe&&n.test(m)&&(s.hasStripe=!0),!s.hasSupabase&&o.test(m)&&(s.hasSupabase=!0)}catch{continue}}}return s.hasStripe&&s.detectedVendors.push("stripe"),s.hasSupabase&&s.detectedVendors.push("supabase"),s}var Ui={L1:"Architecture",L2:"Safety",L3:"Foundation",L4:"Schema"},ji={P0:5,P1:3,P2:2,P3:1};function Oi(e){let i=e.filter(c=>c.layer==="L2"||c.layer==="L3"||c.layer==="L4").filter(c=>c.result==="FAIL"),s=0;for(let c of i)s+=ji[c.priority]??1;let r=Math.max(0,Math.min(100,100-s)),n=i.some(c=>c.priority==="P0"),o=i.filter(c=>c.priority==="P0").length,a;return r>=90?a="A":r>=80?a="B":r>=70?a="C":r>=55?a="D":a="F",n&&a==="A"&&(a="B"),o>=3&&(a==="A"||a==="B")&&(a="C"),{grade:a,score:r}}var Wi={"ARCH-01":{points:20,rc:"RC01",rcName:"Architecture Drift"},"ARCH-04":{points:10,rc:"RC01",rcName:"Architecture Drift"},"ARCH-06":{points:10,rc:"RC01",rcName:"Architecture Drift"},"ARCH-03":{points:20,rc:"RC02",rcName:"Dependency Corruption"},"ARCH-02":{points:10,rc:"RC03",rcName:"Structural Entropy"},"ARCH-05":{points:10,rc:"RC03",rcName:"Structural Entropy"},"STR-02":{points:10,rc:"RC04",rcName:"Test Infrastructure"},"STR-01":{points:10,rc:"RC05",rcName:"Deployment Safety"}},me=["RC01","RC02","RC03","RC04","RC05"],Bi={RC01:"Architecture Drift",RC02:"Dependency Corruption",RC03:"Structural Entropy",RC04:"Test Infrastructure",RC05:"Deployment Safety"},ge={RC01:40,RC02:20,RC03:20,RC04:10,RC05:10};function Hi(e){let t={};for(let a of me)t[a]=0;for(let a of e){if(a.result!=="FAIL")continue;let c=Wi[a.id];c&&(t[c.rc]+=c.points)}let i=Object.values(t).reduce((a,c)=>a+c,0),s;i<=20?s="Minimal":i<=40?s="Low":i<=60?s="Moderate":i<=80?s="High":s="Critical";let r=me.map(a=>({code:a,name:Bi[a],score:t[a],maxScore:ge[a]})),n="RC01",o=0;for(let a of me){let c=ge[a]>0?t[a]/ge[a]:0;c>o&&(o=c,n=a)}return{score:i,band:s,breakdown:r,primaryVector:n}}var re=class{evaluate(t,i,s){let r=this.computeLayerScores(t),n={project:s,timestamp:new Date().toISOString(),mode:i,checks:t,layers:r};return(i==="trust-score"||i==="all")&&(n.trustScore=Oi(t)),(i==="architecture"||i==="all")&&(n.chaosIndex=Hi(t)),n}computeLayerScores(t){let i={},s=new Set;for(let r of t)s.add(r.layer);for(let r of s){let o=t.filter(f=>f.layer===r).filter(f=>f.result!=="N/A"&&f.result!=="UNKNOWN"&&f.result!=="NOT_APPLICABLE"),a=o.filter(f=>f.result==="PASS").length,c=o.filter(f=>f.result==="FAIL").length,l=o.length,d=l>0?Math.round(a/l*10):0;i[r]={name:Ui[r]??r,passed:a,failed:c,total:l,score:d}}return i}};var zi={"trust-score":["L2","L3","L4"],architecture:["L1"],all:["L1","L2","L3","L4"]};async function Ds(e,t={}){let{mode:i="trust-score",engine:s=new re,allChecks:r=!1,disabledChecks:n}=t,o=Ts.resolve(e),a=await Fs(o),c=zi[i],d=await Es(o,a,$=>ne(o,$)),f={rootDir:o,files:a,fingerprint:d,readFile:$=>ne(o,$),grepFiles:($,F)=>Te({rootDir:o,files:a,readFile:ie=>ne(o,ie)},$,F)},m=n??qi(),p=Rs.filter($=>c.includes($.layer)&&(r||$.phase===1)),S=[];for(let $ of p)if(!m.has($.id)){if(Ki($,d)){S.push({id:$.id,name:$.name,module:$.module,layer:$.layer,priority:$.priority,category:$.category,result:"NOT_APPLICABLE",message:Gi($)});continue}try{let F=await $.run(f);S.push({id:$.id,name:$.name,module:$.module,layer:$.layer,priority:$.priority,category:$.category,result:F.result,message:F.message,evidence:F.evidence,shadow:$.shadow})}catch(F){S.push({id:$.id,name:$.name,module:$.module,layer:$.layer,priority:$.priority,category:$.category,result:"UNKNOWN",message:`Check failed with error: ${F instanceof Error?F.message:String(F)}`,shadow:$.shadow})}}let M=Ts.basename(o);return s.evaluate(S,i,M)}function qi(){let e=process.env.VIBECODIQ_DISABLED_CHECKS||"";return e.trim()?new Set(e.split(",").map(t=>t.trim().toUpperCase()).filter(Boolean)):new Set}function Ki(e,t){return e.vendor==="stripe"&&!t.hasStripe||e.vendor==="supabase"&&!t.hasSupabase}function Gi(e){return e.vendor==="stripe"?"No Stripe integration detected \u2014 check not applicable":e.vendor==="supabase"?"No Supabase integration detected \u2014 check not applicable":"Prerequisite not detected \u2014 check not applicable"}var u="\x1B[0m",g="\x1B[1m",h="\x1B[2m",k="\x1B[31m",_="\x1B[32m",L="\x1B[33m";var he="\x1B[37m",Vi="\x1B[41m",Yi="\x1B[43m",Ms="\x1B[35m";function js(e,t=10){let i=t>0?Math.round(e/t*10):0;return`${i>=7?_:i>=4?L:k}${"\u2588".repeat(i)}${h}${"\u2591".repeat(10-i)}${u}`}function V(e){return e==="A"||e==="B"?_:e==="C"?L:k}function H(e){switch(e){case"PASS":return`${_}\u2713${u}`;case"FAIL":return`${k}\u2717${u}`;case"REVIEW":return`${L}\u26A0${u}`;case"UNKNOWN":return`${L}?${u}`;default:return`${h}\u25CB${u}`}}function Os(e){switch(e){case"P0":return`${Vi}${he}${g} P0 ${u}`;case"P1":return`${Yi}${he}${g} P1 ${u}`;default:return`${h} ${e} ${u}`}}var Us={auth:"Auth",billing:"Billing",admin:"Admin",architecture:"Architecture",foundation:"Foundation",schema:"Schema"};function Ws(e){let t=new Map;for(let r of e){let n=r.module||"other";t.has(n)||t.set(n,[]),t.get(n).push(r)}let i=["auth","billing","admin","architecture","foundation","schema"],s=[];for(let r of i)t.has(r)&&(s.push([Us[r]||r,t.get(r)]),t.delete(r));for(let[r,n]of t)s.push([Us[r]||r,n]);return s}function Xi(){let e=t=>process.stdout.write(t+`
12
12
  `);e(""),e(` ${h}Static source-code analysis. Not a full security audit.${u}`)}function Bs(){let e=t=>process.stdout.write(t+`
13
13
  `);e(""),e(`${h}${"\u2500".repeat(62)}${u}`),e(` ${h}Static source-code analysis focused on high-confidence patterns.${u}`),e(` ${h}Not a complete security audit. Some findings may need manual review.${u}`),e(` ${h}https://vibecodiq.com | https://asastandard.org${u}`),e("")}function Hs(e){let t=l=>process.stdout.write(l+`
14
- `),i=e.checks.filter(l=>!l.shadow),s=i.filter(l=>l.result==="FAIL"),r=i.filter(l=>l.result==="PASS"),n=i.filter(l=>l.result==="UNKNOWN"),o=s.filter(l=>l.priority==="P0"),a=s.filter(l=>l.priority==="P1");if(t(""),t(` ${g}Safety Scan${u} ${h}\u2014 ${e.project}${u}`),e.trustScore){let l=e.trustScore;t(""),t(` ${g}Trust Score:${u} ${V(l.grade)}${g}${l.score} / 100${u} (Grade ${V(l.grade)}${g}${l.grade}${u})`),o.length>0?t(` ${g}Verdict:${u} ${k}${g}NOT READY FOR PRODUCTION${u}`):a.length>0?t(` ${g}Verdict:${u} ${L}${g}NEEDS ATTENTION${u}`):s.length>0?t(` ${g}Verdict:${u} ${h}MINOR ISSUES${u}`):t(` ${g}Verdict:${u} ${_}${g}READY FOR PRODUCTION${u}`)}t("");let c=Ws(i);for(let[l,d]of c){let f=d.filter(M=>M.result==="PASS").length,m=d.filter(M=>M.result==="FAIL").length,p=d.length,y=m>0?` ${k}${m} fail${u}`:"";t(` ${g}${l.padEnd(12)}${u} ${js(f,p)} ${f}/${p} pass${y}`)}if(s.length>0){t("");let l=[];o.length>0&&l.push(`${k}${o.length} critical${u}`),a.length>0&&l.push(`${L}${a.length} important${u}`),t(` ${s.length} issues found (${l.join(", ")})`)}n.length>0&&t(` ${h}${n.length} checks returned unknown \u2014 manual review recommended${u}`),Xi()}function zs(e){let t=o=>process.stdout.write(o+`
14
+ `),i=e.checks.filter(l=>!l.shadow),s=i.filter(l=>l.result==="FAIL"),r=i.filter(l=>l.result==="PASS"),n=i.filter(l=>l.result==="UNKNOWN"),o=s.filter(l=>l.priority==="P0"),a=s.filter(l=>l.priority==="P1");if(t(""),t(` ${g}Safety Scan${u} ${h}\u2014 ${e.project}${u}`),e.trustScore){let l=e.trustScore;t(""),t(` ${g}Trust Score:${u} ${V(l.grade)}${g}${l.score} / 100${u} (Grade ${V(l.grade)}${g}${l.grade}${u})`),o.length>0?t(` ${g}Verdict:${u} ${k}${g}NOT READY FOR PRODUCTION${u}`):a.length>0?t(` ${g}Verdict:${u} ${L}${g}NEEDS ATTENTION${u}`):s.length>0?t(` ${g}Verdict:${u} ${h}MINOR ISSUES${u}`):t(` ${g}Verdict:${u} ${_}${g}READY FOR PRODUCTION${u}`)}t("");let c=Ws(i);for(let[l,d]of c){let f=d.filter(M=>M.result==="PASS").length,m=d.filter(M=>M.result==="FAIL").length,p=d.length,S=m>0?` ${k}${m} fail${u}`:"";t(` ${g}${l.padEnd(12)}${u} ${js(f,p)} ${f}/${p} pass${S}`)}if(s.length>0){t("");let l=[];o.length>0&&l.push(`${k}${o.length} critical${u}`),a.length>0&&l.push(`${L}${a.length} important${u}`),t(` ${s.length} issues found (${l.join(", ")})`)}n.length>0&&t(` ${h}${n.length} checks returned unknown \u2014 manual review recommended${u}`),Xi()}function zs(e){let t=o=>process.stdout.write(o+`
15
15
  `),s=e.checks.filter(o=>!o.shadow).filter(o=>o.result==="FAIL"),r=s.filter(o=>o.priority==="P0"),n=s.filter(o=>o.priority==="P1");if(t(""),t(` ${g}Architecture Scan${u} ${h}\u2014 ${e.project}${u}`),e.chaosIndex){let o=e.chaosIndex,a=o.score<=20||o.score<=40?_:o.score<=60?L:k;t(""),t(` ${g}AI Chaos Index:${u} ${a}${g}${o.score} / 100${u}`),t(` ${g}Risk Band:${u} ${a}${g}${o.band}${u}`),t("");for(let c of o.breakdown){let l=`${c.code} ${c.name}`,d=c.maxScore>0?Math.round(c.score/c.maxScore*10):0,m=`${d<=3?_:d<=6?L:k}${"\u2588".repeat(d)}${h}${"\u2591".repeat(10-d)}${u}`;t(` ${l.padEnd(30)} ${m} ${c.score}/${c.maxScore}`)}if(s.length>0){let c=o.breakdown.find(l=>l.code===o.primaryVector)?.name??"";t(""),t(` ${g}Primary Risk Vector:${u} ${o.primaryVector} \u2014 ${c}`)}}if(s.length>0){t("");let o=[];r.length>0&&o.push(`${k}${r.length} critical${u}`),n.length>0&&o.push(`${L}${n.length} important${u}`),t(` ${s.length} structural issues found (${o.join(", ")})`)}else t(""),t(` ${_}${g}Clean architecture \u2014 no structural issues found.${u}`);t(""),t(` ${h}Static source-code analysis. Higher score = more chaos.${u}`)}function Ji(e){Hs(e);let t=i=>process.stdout.write(i+`
16
16
  `);t(""),t(`${h}${"\u2500".repeat(62)}${u}`),zs(e)}function qs(e){e.mode==="architecture"?zs(e):e.mode==="all"?Ji(e):Hs(e)}function oe(e){let t=p=>process.stdout.write(p+`
17
- `),i=e.checks.filter(p=>!p.shadow),s=i.filter(p=>p.result==="FAIL"),r=i.filter(p=>p.result==="PASS"),n=i.filter(p=>p.result==="UNKNOWN"),o=i.filter(p=>p.result==="NOT_APPLICABLE"),a=i.filter(p=>p.result==="REVIEW"),c=s.filter(p=>p.priority==="P0"),l=s.filter(p=>p.priority==="P1"),d=s.filter(p=>p.priority!=="P0"&&p.priority!=="P1");if(t(""),e.mode==="architecture"&&e.chaosIndex){let p=e.chaosIndex,y=p.score<=40?_:p.score<=60?L:k;t(` ${g}Architecture Scan${u} ${h}\u2014 ${e.project}${u}`),t(""),t(` ${g}AI Chaos Index:${u} ${y}${g}${p.score} / 100${u} ${g}Risk Band:${u} ${y}${g}${p.band}${u}`)}else if(t(` ${g}Safety Scan${u} ${h}\u2014 ${e.project}${u}`),e.trustScore){let p=e.trustScore;t(""),t(` ${g}Trust Score:${u} ${V(p.grade)}${g}${p.score} / 100${u} (Grade ${V(p.grade)}${g}${p.grade}${u})`)}t(""),t(`${h}${"\u2500".repeat(62)}${u}`),t("");for(let p of["L1","L2","L3","L4"]){let y=e.layers[p];if(!y)continue;let M=`${p} ${y.name}`,$=y.score,F=10-$,fe=`${$>=7?_:$>=4?L:k}${"\u2588".repeat($)}${h}${"\u2591".repeat(F)}${u} ${$}/10`;t(` ${g}${M.padEnd(22)}${u} ${fe} ${y.passed}/${y.total} pass ${y.failed>0?`${k}${y.failed} fail${u}`:`${_}0 fail${u}`}`)}if(c.length>0){t(""),t(`${h}${"\u2500".repeat(62)}${u}`),t(""),t(` ${k}${g}CRITICAL (${c.length})${u}`);for(let p of c)if(t(""),t(` ${H(p.result)} ${Os(p.priority)} ${g}${p.id}${u} ${p.name}`),t(` ${h}${p.message}${u}`),p.evidence){for(let y of p.evidence.slice(0,3))t(` ${h}\u2192 ${y}${u}`);p.evidence.length>3&&t(` ${h} ... and ${p.evidence.length-3} more${u}`)}}if(l.length>0){t(""),t(`${h}${"\u2500".repeat(62)}${u}`),t(""),t(` ${L}${g}IMPORTANT (${l.length})${u}`),t("");for(let p of l)t(` ${H(p.result)} ${g}${p.id}${u} ${p.name}`)}if(d.length>0){t(""),t(`${h}${"\u2500".repeat(62)}${u}`),t(""),t(` ${h}${g}QUALITY (${d.length})${u}`),t("");for(let p of d)t(` ${H(p.result)} ${h}${p.id.padEnd(16)}${p.name}${u}`)}if(r.length>0){t(""),t(`${h}${"\u2500".repeat(62)}${u}`),t(""),t(`${_}${g} PASSED (${r.length})${u}`),t("");for(let p of r)t(` ${H(p.result)} ${h}${p.id.padEnd(16)}${u}${p.name}`)}if(n.length>0){t(""),t(`${h}${"\u2500".repeat(62)}${u}`),t(""),t(`${L}${g} UNKNOWN (${n.length})${u} ${h}\u2014 manual review recommended${u}`),t("");for(let p of n)t(` ${H(p.result)} ${h}${p.id.padEnd(16)}${p.name}${u}`)}if(o.length>0){t(""),t(`${h}${"\u2500".repeat(62)}${u}`),t(""),t(`${h}${g} NOT APPLICABLE (${o.length})${u}`),t("");for(let p of o)t(` ${H(p.result)} ${h}${p.id.padEnd(16)}${p.name}${u}`)}t(""),t(`${h}${"\u2500".repeat(62)}${u}`),t("");let f=i.length,m=[`${_}${r.length} pass${u}`,s.length>0?`${k}${s.length} fail${u}`:`${_}0 fail${u}`];n.length>0&&m.push(`${n.length} unknown`),o.length>0&&m.push(`${h}${o.length} n/a${u}`),t(` ${g}Total:${u} ${f} checks \u2014 ${m.join(", ")}`),Bs()}function ae(e){console.log(JSON.stringify(e,null,2))}function Ks(e,t){let i=c=>process.stdout.write(c+`
18
- `),s=e.checks.filter(c=>!c.shadow),r=s.filter(c=>c.result==="FAIL"),n=s.filter(c=>c.result==="PASS"),o=s.filter(c=>c.result==="UNKNOWN");i(""),i(` ${g}Safety Scan${u} ${h}\u2014 ${e.project}${u}`),i(""),i(` ${g}Trust Score:${u} ${V(t.grade)}${g}${t.trust_score} / 100${u} (Grade ${V(t.grade)}${g}${t.grade}${u})`),i(` ${g}Verdict:${u} ${t.fix_vs_rebuild==="FIX"?`${_}FIX${u} \u2014 worth fixing`:`${k}REBUILD${u} \u2014 consider rebuilding`}`),i("");let a=Ws(s);for(let[c,l]of a){let d=l.filter(y=>y.result==="PASS").length,f=l.filter(y=>y.result==="FAIL").length,m=l.length,p=f>0?` ${k}${f} fail${u}`:"";i(` ${g}${c.padEnd(12)}${u} ${js(d,m)} ${d}/${m} pass${p}`)}if(r.length>0){i(""),i(`${h}${"\u2500".repeat(62)}${u}`),i(""),i(` ${k}${g}FAILED (${r.length})${u}`);for(let c of r)if(i(""),i(` ${H(c.result)} ${Os(c.priority)} ${g}${c.id}${u} ${c.name}`),i(` ${h}${c.message}${u}`),c.evidence){for(let l of c.evidence.slice(0,3))i(` ${h}\u2192 ${l}${u}`);c.evidence.length>3&&i(` ${h} ... and ${c.evidence.length-3} more${u}`)}}if(t.fix_batches&&t.fix_batches.length>0){i(""),i(`${h}${"\u2500".repeat(62)}${u}`),i(""),i(` ${Ms}${g}FIX BATCHES (${t.fix_batches.length})${u}`),i(` ${h}Copy-paste these prompts into your AI tool to fix issues.${u}`);for(let c of t.fix_batches)i(""),i(` ${Ms}${g}Batch: ${c.title}${u} ${h}(${c.checks.join(", ")})${u}`),i(` ${h}${"\u2504".repeat(56)}${u}`),i(` ${he}${c.prompt}${u}`)}i(""),i(`${h}${"\u2500".repeat(62)}${u}`),i(""),i(` ${g}Total:${u} ${s.length} checks \u2014 ${_}${n.length} pass${u}, ${r.length>0?`${k}${r.length} fail${u}`:`${_}0 fail${u}`}${o.length>0?`, ${o.length} unknown`:""}`),i(""),i(` ${h}${t.summary}${u}`),Bs()}function Se(){let e=t=>process.stdout.write(t+`
19
- `);e(""),e(` ${L}\u26A0 API unavailable. Showing local results only.${u}`),e(` ${h}Run again later for fix prompts.${u}`),e("")}import ee from"fs";import ye from"path";var Z=["#!/bin/bash","# ASA Structure Check \u2014 validates Slice Architecture rules","# Run: npx @vibecodiq/cli guard check (or ./check-structure.sh)","","set -e","",'RULES_FILE=".asa/rules/architecture.md"',"ERRORS=0","WARNINGS=0","",'echo "\u{1F50D} ASA Structure Check \u2014 validating architecture rules..."','echo " Rules: $RULES_FILE"','echo ""',"","fail() {",' echo "\u274C FAIL: $1"',' echo " \u2192 $2"',' echo " \u2192 Fix: see $RULES_FILE"',' echo ""'," ERRORS=$((ERRORS + 1))","}","","warn() {",' echo "\u26A0\uFE0F WARN: $1"',' echo " \u2192 $2"',' echo ""'," WARNINGS=$((WARNINGS + 1))","}","","pass() {",' echo "\u2705 PASS: $1"',"}","","# CHECK 1: Business logic in src/domains/",'echo "--- Check 1: Business logic in src/domains/ ---"',"",'BIZ_IN_PAGES=$(grep -rl "supabase\\.\\(from\\|auth\\|rpc\\|storage\\)" src/pages/ 2>/dev/null || true)','if [ -n "$BIZ_IN_PAGES" ]; then'," for f in $BIZ_IN_PAGES; do",' fail "Business logic in page file: $f" \\',' "Supabase calls must be in src/domains/, not in pages."'," done","else",' pass "No business logic found in src/pages/"',"fi","",'if [ -d "src/components" ]; then',' BIZ_IN_COMPONENTS=$(grep -rl "supabase\\.\\(from\\|auth\\|rpc\\|storage\\)" src/components/ 2>/dev/null || true)',' if [ -n "$BIZ_IN_COMPONENTS" ]; then'," for f in $BIZ_IN_COMPONENTS; do",' fail "Business logic in component file: $f" \\',' "Supabase calls must be in src/domains/, not in components/."'," done"," else",' pass "No business logic found in src/components/"'," fi","fi","","# CHECK 2: src/domains/ directory exists",'echo ""','echo "--- Check 2: src/domains/ directory exists ---"',"",'TS_FILES=$(find src/ -name "*.ts" -o -name "*.tsx" | grep -v "node_modules" | grep -v "vite-env" | wc -l)',"",'if [ "$TS_FILES" -gt 5 ]; then',' if [ -d "src/domains" ]; then',' pass "src/domains/ directory exists"'," DOMAIN_COUNT=$(find src/domains -mindepth 1 -maxdepth 1 -type d | wc -l)",' if [ "$DOMAIN_COUNT" -gt 0 ]; then',' pass "Found $DOMAIN_COUNT domain(s) in src/domains/"'," else",' warn "src/domains/ exists but is empty" \\',' "Add domain folders like src/domains/auth/, src/domains/tasks/"'," fi"," else",' fail "src/domains/ directory missing" \\',' "Business logic must be in src/domains/<domain>/<slice>/."'," fi","else",' pass "Project is small ($TS_FILES files) \u2014 src/domains/ not yet required"',"fi","","# CHECK 3: No cross-domain imports",'echo ""','echo "--- Check 3: No cross-domain imports ---"',"",'if [ -d "src/domains" ]; then'," CROSS_DOMAIN_VIOLATIONS=0"," for domain_dir in src/domains/*/; do",' [ ! -d "$domain_dir" ] && continue',' domain_name=$(basename "$domain_dir")',' OTHER_DOMAINS=$(find src/domains -mindepth 1 -maxdepth 1 -type d ! -name "$domain_name" -exec basename {} \\;)'," for other in $OTHER_DOMAINS; do",' VIOLATIONS=$(grep -rn "from.*domains/$other\\|import.*domains/$other" "$domain_dir" 2>/dev/null || true)',' if [ -n "$VIOLATIONS" ]; then'," while IFS= read -r line; do",' fail "Cross-domain import in $domain_name \u2192 $other" "$line"',' done <<< "$VIOLATIONS"'," CROSS_DOMAIN_VIOLATIONS=$((CROSS_DOMAIN_VIOLATIONS + 1))"," fi"," done"," done",' [ "$CROSS_DOMAIN_VIOLATIONS" -eq 0 ] && pass "No cross-domain imports detected"',"else",' pass "No domains yet \u2014 cross-domain check skipped"',"fi","","# CHECK 4: Pages are thin wrappers",'echo ""','echo "--- Check 4: Pages are thin wrappers ---"',"",'if [ -d "src/pages" ]; then'," for page in src/pages/*.tsx; do",' [ ! -f "$page" ] && continue',' PAGE_LINES=$(wc -l < "$page")',' if [ "$PAGE_LINES" -gt 80 ]; then',' fail "Page too large: $page ($PAGE_LINES lines)" \\',' "Pages should be <80 lines. Move logic to src/domains/."'," fi"," done",' pass "Page thin wrapper check complete"',"fi","","# CHECK 5: shared/ has no business logic",'echo ""','echo "--- Check 5: shared/ has no business logic ---"',"",'if [ -d "src/shared" ]; then',' BIZ_IN_SHARED=$(grep -rln "TaskList\\|TaskForm\\|PricingCard\\|AdminUser\\|LoginForm\\|RegisterForm" src/shared/ 2>/dev/null || true)',' if [ -n "$BIZ_IN_SHARED" ]; then'," for f in $BIZ_IN_SHARED; do",' fail "Business component in shared/: $f" \\',' "Domain-specific components belong in src/domains/."'," done"," else",' pass "No business logic found in src/shared/"'," fi","else",' pass "src/shared/ not yet created \u2014 check skipped"',"fi","","# RESULTS",'echo ""','echo "=========================================="','if [ "$ERRORS" -gt 0 ]; then',' echo "\u274C ASA STRUCTURE CHECK FAILED"',' echo " $ERRORS error(s), $WARNINGS warning(s)"',' echo " Read the architecture rules: $RULES_FILE"',' echo "=========================================="'," exit 1","else",' echo "\u2705 ASA STRUCTURE CHECK PASSED"',' echo " 0 errors, $WARNINGS warning(s)"',' echo "=========================================="'," exit 0","fi",""].join(`
17
+ `),i=e.checks.filter(p=>!p.shadow),s=i.filter(p=>p.result==="FAIL"),r=i.filter(p=>p.result==="PASS"),n=i.filter(p=>p.result==="UNKNOWN"),o=i.filter(p=>p.result==="NOT_APPLICABLE"),a=i.filter(p=>p.result==="REVIEW"),c=s.filter(p=>p.priority==="P0"),l=s.filter(p=>p.priority==="P1"),d=s.filter(p=>p.priority!=="P0"&&p.priority!=="P1");if(t(""),e.mode==="architecture"&&e.chaosIndex){let p=e.chaosIndex,S=p.score<=40?_:p.score<=60?L:k;t(` ${g}Architecture Scan${u} ${h}\u2014 ${e.project}${u}`),t(""),t(` ${g}AI Chaos Index:${u} ${S}${g}${p.score} / 100${u} ${g}Risk Band:${u} ${S}${g}${p.band}${u}`)}else if(t(` ${g}Safety Scan${u} ${h}\u2014 ${e.project}${u}`),e.trustScore){let p=e.trustScore;t(""),t(` ${g}Trust Score:${u} ${V(p.grade)}${g}${p.score} / 100${u} (Grade ${V(p.grade)}${g}${p.grade}${u})`)}t(""),t(`${h}${"\u2500".repeat(62)}${u}`),t("");for(let p of["L1","L2","L3","L4"]){let S=e.layers[p];if(!S)continue;let M=`${p} ${S.name}`,$=S.score,F=10-$,fe=`${$>=7?_:$>=4?L:k}${"\u2588".repeat($)}${h}${"\u2591".repeat(F)}${u} ${$}/10`;t(` ${g}${M.padEnd(22)}${u} ${fe} ${S.passed}/${S.total} pass ${S.failed>0?`${k}${S.failed} fail${u}`:`${_}0 fail${u}`}`)}if(c.length>0){t(""),t(`${h}${"\u2500".repeat(62)}${u}`),t(""),t(` ${k}${g}CRITICAL (${c.length})${u}`);for(let p of c)if(t(""),t(` ${H(p.result)} ${Os(p.priority)} ${g}${p.id}${u} ${p.name}`),t(` ${h}${p.message}${u}`),p.evidence){for(let S of p.evidence.slice(0,3))t(` ${h}\u2192 ${S}${u}`);p.evidence.length>3&&t(` ${h} ... and ${p.evidence.length-3} more${u}`)}}if(l.length>0){t(""),t(`${h}${"\u2500".repeat(62)}${u}`),t(""),t(` ${L}${g}IMPORTANT (${l.length})${u}`),t("");for(let p of l)t(` ${H(p.result)} ${g}${p.id}${u} ${p.name}`)}if(d.length>0){t(""),t(`${h}${"\u2500".repeat(62)}${u}`),t(""),t(` ${h}${g}QUALITY (${d.length})${u}`),t("");for(let p of d)t(` ${H(p.result)} ${h}${p.id.padEnd(16)}${p.name}${u}`)}if(r.length>0){t(""),t(`${h}${"\u2500".repeat(62)}${u}`),t(""),t(`${_}${g} PASSED (${r.length})${u}`),t("");for(let p of r)t(` ${H(p.result)} ${h}${p.id.padEnd(16)}${u}${p.name}`)}if(n.length>0){t(""),t(`${h}${"\u2500".repeat(62)}${u}`),t(""),t(`${L}${g} UNKNOWN (${n.length})${u} ${h}\u2014 manual review recommended${u}`),t("");for(let p of n)t(` ${H(p.result)} ${h}${p.id.padEnd(16)}${p.name}${u}`)}if(o.length>0){t(""),t(`${h}${"\u2500".repeat(62)}${u}`),t(""),t(`${h}${g} NOT APPLICABLE (${o.length})${u}`),t("");for(let p of o)t(` ${H(p.result)} ${h}${p.id.padEnd(16)}${p.name}${u}`)}t(""),t(`${h}${"\u2500".repeat(62)}${u}`),t("");let f=i.length,m=[`${_}${r.length} pass${u}`,s.length>0?`${k}${s.length} fail${u}`:`${_}0 fail${u}`];n.length>0&&m.push(`${n.length} unknown`),o.length>0&&m.push(`${h}${o.length} n/a${u}`),t(` ${g}Total:${u} ${f} checks \u2014 ${m.join(", ")}`),Bs()}function ae(e){console.log(JSON.stringify(e,null,2))}function Ks(e,t){let i=c=>process.stdout.write(c+`
18
+ `),s=e.checks.filter(c=>!c.shadow),r=s.filter(c=>c.result==="FAIL"),n=s.filter(c=>c.result==="PASS"),o=s.filter(c=>c.result==="UNKNOWN");i(""),i(` ${g}Safety Scan${u} ${h}\u2014 ${e.project}${u}`),i(""),i(` ${g}Trust Score:${u} ${V(t.grade)}${g}${t.trust_score} / 100${u} (Grade ${V(t.grade)}${g}${t.grade}${u})`),i(` ${g}Verdict:${u} ${t.fix_vs_rebuild==="FIX"?`${_}FIX${u} \u2014 worth fixing`:`${k}REBUILD${u} \u2014 consider rebuilding`}`),i("");let a=Ws(s);for(let[c,l]of a){let d=l.filter(S=>S.result==="PASS").length,f=l.filter(S=>S.result==="FAIL").length,m=l.length,p=f>0?` ${k}${f} fail${u}`:"";i(` ${g}${c.padEnd(12)}${u} ${js(d,m)} ${d}/${m} pass${p}`)}if(r.length>0){i(""),i(`${h}${"\u2500".repeat(62)}${u}`),i(""),i(` ${k}${g}FAILED (${r.length})${u}`);for(let c of r)if(i(""),i(` ${H(c.result)} ${Os(c.priority)} ${g}${c.id}${u} ${c.name}`),i(` ${h}${c.message}${u}`),c.evidence){for(let l of c.evidence.slice(0,3))i(` ${h}\u2192 ${l}${u}`);c.evidence.length>3&&i(` ${h} ... and ${c.evidence.length-3} more${u}`)}}if(t.fix_batches&&t.fix_batches.length>0){i(""),i(`${h}${"\u2500".repeat(62)}${u}`),i(""),i(` ${Ms}${g}FIX BATCHES (${t.fix_batches.length})${u}`),i(` ${h}Copy-paste these prompts into your AI tool to fix issues.${u}`);for(let c of t.fix_batches)i(""),i(` ${Ms}${g}Batch: ${c.title}${u} ${h}(${c.checks.join(", ")})${u}`),i(` ${h}${"\u2504".repeat(56)}${u}`),i(` ${he}${c.prompt}${u}`)}i(""),i(`${h}${"\u2500".repeat(62)}${u}`),i(""),i(` ${g}Total:${u} ${s.length} checks \u2014 ${_}${n.length} pass${u}, ${r.length>0?`${k}${r.length} fail${u}`:`${_}0 fail${u}`}${o.length>0?`, ${o.length} unknown`:""}`),i(""),i(` ${h}${t.summary}${u}`),Bs()}function ye(){let e=t=>process.stdout.write(t+`
19
+ `);e(""),e(` ${L}\u26A0 API unavailable. Showing local results only.${u}`),e(` ${h}Run again later for fix prompts.${u}`),e("")}import ee from"fs";import Se from"path";var Z=["#!/bin/bash","# ASA Structure Check \u2014 validates Slice Architecture rules","# Run: npx @vibecodiq/cli guard check (or ./check-structure.sh)","","set -e","",'RULES_FILE=".asa/rules/architecture.md"',"ERRORS=0","WARNINGS=0","",'echo "\u{1F50D} ASA Structure Check \u2014 validating architecture rules..."','echo " Rules: $RULES_FILE"','echo ""',"","fail() {",' echo "\u274C FAIL: $1"',' echo " \u2192 $2"',' echo " \u2192 Fix: see $RULES_FILE"',' echo ""'," ERRORS=$((ERRORS + 1))","}","","warn() {",' echo "\u26A0\uFE0F WARN: $1"',' echo " \u2192 $2"',' echo ""'," WARNINGS=$((WARNINGS + 1))","}","","pass() {",' echo "\u2705 PASS: $1"',"}","","# CHECK 1: Business logic in src/domains/",'echo "--- Check 1: Business logic in src/domains/ ---"',"",'BIZ_IN_PAGES=$(grep -rl "supabase\\.\\(from\\|auth\\|rpc\\|storage\\)" src/pages/ 2>/dev/null || true)','if [ -n "$BIZ_IN_PAGES" ]; then'," for f in $BIZ_IN_PAGES; do",' fail "Business logic in page file: $f" \\',' "Supabase calls must be in src/domains/, not in pages."'," done","else",' pass "No business logic found in src/pages/"',"fi","",'if [ -d "src/components" ]; then',' BIZ_IN_COMPONENTS=$(grep -rl "supabase\\.\\(from\\|auth\\|rpc\\|storage\\)" src/components/ 2>/dev/null || true)',' if [ -n "$BIZ_IN_COMPONENTS" ]; then'," for f in $BIZ_IN_COMPONENTS; do",' fail "Business logic in component file: $f" \\',' "Supabase calls must be in src/domains/, not in components/."'," done"," else",' pass "No business logic found in src/components/"'," fi","fi","","# CHECK 2: src/domains/ directory exists",'echo ""','echo "--- Check 2: src/domains/ directory exists ---"',"",'TS_FILES=$(find src/ -name "*.ts" -o -name "*.tsx" | grep -v "node_modules" | grep -v "vite-env" | wc -l)',"",'if [ "$TS_FILES" -gt 5 ]; then',' if [ -d "src/domains" ]; then',' pass "src/domains/ directory exists"'," DOMAIN_COUNT=$(find src/domains -mindepth 1 -maxdepth 1 -type d | wc -l)",' if [ "$DOMAIN_COUNT" -gt 0 ]; then',' pass "Found $DOMAIN_COUNT domain(s) in src/domains/"'," else",' warn "src/domains/ exists but is empty" \\',' "Add domain folders like src/domains/auth/, src/domains/tasks/"'," fi"," else",' fail "src/domains/ directory missing" \\',' "Business logic must be in src/domains/<domain>/<slice>/."'," fi","else",' pass "Project is small ($TS_FILES files) \u2014 src/domains/ not yet required"',"fi","","# CHECK 3: No cross-domain imports",'echo ""','echo "--- Check 3: No cross-domain imports ---"',"",'if [ -d "src/domains" ]; then'," CROSS_DOMAIN_VIOLATIONS=0"," for domain_dir in src/domains/*/; do",' [ ! -d "$domain_dir" ] && continue',' domain_name=$(basename "$domain_dir")',' OTHER_DOMAINS=$(find src/domains -mindepth 1 -maxdepth 1 -type d ! -name "$domain_name" -exec basename {} \\;)'," for other in $OTHER_DOMAINS; do",' VIOLATIONS=$(grep -rn "from.*domains/$other\\|import.*domains/$other" "$domain_dir" 2>/dev/null || true)',' if [ -n "$VIOLATIONS" ]; then'," while IFS= read -r line; do",' fail "Cross-domain import in $domain_name \u2192 $other" "$line"',' done <<< "$VIOLATIONS"'," CROSS_DOMAIN_VIOLATIONS=$((CROSS_DOMAIN_VIOLATIONS + 1))"," fi"," done"," done",' [ "$CROSS_DOMAIN_VIOLATIONS" -eq 0 ] && pass "No cross-domain imports detected"',"else",' pass "No domains yet \u2014 cross-domain check skipped"',"fi","","# CHECK 4: Pages are thin wrappers",'echo ""','echo "--- Check 4: Pages are thin wrappers ---"',"",'if [ -d "src/pages" ]; then'," for page in src/pages/*.tsx; do",' [ ! -f "$page" ] && continue',' PAGE_LINES=$(wc -l < "$page")',' if [ "$PAGE_LINES" -gt 80 ]; then',' fail "Page too large: $page ($PAGE_LINES lines)" \\',' "Pages should be <80 lines. Move logic to src/domains/."'," fi"," done",' pass "Page thin wrapper check complete"',"fi","","# CHECK 5: shared/ has no business logic",'echo ""','echo "--- Check 5: shared/ has no business logic ---"',"",'if [ -d "src/shared" ]; then',' BIZ_IN_SHARED=$(grep -rln "TaskList\\|TaskForm\\|PricingCard\\|AdminUser\\|LoginForm\\|RegisterForm" src/shared/ 2>/dev/null || true)',' if [ -n "$BIZ_IN_SHARED" ]; then'," for f in $BIZ_IN_SHARED; do",' fail "Business component in shared/: $f" \\',' "Domain-specific components belong in src/domains/."'," done"," else",' pass "No business logic found in src/shared/"'," fi","else",' pass "src/shared/ not yet created \u2014 check skipped"',"fi","","# RESULTS",'echo ""','echo "=========================================="','if [ "$ERRORS" -gt 0 ]; then',' echo "\u274C ASA STRUCTURE CHECK FAILED"',' echo " $ERRORS error(s), $WARNINGS warning(s)"',' echo " Read the architecture rules: $RULES_FILE"',' echo "=========================================="'," exit 1","else",' echo "\u2705 ASA STRUCTURE CHECK PASSED"',' echo " 0 errors, $WARNINGS warning(s)"',' echo "=========================================="'," exit 0","fi",""].join(`
20
20
  `);var ce=["# ASA Architecture Rules","","This project follows the ASA Slice Architecture. All code in this repository","MUST follow these rules. CI checks will fail if rules are violated.","","---","","## Rule 1: Business logic goes in `src/domains/`","","All business logic MUST be organized in domain folders:","","```","src/domains/","\u251C\u2500\u2500 auth/ # Authentication and authorization","\u251C\u2500\u2500 billing/ # Payments and subscriptions","\u2514\u2500\u2500 admin/ # Admin panel and user management","```","","Each domain contains **slices** \u2014 self-contained features:","","```","src/domains/<domain>/<slice>/","\u251C\u2500\u2500 <Component>.tsx # React component (UI)","\u251C\u2500\u2500 use<Action>.ts # React hook (data fetching / mutations)","\u2514\u2500\u2500 types.ts # Types (optional)","```","","### What is a Slice?","","One slice = one user action. Examples:","","| Domain | Slice | What it does |","|--------|-------|-------------|","| `auth` | `login` | User logs in |","| `auth` | `register` | User creates account |","| `billing` | `subscribe` | User subscribes to a plan |","| `billing` | `check-limits` | Check if user hit plan limits |","| `admin` | `user-list` | Admin sees all users |","","---","","## Rule 2: Pages are thin wrappers","","Page files MUST be thin wrappers that import from `src/domains/`.","Pages contain NO business logic \u2014 only layout and composition.","Maximum 80 lines per page file.","","---","","## Rule 3: Shared infrastructure goes in `src/shared/`","","Database clients, auth helpers, and external service configs go in `src/shared/`:","","```","src/shared/","\u251C\u2500\u2500 db/","\u2502 \u2514\u2500\u2500 supabase-client.ts","\u251C\u2500\u2500 auth/","\u2502 \u251C\u2500\u2500 AuthGuard.tsx","\u2502 \u2514\u2500\u2500 useCurrentUser.ts","\u2514\u2500\u2500 billing/"," \u2514\u2500\u2500 stripe-client.ts","```","","---","","## Rule 4: No cross-domain imports","","A file in one domain MUST NOT import from another domain.","Use `src/shared/` for cross-domain communication.","","---","","## Rule 5: File naming conventions","","| Type | Pattern | Example |","|------|---------|---------|","| Component | `PascalCase.tsx` | `LoginForm.tsx` |","| Hook | `use<Action>.ts` | `useLogin.ts` |","| Types | `types.ts` | `types.ts` |","| Page | `page.tsx` | `page.tsx` |","| Shared utility | `camelCase.ts` | `supabase-client.ts` |","","---","","**If CI fails, restructure your code to follow these rules.**",""].join(`
21
21
  `),le=["name: Vibecodiq Guard","","on:"," push:"," branches: [main]"," paths-ignore:"," - '.asa/logs/**'"," pull_request:"," branches: [main]","","permissions:"," contents: read"," pull-requests: write"," actions: read","","jobs:"," vibecodiq-guard:"," name: Vibecodiq Safety Scan"," runs-on: ubuntu-latest",""," steps:"," - name: Checkout code"," uses: actions/checkout@v4"," with:"," fetch-depth: 0",""," - name: Setup Node.js"," uses: actions/setup-node@v4"," with:"," node-version: '18'",""," - name: Run Vibecodiq scan"," id: scan"," run: |"," npx @vibecodiq/cli@latest scan --all --json > /tmp/scan-result.json 2>/dev/null || true",` FAIL_COUNT=$(cat /tmp/scan-result.json | python3 -c "import sys,json; r=json.load(sys.stdin); print(sum(1 for c in r.get('checks',[]) if c.get('result')=='FAIL'))" 2>/dev/null || echo 0)`,` TOTAL=$(cat /tmp/scan-result.json | python3 -c "import sys,json; r=json.load(sys.stdin); print(len(r.get('checks',[])))" 2>/dev/null || echo 0)`,' echo "fail_count=$FAIL_COUNT" >> "$GITHUB_OUTPUT"',' echo "total=$TOTAL" >> "$GITHUB_OUTPUT"',' if [ "$FAIL_COUNT" -gt 0 ]; then',' echo "has_failures=true" >> "$GITHUB_OUTPUT"'," else",' echo "has_failures=false" >> "$GITHUB_OUTPUT"'," fi",""," - name: Call API for fix prompts"," id: api"," if: steps.scan.outputs.has_failures == 'true' && env.VIBECODIQ_API_TOKEN != ''"," env:"," VIBECODIQ_API_TOKEN: ${{ secrets.VIBECODIQ_API_TOKEN }}"," run: |"," npx @vibecodiq/cli@latest scan --all --fix --json > /tmp/fix-result.json 2>/dev/null || true"," if [ -s /tmp/fix-result.json ]; then",' echo "api_ok=true" >> "$GITHUB_OUTPUT"'," else",' echo "api_ok=false" >> "$GITHUB_OUTPUT"'," fi",""," - name: Upload scan artifact"," if: always()"," uses: actions/upload-artifact@v4"," with:"," name: vibecodiq-scan"," path: /tmp/scan-result.json"," retention-days: 30",""," - name: Write job summary"," if: always()"," run: |"," echo '## Vibecodiq Safety Scan' >> $GITHUB_STEP_SUMMARY"," FAIL_COUNT=${{ steps.scan.outputs.fail_count }}"," TOTAL=${{ steps.scan.outputs.total }}",' if [ "$FAIL_COUNT" = "0" ]; then'," echo '\u2705 **All checks passed** \u2014 your app is production-ready.' >> $GITHUB_STEP_SUMMARY"," else",' echo "\u274C **$FAIL_COUNT failures** out of $TOTAL checks." >> $GITHUB_STEP_SUMMARY'," echo '' >> $GITHUB_STEP_SUMMARY"," echo 'Download the scan artifact for full details.' >> $GITHUB_STEP_SUMMARY"," fi"," echo '' >> $GITHUB_STEP_SUMMARY"," echo '---' >> $GITHUB_STEP_SUMMARY"," echo '*Scanned by [Vibecodiq](https://vibecodiq.com) production-readiness checks*' >> $GITHUB_STEP_SUMMARY",""," - name: Post PR comment"," if: github.event_name == 'pull_request' && steps.scan.outputs.has_failures == 'true'"," uses: actions/github-script@v7"," with:"," script: |"," const fs = require('fs');"," const failCount = '${{ steps.scan.outputs.fail_count }}';"," const total = '${{ steps.scan.outputs.total }}';"," "," // Build comment body"," let body = `## \u274C Vibecodiq Safety Scan \u2014 ${failCount} failures\\n\\n`;"," body += `**${failCount}** of **${total}** checks failed.\\n\\n`;"," "," // Try to include fix prompts from API result"," try {"," const fixResult = JSON.parse(fs.readFileSync('/tmp/fix-result.json', 'utf-8'));"," if (fixResult.fix_batches && fixResult.fix_batches.length > 0) {"," body += `**Trust Score:** ${fixResult.grade} (${fixResult.trust_score}/100) \xB7 `;"," body += `**Fix:** ~${fixResult.fix_cost_hours}h \xB7 `;"," body += `**Verdict:** ${fixResult.fix_vs_rebuild}\\n\\n`;"," body += `### Top Fix Batches\\n\\n`;"," for (const batch of fixResult.fix_batches.slice(0, 3)) {"," body += `<details>\\n<summary>${batch.title} (${batch.checks.join(', ')})</summary>\\n\\n`;"," body += '```\\n' + batch.prompt + '\\n```\\n\\n';"," body += `</details>\\n\\n`;"," }"," }"," } catch (e) {}"," "," body += `---\\n*Scanned by [Vibecodiq](https://vibecodiq.com) production-readiness checks*`;"," "," // Find and update existing comment (sticky)"," const marker = '## \u274C Vibecodiq Safety Scan';"," const { data: comments } = await github.rest.issues.listComments({"," owner: context.repo.owner,"," repo: context.repo.repo,"," issue_number: context.issue.number,"," });"," const existing = comments.find(c => c.body && c.body.startsWith(marker));"," "," if (existing) {"," await github.rest.issues.updateComment({"," owner: context.repo.owner,"," repo: context.repo.repo,"," comment_id: existing.id,"," body: body,"," });"," } else {"," await github.rest.issues.createComment({"," owner: context.repo.owner,"," repo: context.repo.repo,"," issue_number: context.issue.number,"," body: body,"," });"," }",""," - name: Delete PR comment on pass"," if: github.event_name == 'pull_request' && steps.scan.outputs.has_failures == 'false'"," uses: actions/github-script@v7"," with:"," script: |"," const marker = '## \u274C Vibecodiq Safety Scan';"," const { data: comments } = await github.rest.issues.listComments({"," owner: context.repo.owner,"," repo: context.repo.repo,"," issue_number: context.issue.number,"," });"," const existing = comments.find(c => c.body && c.body.startsWith(marker));"," if (existing) {"," await github.rest.issues.deleteComment({"," owner: context.repo.owner,"," repo: context.repo.repo,"," comment_id: existing.id,"," });"," }",""," - name: Fail if checks failed"," if: steps.scan.outputs.has_failures == 'true'"," run: |",' echo "\u274C Vibecodiq scan found ${{ steps.scan.outputs.fail_count }} failures."',' echo "Download the scan artifact or check the PR comment for fix instructions."'," exit 1",""].join(`
22
- `);var Qi="\x1B[36m",Zi="\x1B[32m",en="\x1B[33m";var Gs="\x1B[1m",z="\x1B[2m",E="\x1B[0m";async function Vs(e,t=!1){let i=ye.resolve(e);console.log(""),console.log(` ${Qi}\u{1F6E1}\uFE0F Initializing ASA Guard in ${i}${E}`),console.log("");let s=[{path:".asa/rules/architecture.md",content:ce},{path:".github/workflows/asa-guard.yml",content:le},{path:"check-structure.sh",content:Z,executable:!0}],r=0,n=0;for(let o of s){let a=ye.join(i,o.path),c=ye.dirname(a);ee.existsSync(c)||ee.mkdirSync(c,{recursive:!0}),ee.existsSync(a)?(console.log(` ${en}\u23ED ${o.path}${E} ${z}(already exists)${E}`),n++):(ee.writeFileSync(a,o.content,"utf-8"),o.executable&&ee.chmodSync(a,493),console.log(` ${Zi}\u2713 ${o.path}${E}`),r++)}console.log(""),console.log(` ${Gs}Done:${E} ${r} file${r!==1?"s":""} created, ${n} skipped.`),console.log(""),console.log(` ${Gs}Next steps:${E}`),console.log(` ${z}1.${E} Commit the new files: ${z}git add -A && git commit -m "chore: add ASA Guard"${E}`),console.log(` ${z}2.${E} Push to GitHub \u2014 CI will run architecture checks on every PR`),console.log(` ${z}3.${E} Run locally: ${z}npx @vibecodiq/cli guard check${E}`),console.log(""),console.log(` ${z}Learn more: https://asastandard.org/checks/methodology${E}`),console.log("")}import R from"fs";import I from"path";var tn="\x1B[36m",Ys="\x1B[32m",sn="\x1B[33m",Xs="\x1B[31m",Js="\x1B[1m",Y="\x1B[2m",T="\x1B[0m";async function Qs(e){let t=I.resolve(e);console.log(""),console.log(` ${tn}\u{1F6E1}\uFE0F ASA Guard \u2014 Architecture Check${T}`),console.log(` ${Y} Target: ${t}${T}`),console.log("");let i=[],s=nn(t);i.push(await rn(t,s)),i.push(await on(t,s)),i.push(await an(t,s)),i.push(await cn(t,s)),i.push(await ln(t,s));let r=0,n=0,o=0;for(let a of i){let c=a.status==="PASS"?`${Ys}\u2705${T}`:a.status==="FAIL"?`${Xs}\u274C${T}`:a.status==="WARN"?`${sn}\u26A0\uFE0F${T}`:`${Y}\u23ED${T}`;if(console.log(` ${c} ${a.name}`),a.status!=="PASS"&&a.status!=="SKIP"&&(console.log(` ${Y}${a.message}${T}`),a.evidence))for(let l of a.evidence.slice(0,3))console.log(` ${Y}\u2192 ${l}${T}`);a.status==="FAIL"?r++:a.status==="WARN"?n++:a.status==="PASS"&&o++}console.log(""),r>0?(console.log(` ${Xs}${Js}\u274C ASA GUARD CHECK FAILED${T} \u2014 ${r} error${r>1?"s":""}, ${n} warning${n>1?"s":""}`),console.log(` ${Y} Fix violations and run again.${T}`),console.log(` ${Y} Rules: .asa/rules/architecture.md${T}`)):console.log(` ${Ys}${Js}\u2705 ASA GUARD CHECK PASSED${T} \u2014 ${o} passed, ${n} warning${n>1?"s":""}`),console.log(""),process.exit(r>0?1:0)}function nn(e){return R.existsSync(I.join(e,"src"))?"src":R.existsSync(I.join(e,"app"))?"app":null}function X(e,t){let i=[];if(!R.existsSync(e))return i;let s=R.readdirSync(e,{withFileTypes:!0});for(let r of s){let n=I.join(e,r.name);r.name==="node_modules"||r.name===".git"||(r.isDirectory()?i.push(...X(n,t)):t.some(o=>r.name.endsWith(o))&&i.push(n))}return i}async function rn(e,t){let i="Business logic in domains/";if(!t)return{name:i,status:"SKIP",message:"No src/ directory found"};let s=I.join(e,t,"pages"),r=I.join(e,t,"components"),n=[],o=/supabase\.(from|auth|rpc|storage)\b/;for(let a of[s,r]){if(!R.existsSync(a))continue;let c=X(a,[".ts",".tsx",".js",".jsx"]);for(let l of c){let d=R.readFileSync(l,"utf-8");o.test(d)&&n.push(I.relative(e,l))}}return n.length>0?{name:i,status:"FAIL",message:`Supabase calls found in pages/components (${n.length} file${n.length>1?"s":""})`,evidence:n}:{name:i,status:"PASS",message:""}}async function on(e,t){let i="domains/ directory exists";if(!t)return{name:i,status:"SKIP",message:"No src/ directory found"};let s=I.join(e,t,"domains"),r=X(I.join(e,t),[".ts",".tsx"]);if(r.length<=5)return{name:i,status:"PASS",message:"Project is small \u2014 domains/ not yet required"};if(!R.existsSync(s))return{name:i,status:"FAIL",message:`${r.length} files in ${t}/ but no domains/ directory`,evidence:["Create src/domains/<domain>/<slice>/ for business logic"]};let n=R.readdirSync(s,{withFileTypes:!0}).filter(o=>o.isDirectory());return n.length===0?{name:i,status:"WARN",message:"domains/ exists but is empty"}:{name:i,status:"PASS",message:`${n.length} domain${n.length>1?"s":""} found`}}async function an(e,t){let i="No cross-domain imports";if(!t)return{name:i,status:"SKIP",message:"No src/ directory found"};let s=I.join(e,t,"domains");if(!R.existsSync(s))return{name:i,status:"PASS",message:"No domains yet"};let r=R.readdirSync(s,{withFileTypes:!0}).filter(o=>o.isDirectory()).map(o=>o.name),n=[];for(let o of r){let a=I.join(s,o),c=X(a,[".ts",".tsx",".js",".jsx"]),l=r.filter(d=>d!==o);for(let d of c){let f=R.readFileSync(d,"utf-8");for(let m of l)new RegExp(`from.*domains/${m}|import.*domains/${m}`).test(f)&&n.push(`${I.relative(e,d)} \u2192 imports from ${m}/`)}}return n.length>0?{name:i,status:"FAIL",message:`${n.length} cross-domain import${n.length>1?"s":""} found`,evidence:n}:{name:i,status:"PASS",message:""}}async function cn(e,t){let i="Pages are thin wrappers";if(!t)return{name:i,status:"SKIP",message:"No src/ directory found"};let s=I.join(e,t,"pages");if(!R.existsSync(s))return{name:i,status:"PASS",message:"No pages/ directory (Next.js App Router or no pages)"};let r=X(s,[".tsx",".jsx"]),n=[];for(let o of r){let c=R.readFileSync(o,"utf-8").split(`
23
- `).length;c>80&&n.push(`${I.relative(e,o)} (${c} lines)`)}return n.length>0?{name:i,status:"FAIL",message:`${n.length} page${n.length>1?"s":""} over 80 lines`,evidence:n}:{name:i,status:"PASS",message:""}}async function ln(e,t){let i="shared/ has no business logic";if(!t)return{name:i,status:"SKIP",message:"No src/ directory found"};let s=I.join(e,t,"shared");if(!R.existsSync(s))return{name:i,status:"PASS",message:"No shared/ directory yet"};let r=/TaskList|TaskForm|PricingCard|AdminUser|LoginForm|RegisterForm/,n=X(s,[".ts",".tsx",".js",".jsx"]),o=[];for(let a of n){let c=R.readFileSync(a,"utf-8");r.test(c)&&o.push(I.relative(e,a))}return o.length>0?{name:i,status:"FAIL",message:`Business components found in shared/ (${o.length} file${o.length>1?"s":""})`,evidence:o}:{name:i,status:"PASS",message:""}}import ue from"fs";import Zs from"path";var un="\x1B[32m",dn="\x1B[33m",pn="\x1B[36m",ei="\x1B[1m",$e="\x1B[2m",O="\x1B[0m";async function ti(e){let t=Zs.resolve(e);console.log(""),console.log(` ${pn}\u{1F504} Upgrading ASA Guard rules...${O}`),console.log("");let i=[{path:".asa/rules/architecture.md",content:ce},{path:".github/workflows/asa-guard.yml",content:le},{path:"check-structure.sh",content:Z,executable:!0}],s=0;for(let r of i){let n=Zs.join(t,r.path);if(!ue.existsSync(n)){console.log(` ${dn}\u23ED ${r.path}${O} ${$e}(not found \u2014 run guard init first)${O}`);continue}if(ue.readFileSync(n,"utf-8")===r.content){console.log(` ${$e}\u2713 ${r.path} (already up to date)${O}`);continue}ue.writeFileSync(n,r.content,"utf-8"),r.executable&&ue.chmodSync(n,493),console.log(` ${un}\u2713 ${r.path}${O} ${ei}updated${O}`),s++}console.log(""),s>0?console.log(` ${ei}${s} file${s>1?"s":""} updated.${O} Commit the changes.`):console.log(` ${$e}All rules are already up to date.${O}`),console.log("")}import N from"fs";import C from"path";import{fileURLToPath as fn}from"url";var si="\x1B[36m",te="\x1B[32m",ii="\x1B[33m",Ae="\x1B[31m",q="\x1B[1m",P="\x1B[2m",A="\x1B[0m",mn={auth:["db_basic","auth_basic"],payments:["db_basic","payments_basic"],admin:["db_basic","admin_basic"],foundation:["db_basic","auth_basic","payments_basic","admin_basic"]},ni={auth:"Auth \u2014 login, register, sessions, middleware, RLS migrations",payments:"Payments \u2014 Stripe checkout, webhooks, subscriptions, entitlements",admin:"Admin \u2014 RBAC roles, audit log, user management, impersonation",foundation:"Foundation \u2014 Auth + Payments + Admin + DB utilities"};function gn(){let e=C.dirname(fn(import.meta.url)),t=[C.join(e,"foundation"),C.join(e,"..","foundation"),C.join(e,"..","src","foundation")];for(let i of t)if(N.existsSync(i))return i;return t[0]}function ri(e){let t=C.join(e,"manifest.json");if(!N.existsSync(t))return null;try{return JSON.parse(N.readFileSync(t,"utf-8"))}catch{return null}}function de(e,t,i){let s=C.dirname(t);return N.existsSync(s)||N.mkdirSync(s,{recursive:!0}),N.existsSync(t)?i?(N.copyFileSync(e,t),"overwritten"):"skipped":(N.copyFileSync(e,t),"created")}function hn(e,t,i){let s=C.join(i,e),r=ri(s);if(!r)return console.log(` ${Ae}\u2717 No manifest found for ${e}${A}`),{created:0,skipped:0,notes:[]};let n=0,o=0;for(let a of r.shared||[]){let c=C.join(s,a.src),l=C.join(t,a.dest);if(!N.existsSync(c))continue;let d=de(c,l,a.overwrite);d==="created"||d==="overwritten"?(console.log(` ${te}+${A} ${a.dest}`),n++):(console.log(` ${P}~ ${a.dest} (exists, kept)${A}`),o++)}for(let a of r.slices||[]){let c=C.join(s,"slices",a.name.replace("-","_")),l=C.join(t,"domains",a.domain,a.name);for(let d of["handler.ts","repository.ts","schemas.ts","slice.contract.json"]){let f=C.join(c,d);if(!N.existsSync(f))continue;let m=C.join(l,d);de(f,m,!1)==="created"?(console.log(` ${te}+${A} domains/${a.domain}/${a.name}/${d}`),n++):(console.log(` ${P}~ domains/${a.domain}/${a.name}/${d} (exists, kept)${A}`),o++)}if(a.has_ui){let d=C.join(c,"ui"),f=C.join(l,"ui");if(N.existsSync(d))for(let m of N.readdirSync(d)){let p=C.join(d,m),y=C.join(f,m);de(p,y,!1)==="created"?(console.log(` ${te}+${A} domains/${a.domain}/${a.name}/ui/${m}`),n++):(console.log(` ${P}~ domains/${a.domain}/${a.name}/ui/${m} (exists, kept)${A}`),o++)}}}for(let a of r.migrations||[]){let c=C.join(s,a.src),l=C.join(t,a.dest);if(!N.existsSync(c))continue;de(c,l,!1)==="created"?(console.log(` ${te}+${A} ${a.dest}`),n++):(console.log(` ${P}~ ${a.dest} (exists, kept)${A}`),o++)}return{created:n,skipped:o,notes:r.post_install_notes||[]}}async function oi(e,t){let i=C.resolve(t),s=mn[e];if(!s){console.log(""),console.log(` ${Ae}Unknown module: ${e}${A}`),console.log(""),console.log(` ${q}Available modules:${A}`);for(let[d,f]of Object.entries(ni))console.log(` ${si}install ${d}${A}`),console.log(` ${P}${f}${A}`);console.log(""),process.exit(1)}let r=gn();N.existsSync(r)||(console.log(""),console.log(` ${Ae}Foundation modules not found at: ${r}${A}`),console.log(` ${P}This is likely a bug \u2014 please report it at vibecodiq.com${A}`),console.log(""),process.exit(1)),console.log(""),console.log(` ${si}${q}Vibecodiq Foundation${A} \u2014 Installing ${q}${e}${A}`),console.log(` ${P}${ni[e]}${A}`),console.log(` ${P}Target: ${i}${A}`),console.log("");let n=new Set,o=[],a=0,c=0;for(let d of s){if(n.has(d))continue;n.add(d);let f=d.replace("_basic","").replace("_"," ");console.log(` ${q}${f.charAt(0).toUpperCase()+f.slice(1)}${A}`);let{created:m,skipped:p,notes:y}=hn(d,i,r);a+=m,c+=p,o.push(...y),console.log("")}console.log(` ${P}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${A}`),console.log(` ${te}${q}${a} files installed${A} ${P}(${c} already existed)${A}`),console.log("");let l=s.map(d=>ri(C.join(r,d))?.package_dependencies??{}).reduce((d,f)=>({...d,...f}),{});if(Object.keys(l).length>0){console.log(` ${ii}${q}Required npm packages:${A}`);let d=Object.entries(l).map(([f,m])=>`${f}@${m}`).join(" ");console.log(` ${P} npm install ${d}${A}`),console.log(` ${P} # or: pnpm add ${d}${A}`),console.log("")}if(o.length>0){let d=[...new Set(o)];console.log(` ${ii}${q}Next steps:${A}`);for(let f of d)console.log(` ${P} \u2022 ${f}${A}`);console.log("")}console.log(` ${P}Run a full scan to verify:${A}`),console.log(` ${P} npx @vibecodiq/cli scan --all${A}`),console.log(` ${P} npx @vibecodiq/cli guard init --all${A}`),console.log("")}function Sn(e,t){if(e.startsWith(t))return e.slice(t.length).replace(/^[/\\]/,"");let i=/^\/(?:home|Users)\/[^/]+\//;if(i.test(e))return e.replace(i,"");let s=/^[A-Z]:\\Users\\[^\\]+\\/i;return s.test(e)?e.replace(s,""):e}function yn(e,t){return e.map(i=>i.replace(/(?:\/(?:home|Users)\/[^\s:]+|[A-Z]:\\Users\\[^\s:]+)/g,s=>Sn(s,t)))}function be(e,t,i,s,r){let n=e.map(o=>({check_id:o.id,result:o.result,name:o.name,module:o.module,layer:o.layer,priority:o.priority,category:o.category,message:o.message,evidence:o.evidence?yn(o.evidence,t):void 0}));return{cli_version:i,scan_mode:s,project_name:r,timestamp:new Date().toISOString(),findings:n}}import{readFileSync as $n}from"fs";import{join as An}from"path";import{homedir as bn}from"os";var ai=process.env.VIBECODIQ_API_URL||"https://api.vibecodiq.com",ci=1e4,J=1;async function li(e,t,i){let s=new AbortController,r=setTimeout(()=>s.abort(),i);try{return await fetch(e,{...t,signal:s.signal})}finally{clearTimeout(r)}}async function Ce(e,t){for(let i=0;i<=J;i++)try{let s=await li(`${ai}/scan`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`},body:JSON.stringify(e)},ci);if(s.ok)return await s.json();if(s.status===401){let r=await s.json().catch(()=>({detail:"Unauthorized"}));throw new K(r.detail??"Invalid API token")}if(s.status>=500&&i<J)continue;return null}catch(s){if(s instanceof K)throw s;if(i<J)continue;return null}return null}async function ve(e){for(let t=0;t<=J;t++)try{let i=await li(`${ai}/scan/free`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)},ci);if(i.ok)return await i.json();if(i.status>=500&&t<J)continue;return null}catch{if(t<J)continue;return null}return null}var K=class extends Error{constructor(t){super(t),this.name="AuthError"}};function ke(){let e=process.env.VIBECODIQ_API_TOKEN;if(e)return e;try{let t=An(bn(),".vibecodiq","token"),i=$n(t,"utf-8").trim();if(i)return i}catch{}return null}var vn="533f2e11ca9bf0419a066f73ff9cc910e23a39053fcb3051eb03f9521794e8db",pe="0.6.0",D="\x1B[1m",b="\x1B[2m",W="\x1B[36m",_e="\x1B[33m",S="\x1B[0m",j=process.argv.slice(2),G=j[0],Ie=j[1];function se(e){for(let t of e.slice(1))if(!t.startsWith("-"))return t;return"."}function B(e){return j.includes(e)}if(G==="scan"){let e=B("--fix"),t=se(j),i=B("--json"),s=!1;if(B("--unreleased")){let m=process.env.VIBECODIQ_INTERNAL_KEY||"";Cn("sha256").update(m).digest("hex")!==vn&&(console.log(""),console.log(` ${_e}\u26A0 --unreleased requires VIBECODIQ_INTERNAL_KEY environment variable.${S}`),console.log(` ${b}This flag is restricted to authorized internal use.${S}`),console.log(""),process.exit(1)),s=!0}let r="trust-score";B("--architecture")&&(r="architecture"),B("--all")&&(r="all");let n=r==="trust-score"?"Safety Scan":r==="architecture"?"Architecture Scan":"Full Scan";i||(console.log(""),console.log(` ${W}\u23F3 Scanning project \u2014 ${n}...${S}`));let o=await Ds(t,{mode:r,allChecks:s}),a=o.checks.some(m=>m.result==="FAIL");if(e){let m=ke();m||(console.log(""),console.log(` ${_e}\u26A0 No API token found.${S}`),console.log(""),console.log(" Set your token via:"),console.log(` ${W}export VIBECODIQ_API_TOKEN=<your-token>${S}`),console.log(" Or save to file:"),console.log(` ${W}mkdir -p ~/.vibecodiq && echo "<your-token>" > ~/.vibecodiq/token${S}`),console.log(""),console.log(` ${b}Get your token at: https://vibecodiq.com/pricing${S}`),console.log(""),process.exit(1)),i||console.log(` ${W}\u23F3 Sending sanitized findings to API...${S}`);let p=be(o.checks,t.startsWith("/")?t:process.cwd(),pe,r,o.project);try{let y=await Ce(p,m);y?i?console.log(JSON.stringify({...o,...y},null,2)):(Ks(o,y),y.share_url&&(console.log(""),console.log(` ${b}\u{1F4CB} Full report: ${W}${y.share_url}${S}`))):i?ae(o):(oe(o),Se())}catch(y){y instanceof K&&(console.log(""),console.log(` ${_e}\u26A0 API authentication failed: ${y.message}${S}`),console.log(` ${b}Check your VIBECODIQ_API_TOKEN.${S}`),console.log(""),process.exit(1)),i?ae(o):(oe(o),Se())}process.exit(a?1:0)}let c=B("--verbose");i?ae(o):c?oe(o):qs(o);let l=be(o.checks,t.startsWith("/")?t:process.cwd(),pe,r,o.project),d=ke(),f;try{if(d)try{f=(await Ce(l,d))?.share_url??void 0}catch(m){if(m instanceof K)f=(await ve(l))?.share_url??void 0;else throw m}else f=(await ve(l))?.share_url??void 0}catch{}f&&!i&&(console.log(""),console.log(` ${b}\u{1F4CB} Full report: ${W}${f}${S}`)),process.exit(a?1:0)}else if(G==="guard")if(Ie==="init"){let e=se(j.slice(1)),t=B("--architecture"),i=B("--all");await Vs(e,t||i)}else if(Ie==="check"){let e=se(j.slice(1));await Qs(e)}else if(Ie==="upgrade"){let e=se(j.slice(1));await ti(e)}else console.log(""),console.log(` ${D}@vibecodiq/cli guard${S} \u2014 Architecture rules & CI enforcement`),console.log(""),console.log(` ${D}Commands:${S}`),console.log(" guard init Install safety rules + CI into your repo"),console.log(" guard init --architecture + ASA architecture rules"),console.log(" guard init --all Install everything"),console.log(" guard check Run architecture checks locally"),console.log(" guard upgrade Upgrade rules to latest version"),console.log(""),console.log(` ${b}Learn more: https://vibecodiq.com/guard${S}`),console.log("");else if(G==="install"){let e=j[1],t=se(j.slice(1));(!e||e.startsWith("-"))&&(console.log(""),console.log(` ${D}@vibecodiq/cli install${S} \u2014 Vibecodiq Foundation modules`),console.log(""),console.log(` ${D}Usage:${S}`),console.log(" npx @vibecodiq/cli install auth"),console.log(" npx @vibecodiq/cli install payments"),console.log(" npx @vibecodiq/cli install admin"),console.log(` npx @vibecodiq/cli install foundation ${b}(all three)${S}`),console.log(""),console.log(` ${D}Modules:${S}`),console.log(` auth ${b}Login, register, sessions, server-side auth, RLS${S}`),console.log(` payments ${b}Stripe checkout, webhooks, subscriptions, entitlements${S}`),console.log(` admin ${b}RBAC roles, audit log, user management, impersonation${S}`),console.log(` foundation ${b}All three modules + DB utilities${S}`),console.log(""),console.log(` ${b}ASA-certified modules \u2014 pass 100% of Phase 1 checks out of the box.${S}`),console.log(` ${b}Learn more: https://vibecodiq.com/foundation${S}`),console.log(""),process.exit(0)),await oi(e,t)}else if(G==="login"){let e=j[1];e||(console.log(""),console.log(` ${D}@vibecodiq/cli login${S} \u2014 Save your API token`),console.log(""),console.log(` ${D}Usage:${S}`),console.log(" npx @vibecodiq/cli login <your-token>"),console.log(""),console.log(` ${b}Your token is saved to ~/.vibecodiq/token${S}`),console.log(` ${b}Get your token at: https://vibecodiq.com/pricing${S}`),console.log(""),process.exit(1));let{mkdirSync:t,writeFileSync:i}=await import("fs"),{join:s}=await import("path"),{homedir:r}=await import("os"),n=s(r(),".vibecodiq"),o=s(n,"token");t(n,{recursive:!0}),i(o,e.trim()+`
24
- `,{mode:384}),console.log(""),console.log(` ${W}\u2713${S} Token saved to ${b}${o}${S}`),console.log(` ${b}Run: npx @vibecodiq/cli scan --fix${S}`),console.log("")}else if(G==="logout"){let{unlinkSync:e,existsSync:t}=await import("fs"),{join:i}=await import("path"),{homedir:s}=await import("os"),r=i(s(),".vibecodiq","token");t(r)?(e(r),console.log(""),console.log(` ${W}\u2713${S} Token removed.`),console.log("")):(console.log(""),console.log(` ${b}No token found. Already logged out.${S}`),console.log(""))}else G==="--version"||G==="-v"?console.log(`@vibecodiq/cli v${pe}`):(console.log(""),console.log(` ${D}@vibecodiq/cli${S} v${pe} \u2014 Production safety for AI-built apps`),console.log(""),console.log(` ${D}Scan${S} ${b}(check what you already have)${S}`),console.log(" scan [path] Safety scan \u2014 Phase 1 (L2+L3+L4)"),console.log(" scan --architecture Architecture scan \u2014 Phase 1 (L1)"),console.log(" scan --all Full scan \u2014 Phase 1 (all layers)"),console.log(` scan --unreleased Run all checks incl. unreleased ${b}(internal)${S}`),console.log(" scan --json JSON output for CI pipelines"),console.log(` scan --fix AI fix prompts for failures ${b}(Pro)${S}`),console.log(""),console.log(` ${D}Guard${S} ${b}(build safely from day one)${S}`),console.log(" guard init Install safety rules + CI"),console.log(" guard init --architecture + ASA architecture rules"),console.log(" guard init --all Install everything"),console.log(" guard check Run architecture checks locally"),console.log(" guard upgrade Upgrade rules to latest"),console.log(""),console.log(` ${D}Foundation${S} ${b}(install safe modules into your app)${S}`),console.log(" install auth Auth module \u2014 login, register, sessions"),console.log(" install payments Payments module \u2014 Stripe + webhooks"),console.log(" install admin Admin module \u2014 RBAC, audit log"),console.log(" install foundation All three modules + DB utilities"),console.log(""),console.log(` ${D}Account${S}`),console.log(" login Authenticate for Pro features"),console.log(" logout Sign out"),console.log(" --version Show version"),console.log(""),console.log(` ${b}Docs: https://asastandard.org/checks${S}`),console.log(` ${b}Pricing: https://vibecodiq.com/pricing${S}`),console.log(""));
22
+ `);var Qi="\x1B[36m",Zi="\x1B[32m",en="\x1B[33m";var Gs="\x1B[1m",z="\x1B[2m",E="\x1B[0m";async function Vs(e,t=!1){let i=Se.resolve(e);console.log(""),console.log(` ${Qi}\u{1F6E1}\uFE0F Initializing ASA Guard in ${i}${E}`),console.log("");let s=[{path:".asa/rules/architecture.md",content:ce},{path:".github/workflows/asa-guard.yml",content:le},{path:"check-structure.sh",content:Z,executable:!0}],r=0,n=0;for(let o of s){let a=Se.join(i,o.path),c=Se.dirname(a);ee.existsSync(c)||ee.mkdirSync(c,{recursive:!0}),ee.existsSync(a)?(console.log(` ${en}\u23ED ${o.path}${E} ${z}(already exists)${E}`),n++):(ee.writeFileSync(a,o.content,"utf-8"),o.executable&&ee.chmodSync(a,493),console.log(` ${Zi}\u2713 ${o.path}${E}`),r++)}console.log(""),console.log(` ${Gs}Done:${E} ${r} file${r!==1?"s":""} created, ${n} skipped.`),console.log(""),console.log(` ${Gs}Next steps:${E}`),console.log(` ${z}1.${E} Commit the new files: ${z}git add -A && git commit -m "chore: add ASA Guard"${E}`),console.log(` ${z}2.${E} Push to GitHub \u2014 CI will run architecture checks on every PR`),console.log(` ${z}3.${E} Run locally: ${z}npx @vibecodiq/cli guard check${E}`),console.log(""),console.log(` ${z}Learn more: https://asastandard.org/checks/methodology${E}`),console.log("")}import R from"fs";import I from"path";var tn="\x1B[36m",Ys="\x1B[32m",sn="\x1B[33m",Xs="\x1B[31m",Js="\x1B[1m",Y="\x1B[2m",T="\x1B[0m";async function Qs(e){let t=I.resolve(e);console.log(""),console.log(` ${tn}\u{1F6E1}\uFE0F ASA Guard \u2014 Architecture Check${T}`),console.log(` ${Y} Target: ${t}${T}`),console.log("");let i=[],s=nn(t);i.push(await rn(t,s)),i.push(await on(t,s)),i.push(await an(t,s)),i.push(await cn(t,s)),i.push(await ln(t,s));let r=0,n=0,o=0;for(let a of i){let c=a.status==="PASS"?`${Ys}\u2705${T}`:a.status==="FAIL"?`${Xs}\u274C${T}`:a.status==="WARN"?`${sn}\u26A0\uFE0F${T}`:`${Y}\u23ED${T}`;if(console.log(` ${c} ${a.name}`),a.status!=="PASS"&&a.status!=="SKIP"&&(console.log(` ${Y}${a.message}${T}`),a.evidence))for(let l of a.evidence.slice(0,3))console.log(` ${Y}\u2192 ${l}${T}`);a.status==="FAIL"?r++:a.status==="WARN"?n++:a.status==="PASS"&&o++}console.log(""),r>0?(console.log(` ${Xs}${Js}\u274C ASA GUARD CHECK FAILED${T} \u2014 ${r} error${r>1?"s":""}, ${n} warning${n>1?"s":""}`),console.log(` ${Y} Fix violations and run again.${T}`),console.log(` ${Y} Rules: .asa/rules/architecture.md${T}`)):console.log(` ${Ys}${Js}\u2705 ASA GUARD CHECK PASSED${T} \u2014 ${o} passed, ${n} warning${n>1?"s":""}`),console.log(""),process.exit(r>0?1:0)}function nn(e){return R.existsSync(I.join(e,"src"))?"src":R.existsSync(I.join(e,"app"))?"app":null}function X(e,t){let i=[];if(!R.existsSync(e))return i;let s=R.readdirSync(e,{withFileTypes:!0});for(let r of s){let n=I.join(e,r.name);r.name==="node_modules"||r.name===".git"||(r.isDirectory()?i.push(...X(n,t)):t.some(o=>r.name.endsWith(o))&&i.push(n))}return i}async function rn(e,t){let i="Business logic in domains/";if(!t)return{name:i,status:"SKIP",message:"No src/ directory found"};let s=I.join(e,t,"pages"),r=I.join(e,t,"components"),n=[],o=/supabase\.(from|auth|rpc|storage)\b/;for(let a of[s,r]){if(!R.existsSync(a))continue;let c=X(a,[".ts",".tsx",".js",".jsx"]);for(let l of c){let d=R.readFileSync(l,"utf-8");o.test(d)&&n.push(I.relative(e,l))}}return n.length>0?{name:i,status:"FAIL",message:`Supabase calls found in pages/components (${n.length} file${n.length>1?"s":""})`,evidence:n}:{name:i,status:"PASS",message:""}}async function on(e,t){let i="domains/ directory exists";if(!t)return{name:i,status:"SKIP",message:"No src/ directory found"};let s=I.join(e,t,"domains"),r=X(I.join(e,t),[".ts",".tsx"]);if(r.length<=5)return{name:i,status:"PASS",message:"Project is small \u2014 domains/ not yet required"};if(!R.existsSync(s))return{name:i,status:"FAIL",message:`${r.length} files in ${t}/ but no domains/ directory`,evidence:["Create src/domains/<domain>/<slice>/ for business logic"]};let n=R.readdirSync(s,{withFileTypes:!0}).filter(o=>o.isDirectory());return n.length===0?{name:i,status:"WARN",message:"domains/ exists but is empty"}:{name:i,status:"PASS",message:`${n.length} domain${n.length>1?"s":""} found`}}async function an(e,t){let i="No cross-domain imports";if(!t)return{name:i,status:"SKIP",message:"No src/ directory found"};let s=I.join(e,t,"domains");if(!R.existsSync(s))return{name:i,status:"PASS",message:"No domains yet"};let r=R.readdirSync(s,{withFileTypes:!0}).filter(o=>o.isDirectory()).map(o=>o.name),n=[];for(let o of r){let a=I.join(s,o),c=X(a,[".ts",".tsx",".js",".jsx"]),l=r.filter(d=>d!==o);for(let d of c){let f=R.readFileSync(d,"utf-8");for(let m of l)new RegExp(`from.*domains/${m}|import.*domains/${m}`).test(f)&&n.push(`${I.relative(e,d)} \u2192 imports from ${m}/`)}}return n.length>0?{name:i,status:"FAIL",message:`${n.length} cross-domain import${n.length>1?"s":""} found`,evidence:n}:{name:i,status:"PASS",message:""}}async function cn(e,t){let i="Pages are thin wrappers";if(!t)return{name:i,status:"SKIP",message:"No src/ directory found"};let s=I.join(e,t,"pages");if(!R.existsSync(s))return{name:i,status:"PASS",message:"No pages/ directory (Next.js App Router or no pages)"};let r=X(s,[".tsx",".jsx"]),n=[];for(let o of r){let c=R.readFileSync(o,"utf-8").split(`
23
+ `).length;c>80&&n.push(`${I.relative(e,o)} (${c} lines)`)}return n.length>0?{name:i,status:"FAIL",message:`${n.length} page${n.length>1?"s":""} over 80 lines`,evidence:n}:{name:i,status:"PASS",message:""}}async function ln(e,t){let i="shared/ has no business logic";if(!t)return{name:i,status:"SKIP",message:"No src/ directory found"};let s=I.join(e,t,"shared");if(!R.existsSync(s))return{name:i,status:"PASS",message:"No shared/ directory yet"};let r=/TaskList|TaskForm|PricingCard|AdminUser|LoginForm|RegisterForm/,n=X(s,[".ts",".tsx",".js",".jsx"]),o=[];for(let a of n){let c=R.readFileSync(a,"utf-8");r.test(c)&&o.push(I.relative(e,a))}return o.length>0?{name:i,status:"FAIL",message:`Business components found in shared/ (${o.length} file${o.length>1?"s":""})`,evidence:o}:{name:i,status:"PASS",message:""}}import ue from"fs";import Zs from"path";var un="\x1B[32m",dn="\x1B[33m",pn="\x1B[36m",ei="\x1B[1m",$e="\x1B[2m",O="\x1B[0m";async function ti(e){let t=Zs.resolve(e);console.log(""),console.log(` ${pn}\u{1F504} Upgrading ASA Guard rules...${O}`),console.log("");let i=[{path:".asa/rules/architecture.md",content:ce},{path:".github/workflows/asa-guard.yml",content:le},{path:"check-structure.sh",content:Z,executable:!0}],s=0;for(let r of i){let n=Zs.join(t,r.path);if(!ue.existsSync(n)){console.log(` ${dn}\u23ED ${r.path}${O} ${$e}(not found \u2014 run guard init first)${O}`);continue}if(ue.readFileSync(n,"utf-8")===r.content){console.log(` ${$e}\u2713 ${r.path} (already up to date)${O}`);continue}ue.writeFileSync(n,r.content,"utf-8"),r.executable&&ue.chmodSync(n,493),console.log(` ${un}\u2713 ${r.path}${O} ${ei}updated${O}`),s++}console.log(""),s>0?console.log(` ${ei}${s} file${s>1?"s":""} updated.${O} Commit the changes.`):console.log(` ${$e}All rules are already up to date.${O}`),console.log("")}import N from"fs";import C from"path";import{fileURLToPath as fn}from"url";var si="\x1B[36m",te="\x1B[32m",ii="\x1B[33m",Ae="\x1B[31m",q="\x1B[1m",P="\x1B[2m",A="\x1B[0m",mn={auth:["db_basic","auth_basic"],payments:["db_basic","payments_basic"],admin:["db_basic","admin_basic"],foundation:["db_basic","auth_basic","payments_basic","admin_basic"]},ni={auth:"Auth \u2014 login, register, sessions, middleware, RLS migrations",payments:"Payments \u2014 Stripe checkout, webhooks, subscriptions, entitlements",admin:"Admin \u2014 RBAC roles, audit log, user management, impersonation",foundation:"Foundation \u2014 Auth + Payments + Admin + DB utilities"};function gn(){let e=C.dirname(fn(import.meta.url)),t=[C.join(e,"foundation"),C.join(e,"..","foundation"),C.join(e,"..","src","foundation")];for(let i of t)if(N.existsSync(i))return i;return t[0]}function ri(e){let t=C.join(e,"manifest.json");if(!N.existsSync(t))return null;try{return JSON.parse(N.readFileSync(t,"utf-8"))}catch{return null}}function de(e,t,i){let s=C.dirname(t);return N.existsSync(s)||N.mkdirSync(s,{recursive:!0}),N.existsSync(t)?i?(N.copyFileSync(e,t),"overwritten"):"skipped":(N.copyFileSync(e,t),"created")}function hn(e,t,i){let s=C.join(i,e),r=ri(s);if(!r)return console.log(` ${Ae}\u2717 No manifest found for ${e}${A}`),{created:0,skipped:0,notes:[]};let n=0,o=0;for(let a of r.shared||[]){let c=C.join(s,a.src),l=C.join(t,a.dest);if(!N.existsSync(c))continue;let d=de(c,l,a.overwrite);d==="created"||d==="overwritten"?(console.log(` ${te}+${A} ${a.dest}`),n++):(console.log(` ${P}~ ${a.dest} (exists, kept)${A}`),o++)}for(let a of r.slices||[]){let c=C.join(s,"slices",a.name.replace("-","_")),l=C.join(t,"domains",a.domain,a.name);for(let d of["handler.ts","repository.ts","schemas.ts","slice.contract.json"]){let f=C.join(c,d);if(!N.existsSync(f))continue;let m=C.join(l,d);de(f,m,!1)==="created"?(console.log(` ${te}+${A} domains/${a.domain}/${a.name}/${d}`),n++):(console.log(` ${P}~ domains/${a.domain}/${a.name}/${d} (exists, kept)${A}`),o++)}if(a.has_ui){let d=C.join(c,"ui"),f=C.join(l,"ui");if(N.existsSync(d))for(let m of N.readdirSync(d)){let p=C.join(d,m),S=C.join(f,m);de(p,S,!1)==="created"?(console.log(` ${te}+${A} domains/${a.domain}/${a.name}/ui/${m}`),n++):(console.log(` ${P}~ domains/${a.domain}/${a.name}/ui/${m} (exists, kept)${A}`),o++)}}}for(let a of r.migrations||[]){let c=C.join(s,a.src),l=C.join(t,a.dest);if(!N.existsSync(c))continue;de(c,l,!1)==="created"?(console.log(` ${te}+${A} ${a.dest}`),n++):(console.log(` ${P}~ ${a.dest} (exists, kept)${A}`),o++)}return{created:n,skipped:o,notes:r.post_install_notes||[]}}async function oi(e,t){let i=C.resolve(t),s=mn[e];if(!s){console.log(""),console.log(` ${Ae}Unknown module: ${e}${A}`),console.log(""),console.log(` ${q}Available modules:${A}`);for(let[d,f]of Object.entries(ni))console.log(` ${si}install ${d}${A}`),console.log(` ${P}${f}${A}`);console.log(""),process.exit(1)}let r=gn();N.existsSync(r)||(console.log(""),console.log(` ${Ae}Foundation modules not found at: ${r}${A}`),console.log(` ${P}This is likely a bug \u2014 please report it at vibecodiq.com${A}`),console.log(""),process.exit(1)),console.log(""),console.log(` ${si}${q}Vibecodiq Foundation${A} \u2014 Installing ${q}${e}${A}`),console.log(` ${P}${ni[e]}${A}`),console.log(` ${P}Target: ${i}${A}`),console.log("");let n=new Set,o=[],a=0,c=0;for(let d of s){if(n.has(d))continue;n.add(d);let f=d.replace("_basic","").replace("_"," ");console.log(` ${q}${f.charAt(0).toUpperCase()+f.slice(1)}${A}`);let{created:m,skipped:p,notes:S}=hn(d,i,r);a+=m,c+=p,o.push(...S),console.log("")}console.log(` ${P}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${A}`),console.log(` ${te}${q}${a} files installed${A} ${P}(${c} already existed)${A}`),console.log("");let l=s.map(d=>ri(C.join(r,d))?.package_dependencies??{}).reduce((d,f)=>({...d,...f}),{});if(Object.keys(l).length>0){console.log(` ${ii}${q}Required npm packages:${A}`);let d=Object.entries(l).map(([f,m])=>`${f}@${m}`).join(" ");console.log(` ${P} npm install ${d}${A}`),console.log(` ${P} # or: pnpm add ${d}${A}`),console.log("")}if(o.length>0){let d=[...new Set(o)];console.log(` ${ii}${q}Next steps:${A}`);for(let f of d)console.log(` ${P} \u2022 ${f}${A}`);console.log("")}console.log(` ${P}Run a full scan to verify:${A}`),console.log(` ${P} npx @vibecodiq/cli scan --all${A}`),console.log(` ${P} npx @vibecodiq/cli guard init --all${A}`),console.log("")}function yn(e,t){if(e.startsWith(t))return e.slice(t.length).replace(/^[/\\]/,"");let i=/^\/(?:home|Users)\/[^/]+\//;if(i.test(e))return e.replace(i,"");let s=/^[A-Z]:\\Users\\[^\\]+\\/i;return s.test(e)?e.replace(s,""):e}function Sn(e,t){return e.map(i=>i.replace(/(?:\/(?:home|Users)\/[^\s:]+|[A-Z]:\\Users\\[^\s:]+)/g,s=>yn(s,t)))}function be(e,t,i,s,r){let n=e.map(o=>({check_id:o.id,result:o.result,name:o.name,module:o.module,layer:o.layer,priority:o.priority,category:o.category,message:o.message,evidence:o.evidence?Sn(o.evidence,t):void 0}));return{cli_version:i,scan_mode:s,project_name:r,timestamp:new Date().toISOString(),findings:n}}import{readFileSync as $n}from"fs";import{join as An}from"path";import{homedir as bn}from"os";var ai=process.env.VIBECODIQ_API_URL||"https://api.vibecodiq.com",ci=1e4,J=1;async function li(e,t,i){let s=new AbortController,r=setTimeout(()=>s.abort(),i);try{return await fetch(e,{...t,signal:s.signal})}finally{clearTimeout(r)}}async function Ce(e,t){for(let i=0;i<=J;i++)try{let s=await li(`${ai}/scan`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`},body:JSON.stringify(e)},ci);if(s.ok)return await s.json();if(s.status===401){let r=await s.json().catch(()=>({detail:"Unauthorized"}));throw new K(r.detail??"Invalid API token")}if(s.status>=500&&i<J)continue;return null}catch(s){if(s instanceof K)throw s;if(i<J)continue;return null}return null}async function ve(e){for(let t=0;t<=J;t++)try{let i=await li(`${ai}/scan/free`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)},ci);if(i.ok)return await i.json();if(i.status>=500&&t<J)continue;return null}catch{if(t<J)continue;return null}return null}var K=class extends Error{constructor(t){super(t),this.name="AuthError"}};function ke(){let e=process.env.VIBECODIQ_API_TOKEN;if(e)return e;try{let t=An(bn(),".vibecodiq","token"),i=$n(t,"utf-8").trim();if(i)return i}catch{}return null}var vn="533f2e11ca9bf0419a066f73ff9cc910e23a39053fcb3051eb03f9521794e8db",pe="0.6.0",D="\x1B[1m",b="\x1B[2m",W="\x1B[36m",_e="\x1B[33m",y="\x1B[0m",j=process.argv.slice(2),G=j[0],Ie=j[1];function se(e){for(let t of e.slice(1))if(!t.startsWith("-"))return t;return"."}function B(e){return j.includes(e)}if(G==="scan"){let e=B("--fix"),t=se(j),i=B("--json"),s=!1;if(B("--unreleased")){let m=process.env.VIBECODIQ_INTERNAL_KEY||"";Cn("sha256").update(m).digest("hex")!==vn&&(console.log(""),console.log(` ${_e}\u26A0 --unreleased requires VIBECODIQ_INTERNAL_KEY environment variable.${y}`),console.log(` ${b}This flag is restricted to authorized internal use.${y}`),console.log(""),process.exit(1)),s=!0}let r="trust-score";B("--architecture")&&(r="architecture"),B("--all")&&(r="all");let n=r==="trust-score"?"Safety Scan":r==="architecture"?"Architecture Scan":"Full Scan";i||(console.log(""),console.log(` ${W}\u23F3 Scanning project \u2014 ${n}...${y}`));let o=await Ds(t,{mode:r,allChecks:s}),a=o.checks.some(m=>m.result==="FAIL");if(e){let m=ke();m||(console.log(""),console.log(` ${_e}\u26A0 No API token found.${y}`),console.log(""),console.log(" Set your token via:"),console.log(` ${W}export VIBECODIQ_API_TOKEN=<your-token>${y}`),console.log(" Or save to file:"),console.log(` ${W}mkdir -p ~/.vibecodiq && echo "<your-token>" > ~/.vibecodiq/token${y}`),console.log(""),console.log(` ${b}Get your token at: https://vibecodiq.com/pricing${y}`),console.log(""),process.exit(1)),i||console.log(` ${W}\u23F3 Sending sanitized findings to API...${y}`);let p=be(o.checks,t.startsWith("/")?t:process.cwd(),pe,r,o.project);try{let S=await Ce(p,m);S?i?console.log(JSON.stringify({...o,...S},null,2)):(Ks(o,S),S.share_url&&(console.log(""),console.log(` ${b}\u{1F4CB} Full report: ${W}${S.share_url}${y}`))):i?ae(o):(oe(o),ye())}catch(S){S instanceof K&&(console.log(""),console.log(` ${_e}\u26A0 API authentication failed: ${S.message}${y}`),console.log(` ${b}Check your VIBECODIQ_API_TOKEN.${y}`),console.log(""),process.exit(1)),i?ae(o):(oe(o),ye())}process.exit(a?1:0)}let c=B("--verbose");i?ae(o):c?oe(o):qs(o);let l=be(o.checks,t.startsWith("/")?t:process.cwd(),pe,r,o.project),d=ke(),f;try{if(d)try{f=(await Ce(l,d))?.share_url??void 0}catch(m){if(m instanceof K)f=(await ve(l))?.share_url??void 0;else throw m}else f=(await ve(l))?.share_url??void 0}catch{}f&&!i&&(console.log(""),console.log(` ${b}\u{1F4CB} Full report: ${W}${f}${y}`)),process.exit(a?1:0)}else if(G==="guard")if(Ie==="init"){let e=se(j.slice(1)),t=B("--architecture"),i=B("--all");await Vs(e,t||i)}else if(Ie==="check"){let e=se(j.slice(1));await Qs(e)}else if(Ie==="upgrade"){let e=se(j.slice(1));await ti(e)}else console.log(""),console.log(` ${D}@vibecodiq/cli guard${y} \u2014 Architecture rules & CI enforcement`),console.log(""),console.log(` ${D}Commands:${y}`),console.log(" guard init Install safety rules + CI into your repo"),console.log(" guard init --architecture + ASA architecture rules"),console.log(" guard init --all Install everything"),console.log(" guard check Run architecture checks locally"),console.log(" guard upgrade Upgrade rules to latest version"),console.log(""),console.log(` ${b}Learn more: https://vibecodiq.com/guard${y}`),console.log("");else if(G==="install"){let e=j[1],t=se(j.slice(1));(!e||e.startsWith("-"))&&(console.log(""),console.log(` ${D}@vibecodiq/cli install${y} \u2014 Vibecodiq Foundation modules`),console.log(""),console.log(` ${D}Usage:${y}`),console.log(" npx @vibecodiq/cli install auth"),console.log(" npx @vibecodiq/cli install payments"),console.log(" npx @vibecodiq/cli install admin"),console.log(` npx @vibecodiq/cli install foundation ${b}(all three)${y}`),console.log(""),console.log(` ${D}Modules:${y}`),console.log(` auth ${b}Login, register, sessions, server-side auth, RLS${y}`),console.log(` payments ${b}Stripe checkout, webhooks, subscriptions, entitlements${y}`),console.log(` admin ${b}RBAC roles, audit log, user management, impersonation${y}`),console.log(` foundation ${b}All three modules + DB utilities${y}`),console.log(""),console.log(` ${b}ASA-certified modules \u2014 pass 100% of Phase 1 checks out of the box.${y}`),console.log(` ${b}Learn more: https://vibecodiq.com/foundation${y}`),console.log(""),process.exit(0)),await oi(e,t)}else if(G==="login"){let e=j[1];e||(console.log(""),console.log(` ${D}@vibecodiq/cli login${y} \u2014 Save your API token`),console.log(""),console.log(` ${D}Usage:${y}`),console.log(" npx @vibecodiq/cli login <your-token>"),console.log(""),console.log(` ${b}Your token is saved to ~/.vibecodiq/token${y}`),console.log(` ${b}Get your token at: https://vibecodiq.com/pricing${y}`),console.log(""),process.exit(1));let{mkdirSync:t,writeFileSync:i}=await import("fs"),{join:s}=await import("path"),{homedir:r}=await import("os"),n=s(r(),".vibecodiq"),o=s(n,"token");t(n,{recursive:!0}),i(o,e.trim()+`
24
+ `,{mode:384}),console.log(""),console.log(` ${W}\u2713${y} Token saved to ${b}${o}${y}`),console.log(` ${b}Run: npx @vibecodiq/cli scan --fix${y}`),console.log("")}else if(G==="logout"){let{unlinkSync:e,existsSync:t}=await import("fs"),{join:i}=await import("path"),{homedir:s}=await import("os"),r=i(s(),".vibecodiq","token");t(r)?(e(r),console.log(""),console.log(` ${W}\u2713${y} Token removed.`),console.log("")):(console.log(""),console.log(` ${b}No token found. Already logged out.${y}`),console.log(""))}else G==="--version"||G==="-v"?console.log(`@vibecodiq/cli v${pe}`):(console.log(""),console.log(` ${D}@vibecodiq/cli${y} v${pe} \u2014 Production safety for AI-built apps`),console.log(""),console.log(` ${D}Scan${y} ${b}(check what you already have)${y}`),console.log(" scan [path] Safety scan \u2014 Phase 1 (L2+L3+L4)"),console.log(" scan --architecture Architecture scan \u2014 Phase 1 (L1)"),console.log(" scan --all Full scan \u2014 Phase 1 (all layers)"),console.log(` scan --unreleased Run all checks incl. unreleased ${b}(internal)${y}`),console.log(" scan --json JSON output for CI pipelines"),console.log(` scan --fix AI fix prompts for failures ${b}(Pro)${y}`),console.log(""),console.log(` ${D}Guard${y} ${b}(build safely from day one)${y}`),console.log(" guard init Install safety rules + CI"),console.log(" guard init --architecture + ASA architecture rules"),console.log(" guard init --all Install everything"),console.log(" guard check Run architecture checks locally"),console.log(" guard upgrade Upgrade rules to latest"),console.log(""),console.log(` ${D}Foundation${y} ${b}(install safe modules into your app)${y}`),console.log(" install auth Auth module \u2014 login, register, sessions"),console.log(" install payments Payments module \u2014 Stripe + webhooks"),console.log(" install admin Admin module \u2014 RBAC, audit log"),console.log(" install foundation All three modules + DB utilities"),console.log(""),console.log(` ${D}Account${y}`),console.log(" login Authenticate for Pro features"),console.log(" logout Sign out"),console.log(" --version Show version"),console.log(""),console.log(` ${b}Docs: https://asastandard.org/checks${y}`),console.log(` ${b}Pricing: https://vibecodiq.com/pricing${y}`),console.log(""));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecodiq/cli",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Production safety scanner & architecture guard for AI-built apps",
5
5
  "type": "module",
6
6
  "bin": {