@johnowennixon/diffdash 1.7.0 → 1.9.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 +18 -18
- package/dist/package.json +24 -22
- package/dist/src/lib_datetime.js +4 -1
- package/dist/src/lib_debug.js +1 -0
- package/dist/src/lib_diffdash_cli.js +3 -5
- package/dist/src/lib_diffdash_config.js +6 -15
- package/dist/src/lib_diffdash_llm.js +8 -13
- package/dist/src/lib_diffdash_sequence.js +33 -17
- package/dist/src/lib_git_message_generate.js +19 -19
- package/dist/src/lib_git_message_prompt.js +3 -3
- package/dist/src/lib_git_message_schema.js +3 -3
- package/dist/src/lib_git_simple_staging.js +11 -6
- package/dist/src/lib_llm_access.js +14 -53
- package/dist/src/lib_llm_api.js +2 -36
- package/dist/src/lib_llm_chat.js +26 -19
- package/dist/src/lib_llm_config.js +12 -15
- package/dist/src/lib_llm_list.js +10 -8
- package/dist/src/lib_llm_model.js +369 -241
- package/dist/src/lib_llm_results.js +50 -0
- package/dist/src/lib_llm_tokens.js +6 -3
- package/dist/src/lib_parse_number.js +2 -2
- package/dist/src/lib_tell.js +6 -5
- package/dist/src/lib_tui_confirm.js +25 -0
- package/dist/src/lib_tui_none.js +12 -0
- package/dist/src/lib_tui_number.js +22 -0
- package/dist/src/lib_tui_table.js +14 -6
- package/package.json +24 -22
- package/dist/src/lib_assert_type.js +0 -30
- package/dist/src/lib_tui_readline.js +0 -16
- package/dist/src/lib_type_guard.js +0 -15
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# DiffDash
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
[](https://deepwiki.com/johnowennixon/diffdash)
|
|
7
|
+
|
|
3
8
|
A command-line tool to generate Git commit messages using AI.
|
|
4
9
|
|
|
5
10
|
## Demonstration
|
|
@@ -8,15 +13,15 @@ A command-line tool to generate Git commit messages using AI.
|
|
|
8
13
|
|
|
9
14
|
## Features
|
|
10
15
|
|
|
11
|
-
* Generate Git commit messages in natural English
|
|
16
|
+
* Generate Git commit messages in **natural English**
|
|
12
17
|
* Add a footer to the generated commit messages
|
|
13
18
|
* Add a prefix or suffix to the summary line
|
|
14
19
|
* Select from a choice of LLM models
|
|
15
20
|
* Compare messages generated from all configured models
|
|
16
21
|
* Disable or auto-approve various stages
|
|
17
|
-
*
|
|
22
|
+
* Option to output just the commit message for use in scripts
|
|
18
23
|
* Configuration using standard API provider environment variables
|
|
19
|
-
* Uses the Vercel AI SDK
|
|
24
|
+
* Uses the Vercel AI SDK (version 5)
|
|
20
25
|
* Uses structured JSON with compatible models
|
|
21
26
|
* Substantially written using AI coding (Claude Code, Roo Code, and Amp)
|
|
22
27
|
|
|
@@ -28,24 +33,21 @@ npm install -g @johnowennixon/diffdash
|
|
|
28
33
|
|
|
29
34
|
## LLM Models
|
|
30
35
|
|
|
31
|
-
Currently, for this application, the best LLM model
|
|
36
|
+
Currently, for this application, the best LLM model is **gpt-4.1-mini** from OpenAI.
|
|
32
37
|
It is set as the default model.
|
|
33
38
|
I can only presume they have done a ton of training on diffs.
|
|
34
39
|
|
|
40
|
+
I am now testing the GPT-5 models and **gpt-5-mini-minimal** (GPT-5 Mini with reasoning disabled) is behaving much the same.
|
|
41
|
+
It will probably become the default model soon.
|
|
42
|
+
|
|
35
43
|
## API Keys
|
|
36
44
|
|
|
37
45
|
DiffDash requires at least one API key for an LLM provider. These must be provided as environment variables.
|
|
38
46
|
|
|
39
47
|
```bash
|
|
40
|
-
# For OpenAI (recommended)
|
|
48
|
+
# For OpenAI (strongly recommended)
|
|
41
49
|
export OPENAI_API_KEY=your-api-key
|
|
42
50
|
|
|
43
|
-
# For Requesty
|
|
44
|
-
export REQUESTY_API_KEY=your-api-key
|
|
45
|
-
|
|
46
|
-
# For OpenRouter
|
|
47
|
-
export OPENROUTER_API_KEY=your-api-key
|
|
48
|
-
|
|
49
51
|
# For Anthropic
|
|
50
52
|
export ANTHROPIC_API_KEY=your-api-key
|
|
51
53
|
|
|
@@ -54,6 +56,9 @@ export DEEPSEEK_API_KEY=your-api-key
|
|
|
54
56
|
|
|
55
57
|
# For Google Gemini
|
|
56
58
|
export GEMINI_API_KEY=your-api-key
|
|
59
|
+
|
|
60
|
+
# For OpenRouter (all other models)
|
|
61
|
+
export OPENROUTER_API_KEY=your-api-key
|
|
57
62
|
```
|
|
58
63
|
|
|
59
64
|
## Usage
|
|
@@ -98,9 +103,6 @@ diffdash --add-suffix "(closes #123)"
|
|
|
98
103
|
# Display commit messages generated by all models
|
|
99
104
|
diffdash --llm-compare
|
|
100
105
|
|
|
101
|
-
# Use the fallback LLM model
|
|
102
|
-
diffdash --llm-fallback
|
|
103
|
-
|
|
104
106
|
# Specify the LLM model by name
|
|
105
107
|
diffdash --llm-model claude-3.5-haiku
|
|
106
108
|
|
|
@@ -127,14 +129,12 @@ All command-line arguments are optional.
|
|
|
127
129
|
| `--disable-preview` | disable previewing the generated message|
|
|
128
130
|
| `--disable-commit` | disable committing changes - exit after generating the message |
|
|
129
131
|
| `--disable-push` | disable pushing changes - exit after making the commit |
|
|
130
|
-
| `--push-no-verify` | bypass git hooks when pushing to Git |
|
|
131
|
-
| `--push-force` | apply force when pushing to Git |
|
|
132
132
|
| `--add-prefix PREFIX` | add a prefix to the commit message summary line |
|
|
133
133
|
| `--add-suffix SUFFIX` | add a suffix to the commit message summary line |
|
|
134
|
+
| `--no-verify` | bypass git hooks when committing or pushing to Git |
|
|
135
|
+
| `--force` | apply force when pushing to Git |
|
|
134
136
|
| `--llm-list` | display a list of available Large Language Models and exit |
|
|
135
137
|
| `--llm-compare` | compare the generated messages from all models - but do not commit |
|
|
136
|
-
| `--llm-router` | prefer to access the LLM via a router rather than direct |
|
|
137
|
-
| `--llm-fallback` | use the fallback LLM model instead of the default |
|
|
138
138
|
| `--llm-model MODEL` | choose the LLM model by name (the default is normally best) |
|
|
139
139
|
| `--llm-excludes MODELS` | models to exclude from comparison (comma separated) |
|
|
140
140
|
| `--just-output` | just output the commit message for use in scripts |
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johnowennixon/diffdash",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.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",
|
|
@@ -24,49 +24,51 @@
|
|
|
24
24
|
"build:clean": "echo 'Removing dist' && rimraf dist",
|
|
25
25
|
"build:shebang": "echo 'Fixing the shebangs' && add-shebangs --node --exclude 'dist/**/lib_*.js' 'dist/**/*.js'",
|
|
26
26
|
"build:tsc": "echo 'Transpiling TypeScript to dist (using tsc)' && tsc --erasableSyntaxOnly --libReplacement false",
|
|
27
|
+
"build:tsgo": "echo 'Transpiling TypeScript to dist (using tsgo)' && tsgo || (rimraf dist && false)",
|
|
27
28
|
"fix": "run-s -ls fix:biome fix:markdownlint",
|
|
28
29
|
"fix:biome": "echo 'Fixing with Biome' && biome check --write",
|
|
29
|
-
"fix:docbot": "echo 'Fixing with DocBot' && docbot --
|
|
30
|
+
"fix:docbot": "echo 'Fixing with DocBot' && docbot --prune --generate",
|
|
30
31
|
"fix:markdownlint": "echo 'Fixing with markdownlint' && markdownlint-cli2 '**/*.md' --fix",
|
|
31
|
-
"fix:oxlint": "echo 'Fixing with
|
|
32
|
+
"fix:oxlint": "echo 'Fixing with Oxlint' && oxlint --fix",
|
|
32
33
|
"lint": "run-s -ls lint:biome lint:oxlint lint:tsgolint lint:knip lint:markdownlint",
|
|
33
34
|
"lint:biome": "echo 'Linting with Biome' && biome check",
|
|
34
35
|
"lint:docbot": "echo 'Linting with DocBot' && docbot",
|
|
35
36
|
"lint:knip": "echo 'Linting with Knip' && knip",
|
|
36
|
-
"lint:markdownlint": "echo 'Linting with
|
|
37
|
-
"lint:oxlint": "echo 'Linting with
|
|
37
|
+
"lint:markdownlint": "echo 'Linting with Markdownlint' && markdownlint-cli2 '**/*.md'",
|
|
38
|
+
"lint:oxlint": "echo 'Linting with Oxlint' && oxlint",
|
|
38
39
|
"lint:tsc": "echo 'Linting with tsc' && tsc --noEmit --erasableSyntaxOnly --libReplacement false",
|
|
39
|
-
"lint:
|
|
40
|
+
"lint:tsgo": "echo 'Linting with tsgo' && tsgo --noEmit",
|
|
41
|
+
"lint:tsgolint": "echo 'Linting with tsgolint' && candide-tsgolint",
|
|
40
42
|
"test": "run-s -ls lint build"
|
|
41
43
|
},
|
|
42
44
|
"dependencies": {
|
|
43
|
-
"@ai-sdk/anthropic": "
|
|
44
|
-
"@ai-sdk/deepseek": "0.
|
|
45
|
-
"@ai-sdk/google": "
|
|
46
|
-
"@ai-sdk/openai": "
|
|
47
|
-
"@
|
|
48
|
-
"@
|
|
49
|
-
"ai": "
|
|
45
|
+
"@ai-sdk/anthropic": "2.0.9",
|
|
46
|
+
"@ai-sdk/deepseek": "1.0.13",
|
|
47
|
+
"@ai-sdk/google": "2.0.11",
|
|
48
|
+
"@ai-sdk/openai": "2.0.23",
|
|
49
|
+
"@inquirer/prompts": "7.8.4",
|
|
50
|
+
"@openrouter/ai-sdk-provider": "1.1.2",
|
|
51
|
+
"ai": "5.0.29",
|
|
50
52
|
"ansis": "4.1.0",
|
|
51
53
|
"argparse": "2.0.1",
|
|
52
54
|
"cli-table3": "0.6.5",
|
|
53
55
|
"json5": "2.2.3",
|
|
54
56
|
"simple-git": "3.28.0",
|
|
55
|
-
"zod": "
|
|
57
|
+
"zod": "4.1.5"
|
|
56
58
|
},
|
|
57
59
|
"devDependencies": {
|
|
58
|
-
"@biomejs/biome": "2.
|
|
60
|
+
"@biomejs/biome": "2.2.2",
|
|
61
|
+
"@candide/tsgolint": "1.3.0",
|
|
59
62
|
"@johnowennixon/add-shebangs": "1.1.0",
|
|
60
|
-
"@johnowennixon/chmodx": "2.
|
|
61
|
-
"@johnowennixon/pipe-exit": "1.0.1",
|
|
63
|
+
"@johnowennixon/chmodx": "2.1.0",
|
|
62
64
|
"@types/argparse": "2.0.17",
|
|
63
|
-
"@types/node": "24.
|
|
64
|
-
"
|
|
65
|
+
"@types/node": "24.3.0",
|
|
66
|
+
"@typescript/native-preview": "7.0.0-dev.20250902.1",
|
|
67
|
+
"knip": "5.63.0",
|
|
65
68
|
"markdownlint-cli2": "0.18.1",
|
|
66
69
|
"npm-run-all2": "8.0.4",
|
|
67
|
-
"oxlint": "1.
|
|
68
|
-
"oxlint-tsgolint": "0.0.0-8",
|
|
70
|
+
"oxlint": "1.14.0",
|
|
69
71
|
"rimraf": "6.0.1",
|
|
70
|
-
"typescript": "5.
|
|
72
|
+
"typescript": "5.9.2"
|
|
71
73
|
}
|
|
72
74
|
}
|
package/dist/src/lib_datetime.js
CHANGED
|
@@ -25,6 +25,9 @@ export function datetime_parse_timestamp(timestamp) {
|
|
|
25
25
|
export function datetime_format_utc_iso_ymdthms(date) {
|
|
26
26
|
return date.toISOString().slice(0, 19);
|
|
27
27
|
}
|
|
28
|
+
export function datetime_format_utc_iso_ymd_hms(date) {
|
|
29
|
+
return date.toISOString().slice(0, 19).replace("T", SPACE);
|
|
30
|
+
}
|
|
28
31
|
export function datetime_format_utc_iso_ymd(date) {
|
|
29
32
|
return date.toISOString().slice(0, 10);
|
|
30
33
|
}
|
|
@@ -44,7 +47,7 @@ export function datetime_format_local_iso_ymdthms(date) {
|
|
|
44
47
|
return datetime_format_utc_iso_ymdthms(datetime_localize(date));
|
|
45
48
|
}
|
|
46
49
|
export function datetime_format_local_iso_ymd_hms(date) {
|
|
47
|
-
return
|
|
50
|
+
return datetime_format_utc_iso_ymd_hms(datetime_localize(date));
|
|
48
51
|
}
|
|
49
52
|
export function datetime_format_local_iso_ymd(date) {
|
|
50
53
|
return datetime_format_utc_iso_ymd(datetime_localize(date));
|
package/dist/src/lib_debug.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cli_boolean, cli_choice_default, cli_make_parser, cli_string } from "./lib_cli.js";
|
|
2
|
-
import { diffdash_llm_model_choices, diffdash_llm_model_default
|
|
2
|
+
import { diffdash_llm_model_choices, diffdash_llm_model_default } from "./lib_diffdash_llm.js";
|
|
3
3
|
const diffdash_cli_schema = {
|
|
4
4
|
version: cli_boolean({ help: "show program version information and exit" }),
|
|
5
5
|
auto_add: cli_boolean({ help: "automatically stage all changes without confirmation" }),
|
|
@@ -10,14 +10,12 @@ 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
|
-
|
|
14
|
-
|
|
13
|
+
no_verify: cli_boolean({ help: "bypass git hooks when committing or pushing to Git" }),
|
|
14
|
+
force: cli_boolean({ help: "apply force when pushing to Git" }),
|
|
15
15
|
add_prefix: cli_string({ help: "add a prefix to the commit message summary line", metavar: "PREFIX" }),
|
|
16
16
|
add_suffix: cli_string({ help: "add a suffix to the commit message summary line", metavar: "SUFFIX" }),
|
|
17
17
|
llm_list: cli_boolean({ help: "display a list of available Large Language Models and exit" }),
|
|
18
18
|
llm_compare: cli_boolean({ help: "compare the generated messages from all models - but do not commit" }),
|
|
19
|
-
llm_router: cli_boolean({ help: "prefer to access the LLM via a router rather than direct" }),
|
|
20
|
-
llm_fallback: cli_boolean({ help: `use the fallback model (${diffdash_llm_model_fallback})` }),
|
|
21
19
|
llm_model: cli_choice_default({
|
|
22
20
|
help: `choose the Large Language Model by name (defaults to ${diffdash_llm_model_default})`,
|
|
23
21
|
choices: diffdash_llm_model_choices,
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { abort_with_error } from "./lib_abort.js";
|
|
3
3
|
import { debug_channels, debug_inspect_when } from "./lib_debug.js";
|
|
4
4
|
import { diffdash_cli_parsed_args } from "./lib_diffdash_cli.js";
|
|
5
|
-
import { diffdash_llm_model_details
|
|
5
|
+
import { diffdash_llm_model_details } from "./lib_diffdash_llm.js";
|
|
6
6
|
import { file_io_read_text } from "./lib_file_io.js";
|
|
7
7
|
import { file_is_file } from "./lib_file_is.js";
|
|
8
8
|
import { json5_parse } from "./lib_json5.js";
|
|
@@ -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, no_verify, force, add_prefix, add_suffix, llm_list, llm_compare, llm_model, llm_excludes, 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);
|
|
@@ -42,17 +42,8 @@ export function diffdash_config_get() {
|
|
|
42
42
|
llm_list_models({ llm_model_details: diffdash_llm_model_details });
|
|
43
43
|
process.exit(0);
|
|
44
44
|
}
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
llm_model_details: diffdash_llm_model_details,
|
|
48
|
-
llm_model_name,
|
|
49
|
-
llm_router,
|
|
50
|
-
});
|
|
51
|
-
const all_llm_configs = llm_config_get_all({
|
|
52
|
-
llm_model_details: diffdash_llm_model_details,
|
|
53
|
-
llm_router,
|
|
54
|
-
llm_excludes,
|
|
55
|
-
});
|
|
45
|
+
const llm_config = llm_config_get({ llm_model_details: diffdash_llm_model_details, llm_model_name: llm_model });
|
|
46
|
+
const all_llm_configs = llm_config_get_all({ llm_model_details: diffdash_llm_model_details, llm_excludes });
|
|
56
47
|
debug_channels.llm_prompts = debug_llm_prompts;
|
|
57
48
|
debug_channels.llm_inputs = debug_llm_inputs;
|
|
58
49
|
debug_channels.llm_outputs = debug_llm_outputs;
|
|
@@ -65,10 +56,10 @@ export function diffdash_config_get() {
|
|
|
65
56
|
disable_preview,
|
|
66
57
|
disable_status,
|
|
67
58
|
disable_push,
|
|
68
|
-
push_no_verify,
|
|
69
|
-
push_force,
|
|
70
59
|
add_prefix,
|
|
71
60
|
add_suffix,
|
|
61
|
+
no_verify,
|
|
62
|
+
force,
|
|
72
63
|
llm_compare,
|
|
73
64
|
llm_config,
|
|
74
65
|
all_llm_configs,
|
|
@@ -1,25 +1,20 @@
|
|
|
1
1
|
import { env_get_substitute } from "./lib_env.js";
|
|
2
2
|
import { llm_model_get_choices, llm_model_get_details } from "./lib_llm_model.js";
|
|
3
3
|
const model_name_default = "gpt-4.1-mini";
|
|
4
|
-
const model_name_fallback = "claude-3.5-haiku";
|
|
5
4
|
const model_name_options = [
|
|
6
|
-
"claude-3.5-haiku",
|
|
7
|
-
"deepseek-
|
|
8
|
-
"devstral-medium",
|
|
9
|
-
"devstral-small",
|
|
5
|
+
"claude-3.5-haiku", // fallback
|
|
6
|
+
"deepseek-chat",
|
|
10
7
|
"gemini-2.0-flash",
|
|
11
8
|
"gemini-2.5-flash",
|
|
12
9
|
"gpt-4.1-mini", // the best
|
|
13
10
|
"gpt-4.1-nano",
|
|
14
|
-
"gpt-
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"qwen3-235b-a22b",
|
|
11
|
+
"gpt-5-mini",
|
|
12
|
+
"gpt-5-mini-minimal", // fallback
|
|
13
|
+
"gpt-5-nano",
|
|
14
|
+
"gpt-5-nano-minimal",
|
|
15
|
+
"grok-code-fast-1",
|
|
16
|
+
"llama-4-maverick@cerebras",
|
|
21
17
|
];
|
|
22
18
|
export const diffdash_llm_model_details = llm_model_get_details({ llm_model_names: model_name_options });
|
|
23
19
|
export const diffdash_llm_model_choices = llm_model_get_choices({ llm_model_details: diffdash_llm_model_details });
|
|
24
20
|
export const diffdash_llm_model_default = env_get_substitute("DIFFDASH_LLM_MODEL", model_name_default);
|
|
25
|
-
export const diffdash_llm_model_fallback = model_name_fallback;
|
|
@@ -1,4 +1,5 @@
|
|
|
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
5
|
import { error_get_text } from "./lib_error.js";
|
|
@@ -7,11 +8,11 @@ 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
|
-
import {
|
|
11
|
+
import { llm_results_summary } from "./lib_llm_results.js";
|
|
11
12
|
import { stdio_write_stdout, stdio_write_stdout_linefeed } from "./lib_stdio_write.js";
|
|
12
13
|
import { tell_action, tell_info, tell_plain, tell_success, tell_warning } from "./lib_tell.js";
|
|
14
|
+
import { tui_confirm } from "./lib_tui_confirm.js";
|
|
13
15
|
import { tui_justify_left } from "./lib_tui_justify.js";
|
|
14
|
-
import { tui_readline_confirm } from "./lib_tui_readline.js";
|
|
15
16
|
async function phase_open() {
|
|
16
17
|
const git = await git_simple_open_git_repo();
|
|
17
18
|
await git_simple_open_check_not_bare(git);
|
|
@@ -40,7 +41,11 @@ async function phase_add({ config, git }) {
|
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
else {
|
|
43
|
-
const add_confirmed = await
|
|
44
|
+
const add_confirmed = await tui_confirm({
|
|
45
|
+
question: "No staged changes found - would you like to add all changes?",
|
|
46
|
+
default: true,
|
|
47
|
+
style_message: ansi_blue,
|
|
48
|
+
});
|
|
44
49
|
if (!add_confirmed) {
|
|
45
50
|
abort_with_warning("Please add changes before creating a commit");
|
|
46
51
|
}
|
|
@@ -98,25 +103,27 @@ async function phase_compare({ config, git }) {
|
|
|
98
103
|
const all_results = await Promise.all(result_promises);
|
|
99
104
|
for (const result of all_results) {
|
|
100
105
|
const { llm_config, seconds, error_text } = result;
|
|
101
|
-
const
|
|
106
|
+
const { llm_model_name } = llm_config;
|
|
102
107
|
if (error_text !== null) {
|
|
103
|
-
tell_warning(`Failed to generate a commit message in ${seconds} seconds using ${
|
|
108
|
+
tell_warning(`Failed to generate a commit message in ${seconds} seconds using ${llm_model_name}: ${error_text}`);
|
|
104
109
|
continue;
|
|
105
110
|
}
|
|
106
|
-
tell_info(`Git commit message in ${seconds} seconds using ${
|
|
107
|
-
|
|
111
|
+
tell_info(`Git commit message in ${seconds} seconds using ${llm_model_name}:`);
|
|
112
|
+
const { outputs } = result;
|
|
113
|
+
let { generated_text: git_message } = outputs;
|
|
108
114
|
const validation_result = git_message_validate_get_result(git_message);
|
|
109
115
|
const teller = validation_result.valid ? tell_plain : tell_warning;
|
|
110
116
|
git_message = diffdash_add_prefix_or_suffix({ git_message, add_prefix, add_suffix });
|
|
111
117
|
git_message = diffdash_add_footer({ git_message, llm_config });
|
|
112
118
|
git_message_display({ git_message, teller });
|
|
113
119
|
}
|
|
120
|
+
llm_results_summary(all_results);
|
|
114
121
|
}
|
|
115
122
|
async function phase_generate({ config, git }) {
|
|
116
123
|
const { disable_preview, add_prefix, add_suffix, llm_config, just_output, silent, extra_prompts } = config;
|
|
117
|
-
const
|
|
124
|
+
const { llm_model_name } = llm_config;
|
|
118
125
|
if (!silent && !just_output) {
|
|
119
|
-
tell_action(`Generating the Git commit message using ${
|
|
126
|
+
tell_action(`Generating the Git commit message using ${llm_model_name}`);
|
|
120
127
|
}
|
|
121
128
|
const diffstat = await git_simple_staging_get_staged_diffstat(git);
|
|
122
129
|
const diff = await git_simple_staging_get_staged_diff(git);
|
|
@@ -124,9 +131,10 @@ async function phase_generate({ config, git }) {
|
|
|
124
131
|
const result = await git_message_generate_result({ llm_config, inputs });
|
|
125
132
|
const { error_text } = result;
|
|
126
133
|
if (error_text !== null) {
|
|
127
|
-
abort_with_error(`Failed to generate a commit message using ${
|
|
134
|
+
abort_with_error(`Failed to generate a commit message using ${llm_model_name}: ${error_text}`);
|
|
128
135
|
}
|
|
129
|
-
|
|
136
|
+
const { outputs } = result;
|
|
137
|
+
let { generated_text: git_message } = outputs;
|
|
130
138
|
git_message_validate_check(git_message);
|
|
131
139
|
git_message = diffdash_add_prefix_or_suffix({ git_message, add_prefix, add_suffix });
|
|
132
140
|
git_message = diffdash_add_footer({ git_message, llm_config });
|
|
@@ -139,7 +147,7 @@ async function phase_generate({ config, git }) {
|
|
|
139
147
|
return git_message;
|
|
140
148
|
}
|
|
141
149
|
async function phase_commit({ config, git, git_message, }) {
|
|
142
|
-
const { auto_commit, disable_commit, silent } = config;
|
|
150
|
+
const { auto_commit, disable_commit, no_verify, silent } = config;
|
|
143
151
|
if (disable_commit) {
|
|
144
152
|
return;
|
|
145
153
|
}
|
|
@@ -149,18 +157,22 @@ async function phase_commit({ config, git, git_message, }) {
|
|
|
149
157
|
}
|
|
150
158
|
}
|
|
151
159
|
else {
|
|
152
|
-
const commit_confirmed = await
|
|
160
|
+
const commit_confirmed = await tui_confirm({
|
|
161
|
+
question: "Do you want to commit these changes?",
|
|
162
|
+
default: true,
|
|
163
|
+
style_message: ansi_blue,
|
|
164
|
+
});
|
|
153
165
|
if (!commit_confirmed) {
|
|
154
166
|
abort_with_warning("Commit cancelled by user");
|
|
155
167
|
}
|
|
156
168
|
}
|
|
157
|
-
await git_simple_staging_create_commit(git, git_message);
|
|
169
|
+
await git_simple_staging_create_commit({ git, git_message, no_verify });
|
|
158
170
|
if (!silent) {
|
|
159
171
|
tell_success("Changes committed successfully");
|
|
160
172
|
}
|
|
161
173
|
}
|
|
162
174
|
async function phase_push({ config, git }) {
|
|
163
|
-
const { auto_push, disable_commit, disable_push,
|
|
175
|
+
const { auto_push, disable_commit, disable_push, no_verify, force, silent } = config;
|
|
164
176
|
if (disable_push || disable_commit) {
|
|
165
177
|
return;
|
|
166
178
|
}
|
|
@@ -170,13 +182,17 @@ async function phase_push({ config, git }) {
|
|
|
170
182
|
}
|
|
171
183
|
}
|
|
172
184
|
else {
|
|
173
|
-
const push_confirmed = await
|
|
185
|
+
const push_confirmed = await tui_confirm({
|
|
186
|
+
question: "Do you want to push these changes?",
|
|
187
|
+
default: true,
|
|
188
|
+
style_message: ansi_blue,
|
|
189
|
+
});
|
|
174
190
|
if (!push_confirmed) {
|
|
175
191
|
return;
|
|
176
192
|
}
|
|
177
193
|
}
|
|
178
194
|
try {
|
|
179
|
-
await git_simple_staging_push_to_remote({ git, no_verify
|
|
195
|
+
await git_simple_staging_push_to_remote({ git, no_verify, force });
|
|
180
196
|
}
|
|
181
197
|
catch (error) {
|
|
182
198
|
abort_with_error(`Failed to push to remote: ${error_get_text(error)}`);
|
|
@@ -1,55 +1,55 @@
|
|
|
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 {
|
|
6
|
+
import { llm_tokens_debug_usage, llm_tokens_estimate_length_from_tokens, llm_tokens_estimate_tokens_from_length, } from "./lib_llm_tokens.js";
|
|
7
7
|
async function git_message_generate_unstructured({ llm_config, system_prompt, user_prompt, }) {
|
|
8
|
-
const
|
|
9
|
-
return
|
|
8
|
+
const outputs = await llm_chat_generate_text({ llm_config, system_prompt, user_prompt });
|
|
9
|
+
return outputs;
|
|
10
10
|
}
|
|
11
11
|
async function git_message_generate_structured({ llm_config, system_prompt, user_prompt, }) {
|
|
12
12
|
const schema = git_message_schema;
|
|
13
|
-
const
|
|
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
17
|
schema,
|
|
18
18
|
});
|
|
19
|
-
const
|
|
20
|
-
return
|
|
19
|
+
const generated_text = git_message_schema_format(generated_object);
|
|
20
|
+
return { generated_text, reasoning_text: undefined, total_usage, provider_metadata };
|
|
21
21
|
}
|
|
22
22
|
export async function git_message_generate_string({ llm_config, inputs, }) {
|
|
23
|
-
const {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const user_tokens =
|
|
27
|
-
const user_length = user_tokens
|
|
28
|
-
const user_prompt =
|
|
23
|
+
const { effective_context_window } = llm_config;
|
|
24
|
+
const { has_structured_json } = llm_config.llm_model_detail;
|
|
25
|
+
const system_prompt = git_message_prompt_get_system({ has_structured_json, inputs });
|
|
26
|
+
const user_tokens = effective_context_window - llm_tokens_estimate_tokens_from_length({ llm_config, length: system_prompt.length }) - 1000;
|
|
27
|
+
const user_length = llm_tokens_estimate_length_from_tokens({ llm_config, tokens: user_tokens });
|
|
28
|
+
const user_prompt = git_message_prompt_get_user({
|
|
29
29
|
has_structured_json,
|
|
30
30
|
inputs,
|
|
31
31
|
max_length: user_length,
|
|
32
32
|
});
|
|
33
33
|
llm_tokens_debug_usage({ name: "Inputs", llm_config, text: system_prompt + user_prompt });
|
|
34
|
-
const
|
|
34
|
+
const outputs = has_structured_json
|
|
35
35
|
? await git_message_generate_structured({ llm_config, system_prompt, user_prompt })
|
|
36
36
|
: await git_message_generate_unstructured({ llm_config, system_prompt, user_prompt });
|
|
37
|
-
llm_tokens_debug_usage({ name: "Outputs", llm_config, text:
|
|
38
|
-
return
|
|
37
|
+
llm_tokens_debug_usage({ name: "Outputs", llm_config, text: outputs.generated_text });
|
|
38
|
+
return outputs;
|
|
39
39
|
}
|
|
40
40
|
export async function git_message_generate_result({ llm_config, inputs, }) {
|
|
41
41
|
const duration = new Duration();
|
|
42
42
|
duration.start();
|
|
43
43
|
try {
|
|
44
|
-
const
|
|
44
|
+
const outputs = await git_message_generate_string({ llm_config, inputs });
|
|
45
45
|
duration.stop();
|
|
46
46
|
const seconds = duration.seconds_rounded();
|
|
47
|
-
return { llm_config, seconds,
|
|
47
|
+
return { llm_config, seconds, error_text: null, outputs };
|
|
48
48
|
}
|
|
49
49
|
catch (error) {
|
|
50
50
|
duration.stop();
|
|
51
51
|
const seconds = duration.seconds_rounded();
|
|
52
52
|
const error_text = error_get_text(error);
|
|
53
|
-
return { llm_config, seconds,
|
|
53
|
+
return { llm_config, seconds, error_text, outputs: null };
|
|
54
54
|
}
|
|
55
55
|
}
|
|
@@ -45,13 +45,13 @@ A simple change needs only two additional sentences scaling up to a complex chan
|
|
|
45
45
|
If there are a lot of changes, you will need to summarize even more.
|
|
46
46
|
`.trim() + LF_LF;
|
|
47
47
|
const portion_extra = (extra_prompts) => {
|
|
48
|
-
return extra_prompts && extra_prompts.length > 0 ? extra_prompts.map((s) => s.trim).join(LF) + LF_LF : EMPTY;
|
|
48
|
+
return extra_prompts && extra_prompts.length > 0 ? extra_prompts.map((s) => s.trim()).join(LF) + LF_LF : EMPTY;
|
|
49
49
|
};
|
|
50
50
|
const portion_final = `
|
|
51
51
|
Everything you write will be checked for validity and then saved directly to Git - it will not be reviewed by a human.
|
|
52
52
|
Therefore, you must just output the Git message itself without any introductory or concluding sections.
|
|
53
53
|
`.trim() + LF_LF;
|
|
54
|
-
export function
|
|
54
|
+
export function git_message_prompt_get_system({ has_structured_json, inputs, }) {
|
|
55
55
|
let system_prompt = EMPTY;
|
|
56
56
|
system_prompt += portion_role;
|
|
57
57
|
system_prompt += portion_inputs;
|
|
@@ -62,7 +62,7 @@ export function git_message_get_system_prompt({ has_structured_json, inputs, })
|
|
|
62
62
|
system_prompt += portion_final;
|
|
63
63
|
return system_prompt.trim();
|
|
64
64
|
}
|
|
65
|
-
export function
|
|
65
|
+
export function git_message_prompt_get_user({ has_structured_json, inputs, max_length, }) {
|
|
66
66
|
const { diffstat, diff } = inputs;
|
|
67
67
|
const truncate = diffstat.length + diff.length > max_length;
|
|
68
68
|
const diff_truncated = truncate ? diff.slice(0, max_length - diffstat.length) + LF : diff;
|
|
@@ -7,10 +7,10 @@ export const git_message_schema = z.object({
|
|
|
7
7
|
.array(z.string().describe("Another sentence giving more information about the changes."))
|
|
8
8
|
.describe("More information about the changes."),
|
|
9
9
|
});
|
|
10
|
-
export function git_message_schema_format(
|
|
10
|
+
export function git_message_schema_format(git_message_object) {
|
|
11
11
|
return [
|
|
12
|
-
|
|
12
|
+
git_message_object.summary_line,
|
|
13
13
|
EMPTY, // Empty line
|
|
14
|
-
...
|
|
14
|
+
...git_message_object.extra_lines.map((line) => (line.startsWith("- ") ? line : `- ${line}`)),
|
|
15
15
|
].join(LF);
|
|
16
16
|
}
|
|
@@ -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
|
}
|