@viberaven/cli 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -170,7 +170,7 @@ __export(cli_exports, {
170
170
  runScanCommand: () => runScanCommand
171
171
  });
172
172
  module.exports = __toCommonJS(cli_exports);
173
- var import_promises15 = require("node:fs/promises");
173
+ var import_promises16 = require("node:fs/promises");
174
174
  var import_node_path21 = require("node:path");
175
175
 
176
176
  // src/config.ts
@@ -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}`;
@@ -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)) {
@@ -11406,7 +11515,30 @@ var AGENT_RULES_BODY = `## VibeRaven Production-Readiness Gate
11406
11515
  - Cleanup is non-destructive: only run \`${PUBLIC_CLEAN_PLAN_COMMAND}\` to create a reviewable cleanup plan.
11407
11516
  - 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
11517
  - 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.`;
11518
+ - 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.
11519
+
11520
+ ## VibeRaven Production Copilot Loop
11521
+
11522
+ After every scan, read \`.viberaven/agent-tasklist.md\` for the prioritized task list.
11523
+
11524
+ The loop is:
11525
+
11526
+ 1. Read \`VIBERAVEN_NEXT_ACTION\` from scan output for \`batchSize\`, \`batchApplied\`, \`scanNow\`, and \`stalled\`.
11527
+ 2. If \`stalled: true\`, stop calling verify \u2014 address provider-action gaps or report to the user.
11528
+ 3. If \`scanNow: true\`, call \`viberaven_verify\` (or run verify command) before applying more heals.
11529
+ 4. For each repo-code task where \`requiresUserAction: false\` (up to \`batchSize\` per batch):
11530
+ - Call MCP: \`viberaven_heal_apply { gap: "<gapId>", yes: true }\`
11531
+ - Or run heal apply command
11532
+ 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.
11533
+ 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.
11534
+ 7. Repeat until \`gate.status === 'clear'\` in \`.viberaven/gate-result.json\`.
11535
+ 8. If \`requiresUserAction: true\`:
11536
+ - Read \`VIBERAVEN_PROVIDER_ACTION\` from terminal output.
11537
+ - Present \`dashboardUrl\` and \`exactStep\` to the user.
11538
+ - Wait for user confirmation, then verify.
11539
+ 9. If \`fixType === 'upgrade-required'\`, provide upgradeUrl and move on.
11540
+
11541
+ Do not stop at "scan complete." The loop is not done until \`gate.status === 'clear'\`.`;
11410
11542
  function buildAgentRulesBlock() {
11411
11543
  return [VIBERAVEN_AGENT_RULES_START, AGENT_RULES_BODY, VIBERAVEN_AGENT_RULES_END].join("\n");
11412
11544
  }
@@ -11521,7 +11653,6 @@ var AGENT_RULE_TARGET_ALIAS_ENTRIES = ALL_AGENT_RULE_TARGETS.flatMap((target) =>
11521
11653
  ...(AGENT_RULE_TARGETS[target].aliases ?? []).map((alias) => [alias, target])
11522
11654
  ]);
11523
11655
  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";
11525
11656
  function renderAgentRulesForTarget(target) {
11526
11657
  if (target === "claude") {
11527
11658
  return ["@AGENTS.md", "", buildAgentRulesBlock()].join("\n");
@@ -11539,27 +11670,6 @@ function renderAgentRulesForTarget(target) {
11539
11670
  }
11540
11671
  return buildAgentRulesBlock();
11541
11672
  }
11542
- function validAgentRulesTargetText() {
11543
- return VALID_AGENT_RULES_TARGET_TEXT;
11544
- }
11545
- function getAgentRulesTargets(value) {
11546
- if (value === void 0 || value.trim() === "" || value.trim().toLowerCase() === "all") {
11547
- return [...ALL_AGENT_RULE_TARGETS];
11548
- }
11549
- const requested = value.split(",").map((target) => target.trim().toLowerCase()).filter(Boolean);
11550
- if (requested.length === 0 || requested.includes("all")) {
11551
- return [...ALL_AGENT_RULE_TARGETS];
11552
- }
11553
- const resolved = requested.map((target) => {
11554
- const canonicalTarget = AGENT_RULE_TARGET_ALIASES.get(target);
11555
- if (!canonicalTarget) {
11556
- throw new Error(`Unknown agent rules target "${target}". Valid targets: ${validAgentRulesTargetText()}.`);
11557
- }
11558
- return canonicalTarget;
11559
- });
11560
- const requestedTargets = new Set(resolved);
11561
- return ALL_AGENT_RULE_TARGETS.filter((target) => requestedTargets.has(target));
11562
- }
11563
11673
 
11564
11674
  // src/commands/initRules.ts
11565
11675
  async function initAgentRules(options) {
@@ -11579,15 +11689,6 @@ async function initAgentRules(options) {
11579
11689
  }
11580
11690
  return results;
11581
11691
  }
11582
- function renderAgentRulesDryRun(targets) {
11583
- const files = targets.map((target) => `- ${target}: ${AGENT_RULE_TARGETS[target].file}`).join("\n");
11584
- const previews = targets.flatMap((target) => [
11585
- `Preview: ${target} (${AGENT_RULE_TARGETS[target].file})`,
11586
- "",
11587
- renderAgentRulesForTarget(target)
11588
- ]);
11589
- return [`VibeRaven agent rules dry run`, "", `Target files:`, files, "", ...previews].join("\n");
11590
- }
11591
11692
  async function readExistingFile(path) {
11592
11693
  try {
11593
11694
  return { exists: true, content: await (0, import_promises8.readFile)(path, "utf-8") };
@@ -11977,8 +12078,9 @@ async function runCondenseCommand(options) {
11977
12078
  }
11978
12079
 
11979
12080
  // src/heal/apply.ts
11980
- var import_promises10 = require("node:fs/promises");
11981
- var import_node_path15 = require("node:path");
12081
+ var import_promises11 = require("node:fs/promises");
12082
+ var import_node_fs8 = require("node:fs");
12083
+ var import_node_path16 = require("node:path");
11982
12084
 
11983
12085
  // src/heal/pathSafety.ts
11984
12086
  var import_node_path14 = require("node:path");
@@ -12008,6 +12110,507 @@ function applyEmptyCatchRecipe(source) {
12008
12110
  return { changed: output !== source, output };
12009
12111
  }
12010
12112
 
12113
+ // src/heal/recipes/index.ts
12114
+ var import_node_fs7 = require("node:fs");
12115
+ var import_promises10 = require("node:fs/promises");
12116
+ var import_node_path15 = require("node:path");
12117
+
12118
+ // src/heal/recipes/envAuthSecret.ts
12119
+ function applyAuthSecretRecipe(source) {
12120
+ if (/^\s*NEXTAUTH_SECRET\s*=/m.test(source)) {
12121
+ return { changed: false, output: source, canAutoApply: true };
12122
+ }
12123
+ const line = "NEXTAUTH_SECRET=<generate with: openssl rand -base64 32>";
12124
+ const output = source.trimEnd() ? `${source.trimEnd()}
12125
+ ${line}
12126
+ ` : `${line}
12127
+ `;
12128
+ return { changed: true, output, canAutoApply: true };
12129
+ }
12130
+
12131
+ // src/heal/recipes/envNodeEnv.ts
12132
+ function applyNodeEnvRecipe(source) {
12133
+ if (/^\s*NODE_ENV\s*=/m.test(source)) {
12134
+ return { changed: false, output: source, canAutoApply: true };
12135
+ }
12136
+ const line = "NODE_ENV=production";
12137
+ const output = source.trimEnd() ? `${source.trimEnd()}
12138
+ ${line}
12139
+ ` : `${line}
12140
+ `;
12141
+ return { changed: true, output, canAutoApply: true };
12142
+ }
12143
+
12144
+ // src/heal/recipes/envDatabaseUrl.ts
12145
+ function applyDatabaseUrlRecipe(source) {
12146
+ if (/^\s*DATABASE_URL\s*=/m.test(source)) {
12147
+ return { changed: false, output: source, canAutoApply: true };
12148
+ }
12149
+ const lines = [
12150
+ "# Get this from: https://supabase.com/dashboard/project/<ref>/settings/database",
12151
+ "DATABASE_URL=<your-supabase-postgres-url>"
12152
+ ].join("\n");
12153
+ const output = source.trimEnd() ? `${source.trimEnd()}
12154
+ ${lines}
12155
+ ` : `${lines}
12156
+ `;
12157
+ return { changed: true, output, canAutoApply: true };
12158
+ }
12159
+
12160
+ // src/heal/recipes/nextjsErrorBoundary.ts
12161
+ var ERROR_BOUNDARY_CONTENT = `'use client';
12162
+
12163
+ import { useEffect } from 'react';
12164
+
12165
+ interface ErrorBoundaryProps {
12166
+ error: Error & { digest?: string };
12167
+ reset: () => void;
12168
+ }
12169
+
12170
+ export default function ErrorBoundary({ error, reset }: ErrorBoundaryProps) {
12171
+ useEffect(() => {
12172
+ // Log to an error reporting service
12173
+ console.error('[VibeRaven] Unhandled error:', error);
12174
+ }, [error]);
12175
+
12176
+ return (
12177
+ <main className="flex min-h-screen flex-col items-center justify-center p-4">
12178
+ <h1 className="text-2xl font-bold mb-4">Something went wrong</h1>
12179
+ {error.digest && (
12180
+ <p className="text-sm text-gray-500 mb-4">Error ID: {error.digest}</p>
12181
+ )}
12182
+ <button
12183
+ onClick={reset}
12184
+ className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition"
12185
+ >
12186
+ Try again
12187
+ </button>
12188
+ </main>
12189
+ );
12190
+ }
12191
+ `;
12192
+ function applyErrorBoundaryRecipe(source) {
12193
+ if (source.trim().length > 0) {
12194
+ return {
12195
+ changed: false,
12196
+ output: source,
12197
+ targetFile: "app/error.tsx",
12198
+ canAutoApply: true
12199
+ };
12200
+ }
12201
+ return {
12202
+ changed: true,
12203
+ output: ERROR_BOUNDARY_CONTENT,
12204
+ targetFile: "app/error.tsx",
12205
+ canAutoApply: true
12206
+ };
12207
+ }
12208
+
12209
+ // src/heal/recipes/nextjsHealthRoute.ts
12210
+ var HEALTH_ROUTE_CONTENT = `import { NextResponse } from 'next/server';
12211
+
12212
+ /**
12213
+ * GET /api/health
12214
+ * Simple health-check endpoint \u2014 returns { status: 'ok', ts: <timestamp> }.
12215
+ * Created by VibeRaven heal recipe (missing_health_route).
12216
+ */
12217
+ export function GET(): NextResponse {
12218
+ return NextResponse.json({ status: 'ok', ts: Date.now() });
12219
+ }
12220
+ `;
12221
+ function applyHealthRouteRecipe(source) {
12222
+ if (source.trim().length > 0) {
12223
+ return {
12224
+ changed: false,
12225
+ output: source,
12226
+ targetFile: "app/api/health/route.ts",
12227
+ canAutoApply: true
12228
+ };
12229
+ }
12230
+ return {
12231
+ changed: true,
12232
+ output: HEALTH_ROUTE_CONTENT,
12233
+ targetFile: "app/api/health/route.ts",
12234
+ canAutoApply: true
12235
+ };
12236
+ }
12237
+
12238
+ // src/heal/recipes/nextjsLoadingState.ts
12239
+ var LOADING_CONTENT = `/**
12240
+ * Next.js App Router loading skeleton.
12241
+ * Created by VibeRaven heal recipe (missing_loading_state).
12242
+ */
12243
+ export default function Loading() {
12244
+ return (
12245
+ <main className="flex min-h-screen flex-col items-center justify-center p-4">
12246
+ <div className="animate-pulse space-y-4 w-full max-w-md">
12247
+ <div className="h-8 bg-gray-200 rounded w-3/4" />
12248
+ <div className="h-4 bg-gray-200 rounded w-full" />
12249
+ <div className="h-4 bg-gray-200 rounded w-5/6" />
12250
+ <div className="h-4 bg-gray-200 rounded w-4/6" />
12251
+ <div className="h-10 bg-gray-200 rounded w-1/3" />
12252
+ </div>
12253
+ </main>
12254
+ );
12255
+ }
12256
+ `;
12257
+ function applyLoadingStateRecipe(source) {
12258
+ if (source.trim().length > 0) {
12259
+ return {
12260
+ changed: false,
12261
+ output: source,
12262
+ targetFile: "app/loading.tsx",
12263
+ canAutoApply: true
12264
+ };
12265
+ }
12266
+ return {
12267
+ changed: true,
12268
+ output: LOADING_CONTENT,
12269
+ targetFile: "app/loading.tsx",
12270
+ canAutoApply: true
12271
+ };
12272
+ }
12273
+
12274
+ // src/heal/recipes/nextjsNotFound.ts
12275
+ var NOT_FOUND_CONTENT = `import Link from 'next/link';
12276
+
12277
+ /**
12278
+ * Next.js App Router 404 not-found page.
12279
+ * Created by VibeRaven heal recipe (missing_404_page).
12280
+ */
12281
+ export default function NotFound() {
12282
+ return (
12283
+ <main className="flex min-h-screen flex-col items-center justify-center p-4">
12284
+ <h1 className="text-6xl font-bold text-gray-300 mb-4">404</h1>
12285
+ <h2 className="text-2xl font-semibold mb-2">Page Not Found</h2>
12286
+ <p className="text-gray-500 mb-8">
12287
+ The page you are looking for does not exist or has been moved.
12288
+ </p>
12289
+ <Link
12290
+ href="/"
12291
+ className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition"
12292
+ >
12293
+ Go home
12294
+ </Link>
12295
+ </main>
12296
+ );
12297
+ }
12298
+ `;
12299
+ function applyNotFoundRecipe(source) {
12300
+ if (source.trim().length > 0) {
12301
+ return {
12302
+ changed: false,
12303
+ output: source,
12304
+ targetFile: "app/not-found.tsx",
12305
+ canAutoApply: true
12306
+ };
12307
+ }
12308
+ return {
12309
+ changed: true,
12310
+ output: NOT_FOUND_CONTENT,
12311
+ targetFile: "app/not-found.tsx",
12312
+ canAutoApply: true
12313
+ };
12314
+ }
12315
+
12316
+ // src/heal/recipes/supabaseRls.ts
12317
+ function applyRlsRecipe(_source) {
12318
+ return {
12319
+ changed: false,
12320
+ output: _source,
12321
+ canAutoApply: false,
12322
+ reason: "dashboard-only",
12323
+ providerAction: {
12324
+ provider: "supabase",
12325
+ dashboardUrl: "https://supabase.com/dashboard/project/{{PROJECT_REF}}/auth/policies",
12326
+ exactStep: 'Enable Row Level Security (RLS) on each table under the "Table Editor" or "Auth > Policies" section.',
12327
+ doneSignal: "RLS toggle is green for all public tables",
12328
+ verifyCommand: "npx -y @viberaven/cli audit --vercel-supabase --json"
12329
+ }
12330
+ };
12331
+ }
12332
+
12333
+ // src/heal/recipes/nextjsCspHeader.ts
12334
+ var CSP_HEADER_VALUE = [
12335
+ "default-src 'self'",
12336
+ "script-src 'self' 'unsafe-inline' 'unsafe-eval'",
12337
+ "style-src 'self' 'unsafe-inline'",
12338
+ "img-src 'self' data: blob: https:",
12339
+ "font-src 'self' data:",
12340
+ "connect-src 'self' https:",
12341
+ "frame-ancestors 'none'"
12342
+ ].join("; ");
12343
+ var HEADERS_BLOCK = `
12344
+ async headers() {
12345
+ return [
12346
+ {
12347
+ source: '/(.*)',
12348
+ headers: [
12349
+ {
12350
+ key: 'Content-Security-Policy',
12351
+ value: \`${CSP_HEADER_VALUE}\`,
12352
+ },
12353
+ ],
12354
+ },
12355
+ ];
12356
+ },`;
12357
+ function applyCspHeaderRecipe(source) {
12358
+ if (/Content-Security-Policy/i.test(source)) {
12359
+ return { changed: false, output: source, canAutoApply: true };
12360
+ }
12361
+ 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);
12362
+ if (exportsFn) {
12363
+ return {
12364
+ changed: false,
12365
+ output: source,
12366
+ canAutoApply: false,
12367
+ reason: "config-exports-function"
12368
+ };
12369
+ }
12370
+ if (/\bheaders\s*\(/.test(source)) {
12371
+ return {
12372
+ changed: false,
12373
+ output: source,
12374
+ canAutoApply: false,
12375
+ reason: "headers-already-defined"
12376
+ };
12377
+ }
12378
+ const inlineConfigMatch = /(?:const|let|var)\s+nextConfig\s*=\s*\{[\s\S]*?\}(?=\s*;)/.exec(source);
12379
+ if (inlineConfigMatch) {
12380
+ const closingBrace = inlineConfigMatch.index + inlineConfigMatch[0].lastIndexOf("}");
12381
+ const output2 = source.slice(0, closingBrace) + "," + HEADERS_BLOCK + source.slice(closingBrace);
12382
+ return { changed: true, output: output2, canAutoApply: true };
12383
+ }
12384
+ const moduleExportsMatch = /module\.exports\s*=\s*\{[\s\S]*?\}(?=\s*;?)/.exec(source);
12385
+ if (moduleExportsMatch) {
12386
+ const closingBrace = moduleExportsMatch.index + moduleExportsMatch[0].lastIndexOf("}");
12387
+ const output2 = source.slice(0, closingBrace) + "," + HEADERS_BLOCK + source.slice(closingBrace);
12388
+ return { changed: true, output: output2, canAutoApply: true };
12389
+ }
12390
+ const lastBrace = source.lastIndexOf("\n}");
12391
+ if (lastBrace === -1) {
12392
+ return {
12393
+ changed: false,
12394
+ output: source,
12395
+ canAutoApply: false,
12396
+ reason: "cannot-locate-config-closing-brace"
12397
+ };
12398
+ }
12399
+ const output = source.slice(0, lastBrace) + "," + HEADERS_BLOCK + source.slice(lastBrace);
12400
+ return { changed: true, output, canAutoApply: true };
12401
+ }
12402
+
12403
+ // src/heal/recipes/nextjsRateLimit.ts
12404
+ var DEPENDENCY_HINT = "If you see @upstash/ratelimit references in this file, run: npm install @upstash/ratelimit @upstash/redis";
12405
+ var UPSTASH_MIDDLEWARE = `import { NextResponse } from 'next/server';
12406
+ import type { NextRequest } from 'next/server';
12407
+ import { Ratelimit } from '@upstash/ratelimit';
12408
+ import { Redis } from '@upstash/redis';
12409
+
12410
+ // VibeRaven heal: missing_rate_limit (Upstash)
12411
+ // Configure UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN in .env.local
12412
+ const ratelimit = new Ratelimit({
12413
+ redis: Redis.fromEnv(),
12414
+ limiter: Ratelimit.slidingWindow(60, '1 m'), // 60 requests per minute per IP
12415
+ analytics: false,
12416
+ });
12417
+
12418
+ export async function middleware(request: NextRequest): Promise<NextResponse> {
12419
+ const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? '127.0.0.1';
12420
+ const { success } = await ratelimit.limit(ip);
12421
+
12422
+ if (!success) {
12423
+ return new NextResponse('Too Many Requests', { status: 429 });
12424
+ }
12425
+
12426
+ return NextResponse.next();
12427
+ }
12428
+
12429
+ export const config = {
12430
+ matcher: ['/api/:path*'],
12431
+ };
12432
+ `;
12433
+ var INMEMORY_MIDDLEWARE = `import { NextResponse } from 'next/server';
12434
+ import type { NextRequest } from 'next/server';
12435
+
12436
+ // VibeRaven heal: missing_rate_limit (in-memory fallback \u2014 resets on cold start)
12437
+ // For production, replace with @upstash/ratelimit + Redis.
12438
+ const WINDOW_MS = 60_000; // 1 minute
12439
+ const MAX_REQUESTS = 60; // per IP per window
12440
+
12441
+ const ipMap = new Map<string, { count: number; windowStart: number }>();
12442
+
12443
+ function isRateLimited(ip: string): boolean {
12444
+ const now = Date.now();
12445
+ const entry = ipMap.get(ip);
12446
+
12447
+ if (!entry || now - entry.windowStart > WINDOW_MS) {
12448
+ ipMap.set(ip, { count: 1, windowStart: now });
12449
+ return false;
12450
+ }
12451
+
12452
+ entry.count += 1;
12453
+ return entry.count > MAX_REQUESTS;
12454
+ }
12455
+
12456
+ export function middleware(request: NextRequest): NextResponse {
12457
+ const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? '127.0.0.1';
12458
+
12459
+ if (isRateLimited(ip)) {
12460
+ return new NextResponse('Too Many Requests', { status: 429 });
12461
+ }
12462
+
12463
+ return NextResponse.next();
12464
+ }
12465
+
12466
+ export const config = {
12467
+ matcher: ['/api/:path*'],
12468
+ };
12469
+ `;
12470
+ function applyRateLimitRecipe(source, hasUpstash) {
12471
+ if (/ratelimit|rate.limit|ipMap/i.test(source) || /429/.test(source)) {
12472
+ return {
12473
+ changed: false,
12474
+ output: source,
12475
+ canAutoApply: true,
12476
+ usedUpstash: false
12477
+ };
12478
+ }
12479
+ if (hasUpstash) {
12480
+ return {
12481
+ changed: true,
12482
+ output: UPSTASH_MIDDLEWARE,
12483
+ canAutoApply: true,
12484
+ usedUpstash: true,
12485
+ dependencyHint: DEPENDENCY_HINT
12486
+ };
12487
+ }
12488
+ return {
12489
+ changed: true,
12490
+ output: INMEMORY_MIDDLEWARE,
12491
+ canAutoApply: true,
12492
+ usedUpstash: false
12493
+ };
12494
+ }
12495
+
12496
+ // src/heal/recipes/index.ts
12497
+ function defaultTargetFile(gapId) {
12498
+ const map = {
12499
+ auth_secret_missing: ".env.local",
12500
+ node_env_not_set: ".env.local",
12501
+ database_url_missing: ".env.local",
12502
+ missing_error_boundary: "app/error.tsx",
12503
+ missing_health_route: "app/api/health/route.ts",
12504
+ missing_loading_state: "app/loading.tsx",
12505
+ missing_404_page: "app/not-found.tsx",
12506
+ rls_disabled: "",
12507
+ // no file — provider-action
12508
+ missing_csp_header: "next.config.js",
12509
+ missing_rate_limit: "middleware.ts"
12510
+ };
12511
+ return map[gapId];
12512
+ }
12513
+ async function readSourceOrEmpty(absolutePath) {
12514
+ if (!(0, import_node_fs7.existsSync)(absolutePath)) return "";
12515
+ return (0, import_promises10.readFile)(absolutePath, "utf8");
12516
+ }
12517
+ async function detectUpstash(cwd) {
12518
+ try {
12519
+ const pkgPath = (0, import_node_path15.join)(cwd, "package.json");
12520
+ if (!(0, import_node_fs7.existsSync)(pkgPath)) return false;
12521
+ const raw = await (0, import_promises10.readFile)(pkgPath, "utf8");
12522
+ const pkg = JSON.parse(raw);
12523
+ const deps = {
12524
+ ...pkg["dependencies"] ?? {},
12525
+ ...pkg["devDependencies"] ?? {}
12526
+ };
12527
+ return "@upstash/ratelimit" in deps;
12528
+ } catch {
12529
+ return false;
12530
+ }
12531
+ }
12532
+ async function dispatchRecipeByGapId(gapId, cwd, explicitTarget) {
12533
+ const targetFile = explicitTarget ?? defaultTargetFile(gapId);
12534
+ if (targetFile === void 0) return null;
12535
+ if (gapId === "auth_secret_missing" || gapId === "node_env_not_set" || gapId === "database_url_missing") {
12536
+ const absolutePath = (0, import_node_path15.join)(cwd, targetFile);
12537
+ const source = await readSourceOrEmpty(absolutePath);
12538
+ let result;
12539
+ if (gapId === "auth_secret_missing") result = applyAuthSecretRecipe(source);
12540
+ else if (gapId === "node_env_not_set") result = applyNodeEnvRecipe(source);
12541
+ else result = applyDatabaseUrlRecipe(source);
12542
+ return {
12543
+ ...result,
12544
+ targetFile,
12545
+ canAutoApply: true,
12546
+ recipeName: gapId
12547
+ };
12548
+ }
12549
+ if (gapId === "missing_error_boundary") {
12550
+ const absolutePath = (0, import_node_path15.join)(cwd, "app/error.tsx");
12551
+ const source = await readSourceOrEmpty(absolutePath);
12552
+ const result = applyErrorBoundaryRecipe(source);
12553
+ return { ...result, canAutoApply: true, recipeName: gapId };
12554
+ }
12555
+ if (gapId === "missing_health_route") {
12556
+ const absolutePath = (0, import_node_path15.join)(cwd, "app/api/health/route.ts");
12557
+ const source = await readSourceOrEmpty(absolutePath);
12558
+ const result = applyHealthRouteRecipe(source);
12559
+ return { ...result, canAutoApply: true, recipeName: gapId };
12560
+ }
12561
+ if (gapId === "missing_loading_state") {
12562
+ const absolutePath = (0, import_node_path15.join)(cwd, "app/loading.tsx");
12563
+ const source = await readSourceOrEmpty(absolutePath);
12564
+ const result = applyLoadingStateRecipe(source);
12565
+ return { ...result, canAutoApply: true, recipeName: gapId };
12566
+ }
12567
+ if (gapId === "missing_404_page") {
12568
+ const absolutePath = (0, import_node_path15.join)(cwd, "app/not-found.tsx");
12569
+ const source = await readSourceOrEmpty(absolutePath);
12570
+ const result = applyNotFoundRecipe(source);
12571
+ return { ...result, canAutoApply: true, recipeName: gapId };
12572
+ }
12573
+ if (gapId === "rls_disabled") {
12574
+ const result = applyRlsRecipe("");
12575
+ return {
12576
+ changed: false,
12577
+ output: "",
12578
+ targetFile: "",
12579
+ canAutoApply: false,
12580
+ reason: result.reason,
12581
+ recipeName: gapId
12582
+ };
12583
+ }
12584
+ if (gapId === "missing_csp_header") {
12585
+ let configFile = "next.config.js";
12586
+ let absolutePath = (0, import_node_path15.join)(cwd, configFile);
12587
+ if (!(0, import_node_fs7.existsSync)(absolutePath)) {
12588
+ configFile = "next.config.mjs";
12589
+ absolutePath = (0, import_node_path15.join)(cwd, configFile);
12590
+ }
12591
+ const source = await readSourceOrEmpty(absolutePath);
12592
+ const result = applyCspHeaderRecipe(source);
12593
+ return {
12594
+ ...result,
12595
+ targetFile: configFile,
12596
+ recipeName: gapId
12597
+ };
12598
+ }
12599
+ if (gapId === "missing_rate_limit") {
12600
+ const hasUpstash = await detectUpstash(cwd);
12601
+ const middlewarePath = (0, import_node_path15.join)(cwd, "middleware.ts");
12602
+ const source = await readSourceOrEmpty(middlewarePath);
12603
+ const result = applyRateLimitRecipe(source, hasUpstash);
12604
+ return {
12605
+ ...result,
12606
+ targetFile: "middleware.ts",
12607
+ canAutoApply: true,
12608
+ recipeName: gapId
12609
+ };
12610
+ }
12611
+ return null;
12612
+ }
12613
+
12011
12614
  // src/heal/apply.ts
12012
12615
  function healId() {
12013
12616
  return `heal_${(/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14)}`;
@@ -12029,6 +12632,88 @@ async function applyHeal(options) {
12029
12632
  rollback: { available: false, instructions: "Re-run with --yes to allow guarded repo-code edits." }
12030
12633
  };
12031
12634
  }
12635
+ if (options.gapId) {
12636
+ const dispatched = await dispatchRecipeByGapId(
12637
+ options.gapId,
12638
+ options.cwd,
12639
+ options.target
12640
+ );
12641
+ if (dispatched !== null) {
12642
+ if (!dispatched.canAutoApply) {
12643
+ return {
12644
+ $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
12645
+ schemaVersion: "v1",
12646
+ runId: "vr_heal_apply",
12647
+ healId: id,
12648
+ mode: "apply",
12649
+ status: "refused_unsupported",
12650
+ gapId: options.gapId,
12651
+ changedFiles: [],
12652
+ artifacts: {},
12653
+ rollback: {
12654
+ available: false,
12655
+ instructions: dispatched.reason ?? "This gap requires a manual provider-dashboard action."
12656
+ }
12657
+ };
12658
+ }
12659
+ if (!dispatched.changed) {
12660
+ return {
12661
+ $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
12662
+ schemaVersion: "v1",
12663
+ runId: "vr_heal_apply",
12664
+ healId: id,
12665
+ mode: "apply",
12666
+ status: "refused_unsupported",
12667
+ gapId: options.gapId,
12668
+ target: dispatched.targetFile || options.target,
12669
+ changedFiles: [],
12670
+ artifacts: {},
12671
+ rollback: { available: false, instructions: "Recipe matched but no change was needed (already applied or file already exists)." }
12672
+ };
12673
+ }
12674
+ const absoluteTarget = (0, import_node_path16.join)(options.cwd, dispatched.targetFile);
12675
+ assertSafeHealTarget(options.cwd, dispatched.targetFile);
12676
+ await (0, import_promises11.mkdir)((0, import_node_path16.dirname)(absoluteTarget), { recursive: true });
12677
+ const healDir2 = (0, import_node_path16.join)(options.cwd, ".viberaven", "heal", id);
12678
+ await (0, import_promises11.mkdir)((0, import_node_path16.join)(healDir2, "before"), { recursive: true });
12679
+ const beforeContent = (0, import_node_fs8.existsSync)(absoluteTarget) ? await (0, import_promises11.readFile)(absoluteTarget, "utf8") : "";
12680
+ await (0, import_promises11.writeFile)((0, import_node_path16.join)(healDir2, "before", "target.txt"), beforeContent, "utf8");
12681
+ await (0, import_promises11.writeFile)(absoluteTarget, dispatched.output, "utf8");
12682
+ const patch2 = [
12683
+ `--- ${dispatched.targetFile}`,
12684
+ `+++ ${dispatched.targetFile}`,
12685
+ "@@ VibeRaven guarded heal @@",
12686
+ beforeContent || "(new file)",
12687
+ "--- after ---",
12688
+ dispatched.output,
12689
+ ""
12690
+ ].join("\n");
12691
+ await (0, import_promises11.writeFile)((0, import_node_path16.join)(healDir2, "patch.diff"), patch2, "utf8");
12692
+ const result2 = {
12693
+ $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
12694
+ schemaVersion: "v1",
12695
+ runId: "vr_heal_apply",
12696
+ healId: id,
12697
+ mode: "apply",
12698
+ status: "applied_verify_not_run",
12699
+ gapId: options.gapId,
12700
+ recipe: dispatched.recipeName,
12701
+ target: dispatched.targetFile,
12702
+ changedFiles: [(0, import_node_path16.relative)(options.cwd, absoluteTarget).replace(/\\/g, "/")],
12703
+ artifacts: {
12704
+ patch: `.viberaven/heal/${id}/patch.diff`,
12705
+ result: `.viberaven/heal/${id}/result.json`
12706
+ },
12707
+ rollback: {
12708
+ available: true,
12709
+ instructions: "Restore .viberaven/heal/<healId>/before/target.txt to the target file or apply the reverse patch."
12710
+ }
12711
+ };
12712
+ await (0, import_promises11.writeFile)((0, import_node_path16.join)(healDir2, "result.json"), `${JSON.stringify(result2, null, 2)}
12713
+ `, "utf8");
12714
+ return result2;
12715
+ }
12716
+ }
12032
12717
  if (!options.target) {
12033
12718
  return {
12034
12719
  $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
@@ -12044,7 +12729,7 @@ async function applyHeal(options) {
12044
12729
  };
12045
12730
  }
12046
12731
  const absolute = assertSafeHealTarget(options.cwd, options.target);
12047
- const before = await (0, import_promises10.readFile)(absolute, "utf8");
12732
+ const before = await (0, import_promises11.readFile)(absolute, "utf8");
12048
12733
  const recipe = applyEmptyCatchRecipe(before);
12049
12734
  if (!recipe.changed) {
12050
12735
  return {
@@ -12061,10 +12746,10 @@ async function applyHeal(options) {
12061
12746
  rollback: { available: false, instructions: "No supported heal recipe matched this file." }
12062
12747
  };
12063
12748
  }
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");
12749
+ const healDir = (0, import_node_path16.join)(options.cwd, ".viberaven", "heal", id);
12750
+ await (0, import_promises11.mkdir)((0, import_node_path16.join)(healDir, "before"), { recursive: true });
12751
+ await (0, import_promises11.writeFile)((0, import_node_path16.join)(healDir, "before", "target.txt"), before, "utf8");
12752
+ await (0, import_promises11.writeFile)(absolute, recipe.output, "utf8");
12068
12753
  const patch = [
12069
12754
  `--- ${options.target}`,
12070
12755
  `+++ ${options.target}`,
@@ -12074,7 +12759,7 @@ async function applyHeal(options) {
12074
12759
  recipe.output,
12075
12760
  ""
12076
12761
  ].join("\n");
12077
- await (0, import_promises10.writeFile)((0, import_node_path15.join)(healDir, "patch.diff"), patch, "utf8");
12762
+ await (0, import_promises11.writeFile)((0, import_node_path16.join)(healDir, "patch.diff"), patch, "utf8");
12078
12763
  const result = {
12079
12764
  $schema: "https://viberaven.dev/schemas/heal-result.schema.json",
12080
12765
  schemaVersion: "v1",
@@ -12085,7 +12770,7 @@ async function applyHeal(options) {
12085
12770
  gapId: options.gapId,
12086
12771
  recipe: "empty-catch-safe-response",
12087
12772
  target: options.target,
12088
- changedFiles: [(0, import_node_path15.relative)(options.cwd, absolute).replace(/\\/g, "/")],
12773
+ changedFiles: [(0, import_node_path16.relative)(options.cwd, absolute).replace(/\\/g, "/")],
12089
12774
  artifacts: {
12090
12775
  patch: `.viberaven/heal/${id}/patch.diff`,
12091
12776
  result: `.viberaven/heal/${id}/result.json`
@@ -12095,20 +12780,20 @@ async function applyHeal(options) {
12095
12780
  instructions: "Restore .viberaven/heal/<healId>/before/target.txt to the target file or apply the reverse patch."
12096
12781
  }
12097
12782
  };
12098
- await (0, import_promises10.writeFile)((0, import_node_path15.join)(healDir, "result.json"), `${JSON.stringify(result, null, 2)}
12783
+ await (0, import_promises11.writeFile)((0, import_node_path16.join)(healDir, "result.json"), `${JSON.stringify(result, null, 2)}
12099
12784
  `, "utf8");
12100
12785
  return result;
12101
12786
  }
12102
12787
 
12103
12788
  // src/heal/plan.ts
12104
- var import_promises11 = require("node:fs/promises");
12105
- var import_node_path16 = require("node:path");
12789
+ var import_promises12 = require("node:fs/promises");
12790
+ var import_node_path17 = require("node:path");
12106
12791
  function healId2() {
12107
12792
  return `heal_${(/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14)}`;
12108
12793
  }
12109
12794
  async function writeHealPlan(options) {
12110
- const dir = (0, import_node_path16.join)(options.cwd, ".viberaven");
12111
- await (0, import_promises11.mkdir)(dir, { recursive: true });
12795
+ const dir = (0, import_node_path17.join)(options.cwd, ".viberaven");
12796
+ await (0, import_promises12.mkdir)(dir, { recursive: true });
12112
12797
  const id = healId2();
12113
12798
  const target = options.target ?? `gap:${options.gapId}`;
12114
12799
  const markdown = [
@@ -12135,21 +12820,21 @@ async function writeHealPlan(options) {
12135
12820
  artifacts: { plan: ".viberaven/heal-plan.md" },
12136
12821
  rollback: { available: false, instructions: "No source files were changed." }
12137
12822
  };
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)}
12823
+ await (0, import_promises12.writeFile)((0, import_node_path17.join)(dir, "heal-plan.md"), markdown, "utf8");
12824
+ await (0, import_promises12.writeFile)((0, import_node_path17.join)(dir, "heal-plan.json"), `${JSON.stringify(result, null, 2)}
12140
12825
  `, "utf8");
12141
12826
  return result;
12142
12827
  }
12143
12828
 
12144
12829
  // src/heal/prompt.ts
12145
- var import_promises12 = require("node:fs/promises");
12146
- var import_node_path17 = require("node:path");
12830
+ var import_promises13 = require("node:fs/promises");
12831
+ var import_node_path18 = require("node:path");
12147
12832
  function healId3() {
12148
12833
  return `heal_${(/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14)}`;
12149
12834
  }
12150
12835
  async function writeHealPrompt(options) {
12151
- const dir = (0, import_node_path17.join)(options.cwd, ".viberaven");
12152
- await (0, import_promises12.mkdir)(dir, { recursive: true });
12836
+ const dir = (0, import_node_path18.join)(options.cwd, ".viberaven");
12837
+ await (0, import_promises13.mkdir)(dir, { recursive: true });
12153
12838
  const id = healId3();
12154
12839
  const target = options.target ?? `gap:${options.gapId}`;
12155
12840
  const prompt = [
@@ -12176,449 +12861,94 @@ async function writeHealPrompt(options) {
12176
12861
  artifacts: { prompt: ".viberaven/heal-prompt.md" },
12177
12862
  rollback: { available: false, instructions: "No source files were changed." }
12178
12863
  };
12179
- await (0, import_promises12.writeFile)((0, import_node_path17.join)(dir, "heal-prompt.md"), prompt, "utf8");
12864
+ await (0, import_promises13.writeFile)((0, import_node_path18.join)(dir, "heal-prompt.md"), prompt, "utf8");
12180
12865
  return result;
12181
12866
  }
12182
12867
 
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);
12190
- }
12191
- if (options.mode === "plan") return writeHealPlan(options);
12192
- if (options.mode === "prompt") return writeHealPrompt(options);
12193
- return applyHeal(options);
12194
- }
12195
-
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
- }
12220
- 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
12224
- };
12225
- }
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";
12241
- }
12242
- if (isCachePath(relativePath)) {
12243
- return "cache-directory";
12244
- }
12245
- if (relativePath.endsWith(".log") && file.sizeBytes >= LARGE_LOG_BYTES) {
12246
- return "large-log";
12247
- }
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.";
12261
- }
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.");
12267
- }
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}`);
12273
- }
12274
- return `${lines.join("\n")}
12275
- `;
12276
- }
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
- }
12309
- }
12310
- }
12311
- function normalizePath5(path) {
12312
- return path.replace(/\\/g, "/");
12313
- }
12314
-
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
12868
+ // src/loopState.ts
12869
+ var import_promises14 = require("fs/promises");
12870
+ var import_path = require("path");
12871
+ var DEFAULT_LOOP_STATE = {
12872
+ batchApplied: 0,
12873
+ lastGapCount: -1,
12874
+ stalledScans: 0,
12875
+ appliedGapIdsSinceScan: []
12335
12876
  };
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
12406
- }
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];
12877
+ function loopStatePath(workspaceRoot) {
12878
+ return (0, import_path.join)(workspaceRoot, ".viberaven", "loop-state.json");
12415
12879
  }
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}`);
12431
- }
12432
- return cwd;
12433
- }
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`);
12440
- }
12441
- if (/[&|<>^%!"\r\n]/.test(value)) {
12442
- throw new Error(`Unsafe characters in ${label}`);
12443
- }
12444
- return value;
12445
- }
12446
- function buildToolArgs(name, input) {
12447
- if (name === "viberaven_check_readiness") {
12448
- return ["--agent-mode"];
12449
- }
12450
- if (name === "viberaven_verify") {
12451
- return ["--verify"];
12452
- }
12453
- if (name === "viberaven_audit") {
12454
- return ["audit", "--vercel-supabase", ...input.json ? ["--json"] : []];
12455
- }
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");
12880
+ async function loadLoopState(workspaceRoot) {
12881
+ try {
12882
+ const raw = await (0, import_promises14.readFile)(loopStatePath(workspaceRoot), "utf8");
12883
+ const parsed = JSON.parse(raw);
12884
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.batchApplied === "number" && typeof parsed.lastGapCount === "number" && typeof parsed.stalledScans === "number") {
12885
+ const p2 = parsed;
12886
+ const appliedGapIdsSinceScan = Array.isArray(p2.appliedGapIdsSinceScan) ? p2.appliedGapIdsSinceScan.filter((value) => typeof value === "string") : [];
12887
+ return {
12888
+ batchApplied: p2.batchApplied,
12889
+ lastGapCount: p2.lastGapCount,
12890
+ stalledScans: p2.stalledScans,
12891
+ appliedGapIdsSinceScan
12892
+ };
12463
12893
  }
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"];
12471
- }
12472
- if (name === "viberaven_gate_result") {
12473
- return ["--agent-mode", "--json"];
12474
- }
12475
- if (name === "viberaven_context_map") {
12476
- return ["--condense"];
12477
- }
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;
12483
- }
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;
12489
- }
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;
12894
+ return { ...DEFAULT_LOOP_STATE };
12895
+ } catch {
12896
+ return { ...DEFAULT_LOOP_STATE };
12496
12897
  }
12497
- throw new Error(`Unknown VibeRaven MCP tool: ${name}`);
12498
- }
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
- });
12522
12898
  }
12523
- function parseJsonObjectFromToolText(text) {
12524
- const jsonStart = text.indexOf("{");
12525
- if (jsonStart === -1) return void 0;
12899
+ async function saveLoopState(workspaceRoot, state) {
12526
12900
  try {
12527
- return JSON.parse(text.slice(jsonStart));
12528
- } catch {
12529
- return void 0;
12901
+ const dir = (0, import_path.join)(workspaceRoot, ".viberaven");
12902
+ await (0, import_promises14.mkdir)(dir, { recursive: true });
12903
+ await (0, import_promises14.writeFile)(loopStatePath(workspaceRoot), JSON.stringify(state, null, 2) + "\n", "utf8");
12904
+ } catch (err) {
12905
+ console.warn("[VibeRaven] Could not save loop-state.json:", err instanceof Error ? err.message : String(err));
12530
12906
  }
12531
12907
  }
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}` : ""}`;
12908
+ function incrementBatch(state, gapId) {
12909
+ const appliedGapIdsSinceScan = [...state.appliedGapIdsSinceScan ?? []];
12910
+ if (gapId && !appliedGapIdsSinceScan.includes(gapId)) {
12911
+ appliedGapIdsSinceScan.push(gapId);
12912
+ }
12913
+ return { ...state, batchApplied: state.batchApplied + 1, appliedGapIdsSinceScan };
12538
12914
  }
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
- };
12915
+ function resetBatch(state, newGapCount) {
12916
+ if (state.lastGapCount === -1) {
12917
+ return { batchApplied: 0, lastGapCount: newGapCount, stalledScans: 0, appliedGapIdsSinceScan: [] };
12546
12918
  }
12547
- if (method === "tools/list") {
12548
- return { tools: CLI_MCP_TOOLS };
12919
+ if (newGapCount < state.lastGapCount) {
12920
+ return { batchApplied: 0, lastGapCount: newGapCount, stalledScans: 0, appliedGapIdsSinceScan: [] };
12549
12921
  }
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
- };
12559
- }
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}`);
12922
+ return {
12923
+ batchApplied: 0,
12924
+ lastGapCount: newGapCount,
12925
+ stalledScans: state.stalledScans + 1,
12926
+ appliedGapIdsSinceScan: []
12927
+ };
12567
12928
  }
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;
12929
+
12930
+ // src/commands/heal.ts
12931
+ async function runHealCommand(options) {
12932
+ if (!options.target && !options.gapId) {
12933
+ throw new Error("Heal requires --target <file> or --gap <id>.");
12577
12934
  }
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
- });
12935
+ if (options.target) {
12936
+ assertSafeHealTarget(options.cwd, options.target);
12590
12937
  }
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);
12938
+ if (options.mode === "plan") return writeHealPlan(options);
12939
+ if (options.mode === "prompt") return writeHealPrompt(options);
12940
+ const result = await applyHeal(options);
12941
+ if (result.status.startsWith("applied_") && result.changedFiles.length > 0) {
12942
+ const loopState = await loadLoopState(options.cwd);
12943
+ await saveLoopState(options.cwd, incrementBatch(loopState, result.gapId));
12609
12944
  }
12610
- }
12611
- function runMcpServer() {
12612
- process.stdin.on("data", (chunk) => {
12613
- buffer = Buffer.concat([buffer, chunk]);
12614
- drainMessages();
12615
- });
12945
+ return result;
12616
12946
  }
12617
12947
 
12618
12948
  // 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");
12949
+ var import_promises15 = require("node:fs/promises");
12950
+ var import_node_fs9 = require("node:fs");
12951
+ var import_node_path19 = require("node:path");
12622
12952
  var DEFAULT_STACK = {
12623
12953
  frontend: "react",
12624
12954
  ui: "tailwind + shadcn/ui",
@@ -12629,11 +12959,11 @@ var DEFAULT_STACK = {
12629
12959
  reason: "Agent-default stack for lowest launch friction when repo signals are ambiguous"
12630
12960
  };
12631
12961
  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)) {
12962
+ const pkgPath = (0, import_node_path19.join)(cwd, "package.json");
12963
+ if (!(0, import_node_fs9.existsSync)(pkgPath)) {
12634
12964
  return DEFAULT_STACK;
12635
12965
  }
12636
- const pkg = JSON.parse(await (0, import_promises14.readFile)(pkgPath, "utf-8"));
12966
+ const pkg = JSON.parse(await (0, import_promises15.readFile)(pkgPath, "utf-8"));
12637
12967
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
12638
12968
  const names = Object.keys(deps).join(" ").toLowerCase();
12639
12969
  const rec = {
@@ -12662,6 +12992,222 @@ async function recommendStack(cwd = process.cwd()) {
12662
12992
  return rec;
12663
12993
  }
12664
12994
 
12995
+ // src/output/nextActionBlock.ts
12996
+ function buildNextActionBlock(tasks, loopState, plan) {
12997
+ const batchSize = plan === "pro" ? 10 : 3;
12998
+ const batchApplied = loopState.batchApplied;
12999
+ const remainingInBatch = batchSize - batchApplied;
13000
+ const scanNow = batchApplied >= batchSize;
13001
+ const stalled = loopState.stalledScans >= 2;
13002
+ const stalledScans = loopState.stalledScans;
13003
+ const base = { batchSize, batchApplied, remainingInBatch, scanNow, stalled, stalledScans };
13004
+ if (stalled) {
13005
+ const stallReason = resolveStallReason(tasks);
13006
+ return {
13007
+ ...base,
13008
+ type: "stalled",
13009
+ title: "Loop stalled \u2014 no gap reduction after 2+ consecutive scans",
13010
+ requiresUserAction: true,
13011
+ stallReason
13012
+ };
13013
+ }
13014
+ if (scanNow) {
13015
+ return {
13016
+ ...base,
13017
+ type: "verify",
13018
+ title: "Batch complete \u2014 run verify to rescan before next batch",
13019
+ mcpTool: "viberaven_verify",
13020
+ mcpArgs: {},
13021
+ requiresUserAction: false
13022
+ };
13023
+ }
13024
+ const repoCodeTask = tasks.find((t) => t.fixType === "repo-code" && !t.requiresUserAction);
13025
+ if (repoCodeTask) {
13026
+ return {
13027
+ ...base,
13028
+ type: "repo-code",
13029
+ gapId: repoCodeTask.gapId,
13030
+ title: repoCodeTask.title,
13031
+ mcpTool: repoCodeTask.mcpTool,
13032
+ mcpArgs: repoCodeTask.mcpArgs,
13033
+ fallbackCommand: repoCodeTask.mcpArgs ? `npx -y @viberaven/cli --heal --apply --gap ${repoCodeTask.gapId} --yes` : void 0,
13034
+ requiresUserAction: false
13035
+ };
13036
+ }
13037
+ const providerTask = tasks.find((t) => t.fixType === "provider-action");
13038
+ if (providerTask) {
13039
+ return {
13040
+ ...base,
13041
+ type: "provider-action",
13042
+ gapId: providerTask.gapId,
13043
+ title: providerTask.title,
13044
+ requiresUserAction: true
13045
+ };
13046
+ }
13047
+ const upgradeTask = tasks.find((t) => t.fixType === "upgrade-required");
13048
+ if (upgradeTask) {
13049
+ return {
13050
+ ...base,
13051
+ type: "upgrade-required",
13052
+ gapId: upgradeTask.gapId,
13053
+ title: upgradeTask.title,
13054
+ requiresUserAction: true,
13055
+ upgradeUrl: "https://viberaven.dev/pricing"
13056
+ };
13057
+ }
13058
+ return {
13059
+ ...base,
13060
+ type: "done",
13061
+ title: "All gaps resolved \u2014 production gate is clear",
13062
+ requiresUserAction: false
13063
+ };
13064
+ }
13065
+ function resolveStallReason(tasks) {
13066
+ if (tasks.length === 0) return "no-recipes";
13067
+ const allUpgradeOrEmpty = tasks.every((t) => t.fixType === "upgrade-required");
13068
+ if (allUpgradeOrEmpty) return "no-recipes";
13069
+ const allProviderAction = tasks.every((t) => t.fixType === "provider-action");
13070
+ if (allProviderAction) return "provider-action-required";
13071
+ return "unknown";
13072
+ }
13073
+ var NEXT_ACTION_START = "VIBERAVEN_NEXT_ACTION_START";
13074
+ var NEXT_ACTION_END = "VIBERAVEN_NEXT_ACTION_END";
13075
+ var PROVIDER_ACTION_START = "VIBERAVEN_PROVIDER_ACTION_START";
13076
+ var PROVIDER_ACTION_END = "VIBERAVEN_PROVIDER_ACTION_END";
13077
+ function buildProviderActionBlock(task) {
13078
+ if (task.fixType !== "provider-action" || !task.providerAction) {
13079
+ return void 0;
13080
+ }
13081
+ const pa = task.providerAction;
13082
+ return {
13083
+ VIBERAVEN_PROVIDER_ACTION: {
13084
+ gap: task.gapId,
13085
+ provider: pa.provider,
13086
+ dashboardUrl: pa.dashboardUrl,
13087
+ exactStep: pa.exactStep,
13088
+ envKeyName: pa.envKeyName ?? null,
13089
+ envKeyExample: pa.envKeyExample ?? null,
13090
+ doneSignal: pa.doneSignal,
13091
+ verifyCommand: task.verifyCommand,
13092
+ mcpAlternative: task.mcpTool ?? pa.mcpAlternative ?? null
13093
+ }
13094
+ };
13095
+ }
13096
+ function printProviderActionBlock(tasks) {
13097
+ const task = tasks.find(
13098
+ (t) => t.fixType === "provider-action" && t.requiresUserAction === true
13099
+ );
13100
+ if (!task) return;
13101
+ const block = buildProviderActionBlock(task);
13102
+ if (!block) return;
13103
+ console.log(PROVIDER_ACTION_START);
13104
+ console.log(JSON.stringify(block, null, 2));
13105
+ console.log(PROVIDER_ACTION_END);
13106
+ }
13107
+ function printNextActionBlock(block) {
13108
+ console.log(NEXT_ACTION_START);
13109
+ console.log(JSON.stringify(block, null, 2));
13110
+ console.log(NEXT_ACTION_END);
13111
+ }
13112
+
13113
+ // src/providerMcpBridge.ts
13114
+ var import_node_fs10 = require("node:fs");
13115
+ var import_node_os2 = require("node:os");
13116
+ var import_node_path20 = require("node:path");
13117
+ var UPGRADE_URL4 = "https://viberaven.dev/pricing";
13118
+ var FALLBACK_COMMAND = "npx -y @viberaven/cli audit --vercel-supabase --json";
13119
+ var SUPPORTED_PROVIDERS = /* @__PURE__ */ new Set(["supabase", "vercel"]);
13120
+ var configPathsOverride;
13121
+ function defaultMcpConfigPaths() {
13122
+ const home = (0, import_node_os2.homedir)();
13123
+ return [
13124
+ (0, import_node_path20.join)(home, ".config", "claude", "claude_desktop_config.json"),
13125
+ (0, import_node_path20.join)(home, ".cursor", "mcp.json"),
13126
+ (0, import_node_path20.join)(home, ".gemini", "antigravity", "mcp_config.json")
13127
+ ];
13128
+ }
13129
+ function resolveConfigPaths() {
13130
+ return configPathsOverride ?? defaultMcpConfigPaths();
13131
+ }
13132
+ function parseMcpServers(raw) {
13133
+ if (!raw || typeof raw !== "object") {
13134
+ return void 0;
13135
+ }
13136
+ const obj = raw;
13137
+ if (obj.mcpServers && typeof obj.mcpServers === "object") {
13138
+ return obj.mcpServers;
13139
+ }
13140
+ if (obj.servers && typeof obj.servers === "object") {
13141
+ return obj.servers;
13142
+ }
13143
+ return void 0;
13144
+ }
13145
+ function findServerEntry(servers, provider2) {
13146
+ if (servers[provider2]) {
13147
+ return servers[provider2];
13148
+ }
13149
+ const key = Object.keys(servers).find((candidate) => candidate.toLowerCase() === provider2);
13150
+ return key ? servers[key] : void 0;
13151
+ }
13152
+ function findProviderMcpConfig(provider2, configPaths) {
13153
+ const paths = configPaths ?? resolveConfigPaths();
13154
+ for (const path of paths) {
13155
+ if (!(0, import_node_fs10.existsSync)(path)) {
13156
+ continue;
13157
+ }
13158
+ try {
13159
+ const raw = JSON.parse((0, import_node_fs10.readFileSync)(path, "utf8"));
13160
+ const servers = parseMcpServers(raw);
13161
+ if (!servers) {
13162
+ continue;
13163
+ }
13164
+ const entry = findServerEntry(servers, provider2);
13165
+ if (!entry || typeof entry !== "object") {
13166
+ continue;
13167
+ }
13168
+ const server = entry;
13169
+ return {
13170
+ command: typeof server.command === "string" ? server.command : void 0,
13171
+ args: Array.isArray(server.args) ? server.args.filter((arg) => typeof arg === "string") : void 0,
13172
+ url: typeof server.url === "string" ? server.url : void 0,
13173
+ source: path
13174
+ };
13175
+ } catch {
13176
+ continue;
13177
+ }
13178
+ }
13179
+ return void 0;
13180
+ }
13181
+ async function verifyProviderGap(options) {
13182
+ if (options.plan !== "pro") {
13183
+ return {
13184
+ verified: false,
13185
+ reason: "pro-required",
13186
+ upgradeUrl: UPGRADE_URL4
13187
+ };
13188
+ }
13189
+ const provider2 = options.provider.toLowerCase().trim();
13190
+ if (!SUPPORTED_PROVIDERS.has(provider2)) {
13191
+ return {
13192
+ verified: false,
13193
+ reason: "unsupported-provider"
13194
+ };
13195
+ }
13196
+ const mcpConfig = findProviderMcpConfig(provider2);
13197
+ if (!mcpConfig) {
13198
+ return {
13199
+ verified: false,
13200
+ mcpUnavailable: true,
13201
+ fallbackCommand: FALLBACK_COMMAND
13202
+ };
13203
+ }
13204
+ return {
13205
+ verified: false,
13206
+ mcpUnavailable: true,
13207
+ fallbackCommand: FALLBACK_COMMAND
13208
+ };
13209
+ }
13210
+
12665
13211
  // src/cli.ts
12666
13212
  function printHelp() {
12667
13213
  console.log(`viberaven ${VERSION} \u2014 launch readiness for AI-built apps
@@ -12720,26 +13266,6 @@ Usage:
12720
13266
  viberaven open [provider|url]
12721
13267
  Open dashboard URL from next action or playbook
12722
13268
 
12723
- viberaven init --agents all [--dry-run]
12724
- Install bounded VibeRaven rules into native agent instruction files
12725
-
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}
12742
-
12743
13269
 
12744
13270
 
12745
13271
  Agent workflow (Claude Code / Codex):
@@ -12807,15 +13333,25 @@ function parseArgs(argv) {
12807
13333
  return { command, flags, positional };
12808
13334
  }
12809
13335
  function isBooleanFlag(command, key) {
12810
- if (["agent-mode", "json", "jsonl", "condense", "heal", "plan", "prompt", "apply", "yes", "no-verify"].includes(key)) {
13336
+ if ([
13337
+ "agent-mode",
13338
+ "json",
13339
+ "jsonl",
13340
+ "condense",
13341
+ "heal",
13342
+ "plan",
13343
+ "prompt",
13344
+ "apply",
13345
+ "yes",
13346
+ "no-verify",
13347
+ "force-scan"
13348
+ ].includes(key)) {
12811
13349
  return true;
12812
13350
  }
12813
13351
  if (key === "strict") return true;
12814
13352
  if (key === "open" && (command === "" || command === "scan" || command === "report")) return true;
12815
13353
  if (key === "verify" && command === "") return true;
12816
13354
  if (key === "vercel-supabase" && command === "audit") return true;
12817
- if (key === "dry-run" && command === "init") return true;
12818
- if (key === "plan" && command === "clean") return true;
12819
13355
  return false;
12820
13356
  }
12821
13357
  function shouldConsumeLeadingHyphenValue(command, key, value) {
@@ -12824,6 +13360,44 @@ function shouldConsumeLeadingHyphenValue(command, key, value) {
12824
13360
  function hasFlag(flags, key) {
12825
13361
  return flags[key] === true || typeof flags[key] === "string";
12826
13362
  }
13363
+ async function guardEarlyVerifyScan(input) {
13364
+ if (input.flags["force-scan"] === true) {
13365
+ return void 0;
13366
+ }
13367
+ const verifyLike = input.flags.verify === true || input.wantsStrict;
13368
+ if (!verifyLike) {
13369
+ return void 0;
13370
+ }
13371
+ const workspacePath = input.positional[0] ? (0, import_node_path21.join)(process.cwd(), input.positional[0]) : await resolveWorkspaceRoot(process.cwd());
13372
+ const loopState = await loadLoopState(workspacePath);
13373
+ if (loopState.batchApplied <= 0) {
13374
+ return void 0;
13375
+ }
13376
+ let artifact;
13377
+ try {
13378
+ artifact = await loadLastArtifact(workspacePath);
13379
+ } catch {
13380
+ return void 0;
13381
+ }
13382
+ const plan = artifact.plan ?? (await loadCredentials())?.plan ?? "free";
13383
+ const batchSize = plan === "pro" ? 10 : 3;
13384
+ if (loopState.batchApplied >= batchSize) {
13385
+ return void 0;
13386
+ }
13387
+ const appliedGapIds = new Set(loopState.appliedGapIdsSinceScan ?? []);
13388
+ const remainingRepoCodeTasks = buildTaskList(artifact).filter(
13389
+ (task) => task.fixType === "repo-code" && task.requiresUserAction === false && !appliedGapIds.has(task.gapId)
13390
+ );
13391
+ if (remainingRepoCodeTasks.length === 0) {
13392
+ return void 0;
13393
+ }
13394
+ const nextTask = remainingRepoCodeTasks[0];
13395
+ console.error("SCAN_DEFERRED: Local heal batch is not full yet, so VibeRaven is protecting scan quota.");
13396
+ console.error(`Batch progress: ${loopState.batchApplied}/${batchSize} local heals applied since the last scan.`);
13397
+ console.error(`Next local heal: npx -y @viberaven/cli --heal --apply --gap ${nextTask.gapId} --yes`);
13398
+ console.error("Run verify again after the batch is full, or add --force-scan if the user explicitly wants to spend a scan now.");
13399
+ return 4;
13400
+ }
12827
13401
  function resolveDefaultEntrypointMode(options) {
12828
13402
  if (options.env.VIBERAVEN_TUI === "1") return "interactive";
12829
13403
  if (options.env.VIBERAVEN_AGENT === "1") return "agent-scan";
@@ -12841,6 +13415,29 @@ async function cmdLogout() {
12841
13415
  await clearCredentials();
12842
13416
  console.log("Signed out.");
12843
13417
  }
13418
+ async function cmdProviderVerify(flags, positional) {
13419
+ const provider2 = typeof flags.provider === "string" ? flags.provider : positional[0];
13420
+ const check = typeof flags.check === "string" ? flags.check : positional[1];
13421
+ if (!provider2 || !check) {
13422
+ console.error("Usage: viberaven provider-verify --provider <supabase|vercel> --check <id> [--plan free|pro]");
13423
+ return 1;
13424
+ }
13425
+ let plan = typeof flags.plan === "string" ? flags.plan : "free";
13426
+ if (!flags.plan) {
13427
+ const creds = await loadCredentials();
13428
+ if (creds?.plan === "pro" || creds?.plan === "free") {
13429
+ plan = creds.plan;
13430
+ }
13431
+ }
13432
+ const result = await verifyProviderGap({
13433
+ provider: provider2,
13434
+ check,
13435
+ cwd: process.cwd(),
13436
+ plan
13437
+ });
13438
+ console.log(JSON.stringify(result, null, 2));
13439
+ return result.verified ? 0 : 1;
13440
+ }
12844
13441
  async function cmdStatus(flags, positional) {
12845
13442
  const creds = await loadCredentials();
12846
13443
  if (!creds?.accessToken) {
@@ -13009,11 +13606,10 @@ async function runScanCommand(flags, positional, options) {
13009
13606
  } catch (error) {
13010
13607
  const message = error instanceof Error ? error.message : String(error);
13011
13608
  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.");
13609
+ console.log("LOGIN_REQUIRED: Starting VibeRaven browser sign-in so this scan can continue.");
13610
+ console.log("AGENT_ACTION: Open the VibeRaven approval URL for the user if the browser does not open automatically.");
13015
13611
  await runDeviceLogin(apiBaseUrl);
13016
- loginLog("LOGIN_RESUME: Sign-in complete. Continuing the original scan.");
13612
+ console.log("LOGIN_RESUME: Sign-in complete. Continuing the original scan.");
13017
13613
  try {
13018
13614
  ({ accessToken } = await requireCredentials(apiBaseUrl));
13019
13615
  } catch (retryError) {
@@ -13059,6 +13655,17 @@ async function runScanCommand(flags, positional, options) {
13059
13655
  if (artifact.usage && !options?.deferMachineOutput) {
13060
13656
  console.log(formatUsageLine(artifact.usage));
13061
13657
  }
13658
+ if (flags["agent-mode"] && !options?.deferMachineOutput) {
13659
+ const loopState = await loadLoopState(workspacePath);
13660
+ const openGapCount = artifact.gaps.length;
13661
+ const updatedState = resetBatch(loopState, openGapCount);
13662
+ const tasks = buildTaskList(artifact);
13663
+ const plan = artifact.plan ?? "free";
13664
+ const block = buildNextActionBlock(tasks, updatedState, plan);
13665
+ printNextActionBlock(block);
13666
+ printProviderActionBlock(tasks);
13667
+ await saveLoopState(workspacePath, updatedState);
13668
+ }
13062
13669
  if (flags.open) {
13063
13670
  try {
13064
13671
  await openPathInBrowser(paths.reportPath);
@@ -13174,67 +13781,9 @@ async function cmdStack(positional) {
13174
13781
  console.error(`Unknown stack subcommand "${sub}". Use set, clear, or list.`);
13175
13782
  return 1;
13176
13783
  }
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
13784
  async function main() {
13236
13785
  const { command, flags, positional } = parseArgs(process.argv.slice(2));
13237
- if (flags.help && command !== "mcp") {
13786
+ if (flags.help) {
13238
13787
  printHelp();
13239
13788
  return 0;
13240
13789
  }
@@ -13265,30 +13814,28 @@ async function main() {
13265
13814
  console.log(JSON.stringify(result, null, 2));
13266
13815
  return result.status.startsWith("refused") || result.status === "failed" ? 1 : 0;
13267
13816
  }
13268
- if (!command && (isAgentMode || wantsJson || wantsJsonl || wantsStrict)) {
13817
+ if (!command && (isAgentMode || flags.verify === true || wantsJson || wantsJsonl || wantsStrict)) {
13818
+ const guardedExitCode = await guardEarlyVerifyScan({ flags, positional, wantsStrict });
13819
+ if (guardedExitCode !== void 0) {
13820
+ return guardedExitCode;
13821
+ }
13269
13822
  const deferMachineOutput = wantsJson || wantsJsonl;
13270
13823
  const scanResult = await runScanCommand(flags, positional, { deferMachineOutput });
13271
13824
  if ((wantsJson || wantsJsonl) && !scanResult.artifacts) {
13272
13825
  console.error("VibeRaven could not produce machine output because scan artifacts were not written.");
13273
13826
  return 3;
13274
13827
  }
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"));
13828
+ const gateResult = scanResult.artifacts && (wantsJson || wantsJsonl || wantsStrict) ? JSON.parse(await (0, import_promises16.readFile)(scanResult.artifacts.gateResultPath, "utf8")) : void 0;
13829
+ const strictExitCode = wantsStrict && gateResult ? exitCodeForStrictGate(gateResult, { failOnWarnings: flags.strict === "warning" }) : scanResult.exitCode;
13830
+ if (wantsJson && gateResult) {
13283
13831
  process.stdout.write(renderGateResultJson(gateResult));
13284
- return strictExitCode ?? scanResult.exitCode;
13832
+ return strictExitCode;
13285
13833
  }
13286
- if (wantsJsonl && scanResult.artifacts) {
13287
- const gateResult = JSON.parse(await (0, import_promises15.readFile)(scanResult.artifacts.gateResultPath, "utf8"));
13834
+ if (wantsJsonl && gateResult) {
13288
13835
  process.stdout.write(renderJsonlEvents(gateResult));
13289
- return strictExitCode ?? scanResult.exitCode;
13836
+ return strictExitCode;
13290
13837
  }
13291
- if (strictExitCode !== void 0) {
13838
+ if (wantsStrict && gateResult) {
13292
13839
  return strictExitCode;
13293
13840
  }
13294
13841
  return scanResult.exitCode;
@@ -13348,18 +13895,8 @@ async function main() {
13348
13895
  return cmdPrompt(flags, positional);
13349
13896
  case "stack":
13350
13897
  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;
13362
- }
13898
+ case "provider-verify":
13899
+ return cmdProviderVerify(flags, positional);
13363
13900
  default:
13364
13901
  console.error(`Unknown command: ${command}`);
13365
13902
  printHelp();