gitxplain 0.1.8 → 0.1.9
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 +190 -4
- package/cli/index.js +430 -117
- package/cli/services/aiService.js +234 -28
- package/cli/services/cacheService.js +92 -1
- package/cli/services/clipboardService.js +6 -1
- package/cli/services/colorSupport.js +31 -0
- package/cli/services/commitService.js +105 -23
- package/cli/services/configService.js +18 -2
- package/cli/services/envLoader.js +2 -2
- package/cli/services/gitService.js +369 -23
- package/cli/services/hookService.js +36 -4
- package/cli/services/mergeService.js +33 -27
- package/cli/services/outputFormatter.js +23 -73
- package/cli/services/pipelineService.js +112 -0
- package/cli/services/promptService.js +8 -1
- package/cli/services/splitService.js +1 -21
- package/cli/services/usageService.js +158 -0
- package/package.json +2 -2
- package/prompts/blame.txt +29 -0
- package/prompts/changelog.txt +36 -0
- package/prompts/conflict.txt +33 -0
- package/prompts/pr-description.txt +40 -0
- package/prompts/refactor.txt +29 -0
- package/prompts/stash.txt +34 -0
- package/prompts/test-suggest.txt +29 -0
- package/IMPLEMENTATION.md +0 -225
- package/cli/services/chatService.js +0 -683
- package/cli/services/gitConnectionService.js +0 -267
|
@@ -1,34 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
const ANSI = {
|
|
4
|
-
reset: "\u001b[0m",
|
|
5
|
-
bold: "\u001b[1m",
|
|
6
|
-
cyan: "\u001b[36m",
|
|
7
|
-
yellow: "\u001b[33m",
|
|
8
|
-
green: "\u001b[32m",
|
|
9
|
-
red: "\u001b[31m",
|
|
10
|
-
gray: "\u001b[90m"
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
function supportsColor() {
|
|
14
|
-
if (process.env.FORCE_COLOR != null && process.env.FORCE_COLOR !== "0") {
|
|
15
|
-
return true;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (process.env.NO_COLOR != null) {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return Boolean(process.stdout?.isTTY);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function colorize(text, color) {
|
|
26
|
-
if (!supportsColor()) {
|
|
27
|
-
return text;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return `${color}${text}${ANSI.reset}`;
|
|
31
|
-
}
|
|
1
|
+
import { ANSI, colorize } from "./colorSupport.js";
|
|
32
2
|
|
|
33
3
|
function stripInlineMarkdown(text) {
|
|
34
4
|
return text
|
|
@@ -84,61 +54,37 @@ function normalizeMarkdownLine(line, state) {
|
|
|
84
54
|
}
|
|
85
55
|
|
|
86
56
|
function formatTargetLabel(commitData) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
function normalizeHeading(line) {
|
|
91
|
-
const match = line.match(/^([0-9]+\.)?\s*(Summary|Issues? Fixed|Issue|Root Cause|Fix(?: Explanation)?|Impact|Risk Level|Severity|Technical Breakdown|Full Analysis|Line-by-Line Code Walkthrough|Code Review|Security Review|Security Findings|Review Findings|Suggestions|Recommended Mitigations)\s*:?\s*$/i);
|
|
92
|
-
|
|
93
|
-
if (!match) {
|
|
94
|
-
return null;
|
|
57
|
+
if (commitData.analysisType === "range") {
|
|
58
|
+
return "Range";
|
|
95
59
|
}
|
|
96
60
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
function isFileHeading(line) {
|
|
101
|
-
return /^(?:File|Path)\s*:/i.test(line) || /^[A-Za-z0-9_./-]+\.[A-Za-z0-9]+:\s*$/.test(line);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function classifyTone(line) {
|
|
105
|
-
if (/^\s*low(?:\b|[.:])/i.test(line)) {
|
|
106
|
-
return "good";
|
|
61
|
+
if (commitData.analysisType === "blame") {
|
|
62
|
+
return "File";
|
|
107
63
|
}
|
|
108
64
|
|
|
109
|
-
if (
|
|
110
|
-
return "
|
|
65
|
+
if (commitData.analysisType === "stash") {
|
|
66
|
+
return "Stash";
|
|
111
67
|
}
|
|
112
68
|
|
|
113
|
-
if (
|
|
114
|
-
return "
|
|
69
|
+
if (commitData.analysisType === "conflict") {
|
|
70
|
+
return "Conflict";
|
|
115
71
|
}
|
|
116
72
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
line
|
|
120
|
-
)
|
|
121
|
-
) {
|
|
122
|
-
return "good";
|
|
123
|
-
}
|
|
73
|
+
return "Commit";
|
|
74
|
+
}
|
|
124
75
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
line
|
|
128
|
-
)
|
|
129
|
-
) {
|
|
130
|
-
return "bad";
|
|
131
|
-
}
|
|
76
|
+
function normalizeHeading(line) {
|
|
77
|
+
const match = line.match(/^([0-9]+\.)?\s*(Summary|Issues? Fixed|Issue|Root Cause|Fix(?: Explanation)?|Impact|Risk Level|Severity|Technical Breakdown|Full Analysis|Line-by-Line Code Walkthrough|Code Review|Security Review|Security Findings|Review Findings|Suggestions|Recommended Mitigations)\s*:?\s*$/i);
|
|
132
78
|
|
|
133
|
-
if (
|
|
134
|
-
return
|
|
79
|
+
if (!match) {
|
|
80
|
+
return null;
|
|
135
81
|
}
|
|
136
82
|
|
|
137
|
-
return
|
|
83
|
+
return `${match[2]}:`;
|
|
138
84
|
}
|
|
139
85
|
|
|
140
|
-
function
|
|
141
|
-
return line;
|
|
86
|
+
function isFileHeading(line) {
|
|
87
|
+
return /^(?:File|Path)\s*:/i.test(line) || /^[A-Za-z0-9_./-]+\.[A-Za-z0-9]+:\s*$/.test(line);
|
|
142
88
|
}
|
|
143
89
|
|
|
144
90
|
function formatBulletLine(line) {
|
|
@@ -189,7 +135,7 @@ function formatLine(line) {
|
|
|
189
135
|
return severityLine;
|
|
190
136
|
}
|
|
191
137
|
|
|
192
|
-
return
|
|
138
|
+
return line;
|
|
193
139
|
}
|
|
194
140
|
|
|
195
141
|
function formatExplanation(explanation) {
|
|
@@ -266,6 +212,10 @@ export function formatFooter({ responseMeta, promptMeta, options }) {
|
|
|
266
212
|
lines.push(`Usage: ${JSON.stringify(responseMeta.usage)}`);
|
|
267
213
|
}
|
|
268
214
|
|
|
215
|
+
if (responseMeta.estimatedCostUsd != null) {
|
|
216
|
+
lines.push(`Estimated Cost: $${responseMeta.estimatedCostUsd.toFixed(6)}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
269
219
|
if (promptMeta?.warnings?.length) {
|
|
270
220
|
lines.push(...promptMeta.warnings);
|
|
271
221
|
}
|
|
@@ -414,6 +414,24 @@ export function inspectRepositoryForPipeline(cwd) {
|
|
|
414
414
|
label: "GitHub Actions CI verification",
|
|
415
415
|
description: "Runs install, lint, test, build, and package checks when supported.",
|
|
416
416
|
files: [".github/workflows/ci.yml"]
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
id: "gitlab-ci",
|
|
420
|
+
label: "GitLab CI verification",
|
|
421
|
+
description: "Creates a .gitlab-ci.yml pipeline with install, lint, test, and build stages.",
|
|
422
|
+
files: [".gitlab-ci.yml"]
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
id: "circleci",
|
|
426
|
+
label: "CircleCI verification",
|
|
427
|
+
description: "Creates a .circleci/config.yml pipeline for verification jobs.",
|
|
428
|
+
files: [".circleci/config.yml"]
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
id: "bitbucket-pipelines",
|
|
432
|
+
label: "Bitbucket Pipelines verification",
|
|
433
|
+
description: "Creates bitbucket-pipelines.yml with install, test, and build steps.",
|
|
434
|
+
files: ["bitbucket-pipelines.yml"]
|
|
417
435
|
}
|
|
418
436
|
];
|
|
419
437
|
|
|
@@ -676,6 +694,87 @@ export function buildContainerWorkflow() {
|
|
|
676
694
|
].join("\n").concat("\n");
|
|
677
695
|
}
|
|
678
696
|
|
|
697
|
+
function buildPipelineCommands(context) {
|
|
698
|
+
return [context.commands.install, context.commands.lint, context.commands.test, context.commands.build, context.commands.pack]
|
|
699
|
+
.filter(Boolean);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
export function buildGitLabCiWorkflow(context) {
|
|
703
|
+
const commands = buildPipelineCommands(context);
|
|
704
|
+
const image =
|
|
705
|
+
context.type === "python"
|
|
706
|
+
? "python:3.12"
|
|
707
|
+
: context.type === "go"
|
|
708
|
+
? "golang:1.22"
|
|
709
|
+
: context.type === "rust"
|
|
710
|
+
? "rust:latest"
|
|
711
|
+
: "node:20";
|
|
712
|
+
|
|
713
|
+
return [
|
|
714
|
+
`image: ${image}`,
|
|
715
|
+
"",
|
|
716
|
+
"stages:",
|
|
717
|
+
" - verify",
|
|
718
|
+
"",
|
|
719
|
+
"verify:",
|
|
720
|
+
" stage: verify",
|
|
721
|
+
" script:",
|
|
722
|
+
...commands.map((command) => ` - ${command}`)
|
|
723
|
+
].join("\n").concat("\n");
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
export function buildCircleCiWorkflow(context) {
|
|
727
|
+
const image =
|
|
728
|
+
context.type === "python"
|
|
729
|
+
? "cimg/python:3.12"
|
|
730
|
+
: context.type === "go"
|
|
731
|
+
? "cimg/go:1.22"
|
|
732
|
+
: context.type === "rust"
|
|
733
|
+
? "cimg/rust:1.83"
|
|
734
|
+
: "cimg/node:20.10";
|
|
735
|
+
const commands = buildPipelineCommands(context);
|
|
736
|
+
|
|
737
|
+
return [
|
|
738
|
+
"version: 2.1",
|
|
739
|
+
"",
|
|
740
|
+
"jobs:",
|
|
741
|
+
" verify:",
|
|
742
|
+
" docker:",
|
|
743
|
+
` - image: ${image}`,
|
|
744
|
+
" steps:",
|
|
745
|
+
" - checkout",
|
|
746
|
+
...commands.map((command) => ` - run: ${command}`),
|
|
747
|
+
"",
|
|
748
|
+
"workflows:",
|
|
749
|
+
" verify:",
|
|
750
|
+
" jobs:",
|
|
751
|
+
" - verify"
|
|
752
|
+
].join("\n").concat("\n");
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
export function buildBitbucketPipelinesWorkflow(context) {
|
|
756
|
+
const image =
|
|
757
|
+
context.type === "python"
|
|
758
|
+
? "python:3.12"
|
|
759
|
+
: context.type === "go"
|
|
760
|
+
? "golang:1.22"
|
|
761
|
+
: context.type === "rust"
|
|
762
|
+
? "rust:latest"
|
|
763
|
+
: "node:20";
|
|
764
|
+
const commands = buildPipelineCommands(context);
|
|
765
|
+
|
|
766
|
+
return [
|
|
767
|
+
`image: ${image}`,
|
|
768
|
+
"",
|
|
769
|
+
"pipelines:",
|
|
770
|
+
" default:",
|
|
771
|
+
" - step:",
|
|
772
|
+
" name: Verify",
|
|
773
|
+
" script:",
|
|
774
|
+
...commands.map((command) => ` - ${command}`)
|
|
775
|
+
].join("\n").concat("\n");
|
|
776
|
+
}
|
|
777
|
+
|
|
679
778
|
export function writePipelineFiles(cwd, analysis, selection) {
|
|
680
779
|
if (!analysis.supported) {
|
|
681
780
|
throw new Error(analysis.reason);
|
|
@@ -689,6 +788,7 @@ export function writePipelineFiles(cwd, analysis, selection) {
|
|
|
689
788
|
|
|
690
789
|
const writeWorkflow = (relativePath, contents) => {
|
|
691
790
|
const absolutePath = path.join(cwd, relativePath);
|
|
791
|
+
mkdirSync(path.dirname(absolutePath), { recursive: true });
|
|
692
792
|
writeFileSync(absolutePath, contents, "utf8");
|
|
693
793
|
writtenFiles.push(relativePath);
|
|
694
794
|
};
|
|
@@ -713,6 +813,18 @@ export function writePipelineFiles(cwd, analysis, selection) {
|
|
|
713
813
|
writeWorkflow(".github/workflows/container.yml", buildContainerWorkflow());
|
|
714
814
|
}
|
|
715
815
|
|
|
816
|
+
if (selection.id === "gitlab-ci") {
|
|
817
|
+
writeWorkflow(".gitlab-ci.yml", buildGitLabCiWorkflow(analysis.primary));
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (selection.id === "circleci") {
|
|
821
|
+
writeWorkflow(".circleci/config.yml", buildCircleCiWorkflow(analysis.primary));
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (selection.id === "bitbucket-pipelines") {
|
|
825
|
+
writeWorkflow("bitbucket-pipelines.yml", buildBitbucketPipelinesWorkflow(analysis.primary));
|
|
826
|
+
}
|
|
827
|
+
|
|
716
828
|
if (selection.id === "container" && !selection.files.includes(".github/workflows/ci.yml")) {
|
|
717
829
|
notes.push("This option only creates the container workflow. Run `gitxplain --pipeline` again if you also want CI verification.");
|
|
718
830
|
}
|
|
@@ -16,7 +16,14 @@ const PROMPT_FILES = {
|
|
|
16
16
|
review: "review.txt",
|
|
17
17
|
security: "security.txt",
|
|
18
18
|
split: "split.txt",
|
|
19
|
-
commit: "commit.txt"
|
|
19
|
+
commit: "commit.txt",
|
|
20
|
+
changelog: "changelog.txt",
|
|
21
|
+
refactor: "refactor.txt",
|
|
22
|
+
"test-suggest": "test-suggest.txt",
|
|
23
|
+
"pr-description": "pr-description.txt",
|
|
24
|
+
blame: "blame.txt",
|
|
25
|
+
stash: "stash.txt",
|
|
26
|
+
conflict: "conflict.txt"
|
|
20
27
|
};
|
|
21
28
|
|
|
22
29
|
function fillTemplate(template, values) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import process from "node:process";
|
|
2
1
|
import {
|
|
3
2
|
createCommitFromTree,
|
|
4
3
|
deletePaths,
|
|
@@ -32,26 +31,7 @@ import {
|
|
|
32
31
|
runGitCommandUnchecked,
|
|
33
32
|
resolveCommitSha
|
|
34
33
|
} from "./gitService.js";
|
|
35
|
-
|
|
36
|
-
const ANSI = {
|
|
37
|
-
reset: "\u001b[0m",
|
|
38
|
-
bold: "\u001b[1m",
|
|
39
|
-
cyan: "\u001b[36m",
|
|
40
|
-
yellow: "\u001b[33m",
|
|
41
|
-
green: "\u001b[32m"
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
function supportsColor() {
|
|
45
|
-
return Boolean(process.stdout?.isTTY) && process.env.NO_COLOR == null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function colorize(text, color) {
|
|
49
|
-
if (!supportsColor()) {
|
|
50
|
-
return text;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return `${color}${text}${ANSI.reset}`;
|
|
54
|
-
}
|
|
34
|
+
import { ANSI, colorize } from "./colorSupport.js";
|
|
55
35
|
|
|
56
36
|
function extractJsonPayload(explanation) {
|
|
57
37
|
const fencedMatch = explanation.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
function getUsageLogPath() {
|
|
6
|
+
return path.join(os.homedir(), ".gitxplain", "usage.jsonl");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getUsageLogFile() {
|
|
10
|
+
return getUsageLogPath();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function readRecords() {
|
|
14
|
+
const filePath = getUsageLogPath();
|
|
15
|
+
if (!existsSync(filePath)) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return readFileSync(filePath, "utf8")
|
|
20
|
+
.split("\n")
|
|
21
|
+
.map((line) => line.trim())
|
|
22
|
+
.filter(Boolean)
|
|
23
|
+
.map((line) => JSON.parse(line));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function parseNumeric(value) {
|
|
27
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function normalizeUsageMetrics(usage) {
|
|
31
|
+
if (!usage || typeof usage !== "object") {
|
|
32
|
+
return {
|
|
33
|
+
inputTokens: 0,
|
|
34
|
+
outputTokens: 0,
|
|
35
|
+
totalTokens: 0
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const inputTokens =
|
|
40
|
+
parseNumeric(usage.prompt_tokens) ||
|
|
41
|
+
parseNumeric(usage.input_tokens) ||
|
|
42
|
+
parseNumeric(usage.promptTokenCount);
|
|
43
|
+
const outputTokens =
|
|
44
|
+
parseNumeric(usage.completion_tokens) ||
|
|
45
|
+
parseNumeric(usage.output_tokens) ||
|
|
46
|
+
parseNumeric(usage.candidatesTokenCount);
|
|
47
|
+
const totalTokens =
|
|
48
|
+
parseNumeric(usage.total_tokens) ||
|
|
49
|
+
parseNumeric(usage.totalTokenCount) ||
|
|
50
|
+
inputTokens + outputTokens;
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
inputTokens,
|
|
54
|
+
outputTokens,
|
|
55
|
+
totalTokens
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parseEnvPrice(envKey) {
|
|
60
|
+
const raw = process.env[envKey];
|
|
61
|
+
if (raw == null || raw === "") {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const parsed = Number.parseFloat(raw);
|
|
66
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function resolvePricing(config) {
|
|
70
|
+
const providerKey = config.provider.toUpperCase().replace(/[^A-Z0-9]+/g, "_");
|
|
71
|
+
const modelKey = String(config.model ?? "default").toUpperCase().replace(/[^A-Z0-9]+/g, "_");
|
|
72
|
+
|
|
73
|
+
const inputPerMillion =
|
|
74
|
+
parseEnvPrice(`${providerKey}_${modelKey}_INPUT_COST_PER_MTOK`) ??
|
|
75
|
+
parseEnvPrice(`${providerKey}_INPUT_COST_PER_MTOK`) ??
|
|
76
|
+
parseEnvPrice("LLM_INPUT_COST_PER_MTOK");
|
|
77
|
+
const outputPerMillion =
|
|
78
|
+
parseEnvPrice(`${providerKey}_${modelKey}_OUTPUT_COST_PER_MTOK`) ??
|
|
79
|
+
parseEnvPrice(`${providerKey}_OUTPUT_COST_PER_MTOK`) ??
|
|
80
|
+
parseEnvPrice("LLM_OUTPUT_COST_PER_MTOK");
|
|
81
|
+
|
|
82
|
+
if (inputPerMillion == null || outputPerMillion == null) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
inputPerMillion,
|
|
88
|
+
outputPerMillion
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function estimateCostUsd(usage, pricing) {
|
|
93
|
+
if (!pricing) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const metrics = normalizeUsageMetrics(usage);
|
|
98
|
+
const costUsd =
|
|
99
|
+
(metrics.inputTokens / 1_000_000) * pricing.inputPerMillion +
|
|
100
|
+
(metrics.outputTokens / 1_000_000) * pricing.outputPerMillion;
|
|
101
|
+
|
|
102
|
+
return Number.isFinite(costUsd) ? costUsd : null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function appendUsageRecord({ provider, model, usage, latencyMs, estimatedCostUsd }) {
|
|
106
|
+
const metrics = normalizeUsageMetrics(usage);
|
|
107
|
+
if (metrics.totalTokens === 0 && estimatedCostUsd == null) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const filePath = getUsageLogPath();
|
|
112
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
113
|
+
appendFileSync(
|
|
114
|
+
filePath,
|
|
115
|
+
`${JSON.stringify({
|
|
116
|
+
timestamp: new Date().toISOString(),
|
|
117
|
+
provider,
|
|
118
|
+
model,
|
|
119
|
+
usage: metrics,
|
|
120
|
+
latencyMs: latencyMs ?? null,
|
|
121
|
+
estimatedCostUsd
|
|
122
|
+
})}\n`,
|
|
123
|
+
"utf8"
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function getUsageStats() {
|
|
128
|
+
const records = readRecords();
|
|
129
|
+
|
|
130
|
+
return records.reduce(
|
|
131
|
+
(summary, record) => {
|
|
132
|
+
summary.requestCount += 1;
|
|
133
|
+
summary.inputTokens += parseNumeric(record.usage?.inputTokens);
|
|
134
|
+
summary.outputTokens += parseNumeric(record.usage?.outputTokens);
|
|
135
|
+
summary.totalTokens += parseNumeric(record.usage?.totalTokens);
|
|
136
|
+
summary.estimatedCostUsd += parseNumeric(record.estimatedCostUsd);
|
|
137
|
+
return summary;
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
requestCount: 0,
|
|
141
|
+
inputTokens: 0,
|
|
142
|
+
outputTokens: 0,
|
|
143
|
+
totalTokens: 0,
|
|
144
|
+
estimatedCostUsd: 0
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function clearUsageLog() {
|
|
150
|
+
const filePath = getUsageLogPath();
|
|
151
|
+
const count = readRecords().length;
|
|
152
|
+
|
|
153
|
+
if (existsSync(filePath)) {
|
|
154
|
+
rmSync(filePath, { force: true });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return count;
|
|
158
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitxplain",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "AI-powered Git commit explainer CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node ./cli/index.js",
|
|
12
|
-
"lint": "node --check ./cli/index.js && node --check ./cli/services/
|
|
12
|
+
"lint": "node --check ./cli/index.js && node --check ./cli/services/envLoader.js && node --check ./cli/services/pipelineService.js",
|
|
13
13
|
"test": "node --test"
|
|
14
14
|
},
|
|
15
15
|
"keywords": [
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Analyze this git blame report for a file and explain ownership patterns.
|
|
2
|
+
|
|
3
|
+
Context:
|
|
4
|
+
- The input below is not a diff. It is a compact blame summary built from `git blame --line-porcelain`.
|
|
5
|
+
- Focus on who changed which parts of the file, when the major edits happened, and what kinds of changes appear to have shaped the file.
|
|
6
|
+
- Call out concentrated ownership, legacy areas, and spots that may need extra care during onboarding or refactoring.
|
|
7
|
+
|
|
8
|
+
Commit Message:
|
|
9
|
+
{{commit_message}}
|
|
10
|
+
|
|
11
|
+
Files Changed:
|
|
12
|
+
{{files_changed}}
|
|
13
|
+
|
|
14
|
+
Stats:
|
|
15
|
+
{{stats}}
|
|
16
|
+
|
|
17
|
+
Blame Report:
|
|
18
|
+
{{diff}}
|
|
19
|
+
|
|
20
|
+
Return:
|
|
21
|
+
|
|
22
|
+
1. Ownership Summary:
|
|
23
|
+
- Summarize the main authors or time periods that shaped the file
|
|
24
|
+
|
|
25
|
+
2. Likely Change Themes:
|
|
26
|
+
- Explain what kinds of changes the major contributors appear to have made
|
|
27
|
+
|
|
28
|
+
3. Onboarding Notes:
|
|
29
|
+
- Suggest where a new maintainer should read carefully, who to ask first, or what parts look risky or stable
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Generate release notes in a conventional-changelog style.
|
|
2
|
+
|
|
3
|
+
Context:
|
|
4
|
+
- This may represent a single commit or a range of commits.
|
|
5
|
+
- Prefer grouping entries under headings like Features, Fixes, Refactors, Docs, Tests, Chores, and Breaking Changes when supported by the evidence.
|
|
6
|
+
- Use commit messages first, then use the diff to sharpen unclear items.
|
|
7
|
+
- Be conservative and do not invent user-facing changes that are not visible in the Git data.
|
|
8
|
+
|
|
9
|
+
Commit Message:
|
|
10
|
+
{{commit_message}}
|
|
11
|
+
|
|
12
|
+
Files Changed:
|
|
13
|
+
{{files_changed}}
|
|
14
|
+
|
|
15
|
+
Stats:
|
|
16
|
+
{{stats}}
|
|
17
|
+
|
|
18
|
+
Change Hints:
|
|
19
|
+
{{change_hints}}
|
|
20
|
+
|
|
21
|
+
Diff:
|
|
22
|
+
{{diff}}
|
|
23
|
+
|
|
24
|
+
Return:
|
|
25
|
+
|
|
26
|
+
1. Release Summary:
|
|
27
|
+
- One short paragraph describing the overall release/change set
|
|
28
|
+
|
|
29
|
+
2. Changelog:
|
|
30
|
+
- Group entries under relevant categories
|
|
31
|
+
- Keep bullets concise and user-facing
|
|
32
|
+
- If there are no clear entries for a category, omit it
|
|
33
|
+
|
|
34
|
+
3. Upgrade Notes:
|
|
35
|
+
- Mention any migration steps, rollout cautions, or compatibility risks
|
|
36
|
+
- If none are apparent, say so clearly
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
You are helping resolve unresolved Git merge conflicts.
|
|
2
|
+
|
|
3
|
+
Context:
|
|
4
|
+
- The input below is a structured report extracted from conflict markers in the working tree.
|
|
5
|
+
- Explain what each side appears to be doing, suggest the most likely safe resolution, and call out any ambiguity that requires human review.
|
|
6
|
+
- Be conservative. Do not pretend a single resolution is certain when the intent is unclear.
|
|
7
|
+
|
|
8
|
+
Conflict Summary:
|
|
9
|
+
{{commit_message}}
|
|
10
|
+
|
|
11
|
+
Files Changed:
|
|
12
|
+
{{files_changed}}
|
|
13
|
+
|
|
14
|
+
Stats:
|
|
15
|
+
{{stats}}
|
|
16
|
+
|
|
17
|
+
Conflict Report:
|
|
18
|
+
{{diff}}
|
|
19
|
+
|
|
20
|
+
Return:
|
|
21
|
+
|
|
22
|
+
1. Conflict Summary:
|
|
23
|
+
- Explain the overall source of the conflict
|
|
24
|
+
|
|
25
|
+
2. Suggested Resolution:
|
|
26
|
+
- For each file or conflict block, describe the most likely resolution
|
|
27
|
+
- Be explicit about which lines to keep, merge, or rewrite
|
|
28
|
+
|
|
29
|
+
3. Why:
|
|
30
|
+
- Explain why the suggested resolution is the safest or most consistent
|
|
31
|
+
|
|
32
|
+
4. Follow-Up Checks:
|
|
33
|
+
- Suggest tests, manual verification steps, or edge cases to validate after resolving the conflict
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Write a pull request description for this change set.
|
|
2
|
+
|
|
3
|
+
Context:
|
|
4
|
+
- This may be a single commit, a commit range, or a branch comparison.
|
|
5
|
+
- The result should be ready to paste into GitHub or GitLab.
|
|
6
|
+
- Be accurate and concise. Do not invent testing or screenshots that are not evidenced by the diff.
|
|
7
|
+
|
|
8
|
+
Commit Message:
|
|
9
|
+
{{commit_message}}
|
|
10
|
+
|
|
11
|
+
Files Changed:
|
|
12
|
+
{{files_changed}}
|
|
13
|
+
|
|
14
|
+
Stats:
|
|
15
|
+
{{stats}}
|
|
16
|
+
|
|
17
|
+
Change Hints:
|
|
18
|
+
{{change_hints}}
|
|
19
|
+
|
|
20
|
+
Diff:
|
|
21
|
+
{{diff}}
|
|
22
|
+
|
|
23
|
+
Return:
|
|
24
|
+
|
|
25
|
+
## Summary
|
|
26
|
+
- Short bullets describing the main changes
|
|
27
|
+
|
|
28
|
+
## Why
|
|
29
|
+
- Why this PR exists
|
|
30
|
+
|
|
31
|
+
## Testing
|
|
32
|
+
- Mention visible testing evidence from the diff if any
|
|
33
|
+
- If no testing evidence is visible, say "Not specified in diff"
|
|
34
|
+
|
|
35
|
+
## Risks
|
|
36
|
+
- Briefly describe rollout, compatibility, or review risks
|
|
37
|
+
|
|
38
|
+
## Screenshots
|
|
39
|
+
- If the diff suggests UI work, include "Screenshots: Needed"
|
|
40
|
+
- Otherwise include "Screenshots: Not needed"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Review this change for refactoring opportunities.
|
|
2
|
+
|
|
3
|
+
Commit Message:
|
|
4
|
+
{{commit_message}}
|
|
5
|
+
|
|
6
|
+
Files Changed:
|
|
7
|
+
{{files_changed}}
|
|
8
|
+
|
|
9
|
+
Stats:
|
|
10
|
+
{{stats}}
|
|
11
|
+
|
|
12
|
+
Change Hints:
|
|
13
|
+
{{change_hints}}
|
|
14
|
+
|
|
15
|
+
Diff:
|
|
16
|
+
{{diff}}
|
|
17
|
+
|
|
18
|
+
Return:
|
|
19
|
+
|
|
20
|
+
1. Refactor Opportunities:
|
|
21
|
+
- List concrete refactors suggested by the code shown
|
|
22
|
+
- Focus on duplication, naming, cohesion, dead code, control flow, error handling, and API clarity
|
|
23
|
+
- If there are no worthwhile refactors, say so explicitly
|
|
24
|
+
|
|
25
|
+
2. Why These Matter:
|
|
26
|
+
- Explain the maintenance or correctness benefits of the top opportunities
|
|
27
|
+
|
|
28
|
+
3. Safe Next Steps:
|
|
29
|
+
- Suggest a small, practical follow-up plan that avoids mixing refactors with risky behavior changes
|