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