threadlines 0.2.6 → 0.2.7

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.
@@ -147,11 +147,11 @@ async function checkCommand(options) {
147
147
  let branchName;
148
148
  let metadata = {};
149
149
  // Check for explicit flags
150
- const explicitFlags = [options.branch, options.commit, options.file, options.folder, options.files].filter(Boolean);
150
+ const explicitFlags = [options.commit, options.file, options.folder, options.files].filter(Boolean);
151
151
  // Validate mutually exclusive flags
152
152
  if (explicitFlags.length > 1) {
153
153
  console.error(chalk_1.default.red('❌ Error: Only one review option can be specified at a time'));
154
- console.log(chalk_1.default.gray(' Options: --branch, --commit, --file, --folder, --files'));
154
+ console.log(chalk_1.default.gray(' Options: --commit, --file, --folder, --files'));
155
155
  process.exit(1);
156
156
  }
157
157
  // CI environments: auto-detect only, flags are ignored with warning
@@ -159,10 +159,9 @@ async function checkCommand(options) {
159
159
  if ((0, environment_1.isCIEnvironment)(environment)) {
160
160
  // Warn if flags are passed in CI - they're meant for local development
161
161
  if (explicitFlags.length > 0) {
162
- const flagName = options.branch ? '--branch' :
163
- options.commit ? '--commit' :
164
- options.file ? '--file' :
165
- options.folder ? '--folder' : '--files';
162
+ const flagName = options.commit ? '--commit' :
163
+ options.file ? '--file' :
164
+ options.folder ? '--folder' : '--files';
166
165
  console.log(chalk_1.default.yellow(`⚠️ Warning: ${flagName} flag ignored in CI environment. Using auto-detection.\n`));
167
166
  }
168
167
  // CI auto-detect: use environment-specific context
@@ -199,20 +198,6 @@ async function checkCommand(options) {
199
198
  console.log(chalk_1.default.gray(`📝 Reading ${options.files.length} file(s)...`));
200
199
  gitDiff = await (0, file_1.getMultipleFilesContent)(repoRoot, options.files);
201
200
  }
202
- else if (options.branch) {
203
- console.log(chalk_1.default.gray(`📝 Collecting git changes for branch: ${options.branch}...`));
204
- gitDiff = await (0, diff_1.getBranchDiff)(repoRoot, options.branch);
205
- // Use local context for metadata
206
- const localContext = await (0, local_1.getLocalContext)(repoRoot);
207
- repoName = localContext.repoName;
208
- branchName = localContext.branchName;
209
- metadata = {
210
- commitSha: localContext.commitSha,
211
- commitMessage: localContext.commitMessage,
212
- commitAuthorName: localContext.commitAuthor.name,
213
- commitAuthorEmail: localContext.commitAuthor.email
214
- };
215
- }
216
201
  else if (options.commit) {
217
202
  console.log(chalk_1.default.gray(`📝 Collecting git changes for commit: ${options.commit}...`));
218
203
  gitDiff = await (0, diff_1.getCommitDiff)(repoRoot, options.commit);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * Bitbucket Pipelines Environment - Complete Isolation
3
+ * Bitbucket Pipelines Environment
4
4
  *
5
5
  * All Bitbucket-specific logic is contained in this file.
6
6
  * No dependencies on other environment implementations.
@@ -65,30 +65,17 @@ async function getBitbucketContext(repoRoot) {
65
65
  /**
66
66
  * Get diff for Bitbucket Pipelines environment
67
67
  *
68
- * Bitbucket Pipelines with depth: full has full git history available,
69
- * including origin/main. Unlike GitLab, no fetch is needed.
68
+ * Strategy:
69
+ * - PR context: Compare source branch vs target branch (full PR diff)
70
+ * - Any push (main or feature branch): Compare last commit only (HEAD~1...HEAD)
70
71
  *
71
- * Diff Strategy:
72
- *
73
- * | Scenario | Target Branch Known? | Diff Command |
74
- * |---------------------------|---------------------------------------------|-------------------------------------------|
75
- * | PR | ✅ Yes - BITBUCKET_PR_DESTINATION_BRANCH | origin/${destination}...HEAD |
76
- * | Feature branch (no PR) | ❌ No - detect main/master | origin/main...HEAD or origin/master...HEAD|
77
- * | Push to default branch | N/A | HEAD~1...HEAD |
78
- *
79
- * Key point: For PRs, Bitbucket provides BITBUCKET_PR_DESTINATION_BRANCH - this is the
80
- * most relevant comparison point because it's where the code will be merged.
81
- *
82
- * For non-PR feature branches, Bitbucket does NOT provide a default branch env var
83
- * (unlike GitLab's CI_DEFAULT_BRANCH), so we detect by checking if origin/main or
84
- * origin/master exists.
72
+ * Note: Bitbucket Pipelines with depth: full has full git history available.
85
73
  */
86
74
  async function getDiff(repoRoot) {
87
75
  const git = (0, simple_git_1.default)(repoRoot);
88
- const branchName = process.env.BITBUCKET_BRANCH;
89
76
  const prId = process.env.BITBUCKET_PR_ID;
90
77
  const prDestinationBranch = process.env.BITBUCKET_PR_DESTINATION_BRANCH;
91
- // Scenario 1: PR context - use the target branch from env var
78
+ // PR Context: Compare source vs target branch
92
79
  if (prId) {
93
80
  if (!prDestinationBranch) {
94
81
  throw new Error('Bitbucket PR context detected but BITBUCKET_PR_DESTINATION_BRANCH is not set. ' +
@@ -100,70 +87,12 @@ async function getDiff(repoRoot) {
100
87
  const changedFiles = diffSummary.files.map(f => f.file);
101
88
  return { diff: diff || '', changedFiles };
102
89
  }
103
- // Scenario 2: Non-PR push
104
- if (!branchName) {
105
- throw new Error('Bitbucket Pipelines: BITBUCKET_BRANCH environment variable is not set. ' +
106
- 'This should be automatically provided by Bitbucket Pipelines.');
107
- }
108
- // Detect the default branch (Bitbucket doesn't provide this as an env var)
109
- const defaultBranch = await detectDefaultBranch(git);
110
- // If we're on the default branch, just show the last commit
111
- if (branchName === defaultBranch) {
112
- console.log(` [Bitbucket] Push to ${defaultBranch}, using HEAD~1...HEAD`);
113
- const diff = await git.diff(['HEAD~1...HEAD', '-U200']);
114
- const diffSummary = await git.diffSummary(['HEAD~1...HEAD']);
115
- const changedFiles = diffSummary.files.map(f => f.file);
116
- return { diff: diff || '', changedFiles };
117
- }
118
- // Feature branch: compare against default branch
119
- // This shows all changes the branch introduces, correctly excluding
120
- // any commits merged in from the default branch
121
- console.log(` [Bitbucket] Feature branch "${branchName}", using origin/${defaultBranch}...HEAD`);
122
- const diff = await git.diff([`origin/${defaultBranch}...HEAD`, '-U200']);
123
- const diffSummary = await git.diffSummary([`origin/${defaultBranch}...HEAD`]);
90
+ // Any push (main or feature branch): Review last commit only
91
+ const diff = await git.diff(['HEAD~1...HEAD', '-U200']);
92
+ const diffSummary = await git.diffSummary(['HEAD~1...HEAD']);
124
93
  const changedFiles = diffSummary.files.map(f => f.file);
125
94
  return { diff: diff || '', changedFiles };
126
95
  }
127
- /**
128
- * Detect the default branch for Bitbucket Pipelines.
129
- *
130
- * Bitbucket does NOT provide a default branch env var (unlike GitLab's CI_DEFAULT_BRANCH
131
- * or GitHub's repository.default_branch in the event JSON).
132
- *
133
- * We try 'main' first (most common), then 'master' as fallback.
134
- * This covers the vast majority of repositories.
135
- *
136
- * ---
137
- * Design Decision: We compare against main instead of just checking the last commit
138
- *
139
- * Threadlines assumes that feature branches are intended to eventually merge to the
140
- * default branch. Comparing against main shows ALL changes the branch introduces,
141
- * which is what you want to review before merging.
142
- *
143
- * Per-commit checking happens during local development.
144
- * ---
145
- */
146
- async function detectDefaultBranch(git) {
147
- // Try 'main' first (modern default)
148
- try {
149
- await git.revparse(['--verify', 'origin/main']);
150
- return 'main';
151
- }
152
- catch {
153
- // origin/main doesn't exist, try master
154
- }
155
- // Try 'master' (legacy default)
156
- try {
157
- await git.revparse(['--verify', 'origin/master']);
158
- return 'master';
159
- }
160
- catch {
161
- // origin/master doesn't exist either
162
- }
163
- throw new Error('Bitbucket Pipelines: Cannot determine default branch. ' +
164
- 'Neither origin/main nor origin/master found. ' +
165
- 'For repositories with a different default branch, create a PR to trigger branch comparison.');
166
- }
167
96
  /**
168
97
  * Gets repository name for Bitbucket Pipelines
169
98
  *
@@ -190,7 +119,10 @@ function getBranchName() {
190
119
  return branchName;
191
120
  }
192
121
  /**
193
- * Detects Bitbucket context (PR, branch, or commit)
122
+ * Detects Bitbucket context (PR or commit)
123
+ *
124
+ * - PR context: When BITBUCKET_PR_ID is set
125
+ * - Commit context: Any push (main or feature branch) - reviews single commit
194
126
  */
195
127
  function detectContext() {
196
128
  // PR context
@@ -205,22 +137,16 @@ function detectContext() {
205
137
  targetBranch: prDestinationBranch
206
138
  };
207
139
  }
208
- // Branch context
209
- if (process.env.BITBUCKET_BRANCH) {
210
- return {
211
- type: 'branch',
212
- branchName: process.env.BITBUCKET_BRANCH
213
- };
214
- }
215
- // Commit context
140
+ // Any push (main or feature branch) → commit context
216
141
  if (process.env.BITBUCKET_COMMIT) {
217
142
  return {
218
143
  type: 'commit',
219
144
  commitSha: process.env.BITBUCKET_COMMIT
220
145
  };
221
146
  }
222
- // Fallback to local (shouldn't happen in Bitbucket Pipelines)
223
- return { type: 'local' };
147
+ throw new Error('Bitbucket Pipelines: Could not detect context. ' +
148
+ 'Expected BITBUCKET_PR_ID or BITBUCKET_COMMIT to be set. ' +
149
+ 'This should be automatically provided by Bitbucket Pipelines.');
224
150
  }
225
151
  /**
226
152
  * Gets commit SHA from Bitbucket environment
package/dist/git/diff.js CHANGED
@@ -3,185 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getBranchDiff = getBranchDiff;
7
6
  exports.getCommitMessage = getCommitMessage;
8
7
  exports.getCommitAuthor = getCommitAuthor;
9
8
  exports.getCommitDiff = getCommitDiff;
10
- exports.getPRMRDiff = getPRMRDiff;
11
9
  const simple_git_1 = __importDefault(require("simple-git"));
12
- /**
13
- * Get diff for a specific branch (all commits vs base branch)
14
- * Uses git merge-base to find common ancestor, then diffs from there
15
- */
16
- async function getBranchDiff(repoRoot, branchName, baseBranch) {
17
- const git = (0, simple_git_1.default)(repoRoot);
18
- // Check if we're in a git repo
19
- const isRepo = await git.checkIsRepo();
20
- if (!isRepo) {
21
- throw new Error('Not a git repository. Threadline requires a git repository.');
22
- }
23
- // Determine base branch
24
- let base;
25
- if (baseBranch) {
26
- // Use provided base branch
27
- base = baseBranch;
28
- }
29
- else {
30
- // Check if the branch itself is a base branch (main/master)
31
- const baseBranchNames = ['main', 'master'];
32
- const isBaseBranch = baseBranchNames.includes(branchName.toLowerCase());
33
- if (isBaseBranch) {
34
- // For main/master branch, compare against previous commit (HEAD~1)
35
- // This checks what changed in the most recent commit
36
- try {
37
- const previousCommit = await git.revparse(['HEAD~1']);
38
- // Use commit-based diff instead
39
- const diff = await git.diff([`${previousCommit}..HEAD`, '-U200']);
40
- const diffSummary = await git.diffSummary([`${previousCommit}..HEAD`]);
41
- const changedFiles = diffSummary.files.map(f => f.file);
42
- return {
43
- diff: diff || '',
44
- changedFiles
45
- };
46
- }
47
- catch (error) {
48
- // If no previous commit, return empty (first commit)
49
- const errorMessage = error instanceof Error ? error.message : 'HEAD~1 does not exist';
50
- console.log(`[DEBUG] No previous commit found (first commit or error): ${errorMessage}`);
51
- return {
52
- diff: '',
53
- changedFiles: []
54
- };
55
- }
56
- }
57
- // Try to detect base branch: upstream, default branch, or common names
58
- base = await detectBaseBranch(git, branchName);
59
- }
60
- // Helper function to detect base branch
61
- // Returns the branch name to use in git commands (may be local or remote)
62
- // In CI environments, prioritizes remote refs since local branches often don't exist
63
- // Note: Vercel is excluded here because it uses commit context, not branch context
64
- async function detectBaseBranch(git, branchName) {
65
- const isCI = !!(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI);
66
- // Strategy 1: Try upstream tracking branch (most reliable if set)
67
- try {
68
- const upstream = await git.revparse(['--abbrev-ref', '--symbolic-full-name', `${branchName}@{u}`]);
69
- const upstreamBranch = upstream.replace(/^origin\//, '');
70
- // Don't use the branch itself as its base
71
- if (upstreamBranch !== branchName) {
72
- // In CI, prefer remote refs since local branches often don't exist
73
- if (isCI) {
74
- console.log(`[DEBUG] CI environment detected, using upstream tracking branch (remote): ${upstream}`);
75
- return upstream;
76
- }
77
- // In local dev, check if local branch exists
78
- try {
79
- await git.revparse([upstreamBranch]);
80
- console.log(`[DEBUG] Using upstream tracking branch (local): ${upstreamBranch}`);
81
- return upstreamBranch;
82
- }
83
- catch {
84
- console.log(`[DEBUG] Upstream tracking branch exists but local branch '${upstreamBranch}' not found, using remote: ${upstream}`);
85
- return upstream;
86
- }
87
- }
88
- else {
89
- console.log(`[DEBUG] Upstream tracking branch '${upstreamBranch}' is the same as current branch, skipping`);
90
- }
91
- }
92
- catch (error) {
93
- const errorMessage = error instanceof Error ? error.message : 'no upstream configured';
94
- console.log(`[DEBUG] Upstream tracking branch not set for '${branchName}': ${errorMessage}`);
95
- }
96
- // Strategy 2: Try default branch from origin/HEAD (reliable if configured)
97
- try {
98
- const defaultBranch = await git.revparse(['--abbrev-ref', 'refs/remotes/origin/HEAD']);
99
- const defaultBranchName = defaultBranch.replace(/^origin\//, '');
100
- // Don't use the branch itself as its base
101
- if (defaultBranchName !== branchName) {
102
- // In CI, prefer remote refs
103
- if (isCI) {
104
- console.log(`[DEBUG] CI environment detected, using default branch (remote): ${defaultBranch}`);
105
- return defaultBranch;
106
- }
107
- // In local dev, check if local branch exists
108
- try {
109
- await git.revparse([defaultBranchName]);
110
- console.log(`[DEBUG] Using default branch (local): ${defaultBranchName}`);
111
- return defaultBranchName;
112
- }
113
- catch {
114
- console.log(`[DEBUG] Default branch exists but local branch '${defaultBranchName}' not found, using remote: ${defaultBranch}`);
115
- return defaultBranch;
116
- }
117
- }
118
- else {
119
- console.log(`[DEBUG] Default branch '${defaultBranchName}' is the same as current branch, skipping`);
120
- }
121
- }
122
- catch (error) {
123
- const errorMessage = error instanceof Error ? error.message : 'not found';
124
- console.log(`[DEBUG] Default branch (refs/remotes/origin/HEAD) not configured: ${errorMessage}`);
125
- }
126
- // Strategy 3: Try common branch names by checking remote refs first, then local branches
127
- // This works reliably in CI with fetch-depth: 0, and also works locally
128
- const commonBases = ['main', 'master', 'develop'];
129
- for (const candidate of commonBases) {
130
- if (candidate.toLowerCase() === branchName.toLowerCase()) {
131
- continue; // Skip if it's the same branch
132
- }
133
- // Try remote ref first
134
- try {
135
- await git.revparse([`origin/${candidate}`]);
136
- // In CI, prefer remote refs since local branches often don't exist
137
- if (isCI) {
138
- console.log(`[DEBUG] CI environment detected, using common branch name (remote): origin/${candidate}`);
139
- return `origin/${candidate}`;
140
- }
141
- // In local dev, check if local branch exists
142
- try {
143
- await git.revparse([candidate]);
144
- console.log(`[DEBUG] Using common branch name (local): ${candidate}`);
145
- return candidate;
146
- }
147
- catch {
148
- console.log(`[DEBUG] Common branch '${candidate}' exists remotely but not locally, using remote: origin/${candidate}`);
149
- return `origin/${candidate}`;
150
- }
151
- }
152
- catch (error) {
153
- const errorMessage = error instanceof Error ? error.message : 'does not exist';
154
- console.log(`[DEBUG] Remote branch 'origin/${candidate}' not found: ${errorMessage}`);
155
- // If remote doesn't exist, also try local branch (especially for CI like Vercel)
156
- try {
157
- await git.revparse([candidate]);
158
- console.log(`[DEBUG] Remote 'origin/${candidate}' not available, but local branch '${candidate}' found - using local`);
159
- return candidate;
160
- }
161
- catch (localError) {
162
- const localErrorMessage = localError instanceof Error ? localError.message : 'does not exist';
163
- console.log(`[DEBUG] Local branch '${candidate}' also not found: ${localErrorMessage}`);
164
- // Continue to next candidate
165
- }
166
- }
167
- }
168
- // All strategies failed - provide clear error with context
169
- throw new Error(`Could not determine base branch for '${branchName}'. ` +
170
- `Tried: upstream tracking, default branch (origin/HEAD), and common names (main, master, develop). ` +
171
- `Please specify base branch with --base flag or configure upstream tracking with: ` +
172
- `git branch --set-upstream-to=origin/main ${branchName}`);
173
- }
174
- // Get diff between base and branch (cumulative diff of all commits)
175
- // Format: git diff base...branch (three-dot notation finds common ancestor)
176
- const diff = await git.diff([`${base}...${branchName}`, '-U200']);
177
- // Get list of changed files
178
- const diffSummary = await git.diffSummary([`${base}...${branchName}`]);
179
- const changedFiles = diffSummary.files.map(f => f.file);
180
- return {
181
- diff: diff || '',
182
- changedFiles
183
- };
184
- }
185
10
  /**
186
11
  * Get commit message for a specific commit SHA
187
12
  * Returns full commit message (subject + body) or null if commit not found
@@ -273,24 +98,3 @@ async function getCommitDiff(repoRoot, sha) {
273
98
  changedFiles
274
99
  };
275
100
  }
276
- /**
277
- * Get diff for PR/MR (source branch vs target branch)
278
- */
279
- async function getPRMRDiff(repoRoot, sourceBranch, targetBranch) {
280
- const git = (0, simple_git_1.default)(repoRoot);
281
- // Check if we're in a git repo
282
- const isRepo = await git.checkIsRepo();
283
- if (!isRepo) {
284
- throw new Error('Not a git repository. Threadline requires a git repository.');
285
- }
286
- // Get diff between target and source (cumulative diff)
287
- // Format: git diff target...source (three-dot notation finds common ancestor)
288
- const diff = await git.diff([`${targetBranch}...${sourceBranch}`, '-U200']);
289
- // Get list of changed files
290
- const diffSummary = await git.diffSummary([`${targetBranch}...${sourceBranch}`]);
291
- const changedFiles = diffSummary.files.map(f => f.file);
292
- return {
293
- diff: diff || '',
294
- changedFiles
295
- };
296
- }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * GitHub Actions Environment - Complete Isolation
3
+ * GitHub Actions Environment
4
4
  *
5
5
  * All GitHub-specific logic is contained in this file.
6
6
  * No dependencies on other environment implementations.
@@ -52,10 +52,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
52
52
  exports.getGitHubContext = getGitHubContext;
53
53
  const simple_git_1 = __importDefault(require("simple-git"));
54
54
  const fs = __importStar(require("fs"));
55
- const repo_1 = require("./repo");
56
55
  const diff_1 = require("./diff");
57
56
  /**
58
- * Gets all GitHub context in one call - completely isolated from other environments.
57
+ * Gets all GitHub context
59
58
  */
60
59
  async function getGitHubContext(repoRoot) {
61
60
  const git = (0, simple_git_1.default)(repoRoot);
@@ -70,7 +69,6 @@ async function getGitHubContext(repoRoot) {
70
69
  const branchName = await getBranchName();
71
70
  const context = detectContext();
72
71
  const commitSha = getCommitSha(context);
73
- // Get commit author (fails loudly if unavailable)
74
72
  // Note: commitSha parameter not needed - GitHub reads from GITHUB_EVENT_PATH JSON
75
73
  const commitAuthor = await getCommitAuthor();
76
74
  // Get commit message if we have a SHA
@@ -96,17 +94,17 @@ async function getGitHubContext(repoRoot) {
96
94
  }
97
95
  /**
98
96
  * Gets diff for GitHub Actions CI environment
97
+ *
98
+ * Strategy:
99
+ * - PR context: Compare source branch vs target branch (full PR diff)
100
+ * - Any push (main or feature branch): Compare last commit only (HEAD~1...HEAD)
99
101
  */
100
102
  async function getDiff(repoRoot) {
101
103
  const git = (0, simple_git_1.default)(repoRoot);
102
- const defaultBranch = await (0, repo_1.getDefaultBranchName)(repoRoot);
103
- // Determine context from GitHub environment variables
104
104
  const eventName = process.env.GITHUB_EVENT_NAME;
105
105
  const baseRef = process.env.GITHUB_BASE_REF;
106
106
  const headRef = process.env.GITHUB_HEAD_REF;
107
- const refName = process.env.GITHUB_REF_NAME;
108
- const commitSha = process.env.GITHUB_SHA;
109
- // Scenario 1: PR Context
107
+ // PR Context: Compare source vs target branch
110
108
  if (eventName === 'pull_request') {
111
109
  if (!baseRef || !headRef) {
112
110
  throw new Error('GitHub PR context detected but GITHUB_BASE_REF or GITHUB_HEAD_REF is missing. ' +
@@ -120,36 +118,14 @@ async function getDiff(repoRoot) {
120
118
  changedFiles
121
119
  };
122
120
  }
123
- // Scenario 2 & 4: Default Branch Push
124
- if (refName === defaultBranch && commitSha) {
125
- try {
126
- const diff = await git.diff([`origin/${defaultBranch}~1...origin/${defaultBranch}`, '-U200']);
127
- const diffSummary = await git.diffSummary([`origin/${defaultBranch}~1...origin/${defaultBranch}`]);
128
- const changedFiles = diffSummary.files.map(f => f.file);
129
- return {
130
- diff: diff || '',
131
- changedFiles
132
- };
133
- }
134
- catch (error) {
135
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
136
- throw new Error(`Could not get diff for default branch '${defaultBranch}'. ` +
137
- `This might be the first commit on the branch. Error: ${errorMessage}`);
138
- }
139
- }
140
- // Scenario 3: Feature Branch Push
141
- if (refName) {
142
- const diff = await git.diff([`origin/${defaultBranch}...origin/${refName}`, '-U200']);
143
- const diffSummary = await git.diffSummary([`origin/${defaultBranch}...origin/${refName}`]);
144
- const changedFiles = diffSummary.files.map(f => f.file);
145
- return {
146
- diff: diff || '',
147
- changedFiles
148
- };
149
- }
150
- throw new Error('GitHub Actions environment detected but no valid context found. ' +
151
- 'Expected GITHUB_EVENT_NAME="pull_request" (with GITHUB_BASE_REF/GITHUB_HEAD_REF) ' +
152
- 'or GITHUB_REF_NAME for branch context.');
121
+ // Any push (main or feature branch): Review last commit only
122
+ const diff = await git.diff(['HEAD~1...HEAD', '-U200']);
123
+ const diffSummary = await git.diffSummary(['HEAD~1...HEAD']);
124
+ const changedFiles = diffSummary.files.map(f => f.file);
125
+ return {
126
+ diff: diff || '',
127
+ changedFiles
128
+ };
153
129
  }
154
130
  /**
155
131
  * Gets repository name for GitHub Actions
@@ -175,10 +151,13 @@ async function getBranchName() {
175
151
  return refName;
176
152
  }
177
153
  /**
178
- * Detects GitHub context (PR, branch, or commit)
154
+ * Detects GitHub context (PR or commit)
155
+ *
156
+ * - PR context: When GITHUB_EVENT_NAME is 'pull_request'
157
+ * - Commit context: Any push (main or feature branch) - reviews single commit
179
158
  */
180
159
  function detectContext() {
181
- // 1. Check for PR context
160
+ // PR context
182
161
  if (process.env.GITHUB_EVENT_NAME === 'pull_request') {
183
162
  const targetBranch = process.env.GITHUB_BASE_REF;
184
163
  const sourceBranch = process.env.GITHUB_HEAD_REF;
@@ -192,22 +171,16 @@ function detectContext() {
192
171
  };
193
172
  }
194
173
  }
195
- // 2. Check for branch context
196
- if (process.env.GITHUB_REF_NAME) {
197
- return {
198
- type: 'branch',
199
- branchName: process.env.GITHUB_REF_NAME
200
- };
201
- }
202
- // 3. Check for commit context
174
+ // Any push (main or feature branch) → commit context
203
175
  if (process.env.GITHUB_SHA) {
204
176
  return {
205
177
  type: 'commit',
206
178
  commitSha: process.env.GITHUB_SHA
207
179
  };
208
180
  }
209
- // 4. Fallback to local (shouldn't happen in GitHub Actions, but TypeScript needs it)
210
- return { type: 'local' };
181
+ throw new Error('GitHub Actions: Could not detect context. ' +
182
+ 'Expected GITHUB_EVENT_NAME="pull_request" or GITHUB_SHA to be set. ' +
183
+ 'This should be automatically provided by GitHub Actions.');
211
184
  }
212
185
  /**
213
186
  * Gets commit SHA from context
@@ -216,7 +189,7 @@ function getCommitSha(context) {
216
189
  if (context.type === 'commit') {
217
190
  return context.commitSha;
218
191
  }
219
- if (context.type === 'branch' || context.type === 'pr') {
192
+ if (context.type === 'pr') {
220
193
  return process.env.GITHUB_SHA;
221
194
  }
222
195
  return undefined;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * GitLab CI Environment - Complete Isolation
3
+ * GitLab CI Environment
4
4
  *
5
5
  * All GitLab-specific logic is contained in this file.
6
6
  * No dependencies on other environment implementations.
@@ -20,7 +20,7 @@ exports.getGitLabContext = getGitLabContext;
20
20
  const simple_git_1 = __importDefault(require("simple-git"));
21
21
  const diff_1 = require("./diff");
22
22
  /**
23
- * Gets all GitLab context in one call - completely isolated from other environments.
23
+ * Gets all GitLab context
24
24
  */
25
25
  async function getGitLabContext(repoRoot) {
26
26
  const git = (0, simple_git_1.default)(repoRoot);
@@ -61,29 +61,19 @@ async function getGitLabContext(repoRoot) {
61
61
  /**
62
62
  * Get diff for GitLab CI environment
63
63
  *
64
- * GitLab CI does a shallow clone of ONLY the current branch. The default branch
65
- * (e.g., origin/main) is NOT available by default. We fetch it on-demand.
64
+ * Strategy:
65
+ * - MR context: Fetch target branch, compare source vs target (full MR diff)
66
+ * - Any push (main or feature branch): Compare last commit only (HEAD~1...HEAD)
66
67
  *
67
- * Scenarios handled:
68
- *
69
- * 1. MR Context (CI_MERGE_REQUEST_IID is set):
70
- * - Fetch target branch, then diff target vs source
71
- *
72
- * 2. Feature Branch Push (CI_COMMIT_REF_NAME != CI_DEFAULT_BRANCH):
73
- * - Fetch default branch, then diff default vs feature
74
- *
75
- * 3. Default Branch Push (CI_COMMIT_REF_NAME == CI_DEFAULT_BRANCH):
76
- * - Use HEAD~1...HEAD (last commit only, no fetch needed)
68
+ * Note: GitLab CI does a shallow clone, so we fetch the target branch for MR context.
69
+ * For regular pushes, HEAD~1...HEAD works without additional fetching.
77
70
  */
78
71
  async function getDiff(repoRoot) {
79
72
  const git = (0, simple_git_1.default)(repoRoot);
80
- // Get GitLab CI environment variables
81
73
  const mrIid = process.env.CI_MERGE_REQUEST_IID;
82
74
  const targetBranch = process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME;
83
75
  const sourceBranch = process.env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME;
84
- const refName = process.env.CI_COMMIT_REF_NAME;
85
- const defaultBranch = process.env.CI_DEFAULT_BRANCH || 'main';
86
- // Scenario 1: MR Context
76
+ // MR Context: Fetch target branch and compare
87
77
  if (mrIid) {
88
78
  if (!targetBranch || !sourceBranch) {
89
79
  throw new Error('GitLab MR context detected but CI_MERGE_REQUEST_TARGET_BRANCH_NAME or ' +
@@ -97,23 +87,9 @@ async function getDiff(repoRoot) {
97
87
  const changedFiles = diffSummary.files.map(f => f.file);
98
88
  return { diff: diff || '', changedFiles };
99
89
  }
100
- if (!refName) {
101
- throw new Error('GitLab CI: CI_COMMIT_REF_NAME environment variable is not set. ' +
102
- 'This should be automatically provided by GitLab CI.');
103
- }
104
- // Scenario 3: Default Branch Push
105
- if (refName === defaultBranch) {
106
- console.log(` [GitLab] Push to default branch (${defaultBranch}), using HEAD~1...HEAD`);
107
- const diff = await git.diff(['HEAD~1...HEAD', '-U200']);
108
- const diffSummary = await git.diffSummary(['HEAD~1...HEAD']);
109
- const changedFiles = diffSummary.files.map(f => f.file);
110
- return { diff: diff || '', changedFiles };
111
- }
112
- // Scenario 2: Feature Branch Push
113
- console.log(` [GitLab] Feature branch push, fetching default branch: origin/${defaultBranch}`);
114
- await git.fetch(['origin', `${defaultBranch}:refs/remotes/origin/${defaultBranch}`, '--depth=1']);
115
- const diff = await git.diff([`origin/${defaultBranch}...origin/${refName}`, '-U200']);
116
- const diffSummary = await git.diffSummary([`origin/${defaultBranch}...origin/${refName}`]);
90
+ // Any push (main or feature branch): Review last commit only
91
+ const diff = await git.diff(['HEAD~1...HEAD', '-U200']);
92
+ const diffSummary = await git.diffSummary(['HEAD~1...HEAD']);
117
93
  const changedFiles = diffSummary.files.map(f => f.file);
118
94
  return { diff: diff || '', changedFiles };
119
95
  }
@@ -140,10 +116,13 @@ async function getBranchName() {
140
116
  return refName;
141
117
  }
142
118
  /**
143
- * Detects GitLab context (MR, branch, or commit)
119
+ * Detects GitLab context (MR or commit)
120
+ *
121
+ * - MR context: When CI_MERGE_REQUEST_IID is set
122
+ * - Commit context: Any push (main or feature branch) - reviews single commit
144
123
  */
145
124
  function detectContext() {
146
- // 1. Check for MR context
125
+ // MR context
147
126
  const mrIid = process.env.CI_MERGE_REQUEST_IID;
148
127
  if (mrIid) {
149
128
  const targetBranch = process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME;
@@ -157,22 +136,16 @@ function detectContext() {
157
136
  };
158
137
  }
159
138
  }
160
- // 2. Check for branch context
161
- if (process.env.CI_COMMIT_REF_NAME) {
162
- return {
163
- type: 'branch',
164
- branchName: process.env.CI_COMMIT_REF_NAME
165
- };
166
- }
167
- // 3. Check for commit context
139
+ // Any push (main or feature branch) → commit context
168
140
  if (process.env.CI_COMMIT_SHA) {
169
141
  return {
170
142
  type: 'commit',
171
143
  commitSha: process.env.CI_COMMIT_SHA
172
144
  };
173
145
  }
174
- // 4. Fallback to local (shouldn't happen in GitLab CI, but TypeScript needs it)
175
- return { type: 'local' };
146
+ throw new Error('GitLab CI: Could not detect context. ' +
147
+ 'Expected CI_MERGE_REQUEST_IID or CI_COMMIT_SHA to be set. ' +
148
+ 'This should be automatically provided by GitLab CI.');
176
149
  }
177
150
  /**
178
151
  * Gets commit SHA from context
@@ -181,7 +154,7 @@ function getCommitSha(context) {
181
154
  if (context.type === 'commit') {
182
155
  return context.commitSha;
183
156
  }
184
- if (context.type === 'branch' || context.type === 'mr') {
157
+ if (context.type === 'mr') {
185
158
  return process.env.CI_COMMIT_SHA;
186
159
  }
187
160
  return undefined;
package/dist/git/local.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * Local Environment - Complete Isolation
3
+ * Local Environment
4
4
  *
5
5
  * All Local-specific logic is contained in this file.
6
6
  * No dependencies on other environment implementations.
@@ -19,7 +19,7 @@ exports.getLocalContext = getLocalContext;
19
19
  const simple_git_1 = __importDefault(require("simple-git"));
20
20
  const diff_1 = require("./diff");
21
21
  /**
22
- * Gets all Local context in one call - completely isolated from other environments.
22
+ * Gets all Local context
23
23
  */
24
24
  async function getLocalContext(repoRoot, commitSha) {
25
25
  const git = (0, simple_git_1.default)(repoRoot);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * Vercel Environment - Complete Isolation
3
+ * Vercel Environment
4
4
  *
5
5
  * All Vercel-specific logic is contained in this file.
6
6
  * No dependencies on other environment implementations.
@@ -20,7 +20,7 @@ const simple_git_1 = __importDefault(require("simple-git"));
20
20
  const child_process_1 = require("child_process");
21
21
  const diff_1 = require("./diff");
22
22
  /**
23
- * Gets all Vercel context in one call - completely isolated from other environments.
23
+ * Gets all Vercel context
24
24
  */
25
25
  async function getVercelContext(repoRoot) {
26
26
  const git = (0, simple_git_1.default)(repoRoot);
package/dist/index.js CHANGED
@@ -63,7 +63,6 @@ program
63
63
  .description('Check code against your threadlines')
64
64
  .option('--api-url <url>', 'Threadline server URL', process.env.THREADLINE_API_URL || 'https://devthreadline.com')
65
65
  .option('--full', 'Show all results (compliant, attention, not_relevant). Default: only attention items')
66
- .option('--branch <name>', 'Review all commits in branch vs base (e.g., --branch feature/new-feature)')
67
66
  .option('--commit <ref>', 'Review specific commit. Accepts commit SHA or git reference (e.g., HEAD, HEAD~1, abc123). Example: --commit HEAD')
68
67
  .option('--file <path>', 'Review entire file (all lines as additions)')
69
68
  .option('--folder <path>', 'Review all files in folder recursively')
@@ -72,13 +71,12 @@ program
72
71
  Examples:
73
72
  $ threadlines check # Check staged/unstaged changes (local dev)
74
73
  $ threadlines check --commit HEAD # Check latest commit locally
75
- $ threadlines check --branch main # Check all commits in branch vs base
76
74
  $ threadlines check --file src/api.ts # Check entire file
77
75
  $ threadlines check --full # Show all results (not just attention items)
78
76
 
79
77
  Auto-detection in CI:
80
- - CI with branch detected → reviews all commits in branch vs base
81
- - CI with commit SHA detected → reviews specific commit
78
+ - PR/MR context → reviews all changes in the PR/MR
79
+ - Push to any branch → reviews the commit being pushed
82
80
  - Local development → reviews staged/unstaged changes
83
81
  `)
84
82
  .action(check_1.checkCommand);
@@ -1,190 +1,10 @@
1
1
  "use strict";
2
2
  /**
3
- * Review Context Detection
3
+ * Review Context Types
4
4
  *
5
- * Determines what type of code review context we're in:
6
- * - PR/MR: Multiple commits comparing two branches
7
- * - Branch: All commits on a branch vs base branch
8
- * - Commit: Single commit changes
5
+ * Type definitions for the different code review contexts:
6
+ * - PR/MR: Comparing source branch vs target branch (branch-level diff)
7
+ * - Commit: Single commit changes (any push without PR/MR)
9
8
  * - Local: Staged/unstaged changes in working directory
10
- *
11
- * Context detection is environment-specific - each CI platform
12
- * provides different environment variables.
13
9
  */
14
10
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.detectContext = detectContext;
16
- /**
17
- * Detects the review context based on the environment.
18
- *
19
- * Each environment has different environment variables available,
20
- * so detection logic is environment-specific.
21
- */
22
- function detectContext(environment) {
23
- switch (environment) {
24
- case 'github':
25
- return detectGitHubContext();
26
- case 'gitlab':
27
- return detectGitLabContext();
28
- case 'bitbucket':
29
- return detectBitbucketContext();
30
- case 'vercel':
31
- return detectVercelContext();
32
- case 'local':
33
- return { type: 'local' };
34
- default:
35
- return { type: 'local' };
36
- }
37
- }
38
- /**
39
- * GitHub Actions context detection
40
- *
41
- * Environment Variables:
42
- * - PR: GITHUB_EVENT_NAME='pull_request', GITHUB_BASE_REF, GITHUB_HEAD_REF, GITHUB_EVENT_NUMBER
43
- * - Branch: GITHUB_REF_NAME
44
- * - Commit: GITHUB_SHA
45
- */
46
- function detectGitHubContext() {
47
- // 1. Check for PR context
48
- if (process.env.GITHUB_EVENT_NAME === 'pull_request') {
49
- const targetBranch = process.env.GITHUB_BASE_REF;
50
- const sourceBranch = process.env.GITHUB_HEAD_REF;
51
- const prNumber = process.env.GITHUB_EVENT_PULL_REQUEST_NUMBER || process.env.GITHUB_EVENT_NUMBER;
52
- if (targetBranch && sourceBranch && prNumber) {
53
- return {
54
- type: 'pr',
55
- prNumber,
56
- sourceBranch,
57
- targetBranch
58
- };
59
- }
60
- }
61
- // 2. Check for branch context
62
- if (process.env.GITHUB_REF_NAME) {
63
- return {
64
- type: 'branch',
65
- branchName: process.env.GITHUB_REF_NAME
66
- };
67
- }
68
- // 3. Check for commit context
69
- if (process.env.GITHUB_SHA) {
70
- return {
71
- type: 'commit',
72
- commitSha: process.env.GITHUB_SHA
73
- };
74
- }
75
- // 4. Fallback to local
76
- return { type: 'local' };
77
- }
78
- /**
79
- * GitLab CI context detection
80
- *
81
- * Environment Variables:
82
- * - MR: CI_MERGE_REQUEST_IID, CI_MERGE_REQUEST_TARGET_BRANCH_NAME, CI_MERGE_REQUEST_SOURCE_BRANCH_NAME, CI_MERGE_REQUEST_TITLE
83
- * - Branch: CI_COMMIT_REF_NAME
84
- * - Commit: CI_COMMIT_SHA
85
- */
86
- function detectGitLabContext() {
87
- // 1. Check for MR context
88
- if (process.env.CI_MERGE_REQUEST_IID) {
89
- const targetBranch = process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME;
90
- const sourceBranch = process.env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME;
91
- const mrNumber = process.env.CI_MERGE_REQUEST_IID;
92
- const mrTitle = process.env.CI_MERGE_REQUEST_TITLE;
93
- if (targetBranch && sourceBranch && mrNumber) {
94
- return {
95
- type: 'mr',
96
- mrNumber,
97
- sourceBranch,
98
- targetBranch,
99
- prTitle: mrTitle || undefined
100
- };
101
- }
102
- }
103
- // 2. Check for branch context
104
- if (process.env.CI_COMMIT_REF_NAME) {
105
- return {
106
- type: 'branch',
107
- branchName: process.env.CI_COMMIT_REF_NAME
108
- };
109
- }
110
- // 3. Check for commit context
111
- if (process.env.CI_COMMIT_SHA) {
112
- return {
113
- type: 'commit',
114
- commitSha: process.env.CI_COMMIT_SHA
115
- };
116
- }
117
- // 4. Fallback to local
118
- return { type: 'local' };
119
- }
120
- /**
121
- * Vercel context detection
122
- *
123
- * Environment Variables:
124
- * - Branch: VERCEL_GIT_COMMIT_REF
125
- * - Commit: VERCEL_GIT_COMMIT_SHA
126
- *
127
- * Vercel Limitation:
128
- * Vercel performs shallow clones of the repository, typically fetching only the
129
- * specific commit being deployed. The git repository in Vercel's build environment
130
- * does not contain the full git history or remote branch references (e.g., origin/main).
131
- * This means branch-based diff operations (comparing feature branch against base branch)
132
- * are not possible because the base branch refs are not available in the repository.
133
- *
134
- * Solution:
135
- * We hardcode commit context for Vercel, using VERCEL_GIT_COMMIT_SHA to get a
136
- * commit-based diff (comparing the commit against its parent). This works within
137
- * Vercel's constraints since we only need the commit SHA, not branch references.
138
- */
139
- function detectVercelContext() {
140
- // Hardcode commit context for Vercel due to shallow clone limitations
141
- // Vercel's git repository doesn't have base branch refs available
142
- if (process.env.VERCEL_GIT_COMMIT_SHA) {
143
- return {
144
- type: 'commit',
145
- commitSha: process.env.VERCEL_GIT_COMMIT_SHA
146
- };
147
- }
148
- // Fallback to local
149
- return { type: 'local' };
150
- }
151
- /**
152
- * Bitbucket Pipelines context detection
153
- *
154
- * Environment Variables (all tested 2026-01-18):
155
- * - Branch: BITBUCKET_BRANCH
156
- * - Commit: BITBUCKET_COMMIT
157
- * - PR: BITBUCKET_PR_ID, BITBUCKET_PR_DESTINATION_BRANCH
158
- *
159
- * Note: Bitbucket does not provide PR title as an environment variable.
160
- */
161
- function detectBitbucketContext() {
162
- // PR context
163
- const prId = process.env.BITBUCKET_PR_ID;
164
- const prDestinationBranch = process.env.BITBUCKET_PR_DESTINATION_BRANCH;
165
- const sourceBranch = process.env.BITBUCKET_BRANCH;
166
- if (prId && prDestinationBranch && sourceBranch) {
167
- return {
168
- type: 'pr',
169
- prNumber: prId,
170
- sourceBranch,
171
- targetBranch: prDestinationBranch
172
- };
173
- }
174
- // Branch context
175
- if (process.env.BITBUCKET_BRANCH) {
176
- return {
177
- type: 'branch',
178
- branchName: process.env.BITBUCKET_BRANCH
179
- };
180
- }
181
- // Commit context
182
- if (process.env.BITBUCKET_COMMIT) {
183
- return {
184
- type: 'commit',
185
- commitSha: process.env.BITBUCKET_COMMIT
186
- };
187
- }
188
- // Fallback to local
189
- return { type: 'local' };
190
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "threadlines",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Threadlines CLI - AI-powered linter based on your natural language documentation",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/dist/git/repo.js DELETED
@@ -1,253 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.getGitHubRepoName = getGitHubRepoName;
40
- exports.getVercelRepoName = getVercelRepoName;
41
- exports.getLocalRepoName = getLocalRepoName;
42
- exports.getGitHubBranchName = getGitHubBranchName;
43
- exports.getVercelBranchName = getVercelBranchName;
44
- exports.getLocalBranchName = getLocalBranchName;
45
- exports.getGitLabRepoName = getGitLabRepoName;
46
- exports.getGitLabBranchName = getGitLabBranchName;
47
- exports.getDefaultBranchName = getDefaultBranchName;
48
- const simple_git_1 = __importDefault(require("simple-git"));
49
- const fs = __importStar(require("fs"));
50
- const path = __importStar(require("path"));
51
- /**
52
- * GitHub Actions: Get repository name
53
- *
54
- * Uses GITHUB_REPOSITORY environment variable (format: "owner/repo").
55
- * This is the ONLY method for GitHub - no fallbacks, no alternatives.
56
- */
57
- async function getGitHubRepoName(_repoRoot) {
58
- const githubRepo = process.env.GITHUB_REPOSITORY;
59
- if (!githubRepo) {
60
- throw new Error('GitHub Actions: GITHUB_REPOSITORY environment variable is not set. ' +
61
- 'This should be automatically provided by GitHub Actions.');
62
- }
63
- const serverUrl = process.env.GITHUB_SERVER_URL || 'https://github.com';
64
- return `${serverUrl}/${githubRepo}.git`;
65
- }
66
- /**
67
- * Vercel: Get repository name
68
- *
69
- * Uses VERCEL_GIT_REPO_OWNER and VERCEL_GIT_REPO_SLUG environment variables.
70
- * This is the ONLY method for Vercel - no fallbacks, no alternatives.
71
- */
72
- async function getVercelRepoName(_repoRoot) {
73
- const owner = process.env.VERCEL_GIT_REPO_OWNER;
74
- const slug = process.env.VERCEL_GIT_REPO_SLUG;
75
- if (!owner || !slug) {
76
- throw new Error('Vercel: VERCEL_GIT_REPO_OWNER or VERCEL_GIT_REPO_SLUG environment variable is not set. ' +
77
- 'This should be automatically provided by Vercel CI.');
78
- }
79
- return `https://github.com/${owner}/${slug}.git`;
80
- }
81
- /**
82
- * Local: Get repository name
83
- *
84
- * Uses git command to get origin remote URL.
85
- * This is the ONLY method for local - no fallbacks, no alternatives.
86
- * Git should always be available in local development.
87
- */
88
- async function getLocalRepoName(repoRoot) {
89
- const git = (0, simple_git_1.default)(repoRoot);
90
- // Check if we're in a git repo
91
- const isRepo = await git.checkIsRepo();
92
- if (!isRepo) {
93
- throw new Error('Local: Not a git repository. Threadline requires a git repository.');
94
- }
95
- try {
96
- const remotes = await git.getRemotes(true);
97
- const origin = remotes.find(r => r.name === 'origin');
98
- if (!origin || !origin.refs?.fetch) {
99
- throw new Error('Local: No origin remote found. ' +
100
- 'Please configure an origin remote: git remote add origin <url>');
101
- }
102
- return origin.refs.fetch;
103
- }
104
- catch (error) {
105
- // If it's already our error, re-throw it
106
- if (error instanceof Error && error.message.includes('Local:')) {
107
- throw error;
108
- }
109
- // Otherwise, wrap it
110
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
111
- throw new Error(`Local: Failed to get repository name from git: ${errorMessage}`);
112
- }
113
- }
114
- /**
115
- * GitHub Actions: Get branch name
116
- *
117
- * Uses GITHUB_REF_NAME environment variable.
118
- * This is the ONLY method for GitHub - no fallbacks, no alternatives.
119
- */
120
- async function getGitHubBranchName(_repoRoot) {
121
- const refName = process.env.GITHUB_REF_NAME;
122
- if (!refName) {
123
- throw new Error('GitHub Actions: GITHUB_REF_NAME environment variable is not set. ' +
124
- 'This should be automatically provided by GitHub Actions.');
125
- }
126
- return refName;
127
- }
128
- /**
129
- * Vercel: Get branch name
130
- *
131
- * Uses VERCEL_GIT_COMMIT_REF environment variable.
132
- * This is the ONLY method for Vercel - no fallbacks, no alternatives.
133
- */
134
- async function getVercelBranchName(_repoRoot) {
135
- const branchName = process.env.VERCEL_GIT_COMMIT_REF;
136
- if (!branchName) {
137
- throw new Error('Vercel: VERCEL_GIT_COMMIT_REF environment variable is not set. ' +
138
- 'This should be automatically provided by Vercel CI.');
139
- }
140
- return branchName;
141
- }
142
- /**
143
- * Local: Get branch name
144
- *
145
- * Uses git command to get current branch name.
146
- * This is the ONLY method for local - no fallbacks, no alternatives.
147
- * Git should always be available in local development.
148
- */
149
- async function getLocalBranchName(repoRoot) {
150
- const git = (0, simple_git_1.default)(repoRoot);
151
- // Check if we're in a git repo
152
- const isRepo = await git.checkIsRepo();
153
- if (!isRepo) {
154
- throw new Error('Local: Not a git repository. Threadline requires a git repository.');
155
- }
156
- try {
157
- const branchName = await git.revparse(['--abbrev-ref', 'HEAD']);
158
- if (!branchName || branchName.trim() === '') {
159
- throw new Error('Local: Could not determine branch name. ' +
160
- 'This might be a brand new repository with no commits. ' +
161
- 'Make at least one commit before running threadlines check.');
162
- }
163
- // Handle detached HEAD state
164
- if (branchName === 'HEAD') {
165
- throw new Error('Local: Currently in detached HEAD state. ' +
166
- 'Please checkout a branch before running threadlines check.');
167
- }
168
- return branchName.trim();
169
- }
170
- catch (error) {
171
- // If it's already our error, re-throw it
172
- if (error instanceof Error && error.message.includes('Local:')) {
173
- throw error;
174
- }
175
- // Otherwise, wrap it
176
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
177
- throw new Error(`Local: Failed to get branch name from git: ${errorMessage}`);
178
- }
179
- }
180
- /**
181
- * GitLab CI: Get repository name
182
- *
183
- * Uses CI_PROJECT_URL environment variable.
184
- * This is the ONLY method for GitLab - no fallbacks, no alternatives.
185
- */
186
- async function getGitLabRepoName(_repoRoot) {
187
- const projectUrl = process.env.CI_PROJECT_URL;
188
- if (!projectUrl) {
189
- throw new Error('GitLab CI: CI_PROJECT_URL environment variable is not set. ' +
190
- 'This should be automatically provided by GitLab CI.');
191
- }
192
- // CI_PROJECT_URL is like "https://gitlab.com/owner/repo"
193
- // Add .git suffix for consistency with other environments
194
- return `${projectUrl}.git`;
195
- }
196
- /**
197
- * GitLab CI: Get branch name
198
- *
199
- * Uses CI_COMMIT_REF_NAME environment variable.
200
- * This is the ONLY method for GitLab - no fallbacks, no alternatives.
201
- */
202
- async function getGitLabBranchName(_repoRoot) {
203
- const refName = process.env.CI_COMMIT_REF_NAME;
204
- if (!refName) {
205
- throw new Error('GitLab CI: CI_COMMIT_REF_NAME environment variable is not set. ' +
206
- 'This should be automatically provided by GitLab CI.');
207
- }
208
- return refName;
209
- }
210
- /**
211
- * Detects the default branch name of the repository for GitHub Actions.
212
- *
213
- * Uses GITHUB_EVENT_PATH JSON (repository.default_branch) - the most authoritative source
214
- * provided directly by GitHub Actions.
215
- *
216
- * This function is ONLY called from GitHub Actions context (github.ts),
217
- * so GITHUB_EVENT_PATH should always be available. If it's not, we fail with a clear error.
218
- *
219
- * Returns the branch name (e.g., "main", "master") without the "origin/" prefix.
220
- * Throws an error if the default branch cannot be detected.
221
- */
222
- async function getDefaultBranchName(_repoRoot) {
223
- // GitHub Actions provides GITHUB_EVENT_PATH which contains repository.default_branch
224
- const githubEventPath = process.env.GITHUB_EVENT_PATH;
225
- if (!githubEventPath) {
226
- throw new Error('GITHUB_EVENT_PATH environment variable is not set. ' +
227
- 'This should be automatically provided by GitHub Actions. ' +
228
- 'This function should only be called in GitHub Actions context.');
229
- }
230
- try {
231
- const eventPath = path.resolve(githubEventPath);
232
- if (!fs.existsSync(eventPath)) {
233
- throw new Error(`GITHUB_EVENT_PATH file does not exist: ${eventPath}`);
234
- }
235
- const eventJson = JSON.parse(fs.readFileSync(eventPath, 'utf8'));
236
- const defaultBranch = eventJson.repository?.default_branch;
237
- if (!defaultBranch || typeof defaultBranch !== 'string') {
238
- throw new Error('Could not find repository.default_branch in GITHUB_EVENT_PATH JSON. ' +
239
- 'This should be automatically provided by GitHub Actions.');
240
- }
241
- return defaultBranch;
242
- }
243
- catch (error) {
244
- // If it's already our error, re-throw it
245
- if (error instanceof Error && (error.message.includes('GITHUB_EVENT_PATH') || error.message.includes('default_branch'))) {
246
- throw error;
247
- }
248
- // Otherwise, wrap it
249
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
250
- throw new Error(`Failed to read or parse GITHUB_EVENT_PATH: ${errorMessage}. ` +
251
- 'This should be automatically provided by GitHub Actions.');
252
- }
253
- }