@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.
- package/dist/commands/config/show.js +8 -2
- package/dist/commands/decompose.js +26 -5
- package/dist/commands/epics/create.d.ts +1 -0
- package/dist/commands/mcp/add.d.ts +16 -0
- package/dist/commands/mcp/add.js +77 -0
- package/dist/commands/mcp/credential/get.d.ts +14 -0
- package/dist/commands/mcp/credential/get.js +35 -0
- package/dist/commands/mcp/credential/list.d.ts +17 -0
- package/dist/commands/mcp/credential/list.js +67 -0
- package/dist/commands/mcp/credential/remove.d.ts +18 -0
- package/dist/commands/mcp/credential/remove.js +84 -0
- package/dist/commands/mcp/credential/set.d.ts +16 -0
- package/dist/commands/mcp/credential/set.js +41 -0
- package/dist/commands/mcp/credential/validate.d.ts +12 -0
- package/dist/commands/mcp/credential/validate.js +150 -0
- package/dist/commands/mcp/list.d.ts +17 -0
- package/dist/commands/mcp/list.js +80 -0
- package/dist/commands/mcp/logs.d.ts +15 -0
- package/dist/commands/mcp/logs.js +64 -0
- package/dist/commands/mcp/preset.d.ts +15 -0
- package/dist/commands/mcp/preset.js +84 -0
- package/dist/commands/mcp/remove.d.ts +14 -0
- package/dist/commands/mcp/remove.js +36 -0
- package/dist/commands/mcp/start.d.ts +12 -0
- package/dist/commands/mcp/start.js +80 -0
- package/dist/commands/mcp/status.d.ts +30 -0
- package/dist/commands/mcp/status.js +180 -0
- package/dist/commands/mcp/stop.d.ts +12 -0
- package/dist/commands/mcp/stop.js +47 -0
- package/dist/commands/stories/create.d.ts +1 -0
- package/dist/commands/stories/develop.d.ts +1 -0
- package/dist/commands/stories/qa.js +5 -2
- package/dist/commands/stories/review.d.ts +124 -0
- package/dist/commands/stories/review.js +516 -0
- package/dist/commands/workflow.d.ts +8 -0
- package/dist/commands/workflow.js +110 -2
- package/dist/mcp/types.d.ts +99 -0
- package/dist/mcp/types.js +7 -0
- package/dist/mcp/utils/docker-utils.d.ts +56 -0
- package/dist/mcp/utils/docker-utils.js +108 -0
- package/dist/mcp/utils/template-loader.d.ts +21 -0
- package/dist/mcp/utils/template-loader.js +60 -0
- package/dist/models/agent-options.d.ts +10 -1
- package/dist/models/workflow-config.d.ts +77 -0
- package/dist/models/workflow-result.d.ts +7 -0
- package/dist/services/agents/claude-agent-runner.js +19 -3
- package/dist/services/file-system/path-resolver.d.ts +10 -0
- package/dist/services/file-system/path-resolver.js +12 -0
- package/dist/services/mcp/mcp-config-manager.d.ts +54 -0
- package/dist/services/mcp/mcp-config-manager.js +146 -0
- package/dist/services/mcp/mcp-context-injector.d.ts +92 -0
- package/dist/services/mcp/mcp-context-injector.js +168 -0
- package/dist/services/mcp/mcp-credential-manager.d.ts +48 -0
- package/dist/services/mcp/mcp-credential-manager.js +124 -0
- package/dist/services/mcp/mcp-health-checker.d.ts +56 -0
- package/dist/services/mcp/mcp-health-checker.js +162 -0
- package/dist/services/mcp/types/health-types.d.ts +31 -0
- package/dist/services/mcp/types/health-types.js +7 -0
- package/dist/services/orchestration/dependency-graph-executor.js +1 -1
- package/dist/services/orchestration/task-decomposition-service.d.ts +2 -1
- package/dist/services/orchestration/task-decomposition-service.js +90 -36
- package/dist/services/orchestration/workflow-orchestrator.d.ts +54 -2
- package/dist/services/orchestration/workflow-orchestrator.js +303 -17
- package/dist/services/review/ai-review-scanner.d.ts +66 -0
- package/dist/services/review/ai-review-scanner.js +142 -0
- package/dist/services/review/coderabbit-scanner.d.ts +25 -0
- package/dist/services/review/coderabbit-scanner.js +31 -0
- package/dist/services/review/index.d.ts +20 -0
- package/dist/services/review/index.js +15 -0
- package/dist/services/review/lint-scanner.d.ts +46 -0
- package/dist/services/review/lint-scanner.js +172 -0
- package/dist/services/review/review-config.d.ts +62 -0
- package/dist/services/review/review-config.js +91 -0
- package/dist/services/review/review-phase-executor.d.ts +69 -0
- package/dist/services/review/review-phase-executor.js +152 -0
- package/dist/services/review/review-queue.d.ts +98 -0
- package/dist/services/review/review-queue.js +174 -0
- package/dist/services/review/review-reporter.d.ts +94 -0
- package/dist/services/review/review-reporter.js +386 -0
- package/dist/services/review/scanner-factory.d.ts +42 -0
- package/dist/services/review/scanner-factory.js +60 -0
- package/dist/services/review/self-heal-loop.d.ts +58 -0
- package/dist/services/review/self-heal-loop.js +132 -0
- package/dist/services/review/severity-classifier.d.ts +17 -0
- package/dist/services/review/severity-classifier.js +314 -0
- package/dist/services/review/tech-debt-tracker.d.ts +52 -0
- package/dist/services/review/tech-debt-tracker.js +245 -0
- package/dist/services/review/types.d.ts +93 -0
- package/dist/services/review/types.js +23 -0
- package/dist/services/validation/config-validator.d.ts +84 -0
- package/dist/services/validation/config-validator.js +78 -0
- package/dist/utils/credential-utils.d.ts +14 -0
- package/dist/utils/credential-utils.js +19 -0
- package/dist/utils/duration.d.ts +41 -0
- package/dist/utils/duration.js +89 -0
- package/dist/utils/shared-flags.d.ts +1 -0
- package/dist/utils/shared-flags.js +11 -2
- package/package.json +4 -2
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review Types
|
|
3
|
+
*
|
|
4
|
+
* Core type definitions for the automated code review system.
|
|
5
|
+
* Scanners return raw output; classification into severity levels
|
|
6
|
+
* is handled separately by SeverityClassifier (Story 1.2).
|
|
7
|
+
*
|
|
8
|
+
* Severity model maps to BMAD governance:
|
|
9
|
+
* CRITICAL = NON-NEGOTIABLE (blocks pipeline)
|
|
10
|
+
* HIGH = MUST (blocks pipeline)
|
|
11
|
+
* MEDIUM = SHOULD (documented as tech debt)
|
|
12
|
+
* LOW = MAY (noted only)
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Issue severity levels aligned with BMAD governance tiers
|
|
16
|
+
*/
|
|
17
|
+
export declare enum Severity {
|
|
18
|
+
CRITICAL = "CRITICAL",
|
|
19
|
+
HIGH = "HIGH",
|
|
20
|
+
LOW = "LOW",
|
|
21
|
+
MEDIUM = "MEDIUM"
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Review verdict — PASS allows pipeline to continue, FAIL blocks it
|
|
25
|
+
*/
|
|
26
|
+
export type ReviewVerdict = 'FAIL' | 'PASS';
|
|
27
|
+
/**
|
|
28
|
+
* Context provided to scanners describing what to review
|
|
29
|
+
*/
|
|
30
|
+
export interface ReviewContext {
|
|
31
|
+
/** Base branch to diff against (e.g., "main", "develop") */
|
|
32
|
+
baseBranch: string;
|
|
33
|
+
/** List of changed file paths (relative to projectRoot) */
|
|
34
|
+
changedFiles: string[];
|
|
35
|
+
/** Absolute path to the project root directory */
|
|
36
|
+
projectRoot: string;
|
|
37
|
+
/** List of reference file paths for context (e.g., architecture docs) */
|
|
38
|
+
referenceFiles: string[];
|
|
39
|
+
/** Absolute path to the story markdown file */
|
|
40
|
+
storyFile: string;
|
|
41
|
+
/** Story identifier (e.g., "PROJ-story-1.001") */
|
|
42
|
+
storyId: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Raw output from a scanner before classification
|
|
46
|
+
*/
|
|
47
|
+
export interface RawReviewOutput {
|
|
48
|
+
/** Raw scanner output (lint text, AI response, etc.) */
|
|
49
|
+
raw: string;
|
|
50
|
+
/** Identifier of the scanner that produced this output (e.g., "eslint", "claude-ai", "coderabbit") */
|
|
51
|
+
source: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A single classified issue with severity and location
|
|
55
|
+
*/
|
|
56
|
+
export interface ClassifiedIssue {
|
|
57
|
+
/** File path where the issue was found */
|
|
58
|
+
file: string;
|
|
59
|
+
/** Suggested fix (optional) */
|
|
60
|
+
fix?: string;
|
|
61
|
+
/** Description of the issue */
|
|
62
|
+
issue: string;
|
|
63
|
+
/** Line number where the issue occurs */
|
|
64
|
+
line: number;
|
|
65
|
+
/** Severity level of the issue */
|
|
66
|
+
severity: Severity;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Final review result after classification and optional self-heal iterations
|
|
70
|
+
*/
|
|
71
|
+
export interface ReviewResult {
|
|
72
|
+
/** All classified issues found during review */
|
|
73
|
+
issues: ClassifiedIssue[];
|
|
74
|
+
/** Number of self-heal iterations performed (0 if no retries) */
|
|
75
|
+
iterations: number;
|
|
76
|
+
/** Optional summary message */
|
|
77
|
+
message?: string;
|
|
78
|
+
/** Overall verdict — PASS or FAIL */
|
|
79
|
+
verdict: ReviewVerdict;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Scanner interface — all scanner implementations (lint, AI, CodeRabbit) must implement this.
|
|
83
|
+
* Mirrors the AIProviderRunner pattern: single async method with typed input/output.
|
|
84
|
+
*/
|
|
85
|
+
export interface ReviewScanner {
|
|
86
|
+
/**
|
|
87
|
+
* Scan the given context and produce raw review output
|
|
88
|
+
*
|
|
89
|
+
* @param context - Review context describing what to scan
|
|
90
|
+
* @returns Raw output from the scanner
|
|
91
|
+
*/
|
|
92
|
+
scan(context: ReviewContext): Promise<RawReviewOutput>;
|
|
93
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review Types
|
|
3
|
+
*
|
|
4
|
+
* Core type definitions for the automated code review system.
|
|
5
|
+
* Scanners return raw output; classification into severity levels
|
|
6
|
+
* is handled separately by SeverityClassifier (Story 1.2).
|
|
7
|
+
*
|
|
8
|
+
* Severity model maps to BMAD governance:
|
|
9
|
+
* CRITICAL = NON-NEGOTIABLE (blocks pipeline)
|
|
10
|
+
* HIGH = MUST (blocks pipeline)
|
|
11
|
+
* MEDIUM = SHOULD (documented as tech debt)
|
|
12
|
+
* LOW = MAY (noted only)
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Issue severity levels aligned with BMAD governance tiers
|
|
16
|
+
*/
|
|
17
|
+
export var Severity;
|
|
18
|
+
(function (Severity) {
|
|
19
|
+
Severity["CRITICAL"] = "CRITICAL";
|
|
20
|
+
Severity["HIGH"] = "HIGH";
|
|
21
|
+
Severity["LOW"] = "LOW";
|
|
22
|
+
Severity["MEDIUM"] = "MEDIUM";
|
|
23
|
+
})(Severity || (Severity = {}));
|
|
@@ -8,6 +8,53 @@
|
|
|
8
8
|
import type pino from 'pino';
|
|
9
9
|
import { z } from 'zod';
|
|
10
10
|
import type { FileManager } from '../file-system/file-manager.js';
|
|
11
|
+
/**
|
|
12
|
+
* Zod schema for review configuration section
|
|
13
|
+
*
|
|
14
|
+
* Defines scanners, severity thresholds, self-heal limits, and path rules
|
|
15
|
+
* for automated code review in the BMAD workflow pipeline.
|
|
16
|
+
*/
|
|
17
|
+
export declare const reviewConfigSchema: z.ZodObject<{
|
|
18
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
19
|
+
pathRules: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
20
|
+
focus: z.ZodString;
|
|
21
|
+
pattern: z.ZodString;
|
|
22
|
+
}, z.core.$strip>>>;
|
|
23
|
+
scanners: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
24
|
+
ai: "ai";
|
|
25
|
+
coderabbit: "coderabbit";
|
|
26
|
+
lint: "lint";
|
|
27
|
+
}>>>;
|
|
28
|
+
selfHeal: z.ZodDefault<z.ZodObject<{
|
|
29
|
+
fixAgent: z.ZodDefault<z.ZodString>;
|
|
30
|
+
fixTimeout: z.ZodDefault<z.ZodNumber>;
|
|
31
|
+
maxIterations: z.ZodDefault<z.ZodNumber>;
|
|
32
|
+
}, z.core.$strip>>;
|
|
33
|
+
severity: z.ZodDefault<z.ZodObject<{
|
|
34
|
+
blockOn: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
35
|
+
CRITICAL: "CRITICAL";
|
|
36
|
+
HIGH: "HIGH";
|
|
37
|
+
LOW: "LOW";
|
|
38
|
+
MEDIUM: "MEDIUM";
|
|
39
|
+
}>>>;
|
|
40
|
+
documentOn: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
41
|
+
CRITICAL: "CRITICAL";
|
|
42
|
+
HIGH: "HIGH";
|
|
43
|
+
LOW: "LOW";
|
|
44
|
+
MEDIUM: "MEDIUM";
|
|
45
|
+
}>>>;
|
|
46
|
+
ignoreOn: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
47
|
+
CRITICAL: "CRITICAL";
|
|
48
|
+
HIGH: "HIGH";
|
|
49
|
+
LOW: "LOW";
|
|
50
|
+
MEDIUM: "MEDIUM";
|
|
51
|
+
}>>>;
|
|
52
|
+
}, z.core.$strip>>;
|
|
53
|
+
}, z.core.$strip>;
|
|
54
|
+
/**
|
|
55
|
+
* Inferred TypeScript type from review config Zod schema
|
|
56
|
+
*/
|
|
57
|
+
export type ReviewConfig = z.infer<typeof reviewConfigSchema>;
|
|
11
58
|
/**
|
|
12
59
|
* Zod schema for core configuration
|
|
13
60
|
*
|
|
@@ -22,6 +69,43 @@ declare const configSchema: z.ZodObject<{
|
|
|
22
69
|
qa: z.ZodOptional<z.ZodObject<{
|
|
23
70
|
qaLocation: z.ZodOptional<z.ZodString>;
|
|
24
71
|
}, z.core.$strip>>;
|
|
72
|
+
review: z.ZodOptional<z.ZodObject<{
|
|
73
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
74
|
+
pathRules: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
75
|
+
focus: z.ZodString;
|
|
76
|
+
pattern: z.ZodString;
|
|
77
|
+
}, z.core.$strip>>>;
|
|
78
|
+
scanners: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
79
|
+
ai: "ai";
|
|
80
|
+
coderabbit: "coderabbit";
|
|
81
|
+
lint: "lint";
|
|
82
|
+
}>>>;
|
|
83
|
+
selfHeal: z.ZodDefault<z.ZodObject<{
|
|
84
|
+
fixAgent: z.ZodDefault<z.ZodString>;
|
|
85
|
+
fixTimeout: z.ZodDefault<z.ZodNumber>;
|
|
86
|
+
maxIterations: z.ZodDefault<z.ZodNumber>;
|
|
87
|
+
}, z.core.$strip>>;
|
|
88
|
+
severity: z.ZodDefault<z.ZodObject<{
|
|
89
|
+
blockOn: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
90
|
+
CRITICAL: "CRITICAL";
|
|
91
|
+
HIGH: "HIGH";
|
|
92
|
+
LOW: "LOW";
|
|
93
|
+
MEDIUM: "MEDIUM";
|
|
94
|
+
}>>>;
|
|
95
|
+
documentOn: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
96
|
+
CRITICAL: "CRITICAL";
|
|
97
|
+
HIGH: "HIGH";
|
|
98
|
+
LOW: "LOW";
|
|
99
|
+
MEDIUM: "MEDIUM";
|
|
100
|
+
}>>>;
|
|
101
|
+
ignoreOn: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
102
|
+
CRITICAL: "CRITICAL";
|
|
103
|
+
HIGH: "HIGH";
|
|
104
|
+
LOW: "LOW";
|
|
105
|
+
MEDIUM: "MEDIUM";
|
|
106
|
+
}>>>;
|
|
107
|
+
}, z.core.$strip>>;
|
|
108
|
+
}, z.core.$strip>>;
|
|
25
109
|
}, z.core.$strip>;
|
|
26
110
|
/**
|
|
27
111
|
* Inferred TypeScript type from Zod schema
|
|
@@ -7,6 +7,77 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
import { ValidationError } from '../../utils/errors.js';
|
|
10
|
+
/**
|
|
11
|
+
* Valid scanner identifiers for automated code review
|
|
12
|
+
*/
|
|
13
|
+
const VALID_SCANNERS = ['ai', 'lint', 'coderabbit'];
|
|
14
|
+
/**
|
|
15
|
+
* Valid severity levels
|
|
16
|
+
*/
|
|
17
|
+
const VALID_SEVERITIES = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'];
|
|
18
|
+
/**
|
|
19
|
+
* Zod schema for review.severity configuration
|
|
20
|
+
*/
|
|
21
|
+
const severityArraySchema = z.array(z.enum(VALID_SEVERITIES, {
|
|
22
|
+
message: `Invalid severity level. Valid values: ${VALID_SEVERITIES.join(', ')}`,
|
|
23
|
+
}));
|
|
24
|
+
/**
|
|
25
|
+
* Zod schema for review.pathRules entries
|
|
26
|
+
*/
|
|
27
|
+
const pathRuleSchema = z.object({
|
|
28
|
+
focus: z.string().min(1, { message: 'Path rule focus description is required' }),
|
|
29
|
+
pattern: z.string().min(1, { message: 'Path rule pattern is required' }),
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* Zod schema for review configuration section
|
|
33
|
+
*
|
|
34
|
+
* Defines scanners, severity thresholds, self-heal limits, and path rules
|
|
35
|
+
* for automated code review in the BMAD workflow pipeline.
|
|
36
|
+
*/
|
|
37
|
+
export const reviewConfigSchema = z
|
|
38
|
+
.object({
|
|
39
|
+
enabled: z.boolean().default(false),
|
|
40
|
+
pathRules: z.array(pathRuleSchema).optional(),
|
|
41
|
+
scanners: z
|
|
42
|
+
.array(z.enum(VALID_SCANNERS, {
|
|
43
|
+
message: `Unknown scanner name. Valid values: ${VALID_SCANNERS.join(', ')}`,
|
|
44
|
+
}))
|
|
45
|
+
.default(['ai', 'lint']),
|
|
46
|
+
selfHeal: z
|
|
47
|
+
.object({
|
|
48
|
+
fixAgent: z.string().default('dev'),
|
|
49
|
+
fixTimeout: z.number().int().positive({ message: 'selfHeal.fixTimeout must be a positive integer' }).default(300_000),
|
|
50
|
+
maxIterations: z
|
|
51
|
+
.number()
|
|
52
|
+
.int()
|
|
53
|
+
.positive({ message: 'selfHeal.maxIterations must be a positive integer' })
|
|
54
|
+
.default(3),
|
|
55
|
+
})
|
|
56
|
+
.default(() => ({ fixAgent: 'dev', fixTimeout: 300_000, maxIterations: 3 })),
|
|
57
|
+
severity: z
|
|
58
|
+
.object({
|
|
59
|
+
blockOn: severityArraySchema.default(['CRITICAL', 'HIGH']),
|
|
60
|
+
documentOn: severityArraySchema.default(['MEDIUM']),
|
|
61
|
+
ignoreOn: severityArraySchema.default(['LOW']),
|
|
62
|
+
})
|
|
63
|
+
.default(() => ({
|
|
64
|
+
blockOn: ['CRITICAL', 'HIGH'],
|
|
65
|
+
documentOn: ['MEDIUM'],
|
|
66
|
+
ignoreOn: ['LOW'],
|
|
67
|
+
})),
|
|
68
|
+
})
|
|
69
|
+
.superRefine((data, ctx) => {
|
|
70
|
+
// Validate that blockOn and ignoreOn do not overlap
|
|
71
|
+
const blockSet = new Set(data.severity.blockOn);
|
|
72
|
+
const overlapping = data.severity.ignoreOn.filter((s) => blockSet.has(s));
|
|
73
|
+
if (overlapping.length > 0) {
|
|
74
|
+
ctx.addIssue({
|
|
75
|
+
code: z.ZodIssueCode.custom,
|
|
76
|
+
message: `severity.blockOn and severity.ignoreOn must not overlap. Overlapping: ${overlapping.join(', ')}`,
|
|
77
|
+
path: ['severity'],
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
});
|
|
10
81
|
/**
|
|
11
82
|
* Zod schema for core configuration
|
|
12
83
|
*
|
|
@@ -25,6 +96,7 @@ const configSchema = z.object({
|
|
|
25
96
|
qaLocation: z.string().optional(),
|
|
26
97
|
})
|
|
27
98
|
.optional(),
|
|
99
|
+
review: reviewConfigSchema.optional(),
|
|
28
100
|
});
|
|
29
101
|
/**
|
|
30
102
|
* ConfigValidator service validates configuration files against schemas
|
|
@@ -105,11 +177,17 @@ export class ConfigValidator {
|
|
|
105
177
|
devStoryLocation: 'docs/stories',
|
|
106
178
|
'prd.prdFile': 'docs/prd.md',
|
|
107
179
|
'qa.qaLocation': 'docs/qa',
|
|
180
|
+
'review.scanners': '[ai, lint]',
|
|
181
|
+
'review.severity.blockOn': '[CRITICAL, HIGH]',
|
|
182
|
+
'review.selfHeal.maxIterations': '3',
|
|
108
183
|
};
|
|
109
184
|
const fieldDisplayNames = {
|
|
110
185
|
devStoryLocation: 'Story directory path (devStoryLocation)',
|
|
111
186
|
'prd.prdFile': 'PRD file path (prd.prdFile)',
|
|
112
187
|
'qa.qaLocation': 'QA location path (qa.qaLocation)',
|
|
188
|
+
'review.scanners': 'Review scanners (review.scanners)',
|
|
189
|
+
'review.severity.blockOn': 'Review severity blockOn (review.severity.blockOn)',
|
|
190
|
+
'review.selfHeal.maxIterations': 'Self-heal max iterations (review.selfHeal.maxIterations)',
|
|
113
191
|
};
|
|
114
192
|
const example = examples[fieldName] || 'path/to/directory';
|
|
115
193
|
const displayName = fieldDisplayNames[fieldName] || fieldName;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential utility functions
|
|
3
|
+
*
|
|
4
|
+
* Shared helpers for credential display masking.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Mask a credential value for display.
|
|
8
|
+
* Shows first 3 chars + '***...***' + last 3 chars.
|
|
9
|
+
* For values shorter than 8 chars, mask entirely as '***'.
|
|
10
|
+
*
|
|
11
|
+
* @param value - The raw credential value
|
|
12
|
+
* @returns Masked string safe for display
|
|
13
|
+
*/
|
|
14
|
+
export declare function maskValue(value: string): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential utility functions
|
|
3
|
+
*
|
|
4
|
+
* Shared helpers for credential display masking.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Mask a credential value for display.
|
|
8
|
+
* Shows first 3 chars + '***...***' + last 3 chars.
|
|
9
|
+
* For values shorter than 8 chars, mask entirely as '***'.
|
|
10
|
+
*
|
|
11
|
+
* @param value - The raw credential value
|
|
12
|
+
* @returns Masked string safe for display
|
|
13
|
+
*/
|
|
14
|
+
export function maskValue(value) {
|
|
15
|
+
if (value.length < 8) {
|
|
16
|
+
return '***';
|
|
17
|
+
}
|
|
18
|
+
return `${value.slice(0, 3)}***...***${value.slice(-3)}`;
|
|
19
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Duration Parsing Utility
|
|
3
|
+
*
|
|
4
|
+
* Parses human-readable duration strings (e.g., "30s", "5m", "1h", "90m")
|
|
5
|
+
* into milliseconds. Also accepts raw millisecond numbers for backward
|
|
6
|
+
* compatibility with existing --timeout flag usage.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* parseDuration('30s') // 30_000
|
|
11
|
+
* parseDuration('5m') // 300_000
|
|
12
|
+
* parseDuration('45m') // 2_700_000
|
|
13
|
+
* parseDuration('1h') // 3_600_000
|
|
14
|
+
* parseDuration('1.5h') // 5_400_000
|
|
15
|
+
* parseDuration('2700000') // 2_700_000 (raw ms, backward compat)
|
|
16
|
+
* parseDuration(2700000) // 2_700_000 (numeric passthrough)
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Parse a duration string or number into milliseconds.
|
|
21
|
+
*
|
|
22
|
+
* Accepted formats:
|
|
23
|
+
* - `"30s"` — seconds
|
|
24
|
+
* - `"5m"` — minutes
|
|
25
|
+
* - `"1h"` — hours
|
|
26
|
+
* - `"1.5h"` — fractional units
|
|
27
|
+
* - `"2700000"` — raw milliseconds (string)
|
|
28
|
+
* - `2700000` — raw milliseconds (number)
|
|
29
|
+
*
|
|
30
|
+
* @param input - Duration string or number
|
|
31
|
+
* @returns Duration in milliseconds
|
|
32
|
+
* @throws Error if the input format is invalid or value is non-positive
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseDuration(input: number | string): number;
|
|
35
|
+
/**
|
|
36
|
+
* Format a millisecond duration into a human-readable string.
|
|
37
|
+
*
|
|
38
|
+
* @param ms - Duration in milliseconds
|
|
39
|
+
* @returns Formatted string (e.g., "45m", "1.5h", "30s")
|
|
40
|
+
*/
|
|
41
|
+
export declare function formatDuration(ms: number): string;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Duration Parsing Utility
|
|
3
|
+
*
|
|
4
|
+
* Parses human-readable duration strings (e.g., "30s", "5m", "1h", "90m")
|
|
5
|
+
* into milliseconds. Also accepts raw millisecond numbers for backward
|
|
6
|
+
* compatibility with existing --timeout flag usage.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* parseDuration('30s') // 30_000
|
|
11
|
+
* parseDuration('5m') // 300_000
|
|
12
|
+
* parseDuration('45m') // 2_700_000
|
|
13
|
+
* parseDuration('1h') // 3_600_000
|
|
14
|
+
* parseDuration('1.5h') // 5_400_000
|
|
15
|
+
* parseDuration('2700000') // 2_700_000 (raw ms, backward compat)
|
|
16
|
+
* parseDuration(2700000) // 2_700_000 (numeric passthrough)
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
/** Duration unit multipliers in milliseconds */
|
|
20
|
+
const UNIT_MS = {
|
|
21
|
+
h: 3_600_000,
|
|
22
|
+
m: 60_000,
|
|
23
|
+
s: 1_000,
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Parse a duration string or number into milliseconds.
|
|
27
|
+
*
|
|
28
|
+
* Accepted formats:
|
|
29
|
+
* - `"30s"` — seconds
|
|
30
|
+
* - `"5m"` — minutes
|
|
31
|
+
* - `"1h"` — hours
|
|
32
|
+
* - `"1.5h"` — fractional units
|
|
33
|
+
* - `"2700000"` — raw milliseconds (string)
|
|
34
|
+
* - `2700000` — raw milliseconds (number)
|
|
35
|
+
*
|
|
36
|
+
* @param input - Duration string or number
|
|
37
|
+
* @returns Duration in milliseconds
|
|
38
|
+
* @throws Error if the input format is invalid or value is non-positive
|
|
39
|
+
*/
|
|
40
|
+
export function parseDuration(input) {
|
|
41
|
+
// Numeric passthrough
|
|
42
|
+
if (typeof input === 'number') {
|
|
43
|
+
if (input <= 0 || !Number.isFinite(input)) {
|
|
44
|
+
throw new Error(`Invalid timeout value: ${input}. Must be a positive number.`);
|
|
45
|
+
}
|
|
46
|
+
return Math.round(input);
|
|
47
|
+
}
|
|
48
|
+
const trimmed = input.trim();
|
|
49
|
+
if (trimmed.length === 0) {
|
|
50
|
+
throw new Error('Timeout value cannot be empty.');
|
|
51
|
+
}
|
|
52
|
+
// Try matching duration pattern: number + unit suffix
|
|
53
|
+
const match = trimmed.match(/^(\d+(?:\.\d+)?)\s*(s|m|h)$/i);
|
|
54
|
+
if (match) {
|
|
55
|
+
const value = Number.parseFloat(match[1]);
|
|
56
|
+
const unit = match[2].toLowerCase();
|
|
57
|
+
const multiplier = UNIT_MS[unit];
|
|
58
|
+
const ms = Math.round(value * multiplier);
|
|
59
|
+
if (ms <= 0) {
|
|
60
|
+
throw new Error(`Invalid timeout value: ${trimmed}. Must result in a positive duration.`);
|
|
61
|
+
}
|
|
62
|
+
return ms;
|
|
63
|
+
}
|
|
64
|
+
// Try raw numeric string (milliseconds)
|
|
65
|
+
const numeric = Number(trimmed);
|
|
66
|
+
if (!Number.isNaN(numeric) && Number.isFinite(numeric) && numeric > 0) {
|
|
67
|
+
return Math.round(numeric);
|
|
68
|
+
}
|
|
69
|
+
throw new Error(`Invalid timeout format: "${trimmed}". ` +
|
|
70
|
+
`Expected a duration like "30s", "5m", "1h", "90m", or raw milliseconds like "2700000".`);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Format a millisecond duration into a human-readable string.
|
|
74
|
+
*
|
|
75
|
+
* @param ms - Duration in milliseconds
|
|
76
|
+
* @returns Formatted string (e.g., "45m", "1.5h", "30s")
|
|
77
|
+
*/
|
|
78
|
+
export function formatDuration(ms) {
|
|
79
|
+
if (ms >= 3_600_000) {
|
|
80
|
+
const hours = ms / 3_600_000;
|
|
81
|
+
return Number.isInteger(hours) ? `${hours}h` : `${hours.toFixed(1)}h`;
|
|
82
|
+
}
|
|
83
|
+
if (ms >= 60_000) {
|
|
84
|
+
const minutes = ms / 60_000;
|
|
85
|
+
return Number.isInteger(minutes) ? `${minutes}m` : `${minutes.toFixed(1)}m`;
|
|
86
|
+
}
|
|
87
|
+
const seconds = ms / 1_000;
|
|
88
|
+
return Number.isInteger(seconds) ? `${seconds}s` : `${seconds.toFixed(1)}s`;
|
|
89
|
+
}
|
|
@@ -27,6 +27,7 @@ export declare const agentFlags: {
|
|
|
27
27
|
provider: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
28
28
|
task: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
29
29
|
timeout: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
30
|
+
'review-timeout': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
30
31
|
'max-retries': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
31
32
|
'retry-backoff': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
32
33
|
};
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
17
|
import { Flags } from '@oclif/core';
|
|
18
|
+
import { parseDuration } from './duration.js';
|
|
18
19
|
/**
|
|
19
20
|
* Agent, Task, and Provider flags for customizing command behavior
|
|
20
21
|
*
|
|
@@ -44,9 +45,17 @@ export const agentFlags = {
|
|
|
44
45
|
description: 'Override which task command to execute (e.g., develop-story, draft, review-implementation). Defaults to command-appropriate task.',
|
|
45
46
|
helpGroup: 'Agent Customization',
|
|
46
47
|
}),
|
|
47
|
-
timeout: Flags.
|
|
48
|
+
timeout: Flags.custom({
|
|
49
|
+
parse: async (input) => parseDuration(input),
|
|
50
|
+
})({
|
|
48
51
|
default: 2_700_000,
|
|
49
|
-
description: 'Agent execution timeout
|
|
52
|
+
description: 'Agent execution timeout — accepts durations like 30s, 5m, 1h, 90m, or raw milliseconds (default: 45m)',
|
|
53
|
+
helpGroup: 'Resilience',
|
|
54
|
+
}),
|
|
55
|
+
'review-timeout': Flags.custom({
|
|
56
|
+
parse: async (input) => parseDuration(input),
|
|
57
|
+
})({
|
|
58
|
+
description: 'AI review scanner timeout — overrides --timeout for review phase only (default: 5m)',
|
|
50
59
|
helpGroup: 'Resilience',
|
|
51
60
|
}),
|
|
52
61
|
'max-retries': Flags.integer({
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperdrive.bot/bmad-workflow",
|
|
3
3
|
"description": "AI-driven development workflow orchestration CLI for BMAD projects",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.19",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "DevSquad",
|
|
7
7
|
"email": "marcelo@devsquad.email",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"url": "https://gitlab.com/dev_squad/repo/cli/bmad-orchestrator/issues"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
+
"@hyperdrive.bot/plugin-telemetry": "file:../telemetry-plugin",
|
|
17
18
|
"@oclif/core": "^4",
|
|
18
19
|
"@oclif/plugin-help": "^6",
|
|
19
20
|
"bcrypt": "^6.0.0",
|
|
@@ -89,7 +90,8 @@
|
|
|
89
90
|
"dirname": "bmad-workflow",
|
|
90
91
|
"commands": "./dist/commands",
|
|
91
92
|
"plugins": [
|
|
92
|
-
"@oclif/plugin-help"
|
|
93
|
+
"@oclif/plugin-help",
|
|
94
|
+
"@hyperdrive.bot/plugin-telemetry"
|
|
93
95
|
],
|
|
94
96
|
"topicSeparator": " "
|
|
95
97
|
},
|