swellai 1.0.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/README.md +711 -0
- package/dist/agents/linear-agent.d.ts +32 -0
- package/dist/agents/linear-agent.d.ts.map +1 -0
- package/dist/agents/linear-agent.js +263 -0
- package/dist/agents/linear-agent.js.map +1 -0
- package/dist/agents/planning-agent.d.ts +36 -0
- package/dist/agents/planning-agent.d.ts.map +1 -0
- package/dist/agents/planning-agent.js +248 -0
- package/dist/agents/planning-agent.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +102 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/install.d.ts +11 -0
- package/dist/cli/install.d.ts.map +1 -0
- package/dist/cli/install.js +257 -0
- package/dist/cli/install.js.map +1 -0
- package/dist/cli/manifest.d.ts +27 -0
- package/dist/cli/manifest.d.ts.map +1 -0
- package/dist/cli/manifest.js +65 -0
- package/dist/cli/manifest.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/claude-agent-sdk.d.ts +73 -0
- package/dist/lib/claude-agent-sdk.d.ts.map +1 -0
- package/dist/lib/claude-agent-sdk.js +114 -0
- package/dist/lib/claude-agent-sdk.js.map +1 -0
- package/dist/lib/conversation-logger.d.ts +66 -0
- package/dist/lib/conversation-logger.d.ts.map +1 -0
- package/dist/lib/conversation-logger.js +159 -0
- package/dist/lib/conversation-logger.js.map +1 -0
- package/dist/lib/opencode.d.ts +68 -0
- package/dist/lib/opencode.d.ts.map +1 -0
- package/dist/lib/opencode.js +151 -0
- package/dist/lib/opencode.js.map +1 -0
- package/dist/lib/turso-schema.d.ts +13 -0
- package/dist/lib/turso-schema.d.ts.map +1 -0
- package/dist/lib/turso-schema.js +69 -0
- package/dist/lib/turso-schema.js.map +1 -0
- package/dist/lib/turso.d.ts +56 -0
- package/dist/lib/turso.d.ts.map +1 -0
- package/dist/lib/turso.js +144 -0
- package/dist/lib/turso.js.map +1 -0
- package/dist/lib/types.d.ts +31 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +20 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/utils.d.ts +34 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +72 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/scripts/build-templates.d.ts +17 -0
- package/dist/scripts/build-templates.d.ts.map +1 -0
- package/dist/scripts/build-templates.js +132 -0
- package/dist/scripts/build-templates.js.map +1 -0
- package/dist/scripts/claude-agent-runner.d.ts +28 -0
- package/dist/scripts/claude-agent-runner.d.ts.map +1 -0
- package/dist/scripts/claude-agent-runner.js +278 -0
- package/dist/scripts/claude-agent-runner.js.map +1 -0
- package/dist/src/agents/linear-agent.d.ts +32 -0
- package/dist/src/agents/linear-agent.d.ts.map +1 -0
- package/dist/src/agents/linear-agent.js +285 -0
- package/dist/src/agents/linear-agent.js.map +1 -0
- package/dist/src/agents/planning-agent.d.ts +36 -0
- package/dist/src/agents/planning-agent.d.ts.map +1 -0
- package/dist/src/agents/planning-agent.js +248 -0
- package/dist/src/agents/planning-agent.js.map +1 -0
- package/dist/src/cli/index.d.ts +3 -0
- package/dist/src/cli/index.d.ts.map +1 -0
- package/dist/src/cli/index.js +102 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/cli/install.d.ts +11 -0
- package/dist/src/cli/install.d.ts.map +1 -0
- package/dist/src/cli/install.js +257 -0
- package/dist/src/cli/install.js.map +1 -0
- package/dist/src/cli/manifest.d.ts +27 -0
- package/dist/src/cli/manifest.d.ts.map +1 -0
- package/dist/src/cli/manifest.js +65 -0
- package/dist/src/cli/manifest.js.map +1 -0
- package/dist/src/index.d.ts +17 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +17 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib/claude-agent-sdk.d.ts +73 -0
- package/dist/src/lib/claude-agent-sdk.d.ts.map +1 -0
- package/dist/src/lib/claude-agent-sdk.js +114 -0
- package/dist/src/lib/claude-agent-sdk.js.map +1 -0
- package/dist/src/lib/conversation-logger.d.ts +66 -0
- package/dist/src/lib/conversation-logger.d.ts.map +1 -0
- package/dist/src/lib/conversation-logger.js +159 -0
- package/dist/src/lib/conversation-logger.js.map +1 -0
- package/dist/src/lib/opencode.d.ts +153 -0
- package/dist/src/lib/opencode.d.ts.map +1 -0
- package/dist/src/lib/opencode.js +153 -0
- package/dist/src/lib/opencode.js.map +1 -0
- package/dist/src/lib/turso-schema.d.ts +13 -0
- package/dist/src/lib/turso-schema.d.ts.map +1 -0
- package/dist/src/lib/turso-schema.js +69 -0
- package/dist/src/lib/turso-schema.js.map +1 -0
- package/dist/src/lib/turso.d.ts +56 -0
- package/dist/src/lib/turso.d.ts.map +1 -0
- package/dist/src/lib/turso.js +144 -0
- package/dist/src/lib/turso.js.map +1 -0
- package/dist/src/lib/types.d.ts +31 -0
- package/dist/src/lib/types.d.ts.map +1 -0
- package/dist/src/lib/types.js +20 -0
- package/dist/src/lib/types.js.map +1 -0
- package/dist/src/lib/utils.d.ts +34 -0
- package/dist/src/lib/utils.d.ts.map +1 -0
- package/dist/src/lib/utils.js +72 -0
- package/dist/src/lib/utils.js.map +1 -0
- package/package.json +63 -0
- package/templates/.env.example +51 -0
- package/templates/agents/codebase-analyzer.md +121 -0
- package/templates/agents/codebase-locator.md +105 -0
- package/templates/agents/coding-agent.md +187 -0
- package/templates/agents/debug-agent.md +300 -0
- package/templates/prompts/consolidate-and-create-linear.md +282 -0
- package/templates/prompts/implementation.md +94 -0
- package/templates/prompts/plan-generation.md +171 -0
- package/templates/prompts/review.md +39 -0
- package/templates/prompts/verify.md +80 -0
- package/templates/scripts/claude-agent-runner.js +12887 -0
- package/templates/scripts/detect-runtime.sh +95 -0
- package/templates/scripts/linear-agent.js +1753 -0
- package/templates/scripts/planning-agent.js +1738 -0
- package/templates/workflows/claude-implement.yml +931 -0
- package/templates/workflows/claude-plan.yml +301 -0
|
@@ -0,0 +1,931 @@
|
|
|
1
|
+
name: Claude Implement - Parallel Implementation Workflow
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issues:
|
|
5
|
+
types: [labeled]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
linear_issue:
|
|
9
|
+
description: 'Linear issue ID (e.g., ENG-123) or URL'
|
|
10
|
+
required: true
|
|
11
|
+
type: string
|
|
12
|
+
num_implementations:
|
|
13
|
+
description: 'Number of parallel implementations'
|
|
14
|
+
required: false
|
|
15
|
+
type: number
|
|
16
|
+
default: 3
|
|
17
|
+
claude_model:
|
|
18
|
+
description: 'Claude model to use'
|
|
19
|
+
required: false
|
|
20
|
+
type: string
|
|
21
|
+
default: 'claude-opus-4-5-20251101'
|
|
22
|
+
dry_run:
|
|
23
|
+
description: 'Skip Claude, use mock responses'
|
|
24
|
+
required: false
|
|
25
|
+
type: boolean
|
|
26
|
+
default: false
|
|
27
|
+
|
|
28
|
+
jobs:
|
|
29
|
+
generate-matrix:
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
outputs:
|
|
32
|
+
matrix: ${{ steps.generate.outputs.matrix }}
|
|
33
|
+
linear_issue: ${{ steps.extract.outputs.linear_issue }}
|
|
34
|
+
steps:
|
|
35
|
+
- name: Validate authentication
|
|
36
|
+
if: ${{ inputs.dry_run != true }}
|
|
37
|
+
env:
|
|
38
|
+
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
39
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
40
|
+
run: |
|
|
41
|
+
if [[ -z "$CLAUDE_CODE_OAUTH_TOKEN" ]] && [[ -z "$ANTHROPIC_API_KEY" ]]; then
|
|
42
|
+
echo "Error: At least one authentication method must be provided"
|
|
43
|
+
echo "Please provide either CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY"
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
- name: Extract Linear issue from label or input
|
|
48
|
+
id: extract
|
|
49
|
+
env:
|
|
50
|
+
EVENT_NAME: ${{ github.event_name }}
|
|
51
|
+
INPUT_LINEAR_ISSUE: ${{ inputs.linear_issue }}
|
|
52
|
+
ISSUE_LABELS: ${{ toJson(github.event.issue.labels.*.name) }}
|
|
53
|
+
run: |
|
|
54
|
+
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
|
|
55
|
+
ISSUE="$INPUT_LINEAR_ISSUE"
|
|
56
|
+
else
|
|
57
|
+
# Extract from label like "linear:ENG-123"
|
|
58
|
+
ISSUE=$(echo "$ISSUE_LABELS" | jq -r '.[] | select(startswith("linear:")) | sub("linear:"; "")')
|
|
59
|
+
if [ -z "$ISSUE" ]; then
|
|
60
|
+
echo "Error: No linear: label found on issue"
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
fi
|
|
64
|
+
echo "linear_issue=$ISSUE" >> $GITHUB_OUTPUT
|
|
65
|
+
echo "Using Linear issue: $ISSUE"
|
|
66
|
+
|
|
67
|
+
- name: Generate matrix
|
|
68
|
+
id: generate
|
|
69
|
+
run: |
|
|
70
|
+
# Generate array [1, 2, ..., num_implementations]
|
|
71
|
+
MATRIX=$(seq 1 ${{ inputs.num_implementations || 3 }} | jq -s -c '.')
|
|
72
|
+
echo "matrix=$MATRIX" >> $GITHUB_OUTPUT
|
|
73
|
+
echo "Generated matrix: $MATRIX"
|
|
74
|
+
|
|
75
|
+
implement:
|
|
76
|
+
needs: generate-matrix
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
strategy:
|
|
79
|
+
matrix:
|
|
80
|
+
impl: ${{ fromJSON(needs.generate-matrix.outputs.matrix) }}
|
|
81
|
+
fail-fast: false
|
|
82
|
+
|
|
83
|
+
steps:
|
|
84
|
+
- name: Checkout
|
|
85
|
+
uses: actions/checkout@v4
|
|
86
|
+
with:
|
|
87
|
+
fetch-depth: 0
|
|
88
|
+
token: ${{ secrets.GH_PAT }}
|
|
89
|
+
|
|
90
|
+
- name: Create implementation branch
|
|
91
|
+
run: |
|
|
92
|
+
BRANCH="impl-${{ github.run_id }}-${{ matrix.impl }}"
|
|
93
|
+
git checkout -b "$BRANCH"
|
|
94
|
+
echo "BRANCH=$BRANCH" >> $GITHUB_ENV
|
|
95
|
+
|
|
96
|
+
- name: Setup Claude CLI
|
|
97
|
+
if: ${{ inputs.dry_run != true }}
|
|
98
|
+
env:
|
|
99
|
+
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
100
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
101
|
+
run: |
|
|
102
|
+
# Validate authentication
|
|
103
|
+
if [[ -z "$CLAUDE_CODE_OAUTH_TOKEN" ]] && [[ -z "$ANTHROPIC_API_KEY" ]]; then
|
|
104
|
+
echo "Error: At least one authentication method must be provided"
|
|
105
|
+
exit 1
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# Install Claude CLI
|
|
109
|
+
curl -fsSL https://claude.ai/install.sh | bash
|
|
110
|
+
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
|
111
|
+
|
|
112
|
+
# Set authentication method
|
|
113
|
+
if [[ -n "$CLAUDE_CODE_OAUTH_TOKEN" ]]; then
|
|
114
|
+
echo "CLAUDE_CODE_OAUTH_TOKEN=$CLAUDE_CODE_OAUTH_TOKEN" >> $GITHUB_ENV
|
|
115
|
+
echo "Using OAuth token for authentication"
|
|
116
|
+
elif [[ -n "$ANTHROPIC_API_KEY" ]]; then
|
|
117
|
+
echo "ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY" >> $GITHUB_ENV
|
|
118
|
+
echo "Using API key for authentication"
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
- name: Setup Bun for Agent Runner
|
|
122
|
+
if: ${{ inputs.dry_run != true }}
|
|
123
|
+
uses: oven-sh/setup-bun@v2
|
|
124
|
+
with:
|
|
125
|
+
bun-version: latest
|
|
126
|
+
|
|
127
|
+
- name: Install agent runner dependencies
|
|
128
|
+
if: ${{ inputs.dry_run != true }}
|
|
129
|
+
run: |
|
|
130
|
+
cd ${{ github.workspace }}
|
|
131
|
+
bun install
|
|
132
|
+
|
|
133
|
+
- name: Copy custom agents to home directory
|
|
134
|
+
if: ${{ inputs.dry_run != true }}
|
|
135
|
+
run: |
|
|
136
|
+
mkdir -p "$HOME/.claude/agents"
|
|
137
|
+
for agent in codebase-analyzer codebase-locator coding-agent debug-agent; do
|
|
138
|
+
cp .claude/agents/${agent}.md "$HOME/.claude/agents/${agent}.md"
|
|
139
|
+
done
|
|
140
|
+
echo "Installed agents:"
|
|
141
|
+
ls -la "$HOME/.claude/agents/"
|
|
142
|
+
|
|
143
|
+
- name: Detect runtime
|
|
144
|
+
id: runtime
|
|
145
|
+
run: |
|
|
146
|
+
# Run the bundled detect-runtime script from installed location
|
|
147
|
+
bash .github/claude-parallel/scripts/detect-runtime.sh
|
|
148
|
+
|
|
149
|
+
- name: Setup Bun
|
|
150
|
+
if: steps.runtime.outputs.runtime == 'js' && steps.runtime.outputs.package_manager == 'bun'
|
|
151
|
+
uses: oven-sh/setup-bun@v2
|
|
152
|
+
|
|
153
|
+
- name: Setup Node.js
|
|
154
|
+
if: steps.runtime.outputs.runtime == 'js' && steps.runtime.outputs.package_manager != 'bun'
|
|
155
|
+
uses: actions/setup-node@v4
|
|
156
|
+
with:
|
|
157
|
+
node-version: '20'
|
|
158
|
+
|
|
159
|
+
- name: Setup Python
|
|
160
|
+
if: steps.runtime.outputs.runtime == 'python'
|
|
161
|
+
uses: actions/setup-python@v5
|
|
162
|
+
with:
|
|
163
|
+
python-version: '3.11'
|
|
164
|
+
|
|
165
|
+
- name: Setup Go
|
|
166
|
+
if: steps.runtime.outputs.runtime == 'go'
|
|
167
|
+
uses: actions/setup-go@v5
|
|
168
|
+
with:
|
|
169
|
+
go-version: '1.21'
|
|
170
|
+
|
|
171
|
+
- name: Setup Rust
|
|
172
|
+
if: steps.runtime.outputs.runtime == 'rust'
|
|
173
|
+
uses: actions-rust-lang/setup-rust-toolchain@v1
|
|
174
|
+
|
|
175
|
+
- name: Install dependencies
|
|
176
|
+
run: |
|
|
177
|
+
case "${{ steps.runtime.outputs.runtime }}" in
|
|
178
|
+
js)
|
|
179
|
+
if [ -f "package.json" ]; then
|
|
180
|
+
case "${{ steps.runtime.outputs.package_manager }}" in
|
|
181
|
+
bun) bun install ;;
|
|
182
|
+
pnpm) npm install -g pnpm && pnpm install ;;
|
|
183
|
+
yarn) yarn install ;;
|
|
184
|
+
npm) npm install ;;
|
|
185
|
+
esac
|
|
186
|
+
fi
|
|
187
|
+
;;
|
|
188
|
+
python)
|
|
189
|
+
if [ -f "requirements.txt" ]; then
|
|
190
|
+
pip install -r requirements.txt
|
|
191
|
+
elif [ -f "pyproject.toml" ]; then
|
|
192
|
+
if [ "${{ steps.runtime.outputs.package_manager }}" = "poetry" ]; then
|
|
193
|
+
pip install poetry && poetry install
|
|
194
|
+
else
|
|
195
|
+
pip install .
|
|
196
|
+
fi
|
|
197
|
+
fi
|
|
198
|
+
;;
|
|
199
|
+
go)
|
|
200
|
+
if [ -f "go.mod" ]; then
|
|
201
|
+
go mod download
|
|
202
|
+
fi
|
|
203
|
+
;;
|
|
204
|
+
rust)
|
|
205
|
+
if [ -f "Cargo.toml" ]; then
|
|
206
|
+
cargo fetch
|
|
207
|
+
fi
|
|
208
|
+
;;
|
|
209
|
+
esac
|
|
210
|
+
|
|
211
|
+
- name: Run implementation
|
|
212
|
+
if: ${{ inputs.dry_run != true }}
|
|
213
|
+
env:
|
|
214
|
+
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
|
|
215
|
+
run: |
|
|
216
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
217
|
+
|
|
218
|
+
# Read implementation prompt template from installed location
|
|
219
|
+
IMPL_TEMPLATE=$(cat .github/claude-parallel/prompts/implementation.md)
|
|
220
|
+
|
|
221
|
+
# Substitute LINEAR_ISSUE placeholder with the issue ID/URL
|
|
222
|
+
IMPL_PROMPT=$(echo "$IMPL_TEMPLATE" | sed "s|{{LINEAR_ISSUE}}|${{ needs.generate-matrix.outputs.linear_issue }}|g")
|
|
223
|
+
|
|
224
|
+
echo "Starting implementation ${{ matrix.impl }}..."
|
|
225
|
+
echo "Linear issue: ${{ needs.generate-matrix.outputs.linear_issue }}"
|
|
226
|
+
echo "Prompt size: $(echo "$IMPL_PROMPT" | wc -c) bytes"
|
|
227
|
+
|
|
228
|
+
# Run agent runner and capture exit code
|
|
229
|
+
set +e
|
|
230
|
+
echo "$IMPL_PROMPT" | node .github/claude-parallel/scripts/claude-agent-runner.js \
|
|
231
|
+
--cwd "$(pwd)" \
|
|
232
|
+
--model ${{ inputs.claude_model || 'claude-opus-4-5-20251101' }} \
|
|
233
|
+
--mode implementation \
|
|
234
|
+
> result.json 2> error.log
|
|
235
|
+
RUNNER_EXIT=$?
|
|
236
|
+
set -e
|
|
237
|
+
|
|
238
|
+
echo "Agent runner exit code: $RUNNER_EXIT"
|
|
239
|
+
|
|
240
|
+
# Check if we got valid output
|
|
241
|
+
if [ -s result.json ] && jq -e . result.json > /dev/null 2>&1; then
|
|
242
|
+
echo "Implementation completed successfully"
|
|
243
|
+
else
|
|
244
|
+
echo "::error::Implementation ${{ matrix.impl }} failed"
|
|
245
|
+
echo "=== Agent runner exit code: $RUNNER_EXIT ==="
|
|
246
|
+
echo "=== result.json contents (first 500 chars): ==="
|
|
247
|
+
head -c 500 result.json 2>/dev/null || echo "(empty or missing)"
|
|
248
|
+
echo ""
|
|
249
|
+
echo "=== error.log contents: ==="
|
|
250
|
+
cat error.log 2>/dev/null || echo "(empty or missing)"
|
|
251
|
+
exit 1
|
|
252
|
+
fi
|
|
253
|
+
|
|
254
|
+
- name: Mock implementation (dry run)
|
|
255
|
+
if: ${{ inputs.dry_run == true }}
|
|
256
|
+
run: |
|
|
257
|
+
# Create mock result
|
|
258
|
+
echo '{"result": "Mock implementation ${{ matrix.impl }} completed"}' > result.json
|
|
259
|
+
|
|
260
|
+
# Create a mock change to test the workflow
|
|
261
|
+
echo "# Mock Implementation ${{ matrix.impl }}" >> MOCK_CHANGES.md
|
|
262
|
+
echo "" >> MOCK_CHANGES.md
|
|
263
|
+
echo "Issue: ${{ needs.generate-matrix.outputs.linear_issue }}" >> MOCK_CHANGES.md
|
|
264
|
+
echo "Run ID: ${{ github.run_id }}" >> MOCK_CHANGES.md
|
|
265
|
+
echo "Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> MOCK_CHANGES.md
|
|
266
|
+
|
|
267
|
+
- name: Commit changes
|
|
268
|
+
env:
|
|
269
|
+
GIT_AUTHOR_NAME: Claude Parallel Bot
|
|
270
|
+
GIT_AUTHOR_EMAIL: bot@claude-parallel.dev
|
|
271
|
+
GIT_COMMITTER_NAME: Claude Parallel Bot
|
|
272
|
+
GIT_COMMITTER_EMAIL: bot@claude-parallel.dev
|
|
273
|
+
run: |
|
|
274
|
+
git config user.name "Claude Parallel Bot"
|
|
275
|
+
git config user.email "bot@claude-parallel.dev"
|
|
276
|
+
|
|
277
|
+
# Add all changes
|
|
278
|
+
git add -A
|
|
279
|
+
|
|
280
|
+
if git diff --staged --quiet; then
|
|
281
|
+
echo "No changes to commit"
|
|
282
|
+
echo "HAS_CHANGES=false" >> $GITHUB_ENV
|
|
283
|
+
else
|
|
284
|
+
git commit -m "Implementation ${{ matrix.impl }}: ${{ needs.generate-matrix.outputs.linear_issue }}"
|
|
285
|
+
echo "HAS_CHANGES=true" >> $GITHUB_ENV
|
|
286
|
+
fi
|
|
287
|
+
|
|
288
|
+
- name: Push branch
|
|
289
|
+
if: env.HAS_CHANGES == 'true'
|
|
290
|
+
run: |
|
|
291
|
+
git push origin "$BRANCH"
|
|
292
|
+
|
|
293
|
+
- name: Upload result
|
|
294
|
+
uses: actions/upload-artifact@v4
|
|
295
|
+
with:
|
|
296
|
+
name: impl-${{ matrix.impl }}
|
|
297
|
+
path: |
|
|
298
|
+
result.json
|
|
299
|
+
error.log
|
|
300
|
+
retention-days: 1
|
|
301
|
+
|
|
302
|
+
review:
|
|
303
|
+
needs: [generate-matrix, implement]
|
|
304
|
+
if: always() && !cancelled()
|
|
305
|
+
runs-on: ubuntu-latest
|
|
306
|
+
outputs:
|
|
307
|
+
winning_branch: ${{ steps.create_pr.outputs.winning_branch }}
|
|
308
|
+
pr_number: ${{ steps.create_pr.outputs.pr_number }}
|
|
309
|
+
|
|
310
|
+
steps:
|
|
311
|
+
- name: Checkout
|
|
312
|
+
uses: actions/checkout@v4
|
|
313
|
+
with:
|
|
314
|
+
fetch-depth: 0
|
|
315
|
+
token: ${{ secrets.GH_PAT }}
|
|
316
|
+
|
|
317
|
+
- name: Download all implementation results
|
|
318
|
+
uses: actions/download-artifact@v4
|
|
319
|
+
with:
|
|
320
|
+
path: artifacts
|
|
321
|
+
|
|
322
|
+
- name: Fetch implementation branches
|
|
323
|
+
run: |
|
|
324
|
+
mkdir -p worktrees
|
|
325
|
+
for i in $(echo '${{ needs.generate-matrix.outputs.matrix }}' | jq -r '.[]'); do
|
|
326
|
+
BRANCH="impl-${{ github.run_id }}-$i"
|
|
327
|
+
if git fetch origin "$BRANCH" 2>/dev/null; then
|
|
328
|
+
git worktree add "worktrees/impl-$i" "origin/$BRANCH"
|
|
329
|
+
echo "Fetched impl-$i"
|
|
330
|
+
else
|
|
331
|
+
echo "Branch $BRANCH not found (implementation may have failed)"
|
|
332
|
+
fi
|
|
333
|
+
done
|
|
334
|
+
|
|
335
|
+
- name: Setup Claude CLI
|
|
336
|
+
if: ${{ inputs.dry_run != true }}
|
|
337
|
+
env:
|
|
338
|
+
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
339
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
340
|
+
run: |
|
|
341
|
+
# Validate authentication
|
|
342
|
+
if [[ -z "$CLAUDE_CODE_OAUTH_TOKEN" ]] && [[ -z "$ANTHROPIC_API_KEY" ]]; then
|
|
343
|
+
echo "Error: At least one authentication method must be provided"
|
|
344
|
+
exit 1
|
|
345
|
+
fi
|
|
346
|
+
|
|
347
|
+
# Install Claude CLI
|
|
348
|
+
curl -fsSL https://claude.ai/install.sh | bash
|
|
349
|
+
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
|
350
|
+
|
|
351
|
+
# Set authentication method
|
|
352
|
+
if [[ -n "$CLAUDE_CODE_OAUTH_TOKEN" ]]; then
|
|
353
|
+
echo "CLAUDE_CODE_OAUTH_TOKEN=$CLAUDE_CODE_OAUTH_TOKEN" >> $GITHUB_ENV
|
|
354
|
+
echo "Using OAuth token for authentication"
|
|
355
|
+
elif [[ -n "$ANTHROPIC_API_KEY" ]]; then
|
|
356
|
+
echo "ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY" >> $GITHUB_ENV
|
|
357
|
+
echo "Using API key for authentication"
|
|
358
|
+
fi
|
|
359
|
+
|
|
360
|
+
- name: Setup Bun for Agent Runner
|
|
361
|
+
if: ${{ inputs.dry_run != true }}
|
|
362
|
+
uses: oven-sh/setup-bun@v2
|
|
363
|
+
with:
|
|
364
|
+
bun-version: latest
|
|
365
|
+
|
|
366
|
+
- name: Install agent runner dependencies
|
|
367
|
+
if: ${{ inputs.dry_run != true }}
|
|
368
|
+
run: |
|
|
369
|
+
cd ${{ github.workspace }}
|
|
370
|
+
bun install
|
|
371
|
+
|
|
372
|
+
- name: Copy custom agents to home directory
|
|
373
|
+
if: ${{ inputs.dry_run != true }}
|
|
374
|
+
run: |
|
|
375
|
+
mkdir -p "$HOME/.claude/agents"
|
|
376
|
+
for agent in codebase-analyzer codebase-locator coding-agent debug-agent; do
|
|
377
|
+
cp .claude/agents/${agent}.md "$HOME/.claude/agents/${agent}.md"
|
|
378
|
+
done
|
|
379
|
+
echo "Installed agents:"
|
|
380
|
+
ls -la "$HOME/.claude/agents/"
|
|
381
|
+
|
|
382
|
+
- name: Review implementations
|
|
383
|
+
if: ${{ inputs.dry_run != true }}
|
|
384
|
+
env:
|
|
385
|
+
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
|
|
386
|
+
run: |
|
|
387
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
388
|
+
|
|
389
|
+
# Read review prompt template from installed location
|
|
390
|
+
REVIEW_TEMPLATE=$(cat .github/claude-parallel/prompts/review.md)
|
|
391
|
+
|
|
392
|
+
# Substitute template variables
|
|
393
|
+
REVIEW_PROMPT=$(echo "$REVIEW_TEMPLATE" | sed \
|
|
394
|
+
-e "s|{{LINEAR_ISSUE}}|${{ needs.generate-matrix.outputs.linear_issue }}|g" \
|
|
395
|
+
-e "s|{{WORKTREES_DIR}}|worktrees|g" \
|
|
396
|
+
-e "s|{{NUM_IMPLEMENTATIONS}}|${{ inputs.num_implementations || 3 }}|g")
|
|
397
|
+
|
|
398
|
+
echo "Starting review..."
|
|
399
|
+
echo "Linear issue: ${{ needs.generate-matrix.outputs.linear_issue }}"
|
|
400
|
+
|
|
401
|
+
# Run agent runner in review mode (schema automatically applied)
|
|
402
|
+
echo "$REVIEW_PROMPT" | node .github/claude-parallel/scripts/claude-agent-runner.js \
|
|
403
|
+
--cwd "$(pwd)" \
|
|
404
|
+
--model ${{ inputs.claude_model || 'claude-opus-4-5-20251101' }} \
|
|
405
|
+
--mode review \
|
|
406
|
+
> review-result.json 2> review-error.log || true
|
|
407
|
+
|
|
408
|
+
cat review-result.json
|
|
409
|
+
|
|
410
|
+
- name: Mock review (dry run)
|
|
411
|
+
if: ${{ inputs.dry_run == true }}
|
|
412
|
+
run: |
|
|
413
|
+
# Pick a random winner (1-num_implementations) for testing
|
|
414
|
+
MAX=${{ inputs.num_implementations || 3 }}
|
|
415
|
+
WINNER=$(( (RANDOM % MAX) + 1 ))
|
|
416
|
+
echo "Mock review selecting implementation $WINNER"
|
|
417
|
+
|
|
418
|
+
# Agent runner with --mode review always produces structured_output
|
|
419
|
+
echo "{\"type\":\"result\",\"result\":\"\",\"structured_output\":{\"best\":$WINNER,\"reasoning\":\"Dry run mock - selected implementation $WINNER\"}}" > review-result.json
|
|
420
|
+
|
|
421
|
+
echo "=== Mock review-result.json: ==="
|
|
422
|
+
cat review-result.json
|
|
423
|
+
|
|
424
|
+
- name: Parse review and create PR
|
|
425
|
+
id: create_pr
|
|
426
|
+
env:
|
|
427
|
+
GH_TOKEN: ${{ secrets.GH_PAT }}
|
|
428
|
+
run: |
|
|
429
|
+
# Extract decision from structured_output (guaranteed by agent runner)
|
|
430
|
+
echo "=== Parsing review result ==="
|
|
431
|
+
cat review-result.json
|
|
432
|
+
echo ""
|
|
433
|
+
|
|
434
|
+
DECISION_JSON=$(jq -c '.structured_output' review-result.json 2>/dev/null)
|
|
435
|
+
|
|
436
|
+
# Validate the decision exists and has required fields
|
|
437
|
+
if [ -z "$DECISION_JSON" ] || [ "$DECISION_JSON" = "null" ]; then
|
|
438
|
+
echo "::error::No structured_output field in review result"
|
|
439
|
+
echo "Agent runner with --mode review should always produce structured_output"
|
|
440
|
+
exit 1
|
|
441
|
+
fi
|
|
442
|
+
|
|
443
|
+
if ! echo "$DECISION_JSON" | jq -e '.best' >/dev/null 2>&1; then
|
|
444
|
+
echo "::error::structured_output missing 'best' field"
|
|
445
|
+
exit 1
|
|
446
|
+
fi
|
|
447
|
+
|
|
448
|
+
echo "✓ Successfully extracted decision from structured_output"
|
|
449
|
+
|
|
450
|
+
# Parse the extracted JSON
|
|
451
|
+
BEST=$(echo "$DECISION_JSON" | jq -r '.best')
|
|
452
|
+
REASONING=$(echo "$DECISION_JSON" | jq -r '.reasoning // "No reasoning provided"')
|
|
453
|
+
|
|
454
|
+
echo "Selected implementation: $BEST"
|
|
455
|
+
|
|
456
|
+
WINNING_BRANCH="impl-${{ github.run_id }}-$BEST"
|
|
457
|
+
echo "winning_branch=$WINNING_BRANCH" >> $GITHUB_OUTPUT
|
|
458
|
+
|
|
459
|
+
# Create PR
|
|
460
|
+
PR_BODY="## AI-Generated Implementation (Best of ${{ inputs.num_implementations || 3 }})
|
|
461
|
+
|
|
462
|
+
**Linear Issue:** ${{ needs.generate-matrix.outputs.linear_issue }}
|
|
463
|
+
**Selected:** Implementation $BEST
|
|
464
|
+
|
|
465
|
+
### Reasoning
|
|
466
|
+
$REASONING
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
*Generated by Claude Parallel workflow*"
|
|
470
|
+
|
|
471
|
+
PR_URL=$(gh pr create \
|
|
472
|
+
--head "$WINNING_BRANCH" \
|
|
473
|
+
--title "Implementation: ${{ needs.generate-matrix.outputs.linear_issue }}" \
|
|
474
|
+
--body "$PR_BODY" \
|
|
475
|
+
--draft)
|
|
476
|
+
|
|
477
|
+
# Extract PR number from URL
|
|
478
|
+
PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$')
|
|
479
|
+
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
|
|
480
|
+
echo "Created PR #$PR_NUMBER"
|
|
481
|
+
|
|
482
|
+
- name: Cleanup losing branches
|
|
483
|
+
if: always() && steps.create_pr.outputs.winning_branch != ''
|
|
484
|
+
run: |
|
|
485
|
+
WINNING="${{ steps.create_pr.outputs.winning_branch }}"
|
|
486
|
+
# Extract impl number (last character after final dash)
|
|
487
|
+
BEST="${WINNING##*-}"
|
|
488
|
+
|
|
489
|
+
for i in $(echo '${{ needs.generate-matrix.outputs.matrix }}' | jq -r '.[]'); do
|
|
490
|
+
if [ "$i" != "$BEST" ]; then
|
|
491
|
+
BRANCH="impl-${{ github.run_id }}-$i"
|
|
492
|
+
git push origin --delete "$BRANCH" 2>/dev/null || true
|
|
493
|
+
fi
|
|
494
|
+
done
|
|
495
|
+
|
|
496
|
+
verify:
|
|
497
|
+
needs: [generate-matrix, review]
|
|
498
|
+
if: needs.review.outputs.pr_number != ''
|
|
499
|
+
runs-on: ubuntu-latest
|
|
500
|
+
|
|
501
|
+
steps:
|
|
502
|
+
- name: Checkout winning branch
|
|
503
|
+
uses: actions/checkout@v4
|
|
504
|
+
with:
|
|
505
|
+
ref: ${{ needs.review.outputs.winning_branch }}
|
|
506
|
+
fetch-depth: 0
|
|
507
|
+
token: ${{ secrets.GH_PAT }}
|
|
508
|
+
|
|
509
|
+
- name: Setup Claude CLI
|
|
510
|
+
if: ${{ inputs.dry_run != true }}
|
|
511
|
+
env:
|
|
512
|
+
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
513
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
514
|
+
run: |
|
|
515
|
+
# Validate authentication
|
|
516
|
+
if [[ -z "$CLAUDE_CODE_OAUTH_TOKEN" ]] && [[ -z "$ANTHROPIC_API_KEY" ]]; then
|
|
517
|
+
echo "Error: At least one authentication method must be provided"
|
|
518
|
+
exit 1
|
|
519
|
+
fi
|
|
520
|
+
|
|
521
|
+
# Install Claude CLI
|
|
522
|
+
curl -fsSL https://claude.ai/install.sh | bash
|
|
523
|
+
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
|
524
|
+
|
|
525
|
+
# Set authentication method
|
|
526
|
+
if [[ -n "$CLAUDE_CODE_OAUTH_TOKEN" ]]; then
|
|
527
|
+
echo "CLAUDE_CODE_OAUTH_TOKEN=$CLAUDE_CODE_OAUTH_TOKEN" >> $GITHUB_ENV
|
|
528
|
+
echo "Using OAuth token for authentication"
|
|
529
|
+
elif [[ -n "$ANTHROPIC_API_KEY" ]]; then
|
|
530
|
+
echo "ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY" >> $GITHUB_ENV
|
|
531
|
+
echo "Using API key for authentication"
|
|
532
|
+
fi
|
|
533
|
+
|
|
534
|
+
- name: Setup Bun for Agent Runner
|
|
535
|
+
if: ${{ inputs.dry_run != true }}
|
|
536
|
+
uses: oven-sh/setup-bun@v2
|
|
537
|
+
with:
|
|
538
|
+
bun-version: latest
|
|
539
|
+
|
|
540
|
+
- name: Install agent runner dependencies
|
|
541
|
+
if: ${{ inputs.dry_run != true }}
|
|
542
|
+
run: |
|
|
543
|
+
cd ${{ github.workspace }}
|
|
544
|
+
bun install
|
|
545
|
+
|
|
546
|
+
- name: Copy custom agents to home directory
|
|
547
|
+
if: ${{ inputs.dry_run != true }}
|
|
548
|
+
run: |
|
|
549
|
+
mkdir -p "$HOME/.claude/agents"
|
|
550
|
+
for agent in codebase-analyzer codebase-locator coding-agent debug-agent; do
|
|
551
|
+
cp .claude/agents/${agent}.md "$HOME/.claude/agents/${agent}.md"
|
|
552
|
+
done
|
|
553
|
+
echo "Installed agents:"
|
|
554
|
+
ls -la "$HOME/.claude/agents/"
|
|
555
|
+
|
|
556
|
+
- name: Detect runtime
|
|
557
|
+
id: runtime
|
|
558
|
+
run: |
|
|
559
|
+
# Run the bundled detect-runtime script from installed location
|
|
560
|
+
bash .github/claude-parallel/scripts/detect-runtime.sh
|
|
561
|
+
|
|
562
|
+
- name: Setup Bun
|
|
563
|
+
if: steps.runtime.outputs.runtime == 'js' && steps.runtime.outputs.package_manager == 'bun'
|
|
564
|
+
uses: oven-sh/setup-bun@v2
|
|
565
|
+
|
|
566
|
+
- name: Setup Node.js
|
|
567
|
+
if: steps.runtime.outputs.runtime == 'js' && steps.runtime.outputs.package_manager != 'bun'
|
|
568
|
+
uses: actions/setup-node@v4
|
|
569
|
+
with:
|
|
570
|
+
node-version: '20'
|
|
571
|
+
|
|
572
|
+
- name: Setup Python
|
|
573
|
+
if: steps.runtime.outputs.runtime == 'python'
|
|
574
|
+
uses: actions/setup-python@v5
|
|
575
|
+
with:
|
|
576
|
+
python-version: '3.11'
|
|
577
|
+
|
|
578
|
+
- name: Setup Go
|
|
579
|
+
if: steps.runtime.outputs.runtime == 'go'
|
|
580
|
+
uses: actions/setup-go@v5
|
|
581
|
+
with:
|
|
582
|
+
go-version: '1.21'
|
|
583
|
+
|
|
584
|
+
- name: Setup Rust
|
|
585
|
+
if: steps.runtime.outputs.runtime == 'rust'
|
|
586
|
+
uses: actions-rust-lang/setup-rust-toolchain@v1
|
|
587
|
+
|
|
588
|
+
- name: Install dependencies
|
|
589
|
+
run: |
|
|
590
|
+
case "${{ steps.runtime.outputs.runtime }}" in
|
|
591
|
+
js)
|
|
592
|
+
if [ -f "package.json" ]; then
|
|
593
|
+
case "${{ steps.runtime.outputs.package_manager }}" in
|
|
594
|
+
bun) bun install ;;
|
|
595
|
+
pnpm) npm install -g pnpm && pnpm install ;;
|
|
596
|
+
yarn) yarn install ;;
|
|
597
|
+
npm) npm install ;;
|
|
598
|
+
esac
|
|
599
|
+
fi
|
|
600
|
+
;;
|
|
601
|
+
python)
|
|
602
|
+
if [ -f "requirements.txt" ]; then
|
|
603
|
+
pip install -r requirements.txt
|
|
604
|
+
elif [ -f "pyproject.toml" ]; then
|
|
605
|
+
if [ "${{ steps.runtime.outputs.package_manager }}" = "poetry" ]; then
|
|
606
|
+
pip install poetry && poetry install
|
|
607
|
+
else
|
|
608
|
+
pip install .
|
|
609
|
+
fi
|
|
610
|
+
fi
|
|
611
|
+
;;
|
|
612
|
+
go)
|
|
613
|
+
if [ -f "go.mod" ]; then
|
|
614
|
+
go mod download
|
|
615
|
+
fi
|
|
616
|
+
;;
|
|
617
|
+
rust)
|
|
618
|
+
if [ -f "Cargo.toml" ]; then
|
|
619
|
+
cargo fetch
|
|
620
|
+
fi
|
|
621
|
+
;;
|
|
622
|
+
esac
|
|
623
|
+
|
|
624
|
+
- name: Run build checks
|
|
625
|
+
id: build_checks
|
|
626
|
+
run: |
|
|
627
|
+
mkdir -p /tmp/build-results
|
|
628
|
+
|
|
629
|
+
# Initialize statuses
|
|
630
|
+
BUILD_STATUS="skip"
|
|
631
|
+
TESTS_STATUS="skip"
|
|
632
|
+
LINT_STATUS="skip"
|
|
633
|
+
TYPECHECK_STATUS="skip"
|
|
634
|
+
|
|
635
|
+
# Run build
|
|
636
|
+
echo "=== Running build ===" > /tmp/build-results/build.txt
|
|
637
|
+
case "${{ steps.runtime.outputs.runtime }}" in
|
|
638
|
+
js)
|
|
639
|
+
if [ -f "package.json" ] && grep -q '"build"' package.json; then
|
|
640
|
+
if ${{ steps.runtime.outputs.package_manager }} run build >> /tmp/build-results/build.txt 2>&1; then
|
|
641
|
+
BUILD_STATUS="pass"
|
|
642
|
+
else
|
|
643
|
+
BUILD_STATUS="fail"
|
|
644
|
+
fi
|
|
645
|
+
else
|
|
646
|
+
echo "No build script found" >> /tmp/build-results/build.txt
|
|
647
|
+
fi
|
|
648
|
+
;;
|
|
649
|
+
python)
|
|
650
|
+
if [ -f "setup.py" ] || ([ -f "pyproject.toml" ] && grep -q '\[build-system\]' pyproject.toml); then
|
|
651
|
+
if python -m build >> /tmp/build-results/build.txt 2>&1; then
|
|
652
|
+
BUILD_STATUS="pass"
|
|
653
|
+
else
|
|
654
|
+
BUILD_STATUS="fail"
|
|
655
|
+
fi
|
|
656
|
+
else
|
|
657
|
+
echo "No build system found" >> /tmp/build-results/build.txt
|
|
658
|
+
fi
|
|
659
|
+
;;
|
|
660
|
+
go)
|
|
661
|
+
if go build ./... >> /tmp/build-results/build.txt 2>&1; then
|
|
662
|
+
BUILD_STATUS="pass"
|
|
663
|
+
else
|
|
664
|
+
BUILD_STATUS="fail"
|
|
665
|
+
fi
|
|
666
|
+
;;
|
|
667
|
+
rust)
|
|
668
|
+
if cargo build >> /tmp/build-results/build.txt 2>&1; then
|
|
669
|
+
BUILD_STATUS="pass"
|
|
670
|
+
else
|
|
671
|
+
BUILD_STATUS="fail"
|
|
672
|
+
fi
|
|
673
|
+
;;
|
|
674
|
+
esac
|
|
675
|
+
echo "BUILD_STATUS=$BUILD_STATUS" >> $GITHUB_OUTPUT
|
|
676
|
+
|
|
677
|
+
# Run tests
|
|
678
|
+
echo "=== Running tests ===" > /tmp/build-results/tests.txt
|
|
679
|
+
case "${{ steps.runtime.outputs.runtime }}" in
|
|
680
|
+
js)
|
|
681
|
+
if [ -f "package.json" ] && grep -q '"test"' package.json; then
|
|
682
|
+
if ${{ steps.runtime.outputs.package_manager }} test >> /tmp/build-results/tests.txt 2>&1; then
|
|
683
|
+
TESTS_STATUS="pass"
|
|
684
|
+
else
|
|
685
|
+
TESTS_STATUS="fail"
|
|
686
|
+
fi
|
|
687
|
+
else
|
|
688
|
+
echo "No test script found" >> /tmp/build-results/tests.txt
|
|
689
|
+
fi
|
|
690
|
+
;;
|
|
691
|
+
python)
|
|
692
|
+
if command -v pytest >/dev/null 2>&1; then
|
|
693
|
+
if pytest >> /tmp/build-results/tests.txt 2>&1; then
|
|
694
|
+
TESTS_STATUS="pass"
|
|
695
|
+
else
|
|
696
|
+
TESTS_STATUS="fail"
|
|
697
|
+
fi
|
|
698
|
+
else
|
|
699
|
+
echo "pytest not found" >> /tmp/build-results/tests.txt
|
|
700
|
+
fi
|
|
701
|
+
;;
|
|
702
|
+
go)
|
|
703
|
+
if go test ./... >> /tmp/build-results/tests.txt 2>&1; then
|
|
704
|
+
TESTS_STATUS="pass"
|
|
705
|
+
else
|
|
706
|
+
TESTS_STATUS="fail"
|
|
707
|
+
fi
|
|
708
|
+
;;
|
|
709
|
+
rust)
|
|
710
|
+
if cargo test >> /tmp/build-results/tests.txt 2>&1; then
|
|
711
|
+
TESTS_STATUS="pass"
|
|
712
|
+
else
|
|
713
|
+
TESTS_STATUS="fail"
|
|
714
|
+
fi
|
|
715
|
+
;;
|
|
716
|
+
esac
|
|
717
|
+
echo "TESTS_STATUS=$TESTS_STATUS" >> $GITHUB_OUTPUT
|
|
718
|
+
|
|
719
|
+
# Run lint
|
|
720
|
+
echo "=== Running lint ===" > /tmp/build-results/lint.txt
|
|
721
|
+
case "${{ steps.runtime.outputs.runtime }}" in
|
|
722
|
+
js)
|
|
723
|
+
if [ -f "package.json" ] && grep -q '"lint"' package.json; then
|
|
724
|
+
if ${{ steps.runtime.outputs.package_manager }} run lint >> /tmp/build-results/lint.txt 2>&1; then
|
|
725
|
+
LINT_STATUS="pass"
|
|
726
|
+
else
|
|
727
|
+
LINT_STATUS="fail"
|
|
728
|
+
fi
|
|
729
|
+
else
|
|
730
|
+
echo "No lint script found" >> /tmp/build-results/lint.txt
|
|
731
|
+
fi
|
|
732
|
+
;;
|
|
733
|
+
python)
|
|
734
|
+
if command -v ruff >/dev/null 2>&1; then
|
|
735
|
+
if ruff check . >> /tmp/build-results/lint.txt 2>&1; then
|
|
736
|
+
LINT_STATUS="pass"
|
|
737
|
+
else
|
|
738
|
+
LINT_STATUS="fail"
|
|
739
|
+
fi
|
|
740
|
+
else
|
|
741
|
+
echo "ruff not found" >> /tmp/build-results/lint.txt
|
|
742
|
+
fi
|
|
743
|
+
;;
|
|
744
|
+
go)
|
|
745
|
+
if command -v golint >/dev/null 2>&1; then
|
|
746
|
+
if golint ./... >> /tmp/build-results/lint.txt 2>&1; then
|
|
747
|
+
LINT_STATUS="pass"
|
|
748
|
+
else
|
|
749
|
+
LINT_STATUS="fail"
|
|
750
|
+
fi
|
|
751
|
+
else
|
|
752
|
+
echo "golint not found" >> /tmp/build-results/lint.txt
|
|
753
|
+
fi
|
|
754
|
+
;;
|
|
755
|
+
rust)
|
|
756
|
+
if cargo clippy >> /tmp/build-results/lint.txt 2>&1; then
|
|
757
|
+
LINT_STATUS="pass"
|
|
758
|
+
else
|
|
759
|
+
LINT_STATUS="fail"
|
|
760
|
+
fi
|
|
761
|
+
;;
|
|
762
|
+
esac
|
|
763
|
+
echo "LINT_STATUS=$LINT_STATUS" >> $GITHUB_OUTPUT
|
|
764
|
+
|
|
765
|
+
# Run typecheck
|
|
766
|
+
echo "=== Running typecheck ===" > /tmp/build-results/typecheck.txt
|
|
767
|
+
case "${{ steps.runtime.outputs.runtime }}" in
|
|
768
|
+
js)
|
|
769
|
+
if [ -f "tsconfig.json" ]; then
|
|
770
|
+
if npx tsc --noEmit >> /tmp/build-results/typecheck.txt 2>&1; then
|
|
771
|
+
TYPECHECK_STATUS="pass"
|
|
772
|
+
else
|
|
773
|
+
TYPECHECK_STATUS="fail"
|
|
774
|
+
fi
|
|
775
|
+
else
|
|
776
|
+
echo "No tsconfig.json found" >> /tmp/build-results/typecheck.txt
|
|
777
|
+
fi
|
|
778
|
+
;;
|
|
779
|
+
python)
|
|
780
|
+
if command -v mypy >/dev/null 2>&1; then
|
|
781
|
+
if mypy . >> /tmp/build-results/typecheck.txt 2>&1; then
|
|
782
|
+
TYPECHECK_STATUS="pass"
|
|
783
|
+
else
|
|
784
|
+
TYPECHECK_STATUS="fail"
|
|
785
|
+
fi
|
|
786
|
+
else
|
|
787
|
+
echo "mypy not found" >> /tmp/build-results/typecheck.txt
|
|
788
|
+
fi
|
|
789
|
+
;;
|
|
790
|
+
*)
|
|
791
|
+
echo "Typecheck not applicable for ${{ steps.runtime.outputs.runtime }}" >> /tmp/build-results/typecheck.txt
|
|
792
|
+
;;
|
|
793
|
+
esac
|
|
794
|
+
echo "TYPECHECK_STATUS=$TYPECHECK_STATUS" >> $GITHUB_OUTPUT
|
|
795
|
+
|
|
796
|
+
# Combine all results
|
|
797
|
+
cat /tmp/build-results/*.txt > /tmp/build-results/all.txt
|
|
798
|
+
echo "Build: $BUILD_STATUS"
|
|
799
|
+
echo "Tests: $TESTS_STATUS"
|
|
800
|
+
echo "Lint: $LINT_STATUS"
|
|
801
|
+
echo "TypeCheck: $TYPECHECK_STATUS"
|
|
802
|
+
|
|
803
|
+
- name: Run verification
|
|
804
|
+
if: ${{ inputs.dry_run != true }}
|
|
805
|
+
env:
|
|
806
|
+
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
|
|
807
|
+
run: |
|
|
808
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
809
|
+
|
|
810
|
+
# Read verify prompt template from installed location
|
|
811
|
+
VERIFY_TEMPLATE=$(cat .github/claude-parallel/prompts/verify.md)
|
|
812
|
+
|
|
813
|
+
# Read build output for substitution
|
|
814
|
+
BUILD_OUTPUT=$(cat /tmp/build-results/all.txt)
|
|
815
|
+
|
|
816
|
+
# Substitute template variables
|
|
817
|
+
VERIFY_PROMPT=$(echo "$VERIFY_TEMPLATE" | sed \
|
|
818
|
+
-e "s|{{LINEAR_ISSUE}}|${{ needs.generate-matrix.outputs.linear_issue }}|g" \
|
|
819
|
+
-e "s|{{WINNING_BRANCH}}|${{ needs.review.outputs.winning_branch }}|g" \
|
|
820
|
+
-e "s|{{PR_NUMBER}}|${{ needs.review.outputs.pr_number }}|g" \
|
|
821
|
+
-e "s|{{BUILD_STATUS}}|${{ steps.build_checks.outputs.BUILD_STATUS }}|g" \
|
|
822
|
+
-e "s|{{TESTS_STATUS}}|${{ steps.build_checks.outputs.TESTS_STATUS }}|g" \
|
|
823
|
+
-e "s|{{LINT_STATUS}}|${{ steps.build_checks.outputs.LINT_STATUS }}|g" \
|
|
824
|
+
-e "s|{{TYPECHECK_STATUS}}|${{ steps.build_checks.outputs.TYPECHECK_STATUS }}|g" \
|
|
825
|
+
| awk -v build="$BUILD_OUTPUT" '{gsub(/\{\{BUILD_OUTPUT\}\}/, build); print}')
|
|
826
|
+
|
|
827
|
+
echo "Starting verification..."
|
|
828
|
+
echo "Linear issue: ${{ needs.generate-matrix.outputs.linear_issue }}"
|
|
829
|
+
|
|
830
|
+
# Run agent runner in implementation mode
|
|
831
|
+
echo "$VERIFY_PROMPT" | node .github/claude-parallel/scripts/claude-agent-runner.js \
|
|
832
|
+
--cwd "$(pwd)" \
|
|
833
|
+
--model ${{ inputs.claude_model || 'claude-opus-4-5-20251101' }} \
|
|
834
|
+
--mode implementation \
|
|
835
|
+
> verify-result.json 2> verify-error.log || true
|
|
836
|
+
|
|
837
|
+
cat verify-result.json
|
|
838
|
+
|
|
839
|
+
- name: Mock verification (dry run)
|
|
840
|
+
if: ${{ inputs.dry_run == true }}
|
|
841
|
+
run: |
|
|
842
|
+
# Create mock verification result based on build checks
|
|
843
|
+
BUILD="${{ steps.build_checks.outputs.BUILD_STATUS }}"
|
|
844
|
+
TESTS="${{ steps.build_checks.outputs.TESTS_STATUS }}"
|
|
845
|
+
|
|
846
|
+
if [ "$BUILD" = "pass" ] || [ "$BUILD" = "skip" ]; then
|
|
847
|
+
VERIFIED="true"
|
|
848
|
+
SUMMARY="Dry run verification passed"
|
|
849
|
+
else
|
|
850
|
+
VERIFIED="false"
|
|
851
|
+
SUMMARY="Dry run verification failed due to build errors"
|
|
852
|
+
fi
|
|
853
|
+
|
|
854
|
+
# Create mock result (no leading whitespace - matches Claude output format)
|
|
855
|
+
echo "{\"result\": \"Mock verification complete. {\\\"verified\\\": $VERIFIED, \\\"summary\\\": \\\"$SUMMARY\\\", \\\"issues\\\": []}\"}" > verify-result.json
|
|
856
|
+
cat verify-result.json
|
|
857
|
+
|
|
858
|
+
- name: Post verification results to PR
|
|
859
|
+
env:
|
|
860
|
+
GH_TOKEN: ${{ secrets.GH_PAT }}
|
|
861
|
+
run: |
|
|
862
|
+
# Use pre-computed build results
|
|
863
|
+
BUILD="${{ steps.build_checks.outputs.BUILD_STATUS }}"
|
|
864
|
+
TESTS="${{ steps.build_checks.outputs.TESTS_STATUS }}"
|
|
865
|
+
LINT="${{ steps.build_checks.outputs.LINT_STATUS }}"
|
|
866
|
+
TYPECHECK="${{ steps.build_checks.outputs.TYPECHECK_STATUS }}"
|
|
867
|
+
|
|
868
|
+
# Extract the verification result for summary and issues
|
|
869
|
+
RESULT=$(jq -r '.result // .content[0].text // .text // .' verify-result.json)
|
|
870
|
+
VERIFY_JSON=$(echo "$RESULT" | grep -oE '\{[^{}]*"verified"[^{}]*\}' | tail -1)
|
|
871
|
+
|
|
872
|
+
if [ -z "$VERIFY_JSON" ]; then
|
|
873
|
+
VERIFIED="false"
|
|
874
|
+
SUMMARY="Could not parse Claude verification output"
|
|
875
|
+
ISSUES=""
|
|
876
|
+
else
|
|
877
|
+
VERIFIED=$(echo "$VERIFY_JSON" | jq -r '.verified // false')
|
|
878
|
+
SUMMARY=$(echo "$VERIFY_JSON" | jq -r '.summary // "No summary"')
|
|
879
|
+
ISSUES=$(echo "$VERIFY_JSON" | jq -r '.issues // [] | .[]' | sed 's/^/- /')
|
|
880
|
+
fi
|
|
881
|
+
|
|
882
|
+
# Determine overall status
|
|
883
|
+
if [ "$BUILD" = "fail" ] || [ "$TESTS" = "fail" ]; then
|
|
884
|
+
STATUS=":x: **Verification Failed**"
|
|
885
|
+
elif [ "$VERIFIED" = "true" ]; then
|
|
886
|
+
STATUS=":white_check_mark: **Verified**"
|
|
887
|
+
else
|
|
888
|
+
STATUS=":warning: **Needs Review**"
|
|
889
|
+
fi
|
|
890
|
+
|
|
891
|
+
# Format status icons
|
|
892
|
+
format_status() {
|
|
893
|
+
case "$1" in
|
|
894
|
+
pass) echo ":white_check_mark: pass" ;;
|
|
895
|
+
fail) echo ":x: fail" ;;
|
|
896
|
+
skip) echo ":heavy_minus_sign: skip" ;;
|
|
897
|
+
*) echo ":question: $1" ;;
|
|
898
|
+
esac
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
COMMENT="## Verification Results
|
|
902
|
+
|
|
903
|
+
$STATUS
|
|
904
|
+
|
|
905
|
+
| Check | Status |
|
|
906
|
+
|-------|--------|
|
|
907
|
+
| Build | $(format_status "$BUILD") |
|
|
908
|
+
| Tests | $(format_status "$TESTS") |
|
|
909
|
+
| Lint | $(format_status "$LINT") |
|
|
910
|
+
| TypeCheck | $(format_status "$TYPECHECK") |
|
|
911
|
+
|
|
912
|
+
### Summary
|
|
913
|
+
$SUMMARY"
|
|
914
|
+
|
|
915
|
+
if [ -n "$ISSUES" ]; then
|
|
916
|
+
COMMENT="$COMMENT
|
|
917
|
+
|
|
918
|
+
### Issues Found
|
|
919
|
+
$ISSUES"
|
|
920
|
+
fi
|
|
921
|
+
|
|
922
|
+
gh pr comment "${{ needs.review.outputs.pr_number }}" --body "$COMMENT"
|
|
923
|
+
|
|
924
|
+
- name: Upload verification result
|
|
925
|
+
uses: actions/upload-artifact@v4
|
|
926
|
+
with:
|
|
927
|
+
name: verify-result
|
|
928
|
+
path: |
|
|
929
|
+
verify-result.json
|
|
930
|
+
verify-error.log
|
|
931
|
+
retention-days: 7
|