threadlines 0.1.21 → 0.1.22

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.
@@ -151,8 +151,10 @@ async function checkCommand(options) {
151
151
  // 3. Collect metadata (commit SHA, commit message, PR title)
152
152
  const metadata = await (0, metadata_1.collectMetadata)(context, environment, repoRoot);
153
153
  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);
154
+ console.error(chalk_1.default.red('❌ Error: No changes detected.'));
155
+ console.error(chalk_1.default.red(' Threadline check requires code changes to analyze.'));
156
+ console.error(chalk_1.default.red(' This may indicate a problem with git diff detection.'));
157
+ process.exit(1);
156
158
  }
157
159
  // Check for zero diff (files changed but no actual code changes)
158
160
  if (!gitDiff.diff || gitDiff.diff.trim() === '') {
@@ -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,11 +1,47 @@
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
39
  exports.getRepoName = getRepoName;
7
40
  exports.getBranchName = getBranchName;
41
+ exports.getDefaultBranchName = getDefaultBranchName;
8
42
  const simple_git_1 = __importDefault(require("simple-git"));
43
+ const fs = __importStar(require("fs"));
44
+ const path = __importStar(require("path"));
9
45
  /**
10
46
  * Gets repository URL. Prefers CI environment variables over git commands.
11
47
  *
@@ -61,3 +97,46 @@ async function getBranchName(repoRoot) {
61
97
  return null;
62
98
  }
63
99
  }
100
+ /**
101
+ * Detects the default branch name of the repository for GitHub Actions.
102
+ *
103
+ * Uses GITHUB_EVENT_PATH JSON (repository.default_branch) - the most authoritative source
104
+ * provided directly by GitHub Actions.
105
+ *
106
+ * This function is ONLY called from GitHub Actions context (getGitHubDiff),
107
+ * so GITHUB_EVENT_PATH should always be available. If it's not, we fail with a clear error.
108
+ *
109
+ * Returns the branch name (e.g., "main", "master") without the "origin/" prefix.
110
+ * Throws an error if the default branch cannot be detected.
111
+ */
112
+ async function getDefaultBranchName(repoRoot) {
113
+ // GitHub Actions provides GITHUB_EVENT_PATH which contains repository.default_branch
114
+ const githubEventPath = process.env.GITHUB_EVENT_PATH;
115
+ if (!githubEventPath) {
116
+ throw new Error('GITHUB_EVENT_PATH environment variable is not set. ' +
117
+ 'This should be automatically provided by GitHub Actions. ' +
118
+ 'This function should only be called in GitHub Actions context.');
119
+ }
120
+ try {
121
+ const eventPath = path.resolve(githubEventPath);
122
+ if (!fs.existsSync(eventPath)) {
123
+ throw new Error(`GITHUB_EVENT_PATH file does not exist: ${eventPath}`);
124
+ }
125
+ const eventJson = JSON.parse(fs.readFileSync(eventPath, 'utf8'));
126
+ const defaultBranch = eventJson.repository?.default_branch;
127
+ if (!defaultBranch || typeof defaultBranch !== 'string') {
128
+ throw new Error('Could not find repository.default_branch in GITHUB_EVENT_PATH JSON. ' +
129
+ 'This should be automatically provided by GitHub Actions.');
130
+ }
131
+ return defaultBranch;
132
+ }
133
+ catch (error) {
134
+ // If it's already our error, re-throw it
135
+ if (error.message.includes('GITHUB_EVENT_PATH') || error.message.includes('default_branch')) {
136
+ throw error;
137
+ }
138
+ // Otherwise, wrap it
139
+ throw new Error(`Failed to read or parse GITHUB_EVENT_PATH: ${error.message}. ` +
140
+ 'This should be automatically provided by GitHub Actions.');
141
+ }
142
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "threadlines",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "Threadline CLI - AI-powered linter based on your natural language documentation",
5
5
  "main": "dist/index.js",
6
6
  "bin": {