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