claude-autopm 1.28.0 → 1.30.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.
@@ -0,0 +1,322 @@
1
+ /**
2
+ * QueryParser - Parse command-line filter arguments into structured queries
3
+ *
4
+ * Converts CLI-style filter arguments into structured query objects for
5
+ * filtering PRDs, Epics, and Tasks.
6
+ *
7
+ * @example Basic Usage
8
+ * ```javascript
9
+ * const QueryParser = require('./lib/query-parser');
10
+ * const parser = new QueryParser();
11
+ *
12
+ * // Parse CLI arguments
13
+ * const query = parser.parse(['--status', 'active', '--priority', 'high']);
14
+ * // Returns: { status: 'active', priority: 'high' }
15
+ *
16
+ * // Validate query
17
+ * const validation = parser.validate(query);
18
+ * // Returns: { valid: true, errors: [] }
19
+ * ```
20
+ *
21
+ * @example Supported Filters
22
+ * ```javascript
23
+ * // Status filter
24
+ * parser.parse(['--status', 'active']);
25
+ *
26
+ * // Priority filter
27
+ * parser.parse(['--priority', 'P0']);
28
+ *
29
+ * // Date range filter
30
+ * parser.parse(['--created-after', '2025-01-01', '--created-before', '2025-12-31']);
31
+ *
32
+ * // Full-text search
33
+ * parser.parse(['--search', 'authentication API']);
34
+ *
35
+ * // Combined filters
36
+ * parser.parse([
37
+ * '--status', 'active',
38
+ * '--priority', 'high',
39
+ * '--created-after', '2025-01-01',
40
+ * '--search', 'OAuth2'
41
+ * ]);
42
+ * ```
43
+ *
44
+ * @module QueryParser
45
+ * @version 1.0.0
46
+ * @since v1.28.0
47
+ */
48
+
49
+ class QueryParser {
50
+ constructor() {
51
+ /**
52
+ * Supported filter names
53
+ * @type {string[]}
54
+ */
55
+ this.supportedFilters = [
56
+ 'status',
57
+ 'priority',
58
+ 'epic',
59
+ 'author',
60
+ 'assignee',
61
+ 'created-after',
62
+ 'created-before',
63
+ 'updated-after',
64
+ 'updated-before',
65
+ 'search'
66
+ ];
67
+
68
+ /**
69
+ * Valid status values
70
+ * @type {string[]}
71
+ */
72
+ this.validStatuses = [
73
+ 'draft',
74
+ 'active',
75
+ 'in_progress',
76
+ 'completed',
77
+ 'blocked',
78
+ 'archived'
79
+ ];
80
+
81
+ /**
82
+ * Valid priority values
83
+ * @type {string[]}
84
+ */
85
+ this.validPriorities = [
86
+ 'P0', 'P1', 'P2', 'P3',
87
+ 'p0', 'p1', 'p2', 'p3',
88
+ 'high', 'medium', 'low',
89
+ 'High', 'Medium', 'Low',
90
+ 'HIGH', 'MEDIUM', 'LOW'
91
+ ];
92
+
93
+ /**
94
+ * Date filter fields
95
+ * @type {string[]}
96
+ */
97
+ this.dateFilters = [
98
+ 'created-after',
99
+ 'created-before',
100
+ 'updated-after',
101
+ 'updated-before'
102
+ ];
103
+ }
104
+
105
+ /**
106
+ * Parse command-line filter arguments into structured query
107
+ *
108
+ * @param {string[]} args - Command-line arguments (e.g., ['--status', 'active'])
109
+ * @returns {Object} - Parsed query object
110
+ *
111
+ * @example
112
+ * const query = parser.parse(['--status', 'active', '--priority', 'high']);
113
+ * // Returns: { status: 'active', priority: 'high' }
114
+ */
115
+ parse(args) {
116
+ const query = {};
117
+
118
+ if (!Array.isArray(args) || args.length === 0) {
119
+ return query;
120
+ }
121
+
122
+ for (let i = 0; i < args.length; i++) {
123
+ const arg = args[i];
124
+
125
+ // Check if this is a filter flag (starts with --)
126
+ if (!arg.startsWith('--')) {
127
+ continue;
128
+ }
129
+
130
+ // Extract filter name (remove -- prefix)
131
+ const filterName = arg.substring(2);
132
+
133
+ // Check if this is a supported filter
134
+ if (!this.supportedFilters.includes(filterName)) {
135
+ continue;
136
+ }
137
+
138
+ // Get the next argument as the value
139
+ const value = args[i + 1];
140
+
141
+ // Skip if no value or value is another flag
142
+ if (!value || value.startsWith('--')) {
143
+ continue;
144
+ }
145
+
146
+ // Trim whitespace
147
+ const trimmedValue = value.trim();
148
+
149
+ // Skip empty values
150
+ if (trimmedValue === '') {
151
+ continue;
152
+ }
153
+
154
+ // Add to query
155
+ query[filterName] = trimmedValue;
156
+
157
+ // Skip the next argument (we just consumed it as a value)
158
+ i++;
159
+ }
160
+
161
+ return query;
162
+ }
163
+
164
+ /**
165
+ * Validate query object
166
+ *
167
+ * Checks for valid date formats and other constraints.
168
+ *
169
+ * @param {Object} query - Query object to validate
170
+ * @returns {Object} - { valid: boolean, errors: string[] }
171
+ *
172
+ * @example
173
+ * const result = parser.validate({ status: 'active', 'created-after': '2025-01-01' });
174
+ * // Returns: { valid: true, errors: [] }
175
+ *
176
+ * const result2 = parser.validate({ 'created-after': 'invalid-date' });
177
+ * // Returns: { valid: false, errors: ['Invalid date format...'] }
178
+ */
179
+ validate(query) {
180
+ const errors = [];
181
+
182
+ if (!query || typeof query !== 'object') {
183
+ return { valid: true, errors: [] };
184
+ }
185
+
186
+ // Validate date filters
187
+ for (const dateFilter of this.dateFilters) {
188
+ if (query[dateFilter]) {
189
+ const value = query[dateFilter];
190
+
191
+ // Check YYYY-MM-DD format
192
+ if (!this.isValidDateFormat(value)) {
193
+ errors.push(`Invalid date format for ${dateFilter}: ${value} (expected YYYY-MM-DD)`);
194
+ }
195
+ }
196
+ }
197
+
198
+ return {
199
+ valid: errors.length === 0,
200
+ errors
201
+ };
202
+ }
203
+
204
+ /**
205
+ * Check if a string is a valid date in YYYY-MM-DD format
206
+ *
207
+ * @param {string} dateString - Date string to validate
208
+ * @returns {boolean} - True if valid
209
+ * @private
210
+ */
211
+ isValidDateFormat(dateString) {
212
+ // Check format: YYYY-MM-DD
213
+ const regex = /^\d{4}-\d{2}-\d{2}$/;
214
+ if (!regex.test(dateString)) {
215
+ return false;
216
+ }
217
+
218
+ // Parse and validate actual date
219
+ const parts = dateString.split('-');
220
+ const year = parseInt(parts[0], 10);
221
+ const month = parseInt(parts[1], 10);
222
+ const day = parseInt(parts[2], 10);
223
+
224
+ // Check ranges
225
+ if (month < 1 || month > 12) {
226
+ return false;
227
+ }
228
+
229
+ if (day < 1 || day > 31) {
230
+ return false;
231
+ }
232
+
233
+ // Create date and verify it matches input
234
+ // (this catches invalid dates like 2025-02-31)
235
+ const date = new Date(year, month - 1, day);
236
+ return (
237
+ date.getFullYear() === year &&
238
+ date.getMonth() === month - 1 &&
239
+ date.getDate() === day
240
+ );
241
+ }
242
+
243
+ /**
244
+ * Get list of supported filters
245
+ *
246
+ * @returns {string[]} - Array of supported filter names
247
+ *
248
+ * @example
249
+ * const filters = parser.getSupportedFilters();
250
+ * // Returns: ['status', 'priority', 'epic', ...]
251
+ */
252
+ getSupportedFilters() {
253
+ return [...this.supportedFilters];
254
+ }
255
+
256
+ /**
257
+ * Get help text for filters
258
+ *
259
+ * Returns formatted help text describing all available filters
260
+ * with examples.
261
+ *
262
+ * @returns {string} - Formatted help text
263
+ *
264
+ * @example
265
+ * console.log(parser.getFilterHelp());
266
+ * // Outputs:
267
+ * // Available Filters:
268
+ * // --status <value> Filter by status (draft, active, ...)
269
+ * // --priority <value> Filter by priority (P0, P1, high, ...)
270
+ * // ...
271
+ */
272
+ getFilterHelp() {
273
+ return `
274
+ Available Filters:
275
+
276
+ --status <value> Filter by status
277
+ Values: draft, active, in_progress, completed, blocked, archived
278
+ Example: --status active
279
+
280
+ --priority <value> Filter by priority
281
+ Values: P0, P1, P2, P3, high, medium, low
282
+ Example: --priority high
283
+
284
+ --epic <id> Filter by epic ID
285
+ Example: --epic epic-001
286
+
287
+ --author <name> Filter by author name
288
+ Example: --author john
289
+
290
+ --assignee <name> Filter by assignee name
291
+ Example: --assignee jane
292
+
293
+ --created-after <date> Created after date (YYYY-MM-DD)
294
+ Example: --created-after 2025-01-01
295
+
296
+ --created-before <date> Created before date (YYYY-MM-DD)
297
+ Example: --created-before 2025-12-31
298
+
299
+ --updated-after <date> Updated after date (YYYY-MM-DD)
300
+ Example: --updated-after 2025-06-01
301
+
302
+ --updated-before <date> Updated before date (YYYY-MM-DD)
303
+ Example: --updated-before 2025-06-30
304
+
305
+ --search <text> Full-text search in content and frontmatter
306
+ Example: --search "authentication API"
307
+
308
+ Examples:
309
+
310
+ # Filter by status and priority
311
+ --status active --priority high
312
+
313
+ # Filter by date range
314
+ --created-after 2025-01-01 --created-before 2025-12-31
315
+
316
+ # Combine filters
317
+ --status active --priority P0 --epic epic-001 --search "OAuth2"
318
+ `.trim();
319
+ }
320
+ }
321
+
322
+ module.exports = QueryParser;
@@ -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.28.0",
3
+ "version": "1.30.0",
4
4
  "description": "Autonomous Project Management Framework for Claude Code - Advanced AI-powered development automation",
5
5
  "main": "bin/autopm.js",
6
6
  "bin": {
@@ -114,6 +114,7 @@
114
114
  "README.md"
115
115
  ],
116
116
  "dependencies": {
117
+ "@octokit/rest": "^22.0.0",
117
118
  "azure-devops-node-api": "^15.1.1",
118
119
  "chalk": "^5.3.0",
119
120
  "dotenv": "^16.6.1",
@@ -128,9 +129,9 @@
128
129
  "ora": "^5.4.1",
129
130
  "simple-git": "^3.20.0",
130
131
  "table": "^6.8.1",
131
- "which": "^4.0.0",
132
+ "which": "^5.0.0",
132
133
  "yaml": "^2.8.1",
133
- "yargs": "^17.7.2"
134
+ "yargs": "^18.0.0"
134
135
  },
135
136
  "devDependencies": {
136
137
  "@jest/globals": "^30.1.2",
@@ -151,7 +152,7 @@
151
152
  "sinon": "^21.0.0"
152
153
  },
153
154
  "optionalDependencies": {
154
- "@playwright/mcp": "^0.0.40",
155
+ "@playwright/mcp": "^0.0.41",
155
156
  "@upstash/context7-mcp": "^1.0.0"
156
157
  },
157
158
  "publishConfig": {