threadlines 0.1.27 → 0.1.29

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.
@@ -21,14 +21,16 @@ class ReviewAPIClient {
21
21
  return response.data;
22
22
  }
23
23
  catch (error) {
24
- if (error.response) {
25
- throw new Error(`API error: ${error.response.status} - ${error.response.data?.message || error.message}`);
24
+ if (error && typeof error === 'object' && 'response' in error) {
25
+ const axiosError = error;
26
+ throw new Error(`API error: ${axiosError.response.status} - ${axiosError.response.data?.message || axiosError.message || 'Unknown error'}`);
26
27
  }
27
- else if (error.request) {
28
+ else if (error && typeof error === 'object' && 'request' in error) {
28
29
  throw new Error(`Network error: Could not reach Threadline server at ${this.client.defaults.baseURL}`);
29
30
  }
30
31
  else {
31
- throw new Error(`Request error: ${error.message}`);
32
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
33
+ throw new Error(`Request error: ${errorMessage}`);
32
34
  }
33
35
  }
34
36
  }
@@ -59,11 +59,8 @@ async function checkCommand(options) {
59
59
  // Pre-flight check: Validate ALL required environment variables at once
60
60
  const apiKey = (0, config_1.getThreadlineApiKey)();
61
61
  const account = (0, config_1.getThreadlineAccount)();
62
- // Debug: Show what we got (masked for security)
63
- console.log(chalk_1.default.gray(` [Debug] THREADLINE_API_KEY: ${apiKey ? `"${apiKey.substring(0, 4)}..."` : 'undefined'}`));
64
- console.log(chalk_1.default.gray(` [Debug] THREADLINE_ACCOUNT: ${account ? `"${account.substring(0, 4)}..."` : 'undefined'}`));
65
62
  const missingVars = [];
66
- // Check for undefined, empty string, or literal unexpanded variable
63
+ // Check for undefined, empty string, or literal unexpanded variable (GitLab keeps "$VAR" literal)
67
64
  if (!apiKey || apiKey.startsWith('$'))
68
65
  missingVars.push('THREADLINE_API_KEY');
69
66
  if (!account || account.startsWith('$'))
@@ -187,7 +184,6 @@ async function checkCommand(options) {
187
184
  if (gitDiff.changedFiles.length === 0) {
188
185
  console.error(chalk_1.default.red('❌ Error: No changes detected.'));
189
186
  console.error(chalk_1.default.red(' Threadline check requires code changes to analyze.'));
190
- console.error(chalk_1.default.red(' This may indicate a problem with git diff detection.'));
191
187
  process.exit(1);
192
188
  }
193
189
  // Check for zero diff (files changed but no actual code changes)
@@ -249,7 +245,8 @@ async function checkCommand(options) {
249
245
  process.exit(hasAttention ? 1 : 0);
250
246
  }
251
247
  catch (error) {
252
- console.error(chalk_1.default.red(`\n❌ Error: ${error.message}`));
248
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
249
+ console.error(chalk_1.default.red(`\n❌ Error: ${errorMessage}`));
253
250
  process.exit(1);
254
251
  }
255
252
  }
@@ -108,7 +108,8 @@ async function initCommand() {
108
108
  console.log(chalk_1.default.gray(' (Use npx --yes threadlines check in non-interactive environments)'));
109
109
  }
110
110
  catch (error) {
111
- console.error(chalk_1.default.red(`\n❌ Error: ${error.message}`));
111
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
112
+ console.error(chalk_1.default.red(`\n❌ Error: ${errorMessage}`));
112
113
  process.exit(1);
113
114
  }
114
115
  }
package/dist/git/diff.js CHANGED
@@ -45,7 +45,8 @@ async function getBranchDiff(repoRoot, branchName, baseBranch) {
45
45
  }
46
46
  catch (error) {
47
47
  // If no previous commit, return empty (first commit)
48
- console.log(`[DEBUG] No previous commit found (first commit or error): ${error.message || 'HEAD~1 does not exist'}`);
48
+ const errorMessage = error instanceof Error ? error.message : 'HEAD~1 does not exist';
49
+ console.log(`[DEBUG] No previous commit found (first commit or error): ${errorMessage}`);
49
50
  return {
50
51
  diff: '',
51
52
  changedFiles: []
@@ -88,7 +89,8 @@ async function getBranchDiff(repoRoot, branchName, baseBranch) {
88
89
  }
89
90
  }
90
91
  catch (error) {
91
- console.log(`[DEBUG] Upstream tracking branch not set for '${branchName}': ${error.message || 'no upstream configured'}`);
92
+ const errorMessage = error instanceof Error ? error.message : 'no upstream configured';
93
+ console.log(`[DEBUG] Upstream tracking branch not set for '${branchName}': ${errorMessage}`);
92
94
  }
93
95
  // Strategy 2: Try default branch from origin/HEAD (reliable if configured)
94
96
  try {
@@ -117,7 +119,8 @@ async function getBranchDiff(repoRoot, branchName, baseBranch) {
117
119
  }
118
120
  }
119
121
  catch (error) {
120
- console.log(`[DEBUG] Default branch (refs/remotes/origin/HEAD) not configured: ${error.message || 'not found'}`);
122
+ const errorMessage = error instanceof Error ? error.message : 'not found';
123
+ console.log(`[DEBUG] Default branch (refs/remotes/origin/HEAD) not configured: ${errorMessage}`);
121
124
  }
122
125
  // Strategy 3: Try common branch names by checking remote refs first, then local branches
123
126
  // This works reliably in CI with fetch-depth: 0, and also works locally
@@ -146,7 +149,8 @@ async function getBranchDiff(repoRoot, branchName, baseBranch) {
146
149
  }
147
150
  }
148
151
  catch (error) {
149
- console.log(`[DEBUG] Remote branch 'origin/${candidate}' not found: ${error.message || 'does not exist'}`);
152
+ const errorMessage = error instanceof Error ? error.message : 'does not exist';
153
+ console.log(`[DEBUG] Remote branch 'origin/${candidate}' not found: ${errorMessage}`);
150
154
  // If remote doesn't exist, also try local branch (especially for CI like Vercel)
151
155
  try {
152
156
  await git.revparse([candidate]);
@@ -154,7 +158,8 @@ async function getBranchDiff(repoRoot, branchName, baseBranch) {
154
158
  return candidate;
155
159
  }
156
160
  catch (localError) {
157
- console.log(`[DEBUG] Local branch '${candidate}' also not found: ${localError.message || 'does not exist'}`);
161
+ const localErrorMessage = localError instanceof Error ? localError.message : 'does not exist';
162
+ console.log(`[DEBUG] Local branch '${candidate}' also not found: ${localErrorMessage}`);
158
163
  // Continue to next candidate
159
164
  }
160
165
  }
@@ -187,7 +192,7 @@ async function getCommitMessage(repoRoot, sha) {
187
192
  const message = await git.show([sha, '--format=%B', '--no-patch']);
188
193
  return message.trim() || null;
189
194
  }
190
- catch (error) {
195
+ catch {
191
196
  // Commit not found or invalid
192
197
  return null;
193
198
  }
@@ -225,7 +230,9 @@ async function getCommitDiff(repoRoot, sha) {
225
230
  changedFiles = diffSummary.files.map(f => f.file);
226
231
  }
227
232
  catch (diffError) {
228
- throw new Error(`Commit ${sha} not found or invalid: ${error.message || diffError.message}`);
233
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
234
+ const diffErrorMessage = diffError instanceof Error ? diffError.message : 'Unknown error';
235
+ throw new Error(`Commit ${sha} not found or invalid: ${errorMessage || diffErrorMessage}`);
229
236
  }
230
237
  }
231
238
  return {
package/dist/git/file.js CHANGED
@@ -57,7 +57,7 @@ async function getFileContent(repoRoot, filePath) {
57
57
  const content = fs.readFileSync(fullPath, 'utf-8');
58
58
  // Create artificial diff (all lines as additions)
59
59
  const lines = content.split('\n');
60
- const diff = lines.map((line, index) => `+${line}`).join('\n');
60
+ const diff = lines.map((line) => `+${line}`).join('\n');
61
61
  // Add diff header
62
62
  const diffHeader = `--- /dev/null\n+++ ${filePath}\n@@ -0,0 +1,${lines.length} @@\n`;
63
63
  const fullDiff = diffHeader + diff;
@@ -108,14 +108,15 @@ async function getFolderContent(repoRoot, folderPath) {
108
108
  const content = fs.readFileSync(path.resolve(repoRoot, filePath), 'utf-8');
109
109
  const lines = content.split('\n');
110
110
  // Create artificial diff for this file
111
- const fileDiff = lines.map((line, index) => `+${line}`).join('\n');
111
+ const fileDiff = lines.map((line) => `+${line}`).join('\n');
112
112
  const diffHeader = `--- /dev/null\n+++ ${filePath}\n@@ -0,0 +1,${lines.length} @@\n`;
113
113
  diffs.push(diffHeader + fileDiff);
114
114
  changedFiles.push(filePath);
115
115
  }
116
116
  catch (error) {
117
117
  // Skip files that can't be read (permissions, etc.)
118
- console.warn(`Warning: Could not read file '${filePath}': ${error.message}`);
118
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
119
+ console.warn(`Warning: Could not read file '${filePath}': ${errorMessage}`);
119
120
  }
120
121
  }
121
122
  return {
@@ -147,7 +148,7 @@ async function getMultipleFilesContent(repoRoot, filePaths) {
147
148
  const content = fs.readFileSync(fullPath, 'utf-8');
148
149
  const lines = content.split('\n');
149
150
  // Create artificial diff for this file
150
- const fileDiff = lines.map((line, index) => `+${line}`).join('\n');
151
+ const fileDiff = lines.map((line) => `+${line}`).join('\n');
151
152
  const diffHeader = `--- /dev/null\n+++ ${filePath}\n@@ -0,0 +1,${lines.length} @@\n`;
152
153
  diffs.push(diffHeader + fileDiff);
153
154
  changedFiles.push(filePath);
@@ -87,8 +87,9 @@ async function getGitHubDiff(repoRoot) {
87
87
  }
88
88
  catch (error) {
89
89
  // If we can't get the diff (e.g., first commit on branch), throw a clear error
90
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
90
91
  throw new Error(`Could not get diff for default branch '${defaultBranch}'. ` +
91
- `This might be the first commit on the branch. Error: ${error.message}`);
92
+ `This might be the first commit on the branch. Error: ${errorMessage}`);
92
93
  }
93
94
  }
94
95
  // Scenario 3: Feature Branch Push
package/dist/git/repo.js CHANGED
@@ -54,7 +54,7 @@ const path = __importStar(require("path"));
54
54
  * Uses GITHUB_REPOSITORY environment variable (format: "owner/repo").
55
55
  * This is the ONLY method for GitHub - no fallbacks, no alternatives.
56
56
  */
57
- async function getGitHubRepoName(repoRoot) {
57
+ async function getGitHubRepoName(_repoRoot) {
58
58
  const githubRepo = process.env.GITHUB_REPOSITORY;
59
59
  if (!githubRepo) {
60
60
  throw new Error('GitHub Actions: GITHUB_REPOSITORY environment variable is not set. ' +
@@ -69,7 +69,7 @@ async function getGitHubRepoName(repoRoot) {
69
69
  * Uses VERCEL_GIT_REPO_OWNER and VERCEL_GIT_REPO_SLUG environment variables.
70
70
  * This is the ONLY method for Vercel - no fallbacks, no alternatives.
71
71
  */
72
- async function getVercelRepoName(repoRoot) {
72
+ async function getVercelRepoName(_repoRoot) {
73
73
  const owner = process.env.VERCEL_GIT_REPO_OWNER;
74
74
  const slug = process.env.VERCEL_GIT_REPO_SLUG;
75
75
  if (!owner || !slug) {
@@ -103,11 +103,12 @@ async function getLocalRepoName(repoRoot) {
103
103
  }
104
104
  catch (error) {
105
105
  // If it's already our error, re-throw it
106
- if (error.message.includes('Local:')) {
106
+ if (error instanceof Error && error.message.includes('Local:')) {
107
107
  throw error;
108
108
  }
109
109
  // Otherwise, wrap it
110
- throw new Error(`Local: Failed to get repository name from git: ${error.message}`);
110
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
111
+ throw new Error(`Local: Failed to get repository name from git: ${errorMessage}`);
111
112
  }
112
113
  }
113
114
  /**
@@ -116,7 +117,7 @@ async function getLocalRepoName(repoRoot) {
116
117
  * Uses GITHUB_REF_NAME environment variable.
117
118
  * This is the ONLY method for GitHub - no fallbacks, no alternatives.
118
119
  */
119
- async function getGitHubBranchName(repoRoot) {
120
+ async function getGitHubBranchName(_repoRoot) {
120
121
  const refName = process.env.GITHUB_REF_NAME;
121
122
  if (!refName) {
122
123
  throw new Error('GitHub Actions: GITHUB_REF_NAME environment variable is not set. ' +
@@ -130,7 +131,7 @@ async function getGitHubBranchName(repoRoot) {
130
131
  * Uses VERCEL_GIT_COMMIT_REF environment variable.
131
132
  * This is the ONLY method for Vercel - no fallbacks, no alternatives.
132
133
  */
133
- async function getVercelBranchName(repoRoot) {
134
+ async function getVercelBranchName(_repoRoot) {
134
135
  const branchName = process.env.VERCEL_GIT_COMMIT_REF;
135
136
  if (!branchName) {
136
137
  throw new Error('Vercel: VERCEL_GIT_COMMIT_REF environment variable is not set. ' +
@@ -168,11 +169,12 @@ async function getLocalBranchName(repoRoot) {
168
169
  }
169
170
  catch (error) {
170
171
  // If it's already our error, re-throw it
171
- if (error.message.includes('Local:')) {
172
+ if (error instanceof Error && error.message.includes('Local:')) {
172
173
  throw error;
173
174
  }
174
175
  // Otherwise, wrap it
175
- throw new Error(`Local: Failed to get branch name from git: ${error.message}`);
176
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
177
+ throw new Error(`Local: Failed to get branch name from git: ${errorMessage}`);
176
178
  }
177
179
  }
178
180
  /**
@@ -181,7 +183,7 @@ async function getLocalBranchName(repoRoot) {
181
183
  * Uses CI_PROJECT_URL environment variable.
182
184
  * This is the ONLY method for GitLab - no fallbacks, no alternatives.
183
185
  */
184
- async function getGitLabRepoName(repoRoot) {
186
+ async function getGitLabRepoName(_repoRoot) {
185
187
  const projectUrl = process.env.CI_PROJECT_URL;
186
188
  if (!projectUrl) {
187
189
  throw new Error('GitLab CI: CI_PROJECT_URL environment variable is not set. ' +
@@ -197,7 +199,7 @@ async function getGitLabRepoName(repoRoot) {
197
199
  * Uses CI_COMMIT_REF_NAME environment variable.
198
200
  * This is the ONLY method for GitLab - no fallbacks, no alternatives.
199
201
  */
200
- async function getGitLabBranchName(repoRoot) {
202
+ async function getGitLabBranchName(_repoRoot) {
201
203
  const refName = process.env.CI_COMMIT_REF_NAME;
202
204
  if (!refName) {
203
205
  throw new Error('GitLab CI: CI_COMMIT_REF_NAME environment variable is not set. ' +
@@ -217,7 +219,7 @@ async function getGitLabBranchName(repoRoot) {
217
219
  * Returns the branch name (e.g., "main", "master") without the "origin/" prefix.
218
220
  * Throws an error if the default branch cannot be detected.
219
221
  */
220
- async function getDefaultBranchName(repoRoot) {
222
+ async function getDefaultBranchName(_repoRoot) {
221
223
  // GitHub Actions provides GITHUB_EVENT_PATH which contains repository.default_branch
222
224
  const githubEventPath = process.env.GITHUB_EVENT_PATH;
223
225
  if (!githubEventPath) {
@@ -240,11 +242,12 @@ async function getDefaultBranchName(repoRoot) {
240
242
  }
241
243
  catch (error) {
242
244
  // If it's already our error, re-throw it
243
- if (error.message.includes('GITHUB_EVENT_PATH') || error.message.includes('default_branch')) {
245
+ if (error instanceof Error && (error.message.includes('GITHUB_EVENT_PATH') || error.message.includes('default_branch'))) {
244
246
  throw error;
245
247
  }
246
248
  // Otherwise, wrap it
247
- throw new Error(`Failed to read or parse GITHUB_EVENT_PATH: ${error.message}. ` +
249
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
250
+ throw new Error(`Failed to read or parse GITHUB_EVENT_PATH: ${errorMessage}. ` +
248
251
  'This should be automatically provided by GitHub Actions.');
249
252
  }
250
253
  }
@@ -26,7 +26,7 @@ const gitlab_diff_1 = require("../git/gitlab-diff");
26
26
  * No fallbacks - if the environment-specific implementation fails, we fail clearly.
27
27
  * Each environment is completely isolated - changes to one don't affect others.
28
28
  */
29
- async function getDiffForEnvironment(environment, repoRoot, context) {
29
+ async function getDiffForEnvironment(environment, repoRoot, _context) {
30
30
  switch (environment) {
31
31
  case 'vercel':
32
32
  // Vercel: Single implementation using commit SHA
@@ -85,7 +85,7 @@ async function validateThreadline(filePath, repoRoot) {
85
85
  if (frontmatter.patterns && !Array.isArray(frontmatter.patterns)) {
86
86
  errors.push('patterns must be an array');
87
87
  }
88
- if (frontmatter.patterns && frontmatter.patterns.length === 0) {
88
+ if (Array.isArray(frontmatter.patterns) && frontmatter.patterns.length === 0) {
89
89
  errors.push('patterns array cannot be empty');
90
90
  }
91
91
  // Validate context_files if present
@@ -96,9 +96,11 @@ async function validateThreadline(filePath, repoRoot) {
96
96
  else {
97
97
  // Check if context files exist
98
98
  for (const contextFile of frontmatter.context_files) {
99
- const fullPath = path.join(repoRoot, contextFile);
100
- if (!fs.existsSync(fullPath)) {
101
- errors.push(`Context file not found: ${contextFile}`);
99
+ if (typeof contextFile === 'string') {
100
+ const fullPath = path.join(repoRoot, contextFile);
101
+ if (!fs.existsSync(fullPath)) {
102
+ errors.push(`Context file not found: ${contextFile}`);
103
+ }
102
104
  }
103
105
  }
104
106
  }
@@ -108,26 +110,28 @@ async function validateThreadline(filePath, repoRoot) {
108
110
  errors.push('Threadline body cannot be empty');
109
111
  }
110
112
  // Validate version format (basic semver check)
111
- if (frontmatter.version && !/^\d+\.\d+\.\d+/.test(frontmatter.version)) {
113
+ if (frontmatter.version && typeof frontmatter.version === 'string' && !/^\d+\.\d+\.\d+/.test(frontmatter.version)) {
112
114
  errors.push('version must be in semver format (e.g., 1.0.0)');
113
115
  }
114
116
  if (errors.length > 0) {
115
117
  return { valid: false, errors };
116
118
  }
119
+ // Type assertions for required fields (already validated above)
117
120
  const threadline = {
118
121
  id: frontmatter.id,
119
122
  version: frontmatter.version,
120
123
  patterns: frontmatter.patterns,
121
- contextFiles: frontmatter.context_files || [],
124
+ contextFiles: (Array.isArray(frontmatter.context_files) ? frontmatter.context_files.filter((f) => typeof f === 'string') : []),
122
125
  content: body,
123
126
  filePath: path.relative(repoRoot, filePath)
124
127
  };
125
128
  return { valid: true, threadline };
126
129
  }
127
130
  catch (error) {
131
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
128
132
  return {
129
133
  valid: false,
130
- errors: [`Failed to parse threadline file: ${error.message}`]
134
+ errors: [`Failed to parse threadline file: ${errorMessage}`]
131
135
  };
132
136
  }
133
137
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "threadlines",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "Threadline CLI - AI-powered linter based on your natural language documentation",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -27,10 +27,17 @@
27
27
  "directory": "packages/cli"
28
28
  },
29
29
  "scripts": {
30
- "build": "tsc",
30
+ "build": "npm run check && (npm run threadlines || exit 0) && tsc",
31
31
  "dev": "tsc --watch",
32
32
  "start": "node dist/index.js",
33
- "prepublishOnly": "npm run build"
33
+ "prepublishOnly": "npm run build",
34
+ "lint": "eslint src/**/*.ts",
35
+ "lint:fix": "eslint src/**/*.ts --fix",
36
+ "typecheck": "tsc --noEmit",
37
+ "check": "npm run lint && npm run typecheck",
38
+ "threadlines": "npx -y threadlines check",
39
+ "threadlines:local": "npx -y threadlines check || true",
40
+ "check:all": "npm run check && npm run threadlines"
34
41
  },
35
42
  "dependencies": {
36
43
  "axios": "^1.7.9",
@@ -45,6 +52,9 @@
45
52
  "@types/glob": "^8.1.0",
46
53
  "@types/js-yaml": "^4.0.9",
47
54
  "@types/node": "^22.10.2",
55
+ "@typescript-eslint/eslint-plugin": "^8.51.0",
56
+ "@typescript-eslint/parser": "^8.51.0",
57
+ "eslint": "^9.39.2",
48
58
  "typescript": "^5.7.2"
49
59
  }
50
60
  }