patchwise 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,291 @@
1
+ # Patchwise
2
+
3
+ AI-assisted Git commits, with you still in charge.
4
+
5
+ Patchwise is a CLI tool that helps developers turn raw Git changes into clean, structured, and meaningful commits using AI assistance.
6
+
7
+ It analyzes your diff, suggests commit messages, and guides you through staging and committing while keeping you fully in control.
8
+
9
+ ---
10
+
11
+ ## Features
12
+
13
+ * Analyze staged or unstaged changes
14
+ * Generate commit messages using AI
15
+ * Supports Conventional Commits
16
+ * Interactive file selection
17
+ * Edit before committing
18
+ * Optional push after commit
19
+ * Provider-agnostic architecture, Groq by default
20
+ * Multi-language support (EN / FR)
21
+
22
+ ---
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ npm install -g patchwise
28
+ ```
29
+
30
+ or
31
+
32
+ ```bash
33
+ pnpm add -g patchwise
34
+ ```
35
+
36
+ ---
37
+
38
+ ## Quick Start
39
+
40
+ ### 1. Set your API key
41
+
42
+ ```bash
43
+ export GROQ_API_KEY=your_api_key_here
44
+ ```
45
+
46
+ ### 2. Stage your changes
47
+
48
+ ```bash
49
+ git add .
50
+ ```
51
+
52
+ ### 3. Generate and commit
53
+
54
+ ```bash
55
+ patchwise commit
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Example
61
+
62
+ ```bash
63
+ patchwise commit
64
+ ```
65
+
66
+ ```txt
67
+ Summary:
68
+ Adds retry logic for failed payment transactions
69
+
70
+ Suggestions:
71
+ 1. feat(payment): add retry mechanism for failed transactions
72
+ 2. fix(payment): handle transient provider failures
73
+ 3. refactor(payment): improve error handling
74
+
75
+ Select a commit message:
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Commands
81
+
82
+ ### patchwise commit
83
+
84
+ Generate and create a commit from staged changes.
85
+
86
+ ```bash
87
+ patchwise commit
88
+ ```
89
+
90
+ Options:
91
+
92
+ * `--all` stage all changes before commit
93
+ * `--select` interactively select files
94
+ * `--push` push after commit
95
+ * `--yes` skip confirmations
96
+ * `--lang <en|fr>` commit language
97
+ * `--provider <name>` AI provider
98
+ * `--model <model>` AI model
99
+ * `--scope <scope>` set commit scope
100
+ * `--no-scope` disable scope
101
+
102
+ ---
103
+
104
+ ### patchwise suggest
105
+
106
+ Generate commit suggestions without committing.
107
+
108
+ ```bash
109
+ patchwise suggest
110
+ ```
111
+
112
+ ---
113
+
114
+ ### patchwise stage
115
+
116
+ Interactively select files to stage.
117
+
118
+ ```bash
119
+ patchwise stage
120
+ ```
121
+
122
+ ---
123
+
124
+ ### patchwise config init
125
+
126
+ Create a config file.
127
+
128
+ ```bash
129
+ patchwise config init
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Configuration
135
+
136
+ ### Environment variables
137
+
138
+ ```bash
139
+ GROQ_API_KEY=xxx
140
+ PATCHWISE_PROVIDER=groq
141
+ PATCHWISE_MODEL=llama-3.3-70b-versatile
142
+ ```
143
+
144
+ ---
145
+
146
+ ### Config file
147
+
148
+ Create `patchwise.config.json`:
149
+
150
+ ```json
151
+ {
152
+ "provider": "groq",
153
+ "model": "llama-3.3-70b-versatile",
154
+ "commitConvention": "conventional",
155
+ "language": "en",
156
+ "maxSubjectLength": 72,
157
+ "confirmBeforeCommit": true,
158
+ "confirmBeforePush": true
159
+ }
160
+ ```
161
+
162
+ ---
163
+
164
+ ## Conventional Commits
165
+
166
+ Patchwise generates commits following this format:
167
+
168
+ ```txt
169
+ type(scope): subject
170
+ ```
171
+
172
+ Examples:
173
+
174
+ ```txt
175
+ feat(auth): add email verification flow
176
+ fix(api): handle null company id
177
+ refactor(ui): simplify sidebar logic
178
+ docs(readme): update installation guide
179
+ ```
180
+
181
+ ---
182
+
183
+ ## AI Providers
184
+
185
+ Supported:
186
+
187
+ * Groq
188
+
189
+ Planned:
190
+
191
+ * OpenAI
192
+ * Ollama
193
+ * Anthropic
194
+
195
+ ---
196
+
197
+ ## Architecture
198
+
199
+ ```
200
+ CLI
201
+ ├── Git Layer
202
+ ├── AI Layer
203
+ ├── Commit Engine
204
+ └── UI Layer
205
+ ```
206
+
207
+ ---
208
+
209
+ ## Safety
210
+
211
+ * No commit without user validation
212
+ * No push without confirmation unless explicitly requested
213
+ * API keys are not stored in the project
214
+ * Only staged changes are used by default
215
+
216
+ ---
217
+
218
+ ## Development
219
+
220
+ Install dependencies:
221
+
222
+ ```bash
223
+ pnpm install
224
+ ```
225
+
226
+ Run in development:
227
+
228
+ ```bash
229
+ pnpm dev
230
+ ```
231
+
232
+ Build:
233
+
234
+ ```bash
235
+ pnpm build
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Roadmap
241
+
242
+ V1:
243
+
244
+ * commit suggestions
245
+ * commit execution
246
+ * Groq integration
247
+ * file selection
248
+
249
+ V1.1:
250
+
251
+ * push support
252
+ * commit body generation
253
+ * language support
254
+
255
+ V2:
256
+
257
+ * hunk selection
258
+ * multi-commit split
259
+ * multi-provider support
260
+ * local AI
261
+
262
+ ---
263
+
264
+ ## Contributing
265
+
266
+ Contributions are welcome.
267
+
268
+ * open an issue
269
+ * submit a pull request
270
+ * improve documentation
271
+ * propose features
272
+
273
+ ---
274
+
275
+ ## License
276
+
277
+ MIT
278
+
279
+ ---
280
+
281
+ ## Philosophy
282
+
283
+ Patchwise does not replace Git.
284
+
285
+ It helps you write better commits, keep history clean, and move faster without losing control.
286
+
287
+ ---
288
+
289
+ ## Support
290
+
291
+ If you find the project useful, consider starring the repository.
@@ -0,0 +1,865 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/program.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/core/ai/prompt.ts
7
+ function buildPrompt(input2) {
8
+ const categories = categorizeFiles(input2.fileNames);
9
+ const categoryList = Array.from(categories).join(", ");
10
+ return [
11
+ "You generate Conventional Commit messages from a Git diff.",
12
+ "Return strict JSON only.",
13
+ "The JSON shape must be:",
14
+ '{"summary":"string","suggestions":[{"type":"feat","scope":"optional","subject":"string","body":"optional string"}]}',
15
+ "Rules:",
16
+ "- EVERY suggestion must cover ALL changes in the diff, never just a subset.",
17
+ "- The body should list each change group as bullet points.",
18
+ `- This diff touches these areas: ${categoryList}.`,
19
+ "",
20
+ "Suggestion 1 \u2014 Primary: the most accurate type/scope for the full change.",
21
+ "Suggestion 2 \u2014 Alternative angle: use a different type or scope that also fits.",
22
+ "Suggestion 3 \u2014 Broad: a higher-level framing that encompasses everything.",
23
+ "",
24
+ "Each suggestion must have a DISTINCT type, scope, or subject. No rephrasing.",
25
+ "- Subjects must be imperative, concise, and lower-case.",
26
+ `- Subject max length: ${input2.maxSubjectLength}.`,
27
+ `- Language: ${input2.language}.`,
28
+ `- Scope strategy: ${input2.scopeStrategy}.`,
29
+ input2.scope ? `- Preferred scope: ${input2.scope}.` : "- Preferred scope: infer only if obvious.",
30
+ `- Files involved: ${input2.fileNames.join(", ") || "unknown"}.`,
31
+ "Diff:",
32
+ input2.diff,
33
+ input2.diff.includes("[diff truncated") ? "\nNote: the diff was truncated to fit token limits. Use the file list and stat summary to infer the full scope." : ""
34
+ ].join("\n");
35
+ }
36
+ function categorizeFiles(files) {
37
+ const categories = /* @__PURE__ */ new Set();
38
+ for (const f of files) {
39
+ if (f.startsWith("src/") || f.startsWith("lib/")) categories.add("code");
40
+ else if (f.startsWith("test/") || f.startsWith("tests/") || f.startsWith("spec/")) categories.add("test");
41
+ else if (f.startsWith("docs/") || f.endsWith(".md")) categories.add("docs");
42
+ else if (f.includes(".config.") || f.endsWith("rc") || f.endsWith(".json")) categories.add("config");
43
+ else if (f.startsWith(".github/") || f.endsWith(".yml") || f.endsWith(".yaml")) categories.add("ci");
44
+ else categories.add("other");
45
+ }
46
+ return categories;
47
+ }
48
+
49
+ // src/core/ai/schemas.ts
50
+ import { z } from "zod";
51
+ var VALID_COMMIT_TYPES = [
52
+ "feat",
53
+ "fix",
54
+ "refactor",
55
+ "docs",
56
+ "test",
57
+ "chore",
58
+ "perf",
59
+ "build",
60
+ "ci"
61
+ ];
62
+ function coerceCommitType(value) {
63
+ if (VALID_COMMIT_TYPES.includes(value)) {
64
+ return value;
65
+ }
66
+ const aliasMap = {
67
+ bugfix: "fix",
68
+ "bug-fix": "fix",
69
+ bug: "fix",
70
+ feature: "feat",
71
+ "new-feature": "feat",
72
+ improvement: "refactor",
73
+ improve: "refactor",
74
+ cleanup: "chore",
75
+ maintenance: "chore",
76
+ style: "refactor",
77
+ formatting: "chore",
78
+ config: "chore",
79
+ configuration: "chore",
80
+ build: "build",
81
+ ci: "ci",
82
+ test: "test",
83
+ tests: "test",
84
+ testing: "test",
85
+ docs: "docs",
86
+ doc: "docs",
87
+ documentation: "docs",
88
+ perf: "perf",
89
+ performance: "perf",
90
+ refactor: "refactor",
91
+ feat: "feat",
92
+ fix: "fix",
93
+ chore: "chore"
94
+ };
95
+ return aliasMap[value.toLowerCase()] ?? "feat";
96
+ }
97
+ var suggestionSchema = z.object({
98
+ type: z.string().transform(coerceCommitType),
99
+ scope: z.string().optional().transform((v) => v && v.trim().length > 0 ? v.trim() : void 0),
100
+ subject: z.string().min(1),
101
+ body: z.string().optional()
102
+ });
103
+ var providerResponseSchema = z.object({
104
+ summary: z.string().min(1),
105
+ suggestions: z.array(suggestionSchema).min(1).max(10)
106
+ });
107
+
108
+ // src/core/ai/providers/groq.ts
109
+ var GROQ_API_URL = "https://api.groq.com/openai/v1/chat/completions";
110
+ var GroqAIProvider = class {
111
+ constructor(apiKey, model) {
112
+ this.apiKey = apiKey;
113
+ this.model = model;
114
+ }
115
+ apiKey;
116
+ model;
117
+ async generateCommitSuggestions(input2) {
118
+ const response = await fetch(GROQ_API_URL, {
119
+ method: "POST",
120
+ headers: {
121
+ "Content-Type": "application/json",
122
+ Authorization: `Bearer ${this.apiKey}`
123
+ },
124
+ body: JSON.stringify({
125
+ model: this.model,
126
+ temperature: 0.2,
127
+ response_format: { type: "json_object" },
128
+ messages: [
129
+ {
130
+ role: "system",
131
+ content: "You are a precise commit assistant. Return valid JSON only."
132
+ },
133
+ {
134
+ role: "user",
135
+ content: buildPrompt(input2)
136
+ }
137
+ ]
138
+ })
139
+ });
140
+ if (!response.ok) {
141
+ const body = await response.text();
142
+ throw new Error(`Groq API error (${response.status}): ${body}`);
143
+ }
144
+ const payload = await response.json();
145
+ const rawContent = payload.choices?.[0]?.message?.content;
146
+ if (!rawContent) {
147
+ throw new Error("Groq API returned an empty response.");
148
+ }
149
+ const parsed = providerResponseSchema.parse(JSON.parse(rawContent));
150
+ return parsed;
151
+ }
152
+ };
153
+
154
+ // src/core/ai/create-provider.ts
155
+ function createAIProvider(config) {
156
+ if (config.provider === "groq") {
157
+ const apiKey = config.groqApiKey;
158
+ if (!apiKey) {
159
+ throw new Error(
160
+ "Missing Groq API key. Run `patchwise setup` to configure it."
161
+ );
162
+ }
163
+ return new GroqAIProvider(apiKey, config.model);
164
+ }
165
+ throw new Error(`Unsupported provider: ${config.provider}`);
166
+ }
167
+
168
+ // src/core/commit/diff.ts
169
+ function truncateDiff(diff, maxChars = 8e3) {
170
+ if (diff.length <= maxChars) {
171
+ return diff;
172
+ }
173
+ const files = parseDiffFiles(diff);
174
+ if (files.length === 0) {
175
+ return diff.slice(0, maxChars) + "\n\n[diff truncated]";
176
+ }
177
+ const statLines = files.map((f) => `${f.path} (+${f.added}/-${f.removed})`);
178
+ let result = `Files changed: ${files.length}
179
+ ${statLines.join("\n")}
180
+
181
+ `;
182
+ const maxLinesPerFile = Math.max(10, Math.floor((maxChars - result.length) / files.length / 6));
183
+ for (const file of files) {
184
+ const header = `--- ${file.path} ---`;
185
+ const lines = file.diffLines.slice(0, maxLinesPerFile);
186
+ const truncated = file.diffLines.length > maxLinesPerFile ? `
187
+ [... ${file.diffLines.length - maxLinesPerFile} more lines]` : "";
188
+ const fileBlock = `${header}
189
+ ${lines.join("\n")}${truncated}
190
+ `;
191
+ if (result.length + fileBlock.length > maxChars) {
192
+ result += "\n[diff truncated \u2014 remaining files omitted]";
193
+ break;
194
+ }
195
+ result += fileBlock;
196
+ }
197
+ return result;
198
+ }
199
+ function parseDiffFiles(diff) {
200
+ const files = [];
201
+ const lines = diff.split("\n");
202
+ let current = null;
203
+ for (const line of lines) {
204
+ if (line.startsWith("diff --git ")) {
205
+ if (current) files.push(current);
206
+ const parts = line.split(" ");
207
+ const path2 = parts[2]?.replace(/^a\//, "") ?? "unknown";
208
+ current = { path: path2, diffLines: [], added: 0, removed: 0 };
209
+ } else if (current) {
210
+ current.diffLines.push(line);
211
+ if (line.startsWith("+")) current.added++;
212
+ else if (line.startsWith("-")) current.removed++;
213
+ }
214
+ }
215
+ if (current) files.push(current);
216
+ return files;
217
+ }
218
+ function extractFileNamesFromDiff(diff) {
219
+ const fileNames = /* @__PURE__ */ new Set();
220
+ for (const line of diff.split("\n")) {
221
+ if (!line.startsWith("diff --git ")) {
222
+ continue;
223
+ }
224
+ const parts = line.split(" ");
225
+ const filePath = parts[2]?.replace(/^a\//, "");
226
+ if (filePath) {
227
+ fileNames.add(filePath);
228
+ }
229
+ }
230
+ return [...fileNames];
231
+ }
232
+
233
+ // src/core/commit/format.ts
234
+ function formatCommitMessage(suggestion) {
235
+ const scope = suggestion.scope ? `(${suggestion.scope})` : "";
236
+ return `${suggestion.type}${scope}: ${suggestion.subject}`;
237
+ }
238
+ function formatCommitMessageWithBody(suggestion) {
239
+ const header = formatCommitMessage(suggestion);
240
+ if (!suggestion.body) return header;
241
+ return `${header}
242
+
243
+ ${suggestion.body}`;
244
+ }
245
+ function applyScopeOverride(suggestion, scope, disableScope) {
246
+ if (disableScope) {
247
+ return {
248
+ ...suggestion,
249
+ scope: void 0
250
+ };
251
+ }
252
+ if (!scope) {
253
+ return suggestion;
254
+ }
255
+ return {
256
+ ...suggestion,
257
+ scope
258
+ };
259
+ }
260
+ function truncateSubject(subject, maxLength) {
261
+ if (subject.length <= maxLength) {
262
+ return subject;
263
+ }
264
+ return subject.slice(0, maxLength - 1).trimEnd();
265
+ }
266
+
267
+ // src/cli/services.ts
268
+ async function generateSuggestionsFromDiff(diff, config, options) {
269
+ const provider = createAIProvider(config);
270
+ const input2 = {
271
+ diff: truncateDiff(diff),
272
+ fileNames: extractFileNamesFromDiff(diff),
273
+ language: options?.language ?? config.language,
274
+ scopeStrategy: options?.noScope ? "none" : options?.scope ? "manual" : config.scopeStrategy,
275
+ scope: options?.scope,
276
+ maxSubjectLength: config.maxSubjectLength
277
+ };
278
+ const result = await provider.generateCommitSuggestions(input2);
279
+ return {
280
+ ...result,
281
+ suggestions: result.suggestions.map(
282
+ (suggestion) => normalizeSuggestion(
283
+ suggestion,
284
+ config.maxSubjectLength,
285
+ options?.scope,
286
+ options?.noScope
287
+ )
288
+ ).slice(0, 3)
289
+ };
290
+ }
291
+ function normalizeSuggestion(suggestion, maxSubjectLength, scope, noScope) {
292
+ const withScope = applyScopeOverride(suggestion, scope, noScope);
293
+ return {
294
+ ...withScope,
295
+ subject: truncateSubject(withScope.subject, maxSubjectLength)
296
+ };
297
+ }
298
+
299
+ // src/core/git/client.ts
300
+ import { execFile } from "child_process";
301
+ import { promisify } from "util";
302
+ var execFileAsync = promisify(execFile);
303
+ async function assertGitRepository(cwd = process.cwd()) {
304
+ try {
305
+ await execGit(["rev-parse", "--is-inside-work-tree"], cwd);
306
+ } catch {
307
+ throw new Error("Current directory is not a Git repository.");
308
+ }
309
+ }
310
+ async function getStagedDiff(cwd = process.cwd()) {
311
+ return execGit(["diff", "--cached", "--no-ext-diff"], cwd);
312
+ }
313
+ async function getModifiedFiles(cwd = process.cwd()) {
314
+ const output = await execGit(["status", "--short"], cwd);
315
+ return output.split("\n").filter(Boolean).map((line) => ({
316
+ indexStatus: line[0] ?? " ",
317
+ workingTreeStatus: line[1] ?? " ",
318
+ path: line.slice(3).trim()
319
+ }));
320
+ }
321
+ async function stageFiles(files, cwd = process.cwd()) {
322
+ if (files.length === 0) {
323
+ return;
324
+ }
325
+ await execGit(["add", "--", ...files], cwd);
326
+ }
327
+ async function stageAll(cwd = process.cwd()) {
328
+ await execGit(["add", "-A"], cwd);
329
+ }
330
+ async function createCommit(message, cwd = process.cwd()) {
331
+ await execGit(["commit", "-m", message], cwd);
332
+ }
333
+ async function pushCurrentBranch(cwd = process.cwd()) {
334
+ await execGit(["push"], cwd);
335
+ }
336
+ async function getCurrentBranch(cwd = process.cwd()) {
337
+ return execGit(["branch", "--show-current"], cwd);
338
+ }
339
+ async function execGit(args, cwd) {
340
+ try {
341
+ const { stdout } = await execFileAsync("git", args, {
342
+ cwd,
343
+ encoding: "utf8",
344
+ maxBuffer: 1024 * 1024 * 10
345
+ });
346
+ return stdout.trim();
347
+ } catch (error) {
348
+ if (error instanceof Error && "stderr" in error) {
349
+ const stderr = String(error.stderr ?? "").trim();
350
+ throw new Error(stderr || "Git command failed.", { cause: error });
351
+ }
352
+ throw error;
353
+ }
354
+ }
355
+
356
+ // src/core/ui/output.ts
357
+ import chalk from "chalk";
358
+ var BORDER = "\u2500";
359
+ var WIDTH = 56;
360
+ function divider(label) {
361
+ const line = chalk.dim(BORDER.repeat(WIDTH));
362
+ if (!label) return line;
363
+ const padded = ` ${label} `;
364
+ const remaining = Math.max(0, WIDTH - padded.length);
365
+ const half = Math.floor(remaining / 2);
366
+ return chalk.dim(BORDER.repeat(half)) + chalk.dim(padded) + chalk.dim(BORDER.repeat(remaining - half));
367
+ }
368
+ function typeBadge(type) {
369
+ const colors = {
370
+ feat: chalk.cyan,
371
+ fix: chalk.yellow,
372
+ refactor: chalk.magenta,
373
+ docs: chalk.blue,
374
+ test: chalk.green,
375
+ chore: chalk.gray,
376
+ perf: chalk.red,
377
+ build: chalk.yellow,
378
+ ci: chalk.blueBright
379
+ };
380
+ const color = colors[type] ?? chalk.white;
381
+ return color(` ${type.toUpperCase()} `);
382
+ }
383
+ function formatSuggestionLine(suggestion, index) {
384
+ const header = formatCommitMessage(suggestion);
385
+ const badge = typeBadge(suggestion.type);
386
+ const number = chalk.bold(`${index + 1}.`);
387
+ const line = ` ${number} ${badge} ${chalk.white(header)}`;
388
+ if (suggestion.body) {
389
+ const bodyLines = suggestion.body.split("\n");
390
+ const preview = bodyLines.slice(0, 3).join("\n");
391
+ const more = bodyLines.length > 3 ? chalk.dim(` ... +${bodyLines.length - 3} more lines`) : "";
392
+ return `${line}
393
+ ${chalk.dim(` ${preview}`)}${more ? `
394
+ ${more}` : ""}`;
395
+ }
396
+ return line;
397
+ }
398
+ function printSuggestionResult(result) {
399
+ console.log();
400
+ console.log(divider());
401
+ console.log(` ${chalk.bold(chalk.white("\u{1F4DD} Commit Suggestions"))}`);
402
+ console.log(divider());
403
+ console.log();
404
+ console.log(` ${chalk.dim("Summary:")}`);
405
+ console.log(` ${chalk.italic(chalk.white(result.summary))}`);
406
+ console.log();
407
+ console.log(` ${chalk.dim("Suggestions:")}`);
408
+ console.log();
409
+ result.suggestions.forEach((suggestion, index) => {
410
+ console.log(formatSuggestionLine(suggestion, index));
411
+ });
412
+ console.log();
413
+ console.log(divider());
414
+ console.log();
415
+ }
416
+ function printCommitCreated(message, branch) {
417
+ console.log();
418
+ console.log(divider());
419
+ console.log(` ${chalk.green("\u2714")} ${chalk.green("Commit created:")}`);
420
+ console.log(` ${chalk.white(message)}`);
421
+ if (branch) {
422
+ console.log(` ${chalk.dim(`on branch ${branch}`)}`);
423
+ }
424
+ console.log(divider());
425
+ console.log();
426
+ }
427
+ function printPushed(branch) {
428
+ console.log();
429
+ console.log(divider());
430
+ console.log(
431
+ ` ${chalk.green("\u2714")} ${chalk.green(`Pushed to origin/${branch}`)}`
432
+ );
433
+ console.log(divider());
434
+ console.log();
435
+ }
436
+ function printCancelled() {
437
+ console.log(` ${chalk.yellow("\u21A9")} ${chalk.yellow("Cancelled.")}`);
438
+ }
439
+
440
+ // src/core/ui/prompts.ts
441
+ import { checkbox, confirm, input, password, select } from "@inquirer/prompts";
442
+ import chalk2 from "chalk";
443
+
444
+ // src/core/ai/models.ts
445
+ var GROQ_MODELS_URL = "https://api.groq.com/openai/v1/models";
446
+ async function fetchGroqModels(apiKey) {
447
+ const response = await fetch(GROQ_MODELS_URL, {
448
+ headers: {
449
+ Authorization: `Bearer ${apiKey}`
450
+ }
451
+ });
452
+ if (!response.ok) {
453
+ throw new Error(
454
+ `Failed to fetch models (${response.status}). Check your API key.`
455
+ );
456
+ }
457
+ const data = await response.json();
458
+ return (data.data ?? []).filter((m) => m.active !== false).map((m) => ({
459
+ id: m.id,
460
+ name: formatModelName(m.id),
461
+ active: m.active ?? true
462
+ })).sort((a, b) => a.name.localeCompare(b.name));
463
+ }
464
+ function formatModelName(id) {
465
+ return id.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
466
+ }
467
+
468
+ // src/core/ui/prompts.ts
469
+ async function promptForFiles(files) {
470
+ return checkbox({
471
+ message: chalk2.bold("Select files to stage"),
472
+ choices: files.map((file) => ({
473
+ name: `${getStatusIcon(file.indexStatus, file.workingTreeStatus)} ${file.path}`,
474
+ value: file.path
475
+ })),
476
+ required: false
477
+ });
478
+ }
479
+ function getStatusIcon(index, working) {
480
+ if (index !== " ") return chalk2.green(`[${index}]`);
481
+ if (working !== " ") return chalk2.yellow(`[${working}]`);
482
+ return chalk2.dim(`[\xB7]`);
483
+ }
484
+ async function promptForSuggestion(suggestions) {
485
+ const selected = await select({
486
+ message: chalk2.bold("Select a commit message"),
487
+ choices: [
488
+ ...suggestions.map((suggestion, index) => ({
489
+ name: `${chalk2.bold(`${index + 1}.`)} ${formatCommitMessageWithBody(suggestion)}`,
490
+ value: formatCommitMessageWithBody(suggestion)
491
+ })),
492
+ {
493
+ name: chalk2.italic("\u270F\uFE0F Write a custom message"),
494
+ value: "__custom__"
495
+ }
496
+ ]
497
+ });
498
+ if (selected !== "__custom__") {
499
+ return selected;
500
+ }
501
+ return input({
502
+ message: chalk2.bold("Commit message"),
503
+ validate(value) {
504
+ return value.trim().length > 0 || "Commit message cannot be empty.";
505
+ }
506
+ });
507
+ }
508
+ async function confirmAction(message, defaultValue = true) {
509
+ return confirm({
510
+ message: chalk2.yellow(`? ${message}`),
511
+ default: defaultValue
512
+ });
513
+ }
514
+ async function promptForSetup(defaults) {
515
+ const provider = await select({
516
+ message: chalk2.bold("Select your AI provider"),
517
+ choices: [{ name: "Groq", value: "groq" }],
518
+ default: defaults.provider ?? "groq"
519
+ });
520
+ const groqApiKey = await password({
521
+ message: chalk2.bold("Groq API key") + chalk2.dim(" (https://console.groq.com/keys)"),
522
+ mask: "*",
523
+ validate(value) {
524
+ return value.trim().length > 0 || "API key is required.";
525
+ }
526
+ });
527
+ const trimmedApiKey = groqApiKey.trim();
528
+ let modelChoices = [];
529
+ try {
530
+ const models = await fetchGroqModels(trimmedApiKey);
531
+ modelChoices = models.map((m) => ({
532
+ name: `${m.name}`,
533
+ value: m.id
534
+ }));
535
+ } catch {
536
+ }
537
+ let model;
538
+ if (modelChoices.length > 0) {
539
+ const defaultModel = defaults.model ?? "llama-3.3-70b-versatile";
540
+ const defaultChoice = modelChoices.find((c) => c.value === defaultModel);
541
+ model = await select({
542
+ message: chalk2.bold("Select a Groq model"),
543
+ choices: modelChoices,
544
+ default: defaultChoice?.value
545
+ });
546
+ } else {
547
+ model = await input({
548
+ message: chalk2.bold("Groq model") + chalk2.dim(" (enter manually)"),
549
+ default: defaults.model ?? "llama-3.3-70b-versatile",
550
+ validate(value) {
551
+ return value.trim().length > 0 || "Model is required.";
552
+ }
553
+ });
554
+ }
555
+ const language = await select({
556
+ message: chalk2.bold("Default commit language"),
557
+ choices: [
558
+ { name: "\u{1F1EC}\u{1F1E7} English", value: "en" },
559
+ { name: "\u{1F1EB}\u{1F1F7} French", value: "fr" }
560
+ ],
561
+ default: defaults.language ?? "en"
562
+ });
563
+ return {
564
+ provider,
565
+ model: model.trim(),
566
+ language,
567
+ groqApiKey: trimmedApiKey
568
+ };
569
+ }
570
+
571
+ // src/cli/commands/commit.ts
572
+ async function runCommitCommand(context, options) {
573
+ await assertGitRepository(context.cwd);
574
+ if (options.all) {
575
+ await stageAll(context.cwd);
576
+ }
577
+ if (options.select) {
578
+ const files = await getModifiedFiles(context.cwd);
579
+ if (files.length === 0) {
580
+ throw new Error("No modified files found.");
581
+ }
582
+ const selected = await promptForFiles(files);
583
+ if (selected.length === 0) {
584
+ throw new Error("No files selected for staging.");
585
+ }
586
+ await stageFiles(selected, context.cwd);
587
+ }
588
+ const diff = await getStagedDiff(context.cwd);
589
+ if (!diff) {
590
+ throw new Error(
591
+ "No staged changes found. Stage files before running commit."
592
+ );
593
+ }
594
+ const config = {
595
+ ...context.config,
596
+ language: options.lang ?? context.config.language,
597
+ provider: options.provider ?? context.config.provider,
598
+ model: options.model ?? context.config.model
599
+ };
600
+ const result = await generateSuggestionsFromDiff(diff, config, {
601
+ language: options.lang,
602
+ scope: options.scope,
603
+ noScope: options.noScope
604
+ });
605
+ printSuggestionResult(result);
606
+ const message = await promptForSuggestion(result.suggestions);
607
+ const shouldCommit = options.yes || !config.confirmBeforeCommit || await confirmAction(`Commit with "${message}"?`);
608
+ if (!shouldCommit) {
609
+ printCancelled();
610
+ return;
611
+ }
612
+ const branch = await getCurrentBranch(context.cwd);
613
+ await createCommit(message, context.cwd);
614
+ printCommitCreated(message, branch);
615
+ if (!options.push) {
616
+ return;
617
+ }
618
+ const shouldPush = options.yes || !config.confirmBeforePush || await confirmAction(`Push to origin/${branch}?`);
619
+ if (!shouldPush) {
620
+ printCancelled();
621
+ return;
622
+ }
623
+ await pushCurrentBranch(context.cwd);
624
+ printPushed(branch);
625
+ }
626
+
627
+ // src/core/config/load-config.ts
628
+ import { access, mkdir, readFile, writeFile } from "fs/promises";
629
+ import os from "os";
630
+ import path from "path";
631
+ import { z as z2 } from "zod";
632
+
633
+ // src/core/config/defaults.ts
634
+ var DEFAULT_CONFIG = {
635
+ provider: "groq",
636
+ model: "llama-3.3-70b-versatile",
637
+ commitConvention: "conventional",
638
+ language: "en",
639
+ maxSubjectLength: 72,
640
+ confirmBeforeCommit: true,
641
+ confirmBeforePush: true,
642
+ scopeStrategy: "auto",
643
+ onboardingComplete: false
644
+ };
645
+ var CONFIG_FILE_NAME = "patchwise.config.json";
646
+
647
+ // src/core/config/load-config.ts
648
+ var configSchema = z2.object({
649
+ provider: z2.literal("groq").optional(),
650
+ model: z2.string().min(1).optional(),
651
+ commitConvention: z2.literal("conventional").optional(),
652
+ language: z2.enum(["en", "fr"]).optional(),
653
+ maxSubjectLength: z2.number().int().positive().optional(),
654
+ confirmBeforeCommit: z2.boolean().optional(),
655
+ confirmBeforePush: z2.boolean().optional(),
656
+ scopeStrategy: z2.enum(["auto", "manual", "none"]).optional(),
657
+ groqApiKey: z2.string().min(1).optional(),
658
+ onboardingComplete: z2.boolean().optional()
659
+ });
660
+ async function loadConfig(cwd = process.cwd()) {
661
+ const fileConfig = await loadProjectConfig(cwd);
662
+ const userConfig = await loadUserConfig();
663
+ return {
664
+ ...DEFAULT_CONFIG,
665
+ ...userConfig,
666
+ ...fileConfig,
667
+ provider: readEnv("PATCHWISE_PROVIDER", DEFAULT_CONFIG.provider),
668
+ model: process.env.PATCHWISE_MODEL ?? fileConfig.model ?? userConfig.model ?? DEFAULT_CONFIG.model,
669
+ language: readEnv(
670
+ "PATCHWISE_LANGUAGE",
671
+ fileConfig.language ?? userConfig.language ?? DEFAULT_CONFIG.language
672
+ ),
673
+ groqApiKey: process.env.GROQ_API_KEY ?? fileConfig.groqApiKey ?? userConfig.groqApiKey,
674
+ onboardingComplete: fileConfig.onboardingComplete ?? userConfig.onboardingComplete ?? DEFAULT_CONFIG.onboardingComplete
675
+ };
676
+ }
677
+ async function initConfigFile(cwd = process.cwd()) {
678
+ const configPath = path.join(cwd, CONFIG_FILE_NAME);
679
+ try {
680
+ await access(configPath);
681
+ return configPath;
682
+ } catch (error) {
683
+ if (!isMissingFile(error)) {
684
+ throw error;
685
+ }
686
+ }
687
+ await writeFile(
688
+ `${configPath}`,
689
+ `${JSON.stringify(DEFAULT_CONFIG, null, 2)}
690
+ `,
691
+ "utf8"
692
+ );
693
+ return configPath;
694
+ }
695
+ async function loadUserConfig() {
696
+ const configPath = getUserConfigPath();
697
+ try {
698
+ const raw = await readFile(configPath, "utf8");
699
+ return configSchema.parse(JSON.parse(raw));
700
+ } catch (error) {
701
+ if (!isMissingFile(error)) {
702
+ throw error;
703
+ }
704
+ }
705
+ return {};
706
+ }
707
+ async function saveUserConfig(config) {
708
+ const configPath = getUserConfigPath();
709
+ await mkdir(path.dirname(configPath), { recursive: true });
710
+ await writeFile(configPath, `${JSON.stringify(config, null, 2)}
711
+ `, "utf8");
712
+ return configPath;
713
+ }
714
+ function getUserConfigPath() {
715
+ if (process.platform === "win32") {
716
+ const appData = process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming");
717
+ return path.join(appData, "patchwise", "config.json");
718
+ }
719
+ if (process.platform === "darwin") {
720
+ return path.join(
721
+ os.homedir(),
722
+ "Library",
723
+ "Application Support",
724
+ "patchwise",
725
+ "config.json"
726
+ );
727
+ }
728
+ const xdgConfigHome = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config");
729
+ return path.join(xdgConfigHome, "patchwise", "config.json");
730
+ }
731
+ async function loadProjectConfig(cwd) {
732
+ const configPath = path.join(cwd, CONFIG_FILE_NAME);
733
+ try {
734
+ const raw = await readFile(configPath, "utf8");
735
+ return configSchema.parse(JSON.parse(raw));
736
+ } catch (error) {
737
+ if (!isMissingFile(error)) {
738
+ throw error;
739
+ }
740
+ }
741
+ return {};
742
+ }
743
+ function isMissingFile(error) {
744
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
745
+ }
746
+ function readEnv(name, fallback) {
747
+ const value = process.env[name];
748
+ if (!value) {
749
+ return fallback;
750
+ }
751
+ return value;
752
+ }
753
+
754
+ // src/cli/commands/config.ts
755
+ async function runConfigInitCommand(context) {
756
+ const configPath = await initConfigFile(context.cwd);
757
+ console.log(`Config ready at ${configPath}`);
758
+ }
759
+ async function runSetupCommand(context, options) {
760
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
761
+ if (options?.silentWhenNonInteractive) {
762
+ return;
763
+ }
764
+ throw new Error(
765
+ "Interactive setup requires a TTY. Run `patchwise setup` in a terminal."
766
+ );
767
+ }
768
+ const currentConfig = await loadConfig(context.cwd);
769
+ const answers = await promptForSetup(currentConfig);
770
+ const configPath = await saveUserConfig({
771
+ provider: answers.provider,
772
+ model: answers.model,
773
+ language: answers.language,
774
+ groqApiKey: answers.groqApiKey,
775
+ onboardingComplete: true
776
+ });
777
+ context.config = await loadConfig(context.cwd);
778
+ console.log(`User config saved to ${configPath}`);
779
+ }
780
+
781
+ // src/cli/commands/stage.ts
782
+ async function runStageCommand(context) {
783
+ await assertGitRepository(context.cwd);
784
+ const files = await getModifiedFiles(context.cwd);
785
+ if (files.length === 0) {
786
+ throw new Error("No modified files found.");
787
+ }
788
+ const selected = await promptForFiles(files);
789
+ if (selected.length === 0) {
790
+ console.log("No files selected.");
791
+ return;
792
+ }
793
+ await stageFiles(selected, context.cwd);
794
+ console.log(`Staged ${selected.length} file(s).`);
795
+ }
796
+
797
+ // src/cli/commands/suggest.ts
798
+ async function runSuggestCommand(context) {
799
+ await assertGitRepository(context.cwd);
800
+ const diff = await getStagedDiff(context.cwd);
801
+ if (!diff) {
802
+ throw new Error(
803
+ "No staged changes found. Stage files before running suggest."
804
+ );
805
+ }
806
+ const result = await generateSuggestionsFromDiff(diff, context.config);
807
+ printSuggestionResult(result);
808
+ }
809
+
810
+ // src/cli/program.ts
811
+ async function createProgram(cwd = process.cwd()) {
812
+ const context = { cwd, config: await loadConfig(cwd) };
813
+ const program2 = new Command();
814
+ program2.name("patchwise").description("AI-assisted Git commits with explicit human validation.").version("0.1.0");
815
+ program2.command("suggest").description("Generate commit suggestions from staged changes.").action(async () => handleCommand(() => runSuggestCommand(context)));
816
+ program2.command("stage").description("Interactively select files to stage.").action(async () => handleCommand(() => runStageCommand(context)));
817
+ program2.command("commit").description(
818
+ "Generate suggestions and create a commit from staged changes."
819
+ ).option("--all", "Stage all changes before generating a commit.").option(
820
+ "--select",
821
+ "Select files interactively before generating a commit."
822
+ ).option("--push", "Push after creating the commit.").option("--yes", "Skip commit and push confirmations.").option("--lang <lang>", "Commit message language (en|fr).").option("--provider <provider>", "AI provider to use.").option("--model <model>", "Model name to use.").option("--scope <scope>", "Force a commit scope.").option("--no-scope", "Disable commit scope.").action(
823
+ async (options) => handleCommand(
824
+ () => runCommitCommand(context, sanitizeCommitOptions(options))
825
+ )
826
+ );
827
+ program2.command("setup").description(
828
+ "Run the one-time interactive setup and store user configuration."
829
+ ).action(async () => handleCommand(async () => runSetupCommand(context)));
830
+ const configCommand = program2.command("config").description("Manage project configuration.");
831
+ configCommand.command("init").description("Create patchwise.config.json if it does not exist.").action(async () => handleCommand(() => runConfigInitCommand(context)));
832
+ program2.hook("preAction", async (_, actionCommand) => {
833
+ if (actionCommand.name() === "setup" || actionCommand.name() === "init") {
834
+ return;
835
+ }
836
+ context.config = await loadConfig(cwd);
837
+ if (context.config.onboardingComplete && context.config.groqApiKey) {
838
+ return;
839
+ }
840
+ await runSetupCommand(context, { silentWhenNonInteractive: true });
841
+ context.config = await loadConfig(cwd);
842
+ });
843
+ return program2;
844
+ }
845
+ function sanitizeCommitOptions(options) {
846
+ return {
847
+ ...options,
848
+ lang: options.lang === "fr" ? "fr" : "en",
849
+ provider: options.provider ?? "groq"
850
+ };
851
+ }
852
+ async function handleCommand(action) {
853
+ try {
854
+ await action();
855
+ } catch (error) {
856
+ const message = error instanceof Error ? error.message : "Unknown error";
857
+ console.error(`patchwise: ${message}`);
858
+ process.exitCode = 1;
859
+ }
860
+ }
861
+
862
+ // src/bin/patchwise.ts
863
+ var program = await createProgram();
864
+ await program.parseAsync(process.argv);
865
+ //# sourceMappingURL=patchwise.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/program.ts","../src/core/ai/prompt.ts","../src/core/ai/schemas.ts","../src/core/ai/providers/groq.ts","../src/core/ai/create-provider.ts","../src/core/commit/diff.ts","../src/core/commit/format.ts","../src/cli/services.ts","../src/core/git/client.ts","../src/core/ui/output.ts","../src/core/ui/prompts.ts","../src/core/ai/models.ts","../src/cli/commands/commit.ts","../src/core/config/load-config.ts","../src/core/config/defaults.ts","../src/cli/commands/config.ts","../src/cli/commands/stage.ts","../src/cli/commands/suggest.ts","../src/bin/patchwise.ts"],"sourcesContent":["import { Command } from \"commander\";\n\nimport { runCommitCommand } from \"@/cli/commands/commit\";\nimport { runConfigInitCommand, runSetupCommand } from \"@/cli/commands/config\";\nimport { runStageCommand } from \"@/cli/commands/stage\";\nimport { runSuggestCommand } from \"@/cli/commands/suggest\";\nimport type { CommandContext } from \"@/cli/context\";\nimport { loadConfig } from \"@/core/config/load-config\";\nimport type { CommitCommandOptions, ProviderName } from \"@/types\";\n\nexport async function createProgram(cwd = process.cwd()): Promise<Command> {\n const context: CommandContext = { cwd, config: await loadConfig(cwd) };\n\n const program = new Command();\n\n program\n .name(\"patchwise\")\n .description(\"AI-assisted Git commits with explicit human validation.\")\n .version(\"0.1.0\");\n\n program\n .command(\"suggest\")\n .description(\"Generate commit suggestions from staged changes.\")\n .action(async () => handleCommand(() => runSuggestCommand(context)));\n\n program\n .command(\"stage\")\n .description(\"Interactively select files to stage.\")\n .action(async () => handleCommand(() => runStageCommand(context)));\n\n program\n .command(\"commit\")\n .description(\n \"Generate suggestions and create a commit from staged changes.\",\n )\n .option(\"--all\", \"Stage all changes before generating a commit.\")\n .option(\n \"--select\",\n \"Select files interactively before generating a commit.\",\n )\n .option(\"--push\", \"Push after creating the commit.\")\n .option(\"--yes\", \"Skip commit and push confirmations.\")\n .option(\"--lang <lang>\", \"Commit message language (en|fr).\")\n .option(\"--provider <provider>\", \"AI provider to use.\")\n .option(\"--model <model>\", \"Model name to use.\")\n .option(\"--scope <scope>\", \"Force a commit scope.\")\n .option(\"--no-scope\", \"Disable commit scope.\")\n .action(async (options: CommitCommandOptions) =>\n handleCommand(() =>\n runCommitCommand(context, sanitizeCommitOptions(options)),\n ),\n );\n\n program\n .command(\"setup\")\n .description(\n \"Run the one-time interactive setup and store user configuration.\",\n )\n .action(async () => handleCommand(async () => runSetupCommand(context)));\n\n const configCommand = program\n .command(\"config\")\n .description(\"Manage project configuration.\");\n configCommand\n .command(\"init\")\n .description(\"Create patchwise.config.json if it does not exist.\")\n .action(async () => handleCommand(() => runConfigInitCommand(context)));\n\n program.hook(\"preAction\", async (_, actionCommand) => {\n if (actionCommand.name() === \"setup\" || actionCommand.name() === \"init\") {\n return;\n }\n\n context.config = await loadConfig(cwd);\n\n if (context.config.onboardingComplete && context.config.groqApiKey) {\n return;\n }\n\n await runSetupCommand(context, { silentWhenNonInteractive: true });\n context.config = await loadConfig(cwd);\n });\n\n return program;\n}\n\nfunction sanitizeCommitOptions(\n options: CommitCommandOptions,\n): CommitCommandOptions {\n return {\n ...options,\n lang: options.lang === \"fr\" ? \"fr\" : \"en\",\n provider: (options.provider ?? \"groq\") as ProviderName,\n };\n}\n\nasync function handleCommand(action: () => Promise<void>): Promise<void> {\n try {\n await action();\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`patchwise: ${message}`);\n process.exitCode = 1;\n }\n}\n","import type { SuggestCommitInput } from \"@/types\";\n\nexport function buildPrompt(input: SuggestCommitInput): string {\n const categories = categorizeFiles(input.fileNames);\n const categoryList = Array.from(categories).join(\", \");\n\n return [\n \"You generate Conventional Commit messages from a Git diff.\",\n \"Return strict JSON only.\",\n \"The JSON shape must be:\",\n '{\"summary\":\"string\",\"suggestions\":[{\"type\":\"feat\",\"scope\":\"optional\",\"subject\":\"string\",\"body\":\"optional string\"}]}',\n \"Rules:\",\n \"- EVERY suggestion must cover ALL changes in the diff, never just a subset.\",\n \"- The body should list each change group as bullet points.\",\n `- This diff touches these areas: ${categoryList}.`,\n \"\",\n \"Suggestion 1 — Primary: the most accurate type/scope for the full change.\",\n \"Suggestion 2 — Alternative angle: use a different type or scope that also fits.\",\n \"Suggestion 3 — Broad: a higher-level framing that encompasses everything.\",\n \"\",\n \"Each suggestion must have a DISTINCT type, scope, or subject. No rephrasing.\",\n \"- Subjects must be imperative, concise, and lower-case.\",\n `- Subject max length: ${input.maxSubjectLength}.`,\n `- Language: ${input.language}.`,\n `- Scope strategy: ${input.scopeStrategy}.`,\n input.scope\n ? `- Preferred scope: ${input.scope}.`\n : \"- Preferred scope: infer only if obvious.\",\n `- Files involved: ${input.fileNames.join(\", \") || \"unknown\"}.`,\n \"Diff:\",\n input.diff,\n input.diff.includes(\"[diff truncated\")\n ? \"\\nNote: the diff was truncated to fit token limits. Use the file list and stat summary to infer the full scope.\"\n : \"\",\n ].join(\"\\n\");\n}\n\nfunction categorizeFiles(files: string[]): Set<string> {\n const categories = new Set<string>();\n\n for (const f of files) {\n if (f.startsWith(\"src/\") || f.startsWith(\"lib/\")) categories.add(\"code\");\n else if (f.startsWith(\"test/\") || f.startsWith(\"tests/\") || f.startsWith(\"spec/\")) categories.add(\"test\");\n else if (f.startsWith(\"docs/\") || f.endsWith(\".md\")) categories.add(\"docs\");\n else if (f.includes(\".config.\") || f.endsWith(\"rc\") || f.endsWith(\".json\")) categories.add(\"config\");\n else if (f.startsWith(\".github/\") || f.endsWith(\".yml\") || f.endsWith(\".yaml\")) categories.add(\"ci\");\n else categories.add(\"other\");\n }\n\n return categories;\n}\n","import { z } from \"zod\";\n\nimport type { CommitType } from \"@/types\";\n\nconst VALID_COMMIT_TYPES: CommitType[] = [\n \"feat\",\n \"fix\",\n \"refactor\",\n \"docs\",\n \"test\",\n \"chore\",\n \"perf\",\n \"build\",\n \"ci\",\n];\n\n// Accept any string for type, then coerce to the closest valid type.\n// This prevents crashes when the AI returns \"bugfix\", \"feature\", etc.\nfunction coerceCommitType(value: string): CommitType {\n if (VALID_COMMIT_TYPES.includes(value as CommitType)) {\n return value as CommitType;\n }\n\n // Common AI hallucinations → real types\n const aliasMap: Record<string, CommitType> = {\n bugfix: \"fix\",\n \"bug-fix\": \"fix\",\n bug: \"fix\",\n feature: \"feat\",\n \"new-feature\": \"feat\",\n improvement: \"refactor\",\n improve: \"refactor\",\n cleanup: \"chore\",\n maintenance: \"chore\",\n style: \"refactor\",\n formatting: \"chore\",\n config: \"chore\",\n configuration: \"chore\",\n build: \"build\",\n ci: \"ci\",\n test: \"test\",\n tests: \"test\",\n testing: \"test\",\n docs: \"docs\",\n doc: \"docs\",\n documentation: \"docs\",\n perf: \"perf\",\n performance: \"perf\",\n refactor: \"refactor\",\n feat: \"feat\",\n fix: \"fix\",\n chore: \"chore\",\n };\n\n return aliasMap[value.toLowerCase()] ?? \"feat\";\n}\n\nexport const suggestionSchema = z.object({\n type: z.string().transform(coerceCommitType),\n scope: z\n .string()\n .optional()\n .transform((v) => (v && v.trim().length > 0 ? v.trim() : undefined)),\n subject: z.string().min(1),\n body: z.string().optional(),\n});\n\nexport const providerResponseSchema = z.object({\n summary: z.string().min(1),\n suggestions: z.array(suggestionSchema).min(1).max(10),\n});\n","import { buildPrompt } from \"@/core/ai/prompt\";\nimport { providerResponseSchema } from \"@/core/ai/schemas\";\nimport type { AIProvider, SuggestCommitInput, SuggestionResult } from \"@/types\";\n\nconst GROQ_API_URL = \"https://api.groq.com/openai/v1/chat/completions\";\n\ninterface GroqResponse {\n choices?: Array<{\n message?: {\n content?: string;\n };\n }>;\n}\n\nexport class GroqAIProvider implements AIProvider {\n constructor(\n private readonly apiKey: string,\n private readonly model: string,\n ) {}\n\n async generateCommitSuggestions(\n input: SuggestCommitInput,\n ): Promise<SuggestionResult> {\n const response = await fetch(GROQ_API_URL, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({\n model: this.model,\n temperature: 0.2,\n response_format: { type: \"json_object\" },\n messages: [\n {\n role: \"system\",\n content:\n \"You are a precise commit assistant. Return valid JSON only.\",\n },\n {\n role: \"user\",\n content: buildPrompt(input),\n },\n ],\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Groq API error (${response.status}): ${body}`);\n }\n\n const payload = (await response.json()) as GroqResponse;\n const rawContent = payload.choices?.[0]?.message?.content;\n\n if (!rawContent) {\n throw new Error(\"Groq API returned an empty response.\");\n }\n\n const parsed = providerResponseSchema.parse(JSON.parse(rawContent));\n\n return parsed;\n }\n}\n","import { GroqAIProvider } from \"@/core/ai/providers/groq\";\nimport type { AIProvider, AppConfig } from \"@/types\";\n\nexport function createAIProvider(config: AppConfig): AIProvider {\n if (config.provider === \"groq\") {\n const apiKey = config.groqApiKey;\n\n if (!apiKey) {\n throw new Error(\n \"Missing Groq API key. Run `patchwise setup` to configure it.\",\n );\n }\n\n return new GroqAIProvider(apiKey, config.model);\n }\n\n throw new Error(`Unsupported provider: ${config.provider}`);\n}\n","export function truncateDiff(diff: string, maxChars = 8_000): string {\n if (diff.length <= maxChars) {\n return diff;\n }\n\n // Smart truncation: keep file headers and first N lines per file\n const files = parseDiffFiles(diff);\n\n if (files.length === 0) {\n return diff.slice(0, maxChars) + \"\\n\\n[diff truncated]\";\n }\n\n // Start with a stat summary\n const statLines = files.map((f) => `${f.path} (+${f.added}/-${f.removed})`);\n let result = `Files changed: ${files.length}\\n${statLines.join(\"\\n\")}\\n\\n`;\n\n // Add truncated diffs, limiting lines per file\n const maxLinesPerFile = Math.max(10, Math.floor((maxChars - result.length) / files.length / 6));\n\n for (const file of files) {\n const header = `--- ${file.path} ---`;\n const lines = file.diffLines.slice(0, maxLinesPerFile);\n const truncated = file.diffLines.length > maxLinesPerFile ? `\\n[... ${file.diffLines.length - maxLinesPerFile} more lines]` : \"\";\n const fileBlock = `${header}\\n${lines.join(\"\\n\")}${truncated}\\n`;\n\n if (result.length + fileBlock.length > maxChars) {\n result += \"\\n[diff truncated — remaining files omitted]\";\n break;\n }\n\n result += fileBlock;\n }\n\n return result;\n}\n\ninterface ParsedFile {\n path: string;\n diffLines: string[];\n added: number;\n removed: number;\n}\n\nfunction parseDiffFiles(diff: string): ParsedFile[] {\n const files: ParsedFile[] = [];\n const lines = diff.split(\"\\n\");\n let current: ParsedFile | null = null;\n\n for (const line of lines) {\n if (line.startsWith(\"diff --git \")) {\n if (current) files.push(current);\n const parts = line.split(\" \");\n const path = parts[2]?.replace(/^a\\//, \"\") ?? \"unknown\";\n current = { path, diffLines: [], added: 0, removed: 0 };\n } else if (current) {\n current.diffLines.push(line);\n if (line.startsWith(\"+\")) current.added++;\n else if (line.startsWith(\"-\")) current.removed++;\n }\n }\n\n if (current) files.push(current);\n return files;\n}\n\nexport function extractFileNamesFromDiff(diff: string): string[] {\n const fileNames = new Set<string>();\n\n for (const line of diff.split(\"\\n\")) {\n if (!line.startsWith(\"diff --git \")) {\n continue;\n }\n\n const parts = line.split(\" \");\n const filePath = parts[2]?.replace(/^a\\//, \"\");\n\n if (filePath) {\n fileNames.add(filePath);\n }\n }\n\n return [...fileNames];\n}\n","import type { CommitSuggestion } from \"@/types\";\n\nexport function formatCommitMessage(suggestion: CommitSuggestion): string {\n const scope = suggestion.scope ? `(${suggestion.scope})` : \"\";\n return `${suggestion.type}${scope}: ${suggestion.subject}`;\n}\n\nexport function formatCommitMessageWithBody(suggestion: CommitSuggestion): string {\n const header = formatCommitMessage(suggestion);\n if (!suggestion.body) return header;\n return `${header}\\n\\n${suggestion.body}`;\n}\n\nexport function applyScopeOverride(\n suggestion: CommitSuggestion,\n scope: string | undefined,\n disableScope: boolean | undefined,\n): CommitSuggestion {\n if (disableScope) {\n return {\n ...suggestion,\n scope: undefined,\n };\n }\n\n if (!scope) {\n return suggestion;\n }\n\n return {\n ...suggestion,\n scope,\n };\n}\n\nexport function truncateSubject(subject: string, maxLength: number): string {\n if (subject.length <= maxLength) {\n return subject;\n }\n\n return subject.slice(0, maxLength - 1).trimEnd();\n}\n","import { createAIProvider } from \"@/core/ai/create-provider\";\nimport { extractFileNamesFromDiff, truncateDiff } from \"@/core/commit/diff\";\nimport { applyScopeOverride, truncateSubject } from \"@/core/commit/format\";\nimport type { AppConfig, CommitSuggestion, SuggestionResult } from \"@/types\";\n\nexport async function generateSuggestionsFromDiff(\n diff: string,\n config: AppConfig,\n options?: {\n language?: AppConfig[\"language\"];\n scope?: string;\n noScope?: boolean;\n },\n): Promise<SuggestionResult> {\n const provider = createAIProvider(config);\n const input = {\n diff: truncateDiff(diff),\n fileNames: extractFileNamesFromDiff(diff),\n language: options?.language ?? config.language,\n scopeStrategy: options?.noScope\n ? \"none\"\n : options?.scope\n ? \"manual\"\n : config.scopeStrategy,\n scope: options?.scope,\n maxSubjectLength: config.maxSubjectLength,\n };\n\n const result = await provider.generateCommitSuggestions(input);\n\n return {\n ...result,\n suggestions: result.suggestions\n .map((suggestion) =>\n normalizeSuggestion(\n suggestion,\n config.maxSubjectLength,\n options?.scope,\n options?.noScope,\n ),\n )\n .slice(0, 3),\n };\n}\n\nfunction normalizeSuggestion(\n suggestion: CommitSuggestion,\n maxSubjectLength: number,\n scope?: string,\n noScope?: boolean,\n): CommitSuggestion {\n const withScope = applyScopeOverride(suggestion, scope, noScope);\n\n return {\n ...withScope,\n subject: truncateSubject(withScope.subject, maxSubjectLength),\n };\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\nexport interface FileStatus {\n path: string;\n indexStatus: string;\n workingTreeStatus: string;\n}\n\nexport async function assertGitRepository(cwd = process.cwd()): Promise<void> {\n try {\n await execGit([\"rev-parse\", \"--is-inside-work-tree\"], cwd);\n } catch {\n throw new Error(\"Current directory is not a Git repository.\");\n }\n}\n\nexport async function getStagedDiff(cwd = process.cwd()): Promise<string> {\n return execGit([\"diff\", \"--cached\", \"--no-ext-diff\"], cwd);\n}\n\nexport async function getModifiedFiles(\n cwd = process.cwd(),\n): Promise<FileStatus[]> {\n const output = await execGit([\"status\", \"--short\"], cwd);\n\n return output\n .split(\"\\n\")\n .filter(Boolean)\n .map((line) => ({\n indexStatus: line[0] ?? \" \",\n workingTreeStatus: line[1] ?? \" \",\n path: line.slice(3).trim(),\n }));\n}\n\nexport async function stageFiles(\n files: string[],\n cwd = process.cwd(),\n): Promise<void> {\n if (files.length === 0) {\n return;\n }\n\n await execGit([\"add\", \"--\", ...files], cwd);\n}\n\nexport async function stageAll(cwd = process.cwd()): Promise<void> {\n await execGit([\"add\", \"-A\"], cwd);\n}\n\nexport async function createCommit(\n message: string,\n cwd = process.cwd(),\n): Promise<void> {\n await execGit([\"commit\", \"-m\", message], cwd);\n}\n\nexport async function pushCurrentBranch(cwd = process.cwd()): Promise<void> {\n await execGit([\"push\"], cwd);\n}\n\nexport async function getCurrentBranch(cwd = process.cwd()): Promise<string> {\n return execGit([\"branch\", \"--show-current\"], cwd);\n}\n\nasync function execGit(args: string[], cwd: string): Promise<string> {\n try {\n const { stdout } = await execFileAsync(\"git\", args, {\n cwd,\n encoding: \"utf8\",\n maxBuffer: 1024 * 1024 * 10,\n });\n\n return stdout.trim();\n } catch (error) {\n if (error instanceof Error && \"stderr\" in error) {\n const stderr = String(error.stderr ?? \"\").trim();\n\n throw new Error(stderr || \"Git command failed.\", { cause: error });\n }\n\n throw error;\n }\n}\n","import chalk from \"chalk\";\n\nimport { formatCommitMessage } from \"@/core/commit/format\";\nimport type { CommitSuggestion, SuggestionResult } from \"@/types\";\n\nconst BORDER = \"─\";\nconst WIDTH = 56;\n\nfunction divider(label?: string): string {\n const line = chalk.dim(BORDER.repeat(WIDTH));\n if (!label) return line;\n const padded = ` ${label} `;\n const remaining = Math.max(0, WIDTH - padded.length);\n const half = Math.floor(remaining / 2);\n return (\n chalk.dim(BORDER.repeat(half)) +\n chalk.dim(padded) +\n chalk.dim(BORDER.repeat(remaining - half))\n );\n}\n\nfunction typeBadge(type: string): string {\n const colors: Record<string, typeof chalk.green> = {\n feat: chalk.cyan,\n fix: chalk.yellow,\n refactor: chalk.magenta,\n docs: chalk.blue,\n test: chalk.green,\n chore: chalk.gray,\n perf: chalk.red,\n build: chalk.yellow,\n ci: chalk.blueBright,\n };\n const color = colors[type] ?? chalk.white;\n return color(` ${type.toUpperCase()} `);\n}\n\nfunction formatSuggestionLine(\n suggestion: CommitSuggestion,\n index: number,\n): string {\n const header = formatCommitMessage(suggestion);\n const badge = typeBadge(suggestion.type);\n const number = chalk.bold(`${index + 1}.`);\n const line = ` ${number} ${badge} ${chalk.white(header)}`;\n\n if (suggestion.body) {\n const bodyLines = suggestion.body.split(\"\\n\");\n const preview = bodyLines.slice(0, 3).join(\"\\n\");\n const more = bodyLines.length > 3 ? chalk.dim(` ... +${bodyLines.length - 3} more lines`) : \"\";\n return `${line}\\n${chalk.dim(` ${preview}`)}${more ? `\\n${more}` : \"\"}`;\n }\n\n return line;\n}\n\nexport function printSuggestionResult(result: SuggestionResult): void {\n console.log();\n console.log(divider());\n console.log(` ${chalk.bold(chalk.white(\"📝 Commit Suggestions\"))}`);\n console.log(divider());\n console.log();\n console.log(` ${chalk.dim(\"Summary:\")}`);\n console.log(` ${chalk.italic(chalk.white(result.summary))}`);\n console.log();\n console.log(` ${chalk.dim(\"Suggestions:\")}`);\n console.log();\n\n result.suggestions.forEach((suggestion, index) => {\n console.log(formatSuggestionLine(suggestion, index));\n });\n\n console.log();\n console.log(divider());\n console.log();\n}\n\nexport function printSuccess(message: string): void {\n console.log(` ${chalk.green(\"✔\")} ${chalk.green(message)}`);\n}\n\nexport function printInfo(message: string): void {\n console.log(` ${chalk.blue(\"ℹ\")} ${chalk.blue(message)}`);\n}\n\nexport function printWarning(message: string): void {\n console.log(` ${chalk.yellow(\"⚠\")} ${chalk.yellow(message)}`);\n}\n\nexport function printError(message: string): void {\n console.error(` ${chalk.red(\"✖\")} ${chalk.red(message)}`);\n}\n\nexport function printCommitCreated(message: string, branch?: string): void {\n console.log();\n console.log(divider());\n console.log(` ${chalk.green(\"✔\")} ${chalk.green(\"Commit created:\")}`);\n console.log(` ${chalk.white(message)}`);\n if (branch) {\n console.log(` ${chalk.dim(`on branch ${branch}`)}`);\n }\n console.log(divider());\n console.log();\n}\n\nexport function printPushed(branch: string): void {\n console.log();\n console.log(divider());\n console.log(\n ` ${chalk.green(\"✔\")} ${chalk.green(`Pushed to origin/${branch}`)}`,\n );\n console.log(divider());\n console.log();\n}\n\nexport function printCancelled(): void {\n console.log(` ${chalk.yellow(\"↩\")} ${chalk.yellow(\"Cancelled.\")}`);\n}\n","import { checkbox, confirm, input, password, select } from \"@inquirer/prompts\";\nimport chalk from \"chalk\";\n\nimport { fetchGroqModels } from \"@/core/ai/models\";\nimport { formatCommitMessageWithBody } from \"@/core/commit/format\";\nimport type { FileStatus } from \"@/core/git/client\";\nimport type {\n AppConfig,\n CommitSuggestion,\n Language,\n ProviderName,\n} from \"@/types\";\n\nexport async function promptForFiles(files: FileStatus[]): Promise<string[]> {\n return checkbox({\n message: chalk.bold(\"Select files to stage\"),\n choices: files.map((file) => ({\n name: `${getStatusIcon(file.indexStatus, file.workingTreeStatus)} ${file.path}`,\n value: file.path,\n })),\n required: false,\n });\n}\n\nfunction getStatusIcon(index: string, working: string): string {\n if (index !== \" \") return chalk.green(`[${index}]`);\n if (working !== \" \") return chalk.yellow(`[${working}]`);\n return chalk.dim(`[·]`);\n}\n\nexport async function promptForSuggestion(\n suggestions: CommitSuggestion[],\n): Promise<string> {\n const selected = await select({\n message: chalk.bold(\"Select a commit message\"),\n choices: [\n ...suggestions.map((suggestion, index) => ({\n name: `${chalk.bold(`${index + 1}.`)} ${formatCommitMessageWithBody(suggestion)}`,\n value: formatCommitMessageWithBody(suggestion),\n })),\n {\n name: chalk.italic(\"✏️ Write a custom message\"),\n value: \"__custom__\",\n },\n ],\n });\n\n if (selected !== \"__custom__\") {\n return selected;\n }\n\n return input({\n message: chalk.bold(\"Commit message\"),\n validate(value) {\n return value.trim().length > 0 || \"Commit message cannot be empty.\";\n },\n });\n}\n\nexport async function confirmAction(\n message: string,\n defaultValue = true,\n): Promise<boolean> {\n return confirm({\n message: chalk.yellow(`? ${message}`),\n default: defaultValue,\n });\n}\n\nexport interface SetupAnswers {\n provider: ProviderName;\n model: string;\n language: Language;\n groqApiKey: string;\n}\n\nexport async function promptForSetup(\n defaults: Partial<AppConfig>,\n): Promise<SetupAnswers> {\n const provider = await select<ProviderName>({\n message: chalk.bold(\"Select your AI provider\"),\n choices: [{ name: \"Groq\", value: \"groq\" }],\n default: defaults.provider ?? \"groq\",\n });\n\n const groqApiKey = await password({\n message:\n chalk.bold(\"Groq API key\") +\n chalk.dim(\" (https://console.groq.com/keys)\"),\n mask: \"*\",\n validate(value) {\n return value.trim().length > 0 || \"API key is required.\";\n },\n });\n\n const trimmedApiKey = groqApiKey.trim();\n\n // Fetch available models from Groq\n let modelChoices: Array<{ name: string; value: string }> = [];\n\n try {\n const models = await fetchGroqModels(trimmedApiKey);\n modelChoices = models.map((m) => ({\n name: `${m.name}`,\n value: m.id,\n }));\n } catch {\n // Fallback to manual input if fetch fails\n }\n\n let model: string;\n\n if (modelChoices.length > 0) {\n const defaultModel = defaults.model ?? \"llama-3.3-70b-versatile\";\n const defaultChoice = modelChoices.find((c) => c.value === defaultModel);\n\n model = await select({\n message: chalk.bold(\"Select a Groq model\"),\n choices: modelChoices,\n default: defaultChoice?.value,\n });\n } else {\n model = await input({\n message: chalk.bold(\"Groq model\") + chalk.dim(\" (enter manually)\"),\n default: defaults.model ?? \"llama-3.3-70b-versatile\",\n validate(value) {\n return value.trim().length > 0 || \"Model is required.\";\n },\n });\n }\n\n const language = await select<Language>({\n message: chalk.bold(\"Default commit language\"),\n choices: [\n { name: \"🇬🇧 English\", value: \"en\" },\n { name: \"🇫🇷 French\", value: \"fr\" },\n ],\n default: defaults.language ?? \"en\",\n });\n\n return {\n provider,\n model: model.trim(),\n language,\n groqApiKey: trimmedApiKey,\n };\n}\n","const GROQ_MODELS_URL = \"https://api.groq.com/openai/v1/models\";\n\nexport interface GroqModel {\n id: string;\n name: string;\n active: boolean;\n}\n\nexport async function fetchGroqModels(apiKey: string): Promise<GroqModel[]> {\n const response = await fetch(GROQ_MODELS_URL, {\n headers: {\n Authorization: `Bearer ${apiKey}`,\n },\n });\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch models (${response.status}). Check your API key.`,\n );\n }\n\n const data = (await response.json()) as {\n data?: Array<{ id: string; active?: boolean }>;\n };\n\n return (data.data ?? [])\n .filter((m) => m.active !== false)\n .map((m) => ({\n id: m.id,\n name: formatModelName(m.id),\n active: m.active ?? true,\n }))\n .sort((a, b) => a.name.localeCompare(b.name));\n}\n\nfunction formatModelName(id: string): string {\n return id.replace(/-/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n","import type { CommandContext } from \"@/cli/context\";\nimport { generateSuggestionsFromDiff } from \"@/cli/services\";\nimport {\n assertGitRepository,\n createCommit,\n getCurrentBranch,\n getModifiedFiles,\n getStagedDiff,\n pushCurrentBranch,\n stageAll,\n stageFiles,\n} from \"@/core/git/client\";\nimport {\n printCancelled,\n printCommitCreated,\n printPushed,\n printSuggestionResult,\n} from \"@/core/ui/output\";\nimport {\n confirmAction,\n promptForFiles,\n promptForSuggestion,\n} from \"@/core/ui/prompts\";\nimport type { CommitCommandOptions } from \"@/types\";\n\nexport async function runCommitCommand(\n context: CommandContext,\n options: CommitCommandOptions,\n): Promise<void> {\n await assertGitRepository(context.cwd);\n\n if (options.all) {\n await stageAll(context.cwd);\n }\n\n if (options.select) {\n const files = await getModifiedFiles(context.cwd);\n\n if (files.length === 0) {\n throw new Error(\"No modified files found.\");\n }\n\n const selected = await promptForFiles(files);\n\n if (selected.length === 0) {\n throw new Error(\"No files selected for staging.\");\n }\n\n await stageFiles(selected, context.cwd);\n }\n\n const diff = await getStagedDiff(context.cwd);\n\n if (!diff) {\n throw new Error(\n \"No staged changes found. Stage files before running commit.\",\n );\n }\n\n const config = {\n ...context.config,\n language: options.lang ?? context.config.language,\n provider: options.provider ?? context.config.provider,\n model: options.model ?? context.config.model,\n };\n\n const result = await generateSuggestionsFromDiff(diff, config, {\n language: options.lang,\n scope: options.scope,\n noScope: options.noScope,\n });\n\n printSuggestionResult(result);\n\n const message = await promptForSuggestion(result.suggestions);\n const shouldCommit =\n options.yes ||\n !config.confirmBeforeCommit ||\n (await confirmAction(`Commit with \"${message}\"?`));\n\n if (!shouldCommit) {\n printCancelled();\n return;\n }\n\n const branch = await getCurrentBranch(context.cwd);\n await createCommit(message, context.cwd);\n printCommitCreated(message, branch);\n\n if (!options.push) {\n return;\n }\n\n const shouldPush =\n options.yes ||\n !config.confirmBeforePush ||\n (await confirmAction(`Push to origin/${branch}?`));\n\n if (!shouldPush) {\n printCancelled();\n return;\n }\n\n await pushCurrentBranch(context.cwd);\n printPushed(branch);\n}\n","import { access, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { z } from \"zod\";\n\nimport type { AppConfig, Language, ProviderName, ScopeStrategy } from \"@/types\";\nimport { CONFIG_FILE_NAME, DEFAULT_CONFIG } from \"@/core/config/defaults\";\n\nconst configSchema = z.object({\n provider: z.literal(\"groq\").optional(),\n model: z.string().min(1).optional(),\n commitConvention: z.literal(\"conventional\").optional(),\n language: z.enum([\"en\", \"fr\"]).optional(),\n maxSubjectLength: z.number().int().positive().optional(),\n confirmBeforeCommit: z.boolean().optional(),\n confirmBeforePush: z.boolean().optional(),\n scopeStrategy: z.enum([\"auto\", \"manual\", \"none\"]).optional(),\n groqApiKey: z.string().min(1).optional(),\n onboardingComplete: z.boolean().optional(),\n});\n\nexport async function loadConfig(cwd = process.cwd()): Promise<AppConfig> {\n const fileConfig = await loadProjectConfig(cwd);\n const userConfig = await loadUserConfig();\n\n return {\n ...DEFAULT_CONFIG,\n ...userConfig,\n ...fileConfig,\n provider: readEnv(\"PATCHWISE_PROVIDER\", DEFAULT_CONFIG.provider),\n model:\n process.env.PATCHWISE_MODEL ??\n fileConfig.model ??\n userConfig.model ??\n DEFAULT_CONFIG.model,\n language: readEnv(\n \"PATCHWISE_LANGUAGE\",\n fileConfig.language ?? userConfig.language ?? DEFAULT_CONFIG.language,\n ),\n groqApiKey:\n process.env.GROQ_API_KEY ??\n fileConfig.groqApiKey ??\n userConfig.groqApiKey,\n onboardingComplete:\n fileConfig.onboardingComplete ??\n userConfig.onboardingComplete ??\n DEFAULT_CONFIG.onboardingComplete,\n };\n}\n\nexport async function initConfigFile(cwd = process.cwd()): Promise<string> {\n const configPath = path.join(cwd, CONFIG_FILE_NAME);\n\n try {\n await access(configPath);\n return configPath;\n } catch (error) {\n if (!isMissingFile(error)) {\n throw error;\n }\n }\n\n await writeFile(\n `${configPath}`,\n `${JSON.stringify(DEFAULT_CONFIG, null, 2)}\\n`,\n \"utf8\",\n );\n\n return configPath;\n}\n\nexport async function loadUserConfig(): Promise<Partial<AppConfig>> {\n const configPath = getUserConfigPath();\n\n try {\n const raw = await readFile(configPath, \"utf8\");\n return configSchema.parse(JSON.parse(raw));\n } catch (error) {\n if (!isMissingFile(error)) {\n throw error;\n }\n }\n\n return {};\n}\n\nexport async function saveUserConfig(\n config: Partial<AppConfig>,\n): Promise<string> {\n const configPath = getUserConfigPath();\n await mkdir(path.dirname(configPath), { recursive: true });\n await writeFile(configPath, `${JSON.stringify(config, null, 2)}\\n`, \"utf8\");\n return configPath;\n}\n\nexport function getUserConfigPath(): string {\n if (process.platform === \"win32\") {\n const appData =\n process.env.APPDATA ?? path.join(os.homedir(), \"AppData\", \"Roaming\");\n return path.join(appData, \"patchwise\", \"config.json\");\n }\n\n if (process.platform === \"darwin\") {\n return path.join(\n os.homedir(),\n \"Library\",\n \"Application Support\",\n \"patchwise\",\n \"config.json\",\n );\n }\n\n const xdgConfigHome =\n process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), \".config\");\n return path.join(xdgConfigHome, \"patchwise\", \"config.json\");\n}\n\nasync function loadProjectConfig(cwd: string): Promise<Partial<AppConfig>> {\n const configPath = path.join(cwd, CONFIG_FILE_NAME);\n\n try {\n const raw = await readFile(configPath, \"utf8\");\n return configSchema.parse(JSON.parse(raw));\n } catch (error) {\n if (!isMissingFile(error)) {\n throw error;\n }\n }\n\n return {};\n}\n\nfunction isMissingFile(error: unknown): boolean {\n return error instanceof Error && \"code\" in error && error.code === \"ENOENT\";\n}\n\nfunction readEnv<T extends Language | ProviderName | ScopeStrategy>(\n name: string,\n fallback: T,\n): T {\n const value = process.env[name];\n\n if (!value) {\n return fallback;\n }\n\n return value as T;\n}\n","import type { AppConfig } from \"@/types\";\n\nexport const DEFAULT_CONFIG: AppConfig = {\n provider: \"groq\",\n model: \"llama-3.3-70b-versatile\",\n commitConvention: \"conventional\",\n language: \"en\",\n maxSubjectLength: 72,\n confirmBeforeCommit: true,\n confirmBeforePush: true,\n scopeStrategy: \"auto\",\n onboardingComplete: false,\n};\n\nexport const CONFIG_FILE_NAME = \"patchwise.config.json\";\n","import type { CommandContext } from \"@/cli/context\";\nimport {\n initConfigFile,\n loadConfig,\n saveUserConfig,\n} from \"@/core/config/load-config\";\nimport { promptForSetup } from \"@/core/ui/prompts\";\n\nexport async function runConfigInitCommand(\n context: CommandContext,\n): Promise<void> {\n const configPath = await initConfigFile(context.cwd);\n console.log(`Config ready at ${configPath}`);\n}\n\nexport async function runSetupCommand(\n context: CommandContext,\n options?: { silentWhenNonInteractive?: boolean },\n): Promise<void> {\n if (!process.stdin.isTTY || !process.stdout.isTTY) {\n if (options?.silentWhenNonInteractive) {\n return;\n }\n\n throw new Error(\n \"Interactive setup requires a TTY. Run `patchwise setup` in a terminal.\",\n );\n }\n\n const currentConfig = await loadConfig(context.cwd);\n const answers = await promptForSetup(currentConfig);\n const configPath = await saveUserConfig({\n provider: answers.provider,\n model: answers.model,\n language: answers.language,\n groqApiKey: answers.groqApiKey,\n onboardingComplete: true,\n });\n\n context.config = await loadConfig(context.cwd);\n console.log(`User config saved to ${configPath}`);\n}\n","import type { CommandContext } from \"@/cli/context\";\nimport {\n assertGitRepository,\n getModifiedFiles,\n stageFiles,\n} from \"@/core/git/client\";\nimport { promptForFiles } from \"@/core/ui/prompts\";\n\nexport async function runStageCommand(context: CommandContext): Promise<void> {\n await assertGitRepository(context.cwd);\n\n const files = await getModifiedFiles(context.cwd);\n\n if (files.length === 0) {\n throw new Error(\"No modified files found.\");\n }\n\n const selected = await promptForFiles(files);\n\n if (selected.length === 0) {\n console.log(\"No files selected.\");\n return;\n }\n\n await stageFiles(selected, context.cwd);\n console.log(`Staged ${selected.length} file(s).`);\n}\n","import type { CommandContext } from \"@/cli/context\";\nimport { generateSuggestionsFromDiff } from \"@/cli/services\";\nimport { assertGitRepository, getStagedDiff } from \"@/core/git/client\";\nimport { printSuggestionResult } from \"@/core/ui/output\";\n\nexport async function runSuggestCommand(\n context: CommandContext,\n): Promise<void> {\n await assertGitRepository(context.cwd);\n\n const diff = await getStagedDiff(context.cwd);\n\n if (!diff) {\n throw new Error(\n \"No staged changes found. Stage files before running suggest.\",\n );\n }\n\n const result = await generateSuggestionsFromDiff(diff, context.config);\n printSuggestionResult(result);\n}\n","#!/usr/bin/env node\n\nimport { createProgram } from \"@/cli/program\";\n\nconst program = await createProgram();\nawait program.parseAsync(process.argv);\n"],"mappings":"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","names":["input","input","path","input","chalk","chalk","z","z","program"]}
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "patchwise",
3
+ "version": "1.0.0",
4
+ "description": "AI-assisted Git commits, with the developer still in charge.",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=20"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "bin": {
16
+ "patchwise": "./dist/patchwise.js"
17
+ },
18
+ "keywords": [
19
+ "cli",
20
+ "git",
21
+ "commit",
22
+ "ai",
23
+ "conventional-commits"
24
+ ],
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/jiordiviera/patchwise.git"
28
+ },
29
+ "author": "",
30
+ "license": "MIT",
31
+ "dependencies": {
32
+ "@inquirer/prompts": "^8.3.2",
33
+ "chalk": "^5.6.2",
34
+ "commander": "^14.0.3",
35
+ "vite-tsconfig-paths": "^6.1.1",
36
+ "zod": "^4.3.6"
37
+ },
38
+ "devDependencies": {
39
+ "@changesets/cli": "^2.30.0",
40
+ "@eslint/js": "^10.0.1",
41
+ "@eslint/json": "^1.2.0",
42
+ "@eslint/markdown": "^8.0.1",
43
+ "@types/node": "^25.5.2",
44
+ "eslint": "^10.2.0",
45
+ "globals": "^17.4.0",
46
+ "jiti": "^2.6.1",
47
+ "prettier": "3.8.1",
48
+ "rimraf": "^6.1.3",
49
+ "tsup": "^8.5.1",
50
+ "tsx": "^4.20.6",
51
+ "typescript": "^5.9.3",
52
+ "typescript-eslint": "^8.58.0",
53
+ "vitest": "^4.1.2"
54
+ },
55
+ "scripts": {
56
+ "build": "tsup",
57
+ "check:ci": "pnpm run lint && pnpm run typecheck",
58
+ "clean": "rimraf dist",
59
+ "dev": "tsup --watch",
60
+ "check": "pnpm run format:write && eslint --fix",
61
+ "format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache",
62
+ "format:write": "prettier --write \"**/*.{ts,tsx,mdx}\" --cache",
63
+ "lint": "eslint",
64
+ "pub:beta": "pnpm build && pnpm publish --no-git-checks --access public --tag beta",
65
+ "pub:next": "pnpm build && pnpm publish --no-git-checks --access public --tag next",
66
+ "pub:release": "pnpm build && pnpm publish --access public",
67
+ "changeset": "changeset",
68
+ "version": "changeset version",
69
+ "release": "changeset publish",
70
+ "start": "node dist/index.js",
71
+ "test": "vitest run",
72
+ "test:coverage": "vitest run --coverage",
73
+ "test:watch": "vitest",
74
+ "typecheck": "tsc --noEmit"
75
+ }
76
+ }