@re3se/gitsniffer 1.0.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/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # GitSniffer
2
+ ### Smart Code Sentry for Your Terminal
3
+
4
+ > "If it's not clean, it's not finished."
5
+
6
+ GitSniffer is a CLI tool designed to act as an intelligent filter for your codebase. It ensures that no debug leftovers, private keys, or sloppy comments make their way into your repository. It's not just a linter, it's a gatekeeper for quality.
7
+
8
+ ## 🚀 The Philosophy
9
+ I build ecosystems where quality is non-negotiable. GitSniffer was born from a simple need: **intentionality**.
10
+
11
+ * **Security First**: Prevent API key leaks before they happen.
12
+ * **Clean Code**: Stop `console.log` and `debugger` statements from polluting production.
13
+ * **Efficiency**: Catch errors in the staging area, seconds before the commit.
14
+
15
+ ## ⚙️ How It Works
16
+ GitSniffer hooks into your workflow at the most critical moment: **pre-commit**.
17
+
18
+ 1. **Scans**: It analyzes *only* your staged changes (`git diff --cached`).
19
+ 2. **Sniffs**: Applies regex-based heuristics to detect code smells and security risks.
20
+ 3. **Blocks**: If it finds a critical error (like a private key), it stops the commit.
21
+
22
+ ## 🔧 Installation
23
+ Install it globally to use it across all your projects:
24
+
25
+ ```bash
26
+ npm install -g gitsniffer
27
+ ```
28
+
29
+ ## 🚀 Usage
30
+ Run it manually in any git repository:
31
+
32
+ ```bash
33
+ gitsniffer --run
34
+ ```
35
+
36
+ ### Automate with Git Hooks
37
+ To enforce quality standards automatically, add it to your pre-commit hook.
38
+
39
+ **Option 1: Raw Git Hook**
40
+ Add this to `.git/hooks/pre-commit` and make it executable (`chmod +x`):
41
+ ```bash
42
+ #!/bin/sh
43
+ gitsniffer --run
44
+ ```
45
+
46
+ **Option 2: Husky**
47
+ If you use Husky in your project:
48
+ ```bash
49
+ npx husky add .husky/pre-commit "gitsniffer --run"
50
+ ```
51
+
52
+ ## 🛡️ Default Rules
53
+ GitSniffer comes pre-configured with a zero-tolerance policy for:
54
+
55
+ * 🔴 **Private Keys** (AWS, RSA, generic private keys) -> `[ERROR]` (Blocks commit)
56
+ * 🔴 **Debugger Statements** -> `[ERROR]` (Blocks commit)
57
+ * 🟡 **Console Logs** -> `[WARNING]`
58
+ * 🔵 **TODO Comments** -> `[INFO]`
59
+
60
+ ## 🛠️ Tech Stack
61
+ Built with intentionality using:
62
+ * **Node.js**
63
+ * **Commander.js**
64
+ * **Execa**
65
+ * **Chalk**
66
+
67
+ ---
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../src/index.js';
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@re3se/gitsniffer",
3
+ "version": "1.0.0",
4
+ "description": "An intelligent CLI tool that prevents bad code and security leaks from entering your repository.",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "gitsniffer": "./bin/gitsniffer.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node src/index.js",
12
+ "test": "echo \"Error: no test specified\" && exit 1"
13
+ },
14
+ "keywords": [
15
+ "git",
16
+ "cli",
17
+ "linter",
18
+ "security",
19
+ "clean-code",
20
+ "pre-commit"
21
+ ],
22
+ "author": "re3se",
23
+ "license": "ISC",
24
+ "dependencies": {
25
+ "chalk": "^5.3.0",
26
+ "commander": "^12.0.0",
27
+ "execa": "^8.0.0"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ }
32
+ }
package/src/config.js ADDED
@@ -0,0 +1,32 @@
1
+ export const defaultRules = [
2
+ {
3
+ id: 'no-console',
4
+ message: 'Found console.log usage',
5
+ regex: /^(?!\s*\/\/).*console\.log\(/,
6
+ severity: 'warning'
7
+ },
8
+ {
9
+ id: 'no-todo',
10
+ message: 'Found TODO comment',
11
+ regex: /\/\/\s*TODO:/i,
12
+ severity: 'info'
13
+ },
14
+ {
15
+ id: 'no-private-keys',
16
+ message: 'Potential private key found',
17
+ regex: /-----BEGIN PRIVATE KEY-----/,
18
+ severity: 'error'
19
+ },
20
+ {
21
+ id: 'no-aws-keys',
22
+ message: 'Potential AWS Access Key found',
23
+ regex: /(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}/,
24
+ severity: 'error'
25
+ },
26
+ {
27
+ id: 'no-debugger',
28
+ message: 'Found debugger statement',
29
+ regex: /debugger;?/,
30
+ severity: 'error'
31
+ }
32
+ ];
package/src/git.js ADDED
@@ -0,0 +1,69 @@
1
+ import { execa } from 'execa';
2
+ import { extname } from 'path';
3
+
4
+ const BINARY_EXTENSIONS = new Set([
5
+ '.png', '.jpg', '.jpeg', '.gif', '.ico',
6
+ '.pdf', '.zip', '.tar', '.gz', '.7z',
7
+ '.exe', '.dll', '.so', '.dylib', '.bin',
8
+ '.mp3', '.mp4', '.mov', '.avi', '.woff', '.woff2', '.ttf', '.eot'
9
+ ]);
10
+
11
+ export async function getGitDiff() {
12
+ try {
13
+ const { stdout: stagedFiles } = await execa('git', ['diff', '--name-only', '--cached']);
14
+
15
+ const { stdout: diffOutput } = await execa('git', ['diff', '--cached', '--unified=0']);
16
+
17
+ return parseDiff(diffOutput);
18
+ } catch (error) {
19
+ console.error('Error executing git command:', error.message);
20
+ return [];
21
+ }
22
+ }
23
+
24
+ function parseDiff(diffOutput) {
25
+ const files = [];
26
+ let currentFile = null;
27
+
28
+ const lines = diffOutput.split('\n');
29
+
30
+ for (const line of lines) {
31
+ if (line.startsWith('diff --git')) {
32
+ const match = line.match(/b\/(.*)$/);
33
+ if (match) {
34
+ const filePath = match[1];
35
+
36
+ if (isBinaryFile(filePath)) {
37
+ currentFile = null;
38
+ continue;
39
+ }
40
+
41
+ currentFile = {
42
+ path: filePath,
43
+ changes: []
44
+ };
45
+ files.push(currentFile);
46
+ }
47
+ } else if (line.startsWith('@@')) {
48
+ const match = line.match(/\+(\d+)(?:,(\d+))?/);
49
+ if (match && currentFile) {
50
+ currentFile.currentLineNumber = parseInt(match[1], 10);
51
+ }
52
+ } else if (line.startsWith('+') && !line.startsWith('+++')) {
53
+ if (currentFile && currentFile.currentLineNumber !== undefined) {
54
+ currentFile.changes.push({
55
+ line: currentFile.currentLineNumber,
56
+ content: line.substring(1)
57
+ });
58
+ currentFile.currentLineNumber++;
59
+ }
60
+ }
61
+ }
62
+
63
+ return files;
64
+ }
65
+
66
+ function isBinaryFile(filePath) {
67
+ const ext = extname(filePath).toLowerCase();
68
+ return BINARY_EXTENSIONS.has(ext);
69
+ }
package/src/index.js ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import { getGitDiff } from './git.js';
6
+ import { scanCode } from './scanner.js';
7
+ import { reportIssues } from './reporter.js';
8
+ import { defaultRules } from './config.js';
9
+ import { readFileSync } from 'fs';
10
+ import { join, dirname } from 'path';
11
+ import { fileURLToPath } from 'url';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+
16
+ const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
17
+
18
+ const program = new Command();
19
+
20
+ program
21
+ .name('gitsniffer')
22
+ .description('Smart CLI tool to prevent committing bad code')
23
+ .version(packageJson.version);
24
+
25
+ program
26
+ .option('-r, --run', 'Run the sniffer on staged files')
27
+ .action(async (options) => {
28
+ if (options.run) {
29
+ console.log(chalk.cyan('Starting code analysis...'));
30
+
31
+ const files = await getGitDiff();
32
+
33
+ if (files.length === 0) {
34
+ console.log(chalk.green('No staged changes detected.'));
35
+ return;
36
+ }
37
+
38
+ const issues = scanCode(files, defaultRules);
39
+ const exitCode = reportIssues(issues);
40
+
41
+ process.exit(exitCode);
42
+ } else {
43
+ program.help();
44
+ }
45
+ });
46
+
47
+ program.parse(process.argv);
@@ -0,0 +1,52 @@
1
+ import chalk from 'chalk';
2
+
3
+ export function reportIssues(issues) {
4
+ if (issues.length === 0) {
5
+ console.log(chalk.green('No issues found.'));
6
+ return 0;
7
+ }
8
+
9
+ console.log(chalk.bold.underline(`Found ${issues.length} potential issues:\n`));
10
+
11
+ let errorCount = 0;
12
+ let warningCount = 0;
13
+
14
+ issues.forEach(issue => {
15
+ const location = chalk.gray(`${issue.file}:${issue.line}`);
16
+ let severityLabel;
17
+ let messageColor;
18
+
19
+ switch (issue.severity) {
20
+ case 'error':
21
+ severityLabel = chalk.red('[ERROR]');
22
+ messageColor = chalk.red;
23
+ errorCount++;
24
+ break;
25
+ case 'warning':
26
+ severityLabel = chalk.yellow('[WARNING]');
27
+ messageColor = chalk.yellow;
28
+ warningCount++;
29
+ break;
30
+ case 'info':
31
+ severityLabel = chalk.blue('[INFO]');
32
+ messageColor = chalk.blue;
33
+ break;
34
+ default:
35
+ severityLabel = chalk.white('[UNKNOWN]');
36
+ messageColor = chalk.white;
37
+ }
38
+
39
+ console.log(` ${severityLabel} ${messageColor(issue.message)}`);
40
+ console.log(` Location: ${location}`);
41
+ console.log(` Code: ${chalk.gray(issue.content)}`);
42
+ console.log('');
43
+ });
44
+
45
+ if (errorCount > 0) {
46
+ console.log(chalk.red.bold(`Analysis complete: ${errorCount} errors and ${warningCount} warnings found.`));
47
+ return 1;
48
+ } else {
49
+ console.log(chalk.yellow.bold(`Analysis complete: ${warningCount} warnings found.`));
50
+ return 0;
51
+ }
52
+ }
package/src/scanner.js ADDED
@@ -0,0 +1,22 @@
1
+ export function scanCode(files, rules) {
2
+ const issues = [];
3
+
4
+ for (const file of files) {
5
+ for (const change of file.changes) {
6
+ for (const rule of rules) {
7
+ if (rule.regex.test(change.content)) {
8
+ issues.push({
9
+ file: file.path,
10
+ line: change.line,
11
+ content: change.content.trim(),
12
+ ruleId: rule.id,
13
+ message: rule.message,
14
+ severity: rule.severity
15
+ });
16
+ }
17
+ }
18
+ }
19
+ }
20
+
21
+ return issues;
22
+ }