@covibes/zeroshot 5.2.1 → 5.4.0
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/CHANGELOG.md +174 -189
- package/README.md +226 -195
- package/cli/commands/providers.js +149 -0
- package/cli/index.js +3145 -2366
- package/cli/lib/first-run.js +40 -3
- package/cli/message-formatters-normal.js +28 -6
- package/cluster-templates/base-templates/debug-workflow.json +24 -78
- package/cluster-templates/base-templates/full-workflow.json +99 -316
- package/cluster-templates/base-templates/single-worker.json +23 -15
- package/cluster-templates/base-templates/worker-validator.json +105 -36
- package/cluster-templates/conductor-bootstrap.json +9 -7
- package/lib/docker-config.js +14 -1
- package/lib/git-remote-utils.js +165 -0
- package/lib/id-detector.js +10 -7
- package/lib/provider-defaults.js +62 -0
- package/lib/provider-detection.js +59 -0
- package/lib/provider-names.js +57 -0
- package/lib/settings/claude-auth.js +78 -0
- package/lib/settings.js +298 -15
- package/lib/stream-json-parser.js +4 -238
- package/package.json +27 -6
- package/scripts/setup-merge-queue.sh +170 -0
- package/scripts/validate-templates.js +100 -0
- package/src/agent/agent-config.js +140 -63
- package/src/agent/agent-context-builder.js +336 -165
- package/src/agent/agent-hook-executor.js +337 -67
- package/src/agent/agent-lifecycle.js +386 -287
- package/src/agent/agent-stuck-detector.js +7 -7
- package/src/agent/agent-task-executor.js +944 -683
- package/src/agent/output-extraction.js +217 -0
- package/src/agent/output-reformatter.js +175 -0
- package/src/agent/schema-utils.js +146 -0
- package/src/agent-wrapper.js +112 -31
- package/src/agents/git-pusher-template.js +285 -0
- package/src/claude-task-runner.js +145 -44
- package/src/config-router.js +13 -13
- package/src/config-validator.js +1049 -563
- package/src/input-helpers.js +65 -0
- package/src/isolation-manager.js +499 -320
- package/src/issue-providers/README.md +305 -0
- package/src/issue-providers/azure-devops-provider.js +273 -0
- package/src/issue-providers/base-provider.js +232 -0
- package/src/issue-providers/github-provider.js +179 -0
- package/src/issue-providers/gitlab-provider.js +241 -0
- package/src/issue-providers/index.js +196 -0
- package/src/issue-providers/jira-provider.js +239 -0
- package/src/ledger.js +50 -11
- package/src/lib/safe-exec.js +88 -0
- package/src/orchestrator.js +1348 -757
- package/src/preflight.js +306 -149
- package/src/process-metrics.js +98 -56
- package/src/providers/anthropic/cli-builder.js +73 -0
- package/src/providers/anthropic/index.js +204 -0
- package/src/providers/anthropic/models.js +23 -0
- package/src/providers/anthropic/output-parser.js +177 -0
- package/src/providers/base-provider.js +251 -0
- package/src/providers/capabilities.js +60 -0
- package/src/providers/google/cli-builder.js +55 -0
- package/src/providers/google/index.js +116 -0
- package/src/providers/google/models.js +24 -0
- package/src/providers/google/output-parser.js +101 -0
- package/src/providers/index.js +91 -0
- package/src/providers/openai/cli-builder.js +133 -0
- package/src/providers/openai/index.js +136 -0
- package/src/providers/openai/models.js +21 -0
- package/src/providers/openai/output-parser.js +143 -0
- package/src/providers/opencode/cli-builder.js +42 -0
- package/src/providers/opencode/index.js +103 -0
- package/src/providers/opencode/models.js +55 -0
- package/src/providers/opencode/output-parser.js +122 -0
- package/src/schemas/sub-cluster.js +68 -39
- package/src/status-footer.js +94 -48
- package/src/sub-cluster-wrapper.js +92 -36
- package/src/task-runner.js +8 -6
- package/src/template-resolver.js +12 -9
- package/src/tui/data-poller.js +123 -99
- package/src/tui/formatters.js +4 -3
- package/src/tui/keybindings.js +259 -318
- package/src/tui/layout.js +20 -3
- package/src/tui/renderer.js +11 -21
- package/task-lib/attachable-watcher.js +150 -111
- package/task-lib/claude-recovery.js +156 -0
- package/task-lib/commands/episodes.js +105 -0
- package/task-lib/commands/list.js +3 -3
- package/task-lib/commands/logs.js +231 -189
- package/task-lib/commands/resume.js +3 -2
- package/task-lib/commands/run.js +12 -3
- package/task-lib/commands/schedules.js +111 -61
- package/task-lib/config.js +0 -2
- package/task-lib/runner.js +128 -50
- package/task-lib/scheduler.js +3 -3
- package/task-lib/store.js +464 -152
- package/task-lib/tui/formatters.js +94 -45
- package/task-lib/tui/renderer.js +13 -3
- package/task-lib/tui.js +73 -32
- package/task-lib/watcher.js +157 -100
- package/src/agents/git-pusher-agent.json +0 -20
- package/src/github.js +0 -103
|
@@ -1,244 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Parses NDJSON (newline-delimited JSON) streaming output from Claude Code.
|
|
5
|
-
* Extracts: text output, tool calls, tool results, thinking, errors.
|
|
6
|
-
*
|
|
7
|
-
* Event types from Claude Code stream-json format:
|
|
8
|
-
* - system: Session initialization
|
|
9
|
-
* - stream_event: Real-time streaming (content_block_start, content_block_delta, etc.)
|
|
10
|
-
* - assistant: Complete assistant message with content array
|
|
11
|
-
* - user: Tool results
|
|
12
|
-
* - result: Final task result
|
|
2
|
+
* Compatibility wrapper for Claude stream-json parsing.
|
|
3
|
+
* Prefer provider-specific parsers in src/providers.
|
|
13
4
|
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Parse a single JSON line and extract displayable content
|
|
17
|
-
* @param {string} line - Single line of NDJSON
|
|
18
|
-
* @returns {Object|null} Parsed event with type and content, or null if not displayable
|
|
19
|
-
*/
|
|
20
|
-
function parseStreamLine(line) {
|
|
21
|
-
const trimmed = line.trim();
|
|
22
|
-
if (!trimmed || !trimmed.startsWith('{') || !trimmed.endsWith('}')) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
let event;
|
|
27
|
-
try {
|
|
28
|
-
event = JSON.parse(trimmed);
|
|
29
|
-
} catch {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// stream_event - real-time streaming updates
|
|
34
|
-
if (event.type === 'stream_event' && event.event) {
|
|
35
|
-
return parseStreamEvent(event.event);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// assistant - complete message with content blocks
|
|
39
|
-
if (event.type === 'assistant' && event.message?.content) {
|
|
40
|
-
return parseAssistantMessage(event.message);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// user - tool result
|
|
44
|
-
if (event.type === 'user' && event.message?.content) {
|
|
45
|
-
return parseUserMessage(event.message);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// result - final task result (includes token usage and cost)
|
|
49
|
-
if (event.type === 'result') {
|
|
50
|
-
const usage = event.usage || {};
|
|
51
|
-
return {
|
|
52
|
-
type: 'result',
|
|
53
|
-
success: event.subtype === 'success',
|
|
54
|
-
result: event.result,
|
|
55
|
-
error: event.is_error ? event.result : null,
|
|
56
|
-
cost: event.total_cost_usd,
|
|
57
|
-
duration: event.duration_ms,
|
|
58
|
-
// Token usage from Claude API
|
|
59
|
-
inputTokens: usage.input_tokens || 0,
|
|
60
|
-
outputTokens: usage.output_tokens || 0,
|
|
61
|
-
cacheReadInputTokens: usage.cache_read_input_tokens || 0,
|
|
62
|
-
cacheCreationInputTokens: usage.cache_creation_input_tokens || 0,
|
|
63
|
-
// Per-model breakdown (for multi-model tasks)
|
|
64
|
-
modelUsage: event.modelUsage || null,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// system - session init (skip, not user-facing)
|
|
69
|
-
if (event.type === 'system') {
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Parse stream_event inner event
|
|
78
|
-
*/
|
|
79
|
-
function parseStreamEvent(inner) {
|
|
80
|
-
// content_block_start - tool use or text block starting
|
|
81
|
-
if (inner.type === 'content_block_start' && inner.content_block) {
|
|
82
|
-
const block = inner.content_block;
|
|
83
|
-
|
|
84
|
-
if (block.type === 'tool_use') {
|
|
85
|
-
return {
|
|
86
|
-
type: 'tool_start',
|
|
87
|
-
toolName: block.name,
|
|
88
|
-
toolId: block.id,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (block.type === 'thinking') {
|
|
93
|
-
return {
|
|
94
|
-
type: 'thinking_start',
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// text block start - usually empty, skip
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// content_block_delta - incremental content
|
|
103
|
-
if (inner.type === 'content_block_delta' && inner.delta) {
|
|
104
|
-
const delta = inner.delta;
|
|
105
|
-
|
|
106
|
-
if (delta.type === 'text_delta' && delta.text) {
|
|
107
|
-
return {
|
|
108
|
-
type: 'text',
|
|
109
|
-
text: delta.text,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (delta.type === 'thinking_delta' && delta.thinking) {
|
|
114
|
-
return {
|
|
115
|
-
type: 'thinking',
|
|
116
|
-
text: delta.thinking,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (delta.type === 'input_json_delta' && delta.partial_json) {
|
|
121
|
-
return {
|
|
122
|
-
type: 'tool_input',
|
|
123
|
-
json: delta.partial_json,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// content_block_stop - block ended
|
|
129
|
-
if (inner.type === 'content_block_stop') {
|
|
130
|
-
return {
|
|
131
|
-
type: 'block_end',
|
|
132
|
-
index: inner.index,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// message_start - skip text extraction here since it will come via text_delta events
|
|
137
|
-
// (extracting here causes duplicate text output)
|
|
138
|
-
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Parse assistant message content blocks
|
|
144
|
-
* NOTE: Skip text blocks here since they were already streamed via text_delta events.
|
|
145
|
-
* Extracting text here would cause duplicate output.
|
|
146
|
-
*/
|
|
147
|
-
function parseAssistantMessage(message) {
|
|
148
|
-
const results = [];
|
|
149
|
-
|
|
150
|
-
for (const block of message.content) {
|
|
151
|
-
// Skip text blocks - already streamed via text_delta events
|
|
152
|
-
// Extracting here causes duplicate output
|
|
153
|
-
|
|
154
|
-
if (block.type === 'tool_use') {
|
|
155
|
-
results.push({
|
|
156
|
-
type: 'tool_call',
|
|
157
|
-
toolName: block.name,
|
|
158
|
-
toolId: block.id,
|
|
159
|
-
input: block.input,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (block.type === 'thinking' && block.thinking) {
|
|
164
|
-
// Skip thinking blocks too - already streamed via thinking_delta
|
|
165
|
-
// But keep for non-streaming contexts (direct API responses)
|
|
166
|
-
// Only emit if we haven't seen streaming deltas (detected by having results already)
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (results.length === 1) {
|
|
171
|
-
return results[0];
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (results.length > 1) {
|
|
175
|
-
return {
|
|
176
|
-
type: 'multi',
|
|
177
|
-
events: results,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Parse user message (tool results)
|
|
186
|
-
*/
|
|
187
|
-
function parseUserMessage(message) {
|
|
188
|
-
const results = [];
|
|
189
|
-
|
|
190
|
-
for (const block of message.content) {
|
|
191
|
-
if (block.type === 'tool_result') {
|
|
192
|
-
results.push({
|
|
193
|
-
type: 'tool_result',
|
|
194
|
-
toolId: block.tool_use_id,
|
|
195
|
-
content: typeof block.content === 'string' ? block.content : JSON.stringify(block.content),
|
|
196
|
-
isError: block.is_error || false,
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (results.length === 1) {
|
|
202
|
-
return results[0];
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (results.length > 1) {
|
|
206
|
-
return {
|
|
207
|
-
type: 'multi',
|
|
208
|
-
events: results,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return null;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Parse multiple lines of NDJSON
|
|
217
|
-
* @param {string} chunk - Chunk of text potentially containing multiple JSON lines
|
|
218
|
-
* @returns {Array} Array of parsed events
|
|
219
|
-
*/
|
|
220
|
-
function parseChunk(chunk) {
|
|
221
|
-
const events = [];
|
|
222
|
-
const lines = chunk.split('\n');
|
|
223
|
-
|
|
224
|
-
for (const line of lines) {
|
|
225
|
-
const event = parseStreamLine(line);
|
|
226
|
-
if (event) {
|
|
227
|
-
if (event.type === 'multi') {
|
|
228
|
-
events.push(...event.events);
|
|
229
|
-
} else {
|
|
230
|
-
events.push(event);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return events;
|
|
236
|
-
}
|
|
5
|
+
const { parseEvent, parseChunk } = require('../src/providers/anthropic/output-parser');
|
|
237
6
|
|
|
238
7
|
module.exports = {
|
|
239
|
-
|
|
8
|
+
parseEvent,
|
|
240
9
|
parseChunk,
|
|
241
|
-
parseStreamEvent,
|
|
242
|
-
parseAssistantMessage,
|
|
243
|
-
parseUserMessage,
|
|
244
10
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@covibes/zeroshot",
|
|
3
|
-
"version": "5.
|
|
4
|
-
"description": "Multi-agent orchestration engine for Claude
|
|
3
|
+
"version": "5.4.0",
|
|
4
|
+
"description": "Multi-agent orchestration engine for Claude, Codex, and Gemini",
|
|
5
5
|
"main": "src/orchestrator.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"zeroshot": "./cli/index.js"
|
|
@@ -11,14 +11,18 @@
|
|
|
11
11
|
"test": "tests"
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
|
-
"test": "mocha 'tests/**/*.test.js'",
|
|
15
|
-
"test:
|
|
16
|
-
"test:
|
|
14
|
+
"test": "mocha 'tests/unit/**/*.test.js' 'tests/*.test.js' --parallel",
|
|
15
|
+
"test:unit": "mocha 'tests/unit/**/*.test.js' 'tests/*.test.js' --parallel",
|
|
16
|
+
"test:slow": "mocha 'tests/integration/**/*.test.js' --timeout 180000",
|
|
17
|
+
"test:all": "npm run test && npm run test:slow",
|
|
18
|
+
"test:coverage": "c8 npm run test:unit",
|
|
19
|
+
"test:coverage:report": "c8 --reporter=html npm run test:unit && echo 'Coverage report generated at coverage/index.html'",
|
|
17
20
|
"postinstall": "node scripts/fix-node-pty-permissions.js",
|
|
18
21
|
"start": "node cli/index.js",
|
|
19
22
|
"typecheck": "tsc --noEmit",
|
|
20
23
|
"lint": "eslint .",
|
|
21
24
|
"lint:fix": "eslint . --fix",
|
|
25
|
+
"validate:templates": "node scripts/validate-templates.js",
|
|
22
26
|
"format": "prettier --write .",
|
|
23
27
|
"format:check": "prettier --check .",
|
|
24
28
|
"deadcode": "ts-prune --skip node_modules",
|
|
@@ -55,6 +59,8 @@
|
|
|
55
59
|
"multi-agent",
|
|
56
60
|
"orchestration",
|
|
57
61
|
"claude",
|
|
62
|
+
"codex",
|
|
63
|
+
"gemini",
|
|
58
64
|
"automation"
|
|
59
65
|
],
|
|
60
66
|
"author": "Covibes",
|
|
@@ -112,10 +118,14 @@
|
|
|
112
118
|
"depcheck": "^1.4.7",
|
|
113
119
|
"eslint": "^9.39.1",
|
|
114
120
|
"eslint-config-prettier": "^10.1.8",
|
|
121
|
+
"eslint-plugin-security": "^3.0.1",
|
|
122
|
+
"eslint-plugin-sonarjs": "^3.0.5",
|
|
115
123
|
"eslint-plugin-unused-imports": "^4.3.0",
|
|
116
124
|
"husky": "^9.1.7",
|
|
117
125
|
"jscpd": "^3.5.10",
|
|
126
|
+
"lint-staged": "^16.2.7",
|
|
118
127
|
"mocha": "^11.7.5",
|
|
128
|
+
"prettier": "^3.7.4",
|
|
119
129
|
"semantic-release": "^25.0.2",
|
|
120
130
|
"sinon": "^21.0.0",
|
|
121
131
|
"ts-prune": "^0.10.3",
|
|
@@ -123,6 +133,17 @@
|
|
|
123
133
|
"unimported": "^1.31.0"
|
|
124
134
|
},
|
|
125
135
|
"overrides": {
|
|
126
|
-
"xml2js": "^0.5.0"
|
|
136
|
+
"xml2js": "^0.5.0",
|
|
137
|
+
"diff": ">=8.0.3",
|
|
138
|
+
"tar": ">=7.5.3",
|
|
139
|
+
"undici": ">=7.18.2"
|
|
140
|
+
},
|
|
141
|
+
"lint-staged": {
|
|
142
|
+
"*.{js,mjs,cjs}": [
|
|
143
|
+
"eslint --fix"
|
|
144
|
+
],
|
|
145
|
+
"*.{js,mjs,cjs,json,md}": [
|
|
146
|
+
"prettier --write"
|
|
147
|
+
]
|
|
127
148
|
}
|
|
128
149
|
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Setup GitHub merge queue and branch protection for zeroshot
|
|
3
|
+
# Run once after creating the repo or to update settings
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
REPO="covibes/zeroshot"
|
|
8
|
+
|
|
9
|
+
echo "╔═══════════════════════════════════════════════════════════════════╗"
|
|
10
|
+
echo "║ Setting up merge queue for $REPO ║"
|
|
11
|
+
echo "╚═══════════════════════════════════════════════════════════════════╝"
|
|
12
|
+
echo ""
|
|
13
|
+
|
|
14
|
+
# Check gh is authenticated
|
|
15
|
+
if ! gh auth status &>/dev/null; then
|
|
16
|
+
echo "❌ ERROR: Not authenticated with GitHub CLI"
|
|
17
|
+
echo " Run: gh auth login"
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Check we have admin access
|
|
22
|
+
if ! gh api "repos/$REPO" --jq '.permissions.admin' | grep -q true; then
|
|
23
|
+
echo "❌ ERROR: You need admin access to $REPO"
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
echo "✓ Authenticated with admin access"
|
|
28
|
+
echo ""
|
|
29
|
+
|
|
30
|
+
# ============================================================================
|
|
31
|
+
# Configure 'dev' branch protection (merge target)
|
|
32
|
+
# ============================================================================
|
|
33
|
+
|
|
34
|
+
echo "→ Configuring 'dev' branch protection..."
|
|
35
|
+
|
|
36
|
+
gh api --method PUT "repos/$REPO/branches/dev/protection" \
|
|
37
|
+
--input - <<EOF
|
|
38
|
+
{
|
|
39
|
+
"required_status_checks": {
|
|
40
|
+
"strict": true,
|
|
41
|
+
"contexts": ["check", "install-matrix (ubuntu-latest, 20)", "install-matrix (macos-latest, 20)"]
|
|
42
|
+
},
|
|
43
|
+
"enforce_admins": false,
|
|
44
|
+
"required_pull_request_reviews": null,
|
|
45
|
+
"restrictions": null,
|
|
46
|
+
"allow_force_pushes": false,
|
|
47
|
+
"allow_deletions": false,
|
|
48
|
+
"required_linear_history": true,
|
|
49
|
+
"required_conversation_resolution": false
|
|
50
|
+
}
|
|
51
|
+
EOF
|
|
52
|
+
|
|
53
|
+
echo "✓ 'dev' branch protection configured"
|
|
54
|
+
|
|
55
|
+
# Enable merge queue for dev branch
|
|
56
|
+
echo "→ Enabling merge queue for 'dev' branch..."
|
|
57
|
+
|
|
58
|
+
# Note: Merge queue requires GitHub Enterprise or public repos with Actions
|
|
59
|
+
# Using the ruleset API which supports merge queue
|
|
60
|
+
gh api --method POST "repos/$REPO/rulesets" \
|
|
61
|
+
--input - <<EOF 2>/dev/null || echo " (ruleset may already exist)"
|
|
62
|
+
{
|
|
63
|
+
"name": "dev-merge-queue",
|
|
64
|
+
"target": "branch",
|
|
65
|
+
"enforcement": "active",
|
|
66
|
+
"conditions": {
|
|
67
|
+
"ref_name": {
|
|
68
|
+
"include": ["refs/heads/dev"],
|
|
69
|
+
"exclude": []
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"rules": [
|
|
73
|
+
{
|
|
74
|
+
"type": "merge_queue",
|
|
75
|
+
"parameters": {
|
|
76
|
+
"check_response_timeout_minutes": 30,
|
|
77
|
+
"grouping_strategy": "ALLGREEN",
|
|
78
|
+
"max_entries_to_build": 5,
|
|
79
|
+
"max_entries_to_merge": 5,
|
|
80
|
+
"merge_method": "SQUASH",
|
|
81
|
+
"min_entries_to_merge": 1,
|
|
82
|
+
"min_entries_to_merge_wait_minutes": 1
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
EOF
|
|
88
|
+
|
|
89
|
+
echo "✓ Merge queue enabled for 'dev'"
|
|
90
|
+
|
|
91
|
+
# ============================================================================
|
|
92
|
+
# Configure 'main' branch protection (release branch)
|
|
93
|
+
# ============================================================================
|
|
94
|
+
|
|
95
|
+
echo "→ Configuring 'main' branch protection..."
|
|
96
|
+
|
|
97
|
+
gh api --method PUT "repos/$REPO/branches/main/protection" \
|
|
98
|
+
--input - <<EOF
|
|
99
|
+
{
|
|
100
|
+
"required_status_checks": {
|
|
101
|
+
"strict": true,
|
|
102
|
+
"contexts": ["check", "install-matrix (ubuntu-latest, 20)", "install-matrix (macos-latest, 20)"]
|
|
103
|
+
},
|
|
104
|
+
"enforce_admins": false,
|
|
105
|
+
"required_pull_request_reviews": {
|
|
106
|
+
"required_approving_review_count": 1,
|
|
107
|
+
"dismiss_stale_reviews": true,
|
|
108
|
+
"require_code_owner_reviews": false
|
|
109
|
+
},
|
|
110
|
+
"restrictions": null,
|
|
111
|
+
"allow_force_pushes": false,
|
|
112
|
+
"allow_deletions": false,
|
|
113
|
+
"required_linear_history": true,
|
|
114
|
+
"required_conversation_resolution": false
|
|
115
|
+
}
|
|
116
|
+
EOF
|
|
117
|
+
|
|
118
|
+
echo "✓ 'main' branch protection configured"
|
|
119
|
+
|
|
120
|
+
# ============================================================================
|
|
121
|
+
# Configure repository settings
|
|
122
|
+
# ============================================================================
|
|
123
|
+
|
|
124
|
+
echo "→ Configuring repository settings..."
|
|
125
|
+
|
|
126
|
+
gh api --method PATCH "repos/$REPO" \
|
|
127
|
+
--input - <<EOF
|
|
128
|
+
{
|
|
129
|
+
"allow_squash_merge": true,
|
|
130
|
+
"allow_merge_commit": false,
|
|
131
|
+
"allow_rebase_merge": false,
|
|
132
|
+
"squash_merge_commit_title": "PR_TITLE",
|
|
133
|
+
"squash_merge_commit_message": "PR_BODY",
|
|
134
|
+
"delete_branch_on_merge": true,
|
|
135
|
+
"allow_auto_merge": true
|
|
136
|
+
}
|
|
137
|
+
EOF
|
|
138
|
+
|
|
139
|
+
echo "✓ Repository settings configured"
|
|
140
|
+
|
|
141
|
+
# ============================================================================
|
|
142
|
+
# Summary
|
|
143
|
+
# ============================================================================
|
|
144
|
+
|
|
145
|
+
echo ""
|
|
146
|
+
echo "╔═══════════════════════════════════════════════════════════════════╗"
|
|
147
|
+
echo "║ ✓ Merge queue setup complete! ║"
|
|
148
|
+
echo "╚═══════════════════════════════════════════════════════════════════╝"
|
|
149
|
+
echo ""
|
|
150
|
+
echo "Workflow:"
|
|
151
|
+
echo " feature-branch (local)"
|
|
152
|
+
echo " ↓"
|
|
153
|
+
echo " pre-push hook → lint + typecheck (~5s)"
|
|
154
|
+
echo " ↓"
|
|
155
|
+
echo " push to origin/feature-branch"
|
|
156
|
+
echo " ↓"
|
|
157
|
+
echo " gh pr create --base dev"
|
|
158
|
+
echo " ↓"
|
|
159
|
+
echo " CI runs tests on PR branch"
|
|
160
|
+
echo " ↓"
|
|
161
|
+
echo " gh pr merge --auto --squash → enters merge queue"
|
|
162
|
+
echo " ↓"
|
|
163
|
+
echo " Queue rebases PR on latest dev + runs CI again"
|
|
164
|
+
echo " ↓"
|
|
165
|
+
echo " Merge to dev (only if CI passes on rebased code)"
|
|
166
|
+
echo ""
|
|
167
|
+
echo "Release workflow:"
|
|
168
|
+
echo " gh pr create --base main --head dev --title \"Release\""
|
|
169
|
+
echo " → CI passes → merge → semantic-release publishes"
|
|
170
|
+
echo ""
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Validate all cluster templates for config errors
|
|
4
|
+
* Run in CI to prevent broken templates from being merged
|
|
5
|
+
*
|
|
6
|
+
* Usage: node scripts/validate-templates.js
|
|
7
|
+
* Exit codes: 0 = all valid, 1 = validation errors found
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { validateConfig } = require('../src/config-validator');
|
|
13
|
+
|
|
14
|
+
const TEMPLATES_DIR = path.join(__dirname, '../cluster-templates');
|
|
15
|
+
|
|
16
|
+
function findJsonFiles(dir) {
|
|
17
|
+
const files = [];
|
|
18
|
+
if (!fs.existsSync(dir)) return files;
|
|
19
|
+
|
|
20
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
21
|
+
const fullPath = path.join(dir, entry.name);
|
|
22
|
+
if (entry.isDirectory()) {
|
|
23
|
+
files.push(...findJsonFiles(fullPath));
|
|
24
|
+
} else if (entry.name.endsWith('.json')) {
|
|
25
|
+
files.push(fullPath);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return files;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function validateTemplate(filePath) {
|
|
32
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
36
|
+
const config = JSON.parse(content);
|
|
37
|
+
|
|
38
|
+
// Skip non-cluster configs (like package.json)
|
|
39
|
+
if (!config.agents && !config.name) {
|
|
40
|
+
return { valid: true, skipped: true };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const result = validateConfig(config);
|
|
44
|
+
|
|
45
|
+
if (!result.valid) {
|
|
46
|
+
console.error(`\n❌ ${relativePath}`);
|
|
47
|
+
for (const error of result.errors) {
|
|
48
|
+
console.error(` ERROR: ${error}`);
|
|
49
|
+
}
|
|
50
|
+
} else if (result.warnings.length > 0) {
|
|
51
|
+
console.warn(`\n⚠️ ${relativePath}`);
|
|
52
|
+
for (const warning of result.warnings) {
|
|
53
|
+
console.warn(` WARN: ${warning}`);
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
console.log(`✓ ${relativePath}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return result;
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error(`\n❌ ${relativePath}`);
|
|
62
|
+
console.error(` PARSE ERROR: ${err.message}`);
|
|
63
|
+
return { valid: false, errors: [err.message], warnings: [] };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function main() {
|
|
68
|
+
console.log('Validating cluster templates...\n');
|
|
69
|
+
|
|
70
|
+
const templateFiles = [...findJsonFiles(TEMPLATES_DIR)];
|
|
71
|
+
|
|
72
|
+
let hasErrors = false;
|
|
73
|
+
let validated = 0;
|
|
74
|
+
let skipped = 0;
|
|
75
|
+
|
|
76
|
+
for (const file of templateFiles) {
|
|
77
|
+
const result = validateTemplate(file);
|
|
78
|
+
if (result.skipped) {
|
|
79
|
+
skipped++;
|
|
80
|
+
} else {
|
|
81
|
+
validated++;
|
|
82
|
+
if (!result.valid) {
|
|
83
|
+
hasErrors = true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(`\n${'='.repeat(60)}`);
|
|
89
|
+
console.log(`Validated: ${validated} templates, Skipped: ${skipped} files`);
|
|
90
|
+
|
|
91
|
+
if (hasErrors) {
|
|
92
|
+
console.error('\n❌ VALIDATION FAILED - Fix errors above before merging\n');
|
|
93
|
+
process.exit(1);
|
|
94
|
+
} else {
|
|
95
|
+
console.log('\n✓ All templates valid\n');
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
main();
|