@viberaven/cli 1.5.2 → 1.6.0

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.
package/dist/cli.js CHANGED
@@ -6970,7 +6970,7 @@ var init_mockSupabaseVerifier = __esm({
6970
6970
  providerObservationMet: false,
6971
6971
  fixType: "mcp-connect",
6972
6972
  severity: tables.length > 0 ? "critical" : "info",
6973
- repoSignals: tables.map((table) => `table referenced: ${table}`),
6973
+ repoSignals: tables.map((table2) => `table referenced: ${table2}`),
6974
6974
  providerSignals: ["Supabase policy list API or MCP"],
6975
6975
  requiredEvidence: ["Policies exist for tables used in application code"]
6976
6976
  }),
@@ -6986,7 +6986,7 @@ var init_mockSupabaseVerifier = __esm({
6986
6986
  providerObservationMet: false,
6987
6987
  fixType: "provider-config",
6988
6988
  severity: "warning",
6989
- repoSignals: tables.map((table) => `.from("${table}")`),
6989
+ repoSignals: tables.map((table2) => `.from("${table2}")`),
6990
6990
  providerSignals: ["Live table policy map"],
6991
6991
  requiredEvidence: ["Each referenced public table has RLS + policy in production"]
6992
6992
  })
@@ -7008,7 +7008,7 @@ var init_mockSupabaseVerifier = __esm({
7008
7008
  providerActual: "Live RLS state unknown without Supabase MCP",
7009
7009
  severity: "critical",
7010
7010
  suggestedFix: "repo-fix",
7011
- evidenceRefs: tables.map((table) => `table: ${table}`)
7011
+ evidenceRefs: tables.map((table2) => `table: ${table2}`)
7012
7012
  });
7013
7013
  }
7014
7014
  if (rlsInRepo) {
@@ -8303,7 +8303,7 @@ function replayProject(files, root) {
8303
8303
  }
8304
8304
  }
8305
8305
  const replayed = [...tables.values()];
8306
- for (const table of replayed) table.root = root;
8306
+ for (const table2 of replayed) table2.root = root;
8307
8307
  return { tables: replayed, functions: [...functions.values()], statementsSeen };
8308
8308
  }
8309
8309
  function isSqlTestPath(path) {
@@ -8574,38 +8574,38 @@ function analyzeRls(replay) {
8574
8574
  const noPolicies = [];
8575
8575
  const permissive = [];
8576
8576
  const rlsEnabledEvidence = [];
8577
- for (const table of replay.tables) {
8578
- if (table.schema !== "public") continue;
8579
- if (table.rls) {
8580
- rlsEnabledEvidence.push(`${at(table.rlsLoc)}: row level security enabled on ${safe(table.display)}`);
8577
+ for (const table2 of replay.tables) {
8578
+ if (table2.schema !== "public") continue;
8579
+ if (table2.rls) {
8580
+ rlsEnabledEvidence.push(`${at(table2.rlsLoc)}: row level security enabled on ${safe(table2.display)}`);
8581
8581
  }
8582
- if (!table.rls && (table.created || table.disabledLoc)) {
8583
- const where = table.disabledLoc ?? table.arrivedLoc ?? table.created;
8582
+ if (!table2.rls && (table2.created || table2.disabledLoc)) {
8583
+ const where = table2.disabledLoc ?? table2.arrivedLoc ?? table2.created;
8584
8584
  const risk = "Anyone holding the anon key your frontend ships can read and change every row through the Data API (unless the table's grants were revoked).";
8585
- const message = table.disabledLoc ? `${table.display} has row level security turned off at ${at(table.disabledLoc)}. ${risk}` : table.arrivedLoc ? `${table.display} is moved into public at ${at(table.arrivedLoc)} without row level security, and no later migration enables it. ${risk}` : `${table.display} is created at ${at(table.created)} and no migration enables row level security on it. ${risk}`;
8585
+ const message = table2.disabledLoc ? `${table2.display} has row level security turned off at ${at(table2.disabledLoc)}. ${risk}` : table2.arrivedLoc ? `${table2.display} is moved into public at ${at(table2.arrivedLoc)} without row level security, and no later migration enables it. ${risk}` : `${table2.display} is created at ${at(table2.created)} and no migration enables row level security on it. ${risk}`;
8586
8586
  missingRls.push({
8587
8587
  id: "rls_disabled",
8588
8588
  severity: "critical",
8589
8589
  message: safe(message),
8590
8590
  file: where?.file,
8591
8591
  line: where?.line,
8592
- table: table.display,
8593
- fix: `alter table ${table.display} enable row level security;`
8592
+ table: table2.display,
8593
+ fix: `alter table ${table2.display} enable row level security;`
8594
8594
  });
8595
8595
  }
8596
- if (table.rls && table.policies.size === 0) {
8596
+ if (table2.rls && table2.policies.size === 0) {
8597
8597
  noPolicies.push({
8598
8598
  id: "rls_no_policies",
8599
8599
  severity: "info",
8600
8600
  message: safe(
8601
- `${table.display} has row level security on and no policies, so it is locked to the service role: the browser client gets no rows back and cannot write. Fine for server-only tables; add policies if the app reads it from the frontend.`
8601
+ `${table2.display} has row level security on and no policies, so it is locked to the service role: the browser client gets no rows back and cannot write. Fine for server-only tables; add policies if the app reads it from the frontend.`
8602
8602
  ),
8603
- file: table.rlsLoc?.file,
8604
- line: table.rlsLoc?.line,
8605
- table: table.display
8603
+ file: table2.rlsLoc?.file,
8604
+ line: table2.rlsLoc?.line,
8605
+ table: table2.display
8606
8606
  });
8607
8607
  }
8608
- for (const policy of table.policies.values()) {
8608
+ for (const policy of table2.policies.values()) {
8609
8609
  if (!policy.permissive) continue;
8610
8610
  const roles = policy.roles.filter((r) => r === "public" || r === "anon" || r === "authenticated");
8611
8611
  if (roles.length === 0) continue;
@@ -8618,11 +8618,11 @@ function analyzeRls(replay) {
8618
8618
  id: "rls_policy_allows_all_read",
8619
8619
  severity: "high",
8620
8620
  message: safe(
8621
- `Policy "${policy.name}" on ${table.display} (${at(policy.loc)}) lets ${who} read every row: using (true). Fine for truly public content; wrong for anything per-user.`
8621
+ `Policy "${policy.name}" on ${table2.display} (${at(policy.loc)}) lets ${who} read every row: using (true). Fine for truly public content; wrong for anything per-user.`
8622
8622
  ),
8623
8623
  file: policy.loc.file,
8624
8624
  line: policy.loc.line,
8625
- table: table.display,
8625
+ table: table2.display,
8626
8626
  policy: policy.name
8627
8627
  });
8628
8628
  continue;
@@ -8650,10 +8650,10 @@ function analyzeRls(replay) {
8650
8650
  permissive.push({
8651
8651
  id: "rls_policy_allows_all_write",
8652
8652
  severity,
8653
- message: safe(`Policy "${policy.name}" on ${table.display} (${at(policy.loc)}) lets ${who} ${verb}: ${clause}.`),
8653
+ message: safe(`Policy "${policy.name}" on ${table2.display} (${at(policy.loc)}) lets ${who} ${verb}: ${clause}.`),
8654
8654
  file: policy.loc.file,
8655
8655
  line: policy.loc.line,
8656
- table: table.display,
8656
+ table: table2.display,
8657
8657
  policy: policy.name
8658
8658
  });
8659
8659
  }
@@ -8733,35 +8733,35 @@ function analyzeGrants(replay, uses, autoExpose) {
8733
8733
  if (existing) use.ops.forEach((op) => existing.ops.add(op));
8734
8734
  else byName.set(use.table, { ...use, ops: new Set(use.ops) });
8735
8735
  }
8736
- for (const table of replay.tables) {
8737
- if (table.schema !== "public") continue;
8738
- const granted = table.grants.size > 0;
8739
- if (granted && !table.rls) {
8740
- const roles2 = [...new Set([...table.grants].map((g2) => g2.split(":")[0]))].join(" and ");
8736
+ for (const table2 of replay.tables) {
8737
+ if (table2.schema !== "public") continue;
8738
+ const granted = table2.grants.size > 0;
8739
+ if (granted && !table2.rls) {
8740
+ const roles2 = [...new Set([...table2.grants].map((g2) => g2.split(":")[0]))].join(" and ");
8741
8741
  out.push({
8742
8742
  id: "data_api_grant_without_rls",
8743
8743
  severity: "critical",
8744
8744
  message: safe(
8745
- `${table.display} is granted to ${roles2} (${at(table.grantLoc)}) but row level security is off. A grant without RLS is the dangerous combination: the Data API serves every row to that role. Enable RLS and add policies before granting.`
8745
+ `${table2.display} is granted to ${roles2} (${at(table2.grantLoc)}) but row level security is off. A grant without RLS is the dangerous combination: the Data API serves every row to that role. Enable RLS and add policies before granting.`
8746
8746
  ),
8747
- file: table.grantLoc?.file,
8748
- line: table.grantLoc?.line,
8749
- table: table.display,
8750
- fix: `alter table ${table.display} enable row level security;`
8747
+ file: table2.grantLoc?.file,
8748
+ line: table2.grantLoc?.line,
8749
+ table: table2.display,
8750
+ fix: `alter table ${table2.display} enable row level security;`
8751
8751
  });
8752
8752
  continue;
8753
8753
  }
8754
- if (!table.created || !table.rls) continue;
8754
+ if (!table2.created || !table2.rls) continue;
8755
8755
  const configOff = autoExpose && !autoExpose.value ? autoExpose : void 0;
8756
- const revokedAtCreate = table.defaultsRevoked ?? /* @__PURE__ */ new Set();
8756
+ const revokedAtCreate = table2.defaultsRevoked ?? /* @__PURE__ */ new Set();
8757
8757
  if (revokedAtCreate.size === 0 && !configOff) continue;
8758
- const use = byName.get(table.name);
8758
+ const use = byName.get(table2.name);
8759
8759
  if (!use) continue;
8760
- const lacks = (role, priv) => !table.grants.has(`${role}:${priv}`) && (configOff !== void 0 || revokedAtCreate.has(`${role}:${priv}`));
8760
+ const lacks = (role, priv) => !table2.grants.has(`${role}:${priv}`) && (configOff !== void 0 || revokedAtCreate.has(`${role}:${priv}`));
8761
8761
  const rolesFor = (priv) => {
8762
- if (!table.rls) return [...DATA_API_ROLES];
8762
+ if (!table2.rls) return [...DATA_API_ROLES];
8763
8763
  return expandRoles(
8764
- [...table.policies.values()].filter((p2) => p2.permissive && (p2.command === priv || p2.command === "all")).flatMap((p2) => p2.roles)
8764
+ [...table2.policies.values()].filter((p2) => p2.permissive && (p2.command === priv || p2.command === "all")).flatMap((p2) => p2.roles)
8765
8765
  );
8766
8766
  };
8767
8767
  const blocked = /* @__PURE__ */ new Map();
@@ -8773,17 +8773,17 @@ function analyzeGrants(replay, uses, autoExpose) {
8773
8773
  if (blocked.size === 0) continue;
8774
8774
  const privs = [...blocked.keys()];
8775
8775
  const roles = [...new Set([...blocked.values()].flat())];
8776
- const reason = configOff ? `${configOff.file}:${configOff.line} sets auto_expose_new_tables = false, so new public tables get no default grants` : `the default grants for new public tables are revoked at ${at(table.defaultsRevokedLoc)}`;
8777
- const fix = `grant ${privs.join(", ")} on table ${table.display} to ${roles.join(", ")};`;
8776
+ const reason = configOff ? `${configOff.file}:${configOff.line} sets auto_expose_new_tables = false, so new public tables get no default grants` : `the default grants for new public tables are revoked at ${at(table2.defaultsRevokedLoc)}`;
8777
+ const fix = `grant ${privs.join(", ")} on table ${table2.display} to ${roles.join(", ")};`;
8778
8778
  out.push({
8779
8779
  id: "data_api_grant_missing",
8780
8780
  severity: "warning",
8781
8781
  message: safe(
8782
- `${table.display} is used from the client (${use.file}:${use.line}) for ${privs.join(", ")}, but ${roles.join(" and ")} ${roles.length === 1 ? "has" : "have"} no ${privs.join("/")} grant on it: ${reason}, and no migration grants it back. Those calls get "permission denied for table ${table.name}". Add to a migration: ${fix}`
8782
+ `${table2.display} is used from the client (${use.file}:${use.line}) for ${privs.join(", ")}, but ${roles.join(" and ")} ${roles.length === 1 ? "has" : "have"} no ${privs.join("/")} grant on it: ${reason}, and no migration grants it back. Those calls get "permission denied for table ${table2.name}". Add to a migration: ${fix}`
8783
8783
  ),
8784
- file: table.created.file,
8785
- line: table.created.line,
8786
- table: table.display,
8784
+ file: table2.created.file,
8785
+ line: table2.created.line,
8786
+ table: table2.display,
8787
8787
  fix
8788
8788
  });
8789
8789
  }
@@ -10291,10 +10291,10 @@ async function listTrackedIgnoredFiles(workspacePath) {
10291
10291
  return { paths: [], incomplete: false };
10292
10292
  }
10293
10293
  try {
10294
- const git = await safeGitInvocation(workspacePath, ["ls-files", "-c", "-i", "--exclude-standard", "-z"]);
10295
- const { stdout } = await execFileAsync2("git", git.args, {
10294
+ const git2 = await safeGitInvocation(workspacePath, ["ls-files", "-c", "-i", "--exclude-standard", "-z"]);
10295
+ const { stdout } = await execFileAsync2("git", git2.args, {
10296
10296
  cwd: workspacePath,
10297
- env: git.env,
10297
+ env: git2.env,
10298
10298
  timeout: GIT_TIMEOUT_MS,
10299
10299
  maxBuffer: GIT_MAX_OUTPUT_BYTES,
10300
10300
  windowsHide: true
@@ -10318,14 +10318,14 @@ async function collectDatabaseEvidence(workspacePath, scan) {
10318
10318
  [...scan.fileTree.split("\n"), ...scan.secretsFound, ...walk.listed, ...trackedIgnored.paths].map((p2) => normalizedPath(p2.trim()))
10319
10319
  )
10320
10320
  ].filter((p2) => p2.length > 0 && !isOwnArtifact(p2) && !fullyLoaded.has(p2));
10321
- const wanted = [
10321
+ const wanted2 = [
10322
10322
  ...candidates.filter((p2) => EVIDENCE_MANIFEST.test(p2)),
10323
10323
  ...candidates.filter((p2) => !EVIDENCE_MANIFEST.test(p2) && EVIDENCE_CODE.test(p2))
10324
10324
  ];
10325
- if (wanted.length > EVIDENCE_MAX_FILES) incomplete = true;
10325
+ if (wanted2.length > EVIDENCE_MAX_FILES) incomplete = true;
10326
10326
  const files = [];
10327
10327
  let bytesRead = 0;
10328
- for (const path of wanted.slice(0, EVIDENCE_MAX_FILES)) {
10328
+ for (const path of wanted2.slice(0, EVIDENCE_MAX_FILES)) {
10329
10329
  if (bytesRead >= EVIDENCE_MAX_TOTAL_BYTES) {
10330
10330
  incomplete = true;
10331
10331
  break;
@@ -10605,6 +10605,25 @@ function assessStripeWebhooks(files) {
10605
10605
  }
10606
10606
  return { verified: verified.sort(), unverified: unverified.sort((a, b3) => a.path < b3.path ? -1 : 1) };
10607
10607
  }
10608
+ function stripeUsageEvidence(scan, files) {
10609
+ const dependency = (scan.packageDeps ?? []).find((dep) => {
10610
+ const name = dep.toLowerCase();
10611
+ return name === "stripe" || name.startsWith("@stripe/");
10612
+ });
10613
+ if (dependency) return { kind: "dependency", dependency };
10614
+ const sorted = [...files].sort((a, b3) => a.path < b3.path ? -1 : 1);
10615
+ for (const file of sorted) {
10616
+ const path = file.path.replace(/\\/g, "/");
10617
+ if (!CODE_FILE.test(path) || TEST_FILE.test(path)) continue;
10618
+ const match = stripComments2(file.content).match(STRIPE_CODE_USE);
10619
+ if (match) return { kind: "code", path, signal: match[0] };
10620
+ }
10621
+ return void 0;
10622
+ }
10623
+ function stripeCodeOnlyInEdgeFunctions(files) {
10624
+ const users = files.map((file) => ({ path: file.path.replace(/\\/g, "/"), content: file.content })).filter((file) => CODE_FILE.test(file.path) && !TEST_FILE.test(file.path) && STRIPE_CODE_USE.test(stripComments2(file.content)));
10625
+ return users.length > 0 && users.every((file) => /(^|\/)supabase\/functions\//.test(file.path));
10626
+ }
10608
10627
  function codeFilesFor(scan, fullCopies = []) {
10609
10628
  const full = new Map(fullCopies.map((f) => [f.path.replace(/\\/g, "/"), f.content]));
10610
10629
  const merged = /* @__PURE__ */ new Map();
@@ -10650,8 +10669,8 @@ function noReleaseTagsSeed(commitCount) {
10650
10669
  }
10651
10670
  async function detectReleaseTagGap(workspacePath) {
10652
10671
  try {
10653
- const git = (args) => safeGitInvocation(workspacePath, args).then((call) => execFileAsync3("git", call.args, { cwd: workspacePath, env: call.env, windowsHide: true }));
10654
- const [tags, count] = await Promise.all([git(["tag", "--list"]), git(["rev-list", "--count", "HEAD"])]);
10672
+ const git2 = (args) => safeGitInvocation(workspacePath, args).then((call) => execFileAsync3("git", call.args, { cwd: workspacePath, env: call.env, windowsHide: true }));
10673
+ const [tags, count] = await Promise.all([git2(["tag", "--list"]), git2(["rev-list", "--count", "HEAD"])]);
10655
10674
  const tagCount = tags.stdout.split("\n").filter((line) => line.trim().length > 0).length;
10656
10675
  const commitCount = Number.parseInt(count.stdout.trim(), 10) || 0;
10657
10676
  if (tagCount === 0 && commitCount >= 20) {
@@ -10891,7 +10910,8 @@ ${check.evidence.map((e2) => `- ${e2}`).join("\n")}` : "";
10891
10910
  });
10892
10911
  }
10893
10912
  if (signals.hasStripe) {
10894
- const webhooks = assessStripeWebhooks(codeFilesFor(scan, extra.codeFiles));
10913
+ const stripeFiles = codeFilesFor(scan, extra.codeFiles);
10914
+ const webhooks = assessStripeWebhooks(stripeFiles);
10895
10915
  if (webhooks.unverified.length > 0) {
10896
10916
  const lines = webhooks.unverified.map((u2) => `${u2.path} (${u2.missing.join(", ")})`);
10897
10917
  seeds.push({
@@ -10904,15 +10924,20 @@ ${check.evidence.map((e2) => `- ${e2}`).join("\n")}` : "";
10904
10924
  primaryMapCategory: "payments"
10905
10925
  });
10906
10926
  } else if (webhooks.verified.length === 0) {
10907
- seeds.push({
10908
- id: "missing_stripe_webhook",
10909
- category: "MISSING FEATURES",
10910
- severity: "warning",
10911
- title: "Stripe installed but no webhook handler",
10912
- detail: "Stripe is a dependency but no webhook route or signature verification (constructEvent) was found. Payment state will silently drift from app state.",
10913
- copyPrompt: "Add a Stripe webhook route (e.g. app/api/webhooks/stripe/route.ts) that verifies the signature with stripe.webhooks.constructEvent and handles checkout.session.completed plus subscription lifecycle events.",
10914
- primaryMapCategory: "payments"
10915
- });
10927
+ const usage = stripeUsageEvidence(scan, stripeFiles);
10928
+ if (usage) {
10929
+ const seen = usage.kind === "dependency" ? `package.json lists ${usage.dependency}` : `${usage.path} uses Stripe (${usage.signal}) and package.json does not list stripe`;
10930
+ seeds.push({
10931
+ id: "missing_stripe_webhook",
10932
+ category: "MISSING FEATURES",
10933
+ severity: "warning",
10934
+ title: usage.kind === "dependency" ? "Stripe installed but no webhook handler" : "Stripe used but no webhook handler",
10935
+ detail: `${seen}, but no webhook route or signature verification (constructEvent) was found. Payment state will silently drift from app state.`,
10936
+ // Stripe code that only runs in Supabase Edge Functions is verified there, on Deno, not in a Next.js route.
10937
+ copyPrompt: stripeCodeOnlyInEdgeFunctions(stripeFiles) ? STRIPE_EDGE_WEBHOOK_PROMPT : (usage.kind === "code" ? "Add the stripe package, then add" : "Add") + " a Stripe webhook route (e.g. app/api/webhooks/stripe/route.ts) that verifies the signature with stripe.webhooks.constructEvent and handles checkout.session.completed plus subscription lifecycle events.",
10938
+ primaryMapCategory: "payments"
10939
+ });
10940
+ }
10916
10941
  }
10917
10942
  }
10918
10943
  {
@@ -11112,7 +11137,7 @@ async function buildLocalStationOutput(input) {
11112
11137
  productionChecklist: checklistFrom(seeds)
11113
11138
  };
11114
11139
  }
11115
- var import_node_child_process3, import_node_util3, import_promises6, import_node_path7, execFileAsync3, ENV_DRIFT_IGNORE, ENV_DRIFT_IGNORE_PREFIX, SUPABASE_EDGE_ENV, ENV_TEMPLATE_NAME, ENV_CODE_FILE, ENV_NAME, ENV_REFERENCES, SVELTEKIT_STATIC_IMPORT, SVELTEKIT_DYNAMIC_IMPORT, ENV_DESTRUCTURE, CODE_FILE, STRIPE_VERIFY_CALL, STRIPE_VERIFY_JSON_BODY, STRIPE_SIGNATURE_HEADER, RAW_BODY, STRIPE_EVENT_TYPE, REQUEST_HANDLER, TEST_FILE, AUDIT_FINDING_GAPS, FINDING_SEVERITY_RANK, DETAIL_MAX_FINDINGS, SEVERITY_ORDER, FULL_READ_LIMITS, PAYMENT_PATH;
11140
+ var import_node_child_process3, import_node_util3, import_promises6, import_node_path7, execFileAsync3, ENV_DRIFT_IGNORE, ENV_DRIFT_IGNORE_PREFIX, SUPABASE_EDGE_ENV, ENV_TEMPLATE_NAME, ENV_CODE_FILE, ENV_NAME, ENV_REFERENCES, SVELTEKIT_STATIC_IMPORT, SVELTEKIT_DYNAMIC_IMPORT, ENV_DESTRUCTURE, CODE_FILE, STRIPE_VERIFY_CALL, STRIPE_VERIFY_JSON_BODY, STRIPE_SIGNATURE_HEADER, RAW_BODY, STRIPE_EVENT_TYPE, REQUEST_HANDLER, TEST_FILE, STRIPE_CODE_USE, STRIPE_EDGE_WEBHOOK_PROMPT, AUDIT_FINDING_GAPS, FINDING_SEVERITY_RANK, DETAIL_MAX_FINDINGS, SEVERITY_ORDER, FULL_READ_LIMITS, PAYMENT_PATH;
11116
11141
  var init_localStation = __esm({
11117
11142
  "src/localStation.ts"() {
11118
11143
  "use strict";
@@ -11204,6 +11229,8 @@ var init_localStation = __esm({
11204
11229
  STRIPE_EVENT_TYPE = /['"`](?:checkout\.session|customer\.subscription|invoice|payment_intent|charge)\.[a-z_.]+['"`]/;
11205
11230
  REQUEST_HANDLER = /export\s+(?:async\s+)?function\s+POST\b|export\s+const\s+POST\b|export\s+default\s+(?:async\s+)?function\s*[\w$]*\s*\(\s*(?:req|request)\b|export\s+(?:async\s+function|function|const)\s+handler\b|\b[A-Za-z_$][\w$]*\.post\s*\(|Deno\.serve\s*\(|\bserve\s*\(|addEventListener\(\s*['"]fetch|export\s+default\s*\{\s*(?:async\s+)?fetch\b/;
11206
11231
  TEST_FILE = /(^|\/)(?:__tests__|__mocks__)\/|\.(?:test|spec)\.[cm]?[jt]sx?$/i;
11232
+ STRIPE_CODE_USE = /\bSTRIPE_(?:SECRET_KEY|WEBHOOK_SECRET)\b|\bapi\.stripe\.com\b|\bfrom\s*['"](?:npm:)?stripe(?:@[^'"]*)?['"]|\brequire\(\s*['"]stripe['"]\s*\)|\bimport\(\s*['"](?:npm:)?stripe(?:@[^'"]*)?['"]\s*\)/;
11233
+ STRIPE_EDGE_WEBHOOK_PROMPT = "In the Supabase Edge Function that receives Stripe events (add one under supabase/functions/ if there is none, e.g. supabase/functions/stripe-webhook/index.ts), read the raw body with await req.text(), read the Stripe-Signature header, and verify both with await stripe.webhooks.constructEventAsync(body, signature, Deno.env.get('STRIPE_WEBHOOK_SECRET'), undefined, Stripe.createSubtleCryptoProvider()) before trusting event.type. Return 400 when verification throws, and handle checkout.session.completed plus subscription lifecycle events.";
11207
11234
  AUDIT_FINDING_GAPS = {
11208
11235
  rls_policy_allows_all_write: {
11209
11236
  title: "A policy lets anyone write every row",
@@ -12391,7 +12418,81 @@ var init_agentSummary = __esm({
12391
12418
  }
12392
12419
  });
12393
12420
 
12421
+ // src/heal/rlsMigrationTarget.ts
12422
+ function rlsEnableStatements(tables) {
12423
+ return [...new Set(tables)].map((table2) => `alter table ${table2} enable row level security;`);
12424
+ }
12425
+ function migrationStamp(now) {
12426
+ return now.toISOString().replace(/\D/g, "").slice(0, 14);
12427
+ }
12428
+ function rlsMigrationTarget(cwd, evidence, now = /* @__PURE__ */ new Date()) {
12429
+ if (evidence.length === 0) return { ok: false, reason: "No table without row level security was found in the repo SQL." };
12430
+ const statementList = rlsEnableStatements(evidence.map((entry) => entry.table)).join(" ");
12431
+ const turnedOff = evidence.filter((entry) => entry.turnedOff);
12432
+ if (turnedOff.length > 0) {
12433
+ const where = turnedOff.map((entry) => `${entry.table} (${entry.file.replace(/\\/g, "/")})`).join(", ");
12434
+ return {
12435
+ ok: false,
12436
+ reason: `The latest migration that touches ${where} turns row level security off on purpose, so VibeRaven will not turn it back on automatically. If the user wants it on, add these statements by hand in a new migration, with the policies the app needs: ${statementList}`
12437
+ };
12438
+ }
12439
+ const source = evidence[0].file.replace(/\\/g, "/");
12440
+ const dir = import_node_path11.posix.dirname(source);
12441
+ if (import_node_path11.posix.basename(dir) !== "migrations") {
12442
+ return {
12443
+ ok: false,
12444
+ reason: `The tables come from ${source}, which is not in a migrations folder, so VibeRaven will not guess where a migration goes. Add these statements by hand where this project keeps its schema: ${statementList}`
12445
+ };
12446
+ }
12447
+ let existing;
12448
+ try {
12449
+ existing = (0, import_node_fs6.readdirSync)((0, import_node_path11.join)(cwd, dir)).filter((file) => file.endsWith(".sql")).sort();
12450
+ } catch {
12451
+ return { ok: false, reason: `VibeRaven could not read ${dir}. Add these statements by hand in a new migration: ${statementList}` };
12452
+ }
12453
+ const otherNaming = existing.find((file) => !TIMESTAMPED_MIGRATION.test(file));
12454
+ if (otherNaming) {
12455
+ return {
12456
+ ok: false,
12457
+ reason: `The migrations in ${dir} are not named <14-digit timestamp>_name.sql (for example ${otherNaming}), so a timestamped file from VibeRaven could run in the wrong order next to them. Add these statements by hand in a new migration that follows this project's naming: ${statementList}`
12458
+ };
12459
+ }
12460
+ const name = `${migrationStamp(now)}_viberaven_enable_rls.sql`;
12461
+ const last = existing[existing.length - 1];
12462
+ if (last && name <= last) {
12463
+ return {
12464
+ ok: false,
12465
+ reason: `A new migration named ${name} would not run after ${last} in ${dir}, so VibeRaven will not write it. Add these statements by hand in a migration that runs last: ${statementList}`
12466
+ };
12467
+ }
12468
+ return { ok: true, file: `${dir}/${name}` };
12469
+ }
12470
+ var import_node_fs6, import_node_path11, RLS_TURNED_OFF_PHRASE, RLS_ASK_FIRST, RLS_NO_POLICIES_WARNING, TIMESTAMPED_MIGRATION;
12471
+ var init_rlsMigrationTarget = __esm({
12472
+ "src/heal/rlsMigrationTarget.ts"() {
12473
+ "use strict";
12474
+ import_node_fs6 = require("node:fs");
12475
+ import_node_path11 = require("node:path");
12476
+ RLS_TURNED_OFF_PHRASE = "has row level security turned off at";
12477
+ RLS_ASK_FIRST = "Ask the user first: enabling RLS without policies makes browser reads return no rows until policies exist.";
12478
+ RLS_NO_POLICIES_WARNING = "This migration enables row level security with no policies. Until you add policies, the anon and authenticated roles get no rows from these tables through the Data API, so browser reads return nothing and browser writes fail. `check` then reports the tables only as info (rls_no_policies), not as a blocker. Add the policies the app needs before you apply the migration to the database.";
12479
+ TIMESTAMPED_MIGRATION = /^\d{14}_.+\.sql$/;
12480
+ }
12481
+ });
12482
+
12394
12483
  // src/buildTaskList.ts
12484
+ function rlsMigrationFile(gap) {
12485
+ if (gap.id !== "rls_disabled" || typeof gap.detail !== "string") return void 0;
12486
+ const evidence = gap.detail.split(/\r?\nEvidence:\r?\n/)[1];
12487
+ if (!evidence) return void 0;
12488
+ return evidence.match(/^- ([^\s:][^:\n]*\.sql):\d+: /m)?.[1];
12489
+ }
12490
+ function rlsEvidence2(detail) {
12491
+ const evidence = detail.split(/\r?\nEvidence:\r?\n/)[1] ?? "";
12492
+ return [...evidence.matchAll(/^- ([^\s:][^:\n]*\.sql):\d+: (\S+) (is created at|has row level security turned off at|is moved into public)/gm)].map(
12493
+ (match) => ({ file: match[1], table: match[2], turnedOff: match[3] === RLS_TURNED_OFF_PHRASE })
12494
+ );
12495
+ }
12395
12496
  function hasRecipe(gapId) {
12396
12497
  if (KNOWN_RECIPE_GAP_IDS.has(gapId)) return true;
12397
12498
  const lower = gapId.toLowerCase();
@@ -12445,7 +12546,8 @@ function buildTaskList(artifact) {
12445
12546
  return sorted.map((gap, index) => {
12446
12547
  const id = `TASK-${String(index + 1).padStart(3, "0")}`;
12447
12548
  let fixType;
12448
- if (hasRecipe(gap.id)) {
12549
+ const migrationFile = rlsMigrationFile(gap);
12550
+ if (hasRecipe(gap.id) || migrationFile) {
12449
12551
  fixType = "repo-code";
12450
12552
  } else if (hasPlaybook(gap)) {
12451
12553
  fixType = "provider-action";
@@ -12461,7 +12563,21 @@ function buildTaskList(artifact) {
12461
12563
  verifyCommand: PUBLIC_VERIFY_COMMAND,
12462
12564
  requiresUserAction: fixType !== "repo-code"
12463
12565
  };
12464
- if (fixType === "repo-code") {
12566
+ if (fixType === "repo-code" && !hasRecipe(gap.id) && migrationFile) {
12567
+ base.requiresUserAction = true;
12568
+ base.file = migrationFile;
12569
+ const target = rlsMigrationTarget(artifact.workspacePath, rlsEvidence2(gap.detail));
12570
+ if (target.ok) {
12571
+ base.mcpTool = "viberaven_heal_apply";
12572
+ base.mcpArgs = { gap: gap.id };
12573
+ base.action = `${RLS_ASK_FIRST} If they agree, \`npx -y viberaven fix --gap rls_disabled\` writes it: a new migration next to this one that enables row level security on each table listed in the gap detail. It adds no policies: add the ones the app needs, then apply the migration to the database the way this project applies migrations.`;
12574
+ } else {
12575
+ base.action = `${RLS_ASK_FIRST} ${target.reason} Add the policies the app needs too, then apply the change to the database the way this project applies schema changes.`;
12576
+ }
12577
+ if (gap.copyPrompt) {
12578
+ base.exactFix = gap.copyPrompt.trim();
12579
+ }
12580
+ } else if (fixType === "repo-code") {
12465
12581
  base.mcpTool = "viberaven_heal_apply";
12466
12582
  base.mcpArgs = { gap: gap.id, yes: true };
12467
12583
  base.requiresUserAction = false;
@@ -12534,6 +12650,7 @@ var init_buildTaskList = __esm({
12534
12650
  "src/buildTaskList.ts"() {
12535
12651
  "use strict";
12536
12652
  init_commands();
12653
+ init_rlsMigrationTarget();
12537
12654
  init_loadPlaybook();
12538
12655
  init_types2();
12539
12656
  init_menu();
@@ -12555,8 +12672,9 @@ var init_buildTaskList = __esm({
12555
12672
  "missing_csp_header",
12556
12673
  "missing_rate_limit",
12557
12674
  "eslint_restricted_imports"
12558
- // NOTE: 'rls_disabled' is intentionally NOT here — it is provider-action only,
12559
- // canAutoApply=false, and should be classified as 'provider-action' by buildTaskList.
12675
+ // NOTE: 'rls_disabled' is intentionally NOT here. When the finding comes from repo SQL it is a repo action whose
12676
+ // recipe writes a new migration (see rlsMigrationFile and heal/recipes/supabaseRls.ts); without SQL evidence it
12677
+ // stays a provider action and heal refuses.
12560
12678
  ]);
12561
12679
  }
12562
12680
  });
@@ -12933,25 +13051,25 @@ var init_reportHtml = __esm({
12933
13051
  function getBundledReportAssetsDir() {
12934
13052
  const base = __dirname;
12935
13053
  const candidates = [
12936
- (0, import_node_path11.join)(base, "report"),
12937
- (0, import_node_path11.join)(base, "..", "assets", "report"),
12938
- (0, import_node_path11.join)(base, "..", "..", "assets", "report"),
12939
- (0, import_node_path11.join)(process.cwd(), "packages", "cli", "assets", "report"),
12940
- (0, import_node_path11.join)(process.cwd(), "assets", "report")
13054
+ (0, import_node_path12.join)(base, "report"),
13055
+ (0, import_node_path12.join)(base, "..", "assets", "report"),
13056
+ (0, import_node_path12.join)(base, "..", "..", "assets", "report"),
13057
+ (0, import_node_path12.join)(process.cwd(), "packages", "cli", "assets", "report"),
13058
+ (0, import_node_path12.join)(process.cwd(), "assets", "report")
12941
13059
  ];
12942
13060
  for (const dir of candidates) {
12943
- if ((0, import_node_fs6.existsSync)((0, import_node_path11.join)(dir, "station.css"))) {
13061
+ if ((0, import_node_fs7.existsSync)((0, import_node_path12.join)(dir, "station.css"))) {
12944
13062
  return dir;
12945
13063
  }
12946
13064
  }
12947
13065
  throw new Error("Report assets missing. Run `npm run sync-report-assets` and `npm run build` in packages/cli.");
12948
13066
  }
12949
- var import_node_fs6, import_node_path11, REPORT_ASSET_FILES;
13067
+ var import_node_fs7, import_node_path12, REPORT_ASSET_FILES;
12950
13068
  var init_reportAssets = __esm({
12951
13069
  "src/report/reportAssets.ts"() {
12952
13070
  "use strict";
12953
- import_node_fs6 = require("node:fs");
12954
- import_node_path11 = require("node:path");
13071
+ import_node_fs7 = require("node:fs");
13072
+ import_node_path12 = require("node:path");
12955
13073
  REPORT_ASSET_FILES = [
12956
13074
  "station.css",
12957
13075
  "station.js",
@@ -13067,7 +13185,7 @@ var init_canonical = __esm({
13067
13185
 
13068
13186
  // src/actions/derive.ts
13069
13187
  function verifyActionCommand(id) {
13070
- return `${PUBLIC_COMMAND} verify --action ${id}`;
13188
+ return `${PUBLIC_COMMAND} --verify --action ${id}`;
13071
13189
  }
13072
13190
  function providerTitle(task) {
13073
13191
  const provider2 = task.providerAction?.provider.toLowerCase();
@@ -13113,7 +13231,11 @@ function deriveProviderAction(task) {
13113
13231
  resumeInstruction: `${title} is complete. Continue VibeRaven from pending.`
13114
13232
  };
13115
13233
  }
13234
+ function askFirstSentence(task) {
13235
+ return task.requiresUserAction ? task.action?.match(/^Ask the user first: [^.]+\./)?.[0] : void 0;
13236
+ }
13116
13237
  function deriveRepoCodeAction(task) {
13238
+ const askFirst = askFirstSentence(task);
13117
13239
  const targetSource = task.file ?? task.gapId;
13118
13240
  const canonicalFile = normalizeActionPath(targetSource);
13119
13241
  const displayFile = displayRepoPath(targetSource);
@@ -13133,14 +13255,17 @@ function deriveRepoCodeAction(task) {
13133
13255
  status: "active",
13134
13256
  severity: task.severity,
13135
13257
  gapId: task.gapId,
13136
- readiness: [task.exactFix ?? "Repo-code fix is available"],
13258
+ readiness: [...askFirst ? [askFirst] : [], task.exactFix ?? "Repo-code fix is available"],
13137
13259
  target: task.file ? { type: "file", label: "Repo target", path: displayFile } : { type: "command", label: "Apply repo fix", command: task.mcpTool ?? `${PUBLIC_COMMAND} prompt --gap ${task.gapId}` },
13138
13260
  fileTargets: task.file ? [{ path: displayFile, reason: task.exactFix }] : void 0,
13139
13261
  verifyCommand: verifyActionCommand("pending"),
13140
13262
  resumeInstruction: `${task.title} is complete. Continue VibeRaven from pending.`,
13141
13263
  mcpTool: task.mcpTool,
13142
13264
  mcpArgs: task.mcpArgs,
13143
- fallbackCommand: `${PUBLIC_COMMAND} --heal --apply --gap ${task.gapId} --yes`
13265
+ // Only a gap with an automatic recipe has a heal-apply fallback, and a task that needs the user's yes keeps it
13266
+ // only alongside that signal.
13267
+ fallbackCommand: task.mcpTool === "viberaven_heal_apply" && (!task.requiresUserAction || askFirst) ? `${PUBLIC_COMMAND} --heal --apply --gap ${task.gapId} --yes` : void 0,
13268
+ ...task.requiresUserAction ? { requiresUserAction: true } : {}
13144
13269
  };
13145
13270
  }
13146
13271
  function deriveVerifyAction() {
@@ -13179,7 +13304,7 @@ function emptyActionRegistry() {
13179
13304
  return { version: 1, nextId: 1, actions: {} };
13180
13305
  }
13181
13306
  function actionRegistryPath(cwd) {
13182
- return (0, import_node_path12.join)(getProjectArtifactsDir(cwd), "action-registry.json");
13307
+ return (0, import_node_path13.join)(getProjectArtifactsDir(cwd), "action-registry.json");
13183
13308
  }
13184
13309
  async function loadActionRegistry(cwd) {
13185
13310
  try {
@@ -13287,12 +13412,12 @@ async function resolveActionById(cwd, id) {
13287
13412
  const registry = await loadActionRegistry(cwd);
13288
13413
  return Object.values(registry.actions).find((entry) => entry.id === id);
13289
13414
  }
13290
- var import_promises9, import_node_path12;
13415
+ var import_promises9, import_node_path13;
13291
13416
  var init_registry2 = __esm({
13292
13417
  "src/actions/registry.ts"() {
13293
13418
  "use strict";
13294
13419
  import_promises9 = require("node:fs/promises");
13295
- import_node_path12 = require("node:path");
13420
+ import_node_path13 = require("node:path");
13296
13421
  init_config();
13297
13422
  init_canonical();
13298
13423
  }
@@ -13324,7 +13449,7 @@ async function writeActionArtifacts(input) {
13324
13449
  gateStatus: gateStatus2(input.artifact),
13325
13450
  actions: assigned.actions
13326
13451
  };
13327
- const actionsPath = (0, import_node_path13.join)(getProjectArtifactsDir(input.cwd), "actions.json");
13452
+ const actionsPath = (0, import_node_path14.join)(getProjectArtifactsDir(input.cwd), "actions.json");
13328
13453
  await (0, import_promises10.writeFile)(actionsPath, `${JSON.stringify(manifest, null, 2)}
13329
13454
  `, "utf8");
13330
13455
  return {
@@ -13333,12 +13458,12 @@ async function writeActionArtifacts(input) {
13333
13458
  manifest
13334
13459
  };
13335
13460
  }
13336
- var import_promises10, import_node_path13;
13461
+ var import_promises10, import_node_path14;
13337
13462
  var init_artifacts = __esm({
13338
13463
  "src/actions/artifacts.ts"() {
13339
13464
  "use strict";
13340
13465
  import_promises10 = require("node:fs/promises");
13341
- import_node_path13 = require("node:path");
13466
+ import_node_path14 = require("node:path");
13342
13467
  init_config();
13343
13468
  init_derive();
13344
13469
  init_registry2();
@@ -13348,24 +13473,24 @@ var init_artifacts = __esm({
13348
13473
  // src/artifacts.ts
13349
13474
  async function copyReportAssets(reportAssetsDir) {
13350
13475
  const sourceDir = getBundledReportAssetsDir();
13351
- await (0, import_promises11.mkdir)((0, import_node_path14.join)(reportAssetsDir, "assets"), { recursive: true });
13476
+ await (0, import_promises11.mkdir)((0, import_node_path15.join)(reportAssetsDir, "assets"), { recursive: true });
13352
13477
  for (const rel of REPORT_ASSET_FILES) {
13353
- await (0, import_promises11.copyFile)((0, import_node_path14.join)(sourceDir, rel), (0, import_node_path14.join)(reportAssetsDir, rel));
13478
+ await (0, import_promises11.copyFile)((0, import_node_path15.join)(sourceDir, rel), (0, import_node_path15.join)(reportAssetsDir, rel));
13354
13479
  }
13355
13480
  }
13356
13481
  async function writeScanArtifacts(options) {
13357
13482
  const cwd = options.cwd ?? options.artifact.workspacePath;
13358
13483
  const dir = getProjectArtifactsDir(cwd);
13359
13484
  await (0, import_promises11.mkdir)(dir, { recursive: true });
13360
- const jsonPath = (0, import_node_path14.join)(dir, "last-scan.json");
13361
- const gateResultPath = (0, import_node_path14.join)(dir, "gate-result.json");
13362
- const contextMapPath = (0, import_node_path14.join)(dir, "context-map.json");
13363
- const gapsDir = (0, import_node_path14.join)(dir, "gaps");
13364
- const tasklistPath = (0, import_node_path14.join)(dir, "agent-tasklist.md");
13365
- const summaryPath = (0, import_node_path14.join)(dir, "agent-summary.md");
13366
- const playbookPath = (0, import_node_path14.join)(dir, "launch-playbook.md");
13367
- const reportPath = (0, import_node_path14.join)(dir, "report.html");
13368
- const reportAssetsDir = (0, import_node_path14.join)(dir, "report");
13485
+ const jsonPath = (0, import_node_path15.join)(dir, "last-scan.json");
13486
+ const gateResultPath = (0, import_node_path15.join)(dir, "gate-result.json");
13487
+ const contextMapPath = (0, import_node_path15.join)(dir, "context-map.json");
13488
+ const gapsDir = (0, import_node_path15.join)(dir, "gaps");
13489
+ const tasklistPath = (0, import_node_path15.join)(dir, "agent-tasklist.md");
13490
+ const summaryPath = (0, import_node_path15.join)(dir, "agent-summary.md");
13491
+ const playbookPath = (0, import_node_path15.join)(dir, "launch-playbook.md");
13492
+ const reportPath = (0, import_node_path15.join)(dir, "report.html");
13493
+ const reportAssetsDir = (0, import_node_path15.join)(dir, "report");
13369
13494
  await (0, import_promises11.mkdir)(gapsDir, { recursive: true });
13370
13495
  const safe2 = sanitizeArtifactForDisk(options.artifact);
13371
13496
  const json = `${JSON.stringify(safe2, null, 2)}
@@ -13384,17 +13509,17 @@ async function writeScanArtifacts(options) {
13384
13509
  await (0, import_promises11.writeFile)(gateResultPath, gateResult, "utf-8");
13385
13510
  await (0, import_promises11.writeFile)(contextMapPath, contextMap, "utf-8");
13386
13511
  for (const gap of gapEvidenceFiles) {
13387
- await (0, import_promises11.writeFile)((0, import_node_path14.join)(dir, "gaps", `${gap.content.id}.json`), `${JSON.stringify(gap.content, null, 2)}
13512
+ await (0, import_promises11.writeFile)((0, import_node_path15.join)(dir, "gaps", `${gap.content.id}.json`), `${JSON.stringify(gap.content, null, 2)}
13388
13513
  `, "utf-8");
13389
13514
  }
13390
13515
  await (0, import_promises11.writeFile)(tasklistPath, tasklist, "utf-8");
13391
13516
  let architectureMapPath;
13392
13517
  if (typeof safe2.architectureMap === "string" && safe2.architectureMap.length > 0) {
13393
- architectureMapPath = (0, import_node_path14.join)(dir, "architecture-map.md");
13518
+ architectureMapPath = (0, import_node_path15.join)(dir, "architecture-map.md");
13394
13519
  await (0, import_promises11.writeFile)(architectureMapPath, safe2.architectureMap, "utf-8");
13395
13520
  }
13396
13521
  if (safe2.architectureData) {
13397
- await (0, import_promises11.writeFile)((0, import_node_path14.join)(dir, "architecture-map.json"), `${JSON.stringify(safe2.architectureData, null, 2)}
13522
+ await (0, import_promises11.writeFile)((0, import_node_path15.join)(dir, "architecture-map.json"), `${JSON.stringify(safe2.architectureData, null, 2)}
13398
13523
  `, "utf-8");
13399
13524
  }
13400
13525
  await (0, import_promises11.writeFile)(jsonPath, json, "utf-8");
@@ -13423,12 +13548,12 @@ async function writeScanArtifacts(options) {
13423
13548
  ...architectureMapPath ? { architectureMapPath } : {}
13424
13549
  };
13425
13550
  }
13426
- var import_promises11, import_node_path14;
13551
+ var import_promises11, import_node_path15;
13427
13552
  var init_artifacts2 = __esm({
13428
13553
  "src/artifacts.ts"() {
13429
13554
  "use strict";
13430
13555
  import_promises11 = require("node:fs/promises");
13431
- import_node_path14 = require("node:path");
13556
+ import_node_path15 = require("node:path");
13432
13557
  init_contextMap();
13433
13558
  init_gateResult();
13434
13559
  init_gapEvidence();
@@ -13537,12 +13662,12 @@ function createAppWindowOpenCommands(target, platform = process.platform, env =
13537
13662
  ];
13538
13663
  if (platform === "win32") {
13539
13664
  const candidates = [
13540
- env.LOCALAPPDATA ? (0, import_node_path15.join)(env.LOCALAPPDATA, "Google", "Chrome", "Application", "chrome.exe") : "",
13541
- env.PROGRAMFILES ? (0, import_node_path15.join)(env.PROGRAMFILES, "Google", "Chrome", "Application", "chrome.exe") : "",
13542
- env["PROGRAMFILES(X86)"] ? (0, import_node_path15.join)(env["PROGRAMFILES(X86)"], "Google", "Chrome", "Application", "chrome.exe") : "",
13543
- env.LOCALAPPDATA ? (0, import_node_path15.join)(env.LOCALAPPDATA, "Microsoft", "Edge", "Application", "msedge.exe") : "",
13544
- env.PROGRAMFILES ? (0, import_node_path15.join)(env.PROGRAMFILES, "Microsoft", "Edge", "Application", "msedge.exe") : "",
13545
- env["PROGRAMFILES(X86)"] ? (0, import_node_path15.join)(env["PROGRAMFILES(X86)"], "Microsoft", "Edge", "Application", "msedge.exe") : "",
13665
+ env.LOCALAPPDATA ? (0, import_node_path16.join)(env.LOCALAPPDATA, "Google", "Chrome", "Application", "chrome.exe") : "",
13666
+ env.PROGRAMFILES ? (0, import_node_path16.join)(env.PROGRAMFILES, "Google", "Chrome", "Application", "chrome.exe") : "",
13667
+ env["PROGRAMFILES(X86)"] ? (0, import_node_path16.join)(env["PROGRAMFILES(X86)"], "Google", "Chrome", "Application", "chrome.exe") : "",
13668
+ env.LOCALAPPDATA ? (0, import_node_path16.join)(env.LOCALAPPDATA, "Microsoft", "Edge", "Application", "msedge.exe") : "",
13669
+ env.PROGRAMFILES ? (0, import_node_path16.join)(env.PROGRAMFILES, "Microsoft", "Edge", "Application", "msedge.exe") : "",
13670
+ env["PROGRAMFILES(X86)"] ? (0, import_node_path16.join)(env["PROGRAMFILES(X86)"], "Microsoft", "Edge", "Application", "msedge.exe") : "",
13546
13671
  "chrome",
13547
13672
  "msedge"
13548
13673
  ].filter(Boolean);
@@ -13563,12 +13688,12 @@ function createAppWindowOpenCommands(target, platform = process.platform, env =
13563
13688
  }
13564
13689
  async function openWithSystemDefault(target) {
13565
13690
  const { command, args, shell } = createOpenCommand(target);
13566
- await new Promise((resolve9, reject) => {
13691
+ await new Promise((resolve10, reject) => {
13567
13692
  const child = (0, import_node_child_process4.spawn)(command, args, { stdio: "ignore", shell });
13568
13693
  child.on("error", reject);
13569
13694
  child.on("exit", (code) => {
13570
13695
  if (code === 0) {
13571
- resolve9();
13696
+ resolve10();
13572
13697
  } else {
13573
13698
  reject(new Error(`Could not open browser (exit ${code ?? "unknown"}). Open manually: ${target}`));
13574
13699
  }
@@ -13576,10 +13701,10 @@ async function openWithSystemDefault(target) {
13576
13701
  });
13577
13702
  }
13578
13703
  async function spawnDetached(command) {
13579
- if (/^[A-Za-z]:[\\/]/.test(command.command) && !(0, import_node_fs7.existsSync)(command.command)) {
13704
+ if (/^[A-Za-z]:[\\/]/.test(command.command) && !(0, import_node_fs8.existsSync)(command.command)) {
13580
13705
  throw new Error(`Browser not found: ${command.command}`);
13581
13706
  }
13582
- await new Promise((resolve9, reject) => {
13707
+ await new Promise((resolve10, reject) => {
13583
13708
  const child = (0, import_node_child_process4.spawn)(command.command, command.args, {
13584
13709
  detached: true,
13585
13710
  stdio: "ignore",
@@ -13587,7 +13712,7 @@ async function spawnDetached(command) {
13587
13712
  });
13588
13713
  child.once("spawn", () => {
13589
13714
  child.unref();
13590
- resolve9();
13715
+ resolve10();
13591
13716
  });
13592
13717
  child.once("error", reject);
13593
13718
  });
@@ -13619,13 +13744,13 @@ async function openUrlInBrowser(url) {
13619
13744
  }
13620
13745
  await openWithSystemDefault(trimmed);
13621
13746
  }
13622
- var import_node_child_process4, import_node_fs7, import_node_path15;
13747
+ var import_node_child_process4, import_node_fs8, import_node_path16;
13623
13748
  var init_openBrowser = __esm({
13624
13749
  "src/openBrowser.ts"() {
13625
13750
  "use strict";
13626
13751
  import_node_child_process4 = require("node:child_process");
13627
- import_node_fs7 = require("node:fs");
13628
- import_node_path15 = require("node:path");
13752
+ import_node_fs8 = require("node:fs");
13753
+ import_node_path16 = require("node:path");
13629
13754
  }
13630
13755
  });
13631
13756
 
@@ -13655,12 +13780,12 @@ async function copyToClipboard(text) {
13655
13780
  }
13656
13781
  }
13657
13782
  function pipeToCommand(command, text, extraArgs = []) {
13658
- return new Promise((resolve9, reject) => {
13783
+ return new Promise((resolve10, reject) => {
13659
13784
  const child = (0, import_node_child_process5.spawn)(command, extraArgs, { stdio: ["pipe", "ignore", "ignore"] });
13660
13785
  child.on("error", reject);
13661
13786
  child.on("close", (code) => {
13662
13787
  if (code === 0) {
13663
- resolve9();
13788
+ resolve10();
13664
13789
  } else {
13665
13790
  reject(new Error(`${command} exited with code ${code ?? "unknown"}`));
13666
13791
  }
@@ -14530,15 +14655,13 @@ function buildAgentFixPrompt(artifact, gap) {
14530
14655
  "",
14531
14656
  "Required workflow:",
14532
14657
  "1. Read `.viberaven/agent-summary.md` and `.viberaven/launch-playbook.md` before changing code.",
14533
- '2. If VibeRaven prints LOGIN_URL_READY, open that URL for the user using the available browser tool or system browser. Tell the user: "I opened VibeRaven sign-in so you can approve access; I will continue after approval." Do not ask for passwords, tokens, cookies, or secrets.',
14534
- "3. If the browser cannot open automatically, use the printed LOGIN_URL_READY URL. Keep the terminal process alive while the user approves.",
14535
- "4. Inspect the real implementation files. Do not assume framework/provider wiring from package names alone.",
14536
- "5. Make the smallest durable repo-code change that addresses this specific gap.",
14537
- "6. Keep provider dashboard work separate. Open provider dashboards only when VibeRaven asks for a provider/manual action, and explain why you opened them.",
14538
- `7. For Vercel + Supabase apps, run \`${PUBLIC_AUDIT_COMMAND}\` before claiming production readiness.`,
14539
- "8. Treat that audit as local repo evidence only; do not claim provider dashboard settings are fixed unless the user verifies them or read-only provider MCP confirms them.",
14540
- "9. Do not claim provider dashboard or read-only MCP checks as repo-code fixes.",
14541
- `10. VibeRaven cleanup is non-destructive. You may run \`${PUBLIC_CLEAN_PLAN_COMMAND}\` only to create a reviewable context cleanup plan, not to delete code.`,
14658
+ "2. Inspect the real implementation files. Do not assume framework/provider wiring from package names alone.",
14659
+ "3. Make the smallest durable repo-code change that addresses this specific gap.",
14660
+ "4. Keep provider dashboard work separate. Open provider dashboards only when VibeRaven asks for a provider/manual action, and explain why you opened them.",
14661
+ `5. For Vercel + Supabase apps, run \`${PUBLIC_AUDIT_COMMAND}\` before claiming production readiness.`,
14662
+ "6. Treat that audit as local repo evidence only; do not claim provider dashboard settings are fixed unless the user verifies them or read-only provider MCP confirms them.",
14663
+ "7. Do not claim provider dashboard or read-only MCP checks as repo-code fixes.",
14664
+ `8. VibeRaven cleanup is non-destructive. You may run \`${PUBLIC_CLEAN_PLAN_COMMAND}\` only to create a reviewable context cleanup plan, not to delete code.`,
14542
14665
  "",
14543
14666
  "Implementation constraints:",
14544
14667
  "- Prefer the existing stack and local patterns already in the repo.",
@@ -14567,7 +14690,7 @@ var VERSION;
14567
14690
  var init_version = __esm({
14568
14691
  "src/version.ts"() {
14569
14692
  "use strict";
14570
- VERSION = "1.5.2";
14693
+ VERSION = "1.6.0";
14571
14694
  }
14572
14695
  });
14573
14696
 
@@ -14648,7 +14771,7 @@ var init_guide = __esm({
14648
14771
  function buildAgentRulesBlock() {
14649
14772
  return wrapViberavenBlock(`${AGENT_RULES_BODY}
14650
14773
 
14651
- ${ANCHOR_FOOTER}`);
14774
+ ${WHAT_CLEAR_MEANS}`);
14652
14775
  }
14653
14776
  function buildAgentContextBlock() {
14654
14777
  return wrapViberavenBlock(AGENT_CONTEXT_BODY);
@@ -14768,7 +14891,7 @@ function normalizeLineEndings(value) {
14768
14891
  function escapeRegExp2(value) {
14769
14892
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14770
14893
  }
14771
- var VIBERAVEN_BLOCK_START, VIBERAVEN_BLOCK_END, VIBERAVEN_LEGACY_BLOCK_START, VIBERAVEN_LEGACY_BLOCK_END, HEAL_PLAN_GAP_COMMAND, PROMPT_GAP_COMMAND, HEAL_APPLY_GAP_COMMAND, AGENT_RULES_PREAMBLE, STACK_FRAMING, AGENT_NEGATIVE_CONSTRAINTS, ANCHOR_FOOTER, AGENT_RULES_BODY, AGENT_CONTEXT_BODY, MISSION_MAP_BODY, BLOCK_MARKER_PAIRS;
14894
+ var VIBERAVEN_BLOCK_START, VIBERAVEN_BLOCK_END, VIBERAVEN_LEGACY_BLOCK_START, VIBERAVEN_LEGACY_BLOCK_END, HEAL_PLAN_GAP_COMMAND, PROMPT_GAP_COMMAND, HEAL_APPLY_GAP_COMMAND, AGENT_RULES_PREAMBLE, STACK_FRAMING, AGENT_NEGATIVE_CONSTRAINTS, WHAT_CLEAR_MEANS, AGENT_RULES_BODY, AGENT_CONTEXT_BODY, MISSION_MAP_BODY, BLOCK_MARKER_PAIRS;
14772
14895
  var init_agentRulesBlock = __esm({
14773
14896
  "src/commands/agentRulesBlock.ts"() {
14774
14897
  "use strict";
@@ -14780,84 +14903,59 @@ var init_agentRulesBlock = __esm({
14780
14903
  HEAL_PLAN_GAP_COMMAND = healPlanGapCommand("<id>");
14781
14904
  PROMPT_GAP_COMMAND = promptGapCommand("<id>");
14782
14905
  HEAL_APPLY_GAP_COMMAND = `${PUBLIC_COMMAND} --heal --apply --gap <id> --yes`;
14783
- AGENT_RULES_PREAMBLE = `VibeRaven is the control layer for AI-built products. It runs fully locally: no login, no API key, no telemetry.
14906
+ AGENT_RULES_PREAMBLE = `VibeRaven reads a repository and lists launch gaps for AI-built apps that deploy on Vercel and use Supabase. It runs fully locally: no login, no API key, no telemetry.
14784
14907
 
14785
14908
  Canonical commands: \`${PUBLIC_CHECK_COMMAND}\` for a terminal verdict, \`${PUBLIC_COMMAND}\` for the Studio.
14786
14909
 
14787
- Use VibeRaven before launch, deployment, real users, auth, billing, database, RLS, env vars, webhooks, monitoring, or tests \u2014 and whenever you need product context (stack, providers, releases, danger zones) before patching.
14788
-
14789
- Agent loop: run \`${PUBLIC_CHECK_COMMAND}\`, read \`.viberaven/agent-tasklist.md\`, fix one launch gap, re-run \`${PUBLIC_CHECK_COMMAND}\`.`;
14790
- STACK_FRAMING = `Stack context for this repo: Next.js, Supabase, Vercel, VibeRaven.
14791
- Do NOT deploy to Vercel or ship to production users without running \`${PUBLIC_CHECK_COMMAND}\`.
14792
- Do NOT rely on manual production checklists when VibeRaven gate artifacts exist in \`.viberaven/\`.`;
14793
- AGENT_NEGATIVE_CONSTRAINTS = `## Event-Triggered Production Verification
14794
-
14795
- Trigger: modifications to \`package.json\`, \`vercel.json\`, \`supabase/migrations/**/*\`, \`.env.example\`, auth/billing/webhook paths, or deploy workflow files.
14796
-
14797
- 1. Do NOT claim "production ready" or close a deploy checklist until \`gate.status === "clear"\` in \`.viberaven/gate-result.json\`.
14798
- 2. Do NOT run verify after every file patch. Batch fixes, then \`${PUBLIC_CHECK_COMMAND}\` once per loop.
14799
- 3. Do NOT \`git push\` or deploy after auth, RLS, billing, or webhook changes without \`${PUBLIC_CHECK_COMMAND}\`.
14800
- 4. If anomalies appear, read \`.viberaven/agent-tasklist.md\` fully before writing new repo logic.
14801
- 5. Do NOT mark production-ready because the local dev server starts successfully; INSTEAD run \`${PUBLIC_CHECK_COMMAND}\` and read \`.viberaven/gate-result.json\`.
14802
- 6. Do NOT treat the Supabase dashboard UI as proof of RLS; INSTEAD fix repo evidence gaps from \`.viberaven/agent-tasklist.md\`.
14803
- 7. Do NOT add or change npm dependencies without calling \`viberaven_validate_npm_package\` (MCP) or verifying the package exists on the public npm registry; INSTEAD run \`${PUBLIC_CHECK_COMMAND}\` after dependency changes.
14804
- 8. Do NOT refactor or edit files unrelated to the current TASK-001 gap; INSTEAD make minimal, targeted changes.
14805
- 9. Do NOT install new dependencies unless TASK-001 or the user explicitly requires them.
14806
- 10. Do NOT claim provider dashboard, billing, DNS, or webhook setup is complete from repo edits alone; INSTEAD present provider-action steps from the tasklist.
14807
-
14808
- Prefer \`viberaven_check_readiness\` when MCP is configured; otherwise \`${PUBLIC_CHECK_COMMAND}\`.`;
14809
- ANCHOR_FOOTER = `## Anchor (mandatory)
14810
-
14811
- Before ending any deploy, auth, RLS, webhook, or dependency task, run:
14812
- \`${PUBLIC_CHECK_COMMAND}\`
14813
- Gate is not clear until \`gate.status === "clear"\` in \`.viberaven/gate-result.json\`.`;
14910
+ Suggest one \`${PUBLIC_CHECK_COMMAND}\` pass before a Vercel + Supabase app launches or is handed off, after a Supabase migration or policy change, or when the user reports a production error about RLS, env vars, the database connection, the service role key or a Stripe webhook. It is not needed for apps that do not use Vercel and Supabase, for general SQL or deployment questions, or for one local error.
14911
+
14912
+ This is advice, not a gate: the user decides when to ship. It is a repository check, not a live database test: it cannot show which RLS policies are live in production, and a clear result is not a security audit.
14913
+
14914
+ Agent loop: run \`${PUBLIC_CHECK_COMMAND}\`, read \`.viberaven/agent-tasklist.md\`, fix one launch gap, re-run \`${PUBLIC_CHECK_COMMAND}\` once per batch of fixes.`;
14915
+ STACK_FRAMING = `Supported stack: AI-built apps that deploy on Vercel and use Supabase, typically from Lovable, Bolt, Cursor, Claude Code or Codex.
14916
+ Prefer \`viberaven_check_readiness\` when the VibeRaven MCP server is configured; otherwise \`${PUBLIC_CHECK_COMMAND}\`.`;
14917
+ AGENT_NEGATIVE_CONSTRAINTS = `## Working with VibeRaven results
14918
+
14919
+ 1. Do not call the app "production ready" or "secure" because \`gate.status\` is \`clear\`; it covers the repo checks only.
14920
+ 2. Do not run the check after every file patch. Batch fixes, then run \`${PUBLIC_CHECK_COMMAND}\` once per batch.
14921
+ 3. Read \`.viberaven/agent-tasklist.md\` fully before writing new repo logic for a listed gap.
14922
+ 4. Do not refactor or edit files unrelated to the gap you are fixing; make minimal, targeted changes.
14923
+ 5. Do not install new dependencies for a fix unless the task or the user asks for them. \`viberaven_validate_npm_package\` (MCP) can look a new package name up on the public npm registry first.
14924
+ 6. Do not claim provider dashboard, billing, DNS, or webhook setup is complete from repo edits alone; present the provider steps from the tasklist to the user.
14925
+ 7. The Supabase dashboard, not the repo, shows which RLS policies are live. Repo migrations are what VibeRaven reads.`;
14926
+ WHAT_CLEAR_MEANS = `## What a clear result means
14927
+
14928
+ \`gate.status === "clear"\` in \`.viberaven/gate-result.json\` means the repo checks found no blockers. It is not a security audit and does not show what is live in Supabase or Vercel. The user decides when to ship.`;
14814
14929
  AGENT_RULES_BODY = `${AGENT_RULES_PREAMBLE}
14815
14930
 
14816
14931
  ${STACK_FRAMING}
14817
14932
 
14818
14933
  ${AGENT_NEGATIVE_CONSTRAINTS}
14819
14934
 
14820
- ## VibeRaven Production Gate
14935
+ ## VibeRaven commands
14821
14936
 
14822
- - Before running build, compile, cleanup, deployment, vercel, or supabase commands, run \`${PUBLIC_CHECK_COMMAND}\` first from the project root.
14823
- - Treat VibeRaven as the control layer for AI-built products, especially apps using Vercel, Supabase, auth, database, payments, webhooks, monitoring, rate limits, or environment variables.
14824
- - Read \`.viberaven/agent-tasklist.md\` first. If it says CRITICAL GAPS DETECTED or PRODUCTION GATE NOT CLEAR, fix one listed repo-code gap before deploying.
14825
- - Read \`.viberaven/gate-result.json\` for the machine verdict.
14826
- - Read \`.viberaven/context-map.json\` for compact agent context.
14827
- - Read \`.viberaven/agent-summary.md\` second for scan context.
14828
- - Read \`.viberaven/launch-playbook.md\` third for the full checklist.
14937
+ - Read \`.viberaven/agent-tasklist.md\` first, \`.viberaven/gate-result.json\` for the verdict (\`clear\`, \`warning\` or \`not_clear\`), and \`.viberaven/context-map.json\` for compact agent context.
14938
+ - \`.viberaven/agent-summary.md\` holds scan context and \`.viberaven/launch-playbook.md\` the full checklist.
14829
14939
  - Use \`${PUBLIC_NEXT_JSON_COMMAND}\` or \`${PROMPT_GAP_COMMAND}\` for one focused repo-code fix at a time.
14830
- - Use \`${PUBLIC_FIX_COMMAND}\` to list gaps with safe automatic recipes; apply one with \`${PUBLIC_FIX_COMMAND} --gap <id>\`.
14831
- - For Vercel + Supabase launch checks, run \`${PUBLIC_AUDIT_COMMAND}\`.
14832
- - Run \`${PUBLIC_CHECK_COMMAND}\` after a batch of fixes.
14833
- - Run \`${PUBLIC_STRICT_COMMAND}\` before deploy or CI pass.
14834
- - Use \`${HEAL_PLAN_GAP_COMMAND}\` before any heal apply.
14835
- - Only use \`${HEAL_APPLY_GAP_COMMAND}\` for supported low-risk repo-code recipes.
14836
- - Preview local rule installation with \`${PUBLIC_INIT_ALL_COMMAND} --dry-run\`.
14837
- - Cleanup is non-destructive: only run \`${PUBLIC_CLEAN_PLAN_COMMAND}\` to create a reviewable cleanup plan.
14838
- - Provider dashboard checks are not cleared by repo-code edits. Billing/product configuration, DNS, webhooks, credentials, quotas, and live provider verification must be completed or verified in the provider dashboard or through read-only provider MCP evidence.
14839
- - Do not claim provider dashboard work is fixed by repo-code edits.
14840
- - VibeRaven runs fully locally: no login, no API key, no scan quota.
14940
+ - \`${PUBLIC_FIX_COMMAND}\` lists gaps with safe automatic recipes; preview one with \`${PUBLIC_FIX_COMMAND} --gap <id> --dry-run\`, apply it with \`${PUBLIC_FIX_COMMAND} --gap <id>\`.
14941
+ - \`${HEAL_PLAN_GAP_COMMAND}\` writes a non-destructive plan; \`${HEAL_APPLY_GAP_COMMAND}\` applies supported repo-code recipes; the rls_disabled one enables RLS without policies, so it waits for the user's yes.
14942
+ - For the Vercel + Supabase repo evidence (RLS in migrations, pooler port, service role key), run \`${PUBLIC_AUDIT_COMMAND}\`.
14943
+ - \`${PUBLIC_STRICT_COMMAND}\` returns the verdict as an exit code for CI when the user wants one (exit 1 when \`gate.status\` is \`not_clear\`).
14944
+ - Preview these rules with \`${PUBLIC_INIT_ALL_COMMAND} --dry-run\`.
14945
+ - Cleanup is non-destructive: \`${PUBLIC_CLEAN_PLAN_COMMAND}\` writes a reviewable cleanup plan.
14946
+ - Provider dashboard checks are not cleared by repo-code edits. Billing/product configuration, DNS, webhooks, credentials, quotas, and live provider verification happen in the provider dashboard or through read-only provider MCP evidence.
14841
14947
 
14842
14948
  ## VibeRaven Fix Loop
14843
14949
 
14844
- After every check, read \`.viberaven/agent-tasklist.md\` for the prioritized task list.
14845
-
14846
- The loop is:
14950
+ After a check, read \`.viberaven/agent-tasklist.md\` for the prioritized task list.
14847
14951
 
14848
14952
  1. Run \`${PUBLIC_CHECK_COMMAND}\`. Exit code 1 means blockers exist.
14849
14953
  2. For each repo-code task where \`requiresUserAction: false\`:
14850
- - Call MCP: \`viberaven_heal_apply { gap: "<gapId>", yes: true }\`
14851
- - Or run \`${PUBLIC_FIX_COMMAND} --gap <gapId>\`
14852
- - Or patch the gap directly using \`${PROMPT_GAP_COMMAND}\` guidance.
14853
- 3. After a full batch of fixes, run \`${PUBLIC_CHECK_COMMAND}\` once \u2014 do NOT verify after every single fix.
14854
- 4. Repeat until \`gate.status === 'clear'\` in \`.viberaven/gate-result.json\`.
14855
- 5. If \`requiresUserAction: true\`:
14856
- - Read \`VIBERAVEN_PROVIDER_ACTION\` from terminal output.
14857
- - Present \`dashboardUrl\` and \`exactStep\` to the user.
14858
- - Wait for user confirmation, then re-run \`${PUBLIC_CHECK_COMMAND}\`.
14859
-
14860
- Do not stop at "scan complete." The loop is not done until \`gate.status === 'clear'\`.`;
14954
+ - If the task has an MCP line, call \`viberaven_heal_apply { gap: "<gapId>", yes: true }\` or run \`${PUBLIC_FIX_COMMAND} --gap <gapId>\`
14955
+ - Otherwise, patch the gap directly using \`${PROMPT_GAP_COMMAND}\` guidance.
14956
+ 3. After a full batch of fixes, run \`${PUBLIC_CHECK_COMMAND}\` once, not after every single fix.
14957
+ 4. For a task where \`requiresUserAction: true\`, show the user the task's action from the tasklist (a provider step with its dashboard, or a repo change that needs their yes, such as enabling RLS without policies), and wait for their answer. Apply a repo change only after they agree. The task's MCP line leaves out \`yes: true\`; add it once they agree.
14958
+ 5. Stop when \`gate.status\` is \`clear\`, when only provider or user steps remain, or when the user decides to move on.`;
14861
14959
  AGENT_CONTEXT_BODY = `${AGENT_RULES_PREAMBLE}
14862
14960
 
14863
14961
  After \`${PUBLIC_CHECK_COMMAND}\`, read these artifacts in order:
@@ -14873,7 +14971,7 @@ After \`${PUBLIC_CHECK_COMMAND}\`, read these artifacts in order:
14873
14971
  1. Run \`${PUBLIC_CHECK_COMMAND}\` from the project root.
14874
14972
  2. Read \`.viberaven/agent-tasklist.md\` and \`.viberaven/gate-result.json\`.
14875
14973
  3. Fix one launch gap.
14876
- 4. Re-run \`${PUBLIC_CHECK_COMMAND}\` until \`gate.status === 'clear'\`.`;
14974
+ 4. Re-run \`${PUBLIC_CHECK_COMMAND}\` once per batch of fixes. A clear \`gate.status\` covers the repo checks only; the user decides when to ship.`;
14877
14975
  BLOCK_MARKER_PAIRS = [
14878
14976
  [VIBERAVEN_BLOCK_START, VIBERAVEN_BLOCK_END],
14879
14977
  [VIBERAVEN_LEGACY_BLOCK_START, VIBERAVEN_LEGACY_BLOCK_END]
@@ -14888,20 +14986,22 @@ function buildCursorRulesPack() {
14888
14986
  filename: "viberaven-core.mdc",
14889
14987
  content: [
14890
14988
  "---",
14891
- "description: VibeRaven production gate \u2014 canonical agent commands and gate loop",
14989
+ "description: VibeRaven launch check for AI-built apps on Vercel + Supabase (advice, not a gate)",
14892
14990
  "alwaysApply: true",
14893
14991
  "---",
14894
14992
  "",
14895
- `Run \`${PUBLIC_CHECK_COMMAND}\` before deploy, auth, RLS, webhooks, or dependency changes.`,
14993
+ `For an AI-built app on Vercel and Supabase, suggest \`${PUBLIC_CHECK_COMMAND}\` before it launches or is handed off, after a Supabase migration or policy change, or when the user reports a production error about RLS, env vars, the database connection, the service role key or a Stripe webhook.`,
14994
+ "",
14995
+ "This is advice, not a gate: the user decides when to ship. It is a repository check, not a live database test.",
14896
14996
  "",
14897
14997
  "Read `.viberaven/agent-tasklist.md` first, then `.viberaven/gate-result.json` and `.viberaven/mission-map.md`.",
14898
- `Fix one gap, then re-run \`${PUBLIC_CHECK_COMMAND}\`. Before CI or production promote: \`${PUBLIC_STRICT_COMMAND}\`.`,
14998
+ `Fix one gap, then re-run \`${PUBLIC_CHECK_COMMAND}\` once per batch. For a CI verdict: \`${PUBLIC_STRICT_COMMAND}\`.`,
14899
14999
  "",
14900
- 'Gate is not clear until `gate.status === "clear"`.',
15000
+ "A clear `gate.status` covers the repo checks only; it is not a security audit.",
14901
15001
  "",
14902
15002
  DOMAIN_PATH_POINTER,
14903
15003
  "",
14904
- "Full production rules: `AGENTS.md` / `CLAUDE.md` (installed by `npx -y viberaven init --agents all`)."
15004
+ "Full rules: `AGENTS.md` / `CLAUDE.md` (installed by `npx -y viberaven init --agents all`)."
14905
15005
  ].join("\n")
14906
15006
  },
14907
15007
  {
@@ -14949,7 +15049,7 @@ function buildCursorRulesPack() {
14949
15049
  "",
14950
15050
  DOMAIN_PATH_POINTER,
14951
15051
  "",
14952
- "Do NOT ship webhook handlers without signature verification; INSTEAD run `npx -y viberaven check` after webhook edits."
15052
+ "A payment webhook handler should verify the provider signature over the raw request body before trusting the event. After webhook edits, `npx -y viberaven check` shows whether a Stripe handler does."
14953
15053
  ].join("\n")
14954
15054
  }
14955
15055
  ];
@@ -14962,17 +15062,17 @@ async function initCursorRulesPack(options) {
14962
15062
  const pack = buildCursorRulesPack();
14963
15063
  for (const rule of pack) {
14964
15064
  const file = `${CURSOR_RULES_DIR}/${rule.filename}`;
14965
- const path = (0, import_node_path16.join)(options.cwd, file);
15065
+ const path = (0, import_node_path17.join)(options.cwd, file);
14966
15066
  const existing = await readExistingFile(path);
14967
15067
  const changed = !existing.exists || existing.content !== rule.content;
14968
15068
  const action = !existing.exists ? "created" : changed ? "updated" : "unchanged";
14969
15069
  if (!options.dryRun && changed) {
14970
- await (0, import_promises12.mkdir)((0, import_node_path16.join)(options.cwd, CURSOR_RULES_DIR), { recursive: true });
15070
+ await (0, import_promises12.mkdir)((0, import_node_path17.join)(options.cwd, CURSOR_RULES_DIR), { recursive: true });
14971
15071
  await (0, import_promises12.writeFile)(path, rule.content, "utf-8");
14972
15072
  }
14973
15073
  results.push({ target: "cursor", file, path, action });
14974
15074
  }
14975
- const legacyPath = (0, import_node_path16.join)(options.cwd, LEGACY_CURSOR_RULE_FILE);
15075
+ const legacyPath = (0, import_node_path17.join)(options.cwd, LEGACY_CURSOR_RULE_FILE);
14976
15076
  if (!options.dryRun && await fileExists(legacyPath)) {
14977
15077
  await (0, import_promises12.rm)(legacyPath, { force: true });
14978
15078
  }
@@ -14999,12 +15099,12 @@ async function fileExists(path) {
14999
15099
  function isFileNotFoundError(error) {
15000
15100
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
15001
15101
  }
15002
- var import_promises12, import_node_path16, CURSOR_RULES_DIR, LEGACY_CURSOR_RULE_FILE, DOMAIN_PATH_POINTER;
15102
+ var import_promises12, import_node_path17, CURSOR_RULES_DIR, LEGACY_CURSOR_RULE_FILE, DOMAIN_PATH_POINTER;
15003
15103
  var init_cursorRulesPack = __esm({
15004
15104
  "src/commands/cursorRulesPack.ts"() {
15005
15105
  "use strict";
15006
15106
  import_promises12 = require("node:fs/promises");
15007
- import_node_path16 = require("node:path");
15107
+ import_node_path17 = require("node:path");
15008
15108
  init_commands();
15009
15109
  CURSOR_RULES_DIR = ".cursor/rules";
15010
15110
  LEGACY_CURSOR_RULE_FILE = `${CURSOR_RULES_DIR}/viberaven.mdc`;
@@ -15104,8 +15204,8 @@ var init_agentTargets = __esm({
15104
15204
 
15105
15205
  // src/commands/seedPackageJsonScripts.ts
15106
15206
  async function seedPackageJsonScripts(options) {
15107
- const packageJsonPath = (0, import_node_path17.join)(options.cwd, "package.json");
15108
- if (!(0, import_node_fs8.existsSync)(packageJsonPath)) {
15207
+ const packageJsonPath = (0, import_node_path18.join)(options.cwd, "package.json");
15208
+ if (!(0, import_node_fs9.existsSync)(packageJsonPath)) {
15109
15209
  return null;
15110
15210
  }
15111
15211
  const raw = await (0, import_promises13.readFile)(packageJsonPath, "utf-8");
@@ -15142,13 +15242,13 @@ async function seedPackageJsonScripts(options) {
15142
15242
  }
15143
15243
  return { action: "updated", added, skipped, changed: true };
15144
15244
  }
15145
- var import_node_fs8, import_promises13, import_node_path17, VIBERAVEN_PACKAGE_JSON_SCRIPTS;
15245
+ var import_node_fs9, import_promises13, import_node_path18, VIBERAVEN_PACKAGE_JSON_SCRIPTS;
15146
15246
  var init_seedPackageJsonScripts = __esm({
15147
15247
  "src/commands/seedPackageJsonScripts.ts"() {
15148
15248
  "use strict";
15149
- import_node_fs8 = require("node:fs");
15249
+ import_node_fs9 = require("node:fs");
15150
15250
  import_promises13 = require("node:fs/promises");
15151
- import_node_path17 = require("node:path");
15251
+ import_node_path18 = require("node:path");
15152
15252
  init_commands();
15153
15253
  VIBERAVEN_PACKAGE_JSON_SCRIPTS = {
15154
15254
  "viberaven:gate": PUBLIC_AGENT_MODE_COMMAND,
@@ -15168,12 +15268,12 @@ async function initAgentRules(options) {
15168
15268
  continue;
15169
15269
  }
15170
15270
  const file = AGENT_RULE_TARGETS[target].file;
15171
- const path = (0, import_node_path18.join)(options.cwd, file);
15271
+ const path = (0, import_node_path19.join)(options.cwd, file);
15172
15272
  const existing = await readExistingFile2(path);
15173
15273
  const injected = injectAgentRulesBlock(existing.content, renderAgentRulesForTarget(target));
15174
15274
  const action = !existing.exists ? "created" : injected.changed ? "updated" : "unchanged";
15175
15275
  if (!options.dryRun && injected.changed) {
15176
- await (0, import_promises14.mkdir)((0, import_node_path18.dirname)(path), { recursive: true });
15276
+ await (0, import_promises14.mkdir)((0, import_node_path19.dirname)(path), { recursive: true });
15177
15277
  await (0, import_promises14.writeFile)(path, injected.content, "utf-8");
15178
15278
  }
15179
15279
  results.push({ target, file, path, action });
@@ -15253,12 +15353,12 @@ async function readExistingFile2(path) {
15253
15353
  function isFileNotFoundError2(error) {
15254
15354
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
15255
15355
  }
15256
- var import_promises14, import_node_path18;
15356
+ var import_promises14, import_node_path19;
15257
15357
  var init_initRules = __esm({
15258
15358
  "src/commands/initRules.ts"() {
15259
15359
  "use strict";
15260
15360
  import_promises14 = require("node:fs/promises");
15261
- import_node_path18 = require("node:path");
15361
+ import_node_path19 = require("node:path");
15262
15362
  init_agentRulesBlock();
15263
15363
  init_agentTargets();
15264
15364
  init_cursorRulesPack();
@@ -15521,7 +15621,7 @@ async function runInteractiveSession(startDir = process.cwd()) {
15521
15621
  Ie(`${import_picocolors4.default.bold("VibeRaven")} ${import_picocolors4.default.dim(VERSION)}`);
15522
15622
  const cwd = await resolveWorkspaceRoot(startDir);
15523
15623
  const artifactsAt = await findArtifactsWorkspace(startDir);
15524
- if (artifactsAt && (0, import_node_path19.resolve)(artifactsAt) !== (0, import_node_path19.resolve)(startDir)) {
15624
+ if (artifactsAt && (0, import_node_path20.resolve)(artifactsAt) !== (0, import_node_path20.resolve)(startDir)) {
15525
15625
  M2.message(import_picocolors4.default.dim(`Using scan from: ${artifactsAt}`));
15526
15626
  } else {
15527
15627
  M2.message(import_picocolors4.default.dim(`Project folder: ${cwd}`));
@@ -15580,11 +15680,11 @@ async function runInteractiveSession(startDir = process.cwd()) {
15580
15680
  }
15581
15681
  Se(import_picocolors4.default.dim("Run viberaven anytime for the interactive menu."));
15582
15682
  }
15583
- var import_node_path19, import_picocolors4;
15683
+ var import_node_path20, import_picocolors4;
15584
15684
  var init_runInteractive = __esm({
15585
15685
  "src/tui/runInteractive.ts"() {
15586
15686
  "use strict";
15587
- import_node_path19 = require("node:path");
15687
+ import_node_path20 = require("node:path");
15588
15688
  init_dist2();
15589
15689
  import_picocolors4 = __toESM(require_picocolors());
15590
15690
  init_config();
@@ -15609,18 +15709,18 @@ var init_runInteractive = __esm({
15609
15709
  // src/commands/condense.ts
15610
15710
  async function runCondenseCommand(options) {
15611
15711
  const dir = getProjectArtifactsDir(options.cwd);
15612
- const artifact = JSON.parse(await (0, import_promises15.readFile)((0, import_node_path20.join)(dir, "last-scan.json"), "utf8"));
15613
- const contextMapPath = (0, import_node_path20.join)(dir, "context-map.json");
15712
+ const artifact = JSON.parse(await (0, import_promises15.readFile)((0, import_node_path21.join)(dir, "last-scan.json"), "utf8"));
15713
+ const contextMapPath = (0, import_node_path21.join)(dir, "context-map.json");
15614
15714
  await (0, import_promises15.writeFile)(contextMapPath, `${JSON.stringify(generateContextMap(artifact), null, 2)}
15615
15715
  `, "utf8");
15616
15716
  return { contextMapPath };
15617
15717
  }
15618
- var import_promises15, import_node_path20;
15718
+ var import_promises15, import_node_path21;
15619
15719
  var init_condense = __esm({
15620
15720
  "src/commands/condense.ts"() {
15621
15721
  "use strict";
15622
15722
  import_promises15 = require("node:fs/promises");
15623
- import_node_path20 = require("node:path");
15723
+ import_node_path21 = require("node:path");
15624
15724
  init_config();
15625
15725
  init_contextMap();
15626
15726
  }
@@ -15628,9 +15728,9 @@ var init_condense = __esm({
15628
15728
 
15629
15729
  // src/heal/pathSafety.ts
15630
15730
  function assertSafeHealTarget(cwd, target) {
15631
- const root = (0, import_node_path21.resolve)(cwd);
15632
- const absolute = (0, import_node_path21.resolve)(root, target);
15633
- const rel = (0, import_node_path21.relative)(root, absolute);
15731
+ const root = (0, import_node_path22.resolve)(cwd);
15732
+ const absolute = (0, import_node_path22.resolve)(root, target);
15733
+ const rel = (0, import_node_path22.relative)(root, absolute);
15634
15734
  if (rel.startsWith("..") || rel === "" || /^[A-Za-z]:/.test(rel)) {
15635
15735
  throw new Error("Heal target must stay inside the workspace");
15636
15736
  }
@@ -15640,11 +15740,11 @@ function assertSafeHealTarget(cwd, target) {
15640
15740
  }
15641
15741
  return absolute;
15642
15742
  }
15643
- var import_node_path21, BLOCKED_SEGMENTS;
15743
+ var import_node_path22, BLOCKED_SEGMENTS;
15644
15744
  var init_pathSafety = __esm({
15645
15745
  "src/heal/pathSafety.ts"() {
15646
15746
  "use strict";
15647
- import_node_path21 = require("node:path");
15747
+ import_node_path22 = require("node:path");
15648
15748
  BLOCKED_SEGMENTS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", ".viberaven"]);
15649
15749
  }
15650
15750
  });
@@ -15665,6 +15765,35 @@ var init_emptyCatch = __esm({
15665
15765
  }
15666
15766
  });
15667
15767
 
15768
+ // src/heal/healId.ts
15769
+ function newHealId(now = /* @__PURE__ */ new Date()) {
15770
+ return `heal_${now.toISOString().replace(/\D/g, "").slice(0, 17)}_${(0, import_node_crypto.randomBytes)(3).toString("hex")}`;
15771
+ }
15772
+ async function createHealDir(cwd, makeId = () => newHealId()) {
15773
+ const parent = (0, import_node_path23.join)(cwd, ".viberaven", "heal");
15774
+ await (0, import_promises16.mkdir)(parent, { recursive: true });
15775
+ for (let attempt = 0; attempt < 10; attempt += 1) {
15776
+ const id = makeId();
15777
+ const dir = (0, import_node_path23.join)(parent, id);
15778
+ try {
15779
+ await (0, import_promises16.mkdir)(dir);
15780
+ return { id, dir };
15781
+ } catch (error) {
15782
+ if (error.code !== "EEXIST") throw error;
15783
+ }
15784
+ }
15785
+ throw new Error("Could not create a new heal directory under .viberaven/heal.");
15786
+ }
15787
+ var import_node_crypto, import_promises16, import_node_path23;
15788
+ var init_healId = __esm({
15789
+ "src/heal/healId.ts"() {
15790
+ "use strict";
15791
+ import_node_crypto = require("node:crypto");
15792
+ import_promises16 = require("node:fs/promises");
15793
+ import_node_path23 = require("node:path");
15794
+ }
15795
+ });
15796
+
15668
15797
  // src/heal/recipes/envAuthSecret.ts
15669
15798
  function applyAuthSecretRecipe(source) {
15670
15799
  if (/^\s*(AUTH_SECRET|NEXTAUTH_SECRET)\s*=/m.test(source)) {
@@ -15907,24 +16036,46 @@ export default function NotFound() {
15907
16036
  });
15908
16037
 
15909
16038
  // src/heal/recipes/supabaseRls.ts
15910
- function applyRlsRecipe(_source) {
15911
- return {
15912
- changed: false,
15913
- output: _source,
15914
- canAutoApply: false,
15915
- reason: "dashboard-only",
15916
- providerAction: {
15917
- provider: "supabase",
15918
- dashboardUrl: "https://supabase.com/dashboard/project/{{PROJECT_REF}}/auth/policies",
15919
- exactStep: 'Enable Row Level Security (RLS) on each table under the "Table Editor" or "Auth > Policies" section.',
15920
- doneSignal: "RLS toggle is green for all public tables",
15921
- verifyCommand: "npx -y viberaven audit --vercel-supabase --json"
16039
+ async function planRlsMigration(cwd, now = /* @__PURE__ */ new Date()) {
16040
+ const audit = buildVercelSupabaseAudit(await collectVercelSupabaseAuditInput(cwd));
16041
+ const check = audit.checks.find((entry) => entry.id === "supabase-rls-policy-proof");
16042
+ const missing = (check?.findings ?? []).filter((finding) => finding.id === "rls_disabled" && finding.table && finding.file);
16043
+ if (missing.length === 0) {
16044
+ if (check?.status === "needs_work") {
16045
+ return {
16046
+ kind: "refused",
16047
+ reason: "No SQL evidence in the repo: no migration here creates the tables or enables row level security, so there is nothing in the repo to patch. Add migrations that create your tables with row level security enabled, or check RLS in the Supabase dashboard."
16048
+ };
15922
16049
  }
16050
+ return { kind: "nothing-to-do" };
16051
+ }
16052
+ const tables = [...new Set(missing.map((finding) => finding.table))];
16053
+ const evidence = missing.map((finding) => ({
16054
+ file: finding.file,
16055
+ table: finding.table,
16056
+ turnedOff: finding.message.includes(RLS_TURNED_OFF_PHRASE)
16057
+ }));
16058
+ const target = rlsMigrationTarget(cwd, evidence, now);
16059
+ if (!target.ok) return { kind: "refused", reason: target.reason };
16060
+ return {
16061
+ kind: "migration",
16062
+ file: target.file,
16063
+ sql: [RLS_MIGRATION_NOTE, "", ...rlsEnableStatements(tables), ""].join("\n"),
16064
+ tables
15923
16065
  };
15924
16066
  }
16067
+ var RLS_MIGRATION_NOTE;
15925
16068
  var init_supabaseRls2 = __esm({
15926
16069
  "src/heal/recipes/supabaseRls.ts"() {
15927
16070
  "use strict";
16071
+ init_audit();
16072
+ init_rlsMigrationTarget();
16073
+ RLS_MIGRATION_NOTE = [
16074
+ "-- Added by VibeRaven (heal rls_disabled). Enables row level security on the public tables that the migrations in",
16075
+ "-- this repo create without it. It adds no policies: until you add them, the anon and authenticated roles get no",
16076
+ "-- rows from these tables through the Data API, and only the service role can read or write them. Add the policies",
16077
+ "-- the app needs, then apply this migration the way this project applies migrations."
16078
+ ].join("\n");
15928
16079
  }
15929
16080
  });
15930
16081
 
@@ -16106,7 +16257,7 @@ export const config = {
16106
16257
  // src/heal/recipes/eslintRestrictedImports.ts
16107
16258
  function detectEslintConfigFile(cwd) {
16108
16259
  for (const candidate of ESLINT_CONFIG_CANDIDATES) {
16109
- if ((0, import_node_fs9.existsSync)((0, import_node_path22.join)(cwd, candidate))) {
16260
+ if ((0, import_node_fs10.existsSync)((0, import_node_path24.join)(cwd, candidate))) {
16110
16261
  return candidate;
16111
16262
  }
16112
16263
  }
@@ -16261,12 +16412,12 @@ function applyEslintRestrictedImportsRecipe(source, configFile) {
16261
16412
  }
16262
16413
  return applyLegacyModuleRecipe(source);
16263
16414
  }
16264
- var import_node_fs9, import_node_path22, VIBERAVEN_ESLINT_MARKER, RESTRICTED_IMPORTS_MESSAGE, RESTRICTED_PATHS, RESTRICTED_PATTERNS, ESLINT_CONFIG_CANDIDATES;
16415
+ var import_node_fs10, import_node_path24, VIBERAVEN_ESLINT_MARKER, RESTRICTED_IMPORTS_MESSAGE, RESTRICTED_PATHS, RESTRICTED_PATTERNS, ESLINT_CONFIG_CANDIDATES;
16265
16416
  var init_eslintRestrictedImports = __esm({
16266
16417
  "src/heal/recipes/eslintRestrictedImports.ts"() {
16267
16418
  "use strict";
16268
- import_node_fs9 = require("node:fs");
16269
- import_node_path22 = require("node:path");
16419
+ import_node_fs10 = require("node:fs");
16420
+ import_node_path24 = require("node:path");
16270
16421
  init_commands();
16271
16422
  VIBERAVEN_ESLINT_MARKER = "VibeRaven heal: eslint_restricted_imports";
16272
16423
  RESTRICTED_IMPORTS_MESSAGE = `Restricted import. Run ${PUBLIC_AGENT_MODE_COMMAND} before substituting packages.`;
@@ -16309,7 +16460,7 @@ function defaultTargetFile(gapId) {
16309
16460
  missing_loading_state: "app/loading.tsx",
16310
16461
  missing_404_page: "app/not-found.tsx",
16311
16462
  rls_disabled: "",
16312
- // no file — provider-action
16463
+ // a new migration, named by planRlsMigration
16313
16464
  missing_csp_header: "next.config.js",
16314
16465
  missing_rate_limit: "middleware.ts",
16315
16466
  eslint_restricted_imports: ""
@@ -16317,14 +16468,14 @@ function defaultTargetFile(gapId) {
16317
16468
  return map[gapId];
16318
16469
  }
16319
16470
  async function readSourceOrEmpty(absolutePath) {
16320
- if (!(0, import_node_fs10.existsSync)(absolutePath)) return "";
16321
- return (0, import_promises16.readFile)(absolutePath, "utf8");
16471
+ if (!(0, import_node_fs11.existsSync)(absolutePath)) return "";
16472
+ return (0, import_promises17.readFile)(absolutePath, "utf8");
16322
16473
  }
16323
16474
  async function detectUpstash(cwd) {
16324
16475
  try {
16325
- const pkgPath = (0, import_node_path23.join)(cwd, "package.json");
16326
- if (!(0, import_node_fs10.existsSync)(pkgPath)) return false;
16327
- const raw = await (0, import_promises16.readFile)(pkgPath, "utf8");
16476
+ const pkgPath = (0, import_node_path25.join)(cwd, "package.json");
16477
+ if (!(0, import_node_fs11.existsSync)(pkgPath)) return false;
16478
+ const raw = await (0, import_promises17.readFile)(pkgPath, "utf8");
16328
16479
  const pkg = JSON.parse(raw);
16329
16480
  const deps = {
16330
16481
  ...pkg["dependencies"] ?? {},
@@ -16339,7 +16490,7 @@ async function dispatchRecipeByGapId(gapId, cwd, explicitTarget) {
16339
16490
  const targetFile = explicitTarget ?? defaultTargetFile(gapId);
16340
16491
  if (targetFile === void 0) return null;
16341
16492
  if (gapId === "auth_secret_missing" || gapId === "node_env_not_set" || gapId === "database_url_missing") {
16342
- const absolutePath = (0, import_node_path23.join)(cwd, targetFile);
16493
+ const absolutePath = (0, import_node_path25.join)(cwd, targetFile);
16343
16494
  const source = await readSourceOrEmpty(absolutePath);
16344
16495
  let result;
16345
16496
  if (gapId === "auth_secret_missing") result = applyAuthSecretRecipe(source);
@@ -16353,46 +16504,45 @@ async function dispatchRecipeByGapId(gapId, cwd, explicitTarget) {
16353
16504
  };
16354
16505
  }
16355
16506
  if (gapId === "missing_error_boundary") {
16356
- const absolutePath = (0, import_node_path23.join)(cwd, "app/error.tsx");
16507
+ const absolutePath = (0, import_node_path25.join)(cwd, "app/error.tsx");
16357
16508
  const source = await readSourceOrEmpty(absolutePath);
16358
16509
  const result = applyErrorBoundaryRecipe(source);
16359
16510
  return { ...result, canAutoApply: true, recipeName: gapId };
16360
16511
  }
16361
16512
  if (gapId === "missing_health_route") {
16362
- const absolutePath = (0, import_node_path23.join)(cwd, "app/api/health/route.ts");
16513
+ const absolutePath = (0, import_node_path25.join)(cwd, "app/api/health/route.ts");
16363
16514
  const source = await readSourceOrEmpty(absolutePath);
16364
16515
  const result = applyHealthRouteRecipe(source);
16365
16516
  return { ...result, canAutoApply: true, recipeName: gapId };
16366
16517
  }
16367
16518
  if (gapId === "missing_loading_state") {
16368
- const absolutePath = (0, import_node_path23.join)(cwd, "app/loading.tsx");
16519
+ const absolutePath = (0, import_node_path25.join)(cwd, "app/loading.tsx");
16369
16520
  const source = await readSourceOrEmpty(absolutePath);
16370
16521
  const result = applyLoadingStateRecipe(source);
16371
16522
  return { ...result, canAutoApply: true, recipeName: gapId };
16372
16523
  }
16373
16524
  if (gapId === "missing_404_page") {
16374
- const absolutePath = (0, import_node_path23.join)(cwd, "app/not-found.tsx");
16525
+ const absolutePath = (0, import_node_path25.join)(cwd, "app/not-found.tsx");
16375
16526
  const source = await readSourceOrEmpty(absolutePath);
16376
16527
  const result = applyNotFoundRecipe(source);
16377
16528
  return { ...result, canAutoApply: true, recipeName: gapId };
16378
16529
  }
16379
16530
  if (gapId === "rls_disabled") {
16380
- const result = applyRlsRecipe("");
16381
- return {
16382
- changed: false,
16383
- output: "",
16384
- targetFile: "",
16385
- canAutoApply: false,
16386
- reason: result.reason,
16387
- recipeName: gapId
16388
- };
16531
+ const plan = await planRlsMigration(cwd);
16532
+ if (plan.kind === "migration") {
16533
+ return { changed: true, output: plan.sql, targetFile: plan.file, canAutoApply: true, recipeName: gapId, warnings: [RLS_NO_POLICIES_WARNING] };
16534
+ }
16535
+ if (plan.kind === "nothing-to-do") {
16536
+ return { changed: false, output: "", targetFile: "", canAutoApply: true, recipeName: gapId };
16537
+ }
16538
+ return { changed: false, output: "", targetFile: "", canAutoApply: false, reason: plan.reason, recipeName: gapId };
16389
16539
  }
16390
16540
  if (gapId === "missing_csp_header") {
16391
16541
  let configFile = "next.config.js";
16392
- let absolutePath = (0, import_node_path23.join)(cwd, configFile);
16393
- if (!(0, import_node_fs10.existsSync)(absolutePath)) {
16542
+ let absolutePath = (0, import_node_path25.join)(cwd, configFile);
16543
+ if (!(0, import_node_fs11.existsSync)(absolutePath)) {
16394
16544
  configFile = "next.config.mjs";
16395
- absolutePath = (0, import_node_path23.join)(cwd, configFile);
16545
+ absolutePath = (0, import_node_path25.join)(cwd, configFile);
16396
16546
  }
16397
16547
  const source = await readSourceOrEmpty(absolutePath);
16398
16548
  const result = applyCspHeaderRecipe(source);
@@ -16404,7 +16554,7 @@ async function dispatchRecipeByGapId(gapId, cwd, explicitTarget) {
16404
16554
  }
16405
16555
  if (gapId === "missing_rate_limit") {
16406
16556
  const hasUpstash = await detectUpstash(cwd);
16407
- const middlewarePath = (0, import_node_path23.join)(cwd, "middleware.ts");
16557
+ const middlewarePath = (0, import_node_path25.join)(cwd, "middleware.ts");
16408
16558
  const source = await readSourceOrEmpty(middlewarePath);
16409
16559
  const result = applyRateLimitRecipe(source, hasUpstash);
16410
16560
  return {
@@ -16426,7 +16576,7 @@ async function dispatchRecipeByGapId(gapId, cwd, explicitTarget) {
16426
16576
  recipeName: gapId
16427
16577
  };
16428
16578
  }
16429
- const absolutePath = (0, import_node_path23.join)(cwd, configFile);
16579
+ const absolutePath = (0, import_node_path25.join)(cwd, configFile);
16430
16580
  const source = await readSourceOrEmpty(absolutePath);
16431
16581
  const result = applyEslintRestrictedImportsRecipe(source, configFile);
16432
16582
  return {
@@ -16440,13 +16590,13 @@ async function dispatchRecipeByGapId(gapId, cwd, explicitTarget) {
16440
16590
  }
16441
16591
  return null;
16442
16592
  }
16443
- var import_node_fs10, import_promises16, import_node_path23;
16593
+ var import_node_fs11, import_promises17, import_node_path25;
16444
16594
  var init_recipes = __esm({
16445
16595
  "src/heal/recipes/index.ts"() {
16446
16596
  "use strict";
16447
- import_node_fs10 = require("node:fs");
16448
- import_promises16 = require("node:fs/promises");
16449
- import_node_path23 = require("node:path");
16597
+ import_node_fs11 = require("node:fs");
16598
+ import_promises17 = require("node:fs/promises");
16599
+ import_node_path25 = require("node:path");
16450
16600
  init_envAuthSecret();
16451
16601
  init_envNodeEnv();
16452
16602
  init_envDatabaseUrl();
@@ -16455,6 +16605,7 @@ var init_recipes = __esm({
16455
16605
  init_nextjsLoadingState();
16456
16606
  init_nextjsNotFound();
16457
16607
  init_supabaseRls2();
16608
+ init_rlsMigrationTarget();
16458
16609
  init_nextjsCspHeader();
16459
16610
  init_nextjsRateLimit();
16460
16611
  init_eslintRestrictedImports();
@@ -16462,11 +16613,12 @@ var init_recipes = __esm({
16462
16613
  });
16463
16614
 
16464
16615
  // src/heal/apply.ts
16465
- function healId() {
16466
- return `heal_${(/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14)}`;
16616
+ function rollbackInstructions(id, file, existedBefore) {
16617
+ if (!existedBefore) return `Delete ${file}: it did not exist before this heal.`;
16618
+ return `Copy .viberaven/heal/${id}/before/target.txt back over ${file}, or apply the reverse of .viberaven/heal/${id}/patch.diff.`;
16467
16619
  }
16468
16620
  async function applyHeal(options) {
16469
- const id = healId();
16621
+ let id = newHealId();
16470
16622
  if (!options.yes) {
16471
16623
  return {
16472
16624
  $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
@@ -16521,14 +16673,17 @@ async function applyHeal(options) {
16521
16673
  rollback: { available: false, instructions: "Recipe matched but no change was needed (already applied or file already exists)." }
16522
16674
  };
16523
16675
  }
16524
- const absoluteTarget = (0, import_node_path24.join)(options.cwd, dispatched.targetFile);
16676
+ const absoluteTarget = (0, import_node_path26.join)(options.cwd, dispatched.targetFile);
16525
16677
  assertSafeHealTarget(options.cwd, dispatched.targetFile);
16526
- await (0, import_promises17.mkdir)((0, import_node_path24.dirname)(absoluteTarget), { recursive: true });
16527
- const healDir2 = (0, import_node_path24.join)(options.cwd, ".viberaven", "heal", id);
16528
- await (0, import_promises17.mkdir)((0, import_node_path24.join)(healDir2, "before"), { recursive: true });
16529
- const beforeContent = (0, import_node_fs11.existsSync)(absoluteTarget) ? await (0, import_promises17.readFile)(absoluteTarget, "utf8") : "";
16530
- await (0, import_promises17.writeFile)((0, import_node_path24.join)(healDir2, "before", "target.txt"), beforeContent, "utf8");
16531
- await (0, import_promises17.writeFile)(absoluteTarget, dispatched.output, "utf8");
16678
+ await (0, import_promises18.mkdir)((0, import_node_path26.dirname)(absoluteTarget), { recursive: true });
16679
+ const created2 = await createHealDir(options.cwd);
16680
+ id = created2.id;
16681
+ const healDir2 = created2.dir;
16682
+ await (0, import_promises18.mkdir)((0, import_node_path26.join)(healDir2, "before"));
16683
+ const existedBefore = (0, import_node_fs12.existsSync)(absoluteTarget);
16684
+ const beforeContent = existedBefore ? await (0, import_promises18.readFile)(absoluteTarget, "utf8") : "";
16685
+ await (0, import_promises18.writeFile)((0, import_node_path26.join)(healDir2, "before", "target.txt"), beforeContent, "utf8");
16686
+ await (0, import_promises18.writeFile)(absoluteTarget, dispatched.output, "utf8");
16532
16687
  const patch2 = [
16533
16688
  `--- ${dispatched.targetFile}`,
16534
16689
  `+++ ${dispatched.targetFile}`,
@@ -16538,7 +16693,7 @@ async function applyHeal(options) {
16538
16693
  dispatched.output,
16539
16694
  ""
16540
16695
  ].join("\n");
16541
- await (0, import_promises17.writeFile)((0, import_node_path24.join)(healDir2, "patch.diff"), patch2, "utf8");
16696
+ await (0, import_promises18.writeFile)((0, import_node_path26.join)(healDir2, "patch.diff"), patch2, "utf8");
16542
16697
  const result2 = {
16543
16698
  $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
16544
16699
  schemaVersion: "v1",
@@ -16548,18 +16703,19 @@ async function applyHeal(options) {
16548
16703
  status: "applied_verify_not_run",
16549
16704
  gapId: options.gapId,
16550
16705
  recipe: dispatched.recipeName,
16706
+ ...dispatched.warnings?.length ? { warnings: dispatched.warnings } : {},
16551
16707
  target: dispatched.targetFile,
16552
- changedFiles: [(0, import_node_path24.relative)(options.cwd, absoluteTarget).replace(/\\/g, "/")],
16708
+ changedFiles: [(0, import_node_path26.relative)(options.cwd, absoluteTarget).replace(/\\/g, "/")],
16553
16709
  artifacts: {
16554
16710
  patch: `.viberaven/heal/${id}/patch.diff`,
16555
16711
  result: `.viberaven/heal/${id}/result.json`
16556
16712
  },
16557
16713
  rollback: {
16558
16714
  available: true,
16559
- instructions: "Restore .viberaven/heal/<healId>/before/target.txt to the target file or apply the reverse patch."
16715
+ instructions: rollbackInstructions(id, (0, import_node_path26.relative)(options.cwd, absoluteTarget).replace(/\\/g, "/"), existedBefore)
16560
16716
  }
16561
16717
  };
16562
- await (0, import_promises17.writeFile)((0, import_node_path24.join)(healDir2, "result.json"), `${JSON.stringify(result2, null, 2)}
16718
+ await (0, import_promises18.writeFile)((0, import_node_path26.join)(healDir2, "result.json"), `${JSON.stringify(result2, null, 2)}
16563
16719
  `, "utf8");
16564
16720
  return result2;
16565
16721
  }
@@ -16579,7 +16735,7 @@ async function applyHeal(options) {
16579
16735
  };
16580
16736
  }
16581
16737
  const absolute = assertSafeHealTarget(options.cwd, options.target);
16582
- const before = await (0, import_promises17.readFile)(absolute, "utf8");
16738
+ const before = await (0, import_promises18.readFile)(absolute, "utf8");
16583
16739
  const recipe = applyEmptyCatchRecipe(before);
16584
16740
  if (!recipe.changed) {
16585
16741
  return {
@@ -16596,10 +16752,12 @@ async function applyHeal(options) {
16596
16752
  rollback: { available: false, instructions: "No supported heal recipe matched this file." }
16597
16753
  };
16598
16754
  }
16599
- const healDir = (0, import_node_path24.join)(options.cwd, ".viberaven", "heal", id);
16600
- await (0, import_promises17.mkdir)((0, import_node_path24.join)(healDir, "before"), { recursive: true });
16601
- await (0, import_promises17.writeFile)((0, import_node_path24.join)(healDir, "before", "target.txt"), before, "utf8");
16602
- await (0, import_promises17.writeFile)(absolute, recipe.output, "utf8");
16755
+ const created = await createHealDir(options.cwd);
16756
+ id = created.id;
16757
+ const healDir = created.dir;
16758
+ await (0, import_promises18.mkdir)((0, import_node_path26.join)(healDir, "before"));
16759
+ await (0, import_promises18.writeFile)((0, import_node_path26.join)(healDir, "before", "target.txt"), before, "utf8");
16760
+ await (0, import_promises18.writeFile)(absolute, recipe.output, "utf8");
16603
16761
  const patch = [
16604
16762
  `--- ${options.target}`,
16605
16763
  `+++ ${options.target}`,
@@ -16609,7 +16767,7 @@ async function applyHeal(options) {
16609
16767
  recipe.output,
16610
16768
  ""
16611
16769
  ].join("\n");
16612
- await (0, import_promises17.writeFile)((0, import_node_path24.join)(healDir, "patch.diff"), patch, "utf8");
16770
+ await (0, import_promises18.writeFile)((0, import_node_path26.join)(healDir, "patch.diff"), patch, "utf8");
16613
16771
  const result = {
16614
16772
  $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
16615
16773
  schemaVersion: "v1",
@@ -16620,42 +16778,65 @@ async function applyHeal(options) {
16620
16778
  gapId: options.gapId,
16621
16779
  recipe: "empty-catch-safe-response",
16622
16780
  target: options.target,
16623
- changedFiles: [(0, import_node_path24.relative)(options.cwd, absolute).replace(/\\/g, "/")],
16781
+ changedFiles: [(0, import_node_path26.relative)(options.cwd, absolute).replace(/\\/g, "/")],
16624
16782
  artifacts: {
16625
16783
  patch: `.viberaven/heal/${id}/patch.diff`,
16626
16784
  result: `.viberaven/heal/${id}/result.json`
16627
16785
  },
16628
16786
  rollback: {
16629
16787
  available: true,
16630
- instructions: "Restore .viberaven/heal/<healId>/before/target.txt to the target file or apply the reverse patch."
16788
+ // The empty-catch recipe only edits a file that already exists.
16789
+ instructions: rollbackInstructions(id, (0, import_node_path26.relative)(options.cwd, absolute).replace(/\\/g, "/"), true)
16631
16790
  }
16632
16791
  };
16633
- await (0, import_promises17.writeFile)((0, import_node_path24.join)(healDir, "result.json"), `${JSON.stringify(result, null, 2)}
16792
+ await (0, import_promises18.writeFile)((0, import_node_path26.join)(healDir, "result.json"), `${JSON.stringify(result, null, 2)}
16634
16793
  `, "utf8");
16635
16794
  return result;
16636
16795
  }
16637
- var import_promises17, import_node_fs11, import_node_path24;
16796
+ var import_promises18, import_node_fs12, import_node_path26;
16638
16797
  var init_apply = __esm({
16639
16798
  "src/heal/apply.ts"() {
16640
16799
  "use strict";
16641
- import_promises17 = require("node:fs/promises");
16642
- import_node_fs11 = require("node:fs");
16643
- import_node_path24 = require("node:path");
16800
+ import_promises18 = require("node:fs/promises");
16801
+ import_node_fs12 = require("node:fs");
16802
+ import_node_path26 = require("node:path");
16644
16803
  init_pathSafety();
16645
16804
  init_emptyCatch();
16805
+ init_healId();
16646
16806
  init_recipes();
16647
16807
  }
16648
16808
  });
16649
16809
 
16650
16810
  // src/heal/plan.ts
16651
- function healId2() {
16652
- return `heal_${(/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14)}`;
16653
- }
16654
16811
  async function writeHealPlan(options) {
16655
- const dir = (0, import_node_path25.join)(options.cwd, ".viberaven");
16656
- await (0, import_promises18.mkdir)(dir, { recursive: true });
16657
- const id = healId2();
16812
+ const dir = (0, import_node_path27.join)(options.cwd, ".viberaven");
16813
+ await (0, import_promises19.mkdir)(dir, { recursive: true });
16814
+ const id = newHealId();
16658
16815
  const target = options.target ?? `gap:${options.gapId}`;
16816
+ const proposal = [];
16817
+ let proposedFile;
16818
+ if (!options.target && options.gapId === "rls_disabled") {
16819
+ const rls = await planRlsMigration(options.cwd);
16820
+ if (rls.kind === "migration") {
16821
+ proposedFile = rls.file;
16822
+ proposal.push(
16823
+ "## Proposed change",
16824
+ "",
16825
+ `New migration: \`${rls.file}\``,
16826
+ "",
16827
+ "```sql",
16828
+ rls.sql.trimEnd(),
16829
+ "```",
16830
+ "",
16831
+ "It adds no policies. Apply it with `npx -y viberaven fix --gap rls_disabled`, add the policies the app needs, then apply the migration to the database the way this project applies migrations.",
16832
+ ""
16833
+ );
16834
+ } else if (rls.kind === "refused") {
16835
+ proposal.push("## No automatic recipe", "", rls.reason, "");
16836
+ } else {
16837
+ proposal.push("## Nothing to change", "", "Every public table the migrations create already has row level security.", "");
16838
+ }
16839
+ }
16659
16840
  const markdown = [
16660
16841
  "# VibeRaven Heal Plan",
16661
16842
  "",
@@ -16664,6 +16845,7 @@ async function writeHealPlan(options) {
16664
16845
  "",
16665
16846
  "This plan is non-destructive. It does not edit source files.",
16666
16847
  "",
16848
+ ...proposal,
16667
16849
  `Verify after manual fix: \`${PUBLIC_VERIFY_COMMAND}\``,
16668
16850
  ""
16669
16851
  ].join("\n");
@@ -16675,34 +16857,33 @@ async function writeHealPlan(options) {
16675
16857
  mode: "plan",
16676
16858
  status: "planned",
16677
16859
  gapId: options.gapId,
16678
- target: options.target,
16860
+ target: options.target ?? proposedFile,
16679
16861
  changedFiles: [],
16680
16862
  artifacts: { plan: ".viberaven/heal-plan.md" },
16681
16863
  rollback: { available: false, instructions: "No source files were changed." }
16682
16864
  };
16683
- await (0, import_promises18.writeFile)((0, import_node_path25.join)(dir, "heal-plan.md"), markdown, "utf8");
16684
- await (0, import_promises18.writeFile)((0, import_node_path25.join)(dir, "heal-plan.json"), `${JSON.stringify(result, null, 2)}
16865
+ await (0, import_promises19.writeFile)((0, import_node_path27.join)(dir, "heal-plan.md"), markdown, "utf8");
16866
+ await (0, import_promises19.writeFile)((0, import_node_path27.join)(dir, "heal-plan.json"), `${JSON.stringify(result, null, 2)}
16685
16867
  `, "utf8");
16686
16868
  return result;
16687
16869
  }
16688
- var import_promises18, import_node_path25;
16870
+ var import_promises19, import_node_path27;
16689
16871
  var init_plan = __esm({
16690
16872
  "src/heal/plan.ts"() {
16691
16873
  "use strict";
16692
- import_promises18 = require("node:fs/promises");
16693
- import_node_path25 = require("node:path");
16874
+ import_promises19 = require("node:fs/promises");
16875
+ init_healId();
16876
+ import_node_path27 = require("node:path");
16694
16877
  init_commands();
16878
+ init_supabaseRls2();
16695
16879
  }
16696
16880
  });
16697
16881
 
16698
16882
  // src/heal/prompt.ts
16699
- function healId3() {
16700
- return `heal_${(/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14)}`;
16701
- }
16702
16883
  async function writeHealPrompt(options) {
16703
- const dir = (0, import_node_path26.join)(options.cwd, ".viberaven");
16704
- await (0, import_promises19.mkdir)(dir, { recursive: true });
16705
- const id = healId3();
16884
+ const dir = (0, import_node_path28.join)(options.cwd, ".viberaven");
16885
+ await (0, import_promises20.mkdir)(dir, { recursive: true });
16886
+ const id = newHealId();
16706
16887
  const target = options.target ?? `gap:${options.gapId}`;
16707
16888
  const prompt = [
16708
16889
  "# VibeRaven Heal Prompt",
@@ -16728,15 +16909,16 @@ async function writeHealPrompt(options) {
16728
16909
  artifacts: { prompt: ".viberaven/heal-prompt.md" },
16729
16910
  rollback: { available: false, instructions: "No source files were changed." }
16730
16911
  };
16731
- await (0, import_promises19.writeFile)((0, import_node_path26.join)(dir, "heal-prompt.md"), prompt, "utf8");
16912
+ await (0, import_promises20.writeFile)((0, import_node_path28.join)(dir, "heal-prompt.md"), prompt, "utf8");
16732
16913
  return result;
16733
16914
  }
16734
- var import_promises19, import_node_path26;
16915
+ var import_promises20, import_node_path28;
16735
16916
  var init_prompt = __esm({
16736
16917
  "src/heal/prompt.ts"() {
16737
16918
  "use strict";
16738
- import_promises19 = require("node:fs/promises");
16739
- import_node_path26 = require("node:path");
16919
+ import_promises20 = require("node:fs/promises");
16920
+ init_healId();
16921
+ import_node_path28 = require("node:path");
16740
16922
  init_commands();
16741
16923
  }
16742
16924
  });
@@ -16747,7 +16929,7 @@ function loopStatePath(workspaceRoot) {
16747
16929
  }
16748
16930
  async function loadLoopState(workspaceRoot) {
16749
16931
  try {
16750
- const raw = await (0, import_promises20.readFile)(loopStatePath(workspaceRoot), "utf8");
16932
+ const raw = await (0, import_promises21.readFile)(loopStatePath(workspaceRoot), "utf8");
16751
16933
  const parsed = JSON.parse(raw);
16752
16934
  if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.batchApplied === "number" && typeof parsed.lastGapCount === "number" && typeof parsed.stalledScans === "number") {
16753
16935
  const p2 = parsed;
@@ -16767,8 +16949,8 @@ async function loadLoopState(workspaceRoot) {
16767
16949
  async function saveLoopState(workspaceRoot, state) {
16768
16950
  try {
16769
16951
  const dir = (0, import_path.join)(workspaceRoot, ".viberaven");
16770
- await (0, import_promises20.mkdir)(dir, { recursive: true });
16771
- await (0, import_promises20.writeFile)(loopStatePath(workspaceRoot), JSON.stringify(state, null, 2) + "\n", "utf8");
16952
+ await (0, import_promises21.mkdir)(dir, { recursive: true });
16953
+ await (0, import_promises21.writeFile)(loopStatePath(workspaceRoot), JSON.stringify(state, null, 2) + "\n", "utf8");
16772
16954
  } catch (err) {
16773
16955
  console.warn("[VibeRaven] Could not save loop-state.json:", err instanceof Error ? err.message : String(err));
16774
16956
  }
@@ -16794,11 +16976,11 @@ function resetBatch(state, newGapCount) {
16794
16976
  appliedGapIdsSinceScan: []
16795
16977
  };
16796
16978
  }
16797
- var import_promises20, import_path, DEFAULT_LOOP_STATE;
16979
+ var import_promises21, import_path, DEFAULT_LOOP_STATE;
16798
16980
  var init_loopState = __esm({
16799
16981
  "src/loopState.ts"() {
16800
16982
  "use strict";
16801
- import_promises20 = require("fs/promises");
16983
+ import_promises21 = require("fs/promises");
16802
16984
  import_path = require("path");
16803
16985
  DEFAULT_LOOP_STATE = {
16804
16986
  batchApplied: 0,
@@ -16839,11 +17021,11 @@ var init_heal = __esm({
16839
17021
 
16840
17022
  // src/stackRecommend.ts
16841
17023
  async function recommendStack(cwd = process.cwd()) {
16842
- const pkgPath = (0, import_node_path27.join)(cwd, "package.json");
16843
- if (!(0, import_node_fs12.existsSync)(pkgPath)) {
17024
+ const pkgPath = (0, import_node_path29.join)(cwd, "package.json");
17025
+ if (!(0, import_node_fs13.existsSync)(pkgPath)) {
16844
17026
  return DEFAULT_STACK;
16845
17027
  }
16846
- const pkg = JSON.parse(await (0, import_promises21.readFile)(pkgPath, "utf-8"));
17028
+ const pkg = JSON.parse(await (0, import_promises22.readFile)(pkgPath, "utf-8"));
16847
17029
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
16848
17030
  const names = Object.keys(deps).join(" ").toLowerCase();
16849
17031
  const rec = {
@@ -16871,13 +17053,13 @@ async function recommendStack(cwd = process.cwd()) {
16871
17053
  }
16872
17054
  return rec;
16873
17055
  }
16874
- var import_promises21, import_node_fs12, import_node_path27, DEFAULT_STACK;
17056
+ var import_promises22, import_node_fs13, import_node_path29, DEFAULT_STACK;
16875
17057
  var init_stackRecommend = __esm({
16876
17058
  "src/stackRecommend.ts"() {
16877
17059
  "use strict";
16878
- import_promises21 = require("node:fs/promises");
16879
- import_node_fs12 = require("node:fs");
16880
- import_node_path27 = require("node:path");
17060
+ import_promises22 = require("node:fs/promises");
17061
+ import_node_fs13 = require("node:fs");
17062
+ import_node_path29 = require("node:path");
16881
17063
  DEFAULT_STACK = {
16882
17064
  frontend: "react",
16883
17065
  ui: "tailwind + shadcn/ui",
@@ -16918,11 +17100,332 @@ var init_runInit = __esm({
16918
17100
  }
16919
17101
  });
16920
17102
 
17103
+ // src/commands/checkChange.ts
17104
+ function findingKey(finding) {
17105
+ return `${finding.id}\0${finding.title}`;
17106
+ }
17107
+ function findingSubjects(finding) {
17108
+ const line = (finding.detail.split("\n").find((part) => part.trim()) ?? "").replace(LOCATION_TEXT, " ");
17109
+ return [...new Set((line.match(SUBJECT) ?? []).map((subject) => subject.toLowerCase()))].sort();
17110
+ }
17111
+ function bySeverity(a, b3) {
17112
+ return SEVERITY_RANK3[a.severity] - SEVERITY_RANK3[b3.severity] || a.title.localeCompare(b3.title);
17113
+ }
17114
+ function compareFindings(base, head, ref, baseCommit) {
17115
+ const baseByKey = new Map(base.map((finding) => [findingKey(finding), finding]));
17116
+ const headKeys = new Set(head.map(findingKey));
17117
+ const introduced = [];
17118
+ const resolved = base.filter((finding) => !headKeys.has(findingKey(finding)));
17119
+ const unchanged = [];
17120
+ for (const finding of head) {
17121
+ const before = baseByKey.get(findingKey(finding));
17122
+ if (!before) {
17123
+ introduced.push(finding);
17124
+ continue;
17125
+ }
17126
+ const was = new Set(findingSubjects(before));
17127
+ const now = findingSubjects(finding);
17128
+ const added = now.filter((subject) => !was.has(subject));
17129
+ const removed = [...was].filter((subject) => !now.includes(subject));
17130
+ if (added.length) introduced.push({ ...finding, subjects: added });
17131
+ else unchanged.push(finding);
17132
+ if (removed.length) resolved.push({ ...before, subjects: removed });
17133
+ }
17134
+ return { base: ref, baseCommit, introduced: introduced.sort(bySeverity), resolved: resolved.sort(bySeverity), unchanged: unchanged.sort(bySeverity) };
17135
+ }
17136
+ async function git(cwd, args) {
17137
+ const call = await safeGitInvocation(cwd, args);
17138
+ const { stdout } = await execFileAsync4("git", call.args, { cwd, env: call.env, windowsHide: true, maxBuffer: 64 * 1024 * 1024 });
17139
+ return stdout;
17140
+ }
17141
+ async function resolveBaseCommit(cwd, ref) {
17142
+ let commit;
17143
+ try {
17144
+ commit = (await git(cwd, ["rev-parse", "--verify", "--end-of-options", `${ref}^{commit}`])).trim();
17145
+ } catch {
17146
+ throw new Error(
17147
+ `Could not find "${ref}" in this repository. Fetch it first (git fetch origin ${ref.replace(/^origin\//, "")}); in GitHub Actions, check out with fetch-depth: 0.`
17148
+ );
17149
+ }
17150
+ try {
17151
+ const mergeBase = (await git(cwd, ["merge-base", commit, "HEAD"])).trim();
17152
+ return mergeBase || commit;
17153
+ } catch {
17154
+ return commit;
17155
+ }
17156
+ }
17157
+ function parseTree(output) {
17158
+ const entries = [];
17159
+ for (const record of output.split("\0")) {
17160
+ if (!record) continue;
17161
+ const tab = record.indexOf(" ");
17162
+ const [mode, type, sha, size] = record.slice(0, tab).trim().split(/\s+/);
17163
+ entries.push({ mode, type, sha, size: Number.parseInt(size, 10) || 0, path: record.slice(tab + 1) });
17164
+ }
17165
+ return entries;
17166
+ }
17167
+ function wanted(entry) {
17168
+ if (entry.type !== "blob" || entry.mode !== "100644" && entry.mode !== "100755") return false;
17169
+ if (entry.size > MAX_BLOB_BYTES) return false;
17170
+ return !entry.path.split("/").some((segment) => SKIP_SEGMENTS.has(segment));
17171
+ }
17172
+ async function readBlobs(cwd, shas) {
17173
+ const blobs = /* @__PURE__ */ new Map();
17174
+ if (!shas.length) return blobs;
17175
+ const call = await safeGitInvocation(cwd, ["cat-file", "--batch"]);
17176
+ const output = await new Promise((resolveOutput, reject) => {
17177
+ const child = (0, import_node_child_process6.spawn)("git", call.args, { cwd, env: call.env, windowsHide: true });
17178
+ const chunks = [];
17179
+ let stderr = "";
17180
+ child.stdout.on("data", (chunk) => chunks.push(chunk));
17181
+ child.stderr.on("data", (chunk) => {
17182
+ stderr += chunk.toString();
17183
+ });
17184
+ child.on("error", reject);
17185
+ child.on("close", (code) => code === 0 ? resolveOutput(Buffer.concat(chunks)) : reject(new Error(stderr.trim() || `git cat-file exited with ${code}`)));
17186
+ child.stdin.end(`${shas.join("\n")}
17187
+ `);
17188
+ });
17189
+ let offset = 0;
17190
+ while (offset < output.length) {
17191
+ const newline = output.indexOf(10, offset);
17192
+ if (newline === -1) break;
17193
+ const [sha, type, sizeText] = output.subarray(offset, newline).toString().split(" ");
17194
+ offset = newline + 1;
17195
+ if (type === "missing" || sizeText === void 0) continue;
17196
+ const size = Number.parseInt(sizeText, 10);
17197
+ blobs.set(sha, output.subarray(offset, offset + size));
17198
+ offset += size + 1;
17199
+ }
17200
+ return blobs;
17201
+ }
17202
+ async function exportCommit(cwd, commit) {
17203
+ const entries = parseTree(await git(cwd, ["ls-tree", "-r", "-z", "-l", "--full-tree", commit])).filter(wanted);
17204
+ const blobs = await readBlobs(cwd, [...new Set(entries.map((entry) => entry.sha))]);
17205
+ const root = await (0, import_promises23.mkdtemp)((0, import_node_path30.join)((0, import_node_os.tmpdir)(), "viberaven-base-"));
17206
+ for (const entry of entries) {
17207
+ const target = (0, import_node_path30.resolve)(root, ...entry.path.split("/"));
17208
+ if (!target.startsWith(root + import_node_path30.sep)) continue;
17209
+ const content = blobs.get(entry.sha);
17210
+ if (!content) continue;
17211
+ await (0, import_promises23.mkdir)((0, import_node_path30.dirname)(target), { recursive: true });
17212
+ await (0, import_promises23.writeFile)(target, content);
17213
+ }
17214
+ return root;
17215
+ }
17216
+ async function checkChange(cwd, ref, head) {
17217
+ const baseCommit = await resolveBaseCommit(cwd, ref);
17218
+ const folder = await exportCommit(cwd, baseCommit);
17219
+ try {
17220
+ const result = await runProjectScan({ workspacePath: folder });
17221
+ if (!result.ok) throw new Error(`The check on ${ref} did not finish: ${result.message}`);
17222
+ return compareFindings(result.artifact.gaps, head, ref, baseCommit);
17223
+ } finally {
17224
+ await (0, import_promises23.rm)(folder, { recursive: true, force: true, maxRetries: 3, retryDelay: 200 }).catch(() => void 0);
17225
+ }
17226
+ }
17227
+ function cell(text) {
17228
+ return text.replace(/\r?\n/g, " ").replace(/\|/g, "\\|").trim();
17229
+ }
17230
+ function firstLine(detail) {
17231
+ const line = detail.split("\n").map((part) => part.trim()).find(Boolean) ?? "";
17232
+ return line.length > 180 ? `${line.slice(0, 177)}\u2026` : line;
17233
+ }
17234
+ function plural2(count, word) {
17235
+ return `${count} ${word}${count === 1 ? "" : "s"}`;
17236
+ }
17237
+ function table(findings) {
17238
+ return [
17239
+ "| | Finding | Check |",
17240
+ "|---|---|---|",
17241
+ ...findings.map((finding) => {
17242
+ const why = finding.subjects?.length ? `New in this change: ${finding.subjects.join(", ")}` : firstLine(finding.detail);
17243
+ return `| ${DOT[finding.severity]} | **${cell(finding.title)}**${why ? `<br>${cell(why)}` : ""} | \`${cell(finding.id)}\` |`;
17244
+ })
17245
+ ];
17246
+ }
17247
+ function fixedLabel(finding) {
17248
+ return `\u2705 ${cell(finding.title)}${finding.subjects?.length ? ` for ${cell(finding.subjects.join(", "))}` : ""} (\`${cell(finding.id)}\`)`;
17249
+ }
17250
+ function renderCheckMarkdown({ head, change, version, score }) {
17251
+ const lines = [COMMENT_MARKER];
17252
+ const sorted = [...head].sort(bySeverity);
17253
+ const critical = sorted.filter((finding) => finding.severity === "critical").length;
17254
+ const warnings = sorted.filter((finding) => finding.severity === "warning").length;
17255
+ if (change) {
17256
+ const newCritical = change.introduced.filter((finding) => finding.severity === "critical").length;
17257
+ const headline = newCritical ? `${plural2(newCritical, "new blocker")} in this change` : change.introduced.length ? `${plural2(change.introduced.length, "new finding")} in this change, no new blockers` : "No new findings in this change";
17258
+ lines.push(`### VibeRaven \xB7 ${headline}`, "");
17259
+ lines.push(`Compared with \`${change.base}\` (${change.baseCommit.slice(0, 7)}).${change.resolved.length ? ` This change fixes ${plural2(change.resolved.length, "finding")}.` : ""}`, "");
17260
+ if (change.introduced.length) lines.push(...table(change.introduced), "");
17261
+ if (change.resolved.length) {
17262
+ lines.push(`**Fixed by this change:** ${change.resolved.map(fixedLabel).join(" \xB7 ")}`, "");
17263
+ }
17264
+ if (change.unchanged.length) {
17265
+ lines.push(`<details><summary>Already in ${cell(change.base)} before this change (${change.unchanged.length})</summary>`, "", ...table(change.unchanged), "", "</details>", "");
17266
+ }
17267
+ const firstNew = change.introduced.find((finding) => finding.severity === "critical") ?? change.introduced[0];
17268
+ if (firstNew) lines.push(`Fix it with your coding agent: \`npx -y viberaven fix --gap ${firstNew.id}\`, or open the Studio with \`npx -y viberaven\`.`, "");
17269
+ } else {
17270
+ const headline = critical ? `${plural2(critical, "blocker")}, ${plural2(warnings, "warning")}` : `No blockers, ${plural2(warnings, "warning")}`;
17271
+ lines.push(`### VibeRaven \xB7 ${headline}`, "");
17272
+ if (sorted.length) lines.push(...table(sorted), "");
17273
+ else lines.push("No launch findings in the repo evidence.", "");
17274
+ }
17275
+ lines.push(
17276
+ `<sub>viberaven ${version}${typeof score === "number" ? ` \xB7 readiness ${score}/100` : ""} \xB7 checked where it ran, nothing was uploaded. Advice, not a gate: a clear result reads the repo, it is not a security audit.</sub>`
17277
+ );
17278
+ return `${lines.join("\n")}
17279
+ `;
17280
+ }
17281
+ var import_node_child_process6, import_promises23, import_node_os, import_node_path30, import_node_util4, execFileAsync4, MAX_BLOB_BYTES, SKIP_SEGMENTS, SEVERITY_RANK3, LOCATION_TEXT, SUBJECT, COMMENT_MARKER, DOT;
17282
+ var init_checkChange = __esm({
17283
+ "src/commands/checkChange.ts"() {
17284
+ "use strict";
17285
+ import_node_child_process6 = require("node:child_process");
17286
+ import_promises23 = require("node:fs/promises");
17287
+ import_node_os = require("node:os");
17288
+ import_node_path30 = require("node:path");
17289
+ import_node_util4 = require("node:util");
17290
+ init_runScan();
17291
+ init_safeGit();
17292
+ execFileAsync4 = (0, import_node_util4.promisify)(import_node_child_process6.execFile);
17293
+ MAX_BLOB_BYTES = 1e6;
17294
+ SKIP_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", ".git", ".next", ".turbo", ".vercel", ".output", "dist", "build", "coverage"]);
17295
+ SEVERITY_RANK3 = { critical: 0, warning: 1, info: 2 };
17296
+ LOCATION_TEXT = /[\w./\\-]+\.(?:sql|tsx?|jsx?|mjs|cjs|json|toml|ya?ml|env|prisma|md)(?::\d+)?/gi;
17297
+ SUBJECT = /\b[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\b/gi;
17298
+ COMMENT_MARKER = "<!-- viberaven-check -->";
17299
+ DOT = { critical: "\u{1F534}", warning: "\u{1F7E1}", info: "\u26AA" };
17300
+ }
17301
+ });
17302
+
17303
+ // src/commands/initGithub.ts
17304
+ function renderWorkflow(version = VERSION) {
17305
+ return [
17306
+ "# VibeRaven checks every pull request in this repository's own GitHub Actions run.",
17307
+ "# The code is read on the runner and never uploaded. Written by `npx -y viberaven init --github`.",
17308
+ "name: VibeRaven",
17309
+ "",
17310
+ "on:",
17311
+ " pull_request:",
17312
+ "",
17313
+ "permissions:",
17314
+ " contents: read",
17315
+ " pull-requests: write",
17316
+ "",
17317
+ "jobs:",
17318
+ " check:",
17319
+ " name: Launch check",
17320
+ " runs-on: ubuntu-latest",
17321
+ " steps:",
17322
+ " - uses: actions/checkout@v4",
17323
+ " with:",
17324
+ " fetch-depth: 0",
17325
+ "",
17326
+ " - uses: actions/setup-node@v4",
17327
+ " with:",
17328
+ " node-version: 20",
17329
+ "",
17330
+ " - name: Check this change",
17331
+ " id: check",
17332
+ " env:",
17333
+ " BASE_REF: ${{ github.base_ref }}",
17334
+ " run: |",
17335
+ " code=0",
17336
+ ` npx -y viberaven@${version} check --base "origin/$BASE_REF" --markdown > viberaven-check.md || code=$?`,
17337
+ ' cat viberaven-check.md >> "$GITHUB_STEP_SUMMARY"',
17338
+ ' echo "code=$code" >> "$GITHUB_OUTPUT"',
17339
+ "",
17340
+ " # Pull requests from forks get a read-only token: they keep the job summary and skip the comment.",
17341
+ " - name: Comment on the pull request",
17342
+ " if: github.event.pull_request.head.repo.full_name == github.repository",
17343
+ " env:",
17344
+ " GH_TOKEN: ${{ github.token }}",
17345
+ " PR: ${{ github.event.pull_request.number }}",
17346
+ " run: |",
17347
+ ` id=$(gh api "repos/$GITHUB_REPOSITORY/issues/$PR/comments" --paginate --jq '.[] | select(.body | contains("${COMMENT_MARKER}")) | .id' | tail -n 1)`,
17348
+ ' if [ -n "$id" ]; then',
17349
+ ' gh api --method PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$id" -F body=@viberaven-check.md > /dev/null',
17350
+ " else",
17351
+ ' gh pr comment "$PR" --body-file viberaven-check.md',
17352
+ " fi",
17353
+ "",
17354
+ " # Advice, not a gate. To block merging when a change adds a launch blocker, uncomment this step",
17355
+ ' # and mark "Launch check" as required in the branch protection settings.',
17356
+ " # - name: Fail on new blockers",
17357
+ " # if: steps.check.outputs.code == '1'",
17358
+ " # run: exit 1",
17359
+ ""
17360
+ ].join("\n");
17361
+ }
17362
+ async function hasGithubRemote(cwd) {
17363
+ try {
17364
+ const call = await safeGitInvocation(cwd, ["remote", "-v"]);
17365
+ const { stdout } = await execFileAsync5("git", call.args, { cwd, env: call.env, windowsHide: true });
17366
+ return /github\.com[:/]/i.test(stdout);
17367
+ } catch {
17368
+ return false;
17369
+ }
17370
+ }
17371
+ async function githubWorkflowStatus(cwd) {
17372
+ return { exists: (0, import_node_fs14.existsSync)((0, import_node_path31.join)(cwd, WORKFLOW_PATH)), path: DISPLAY_PATH, onGithub: await hasGithubRemote(cwd) };
17373
+ }
17374
+ async function writeGithubWorkflow(cwd, options = {}) {
17375
+ const target = (0, import_node_path31.join)(cwd, WORKFLOW_PATH);
17376
+ if ((0, import_node_fs14.existsSync)(target) && !options.force) return { written: false, status: await githubWorkflowStatus(cwd) };
17377
+ await (0, import_promises24.mkdir)((0, import_node_path31.dirname)(target), { recursive: true });
17378
+ await (0, import_promises24.writeFile)(target, renderWorkflow(), "utf8");
17379
+ return { written: true, status: await githubWorkflowStatus(cwd) };
17380
+ }
17381
+ async function runInitGithubCommand(options) {
17382
+ if (options.dryRun) {
17383
+ console.log(`Would write ${DISPLAY_PATH}:
17384
+ `);
17385
+ console.log(renderWorkflow());
17386
+ return 0;
17387
+ }
17388
+ const { written, status } = await writeGithubWorkflow(options.cwd, { force: options.force });
17389
+ if (!written) {
17390
+ console.log(`${DISPLAY_PATH} already exists. Nothing changed. Run with --force to replace it.`);
17391
+ return 0;
17392
+ }
17393
+ console.log(`Wrote ${DISPLAY_PATH}.`);
17394
+ console.log("");
17395
+ console.log("Commit it, and every pull request gets one VibeRaven comment with what the change added or fixed:");
17396
+ console.log(` git add ${DISPLAY_PATH} && git commit -m "Check pull requests with VibeRaven"`);
17397
+ console.log("");
17398
+ console.log("It runs in your own GitHub Actions, so private repositories work and the code is never uploaded.");
17399
+ if (!status.onGithub) console.log("This repository has no github.com remote yet: the workflow runs once it is pushed to GitHub.");
17400
+ return 0;
17401
+ }
17402
+ var import_node_child_process7, import_node_fs14, import_promises24, import_node_path31, import_node_util5, execFileAsync5, WORKFLOW_PATH, DISPLAY_PATH;
17403
+ var init_initGithub = __esm({
17404
+ "src/commands/initGithub.ts"() {
17405
+ "use strict";
17406
+ import_node_child_process7 = require("node:child_process");
17407
+ import_node_fs14 = require("node:fs");
17408
+ import_promises24 = require("node:fs/promises");
17409
+ import_node_path31 = require("node:path");
17410
+ import_node_util5 = require("node:util");
17411
+ init_safeGit();
17412
+ init_version();
17413
+ init_checkChange();
17414
+ execFileAsync5 = (0, import_node_util5.promisify)(import_node_child_process7.execFile);
17415
+ WORKFLOW_PATH = (0, import_node_path31.join)(".github", "workflows", "viberaven.yml");
17416
+ DISPLAY_PATH = ".github/workflows/viberaven.yml";
17417
+ }
17418
+ });
17419
+
16921
17420
  // src/commands/doctorAgents.ts
17421
+ function managedBlocks(content) {
17422
+ const blocks = content.match(/<!-- (?:VIBERAVEN:START|viberaven:agent-rules:start) -->[\s\S]*?<!-- (?:VIBERAVEN:END|viberaven:agent-rules:end) -->/g);
17423
+ return (blocks ?? []).join("\n");
17424
+ }
16922
17425
  async function checkAgentInjection(cwd) {
16923
17426
  const checks = [];
16924
17427
  for (const item4 of REQUIRED_EXISTENCE_CHECKS) {
16925
- const exists = await fileExists2((0, import_node_path28.join)(cwd, item4.file));
17428
+ const exists = await fileExists2((0, import_node_path32.join)(cwd, item4.file));
16926
17429
  checks.push({
16927
17430
  id: item4.id,
16928
17431
  status: exists ? "pass" : "fail",
@@ -16930,17 +17433,17 @@ async function checkAgentInjection(cwd) {
16930
17433
  });
16931
17434
  }
16932
17435
  for (const item4 of OPTIONAL_CURSOR_PACK_CHECKS) {
16933
- const exists = await fileExists2((0, import_node_path28.join)(cwd, item4.file));
17436
+ const exists = await fileExists2((0, import_node_path32.join)(cwd, item4.file));
16934
17437
  checks.push({
16935
17438
  id: item4.id,
16936
17439
  status: exists ? "pass" : "fail",
16937
17440
  message: exists ? `${item4.file} exists` : `Missing ${item4.file} \u2014 run npx -y viberaven init --agents all`
16938
17441
  });
16939
17442
  }
16940
- const legacyCursorPath = (0, import_node_path28.join)(cwd, ".cursor/rules/viberaven.mdc");
17443
+ const legacyCursorPath = (0, import_node_path32.join)(cwd, ".cursor/rules/viberaven.mdc");
16941
17444
  if (await fileExists2(legacyCursorPath)) {
16942
- const legacyContent = await (0, import_promises22.readFile)(legacyCursorPath, "utf-8");
16943
- const hasCoreSplit = await fileExists2((0, import_node_path28.join)(cwd, ".cursor/rules/viberaven-core.mdc"));
17445
+ const legacyContent = await (0, import_promises25.readFile)(legacyCursorPath, "utf-8");
17446
+ const hasCoreSplit = await fileExists2((0, import_node_path32.join)(cwd, ".cursor/rules/viberaven-core.mdc"));
16944
17447
  if (!hasCoreSplit || legacyContent.includes("alwaysApply: true")) {
16945
17448
  checks.push({
16946
17449
  id: "cursor-legacy-mdc",
@@ -16951,7 +17454,7 @@ async function checkAgentInjection(cwd) {
16951
17454
  }
16952
17455
  for (const target of CORE_AGENT_INJECTION_TARGETS) {
16953
17456
  const file = target === "cursor" ? ".cursor/rules/viberaven-core.mdc" : AGENT_RULE_TARGETS[target].file;
16954
- const path = (0, import_node_path28.join)(cwd, file);
17457
+ const path = (0, import_node_path32.join)(cwd, file);
16955
17458
  const exists = await fileExists2(path);
16956
17459
  if (!exists) {
16957
17460
  checks.push({
@@ -16961,7 +17464,7 @@ async function checkAgentInjection(cwd) {
16961
17464
  });
16962
17465
  continue;
16963
17466
  }
16964
- const content = await (0, import_promises22.readFile)(path, "utf-8");
17467
+ const content = await (0, import_promises25.readFile)(path, "utf-8");
16965
17468
  const hasCommand = content.includes(PUBLIC_CHECK_COMMAND);
16966
17469
  checks.push({
16967
17470
  id: `canonical-${target}`,
@@ -16977,6 +17480,15 @@ async function checkAgentInjection(cwd) {
16977
17480
  });
16978
17481
  }
16979
17482
  }
17483
+ const managed = target === "cursor" ? content : managedBlocks(content);
17484
+ const gatePhrase = GATE_RULE_PHRASES_1_5_2.find((phrase) => managed.includes(phrase));
17485
+ if (gatePhrase) {
17486
+ checks.push({
17487
+ id: `stale-gate-rules-${target}`,
17488
+ status: "fail",
17489
+ message: `${file} still has the 1.5.2 gate rules ("${gatePhrase} ..."). Run npx -y viberaven init --agents all to replace them with the current advice block.`
17490
+ });
17491
+ }
16980
17492
  }
16981
17493
  return {
16982
17494
  ok: checks.every((check) => check.status === "pass"),
@@ -16995,18 +17507,18 @@ function formatDoctorAgentsReport(report) {
16995
17507
  }
16996
17508
  async function fileExists2(path) {
16997
17509
  try {
16998
- await (0, import_promises22.access)(path);
17510
+ await (0, import_promises25.access)(path);
16999
17511
  return true;
17000
17512
  } catch {
17001
17513
  return false;
17002
17514
  }
17003
17515
  }
17004
- var import_promises22, import_node_path28, REQUIRED_EXISTENCE_CHECKS, OPTIONAL_CURSOR_PACK_CHECKS, STALE_PATTERNS;
17516
+ var import_promises25, import_node_path32, REQUIRED_EXISTENCE_CHECKS, OPTIONAL_CURSOR_PACK_CHECKS, STALE_PATTERNS, GATE_RULE_PHRASES_1_5_2;
17005
17517
  var init_doctorAgents = __esm({
17006
17518
  "src/commands/doctorAgents.ts"() {
17007
17519
  "use strict";
17008
- import_promises22 = require("node:fs/promises");
17009
- import_node_path28 = require("node:path");
17520
+ import_promises25 = require("node:fs/promises");
17521
+ import_node_path32 = require("node:path");
17010
17522
  init_commands();
17011
17523
  init_agentTargets();
17012
17524
  REQUIRED_EXISTENCE_CHECKS = [
@@ -17025,6 +17537,12 @@ var init_doctorAgents = __esm({
17025
17537
  { id: "stale-viberaven-station", pattern: "viberaven-station" },
17026
17538
  { id: "stale-viberice", pattern: "viberice" }
17027
17539
  ];
17540
+ GATE_RULE_PHRASES_1_5_2 = [
17541
+ "The loop is not done until",
17542
+ "Gate is not clear until",
17543
+ "Do NOT rely on manual production checklists",
17544
+ "Use VibeRaven before launch, deployment, real users"
17545
+ ];
17028
17546
  }
17029
17547
  });
17030
17548
 
@@ -17271,7 +17789,7 @@ function buildContextCleanupPlan(input) {
17271
17789
  const items = [];
17272
17790
  const seen = /* @__PURE__ */ new Set();
17273
17791
  for (const file of input.files) {
17274
- const path = normalizePath5((0, import_node_path29.relative)(input.projectRoot, file.path) || file.path);
17792
+ const path = normalizePath5((0, import_node_path33.relative)(input.projectRoot, file.path) || file.path);
17275
17793
  const fullPath = normalizePath5(file.path);
17276
17794
  const category = categorizeCleanupFile(path, fullPath, file);
17277
17795
  if (!category || seen.has(path)) {
@@ -17297,10 +17815,10 @@ async function collectCleanupFiles(projectRoot) {
17297
17815
  return files;
17298
17816
  }
17299
17817
  async function writeCleanupPlan(projectRoot, plan) {
17300
- const outputDir = (0, import_node_path29.join)(projectRoot, ".viberaven");
17301
- const outputPath = (0, import_node_path29.join)(outputDir, "context-cleanup.md");
17302
- await (0, import_promises23.mkdir)(outputDir, { recursive: true });
17303
- await (0, import_promises23.writeFile)(outputPath, renderCleanupPlan(plan), "utf8");
17818
+ const outputDir = (0, import_node_path33.join)(projectRoot, ".viberaven");
17819
+ const outputPath = (0, import_node_path33.join)(outputDir, "context-cleanup.md");
17820
+ await (0, import_promises26.mkdir)(outputDir, { recursive: true });
17821
+ await (0, import_promises26.writeFile)(outputPath, renderCleanupPlan(plan), "utf8");
17304
17822
  return outputPath;
17305
17823
  }
17306
17824
  function categorizeCleanupFile(relativePath, fullPath, file) {
@@ -17348,7 +17866,7 @@ async function walkCleanupFiles(root, dir, result, depth) {
17348
17866
  }
17349
17867
  let entries;
17350
17868
  try {
17351
- entries = await (0, import_promises23.readdir)(dir, { withFileTypes: true });
17869
+ entries = await (0, import_promises26.readdir)(dir, { withFileTypes: true });
17352
17870
  } catch {
17353
17871
  return;
17354
17872
  }
@@ -17356,9 +17874,9 @@ async function walkCleanupFiles(root, dir, result, depth) {
17356
17874
  if (SKIP_DIRECTORIES.has(entry.name)) {
17357
17875
  continue;
17358
17876
  }
17359
- const absolute = (0, import_node_path29.join)(dir, entry.name);
17877
+ const absolute = (0, import_node_path33.join)(dir, entry.name);
17360
17878
  if (entry.isDirectory()) {
17361
- if (isCachePath(normalizePath5((0, import_node_path29.relative)(root, absolute)))) {
17879
+ if (isCachePath(normalizePath5((0, import_node_path33.relative)(root, absolute)))) {
17362
17880
  result.push({ path: absolute, sizeBytes: 0, kind: "directory" });
17363
17881
  continue;
17364
17882
  }
@@ -17369,7 +17887,7 @@ async function walkCleanupFiles(root, dir, result, depth) {
17369
17887
  continue;
17370
17888
  }
17371
17889
  try {
17372
- const fileStat = await (0, import_promises23.stat)(absolute);
17890
+ const fileStat = await (0, import_promises26.stat)(absolute);
17373
17891
  result.push({ path: absolute, sizeBytes: fileStat.size, kind: "file" });
17374
17892
  } catch {
17375
17893
  continue;
@@ -17379,12 +17897,12 @@ async function walkCleanupFiles(root, dir, result, depth) {
17379
17897
  function normalizePath5(path) {
17380
17898
  return path.replace(/\\/g, "/");
17381
17899
  }
17382
- var import_promises23, import_node_path29, MAX_WALK_DEPTH, LARGE_LOG_BYTES, SKIP_DIRECTORIES;
17900
+ var import_promises26, import_node_path33, MAX_WALK_DEPTH, LARGE_LOG_BYTES, SKIP_DIRECTORIES;
17383
17901
  var init_cleanPlan = __esm({
17384
17902
  "src/commands/cleanPlan.ts"() {
17385
17903
  "use strict";
17386
- import_promises23 = require("node:fs/promises");
17387
- import_node_path29 = require("node:path");
17904
+ import_promises26 = require("node:fs/promises");
17905
+ import_node_path33 = require("node:path");
17388
17906
  MAX_WALK_DEPTH = 5;
17389
17907
  LARGE_LOG_BYTES = 8192;
17390
17908
  SKIP_DIRECTORIES = /* @__PURE__ */ new Set([".git", "node_modules"]);
@@ -17432,6 +17950,10 @@ function renderTarget(target) {
17432
17950
  return ["Verify:", "```bash", redact(target.command), "```"];
17433
17951
  }
17434
17952
  function renderReadiness(action) {
17953
+ if (action.requiresUserAction && action.readiness?.[0]) {
17954
+ const rest = action.readiness.slice(1, 3).map(redact).filter(Boolean);
17955
+ return [redact(action.readiness[0]), rest.length ? `Ready: ${rest.join(", ")}` : void 0].filter(Boolean).join("\n");
17956
+ }
17435
17957
  const readiness = action.readiness?.slice(0, 2).map(redact).filter(Boolean);
17436
17958
  if (!readiness || readiness.length === 0) {
17437
17959
  return void 0;
@@ -17493,10 +18015,10 @@ var init_render = __esm({
17493
18015
 
17494
18016
  // src/commands/actions.ts
17495
18017
  async function runActionsCommand(input) {
17496
- const actionsPath = (0, import_node_path30.join)(getProjectArtifactsDir(input.cwd), "actions.json");
18018
+ const actionsPath = (0, import_node_path34.join)(getProjectArtifactsDir(input.cwd), "actions.json");
17497
18019
  let raw;
17498
18020
  try {
17499
- raw = await (0, import_promises24.readFile)(actionsPath, "utf8");
18021
+ raw = await (0, import_promises27.readFile)(actionsPath, "utf8");
17500
18022
  } catch {
17501
18023
  process.stderr.write("No VibeRaven actions found. Run `npx -y viberaven --agent-mode` first.\n");
17502
18024
  return 1;
@@ -17510,12 +18032,12 @@ async function runActionsCommand(input) {
17510
18032
  process.stdout.write(renderActionSurface(manifest));
17511
18033
  return 0;
17512
18034
  }
17513
- var import_promises24, import_node_path30;
18035
+ var import_promises27, import_node_path34;
17514
18036
  var init_actions = __esm({
17515
18037
  "src/commands/actions.ts"() {
17516
18038
  "use strict";
17517
- import_promises24 = require("node:fs/promises");
17518
- import_node_path30 = require("node:path");
18039
+ import_promises27 = require("node:fs/promises");
18040
+ import_node_path34 = require("node:path");
17519
18041
  init_render();
17520
18042
  init_config();
17521
18043
  }
@@ -17524,7 +18046,7 @@ var init_actions = __esm({
17524
18046
  // src/commands/verifyAction.ts
17525
18047
  async function currentActionExists(cwd, actionId) {
17526
18048
  try {
17527
- const raw = await (0, import_promises25.readFile)((0, import_node_path31.join)(getProjectArtifactsDir(cwd), "actions.json"), "utf8");
18049
+ const raw = await (0, import_promises28.readFile)((0, import_node_path35.join)(getProjectArtifactsDir(cwd), "actions.json"), "utf8");
17528
18050
  const manifest = JSON.parse(raw);
17529
18051
  return manifest.actions.some((action) => action.id === actionId);
17530
18052
  } catch {
@@ -17563,17 +18085,17 @@ async function runVerifyActionCommand(input) {
17563
18085
  }
17564
18086
  process.stdout.write(
17565
18087
  `Verifying ${entry.id} (${entry.title ?? "untitled action"}).
17566
- Run \`npx -y viberaven --verify\` to refresh the production gate for this action.
18088
+ This only looks the action up. Run \`npx -y viberaven --verify\` to scan the repo again and see whether the gap is gone.
17567
18089
  `
17568
18090
  );
17569
18091
  return 0;
17570
18092
  }
17571
- var import_promises25, import_node_path31;
18093
+ var import_promises28, import_node_path35;
17572
18094
  var init_verifyAction = __esm({
17573
18095
  "src/commands/verifyAction.ts"() {
17574
18096
  "use strict";
17575
- import_promises25 = require("node:fs/promises");
17576
- import_node_path31 = require("node:path");
18097
+ import_promises28 = require("node:fs/promises");
18098
+ import_node_path35 = require("node:path");
17577
18099
  init_registry2();
17578
18100
  init_config();
17579
18101
  }
@@ -17614,7 +18136,7 @@ function buildPreviewManifest() {
17614
18136
  ]
17615
18137
  }
17616
18138
  ],
17617
- verifyCommand: `${PUBLIC_COMMAND} verify --action VR-A1`,
18139
+ verifyCommand: `${PUBLIC_COMMAND} --verify --action VR-A1`,
17618
18140
  resumeInstruction: "Stripe webhook is configured. Continue VibeRaven from VR-A1."
17619
18141
  },
17620
18142
  {
@@ -17640,7 +18162,7 @@ function buildPreviewManifest() {
17640
18162
  value: 'alter table public.example enable row level security;\ncreate policy "Users manage own rows" on public.example for all using (auth.uid() = user_id);'
17641
18163
  }
17642
18164
  ],
17643
- verifyCommand: `${PUBLIC_COMMAND} verify --action VR-A2`,
18165
+ verifyCommand: `${PUBLIC_COMMAND} --verify --action VR-A2`,
17644
18166
  resumeInstruction: "Supabase RLS is applied. Continue VibeRaven from VR-A2."
17645
18167
  },
17646
18168
  {
@@ -17697,11 +18219,36 @@ var init_preview = __esm({
17697
18219
  async function runCheckCommand(options) {
17698
18220
  const result = await runProjectScan({ workspacePath: options.cwd });
17699
18221
  if (!result.ok) {
18222
+ if (options.markdown) console.log(`${COMMENT_MARKER}
18223
+ ### VibeRaven could not check this change
18224
+
18225
+ ${result.message}
18226
+ `);
17700
18227
  console.error(formatAgentStatus(ERROR, result.message));
17701
18228
  return 2;
17702
18229
  }
17703
18230
  const artifact = result.artifact;
17704
18231
  const paths = await writeScanArtifacts({ artifact, cwd: options.cwd });
18232
+ let change;
18233
+ if (options.base) {
18234
+ try {
18235
+ change = await checkChange(options.cwd, options.base, artifact.gaps);
18236
+ } catch (error) {
18237
+ const message = error instanceof Error ? error.message : String(error);
18238
+ if (options.markdown) console.log(`${COMMENT_MARKER}
18239
+ ### VibeRaven could not compare this change
18240
+
18241
+ ${message}
18242
+ `);
18243
+ console.error(formatAgentStatus(ERROR, message));
18244
+ return 2;
18245
+ }
18246
+ }
18247
+ const failing = change ? change.introduced.some((g2) => g2.severity === "critical") : artifact.gaps.some((g2) => g2.severity === "critical");
18248
+ if (options.markdown) {
18249
+ console.log(renderCheckMarkdown({ head: artifact.gaps, change, version: VERSION, score: artifact.score }));
18250
+ return failing ? 1 : 0;
18251
+ }
17705
18252
  if (options.json) {
17706
18253
  console.log(
17707
18254
  JSON.stringify(
@@ -17716,13 +18263,22 @@ async function runCheckCommand(options) {
17716
18263
  title: gap.title,
17717
18264
  detail: gap.detail
17718
18265
  })),
18266
+ ...change ? {
18267
+ change: {
18268
+ base: change.base,
18269
+ baseCommit: change.baseCommit,
18270
+ introduced: change.introduced.map((gap) => gap.id),
18271
+ resolved: change.resolved.map((gap) => gap.id),
18272
+ unchanged: change.unchanged.map((gap) => gap.id)
18273
+ }
18274
+ } : {},
17719
18275
  artifacts: paths
17720
18276
  },
17721
18277
  null,
17722
18278
  2
17723
18279
  )
17724
18280
  );
17725
- return artifact.gaps.some((g2) => g2.severity === "critical") ? 1 : 0;
18281
+ return failing ? 1 : 0;
17726
18282
  }
17727
18283
  const sorted = [...artifact.gaps].sort(
17728
18284
  (a, b3) => SEVERITY_ORDER3[a.severity] - SEVERITY_ORDER3[b3.severity]
@@ -17732,14 +18288,37 @@ async function runCheckCommand(options) {
17732
18288
  console.log("");
17733
18289
  console.log(`viberaven check \xB7 ${options.cwd}`);
17734
18290
  console.log("");
18291
+ if (change) {
18292
+ const line = (gap) => `${SEVERITY_ICON[gap.severity]} ${gap.title} (${gap.id})`;
18293
+ console.log(`Compared with ${change.base} (${change.baseCommit.slice(0, 7)})`);
18294
+ console.log("");
18295
+ console.log(change.introduced.length ? "New in this change:" : "New in this change: nothing");
18296
+ for (const gap of change.introduced) {
18297
+ console.log(` ${line(gap)}${gap.subjects?.length ? ` new: ${gap.subjects.join(", ")}` : ""}`);
18298
+ if (!options.quiet && !gap.subjects?.length && gap.detail) console.log(` ${gap.detail.split("\n")[0]}`);
18299
+ }
18300
+ if (change.resolved.length) {
18301
+ console.log("Fixed by this change:");
18302
+ for (const gap of change.resolved) console.log(` \u2705 ${gap.title}${gap.subjects?.length ? ` for ${gap.subjects.join(", ")}` : ""} (${gap.id})`);
18303
+ }
18304
+ if (change.unchanged.length) console.log(`Already there before: ${change.unchanged.length}`);
18305
+ const newCritical = change.introduced.filter((g2) => g2.severity === "critical").length;
18306
+ console.log("");
18307
+ console.log(
18308
+ newCritical > 0 ? `Verdict: \u274C ${newCritical} new blocker${newCritical === 1 ? "" : "s"} in this change \xB7 score ${artifact.score}` : `Verdict: \u2705 no new blockers in this change \xB7 score ${artifact.score}`
18309
+ );
18310
+ console.log(`Fix: viberaven fix \xB7 Details: ${paths.tasklistPath}`);
18311
+ console.log("");
18312
+ return failing ? 1 : 0;
18313
+ }
17735
18314
  if (sorted.length === 0) {
17736
18315
  console.log("\u2705 No launch blockers found in repo evidence.");
17737
18316
  } else {
17738
18317
  for (const gap of sorted) {
17739
18318
  console.log(`${SEVERITY_ICON[gap.severity]} ${gap.title} (${gap.id})`);
17740
18319
  if (!options.quiet && gap.detail) {
17741
- const firstLine2 = gap.detail.split("\n")[0];
17742
- console.log(` ${firstLine2}`);
18320
+ const firstLine3 = gap.detail.split("\n")[0];
18321
+ console.log(` ${firstLine3}`);
17743
18322
  }
17744
18323
  }
17745
18324
  }
@@ -17758,6 +18337,8 @@ var init_check = __esm({
17758
18337
  init_runScan();
17759
18338
  init_artifacts2();
17760
18339
  init_statusLabels();
18340
+ init_version();
18341
+ init_checkChange();
17761
18342
  SEVERITY_ICON = {
17762
18343
  critical: "\u{1F534}",
17763
18344
  // red circle
@@ -17772,7 +18353,7 @@ var init_check = __esm({
17772
18353
 
17773
18354
  // src/rls/permissionsMatrix.ts
17774
18355
  function sha256(data) {
17775
- return (0, import_node_crypto.createHash)("sha256").update(data).digest("hex");
18356
+ return (0, import_node_crypto2.createHash)("sha256").update(data).digest("hex");
17776
18357
  }
17777
18358
  function parsePermissionsMatrix(text) {
17778
18359
  let raw;
@@ -17801,12 +18382,12 @@ function parsePermissionsMatrix(text) {
17801
18382
  } else {
17802
18383
  const seen = /* @__PURE__ */ new Set();
17803
18384
  for (const [key, value] of Object.entries(raw.tables)) {
17804
- const table = parseTable(key, value, problems);
17805
- if (!table) continue;
17806
- const full = `${table.schema}.${table.name}`;
18385
+ const table2 = parseTable(key, value, problems);
18386
+ if (!table2) continue;
18387
+ const full = `${table2.schema}.${table2.name}`;
17807
18388
  if (seen.has(full)) problems.push(`tables[${show(key)}]: ${full} is listed twice`);
17808
18389
  seen.add(full);
17809
- tables.push(table);
18390
+ tables.push(table2);
17810
18391
  }
17811
18392
  }
17812
18393
  if (problems.length > 0) throw new MatrixError(problems);
@@ -17880,10 +18461,10 @@ function parseTable(key, value, problems) {
17880
18461
  }
17881
18462
  const parsed = {};
17882
18463
  for (const action of MATRIX_ACTIONS) {
17883
- const cell = cells[action];
17884
- if (cell === void 0) problems.push(`${actorAt}.${action}: missing; write ${quoteList(CELL_VALUES)}`);
17885
- else if (!CELL_VALUES.includes(cell)) problems.push(`${actorAt}.${action}: must be ${quoteList(CELL_VALUES)} (got ${show(cell)})`);
17886
- else parsed[action] = cell;
18464
+ const cell2 = cells[action];
18465
+ if (cell2 === void 0) problems.push(`${actorAt}.${action}: missing; write ${quoteList(CELL_VALUES)}`);
18466
+ else if (!CELL_VALUES.includes(cell2)) problems.push(`${actorAt}.${action}: must be ${quoteList(CELL_VALUES)} (got ${show(cell2)})`);
18467
+ else parsed[action] = cell2;
17887
18468
  }
17888
18469
  if (cells.reassign !== void 0) {
17889
18470
  if (!CELL_VALUES.includes(cells.reassign)) problems.push(`${actorAt}.reassign: must be ${quoteList(CELL_VALUES)} (got ${show(cells.reassign)})`);
@@ -17920,11 +18501,11 @@ function parseTable(key, value, problems) {
17920
18501
  const [schema, name] = parts.length === 2 ? parts : ["public", parts[0]];
17921
18502
  return { key, schema, name, ownerColumn, expect, serviceRoleExcluded, seed, ...updateColumn ? { updateColumn } : {} };
17922
18503
  }
17923
- var import_node_crypto, MATRIX_ACTORS, TESTED_ACTORS, MATRIX_ACTIONS, OPTIONAL_ACTIONS, ALL_ACTIONS, CELL_VALUES, DEFAULT_GRANT_MODES, DEFAULT_MATRIX_FILE, MatrixError, IDENT, TOP_KEYS, TABLE_KEYS, OWNER_KEYS, isObject2, show, quoteList;
18504
+ var import_node_crypto2, MATRIX_ACTORS, TESTED_ACTORS, MATRIX_ACTIONS, OPTIONAL_ACTIONS, ALL_ACTIONS, CELL_VALUES, DEFAULT_GRANT_MODES, DEFAULT_MATRIX_FILE, MatrixError, IDENT, TOP_KEYS, TABLE_KEYS, OWNER_KEYS, isObject2, show, quoteList;
17924
18505
  var init_permissionsMatrix = __esm({
17925
18506
  "src/rls/permissionsMatrix.ts"() {
17926
18507
  "use strict";
17927
- import_node_crypto = require("node:crypto");
18508
+ import_node_crypto2 = require("node:crypto");
17928
18509
  MATRIX_ACTORS = ["owner", "other_user", "anon", "service_role"];
17929
18510
  TESTED_ACTORS = ["owner", "other_user", "anon"];
17930
18511
  MATRIX_ACTIONS = ["select", "insert", "update", "delete"];
@@ -18103,13 +18684,13 @@ async function createWorkerDatabase(options) {
18103
18684
  const call = (message, timeoutMs) => {
18104
18685
  if (dead) return Promise.reject(dead);
18105
18686
  const id = ++nextId;
18106
- return new Promise((resolve9, reject) => {
18687
+ return new Promise((resolve10, reject) => {
18107
18688
  const timer = setTimeout(() => {
18108
18689
  const error = new RlsTimeoutError(`timed out after ${Math.round(timeoutMs / 1e3)} s`);
18109
18690
  failAll(error);
18110
18691
  void worker.terminate();
18111
18692
  }, timeoutMs);
18112
- pending.set(id, { resolve: resolve9, reject, timer });
18693
+ pending.set(id, { resolve: resolve10, reject, timer });
18113
18694
  worker.postMessage({ ...message, id });
18114
18695
  });
18115
18696
  };
@@ -18194,22 +18775,22 @@ parentPort.on('message', async (m) => {
18194
18775
  // src/rls/rlsRunner.ts
18195
18776
  function sqlError(error) {
18196
18777
  const e2 = error;
18197
- return { code: typeof e2?.code === "string" ? e2.code : "unknown", message: firstLine(typeof e2?.message === "string" ? e2.message : String(error)) };
18778
+ return { code: typeof e2?.code === "string" ? e2.code : "unknown", message: firstLine2(typeof e2?.message === "string" ? e2.message : String(error)) };
18198
18779
  }
18199
18780
  function isSqlError(error) {
18200
18781
  return !(error instanceof RlsTimeoutError) && typeof error?.code === "string";
18201
18782
  }
18202
18783
  function displayPath(projectDir, path) {
18203
18784
  for (const base of [projectDir, process.cwd()]) {
18204
- const rel = (0, import_node_path32.relative)(base, path);
18205
- if (rel && !rel.startsWith("..") && !(0, import_node_path32.isAbsolute)(rel)) return rel.split("\\").join("/");
18785
+ const rel = (0, import_node_path36.relative)(base, path);
18786
+ if (rel && !rel.startsWith("..") && !(0, import_node_path36.isAbsolute)(rel)) return rel.split("\\").join("/");
18206
18787
  }
18207
18788
  return path;
18208
18789
  }
18209
18790
  async function gitCommit(projectDir, paths) {
18210
18791
  const run = async (args) => {
18211
18792
  const call = await safeGitInvocation(projectDir, args);
18212
- const { stdout } = await (0, import_node_util4.promisify)(import_node_child_process6.execFile)("git", call.args, { cwd: projectDir, env: call.env, windowsHide: true, timeout: 5e3, encoding: "utf8" });
18793
+ const { stdout } = await (0, import_node_util6.promisify)(import_node_child_process8.execFile)("git", call.args, { cwd: projectDir, env: call.env, windowsHide: true, timeout: 5e3, encoding: "utf8" });
18213
18794
  return String(stdout);
18214
18795
  };
18215
18796
  let sha;
@@ -18256,7 +18837,7 @@ function valueCandidates(column, label) {
18256
18837
  const u2 = label === "u";
18257
18838
  switch (column.typname) {
18258
18839
  case "uuid":
18259
- return [(0, import_node_crypto2.randomUUID)()];
18840
+ return [(0, import_node_crypto3.randomUUID)()];
18260
18841
  case "json":
18261
18842
  case "jsonb":
18262
18843
  return u2 ? ['{"vr":"u"}', "{}"] : ["{}"];
@@ -18282,11 +18863,11 @@ function valueCandidates(column, label) {
18282
18863
  if (column.category === "A") return u2 ? [] : ["{}"];
18283
18864
  return [];
18284
18865
  }
18285
- async function describeTable(db, table) {
18286
- const display = `${table.schema}.${table.name}`;
18866
+ async function describeTable(db, table2) {
18867
+ const display = `${table2.schema}.${table2.name}`;
18287
18868
  const rel = await db.query(
18288
18869
  `select c.oid::int as oid, c.relkind::text as kind from pg_class c join pg_namespace n on n.oid = c.relnamespace where n.nspname = $1 and c.relname = $2`,
18289
- [table.schema, table.name]
18870
+ [table2.schema, table2.name]
18290
18871
  );
18291
18872
  if (rel.rows.length === 0) return { reason: `${display} does not exist after the migrations; check the table name in the matrix.` };
18292
18873
  const kind = String(rel.rows[0].kind);
@@ -18325,35 +18906,35 @@ async function describeTable(db, table) {
18325
18906
  };
18326
18907
  });
18327
18908
  const byName = new Map(columns.map((c) => [c.name, c]));
18328
- const owner = byName.get(table.ownerColumn);
18329
- if (!owner) return { reason: `${display} has no column ${table.ownerColumn} (the matrix owner column).` };
18909
+ const owner = byName.get(table2.ownerColumn);
18910
+ if (!owner) return { reason: `${display} has no column ${table2.ownerColumn} (the matrix owner column).` };
18330
18911
  if (owner.typname !== "uuid" && owner.category !== "S") {
18331
- return { reason: `${display}.${table.ownerColumn} is ${owner.type}; the owner column must hold auth.uid() (uuid or text).` };
18912
+ return { reason: `${display}.${table2.ownerColumn} is ${owner.type}; the owner column must hold auth.uid() (uuid or text).` };
18332
18913
  }
18333
18914
  const pk = columns.filter((c) => c.isPk);
18334
18915
  if (pk.length === 0) return { reason: `${display} has no primary key; rls-test targets the owner's row by primary key.` };
18335
- for (const column of Object.keys(table.seed)) {
18916
+ for (const column of Object.keys(table2.seed)) {
18336
18917
  if (!byName.has(column)) return { reason: `the matrix seeds ${display}.${column}, which does not exist.` };
18337
18918
  }
18338
- if (table.updateColumn) {
18339
- const column = byName.get(table.updateColumn);
18340
- if (!column) return { reason: `the matrix updateColumn ${display}.${table.updateColumn} does not exist.` };
18919
+ if (table2.updateColumn) {
18920
+ const column = byName.get(table2.updateColumn);
18921
+ if (!column) return { reason: `the matrix updateColumn ${display}.${table2.updateColumn} does not exist.` };
18341
18922
  if (valueCandidates(column, "u").length === 0) return { reason: `the test cannot invent a new value for ${display}.${column.name} (${column.type}); pick another updateColumn.` };
18342
18923
  }
18343
18924
  const writable = (c) => c.identity === "" && c.generated === "";
18344
- const required = columns.filter((c) => c.name !== owner.name && c.notNull && !c.hasDefault && writable(c) && !(c.name in table.seed));
18925
+ const required = columns.filter((c) => c.name !== owner.name && c.notNull && !c.hasDefault && writable(c) && !(c.name in table2.seed));
18345
18926
  for (const column of required) {
18346
18927
  if (column.isFk) return { reason: `${display}.${column.name} is a NOT NULL foreign key the test cannot invent; give it a value under "seed" in the matrix.` };
18347
18928
  if (valueCandidates(column, "a").length === 0) {
18348
18929
  return { reason: `${display}.${column.name} (${column.type}) is NOT NULL and the test cannot invent a value; give it one under "seed" in the matrix.` };
18349
18930
  }
18350
18931
  }
18351
- const seedColumns = Object.keys(table.seed);
18932
+ const seedColumns = Object.keys(table2.seed);
18352
18933
  return {
18353
18934
  shape: {
18354
- table,
18935
+ table: table2,
18355
18936
  oid,
18356
- qualified: `${quoteIdent2(table.schema)}.${quoteIdent2(table.name)}`,
18937
+ qualified: `${quoteIdent2(table2.schema)}.${quoteIdent2(table2.name)}`,
18357
18938
  display,
18358
18939
  columns,
18359
18940
  pk,
@@ -18361,7 +18942,7 @@ async function describeTable(db, table) {
18361
18942
  ownerUnique: unique3.has(owner.name),
18362
18943
  ownerInPk: pk.some((c) => c.name === owner.name),
18363
18944
  insertColumns: [owner.name, ...seedColumns, ...required.map((c) => c.name)],
18364
- rowValues: (label, ownerId) => [ownerId, ...seedColumns.map((c) => table.seed[c]), ...required.map((c) => valueCandidates(c, label)[0])]
18945
+ rowValues: (label, ownerId) => [ownerId, ...seedColumns.map((c) => table2.seed[c]), ...required.map((c) => valueCandidates(c, label)[0])]
18365
18946
  }
18366
18947
  };
18367
18948
  }
@@ -18457,10 +19038,10 @@ function writeControl(filtered, changed) {
18457
19038
  if (!changed(run)) {
18458
19039
  return {
18459
19040
  ok: false,
18460
- detail: filtered ? `as the superuser the same statement changed ${plural2(run.affected ?? 0, "row")}, expected 1` : "as the superuser the same statement did not change the owner row"
19041
+ detail: filtered ? `as the superuser the same statement changed ${plural3(run.affected ?? 0, "row")}, expected 1` : "as the superuser the same statement did not change the owner row"
18461
19042
  };
18462
19043
  }
18463
- return { ok: true, detail: `as the superuser the same statement changed ${plural2(filtered ? run.affected ?? 0 : 1, "row")}` };
19044
+ return { ok: true, detail: `as the superuser the same statement changed ${plural3(filtered ? run.affected ?? 0 : 1, "row")}` };
18464
19045
  };
18465
19046
  }
18466
19047
  async function differingValue(db, shape, target, column) {
@@ -18487,7 +19068,7 @@ async function selectPlans(c) {
18487
19068
  return [
18488
19069
  {
18489
19070
  stmt: { name: "select", sql: `select ${pkList} from ${shape.qualified} where ${pkCondition(shape, 1)}`, params: target, display: `select the owner's row from ${shape.display} by primary key`, filtered: true, select: "pk" },
18490
- controlOk: (run) => run.error ? { ok: false, detail: `as the superuser the same statement failed: ${run.error.message} (${run.error.code})` } : run.rows?.length === 1 ? { ok: true, detail: "as the superuser the same statement returned 1 row" } : { ok: false, detail: `as the superuser the same statement returned ${plural2(run.rows?.length ?? 0, "row")}, expected 1` },
19071
+ controlOk: (run) => run.error ? { ok: false, detail: `as the superuser the same statement failed: ${run.error.message} (${run.error.code})` } : run.rows?.length === 1 ? { ok: true, detail: "as the superuser the same statement returned 1 row" } : { ok: false, detail: `as the superuser the same statement returned ${plural3(run.rows?.length ?? 0, "row")}, expected 1` },
18491
19072
  judge: (run) => {
18492
19073
  const denied = privilegeDenied(run);
18493
19074
  if (denied) return denied;
@@ -18542,7 +19123,7 @@ function insertPlans(c) {
18542
19123
  prep,
18543
19124
  controlOk: (run) => {
18544
19125
  if (run.error) return { ok: false, detail: `as the superuser the same statement failed: ${run.error.message} (${run.error.code})` };
18545
- return added(run) ? { ok: true, detail: "as the superuser the same statement changed 1 row" } : { ok: false, detail: `as the superuser the same statement changed ${plural2(run.affected ?? 0, "row")}, expected 1` };
19126
+ return added(run) ? { ok: true, detail: "as the superuser the same statement changed 1 row" } : { ok: false, detail: `as the superuser the same statement changed ${plural3(run.affected ?? 0, "row")}, expected 1` };
18546
19127
  },
18547
19128
  judge: (run) => {
18548
19129
  if (isRlsRefusal(run)) return { verdict: "unknown", detail: `refused by WITH CHECK: ${run.error.message} (42501). Only the generated row was tried; other values may allow insertion` };
@@ -18554,7 +19135,7 @@ function insertPlans(c) {
18554
19135
  if ((run.affected ?? 0) === 0 && run.after.digest === run.before.digest) return { verdict: "denied", detail: "0 rows added, table unchanged" };
18555
19136
  return {
18556
19137
  verdict: "unknown",
18557
- detail: `reported ${plural2(run.affected ?? 0, "row")}, but the table went from ${run.before.count} to ${run.after.count} rows and the owner's rows from ${run.before.owned} to ${run.after.owned}`
19138
+ detail: `reported ${plural3(run.affected ?? 0, "row")}, but the table went from ${run.before.count} to ${run.after.count} rows and the owner's rows from ${run.before.owned} to ${run.after.owned}`
18558
19139
  };
18559
19140
  }
18560
19141
  }
@@ -18602,7 +19183,7 @@ async function updatePlans(c) {
18602
19183
  if (changed(run)) return { verdict: "allowed", detail: noop ? "1 row updated (rewrote its own value)" : `${changedRows(filtered ? run.affected : 1)} (${sets.map((s) => s.column).join(" and ")} rewritten)` };
18603
19184
  if (!filtered) return { verdict: "denied", detail: "the owner row is unchanged" };
18604
19185
  if ((run.affected ?? 0) === 0 && run.after.digest === run.before.digest) return { verdict: "denied", detail: "0 rows changed, the row is still there and unchanged" };
18605
- return { verdict: "unknown", detail: `reported ${plural2(run.affected ?? 0, "row")}, but the owner row ${run.after.target === null ? "is gone" : "did not change"} and the table ${run.after.digest === run.before.digest ? "did not" : "did"} change` };
19186
+ return { verdict: "unknown", detail: `reported ${plural3(run.affected ?? 0, "row")}, but the owner row ${run.after.target === null ? "is gone" : "did not change"} and the table ${run.after.digest === run.before.digest ? "did not" : "did"} change` };
18606
19187
  }
18607
19188
  });
18608
19189
  };
@@ -18654,7 +19235,7 @@ function reassignPlans(c) {
18654
19235
  if (moved(run)) return { verdict: "allowed", detail: `the owner row now belongs to the other user` };
18655
19236
  if (!filtered && run.after.target === run.before.target) return { verdict: "denied", detail: "the owner row is unchanged" };
18656
19237
  if ((run.affected ?? 0) === 0 && run.after.digest === run.before.digest) return { verdict: "denied", detail: "0 rows changed, the row is still there and unchanged" };
18657
- return { verdict: "unknown", detail: `reported ${plural2(run.affected ?? 0, "row")}, but the owner column did not move` };
19238
+ return { verdict: "unknown", detail: `reported ${plural3(run.affected ?? 0, "row")}, but the owner column did not move` };
18658
19239
  }
18659
19240
  }
18660
19241
  ];
@@ -18683,7 +19264,7 @@ function deletePlans(c) {
18683
19264
  if ((run.affected ?? 0) === 0 && run.after.digest === run.before.digest) return { verdict: "denied", detail: "0 rows changed, the row is still there and unchanged" };
18684
19265
  return {
18685
19266
  verdict: "unknown",
18686
- detail: `reported ${plural2(run.affected ?? 0, "row")}, but the owner row is ${run.after.target === null ? "gone" : "still there"} and the table ${run.after.digest === run.before.digest ? "did not" : "did"} change`
19267
+ detail: `reported ${plural3(run.affected ?? 0, "row")}, but the owner row is ${run.after.target === null ? "gone" : "still there"} and the table ${run.after.digest === run.before.digest ? "did not" : "did"} change`
18687
19268
  };
18688
19269
  }
18689
19270
  }
@@ -18754,25 +19335,25 @@ async function runCell(ctx, privilegesByRole, actor, action, expected) {
18754
19335
  const reason = pass ? `Expected ${expected}, observed ${allowed ? "allowed" : "denied"}.` : want ? `Expected ${actorText} to be allowed to ${action}, but ${observed.summary}.${action !== "select" && !r.error ? " Postgres reports rows no policy allows as 0 rows, not as an error, so the app sees no failure." : ""}` : `Expected ${actorText} to be denied ${action}, but ${observed.summary}.`;
18755
19336
  return { ...base, outcome: pass ? "Pass" : "Fail", observed, reason };
18756
19337
  }
18757
- function cellsFor(table) {
19338
+ function cellsFor(table2) {
18758
19339
  const out = [];
18759
19340
  for (const actor of TESTED_ACTORS) {
18760
- for (const action of MATRIX_ACTIONS) out.push({ actor, action, expected: table.expect[actor][action] });
18761
- const reassign = table.expect[actor].reassign;
19341
+ for (const action of MATRIX_ACTIONS) out.push({ actor, action, expected: table2.expect[actor][action] });
19342
+ const reassign = table2.expect[actor].reassign;
18762
19343
  if (reassign) out.push({ actor, action: "reassign", expected: reassign });
18763
19344
  }
18764
19345
  return out;
18765
19346
  }
18766
- function unknownCells(table, reason, summary) {
18767
- const display = `${table.schema}.${table.name}`;
18768
- const cells = cellsFor(table).map(
19347
+ function unknownCells(table2, reason, summary) {
19348
+ const display = `${table2.schema}.${table2.name}`;
19349
+ const cells = cellsFor(table2).map(
18769
19350
  ({ actor, action, expected }) => expected === "skip" ? { table: display, actor, action, expected, outcome: "N/A", observed: { summary: "not tested (skip in the matrix)" }, reason: "The matrix marks this cell skip." } : { table: display, actor, action, expected, outcome: "Unknown", observed: { summary }, reason }
18770
19351
  );
18771
- return [...cells, ...serviceRoleCells(table)];
19352
+ return [...cells, ...serviceRoleCells(table2)];
18772
19353
  }
18773
- function serviceRoleCells(table) {
18774
- if (!table.serviceRoleExcluded) return [];
18775
- const display = `${table.schema}.${table.name}`;
19354
+ function serviceRoleCells(table2) {
19355
+ if (!table2.serviceRoleExcluded) return [];
19356
+ const display = `${table2.schema}.${table2.name}`;
18776
19357
  return MATRIX_ACTIONS.map((action) => ({
18777
19358
  table: display,
18778
19359
  actor: "service_role",
@@ -18792,40 +19373,40 @@ function summarise(tables) {
18792
19373
  function verdictFor(summary) {
18793
19374
  if (summary.fail > 0) {
18794
19375
  return {
18795
- verdict: `Not ready within tested scope: ${plural2(summary.fail, "cell does", "cells do")} not match the matrix; ${plural2(summary.unknown, "unknown")}.`,
19376
+ verdict: `Not ready within tested scope: ${plural3(summary.fail, "cell does", "cells do")} not match the matrix; ${plural3(summary.unknown, "unknown")}.`,
18796
19377
  exitCode: 1
18797
19378
  };
18798
19379
  }
18799
19380
  if (summary.pass === 0) {
18800
- return { verdict: `Nothing was observed: ${plural2(summary.unknown, "unknown")}, ${summary.notApplicable} N/A.`, exitCode: 2 };
19381
+ return { verdict: `Nothing was observed: ${plural3(summary.unknown, "unknown")}, ${summary.notApplicable} N/A.`, exitCode: 2 };
18801
19382
  }
18802
19383
  if (summary.unknown > 0) {
18803
- return { verdict: `No blockers found within tested scope; ${plural2(summary.unknown, "unknown")}.`, exitCode: 2 };
19384
+ return { verdict: `No blockers found within tested scope; ${plural3(summary.unknown, "unknown")}.`, exitCode: 2 };
18804
19385
  }
18805
19386
  return { verdict: "No blockers found within tested scope; 0 unknowns. Scenario passed locally.", exitCode: 0 };
18806
19387
  }
18807
19388
  async function listMigrations(dir) {
18808
19389
  try {
18809
- const entries = await (0, import_promises26.readdir)(dir, { withFileTypes: true });
19390
+ const entries = await (0, import_promises29.readdir)(dir, { withFileTypes: true });
18810
19391
  return entries.filter((e2) => e2.isFile() && /\.sql$/i.test(e2.name) && !e2.name.startsWith(".")).map((e2) => e2.name).sort((a, b3) => a < b3 ? -1 : a > b3 ? 1 : 0);
18811
19392
  } catch {
18812
19393
  return [];
18813
19394
  }
18814
19395
  }
18815
19396
  async function runRlsTest(options) {
18816
- const projectDir = (0, import_node_path32.resolve)(options.projectDir);
18817
- const matrixPath = (0, import_node_path32.resolve)(projectDir, options.matrixPath ?? DEFAULT_MATRIX_FILE);
18818
- const migrationsDir = (0, import_node_path32.resolve)(projectDir, options.migrationsDir ?? (0, import_node_path32.join)("supabase", "migrations"));
19397
+ const projectDir = (0, import_node_path36.resolve)(options.projectDir);
19398
+ const matrixPath = (0, import_node_path36.resolve)(projectDir, options.matrixPath ?? DEFAULT_MATRIX_FILE);
19399
+ const migrationsDir = (0, import_node_path36.resolve)(projectDir, options.migrationsDir ?? (0, import_node_path36.join)("supabase", "migrations"));
18819
19400
  const timeouts = { ...DEFAULT_TIMEOUTS, ...options.timeouts };
18820
- if ((0, import_node_path32.basename)(matrixPath).toLowerCase().startsWith(".env")) {
18821
- throw new RlsInputError(`Refusing to read ${(0, import_node_path32.basename)(matrixPath)}: rls-test never reads .env files. Point --matrix at a viberaven.permissions.json.`);
19401
+ if ((0, import_node_path36.basename)(matrixPath).toLowerCase().startsWith(".env")) {
19402
+ throw new RlsInputError(`Refusing to read ${(0, import_node_path36.basename)(matrixPath)}: rls-test never reads .env files. Point --matrix at a viberaven.permissions.json.`);
18822
19403
  }
18823
19404
  const filesRead = [];
18824
19405
  let matrixText;
18825
19406
  try {
18826
- const info = await (0, import_promises26.stat)(matrixPath);
19407
+ const info = await (0, import_promises29.stat)(matrixPath);
18827
19408
  if (!info.isFile()) throw new Error("not a file");
18828
- matrixText = normalise(await (0, import_promises26.readFile)(matrixPath, "utf8"));
19409
+ matrixText = normalise(await (0, import_promises29.readFile)(matrixPath, "utf8"));
18829
19410
  } catch {
18830
19411
  throw new RlsInputError(
18831
19412
  `No permissions matrix at ${matrixPath}. Write one by hand (schema: schemas/permissions-matrix.schema.json in the @viberaven/cli package); rls-test never infers it from your policies.`
@@ -18836,8 +19417,8 @@ async function runRlsTest(options) {
18836
19417
  const names = await listMigrations(migrationsDir);
18837
19418
  const migrationFiles = [];
18838
19419
  for (const name of names) {
18839
- const sql = normalise(await (0, import_promises26.readFile)((0, import_node_path32.join)(migrationsDir, name), "utf8"));
18840
- filesRead.push(displayPath(projectDir, (0, import_node_path32.join)(migrationsDir, name)));
19420
+ const sql = normalise(await (0, import_promises29.readFile)((0, import_node_path36.join)(migrationsDir, name), "utf8"));
19421
+ filesRead.push(displayPath(projectDir, (0, import_node_path36.join)(migrationsDir, name)));
18841
19422
  migrationFiles.push({ name, sha256: sha256(sql), sql });
18842
19423
  }
18843
19424
  const migrationsHash = sha256(migrationFiles.map((f) => `${f.name}\0${f.sha256}`).join("\n"));
@@ -18889,7 +19470,7 @@ async function runRlsTest(options) {
18889
19470
  engineLoaded = true;
18890
19471
  db = await engine.create({ env: allowlistedEnv(process.env), statementTimeoutMs: timeouts.statementMs });
18891
19472
  } catch (error) {
18892
- result.engine.error = firstLine(error instanceof Error ? error.message : String(error));
19473
+ result.engine.error = firstLine2(error instanceof Error ? error.message : String(error));
18893
19474
  if (engineLoaded) return allUnknown(`PGlite could not start: ${result.engine.error}. Nothing was tested.`, "not run: PGlite startup failed");
18894
19475
  const missing = error?.code === "MODULE_NOT_FOUND" || error?.code === "ERR_MODULE_NOT_FOUND";
18895
19476
  return missing ? allUnknown(PGLITE_INSTALL_HINT, "not run: PGlite is not installed") : allUnknown(`PGlite could not load: ${result.engine.error}. Nothing was tested.`, "not run: PGlite loading failed");
@@ -18949,13 +19530,13 @@ async function runRlsTest(options) {
18949
19530
  result.roleChecks.push({ role, ok, detail: row ? `superuser ${Boolean(row.su)}, BYPASSRLS ${Boolean(row.bypass)}` : "the role does not exist after the migrations" });
18950
19531
  }
18951
19532
  const badRoles = result.roleChecks.filter((c) => !c.ok);
18952
- for (const table of matrix.tables) {
18953
- result.tables.push(await runTable(db, table, badRoles, result.roleChecks));
19533
+ for (const table2 of matrix.tables) {
19534
+ result.tables.push(await runTable(db, table2, badRoles, result.roleChecks));
18954
19535
  }
18955
19536
  return finish();
18956
19537
  } catch (error) {
18957
19538
  if (error instanceof RlsTimeoutError || !isSqlError(error)) {
18958
- const message = firstLine(error instanceof Error ? error.message : String(error));
19539
+ const message = firstLine2(error instanceof Error ? error.message : String(error));
18959
19540
  return allUnknown(
18960
19541
  error instanceof RlsTimeoutError ? `A statement did not finish within ${Math.round(timeouts.statementMs / 1e3)} s, so the PGlite worker was stopped and no cell is reported as passed.` : `The PGlite worker stopped (${message}), so no cell is reported as passed.`,
18961
19542
  "not run: the database stopped"
@@ -18981,13 +19562,13 @@ async function seedRow(db, shape, label, userId) {
18981
19562
  if (r.rows.length !== 1) throw Object.assign(new Error("the insert returned no row (a trigger or rule may drop it)"), { code: "vr000" });
18982
19563
  return { key: keyOf2(r.rows[0]), reused: false };
18983
19564
  }
18984
- async function runTable(db, table, badRoles, roleChecks) {
18985
- const display = `${table.schema}.${table.name}`;
18986
- const unknownTable = (reason, extra = {}) => ({ name: display, ...extra, unknownReason: reason, cells: unknownCells(table, reason, "not run") });
19565
+ async function runTable(db, table2, badRoles, roleChecks) {
19566
+ const display = `${table2.schema}.${table2.name}`;
19567
+ const unknownTable = (reason, extra = {}) => ({ name: display, ...extra, unknownReason: reason, cells: unknownCells(table2, reason, "not run") });
18987
19568
  if (badRoles.length > 0) {
18988
19569
  return unknownTable(`Role check failed (${badRoles.map((c) => `${c.role}: ${c.detail}`).join("; ")}), so RLS would not apply to the probes the way it does for the Data API.`);
18989
19570
  }
18990
- const described = await describeTable(db, table);
19571
+ const described = await describeTable(db, table2);
18991
19572
  if (!described.shape) return unknownTable(described.reason ?? "unknown table problem");
18992
19573
  const shape = described.shape;
18993
19574
  const rel = await db.query(
@@ -19021,15 +19602,15 @@ async function runTable(db, table, badRoles, roleChecks) {
19021
19602
  const privilegesByRole = /* @__PURE__ */ new Map();
19022
19603
  for (const role of ["anon", "authenticated"]) privilegesByRole.set(role, await privilegesFor(db, shape, role));
19023
19604
  const cells = [];
19024
- for (const { actor, action, expected } of cellsFor(table)) {
19605
+ for (const { actor, action, expected } of cellsFor(table2)) {
19025
19606
  cells.push(await runCell({ db, shape, target: ownerSeed.key }, privilegesByRole, actor, action, expected));
19026
19607
  }
19027
- cells.push(...serviceRoleCells(table));
19608
+ cells.push(...serviceRoleCells(table2));
19028
19609
  const columns = {};
19029
19610
  shape.insertColumns.forEach((c) => {
19030
19611
  columns[c] = c === shape.owner.name ? "the test user id" : "generated test value";
19031
19612
  });
19032
- for (const c of Object.keys(table.seed)) columns[c] = "from the matrix seed";
19613
+ for (const c of Object.keys(table2.seed)) columns[c] = "from the matrix seed";
19033
19614
  return {
19034
19615
  name: display,
19035
19616
  rls,
@@ -19037,16 +19618,16 @@ async function runTable(db, table, badRoles, roleChecks) {
19037
19618
  cells
19038
19619
  };
19039
19620
  }
19040
- var import_node_child_process6, import_node_crypto2, import_promises26, import_node_net, import_node_path32, import_node_util4, NON_AUTHORITATIVE_LABEL, PGLITE_INSTALL_HINT, ENV_ALLOWLIST, PERMISSION_DENIED, RLS_REFUSAL, DEFAULT_TIMEOUTS, RlsInputError, normalise, quoteIdent2, firstLine, plural2, NUMBERS, ACTOR_SESSION, changedRows, isRlsRefusal, isPrivilegeRefusal, SELF, PROBE_EXCLUSIONS, SEED_HINTS;
19621
+ var import_node_child_process8, import_node_crypto3, import_promises29, import_node_net, import_node_path36, import_node_util6, NON_AUTHORITATIVE_LABEL, PGLITE_INSTALL_HINT, ENV_ALLOWLIST, PERMISSION_DENIED, RLS_REFUSAL, DEFAULT_TIMEOUTS, RlsInputError, normalise, quoteIdent2, firstLine2, plural3, NUMBERS, ACTOR_SESSION, changedRows, isRlsRefusal, isPrivilegeRefusal, SELF, PROBE_EXCLUSIONS, SEED_HINTS;
19041
19622
  var init_rlsRunner = __esm({
19042
19623
  "src/rls/rlsRunner.ts"() {
19043
19624
  "use strict";
19044
- import_node_child_process6 = require("node:child_process");
19045
- import_node_crypto2 = require("node:crypto");
19046
- import_promises26 = require("node:fs/promises");
19625
+ import_node_child_process8 = require("node:child_process");
19626
+ import_node_crypto3 = require("node:crypto");
19627
+ import_promises29 = require("node:fs/promises");
19047
19628
  import_node_net = __toESM(require("node:net"));
19048
- import_node_path32 = require("node:path");
19049
- import_node_util4 = require("node:util");
19629
+ import_node_path36 = require("node:path");
19630
+ import_node_util6 = require("node:util");
19050
19631
  init_safeGit();
19051
19632
  init_permissionsMatrix();
19052
19633
  init_supabaseImitation();
@@ -19062,15 +19643,15 @@ var init_rlsRunner = __esm({
19062
19643
  };
19063
19644
  normalise = (text) => text.replace(/\r\n/g, "\n");
19064
19645
  quoteIdent2 = (name) => `"${name.replace(/"/g, '""')}"`;
19065
- firstLine = (message) => message.split("\n")[0].trim();
19066
- plural2 = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
19646
+ firstLine2 = (message) => message.split("\n")[0].trim();
19647
+ plural3 = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
19067
19648
  NUMBERS = { a: ["1001"], b: ["1002"], i: ["1003"], u: ["1004", "7", "1"] };
19068
19649
  ACTOR_SESSION = {
19069
19650
  owner: { role: "authenticated", uid: OWNER_ID, claims: { sub: OWNER_ID, role: "authenticated", aud: "authenticated", email: "owner@example.test" } },
19070
19651
  other_user: { role: "authenticated", uid: OTHER_USER_ID, claims: { sub: OTHER_USER_ID, role: "authenticated", aud: "authenticated", email: "other-user@example.test" } },
19071
19652
  anon: { role: "anon", uid: null, claims: { role: "anon" } }
19072
19653
  };
19073
- changedRows = (n) => `${plural2(n ?? 0, "row")} changed`;
19654
+ changedRows = (n) => `${plural3(n ?? 0, "row")} changed`;
19074
19655
  isRlsRefusal = (run) => run.error?.code === PERMISSION_DENIED && RLS_REFUSAL.test(run.error.message);
19075
19656
  isPrivilegeRefusal = (run) => run.error?.code === PERMISSION_DENIED && !RLS_REFUSAL.test(run.error.message);
19076
19657
  SELF = Symbol("self");
@@ -19117,25 +19698,25 @@ function renderRlsText(result) {
19117
19698
  lines.push("");
19118
19699
  if (result.engine.error) lines.push(`(${result.engine.error})`);
19119
19700
  }
19120
- for (const table of result.tables) {
19701
+ for (const table2 of result.tables) {
19121
19702
  lines.push("");
19122
- const rls = table.rls ? `RLS ${table.rls.enabled ? "on" : "off"}${table.rls.forced ? ", forced" : ""}, owner ${table.rls.owner}` : "not inspected";
19123
- lines.push(`${table.name} (${rls})`);
19124
- if (table.seed) {
19125
- const reused = [table.seed.reused.owner ? "the owner" : "", table.seed.reused.otherUser ? "the other user" : ""].filter(Boolean);
19703
+ const rls = table2.rls ? `RLS ${table2.rls.enabled ? "on" : "off"}${table2.rls.forced ? ", forced" : ""}, owner ${table2.rls.owner}` : "not inspected";
19704
+ lines.push(`${table2.name} (${rls})`);
19705
+ if (table2.seed) {
19706
+ const reused = [table2.seed.reused.owner ? "the owner" : "", table2.seed.reused.otherUser ? "the other user" : ""].filter(Boolean);
19126
19707
  lines.push(
19127
- ` One row for the owner and one for another user (${table.seed.rowsBefore} rows in the table)${reused.length > 0 ? `; reused the rows the migrations created for ${reused.join(" and ")}` : ""}.`
19708
+ ` One row for the owner and one for another user (${table2.seed.rowsBefore} rows in the table)${reused.length > 0 ? `; reused the rows the migrations created for ${reused.join(" and ")}` : ""}.`
19128
19709
  );
19129
19710
  }
19130
- if (table.unknownReason) lines.push(` Unknown: ${table.unknownReason}`);
19131
- const tested = table.cells.filter((c) => c.actor !== "service_role");
19711
+ if (table2.unknownReason) lines.push(` Unknown: ${table2.unknownReason}`);
19712
+ const tested = table2.cells.filter((c) => c.actor !== "service_role");
19132
19713
  const actorWidth = 14;
19133
19714
  const actionWidth = 10;
19134
19715
  lines.push(` ${"actor".padEnd(actorWidth)}${"action".padEnd(actionWidth)}${"expected".padEnd(10)}${"result".padEnd(9)}observed`);
19135
- for (const cell of tested) {
19136
- lines.push(` ${cell.actor.padEnd(actorWidth)}${cell.action.padEnd(actionWidth)}${cell.expected.padEnd(10)}${cell.outcome.padEnd(9)}${cell.observed.summary}`);
19716
+ for (const cell2 of tested) {
19717
+ lines.push(` ${cell2.actor.padEnd(actorWidth)}${cell2.action.padEnd(actionWidth)}${cell2.expected.padEnd(10)}${cell2.outcome.padEnd(9)}${cell2.observed.summary}`);
19137
19718
  }
19138
- const service = table.cells.filter((c) => c.actor === "service_role");
19719
+ const service = table2.cells.filter((c) => c.actor === "service_role");
19139
19720
  if (service.length > 0) lines.push(` ${"service_role".padEnd(actorWidth)}${"all".padEnd(actionWidth)}${"excluded".padEnd(10)}${"N/A".padEnd(9)}bypasses RLS by design, not tested`);
19140
19721
  }
19141
19722
  const s = result.summary;
@@ -19143,8 +19724,8 @@ function renderRlsText(result) {
19143
19724
  lines.push(`Result: ${s.pass} Pass, ${s.fail} Fail, ${s.unknown} Unknown, ${s.notApplicable} N/A \xB7 ${s.pass + s.fail} of ${s.tested} tested cells observed (${s.total} cells in the matrix)`);
19144
19725
  lines.push(result.verdict);
19145
19726
  const failed = result.tables.flatMap((t) => t.cells).filter((c) => c.outcome === "Fail");
19146
- for (const cell of failed) {
19147
- lines.push(` Fail ${cell.actor} ${cell.action} on ${cell.table}: ${cell.reason}`);
19727
+ for (const cell2 of failed) {
19728
+ lines.push(` Fail ${cell2.actor} ${cell2.action} on ${cell2.table}: ${cell2.reason}`);
19148
19729
  }
19149
19730
  const unknown = result.tables.flatMap((t) => t.cells).filter((c) => c.outcome === "Unknown");
19150
19731
  const unknownReasons = [...new Set(unknown.map((c) => c.reason))];
@@ -19829,7 +20410,19 @@ function buildConnectProviders(providers) {
19829
20410
  dashboardUrl: providerDashboardUrls[provider2.id]
19830
20411
  }));
19831
20412
  }
20413
+ function asksFirstByGap(artifact) {
20414
+ const asks = /* @__PURE__ */ new Map();
20415
+ try {
20416
+ for (const task of buildTaskList(artifact)) {
20417
+ const sentence = task.fixType === "repo-code" && task.requiresUserAction ? task.action?.match(/^Ask the user first: [^.]+\./)?.[0] : void 0;
20418
+ if (sentence) asks.set(task.gapId, sentence);
20419
+ }
20420
+ } catch {
20421
+ }
20422
+ return asks;
20423
+ }
19832
20424
  function buildMissionCards(artifact, providers) {
20425
+ const asksFirst = asksFirstByGap(artifact);
19833
20426
  const blockers = artifact.gaps.map((gap) => ({
19834
20427
  id: gap.id,
19835
20428
  title: gap.title,
@@ -19838,13 +20431,15 @@ function buildMissionCards(artifact, providers) {
19838
20431
  category: blockerCategoryLabel(gap),
19839
20432
  tone: blockerTone(gap.severity),
19840
20433
  prompt: gap.copyPrompt || void 0,
19841
- requiresUserAction: gapRequiresUserAction(gap)
20434
+ requiresUserAction: gapRequiresUserAction(gap),
20435
+ ...asksFirst.has(gap.id) ? { asksFirst: asksFirst.get(gap.id) } : {}
19842
20436
  }));
19843
20437
  const planSteps = artifact.gaps.map((gap) => ({
19844
20438
  id: gap.id,
19845
20439
  title: gap.title,
19846
20440
  category: planStepCategoryLabel(gap),
19847
- requiresUserAction: gapRequiresUserAction(gap)
20441
+ requiresUserAction: gapRequiresUserAction(gap),
20442
+ ...asksFirst.has(gap.id) ? { asksFirst: asksFirst.get(gap.id) } : {}
19848
20443
  }));
19849
20444
  const connectProviders = buildConnectProviders(providers);
19850
20445
  const blockerCount = blockers.length;
@@ -19859,7 +20454,7 @@ function buildMissionCards(artifact, providers) {
19859
20454
  };
19860
20455
  }
19861
20456
  function projectNameFromPath(pathValue) {
19862
- return (0, import_node_path33.basename)(pathValue.replace(/[\\/]$/, "")) || "current repo";
20457
+ return (0, import_node_path37.basename)(pathValue.replace(/[\\/]$/, "")) || "current repo";
19863
20458
  }
19864
20459
  function gapSearchText(gap) {
19865
20460
  return [
@@ -19927,15 +20522,7 @@ function providerStatusText(state) {
19927
20522
  return "Missing";
19928
20523
  }
19929
20524
  }
19930
- function verifyActionForGap(gap) {
19931
- if (gap?.id) {
19932
- return {
19933
- label: "Verify",
19934
- commandPreview: `npx -y viberaven verify --action ${gap.id}`,
19935
- intent: "verify_action",
19936
- actionId: gap.id
19937
- };
19938
- }
20525
+ function verifyActionForGap(_gap) {
19939
20526
  return {
19940
20527
  label: "Verify",
19941
20528
  commandPreview: "npx -y viberaven --verify",
@@ -20278,20 +20865,21 @@ function mapArtifactToLocalUiState(artifact, presentProviderIds) {
20278
20865
  selectedProviderId: selectedProvider.id,
20279
20866
  currentPrompt: selectedProvider.nextFix?.prompt ?? "",
20280
20867
  artifacts: {
20281
- tasklist: (0, import_node_path33.join)(".viberaven", "agent-tasklist.md"),
20282
- contextMap: (0, import_node_path33.join)(".viberaven", "context-map.json"),
20283
- gateResult: (0, import_node_path33.join)(".viberaven", "gate-result.json"),
20284
- lastScan: (0, import_node_path33.join)(".viberaven", "last-scan.json")
20868
+ tasklist: (0, import_node_path37.join)(".viberaven", "agent-tasklist.md"),
20869
+ contextMap: (0, import_node_path37.join)(".viberaven", "context-map.json"),
20870
+ gateResult: (0, import_node_path37.join)(".viberaven", "gate-result.json"),
20871
+ lastScan: (0, import_node_path37.join)(".viberaven", "last-scan.json")
20285
20872
  },
20286
20873
  empty: false
20287
20874
  };
20288
20875
  }
20289
- var import_node_path33, CONNECT_PROVIDER_PRIORITY, providerDashboardUrls, providerHints, PROVIDER_OWNED_GAP;
20876
+ var import_node_path37, CONNECT_PROVIDER_PRIORITY, providerDashboardUrls, providerHints, PROVIDER_OWNED_GAP;
20290
20877
  var init_artifactAdapter = __esm({
20291
20878
  "src/local-ui/artifactAdapter.ts"() {
20292
20879
  "use strict";
20293
- import_node_path33 = require("node:path");
20880
+ import_node_path37 = require("node:path");
20294
20881
  init_providerLogos();
20882
+ init_buildTaskList();
20295
20883
  init_prompts();
20296
20884
  init_providerLaunchPaths();
20297
20885
  init_launchRunway();
@@ -20592,18 +21180,18 @@ var init_planLimits = __esm({
20592
21180
  // src/hosted/credentials.ts
20593
21181
  function defaultConfigDir(env = process.env, platform = process.platform) {
20594
21182
  const override = env.VIBERAVEN_CONFIG_DIR?.trim();
20595
- if (override) return (0, import_node_path34.resolve)(override);
21183
+ if (override) return (0, import_node_path38.resolve)(override);
20596
21184
  if (platform === "win32") {
20597
- return (0, import_node_path34.join)(env.APPDATA?.trim() || (0, import_node_path34.join)((0, import_node_os.homedir)(), "AppData", "Roaming"), "viberaven");
21185
+ return (0, import_node_path38.join)(env.APPDATA?.trim() || (0, import_node_path38.join)((0, import_node_os2.homedir)(), "AppData", "Roaming"), "viberaven");
20598
21186
  }
20599
21187
  if (platform === "darwin") {
20600
- return (0, import_node_path34.join)((0, import_node_os.homedir)(), "Library", "Application Support", "viberaven");
21188
+ return (0, import_node_path38.join)((0, import_node_os2.homedir)(), "Library", "Application Support", "viberaven");
20601
21189
  }
20602
- return (0, import_node_path34.join)(env.XDG_CONFIG_HOME?.trim() || (0, import_node_path34.join)((0, import_node_os.homedir)(), ".config"), "viberaven");
21190
+ return (0, import_node_path38.join)(env.XDG_CONFIG_HOME?.trim() || (0, import_node_path38.join)((0, import_node_os2.homedir)(), ".config"), "viberaven");
20603
21191
  }
20604
21192
  function isInside(child, parent) {
20605
- const rel = (0, import_node_path34.relative)((0, import_node_path34.resolve)(parent), (0, import_node_path34.resolve)(child));
20606
- return rel === "" || !rel.startsWith("..") && !(0, import_node_path34.isAbsolute)(rel);
21193
+ const rel = (0, import_node_path38.relative)((0, import_node_path38.resolve)(parent), (0, import_node_path38.resolve)(child));
21194
+ return rel === "" || !rel.startsWith("..") && !(0, import_node_path38.isAbsolute)(rel);
20607
21195
  }
20608
21196
  async function retryTransient(work) {
20609
21197
  for (let attempt = 1; ; attempt += 1) {
@@ -20616,15 +21204,15 @@ async function retryTransient(work) {
20616
21204
  }
20617
21205
  }
20618
21206
  }
20619
- var import_node_child_process7, import_node_crypto3, import_promises27, import_node_os, import_node_path34, CREDENTIALS_FILE_NAME, CredentialLocationError, CredentialPermissionError, restrictWithIcacls, TRANSIENT_FS_CODES, FS_ATTEMPTS, FS_BACKOFF_MS, CredentialStore;
21207
+ var import_node_child_process9, import_node_crypto4, import_promises30, import_node_os2, import_node_path38, CREDENTIALS_FILE_NAME, CredentialLocationError, CredentialPermissionError, restrictWithIcacls, TRANSIENT_FS_CODES, FS_ATTEMPTS, FS_BACKOFF_MS, CredentialStore;
20620
21208
  var init_credentials = __esm({
20621
21209
  "src/hosted/credentials.ts"() {
20622
21210
  "use strict";
20623
- import_node_child_process7 = require("node:child_process");
20624
- import_node_crypto3 = require("node:crypto");
20625
- import_promises27 = require("node:fs/promises");
20626
- import_node_os = require("node:os");
20627
- import_node_path34 = require("node:path");
21211
+ import_node_child_process9 = require("node:child_process");
21212
+ import_node_crypto4 = require("node:crypto");
21213
+ import_promises30 = require("node:fs/promises");
21214
+ import_node_os2 = require("node:os");
21215
+ import_node_path38 = require("node:path");
20628
21216
  CREDENTIALS_FILE_NAME = "credentials.json";
20629
21217
  CredentialLocationError = class extends Error {
20630
21218
  };
@@ -20635,10 +21223,10 @@ var init_credentials = __esm({
20635
21223
  }
20636
21224
  };
20637
21225
  restrictWithIcacls = (path) => {
20638
- const name = (0, import_node_os.userInfo)().username;
21226
+ const name = (0, import_node_os2.userInfo)().username;
20639
21227
  const user = process.env.USERDOMAIN ? `${process.env.USERDOMAIN}\\${name}` : name;
20640
21228
  return new Promise((resolveRestrict, reject) => {
20641
- (0, import_node_child_process7.execFile)("icacls", [path, "/inheritance:r", "/grant:r", `${user}:F`], { windowsHide: true, timeout: 1e4 }, (error) => {
21229
+ (0, import_node_child_process9.execFile)("icacls", [path, "/inheritance:r", "/grant:r", `${user}:F`], { windowsHide: true, timeout: 1e4 }, (error) => {
20642
21230
  if (error) reject(error);
20643
21231
  else resolveRestrict();
20644
21232
  });
@@ -20651,9 +21239,9 @@ var init_credentials = __esm({
20651
21239
  constructor(configDir, workspacePath, options = {}) {
20652
21240
  this.configDir = configDir;
20653
21241
  this.workspacePath = workspacePath;
20654
- this.path = (0, import_node_path34.join)(configDir, CREDENTIALS_FILE_NAME);
21242
+ this.path = (0, import_node_path38.join)(configDir, CREDENTIALS_FILE_NAME);
20655
21243
  this.restrict = options.restrict !== void 0 ? options.restrict : process.platform === "win32" ? restrictWithIcacls : null;
20656
- this.fs = { readFile: (path, encoding) => (0, import_promises27.readFile)(path, encoding), rename: (from, to) => (0, import_promises27.rename)(from, to), rm: (path) => (0, import_promises27.rm)(path, { force: true }), ...options.fs };
21244
+ this.fs = { readFile: (path, encoding) => (0, import_promises30.readFile)(path, encoding), rename: (from, to) => (0, import_promises30.rename)(from, to), rm: (path) => (0, import_promises30.rm)(path, { force: true }), ...options.fs };
20657
21245
  }
20658
21246
  path;
20659
21247
  restrict;
@@ -20702,15 +21290,15 @@ var init_credentials = __esm({
20702
21290
  `Refusing to store the VibeRaven sign-in inside this project (${this.configDir}). Set VIBERAVEN_CONFIG_DIR to a folder outside the repo.`
20703
21291
  );
20704
21292
  }
20705
- await (0, import_promises27.mkdir)(this.configDir, { recursive: true, mode: 448 });
20706
- const temp = `${this.path}.${(0, import_node_crypto3.randomUUID)()}.tmp`;
20707
- await (0, import_promises27.writeFile)(temp, `${JSON.stringify(body, null, 2)}
21293
+ await (0, import_promises30.mkdir)(this.configDir, { recursive: true, mode: 448 });
21294
+ const temp = `${this.path}.${(0, import_node_crypto4.randomUUID)()}.tmp`;
21295
+ await (0, import_promises30.writeFile)(temp, `${JSON.stringify(body, null, 2)}
20708
21296
  `, { encoding: "utf8", mode: 384 });
20709
- await (0, import_promises27.chmod)(temp, 384).catch(() => void 0);
21297
+ await (0, import_promises30.chmod)(temp, 384).catch(() => void 0);
20710
21298
  try {
20711
21299
  if (this.restrict) await this.restrict(temp);
20712
21300
  } catch (error) {
20713
- await (0, import_promises27.rm)(temp, { force: true });
21301
+ await (0, import_promises30.rm)(temp, { force: true });
20714
21302
  const code = error?.code;
20715
21303
  const toolMissing = code === "ENOENT";
20716
21304
  throw new CredentialPermissionError(
@@ -20721,17 +21309,17 @@ var init_credentials = __esm({
20721
21309
  if (guard) {
20722
21310
  const onDisk = await this.readFor(guard.apiBaseUrl);
20723
21311
  if (onDisk?.accessToken !== guard.accessToken || guard.isCurrent && !guard.isCurrent()) {
20724
- await (0, import_promises27.rm)(temp, { force: true });
21312
+ await (0, import_promises30.rm)(temp, { force: true });
20725
21313
  return false;
20726
21314
  }
20727
21315
  }
20728
21316
  try {
20729
21317
  await retryTransient(() => this.fs.rename(temp, this.path));
20730
21318
  } catch (error) {
20731
- await (0, import_promises27.rm)(temp, { force: true });
21319
+ await (0, import_promises30.rm)(temp, { force: true });
20732
21320
  throw error;
20733
21321
  }
20734
- await (0, import_promises27.chmod)(this.path, 384).catch(() => void 0);
21322
+ await (0, import_promises30.chmod)(this.path, 384).catch(() => void 0);
20735
21323
  return true;
20736
21324
  }
20737
21325
  /** Keeps the stored plan label in step with what the API last said. */
@@ -21100,7 +21688,7 @@ async function buildHostedCheckPayload(workspacePath) {
21100
21688
  throw new Error("Could not assemble the full check request.");
21101
21689
  }
21102
21690
  function finalizePayload(payload) {
21103
- const folder = (0, import_node_path35.basename)(payload.workspacePath.split("\\").join("/").replace(/\/+$/, "")) || "project";
21691
+ const folder = (0, import_node_path39.basename)(payload.workspacePath.split("\\").join("/").replace(/\/+$/, "")) || "project";
21104
21692
  const redacted = redactPayload({ ...payload, workspacePath: folder });
21105
21693
  return { payload: redacted.payload, redactions: redacted.count };
21106
21694
  }
@@ -21131,13 +21719,13 @@ function splitSections(prompt) {
21131
21719
  chars: section.length
21132
21720
  }));
21133
21721
  }
21134
- var import_node_path35, PayloadCaptured;
21722
+ var import_node_path39, PayloadCaptured;
21135
21723
  var init_payload = __esm({
21136
21724
  "src/hosted/payload.ts"() {
21137
21725
  "use strict";
21138
21726
  init_fileScanner();
21139
21727
  init_orchestrator();
21140
- import_node_path35 = require("node:path");
21728
+ import_node_path39 = require("node:path");
21141
21729
  init_config();
21142
21730
  init_redact();
21143
21731
  PayloadCaptured = class extends Error {
@@ -21175,7 +21763,7 @@ function shapeResult(response, now = /* @__PURE__ */ new Date()) {
21175
21763
  const usage = toUsage(response.usage);
21176
21764
  const freeKeys = new Set(FREE_TRIAL_UNLOCKED_MAP_CATEGORY_KEYS);
21177
21765
  const unlocked = (key) => usage.plan === "pro" || freeKeys.has(key) && response.usage.unlockedMapCategoryKeys.includes(key);
21178
- const sorted = [...output.gaps].sort((a, b3) => SEVERITY_RANK3[a.severity] - SEVERITY_RANK3[b3.severity]);
21766
+ const sorted = [...output.gaps].sort((a, b3) => SEVERITY_RANK4[a.severity] - SEVERITY_RANK4[b3.severity]);
21179
21767
  const areas = PRODUCTION_MAP_LANES.map((lane) => {
21180
21768
  const gaps = sorted.filter((gap) => gap.primaryMapCategory === lane.extensionKey);
21181
21769
  const locked = !unlocked(lane.extensionKey);
@@ -21213,11 +21801,11 @@ function readServerMessage(body) {
21213
21801
  return body.slice(0, 300);
21214
21802
  }
21215
21803
  }
21216
- var import_node_crypto4, PRICING_URL, FREE_LIMIT_MESSAGE, PREVIEW_TTL_MS, STATUS_TIMEOUT_MS, STATUS_CACHE_MS, MAX_PREVIEWS, TRANSIENT_REASONS, SEVERITY_RANK3, FullCheckService;
21804
+ var import_node_crypto5, PRICING_URL, FREE_LIMIT_MESSAGE, PREVIEW_TTL_MS, STATUS_TIMEOUT_MS, STATUS_CACHE_MS, MAX_PREVIEWS, TRANSIENT_REASONS, SEVERITY_RANK4, FullCheckService;
21217
21805
  var init_fullCheck = __esm({
21218
21806
  "src/hosted/fullCheck.ts"() {
21219
21807
  "use strict";
21220
- import_node_crypto4 = require("node:crypto");
21808
+ import_node_crypto5 = require("node:crypto");
21221
21809
  init_engines();
21222
21810
  init_planLimits();
21223
21811
  init_productionMapLanes();
@@ -21232,7 +21820,7 @@ var init_fullCheck = __esm({
21232
21820
  STATUS_CACHE_MS = 5e3;
21233
21821
  MAX_PREVIEWS = 5;
21234
21822
  TRANSIENT_REASONS = /* @__PURE__ */ new Set(["network", "timeout", "server", "rate_limited", "busy"]);
21235
- SEVERITY_RANK3 = { critical: 0, warning: 1, info: 2 };
21823
+ SEVERITY_RANK4 = { critical: 0, warning: 1, info: 2 };
21236
21824
  FullCheckService = class {
21237
21825
  constructor(workspacePath, options = {}) {
21238
21826
  this.workspacePath = workspacePath;
@@ -21482,8 +22070,8 @@ var init_fullCheck = __esm({
21482
22070
  const built = await this.buildPayload(this.workspacePath);
21483
22071
  const final = finalizePayload(built.payload);
21484
22072
  this.prunePreviews();
21485
- const id = (0, import_node_crypto4.randomUUID)();
21486
- this.previews.set(id, { payload: final.payload, idempotencyKey: (0, import_node_crypto4.randomUUID)(), createdAt: Date.now() });
22073
+ const id = (0, import_node_crypto5.randomUUID)();
22074
+ this.previews.set(id, { payload: final.payload, idempotencyKey: (0, import_node_crypto5.randomUUID)(), createdAt: Date.now() });
21487
22075
  return { kind: "preview", preview: describePayload(id, `${this.apiBaseUrl}/v1/station/run`, final.payload, built.scan, final.redactions) };
21488
22076
  } catch (error) {
21489
22077
  if (error instanceof HostedCallError) return this.fromCallError(error);
@@ -21603,10 +22191,10 @@ async function connectProviderMcp(cwd, providerId) {
21603
22191
  if (!recipe) {
21604
22192
  return { ok: false, message: `No known MCP server for ${providerId} yet. Add one manually to .mcp.json.` };
21605
22193
  }
21606
- const configPath = (0, import_node_path36.join)(cwd, ".mcp.json");
22194
+ const configPath = (0, import_node_path40.join)(cwd, ".mcp.json");
21607
22195
  let config = {};
21608
22196
  try {
21609
- const raw = await (0, import_promises28.readFile)(configPath, "utf8");
22197
+ const raw = await (0, import_promises31.readFile)(configPath, "utf8");
21610
22198
  const parsed = JSON.parse(raw.replace(/^\uFEFF/, ""));
21611
22199
  if (!isRecord5(parsed) || parsed.mcpServers !== void 0 && !isRecord5(parsed.mcpServers)) {
21612
22200
  return { ok: false, message: "Existing .mcp.json has an unsupported structure. It was left unchanged." };
@@ -21623,7 +22211,7 @@ async function connectProviderMcp(cwd, providerId) {
21623
22211
  }
21624
22212
  servers[providerId] = recipe.entry;
21625
22213
  const next = { ...config, mcpServers: servers };
21626
- await (0, import_promises28.writeFile)(configPath, `${JSON.stringify(next, null, 2)}
22214
+ await (0, import_promises31.writeFile)(configPath, `${JSON.stringify(next, null, 2)}
21627
22215
  `, "utf8");
21628
22216
  const suffix = recipe.note ? ` ${recipe.note}` : "";
21629
22217
  return { ok: true, message: `Added ${providerId} MCP server to .mcp.json.${suffix}`, path: ".mcp.json" };
@@ -21635,7 +22223,7 @@ function headerValue(request, name) {
21635
22223
  function tokensMatch(given, expected) {
21636
22224
  const a = Buffer.from(given, "utf8");
21637
22225
  const b3 = Buffer.from(expected, "utf8");
21638
- return a.length === b3.length && (0, import_node_crypto5.timingSafeEqual)(a, b3);
22226
+ return a.length === b3.length && (0, import_node_crypto6.timingSafeEqual)(a, b3);
21639
22227
  }
21640
22228
  function guardRequest(request, response, pathname, guard) {
21641
22229
  const allowedHosts = [`127.0.0.1:${guard.port}`, `localhost:${guard.port}`];
@@ -21676,7 +22264,7 @@ async function defaultScanRunner(flags, positional) {
21676
22264
  }
21677
22265
  async function readPackageJson(cwd) {
21678
22266
  try {
21679
- const raw = await (0, import_promises28.readFile)((0, import_node_path36.join)(cwd, "package.json"), "utf8");
22267
+ const raw = await (0, import_promises31.readFile)((0, import_node_path40.join)(cwd, "package.json"), "utf8");
21680
22268
  const parsed = JSON.parse(raw.replace(/^\uFEFF/, ""));
21681
22269
  return isRecord5(parsed) ? parsed : {};
21682
22270
  } catch {
@@ -21699,7 +22287,7 @@ async function listProjectFiles(cwd, maxFiles = 1800) {
21699
22287
  if (files.length >= maxFiles) return;
21700
22288
  let entries;
21701
22289
  try {
21702
- entries = await (0, import_promises28.readdir)(dir, { withFileTypes: true });
22290
+ entries = await (0, import_promises31.readdir)(dir, { withFileTypes: true });
21703
22291
  } catch {
21704
22292
  return;
21705
22293
  }
@@ -21707,7 +22295,7 @@ async function listProjectFiles(cwd, maxFiles = 1800) {
21707
22295
  if (files.length >= maxFiles) return;
21708
22296
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
21709
22297
  if (entry.isDirectory()) {
21710
- if (!skipped.has(entry.name)) await walk((0, import_node_path36.join)(dir, entry.name), rel);
22298
+ if (!skipped.has(entry.name)) await walk((0, import_node_path40.join)(dir, entry.name), rel);
21711
22299
  continue;
21712
22300
  }
21713
22301
  if (entry.isFile()) files.push(rel.toLowerCase());
@@ -21726,7 +22314,7 @@ async function readEnvKeys(cwd) {
21726
22314
  const keys = /* @__PURE__ */ new Set();
21727
22315
  for (const name of [".env.example", ".env"]) {
21728
22316
  try {
21729
- const raw = await (0, import_promises28.readFile)((0, import_node_path36.join)(cwd, name), "utf8");
22317
+ const raw = await (0, import_promises31.readFile)((0, import_node_path40.join)(cwd, name), "utf8");
21730
22318
  for (const line of raw.split(/\r?\n/)) {
21731
22319
  const trimmed = line.trim();
21732
22320
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -21820,7 +22408,7 @@ function applyProviderDetection(state, deps, files) {
21820
22408
  }
21821
22409
  function runGit(cwd, args, timeoutMs = 1500) {
21822
22410
  return safeGitInvocation(cwd, args).then(
21823
- (git) => spawnGit(cwd, git.args, git.env, timeoutMs),
22411
+ (git2) => spawnGit(cwd, git2.args, git2.env, timeoutMs),
21824
22412
  (error) => {
21825
22413
  if (error instanceof SafeGitRefusal) throw error;
21826
22414
  return "";
@@ -21829,7 +22417,7 @@ function runGit(cwd, args, timeoutMs = 1500) {
21829
22417
  }
21830
22418
  function spawnGit(cwd, args, env, timeoutMs) {
21831
22419
  return new Promise((resolveGit) => {
21832
- const child = (0, import_node_child_process8.spawn)("git", args, {
22420
+ const child = (0, import_node_child_process10.spawn)("git", args, {
21833
22421
  cwd,
21834
22422
  env,
21835
22423
  windowsHide: true,
@@ -21892,7 +22480,7 @@ function parseGitShortstat(raw) {
21892
22480
  }
21893
22481
  function runGitCommand(cwd, args, timeoutMs = 5e3) {
21894
22482
  return safeGitInvocation(cwd, args).then(
21895
- (git) => spawnGitCommand(cwd, git.args, git.env, timeoutMs),
22483
+ (git2) => spawnGitCommand(cwd, git2.args, git2.env, timeoutMs),
21896
22484
  (error) => ({
21897
22485
  code: 1,
21898
22486
  stdout: "",
@@ -21902,7 +22490,7 @@ function runGitCommand(cwd, args, timeoutMs = 5e3) {
21902
22490
  }
21903
22491
  function spawnGitCommand(cwd, args, env, timeoutMs) {
21904
22492
  return new Promise((resolveCommand) => {
21905
- const child = (0, import_node_child_process8.spawn)("git", args, {
22493
+ const child = (0, import_node_child_process10.spawn)("git", args, {
21906
22494
  cwd,
21907
22495
  env,
21908
22496
  windowsHide: true,
@@ -22044,7 +22632,7 @@ async function codexModelOptions() {
22044
22632
  { id: "gpt-5.4-mini", label: "GPT-5.4 Mini" }
22045
22633
  ];
22046
22634
  try {
22047
- const raw = await (0, import_promises28.readFile)((0, import_node_path36.join)((0, import_node_os2.homedir)(), ".codex", "models_cache.json"), "utf8");
22635
+ const raw = await (0, import_promises31.readFile)((0, import_node_path40.join)((0, import_node_os3.homedir)(), ".codex", "models_cache.json"), "utf8");
22048
22636
  const parsed = JSON.parse(raw);
22049
22637
  const list = Array.isArray(parsed) ? parsed : parsed.models ?? [];
22050
22638
  const options = list.filter((model) => typeof model?.slug === "string").filter((model) => !/auto-review|reserve/i.test(model.slug)).map((model) => ({ id: model.slug, label: model.display_name?.trim() || model.slug }));
@@ -22068,7 +22656,7 @@ function cliModelDefaults(id) {
22068
22656
  }
22069
22657
  function detectCommand(command, cwd) {
22070
22658
  return new Promise((resolveDetect) => {
22071
- const child = (0, import_node_child_process8.spawn)(process.platform === "win32" ? "where.exe" : "sh", process.platform === "win32" ? [command] : ["-lc", `command -v ${command}`], {
22659
+ const child = (0, import_node_child_process10.spawn)(process.platform === "win32" ? "where.exe" : "sh", process.platform === "win32" ? [command] : ["-lc", `command -v ${command}`], {
22072
22660
  cwd,
22073
22661
  windowsHide: true,
22074
22662
  stdio: ["ignore", "pipe", "pipe"]
@@ -22095,7 +22683,7 @@ function commandMissingFromOutput(text) {
22095
22683
  return /not recognized|not found|unknown command|no such file|command not found|is not recognized as an internal or external command/.test(text.toLowerCase());
22096
22684
  }
22097
22685
  function redactProbeOutput(output) {
22098
- const home = (0, import_node_os2.homedir)();
22686
+ const home = (0, import_node_os3.homedir)();
22099
22687
  const homeVariants = [home, home.split("\\").join("\\\\"), home.split("\\").join("/")].filter((value) => value.length > 3);
22100
22688
  let scrubbed = output;
22101
22689
  for (const variant of homeVariants) scrubbed = scrubbed.split(variant).join("~");
@@ -22155,7 +22743,7 @@ function setupCommandFor(cliId, step) {
22155
22743
  function runShellCommand(command, cwd, timeoutMs = 18e4) {
22156
22744
  return new Promise((resolveRun) => {
22157
22745
  const useCmd = process.platform === "win32";
22158
- const child = (0, import_node_child_process8.spawn)(useCmd ? process.env.ComSpec || "cmd.exe" : "sh", useCmd ? ["/d", "/s", "/c", command] : ["-lc", command], {
22746
+ const child = (0, import_node_child_process10.spawn)(useCmd ? process.env.ComSpec || "cmd.exe" : "sh", useCmd ? ["/d", "/s", "/c", command] : ["-lc", command], {
22159
22747
  cwd,
22160
22748
  windowsHide: true,
22161
22749
  stdio: ["ignore", "pipe", "pipe"]
@@ -22189,7 +22777,7 @@ function openSystemTerminal(command, cwd) {
22189
22777
  const fullCommand = process.platform === "win32" ? `cd /d ${quotedCwd} && ${command}` : `cd ${quotedCwd} && ${command}`;
22190
22778
  let child;
22191
22779
  if (process.platform === "win32") {
22192
- child = (0, import_node_child_process8.spawn)("cmd.exe", ["/c", "start", "cmd.exe", "/k", fullCommand], {
22780
+ child = (0, import_node_child_process10.spawn)("cmd.exe", ["/c", "start", "cmd.exe", "/k", fullCommand], {
22193
22781
  cwd,
22194
22782
  windowsHide: true,
22195
22783
  stdio: "ignore",
@@ -22197,9 +22785,9 @@ function openSystemTerminal(command, cwd) {
22197
22785
  });
22198
22786
  } else if (process.platform === "darwin") {
22199
22787
  const script = `tell application "Terminal" to do script "${fullCommand.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
22200
- child = (0, import_node_child_process8.spawn)("osascript", ["-e", script], { cwd, windowsHide: true, stdio: "ignore", detached: true });
22788
+ child = (0, import_node_child_process10.spawn)("osascript", ["-e", script], { cwd, windowsHide: true, stdio: "ignore", detached: true });
22201
22789
  } else {
22202
- child = (0, import_node_child_process8.spawn)("x-terminal-emulator", ["-e", `bash -lc ${JSON.stringify(fullCommand)}`], {
22790
+ child = (0, import_node_child_process10.spawn)("x-terminal-emulator", ["-e", `bash -lc ${JSON.stringify(fullCommand)}`], {
22203
22791
  cwd,
22204
22792
  windowsHide: true,
22205
22793
  stdio: "ignore",
@@ -22247,19 +22835,19 @@ function isPlaceholderProjectName(value) {
22247
22835
  async function readMcpEvidence(cwd) {
22248
22836
  const candidates = MCP_CONFIG_PATHS.map((relativePath) => ({
22249
22837
  source: relativePath.replace(/\\/g, "/"),
22250
- path: (0, import_node_path36.resolve)(cwd, relativePath)
22838
+ path: (0, import_node_path40.resolve)(cwd, relativePath)
22251
22839
  }));
22252
22840
  const home = process.env.USERPROFILE || process.env.HOME;
22253
22841
  if (home) {
22254
22842
  candidates.push({
22255
22843
  source: "~/.codex/config.toml",
22256
- path: (0, import_node_path36.resolve)(home, ".codex", "config.toml")
22844
+ path: (0, import_node_path40.resolve)(home, ".codex", "config.toml")
22257
22845
  });
22258
22846
  }
22259
22847
  const evidence = [];
22260
22848
  for (const candidate of candidates) {
22261
22849
  try {
22262
- const text = await (0, import_promises28.readFile)(candidate.path, "utf8");
22850
+ const text = await (0, import_promises31.readFile)(candidate.path, "utf8");
22263
22851
  if (/mcp/i.test(text)) evidence.push({ source: candidate.source, text });
22264
22852
  } catch {
22265
22853
  }
@@ -22350,7 +22938,7 @@ async function enrichProjectMetadata(state, cwd) {
22350
22938
  async function readState(cwd) {
22351
22939
  let state;
22352
22940
  try {
22353
- const raw = await (0, import_promises28.readFile)((0, import_node_path36.join)(cwd, ".viberaven", "last-scan.json"), "utf8");
22941
+ const raw = await (0, import_promises31.readFile)((0, import_node_path40.join)(cwd, ".viberaven", "last-scan.json"), "utf8");
22354
22942
  const packageJson = await readPackageJson(cwd);
22355
22943
  const deps = dependencyNames(packageJson);
22356
22944
  const files = await listProjectFiles(cwd);
@@ -22596,7 +23184,7 @@ function quoteWindowsCmdArg(value) {
22596
23184
  }
22597
23185
  async function whereExe(command, cwd) {
22598
23186
  return new Promise((resolvePath) => {
22599
- const child = (0, import_node_child_process8.spawn)("where.exe", [command], {
23187
+ const child = (0, import_node_child_process10.spawn)("where.exe", [command], {
22600
23188
  cwd,
22601
23189
  windowsHide: true,
22602
23190
  stdio: ["ignore", "pipe", "ignore"]
@@ -22617,7 +23205,7 @@ async function whereExe(command, cwd) {
22617
23205
  }
22618
23206
  function expandShimPath(value, base) {
22619
23207
  const relative8 = value.replace(/^%(?:dp0%\\+|~dp0\\*)/i, "");
22620
- return relative8 === value ? value : (0, import_node_path36.join)(base, ...relative8.split(/[\\/]+/));
23208
+ return relative8 === value ? value : (0, import_node_path40.join)(base, ...relative8.split(/[\\/]+/));
22621
23209
  }
22622
23210
  function findShimTarget(lines, pattern, accept = () => true) {
22623
23211
  for (const line of lines) {
@@ -22628,21 +23216,21 @@ function findShimTarget(lines, pattern, accept = () => true) {
22628
23216
  }
22629
23217
  async function resolveWindowsShimTarget(shimPath) {
22630
23218
  try {
22631
- const content = await (0, import_promises28.readFile)(shimPath, "utf8");
22632
- const base = (0, import_node_path36.dirname)(shimPath);
23219
+ const content = await (0, import_promises31.readFile)(shimPath, "utf8");
23220
+ const base = (0, import_node_path40.dirname)(shimPath);
22633
23221
  const lines = content.split(/\r?\n/);
22634
23222
  const trailingArgs = (line, after) => [...line.slice(after).split("%*")[0].matchAll(/"([^"]*)"|([^\s"&|<>]+)/g)].map((token) => expandShimPath(token[1] ?? token[2], base)).filter((value) => value.length > 0 && !value.includes("%"));
22635
23223
  const script = findShimTarget(lines, /"%(?:dp0%\\+|~dp0\\*)([^"]+\.js)"/i);
22636
23224
  if (script) {
22637
23225
  return {
22638
23226
  command: process.execPath,
22639
- args: [(0, import_node_path36.join)(base, ...script.match[1].split(/[\\/]+/)), ...trailingArgs(script.line, script.match.index + script.match[0].length)]
23227
+ args: [(0, import_node_path40.join)(base, ...script.match[1].split(/[\\/]+/)), ...trailingArgs(script.line, script.match.index + script.match[0].length)]
22640
23228
  };
22641
23229
  }
22642
- const binary = findShimTarget(lines, /"%(?:dp0%\\+|~dp0\\*)([^"]+\.exe)"/i, (target) => (0, import_node_path36.basename)(target).toLowerCase() !== "node.exe");
23230
+ const binary = findShimTarget(lines, /"%(?:dp0%\\+|~dp0\\*)([^"]+\.exe)"/i, (target) => (0, import_node_path40.basename)(target).toLowerCase() !== "node.exe");
22643
23231
  if (binary) {
22644
23232
  return {
22645
- command: (0, import_node_path36.join)(base, ...binary.match[1].split(/[\\/]+/)),
23233
+ command: (0, import_node_path40.join)(base, ...binary.match[1].split(/[\\/]+/)),
22646
23234
  args: trailingArgs(binary.line, binary.match.index + binary.match[0].length)
22647
23235
  };
22648
23236
  }
@@ -22663,7 +23251,7 @@ function killProcessTree(pid, fallback) {
22663
23251
  return;
22664
23252
  }
22665
23253
  if (process.platform === "win32") {
22666
- const killer = (0, import_node_child_process8.spawn)("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true, stdio: "ignore" });
23254
+ const killer = (0, import_node_child_process10.spawn)("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true, stdio: "ignore" });
22667
23255
  killer.once("error", fallback);
22668
23256
  killer.once("close", (code) => {
22669
23257
  if (code !== 0) fallback();
@@ -22688,7 +23276,7 @@ async function runAgentCommand(command, args, cwd, stdin, runId) {
22688
23276
  const spawnCommand = shim?.command ?? (useCmdShim ? process.env.ComSpec || "cmd.exe" : command);
22689
23277
  const spawnArgs = shim ? [...shim.args, ...args] : useCmdShim ? ["/d", "/s", "/c", [command, ...args.map(quoteWindowsCmdArg)].join(" ")] : args;
22690
23278
  return new Promise((resolveRun) => {
22691
- const child = (0, import_node_child_process8.spawn)(spawnCommand, spawnArgs, {
23279
+ const child = (0, import_node_child_process10.spawn)(spawnCommand, spawnArgs, {
22692
23280
  cwd,
22693
23281
  windowsHide: true,
22694
23282
  // A separate process group lets Stop and timeout terminate descendants on Unix too.
@@ -22767,8 +23355,8 @@ async function writeChatContextArtifact(cwd, request, prompt) {
22767
23355
  "",
22768
23356
  prompt
22769
23357
  ].filter((line) => typeof line === "string");
22770
- await (0, import_promises28.mkdir)((0, import_node_path36.join)(cwd, ".viberaven"), { recursive: true });
22771
- await (0, import_promises28.writeFile)((0, import_node_path36.join)(cwd, ".viberaven", "chat-context.md"), `${lines.join("\n")}
23358
+ await (0, import_promises31.mkdir)((0, import_node_path40.join)(cwd, ".viberaven"), { recursive: true });
23359
+ await (0, import_promises31.writeFile)((0, import_node_path40.join)(cwd, ".viberaven", "chat-context.md"), `${lines.join("\n")}
22772
23360
  `, "utf8");
22773
23361
  } catch {
22774
23362
  }
@@ -22788,13 +23376,13 @@ async function runAgentChat(cwd, request) {
22788
23376
  }
22789
23377
  const prompt = await buildAgentChatPrompt(request, cwd);
22790
23378
  await writeChatContextArtifact(cwd, request, prompt);
22791
- const outputPath = request.cliId === "codex" ? (0, import_node_path36.join)(cwd, ".viberaven", `studio-agent-${Date.now()}-${Math.random().toString(36).slice(2)}.md`) : void 0;
22792
- if (outputPath) await (0, import_promises28.mkdir)((0, import_node_path36.dirname)(outputPath), { recursive: true });
23379
+ const outputPath = request.cliId === "codex" ? (0, import_node_path40.join)(cwd, ".viberaven", `studio-agent-${Date.now()}-${Math.random().toString(36).slice(2)}.md`) : void 0;
23380
+ if (outputPath) await (0, import_promises31.mkdir)((0, import_node_path40.dirname)(outputPath), { recursive: true });
22793
23381
  const { command, args, stdin } = commandForAgentChat(request, prompt, cwd, outputPath);
22794
23382
  const result = await runAgentCommand(command, args, cwd, stdin, request.runId);
22795
23383
  if (outputPath) {
22796
23384
  try {
22797
- const finalMessage = (await (0, import_promises28.readFile)(outputPath, "utf8")).trim();
23385
+ const finalMessage = (await (0, import_promises31.readFile)(outputPath, "utf8")).trim();
22798
23386
  if (finalMessage) return { ...result, output: finalMessage };
22799
23387
  } catch {
22800
23388
  }
@@ -22806,8 +23394,8 @@ function firstGapId(state) {
22806
23394
  return state.providers.find((provider2) => provider2.nextFix?.id)?.nextFix?.id;
22807
23395
  }
22808
23396
  async function runLocalAction(cwd, intent) {
22809
- const artifactDir = (0, import_node_path36.join)(cwd, ".viberaven");
22810
- await (0, import_promises28.mkdir)(artifactDir, { recursive: true });
23397
+ const artifactDir = (0, import_node_path40.join)(cwd, ".viberaven");
23398
+ await (0, import_promises31.mkdir)(artifactDir, { recursive: true });
22811
23399
  if (intent === "preview_rehearsal") {
22812
23400
  const state2 = await readState(cwd);
22813
23401
  const topProvider = state2.providers[0];
@@ -22848,7 +23436,7 @@ async function runLocalAction(cwd, intent) {
22848
23436
  proof,
22849
23437
  ""
22850
23438
  ].join("\n");
22851
- await (0, import_promises28.writeFile)((0, import_node_path36.join)(artifactDir, "preview-rehearsal.md"), markdown, "utf8");
23439
+ await (0, import_promises31.writeFile)((0, import_node_path40.join)(artifactDir, "preview-rehearsal.md"), markdown, "utf8");
22852
23440
  return {
22853
23441
  title: "Preview rehearsal ready",
22854
23442
  summary: `${projectName}: ${gateLabel} with ${state2.providers.length} provider beat${state2.providers.length === 1 ? "" : "s"}`,
@@ -22858,7 +23446,7 @@ async function runLocalAction(cwd, intent) {
22858
23446
  if (intent === "audit_vercel_supabase") {
22859
23447
  const auditInput = await collectVercelSupabaseAuditInput(cwd);
22860
23448
  const result2 = buildVercelSupabaseAudit(auditInput);
22861
- await (0, import_promises28.writeFile)((0, import_node_path36.join)(artifactDir, "audit-vercel-supabase.txt"), `${renderVercelSupabaseAudit(result2)}
23449
+ await (0, import_promises31.writeFile)((0, import_node_path40.join)(artifactDir, "audit-vercel-supabase.txt"), `${renderVercelSupabaseAudit(result2)}
22862
23450
  `, "utf8");
22863
23451
  return {
22864
23452
  title: result2.status === "pass" ? "Audit passed" : "Audit needs work",
@@ -22886,7 +23474,7 @@ async function runLocalAction(cwd, intent) {
22886
23474
  const mode = intent === "heal_prompt" ? "prompt" : intent === "heal_apply" ? "apply" : "plan";
22887
23475
  const result = await runHealCommand({ cwd, mode, gapId, yes: mode === "apply", noVerify: true });
22888
23476
  if (mode === "apply") {
22889
- await (0, import_promises28.writeFile)((0, import_node_path36.join)(artifactDir, "heal-apply-result.json"), `${JSON.stringify(result, null, 2)}
23477
+ await (0, import_promises31.writeFile)((0, import_node_path40.join)(artifactDir, "heal-apply-result.json"), `${JSON.stringify(result, null, 2)}
22890
23478
  `, "utf8");
22891
23479
  }
22892
23480
  return {
@@ -22896,14 +23484,14 @@ async function runLocalAction(cwd, intent) {
22896
23484
  };
22897
23485
  }
22898
23486
  async function sendArtifact(cwd, rel, response, contentType = "text/plain; charset=utf-8") {
22899
- const absolute = (0, import_node_path36.resolve)(cwd, rel);
22900
- const root = (0, import_node_path36.resolve)(cwd);
23487
+ const absolute = (0, import_node_path40.resolve)(cwd, rel);
23488
+ const root = (0, import_node_path40.resolve)(cwd);
22901
23489
  if (!absolute.startsWith(root)) {
22902
23490
  sendText(response, 403, "Forbidden");
22903
23491
  return;
22904
23492
  }
22905
23493
  try {
22906
- const text = await (0, import_promises28.readFile)(absolute, "utf8");
23494
+ const text = await (0, import_promises31.readFile)(absolute, "utf8");
22907
23495
  response.writeHead(200, { "content-type": contentType });
22908
23496
  response.end(text);
22909
23497
  } catch {
@@ -22912,7 +23500,7 @@ async function sendArtifact(cwd, rel, response, contentType = "text/plain; chars
22912
23500
  }
22913
23501
  async function sendReportArtifact(cwd, response) {
22914
23502
  try {
22915
- const text = await (0, import_promises28.readFile)((0, import_node_path36.join)(cwd, ".viberaven", "report.html"), "utf8");
23503
+ const text = await (0, import_promises31.readFile)((0, import_node_path40.join)(cwd, ".viberaven", "report.html"), "utf8");
22916
23504
  response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
22917
23505
  response.end(text);
22918
23506
  return;
@@ -23039,7 +23627,7 @@ async function sendReportArtifact(cwd, response) {
23039
23627
  }
23040
23628
  async function sendExtensionIcon(cwd, response) {
23041
23629
  try {
23042
- const body = await (0, import_promises28.readFile)((0, import_node_path36.join)(cwd, "media", "extension-icon.png"));
23630
+ const body = await (0, import_promises31.readFile)((0, import_node_path40.join)(cwd, "media", "extension-icon.png"));
23043
23631
  sendBinary(response, 200, "image/png", body);
23044
23632
  } catch {
23045
23633
  sendText(response, 404, "Icon not found");
@@ -23052,15 +23640,15 @@ async function sendMangaAsset(pathname, response) {
23052
23640
  return;
23053
23641
  }
23054
23642
  const candidates = [
23055
- (0, import_node_path36.join)(__dirname, "manga", name),
23643
+ (0, import_node_path40.join)(__dirname, "manga", name),
23056
23644
  // installed + dev: <pkg>/dist/manga
23057
- (0, import_node_path36.join)(__dirname, "..", "assets", "manga", name),
23645
+ (0, import_node_path40.join)(__dirname, "..", "assets", "manga", name),
23058
23646
  // fallback: <pkg>/assets/manga (source)
23059
- (0, import_node_path36.join)(__dirname, "..", "..", "assets", "manga", name)
23647
+ (0, import_node_path40.join)(__dirname, "..", "..", "assets", "manga", name)
23060
23648
  ];
23061
23649
  for (const candidate of candidates) {
23062
23650
  try {
23063
- const body = await (0, import_promises28.readFile)(candidate);
23651
+ const body = await (0, import_promises31.readFile)(candidate);
23064
23652
  response.setHeader("cache-control", "no-cache");
23065
23653
  sendBinary(response, 200, contentTypeForAsset(candidate), body);
23066
23654
  return;
@@ -23070,7 +23658,7 @@ async function sendMangaAsset(pathname, response) {
23070
23658
  sendText(response, 404, "Asset not found");
23071
23659
  }
23072
23660
  function contentTypeForAsset(pathname) {
23073
- switch ((0, import_node_path36.extname)(pathname).toLowerCase()) {
23661
+ switch ((0, import_node_path40.extname)(pathname).toLowerCase()) {
23074
23662
  case ".svg":
23075
23663
  return "image/svg+xml; charset=utf-8";
23076
23664
  case ".png":
@@ -23087,14 +23675,14 @@ function contentTypeForAsset(pathname) {
23087
23675
  }
23088
23676
  async function sendBundledReportAsset(pathname, response) {
23089
23677
  const rel = decodeURIComponent(pathname.replace(/^\/report\//, ""));
23090
- const assetRoot = (0, import_node_path36.resolve)(getBundledReportAssetsDir());
23091
- const absolute = (0, import_node_path36.resolve)(assetRoot, rel);
23678
+ const assetRoot = (0, import_node_path40.resolve)(getBundledReportAssetsDir());
23679
+ const absolute = (0, import_node_path40.resolve)(assetRoot, rel);
23092
23680
  if (!absolute.startsWith(assetRoot)) {
23093
23681
  sendText(response, 403, "Forbidden");
23094
23682
  return;
23095
23683
  }
23096
23684
  try {
23097
- const body = await (0, import_promises28.readFile)(absolute);
23685
+ const body = await (0, import_promises31.readFile)(absolute);
23098
23686
  sendBinary(response, 200, contentTypeForAsset(absolute), body);
23099
23687
  } catch {
23100
23688
  sendText(response, 404, "Asset not found");
@@ -23116,18 +23704,18 @@ async function bundleStackClient(entryPath) {
23116
23704
  return output.text;
23117
23705
  }
23118
23706
  async function sendStackAppJs(response) {
23119
- const distPath = (0, import_node_path36.join)(__dirname, "stack", "app.js");
23707
+ const distPath = (0, import_node_path40.join)(__dirname, "stack", "app.js");
23120
23708
  try {
23121
- const body = await (0, import_promises28.readFile)(distPath);
23709
+ const body = await (0, import_promises31.readFile)(distPath);
23122
23710
  response.writeHead(200, { "content-type": "application/javascript; charset=utf-8", "cache-control": "no-store" });
23123
23711
  response.end(body);
23124
23712
  return;
23125
23713
  } catch {
23126
23714
  }
23127
- const entryPath = (0, import_node_path36.join)(__dirname, "stack", "main.ts");
23715
+ const entryPath = (0, import_node_path40.join)(__dirname, "stack", "main.ts");
23128
23716
  let mtimeMs;
23129
23717
  try {
23130
- mtimeMs = (await (0, import_promises28.stat)(entryPath)).mtimeMs;
23718
+ mtimeMs = (await (0, import_promises31.stat)(entryPath)).mtimeMs;
23131
23719
  } catch {
23132
23720
  sendText(response, 404, "Stack client entry not found: src/local-ui/stack/main.ts is missing.");
23133
23721
  return;
@@ -23146,7 +23734,7 @@ async function sendStackAppJs(response) {
23146
23734
  }
23147
23735
  async function sendStackAppCss(response) {
23148
23736
  try {
23149
- const body = await (0, import_promises28.readFile)((0, import_node_path36.join)(__dirname, "stack", "stack.css"));
23737
+ const body = await (0, import_promises31.readFile)((0, import_node_path40.join)(__dirname, "stack", "stack.css"));
23150
23738
  response.writeHead(200, { "content-type": "text/css; charset=utf-8", "cache-control": "no-store" });
23151
23739
  response.end(body);
23152
23740
  } catch {
@@ -23155,7 +23743,7 @@ async function sendStackAppCss(response) {
23155
23743
  }
23156
23744
  async function readProviderVerifyLedger(cwd) {
23157
23745
  try {
23158
- const raw = await (0, import_promises28.readFile)((0, import_node_path36.join)(cwd, ".viberaven", "provider-verify.json"), "utf8");
23746
+ const raw = await (0, import_promises31.readFile)((0, import_node_path40.join)(cwd, ".viberaven", "provider-verify.json"), "utf8");
23159
23747
  const parsed = JSON.parse(raw.replace(/^\uFEFF/, ""));
23160
23748
  return isRecord5(parsed) ? parsed : {};
23161
23749
  } catch {
@@ -23165,8 +23753,8 @@ async function readProviderVerifyLedger(cwd) {
23165
23753
  async function writeProviderVerifyEntry(cwd, providerId, entry) {
23166
23754
  const ledger = await readProviderVerifyLedger(cwd);
23167
23755
  ledger[providerId] = entry;
23168
- await (0, import_promises28.mkdir)((0, import_node_path36.join)(cwd, ".viberaven"), { recursive: true });
23169
- await (0, import_promises28.writeFile)((0, import_node_path36.join)(cwd, ".viberaven", "provider-verify.json"), `${JSON.stringify(ledger, null, 2)}
23756
+ await (0, import_promises31.mkdir)((0, import_node_path40.join)(cwd, ".viberaven"), { recursive: true });
23757
+ await (0, import_promises31.writeFile)((0, import_node_path40.join)(cwd, ".viberaven", "provider-verify.json"), `${JSON.stringify(ledger, null, 2)}
23170
23758
  `, "utf8");
23171
23759
  }
23172
23760
  async function handleRequest(request, response, options) {
@@ -23209,6 +23797,18 @@ async function handleRequest(request, response, options) {
23209
23797
  sendText(response, 404, "This VibeRaven surface has been removed. Use the Launch Canvas at /.");
23210
23798
  return;
23211
23799
  }
23800
+ if (request.method === "GET" && url.pathname === "/api/github-workflow") {
23801
+ sendJson(response, await githubWorkflowStatus(options.cwd));
23802
+ return;
23803
+ }
23804
+ if (request.method === "POST" && url.pathname === "/api/github-workflow") {
23805
+ try {
23806
+ sendJson(response, await writeGithubWorkflow(options.cwd));
23807
+ } catch (error) {
23808
+ sendJsonStatus(response, 500, { error: `Could not write the workflow: ${error instanceof Error ? error.message : String(error)}` });
23809
+ }
23810
+ return;
23811
+ }
23212
23812
  if (request.method === "GET" && url.pathname === "/api/project") {
23213
23813
  sendJson(response, await readState(options.cwd));
23214
23814
  return;
@@ -23227,13 +23827,13 @@ async function handleRequest(request, response, options) {
23227
23827
  return;
23228
23828
  }
23229
23829
  if (request.method === "GET" && url.pathname === "/api/architecture-map") {
23230
- const mapPath = (0, import_node_path36.join)(options.cwd, ".viberaven", "architecture-map.md");
23231
- const markdown = await (0, import_promises28.readFile)(mapPath, "utf8").catch(() => null);
23830
+ const mapPath = (0, import_node_path40.join)(options.cwd, ".viberaven", "architecture-map.md");
23831
+ const markdown = await (0, import_promises31.readFile)(mapPath, "utf8").catch(() => null);
23232
23832
  if (markdown === null) {
23233
23833
  sendJsonStatus(response, 404, { error: "No architecture map yet. Run a scan first (it is written on every scan)." });
23234
23834
  return;
23235
23835
  }
23236
- const dataRaw = await (0, import_promises28.readFile)((0, import_node_path36.join)(options.cwd, ".viberaven", "architecture-map.json"), "utf8").catch(() => null);
23836
+ const dataRaw = await (0, import_promises31.readFile)((0, import_node_path40.join)(options.cwd, ".viberaven", "architecture-map.json"), "utf8").catch(() => null);
23237
23837
  let data = null;
23238
23838
  if (dataRaw) {
23239
23839
  try {
@@ -23242,7 +23842,7 @@ async function handleRequest(request, response, options) {
23242
23842
  data = null;
23243
23843
  }
23244
23844
  }
23245
- const contextRaw = await (0, import_promises28.readFile)((0, import_node_path36.join)(options.cwd, ".viberaven", "context-map.json"), "utf8").catch(() => null);
23845
+ const contextRaw = await (0, import_promises31.readFile)((0, import_node_path40.join)(options.cwd, ".viberaven", "context-map.json"), "utf8").catch(() => null);
23246
23846
  let gaps = [];
23247
23847
  if (contextRaw) {
23248
23848
  try {
@@ -23508,35 +24108,35 @@ async function handleRequest(request, response, options) {
23508
24108
  return;
23509
24109
  }
23510
24110
  if (request.method === "GET" && url.pathname === "/api/artifacts/tasklist") {
23511
- await sendArtifact(options.cwd, (0, import_node_path36.join)(".viberaven", "agent-tasklist.md"), response);
24111
+ await sendArtifact(options.cwd, (0, import_node_path40.join)(".viberaven", "agent-tasklist.md"), response);
23512
24112
  return;
23513
24113
  }
23514
24114
  if (request.method === "GET" && url.pathname === "/api/artifacts/playbook") {
23515
- await sendArtifact(options.cwd, (0, import_node_path36.join)(".viberaven", "launch-playbook.md"), response);
24115
+ await sendArtifact(options.cwd, (0, import_node_path40.join)(".viberaven", "launch-playbook.md"), response);
23516
24116
  return;
23517
24117
  }
23518
24118
  if (request.method === "GET" && url.pathname === "/api/artifacts/audit") {
23519
- await sendArtifact(options.cwd, (0, import_node_path36.join)(".viberaven", "audit-vercel-supabase.txt"), response);
24119
+ await sendArtifact(options.cwd, (0, import_node_path40.join)(".viberaven", "audit-vercel-supabase.txt"), response);
23520
24120
  return;
23521
24121
  }
23522
24122
  if (request.method === "GET" && url.pathname === "/api/artifacts/cleanup") {
23523
- await sendArtifact(options.cwd, (0, import_node_path36.join)(".viberaven", "context-cleanup.md"), response);
24123
+ await sendArtifact(options.cwd, (0, import_node_path40.join)(".viberaven", "context-cleanup.md"), response);
23524
24124
  return;
23525
24125
  }
23526
24126
  if (request.method === "GET" && url.pathname === "/api/artifacts/preview") {
23527
- await sendArtifact(options.cwd, (0, import_node_path36.join)(".viberaven", "preview-rehearsal.md"), response);
24127
+ await sendArtifact(options.cwd, (0, import_node_path40.join)(".viberaven", "preview-rehearsal.md"), response);
23528
24128
  return;
23529
24129
  }
23530
24130
  if (request.method === "GET" && url.pathname === "/api/artifacts/heal-plan") {
23531
- await sendArtifact(options.cwd, (0, import_node_path36.join)(".viberaven", "heal-plan.md"), response);
24131
+ await sendArtifact(options.cwd, (0, import_node_path40.join)(".viberaven", "heal-plan.md"), response);
23532
24132
  return;
23533
24133
  }
23534
24134
  if (request.method === "GET" && url.pathname === "/api/artifacts/heal-prompt") {
23535
- await sendArtifact(options.cwd, (0, import_node_path36.join)(".viberaven", "heal-prompt.md"), response);
24135
+ await sendArtifact(options.cwd, (0, import_node_path40.join)(".viberaven", "heal-prompt.md"), response);
23536
24136
  return;
23537
24137
  }
23538
24138
  if (request.method === "GET" && url.pathname === "/api/artifacts/heal-apply") {
23539
- await sendArtifact(options.cwd, (0, import_node_path36.join)(".viberaven", "heal-apply-result.json"), response, "application/json; charset=utf-8");
24139
+ await sendArtifact(options.cwd, (0, import_node_path40.join)(".viberaven", "heal-apply-result.json"), response, "application/json; charset=utf-8");
23540
24140
  return;
23541
24141
  }
23542
24142
  if (request.method === "GET" && url.pathname === "/api/artifacts/report") {
@@ -23562,12 +24162,12 @@ async function handleRequest(request, response, options) {
23562
24162
  }
23563
24163
  if (request.method === "GET" && url.pathname === "/vendor/anime.umd.min.js") {
23564
24164
  for (const candidate of [
23565
- (0, import_node_path36.join)(__dirname, "vendor", "anime.umd.min.js"),
23566
- (0, import_node_path36.join)(__dirname, "..", "assets", "vendor", "anime.umd.min.js"),
23567
- (0, import_node_path36.join)(__dirname, "..", "..", "assets", "vendor", "anime.umd.min.js")
24165
+ (0, import_node_path40.join)(__dirname, "vendor", "anime.umd.min.js"),
24166
+ (0, import_node_path40.join)(__dirname, "..", "assets", "vendor", "anime.umd.min.js"),
24167
+ (0, import_node_path40.join)(__dirname, "..", "..", "assets", "vendor", "anime.umd.min.js")
23568
24168
  ]) {
23569
24169
  try {
23570
- const body = await (0, import_promises28.readFile)(candidate);
24170
+ const body = await (0, import_promises31.readFile)(candidate);
23571
24171
  response.setHeader("cache-control", "public, max-age=604800, immutable");
23572
24172
  sendBinary(response, 200, "text/javascript; charset=utf-8", body);
23573
24173
  return;
@@ -23784,11 +24384,11 @@ function listen(server, startPort) {
23784
24384
  });
23785
24385
  }
23786
24386
  async function startLocalUiServer(options) {
23787
- const cwd = (0, import_node_path36.resolve)(options.cwd);
24387
+ const cwd = (0, import_node_path40.resolve)(options.cwd);
23788
24388
  const scanRunner = options.scanRunner ?? defaultScanRunner;
23789
24389
  const autoScan = { state: "idle" };
23790
24390
  const fullCheck = new FullCheckService(cwd, options.fullCheck);
23791
- const guard = { token: (0, import_node_crypto5.randomBytes)(32).toString("base64url"), port: 0 };
24391
+ const guard = { token: (0, import_node_crypto6.randomBytes)(32).toString("base64url"), port: 0 };
23792
24392
  const server = (0, import_node_http.createServer)((request, response) => {
23793
24393
  const pathname = new URL(request.url ?? "/", "http://127.0.0.1").pathname;
23794
24394
  if (!guardRequest(request, response, pathname, guard)) {
@@ -23801,7 +24401,7 @@ async function startLocalUiServer(options) {
23801
24401
  });
23802
24402
  const port = await listen(server, options.port ?? 4177);
23803
24403
  guard.port = port;
23804
- const hasArtifact = await (0, import_promises28.readFile)((0, import_node_path36.join)(cwd, ".viberaven", "last-scan.json"), "utf8").then(
24404
+ const hasArtifact = await (0, import_promises31.readFile)((0, import_node_path40.join)(cwd, ".viberaven", "last-scan.json"), "utf8").then(
23805
24405
  () => true,
23806
24406
  () => false
23807
24407
  );
@@ -23840,16 +24440,17 @@ async function waitForServerShutdown() {
23840
24440
  process.once("SIGTERM", done);
23841
24441
  });
23842
24442
  }
23843
- var import_node_http, import_node_child_process8, import_node_crypto5, import_promises28, import_node_os2, import_node_path36, CLI_AGENT_SETUP, MCP_CONFIG_PATHS, MCP_PROVIDER_NEEDLES, MCP_CONNECT_COMMANDS, MCP_SERVER_RECIPES, STUDIO_TOKEN_HEADER, PROVIDER_IDS, STUDIO_STATUS_ARGS, CLI_AUTH_PROBE, AGENT_RUN_TIMEOUT_MS, activeAgentRuns, stackBundleCache;
24443
+ var import_node_http, import_node_child_process10, import_node_crypto6, import_promises31, import_node_os3, import_node_path40, CLI_AGENT_SETUP, MCP_CONFIG_PATHS, MCP_PROVIDER_NEEDLES, MCP_CONNECT_COMMANDS, MCP_SERVER_RECIPES, STUDIO_TOKEN_HEADER, PROVIDER_IDS, STUDIO_STATUS_ARGS, CLI_AUTH_PROBE, AGENT_RUN_TIMEOUT_MS, activeAgentRuns, stackBundleCache;
23844
24444
  var init_server = __esm({
23845
24445
  "src/local-ui/server.ts"() {
23846
24446
  "use strict";
23847
24447
  import_node_http = require("node:http");
23848
- import_node_child_process8 = require("node:child_process");
23849
- import_node_crypto5 = require("node:crypto");
23850
- import_promises28 = require("node:fs/promises");
23851
- import_node_os2 = require("node:os");
23852
- import_node_path36 = require("node:path");
24448
+ import_node_child_process10 = require("node:child_process");
24449
+ import_node_crypto6 = require("node:crypto");
24450
+ import_promises31 = require("node:fs/promises");
24451
+ import_node_os3 = require("node:os");
24452
+ import_node_path40 = require("node:path");
24453
+ init_initGithub();
23853
24454
  init_cleanPlan();
23854
24455
  init_audit();
23855
24456
  init_heal();
@@ -23890,8 +24491,8 @@ var init_server = __esm({
23890
24491
  MCP_CONFIG_PATHS = [
23891
24492
  ".mcp.json",
23892
24493
  "mcp.json",
23893
- (0, import_node_path36.join)(".cursor", "mcp.json"),
23894
- (0, import_node_path36.join)(".vscode", "mcp.json")
24494
+ (0, import_node_path40.join)(".cursor", "mcp.json"),
24495
+ (0, import_node_path40.join)(".vscode", "mcp.json")
23895
24496
  ];
23896
24497
  MCP_PROVIDER_NEEDLES = {
23897
24498
  supabase: ["supabase", "@supabase/mcp", "mcp-server-supabase"],
@@ -23976,13 +24577,25 @@ function buildNextActionBlock(tasks, loopState) {
23976
24577
  requiresUserAction: false
23977
24578
  };
23978
24579
  }
23979
- const providerTask = tasks.find((t) => t.fixType === "provider-action");
23980
- if (providerTask) {
24580
+ const userTask = tasks.find((t) => t.fixType === "provider-action" || t.fixType === "repo-code" && t.requiresUserAction);
24581
+ if (userTask?.fixType === "repo-code") {
24582
+ return {
24583
+ ...base,
24584
+ type: "repo-code",
24585
+ gapId: userTask.gapId,
24586
+ title: userTask.title,
24587
+ mcpTool: userTask.mcpTool,
24588
+ mcpArgs: userTask.mcpArgs,
24589
+ fallbackCommand: userTask.mcpArgs ? `npx -y viberaven --heal --apply --gap ${userTask.gapId} --yes` : void 0,
24590
+ requiresUserAction: true
24591
+ };
24592
+ }
24593
+ if (userTask) {
23981
24594
  return {
23982
24595
  ...base,
23983
24596
  type: "provider-action",
23984
- gapId: providerTask.gapId,
23985
- title: providerTask.title,
24597
+ gapId: userTask.gapId,
24598
+ title: userTask.title,
23986
24599
  requiresUserAction: true
23987
24600
  };
23988
24601
  }
@@ -24048,11 +24661,11 @@ var init_nextActionBlock = __esm({
24048
24661
 
24049
24662
  // src/providerMcpBridge.ts
24050
24663
  function defaultMcpConfigPaths() {
24051
- const home = (0, import_node_os3.homedir)();
24664
+ const home = (0, import_node_os4.homedir)();
24052
24665
  return [
24053
- (0, import_node_path37.join)(home, ".config", "claude", "claude_desktop_config.json"),
24054
- (0, import_node_path37.join)(home, ".cursor", "mcp.json"),
24055
- (0, import_node_path37.join)(home, ".gemini", "antigravity", "mcp_config.json")
24666
+ (0, import_node_path41.join)(home, ".config", "claude", "claude_desktop_config.json"),
24667
+ (0, import_node_path41.join)(home, ".cursor", "mcp.json"),
24668
+ (0, import_node_path41.join)(home, ".gemini", "antigravity", "mcp_config.json")
24056
24669
  ];
24057
24670
  }
24058
24671
  function resolveConfigPaths() {
@@ -24081,11 +24694,11 @@ function findServerEntry(servers, provider2) {
24081
24694
  function findProviderMcpConfig(provider2, configPaths) {
24082
24695
  const paths = configPaths ?? resolveConfigPaths();
24083
24696
  for (const path of paths) {
24084
- if (!(0, import_node_fs13.existsSync)(path)) {
24697
+ if (!(0, import_node_fs15.existsSync)(path)) {
24085
24698
  continue;
24086
24699
  }
24087
24700
  try {
24088
- const raw = JSON.parse((0, import_node_fs13.readFileSync)(path, "utf8"));
24701
+ const raw = JSON.parse((0, import_node_fs15.readFileSync)(path, "utf8"));
24089
24702
  const servers = parseMcpServers(raw);
24090
24703
  if (!servers) {
24091
24704
  continue;
@@ -24129,13 +24742,13 @@ async function verifyProviderGap(options) {
24129
24742
  fallbackCommand: FALLBACK_COMMAND
24130
24743
  };
24131
24744
  }
24132
- var import_node_fs13, import_node_os3, import_node_path37, FALLBACK_COMMAND, SUPPORTED_PROVIDERS, configPathsOverride;
24745
+ var import_node_fs15, import_node_os4, import_node_path41, FALLBACK_COMMAND, SUPPORTED_PROVIDERS, configPathsOverride;
24133
24746
  var init_providerMcpBridge = __esm({
24134
24747
  "src/providerMcpBridge.ts"() {
24135
24748
  "use strict";
24136
- import_node_fs13 = require("node:fs");
24137
- import_node_os3 = require("node:os");
24138
- import_node_path37 = require("node:path");
24749
+ import_node_fs15 = require("node:fs");
24750
+ import_node_os4 = require("node:os");
24751
+ import_node_path41 = require("node:path");
24139
24752
  FALLBACK_COMMAND = "npx -y viberaven audit --vercel-supabase --json";
24140
24753
  SUPPORTED_PROVIDERS = /* @__PURE__ */ new Set(["supabase", "vercel"]);
24141
24754
  }
@@ -24164,10 +24777,12 @@ Usage:
24164
24777
  viberaven tui Interactive terminal menu
24165
24778
 
24166
24779
  viberaven check [--json] [--quiet] [path]
24780
+ viberaven check --base <ref> [--markdown] What this change introduced and fixed, compared with <ref>
24781
+ viberaven init --github [--dry-run] Check every pull request in GitHub Actions
24167
24782
  Local production check: one line per finding, exit 1 on blockers
24168
24783
 
24169
- viberaven fix [--gap <id>] [path]
24170
- Apply a safe automated fix for a known gap (heal recipe)
24784
+ viberaven fix [--gap <id>] [--dry-run] [path]
24785
+ Apply a safe automated fix for a known gap (heal recipe); --dry-run previews only
24171
24786
 
24172
24787
  viberaven scan [--open] [--json] [path]
24173
24788
  Full local scan; writes .viberaven/ artifacts
@@ -24313,9 +24928,12 @@ function isBooleanFlag(command, key) {
24313
24928
  if (key === "open" && (command === "" || command === "scan" || command === "report")) return true;
24314
24929
  if (key === "verify" && command === "") return true;
24315
24930
  if (key === "quiet" && command === "check") return true;
24931
+ if (key === "markdown" && command === "check") return true;
24932
+ if (key === "github" && command === "init") return true;
24933
+ if (key === "force" && command === "init") return true;
24316
24934
  if (key === "vercel-supabase" && command === "audit") return true;
24317
24935
  if (key === "json" && command === "validate-npm-package") return true;
24318
- if (key === "dry-run" && command === "init") return true;
24936
+ if (key === "dry-run" && (command === "init" || command === "fix")) return true;
24319
24937
  if (key === "agents" && command === "doctor") return true;
24320
24938
  return false;
24321
24939
  }
@@ -24346,7 +24964,7 @@ async function cmdProviderVerify(flags, positional) {
24346
24964
  return result.verified ? 0 : 1;
24347
24965
  }
24348
24966
  async function cmdStatus(flags, positional) {
24349
- const startDir = positional[0] ? (0, import_node_path38.resolve)(process.cwd(), positional[0]) : process.cwd();
24967
+ const startDir = positional[0] ? (0, import_node_path42.resolve)(process.cwd(), positional[0]) : process.cwd();
24350
24968
  let artifact;
24351
24969
  try {
24352
24970
  artifact = await loadLastArtifact(startDir);
@@ -24385,7 +25003,7 @@ async function cmdStatus(flags, positional) {
24385
25003
  return 0;
24386
25004
  }
24387
25005
  async function runScanCommand(flags, positional, options) {
24388
- const workspacePath = positional[0] ? (0, import_node_path38.resolve)(process.cwd(), positional[0]) : await resolveWorkspaceRoot(process.cwd());
25006
+ const workspacePath = positional[0] ? (0, import_node_path42.resolve)(process.cwd(), positional[0]) : await resolveWorkspaceRoot(process.cwd());
24389
25007
  if (!options?.deferMachineOutput) {
24390
25008
  console.log(`Scanning ${workspacePath}\u2026 (local, offline)`);
24391
25009
  }
@@ -24412,7 +25030,7 @@ async function runScanCommand(flags, positional, options) {
24412
25030
  printNextActionBlock(block);
24413
25031
  printProviderActionBlock(tasks);
24414
25032
  try {
24415
- const manifest = JSON.parse(await (0, import_promises29.readFile)(paths.actionsPath, "utf8"));
25033
+ const manifest = JSON.parse(await (0, import_promises32.readFile)(paths.actionsPath, "utf8"));
24416
25034
  console.log(renderActionSurface(manifest, { limit: 5 }).trimEnd());
24417
25035
  } catch (error) {
24418
25036
  console.warn(error instanceof Error ? error.message : String(error));
@@ -24429,7 +25047,7 @@ async function runScanCommand(flags, positional, options) {
24429
25047
  return { exitCode: 0, artifacts: paths };
24430
25048
  }
24431
25049
  async function cmdReport(flags, positional) {
24432
- const startDir = positional[0] ? (0, import_node_path38.resolve)(process.cwd(), positional[0]) : process.cwd();
25050
+ const startDir = positional[0] ? (0, import_node_path42.resolve)(process.cwd(), positional[0]) : process.cwd();
24433
25051
  try {
24434
25052
  const paths = await refreshReportFromDisk(startDir);
24435
25053
  console.log(`Report refreshed: ${paths.reportPath}`);
@@ -24451,7 +25069,7 @@ async function cmdReport(flags, positional) {
24451
25069
  }
24452
25070
  }
24453
25071
  async function cmdPrompt(flags, positional) {
24454
- const startDir = positional[0] ? (0, import_node_path38.resolve)(process.cwd(), positional[0]) : process.cwd();
25072
+ const startDir = positional[0] ? (0, import_node_path42.resolve)(process.cwd(), positional[0]) : process.cwd();
24455
25073
  let artifact;
24456
25074
  try {
24457
25075
  artifact = await loadLastArtifact(startDir);
@@ -24548,7 +25166,7 @@ async function main() {
24548
25166
  const wantsJsonl = hasFlag(flags, "jsonl");
24549
25167
  const wantsStrict = hasFlag(flags, "strict");
24550
25168
  if (flags.condense) {
24551
- const cwd = positional[0] ? (0, import_node_path38.resolve)(process.cwd(), positional[0]) : process.cwd();
25169
+ const cwd = positional[0] ? (0, import_node_path42.resolve)(process.cwd(), positional[0]) : process.cwd();
24552
25170
  const result = await runCondenseCommand({ cwd });
24553
25171
  console.log(`VibeRaven context map refreshed: ${result.contextMapPath}`);
24554
25172
  return 0;
@@ -24568,7 +25186,7 @@ async function main() {
24568
25186
  }
24569
25187
  if (!command && flags.verify === true && typeof flags.action === "string") {
24570
25188
  return runVerifyActionCommand({
24571
- cwd: positional[0] ? (0, import_node_path38.resolve)(process.cwd(), positional[0]) : process.cwd(),
25189
+ cwd: positional[0] ? (0, import_node_path42.resolve)(process.cwd(), positional[0]) : process.cwd(),
24572
25190
  actionId: flags.action
24573
25191
  });
24574
25192
  }
@@ -24579,7 +25197,7 @@ async function main() {
24579
25197
  console.error("VibeRaven could not produce machine output because scan artifacts were not written.");
24580
25198
  return 3;
24581
25199
  }
24582
- const gateResult = scanResult.artifacts && (wantsJson || wantsJsonl || wantsStrict) ? JSON.parse(await (0, import_promises29.readFile)(scanResult.artifacts.gateResultPath, "utf8")) : void 0;
25200
+ const gateResult = scanResult.artifacts && (wantsJson || wantsJsonl || wantsStrict) ? JSON.parse(await (0, import_promises32.readFile)(scanResult.artifacts.gateResultPath, "utf8")) : void 0;
24583
25201
  const strictExitCode = wantsStrict && gateResult ? exitCodeForStrictGate(gateResult, { failOnWarnings: flags.strict === "warning" }) : scanResult.exitCode;
24584
25202
  if (wantsJson && gateResult) {
24585
25203
  process.stdout.write(renderGateResultJson(gateResult));
@@ -24601,7 +25219,7 @@ async function main() {
24601
25219
  env: process.env
24602
25220
  });
24603
25221
  if (mode === "local-ui") {
24604
- const cwd = positional[0] ? (0, import_node_path38.resolve)(process.cwd(), positional[0]) : process.cwd();
25222
+ const cwd = positional[0] ? (0, import_node_path42.resolve)(process.cwd(), positional[0]) : process.cwd();
24605
25223
  const handle = await startLocalUiServer({ cwd });
24606
25224
  console.log(`VibeRaven local UI: ${handle.url}`);
24607
25225
  await waitForServerShutdown();
@@ -24621,7 +25239,7 @@ async function main() {
24621
25239
  await runInteractiveSession();
24622
25240
  return 0;
24623
25241
  case "ui": {
24624
- const cwd = positional[0] ? (0, import_node_path38.resolve)(process.cwd(), positional[0]) : process.cwd();
25242
+ const cwd = positional[0] ? (0, import_node_path42.resolve)(process.cwd(), positional[0]) : process.cwd();
24625
25243
  const envPort = Number(process.env.PORT);
24626
25244
  const handle = await startLocalUiServer({ cwd, port: Number.isFinite(envPort) && envPort > 0 ? envPort : void 0 });
24627
25245
  console.log(`VibeRaven local UI: ${handle.url}`);
@@ -24633,31 +25251,35 @@ async function main() {
24633
25251
  return cmdStatus(flags, positional);
24634
25252
  case "check":
24635
25253
  return runCheckCommand({
24636
- cwd: positional[0] ? (0, import_node_path38.resolve)(process.cwd(), positional[0]) : process.cwd(),
25254
+ cwd: positional[0] ? (0, import_node_path42.resolve)(process.cwd(), positional[0]) : process.cwd(),
24637
25255
  json: Boolean(flags.json),
24638
- quiet: Boolean(flags.quiet)
25256
+ quiet: Boolean(flags.quiet),
25257
+ base: typeof flags.base === "string" ? flags.base : void 0,
25258
+ markdown: flags.markdown === true
24639
25259
  });
24640
25260
  case "fix": {
24641
- const fixCwd = positional[0] ? (0, import_node_path38.resolve)(process.cwd(), positional[0]) : process.cwd();
25261
+ const fixCwd = positional[0] ? (0, import_node_path42.resolve)(process.cwd(), positional[0]) : process.cwd();
24642
25262
  const gapId = typeof flags.gap === "string" ? flags.gap : void 0;
24643
25263
  if (!gapId) {
24644
25264
  const artifact = await loadLastArtifact(fixCwd).catch(() => void 0);
24645
- const fixable = artifact ? buildTaskList(artifact).filter((t) => t.fixType === "repo-code" && !t.requiresUserAction) : [];
25265
+ const fixable = artifact ? buildTaskList(artifact).filter((t) => t.fixType === "repo-code" && t.mcpTool === "viberaven_heal_apply") : [];
24646
25266
  if (fixable.length === 0) {
24647
25267
  console.log("Nothing to fix automatically. Run `viberaven check` first, then `viberaven fix --gap <id>`.");
24648
25268
  return artifact ? 0 : 1;
24649
25269
  }
24650
25270
  console.log("Fixable gaps:");
24651
25271
  for (const task of fixable) {
24652
- console.log(` viberaven fix --gap ${task.gapId} ${task.title}`);
25272
+ const ask = task.requiresUserAction ? task.action?.match(/^Ask the user first: ([^.]+)\./)?.[1] : void 0;
25273
+ console.log(` viberaven fix --gap ${task.gapId} ${task.title}${ask ? ` (asks first: ${ask})` : ""}`);
24653
25274
  }
24654
25275
  return 0;
24655
25276
  }
25277
+ const dryRun = flags["dry-run"] === true;
24656
25278
  const result = await runHealCommand({
24657
25279
  cwd: fixCwd,
24658
- mode: "apply",
25280
+ mode: dryRun ? "plan" : "apply",
24659
25281
  gapId,
24660
- yes: true,
25282
+ yes: !dryRun,
24661
25283
  noVerify: flags["no-verify"] === true
24662
25284
  });
24663
25285
  console.log(JSON.stringify(result, null, 2));
@@ -24665,19 +25287,19 @@ async function main() {
24665
25287
  }
24666
25288
  case "actions":
24667
25289
  return runActionsCommand({
24668
- cwd: positional[0] ? (0, import_node_path38.resolve)(process.cwd(), positional[0]) : process.cwd(),
25290
+ cwd: positional[0] ? (0, import_node_path42.resolve)(process.cwd(), positional[0]) : process.cwd(),
24669
25291
  json: Boolean(flags.json)
24670
25292
  });
24671
25293
  case "preview":
24672
25294
  return runPreviewCommand({
24673
- cwd: positional[0] ? (0, import_node_path38.resolve)(process.cwd(), positional[0]) : process.cwd(),
25295
+ cwd: positional[0] ? (0, import_node_path42.resolve)(process.cwd(), positional[0]) : process.cwd(),
24674
25296
  agentMode: flags["agent-mode"] === true,
24675
25297
  json: Boolean(flags.json)
24676
25298
  });
24677
25299
  case "next":
24678
25300
  return runNextCommand({
24679
25301
  json: Boolean(flags.json),
24680
- cwd: positional[0] ? (0, import_node_path38.resolve)(process.cwd(), positional[0]) : process.cwd()
25302
+ cwd: positional[0] ? (0, import_node_path42.resolve)(process.cwd(), positional[0]) : process.cwd()
24681
25303
  });
24682
25304
  case "guide": {
24683
25305
  const provider2 = positional[0];
@@ -24711,8 +25333,11 @@ async function main() {
24711
25333
  case "provider-verify":
24712
25334
  return cmdProviderVerify(flags, positional);
24713
25335
  case "init": {
24714
- const cwd = positional[0] ? (0, import_node_path38.resolve)(process.cwd(), positional[0]) : process.cwd();
25336
+ const cwd = positional[0] ? (0, import_node_path42.resolve)(process.cwd(), positional[0]) : process.cwd();
24715
25337
  const agents = typeof flags.agents === "string" ? flags.agents : void 0;
25338
+ if (flags.github === true) {
25339
+ return runInitGithubCommand({ cwd, dryRun: flags["dry-run"] === true, force: flags.force === true });
25340
+ }
24716
25341
  return runInitCommand({
24717
25342
  cwd,
24718
25343
  agents,
@@ -24725,7 +25350,7 @@ async function main() {
24725
25350
  return 1;
24726
25351
  }
24727
25352
  return runDoctorAgentsCommand({
24728
- cwd: positional[0] ? (0, import_node_path38.resolve)(process.cwd(), positional[0]) : process.cwd()
25353
+ cwd: positional[0] ? (0, import_node_path42.resolve)(process.cwd(), positional[0]) : process.cwd()
24729
25354
  });
24730
25355
  case "validate-npm-package":
24731
25356
  return runValidateNpmPackageCommand({
@@ -24738,7 +25363,7 @@ async function main() {
24738
25363
  return 1;
24739
25364
  }
24740
25365
  return runAuditCommand({
24741
- cwd: positional[0] ? (0, import_node_path38.resolve)(process.cwd(), positional[0]) : process.cwd(),
25366
+ cwd: positional[0] ? (0, import_node_path42.resolve)(process.cwd(), positional[0]) : process.cwd(),
24742
25367
  json: Boolean(flags.json)
24743
25368
  });
24744
25369
  case "clean": {
@@ -24746,7 +25371,7 @@ async function main() {
24746
25371
  console.error("Usage: viberaven clean --plan [path]");
24747
25372
  return 1;
24748
25373
  }
24749
- const cwd = positional[0] ? (0, import_node_path38.resolve)(process.cwd(), positional[0]) : process.cwd();
25374
+ const cwd = positional[0] ? (0, import_node_path42.resolve)(process.cwd(), positional[0]) : process.cwd();
24750
25375
  const plan = buildContextCleanupPlan({ projectRoot: cwd, files: await collectCleanupFiles(cwd) });
24751
25376
  const outputPath = await writeCleanupPlan(cwd, plan);
24752
25377
  console.log(`Wrote ${outputPath}
@@ -24756,9 +25381,9 @@ async function main() {
24756
25381
  }
24757
25382
  case "rls-test":
24758
25383
  return runRlsTestCommand({
24759
- cwd: positional[0] ? (0, import_node_path38.resolve)(process.cwd(), positional[0]) : process.cwd(),
24760
- matrix: typeof flags.matrix === "string" ? (0, import_node_path38.resolve)(process.cwd(), flags.matrix) : void 0,
24761
- migrations: typeof flags.migrations === "string" ? (0, import_node_path38.resolve)(process.cwd(), flags.migrations) : void 0,
25384
+ cwd: positional[0] ? (0, import_node_path42.resolve)(process.cwd(), positional[0]) : process.cwd(),
25385
+ matrix: typeof flags.matrix === "string" ? (0, import_node_path42.resolve)(process.cwd(), flags.matrix) : void 0,
25386
+ migrations: typeof flags.migrations === "string" ? (0, import_node_path42.resolve)(process.cwd(), flags.migrations) : void 0,
24762
25387
  json: Boolean(flags.json)
24763
25388
  });
24764
25389
  default:
@@ -24767,11 +25392,11 @@ async function main() {
24767
25392
  return 1;
24768
25393
  }
24769
25394
  }
24770
- var import_promises29, import_node_path38, STACK_AREAS;
25395
+ var import_promises32, import_node_path42, STACK_AREAS;
24771
25396
  var init_cli = __esm({
24772
25397
  "src/cli.ts"() {
24773
- import_promises29 = require("node:fs/promises");
24774
- import_node_path38 = require("node:path");
25398
+ import_promises32 = require("node:fs/promises");
25399
+ import_node_path42 = require("node:path");
24775
25400
  init_config();
24776
25401
  init_runScan();
24777
25402
  init_artifacts2();
@@ -24795,6 +25420,7 @@ var init_cli = __esm({
24795
25420
  init_stackRecommend();
24796
25421
  init_commands();
24797
25422
  init_runInit();
25423
+ init_initGithub();
24798
25424
  init_runDoctor();
24799
25425
  init_runValidateNpmPackage();
24800
25426
  init_runAudit();