threadlines 0.1.21 → 0.1.23

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.
@@ -39,13 +39,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.checkCommand = checkCommand;
40
40
  const experts_1 = require("../validators/experts");
41
41
  const file_1 = require("../git/file");
42
- const repo_1 = require("../git/repo");
43
42
  const client_1 = require("../api/client");
44
43
  const config_1 = require("../utils/config");
45
44
  const environment_1 = require("../utils/environment");
46
45
  const context_1 = require("../utils/context");
47
46
  const metadata_1 = require("../utils/metadata");
48
47
  const git_diff_executor_1 = require("../utils/git-diff-executor");
48
+ const context_2 = require("../git/context");
49
49
  const fs = __importStar(require("fs"));
50
50
  const path = __importStar(require("path"));
51
51
  const chalk_1 = __importDefault(require("chalk"));
@@ -92,6 +92,8 @@ async function checkCommand(options) {
92
92
  const environment = (0, environment_1.detectEnvironment)();
93
93
  let context;
94
94
  let gitDiff;
95
+ let repoName;
96
+ let branchName;
95
97
  // Validate mutually exclusive flags
96
98
  const explicitFlags = [options.branch, options.commit, options.file, options.folder, options.files].filter(Boolean);
97
99
  if (explicitFlags.length > 1) {
@@ -104,37 +106,52 @@ async function checkCommand(options) {
104
106
  console.log(chalk_1.default.gray(`📝 Reading file: ${options.file}...`));
105
107
  gitDiff = await (0, file_1.getFileContent)(repoRoot, options.file);
106
108
  context = { type: 'local' }; // File context doesn't need git context
109
+ // For file/folder/files, repo/branch are not available - skip them
107
110
  }
108
111
  else if (options.folder) {
109
112
  console.log(chalk_1.default.gray(`📝 Reading folder: ${options.folder}...`));
110
113
  gitDiff = await (0, file_1.getFolderContent)(repoRoot, options.folder);
111
114
  context = { type: 'local' };
115
+ // For file/folder/files, repo/branch are not available - skip them
112
116
  }
113
117
  else if (options.files && options.files.length > 0) {
114
118
  console.log(chalk_1.default.gray(`📝 Reading ${options.files.length} file(s)...`));
115
119
  gitDiff = await (0, file_1.getMultipleFilesContent)(repoRoot, options.files);
116
120
  context = { type: 'local' };
121
+ // For file/folder/files, repo/branch are not available - skip them
117
122
  }
118
123
  else if (options.branch) {
119
124
  console.log(chalk_1.default.gray(`📝 Collecting git changes for branch: ${options.branch}...`));
120
125
  context = { type: 'branch', branchName: options.branch };
121
126
  gitDiff = await (0, git_diff_executor_1.getDiffForContext)(context, repoRoot, environment);
127
+ // Get repo/branch using unified approach
128
+ const gitContext = await (0, context_2.getGitContextForEnvironment)(environment, repoRoot);
129
+ repoName = gitContext.repoName;
130
+ branchName = gitContext.branchName;
122
131
  }
123
132
  else if (options.commit) {
124
133
  console.log(chalk_1.default.gray(`📝 Collecting git changes for commit: ${options.commit}...`));
125
134
  context = { type: 'commit', commitSha: options.commit };
126
135
  gitDiff = await (0, git_diff_executor_1.getDiffForContext)(context, repoRoot, environment);
136
+ // Get repo/branch using unified approach
137
+ const gitContext = await (0, context_2.getGitContextForEnvironment)(environment, repoRoot);
138
+ repoName = gitContext.repoName;
139
+ branchName = gitContext.branchName;
127
140
  }
128
141
  else {
129
- // Auto-detect: Use environment-specific implementation
142
+ // Auto-detect: Use unified git context collection
130
143
  const envNames = {
131
144
  vercel: 'Vercel',
132
145
  github: 'GitHub',
133
146
  gitlab: 'GitLab',
134
147
  local: 'Local'
135
148
  };
136
- console.log(chalk_1.default.gray(`📝 Collecting git changes for ${envNames[environment]}...`));
137
- gitDiff = await (0, git_diff_executor_1.getDiffForEnvironment)(environment, repoRoot);
149
+ console.log(chalk_1.default.gray(`📝 Collecting git context for ${envNames[environment]}...`));
150
+ // Use unified git context collection (diff + repo + branch)
151
+ const gitContext = await (0, context_2.getGitContextForEnvironment)(environment, repoRoot);
152
+ gitDiff = gitContext.diff;
153
+ repoName = gitContext.repoName;
154
+ branchName = gitContext.branchName;
138
155
  // Create context for metadata collection
139
156
  if (environment === 'vercel') {
140
157
  context = { type: 'commit', commitSha: process.env.VERCEL_GIT_COMMIT_SHA };
@@ -151,8 +168,10 @@ async function checkCommand(options) {
151
168
  // 3. Collect metadata (commit SHA, commit message, PR title)
152
169
  const metadata = await (0, metadata_1.collectMetadata)(context, environment, repoRoot);
153
170
  if (gitDiff.changedFiles.length === 0) {
154
- console.log(chalk_1.default.yellow('⚠️ No changes detected. Make some code changes and try again.'));
155
- process.exit(0);
171
+ console.error(chalk_1.default.red('❌ Error: No changes detected.'));
172
+ console.error(chalk_1.default.red(' Threadline check requires code changes to analyze.'));
173
+ console.error(chalk_1.default.red(' This may indicate a problem with git diff detection.'));
174
+ process.exit(1);
156
175
  }
157
176
  // Check for zero diff (files changed but no actual code changes)
158
177
  if (!gitDiff.diff || gitDiff.diff.trim() === '') {
@@ -166,7 +185,7 @@ async function checkCommand(options) {
166
185
  process.exit(0);
167
186
  }
168
187
  console.log(chalk_1.default.green(`✓ Found ${gitDiff.changedFiles.length} changed file(s)\n`));
169
- // 3. Read context files for each threadline
188
+ // 4. Read context files for each threadline
170
189
  const threadlinesWithContext = threadlines.map(threadline => {
171
190
  const contextContent = {};
172
191
  if (threadline.contextFiles) {
@@ -186,9 +205,6 @@ async function checkCommand(options) {
186
205
  contextContent
187
206
  };
188
207
  });
189
- // 4. Get repo name and branch name
190
- const repoName = await (0, repo_1.getRepoName)(repoRoot);
191
- const branchName = await (0, repo_1.getBranchName)(repoRoot);
192
208
  // 5. Get API URL
193
209
  const apiUrl = options.apiUrl ||
194
210
  process.env.THREADLINE_API_URL ||
@@ -202,8 +218,8 @@ async function checkCommand(options) {
202
218
  files: gitDiff.changedFiles,
203
219
  apiKey,
204
220
  account,
205
- repoName: repoName || undefined,
206
- branchName: branchName || undefined,
221
+ repoName: repoName,
222
+ branchName: branchName,
207
223
  commitSha: metadata.commitSha,
208
224
  commitMessage: metadata.commitMessage,
209
225
  prTitle: metadata.prTitle,
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ /**
3
+ * Unified Git Context Collection
4
+ *
5
+ * Collects all git-related information (diff, repo name, branch name) in a unified way.
6
+ * Each environment has isolated implementations - changes to one don't affect others.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.getGitContextForEnvironment = getGitContextForEnvironment;
10
+ const git_diff_executor_1 = require("../utils/git-diff-executor");
11
+ const repo_1 = require("./repo");
12
+ const repo_2 = require("./repo");
13
+ /**
14
+ * Collects all git context (diff, repo name, branch name) for the given environment.
15
+ *
16
+ * Each environment has a single, specific implementation:
17
+ * - GitHub: Uses GITHUB_REPOSITORY, GITHUB_REF_NAME, and GitHub-specific diff logic
18
+ * - Vercel: Uses VERCEL_GIT_REPO_OWNER/SLUG, VERCEL_GIT_COMMIT_REF, and Vercel-specific diff logic
19
+ * - Local: Uses git commands for repo/branch, and local diff logic
20
+ *
21
+ * All methods fail loudly if they can't get the required information.
22
+ */
23
+ async function getGitContextForEnvironment(environment, repoRoot) {
24
+ switch (environment) {
25
+ case 'github':
26
+ return {
27
+ diff: await (0, git_diff_executor_1.getDiffForEnvironment)('github', repoRoot),
28
+ repoName: await (0, repo_1.getGitHubRepoName)(repoRoot),
29
+ branchName: await (0, repo_2.getGitHubBranchName)(repoRoot)
30
+ };
31
+ case 'vercel':
32
+ return {
33
+ diff: await (0, git_diff_executor_1.getDiffForEnvironment)('vercel', repoRoot),
34
+ repoName: await (0, repo_1.getVercelRepoName)(repoRoot),
35
+ branchName: await (0, repo_2.getVercelBranchName)(repoRoot)
36
+ };
37
+ case 'local':
38
+ return {
39
+ diff: await (0, git_diff_executor_1.getDiffForEnvironment)('local', repoRoot),
40
+ repoName: await (0, repo_1.getLocalRepoName)(repoRoot),
41
+ branchName: await (0, repo_2.getLocalBranchName)(repoRoot)
42
+ };
43
+ case 'gitlab':
44
+ // GitLab not implemented yet - will be added later
45
+ throw new Error('GitLab environment not yet supported for unified git context collection.');
46
+ default:
47
+ const _exhaustive = environment;
48
+ throw new Error(`Unknown environment: ${_exhaustive}`);
49
+ }
50
+ }
@@ -5,16 +5,29 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getGitHubDiff = getGitHubDiff;
7
7
  const simple_git_1 = __importDefault(require("simple-git"));
8
+ const repo_1 = require("./repo");
8
9
  /**
9
10
  * Get diff for GitHub Actions CI environment
10
11
  *
11
- * GitHub Actions provides environment variables that tell us exactly what to compare:
12
- * - PR context: GITHUB_BASE_REF (target branch) and GITHUB_HEAD_REF (source branch)
13
- * - Branch context: GITHUB_REF_NAME (current branch), compare against origin/main
12
+ * Handles four scenarios:
14
13
  *
15
- * This is the ONLY implementation for GitHub - no fallbacks, no alternatives.
16
- * If this doesn't work, we fail with a clear error.
17
- */
14
+ * 1. PR Context (pull_request event):
15
+ * Uses GITHUB_BASE_REF vs GITHUB_HEAD_REF
16
+ * Shows: All changes in the PR
17
+ *
18
+ * 2. Merge Commit to Default Branch (push event, default branch):
19
+ * Compare: origin/default~1 vs origin/default
20
+ * Shows: All changes that were merged in
21
+ *
22
+ * 3. Feature Branch Push (push event, feature branch):
23
+ * Compare: origin/default vs origin/feature-branch
24
+ * Shows: Cumulative changes in feature branch vs default
25
+ *
26
+ * 4. Direct Commit to Default Branch (push event, default branch, non-merge):
27
+ * Compare: origin/default~1 vs origin/default
28
+ * Shows: Changes in the direct commit
29
+ *
30
+ */
18
31
  async function getGitHubDiff(repoRoot) {
19
32
  const git = (0, simple_git_1.default)(repoRoot);
20
33
  // Check if we're in a git repo
@@ -22,18 +35,25 @@ async function getGitHubDiff(repoRoot) {
22
35
  if (!isRepo) {
23
36
  throw new Error('Not a git repository. Threadline requires a git repository.');
24
37
  }
38
+ // Detect the default branch name (e.g., "main", "master")
39
+ // This is used for scenarios 2, 3, and 4
40
+ const defaultBranch = await (0, repo_1.getDefaultBranchName)(repoRoot);
25
41
  // Determine context from GitHub environment variables
26
42
  const eventName = process.env.GITHUB_EVENT_NAME;
27
43
  const baseRef = process.env.GITHUB_BASE_REF;
28
44
  const headRef = process.env.GITHUB_HEAD_REF;
29
45
  const refName = process.env.GITHUB_REF_NAME;
30
- // PR context: GitHub provides both base and head branches
46
+ const commitSha = process.env.GITHUB_SHA;
47
+ // Scenario 1: PR Context
48
+ // When a PR is created or updated, GitHub provides both base and head branches
49
+ // This is the simplest case - we use what GitHub gives us directly
31
50
  if (eventName === 'pull_request') {
32
51
  if (!baseRef || !headRef) {
33
52
  throw new Error('GitHub PR context detected but GITHUB_BASE_REF or GITHUB_HEAD_REF is missing. ' +
34
53
  'This should be automatically provided by GitHub Actions.');
35
54
  }
36
- // Use the branches GitHub provides directly - no detection needed
55
+ // Compare target branch (base) vs source branch (head)
56
+ // This shows all changes in the PR
37
57
  const diff = await git.diff([`origin/${baseRef}...origin/${headRef}`, '-U200']);
38
58
  const diffSummary = await git.diffSummary([`origin/${baseRef}...origin/${headRef}`]);
39
59
  const changedFiles = diffSummary.files.map(f => f.file);
@@ -42,12 +62,40 @@ async function getGitHubDiff(repoRoot) {
42
62
  changedFiles
43
63
  };
44
64
  }
45
- // Branch context: GitHub provides branch name, compare against origin/main
65
+ // Scenario 2 & 4: Default Branch Push (merge commit or direct commit)
66
+ // When code is pushed to the default branch, we compare default~1 vs default
67
+ // This works for both merge commits and direct commits:
68
+ // - Merge commits: Shows all changes that were merged in
69
+ // - Direct commits: Shows the changes in the direct commit
70
+ if (refName === defaultBranch && commitSha) {
71
+ // Compare default branch before the push (default~1) vs default branch after the push (default)
72
+ // This shows all changes introduced by the push, whether merged or direct
73
+ try {
74
+ const diff = await git.diff([`origin/${defaultBranch}~1...origin/${defaultBranch}`, '-U200']);
75
+ const diffSummary = await git.diffSummary([`origin/${defaultBranch}~1...origin/${defaultBranch}`]);
76
+ const changedFiles = diffSummary.files.map(f => f.file);
77
+ return {
78
+ diff: diff || '',
79
+ changedFiles
80
+ };
81
+ }
82
+ catch (error) {
83
+ // If we can't get the diff (e.g., first commit on branch), throw a clear error
84
+ throw new Error(`Could not get diff for default branch '${defaultBranch}'. ` +
85
+ `This might be the first commit on the branch. Error: ${error.message}`);
86
+ }
87
+ }
88
+ // Scenario 3: Feature Branch Push
89
+ // When code is pushed to a feature branch, we want to see all changes vs the default branch
90
+ // Compare: origin/default vs origin/feature-branch
91
+ // This shows cumulative changes in the feature branch (all commits vs default branch)
92
+ // Note: We don't use HEAD~1 vs HEAD because that only shows the last commit,
93
+ // not the cumulative changes in the branch
46
94
  if (refName) {
47
- // For branch pushes, compare against origin/main (standard base branch)
48
- // GitHub Actions with fetch-depth: 0 should have origin/main available
49
- const diff = await git.diff([`origin/main...origin/${refName}`, '-U200']);
50
- const diffSummary = await git.diffSummary([`origin/main...origin/${refName}`]);
95
+ // For branch pushes, compare against origin/default (detected default branch)
96
+ // GitHub Actions with fetch-depth: 0 should have origin/default available
97
+ const diff = await git.diff([`origin/${defaultBranch}...origin/${refName}`, '-U200']);
98
+ const diffSummary = await git.diffSummary([`origin/${defaultBranch}...origin/${refName}`]);
51
99
  const changedFiles = diffSummary.files.map(f => f.file);
52
100
  return {
53
101
  diff: diff || '',
package/dist/git/repo.js CHANGED
@@ -1,63 +1,218 @@
1
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
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getRepoName = getRepoName;
7
- exports.getBranchName = getBranchName;
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.getDefaultBranchName = getDefaultBranchName;
8
46
  const simple_git_1 = __importDefault(require("simple-git"));
47
+ const fs = __importStar(require("fs"));
48
+ const path = __importStar(require("path"));
9
49
  /**
10
- * Gets repository URL. Prefers CI environment variables over git commands.
50
+ * GitHub Actions: Get repository name
11
51
  *
12
- * CI Environment Variables:
13
- * - GitHub Actions: GITHUB_REPOSITORY (format: "owner/repo"), GITHUB_SERVER_URL
14
- * - Vercel: VERCEL_GIT_REPO_OWNER, VERCEL_GIT_REPO_SLUG
15
- * - GitLab CI: Not available (falls back to git)
16
- * - Local: Falls back to git origin remote
52
+ * Uses GITHUB_REPOSITORY environment variable (format: "owner/repo").
53
+ * This is the ONLY method for GitHub - no fallbacks, no alternatives.
17
54
  */
18
- async function getRepoName(repoRoot) {
19
- // GitHub Actions: GITHUB_REPOSITORY = "owner/repo"
20
- if (process.env.GITHUB_REPOSITORY) {
21
- const serverUrl = process.env.GITHUB_SERVER_URL || 'https://github.com';
22
- return `${serverUrl}/${process.env.GITHUB_REPOSITORY}.git`;
55
+ async function getGitHubRepoName(repoRoot) {
56
+ const githubRepo = process.env.GITHUB_REPOSITORY;
57
+ if (!githubRepo) {
58
+ throw new Error('GitHub Actions: GITHUB_REPOSITORY environment variable is not set. ' +
59
+ 'This should be automatically provided by GitHub Actions.');
23
60
  }
24
- // Vercel: Construct from owner + slug
25
- if (process.env.VERCEL_GIT_REPO_OWNER && process.env.VERCEL_GIT_REPO_SLUG) {
26
- return `https://github.com/${process.env.VERCEL_GIT_REPO_OWNER}/${process.env.VERCEL_GIT_REPO_SLUG}.git`;
61
+ const serverUrl = process.env.GITHUB_SERVER_URL || 'https://github.com';
62
+ return `${serverUrl}/${githubRepo}.git`;
63
+ }
64
+ /**
65
+ * Vercel: Get repository name
66
+ *
67
+ * Uses VERCEL_GIT_REPO_OWNER and VERCEL_GIT_REPO_SLUG environment variables.
68
+ * This is the ONLY method for Vercel - no fallbacks, no alternatives.
69
+ */
70
+ async function getVercelRepoName(repoRoot) {
71
+ const owner = process.env.VERCEL_GIT_REPO_OWNER;
72
+ const slug = process.env.VERCEL_GIT_REPO_SLUG;
73
+ if (!owner || !slug) {
74
+ throw new Error('Vercel: VERCEL_GIT_REPO_OWNER or VERCEL_GIT_REPO_SLUG environment variable is not set. ' +
75
+ 'This should be automatically provided by Vercel CI.');
27
76
  }
28
- // Fallback: git origin remote (local dev, GitLab CI)
77
+ return `https://github.com/${owner}/${slug}.git`;
78
+ }
79
+ /**
80
+ * Local: Get repository name
81
+ *
82
+ * Uses git command to get origin remote URL.
83
+ * This is the ONLY method for local - no fallbacks, no alternatives.
84
+ * Git should always be available in local development.
85
+ */
86
+ async function getLocalRepoName(repoRoot) {
29
87
  const git = (0, simple_git_1.default)(repoRoot);
88
+ // Check if we're in a git repo
89
+ const isRepo = await git.checkIsRepo();
90
+ if (!isRepo) {
91
+ throw new Error('Local: Not a git repository. Threadline requires a git repository.');
92
+ }
30
93
  try {
31
94
  const remotes = await git.getRemotes(true);
32
95
  const origin = remotes.find(r => r.name === 'origin');
33
- return origin?.refs?.fetch || null;
96
+ if (!origin || !origin.refs?.fetch) {
97
+ throw new Error('Local: No origin remote found. ' +
98
+ 'Please configure an origin remote: git remote add origin <url>');
99
+ }
100
+ return origin.refs.fetch;
34
101
  }
35
- catch {
36
- return null;
102
+ catch (error) {
103
+ // If it's already our error, re-throw it
104
+ if (error.message.includes('Local:')) {
105
+ throw error;
106
+ }
107
+ // Otherwise, wrap it
108
+ throw new Error(`Local: Failed to get repository name from git: ${error.message}`);
109
+ }
110
+ }
111
+ /**
112
+ * GitHub Actions: Get branch name
113
+ *
114
+ * Uses GITHUB_REF_NAME environment variable.
115
+ * This is the ONLY method for GitHub - no fallbacks, no alternatives.
116
+ */
117
+ async function getGitHubBranchName(repoRoot) {
118
+ const refName = process.env.GITHUB_REF_NAME;
119
+ if (!refName) {
120
+ throw new Error('GitHub Actions: GITHUB_REF_NAME environment variable is not set. ' +
121
+ 'This should be automatically provided by GitHub Actions.');
37
122
  }
123
+ return refName;
38
124
  }
39
125
  /**
40
- * Gets branch name. Prefers CI environment variables over git commands.
126
+ * Vercel: Get branch name
41
127
  *
42
- * CI Environment Variables:
43
- * - GitHub Actions: GITHUB_REF_NAME
44
- * - Vercel: VERCEL_GIT_COMMIT_REF
45
- * - GitLab CI: CI_COMMIT_REF_NAME
46
- * - Local: Falls back to git revparse --abbrev-ref HEAD
128
+ * Uses VERCEL_GIT_COMMIT_REF environment variable.
129
+ * This is the ONLY method for Vercel - no fallbacks, no alternatives.
47
130
  */
48
- async function getBranchName(repoRoot) {
49
- if (process.env.GITHUB_REF_NAME)
50
- return process.env.GITHUB_REF_NAME;
51
- if (process.env.VERCEL_GIT_COMMIT_REF)
52
- return process.env.VERCEL_GIT_COMMIT_REF;
53
- if (process.env.CI_COMMIT_REF_NAME)
54
- return process.env.CI_COMMIT_REF_NAME;
55
- // Fallback: git command
131
+ async function getVercelBranchName(repoRoot) {
132
+ const branchName = process.env.VERCEL_GIT_COMMIT_REF;
133
+ if (!branchName) {
134
+ throw new Error('Vercel: VERCEL_GIT_COMMIT_REF environment variable is not set. ' +
135
+ 'This should be automatically provided by Vercel CI.');
136
+ }
137
+ return branchName;
138
+ }
139
+ /**
140
+ * Local: Get branch name
141
+ *
142
+ * Uses git command to get current branch name.
143
+ * This is the ONLY method for local - no fallbacks, no alternatives.
144
+ * Git should always be available in local development.
145
+ */
146
+ async function getLocalBranchName(repoRoot) {
56
147
  const git = (0, simple_git_1.default)(repoRoot);
148
+ // Check if we're in a git repo
149
+ const isRepo = await git.checkIsRepo();
150
+ if (!isRepo) {
151
+ throw new Error('Local: Not a git repository. Threadline requires a git repository.');
152
+ }
153
+ try {
154
+ const branchName = await git.revparse(['--abbrev-ref', 'HEAD']);
155
+ if (!branchName || branchName.trim() === '') {
156
+ throw new Error('Local: Could not determine branch name. ' +
157
+ 'This might be a brand new repository with no commits. ' +
158
+ 'Make at least one commit before running threadlines check.');
159
+ }
160
+ // Handle detached HEAD state
161
+ if (branchName === 'HEAD') {
162
+ throw new Error('Local: Currently in detached HEAD state. ' +
163
+ 'Please checkout a branch before running threadlines check.');
164
+ }
165
+ return branchName.trim();
166
+ }
167
+ catch (error) {
168
+ // If it's already our error, re-throw it
169
+ if (error.message.includes('Local:')) {
170
+ throw error;
171
+ }
172
+ // Otherwise, wrap it
173
+ throw new Error(`Local: Failed to get branch name from git: ${error.message}`);
174
+ }
175
+ }
176
+ /**
177
+ * Detects the default branch name of the repository for GitHub Actions.
178
+ *
179
+ * Uses GITHUB_EVENT_PATH JSON (repository.default_branch) - the most authoritative source
180
+ * provided directly by GitHub Actions.
181
+ *
182
+ * This function is ONLY called from GitHub Actions context (getGitHubDiff),
183
+ * so GITHUB_EVENT_PATH should always be available. If it's not, we fail with a clear error.
184
+ *
185
+ * Returns the branch name (e.g., "main", "master") without the "origin/" prefix.
186
+ * Throws an error if the default branch cannot be detected.
187
+ */
188
+ async function getDefaultBranchName(repoRoot) {
189
+ // GitHub Actions provides GITHUB_EVENT_PATH which contains repository.default_branch
190
+ const githubEventPath = process.env.GITHUB_EVENT_PATH;
191
+ if (!githubEventPath) {
192
+ throw new Error('GITHUB_EVENT_PATH environment variable is not set. ' +
193
+ 'This should be automatically provided by GitHub Actions. ' +
194
+ 'This function should only be called in GitHub Actions context.');
195
+ }
57
196
  try {
58
- return await git.revparse(['--abbrev-ref', 'HEAD']) || null;
197
+ const eventPath = path.resolve(githubEventPath);
198
+ if (!fs.existsSync(eventPath)) {
199
+ throw new Error(`GITHUB_EVENT_PATH file does not exist: ${eventPath}`);
200
+ }
201
+ const eventJson = JSON.parse(fs.readFileSync(eventPath, 'utf8'));
202
+ const defaultBranch = eventJson.repository?.default_branch;
203
+ if (!defaultBranch || typeof defaultBranch !== 'string') {
204
+ throw new Error('Could not find repository.default_branch in GITHUB_EVENT_PATH JSON. ' +
205
+ 'This should be automatically provided by GitHub Actions.');
206
+ }
207
+ return defaultBranch;
59
208
  }
60
- catch {
61
- return null;
209
+ catch (error) {
210
+ // If it's already our error, re-throw it
211
+ if (error.message.includes('GITHUB_EVENT_PATH') || error.message.includes('default_branch')) {
212
+ throw error;
213
+ }
214
+ // Otherwise, wrap it
215
+ throw new Error(`Failed to read or parse GITHUB_EVENT_PATH: ${error.message}. ` +
216
+ 'This should be automatically provided by GitHub Actions.');
62
217
  }
63
218
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "threadlines",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "Threadline CLI - AI-powered linter based on your natural language documentation",
5
5
  "main": "dist/index.js",
6
6
  "bin": {