SuperAPI AI Directory

moai-workflow-ci-autofix

Deployment & operations

View repository

Updated 10-15 21:02

About this skill

CI Auto-Fix Loop (moai-workflow-ci-autofix)

Quick Reference

Wave 2 CI watch loop(moai-workflow-ci-watch) emits a required check failure (exit 2 + JSON handoff), this skill takes over and performs the following:

  1. scripts/ci-autofix/log-fetch.shcapture failure log + PR diff
  2. scripts/ci-autofix/classify.shclassify failure type (mechanical / semantic / unknown)
  3. according to the OQ2 cadence matrix, manager-quality delegate diagnosis to subagent
  4. mechanical failure: propose patch → orchestrator AskUserQuestion → apply → push → re-invoke ci-watch
  5. semantic failure: immediate AskUserQuestion escalation (no patch attempt)
  6. after up to 3 iterations, mandatory blocking AskUserQuestion

[HARD] Only the orchestrator (the main session running this skill) calls AskUserQuestion. manager-quality subagents never call AskUserQuestion.

state file

.moai/state/ci-autofix-<PR번호>.json

Wave 2의 .moai/state/ci-watch-active.flagis located in the same directory as . Since the filename includes the PR number, duplicate entry for the same PR and conflicts between different PRs are prevented.


Implementation Guide

Wave 2 → 3 Handoff consumption

Wave 2 (scripts/ci-watch/run.sh) exits with exit 2, JSON is output to stdout:

{
  "prNumber": 785,
  "branch": "feat/my-feature",
  "failedChecks": [
    {
      "name": "Lint",
      "runId": "12345678",
      "logUrl": "https://github.com/.../actions/runs/12345678",
      "conclusionDetail": ""
    }
  ],
  "auxiliaryFailCount": 1,
  "totalRequired": 6
}

Schema source: internal/ciwatch/handoff.go::Handoff (Wave 2, commit 5d3f6a4c1). failedChecks[]includes only required failures. auxiliary failures are counted only in auxiliaryFailCount.

[HARD] orchestrator는 위 JSON의 failedChecks[].runId와 logUrl field scripts/ci-autofix/log-fetch.shas arguments. When changing schema field names, both Wave 2 + Wave 3 must be modified simultaneously.

state file initialization and management

# Wave 2 handoff JSON을 파싱하여 상태 파일 생성
PR_NUMBER=$(printf '%s\n' "$HANDOFF_JSON" | jq -r '.prNumber')
STATE_FILE=".moai/state/ci-autofix-${PR_NUMBER}.json"

# 상태 파일 초기화 (iteration=1)
cat > "$STATE_FILE" <<EOF
{
  "pr_number": $PR_NUMBER,
  "iteration": 1,
  "max_iterations": 3,
  "last_classification": "",
  "last_subclass": "",
  "last_action": "",
  "started_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
  "history": []
}
EOF

crash recovery: stale state files older than 24 hours may be overwritten by a new invocation.

log capture and classification

# failed check의 첫 번째 runId를 추출
RUN_ID=$(printf '%s\n' "$HANDOFF_JSON" | jq -r '.failedChecks[0].runId')
PR_NUMBER=$(printf '%s\n' "$HANDOFF_JSON" | jq -r '.prNumber')

# 로그 + diff 캡처
LOG_AND_DIFF=$(sh scripts/ci-autofix/log-fetch.sh "$RUN_ID" "$PR_NUMBER")

# 분류
CLASSIFICATION=$(printf '%s\n' "$LOG_AND_DIFF" | sh scripts/ci-autofix/classify.sh)
CLASS=$(printf '%s\n' "$CLASSIFICATION" | grep '^classification=' | cut -d= -f2)
SUB=$(printf '%s\n' "$CLASSIFICATION" | grep '^sub_class=' | cut -d= -f2)

OQ2 Cadence Matrix (Authoritative)

This table is the single source of truth for per-iteration behavior:

iteration classification sub_class action AskUserQuestion?
1 mechanical trivial confirm + apply YES (1st option "Apply patch (recommended)")
1 mechanical non-trivial confirm + apply YES (1st option "Apply patch (recommended)")
1 semantic / unknown n/a escalate (no patch) YES (includes diagnostic report)
2 mechanical trivial silent + apply + log NO (automatic, recorded only in report)
2 mechanical non-trivial confirm + apply YES
2 semantic / unknown n/a escalate (no patch) YES
3 mechanical trivial silent + apply + log NO
3 mechanical non-trivial confirm + apply YES
3 semantic / unknown n/a escalate (no patch) YES
post-iter-3 any (still failing) n/a mandatory escalation YES (blocking, no timer)

"trivial" definition: whitespace, gofmt/goimports, import-order. classify.sh의 RX_TRIVIAL_* 1:1 match with patterns.

Orchestrator state machine flow

[wave2-handoff-received]
  → 상태 파일 생성 (iteration=1)
  → log-fetch.sh로 failed log + PR diff 캡처
  → classify.sh로 분류
  → if classification == "semantic" || "unknown" → [escalate-immediate]
  → OQ2 matrix 조회
  → if iteration == 1 → [confirm-required]   (iter 1 항상 confirm)
  → if subclass == "trivial" && iteration >= 2 → [silent-apply]
  → else → [confirm-required]

[confirm-required]
  → ToolSearch(query: "select:AskUserQuestion")
  → AskUserQuestion 패치 내용 제시 (1순위 = "패치 적용 (권장)")
  → user accepts → manager-quality patch apply → git add/commit/push (force-push 금지)
                 → ci-watch run.sh 재호출
  → user rejects → [escalate-user-choice]

[silent-apply]
  → manager-quality patch apply → git commit/push (force-push 금지)
  → audit log에 iteration 결과 기록 (AskUserQuestion 없음)
  → ci-watch run.sh 재호출

[ci-watch returns]
  → exit 0 (green) → 상태 파일 삭제 + skill 성공 종료
  → exit 2 (still failing) → iteration++ → state file 갱신 → 상태 머신 처음으로

[iteration > 3]
  → ToolSearch(query: "select:AskUserQuestion")
  → AskUserQuestion mandatory (blocking, no timer, 사용자 응답 전까지 무한 대기)
  → 옵션 3개 + Other: (권장) 직접 수동 수정 / SPEC 수정 / PR 포기
  → 모든 iteration 결과를 audit log에 첨부하여 보고

[escalate-immediate / escalate-user-choice]
  → ToolSearch(query: "select:AskUserQuestion")
  → AskUserQuestion 진단 보고서 포함 (patch 시도 없음)
  → audit log에 escalation 이유 기록

manager-quality Subagent invocation pattern

orchestrator가 manager-qualitycontext to inject into the spawn prompt when calling :

## CI Auto-Fix Context

**Wave 2 Handoff JSON:**
<handoff_json>

**Classification Result:**
- classification: <mechanical|semantic|unknown>
- sub_class: <trivial|non-trivial|none>

**Failed CI Log + PR Diff:**
<log_and_diff_content>

**Mode:**
- If classification == "mechanical": propose a patch in unified diff format
- If classification == "semantic" or "unknown": return diagnosis only (NO patch)

[HARD] AskUserQuestion 호출 금지. patch 제안 또는 diagnosis를 Markdown으로 반환만 한다.

manager-quality proposes a patch on mechanical failure and returns only a diagnostic report on semantic failure. After the orchestrator receives the patch, it gets user confirmation via AskUserQuestion.

Audit Log writing

Every iteration is recorded in .moai/reports/ci-autofix/<PR-NNN>-<YYYY-MM-DD>.md:

# CI Auto-Fix Log — PR #785 — 2026-05-07

## Iteration 1

- **classification**: mechanical
- **sub_class**: non-trivial
- **action**: applied
- **patch_sha**: abc1234
- **escalation_reason**: N/A

## Iteration 2

- **classification**: mechanical
- **sub_class**: trivial
- **action**: applied (silent)
- **patch_sha**: def5678
- **escalation_reason**: N/A

Append-only recording via the Bash tool. On the first iteration, create the header + Iteration 1 section. For subsequent iterations, append the ## Iteration N section.

report directory creation

mkdir -p .moai/reports/ci-autofix/
REPORT_FILE=".moai/reports/ci-autofix/PR-${PR_NUMBER}-$(date +%Y-%m-%d).md"

# 첫 iteration: 헤더 작성
if [ ! -f "$REPORT_FILE" ]; then
    printf '# CI Auto-Fix Log — PR #%s — %s\n\n' \
        "$PR_NUMBER" "$(date +%Y-%m-%d)" > "$REPORT_FILE"
fi

# 각 iteration: append
printf '\n## Iteration %s\n\n- **classification**: %s\n- **sub_class**: %s\n- **action**: %s\n' \
    "$ITER" "$CLASS" "$SUB" "$ACTION" >> "$REPORT_FILE"

Advanced Patterns

escalation path summary

Trigger Path
classification == semantic / unknown (any iter) escalate-immediate: manager-quality = diagnosis only; no patch
iter 1, mechanical, user rejects patch escalate-user-choice: record reason + loop halt
iter == 3 + still failing (mechanical) mandatory AskUserQuestion (blocking, no timer)
log-fetch.sh failure (gh auth, network) exit 1 + remediation hint: guide user to manual fallback
classify.sh returns unknown twice in a row "classifier cannot interpret" → user manual takeover

Force-Push prohibited (Invariant)

[HARD] All auto-fix patches are added as new commits to the PR branch. git push --force, git push -f, git push --force-with-lease Absolutely prohibited. This invariant is ci-autofix-protocol.mdis specified as a HARD rule in.

Wave 2 Cross-Reference

  • Schema: internal/ciwatch/handoff.go::Handoff
  • SKILL: internal/template/templates/.claude/skills/moai-workflow-ci-watch/SKILL.md §"Wave 3 Handoff Schema"
  • Protocol: internal/template/templates/.claude/rules/moai/workflow/ci-watch-protocol.md §"T3 Handoff Format"

Works Well With

  • moai-workflow-ci-watch → Wave 2 handoff source
  • manager-quality → failure log diagnosis + patch suggestion
  • scripts/ci-autofix/classify.sh → mechanical vs semantic classification
  • scripts/ci-autofix/log-fetch.sh → failure log + PR diff capture
  • .claude/rules/moai/workflow/ci-autofix-protocol.md → HARD invocation contract

Common Rationalizations

Rationalization Reality
"Let's try auto-patching semantic failures too" Semantic failures (race condition, test assertion) cannot be auto-patched without context. A wrong patch creates a bigger problem.
"It's convenient to time out after iter 3" AC-CIAUT-008 invariant — no silent timeout. The user must decide.
"Cleaning up history with force-push is neat" Force-pushing to a PR branch makes reviewers lose the diff. Always a new commit.
"Let's apply trivial fixes immediately without confirm" iter 1 always confirms (OQ2 matrix). The user must review the first patch.

Red Flags

  • manager-quality subagent tries to call AskUserQuestion (HARD violation: orchestrator only)
  • auto-continue at iteration 4 (no AskUserQuestion) — missing blocking call
  • using git push --force or push -f — violates the force-push prohibition invariant
  • attempting to generate a patch for a semantic failure — missing the immediate escalate path
  • state file not created — iteration counter lost, risk of infinite loop

Verification

  • Does Wave 2 exit 2 + JSON trigger state file initialization
  • Does every classification in iter 1 raise AskUserQuestion
  • Are iter 2-3 trivial mechanical silent apply + log only
  • Does semantic/unknown escalate immediately to AskUserQuestion without a patch
  • Does entering iter > 3 raise a blocking AskUserQuestion (no timer)
  • .moai/reports/ci-autofix/<PR>-<DATE>.mdAre all iterations recorded in
  • Is git push --force / -f not used (grep -r 'push -f\|push --force' scripts/ci-autofix/)