gitxplain 0.1.8 → 0.2.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/.github/workflows/ci.yml +2 -0
- package/.github/workflows/release.yml +92 -5
- package/README.md +227 -4
- package/cli/index.js +439 -114
- 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 +43 -30
- package/cli/services/outputFormatter.js +23 -73
- package/cli/services/pipelineService.js +344 -9
- package/cli/services/promptService.js +8 -1
- package/cli/services/splitService.js +1 -21
- package/cli/services/usageService.js +158 -0
- package/package.json +4 -4
- package/packaging/README.md +60 -0
- package/packaging/aur/.SRCINFO +12 -0
- package/packaging/aur/PKGBUILD +22 -0
- package/packaging/homebrew-tap/Formula/gitxplain.rb +19 -0
- 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/scripts/build-deb.sh +64 -0
- package/IMPLEMENTATION.md +0 -225
- package/cli/services/chatService.js +0 -683
- package/cli/services/gitConnectionService.js +0 -267
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import process from "node:process";
|
|
2
1
|
import {
|
|
3
2
|
createCommitFromTree,
|
|
4
3
|
getCommitMetadata,
|
|
@@ -15,19 +14,13 @@ import {
|
|
|
15
14
|
listBranchCommits,
|
|
16
15
|
listCommitsAfter,
|
|
17
16
|
listTags,
|
|
17
|
+
listTagTargets,
|
|
18
18
|
localBranchExists,
|
|
19
19
|
resolveTreeSha,
|
|
20
20
|
resolveCommitSha,
|
|
21
21
|
runGitCommand
|
|
22
22
|
} from "./gitService.js";
|
|
23
|
-
|
|
24
|
-
const ANSI = {
|
|
25
|
-
reset: "\u001b[0m",
|
|
26
|
-
bold: "\u001b[1m",
|
|
27
|
-
cyan: "\u001b[36m",
|
|
28
|
-
yellow: "\u001b[33m",
|
|
29
|
-
green: "\u001b[32m"
|
|
30
|
-
};
|
|
23
|
+
import { ANSI, colorize } from "./colorSupport.js";
|
|
31
24
|
|
|
32
25
|
const RELEASE_BRANCH = "release";
|
|
33
26
|
const VERSION_PATTERN = /\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?\b/g;
|
|
@@ -36,22 +29,14 @@ const SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$
|
|
|
36
29
|
const INTEGER_PATTERN = /^\d+$/;
|
|
37
30
|
const TAG_VERSION_PATTERN = /^v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?|\d+)$/;
|
|
38
31
|
|
|
39
|
-
function supportsColor() {
|
|
40
|
-
return Boolean(process.stdout?.isTTY) && process.env.NO_COLOR == null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function colorize(text, color) {
|
|
44
|
-
if (!supportsColor()) {
|
|
45
|
-
return text;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return `${color}${text}${ANSI.reset}`;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
32
|
function unique(values) {
|
|
52
33
|
return [...new Set(values)];
|
|
53
34
|
}
|
|
54
35
|
|
|
36
|
+
function formatVersionTag(version) {
|
|
37
|
+
return `v${version}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
55
40
|
function extractVersions(line) {
|
|
56
41
|
return unique(line.match(VERSION_PATTERN) ?? []);
|
|
57
42
|
}
|
|
@@ -335,21 +320,41 @@ export function selectReleaseWindows(sourceCommits, releaseCommits = []) {
|
|
|
335
320
|
};
|
|
336
321
|
}
|
|
337
322
|
|
|
338
|
-
export function selectReleaseTags(sourceCommits, existingTagNames = []) {
|
|
323
|
+
export function selectReleaseTags(sourceCommits, existingTagNames = [], existingTagTargets = []) {
|
|
339
324
|
const windows = selectLatestWindowsPerVersion(buildReleaseWindows(sourceCommits));
|
|
340
325
|
const taggedVersions = extractTaggedVersions(existingTagNames);
|
|
326
|
+
const existingTagByVersion = new Map(
|
|
327
|
+
existingTagTargets
|
|
328
|
+
.map((tag) => {
|
|
329
|
+
const version = tag.tagName?.match(TAG_VERSION_PATTERN)?.[1] ?? null;
|
|
330
|
+
return version ? [version, tag] : null;
|
|
331
|
+
})
|
|
332
|
+
.filter(Boolean)
|
|
333
|
+
);
|
|
341
334
|
const tags = windows
|
|
342
|
-
.filter((window) => !taggedVersions.has(window.version))
|
|
343
335
|
.map((window) => {
|
|
344
336
|
const targetCommit = window.commits.at(-1) ?? null;
|
|
337
|
+
const existingTag = existingTagByVersion.get(window.version) ?? null;
|
|
338
|
+
const existingTargetSha = existingTag?.targetSha ?? null;
|
|
339
|
+
const windowCommitShas = new Set(window.commits.map((commit) => commit.sha));
|
|
340
|
+
|
|
345
341
|
return {
|
|
346
342
|
...window,
|
|
347
|
-
|
|
343
|
+
version: window.version,
|
|
344
|
+
tagName: existingTag?.tagName ?? formatVersionTag(window.version),
|
|
345
|
+
existingTagName: existingTag?.tagName ?? null,
|
|
346
|
+
existingTargetSha,
|
|
347
|
+
needsMove:
|
|
348
|
+
existingTargetSha != null &&
|
|
349
|
+
targetCommit?.sha != null &&
|
|
350
|
+
windowCommitShas.has(existingTargetSha) &&
|
|
351
|
+
existingTargetSha !== targetCommit.sha,
|
|
348
352
|
targetSha: targetCommit?.sha ?? null,
|
|
349
353
|
targetShortSha: targetCommit?.shortSha ?? null,
|
|
350
354
|
targetSubject: targetCommit?.subject ?? null
|
|
351
355
|
};
|
|
352
356
|
})
|
|
357
|
+
.filter((tag) => !taggedVersions.has(tag.version) || tag.needsMove)
|
|
353
358
|
.filter((tag) => tag.targetSha != null);
|
|
354
359
|
|
|
355
360
|
return {
|
|
@@ -370,7 +375,8 @@ function findLatestTaggedSourceVersion(sourceCommits, taggedVersions) {
|
|
|
370
375
|
function buildReleaseTagPlanForSource(sourceBranch, sourceRef, cwd) {
|
|
371
376
|
const sourceCommits = listBranchCommits(sourceRef, cwd).map((sha) => inspectCommit(sha, cwd));
|
|
372
377
|
const existingTagNames = listTags(cwd);
|
|
373
|
-
const
|
|
378
|
+
const existingTagTargets = listTagTargets(cwd);
|
|
379
|
+
const selection = selectReleaseTags(sourceCommits, existingTagNames, existingTagTargets);
|
|
374
380
|
|
|
375
381
|
return {
|
|
376
382
|
sourceBranch,
|
|
@@ -395,7 +401,7 @@ export function selectReleaseTagsFromReleaseCommits(releaseCommits, existingTagN
|
|
|
395
401
|
.filter((entry) => !taggedVersions.has(entry.version))
|
|
396
402
|
.map(({ commit, version }) => ({
|
|
397
403
|
version,
|
|
398
|
-
tagName: version,
|
|
404
|
+
tagName: formatVersionTag(version),
|
|
399
405
|
startRef: commit.shortSha,
|
|
400
406
|
endRef: commit.shortSha,
|
|
401
407
|
targetSha: commit.sha,
|
|
@@ -655,15 +661,18 @@ export function formatReleaseTagPlan(plan) {
|
|
|
655
661
|
];
|
|
656
662
|
|
|
657
663
|
if (plan.tags.length === 0) {
|
|
658
|
-
lines.push(colorize("No
|
|
664
|
+
lines.push(colorize("No release tag changes detected. Nothing to tag.", ANSI.green));
|
|
659
665
|
return lines.join("\n");
|
|
660
666
|
}
|
|
661
667
|
|
|
662
668
|
for (const tag of plan.tags) {
|
|
663
669
|
lines.push("");
|
|
664
|
-
lines.push(colorize(
|
|
670
|
+
lines.push(colorize(`${tag.needsMove ? "move tag" : "tag"} ${tag.tagName}`, ANSI.bold + ANSI.yellow));
|
|
665
671
|
lines.push(`${colorize("Commit Range:", ANSI.bold + ANSI.cyan)} ${tag.startRef}..${tag.endRef}`);
|
|
666
672
|
lines.push(`${colorize("Target Commit:", ANSI.bold + ANSI.cyan)} ${tag.targetShortSha} ${tag.targetSubject}`);
|
|
673
|
+
if (tag.needsMove) {
|
|
674
|
+
lines.push(`${colorize("Action:", ANSI.bold + ANSI.cyan)} move existing tag to the latest commit for ${tag.tagName}`);
|
|
675
|
+
}
|
|
667
676
|
|
|
668
677
|
for (const commit of tag.commits) {
|
|
669
678
|
lines.push(`${colorize(commit.shortSha, ANSI.bold + ANSI.cyan)} ${commit.subject}`);
|
|
@@ -817,14 +826,18 @@ export function executeReleaseMerge(plan, cwd) {
|
|
|
817
826
|
|
|
818
827
|
export function executeReleaseTagPlan(plan, cwd) {
|
|
819
828
|
if (plan.tags.length === 0) {
|
|
820
|
-
throw new Error("No
|
|
829
|
+
throw new Error("No release tag changes detected. Nothing to tag.");
|
|
821
830
|
}
|
|
822
831
|
|
|
823
832
|
const createdTags = [];
|
|
824
833
|
|
|
825
834
|
try {
|
|
826
835
|
for (const tag of plan.tags) {
|
|
827
|
-
|
|
836
|
+
if (tag.needsMove) {
|
|
837
|
+
gitDeleteTag(tag.existingTagName ?? tag.tagName, cwd);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
gitCreateAnnotatedTag(tag.tagName, tag.targetSha, `release ${tag.version ?? tag.tagName.replace(/^v/, "")}`, cwd);
|
|
828
841
|
createdTags.push(tag.tagName);
|
|
829
842
|
}
|
|
830
843
|
} catch (error) {
|
|
@@ -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
|
}
|