claude-autopm 1.29.0 → 1.30.1

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.
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Visual Diff Renderer
3
+ *
4
+ * Renders ASCII side-by-side comparisons of text differences.
5
+ * Highlights conflicting sections and provides context lines.
6
+ *
7
+ * @example
8
+ * const VisualDiff = require('./lib/visual-diff');
9
+ *
10
+ * const diff = new VisualDiff({
11
+ * columnWidth: 80
12
+ * });
13
+ *
14
+ * // Render side-by-side comparison
15
+ * const rendered = diff.sideBySide(localContent, remoteContent);
16
+ * console.log(rendered);
17
+ *
18
+ * // Highlight conflicts
19
+ * const highlighted = diff.highlightConflicts(text, conflicts);
20
+ *
21
+ * // Render with context
22
+ * const context = diff.renderContext(text, [5, 10], 3);
23
+ */
24
+
25
+ class VisualDiff {
26
+ /**
27
+ * Create a new VisualDiff instance
28
+ *
29
+ * @param {Object} options - Configuration options
30
+ * @param {number} options.columnWidth - Width of each column in side-by-side view (default: 60)
31
+ * @param {boolean} options.showLineNumbers - Show line numbers (default: true)
32
+ * @param {string} options.separator - Column separator (default: ' | ')
33
+ */
34
+ constructor(options = {}) {
35
+ // Validate configuration
36
+ if (options.columnWidth !== undefined) {
37
+ if (typeof options.columnWidth !== 'number' || options.columnWidth <= 0) {
38
+ throw new Error('columnWidth must be a positive number');
39
+ }
40
+ }
41
+
42
+ this.options = {
43
+ columnWidth: options.columnWidth || 60,
44
+ showLineNumbers: options.showLineNumbers !== false,
45
+ separator: options.separator || ' | '
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Render side-by-side comparison of two text versions
51
+ *
52
+ * @param {string} left - Left side content (local)
53
+ * @param {string} right - Right side content (remote)
54
+ * @param {Object} options - Rendering options
55
+ * @returns {string} ASCII side-by-side comparison
56
+ */
57
+ sideBySide(left, right, options = {}) {
58
+ const leftLines = left.split('\n');
59
+ const rightLines = right.split('\n');
60
+
61
+ const maxLines = Math.max(leftLines.length, rightLines.length);
62
+
63
+ const output = [];
64
+
65
+ // Header
66
+ const colWidth = this.options.columnWidth;
67
+ const header = this._padRight('LOCAL', colWidth) +
68
+ this.options.separator +
69
+ this._padRight('REMOTE', colWidth);
70
+ output.push(header);
71
+ output.push('='.repeat(header.length));
72
+
73
+ // Content lines
74
+ for (let i = 0; i < maxLines; i++) {
75
+ const leftLine = leftLines[i] || '';
76
+ const rightLine = rightLines[i] || '';
77
+
78
+ const lineNum = this.options.showLineNumbers ? `${(i + 1).toString().padStart(4)} ` : '';
79
+
80
+ const leftContent = this._truncate(leftLine, colWidth - lineNum.length);
81
+ const rightContent = this._truncate(rightLine, colWidth - lineNum.length);
82
+
83
+ // Mark differences
84
+ const marker = leftLine !== rightLine ? '*' : ' ';
85
+
86
+ const line = marker + lineNum + this._padRight(leftContent, colWidth - lineNum.length - 1) +
87
+ this.options.separator +
88
+ marker + lineNum + this._padRight(rightContent, colWidth - lineNum.length - 1);
89
+
90
+ output.push(line);
91
+ }
92
+
93
+ return output.join('\n');
94
+ }
95
+
96
+ /**
97
+ * Highlight conflicts in text with conflict markers
98
+ *
99
+ * @param {string} text - Original text
100
+ * @param {Array<Object>} conflicts - Array of conflict objects
101
+ * @returns {string} Text with conflict markers inserted
102
+ */
103
+ highlightConflicts(text, conflicts) {
104
+ const lines = text.split('\n');
105
+
106
+ // Sort conflicts by line number (ascending)
107
+ const sortedConflicts = [...conflicts].sort((a, b) => a.line - b.line);
108
+
109
+ // Build result in a single pass
110
+ const result = [];
111
+ let conflictIdx = 0;
112
+ for (let i = 0; i < lines.length; i++) {
113
+ // Check if current line matches the next conflict
114
+ if (
115
+ conflictIdx < sortedConflicts.length &&
116
+ sortedConflicts[conflictIdx].line - 1 === i
117
+ ) {
118
+ const conflict = sortedConflicts[conflictIdx];
119
+ result.push('<<<<<<< LOCAL');
120
+ result.push(conflict.localContent);
121
+ result.push('=======');
122
+ result.push(conflict.remoteContent);
123
+ result.push('>>>>>>> REMOTE');
124
+ conflictIdx++;
125
+ } else {
126
+ result.push(lines[i]);
127
+ }
128
+ }
129
+
130
+ return result.join('\n');
131
+ }
132
+
133
+ /**
134
+ * Render context lines around specified line numbers
135
+ *
136
+ * @param {string} text - Full text content
137
+ * @param {Array<number>} lineNumbers - Line numbers to show context for
138
+ * @param {number} contextLines - Number of context lines before and after (default: 3)
139
+ * @returns {string} Text with only context lines
140
+ */
141
+ renderContext(text, lineNumbers, contextLines = 3) {
142
+ const lines = text.split('\n');
143
+ const lineSet = new Set();
144
+
145
+ // Add context lines for each specified line number
146
+ for (const lineNum of lineNumbers) {
147
+ const lineIndex = lineNum - 1; // Convert to 0-based
148
+
149
+ // Add lines in context range
150
+ const start = Math.max(0, lineIndex - contextLines);
151
+ const end = Math.min(lines.length - 1, lineIndex + contextLines);
152
+
153
+ for (let i = start; i <= end; i++) {
154
+ lineSet.add(i);
155
+ }
156
+ }
157
+
158
+ // Convert to sorted array
159
+ const includedLines = Array.from(lineSet).sort((a, b) => a - b);
160
+
161
+ // Build output with ellipsis for gaps
162
+ const output = [];
163
+ let lastLine = -2;
164
+
165
+ for (const lineIndex of includedLines) {
166
+ // Add ellipsis if there's a gap
167
+ if (lineIndex > lastLine + 1) {
168
+ output.push('...');
169
+ }
170
+
171
+ // Add the line with line number
172
+ const lineNum = (lineIndex + 1).toString().padStart(4);
173
+ output.push(`${lineNum} | ${lines[lineIndex]}`);
174
+
175
+ lastLine = lineIndex;
176
+ }
177
+
178
+ return output.join('\n');
179
+ }
180
+
181
+ /**
182
+ * Calculate diff statistics
183
+ *
184
+ * @param {string} left - Left side content
185
+ * @param {string} right - Right side content
186
+ * @returns {Object} Diff statistics
187
+ */
188
+ getStats(left, right) {
189
+ const leftLines = left.split('\n');
190
+ const rightLines = right.split('\n');
191
+
192
+ let added = 0;
193
+ let removed = 0;
194
+ let modified = 0;
195
+ let unchanged = 0;
196
+
197
+ const maxLines = Math.max(leftLines.length, rightLines.length);
198
+
199
+ for (let i = 0; i < maxLines; i++) {
200
+ const leftLine = leftLines[i];
201
+ const rightLine = rightLines[i];
202
+
203
+ if (leftLine === undefined && rightLine !== undefined) {
204
+ added++;
205
+ } else if (leftLine !== undefined && rightLine === undefined) {
206
+ removed++;
207
+ } else if (leftLine !== rightLine) {
208
+ modified++;
209
+ } else {
210
+ unchanged++;
211
+ }
212
+ }
213
+
214
+ return {
215
+ added,
216
+ removed,
217
+ modified,
218
+ unchanged,
219
+ total: maxLines
220
+ };
221
+ }
222
+
223
+ /**
224
+ * Truncate text to fit within column width
225
+ *
226
+ * @private
227
+ * @param {string} text - Text to truncate
228
+ * @param {number} maxWidth - Maximum width
229
+ * @returns {string} Truncated text
230
+ */
231
+ _truncate(text, maxWidth) {
232
+ if (text.length <= maxWidth) {
233
+ return text;
234
+ }
235
+ // If maxWidth is less than 3, we can't fit the ellipsis, so just truncate to maxWidth
236
+ if (maxWidth < 3) {
237
+ return text.substring(0, maxWidth);
238
+ }
239
+ return text.substring(0, maxWidth - 3) + '...';
240
+ }
241
+
242
+ /**
243
+ * Pad text to the right with spaces
244
+ *
245
+ * @private
246
+ * @param {string} text - Text to pad
247
+ * @param {number} width - Target width
248
+ * @returns {string} Padded text
249
+ */
250
+ _padRight(text, width) {
251
+ if (text.length >= width) {
252
+ return text.substring(0, width);
253
+ }
254
+
255
+ return text + ' '.repeat(width - text.length);
256
+ }
257
+
258
+ /**
259
+ * Render unified diff format (similar to git diff)
260
+ *
261
+ * @param {string} left - Left side content
262
+ * @param {string} right - Right side content
263
+ * @param {Object} options - Rendering options
264
+ * @returns {string} Unified diff output
265
+ */
266
+ unified(left, right, options = {}) {
267
+ const leftLines = left.split('\n');
268
+ const rightLines = right.split('\n');
269
+
270
+ const output = [];
271
+ output.push('--- LOCAL');
272
+ output.push('+++ REMOTE');
273
+ output.push(`@@ -1,${leftLines.length} +1,${rightLines.length} @@`);
274
+
275
+ const maxLines = Math.max(leftLines.length, rightLines.length);
276
+
277
+ for (let i = 0; i < maxLines; i++) {
278
+ const leftLine = leftLines[i];
279
+ const rightLine = rightLines[i];
280
+
281
+ if (leftLine === rightLine) {
282
+ output.push(` ${leftLine || ''}`);
283
+ } else if (leftLine === undefined) {
284
+ output.push(`+${rightLine}`);
285
+ } else if (rightLine === undefined) {
286
+ output.push(`-${leftLine}`);
287
+ } else {
288
+ output.push(`-${leftLine}`);
289
+ output.push(`+${rightLine}`);
290
+ }
291
+ }
292
+
293
+ return output.join('\n');
294
+ }
295
+ }
296
+
297
+ module.exports = VisualDiff;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-autopm",
3
- "version": "1.29.0",
3
+ "version": "1.30.1",
4
4
  "description": "Autonomous Project Management Framework for Claude Code - Advanced AI-powered development automation",
5
5
  "main": "bin/autopm.js",
6
6
  "bin": {