threadlines 0.2.24 → 0.3.0

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.
package/README.md CHANGED
@@ -4,17 +4,17 @@ Threadline CLI - AI-powered linter based on your natural language documentation.
4
4
 
5
5
  ## Why Threadline?
6
6
 
7
- Getting teams to follow consistent quality standards is **hard**. Really hard.
7
+ Getting teams to consistently follow coding patterns and quality standards is **hard**. Really hard.
8
8
 
9
9
  - **Documentation** → Nobody reads it. Or it's outdated before you finish writing it.
10
10
  - **Linting** → Catches syntax errors, but misses nuanced stuff.
11
11
  - **AI Code Reviewers** → Powerful, but you can't trust them. Did they actually check what you care about? Can you customize them with your team's specific rules?
12
12
 
13
- **Threadline solves this** by running **separate, parallel, highly focused AI-powered reviews** - each focused on a single, specific concern. Your coding standards live in your repository as markdown files, version-controlled and always in sync with your codebase. Each threadline gets its own dedicated AI check, ensuring focused attention on what matters to your team.
13
+ **Threadline solves this** by running **separate, parallel, highly focused AI-powered reviews** - each focused on a single, specific concern or pattern: the stuff that takes engineers months to internalise - and they keep forgetting. Your coding patterns live in your repository as 'Threadline' markdown files, version-controlled and always in sync with your codebase. Each threadline is its own AI agent, ensuring focused attention on what matters to your team.
14
14
 
15
15
  ### What Makes Threadline Different?
16
16
 
17
- - **Focused Reviews** - Instead of one AI trying to check everything, Threadline runs multiple specialized AI reviewers in parallel. Each threadline focuses on one thing and does it well.
17
+ - **Focused Reviews** - Instead of one AI agent checking everything, Threadline runs multiple specialized AI reviewers in parallel. Each threadline focuses on one thing and does it well.
18
18
 
19
19
  - **Documentation That Lives With Your Code** - Your coding standards live in your repo, in a `/threadlines` folder. They're version-controlled, reviewable, and always in sync with your codebase.
20
20
 
@@ -24,18 +24,7 @@ Getting teams to follow consistent quality standards is **hard**. Really hard.
24
24
 
25
25
  ## Installation
26
26
 
27
- ### Option 1: Global Installation (Recommended for Regular Use)
28
-
29
- ```bash
30
- npm install -g threadlines
31
- ```
32
-
33
- Then use directly:
34
- ```bash
35
- threadlines check
36
- ```
37
-
38
- ### Option 2: Use with npx (No Installation)
27
+ ### Option 1: Use with npx (No Installation)
39
28
 
40
29
  ```bash
41
30
  npx threadlines check
@@ -82,8 +71,17 @@ Edit `threadlines/example.md` with your coding standards, then rename it to some
82
71
  ```bash
83
72
  npx threadlines check
84
73
  ```
74
+
85
75
  By default, analyzes your staged/unstaged git changes against all threadlines in the `/threadlines` directory.
86
76
 
77
+ **Review Context Types:**
78
+ - `local` - Staged/unstaged changes (default for local development)
79
+ - `commit` - Specific commit (when using `--commit` flag)
80
+ - `pr` - Pull Request/Merge Request (auto-detected in CI)
81
+ - `file` - Single file (when using `--file` flag)
82
+ - `folder` - Folder contents (when using `--folder` flag)
83
+ - `files` - Multiple files (when using `--files` flag)
84
+
87
85
  **Common Use Cases:**
88
86
 
89
87
  **Check latest commit locally:**
@@ -96,11 +94,6 @@ threadlines check --commit HEAD
96
94
  threadlines check --commit abc123def
97
95
  ```
98
96
 
99
- **Check all commits in a branch:**
100
- ```bash
101
- threadlines check --branch feature/new-feature
102
- ```
103
-
104
97
  **Check entire file(s):**
105
98
  ```bash
106
99
  threadlines check --file src/api/users.ts
@@ -108,24 +101,35 @@ threadlines check --files src/api/users.ts src/api/posts.ts
108
101
  threadlines check --folder src/api
109
102
  ```
110
103
 
104
+ **Debug mode (verbose output):**
105
+ ```bash
106
+ threadlines check --debug
107
+ ```
108
+
111
109
  **Show all results (not just violations):**
112
110
  ```bash
113
111
  threadlines check --full
114
112
  ```
115
113
 
114
+ **Enable debug logging:**
115
+ ```bash
116
+ threadlines check --debug
117
+ ```
118
+
116
119
  **Options:**
117
- - `--api-url <url>` - Override the server URL (default: http://localhost:3000)
118
- - `--commit <ref>` - Review specific commit. Accepts commit SHA or git reference (e.g., `HEAD`, `HEAD~1`, `abc123`)
119
- - `--branch <name>` - Review all commits in branch vs base
120
- - `--file <path>` - Review entire file (all lines as additions)
121
- - `--folder <path>` - Review all files in folder recursively
122
- - `--files <paths...>` - Review multiple specified files
120
+ - `--commit <ref>` - Review specific commit. Accepts commit SHA or git reference (e.g., `HEAD`, `HEAD~1`, `abc123`). Sets review context to `commit`.
121
+ - `--file <path>` - Review entire file (all lines as additions). Sets review context to `file`.
122
+ - `--folder <path>` - Review all files in folder recursively. Sets review context to `folder`.
123
+ - `--files <paths...>` - Review multiple specified files. Sets review context to `files`.
123
124
  - `--full` - Show all results (compliant, attention, not_relevant). Default: only attention items
125
+ - `--debug` - Enable debug logging (verbose output for troubleshooting)
126
+
127
+ **Note:** Flags (`--commit`, `--file`, `--folder`, `--files`) are for local development only. In CI/CD environments, these flags are ignored and the CLI auto-detects the appropriate context.
124
128
 
125
129
  **Auto-detection in CI:**
126
- - CI with branch detectedreviews all commits in branch vs base
127
- - CI with commit SHA detected reviews specific commit
128
- - Local development → reviews staged/unstaged changes
130
+ - **Pull Request/Merge Request context**Reviews all changes in the PR/MR (review context: `pr`)
131
+ - **Push to any branch**Reviews the commit being pushed (review context: `commit`)
132
+ - **Local development**Reviews staged/unstaged changes (review context: `local`)
129
133
 
130
134
  ## Configuration
131
135
 
@@ -135,11 +139,40 @@ threadlines check --full
135
139
  |----------|---------|----------|
136
140
  | `THREADLINE_API_KEY` | Authentication with Threadlines API | Yes |
137
141
  | `THREADLINE_ACCOUNT` | Your Threadlines account email | Yes |
138
- | `THREADLINE_API_URL` | Custom API endpoint (default: https://devthreadline.com) | No |
139
142
 
140
143
  Both required variables can be set in a `.env.local` file (recommended for local development) or as environment variables (required for CI/CD).
141
144
 
142
- The optional `THREADLINE_API_URL` allows you to point to a custom server if you want to intercept telemetry with your own endpoint. It can also be set with the `--api-url` flag.
145
+ **Local Development:**
146
+ Create a `.env.local` file in your project root:
147
+ ```bash
148
+ THREADLINE_API_KEY=your-api-key-here
149
+ THREADLINE_ACCOUNT=your-email@example.com
150
+ ```
151
+
152
+ **CI/CD:**
153
+ Set these as environment variables in your platform:
154
+ - **GitHub Actions**: Settings → Secrets → Add variables
155
+ - **GitLab CI**: Settings → CI/CD → Variables
156
+ - **Bitbucket Pipelines**: Repository settings → Repository variables
157
+ - **Vercel**: Settings → Environment Variables
158
+
159
+ Get your credentials at: https://devthreadline.com/settings
160
+
161
+ ### Configuration File (`.threadlinerc`)
162
+
163
+ You can customize the API endpoint and other settings by creating a `.threadlinerc` file in your project root:
164
+
165
+ ```json
166
+ {
167
+ "mode": "online",
168
+ "api_url": "https://devthreadline.com",
169
+ "openai_model": "gpt-5.2",
170
+ "openai_service_tier": "Flex",
171
+ "diff_context_lines": 10
172
+ }
173
+ ```
174
+
175
+ The `api_url` field allows you to point to a custom server if needed. Default is `https://devthreadline.com`.
143
176
 
144
177
  ## Threadline Files
145
178
 
@@ -176,26 +209,38 @@ Your guidelines and standards here...
176
209
 
177
210
  - **`context_files`**: Array of file paths that provide context (always included, even if unchanged)
178
211
 
179
- ### Example: SQL Queries with Schema Context
212
+ ### Example: Feature Flagging Standards
180
213
 
181
214
  ```markdown
182
215
  ---
183
- id: sql-queries
216
+ id: feature-flags
184
217
  version: 1.0.0
185
218
  patterns:
186
- - "**/queries/**"
187
- - "**/*.sql"
219
+ - "**/features/**"
220
+ - "**/components/**"
221
+ - "**/*.tsx"
222
+ - "**/*.ts"
188
223
  context_files:
189
- - "schema.sql"
224
+ - "config/feature-flags.ts"
190
225
  ---
191
226
 
192
- # SQL Query Standards
227
+ # Feature Flag Standards
228
+
229
+ All feature flag usage must:
230
+ - Check flags using the centralized `isFeatureEnabled()` function from `config/feature-flags.ts`
231
+ - Never hardcode feature flag names as strings (use constants from the config)
232
+ - Include proper cleanup: remove feature flag checks when features are fully rolled out
233
+ - Document rollout plan in PR description (target percentage, timeline)
234
+ - Use feature flags for gradual rollouts, not as permanent configuration
193
235
 
194
- All SQL queries must:
195
- - Reference tables and columns that exist in schema.sql
196
- - Use parameterized queries (no string concatenation)
197
- - Include proper indexes for WHERE clauses
236
+ **Violations:**
237
+ - `if (process.env.NEW_FEATURE === 'true')` (hardcoded, not using registry)
238
+ - `if (flags['new-feature'])` (string literal instead of constant)
239
+ - `if (isFeatureEnabled(FeatureFlags.NEW_DASHBOARD))` (using centralized function)
198
240
  ```
199
241
 
200
- The `schema.sql` file will always be included as context, even if you're only changing query files.
242
+ The `config/feature-flags.ts` file will always be included as context, ensuring the AI reviewer can verify that:
243
+ - Feature flag names match the registry
244
+ - The correct flag checking function is used
245
+ - Flags are properly typed and documented
201
246
 
@@ -19,5 +19,9 @@ class ReviewAPIClient {
19
19
  const response = await this.client.post('/api/threadline-check', request);
20
20
  return response.data;
21
21
  }
22
+ async syncResults(request) {
23
+ const response = await this.client.post('/api/threadline-check-results', request);
24
+ return response.data;
25
+ }
22
26
  }
23
27
  exports.ReviewAPIClient = ReviewAPIClient;
@@ -46,6 +46,7 @@ const ci_context_1 = require("../git/ci-context");
46
46
  const local_1 = require("../git/local");
47
47
  const config_file_1 = require("../utils/config-file");
48
48
  const logger_1 = require("../utils/logger");
49
+ const expert_1 = require("../processors/expert");
49
50
  const fs = __importStar(require("fs"));
50
51
  const path = __importStar(require("path"));
51
52
  const chalk_1 = __importDefault(require("chalk"));
@@ -78,7 +79,7 @@ async function checkCommand(options) {
78
79
  const repoRoot = cwd; // Keep for backward compatibility with rest of function
79
80
  // Load configuration
80
81
  const config = await (0, config_file_1.loadConfig)(cwd);
81
- console.log(chalk_1.default.blue(`🔍 Threadline CLI v${CLI_VERSION}: Checking code against your threadlines...\n`));
82
+ logger_1.logger.info(`🔍 Threadline CLI v${CLI_VERSION}: Checking code against your threadlines...\n`);
82
83
  // Get git root for consistent file paths across monorepo
83
84
  const git = (0, simple_git_1.default)(cwd);
84
85
  let gitRoot;
@@ -95,51 +96,36 @@ async function checkCommand(options) {
95
96
  logger_1.logger.error(`Failed to get git root: ${message}`);
96
97
  process.exit(1);
97
98
  }
98
- // Pre-flight check: Validate ALL required environment variables at once
99
- const apiKey = (0, config_1.getThreadlineApiKey)();
100
- const account = (0, config_1.getThreadlineAccount)();
101
- const missingVars = [];
102
- // Check for undefined, empty string, or literal unexpanded variable
103
- // GitLab CI keeps variables as literal "$VAR" if not defined in CI/CD settings
104
- if (!apiKey || apiKey === '$THREADLINE_API_KEY')
105
- missingVars.push('THREADLINE_API_KEY');
106
- if (!account || account === '$THREADLINE_ACCOUNT')
107
- missingVars.push('THREADLINE_ACCOUNT');
108
- if (missingVars.length > 0) {
109
- logger_1.logger.error('Missing required environment variables:');
110
- for (const varName of missingVars) {
111
- logger_1.logger.error(` • ${varName}`);
112
- }
113
- console.log('');
114
- console.log(chalk_1.default.yellow('To fix this:'));
115
- console.log('');
116
- console.log(chalk_1.default.white(' Local development:'));
117
- console.log(chalk_1.default.gray(' 1. Create a .env.local file in your project root'));
118
- console.log(chalk_1.default.gray(' 2. Add the missing variable(s):'));
119
- if (missingVars.includes('THREADLINE_API_KEY')) {
120
- console.log(chalk_1.default.gray(' THREADLINE_API_KEY=your-api-key-here'));
121
- }
122
- if (missingVars.includes('THREADLINE_ACCOUNT')) {
123
- console.log(chalk_1.default.gray(' THREADLINE_ACCOUNT=your-email@example.com'));
124
- }
125
- console.log(chalk_1.default.gray(' 3. Make sure .env.local is in your .gitignore'));
126
- console.log('');
127
- console.log(chalk_1.default.white(' CI/CD:'));
128
- console.log(chalk_1.default.gray(' GitHub Actions: Settings → Secrets → Add variables'));
129
- console.log(chalk_1.default.gray(' GitLab CI: Settings → CI/CD → Variables'));
130
- console.log(chalk_1.default.gray(' Bitbucket Pipelines: Repository settings → Repository variables'));
131
- console.log(chalk_1.default.gray(' Vercel: Settings → Environment Variables'));
132
- console.log('');
133
- console.log(chalk_1.default.gray('Get your credentials at: https://devthreadline.com/settings'));
99
+ // Pre-flight check: Validate OpenAI API key is set (required for local processing)
100
+ const openAIConfig = (0, config_1.getOpenAIConfig)(config);
101
+ if (!openAIConfig) {
102
+ logger_1.logger.error('Missing required environment variable: OPENAI_API_KEY');
103
+ logger_1.logger.output('');
104
+ logger_1.logger.output(chalk_1.default.yellow('To fix this:'));
105
+ logger_1.logger.output('');
106
+ logger_1.logger.output(chalk_1.default.white(' Local development:'));
107
+ logger_1.logger.output(chalk_1.default.gray(' 1. Create a .env.local file in your project root'));
108
+ logger_1.logger.output(chalk_1.default.gray(' 2. Add: OPENAI_API_KEY=your-openai-api-key'));
109
+ logger_1.logger.output(chalk_1.default.gray(' 3. Make sure .env.local is in your .gitignore'));
110
+ logger_1.logger.output('');
111
+ logger_1.logger.output(chalk_1.default.white(' CI/CD:'));
112
+ logger_1.logger.output(chalk_1.default.gray(' GitHub Actions: Settings → Secrets → Add OPENAI_API_KEY'));
113
+ logger_1.logger.output(chalk_1.default.gray(' GitLab CI: Settings → CI/CD → Variables → Add OPENAI_API_KEY'));
114
+ logger_1.logger.output(chalk_1.default.gray(' Bitbucket Pipelines: Repository settings → Repository variables → Add OPENAI_API_KEY'));
115
+ logger_1.logger.output(chalk_1.default.gray(' Vercel: Settings Environment Variables → Add OPENAI_API_KEY'));
116
+ logger_1.logger.output('');
117
+ logger_1.logger.output(chalk_1.default.gray('Get your OpenAI API key at: https://platform.openai.com/api-keys'));
134
118
  process.exit(1);
135
119
  }
120
+ // Log OpenAI configuration
121
+ (0, config_1.logOpenAIConfig)(openAIConfig);
136
122
  // 1. Find and validate threadlines
137
123
  logger_1.logger.info('Finding threadlines...');
138
124
  const threadlines = await (0, experts_1.findThreadlines)(cwd, gitRoot);
139
- console.log(chalk_1.default.green(`✓ Found ${threadlines.length} threadline(s)\n`));
125
+ logger_1.logger.info(`✓ Found ${threadlines.length} threadline(s)\n`);
140
126
  if (threadlines.length === 0) {
141
- console.log(chalk_1.default.yellow('⚠️ No valid threadlines found.'));
142
- console.log(chalk_1.default.gray(' Run `npx threadlines init` to create your first threadline.'));
127
+ logger_1.logger.warn('No valid threadlines found.');
128
+ logger_1.logger.output(chalk_1.default.gray(' Run `npx threadlines init` to create your first threadline.'));
143
129
  process.exit(0);
144
130
  }
145
131
  // 2. Detect environment and context
@@ -154,7 +140,7 @@ async function checkCommand(options) {
154
140
  // Validate mutually exclusive flags
155
141
  if (explicitFlags.length > 1) {
156
142
  logger_1.logger.error('Only one review option can be specified at a time');
157
- console.log(chalk_1.default.gray(' Options: --commit, --file, --folder, --files'));
143
+ logger_1.logger.output(chalk_1.default.gray(' Options: --commit, --file, --folder, --files'));
158
144
  process.exit(1);
159
145
  }
160
146
  // CI environments: auto-detect only, flags are ignored with warning
@@ -224,29 +210,29 @@ async function checkCommand(options) {
224
210
  }
225
211
  }
226
212
  if (gitDiff.changedFiles.length === 0) {
227
- console.error(chalk_1.default.bold('ℹ️ No changes detected.'));
213
+ logger_1.logger.info('ℹ️ No changes detected.');
228
214
  process.exit(0);
229
215
  }
230
216
  // Safety limit: prevent expensive API calls on large diffs
231
217
  const MAX_CHANGED_FILES = 20;
232
218
  if (gitDiff.changedFiles.length > MAX_CHANGED_FILES) {
233
- console.error(chalk_1.default.red(`❌ Too many changed files: ${gitDiff.changedFiles.length} (max: ${MAX_CHANGED_FILES})`));
234
- console.error(chalk_1.default.gray(' This limit prevents expensive API calls on large diffs.'));
235
- console.error(chalk_1.default.gray(' Consider reviewing smaller batches of changes.'));
219
+ logger_1.logger.error(`Too many changed files: ${gitDiff.changedFiles.length} (max: ${MAX_CHANGED_FILES})`);
220
+ logger_1.logger.output(chalk_1.default.gray(' This limit prevents expensive API calls on large diffs.'));
221
+ logger_1.logger.output(chalk_1.default.gray(' Consider reviewing smaller batches of changes.'));
236
222
  process.exit(1);
237
223
  }
238
224
  // Check for zero diff (files changed but no actual code changes)
239
225
  if (!gitDiff.diff || gitDiff.diff.trim() === '') {
240
- console.log(chalk_1.default.blue('ℹ️ No code changes detected. Diff contains zero lines added or removed.'));
241
- console.log(chalk_1.default.gray(` ${gitDiff.changedFiles.length} file(s) changed but no content modifications detected.`));
242
- console.log('');
243
- console.log(chalk_1.default.bold('Results:\n'));
244
- console.log(chalk_1.default.gray(`${threadlines.length} threadlines checked`));
245
- console.log(chalk_1.default.gray(` ${threadlines.length} not relevant`));
246
- console.log('');
226
+ logger_1.logger.info('ℹ️ No code changes detected. Diff contains zero lines added or removed.');
227
+ logger_1.logger.output(chalk_1.default.gray(` ${gitDiff.changedFiles.length} file(s) changed but no content modifications detected.`));
228
+ logger_1.logger.output('');
229
+ logger_1.logger.output(chalk_1.default.bold('Results:\n'));
230
+ logger_1.logger.output(chalk_1.default.gray(`${threadlines.length} threadlines checked`));
231
+ logger_1.logger.output(chalk_1.default.gray(` ${threadlines.length} not relevant`));
232
+ logger_1.logger.output('');
247
233
  process.exit(0);
248
234
  }
249
- console.log(chalk_1.default.green(`✓ Found ${gitDiff.changedFiles.length} changed file(s) (context: ${reviewContext})\n`));
235
+ logger_1.logger.info(`✓ Found ${gitDiff.changedFiles.length} changed file(s) (context: ${reviewContext})\n`);
250
236
  // Log the files being sent
251
237
  for (const file of gitDiff.changedFiles) {
252
238
  logger_1.logger.info(` → ${file}`);
@@ -281,26 +267,86 @@ async function checkCommand(options) {
281
267
  contextContent
282
268
  };
283
269
  });
284
- // 5. Call review API
270
+ // 5. Process threadlines locally using OpenAI
285
271
  logger_1.logger.info('Running threadline checks...');
286
- const client = new client_1.ReviewAPIClient(config.api_url);
287
- const response = await client.review({
288
- threadlines: threadlinesWithContext,
272
+ const processResponse = await (0, expert_1.processThreadlines)({
273
+ threadlines: threadlinesWithContext.map(t => ({
274
+ id: t.id,
275
+ version: t.version,
276
+ patterns: t.patterns,
277
+ content: t.content,
278
+ contextFiles: t.contextFiles,
279
+ contextContent: t.contextContent
280
+ })),
289
281
  diff: gitDiff.diff,
290
282
  files: gitDiff.changedFiles,
291
- apiKey: apiKey,
292
- account: account,
293
- repoName: repoName,
294
- branchName: branchName,
295
- commitSha: metadata.commitSha,
296
- commitMessage: metadata.commitMessage,
297
- commitAuthorName: metadata.commitAuthorName,
298
- commitAuthorEmail: metadata.commitAuthorEmail,
299
- prTitle: metadata.prTitle,
300
- environment: environment,
301
- cliVersion: CLI_VERSION,
302
- reviewContext: reviewContext
283
+ apiKey: openAIConfig.apiKey,
284
+ model: openAIConfig.model,
285
+ serviceTier: openAIConfig.serviceTier,
286
+ contextLinesForLLM: config.diff_context_lines
303
287
  });
288
+ // Convert ProcessThreadlinesResponse to ReviewResponse format for displayResults
289
+ const response = {
290
+ results: processResponse.results,
291
+ metadata: {
292
+ totalThreadlines: processResponse.metadata.totalThreadlines,
293
+ completed: processResponse.metadata.completed,
294
+ timedOut: processResponse.metadata.timedOut,
295
+ errors: processResponse.metadata.errors
296
+ }
297
+ };
298
+ // 6. Sync results to web app (if mode is "online")
299
+ if (config.mode === 'online') {
300
+ const apiKey = (0, config_1.getThreadlineApiKey)();
301
+ const account = (0, config_1.getThreadlineAccount)();
302
+ if (!apiKey || !account) {
303
+ // Configuration error: mode is "online" but credentials are missing
304
+ // Fail loudly - this is a user configuration error that needs to be fixed
305
+ logger_1.logger.error('Sync mode is "online" but required credentials are missing.');
306
+ logger_1.logger.error('Set THREADLINE_API_KEY and THREADLINE_ACCOUNT environment variables to enable syncing.');
307
+ logger_1.logger.error('Alternatively, set mode to "offline" in .threadlinerc to disable syncing.');
308
+ throw new Error('Sync configuration error: mode is "online" but THREADLINE_API_KEY or THREADLINE_ACCOUNT is not set. ' +
309
+ 'Either set these environment variables or change mode to "offline" in .threadlinerc.');
310
+ }
311
+ // Attempt sync - if it fails, show error but don't fail the check (local processing succeeded)
312
+ try {
313
+ logger_1.logger.info('Syncing results to web app...');
314
+ const client = new client_1.ReviewAPIClient(config.api_url);
315
+ await client.syncResults({
316
+ threadlines: threadlinesWithContext,
317
+ diff: gitDiff.diff,
318
+ files: gitDiff.changedFiles,
319
+ results: processResponse.results,
320
+ metadata: {
321
+ totalThreadlines: processResponse.metadata.totalThreadlines,
322
+ completed: processResponse.metadata.completed,
323
+ timedOut: processResponse.metadata.timedOut,
324
+ errors: processResponse.metadata.errors,
325
+ llmModel: processResponse.metadata.llmModel
326
+ },
327
+ apiKey,
328
+ account,
329
+ repoName,
330
+ branchName,
331
+ commitSha: metadata.commitSha,
332
+ commitMessage: metadata.commitMessage,
333
+ commitAuthorName: metadata.commitAuthorName,
334
+ commitAuthorEmail: metadata.commitAuthorEmail,
335
+ prTitle: metadata.prTitle,
336
+ environment: environment,
337
+ cliVersion: CLI_VERSION,
338
+ reviewContext: reviewContext
339
+ });
340
+ logger_1.logger.info('✓ Results synced successfully');
341
+ }
342
+ catch (error) {
343
+ // Sync API call failed - show error prominently but don't fail the check (local processing succeeded)
344
+ // This is not a silent fallback: we explicitly show the error and explain why we continue
345
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
346
+ logger_1.logger.error(`Failed to sync results to web app: ${errorMessage}`);
347
+ logger_1.logger.warn('Check results are still valid - sync failure does not affect local processing.');
348
+ }
349
+ }
304
350
  // 7. Display results (with filtering if --full not specified)
305
351
  displayResults(response, options.full || false);
306
352
  // Exit with appropriate code (attention or errors = failure)
@@ -315,7 +361,7 @@ function displayResults(response, showFull) {
315
361
  : results.filter((r) => r.status === 'attention');
316
362
  // Display informational message if present (e.g., zero diffs)
317
363
  if (message) {
318
- console.log('\n' + chalk_1.default.blue('ℹ️ ' + message));
364
+ logger_1.logger.output('\n' + chalk_1.default.blue('ℹ️ ' + message));
319
365
  }
320
366
  const notRelevant = results.filter((r) => r.status === 'not_relevant').length;
321
367
  const compliant = results.filter((r) => r.status === 'compliant').length;
@@ -344,81 +390,81 @@ function displayResults(response, showFull) {
344
390
  // Show success message with breakdown if no issues
345
391
  if (attention === 0 && metadata.timedOut === 0 && errors === 0) {
346
392
  const summary = summaryParts.length > 0 ? ` (${summaryParts.join(', ')})` : '';
347
- console.log('\n' + chalk_1.default.green(`✓ Threadline check passed${summary}`));
348
- console.log(chalk_1.default.gray(` ${metadata.totalThreadlines} threadline${metadata.totalThreadlines !== 1 ? 's' : ''} checked\n`));
393
+ logger_1.logger.output('\n' + chalk_1.default.green(`✓ Threadline check passed${summary}`));
394
+ logger_1.logger.output(chalk_1.default.gray(` ${metadata.totalThreadlines} threadline${metadata.totalThreadlines !== 1 ? 's' : ''} checked\n`));
349
395
  }
350
396
  else {
351
397
  // Show detailed breakdown when there are issues
352
- console.log('\n' + chalk_1.default.bold('Results:\n'));
353
- console.log(chalk_1.default.gray(`${metadata.totalThreadlines} threadlines checked`));
398
+ logger_1.logger.output('\n' + chalk_1.default.bold('Results:\n'));
399
+ logger_1.logger.output(chalk_1.default.gray(`${metadata.totalThreadlines} threadlines checked`));
354
400
  if (showFull) {
355
401
  // Show all results when --full flag is used
356
402
  if (notRelevant > 0) {
357
- console.log(chalk_1.default.gray(` ${notRelevant} not relevant`));
403
+ logger_1.logger.output(chalk_1.default.gray(` ${notRelevant} not relevant`));
358
404
  }
359
405
  if (compliant > 0) {
360
- console.log(chalk_1.default.green(` ${compliant} compliant`));
406
+ logger_1.logger.output(chalk_1.default.green(` ${compliant} compliant`));
361
407
  }
362
408
  if (attention > 0) {
363
- console.log(chalk_1.default.yellow(` ${attention} attention`));
409
+ logger_1.logger.output(chalk_1.default.yellow(` ${attention} attention`));
364
410
  }
365
411
  }
366
412
  else {
367
413
  // Default: only show attention items
368
414
  if (attention > 0) {
369
- console.log(chalk_1.default.yellow(` ${attention} attention`));
415
+ logger_1.logger.output(chalk_1.default.yellow(` ${attention} attention`));
370
416
  }
371
417
  }
372
418
  if (metadata.timedOut > 0) {
373
- console.log(chalk_1.default.yellow(` ${metadata.timedOut} timed out`));
419
+ logger_1.logger.output(chalk_1.default.yellow(` ${metadata.timedOut} timed out`));
374
420
  }
375
421
  if (errors > 0) {
376
- console.log(chalk_1.default.red(` ${errors} errors`));
422
+ logger_1.logger.output(chalk_1.default.red(` ${errors} errors`));
377
423
  }
378
- console.log('');
424
+ logger_1.logger.output('');
379
425
  }
380
426
  // Show attention items
381
427
  if (attentionItems.length > 0) {
382
428
  for (const item of attentionItems) {
383
- console.log(chalk_1.default.yellow(`[attention] ${item.expertId}`));
429
+ logger_1.logger.output(chalk_1.default.yellow(`[attention] ${item.expertId}`));
384
430
  if (item.fileReferences && item.fileReferences.length > 0) {
385
431
  // List all files as bullet points
386
432
  for (const fileRef of item.fileReferences) {
387
433
  const lineRef = item.lineReferences?.[item.fileReferences.indexOf(fileRef)];
388
434
  const lineStr = lineRef ? `:${lineRef}` : '';
389
- console.log(chalk_1.default.gray(`* ${fileRef}${lineStr}`));
435
+ logger_1.logger.output(chalk_1.default.gray(`* ${fileRef}${lineStr}`));
390
436
  }
391
437
  }
392
438
  // Show reasoning once at the end (if available)
393
439
  if (item.reasoning) {
394
- console.log(chalk_1.default.gray(item.reasoning));
440
+ logger_1.logger.output(chalk_1.default.gray(item.reasoning));
395
441
  }
396
442
  else if (!item.fileReferences || item.fileReferences.length === 0) {
397
- console.log(chalk_1.default.gray('Needs attention'));
443
+ logger_1.logger.output(chalk_1.default.gray('Needs attention'));
398
444
  }
399
- console.log(''); // Empty line between threadlines
445
+ logger_1.logger.output(''); // Empty line between threadlines
400
446
  }
401
447
  }
402
448
  // Show error items (always shown, regardless of --full flag)
403
449
  if (errorItems.length > 0) {
404
450
  for (const item of errorItems) {
405
- console.log(chalk_1.default.red(`[error] ${item.expertId}`));
451
+ logger_1.logger.output(chalk_1.default.red(`[error] ${item.expertId}`));
406
452
  // Show error message
407
453
  if (item.error) {
408
- console.log(chalk_1.default.red(` Error: ${item.error.message}`));
454
+ logger_1.logger.output(chalk_1.default.red(` Error: ${item.error.message}`));
409
455
  if (item.error.type) {
410
- console.log(chalk_1.default.red(` Type: ${item.error.type}`));
456
+ logger_1.logger.output(chalk_1.default.red(` Type: ${item.error.type}`));
411
457
  }
412
458
  if (item.error.code) {
413
- console.log(chalk_1.default.red(` Code: ${item.error.code}`));
459
+ logger_1.logger.output(chalk_1.default.red(` Code: ${item.error.code}`));
414
460
  }
415
461
  // Show raw response for debugging
416
462
  if (item.error.rawResponse) {
417
- console.log(chalk_1.default.gray(' Raw response:'));
418
- console.log(chalk_1.default.gray(JSON.stringify(item.error.rawResponse, null, 2).split('\n').map(line => ' ' + line).join('\n')));
463
+ logger_1.logger.output(chalk_1.default.gray(' Raw response:'));
464
+ logger_1.logger.output(chalk_1.default.gray(JSON.stringify(item.error.rawResponse, null, 2).split('\n').map(line => ' ' + line).join('\n')));
419
465
  }
420
466
  }
421
- console.log(''); // Empty line between errors
467
+ logger_1.logger.output(''); // Empty line between errors
422
468
  }
423
469
  }
424
470
  }