Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
990 changes: 990 additions & 0 deletions .apb/playbooks/osb-pr-prepare/1.4.0/playbook.yaml

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions .apb/playbooks/osb-pr-prepare/1.4.0/scripts/arch-gate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/bin/sh
# Architecture gate: code-ranker in baseline mode. The merge base with
# origin/<base> is analyzed as the baseline and the committed HEAD as the
# candidate (both from `git archive`, so untracked files and local tool
# directories never enter the graph); only violations the branch introduces
# fail the gate. Pre-existing debt on the base is not this PR's to fix.
#
# Exit 0: no new violation. Exit 1: new violations (the output starts with
# `ARCH-GATE: violations`, followed by code-ranker's fix prompt). Exit 2: the
# tool itself failed (`ERROR:`), which no code change in the branch can fix.
[ -n "${BASH_VERSION:-}" ] || exec bash "$0" "$@"
set -euo pipefail
. "$(dirname "$0")/lib.sh"

root=$(repo_root)
cd "$root"
load_ctx
command -v code-ranker >/dev/null 2>&1 || die "code-ranker is not on PATH"

mb=$(git merge-base "origin/$OSB_BASE" HEAD) || die "no merge base with origin/$OSB_BASE"
work=$(mktemp -d "${TMPDIR:-/tmp}/osb-arch-gate.XXXXXX")
trap 'rm -rf "$work"' EXIT
mkdir -p "$work/base" "$work/head"
git archive "$mb" | tar -x -C "$work/base"
git archive HEAD | tar -x -C "$work/head"

# Explicit plugins so base and head are analyzed with the same languages.
plugins=(--plugins ts --plugins python --plugins md)
cd "$work" # code-ranker writes its default HTML viewer under the cwd
code-ranker report base "${plugins[@]}" --output.mode quiet \
--output.json.path "$work/base.json" --output.html.path "$work/base.html" >/dev/null 2>"$work/report.err" ||
die "code-ranker report on the merge base failed: $(cat "$work/report.err")"

set +e
code-ranker check head "${plugins[@]}" --baseline "$work/base.json" \
--output-format prompt --output.mode summary >"$work/check.out" 2>&1
rc=$?
set -e

if [ "$rc" -eq 0 ]; then
cat "$work/check.out"
echo "ARCH-GATE: pass (no new violation against $mb)"
exit 0
fi
if grep -q 'new violation(s) vs baseline' "$work/check.out"; then
echo "ARCH-GATE: violations (new against merge base $mb)"
sed "s#$work/##g" "$work/check.out"
exit 1
fi
cat "$work/check.out"
die "code-ranker check failed without a violation verdict (exit $rc)"
24 changes: 24 additions & 0 deletions .apb/playbooks/osb-pr-prepare/1.4.0/scripts/check-context.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/sh
# success_check of `intake`: the recorded PR context is complete and true.
# apb runs every script through `sh`; re-exec under bash for pipefail.
[ -n "${BASH_VERSION:-}" ] || exec bash "$0" "$@"
set -euo pipefail
. "$(dirname "$0")/lib.sh"

cd "$(repo_root)"
load_ctx

case " $OSB_KINDS " in *" $OSB_KIND "*) ;; *) die "OSB_KIND=$OSB_KIND is not one of: $OSB_KINDS" ;; esac
case " $OSB_RELEASES " in *" $OSB_RELEASE "*) ;; *) die "OSB_RELEASE=$OSB_RELEASE is not one of: $OSB_RELEASES" ;; esac

branch=$(git symbolic-ref --quiet --short HEAD) || die "HEAD is detached; the PR needs a branch"
[ "$branch" = "$OSB_BRANCH" ] || die "checked-out branch $branch differs from OSB_BRANCH=$OSB_BRANCH"
[ "$branch" != "$OSB_BASE" ] || die "the PR branch is the base branch $OSB_BASE"
git rev-parse --verify --quiet "origin/$OSB_BASE" >/dev/null || die "origin/$OSB_BASE does not exist; fetch it"
mb=$(git merge-base "origin/$OSB_BASE" HEAD) || die "no merge base between origin/$OSB_BASE and HEAD"
git diff --quiet "$mb" HEAD && die "the branch carries no change against origin/$OSB_BASE"
git diff --quiet || die "tracked files have unstaged changes; the intake commits the implementation first"
git diff --cached --quiet || die "the index holds staged, uncommitted changes"

echo "context ok: kind=$OSB_KIND release=$OSB_RELEASE base=$OSB_BASE branch=$OSB_BRANCH merge-base=$mb"
echo "commits: $(git rev-list --count "$mb"..HEAD), files: $(git diff --name-only "$mb" HEAD | wc -l)"
27 changes: 27 additions & 0 deletions .apb/playbooks/osb-pr-prepare/1.4.0/scripts/check-pr.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/sh
# success_check of `push_pr` (and of the fix nodes that push): the PR recorded
# in pr.env is open, not merged, targets the base branch, and its head is
# exactly the local HEAD, which the remote branch also carries.
[ -n "${BASH_VERSION:-}" ] || exec bash "$0" "$@"
set -euo pipefail
. "$(dirname "$0")/lib.sh"

cd "$(repo_root)"
load_ctx
load_pr

head=$(git rev-parse HEAD)
git diff --quiet && git diff --cached --quiet || die "uncommitted tracked changes: commit and push them"
remote=$(git ls-remote origin "refs/heads/$OSB_BRANCH" | cut -f1)
[ "$remote" = "$head" ] || die "origin/$OSB_BRANCH is '${remote:-absent}', local HEAD is $head: push"

info=$(gh pr view "$OSB_PR_NUMBER" --json state,headRefOid,baseRefName,headRefName,url \
-q '[.state,.headRefOid,.baseRefName,.headRefName,.url]|@tsv') || die "gh pr view $OSB_PR_NUMBER failed"
IFS=$'\t' read -r state pr_head pr_base pr_branch url <<<"$info"
[ "$state" = OPEN ] || die "PR #$OSB_PR_NUMBER is $state, expected OPEN (this playbook never merges)"
[ "$pr_base" = "$OSB_BASE" ] || die "PR #$OSB_PR_NUMBER targets $pr_base, expected $OSB_BASE"
[ "$pr_branch" = "$OSB_BRANCH" ] || die "PR #$OSB_PR_NUMBER is from $pr_branch, expected $OSB_BRANCH"
[ "$pr_head" = "$head" ] || die "PR #$OSB_PR_NUMBER head is $pr_head, local HEAD is $head"
auto=$(gh pr view "$OSB_PR_NUMBER" --json autoMergeRequest -q '.autoMergeRequest != null')
[ "$auto" = false ] || die "PR #$OSB_PR_NUMBER has auto-merge armed; merging is the operator's call: gh pr merge --disable-auto $OSB_PR_NUMBER"
echo "PR ok: #$OSB_PR_NUMBER $url open on $OSB_BASE at $head"
55 changes: 55 additions & 0 deletions .apb/playbooks/osb-pr-prepare/1.4.0/scripts/check-release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/bin/sh
# success_check of `docs_version`: the version and CHANGELOG state match what
# the change kind decided (CLAUDE.md "Versioning").
#
# Release-bearing PR (OSB_RELEASE major|minor|patch):
# - package.json carries the expected bump of the base version;
# - the first `## [` heading of CHANGELOG.md is `## [X.Y.Z] - YYYY-MM-DD`
# (no `[Unreleased]` left above it);
# - the `[X.Y.Z]: .../compare/vPREV...vX.Y.Z` link reference exists;
# - `sync-version --check` passes and everything is committed.
# No-release PR (OSB_RELEASE none): package.json still carries the base version.
[ -n "${BASH_VERSION:-}" ] || exec bash "$0" "$@"
set -euo pipefail
. "$(dirname "$0")/lib.sh"

cd "$(repo_root)"
load_ctx

prev=$(git show "origin/$OSB_BASE:package.json" | json_version)
now=$(json_version <package.json)

if [ "$OSB_RELEASE" = none ]; then
[ "$now" = "$prev" ] || die "kind $OSB_KIND ships no release, but package.json moved $prev -> $now"
base_first=$(git show "origin/$OSB_BASE:CHANGELOG.md" | grep -m1 -E '^## \[' || true)
head_first=$(grep -m1 -E '^## \[' CHANGELOG.md || true)
[ "$head_first" = "$base_first" ] ||
die "kind $OSB_KIND ships no release, but the first CHANGELOG heading moved '$base_first' -> '$head_first'"
echo "release check ok: no release, version stays $now"
exit 0
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.

expected=$(python3 - "$prev" "$OSB_RELEASE" <<'PY'
import sys
major, minor, patch = (int(p) for p in sys.argv[1].split("."))
bump = sys.argv[2]
if bump == "major":
major, minor, patch = major + 1, 0, 0
elif bump == "minor":
minor, patch = minor + 1, 0
else:
patch += 1
print(f"{major}.{minor}.{patch}")
PY
)
[ "$now" = "$expected" ] || die "package.json is $now; a $OSB_RELEASE bump of $prev is $expected"

first=$(grep -m1 -E '^## \[' CHANGELOG.md || true)
printf '%s\n' "$first" | grep -qE "^## \[$expected\] - [0-9]{4}-[0-9]{2}-[0-9]{2}\$" ||
die "the first CHANGELOG heading is '$first', expected '## [$expected] - <date>'"
grep -qE "^\[$expected\]: https://github\.com/itechmeat/open-second-brain/compare/v$prev\.\.\.v$expected\$" CHANGELOG.md ||
die "CHANGELOG.md lacks the link reference [$expected]: https://github.com/itechmeat/open-second-brain/compare/v$prev...v$expected"

bun run scripts/sync-version.ts --check >/dev/null 2>&1 || die "sync-version --check fails: run bun run scripts/sync-version.ts"
git diff --quiet && git diff --cached --quiet || die "version or CHANGELOG changes are not committed"
echo "release check ok: $prev -> $expected ($OSB_RELEASE), CHANGELOG heading and link reference present"
69 changes: 69 additions & 0 deletions .apb/playbooks/osb-pr-prepare/1.4.0/scripts/ci-wait.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/bin/sh
# Waits for the PR's checks on the current head with ONE blocking command,
# `gh pr checks --watch` (no polling loop of our own), then prints the verdict.
# The checks include CodeRabbit's status, so a green result also means the bot
# review has finished (or was skipped).
#
# The watch runs WITHOUT --fail-fast: the verdict waits until every check has a
# terminal state. A fast failure (a scan failing in seconds) must not start the
# fix round while a slow job (windows) is still running, or a late failure of
# that job needs a second fix round. The red verdict lists every failed check.
#
# Before the watch, the script makes sure the CI workflow run for THIS head
# commit exists. Right after a push, `gh pr checks` can still show the previous
# commit's finished checks and would return at once with a stale verdict. That
# registration wait is bounded (REGISTRATION_TRIES x REGISTRATION_SLEEP) and
# ends as soon as GitHub lists the run.
#
# Exit 0 green, 1 red (failed-job logs follow), 2 setup error.
[ -n "${BASH_VERSION:-}" ] || exec bash "$0" "$@"
set -euo pipefail
. "$(dirname "$0")/lib.sh"

REGISTRATION_TRIES=18
REGISTRATION_SLEEP=10
WATCH_TIMEOUT=6000 # the windows job alone has taken 45 minutes on a slow runner
WATCH_INTERVAL=60 # gh's own refresh period while it blocks
LOG_TAIL=250
CI_WORKFLOW=ci.yml

cd "$(repo_root)"
load_ctx
load_pr

head=$(git rev-parse HEAD)
pr_head=$(gh pr view "$OSB_PR_NUMBER" --json headRefOid -q .headRefOid)
[ "$pr_head" = "$head" ] || die "PR #$OSB_PR_NUMBER head $pr_head is not local HEAD $head: push first"

run_id=""
for _ in $(seq "$REGISTRATION_TRIES"); do
run_id=$(gh run list --commit "$head" --workflow "$CI_WORKFLOW" --limit 1 --json databaseId -q '.[0].databaseId // empty')
[ -n "$run_id" ] && break
sleep "$REGISTRATION_SLEEP"
done
[ -n "$run_id" ] || die "no $CI_WORKFLOW run registered for $head after $((REGISTRATION_TRIES * REGISTRATION_SLEEP))s"

set +e
timeout "$WATCH_TIMEOUT" gh pr checks "$OSB_PR_NUMBER" --watch --interval "$WATCH_INTERVAL" >/dev/null 2>&1
rc=$?
set -e
echo "checks for PR #$OSB_PR_NUMBER at $head (CI run $run_id):"
gh pr checks "$OSB_PR_NUMBER" 2>&1 || true

if [ "$rc" -eq 0 ]; then
echo "CI-GATE: green"
exit 0
fi
if [ "$rc" -eq 124 ]; then
echo "CI-GATE: red (checks still pending after ${WATCH_TIMEOUT}s)"
exit 1
fi
failed=$(gh pr checks "$OSB_PR_NUMBER" --json name,bucket -q '[.[] | select(.bucket == "fail" or .bucket == "cancel") | .name] | join(", ")' 2>/dev/null || true)
echo "CI-GATE: red (failed: ${failed:-the check list could not be read, see the checks above})"
# Per failed job, not `--log-failed` on the run: the job logs stay readable
# even when another workflow run on the same head is still in progress.
for job in $(gh run view "$run_id" --json jobs -q '.jobs[] | select(.conclusion == "failure") | .databaseId'); do
echo "--- failed job $job of run $run_id (log tail) ---"
gh run view --job "$job" --log 2>&1 | tail -n "$LOG_TAIL" || true
done
exit 1
65 changes: 65 additions & 0 deletions .apb/playbooks/osb-pr-prepare/1.4.0/scripts/cycle-run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/bin/sh
# Steps 1-3 of the dev quality cycle (skill dev-quality-cycle): OpenCodeReview
# on the claude-code provider, the read-only Codex review and Claude Code's
# /security-review, through the skill's own runner `scripts/cycle.sh`. The
# skill owns the commands, flags, effort levels and failure detection; this
# script only picks the range and records where the run folder is.
#
# Range: what the cycle has not reviewed yet, never what was merely pushed. The
# cycle_review and final_fix nodes record the head the cycle reviewed in
# <git-dir>/osb-pr-prepare/cycle-reviewed as "<branch> <sha>". When that record
# names this branch and its sha is an ancestor of HEAD, the range is <sha>..HEAD;
# otherwise it is the whole branch against origin/<base>. The runner exits 4
# when the range is empty (HEAD already reviewed).
#
# The skill lives in $DEV_QUALITY_CYCLE_DIR, default ~/.agents/skills/dev-quality-cycle.
#
# Output: the runner's log, then one marker line
# `CYCLE-RUN: ok|failed|resumable|nothing-new exit=<n> summary=<path>`
# Exit 0 for every marker: the cycle_review node reads the outputs and decides.
# Exit 2: setup error (`ERROR:`), which no code change can fix.
[ -n "${BASH_VERSION:-}" ] || exec bash "$0" "$@"
set -uo pipefail
. "$(dirname "$0")/lib.sh"

cd "$(repo_root)"
load_ctx

skill="${DEV_QUALITY_CYCLE_DIR:-$HOME/.agents/skills/dev-quality-cycle}"
runner="$skill/scripts/cycle.sh"
[ -x "$runner" ] || die "the dev-quality-cycle skill runner is missing: $runner"

git rev-parse -q --verify "origin/$OSB_BASE^{commit}" >/dev/null ||
die "origin/$OSB_BASE is unknown: fetch it first"
range_args=(--base "origin/$OSB_BASE")
reviewed_file="$CTX_DIR/cycle-reviewed"
if [ -s "$reviewed_file" ]; then
read -r reviewed_branch reviewed_sha <"$reviewed_file"
if [ "$reviewed_branch" = "$OSB_BRANCH" ] && [ -n "${reviewed_sha:-}" ] &&
git merge-base --is-ancestor "$reviewed_sha" HEAD 2>/dev/null; then
range_args=(--range "$reviewed_sha..HEAD")
fi
fi

# What the change is for: OCR --background-file and the Codex prompt.
intent="$CTX_DIR/cycle-intent.md"
{
printf 'Open Second Brain change of kind %s on branch %s against %s.\n' "$OSB_KIND" "$OSB_BRANCH" "$OSB_BASE"
printf 'Commits:\n'
git log --no-merges --format='- %s' "origin/$OSB_BASE..HEAD"
} >"$intent"

log="$CTX_DIR/cycle-run.log"
"$runner" "${range_args[@]}" --context-file "$intent" --exclude 'bun.lock,uv.lock' . >"$log" 2>&1
rc=$?
cat "$log"
summary=$(sed -n 's/^Summary: //p' "$log" | tail -1)
case "$rc" in
0) status=ok ;;
1) status=failed ;;
3) status=resumable ;;
4) status=nothing-new ;;
*) die "the cycle runner exited $rc (usage or setup error); see $log" ;;
esac
echo "CYCLE-RUN: $status exit=$rc summary=${summary:-none}"
exit 0
43 changes: 43 additions & 0 deletions .apb/playbooks/osb-pr-prepare/1.4.0/scripts/final-gates.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/sh
# Final gates before the push: the QA gates (qa-gates.sh) and the
# architecture gate (arch-gate.sh) again, after the OpenCodeReview pass may
# have committed fixes. When HEAD is still the commit the QA gates last
# passed on, nothing changed since the green QA and architecture gates (the
# architecture gate runs after the QA gates and any fix it needed moves
# HEAD), so the re-run is skipped.
#
# Exit 0: both green, or skipped (`FINAL-GATES: pass`). Exit 1: a red gate
# (`FINAL-GATES: red`, followed by the markers and output of the red gates).
# Exit 2: a setup or tool error (`ERROR:`), which no code change can fix.
[ -n "${BASH_VERSION:-}" ] || exec bash "$0" "$@"
set -uo pipefail
here=$(dirname "$0")
. "$here/lib.sh"

cd "$(repo_root)"
load_ctx

head=$(git rev-parse HEAD)
green="$CTX_DIR/qa-green-head"
if [ -f "$green" ] && [ "$(cat "$green")" = "$head" ]; then
echo "FINAL-GATES: pass (HEAD $head unchanged since the green QA and architecture gates)"
exit 0
fi

qa_out=$(sh "$here/qa-gates.sh" 2>&1)
qa_rc=$?
arch_out=$(sh "$here/arch-gate.sh" 2>&1)
arch_rc=$?

if [ "$qa_rc" -eq 0 ] && [ "$arch_rc" -eq 0 ]; then
printf '%s\n%s\n' "$qa_out" "$arch_out"
echo "FINAL-GATES: pass (QA and architecture gates green at $head)"
exit 0
fi
if [ "$qa_rc" -eq 2 ] || [ "$arch_rc" -eq 2 ]; then
printf '%s\n%s\n' "$qa_out" "$arch_out"
die "a gate could not run (qa exit $qa_rc, arch exit $arch_rc)"
fi
echo "FINAL-GATES: red (qa exit $qa_rc, arch exit $arch_rc)"
printf '%s\n%s\n' "$qa_out" "$arch_out"
exit 1
Loading
Loading