gitreviewpilot 0.1.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 (100) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +234 -0
  3. package/dist/commands/ask.d.ts +3 -0
  4. package/dist/commands/ask.d.ts.map +1 -0
  5. package/dist/commands/ask.js +32 -0
  6. package/dist/commands/ask.js.map +1 -0
  7. package/dist/commands/changes.d.ts +3 -0
  8. package/dist/commands/changes.d.ts.map +1 -0
  9. package/dist/commands/changes.js +57 -0
  10. package/dist/commands/changes.js.map +1 -0
  11. package/dist/commands/install.d.ts +3 -0
  12. package/dist/commands/install.d.ts.map +1 -0
  13. package/dist/commands/install.js +86 -0
  14. package/dist/commands/install.js.map +1 -0
  15. package/dist/commands/pr.d.ts +3 -0
  16. package/dist/commands/pr.d.ts.map +1 -0
  17. package/dist/commands/pr.js +53 -0
  18. package/dist/commands/pr.js.map +1 -0
  19. package/dist/commands/review.d.ts +3 -0
  20. package/dist/commands/review.d.ts.map +1 -0
  21. package/dist/commands/review.js +45 -0
  22. package/dist/commands/review.js.map +1 -0
  23. package/dist/commands/version.d.ts +3 -0
  24. package/dist/commands/version.d.ts.map +1 -0
  25. package/dist/commands/version.js +16 -0
  26. package/dist/commands/version.js.map +1 -0
  27. package/dist/config/env.d.ts +3 -0
  28. package/dist/config/env.d.ts.map +1 -0
  29. package/dist/config/env.js +20 -0
  30. package/dist/config/env.js.map +1 -0
  31. package/dist/config/gitreviewpilotConfig.d.ts +16 -0
  32. package/dist/config/gitreviewpilotConfig.d.ts.map +1 -0
  33. package/dist/config/gitreviewpilotConfig.js +78 -0
  34. package/dist/config/gitreviewpilotConfig.js.map +1 -0
  35. package/dist/index.d.ts +3 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +56 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/services/ai.service.d.ts +60 -0
  40. package/dist/services/ai.service.d.ts.map +1 -0
  41. package/dist/services/ai.service.js +396 -0
  42. package/dist/services/ai.service.js.map +1 -0
  43. package/dist/services/askService.d.ts +12 -0
  44. package/dist/services/askService.d.ts.map +1 -0
  45. package/dist/services/askService.js +68 -0
  46. package/dist/services/askService.js.map +1 -0
  47. package/dist/services/git.service.d.ts +12 -0
  48. package/dist/services/git.service.d.ts.map +1 -0
  49. package/dist/services/git.service.js +61 -0
  50. package/dist/services/git.service.js.map +1 -0
  51. package/dist/services/github.service.d.ts +46 -0
  52. package/dist/services/github.service.d.ts.map +1 -0
  53. package/dist/services/github.service.js +169 -0
  54. package/dist/services/github.service.js.map +1 -0
  55. package/dist/services/prReviewService.d.ts +29 -0
  56. package/dist/services/prReviewService.d.ts.map +1 -0
  57. package/dist/services/prReviewService.js +107 -0
  58. package/dist/services/prReviewService.js.map +1 -0
  59. package/dist/services/prService.d.ts +12 -0
  60. package/dist/services/prService.d.ts.map +1 -0
  61. package/dist/services/prService.js +8 -0
  62. package/dist/services/prService.js.map +1 -0
  63. package/dist/services/reviewErrors.d.ts +7 -0
  64. package/dist/services/reviewErrors.d.ts.map +1 -0
  65. package/dist/services/reviewErrors.js +13 -0
  66. package/dist/services/reviewErrors.js.map +1 -0
  67. package/dist/services/reviewService.d.ts +16 -0
  68. package/dist/services/reviewService.d.ts.map +1 -0
  69. package/dist/services/reviewService.js +109 -0
  70. package/dist/services/reviewService.js.map +1 -0
  71. package/dist/utils/cli.d.ts +16 -0
  72. package/dist/utils/cli.d.ts.map +1 -0
  73. package/dist/utils/cli.js +108 -0
  74. package/dist/utils/cli.js.map +1 -0
  75. package/dist/utils/diff.d.ts +21 -0
  76. package/dist/utils/diff.d.ts.map +1 -0
  77. package/dist/utils/diff.js +105 -0
  78. package/dist/utils/diff.js.map +1 -0
  79. package/dist/utils/formatter.d.ts +11 -0
  80. package/dist/utils/formatter.d.ts.map +1 -0
  81. package/dist/utils/formatter.js +186 -0
  82. package/dist/utils/formatter.js.map +1 -0
  83. package/dist/utils/logger.d.ts +7 -0
  84. package/dist/utils/logger.d.ts.map +1 -0
  85. package/dist/utils/logger.js +35 -0
  86. package/dist/utils/logger.js.map +1 -0
  87. package/dist/utils/repoContext.d.ts +26 -0
  88. package/dist/utils/repoContext.d.ts.map +1 -0
  89. package/dist/utils/repoContext.js +136 -0
  90. package/dist/utils/repoContext.js.map +1 -0
  91. package/dist/utils/spinner.d.ts +3 -0
  92. package/dist/utils/spinner.d.ts.map +1 -0
  93. package/dist/utils/spinner.js +5 -0
  94. package/dist/utils/spinner.js.map +1 -0
  95. package/dist/utils/structuredReview.d.ts +21 -0
  96. package/dist/utils/structuredReview.d.ts.map +1 -0
  97. package/dist/utils/structuredReview.js +59 -0
  98. package/dist/utils/structuredReview.js.map +1 -0
  99. package/gitreviewpilot.config.json +3 -0
  100. package/package.json +37 -0
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,234 @@
1
+ # GitReviewPilot
2
+
3
+ **Fast, structured AI reviews** for your code and PRs — with a focused, token-friendly CLI.
4
+
5
+ Use it for:
6
+
7
+ - Local files (`gitreviewpilot review`)
8
+ - GitHub PR diffs (`gitreviewpilot pr`)
9
+ - Your local branch changes vs upstream (`gitreviewpilot changes`)
10
+ - Questions about your codebase (`gitreviewpilot ask`)
11
+
12
+ It minimizes diffs (keeps only changed lines) to reduce tokens and keep reviews focused.
13
+
14
+ ## Install
15
+
16
+ ### From npm (recommended)
17
+
18
+ ```bash
19
+ npm i -g gitreviewpilot
20
+ gitreviewpilot --help
21
+ ```
22
+
23
+ Or run without installing globally:
24
+
25
+ ```bash
26
+ npx gitreviewpilot --help
27
+ ```
28
+
29
+ ## Quick start
30
+
31
+ ```bash
32
+ gitreviewpilot review README.md
33
+ gitreviewpilot pr 123 --repo vercel/next.js
34
+ gitreviewpilot changes
35
+ gitreviewpilot ask "Where is config loaded?"
36
+ gitreviewpilot version
37
+ ```
38
+
39
+ ## How to use
40
+
41
+ ### Set env (required for AI)
42
+
43
+ Pick one LLM provider and set its key (details are in this README under **LLM Provider**). Example (OpenAI-compatible):
44
+
45
+ ```bash
46
+ export GITREVIEWPILOT_LLM_PROVIDER=openai-compatible
47
+ export GITREVIEWPILOT_LLM_BASE_URL=https://api.openai.com
48
+ export GITREVIEWPILOT_LLM_API_KEY=...
49
+ export GITREVIEWPILOT_LLM_MODEL=gpt-4o-mini
50
+ ```
51
+
52
+ If you want `gitreviewpilot pr`, also set:
53
+
54
+ ```bash
55
+ export GITREVIEWPILOT_GITHUB_TOKEN=...
56
+ ```
57
+
58
+ ### Run commands
59
+
60
+ #### Review a file
61
+
62
+ ```bash
63
+ gitreviewpilot review README.md
64
+ ```
65
+
66
+ #### Review a GitHub PR
67
+
68
+ ```bash
69
+ gitreviewpilot pr 123 --repo vercel/next.js
70
+ ```
71
+
72
+ #### Review local branch changes vs upstream
73
+
74
+ ```bash
75
+ gitreviewpilot changes
76
+ ```
77
+
78
+ #### Ask a question about the repo
79
+
80
+ ```bash
81
+ gitreviewpilot ask "Where is config loaded?"
82
+ ```
83
+
84
+ #### Print version
85
+
86
+ ```bash
87
+ gitreviewpilot version
88
+ ```
89
+
90
+ ### Optional: run before every push
91
+
92
+ Inside a git repo:
93
+
94
+ ```bash
95
+ gitreviewpilot install
96
+ ```
97
+
98
+ That installs a `pre-push` hook that runs `gitreviewpilot changes` before pushing.
99
+
100
+ ## Commands
101
+
102
+ ### `review <file>`
103
+
104
+ Review a local file and print a structured report.
105
+
106
+ ```bash
107
+ gitreviewpilot review src/index.ts
108
+ gitreviewpilot review src/index.ts --max-chars 8000
109
+ ```
110
+
111
+ ### `pr <number> --repo <owner/repo>`
112
+
113
+ Fetch the PR diff from GitHub, minimize it, then generate a structured AI review.
114
+
115
+ ```bash
116
+ gitreviewpilot pr 123 --repo vercel/next.js
117
+ gitreviewpilot pr 123 --repo owner/repo --max-chars 8000
118
+ ```
119
+
120
+ ### `changes`
121
+
122
+ Review your local changes vs the configured upstream branch.
123
+
124
+ Under the hood it runs:
125
+
126
+ ```bash
127
+ git diff --unified=0 @{u}...HEAD
128
+ ```
129
+
130
+ Examples:
131
+
132
+ ```bash
133
+ gitreviewpilot changes
134
+ gitreviewpilot changes --max-chars 8000
135
+ ```
136
+
137
+ If you don’t have an upstream branch set, configure one (example):
138
+
139
+ ```bash
140
+ git branch --set-upstream-to origin/main
141
+ ```
142
+
143
+ ### `ask "<question>"`
144
+
145
+ Ask a question about your local codebase (GitReviewPilot scans a limited subset of files for context).
146
+
147
+ ```bash
148
+ gitreviewpilot ask "How does auth work?"
149
+ gitreviewpilot ask "Where is the database client created?"
150
+ ```
151
+
152
+ ### `install` (optional)
153
+
154
+ Installs a git `pre-push` hook that runs `gitreviewpilot changes` before pushing.
155
+
156
+ ```bash
157
+ gitreviewpilot install
158
+ gitreviewpilot install --force
159
+ ```
160
+
161
+ This writes `.git/hooks/pre-push` to run:
162
+
163
+ ```bash
164
+ node ./dist/index.js changes
165
+ ```
166
+
167
+ ## Configuration
168
+
169
+ Optional: add `gitreviewpilot.config.json` at the project root (for example to customize `focus` areas for prompts).
170
+
171
+ ### Logging
172
+
173
+ - `GITREVIEWPILOT_LOG_LEVEL`: `debug` | `info` | `warn` | `error` (default: `info`)
174
+
175
+ ### GitHub (for `gitreviewpilot pr`)
176
+
177
+ - `GITREVIEWPILOT_GITHUB_TOKEN` (recommended) or `GITHUB_TOKEN`
178
+
179
+ ### LLM Provider (required for AI)
180
+
181
+ GitReviewPilot supports multiple providers. Pick one.
182
+
183
+ #### OpenAI / OpenAI-compatible (default)
184
+
185
+ ```bash
186
+ export GITREVIEWPILOT_LLM_PROVIDER=openai-compatible
187
+ export GITREVIEWPILOT_LLM_BASE_URL=https://api.openai.com
188
+ export GITREVIEWPILOT_LLM_API_KEY=...
189
+ export GITREVIEWPILOT_LLM_MODEL=gpt-4o-mini
190
+ ```
191
+
192
+ Back-compat env vars also work: `OPENAI_BASE_URL`, `OPENAI_API_KEY`, `OPENAI_MODEL`.
193
+
194
+ #### Gemini
195
+
196
+ ```bash
197
+ export GITREVIEWPILOT_LLM_PROVIDER=gemini
198
+ export GITREVIEWPILOT_LLM_API_KEY=...
199
+ export GITREVIEWPILOT_LLM_MODEL=gemini-2.0-flash
200
+ ```
201
+
202
+ #### Anthropic
203
+
204
+ ```bash
205
+ export GITREVIEWPILOT_LLM_PROVIDER=anthropic
206
+ export GITREVIEWPILOT_LLM_API_KEY=...
207
+ export GITREVIEWPILOT_LLM_MODEL=claude-3-5-sonnet-latest
208
+ ```
209
+
210
+ ## Privacy & security notes
211
+
212
+ - GitReviewPilot sends the content it is reviewing (files/diffs and related context) to the configured LLM provider.
213
+ - **Do not** run it on proprietary code if you’re not allowed to share that code with your chosen provider.
214
+ - API keys/tokens are read from environment variables (for example `GITREVIEWPILOT_LLM_API_KEY`, `GITREVIEWPILOT_GITHUB_TOKEN`). Don’t commit them to git.
215
+
216
+ ## Development (this repo)
217
+
218
+ ```bash
219
+ npm install
220
+ npm run build
221
+ node dist/index.js --help
222
+ ```
223
+
224
+ Optional (for a `gitreviewpilot` command in your shell while developing):
225
+
226
+ ```bash
227
+ npm link
228
+ gitreviewpilot --help
229
+ ```
230
+
231
+ ## Requirements
232
+
233
+ - Node.js `>= 20`
234
+ - Git (for `gitreviewpilot changes` and `gitreviewpilot install`)
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function registerAskCommand(program: Command): void;
3
+ //# sourceMappingURL=ask.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ask.d.ts","sourceRoot":"","sources":["../../src/commands/ask.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA4BzD"}
@@ -0,0 +1,32 @@
1
+ import chalk from "chalk";
2
+ import { createSpinner } from "../utils/spinner.js";
3
+ import { logger } from "../utils/logger.js";
4
+ import { askQuestion } from "../services/askService.js";
5
+ export function registerAskCommand(program) {
6
+ program
7
+ .command("ask")
8
+ .argument("<question>", "Question to ask about the codebase")
9
+ .description("Ask a question about the local codebase (reads a limited set of files for context).")
10
+ .addHelpText("after", "\nExamples:\n gitreviewpilot ask \"How does auth work?\"\n gitreviewpilot ask \"Where is the database client created?\"\n")
11
+ .action(async (question) => {
12
+ logger.info(`Question: ${chalk.bold(question)}`);
13
+ const spinner = createSpinner("Thinking...").start();
14
+ try {
15
+ const result = await askQuestion(question);
16
+ spinner.succeed(chalk.green("Answer ready"));
17
+ logger.info(result.answer.trim());
18
+ if (result.includedFiles.length) {
19
+ logger.info("");
20
+ logger.info(chalk.dim(`Context files (${result.includedFiles.length}):`));
21
+ for (const f of result.includedFiles)
22
+ logger.info(chalk.dim(`- ${f}`));
23
+ }
24
+ }
25
+ catch (err) {
26
+ spinner.fail(chalk.red("Ask failed"));
27
+ logger.error(err instanceof Error ? err.message : String(err));
28
+ process.exitCode = 1;
29
+ }
30
+ });
31
+ }
32
+ //# sourceMappingURL=ask.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ask.js","sourceRoot":"","sources":["../../src/commands/ask.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAExD,MAAM,UAAU,kBAAkB,CAAC,OAAgB;IACjD,OAAO;SACJ,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CAAC,YAAY,EAAE,oCAAoC,CAAC;SAC5D,WAAW,CAAC,qFAAqF,CAAC;SAClG,WAAW,CACV,OAAO,EACP,6HAA6H,CAC9H;SACA,MAAM,CAAC,KAAK,EAAE,QAAgB,EAAE,EAAE;QACjC,MAAM,CAAC,IAAI,CAAC,aAAa,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACjD,MAAM,OAAO,GAAG,aAAa,CAAC,aAAa,CAAC,CAAC,KAAK,EAAE,CAAC;QAErD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,CAAC;YAC3C,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;YAC7C,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;YAClC,IAAI,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC;gBAChC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBAChB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,kBAAkB,MAAM,CAAC,aAAa,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;gBAC1E,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,aAAa;oBAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;YACzE,CAAC;QACH,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC;YACtC,MAAM,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAC/D,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function registerChangesCommand(program: Command): void;
3
+ //# sourceMappingURL=changes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changes.d.ts","sourceRoot":"","sources":["../../src/commands/changes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAoD7D"}
@@ -0,0 +1,57 @@
1
+ import chalk from "chalk";
2
+ import { createSpinner } from "../utils/spinner.js";
3
+ import { logger } from "../utils/logger.js";
4
+ import { unifiedDiffAgainstUpstream } from "../services/git.service.js";
5
+ import { parseUnifiedDiff, toMinimizedDiff, truncateToChars } from "../utils/diff.js";
6
+ import { analyze, buildPRReviewPrompt, LlmApiError } from "../services/ai.service.js";
7
+ import { formatAiReview } from "../utils/formatter.js";
8
+ import { parsePositiveIntOption } from "../utils/cli.js";
9
+ export function registerChangesCommand(program) {
10
+ program
11
+ .command("changes")
12
+ .description("Review local changes vs upstream (git diff @{u}...HEAD) using minimized diff.")
13
+ .option("--max-chars <n>", "Max characters of minimized diff to send (default: 12000)", parsePositiveIntOption)
14
+ .addHelpText("after", "\nExamples:\n gitreviewpilot changes\n gitreviewpilot changes --max-chars 8000\n\nNotes:\n - Requires an upstream branch (e.g. origin/main).\n - Uses `git diff --unified=0 @{u}...HEAD` under the hood.\n")
15
+ .action(async (opts) => {
16
+ const spinner = createSpinner("Collecting git diff and generating review...").start();
17
+ try {
18
+ const maxChars = opts.maxChars ?? 12_000;
19
+ const diff = await unifiedDiffAgainstUpstream();
20
+ const files = parseUnifiedDiff(diff);
21
+ const minimized = toMinimizedDiff(files).trim();
22
+ if (!minimized) {
23
+ spinner.fail(chalk.yellow("No textual changes detected"));
24
+ logger.info("No textual hunks to review in `git diff @{u}...HEAD`.");
25
+ return;
26
+ }
27
+ const { text, truncated } = truncateToChars(minimized, maxChars);
28
+ const minimizedForAi = truncated
29
+ ? `${text}\n\n[Note: diff was truncated to ${maxChars.toLocaleString()} characters for analysis.]`
30
+ : text;
31
+ const prompt = buildPRReviewPrompt(minimizedForAi);
32
+ const aiRaw = await analyze(prompt);
33
+ spinner.succeed(chalk.green("Review complete"));
34
+ const changedFiles = files
35
+ .map((f) => f.newPath ?? f.oldPath ?? "(unknown file)")
36
+ .filter((v, i, arr) => Boolean(v) && arr.indexOf(v) === i);
37
+ if (changedFiles.length) {
38
+ console.log(chalk.bold("Files changed:"));
39
+ for (const f of changedFiles)
40
+ console.log(`- ${f}`);
41
+ console.log("");
42
+ }
43
+ console.log(formatAiReview(aiRaw));
44
+ }
45
+ catch (err) {
46
+ spinner.fail(chalk.red("Review failed"));
47
+ if (err instanceof LlmApiError && err.retryAfterMs !== undefined) {
48
+ logger.error(`${err.message} Retry after ${Math.ceil(err.retryAfterMs / 1000)}s.`);
49
+ }
50
+ else {
51
+ throw err;
52
+ }
53
+ process.exitCode = 1;
54
+ }
55
+ });
56
+ }
57
+ //# sourceMappingURL=changes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changes.js","sourceRoot":"","sources":["../../src/commands/changes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function registerInstallCommand(program: Command): void;
3
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoDpC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAyC7D"}
@@ -0,0 +1,86 @@
1
+ import chalk from "chalk";
2
+ import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { createSpinner } from "../utils/spinner.js";
5
+ import { CliError } from "../utils/cli.js";
6
+ import { ensureGitRepo } from "../services/git.service.js";
7
+ async function fileExists(p) {
8
+ try {
9
+ const st = await stat(p);
10
+ return st.isFile();
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ }
16
+ function hookScript() {
17
+ // A Node-based hook is the most portable approach across macOS/Linux/Windows git-bash.
18
+ // In "dev-only" usage (running from a cloned repo), we prefer `node ./dist/index.js changes`.
19
+ // If `dist/index.js` doesn't exist, we fall back to `npx gitreviewpilot changes` for published installs.
20
+ return [
21
+ "#!/usr/bin/env node",
22
+ "/* GitReviewPilot pre-push hook (generated). */",
23
+ "import { spawnSync } from 'node:child_process';",
24
+ "import { existsSync } from 'node:fs';",
25
+ "",
26
+ "const localEntry = './dist/index.js';",
27
+ "const useLocal = existsSync(localEntry);",
28
+ "",
29
+ "let r;",
30
+ "if (useLocal) {",
31
+ " r = spawnSync(process.execPath, [localEntry, 'changes'], { stdio: 'inherit' });",
32
+ "} else {",
33
+ " const cmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';",
34
+ " const args = ['-y', 'gitreviewpilot', 'changes'];",
35
+ " r = spawnSync(cmd, args, { stdio: 'inherit' });",
36
+ "}",
37
+ "process.exitCode = r.status ?? 1;",
38
+ ""
39
+ ].join("\n");
40
+ }
41
+ function hookLooksLikeOurs(existing) {
42
+ return (existing.includes("GitReviewPilot pre-push hook") ||
43
+ existing.includes("gitreviewpilot changes") ||
44
+ existing.includes("PRlens pre-push hook") ||
45
+ existing.includes("prlens changes"));
46
+ }
47
+ export function registerInstallCommand(program) {
48
+ program
49
+ .command("install")
50
+ .description("Install a git pre-push hook that runs `gitreviewpilot changes` before pushing.")
51
+ .option("--force", "Overwrite an existing pre-push hook")
52
+ .addHelpText("after", "\nExamples:\n gitreviewpilot install\n gitreviewpilot install --force\n\nWhat it does:\n - Writes `.git/hooks/pre-push` to run `node ./dist/index.js changes` when available.\n - Falls back to `npx -y gitreviewpilot changes` if `dist/` isn't present.\n")
53
+ .action(async (opts) => {
54
+ const spinner = createSpinner("Installing git pre-push hook...").start();
55
+ try {
56
+ await ensureGitRepo();
57
+ const hooksDir = path.join(process.cwd(), ".git", "hooks");
58
+ await mkdir(hooksDir, { recursive: true });
59
+ const hookPath = path.join(hooksDir, "pre-push");
60
+ if (!opts.force && (await fileExists(hookPath))) {
61
+ const existing = await readFile(hookPath, "utf8").catch(() => "");
62
+ const already = hookLooksLikeOurs(existing);
63
+ spinner.fail(chalk.yellow("Hook already exists"));
64
+ if (already) {
65
+ console.log(chalk.dim(`Existing hook already looks like GitReviewPilot. If you want to rewrite it, run: gitreviewpilot install --force`));
66
+ }
67
+ else {
68
+ console.log(chalk.dim(`A pre-push hook already exists at ${hookPath}. Use --force to overwrite.`));
69
+ }
70
+ process.exitCode = 1;
71
+ return;
72
+ }
73
+ await writeFile(hookPath, hookScript(), { encoding: "utf8" });
74
+ spinner.succeed(chalk.green("Installed pre-push hook"));
75
+ console.log(chalk.dim(`Hook path: ${hookPath}`));
76
+ console.log(chalk.dim("Next: run `gitreviewpilot changes` once to ensure your upstream branch is configured."));
77
+ }
78
+ catch (err) {
79
+ spinner.fail(chalk.red("Install failed"));
80
+ if (err instanceof CliError)
81
+ throw err;
82
+ throw new CliError("HOOK_INSTALL_FAILED", err instanceof Error ? err.message : String(err), undefined, err);
83
+ }
84
+ });
85
+ }
86
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function registerPrCommand(program: Command): void;
3
+ //# sourceMappingURL=pr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pr.d.ts","sourceRoot":"","sources":["../../src/commands/pr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAsDxD"}
@@ -0,0 +1,53 @@
1
+ import chalk from "chalk";
2
+ import { createSpinner } from "../utils/spinner.js";
3
+ import { logger } from "../utils/logger.js";
4
+ import { reviewPullRequest, PrReviewError } from "../services/prReviewService.js";
5
+ import { formatAiReview } from "../utils/formatter.js";
6
+ import { parsePositiveIntOption, parseRepoRefArg } from "../utils/cli.js";
7
+ export function registerPrCommand(program) {
8
+ program
9
+ .command("pr")
10
+ .argument("<prNumber>", "Pull request number to inspect")
11
+ .requiredOption("--repo <owner/repo>", "GitHub repository (e.g. vercel/next.js)", parseRepoRefArg)
12
+ .option("--max-chars <n>", "Max characters of minimized diff to send (default: 12000)", parsePositiveIntOption)
13
+ .description("Fetch PR diff from GitHub, extract only changed lines, and generate a structured AI review.")
14
+ .addHelpText("after", "\nExamples:\n gitreviewpilot pr 123 --repo vercel/next.js\n gitreviewpilot pr 123 --repo owner/repo --max-chars 8000\n\nTips:\n - Set `GITREVIEWPILOT_GITHUB_TOKEN` to avoid rate limits.\n")
15
+ .action(async (prNumberRaw, opts) => {
16
+ logger.info(`PR review requested for ${chalk.bold(opts.repo)}#${chalk.bold(prNumberRaw)}`);
17
+ const spinner = createSpinner("Fetching PR diff and generating review...").start();
18
+ try {
19
+ const result = await reviewPullRequest(opts.repo, prNumberRaw, opts.maxChars === undefined ? {} : { maxChars: opts.maxChars });
20
+ spinner.succeed(chalk.green("Review complete"));
21
+ const metaBits = [
22
+ chalk.bold("PR:"),
23
+ `${result.repo.owner}/${result.repo.repo}#${result.pr.number}`,
24
+ chalk.bold(result.pr.title),
25
+ chalk.gray(`(${result.pr.htmlUrl})`)
26
+ ];
27
+ console.log(metaBits.join(" "));
28
+ console.log(chalk.gray(`Diff: ${result.diffChars.toLocaleString()} chars, minimized: ${result.minimizedDiffChars.toLocaleString()} chars` +
29
+ (result.truncated ? `, sent: ${result.maxChars.toLocaleString()} chars (truncated)` : "")));
30
+ if (result.changedFiles.length > 0) {
31
+ console.log(chalk.bold("Files changed:"));
32
+ for (const f of result.changedFiles)
33
+ console.log(`- ${f}`);
34
+ }
35
+ console.log("");
36
+ console.log(formatAiReview(result.aiRaw));
37
+ }
38
+ catch (err) {
39
+ spinner.fail(chalk.red("PR review failed"));
40
+ if (err instanceof PrReviewError) {
41
+ logger.error(err.message);
42
+ if (process.env.GITREVIEWPILOT_LOG_LEVEL === "debug" && err.details) {
43
+ logger.debug(chalk.gray(err.details));
44
+ }
45
+ }
46
+ else {
47
+ logger.error(err instanceof Error ? err.message : String(err));
48
+ }
49
+ process.exitCode = 1;
50
+ }
51
+ });
52
+ }
53
+ //# sourceMappingURL=pr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pr.js","sourceRoot":"","sources":["../../src/commands/pr.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function registerReviewCommand(program: Command): void;
3
+ //# sourceMappingURL=review.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/commands/review.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAuC5D"}
@@ -0,0 +1,45 @@
1
+ import chalk from "chalk";
2
+ import { createSpinner } from "../utils/spinner.js";
3
+ import { logger } from "../utils/logger.js";
4
+ import { reviewFile } from "../services/reviewService.js";
5
+ import { formatAiReview } from "../utils/formatter.js";
6
+ import { ReviewError } from "../services/reviewErrors.js";
7
+ import { parsePositiveIntOption } from "../utils/cli.js";
8
+ export function registerReviewCommand(program) {
9
+ program
10
+ .command("review")
11
+ .argument("<file>", "File path to review")
12
+ .option("--max-chars <n>", "Max characters to send to the AI (default: 12000)", parsePositiveIntOption)
13
+ .description("Review a local file with AI and print a structured report.")
14
+ .addHelpText("after", "\nExamples:\n gitreviewpilot review README.md\n gitreviewpilot review src/index.ts --max-chars 8000\n")
15
+ .action(async (file, opts) => {
16
+ logger.info(`Review requested for ${chalk.bold(file)}`);
17
+ const spinner = createSpinner(`Reviewing ${file}...`).start();
18
+ try {
19
+ const result = await reviewFile(file, opts.maxChars === undefined ? {} : { maxChars: opts.maxChars });
20
+ spinner.succeed(chalk.green("Review complete"));
21
+ const headerBits = [
22
+ chalk.bold("File:"),
23
+ result.filePath,
24
+ chalk.gray(`(${result.bytesRead.toLocaleString()} bytes${result.truncated ? `, truncated to ${result.maxChars.toLocaleString()} chars` : ""})`)
25
+ ];
26
+ console.log(headerBits.join(" "));
27
+ console.log("");
28
+ console.log(formatAiReview(result.aiRaw));
29
+ }
30
+ catch (err) {
31
+ spinner.fail(chalk.red("Review failed"));
32
+ if (err instanceof ReviewError) {
33
+ logger.error(err.message);
34
+ if (process.env.GITREVIEWPILOT_LOG_LEVEL === "debug" && err.details) {
35
+ logger.debug(chalk.gray(err.details));
36
+ }
37
+ }
38
+ else {
39
+ logger.error(err instanceof Error ? err.message : String(err));
40
+ }
41
+ process.exitCode = 1;
42
+ }
43
+ });
44
+ }
45
+ //# sourceMappingURL=review.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.js","sourceRoot":"","sources":["../../src/commands/review.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,OAAO;SACJ,OAAO,CAAC,QAAQ,CAAC;SACjB,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,CAAC;SACzC,MAAM,CAAC,iBAAiB,EAAE,mDAAmD,EAAE,sBAAsB,CAAC;SACtG,WAAW,CAAC,4DAA4D,CAAC;SACzE,WAAW,CACV,OAAO,EACP,yGAAyG,CAC1G;SACA,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,IAA2B,EAAE,EAAE;QAC1D,MAAM,CAAC,IAAI,CAAC,wBAAwB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACxD,MAAM,OAAO,GAAG,aAAa,CAAC,aAAa,IAAI,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC;QAE9D,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;YACtG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC;YAEhD,MAAM,UAAU,GAAG;gBACjB,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC;gBACnB,MAAM,CAAC,QAAQ;gBACf,KAAK,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,EAAE,SAAS,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,kBAAkB,MAAM,CAAC,QAAQ,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC;aAChJ,CAAC;YACF,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAClC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC;YACzC,IAAI,GAAG,YAAY,WAAW,EAAE,CAAC;gBAC/B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBAC1B,IAAI,OAAO,CAAC,GAAG,CAAC,wBAAwB,KAAK,OAAO,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;oBACpE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;gBACxC,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YACjE,CAAC;YACD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function registerVersionCommand(program: Command): void;
3
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/commands/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgB7D"}
@@ -0,0 +1,16 @@
1
+ import { createRequire } from "node:module";
2
+ export function registerVersionCommand(program) {
3
+ program
4
+ .command("version")
5
+ .description("Print the installed GitReviewPilot version.")
6
+ .addHelpText("after", "\nExamples:\n gitreviewpilot version\n gitreviewpilot -V\n")
7
+ .action(() => {
8
+ const require = createRequire(import.meta.url);
9
+ const pkg = require("../../package.json");
10
+ const name = pkg.name ?? "gitreviewpilot";
11
+ const version = pkg.version ?? "0.0.0";
12
+ // Standard CLI output: `<name> <version>`
13
+ console.log(`${name} ${version}`);
14
+ });
15
+ }
16
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/commands/version.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,UAAU,sBAAsB,CAAC,OAAgB;IACrD,OAAO;SACJ,OAAO,CAAC,SAAS,CAAC;SAClB,WAAW,CAAC,6CAA6C,CAAC;SAC1D,WAAW,CACV,OAAO,EACP,8DAA8D,CAC/D;SACA,MAAM,CAAC,GAAG,EAAE;QACX,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,oBAAoB,CAAwC,CAAC;QACjF,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,IAAI,gBAAgB,CAAC;QAC1C,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC;QACvC,0CAA0C;QAC1C,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,OAAO,EAAE,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function loadEnv(): void;
2
+ export declare function getEnv(name: string): string | undefined;
3
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/config/env.ts"],"names":[],"mappings":"AAKA,wBAAgB,OAAO,IAAI,IAAI,CAW9B;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEvD"}