Skip to content

Commit fb7da14

Browse files
Copilottschm
andauthored
fix: add --dangerously-skip-permissions to Claude CI invocations and add autopilot.yml
Co-authored-by: tschm <2046079+tschm@users.noreply.github.com> Agent-Logs-Url: https://github.com/tschm/jquantstats/sessions/8911e4ee-a1fa-4291-ad17-0167c0ab878b
1 parent ee93e8e commit fb7da14

2 files changed

Lines changed: 266 additions & 0 deletions

File tree

.github/workflows/autopilot.yml

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
name: Autopilot
2+
3+
on:
4+
workflow_dispatch:
5+
schedule:
6+
- cron: '0 0 * * 1' # Weekly on Monday
7+
8+
permissions:
9+
contents: write
10+
issues: write
11+
pull-requests: write
12+
13+
jobs:
14+
# ── Job 1: Run the analyser agent and commit updated REPOSITORY_ANALYSIS.md ──
15+
analyse:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Checkout repository
19+
uses: actions/checkout@v6.0.2
20+
with:
21+
lfs: true
22+
token: ${{ secrets.GH_PAT || github.token }}
23+
24+
- name: Install uv
25+
uses: astral-sh/setup-uv@v7.6.0
26+
with:
27+
version: "0.10.12"
28+
29+
- name: Install Claude CLI
30+
run: npm install -g @anthropic-ai/claude-code
31+
32+
- name: Configure git auth for private packages
33+
uses: ./.github/actions/configure-git-auth
34+
with:
35+
token: ${{ secrets.GH_PAT }}
36+
37+
- name: Run analyse-repo
38+
env:
39+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
40+
UV_EXTRA_INDEX_URL: ${{ secrets.UV_EXTRA_INDEX_URL }}
41+
CLAUDE_BIN: claude
42+
run: make analyse-repo
43+
44+
- name: Commit and push updated analysis
45+
env:
46+
GH_PAT: ${{ secrets.GH_PAT || github.token }}
47+
run: |
48+
git config user.name "github-actions[bot]"
49+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
50+
git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "https://github.com/"
51+
git add REPOSITORY_ANALYSIS.md
52+
if ! git diff --staged --quiet; then
53+
git commit -m "chore: update REPOSITORY_ANALYSIS.md [autopilot] [skip ci]"
54+
git push origin ${{ github.ref_name }}
55+
else
56+
echo "No changes to REPOSITORY_ANALYSIS.md, skipping commit."
57+
fi
58+
59+
# ── Job 2: Parse the latest analysis entry and create up to 3 GitHub issues ──
60+
create-issues:
61+
needs: analyse
62+
runs-on: ubuntu-latest
63+
outputs:
64+
issue_numbers: ${{ steps.create.outputs.issue_numbers }}
65+
steps:
66+
- name: Checkout repository
67+
uses: actions/checkout@v6.0.2
68+
with:
69+
ref: ${{ github.ref }}
70+
71+
- name: Create autopilot label if missing
72+
env:
73+
GH_TOKEN: ${{ secrets.GH_PAT || github.token }}
74+
run: |
75+
gh label create autopilot \
76+
--color 0075ca \
77+
--description "Autopilot-generated issue" \
78+
--repo "${{ github.repository }}" 2>/dev/null || true
79+
80+
- name: Parse analysis and create issues
81+
id: create
82+
env:
83+
GH_TOKEN: ${{ secrets.GH_PAT || github.token }}
84+
run: |
85+
python3 << 'PYEOF'
86+
import os
87+
import re
88+
import json
89+
import subprocess
90+
import sys
91+
92+
with open("REPOSITORY_ANALYSIS.md") as f:
93+
content = f.read()
94+
95+
# Find the most recent dated entry (## YYYY-MM-DD …)
96+
date_pattern = re.compile(r'^(## \d{4}-\d{2}-\d{2}[^\n]*)', re.MULTILINE)
97+
entry_starts = [m.start() for m in date_pattern.finditer(content)]
98+
if not entry_starts:
99+
print("ERROR: No dated entry found in REPOSITORY_ANALYSIS.md")
100+
sys.exit(1)
101+
102+
# Latest entry is the first occurrence
103+
entry_text = content[entry_starts[0] : entry_starts[1] if len(entry_starts) > 1 else len(content)]
104+
date_header = date_pattern.search(entry_text).group(1)
105+
106+
def extract_bullets(section_text):
107+
"""Return a list of full bullet strings from a markdown bullet list."""
108+
bullets = []
109+
current = []
110+
for line in section_text.splitlines():
111+
if re.match(r'^- ', line):
112+
if current:
113+
bullets.append('\n'.join(current))
114+
current = [line[2:]] # strip leading "- "
115+
elif current and (line.startswith(' ') or line == ''):
116+
current.append(line)
117+
else:
118+
if current:
119+
bullets.append('\n'.join(current))
120+
current = []
121+
if current:
122+
bullets.append('\n'.join(current))
123+
return bullets
124+
125+
# Extract Weaknesses section
126+
weaknesses = []
127+
w_match = re.search(
128+
r'### Weaknesses\n(.*?)(?=\n### |\Z)',
129+
entry_text,
130+
re.DOTALL,
131+
)
132+
if w_match:
133+
weaknesses = extract_bullets(w_match.group(1))
134+
135+
# Extract Risks / Technical Debt section
136+
risks = []
137+
r_match = re.search(
138+
r'### Risks / Technical Debt\n(.*?)(?=\n### |\Z)',
139+
entry_text,
140+
re.DOTALL,
141+
)
142+
if r_match:
143+
risks = extract_bullets(r_match.group(1))
144+
145+
# Prioritise Weaknesses, then Risks, take up to 3
146+
items = (weaknesses + risks)[:3]
147+
148+
if not items:
149+
print("No weaknesses or risks found in the latest entry.")
150+
with open(os.environ['GITHUB_OUTPUT'], 'a') as gho:
151+
gho.write('issue_numbers=[]\n')
152+
sys.exit(0)
153+
154+
issue_numbers = []
155+
for item in items:
156+
# Title: bold phrase or first sentence (max 100 chars)
157+
bold = re.match(r'\*\*([^*]+)\*\*', item)
158+
if bold:
159+
title = bold.group(1).rstrip('.')
160+
else:
161+
title = item.split('.')[0].strip()[:100]
162+
163+
body = f"- {item}\n\n---\n*Source: {date_header}*"
164+
165+
result = subprocess.run(
166+
[
167+
'gh', 'issue', 'create',
168+
'--title', title,
169+
'--body', body,
170+
'--label', 'autopilot',
171+
],
172+
capture_output=True,
173+
text=True,
174+
check=True,
175+
)
176+
# gh prints the issue URL; extract the number from the URL
177+
url = result.stdout.strip()
178+
num = int(url.rstrip('/').split('/')[-1])
179+
issue_numbers.append(num)
180+
print(f"Created issue #{num}: {title}")
181+
182+
with open(os.environ['GITHUB_OUTPUT'], 'a') as gho:
183+
gho.write(f'issue_numbers={json.dumps(issue_numbers)}\n')
184+
PYEOF
185+
186+
# ── Job 3: Spawn Claude to solve each issue and open a PR ──
187+
solve-issues:
188+
needs: create-issues
189+
if: needs.create-issues.outputs.issue_numbers != '[]'
190+
runs-on: ubuntu-latest
191+
strategy:
192+
matrix:
193+
issue: ${{ fromJson(needs.create-issues.outputs.issue_numbers) }}
194+
fail-fast: false
195+
steps:
196+
- name: Checkout repository
197+
uses: actions/checkout@v6.0.2
198+
with:
199+
lfs: true
200+
token: ${{ secrets.GH_PAT || github.token }}
201+
202+
- name: Install uv
203+
uses: astral-sh/setup-uv@v7.6.0
204+
with:
205+
version: "0.10.12"
206+
207+
- name: Install Claude CLI
208+
run: npm install -g @anthropic-ai/claude-code
209+
210+
- name: Configure git auth for private packages
211+
uses: ./.github/actions/configure-git-auth
212+
with:
213+
token: ${{ secrets.GH_PAT }}
214+
215+
- name: Configure git identity
216+
env:
217+
GH_PAT: ${{ secrets.GH_PAT || github.token }}
218+
run: |
219+
git config user.name "github-actions[bot]"
220+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
221+
git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "https://github.com/"
222+
223+
- name: Fetch issue details
224+
id: issue
225+
env:
226+
GH_TOKEN: ${{ secrets.GH_PAT || github.token }}
227+
run: |
228+
ISSUE_JSON=$(gh issue view ${{ matrix.issue }} --json title,body)
229+
ISSUE_TITLE=$(echo "$ISSUE_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin)['title'])")
230+
ISSUE_BODY=$(echo "$ISSUE_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin)['body'])")
231+
echo "title=$ISSUE_TITLE" >> "$GITHUB_OUTPUT"
232+
# Body may be multiline — write to a temp file instead
233+
echo "$ISSUE_BODY" > /tmp/issue_body.txt
234+
235+
- name: Solve issue with Claude and open PR
236+
env:
237+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
238+
GH_TOKEN: ${{ secrets.GH_PAT || github.token }}
239+
ISSUE_NUMBER: ${{ matrix.issue }}
240+
ISSUE_TITLE: ${{ steps.issue.outputs.title }}
241+
run: |
242+
BRANCH="autopilot/fix-issue-${ISSUE_NUMBER}"
243+
ISSUE_BODY=$(cat /tmp/issue_body.txt)
244+
245+
claude --print \
246+
--dangerously-skip-permissions \
247+
--allowedTools "Bash,Write,Edit" \
248+
"You are working on the repository at $(pwd).
249+
250+
GitHub issue #${ISSUE_NUMBER}: ${ISSUE_TITLE}
251+
252+
Issue description:
253+
${ISSUE_BODY}
254+
255+
Your task:
256+
1. Create a new branch: git checkout -b ${BRANCH}
257+
2. Analyse the issue and make the minimal necessary code changes to fix it.
258+
3. Run 'make test' to verify the fix passes all tests.
259+
4. Commit your changes with a descriptive message referencing the issue.
260+
5. Push the branch: git push origin ${BRANCH}
261+
6. Open a pull request using: gh pr create --title 'fix: ${ISSUE_TITLE}' --body 'Fixes #${ISSUE_NUMBER}
262+
263+
Automated fix generated by the autopilot workflow.' --head ${BRANCH} --base ${{ github.ref_name }}
264+
265+
Keep changes minimal and focused on addressing the issue described."

.rhiza/make.d/agentic.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ claude: install-claude ## open interactive prompt for claude code
1818

1919
analyse-repo: install-claude ## run the analyser agent to update REPOSITORY_ANALYSIS.md
2020
@"$(CLAUDE_BIN)" --print \
21+
--dangerously-skip-permissions \
2122
--allowedTools "Write" \
2223
--agent .github/agents/analyser.md \
2324
"Analyze the repository and update REPOSITORY_ANALYSIS.md"

0 commit comments

Comments
 (0)