claude-autopm 2.8.1 → 2.8.2
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 +116 -8
- package/bin/autopm.js +2 -0
- package/bin/commands/plugin.js +395 -0
- package/bin/commands/team.js +184 -10
- package/install/install.js +223 -4
- package/lib/plugins/PluginManager.js +1328 -0
- package/lib/plugins/PluginManager.old.js +400 -0
- package/package.json +4 -1
- package/scripts/publish-plugins.sh +166 -0
- package/autopm/.claude/agents/cloud/README.md +0 -55
- package/autopm/.claude/agents/cloud/aws-cloud-architect.md +0 -521
- package/autopm/.claude/agents/cloud/azure-cloud-architect.md +0 -436
- package/autopm/.claude/agents/cloud/gcp-cloud-architect.md +0 -385
- package/autopm/.claude/agents/cloud/gcp-cloud-functions-engineer.md +0 -306
- package/autopm/.claude/agents/cloud/gemini-api-expert.md +0 -880
- package/autopm/.claude/agents/cloud/kubernetes-orchestrator.md +0 -566
- package/autopm/.claude/agents/cloud/openai-python-expert.md +0 -1087
- package/autopm/.claude/agents/cloud/terraform-infrastructure-expert.md +0 -454
- package/autopm/.claude/agents/core/agent-manager.md +0 -296
- package/autopm/.claude/agents/core/code-analyzer.md +0 -131
- package/autopm/.claude/agents/core/file-analyzer.md +0 -162
- package/autopm/.claude/agents/core/test-runner.md +0 -200
- package/autopm/.claude/agents/data/airflow-orchestration-expert.md +0 -52
- package/autopm/.claude/agents/data/kedro-pipeline-expert.md +0 -50
- package/autopm/.claude/agents/data/langgraph-workflow-expert.md +0 -520
- package/autopm/.claude/agents/databases/README.md +0 -50
- package/autopm/.claude/agents/databases/bigquery-expert.md +0 -392
- package/autopm/.claude/agents/databases/cosmosdb-expert.md +0 -368
- package/autopm/.claude/agents/databases/mongodb-expert.md +0 -398
- package/autopm/.claude/agents/databases/postgresql-expert.md +0 -321
- package/autopm/.claude/agents/databases/redis-expert.md +0 -52
- package/autopm/.claude/agents/devops/README.md +0 -52
- package/autopm/.claude/agents/devops/azure-devops-specialist.md +0 -308
- package/autopm/.claude/agents/devops/docker-containerization-expert.md +0 -298
- package/autopm/.claude/agents/devops/github-operations-specialist.md +0 -335
- package/autopm/.claude/agents/devops/mcp-context-manager.md +0 -319
- package/autopm/.claude/agents/devops/observability-engineer.md +0 -574
- package/autopm/.claude/agents/devops/ssh-operations-expert.md +0 -1093
- package/autopm/.claude/agents/devops/traefik-proxy-expert.md +0 -444
- package/autopm/.claude/agents/frameworks/README.md +0 -64
- package/autopm/.claude/agents/frameworks/e2e-test-engineer.md +0 -360
- package/autopm/.claude/agents/frameworks/nats-messaging-expert.md +0 -254
- package/autopm/.claude/agents/frameworks/react-frontend-engineer.md +0 -217
- package/autopm/.claude/agents/frameworks/react-ui-expert.md +0 -226
- package/autopm/.claude/agents/frameworks/tailwindcss-expert.md +0 -770
- package/autopm/.claude/agents/frameworks/ux-design-expert.md +0 -244
- package/autopm/.claude/agents/integration/message-queue-engineer.md +0 -794
- package/autopm/.claude/agents/languages/README.md +0 -50
- package/autopm/.claude/agents/languages/bash-scripting-expert.md +0 -541
- package/autopm/.claude/agents/languages/javascript-frontend-engineer.md +0 -197
- package/autopm/.claude/agents/languages/nodejs-backend-engineer.md +0 -226
- package/autopm/.claude/agents/languages/python-backend-engineer.md +0 -214
- package/autopm/.claude/agents/languages/python-backend-expert.md +0 -289
- package/autopm/.claude/agents/testing/frontend-testing-engineer.md +0 -395
- package/autopm/.claude/commands/ai/langgraph-workflow.md +0 -65
- package/autopm/.claude/commands/ai/openai-chat.md +0 -65
- package/autopm/.claude/commands/azure/COMMANDS.md +0 -107
- package/autopm/.claude/commands/azure/COMMAND_MAPPING.md +0 -252
- package/autopm/.claude/commands/azure/INTEGRATION_FIX.md +0 -103
- package/autopm/.claude/commands/azure/README.md +0 -246
- package/autopm/.claude/commands/azure/active-work.md +0 -198
- package/autopm/.claude/commands/azure/aliases.md +0 -143
- package/autopm/.claude/commands/azure/blocked-items.md +0 -287
- package/autopm/.claude/commands/azure/clean.md +0 -93
- package/autopm/.claude/commands/azure/docs-query.md +0 -48
- package/autopm/.claude/commands/azure/feature-decompose.md +0 -380
- package/autopm/.claude/commands/azure/feature-list.md +0 -61
- package/autopm/.claude/commands/azure/feature-new.md +0 -115
- package/autopm/.claude/commands/azure/feature-show.md +0 -205
- package/autopm/.claude/commands/azure/feature-start.md +0 -130
- package/autopm/.claude/commands/azure/fix-integration-example.md +0 -93
- package/autopm/.claude/commands/azure/help.md +0 -150
- package/autopm/.claude/commands/azure/import-us.md +0 -269
- package/autopm/.claude/commands/azure/init.md +0 -211
- package/autopm/.claude/commands/azure/next-task.md +0 -262
- package/autopm/.claude/commands/azure/search.md +0 -160
- package/autopm/.claude/commands/azure/sprint-status.md +0 -235
- package/autopm/.claude/commands/azure/standup.md +0 -260
- package/autopm/.claude/commands/azure/sync-all.md +0 -99
- package/autopm/.claude/commands/azure/task-analyze.md +0 -186
- package/autopm/.claude/commands/azure/task-close.md +0 -329
- package/autopm/.claude/commands/azure/task-edit.md +0 -145
- package/autopm/.claude/commands/azure/task-list.md +0 -263
- package/autopm/.claude/commands/azure/task-new.md +0 -84
- package/autopm/.claude/commands/azure/task-reopen.md +0 -79
- package/autopm/.claude/commands/azure/task-show.md +0 -126
- package/autopm/.claude/commands/azure/task-start.md +0 -301
- package/autopm/.claude/commands/azure/task-status.md +0 -65
- package/autopm/.claude/commands/azure/task-sync.md +0 -67
- package/autopm/.claude/commands/azure/us-edit.md +0 -164
- package/autopm/.claude/commands/azure/us-list.md +0 -202
- package/autopm/.claude/commands/azure/us-new.md +0 -265
- package/autopm/.claude/commands/azure/us-parse.md +0 -253
- package/autopm/.claude/commands/azure/us-show.md +0 -188
- package/autopm/.claude/commands/azure/us-status.md +0 -320
- package/autopm/.claude/commands/azure/validate.md +0 -86
- package/autopm/.claude/commands/azure/work-item-sync.md +0 -47
- package/autopm/.claude/commands/cloud/infra-deploy.md +0 -38
- package/autopm/.claude/commands/github/workflow-create.md +0 -42
- package/autopm/.claude/commands/infrastructure/ssh-security.md +0 -65
- package/autopm/.claude/commands/infrastructure/traefik-setup.md +0 -65
- package/autopm/.claude/commands/kubernetes/deploy.md +0 -37
- package/autopm/.claude/commands/playwright/test-scaffold.md +0 -38
- package/autopm/.claude/commands/pm/blocked.md +0 -28
- package/autopm/.claude/commands/pm/clean.md +0 -119
- package/autopm/.claude/commands/pm/context-create.md +0 -136
- package/autopm/.claude/commands/pm/context-prime.md +0 -170
- package/autopm/.claude/commands/pm/context-update.md +0 -292
- package/autopm/.claude/commands/pm/context.md +0 -28
- package/autopm/.claude/commands/pm/epic-close.md +0 -86
- package/autopm/.claude/commands/pm/epic-decompose.md +0 -370
- package/autopm/.claude/commands/pm/epic-edit.md +0 -83
- package/autopm/.claude/commands/pm/epic-list.md +0 -30
- package/autopm/.claude/commands/pm/epic-merge.md +0 -222
- package/autopm/.claude/commands/pm/epic-oneshot.md +0 -119
- package/autopm/.claude/commands/pm/epic-refresh.md +0 -119
- package/autopm/.claude/commands/pm/epic-show.md +0 -28
- package/autopm/.claude/commands/pm/epic-split.md +0 -120
- package/autopm/.claude/commands/pm/epic-start.md +0 -195
- package/autopm/.claude/commands/pm/epic-status.md +0 -28
- package/autopm/.claude/commands/pm/epic-sync-modular.md +0 -338
- package/autopm/.claude/commands/pm/epic-sync-original.md +0 -473
- package/autopm/.claude/commands/pm/epic-sync.md +0 -486
- package/autopm/.claude/commands/pm/help.md +0 -28
- package/autopm/.claude/commands/pm/import.md +0 -115
- package/autopm/.claude/commands/pm/in-progress.md +0 -28
- package/autopm/.claude/commands/pm/init.md +0 -28
- package/autopm/.claude/commands/pm/issue-analyze.md +0 -202
- package/autopm/.claude/commands/pm/issue-close.md +0 -119
- package/autopm/.claude/commands/pm/issue-edit.md +0 -93
- package/autopm/.claude/commands/pm/issue-reopen.md +0 -87
- package/autopm/.claude/commands/pm/issue-show.md +0 -41
- package/autopm/.claude/commands/pm/issue-start.md +0 -234
- package/autopm/.claude/commands/pm/issue-status.md +0 -95
- package/autopm/.claude/commands/pm/issue-sync.md +0 -411
- package/autopm/.claude/commands/pm/next.md +0 -28
- package/autopm/.claude/commands/pm/prd-edit.md +0 -82
- package/autopm/.claude/commands/pm/prd-list.md +0 -28
- package/autopm/.claude/commands/pm/prd-new.md +0 -55
- package/autopm/.claude/commands/pm/prd-parse.md +0 -42
- package/autopm/.claude/commands/pm/prd-status.md +0 -28
- package/autopm/.claude/commands/pm/search.md +0 -28
- package/autopm/.claude/commands/pm/standup.md +0 -28
- package/autopm/.claude/commands/pm/status.md +0 -28
- package/autopm/.claude/commands/pm/sync.md +0 -99
- package/autopm/.claude/commands/pm/test-reference-update.md +0 -151
- package/autopm/.claude/commands/pm/validate.md +0 -28
- package/autopm/.claude/commands/pm/what-next.md +0 -28
- package/autopm/.claude/commands/python/api-scaffold.md +0 -50
- package/autopm/.claude/commands/python/docs-query.md +0 -48
- package/autopm/.claude/commands/react/app-scaffold.md +0 -50
- package/autopm/.claude/commands/testing/prime.md +0 -314
- package/autopm/.claude/commands/testing/run.md +0 -125
- package/autopm/.claude/commands/ui/bootstrap-scaffold.md +0 -65
- package/autopm/.claude/commands/ui/tailwind-system.md +0 -64
- package/autopm/.claude/rules/ai-integration-patterns.md +0 -219
- package/autopm/.claude/rules/ci-cd-kubernetes-strategy.md +0 -25
- package/autopm/.claude/rules/database-management-strategy.md +0 -17
- package/autopm/.claude/rules/database-pipeline.md +0 -94
- package/autopm/.claude/rules/devops-troubleshooting-playbook.md +0 -450
- package/autopm/.claude/rules/docker-first-development.md +0 -404
- package/autopm/.claude/rules/infrastructure-pipeline.md +0 -128
- package/autopm/.claude/rules/performance-guidelines.md +0 -403
- package/autopm/.claude/rules/ui-development-standards.md +0 -281
- package/autopm/.claude/rules/ui-framework-rules.md +0 -151
- package/autopm/.claude/rules/ux-design-rules.md +0 -209
- package/autopm/.claude/rules/visual-testing.md +0 -223
- package/autopm/.claude/scripts/azure/README.md +0 -192
- package/autopm/.claude/scripts/azure/active-work.js +0 -524
- package/autopm/.claude/scripts/azure/active-work.sh +0 -20
- package/autopm/.claude/scripts/azure/blocked.js +0 -520
- package/autopm/.claude/scripts/azure/blocked.sh +0 -20
- package/autopm/.claude/scripts/azure/daily.js +0 -533
- package/autopm/.claude/scripts/azure/daily.sh +0 -20
- package/autopm/.claude/scripts/azure/dashboard.js +0 -970
- package/autopm/.claude/scripts/azure/dashboard.sh +0 -20
- package/autopm/.claude/scripts/azure/feature-list.js +0 -254
- package/autopm/.claude/scripts/azure/feature-list.sh +0 -20
- package/autopm/.claude/scripts/azure/feature-show.js +0 -7
- package/autopm/.claude/scripts/azure/feature-show.sh +0 -20
- package/autopm/.claude/scripts/azure/feature-status.js +0 -604
- package/autopm/.claude/scripts/azure/feature-status.sh +0 -20
- package/autopm/.claude/scripts/azure/help.js +0 -342
- package/autopm/.claude/scripts/azure/help.sh +0 -20
- package/autopm/.claude/scripts/azure/next-task.js +0 -508
- package/autopm/.claude/scripts/azure/next-task.sh +0 -20
- package/autopm/.claude/scripts/azure/search.js +0 -469
- package/autopm/.claude/scripts/azure/search.sh +0 -20
- package/autopm/.claude/scripts/azure/setup.js +0 -745
- package/autopm/.claude/scripts/azure/setup.sh +0 -20
- package/autopm/.claude/scripts/azure/sprint-report.js +0 -1012
- package/autopm/.claude/scripts/azure/sprint-report.sh +0 -20
- package/autopm/.claude/scripts/azure/sync.js +0 -563
- package/autopm/.claude/scripts/azure/sync.sh +0 -20
- package/autopm/.claude/scripts/azure/us-list.js +0 -210
- package/autopm/.claude/scripts/azure/us-list.sh +0 -20
- package/autopm/.claude/scripts/azure/us-status.js +0 -238
- package/autopm/.claude/scripts/azure/us-status.sh +0 -20
- package/autopm/.claude/scripts/azure/validate.js +0 -626
- package/autopm/.claude/scripts/azure/validate.sh +0 -20
- package/autopm/.claude/scripts/azure/wrapper-template.sh +0 -20
- package/autopm/.claude/scripts/github/dependency-tracker.js +0 -554
- package/autopm/.claude/scripts/github/dependency-validator.js +0 -545
- package/autopm/.claude/scripts/github/dependency-visualizer.js +0 -477
- package/autopm/.claude/scripts/pm/analytics.js +0 -425
- package/autopm/.claude/scripts/pm/blocked.js +0 -164
- package/autopm/.claude/scripts/pm/blocked.sh +0 -78
- package/autopm/.claude/scripts/pm/clean.js +0 -464
- package/autopm/.claude/scripts/pm/context-create.js +0 -216
- package/autopm/.claude/scripts/pm/context-prime.js +0 -335
- package/autopm/.claude/scripts/pm/context-update.js +0 -344
- package/autopm/.claude/scripts/pm/context.js +0 -338
- package/autopm/.claude/scripts/pm/epic-close.js +0 -347
- package/autopm/.claude/scripts/pm/epic-edit.js +0 -382
- package/autopm/.claude/scripts/pm/epic-list.js +0 -273
- package/autopm/.claude/scripts/pm/epic-list.sh +0 -109
- package/autopm/.claude/scripts/pm/epic-show.js +0 -291
- package/autopm/.claude/scripts/pm/epic-show.sh +0 -105
- package/autopm/.claude/scripts/pm/epic-split.js +0 -522
- package/autopm/.claude/scripts/pm/epic-start/epic-start.js +0 -183
- package/autopm/.claude/scripts/pm/epic-start/epic-start.sh +0 -94
- package/autopm/.claude/scripts/pm/epic-status.js +0 -291
- package/autopm/.claude/scripts/pm/epic-status.sh +0 -104
- package/autopm/.claude/scripts/pm/epic-sync/README.md +0 -208
- package/autopm/.claude/scripts/pm/epic-sync/create-epic-issue.sh +0 -77
- package/autopm/.claude/scripts/pm/epic-sync/create-task-issues.sh +0 -86
- package/autopm/.claude/scripts/pm/epic-sync/update-epic-file.sh +0 -79
- package/autopm/.claude/scripts/pm/epic-sync/update-references.sh +0 -89
- package/autopm/.claude/scripts/pm/epic-sync.sh +0 -137
- package/autopm/.claude/scripts/pm/help.js +0 -92
- package/autopm/.claude/scripts/pm/help.sh +0 -90
- package/autopm/.claude/scripts/pm/in-progress.js +0 -178
- package/autopm/.claude/scripts/pm/in-progress.sh +0 -93
- package/autopm/.claude/scripts/pm/init.js +0 -321
- package/autopm/.claude/scripts/pm/init.sh +0 -178
- package/autopm/.claude/scripts/pm/issue-close.js +0 -232
- package/autopm/.claude/scripts/pm/issue-edit.js +0 -310
- package/autopm/.claude/scripts/pm/issue-show.js +0 -272
- package/autopm/.claude/scripts/pm/issue-start.js +0 -181
- package/autopm/.claude/scripts/pm/issue-sync/format-comment.sh +0 -468
- package/autopm/.claude/scripts/pm/issue-sync/gather-updates.sh +0 -460
- package/autopm/.claude/scripts/pm/issue-sync/post-comment.sh +0 -330
- package/autopm/.claude/scripts/pm/issue-sync/preflight-validation.sh +0 -348
- package/autopm/.claude/scripts/pm/issue-sync/update-frontmatter.sh +0 -387
- package/autopm/.claude/scripts/pm/lib/README.md +0 -85
- package/autopm/.claude/scripts/pm/lib/epic-discovery.js +0 -119
- package/autopm/.claude/scripts/pm/lib/logger.js +0 -78
- package/autopm/.claude/scripts/pm/next.js +0 -189
- package/autopm/.claude/scripts/pm/next.sh +0 -72
- package/autopm/.claude/scripts/pm/optimize.js +0 -407
- package/autopm/.claude/scripts/pm/pr-create.js +0 -337
- package/autopm/.claude/scripts/pm/pr-list.js +0 -257
- package/autopm/.claude/scripts/pm/prd-list.js +0 -242
- package/autopm/.claude/scripts/pm/prd-list.sh +0 -103
- package/autopm/.claude/scripts/pm/prd-new.js +0 -684
- package/autopm/.claude/scripts/pm/prd-parse.js +0 -547
- package/autopm/.claude/scripts/pm/prd-status.js +0 -152
- package/autopm/.claude/scripts/pm/prd-status.sh +0 -63
- package/autopm/.claude/scripts/pm/release.js +0 -460
- package/autopm/.claude/scripts/pm/search.js +0 -192
- package/autopm/.claude/scripts/pm/search.sh +0 -89
- package/autopm/.claude/scripts/pm/standup.js +0 -362
- package/autopm/.claude/scripts/pm/standup.sh +0 -95
- package/autopm/.claude/scripts/pm/status.js +0 -148
- package/autopm/.claude/scripts/pm/status.sh +0 -59
- package/autopm/.claude/scripts/pm/sync-batch.js +0 -337
- package/autopm/.claude/scripts/pm/sync.js +0 -343
- package/autopm/.claude/scripts/pm/template-list.js +0 -141
- package/autopm/.claude/scripts/pm/template-new.js +0 -366
- package/autopm/.claude/scripts/pm/validate.js +0 -274
- package/autopm/.claude/scripts/pm/validate.sh +0 -106
- package/autopm/.claude/scripts/pm/what-next.js +0 -660
- package/bin/node/azure-feature-show.js +0 -7
|
@@ -1,1012 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Azure DevOps Sprint Report
|
|
5
|
-
* Generates a comprehensive report for the current or specified sprint
|
|
6
|
-
* Full implementation with Azure DevOps API integration
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const chalk = require('chalk');
|
|
12
|
-
const AzureDevOpsClient = require('../../providers/azure/lib/client');
|
|
13
|
-
const AzureFormatter = require('../../providers/azure/lib/formatter');
|
|
14
|
-
const { table } = require('table');
|
|
15
|
-
|
|
16
|
-
class AzureSprintReport {
|
|
17
|
-
constructor(options = {}) {
|
|
18
|
-
this.silent = options.silent || false;
|
|
19
|
-
this.format = options.format || 'table'; // table, json, csv, markdown, html
|
|
20
|
-
this.sprintPath = options.sprint || null;
|
|
21
|
-
this.includeMetrics = options.metrics !== false;
|
|
22
|
-
this.includeBurndown = options.burndown !== false;
|
|
23
|
-
this.includeVelocity = options.velocity !== false;
|
|
24
|
-
this.exportPath = options.export || null;
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
// Load environment variables from .env file if it exists
|
|
28
|
-
const envPath = path.join(process.cwd(), '.env');
|
|
29
|
-
if (fs.existsSync(envPath)) {
|
|
30
|
-
require('dotenv').config({ path: envPath });
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Also check .claude/.env
|
|
34
|
-
const claudeEnvPath = path.join(process.cwd(), '.claude', '.env');
|
|
35
|
-
if (fs.existsSync(claudeEnvPath)) {
|
|
36
|
-
require('dotenv').config({ path: claudeEnvPath });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Initialize Azure DevOps client
|
|
40
|
-
this.client = new AzureDevOpsClient();
|
|
41
|
-
} catch (error) {
|
|
42
|
-
this.handleInitError(error);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
handleInitError(error) {
|
|
47
|
-
if (error.message.includes('Missing required environment variables')) {
|
|
48
|
-
console.error('❌ Azure DevOps configuration missing!\n');
|
|
49
|
-
console.error('Please set the following environment variables:');
|
|
50
|
-
console.error(' - AZURE_DEVOPS_ORG: Your Azure DevOps organization');
|
|
51
|
-
console.error(' - AZURE_DEVOPS_PROJECT: Your project name');
|
|
52
|
-
console.error(' - AZURE_DEVOPS_PAT: Your Personal Access Token\n');
|
|
53
|
-
console.error('You can set these in .env or .claude/.env file\n');
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
throw error;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async generateReport() {
|
|
60
|
-
try {
|
|
61
|
-
if (!this.silent && this.format !== "json") {
|
|
62
|
-
console.log(chalk.cyan.bold('\n📊 Generating Sprint Report...\n'));
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Get current or specified sprint
|
|
66
|
-
const sprint = await this.getSprintInfo();
|
|
67
|
-
if (!sprint) {
|
|
68
|
-
if (!this.silent) {
|
|
69
|
-
console.log(chalk.yellow('No active sprint found.'));
|
|
70
|
-
}
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Fetch all work items in the sprint
|
|
75
|
-
const workItems = await this.getSprintWorkItems(sprint);
|
|
76
|
-
|
|
77
|
-
// Calculate statistics and metrics
|
|
78
|
-
const statistics = this.calculateStatistics(workItems);
|
|
79
|
-
const burndown = this.includeBurndown ? await this.calculateBurndown(sprint, workItems) : null;
|
|
80
|
-
const velocity = this.includeVelocity ? await this.calculateVelocity() : null;
|
|
81
|
-
const teamPerformance = this.includeMetrics ? await this.calculateTeamPerformance(workItems) : null;
|
|
82
|
-
|
|
83
|
-
// Identify blockers and risks
|
|
84
|
-
const blockers = this.identifyBlockers(workItems);
|
|
85
|
-
const risks = this.identifyRisks(workItems, statistics);
|
|
86
|
-
|
|
87
|
-
// Group work items by various criteria
|
|
88
|
-
const byState = this.groupByState(workItems);
|
|
89
|
-
const byAssignee = this.groupByAssignee(workItems);
|
|
90
|
-
const byType = this.groupByType(workItems);
|
|
91
|
-
|
|
92
|
-
const report = {
|
|
93
|
-
sprint: {
|
|
94
|
-
name: sprint.name,
|
|
95
|
-
path: sprint.path,
|
|
96
|
-
startDate: sprint.attributes?.startDate ?
|
|
97
|
-
new Date(sprint.attributes.startDate).toLocaleDateString() : 'N/A',
|
|
98
|
-
endDate: sprint.attributes?.finishDate ?
|
|
99
|
-
new Date(sprint.attributes.finishDate).toLocaleDateString() : 'N/A',
|
|
100
|
-
state: this.getSprintState(sprint),
|
|
101
|
-
daysRemaining: this.calculateDaysRemaining(sprint)
|
|
102
|
-
},
|
|
103
|
-
statistics,
|
|
104
|
-
byState,
|
|
105
|
-
byType,
|
|
106
|
-
byAssignee,
|
|
107
|
-
workItemDetails: this.formatWorkItemDetails(workItems),
|
|
108
|
-
blockers,
|
|
109
|
-
risks,
|
|
110
|
-
burndown,
|
|
111
|
-
velocity,
|
|
112
|
-
teamPerformance
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
// Display or export the report
|
|
116
|
-
if (this.exportPath) {
|
|
117
|
-
await this.exportReport(report);
|
|
118
|
-
} else if (!this.silent) {
|
|
119
|
-
this.displayReport(report);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return report;
|
|
123
|
-
} catch (error) {
|
|
124
|
-
console.error('Error generating report:', error.message);
|
|
125
|
-
process.exit(1);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async getSprintInfo() {
|
|
130
|
-
if (this.sprintPath) {
|
|
131
|
-
// Get specific sprint by path
|
|
132
|
-
const query = `
|
|
133
|
-
SELECT [System.Id] FROM workitems
|
|
134
|
-
WHERE [System.IterationPath] = '${this.sprintPath}'
|
|
135
|
-
AND [System.WorkItemType] IN ('Task', 'Bug', 'User Story', 'Feature')
|
|
136
|
-
`;
|
|
137
|
-
const result = await this.client.executeWiql(query);
|
|
138
|
-
if (result && result.workItems && result.workItems.length > 0) {
|
|
139
|
-
return {
|
|
140
|
-
name: this.sprintPath.split('\\').pop(),
|
|
141
|
-
path: this.sprintPath,
|
|
142
|
-
attributes: {}
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
return null;
|
|
146
|
-
} else {
|
|
147
|
-
// Get current sprint
|
|
148
|
-
return await this.client.getCurrentSprint();
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async getSprintWorkItems(sprint) {
|
|
153
|
-
const sprintPath = sprint.path || `${this.client.project}\\${sprint.name}`;
|
|
154
|
-
|
|
155
|
-
const query = `
|
|
156
|
-
SELECT [System.Id],
|
|
157
|
-
[System.Title],
|
|
158
|
-
[System.State],
|
|
159
|
-
[System.WorkItemType],
|
|
160
|
-
[System.AssignedTo],
|
|
161
|
-
[Microsoft.VSTS.Scheduling.StoryPoints],
|
|
162
|
-
[Microsoft.VSTS.Scheduling.RemainingWork],
|
|
163
|
-
[Microsoft.VSTS.Scheduling.CompletedWork],
|
|
164
|
-
[Microsoft.VSTS.Scheduling.OriginalEstimate],
|
|
165
|
-
[Microsoft.VSTS.Common.Priority],
|
|
166
|
-
[System.Tags],
|
|
167
|
-
[System.CreatedDate],
|
|
168
|
-
[System.ChangedDate],
|
|
169
|
-
[System.ClosedDate]
|
|
170
|
-
FROM workitems
|
|
171
|
-
WHERE [System.IterationPath] = '${sprintPath}'
|
|
172
|
-
AND [System.WorkItemType] IN ('Task', 'Bug', 'User Story', 'Feature', 'Epic')
|
|
173
|
-
ORDER BY [Microsoft.VSTS.Common.Priority] ASC, [System.Id] ASC
|
|
174
|
-
`;
|
|
175
|
-
|
|
176
|
-
const result = await this.client.executeWiql(query);
|
|
177
|
-
if (!result || !result.workItems || result.workItems.length === 0) {
|
|
178
|
-
return [];
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const ids = result.workItems.map(item => item.id);
|
|
182
|
-
return await this.client.getWorkItems(ids);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
calculateStatistics(workItems) {
|
|
186
|
-
const states = {
|
|
187
|
-
'New': 0,
|
|
188
|
-
'Active': 0,
|
|
189
|
-
'In Progress': 0,
|
|
190
|
-
'Resolved': 0,
|
|
191
|
-
'Closed': 0,
|
|
192
|
-
'Done': 0,
|
|
193
|
-
'Removed': 0
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
let totalStoryPoints = 0;
|
|
197
|
-
let completedStoryPoints = 0;
|
|
198
|
-
let totalRemainingWork = 0;
|
|
199
|
-
let totalCompletedWork = 0;
|
|
200
|
-
let totalOriginalEstimate = 0;
|
|
201
|
-
|
|
202
|
-
workItems.forEach(item => {
|
|
203
|
-
const fields = item.fields || {};
|
|
204
|
-
const state = fields['System.State'] || 'Unknown';
|
|
205
|
-
const storyPoints = fields['Microsoft.VSTS.Scheduling.StoryPoints'] || 0;
|
|
206
|
-
const remainingWork = fields['Microsoft.VSTS.Scheduling.RemainingWork'] || 0;
|
|
207
|
-
const completedWork = fields['Microsoft.VSTS.Scheduling.CompletedWork'] || 0;
|
|
208
|
-
const originalEstimate = fields['Microsoft.VSTS.Scheduling.OriginalEstimate'] || 0;
|
|
209
|
-
|
|
210
|
-
states[state] = (states[state] || 0) + 1;
|
|
211
|
-
totalStoryPoints += storyPoints;
|
|
212
|
-
totalRemainingWork += remainingWork;
|
|
213
|
-
totalCompletedWork += completedWork;
|
|
214
|
-
totalOriginalEstimate += originalEstimate;
|
|
215
|
-
|
|
216
|
-
if (state === 'Done' || state === 'Closed' || state === 'Resolved') {
|
|
217
|
-
completedStoryPoints += storyPoints;
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
const totalItems = workItems.length;
|
|
222
|
-
const completedItems = states['Done'] + states['Closed'] + states['Resolved'];
|
|
223
|
-
const inProgressItems = states['Active'] + states['In Progress'];
|
|
224
|
-
const newItems = states['New'];
|
|
225
|
-
const completionRate = totalItems > 0 ?
|
|
226
|
-
((completedItems / totalItems) * 100).toFixed(1) + '%' : '0%';
|
|
227
|
-
const storyPointsCompletion = totalStoryPoints > 0 ?
|
|
228
|
-
((completedStoryPoints / totalStoryPoints) * 100).toFixed(1) + '%' : '0%';
|
|
229
|
-
|
|
230
|
-
return {
|
|
231
|
-
totalItems,
|
|
232
|
-
completedItems,
|
|
233
|
-
inProgressItems,
|
|
234
|
-
newItems,
|
|
235
|
-
completionRate,
|
|
236
|
-
storyPointsCompletion,
|
|
237
|
-
totalStoryPoints,
|
|
238
|
-
completedStoryPoints,
|
|
239
|
-
remainingStoryPoints: totalStoryPoints - completedStoryPoints,
|
|
240
|
-
totalRemainingWork,
|
|
241
|
-
totalCompletedWork,
|
|
242
|
-
totalOriginalEstimate,
|
|
243
|
-
workCompletion: totalOriginalEstimate > 0 ?
|
|
244
|
-
((totalCompletedWork / totalOriginalEstimate) * 100).toFixed(1) + '%' : 'N/A',
|
|
245
|
-
velocity: completedStoryPoints,
|
|
246
|
-
burndownTrend: this.calculateBurndownTrend(totalRemainingWork, totalOriginalEstimate),
|
|
247
|
-
states
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
calculateBurndownTrend(remaining, original) {
|
|
252
|
-
if (original === 0) return 'No estimates';
|
|
253
|
-
const percentRemaining = (remaining / original) * 100;
|
|
254
|
-
if (percentRemaining > 70) return 'Behind schedule';
|
|
255
|
-
if (percentRemaining > 40) return 'On track';
|
|
256
|
-
return 'Ahead of schedule';
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
async calculateBurndown(sprint, workItems) {
|
|
260
|
-
// Simplified burndown calculation
|
|
261
|
-
// In a real implementation, this would fetch historical data
|
|
262
|
-
const startDate = sprint.attributes?.startDate ? new Date(sprint.attributes.startDate) : null;
|
|
263
|
-
const endDate = sprint.attributes?.finishDate ? new Date(sprint.attributes.finishDate) : null;
|
|
264
|
-
|
|
265
|
-
if (!startDate || !endDate) {
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const totalDays = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24));
|
|
270
|
-
const today = new Date();
|
|
271
|
-
const daysElapsed = Math.ceil((today - startDate) / (1000 * 60 * 60 * 24));
|
|
272
|
-
const daysRemaining = Math.max(0, totalDays - daysElapsed);
|
|
273
|
-
|
|
274
|
-
const totalWork = workItems.reduce((sum, item) => {
|
|
275
|
-
const estimate = item.fields?.['Microsoft.VSTS.Scheduling.OriginalEstimate'] || 0;
|
|
276
|
-
return sum + estimate;
|
|
277
|
-
}, 0);
|
|
278
|
-
|
|
279
|
-
const remainingWork = workItems.reduce((sum, item) => {
|
|
280
|
-
const remaining = item.fields?.['Microsoft.VSTS.Scheduling.RemainingWork'] || 0;
|
|
281
|
-
return sum + remaining;
|
|
282
|
-
}, 0);
|
|
283
|
-
|
|
284
|
-
const idealBurnRate = totalWork / totalDays;
|
|
285
|
-
const actualBurnRate = daysElapsed > 0 ? (totalWork - remainingWork) / daysElapsed : 0;
|
|
286
|
-
|
|
287
|
-
return {
|
|
288
|
-
totalDays,
|
|
289
|
-
daysElapsed,
|
|
290
|
-
daysRemaining,
|
|
291
|
-
totalWork,
|
|
292
|
-
remainingWork,
|
|
293
|
-
completedWork: totalWork - remainingWork,
|
|
294
|
-
idealBurnRate: idealBurnRate.toFixed(1),
|
|
295
|
-
actualBurnRate: actualBurnRate.toFixed(1),
|
|
296
|
-
projectedCompletion: actualBurnRate > 0 ?
|
|
297
|
-
Math.ceil(remainingWork / actualBurnRate) : 'Unknown',
|
|
298
|
-
status: actualBurnRate >= idealBurnRate ? 'On Track' : 'Behind'
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
async calculateVelocity() {
|
|
303
|
-
// Fetch historical velocity data for the last 3 sprints
|
|
304
|
-
// This is a simplified version - real implementation would query past sprints
|
|
305
|
-
const historicalVelocities = [35, 42, 38]; // Mock data
|
|
306
|
-
const averageVelocity = historicalVelocities.reduce((a, b) => a + b, 0) / historicalVelocities.length;
|
|
307
|
-
|
|
308
|
-
return {
|
|
309
|
-
historical: historicalVelocities,
|
|
310
|
-
average: averageVelocity.toFixed(1),
|
|
311
|
-
trend: this.calculateVelocityTrend(historicalVelocities)
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
calculateVelocityTrend(velocities) {
|
|
316
|
-
if (velocities.length < 2) return 'Insufficient data';
|
|
317
|
-
const recent = velocities[velocities.length - 1];
|
|
318
|
-
const previous = velocities[velocities.length - 2];
|
|
319
|
-
if (recent > previous * 1.1) return 'Improving';
|
|
320
|
-
if (recent < previous * 0.9) return 'Declining';
|
|
321
|
-
return 'Stable';
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
async calculateTeamPerformance(workItems) {
|
|
325
|
-
const byAssignee = {};
|
|
326
|
-
let totalEstimates = 0;
|
|
327
|
-
let totalCompleted = 0;
|
|
328
|
-
let bugCount = 0;
|
|
329
|
-
let totalItems = 0;
|
|
330
|
-
|
|
331
|
-
workItems.forEach(item => {
|
|
332
|
-
const fields = item.fields || {};
|
|
333
|
-
const assignee = fields['System.AssignedTo']?.displayName || 'Unassigned';
|
|
334
|
-
const type = fields['System.WorkItemType'];
|
|
335
|
-
const state = fields['System.State'];
|
|
336
|
-
const storyPoints = fields['Microsoft.VSTS.Scheduling.StoryPoints'] || 0;
|
|
337
|
-
const originalEstimate = fields['Microsoft.VSTS.Scheduling.OriginalEstimate'] || 0;
|
|
338
|
-
const completedWork = fields['Microsoft.VSTS.Scheduling.CompletedWork'] || 0;
|
|
339
|
-
|
|
340
|
-
if (!byAssignee[assignee]) {
|
|
341
|
-
byAssignee[assignee] = {
|
|
342
|
-
totalItems: 0,
|
|
343
|
-
completedItems: 0,
|
|
344
|
-
storyPoints: 0,
|
|
345
|
-
completedPoints: 0,
|
|
346
|
-
bugs: 0
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
byAssignee[assignee].totalItems++;
|
|
351
|
-
byAssignee[assignee].storyPoints += storyPoints;
|
|
352
|
-
|
|
353
|
-
if (state === 'Done' || state === 'Closed' || state === 'Resolved') {
|
|
354
|
-
byAssignee[assignee].completedItems++;
|
|
355
|
-
byAssignee[assignee].completedPoints += storyPoints;
|
|
356
|
-
totalCompleted += completedWork;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
if (type === 'Bug') {
|
|
360
|
-
byAssignee[assignee].bugs++;
|
|
361
|
-
bugCount++;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
totalEstimates += originalEstimate;
|
|
365
|
-
totalItems++;
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
const capacityUtilization = totalEstimates > 0 ?
|
|
369
|
-
((totalCompleted / totalEstimates) * 100).toFixed(1) + '%' : 'N/A';
|
|
370
|
-
const defectRate = totalItems > 0 ?
|
|
371
|
-
((bugCount / totalItems) * 100).toFixed(1) + '%' : '0%';
|
|
372
|
-
|
|
373
|
-
return {
|
|
374
|
-
byAssignee,
|
|
375
|
-
capacityUtilization,
|
|
376
|
-
defectRate,
|
|
377
|
-
bugCount,
|
|
378
|
-
teamSize: Object.keys(byAssignee).length - (byAssignee['Unassigned'] ? 1 : 0)
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
identifyBlockers(workItems) {
|
|
383
|
-
const blockers = [];
|
|
384
|
-
|
|
385
|
-
workItems.forEach(item => {
|
|
386
|
-
const fields = item.fields || {};
|
|
387
|
-
const tags = (fields['System.Tags'] || '').toLowerCase();
|
|
388
|
-
const state = fields['System.State'];
|
|
389
|
-
const title = fields['System.Title'];
|
|
390
|
-
const assignee = fields['System.AssignedTo']?.displayName || 'Unassigned';
|
|
391
|
-
|
|
392
|
-
if (tags.includes('blocked') || tags.includes('blocker')) {
|
|
393
|
-
blockers.push({
|
|
394
|
-
id: item.id,
|
|
395
|
-
title,
|
|
396
|
-
assignee,
|
|
397
|
-
reason: this.extractBlockerReason(tags),
|
|
398
|
-
daysBlocked: this.calculateDaysInState(fields['System.ChangedDate'])
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
return blockers;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
extractBlockerReason(tags) {
|
|
407
|
-
if (tags.includes('waiting')) return 'Waiting for dependencies';
|
|
408
|
-
if (tags.includes('approval')) return 'Pending approval';
|
|
409
|
-
if (tags.includes('resource')) return 'Resource constraints';
|
|
410
|
-
if (tags.includes('technical')) return 'Technical blocker';
|
|
411
|
-
return 'Unspecified blocker';
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
identifyRisks(workItems, statistics) {
|
|
415
|
-
const risks = [];
|
|
416
|
-
|
|
417
|
-
// Check completion rate
|
|
418
|
-
const completionRate = parseFloat(statistics.completionRate);
|
|
419
|
-
if (completionRate < 40) {
|
|
420
|
-
risks.push({
|
|
421
|
-
severity: 'High',
|
|
422
|
-
description: `Low completion rate (${statistics.completionRate}) - sprint goals at risk`
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// Check for too many new items
|
|
427
|
-
if (statistics.newItems > statistics.totalItems * 0.3) {
|
|
428
|
-
risks.push({
|
|
429
|
-
severity: 'Medium',
|
|
430
|
-
description: `High number of new items (${statistics.newItems}) - scope may be unclear`
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// Check for unassigned critical items
|
|
435
|
-
const unassignedCritical = workItems.filter(item => {
|
|
436
|
-
const fields = item.fields || {};
|
|
437
|
-
const priority = fields['Microsoft.VSTS.Common.Priority'] || 999;
|
|
438
|
-
const assignee = fields['System.AssignedTo'];
|
|
439
|
-
return priority <= 1 && !assignee;
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
if (unassignedCritical.length > 0) {
|
|
443
|
-
risks.push({
|
|
444
|
-
severity: 'High',
|
|
445
|
-
description: `${unassignedCritical.length} critical items are unassigned`
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// Check burndown trend
|
|
450
|
-
if (statistics.burndownTrend === 'Behind schedule') {
|
|
451
|
-
risks.push({
|
|
452
|
-
severity: 'High',
|
|
453
|
-
description: 'Burndown indicates the team is behind schedule'
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
return risks;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
calculateDaysInState(changedDate) {
|
|
461
|
-
if (!changedDate) return 0;
|
|
462
|
-
const changed = new Date(changedDate);
|
|
463
|
-
const now = new Date();
|
|
464
|
-
return Math.floor((now - changed) / (1000 * 60 * 60 * 24));
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
calculateDaysRemaining(sprint) {
|
|
468
|
-
if (!sprint.attributes?.finishDate) return 'N/A';
|
|
469
|
-
const endDate = new Date(sprint.attributes.finishDate);
|
|
470
|
-
const now = new Date();
|
|
471
|
-
const days = Math.ceil((endDate - now) / (1000 * 60 * 60 * 24));
|
|
472
|
-
return days > 0 ? days : 0;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
getSprintState(sprint) {
|
|
476
|
-
if (!sprint.attributes) return 'Unknown';
|
|
477
|
-
const now = new Date();
|
|
478
|
-
const startDate = sprint.attributes.startDate ? new Date(sprint.attributes.startDate) : null;
|
|
479
|
-
const endDate = sprint.attributes.finishDate ? new Date(sprint.attributes.finishDate) : null;
|
|
480
|
-
|
|
481
|
-
if (!startDate || !endDate) return 'Not Scheduled';
|
|
482
|
-
if (now < startDate) return 'Future';
|
|
483
|
-
if (now > endDate) return 'Past';
|
|
484
|
-
return 'Current';
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
groupByState(workItems) {
|
|
488
|
-
const groups = {};
|
|
489
|
-
workItems.forEach(item => {
|
|
490
|
-
const state = item.fields?.['System.State'] || 'Unknown';
|
|
491
|
-
if (!groups[state]) groups[state] = [];
|
|
492
|
-
groups[state].push({
|
|
493
|
-
id: item.id,
|
|
494
|
-
title: item.fields?.['System.Title'],
|
|
495
|
-
type: item.fields?.['System.WorkItemType'],
|
|
496
|
-
assignee: item.fields?.['System.AssignedTo']?.displayName || 'Unassigned'
|
|
497
|
-
});
|
|
498
|
-
});
|
|
499
|
-
return groups;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
groupByAssignee(workItems) {
|
|
503
|
-
const groups = {};
|
|
504
|
-
workItems.forEach(item => {
|
|
505
|
-
const assignee = item.fields?.['System.AssignedTo']?.displayName || 'Unassigned';
|
|
506
|
-
if (!groups[assignee]) groups[assignee] = [];
|
|
507
|
-
groups[assignee].push({
|
|
508
|
-
id: item.id,
|
|
509
|
-
title: item.fields?.['System.Title'],
|
|
510
|
-
type: item.fields?.['System.WorkItemType'],
|
|
511
|
-
state: item.fields?.['System.State']
|
|
512
|
-
});
|
|
513
|
-
});
|
|
514
|
-
return groups;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
groupByType(workItems) {
|
|
518
|
-
const groups = {};
|
|
519
|
-
workItems.forEach(item => {
|
|
520
|
-
const type = item.fields?.['System.WorkItemType'] || 'Unknown';
|
|
521
|
-
if (!groups[type]) groups[type] = [];
|
|
522
|
-
groups[type].push({
|
|
523
|
-
id: item.id,
|
|
524
|
-
title: item.fields?.['System.Title'],
|
|
525
|
-
state: item.fields?.['System.State'],
|
|
526
|
-
assignee: item.fields?.['System.AssignedTo']?.displayName || 'Unassigned'
|
|
527
|
-
});
|
|
528
|
-
});
|
|
529
|
-
return groups;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
formatWorkItemDetails(workItems) {
|
|
533
|
-
return workItems.map(item => {
|
|
534
|
-
const fields = item.fields || {};
|
|
535
|
-
return {
|
|
536
|
-
id: item.id,
|
|
537
|
-
title: fields['System.Title'],
|
|
538
|
-
type: fields['System.WorkItemType'],
|
|
539
|
-
state: fields['System.State'],
|
|
540
|
-
assignedTo: fields['System.AssignedTo']?.displayName || 'Unassigned',
|
|
541
|
-
storyPoints: fields['Microsoft.VSTS.Scheduling.StoryPoints'] || 0,
|
|
542
|
-
remainingWork: fields['Microsoft.VSTS.Scheduling.RemainingWork'] || 0,
|
|
543
|
-
priority: fields['Microsoft.VSTS.Common.Priority'] || 999
|
|
544
|
-
};
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
displayReport(report) {
|
|
549
|
-
switch (this.format) {
|
|
550
|
-
case 'json':
|
|
551
|
-
console.log(JSON.stringify(report, null, 2));
|
|
552
|
-
break;
|
|
553
|
-
case 'csv':
|
|
554
|
-
this.displayCSV(report);
|
|
555
|
-
break;
|
|
556
|
-
case 'markdown':
|
|
557
|
-
this.displayMarkdown(report);
|
|
558
|
-
break;
|
|
559
|
-
case 'html':
|
|
560
|
-
this.displayHTML(report);
|
|
561
|
-
break;
|
|
562
|
-
default:
|
|
563
|
-
this.displayTable(report);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
displayTable(report) {
|
|
568
|
-
console.log(chalk.cyan.bold(`🚀 ${report.sprint.name} Report`));
|
|
569
|
-
console.log(`Period: ${report.sprint.startDate} to ${report.sprint.endDate}`);
|
|
570
|
-
console.log(`Status: ${this.getStateColor(report.sprint.state)}`);
|
|
571
|
-
if (report.sprint.daysRemaining !== 'N/A') {
|
|
572
|
-
console.log(`Days Remaining: ${report.sprint.daysRemaining}\n`);
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
console.log(chalk.yellow.bold('📈 Statistics:'));
|
|
576
|
-
const stats = report.statistics;
|
|
577
|
-
console.log(` Total Items: ${chalk.blue(stats.totalItems)}`);
|
|
578
|
-
console.log(` Completed: ${chalk.green(stats.completedItems)} (${stats.completionRate})`);
|
|
579
|
-
console.log(` In Progress: ${chalk.yellow(stats.inProgressItems)}`);
|
|
580
|
-
console.log(` New: ${chalk.gray(stats.newItems)}`);
|
|
581
|
-
console.log(` Story Points: ${chalk.green(stats.completedStoryPoints)}/${stats.totalStoryPoints} (${stats.storyPointsCompletion})`);
|
|
582
|
-
console.log(` Work Hours: ${chalk.blue(stats.totalCompletedWork)}h completed, ${chalk.yellow(stats.totalRemainingWork)}h remaining`);
|
|
583
|
-
console.log(` Velocity: ${chalk.cyan(stats.velocity)} points`);
|
|
584
|
-
console.log(` Burndown: ${this.getBurndownColor(stats.burndownTrend)}\n`);
|
|
585
|
-
|
|
586
|
-
// Display burndown chart if included
|
|
587
|
-
if (report.burndown) {
|
|
588
|
-
this.displayBurndownChart(report.burndown);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// Display work item breakdown
|
|
592
|
-
console.log(chalk.green.bold('📋 Work Item Breakdown:'));
|
|
593
|
-
Object.entries(report.byType).forEach(([type, items]) => {
|
|
594
|
-
console.log(` ${type}: ${items.length} items`);
|
|
595
|
-
});
|
|
596
|
-
console.log('');
|
|
597
|
-
|
|
598
|
-
// Display team workload
|
|
599
|
-
console.log(chalk.blue.bold('👥 Team Workload:'));
|
|
600
|
-
const sortedAssignees = Object.entries(report.byAssignee).sort((a, b) => b[1].length - a[1].length);
|
|
601
|
-
sortedAssignees.slice(0, 5).forEach(([assignee, items]) => {
|
|
602
|
-
if (assignee !== 'Unassigned') {
|
|
603
|
-
const completed = items.filter(i => ['Done', 'Closed', 'Resolved'].includes(i.state)).length;
|
|
604
|
-
console.log(` ${assignee}: ${items.length} items (${completed} completed)`);
|
|
605
|
-
}
|
|
606
|
-
});
|
|
607
|
-
if (report.byAssignee['Unassigned']) {
|
|
608
|
-
console.log(` ${chalk.yellow('Unassigned')}: ${report.byAssignee['Unassigned'].length} items`);
|
|
609
|
-
}
|
|
610
|
-
console.log('');
|
|
611
|
-
|
|
612
|
-
// Display blockers
|
|
613
|
-
if (report.blockers && report.blockers.length > 0) {
|
|
614
|
-
console.log(chalk.red.bold('🚧 Blockers:'));
|
|
615
|
-
report.blockers.forEach(blocker => {
|
|
616
|
-
console.log(` [${chalk.red(blocker.id)}] ${blocker.title}`);
|
|
617
|
-
console.log(` Assigned to: ${blocker.assignee} | Reason: ${blocker.reason}`);
|
|
618
|
-
console.log(` Days blocked: ${chalk.red(blocker.daysBlocked)}`);
|
|
619
|
-
});
|
|
620
|
-
console.log('');
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
// Display risks
|
|
624
|
-
if (report.risks && report.risks.length > 0) {
|
|
625
|
-
console.log(chalk.yellow.bold('⚠️ Risks:'));
|
|
626
|
-
report.risks.forEach(risk => {
|
|
627
|
-
const color = risk.severity === 'High' ? chalk.red : chalk.yellow;
|
|
628
|
-
console.log(` ${color(`[${risk.severity}]`)} ${risk.description}`);
|
|
629
|
-
});
|
|
630
|
-
console.log('');
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// Display velocity trends if included
|
|
634
|
-
if (report.velocity) {
|
|
635
|
-
console.log(chalk.magenta.bold('📊 Velocity Trends:'));
|
|
636
|
-
console.log(` Historical: ${report.velocity.historical.join(', ')} points`);
|
|
637
|
-
console.log(` Average: ${report.velocity.average} points`);
|
|
638
|
-
console.log(` Trend: ${this.getTrendColor(report.velocity.trend)}\n`);
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// Display team performance metrics if included
|
|
642
|
-
if (report.teamPerformance) {
|
|
643
|
-
console.log(chalk.cyan.bold('🎯 Team Performance:'));
|
|
644
|
-
console.log(` Team Size: ${report.teamPerformance.teamSize} members`);
|
|
645
|
-
console.log(` Capacity Utilization: ${report.teamPerformance.capacityUtilization}`);
|
|
646
|
-
console.log(` Defect Rate: ${report.teamPerformance.defectRate}`);
|
|
647
|
-
console.log(` Total Bugs: ${report.teamPerformance.bugCount}\n`);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
displayBurndownChart(burndown) {
|
|
652
|
-
console.log(chalk.magenta.bold('📉 Burndown Analysis:'));
|
|
653
|
-
console.log(` Sprint Duration: ${burndown.totalDays} days`);
|
|
654
|
-
console.log(` Days Elapsed: ${burndown.daysElapsed} | Days Remaining: ${burndown.daysRemaining}`);
|
|
655
|
-
console.log(` Work Completed: ${chalk.green(burndown.completedWork + 'h')} / ${burndown.totalWork}h`);
|
|
656
|
-
console.log(` Ideal Burn Rate: ${burndown.idealBurnRate}h/day`);
|
|
657
|
-
console.log(` Actual Burn Rate: ${burndown.actualBurnRate}h/day`);
|
|
658
|
-
console.log(` Status: ${this.getBurndownColor(burndown.status)}`);
|
|
659
|
-
if (burndown.projectedCompletion !== 'Unknown') {
|
|
660
|
-
console.log(` Projected Completion: ${burndown.projectedCompletion} days`);
|
|
661
|
-
}
|
|
662
|
-
console.log('');
|
|
663
|
-
|
|
664
|
-
// Simple ASCII burndown chart
|
|
665
|
-
this.drawAsciiBurndown(burndown);
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
drawAsciiBurndown(burndown) {
|
|
669
|
-
const width = 50;
|
|
670
|
-
const height = 10;
|
|
671
|
-
const chart = [];
|
|
672
|
-
|
|
673
|
-
// Initialize chart
|
|
674
|
-
for (let i = 0; i < height; i++) {
|
|
675
|
-
chart[i] = new Array(width).fill(' ');
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
// Draw axes
|
|
679
|
-
for (let i = 0; i < width; i++) {
|
|
680
|
-
chart[height - 1][i] = '─';
|
|
681
|
-
}
|
|
682
|
-
for (let i = 0; i < height; i++) {
|
|
683
|
-
chart[i][0] = '│';
|
|
684
|
-
}
|
|
685
|
-
chart[height - 1][0] = '└';
|
|
686
|
-
|
|
687
|
-
// Draw ideal line (diagonal from top-left to bottom-right)
|
|
688
|
-
const idealStep = burndown.totalWork / burndown.totalDays;
|
|
689
|
-
for (let day = 0; day <= burndown.totalDays && day < width - 1; day++) {
|
|
690
|
-
const idealRemaining = burndown.totalWork - (idealStep * day);
|
|
691
|
-
const y = Math.floor((height - 2) * (1 - idealRemaining / burndown.totalWork));
|
|
692
|
-
if (y >= 0 && y < height - 1) {
|
|
693
|
-
chart[y][day + 1] = '·';
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
// Draw actual line
|
|
698
|
-
const actualProgress = burndown.daysElapsed;
|
|
699
|
-
for (let day = 0; day <= actualProgress && day < width - 1; day++) {
|
|
700
|
-
const actualRemaining = burndown.totalWork - (burndown.actualBurnRate * day);
|
|
701
|
-
const y = Math.floor((height - 2) * (1 - actualRemaining / burndown.totalWork));
|
|
702
|
-
if (y >= 0 && y < height - 1) {
|
|
703
|
-
chart[y][day + 1] = '█';
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// Print chart
|
|
708
|
-
console.log(' Burndown Chart (█ = Actual, · = Ideal):');
|
|
709
|
-
chart.forEach(row => {
|
|
710
|
-
console.log(' ' + row.join(''));
|
|
711
|
-
});
|
|
712
|
-
console.log(' Days →\n');
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
displayCSV(report) {
|
|
716
|
-
console.log('Sprint,Start Date,End Date,Total Items,Completed,In Progress,New,Completion Rate,Story Points,Velocity');
|
|
717
|
-
const s = report.statistics;
|
|
718
|
-
console.log(`"${report.sprint.name}","${report.sprint.startDate}","${report.sprint.endDate}",${s.totalItems},${s.completedItems},${s.inProgressItems},${s.newItems},"${s.completionRate}",${s.totalStoryPoints},${s.velocity}`);
|
|
719
|
-
|
|
720
|
-
if (report.workItemDetails && report.workItemDetails.length > 0) {
|
|
721
|
-
console.log('\nWork Items:');
|
|
722
|
-
console.log('ID,Title,Type,State,Assigned To,Story Points,Remaining Work,Priority');
|
|
723
|
-
report.workItemDetails.forEach(item => {
|
|
724
|
-
console.log(`${item.id},"${item.title}","${item.type}","${item.state}","${item.assignedTo}",${item.storyPoints},${item.remainingWork},${item.priority}`);
|
|
725
|
-
});
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
displayMarkdown(report) {
|
|
730
|
-
console.log(`# ${report.sprint.name} Sprint Report\n`);
|
|
731
|
-
console.log(`**Period:** ${report.sprint.startDate} to ${report.sprint.endDate}`);
|
|
732
|
-
console.log(`**Status:** ${report.sprint.state}`);
|
|
733
|
-
console.log(`**Days Remaining:** ${report.sprint.daysRemaining}\n`);
|
|
734
|
-
|
|
735
|
-
console.log('## Statistics\n');
|
|
736
|
-
const stats = report.statistics;
|
|
737
|
-
console.log('| Metric | Value |');
|
|
738
|
-
console.log('|--------|-------|');
|
|
739
|
-
console.log(`| Total Items | ${stats.totalItems} |`);
|
|
740
|
-
console.log(`| Completed | ${stats.completedItems} (${stats.completionRate}) |`);
|
|
741
|
-
console.log(`| In Progress | ${stats.inProgressItems} |`);
|
|
742
|
-
console.log(`| New | ${stats.newItems} |`);
|
|
743
|
-
console.log(`| Story Points | ${stats.completedStoryPoints}/${stats.totalStoryPoints} |`);
|
|
744
|
-
console.log(`| Velocity | ${stats.velocity} points |`);
|
|
745
|
-
console.log(`| Burndown Trend | ${stats.burndownTrend} |\n`);
|
|
746
|
-
|
|
747
|
-
if (report.blockers && report.blockers.length > 0) {
|
|
748
|
-
console.log('## Blockers\n');
|
|
749
|
-
report.blockers.forEach(blocker => {
|
|
750
|
-
console.log(`- **[${blocker.id}]** ${blocker.title}`);
|
|
751
|
-
console.log(` - Assigned to: ${blocker.assignee}`);
|
|
752
|
-
console.log(` - Reason: ${blocker.reason}`);
|
|
753
|
-
console.log(` - Days blocked: ${blocker.daysBlocked}`);
|
|
754
|
-
});
|
|
755
|
-
console.log('');
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
if (report.risks && report.risks.length > 0) {
|
|
759
|
-
console.log('## Risks\n');
|
|
760
|
-
report.risks.forEach(risk => {
|
|
761
|
-
console.log(`- **[${risk.severity}]** ${risk.description}`);
|
|
762
|
-
});
|
|
763
|
-
console.log('');
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
if (report.teamPerformance) {
|
|
767
|
-
console.log('## Team Performance\n');
|
|
768
|
-
console.log(`- **Team Size:** ${report.teamPerformance.teamSize} members`);
|
|
769
|
-
console.log(`- **Capacity Utilization:** ${report.teamPerformance.capacityUtilization}`);
|
|
770
|
-
console.log(`- **Defect Rate:** ${report.teamPerformance.defectRate}`);
|
|
771
|
-
console.log(`- **Total Bugs:** ${report.teamPerformance.bugCount}\n`);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
displayHTML(report) {
|
|
776
|
-
const html = `
|
|
777
|
-
<!DOCTYPE html>
|
|
778
|
-
<html>
|
|
779
|
-
<head>
|
|
780
|
-
<title>${report.sprint.name} Sprint Report</title>
|
|
781
|
-
<style>
|
|
782
|
-
body { font-family: Arial, sans-serif; margin: 20px; }
|
|
783
|
-
h1 { color: #0078d4; }
|
|
784
|
-
h2 { color: #106ebe; border-bottom: 2px solid #e5e5e5; padding-bottom: 5px; }
|
|
785
|
-
table { border-collapse: collapse; width: 100%; margin: 15px 0; }
|
|
786
|
-
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
|
|
787
|
-
th { background-color: #f2f2f2; }
|
|
788
|
-
.high-risk { color: #d83b01; font-weight: bold; }
|
|
789
|
-
.medium-risk { color: #f7630c; }
|
|
790
|
-
.blocker { background-color: #fde7e9; }
|
|
791
|
-
.metric-card { display: inline-block; padding: 15px; margin: 10px;
|
|
792
|
-
background: #f5f5f5; border-radius: 5px; min-width: 150px; }
|
|
793
|
-
.metric-value { font-size: 24px; font-weight: bold; color: #0078d4; }
|
|
794
|
-
.metric-label { color: #666; font-size: 12px; }
|
|
795
|
-
</style>
|
|
796
|
-
</head>
|
|
797
|
-
<body>
|
|
798
|
-
<h1>${report.sprint.name} Sprint Report</h1>
|
|
799
|
-
<p><strong>Period:</strong> ${report.sprint.startDate} to ${report.sprint.endDate}</p>
|
|
800
|
-
<p><strong>Status:</strong> ${report.sprint.state} | <strong>Days Remaining:</strong> ${report.sprint.daysRemaining}</p>
|
|
801
|
-
|
|
802
|
-
<h2>Key Metrics</h2>
|
|
803
|
-
<div>
|
|
804
|
-
<div class="metric-card">
|
|
805
|
-
<div class="metric-value">${report.statistics.completionRate}</div>
|
|
806
|
-
<div class="metric-label">Completion Rate</div>
|
|
807
|
-
</div>
|
|
808
|
-
<div class="metric-card">
|
|
809
|
-
<div class="metric-value">${report.statistics.velocity}</div>
|
|
810
|
-
<div class="metric-label">Velocity (Points)</div>
|
|
811
|
-
</div>
|
|
812
|
-
<div class="metric-card">
|
|
813
|
-
<div class="metric-value">${report.statistics.totalItems}</div>
|
|
814
|
-
<div class="metric-label">Total Items</div>
|
|
815
|
-
</div>
|
|
816
|
-
</div>
|
|
817
|
-
|
|
818
|
-
${this.generateHTMLTable(report)}
|
|
819
|
-
${this.generateHTMLBlockers(report.blockers)}
|
|
820
|
-
${this.generateHTMLRisks(report.risks)}
|
|
821
|
-
</body>
|
|
822
|
-
</html>`;
|
|
823
|
-
console.log(html);
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
generateHTMLTable(report) {
|
|
827
|
-
let html = '<h2>Work Items</h2><table><thead><tr>';
|
|
828
|
-
html += '<th>ID</th><th>Title</th><th>Type</th><th>State</th><th>Assigned To</th>';
|
|
829
|
-
html += '</tr></thead><tbody>';
|
|
830
|
-
|
|
831
|
-
report.workItemDetails.slice(0, 20).forEach(item => {
|
|
832
|
-
html += `<tr>`;
|
|
833
|
-
html += `<td>${item.id}</td>`;
|
|
834
|
-
html += `<td>${item.title}</td>`;
|
|
835
|
-
html += `<td>${item.type}</td>`;
|
|
836
|
-
html += `<td>${item.state}</td>`;
|
|
837
|
-
html += `<td>${item.assignedTo}</td>`;
|
|
838
|
-
html += `</tr>`;
|
|
839
|
-
});
|
|
840
|
-
|
|
841
|
-
html += '</tbody></table>';
|
|
842
|
-
return html;
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
generateHTMLBlockers(blockers) {
|
|
846
|
-
if (!blockers || blockers.length === 0) return '';
|
|
847
|
-
|
|
848
|
-
let html = '<h2>Blockers</h2><table class="blocker"><thead><tr>';
|
|
849
|
-
html += '<th>ID</th><th>Title</th><th>Assigned To</th><th>Reason</th><th>Days Blocked</th>';
|
|
850
|
-
html += '</tr></thead><tbody>';
|
|
851
|
-
|
|
852
|
-
blockers.forEach(blocker => {
|
|
853
|
-
html += `<tr>`;
|
|
854
|
-
html += `<td>${blocker.id}</td>`;
|
|
855
|
-
html += `<td>${blocker.title}</td>`;
|
|
856
|
-
html += `<td>${blocker.assignee}</td>`;
|
|
857
|
-
html += `<td>${blocker.reason}</td>`;
|
|
858
|
-
html += `<td>${blocker.daysBlocked}</td>`;
|
|
859
|
-
html += `</tr>`;
|
|
860
|
-
});
|
|
861
|
-
|
|
862
|
-
html += '</tbody></table>';
|
|
863
|
-
return html;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
generateHTMLRisks(risks) {
|
|
867
|
-
if (!risks || risks.length === 0) return '';
|
|
868
|
-
|
|
869
|
-
let html = '<h2>Risks</h2><ul>';
|
|
870
|
-
risks.forEach(risk => {
|
|
871
|
-
const cssClass = risk.severity === 'High' ? 'high-risk' : 'medium-risk';
|
|
872
|
-
html += `<li class="${cssClass}">[${risk.severity}] ${risk.description}</li>`;
|
|
873
|
-
});
|
|
874
|
-
html += '</ul>';
|
|
875
|
-
return html;
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
async exportReport(report) {
|
|
879
|
-
const content = this.format === 'json' ?
|
|
880
|
-
JSON.stringify(report, null, 2) :
|
|
881
|
-
this.format === 'html' ?
|
|
882
|
-
this.generateFullHTML(report) :
|
|
883
|
-
this.generateExportContent(report);
|
|
884
|
-
|
|
885
|
-
await fs.writeFile(this.exportPath, content);
|
|
886
|
-
console.log(chalk.green(`✅ Report exported to ${this.exportPath}`));
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
generateFullHTML(report) {
|
|
890
|
-
// Generate complete HTML document for export
|
|
891
|
-
return this.displayHTML(report);
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
generateExportContent(report) {
|
|
895
|
-
// Generate content based on format for export
|
|
896
|
-
if (this.format === 'markdown') {
|
|
897
|
-
let content = '';
|
|
898
|
-
const originalLog = console.log;
|
|
899
|
-
console.log = (msg) => { content += msg + '\n'; };
|
|
900
|
-
this.displayMarkdown(report);
|
|
901
|
-
console.log = originalLog;
|
|
902
|
-
return content;
|
|
903
|
-
}
|
|
904
|
-
return JSON.stringify(report, null, 2);
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
getStateColor(state) {
|
|
908
|
-
const colors = {
|
|
909
|
-
'Current': chalk.green(state),
|
|
910
|
-
'Future': chalk.blue(state),
|
|
911
|
-
'Past': chalk.gray(state),
|
|
912
|
-
'Not Scheduled': chalk.yellow(state)
|
|
913
|
-
};
|
|
914
|
-
return colors[state] || chalk.white(state);
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
getBurndownColor(trend) {
|
|
918
|
-
const colors = {
|
|
919
|
-
'Ahead of schedule': chalk.green(trend),
|
|
920
|
-
'On track': chalk.blue(trend),
|
|
921
|
-
'Behind schedule': chalk.red(trend),
|
|
922
|
-
'No estimates': chalk.gray(trend)
|
|
923
|
-
};
|
|
924
|
-
return colors[trend] || chalk.white(trend);
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
getTrendColor(trend) {
|
|
928
|
-
const colors = {
|
|
929
|
-
'Improving': chalk.green(trend),
|
|
930
|
-
'Stable': chalk.blue(trend),
|
|
931
|
-
'Declining': chalk.red(trend),
|
|
932
|
-
'Insufficient data': chalk.gray(trend)
|
|
933
|
-
};
|
|
934
|
-
return colors[trend] || chalk.white(trend);
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
static parseArguments(args = process.argv) {
|
|
938
|
-
const options = {};
|
|
939
|
-
|
|
940
|
-
args.forEach((arg, index) => {
|
|
941
|
-
if (arg === '--sprint' && args[index + 1]) {
|
|
942
|
-
options.sprint = args[index + 1];
|
|
943
|
-
} else if (arg === '--format' && args[index + 1]) {
|
|
944
|
-
options.format = args[index + 1];
|
|
945
|
-
} else if (arg === '--export' && args[index + 1]) {
|
|
946
|
-
options.export = args[index + 1];
|
|
947
|
-
} else if (arg === '--no-metrics') {
|
|
948
|
-
options.metrics = false;
|
|
949
|
-
} else if (arg === '--no-burndown') {
|
|
950
|
-
options.burndown = false;
|
|
951
|
-
} else if (arg === '--no-velocity') {
|
|
952
|
-
options.velocity = false;
|
|
953
|
-
} else if (arg === '--json') {
|
|
954
|
-
options.format = 'json';
|
|
955
|
-
} else if (arg === '--csv') {
|
|
956
|
-
options.format = 'csv';
|
|
957
|
-
} else if (arg === '--markdown' || arg === '--md') {
|
|
958
|
-
options.format = 'markdown';
|
|
959
|
-
} else if (arg === '--html') {
|
|
960
|
-
options.format = 'html';
|
|
961
|
-
} else if (arg === '--silent' || arg === '-s') {
|
|
962
|
-
options.silent = true;
|
|
963
|
-
} else if (arg === '--help' || arg === '-h') {
|
|
964
|
-
console.log(chalk.cyan.bold('\nAzure DevOps Sprint Report Generator\n'));
|
|
965
|
-
console.log('Usage: azure-sprint-report [options]\n');
|
|
966
|
-
console.log('Options:');
|
|
967
|
-
console.log(' --sprint <path> Sprint iteration path');
|
|
968
|
-
console.log(' --format <format> Output format: table, json, csv, markdown, html');
|
|
969
|
-
console.log(' --export <file> Export report to file');
|
|
970
|
-
console.log(' --no-metrics Exclude team performance metrics');
|
|
971
|
-
console.log(' --no-burndown Exclude burndown analysis');
|
|
972
|
-
console.log(' --no-velocity Exclude velocity trends');
|
|
973
|
-
console.log(' --json Output as JSON');
|
|
974
|
-
console.log(' --csv Output as CSV');
|
|
975
|
-
console.log(' --markdown, --md Output as Markdown');
|
|
976
|
-
console.log(' --html Output as HTML');
|
|
977
|
-
console.log(' --silent, -s Silent mode');
|
|
978
|
-
console.log(' --help, -h Show this help\n');
|
|
979
|
-
console.log('Examples:');
|
|
980
|
-
console.log(' azure-sprint-report');
|
|
981
|
-
console.log(' azure-sprint-report --sprint "Project\\Sprint 2024.1"');
|
|
982
|
-
console.log(' azure-sprint-report --format markdown --export sprint-report.md');
|
|
983
|
-
console.log(' azure-sprint-report --no-metrics --json');
|
|
984
|
-
process.exit(0);
|
|
985
|
-
}
|
|
986
|
-
});
|
|
987
|
-
|
|
988
|
-
return options;
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
// Run if called directly
|
|
993
|
-
if (require.main === module) {
|
|
994
|
-
const options = AzureSprintReport.parseArguments();
|
|
995
|
-
const sprintReport = new AzureSprintReport(options);
|
|
996
|
-
|
|
997
|
-
sprintReport.generateReport()
|
|
998
|
-
.then(() => {
|
|
999
|
-
if (sprintReport.client) {
|
|
1000
|
-
const stats = sprintReport.client.getCacheStats();
|
|
1001
|
-
if (!options.silent && process.env.DEBUG) {
|
|
1002
|
-
console.log(chalk.dim(`\nCache stats: ${JSON.stringify(stats)}`));
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
})
|
|
1006
|
-
.catch(error => {
|
|
1007
|
-
console.error('Error:', error.message);
|
|
1008
|
-
process.exit(1);
|
|
1009
|
-
});
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
module.exports = AzureSprintReport;
|