@johnowennixon/diffdash 1.6.1 → 1.8.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 -20
- package/dist/package.json +25 -32
- package/dist/src/lib_abort.js +2 -2
- package/dist/src/lib_char_smart.js +4 -0
- package/dist/src/lib_datetime.js +4 -1
- package/dist/src/lib_debug.js +1 -0
- package/dist/src/lib_diffdash_cli.js +5 -5
- package/dist/src/lib_diffdash_config.js +35 -15
- package/dist/src/lib_diffdash_llm.js +8 -14
- package/dist/src/lib_diffdash_sequence.js +18 -18
- package/dist/src/lib_file_io.js +13 -0
- package/dist/src/lib_file_is.js +34 -0
- package/dist/src/lib_git_message_generate.js +12 -12
- package/dist/src/lib_git_message_prompt.js +25 -20
- package/dist/src/lib_git_message_schema.js +3 -3
- package/dist/src/lib_json5.js +4 -0
- package/dist/src/lib_llm_access.js +20 -54
- package/dist/src/{lib_llm_provider.js → lib_llm_api.js} +11 -29
- package/dist/src/lib_llm_chat.js +62 -24
- package/dist/src/lib_llm_config.js +9 -19
- package/dist/src/lib_llm_list.js +11 -9
- package/dist/src/lib_llm_model.js +383 -202
- package/dist/src/lib_llm_results.js +47 -0
- package/dist/src/lib_tell.js +11 -10
- package/dist/src/lib_tui_none.js +15 -0
- package/dist/src/lib_tui_number.js +22 -0
- package/dist/src/lib_tui_quote.js +26 -0
- package/dist/src/lib_tui_table.js +13 -5
- package/package.json +25 -32
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# DiffDash
|
|
2
2
|
|
|
3
|
+
[](https://deepwiki.com/johnowennixon/diffdash)
|
|
4
|
+
|
|
3
5
|
A command-line tool to generate Git commit messages using AI.
|
|
4
6
|
|
|
5
7
|
## Demonstration
|
|
@@ -8,15 +10,15 @@ A command-line tool to generate Git commit messages using AI.
|
|
|
8
10
|
|
|
9
11
|
## Features
|
|
10
12
|
|
|
11
|
-
* Generate Git commit messages in natural English
|
|
13
|
+
* Generate Git commit messages in **natural English**
|
|
12
14
|
* Add a footer to the generated commit messages
|
|
13
15
|
* Add a prefix or suffix to the summary line
|
|
14
16
|
* Select from a choice of LLM models
|
|
15
17
|
* Compare messages generated from all configured models
|
|
16
18
|
* Disable or auto-approve various stages
|
|
17
|
-
*
|
|
19
|
+
* Option to output just the commit message for use in scripts
|
|
18
20
|
* Configuration using standard API provider environment variables
|
|
19
|
-
* Uses the Vercel AI SDK
|
|
21
|
+
* Uses the Vercel AI SDK (version 5)
|
|
20
22
|
* Uses structured JSON with compatible models
|
|
21
23
|
* Substantially written using AI coding (Claude Code, Roo Code, and Amp)
|
|
22
24
|
|
|
@@ -28,24 +30,21 @@ npm install -g @johnowennixon/diffdash
|
|
|
28
30
|
|
|
29
31
|
## LLM Models
|
|
30
32
|
|
|
31
|
-
Currently, for this application, the best LLM model
|
|
33
|
+
Currently, for this application, the best LLM model is **gpt-4.1-mini** from OpenAI.
|
|
32
34
|
It is set as the default model.
|
|
33
35
|
I can only presume they have done a ton of training on diffs.
|
|
34
36
|
|
|
37
|
+
I am now testing the GPT-5 models and **gpt-5-mini-minimal** (GPT-5 Mini with reasoning disabled) is behaving much the same.
|
|
38
|
+
It will probably become the default model soon.
|
|
39
|
+
|
|
35
40
|
## API Keys
|
|
36
41
|
|
|
37
42
|
DiffDash requires at least one API key for an LLM provider. These must be provided as environment variables.
|
|
38
43
|
|
|
39
44
|
```bash
|
|
40
|
-
# For OpenAI (recommended)
|
|
45
|
+
# For OpenAI (strongly recommended)
|
|
41
46
|
export OPENAI_API_KEY=your-api-key
|
|
42
47
|
|
|
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
48
|
# For Anthropic
|
|
50
49
|
export ANTHROPIC_API_KEY=your-api-key
|
|
51
50
|
|
|
@@ -54,6 +53,9 @@ export DEEPSEEK_API_KEY=your-api-key
|
|
|
54
53
|
|
|
55
54
|
# For Google Gemini
|
|
56
55
|
export GEMINI_API_KEY=your-api-key
|
|
56
|
+
|
|
57
|
+
# For OpenRouter (all other models)
|
|
58
|
+
export OPENROUTER_API_KEY=your-api-key
|
|
57
59
|
```
|
|
58
60
|
|
|
59
61
|
## Usage
|
|
@@ -98,17 +100,14 @@ diffdash --add-suffix "(closes #123)"
|
|
|
98
100
|
# Display commit messages generated by all models
|
|
99
101
|
diffdash --llm-compare
|
|
100
102
|
|
|
101
|
-
# Use the fallback LLM model
|
|
102
|
-
diffdash --llm-fallback
|
|
103
|
-
|
|
104
103
|
# Specify the LLM model by name
|
|
105
104
|
diffdash --llm-model claude-3.5-haiku
|
|
106
105
|
|
|
107
106
|
# Just output the commit message for use in scripts
|
|
108
107
|
diffdash --just-output
|
|
109
108
|
|
|
110
|
-
# Debug
|
|
111
|
-
diffdash --debug-llm-
|
|
109
|
+
# Debug prompts
|
|
110
|
+
diffdash --debug-llm-prompts
|
|
112
111
|
```
|
|
113
112
|
|
|
114
113
|
## Command Line Arguments
|
|
@@ -133,14 +132,13 @@ All command-line arguments are optional.
|
|
|
133
132
|
| `--add-suffix SUFFIX` | add a suffix to the commit message summary line |
|
|
134
133
|
| `--llm-list` | display a list of available Large Language Models and exit |
|
|
135
134
|
| `--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
135
|
| `--llm-model MODEL` | choose the LLM model by name (the default is normally best) |
|
|
139
136
|
| `--llm-excludes MODELS` | models to exclude from comparison (comma separated) |
|
|
140
137
|
| `--just-output` | just output the commit message for use in scripts |
|
|
141
138
|
| `--silent` | suppress all normal output - errors and aborts still display |
|
|
142
|
-
| `--debug-llm-
|
|
143
|
-
| `--debug-llm-
|
|
139
|
+
| `--debug-llm-prompts` | show prompts sent to the LLM |
|
|
140
|
+
| `--debug-llm-inputs` | show inputs object sent to the LLM |
|
|
141
|
+
| `--debug-llm-outputs` | show outputs object received from the LLM |
|
|
144
142
|
|
|
145
143
|
## Files containing secrets
|
|
146
144
|
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johnowennixon/diffdash",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.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,57 +24,50 @@
|
|
|
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:
|
|
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
|
-
"lint": "run-s -ls lint:biome lint:oxlint lint:knip lint:markdownlint",
|
|
32
|
+
"fix:oxlint": "echo 'Fixing with Oxlint' && oxlint --fix",
|
|
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
|
-
"lint:
|
|
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",
|
|
40
|
+
"lint:tsgo": "echo 'Linting with tsgo' && tsgo --noEmit",
|
|
41
|
+
"lint:tsgolint": "echo 'Linting with tsgolint' && candide-tsgolint",
|
|
39
42
|
"test": "run-s -ls lint build"
|
|
40
43
|
},
|
|
41
44
|
"dependencies": {
|
|
42
|
-
"@ai-sdk/anthropic": "
|
|
43
|
-
"@ai-sdk/deepseek": "0.
|
|
44
|
-
"@ai-sdk/google": "
|
|
45
|
-
"@ai-sdk/openai": "
|
|
46
|
-
"@openrouter/ai-sdk-provider": "
|
|
47
|
-
"
|
|
48
|
-
"ai": "4.3.19",
|
|
45
|
+
"@ai-sdk/anthropic": "2.0.1",
|
|
46
|
+
"@ai-sdk/deepseek": "1.0.5",
|
|
47
|
+
"@ai-sdk/google": "2.0.4",
|
|
48
|
+
"@ai-sdk/openai": "2.0.9",
|
|
49
|
+
"@openrouter/ai-sdk-provider": "1.1.2",
|
|
50
|
+
"ai": "5.0.9",
|
|
49
51
|
"ansis": "4.1.0",
|
|
50
52
|
"argparse": "2.0.1",
|
|
51
53
|
"cli-table3": "0.6.5",
|
|
54
|
+
"json5": "2.2.3",
|
|
52
55
|
"simple-git": "3.28.0",
|
|
53
|
-
"zod": "
|
|
56
|
+
"zod": "4.0.17"
|
|
54
57
|
},
|
|
55
58
|
"devDependencies": {
|
|
56
|
-
"@biomejs/biome": "2.1.
|
|
57
|
-
"@
|
|
58
|
-
"@eslint/js": "9.31.0",
|
|
59
|
+
"@biomejs/biome": "2.1.4",
|
|
60
|
+
"@candide/tsgolint": "1.3.0",
|
|
59
61
|
"@johnowennixon/add-shebangs": "1.1.0",
|
|
60
62
|
"@johnowennixon/chmodx": "2.0.0",
|
|
61
|
-
"@stylistic/eslint-plugin": "5.2.0",
|
|
62
63
|
"@types/argparse": "2.0.17",
|
|
63
|
-
"@types/node": "24.
|
|
64
|
-
"@typescript
|
|
65
|
-
"
|
|
66
|
-
"eslint": "9.31.0",
|
|
67
|
-
"eslint-import-resolver-typescript": "4.4.4",
|
|
68
|
-
"eslint-plugin-import-x": "4.16.1",
|
|
69
|
-
"eslint-plugin-sonarjs": "3.0.4",
|
|
70
|
-
"eslint-plugin-unicorn": "59.0.1",
|
|
71
|
-
"globals": "16.3.0",
|
|
72
|
-
"knip": "5.61.3",
|
|
64
|
+
"@types/node": "24.2.1",
|
|
65
|
+
"@typescript/native-preview": "7.0.0-dev.20250811.1",
|
|
66
|
+
"knip": "5.62.0",
|
|
73
67
|
"markdownlint-cli2": "0.18.1",
|
|
74
68
|
"npm-run-all2": "8.0.4",
|
|
75
|
-
"oxlint": "1.
|
|
69
|
+
"oxlint": "1.11.1",
|
|
76
70
|
"rimraf": "6.0.1",
|
|
77
|
-
"typescript": "5.
|
|
78
|
-
"typescript-eslint": "8.37.0"
|
|
71
|
+
"typescript": "5.9.2"
|
|
79
72
|
}
|
|
80
73
|
}
|
package/dist/src/lib_abort.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ansi_red, ansi_yellow } from "./lib_ansi.js";
|
|
2
2
|
import { stdio_write_stderr_linefeed } from "./lib_stdio_write.js";
|
|
3
|
-
export function abort_exit() {
|
|
4
|
-
process.exit(
|
|
3
|
+
export function abort_exit(exitcode = 1) {
|
|
4
|
+
process.exit(exitcode);
|
|
5
5
|
}
|
|
6
6
|
export function abort_with_warning(message) {
|
|
7
7
|
if (process.stdout.isTTY) {
|
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" }),
|
|
@@ -16,8 +16,6 @@ const diffdash_cli_schema = {
|
|
|
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,
|
|
@@ -26,10 +24,12 @@ const diffdash_cli_schema = {
|
|
|
26
24
|
llm_excludes: cli_string({ help: "models to exclude from comparison (comma separated)", metavar: "MODELS" }),
|
|
27
25
|
just_output: cli_boolean({ help: "just output the commit message for use in scripts" }),
|
|
28
26
|
silent: cli_boolean({ help: "suppress all normal output - errors and aborts still display" }),
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
debug_llm_prompts: cli_boolean({ help: "debug prompts sent to the LLM" }),
|
|
28
|
+
debug_llm_inputs: cli_boolean({ help: "debug inputs object sent to the LLM" }),
|
|
29
|
+
debug_llm_outputs: cli_boolean({ help: "debug outputs object received from the LLM" }),
|
|
31
30
|
};
|
|
32
31
|
export const diffdash_cli_parser = cli_make_parser({
|
|
33
32
|
cli_schema: diffdash_cli_schema,
|
|
34
33
|
description: "DiffDash - generate Git commit messages using AI",
|
|
35
34
|
});
|
|
35
|
+
export const diffdash_cli_parsed_args = diffdash_cli_parser.parsed_args;
|
|
@@ -1,13 +1,39 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { abort_with_error } from "./lib_abort.js";
|
|
1
3
|
import { debug_channels, debug_inspect_when } from "./lib_debug.js";
|
|
2
|
-
import {
|
|
3
|
-
import { diffdash_llm_model_details
|
|
4
|
+
import { diffdash_cli_parsed_args } from "./lib_diffdash_cli.js";
|
|
5
|
+
import { diffdash_llm_model_details } from "./lib_diffdash_llm.js";
|
|
6
|
+
import { file_io_read_text } from "./lib_file_io.js";
|
|
7
|
+
import { file_is_file } from "./lib_file_is.js";
|
|
8
|
+
import { json5_parse } from "./lib_json5.js";
|
|
4
9
|
import { llm_config_get, llm_config_get_all } from "./lib_llm_config.js";
|
|
5
10
|
import { llm_list_models } from "./lib_llm_list.js";
|
|
6
11
|
import { PACKAGE_NAME, PACKAGE_VERSION } from "./lib_package.js";
|
|
7
12
|
import { tell_plain } from "./lib_tell.js";
|
|
13
|
+
import { tui_quote_smart_single } from "./lib_tui_quote.js";
|
|
14
|
+
const diffdash_config_file_schema = z
|
|
15
|
+
.object({
|
|
16
|
+
extra_prompts: z.string().array().optional(),
|
|
17
|
+
})
|
|
18
|
+
.strict();
|
|
19
|
+
function diffdash_config_file_read(config) {
|
|
20
|
+
const config_file_name = ".diffdash.json5";
|
|
21
|
+
if (!file_is_file(config_file_name)) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const config_content = file_io_read_text(config_file_name);
|
|
25
|
+
const parsed_json = json5_parse(config_content);
|
|
26
|
+
const validation_result = diffdash_config_file_schema.safeParse(parsed_json);
|
|
27
|
+
if (!validation_result.success) {
|
|
28
|
+
abort_with_error(`Unable to parse DiffDash config file: ${tui_quote_smart_single(config_file_name)}`);
|
|
29
|
+
}
|
|
30
|
+
const data = validation_result.data;
|
|
31
|
+
if (data.extra_prompts) {
|
|
32
|
+
config.extra_prompts = data.extra_prompts;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
8
35
|
export function diffdash_config_get() {
|
|
9
|
-
const
|
|
10
|
-
const { version, auto_add, auto_commit, auto_push, disable_add, disable_commit, disable_preview, disable_status, disable_push, push_no_verify, push_force, add_prefix, add_suffix, llm_list, llm_compare, llm_router, llm_fallback, llm_model, llm_excludes, just_output, silent, debug_llm_inputs, debug_llm_outputs, } = pa;
|
|
36
|
+
const { version, auto_add, auto_commit, auto_push, disable_add, disable_commit, disable_preview, disable_status, disable_push, push_no_verify, push_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;
|
|
11
37
|
if (version) {
|
|
12
38
|
tell_plain(`${PACKAGE_NAME} v${PACKAGE_VERSION}`);
|
|
13
39
|
process.exit(0);
|
|
@@ -16,17 +42,9 @@ export function diffdash_config_get() {
|
|
|
16
42
|
llm_list_models({ llm_model_details: diffdash_llm_model_details });
|
|
17
43
|
process.exit(0);
|
|
18
44
|
}
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
llm_model_name,
|
|
23
|
-
llm_router,
|
|
24
|
-
});
|
|
25
|
-
const all_llm_configs = llm_config_get_all({
|
|
26
|
-
llm_model_details: diffdash_llm_model_details,
|
|
27
|
-
llm_router,
|
|
28
|
-
llm_excludes,
|
|
29
|
-
});
|
|
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 });
|
|
47
|
+
debug_channels.llm_prompts = debug_llm_prompts;
|
|
30
48
|
debug_channels.llm_inputs = debug_llm_inputs;
|
|
31
49
|
debug_channels.llm_outputs = debug_llm_outputs;
|
|
32
50
|
const config = {
|
|
@@ -47,7 +65,9 @@ export function diffdash_config_get() {
|
|
|
47
65
|
all_llm_configs,
|
|
48
66
|
just_output,
|
|
49
67
|
silent,
|
|
68
|
+
extra_prompts: undefined,
|
|
50
69
|
};
|
|
70
|
+
diffdash_config_file_read(config);
|
|
51
71
|
debug_inspect_when(debug_channels.config, config, "config");
|
|
52
72
|
return config;
|
|
53
73
|
}
|
|
@@ -1,25 +1,19 @@
|
|
|
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
|
-
"mistral-medium-3",
|
|
20
|
-
"qwen3-235b-a22b",
|
|
11
|
+
"gpt-5-mini",
|
|
12
|
+
"gpt-5-mini-minimal", // fallback
|
|
13
|
+
"gpt-5-nano",
|
|
14
|
+
"gpt-5-nano-minimal",
|
|
15
|
+
"llama-4-maverick@cerebras",
|
|
21
16
|
];
|
|
22
17
|
export const diffdash_llm_model_details = llm_model_get_details({ llm_model_names: model_name_options });
|
|
23
|
-
export const diffdash_llm_model_choices = llm_model_get_choices(diffdash_llm_model_details);
|
|
18
|
+
export const diffdash_llm_model_choices = llm_model_get_choices({ llm_model_details: diffdash_llm_model_details });
|
|
24
19
|
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;
|
|
@@ -7,7 +7,7 @@ import { git_message_generate_result } from "./lib_git_message_generate.js";
|
|
|
7
7
|
import { git_message_validate_check, git_message_validate_get_result } from "./lib_git_message_validate.js";
|
|
8
8
|
import { git_simple_open_check_not_bare, git_simple_open_git_repo } from "./lib_git_simple_open.js";
|
|
9
9
|
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 {
|
|
10
|
+
import { llm_results_summary } from "./lib_llm_results.js";
|
|
11
11
|
import { stdio_write_stdout, stdio_write_stdout_linefeed } from "./lib_stdio_write.js";
|
|
12
12
|
import { tell_action, tell_info, tell_plain, tell_success, tell_warning } from "./lib_tell.js";
|
|
13
13
|
import { tui_justify_left } from "./lib_tui_justify.js";
|
|
@@ -90,46 +90,46 @@ async function phase_compare({ config, git }) {
|
|
|
90
90
|
if (!silent) {
|
|
91
91
|
tell_action("Generating Git commit messages using all models in parallel");
|
|
92
92
|
}
|
|
93
|
-
const { all_llm_configs, add_prefix, add_suffix } = config;
|
|
93
|
+
const { all_llm_configs, add_prefix, add_suffix, extra_prompts } = config;
|
|
94
94
|
const diffstat = await git_simple_staging_get_staged_diffstat(git);
|
|
95
95
|
const diff = await git_simple_staging_get_staged_diff(git);
|
|
96
|
-
const inputs = { diffstat, diff };
|
|
96
|
+
const inputs = { diffstat, diff, extra_prompts };
|
|
97
97
|
const result_promises = all_llm_configs.map((llm_config) => git_message_generate_result({ llm_config, inputs }));
|
|
98
98
|
const all_results = await Promise.all(result_promises);
|
|
99
99
|
for (const result of all_results) {
|
|
100
100
|
const { llm_config, seconds, error_text } = result;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
tell_warning(`Failed to generate a commit message in ${seconds} seconds using ${model_via}: ${error_text}`);
|
|
101
|
+
const { llm_model_name } = llm_config;
|
|
102
|
+
if (error_text !== null) {
|
|
103
|
+
tell_warning(`Failed to generate a commit message in ${seconds} seconds using ${llm_model_name}: ${error_text}`);
|
|
105
104
|
continue;
|
|
106
105
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
tell_info(`Git commit message in ${seconds} seconds using ${model_via}:`);
|
|
106
|
+
tell_info(`Git commit message in ${seconds} seconds using ${llm_model_name}:`);
|
|
107
|
+
const { outputs } = result;
|
|
108
|
+
let { generated_text: git_message } = outputs;
|
|
111
109
|
const validation_result = git_message_validate_get_result(git_message);
|
|
112
110
|
const teller = validation_result.valid ? tell_plain : tell_warning;
|
|
113
111
|
git_message = diffdash_add_prefix_or_suffix({ git_message, add_prefix, add_suffix });
|
|
114
112
|
git_message = diffdash_add_footer({ git_message, llm_config });
|
|
115
113
|
git_message_display({ git_message, teller });
|
|
116
114
|
}
|
|
115
|
+
llm_results_summary(all_results);
|
|
117
116
|
}
|
|
118
117
|
async function phase_generate({ config, git }) {
|
|
119
|
-
const { disable_preview, add_prefix, add_suffix, llm_config, just_output, silent } = config;
|
|
120
|
-
const
|
|
118
|
+
const { disable_preview, add_prefix, add_suffix, llm_config, just_output, silent, extra_prompts } = config;
|
|
119
|
+
const { llm_model_name } = llm_config;
|
|
121
120
|
if (!silent && !just_output) {
|
|
122
|
-
tell_action(`Generating the Git commit message using ${
|
|
121
|
+
tell_action(`Generating the Git commit message using ${llm_model_name}`);
|
|
123
122
|
}
|
|
124
123
|
const diffstat = await git_simple_staging_get_staged_diffstat(git);
|
|
125
124
|
const diff = await git_simple_staging_get_staged_diff(git);
|
|
126
|
-
const inputs = { diffstat, diff };
|
|
125
|
+
const inputs = { diffstat, diff, extra_prompts };
|
|
127
126
|
const result = await git_message_generate_result({ llm_config, inputs });
|
|
128
127
|
const { error_text } = result;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
abort_with_error(`Failed to generate a commit message using ${model_via}: ${error_text}`);
|
|
128
|
+
if (error_text !== null) {
|
|
129
|
+
abort_with_error(`Failed to generate a commit message using ${llm_model_name}: ${error_text}`);
|
|
132
130
|
}
|
|
131
|
+
const { outputs } = result;
|
|
132
|
+
let { generated_text: git_message } = outputs;
|
|
133
133
|
git_message_validate_check(git_message);
|
|
134
134
|
git_message = diffdash_add_prefix_or_suffix({ git_message, add_prefix, add_suffix });
|
|
135
135
|
git_message = diffdash_add_footer({ git_message, llm_config });
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
export function file_io_read_binary(file_path) {
|
|
3
|
+
return fs.readFileSync(file_path);
|
|
4
|
+
}
|
|
5
|
+
export function file_io_read_text(file_path) {
|
|
6
|
+
return fs.readFileSync(file_path, { encoding: "utf8" });
|
|
7
|
+
}
|
|
8
|
+
export function file_io_write_binary({ file_path, data }) {
|
|
9
|
+
fs.writeFileSync(file_path, data);
|
|
10
|
+
}
|
|
11
|
+
export function file_io_write_text({ file_path, text }) {
|
|
12
|
+
fs.writeFileSync(file_path, text, { encoding: "utf8" });
|
|
13
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
export function file_is_dir(dir_path) {
|
|
3
|
+
try {
|
|
4
|
+
return fs.statSync(dir_path).isDirectory();
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function file_is_file(file_path) {
|
|
11
|
+
try {
|
|
12
|
+
return fs.statSync(file_path).isFile();
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function file_is_socket(file_path) {
|
|
19
|
+
try {
|
|
20
|
+
return fs.statSync(file_path).isSocket();
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function file_is_executable(file_path) {
|
|
27
|
+
try {
|
|
28
|
+
fs.accessSync(file_path, fs.constants.X_OK);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -5,23 +5,23 @@ import { git_message_schema, git_message_schema_format } from "./lib_git_message
|
|
|
5
5
|
import { llm_chat_generate_object, llm_chat_generate_text } from "./lib_llm_chat.js";
|
|
6
6
|
import { llm_tokens_count_estimated, llm_tokens_debug_usage } 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
23
|
const { context_window, has_structured_json } = llm_config.llm_model_detail;
|
|
24
|
-
const system_prompt = git_message_get_system_prompt({ has_structured_json });
|
|
24
|
+
const system_prompt = git_message_get_system_prompt({ has_structured_json, inputs });
|
|
25
25
|
// Estimate remaining prompt length
|
|
26
26
|
const user_tokens = context_window - llm_tokens_count_estimated({ llm_config, text: system_prompt }) - 1000;
|
|
27
27
|
const user_length = user_tokens * 3;
|
|
@@ -31,25 +31,25 @@ export async function git_message_generate_string({ llm_config, 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
|
}
|