threadlines 0.1.32 â 0.1.33
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/dist/commands/check.js +66 -35
- package/dist/git/diff.js +33 -0
- package/dist/utils/metadata.js +159 -1
- package/package.json +2 -2
package/dist/commands/check.js
CHANGED
|
@@ -181,6 +181,14 @@ async function checkCommand(options) {
|
|
|
181
181
|
}
|
|
182
182
|
// 3. Collect metadata (commit SHA, commit message, PR title)
|
|
183
183
|
const metadata = await (0, metadata_1.collectMetadata)(context, environment, repoRoot);
|
|
184
|
+
// Debug: Log collected metadata
|
|
185
|
+
console.log(`[DEBUG] Metadata after collectMetadata: ${JSON.stringify(metadata)}`);
|
|
186
|
+
if (metadata.commitAuthorName || metadata.commitAuthorEmail) {
|
|
187
|
+
console.log(chalk_1.default.gray(` Author: ${metadata.commitAuthorName} <${metadata.commitAuthorEmail}>`));
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
console.log(`[DEBUG] No author info - commitAuthorName=${metadata.commitAuthorName}, commitAuthorEmail=${metadata.commitAuthorEmail}`);
|
|
191
|
+
}
|
|
184
192
|
if (gitDiff.changedFiles.length === 0) {
|
|
185
193
|
console.error(chalk_1.default.red('â Error: No changes detected.'));
|
|
186
194
|
console.error(chalk_1.default.red(' Threadline check requires code changes to analyze.'));
|
|
@@ -225,7 +233,7 @@ async function checkCommand(options) {
|
|
|
225
233
|
// 6. Call review API
|
|
226
234
|
console.log(chalk_1.default.gray('đ¤ Running threadline checks...'));
|
|
227
235
|
const client = new client_1.ReviewAPIClient(apiUrl);
|
|
228
|
-
const
|
|
236
|
+
const reviewRequest = {
|
|
229
237
|
threadlines: threadlinesWithContext,
|
|
230
238
|
diff: gitDiff.diff,
|
|
231
239
|
files: gitDiff.changedFiles,
|
|
@@ -235,9 +243,13 @@ async function checkCommand(options) {
|
|
|
235
243
|
branchName: branchName,
|
|
236
244
|
commitSha: metadata.commitSha,
|
|
237
245
|
commitMessage: metadata.commitMessage,
|
|
246
|
+
commitAuthorName: metadata.commitAuthorName,
|
|
247
|
+
commitAuthorEmail: metadata.commitAuthorEmail,
|
|
238
248
|
prTitle: metadata.prTitle,
|
|
239
249
|
environment: environment
|
|
240
|
-
}
|
|
250
|
+
};
|
|
251
|
+
console.log(`[DEBUG] Sending to API - commitAuthorName: ${reviewRequest.commitAuthorName}, commitAuthorEmail: ${reviewRequest.commitAuthorEmail}`);
|
|
252
|
+
const response = await client.review(reviewRequest);
|
|
241
253
|
// 7. Display results (with filtering if --full not specified)
|
|
242
254
|
displayResults(response, options.full || false);
|
|
243
255
|
// Exit with appropriate code
|
|
@@ -260,38 +272,68 @@ function displayResults(response, showFull) {
|
|
|
260
272
|
if (message) {
|
|
261
273
|
console.log('\n' + chalk_1.default.blue('âšī¸ ' + message));
|
|
262
274
|
}
|
|
263
|
-
console.log('\n' + chalk_1.default.bold('Results:\n'));
|
|
264
|
-
console.log(chalk_1.default.gray(`${metadata.totalThreadlines} threadlines checked`));
|
|
265
275
|
const notRelevant = results.filter((r) => r.status === 'not_relevant').length;
|
|
266
276
|
const compliant = results.filter((r) => r.status === 'compliant').length;
|
|
267
277
|
const attention = results.filter((r) => r.status === 'attention').length;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
if (compliant > 0) {
|
|
274
|
-
console.log(chalk_1.default.green(` ${compliant} compliant`));
|
|
275
|
-
}
|
|
276
|
-
if (attention > 0) {
|
|
277
|
-
console.log(chalk_1.default.yellow(` ${attention} attention`));
|
|
278
|
-
}
|
|
278
|
+
const attentionItems = filteredResults.filter((r) => r.status === 'attention');
|
|
279
|
+
// Build summary parts
|
|
280
|
+
const summaryParts = [];
|
|
281
|
+
if (notRelevant > 0) {
|
|
282
|
+
summaryParts.push(`${notRelevant} not relevant`);
|
|
279
283
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
284
|
+
if (compliant > 0) {
|
|
285
|
+
summaryParts.push(`${compliant} compliant`);
|
|
286
|
+
}
|
|
287
|
+
if (attention > 0) {
|
|
288
|
+
summaryParts.push(`${attention} attention`);
|
|
285
289
|
}
|
|
286
290
|
if (metadata.timedOut > 0) {
|
|
287
|
-
|
|
291
|
+
summaryParts.push(`${metadata.timedOut} timed out`);
|
|
288
292
|
}
|
|
289
293
|
if (metadata.errors > 0) {
|
|
290
|
-
|
|
294
|
+
summaryParts.push(`${metadata.errors} errors`);
|
|
295
|
+
}
|
|
296
|
+
// Display informational message if present (e.g., zero diffs)
|
|
297
|
+
if (message) {
|
|
298
|
+
console.log('\n' + chalk_1.default.blue('âšī¸ ' + message));
|
|
299
|
+
}
|
|
300
|
+
// Show success message with breakdown if no issues
|
|
301
|
+
if (attention === 0 && metadata.timedOut === 0 && metadata.errors === 0) {
|
|
302
|
+
const summary = summaryParts.length > 0 ? ` (${summaryParts.join(', ')})` : '';
|
|
303
|
+
console.log('\n' + chalk_1.default.green(`â Threadline check passed${summary}`));
|
|
304
|
+
console.log(chalk_1.default.gray(` ${metadata.totalThreadlines} threadline${metadata.totalThreadlines !== 1 ? 's' : ''} checked\n`));
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
// Show detailed breakdown when there are issues
|
|
308
|
+
console.log('\n' + chalk_1.default.bold('Results:\n'));
|
|
309
|
+
console.log(chalk_1.default.gray(`${metadata.totalThreadlines} threadlines checked`));
|
|
310
|
+
if (showFull) {
|
|
311
|
+
// Show all results when --full flag is used
|
|
312
|
+
if (notRelevant > 0) {
|
|
313
|
+
console.log(chalk_1.default.gray(` ${notRelevant} not relevant`));
|
|
314
|
+
}
|
|
315
|
+
if (compliant > 0) {
|
|
316
|
+
console.log(chalk_1.default.green(` ${compliant} compliant`));
|
|
317
|
+
}
|
|
318
|
+
if (attention > 0) {
|
|
319
|
+
console.log(chalk_1.default.yellow(` ${attention} attention`));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
// Default: only show attention items
|
|
324
|
+
if (attention > 0) {
|
|
325
|
+
console.log(chalk_1.default.yellow(` ${attention} attention`));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (metadata.timedOut > 0) {
|
|
329
|
+
console.log(chalk_1.default.yellow(` ${metadata.timedOut} timed out`));
|
|
330
|
+
}
|
|
331
|
+
if (metadata.errors > 0) {
|
|
332
|
+
console.log(chalk_1.default.red(` ${metadata.errors} errors`));
|
|
333
|
+
}
|
|
334
|
+
console.log('');
|
|
291
335
|
}
|
|
292
|
-
console.log('');
|
|
293
336
|
// Show attention items
|
|
294
|
-
const attentionItems = filteredResults.filter((r) => r.status === 'attention');
|
|
295
337
|
if (attentionItems.length > 0) {
|
|
296
338
|
for (const item of attentionItems) {
|
|
297
339
|
console.log(chalk_1.default.yellow(`â ī¸ ${item.expertId}`));
|
|
@@ -308,15 +350,4 @@ function displayResults(response, showFull) {
|
|
|
308
350
|
}
|
|
309
351
|
console.log('');
|
|
310
352
|
}
|
|
311
|
-
// Show compliant items (only when --full flag is used)
|
|
312
|
-
if (showFull) {
|
|
313
|
-
const compliantItems = filteredResults.filter((r) => r.status === 'compliant');
|
|
314
|
-
if (compliantItems.length > 0 && attentionItems.length === 0) {
|
|
315
|
-
console.log(chalk_1.default.green('â All threadlines passed!\n'));
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
else if (attentionItems.length === 0 && compliant > 0) {
|
|
319
|
-
// Default: show success message if no attention items
|
|
320
|
-
console.log(chalk_1.default.green('â All threadlines passed!\n'));
|
|
321
|
-
}
|
|
322
353
|
}
|
package/dist/git/diff.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getBranchDiff = getBranchDiff;
|
|
7
7
|
exports.getCommitMessage = getCommitMessage;
|
|
8
|
+
exports.getCommitAuthor = getCommitAuthor;
|
|
8
9
|
exports.getCommitDiff = getCommitDiff;
|
|
9
10
|
exports.getPRMRDiff = getPRMRDiff;
|
|
10
11
|
const simple_git_1 = __importDefault(require("simple-git"));
|
|
@@ -197,6 +198,38 @@ async function getCommitMessage(repoRoot, sha) {
|
|
|
197
198
|
return null;
|
|
198
199
|
}
|
|
199
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Get commit author name and email for a specific commit SHA or HEAD.
|
|
203
|
+
*
|
|
204
|
+
* Uses git log to extract author information from the commit.
|
|
205
|
+
* Works in all environments where git is available.
|
|
206
|
+
*/
|
|
207
|
+
async function getCommitAuthor(repoRoot, sha) {
|
|
208
|
+
const git = (0, simple_git_1.default)(repoRoot);
|
|
209
|
+
try {
|
|
210
|
+
let logResult;
|
|
211
|
+
if (sha) {
|
|
212
|
+
// Use git log for specific commit SHA
|
|
213
|
+
logResult = await git.log({ from: sha, to: sha, maxCount: 1 });
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
// Use git log for HEAD (local environment only)
|
|
217
|
+
logResult = await git.log({ maxCount: 1 });
|
|
218
|
+
}
|
|
219
|
+
if (!logResult.latest) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
const name = logResult.latest.author_name?.trim();
|
|
223
|
+
const email = logResult.latest.author_email?.trim();
|
|
224
|
+
if (!name || !email) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
return { name, email };
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
200
233
|
/**
|
|
201
234
|
* Get diff for a specific commit
|
|
202
235
|
*/
|
package/dist/utils/metadata.js
CHANGED
|
@@ -10,9 +10,47 @@
|
|
|
10
10
|
* Metadata collection is environment-specific because each CI platform
|
|
11
11
|
* provides different environment variables.
|
|
12
12
|
*/
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
47
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
48
|
+
};
|
|
13
49
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
50
|
exports.collectMetadata = collectMetadata;
|
|
15
51
|
const diff_1 = require("../git/diff");
|
|
52
|
+
const fs = __importStar(require("fs"));
|
|
53
|
+
const simple_git_1 = __importDefault(require("simple-git"));
|
|
16
54
|
/**
|
|
17
55
|
* Collects metadata for the given context and environment.
|
|
18
56
|
*
|
|
@@ -23,12 +61,29 @@ async function collectMetadata(context, environment, repoRoot) {
|
|
|
23
61
|
const metadata = {};
|
|
24
62
|
// Collect commit SHA (environment-specific)
|
|
25
63
|
metadata.commitSha = getCommitSha(context, environment);
|
|
26
|
-
// Collect commit message
|
|
64
|
+
// Collect commit message and author (environment-specific)
|
|
27
65
|
if (metadata.commitSha) {
|
|
28
66
|
const message = await (0, diff_1.getCommitMessage)(repoRoot, metadata.commitSha);
|
|
29
67
|
if (message) {
|
|
30
68
|
metadata.commitMessage = message;
|
|
31
69
|
}
|
|
70
|
+
// Get commit author - environment-specific approach
|
|
71
|
+
const author = await getCommitAuthorForEnvironment(environment, repoRoot, metadata.commitSha);
|
|
72
|
+
if (author) {
|
|
73
|
+
metadata.commitAuthorName = author.name;
|
|
74
|
+
metadata.commitAuthorEmail = author.email;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// For local environment without explicit commit SHA:
|
|
79
|
+
// Use git config (who will commit staged/unstaged changes)
|
|
80
|
+
// No fallbacks - if git config fails, the error propagates and fails the check
|
|
81
|
+
console.log('[DEBUG] Local environment - calling getGitConfigUser()');
|
|
82
|
+
const author = await getGitConfigUser(repoRoot);
|
|
83
|
+
console.log(`[DEBUG] getGitConfigUser returned: ${JSON.stringify(author)}`);
|
|
84
|
+
metadata.commitAuthorName = author.name;
|
|
85
|
+
metadata.commitAuthorEmail = author.email;
|
|
86
|
+
console.log(`[DEBUG] metadata after assignment: commitAuthorName=${metadata.commitAuthorName}, commitAuthorEmail=${metadata.commitAuthorEmail}`);
|
|
32
87
|
}
|
|
33
88
|
// Collect PR/MR title (environment-specific)
|
|
34
89
|
metadata.prTitle = getPRTitle(context, environment);
|
|
@@ -69,6 +124,109 @@ function getCommitSha(context, environment) {
|
|
|
69
124
|
}
|
|
70
125
|
return undefined;
|
|
71
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Gets commit author information using environment-specific methods.
|
|
129
|
+
*
|
|
130
|
+
* For GitHub: Reads from GITHUB_EVENT_PATH JSON file (most reliable)
|
|
131
|
+
* For GitLab: Uses CI_COMMIT_AUTHOR environment variable (most reliable)
|
|
132
|
+
* For Local: Uses git config (for uncommitted changes, represents who will commit)
|
|
133
|
+
* For other environments: Uses git log command
|
|
134
|
+
*/
|
|
135
|
+
async function getCommitAuthorForEnvironment(environment, repoRoot, commitSha) {
|
|
136
|
+
if (environment === 'github') {
|
|
137
|
+
// GitHub: Read from GITHUB_EVENT_PATH JSON file
|
|
138
|
+
// This is more reliable than git commands, especially in shallow clones
|
|
139
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
140
|
+
if (eventPath && fs.existsSync(eventPath)) {
|
|
141
|
+
try {
|
|
142
|
+
const eventData = JSON.parse(fs.readFileSync(eventPath, 'utf-8'));
|
|
143
|
+
// For push events, use head_commit.author
|
|
144
|
+
if (eventData.head_commit?.author) {
|
|
145
|
+
return {
|
|
146
|
+
name: eventData.head_commit.author.name,
|
|
147
|
+
email: eventData.head_commit.author.email
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
// For PR events, use commits[0].author (first commit in the PR)
|
|
151
|
+
if (eventData.commits && eventData.commits.length > 0 && eventData.commits[0].author) {
|
|
152
|
+
return {
|
|
153
|
+
name: eventData.commits[0].author.name,
|
|
154
|
+
email: eventData.commits[0].author.email
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// Fallback to pull_request.head.commit.author for PR events
|
|
158
|
+
if (eventData.pull_request?.head?.commit?.author) {
|
|
159
|
+
return {
|
|
160
|
+
name: eventData.pull_request.head.commit.author.name,
|
|
161
|
+
email: eventData.pull_request.head.commit.author.email
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
// If JSON parsing fails, fall through to git command
|
|
167
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
168
|
+
console.warn(`Warning: Failed to read GitHub event JSON: ${errorMessage}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (environment === 'gitlab') {
|
|
173
|
+
// GitLab: Use CI_COMMIT_AUTHOR environment variable
|
|
174
|
+
// Format: "name <email>" (e.g., "ngrootscholten <niels.grootscholten@gmail.com>")
|
|
175
|
+
// This is more reliable than git commands, especially in shallow clones
|
|
176
|
+
const commitAuthor = process.env.CI_COMMIT_AUTHOR;
|
|
177
|
+
if (commitAuthor) {
|
|
178
|
+
// Parse "name <email>" format
|
|
179
|
+
const match = commitAuthor.match(/^(.+?)\s*<(.+?)>$/);
|
|
180
|
+
if (match) {
|
|
181
|
+
return {
|
|
182
|
+
name: match[1].trim(),
|
|
183
|
+
email: match[2].trim()
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
// If format doesn't match expected pattern, try to extract anyway
|
|
187
|
+
// Some GitLab versions might format differently
|
|
188
|
+
const parts = commitAuthor.trim().split(/\s+/);
|
|
189
|
+
if (parts.length >= 2) {
|
|
190
|
+
// Assume last part is email if it contains @
|
|
191
|
+
const emailIndex = parts.findIndex(p => p.includes('@'));
|
|
192
|
+
if (emailIndex >= 0) {
|
|
193
|
+
const email = parts[emailIndex].replace(/[<>]/g, '').trim();
|
|
194
|
+
const name = parts.slice(0, emailIndex).join(' ').trim();
|
|
195
|
+
if (name && email) {
|
|
196
|
+
return { name, email };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Fallback to git command for all environments (including GitHub/GitLab if env vars unavailable)
|
|
203
|
+
return await (0, diff_1.getCommitAuthor)(repoRoot, commitSha);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Gets git user info from git config (for local uncommitted changes).
|
|
207
|
+
* This represents who is currently working on the changes and will commit them.
|
|
208
|
+
*
|
|
209
|
+
* No fallbacks - if git config is not set or fails, throws an error.
|
|
210
|
+
*/
|
|
211
|
+
async function getGitConfigUser(repoRoot) {
|
|
212
|
+
const git = (0, simple_git_1.default)(repoRoot);
|
|
213
|
+
try {
|
|
214
|
+
const name = await git.getConfig('user.name');
|
|
215
|
+
const email = await git.getConfig('user.email');
|
|
216
|
+
if (!name.value || !email.value) {
|
|
217
|
+
throw new Error('Git config user.name or user.email is not set. ' +
|
|
218
|
+
'Run: git config user.name "Your Name" && git config user.email "your.email@example.com"');
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
name: name.value.trim(),
|
|
222
|
+
email: email.value.trim()
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
227
|
+
throw new Error(`Failed to get git config user: ${errorMessage}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
72
230
|
/**
|
|
73
231
|
* Extracts PR/MR title from context and environment.
|
|
74
232
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "threadlines",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.33",
|
|
4
4
|
"description": "Threadline CLI - AI-powered linter based on your natural language documentation",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"directory": "packages/cli"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
|
-
"build": "npm run check &&
|
|
30
|
+
"build": "npm run check && tsc",
|
|
31
31
|
"dev": "tsc --watch",
|
|
32
32
|
"start": "node dist/index.js",
|
|
33
33
|
"prepublishOnly": "npm run build",
|