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