diff --git a/bin/gemini-loop.sh b/bin/gemini-loop.sh index 2954942..39fad0c 100755 --- a/bin/gemini-loop.sh +++ b/bin/gemini-loop.sh @@ -11,7 +11,7 @@ set -euo pipefail NUM_WORKERS="${1:-5}" MAX_WORKERS=12 WORKTREE_BASE="$HOME/worktrees" -GITEA_URL="http://143.198.27.163:3000" +GITEA_URL="${GITEA_URL:-https://forge.alexanderwhitestone.com}" GITEA_TOKEN=$(cat "$HOME/.hermes/gemini_token") GEMINI_TIMEOUT=600 # 10 min per issue COOLDOWN=2 # seconds between issues — max speed diff --git a/bin/kimi-loop.sh b/bin/kimi-loop.sh index df4fe7a..d478963 100755 --- a/bin/kimi-loop.sh +++ b/bin/kimi-loop.sh @@ -15,7 +15,7 @@ set -euo pipefail # === CONFIG === REPO_DIR="$HOME/worktrees/kimi-repo" WORKTREE_BASE="$HOME/worktrees" -GITEA_URL="http://143.198.27.163:3000" +GITEA_URL="${GITEA_URL:-https://forge.alexanderwhitestone.com}" GITEA_TOKEN=$(cat "$HOME/.hermes/kimi_token") REPO_OWNER="rockachopa" REPO_NAME="Timmy-time-dashboard" @@ -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] (#${issue_num})", "body": "Fixes #${issue_num}\n\n", "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] (#${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. "}' + + +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: + - push: ok + - pr: + - verification: + - tox -e format: + - tox -e unit: + - tox -e lint: + - summary: 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 diff --git a/bin/timmy-orchestrator.sh b/bin/timmy-orchestrator.sh index 3b68083..c68cefd 100755 --- a/bin/timmy-orchestrator.sh +++ b/bin/timmy-orchestrator.sh @@ -8,10 +8,10 @@ set -uo pipefail LOG_DIR="$HOME/.hermes/logs" LOG="$LOG_DIR/timmy-orchestrator.log" PIDFILE="$LOG_DIR/timmy-orchestrator.pid" -GITEA_URL="http://143.198.27.163:3000" +GITEA_URL="${GITEA_URL:-https://forge.alexanderwhitestone.com}" GITEA_TOKEN=$(cat "$HOME/.config/gitea/token" 2>/dev/null) CYCLE_INTERVAL=300 -HERMES_TIMEOUT=180 +HERMES_TIMEOUT=300 mkdir -p "$LOG_DIR" @@ -158,7 +158,14 @@ HEADER fi echo "" >> "$prompt_file" - echo "Review each PR above. Execute curl commands for your decisions. Be brief." >> "$prompt_file" + cat >> "$prompt_file" <<'FOOTER' +INSTRUCTIONS: For EACH PR above, do ONE of the following RIGHT NOW using your terminal tool: +- Run the merge curl command if the diff looks good +- Run the close curl command if it is a duplicate or garbage +- Run the comment curl command only if there is a clear bug + +IMPORTANT: Actually run the curl commands in your terminal. Do not just describe what you would do. Most PRs from agents are fine — merge aggressively. Output only the commands you ran and their results. +FOOTER local prompt_text prompt_text=$(cat "$prompt_file") diff --git a/memories/MEMORY.md b/memories/MEMORY.md index 1955563..b0bd3fe 100644 --- a/memories/MEMORY.md +++ b/memories/MEMORY.md @@ -1,6 +1,4 @@ -Gitea (localhost:3000): Users: rockachopa(admin), hermes(id=4), manus(id=3), kimi(id=5). Repos: rockachopa/Timmy-time-dashboard, hermes/hermes-config (private, rockachopa=admin collab). Hermes token: ~/.hermes/gitea_token. DO NOT use Alex's token — ever. Sync tool: ~/.hermes/bin/hermes-config-sync — run after modifying config/scripts/memories/skills. Future work: agentd user isolation. -§ -Timmy architecture plan: Replace hardcoded _PERSONAS and TimmyOrchestrator with YAML-driven agent config (agents.yaml). One seed class, all differentiation via config. User wants to update YAML files, not Python, to add capabilities. Key files to refactor: agents/timmy.py, agents/base.py, tools_delegation/__init__.py, tools_intro/__init__.py. SubAgent class stays mostly as-is, just reads from YAML. Routing should be config-based pattern matching, not LLM calls. Per-agent model assignment (big model for orchestrator/code/research, small for simple tasks). qwen3:30b pulling as primary local model (~18GB, MoE 3B active). +Gitea (143.198.27.163:3000): Organization: Timmy_Foundation (rockachopa, Timmy, perplexity = Owners). Active repos: Timmy_Foundation/hermes-agent (sovereign agent runtime, primary), Timmy_Foundation/the-nexus (3D environment, Timmy's home), hermes/hermes-config (operational config, scripts, skills, memories), Rockachopa/alexanderwhitestone.com (public site). Frozen/retired: rockachopa/Timmy-time-dashboard (ARCHIVED, do not use), Timmy/the-matrix (empty). Agent accounts: claude, kimi, hermes, perplexity, google, replit, manus. Hermes token: ~/.hermes/gitea_token. DO NOT use Alex's token — ever. Sync tool: ~/.hermes/bin/hermes-config-sync — run after modifying config/scripts/memories/skills. § Kimi delegation: 1-3 files max, pre-extract context (scans full codebase otherwise), ~/worktrees/ not /tmp/, verify commits, two-attempt rule. Worktrees: ~/worktrees/kimi-*. §