@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.
Files changed (91) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +371 -0
  3. package/bin/git-diff-lines +146 -0
  4. package/bin/pair-review.js +49 -0
  5. package/package.json +71 -0
  6. package/public/css/ai-summary-modal.css +183 -0
  7. package/public/css/pr.css +8698 -0
  8. package/public/css/repo-settings.css +891 -0
  9. package/public/css/styles.css +479 -0
  10. package/public/favicon.png +0 -0
  11. package/public/index.html +1104 -0
  12. package/public/js/components/AIPanel.js +1639 -0
  13. package/public/js/components/AISummaryModal.js +278 -0
  14. package/public/js/components/AnalysisConfigModal.js +684 -0
  15. package/public/js/components/ConfirmDialog.js +227 -0
  16. package/public/js/components/PreviewModal.js +344 -0
  17. package/public/js/components/ProgressModal.js +678 -0
  18. package/public/js/components/ReviewModal.js +531 -0
  19. package/public/js/components/SplitButton.js +382 -0
  20. package/public/js/components/StatusIndicator.js +265 -0
  21. package/public/js/components/SuggestionNavigator.js +489 -0
  22. package/public/js/components/Toast.js +166 -0
  23. package/public/js/local.js +1580 -0
  24. package/public/js/modules/analysis-history.js +940 -0
  25. package/public/js/modules/comment-manager.js +643 -0
  26. package/public/js/modules/diff-renderer.js +585 -0
  27. package/public/js/modules/file-comment-manager.js +1242 -0
  28. package/public/js/modules/gap-coordinates.js +190 -0
  29. package/public/js/modules/hunk-parser.js +358 -0
  30. package/public/js/modules/line-tracker.js +386 -0
  31. package/public/js/modules/panel-resizer.js +228 -0
  32. package/public/js/modules/storage-cleanup.js +36 -0
  33. package/public/js/modules/suggestion-manager.js +692 -0
  34. package/public/js/pr.js +3503 -0
  35. package/public/js/repo-settings.js +691 -0
  36. package/public/js/utils/file-order.js +87 -0
  37. package/public/js/utils/markdown.js +97 -0
  38. package/public/js/utils/suggestion-ui.js +55 -0
  39. package/public/js/utils/tier-icons.js +25 -0
  40. package/public/local.html +460 -0
  41. package/public/pr.html +329 -0
  42. package/public/repo-settings.html +243 -0
  43. package/src/ai/analyzer.js +2592 -0
  44. package/src/ai/claude-cli.js +153 -0
  45. package/src/ai/claude-provider.js +261 -0
  46. package/src/ai/codex-provider.js +361 -0
  47. package/src/ai/copilot-provider.js +345 -0
  48. package/src/ai/gemini-provider.js +375 -0
  49. package/src/ai/index.js +47 -0
  50. package/src/ai/prompts/baseline/_meta.json +14 -0
  51. package/src/ai/prompts/baseline/level1/balanced.js +239 -0
  52. package/src/ai/prompts/baseline/level1/fast.js +194 -0
  53. package/src/ai/prompts/baseline/level1/thorough.js +319 -0
  54. package/src/ai/prompts/baseline/level2/balanced.js +248 -0
  55. package/src/ai/prompts/baseline/level2/fast.js +201 -0
  56. package/src/ai/prompts/baseline/level2/thorough.js +367 -0
  57. package/src/ai/prompts/baseline/level3/balanced.js +280 -0
  58. package/src/ai/prompts/baseline/level3/fast.js +220 -0
  59. package/src/ai/prompts/baseline/level3/thorough.js +459 -0
  60. package/src/ai/prompts/baseline/orchestration/balanced.js +259 -0
  61. package/src/ai/prompts/baseline/orchestration/fast.js +213 -0
  62. package/src/ai/prompts/baseline/orchestration/thorough.js +446 -0
  63. package/src/ai/prompts/config.js +52 -0
  64. package/src/ai/prompts/index.js +267 -0
  65. package/src/ai/prompts/shared/diff-instructions.js +50 -0
  66. package/src/ai/prompts/shared/output-schema.js +179 -0
  67. package/src/ai/prompts/shared/valid-files.js +37 -0
  68. package/src/ai/provider.js +260 -0
  69. package/src/config.js +139 -0
  70. package/src/database.js +2284 -0
  71. package/src/git/gitattributes.js +207 -0
  72. package/src/git/worktree.js +688 -0
  73. package/src/github/client.js +893 -0
  74. package/src/github/parser.js +247 -0
  75. package/src/local-review.js +691 -0
  76. package/src/main.js +987 -0
  77. package/src/routes/analysis.js +897 -0
  78. package/src/routes/comments.js +534 -0
  79. package/src/routes/config.js +250 -0
  80. package/src/routes/local.js +1728 -0
  81. package/src/routes/pr.js +1164 -0
  82. package/src/routes/shared.js +218 -0
  83. package/src/routes/worktrees.js +500 -0
  84. package/src/server.js +295 -0
  85. package/src/utils/diff-annotator.js +414 -0
  86. package/src/utils/instructions.js +33 -0
  87. package/src/utils/json-extractor.js +107 -0
  88. package/src/utils/line-validation.js +183 -0
  89. package/src/utils/logger.js +142 -0
  90. package/src/utils/paths.js +161 -0
  91. 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
+ };