@flamki/vibeguard 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,30 @@
1
+ name: Publish to npm
2
+
3
+ on:
4
+ release:
5
+ types: [created]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+
11
+ steps:
12
+ - name: Checkout repo
13
+ uses: actions/checkout@v4
14
+
15
+ - name: Setup Node
16
+ uses: actions/setup-node@v4
17
+ with:
18
+ node-version: 18
19
+ registry-url: https://registry.npmjs.org/
20
+
21
+ - name: Install dependencies
22
+ run: npm ci
23
+
24
+ - name: Build
25
+ run: npm run build
26
+
27
+ - name: Publish
28
+ run: npm publish --access public
29
+ env:
30
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/dist/cli.js ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const scanner_1 = require("./scanner");
10
+ const consoleReporter_1 = require("./reporter/consoleReporter");
11
+ const program = new commander_1.Command();
12
+ console.log(chalk_1.default.cyan.bold(`
13
+ 🛡️ VibeGuard
14
+ -----------------------
15
+ AI code safety net
16
+ `));
17
+ program
18
+ .name("vibeguard")
19
+ .description("Scan codebases for common AI-generated mistakes")
20
+ .version("0.1.0");
21
+ program
22
+ .command("scan")
23
+ .argument("<path>", "Path to project")
24
+ .action(async (path) => {
25
+ try {
26
+ const results = await (0, scanner_1.runScan)(path);
27
+ (0, consoleReporter_1.printReport)(results);
28
+ }
29
+ catch (err) {
30
+ console.error(chalk_1.default.red("❌ Scan failed"), err);
31
+ process.exit(1);
32
+ }
33
+ });
34
+ program.parse(process.argv);
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.printReport = printReport;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const path_1 = __importDefault(require("path"));
9
+ function printReport(results) {
10
+ if (results.length === 0) {
11
+ console.log(chalk_1.default.green("✅ No issues found. Ship it."));
12
+ return;
13
+ }
14
+ const grouped = {};
15
+ for (const r of results) {
16
+ const file = path_1.default.basename(r.file || "unknown");
17
+ grouped[file] ?? (grouped[file] = []);
18
+ grouped[file].push(r);
19
+ }
20
+ console.log(chalk_1.default.bold("\n🛡️ VibeGuard Report\n"));
21
+ for (const file in grouped) {
22
+ console.log(chalk_1.default.cyan(file));
23
+ for (const r of grouped[file]) {
24
+ const lineInfo = r.line ? `Line ${r.line}` : "";
25
+ console.log(` ${chalk_1.default.yellow("⚠️")} ${lineInfo} ${r.message}`);
26
+ }
27
+ console.log("");
28
+ }
29
+ console.log(chalk_1.default.bold(`Summary: ${results.length} warning(s)\n`));
30
+ }
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hallucinatedApiRule = hallucinatedApiRule;
4
+ /**
5
+ * Globals and well-known functions we NEVER want to flag
6
+ */
7
+ const ALLOWED_GLOBALS = new Set([
8
+ "fetch",
9
+ "console",
10
+ "setTimeout",
11
+ "setInterval",
12
+ "clearTimeout",
13
+ "clearInterval",
14
+ "require",
15
+ "import",
16
+ ]);
17
+ function hallucinatedApiRule(code, file) {
18
+ const warnings = [];
19
+ /**
20
+ * Matches function calls NOT preceded by a dot
21
+ * Example matched: getUserByIdSafe(
22
+ * Example ignored: obj.method(
23
+ */
24
+ const callRegex = /(?<!\.)\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g;
25
+ /**
26
+ * Matches function declarations, variables, and imports
27
+ */
28
+ const declaredRegex = /(function\s+([a-zA-Z_][a-zA-Z0-9_]*))|(const\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=)|(let\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=)|(var\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=)|import\s+.*\b([a-zA-Z_][a-zA-Z0-9_]*)\b/g;
29
+ const calls = new Set();
30
+ const declared = new Set();
31
+ let match;
32
+ // Collect all function calls
33
+ while ((match = callRegex.exec(code))) {
34
+ const fn = match[1];
35
+ // Skip globals, constructors, and obvious safe cases
36
+ if (ALLOWED_GLOBALS.has(fn) ||
37
+ fn[0] === fn[0].toUpperCase()) {
38
+ continue;
39
+ }
40
+ calls.add(fn);
41
+ }
42
+ // Collect declared functions / variables / imports
43
+ while ((match = declaredRegex.exec(code))) {
44
+ const name = match[2] ||
45
+ match[4] ||
46
+ match[6] ||
47
+ match[8] ||
48
+ match[9];
49
+ if (name) {
50
+ declared.add(name);
51
+ }
52
+ }
53
+ // Diff: calls that were never declared
54
+ for (const fn of calls) {
55
+ if (!declared.has(fn)) {
56
+ const line = code
57
+ .split("\n")
58
+ .findIndex((l) => l.includes(`${fn}(`)) + 1;
59
+ warnings.push({
60
+ ruleId: "hallucinated-api",
61
+ severity: "warning",
62
+ message: `Possible hallucinated API: ${fn}() is used but never defined or imported.`,
63
+ file,
64
+ line: line > 0 ? line : undefined,
65
+ });
66
+ }
67
+ }
68
+ return warnings;
69
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rules = void 0;
4
+ const localStorageToken_1 = require("./localStorageToken");
5
+ const missingTryCatch_1 = require("./missingTryCatch");
6
+ const hallucinatedApi_1 = require("./hallucinatedApi");
7
+ exports.rules = [
8
+ localStorageToken_1.localStorageTokenRule,
9
+ missingTryCatch_1.missingTryCatchRule,
10
+ hallucinatedApi_1.hallucinatedApiRule,
11
+ ];
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.localStorageTokenRule = localStorageTokenRule;
4
+ function localStorageTokenRule(code, file) {
5
+ const warnings = [];
6
+ const regex = /localStorage\.setItem\(['"`].*token.*['"`]/gi;
7
+ let match;
8
+ while ((match = regex.exec(code))) {
9
+ const line = code.slice(0, match.index).split("\n").length;
10
+ warnings.push({
11
+ ruleId: "local-storage-token",
12
+ severity: "warning",
13
+ message: "Token stored in localStorage. This is vulnerable to XSS. Use httpOnly cookies.",
14
+ file,
15
+ line,
16
+ });
17
+ }
18
+ return warnings;
19
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.missingTryCatchRule = missingTryCatchRule;
4
+ function missingTryCatchRule(code, file) {
5
+ if (code.includes("await") && !code.includes("try {")) {
6
+ const line = code
7
+ .split("\n")
8
+ .findIndex((l) => l.includes("await")) + 1;
9
+ return [
10
+ {
11
+ ruleId: "missing-try-catch",
12
+ severity: "warning",
13
+ message: "Async operation without try/catch. This may crash in production.",
14
+ file,
15
+ line,
16
+ },
17
+ ];
18
+ }
19
+ return [];
20
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.walkFiles = walkFiles;
7
+ const glob_1 = require("glob");
8
+ const path_1 = __importDefault(require("path"));
9
+ async function walkFiles(rootPath) {
10
+ const absoluteRoot = path_1.default.resolve(rootPath);
11
+ const pattern = "**/*.{js,ts,jsx,tsx}";
12
+ const files = await (0, glob_1.glob)(pattern, {
13
+ cwd: absoluteRoot,
14
+ absolute: true,
15
+ ignore: [
16
+ "**/node_modules/**",
17
+ "**/dist/**",
18
+ "**/.git/**",
19
+ "**/src/**"
20
+ ],
21
+ });
22
+ return files;
23
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runScan = runScan;
4
+ const fileWalker_1 = require("./fileWalker");
5
+ const scanFile_1 = require("./scanFile");
6
+ async function runScan(rootPath) {
7
+ const files = await (0, fileWalker_1.walkFiles)(rootPath);
8
+ const results = [];
9
+ for (const file of files) {
10
+ const warnings = await (0, scanFile_1.scanFile)(file);
11
+ results.push(...warnings);
12
+ }
13
+ return results;
14
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.scanFile = scanFile;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const rules_1 = require("../rules");
9
+ async function scanFile(filePath) {
10
+ const code = fs_1.default.readFileSync(filePath, "utf-8");
11
+ const warnings = [];
12
+ for (const rule of rules_1.rules) {
13
+ const result = rule(code, filePath);
14
+ warnings.push(...result);
15
+ }
16
+ return warnings;
17
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@flamki/vibeguard",
3
+ "version": "1.0.1",
4
+ "description": "A CLI safety net for AI-generated code",
5
+ "main": "dist/cli.js",
6
+ "type": "commonjs",
7
+ "bin": {
8
+ "vibeguard": "dist/cli.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "npm run build && node dist/cli.js scan ."
13
+ },
14
+ "keywords": [
15
+ "ai",
16
+ "cli",
17
+ "static-analysis",
18
+ "security",
19
+ "developer-tools",
20
+ "code-quality"
21
+ ],
22
+ "author": "Ayush Singh",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "chalk": "^5.3.0",
26
+ "commander": "^11.0.0",
27
+ "glob": "^10.3.10"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^20.0.0",
31
+ "typescript": "^5.3.0"
32
+ }
33
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from "commander";
4
+ import chalk from "chalk";
5
+ import { runScan } from "./scanner";
6
+ import { printReport } from "./reporter/consoleReporter";
7
+
8
+ const program = new Command();
9
+
10
+ console.log(
11
+ chalk.cyan.bold(`
12
+ 🛡️ VibeGuard
13
+ -----------------------
14
+ AI code safety net
15
+ `)
16
+ );
17
+
18
+ program
19
+ .name("vibeguard")
20
+ .description("Scan codebases for common AI-generated mistakes")
21
+ .version("0.1.0");
22
+
23
+ program
24
+ .command("scan")
25
+ .argument("<path>", "Path to project")
26
+ .action(async (path: string) => {
27
+ try {
28
+ const results = await runScan(path);
29
+ printReport(results);
30
+ } catch (err) {
31
+ console.error(chalk.red("❌ Scan failed"), err);
32
+ process.exit(1);
33
+ }
34
+ });
35
+
36
+ program.parse(process.argv);
@@ -0,0 +1,37 @@
1
+ import chalk from "chalk";
2
+ import { ScanWarning } from "../types";
3
+ import path from "path";
4
+
5
+ export function printReport(results: ScanWarning[]) {
6
+ if (results.length === 0) {
7
+ console.log(chalk.green("✅ No issues found. Ship it."));
8
+ return;
9
+ }
10
+
11
+ const grouped: Record<string, ScanWarning[]> = {};
12
+
13
+ for (const r of results) {
14
+ const file = path.basename(r.file || "unknown");
15
+ grouped[file] ??= [];
16
+ grouped[file].push(r);
17
+ }
18
+
19
+ console.log(chalk.bold("\n🛡️ VibeGuard Report\n"));
20
+
21
+ for (const file in grouped) {
22
+ console.log(chalk.cyan(file));
23
+
24
+ for (const r of grouped[file]) {
25
+ const lineInfo = r.line ? `Line ${r.line}` : "";
26
+ console.log(
27
+ ` ${chalk.yellow("⚠️")} ${lineInfo} ${r.message}`
28
+ );
29
+ }
30
+
31
+ console.log("");
32
+ }
33
+
34
+ console.log(
35
+ chalk.bold(`Summary: ${results.length} warning(s)\n`)
36
+ );
37
+ }
@@ -0,0 +1,89 @@
1
+ import { ScanWarning } from "../types";
2
+
3
+ /**
4
+ * Globals and well-known functions we NEVER want to flag
5
+ */
6
+ const ALLOWED_GLOBALS = new Set([
7
+ "fetch",
8
+ "console",
9
+ "setTimeout",
10
+ "setInterval",
11
+ "clearTimeout",
12
+ "clearInterval",
13
+ "require",
14
+ "import",
15
+ ]);
16
+
17
+ export function hallucinatedApiRule(
18
+ code: string,
19
+ file: string
20
+ ): ScanWarning[] {
21
+ const warnings: ScanWarning[] = [];
22
+
23
+ /**
24
+ * Matches function calls NOT preceded by a dot
25
+ * Example matched: getUserByIdSafe(
26
+ * Example ignored: obj.method(
27
+ */
28
+ const callRegex = /(?<!\.)\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g;
29
+
30
+ /**
31
+ * Matches function declarations, variables, and imports
32
+ */
33
+ const declaredRegex =
34
+ /(function\s+([a-zA-Z_][a-zA-Z0-9_]*))|(const\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=)|(let\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=)|(var\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=)|import\s+.*\b([a-zA-Z_][a-zA-Z0-9_]*)\b/g;
35
+
36
+ const calls = new Set<string>();
37
+ const declared = new Set<string>();
38
+
39
+ let match;
40
+
41
+ // Collect all function calls
42
+ while ((match = callRegex.exec(code))) {
43
+ const fn = match[1];
44
+
45
+ // Skip globals, constructors, and obvious safe cases
46
+ if (
47
+ ALLOWED_GLOBALS.has(fn) ||
48
+ fn[0] === fn[0].toUpperCase()
49
+ ) {
50
+ continue;
51
+ }
52
+
53
+ calls.add(fn);
54
+ }
55
+
56
+ // Collect declared functions / variables / imports
57
+ while ((match = declaredRegex.exec(code))) {
58
+ const name =
59
+ match[2] ||
60
+ match[4] ||
61
+ match[6] ||
62
+ match[8] ||
63
+ match[9];
64
+
65
+ if (name) {
66
+ declared.add(name);
67
+ }
68
+ }
69
+
70
+ // Diff: calls that were never declared
71
+ for (const fn of calls) {
72
+ if (!declared.has(fn)) {
73
+ const line =
74
+ code
75
+ .split("\n")
76
+ .findIndex((l) => l.includes(`${fn}(`)) + 1;
77
+
78
+ warnings.push({
79
+ ruleId: "hallucinated-api",
80
+ severity: "warning",
81
+ message: `Possible hallucinated API: ${fn}() is used but never defined or imported.`,
82
+ file,
83
+ line: line > 0 ? line : undefined,
84
+ });
85
+ }
86
+ }
87
+
88
+ return warnings;
89
+ }
@@ -0,0 +1,9 @@
1
+ import { localStorageTokenRule } from "./localStorageToken";
2
+ import { missingTryCatchRule } from "./missingTryCatch";
3
+ import { hallucinatedApiRule } from "./hallucinatedApi";
4
+
5
+ export const rules = [
6
+ localStorageTokenRule,
7
+ missingTryCatchRule,
8
+ hallucinatedApiRule,
9
+ ];
@@ -0,0 +1,26 @@
1
+ import { ScanWarning } from "../types";
2
+
3
+ export function localStorageTokenRule(
4
+ code: string,
5
+ file: string
6
+ ): ScanWarning[] {
7
+ const warnings: ScanWarning[] = [];
8
+ const regex = /localStorage\.setItem\(['"`].*token.*['"`]/gi;
9
+
10
+ let match;
11
+ while ((match = regex.exec(code))) {
12
+ const line =
13
+ code.slice(0, match.index).split("\n").length;
14
+
15
+ warnings.push({
16
+ ruleId: "local-storage-token",
17
+ severity: "warning",
18
+ message:
19
+ "Token stored in localStorage. This is vulnerable to XSS. Use httpOnly cookies.",
20
+ file,
21
+ line,
22
+ });
23
+ }
24
+
25
+ return warnings;
26
+ }
@@ -0,0 +1,25 @@
1
+ import { ScanWarning } from "../types";
2
+
3
+ export function missingTryCatchRule(
4
+ code: string,
5
+ file: string
6
+ ): ScanWarning[] {
7
+ if (code.includes("await") && !code.includes("try {")) {
8
+ const line = code
9
+ .split("\n")
10
+ .findIndex((l) => l.includes("await")) + 1;
11
+
12
+ return [
13
+ {
14
+ ruleId: "missing-try-catch",
15
+ severity: "warning",
16
+ message:
17
+ "Async operation without try/catch. This may crash in production.",
18
+ file,
19
+ line,
20
+ },
21
+ ];
22
+ }
23
+
24
+ return [];
25
+ }
@@ -0,0 +1,21 @@
1
+ import { glob } from "glob";
2
+ import path from "path";
3
+
4
+ export async function walkFiles(rootPath: string): Promise<string[]> {
5
+ const absoluteRoot = path.resolve(rootPath);
6
+
7
+ const pattern = "**/*.{js,ts,jsx,tsx}";
8
+
9
+ const files = await glob(pattern, {
10
+ cwd: absoluteRoot,
11
+ absolute: true,
12
+ ignore: [
13
+ "**/node_modules/**",
14
+ "**/dist/**",
15
+ "**/.git/**",
16
+ "**/src/**"
17
+ ],
18
+ });
19
+
20
+ return files;
21
+ }
@@ -0,0 +1,15 @@
1
+ import { walkFiles } from "./fileWalker";
2
+ import { scanFile } from "./scanFile";
3
+ import { ScanWarning } from "../types";
4
+
5
+ export async function runScan(rootPath: string): Promise<ScanWarning[]> {
6
+ const files = await walkFiles(rootPath);
7
+ const results: ScanWarning[] = [];
8
+
9
+ for (const file of files) {
10
+ const warnings = await scanFile(file);
11
+ results.push(...warnings);
12
+ }
13
+
14
+ return results;
15
+ }
@@ -0,0 +1,15 @@
1
+ import fs from "fs";
2
+ import { ScanWarning } from "../types";
3
+ import { rules } from "../rules";
4
+
5
+ export async function scanFile(filePath: string): Promise<ScanWarning[]> {
6
+ const code = fs.readFileSync(filePath, "utf-8");
7
+ const warnings: ScanWarning[] = [];
8
+
9
+ for (const rule of rules) {
10
+ const result = rule(code, filePath);
11
+ warnings.push(...result);
12
+ }
13
+
14
+ return warnings;
15
+ }
package/src/types.ts ADDED
@@ -0,0 +1,7 @@
1
+ export interface ScanWarning {
2
+ ruleId: string;
3
+ severity: "warning" | "error";
4
+ message: string;
5
+ line?: number;
6
+ file?: string;
7
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "CommonJS",
5
+ "rootDir": "src",
6
+ "outDir": "dist",
7
+
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+
12
+ "skipLibCheck": true
13
+ },
14
+ "include": ["src"]
15
+ }