@nektarlabs/pushguard 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/LICENSE +7 -0
- package/README.md +97 -0
- package/bin/pushguard.js +2 -0
- package/dist/analysis/claude.d.ts +4 -0
- package/dist/analysis/claude.d.ts.map +1 -0
- package/dist/analysis/claude.js +65 -0
- package/dist/analysis/claude.js.map +1 -0
- package/dist/analysis/prompt.d.ts +5 -0
- package/dist/analysis/prompt.d.ts.map +1 -0
- package/dist/analysis/prompt.js +54 -0
- package/dist/analysis/prompt.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +33 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/defaults.d.ts +3 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +11 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/loader.d.ts +3 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +30 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/schema.d.ts +21 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +2 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/git/diff.d.ts +5 -0
- package/dist/git/diff.d.ts.map +1 -0
- package/dist/git/diff.js +86 -0
- package/dist/git/diff.js.map +1 -0
- package/dist/git/parse-stdin.d.ts +3 -0
- package/dist/git/parse-stdin.d.ts.map +1 -0
- package/dist/git/parse-stdin.js +25 -0
- package/dist/git/parse-stdin.js.map +1 -0
- package/dist/hooks/analyze.d.ts +6 -0
- package/dist/hooks/analyze.d.ts.map +1 -0
- package/dist/hooks/analyze.js +85 -0
- package/dist/hooks/analyze.js.map +1 -0
- package/dist/hooks/pre-push.d.ts +2 -0
- package/dist/hooks/pre-push.d.ts.map +1 -0
- package/dist/hooks/pre-push.js +75 -0
- package/dist/hooks/pre-push.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/output/reporter.d.ts +5 -0
- package/dist/output/reporter.d.ts.map +1 -0
- package/dist/output/reporter.js +77 -0
- package/dist/output/reporter.js.map +1 -0
- package/dist/setup/install.d.ts +2 -0
- package/dist/setup/install.d.ts.map +1 -0
- package/dist/setup/install.js +33 -0
- package/dist/setup/install.js.map +1 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +83 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2026 Nektar Labs SRL
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# pushguard
|
|
2
|
+
|
|
3
|
+
Pre-push hook that analyzes your code changes with [Claude Code](https://docs.anthropic.com/en/docs/claude-code) before pushing. Catches security issues, bugs, logic errors, and code quality problems automatically.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated
|
|
8
|
+
- Node.js >= 22.14.0
|
|
9
|
+
- Git
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add -D @nektarlabs/pushguard husky
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx pushguard init
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This creates a `.husky/pre-push` hook that runs pushguard before every push.
|
|
24
|
+
|
|
25
|
+
## How it works
|
|
26
|
+
|
|
27
|
+
1. You run `git push`
|
|
28
|
+
2. Pushguard computes the diff of all unpushed commits
|
|
29
|
+
3. The diff is sent to Claude Code for analysis
|
|
30
|
+
4. Claude returns a structured verdict with categorized issues
|
|
31
|
+
5. Push is **blocked** if any issue meets the severity threshold, otherwise allowed
|
|
32
|
+
|
|
33
|
+
## Manual analysis
|
|
34
|
+
|
|
35
|
+
You can run the analysis manually at any time without pushing:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx pushguard analyze
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This diffs your unpushed commits against the remote tracking branch (or `origin/main` as fallback) and runs the same analysis as the pre-push hook.
|
|
42
|
+
|
|
43
|
+
## Configuration
|
|
44
|
+
|
|
45
|
+
Add a `"pushguard"` key to your `package.json` or create a `.pushguard.json` file:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"categories": ["security", "bug", "logic", "performance", "quality", "style"],
|
|
50
|
+
"blockOnSeverity": "high",
|
|
51
|
+
"model": "claude-opus-4-6",
|
|
52
|
+
"maxDiffSize": 100000,
|
|
53
|
+
"failOnError": false,
|
|
54
|
+
"exclude": ["*.lock", "*.min.js", "*.map", "dist/**"],
|
|
55
|
+
"verbose": false,
|
|
56
|
+
"skipBranches": ["develop"],
|
|
57
|
+
"customPrompt": "Also check for proper error handling"
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Options
|
|
62
|
+
|
|
63
|
+
| Option | Default | Description |
|
|
64
|
+
| ----------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
65
|
+
| `categories` | `["security", "bug", "logic"]` | What to check: `security`, `bug`, `logic`, `performance`, `quality`, `style` |
|
|
66
|
+
| `blockOnSeverity` | `"high"` | Minimum severity to block push: `critical`, `high`, `medium`, `low` |
|
|
67
|
+
| `model` | `"claude-opus-4-6"` | Claude model to use |
|
|
68
|
+
| `maxDiffSize` | `100000` | Max diff size in bytes before truncation |
|
|
69
|
+
| `failOnError` | `false` | Block push if Claude CLI errors (fail-open by default) |
|
|
70
|
+
| `exclude` | `["*.lock", "*.min.js", "*.map", "dist/**", "node_modules/**"]` | File patterns to skip |
|
|
71
|
+
| `verbose` | `false` | Show full analysis summary |
|
|
72
|
+
| `skipBranches` | `[]` | Branch patterns to skip (supports trailing `*` wildcard) |
|
|
73
|
+
| `customPrompt` | — | Additional instructions for the review |
|
|
74
|
+
|
|
75
|
+
## Example output
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
pushguard: Analyzing 3 changed files before push...
|
|
79
|
+
|
|
80
|
+
CRITICAL security src/auth/login.ts:42
|
|
81
|
+
Hardcoded API secret exposed in source code
|
|
82
|
+
> Move to environment variable: const API_KEY = process.env.API_KEY
|
|
83
|
+
|
|
84
|
+
HIGH bug src/utils/parse.ts:18
|
|
85
|
+
Uncaught exception when input is null
|
|
86
|
+
> Add null check: if (input == null) return []
|
|
87
|
+
|
|
88
|
+
HIGH logic src/billing/calc.ts:55
|
|
89
|
+
Wrong comparison operator causes free tier users to be charged
|
|
90
|
+
> Change `>=` to `>`: if (usage > FREE_TIER_LIMIT)
|
|
91
|
+
|
|
92
|
+
pushguard: Push BLOCKED (1 critical, 2 high)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
MIT
|
package/bin/pushguard.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { GuardStagedConfig } from "../config/schema.js";
|
|
2
|
+
import type { AnalysisResult } from "../types.js";
|
|
3
|
+
export declare function analyzeWithClaude(systemPrompt: string, userPrompt: string, config: GuardStagedConfig): Promise<AnalysisResult>;
|
|
4
|
+
//# sourceMappingURL=claude.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/analysis/claude.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAIlD,wBAAsB,iBAAiB,CACrC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,cAAc,CAAC,CAOzB"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
const TIMEOUT_MS = 60_000;
|
|
3
|
+
export async function analyzeWithClaude(systemPrompt, userPrompt, config) {
|
|
4
|
+
const prompt = `${systemPrompt}\n\n---\n\n${userPrompt}`;
|
|
5
|
+
const args = ["-p", prompt, "--output-format", "json", "--model", config.model, "--max-turns", "1"];
|
|
6
|
+
const stdout = await spawnClaude(args);
|
|
7
|
+
return parseClaudeResponse(stdout);
|
|
8
|
+
}
|
|
9
|
+
function spawnClaude(args) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const child = spawn("claude", args, {
|
|
12
|
+
env: {
|
|
13
|
+
...process.env,
|
|
14
|
+
CLAUDE_CODE_ENTRYPOINT: undefined,
|
|
15
|
+
CLAUDECODE: undefined,
|
|
16
|
+
},
|
|
17
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
18
|
+
});
|
|
19
|
+
let stdout = "";
|
|
20
|
+
let stderr = "";
|
|
21
|
+
child.stdout.on("data", (data) => {
|
|
22
|
+
stdout += data.toString();
|
|
23
|
+
});
|
|
24
|
+
child.stderr.on("data", (data) => {
|
|
25
|
+
stderr += data.toString();
|
|
26
|
+
});
|
|
27
|
+
const timer = setTimeout(() => {
|
|
28
|
+
child.kill();
|
|
29
|
+
reject(new Error("Claude CLI timed out"));
|
|
30
|
+
}, TIMEOUT_MS);
|
|
31
|
+
child.on("close", (code) => {
|
|
32
|
+
clearTimeout(timer);
|
|
33
|
+
if (code !== 0) {
|
|
34
|
+
reject(new Error(`Claude CLI exited with code ${code}: ${stderr}`));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
resolve(stdout);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
child.on("error", (err) => {
|
|
41
|
+
clearTimeout(timer);
|
|
42
|
+
reject(err);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function parseClaudeResponse(raw) {
|
|
47
|
+
const response = JSON.parse(raw);
|
|
48
|
+
// Claude CLI --output-format json wraps the result
|
|
49
|
+
const text = response.result ?? response.content ?? raw;
|
|
50
|
+
// Try to extract JSON from the text
|
|
51
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
52
|
+
if (!jsonMatch) {
|
|
53
|
+
throw new Error("No JSON object found in Claude response");
|
|
54
|
+
}
|
|
55
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
56
|
+
// Validate required fields
|
|
57
|
+
if (!parsed.verdict || !parsed.summary || !Array.isArray(parsed.issues)) {
|
|
58
|
+
throw new Error("Invalid analysis result structure");
|
|
59
|
+
}
|
|
60
|
+
if (!["pass", "warn", "fail"].includes(parsed.verdict)) {
|
|
61
|
+
throw new Error(`Invalid verdict: ${parsed.verdict}`);
|
|
62
|
+
}
|
|
63
|
+
return parsed;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/analysis/claude.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { GuardStagedConfig } from "../config/schema.js";
|
|
2
|
+
import type { DiffResult } from "../types.js";
|
|
3
|
+
export declare function buildSystemPrompt(config: GuardStagedConfig): string;
|
|
4
|
+
export declare function buildUserPrompt(diffResult: DiffResult): string;
|
|
5
|
+
//# sourceMappingURL=prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/analysis/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAI9C,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CA2CnE;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAc9D"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const SEVERITY_ORDER = ["critical", "high", "medium", "low", "info"];
|
|
2
|
+
export function buildSystemPrompt(config) {
|
|
3
|
+
const categories = config.categories.join(", ");
|
|
4
|
+
const blockIdx = SEVERITY_ORDER.indexOf(config.blockOnSeverity);
|
|
5
|
+
const blockSeverities = SEVERITY_ORDER.slice(0, blockIdx + 1).join(", ");
|
|
6
|
+
let prompt = `You are a code review guardian analyzing a git diff before push.
|
|
7
|
+
|
|
8
|
+
Focus areas: ${categories}
|
|
9
|
+
Blocking severities: ${blockSeverities}
|
|
10
|
+
|
|
11
|
+
Rules:
|
|
12
|
+
- Set verdict to "fail" if any issue of severity ${blockSeverities} is found
|
|
13
|
+
- Set verdict to "warn" if issues exist but none meet the blocking threshold
|
|
14
|
+
- Set verdict to "pass" if no actionable issues are found
|
|
15
|
+
- Be precise about file paths and line numbers (from the diff headers)
|
|
16
|
+
- Only report real, actionable issues — no nitpicks unless "style" is in the focus areas
|
|
17
|
+
- For security issues, check for: hardcoded secrets, injection vulnerabilities, auth bypasses, insecure crypto, exposed sensitive data
|
|
18
|
+
- For bugs, check for: null/undefined errors, off-by-one, race conditions, unhandled exceptions
|
|
19
|
+
- For logic errors, check for: incorrect conditionals, inverted boolean logic, wrong operator (e.g. && vs ||), unreachable code paths, missing edge cases, incorrect state transitions, flawed control flow, wrong variable used in comparisons, off-by-one in loops/ranges
|
|
20
|
+
- For every issue, always include a "suggestion" field with a concrete fix (show the corrected code snippet when possible)
|
|
21
|
+
- Be concise in messages and suggestions
|
|
22
|
+
|
|
23
|
+
Respond with ONLY a JSON object (no markdown, no code fences, no explanation) matching this exact schema:
|
|
24
|
+
{
|
|
25
|
+
"verdict": "pass" | "warn" | "fail",
|
|
26
|
+
"summary": "Brief summary of findings",
|
|
27
|
+
"issues": [
|
|
28
|
+
{
|
|
29
|
+
"severity": "critical" | "high" | "medium" | "low" | "info",
|
|
30
|
+
"category": "security" | "bug" | "logic" | "performance" | "quality" | "style",
|
|
31
|
+
"file": "path/to/file",
|
|
32
|
+
"line": 42,
|
|
33
|
+
"message": "Description of the issue",
|
|
34
|
+
"suggestion": "How to fix it (include corrected code when possible)"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}`;
|
|
38
|
+
if (config.customPrompt) {
|
|
39
|
+
prompt += `\n\nAdditional instructions:\n${config.customPrompt}`;
|
|
40
|
+
}
|
|
41
|
+
return prompt;
|
|
42
|
+
}
|
|
43
|
+
export function buildUserPrompt(diffResult) {
|
|
44
|
+
if (!diffResult.diff) {
|
|
45
|
+
return "No changes to analyze.";
|
|
46
|
+
}
|
|
47
|
+
let prompt = `## Changed files\n${diffResult.files.join("\n")}\n\n`;
|
|
48
|
+
if (diffResult.truncated) {
|
|
49
|
+
prompt += `**Note:** The diff was truncated due to size. Focus on the available content.\n\n`;
|
|
50
|
+
}
|
|
51
|
+
prompt += `## Diff\n\`\`\`diff\n${diffResult.diff}\n\`\`\``;
|
|
52
|
+
return prompt;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/analysis/prompt.ts"],"names":[],"mappings":"AAGA,MAAM,cAAc,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAU,CAAC;AAE9E,MAAM,UAAU,iBAAiB,CAAC,MAAyB;IACzD,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IAChE,MAAM,eAAe,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEzE,IAAI,MAAM,GAAG;;eAEA,UAAU;uBACF,eAAe;;;mDAGa,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;EAyBhE,CAAC;IAED,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;QACxB,MAAM,IAAI,iCAAiC,MAAM,CAAC,YAAY,EAAE,CAAC;IACnE,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,UAAsB;IACpD,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QACrB,OAAO,wBAAwB,CAAC;IAClC,CAAC;IAED,IAAI,MAAM,GAAG,qBAAqB,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;IAEpE,IAAI,UAAU,CAAC,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,mFAAmF,CAAC;IAChG,CAAC;IAED,MAAM,IAAI,wBAAwB,UAAU,CAAC,IAAI,UAAU,CAAC;IAE5D,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
async function main() {
|
|
4
|
+
const { positionals } = parseArgs({
|
|
5
|
+
allowPositionals: true,
|
|
6
|
+
strict: false,
|
|
7
|
+
});
|
|
8
|
+
const command = positionals[0];
|
|
9
|
+
switch (command) {
|
|
10
|
+
case "init": {
|
|
11
|
+
const { init } = await import("./setup/install.js");
|
|
12
|
+
await init();
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
case "analyze": {
|
|
16
|
+
const { runAnalyze } = await import("./hooks/analyze.js");
|
|
17
|
+
const exitCode = await runAnalyze();
|
|
18
|
+
process.exit(exitCode);
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
default: {
|
|
22
|
+
// Default: run as pre-push hook
|
|
23
|
+
const { runPrePush } = await import("./hooks/pre-push.js");
|
|
24
|
+
const exitCode = await runPrePush();
|
|
25
|
+
process.exit(exitCode);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
main().catch((error) => {
|
|
30
|
+
console.error("guard-staged: Fatal error:", error);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
});
|
|
33
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,KAAK,UAAU,IAAI;IACjB,MAAM,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC;QAChC,gBAAgB,EAAE,IAAI;QACtB,MAAM,EAAE,KAAK;KACd,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;IAE/B,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;YACpD,MAAM,IAAI,EAAE,CAAC;YACb,MAAM;QACR,CAAC;QACD,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;YAC1D,MAAM,QAAQ,GAAG,MAAM,UAAU,EAAE,CAAC;YACpC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACvB,MAAM;QACR,CAAC;QACD,OAAO,CAAC,CAAC,CAAC;YACR,gCAAgC;YAChC,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC;YAC3D,MAAM,QAAQ,GAAG,MAAM,UAAU,EAAE,CAAC;YACpC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAC;IACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,eAAO,MAAM,QAAQ,EAAE,iBAStB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const DEFAULTS = {
|
|
2
|
+
categories: ["security", "bug", "logic"],
|
|
3
|
+
blockOnSeverity: "high",
|
|
4
|
+
model: "claude-opus-4-6",
|
|
5
|
+
maxDiffSize: 100_000,
|
|
6
|
+
failOnError: false,
|
|
7
|
+
exclude: ["*.lock", "*.min.js", "*.map", "dist/**", "node_modules/**"],
|
|
8
|
+
verbose: false,
|
|
9
|
+
skipBranches: [],
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=defaults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,QAAQ,GAAsB;IACzC,UAAU,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,OAAO,CAAC;IACxC,eAAe,EAAE,MAAM;IACvB,KAAK,EAAE,iBAAiB;IACxB,WAAW,EAAE,OAAO;IACpB,WAAW,EAAE,KAAK;IAClB,OAAO,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,iBAAiB,CAAC;IACtE,OAAO,EAAE,KAAK;IACd,YAAY,EAAE,EAAE;CACjB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGrD,wBAAsB,UAAU,CAAC,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAGxF"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { DEFAULTS } from "./defaults.js";
|
|
4
|
+
export async function loadConfig(cwd = process.cwd()) {
|
|
5
|
+
const overrides = (await loadFromRc(cwd)) ?? (await loadFromPackageJson(cwd));
|
|
6
|
+
return { ...DEFAULTS, ...overrides };
|
|
7
|
+
}
|
|
8
|
+
async function loadFromRc(cwd) {
|
|
9
|
+
try {
|
|
10
|
+
const content = await readFile(resolve(cwd, ".pushguard.json"), "utf-8");
|
|
11
|
+
return JSON.parse(content);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function loadFromPackageJson(cwd) {
|
|
18
|
+
try {
|
|
19
|
+
const content = await readFile(resolve(cwd, "package.json"), "utf-8");
|
|
20
|
+
const pkg = JSON.parse(content);
|
|
21
|
+
if (pkg["pushguard"] && typeof pkg["pushguard"] === "object") {
|
|
22
|
+
return pkg["pushguard"];
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IAC1D,MAAM,SAAS,GAAG,CAAC,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9E,OAAO,EAAE,GAAG,QAAQ,EAAE,GAAG,SAAS,EAAE,CAAC;AACvC,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,GAAW;IACnC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,iBAAiB,CAAC,EAAE,OAAO,CAAC,CAAC;QACzE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAA+B,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,mBAAmB,CAAC,GAAW;IAC5C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC;QACtE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAA4B,CAAC;QAC3D,IAAI,GAAG,CAAC,WAAW,CAAC,IAAI,OAAO,GAAG,CAAC,WAAW,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC7D,OAAO,GAAG,CAAC,WAAW,CAA+B,CAAC;QACxD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface GuardStagedConfig {
|
|
2
|
+
/** Categories to check */
|
|
3
|
+
categories: ("security" | "bug" | "logic" | "performance" | "quality" | "style")[];
|
|
4
|
+
/** Minimum severity to block push */
|
|
5
|
+
blockOnSeverity: "critical" | "high" | "medium" | "low";
|
|
6
|
+
/** Claude model to use */
|
|
7
|
+
model: string;
|
|
8
|
+
/** Max diff size in bytes before truncation */
|
|
9
|
+
maxDiffSize: number;
|
|
10
|
+
/** Whether to block push if Claude CLI fails */
|
|
11
|
+
failOnError: boolean;
|
|
12
|
+
/** File patterns to exclude from analysis */
|
|
13
|
+
exclude: string[];
|
|
14
|
+
/** Custom additional prompt instructions */
|
|
15
|
+
customPrompt?: string;
|
|
16
|
+
/** Whether to show the full analysis or just the verdict */
|
|
17
|
+
verbose: boolean;
|
|
18
|
+
/** Skip analysis for these branch patterns */
|
|
19
|
+
skipBranches: string[];
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,0BAA0B;IAC1B,UAAU,EAAE,CAAC,UAAU,GAAG,KAAK,GAAG,OAAO,GAAG,aAAa,GAAG,SAAS,GAAG,OAAO,CAAC,EAAE,CAAC;IACnF,qCAAqC;IACrC,eAAe,EAAE,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACxD,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,WAAW,EAAE,OAAO,CAAC;IACrB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,OAAO,EAAE,OAAO,CAAC;IACjB,8CAA8C;IAC9C,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PushRef, DiffResult } from "../types.js";
|
|
2
|
+
import type { GuardStagedConfig } from "../config/schema.js";
|
|
3
|
+
export declare function getDefaultBranch(): Promise<string>;
|
|
4
|
+
export declare function getDiff(pushRef: PushRef, config: GuardStagedConfig): Promise<DiffResult>;
|
|
5
|
+
//# sourceMappingURL=diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/git/diff.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAI7D,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,CAOxD;AAED,wBAAsB,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CAmE9F"}
|
package/dist/git/diff.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const exec = promisify(execFile);
|
|
4
|
+
export async function getDefaultBranch() {
|
|
5
|
+
try {
|
|
6
|
+
const { stdout } = await exec("git", ["symbolic-ref", "refs/remotes/origin/HEAD", "--short"]);
|
|
7
|
+
return stdout.trim().replace("origin/", "");
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return "main";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export async function getDiff(pushRef, config) {
|
|
14
|
+
if (pushRef.isDelete) {
|
|
15
|
+
return { diff: "", files: [], truncated: false };
|
|
16
|
+
}
|
|
17
|
+
let baseRef;
|
|
18
|
+
if (pushRef.isNew) {
|
|
19
|
+
const defaultBranch = await getDefaultBranch();
|
|
20
|
+
try {
|
|
21
|
+
const { stdout } = await exec("git", ["merge-base", `origin/${defaultBranch}`, pushRef.localSha]);
|
|
22
|
+
baseRef = stdout.trim();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// If merge-base fails, diff against the default branch tip
|
|
26
|
+
baseRef = `origin/${defaultBranch}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
baseRef = pushRef.remoteSha;
|
|
31
|
+
}
|
|
32
|
+
// Get the list of changed files
|
|
33
|
+
const { stdout: fileList } = await exec("git", [
|
|
34
|
+
"diff",
|
|
35
|
+
"--name-only",
|
|
36
|
+
"--diff-filter=ACMR",
|
|
37
|
+
`${baseRef}..${pushRef.localSha}`,
|
|
38
|
+
]);
|
|
39
|
+
const files = fileList
|
|
40
|
+
.trim()
|
|
41
|
+
.split("\n")
|
|
42
|
+
.filter(Boolean)
|
|
43
|
+
.filter((f) => !matchesExcludePattern(f, config.exclude));
|
|
44
|
+
if (files.length === 0) {
|
|
45
|
+
return { diff: "", files: [], truncated: false };
|
|
46
|
+
}
|
|
47
|
+
// Get the full diff
|
|
48
|
+
const { stdout: diff } = await exec("git", [
|
|
49
|
+
"diff",
|
|
50
|
+
"--diff-filter=ACMR",
|
|
51
|
+
`${baseRef}..${pushRef.localSha}`,
|
|
52
|
+
"--",
|
|
53
|
+
...files,
|
|
54
|
+
]);
|
|
55
|
+
// Check size and truncate if needed
|
|
56
|
+
if (Buffer.byteLength(diff) > config.maxDiffSize) {
|
|
57
|
+
const { stdout: stat } = await exec("git", [
|
|
58
|
+
"diff",
|
|
59
|
+
"--stat",
|
|
60
|
+
"--diff-filter=ACMR",
|
|
61
|
+
`${baseRef}..${pushRef.localSha}`,
|
|
62
|
+
"--",
|
|
63
|
+
...files,
|
|
64
|
+
]);
|
|
65
|
+
const truncatedDiff = diff.slice(0, config.maxDiffSize);
|
|
66
|
+
return {
|
|
67
|
+
diff: `[TRUNCATED — diff exceeded ${config.maxDiffSize} bytes]\n\n## Diff stat\n${stat}\n\n## Partial diff\n${truncatedDiff}`,
|
|
68
|
+
files,
|
|
69
|
+
truncated: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return { diff, files, truncated: false };
|
|
73
|
+
}
|
|
74
|
+
function matchesExcludePattern(file, patterns) {
|
|
75
|
+
return patterns.some((pattern) => {
|
|
76
|
+
// Simple glob matching: *.ext and dir/**
|
|
77
|
+
if (pattern.startsWith("*.")) {
|
|
78
|
+
return file.endsWith(pattern.slice(1));
|
|
79
|
+
}
|
|
80
|
+
if (pattern.endsWith("/**")) {
|
|
81
|
+
return file.startsWith(pattern.slice(0, -3));
|
|
82
|
+
}
|
|
83
|
+
return file === pattern;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/git/diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-stdin.d.ts","sourceRoot":"","sources":["../../src/git/parse-stdin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAI3C,wBAAsB,aAAa,CAAC,KAAK,GAAE,MAAM,CAAC,cAA8B,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAyBpG"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
const ZERO_SHA = "0000000000000000000000000000000000000000";
|
|
3
|
+
export async function parsePushRefs(input = process.stdin) {
|
|
4
|
+
const refs = [];
|
|
5
|
+
const rl = createInterface({ input, crlfDelay: Infinity });
|
|
6
|
+
for await (const line of rl) {
|
|
7
|
+
const trimmed = line.trim();
|
|
8
|
+
if (!trimmed)
|
|
9
|
+
continue;
|
|
10
|
+
const parts = trimmed.split(/\s+/);
|
|
11
|
+
if (parts.length < 4)
|
|
12
|
+
continue;
|
|
13
|
+
const [localRef, localSha, remoteRef, remoteSha] = parts;
|
|
14
|
+
refs.push({
|
|
15
|
+
localRef: localRef,
|
|
16
|
+
localSha: localSha,
|
|
17
|
+
remoteRef: remoteRef,
|
|
18
|
+
remoteSha: remoteSha,
|
|
19
|
+
isNew: remoteSha === ZERO_SHA,
|
|
20
|
+
isDelete: localRef === "(delete)" || localSha === ZERO_SHA,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return refs;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=parse-stdin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-stdin.js","sourceRoot":"","sources":["../../src/git/parse-stdin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGhD,MAAM,QAAQ,GAAG,0CAA0C,CAAC;AAE5D,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAA+B,OAAO,CAAC,KAAK;IAC9E,MAAM,IAAI,GAAc,EAAE,CAAC;IAE3B,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;IAE3D,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,EAAE,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,SAAS;QAEvB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS;QAE/B,MAAM,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,CAAC,GAAG,KAAK,CAAC;QAEzD,IAAI,CAAC,IAAI,CAAC;YACR,QAAQ,EAAE,QAAS;YACnB,QAAQ,EAAE,QAAS;YACnB,SAAS,EAAE,SAAU;YACrB,SAAS,EAAE,SAAU;YACrB,KAAK,EAAE,SAAS,KAAK,QAAQ;YAC7B,QAAQ,EAAE,QAAQ,KAAK,UAAU,IAAI,QAAQ,KAAK,QAAQ;SAC3D,CAAC,CAAC;IACL,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/hooks/analyze.ts"],"names":[],"mappings":"AAWA;;;GAGG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAkBlD"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { getDiff } from "../git/diff.js";
|
|
4
|
+
import { buildSystemPrompt, buildUserPrompt } from "../analysis/prompt.js";
|
|
5
|
+
import { analyzeWithClaude } from "../analysis/claude.js";
|
|
6
|
+
import { loadConfig } from "../config/loader.js";
|
|
7
|
+
import { log, reportStart, reportResult } from "../output/reporter.js";
|
|
8
|
+
const exec = promisify(execFile);
|
|
9
|
+
/**
|
|
10
|
+
* Standalone analyze command — diffs unpushed commits against the remote
|
|
11
|
+
* tracking branch without needing git hook stdin.
|
|
12
|
+
*/
|
|
13
|
+
export async function runAnalyze() {
|
|
14
|
+
const config = await loadConfig();
|
|
15
|
+
const localSha = await resolveHead();
|
|
16
|
+
const remoteSha = await resolveUpstream();
|
|
17
|
+
if (!remoteSha) {
|
|
18
|
+
log("No upstream tracking branch found. Use: git push -u origin <branch>");
|
|
19
|
+
log("Falling back to diff against origin/main...");
|
|
20
|
+
const fallback = await resolveFallback();
|
|
21
|
+
if (!fallback) {
|
|
22
|
+
log("Could not determine a base to diff against.");
|
|
23
|
+
return 1;
|
|
24
|
+
}
|
|
25
|
+
return runDiffAndAnalyze({ remoteSha: fallback, localSha }, config);
|
|
26
|
+
}
|
|
27
|
+
return runDiffAndAnalyze({ remoteSha, localSha }, config);
|
|
28
|
+
}
|
|
29
|
+
async function runDiffAndAnalyze(refs, config) {
|
|
30
|
+
const pushRef = {
|
|
31
|
+
localRef: "HEAD",
|
|
32
|
+
localSha: refs.localSha,
|
|
33
|
+
remoteRef: "",
|
|
34
|
+
remoteSha: refs.remoteSha,
|
|
35
|
+
isNew: false,
|
|
36
|
+
isDelete: false,
|
|
37
|
+
};
|
|
38
|
+
try {
|
|
39
|
+
const diffResult = await getDiff(pushRef, config);
|
|
40
|
+
if (diffResult.files.length === 0) {
|
|
41
|
+
log("No unpushed changes to analyze.");
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
reportStart(diffResult.files.length);
|
|
45
|
+
const systemPrompt = buildSystemPrompt(config);
|
|
46
|
+
const userPrompt = buildUserPrompt(diffResult);
|
|
47
|
+
const result = await analyzeWithClaude(systemPrompt, userPrompt, config);
|
|
48
|
+
reportResult(result, config.verbose);
|
|
49
|
+
return result.verdict === "fail" ? 1 : 0;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
53
|
+
log(`Analysis error: ${msg}`);
|
|
54
|
+
return config.failOnError ? 1 : 0;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function resolveHead() {
|
|
58
|
+
const { stdout } = await exec("git", ["rev-parse", "HEAD"]);
|
|
59
|
+
return stdout.trim();
|
|
60
|
+
}
|
|
61
|
+
async function resolveUpstream() {
|
|
62
|
+
try {
|
|
63
|
+
const { stdout } = await exec("git", ["rev-parse", "@{upstream}"]);
|
|
64
|
+
return stdout.trim();
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function resolveFallback() {
|
|
71
|
+
try {
|
|
72
|
+
const { stdout } = await exec("git", ["rev-parse", "origin/main"]);
|
|
73
|
+
return stdout.trim();
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
try {
|
|
77
|
+
const { stdout } = await exec("git", ["rev-parse", "origin/master"]);
|
|
78
|
+
return stdout.trim();
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=analyze.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../src/hooks/analyze.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pre-push.d.ts","sourceRoot":"","sources":["../../src/hooks/pre-push.ts"],"names":[],"mappings":"AAOA,wBAAsB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CA+DlD"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { parsePushRefs } from "../git/parse-stdin.js";
|
|
2
|
+
import { getDiff } from "../git/diff.js";
|
|
3
|
+
import { buildSystemPrompt, buildUserPrompt } from "../analysis/prompt.js";
|
|
4
|
+
import { analyzeWithClaude } from "../analysis/claude.js";
|
|
5
|
+
import { loadConfig } from "../config/loader.js";
|
|
6
|
+
import { log, reportStart, reportResult } from "../output/reporter.js";
|
|
7
|
+
export async function runPrePush() {
|
|
8
|
+
const config = await loadConfig();
|
|
9
|
+
// Check if current branch should be skipped
|
|
10
|
+
if (config.skipBranches.length > 0) {
|
|
11
|
+
const branch = await getCurrentBranch();
|
|
12
|
+
if (branch && config.skipBranches.some((p) => matchBranch(branch, p))) {
|
|
13
|
+
log(`Skipping analysis for branch "${branch}"`);
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// Parse push refs from stdin
|
|
18
|
+
const refs = await parsePushRefs();
|
|
19
|
+
if (refs.length === 0) {
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
let hasFailure = false;
|
|
23
|
+
for (const ref of refs) {
|
|
24
|
+
if (ref.isDelete) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const diffResult = await getDiff(ref, config);
|
|
29
|
+
if (diffResult.files.length === 0) {
|
|
30
|
+
log("No relevant changes to analyze.");
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
reportStart(diffResult.files.length);
|
|
34
|
+
if (diffResult.truncated) {
|
|
35
|
+
log("Diff was truncated due to size — analysis may be partial.");
|
|
36
|
+
}
|
|
37
|
+
const systemPrompt = buildSystemPrompt(config);
|
|
38
|
+
const userPrompt = buildUserPrompt(diffResult);
|
|
39
|
+
const result = await analyzeWithClaude(systemPrompt, userPrompt, config);
|
|
40
|
+
reportResult(result, config.verbose);
|
|
41
|
+
if (result.verdict === "fail") {
|
|
42
|
+
hasFailure = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
47
|
+
log(`Analysis error: ${msg}`);
|
|
48
|
+
if (config.failOnError) {
|
|
49
|
+
log("Push blocked due to analysis error (failOnError is enabled).");
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
log("Allowing push despite analysis error (fail-open mode).");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return hasFailure ? 1 : 0;
|
|
56
|
+
}
|
|
57
|
+
async function getCurrentBranch() {
|
|
58
|
+
const { execFile } = await import("node:child_process");
|
|
59
|
+
const { promisify } = await import("node:util");
|
|
60
|
+
const exec = promisify(execFile);
|
|
61
|
+
try {
|
|
62
|
+
const { stdout } = await exec("git", ["branch", "--show-current"]);
|
|
63
|
+
return stdout.trim() || null;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function matchBranch(branch, pattern) {
|
|
70
|
+
if (pattern.endsWith("*")) {
|
|
71
|
+
return branch.startsWith(pattern.slice(0, -1));
|
|
72
|
+
}
|
|
73
|
+
return branch === pattern;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=pre-push.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pre-push.js","sourceRoot":"","sources":["../../src/hooks/pre-push.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { runPrePush } from "./hooks/pre-push.js";
|
|
2
|
+
export { loadConfig } from "./config/loader.js";
|
|
3
|
+
export { analyzeWithClaude } from "./analysis/claude.js";
|
|
4
|
+
export { buildSystemPrompt, buildUserPrompt } from "./analysis/prompt.js";
|
|
5
|
+
export { getDiff } from "./git/diff.js";
|
|
6
|
+
export { parsePushRefs } from "./git/parse-stdin.js";
|
|
7
|
+
export type { GuardStagedConfig } from "./config/schema.js";
|
|
8
|
+
export type { PushRef, AnalysisResult, AnalysisIssue, DiffResult } from "./types.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { runPrePush } from "./hooks/pre-push.js";
|
|
2
|
+
export { loadConfig } from "./config/loader.js";
|
|
3
|
+
export { analyzeWithClaude } from "./analysis/claude.js";
|
|
4
|
+
export { buildSystemPrompt, buildUserPrompt } from "./analysis/prompt.js";
|
|
5
|
+
export { getDiff } from "./git/diff.js";
|
|
6
|
+
export { parsePushRefs } from "./git/parse-stdin.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AnalysisResult } from "../types.js";
|
|
2
|
+
export declare function log(message: string): void;
|
|
3
|
+
export declare function reportStart(fileCount: number): void;
|
|
4
|
+
export declare function reportResult(result: AnalysisResult, verbose: boolean): void;
|
|
5
|
+
//# sourceMappingURL=reporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../src/output/reporter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAiB,MAAM,aAAa,CAAC;AAwBjE,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEzC;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAEnD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAiB3E"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const COLORS = {
|
|
2
|
+
reset: "\x1b[0m",
|
|
3
|
+
bold: "\x1b[1m",
|
|
4
|
+
dim: "\x1b[2m",
|
|
5
|
+
red: "\x1b[31m",
|
|
6
|
+
yellow: "\x1b[33m",
|
|
7
|
+
green: "\x1b[32m",
|
|
8
|
+
cyan: "\x1b[36m",
|
|
9
|
+
magenta: "\x1b[35m",
|
|
10
|
+
white: "\x1b[37m",
|
|
11
|
+
};
|
|
12
|
+
const SEVERITY_COLORS = {
|
|
13
|
+
critical: COLORS.red + COLORS.bold,
|
|
14
|
+
high: COLORS.red,
|
|
15
|
+
medium: COLORS.yellow,
|
|
16
|
+
low: COLORS.cyan,
|
|
17
|
+
info: COLORS.dim,
|
|
18
|
+
};
|
|
19
|
+
const PREFIX = `${COLORS.magenta}pushguard${COLORS.reset}`;
|
|
20
|
+
export function log(message) {
|
|
21
|
+
console.error(`${PREFIX}: ${message}`);
|
|
22
|
+
}
|
|
23
|
+
export function reportStart(fileCount) {
|
|
24
|
+
log(`Analyzing ${fileCount} changed file${fileCount === 1 ? "" : "s"} before push...`);
|
|
25
|
+
}
|
|
26
|
+
export function reportResult(result, verbose) {
|
|
27
|
+
console.error("");
|
|
28
|
+
if (result.issues.length > 0) {
|
|
29
|
+
for (const issue of result.issues) {
|
|
30
|
+
reportIssue(issue);
|
|
31
|
+
}
|
|
32
|
+
console.error("");
|
|
33
|
+
}
|
|
34
|
+
if (verbose) {
|
|
35
|
+
log(result.summary);
|
|
36
|
+
}
|
|
37
|
+
const verdictLine = formatVerdict(result);
|
|
38
|
+
log(verdictLine);
|
|
39
|
+
console.error("");
|
|
40
|
+
}
|
|
41
|
+
function reportIssue(issue) {
|
|
42
|
+
const color = SEVERITY_COLORS[issue.severity] ?? COLORS.dim;
|
|
43
|
+
const severity = `${color}${issue.severity.toUpperCase()}${COLORS.reset}`;
|
|
44
|
+
const category = `${COLORS.dim}${issue.category}${COLORS.reset}`;
|
|
45
|
+
let location = "";
|
|
46
|
+
if (issue.file) {
|
|
47
|
+
location = ` ${COLORS.dim}${issue.file}${issue.line ? `:${issue.line}` : ""}${COLORS.reset}`;
|
|
48
|
+
}
|
|
49
|
+
console.error(` ${severity} ${category}${location}`);
|
|
50
|
+
console.error(` ${issue.message}`);
|
|
51
|
+
if (issue.suggestion) {
|
|
52
|
+
console.error(` ${COLORS.dim}> ${issue.suggestion}${COLORS.reset}`);
|
|
53
|
+
}
|
|
54
|
+
console.error("");
|
|
55
|
+
}
|
|
56
|
+
function formatVerdict(result) {
|
|
57
|
+
const issueCounts = countBySeverity(result.issues);
|
|
58
|
+
const countStr = Object.entries(issueCounts)
|
|
59
|
+
.map(([sev, count]) => `${count} ${sev}`)
|
|
60
|
+
.join(", ");
|
|
61
|
+
switch (result.verdict) {
|
|
62
|
+
case "fail":
|
|
63
|
+
return `${COLORS.red}${COLORS.bold}Push BLOCKED${COLORS.reset} (${countStr})`;
|
|
64
|
+
case "warn":
|
|
65
|
+
return `${COLORS.yellow}Push allowed with warnings${COLORS.reset} (${countStr})`;
|
|
66
|
+
case "pass":
|
|
67
|
+
return `${COLORS.green}Push OK${COLORS.reset} — no issues found`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function countBySeverity(issues) {
|
|
71
|
+
const counts = {};
|
|
72
|
+
for (const issue of issues) {
|
|
73
|
+
counts[issue.severity] = (counts[issue.severity] ?? 0) + 1;
|
|
74
|
+
}
|
|
75
|
+
return counts;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=reporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporter.js","sourceRoot":"","sources":["../../src/output/reporter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/setup/install.ts"],"names":[],"mappings":"AAWA,wBAAsB,IAAI,CAAC,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BrE"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { execFile } from "node:child_process";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { log } from "../output/reporter.js";
|
|
7
|
+
const exec = promisify(execFile);
|
|
8
|
+
const HOOK_CONTENT = `npx pushguard\n`;
|
|
9
|
+
export async function init(cwd = process.cwd()) {
|
|
10
|
+
const huskyDir = resolve(cwd, ".husky");
|
|
11
|
+
// Check if husky is set up
|
|
12
|
+
if (!existsSync(huskyDir)) {
|
|
13
|
+
log("Husky not found. Initializing...");
|
|
14
|
+
try {
|
|
15
|
+
await exec("npx", ["husky", "init"], { cwd });
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
19
|
+
console.error(`Failed to initialize Husky. Make sure husky is installed:\n npm install -D husky\n\nError: ${msg}`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Create the pre-push hook
|
|
24
|
+
const hookPath = resolve(huskyDir, "pre-push");
|
|
25
|
+
await writeFile(hookPath, HOOK_CONTENT, { mode: 0o755 });
|
|
26
|
+
log("Created .husky/pre-push hook");
|
|
27
|
+
log("pushguard will now analyze changes before each push.");
|
|
28
|
+
console.error("");
|
|
29
|
+
console.error(" Configuration (optional):");
|
|
30
|
+
console.error(' Add a "pushguard" key to package.json or create .pushguard.json');
|
|
31
|
+
console.error("");
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/setup/install.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AAE5C,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAEjC,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAEvC,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IACpD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAExC,2BAA2B;IAC3B,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,GAAG,CAAC,kCAAkC,CAAC,CAAC;QACxC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QAChD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,GAAG,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACnE,OAAO,CAAC,KAAK,CACX,+FAA+F,GAAG,EAAE,CACrG,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,2BAA2B;IAC3B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAC/C,MAAM,SAAS,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAEzD,GAAG,CAAC,8BAA8B,CAAC,CAAC;IACpC,GAAG,CAAC,sDAAsD,CAAC,CAAC;IAC5D,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;IAC7C,OAAO,CAAC,KAAK,CAAC,mEAAmE,CAAC,CAAC;IACnF,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AACpB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface PushRef {
|
|
2
|
+
localRef: string;
|
|
3
|
+
localSha: string;
|
|
4
|
+
remoteRef: string;
|
|
5
|
+
remoteSha: string;
|
|
6
|
+
isNew: boolean;
|
|
7
|
+
isDelete: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface AnalysisIssue {
|
|
10
|
+
severity: "critical" | "high" | "medium" | "low" | "info";
|
|
11
|
+
category: "security" | "bug" | "logic" | "performance" | "quality" | "style";
|
|
12
|
+
file?: string;
|
|
13
|
+
line?: number;
|
|
14
|
+
message: string;
|
|
15
|
+
suggestion: string;
|
|
16
|
+
}
|
|
17
|
+
export interface AnalysisResult {
|
|
18
|
+
verdict: "pass" | "warn" | "fail";
|
|
19
|
+
summary: string;
|
|
20
|
+
issues: AnalysisIssue[];
|
|
21
|
+
}
|
|
22
|
+
export interface DiffResult {
|
|
23
|
+
diff: string;
|
|
24
|
+
files: string[];
|
|
25
|
+
truncated: boolean;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IAC1D,QAAQ,EAAE,UAAU,GAAG,KAAK,GAAG,OAAO,GAAG,aAAa,GAAG,SAAS,GAAG,OAAO,CAAC;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;CACpB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nektarlabs/pushguard",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Pre-push hook that analyzes code changes with Claude Code before pushing",
|
|
6
|
+
"bin": {
|
|
7
|
+
"pushguard": "./bin/pushguard.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"bin",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc -p tsconfig.build.json",
|
|
26
|
+
"dev": "tsc -p tsconfig.build.json --watch",
|
|
27
|
+
"lint": "eslint src/",
|
|
28
|
+
"format": "prettier --write .",
|
|
29
|
+
"format:check": "prettier --check .",
|
|
30
|
+
"prepare": "husky",
|
|
31
|
+
"prepublishOnly": "pnpm run build",
|
|
32
|
+
"test": "vitest run"
|
|
33
|
+
},
|
|
34
|
+
"lint-staged": {
|
|
35
|
+
"*.{ts,js}": [
|
|
36
|
+
"eslint --fix",
|
|
37
|
+
"prettier --write"
|
|
38
|
+
],
|
|
39
|
+
"*.{json,md,yml,yaml}": [
|
|
40
|
+
"prettier --write"
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"husky": ">=9.0.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependenciesMeta": {
|
|
47
|
+
"husky": {
|
|
48
|
+
"optional": true
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@commitlint/cli": "^20.4.4",
|
|
53
|
+
"@commitlint/config-conventional": "^20.4.4",
|
|
54
|
+
"@eslint/js": "^10.0.1",
|
|
55
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
56
|
+
"@semantic-release/git": "^10.0.1",
|
|
57
|
+
"@types/node": "^20.0.0",
|
|
58
|
+
"eslint": "^10.0.3",
|
|
59
|
+
"lint-staged": "^16.3.3",
|
|
60
|
+
"prettier": "^3.8.1",
|
|
61
|
+
"semantic-release": "^25.0.3",
|
|
62
|
+
"typescript": "^5.5.0",
|
|
63
|
+
"typescript-eslint": "^8.57.0",
|
|
64
|
+
"vitest": "^3.0.0"
|
|
65
|
+
},
|
|
66
|
+
"keywords": [
|
|
67
|
+
"git",
|
|
68
|
+
"hook",
|
|
69
|
+
"pre-push",
|
|
70
|
+
"husky",
|
|
71
|
+
"claude",
|
|
72
|
+
"code-review",
|
|
73
|
+
"ai",
|
|
74
|
+
"security"
|
|
75
|
+
],
|
|
76
|
+
"publishConfig": {
|
|
77
|
+
"access": "public"
|
|
78
|
+
},
|
|
79
|
+
"license": "MIT",
|
|
80
|
+
"engines": {
|
|
81
|
+
"node": ">=22.14.0"
|
|
82
|
+
}
|
|
83
|
+
}
|