threadlines 0.1.20 → 0.1.21

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.
@@ -127,19 +127,25 @@ async function checkCommand(options) {
127
127
  }
128
128
  else {
129
129
  // Auto-detect: Use environment-specific implementation
130
+ const envNames = {
131
+ vercel: 'Vercel',
132
+ github: 'GitHub',
133
+ gitlab: 'GitLab',
134
+ local: 'Local'
135
+ };
136
+ console.log(chalk_1.default.gray(`📝 Collecting git changes for ${envNames[environment]}...`));
137
+ gitDiff = await (0, git_diff_executor_1.getDiffForEnvironment)(environment, repoRoot);
138
+ // Create context for metadata collection
130
139
  if (environment === 'vercel') {
131
- // Vercel: Direct environment-based routing (single implementation)
132
- console.log(chalk_1.default.gray(`📝 Collecting git changes for Vercel commit...`));
133
- gitDiff = await (0, git_diff_executor_1.getDiffForEnvironment)(environment, repoRoot);
134
- // Vercel uses commit context, but we don't need to create it explicitly
135
140
  context = { type: 'commit', commitSha: process.env.VERCEL_GIT_COMMIT_SHA };
136
141
  }
137
- else {
138
- // Other environments: Use context-based routing (legacy)
142
+ else if (environment === 'github' || environment === 'gitlab') {
143
+ // GitHub/GitLab: Detect context for metadata (but diff already obtained)
139
144
  context = (0, context_1.detectContext)(environment);
140
- const contextDesc = (0, git_diff_executor_1.getContextDescription)(context);
141
- console.log(chalk_1.default.gray(`📝 Collecting git changes for ${contextDesc}...`));
142
- gitDiff = await (0, git_diff_executor_1.getDiffForContext)(context, repoRoot, environment);
145
+ }
146
+ else {
147
+ // Local: Use local context
148
+ context = { type: 'local' };
143
149
  }
144
150
  }
145
151
  // 3. Collect metadata (commit SHA, commit message, PR title)
package/dist/git/diff.js CHANGED
@@ -3,49 +3,11 @@ 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.getGitDiff = getGitDiff;
7
6
  exports.getBranchDiff = getBranchDiff;
8
7
  exports.getCommitMessage = getCommitMessage;
9
8
  exports.getCommitDiff = getCommitDiff;
10
9
  exports.getPRMRDiff = getPRMRDiff;
11
10
  const simple_git_1 = __importDefault(require("simple-git"));
12
- /**
13
- * Get diff for staged/unstaged changes (current behavior)
14
- */
15
- async function getGitDiff(repoRoot) {
16
- const git = (0, simple_git_1.default)(repoRoot);
17
- // Check if we're in a git repo
18
- const isRepo = await git.checkIsRepo();
19
- if (!isRepo) {
20
- throw new Error('Not a git repository. Threadline requires a git repository.');
21
- }
22
- // Get diff (staged changes, or unstaged if no staged)
23
- const status = await git.status();
24
- let diff;
25
- if (status.staged.length > 0) {
26
- // Use staged changes
27
- diff = await git.diff(['--cached', '-U200']);
28
- }
29
- else if (status.files.length > 0) {
30
- // Use unstaged changes
31
- diff = await git.diff(['-U200']);
32
- }
33
- else {
34
- // No changes
35
- return {
36
- diff: '',
37
- changedFiles: []
38
- };
39
- }
40
- // Get list of changed files
41
- const changedFiles = status.files
42
- .filter(f => f.working_dir !== ' ' || f.index !== ' ')
43
- .map(f => f.path);
44
- return {
45
- diff: diff || '',
46
- changedFiles
47
- };
48
- }
49
11
  /**
50
12
  * Get diff for a specific branch (all commits vs base branch)
51
13
  * Uses git merge-base to find common ancestor, then diffs from there
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getGitHubDiff = getGitHubDiff;
7
+ const simple_git_1 = __importDefault(require("simple-git"));
8
+ /**
9
+ * Get diff for GitHub Actions CI environment
10
+ *
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
14
+ *
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
+ */
18
+ async function getGitHubDiff(repoRoot) {
19
+ const git = (0, simple_git_1.default)(repoRoot);
20
+ // Check if we're in a git repo
21
+ const isRepo = await git.checkIsRepo();
22
+ if (!isRepo) {
23
+ throw new Error('Not a git repository. Threadline requires a git repository.');
24
+ }
25
+ // Determine context from GitHub environment variables
26
+ const eventName = process.env.GITHUB_EVENT_NAME;
27
+ const baseRef = process.env.GITHUB_BASE_REF;
28
+ const headRef = process.env.GITHUB_HEAD_REF;
29
+ const refName = process.env.GITHUB_REF_NAME;
30
+ // PR context: GitHub provides both base and head branches
31
+ if (eventName === 'pull_request') {
32
+ if (!baseRef || !headRef) {
33
+ throw new Error('GitHub PR context detected but GITHUB_BASE_REF or GITHUB_HEAD_REF is missing. ' +
34
+ 'This should be automatically provided by GitHub Actions.');
35
+ }
36
+ // Use the branches GitHub provides directly - no detection needed
37
+ const diff = await git.diff([`origin/${baseRef}...origin/${headRef}`, '-U200']);
38
+ const diffSummary = await git.diffSummary([`origin/${baseRef}...origin/${headRef}`]);
39
+ const changedFiles = diffSummary.files.map(f => f.file);
40
+ return {
41
+ diff: diff || '',
42
+ changedFiles
43
+ };
44
+ }
45
+ // Branch context: GitHub provides branch name, compare against origin/main
46
+ 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}`]);
51
+ const changedFiles = diffSummary.files.map(f => f.file);
52
+ return {
53
+ diff: diff || '',
54
+ changedFiles
55
+ };
56
+ }
57
+ // Neither PR nor branch context available
58
+ throw new Error('GitHub Actions environment detected but no valid context found. ' +
59
+ 'Expected GITHUB_EVENT_NAME="pull_request" (with GITHUB_BASE_REF/GITHUB_HEAD_REF) ' +
60
+ 'or GITHUB_REF_NAME for branch context.');
61
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getGitLabDiff = getGitLabDiff;
7
+ const simple_git_1 = __importDefault(require("simple-git"));
8
+ /**
9
+ * Get diff for GitLab CI environment
10
+ *
11
+ * GitLab CI provides environment variables that tell us exactly what to compare:
12
+ * - MR context: CI_MERGE_REQUEST_TARGET_BRANCH_NAME (target branch) and CI_MERGE_REQUEST_SOURCE_BRANCH_NAME (source branch)
13
+ * - Branch context: CI_COMMIT_REF_NAME (current branch), compare against origin/main
14
+ *
15
+ * This implementation follows the same pattern as GitHub Actions, using GitLab's equivalent
16
+ * environment variables. This is the ONLY implementation for GitLab - no fallbacks, no alternatives.
17
+ * If this doesn't work, we fail with a clear error.
18
+ */
19
+ async function getGitLabDiff(repoRoot) {
20
+ const git = (0, simple_git_1.default)(repoRoot);
21
+ // Check if we're in a git repo
22
+ const isRepo = await git.checkIsRepo();
23
+ if (!isRepo) {
24
+ throw new Error('Not a git repository. Threadline requires a git repository.');
25
+ }
26
+ // Determine context from GitLab CI environment variables
27
+ const mrIid = process.env.CI_MERGE_REQUEST_IID;
28
+ const targetBranch = process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME;
29
+ const sourceBranch = process.env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME;
30
+ const refName = process.env.CI_COMMIT_REF_NAME;
31
+ // MR context: GitLab provides both target and source branches
32
+ if (mrIid) {
33
+ if (!targetBranch || !sourceBranch) {
34
+ throw new Error('GitLab MR context detected but CI_MERGE_REQUEST_TARGET_BRANCH_NAME or ' +
35
+ 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME is missing. ' +
36
+ 'This should be automatically provided by GitLab CI.');
37
+ }
38
+ // Use the branches GitLab provides directly - no detection needed
39
+ const diff = await git.diff([`origin/${targetBranch}...origin/${sourceBranch}`, '-U200']);
40
+ const diffSummary = await git.diffSummary([`origin/${targetBranch}...origin/${sourceBranch}`]);
41
+ const changedFiles = diffSummary.files.map(f => f.file);
42
+ return {
43
+ diff: diff || '',
44
+ changedFiles
45
+ };
46
+ }
47
+ // Branch context: GitLab provides branch name, compare against origin/main
48
+ if (refName) {
49
+ // For branch pushes, compare against origin/main (standard base branch)
50
+ // GitLab CI with fetch-depth: 0 should have origin/main available
51
+ const diff = await git.diff([`origin/main...origin/${refName}`, '-U200']);
52
+ const diffSummary = await git.diffSummary([`origin/main...origin/${refName}`]);
53
+ const changedFiles = diffSummary.files.map(f => f.file);
54
+ return {
55
+ diff: diff || '',
56
+ changedFiles
57
+ };
58
+ }
59
+ // Neither MR nor branch context available
60
+ throw new Error('GitLab CI environment detected but no valid context found. ' +
61
+ 'Expected CI_MERGE_REQUEST_IID (with CI_MERGE_REQUEST_TARGET_BRANCH_NAME/CI_MERGE_REQUEST_SOURCE_BRANCH_NAME) ' +
62
+ 'or CI_COMMIT_REF_NAME for branch context.');
63
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getLocalDiff = getLocalDiff;
7
+ const simple_git_1 = __importDefault(require("simple-git"));
8
+ /**
9
+ * Get diff for local development environment
10
+ *
11
+ * For local development, we check staged changes first, then unstaged changes.
12
+ * This allows developers to review what they've staged before committing,
13
+ * or review unstaged changes if nothing is staged.
14
+ *
15
+ * This is the ONLY implementation for local - no fallbacks, no alternatives.
16
+ * If this doesn't work, we fail with a clear error.
17
+ */
18
+ async function getLocalDiff(repoRoot) {
19
+ const git = (0, simple_git_1.default)(repoRoot);
20
+ // Check if we're in a git repo
21
+ const isRepo = await git.checkIsRepo();
22
+ if (!isRepo) {
23
+ throw new Error('Not a git repository. Threadline requires a git repository.');
24
+ }
25
+ // Get git status to determine what changes exist
26
+ const status = await git.status();
27
+ let diff;
28
+ let changedFiles;
29
+ // Priority 1: Use staged changes if available
30
+ if (status.staged.length > 0) {
31
+ diff = await git.diff(['--cached', '-U200']);
32
+ // status.staged is an array of strings (file paths)
33
+ changedFiles = status.staged;
34
+ }
35
+ // Priority 2: Use unstaged changes if no staged changes
36
+ else if (status.files.length > 0) {
37
+ diff = await git.diff(['-U200']);
38
+ changedFiles = status.files
39
+ .filter(f => f.working_dir !== ' ' || f.index !== ' ')
40
+ .map(f => f.path);
41
+ }
42
+ // No changes at all
43
+ else {
44
+ return {
45
+ diff: '',
46
+ changedFiles: []
47
+ };
48
+ }
49
+ return {
50
+ diff: diff || '',
51
+ changedFiles
52
+ };
53
+ }
@@ -11,16 +11,20 @@ exports.getDiffForContext = getDiffForContext;
11
11
  exports.getContextDescription = getContextDescription;
12
12
  const diff_1 = require("../git/diff");
13
13
  const vercel_diff_1 = require("../git/vercel-diff");
14
+ const github_diff_1 = require("../git/github-diff");
15
+ const local_diff_1 = require("../git/local-diff");
16
+ const gitlab_diff_1 = require("../git/gitlab-diff");
14
17
  /**
15
18
  * Executes the appropriate git diff function based on environment.
16
19
  *
17
20
  * Each environment has a single, specific implementation:
18
21
  * - Vercel: Uses VERCEL_GIT_COMMIT_SHA, gets commit diff via git show
19
- * - GitHub: Uses branch/PR context with base branch detection
20
- * - GitLab: Uses branch/MR context with base branch detection
21
- * - Local: Uses staged/unstaged changes
22
+ * - GitHub: Uses GITHUB_BASE_REF/GITHUB_HEAD_REF for PRs, GITHUB_REF_NAME for branches
23
+ * - GitLab: Uses CI_MERGE_REQUEST_TARGET_BRANCH_NAME/CI_MERGE_REQUEST_SOURCE_BRANCH_NAME for MRs, CI_COMMIT_REF_NAME for branches
24
+ * - Local: Uses staged changes first, then unstaged changes
22
25
  *
23
26
  * No fallbacks - if the environment-specific implementation fails, we fail clearly.
27
+ * Each environment is completely isolated - changes to one don't affect others.
24
28
  */
25
29
  async function getDiffForEnvironment(environment, repoRoot, context) {
26
30
  switch (environment) {
@@ -28,25 +32,32 @@ async function getDiffForEnvironment(environment, repoRoot, context) {
28
32
  // Vercel: Single implementation using commit SHA
29
33
  return await (0, vercel_diff_1.getVercelDiff)(repoRoot);
30
34
  case 'github':
35
+ // GitHub: Single implementation using GitHub-provided environment variables
36
+ return await (0, github_diff_1.getGitHubDiff)(repoRoot);
31
37
  case 'gitlab':
38
+ // GitLab: Single implementation using GitLab-provided environment variables
39
+ return await (0, gitlab_diff_1.getGitLabDiff)(repoRoot);
32
40
  case 'local':
33
- // For other environments, use context-based routing (legacy, will be refactored)
34
- if (!context) {
35
- throw new Error(`Context required for ${environment} environment`);
36
- }
37
- return await getDiffForContext(context, repoRoot, environment);
41
+ // Local: Single implementation using staged/unstaged changes
42
+ return await (0, local_diff_1.getLocalDiff)(repoRoot);
38
43
  default:
39
44
  const _exhaustive = environment;
40
45
  throw new Error(`Unknown environment: ${_exhaustive}`);
41
46
  }
42
47
  }
43
48
  /**
44
- * Executes the appropriate git diff function based on context.
49
+ * Legacy GitLab-specific diff function (deprecated).
45
50
  *
46
- * This is the legacy context-based routing. New code should use getDiffForEnvironment().
47
- * This function maps context types to their corresponding git diff functions.
51
+ * This function is kept for backward compatibility but should not be used.
52
+ * GitLab now uses getDiffForEnvironment() which routes to getGitLabDiff().
53
+ *
54
+ * @deprecated Use getDiffForEnvironment('gitlab', repoRoot) instead
48
55
  */
49
56
  async function getDiffForContext(context, repoRoot, environment) {
57
+ // This should only be called for GitLab legacy code paths
58
+ if (environment !== 'gitlab') {
59
+ throw new Error(`getDiffForContext() is deprecated. Use getDiffForEnvironment('${environment}', repoRoot) instead.`);
60
+ }
50
61
  switch (context.type) {
51
62
  case 'pr':
52
63
  return await (0, diff_1.getPRMRDiff)(repoRoot, context.sourceBranch, context.targetBranch);
@@ -57,7 +68,7 @@ async function getDiffForContext(context, repoRoot, environment) {
57
68
  case 'commit':
58
69
  return await (0, diff_1.getCommitDiff)(repoRoot, context.commitSha);
59
70
  case 'local':
60
- return await (0, diff_1.getGitDiff)(repoRoot);
71
+ throw new Error('Local context should use getDiffForEnvironment(), not getDiffForContext()');
61
72
  default:
62
73
  // TypeScript exhaustiveness check - should never reach here
63
74
  const _exhaustive = context;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "threadlines",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "description": "Threadline CLI - AI-powered linter based on your natural language documentation",
5
5
  "main": "dist/index.js",
6
6
  "bin": {