fix: require push pr and proof for kimi loop (#2)
This commit was merged in pull request #2.
This commit is contained in:
241
bin/kimi-loop.sh
241
bin/kimi-loop.sh
@@ -82,7 +82,108 @@ log() {
|
||||
echo "$msg" >> "$LOG_DIR/kimi-loop.log"
|
||||
}
|
||||
|
||||
post_issue_comment() {
|
||||
local issue_num="$1"
|
||||
local body="$2"
|
||||
local payload
|
||||
payload=$(python3 - "$body" <<'PY'
|
||||
import json, sys
|
||||
print(json.dumps({"body": sys.argv[1]}))
|
||||
PY
|
||||
)
|
||||
curl -sf -X POST "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/issues/${issue_num}/comments" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$payload" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
|
||||
remote_branch_exists() {
|
||||
local branch="$1"
|
||||
git ls-remote --heads origin "$branch" 2>/dev/null | grep -q .
|
||||
}
|
||||
|
||||
get_pr_num() {
|
||||
local branch="$1"
|
||||
curl -sf "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/pulls?state=all&head=${REPO_OWNER}:${branch}&limit=1" -H "Authorization: token ${GITEA_TOKEN}" | python3 -c "
|
||||
import sys,json
|
||||
prs = json.load(sys.stdin)
|
||||
if prs: print(prs[0]['number'])
|
||||
else: print('')
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
get_pr_file_count() {
|
||||
local pr_num="$1"
|
||||
curl -sf "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/pulls/${pr_num}/files" -H "Authorization: token ${GITEA_TOKEN}" | python3 -c "
|
||||
import sys, json
|
||||
try:
|
||||
files = json.load(sys.stdin)
|
||||
print(len(files) if isinstance(files, list) else 0)
|
||||
except:
|
||||
print(0)
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
get_pr_state() {
|
||||
local pr_num="$1"
|
||||
curl -sf "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/pulls/${pr_num}" -H "Authorization: token ${GITEA_TOKEN}" | python3 -c "
|
||||
import sys, json
|
||||
try:
|
||||
pr = json.load(sys.stdin)
|
||||
if pr.get('merged'):
|
||||
print('merged')
|
||||
else:
|
||||
print(pr.get('state', 'unknown'))
|
||||
except:
|
||||
print('unknown')
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
get_issue_state() {
|
||||
local issue_num="$1"
|
||||
curl -sf "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/issues/${issue_num}" -H "Authorization: token ${GITEA_TOKEN}" | python3 -c "
|
||||
import sys, json
|
||||
try:
|
||||
issue = json.load(sys.stdin)
|
||||
print(issue.get('state', 'unknown'))
|
||||
except:
|
||||
print('unknown')
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
proof_comment_status() {
|
||||
local issue_num="$1"
|
||||
local branch="$2"
|
||||
curl -sf "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/issues/${issue_num}/comments" -H "Authorization: token ${GITEA_TOKEN}" | BRANCH="$branch" python3 -c "
|
||||
import os, sys, json
|
||||
branch = os.environ.get('BRANCH', '').lower()
|
||||
try:
|
||||
comments = json.load(sys.stdin)
|
||||
except Exception:
|
||||
print('missing|')
|
||||
raise SystemExit(0)
|
||||
for c in reversed(comments):
|
||||
user = ((c.get('user') or {}).get('login') or '').lower()
|
||||
body = c.get('body') or ''
|
||||
body_l = body.lower()
|
||||
if user != 'kimi':
|
||||
continue
|
||||
if 'proof:' not in body_l and 'verification:' not in body_l:
|
||||
continue
|
||||
has_branch = branch in body_l
|
||||
has_pr = ('pr:' in body_l) or ('pull request:' in body_l) or ('/pulls/' in body_l)
|
||||
has_push = ('push:' in body_l) or ('pushed' in body_l)
|
||||
has_verify = ('tox -e unit' in body_l) or ('pytest' in body_l) or ('verification:' in body_l)
|
||||
status = 'ok' if (has_branch and has_pr and has_push and has_verify) else 'incomplete'
|
||||
print(status + '|' + (c.get('html_url') or ''))
|
||||
raise SystemExit(0)
|
||||
print('missing|')
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
cleanup_worktree() {
|
||||
|
||||
local wt="$1"
|
||||
local branch="$2"
|
||||
if [ -d "$wt" ]; then
|
||||
@@ -164,19 +265,36 @@ You can do ANYTHING a developer can do. You are not limited to the narrow task.
|
||||
- tox -e lint (must be clean)
|
||||
|
||||
3. COMMIT with conventional commits: fix: / feat: / refactor: / test: / chore:
|
||||
ALWAYS stage files first — NEVER commit without git add:
|
||||
git add .
|
||||
git diff --cached --stat # verify non-empty — abort if nothing staged
|
||||
git commit -m "fix: ... Fixes #${issue_num}"
|
||||
Include "Fixes #${issue_num}" or "Refs #${issue_num}" in the message.
|
||||
If git diff --cached --stat shows nothing, DO NOT commit or create a PR —
|
||||
comment on the issue explaining what you found instead.
|
||||
|
||||
4. PUSH to your branch (kimi/issue-${issue_num}) and CREATE A PR:
|
||||
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/pulls" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"title": "[kimi] <description> (#${issue_num})", "body": "Fixes #${issue_num}\n\n<describe what you did>", "head": "kimi/issue-${issue_num}", "base": "main"}'
|
||||
4. PUSH to your branch (kimi/issue-${issue_num}) and CREATE A PR.
|
||||
Capture the PR URL in your output and in the issue comment.
|
||||
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/pulls" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d '{"title": "[kimi] <description> (#${issue_num})", "body": "Fixes #${issue_num}
|
||||
|
||||
5. COMMENT on the issue when done:
|
||||
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/issues/${issue_num}/comments" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"body": "PR created. <summary of changes>"}'
|
||||
<describe what you did>
|
||||
|
||||
Verification:
|
||||
- tox -e format
|
||||
- tox -e unit
|
||||
- tox -e lint", "head": "kimi/issue-${issue_num}", "base": "main"}'
|
||||
|
||||
5. COMMENT on the issue with a PROOF BLOCK before you exit. Use this exact shape:
|
||||
Proof:
|
||||
- branch: kimi/issue-${issue_num}
|
||||
- commit: <full sha>
|
||||
- push: ok
|
||||
- pr: <full PR url>
|
||||
- verification:
|
||||
- tox -e format: <pass/fail>
|
||||
- tox -e unit: <pass/fail>
|
||||
- tox -e lint: <pass/fail>
|
||||
- summary: <what changed>
|
||||
|
||||
6. FILE NEW ISSUES if you find bugs, missing tests, or improvements while working:
|
||||
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/issues" \
|
||||
@@ -190,6 +308,16 @@ You can do ANYTHING a developer can do. You are not limited to the narrow task.
|
||||
- Never use --no-verify on git commands.
|
||||
- If tests fail after 2 attempts, STOP and comment on the issue explaining why.
|
||||
- Be thorough. If you see something broken nearby, file an issue for it.
|
||||
- ALWAYS run `git add .` before `git commit`. NEVER create an empty commit.
|
||||
- ALWAYS check `git diff --cached --stat` before committing — if empty, do NOT commit or create a PR.
|
||||
- DO NOT claim success unless the branch exists on Gitea, the PR exists, and the Proof block is posted on the issue.
|
||||
|
||||
== CRITICAL: FINISH = PUSHED + PR'D + PROVED ==
|
||||
- NEVER exit without committing your work. Even partial progress MUST be committed.
|
||||
- Before you finish, ALWAYS: git add -A && git commit && git push origin kimi/issue-${issue_num}
|
||||
- ALWAYS create a PR before exiting. No exceptions.
|
||||
- ALWAYS post the Proof block before exiting. No proof comment = not done.
|
||||
- Your work is WASTED if it's not pushed. Push early, push often.
|
||||
PROMPT
|
||||
}
|
||||
|
||||
@@ -271,37 +399,76 @@ while true; do
|
||||
set -e
|
||||
|
||||
if [ "$exit_code" -eq 0 ]; then
|
||||
log "SUCCESS: #${issue_num} completed — attempting auto-merge..."
|
||||
log "SUCCESS: #${issue_num} exited 0 — verifying push + PR + proof..."
|
||||
|
||||
# Find and merge the PR kimi created
|
||||
pr_num=$(curl -sf "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/pulls?state=open&head=${REPO_OWNER}:${branch}&limit=1" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" | python3 -c "
|
||||
import sys,json
|
||||
prs = json.load(sys.stdin)
|
||||
if prs: print(prs[0]['number'])
|
||||
else: print('')
|
||||
" 2>/dev/null)
|
||||
|
||||
if [ -n "$pr_num" ]; then
|
||||
merge_result=$(curl -sf -X POST "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/pulls/${pr_num}/merge" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"Do": "squash"}' 2>&1) || true
|
||||
log " PR #${pr_num} merge attempted"
|
||||
|
||||
# Close the issue (Gitea auto-close via "Fixes #N" is unreliable)
|
||||
curl -sf -X PATCH "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/issues/${issue_num}" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"state": "closed"}' >/dev/null 2>&1 || true
|
||||
log " Issue #${issue_num} closed"
|
||||
cd "$worktree"
|
||||
if ! remote_branch_exists "$branch"; then
|
||||
log " BLOCKED: remote branch ${branch} not found — work was not pushed"
|
||||
post_issue_comment "$issue_num" "Loop gate blocked completion: remote branch \`${branch}\` was not found on origin after Kimi exited. Issue remains open for retry."
|
||||
mark_skip "$issue_num" "missing_remote_branch" 1
|
||||
failure_count=$((failure_count + 1))
|
||||
cd "$REPO_DIR"
|
||||
else
|
||||
log " WARN: No open PR found for branch ${branch}"
|
||||
cd "$REPO_DIR"
|
||||
pr_num=$(get_pr_num "$branch")
|
||||
|
||||
if [ -z "$pr_num" ]; then
|
||||
log " BLOCKED: no PR found for branch ${branch}"
|
||||
post_issue_comment "$issue_num" "Loop gate blocked completion: branch \`${branch}\` exists remotely, but no pull request was found. Issue remains open for retry."
|
||||
mark_skip "$issue_num" "missing_pr" 1
|
||||
failure_count=$((failure_count + 1))
|
||||
else
|
||||
pr_files=$(get_pr_file_count "$pr_num")
|
||||
if [ "${pr_files:-0}" -eq 0 ]; then
|
||||
log " BLOCKED: PR #${pr_num} has 0 changed files — empty commit detected, closing PR without merge"
|
||||
curl -sf -X PATCH "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/pulls/${pr_num}" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d '{"state": "closed"}' >/dev/null 2>&1 || true
|
||||
post_issue_comment "$issue_num" "PR #${pr_num} was closed automatically: it had 0 changed files (empty commit). Kimi must stage files with \`git add\` before committing. Issue remains open for retry."
|
||||
mark_skip "$issue_num" "empty_commit" 2
|
||||
failure_count=$((failure_count + 1))
|
||||
else
|
||||
proof_status=$(proof_comment_status "$issue_num" "$branch")
|
||||
proof_state="${proof_status%%|*}"
|
||||
proof_url="${proof_status#*|}"
|
||||
|
||||
if [ "$proof_state" != "ok" ]; then
|
||||
log " BLOCKED: proof comment missing or incomplete (${proof_state})"
|
||||
post_issue_comment "$issue_num" "Loop gate blocked completion: PR #${pr_num} exists and has ${pr_files} changed file(s), but the required Proof block from Kimi is missing or incomplete. Issue remains open for retry."
|
||||
mark_skip "$issue_num" "missing_proof" 1
|
||||
failure_count=$((failure_count + 1))
|
||||
else
|
||||
log " PROOF: verified issue comment ${proof_url}"
|
||||
pr_state=$(get_pr_state "$pr_num")
|
||||
|
||||
if [ "$pr_state" = "open" ]; then
|
||||
curl -sf -X POST "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/pulls/${pr_num}/merge" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d '{"Do": "squash"}' >/dev/null 2>&1 || true
|
||||
pr_state=$(get_pr_state "$pr_num")
|
||||
fi
|
||||
|
||||
if [ "$pr_state" = "merged" ]; then
|
||||
curl -sf -X PATCH "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/issues/${issue_num}" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d '{"state": "closed"}' >/dev/null 2>&1 || true
|
||||
issue_state=$(get_issue_state "$issue_num")
|
||||
if [ "$issue_state" = "closed" ]; then
|
||||
log " VERIFIED: branch pushed, PR #${pr_num} merged, proof present, issue closed"
|
||||
failure_count=0
|
||||
issues_completed=$((issues_completed + 1))
|
||||
log "Stats: ${issues_completed} issues completed this session"
|
||||
else
|
||||
log " BLOCKED: PR #${pr_num} merged but issue #${issue_num} did not close"
|
||||
post_issue_comment "$issue_num" "Loop gate published and merged the work in PR #${pr_num}, but could not verify the issue closed cleanly. Please inspect state manually."
|
||||
mark_skip "$issue_num" "issue_close_unverified" 1
|
||||
failure_count=$((failure_count + 1))
|
||||
fi
|
||||
else
|
||||
log " BLOCKED: PR #${pr_num} state is ${pr_state} — merge not verified"
|
||||
post_issue_comment "$issue_num" "Loop gate published the work and verified proof, but could not verify merge for PR #${pr_num}. Issue remains open for retry/review."
|
||||
mark_skip "$issue_num" "merge_unverified" 1
|
||||
failure_count=$((failure_count + 1))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
failure_count=0
|
||||
issues_completed=$((issues_completed + 1))
|
||||
log "Stats: ${issues_completed} issues completed this session"
|
||||
elif [ "$exit_code" -eq 124 ]; then
|
||||
log "TIMEOUT: #${issue_num} exceeded ${KIMI_TIMEOUT}s"
|
||||
mark_skip "$issue_num" "timeout" 1
|
||||
|
||||
Reference in New Issue
Block a user