@johnowennixon/diffdash 1.8.0 → 1.10.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 +8 -2
- package/dist/package.json +20 -18
- package/dist/src/lib_char_digit.js +1 -0
- package/dist/src/lib_cli.js +1 -57
- package/dist/src/lib_debug.js +1 -0
- package/dist/src/lib_diffdash_cli.js +3 -2
- package/dist/src/lib_diffdash_config.js +4 -3
- package/dist/src/lib_diffdash_llm.js +1 -0
- package/dist/src/lib_diffdash_sequence.js +47 -17
- package/dist/src/lib_git_message_generate.js +17 -15
- package/dist/src/lib_git_message_prompt.js +7 -3
- package/dist/src/lib_git_simple_staging.js +11 -6
- package/dist/src/lib_llm_chat.js +14 -9
- package/dist/src/lib_llm_config.js +8 -1
- package/dist/src/lib_llm_list.js +1 -1
- package/dist/src/lib_llm_model.js +196 -45
- package/dist/src/lib_llm_results.js +7 -4
- package/dist/src/lib_llm_tokens.js +6 -3
- package/dist/src/lib_parse_number.js +2 -2
- package/dist/src/lib_secret_check.js +109 -0
- package/dist/src/lib_text.js +39 -0
- package/dist/src/lib_tui_confirm.js +25 -0
- package/dist/src/lib_tui_none.js +4 -7
- package/dist/src/lib_tui_number.js +2 -2
- package/dist/src/lib_tui_table.js +2 -2
- package/package.json +20 -18
- package/dist/src/lib_tui_readline.js +0 -16
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# DiffDash
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
3
6
|
[](https://deepwiki.com/johnowennixon/diffdash)
|
|
4
7
|
|
|
5
8
|
A command-line tool to generate Git commit messages using AI.
|
|
@@ -126,14 +129,15 @@ All command-line arguments are optional.
|
|
|
126
129
|
| `--disable-preview` | disable previewing the generated message|
|
|
127
130
|
| `--disable-commit` | disable committing changes - exit after generating the message |
|
|
128
131
|
| `--disable-push` | disable pushing changes - exit after making the commit |
|
|
129
|
-
| `--push-no-verify` | bypass git hooks when pushing to Git |
|
|
130
|
-
| `--push-force` | apply force when pushing to Git |
|
|
131
132
|
| `--add-prefix PREFIX` | add a prefix to the commit message summary line |
|
|
132
133
|
| `--add-suffix SUFFIX` | add a suffix to the commit message summary line |
|
|
133
134
|
| `--llm-list` | display a list of available Large Language Models and exit |
|
|
134
135
|
| `--llm-compare` | compare the generated messages from all models - but do not commit |
|
|
135
136
|
| `--llm-model MODEL` | choose the LLM model by name (the default is normally best) |
|
|
136
137
|
| `--llm-excludes MODELS` | models to exclude from comparison (comma separated) |
|
|
138
|
+
| `--no-secret-check` | bypass checking for secrets in diffs |
|
|
139
|
+
| `--no-verify` | bypass git hooks when committing or pushing to Git |
|
|
140
|
+
| `--force` | apply force when pushing to Git |
|
|
137
141
|
| `--just-output` | just output the commit message for use in scripts |
|
|
138
142
|
| `--silent` | suppress all normal output - errors and aborts still display |
|
|
139
143
|
| `--debug-llm-prompts` | show prompts sent to the LLM |
|
|
@@ -150,6 +154,8 @@ Files containing secrets should not be in Git. But if they are, you can add an e
|
|
|
150
154
|
.env -diff
|
|
151
155
|
```
|
|
152
156
|
|
|
157
|
+
There is a rudimentary check for secrets in diffs before submitting to the LLM. If any are found, there is an interactive option to ignore. If you want to bypass this check, you can use the `--no-secret-check` flag.
|
|
158
|
+
|
|
153
159
|
## Development
|
|
154
160
|
|
|
155
161
|
To install on your laptop:
|
package/dist/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johnowennixon/diffdash",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "A command-line tool to generate Git commit messages using AI",
|
|
5
5
|
"license": "0BSD",
|
|
6
6
|
"author": "John Owen Nixon",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/johnowennixon/diffdash.git"
|
|
9
|
+
"url": "git+https://github.com/johnowennixon/diffdash.git"
|
|
10
10
|
},
|
|
11
11
|
"engines": {
|
|
12
12
|
"node": ">=20"
|
|
@@ -42,32 +42,34 @@
|
|
|
42
42
|
"test": "run-s -ls lint build"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@ai-sdk/anthropic": "2.0.
|
|
46
|
-
"@ai-sdk/deepseek": "1.0.
|
|
47
|
-
"@ai-sdk/google": "2.0.
|
|
48
|
-
"@ai-sdk/openai": "2.0.
|
|
49
|
-
"@
|
|
50
|
-
"ai": "
|
|
51
|
-
"
|
|
45
|
+
"@ai-sdk/anthropic": "2.0.23",
|
|
46
|
+
"@ai-sdk/deepseek": "1.0.20",
|
|
47
|
+
"@ai-sdk/google": "2.0.17",
|
|
48
|
+
"@ai-sdk/openai": "2.0.42",
|
|
49
|
+
"@inquirer/prompts": "7.8.6",
|
|
50
|
+
"@openrouter/ai-sdk-provider": "1.2.0",
|
|
51
|
+
"ai": "5.0.60",
|
|
52
|
+
"ansis": "4.2.0",
|
|
52
53
|
"argparse": "2.0.1",
|
|
53
54
|
"cli-table3": "0.6.5",
|
|
54
55
|
"json5": "2.2.3",
|
|
56
|
+
"magic-regexp": "0.10.0",
|
|
55
57
|
"simple-git": "3.28.0",
|
|
56
|
-
"zod": "4.
|
|
58
|
+
"zod": "4.1.11"
|
|
57
59
|
},
|
|
58
60
|
"devDependencies": {
|
|
59
|
-
"@biomejs/biome": "2.
|
|
60
|
-
"@candide/tsgolint": "1.
|
|
61
|
+
"@biomejs/biome": "2.2.5",
|
|
62
|
+
"@candide/tsgolint": "1.4.0",
|
|
61
63
|
"@johnowennixon/add-shebangs": "1.1.0",
|
|
62
|
-
"@johnowennixon/chmodx": "2.
|
|
64
|
+
"@johnowennixon/chmodx": "2.1.0",
|
|
63
65
|
"@types/argparse": "2.0.17",
|
|
64
|
-
"@types/node": "24.2
|
|
65
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
66
|
-
"knip": "5.
|
|
66
|
+
"@types/node": "24.5.2",
|
|
67
|
+
"@typescript/native-preview": "7.0.0-dev.20250925.1",
|
|
68
|
+
"knip": "5.63.1",
|
|
67
69
|
"markdownlint-cli2": "0.18.1",
|
|
68
70
|
"npm-run-all2": "8.0.4",
|
|
69
|
-
"oxlint": "1.
|
|
71
|
+
"oxlint": "1.19.0",
|
|
70
72
|
"rimraf": "6.0.1",
|
|
71
|
-
"typescript": "5.9.
|
|
73
|
+
"typescript": "5.9.3"
|
|
72
74
|
}
|
|
73
75
|
}
|
package/dist/src/lib_cli.js
CHANGED
|
@@ -17,12 +17,6 @@ export function cli_integer_always(options = {}) {
|
|
|
17
17
|
export function cli_boolean(options = {}) {
|
|
18
18
|
return { kind: "boolean", options, value: false };
|
|
19
19
|
}
|
|
20
|
-
export function cli_command_sync(command_sync, options = {}) {
|
|
21
|
-
return { kind: "boolean", options, value: false, command_sync };
|
|
22
|
-
}
|
|
23
|
-
export function cli_command_async(command_async, options = {}) {
|
|
24
|
-
return { kind: "boolean", options, value: false, command_async };
|
|
25
|
-
}
|
|
26
20
|
export function cli_choice_optional(options = {}) {
|
|
27
21
|
return { kind: "choice", options, value: undefined };
|
|
28
22
|
}
|
|
@@ -126,50 +120,6 @@ function cli_recursive_parse({ schema, namespace, predicate, }) {
|
|
|
126
120
|
}
|
|
127
121
|
return result;
|
|
128
122
|
}
|
|
129
|
-
function cli_recursive_despatch_sync({ schema, namespace, parsed_args, }) {
|
|
130
|
-
for (const key in schema) {
|
|
131
|
-
if (!Object.hasOwn(schema, key)) {
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
const cli = schema[key];
|
|
135
|
-
if (!cli) {
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
if (cli.kind === "meg") {
|
|
139
|
-
const nested_schema = cli.value;
|
|
140
|
-
cli_recursive_despatch_sync({ schema: nested_schema, namespace, parsed_args });
|
|
141
|
-
}
|
|
142
|
-
else if (cli.kind === "boolean") {
|
|
143
|
-
if (namespace[key]) {
|
|
144
|
-
if (cli.command_sync) {
|
|
145
|
-
cli.command_sync(parsed_args);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
async function cli_recursive_despatch_async({ schema, namespace, parsed_args, }) {
|
|
152
|
-
for (const key in schema) {
|
|
153
|
-
if (!Object.hasOwn(schema, key)) {
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
const cli = schema[key];
|
|
157
|
-
if (!cli) {
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
if (cli.kind === "meg") {
|
|
161
|
-
const nested_schema = cli.value;
|
|
162
|
-
await cli_recursive_despatch_async({ schema: nested_schema, namespace, parsed_args });
|
|
163
|
-
}
|
|
164
|
-
else if (cli.kind === "boolean") {
|
|
165
|
-
if (namespace[key]) {
|
|
166
|
-
if (cli.command_async) {
|
|
167
|
-
await cli.command_async(parsed_args);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
123
|
export function cli_make_parser({ cli_schema, description, }) {
|
|
174
124
|
const argument_parser_options = { description, allow_abbrev: false };
|
|
175
125
|
const parser = new ArgumentParser(argument_parser_options);
|
|
@@ -177,11 +127,5 @@ export function cli_make_parser({ cli_schema, description, }) {
|
|
|
177
127
|
const namespace = parser.parse_args();
|
|
178
128
|
debug_inspect_when(debug_channels.cli, namespace, "namespace");
|
|
179
129
|
const parsed_args = cli_recursive_parse({ schema: cli_schema, namespace });
|
|
180
|
-
|
|
181
|
-
cli_recursive_despatch_sync({ schema: cli_schema, namespace, parsed_args });
|
|
182
|
-
}
|
|
183
|
-
async function despatch_async() {
|
|
184
|
-
await cli_recursive_despatch_async({ schema: cli_schema, namespace, parsed_args });
|
|
185
|
-
}
|
|
186
|
-
return { parsed_args, despatch_sync, despatch_async };
|
|
130
|
+
return { parsed_args };
|
|
187
131
|
}
|
package/dist/src/lib_debug.js
CHANGED
|
@@ -10,8 +10,6 @@ const diffdash_cli_schema = {
|
|
|
10
10
|
disable_preview: cli_boolean({ help: "disable previewing the generated message" }),
|
|
11
11
|
disable_commit: cli_boolean({ help: "disable committing changes - exit after generating the message" }),
|
|
12
12
|
disable_push: cli_boolean({ help: "disable pushing changes - exit after making the commit" }),
|
|
13
|
-
push_no_verify: cli_boolean({ help: "bypass git hooks when pushing to Git" }),
|
|
14
|
-
push_force: cli_boolean({ help: "apply force when pushing to Git" }),
|
|
15
13
|
add_prefix: cli_string({ help: "add a prefix to the commit message summary line", metavar: "PREFIX" }),
|
|
16
14
|
add_suffix: cli_string({ help: "add a suffix to the commit message summary line", metavar: "SUFFIX" }),
|
|
17
15
|
llm_list: cli_boolean({ help: "display a list of available Large Language Models and exit" }),
|
|
@@ -22,6 +20,9 @@ const diffdash_cli_schema = {
|
|
|
22
20
|
default: diffdash_llm_model_default,
|
|
23
21
|
}),
|
|
24
22
|
llm_excludes: cli_string({ help: "models to exclude from comparison (comma separated)", metavar: "MODELS" }),
|
|
23
|
+
no_secret_check: cli_boolean({ help: "bypass checking for secrets in diffs" }),
|
|
24
|
+
no_verify: cli_boolean({ help: "bypass git hooks when committing or pushing to Git" }),
|
|
25
|
+
force: cli_boolean({ help: "apply force when pushing to Git" }),
|
|
25
26
|
just_output: cli_boolean({ help: "just output the commit message for use in scripts" }),
|
|
26
27
|
silent: cli_boolean({ help: "suppress all normal output - errors and aborts still display" }),
|
|
27
28
|
debug_llm_prompts: cli_boolean({ help: "debug prompts sent to the LLM" }),
|
|
@@ -33,7 +33,7 @@ function diffdash_config_file_read(config) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
export function diffdash_config_get() {
|
|
36
|
-
const { version, auto_add, auto_commit, auto_push, disable_add, disable_commit, disable_preview, disable_status, disable_push,
|
|
36
|
+
const { version, auto_add, auto_commit, auto_push, disable_add, disable_commit, disable_preview, disable_status, disable_push, add_prefix, add_suffix, llm_list, llm_compare, llm_model, llm_excludes, no_secret_check, no_verify, force, just_output, silent, debug_llm_prompts, debug_llm_inputs, debug_llm_outputs, } = diffdash_cli_parsed_args;
|
|
37
37
|
if (version) {
|
|
38
38
|
tell_plain(`${PACKAGE_NAME} v${PACKAGE_VERSION}`);
|
|
39
39
|
process.exit(0);
|
|
@@ -56,10 +56,11 @@ export function diffdash_config_get() {
|
|
|
56
56
|
disable_preview,
|
|
57
57
|
disable_status,
|
|
58
58
|
disable_push,
|
|
59
|
-
push_no_verify,
|
|
60
|
-
push_force,
|
|
61
59
|
add_prefix,
|
|
62
60
|
add_suffix,
|
|
61
|
+
no_secret_check,
|
|
62
|
+
no_verify,
|
|
63
|
+
force,
|
|
63
64
|
llm_compare,
|
|
64
65
|
llm_config,
|
|
65
66
|
all_llm_configs,
|
|
@@ -12,6 +12,7 @@ const model_name_options = [
|
|
|
12
12
|
"gpt-5-mini-minimal", // fallback
|
|
13
13
|
"gpt-5-nano",
|
|
14
14
|
"gpt-5-nano-minimal",
|
|
15
|
+
"grok-code-fast-1",
|
|
15
16
|
"llama-4-maverick@cerebras",
|
|
16
17
|
];
|
|
17
18
|
export const diffdash_llm_model_details = llm_model_get_details({ llm_model_names: model_name_options });
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { abort_with_error, abort_with_warning } from "./lib_abort.js";
|
|
2
|
+
import { ansi_blue } from "./lib_ansi.js";
|
|
2
3
|
import { debug_channels, debug_inspect } from "./lib_debug.js";
|
|
3
4
|
import { diffdash_add_footer, diffdash_add_prefix_or_suffix } from "./lib_diffdash_add.js";
|
|
4
|
-
import { error_get_text } from "./lib_error.js";
|
|
5
|
+
import { error_get_message, error_get_text } from "./lib_error.js";
|
|
5
6
|
import { git_message_display } from "./lib_git_message_display.js";
|
|
6
7
|
import { git_message_generate_result } from "./lib_git_message_generate.js";
|
|
7
8
|
import { git_message_validate_check, git_message_validate_get_result } from "./lib_git_message_validate.js";
|
|
8
9
|
import { git_simple_open_check_not_bare, git_simple_open_git_repo } from "./lib_git_simple_open.js";
|
|
9
10
|
import { git_simple_staging_create_commit, git_simple_staging_get_staged_diff, git_simple_staging_get_staged_diffstat, git_simple_staging_has_staged_changes, git_simple_staging_has_unstaged_changes, git_simple_staging_push_to_remote, git_simple_staging_stage_all_changes, } from "./lib_git_simple_staging.js";
|
|
10
11
|
import { llm_results_summary } from "./lib_llm_results.js";
|
|
12
|
+
import { secret_check } from "./lib_secret_check.js";
|
|
11
13
|
import { stdio_write_stdout, stdio_write_stdout_linefeed } from "./lib_stdio_write.js";
|
|
12
14
|
import { tell_action, tell_info, tell_plain, tell_success, tell_warning } from "./lib_tell.js";
|
|
15
|
+
import { tui_confirm } from "./lib_tui_confirm.js";
|
|
13
16
|
import { tui_justify_left } from "./lib_tui_justify.js";
|
|
14
|
-
import { tui_readline_confirm } from "./lib_tui_readline.js";
|
|
15
17
|
async function phase_open() {
|
|
16
18
|
const git = await git_simple_open_git_repo();
|
|
17
19
|
await git_simple_open_check_not_bare(git);
|
|
@@ -40,7 +42,11 @@ async function phase_add({ config, git }) {
|
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
else {
|
|
43
|
-
const add_confirmed = await
|
|
45
|
+
const add_confirmed = await tui_confirm({
|
|
46
|
+
question: "No staged changes found - would you like to add all changes?",
|
|
47
|
+
default: true,
|
|
48
|
+
style_message: ansi_blue,
|
|
49
|
+
});
|
|
44
50
|
if (!add_confirmed) {
|
|
45
51
|
abort_with_warning("Please add changes before creating a commit");
|
|
46
52
|
}
|
|
@@ -87,13 +93,21 @@ async function phase_status({ config, git }) {
|
|
|
87
93
|
}
|
|
88
94
|
async function phase_compare({ config, git }) {
|
|
89
95
|
const { silent } = config;
|
|
90
|
-
|
|
91
|
-
tell_action("Generating Git commit messages using all models in parallel");
|
|
92
|
-
}
|
|
93
|
-
const { all_llm_configs, add_prefix, add_suffix, extra_prompts } = config;
|
|
96
|
+
const { all_llm_configs, add_prefix, add_suffix, no_secret_check, extra_prompts } = config;
|
|
94
97
|
const diffstat = await git_simple_staging_get_staged_diffstat(git);
|
|
95
98
|
const diff = await git_simple_staging_get_staged_diff(git);
|
|
99
|
+
if (!no_secret_check) {
|
|
100
|
+
try {
|
|
101
|
+
await secret_check({ text: diff, interactive: true });
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
abort_with_error(`Aborting: ${error_get_message(error)}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
96
107
|
const inputs = { diffstat, diff, extra_prompts };
|
|
108
|
+
if (!silent) {
|
|
109
|
+
tell_action("Generating Git commit messages using all models in parallel");
|
|
110
|
+
}
|
|
97
111
|
const result_promises = all_llm_configs.map((llm_config) => git_message_generate_result({ llm_config, inputs }));
|
|
98
112
|
const all_results = await Promise.all(result_promises);
|
|
99
113
|
for (const result of all_results) {
|
|
@@ -115,14 +129,22 @@ async function phase_compare({ config, git }) {
|
|
|
115
129
|
llm_results_summary(all_results);
|
|
116
130
|
}
|
|
117
131
|
async function phase_generate({ config, git }) {
|
|
118
|
-
const { disable_preview, add_prefix, add_suffix, llm_config, just_output, silent, extra_prompts } = config;
|
|
132
|
+
const { disable_preview, add_prefix, add_suffix, llm_config, no_secret_check, just_output, silent, extra_prompts } = config;
|
|
119
133
|
const { llm_model_name } = llm_config;
|
|
120
|
-
if (!silent && !just_output) {
|
|
121
|
-
tell_action(`Generating the Git commit message using ${llm_model_name}`);
|
|
122
|
-
}
|
|
123
134
|
const diffstat = await git_simple_staging_get_staged_diffstat(git);
|
|
124
135
|
const diff = await git_simple_staging_get_staged_diff(git);
|
|
136
|
+
if (!no_secret_check) {
|
|
137
|
+
try {
|
|
138
|
+
await secret_check({ text: diff, interactive: true });
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
abort_with_error(`Aborting: ${error_get_message(error)}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
125
144
|
const inputs = { diffstat, diff, extra_prompts };
|
|
145
|
+
if (!silent && !just_output) {
|
|
146
|
+
tell_action(`Generating the Git commit message using ${llm_model_name}`);
|
|
147
|
+
}
|
|
126
148
|
const result = await git_message_generate_result({ llm_config, inputs });
|
|
127
149
|
const { error_text } = result;
|
|
128
150
|
if (error_text !== null) {
|
|
@@ -142,7 +164,7 @@ async function phase_generate({ config, git }) {
|
|
|
142
164
|
return git_message;
|
|
143
165
|
}
|
|
144
166
|
async function phase_commit({ config, git, git_message, }) {
|
|
145
|
-
const { auto_commit, disable_commit, silent } = config;
|
|
167
|
+
const { auto_commit, disable_commit, no_verify, silent } = config;
|
|
146
168
|
if (disable_commit) {
|
|
147
169
|
return;
|
|
148
170
|
}
|
|
@@ -152,18 +174,22 @@ async function phase_commit({ config, git, git_message, }) {
|
|
|
152
174
|
}
|
|
153
175
|
}
|
|
154
176
|
else {
|
|
155
|
-
const commit_confirmed = await
|
|
177
|
+
const commit_confirmed = await tui_confirm({
|
|
178
|
+
question: "Do you want to commit these changes?",
|
|
179
|
+
default: true,
|
|
180
|
+
style_message: ansi_blue,
|
|
181
|
+
});
|
|
156
182
|
if (!commit_confirmed) {
|
|
157
183
|
abort_with_warning("Commit cancelled by user");
|
|
158
184
|
}
|
|
159
185
|
}
|
|
160
|
-
await git_simple_staging_create_commit(git, git_message);
|
|
186
|
+
await git_simple_staging_create_commit({ git, git_message, no_verify });
|
|
161
187
|
if (!silent) {
|
|
162
188
|
tell_success("Changes committed successfully");
|
|
163
189
|
}
|
|
164
190
|
}
|
|
165
191
|
async function phase_push({ config, git }) {
|
|
166
|
-
const { auto_push, disable_commit, disable_push,
|
|
192
|
+
const { auto_push, disable_commit, disable_push, no_verify, force, silent } = config;
|
|
167
193
|
if (disable_push || disable_commit) {
|
|
168
194
|
return;
|
|
169
195
|
}
|
|
@@ -173,13 +199,17 @@ async function phase_push({ config, git }) {
|
|
|
173
199
|
}
|
|
174
200
|
}
|
|
175
201
|
else {
|
|
176
|
-
const push_confirmed = await
|
|
202
|
+
const push_confirmed = await tui_confirm({
|
|
203
|
+
question: "Do you want to push these changes?",
|
|
204
|
+
default: true,
|
|
205
|
+
style_message: ansi_blue,
|
|
206
|
+
});
|
|
177
207
|
if (!push_confirmed) {
|
|
178
208
|
return;
|
|
179
209
|
}
|
|
180
210
|
}
|
|
181
211
|
try {
|
|
182
|
-
await git_simple_staging_push_to_remote({ git, no_verify
|
|
212
|
+
await git_simple_staging_push_to_remote({ git, no_verify, force });
|
|
183
213
|
}
|
|
184
214
|
catch (error) {
|
|
185
215
|
abort_with_error(`Failed to push to remote: ${error_get_text(error)}`);
|
|
@@ -1,39 +1,41 @@
|
|
|
1
1
|
import { Duration } from "./lib_duration.js";
|
|
2
2
|
import { error_get_text } from "./lib_error.js";
|
|
3
|
-
import {
|
|
3
|
+
import { git_message_prompt_get_system, git_message_prompt_get_user } from "./lib_git_message_prompt.js";
|
|
4
4
|
import { git_message_schema, git_message_schema_format } from "./lib_git_message_schema.js";
|
|
5
5
|
import { llm_chat_generate_object, llm_chat_generate_text } from "./lib_llm_chat.js";
|
|
6
|
-
import {
|
|
7
|
-
async function git_message_generate_unstructured({ llm_config, system_prompt, user_prompt, }) {
|
|
8
|
-
const outputs = await llm_chat_generate_text({ llm_config, system_prompt, user_prompt });
|
|
6
|
+
import { llm_tokens_debug_usage, llm_tokens_estimate_length_from_tokens, llm_tokens_estimate_tokens_from_length, } from "./lib_llm_tokens.js";
|
|
7
|
+
async function git_message_generate_unstructured({ llm_config, system_prompt, user_prompt, max_output_tokens, }) {
|
|
8
|
+
const outputs = await llm_chat_generate_text({ llm_config, system_prompt, user_prompt, max_output_tokens });
|
|
9
9
|
return outputs;
|
|
10
10
|
}
|
|
11
|
-
async function git_message_generate_structured({ llm_config, system_prompt, user_prompt, }) {
|
|
11
|
+
async function git_message_generate_structured({ llm_config, system_prompt, user_prompt, max_output_tokens, }) {
|
|
12
12
|
const schema = git_message_schema;
|
|
13
13
|
const { generated_object, total_usage, provider_metadata } = await llm_chat_generate_object({
|
|
14
14
|
llm_config,
|
|
15
15
|
system_prompt,
|
|
16
16
|
user_prompt,
|
|
17
|
+
max_output_tokens,
|
|
17
18
|
schema,
|
|
18
19
|
});
|
|
19
20
|
const generated_text = git_message_schema_format(generated_object);
|
|
20
21
|
return { generated_text, reasoning_text: undefined, total_usage, provider_metadata };
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
-
const {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const user_tokens =
|
|
27
|
-
const user_length = user_tokens
|
|
28
|
-
const user_prompt =
|
|
23
|
+
async function git_message_generate_outputs({ llm_config, inputs, }) {
|
|
24
|
+
const { effective_context_window } = llm_config;
|
|
25
|
+
const { has_structured_json } = llm_config.llm_model_detail;
|
|
26
|
+
const system_prompt = git_message_prompt_get_system({ has_structured_json, inputs });
|
|
27
|
+
const user_tokens = effective_context_window - llm_tokens_estimate_tokens_from_length({ llm_config, length: system_prompt.length }) - 1000;
|
|
28
|
+
const user_length = llm_tokens_estimate_length_from_tokens({ llm_config, tokens: user_tokens });
|
|
29
|
+
const user_prompt = git_message_prompt_get_user({
|
|
29
30
|
has_structured_json,
|
|
30
31
|
inputs,
|
|
31
32
|
max_length: user_length,
|
|
32
33
|
});
|
|
34
|
+
const max_output_tokens = 8192; // This is the maximum for some models
|
|
33
35
|
llm_tokens_debug_usage({ name: "Inputs", llm_config, text: system_prompt + user_prompt });
|
|
34
36
|
const outputs = has_structured_json
|
|
35
|
-
? await git_message_generate_structured({ llm_config, system_prompt, user_prompt })
|
|
36
|
-
: await git_message_generate_unstructured({ llm_config, system_prompt, user_prompt });
|
|
37
|
+
? await git_message_generate_structured({ llm_config, system_prompt, user_prompt, max_output_tokens })
|
|
38
|
+
: await git_message_generate_unstructured({ llm_config, system_prompt, user_prompt, max_output_tokens });
|
|
37
39
|
llm_tokens_debug_usage({ name: "Outputs", llm_config, text: outputs.generated_text });
|
|
38
40
|
return outputs;
|
|
39
41
|
}
|
|
@@ -41,7 +43,7 @@ export async function git_message_generate_result({ llm_config, inputs, }) {
|
|
|
41
43
|
const duration = new Duration();
|
|
42
44
|
duration.start();
|
|
43
45
|
try {
|
|
44
|
-
const outputs = await
|
|
46
|
+
const outputs = await git_message_generate_outputs({ llm_config, inputs });
|
|
45
47
|
duration.stop();
|
|
46
48
|
const seconds = duration.seconds_rounded();
|
|
47
49
|
return { llm_config, seconds, error_text: null, outputs };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { LF } from "./lib_char_control.js";
|
|
2
2
|
import { EMPTY } from "./lib_char_empty.js";
|
|
3
|
+
import { tell_warning } from "./lib_tell.js";
|
|
3
4
|
const LF_LF = LF + LF;
|
|
4
5
|
const portion_role = `
|
|
5
6
|
Your role is to generate a Git commit message in conversational English.
|
|
@@ -45,13 +46,13 @@ A simple change needs only two additional sentences scaling up to a complex chan
|
|
|
45
46
|
If there are a lot of changes, you will need to summarize even more.
|
|
46
47
|
`.trim() + LF_LF;
|
|
47
48
|
const portion_extra = (extra_prompts) => {
|
|
48
|
-
return extra_prompts && extra_prompts.length > 0 ? extra_prompts.map((s) => s.trim).join(LF) + LF_LF : EMPTY;
|
|
49
|
+
return extra_prompts && extra_prompts.length > 0 ? extra_prompts.map((s) => s.trim()).join(LF) + LF_LF : EMPTY;
|
|
49
50
|
};
|
|
50
51
|
const portion_final = `
|
|
51
52
|
Everything you write will be checked for validity and then saved directly to Git - it will not be reviewed by a human.
|
|
52
53
|
Therefore, you must just output the Git message itself without any introductory or concluding sections.
|
|
53
54
|
`.trim() + LF_LF;
|
|
54
|
-
export function
|
|
55
|
+
export function git_message_prompt_get_system({ has_structured_json, inputs, }) {
|
|
55
56
|
let system_prompt = EMPTY;
|
|
56
57
|
system_prompt += portion_role;
|
|
57
58
|
system_prompt += portion_inputs;
|
|
@@ -62,9 +63,12 @@ export function git_message_get_system_prompt({ has_structured_json, inputs, })
|
|
|
62
63
|
system_prompt += portion_final;
|
|
63
64
|
return system_prompt.trim();
|
|
64
65
|
}
|
|
65
|
-
export function
|
|
66
|
+
export function git_message_prompt_get_user({ has_structured_json, inputs, max_length, }) {
|
|
66
67
|
const { diffstat, diff } = inputs;
|
|
67
68
|
const truncate = diffstat.length + diff.length > max_length;
|
|
69
|
+
if (truncate) {
|
|
70
|
+
tell_warning("The Diff is too long to fit in the user prompt - it is being truncated");
|
|
71
|
+
}
|
|
68
72
|
const diff_truncated = truncate ? diff.slice(0, max_length - diffstat.length) + LF : diff;
|
|
69
73
|
let user_prompt = EMPTY;
|
|
70
74
|
user_prompt += "<diffstat>" + LF + diffstat + "</diffstat>" + LF_LF;
|
|
@@ -26,16 +26,21 @@ export async function git_simple_staging_get_staged_diffstat(git) {
|
|
|
26
26
|
export async function git_simple_staging_get_staged_diff(git) {
|
|
27
27
|
return await git.diff(["--cached"]);
|
|
28
28
|
}
|
|
29
|
-
export async function git_simple_staging_create_commit(git, git_message) {
|
|
30
|
-
|
|
29
|
+
export async function git_simple_staging_create_commit({ git, git_message, no_verify = false, }) {
|
|
30
|
+
const options = {};
|
|
31
|
+
if (no_verify) {
|
|
32
|
+
options["--no-verify"] = null;
|
|
33
|
+
}
|
|
34
|
+
await git.commit(git_message, options);
|
|
31
35
|
}
|
|
32
36
|
export async function git_simple_staging_push_to_remote({ git, no_verify = false, force = false, }) {
|
|
33
|
-
const
|
|
37
|
+
const options = {};
|
|
38
|
+
options["--follow-tags"] = null;
|
|
34
39
|
if (no_verify) {
|
|
35
|
-
|
|
40
|
+
options["--no-verify"] = null;
|
|
36
41
|
}
|
|
37
42
|
if (force) {
|
|
38
|
-
|
|
43
|
+
options["--force"] = null;
|
|
39
44
|
}
|
|
40
|
-
await git.push(
|
|
45
|
+
await git.push(options);
|
|
41
46
|
}
|
package/dist/src/lib_llm_chat.js
CHANGED
|
@@ -10,7 +10,7 @@ import { tui_block_string } from "./lib_tui_block.js";
|
|
|
10
10
|
function llm_chat_get_parameters() {
|
|
11
11
|
return {
|
|
12
12
|
max_output_tokens: parse_int_or_undefined(env_get_empty("lib_llm_chat_max_output_tokens")),
|
|
13
|
-
timeout: parse_int(env_get_substitute("lib_llm_chat_timeout", "
|
|
13
|
+
timeout: parse_int(env_get_substitute("lib_llm_chat_timeout", "90")),
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
function llm_chat_debug_prompts({ llm_model_name, system_prompt, user_prompt, }) {
|
|
@@ -22,7 +22,7 @@ function llm_chat_debug_prompts({ llm_model_name, system_prompt, user_prompt, })
|
|
|
22
22
|
tui_block_string({ teller, title: `LLM user prompt (for ${llm_model_name}):`, content: user_prompt });
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
export async function llm_chat_generate_text({ llm_config, system_prompt, user_prompt, tools, max_steps, min_steps, }) {
|
|
25
|
+
export async function llm_chat_generate_text({ llm_config, system_prompt, user_prompt, max_output_tokens, tools, max_steps, min_steps, }) {
|
|
26
26
|
const { llm_model_name, llm_model_detail, llm_model_code, llm_api_code, llm_api_key } = llm_config;
|
|
27
27
|
llm_chat_debug_prompts({ system_prompt, user_prompt, llm_model_name });
|
|
28
28
|
const ai_sdk_language_model = llm_api_get_ai_sdk_language_model({
|
|
@@ -30,9 +30,14 @@ export async function llm_chat_generate_text({ llm_config, system_prompt, user_p
|
|
|
30
30
|
llm_api_code,
|
|
31
31
|
llm_api_key,
|
|
32
32
|
});
|
|
33
|
-
const { recommended_temperature, provider_options } = llm_model_detail;
|
|
33
|
+
const { recommended_temperature, provider_options, max_output_tokens: max_output_tokens_model } = llm_model_detail;
|
|
34
34
|
const temperature = recommended_temperature;
|
|
35
|
-
const {
|
|
35
|
+
const { timeout, max_output_tokens: max_output_tokens_env } = llm_chat_get_parameters();
|
|
36
|
+
if (max_output_tokens_env !== undefined) {
|
|
37
|
+
max_output_tokens = max_output_tokens_env;
|
|
38
|
+
}
|
|
39
|
+
max_output_tokens =
|
|
40
|
+
max_output_tokens === undefined ? max_output_tokens_model : Math.min(max_output_tokens, max_output_tokens_model);
|
|
36
41
|
const llm_inputs = {
|
|
37
42
|
model: ai_sdk_language_model,
|
|
38
43
|
system: system_prompt,
|
|
@@ -57,11 +62,11 @@ export async function llm_chat_generate_text({ llm_config, system_prompt, user_p
|
|
|
57
62
|
const { text: generated_text, reasoningText: reasoning_text, totalUsage: total_usage, providerMetadata: provider_metadata, } = llm_outputs;
|
|
58
63
|
return { generated_text, reasoning_text, total_usage, provider_metadata };
|
|
59
64
|
}
|
|
60
|
-
export async function llm_chat_generate_text_result({ llm_config, system_prompt, user_prompt, }) {
|
|
65
|
+
export async function llm_chat_generate_text_result({ llm_config, system_prompt, user_prompt, max_output_tokens, }) {
|
|
61
66
|
const duration = new Duration();
|
|
62
67
|
duration.start();
|
|
63
68
|
try {
|
|
64
|
-
const outputs = await llm_chat_generate_text({ llm_config, system_prompt, user_prompt });
|
|
69
|
+
const outputs = await llm_chat_generate_text({ llm_config, system_prompt, user_prompt, max_output_tokens });
|
|
65
70
|
duration.stop();
|
|
66
71
|
const seconds = duration.seconds_rounded();
|
|
67
72
|
return { llm_config, seconds, error_text: null, outputs };
|
|
@@ -73,7 +78,7 @@ export async function llm_chat_generate_text_result({ llm_config, system_prompt,
|
|
|
73
78
|
return { llm_config, seconds, error_text, outputs: null };
|
|
74
79
|
}
|
|
75
80
|
}
|
|
76
|
-
export async function llm_chat_generate_object({ llm_config, user_prompt, system_prompt, schema, }) {
|
|
81
|
+
export async function llm_chat_generate_object({ llm_config, user_prompt, system_prompt, max_output_tokens, schema, }) {
|
|
77
82
|
const { llm_model_name, llm_model_detail, llm_model_code, llm_api_code, llm_api_key } = llm_config;
|
|
78
83
|
llm_chat_debug_prompts({ system_prompt, user_prompt, llm_model_name });
|
|
79
84
|
const ai_sdk_language_model = llm_api_get_ai_sdk_language_model({
|
|
@@ -83,14 +88,14 @@ export async function llm_chat_generate_object({ llm_config, user_prompt, system
|
|
|
83
88
|
});
|
|
84
89
|
const { recommended_temperature, provider_options } = llm_model_detail;
|
|
85
90
|
const temperature = recommended_temperature;
|
|
86
|
-
const {
|
|
91
|
+
const { timeout, max_output_tokens: max_output_tokens_env } = llm_chat_get_parameters();
|
|
87
92
|
const llm_inputs = {
|
|
88
93
|
model: ai_sdk_language_model,
|
|
89
94
|
system: system_prompt,
|
|
90
95
|
prompt: user_prompt,
|
|
91
96
|
output: "object",
|
|
92
97
|
schema,
|
|
93
|
-
maxOutputTokens: max_output_tokens,
|
|
98
|
+
maxOutputTokens: max_output_tokens_env ?? max_output_tokens,
|
|
94
99
|
temperature,
|
|
95
100
|
providerOptions: provider_options,
|
|
96
101
|
abortSignal: AbortSignal.timeout(timeout * 1000),
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
import { env_get } from "./lib_env.js";
|
|
1
2
|
import { llm_access_available, llm_access_get } from "./lib_llm_access.js";
|
|
2
3
|
import { llm_model_find_detail, llm_model_get_choices } from "./lib_llm_model.js";
|
|
4
|
+
import { parse_int_or_undefined } from "./lib_parse_number.js";
|
|
3
5
|
export function llm_config_get({ llm_model_details, llm_model_name, }) {
|
|
4
6
|
const llm_model_detail = llm_model_find_detail({ llm_model_details, llm_model_name });
|
|
5
7
|
const access = llm_access_get({ llm_model_details, llm_model_name });
|
|
6
8
|
const { llm_model_code, llm_api_code, llm_api_key } = access;
|
|
7
|
-
|
|
9
|
+
let effective_context_window = llm_model_detail.context_window;
|
|
10
|
+
const env_context_window = parse_int_or_undefined(env_get("LLM_CONFIG_CONTEXT_WINDOW"));
|
|
11
|
+
if (env_context_window !== undefined) {
|
|
12
|
+
effective_context_window = Math.min(effective_context_window, env_context_window);
|
|
13
|
+
}
|
|
14
|
+
return { llm_model_name, llm_model_detail, llm_model_code, llm_api_code, llm_api_key, effective_context_window };
|
|
8
15
|
}
|
|
9
16
|
export function llm_config_get_all({ llm_model_details, llm_include, llm_excludes, }) {
|
|
10
17
|
const choices = llm_model_get_choices({ llm_model_details });
|
package/dist/src/lib_llm_list.js
CHANGED
|
@@ -5,7 +5,7 @@ import { TuiTable } from "./lib_tui_table.js";
|
|
|
5
5
|
export function llm_list_models({ llm_model_details }) {
|
|
6
6
|
const headings = ["NAME", "API", "CONTEXT", "INPUT", "OUTPUT", "REASONING"];
|
|
7
7
|
const alignments = ["left", "left", "right", "right", "right", "left"];
|
|
8
|
-
const table = new TuiTable({ headings, alignments });
|
|
8
|
+
const table = new TuiTable({ headings, alignments, compact: true });
|
|
9
9
|
for (const detail of llm_model_details) {
|
|
10
10
|
const { llm_model_name, llm_api_code, context_window, cents_input, cents_output, default_reasoning } = detail;
|
|
11
11
|
const tui_name = llm_model_name;
|