@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.
Files changed (60) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +97 -0
  3. package/bin/pushguard.js +2 -0
  4. package/dist/analysis/claude.d.ts +4 -0
  5. package/dist/analysis/claude.d.ts.map +1 -0
  6. package/dist/analysis/claude.js +65 -0
  7. package/dist/analysis/claude.js.map +1 -0
  8. package/dist/analysis/prompt.d.ts +5 -0
  9. package/dist/analysis/prompt.d.ts.map +1 -0
  10. package/dist/analysis/prompt.js +54 -0
  11. package/dist/analysis/prompt.js.map +1 -0
  12. package/dist/cli.d.ts +3 -0
  13. package/dist/cli.d.ts.map +1 -0
  14. package/dist/cli.js +33 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/config/defaults.d.ts +3 -0
  17. package/dist/config/defaults.d.ts.map +1 -0
  18. package/dist/config/defaults.js +11 -0
  19. package/dist/config/defaults.js.map +1 -0
  20. package/dist/config/loader.d.ts +3 -0
  21. package/dist/config/loader.d.ts.map +1 -0
  22. package/dist/config/loader.js +30 -0
  23. package/dist/config/loader.js.map +1 -0
  24. package/dist/config/schema.d.ts +21 -0
  25. package/dist/config/schema.d.ts.map +1 -0
  26. package/dist/config/schema.js +2 -0
  27. package/dist/config/schema.js.map +1 -0
  28. package/dist/git/diff.d.ts +5 -0
  29. package/dist/git/diff.d.ts.map +1 -0
  30. package/dist/git/diff.js +86 -0
  31. package/dist/git/diff.js.map +1 -0
  32. package/dist/git/parse-stdin.d.ts +3 -0
  33. package/dist/git/parse-stdin.d.ts.map +1 -0
  34. package/dist/git/parse-stdin.js +25 -0
  35. package/dist/git/parse-stdin.js.map +1 -0
  36. package/dist/hooks/analyze.d.ts +6 -0
  37. package/dist/hooks/analyze.d.ts.map +1 -0
  38. package/dist/hooks/analyze.js +85 -0
  39. package/dist/hooks/analyze.js.map +1 -0
  40. package/dist/hooks/pre-push.d.ts +2 -0
  41. package/dist/hooks/pre-push.d.ts.map +1 -0
  42. package/dist/hooks/pre-push.js +75 -0
  43. package/dist/hooks/pre-push.js.map +1 -0
  44. package/dist/index.d.ts +9 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +7 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/output/reporter.d.ts +5 -0
  49. package/dist/output/reporter.d.ts.map +1 -0
  50. package/dist/output/reporter.js +77 -0
  51. package/dist/output/reporter.js.map +1 -0
  52. package/dist/setup/install.d.ts +2 -0
  53. package/dist/setup/install.d.ts.map +1 -0
  54. package/dist/setup/install.js +33 -0
  55. package/dist/setup/install.js.map +1 -0
  56. package/dist/types.d.ts +27 -0
  57. package/dist/types.d.ts.map +1 -0
  58. package/dist/types.js +2 -0
  59. package/dist/types.js.map +1 -0
  60. 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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../dist/cli.js";
@@ -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,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -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
@@ -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,3 @@
1
+ import type { GuardStagedConfig } from "./schema.js";
2
+ export declare const DEFAULTS: GuardStagedConfig;
3
+ //# sourceMappingURL=defaults.d.ts.map
@@ -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,3 @@
1
+ import type { GuardStagedConfig } from "./schema.js";
2
+ export declare function loadConfig(cwd?: string): Promise<GuardStagedConfig>;
3
+ //# sourceMappingURL=loader.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=schema.js.map
@@ -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"}
@@ -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,3 @@
1
+ import type { PushRef } from "../types.js";
2
+ export declare function parsePushRefs(input?: NodeJS.ReadableStream): Promise<PushRef[]>;
3
+ //# sourceMappingURL=parse-stdin.d.ts.map
@@ -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,6 @@
1
+ /**
2
+ * Standalone analyze command — diffs unpushed commits against the remote
3
+ * tracking branch without needing git hook stdin.
4
+ */
5
+ export declare function runAnalyze(): Promise<number>;
6
+ //# sourceMappingURL=analyze.d.ts.map
@@ -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,2 @@
1
+ export declare function runPrePush(): Promise<number>;
2
+ //# sourceMappingURL=pre-push.d.ts.map
@@ -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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"}
@@ -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,2 @@
1
+ export declare function init(cwd?: string): Promise<void>;
2
+ //# sourceMappingURL=install.d.ts.map
@@ -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"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -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
+ }