threadlines 0.2.15 → 0.2.16

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.
@@ -69,36 +69,26 @@ async function getBitbucketContext(repoRoot) {
69
69
  * Get diff for Bitbucket Pipelines environment
70
70
  *
71
71
  * Strategy:
72
- * - PR context: Fetch destination branch on-demand, compare source vs target (full PR diff)
72
+ * - PR context: Uses shared getPRDiff() - fetches destination branch, compares against HEAD
73
73
  * - Any push (main or feature branch): Compare last commit only (HEAD~1...HEAD)
74
74
  *
75
75
  * Note: We fetch the destination branch on-demand so this works with shallow clones.
76
76
  * Users don't need `depth: full` in their bitbucket-pipelines.yml.
77
77
  */
78
78
  async function getDiff(repoRoot) {
79
- const git = (0, simple_git_1.default)(repoRoot);
80
79
  const prId = process.env.BITBUCKET_PR_ID;
81
80
  const prDestinationBranch = process.env.BITBUCKET_PR_DESTINATION_BRANCH;
82
- // PR Context: Fetch destination branch and compare
81
+ // PR Context: Use shared getPRDiff() implementation
83
82
  if (prId) {
84
83
  if (!prDestinationBranch) {
85
84
  throw new Error('Bitbucket PR context detected but BITBUCKET_PR_DESTINATION_BRANCH is not set. ' +
86
85
  'This should be automatically provided by Bitbucket Pipelines.');
87
86
  }
88
- // Fetch destination branch on-demand (works with shallow clones)
89
- logger_1.logger.debug(`Fetching destination branch: origin/${prDestinationBranch}`);
90
- await git.fetch(['origin', `${prDestinationBranch}:refs/remotes/origin/${prDestinationBranch}`, '--depth=1']);
91
- logger_1.logger.debug(`PR #${prId}, using origin/${prDestinationBranch}...HEAD`);
92
- const diff = await git.diff([`origin/${prDestinationBranch}...HEAD`, '-U200']);
93
- const diffSummary = await git.diffSummary([`origin/${prDestinationBranch}...HEAD`]);
94
- const changedFiles = diffSummary.files.map(f => f.file);
95
- return { diff: diff || '', changedFiles };
87
+ return (0, diff_1.getPRDiff)(repoRoot, prDestinationBranch, logger_1.logger);
96
88
  }
97
89
  // Any push (main or feature branch): Review last commit only
98
- const diff = await git.diff(['HEAD~1...HEAD', '-U200']);
99
- const diffSummary = await git.diffSummary(['HEAD~1...HEAD']);
100
- const changedFiles = diffSummary.files.map(f => f.file);
101
- return { diff: diff || '', changedFiles };
90
+ // Use shared getCommitDiff (defaults to HEAD)
91
+ return (0, diff_1.getCommitDiff)(repoRoot);
102
92
  }
103
93
  /**
104
94
  * Gets repository name for Bitbucket Pipelines
package/dist/git/diff.js CHANGED
@@ -80,14 +80,8 @@ async function getCommitAuthor(repoRoot, sha) {
80
80
  * - origin/${sourceBranch} doesn't exist until explicitly fetched
81
81
  * - HEAD IS the source branch in PR/MR pipelines
82
82
  *
83
- * Currently used by:
84
- * - GitLab CI (gitlab.ts)
85
- *
86
- * Future plan:
87
- * - Azure DevOps will use this when added
88
- * - Once proven stable in multiple environments, consider migrating
89
- * GitHub (github.ts) and Bitbucket (bitbucket.ts) to use this shared
90
- * implementation instead of their inline versions.
83
+ * Used by: GitHub, GitLab, Bitbucket (all PR/MR contexts)
84
+ * Future: Azure DevOps will use this when added
91
85
  *
92
86
  * @param repoRoot - Path to the repository root
93
87
  * @param targetBranch - The branch being merged INTO (e.g., "main", "develop")
@@ -118,21 +112,24 @@ async function getPRDiff(repoRoot, targetBranch, logger) {
118
112
  };
119
113
  }
120
114
  /**
121
- * Get diff for a specific commit
115
+ * Get diff for a specific commit (or HEAD if no SHA provided).
116
+ *
117
+ * Uses `git show` which works reliably with shallow clones (depth=1).
118
+ * This is safer than `HEAD~1...HEAD` which requires depth >= 2.
119
+ *
120
+ * Used by:
121
+ * - All CI environments for push/commit context (GitHub, GitLab, Bitbucket, Vercel)
122
+ * - Local environment for --commit flag
123
+ *
124
+ * @param repoRoot - Path to the repository root
125
+ * @param sha - Commit SHA to get diff for (defaults to HEAD)
122
126
  */
123
- async function getCommitDiff(repoRoot, sha) {
127
+ async function getCommitDiff(repoRoot, sha = 'HEAD') {
124
128
  const git = (0, simple_git_1.default)(repoRoot);
125
- // Check if we're in a git repo
126
- const isRepo = await git.checkIsRepo();
127
- if (!isRepo) {
128
- throw new Error('Not a git repository. Threadline requires a git repository.');
129
- }
130
- // Get diff for the commit
131
- // Use git show to get the commit diff
129
+ // Get diff using git show
132
130
  let diff;
133
131
  let changedFiles;
134
132
  try {
135
- // Get diff using git show
136
133
  diff = await git.show([sha, '--format=', '--no-color', '-U200']);
137
134
  // Get changed files using git show --name-only
138
135
  const commitFiles = await git.show([sha, '--name-only', '--format=', '--pretty=format:']);
@@ -142,18 +139,8 @@ async function getCommitDiff(repoRoot, sha) {
142
139
  .map(line => line.trim());
143
140
  }
144
141
  catch (error) {
145
- // Fallback: try git diff format
146
- try {
147
- diff = await git.diff([`${sha}^..${sha}`, '-U200']);
148
- // Get files from diff summary
149
- const diffSummary = await git.diffSummary([`${sha}^..${sha}`]);
150
- changedFiles = diffSummary.files.map(f => f.file);
151
- }
152
- catch (diffError) {
153
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
154
- const diffErrorMessage = diffError instanceof Error ? diffError.message : 'Unknown error';
155
- throw new Error(`Commit ${sha} not found or invalid: ${errorMessage || diffErrorMessage}`);
156
- }
142
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
143
+ throw new Error(`Failed to get diff for commit ${sha}: ${errorMessage}`);
157
144
  }
158
145
  return {
159
146
  diff: diff || '',
@@ -71,53 +71,26 @@ async function getGitHubContext(repoRoot) {
71
71
  * Gets diff for GitHub Actions CI environment
72
72
  *
73
73
  * Strategy:
74
- * - PR context: Fetch base branch on-demand, compare base vs HEAD (full PR diff)
75
- * - Any push (main or feature branch): Compare last commit only (HEAD~1...HEAD)
74
+ * - PR context: Uses shared getPRDiff() - fetches base branch, compares against HEAD
75
+ * - Any push (main or feature branch): Compare last commit only using git show HEAD
76
76
  *
77
77
  * Note: GitHub Actions does shallow clones by default (fetch-depth: 1), so we fetch
78
78
  * the base branch on-demand. HEAD points to the merge commit which contains all PR changes.
79
79
  */
80
80
  async function getDiff(repoRoot) {
81
- const git = (0, simple_git_1.default)(repoRoot);
82
81
  const eventName = process.env.GITHUB_EVENT_NAME;
83
82
  const baseRef = process.env.GITHUB_BASE_REF;
84
- // PR Context: Only pull_request events have GITHUB_BASE_REF set
83
+ // PR Context: Use shared getPRDiff() implementation
85
84
  if (eventName === 'pull_request') {
86
85
  if (!baseRef) {
87
86
  throw new Error('GitHub PR context detected but GITHUB_BASE_REF is missing. ' +
88
87
  'This should be automatically provided by GitHub Actions.');
89
88
  }
90
- // Fetch base branch on-demand (works with shallow clones)
91
- logger_1.logger.debug(`Fetching base branch: origin/${baseRef}`);
92
- try {
93
- await git.fetch(['origin', `${baseRef}:refs/remotes/origin/${baseRef}`, '--depth=1']);
94
- }
95
- catch (fetchError) {
96
- throw new Error(`Failed to fetch base branch origin/${baseRef}. ` +
97
- `This is required for PR diff comparison. Error: ${fetchError instanceof Error ? fetchError.message : 'Unknown error'}`);
98
- }
99
- logger_1.logger.debug(`PR context, using origin/${baseRef}..HEAD (two dots for direct comparison)`);
100
- // Use two dots (..) instead of three dots (...) for direct comparison
101
- // Three dots requires finding merge base which can fail with shallow clones
102
- // Two dots shows all changes in HEAD that aren't in origin/${baseRef}
103
- const diff = await git.diff([`origin/${baseRef}..HEAD`, '-U200']);
104
- const diffSummary = await git.diffSummary([`origin/${baseRef}..HEAD`]);
105
- const changedFiles = diffSummary.files.map(f => f.file);
106
- return {
107
- diff: diff || '',
108
- changedFiles
109
- };
89
+ return (0, diff_1.getPRDiff)(repoRoot, baseRef, logger_1.logger);
110
90
  }
111
91
  // Any push (main or feature branch): Review last commit only
112
- // Use git show HEAD - works with shallow clones and shows the commit diff
113
- const diff = await git.show(['HEAD', '--format=', '--no-color', '-U200']);
114
- // Extract changed files from git show
115
- const showNameOnly = await git.show(['HEAD', '--name-only', '--format=', '--pretty=format:']);
116
- const changedFiles = showNameOnly
117
- .split('\n')
118
- .filter(line => line.trim().length > 0)
119
- .map(line => line.trim());
120
- return { diff: diff || '', changedFiles };
92
+ // Use shared getCommitDiff (defaults to HEAD)
93
+ return (0, diff_1.getCommitDiff)(repoRoot);
121
94
  }
122
95
  /**
123
96
  * Gets repository name for GitHub Actions
@@ -73,7 +73,6 @@ async function getGitLabContext(repoRoot) {
73
73
  * For regular pushes, HEAD~1...HEAD works without additional fetching.
74
74
  */
75
75
  async function getDiff(repoRoot) {
76
- const git = (0, simple_git_1.default)(repoRoot);
77
76
  const mrIid = process.env.CI_MERGE_REQUEST_IID;
78
77
  const targetBranch = process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME;
79
78
  // MR Context: Use shared getPRDiff() implementation
@@ -85,10 +84,8 @@ async function getDiff(repoRoot) {
85
84
  return (0, diff_1.getPRDiff)(repoRoot, targetBranch, logger_1.logger);
86
85
  }
87
86
  // Any push (main or feature branch): Review last commit only
88
- const diff = await git.diff(['HEAD~1...HEAD', '-U200']);
89
- const diffSummary = await git.diffSummary(['HEAD~1...HEAD']);
90
- const changedFiles = diffSummary.files.map(f => f.file);
91
- return { diff: diff || '', changedFiles };
87
+ // Use shared getCommitDiff (defaults to HEAD)
88
+ return (0, diff_1.getCommitDiff)(repoRoot);
92
89
  }
93
90
  /**
94
91
  * Gets repository name for GitLab CI
package/dist/git/local.js CHANGED
@@ -29,7 +29,7 @@ async function getLocalContext(repoRoot, commitSha) {
29
29
  throw new Error('Not a git repository. Threadline requires a git repository.');
30
30
  }
31
31
  // Get all Local context
32
- const diff = commitSha ? await getCommitDiff(repoRoot, commitSha) : await getDiff(repoRoot);
32
+ const diff = commitSha ? await (0, diff_1.getCommitDiff)(repoRoot, commitSha) : await getDiff(repoRoot);
33
33
  const repoName = await getRepoName(repoRoot);
34
34
  const branchName = await getBranchName(repoRoot);
35
35
  const context = commitSha ? { type: 'commit', commitSha } : { type: 'local' };
@@ -96,24 +96,6 @@ async function getDiff(repoRoot) {
96
96
  changedFiles
97
97
  };
98
98
  }
99
- /**
100
- * Get diff for a specific commit (when --commit flag is used)
101
- */
102
- async function getCommitDiff(repoRoot, commitSha) {
103
- const git = (0, simple_git_1.default)(repoRoot);
104
- // Get diff using git show
105
- const diff = await git.show([commitSha, '--format=', '--no-color', '-U200']);
106
- // Get changed files using git show --name-only
107
- const commitFiles = await git.show([commitSha, '--name-only', '--format=', '--pretty=format:']);
108
- const changedFiles = commitFiles
109
- .split('\n')
110
- .filter(line => line.trim().length > 0)
111
- .map(line => line.trim());
112
- return {
113
- diff: diff || '',
114
- changedFiles
115
- };
116
- }
117
99
  /**
118
100
  * Gets repository name for local environment
119
101
  */
@@ -58,29 +58,13 @@ async function getVercelContext(repoRoot) {
58
58
  /**
59
59
  * Get diff for Vercel CI environment
60
60
  *
61
- * Vercel provides VERCEL_GIT_COMMIT_SHA which contains the commit being deployed.
62
- * This function gets the diff for that specific commit using git show.
61
+ * Vercel only supports commit context (no PRs).
62
+ * Uses shared getCommitDiff with HEAD (which equals VERCEL_GIT_COMMIT_SHA).
63
63
  */
64
64
  async function getDiff(repoRoot) {
65
- const git = (0, simple_git_1.default)(repoRoot);
66
- // Get commit SHA from Vercel environment variable
67
- const commitSha = process.env.VERCEL_GIT_COMMIT_SHA;
68
- if (!commitSha) {
69
- throw new Error('VERCEL_GIT_COMMIT_SHA environment variable is not set. ' +
70
- 'This should be automatically provided by Vercel CI.');
71
- }
72
- // Get diff using git show - this is the ONLY way we get diff in Vercel
73
- const diff = await git.show([commitSha, '--format=', '--no-color', '-U200']);
74
- // Get changed files using git show --name-only
75
- const commitFiles = await git.show([commitSha, '--name-only', '--format=', '--pretty=format:']);
76
- const changedFiles = commitFiles
77
- .split('\n')
78
- .filter(line => line.trim().length > 0)
79
- .map(line => line.trim());
80
- return {
81
- diff: diff || '', // Empty diff is legitimate (e.g., metadata-only commits, merge commits)
82
- changedFiles
83
- };
65
+ // Use shared getCommitDiff (defaults to HEAD)
66
+ // In Vercel, HEAD is the commit being deployed
67
+ return (0, diff_1.getCommitDiff)(repoRoot);
84
68
  }
85
69
  /**
86
70
  * Gets repository name for Vercel
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "threadlines",
3
- "version": "0.2.15",
3
+ "version": "0.2.16",
4
4
  "description": "Threadlines CLI - AI-powered linter based on your natural language documentation",
5
5
  "main": "dist/index.js",
6
6
  "bin": {