gitxplain 0.1.0 → 0.1.6
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/.env.example +5 -10
- package/.github/workflows/ci.yml +28 -0
- package/.github/workflows/release.yml +27 -0
- package/IMPLEMENTATION.md +225 -0
- package/README.md +201 -0
- package/cli/index.js +693 -27
- package/cli/services/aiService.js +2 -2
- package/cli/services/chatService.js +683 -0
- package/cli/services/commitService.js +379 -0
- package/cli/services/envLoader.js +33 -0
- package/cli/services/gitConnectionService.js +267 -0
- package/cli/services/gitService.js +633 -1
- package/cli/services/mergeService.js +826 -0
- package/cli/services/outputFormatter.js +185 -13
- package/cli/services/pipelineService.js +721 -0
- package/cli/services/promptService.js +66 -2
- package/cli/services/splitService.js +472 -0
- package/package.json +4 -3
- package/prompts/commit.txt +57 -0
- package/prompts/split.txt +44 -0
|
@@ -11,7 +11,15 @@ const ANSI = {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
function supportsColor() {
|
|
14
|
-
|
|
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);
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
function colorize(text, color) {
|
|
@@ -22,35 +30,199 @@ function colorize(text, color) {
|
|
|
22
30
|
return `${color}${text}${ANSI.reset}`;
|
|
23
31
|
}
|
|
24
32
|
|
|
33
|
+
function stripInlineMarkdown(text) {
|
|
34
|
+
return text
|
|
35
|
+
.replace(/`([^`]+)`/g, "$1")
|
|
36
|
+
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
|
37
|
+
.replace(/__([^_]+)__/g, "$1")
|
|
38
|
+
.replace(/\*([^*]+)\*/g, "$1")
|
|
39
|
+
.replace(/_([^_]+)_/g, "$1")
|
|
40
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)")
|
|
41
|
+
.trimEnd();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeMarkdownLine(line, state) {
|
|
45
|
+
const trimmed = line.trim();
|
|
46
|
+
|
|
47
|
+
if (/^```/.test(trimmed)) {
|
|
48
|
+
state.inCodeBlock = !state.inCodeBlock;
|
|
49
|
+
return "";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (state.inCodeBlock) {
|
|
53
|
+
return ` ${line.replace(/^\s*/, "")}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (/^---+$/.test(trimmed) || /^\*\*\*+$/.test(trimmed)) {
|
|
57
|
+
return "";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let normalizedHeading = trimmed
|
|
61
|
+
.replace(/^#{1,6}\s+/, "")
|
|
62
|
+
.replace(/^([0-9]+\.)\s+/, "");
|
|
63
|
+
normalizedHeading = stripInlineMarkdown(normalizedHeading).replace(/:\s*$/, "").trim();
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
/^(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)$/i.test(
|
|
67
|
+
normalizedHeading
|
|
68
|
+
)
|
|
69
|
+
) {
|
|
70
|
+
return `${normalizedHeading}:`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (/^>\s*/.test(trimmed)) {
|
|
74
|
+
return stripInlineMarkdown(trimmed.replace(/^>\s*/, ""));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const bulletMatch = line.match(/^(\s*)([-*+]|\d+\.)\s+(.*)$/);
|
|
78
|
+
if (bulletMatch) {
|
|
79
|
+
const [, indent, marker, content] = bulletMatch;
|
|
80
|
+
return `${indent}${marker} ${stripInlineMarkdown(content)}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return stripInlineMarkdown(line);
|
|
84
|
+
}
|
|
85
|
+
|
|
25
86
|
function formatTargetLabel(commitData) {
|
|
26
87
|
return commitData.analysisType === "range" ? "Range" : "Commit";
|
|
27
88
|
}
|
|
28
89
|
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
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;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return `${match[2]}:`;
|
|
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";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (/^\s*medium(?:\b|[.:])/i.test(line)) {
|
|
110
|
+
return "neutral";
|
|
32
111
|
}
|
|
33
112
|
|
|
34
|
-
if (
|
|
35
|
-
return
|
|
113
|
+
if (/^\s*high(?:\b|[.:])/i.test(line)) {
|
|
114
|
+
return "bad";
|
|
36
115
|
}
|
|
37
116
|
|
|
38
|
-
if (
|
|
39
|
-
|
|
117
|
+
if (
|
|
118
|
+
/\b(no significant findings|no security findings|none apparent|looks good|safe|improved|improvement|fixed|resolved|successful|passes?|low risk|low severity)\b/i.test(
|
|
119
|
+
line
|
|
120
|
+
)
|
|
121
|
+
) {
|
|
122
|
+
return "good";
|
|
40
123
|
}
|
|
41
124
|
|
|
42
|
-
if (
|
|
43
|
-
|
|
125
|
+
if (
|
|
126
|
+
/\b(issue|issues|bug|broken|failure|failing|risk|risky|severity|vulnerability|insecure|regression|warning|problem|bad|missing|error|high risk|high severity)\b/i.test(
|
|
127
|
+
line
|
|
128
|
+
)
|
|
129
|
+
) {
|
|
130
|
+
return "bad";
|
|
44
131
|
}
|
|
45
132
|
|
|
133
|
+
if (/\b(suggestion|consider|follow-up|todo|medium risk|medium severity)\b/i.test(line)) {
|
|
134
|
+
return "neutral";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function colorizeByTone(line, tone) {
|
|
46
141
|
return line;
|
|
47
142
|
}
|
|
48
143
|
|
|
144
|
+
function formatBulletLine(line) {
|
|
145
|
+
const match = line.match(/^(\s*)([-*]|\d+\.)\s+(.*)$/);
|
|
146
|
+
|
|
147
|
+
if (!match) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const [, indent, marker, content] = match;
|
|
152
|
+
return `${indent}${colorize(marker, ANSI.cyan)} ${content}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function formatSeverityLine(line) {
|
|
156
|
+
if (/\brisk level\b|\bseverity\b/i.test(line) === false) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return line;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function formatLine(line) {
|
|
164
|
+
const trimmed = line.trim();
|
|
165
|
+
|
|
166
|
+
if (trimmed === "") {
|
|
167
|
+
return "";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const normalizedHeading = normalizeHeading(trimmed);
|
|
171
|
+
|
|
172
|
+
if (normalizedHeading) {
|
|
173
|
+
return colorize(normalizedHeading, ANSI.bold + ANSI.cyan);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (isFileHeading(trimmed)) {
|
|
177
|
+
return colorize(trimmed, ANSI.bold + ANSI.cyan);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const bulletLine = formatBulletLine(line);
|
|
181
|
+
|
|
182
|
+
if (bulletLine) {
|
|
183
|
+
return bulletLine;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const severityLine = formatSeverityLine(trimmed);
|
|
187
|
+
|
|
188
|
+
if (severityLine) {
|
|
189
|
+
return severityLine;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return colorizeByTone(line, classifyTone(line));
|
|
193
|
+
}
|
|
194
|
+
|
|
49
195
|
function formatExplanation(explanation) {
|
|
50
|
-
|
|
196
|
+
const state = { inCodeBlock: false };
|
|
197
|
+
const lines = explanation
|
|
198
|
+
.replaceAll("\r\n", "\n")
|
|
51
199
|
.split("\n")
|
|
52
|
-
.map((line) =>
|
|
53
|
-
|
|
200
|
+
.map((line) => normalizeMarkdownLine(line, state));
|
|
201
|
+
const formatted = [];
|
|
202
|
+
let previousWasBlank = false;
|
|
203
|
+
|
|
204
|
+
for (const line of lines) {
|
|
205
|
+
const trimmed = line.trim();
|
|
206
|
+
const formattedLine = formatLine(line);
|
|
207
|
+
const isHeading = normalizeHeading(trimmed) != null || isFileHeading(trimmed);
|
|
208
|
+
|
|
209
|
+
if (trimmed === "") {
|
|
210
|
+
if (!previousWasBlank && formatted.length > 0) {
|
|
211
|
+
formatted.push("");
|
|
212
|
+
}
|
|
213
|
+
previousWasBlank = true;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (isHeading && formatted.length > 0 && !previousWasBlank) {
|
|
218
|
+
formatted.push("");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
formatted.push(formattedLine);
|
|
222
|
+
previousWasBlank = false;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return formatted.join("\n").trimEnd();
|
|
54
226
|
}
|
|
55
227
|
|
|
56
228
|
export function formatPreamble({ mode, commitData, options, promptMeta }) {
|