Skip to content

fix(backlog): address architecture-review follow-ups from post-merge audit - #142

Merged
tstapler merged 5 commits into
mainfrom
fix-backlog-architecture-review-followups
Jul 4, 2026
Merged

fix(backlog): address architecture-review follow-ups from post-merge audit#142
tstapler merged 5 commits into
mainfrom
fix-backlog-architecture-review-followups

Conversation

@tstapler

@tstapler tstapler commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes all 7 findings from a dedicated two-agent architecture review of the GitHub backlog sync feature (PR #138) and the execution-phase prompt driver, run after those areas had already shipped/closed. Filed as gap #10 in project_plans/backlog-cross-platform-audit/gaps-and-risks.md; this PR fixes and closes it.

Execution-phase prompt driver:

  • Cross-referenced Instance.Prompt/Instance.InitialPrompt doc comments (and the matching proto comments) so the CLI-arg vs tmux-typed delivery mechanisms are no longer near-duplicate one-liners.
  • SpawnSessionFromItem now writes slash-commands/context file before spawning the session (new session.EnsureDirectorySessionPath helper), eliminating a race against the just-started claude process instead of relying on startup latency.
  • WriteBacklogContextFile now carries priorSessions and the plan-artifacts line consistently with the live CLI prompt, so the on-disk fallback the agent re-reads after context compaction doesn't lose history.

Backlog sync feature, end-to-end:

  • UpdateFeatureFlag now rolls back the persisted disk flag and returns an error if the controller toggle fails, instead of silently diverging disk/in-memory state.
  • GitHubPRsPlugin.Fetch now fetches CI labels via a bounded errgroup (concurrency 5) instead of serially, avoiding a timeout on repos with many open PRs.
  • BacklogSourcesSettings.tsx's source-creation form is now driven by a per-plugin field schema instead of hardcoded owner/repo fields.
  • GetSyncHistory now reports a truncated flag (new proto field) when history is capped at 200, surfaced as an inline UI notice.

Test plan

  • go build ./..., go vet ./... clean
  • go test ./session/... ./server/... -race all green
  • npx tsc --noEmit clean
  • Full frontend jest suite green (aside from pre-existing, unrelated SessionCard AnalyticsContextProvider failures already confirmed present on clean main)
  • 9 new regression tests added across Go and TS covering each fix

🤖 Generated with Claude Code

…audit

Fixes 7 findings from a dedicated architecture review of the GitHub backlog
sync feature and execution-phase prompt driver (docs/plans backlog audit gap
#10): cross-referenced Prompt/InitialPrompt doc comments, spawn now writes
slash-commands/context file before starting the claude process instead of
racing it, WriteBacklogContextFile carries prior-session history and plan
artifacts consistently with the live CLI prompt, feature-flag toggles roll
back disk state on controller failure instead of silently diverging, GitHub
PR CI-label fetch runs concurrently instead of serially, the source-creation
form is driven by a per-plugin field schema instead of hardcoded owner/repo,
and GetSyncHistory reports a truncated flag when history is capped at 200.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 4, 2026 05:38

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.

Addresses post-merge architecture-review findings for the backlog sync feature and execution-phase prompt driver, focusing on correctness, race elimination, and better UI/API signaling.

Changes:

  • Adds a truncation signal for sync history (proto + server + UI) and updates the web hook/service types accordingly.
  • Eliminates a spawn-time race by ensuring directory sessions are prepared (dir exists + prewritten context/commands) before launching the session process.
  • Improves reliability/perf with feature-flag rollback on controller failure and bounded-concurrency CI-label fetching for GitHub PR sync.

Reviewed changes

Copilot reviewed 23 out of 25 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
web-app/src/lib/hooks/useBacklogSourcesService.ts Extends getSyncHistory to return { events, truncated } instead of just events.
web-app/src/gen/session/v1/session_pb.ts Updates generated comments to clarify prompt vs initial_prompt semantics.
web-app/src/gen/session/v1/backlog_pb.ts Adds generated truncated field to GetSyncHistoryResponse.
web-app/src/components/settings/BacklogSourcesSettings.tsx Converts “Add Source” form to schema-driven fields and displays history truncation notice.
web-app/src/components/settings/BacklogSourcesSettings.test.tsx Adds tests for schema field reset on plugin switch + truncation notice UI.
session/storage.go Updates ListSourceSyncEvents signature to return truncation + adds CreateSourceSyncEvent delegation.
session/instance_worktree.go Adds EnsureDirectorySessionPath helper and uses it for directory sessions.
session/instance.go Clarifies docs for Prompt vs InitialPrompt and their delivery mechanisms.
session/ent_repository_backlog.go Detects truncation by over-fetching sync events and returning (events, truncated).
session/backlog_sync_test.go Updates tests for new return values and adds truncation edge-case coverage.
session/backlog_plugin_github_test.go Adds regression test for concurrent CI-label fetching preserving per-PR labeling/order.
session/backlog_plugin_github_prs.go Fetches CI labels concurrently with bounded errgroup to avoid timeouts.
session/backlog_context.go Moves plan-artifacts line into BuildSessionInitialPrompt for consistent rendering.
session/backlog_commands_test.go Updates call site for new WriteBacklogContextFile signature.
session/backlog_commands.go Threads priorSessions through context file generation for consistency with live prompt.
server/services/feature_flags_test.go Adds regression test for disk rollback when controller enable fails.
server/services/feature_flag_service.go Rolls back persisted disk flag and returns an error if controller toggle fails.
server/services/backlog_service_test.go Verifies GetSyncHistory surfaces truncation via proto.
server/services/backlog_service.go Writes slash commands + context file before spawning session; returns truncated in RPC.
server/dependencies.go Logs startup enable failures as errors (avoids “enabled” log on failure).
proto/session/v1/session.proto Updates field comments for prompt vs initial_prompt.
proto/session/v1/backlog.proto Adds GetSyncHistoryResponse.truncated.
project_plans/backlog-cross-platform-audit/gaps-and-risks.md Marks architecture-review gap #10 as fixed with detailed changelog.
Files not reviewed (2)
  • gen/proto/go/session/v1/backlog.pb.go: Generated file
  • gen/proto/go/session/v1/session.pb.go: Generated file

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

Comment thread session/instance_worktree.go
Comment thread web-app/src/components/settings/BacklogSourcesSettings.tsx
Comment thread web-app/src/lib/hooks/useBacklogSourcesService.ts
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

✅ Registry Validation

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

Building backend scanner...
Scanning backend features...
Wrote 116 feature files to /tmp/tmp.vo0LdzO75K/backend
Wrote 14 feature files to /tmp/tmp.vo0LdzO75K/backend
Wrote 24 feature files to /tmp/tmp.vo0LdzO75K/backend
Wrote 6 feature files to /tmp/tmp.vo0LdzO75K/backend
Wrote 10 feature files to /tmp/tmp.vo0LdzO75K/backend

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

✅ Registry validation passed. Divergence: 0.0%

Test Coverage: 4/158 features have testIds (2.5%)

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

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

E2E RPC Latency

list-sessions-ttfb-mean: 7ms (▼ faster -25.1%; baseline: 9ms)
list-sessions-total-mean: 9ms (▼ faster -31.4%; baseline: 13ms)

@github-actions

github-actions Bot commented Jul 4, 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 Jul 4, 2026

Copy link
Copy Markdown
Contributor

Go Benchmarks (Tier 1)

benchmarks/go/tier1-baseline.txt:6: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:1945: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:3949: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:6014: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:8000: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:9997: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:11955: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:13884: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:15898: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:22402: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:28221: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:34917: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:41300: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:48882: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:55104: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:61571: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:67805: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:73120: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:78928: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:84484: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:90129: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:95629: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:101480: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:106695: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:112434: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:120191: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:128162: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:134784: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:143171: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:150790: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:158567: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:165961: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:173759: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:181057: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:188051: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:195558: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:202857: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:209746: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:217566: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:224992: parsing iteration count: invalid syntax
tier1-bench.txt:6: parsing iteration count: invalid syntax
tier1-bench.txt:2001: parsing iteration count: invalid syntax
tier1-bench.txt:4028: parsing iteration count: invalid syntax
tier1-bench.txt:6071: parsing iteration count: invalid syntax
tier1-bench.txt:8087: parsing iteration count: invalid syntax
tier1-bench.txt:10153: parsing iteration count: invalid syntax
tier1-bench.txt:12209: parsing iteration count: invalid syntax
tier1-bench.txt:14240: parsing iteration count: invalid syntax
tier1-bench.txt:16265: parsing iteration count: invalid syntax
tier1-bench.txt:22950: parsing iteration count: invalid syntax
tier1-bench.txt:29859: parsing iteration count: invalid syntax
tier1-bench.txt:36076: parsing iteration count: invalid syntax
tier1-bench.txt:42490: parsing iteration count: invalid syntax
tier1-bench.txt:48596: parsing iteration count: invalid syntax
tier1-bench.txt:55223: parsing iteration count: invalid syntax
tier1-bench.txt:62658: parsing iteration count: invalid syntax
tier1-bench.txt:69439: parsing iteration count: invalid syntax
tier1-bench.txt:74549: parsing iteration count: invalid syntax
tier1-bench.txt:79848: parsing iteration count: invalid syntax
tier1-bench.txt:85753: parsing iteration count: invalid syntax
tier1-bench.txt:90903: parsing iteration count: invalid syntax
tier1-bench.txt:96699: parsing iteration count: invalid syntax
tier1-bench.txt:102390: parsing iteration count: invalid syntax
tier1-bench.txt:107613: parsing iteration count: invalid syntax
tier1-bench.txt:112881: parsing iteration count: invalid syntax
tier1-bench.txt:120049: parsing iteration count: invalid syntax
tier1-bench.txt:128087: parsing iteration count: invalid syntax
tier1-bench.txt:134968: parsing iteration count: invalid syntax
tier1-bench.txt:142164: parsing iteration count: invalid syntax
tier1-bench.txt:149199: parsing iteration count: invalid syntax
tier1-bench.txt:156810: parsing iteration count: invalid syntax
tier1-bench.txt:164100: parsing iteration count: invalid syntax
tier1-bench.txt:171227: parsing iteration count: invalid syntax
tier1-bench.txt:178632: parsing iteration count: invalid syntax
tier1-bench.txt:185892: parsing iteration count: invalid syntax
tier1-bench.txt:193033: parsing iteration count: invalid syntax
tier1-bench.txt:201557: parsing iteration count: invalid syntax
tier1-bench.txt:208475: parsing iteration count: invalid syntax
tier1-bench.txt:215717: parsing iteration count: invalid syntax
tier1-bench.txt:223997: parsing iteration count: invalid syntax
goos: linux
goarch: amd64
pkg: github.com/tstapler/stapler-squad/session
cpu: AMD EPYC 7763 64-Core Processor                
                                            │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt          │
                                            │              sec/op              │   sec/op     vs base              │
CircularBufferWrite_4KB-4                                          82.42n ± 2%   82.01n ± 2%       ~ (p=0.593 n=8)
CircularBufferWrite_4KB_Allocs-4                                   82.20n ± 2%   82.45n ± 2%       ~ (p=0.223 n=8)
CircularBufferGetRecent_4KB-4                                      513.3n ± 3%   498.1n ± 2%  -2.96% (p=0.007 n=8)
CircularBufferGetAll-4                                             4.025µ ± 3%   3.888µ ± 3%       ~ (p=0.083 n=8)
GetTimeSinceLastMeaningfulOutput_HotPath-4                         66.01n ± 1%   65.09n ± 1%  -1.38% (p=0.002 n=8)
GetTimeSinceLastMeaningfulOutput_ColdPath-4                        39.18n ± 0%   39.26n ± 0%       ~ (p=0.159 n=8)
geomean                                                            181.9n        179.5n       -1.30%

                                            │ benchmarks/go/tier1-baseline.txt │           tier1-bench.txt            │
                                            │               B/op               │     B/op      vs base                │
CircularBufferWrite_4KB-4                                         0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferWrite_4KB_Allocs-4                                  0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferGetRecent_4KB-4                                   4.000Ki ± 0%     4.000Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferGetAll-4                                          40.00Ki ± 0%     40.00Ki ± 0%       ~ (p=1.000 n=8) ¹
GetTimeSinceLastMeaningfulOutput_HotPath-4                        0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
GetTimeSinceLastMeaningfulOutput_ColdPath-4                       0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                                      ²                 +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                            │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                                            │            allocs/op             │ allocs/op   vs base                │
CircularBufferWrite_4KB-4                                         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferWrite_4KB_Allocs-4                                  0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferGetRecent_4KB-4                                     1.000 ± 0%     1.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferGetAll-4                                            1.000 ± 0%     1.000 ± 0%       ~ (p=1.000 n=8) ¹
GetTimeSinceLastMeaningfulOutput_HotPath-4                        0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
GetTimeSinceLastMeaningfulOutput_ColdPath-4                       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                                      ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                              │               B/s                │     B/s       vs base              │
CircularBufferWrite_4KB-4                           46.28Gi ± 2%   46.52Gi ± 2%       ~ (p=0.645 n=8)
CircularBufferGetRecent_4KB-4                       7.433Gi ± 3%   7.659Gi ± 1%  +3.05% (p=0.007 n=8)
geomean                                             18.55Gi        18.88Gi       +1.77%

pkg: github.com/tstapler/stapler-squad/session/detection/ratelimit
                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt          │
                              │              sec/op              │   sec/op     vs base              │
StripANSI_PlainText-4                                6.869n ± 1%   6.861n ± 0%       ~ (p=0.098 n=8)
StripANSI_WithEscapes-4                              751.3n ± 1%   751.6n ± 1%       ~ (p=0.524 n=8)
ProcessOutput_InactiveState-4                        6.037n ± 0%   6.039n ± 0%       ~ (p=0.939 n=8)
geomean                                              31.47n        31.46n       -0.01%

                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                              │               B/op               │    B/op     vs base                │
StripANSI_PlainText-4                               0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
StripANSI_WithEscapes-4                             136.0 ± 0%     136.0 ± 0%       ~ (p=1.000 n=8) ¹
ProcessOutput_InactiveState-4                       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                        ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                              │            allocs/op             │ allocs/op   vs base                │
StripANSI_PlainText-4                               0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
StripANSI_WithEscapes-4                             5.000 ± 0%     5.000 ± 0%       ~ (p=1.000 n=8) ¹
ProcessOutput_InactiveState-4                       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                        ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/session/queue
                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt          │
                              │              sec/op              │   sec/op     vs base              │
ReviewQueue_ConcurrentReads-4                        84.20n ± 5%   84.37n ± 5%       ~ (p=0.818 n=8)
ReviewQueue_Add-4                                    494.0n ± 1%   492.4n ± 0%       ~ (p=0.328 n=8)
geomean                                              203.9n        203.8n       -0.06%

                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                              │               B/op               │    B/op     vs base                │
ReviewQueue_ConcurrentReads-4                       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
ReviewQueue_Add-4                                   640.0 ± 0%     640.0 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                        ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                              │            allocs/op             │ allocs/op   vs base                │
ReviewQueue_ConcurrentReads-4                       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
ReviewQueue_Add-4                                   4.000 ± 0%     4.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                        ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/session/scrollback
                                      │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt          │
                                      │              sec/op              │   sec/op     vs base              │
CircularBuffer_ConcurrentReadWrite-4                         3.475µ ± 1%   3.450µ ± 1%       ~ (p=0.082 n=8)
CircularBuffer_BurstAppend-4                                 103.0µ ± 0%   103.0µ ± 2%       ~ (p=0.382 n=8)
CircularBuffer_GetLastN_LargeBuffer-4                        17.42µ ± 4%   17.10µ ± 3%       ~ (p=0.138 n=8)
CircularBuffer_GetRange_Sequential-4                        10.324µ ± 2%   9.335µ ± 2%  -9.58% (p=0.000 n=8)
CircularBufferAppend-4                                       99.60n ± 1%   97.42n ± 0%  -2.19% (p=0.000 n=8)
CircularBufferGetLastN-4                                     1.936µ ± 1%   1.751µ ± 2%  -9.56% (p=0.000 n=8)
CircularBufferConcurrentAppend-4                             130.9n ± 1%   128.9n ± 0%  -1.60% (p=0.000 n=8)
geomean                                                      2.875µ        2.769µ       -3.71%

                                      │ benchmarks/go/tier1-baseline.txt │           tier1-bench.txt            │
                                      │               B/op               │     B/op      vs base                │
CircularBuffer_ConcurrentReadWrite-4                        6.062Ki ± 0%   6.062Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_BurstAppend-4                                62.50Ki ± 0%   62.50Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_GetLastN_LargeBuffer-4                       56.00Ki ± 0%   56.00Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_GetRange_Sequential-4                        28.00Ki ± 0%   28.00Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferAppend-4                                        24.00 ± 0%     24.00 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferGetLastN-4                                    6.000Ki ± 0%   6.000Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferConcurrentAppend-4                              32.00 ± 0%     32.00 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                     3.077Ki        3.077Ki       +0.00%
¹ all samples are equal

                                      │ benchmarks/go/tier1-baseline.txt │           tier1-bench.txt           │
                                      │            allocs/op             │  allocs/op   vs base                │
CircularBuffer_ConcurrentReadWrite-4                          2.000 ± 0%    2.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_BurstAppend-4                                 1.000k ± 0%   1.000k ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_GetLastN_LargeBuffer-4                         1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_GetRange_Sequential-4                          1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferAppend-4                                        1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferGetLastN-4                                      1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferConcurrentAppend-4                              1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                       2.962         2.962       +0.00%
¹ all samples are equal

                             │ benchmarks/go/tier1-baseline.txt │        tier1-bench.txt        │
                             │               B/s                │     B/s       vs base         │
CircularBuffer_BurstAppend-4                       592.8Mi ± 0%   592.4Mi ± 2%  ~ (p=0.382 n=8)

pkg: github.com/tstapler/stapler-squad/session/tmux
                             │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt          │
                             │              sec/op              │   sec/op     vs base              │
StripANSICodes_PlainText-4                          6.869n ± 0%   6.874n ± 1%       ~ (p=0.169 n=8)
StripANSICodes_WithEscapes-4                        742.4n ± 0%   733.0n ± 0%  -1.26% (p=0.000 n=8)
IsBanner_PlainText-4                                468.8n ± 0%   468.5n ± 0%       ~ (p=0.818 n=8)
geomean                                             133.7n        133.2n       -0.42%

                             │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                             │               B/op               │    B/op     vs base                │
StripANSICodes_PlainText-4                         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
StripANSICodes_WithEscapes-4                       56.00 ± 0%     56.00 ± 0%       ~ (p=1.000 n=8) ¹
IsBanner_PlainText-4                               0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                       ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                             │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                             │            allocs/op             │ allocs/op   vs base                │
StripANSICodes_PlainText-4                         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
StripANSICodes_WithEscapes-4                       4.000 ± 0%     4.000 ± 0%       ~ (p=1.000 n=8) ¹
IsBanner_PlainText-4                               0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                       ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/session/tokens
                                   │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt          │
                                   │              sec/op              │   sec/op     vs base              │
TokenParser_ProcessUserEntry-4                            5.315m ± 1%   5.342m ± 0%  +0.51% (p=0.038 n=8)
DetectCommandsInText/NoSlash-4                            7.190n ± 1%   7.188n ± 0%       ~ (p=0.853 n=8)
DetectCommandsInText/WithCommand-4                        1.612µ ± 1%   1.618µ ± 1%       ~ (p=0.779 n=8)
geomean                                                   3.949µ        3.960µ       +0.27%

                                   │ benchmarks/go/tier1-baseline.txt │           tier1-bench.txt            │
                                   │               B/op               │     B/op      vs base                │
TokenParser_ProcessUserEntry-4                         11.02Mi ± 0%     11.02Mi ± 0%       ~ (p=0.363 n=8)
DetectCommandsInText/NoSlash-4                           0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
DetectCommandsInText/WithCommand-4                       433.0 ± 0%       433.0 ± 0%       ~ (p=0.467 n=8)
geomean                                                             ²                 +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                   │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                                   │            allocs/op             │ allocs/op   vs base                │
TokenParser_ProcessUserEntry-4                           34.00 ± 0%     34.00 ± 0%       ~ (p=1.000 n=8) ¹
DetectCommandsInText/NoSlash-4                           0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
DetectCommandsInText/WithCommand-4                       6.000 ± 0%     6.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                             ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/session/unfinished
                               │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt          │
                               │              sec/op              │   sec/op     vs base              │
DiffShortstat/GitVCSReader-4                          3.163m ± 1%   3.186m ± 1%  +0.73% (p=0.021 n=8)
DiffShortstat/GoGitVCSReader-4                        75.84n ± 0%   76.02n ± 0%       ~ (p=0.396 n=8)
DiffShortstatCached-4                                 77.03n ± 0%   76.53n ± 1%  -0.66% (p=0.020 n=8)
geomean                                               2.644µ        2.646µ       +0.10%

                               │ benchmarks/go/tier1-baseline.txt │           tier1-bench.txt            │
                               │               B/op               │     B/op      vs base                │
DiffShortstat/GitVCSReader-4                       56.57Ki ± 0%     56.57Ki ± 0%       ~ (p=0.523 n=8)
DiffShortstat/GoGitVCSReader-4                       0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
DiffShortstatCached-4                                0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                         ²                 +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                               │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                               │            allocs/op             │ allocs/op   vs base                │
DiffShortstat/GitVCSReader-4                         368.0 ± 0%     368.0 ± 0%       ~ (p=1.000 n=8)
DiffShortstat/GoGitVCSReader-4                       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
DiffShortstatCached-4                                0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                         ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Frontend Terminal Throughput

terminal-throughput-mean: 13 KB/s ▼ -4.1% (baseline: 14 KB/s)
terminal-throughput-p50: 15 KB/s ▼ -3.4% (baseline: 16 KB/s)

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

UX Analysis

Check Status Details
❌ Axe Core (WCAG 2.1 AA) failure 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 Jul 4, 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.

tstapler and others added 2 commits July 3, 2026 23:05
Fixes a CRITICAL path-resolution bug the reordering fix introduced (a
tilde-prefixed repo_path would write pre-spawn files to the wrong location
since the raw string bypassed NewInstance's tilde/abs resolution — extracted
a shared session.ResolveSessionPath helper used by both), a CRITICAL gap
where AttachSessionToItem's entItem omitted PlanArtifactsPath/PlanApproved/
SkipPlanning so its plan-artifacts reminder could never render, and a MAJOR
race in UpdateFeatureFlag's rollback logic (added a per-service mutex so
concurrent toggles of the same flag can't stomp each other's disk state).

Also: reordered AttachSessionToItem to load prior sessions before creating
its own ItemSession (matches SpawnSessionFromItem, no longer correct only by
filter coincidence); wrapped rollback-failure errors so callers see when disk
state may be inconsistent; extracted PLUGIN_SCHEMAS into its own module so a
test can prove the source-creation form is genuinely schema-driven instead of
coincidentally-identical; added a concurrency-boundedness assertion to the
GitHub PRs CI-label fetch test; added regression coverage for the
write-before-spawn ordering fix, EnsureDirectorySessionPath, and
WriteBacklogContextFile's plan-artifacts/prior-sessions threading — all gaps
a 5-agent parallel review + adversarial skeptic pass identified as
untested. Filed one pre-existing, non-blocking DB index gap as a follow-up
note rather than fixing out-of-scope schema in this PR.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- EnsureDirectorySessionPath: distinguish IsNotExist (create+git-init) from
  other stat errors (now returned, not silently swallowed as a no-op)
- BacklogSourcesSettings: never send a leftover token for a plugin that
  doesn't require one; clear the token field on plugin switch
- useBacklogSourcesService: coerce truncated to Boolean() so a falsy/
  undefined wire value can't defeat the SyncHistoryResult contract

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

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

✅ Registry Validation

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

Building backend scanner...
Scanning backend features...
Wrote 116 feature files to /tmp/tmp.1xKoXJ7Ogt/backend
Wrote 14 feature files to /tmp/tmp.1xKoXJ7Ogt/backend
Wrote 24 feature files to /tmp/tmp.1xKoXJ7Ogt/backend
Wrote 6 feature files to /tmp/tmp.1xKoXJ7Ogt/backend
Wrote 10 feature files to /tmp/tmp.1xKoXJ7Ogt/backend

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

✅ Registry validation passed. Divergence: 0.0%

Test Coverage: 4/158 features have testIds (2.5%)

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

- server/services/backlog_service_test.go: use session.Active instead of
  the deprecated session.Running alias (staticcheck SA1019)
- session/backlog_plugin_github_test.go: use a tagged switch on
  r.URL.Path instead of switch{case r.URL.Path == ...} (staticcheck QF1002)

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

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

✅ Registry Validation

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

Building backend scanner...
Scanning backend features...
Wrote 116 feature files to /tmp/tmp.s1VvBtd9kz/backend
Wrote 14 feature files to /tmp/tmp.s1VvBtd9kz/backend
Wrote 24 feature files to /tmp/tmp.s1VvBtd9kz/backend
Wrote 6 feature files to /tmp/tmp.s1VvBtd9kz/backend
Wrote 10 feature files to /tmp/tmp.s1VvBtd9kz/backend

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

✅ Registry validation passed. Divergence: 0.0%

Test Coverage: 4/158 features have testIds (2.5%)

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

The test's fake Instance used Status: session.Active, which
FromInstanceData/LoadInstances treats as a live session requiring cold
process-manager restore (real tmux + claude process). That restore
succeeds (slowly) in a local dev environment but times out in CI (no
claude binary, sandboxed tmux), causing the instance to be silently
skipped and the expected file never written. Status: session.Paused
matches the same pattern already used elsewhere in this test suite and
needs no live process, since AttachSessionToItem only matches on
UUID+Path.

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

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

✅ Registry Validation

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

Building backend scanner...
Scanning backend features...
Wrote 116 feature files to /tmp/tmp.Bdn4sR5f9Y/backend
Wrote 14 feature files to /tmp/tmp.Bdn4sR5f9Y/backend
Wrote 24 feature files to /tmp/tmp.Bdn4sR5f9Y/backend
Wrote 6 feature files to /tmp/tmp.Bdn4sR5f9Y/backend
Wrote 10 feature files to /tmp/tmp.Bdn4sR5f9Y/backend

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

✅ Registry validation passed. Divergence: 0.0%

Test Coverage: 4/158 features have testIds (2.5%)

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

@tstapler
tstapler merged commit 7791548 into main Jul 4, 2026
22 of 23 checks passed
@tstapler
tstapler deleted the fix-backlog-architecture-review-followups branch July 4, 2026 16:27
TylerStaplerAtFanatics added a commit that referenced this pull request Jul 10, 2026
* refactor(session-types): unify SessionType, promote one_off to proto enum, split config

Eliminates three sources of type duplication:

1. config/types.go + config/executor.go extracted from the 1031-line config/config.go
   (SRP fix — config.go now contains only factory functions and the Config struct)

2. session.SessionType is now a Go type alias for config.SessionType, removing the
   duplicate type that required aliasSessionTypeToSessionType no-op conversions

3. bool one_off = 14 promoted to SESSION_TYPE_ONE_OFF = 5 in the SessionType proto enum;
   field 14 is reserved for wire compatibility. All call sites updated: backend handler,
   workflow scheduler, alias defaults service, and all frontend contexts/hooks/tests.

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

* fix(headless): use Setsid instead of Noctty for headless runner subprocess

WithNoControllingTerminal() sets SysProcAttr.Noctty=true on Linux, which
calls ioctl(0, TIOCNOTTY) in the child after fork. This returns ENOTTY when
the parent process has no controlling terminal — the case when stapler-squad
runs as a systemd service — causing every headless triage call to fail with
"fork/exec .../claude: inappropriate ioctl for device" (exit code 1).

Replace WithNoControllingTerminal() with WithNewSession() in ProcessRunner.Run.
Setsid creates a new process session (implying no controlling terminal) without
invoking TIOCNOTTY, so it works regardless of whether the parent has a TTY.

Also corrects the misleading comment in managed_process_linux.go that claimed
Noctty was safe without a controlling terminal.

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

* feat(omnibar): replace Create shortcut hint with clickable Create Session button

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

* feat(alias): add name_prefix field + fix session name oscillation

- Add `name_prefix` to AliasConfig (Go), AliasProto (proto field 12),
  and AliasEntry (TypeScript) — wired through the full stack
- In the detection effect, skip the generic suggestedName update for
  aliases; the alias block now derives the session name as
  namePrefix + typedLabel, falling back to namePrefix alone or the
  alias name — eliminates the oscillation between alias name and
  prefix+label on each keystroke
- AliasesManager settings form now has a Name prefix field with a live
  preview hint
- Create Session button in shortcuts bar uses compact styling on desktop
  and expands to touch-friendly size on coarse-pointer (mobile) devices

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

* fix(detection): detect dynamic workflows + expand turn-marker to ✦

- Add "dynamic workflow" alternate to waiting_for_background_agent pattern
  so "✻ Waiting for N dynamic workflow(s) to finish" → StatusWaitingForAgent
- Expand [✻◉] → [✻◉✦] in verb_duration_completion and
  waiting_for_background_agent to cover ✦ (U+2726, Claude Code primary spinner)
- Add test cases for all three bullet variants on both waiting and completion lines

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

* fix(review-queue): show INPUT_REQUIRED items + UX improvements

- Fix invisible INPUT_REQUIRED/APPROVAL_PENDING items: deriveWorkingState
  maps these to PROCESSING, which was being filtered out; now always
  passes items through when their reason requires user action
- Fix workingCount to exclude INPUT_REQUIRED/APPROVAL_PENDING from the
  "working" tally (they need attention, not patience)
- Fix summaryCount grammar ("input neededs", "task completes", "timed outs")
  by replacing tuple pluralization with per-reason formatter functions
- Fix filter empty state: show "no items match" when a filter is active,
  not the generic "all done" message
- Move auto-advance checkbox into the panel title row (was orphaned above
  the card in page.tsx toolbar div)
- Hide floating help button on mobile (keyboard shortcuts are irrelevant
  on touch devices)
- Increase filter button / toggle touch targets to 44px on mobile
- Downgrade oldest-item callout from alarming orange to neutral muted style
- Show filter toggle whenever any items exist (not only when server
  totalItems > 0)

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

* fix(alias): default session type + name oscillation

- Fix session type not applying for aliases configured as
  "Default (directory)": that option stores SessionType.UNSPECIFIED,
  which the detection effect was explicitly skipping — form stayed at
  the initial "new_worktree" value instead. Now maps UNSPECIFIED → "directory".
- Fix session name oscillating every other keystroke: the generic
  suggestedName block was running for InputType.Alias results and
  resetting lastSuggestedNameRef to the alias slug (e.g. "pw"),
  causing the alias-specific name block to fail its staleness check
  and alternate on each input event. Fixed by skipping the generic
  block for Alias inputs entirely — the alias block below handles naming.

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

* chore(bench): update e2e latency baseline [skip ci]

* chore(bench): update go tier1 baseline [skip ci]

* chore(bench): update frontend throughput baseline [skip ci]

* chore(demos): update E2E feature GIFs [skip ci]

* chore(sdd): planning artifacts for review-queue-jump-fix

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

* fix(review-queue): suppress auto-advance on session status transitions

The "deleted externally" effect in ReviewQueueContent used reviewQueueItems
(the filtered visible list) to check if the selected session still existed.
When a session transitioned to ACTIVE/PROCESSING, it was filtered from the
visible list but remained in the Redux store — the effect incorrectly fired
handleAutoAdvance(id, true), jumping to the next queue item immediately after
the user opened a session and clicked into the terminal.

Fix: use allQueueItems from useReviewQueueContext().items (the unfiltered
Redux store) as the existence oracle. A session filtered from the visible queue
due to status transition stays in the store and no longer triggers auto-advance.
Genuine removals (removeItem Redux events) still fire auto-advance correctly.

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

* fix(sessions): prevent Claude process orphaning after server restart

Three-part fix for tmux session / Claude process accumulation:

**Fix 1 — DeleteSession fallback (session_service.go)**
When FindLiveInstance returns nil (e.g. server restarted since the session
was created, so the in-memory poller is empty), fall back to
KillTmuxSessionByTitle which kills by the deterministic tmux session name.
Previously the DB record was deleted but the Claude process kept running
indefinitely.

**Fix 2 — Startup orphan sweep (session/orphan_sweep.go)**
ReconcileOrphanedTmuxSessions runs as Step 6d of BuildRuntimeDeps, after
the re-adoption passes (6/6b) that hot-attach DB sessions to their live
tmux panes. It enumerates all staplersquad_* tmux sessions, reads the
STAPLER_SESSION_UUID env var from each, and kills any whose UUID (or
sanitized title) has no match in the current workspace DB. The keepalive
sentinel is always preserved.

**Fix 3 — MCPServerURL backfill (session_service.go)**
loadInstancesWithWiring now backfills inst.MCPServerURL from the server's
configured URL for sessions created before MCP integration was wired up.
Without this, buildLaunchCommand omits --mcp-config entirely and Claude
restarts without a session UUID, making it impossible to identify from the
process list or MCP request headers.

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

* chore(bench): update e2e latency baseline [skip ci]

* chore(bench): update go tier1 baseline [skip ci]

* chore(bench): update frontend throughput baseline [skip ci]

* fix(lint): return empty map instead of nil in GetAllInstanceArtifacts

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

* chore(bench): update e2e latency baseline [skip ci]

* chore(bench): update go tier1 baseline [skip ci]

* chore(bench): update frontend throughput baseline [skip ci]

* fix(backlog): harden triage parser and add repoPath UI gate

ParseHeadlessTriageResult now uses brace-scan (strings.Index/LastIndex)
to tolerate natural-language preamble before the JSON block, fixing
silent parse failures on multi-step triage runs. The "Trigger Triage"
button in BacklogItemDetail and BacklogItemCard is now disabled with a
tooltip when repoPath is not set, preventing the confusing
CodeFailedPrecondition server error.

Adds 3 new unit tests for the parser and a Playwright e2e gate test
that creates an item without repoPath and asserts the button is
disabled.

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

* chore(demos): update E2E feature GIFs [skip ci]

* feat(harness): headless triage test harness + alias kebab-case fix

Adds a build-tagged Go harness (go:build harness) that exercises the
backlog triage feature end-to-end via the ConnectRPC HTTP layer with no
browser or UI. Four sub-tests cover distinct phases runnable individually:
Gate (repoPath precondition), TriggerAndPoll (async completion), ParserRobust
(preamble tolerance), and FullFlow (full user journey). Makefile targets
added for each phase.

Also converts alias namePrefix label to kebab-case lowercase
(spaces/underscores → hyphens) before concatenating with the prefix, so
"@SSQ My New Feature" produces "ssq-my-new-feature" instead of
"ssq-My New Feature". Two new tests added to Omnibar.alias.test.tsx.

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

* chore(sdd): planning artifacts for nav-redesign

Navigation redesign: group 16+ flat nav items into 4 sections (Work,
Automation, Insights, Settings & Tools), restore mobile access for 8
currently-hidden routes, and consolidate Settings/Config Files/Features.

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

* feat(nav): group navigation into 4 sections, restore mobile access

Reorganise the 15 nav pages into Work / Automation / Insights / Settings
groups rendered in both DrawerNav (desktop sidebar) and BottomNav More
sheet (mobile).  All 8 routes that were hidden from mobile (Settings,
Insights, Logs, Errors, Help, Escape Analytics, Files, Workflows/Rules)
are now reachable on every screen size.  Removes the redundant Config
Files and Features top-level entries; fixes a DrawerNav bug where items
were shown regardless of feature-flag state.

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

* chore(bench): update e2e latency baseline [skip ci]

* chore(bench): update go tier1 baseline [skip ci]

* chore(bench): update frontend throughput baseline [skip ci]

* chore(demos): update E2E feature GIFs [skip ci]

* chore: commit in-progress work from previous sessions

Includes executor fixes (WithProcessDir support, Linux setsid/Setpgid
EPERM fix), backlog triage harness test expansions, rate-limit
integration test, Makefile test-triage-real target, and planning
artifacts for backlog-triage-e2e-hardening and
put-backlog-behind-a-feature-flag-by-default.

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

* feat: support Antigravity CLI hooks.json format in ssq-hooks

* fix(pane): restore session peek modal integration in pane picker

* feat(files): wire up the premium LocalFileBrowser component to the files page

* chore: commit in-progress work from previous sessions

- ssq-hooks: Antigravity CommandLine/Cwd normalization, workspace-aware
  DB path resolution from cwd, WorkspacePaths fallback
- session service: ForkSession fully wired (callbacks, hook config,
  controller, driver, autonomous mode); ResumeHibernated wires review
  queue poller and autonomous driver
- ent schema: autonomous_mode bool field + generated ORM files
- instance_hibernate: start controller + session driver on resume
- omnibar: initialTitle prop pre-populates session name; OmnibarContext
  threads title through openOmnibar(); page.tsx passes ?title param
- LocalFileBrowser: CSS and component updates
- scripts: find-orphaned-features.py, find-unmerged-commits.py

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

* feat(omnibar): replace Create shortcut hint with clickable Create Session button

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

* feat(settings): add UpsertAlias and DeleteAlias RPCs with AliasesManager UI

Implements full CRUD for alias session presets in Settings > General, removing
the need to manually edit config.json. Adds UpsertAlias/DeleteAlias ConnectRPC
handlers (case-insensitive name matching, slice-scan upsert, validation via
aliasNameRE) and a React AliasesManager component with inline 3-second delete
confirmation, env-var editor, tag management, and ARIA accessibility.

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

* fix(registry): add alias RPCs to scanner methodToID map

UpsertAlias, DeleteAlias, ListAliases were missing from the methodToID
map, causing the scanner to use fallback raw-name IDs (UpsertAlias,
DeleteAlias, ListAliases) instead of canonical kebab-case IDs
(alias:upsert, alias:delete, alias:list). This caused Registry
Validation CI to fail with 3.36% divergence.

Removes the duplicate fallback JSON files from the registry root that
were generated under the old behavior.

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

* feat(analytics): program detail panel with subcommand drill-down

Add DB-backed time-windowed analytics queries and an inline program
detail panel so operators can see exactly which sub-operations are
causing escalations before writing a rule.

Backend (Go):
- Add compound index on (command_program, created_at) to ent schema
- Replace full-table-scan ListAnalytics with time-windowed
  ListAnalyticsSince (WHERE created_at >= ?) — AC-1, AC-2
- Add GetSubcommandBreakdown aggregation query using ent GroupBy — AC-4
- Add ListRecentCommandsByProgram returning last N command previews — AC-5
- Add GetSubcommandTrend returning per-day counts — AC-6
- Add GetProgramAnalytics ConnectRPC method returning SubcommandBreakdown,
  ExampleCommands, RuleCoverage, DailyTrend — AC-7

Frontend (React/TypeScript):
- New ProgramDetailPanel component with subcommand frequency table
  (count, %, decision breakdown), example commands, rule coverage
  summary, trend sparklines, and "Add rule →" links — AC-8 through AC-13
- New useProgramAnalytics hook with AbortController cleanup
- ApprovalAnalyticsPanel: clicking program row opens inline detail panel
- ApprovalRulesPanel: fix panel crush in flex container (flexShrink: 0),
  use window.location.search in useEffect for URL param pre-fill
  (avoids useSearchParams/Suspense issues in Next.js static export)

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

* feat(backlog): gate backlog behind feature flag on all layers

- Frontend layout guard: backlog/layout.tsx redirects to / when flag off
- Backend interceptor: FeatureFlagInterceptor wired to BacklogService only
- E2E tests: beforeAll/afterAll enable+restore the backlog flag

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

* fix: address copilot review comments on analytics drill-down

- Fix 1: exclude NULL command_subcategory rows in GetSubcommandBreakdown
  to avoid sql.ScanSlice scan errors on nullable GROUP BY columns
- Fix 2: replace strings.Fields tokenizer in coveredSubcommands() with
  regexp.Compile + synthetic "<program> <subcommand>" matching so
  regex-style patterns (e.g. \bgit\b.*\bpush\b) work correctly
- Fix 3: add TestGetProgramAnalytics_ReturnsExpectedFields unit test
  covering window_days=7 and non-nil response fields
- Fix 4: add escapeRegex() helper in ApprovalRulesPanel and use it when
  prefilling commandPattern to avoid metacharacter injection; switch word
  boundaries from \b to (?:^|\s)/(?:\s|$) for hyphenated program names
- Fix 5: add e.stopPropagation() on Suggest Rule button and "add manually"
  link so clicking them does not toggle the parent <tr> drill-down row
- Fix 6: add tabIndex, role=button, aria-expanded, aria-label, and
  onKeyDown (Enter/Space) to the clickable <tr> for keyboard accessibility
- Fix 7: call setData(null) before setIsLoading(false) in error path of
  useProgramAnalytics to clear stale data on refresh failure
- Fix 8: render per-program daily trend sparkline in ProgramDetailPanel;
  note that trend data is per-program not per-subcommand (backend limit)
- Fix 9: thread caller context through LoadProgramWindow,
  GetSubcommandBreakdown, and ListRecentCommands instead of context.Background()

* fix(review-queue): resolve UUID→Title before Remove so approved/deleted sessions leave the queue

Queue items are keyed by inst.Title but approval-response and session-deleted
events arrive with UUID. resolveQueueKey() looks up the instance via FindInstance
(which handles both UUID and Title) and returns Title, falling back to the raw
value if the instance is no longer loaded.

Also removes duplicate SubcommandDecisionCount declaration in repository.go
introduced by the analytics cherry-pick merge.

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

* chore(bench): update go tier1 baseline [skip ci]

* fix(web): resolve post-merge TypeScript and lint errors

- ApprovalAnalyticsPanel: add missing imports (useGenerateRule, SuggestionSource,
  addRuleManualLink) and state (activeRowKey, generateLoading, isGenerating)
  for the 'Suggest Rule' button in the programs coverage-gap table
- ApprovalRulesPanel: restore missing RuleFormState interface, emptyForm constant,
  useEffect/useRef imports, and URL-param pre-fill state aliases that were
  dropped during the merge resolution
- feature_flag_interceptor_test: fix nilnil lint violation by returning a
  non-nil connect.Response instead of (nil, nil)

* fix(web): resolve all 102 pre-existing test failures (2811/2811 pass)

jest.setup.js: add global stubs for window.matchMedia, next/navigation,
@xterm/addon-serialize, useAvailablePrograms, and useSlashCommands so
jsdom-based tests don't fail at module load time.

Source fixes:
- ApprovalRulesPanel: replace inline form with dialog modal; add
  add-rule-button testid, Escape handler, second useGenerateRule instance
  for cmd-sample generation, URL-param prefill via RuleBuilderPrefill
- ApprovalAnalyticsPanel: add Suggest Rule buttons + inline suggestion cards
  to the uncovered-tools table (data-testid: suggest-rule-tool-{toolName})
- RuleBuilderForm: add testids for all form fields, advanced-regex-separator,
  generate-from-command-details, command-sample sections; add cmdSuggestions
  and cmdClear props
- OmnibarCreationPanel: update hint to 'typed into the session terminal'
- XtermTerminal: optional-chain terminal.element, attachCustomKeyEventHandler,
  onScroll, onWriteParsed, and Disposable.dispose() for mock environments
- SubStatusChip: guard switch on undefined/null subStatus
- NotificationContext/ThemeContext: return no-op fallback outside Provider
- useShells: guard createAuthInterceptor in test environments
- SessionActionsOverflow: call onClearConversationState directly (no dialog)
- OmnibarResultList/QuickOpenPalette: guard scrollIntoView calls
- useAvailablePrograms: guard fetch in jest.fn() environments
- SessionCard: fix truncateGoal max to produce correct char count
- ruleBuilderPrefill: add commandPattern, initialName, isAiGenerated fields

* chore: update feature registry after merge

make registry-generate removes stale get-program-analytics entry that
was superseded during the fork→upstream sync.

---------

Co-authored-by: Tyler Stapler <tystapler@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.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