BEAR ships a downstream CI wrapper under .bear/ci/ so consuming repos can run deterministic governance gates without copying policy logic into workflow YAML.
This is the downstream PR/CI layer. The inner-loop BEAR path still lives in the agent workflow through validate, compile, and check before code reaches review.
Canonical packaged assets:
.bear/ci/bear-gates.ps1.bear/ci/bear-gates.sh.bear/ci/baseline-allow.json.bear/ci/README.md
Canonical wrapper outputs:
- report artifact:
build/bear/ci/bear-ci-report.json - markdown summary:
build/bear/ci/bear-ci-summary.md - when
GITHUB_STEP_SUMMARYis set, the wrapper appends the exact markdown summary content to that path
The wrapper owns CI policy and base selection. BEAR still owns the governance facts.
Execution order:
bear check --all --project . --blocks <path> --collect=all --agentbear pr-check --all --project . --base <sha> --blocks <path> --collect=all --agentwhen base resolution andcheckexit rules allow it
Skip rules:
- stop after
checkon exits2,64,70,74 - stop after
checkon unexpected exit5 - if base cannot be resolved, record
prCheck.status=not-runwith reasonBASE_UNRESOLVED - continue to
pr-checkaftercheckexits0,3,4,6,7
In v1, check exit 6 is reported under CI_GOVERNANCE_DRIFT because the CI class vocabulary does not add a dedicated undeclared-reach token.
enforce:
- wrapper fails on any non-zero
checkorpr-check - exception:
pr-check exit 5may pass asallowed-expansionwhen.bear/ci/baseline-allow.jsonexactly matches resolved base SHA and the observed boundary-expandingdeltaIdset
observe:
- wrapper still records both gate results
- wrapper returns
review-requiredfor boundary expansion (pr-check exit 5) - wrapper returns
failfor real blocking problems, including drift, test failures, bypass, validation/usage, IO/git, base-resolution failure, and internal wrapper errors
Wrapper process exit contract:
0forpass0forreview-required0forallowed-expansion1forfail
Priority:
- explicit
--base-sha <sha> - GitHub pull request event payload
pull_request.base.sha - GitHub push event payload
before - GitHub push fallback
HEAD~1only whenbeforeis missing or all zeroes
If no base SHA can be resolved, pr-check is not run and the wrapper fails closed.
The companion demo repo shows the intended GitHub usage pattern:
- demo repo: bear-account-demo
- demo guide: DEMO.md
That pattern is:
- checkout with full history
- set up Java
- run
bear compile --all --project . - run
.bear/ci/bear-gates.sh --mode observe - upload
bear-ci-report.jsonandbear-ci-summary.md - publish a sticky PR comment summarizing the BEAR decision
In that setup:
PASSmeans the PR is structurally cleanREVIEW REQUIREDmeans BEAR detected intentional governance expansion, but the CI lane stays non-blockingFAILmeans the PR has a real blocking BEAR problem
The PR target branch defines the comparison base in CI. In the demo, that is what makes these two cases differ cleanly:
- greenfield baseline review against
main->REVIEW REQUIRED - ordinary feature extension against
baseline/greenfield-output->PASS
Path:
.bear/ci/baseline-allow.json
Minimal shape:
{
"schemaVersion": "bear.ci.allow.v1",
"entries": [
{
"baseSha": "abc123",
"deltaIds": [
"BOUNDARY_EXPANDING|ALLOWED_DEPS|ADDED|.:_shared:com.example:demo@1.0.0"
]
}
]
}Rules:
- only boundary expansion (
pr-check exit 5) consults the allow file - match is exact on both
baseShaand the full boundary-expandingdeltaIdset - in
pr-check --all, that set includes repo-level and block-level boundary-expanding deltas - missing, stale, extra, or mismatched entries fail in
enforce - if
extensions.prGovernanceis missing or unparsable on a boundary-expansion path, allow evaluation is unavailable and the wrapper fails closed
When mode=enforce, pr-check exits 5, and PR telemetry is usable, the wrapper also emits the exact allow entry needed for approval. This removes the need to reconstruct deltaIds manually.
Console output on that path:
ALLOW_ENTRY_CANDIDATE:
{"baseSha":"<sha>","deltaIds":["..."]}
If telemetry is unusable on the same path:
ALLOW_ENTRY_CANDIDATE: UNAVAILABLE
Canonical report artifact:
build/bear/ci/bear-ci-report.jsonschemaVersion=bear.ci.governance.v1
Top-level fields:
schemaVersion,mode,resolvedBaseSha,commands[],bearRaw,check,prCheck,allowEvaluation,decision
check shape:
status="ran"exitCode,code,path,remediation,classes[]
prCheck ran shape:
status="ran"exitCode,code,path,remediation,classes[],allowEntryCandidate,deltas[],governanceSignals[]
prCheck not-run shape:
status="not-run"reasonexitCode=null,code=null,path=null,remediation=nullclasses=[],allowEntryCandidate=null,deltas=[],governanceSignals=[]
prCheck.allowEntryCandidate is either null or:
{
"baseSha": "<resolvedBaseSha>",
"deltaIds": ["<sorted-boundary-delta-id>"]
}In pr-check --all, allowEntryCandidate.deltaIds[] is derived from the full boundary-expanding delta set across repo-level and block-level results, deduped and deterministically ordered.
Allowed reason values:
CHECK_PRECONDITION_FAILUREBASE_UNRESOLVEDUNEXPECTED_CHECK_EXIT
decision values:
passreview-requiredfailallowed-expansion
The report also stores bearRaw.checkAgentJson, bearRaw.prCheckAgentJson, and deterministic stdout/stderr SHA-256 hashes so derived fields are auditable.
Wrapper stdout stays compact and begins with a human-facing decision header. The structured line remains immediately after it for deterministic parsing:
BEAR Decision: PASS
MODE=observe DECISION=pass BASE=<sha>
CHECK exit=0 code=- classes=CI_NO_STRUCTURAL_CHANGE
PR-CHECK exit=0 code=- classes=CI_NO_STRUCTURAL_CHANGE
When pr-check is skipped:
PR-CHECK NOT_RUN: BASE_UNRESOLVED
In observe, boundary expansion is surfaced as DECISION=review-required instead of a clean pass. The allow-entry candidate block remains enforce-only.
The wrapper always writes a deterministic markdown summary to:
build/bear/ci/bear-ci-summary.md
If GITHUB_STEP_SUMMARY is set, the wrapper appends the exact file contents to that GitHub summary path.
The markdown summary is derived only from the same wrapper facts already used for console output, report generation, allow evaluation, and final decision. Near the top it includes the same human-facing decision header, for example BEAR Decision: REVIEW REQUIRED, while retaining the bullet metadata below it.
Summary sections:
- heading with
mode,decision,base SHA, and report path Review Requiredline when the wrapper decision isreview-requiredCheckPR CheckorNOT_RUNBoundary Deltaswhen any boundary-expanding deltas existAllow Entry Candidatewhen the exact candidate can be generated- the fixed unavailable note when boundary expansion occurred but telemetry was unusable
In pr-check --all, the boundary summary uses the full boundary-expanding delta set across repo-level and block-level results.
Canonical sample workflow:
Ubuntu runner (observe, recommended for review UX):
- name: Generate BEAR artifacts
run: ./.bear/tools/bear-cli/bin/bear compile --all --project .
- name: BEAR CI governance
run: ./.bear/ci/bear-gates.sh --mode observeUbuntu runner (enforce):
- name: Generate BEAR artifacts
run: ./.bear/tools/bear-cli/bin/bear compile --all --project .
- name: BEAR CI governance (enforce)
run: ./.bear/ci/bear-gates.sh --mode enforceWindows runner (enforce):
- name: BEAR CI governance
shell: powershell
run: .\.bear\ci\bear-gates.ps1 --mode enforceThe sample workflow shows the intended GitHub pattern:
- checkout with full history so BEAR base resolution has the expected git context
- set up Java before invoking the vendored BEAR wrapper
- compile generated artifacts before running governance so
checkevaluates repo state rather than stale generated output - run
.bear/ci/bear-gates.sh --mode observewhen you want governance-review PRs to stay non-blocking but visible - upload
build/bear/ci/bear-ci-report.jsonandbuild/bear/ci/bear-ci-summary.mdas artifacts - optionally publish a sticky PR comment using the report + summary files, as the demo repo does
Runtime note:
bear-gates.shis a thin bash launcher that requirespwsh- if
pwshis unavailable, the script fails deterministically and tells the operator to install PowerShell 7 or runbear-gates.ps1directly - other CI systems should pass
--base-sha <sha>explicitly