@in-the-loop-labs/pair-review 1.0.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/LICENSE +674 -0
- package/README.md +371 -0
- package/bin/git-diff-lines +146 -0
- package/bin/pair-review.js +49 -0
- package/package.json +71 -0
- package/public/css/ai-summary-modal.css +183 -0
- package/public/css/pr.css +8698 -0
- package/public/css/repo-settings.css +891 -0
- package/public/css/styles.css +479 -0
- package/public/favicon.png +0 -0
- package/public/index.html +1104 -0
- package/public/js/components/AIPanel.js +1639 -0
- package/public/js/components/AISummaryModal.js +278 -0
- package/public/js/components/AnalysisConfigModal.js +684 -0
- package/public/js/components/ConfirmDialog.js +227 -0
- package/public/js/components/PreviewModal.js +344 -0
- package/public/js/components/ProgressModal.js +678 -0
- package/public/js/components/ReviewModal.js +531 -0
- package/public/js/components/SplitButton.js +382 -0
- package/public/js/components/StatusIndicator.js +265 -0
- package/public/js/components/SuggestionNavigator.js +489 -0
- package/public/js/components/Toast.js +166 -0
- package/public/js/local.js +1580 -0
- package/public/js/modules/analysis-history.js +940 -0
- package/public/js/modules/comment-manager.js +643 -0
- package/public/js/modules/diff-renderer.js +585 -0
- package/public/js/modules/file-comment-manager.js +1242 -0
- package/public/js/modules/gap-coordinates.js +190 -0
- package/public/js/modules/hunk-parser.js +358 -0
- package/public/js/modules/line-tracker.js +386 -0
- package/public/js/modules/panel-resizer.js +228 -0
- package/public/js/modules/storage-cleanup.js +36 -0
- package/public/js/modules/suggestion-manager.js +692 -0
- package/public/js/pr.js +3503 -0
- package/public/js/repo-settings.js +691 -0
- package/public/js/utils/file-order.js +87 -0
- package/public/js/utils/markdown.js +97 -0
- package/public/js/utils/suggestion-ui.js +55 -0
- package/public/js/utils/tier-icons.js +25 -0
- package/public/local.html +460 -0
- package/public/pr.html +329 -0
- package/public/repo-settings.html +243 -0
- package/src/ai/analyzer.js +2592 -0
- package/src/ai/claude-cli.js +153 -0
- package/src/ai/claude-provider.js +261 -0
- package/src/ai/codex-provider.js +361 -0
- package/src/ai/copilot-provider.js +345 -0
- package/src/ai/gemini-provider.js +375 -0
- package/src/ai/index.js +47 -0
- package/src/ai/prompts/baseline/_meta.json +14 -0
- package/src/ai/prompts/baseline/level1/balanced.js +239 -0
- package/src/ai/prompts/baseline/level1/fast.js +194 -0
- package/src/ai/prompts/baseline/level1/thorough.js +319 -0
- package/src/ai/prompts/baseline/level2/balanced.js +248 -0
- package/src/ai/prompts/baseline/level2/fast.js +201 -0
- package/src/ai/prompts/baseline/level2/thorough.js +367 -0
- package/src/ai/prompts/baseline/level3/balanced.js +280 -0
- package/src/ai/prompts/baseline/level3/fast.js +220 -0
- package/src/ai/prompts/baseline/level3/thorough.js +459 -0
- package/src/ai/prompts/baseline/orchestration/balanced.js +259 -0
- package/src/ai/prompts/baseline/orchestration/fast.js +213 -0
- package/src/ai/prompts/baseline/orchestration/thorough.js +446 -0
- package/src/ai/prompts/config.js +52 -0
- package/src/ai/prompts/index.js +267 -0
- package/src/ai/prompts/shared/diff-instructions.js +50 -0
- package/src/ai/prompts/shared/output-schema.js +179 -0
- package/src/ai/prompts/shared/valid-files.js +37 -0
- package/src/ai/provider.js +260 -0
- package/src/config.js +139 -0
- package/src/database.js +2284 -0
- package/src/git/gitattributes.js +207 -0
- package/src/git/worktree.js +688 -0
- package/src/github/client.js +893 -0
- package/src/github/parser.js +247 -0
- package/src/local-review.js +691 -0
- package/src/main.js +987 -0
- package/src/routes/analysis.js +897 -0
- package/src/routes/comments.js +534 -0
- package/src/routes/config.js +250 -0
- package/src/routes/local.js +1728 -0
- package/src/routes/pr.js +1164 -0
- package/src/routes/shared.js +218 -0
- package/src/routes/worktrees.js +500 -0
- package/src/server.js +295 -0
- package/src/utils/diff-annotator.js +414 -0
- package/src/utils/instructions.js +33 -0
- package/src/utils/json-extractor.js +107 -0
- package/src/utils/line-validation.js +183 -0
- package/src/utils/logger.js +142 -0
- package/src/utils/paths.js +161 -0
- package/src/utils/stats-calculator.js +86 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
const fs = require('fs').promises;
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* GitAttributes parser for detecting linguist-generated files
|
|
7
|
+
*
|
|
8
|
+
* Parses .gitattributes files to identify files marked with:
|
|
9
|
+
* - linguist-generated=true
|
|
10
|
+
* - linguist-generated (without value, defaults to true)
|
|
11
|
+
*/
|
|
12
|
+
class GitAttributesParser {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.generatedPatterns = [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parse .gitattributes file from the worktree root
|
|
19
|
+
* @param {string} worktreePath - Path to the git worktree
|
|
20
|
+
* @returns {Promise<GitAttributesParser>} Returns self for chaining
|
|
21
|
+
*/
|
|
22
|
+
async parse(worktreePath) {
|
|
23
|
+
this.generatedPatterns = [];
|
|
24
|
+
|
|
25
|
+
const gitattributesPath = path.join(worktreePath, '.gitattributes');
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const content = await fs.readFile(gitattributesPath, 'utf8');
|
|
29
|
+
this.parseContent(content);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
// .gitattributes doesn't exist or can't be read - this is fine
|
|
32
|
+
if (error.code !== 'ENOENT') {
|
|
33
|
+
console.warn(`Warning: Could not read .gitattributes: ${error.message}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Parse .gitattributes content
|
|
42
|
+
* @param {string} content - Content of .gitattributes file
|
|
43
|
+
*/
|
|
44
|
+
parseContent(content) {
|
|
45
|
+
const lines = content.split('\n');
|
|
46
|
+
|
|
47
|
+
for (const line of lines) {
|
|
48
|
+
// Skip empty lines and comments
|
|
49
|
+
const trimmed = line.trim();
|
|
50
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check for linguist-generated attribute
|
|
55
|
+
// Formats:
|
|
56
|
+
// - pattern linguist-generated=true
|
|
57
|
+
// - pattern linguist-generated
|
|
58
|
+
// - pattern attr1 linguist-generated=true attr2
|
|
59
|
+
if (trimmed.includes('linguist-generated')) {
|
|
60
|
+
// Split on whitespace to get pattern and attributes
|
|
61
|
+
const parts = trimmed.split(/\s+/);
|
|
62
|
+
if (parts.length >= 2) {
|
|
63
|
+
const pattern = parts[0];
|
|
64
|
+
const attrs = parts.slice(1);
|
|
65
|
+
|
|
66
|
+
// Check if linguist-generated is set to true (or just present, which defaults to true)
|
|
67
|
+
const isGenerated = attrs.some(attr => {
|
|
68
|
+
if (attr === 'linguist-generated') return true;
|
|
69
|
+
if (attr === 'linguist-generated=true') return true;
|
|
70
|
+
if (attr === '-linguist-generated') return false; // Negation
|
|
71
|
+
if (attr === 'linguist-generated=false') return false;
|
|
72
|
+
return false;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (isGenerated) {
|
|
76
|
+
this.generatedPatterns.push(pattern);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Check if a file matches any of the generated patterns
|
|
85
|
+
* @param {string} filePath - Relative file path to check
|
|
86
|
+
* @returns {boolean} True if the file is marked as generated
|
|
87
|
+
*/
|
|
88
|
+
isGenerated(filePath) {
|
|
89
|
+
if (this.generatedPatterns.length === 0) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const pattern of this.generatedPatterns) {
|
|
94
|
+
if (this.matchPattern(pattern, filePath)) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Match a gitattributes pattern against a file path
|
|
104
|
+
* Supports:
|
|
105
|
+
* - Exact matches (package-lock.json)
|
|
106
|
+
* - Wildcards (*.min.js)
|
|
107
|
+
* - Double wildcards (**\/*.generated.js)
|
|
108
|
+
* - Directory matches (vendor/)
|
|
109
|
+
*
|
|
110
|
+
* @param {string} pattern - The gitattributes pattern
|
|
111
|
+
* @param {string} filePath - The file path to test
|
|
112
|
+
* @returns {boolean} True if the pattern matches
|
|
113
|
+
*/
|
|
114
|
+
matchPattern(pattern, filePath) {
|
|
115
|
+
// Normalize paths to use forward slashes
|
|
116
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
117
|
+
const normalizedPattern = pattern.replace(/\\/g, '/');
|
|
118
|
+
|
|
119
|
+
// Handle directory patterns (ending with /)
|
|
120
|
+
if (normalizedPattern.endsWith('/')) {
|
|
121
|
+
const dirPattern = normalizedPattern.slice(0, -1);
|
|
122
|
+
return normalizedPath.startsWith(dirPattern + '/');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Convert gitattributes pattern to regex
|
|
126
|
+
const regex = this.patternToRegex(normalizedPattern);
|
|
127
|
+
return regex.test(normalizedPath);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Convert a gitattributes glob pattern to a regular expression
|
|
132
|
+
* @param {string} pattern - The glob pattern
|
|
133
|
+
* @returns {RegExp} Regular expression for matching
|
|
134
|
+
*/
|
|
135
|
+
patternToRegex(pattern) {
|
|
136
|
+
// Escape regex special characters except * and ?
|
|
137
|
+
let regexStr = pattern
|
|
138
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
139
|
+
// Replace ** with a placeholder
|
|
140
|
+
.replace(/\*\*/g, '\x00')
|
|
141
|
+
// Replace * (not preceded by *) with match any within path segment
|
|
142
|
+
.replace(/\*/g, '[^/]*')
|
|
143
|
+
// Replace placeholder with match any including path separators
|
|
144
|
+
.replace(/\x00/g, '.*')
|
|
145
|
+
// Replace ? with match single character
|
|
146
|
+
.replace(/\?/g, '[^/]');
|
|
147
|
+
|
|
148
|
+
// If pattern doesn't contain a slash, it can match at any directory level
|
|
149
|
+
// e.g., "*.min.js" should match "foo/bar/file.min.js"
|
|
150
|
+
if (!pattern.includes('/')) {
|
|
151
|
+
regexStr = '(^|.*/)'+ regexStr + '$';
|
|
152
|
+
} else if (pattern.startsWith('/')) {
|
|
153
|
+
// Pattern starting with / matches from root only
|
|
154
|
+
regexStr = '^' + regexStr.slice(1) + '$';
|
|
155
|
+
} else {
|
|
156
|
+
// Pattern with slashes must match the exact path structure
|
|
157
|
+
regexStr = '(^|.*/)'+ regexStr + '$';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return new RegExp(regexStr);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get the list of generated file patterns
|
|
165
|
+
* @returns {Array<string>} Array of patterns
|
|
166
|
+
*/
|
|
167
|
+
getPatterns() {
|
|
168
|
+
return [...this.generatedPatterns];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get generated file patterns from a worktree
|
|
174
|
+
* @param {string} worktreePath - Path to the git worktree
|
|
175
|
+
* @returns {Promise<GitAttributesParser>} Parser instance with loaded patterns
|
|
176
|
+
*/
|
|
177
|
+
async function getGeneratedFilePatterns(worktreePath) {
|
|
178
|
+
const parser = new GitAttributesParser();
|
|
179
|
+
await parser.parse(worktreePath);
|
|
180
|
+
return parser;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Check if any of the given files are generated
|
|
185
|
+
* @param {string} worktreePath - Path to the git worktree
|
|
186
|
+
* @param {Array<Object>} files - Array of file objects with 'file' property
|
|
187
|
+
* @returns {Promise<Set<string>>} Set of generated file paths
|
|
188
|
+
*/
|
|
189
|
+
async function getGeneratedFiles(worktreePath, files) {
|
|
190
|
+
const parser = await getGeneratedFilePatterns(worktreePath);
|
|
191
|
+
const generatedFiles = new Set();
|
|
192
|
+
|
|
193
|
+
for (const fileObj of files) {
|
|
194
|
+
const filePath = typeof fileObj === 'string' ? fileObj : fileObj.file;
|
|
195
|
+
if (parser.isGenerated(filePath)) {
|
|
196
|
+
generatedFiles.add(filePath);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return generatedFiles;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = {
|
|
204
|
+
GitAttributesParser,
|
|
205
|
+
getGeneratedFilePatterns,
|
|
206
|
+
getGeneratedFiles
|
|
207
|
+
};
|