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.
- package/README.md +29 -1
- package/autopm/.claude/scripts/pm/prd-new.js +33 -6
- package/autopm/.claude/scripts/pm/template-list.js +25 -3
- package/autopm/.claude/scripts/pm/template-new.js +25 -3
- package/autopm/lib/README-FILTER-SEARCH.md +285 -0
- package/autopm/lib/analytics-engine.js +689 -0
- package/autopm/lib/batch-processor-integration.js +366 -0
- package/autopm/lib/batch-processor.js +278 -0
- package/autopm/lib/burndown-chart.js +415 -0
- package/autopm/lib/conflict-history.js +316 -0
- package/autopm/lib/conflict-resolver.js +330 -0
- package/autopm/lib/dependency-analyzer.js +466 -0
- package/autopm/lib/filter-engine.js +414 -0
- package/autopm/lib/guide/interactive-guide.js +756 -0
- package/autopm/lib/guide/manager.js +663 -0
- package/autopm/lib/query-parser.js +322 -0
- package/autopm/lib/template-engine.js +347 -0
- package/autopm/lib/visual-diff.js +297 -0
- package/install/install.js +2 -1
- package/lib/ai-providers/base-provider.js +110 -0
- package/lib/conflict-history.js +316 -0
- package/lib/conflict-resolver.js +330 -0
- package/lib/visual-diff.js +297 -0
- package/package.json +1 -1
|
@@ -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/install/install.js
CHANGED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base AI Provider Interface
|
|
3
|
+
*
|
|
4
|
+
* Abstract class that all AI providers must implement
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class BaseProvider {
|
|
8
|
+
constructor(config = {}) {
|
|
9
|
+
if (this.constructor === BaseProvider) {
|
|
10
|
+
throw new Error('BaseProvider is abstract and cannot be instantiated directly');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.apiKey = config.apiKey || process.env[this.getApiKeyEnvVar()];
|
|
15
|
+
this.model = config.model || this.getDefaultModel();
|
|
16
|
+
this.maxTokens = config.maxTokens || 4096;
|
|
17
|
+
this.temperature = config.temperature || 0.7;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get environment variable name for API key
|
|
22
|
+
* @abstract
|
|
23
|
+
*/
|
|
24
|
+
getApiKeyEnvVar() {
|
|
25
|
+
throw new Error('getApiKeyEnvVar() must be implemented by provider');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get default model name
|
|
30
|
+
* @abstract
|
|
31
|
+
*/
|
|
32
|
+
getDefaultModel() {
|
|
33
|
+
throw new Error('getDefaultModel() must be implemented by provider');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Complete a prompt (main interface)
|
|
38
|
+
* @abstract
|
|
39
|
+
* @param {string} prompt - The prompt to complete
|
|
40
|
+
* @param {Object} options - Additional options
|
|
41
|
+
* @returns {Promise<string>} - Completion result
|
|
42
|
+
*/
|
|
43
|
+
async complete(prompt, options = {}) {
|
|
44
|
+
throw new Error('complete() must be implemented by provider');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Stream completion (optional, for interactive mode)
|
|
49
|
+
* @abstract
|
|
50
|
+
* @param {string} prompt - The prompt to complete
|
|
51
|
+
* @param {Function} onChunk - Callback for each chunk
|
|
52
|
+
* @param {Object} options - Additional options
|
|
53
|
+
* @returns {Promise<string>} - Full completion result
|
|
54
|
+
*/
|
|
55
|
+
async streamComplete(prompt, onChunk, options = {}) {
|
|
56
|
+
// Default: fall back to non-streaming
|
|
57
|
+
const result = await this.complete(prompt, options);
|
|
58
|
+
if (onChunk) onChunk(result);
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Chat completion (multi-turn conversation)
|
|
64
|
+
* @param {Array<{role: string, content: string}>} messages
|
|
65
|
+
* @param {Object} options
|
|
66
|
+
* @returns {Promise<string>}
|
|
67
|
+
*/
|
|
68
|
+
async chat(messages, options = {}) {
|
|
69
|
+
// Default: convert to single prompt
|
|
70
|
+
const prompt = messages.map(m => `${m.role}: ${m.content}`).join('\n\n');
|
|
71
|
+
return await this.complete(prompt, options);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Validate API key
|
|
76
|
+
* @returns {Promise<boolean>}
|
|
77
|
+
*/
|
|
78
|
+
async validate() {
|
|
79
|
+
try {
|
|
80
|
+
await this.complete('Hello', { maxTokens: 10 });
|
|
81
|
+
return true;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get provider name
|
|
89
|
+
* @returns {string}
|
|
90
|
+
*/
|
|
91
|
+
getName() {
|
|
92
|
+
return this.constructor.name.replace('Provider', '');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get provider info
|
|
97
|
+
* @returns {Object}
|
|
98
|
+
*/
|
|
99
|
+
getInfo() {
|
|
100
|
+
return {
|
|
101
|
+
name: this.getName(),
|
|
102
|
+
model: this.model,
|
|
103
|
+
maxTokens: this.maxTokens,
|
|
104
|
+
temperature: this.temperature,
|
|
105
|
+
apiKeyConfigured: !!this.apiKey
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = BaseProvider;
|