@viberaven/cli 1.0.0 → 1.0.2

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
@@ -170,8 +170,8 @@ __export(cli_exports, {
170
170
  runScanCommand: () => runScanCommand
171
171
  });
172
172
  module.exports = __toCommonJS(cli_exports);
173
- var import_promises15 = require("node:fs/promises");
174
- var import_node_path21 = require("node:path");
173
+ var import_promises17 = require("node:fs/promises");
174
+ var import_node_path22 = require("node:path");
175
175
 
176
176
  // src/config.ts
177
177
  var import_node_os = require("node:os");
@@ -526,6 +526,7 @@ var READY = "READY";
526
526
  var LOGIN_REQUIRED = "LOGIN_REQUIRED";
527
527
  var UPGRADE_REQUIRED = "UPGRADE_REQUIRED";
528
528
  var MANUAL_ACTION_REQUIRED = "MANUAL_ACTION_REQUIRED";
529
+ var AGENT_ACTION = "AGENT_ACTION";
529
530
  var ERROR = "ERROR";
530
531
  function formatAgentStatus(label, message) {
531
532
  return `${label}: ${message}`;
@@ -640,7 +641,7 @@ async function openUrlInBrowser(url) {
640
641
  }
641
642
 
642
643
  // src/contracts/commands.ts
643
- var PUBLIC_PACKAGE = "@viberaven/cli";
644
+ var PUBLIC_PACKAGE = "viberaven";
644
645
  var PUBLIC_COMMAND = `npx -y ${PUBLIC_PACKAGE}`;
645
646
  var PUBLIC_AGENT_MODE_COMMAND = `${PUBLIC_COMMAND} --agent-mode`;
646
647
  var PUBLIC_VERIFY_COMMAND = `${PUBLIC_COMMAND} --verify`;
@@ -676,7 +677,11 @@ async function runDeviceLogin(apiBaseUrl) {
676
677
  await openUrlInBrowser(verificationUrl);
677
678
  console.log("Opened the VibeRaven approval page in your browser.");
678
679
  } catch (error) {
679
- console.warn(error instanceof Error ? error.message : String(error));
680
+ const detail = error instanceof Error ? error.message : String(error);
681
+ console.log(
682
+ "LOGIN_BROWSER_MANUAL: Automatic browser open failed. Open the LOGIN_URL_READY URL manually; the CLI is still waiting."
683
+ );
684
+ console.log(`LOGIN_BROWSER_DETAIL: ${detail}`);
680
685
  }
681
686
  console.log("Waiting for approval in the browser\u2026\n");
682
687
  console.log("LOGIN_WAITING: Complete approval in the browser, then VibeRaven will continue automatically.");
@@ -1078,8 +1083,8 @@ function isSecretFileName(fileName) {
1078
1083
  }
1079
1084
  return SECRET_PATTERNS.some((pattern) => pattern.test(fileName));
1080
1085
  }
1081
- function isLikelyBinary(buffer2) {
1082
- return buffer2.includes(0);
1086
+ function isLikelyBinary(buffer) {
1087
+ return buffer.includes(0);
1083
1088
  }
1084
1089
  function createGitignoreMatcher(gitignoreContent) {
1085
1090
  const rules = gitignoreContent.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && !line.startsWith("!")).map(parseGitignoreRule);
@@ -7693,7 +7698,7 @@ function createStationOrchestrator(deps) {
7693
7698
  if (error instanceof BackendHttpError && error.status === 402) {
7694
7699
  return {
7695
7700
  kind: "scan_limit_reached",
7696
- upgradeUrl: error.upgradeUrl ?? "https://viberice.com/account"
7701
+ upgradeUrl: error.upgradeUrl ?? "https://viberaven.dev/account"
7697
7702
  };
7698
7703
  }
7699
7704
  if (error instanceof BackendHttpError && error.status === 401) {
@@ -8780,81 +8785,169 @@ function generateAgentSummary(artifact) {
8780
8785
  `;
8781
8786
  }
8782
8787
 
8783
- // src/report/agentTasklist.ts
8784
- var SEVERITY_ORDER2 = {
8785
- critical: 0,
8786
- warning: 1,
8787
- info: 2
8788
- };
8789
- function sortGaps2(gaps) {
8790
- return [...gaps].sort(
8791
- (a, b3) => SEVERITY_ORDER2[a.severity] - SEVERITY_ORDER2[b3.severity] || a.title.localeCompare(b3.title)
8792
- );
8793
- }
8794
- function redactDetail(detail) {
8795
- return detail.replace(
8796
- /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
8797
- "<redacted>"
8798
- ).replace(
8799
- /\b([a-z][a-z0-9+.-]*:\/\/)([^:@\s/?#]+):([^@\s/?#]+)@([^\s)]+)/gi,
8800
- "$1<redacted>@$4"
8801
- ).replace(/\bAuthorization\s*:\s*([A-Za-z][A-Za-z0-9._-]*)\s+[^\s;,]+/gi, "Authorization: $1 <redacted>").replace(/\bghp_[A-Za-z0-9_]{20,}\b/g, "<redacted>").replace(/\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, "<redacted>").replace(/\bxox[baprs]-[A-Za-z0-9-]{20,}\b/g, "<redacted>").replace(/\bxapp-[A-Za-z0-9-]{20,}\b/g, "<redacted>").replace(
8802
- /\b([A-Za-z0-9_]*(?:ACCESS_TOKEN|AUTHORIZATION|API_KEY|SECRET|SECRET_KEY|SERVICE_ROLE_KEY|TOKEN|PASSWORD|PRIVATE_KEY|CREDENTIALS?)[A-Za-z0-9_]*)\s*=\s*["']?[^"'\s;,]+["']?/gi,
8803
- "$1=<redacted>"
8804
- ).replace(/\beyJ[A-Za-z0-9._-]*\b/g, "<redacted>").replace(/\[REDACTED(?:_[A-Z_]+)?\]/g, "<redacted>");
8788
+ // src/buildTaskList.ts
8789
+ var KNOWN_RECIPE_GAP_IDS = /* @__PURE__ */ new Set([
8790
+ // emptyCatch (original recipe)
8791
+ "empty-catch",
8792
+ "empty_catch",
8793
+ "emptyCatch",
8794
+ // W3 env-add recipes
8795
+ "auth_secret_missing",
8796
+ "node_env_not_set",
8797
+ "database_url_missing",
8798
+ // W3 file-create recipes
8799
+ "missing_error_boundary",
8800
+ "missing_health_route",
8801
+ "missing_loading_state",
8802
+ "missing_404_page",
8803
+ // W3 file-patch recipes
8804
+ "missing_csp_header",
8805
+ "missing_rate_limit"
8806
+ // NOTE: 'rls_disabled' is intentionally NOT here — it is provider-action only,
8807
+ // canAutoApply=false, and should be classified as 'provider-action' by buildTaskList.
8808
+ ]);
8809
+ function hasRecipe(gapId) {
8810
+ if (KNOWN_RECIPE_GAP_IDS.has(gapId)) return true;
8811
+ const lower = gapId.toLowerCase();
8812
+ return lower.includes("empty") && lower.includes("catch");
8813
+ }
8814
+ function gapToPlaybookProvider(gap) {
8815
+ const cat = gap.primaryMapCategory.toLowerCase();
8816
+ if (cat === "deployment") return "vercel";
8817
+ if (cat === "payments") return "stripe";
8818
+ if (cat === "auth") return "auth-supabase";
8819
+ if (cat === "database") return "supabase";
8820
+ const id = gap.id.toLowerCase();
8821
+ if (id.includes("vercel") || id.includes("deploy")) return "vercel";
8822
+ if (id.includes("stripe") || id.includes("payment")) return "stripe";
8823
+ if (id.includes("supabase") || id.includes("database") || id.includes("db")) return "supabase";
8824
+ if (id.includes("auth")) return "auth-supabase";
8825
+ return void 0;
8805
8826
  }
8806
- function headingFor(gaps) {
8807
- if (gaps.some((gap) => gap.severity === "critical")) {
8808
- return "# VibeRaven Production Gate - CRITICAL GAPS DETECTED";
8827
+ function hasPlaybook(gap) {
8828
+ try {
8829
+ const provider2 = gapToPlaybookProvider(gap);
8830
+ if (!provider2) return false;
8831
+ return PLAYBOOK_PROVIDERS.includes(provider2);
8832
+ } catch {
8833
+ return false;
8809
8834
  }
8810
- if (gaps.length > 0) {
8811
- return "# VibeRaven Production Gate - GAPS DETECTED";
8835
+ }
8836
+ function buildProviderAction(gap, provider2) {
8837
+ try {
8838
+ const playbook = loadPlaybookSync(provider2);
8839
+ const step = playbook.steps[0];
8840
+ if (!step) return void 0;
8841
+ return {
8842
+ provider: provider2,
8843
+ dashboardUrl: step.openUrl ?? `https://${provider2}.com`,
8844
+ // PlaybookStep uses `instruction` — map to exactStep
8845
+ exactStep: step.instruction,
8846
+ // No envKeyName/envKeyExample in current PlaybookStep schema — leave undefined
8847
+ envKeyName: void 0,
8848
+ envKeyExample: void 0,
8849
+ // Use step title as the done signal
8850
+ doneSignal: `${step.title} step completed`,
8851
+ verifyCommand: PUBLIC_VERIFY_COMMAND
8852
+ };
8853
+ } catch {
8854
+ return void 0;
8812
8855
  }
8813
- return "# VibeRaven Production Gate - NO REPO-CODE GAPS DETECTED";
8814
8856
  }
8815
- function generateAgentTasklist(artifact) {
8816
- const lines = [];
8817
- const gaps = sortGaps2(artifact.gaps);
8818
- const hasCritical = gaps.some((gap) => gap.severity === "critical");
8819
- lines.push(headingFor(gaps), "");
8820
- lines.push(`Workspace: \`${artifact.workspacePath}\``);
8821
- lines.push(`Scanned: ${artifact.scannedAt}`);
8822
- lines.push(`Production core: **${artifact.productionCorePercent}%**`);
8823
- lines.push("");
8824
- if (hasCritical) {
8825
- lines.push("PRODUCTION GATE NOT CLEAR", "");
8826
- } else if (gaps.length > 0) {
8827
- lines.push("Do not deploy until the listed production-readiness gaps are reviewed.", "");
8828
- } else {
8829
- lines.push("PRODUCTION GATE CLEAR FOR REPO-CODE GAPS");
8830
- lines.push(
8831
- "Provider dashboard checks still require dashboard or read-only MCP evidence before launch."
8832
- );
8833
- lines.push("");
8857
+ function unlockedKeys2(artifact) {
8858
+ const keys = artifact.usage?.unlockedMapCategoryKeys ?? FREE_TRIAL_UNLOCKED_MAP_CATEGORY_KEYS;
8859
+ return new Set(keys);
8860
+ }
8861
+ function buildTaskList(artifact) {
8862
+ const unlocked = unlockedKeys2(artifact);
8863
+ const sorted = sortGapsByPriority(artifact.gaps);
8864
+ return sorted.map((gap, index) => {
8865
+ const id = `TASK-${String(index + 1).padStart(3, "0")}`;
8866
+ const isLocked = !unlocked.has(gap.primaryMapCategory);
8867
+ let fixType;
8868
+ if (isLocked) {
8869
+ fixType = "upgrade-required";
8870
+ } else if (hasRecipe(gap.id)) {
8871
+ fixType = "repo-code";
8872
+ } else if (hasPlaybook(gap)) {
8873
+ fixType = "provider-action";
8874
+ } else {
8875
+ fixType = "manual-verify";
8876
+ }
8877
+ const base = {
8878
+ id,
8879
+ gapId: gap.id,
8880
+ severity: gap.severity,
8881
+ fixType,
8882
+ title: gap.title,
8883
+ verifyCommand: PUBLIC_VERIFY_COMMAND,
8884
+ requiresUserAction: fixType !== "repo-code"
8885
+ };
8886
+ if (fixType === "repo-code") {
8887
+ base.mcpTool = "viberaven_heal_apply";
8888
+ base.mcpArgs = { gap: gap.id, yes: true };
8889
+ base.requiresUserAction = false;
8890
+ if (gap.copyPrompt) {
8891
+ base.exactFix = gap.copyPrompt.trim();
8892
+ }
8893
+ } else if (fixType === "provider-action") {
8894
+ try {
8895
+ const provider2 = gapToPlaybookProvider(gap);
8896
+ if (provider2) {
8897
+ const pa = buildProviderAction(gap, provider2);
8898
+ if (pa) {
8899
+ base.providerAction = pa;
8900
+ base.action = pa.exactStep;
8901
+ }
8902
+ }
8903
+ } catch {
8904
+ base.fixType = "manual-verify";
8905
+ }
8906
+ base.requiresUserAction = true;
8907
+ }
8908
+ return base;
8909
+ });
8910
+ }
8911
+ function buildTaskListMarkdown(tasks) {
8912
+ if (tasks.length === 0) {
8913
+ return "# VibeRaven Agent Tasklist\n\n_No gaps found \u2014 production core looks solid._\n";
8834
8914
  }
8835
- lines.push("## TOP REPO-CODE GAPS", "");
8836
- if (gaps.length === 0) {
8837
- lines.push("_No repo-code gaps were returned by this scan._", "");
8838
- } else {
8839
- for (const gap of gaps.slice(0, 10)) {
8840
- const marker = gap.severity === "critical" ? "[!]" : "[ ]";
8841
- lines.push(`- ${marker} **${gap.title}**`);
8842
- lines.push(` - Gap ID: \`${gap.id}\``);
8843
- lines.push(` - Severity: ${gap.severity}`);
8844
- lines.push(` - Area: ${gap.primaryMapCategory}`);
8845
- lines.push(` - Detail: ${redactDetail(gap.detail)}`);
8846
- lines.push(` - Command: \`${promptGapCommand(gap.id)}\``);
8915
+ const lines = ["# VibeRaven Agent Tasklist", ""];
8916
+ for (const task of tasks) {
8917
+ const severityLabel = task.severity.toUpperCase();
8918
+ lines.push(`## ${task.id} \xB7 ${task.gapId} \xB7 ${severityLabel}`, "");
8919
+ lines.push(`**Fix type:** ${task.fixType} `);
8920
+ if (task.file) {
8921
+ lines.push(`**File:** \`${task.file}\` `);
8922
+ }
8923
+ if (task.action) {
8924
+ lines.push(`**Action:** ${task.action} `);
8925
+ }
8926
+ if (task.exactFix) {
8927
+ lines.push(`**Exact fix:** ${task.exactFix} `);
8928
+ } else {
8929
+ lines.push(`**Exact fix:** No automated recipe \u2014 see scanner hint. `);
8930
+ }
8931
+ lines.push(`**Verify:** \`${task.verifyCommand}\` `);
8932
+ if (task.mcpTool && task.mcpArgs) {
8933
+ lines.push(
8934
+ `**MCP:** \`${task.mcpTool} ${JSON.stringify(task.mcpArgs)}\` `
8935
+ );
8936
+ }
8937
+ lines.push(`**Requires user action:** ${task.requiresUserAction}`);
8938
+ if (task.providerAction) {
8939
+ const pa = task.providerAction;
8940
+ lines.push("");
8941
+ lines.push("**Provider action:**");
8942
+ lines.push(`- Provider: ${pa.provider}`);
8943
+ lines.push(`- Dashboard: ${pa.dashboardUrl}`);
8944
+ lines.push(`- Step: ${pa.exactStep}`);
8945
+ if (pa.envKeyName) lines.push(`- Env key: \`${pa.envKeyName}\``);
8946
+ if (pa.doneSignal) lines.push(`- Done when: ${pa.doneSignal}`);
8947
+ if (pa.mcpAlternative) lines.push(`- MCP alternative: \`${pa.mcpAlternative}\``);
8847
8948
  }
8848
8949
  lines.push("");
8849
8950
  }
8850
- lines.push("## NEXT STEPS FOR THE AGENT", "");
8851
- lines.push("1. Pick one repo-code gap from this tasklist and fix only that gap.");
8852
- lines.push("2. Use `npx -y @viberaven/cli prompt --gap <id>` for focused guidance.");
8853
- lines.push(`3. Run \`${PUBLIC_VERIFY_COMMAND}\` to rescan and refresh this tasklist.`);
8854
- lines.push(
8855
- "4. Do not claim provider dashboard checks are fixed by repo-code edits; verify those in the provider dashboard or through read-only provider MCP evidence."
8856
- );
8857
- lines.push("");
8858
8951
  return `${lines.join("\n")}
8859
8952
  `;
8860
8953
  }
@@ -8949,7 +9042,7 @@ function getStationHtml(nonce, cssUri, jsUri, options) {
8949
9042
  href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&amp;family=JetBrains+Mono:wght@400;600&amp;display=swap"
8950
9043
  />
8951
9044
  <link rel="stylesheet" href="${cssUri}">
8952
- <title>VibeRaven Station</title>
9045
+ <title>VibeRaven</title>
8953
9046
  </head>
8954
9047
  <body data-surface="${surface}">
8955
9048
  <div class="station-app">
@@ -9337,7 +9430,8 @@ async function writeScanArtifacts(options) {
9337
9430
  const summary = generateAgentSummary(safe);
9338
9431
  const playbook = generateLaunchPlaybook(safe);
9339
9432
  const html = generateReportHtml(safe);
9340
- const tasklist = generateAgentTasklist(safe);
9433
+ const tasks = buildTaskList(safe);
9434
+ const tasklist = buildTaskListMarkdown(tasks);
9341
9435
  const gateResult = `${JSON.stringify(generateGateResult(safe), null, 2)}
9342
9436
  `;
9343
9437
  const contextMap = `${JSON.stringify(generateContextMap(safe), null, 2)}
@@ -9567,6 +9661,21 @@ function printScanSummary(artifact, paths) {
9567
9661
  console.log(import_picocolors.default.dim(` ${paths.summaryPath}`));
9568
9662
  console.log(import_picocolors.default.dim(` ${paths.playbookPath}`));
9569
9663
  console.log("");
9664
+ const next = resolveNextAction(artifact);
9665
+ console.log(`Open report: ${paths.reportPath}`);
9666
+ console.log(`Next action: ${next.title}`);
9667
+ console.log(next.detail);
9668
+ if (next.command) {
9669
+ console.log(`Run: ${next.command}`);
9670
+ }
9671
+ if (next.openUrl) {
9672
+ console.log(`Open: ${next.openUrl}`);
9673
+ }
9674
+ if (next.upgradeUrl) {
9675
+ console.log(`Upgrade: ${next.upgradeUrl}`);
9676
+ }
9677
+ console.log(formatAgentStatus(AGENT_ACTION, "Read .viberaven/agent-tasklist.md, run the next command above, then rescan."));
9678
+ console.log("");
9570
9679
  if (artifact.usage) {
9571
9680
  const lanes = artifact.usage.unlockedMapCategoryKeys.length;
9572
9681
  console.log(
@@ -9686,11 +9795,11 @@ function validateSafeFixRelativePath(path) {
9686
9795
  if (segments.some((segment) => segment === ".git" || segment === "node_modules")) {
9687
9796
  return { ok: false, reason: "Safe fix path targets a blocked directory." };
9688
9797
  }
9689
- const basename4 = segments.at(-1)?.toLowerCase() ?? "";
9690
- if (basename4 !== ".env.example" && (basename4 === ".env" || basename4.startsWith(".env."))) {
9798
+ const basename3 = segments.at(-1)?.toLowerCase() ?? "";
9799
+ if (basename3 !== ".env.example" && (basename3 === ".env" || basename3.startsWith(".env."))) {
9691
9800
  return { ok: false, reason: "Safe fix path targets an environment secret file." };
9692
9801
  }
9693
- if (isSecretLikeFilename(basename4)) {
9802
+ if (isSecretLikeFilename(basename3)) {
9694
9803
  return { ok: false, reason: "Safe fix path targets a secret-like file." };
9695
9804
  }
9696
9805
  if (!isAllowedSafeFixPath(normalized)) {
@@ -11115,7 +11224,7 @@ function buildAgentFixPrompt(artifact, gap) {
11115
11224
  }
11116
11225
 
11117
11226
  // src/version.ts
11118
- var VERSION = "1.0.0";
11227
+ var VERSION = "1.0.2";
11119
11228
 
11120
11229
  // src/commands/guide.ts
11121
11230
  var import_picocolors3 = __toESM(require_picocolors());
@@ -11382,12 +11491,23 @@ var import_promises8 = require("node:fs/promises");
11382
11491
  var import_node_path11 = require("node:path");
11383
11492
 
11384
11493
  // src/commands/agentRulesBlock.ts
11385
- var VIBERAVEN_AGENT_RULES_START = "<!-- viberaven:agent-rules:start -->";
11386
- var VIBERAVEN_AGENT_RULES_END = "<!-- viberaven:agent-rules:end -->";
11494
+ var VIBERAVEN_BLOCK_START = "<!-- VIBERAVEN:START -->";
11495
+ var VIBERAVEN_BLOCK_END = "<!-- VIBERAVEN:END -->";
11496
+ var VIBERAVEN_LEGACY_BLOCK_START = "<!-- viberaven:agent-rules:start -->";
11497
+ var VIBERAVEN_LEGACY_BLOCK_END = "<!-- viberaven:agent-rules:end -->";
11387
11498
  var HEAL_PLAN_GAP_COMMAND = healPlanGapCommand("<id>");
11388
11499
  var PROMPT_GAP_COMMAND = promptGapCommand("<id>");
11389
11500
  var HEAL_APPLY_GAP_COMMAND = `${PUBLIC_COMMAND} --heal --apply --gap <id> --yes`;
11390
- var AGENT_RULES_BODY = `## VibeRaven Production-Readiness Gate
11501
+ var AGENT_RULES_PREAMBLE = `VibeRaven provides production readiness for AI-built apps.
11502
+
11503
+ Canonical command: \`${PUBLIC_AGENT_MODE_COMMAND}\`
11504
+
11505
+ Use VibeRaven before launch, deployment, real users, auth, billing, database, RLS, env vars, webhooks, monitoring, or tests.
11506
+
11507
+ Agent loop: run VibeRaven, read Mission Map (\`.viberaven/mission-map.md\`), fix one launch gap, re-run VibeRaven.`;
11508
+ var AGENT_RULES_BODY = `${AGENT_RULES_PREAMBLE}
11509
+
11510
+ ## VibeRaven Production-Readiness Gate
11391
11511
 
11392
11512
  - Before running build, compile, cleanup, deployment, vercel, or supabase commands, run \`${PUBLIC_AGENT_MODE_COMMAND}\` first from the project root.
11393
11513
  - Treat VibeRaven as the Agent Context + Production Gate for AI-built apps, especially apps using Vercel, Supabase, auth, database, payments, webhooks, monitoring, rate limits, or environment variables.
@@ -11402,19 +11522,68 @@ var AGENT_RULES_BODY = `## VibeRaven Production-Readiness Gate
11402
11522
  - Run \`${PUBLIC_STRICT_COMMAND}\` before deploy or CI pass.
11403
11523
  - Use \`${HEAL_PLAN_GAP_COMMAND}\` before any heal apply.
11404
11524
  - Only use \`${HEAL_APPLY_GAP_COMMAND}\` for supported low-risk repo-code recipes.
11405
- - Preview local rule installation with \`${PUBLIC_COMMAND} init --dry-run\`.
11525
+ - Preview local rule installation with \`${PUBLIC_INIT_ALL_COMMAND} --dry-run\`.
11406
11526
  - Cleanup is non-destructive: only run \`${PUBLIC_CLEAN_PLAN_COMMAND}\` to create a reviewable cleanup plan.
11407
11527
  - 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.
11408
11528
  - Do not claim provider dashboard work is fixed by repo-code edits.
11409
- - 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. Keep the terminal process alive while the user approves.`;
11529
+ - 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. Keep the terminal process alive while the user approves.
11530
+
11531
+ ## VibeRaven Production Copilot Loop
11532
+
11533
+ After every scan, read \`.viberaven/agent-tasklist.md\` for the prioritized task list.
11534
+
11535
+ The loop is:
11536
+
11537
+ 1. Read \`VIBERAVEN_NEXT_ACTION\` from scan output for \`batchSize\`, \`batchApplied\`, \`scanNow\`, and \`stalled\`.
11538
+ 2. If \`stalled: true\`, stop calling verify \u2014 address provider-action gaps or report to the user.
11539
+ 3. If \`scanNow: true\`, call \`viberaven_verify\` (or run verify command) before applying more heals.
11540
+ 4. For each repo-code task where \`requiresUserAction: false\` (up to \`batchSize\` per batch):
11541
+ - Call MCP: \`viberaven_heal_apply { gap: "<gapId>", yes: true }\`
11542
+ - Or run heal apply command
11543
+ 5. After a successful heal, VibeRaven increments \`.viberaven/loop-state.json\`. After a full batch of heals, call verify once - do NOT verify after every single heal.
11544
+ 6. If verify prints \`SCAN_DEFERRED\`, do not retry verify. Apply the next listed repo-code heal. Only use \`--force-scan\` when the user explicitly approves spending a scan early.
11545
+ 7. Repeat until \`gate.status === 'clear'\` in \`.viberaven/gate-result.json\`.
11546
+ 8. If \`requiresUserAction: true\`:
11547
+ - Read \`VIBERAVEN_PROVIDER_ACTION\` from terminal output.
11548
+ - Present \`dashboardUrl\` and \`exactStep\` to the user.
11549
+ - Wait for user confirmation, then verify.
11550
+ 9. If \`fixType === 'upgrade-required'\`, provide upgradeUrl and move on.
11551
+
11552
+ Do not stop at "scan complete." The loop is not done until \`gate.status === 'clear'\`.`;
11553
+ var AGENT_CONTEXT_BODY = `${AGENT_RULES_PREAMBLE}
11554
+
11555
+ After \`--agent-mode\`, read these artifacts in order:
11556
+
11557
+ 1. \`.viberaven/mission-map.md\`
11558
+ 2. \`.viberaven/agent-tasklist.md\`
11559
+ 3. \`.viberaven/gate-result.json\`
11560
+ 4. \`.viberaven/context-map.json\``;
11561
+ var MISSION_MAP_BODY = `${AGENT_RULES_PREAMBLE}
11562
+
11563
+ ## Mission Map loop
11564
+
11565
+ 1. Run \`${PUBLIC_AGENT_MODE_COMMAND}\` from the project root.
11566
+ 2. Read \`.viberaven/agent-tasklist.md\` and \`.viberaven/gate-result.json\`.
11567
+ 3. Fix one launch gap.
11568
+ 4. Re-run VibeRaven until \`gate.status === 'clear'\`.`;
11569
+ var BLOCK_MARKER_PAIRS = [
11570
+ [VIBERAVEN_BLOCK_START, VIBERAVEN_BLOCK_END],
11571
+ [VIBERAVEN_LEGACY_BLOCK_START, VIBERAVEN_LEGACY_BLOCK_END]
11572
+ ];
11410
11573
  function buildAgentRulesBlock() {
11411
- return [VIBERAVEN_AGENT_RULES_START, AGENT_RULES_BODY, VIBERAVEN_AGENT_RULES_END].join("\n");
11574
+ return wrapViberavenBlock(AGENT_RULES_BODY);
11575
+ }
11576
+ function buildAgentContextBlock() {
11577
+ return wrapViberavenBlock(AGENT_CONTEXT_BODY);
11578
+ }
11579
+ function buildMissionMapBlock() {
11580
+ return wrapViberavenBlock(MISSION_MAP_BODY);
11581
+ }
11582
+ function wrapViberavenBlock(body) {
11583
+ return [VIBERAVEN_BLOCK_START, body, VIBERAVEN_BLOCK_END].join("\n");
11412
11584
  }
11413
11585
  function injectAgentRulesBlock(existingContent, replacementBlock = buildAgentRulesBlock()) {
11414
- const boundedBlockPattern = new RegExp(
11415
- `${escapeRegExp3(VIBERAVEN_AGENT_RULES_START)}[\\s\\S]*?${escapeRegExp3(VIBERAVEN_AGENT_RULES_END)}`
11416
- );
11417
- const existingMatch = boundedBlockPattern.exec(existingContent);
11586
+ const existingMatch = findBoundedBlock(existingContent);
11418
11587
  if (existingMatch) {
11419
11588
  const content = replaceExistingAgentRulesBlock({
11420
11589
  existingContent,
@@ -11432,8 +11601,20 @@ function injectAgentRulesBlock(existingContent, replacementBlock = buildAgentRul
11432
11601
  changed: true
11433
11602
  };
11434
11603
  }
11604
+ function findBoundedBlock(content) {
11605
+ for (const [start, end] of BLOCK_MARKER_PAIRS) {
11606
+ const boundedBlockPattern = new RegExp(
11607
+ `${escapeRegExp3(start)}[\\s\\S]*?${escapeRegExp3(end)}`
11608
+ );
11609
+ const match = boundedBlockPattern.exec(content);
11610
+ if (match) {
11611
+ return match;
11612
+ }
11613
+ }
11614
+ return null;
11615
+ }
11435
11616
  function replaceExistingAgentRulesBlock(input) {
11436
- const replacementMarkerIndex = input.replacementBlock.indexOf(VIBERAVEN_AGENT_RULES_START);
11617
+ const replacementMarkerIndex = findBlockStartIndex(input.replacementBlock);
11437
11618
  const existingStart = input.existingMatch.index;
11438
11619
  const existingEnd = existingStart + input.existingMatch[0].length;
11439
11620
  if (replacementMarkerIndex === -1) {
@@ -11451,6 +11632,10 @@ function replaceExistingAgentRulesBlock(input) {
11451
11632
  existingEnd
11452
11633
  )}`;
11453
11634
  }
11635
+ function findBlockStartIndex(block) {
11636
+ const indices = BLOCK_MARKER_PAIRS.map(([start]) => block.indexOf(start)).filter((index) => index !== -1);
11637
+ return indices.length > 0 ? Math.min(...indices) : -1;
11638
+ }
11454
11639
  function findGeneratedPrefixStart(input) {
11455
11640
  if (!input.replacementPrefix) {
11456
11641
  return input.fallbackStart;
@@ -11495,6 +11680,8 @@ var AGENT_RULE_TARGETS = {
11495
11680
  cursorLegacy: { file: ".cursorrules", aliases: ["cursor-legacy"] },
11496
11681
  copilot: { file: ".github/copilot-instructions.md", aliases: ["github-copilot"] },
11497
11682
  gemini: { file: "GEMINI.md" },
11683
+ agentContext: { file: ".viberaven/agent-context.md", aliases: ["agent-context"] },
11684
+ missionMap: { file: ".viberaven/mission-map.md", aliases: ["mission-map"] },
11498
11685
  devin: { file: ".devin/rules/viberaven.md" },
11499
11686
  windsurf: { file: ".windsurf/rules/viberaven.md" },
11500
11687
  cline: { file: ".clinerules/viberaven.md" },
@@ -11502,13 +11689,17 @@ var AGENT_RULE_TARGETS = {
11502
11689
  junie: { file: ".junie/guidelines.md" },
11503
11690
  zed: { file: ".rules" }
11504
11691
  };
11505
- var ALL_AGENT_RULE_TARGETS = [
11692
+ var CORE_AGENT_INJECTION_TARGETS = [
11506
11693
  "codex",
11507
11694
  "claude",
11695
+ "gemini",
11508
11696
  "cursor",
11509
- "cursorLegacy",
11510
11697
  "copilot",
11511
- "gemini",
11698
+ "agentContext",
11699
+ "missionMap"
11700
+ ];
11701
+ var EXTENDED_AGENT_RULE_TARGETS = [
11702
+ "cursorLegacy",
11512
11703
  "devin",
11513
11704
  "windsurf",
11514
11705
  "cline",
@@ -11516,12 +11707,16 @@ var ALL_AGENT_RULE_TARGETS = [
11516
11707
  "junie",
11517
11708
  "zed"
11518
11709
  ];
11710
+ var ALL_AGENT_RULE_TARGETS = [
11711
+ ...CORE_AGENT_INJECTION_TARGETS,
11712
+ ...EXTENDED_AGENT_RULE_TARGETS
11713
+ ];
11519
11714
  var AGENT_RULE_TARGET_ALIAS_ENTRIES = ALL_AGENT_RULE_TARGETS.flatMap((target) => [
11520
11715
  [target.toLowerCase(), target],
11521
11716
  ...(AGENT_RULE_TARGETS[target].aliases ?? []).map((alias) => [alias, target])
11522
11717
  ]);
11523
11718
  var AGENT_RULE_TARGET_ALIASES = new Map(AGENT_RULE_TARGET_ALIAS_ENTRIES);
11524
- var VALID_AGENT_RULES_TARGET_TEXT = "all, codex, claude, cursor, cursor-legacy, copilot, github-copilot, gemini, devin, windsurf, cline, roo, junie, zed";
11719
+ var VALID_AGENT_RULES_TARGET_TEXT = "all, codex, claude, cursor, cursor-legacy, copilot, github-copilot, gemini, agent-context, mission-map, devin, windsurf, cline, roo, junie, zed";
11525
11720
  function renderAgentRulesForTarget(target) {
11526
11721
  if (target === "claude") {
11527
11722
  return ["@AGENTS.md", "", buildAgentRulesBlock()].join("\n");
@@ -11537,6 +11732,12 @@ function renderAgentRulesForTarget(target) {
11537
11732
  buildAgentRulesBlock()
11538
11733
  ].join("\n");
11539
11734
  }
11735
+ if (target === "agentContext") {
11736
+ return ["# VibeRaven Agent Context", "", buildAgentContextBlock()].join("\n");
11737
+ }
11738
+ if (target === "missionMap") {
11739
+ return ["# VibeRaven Mission Map", "", buildMissionMapBlock()].join("\n");
11740
+ }
11540
11741
  return buildAgentRulesBlock();
11541
11742
  }
11542
11743
  function validAgentRulesTargetText() {
@@ -11544,11 +11745,11 @@ function validAgentRulesTargetText() {
11544
11745
  }
11545
11746
  function getAgentRulesTargets(value) {
11546
11747
  if (value === void 0 || value.trim() === "" || value.trim().toLowerCase() === "all") {
11547
- return [...ALL_AGENT_RULE_TARGETS];
11748
+ return [...CORE_AGENT_INJECTION_TARGETS];
11548
11749
  }
11549
11750
  const requested = value.split(",").map((target) => target.trim().toLowerCase()).filter(Boolean);
11550
11751
  if (requested.length === 0 || requested.includes("all")) {
11551
- return [...ALL_AGENT_RULE_TARGETS];
11752
+ return [...CORE_AGENT_INJECTION_TARGETS];
11552
11753
  }
11553
11754
  const resolved = requested.map((target) => {
11554
11755
  const canonicalTarget = AGENT_RULE_TARGET_ALIASES.get(target);
@@ -11563,7 +11764,7 @@ function getAgentRulesTargets(value) {
11563
11764
 
11564
11765
  // src/commands/initRules.ts
11565
11766
  async function initAgentRules(options) {
11566
- const targets = options.targets ?? [...ALL_AGENT_RULE_TARGETS];
11767
+ const targets = options.targets ?? [...CORE_AGENT_INJECTION_TARGETS];
11567
11768
  const results = [];
11568
11769
  for (const target of targets) {
11569
11770
  const file = AGENT_RULE_TARGETS[target].file;
@@ -11588,6 +11789,37 @@ function renderAgentRulesDryRun(targets) {
11588
11789
  ]);
11589
11790
  return [`VibeRaven agent rules dry run`, "", `Target files:`, files, "", ...previews].join("\n");
11590
11791
  }
11792
+ function formatAgentRulesInitSummary(results) {
11793
+ const created = results.filter((result) => result.action === "created");
11794
+ const updated = results.filter((result) => result.action === "updated");
11795
+ const skipped = results.filter((result) => result.action === "unchanged");
11796
+ const lines = ["VibeRaven agent injection summary", ""];
11797
+ if (created.length > 0) {
11798
+ lines.push("Created:");
11799
+ for (const result of created) {
11800
+ lines.push(` + ${result.file}`);
11801
+ }
11802
+ lines.push("");
11803
+ }
11804
+ if (updated.length > 0) {
11805
+ lines.push("Updated:");
11806
+ for (const result of updated) {
11807
+ lines.push(` ~ ${result.file}`);
11808
+ }
11809
+ lines.push("");
11810
+ }
11811
+ if (skipped.length > 0) {
11812
+ lines.push("Skipped (unchanged):");
11813
+ for (const result of skipped) {
11814
+ lines.push(` = ${result.file}`);
11815
+ }
11816
+ lines.push("");
11817
+ }
11818
+ lines.push(
11819
+ `Done: ${created.length} created, ${updated.length} updated, ${skipped.length} skipped.`
11820
+ );
11821
+ return lines.join("\n");
11822
+ }
11591
11823
  async function readExistingFile(path) {
11592
11824
  try {
11593
11825
  return { exists: true, content: await (0, import_promises8.readFile)(path, "utf-8") };
@@ -11810,7 +12042,7 @@ async function handleAuth() {
11810
12042
  await runDeviceLogin(apiBaseUrl);
11811
12043
  }
11812
12044
  async function handleAgentRules(cwd) {
11813
- const results = await initAgentRules({ cwd, targets: ["codex", "claude", "cursor"] });
12045
+ const results = await initAgentRules({ cwd });
11814
12046
  for (const result of results) {
11815
12047
  const color = result.action === "created" ? import_picocolors4.default.green : result.action === "updated" ? import_picocolors4.default.yellow : import_picocolors4.default.dim;
11816
12048
  M2.message(color(`${result.action.toUpperCase()}: ${result.file}`));
@@ -11977,8 +12209,9 @@ async function runCondenseCommand(options) {
11977
12209
  }
11978
12210
 
11979
12211
  // src/heal/apply.ts
11980
- var import_promises10 = require("node:fs/promises");
11981
- var import_node_path15 = require("node:path");
12212
+ var import_promises11 = require("node:fs/promises");
12213
+ var import_node_fs8 = require("node:fs");
12214
+ var import_node_path16 = require("node:path");
11982
12215
 
11983
12216
  // src/heal/pathSafety.ts
11984
12217
  var import_node_path14 = require("node:path");
@@ -12008,617 +12241,845 @@ function applyEmptyCatchRecipe(source) {
12008
12241
  return { changed: output !== source, output };
12009
12242
  }
12010
12243
 
12011
- // src/heal/apply.ts
12012
- function healId() {
12013
- return `heal_${(/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14)}`;
12244
+ // src/heal/recipes/index.ts
12245
+ var import_node_fs7 = require("node:fs");
12246
+ var import_promises10 = require("node:fs/promises");
12247
+ var import_node_path15 = require("node:path");
12248
+
12249
+ // src/heal/recipes/envAuthSecret.ts
12250
+ function applyAuthSecretRecipe(source) {
12251
+ if (/^\s*NEXTAUTH_SECRET\s*=/m.test(source)) {
12252
+ return { changed: false, output: source, canAutoApply: true };
12253
+ }
12254
+ const line = "NEXTAUTH_SECRET=<generate with: openssl rand -base64 32>";
12255
+ const output = source.trimEnd() ? `${source.trimEnd()}
12256
+ ${line}
12257
+ ` : `${line}
12258
+ `;
12259
+ return { changed: true, output, canAutoApply: true };
12014
12260
  }
12015
- async function applyHeal(options) {
12016
- const id = healId();
12017
- if (!options.yes) {
12018
- return {
12019
- $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
12020
- schemaVersion: "v1",
12021
- runId: "vr_heal_apply",
12022
- healId: id,
12023
- mode: "apply",
12024
- status: "refused_dangerous",
12025
- gapId: options.gapId,
12026
- target: options.target,
12027
- changedFiles: [],
12028
- artifacts: {},
12029
- rollback: { available: false, instructions: "Re-run with --yes to allow guarded repo-code edits." }
12030
- };
12261
+
12262
+ // src/heal/recipes/envNodeEnv.ts
12263
+ function applyNodeEnvRecipe(source) {
12264
+ if (/^\s*NODE_ENV\s*=/m.test(source)) {
12265
+ return { changed: false, output: source, canAutoApply: true };
12031
12266
  }
12032
- if (!options.target) {
12267
+ const line = "NODE_ENV=production";
12268
+ const output = source.trimEnd() ? `${source.trimEnd()}
12269
+ ${line}
12270
+ ` : `${line}
12271
+ `;
12272
+ return { changed: true, output, canAutoApply: true };
12273
+ }
12274
+
12275
+ // src/heal/recipes/envDatabaseUrl.ts
12276
+ function applyDatabaseUrlRecipe(source) {
12277
+ if (/^\s*DATABASE_URL\s*=/m.test(source)) {
12278
+ return { changed: false, output: source, canAutoApply: true };
12279
+ }
12280
+ const lines = [
12281
+ "# Get this from: https://supabase.com/dashboard/project/<ref>/settings/database",
12282
+ "DATABASE_URL=<your-supabase-postgres-url>"
12283
+ ].join("\n");
12284
+ const output = source.trimEnd() ? `${source.trimEnd()}
12285
+ ${lines}
12286
+ ` : `${lines}
12287
+ `;
12288
+ return { changed: true, output, canAutoApply: true };
12289
+ }
12290
+
12291
+ // src/heal/recipes/nextjsErrorBoundary.ts
12292
+ var ERROR_BOUNDARY_CONTENT = `'use client';
12293
+
12294
+ import { useEffect } from 'react';
12295
+
12296
+ interface ErrorBoundaryProps {
12297
+ error: Error & { digest?: string };
12298
+ reset: () => void;
12299
+ }
12300
+
12301
+ export default function ErrorBoundary({ error, reset }: ErrorBoundaryProps) {
12302
+ useEffect(() => {
12303
+ // Log to an error reporting service
12304
+ console.error('[VibeRaven] Unhandled error:', error);
12305
+ }, [error]);
12306
+
12307
+ return (
12308
+ <main className="flex min-h-screen flex-col items-center justify-center p-4">
12309
+ <h1 className="text-2xl font-bold mb-4">Something went wrong</h1>
12310
+ {error.digest && (
12311
+ <p className="text-sm text-gray-500 mb-4">Error ID: {error.digest}</p>
12312
+ )}
12313
+ <button
12314
+ onClick={reset}
12315
+ className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition"
12316
+ >
12317
+ Try again
12318
+ </button>
12319
+ </main>
12320
+ );
12321
+ }
12322
+ `;
12323
+ function applyErrorBoundaryRecipe(source) {
12324
+ if (source.trim().length > 0) {
12033
12325
  return {
12034
- $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
12035
- schemaVersion: "v1",
12036
- runId: "vr_heal_apply",
12037
- healId: id,
12038
- mode: "apply",
12039
- status: "refused_unsupported",
12040
- gapId: options.gapId,
12041
- changedFiles: [],
12042
- artifacts: {},
12043
- rollback: { available: false, instructions: "Apply requires a supported --target file in 1.0." }
12326
+ changed: false,
12327
+ output: source,
12328
+ targetFile: "app/error.tsx",
12329
+ canAutoApply: true
12044
12330
  };
12045
12331
  }
12046
- const absolute = assertSafeHealTarget(options.cwd, options.target);
12047
- const before = await (0, import_promises10.readFile)(absolute, "utf8");
12048
- const recipe = applyEmptyCatchRecipe(before);
12049
- if (!recipe.changed) {
12332
+ return {
12333
+ changed: true,
12334
+ output: ERROR_BOUNDARY_CONTENT,
12335
+ targetFile: "app/error.tsx",
12336
+ canAutoApply: true
12337
+ };
12338
+ }
12339
+
12340
+ // src/heal/recipes/nextjsHealthRoute.ts
12341
+ var HEALTH_ROUTE_CONTENT = `import { NextResponse } from 'next/server';
12342
+
12343
+ /**
12344
+ * GET /api/health
12345
+ * Simple health-check endpoint \u2014 returns { status: 'ok', ts: <timestamp> }.
12346
+ * Created by VibeRaven heal recipe (missing_health_route).
12347
+ */
12348
+ export function GET(): NextResponse {
12349
+ return NextResponse.json({ status: 'ok', ts: Date.now() });
12350
+ }
12351
+ `;
12352
+ function applyHealthRouteRecipe(source) {
12353
+ if (source.trim().length > 0) {
12050
12354
  return {
12051
- $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
12052
- schemaVersion: "v1",
12053
- runId: "vr_heal_apply",
12054
- healId: id,
12055
- mode: "apply",
12056
- status: "refused_unsupported",
12057
- target: options.target,
12058
- gapId: options.gapId,
12059
- changedFiles: [],
12060
- artifacts: {},
12061
- rollback: { available: false, instructions: "No supported heal recipe matched this file." }
12355
+ changed: false,
12356
+ output: source,
12357
+ targetFile: "app/api/health/route.ts",
12358
+ canAutoApply: true
12062
12359
  };
12063
12360
  }
12064
- const healDir = (0, import_node_path15.join)(options.cwd, ".viberaven", "heal", id);
12065
- await (0, import_promises10.mkdir)((0, import_node_path15.join)(healDir, "before"), { recursive: true });
12066
- await (0, import_promises10.writeFile)((0, import_node_path15.join)(healDir, "before", "target.txt"), before, "utf8");
12067
- await (0, import_promises10.writeFile)(absolute, recipe.output, "utf8");
12068
- const patch = [
12069
- `--- ${options.target}`,
12070
- `+++ ${options.target}`,
12071
- "@@ VibeRaven guarded heal @@",
12072
- before,
12073
- "--- after ---",
12074
- recipe.output,
12075
- ""
12076
- ].join("\n");
12077
- await (0, import_promises10.writeFile)((0, import_node_path15.join)(healDir, "patch.diff"), patch, "utf8");
12078
- const result = {
12079
- $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
12080
- schemaVersion: "v1",
12081
- runId: "vr_heal_apply",
12082
- healId: id,
12083
- mode: "apply",
12084
- status: "applied_verify_not_run",
12085
- gapId: options.gapId,
12086
- recipe: "empty-catch-safe-response",
12087
- target: options.target,
12088
- changedFiles: [(0, import_node_path15.relative)(options.cwd, absolute).replace(/\\/g, "/")],
12089
- artifacts: {
12090
- patch: `.viberaven/heal/${id}/patch.diff`,
12091
- result: `.viberaven/heal/${id}/result.json`
12092
- },
12093
- rollback: {
12094
- available: true,
12095
- instructions: "Restore .viberaven/heal/<healId>/before/target.txt to the target file or apply the reverse patch."
12096
- }
12361
+ return {
12362
+ changed: true,
12363
+ output: HEALTH_ROUTE_CONTENT,
12364
+ targetFile: "app/api/health/route.ts",
12365
+ canAutoApply: true
12097
12366
  };
12098
- await (0, import_promises10.writeFile)((0, import_node_path15.join)(healDir, "result.json"), `${JSON.stringify(result, null, 2)}
12099
- `, "utf8");
12100
- return result;
12101
12367
  }
12102
12368
 
12103
- // src/heal/plan.ts
12104
- var import_promises11 = require("node:fs/promises");
12105
- var import_node_path16 = require("node:path");
12106
- function healId2() {
12107
- return `heal_${(/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14)}`;
12369
+ // src/heal/recipes/nextjsLoadingState.ts
12370
+ var LOADING_CONTENT = `/**
12371
+ * Next.js App Router loading skeleton.
12372
+ * Created by VibeRaven heal recipe (missing_loading_state).
12373
+ */
12374
+ export default function Loading() {
12375
+ return (
12376
+ <main className="flex min-h-screen flex-col items-center justify-center p-4">
12377
+ <div className="animate-pulse space-y-4 w-full max-w-md">
12378
+ <div className="h-8 bg-gray-200 rounded w-3/4" />
12379
+ <div className="h-4 bg-gray-200 rounded w-full" />
12380
+ <div className="h-4 bg-gray-200 rounded w-5/6" />
12381
+ <div className="h-4 bg-gray-200 rounded w-4/6" />
12382
+ <div className="h-10 bg-gray-200 rounded w-1/3" />
12383
+ </div>
12384
+ </main>
12385
+ );
12108
12386
  }
12109
- async function writeHealPlan(options) {
12110
- const dir = (0, import_node_path16.join)(options.cwd, ".viberaven");
12111
- await (0, import_promises11.mkdir)(dir, { recursive: true });
12112
- const id = healId2();
12113
- const target = options.target ?? `gap:${options.gapId}`;
12114
- const markdown = [
12115
- "# VibeRaven Heal Plan",
12116
- "",
12117
- `Target: \`${target}\``,
12118
- `Mode: \`${options.mode}\``,
12119
- "",
12120
- "This plan is non-destructive. It does not edit source files.",
12121
- "",
12122
- `Verify after manual fix: \`${PUBLIC_VERIFY_COMMAND}\``,
12123
- ""
12124
- ].join("\n");
12125
- const result = {
12126
- $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
12127
- schemaVersion: "v1",
12128
- runId: "vr_heal_plan",
12129
- healId: id,
12130
- mode: "plan",
12131
- status: "planned",
12132
- gapId: options.gapId,
12133
- target: options.target,
12134
- changedFiles: [],
12135
- artifacts: { plan: ".viberaven/heal-plan.md" },
12136
- rollback: { available: false, instructions: "No source files were changed." }
12387
+ `;
12388
+ function applyLoadingStateRecipe(source) {
12389
+ if (source.trim().length > 0) {
12390
+ return {
12391
+ changed: false,
12392
+ output: source,
12393
+ targetFile: "app/loading.tsx",
12394
+ canAutoApply: true
12395
+ };
12396
+ }
12397
+ return {
12398
+ changed: true,
12399
+ output: LOADING_CONTENT,
12400
+ targetFile: "app/loading.tsx",
12401
+ canAutoApply: true
12137
12402
  };
12138
- await (0, import_promises11.writeFile)((0, import_node_path16.join)(dir, "heal-plan.md"), markdown, "utf8");
12139
- await (0, import_promises11.writeFile)((0, import_node_path16.join)(dir, "heal-plan.json"), `${JSON.stringify(result, null, 2)}
12140
- `, "utf8");
12141
- return result;
12142
12403
  }
12143
12404
 
12144
- // src/heal/prompt.ts
12145
- var import_promises12 = require("node:fs/promises");
12146
- var import_node_path17 = require("node:path");
12147
- function healId3() {
12148
- return `heal_${(/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14)}`;
12149
- }
12150
- async function writeHealPrompt(options) {
12151
- const dir = (0, import_node_path17.join)(options.cwd, ".viberaven");
12152
- await (0, import_promises12.mkdir)(dir, { recursive: true });
12153
- const id = healId3();
12154
- const target = options.target ?? `gap:${options.gapId}`;
12155
- const prompt = [
12156
- "# VibeRaven Heal Prompt",
12157
- "",
12158
- `Fix only this target: \`${target}\`.`,
12159
- "",
12160
- "- Do not request secrets.",
12161
- "- Do not edit provider dashboards.",
12162
- "- Do not change migrations, auth authorization logic, payment correctness, or webhook signature logic unless a VibeRaven recipe explicitly supports it.",
12163
- `- After the fix, run \`${PUBLIC_VERIFY_COMMAND}\`.`,
12164
- ""
12165
- ].join("\n");
12166
- const result = {
12167
- $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
12168
- schemaVersion: "v1",
12169
- runId: "vr_heal_prompt",
12170
- healId: id,
12171
- mode: "prompt",
12172
- status: "prompt_written",
12173
- gapId: options.gapId,
12174
- target: options.target,
12175
- changedFiles: [],
12176
- artifacts: { prompt: ".viberaven/heal-prompt.md" },
12177
- rollback: { available: false, instructions: "No source files were changed." }
12178
- };
12179
- await (0, import_promises12.writeFile)((0, import_node_path17.join)(dir, "heal-prompt.md"), prompt, "utf8");
12180
- return result;
12405
+ // src/heal/recipes/nextjsNotFound.ts
12406
+ var NOT_FOUND_CONTENT = `import Link from 'next/link';
12407
+
12408
+ /**
12409
+ * Next.js App Router 404 not-found page.
12410
+ * Created by VibeRaven heal recipe (missing_404_page).
12411
+ */
12412
+ export default function NotFound() {
12413
+ return (
12414
+ <main className="flex min-h-screen flex-col items-center justify-center p-4">
12415
+ <h1 className="text-6xl font-bold text-gray-300 mb-4">404</h1>
12416
+ <h2 className="text-2xl font-semibold mb-2">Page Not Found</h2>
12417
+ <p className="text-gray-500 mb-8">
12418
+ The page you are looking for does not exist or has been moved.
12419
+ </p>
12420
+ <Link
12421
+ href="/"
12422
+ className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition"
12423
+ >
12424
+ Go home
12425
+ </Link>
12426
+ </main>
12427
+ );
12181
12428
  }
12182
-
12183
- // src/commands/heal.ts
12184
- async function runHealCommand(options) {
12185
- if (!options.target && !options.gapId) {
12186
- throw new Error("Heal requires --target <file> or --gap <id>.");
12187
- }
12188
- if (options.target) {
12189
- assertSafeHealTarget(options.cwd, options.target);
12429
+ `;
12430
+ function applyNotFoundRecipe(source) {
12431
+ if (source.trim().length > 0) {
12432
+ return {
12433
+ changed: false,
12434
+ output: source,
12435
+ targetFile: "app/not-found.tsx",
12436
+ canAutoApply: true
12437
+ };
12190
12438
  }
12191
- if (options.mode === "plan") return writeHealPlan(options);
12192
- if (options.mode === "prompt") return writeHealPrompt(options);
12193
- return applyHeal(options);
12439
+ return {
12440
+ changed: true,
12441
+ output: NOT_FOUND_CONTENT,
12442
+ targetFile: "app/not-found.tsx",
12443
+ canAutoApply: true
12444
+ };
12194
12445
  }
12195
12446
 
12196
- // src/commands/cleanPlan.ts
12197
- var import_promises13 = require("node:fs/promises");
12198
- var import_node_path18 = require("node:path");
12199
- var MAX_WALK_DEPTH = 5;
12200
- var LARGE_LOG_BYTES = 8192;
12201
- var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([".git", "node_modules"]);
12202
- function buildContextCleanupPlan(input) {
12203
- const items = [];
12204
- const seen = /* @__PURE__ */ new Set();
12205
- for (const file of input.files) {
12206
- const path = normalizePath5((0, import_node_path18.relative)(input.projectRoot, file.path) || file.path);
12207
- const fullPath = normalizePath5(file.path);
12208
- const category = categorizeCleanupFile(path, fullPath, file);
12209
- if (!category || seen.has(path)) {
12210
- continue;
12211
- }
12212
- seen.add(path);
12213
- items.push({
12214
- path,
12215
- category,
12216
- action: "review-ignore-or-exclude-manually",
12217
- reason: reasonForCategory(category)
12218
- });
12219
- }
12447
+ // src/heal/recipes/supabaseRls.ts
12448
+ function applyRlsRecipe(_source) {
12220
12449
  return {
12221
- title: "VibeRaven context cleanup plan",
12222
- warning: "VibeRaven only writes this review plan. Review each item manually before changing ignore rules or excluding artifacts from agent context.",
12223
- items
12450
+ changed: false,
12451
+ output: _source,
12452
+ canAutoApply: false,
12453
+ reason: "dashboard-only",
12454
+ providerAction: {
12455
+ provider: "supabase",
12456
+ dashboardUrl: "https://supabase.com/dashboard/project/{{PROJECT_REF}}/auth/policies",
12457
+ exactStep: 'Enable Row Level Security (RLS) on each table under the "Table Editor" or "Auth > Policies" section.',
12458
+ doneSignal: "RLS toggle is green for all public tables",
12459
+ verifyCommand: "npx -y viberaven audit --vercel-supabase --json"
12460
+ }
12224
12461
  };
12225
12462
  }
12226
- async function collectCleanupFiles(projectRoot) {
12227
- const files = [];
12228
- await walkCleanupFiles(projectRoot, projectRoot, files, 0);
12229
- return files;
12230
- }
12231
- async function writeCleanupPlan(projectRoot, plan) {
12232
- const outputDir = (0, import_node_path18.join)(projectRoot, ".viberaven");
12233
- const outputPath = (0, import_node_path18.join)(outputDir, "context-cleanup.md");
12234
- await (0, import_promises13.mkdir)(outputDir, { recursive: true });
12235
- await (0, import_promises13.writeFile)(outputPath, renderCleanupPlan(plan), "utf8");
12236
- return outputPath;
12237
- }
12238
- function categorizeCleanupFile(relativePath, fullPath, file) {
12239
- if (relativePath.startsWith(".viberaven/") || fullPath.includes("/.viberaven/")) {
12240
- return "generated-artifact";
12463
+
12464
+ // src/heal/recipes/nextjsCspHeader.ts
12465
+ var CSP_HEADER_VALUE = [
12466
+ "default-src 'self'",
12467
+ "script-src 'self' 'unsafe-inline' 'unsafe-eval'",
12468
+ "style-src 'self' 'unsafe-inline'",
12469
+ "img-src 'self' data: blob: https:",
12470
+ "font-src 'self' data:",
12471
+ "connect-src 'self' https:",
12472
+ "frame-ancestors 'none'"
12473
+ ].join("; ");
12474
+ var HEADERS_BLOCK = `
12475
+ async headers() {
12476
+ return [
12477
+ {
12478
+ source: '/(.*)',
12479
+ headers: [
12480
+ {
12481
+ key: 'Content-Security-Policy',
12482
+ value: \`${CSP_HEADER_VALUE}\`,
12483
+ },
12484
+ ],
12485
+ },
12486
+ ];
12487
+ },`;
12488
+ function applyCspHeaderRecipe(source) {
12489
+ if (/Content-Security-Policy/i.test(source)) {
12490
+ return { changed: false, output: source, canAutoApply: true };
12241
12491
  }
12242
- if (isCachePath(relativePath)) {
12243
- return "cache-directory";
12492
+ const exportsFn = /module\.exports\s*=\s*(async\s+)?function/.test(source) || /export\s+default\s+(async\s+)?function/.test(source) || /module\.exports\s*=\s*\(/.test(source);
12493
+ if (exportsFn) {
12494
+ return {
12495
+ changed: false,
12496
+ output: source,
12497
+ canAutoApply: false,
12498
+ reason: "config-exports-function"
12499
+ };
12244
12500
  }
12245
- if (relativePath.endsWith(".log") && file.sizeBytes >= LARGE_LOG_BYTES) {
12246
- return "large-log";
12501
+ if (/\bheaders\s*\(/.test(source)) {
12502
+ return {
12503
+ changed: false,
12504
+ output: source,
12505
+ canAutoApply: false,
12506
+ reason: "headers-already-defined"
12507
+ };
12247
12508
  }
12248
- return void 0;
12249
- }
12250
- function isCachePath(path) {
12251
- return path === ".next/cache" || path.startsWith(".next/cache/") || path === ".turbo" || path.startsWith(".turbo/") || path === ".vite" || path.startsWith(".vite/") || path.includes("/node_modules/.cache/") || path.endsWith("/node_modules/.cache");
12252
- }
12253
- function reasonForCategory(category) {
12254
- switch (category) {
12255
- case "generated-artifact":
12256
- return "Generated VibeRaven artifact. Keep current scan outputs, but avoid loading old reports into agent context.";
12257
- case "large-log":
12258
- return "Large log file that can add token noise. Review it before changing ignore rules or excluding it from agent context.";
12259
- case "cache-directory":
12260
- return "Build cache evidence. Do not load it into agent context unless debugging build internals.";
12509
+ const inlineConfigMatch = /(?:const|let|var)\s+nextConfig\s*=\s*\{[\s\S]*?\}(?=\s*;)/.exec(source);
12510
+ if (inlineConfigMatch) {
12511
+ const closingBrace = inlineConfigMatch.index + inlineConfigMatch[0].lastIndexOf("}");
12512
+ const output2 = source.slice(0, closingBrace) + "," + HEADERS_BLOCK + source.slice(closingBrace);
12513
+ return { changed: true, output: output2, canAutoApply: true };
12261
12514
  }
12262
- }
12263
- function renderCleanupPlan(plan) {
12264
- const lines = ["# VibeRaven context cleanup plan", "", plan.warning, ""];
12265
- if (plan.items.length === 0) {
12266
- lines.push("No noisy generated artifacts, large logs, or cache directories were found by the conservative scanner.");
12515
+ const moduleExportsMatch = /module\.exports\s*=\s*\{[\s\S]*?\}(?=\s*;?)/.exec(source);
12516
+ if (moduleExportsMatch) {
12517
+ const closingBrace = moduleExportsMatch.index + moduleExportsMatch[0].lastIndexOf("}");
12518
+ const output2 = source.slice(0, closingBrace) + "," + HEADERS_BLOCK + source.slice(closingBrace);
12519
+ return { changed: true, output: output2, canAutoApply: true };
12267
12520
  }
12268
- for (const item3 of plan.items) {
12269
- lines.push(`- ${item3.path}`);
12270
- lines.push(` - Category: ${item3.category}`);
12271
- lines.push(` - Action: ${item3.action}`);
12272
- lines.push(` - Reason: ${item3.reason}`);
12521
+ const lastBrace = source.lastIndexOf("\n}");
12522
+ if (lastBrace === -1) {
12523
+ return {
12524
+ changed: false,
12525
+ output: source,
12526
+ canAutoApply: false,
12527
+ reason: "cannot-locate-config-closing-brace"
12528
+ };
12273
12529
  }
12274
- return `${lines.join("\n")}
12275
- `;
12530
+ const output = source.slice(0, lastBrace) + "," + HEADERS_BLOCK + source.slice(lastBrace);
12531
+ return { changed: true, output, canAutoApply: true };
12276
12532
  }
12277
- async function walkCleanupFiles(root, dir, result, depth) {
12278
- if (depth > MAX_WALK_DEPTH) {
12279
- return;
12280
- }
12281
- let entries;
12282
- try {
12283
- entries = await (0, import_promises13.readdir)(dir, { withFileTypes: true });
12284
- } catch {
12285
- return;
12286
- }
12287
- for (const entry of entries) {
12288
- if (SKIP_DIRECTORIES.has(entry.name)) {
12289
- continue;
12290
- }
12291
- const absolute = (0, import_node_path18.join)(dir, entry.name);
12292
- if (entry.isDirectory()) {
12293
- if (isCachePath(normalizePath5((0, import_node_path18.relative)(root, absolute)))) {
12294
- result.push({ path: absolute, sizeBytes: 0, kind: "directory" });
12295
- continue;
12296
- }
12297
- await walkCleanupFiles(root, absolute, result, depth + 1);
12298
- continue;
12299
- }
12300
- if (!entry.isFile()) {
12301
- continue;
12302
- }
12303
- try {
12304
- const fileStat = await (0, import_promises13.stat)(absolute);
12305
- result.push({ path: absolute, sizeBytes: fileStat.size, kind: "file" });
12306
- } catch {
12307
- continue;
12308
- }
12533
+
12534
+ // src/heal/recipes/nextjsRateLimit.ts
12535
+ var DEPENDENCY_HINT = "If you see @upstash/ratelimit references in this file, run: npm install @upstash/ratelimit @upstash/redis";
12536
+ var UPSTASH_MIDDLEWARE = `import { NextResponse } from 'next/server';
12537
+ import type { NextRequest } from 'next/server';
12538
+ import { Ratelimit } from '@upstash/ratelimit';
12539
+ import { Redis } from '@upstash/redis';
12540
+
12541
+ // VibeRaven heal: missing_rate_limit (Upstash)
12542
+ // Configure UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN in .env.local
12543
+ const ratelimit = new Ratelimit({
12544
+ redis: Redis.fromEnv(),
12545
+ limiter: Ratelimit.slidingWindow(60, '1 m'), // 60 requests per minute per IP
12546
+ analytics: false,
12547
+ });
12548
+
12549
+ export async function middleware(request: NextRequest): Promise<NextResponse> {
12550
+ const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? '127.0.0.1';
12551
+ const { success } = await ratelimit.limit(ip);
12552
+
12553
+ if (!success) {
12554
+ return new NextResponse('Too Many Requests', { status: 429 });
12309
12555
  }
12310
- }
12311
- function normalizePath5(path) {
12312
- return path.replace(/\\/g, "/");
12556
+
12557
+ return NextResponse.next();
12313
12558
  }
12314
12559
 
12315
- // src/mcpServer.ts
12316
- var import_node_child_process4 = require("node:child_process");
12317
- var import_node_fs7 = require("node:fs");
12318
- var import_node_path19 = require("node:path");
12319
- var cwdSchema = {
12320
- type: "object",
12321
- properties: {
12322
- cwd: { type: "string", description: "Project root. Defaults to the MCP server working directory." }
12323
- },
12324
- additionalProperties: false
12325
- };
12326
- var healSchema = {
12327
- type: "object",
12328
- properties: {
12329
- cwd: { type: "string" },
12330
- target: { type: "string" },
12331
- gap: { type: "string" },
12332
- yes: { type: "boolean" }
12333
- },
12334
- additionalProperties: false
12560
+ export const config = {
12561
+ matcher: ['/api/:path*'],
12335
12562
  };
12336
- var CLI_MCP_TOOLS = [
12337
- {
12338
- name: "viberaven_check_readiness",
12339
- description: "Run the main VibeRaven production-readiness check from the current project.",
12340
- inputSchema: cwdSchema
12341
- },
12342
- {
12343
- name: "viberaven_verify",
12344
- description: "Rescan and refresh VibeRaven production-readiness artifacts after a fix.",
12345
- inputSchema: cwdSchema
12346
- },
12347
- {
12348
- name: "viberaven_audit",
12349
- description: "Run local Vercel/Supabase production checks for RLS, service-role boundaries, and Vercel pooler usage.",
12350
- inputSchema: {
12351
- type: "object",
12352
- properties: {
12353
- cwd: { type: "string", description: "Project root. Defaults to current working directory." },
12354
- json: { type: "boolean", description: "Return JSON output." }
12355
- },
12356
- additionalProperties: false
12357
- }
12358
- },
12359
- {
12360
- name: "viberaven_init_rules",
12361
- description: "Install bounded VibeRaven rules into native AI instruction files.",
12362
- inputSchema: {
12363
- type: "object",
12364
- properties: {
12365
- cwd: { type: "string", description: "Project root. Defaults to current working directory." },
12366
- agents: { type: "string", description: "Comma-separated agent targets, or all." },
12367
- dryRun: { type: "boolean", description: "Preview changes without writing files." }
12368
- },
12369
- additionalProperties: false
12370
- }
12371
- },
12372
- {
12373
- name: "viberaven_clean_plan",
12374
- description: "Write a non-destructive context cleanup plan for generated artifacts and logs.",
12375
- inputSchema: cwdSchema
12376
- },
12377
- {
12378
- name: "viberaven_strict_gate",
12379
- description: "Run VibeRaven agent-mode strict gate and return the machine verdict.",
12380
- inputSchema: cwdSchema
12381
- },
12382
- {
12383
- name: "viberaven_gate_result",
12384
- description: "Run VibeRaven agent-mode JSON output and return gate-result.json content.",
12385
- inputSchema: cwdSchema
12386
- },
12387
- {
12388
- name: "viberaven_context_map",
12389
- description: "Refresh .viberaven/context-map.json from the last scan.",
12390
- inputSchema: cwdSchema
12391
- },
12392
- {
12393
- name: "viberaven_heal_plan",
12394
- description: "Write a non-destructive VibeRaven heal plan for a target file or gap.",
12395
- inputSchema: healSchema
12396
- },
12397
- {
12398
- name: "viberaven_heal_prompt",
12399
- description: "Write an agent-ready VibeRaven heal prompt for a target file or gap.",
12400
- inputSchema: healSchema
12401
- },
12402
- {
12403
- name: "viberaven_heal_apply",
12404
- description: "Apply a guarded VibeRaven repo-code heal recipe when supported.",
12405
- inputSchema: healSchema
12563
+ `;
12564
+ var INMEMORY_MIDDLEWARE = `import { NextResponse } from 'next/server';
12565
+ import type { NextRequest } from 'next/server';
12566
+
12567
+ // VibeRaven heal: missing_rate_limit (in-memory fallback \u2014 resets on cold start)
12568
+ // For production, replace with @upstash/ratelimit + Redis.
12569
+ const WINDOW_MS = 60_000; // 1 minute
12570
+ const MAX_REQUESTS = 60; // per IP per window
12571
+
12572
+ const ipMap = new Map<string, { count: number; windowStart: number }>();
12573
+
12574
+ function isRateLimited(ip: string): boolean {
12575
+ const now = Date.now();
12576
+ const entry = ipMap.get(ip);
12577
+
12578
+ if (!entry || now - entry.windowStart > WINDOW_MS) {
12579
+ ipMap.set(ip, { count: 1, windowStart: now });
12580
+ return false;
12406
12581
  }
12407
- ];
12408
- var spawnChild = import_node_child_process4.spawn;
12409
- var buffer = Buffer.alloc(0);
12410
- function buildLocalCliArgs(args) {
12411
- const currentFile = __filename;
12412
- const currentBase = (0, import_node_path19.basename)(currentFile).toLowerCase();
12413
- const cliEntry = currentBase === "cli.js" || currentBase === "cli.ts" ? currentFile : (0, import_node_path19.join)((0, import_node_path19.dirname)(currentFile), currentBase.endsWith(".ts") ? "cli.ts" : "cli.js");
12414
- return [cliEntry, ...args];
12582
+
12583
+ entry.count += 1;
12584
+ return entry.count > MAX_REQUESTS;
12415
12585
  }
12416
- function resolveCwd(cwd) {
12417
- if (cwd === void 0 || cwd === null) {
12418
- return process.cwd();
12419
- }
12420
- if (typeof cwd !== "string") {
12421
- throw new Error("Invalid cwd: expected string");
12422
- }
12423
- if (!cwd.trim()) {
12424
- throw new Error("Invalid cwd: expected non-empty string");
12425
- }
12426
- if (!(0, import_node_fs7.existsSync)(cwd)) {
12427
- throw new Error(`Invalid cwd: directory does not exist: ${cwd}`);
12428
- }
12429
- if (!(0, import_node_fs7.statSync)(cwd).isDirectory()) {
12430
- throw new Error(`Invalid cwd: not a directory: ${cwd}`);
12586
+
12587
+ export function middleware(request: NextRequest): NextResponse {
12588
+ const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? '127.0.0.1';
12589
+
12590
+ if (isRateLimited(ip)) {
12591
+ return new NextResponse('Too Many Requests', { status: 429 });
12431
12592
  }
12432
- return cwd;
12593
+
12594
+ return NextResponse.next();
12433
12595
  }
12434
- function safeStringArg(value, label) {
12435
- if (typeof value !== "string") {
12436
- throw new Error(`Invalid ${label}: expected string`);
12437
- }
12438
- if (!value.trim()) {
12439
- throw new Error(`Invalid ${label}: expected non-empty string`);
12596
+
12597
+ export const config = {
12598
+ matcher: ['/api/:path*'],
12599
+ };
12600
+ `;
12601
+ function applyRateLimitRecipe(source, hasUpstash) {
12602
+ if (/ratelimit|rate.limit|ipMap/i.test(source) || /429/.test(source)) {
12603
+ return {
12604
+ changed: false,
12605
+ output: source,
12606
+ canAutoApply: true,
12607
+ usedUpstash: false
12608
+ };
12440
12609
  }
12441
- if (/[&|<>^%!"\r\n]/.test(value)) {
12442
- throw new Error(`Unsafe characters in ${label}`);
12610
+ if (hasUpstash) {
12611
+ return {
12612
+ changed: true,
12613
+ output: UPSTASH_MIDDLEWARE,
12614
+ canAutoApply: true,
12615
+ usedUpstash: true,
12616
+ dependencyHint: DEPENDENCY_HINT
12617
+ };
12443
12618
  }
12444
- return value;
12619
+ return {
12620
+ changed: true,
12621
+ output: INMEMORY_MIDDLEWARE,
12622
+ canAutoApply: true,
12623
+ usedUpstash: false
12624
+ };
12625
+ }
12626
+
12627
+ // src/heal/recipes/index.ts
12628
+ function defaultTargetFile(gapId) {
12629
+ const map = {
12630
+ auth_secret_missing: ".env.local",
12631
+ node_env_not_set: ".env.local",
12632
+ database_url_missing: ".env.local",
12633
+ missing_error_boundary: "app/error.tsx",
12634
+ missing_health_route: "app/api/health/route.ts",
12635
+ missing_loading_state: "app/loading.tsx",
12636
+ missing_404_page: "app/not-found.tsx",
12637
+ rls_disabled: "",
12638
+ // no file — provider-action
12639
+ missing_csp_header: "next.config.js",
12640
+ missing_rate_limit: "middleware.ts"
12641
+ };
12642
+ return map[gapId];
12643
+ }
12644
+ async function readSourceOrEmpty(absolutePath) {
12645
+ if (!(0, import_node_fs7.existsSync)(absolutePath)) return "";
12646
+ return (0, import_promises10.readFile)(absolutePath, "utf8");
12445
12647
  }
12446
- function buildToolArgs(name, input) {
12447
- if (name === "viberaven_check_readiness") {
12448
- return ["--agent-mode"];
12648
+ async function detectUpstash(cwd) {
12649
+ try {
12650
+ const pkgPath = (0, import_node_path15.join)(cwd, "package.json");
12651
+ if (!(0, import_node_fs7.existsSync)(pkgPath)) return false;
12652
+ const raw = await (0, import_promises10.readFile)(pkgPath, "utf8");
12653
+ const pkg = JSON.parse(raw);
12654
+ const deps = {
12655
+ ...pkg["dependencies"] ?? {},
12656
+ ...pkg["devDependencies"] ?? {}
12657
+ };
12658
+ return "@upstash/ratelimit" in deps;
12659
+ } catch {
12660
+ return false;
12449
12661
  }
12450
- if (name === "viberaven_verify") {
12451
- return ["--verify"];
12662
+ }
12663
+ async function dispatchRecipeByGapId(gapId, cwd, explicitTarget) {
12664
+ const targetFile = explicitTarget ?? defaultTargetFile(gapId);
12665
+ if (targetFile === void 0) return null;
12666
+ if (gapId === "auth_secret_missing" || gapId === "node_env_not_set" || gapId === "database_url_missing") {
12667
+ const absolutePath = (0, import_node_path15.join)(cwd, targetFile);
12668
+ const source = await readSourceOrEmpty(absolutePath);
12669
+ let result;
12670
+ if (gapId === "auth_secret_missing") result = applyAuthSecretRecipe(source);
12671
+ else if (gapId === "node_env_not_set") result = applyNodeEnvRecipe(source);
12672
+ else result = applyDatabaseUrlRecipe(source);
12673
+ return {
12674
+ ...result,
12675
+ targetFile,
12676
+ canAutoApply: true,
12677
+ recipeName: gapId
12678
+ };
12452
12679
  }
12453
- if (name === "viberaven_audit") {
12454
- return ["audit", "--vercel-supabase", ...input.json ? ["--json"] : []];
12680
+ if (gapId === "missing_error_boundary") {
12681
+ const absolutePath = (0, import_node_path15.join)(cwd, "app/error.tsx");
12682
+ const source = await readSourceOrEmpty(absolutePath);
12683
+ const result = applyErrorBoundaryRecipe(source);
12684
+ return { ...result, canAutoApply: true, recipeName: gapId };
12685
+ }
12686
+ if (gapId === "missing_health_route") {
12687
+ const absolutePath = (0, import_node_path15.join)(cwd, "app/api/health/route.ts");
12688
+ const source = await readSourceOrEmpty(absolutePath);
12689
+ const result = applyHealthRouteRecipe(source);
12690
+ return { ...result, canAutoApply: true, recipeName: gapId };
12691
+ }
12692
+ if (gapId === "missing_loading_state") {
12693
+ const absolutePath = (0, import_node_path15.join)(cwd, "app/loading.tsx");
12694
+ const source = await readSourceOrEmpty(absolutePath);
12695
+ const result = applyLoadingStateRecipe(source);
12696
+ return { ...result, canAutoApply: true, recipeName: gapId };
12697
+ }
12698
+ if (gapId === "missing_404_page") {
12699
+ const absolutePath = (0, import_node_path15.join)(cwd, "app/not-found.tsx");
12700
+ const source = await readSourceOrEmpty(absolutePath);
12701
+ const result = applyNotFoundRecipe(source);
12702
+ return { ...result, canAutoApply: true, recipeName: gapId };
12703
+ }
12704
+ if (gapId === "rls_disabled") {
12705
+ const result = applyRlsRecipe("");
12706
+ return {
12707
+ changed: false,
12708
+ output: "",
12709
+ targetFile: "",
12710
+ canAutoApply: false,
12711
+ reason: result.reason,
12712
+ recipeName: gapId
12713
+ };
12455
12714
  }
12456
- if (name === "viberaven_init_rules") {
12457
- const args = ["init"];
12458
- if (input.agents !== void 0) {
12459
- args.push("--agents", safeStringArg(input.agents, "agents"));
12460
- }
12461
- if (input.dryRun) {
12462
- args.push("--dry-run");
12715
+ if (gapId === "missing_csp_header") {
12716
+ let configFile = "next.config.js";
12717
+ let absolutePath = (0, import_node_path15.join)(cwd, configFile);
12718
+ if (!(0, import_node_fs7.existsSync)(absolutePath)) {
12719
+ configFile = "next.config.mjs";
12720
+ absolutePath = (0, import_node_path15.join)(cwd, configFile);
12463
12721
  }
12464
- return args;
12465
- }
12466
- if (name === "viberaven_clean_plan") {
12467
- return ["clean", "--plan"];
12468
- }
12469
- if (name === "viberaven_strict_gate") {
12470
- return ["--agent-mode", "--strict", "--json"];
12722
+ const source = await readSourceOrEmpty(absolutePath);
12723
+ const result = applyCspHeaderRecipe(source);
12724
+ return {
12725
+ ...result,
12726
+ targetFile: configFile,
12727
+ recipeName: gapId
12728
+ };
12471
12729
  }
12472
- if (name === "viberaven_gate_result") {
12473
- return ["--agent-mode", "--json"];
12730
+ if (gapId === "missing_rate_limit") {
12731
+ const hasUpstash = await detectUpstash(cwd);
12732
+ const middlewarePath = (0, import_node_path15.join)(cwd, "middleware.ts");
12733
+ const source = await readSourceOrEmpty(middlewarePath);
12734
+ const result = applyRateLimitRecipe(source, hasUpstash);
12735
+ return {
12736
+ ...result,
12737
+ targetFile: "middleware.ts",
12738
+ canAutoApply: true,
12739
+ recipeName: gapId
12740
+ };
12474
12741
  }
12475
- if (name === "viberaven_context_map") {
12476
- return ["--condense"];
12742
+ return null;
12743
+ }
12744
+
12745
+ // src/heal/apply.ts
12746
+ function healId() {
12747
+ return `heal_${(/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14)}`;
12748
+ }
12749
+ async function applyHeal(options) {
12750
+ const id = healId();
12751
+ if (!options.yes) {
12752
+ return {
12753
+ $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
12754
+ schemaVersion: "v1",
12755
+ runId: "vr_heal_apply",
12756
+ healId: id,
12757
+ mode: "apply",
12758
+ status: "refused_dangerous",
12759
+ gapId: options.gapId,
12760
+ target: options.target,
12761
+ changedFiles: [],
12762
+ artifacts: {},
12763
+ rollback: { available: false, instructions: "Re-run with --yes to allow guarded repo-code edits." }
12764
+ };
12477
12765
  }
12478
- if (name === "viberaven_heal_plan") {
12479
- const args = ["--heal", "--plan"];
12480
- if (typeof input.target === "string") args.push("--target", input.target);
12481
- if (typeof input.gap === "string") args.push("--gap", input.gap);
12482
- return args;
12766
+ if (options.gapId) {
12767
+ const dispatched = await dispatchRecipeByGapId(
12768
+ options.gapId,
12769
+ options.cwd,
12770
+ options.target
12771
+ );
12772
+ if (dispatched !== null) {
12773
+ if (!dispatched.canAutoApply) {
12774
+ return {
12775
+ $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
12776
+ schemaVersion: "v1",
12777
+ runId: "vr_heal_apply",
12778
+ healId: id,
12779
+ mode: "apply",
12780
+ status: "refused_unsupported",
12781
+ gapId: options.gapId,
12782
+ changedFiles: [],
12783
+ artifacts: {},
12784
+ rollback: {
12785
+ available: false,
12786
+ instructions: dispatched.reason ?? "This gap requires a manual provider-dashboard action."
12787
+ }
12788
+ };
12789
+ }
12790
+ if (!dispatched.changed) {
12791
+ return {
12792
+ $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
12793
+ schemaVersion: "v1",
12794
+ runId: "vr_heal_apply",
12795
+ healId: id,
12796
+ mode: "apply",
12797
+ status: "refused_unsupported",
12798
+ gapId: options.gapId,
12799
+ target: dispatched.targetFile || options.target,
12800
+ changedFiles: [],
12801
+ artifacts: {},
12802
+ rollback: { available: false, instructions: "Recipe matched but no change was needed (already applied or file already exists)." }
12803
+ };
12804
+ }
12805
+ const absoluteTarget = (0, import_node_path16.join)(options.cwd, dispatched.targetFile);
12806
+ assertSafeHealTarget(options.cwd, dispatched.targetFile);
12807
+ await (0, import_promises11.mkdir)((0, import_node_path16.dirname)(absoluteTarget), { recursive: true });
12808
+ const healDir2 = (0, import_node_path16.join)(options.cwd, ".viberaven", "heal", id);
12809
+ await (0, import_promises11.mkdir)((0, import_node_path16.join)(healDir2, "before"), { recursive: true });
12810
+ const beforeContent = (0, import_node_fs8.existsSync)(absoluteTarget) ? await (0, import_promises11.readFile)(absoluteTarget, "utf8") : "";
12811
+ await (0, import_promises11.writeFile)((0, import_node_path16.join)(healDir2, "before", "target.txt"), beforeContent, "utf8");
12812
+ await (0, import_promises11.writeFile)(absoluteTarget, dispatched.output, "utf8");
12813
+ const patch2 = [
12814
+ `--- ${dispatched.targetFile}`,
12815
+ `+++ ${dispatched.targetFile}`,
12816
+ "@@ VibeRaven guarded heal @@",
12817
+ beforeContent || "(new file)",
12818
+ "--- after ---",
12819
+ dispatched.output,
12820
+ ""
12821
+ ].join("\n");
12822
+ await (0, import_promises11.writeFile)((0, import_node_path16.join)(healDir2, "patch.diff"), patch2, "utf8");
12823
+ const result2 = {
12824
+ $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
12825
+ schemaVersion: "v1",
12826
+ runId: "vr_heal_apply",
12827
+ healId: id,
12828
+ mode: "apply",
12829
+ status: "applied_verify_not_run",
12830
+ gapId: options.gapId,
12831
+ recipe: dispatched.recipeName,
12832
+ target: dispatched.targetFile,
12833
+ changedFiles: [(0, import_node_path16.relative)(options.cwd, absoluteTarget).replace(/\\/g, "/")],
12834
+ artifacts: {
12835
+ patch: `.viberaven/heal/${id}/patch.diff`,
12836
+ result: `.viberaven/heal/${id}/result.json`
12837
+ },
12838
+ rollback: {
12839
+ available: true,
12840
+ instructions: "Restore .viberaven/heal/<healId>/before/target.txt to the target file or apply the reverse patch."
12841
+ }
12842
+ };
12843
+ await (0, import_promises11.writeFile)((0, import_node_path16.join)(healDir2, "result.json"), `${JSON.stringify(result2, null, 2)}
12844
+ `, "utf8");
12845
+ return result2;
12846
+ }
12483
12847
  }
12484
- if (name === "viberaven_heal_prompt") {
12485
- const args = ["--heal", "--prompt"];
12486
- if (typeof input.target === "string") args.push("--target", input.target);
12487
- if (typeof input.gap === "string") args.push("--gap", input.gap);
12488
- return args;
12848
+ if (!options.target) {
12849
+ return {
12850
+ $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
12851
+ schemaVersion: "v1",
12852
+ runId: "vr_heal_apply",
12853
+ healId: id,
12854
+ mode: "apply",
12855
+ status: "refused_unsupported",
12856
+ gapId: options.gapId,
12857
+ changedFiles: [],
12858
+ artifacts: {},
12859
+ rollback: { available: false, instructions: "Apply requires a supported --target file in 1.0." }
12860
+ };
12489
12861
  }
12490
- if (name === "viberaven_heal_apply") {
12491
- const args = ["--heal", "--apply"];
12492
- if (typeof input.target === "string") args.push("--target", input.target);
12493
- if (typeof input.gap === "string") args.push("--gap", input.gap);
12494
- if (input.yes === true) args.push("--yes");
12495
- return args;
12862
+ const absolute = assertSafeHealTarget(options.cwd, options.target);
12863
+ const before = await (0, import_promises11.readFile)(absolute, "utf8");
12864
+ const recipe = applyEmptyCatchRecipe(before);
12865
+ if (!recipe.changed) {
12866
+ return {
12867
+ $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
12868
+ schemaVersion: "v1",
12869
+ runId: "vr_heal_apply",
12870
+ healId: id,
12871
+ mode: "apply",
12872
+ status: "refused_unsupported",
12873
+ target: options.target,
12874
+ gapId: options.gapId,
12875
+ changedFiles: [],
12876
+ artifacts: {},
12877
+ rollback: { available: false, instructions: "No supported heal recipe matched this file." }
12878
+ };
12496
12879
  }
12497
- throw new Error(`Unknown VibeRaven MCP tool: ${name}`);
12880
+ const healDir = (0, import_node_path16.join)(options.cwd, ".viberaven", "heal", id);
12881
+ await (0, import_promises11.mkdir)((0, import_node_path16.join)(healDir, "before"), { recursive: true });
12882
+ await (0, import_promises11.writeFile)((0, import_node_path16.join)(healDir, "before", "target.txt"), before, "utf8");
12883
+ await (0, import_promises11.writeFile)(absolute, recipe.output, "utf8");
12884
+ const patch = [
12885
+ `--- ${options.target}`,
12886
+ `+++ ${options.target}`,
12887
+ "@@ VibeRaven guarded heal @@",
12888
+ before,
12889
+ "--- after ---",
12890
+ recipe.output,
12891
+ ""
12892
+ ].join("\n");
12893
+ await (0, import_promises11.writeFile)((0, import_node_path16.join)(healDir, "patch.diff"), patch, "utf8");
12894
+ const result = {
12895
+ $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
12896
+ schemaVersion: "v1",
12897
+ runId: "vr_heal_apply",
12898
+ healId: id,
12899
+ mode: "apply",
12900
+ status: "applied_verify_not_run",
12901
+ gapId: options.gapId,
12902
+ recipe: "empty-catch-safe-response",
12903
+ target: options.target,
12904
+ changedFiles: [(0, import_node_path16.relative)(options.cwd, absolute).replace(/\\/g, "/")],
12905
+ artifacts: {
12906
+ patch: `.viberaven/heal/${id}/patch.diff`,
12907
+ result: `.viberaven/heal/${id}/result.json`
12908
+ },
12909
+ rollback: {
12910
+ available: true,
12911
+ instructions: "Restore .viberaven/heal/<healId>/before/target.txt to the target file or apply the reverse patch."
12912
+ }
12913
+ };
12914
+ await (0, import_promises11.writeFile)((0, import_node_path16.join)(healDir, "result.json"), `${JSON.stringify(result, null, 2)}
12915
+ `, "utf8");
12916
+ return result;
12917
+ }
12918
+
12919
+ // src/heal/plan.ts
12920
+ var import_promises12 = require("node:fs/promises");
12921
+ var import_node_path17 = require("node:path");
12922
+ function healId2() {
12923
+ return `heal_${(/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14)}`;
12924
+ }
12925
+ async function writeHealPlan(options) {
12926
+ const dir = (0, import_node_path17.join)(options.cwd, ".viberaven");
12927
+ await (0, import_promises12.mkdir)(dir, { recursive: true });
12928
+ const id = healId2();
12929
+ const target = options.target ?? `gap:${options.gapId}`;
12930
+ const markdown = [
12931
+ "# VibeRaven Heal Plan",
12932
+ "",
12933
+ `Target: \`${target}\``,
12934
+ `Mode: \`${options.mode}\``,
12935
+ "",
12936
+ "This plan is non-destructive. It does not edit source files.",
12937
+ "",
12938
+ `Verify after manual fix: \`${PUBLIC_VERIFY_COMMAND}\``,
12939
+ ""
12940
+ ].join("\n");
12941
+ const result = {
12942
+ $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
12943
+ schemaVersion: "v1",
12944
+ runId: "vr_heal_plan",
12945
+ healId: id,
12946
+ mode: "plan",
12947
+ status: "planned",
12948
+ gapId: options.gapId,
12949
+ target: options.target,
12950
+ changedFiles: [],
12951
+ artifacts: { plan: ".viberaven/heal-plan.md" },
12952
+ rollback: { available: false, instructions: "No source files were changed." }
12953
+ };
12954
+ await (0, import_promises12.writeFile)((0, import_node_path17.join)(dir, "heal-plan.md"), markdown, "utf8");
12955
+ await (0, import_promises12.writeFile)((0, import_node_path17.join)(dir, "heal-plan.json"), `${JSON.stringify(result, null, 2)}
12956
+ `, "utf8");
12957
+ return result;
12958
+ }
12959
+
12960
+ // src/heal/prompt.ts
12961
+ var import_promises13 = require("node:fs/promises");
12962
+ var import_node_path18 = require("node:path");
12963
+ function healId3() {
12964
+ return `heal_${(/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14)}`;
12965
+ }
12966
+ async function writeHealPrompt(options) {
12967
+ const dir = (0, import_node_path18.join)(options.cwd, ".viberaven");
12968
+ await (0, import_promises13.mkdir)(dir, { recursive: true });
12969
+ const id = healId3();
12970
+ const target = options.target ?? `gap:${options.gapId}`;
12971
+ const prompt = [
12972
+ "# VibeRaven Heal Prompt",
12973
+ "",
12974
+ `Fix only this target: \`${target}\`.`,
12975
+ "",
12976
+ "- Do not request secrets.",
12977
+ "- Do not edit provider dashboards.",
12978
+ "- Do not change migrations, auth authorization logic, payment correctness, or webhook signature logic unless a VibeRaven recipe explicitly supports it.",
12979
+ `- After the fix, run \`${PUBLIC_VERIFY_COMMAND}\`.`,
12980
+ ""
12981
+ ].join("\n");
12982
+ const result = {
12983
+ $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
12984
+ schemaVersion: "v1",
12985
+ runId: "vr_heal_prompt",
12986
+ healId: id,
12987
+ mode: "prompt",
12988
+ status: "prompt_written",
12989
+ gapId: options.gapId,
12990
+ target: options.target,
12991
+ changedFiles: [],
12992
+ artifacts: { prompt: ".viberaven/heal-prompt.md" },
12993
+ rollback: { available: false, instructions: "No source files were changed." }
12994
+ };
12995
+ await (0, import_promises13.writeFile)((0, import_node_path18.join)(dir, "heal-prompt.md"), prompt, "utf8");
12996
+ return result;
12498
12997
  }
12499
- function spawnLocalCli(args, cwd) {
12500
- return new Promise((resolve5) => {
12501
- const child = spawnChild(process.execPath, buildLocalCliArgs(args), {
12502
- cwd,
12503
- env: process.env,
12504
- shell: false,
12505
- stdio: ["ignore", "pipe", "pipe"]
12506
- });
12507
- let stdout = "";
12508
- let stderr = "";
12509
- child.stdout.on("data", (chunk) => {
12510
- stdout += chunk.toString();
12511
- });
12512
- child.stderr.on("data", (chunk) => {
12513
- stderr += chunk.toString();
12514
- });
12515
- child.on("error", (error) => {
12516
- resolve5({ code: 1, stdout: "", stderr: `ERROR: ${error.message}` });
12517
- });
12518
- child.on("close", (code) => {
12519
- resolve5({ code, stdout, stderr });
12520
- });
12521
- });
12998
+
12999
+ // src/loopState.ts
13000
+ var import_promises14 = require("fs/promises");
13001
+ var import_path = require("path");
13002
+ var DEFAULT_LOOP_STATE = {
13003
+ batchApplied: 0,
13004
+ lastGapCount: -1,
13005
+ stalledScans: 0,
13006
+ appliedGapIdsSinceScan: []
13007
+ };
13008
+ function loopStatePath(workspaceRoot) {
13009
+ return (0, import_path.join)(workspaceRoot, ".viberaven", "loop-state.json");
12522
13010
  }
12523
- function parseJsonObjectFromToolText(text) {
12524
- const jsonStart = text.indexOf("{");
12525
- if (jsonStart === -1) return void 0;
13011
+ async function loadLoopState(workspaceRoot) {
12526
13012
  try {
12527
- return JSON.parse(text.slice(jsonStart));
13013
+ const raw = await (0, import_promises14.readFile)(loopStatePath(workspaceRoot), "utf8");
13014
+ const parsed = JSON.parse(raw);
13015
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.batchApplied === "number" && typeof parsed.lastGapCount === "number" && typeof parsed.stalledScans === "number") {
13016
+ const p2 = parsed;
13017
+ const appliedGapIdsSinceScan = Array.isArray(p2.appliedGapIdsSinceScan) ? p2.appliedGapIdsSinceScan.filter((value) => typeof value === "string") : [];
13018
+ return {
13019
+ batchApplied: p2.batchApplied,
13020
+ lastGapCount: p2.lastGapCount,
13021
+ stalledScans: p2.stalledScans,
13022
+ appliedGapIdsSinceScan
13023
+ };
13024
+ }
13025
+ return { ...DEFAULT_LOOP_STATE };
12528
13026
  } catch {
12529
- return void 0;
13027
+ return { ...DEFAULT_LOOP_STATE };
12530
13028
  }
12531
13029
  }
12532
- async function callVibeRavenMcpTool(name, input = {}) {
12533
- const cwd = resolveCwd(input.cwd);
12534
- const result = await spawnLocalCli(buildToolArgs(name, input), cwd);
12535
- const combined = `${result.stdout}${result.stderr}`.trim();
12536
- return `exit ${result.code ?? 1}${combined ? `
12537
- ${combined}` : ""}`;
13030
+ async function saveLoopState(workspaceRoot, state) {
13031
+ try {
13032
+ const dir = (0, import_path.join)(workspaceRoot, ".viberaven");
13033
+ await (0, import_promises14.mkdir)(dir, { recursive: true });
13034
+ await (0, import_promises14.writeFile)(loopStatePath(workspaceRoot), JSON.stringify(state, null, 2) + "\n", "utf8");
13035
+ } catch (err) {
13036
+ console.warn("[VibeRaven] Could not save loop-state.json:", err instanceof Error ? err.message : String(err));
13037
+ }
12538
13038
  }
12539
- async function handleRequest(method, params) {
12540
- if (method === "initialize") {
12541
- return {
12542
- protocolVersion: params.protocolVersion ?? "2024-11-05",
12543
- capabilities: { tools: {} },
12544
- serverInfo: { name: "viberaven-cli-mcp", version: VERSION }
12545
- };
13039
+ function incrementBatch(state, gapId) {
13040
+ const appliedGapIdsSinceScan = [...state.appliedGapIdsSinceScan ?? []];
13041
+ if (gapId && !appliedGapIdsSinceScan.includes(gapId)) {
13042
+ appliedGapIdsSinceScan.push(gapId);
12546
13043
  }
12547
- if (method === "tools/list") {
12548
- return { tools: CLI_MCP_TOOLS };
13044
+ return { ...state, batchApplied: state.batchApplied + 1, appliedGapIdsSinceScan };
13045
+ }
13046
+ function resetBatch(state, newGapCount) {
13047
+ if (state.lastGapCount === -1) {
13048
+ return { batchApplied: 0, lastGapCount: newGapCount, stalledScans: 0, appliedGapIdsSinceScan: [] };
12549
13049
  }
12550
- if (method === "tools/call") {
12551
- const name = String(params.name ?? "");
12552
- const args = params.arguments ?? {};
12553
- const text = await callVibeRavenMcpTool(name, args);
12554
- const parsed = parseJsonObjectFromToolText(text);
12555
- return {
12556
- content: [{ type: "text", text }],
12557
- ...parsed ? { structuredContent: parsed } : {}
12558
- };
13050
+ if (newGapCount < state.lastGapCount) {
13051
+ return { batchApplied: 0, lastGapCount: newGapCount, stalledScans: 0, appliedGapIdsSinceScan: [] };
12559
13052
  }
12560
- throw new Error(`Unsupported MCP method: ${method}`);
12561
- }
12562
- function send(message) {
12563
- const body = JSON.stringify(message);
12564
- process.stdout.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r
12565
- \r
12566
- ${body}`);
13053
+ return {
13054
+ batchApplied: 0,
13055
+ lastGapCount: newGapCount,
13056
+ stalledScans: state.stalledScans + 1,
13057
+ appliedGapIdsSinceScan: []
13058
+ };
12567
13059
  }
12568
- async function handleRawMessage(raw) {
12569
- let message;
12570
- try {
12571
- message = JSON.parse(raw);
12572
- } catch {
12573
- return;
12574
- }
12575
- if (typeof message.method !== "string" || message.id === void 0 || message.id === null) {
12576
- return;
13060
+
13061
+ // src/commands/heal.ts
13062
+ async function runHealCommand(options) {
13063
+ if (!options.target && !options.gapId) {
13064
+ throw new Error("Heal requires --target <file> or --gap <id>.");
12577
13065
  }
12578
- try {
12579
- const result = await handleRequest(message.method, message.params ?? {});
12580
- send({ jsonrpc: "2.0", id: message.id, result });
12581
- } catch (error) {
12582
- send({
12583
- jsonrpc: "2.0",
12584
- id: message.id,
12585
- error: {
12586
- code: -32e3,
12587
- message: error instanceof Error ? error.message : String(error)
12588
- }
12589
- });
13066
+ if (options.target) {
13067
+ assertSafeHealTarget(options.cwd, options.target);
12590
13068
  }
12591
- }
12592
- function drainMessages() {
12593
- while (true) {
12594
- const headerEnd = buffer.indexOf("\r\n\r\n");
12595
- if (headerEnd === -1) return;
12596
- const header = buffer.slice(0, headerEnd).toString("utf8");
12597
- const match = header.match(/content-length:\s*(\d+)/i);
12598
- if (!match) {
12599
- buffer = buffer.slice(headerEnd + 4);
12600
- continue;
12601
- }
12602
- const length = Number(match[1]);
12603
- const messageStart = headerEnd + 4;
12604
- const messageEnd = messageStart + length;
12605
- if (buffer.length < messageEnd) return;
12606
- const raw = buffer.slice(messageStart, messageEnd).toString("utf8");
12607
- buffer = buffer.slice(messageEnd);
12608
- void handleRawMessage(raw);
13069
+ if (options.mode === "plan") return writeHealPlan(options);
13070
+ if (options.mode === "prompt") return writeHealPrompt(options);
13071
+ const result = await applyHeal(options);
13072
+ if (result.status.startsWith("applied_") && result.changedFiles.length > 0) {
13073
+ const loopState = await loadLoopState(options.cwd);
13074
+ await saveLoopState(options.cwd, incrementBatch(loopState, result.gapId));
12609
13075
  }
12610
- }
12611
- function runMcpServer() {
12612
- process.stdin.on("data", (chunk) => {
12613
- buffer = Buffer.concat([buffer, chunk]);
12614
- drainMessages();
12615
- });
13076
+ return result;
12616
13077
  }
12617
13078
 
12618
13079
  // src/stackRecommend.ts
12619
- var import_promises14 = require("node:fs/promises");
12620
- var import_node_fs8 = require("node:fs");
12621
- var import_node_path20 = require("node:path");
13080
+ var import_promises15 = require("node:fs/promises");
13081
+ var import_node_fs9 = require("node:fs");
13082
+ var import_node_path19 = require("node:path");
12622
13083
  var DEFAULT_STACK = {
12623
13084
  frontend: "react",
12624
13085
  ui: "tailwind + shadcn/ui",
@@ -12629,11 +13090,11 @@ var DEFAULT_STACK = {
12629
13090
  reason: "Agent-default stack for lowest launch friction when repo signals are ambiguous"
12630
13091
  };
12631
13092
  async function recommendStack(cwd = process.cwd()) {
12632
- const pkgPath = (0, import_node_path20.join)(cwd, "package.json");
12633
- if (!(0, import_node_fs8.existsSync)(pkgPath)) {
13093
+ const pkgPath = (0, import_node_path19.join)(cwd, "package.json");
13094
+ if (!(0, import_node_fs9.existsSync)(pkgPath)) {
12634
13095
  return DEFAULT_STACK;
12635
13096
  }
12636
- const pkg = JSON.parse(await (0, import_promises14.readFile)(pkgPath, "utf-8"));
13097
+ const pkg = JSON.parse(await (0, import_promises15.readFile)(pkgPath, "utf-8"));
12637
13098
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
12638
13099
  const names = Object.keys(deps).join(" ").toLowerCase();
12639
13100
  const rec = {
@@ -12662,6 +13123,328 @@ async function recommendStack(cwd = process.cwd()) {
12662
13123
  return rec;
12663
13124
  }
12664
13125
 
13126
+ // src/commands/runInit.ts
13127
+ async function runInitCommand(options) {
13128
+ let targets;
13129
+ try {
13130
+ targets = getAgentRulesTargets(options.agents);
13131
+ } catch (error) {
13132
+ console.error(error instanceof Error ? error.message : String(error));
13133
+ return 1;
13134
+ }
13135
+ if (options.dryRun) {
13136
+ console.log(renderAgentRulesDryRun(targets));
13137
+ return 0;
13138
+ }
13139
+ const results = await initAgentRules({
13140
+ cwd: options.cwd,
13141
+ targets,
13142
+ dryRun: false
13143
+ });
13144
+ console.log(formatAgentRulesInitSummary(results));
13145
+ return 0;
13146
+ }
13147
+
13148
+ // src/commands/doctorAgents.ts
13149
+ var import_promises16 = require("node:fs/promises");
13150
+ var import_node_path20 = require("node:path");
13151
+ var REQUIRED_EXISTENCE_CHECKS = [
13152
+ { id: "agents-md", file: "AGENTS.md" },
13153
+ { id: "claude-md", file: "CLAUDE.md" },
13154
+ { id: "cursor-rule", file: ".cursor/rules/viberaven.mdc" },
13155
+ { id: "copilot-instructions", file: ".github/copilot-instructions.md" }
13156
+ ];
13157
+ var STALE_PATTERNS = [
13158
+ { id: "stale-beta-scan", pattern: "@beta scan" },
13159
+ { id: "stale-viberaven-station", pattern: "viberaven-station" },
13160
+ { id: "stale-viberice", pattern: "viberice" }
13161
+ ];
13162
+ async function checkAgentInjection(cwd) {
13163
+ const checks = [];
13164
+ for (const item3 of REQUIRED_EXISTENCE_CHECKS) {
13165
+ const exists = await fileExists((0, import_node_path20.join)(cwd, item3.file));
13166
+ checks.push({
13167
+ id: item3.id,
13168
+ status: exists ? "pass" : "fail",
13169
+ message: exists ? `${item3.file} exists` : `Missing ${item3.file}`
13170
+ });
13171
+ }
13172
+ for (const target of CORE_AGENT_INJECTION_TARGETS) {
13173
+ const file = AGENT_RULE_TARGETS[target].file;
13174
+ const path = (0, import_node_path20.join)(cwd, file);
13175
+ const exists = await fileExists(path);
13176
+ if (!exists) {
13177
+ checks.push({
13178
+ id: `canonical-${target}`,
13179
+ status: "fail",
13180
+ message: `Cannot verify canonical command \u2014 missing ${file}`
13181
+ });
13182
+ continue;
13183
+ }
13184
+ const content = await (0, import_promises16.readFile)(path, "utf-8");
13185
+ const hasCommand = content.includes(PUBLIC_AGENT_MODE_COMMAND);
13186
+ checks.push({
13187
+ id: `canonical-${target}`,
13188
+ status: hasCommand ? "pass" : "fail",
13189
+ message: hasCommand ? `Canonical command found in ${file}` : `Missing canonical command in ${file}`
13190
+ });
13191
+ for (const stale of STALE_PATTERNS) {
13192
+ if (content.includes(stale.pattern)) {
13193
+ checks.push({
13194
+ id: `${stale.id}-${target}`,
13195
+ status: "fail",
13196
+ message: `Stale ${stale.pattern} reference in ${file}`
13197
+ });
13198
+ }
13199
+ }
13200
+ }
13201
+ return {
13202
+ ok: checks.every((check) => check.status === "pass"),
13203
+ checks
13204
+ };
13205
+ }
13206
+ function formatDoctorAgentsReport(report) {
13207
+ const lines = ["VibeRaven agent injection doctor", ""];
13208
+ for (const check of report.checks) {
13209
+ const icon = check.status === "pass" ? "\u2713" : "\u2717";
13210
+ lines.push(`${icon} ${check.message}`);
13211
+ }
13212
+ lines.push("");
13213
+ lines.push(report.ok ? "All agent injection checks passed." : "Agent injection checks failed.");
13214
+ return lines.join("\n");
13215
+ }
13216
+ async function fileExists(path) {
13217
+ try {
13218
+ await (0, import_promises16.access)(path);
13219
+ return true;
13220
+ } catch {
13221
+ return false;
13222
+ }
13223
+ }
13224
+
13225
+ // src/commands/runDoctor.ts
13226
+ async function runDoctorAgentsCommand(options) {
13227
+ const report = await checkAgentInjection(options.cwd);
13228
+ console.log(formatDoctorAgentsReport(report));
13229
+ return report.ok ? 0 : 1;
13230
+ }
13231
+
13232
+ // src/output/nextActionBlock.ts
13233
+ function buildNextActionBlock(tasks, loopState, plan) {
13234
+ const batchSize = plan === "pro" ? 10 : 3;
13235
+ const batchApplied = loopState.batchApplied;
13236
+ const remainingInBatch = batchSize - batchApplied;
13237
+ const scanNow = batchApplied >= batchSize;
13238
+ const stalled = loopState.stalledScans >= 2;
13239
+ const stalledScans = loopState.stalledScans;
13240
+ const base = { batchSize, batchApplied, remainingInBatch, scanNow, stalled, stalledScans };
13241
+ if (stalled) {
13242
+ const stallReason = resolveStallReason(tasks);
13243
+ return {
13244
+ ...base,
13245
+ type: "stalled",
13246
+ title: "Loop stalled \u2014 no gap reduction after 2+ consecutive scans",
13247
+ requiresUserAction: true,
13248
+ stallReason
13249
+ };
13250
+ }
13251
+ if (scanNow) {
13252
+ return {
13253
+ ...base,
13254
+ type: "verify",
13255
+ title: "Batch complete \u2014 run verify to rescan before next batch",
13256
+ mcpTool: "viberaven_verify",
13257
+ mcpArgs: {},
13258
+ requiresUserAction: false
13259
+ };
13260
+ }
13261
+ const repoCodeTask = tasks.find((t) => t.fixType === "repo-code" && !t.requiresUserAction);
13262
+ if (repoCodeTask) {
13263
+ return {
13264
+ ...base,
13265
+ type: "repo-code",
13266
+ gapId: repoCodeTask.gapId,
13267
+ title: repoCodeTask.title,
13268
+ mcpTool: repoCodeTask.mcpTool,
13269
+ mcpArgs: repoCodeTask.mcpArgs,
13270
+ fallbackCommand: repoCodeTask.mcpArgs ? `npx -y viberaven --heal --apply --gap ${repoCodeTask.gapId} --yes` : void 0,
13271
+ requiresUserAction: false
13272
+ };
13273
+ }
13274
+ const providerTask = tasks.find((t) => t.fixType === "provider-action");
13275
+ if (providerTask) {
13276
+ return {
13277
+ ...base,
13278
+ type: "provider-action",
13279
+ gapId: providerTask.gapId,
13280
+ title: providerTask.title,
13281
+ requiresUserAction: true
13282
+ };
13283
+ }
13284
+ const upgradeTask = tasks.find((t) => t.fixType === "upgrade-required");
13285
+ if (upgradeTask) {
13286
+ return {
13287
+ ...base,
13288
+ type: "upgrade-required",
13289
+ gapId: upgradeTask.gapId,
13290
+ title: upgradeTask.title,
13291
+ requiresUserAction: true,
13292
+ upgradeUrl: "https://viberaven.dev/pricing"
13293
+ };
13294
+ }
13295
+ return {
13296
+ ...base,
13297
+ type: "done",
13298
+ title: "All gaps resolved \u2014 production gate is clear",
13299
+ requiresUserAction: false
13300
+ };
13301
+ }
13302
+ function resolveStallReason(tasks) {
13303
+ if (tasks.length === 0) return "no-recipes";
13304
+ const allUpgradeOrEmpty = tasks.every((t) => t.fixType === "upgrade-required");
13305
+ if (allUpgradeOrEmpty) return "no-recipes";
13306
+ const allProviderAction = tasks.every((t) => t.fixType === "provider-action");
13307
+ if (allProviderAction) return "provider-action-required";
13308
+ return "unknown";
13309
+ }
13310
+ var NEXT_ACTION_START = "VIBERAVEN_NEXT_ACTION_START";
13311
+ var NEXT_ACTION_END = "VIBERAVEN_NEXT_ACTION_END";
13312
+ var PROVIDER_ACTION_START = "VIBERAVEN_PROVIDER_ACTION_START";
13313
+ var PROVIDER_ACTION_END = "VIBERAVEN_PROVIDER_ACTION_END";
13314
+ function buildProviderActionBlock(task) {
13315
+ if (task.fixType !== "provider-action" || !task.providerAction) {
13316
+ return void 0;
13317
+ }
13318
+ const pa = task.providerAction;
13319
+ return {
13320
+ VIBERAVEN_PROVIDER_ACTION: {
13321
+ gap: task.gapId,
13322
+ provider: pa.provider,
13323
+ dashboardUrl: pa.dashboardUrl,
13324
+ exactStep: pa.exactStep,
13325
+ envKeyName: pa.envKeyName ?? null,
13326
+ envKeyExample: pa.envKeyExample ?? null,
13327
+ doneSignal: pa.doneSignal,
13328
+ verifyCommand: task.verifyCommand,
13329
+ mcpAlternative: task.mcpTool ?? pa.mcpAlternative ?? null
13330
+ }
13331
+ };
13332
+ }
13333
+ function printProviderActionBlock(tasks) {
13334
+ const task = tasks.find(
13335
+ (t) => t.fixType === "provider-action" && t.requiresUserAction === true
13336
+ );
13337
+ if (!task) return;
13338
+ const block = buildProviderActionBlock(task);
13339
+ if (!block) return;
13340
+ console.log(PROVIDER_ACTION_START);
13341
+ console.log(JSON.stringify(block, null, 2));
13342
+ console.log(PROVIDER_ACTION_END);
13343
+ }
13344
+ function printNextActionBlock(block) {
13345
+ console.log(NEXT_ACTION_START);
13346
+ console.log(JSON.stringify(block, null, 2));
13347
+ console.log(NEXT_ACTION_END);
13348
+ }
13349
+
13350
+ // src/providerMcpBridge.ts
13351
+ var import_node_fs10 = require("node:fs");
13352
+ var import_node_os2 = require("node:os");
13353
+ var import_node_path21 = require("node:path");
13354
+ var UPGRADE_URL4 = "https://viberaven.dev/pricing";
13355
+ var FALLBACK_COMMAND = "npx -y viberaven audit --vercel-supabase --json";
13356
+ var SUPPORTED_PROVIDERS = /* @__PURE__ */ new Set(["supabase", "vercel"]);
13357
+ var configPathsOverride;
13358
+ function defaultMcpConfigPaths() {
13359
+ const home = (0, import_node_os2.homedir)();
13360
+ return [
13361
+ (0, import_node_path21.join)(home, ".config", "claude", "claude_desktop_config.json"),
13362
+ (0, import_node_path21.join)(home, ".cursor", "mcp.json"),
13363
+ (0, import_node_path21.join)(home, ".gemini", "antigravity", "mcp_config.json")
13364
+ ];
13365
+ }
13366
+ function resolveConfigPaths() {
13367
+ return configPathsOverride ?? defaultMcpConfigPaths();
13368
+ }
13369
+ function parseMcpServers(raw) {
13370
+ if (!raw || typeof raw !== "object") {
13371
+ return void 0;
13372
+ }
13373
+ const obj = raw;
13374
+ if (obj.mcpServers && typeof obj.mcpServers === "object") {
13375
+ return obj.mcpServers;
13376
+ }
13377
+ if (obj.servers && typeof obj.servers === "object") {
13378
+ return obj.servers;
13379
+ }
13380
+ return void 0;
13381
+ }
13382
+ function findServerEntry(servers, provider2) {
13383
+ if (servers[provider2]) {
13384
+ return servers[provider2];
13385
+ }
13386
+ const key = Object.keys(servers).find((candidate) => candidate.toLowerCase() === provider2);
13387
+ return key ? servers[key] : void 0;
13388
+ }
13389
+ function findProviderMcpConfig(provider2, configPaths) {
13390
+ const paths = configPaths ?? resolveConfigPaths();
13391
+ for (const path of paths) {
13392
+ if (!(0, import_node_fs10.existsSync)(path)) {
13393
+ continue;
13394
+ }
13395
+ try {
13396
+ const raw = JSON.parse((0, import_node_fs10.readFileSync)(path, "utf8"));
13397
+ const servers = parseMcpServers(raw);
13398
+ if (!servers) {
13399
+ continue;
13400
+ }
13401
+ const entry = findServerEntry(servers, provider2);
13402
+ if (!entry || typeof entry !== "object") {
13403
+ continue;
13404
+ }
13405
+ const server = entry;
13406
+ return {
13407
+ command: typeof server.command === "string" ? server.command : void 0,
13408
+ args: Array.isArray(server.args) ? server.args.filter((arg) => typeof arg === "string") : void 0,
13409
+ url: typeof server.url === "string" ? server.url : void 0,
13410
+ source: path
13411
+ };
13412
+ } catch {
13413
+ continue;
13414
+ }
13415
+ }
13416
+ return void 0;
13417
+ }
13418
+ async function verifyProviderGap(options) {
13419
+ if (options.plan !== "pro") {
13420
+ return {
13421
+ verified: false,
13422
+ reason: "pro-required",
13423
+ upgradeUrl: UPGRADE_URL4
13424
+ };
13425
+ }
13426
+ const provider2 = options.provider.toLowerCase().trim();
13427
+ if (!SUPPORTED_PROVIDERS.has(provider2)) {
13428
+ return {
13429
+ verified: false,
13430
+ reason: "unsupported-provider"
13431
+ };
13432
+ }
13433
+ const mcpConfig = findProviderMcpConfig(provider2);
13434
+ if (!mcpConfig) {
13435
+ return {
13436
+ verified: false,
13437
+ mcpUnavailable: true,
13438
+ fallbackCommand: FALLBACK_COMMAND
13439
+ };
13440
+ }
13441
+ return {
13442
+ verified: false,
13443
+ mcpUnavailable: true,
13444
+ fallbackCommand: FALLBACK_COMMAND
13445
+ };
13446
+ }
13447
+
12665
13448
  // src/cli.ts
12666
13449
  function printHelp() {
12667
13450
  console.log(`viberaven ${VERSION} \u2014 launch readiness for AI-built apps
@@ -12720,25 +13503,11 @@ Usage:
12720
13503
  viberaven open [provider|url]
12721
13504
  Open dashboard URL from next action or playbook
12722
13505
 
12723
- viberaven init --agents all [--dry-run]
12724
- Install bounded VibeRaven rules into native agent instruction files
13506
+ viberaven init [--agents all|codex,claude,...] [--dry-run] [path]
13507
+ Install bounded VibeRaven agent rules (${PUBLIC_INIT_ALL_COMMAND})
12725
13508
 
12726
- viberaven audit --vercel-supabase [path]
12727
- Local Vercel/Supabase repo evidence audit (RLS, pooler, service-role boundary)
12728
-
12729
- viberaven clean --plan [path]
12730
- Write a non-destructive context cleanup review plan
12731
-
12732
- viberaven verify [path]
12733
- viberaven --verify [path]
12734
- Rescan after a fix and refresh agent artifacts
12735
-
12736
- viberaven mcp [--help]
12737
- MCP stdio server exposing VibeRaven production-readiness tools
12738
-
12739
- ${PUBLIC_COMMAND}
12740
- ${PUBLIC_VERIFY_COMMAND}
12741
- ${PUBLIC_AUDIT_COMMAND}
13509
+ viberaven doctor --agents [path]
13510
+ Verify agent instruction files and canonical commands
12742
13511
 
12743
13512
 
12744
13513
 
@@ -12807,7 +13576,19 @@ function parseArgs(argv) {
12807
13576
  return { command, flags, positional };
12808
13577
  }
12809
13578
  function isBooleanFlag(command, key) {
12810
- if (["agent-mode", "json", "jsonl", "condense", "heal", "plan", "prompt", "apply", "yes", "no-verify"].includes(key)) {
13579
+ if ([
13580
+ "agent-mode",
13581
+ "json",
13582
+ "jsonl",
13583
+ "condense",
13584
+ "heal",
13585
+ "plan",
13586
+ "prompt",
13587
+ "apply",
13588
+ "yes",
13589
+ "no-verify",
13590
+ "force-scan"
13591
+ ].includes(key)) {
12811
13592
  return true;
12812
13593
  }
12813
13594
  if (key === "strict") return true;
@@ -12815,7 +13596,7 @@ function isBooleanFlag(command, key) {
12815
13596
  if (key === "verify" && command === "") return true;
12816
13597
  if (key === "vercel-supabase" && command === "audit") return true;
12817
13598
  if (key === "dry-run" && command === "init") return true;
12818
- if (key === "plan" && command === "clean") return true;
13599
+ if (key === "agents" && command === "doctor") return true;
12819
13600
  return false;
12820
13601
  }
12821
13602
  function shouldConsumeLeadingHyphenValue(command, key, value) {
@@ -12824,6 +13605,44 @@ function shouldConsumeLeadingHyphenValue(command, key, value) {
12824
13605
  function hasFlag(flags, key) {
12825
13606
  return flags[key] === true || typeof flags[key] === "string";
12826
13607
  }
13608
+ async function guardEarlyVerifyScan(input) {
13609
+ if (input.flags["force-scan"] === true) {
13610
+ return void 0;
13611
+ }
13612
+ const verifyLike = input.flags.verify === true || input.wantsStrict;
13613
+ if (!verifyLike) {
13614
+ return void 0;
13615
+ }
13616
+ const workspacePath = input.positional[0] ? (0, import_node_path22.join)(process.cwd(), input.positional[0]) : await resolveWorkspaceRoot(process.cwd());
13617
+ const loopState = await loadLoopState(workspacePath);
13618
+ if (loopState.batchApplied <= 0) {
13619
+ return void 0;
13620
+ }
13621
+ let artifact;
13622
+ try {
13623
+ artifact = await loadLastArtifact(workspacePath);
13624
+ } catch {
13625
+ return void 0;
13626
+ }
13627
+ const plan = artifact.plan ?? (await loadCredentials())?.plan ?? "free";
13628
+ const batchSize = plan === "pro" ? 10 : 3;
13629
+ if (loopState.batchApplied >= batchSize) {
13630
+ return void 0;
13631
+ }
13632
+ const appliedGapIds = new Set(loopState.appliedGapIdsSinceScan ?? []);
13633
+ const remainingRepoCodeTasks = buildTaskList(artifact).filter(
13634
+ (task) => task.fixType === "repo-code" && task.requiresUserAction === false && !appliedGapIds.has(task.gapId)
13635
+ );
13636
+ if (remainingRepoCodeTasks.length === 0) {
13637
+ return void 0;
13638
+ }
13639
+ const nextTask = remainingRepoCodeTasks[0];
13640
+ console.error("SCAN_DEFERRED: Local heal batch is not full yet, so VibeRaven is protecting scan quota.");
13641
+ console.error(`Batch progress: ${loopState.batchApplied}/${batchSize} local heals applied since the last scan.`);
13642
+ console.error(`Next local heal: npx -y viberaven --heal --apply --gap ${nextTask.gapId} --yes`);
13643
+ console.error("Run verify again after the batch is full, or add --force-scan if the user explicitly wants to spend a scan now.");
13644
+ return 4;
13645
+ }
12827
13646
  function resolveDefaultEntrypointMode(options) {
12828
13647
  if (options.env.VIBERAVEN_TUI === "1") return "interactive";
12829
13648
  if (options.env.VIBERAVEN_AGENT === "1") return "agent-scan";
@@ -12841,13 +13660,36 @@ async function cmdLogout() {
12841
13660
  await clearCredentials();
12842
13661
  console.log("Signed out.");
12843
13662
  }
13663
+ async function cmdProviderVerify(flags, positional) {
13664
+ const provider2 = typeof flags.provider === "string" ? flags.provider : positional[0];
13665
+ const check = typeof flags.check === "string" ? flags.check : positional[1];
13666
+ if (!provider2 || !check) {
13667
+ console.error("Usage: viberaven provider-verify --provider <supabase|vercel> --check <id> [--plan free|pro]");
13668
+ return 1;
13669
+ }
13670
+ let plan = typeof flags.plan === "string" ? flags.plan : "free";
13671
+ if (!flags.plan) {
13672
+ const creds = await loadCredentials();
13673
+ if (creds?.plan === "pro" || creds?.plan === "free") {
13674
+ plan = creds.plan;
13675
+ }
13676
+ }
13677
+ const result = await verifyProviderGap({
13678
+ provider: provider2,
13679
+ check,
13680
+ cwd: process.cwd(),
13681
+ plan
13682
+ });
13683
+ console.log(JSON.stringify(result, null, 2));
13684
+ return result.verified ? 0 : 1;
13685
+ }
12844
13686
  async function cmdStatus(flags, positional) {
12845
13687
  const creds = await loadCredentials();
12846
13688
  if (!creds?.accessToken) {
12847
13689
  console.log("Not signed in. Run: viberaven login");
12848
13690
  return 1;
12849
13691
  }
12850
- const startDir = positional[0] ? (0, import_node_path21.join)(process.cwd(), positional[0]) : process.cwd();
13692
+ const startDir = positional[0] ? (0, import_node_path22.join)(process.cwd(), positional[0]) : process.cwd();
12851
13693
  let artifact;
12852
13694
  try {
12853
13695
  artifact = await loadLastArtifact(startDir);
@@ -13001,7 +13843,7 @@ async function cmdWatch(flags) {
13001
13843
  }
13002
13844
  }
13003
13845
  async function runScanCommand(flags, positional, options) {
13004
- const workspacePath = positional[0] ? (0, import_node_path21.join)(process.cwd(), positional[0]) : await resolveWorkspaceRoot(process.cwd());
13846
+ const workspacePath = positional[0] ? (0, import_node_path22.join)(process.cwd(), positional[0]) : await resolveWorkspaceRoot(process.cwd());
13005
13847
  const apiBaseUrl = resolveApiBaseUrl(typeof flags["api-url"] === "string" ? flags["api-url"] : void 0);
13006
13848
  let accessToken;
13007
13849
  try {
@@ -13009,11 +13851,10 @@ async function runScanCommand(flags, positional, options) {
13009
13851
  } catch (error) {
13010
13852
  const message = error instanceof Error ? error.message : String(error);
13011
13853
  if (message.startsWith(`${LOGIN_REQUIRED}:`)) {
13012
- const loginLog = options?.deferMachineOutput ? console.error : console.log;
13013
- loginLog("LOGIN_REQUIRED: Starting VibeRaven browser sign-in so this scan can continue.");
13014
- loginLog("AGENT_ACTION: Open the VibeRaven approval URL for the user if the browser does not open automatically.");
13854
+ console.log("LOGIN_REQUIRED: Starting VibeRaven browser sign-in so this scan can continue.");
13855
+ console.log("AGENT_ACTION: Open the VibeRaven approval URL for the user if the browser does not open automatically.");
13015
13856
  await runDeviceLogin(apiBaseUrl);
13016
- loginLog("LOGIN_RESUME: Sign-in complete. Continuing the original scan.");
13857
+ console.log("LOGIN_RESUME: Sign-in complete. Continuing the original scan.");
13017
13858
  try {
13018
13859
  ({ accessToken } = await requireCredentials(apiBaseUrl));
13019
13860
  } catch (retryError) {
@@ -13059,6 +13900,17 @@ async function runScanCommand(flags, positional, options) {
13059
13900
  if (artifact.usage && !options?.deferMachineOutput) {
13060
13901
  console.log(formatUsageLine(artifact.usage));
13061
13902
  }
13903
+ if (flags["agent-mode"] && !options?.deferMachineOutput) {
13904
+ const loopState = await loadLoopState(workspacePath);
13905
+ const openGapCount = artifact.gaps.length;
13906
+ const updatedState = resetBatch(loopState, openGapCount);
13907
+ const tasks = buildTaskList(artifact);
13908
+ const plan = artifact.plan ?? "free";
13909
+ const block = buildNextActionBlock(tasks, updatedState, plan);
13910
+ printNextActionBlock(block);
13911
+ printProviderActionBlock(tasks);
13912
+ await saveLoopState(workspacePath, updatedState);
13913
+ }
13062
13914
  if (flags.open) {
13063
13915
  try {
13064
13916
  await openPathInBrowser(paths.reportPath);
@@ -13069,7 +13921,7 @@ async function runScanCommand(flags, positional, options) {
13069
13921
  return { exitCode: 0, artifacts: paths };
13070
13922
  }
13071
13923
  async function cmdReport(flags, positional) {
13072
- const startDir = positional[0] ? (0, import_node_path21.join)(process.cwd(), positional[0]) : process.cwd();
13924
+ const startDir = positional[0] ? (0, import_node_path22.join)(process.cwd(), positional[0]) : process.cwd();
13073
13925
  try {
13074
13926
  const paths = await refreshReportFromDisk(startDir);
13075
13927
  console.log(`Report refreshed: ${paths.reportPath}`);
@@ -13091,7 +13943,7 @@ async function cmdReport(flags, positional) {
13091
13943
  }
13092
13944
  }
13093
13945
  async function cmdPrompt(flags, positional) {
13094
- const startDir = positional[0] ? (0, import_node_path21.join)(process.cwd(), positional[0]) : process.cwd();
13946
+ const startDir = positional[0] ? (0, import_node_path22.join)(process.cwd(), positional[0]) : process.cwd();
13095
13947
  let artifact;
13096
13948
  try {
13097
13949
  artifact = await loadLastArtifact(startDir);
@@ -13174,67 +14026,9 @@ async function cmdStack(positional) {
13174
14026
  console.error(`Unknown stack subcommand "${sub}". Use set, clear, or list.`);
13175
14027
  return 1;
13176
14028
  }
13177
- async function cmdInit(flags, positional) {
13178
- const cwd = process.cwd();
13179
- const dryRun = flags["dry-run"] === true;
13180
- const agentsValue = typeof flags.agents === "string" ? flags.agents : positional[0] === "all" ? "all" : void 0;
13181
- try {
13182
- const targets = getAgentRulesTargets(agentsValue);
13183
- if (dryRun) {
13184
- console.log(renderAgentRulesDryRun(targets));
13185
- return 0;
13186
- }
13187
- const results = await initAgentRules({ cwd, targets });
13188
- for (const result of results) {
13189
- console.log(`${result.action}: ${result.file}`);
13190
- }
13191
- return 0;
13192
- } catch (error) {
13193
- console.error(error instanceof Error ? error.message : String(error));
13194
- return 1;
13195
- }
13196
- }
13197
- function cmdMcp(flags) {
13198
- if (flags.help) {
13199
- console.log("viberaven mcp - MCP stdio server exposing VibeRaven production-readiness tools.");
13200
- console.log("");
13201
- console.log("Usage: viberaven mcp");
13202
- console.log(` ${PUBLIC_COMMAND} mcp`);
13203
- return 0;
13204
- }
13205
- runMcpServer();
13206
- return 0;
13207
- }
13208
- async function cmdAudit(flags, positional) {
13209
- const cwd = positional[0] ? (0, import_node_path21.join)(process.cwd(), positional[0]) : process.cwd();
13210
- if (!flags["vercel-supabase"]) {
13211
- console.error("Usage: viberaven audit --vercel-supabase [path]");
13212
- return 1;
13213
- }
13214
- const input = await collectVercelSupabaseAuditInput(cwd);
13215
- const result = buildVercelSupabaseAudit(input);
13216
- if (flags.json) {
13217
- console.log(JSON.stringify(result, null, 2));
13218
- } else {
13219
- console.log(renderVercelSupabaseAudit(result));
13220
- }
13221
- return result.status === "pass" ? 0 : 1;
13222
- }
13223
- async function cmdClean(flags, positional) {
13224
- const cwd = positional[0] ? (0, import_node_path21.join)(process.cwd(), positional[0]) : process.cwd();
13225
- if (!flags.plan) {
13226
- console.error("Usage: viberaven clean --plan [path]");
13227
- return 1;
13228
- }
13229
- const files = await collectCleanupFiles(cwd);
13230
- const plan = buildContextCleanupPlan({ projectRoot: cwd, files });
13231
- const outputPath = await writeCleanupPlan(cwd, plan);
13232
- console.log(`Wrote ${outputPath}`);
13233
- return 0;
13234
- }
13235
14029
  async function main() {
13236
14030
  const { command, flags, positional } = parseArgs(process.argv.slice(2));
13237
- if (flags.help && command !== "mcp") {
14031
+ if (flags.help) {
13238
14032
  printHelp();
13239
14033
  return 0;
13240
14034
  }
@@ -13247,7 +14041,7 @@ async function main() {
13247
14041
  const wantsJsonl = hasFlag(flags, "jsonl");
13248
14042
  const wantsStrict = hasFlag(flags, "strict");
13249
14043
  if (flags.condense) {
13250
- const cwd = positional[0] ? (0, import_node_path21.join)(process.cwd(), positional[0]) : process.cwd();
14044
+ const cwd = positional[0] ? (0, import_node_path22.join)(process.cwd(), positional[0]) : process.cwd();
13251
14045
  const result = await runCondenseCommand({ cwd });
13252
14046
  console.log(`VibeRaven context map refreshed: ${result.contextMapPath}`);
13253
14047
  return 0;
@@ -13265,30 +14059,28 @@ async function main() {
13265
14059
  console.log(JSON.stringify(result, null, 2));
13266
14060
  return result.status.startsWith("refused") || result.status === "failed" ? 1 : 0;
13267
14061
  }
13268
- if (!command && (isAgentMode || wantsJson || wantsJsonl || wantsStrict)) {
14062
+ if (!command && (isAgentMode || flags.verify === true || wantsJson || wantsJsonl || wantsStrict)) {
14063
+ const guardedExitCode = await guardEarlyVerifyScan({ flags, positional, wantsStrict });
14064
+ if (guardedExitCode !== void 0) {
14065
+ return guardedExitCode;
14066
+ }
13269
14067
  const deferMachineOutput = wantsJson || wantsJsonl;
13270
14068
  const scanResult = await runScanCommand(flags, positional, { deferMachineOutput });
13271
14069
  if ((wantsJson || wantsJsonl) && !scanResult.artifacts) {
13272
14070
  console.error("VibeRaven could not produce machine output because scan artifacts were not written.");
13273
14071
  return 3;
13274
14072
  }
13275
- let strictExitCode;
13276
- if (wantsStrict && scanResult.artifacts) {
13277
- const gateResult = JSON.parse(await (0, import_promises15.readFile)(scanResult.artifacts.gateResultPath, "utf8"));
13278
- const failOnWarnings = flags.strict === "warning";
13279
- strictExitCode = exitCodeForStrictGate(gateResult, { failOnWarnings });
13280
- }
13281
- if (wantsJson && scanResult.artifacts) {
13282
- const gateResult = JSON.parse(await (0, import_promises15.readFile)(scanResult.artifacts.gateResultPath, "utf8"));
14073
+ const gateResult = scanResult.artifacts && (wantsJson || wantsJsonl || wantsStrict) ? JSON.parse(await (0, import_promises17.readFile)(scanResult.artifacts.gateResultPath, "utf8")) : void 0;
14074
+ const strictExitCode = wantsStrict && gateResult ? exitCodeForStrictGate(gateResult, { failOnWarnings: flags.strict === "warning" }) : scanResult.exitCode;
14075
+ if (wantsJson && gateResult) {
13283
14076
  process.stdout.write(renderGateResultJson(gateResult));
13284
- return strictExitCode ?? scanResult.exitCode;
14077
+ return strictExitCode;
13285
14078
  }
13286
- if (wantsJsonl && scanResult.artifacts) {
13287
- const gateResult = JSON.parse(await (0, import_promises15.readFile)(scanResult.artifacts.gateResultPath, "utf8"));
14079
+ if (wantsJsonl && gateResult) {
13288
14080
  process.stdout.write(renderJsonlEvents(gateResult));
13289
- return strictExitCode ?? scanResult.exitCode;
14081
+ return strictExitCode;
13290
14082
  }
13291
- if (strictExitCode !== void 0) {
14083
+ if (wantsStrict && gateResult) {
13292
14084
  return strictExitCode;
13293
14085
  }
13294
14086
  return scanResult.exitCode;
@@ -13313,7 +14105,7 @@ async function main() {
13313
14105
  case "next":
13314
14106
  return runNextCommand({
13315
14107
  json: Boolean(flags.json),
13316
- cwd: positional[0] ? (0, import_node_path21.join)(process.cwd(), positional[0]) : process.cwd()
14108
+ cwd: positional[0] ? (0, import_node_path22.join)(process.cwd(), positional[0]) : process.cwd()
13317
14109
  });
13318
14110
  case "guide": {
13319
14111
  const provider2 = positional[0];
@@ -13348,18 +14140,25 @@ async function main() {
13348
14140
  return cmdPrompt(flags, positional);
13349
14141
  case "stack":
13350
14142
  return cmdStack(positional);
13351
- case "init":
13352
- return cmdInit(flags, positional);
13353
- case "mcp":
13354
- return cmdMcp(flags);
13355
- case "audit":
13356
- return cmdAudit(flags, positional);
13357
- case "clean":
13358
- return cmdClean(flags, positional);
13359
- case "verify": {
13360
- const scanResult = await runScanCommand({ ...flags, verify: true }, positional);
13361
- return scanResult.exitCode;
14143
+ case "provider-verify":
14144
+ return cmdProviderVerify(flags, positional);
14145
+ case "init": {
14146
+ const cwd = positional[0] ? (0, import_node_path22.join)(process.cwd(), positional[0]) : process.cwd();
14147
+ const agents = typeof flags.agents === "string" ? flags.agents : void 0;
14148
+ return runInitCommand({
14149
+ cwd,
14150
+ agents,
14151
+ dryRun: flags["dry-run"] === true
14152
+ });
13362
14153
  }
14154
+ case "doctor":
14155
+ if (flags.agents !== true) {
14156
+ console.error("Usage: viberaven doctor --agents [path]");
14157
+ return 1;
14158
+ }
14159
+ return runDoctorAgentsCommand({
14160
+ cwd: positional[0] ? (0, import_node_path22.join)(process.cwd(), positional[0]) : process.cwd()
14161
+ });
13363
14162
  default:
13364
14163
  console.error(`Unknown command: ${command}`);
13365
14164
  printHelp();