원클릭으로
ghostwriter
Autonomous GitHub/GitLab bot. Monitors repos and creates PRs/MRs/comments under bot or user identity per platform.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Autonomous GitHub/GitLab bot. Monitors repos and creates PRs/MRs/comments under bot or user identity per platform.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | ghostwriter |
| description | Autonomous GitHub/GitLab bot. Monitors repos and creates PRs/MRs/comments under bot or user identity per platform. |
| metadata | {"openclaw":{"requires":{"bins":["jq","yq","git"],"env":[]}}} |
당신은 OpenClaw agent로서 cron이 fire될 때마다 이 skill을 받아서 한 회차를 돈다. 회차의 골격은 deterministic 스크립트가 잡고, 본문/패치/issue 내용 작성 같이 LLM 판단이 필요한 부분만 당신이 한다.
회차의 모든 작업은 단일 lease 안에서 일어나야 한다. cycle.sh 자체는 lease를 잡지 않는다. 그 책임은 이 SKILL.md 가 짊어진다 — agent가 직접 acquire-lease / release-lease 를 호출해 cycle.sh + per-event 본문 작성 + post.sh + scan.sh 까지 모두 같은 lock 안에 둬야 한다.
RUN_ID="$(date -u +%FT%TZ)-$$"
TTL=7200 # config.safety.cycleMaxDuration
if ! bash {baseDir}/scripts/state.sh acquire-lease "$RUN_ID" "$TTL"; then
exit 0 # 다른 회차가 도는 중. 조용히 빠진다
fi
trap 'bash {baseDir}/scripts/state.sh release-lease "$RUN_ID"' EXIT
이제 bash {baseDir}/scripts/cycle.sh 를 호출한다. 출력은 NDJSON 라인:
{"kind":"event","event":{...}} — 처리할 이벤트 1건 (이미 inFlightEvents 에 claim 됨){"kind":"signal","name":"idle-scan"} — 이벤트 0건 + idle scan due + polling 성공{"kind":"signal","name":"poll-failed"} — polling 자체가 실패. 이 회차는 idle-scan 도 안 돈다. 그냥 done 까지 기다리고 종료{"kind":"signal","name":"paused"} — state.json.paused == true{"kind":"done"} — cycle.sh 가 한 일은 끝paused / poll-failed 를 받으면 트랩이 lease 를 풀어주고 즉시 회차 종료. event 를 받을 때마다 아래 §2 를 한 번씩 수행. idle-scan 을 받으면 §3.
스크립트들은 절대 경로를 직접 안 박는다. 모든 경로는 config.yaml 의 paths 필드에서 읽는다. ${OPENCLAW_WORKSPACE} 같은 셸 변수는 본문 안에서 직접 참조하지 말 것.
Long cycles: idle scan 의 make test 가 길어 lease TTL 가까이 다가가면 bash {baseDir}/scripts/state.sh renew-lease "$RUN_ID" "$TTL" 로 연장 가능.
이벤트 JSON은 다음 필드를 갖는다:
provider: github | gitlabrepo: owner/repo (GitHub) 또는 projectId (GitLab)type: issue | pr | mr | icomment | rcomment | review | inote | mnote | reopen | triggerid: stable event id (ack 용)parentId: 댓글 한도 추적용 부모 issue/PR/MR idtargetType: 코멘트가 들어갈 대상 (issue | pr | mr)targetNumber: 그 대상의 번호author, url, title, body, ts판단 흐름:
[ghostwriter]) 또는 (bot mode면) @<봇계정> mention 이 있음stats.botEngagedIssues 에 있음 (봇이 이전 회차에 그 issue/PR/MR 에 코멘트/생성으로 engage 했고, 이번 회차에 새 액션이 붙었음)trigger 이벤트로 변환됨[bot:ghostwriter] 가 누락돼도 post.sh가 자동 부착하지만, 처음부터 prefix 포함해서 쓰는 편이 가독성에 좋다# 이벤트 JSON 자체를 임시 파일로 저장
event_file=$(mktemp); printf '%s' "$EVENT_JSON" >"$event_file"
body_file=$(mktemp); printf '%s' "$BODY" >"$body_file"
bash {baseDir}/scripts/post.sh comment "$event_file" "$body_file"
rm -f "$event_file" "$body_file"
post.sh 가 자동으로:
notifications.smtpUrl 사용)post.sh 가 0 이 아닌 exit code 를 내면 push-pending 이 이미 호출되어 다음 회차에 재시도된다. 같은 이벤트를 같은 회차에 또 시도하지 말 것.
autonomous flag: 사용자 요청에 답하는 PR / issue 생성은 post.sh pr ... / post.sh issue ... 를 인자 그대로 (마지막 autonomous 인자 생략 = false) 호출한다. 그러면 idle scan 의 autonomousPrPerDay / autonomousIssuePerNDays 한도와 무관하게 진행된다. autonomous 한도는 idle scan 폭주 방지용이고, 사용자가 직접 부른 작업까지 한도로 silent skip 하면 사용자 신뢰가 깨진다.
trigger 이벤트 (GitHub/GitLab assigned/mentioned)응답 코멘트만 다는 게 아니라 작업 착수가 의도다. 다음 흐름을 권장:
bot/<topic> branch에서 진행 (scan.sh apply patch 흐름과 동일하게 구성)reopen 이벤트reopen된 issue/PR을 다시 살펴보고 무엇이 바뀌었는지 본문에 정리한 코멘트로 응답한다.
rcomment (PR review line comment)본 plan의 v1에서는 line-level reply API 대신 일반 PR 코멘트로 응답한다. 본문에서 어느 라인/파일에 답하는지 명시한다 (예: re: src/foo.go:42 — ...).
{"kind":"signal","name":"idle-scan"} 가 떨어지면:
bash {baseDir}/scripts/scan.sh prepare
prepare는 deterministic 후보 1개의 컨텍스트 JSON을 stdout으로 준다. 형식:
{
"provider": "github",
"repo": "guny524/kream_crawl",
"checkout": "/.../repos/github/guny524__kream_crawl",
"display": "kream_crawl",
"capabilities": { "pr": true, "issue": true },
"candidate": {
"source": "todoMarkers",
"score": 30,
"file": "scripts/run.sh",
"line": 42,
"summary": "...",
"evidence": "..."
}
}
후보를 보고 두 갈래 결정:
작은 fix면 patch payload를 작성해 scan.sh apply patch 로 넘긴다.
payload=$(mktemp)
cat >"$payload" <<JSON
{
"title": "[bot:ghostwriter] resolve TODO in scripts/run.sh",
"body": "...",
"branch": "bot/todo-run-sh",
"diff": "diff --git a/scripts/run.sh b/scripts/run.sh\n..."
}
JSON
bash {baseDir}/scripts/scan.sh apply patch "$payload"
rm -f "$payload"
scan.sh apply patch:
make test 실행 (있으면)조사 필요/리스크 큼/스코프 큼이면 issue payload만 만들어서 apply issue:
payload=$(mktemp)
cat >"$payload" <<JSON
{
"title": "[bot:ghostwriter] investigate possible race in cycle.sh",
"body": "..."
}
JSON
bash {baseDir}/scripts/scan.sh apply issue "$payload"
rm -f "$payload"
allowIssueCreation: false 또는 issue 한도 hit이면 silent skip (exit 64). 그래도 회차의 lastIdleScanAt 은 갱신된다.
{"kind":"done"} 라인 + 모든 이벤트 처리 완료 + (필요 시) scan.sh apply 까지 끝났으면 회차 종료. bash exit 시 EXIT trap 이 state.sh release-lease "$RUN_ID" 를 호출해 lease 를 풀어준다.
agent 가 비정상 종료해 trap 이 안 돌면 lease 는 expiresAt 까지 (default 2시간) 그대로 유지되고, 그 이후 cron 부터 takeover 된다. 그 사이 동안 새 cron 들은 acquire-lease 에서 막혀 조용히 종료된다.
gh auth setup-git / git config --global 실행git push --force 또는 bot/ 외 branch에 push[bot:ghostwriter] prefix 없이 본문 직접 API 호출 (post.sh 만 사용)