claude-autopm 1.18.0 → 1.20.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/README.md +159 -0
- package/autopm/.claude/agents/core/mcp-manager.md +1 -1
- package/autopm/.claude/commands/pm/context.md +11 -0
- package/autopm/.claude/commands/pm/epic-decompose.md +25 -2
- package/autopm/.claude/commands/pm/epic-oneshot.md +13 -0
- package/autopm/.claude/commands/pm/epic-start.md +19 -0
- package/autopm/.claude/commands/pm/epic-sync-modular.md +10 -10
- package/autopm/.claude/commands/pm/epic-sync.md +14 -14
- package/autopm/.claude/commands/pm/issue-start.md +50 -5
- package/autopm/.claude/commands/pm/issue-sync.md +15 -15
- package/autopm/.claude/commands/pm/what-next.md +11 -0
- package/autopm/.claude/mcp/MCP-REGISTRY.md +1 -1
- package/autopm/.claude/scripts/azure/active-work.js +2 -2
- package/autopm/.claude/scripts/azure/blocked.js +13 -13
- package/autopm/.claude/scripts/azure/daily.js +1 -1
- package/autopm/.claude/scripts/azure/dashboard.js +1 -1
- package/autopm/.claude/scripts/azure/feature-list.js +2 -2
- package/autopm/.claude/scripts/azure/feature-status.js +1 -1
- package/autopm/.claude/scripts/azure/next-task.js +1 -1
- package/autopm/.claude/scripts/azure/search.js +1 -1
- package/autopm/.claude/scripts/azure/setup.js +15 -15
- package/autopm/.claude/scripts/azure/sprint-report.js +2 -2
- package/autopm/.claude/scripts/azure/sync.js +1 -1
- package/autopm/.claude/scripts/azure/us-list.js +1 -1
- package/autopm/.claude/scripts/azure/us-status.js +1 -1
- package/autopm/.claude/scripts/azure/validate.js +13 -13
- package/autopm/.claude/scripts/lib/frontmatter-utils.sh +42 -7
- package/autopm/.claude/scripts/lib/logging-utils.sh +20 -16
- package/autopm/.claude/scripts/lib/validation-utils.sh +1 -1
- package/autopm/.claude/scripts/pm/context.js +338 -0
- package/autopm/.claude/scripts/pm/issue-sync/format-comment.sh +3 -3
- package/autopm/.claude/scripts/pm/lib/README.md +85 -0
- package/autopm/.claude/scripts/pm/lib/logger.js +78 -0
- package/autopm/.claude/scripts/pm/next.js +25 -1
- package/autopm/.claude/scripts/pm/what-next.js +660 -0
- package/bin/autopm.js +25 -0
- package/package.json +1 -1
- package/lib/agentExecutor.js.deprecated +0 -101
- package/lib/azure/cache.js +0 -80
- package/lib/azure/client.js +0 -77
- package/lib/azure/formatter.js +0 -177
- package/lib/commandHelpers.js +0 -177
- package/lib/context/manager.js +0 -290
- package/lib/documentation/manager.js +0 -528
- package/lib/github/workflow-manager.js +0 -546
- package/lib/helpers/azure-batch-api.js +0 -133
- package/lib/helpers/azure-cache-manager.js +0 -287
- package/lib/helpers/azure-parallel-processor.js +0 -158
- package/lib/helpers/azure-work-item-create.js +0 -278
- package/lib/helpers/gh-issue-create.js +0 -250
- package/lib/helpers/interactive-prompt.js +0 -336
- package/lib/helpers/output-manager.js +0 -335
- package/lib/helpers/progress-indicator.js +0 -258
- package/lib/performance/benchmarker.js +0 -429
- package/lib/pm/epic-decomposer.js +0 -273
- package/lib/pm/epic-syncer.js +0 -221
- package/lib/prdMetadata.js +0 -270
- package/lib/providers/azure/index.js +0 -234
- package/lib/providers/factory.js +0 -87
- package/lib/providers/github/index.js +0 -204
- package/lib/providers/interface.js +0 -73
- package/lib/python/scaffold-manager.js +0 -576
- package/lib/react/scaffold-manager.js +0 -745
- package/lib/regression/analyzer.js +0 -578
- package/lib/release/manager.js +0 -324
- package/lib/tailwind/manager.js +0 -486
- package/lib/traefik/manager.js +0 -484
- package/lib/utils/colors.js +0 -126
- package/lib/utils/config.js +0 -317
- package/lib/utils/filesystem.js +0 -316
- package/lib/utils/logger.js +0 -135
- package/lib/utils/prompts.js +0 -294
- package/lib/utils/shell.js +0 -237
- package/lib/validators/email-validator.js +0 -337
- package/lib/workflow/manager.js +0 -449
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { logError } = require('./lib/logger');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* PM Context Script
|
|
7
|
+
* Displays current project context, configuration, and progress
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Constants
|
|
11
|
+
const TASK_FILE_PATTERN = /^\d{3}\.md$/;
|
|
12
|
+
|
|
13
|
+
async function context() {
|
|
14
|
+
console.log('');
|
|
15
|
+
console.log('🎯 Project Context');
|
|
16
|
+
console.log('='.repeat(60));
|
|
17
|
+
console.log('');
|
|
18
|
+
|
|
19
|
+
// Project Information
|
|
20
|
+
console.log('📦 Project Information:');
|
|
21
|
+
|
|
22
|
+
// Get project name from package.json or directory name
|
|
23
|
+
let projectName = path.basename(process.cwd());
|
|
24
|
+
try {
|
|
25
|
+
if (fs.existsSync('package.json')) {
|
|
26
|
+
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
27
|
+
if (pkg.name) {
|
|
28
|
+
projectName = pkg.name;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} catch (err) {
|
|
32
|
+
// Use directory name as fallback
|
|
33
|
+
}
|
|
34
|
+
console.log(` Name: ${projectName}`);
|
|
35
|
+
console.log(` Directory: ${process.cwd()}`);
|
|
36
|
+
console.log('');
|
|
37
|
+
|
|
38
|
+
// Configuration
|
|
39
|
+
console.log('⚙️ Configuration:');
|
|
40
|
+
let provider = 'Not configured';
|
|
41
|
+
let githubOwner = '-';
|
|
42
|
+
let githubRepo = '-';
|
|
43
|
+
let azureOrg = '-';
|
|
44
|
+
let azureProject = '-';
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
if (fs.existsSync('.claude/config.json')) {
|
|
48
|
+
const config = JSON.parse(fs.readFileSync('.claude/config.json', 'utf8'));
|
|
49
|
+
if (config.provider) {
|
|
50
|
+
provider = config.provider.charAt(0).toUpperCase() + config.provider.slice(1);
|
|
51
|
+
}
|
|
52
|
+
if (config.github) {
|
|
53
|
+
githubOwner = config.github.owner || '-';
|
|
54
|
+
githubRepo = config.github.repo || '-';
|
|
55
|
+
}
|
|
56
|
+
if (config.azure) {
|
|
57
|
+
azureOrg = config.azure.organization || '-';
|
|
58
|
+
azureProject = config.azure.project || '-';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
// Config not found
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log(` Provider: ${provider}`);
|
|
66
|
+
if (provider.toLowerCase() === 'github') {
|
|
67
|
+
console.log(` GitHub Owner: ${githubOwner}`);
|
|
68
|
+
console.log(` GitHub Repo: ${githubRepo}`);
|
|
69
|
+
} else if (provider.toLowerCase() === 'azure') {
|
|
70
|
+
console.log(` Azure Org: ${azureOrg}`);
|
|
71
|
+
console.log(` Azure Project: ${azureProject}`);
|
|
72
|
+
}
|
|
73
|
+
console.log('');
|
|
74
|
+
|
|
75
|
+
// Active Team
|
|
76
|
+
console.log('👥 Active Team:');
|
|
77
|
+
let activeTeam = 'Default';
|
|
78
|
+
try {
|
|
79
|
+
if (fs.existsSync('.claude/active_team.txt')) {
|
|
80
|
+
activeTeam = fs.readFileSync('.claude/active_team.txt', 'utf8').trim();
|
|
81
|
+
}
|
|
82
|
+
} catch (err) {
|
|
83
|
+
// No active team
|
|
84
|
+
}
|
|
85
|
+
console.log(` Team: ${activeTeam}`);
|
|
86
|
+
console.log('');
|
|
87
|
+
|
|
88
|
+
// PRDs
|
|
89
|
+
console.log('📄 Product Requirements (PRDs):');
|
|
90
|
+
let prdCount = 0;
|
|
91
|
+
let prdNames = [];
|
|
92
|
+
try {
|
|
93
|
+
if (fs.existsSync('.claude/prds')) {
|
|
94
|
+
const files = fs.readdirSync('.claude/prds')
|
|
95
|
+
.filter(f => f.endsWith('.md') && !f.startsWith('.'));
|
|
96
|
+
prdCount = files.length;
|
|
97
|
+
prdNames = files.map(f => f.replace('.md', ''));
|
|
98
|
+
}
|
|
99
|
+
} catch (err) {
|
|
100
|
+
// No PRDs
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log(` Total: ${prdCount}`);
|
|
104
|
+
if (prdCount > 0) {
|
|
105
|
+
prdNames.slice(0, 5).forEach(name => {
|
|
106
|
+
console.log(` • ${name}`);
|
|
107
|
+
});
|
|
108
|
+
if (prdCount > 5) {
|
|
109
|
+
console.log(` ... and ${prdCount - 5} more`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
console.log('');
|
|
113
|
+
|
|
114
|
+
// Epics with Progress
|
|
115
|
+
console.log('📚 Epics & Progress:');
|
|
116
|
+
let epicCount = 0;
|
|
117
|
+
let totalTasks = 0;
|
|
118
|
+
let completedTasks = 0;
|
|
119
|
+
let inProgressTasks = 0;
|
|
120
|
+
let pendingTasks = 0;
|
|
121
|
+
let epicDetails = [];
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
if (fs.existsSync('.claude/epics')) {
|
|
125
|
+
const epicDirs = fs.readdirSync('.claude/epics', { withFileTypes: true })
|
|
126
|
+
.filter(d => d.isDirectory())
|
|
127
|
+
.map(d => d.name);
|
|
128
|
+
|
|
129
|
+
epicCount = epicDirs.length;
|
|
130
|
+
|
|
131
|
+
// Helper to count tasks in a directory and update counters
|
|
132
|
+
function countTasksInDir(dir, counters) {
|
|
133
|
+
try {
|
|
134
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
135
|
+
for (const entry of entries) {
|
|
136
|
+
const fullPath = path.join(dir, entry.name);
|
|
137
|
+
if (entry.isDirectory()) {
|
|
138
|
+
countTasksInDir(fullPath, counters);
|
|
139
|
+
} else if (entry.isFile() && TASK_FILE_PATTERN.test(entry.name)) {
|
|
140
|
+
counters.epicTasks++;
|
|
141
|
+
totalTasks++;
|
|
142
|
+
try {
|
|
143
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
144
|
+
const statusMatch = content.match(/^status:\s*(.+)$/m);
|
|
145
|
+
if (statusMatch) {
|
|
146
|
+
const status = statusMatch[1].trim().toLowerCase();
|
|
147
|
+
if (status === 'completed' || status === 'done' || status === 'closed') {
|
|
148
|
+
counters.epicCompleted++;
|
|
149
|
+
completedTasks++;
|
|
150
|
+
} else if (status === 'in-progress' || status === 'in_progress') {
|
|
151
|
+
counters.epicInProgress++;
|
|
152
|
+
inProgressTasks++;
|
|
153
|
+
} else {
|
|
154
|
+
counters.epicPending++;
|
|
155
|
+
pendingTasks++;
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
counters.epicPending++;
|
|
159
|
+
pendingTasks++;
|
|
160
|
+
}
|
|
161
|
+
} catch (err) {
|
|
162
|
+
counters.epicPending++;
|
|
163
|
+
pendingTasks++;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} catch (err) {
|
|
168
|
+
// Directory read error
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
for (const epicDir of epicDirs) {
|
|
173
|
+
const epicPath = path.join('.claude/epics', epicDir);
|
|
174
|
+
// Count tasks in this epic (including subdirectories)
|
|
175
|
+
let counters = {
|
|
176
|
+
epicTasks: 0,
|
|
177
|
+
epicCompleted: 0,
|
|
178
|
+
epicInProgress: 0,
|
|
179
|
+
epicPending: 0
|
|
180
|
+
};
|
|
181
|
+
countTasksInDir(epicPath, counters);
|
|
182
|
+
if (counters.epicTasks > 0) {
|
|
183
|
+
const progress = Math.round((counters.epicCompleted / counters.epicTasks) * 100);
|
|
184
|
+
epicDetails.push({
|
|
185
|
+
name: epicDir,
|
|
186
|
+
tasks: counters.epicTasks,
|
|
187
|
+
completed: counters.epicCompleted,
|
|
188
|
+
inProgress: counters.epicInProgress,
|
|
189
|
+
pending: counters.epicPending,
|
|
190
|
+
progress: progress
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} catch (err) {
|
|
196
|
+
// No epics
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.log(` Total Epics: ${epicCount}`);
|
|
200
|
+
console.log(` Total Tasks: ${totalTasks}`);
|
|
201
|
+
console.log(` Completed: ${completedTasks}`);
|
|
202
|
+
console.log(` In Progress: ${inProgressTasks}`);
|
|
203
|
+
console.log(` Pending: ${pendingTasks}`);
|
|
204
|
+
console.log('');
|
|
205
|
+
|
|
206
|
+
if (epicDetails.length > 0) {
|
|
207
|
+
console.log(' Epic Breakdown:');
|
|
208
|
+
epicDetails.slice(0, 5).forEach(epic => {
|
|
209
|
+
const progressBar = createProgressBar(epic.progress, 20);
|
|
210
|
+
console.log(` ${epic.name}`);
|
|
211
|
+
console.log(` ${progressBar} ${epic.progress}% (${epic.completed}/${epic.tasks} tasks)`);
|
|
212
|
+
});
|
|
213
|
+
if (epicDetails.length > 5) {
|
|
214
|
+
console.log(` ... and ${epicDetails.length - 5} more epics`);
|
|
215
|
+
}
|
|
216
|
+
console.log('');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Overall Progress
|
|
220
|
+
if (totalTasks > 0) {
|
|
221
|
+
const overallProgress = Math.round((completedTasks / totalTasks) * 100);
|
|
222
|
+
console.log('📊 Overall Progress:');
|
|
223
|
+
const overallBar = createProgressBar(overallProgress, 40);
|
|
224
|
+
console.log(` ${overallBar} ${overallProgress}%`);
|
|
225
|
+
console.log(` ${completedTasks} / ${totalTasks} tasks completed`);
|
|
226
|
+
console.log('');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Current/Recent Activity
|
|
230
|
+
console.log('🔄 Recent Activity:');
|
|
231
|
+
try {
|
|
232
|
+
// Find most recently modified task file
|
|
233
|
+
let recentTask = null;
|
|
234
|
+
let recentTime = 0;
|
|
235
|
+
|
|
236
|
+
if (fs.existsSync('.claude/epics')) {
|
|
237
|
+
function findRecentTask(dir) {
|
|
238
|
+
let bestTask = null;
|
|
239
|
+
let bestTime = 0;
|
|
240
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
241
|
+
|
|
242
|
+
for (const entry of entries) {
|
|
243
|
+
const fullPath = path.join(dir, entry.name);
|
|
244
|
+
|
|
245
|
+
if (entry.isDirectory()) {
|
|
246
|
+
const subTask = findRecentTask(fullPath);
|
|
247
|
+
if (subTask && subTask.timeMs > bestTime) {
|
|
248
|
+
bestTask = subTask;
|
|
249
|
+
bestTime = subTask.timeMs;
|
|
250
|
+
}
|
|
251
|
+
} else if (entry.isFile() && TASK_FILE_PATTERN.test(entry.name)) {
|
|
252
|
+
const stats = fs.statSync(fullPath);
|
|
253
|
+
if (stats.mtimeMs > bestTime) {
|
|
254
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
255
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
256
|
+
const statusMatch = content.match(/^status:\s*(.+)$/m);
|
|
257
|
+
bestTask = {
|
|
258
|
+
path: fullPath,
|
|
259
|
+
title: titleMatch ? titleMatch[1] : entry.name,
|
|
260
|
+
status: statusMatch ? statusMatch[1].trim() : 'pending',
|
|
261
|
+
time: stats.mtime,
|
|
262
|
+
timeMs: stats.mtimeMs
|
|
263
|
+
};
|
|
264
|
+
bestTime = stats.mtimeMs;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return bestTask;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const foundTask = findRecentTask('.claude/epics');
|
|
272
|
+
if (foundTask) {
|
|
273
|
+
recentTask = foundTask;
|
|
274
|
+
recentTime = foundTask.timeMs;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (recentTask) {
|
|
279
|
+
const timeAgo = getTimeAgo(recentTask.time);
|
|
280
|
+
console.log(` Last Modified: ${recentTask.title}`);
|
|
281
|
+
console.log(` Status: ${recentTask.status}`);
|
|
282
|
+
console.log(` Modified: ${timeAgo}`);
|
|
283
|
+
console.log(` File: ${recentTask.path}`);
|
|
284
|
+
} else {
|
|
285
|
+
console.log(' No recent activity');
|
|
286
|
+
}
|
|
287
|
+
} catch (err) {
|
|
288
|
+
console.log(' No recent activity');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
console.log('');
|
|
292
|
+
console.log('💡 Quick Commands:');
|
|
293
|
+
console.log(' /pm:next # Get next priority task');
|
|
294
|
+
console.log(' /pm:status # Detailed project status');
|
|
295
|
+
console.log(' /pm:standup # Generate standup report');
|
|
296
|
+
console.log(' /pm:search <keyword> # Search PRDs and epics');
|
|
297
|
+
console.log('');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Helper function to create progress bar
|
|
301
|
+
function createProgressBar(percentage, length) {
|
|
302
|
+
const filled = Math.round((percentage / 100) * length);
|
|
303
|
+
const empty = length - filled;
|
|
304
|
+
return '[' + '='.repeat(filled) + '-'.repeat(empty) + ']';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Helper function to get human-readable time ago
|
|
308
|
+
function getTimeAgo(date) {
|
|
309
|
+
const seconds = Math.floor((new Date() - date) / 1000);
|
|
310
|
+
|
|
311
|
+
const intervals = {
|
|
312
|
+
year: 31536000,
|
|
313
|
+
month: 2592000,
|
|
314
|
+
week: 604800,
|
|
315
|
+
day: 86400,
|
|
316
|
+
hour: 3600,
|
|
317
|
+
minute: 60
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
for (const [name, secondsInInterval] of Object.entries(intervals)) {
|
|
321
|
+
const interval = Math.floor(seconds / secondsInInterval);
|
|
322
|
+
if (interval >= 1) {
|
|
323
|
+
return `${interval} ${name}${interval > 1 ? 's' : ''} ago`;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return 'just now';
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Run if called directly
|
|
331
|
+
if (require.main === module) {
|
|
332
|
+
context().catch(err => {
|
|
333
|
+
logError('Error executing context command', err);
|
|
334
|
+
process.exit(1);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
module.exports = context;
|
|
@@ -6,9 +6,9 @@ set -euo pipefail
|
|
|
6
6
|
|
|
7
7
|
# Load libraries
|
|
8
8
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
-
source "${SCRIPT_DIR}
|
|
10
|
-
source "${SCRIPT_DIR}
|
|
11
|
-
source "${SCRIPT_DIR}
|
|
9
|
+
source "${SCRIPT_DIR}/../lib/logging-utils.sh"
|
|
10
|
+
source "${SCRIPT_DIR}/../lib/frontmatter-utils.sh"
|
|
11
|
+
source "${SCRIPT_DIR}/../lib/datetime-utils.sh"
|
|
12
12
|
|
|
13
13
|
# Script configuration
|
|
14
14
|
readonly ISSUE_NUMBER="${1:-}"
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# PM Scripts Library
|
|
2
|
+
|
|
3
|
+
This directory contains shared utilities for PM command scripts.
|
|
4
|
+
|
|
5
|
+
## Logger
|
|
6
|
+
|
|
7
|
+
The `logger.js` module provides consistent, colored logging across all PM commands.
|
|
8
|
+
|
|
9
|
+
### Usage
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
const { logInfo, logSuccess, logWarning, logError, logDebug } = require('./lib/logger');
|
|
13
|
+
|
|
14
|
+
// Info message (blue ℹ icon)
|
|
15
|
+
logInfo('Processing task...');
|
|
16
|
+
|
|
17
|
+
// Success message (green ✓ icon)
|
|
18
|
+
logSuccess('Task completed successfully');
|
|
19
|
+
|
|
20
|
+
// Warning message (yellow ⚠ icon)
|
|
21
|
+
logWarning('Task may require attention');
|
|
22
|
+
|
|
23
|
+
// Error message (red ❌ icon)
|
|
24
|
+
logError('Failed to process task', error);
|
|
25
|
+
// The error parameter is optional and will display error.message and stack (if DEBUG=1)
|
|
26
|
+
|
|
27
|
+
// Debug message (only shown if DEBUG=1)
|
|
28
|
+
logDebug('Detailed debug information');
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Environment Variables
|
|
32
|
+
|
|
33
|
+
- `DEBUG=1` - Enable debug logging and full error stack traces
|
|
34
|
+
|
|
35
|
+
### Color Constants
|
|
36
|
+
|
|
37
|
+
If you need custom formatting, you can import the colors:
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
const { colors } = require('./lib/logger');
|
|
41
|
+
|
|
42
|
+
console.log(`${colors.blue}Custom message${colors.reset}`);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Available colors:
|
|
46
|
+
- `colors.red`
|
|
47
|
+
- `colors.green`
|
|
48
|
+
- `colors.yellow`
|
|
49
|
+
- `colors.blue`
|
|
50
|
+
- `colors.cyan`
|
|
51
|
+
- `colors.gray`
|
|
52
|
+
- `colors.reset`
|
|
53
|
+
|
|
54
|
+
## Migration Guide
|
|
55
|
+
|
|
56
|
+
To migrate existing PM scripts to use the logger:
|
|
57
|
+
|
|
58
|
+
1. Import the logger at the top of your file:
|
|
59
|
+
```javascript
|
|
60
|
+
const { logError, logWarning, logInfo, logSuccess } = require('./lib/logger');
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
2. Replace `console.error()` calls:
|
|
64
|
+
```javascript
|
|
65
|
+
// Before
|
|
66
|
+
console.error('Error:', err.message);
|
|
67
|
+
|
|
68
|
+
// After
|
|
69
|
+
logError('Error description', err);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
3. Add error handling to promise chains:
|
|
73
|
+
```javascript
|
|
74
|
+
// Before
|
|
75
|
+
const results = await Promise.all(promises);
|
|
76
|
+
|
|
77
|
+
// After
|
|
78
|
+
const promises = items.map(item =>
|
|
79
|
+
processItem(item).catch(err => {
|
|
80
|
+
logWarning(`Failed to process ${item.name}`, err);
|
|
81
|
+
return defaultValue;
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
const results = await Promise.all(promises);
|
|
85
|
+
```
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logging utility for PM scripts
|
|
3
|
+
* Provides consistent colored output across all PM commands
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ANSI color codes
|
|
7
|
+
const colors = {
|
|
8
|
+
reset: '\x1b[0m',
|
|
9
|
+
red: '\x1b[31m',
|
|
10
|
+
green: '\x1b[32m',
|
|
11
|
+
yellow: '\x1b[33m',
|
|
12
|
+
blue: '\x1b[34m',
|
|
13
|
+
gray: '\x1b[90m',
|
|
14
|
+
cyan: '\x1b[36m'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Log an info message
|
|
19
|
+
* @param {string} message - The message to log
|
|
20
|
+
*/
|
|
21
|
+
function logInfo(message) {
|
|
22
|
+
console.log(`${colors.blue}ℹ${colors.reset} ${message}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Log a success message
|
|
27
|
+
* @param {string} message - The message to log
|
|
28
|
+
*/
|
|
29
|
+
function logSuccess(message) {
|
|
30
|
+
console.log(`${colors.green}✓${colors.reset} ${message}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Log a warning message
|
|
35
|
+
* @param {string} message - The message to log
|
|
36
|
+
*/
|
|
37
|
+
function logWarning(message) {
|
|
38
|
+
console.warn(`${colors.yellow}⚠${colors.reset} ${message}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Log an error message
|
|
43
|
+
* @param {string} message - The message to log
|
|
44
|
+
* @param {Error} [error] - Optional error object for details
|
|
45
|
+
*/
|
|
46
|
+
function logError(message, error) {
|
|
47
|
+
console.error(`${colors.red}❌${colors.reset} ${message}`);
|
|
48
|
+
if (error && error.message) {
|
|
49
|
+
console.error(`${colors.gray} ${error.message}${colors.reset}`);
|
|
50
|
+
}
|
|
51
|
+
if (error && error.stack && process.env.DEBUG) {
|
|
52
|
+
console.error(`${colors.gray}${error.stack}${colors.reset}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Log a debug message (only shown if DEBUG env var is set)
|
|
58
|
+
* @param {string} message - The message to log
|
|
59
|
+
*/
|
|
60
|
+
function logDebug(message) {
|
|
61
|
+
if (process.env.DEBUG) {
|
|
62
|
+
console.log(`${colors.gray}[DEBUG]${colors.reset} ${message}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Export colors for custom formatting
|
|
68
|
+
*/
|
|
69
|
+
const logColors = colors;
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
logInfo,
|
|
73
|
+
logSuccess,
|
|
74
|
+
logWarning,
|
|
75
|
+
logError,
|
|
76
|
+
logDebug,
|
|
77
|
+
colors: logColors
|
|
78
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { logError } = require('./lib/logger');
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* PM Next Script (Node.js version)
|
|
@@ -73,11 +74,34 @@ async function next() {
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
addMessage('');
|
|
77
|
+
|
|
78
|
+
// Display TDD reminder if tasks are available
|
|
79
|
+
if (result.found > 0) {
|
|
80
|
+
displayTddReminder(addMessage);
|
|
81
|
+
}
|
|
82
|
+
|
|
76
83
|
addMessage(`📊 Summary: ${result.found} tasks ready to start`);
|
|
77
84
|
|
|
78
85
|
return result;
|
|
79
86
|
}
|
|
80
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Display TDD reminder to ensure test-driven development practices
|
|
90
|
+
* Extracted to maintain single responsibility and improve testability
|
|
91
|
+
* @param {Function} addMessage - Function to add messages to the output
|
|
92
|
+
*/
|
|
93
|
+
function displayTddReminder(addMessage) {
|
|
94
|
+
addMessage('⚠️ TDD REMINDER - Before starting work:');
|
|
95
|
+
addMessage('');
|
|
96
|
+
addMessage(' 🚨 ALWAYS follow Test-Driven Development:');
|
|
97
|
+
addMessage(' 1. RED: Write failing test first');
|
|
98
|
+
addMessage(' 2. GREEN: Write minimal code to pass');
|
|
99
|
+
addMessage(' 3. REFACTOR: Clean up while keeping tests green');
|
|
100
|
+
addMessage('');
|
|
101
|
+
addMessage(' See .claude/rules/tdd.enforcement.md for details');
|
|
102
|
+
addMessage('');
|
|
103
|
+
}
|
|
104
|
+
|
|
81
105
|
// Helper function to find available tasks
|
|
82
106
|
async function findAvailableTasks() {
|
|
83
107
|
const availableTasks = [];
|
|
@@ -161,7 +185,7 @@ if (require.main === module) {
|
|
|
161
185
|
module.exports.next().then(() => {
|
|
162
186
|
process.exit(0);
|
|
163
187
|
}).catch(err => {
|
|
164
|
-
|
|
188
|
+
logError('Next tasks command failed', err);
|
|
165
189
|
process.exit(1);
|
|
166
190
|
});
|
|
167
191
|
}
|