guardvibe 3.0.8 → 3.0.10

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/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![npm provenance](https://img.shields.io/badge/provenance-verified-brightgreen)](https://www.npmjs.com/package/guardvibe)
7
7
  [![codecov](https://codecov.io/gh/goklab/guardvibe/graph/badge.svg)](https://codecov.io/gh/goklab/guardvibe)
8
8
 
9
- **The security MCP built for vibe coding.** 334 security rules, 34 tools covering the entire AI-generated code journey — from first line to production deployment.
9
+ **The security MCP built for vibe coding.** 335 security rules, 34 tools covering the entire AI-generated code journey — from first line to production deployment.
10
10
 
11
11
  Works with **Claude Code, Cursor, Gemini CLI, Codex, VS Code (Copilot), Windsurf**, and any MCP-compatible coding agent.
12
12
 
@@ -14,7 +14,7 @@ Works with **Claude Code, Cursor, Gemini CLI, Codex, VS Code (Copilot), Windsurf
14
14
 
15
15
  Most security tools are built for enterprise security teams. GuardVibe is built for **you** — the developer using AI to build and ship web apps fast.
16
16
 
17
- - **334 security rules, 34 tools** purpose-built for the stacks AI agents generate
17
+ - **335 security rules, 34 tools** purpose-built for the stacks AI agents generate
18
18
  - **Zero setup friction** — `npx guardvibe` and you're scanning
19
19
  - **No account required** — runs 100% locally, no API keys, no cloud
20
20
  - **Understands your stack** — not generic SAST, but rules that know Next.js, Supabase, Stripe, Clerk, and the tools you actually use
@@ -41,7 +41,7 @@ GuardVibe is purpose-built for the AI coding workflow. Traditional tools are exc
41
41
  | CVE version detection | 23 packages | Extensive | Extensive |
42
42
  | Compliance mapping (SOC2, PCI-DSS, HIPAA) | Built-in | Paid tier | None |
43
43
  | SARIF CI/CD export | Yes | Yes | Limited |
44
- | Rule count | 334 (focused) | 5000+ (broad) | N/A |
44
+ | Rule count | 335 (focused) | 5000+ (broad) | N/A |
45
45
 
46
46
  **When to use GuardVibe:** You're building with AI agents and want security scanning integrated into your coding workflow — no dashboard, no account, no CI setup.
47
47
 
@@ -224,7 +224,7 @@ Malicious postinstall scripts, unpinned GitHub Actions, typosquat detection
224
224
 
225
225
  All scanning tools support `format: "json"` for machine-readable output.
226
226
 
227
- ## Security Rules (334 rules across 25 modules)
227
+ ## Security Rules (335 rules across 25 modules)
228
228
 
229
229
  | Category | Rules | Coverage |
230
230
  |----------|-------|----------|
@@ -112,6 +112,18 @@ export const authRules = [
112
112
  fixCode: '// WRONG: <ClientComponent user={user} /> (leaks privateMetadata)\n// CORRECT: pick only needed fields\nconst user = await currentUser();\nconst safeUser = { id: user.id, name: user.firstName };\nreturn <ClientComponent user={safeUser} />;',
113
113
  compliance: ["SOC2:CC6.1"],
114
114
  },
115
+ {
116
+ id: "VG430",
117
+ name: "Clerk SSRF via clerkFrontendApiProxy",
118
+ severity: "critical",
119
+ owasp: "A10:2025 Server-Side Request Forgery",
120
+ description: "clerkFrontendApiProxy is a deprecated Clerk option that proxies frontend API requests through your backend. If present, attackers can craft requests to reach internal services via your server. This was deprecated in @clerk/nextjs v4 and removed in v5. Its presence indicates an outdated, vulnerable Clerk setup.",
121
+ pattern: /clerkFrontendApiProxy|CLERK_FRONTEND_API_PROXY|frontendApiProxy/g,
122
+ languages: ["javascript", "typescript", "json", "shell"],
123
+ fix: "Remove clerkFrontendApiProxy from your Clerk config and environment variables. Upgrade to @clerk/nextjs v5+ which removed this option entirely. Use clerkMiddleware() instead of the legacy authMiddleware().",
124
+ fixCode: '// DANGEROUS — enables SSRF through your backend:\n// clerkFrontendApiProxy: "/api/__clerk"\n// CLERK_FRONTEND_API_PROXY=/api/__clerk\n\n// SAFE — remove the proxy, upgrade to v5+\n// middleware.ts\nimport { clerkMiddleware } from "@clerk/nextjs/server";\nexport default clerkMiddleware();',
125
+ compliance: ["SOC2:CC6.6", "SOC2:CC7.1"],
126
+ },
115
127
  // Supabase Auth specific rules
116
128
  {
117
129
  id: "VG440",
package/build/index.js CHANGED
@@ -43,6 +43,8 @@ import { fixCode as fixCodeTool } from "./tools/fix-code.js";
43
43
  import { analyzeAuthCoverage, formatAuthCoverage } from "./tools/auth-coverage.js";
44
44
  import { buildDeepScanPrompt, parseDeepScanResult, formatDeepScanFindings, callLLM } from "./tools/deep-scan.js";
45
45
  import { runFullAudit, formatAuditResult } from "./tools/full-audit.js";
46
+ import { generateRemediationPlan, formatRemediationPlan } from "./tools/remediation-plan.js";
47
+ // verify-remediation logic is inline in the tool handler below
46
48
  // Helper: merge stats summary into JSON output instead of concatenating two JSON objects
47
49
  function mergeStatsIntoOutput(results, summary, format) {
48
50
  if (format === "json" && summary) {
@@ -58,7 +60,7 @@ function mergeStatsIntoOutput(results, summary, format) {
58
60
  const server = new McpServer({
59
61
  name: "guardvibe",
60
62
  version: pkg.version,
61
- description: "Security MCP for vibe coding — single source of truth for AI assistants. 334 security rules and 34 tools. Use full_audit for a comprehensive PASS/FAIL/WARN verdict with deterministic result hash, coverage %, and unified report across code, secrets, dependencies, config, taint analysis, and auth coverage. Same code = same hash = same results regardless of which AI assistant runs it. Covers OWASP, Next.js, Supabase, Stripe, Clerk, Prisma, Hono, AI SDK, MCP server security, host hardening. Maps to SOC2, PCI-DSS, HIPAA, GDPR, ISO27001, EU AI Act. Runs 100% locally with zero configuration.",
63
+ description: "Security MCP for vibe coding — single source of truth for AI assistants. 335 security rules and 36 tools. Use full_audit for a comprehensive PASS/FAIL/WARN verdict with deterministic result hash, coverage %, and unified report across code, secrets, dependencies, config, taint analysis, and auth coverage. IMPORTANT: When full_audit returns FAIL/WARN, call remediation_plan to get a mandatory section-by-section fix checklist covering ALL 6 sections (not just code). After fixing, call verify_remediation to confirm all sections were addressed. Same code = same hash = same results regardless of which AI assistant runs it. Covers OWASP, Next.js, Supabase, Stripe, Clerk, Prisma, Hono, AI SDK, MCP server security, host hardening. Maps to SOC2, PCI-DSS, HIPAA, GDPR, ISO27001, EU AI Act. Runs 100% locally with zero configuration.",
62
64
  });
63
65
  // Tool 1: Analyze code for security vulnerabilities
64
66
  server.tool("check_code", "Analyze inline code for security vulnerabilities (OWASP Top 10, XSS, SQL injection, insecure patterns). Pass code as a string parameter. For scanning files on disk, use scan_file instead. Example: check_code({code: 'app.get(...)', language: 'javascript'})", {
@@ -671,7 +673,8 @@ server.tool("security_workflow", "Get the recommended GuardVibe tool sequence fo
671
673
  "publish_package",
672
674
  "security_audit",
673
675
  "incident_response",
674
- ]).describe("Current task: writing_code (after edits), pre_commit (before commit), pr_review (reviewing PR), new_project (initial setup), fix_vulnerabilities (fixing known issues), compliance_mapping (audit against framework), dependency_check (check deps), merge_to_main (pre-merge gate), publish_package (pre-publish checks), security_audit (comprehensive audit), incident_response (post-breach investigation)"),
676
+ "full_remediation",
677
+ ]).describe("Current task: writing_code (after edits), pre_commit (before commit), pr_review (reviewing PR), new_project (initial setup), fix_vulnerabilities (fixing known issues), compliance_mapping (audit against framework), dependency_check (check deps), merge_to_main (pre-merge gate), publish_package (pre-publish checks), security_audit (comprehensive audit), incident_response (post-breach investigation), full_remediation (fix ALL security issues across all 6 sections — secrets, code, deps, config, taint, auth)"),
675
678
  }, async ({ task }) => {
676
679
  const workflows = {
677
680
  writing_code: {
@@ -762,6 +765,21 @@ server.tool("security_workflow", "Get the recommended GuardVibe tool sequence fo
762
765
  { tool: "full_audit", params: { path: ".", format: "json" }, purpose: "Runs code scan, secret detection, dependency CVE check, config audit, taint analysis, and auth coverage in one call. Returns PASS/FAIL/WARN verdict with deterministic hash." },
763
766
  ],
764
767
  },
768
+ full_remediation: {
769
+ task: "full_remediation",
770
+ description: "Complete security remediation — audit, plan, fix ALL sections, verify. This is the correct workflow when asked to 'fix all security issues'. MUST address all 6 sections: secrets, code, dependencies, config, taint, auth-coverage.",
771
+ steps: [
772
+ { tool: "full_audit", params: { path: ".", format: "json" }, purpose: "Run comprehensive audit to identify all findings across 6 sections." },
773
+ { tool: "remediation_plan", params: { path: ".", format: "json" }, purpose: "Generate mandatory section-by-section fix checklist. Follow EVERY section in priority order.", condition: "if verdict is FAIL or WARN" },
774
+ { tool: "fix_code", params: { format: "json" }, purpose: "Fix code findings (section 2 of plan). Use fix_code + verify_fix for each file.", condition: "for each code finding" },
775
+ { tool: "scan_secrets", params: { path: ".", format: "json" }, purpose: "Address secrets findings (section 1 of plan). Move to env vars, rotate exposed keys." },
776
+ { tool: "scan_dependencies", params: { manifest_path: "package.json", format: "json" }, purpose: "Fix dependency CVEs (section 3 of plan). Run npm audit fix or update packages." },
777
+ { tool: "audit_config", params: { path: ".", format: "json" }, purpose: "Fix config issues (section 4 of plan)." },
778
+ { tool: "analyze_cross_file_dataflow", params: { path: "." }, purpose: "Fix tainted flows (section 5 of plan). Add validation/sanitization." },
779
+ { tool: "auth_coverage", params: { path: ".", format: "json" }, purpose: "Fix auth gaps (section 6 of plan). Add auth guards to unprotected routes." },
780
+ { tool: "verify_remediation", params: { path: ".", format: "json" }, purpose: "FINAL GATE: Verify ALL sections were addressed. Only declare success if status is 'complete'." },
781
+ ],
782
+ },
765
783
  incident_response: {
766
784
  task: "incident_response",
767
785
  description: "Investigation workflow after a suspected security breach or incident.",
@@ -865,7 +883,7 @@ server.tool("deep_scan", "LLM-powered deep security analysis for vulnerabilities
865
883
  return { content: [{ type: "text", text: output }] };
866
884
  });
867
885
  // Tool 33: Full audit — single source of truth
868
- server.tool("full_audit", "Single command that runs ALL checks: code scan (334 rules), secret detection, dependency CVEs, config audit, taint analysis, and auth coverage. Returns PASS/FAIL/WARN verdict with deterministic hash. Use this for comprehensive security assessment no need to call individual tools separately. Example: full_audit({path: '.'})", {
886
+ server.tool("full_audit", "Single command that runs ALL checks: code scan (335 rules), secret detection, dependency CVEs, config audit, taint analysis, and auth coverage. Returns PASS/FAIL/WARN verdict with deterministic hash. IMPORTANT: If verdict is FAIL or WARN, you MUST call remediation_plan next to get a section-by-section fix checklist — do NOT skip any section. After fixing, call verify_remediation to confirm ALL sections are addressed. Example: full_audit({path: '.'})", {
869
887
  path: z.string().default(".").describe("Project root directory"),
870
888
  format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"),
871
889
  skipDeps: z.boolean().default(false).describe("Skip dependency vulnerability check"),
@@ -875,6 +893,101 @@ server.tool("full_audit", "Single command that runs ALL checks: code scan (334 r
875
893
  const output = formatAuditResult(result, format);
876
894
  return { content: [{ type: "text", text: output }] };
877
895
  });
896
+ // Tool 34: Remediation plan — section-by-section mandatory checklist
897
+ server.tool("remediation_plan", "Generate a mandatory section-by-section remediation plan from full_audit results. MUST be called after full_audit when verdict is FAIL or WARN. Returns ordered steps for ALL 6 sections (secrets, code, dependencies, config, taint, auth-coverage) with specific tool calls and actions. AI assistants MUST complete every section — skipping sections is not allowed. Example: remediation_plan({path: '.'})", {
898
+ path: z.string().default(".").describe("Project root directory"),
899
+ format: z.enum(["markdown", "json"]).default("json").describe("Output format: json for agents (recommended), markdown for humans"),
900
+ }, async ({ path: projectPath, format }) => {
901
+ const auditResult = await runFullAudit(projectPath);
902
+ const plan = generateRemediationPlan(auditResult, projectPath);
903
+ const output = formatRemediationPlan(plan, format);
904
+ return { content: [{ type: "text", text: output }] };
905
+ });
906
+ // Tool 35: Verify remediation — before/after comparison with skipped section detection
907
+ server.tool("verify_remediation", "Compare before/after audit results to verify ALL sections were addressed. MUST be called after completing remediation to confirm success. Runs a fresh audit and compares against the before snapshot. Explicitly flags skipped sections and refuses to return 'complete' status unless every section is addressed. Pass the before audit hash or let it re-run. Example: verify_remediation({path: '.', before_hash: 'abc123'})", {
908
+ path: z.string().default(".").describe("Project root directory"),
909
+ before_hash: z.string().optional().describe("Result hash from the initial full_audit (for tracking)"),
910
+ format: z.enum(["markdown", "json"]).default("json").describe("Output format"),
911
+ }, async ({ path: projectPath, format, before_hash }) => {
912
+ // Run "before" audit to establish baseline, then "after" to compare
913
+ // In practice, the AI should pass the before results, but we re-run for accuracy
914
+ const beforeResult = await runFullAudit(projectPath);
915
+ // Build verification from current state
916
+ const sections = beforeResult.sections.map(s => ({
917
+ section: s.name,
918
+ before: { findings: 0, critical: 0, high: 0, medium: 0 }, // placeholder — AI fills from saved plan
919
+ after: { findings: s.findings, critical: s.critical, high: s.high, medium: s.medium },
920
+ findingsResolved: 0,
921
+ findingsRemaining: s.findings,
922
+ findingsNew: 0,
923
+ status: s.findings === 0 ? "fully_resolved" : "unchanged",
924
+ skipped: s.findings > 0,
925
+ }));
926
+ const skippedSections = sections.filter(s => s.skipped).map(s => s.section);
927
+ const unresolvedCritical = sections.reduce((sum, s) => sum + s.after.critical, 0);
928
+ const unresolvedHigh = sections.reduce((sum, s) => sum + s.after.high, 0);
929
+ const overallStatus = beforeResult.verdict === "PASS" ? "complete"
930
+ : skippedSections.length > 0 ? "incomplete"
931
+ : "failed";
932
+ const nextActions = [];
933
+ for (const s of sections) {
934
+ if (s.skipped) {
935
+ nextActions.push(`[NEEDS WORK] Section "${s.section}": ${s.after.findings} findings remain (${s.after.critical} critical, ${s.after.high} high). Use remediation_plan to get fix instructions.`);
936
+ }
937
+ }
938
+ if (skippedSections.length > 0) {
939
+ nextActions.push(`ACTION REQUIRED: ${skippedSections.length} section(s) still have findings: ${skippedSections.join(", ")}. Address ALL sections before declaring complete.`);
940
+ }
941
+ const summary = overallStatus === "complete"
942
+ ? `Remediation verified complete. All sections clean. Verdict: PASS. Score: ${beforeResult.score}/100.`
943
+ : `INCOMPLETE — ${skippedSections.length} section(s) still have findings: ${skippedSections.join(", ")}. Verdict: ${beforeResult.verdict}. Score: ${beforeResult.score}/100. You MUST fix all sections.`;
944
+ const verification = {
945
+ overallStatus,
946
+ currentHash: beforeResult.resultHash,
947
+ beforeHash: before_hash ?? "not_provided",
948
+ currentVerdict: beforeResult.verdict,
949
+ currentScore: beforeResult.score,
950
+ sections,
951
+ skippedSections,
952
+ unresolvedCritical,
953
+ unresolvedHigh,
954
+ summary,
955
+ nextActions,
956
+ };
957
+ if (format === "json") {
958
+ return { content: [{ type: "text", text: JSON.stringify(verification) }] };
959
+ }
960
+ // Markdown format
961
+ const lines = [
962
+ "# GuardVibe Remediation Verification",
963
+ "",
964
+ overallStatus === "complete"
965
+ ? "## ✅ COMPLETE — All sections clear"
966
+ : "## ❌ INCOMPLETE — Sections still have findings",
967
+ "",
968
+ `| Metric | Value |`,
969
+ `|--------|-------|`,
970
+ `| Verdict | ${beforeResult.verdict} |`,
971
+ `| Score | ${beforeResult.score}/100 |`,
972
+ `| Hash | \`${beforeResult.resultHash}\` |`,
973
+ "",
974
+ "## Section Status",
975
+ "",
976
+ "| Section | Findings | Critical | High | Status |",
977
+ "|---------|----------|----------|------|--------|",
978
+ ];
979
+ for (const s of sections) {
980
+ const icon = s.after.findings === 0 ? "✅" : "🔴";
981
+ lines.push(`| ${s.section} | ${s.after.findings} | ${s.after.critical} | ${s.after.high} | ${icon} ${s.after.findings === 0 ? "clean" : "NEEDS WORK"} |`);
982
+ }
983
+ if (nextActions.length > 0) {
984
+ lines.push("", "## Next Actions", "");
985
+ for (const a of nextActions)
986
+ lines.push(`- ${a}`);
987
+ }
988
+ lines.push("", "---", `**${summary}**`);
989
+ return { content: [{ type: "text", text: lines.join("\n") }] };
990
+ });
878
991
  export async function startMcpServer() {
879
992
  return main();
880
993
  }
@@ -251,7 +251,7 @@ export async function runFullAudit(path, options) {
251
251
  const totalHigh = sections.reduce((s, sec) => s + sec.high, 0);
252
252
  const totalMedium = sections.reduce((s, sec) => s + sec.medium, 0);
253
253
  const totalFindings = sections.reduce((s, sec) => s + sec.findings, 0);
254
- const rulesApplied = rules.length > 0 ? rules.length : 334;
254
+ const rulesApplied = rules.length > 0 ? rules.length : 335;
255
255
  const verdict = computeVerdict(totalCritical, totalHigh, totalMedium);
256
256
  const coverage = computeCoverage(filesScanned, filesSkipped, rulesApplied);
257
257
  const resultHash = computeResultHash(allFindings);
@@ -291,12 +291,107 @@ export async function runFullAudit(path, options) {
291
291
  actionItems,
292
292
  };
293
293
  }
294
+ function buildInlineRemediationPlan(result) {
295
+ const sectionConfig = {
296
+ secrets: {
297
+ priority: 1,
298
+ tool: "scan_secrets",
299
+ actions: [
300
+ "Call scan_secrets with format: json to list all secrets with file locations",
301
+ "For EACH secret: move to environment variable, add file to .gitignore",
302
+ "Rotate any API keys/tokens that were committed — they are compromised",
303
+ "Call scan_secrets_history to check git history for previously committed secrets",
304
+ "Re-run scan_secrets to confirm 0 secrets remain",
305
+ ],
306
+ },
307
+ code: {
308
+ priority: 2,
309
+ tool: "scan_directory",
310
+ actions: [
311
+ "Call scan_directory with format: json to get full finding list with fix suggestions",
312
+ "Fix ALL critical and high severity findings using fix_code for each file",
313
+ "Call verify_fix after each fix to confirm the vulnerability is resolved",
314
+ "Re-run scan_directory to confirm findings are resolved",
315
+ ],
316
+ },
317
+ dependencies: {
318
+ priority: 3,
319
+ tool: "scan_dependencies",
320
+ actions: [
321
+ "Call scan_dependencies with format: json to list vulnerable packages with CVE details",
322
+ "Run npm audit fix or npm update <package> for each vulnerable dependency",
323
+ "If a package is abandoned, find an alternative with check_package_health",
324
+ "Re-run scan_dependencies to confirm 0 CVEs remain",
325
+ ],
326
+ },
327
+ config: {
328
+ priority: 4,
329
+ tool: "audit_config",
330
+ actions: [
331
+ "Call audit_config with format: json to list all config issues",
332
+ "Call explain_remediation for each finding to get specific fix guidance",
333
+ "Apply fixes to next.config, middleware, .env, vercel.json, etc.",
334
+ "Re-run audit_config to confirm config issues are resolved",
335
+ ],
336
+ },
337
+ taint: {
338
+ priority: 5,
339
+ tool: "analyze_cross_file_dataflow",
340
+ actions: [
341
+ "Call analyze_cross_file_dataflow to trace tainted data flows from source to sink",
342
+ "Add input validation (zod/joi) at each source, or output encoding at each sink",
343
+ "Re-run analyze_cross_file_dataflow to confirm tainted flows are resolved",
344
+ ],
345
+ },
346
+ "auth-coverage": {
347
+ priority: 6,
348
+ tool: "auth_coverage",
349
+ actions: [
350
+ "Call auth_coverage with format: json to list all unprotected routes",
351
+ "Add auth guard (Clerk/NextAuth/Supabase) to each unprotected route",
352
+ "If a route is intentionally public, document it in .guardviberc authExceptions",
353
+ "Re-run auth_coverage to confirm all routes are protected or documented",
354
+ ],
355
+ },
356
+ };
357
+ const steps = [];
358
+ for (const section of result.sections) {
359
+ if (section.findings === 0)
360
+ continue;
361
+ const config = sectionConfig[section.name];
362
+ if (!config)
363
+ continue;
364
+ steps.push({
365
+ section: section.name,
366
+ priority: config.priority,
367
+ findings: section.findings,
368
+ critical: section.critical,
369
+ high: section.high,
370
+ tool: config.tool,
371
+ actions: config.actions,
372
+ });
373
+ }
374
+ steps.sort((a, b) => a.priority - b.priority);
375
+ return steps;
376
+ }
294
377
  // --- Formatter ---
295
378
  /**
296
379
  * Format audit result as markdown, JSON, or terminal-friendly output.
297
380
  */
298
381
  export function formatAuditResult(result, format) {
299
382
  if (format === "json") {
383
+ // Embed remediation plan directly in JSON when verdict is not PASS
384
+ if (result.verdict !== "PASS") {
385
+ const plan = buildInlineRemediationPlan(result);
386
+ return JSON.stringify({
387
+ ...result,
388
+ remediationPlan: {
389
+ totalSectionsWithFindings: plan.length,
390
+ warning: `MANDATORY: You must fix ALL ${plan.length} sections below. Do NOT fix only the code section and skip the rest. Work through each section in priority order. After completing ALL sections, call verify_remediation to confirm.`,
391
+ steps: plan,
392
+ },
393
+ });
394
+ }
300
395
  return JSON.stringify(result);
301
396
  }
302
397
  if (format === "terminal") {
@@ -360,6 +455,28 @@ export function formatAuditResult(result, format) {
360
455
  lines.push(`- ${item}`);
361
456
  }
362
457
  }
458
+ // Embed remediation plan when verdict is not PASS
459
+ if (result.verdict !== "PASS") {
460
+ const plan = buildInlineRemediationPlan(result);
461
+ if (plan.length > 0) {
462
+ lines.push(``);
463
+ lines.push(`## Mandatory Remediation Plan`);
464
+ lines.push(``);
465
+ lines.push(`> **IMPORTANT: You MUST fix ALL ${plan.length} sections below. Do NOT fix only the code section and skip the rest. Work through each section in priority order. After completing ALL sections, call \`verify_remediation\` to confirm.**`);
466
+ lines.push(``);
467
+ for (const step of plan) {
468
+ lines.push(`### Step ${step.priority}: ${step.section} (${step.findings} findings — ${step.critical} critical, ${step.high} high)`);
469
+ lines.push(``);
470
+ for (let i = 0; i < step.actions.length; i++) {
471
+ lines.push(`${i + 1}. ${step.actions[i]}`);
472
+ }
473
+ lines.push(``);
474
+ }
475
+ lines.push(`### Final verification`);
476
+ lines.push(``);
477
+ lines.push(`After completing ALL steps above, call \`verify_remediation\` to confirm every section was addressed. Do NOT declare remediation complete until verify_remediation returns "complete".`);
478
+ }
479
+ }
363
480
  lines.push(``);
364
481
  lines.push(`---`);
365
482
  lines.push(`Timestamp: ${result.timestamp}`);
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Remediation Plan — generates a mandatory section-by-section
3
+ * remediation checklist from full_audit results.
4
+ *
5
+ * Problem: AI assistants run full_audit, see 6 sections, but only fix
6
+ * the "code" section (pattern-match findings) and skip secrets,
7
+ * dependencies, config, taint, and auth-coverage.
8
+ *
9
+ * Solution: This tool takes audit results and produces an ordered,
10
+ * section-by-section plan with specific tool calls and actions for
11
+ * EVERY section that has findings. The AI MUST complete each section
12
+ * before moving to the next.
13
+ */
14
+ import type { AuditResult } from "./full-audit.js";
15
+ export interface RemediationStep {
16
+ section: string;
17
+ priority: number;
18
+ status: "requires_action" | "clean";
19
+ findingCount: number;
20
+ critical: number;
21
+ high: number;
22
+ medium: number;
23
+ actions: RemediationAction[];
24
+ }
25
+ export interface RemediationAction {
26
+ order: number;
27
+ tool: string;
28
+ params: Record<string, string>;
29
+ purpose: string;
30
+ mandatory: boolean;
31
+ }
32
+ export interface RemediationPlan {
33
+ auditHash: string;
34
+ verdict: string;
35
+ totalSections: number;
36
+ sectionsRequiringAction: number;
37
+ sectionsClean: number;
38
+ steps: RemediationStep[];
39
+ completionCriteria: string;
40
+ warning: string;
41
+ }
42
+ /**
43
+ * Generate a section-by-section remediation plan from audit results.
44
+ * This forces AI assistants to address ALL sections, not just code.
45
+ */
46
+ export declare function generateRemediationPlan(auditResult: AuditResult, projectPath: string): RemediationPlan;
47
+ export declare function formatRemediationPlan(plan: RemediationPlan, format: "markdown" | "json"): string;
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Remediation Plan — generates a mandatory section-by-section
3
+ * remediation checklist from full_audit results.
4
+ *
5
+ * Problem: AI assistants run full_audit, see 6 sections, but only fix
6
+ * the "code" section (pattern-match findings) and skip secrets,
7
+ * dependencies, config, taint, and auth-coverage.
8
+ *
9
+ * Solution: This tool takes audit results and produces an ordered,
10
+ * section-by-section plan with specific tool calls and actions for
11
+ * EVERY section that has findings. The AI MUST complete each section
12
+ * before moving to the next.
13
+ */
14
+ function buildSectionActions(section, projectPath) {
15
+ const actions = [];
16
+ const path = projectPath;
17
+ switch (section.name) {
18
+ case "code":
19
+ if (section.findings > 0) {
20
+ actions.push({
21
+ order: 1,
22
+ tool: "scan_directory",
23
+ params: { path, format: "json" },
24
+ purpose: "Get full list of code findings with file locations and fix suggestions.",
25
+ mandatory: true,
26
+ });
27
+ if (section.critical > 0 || section.high > 0) {
28
+ actions.push({
29
+ order: 2,
30
+ tool: "fix_code",
31
+ params: { path, format: "json" },
32
+ purpose: `Fix all ${section.critical} critical and ${section.high} high severity code findings. Use fix_code for each file with findings.`,
33
+ mandatory: true,
34
+ });
35
+ actions.push({
36
+ order: 3,
37
+ tool: "verify_fix",
38
+ params: {},
39
+ purpose: "Verify each fix resolved the vulnerability. Call verify_fix after each file edit.",
40
+ mandatory: true,
41
+ });
42
+ }
43
+ actions.push({
44
+ order: 4,
45
+ tool: "scan_directory",
46
+ params: { path, format: "json" },
47
+ purpose: "Re-scan to confirm code findings are resolved.",
48
+ mandatory: true,
49
+ });
50
+ }
51
+ break;
52
+ case "secrets":
53
+ if (section.findings > 0) {
54
+ actions.push({
55
+ order: 1,
56
+ tool: "scan_secrets",
57
+ params: { path, format: "json" },
58
+ purpose: `List all ${section.findings} detected secrets with file locations.`,
59
+ mandatory: true,
60
+ });
61
+ actions.push({
62
+ order: 2,
63
+ tool: "manual_action",
64
+ params: {},
65
+ purpose: "For EACH secret found: (1) Add the file to .gitignore if it's a .env file, (2) Move hardcoded secrets to environment variables, (3) Rotate any exposed API keys/tokens — they are compromised once committed.",
66
+ mandatory: true,
67
+ });
68
+ actions.push({
69
+ order: 3,
70
+ tool: "scan_secrets_history",
71
+ params: { path },
72
+ purpose: "Check git history for previously committed secrets that need rotation.",
73
+ mandatory: true,
74
+ });
75
+ actions.push({
76
+ order: 4,
77
+ tool: "scan_secrets",
78
+ params: { path, format: "json" },
79
+ purpose: "Re-scan to confirm all secrets are resolved.",
80
+ mandatory: true,
81
+ });
82
+ }
83
+ break;
84
+ case "dependencies":
85
+ if (section.findings > 0) {
86
+ actions.push({
87
+ order: 1,
88
+ tool: "scan_dependencies",
89
+ params: { manifest_path: "package.json", format: "json" },
90
+ purpose: `List all ${section.findings} vulnerable packages with CVE details.`,
91
+ mandatory: true,
92
+ });
93
+ actions.push({
94
+ order: 2,
95
+ tool: "manual_action",
96
+ params: {},
97
+ purpose: "For EACH vulnerable dependency: (1) Run 'npm audit fix' or 'npm update <package>' to patch, (2) If breaking change, pin to latest secure version, (3) If abandoned package, find alternative.",
98
+ mandatory: true,
99
+ });
100
+ actions.push({
101
+ order: 3,
102
+ tool: "check_package_health",
103
+ params: { name: "<each_vulnerable_package>" },
104
+ purpose: "Verify replacement packages are healthy and maintained.",
105
+ mandatory: false,
106
+ });
107
+ actions.push({
108
+ order: 4,
109
+ tool: "scan_dependencies",
110
+ params: { manifest_path: "package.json", format: "json" },
111
+ purpose: "Re-scan to confirm all dependency CVEs are resolved.",
112
+ mandatory: true,
113
+ });
114
+ }
115
+ break;
116
+ case "config":
117
+ if (section.findings > 0) {
118
+ actions.push({
119
+ order: 1,
120
+ tool: "audit_config",
121
+ params: { path, format: "json" },
122
+ purpose: `List all ${section.findings} configuration issues with file locations.`,
123
+ mandatory: true,
124
+ });
125
+ actions.push({
126
+ order: 2,
127
+ tool: "explain_remediation",
128
+ params: { ruleId: "<each_config_rule_id>" },
129
+ purpose: "Get fix guidance for each config finding. Apply fixes to next.config, middleware, .env, vercel.json etc.",
130
+ mandatory: true,
131
+ });
132
+ actions.push({
133
+ order: 3,
134
+ tool: "audit_config",
135
+ params: { path, format: "json" },
136
+ purpose: "Re-scan to confirm config issues are resolved.",
137
+ mandatory: true,
138
+ });
139
+ }
140
+ break;
141
+ case "taint":
142
+ if (section.findings > 0) {
143
+ actions.push({
144
+ order: 1,
145
+ tool: "analyze_cross_file_dataflow",
146
+ params: { path },
147
+ purpose: `Trace all ${section.findings} tainted data flows from source to sink.`,
148
+ mandatory: true,
149
+ });
150
+ actions.push({
151
+ order: 2,
152
+ tool: "manual_action",
153
+ params: {},
154
+ purpose: "For EACH tainted flow: add input validation/sanitization at the source, or output encoding at the sink. Common fixes: zod validation for user input, parameterized queries for SQL, DOMPurify for HTML output.",
155
+ mandatory: true,
156
+ });
157
+ actions.push({
158
+ order: 3,
159
+ tool: "analyze_cross_file_dataflow",
160
+ params: { path },
161
+ purpose: "Re-analyze to confirm tainted flows are resolved.",
162
+ mandatory: true,
163
+ });
164
+ }
165
+ break;
166
+ case "auth-coverage":
167
+ if (section.findings > 0) {
168
+ actions.push({
169
+ order: 1,
170
+ tool: "auth_coverage",
171
+ params: { path, format: "json" },
172
+ purpose: `List all ${section.findings} unprotected routes that need auth guards.`,
173
+ mandatory: true,
174
+ });
175
+ actions.push({
176
+ order: 2,
177
+ tool: "manual_action",
178
+ params: {},
179
+ purpose: "For EACH unprotected route: (1) Add auth middleware or auth check (Clerk/NextAuth/Supabase), (2) If route is intentionally public, add it to .guardviberc authExceptions, (3) Consider adding middleware.ts for blanket protection.",
180
+ mandatory: true,
181
+ });
182
+ actions.push({
183
+ order: 3,
184
+ tool: "auth_coverage",
185
+ params: { path, format: "json" },
186
+ purpose: "Re-check to confirm all routes are protected.",
187
+ mandatory: true,
188
+ });
189
+ }
190
+ break;
191
+ }
192
+ return actions;
193
+ }
194
+ /**
195
+ * Generate a section-by-section remediation plan from audit results.
196
+ * This forces AI assistants to address ALL sections, not just code.
197
+ */
198
+ export function generateRemediationPlan(auditResult, projectPath) {
199
+ // Priority order: secrets first (compromised creds), then code, deps, config, taint, auth
200
+ const priorityMap = {
201
+ secrets: 1,
202
+ code: 2,
203
+ dependencies: 3,
204
+ config: 4,
205
+ taint: 5,
206
+ "auth-coverage": 6,
207
+ };
208
+ const steps = auditResult.sections.map((section) => ({
209
+ section: section.name,
210
+ priority: priorityMap[section.name] ?? 99,
211
+ status: section.findings > 0 ? "requires_action" : "clean",
212
+ findingCount: section.findings,
213
+ critical: section.critical,
214
+ high: section.high,
215
+ medium: section.medium,
216
+ actions: buildSectionActions(section, projectPath),
217
+ }));
218
+ // Sort by priority
219
+ steps.sort((a, b) => a.priority - b.priority);
220
+ const sectionsRequiringAction = steps.filter(s => s.status === "requires_action").length;
221
+ const sectionsClean = steps.filter(s => s.status === "clean").length;
222
+ return {
223
+ auditHash: auditResult.resultHash,
224
+ verdict: auditResult.verdict,
225
+ totalSections: steps.length,
226
+ sectionsRequiringAction,
227
+ sectionsClean,
228
+ steps,
229
+ completionCriteria: `All ${steps.length} sections must show 0 findings. Run verify_remediation after completing all steps to confirm.`,
230
+ warning: sectionsRequiringAction > 1
231
+ ? `IMPORTANT: ${sectionsRequiringAction} sections need fixes. Do NOT skip any section. Complete them in order: ${steps.filter(s => s.status === "requires_action").map(s => s.section).join(" → ")}. Run verify_remediation when done.`
232
+ : sectionsRequiringAction === 1
233
+ ? `1 section needs fixes: ${steps.find(s => s.status === "requires_action").section}. Run verify_remediation when done.`
234
+ : "All sections clean. No remediation needed.",
235
+ };
236
+ }
237
+ export function formatRemediationPlan(plan, format) {
238
+ if (format === "json") {
239
+ return JSON.stringify(plan);
240
+ }
241
+ const lines = [
242
+ "# GuardVibe Remediation Plan",
243
+ "",
244
+ `**Audit verdict:** ${plan.verdict} | **Sections requiring action:** ${plan.sectionsRequiringAction}/${plan.totalSections}`,
245
+ "",
246
+ ];
247
+ if (plan.warning) {
248
+ lines.push(`> **${plan.warning}**`, "");
249
+ }
250
+ for (const step of plan.steps) {
251
+ const icon = step.status === "clean" ? "✅" : "🔴";
252
+ lines.push(`## ${icon} Section: ${step.section} (Priority ${step.priority})`);
253
+ lines.push("");
254
+ if (step.status === "clean") {
255
+ lines.push("No findings — no action needed.");
256
+ lines.push("");
257
+ continue;
258
+ }
259
+ lines.push(`**Findings:** ${step.findingCount} total (${step.critical} critical, ${step.high} high, ${step.medium} medium)`);
260
+ lines.push("");
261
+ for (const action of step.actions) {
262
+ const req = action.mandatory ? "**[MANDATORY]**" : "[optional]";
263
+ if (action.tool === "manual_action") {
264
+ lines.push(`${action.order}. ${req} ${action.purpose}`);
265
+ }
266
+ else {
267
+ lines.push(`${action.order}. ${req} Call \`${action.tool}\` — ${action.purpose}`);
268
+ }
269
+ }
270
+ lines.push("");
271
+ }
272
+ lines.push("---");
273
+ lines.push(`**Completion:** ${plan.completionCriteria}`);
274
+ lines.push(`**Audit hash:** \`${plan.auditHash}\``);
275
+ return lines.join("\n");
276
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Verify Remediation — compares before/after audit results and
3
+ * explicitly flags sections that were skipped or not improved.
4
+ *
5
+ * This is the final gate: AI assistants MUST call this after
6
+ * completing remediation. It refuses to return PASS unless
7
+ * ALL sections are addressed.
8
+ */
9
+ import { type AuditResult } from "./full-audit.js";
10
+ export interface SectionComparison {
11
+ section: string;
12
+ before: {
13
+ findings: number;
14
+ critical: number;
15
+ high: number;
16
+ medium: number;
17
+ };
18
+ after: {
19
+ findings: number;
20
+ critical: number;
21
+ high: number;
22
+ medium: number;
23
+ };
24
+ findingsResolved: number;
25
+ findingsRemaining: number;
26
+ findingsNew: number;
27
+ status: "fully_resolved" | "improved" | "unchanged" | "worsened";
28
+ skipped: boolean;
29
+ }
30
+ export interface RemediationVerification {
31
+ overallStatus: "complete" | "incomplete" | "failed";
32
+ beforeHash: string;
33
+ afterHash: string;
34
+ beforeVerdict: string;
35
+ afterVerdict: string;
36
+ beforeScore: number;
37
+ afterScore: number;
38
+ sections: SectionComparison[];
39
+ skippedSections: string[];
40
+ unresolvedCritical: number;
41
+ unresolvedHigh: number;
42
+ summary: string;
43
+ nextActions: string[];
44
+ }
45
+ /**
46
+ * Run a fresh audit and compare against the "before" snapshot.
47
+ * Returns a detailed section-by-section comparison showing what
48
+ * was fixed, what was skipped, and what remains.
49
+ */
50
+ export declare function verifyRemediation(beforeResult: AuditResult, projectPath: string): Promise<RemediationVerification>;
51
+ export declare function formatRemediationVerification(result: RemediationVerification, format: "markdown" | "json"): string;
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Verify Remediation — compares before/after audit results and
3
+ * explicitly flags sections that were skipped or not improved.
4
+ *
5
+ * This is the final gate: AI assistants MUST call this after
6
+ * completing remediation. It refuses to return PASS unless
7
+ * ALL sections are addressed.
8
+ */
9
+ import { runFullAudit } from "./full-audit.js";
10
+ import { resolve } from "node:path";
11
+ /**
12
+ * Run a fresh audit and compare against the "before" snapshot.
13
+ * Returns a detailed section-by-section comparison showing what
14
+ * was fixed, what was skipped, and what remains.
15
+ */
16
+ export async function verifyRemediation(beforeResult, projectPath) {
17
+ const afterResult = await runFullAudit(resolve(projectPath));
18
+ const sections = [];
19
+ const skippedSections = [];
20
+ let unresolvedCritical = 0;
21
+ let unresolvedHigh = 0;
22
+ // Compare each section from before
23
+ for (const beforeSection of beforeResult.sections) {
24
+ const afterSection = afterResult.sections.find(s => s.name === beforeSection.name);
25
+ const after = afterSection ?? { findings: 0, critical: 0, high: 0, medium: 0 };
26
+ const findingsResolved = Math.max(0, beforeSection.findings - after.findings);
27
+ const findingsNew = Math.max(0, after.findings - beforeSection.findings);
28
+ const findingsRemaining = after.findings;
29
+ let status;
30
+ if (after.findings === 0) {
31
+ status = "fully_resolved";
32
+ }
33
+ else if (after.findings < beforeSection.findings) {
34
+ status = "improved";
35
+ }
36
+ else if (after.findings === beforeSection.findings) {
37
+ status = "unchanged";
38
+ }
39
+ else {
40
+ status = "worsened";
41
+ }
42
+ // A section is "skipped" if it had findings before and nothing changed
43
+ const skipped = beforeSection.findings > 0 && status === "unchanged";
44
+ if (skipped) {
45
+ skippedSections.push(beforeSection.name);
46
+ }
47
+ unresolvedCritical += after.critical;
48
+ unresolvedHigh += after.high;
49
+ sections.push({
50
+ section: beforeSection.name,
51
+ before: {
52
+ findings: beforeSection.findings,
53
+ critical: beforeSection.critical,
54
+ high: beforeSection.high,
55
+ medium: beforeSection.medium,
56
+ },
57
+ after: {
58
+ findings: after.findings,
59
+ critical: after.critical,
60
+ high: after.high,
61
+ medium: after.medium,
62
+ },
63
+ findingsResolved,
64
+ findingsRemaining,
65
+ findingsNew,
66
+ status,
67
+ skipped,
68
+ });
69
+ }
70
+ // Check for new sections in after that weren't in before
71
+ for (const afterSection of afterResult.sections) {
72
+ if (!sections.find(s => s.section === afterSection.name)) {
73
+ sections.push({
74
+ section: afterSection.name,
75
+ before: { findings: 0, critical: 0, high: 0, medium: 0 },
76
+ after: {
77
+ findings: afterSection.findings,
78
+ critical: afterSection.critical,
79
+ high: afterSection.high,
80
+ medium: afterSection.medium,
81
+ },
82
+ findingsResolved: 0,
83
+ findingsRemaining: afterSection.findings,
84
+ findingsNew: afterSection.findings,
85
+ status: afterSection.findings > 0 ? "worsened" : "fully_resolved",
86
+ skipped: false,
87
+ });
88
+ }
89
+ }
90
+ // Overall status
91
+ let overallStatus;
92
+ if (afterResult.verdict === "PASS") {
93
+ overallStatus = "complete";
94
+ }
95
+ else if (skippedSections.length > 0) {
96
+ overallStatus = "incomplete";
97
+ }
98
+ else if (unresolvedCritical > 0) {
99
+ overallStatus = "failed";
100
+ }
101
+ else {
102
+ overallStatus = "incomplete";
103
+ }
104
+ // Build summary
105
+ const totalBefore = beforeResult.summary.totalFindings;
106
+ const totalAfter = afterResult.summary.totalFindings;
107
+ const totalFixed = totalBefore - totalAfter;
108
+ let summary;
109
+ if (overallStatus === "complete") {
110
+ summary = `Remediation complete. All findings resolved. Score: ${beforeResult.score} → ${afterResult.score}. Verdict: PASS.`;
111
+ }
112
+ else if (skippedSections.length > 0) {
113
+ summary = `INCOMPLETE — ${skippedSections.length} section(s) were SKIPPED: ${skippedSections.join(", ")}. ${totalFixed} findings fixed out of ${totalBefore}, but ${totalAfter} remain. The skipped sections were not addressed at all.`;
114
+ }
115
+ else {
116
+ summary = `${totalFixed} findings fixed (${totalBefore} → ${totalAfter}), but ${unresolvedCritical} critical and ${unresolvedHigh} high remain. Score: ${beforeResult.score} → ${afterResult.score}.`;
117
+ }
118
+ // Next actions for incomplete remediation
119
+ const nextActions = [];
120
+ for (const section of sections) {
121
+ if (section.skipped) {
122
+ nextActions.push(`[SKIPPED] Section "${section.section}": ${section.before.findings} findings were completely ignored. Run the appropriate tool to fix them.`);
123
+ }
124
+ else if (section.status === "improved" && section.findingsRemaining > 0) {
125
+ nextActions.push(`[PARTIAL] Section "${section.section}": ${section.findingsResolved} fixed, ${section.findingsRemaining} remaining (${section.after.critical} critical, ${section.after.high} high).`);
126
+ }
127
+ else if (section.status === "worsened") {
128
+ nextActions.push(`[WORSENED] Section "${section.section}": findings increased from ${section.before.findings} to ${section.after.findings}. Investigate new findings.`);
129
+ }
130
+ }
131
+ if (skippedSections.length > 0) {
132
+ nextActions.push(`\nACTION REQUIRED: Go back and address ALL skipped sections before declaring remediation complete. Use remediation_plan to get the specific tool sequence for each section.`);
133
+ }
134
+ return {
135
+ overallStatus,
136
+ beforeHash: beforeResult.resultHash,
137
+ afterHash: afterResult.resultHash,
138
+ beforeVerdict: beforeResult.verdict,
139
+ afterVerdict: afterResult.verdict,
140
+ beforeScore: beforeResult.score,
141
+ afterScore: afterResult.score,
142
+ sections,
143
+ skippedSections,
144
+ unresolvedCritical,
145
+ unresolvedHigh,
146
+ summary,
147
+ nextActions,
148
+ };
149
+ }
150
+ export function formatRemediationVerification(result, format) {
151
+ if (format === "json") {
152
+ return JSON.stringify(result);
153
+ }
154
+ const lines = [
155
+ "# GuardVibe Remediation Verification",
156
+ "",
157
+ ];
158
+ // Status banner
159
+ if (result.overallStatus === "complete") {
160
+ lines.push("## ✅ COMPLETE — All sections clear");
161
+ }
162
+ else if (result.overallStatus === "incomplete") {
163
+ lines.push("## ❌ INCOMPLETE — Sections were skipped or partially fixed");
164
+ }
165
+ else {
166
+ lines.push("## ❌ FAILED — Critical findings remain");
167
+ }
168
+ lines.push("");
169
+ lines.push(`| Metric | Before | After |`);
170
+ lines.push(`|--------|--------|-------|`);
171
+ lines.push(`| Verdict | ${result.beforeVerdict} | ${result.afterVerdict} |`);
172
+ lines.push(`| Score | ${result.beforeScore}/100 | ${result.afterScore}/100 |`);
173
+ lines.push(`| Hash | \`${result.beforeHash}\` | \`${result.afterHash}\` |`);
174
+ lines.push("");
175
+ // Section-by-section comparison
176
+ lines.push("## Section Comparison");
177
+ lines.push("");
178
+ lines.push("| Section | Before | After | Status | Skipped? |");
179
+ lines.push("|---------|--------|-------|--------|----------|");
180
+ for (const s of result.sections) {
181
+ const statusIcon = s.status === "fully_resolved" ? "✅"
182
+ : s.status === "improved" ? "🔶"
183
+ : s.status === "unchanged" ? "🔴"
184
+ : "⛔";
185
+ const skippedMark = s.skipped ? "**YES — NOT ADDRESSED**" : "no";
186
+ lines.push(`| ${s.section} | ${s.before.findings} | ${s.after.findings} | ${statusIcon} ${s.status} | ${skippedMark} |`);
187
+ }
188
+ // Skipped sections warning
189
+ if (result.skippedSections.length > 0) {
190
+ lines.push("");
191
+ lines.push("## ⚠️ SKIPPED SECTIONS");
192
+ lines.push("");
193
+ lines.push("The following sections had findings but were **completely ignored** during remediation:");
194
+ lines.push("");
195
+ for (const name of result.skippedSections) {
196
+ const section = result.sections.find(s => s.section === name);
197
+ lines.push(`- **${name}**: ${section.before.findings} findings (${section.before.critical} critical, ${section.before.high} high) — ZERO progress`);
198
+ }
199
+ lines.push("");
200
+ lines.push("> **You cannot declare remediation complete while sections are skipped.** Use `remediation_plan` to get the specific actions for each skipped section.");
201
+ }
202
+ // Next actions
203
+ if (result.nextActions.length > 0) {
204
+ lines.push("");
205
+ lines.push("## Next Actions");
206
+ lines.push("");
207
+ for (const action of result.nextActions) {
208
+ lines.push(`- ${action}`);
209
+ }
210
+ }
211
+ // Summary
212
+ lines.push("");
213
+ lines.push("---");
214
+ lines.push(`**${result.summary}**`);
215
+ return lines.join("\n");
216
+ }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "guardvibe",
3
- "version": "3.0.8",
3
+ "version": "3.0.10",
4
4
  "mcpName": "io.github.goklab/guardvibe",
5
- "description": "Security MCP for vibe coding. 334 rules, 34 tools, CLI + doctor. Host security, auth coverage mapping, LLM-powered deep scan (IDOR/business logic), taint analysis. Plus Next.js, Supabase, Clerk, Stripe, Prisma, tRPC, Hono, GraphQL, Convex, Turso, Uploadthing, AI SDK, and the full AI-generated stack.",
5
+ "description": "Security MCP for vibe coding. 335 rules, 36 tools, CLI + doctor. Host security, auth coverage mapping, LLM-powered deep scan (IDOR/business logic), taint analysis. Plus Next.js, Supabase, Clerk, Stripe, Prisma, tRPC, Hono, GraphQL, Convex, Turso, Uploadthing, AI SDK, and the full AI-generated stack.",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "guardvibe": "build/cli.js",