-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcursor-agent-loop.sh
More file actions
executable file
·211 lines (182 loc) · 8.71 KB
/
cursor-agent-loop.sh
File metadata and controls
executable file
·211 lines (182 loc) · 8.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#!/usr/bin/env bash
# Re-exec under bash when invoked as `sh` (often dash on Linux). This script uses
# bash-only syntax ([[, arrays, PIPESTATUS, BASH_SOURCE).
if [ -z "${BASH_VERSION+x}" ]; then
exec /usr/bin/env bash "$0" "$@"
fi
# Drive Cursor Agent CLI in a loop (non-interactive). Each iteration is one agent
# session that explores the repo, picks the next best improvement, implements it,
# runs a small quality gate, commits, and optionally pushes.
#
# Task selection (humans/agents):
# - Read CONTRIBUTING.md ("What to Contribute") and docs/SETUP.md, docs/ARCHITECTURE.md,
# docs/CUSTOMIZATION.md for direction.
# - Search for open work (repo root): rg 'TODO|FIXME|HACK' . --glob '*.mjs' --glob '*.go' --glob '*.md' --glob '*.sh'
# - Improve modes/*.md, .mjs utilities, templates/, examples/, or dashboard/ (Go TUI)
# in one cohesive slice per iteration.
# - Do not commit personal artifacts (cv.md, config/profile.yml, data/applications.md,
# reports/, user portals.yml) — see CONTRIBUTING.md and .gitignore.
#
# Quality gate (repo root):
# npm run verify
# npm run build:dashboard # same as (cd dashboard && go build .)
# verify-pipeline.mjs exits 0 when applications.md is missing (fresh clone); still
# run it. Dashboard build requires Go on PATH when touching dashboard/.
# Do not require npm run sync-check (cv-sync-check.mjs) in the gate: it expects
# personal cv.md and profile.yml and fails on a clean template checkout.
#
# Self-improve: when this script or its header comments are stale vs CONTRIBUTING.md
# or real workflows, a minimal accurate edit here is fair game for an iteration.
#
# Prerequisites:
# - Cursor Agent CLI: https://cursor.com/install (`agent` on PATH)
# - Auth: `agent login` or CURSOR_API_KEY
# - Node.js for npm run verify
# - Go (when working on dashboard/)
# - For push: configured remote; new branches may need `git push -u origin HEAD` once
#
# Environment (optional):
# CURSOR_AGENT_ITERATIONS Max agent runs (default: 100)
# CURSOR_AGENT_PUSH If 1, git push after each iteration (default: 1). 0 = no push.
# CURSOR_AGENT_FORCE_SHELL If 1, pass --force for agent shell (default: 1). Set 0 for prompts.
# CURSOR_AGENT_WORKSPACE Repo root (default: git top-level from cwd)
# CURSOR_AGENT_MODEL Passed as --model (default: composer-2)
# CURSOR_AGENT_EXTRA Extra instructions appended to the built-in prompt
# CURSOR_AGENT_VERBOSE If 1, stream agent progress via stream-json (default: 1). 0 = text only
# CURSOR_AGENT_GIT_AUTHOR_NAME Git author/committer name (default: Charlie Greenman)
# CURSOR_AGENT_GIT_AUTHOR_EMAIL Git author/committer email (default: GitHub noreply for this repo)
#
# Usage:
# ./scripts/cursor-agent-loop.sh
# CURSOR_AGENT_ITERATIONS=3 ./scripts/cursor-agent-loop.sh
# CURSOR_AGENT_PUSH=0 CURSOR_AGENT_VERBOSE=0 ./scripts/cursor-agent-loop.sh
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
STREAM_FORMATTER="${SCRIPT_DIR}/cursor-agent-stream-format.py"
ITERATIONS="${CURSOR_AGENT_ITERATIONS:-100}"
PUSH="${CURSOR_AGENT_PUSH:-1}"
FORCE_SHELL="${CURSOR_AGENT_FORCE_SHELL:-1}"
VERBOSE="${CURSOR_AGENT_VERBOSE:-1}"
WORKSPACE="${CURSOR_AGENT_WORKSPACE:-}"
MODEL="${CURSOR_AGENT_MODEL:-composer-2}"
EXTRA="${CURSOR_AGENT_EXTRA:-}"
if ! command -v agent >/dev/null 2>&1; then
echo "error: 'agent' not found. Install Cursor Agent CLI: https://cursor.com/install" >&2
exit 1
fi
if [[ "$VERBOSE" == "1" ]]; then
if ! command -v python3 >/dev/null 2>&1; then
echo "warning: python3 not found; install Python 3 or set CURSOR_AGENT_VERBOSE=0" >&2
VERBOSE=0
elif [[ ! -f "$STREAM_FORMATTER" ]]; then
echo "warning: missing ${STREAM_FORMATTER}; set CURSOR_AGENT_VERBOSE=0" >&2
VERBOSE=0
fi
fi
if [[ -z "$WORKSPACE" ]]; then
WORKSPACE="$(git rev-parse --show-toplevel 2>/dev/null)" || {
echo "error: not inside a git repository (set CURSOR_AGENT_WORKSPACE)" >&2
exit 1
}
fi
cd "$WORKSPACE"
current_branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
if [[ "$PUSH" == "1" && "$current_branch" == "main" ]]; then
echo "warning: will push to main after each iteration; use a feature branch or CURSOR_AGENT_PUSH=0 if unintended." >&2
fi
case "$ITERATIONS" in
'' | *[!0-9]*)
echo "error: CURSOR_AGENT_ITERATIONS must be a positive integer (got: ${ITERATIONS})" >&2
exit 1
;;
esac
if [[ "$ITERATIONS" -lt 1 ]]; then
echo "error: CURSOR_AGENT_ITERATIONS must be >= 1" >&2
exit 1
fi
if [[ "$PUSH" == "1" ]]; then
PUSH_TEXT="After a successful commit, do not run git push; the host script runs git push immediately after this agent exits."
else
PUSH_TEXT="Do not run git push."
fi
GIT_AUTHOR_NAME="${CURSOR_AGENT_GIT_AUTHOR_NAME:-Charlie Greenman}"
GIT_AUTHOR_EMAIL="${CURSOR_AGENT_GIT_AUTHOR_EMAIL:-CharlieGreenman@users.noreply.github.com}"
export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"
export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
build_prompt() {
cat <<EOF
You are working on JobForge, an AI-powered job-search pipeline (Claude Code modes, Node utilities, optional Go dashboard). Respect CLAUDE.md, CONTRIBUTING.md, and .cursor rules if present.
Single iteration — do exactly one cohesive, meaningful slice of work:
1. Explore the repo. Read CLAUDE.md, CONTRIBUTING.md, and skim docs/ (SETUP, ARCHITECTURE, CUSTOMIZATION). Understand modes/, batch/, templates/, and dashboard/ when relevant.
2. Decide what to work on. Priority order:
a) CONTRIBUTING.md "What to Contribute" and obvious gaps in docs or examples.
b) Search for concrete hooks: e.g. \`rg 'TODO|FIXME|HACK' . --glob '*.mjs' --glob '*.go' --glob '*.md' --glob '*.sh'\` (repo root; adjust as needed).
c) One subsystem per iteration: a mode file in modes/, a script (*.mjs), template/example polish, or dashboard (Go) — not a repo-wide refactor.
d) Self-improve this loop: if scripts/cursor-agent-loop.sh or its header is wrong or missing heuristics, fix it minimally when that is highest leverage.
Do NOT commit or "fix" user-specific files: cv.md, config/profile.yml, portals.yml with personal data, data/applications.md, reports/, or other private paths called out in CONTRIBUTING / .gitignore. Upstream improvements should stay generic.
Pick something concrete. Do NOT claim there is nothing to do.
3. Implement with minimal scope. Match existing style (markdown tone, Node ESM, Go conventions in dashboard/). Scripts should handle missing files gracefully (see CONTRIBUTING).
4. Run the quality gate from the repo root:
npm run verify
npm run build:dashboard
If you did not change dashboard/, the go build still must pass — fix any breakage you introduced. If Go is missing from the environment, say so clearly and stop rather than skipping silently.
5. If you made real changes: git add only what belongs to this task, then commit with author and message rules:
- Author must be exactly: ${GIT_AUTHOR_NAME} <${GIT_AUTHOR_EMAIL}>
Use: git commit --author="${GIT_AUTHOR_NAME} <${GIT_AUTHOR_EMAIL}>" -m "type: short description"
(GIT_AUTHOR_*/GIT_COMMITTER_* may already be exported for this session; the result must still show that author.)
- Subject/body: conventional prefix (feat:/fix:/chore:/docs:/test:/refactor:). Describe the change only.
- Do not mention Cursor, Cursor Agent, ChatGPT, Claude, automation, bots, or "AI" / "generated" in the commit message.
${PUSH_TEXT}
6. Do not force-push. Do not rewrite published history.
7. End your response with a final line: DONE
${EXTRA}
EOF
}
agent_cmd=(agent -p --trust --workspace "$WORKSPACE")
if [[ "$VERBOSE" == "1" ]]; then
agent_cmd+=(--output-format stream-json --stream-partial-output)
else
agent_cmd+=(--output-format text)
fi
if [[ "$FORCE_SHELL" == "1" ]]; then
agent_cmd+=(--force)
fi
agent_cmd+=(--model "$MODEL")
i=1
while true; do
if [[ "$i" -gt "$ITERATIONS" ]]; then
break
fi
echo "=== cursor-agent-loop: iteration $i of ${ITERATIONS} ===" >&2
prompt="$(build_prompt)"
agent_status=0
if [[ "$VERBOSE" == "1" ]]; then
set +e
"${agent_cmd[@]}" "$prompt" | python3 "$STREAM_FORMATTER"
pipe_statuses=("${PIPESTATUS[@]}")
set -e
agent_status=${pipe_statuses[0]}
fmt_status=${pipe_statuses[1]:-0}
if [[ "$fmt_status" -ne 0 ]]; then
echo "error: stream formatter exited non-zero ($fmt_status) on iteration $i" >&2
exit "$fmt_status"
fi
fi
if [[ "$VERBOSE" != "1" ]]; then
set +e
"${agent_cmd[@]}" "$prompt"
agent_status=$?
set -e
fi
if [[ "$agent_status" -ne 0 ]]; then
echo "error: agent exited non-zero ($agent_status) on iteration $i" >&2
exit "$agent_status"
fi
if [[ "$PUSH" == "1" ]]; then
git push
fi
i=$((i + 1))
done
echo "=== cursor-agent-loop: finished ${ITERATIONS} iteration(s) ===" >&2