@hyperdrive.bot/bmad-workflow 1.0.18 → 1.0.19

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 (98) hide show
  1. package/dist/commands/config/show.js +8 -2
  2. package/dist/commands/decompose.js +26 -5
  3. package/dist/commands/epics/create.d.ts +1 -0
  4. package/dist/commands/mcp/add.d.ts +16 -0
  5. package/dist/commands/mcp/add.js +77 -0
  6. package/dist/commands/mcp/credential/get.d.ts +14 -0
  7. package/dist/commands/mcp/credential/get.js +35 -0
  8. package/dist/commands/mcp/credential/list.d.ts +17 -0
  9. package/dist/commands/mcp/credential/list.js +67 -0
  10. package/dist/commands/mcp/credential/remove.d.ts +18 -0
  11. package/dist/commands/mcp/credential/remove.js +84 -0
  12. package/dist/commands/mcp/credential/set.d.ts +16 -0
  13. package/dist/commands/mcp/credential/set.js +41 -0
  14. package/dist/commands/mcp/credential/validate.d.ts +12 -0
  15. package/dist/commands/mcp/credential/validate.js +150 -0
  16. package/dist/commands/mcp/list.d.ts +17 -0
  17. package/dist/commands/mcp/list.js +80 -0
  18. package/dist/commands/mcp/logs.d.ts +15 -0
  19. package/dist/commands/mcp/logs.js +64 -0
  20. package/dist/commands/mcp/preset.d.ts +15 -0
  21. package/dist/commands/mcp/preset.js +84 -0
  22. package/dist/commands/mcp/remove.d.ts +14 -0
  23. package/dist/commands/mcp/remove.js +36 -0
  24. package/dist/commands/mcp/start.d.ts +12 -0
  25. package/dist/commands/mcp/start.js +80 -0
  26. package/dist/commands/mcp/status.d.ts +30 -0
  27. package/dist/commands/mcp/status.js +180 -0
  28. package/dist/commands/mcp/stop.d.ts +12 -0
  29. package/dist/commands/mcp/stop.js +47 -0
  30. package/dist/commands/stories/create.d.ts +1 -0
  31. package/dist/commands/stories/develop.d.ts +1 -0
  32. package/dist/commands/stories/qa.js +5 -2
  33. package/dist/commands/stories/review.d.ts +124 -0
  34. package/dist/commands/stories/review.js +516 -0
  35. package/dist/commands/workflow.d.ts +8 -0
  36. package/dist/commands/workflow.js +110 -2
  37. package/dist/mcp/types.d.ts +99 -0
  38. package/dist/mcp/types.js +7 -0
  39. package/dist/mcp/utils/docker-utils.d.ts +56 -0
  40. package/dist/mcp/utils/docker-utils.js +108 -0
  41. package/dist/mcp/utils/template-loader.d.ts +21 -0
  42. package/dist/mcp/utils/template-loader.js +60 -0
  43. package/dist/models/agent-options.d.ts +10 -1
  44. package/dist/models/workflow-config.d.ts +77 -0
  45. package/dist/models/workflow-result.d.ts +7 -0
  46. package/dist/services/agents/claude-agent-runner.js +19 -3
  47. package/dist/services/file-system/path-resolver.d.ts +10 -0
  48. package/dist/services/file-system/path-resolver.js +12 -0
  49. package/dist/services/mcp/mcp-config-manager.d.ts +54 -0
  50. package/dist/services/mcp/mcp-config-manager.js +146 -0
  51. package/dist/services/mcp/mcp-context-injector.d.ts +92 -0
  52. package/dist/services/mcp/mcp-context-injector.js +168 -0
  53. package/dist/services/mcp/mcp-credential-manager.d.ts +48 -0
  54. package/dist/services/mcp/mcp-credential-manager.js +124 -0
  55. package/dist/services/mcp/mcp-health-checker.d.ts +56 -0
  56. package/dist/services/mcp/mcp-health-checker.js +162 -0
  57. package/dist/services/mcp/types/health-types.d.ts +31 -0
  58. package/dist/services/mcp/types/health-types.js +7 -0
  59. package/dist/services/orchestration/dependency-graph-executor.js +1 -1
  60. package/dist/services/orchestration/task-decomposition-service.d.ts +2 -1
  61. package/dist/services/orchestration/task-decomposition-service.js +90 -36
  62. package/dist/services/orchestration/workflow-orchestrator.d.ts +54 -2
  63. package/dist/services/orchestration/workflow-orchestrator.js +303 -17
  64. package/dist/services/review/ai-review-scanner.d.ts +66 -0
  65. package/dist/services/review/ai-review-scanner.js +142 -0
  66. package/dist/services/review/coderabbit-scanner.d.ts +25 -0
  67. package/dist/services/review/coderabbit-scanner.js +31 -0
  68. package/dist/services/review/index.d.ts +20 -0
  69. package/dist/services/review/index.js +15 -0
  70. package/dist/services/review/lint-scanner.d.ts +46 -0
  71. package/dist/services/review/lint-scanner.js +172 -0
  72. package/dist/services/review/review-config.d.ts +62 -0
  73. package/dist/services/review/review-config.js +91 -0
  74. package/dist/services/review/review-phase-executor.d.ts +69 -0
  75. package/dist/services/review/review-phase-executor.js +152 -0
  76. package/dist/services/review/review-queue.d.ts +98 -0
  77. package/dist/services/review/review-queue.js +174 -0
  78. package/dist/services/review/review-reporter.d.ts +94 -0
  79. package/dist/services/review/review-reporter.js +386 -0
  80. package/dist/services/review/scanner-factory.d.ts +42 -0
  81. package/dist/services/review/scanner-factory.js +60 -0
  82. package/dist/services/review/self-heal-loop.d.ts +58 -0
  83. package/dist/services/review/self-heal-loop.js +132 -0
  84. package/dist/services/review/severity-classifier.d.ts +17 -0
  85. package/dist/services/review/severity-classifier.js +314 -0
  86. package/dist/services/review/tech-debt-tracker.d.ts +52 -0
  87. package/dist/services/review/tech-debt-tracker.js +245 -0
  88. package/dist/services/review/types.d.ts +93 -0
  89. package/dist/services/review/types.js +23 -0
  90. package/dist/services/validation/config-validator.d.ts +84 -0
  91. package/dist/services/validation/config-validator.js +78 -0
  92. package/dist/utils/credential-utils.d.ts +14 -0
  93. package/dist/utils/credential-utils.js +19 -0
  94. package/dist/utils/duration.d.ts +41 -0
  95. package/dist/utils/duration.js +89 -0
  96. package/dist/utils/shared-flags.d.ts +1 -0
  97. package/dist/utils/shared-flags.js +11 -2
  98. package/package.json +4 -2
@@ -0,0 +1,180 @@
1
+ /**
2
+ * MCP Status Command
3
+ *
4
+ * Displays a structured dashboard showing gateway health, per-server health
5
+ * with latency, credential status, and current active preset name.
6
+ */
7
+ import { Command } from '@oclif/core';
8
+ import chalk from 'chalk';
9
+ import { DEFAULT_GATEWAY_URL, formatDockerError, isDockerAvailable } from '../../mcp/utils/docker-utils.js';
10
+ import { createLogger } from '../../utils/logger.js';
11
+ const logger = createLogger({ namespace: 'commands:mcp:status' });
12
+ /**
13
+ * Format latency for display
14
+ */
15
+ function formatLatency(ms) {
16
+ if (ms === null)
17
+ return '—';
18
+ if (ms >= 1000)
19
+ return `${(ms / 1000).toFixed(1)}s`;
20
+ return `${ms}ms`;
21
+ }
22
+ /**
23
+ * Format health status with icon
24
+ */
25
+ function formatStatus(status) {
26
+ switch (status) {
27
+ case 'disabled': {
28
+ return chalk.gray('✗ disabled');
29
+ }
30
+ case 'healthy': {
31
+ return chalk.green('✓ healthy');
32
+ }
33
+ case 'offline': {
34
+ return chalk.red('✗ offline');
35
+ }
36
+ case 'unhealthy': {
37
+ return chalk.yellow('! unhealthy');
38
+ }
39
+ default: {
40
+ return chalk.dim(status);
41
+ }
42
+ }
43
+ }
44
+ /**
45
+ * Render the status dashboard to the terminal
46
+ */
47
+ function renderDashboard(healthReport, presetName, credentials) {
48
+ const lines = [];
49
+ // Header
50
+ lines.push(chalk.bold('MCP Gateway Status'));
51
+ lines.push(chalk.dim('=================='));
52
+ // Gateway line
53
+ const gwStatus = formatStatus(healthReport.gateway.status);
54
+ const gwLatency = formatLatency(healthReport.gateway.latency);
55
+ const gwLine = healthReport.gateway.latency !== null
56
+ ? `${gwStatus} (${gwLatency})`
57
+ : gwStatus;
58
+ lines.push(`Gateway: ${healthReport.gateway.url} ${gwLine}`);
59
+ lines.push(`Preset: ${presetName}`);
60
+ // Servers section
61
+ lines.push('');
62
+ lines.push(chalk.bold('Servers:'));
63
+ if (healthReport.servers.length === 0) {
64
+ lines.push(chalk.dim(' No servers configured'));
65
+ }
66
+ else {
67
+ // Calculate column widths for alignment
68
+ const maxNameLen = Math.max(...healthReport.servers.map((s) => s.name.length));
69
+ for (const server of healthReport.servers) {
70
+ const name = server.name.padEnd(maxNameLen);
71
+ const status = formatStatus(server.status);
72
+ const latency = server.status === 'disabled' ? '—' : formatLatency(server.latency);
73
+ const padLatency = `(${latency})`.padEnd(10);
74
+ const useCase = server.useCase || '';
75
+ lines.push(` ${name} ${status} ${padLatency} ${useCase}`);
76
+ }
77
+ }
78
+ // Credentials section
79
+ if (credentials.length > 0) {
80
+ lines.push('');
81
+ lines.push(chalk.bold('Credentials:'));
82
+ const maxKeyLen = Math.max(...credentials.map((c) => c.key.length));
83
+ for (const cred of credentials) {
84
+ const key = cred.key.padEnd(maxKeyLen);
85
+ if (cred.configured) {
86
+ lines.push(` ${key} ${chalk.green('✓ configured')}`);
87
+ }
88
+ else if (!cred.requiredByPreset) {
89
+ lines.push(` ${key} ${chalk.dim('— not set (not needed for current preset)')}`);
90
+ }
91
+ else {
92
+ lines.push(` ${key} ${chalk.yellow('✗ not set')}`);
93
+ }
94
+ }
95
+ }
96
+ return lines.join('\n');
97
+ }
98
+ export default class McpStatus extends Command {
99
+ static description = 'Display MCP gateway status dashboard with server health and credentials';
100
+ static examples = [
101
+ '<%= config.bin %> mcp status',
102
+ ];
103
+ async run() {
104
+ logger.info('Checking MCP gateway status');
105
+ // 1. Check Docker availability
106
+ const dockerCheck = isDockerAvailable();
107
+ if (!dockerCheck.available) {
108
+ this.error(formatDockerError(dockerCheck), { exit: 1 });
109
+ return;
110
+ }
111
+ // 2. Gather status data
112
+ // NOTE: McpHealthChecker, McpConfigManager, and McpCredentialManager
113
+ // are provided by Epic 1 / Story 2.4. Until those are implemented,
114
+ // we fall back to a direct health check and sensible defaults.
115
+ const healthReport = await this.getHealthReport();
116
+ const presetName = this.getPresetName();
117
+ const credentials = this.getCredentials();
118
+ // 3. Render dashboard
119
+ const dashboard = renderDashboard(healthReport, presetName, credentials);
120
+ this.log(dashboard);
121
+ }
122
+ /**
123
+ * Get credentials status — delegates to McpCredentialManager when available
124
+ */
125
+ getCredentials() {
126
+ try {
127
+ // TODO: Replace with McpCredentialManager.list() when Epic 1 is complete
128
+ return [];
129
+ }
130
+ catch {
131
+ return [];
132
+ }
133
+ }
134
+ /**
135
+ * Gather health report — delegates to McpHealthChecker when available
136
+ */
137
+ async getHealthReport() {
138
+ // TODO: Replace with McpHealthChecker.checkAll() when Story 2.4 is complete
139
+ // For now, do a simple gateway health check
140
+ const gatewayUrl = DEFAULT_GATEWAY_URL;
141
+ const healthUrl = `${gatewayUrl}/health`;
142
+ try {
143
+ const start = Date.now();
144
+ const response = await fetch(healthUrl, { signal: AbortSignal.timeout(5000) });
145
+ const latency = Date.now() - start;
146
+ return {
147
+ gateway: {
148
+ latency,
149
+ status: response.ok ? 'healthy' : 'unhealthy',
150
+ url: gatewayUrl,
151
+ },
152
+ servers: [],
153
+ };
154
+ }
155
+ catch {
156
+ return {
157
+ gateway: {
158
+ latency: null,
159
+ status: 'offline',
160
+ url: gatewayUrl,
161
+ },
162
+ servers: [],
163
+ };
164
+ }
165
+ }
166
+ /**
167
+ * Get current preset name — delegates to McpConfigManager when available
168
+ */
169
+ getPresetName() {
170
+ try {
171
+ // TODO: Replace with McpConfigManager.getPresetName() when Epic 1 is complete
172
+ return 'unknown';
173
+ }
174
+ catch {
175
+ return 'unknown';
176
+ }
177
+ }
178
+ }
179
+ // Export the render function for testing
180
+ export { renderDashboard };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * MCP Stop Command
3
+ *
4
+ * Shuts down the Docker MCP gateway via docker compose down
5
+ * and confirms shutdown to the user.
6
+ */
7
+ import { Command } from '@oclif/core';
8
+ export default class McpStop extends Command {
9
+ static description: string;
10
+ static examples: string[];
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * MCP Stop Command
3
+ *
4
+ * Shuts down the Docker MCP gateway via docker compose down
5
+ * and confirms shutdown to the user.
6
+ */
7
+ import { execSync } from 'node:child_process';
8
+ import { Command } from '@oclif/core';
9
+ import chalk from 'chalk';
10
+ import { dockerComposeFileExists, formatDockerError, getDockerComposeFilePath, isDockerAvailable, } from '../../mcp/utils/docker-utils.js';
11
+ import { createLogger } from '../../utils/logger.js';
12
+ const logger = createLogger({ namespace: 'commands:mcp:stop' });
13
+ export default class McpStop extends Command {
14
+ static description = 'Stop the Docker MCP gateway';
15
+ static examples = [
16
+ '<%= config.bin %> mcp stop',
17
+ ];
18
+ async run() {
19
+ logger.info('Stopping MCP gateway');
20
+ // 1. Check Docker availability
21
+ const dockerCheck = isDockerAvailable();
22
+ if (!dockerCheck.available) {
23
+ this.error(formatDockerError(dockerCheck), { exit: 1 });
24
+ return;
25
+ }
26
+ // 2. Verify compose file exists
27
+ const composePath = getDockerComposeFilePath();
28
+ if (!dockerComposeFileExists()) {
29
+ this.error(`Docker Compose file not found at: ${composePath}\nNothing to stop — the gateway has not been initialized.`, { exit: 1 });
30
+ return;
31
+ }
32
+ // 3. Shut down gateway
33
+ this.log(chalk.dim('Stopping MCP gateway containers...'));
34
+ try {
35
+ execSync(`docker compose -f "${composePath}" down`, { stdio: 'pipe', timeout: 60_000 });
36
+ this.log('');
37
+ this.log(chalk.green.bold('✓ MCP Gateway stopped successfully'));
38
+ logger.info('Gateway stopped');
39
+ }
40
+ catch (error) {
41
+ const err = error;
42
+ const stderr = err.stderr ? err.stderr.toString() : err.message;
43
+ logger.error('Failed to stop gateway: %s', stderr);
44
+ this.error(`Failed to stop MCP gateway:\n${stderr}`, { exit: 1 });
45
+ }
46
+ }
47
+ }
@@ -39,6 +39,7 @@ export default class StoriesCreateCommand extends Command {
39
39
  provider: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
40
40
  task: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
41
41
  timeout: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
42
+ 'review-timeout': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
42
43
  'max-retries': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
43
44
  'retry-backoff': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
44
45
  };
@@ -41,6 +41,7 @@ export default class StoriesDevelopCommand extends Command {
41
41
  provider: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
42
42
  task: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
43
43
  timeout: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
44
+ 'review-timeout': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
44
45
  'max-retries': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
45
46
  'retry-backoff': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
46
47
  };
@@ -21,6 +21,7 @@ import { GlobMatcher } from '../../services/file-system/glob-matcher.js';
21
21
  import { PathResolver } from '../../services/file-system/path-resolver.js';
22
22
  import { StoryParserFactory } from '../../services/parsers/story-parser-factory.js';
23
23
  import * as colors from '../../utils/colors.js';
24
+ import { parseDuration } from '../../utils/duration.js';
24
25
  import { createLogger, generateCorrelationId } from '../../utils/logger.js';
25
26
  import { runAgentWithRetry } from '../../utils/retry.js';
26
27
  /**
@@ -84,9 +85,11 @@ export default class StoriesQaCommand extends Command {
84
85
  description: 'Additional context files for agents',
85
86
  multiple: true,
86
87
  }),
87
- timeout: Flags.integer({
88
+ timeout: Flags.custom({
89
+ parse: async (input) => parseDuration(input),
90
+ })({
88
91
  default: 2_700_000,
89
- description: 'Agent execution timeout in milliseconds (default: 2700000 = 45 minutes)',
92
+ description: 'Agent execution timeout accepts durations like 30s, 5m, 1h, 90m, or raw milliseconds (default: 45m)',
90
93
  helpGroup: 'Resilience',
91
94
  }),
92
95
  'agent-retries': Flags.integer({
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Stories Review Command
3
+ *
4
+ * Standalone command that discovers and reviews existing story files outside
5
+ * the full workflow pipeline. Runs automated code review (scanners + self-heal)
6
+ * on matched stories and produces PASS/FAIL verdicts.
7
+ *
8
+ * @example
9
+ * ```bash
10
+ * bmad-workflow stories review "docs/qa/stories/AUTH-*.md"
11
+ * bmad-workflow stories review "stories/*.md" --scanners ai,lint --dry-run
12
+ * bmad-workflow stories review "stories/*.md" --json
13
+ * bmad-workflow stories review "stories/*.md" --block-on CRITICAL --max-fix 5
14
+ * ```
15
+ */
16
+ import { Command } from '@oclif/core';
17
+ import { Severity } from '../../services/review/types.js';
18
+ /**
19
+ * Parse and validate --scanners flag value
20
+ *
21
+ * @param value - Comma-separated scanner names
22
+ * @returns Array of valid scanner names
23
+ * @throws Error if any scanner name is invalid
24
+ */
25
+ export declare function parseScanners(value: string): string[];
26
+ /**
27
+ * Parse and validate --block-on flag value
28
+ *
29
+ * @param value - Comma-separated severity names
30
+ * @returns Array of valid severity strings
31
+ * @throws Error if any severity is invalid
32
+ */
33
+ export declare function parseBlockOn(value: string): Severity;
34
+ /**
35
+ * Validate --max-fix is a positive integer
36
+ *
37
+ * @param value - Value to validate
38
+ * @returns The validated integer
39
+ * @throws Error if value is not a positive integer
40
+ */
41
+ export declare function validateMaxFix(value: number): number;
42
+ /**
43
+ * Stories Review Command
44
+ *
45
+ * Discovers story files via glob pattern and runs automated code review on each.
46
+ * Supports --dry-run (findings only, no fixes) and --json (structured CI output).
47
+ */
48
+ export default class StoriesReviewCommand extends Command {
49
+ static args: {
50
+ pattern: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
51
+ };
52
+ static description: string;
53
+ static examples: {
54
+ command: string;
55
+ description: string;
56
+ }[];
57
+ static flags: {
58
+ 'block-on': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
59
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
60
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
61
+ 'max-fix': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
62
+ scanners: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
63
+ agent: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
64
+ cwd: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
65
+ model: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
66
+ provider: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
67
+ task: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
68
+ timeout: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
69
+ 'review-timeout': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
70
+ 'max-retries': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
71
+ 'retry-backoff': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
72
+ };
73
+ private agentRunner;
74
+ private fileManager;
75
+ private globMatcher;
76
+ private logger;
77
+ private pathResolver;
78
+ private storyParserFactory;
79
+ /**
80
+ * Run the command
81
+ */
82
+ run(): Promise<void>;
83
+ /**
84
+ * Validate and parse all CLI flags into a typed config object
85
+ */
86
+ private validateFlags;
87
+ /**
88
+ * Discover story files matching the glob pattern
89
+ */
90
+ private discoverStories;
91
+ /**
92
+ * Review all discovered stories sequentially
93
+ */
94
+ private reviewStories;
95
+ /**
96
+ * Write review results to story files and session directory
97
+ */
98
+ private writeResults;
99
+ /**
100
+ * Output structured JSON to stdout (for --json mode)
101
+ */
102
+ private outputJson;
103
+ /**
104
+ * Display human-readable summary table
105
+ */
106
+ private displaySummary;
107
+ /**
108
+ * Initialize service dependencies
109
+ */
110
+ private initializeServices;
111
+ /**
112
+ * Extract story ID from file path
113
+ * e.g., "docs/stories/PROJ-story-1.001.md" → "PROJ-story-1.001"
114
+ */
115
+ private extractStoryId;
116
+ /**
117
+ * Group issues by severity level
118
+ */
119
+ private groupIssuesBySeverity;
120
+ /**
121
+ * Format duration in human-readable format
122
+ */
123
+ private formatDuration;
124
+ }