opencastle 0.26.1 → 0.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/bin/cli.mjs +10 -0
- package/dist/cli/agents.d.ts +3 -0
- package/dist/cli/agents.d.ts.map +1 -0
- package/dist/cli/agents.js +161 -0
- package/dist/cli/agents.js.map +1 -0
- package/dist/cli/baselines.d.ts +3 -0
- package/dist/cli/baselines.d.ts.map +1 -0
- package/dist/cli/baselines.js +128 -0
- package/dist/cli/baselines.js.map +1 -0
- package/dist/cli/convoy/engine.d.ts +68 -2
- package/dist/cli/convoy/engine.d.ts.map +1 -1
- package/dist/cli/convoy/engine.js +2102 -26
- package/dist/cli/convoy/engine.js.map +1 -1
- package/dist/cli/convoy/engine.test.js +1572 -70
- package/dist/cli/convoy/engine.test.js.map +1 -1
- package/dist/cli/convoy/events.d.ts +4 -1
- package/dist/cli/convoy/events.d.ts.map +1 -1
- package/dist/cli/convoy/events.js +74 -13
- package/dist/cli/convoy/events.js.map +1 -1
- package/dist/cli/convoy/events.test.js +154 -27
- package/dist/cli/convoy/events.test.js.map +1 -1
- package/dist/cli/convoy/expertise.d.ts +16 -0
- package/dist/cli/convoy/expertise.d.ts.map +1 -0
- package/dist/cli/convoy/expertise.js +121 -0
- package/dist/cli/convoy/expertise.js.map +1 -0
- package/dist/cli/convoy/expertise.test.d.ts +2 -0
- package/dist/cli/convoy/expertise.test.d.ts.map +1 -0
- package/dist/cli/convoy/expertise.test.js +96 -0
- package/dist/cli/convoy/expertise.test.js.map +1 -0
- package/dist/cli/convoy/export.test.js +1 -0
- package/dist/cli/convoy/export.test.js.map +1 -1
- package/dist/cli/convoy/formula.d.ts +19 -0
- package/dist/cli/convoy/formula.d.ts.map +1 -0
- package/dist/cli/convoy/formula.js +142 -0
- package/dist/cli/convoy/formula.js.map +1 -0
- package/dist/cli/convoy/formula.test.d.ts +2 -0
- package/dist/cli/convoy/formula.test.d.ts.map +1 -0
- package/dist/cli/convoy/formula.test.js +342 -0
- package/dist/cli/convoy/formula.test.js.map +1 -0
- package/dist/cli/convoy/gates.d.ts +128 -0
- package/dist/cli/convoy/gates.d.ts.map +1 -0
- package/dist/cli/convoy/gates.js +606 -0
- package/dist/cli/convoy/gates.js.map +1 -0
- package/dist/cli/convoy/gates.test.d.ts +2 -0
- package/dist/cli/convoy/gates.test.d.ts.map +1 -0
- package/dist/cli/convoy/gates.test.js +976 -0
- package/dist/cli/convoy/gates.test.js.map +1 -0
- package/dist/cli/convoy/health.d.ts +11 -0
- package/dist/cli/convoy/health.d.ts.map +1 -1
- package/dist/cli/convoy/health.js +54 -0
- package/dist/cli/convoy/health.js.map +1 -1
- package/dist/cli/convoy/health.test.js +56 -1
- package/dist/cli/convoy/health.test.js.map +1 -1
- package/dist/cli/convoy/issues.d.ts +8 -0
- package/dist/cli/convoy/issues.d.ts.map +1 -0
- package/dist/cli/convoy/issues.js +98 -0
- package/dist/cli/convoy/issues.js.map +1 -0
- package/dist/cli/convoy/issues.test.d.ts +2 -0
- package/dist/cli/convoy/issues.test.d.ts.map +1 -0
- package/dist/cli/convoy/issues.test.js +107 -0
- package/dist/cli/convoy/issues.test.js.map +1 -0
- package/dist/cli/convoy/knowledge.d.ts +5 -0
- package/dist/cli/convoy/knowledge.d.ts.map +1 -0
- package/dist/cli/convoy/knowledge.js +116 -0
- package/dist/cli/convoy/knowledge.js.map +1 -0
- package/dist/cli/convoy/knowledge.test.d.ts +2 -0
- package/dist/cli/convoy/knowledge.test.d.ts.map +1 -0
- package/dist/cli/convoy/knowledge.test.js +87 -0
- package/dist/cli/convoy/knowledge.test.js.map +1 -0
- package/dist/cli/convoy/lessons.d.ts +17 -0
- package/dist/cli/convoy/lessons.d.ts.map +1 -0
- package/dist/cli/convoy/lessons.js +149 -0
- package/dist/cli/convoy/lessons.js.map +1 -0
- package/dist/cli/convoy/lessons.test.d.ts +2 -0
- package/dist/cli/convoy/lessons.test.d.ts.map +1 -0
- package/dist/cli/convoy/lessons.test.js +135 -0
- package/dist/cli/convoy/lessons.test.js.map +1 -0
- package/dist/cli/convoy/lock.d.ts +13 -0
- package/dist/cli/convoy/lock.d.ts.map +1 -0
- package/dist/cli/convoy/lock.js +88 -0
- package/dist/cli/convoy/lock.js.map +1 -0
- package/dist/cli/convoy/lock.test.d.ts +2 -0
- package/dist/cli/convoy/lock.test.d.ts.map +1 -0
- package/dist/cli/convoy/lock.test.js +136 -0
- package/dist/cli/convoy/lock.test.js.map +1 -0
- package/dist/cli/convoy/merge.d.ts +4 -0
- package/dist/cli/convoy/merge.d.ts.map +1 -1
- package/dist/cli/convoy/merge.js +18 -1
- package/dist/cli/convoy/merge.js.map +1 -1
- package/dist/cli/convoy/merge.test.js +6 -7
- package/dist/cli/convoy/merge.test.js.map +1 -1
- package/dist/cli/convoy/partition.d.ts +51 -0
- package/dist/cli/convoy/partition.d.ts.map +1 -0
- package/dist/cli/convoy/partition.js +186 -0
- package/dist/cli/convoy/partition.js.map +1 -0
- package/dist/cli/convoy/partition.test.d.ts +2 -0
- package/dist/cli/convoy/partition.test.d.ts.map +1 -0
- package/dist/cli/convoy/partition.test.js +315 -0
- package/dist/cli/convoy/partition.test.js.map +1 -0
- package/dist/cli/convoy/pipeline.test.js +6 -0
- package/dist/cli/convoy/pipeline.test.js.map +1 -1
- package/dist/cli/convoy/store.d.ts +47 -5
- package/dist/cli/convoy/store.d.ts.map +1 -1
- package/dist/cli/convoy/store.js +525 -19
- package/dist/cli/convoy/store.js.map +1 -1
- package/dist/cli/convoy/store.test.js +1345 -12
- package/dist/cli/convoy/store.test.js.map +1 -1
- package/dist/cli/convoy/types.d.ts +156 -2
- package/dist/cli/convoy/types.d.ts.map +1 -1
- package/dist/cli/destroy.d.ts +3 -0
- package/dist/cli/destroy.d.ts.map +1 -0
- package/dist/cli/destroy.js +69 -0
- package/dist/cli/destroy.js.map +1 -0
- package/dist/cli/destroy.test.d.ts +2 -0
- package/dist/cli/destroy.test.d.ts.map +1 -0
- package/dist/cli/destroy.test.js +116 -0
- package/dist/cli/destroy.test.js.map +1 -0
- package/dist/cli/gitignore.d.ts +9 -0
- package/dist/cli/gitignore.d.ts.map +1 -1
- package/dist/cli/gitignore.js +29 -0
- package/dist/cli/gitignore.js.map +1 -1
- package/dist/cli/plan.d.ts +3 -0
- package/dist/cli/plan.d.ts.map +1 -0
- package/dist/cli/plan.js +288 -0
- package/dist/cli/plan.js.map +1 -0
- package/dist/cli/run/adapters/claude.d.ts +2 -0
- package/dist/cli/run/adapters/claude.d.ts.map +1 -1
- package/dist/cli/run/adapters/claude.js +89 -49
- package/dist/cli/run/adapters/claude.js.map +1 -1
- package/dist/cli/run/adapters/claude.test.d.ts +2 -0
- package/dist/cli/run/adapters/claude.test.d.ts.map +1 -0
- package/dist/cli/run/adapters/claude.test.js +205 -0
- package/dist/cli/run/adapters/claude.test.js.map +1 -0
- package/dist/cli/run/adapters/copilot.d.ts +1 -0
- package/dist/cli/run/adapters/copilot.d.ts.map +1 -1
- package/dist/cli/run/adapters/copilot.js +84 -46
- package/dist/cli/run/adapters/copilot.js.map +1 -1
- package/dist/cli/run/adapters/copilot.test.d.ts +2 -0
- package/dist/cli/run/adapters/copilot.test.d.ts.map +1 -0
- package/dist/cli/run/adapters/copilot.test.js +195 -0
- package/dist/cli/run/adapters/copilot.test.js.map +1 -0
- package/dist/cli/run/adapters/cursor.d.ts +1 -0
- package/dist/cli/run/adapters/cursor.d.ts.map +1 -1
- package/dist/cli/run/adapters/cursor.js +83 -47
- package/dist/cli/run/adapters/cursor.js.map +1 -1
- package/dist/cli/run/adapters/cursor.test.d.ts +2 -0
- package/dist/cli/run/adapters/cursor.test.d.ts.map +1 -0
- package/dist/cli/run/adapters/cursor.test.js +129 -0
- package/dist/cli/run/adapters/cursor.test.js.map +1 -0
- package/dist/cli/run/adapters/opencode.d.ts +1 -0
- package/dist/cli/run/adapters/opencode.d.ts.map +1 -1
- package/dist/cli/run/adapters/opencode.js +81 -47
- package/dist/cli/run/adapters/opencode.js.map +1 -1
- package/dist/cli/run/adapters/opencode.test.d.ts +2 -0
- package/dist/cli/run/adapters/opencode.test.d.ts.map +1 -0
- package/dist/cli/run/adapters/opencode.test.js +119 -0
- package/dist/cli/run/adapters/opencode.test.js.map +1 -0
- package/dist/cli/run/executor.js +1 -1
- package/dist/cli/run/executor.js.map +1 -1
- package/dist/cli/run/schema.d.ts.map +1 -1
- package/dist/cli/run/schema.js +245 -4
- package/dist/cli/run/schema.js.map +1 -1
- package/dist/cli/run/schema.test.js +669 -0
- package/dist/cli/run/schema.test.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +362 -22
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/types.d.ts +85 -2
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/types.js.map +1 -1
- package/dist/cli/watch.d.ts +15 -0
- package/dist/cli/watch.d.ts.map +1 -0
- package/dist/cli/watch.js +279 -0
- package/dist/cli/watch.js.map +1 -0
- package/package.json +1 -1
- package/src/cli/agents.ts +177 -0
- package/src/cli/baselines.ts +143 -0
- package/src/cli/convoy/engine.test.ts +1839 -70
- package/src/cli/convoy/engine.ts +2417 -38
- package/src/cli/convoy/events.test.ts +179 -38
- package/src/cli/convoy/events.ts +88 -16
- package/src/cli/convoy/expertise.test.ts +128 -0
- package/src/cli/convoy/expertise.ts +163 -0
- package/src/cli/convoy/export.test.ts +1 -0
- package/src/cli/convoy/formula.test.ts +405 -0
- package/src/cli/convoy/formula.ts +174 -0
- package/src/cli/convoy/gates.test.ts +1169 -0
- package/src/cli/convoy/gates.ts +774 -0
- package/src/cli/convoy/health.test.ts +64 -2
- package/src/cli/convoy/health.ts +80 -2
- package/src/cli/convoy/issues.test.ts +143 -0
- package/src/cli/convoy/issues.ts +136 -0
- package/src/cli/convoy/knowledge.test.ts +101 -0
- package/src/cli/convoy/knowledge.ts +132 -0
- package/src/cli/convoy/lessons.test.ts +188 -0
- package/src/cli/convoy/lessons.ts +164 -0
- package/src/cli/convoy/lock.test.ts +181 -0
- package/src/cli/convoy/lock.ts +103 -0
- package/src/cli/convoy/merge.test.ts +6 -7
- package/src/cli/convoy/merge.ts +19 -1
- package/src/cli/convoy/partition.test.ts +423 -0
- package/src/cli/convoy/partition.ts +232 -0
- package/src/cli/convoy/pipeline.test.ts +6 -0
- package/src/cli/convoy/store.test.ts +1512 -14
- package/src/cli/convoy/store.ts +676 -30
- package/src/cli/convoy/types.ts +170 -1
- package/src/cli/destroy.test.ts +141 -0
- package/src/cli/destroy.ts +88 -0
- package/src/cli/gitignore.ts +36 -0
- package/src/cli/plan.ts +316 -0
- package/src/cli/run/adapters/claude.test.ts +234 -0
- package/src/cli/run/adapters/claude.ts +45 -5
- package/src/cli/run/adapters/copilot.test.ts +224 -0
- package/src/cli/run/adapters/copilot.ts +34 -4
- package/src/cli/run/adapters/cursor.test.ts +144 -0
- package/src/cli/run/adapters/cursor.ts +33 -2
- package/src/cli/run/adapters/opencode.test.ts +135 -0
- package/src/cli/run/adapters/opencode.ts +30 -2
- package/src/cli/run/executor.ts +1 -1
- package/src/cli/run/schema.test.ts +758 -0
- package/src/cli/run/schema.ts +300 -25
- package/src/cli/run.ts +341 -21
- package/src/cli/types.ts +86 -1
- package/src/cli/watch.ts +298 -0
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
package/src/cli/run/schema.ts
CHANGED
|
@@ -34,30 +34,89 @@ export function parseTimeout(timeout: string): number {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
interface RawSpec {
|
|
37
|
-
name?:
|
|
38
|
-
concurrency?:
|
|
39
|
-
on_failure?:
|
|
40
|
-
adapter?:
|
|
41
|
-
tasks?:
|
|
42
|
-
version?:
|
|
43
|
-
defaults?:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
37
|
+
name?: string
|
|
38
|
+
concurrency?: number | string
|
|
39
|
+
on_failure?: string
|
|
40
|
+
adapter?: string
|
|
41
|
+
tasks?: RawTask[]
|
|
42
|
+
version?: number
|
|
43
|
+
defaults?: {
|
|
44
|
+
timeout?: string
|
|
45
|
+
model?: string
|
|
46
|
+
max_retries?: number
|
|
47
|
+
agent?: string
|
|
48
|
+
adapter?: string
|
|
49
|
+
mcp_servers?: unknown[]
|
|
50
|
+
mcp_approve_all?: unknown
|
|
51
|
+
mcp_server_approval_timeout?: unknown
|
|
52
|
+
built_in_gates?: unknown
|
|
53
|
+
browser_test?: unknown
|
|
54
|
+
review?: unknown
|
|
55
|
+
reviewer_model?: unknown
|
|
56
|
+
review_budget?: unknown
|
|
57
|
+
on_review_budget_exceeded?: unknown
|
|
58
|
+
max_concurrent_reviews?: unknown
|
|
59
|
+
review_heuristics?: unknown
|
|
60
|
+
max_swarm_concurrency?: unknown
|
|
61
|
+
}
|
|
62
|
+
gates?: string[]
|
|
63
|
+
gate_retries?: number
|
|
64
|
+
branch?: string
|
|
65
|
+
depends_on_convoy?: string[]
|
|
66
|
+
guard?: unknown
|
|
48
67
|
}
|
|
49
68
|
|
|
50
69
|
interface RawTask {
|
|
51
|
-
id?:
|
|
52
|
-
prompt?:
|
|
53
|
-
agent?:
|
|
54
|
-
timeout?:
|
|
55
|
-
depends_on?:
|
|
56
|
-
files?:
|
|
57
|
-
description?:
|
|
58
|
-
model?:
|
|
59
|
-
max_retries?:
|
|
60
|
-
adapter?:
|
|
70
|
+
id?: string
|
|
71
|
+
prompt?: string
|
|
72
|
+
agent?: string
|
|
73
|
+
timeout?: string
|
|
74
|
+
depends_on?: string[]
|
|
75
|
+
files?: string[]
|
|
76
|
+
description?: string
|
|
77
|
+
model?: string
|
|
78
|
+
max_retries?: number
|
|
79
|
+
adapter?: string
|
|
80
|
+
built_in_gates?: unknown
|
|
81
|
+
browser_test?: unknown
|
|
82
|
+
review?: string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Validate a browser_test config object for the given prefix.
|
|
87
|
+
*/
|
|
88
|
+
function validateBrowserTestConfig(value: unknown, prefix: string, errors: string[]): void {
|
|
89
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
90
|
+
errors.push(`\`${prefix}\` must be an object`)
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
const bt = value as Record<string, unknown>
|
|
94
|
+
if (bt.urls === undefined) {
|
|
95
|
+
errors.push(`\`${prefix}.urls\` is required`)
|
|
96
|
+
} else if (!Array.isArray(bt.urls) || (bt.urls as unknown[]).length === 0) {
|
|
97
|
+
errors.push(`\`${prefix}.urls\` must be a non-empty array`)
|
|
98
|
+
} else if (!(bt.urls as unknown[]).every((u) => typeof u === 'string')) {
|
|
99
|
+
errors.push(`\`${prefix}.urls\` must be an array of strings`)
|
|
100
|
+
}
|
|
101
|
+
if (bt.check_console_errors !== undefined && typeof bt.check_console_errors !== 'boolean') {
|
|
102
|
+
errors.push(`\`${prefix}.check_console_errors\` must be a boolean`)
|
|
103
|
+
}
|
|
104
|
+
if (bt.visual_diff_threshold !== undefined) {
|
|
105
|
+
const vdt = Number(bt.visual_diff_threshold)
|
|
106
|
+
if (!Number.isFinite(vdt) || vdt < 0 || vdt > 1) {
|
|
107
|
+
errors.push(`\`${prefix}.visual_diff_threshold\` must be a number between 0 and 1`)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (bt.a11y !== undefined && typeof bt.a11y !== 'boolean') {
|
|
111
|
+
errors.push(`\`${prefix}.a11y\` must be a boolean`)
|
|
112
|
+
}
|
|
113
|
+
const validSeverities = ['critical', 'serious', 'moderate', 'minor']
|
|
114
|
+
if (bt.severity_threshold !== undefined && !validSeverities.includes(bt.severity_threshold as string)) {
|
|
115
|
+
errors.push(`\`${prefix}.severity_threshold\` must be one of: ${validSeverities.join(', ')}`)
|
|
116
|
+
}
|
|
117
|
+
if (bt.baselines_dir !== undefined && typeof bt.baselines_dir !== 'string') {
|
|
118
|
+
errors.push(`\`${prefix}.baselines_dir\` must be a string`)
|
|
119
|
+
}
|
|
61
120
|
}
|
|
62
121
|
|
|
63
122
|
/**
|
|
@@ -79,9 +138,13 @@ export function validateSpec(spec: unknown): ValidationResult {
|
|
|
79
138
|
|
|
80
139
|
// Concurrency
|
|
81
140
|
if (s.concurrency !== undefined) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
141
|
+
if (s.concurrency === 'auto') {
|
|
142
|
+
// valid swarm mode
|
|
143
|
+
} else {
|
|
144
|
+
const c = Number(s.concurrency)
|
|
145
|
+
if (!Number.isInteger(c) || c < 1 || c > 50) {
|
|
146
|
+
errors.push('`concurrency` must be an integer between 1 and 50, or "auto"')
|
|
147
|
+
}
|
|
85
148
|
}
|
|
86
149
|
}
|
|
87
150
|
|
|
@@ -142,6 +205,158 @@ export function validateSpec(spec: unknown): ValidationResult {
|
|
|
142
205
|
if (d.adapter !== undefined && typeof d.adapter !== 'string') {
|
|
143
206
|
errors.push('`defaults.adapter` must be a string')
|
|
144
207
|
}
|
|
208
|
+
|
|
209
|
+
// MCP servers validation (Phase 19.7)
|
|
210
|
+
if (d.mcp_servers !== undefined) {
|
|
211
|
+
if (!Array.isArray(d.mcp_servers)) {
|
|
212
|
+
errors.push('`defaults.mcp_servers` must be an array')
|
|
213
|
+
} else {
|
|
214
|
+
for (let j = 0; j < (d.mcp_servers as unknown[]).length; j++) {
|
|
215
|
+
const server = (d.mcp_servers as unknown[])[j]
|
|
216
|
+
const sp = `defaults.mcp_servers[${j}]`
|
|
217
|
+
if (!server || typeof server !== 'object' || Array.isArray(server)) {
|
|
218
|
+
errors.push(`\`${sp}\` must be an object`)
|
|
219
|
+
continue
|
|
220
|
+
}
|
|
221
|
+
const srv = server as Record<string, unknown>
|
|
222
|
+
if (!srv.name || typeof srv.name !== 'string') {
|
|
223
|
+
errors.push(`\`${sp}.name\` is required and must be a string`)
|
|
224
|
+
}
|
|
225
|
+
if (!srv.type || typeof srv.type !== 'string') {
|
|
226
|
+
errors.push(`\`${sp}.type\` is required and must be a string`)
|
|
227
|
+
}
|
|
228
|
+
if (srv.local !== undefined && typeof srv.local !== 'boolean') {
|
|
229
|
+
errors.push(`\`${sp}.local\` must be a boolean`)
|
|
230
|
+
}
|
|
231
|
+
if (srv.command !== undefined && typeof srv.command !== 'string') {
|
|
232
|
+
errors.push(`\`${sp}.command\` must be a string`)
|
|
233
|
+
}
|
|
234
|
+
if (srv.args !== undefined) {
|
|
235
|
+
if (!Array.isArray(srv.args) || !(srv.args as unknown[]).every(a => typeof a === 'string')) {
|
|
236
|
+
errors.push(`\`${sp}.args\` must be an array of strings`)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (srv.url !== undefined && typeof srv.url !== 'string') {
|
|
240
|
+
errors.push(`\`${sp}.url\` must be a string`)
|
|
241
|
+
}
|
|
242
|
+
if (srv.config !== undefined) {
|
|
243
|
+
if (!srv.config || typeof srv.config !== 'object' || Array.isArray(srv.config)) {
|
|
244
|
+
errors.push(`\`${sp}.config\` must be an object`)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// mcp_approve_all validation
|
|
252
|
+
if (d.mcp_approve_all !== undefined && typeof d.mcp_approve_all !== 'boolean') {
|
|
253
|
+
errors.push('`defaults.mcp_approve_all` must be a boolean')
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// mcp_server_approval_timeout validation
|
|
257
|
+
if (d.mcp_server_approval_timeout !== undefined) {
|
|
258
|
+
const t = Number(d.mcp_server_approval_timeout)
|
|
259
|
+
if (!Number.isFinite(t) || t <= 0) {
|
|
260
|
+
errors.push('`defaults.mcp_server_approval_timeout` must be a number greater than 0')
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// built_in_gates validation
|
|
265
|
+
if (d.built_in_gates !== undefined) {
|
|
266
|
+
if (!d.built_in_gates || typeof d.built_in_gates !== 'object' || Array.isArray(d.built_in_gates)) {
|
|
267
|
+
errors.push('`defaults.built_in_gates` must be an object')
|
|
268
|
+
} else {
|
|
269
|
+
const bg = d.built_in_gates as Record<string, unknown>
|
|
270
|
+
const boolOrAutoFields = ['secret_scan', 'blast_radius', 'dependency_audit', 'regression_test', 'browser_test'] as const
|
|
271
|
+
for (const field of boolOrAutoFields) {
|
|
272
|
+
if (bg[field] !== undefined && typeof bg[field] !== 'boolean' && bg[field] !== 'auto') {
|
|
273
|
+
errors.push(`\`defaults.built_in_gates.${field}\` must be a boolean or "auto"`)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (bg.gate_timeout !== undefined) {
|
|
277
|
+
const gt = Number(bg.gate_timeout)
|
|
278
|
+
if (!Number.isFinite(gt) || gt <= 0) {
|
|
279
|
+
errors.push('`defaults.built_in_gates.gate_timeout` must be a number greater than 0')
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// browser_test config validation
|
|
286
|
+
if (d.browser_test !== undefined) {
|
|
287
|
+
validateBrowserTestConfig(d.browser_test, 'defaults.browser_test', errors)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// review validation
|
|
291
|
+
const VALID_REVIEW = ['auto', 'fast', 'panel', 'none']
|
|
292
|
+
if (d.review !== undefined && !VALID_REVIEW.includes(d.review as string)) {
|
|
293
|
+
errors.push('`defaults.review` must be one of: ' + VALID_REVIEW.join(', '))
|
|
294
|
+
}
|
|
295
|
+
if (d.reviewer_model !== undefined && typeof d.reviewer_model !== 'string') {
|
|
296
|
+
errors.push('`defaults.reviewer_model` must be a string')
|
|
297
|
+
}
|
|
298
|
+
if (d.review_budget !== undefined) {
|
|
299
|
+
const rb = Number(d.review_budget)
|
|
300
|
+
if (!Number.isInteger(rb) || rb < 1) {
|
|
301
|
+
errors.push('`defaults.review_budget` must be a positive integer')
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const VALID_BUDGET_EXCEEDED = ['skip', 'downgrade', 'stop']
|
|
305
|
+
if (
|
|
306
|
+
d.on_review_budget_exceeded !== undefined &&
|
|
307
|
+
!VALID_BUDGET_EXCEEDED.includes(d.on_review_budget_exceeded as string)
|
|
308
|
+
) {
|
|
309
|
+
errors.push(
|
|
310
|
+
'`defaults.on_review_budget_exceeded` must be one of: ' + VALID_BUDGET_EXCEEDED.join(', '),
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
if (d.max_concurrent_reviews !== undefined) {
|
|
314
|
+
const mcr = Number(d.max_concurrent_reviews)
|
|
315
|
+
if (!Number.isInteger(mcr) || mcr < 1) {
|
|
316
|
+
errors.push('`defaults.max_concurrent_reviews` must be a positive integer')
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (d.review_heuristics !== undefined) {
|
|
320
|
+
if (
|
|
321
|
+
!d.review_heuristics ||
|
|
322
|
+
typeof d.review_heuristics !== 'object' ||
|
|
323
|
+
Array.isArray(d.review_heuristics)
|
|
324
|
+
) {
|
|
325
|
+
errors.push('`defaults.review_heuristics` must be an object')
|
|
326
|
+
} else {
|
|
327
|
+
const rh = d.review_heuristics as Record<string, unknown>
|
|
328
|
+
for (const field of ['panel_paths', 'panel_agents', 'auto_pass_agents'] as const) {
|
|
329
|
+
if (rh[field] !== undefined) {
|
|
330
|
+
if (
|
|
331
|
+
!Array.isArray(rh[field]) ||
|
|
332
|
+
!(rh[field] as unknown[]).every((v) => typeof v === 'string')
|
|
333
|
+
) {
|
|
334
|
+
errors.push(
|
|
335
|
+
`\`defaults.review_heuristics.${field}\` must be an array of strings`,
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (rh.auto_pass_max_lines !== undefined) {
|
|
341
|
+
const apl = Number(rh.auto_pass_max_lines)
|
|
342
|
+
if (!Number.isInteger(apl) || apl < 1) {
|
|
343
|
+
errors.push('`defaults.review_heuristics.auto_pass_max_lines` must be a positive integer')
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (rh.auto_pass_max_files !== undefined) {
|
|
347
|
+
const apf = Number(rh.auto_pass_max_files)
|
|
348
|
+
if (!Number.isInteger(apf) || apf < 1) {
|
|
349
|
+
errors.push('`defaults.review_heuristics.auto_pass_max_files` must be a positive integer')
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (d.max_swarm_concurrency !== undefined) {
|
|
355
|
+
const msc = Number(d.max_swarm_concurrency)
|
|
356
|
+
if (!Number.isInteger(msc) || msc < 1 || msc > 50) {
|
|
357
|
+
errors.push('`defaults.max_swarm_concurrency` must be an integer between 1 and 50')
|
|
358
|
+
}
|
|
359
|
+
}
|
|
145
360
|
}
|
|
146
361
|
}
|
|
147
362
|
|
|
@@ -168,6 +383,28 @@ export function validateSpec(spec: unknown): ValidationResult {
|
|
|
168
383
|
errors.push('`branch` must be a string')
|
|
169
384
|
}
|
|
170
385
|
|
|
386
|
+
// guard config validation
|
|
387
|
+
if (s.guard !== undefined) {
|
|
388
|
+
const g = s.guard as Record<string, unknown>
|
|
389
|
+
if (!g || typeof g !== 'object' || Array.isArray(g)) {
|
|
390
|
+
errors.push('`guard` must be an object')
|
|
391
|
+
} else {
|
|
392
|
+
if (g.enabled !== undefined && typeof g.enabled !== 'boolean') {
|
|
393
|
+
errors.push('`guard.enabled` must be a boolean')
|
|
394
|
+
}
|
|
395
|
+
if (g.agent !== undefined && typeof g.agent !== 'string') {
|
|
396
|
+
errors.push('`guard.agent` must be a string')
|
|
397
|
+
}
|
|
398
|
+
if (g.checks !== undefined) {
|
|
399
|
+
if (!Array.isArray(g.checks)) {
|
|
400
|
+
errors.push('`guard.checks` must be an array of non-empty strings')
|
|
401
|
+
} else if (!(g.checks as unknown[]).every((c) => typeof c === 'string' && c.length > 0)) {
|
|
402
|
+
errors.push('`guard.checks` must be an array of non-empty strings')
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
171
408
|
// Tasks: required unless this is a version:2 pipeline spec with depends_on_convoy
|
|
172
409
|
const isPipeline =
|
|
173
410
|
s.version === 2 &&
|
|
@@ -264,6 +501,40 @@ export function validateSpec(spec: unknown): ValidationResult {
|
|
|
264
501
|
if (task.adapter !== undefined && typeof task.adapter !== 'string') {
|
|
265
502
|
errors.push(`${prefix}: \`adapter\` must be a string`)
|
|
266
503
|
}
|
|
504
|
+
|
|
505
|
+
// built_in_gates (task-level)
|
|
506
|
+
if (task.built_in_gates !== undefined) {
|
|
507
|
+
if (!task.built_in_gates || typeof task.built_in_gates !== 'object' || Array.isArray(task.built_in_gates)) {
|
|
508
|
+
errors.push(`${prefix}: \`built_in_gates\` must be an object`)
|
|
509
|
+
} else {
|
|
510
|
+
const bg = task.built_in_gates as Record<string, unknown>
|
|
511
|
+
const boolOrAutoFields = ['secret_scan', 'blast_radius', 'dependency_audit', 'regression_test', 'browser_test'] as const
|
|
512
|
+
for (const field of boolOrAutoFields) {
|
|
513
|
+
if (bg[field] !== undefined && typeof bg[field] !== 'boolean' && bg[field] !== 'auto') {
|
|
514
|
+
errors.push(`${prefix}: \`built_in_gates.${field}\` must be a boolean or "auto"`)
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (bg.gate_timeout !== undefined) {
|
|
518
|
+
const gt = Number(bg.gate_timeout)
|
|
519
|
+
if (!Number.isFinite(gt) || gt <= 0) {
|
|
520
|
+
errors.push(`${prefix}: \`built_in_gates.gate_timeout\` must be a number greater than 0`)
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// browser_test (task-level)
|
|
527
|
+
if (task.browser_test !== undefined) {
|
|
528
|
+
validateBrowserTestConfig(task.browser_test, `${prefix}.browser_test`, errors)
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// review (task-level)
|
|
532
|
+
if (task.review !== undefined) {
|
|
533
|
+
const VALID_TASK_REVIEW = ['auto', 'fast', 'panel', 'none']
|
|
534
|
+
if (!VALID_TASK_REVIEW.includes(task.review as string)) {
|
|
535
|
+
errors.push(`${prefix}: \`review\` must be one of: ${VALID_TASK_REVIEW.join(', ')}`)
|
|
536
|
+
}
|
|
537
|
+
}
|
|
267
538
|
}
|
|
268
539
|
|
|
269
540
|
// DAG cycle detection
|
|
@@ -324,7 +595,7 @@ function detectCycles(tasks: Array<{ id: string; depends_on?: string[] }>): stri
|
|
|
324
595
|
*/
|
|
325
596
|
export function applyDefaults(spec: Record<string, unknown>): TaskSpec {
|
|
326
597
|
const s = spec as Record<string, unknown>
|
|
327
|
-
s.concurrency = s.concurrency !== undefined ? Number(s.concurrency) : 1
|
|
598
|
+
s.concurrency = s.concurrency === 'auto' ? 'auto' : (s.concurrency !== undefined ? Number(s.concurrency) : 1)
|
|
328
599
|
s.on_failure = (s.on_failure as string) || 'continue'
|
|
329
600
|
// Leave adapter empty so run.ts can auto-detect the best available CLI
|
|
330
601
|
s.adapter = (s.adapter as string) || ''
|
|
@@ -358,6 +629,10 @@ export function applyDefaults(spec: Record<string, unknown>): TaskSpec {
|
|
|
358
629
|
if (task.adapter === undefined && d.adapter !== undefined) {
|
|
359
630
|
task.adapter = d.adapter
|
|
360
631
|
}
|
|
632
|
+
// review: task-level overrides defaults
|
|
633
|
+
if (task.review === undefined && d.review !== undefined) {
|
|
634
|
+
task.review = d.review
|
|
635
|
+
}
|
|
361
636
|
}
|
|
362
637
|
|
|
363
638
|
return s as unknown as TaskSpec
|