Skip to content

feat: CI/CD status in diff viewer - #326

Closed
tstapler wants to merge 2 commits into
mainfrom
feature/ci-status-diff-viewer
Closed

feat: CI/CD status in diff viewer#326
tstapler wants to merge 2 commits into
mainfrom
feature/ci-status-diff-viewer

Conversation

@tstapler

@tstapler tstapler commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Summary

Show GitHub Actions CI status alongside the per-session diff viewer, block manual approval on failing CI (with an audited override), and let the auto-approve rule engine require green CI. Closes the backlog item migrated from issue #37.

What Changed

  • CIStatusBadge in the diff-viewer header: 4 states (passing/failing/pending/no-checks), links to the PR's Checks page, renders nothing when the session has no PR. Purely presentational over the already-delivered Session proto — no new fetch code.
  • Fixed PRStatusPoller.applyPRUpdate to publish a WatchSessions update when only the CI conclusion changes (previously gated on priority changes only), so the badge can't go stale between poll ticks.
  • ApprovalService.ResolveApproval blocks manual Approve when review:block-approval-on-ci-failure (default off) is on and the session's branch has failing CI, with a visible inline explanation in NotificationPanel (not a silent no-op). An audited OverrideCiBlock lets a reviewer bypass the block per-approval with a distinct log line.
  • pkg/classifier's Rule.RequireCIPassing ANDs with existing conditions (regex, tool name, etc.) via a new ClassificationContext.CIStatus field, persisted through RuleSpec, an ent schema column, and a new proto field. Exposed via a rule-builder UI checkbox.
  • CI status (GitHubCheckConclusion/GitHubPRNumber/LastPRStatusCheck) is not persisted — it only lives on the poller's in-memory Instance — so both gates read it through the existing LiveInstanceFinder interface (reused from WorkspaceService) rather than *session.Storage. Reads go through Instance.Snapshot(), not raw struct fields, since PRStatusPoller mutates those fields on its own goroutine (a real race caught by this PR's own architecture review — fixed and verified with go test -race).
  • A bounded-staleness guard (2x the poller's constructor-injected PollInterval) treats CIStatus as unknown for the auto-approve path; the manual-approve path's staleness gap is a documented P2 follow-up (project_plans/ci-status-diff-viewer/implementation/pre-mortem.md Failure chore: remove CLA and rename Claude Squad to Stapler Squad #2).
  • Feature registry entries for the new RPC field coverage and the CIStatusBadge UI feature.

Known Gap

approval-ci-block.spec.ts (a frontend e2e spec for the block/override UI, named in the original acceptance criteria) was not delivered — the notification-panel toggle button it would need to interact with does not render in the isolated e2e test server's DOM in any configuration tried, and no existing spec in this repo's ~100-file e2e suite exercises that surface either. The underlying block/override behavior is still covered by 7 real Go integration tests in approval_service_test.go.

Test Plan

  • go build ./... — clean
  • go test ./pkg/classifier/... ./session/... ./server/services/... — all pass
  • go test -race ./server/services/... -run "TestResolveApproval|TestHandlePermissionRequest" — clean (confirms the Snapshot() race fix)
  • npx tsc --noEmit — clean
  • npx jest CIStatusBadge — 12/12 passed
  • npx playwright test ci-status-badge.spec.ts rule-builder-ci-passing.spec.ts — 10/10 passed (chromium + chromium-dom), run against an isolated test server built from this exact branch
  • make registry-diff — 0.0% divergence

🤖 Generated with Claude Code

https://claude.ai/code/session_014qutTQcLJtXZhFiMCaD9Dm

Show GitHub Actions CI status alongside the per-session diff viewer, block
manual approval on failing CI (with an audited override), and let the
auto-approve rule engine require green CI.

## What Changed

- CIStatusBadge in the diff-viewer header: 4 states (passing/failing/
  pending/no-checks), links to the PR's Checks page, renders nothing when
  the session has no PR. Purely presentational over the already-delivered
  Session proto — no new fetch code.
- Fixed PRStatusPoller.applyPRUpdate to publish a WatchSessions update when
  only the CI conclusion changes (previously gated on priority changes
  only), so the badge can't go stale between poll ticks.
- ApprovalService.ResolveApproval blocks manual Approve when
  review:block-approval-on-ci-failure (default off) is on and the session's
  branch has failing CI, with a visible inline explanation in
  NotificationPanel (not a silent no-op). An audited OverrideCiBlock lets a
  reviewer bypass the block per-approval with a distinct log line.
- pkg/classifier's Rule.RequireCIPassing ANDs with existing conditions
  (regex, tool name, etc.) via a new ClassificationContext.CIStatus field,
  persisted through RuleSpec, an ent schema column, and a new proto field.
  Exposed via a rule-builder UI checkbox.
- CI status (GitHubCheckConclusion/GitHubPRNumber/LastPRStatusCheck) is not
  persisted — it only lives on the poller's in-memory Instance — so both
  gates read it through the existing LiveInstanceFinder interface (reused
  from WorkspaceService) rather than *session.Storage. Reads go through
  Instance.Snapshot(), not raw struct fields, since PRStatusPoller mutates
  those fields on its own goroutine.
- A bounded-staleness guard (2x the poller's constructor-injected
  PollInterval) treats CIStatus as unknown for the auto-approve path;
  the manual-approve path's staleness gap is a documented P2 follow-up
  (see project_plans/ci-status-diff-viewer/implementation/pre-mortem.md
  Failure #2).
- Feature registry entries for the new RPC field coverage and the
  CIStatusBadge UI feature.

## Test plan

- [x] go build ./... clean
- [x] go test ./pkg/classifier/... ./session/... ./server/services/... — all pass
- [x] npx tsc --noEmit — clean
- [x] npx jest CIStatusBadge — 12/12 passed
- [x] npx playwright test ci-status-badge.spec.ts rule-builder-ci-passing.spec.ts — 10/10 passed (chromium + chromium-dom), run against an isolated test server built from this exact branch
- [x] make registry-diff — 0.0% divergence

## Known gap

approval-ci-block.spec.ts (frontend e2e for the block/override UI) was not
delivered — the notification-panel toggle button this spec would need does
not render in the isolated test server's DOM in any configuration tried; no
existing spec in this repo's e2e suite exercises that surface either. The
underlying block/override behavior is covered by 7 Go integration tests in
approval_service_test.go.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qutTQcLJtXZhFiMCaD9Dm
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

✅ Registry Validation

Registry Validation
===================

Building backend scanner...
Scanning backend features...
Wrote 117 feature files to /tmp/tmp.X2ykLN8h3F/backend
Wrote 15 feature files to /tmp/tmp.X2ykLN8h3F/backend
Wrote 45 feature files to /tmp/tmp.X2ykLN8h3F/backend
Wrote 8 feature files to /tmp/tmp.X2ykLN8h3F/backend
Wrote 12 feature files to /tmp/tmp.X2ykLN8h3F/backend

=== Backend Registry Diff ===
Committed: 181  Generated: 181  Divergence: 0.0%
⚠️  109 feature(s) missing // +api: marker (markerFound: false)

✅ Registry validation passed. Divergence: 0.0%

Test Coverage: 27/181 features have testIds (14.9%)

Divergence > 2% blocks merges. Coverage reporting is advisory only.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Go Benchmarks (Tier 1)

benchmarks/go/tier1-baseline.txt:98: missing iteration count
benchmarks/go/tier1-baseline.txt:198: missing iteration count
tier1-bench.txt:98: missing iteration count
tier1-bench.txt:198: missing iteration count
goos: linux
goarch: amd64
pkg: github.com/tstapler/stapler-squad/session
cpu: AMD EPYC 7763 64-Core Processor                
                                            │ tier1-bench.txt │
                                            │     sec/op      │
CircularBufferWrite_4KB-4                        81.52n ±  2%
CircularBufferWrite_4KB_Allocs-4                 82.94n ±  1%
CircularBufferGetRecent_4KB-4                    503.6n ±  3%
CircularBufferGetAll-4                           4.722µ ± 14%
GetTimeSinceLastMeaningfulOutput_HotPath-4       65.78n ±  0%
GetTimeSinceLastMeaningfulOutput_ColdPath-4      32.81n ±  1%
geomean                                          180.6n

                                            │ tier1-bench.txt │
                                            │      B/op       │
CircularBufferWrite_4KB-4                        0.000 ± 0%
CircularBufferWrite_4KB_Allocs-4                 0.000 ± 0%
CircularBufferGetRecent_4KB-4                  4.000Ki ± 0%
CircularBufferGetAll-4                         40.00Ki ± 0%
GetTimeSinceLastMeaningfulOutput_HotPath-4       0.000 ± 0%
GetTimeSinceLastMeaningfulOutput_ColdPath-4      0.000 ± 0%
geomean                                                     ¹
¹ summaries must be >0 to compute geomean

                                            │ tier1-bench.txt │
                                            │    allocs/op    │
CircularBufferWrite_4KB-4                        0.000 ± 0%
CircularBufferWrite_4KB_Allocs-4                 0.000 ± 0%
CircularBufferGetRecent_4KB-4                    1.000 ± 0%
CircularBufferGetAll-4                           1.000 ± 0%
GetTimeSinceLastMeaningfulOutput_HotPath-4       0.000 ± 0%
GetTimeSinceLastMeaningfulOutput_ColdPath-4      0.000 ± 0%
geomean                                                     ¹
¹ summaries must be >0 to compute geomean

                              │ tier1-bench.txt │
                              │       B/s       │
CircularBufferWrite_4KB-4          46.80Gi ± 5%
CircularBufferGetRecent_4KB-4      7.574Gi ± 3%
geomean                            18.83Gi

cpu: AMD EPYC 9V74 80-Core Processor                
                                            │ benchmarks/go/tier1-baseline.txt │
                                            │              sec/op              │
CircularBufferWrite_4KB-4                                          62.61n ± 1%
CircularBufferWrite_4KB_Allocs-4                                   62.20n ± 2%
CircularBufferGetRecent_4KB-4                                      435.7n ± 3%
CircularBufferGetAll-4                                             3.059µ ± 1%
GetTimeSinceLastMeaningfulOutput_HotPath-4                         54.36n ± 1%
GetTimeSinceLastMeaningfulOutput_ColdPath-4                        26.76n ± 0%
geomean                                                            140.1n

                                            │ benchmarks/go/tier1-baseline.txt │
                                            │               B/op               │
CircularBufferWrite_4KB-4                                         0.000 ± 0%
CircularBufferWrite_4KB_Allocs-4                                  0.000 ± 0%
CircularBufferGetRecent_4KB-4                                   4.000Ki ± 0%
CircularBufferGetAll-4                                          40.00Ki ± 0%
GetTimeSinceLastMeaningfulOutput_HotPath-4                        0.000 ± 0%
GetTimeSinceLastMeaningfulOutput_ColdPath-4                       0.000 ± 0%
geomean                                                                      ¹
¹ summaries must be >0 to compute geomean

                                            │ benchmarks/go/tier1-baseline.txt │
                                            │            allocs/op             │
CircularBufferWrite_4KB-4                                         0.000 ± 0%
CircularBufferWrite_4KB_Allocs-4                                  0.000 ± 0%
CircularBufferGetRecent_4KB-4                                     1.000 ± 0%
CircularBufferGetAll-4                                            1.000 ± 0%
GetTimeSinceLastMeaningfulOutput_HotPath-4                        0.000 ± 0%
GetTimeSinceLastMeaningfulOutput_ColdPath-4                       0.000 ± 0%
geomean                                                                      ¹
¹ summaries must be >0 to compute geomean

                              │ benchmarks/go/tier1-baseline.txt │
                              │               B/s                │
CircularBufferWrite_4KB-4                           60.93Gi ± 1%
CircularBufferGetRecent_4KB-4                       8.755Gi ± 3%
geomean                                             23.10Gi

pkg: github.com/tstapler/stapler-squad/session/detection/ratelimit
cpu: AMD EPYC 7763 64-Core Processor                
                              │ tier1-bench.txt │
                              │     sec/op      │
StripANSI_PlainText-4               6.867n ± 0%
StripANSI_WithEscapes-4             746.8n ± 0%
ProcessOutput_InactiveState-4       6.324n ± 0%
geomean                             31.89n

                              │ tier1-bench.txt │
                              │      B/op       │
StripANSI_PlainText-4              0.000 ± 0%
StripANSI_WithEscapes-4            136.0 ± 0%
ProcessOutput_InactiveState-4      0.000 ± 0%
geomean                                       ¹
¹ summaries must be >0 to compute geomean

                              │ tier1-bench.txt │
                              │    allocs/op    │
StripANSI_PlainText-4              0.000 ± 0%
StripANSI_WithEscapes-4            5.000 ± 0%
ProcessOutput_InactiveState-4      0.000 ± 0%
geomean                                       ¹
¹ summaries must be >0 to compute geomean

cpu: AMD EPYC 9V74 80-Core Processor                
                              │ benchmarks/go/tier1-baseline.txt │
                              │              sec/op              │
StripANSI_PlainText-4                                5.464n ± 2%
StripANSI_WithEscapes-4                              510.0n ± 1%
ProcessOutput_InactiveState-4                        5.157n ± 1%
geomean                                              24.31n

                              │ benchmarks/go/tier1-baseline.txt │
                              │               B/op               │
StripANSI_PlainText-4                               0.000 ± 0%
StripANSI_WithEscapes-4                             136.0 ± 0%
ProcessOutput_InactiveState-4                       0.000 ± 0%
geomean                                                        ¹
¹ summaries must be >0 to compute geomean

                              │ benchmarks/go/tier1-baseline.txt │
                              │            allocs/op             │
StripANSI_PlainText-4                               0.000 ± 0%
StripANSI_WithEscapes-4                             5.000 ± 0%
ProcessOutput_InactiveState-4                       0.000 ± 0%
geomean                                                        ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/session/queue
cpu: AMD EPYC 7763 64-Core Processor                
                              │ tier1-bench.txt │
                              │     sec/op      │
ReviewQueue_ConcurrentReads-4       82.71n ± 3%
ReviewQueue_Add-4                   511.6n ± 1%
geomean                             205.7n

                              │ tier1-bench.txt │
                              │      B/op       │
ReviewQueue_ConcurrentReads-4      0.000 ± 0%
ReviewQueue_Add-4                  640.0 ± 0%
geomean                                       ¹
¹ summaries must be >0 to compute geomean

                              │ tier1-bench.txt │
                              │    allocs/op    │
ReviewQueue_ConcurrentReads-4      0.000 ± 0%
ReviewQueue_Add-4                  4.000 ± 0%
geomean                                       ¹
¹ summaries must be >0 to compute geomean

cpu: AMD EPYC 9V74 80-Core Processor                
                              │ benchmarks/go/tier1-baseline.txt │
                              │              sec/op              │
ReviewQueue_ConcurrentReads-4                       72.49n ± 16%
ReviewQueue_Add-4                                   389.4n ±  1%
geomean                                             168.0n

                              │ benchmarks/go/tier1-baseline.txt │
                              │               B/op               │
ReviewQueue_ConcurrentReads-4                       0.000 ± 0%
ReviewQueue_Add-4                                   640.0 ± 0%
geomean                                                        ¹
¹ summaries must be >0 to compute geomean

                              │ benchmarks/go/tier1-baseline.txt │
                              │            allocs/op             │
ReviewQueue_ConcurrentReads-4                       0.000 ± 0%
ReviewQueue_Add-4                                   4.000 ± 0%
geomean                                                        ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/session/scrollback
cpu: AMD EPYC 7763 64-Core Processor                
                                      │ tier1-bench.txt │
                                      │     sec/op      │
CircularBuffer_ConcurrentReadWrite-4        3.920µ ± 1%
CircularBuffer_BurstAppend-4                101.9µ ± 0%
CircularBuffer_GetLastN_LargeBuffer-4       20.22µ ± 1%
CircularBuffer_GetRange_Sequential-4        11.14µ ± 2%
CircularBufferAppend-4                      97.93n ± 0%
CircularBufferGetLastN-4                    2.225µ ± 3%
CircularBufferConcurrentAppend-4            129.8n ± 1%
geomean                                     3.065µ

                                      │ tier1-bench.txt │
                                      │      B/op       │
CircularBuffer_ConcurrentReadWrite-4       6.062Ki ± 0%
CircularBuffer_BurstAppend-4               62.50Ki ± 0%
CircularBuffer_GetLastN_LargeBuffer-4      56.00Ki ± 0%
CircularBuffer_GetRange_Sequential-4       28.00Ki ± 0%
CircularBufferAppend-4                       24.00 ± 0%
CircularBufferGetLastN-4                   6.000Ki ± 0%
CircularBufferConcurrentAppend-4             32.00 ± 0%
geomean                                    3.077Ki

                                      │ tier1-bench.txt │
                                      │    allocs/op    │
CircularBuffer_ConcurrentReadWrite-4         2.000 ± 0%
CircularBuffer_BurstAppend-4                1.000k ± 0%
CircularBuffer_GetLastN_LargeBuffer-4        1.000 ± 0%
CircularBuffer_GetRange_Sequential-4         1.000 ± 0%
CircularBufferAppend-4                       1.000 ± 0%
CircularBufferGetLastN-4                     1.000 ± 0%
CircularBufferConcurrentAppend-4             1.000 ± 0%
geomean                                      2.962

                             │ tier1-bench.txt │
                             │       B/s       │
CircularBuffer_BurstAppend-4      598.9Mi ± 1%

cpu: AMD EPYC 9V74 80-Core Processor                
                                      │ benchmarks/go/tier1-baseline.txt │
                                      │              sec/op              │
CircularBuffer_ConcurrentReadWrite-4                         2.809µ ± 3%
CircularBuffer_BurstAppend-4                                 84.26µ ± 3%
CircularBuffer_GetLastN_LargeBuffer-4                        16.00µ ± 4%
CircularBuffer_GetRange_Sequential-4                         10.96µ ± 7%
CircularBufferAppend-4                                       83.80n ± 2%
CircularBufferGetLastN-4                                     1.976µ ± 1%
CircularBufferConcurrentAppend-4                             104.6n ± 1%
geomean                                                      2.559µ

                                      │ benchmarks/go/tier1-baseline.txt │
                                      │               B/op               │
CircularBuffer_ConcurrentReadWrite-4                        6.062Ki ± 0%
CircularBuffer_BurstAppend-4                                62.50Ki ± 0%
CircularBuffer_GetLastN_LargeBuffer-4                       56.00Ki ± 0%
CircularBuffer_GetRange_Sequential-4                        28.00Ki ± 0%
CircularBufferAppend-4                                        24.00 ± 0%
CircularBufferGetLastN-4                                    6.000Ki ± 0%
CircularBufferConcurrentAppend-4                              32.00 ± 0%
geomean                                                     3.077Ki

                                      │ benchmarks/go/tier1-baseline.txt │
                                      │            allocs/op             │
CircularBuffer_ConcurrentReadWrite-4                          2.000 ± 0%
CircularBuffer_BurstAppend-4                                 1.000k ± 0%
CircularBuffer_GetLastN_LargeBuffer-4                         1.000 ± 0%
CircularBuffer_GetRange_Sequential-4                          1.000 ± 0%
CircularBufferAppend-4                                        1.000 ± 0%
CircularBufferGetLastN-4                                      1.000 ± 0%
CircularBufferConcurrentAppend-4                              1.000 ± 0%
geomean                                                       2.962

                             │ benchmarks/go/tier1-baseline.txt │
                             │               B/s                │
CircularBuffer_BurstAppend-4                       724.6Mi ± 4%

pkg: github.com/tstapler/stapler-squad/session/tmux
cpu: AMD EPYC 7763 64-Core Processor                
                             │ tier1-bench.txt │
                             │     sec/op      │
StripANSICodes_PlainText-4         6.877n ± 0%
StripANSICodes_WithEscapes-4       687.6n ± 1%
IsBanner_PlainText-4               478.5n ± 1%
geomean                            131.3n

                             │ tier1-bench.txt │
                             │      B/op       │
StripANSICodes_PlainText-4        0.000 ± 0%
StripANSICodes_WithEscapes-4      56.00 ± 0%
IsBanner_PlainText-4              0.000 ± 0%
geomean                                      ¹
¹ summaries must be >0 to compute geomean

                             │ tier1-bench.txt │
                             │    allocs/op    │
StripANSICodes_PlainText-4        0.000 ± 0%
StripANSICodes_WithEscapes-4      4.000 ± 0%
IsBanner_PlainText-4              0.000 ± 0%
geomean                                      ¹
¹ summaries must be >0 to compute geomean

cpu: AMD EPYC 9V74 80-Core Processor                
                             │ benchmarks/go/tier1-baseline.txt │
                             │              sec/op              │
StripANSICodes_PlainText-4                          5.663n ± 3%
StripANSICodes_WithEscapes-4                        481.8n ± 2%
IsBanner_PlainText-4                                360.8n ± 1%
geomean                                             99.48n

                             │ benchmarks/go/tier1-baseline.txt │
                             │               B/op               │
StripANSICodes_PlainText-4                         0.000 ± 0%
StripANSICodes_WithEscapes-4                       56.00 ± 0%
IsBanner_PlainText-4                               0.000 ± 0%
geomean                                                       ¹
¹ summaries must be >0 to compute geomean

                             │ benchmarks/go/tier1-baseline.txt │
                             │            allocs/op             │
StripANSICodes_PlainText-4                         0.000 ± 0%
StripANSICodes_WithEscapes-4                       4.000 ± 0%
IsBanner_PlainText-4                               0.000 ± 0%
geomean                                                       ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/session/tokens
cpu: AMD EPYC 7763 64-Core Processor                
                                   │ tier1-bench.txt │
                                   │     sec/op      │
TokenParser_ProcessUserEntry-4           5.333m ± 1%
DetectCommandsInText/NoSlash-4           7.496n ± 0%
DetectCommandsInText/WithCommand-4       1.654µ ± 0%
geomean                                  4.044µ

                                   │ tier1-bench.txt │
                                   │      B/op       │
TokenParser_ProcessUserEntry-4        11.02Mi ± 0%
DetectCommandsInText/NoSlash-4          0.000 ± 0%
DetectCommandsInText/WithCommand-4      433.0 ± 0%
geomean                                            ¹
¹ summaries must be >0 to compute geomean

                                   │ tier1-bench.txt │
                                   │    allocs/op    │
TokenParser_ProcessUserEntry-4          34.00 ± 0%
DetectCommandsInText/NoSlash-4          0.000 ± 0%
DetectCommandsInText/WithCommand-4      6.000 ± 0%
geomean                                            ¹
¹ summaries must be >0 to compute geomean

cpu: AMD EPYC 9V74 80-Core Processor                
                                   │ benchmarks/go/tier1-baseline.txt │
                                   │              sec/op              │
TokenParser_ProcessUserEntry-4                            4.330m ± 1%
DetectCommandsInText/NoSlash-4                            5.190n ± 5%
DetectCommandsInText/WithCommand-4                        1.157µ ± 0%
geomean                                                   2.963µ

                                   │ benchmarks/go/tier1-baseline.txt │
                                   │               B/op               │
TokenParser_ProcessUserEntry-4                         11.02Mi ± 0%
DetectCommandsInText/NoSlash-4                           0.000 ± 0%
DetectCommandsInText/WithCommand-4                       433.0 ± 0%
geomean                                                             ¹
¹ summaries must be >0 to compute geomean

                                   │ benchmarks/go/tier1-baseline.txt │
                                   │            allocs/op             │
TokenParser_ProcessUserEntry-4                           34.00 ± 0%
DetectCommandsInText/NoSlash-4                           0.000 ± 0%
DetectCommandsInText/WithCommand-4                       6.000 ± 0%
geomean                                                             ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/session/unfinished
cpu: AMD EPYC 7763 64-Core Processor                
                               │ tier1-bench.txt │
                               │     sec/op      │
DiffShortstat/GitVCSReader-4         3.215m ± 1%
DiffShortstat/GoGitVCSReader-4       80.64n ± 0%
DiffShortstatCached-4                75.95n ± 2%
geomean                              2.700µ

                               │ tier1-bench.txt │
                               │      B/op       │
DiffShortstat/GitVCSReader-4      62.56Ki ± 0%
DiffShortstat/GoGitVCSReader-4      0.000 ± 0%
DiffShortstatCached-4               0.000 ± 0%
geomean                                        ¹
¹ summaries must be >0 to compute geomean

                               │ tier1-bench.txt │
                               │    allocs/op    │
DiffShortstat/GitVCSReader-4        360.0 ± 0%
DiffShortstat/GoGitVCSReader-4      0.000 ± 0%
DiffShortstatCached-4               0.000 ± 0%
geomean                                        ¹
¹ summaries must be >0 to compute geomean

cpu: AMD EPYC 9V74 80-Core Processor                
                               │ benchmarks/go/tier1-baseline.txt │
                               │              sec/op              │
DiffShortstat/GitVCSReader-4                          2.703m ± 1%
DiffShortstat/GoGitVCSReader-4                        62.74n ± 0%
DiffShortstatCached-4                                 63.43n ± 5%
geomean                                               2.208µ

                               │ benchmarks/go/tier1-baseline.txt │
                               │               B/op               │
DiffShortstat/GitVCSReader-4                       62.57Ki ± 0%
DiffShortstat/GoGitVCSReader-4                       0.000 ± 0%
DiffShortstatCached-4                                0.000 ± 0%
geomean                                                         ¹
¹ summaries must be >0 to compute geomean

                               │ benchmarks/go/tier1-baseline.txt │
                               │            allocs/op             │
DiffShortstat/GitVCSReader-4                         360.0 ± 0%
DiffShortstat/GoGitVCSReader-4                       0.000 ± 0%
DiffShortstatCached-4                                0.000 ± 0%
geomean                                                         ¹
¹ summaries must be >0 to compute geomean

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

E2E RPC Latency

list-sessions-ttfb-mean: 7ms (▲ slower +7.8%; baseline: 7ms)
list-sessions-total-mean: 13ms (▲ slower +12.1%; baseline: 12ms)

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Frontend Terminal Throughput

terminal-throughput-mean: 16 KB/s ▲ +18.6% (baseline: 14 KB/s)
terminal-throughput-p50: 17 KB/s ▲ +6.2% (baseline: 16 KB/s)

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

UX Analysis

Check Status Details
✅ Axe Core (WCAG 2.1 AA) success Critical/serious violations block merge
⚠️ Lighthouse Performance Score: unknown Warning if < 70 (non-blocking)
🤖 Claude UX Analysis Advisory See docs/qa/ for findings

Axe Core excludes terminal rendering areas (intentional design).
Lighthouse runs in desktop preset for this developer tool.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

📊 Feature E2E Coverage

Feature coverage report unavailable

Run make e2e-report locally to view the full Allure report.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

🎬 E2E Feature Demos

2 shard(s) recorded feature flows for this PR.

recordings shard 1
recordings shard 2

Demo preview opens directly in browser (single-file HTML). Raw WebM recordings in ZIP. Expires after 30 days.

…entry

make registry-generate (via encoding/json) escapes > as >; my hand-
authored testIds used a literal >, which made `git diff --exit-code
docs/registry/features/` fail in CI after a real make registry-generate
run. Regenerated the file with the actual tool instead of hand-editing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qutTQcLJtXZhFiMCaD9Dm
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

✅ Registry Validation

Registry Validation
===================

Building backend scanner...
Scanning backend features...
Wrote 117 feature files to /tmp/tmp.C5qb3vjn1F/backend
Wrote 15 feature files to /tmp/tmp.C5qb3vjn1F/backend
Wrote 45 feature files to /tmp/tmp.C5qb3vjn1F/backend
Wrote 8 feature files to /tmp/tmp.C5qb3vjn1F/backend
Wrote 12 feature files to /tmp/tmp.C5qb3vjn1F/backend

=== Backend Registry Diff ===
Committed: 181  Generated: 181  Divergence: 0.0%
⚠️  109 feature(s) missing // +api: marker (markerFound: false)

✅ Registry validation passed. Divergence: 0.0%

Test Coverage: 27/181 features have testIds (14.9%)

Divergence > 2% blocks merges. Coverage reporting is advisory only.

@tstapler
tstapler marked this pull request as ready for review August 4, 2026 07:10
Copilot AI lite review requested due to automatic review settings August 4, 2026 07:11

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds GitHub Actions CI status visibility and enforcement across the diff viewer and approval/rule flows, including a manual-approval CI-failure block (with audited override) and an auto-approve rule condition that requires green CI.

Changes:

  • Render a session-scoped CI status badge in the diff viewer header (with unit + e2e coverage).
  • Add require_ci_passing to approval-rule persistence (proto/ent/JSON/UI) and classifier evaluation context.
  • Enforce (flagged) manual-approval blocking on failing CI via live-instance CI state, plus poller/event updates to prevent staleness.

Reviewed changes

Copilot reviewed 40 out of 50 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
web-app/src/lib/ruleTemplates.ts Extends rule template shape with requireCiPassing.
web-app/src/lib/hooks/useApprovalRules.ts Threads requireCiPassing through rule hydration/mapping.
web-app/src/gen/session/v1/types_pb.ts Regenerates TS proto types to include require_ci_passing.
web-app/src/components/ui/NotificationPanel.tsx Adds CI-blocked inline UI + override path for ResolveApproval.
web-app/src/components/ui/NotificationPanel.css.ts Styles for CI-blocked row/text.
web-app/src/components/sessions/tests/CIStatusBadge.test.tsx Unit tests for CI status badge rendering/linking/staleness tooltip.
web-app/src/components/sessions/SessionDetailView.tsx Passes session into DiffViewer for badge rendering.
web-app/src/components/sessions/DiffViewer.tsx Renders CIStatusBadge in diff header when session has a PR.
web-app/src/components/sessions/DiffViewer.css.ts Adds header layout styling for diff viewer.
web-app/src/components/sessions/CIStatusBadge.tsx Implements presentational CI status badge with link to PR checks.
web-app/src/components/rules/RuleBuilderForm.tsx Adds “Require CI passing” checkbox and includes it in payload.
web-app/src/app/settings/features/page.tsx Adds feature-flag label for CI-failure approval block.
tests/e2e/rule-builder-ci-passing.spec.ts E2E coverage for rule-builder checkbox + persistence via RPC.
tests/e2e/pages/SessionDetailPage.ts Adds page object accessor for CI status badge.
tests/e2e/ci-status-badge.spec.ts E2E coverage for diff-viewer CI badge (via response injection).
session/repository.go Adds RequireCIPassing to rule data model.
session/pr_status_poller_test.go Regression test for conclusion-only updates firing onUpdated.
session/pr_status_poller.go Emits updates/logs on CI conclusion changes; adds PollInterval accessor.
session/instance_terminal.go Tracks conclusion-change flag in PR status update result.
session/ent_repository.go Persists RequireCIPassing via ent repository mapping.
session/ent/schema/approvalrule.go Adds require_ci_passing bool column (default false).
server/services/session_service.go Wires live instance finder into ApprovalService.
server/services/rules_store.go Adds require_ci_passing to RuleSpec and classifier rule creation.
server/services/rules_service.go Threads require_ci_passing between proto/spec/classifier.
server/services/feature_flag_service.go Registers CI-failure approval-block flag and constant.
server/services/approval_service_test.go Adds integration tests for CI-failure block + override behavior.
server/services/approval_service.go Implements CI-failure block with override (using live instance snapshot).
server/services/approval_handler_integration_test.go Tests CI status staleness guard and CIStatus context propagation.
server/services/approval_handler.go Adds CIStatus to classifier context with staleness bounding.
server/server.go Wires poll interval + live finder into ApprovalHandler.
server/dependencies.go Expands WatchSessions update fields to include CI conclusion.
proto/session/v1/types.proto Adds require_ci_passing to ApprovalRuleProto.
proto/session/v1/session.proto Adds override_ci_block to ResolveApprovalRequest.
project_plans/ci-status-diff-viewer/implementation/plan.md Documents discovered persistence assumptions + wiring deviations.
pkg/classifier/classifier_test.go Adds classifier tests for RequireCIPassing behavior and AND semantics.
pkg/classifier/classifier.go Adds CIStatus context + RequireCIPassing rule condition.
docs/registry/features/frontend/ci-status-badge.json Feature registry entry for CI badge + tests.
docs/registry/features/backend/approval/upsert-rule.json Marks UpsertApprovalRule as tested w/ new e2e tests.
docs/registry/features/backend/approval/resolve.json Marks ResolveApproval as tested w/ new CI-block tests.
Files not reviewed (10)
  • gen/proto/go/session/v1/session.pb.go: Generated file
  • gen/proto/go/session/v1/types.pb.go: Generated file
  • session/ent/approvalrule.go: Generated file
  • session/ent/approvalrule/approvalrule.go: Generated file
  • session/ent/approvalrule/where.go: Generated file
  • session/ent/approvalrule_create.go: Generated file
  • session/ent/approvalrule_update.go: Generated file
  • session/ent/migrate/schema.go: Generated file
  • session/ent/mutation.go: Generated file
  • session/ent/runtime.go: Generated file
Suppressed comments (3)

web-app/src/components/sessions/CIStatusBadge.tsx:1

  • Rendering the badge as an <a> with role=\"status\" is semantically conflicting (an interactive element presented as a live-region status), and when href is undefined it stops being keyboard-focusable while still being a ‘status’. A more accessible approach is to render a non-interactive status element (e.g., a <span role=\"status\">…</span>) when there is no PR URL, and render a normal link (default link semantics, no role=\"status\") when href is present; if you keep a single element, also avoid target/rel when href is absent.
    web-app/src/components/sessions/tests/CIStatusBadge.test.tsx:1
  • This test mutates global.fetch but doesn’t restore it in a try/finally. If an assertion throws before line 102, subsequent tests can be affected by the mocked fetch; wrap the render/assert in try and restore global.fetch in finally.
    tests/e2e/ci-status-badge.spec.ts:1
  • This loop calls mockCIStatus(page, …) repeatedly, which registers new page.route() handlers each time. Playwright routes are additive unless explicitly removed, so handlers can accumulate across iterations and make the spec flaky. Consider registering a single route once (using a mutable map keyed by sessionId), or call page.unroute() before re-registering routes in each iteration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread server/dependencies.go
warren.SetAlways(w2, "PRStatusPoller.Instances", svc.PRStatusPoller.SetInstances, instances)
warren.SetAlways(w2, "PRStatusPoller.OnUpdated", svc.PRStatusPoller.SetOnUpdated, func(inst *session.Instance) {
eventBus.Publish(events.NewSessionUpdatedEvent(inst, []string{"github_pr_priority", "github_pr_state"}))
eventBus.Publish(events.NewSessionUpdatedEvent(inst, []string{"github_pr_priority", "github_pr_state", "github_check_conclusion"}))
@tstapler

tstapler commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

Superseded — item 3065ecfb (CI/CD status in diff viewer) shipped via PR #353 (merged 2026-08-06). Closing this earlier duplicate attempt.

@tstapler tstapler closed this Aug 6, 2026
@tstapler
tstapler deleted the feature/ci-status-diff-viewer branch August 6, 2026 14:24
tstapler added a commit that referenced this pull request Aug 6, 2026
… an audited override (#351)

* fix(session): guard reconciliation automation against override-linked PR mismatches

Story 5/6 of report-pr-created-branch-mismatch: adds a prByNumberFinder seam
and verifyPRHeadBranchMatchesTracked guard that re-verifies, via a live
GitHub lookup, that a backlog item's PR still has the head branch this item
is tracking before three automated reconciliation call sites
(closeIfSupersededByMain's auto-close, ReconcilePRPending's merge-detected
done transition, reconcileBouncingItems' IsPRMerged-driven done transition)
treat item.PrNumber as ground truth. Also prepends an "unverified
association" disclaimer to spawned fix-session context when the guard
can't confirm a match (Task 6.3a), and documents reconcileOrphanedAgentPRs'
identical pre-existing blind spot (Task 5.1, no functional change).

Fixing 7 pre-existing tests that exercised the now-guarded mutation paths
without any tracked-branch/worktree fixture required adding real work
sessions (via SaveInstances) and a matching prByNumberFinder stub so the
guard verifies true, preserving their original assertions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SpCssZgc5WeLQ5B45q7XV5

* fix(mcp): allow report_pr_created to accept a self-authored PR on a fallback branch

Rewrite VerifyPRMatchesBranch to look PRs up by number (GetPRByNumber) instead of
by head branch, returning a PRVerification value (via the invariant-enforcing
NewPRVerification constructor) instead of a bare bool so reportPRCreated can
distinguish "no such PR" from "PR exists but head branch differs." Add
decideOverridePolicy, a pure decision function gating a new override_reason
argument: a mismatched branch is now accepted only when the caller supplies a
reason and the PR was authored by this server's own resolved GitHub identity
(existence still can never be overridden). Accepted overrides are audited via
a structured log.Warn line.

Covers Stories 2-4 of project_plans/report-pr-created-branch-mismatch's
implementation plan.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SpCssZgc5WeLQ5B45q7XV5

* fix(github): add GetPRByNumber for root-cause PR lookup by number

Adds PRStateOpen/PRStateClosed/PRStateMerged constants and GetPRByNumber,
which looks a PR up by its immutable number via the REST single-PR
endpoint instead of by head branch name, closing the root cause of PR
lookups matching the wrong PR when a branch is reused or renamed.
Validates base.repo.full_name against the requested owner/repo before
returning success, and parses the REST response's user.login (not
author.login, a different endpoint shape) into PRInfo.Author.

Covers Story 1 / Task 1.1-1.3 of the report-pr-created-branch-mismatch
implementation plan: unit tests against httptest fixtures, plus a
live_github-tagged manual test against real, public PR #326.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SpCssZgc5WeLQ5B45q7XV5

* fix(github): don't pin live test to PR #326's mutable merged state

The live-github test asserted State == PRStateMerged, but PR #326 is
currently open on real GitHub, not merged, contradicting the bug report's
"shipped and merged cleanly" framing. HeadRef/Author/BaseRef all parsed
correctly against the real payload, proving the fix's parsing logic is
sound — only the hardcoded lifecycle-state assertion was wrong, since an
external, mutable PR's current state isn't what this test exists to pin.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SpCssZgc5WeLQ5B45q7XV5

* chore(sdd): implementation plan for report-pr-created-branch-mismatch

Plan resolves the architecture.md-vs-pitfalls.md tension by rejecting both
the ancestry/compare-API fallback (mathematically can't distinguish a
history-severed recovery branch from an unrelated PR) and a separate
operator-override tool (no human/operator auth primitive exists in this
codebase), in favor of a root-cause fix (number-keyed GitHub PR lookup) plus
a same-tool relaxed check gated by a mandatory, audited override_reason
argument. Includes ADR-001 documenting that override_reason is a role-gated
+ audit-trail control, not a human-gated one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SpCssZgc5WeLQ5B45q7XV5

* chore(sdd): verify/review artifacts for report-pr-created-branch-mismatch

Adds pre-mortem, architecture review, adversarial review, and validation
docs, and updates ADR-001 and plan.md with fourth plan-repair pass fixes,
produced during sdd:4-validate/6-verify passes on this bugfix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(mcp,session): resolve fallout from rebasing onto post-#346 main

Cherry-picking this fix's commits onto origin/main (cut clean of this
branch's unrelated worktree-shared history, per this same bug's own
documented recovery) surfaced three silent (non-conflicting) merge
issues go vet/test caught but git's line-based merge did not:

- server/mcp/tools_github_test.go and tools_backlog_test.go each
  independently declared resetGhBaseURL; drop the duplicate.
- Two pre-existing tools_backlog_test.go tests still constructed
  verifyPRMatchesBranch mocks against the pre-fix (bool, error)
  signature; update to PRVerification.
- PR #346 (merged same day, unrelated to this item) added
  TestReconcilePRPending_should_StillCloseSupersededPR_When_SessionsRealTipIsOnMain,
  which this fix's new closeIfSupersededByMain branch-verification guard
  (Story 6) now fails closed against since its fixture never stubs a
  PRByNumberFinder. Stub one via the existing stubMatchingPRByNumberFinder
  helper so the guard confirms the match instead.

go build ./... && go test ./session/... ./server/mcp/... ./github/... all pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants