threadlines 0.4.0 → 0.5.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.
Files changed (2) hide show
  1. package/dist/git/local.js +31 -48
  2. package/package.json +1 -1
package/dist/git/local.js CHANGED
@@ -95,58 +95,38 @@ async function getLocalContext(repoRoot, commitSha) {
95
95
  * or review unstaged changes if nothing is staged.
96
96
  */
97
97
  async function getDiff(repoRoot) {
98
- // Get git status in porcelain format to determine what changes exist
99
- // Porcelain format: XY filename
100
- // X = staged status, Y = unstaged status
101
- // ' ' = no change, 'M' = modified, 'A' = added, 'D' = deleted, etc.
102
- // '?' = untracked (only in Y position, X is always '?' too)
98
+ // Use git diff commands as source of truth (more reliable than git status --porcelain)
99
+ // git status --porcelain can be inconsistent in some edge cases
100
+ // Check staged files first (source of truth)
101
+ const stagedFilesOutput = (0, child_process_1.execSync)('git diff --cached --name-only', {
102
+ encoding: 'utf-8',
103
+ cwd: repoRoot
104
+ }).trim();
105
+ const actualStagedFiles = stagedFilesOutput ? stagedFilesOutput.split('\n') : [];
106
+ // Check unstaged files (source of truth)
107
+ const unstagedFilesOutput = (0, child_process_1.execSync)('git diff --name-only', {
108
+ encoding: 'utf-8',
109
+ cwd: repoRoot
110
+ }).trim();
111
+ const actualUnstagedFiles = unstagedFilesOutput ? unstagedFilesOutput.split('\n') : [];
112
+ // Get untracked files from git status --porcelain (only reliable way to get untracked)
103
113
  const statusOutput = (0, child_process_1.execSync)('git status --porcelain', {
104
114
  encoding: 'utf-8',
105
115
  cwd: repoRoot
106
116
  }).trim();
107
117
  const lines = statusOutput ? statusOutput.split('\n') : [];
108
- const staged = [];
109
- const unstaged = [];
110
118
  const untracked = [];
111
119
  for (const line of lines) {
112
120
  const stagedStatus = line[0];
113
121
  const unstagedStatus = line[1];
114
- // Collect untracked files separately (they need special handling)
122
+ // Collect untracked files (only reliable way to detect them)
115
123
  if (stagedStatus === '?' && unstagedStatus === '?') {
116
- // Format: "?? filename" - skip 3 characters
117
124
  const file = line.slice(3);
118
125
  untracked.push(file);
119
- continue;
120
- }
121
- // For tracked files, the format can be:
122
- // - "M filename" (staged, no leading space) - skip 2 characters
123
- // - " M filename" (unstaged, leading space) - skip 3 characters
124
- // - "MM filename" (both staged and unstaged) - skip 3 characters
125
- let file;
126
- if (stagedStatus !== ' ' && unstagedStatus === ' ') {
127
- // Staged only: "M filename" - skip 2 characters (M + space)
128
- file = line.slice(2);
129
- }
130
- else {
131
- // Unstaged or both: " M filename" or "MM filename" - skip 3 characters
132
- file = line.slice(3);
133
- }
134
- if (stagedStatus !== ' ') {
135
- staged.push(file);
136
- }
137
- if (unstagedStatus !== ' ' && unstagedStatus !== '?') {
138
- unstaged.push(file);
139
126
  }
140
127
  }
141
128
  let diff;
142
129
  let changedFiles;
143
- // Check if there are actually staged files (use git diff as source of truth)
144
- // git status parsing can be inconsistent, so we verify with git diff
145
- const stagedFilesOutput = (0, child_process_1.execSync)('git diff --cached --name-only', {
146
- encoding: 'utf-8',
147
- cwd: repoRoot
148
- }).trim();
149
- const actualStagedFiles = stagedFilesOutput ? stagedFilesOutput.split('\n') : [];
150
130
  // Workflow A: Developer has staged files - check ONLY staged files
151
131
  // (Ignore unstaged and untracked - developer explicitly chose to check staged)
152
132
  if (actualStagedFiles.length > 0) {
@@ -168,27 +148,22 @@ async function getDiff(repoRoot) {
168
148
  };
169
149
  }
170
150
  // No staged files - log clearly and continue to unstaged/untracked
171
- if (staged.length > 0) {
172
- // git status showed staged files but git diff doesn't - they were likely unstaged
173
- logger_1.logger.info(`No staged files detected (files may have been unstaged), checking unstaged/untracked files instead.`);
151
+ if (actualUnstagedFiles.length > 0 || untracked.length > 0) {
152
+ logger_1.logger.info(`No staged files, checking unstaged/untracked files.`);
174
153
  }
175
154
  else {
176
- logger_1.logger.info(`No staged files, checking unstaged/untracked files.`);
155
+ logger_1.logger.info(`No staged files detected.`);
177
156
  }
178
157
  // Workflow B: Developer hasn't staged files - check unstaged + untracked files
179
158
  // (Untracked files are conceptually "unstaged" - files being worked on but not committed)
180
- if (unstaged.length > 0 || untracked.length > 0) {
159
+ if (actualUnstagedFiles.length > 0 || untracked.length > 0) {
181
160
  // Get unstaged diff if there are unstaged files
182
- if (unstaged.length > 0) {
161
+ if (actualUnstagedFiles.length > 0) {
183
162
  diff = (0, child_process_1.execSync)('git diff -U200', {
184
163
  encoding: 'utf-8',
185
164
  cwd: repoRoot
186
165
  });
187
- const changedFilesOutput = (0, child_process_1.execSync)('git diff --name-only', {
188
- encoding: 'utf-8',
189
- cwd: repoRoot
190
- }).trim();
191
- changedFiles = changedFilesOutput ? changedFilesOutput.split('\n') : [];
166
+ changedFiles = actualUnstagedFiles;
192
167
  }
193
168
  else {
194
169
  diff = '';
@@ -222,6 +197,14 @@ async function getDiff(repoRoot) {
222
197
  ? (diff ? diff + '\n' : '') + untrackedDiffs.join('\n')
223
198
  : diff;
224
199
  const allChangedFiles = [...changedFiles, ...untrackedFileList];
200
+ // Validate that we actually have changes to review
201
+ // This can happen if:
202
+ // 1. git status showed files but git diff returns empty (files were staged/unstaged between commands)
203
+ // 2. All untracked items are directories (skipped)
204
+ // 3. Parsing incorrectly categorized files
205
+ if (allChangedFiles.length === 0 || !combinedDiff || combinedDiff.trim() === '') {
206
+ throw new Error('No changes detected. Stage files with "git add" or modify files to run threadlines.');
207
+ }
225
208
  const unstagedCount = changedFiles.length;
226
209
  const untrackedCount = untrackedFileList.length;
227
210
  if (unstagedCount > 0 && untrackedCount > 0) {
@@ -230,7 +213,7 @@ async function getDiff(repoRoot) {
230
213
  else if (unstagedCount > 0) {
231
214
  logger_1.logger.info(`Checking UNSTAGED changes (${unstagedCount} file(s))`);
232
215
  }
233
- else {
216
+ else if (untrackedCount > 0) {
234
217
  logger_1.logger.info(`Checking UNTRACKED files (${untrackedCount} file(s))`);
235
218
  }
236
219
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "threadlines",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Threadlines CLI - AI-powered linter based on your natural language documentation",
5
5
  "main": "dist/index.js",
6
6
  "bin": {