Skip to content
45 changes: 41 additions & 4 deletions .agents/skills/codexbar-git-workflow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,11 @@ fork-owned invariant is documented in `docs/ci-policy.md`:
- Use manual Final CI with `full=true` only for unresolved provenance, risky
conflict resolution, or an explicitly requested complete rerun.

Code review may therefore finish before heavy CI exists on the PR. After merge,
check Final CI before release; a failure is fixed forward and release remains
blocked until the relevant final gate passes.
Code review may therefore finish before heavy CI exists on the PR. PR Fast
Checks and Codex Code Review are independent gates: a green check run never
means review is complete. After merge, check Final CI before release; a failure
is fixed forward and release remains blocked until the relevant final gate
passes.

After pushing or opening a PR, check status from GitHub, not memory:

Expand All @@ -159,7 +161,42 @@ gh run view --repo o1xhack/CodexBar-Mobile <run-id> --log-failed
git push
```

For PR review work, do not rely only on flat comments. Check unresolved review state and active threads when available, then iterate until blocking review comments are addressed and CI is green.
For every PR, including docs-only, review-fix, and release-closeout PRs:

1. Record the current `headRefOid`, trigger `@codex review`, and keep polling.
Do not merge while the requested review is still in flight.
2. Inspect thread-aware review state, not only checks or flat comments. Every
finding must be fixed and retested.
3. Reply to the finding with the fix commit/evidence, explicitly resolve the
GitHub review thread, then push and request another Codex review.
4. Any new push invalidates the previous clean result. The final clean review
must name the exact current head, and every thread—including outdated
threads—must show `isResolved=true`.
5. Run `Scripts/check_pr_review_gate.sh <pr>` immediately before merge. A pass
requires the current-head `Didn't find any major issues` result and zero
unresolved threads. CI must also be green.

The loop is `review -> fix -> test -> reply -> resolve -> push -> review` until
clean. After five review rounds, do not continue making narrow patches by
reflex. Before a sixth review, stop and write a `Codex review architecture
audit` PR comment that includes the current head, repeated finding pattern,
root design/requirements problem, and revised approach. Re-plan or rewrite the
affected slice, then resume the loop. The audit is not a merge override: the
final current-head clean review and zero-thread gate still apply.

Use these exact non-empty fields so the merge gate can validate the audit:

```text
Codex review architecture audit
Head: <current head SHA>
Repeated finding pattern: <what keeps recurring>
Root design/requirements problem: <shared underlying cause>
Revised approach: <what will be redesigned or rewritten>
```

For release/upstream-sync work, **merge, tag creation, Mac live release,
appcast publication, and TestFlight upload are blocked until this PR review
gate passes**. Review-fix and closeout PRs are not exceptions.

## Todoist Handoff

Expand Down
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,17 @@ After code is complete:
When work needs a branch, commit, push, PR, review loop, branch cleanup, or
Todoist status update, load and follow `$codexbar-git-workflow`.

Before merging any PR, wait for Codex Code Review on the exact current head,
fix and retest every finding, reply and explicitly resolve every review thread,
then request another review. `PR Fast Checks` being green is not sufficient.
Run `Scripts/check_pr_review_gate.sh <pr>` immediately before merge. If review
exceeds five rounds, pause before round six to audit the shared root/design
problem and revise the approach; do not keep stacking symptom-level patches.
The PR comment must use the exact non-empty `Head`, `Repeated finding pattern`,
`Root design/requirements problem`, and `Revised approach` fields documented in
`$codexbar-git-workflow`.
Release/tag/appcast/TestFlight work remains blocked until this gate passes.

For large Goals, the agent may make staged Git commits when the Goal or user
authorizes implementation work. Do not push, merge, tag, publish a live release,
or upload unless the user explicitly asks or the active Goal explicitly includes
Expand Down
2 changes: 1 addition & 1 deletion CodexBarMobile/Research/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ This directory contains research documents for features being considered for Cod
| 043 | Alibaba Token Plan Rate Windows Hotfix (issue #59, rolling 5-hour and weekly usage restoration ahead of upstream release) | `done` | Live Alibaba account proof and signed publication remain separate gates | [043-alibaba-token-plan-rate-windows.md](043-alibaba-token-plan-rate-windows.md) | 2026-07-24 |
| 044 | Subscription Utilization Fresh-Series Fallback (stale session history no longer masks current weekly quota data) | `done` | — | [044-subscription-utilization-freshness-fallback.md](044-subscription-utilization-freshness-fallback.md) | 2026-07-26 |
| 045 | v0.47.0 Upstream Sync + iOS 1.20.0 (issue #66 plus authoritative Releases v0.46.0-v0.47.0 as one train; upstream Mac fleet sync isolated from the existing Mobile sync contract) | `done` | Draft complete; CloudKit Production schema deploy and live publication remain separate authorization gates | [045-v047-upstream-sync/00-overview.md](045-v047-upstream-sync/00-overview.md) | 2026-08-03 |
| 046 | v0.49.2 Upstream Sync + iOS 1.21.0 (issues #77-#80 plus authoritative v0.49.2; provider plugins/instance identity, generic details, Fireworks/IBM Bob, fleet dedupe) | `done` | Signed/notarized draft uploaded; Mac/iOS full gates and substituted 16-mask matrix pass; CloudKit `NO_DEPLOY`; no push/tag/live/TestFlight | [046-v049-upstream-sync/00-overview.md](046-v049-upstream-sync/00-overview.md) | 2026-08-11 |
| 046 | v0.49.2 Upstream Sync + iOS 1.21.0 (issues #77-#80 plus authoritative v0.49.2; provider plugins/instance identity, generic details, Fireworks/IBM Bob, fleet dedupe) | `done` | Mac `0.49.2.1` live + signed appcast/CLI assets; iOS `1.21.0 (193)` TestFlight `VALID`; CloudKit `NO_DEPLOY`; substituted 16-mask matrix pass | [046-v049-upstream-sync/00-overview.md](046-v049-upstream-sync/00-overview.md) | 2026-08-17 |
253 changes: 253 additions & 0 deletions Scripts/check_pr_review_gate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
#!/usr/bin/env bash
set -euo pipefail

REPO="o1xhack/CodexBar-Mobile"

usage() {
cat <<'EOF'
Usage:
Scripts/check_pr_review_gate.sh <pr-number-or-url>
Scripts/check_pr_review_gate.sh --fixture <pull-request.json>

The gate passes only when Codex has reported a clean review for the current PR
head, every review thread is explicitly resolved, and review rounds above five
have a current-head architecture-audit comment.
EOF
}

if [[ "${1:-}" == "--fixture" ]]; then
[[ -n "${2:-}" && -f "$2" ]] || {
usage >&2
exit 2
}
pr_json=$(cat "$2")
elif [[ -n "${1:-}" ]]; then
command -v gh >/dev/null || {
echo "PR review gate failed: gh is required" >&2
exit 2
}
pr_number=$(gh pr view "$1" --repo "$REPO" --json number --jq '.number')
pr_json=$(gh api graphql -F number="$pr_number" -f query='
query($number: Int!) {
repository(owner: "o1xhack", name: "CodexBar-Mobile") {
pullRequest(number: $number) {
number
url
state
isDraft
headRefOid
reviews(first: 100) {
pageInfo { hasNextPage }
nodes {
author { login }
state
submittedAt
body
commit { oid }
comments(first: 100) {
pageInfo { hasNextPage }
nodes { body }
}
}
}
comments(first: 100) {
pageInfo { hasNextPage }
nodes {
author { login }
body
createdAt
}
}
reviewThreads(first: 100) {
pageInfo { hasNextPage }
nodes {
id
isResolved
isOutdated
comments(first: 20) {
nodes {
author { login }
body
createdAt
path
line
url
}
}
}
}
}
}
}
' | jq -c '.data.repository.pullRequest')
else
usage >&2
exit 2
fi

summary=$(jq -c '
def is_codex:
((.author.login // "") | test("^chatgpt-codex-connector(\\[bot\\])?$"));
def reviewed_oid:
(.body // ""
| (try capture("Reviewed commit:[^`]*`(?<oid>[0-9a-f]{7,40})`").oid catch null) // null);
def is_clean:
((.body // "") | contains("Didn\u0027t find any major issues"));
Comment thread
o1xhack marked this conversation as resolved.
def is_substantive_review_comment:
(.body // "") as $body
| (($body | length) > 0)
and (($body | startswith("To use Codex here,")) | not);
def normalize_oid($oid; $fullOids):
if $oid == null then null
elif ($oid | length) == 40 then $oid
else
([$fullOids[] | select(startswith($oid))] | unique) as $matches
| if ($matches | length) == 1 then $matches[0] else $oid end
end;
def has_audit_field($name):
((.body // "") | test("(?m)^" + $name + ":[ \\t]*[^ \\t\\r\\n]"));
def audit_head_oid:
(.body // ""
| try capture("(?m)^Head:[ \\t]*(?<oid>[0-9a-f]{7,40})[ \\t]*$").oid catch null);

.headRefOid as $head
| ([$head] + [(.reviews.nodes // [])[].commit.oid]
| map(select(. != null and length == 40))
| unique) as $fullOids
| [(.reviews.nodes // [])[]
| select(is_codex)
| reviewed_oid as $bodyOid
| ([((.comments.nodes // [])[]) | select(is_substantive_review_comment)]
| length > 0) as $hasFinding
| (if ($bodyOid != null) or $hasFinding then .commit.oid
else null
end) as $rawOid
| {oid: normalize_oid($rawOid; $fullOids),
at: .submittedAt,
clean: (is_clean and ($hasFinding | not))}
| select(.oid != null and .at != null)] as $reviewEvents
| [(.comments.nodes // [])[]
| select(is_codex)
| reviewed_oid as $rawOid
| {oid: normalize_oid($rawOid; $fullOids), at: .createdAt, clean: is_clean}
| select(.oid != null and .at != null)] as $commentEvents
| ($reviewEvents + $commentEvents) as $allReviewEvents
| ($allReviewEvents
| group_by(.oid)
| map(min_by(.at))
| sort_by(.at)) as $distinctReviewEvents
| ($distinctReviewEvents[5] // null) as $sixthReview
| ($allReviewEvents
| map(select(.oid == $head))
| sort_by(.at)) as $currentHeadEvents
| (($currentHeadEvents | map(.at) | max) // null) as $latestCurrentHeadAt
| (if $latestCurrentHeadAt == null then null
else {
oid: $head,
at: $latestCurrentHeadAt,
clean: ([$currentHeadEvents[] | select(.at == $latestCurrentHeadAt)]
| all(.[]; .clean == true))
}
end) as $latestCurrentHeadEvent
| ([((.comments.nodes // [])[])
| select((is_codex | not))
| select((.body // "") | test("(?im)^[ \\t]*@codex[ \\t]+review[ \\t]*$"))
| .createdAt]
| sort
| .[-1] // null) as $latestReviewRequestAt
| (($latestReviewRequestAt != null)
and (($latestCurrentHeadEvent == null)
or ($latestReviewRequestAt >= $latestCurrentHeadEvent.at))) as $reviewInFlight
| (($latestCurrentHeadEvent != null)
and ($latestCurrentHeadEvent.clean == true)
and ($reviewInFlight | not)) as $currentClean
| [(.reviewThreads.nodes // [])[] | select(.isResolved != true)] as $unresolved
| [(.comments.nodes // [])[]
| select((is_codex | not))
| select((.body // "") | contains("Codex review architecture audit"))
| select($sixthReview != null)
| audit_head_oid as $auditHead
| select($auditHead != null)
| select($sixthReview.oid | startswith($auditHead))
| select(has_audit_field("Repeated finding pattern"))
| select(has_audit_field("Root design/requirements problem"))
| select(has_audit_field("Revised approach"))
| select(.createdAt < $sixthReview.at)] as $architectureAudits
Comment thread
o1xhack marked this conversation as resolved.
| {
number,
url,
state,
isDraft,
head: $head,
rounds: ($distinctReviewEvents | length),
currentClean: $currentClean,
reviewInFlight: $reviewInFlight,
latestCurrentHeadEvent: $latestCurrentHeadEvent,
unresolvedCount: ($unresolved | length),
unresolved: [$unresolved[] | {
id,
isOutdated,
url: (.comments.nodes[0].url // null),
path: (.comments.nodes[0].path // null)
}],
architectureAuditRequired:
(($distinctReviewEvents | length) > 5),
architectureAuditRecorded: (($architectureAudits | length) > 0),
Comment thread
o1xhack marked this conversation as resolved.
sixthReview: $sixthReview,
truncated:
((.reviews.pageInfo.hasNextPage // false)
or (.comments.pageInfo.hasNextPage // false)
or (.reviewThreads.pageInfo.hasNextPage // false)
or ([((.reviews.nodes // [])[].comments.pageInfo.hasNextPage // false)] | any))
}
' <<< "$pr_json")

failed=0
state=$(jq -r '.state' <<< "$summary")
is_draft=$(jq -r '.isDraft' <<< "$summary")
current_clean=$(jq -r '.currentClean' <<< "$summary")
review_in_flight=$(jq -r '.reviewInFlight' <<< "$summary")
unresolved_count=$(jq -r '.unresolvedCount' <<< "$summary")
audit_required=$(jq -r '.architectureAuditRequired' <<< "$summary")
audit_recorded=$(jq -r '.architectureAuditRecorded' <<< "$summary")
truncated=$(jq -r '.truncated' <<< "$summary")

if [[ "$state" != "OPEN" ]]; then
echo "PR review gate failed: PR state is $state, expected OPEN" >&2
failed=1
fi
if [[ "$is_draft" == "true" ]]; then
echo "PR review gate failed: PR is still draft" >&2
failed=1
fi
if [[ "$review_in_flight" == "true" ]]; then
echo "PR review gate failed: requested Codex review is still in flight" >&2
failed=1
elif [[ "$current_clean" != "true" ]]; then
echo "PR review gate failed: current head has no clean Codex review" >&2
failed=1
fi
if [[ "$unresolved_count" != "0" ]]; then
echo "PR review gate failed: $unresolved_count review thread(s) are not explicitly resolved" >&2
jq -r '.unresolved[] | " - \(.url // .id) [\(.path // "unknown path")] outdated=\(.isOutdated)"' \
<<< "$summary" >&2
failed=1
fi
if [[ "$audit_required" == "true" && "$audit_recorded" != "true" ]]; then
echo "PR review gate failed: more than five review rounds require a current-head architecture audit" >&2
failed=1
fi
if [[ "$truncated" == "true" ]]; then
echo "PR review gate failed: GitHub review evidence exceeds the audited 100-item page" >&2
failed=1
fi

if [[ "$failed" == "1" ]]; then
jq . <<< "$summary" >&2
exit 1
fi

printf 'PR review gate passed: PR #%s head=%s rounds=%s unresolved=0\n' \
"$(jq -r '.number' <<< "$summary")" \
"$(jq -r '.head' <<< "$summary")" \
"$(jq -r '.rounds' <<< "$summary")"
5 changes: 5 additions & 0 deletions Scripts/lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ check_release_cli_workflow() {
"${ROOT_DIR}/Scripts/test_release_cli_workflow.sh"
}

check_pr_review_gate() {
"${ROOT_DIR}/Scripts/test_pr_review_gate.sh"
}

check_swift_test_sharding() {
"${ROOT_DIR}/Scripts/test_swift_test_sharding.sh"
}
Expand Down Expand Up @@ -242,6 +246,7 @@ run_portable_checks() {
check_sparkle_signing_paths
check_release_secret_loading
check_release_cli_workflow
check_pr_review_gate
check_swift_test_sharding
check_ci_path_gate
check_ci_upstream_check_gate
Expand Down
Loading