gitpt 1.4.0 → 1.7.2

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 (116) hide show
  1. package/README.md +34 -160
  2. package/dist/commands/commit/commitFlags.js +7 -0
  3. package/dist/commands/commit/context/buildPrompt.js +16 -0
  4. package/dist/commands/commit/context/summaryPrompt.js +20 -0
  5. package/dist/commands/commit/context/systemPrompt.js +4 -1
  6. package/dist/commands/commit/context/userPrompt.js +4 -1
  7. package/dist/commands/commit/generateCommitMessage.js +16 -37
  8. package/dist/commands/commit/index.js +187 -178
  9. package/dist/commands/commit/summarizeDiff.js +193 -0
  10. package/dist/commands/config.js +24 -9
  11. package/dist/commands/hook/index.js +68 -0
  12. package/dist/commands/middleware/capabilitiesMiddleware/ghCapability.js +20 -20
  13. package/dist/commands/middleware/capabilitiesMiddleware/gitCapability.js +13 -10
  14. package/dist/commands/middleware/capabilitiesMiddleware/index.js +12 -11
  15. package/dist/commands/middleware/hasStagedChangesMiddleware.js +9 -6
  16. package/dist/commands/middleware/setupMiddleware/defaultModels.js +15 -0
  17. package/dist/commands/middleware/setupMiddleware/index.js +66 -38
  18. package/dist/commands/model.js +7 -4
  19. package/dist/commands/passthroughArgs.js +11 -0
  20. package/dist/commands/pr/context/systemPrompt.js +4 -1
  21. package/dist/commands/pr/context/userPrompt.js +4 -1
  22. package/dist/commands/pr/generatePRDetails.js +25 -31
  23. package/dist/commands/pr/getPRContext.js +37 -62
  24. package/dist/commands/pr/index.js +57 -62
  25. package/dist/commands/reset.js +26 -0
  26. package/dist/commands/review/index.js +31 -0
  27. package/dist/commands/setup.js +38 -13
  28. package/dist/config.js +63 -55
  29. package/dist/index.js +31 -62
  30. package/dist/llm/budget.js +10 -0
  31. package/dist/llm/index.js +12 -12
  32. package/dist/llm/providers/anthropic/index.js +31 -0
  33. package/dist/llm/providers/apiKey.js +41 -0
  34. package/dist/llm/providers/apple/client.js +77 -0
  35. package/dist/llm/providers/apple/index.js +69 -0
  36. package/dist/llm/providers/apple/models.js +29 -0
  37. package/dist/llm/providers/base.js +36 -0
  38. package/dist/llm/providers/local/index.js +84 -0
  39. package/dist/llm/providers/openai/index.js +19 -0
  40. package/dist/llm/providers/openaiCompatible.js +66 -0
  41. package/dist/llm/providers/openrouter/index.js +19 -0
  42. package/dist/llm/registry.js +39 -0
  43. package/dist/llm/setup/getAvailableModels.js +17 -0
  44. package/dist/llm/setup/selectModel.js +40 -0
  45. package/dist/llm/tokenCount.js +5 -0
  46. package/dist/package.js +67 -0
  47. package/dist/services/gh/createPullRequest.js +38 -58
  48. package/dist/services/gh/index.js +6 -3
  49. package/dist/services/gh/isAvailable.js +10 -8
  50. package/dist/services/git/executeGitAdd.js +11 -12
  51. package/dist/services/git/executeGitCommit.js +10 -11
  52. package/dist/services/git/getAmendChanges.js +23 -0
  53. package/dist/services/git/getChangedFiles.js +28 -61
  54. package/dist/services/git/getCommitsSinceBaseBranch.js +28 -56
  55. package/dist/services/git/getCurrentBranch.js +10 -8
  56. package/dist/services/git/getDefaultBranch.js +35 -60
  57. package/dist/services/git/getStagedChanges.js +10 -8
  58. package/dist/services/git/getStagedFiles.js +10 -9
  59. package/dist/services/git/hasStagedChanges.js +9 -8
  60. package/dist/services/git/index.js +17 -12
  61. package/dist/services/git/isAvailable.js +10 -8
  62. package/dist/services/git/isGitRepository.js +10 -8
  63. package/dist/utils/commitlint.js +97 -135
  64. package/dist/utils/formatBaseURL.js +7 -6
  65. package/dist/utils/maskApiKey.js +6 -7
  66. package/package.json +19 -5
  67. package/dist/commands/commit/context/systemPrompt.d.ts +0 -1
  68. package/dist/commands/commit/context/userPrompt.d.ts +0 -1
  69. package/dist/commands/commit/generateCommitMessage.d.ts +0 -1
  70. package/dist/commands/commit/index.d.ts +0 -7
  71. package/dist/commands/config.d.ts +0 -1
  72. package/dist/commands/middleware/capabilitiesMiddleware/ghCapability.d.ts +0 -1
  73. package/dist/commands/middleware/capabilitiesMiddleware/gitCapability.d.ts +0 -1
  74. package/dist/commands/middleware/capabilitiesMiddleware/index.d.ts +0 -3
  75. package/dist/commands/middleware/hasStagedChangesMiddleware.d.ts +0 -1
  76. package/dist/commands/middleware/setupMiddleware/getAvailableModels.d.ts +0 -4
  77. package/dist/commands/middleware/setupMiddleware/getAvailableModels.js +0 -11
  78. package/dist/commands/middleware/setupMiddleware/getOrUpdateApiKey.d.ts +0 -1
  79. package/dist/commands/middleware/setupMiddleware/getOrUpdateApiKey.js +0 -39
  80. package/dist/commands/middleware/setupMiddleware/index.d.ts +0 -4
  81. package/dist/commands/middleware/setupMiddleware/selectModel.d.ts +0 -5
  82. package/dist/commands/middleware/setupMiddleware/selectModel.js +0 -38
  83. package/dist/commands/middleware/setupMiddleware/setupLocalLLM.d.ts +0 -5
  84. package/dist/commands/middleware/setupMiddleware/setupLocalLLM.js +0 -60
  85. package/dist/commands/middleware/setupMiddleware/setupOpenRouter.d.ts +0 -2
  86. package/dist/commands/middleware/setupMiddleware/setupOpenRouter.js +0 -66
  87. package/dist/commands/middleware/setupMiddleware/types.d.ts +0 -13
  88. package/dist/commands/middleware/setupMiddleware/types.js +0 -1
  89. package/dist/commands/model.d.ts +0 -1
  90. package/dist/commands/pr/context/systemPrompt.d.ts +0 -1
  91. package/dist/commands/pr/context/userPrompt.d.ts +0 -1
  92. package/dist/commands/pr/generatePRDetails.d.ts +0 -4
  93. package/dist/commands/pr/getPRContext.d.ts +0 -1
  94. package/dist/commands/pr/index.d.ts +0 -10
  95. package/dist/commands/setup.d.ts +0 -3
  96. package/dist/config.d.ts +0 -18
  97. package/dist/index.d.ts +0 -2
  98. package/dist/llm/index.d.ts +0 -5
  99. package/dist/services/gh/createPullRequest.d.ts +0 -1
  100. package/dist/services/gh/index.d.ts +0 -4
  101. package/dist/services/gh/isAvailable.d.ts +0 -1
  102. package/dist/services/git/executeGitAdd.d.ts +0 -1
  103. package/dist/services/git/executeGitCommit.d.ts +0 -1
  104. package/dist/services/git/getChangedFiles.d.ts +0 -1
  105. package/dist/services/git/getCommitsSinceBaseBranch.d.ts +0 -1
  106. package/dist/services/git/getCurrentBranch.d.ts +0 -1
  107. package/dist/services/git/getDefaultBranch.d.ts +0 -1
  108. package/dist/services/git/getStagedChanges.d.ts +0 -1
  109. package/dist/services/git/getStagedFiles.d.ts +0 -1
  110. package/dist/services/git/hasStagedChanges.d.ts +0 -1
  111. package/dist/services/git/index.d.ts +0 -13
  112. package/dist/services/git/isAvailable.d.ts +0 -1
  113. package/dist/services/git/isGitRepository.d.ts +0 -1
  114. package/dist/utils/commitlint.d.ts +0 -25
  115. package/dist/utils/formatBaseURL.d.ts +0 -1
  116. package/dist/utils/maskApiKey.d.ts +0 -1
@@ -1,183 +1,192 @@
1
- import chalk from "chalk";
2
- import inquirer from "inquirer";
1
+ import { git } from "../../services/git/index.js";
3
2
  import { capabilitiesMiddleware } from "../middleware/capabilitiesMiddleware/index.js";
3
+ import { isDebug } from "../../config.js";
4
4
  import { setupMiddleware } from "../middleware/setupMiddleware/index.js";
5
- import { git } from "../../services/git/index.js";
6
- import { hasCommitlintConfig, validateCommitMessage, } from "../../utils/commitlint.js";
5
+ import { hasCommitlintConfig, validateCommitMessage } from "../../utils/commitlint.js";
7
6
  import { hasStagedChangesMiddleware } from "../middleware/hasStagedChangesMiddleware.js";
8
7
  import { generateCommitMessage } from "./generateCommitMessage.js";
9
- export const commitCommand = async (options) => {
10
- capabilitiesMiddleware(["git"]);
11
- await setupMiddleware();
12
- hasStagedChangesMiddleware();
13
- let commitMessage;
14
- // If message is provided, use that
15
- if (options.message) {
16
- commitMessage = options.message;
17
- }
18
- else {
19
- try {
20
- // Get staged changes
21
- const diff = git.getStagedChanges();
22
- console.log(chalk.blue("Generating commit message..."));
23
- // Check if commitlint is configured
24
- let hasCommitlint = false;
25
- try {
26
- hasCommitlint = hasCommitlintConfig();
27
- if (hasCommitlint) {
28
- console.log(chalk.blue("Commitlint configuration detected. Generating message according to rules..."));
29
- }
30
- }
31
- catch (error) {
32
- console.warn(chalk.yellow("Warning: Error detecting commitlint config, proceeding without commitlint validation."));
33
- }
34
- // Generate first commit message
35
- commitMessage = await generateCommitMessage(diff);
36
- // If commitlint is configured, try to validate and regenerate up to 3 times
37
- if (hasCommitlint) {
38
- try {
39
- console.log(chalk.blue("Validating commit message against commitlint rules..."));
40
- // Try up to 3 times to get a valid message
41
- let validMessage = false;
42
- let attempts = 0;
43
- const MAX_ATTEMPTS = 3;
44
- let validationErrors;
45
- while (!validMessage && attempts < MAX_ATTEMPTS) {
46
- const validation = await validateCommitMessage(commitMessage);
47
- if (validation.valid) {
48
- console.log(chalk.green("✓ Commit message passed validation"));
49
- validMessage = true;
50
- break;
51
- }
52
- else {
53
- attempts++;
54
- console.log(chalk.yellow(`Commit message failed validation. ${attempts < MAX_ATTEMPTS
55
- ? "Regenerating..."
56
- : "Max attempts reached."}`));
57
- if (validation.errors) {
58
- console.log(chalk.gray(validation.errors));
59
- validationErrors = validation.errors;
60
- }
61
- if (attempts < MAX_ATTEMPTS) {
62
- // Try regenerating with validation errors
63
- try {
64
- commitMessage = await generateCommitMessage(diff, validationErrors);
65
- }
66
- catch (error) {
67
- console.warn(chalk.yellow("Error regenerating message, breaking validation loop."));
68
- break;
69
- }
70
- }
71
- else {
72
- // Ask user what to do after max attempts
73
- const { action } = await inquirer.prompt([
74
- {
75
- type: "list",
76
- name: "action",
77
- message: "Failed to generate a valid commit message after 3 attempts. What would you like to do?",
78
- choices: [
79
- {
80
- name: "Proceed with invalid message",
81
- value: "proceed",
82
- },
83
- { name: "Edit message manually", value: "edit" },
84
- { name: "Abort commit", value: "abort" },
85
- ],
86
- },
87
- ]);
88
- if (action === "abort") {
89
- console.log(chalk.red("Commit aborted due to validation failures."));
90
- process.exit(1);
91
- }
92
- else if (action === "edit") {
93
- options.edit = true; // Force editor to open
94
- }
95
- else {
96
- console.log(chalk.yellow("Proceeding with invalid message..."));
97
- }
98
- break;
99
- }
100
- }
101
- }
102
- }
103
- catch (error) {
104
- // If there's an error with commitlint validation, continue without it
105
- console.warn(chalk.yellow("Warning: Error during commitlint validation, proceeding without validation."));
106
- console.warn(chalk.gray("This may be due to an ESM module cycle conflict or missing commitlint dependencies."));
107
- }
108
- }
109
- console.log(chalk.green("✓ Commit message generated"));
110
- console.log("");
111
- console.log(chalk.cyan("Generated message:"));
112
- console.log(commitMessage);
113
- console.log("");
114
- }
115
- catch (error) {
116
- console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
117
- process.exit(1);
118
- }
119
- }
120
- // If edit is true or not specified, prompt user to edit the message
121
- if (options.edit !== false) {
122
- const answer = await inquirer.prompt([
123
- {
124
- type: "editor",
125
- name: "message",
126
- message: "Edit commit message:",
127
- default: commitMessage,
128
- },
129
- ]);
130
- commitMessage = answer.message;
131
- // Validate the edited message once
132
- try {
133
- const hasCommitlint = hasCommitlintConfig();
134
- if (hasCommitlint) {
135
- console.log(chalk.blue("Validating edited commit message..."));
136
- const validation = await validateCommitMessage(commitMessage);
137
- if (!validation.valid) {
138
- console.log(chalk.yellow("Warning: Edited message still has validation issues."));
139
- if (validation.errors) {
140
- console.log(chalk.gray(validation.errors));
141
- }
142
- // Ask if user wants to proceed anyway
143
- const { proceed } = await inquirer.prompt([
144
- {
145
- type: "confirm",
146
- name: "proceed",
147
- message: "Proceed with invalid commit message?",
148
- default: false,
149
- },
150
- ]);
151
- if (!proceed) {
152
- console.log(chalk.red("Commit aborted by user."));
153
- process.exit(1);
154
- }
155
- }
156
- else {
157
- console.log(chalk.green("✓ Edited message passed validation"));
158
- }
159
- }
160
- }
161
- catch (error) {
162
- // If validation fails, just warn and continue
163
- console.warn(chalk.yellow("Could not validate edited message, proceeding anyway."));
164
- }
165
- }
166
- // Extract other git options to pass through
167
- const gitOptions = Object.keys(options)
168
- .filter((key) => !["message", "edit"].includes(key))
169
- .map((key) => {
170
- if (typeof options[key] === "boolean") {
171
- return options[key] ? `--${key}` : `--no-${key}`;
172
- }
173
- return `--${key}=${options[key]}`;
174
- });
175
- try {
176
- git.commit(commitMessage, gitOptions);
177
- console.log(chalk.green("✓ Changes committed successfully"));
178
- }
179
- catch (error) {
180
- console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
181
- process.exit(1);
182
- }
8
+ import { prepareCommitContext } from "./summarizeDiff.js";
9
+ import { isAmend, skipsStagedGuard } from "./commitFlags.js";
10
+ import { passthroughArgs } from "../passthroughArgs.js";
11
+ import chalk from "chalk";
12
+ import inquirer from "inquirer";
13
+ //#region src/commands/commit/index.ts
14
+ var commitCommand = async (options, command) => {
15
+ if (options.debug) ({}).GITPT_DEBUG = "1";
16
+ capabilitiesMiddleware(["git"]);
17
+ await setupMiddleware();
18
+ const passthrough = passthroughArgs(command);
19
+ const amend = isAmend(passthrough);
20
+ const noEdit = options.edit === false;
21
+ if (!skipsStagedGuard(passthrough)) hasStagedChangesMiddleware();
22
+ if (amend && noEdit && !options.message) {
23
+ if (options.dryRun) {
24
+ console.log(chalk.yellow("[dry-run] Would amend the last commit (keeping its message). Nothing changed."));
25
+ return;
26
+ }
27
+ try {
28
+ git.commit(null, [...passthrough, "--no-edit"]);
29
+ console.log(chalk.green("✓ Amended (kept the original message)."));
30
+ } catch (error) {
31
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
32
+ process.exit(1);
33
+ }
34
+ return;
35
+ }
36
+ let commitMessage;
37
+ let context;
38
+ if (options.message) commitMessage = options.message;
39
+ else try {
40
+ const diff = amend ? git.getAmendChanges() : git.getStagedChanges();
41
+ if (!diff.trim()) {
42
+ console.error(chalk.red("Error: nothing to summarise. Provide a message with -m \"...\"."));
43
+ process.exit(1);
44
+ }
45
+ context = await prepareCommitContext(diff);
46
+ console.log(chalk.blue("Generating commit message..."));
47
+ let hasCommitlint = false;
48
+ try {
49
+ hasCommitlint = hasCommitlintConfig();
50
+ if (hasCommitlint) console.log(chalk.blue("Commitlint configuration detected. Generating message according to rules..."));
51
+ } catch (error) {
52
+ console.warn(chalk.yellow("Warning: Error detecting commitlint config, proceeding without commitlint validation."));
53
+ }
54
+ const startedAt = Date.now();
55
+ commitMessage = await generateCommitMessage(context);
56
+ if (isDebug()) console.log(chalk.gray(`⚙ debug · ~${Math.ceil(context.length / 3)} context tokens · generated in ${Date.now() - startedAt}ms`));
57
+ if (hasCommitlint) try {
58
+ console.log(chalk.blue("Validating commit message against commitlint rules..."));
59
+ let validMessage = false;
60
+ let attempts = 0;
61
+ const MAX_ATTEMPTS = 3;
62
+ let validationErrors;
63
+ while (!validMessage && attempts < MAX_ATTEMPTS) {
64
+ const validation = await validateCommitMessage(commitMessage);
65
+ if (validation.valid) {
66
+ console.log(chalk.green(" Commit message passed validation"));
67
+ validMessage = true;
68
+ break;
69
+ } else {
70
+ attempts++;
71
+ console.log(chalk.yellow(`Commit message failed validation. ${attempts < MAX_ATTEMPTS ? "Regenerating..." : "Max attempts reached."}`));
72
+ if (validation.errors) {
73
+ console.log(chalk.gray(validation.errors));
74
+ validationErrors = validation.errors;
75
+ }
76
+ if (attempts < MAX_ATTEMPTS) try {
77
+ commitMessage = await generateCommitMessage(context, validationErrors);
78
+ } catch (error) {
79
+ console.warn(chalk.yellow("Error regenerating message, breaking validation loop."));
80
+ break;
81
+ }
82
+ else {
83
+ const { action } = await inquirer.prompt([{
84
+ type: "list",
85
+ name: "action",
86
+ message: "Failed to generate a valid commit message after 3 attempts. What would you like to do?",
87
+ choices: [
88
+ {
89
+ name: "Proceed with invalid message",
90
+ value: "proceed"
91
+ },
92
+ {
93
+ name: "Edit message manually",
94
+ value: "edit"
95
+ },
96
+ {
97
+ name: "Abort commit",
98
+ value: "abort"
99
+ }
100
+ ]
101
+ }]);
102
+ if (action === "abort") {
103
+ console.log(chalk.red("Commit aborted due to validation failures."));
104
+ process.exit(1);
105
+ } else if (action === "edit") options.edit = true;
106
+ else console.log(chalk.yellow("Proceeding with invalid message..."));
107
+ break;
108
+ }
109
+ }
110
+ }
111
+ } catch (error) {
112
+ console.warn(chalk.yellow("Warning: Error during commitlint validation, proceeding without validation."));
113
+ console.warn(chalk.gray("This may be due to an ESM module cycle conflict or missing commitlint dependencies."));
114
+ }
115
+ console.log(chalk.green(" Commit message generated"));
116
+ console.log("");
117
+ console.log(chalk.cyan("Generated message:"));
118
+ console.log(commitMessage);
119
+ console.log("");
120
+ } catch (error) {
121
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
122
+ process.exit(1);
123
+ }
124
+ if (options.dryRun) return;
125
+ if (options.edit !== false && !options.message && context !== void 0) {
126
+ let confirmed = false;
127
+ while (!confirmed) {
128
+ const { action } = await inquirer.prompt([{
129
+ type: "list",
130
+ name: "action",
131
+ message: "Use this commit message?",
132
+ choices: [
133
+ {
134
+ name: "Confirm and commit",
135
+ value: "confirm"
136
+ },
137
+ {
138
+ name: "Generate another (different wording)",
139
+ value: "another"
140
+ },
141
+ {
142
+ name: "Edit manually",
143
+ value: "edit"
144
+ },
145
+ {
146
+ name: "Cancel",
147
+ value: "cancel"
148
+ }
149
+ ]
150
+ }]);
151
+ if (action === "confirm") confirmed = true;
152
+ else if (action === "another") {
153
+ commitMessage = (await generateCommitMessage(`${context}\n\nWrite a noticeably different commit message — a fresh wording or angle, not the same as before.`)).trim();
154
+ console.log("");
155
+ console.log(chalk.cyan("Generated message:"));
156
+ console.log(commitMessage);
157
+ console.log("");
158
+ } else if (action === "edit") {
159
+ commitMessage = (await inquirer.prompt([{
160
+ type: "editor",
161
+ name: "message",
162
+ message: "Edit commit message:",
163
+ default: commitMessage
164
+ }])).message;
165
+ confirmed = true;
166
+ } else {
167
+ console.log(chalk.gray("Commit cancelled."));
168
+ process.exit(0);
169
+ }
170
+ }
171
+ try {
172
+ if (hasCommitlintConfig()) {
173
+ const validation = await validateCommitMessage(commitMessage);
174
+ if (!validation.valid) {
175
+ console.log(chalk.yellow("Warning: Edited message still has validation issues."));
176
+ if (validation.errors) console.log(chalk.gray(validation.errors));
177
+ }
178
+ }
179
+ } catch (error) {
180
+ console.warn(chalk.yellow("Could not validate edited message, proceeding anyway."));
181
+ }
182
+ }
183
+ try {
184
+ git.commit(commitMessage, passthrough);
185
+ console.log(chalk.green("✓ Changes committed successfully"));
186
+ } catch (error) {
187
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
188
+ process.exit(1);
189
+ }
183
190
  };
191
+ //#endregion
192
+ export { commitCommand };
@@ -0,0 +1,193 @@
1
+ import { getProvider } from "../../llm/registry.js";
2
+ import { systemPrompt } from "./context/systemPrompt.js";
3
+ import { userPrompt } from "./context/userPrompt.js";
4
+ import { countTokens } from "../../llm/tokenCount.js";
5
+ import { fitBudget } from "../../llm/budget.js";
6
+ import { summarySystemPrompt, summaryUserPrompt } from "./context/summaryPrompt.js";
7
+ import ora from "ora";
8
+ //#region src/commands/commit/summarizeDiff.ts
9
+ var MAX_REDUCE_PASSES = 3;
10
+ var LOW_SIGNAL_PATTERNS = [
11
+ {
12
+ test: /(^|\/)package-lock\.json$/,
13
+ note: "dependency lockfile updated"
14
+ },
15
+ {
16
+ test: /(^|\/)npm-shrinkwrap\.json$/,
17
+ note: "dependency lockfile updated"
18
+ },
19
+ {
20
+ test: /(^|\/)yarn\.lock$/,
21
+ note: "dependency lockfile updated"
22
+ },
23
+ {
24
+ test: /(^|\/)pnpm-lock\.yaml$/,
25
+ note: "dependency lockfile updated"
26
+ },
27
+ {
28
+ test: /\.lock$/,
29
+ note: "dependency lockfile updated"
30
+ },
31
+ {
32
+ test: /(^|\/)(dist|build|out|coverage)\//,
33
+ note: "generated output updated"
34
+ },
35
+ {
36
+ test: /\.min\.(js|css)$/,
37
+ note: "minified asset updated"
38
+ },
39
+ {
40
+ test: /\.(snap)$/,
41
+ note: "test snapshot updated"
42
+ },
43
+ {
44
+ test: /(^|\/)snapshots?\//,
45
+ note: "test snapshot updated"
46
+ },
47
+ {
48
+ test: /\.(patch|diff)$/,
49
+ note: "patch file updated"
50
+ }
51
+ ];
52
+ var lowSignalNote = (file) => LOW_SIGNAL_PATTERNS.find((p) => p.test.test(file))?.note ?? null;
53
+ var finalPromptTokens = (context) => countTokens(`${systemPrompt}\n\n${userPrompt(context)}`);
54
+ var splitIntoFileBlocks = (diff) => diff.split(/(?=^diff --git )/m).filter((part) => part.trim().length > 0);
55
+ var fileNameOf = (block) => {
56
+ const match = block.match(/^diff --git a\/(.+?) b\//m);
57
+ return match ? match[1] : "changes";
58
+ };
59
+ var truncateToBudget = (file, content, budget) => {
60
+ if (countTokens(content) <= budget) return content;
61
+ const marker = `\n... [truncated ${file} to fit context] ...\n`;
62
+ let keep = content.length;
63
+ let truncated = content;
64
+ while (countTokens(truncated) > budget && keep > 200) {
65
+ keep = Math.floor(keep * .7);
66
+ truncated = content.slice(0, keep) + marker;
67
+ }
68
+ return truncated;
69
+ };
70
+ var splitBlockByHunks = (file, content, budget) => {
71
+ const firstHunk = content.search(/^@@ /m);
72
+ if (firstHunk < 0) return [{
73
+ files: [file],
74
+ content: truncateToBudget(file, content, budget)
75
+ }];
76
+ const header = content.slice(0, firstHunk);
77
+ const hunks = content.slice(firstHunk).split(/(?=^@@ )/m).filter((h) => h.length > 0);
78
+ const chunks = [];
79
+ let current = "";
80
+ const flush = () => {
81
+ if (current) {
82
+ chunks.push({
83
+ files: [file],
84
+ content: truncateToBudget(file, header + current, budget)
85
+ });
86
+ current = "";
87
+ }
88
+ };
89
+ for (const hunk of hunks) {
90
+ if (current && countTokens(header + current + hunk) > budget) flush();
91
+ current += hunk;
92
+ }
93
+ flush();
94
+ return chunks;
95
+ };
96
+ var packChunks = (blocks, budget) => {
97
+ const chunks = [];
98
+ let files = [];
99
+ let content = "";
100
+ let tokens = 0;
101
+ const flush = () => {
102
+ if (content) {
103
+ chunks.push({
104
+ files,
105
+ content
106
+ });
107
+ files = [];
108
+ content = "";
109
+ tokens = 0;
110
+ }
111
+ };
112
+ for (const block of blocks) {
113
+ if (block.tokens > budget) {
114
+ flush();
115
+ chunks.push(...splitBlockByHunks(block.file, block.content, budget));
116
+ continue;
117
+ }
118
+ if (content && tokens + block.tokens > budget) flush();
119
+ files.push(block.file);
120
+ content += block.content;
121
+ tokens += block.tokens;
122
+ }
123
+ flush();
124
+ return chunks;
125
+ };
126
+ var summarizeChunk = async (content) => {
127
+ const provider = getProvider();
128
+ return (await provider.complete({
129
+ system: summarySystemPrompt,
130
+ user: summaryUserPrompt(content),
131
+ maxTokens: provider.maxOutputTokens
132
+ })).trim();
133
+ };
134
+ var prepareCommitContext = async (diff) => {
135
+ const reserved = getProvider().maxOutputTokens;
136
+ const window = await getProvider().getContextWindow();
137
+ if (!Number.isFinite(window)) return diff;
138
+ const promptBudget = fitBudget(window, reserved);
139
+ if (finalPromptTokens(diff) <= promptBudget) return diff;
140
+ const spinner = ora({ text: "Analyzing diff size..." }).start();
141
+ try {
142
+ const blocks = splitIntoFileBlocks(diff).map((content) => ({
143
+ file: fileNameOf(content),
144
+ content,
145
+ tokens: countTokens(content)
146
+ }));
147
+ const totalTokens = blocks.reduce((sum, b) => sum + b.tokens, 0);
148
+ const sourceBlocks = blocks.filter((b) => !lowSignalNote(b.file));
149
+ const lowSignalLines = blocks.map((b) => {
150
+ const note = lowSignalNote(b.file);
151
+ return note ? `${b.file}: ${note}` : null;
152
+ }).filter((line) => line !== null);
153
+ const chunkBudget = fitBudget(window, reserved + countTokens(`${summarySystemPrompt}\n\n${summaryUserPrompt("")}`));
154
+ const chunks = packChunks(sourceBlocks, chunkBudget);
155
+ const condensedNote = lowSignalLines.length ? `, ${lowSignalLines.length} generated file(s) condensed` : "";
156
+ spinner.text = `Diff is large (~${totalTokens} tokens). Summarizing ${sourceBlocks.length} source file(s) in ${chunks.length} chunk(s)${condensedNote}...`;
157
+ const summaries = [];
158
+ for (let i = 0; i < chunks.length; i++) {
159
+ const chunk = chunks[i];
160
+ spinner.text = `Summarizing to fit the context window (~${window} tokens) — chunk ${i + 1}/${chunks.length}...`;
161
+ summaries.push(await summarizeChunk(chunk.content));
162
+ }
163
+ const header = `The lines below are per-file summaries of a single commit. Treat them as one related change and write a commit message describing the overall change (not just one file):`;
164
+ let combined = [...summaries.filter(Boolean), ...lowSignalLines].join("\n");
165
+ const framed = () => `${header}\n${combined}`;
166
+ let pass = 0;
167
+ while (finalPromptTokens(framed()) > promptBudget && pass < MAX_REDUCE_PASSES) {
168
+ pass++;
169
+ const reduceChunks = packChunks(combined.split("\n").filter((line) => line.trim().length > 0).map((line) => ({
170
+ file: "summary",
171
+ content: `${line}\n`,
172
+ tokens: countTokens(line)
173
+ })), chunkBudget);
174
+ const reduced = [];
175
+ for (let i = 0; i < reduceChunks.length; i++) {
176
+ spinner.text = `Condensing summary (pass ${pass}, ${i + 1}/${reduceChunks.length})...`;
177
+ reduced.push(await summarizeChunk(reduceChunks[i].content));
178
+ }
179
+ combined = reduced.filter(Boolean).join("\n");
180
+ }
181
+ if (finalPromptTokens(framed()) > promptBudget) {
182
+ const headerTokens = countTokens(`${systemPrompt}\n\n${userPrompt(`${header}\n`)}`);
183
+ combined = truncateToBudget("summary", combined, promptBudget - headerTokens);
184
+ }
185
+ spinner.succeed(`Summarized ${blocks.length} file(s) to fit the context window: ~${totalTokens} → ~${countTokens(framed())} tokens`);
186
+ return framed();
187
+ } catch (error) {
188
+ spinner.fail("Failed to summarize diff");
189
+ throw error;
190
+ }
191
+ };
192
+ //#endregion
193
+ export { prepareCommitContext };
@@ -1,11 +1,26 @@
1
- import chalk from "chalk";
2
- import { getConfig } from "../config.js";
1
+ import { getConfig, isDebug } from "../config.js";
3
2
  import { maskApiKey } from "../utils/maskApiKey.js";
4
- export const configCommand = async () => {
5
- console.log(chalk.blue("GitPT Configuration"));
6
- const { apiKey, ...config } = getConfig();
7
- console.log({
8
- apiKey: apiKey ? maskApiKey(apiKey) : undefined,
9
- ...config,
10
- });
3
+ import chalk from "chalk";
4
+ //#region src/commands/config.ts
5
+ var DEBUG_ONLY = /* @__PURE__ */ new Set(["debug"]);
6
+ var configCommand = async () => {
7
+ console.log(chalk.blue("GitPT Configuration"));
8
+ const { apiKey, ...config } = getConfig();
9
+ const entries = {
10
+ apiKey: apiKey ? maskApiKey(apiKey) : void 0,
11
+ ...config
12
+ };
13
+ const debug = isDebug();
14
+ const labelWidth = Math.max(...Object.keys(entries).map((key) => key.length));
15
+ for (const [key, value] of Object.entries(entries)) {
16
+ if (!debug && DEBUG_ONLY.has(key)) continue;
17
+ let display;
18
+ if (key === "apiKeys" && value && typeof value === "object") {
19
+ const masked = Object.entries(value).map(([provider, k]) => `${provider}: ${maskApiKey(k)}`);
20
+ display = masked.length ? masked.join(", ") : chalk.gray("—");
21
+ } else display = value === void 0 || value === "" ? chalk.gray("—") : String(value);
22
+ console.log(` ${chalk.cyan(key.padEnd(labelWidth))} ${display}`);
23
+ }
11
24
  };
25
+ //#endregion
26
+ export { configCommand };