claude-autopm 2.7.0 → 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.
Files changed (281) hide show
  1. package/README.md +307 -56
  2. package/autopm/.claude/.env +158 -0
  3. package/autopm/.claude/settings.local.json +9 -0
  4. package/bin/autopm.js +11 -2
  5. package/bin/commands/epic.js +23 -3
  6. package/bin/commands/plugin.js +395 -0
  7. package/bin/commands/team.js +184 -10
  8. package/install/install.js +223 -4
  9. package/lib/cli/commands/issue.js +360 -20
  10. package/lib/plugins/PluginManager.js +1328 -0
  11. package/lib/plugins/PluginManager.old.js +400 -0
  12. package/lib/providers/AzureDevOpsProvider.js +575 -0
  13. package/lib/providers/GitHubProvider.js +475 -0
  14. package/lib/services/EpicService.js +1092 -3
  15. package/lib/services/IssueService.js +991 -0
  16. package/package.json +9 -1
  17. package/scripts/publish-plugins.sh +166 -0
  18. package/autopm/.claude/agents/cloud/README.md +0 -55
  19. package/autopm/.claude/agents/cloud/aws-cloud-architect.md +0 -521
  20. package/autopm/.claude/agents/cloud/azure-cloud-architect.md +0 -436
  21. package/autopm/.claude/agents/cloud/gcp-cloud-architect.md +0 -385
  22. package/autopm/.claude/agents/cloud/gcp-cloud-functions-engineer.md +0 -306
  23. package/autopm/.claude/agents/cloud/gemini-api-expert.md +0 -880
  24. package/autopm/.claude/agents/cloud/kubernetes-orchestrator.md +0 -566
  25. package/autopm/.claude/agents/cloud/openai-python-expert.md +0 -1087
  26. package/autopm/.claude/agents/cloud/terraform-infrastructure-expert.md +0 -454
  27. package/autopm/.claude/agents/core/agent-manager.md +0 -296
  28. package/autopm/.claude/agents/core/code-analyzer.md +0 -131
  29. package/autopm/.claude/agents/core/file-analyzer.md +0 -162
  30. package/autopm/.claude/agents/core/test-runner.md +0 -200
  31. package/autopm/.claude/agents/data/airflow-orchestration-expert.md +0 -52
  32. package/autopm/.claude/agents/data/kedro-pipeline-expert.md +0 -50
  33. package/autopm/.claude/agents/data/langgraph-workflow-expert.md +0 -520
  34. package/autopm/.claude/agents/databases/README.md +0 -50
  35. package/autopm/.claude/agents/databases/bigquery-expert.md +0 -392
  36. package/autopm/.claude/agents/databases/cosmosdb-expert.md +0 -368
  37. package/autopm/.claude/agents/databases/mongodb-expert.md +0 -398
  38. package/autopm/.claude/agents/databases/postgresql-expert.md +0 -321
  39. package/autopm/.claude/agents/databases/redis-expert.md +0 -52
  40. package/autopm/.claude/agents/devops/README.md +0 -52
  41. package/autopm/.claude/agents/devops/azure-devops-specialist.md +0 -308
  42. package/autopm/.claude/agents/devops/docker-containerization-expert.md +0 -298
  43. package/autopm/.claude/agents/devops/github-operations-specialist.md +0 -335
  44. package/autopm/.claude/agents/devops/mcp-context-manager.md +0 -319
  45. package/autopm/.claude/agents/devops/observability-engineer.md +0 -574
  46. package/autopm/.claude/agents/devops/ssh-operations-expert.md +0 -1093
  47. package/autopm/.claude/agents/devops/traefik-proxy-expert.md +0 -444
  48. package/autopm/.claude/agents/frameworks/README.md +0 -64
  49. package/autopm/.claude/agents/frameworks/e2e-test-engineer.md +0 -360
  50. package/autopm/.claude/agents/frameworks/nats-messaging-expert.md +0 -254
  51. package/autopm/.claude/agents/frameworks/react-frontend-engineer.md +0 -217
  52. package/autopm/.claude/agents/frameworks/react-ui-expert.md +0 -226
  53. package/autopm/.claude/agents/frameworks/tailwindcss-expert.md +0 -770
  54. package/autopm/.claude/agents/frameworks/ux-design-expert.md +0 -244
  55. package/autopm/.claude/agents/integration/message-queue-engineer.md +0 -794
  56. package/autopm/.claude/agents/languages/README.md +0 -50
  57. package/autopm/.claude/agents/languages/bash-scripting-expert.md +0 -541
  58. package/autopm/.claude/agents/languages/javascript-frontend-engineer.md +0 -197
  59. package/autopm/.claude/agents/languages/nodejs-backend-engineer.md +0 -226
  60. package/autopm/.claude/agents/languages/python-backend-engineer.md +0 -214
  61. package/autopm/.claude/agents/languages/python-backend-expert.md +0 -289
  62. package/autopm/.claude/agents/testing/frontend-testing-engineer.md +0 -395
  63. package/autopm/.claude/commands/ai/langgraph-workflow.md +0 -65
  64. package/autopm/.claude/commands/ai/openai-chat.md +0 -65
  65. package/autopm/.claude/commands/azure/COMMANDS.md +0 -107
  66. package/autopm/.claude/commands/azure/COMMAND_MAPPING.md +0 -252
  67. package/autopm/.claude/commands/azure/INTEGRATION_FIX.md +0 -103
  68. package/autopm/.claude/commands/azure/README.md +0 -246
  69. package/autopm/.claude/commands/azure/active-work.md +0 -198
  70. package/autopm/.claude/commands/azure/aliases.md +0 -143
  71. package/autopm/.claude/commands/azure/blocked-items.md +0 -287
  72. package/autopm/.claude/commands/azure/clean.md +0 -93
  73. package/autopm/.claude/commands/azure/docs-query.md +0 -48
  74. package/autopm/.claude/commands/azure/feature-decompose.md +0 -380
  75. package/autopm/.claude/commands/azure/feature-list.md +0 -61
  76. package/autopm/.claude/commands/azure/feature-new.md +0 -115
  77. package/autopm/.claude/commands/azure/feature-show.md +0 -205
  78. package/autopm/.claude/commands/azure/feature-start.md +0 -130
  79. package/autopm/.claude/commands/azure/fix-integration-example.md +0 -93
  80. package/autopm/.claude/commands/azure/help.md +0 -150
  81. package/autopm/.claude/commands/azure/import-us.md +0 -269
  82. package/autopm/.claude/commands/azure/init.md +0 -211
  83. package/autopm/.claude/commands/azure/next-task.md +0 -262
  84. package/autopm/.claude/commands/azure/search.md +0 -160
  85. package/autopm/.claude/commands/azure/sprint-status.md +0 -235
  86. package/autopm/.claude/commands/azure/standup.md +0 -260
  87. package/autopm/.claude/commands/azure/sync-all.md +0 -99
  88. package/autopm/.claude/commands/azure/task-analyze.md +0 -186
  89. package/autopm/.claude/commands/azure/task-close.md +0 -329
  90. package/autopm/.claude/commands/azure/task-edit.md +0 -145
  91. package/autopm/.claude/commands/azure/task-list.md +0 -263
  92. package/autopm/.claude/commands/azure/task-new.md +0 -84
  93. package/autopm/.claude/commands/azure/task-reopen.md +0 -79
  94. package/autopm/.claude/commands/azure/task-show.md +0 -126
  95. package/autopm/.claude/commands/azure/task-start.md +0 -301
  96. package/autopm/.claude/commands/azure/task-status.md +0 -65
  97. package/autopm/.claude/commands/azure/task-sync.md +0 -67
  98. package/autopm/.claude/commands/azure/us-edit.md +0 -164
  99. package/autopm/.claude/commands/azure/us-list.md +0 -202
  100. package/autopm/.claude/commands/azure/us-new.md +0 -265
  101. package/autopm/.claude/commands/azure/us-parse.md +0 -253
  102. package/autopm/.claude/commands/azure/us-show.md +0 -188
  103. package/autopm/.claude/commands/azure/us-status.md +0 -320
  104. package/autopm/.claude/commands/azure/validate.md +0 -86
  105. package/autopm/.claude/commands/azure/work-item-sync.md +0 -47
  106. package/autopm/.claude/commands/cloud/infra-deploy.md +0 -38
  107. package/autopm/.claude/commands/github/workflow-create.md +0 -42
  108. package/autopm/.claude/commands/infrastructure/ssh-security.md +0 -65
  109. package/autopm/.claude/commands/infrastructure/traefik-setup.md +0 -65
  110. package/autopm/.claude/commands/kubernetes/deploy.md +0 -37
  111. package/autopm/.claude/commands/playwright/test-scaffold.md +0 -38
  112. package/autopm/.claude/commands/pm/blocked.md +0 -28
  113. package/autopm/.claude/commands/pm/clean.md +0 -119
  114. package/autopm/.claude/commands/pm/context-create.md +0 -136
  115. package/autopm/.claude/commands/pm/context-prime.md +0 -170
  116. package/autopm/.claude/commands/pm/context-update.md +0 -292
  117. package/autopm/.claude/commands/pm/context.md +0 -28
  118. package/autopm/.claude/commands/pm/epic-close.md +0 -86
  119. package/autopm/.claude/commands/pm/epic-decompose.md +0 -370
  120. package/autopm/.claude/commands/pm/epic-edit.md +0 -83
  121. package/autopm/.claude/commands/pm/epic-list.md +0 -30
  122. package/autopm/.claude/commands/pm/epic-merge.md +0 -222
  123. package/autopm/.claude/commands/pm/epic-oneshot.md +0 -119
  124. package/autopm/.claude/commands/pm/epic-refresh.md +0 -119
  125. package/autopm/.claude/commands/pm/epic-show.md +0 -28
  126. package/autopm/.claude/commands/pm/epic-split.md +0 -120
  127. package/autopm/.claude/commands/pm/epic-start.md +0 -195
  128. package/autopm/.claude/commands/pm/epic-status.md +0 -28
  129. package/autopm/.claude/commands/pm/epic-sync-modular.md +0 -338
  130. package/autopm/.claude/commands/pm/epic-sync-original.md +0 -473
  131. package/autopm/.claude/commands/pm/epic-sync.md +0 -486
  132. package/autopm/.claude/commands/pm/help.md +0 -28
  133. package/autopm/.claude/commands/pm/import.md +0 -115
  134. package/autopm/.claude/commands/pm/in-progress.md +0 -28
  135. package/autopm/.claude/commands/pm/init.md +0 -28
  136. package/autopm/.claude/commands/pm/issue-analyze.md +0 -202
  137. package/autopm/.claude/commands/pm/issue-close.md +0 -119
  138. package/autopm/.claude/commands/pm/issue-edit.md +0 -93
  139. package/autopm/.claude/commands/pm/issue-reopen.md +0 -87
  140. package/autopm/.claude/commands/pm/issue-show.md +0 -41
  141. package/autopm/.claude/commands/pm/issue-start.md +0 -234
  142. package/autopm/.claude/commands/pm/issue-status.md +0 -95
  143. package/autopm/.claude/commands/pm/issue-sync.md +0 -411
  144. package/autopm/.claude/commands/pm/next.md +0 -28
  145. package/autopm/.claude/commands/pm/prd-edit.md +0 -82
  146. package/autopm/.claude/commands/pm/prd-list.md +0 -28
  147. package/autopm/.claude/commands/pm/prd-new.md +0 -55
  148. package/autopm/.claude/commands/pm/prd-parse.md +0 -42
  149. package/autopm/.claude/commands/pm/prd-status.md +0 -28
  150. package/autopm/.claude/commands/pm/search.md +0 -28
  151. package/autopm/.claude/commands/pm/standup.md +0 -28
  152. package/autopm/.claude/commands/pm/status.md +0 -28
  153. package/autopm/.claude/commands/pm/sync.md +0 -99
  154. package/autopm/.claude/commands/pm/test-reference-update.md +0 -151
  155. package/autopm/.claude/commands/pm/validate.md +0 -28
  156. package/autopm/.claude/commands/pm/what-next.md +0 -28
  157. package/autopm/.claude/commands/python/api-scaffold.md +0 -50
  158. package/autopm/.claude/commands/python/docs-query.md +0 -48
  159. package/autopm/.claude/commands/react/app-scaffold.md +0 -50
  160. package/autopm/.claude/commands/testing/prime.md +0 -314
  161. package/autopm/.claude/commands/testing/run.md +0 -125
  162. package/autopm/.claude/commands/ui/bootstrap-scaffold.md +0 -65
  163. package/autopm/.claude/commands/ui/tailwind-system.md +0 -64
  164. package/autopm/.claude/rules/ai-integration-patterns.md +0 -219
  165. package/autopm/.claude/rules/ci-cd-kubernetes-strategy.md +0 -25
  166. package/autopm/.claude/rules/database-management-strategy.md +0 -17
  167. package/autopm/.claude/rules/database-pipeline.md +0 -94
  168. package/autopm/.claude/rules/devops-troubleshooting-playbook.md +0 -450
  169. package/autopm/.claude/rules/docker-first-development.md +0 -404
  170. package/autopm/.claude/rules/infrastructure-pipeline.md +0 -128
  171. package/autopm/.claude/rules/performance-guidelines.md +0 -403
  172. package/autopm/.claude/rules/ui-development-standards.md +0 -281
  173. package/autopm/.claude/rules/ui-framework-rules.md +0 -151
  174. package/autopm/.claude/rules/ux-design-rules.md +0 -209
  175. package/autopm/.claude/rules/visual-testing.md +0 -223
  176. package/autopm/.claude/scripts/azure/README.md +0 -192
  177. package/autopm/.claude/scripts/azure/active-work.js +0 -524
  178. package/autopm/.claude/scripts/azure/active-work.sh +0 -20
  179. package/autopm/.claude/scripts/azure/blocked.js +0 -520
  180. package/autopm/.claude/scripts/azure/blocked.sh +0 -20
  181. package/autopm/.claude/scripts/azure/daily.js +0 -533
  182. package/autopm/.claude/scripts/azure/daily.sh +0 -20
  183. package/autopm/.claude/scripts/azure/dashboard.js +0 -970
  184. package/autopm/.claude/scripts/azure/dashboard.sh +0 -20
  185. package/autopm/.claude/scripts/azure/feature-list.js +0 -254
  186. package/autopm/.claude/scripts/azure/feature-list.sh +0 -20
  187. package/autopm/.claude/scripts/azure/feature-show.js +0 -7
  188. package/autopm/.claude/scripts/azure/feature-show.sh +0 -20
  189. package/autopm/.claude/scripts/azure/feature-status.js +0 -604
  190. package/autopm/.claude/scripts/azure/feature-status.sh +0 -20
  191. package/autopm/.claude/scripts/azure/help.js +0 -342
  192. package/autopm/.claude/scripts/azure/help.sh +0 -20
  193. package/autopm/.claude/scripts/azure/next-task.js +0 -508
  194. package/autopm/.claude/scripts/azure/next-task.sh +0 -20
  195. package/autopm/.claude/scripts/azure/search.js +0 -469
  196. package/autopm/.claude/scripts/azure/search.sh +0 -20
  197. package/autopm/.claude/scripts/azure/setup.js +0 -745
  198. package/autopm/.claude/scripts/azure/setup.sh +0 -20
  199. package/autopm/.claude/scripts/azure/sprint-report.js +0 -1012
  200. package/autopm/.claude/scripts/azure/sprint-report.sh +0 -20
  201. package/autopm/.claude/scripts/azure/sync.js +0 -563
  202. package/autopm/.claude/scripts/azure/sync.sh +0 -20
  203. package/autopm/.claude/scripts/azure/us-list.js +0 -210
  204. package/autopm/.claude/scripts/azure/us-list.sh +0 -20
  205. package/autopm/.claude/scripts/azure/us-status.js +0 -238
  206. package/autopm/.claude/scripts/azure/us-status.sh +0 -20
  207. package/autopm/.claude/scripts/azure/validate.js +0 -626
  208. package/autopm/.claude/scripts/azure/validate.sh +0 -20
  209. package/autopm/.claude/scripts/azure/wrapper-template.sh +0 -20
  210. package/autopm/.claude/scripts/github/dependency-tracker.js +0 -554
  211. package/autopm/.claude/scripts/github/dependency-validator.js +0 -545
  212. package/autopm/.claude/scripts/github/dependency-visualizer.js +0 -477
  213. package/autopm/.claude/scripts/pm/analytics.js +0 -425
  214. package/autopm/.claude/scripts/pm/blocked.js +0 -164
  215. package/autopm/.claude/scripts/pm/blocked.sh +0 -78
  216. package/autopm/.claude/scripts/pm/clean.js +0 -464
  217. package/autopm/.claude/scripts/pm/context-create.js +0 -216
  218. package/autopm/.claude/scripts/pm/context-prime.js +0 -335
  219. package/autopm/.claude/scripts/pm/context-update.js +0 -344
  220. package/autopm/.claude/scripts/pm/context.js +0 -338
  221. package/autopm/.claude/scripts/pm/epic-close.js +0 -347
  222. package/autopm/.claude/scripts/pm/epic-edit.js +0 -382
  223. package/autopm/.claude/scripts/pm/epic-list.js +0 -273
  224. package/autopm/.claude/scripts/pm/epic-list.sh +0 -109
  225. package/autopm/.claude/scripts/pm/epic-show.js +0 -291
  226. package/autopm/.claude/scripts/pm/epic-show.sh +0 -105
  227. package/autopm/.claude/scripts/pm/epic-split.js +0 -522
  228. package/autopm/.claude/scripts/pm/epic-start/epic-start.js +0 -183
  229. package/autopm/.claude/scripts/pm/epic-start/epic-start.sh +0 -94
  230. package/autopm/.claude/scripts/pm/epic-status.js +0 -291
  231. package/autopm/.claude/scripts/pm/epic-status.sh +0 -104
  232. package/autopm/.claude/scripts/pm/epic-sync/README.md +0 -208
  233. package/autopm/.claude/scripts/pm/epic-sync/create-epic-issue.sh +0 -77
  234. package/autopm/.claude/scripts/pm/epic-sync/create-task-issues.sh +0 -86
  235. package/autopm/.claude/scripts/pm/epic-sync/update-epic-file.sh +0 -79
  236. package/autopm/.claude/scripts/pm/epic-sync/update-references.sh +0 -89
  237. package/autopm/.claude/scripts/pm/epic-sync.sh +0 -137
  238. package/autopm/.claude/scripts/pm/help.js +0 -92
  239. package/autopm/.claude/scripts/pm/help.sh +0 -90
  240. package/autopm/.claude/scripts/pm/in-progress.js +0 -178
  241. package/autopm/.claude/scripts/pm/in-progress.sh +0 -93
  242. package/autopm/.claude/scripts/pm/init.js +0 -321
  243. package/autopm/.claude/scripts/pm/init.sh +0 -178
  244. package/autopm/.claude/scripts/pm/issue-close.js +0 -232
  245. package/autopm/.claude/scripts/pm/issue-edit.js +0 -310
  246. package/autopm/.claude/scripts/pm/issue-show.js +0 -272
  247. package/autopm/.claude/scripts/pm/issue-start.js +0 -181
  248. package/autopm/.claude/scripts/pm/issue-sync/format-comment.sh +0 -468
  249. package/autopm/.claude/scripts/pm/issue-sync/gather-updates.sh +0 -460
  250. package/autopm/.claude/scripts/pm/issue-sync/post-comment.sh +0 -330
  251. package/autopm/.claude/scripts/pm/issue-sync/preflight-validation.sh +0 -348
  252. package/autopm/.claude/scripts/pm/issue-sync/update-frontmatter.sh +0 -387
  253. package/autopm/.claude/scripts/pm/lib/README.md +0 -85
  254. package/autopm/.claude/scripts/pm/lib/epic-discovery.js +0 -119
  255. package/autopm/.claude/scripts/pm/lib/logger.js +0 -78
  256. package/autopm/.claude/scripts/pm/next.js +0 -189
  257. package/autopm/.claude/scripts/pm/next.sh +0 -72
  258. package/autopm/.claude/scripts/pm/optimize.js +0 -407
  259. package/autopm/.claude/scripts/pm/pr-create.js +0 -337
  260. package/autopm/.claude/scripts/pm/pr-list.js +0 -257
  261. package/autopm/.claude/scripts/pm/prd-list.js +0 -242
  262. package/autopm/.claude/scripts/pm/prd-list.sh +0 -103
  263. package/autopm/.claude/scripts/pm/prd-new.js +0 -684
  264. package/autopm/.claude/scripts/pm/prd-parse.js +0 -547
  265. package/autopm/.claude/scripts/pm/prd-status.js +0 -152
  266. package/autopm/.claude/scripts/pm/prd-status.sh +0 -63
  267. package/autopm/.claude/scripts/pm/release.js +0 -460
  268. package/autopm/.claude/scripts/pm/search.js +0 -192
  269. package/autopm/.claude/scripts/pm/search.sh +0 -89
  270. package/autopm/.claude/scripts/pm/standup.js +0 -362
  271. package/autopm/.claude/scripts/pm/standup.sh +0 -95
  272. package/autopm/.claude/scripts/pm/status.js +0 -148
  273. package/autopm/.claude/scripts/pm/status.sh +0 -59
  274. package/autopm/.claude/scripts/pm/sync-batch.js +0 -337
  275. package/autopm/.claude/scripts/pm/sync.js +0 -343
  276. package/autopm/.claude/scripts/pm/template-list.js +0 -141
  277. package/autopm/.claude/scripts/pm/template-new.js +0 -366
  278. package/autopm/.claude/scripts/pm/validate.js +0 -274
  279. package/autopm/.claude/scripts/pm/validate.sh +0 -106
  280. package/autopm/.claude/scripts/pm/what-next.js +0 -660
  281. 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;