vibe-checking 1.0.3 → 1.2.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 +57 -17
- package/dist/analysis/behavior.d.ts +11 -0
- package/dist/analysis/behavior.js +105 -0
- package/dist/analysis/behavior.js.map +1 -0
- package/dist/claude/correlator.js +82 -64
- package/dist/claude/correlator.js.map +1 -1
- package/dist/hooks/installer.d.ts +3 -0
- package/dist/hooks/installer.js +79 -0
- package/dist/hooks/installer.js.map +1 -0
- package/dist/index.js +46 -4
- package/dist/index.js.map +1 -1
- package/dist/repl/display.d.ts +3 -2
- package/dist/repl/display.js +50 -25
- package/dist/repl/display.js.map +1 -1
- package/dist/repl/repl.d.ts +4 -3
- package/dist/repl/repl.js +23 -10
- package/dist/repl/repl.js.map +1 -1
- package/dist/report/html.js +13 -23
- package/dist/report/html.js.map +1 -1
- package/dist/scanners/aggregator.d.ts +1 -1
- package/dist/scanners/aggregator.js.map +1 -1
- package/dist/scanners/deps.js +0 -1
- package/dist/scanners/deps.js.map +1 -1
- package/dist/scanners/gitleaks.js +0 -1
- package/dist/scanners/gitleaks.js.map +1 -1
- package/dist/scanners/rls.js +0 -3
- package/dist/scanners/rls.js.map +1 -1
- package/dist/scanners/semgrep.js +0 -1
- package/dist/scanners/semgrep.js.map +1 -1
- package/dist/state/store.d.ts +5 -0
- package/dist/state/store.js +48 -0
- package/dist/state/store.js.map +1 -0
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,41 +1,57 @@
|
|
|
1
1
|
# vibecheck
|
|
2
2
|
|
|
3
|
-
Security audit for AI-generated codebases.
|
|
3
|
+
Security audit for AI-generated codebases.
|
|
4
|
+
|
|
5
|
+
vibecheck finds security vulnerabilities in your code and traces each one back to the AI prompt that introduced it.
|
|
6
|
+
|
|
7
|
+
Run it before you deploy, or whenever you want to check what your AI sessions left behind.
|
|
4
8
|
|
|
5
9
|
No account. No API key. Nothing leaves your machine.
|
|
6
10
|
|
|
7
11
|
## Quick start
|
|
8
12
|
|
|
9
13
|
```bash
|
|
10
|
-
npx vibe-checking --with-cursor-history
|
|
14
|
+
npx vibe-checking --with-cursor-history --with-claude-history
|
|
11
15
|
```
|
|
12
16
|
|
|
13
|
-
Run this from your project directory.
|
|
17
|
+
Run this from your project directory. To scan automatically on every push:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx vibe-checking hook install
|
|
21
|
+
```
|
|
14
22
|
|
|
15
23
|
## What it checks
|
|
16
24
|
|
|
17
25
|
- **Secrets** — leaked API keys, tokens, credentials in git history (via gitleaks)
|
|
18
|
-
- **Code vulnerabilities** — injection, XSS,
|
|
26
|
+
- **Code vulnerabilities** — injection, XSS, crypto issues, auth gaps (via semgrep)
|
|
19
27
|
- **Supabase RLS** — tables missing Row Level Security in your migrations
|
|
20
28
|
- **Dependencies** — known CVEs in your packages (via npm audit)
|
|
21
29
|
|
|
22
|
-
gitleaks and semgrep are auto-installed if missing.
|
|
30
|
+
gitleaks and semgrep are auto-installed if missing.
|
|
31
|
+
|
|
32
|
+
## Vibe coding analysis
|
|
23
33
|
|
|
24
|
-
|
|
34
|
+
When you add `--with-cursor-history` or `--with-claude-history`, vibecheck reads the session files that Cursor and Claude Code store locally on your machine. It performs two analyses:
|
|
25
35
|
|
|
26
|
-
|
|
36
|
+
**Prompt tracing** — matches each security finding to the AI prompt that generated the vulnerable code. Shows what the prompt asked for, what file was generated, and what security constraints were missing.
|
|
27
37
|
|
|
28
|
-
|
|
38
|
+
**Behavior analysis** — looks at how you interacted with the AI across all sessions:
|
|
39
|
+
- **Blind approval chains** — sequences of 3+ "ok/continue/oui" where code was accepted without review
|
|
40
|
+
- **High-delegation prompts** — short prompts that generated large amounts of code
|
|
41
|
+
- **Files without review** — generated files never mentioned again in the conversation
|
|
42
|
+
- **Security-blind sessions** — sessions where security was never mentioned in any prompt
|
|
29
43
|
|
|
30
|
-
Without these flags, you still get the full security scan — just without the prompt
|
|
44
|
+
Without these flags, you still get the full security scan — just without the prompt tracing and behavior analysis.
|
|
31
45
|
|
|
32
46
|
## Usage
|
|
33
47
|
|
|
34
48
|
```bash
|
|
35
49
|
npx vibe-checking # security scan only
|
|
36
|
-
npx vibe-checking --with-cursor-history # scan +
|
|
37
|
-
npx vibe-checking --with-claude-history # scan +
|
|
38
|
-
npx vibe-checking --with-cursor-history --with-claude-history # scan +
|
|
50
|
+
npx vibe-checking --with-cursor-history # scan + vibe analysis (Cursor)
|
|
51
|
+
npx vibe-checking --with-claude-history # scan + vibe analysis (Claude)
|
|
52
|
+
npx vibe-checking --with-cursor-history --with-claude-history # scan + vibe analysis (both)
|
|
53
|
+
npx vibe-checking hook install # add pre-push hook
|
|
54
|
+
npx vibe-checking hook remove # remove pre-push hook
|
|
39
55
|
```
|
|
40
56
|
|
|
41
57
|
## Interactive commands
|
|
@@ -44,16 +60,40 @@ Once the scan completes, you get an interactive prompt:
|
|
|
44
60
|
|
|
45
61
|
| Command | Action |
|
|
46
62
|
|---------|--------|
|
|
47
|
-
| `1`, `2`, `3`... | Inspect a finding |
|
|
48
|
-
| `
|
|
63
|
+
| `1`, `2`, `3`... | Inspect a finding (shows prompt trace + missing constraints) |
|
|
64
|
+
| `solved` | Mark finding as fixed in code |
|
|
49
65
|
| `ignore` | Dismiss the current finding |
|
|
50
66
|
| `next` | Jump to the next open finding |
|
|
51
67
|
| `list` | Reprint the list with updated score |
|
|
52
|
-
| `
|
|
68
|
+
| `vibe` | Show vibe coding behavior analysis |
|
|
69
|
+
| `q` | Save statuses and write report |
|
|
70
|
+
|
|
71
|
+
## Persistent statuses
|
|
72
|
+
|
|
73
|
+
Findings you mark as `solved` or `ignore` are saved in a `.vibecheck` file at the root of your repo. On the next scan, vibecheck loads these statuses:
|
|
74
|
+
|
|
75
|
+
- **ignored** findings stay dismissed
|
|
76
|
+
- **solved** findings are re-checked — if the vulnerability is still there, it goes back to open
|
|
77
|
+
|
|
78
|
+
Commit `.vibecheck` to share decisions with your team. If all findings are handled, the scan passes silently.
|
|
79
|
+
|
|
80
|
+
## Git hook
|
|
81
|
+
|
|
82
|
+
`npx vibe-checking hook install` adds a pre-push hook to your repo. Every time you `git push`, vibecheck runs a full scan. If there are open findings, the REPL opens and you need to handle them before the push goes through. If everything is already solved or ignored, the push passes immediately.
|
|
83
|
+
|
|
84
|
+
To skip the hook once: `git push --no-verify`.
|
|
85
|
+
|
|
86
|
+
## How it works
|
|
53
87
|
|
|
54
|
-
|
|
88
|
+
Everything runs locally:
|
|
89
|
+
- **gitleaks** and **semgrep** are local binaries that scan your code and git history
|
|
90
|
+
- **RLS analysis** parses your SQL migration files directly
|
|
91
|
+
- **npm audit** checks your lock file against the npm vulnerability database
|
|
92
|
+
- **Prompt history** is read from local files (`~/.claude/projects/` and `~/.cursor/projects/`)
|
|
93
|
+
- **Behavior analysis** is pattern matching on your prompt text and session structure
|
|
94
|
+
- **Statuses** are saved in `.vibecheck` at the root of your repo
|
|
55
95
|
|
|
56
|
-
|
|
96
|
+
No API keys needed. No code uploaded.
|
|
57
97
|
|
|
58
98
|
---
|
|
59
99
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ClaudeSession, Finding } from "../types.js";
|
|
2
|
+
export interface VibeAnalysis {
|
|
3
|
+
blindChains: number;
|
|
4
|
+
highDelegationPrompts: number;
|
|
5
|
+
filesWithoutReview: number;
|
|
6
|
+
sessionsWithoutSecurityMention: number;
|
|
7
|
+
totalSessions: number;
|
|
8
|
+
totalPrompts: number;
|
|
9
|
+
findingsFromBlindChains: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function analyzeVibePatterns(sessions: ClaudeSession[], findings: Finding[]): VibeAnalysis;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const GENERIC_PROMPT_RE = /^(ok|oui|yes|yep|go|continue|next|sure|d'accord|parfait|merci|thanks|good|bien|c'est bon|les autres|et les autres|la suite)/i;
|
|
2
|
+
const SECURITY_KEYWORDS = [
|
|
3
|
+
"security", "auth", "validation", "rls", "sanitize", "encrypt",
|
|
4
|
+
"permission", "policy", "access control", "xss", "injection",
|
|
5
|
+
"csrf", "cors", "secret", "credential", "token verification",
|
|
6
|
+
"signature", "webhook secret", "row level",
|
|
7
|
+
];
|
|
8
|
+
const BLIND_CHAIN_THRESHOLD = 3;
|
|
9
|
+
const HIGH_DELEGATION_RATIO = 20;
|
|
10
|
+
function isGeneric(text) {
|
|
11
|
+
const clean = text.replace(/\s+/g, " ").trim();
|
|
12
|
+
return clean.length < 25 || GENERIC_PROMPT_RE.test(clean);
|
|
13
|
+
}
|
|
14
|
+
function wordCount(text) {
|
|
15
|
+
return text.trim().split(/\s+/).filter(Boolean).length;
|
|
16
|
+
}
|
|
17
|
+
export function analyzeVibePatterns(sessions, findings) {
|
|
18
|
+
let blindChains = 0;
|
|
19
|
+
let highDelegationPrompts = 0;
|
|
20
|
+
let filesWithoutReview = 0;
|
|
21
|
+
let sessionsWithoutSecurityMention = 0;
|
|
22
|
+
let totalPrompts = 0;
|
|
23
|
+
const blindChainFiles = new Set();
|
|
24
|
+
for (const session of sessions) {
|
|
25
|
+
const prompts = session.prompts;
|
|
26
|
+
totalPrompts += prompts.length;
|
|
27
|
+
// Signal 1: blind approval chains
|
|
28
|
+
let consecutiveGeneric = 0;
|
|
29
|
+
for (const prompt of prompts) {
|
|
30
|
+
if (isGeneric(prompt.text)) {
|
|
31
|
+
consecutiveGeneric++;
|
|
32
|
+
if (consecutiveGeneric >= BLIND_CHAIN_THRESHOLD) {
|
|
33
|
+
if (consecutiveGeneric === BLIND_CHAIN_THRESHOLD)
|
|
34
|
+
blindChains++;
|
|
35
|
+
for (const f of prompt.filesGenerated) {
|
|
36
|
+
blindChainFiles.add(f);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
consecutiveGeneric = 0;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Signal 2: high delegation ratio
|
|
45
|
+
for (const prompt of prompts) {
|
|
46
|
+
if (prompt.toolCalls.length === 0)
|
|
47
|
+
continue;
|
|
48
|
+
const words = wordCount(prompt.text);
|
|
49
|
+
if (words < 3)
|
|
50
|
+
continue;
|
|
51
|
+
let linesGenerated = 0;
|
|
52
|
+
for (const tc of prompt.toolCalls) {
|
|
53
|
+
if (tc.content) {
|
|
54
|
+
linesGenerated += tc.content.split("\n").length;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (linesGenerated > 0 && linesGenerated / words > HIGH_DELEGATION_RATIO) {
|
|
58
|
+
highDelegationPrompts++;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Signal 3: files accepted without review
|
|
62
|
+
const allGeneratedFiles = new Set();
|
|
63
|
+
const mentionedInFollowUp = new Set();
|
|
64
|
+
for (let i = 0; i < prompts.length; i++) {
|
|
65
|
+
for (const f of prompts[i].filesGenerated) {
|
|
66
|
+
allGeneratedFiles.add(f);
|
|
67
|
+
}
|
|
68
|
+
// Check if later prompts mention any previously generated files
|
|
69
|
+
if (i > 0) {
|
|
70
|
+
const promptLower = prompts[i].text.toLowerCase();
|
|
71
|
+
for (const f of allGeneratedFiles) {
|
|
72
|
+
const basename = f.split("/").pop() || f;
|
|
73
|
+
if (promptLower.includes(basename.toLowerCase())) {
|
|
74
|
+
mentionedInFollowUp.add(f);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
filesWithoutReview += allGeneratedFiles.size - mentionedInFollowUp.size;
|
|
80
|
+
// Signal 4: no security mention
|
|
81
|
+
const hasSecurityMention = prompts.some((p) => {
|
|
82
|
+
const lower = p.text.toLowerCase();
|
|
83
|
+
return SECURITY_KEYWORDS.some((kw) => lower.includes(kw));
|
|
84
|
+
});
|
|
85
|
+
if (!hasSecurityMention) {
|
|
86
|
+
sessionsWithoutSecurityMention++;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Cross-reference: how many findings come from blind chain files
|
|
90
|
+
const findingsFromBlindChains = findings.filter((f) => {
|
|
91
|
+
if (!f.trace)
|
|
92
|
+
return false;
|
|
93
|
+
return Array.from(blindChainFiles).some((bcf) => bcf.endsWith(f.path) || f.path.endsWith(bcf.split("/").pop() || ""));
|
|
94
|
+
}).length;
|
|
95
|
+
return {
|
|
96
|
+
blindChains,
|
|
97
|
+
highDelegationPrompts,
|
|
98
|
+
filesWithoutReview,
|
|
99
|
+
sessionsWithoutSecurityMention,
|
|
100
|
+
totalSessions: sessions.length,
|
|
101
|
+
totalPrompts,
|
|
102
|
+
findingsFromBlindChains,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=behavior.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"behavior.js","sourceRoot":"","sources":["../../src/analysis/behavior.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,6 @@ export function correlateFindings(findings, sessions, repoPath) {
|
|
|
6
6
|
const correlation = findCorrelation(finding, sessions, repoPath);
|
|
7
7
|
if (correlation) {
|
|
8
8
|
finding.trace = correlation.trace;
|
|
9
|
-
finding.fix = correlation.fix;
|
|
10
9
|
}
|
|
11
10
|
else if (!finding.manual) {
|
|
12
11
|
finding.manual = guessManualNote(finding);
|
|
@@ -52,9 +51,8 @@ function findCorrelation(finding, sessions, repoPath) {
|
|
|
52
51
|
return false;
|
|
53
52
|
});
|
|
54
53
|
if (matchedFile) {
|
|
55
|
-
const trace = buildTrace(finding, prompt, session, matchedFile);
|
|
56
|
-
|
|
57
|
-
return { trace, fix };
|
|
54
|
+
const trace = buildTrace(finding, prompt, session, matchedFile, repoPath);
|
|
55
|
+
return { trace };
|
|
58
56
|
}
|
|
59
57
|
}
|
|
60
58
|
}
|
|
@@ -71,14 +69,22 @@ function extractFilePath(path) {
|
|
|
71
69
|
// "supabase/migrations/0007_orgs.sql" or "app/api/upload/route.ts"
|
|
72
70
|
return path;
|
|
73
71
|
}
|
|
74
|
-
function
|
|
72
|
+
function relativize(filePath, repoPath) {
|
|
73
|
+
const prefix = repoPath.endsWith("/") ? repoPath : repoPath + "/";
|
|
74
|
+
if (filePath.startsWith(prefix))
|
|
75
|
+
return filePath.slice(prefix.length);
|
|
76
|
+
return filePath;
|
|
77
|
+
}
|
|
78
|
+
function buildTrace(finding, prompt, session, matchedFile, repoPath) {
|
|
75
79
|
const ts = formatTimestamp(prompt.timestamp || session.timestamp);
|
|
76
80
|
const lineCount = estimateLineCount(prompt, matchedFile);
|
|
81
|
+
const relFile = relativize(matchedFile, repoPath);
|
|
77
82
|
return {
|
|
78
83
|
prompt: `"${truncate(prompt.text, 120)}"`,
|
|
79
84
|
session: `${ts} · claude code`,
|
|
80
|
-
file: `${
|
|
85
|
+
file: `${relFile}${lineCount ? ` (+${lineCount} lines)` : ""}`,
|
|
81
86
|
result: inferResult(finding),
|
|
87
|
+
missingConstraints: detectMissingConstraints(finding, prompt),
|
|
82
88
|
};
|
|
83
89
|
}
|
|
84
90
|
function inferResult(finding) {
|
|
@@ -112,67 +118,79 @@ function inferResult(finding) {
|
|
|
112
118
|
}
|
|
113
119
|
return `The prompt produced this code without the security constraint. The omission led to ${finding.title.toLowerCase()}.`;
|
|
114
120
|
}
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
const CONCERN_MAP = {
|
|
122
|
+
crypto: [
|
|
123
|
+
{ keywords: ["auth tag", "authentication tag", "gcm tag", "tag verification", "verify tag"], label: "No mention of auth tag verification for GCM mode" },
|
|
124
|
+
{ keywords: ["key derivation", "kdf", "pbkdf", "scrypt", "argon"], label: "No mention of key derivation function" },
|
|
125
|
+
{ keywords: ["tamper", "integrity", "reject ciphertext", "verify integrity"], label: "No mention of tampered ciphertext rejection" },
|
|
126
|
+
{ keywords: ["iv", "nonce", "initialization vector", "random iv"], label: "No mention of IV/nonce management" },
|
|
127
|
+
],
|
|
128
|
+
rls: [
|
|
129
|
+
{ keywords: ["rls", "row level security", "enable rls"], label: "No mention of Row Level Security" },
|
|
130
|
+
{ keywords: ["policy", "policies", "access policy"], label: "No mention of access policy definition" },
|
|
131
|
+
{ keywords: ["anon", "anonymous", "deny anon", "block anon"], label: "No mention of denying anonymous access" },
|
|
132
|
+
],
|
|
133
|
+
injection: [
|
|
134
|
+
{ keywords: ["parameterized", "prepared statement", "bind param", "placeholder"], label: "No mention of parameterized queries" },
|
|
135
|
+
{ keywords: ["validate input", "input validation", "sanitize input"], label: "No mention of input validation" },
|
|
136
|
+
],
|
|
137
|
+
webhook: [
|
|
138
|
+
{ keywords: ["signature", "verify signature", "stripe-signature", "webhook secret"], label: "No mention of signature verification" },
|
|
139
|
+
{ keywords: ["replay", "idempotency", "idempotent"], label: "No mention of replay protection" },
|
|
140
|
+
],
|
|
141
|
+
xss: [
|
|
142
|
+
{ keywords: ["sanitize", "sanitization", "escape", "encoding", "encode"], label: "No mention of output sanitization/encoding" },
|
|
143
|
+
{ keywords: ["csp", "content-security-policy", "content security"], label: "No mention of Content Security Policy" },
|
|
144
|
+
{ keywords: ["innerhtml", "dangerouslysetinnerhtml", "raw html"], label: "No mention of avoiding raw HTML insertion" },
|
|
145
|
+
],
|
|
146
|
+
auth: [
|
|
147
|
+
{ keywords: ["session", "token", "jwt", "verify token", "check session"], label: "No mention of session/token verification" },
|
|
148
|
+
{ keywords: ["access control", "authorization", "permission", "role"], label: "No mention of access control" },
|
|
149
|
+
{ keywords: ["401", "unauthorized", "reject unauthenticated"], label: "No mention of rejecting unauthenticated requests" },
|
|
150
|
+
],
|
|
151
|
+
upload: [
|
|
152
|
+
{ keywords: ["mime", "file type", "content-type", "allowed types"], label: "No mention of MIME type validation" },
|
|
153
|
+
{ keywords: ["size limit", "max size", "file size", "cap size"], label: "No mention of file size limit" },
|
|
154
|
+
{ keywords: ["filename", "sanitize name", "path traversal"], label: "No mention of filename sanitization" },
|
|
155
|
+
],
|
|
156
|
+
};
|
|
157
|
+
function categorizeFinding(finding) {
|
|
117
158
|
const title = finding.title.toLowerCase();
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (title.includes("
|
|
126
|
-
return
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
`against path traversal, require an authenticated session, and enforce an`,
|
|
130
|
-
`RLS policy so a user can only write to their own folder."`,
|
|
131
|
-
];
|
|
132
|
-
}
|
|
133
|
-
if (title.includes("injection") || title.includes("interpolat")) {
|
|
134
|
-
return [
|
|
135
|
-
`"${truncate(prompt.text, 80)}.`,
|
|
136
|
-
`Validate the params as ISO dates and use parameterized Supabase filters —`,
|
|
137
|
-
`never string-concatenate user input into the query."`,
|
|
138
|
-
];
|
|
139
|
-
}
|
|
140
|
-
if (title.includes("webhook") || title.includes("signature")) {
|
|
141
|
-
return [
|
|
142
|
-
`"${truncate(prompt.text, 80)}.`,
|
|
143
|
-
`Verify the Stripe-Signature header against the webhook secret and reject`,
|
|
144
|
-
`any event that fails verification."`,
|
|
145
|
-
];
|
|
146
|
-
}
|
|
147
|
-
if (title.includes("xss") || title.includes("cross-site")) {
|
|
148
|
-
return [
|
|
149
|
-
`"${truncate(prompt.text, 80)}.`,
|
|
150
|
-
`Sanitize all user-provided content before rendering. Use proper encoding`,
|
|
151
|
-
`and never set innerHTML with unescaped user data."`,
|
|
152
|
-
];
|
|
153
|
-
}
|
|
159
|
+
const meta = finding.meta.toLowerCase();
|
|
160
|
+
if (title.includes("rls") || title.includes("row level security"))
|
|
161
|
+
return "rls";
|
|
162
|
+
if (title.includes("upload") || title.includes("validation"))
|
|
163
|
+
return "upload";
|
|
164
|
+
if (title.includes("injection") || title.includes("interpolat"))
|
|
165
|
+
return "injection";
|
|
166
|
+
if (title.includes("webhook") || title.includes("signature"))
|
|
167
|
+
return "webhook";
|
|
168
|
+
if (title.includes("xss") || title.includes("cross-site"))
|
|
169
|
+
return "xss";
|
|
154
170
|
if (title.includes("cipher") || title.includes("crypto") ||
|
|
155
171
|
title.includes("gcm") || title.includes("decipher") ||
|
|
156
|
-
title.includes("hash") || title.includes("hmac"))
|
|
157
|
-
return
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
];
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
172
|
+
title.includes("hash") || title.includes("hmac"))
|
|
173
|
+
return "crypto";
|
|
174
|
+
if (meta.includes("auth") || title.includes("auth"))
|
|
175
|
+
return "auth";
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
function detectMissingConstraints(finding, prompt) {
|
|
179
|
+
const category = categorizeFinding(finding);
|
|
180
|
+
if (!category)
|
|
181
|
+
return [];
|
|
182
|
+
const concerns = CONCERN_MAP[category];
|
|
183
|
+
if (!concerns)
|
|
184
|
+
return [];
|
|
185
|
+
const promptText = prompt.text.toLowerCase();
|
|
186
|
+
const missing = [];
|
|
187
|
+
for (const concern of concerns) {
|
|
188
|
+
const mentioned = concern.keywords.some((kw) => promptText.includes(kw));
|
|
189
|
+
if (!mentioned) {
|
|
190
|
+
missing.push(concern.label);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return missing;
|
|
176
194
|
}
|
|
177
195
|
function formatTimestamp(ts) {
|
|
178
196
|
if (!ts)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"correlator.js","sourceRoot":"","sources":["../../src/claude/correlator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"correlator.js","sourceRoot":"","sources":["../../src/claude/correlator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { writeFile, unlink, readFile, chmod, mkdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
const MARKER = "# vibecheck hook";
|
|
6
|
+
const PRE_PUSH_SCRIPT = `#!/bin/sh
|
|
7
|
+
${MARKER}
|
|
8
|
+
npx vibe-checking --with-cursor-history --with-claude-history
|
|
9
|
+
`;
|
|
10
|
+
function hookPath(repoPath) {
|
|
11
|
+
return join(repoPath, ".git", "hooks", "pre-push");
|
|
12
|
+
}
|
|
13
|
+
export function isHookInstalled(repoPath) {
|
|
14
|
+
const path = hookPath(repoPath);
|
|
15
|
+
if (!existsSync(path))
|
|
16
|
+
return false;
|
|
17
|
+
try {
|
|
18
|
+
const content = readFileSync(path, "utf-8");
|
|
19
|
+
return content.includes(MARKER);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export async function installHook(repoPath) {
|
|
26
|
+
const hooksDir = join(repoPath, ".git", "hooks");
|
|
27
|
+
if (!existsSync(join(repoPath, ".git"))) {
|
|
28
|
+
console.log(pc.red("not a git repository — cannot install hook."));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const path = hookPath(repoPath);
|
|
32
|
+
if (existsSync(path)) {
|
|
33
|
+
const existing = await readFile(path, "utf-8");
|
|
34
|
+
if (existing.includes(MARKER)) {
|
|
35
|
+
console.log(pc.dim("vibecheck hook is already installed."));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// Append to existing hook
|
|
39
|
+
await writeFile(path, existing.trimEnd() + "\n\n" + PRE_PUSH_SCRIPT, "utf-8");
|
|
40
|
+
console.log(pc.green("✓ vibecheck added to existing pre-push hook."));
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
if (!existsSync(hooksDir)) {
|
|
44
|
+
await mkdir(hooksDir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
await writeFile(path, PRE_PUSH_SCRIPT, "utf-8");
|
|
47
|
+
await chmod(path, 0o755);
|
|
48
|
+
console.log(pc.green("✓ pre-push hook installed."));
|
|
49
|
+
}
|
|
50
|
+
console.log(pc.dim("vibecheck will run automatically on every git push."));
|
|
51
|
+
}
|
|
52
|
+
export async function removeHook(repoPath) {
|
|
53
|
+
const path = hookPath(repoPath);
|
|
54
|
+
if (!existsSync(path)) {
|
|
55
|
+
console.log(pc.dim("no pre-push hook found."));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const content = await readFile(path, "utf-8");
|
|
59
|
+
if (!content.includes(MARKER)) {
|
|
60
|
+
console.log(pc.dim("pre-push hook exists but was not installed by vibecheck."));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// If the hook only contains our script, remove the file
|
|
64
|
+
const lines = content.split("\n");
|
|
65
|
+
const otherLines = lines.filter((l) => !l.includes(MARKER) && !l.includes("vibe-checking") && l.trim() !== "#!/bin/sh" && l.trim() !== "");
|
|
66
|
+
if (otherLines.length === 0) {
|
|
67
|
+
await unlink(path);
|
|
68
|
+
console.log(pc.green("✓ pre-push hook removed."));
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// Remove just our section
|
|
72
|
+
const cleaned = content
|
|
73
|
+
.replace(/\n*# vibecheck hook\nnpx vibe-checking[^\n]*/g, "")
|
|
74
|
+
.trimEnd() + "\n";
|
|
75
|
+
await writeFile(path, cleaned, "utf-8");
|
|
76
|
+
console.log(pc.green("✓ vibecheck removed from pre-push hook."));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=installer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installer.js","sourceRoot":"","sources":["../../src/hooks/installer.ts"],"names":[],"mappings":"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"}
|