Skip to content

feat(backlog): two-way GitHub issue sync — provenance, forward/backward status sync, loop prevention - #336

Merged
tstapler merged 18 commits into
mainfrom
backlog/stapler-squad-backlog-github-two-way-sync
Aug 4, 2026
Merged

feat(backlog): two-way GitHub issue sync — provenance, forward/backward status sync, loop prevention#336
tstapler merged 18 commits into
mainfrom
backlog/stapler-squad-backlog-github-two-way-sync

Conversation

@tstapler

@tstapler tstapler commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Summary

The GitHub Issues backlog source was pull-only and lossy: Fetch only queried
open issues, MapToBacklogItem dropped the issue's URL/labels on the floor,
and SyncOne never wrote status back in either direction — so a backlog item
could sit around long after its source issue closed, and shipping a backlog
item never closed the linked issue. This PR builds a real two-way
integration, gated entirely behind opt-in per-source settings.

Closes backlog item be676dab-6798-410e-a3ec-708fc03758e0.

What Changed

  • Provenance: Fetch now queries state=all (observes closed/reopened
    issues); ExternalURL/Labels persist end-to-end (ent → repository →
    proto) and render as a card badge + detail "Source" section.
  • Forward sync (opt-in, default off): a done transition closes the
    linked GitHub issue via a new EventBus subscriber, optionally applies a
    configured label, and posts one explanatory bot comment.
  • Backward sync (opt-in, default off): SyncOne applies the issue's
    closed/open state and label changes onto the backlog item, respecting the
    existing UserModifiedFields local-wins guard — closed issues in pre-work
    statuses (idea/refining/ready/queued) auto-archive; issues closed
    while an item is mid-flight are left alone (see ADR-002).
  • Loop prevention: a GitHubSyncedIssueUpdatedAt watermark (GitHub's own
    timestamp, not local wall-clock — see ADR-003) prevents forward/backward
    sync from re-processing each other's writes, including on a failed
    transition (retried next tick, not silently dropped).
  • Blast-radius safety: enabling backward sync for the first time on a
    source runs a PreviewBackwardSyncImpact check and shows a confirm dialog
    with the exact item count before anything changes — added after triad
    review flagged silent bulk-archiving as a launch blocker.
  • Settings UI: both sync directions are toggled per source in
    Settings > Backlog Sources, with a persistent row-level warning for
    non-transient sync failures (auth/401, from either the read or write side).
  • Fixed a dormant local-wins gap: UpdateBacklogItem's RPC handler now
    actually populates UserModifiedFields (via value-diff, not a naive
    presence check) — previously the local-wins guard existed in code but was
    unreachable in production for any item, GitHub-linked or not.

Test plan

  • go build ./... — clean
  • go test ./session/... ./server/... — all pass, 0 failures
  • go test -race scoped to every touched package (forward sync, backward
    sync, plugin, preview RPC, UserModifiedFields) — clean, no races
  • make lint — 0 issues
  • make build — full production build succeeds
  • cd web-app && npx tsc --noEmit — clean
  • cd web-app && npx jest --testPathPatterns="Backlog" — 3720/3727
    passing (2 unrelated pre-existing failures confirmed via empty diff
    against the pre-feature commit)
  • Dedicated security review (token handling, outbound GitHub calls,
    inbound GitHub data) — no HIGH/MEDIUM findings
  • sdd:6-verify (idiom + architecture + correctness gates) — PASS after
    one fix-loop iteration resolving 3 real bugs found by review (WCAG AA
    contrast failure, a keyboard-nav bug, a stale-closure double-click bug)
  • All 11 acceptance criteria independently verified against the diff by
    a dedicated spec-compliance sweep
  • Automated backlog review verdict: PASS

tstapler and others added 14 commits August 3, 2026 14:23
…og-github-two-way-sync

- validation.md: 52 test cases (Go unit/integration/migration, Jest, Playwright)
  covering all 11 acceptance criteria.
- pre-mortem.md: 3 P1 failure modes identified and resolved directly in plan.md
  (watermark clock-skew in CloseIssue's signature, UserModifiedFields
  presence-check vs value-diff, forward-sync failures wired into the
  row-level-warning store).
- plan.md: fixed a real correctness bug found by cross-artifact consistency
  review (Labels backward-sync was missing its BackwardSyncEnabled gate,
  unlike the status blocks in Epic 2.1/2.2), and added Epic 4.4
  (PreviewBackwardSyncImpact + confirm dialog) to close a Product Triad
  Review UX blocker: enabling backward sync could previously bulk-archive
  already-imported items with no preview or confirmation.

Readiness gate: PASS. Triad review: READY TO BUILD (verified via a fresh,
independent UX re-check after the blocker fix).
…owed, SyncLoop.workflowEngine

Adds the audit-trail marker and read-only guard-evaluation helper the
backward sync (GitHub -> backlog) work needs, without creating an
import cycle (session cannot import server/services):

- TriggeredByGitHubSync = "github_sync" constant alongside
  TriggeredByUser/TriggeredBySystem (session/backlog.go).
- GuardedTransitionAllowed(engine, item, to) evaluates CanTransition +
  ValidateGates without executing the transition — the read-only
  counterpart to transitionWithGuard for callers in package session
  that can't import server/services (session/workflow_engine.go).
- SyncLoop gains a workflowEngine field, defaulted to
  NewDefaultWorkflowEngine() in both constructors so existing
  NewSyncLoop(...)/NewSyncLoopWithKeyProvider(...) call sites compile
  unchanged (session/backlog_sync.go). Consumed by later Phase 2/3
  work, not by this change.

Ref: project_plans/backlog-github-two-way-sync/implementation/plan.md
Epic 0.4.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NYt425C3izWQRqKgYLoj74
Add ForwardSyncEnabled, BackwardSyncEnabled, ForwardSyncCloseLabel as
first-class ItemSource fields end-to-end (ent schema -> generated code
-> repository -> UpdateItemSource RPC handler -> proto), mirroring the
existing Enabled field's shape. Proto field numbers verified free
against the live .proto before assigning (ItemSource highest was 8,
UpdateItemSourceRequest highest was 4 - matches plan.md's projected
9/10/11 and 5/6/7).

Also fixes a latent bug found while adding the UpdateItemSource
not-found test: EntRepository.UpdateItemSource wraps ent's
*ent.NotFoundError as session.ErrNotFound before returning, so the
handler's `ent.IsNotFound(err)` check never matched and unknown source
IDs fell through to CodeInternal instead of CodeNotFound. Scoped the
fix to UpdateItemSource only (per assigned scope).

Ref: project_plans/backlog-github-two-way-sync/implementation/plan.md
Epic 0.5.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NYt425C3izWQRqKgYLoj74
…bSyncedIssueUpdatedAt watermark (Epics 0.1/0.2/0.6)

Lands the ent schema, repository, and GitHub-plugin plumbing that both
sync directions depend on:

- Epic 0.1: `labels []string` + `external_url string` (backlog-github-issue-link
  had not landed external_url yet, so this adds it defensively per Task 0.1.1a)
  added to the BacklogItem ent schema, threaded through BacklogItemData/
  BacklogItemUpdate and the ent create/update/read mapping, and populated by
  GitHubIssuesPlugin.MapToBacklogItem instead of being dropped.
- Epic 0.2: GitHubIssuesPlugin.Fetch now queries state=all instead of
  state=open so closed/reopened issues are observed; ExternalItem gains
  State and IssueUpdatedAt (parsed from the issue's updated_at, reusing the
  same value already used to compute the Fetch cursor).
- Epic 0.6: GitHubSyncedIssueUpdatedAt *time.Time loop-prevention watermark
  added to the ent schema/BacklogItemData/BacklogItemUpdate, mirroring
  PrFeedbackAddressedAt's exact shape (Set.../Clear... pair).

Single `go generate ./session/ent` pass covers all new backlog_item fields
across the three epics, per plan.md's Phase 0 instruction.

Note: DecryptConfigToken (Epic 0.6, Story 0.6.2) was already renamed and
committed as an incidental part of an earlier concurrent commit
(58ded38) in this shared worktree — no separate change needed here.
TestDecryptConfigToken is kept as a thin forwarding wrapper rather than
deleted, since server/services/backlog_service_encryption_test.go (outside
this task's file-ownership scope) still calls it directly.

Ref: project_plans/backlog-github-two-way-sync/implementation/plan.md
Epics 0.1, 0.2, 0.6.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NYt425C3izWQRqKgYLoj74
…ic 0.3)

Stories 0.3.1/0.3.2 of backlog-github-two-way-sync: export
ParseUserModifiedFields/ContainsModifiedField and add MergeUserModifiedFields
in package session; thread UserModifiedFields through BacklogItemUpdate and
UpdateBacklogItem (repo + ent layers); populate it in the UpdateBacklogItem
RPC handler via a value-diff against the existing item (not a presence
check), per the pre-mortem P1 #2 correction — the only frontend edit form
always resubmits Title verbatim, so a presence-only check would falsely mark
it user-modified on nearly every edit.

This makes the pre-existing local-wins gate in SyncOne reachable in
production for the first time.
Epic 0.1's ent/repository layer already persisted ExternalURL/Labels, but
nothing surfaced them through BacklogItem's proto message or the
list-view BacklogItemSummary struct — the frontend had no way to read
them. Adds external_url (30) and labels (31) to the proto, wires both
proto-conversion functions, and adds the fields to BacklogItemSummary
(populated directly from the ent entity, no new query).

Prerequisite for Epic 4.1/4.2 (card badge, detail Source section).
…k (Phase 2-3)

Implements Epics 2.1-2.4 (closed-issue -> archived status mapping per
ADR-002, reopened-issue log-only no-op, gated Labels backward sync,
ExternalURL/Labels backfill for pre-existing items) plus Phase 3's
GitHubSyncedIssueUpdatedAt watermark read-and-skip check in SyncOne
(ADR-003), all in the same gated-block style as the existing
title/description/priority local-wins blocks.

Includes the validation-pass correction from Task 2.3.1a: the Labels
backward-sync block gates on source.BackwardSyncEnabled (previously
missing from the plan draft), matching the closed/reopened status
blocks' existing gate.

Adds 15 new tests covering the ADR-002 decision table, the
BackwardSyncEnabled/UserModifiedFields gates (including their
deliberate asymmetry for ExternalURL vs Labels), and the two AC7
loop-prevention regressions (Risk A: done->done is structurally
impossible; Risk B: a manual reopen after forward-sync-close is not
re-closed by an exact-echo watermark comparison, while a genuinely
newer external change is still processed).
… (Epics 4.1-4.3)

Card badge (Epic 4.1) and detail-view Source section (Epic 4.2) show an
item's GitHub provenance (issue link + labels) when ExternalURL/Labels are
present, per ux.md's icon+identifier+link recommendation. lucide-react
1.14 ships no brand "Github" glyph, so CircleDot substitutes for it.

Settings (Epic 4.3) adds two role="switch" toggles per source ("Close
GitHub issues when I finish here" / "Reflect GitHub status back here"),
a close-label input, a both-directions loop-risk warning, and a
row-level warning for a non-transient (401/403/revoked) sync failure —
sourced from eagerly-fetched sync history so it's visible without
expanding it. The three new setForwardSyncEnabled/setBackwardSyncEnabled/
setForwardSyncCloseLabel hook functions (and the existing
setItemSourceEnabled) now round-trip the full current ItemSource through
UpdateItemSource, since its fields are unconditionally overwritten, not
partial-update.

Backward-sync-enable currently flips directly on click; Epic 4.4's
confirm-with-preview gate (depends on Epic 2.1's
determineBackwardSyncTarget, in flight concurrently) lands in a later
wave — noted in code, not implemented here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NYt425C3izWQRqKgYLoj74
…e 1, Epics 1.1-1.3)

Implements AC3: transitioning a backlog item to done closes its linked
GitHub issue (merging in a configured close label) and leaves an
explanatory bot comment, gated by ItemSource.ForwardSyncEnabled.

- Epic 1.1: GitHubIssuesPlugin.CloseIssue/PostIssueComment
  (session/backlog_plugin_github.go). CloseIssue returns the PATCH
  response's own updated_at (not wall-clock time), per ADR-003's
  loop-prevention watermark design (pre-mortem P1 #1).
- Epic 1.2/1.3: externalIssueCloser interface + a new EventBus
  subscriber, StartBacklogGitHubForwardSyncSubscriber
  (server/services/backlog_github_forward_sync.go), wired in server.go
  alongside the other Start*Subscriber calls. On CloseIssue failure,
  records a queryable RecordSourceSyncFailure row instead of only
  logging (pre-mortem P1 #3; new EntRepository/Storage method,
  session/ent_repository_backlog.go + storage.go).

Plan deviations discovered while implementing:
- deps.SyncLoop is always nil (the live periodic SyncLoop is owned
  internally by session.BacklogController) and *session.SyncLoop has
  no exported registry accessor, so the subscriber takes the plugin
  registry and a SyncLoop as separate params, sourced from two new
  BacklogService accessors (Registry, SyncLoopForForwardSync) added
  in server/services/backlog_service_sync.go, mirroring TriggerSync's
  own inline SyncLoop construction — rather than session/backlog_sync.go,
  which a concurrent worker owns for Phase 2.
- EntRepository.TransitionBacklogItemStatus reloads the item via a
  plain BacklogItem.Get (no .WithSource()), so the EventBus payload's
  Item can have an empty SourceID even for a source-linked item.
  handleForwardSyncClose re-fetches via storage.GetBacklogItem (which
  does eager-load Source) instead of trusting the payload snapshot.

Deferred (explicitly non-blocking per plan.md pre-mortem P2 #5):
skipping the close+comment when the issue is already known closed —
BacklogItemData has no stored external-state field, so this would
need a schema change or an extra GitHub call; left as a follow-up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NYt425C3izWQRqKgYLoj74
…dialog (Epic 4.4)

Closes Unresolved Question #3 (Product Triad Review UX blocker): turning on
backward sync no longer silently bulk-archives already-imported items in the
same tick the toggle flips.

- New PreviewBackwardSyncImpact RPC (proto/session/v1/backlog.proto):
  server/services/backlog_sources_preview.go loads the source, decrypts its
  token, calls the plugin's Fetch once, and reuses determineBackwardSyncTarget
  (session/backlog_sync.go's new SyncLoop.PreviewBackwardSyncImpact) to count
  only items in idea/refining/ready/queued whose linked issue is closed.
- BacklogSourcesSettings' backward-sync toggle now calls the preview RPC on
  enable; itemCount 0 flips immediately, itemCount > 0 shows a new
  BackwardSyncConfirmDialog (informed-consent copy, focus trap, Escape-to-
  cancel, focus-return) before calling setBackwardSyncEnabled. Toggle shows a
  pending state during the preview call; a preview failure shows an inline
  error with no dialog and no toggle flip.
- tools/scanner/backend/proto_scanner.go: registered the new RPC's
  methodToID mapping so registry-generate produces the kebab-case feature id
  instead of falling back to the raw method name.

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

session/backlog_sync.go's closed-issue backward-sync block advanced
GitHubSyncedIssueUpdatedAt unconditionally, including when
TransitionBacklogItemStatus itself failed — a failed write would
permanently mark the item as "already reconciled" and never be retried
on a later sync tick. Gate the watermark advance on the transition
having actually succeeded (or deliberately skipped), matching this
codebase's established retry-on-next-tick convention (see
backlog_lifecycle.go's ReconcilePRPending precedent).

Also resolves the make lint silenttransition finding on this line —
the errored++ counter and next-tick retry are the existing notify
mechanisms, per the //nolint justification added.
Per .claude/rules/feature-registry.md — sweep found several stale
entries and one missing marker from the preceding waves:
- update-item.json/update-source.json: testIds were missing the new
  UserModifiedFields/sync-direction round-trip tests.
- backlog-item-card.json/backlog-item-detail.json: testIds were
  missing the new provenance-badge/SourceSection tests.
- settings-backlog-sources.json: testIds hadn't been touched since
  before Epic 4.3/4.4 landed (11 new tests added).
- SourceSection.tsx had no // +feature: marker despite this repo's own
  precedent for detail sub-sections (LifecycleSummary.tsx,
  SessionDiagnosticPanel.tsx) — added the marker + a new registry
  entry.
MUST FIX:
- WCAG AA contrast: provenanceBadge / subHeading / previewPendingLabel
  rendered textMuted on surfaceMuted, failing 4.5:1 in the dark theme
  (2.99:1) and clean theme (3.10:1). Switch to textSecondary, and bump
  clean theme's textSecondary token (4.02:1 -> 4.57:1 against
  surfaceMuted) since it was itself marginal.
- Keyboard nav: BacklogItemCard's onKeyDown fired on any bubbled
  Enter/Space, so Enter on the nested provenance-badge <a> both
  preventDefault()'d the anchor's navigation and opened the item
  detail. Guard on e.target === e.currentTarget.
- Stale-closure double-click: handleToggleEnabled/handleToggleForwardSync
  had no in-flight guard, unlike handleToggleBackwardSync's
  backwardSyncPreviewPendingId pattern, so a rapid double-click could
  send the same target value twice. Added matching per-source pending-id
  guards for both.

Cheap follow-ups:
- PreviewBackwardSyncImpact was missing TriggerSync's syncFeatureEnabled
  gate — added it, and switched to SyncLoopForForwardSync() instead of
  reimplementing its branch inline.
- Deleted TestDecryptConfigToken, a single-caller forwarding wrapper;
  the one caller now calls DecryptConfigToken directly.
- closeLabelDrafts never cleared after a successful commit, permanently
  pinning the input to the locally-typed value. Clear the draft entry
  once refresh() succeeds.
- The closed-issue backward-sync "no valid target" skip branch (item is
  in_progress/review/pr_pending) left advanceWatermark true even though
  nothing changed locally, which could permanently suppress a later
  legitimate auto-archive after a manual status revert. Mirrors the
  transition-failure branch's existing fix (0fa219f).

Added regression tests for the keyboard-nav guard, the double-click
guards (both toggles), the close-label reconciliation, and the
watermark fix (including an end-to-end two-tick reprocessing test).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HWAXmtpHQXXZTKmnvLSSWb
CRITICAL:
- UpdateItemSource guarded ForwardSyncCloseLabel on non-empty, so a user
  clearing the close-label input via blur got a 200 response but the label
  silently reappeared unchanged. Write it unconditionally like the sibling
  full-state-overwrite fields (Enabled, ForwardSyncEnabled,
  BackwardSyncEnabled).
- PreviewBackwardSyncImpact called plugin.Fetch's single page (50 issues,
  sorted by created desc), silently missing older closed issues on repos
  with >50 total issues — could report "0 items affected" when more exist,
  undermining Epic 4.4's entire purpose. Added GitHubIssuesPlugin.FetchAll
  (a PaginatedFetcher the preview path type-asserts for) which paginates up
  to maxPreviewFetchPages (20 pages / 1000 issues), and a
  possibly_incomplete response field + UI caveat when the cap is hit — went
  with pagination (approach a) since it stayed contained to
  Fetch/FetchAll/PreviewBackwardSyncImpact.

MAJOR:
- Batched PreviewBackwardSyncImpact's N+1 per-issue GetBacklogItemByExternalID
  loop into one GetBacklogItemsByExternalIDs query.
- Added regression tests for previously-untested guard paths: watermark
  persists when PostIssueComment fails after a successful CloseIssue; the
  GuardedTransitionAllowed-denied branch in SyncOne's closed-issue block;
  locally-created items (no SourceID/ExternalID) never trigger CloseIssue.

NIT:
- Fixed a stale e2e helper comment claiming the Epic 4.4 confirm-with-preview
  gate was a later wave — it ships in this PR; the fixture just has zero
  linked items so the dialog auto-skips.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HWAXmtpHQXXZTKmnvLSSWb
@tstapler
tstapler marked this pull request as ready for review August 4, 2026 17:14
Copilot AI lite review requested due to automatic review settings August 4, 2026 17:14

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.

Implements an opt-in, two-way GitHub Issues ↔ backlog integration with provenance (URL/labels), forward/backward status sync, and loop-prevention via a GitHub-side timestamp watermark.

Changes:

  • Persist and surface provenance (external_url, labels) end-to-end; render on card + detail “Source” section.
  • Add per-source sync-direction settings (forward close-on-done + optional close label; backward reflect-back) including a preview + confirm gate for first-time backward sync enable.
  • Add forward-sync EventBus subscriber, backward-sync logic in SyncOne, and supporting storage/proto/schema changes (watermark, sync failure recording, pagination preview).

Reviewed changes

Copilot reviewed 69 out of 71 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
web-app/src/styles/theme.css.ts Adjusts clean theme text contrast token to meet WCAG AA on muted surfaces.
web-app/src/lib/hooks/useBacklogSourcesService.ts Adds new per-source sync-direction fields and RPC helpers, plus backward-sync impact preview call.
web-app/src/lib/hooks/useBacklogService.ts Maps new backlog item provenance fields from proto (externalId, externalUrl, labels).
web-app/src/components/settings/BackwardSyncConfirmDialog.tsx New accessible confirm dialog with focus trap/restore for backward sync enable preview.
web-app/src/components/settings/BackwardSyncConfirmDialog.css.ts Styles for the new confirm dialog.
web-app/src/components/settings/BacklogSourcesSettings.tsx Adds sync-direction toggles, preview/confirm flow, close-label input, and row-level warnings.
web-app/src/components/settings/BacklogSourcesSettings.css.ts Adds styles for toggle pending state, sync-direction panel, warnings, and inline preview error.
web-app/src/components/backlog/detail/SourceSection.tsx New backlog item detail “Source” section showing external link + labels.
web-app/src/components/backlog/detail/SourceSection.test.tsx RTL tests for SourceSection rendering and collapse behavior.
web-app/src/components/backlog/detail/SourceSection.css.ts Styles for SourceSection and label badges.
web-app/src/components/backlog/BacklogItemDetail.tsx Wires SourceSection into detail view with expansion state.
web-app/src/components/backlog/BacklogItemDetail.test.tsx Tests for conditional rendering of Source section.
web-app/src/components/backlog/BacklogItemCard.tsx Adds provenance badge link + keyboard handling fix for nested focusables.
web-app/src/components/backlog/BacklogItemCard.test.tsx Adds tests for provenance badge rendering and event propagation/keyboard behavior.
web-app/src/components/backlog/BacklogItemCard.css.ts Styles for provenance badge with WCAG-conscious tokens.
tools/scanner/backend/proto_scanner.go Registers new RPC method ID for preview backward sync impact.
tests/e2e/pages/BacklogSourcesSettingsPage.ts Adds page-object helpers for forward/backward sync toggles.
tests/e2e/backlog-github-sync-settings.spec.ts Adds Playwright coverage for sync-direction settings persistence and loop-risk warning.
session/workflow_engine_test.go Adds unit test coverage for new GuardedTransitionAllowed helper.
session/workflow_engine.go Adds GuardedTransitionAllowed to evaluate transitions without executing them.
session/storage.go Adds GetItemSourceByID and RecordSourceSyncFailure helpers for forward sync and UI warnings.
session/repository.go Extends domain models for provenance, watermark, user-modified fields, and sync-direction settings.
session/ent_repository_backlog_test.go Adds tests for labels NULL-safety and GitHub watermark round-tripping.
session/ent_repository_backlog.go Persists new fields and adds batched lookup for preview (avoids N+1).
session/ent/schema/item_source.go Adds per-source sync-direction fields to schema.
session/ent/schema/backlog_item.go Adds provenance, labels, and watermark fields to schema.
session/ent/runtime.go Updates generated runtime defaults/indexes for new schema fields.
session/ent/migrate/schema.go Updates migration schema to include new columns and indices.
session/ent/itemsource_update.go Generated setters/sql save wiring for new ItemSource fields.
session/ent/itemsource_create.go Generated defaults/check/spec wiring for new ItemSource fields.
session/ent/itemsource/where.go Generated predicates for new ItemSource fields.
session/ent/itemsource/itemsource.go Generated column constants/orderers/defaults for new ItemSource fields.
session/ent/itemsource.go Generated scan/assign/string wiring for new ItemSource fields.
session/ent/backlogitem_update.go Generated setters/sql save wiring for new BacklogItem fields (labels, external_url, watermark).
session/ent/backlogitem_create.go Generated create spec/upsert wiring for new BacklogItem fields.
session/ent/backlogitem/where.go Generated predicates for new BacklogItem fields.
session/ent/backlogitem/backlogitem.go Generated column constants/orderers for new BacklogItem fields.
session/ent/backlogitem.go Generated scan/assign/string wiring for provenance/labels/watermark.
session/backlog_sync.go Adds backward sync impact preview, watermark handling, label/url sync, and exports token decrypt helper.
session/backlog_plugin_github_test.go Adds extensive tests for state=all fetch, pagination, close-issue semantics, comments, and watermark timestamps.
session/backlog_plugin_github.go Adds closed/open state parsing, FetchAll pagination, CloseIssue + PostIssueComment, labels/url mapping.
session/backlog_plugin.go Extends ExternalItem with state/timestamp and introduces PaginatedFetcher capability.
session/backlog_lifecycle_test.go Adds integration round-trip test for labels + external URL through storage.
session/backlog.go Adds TriggeredByGitHubSync for status event provenance.
server/services/backlog_sources_preview_test.go Adds test coverage for PreviewBackwardSyncImpact behavior and error handling.
server/services/backlog_sources_preview.go Implements PreviewBackwardSyncImpact RPC (read-only, gated, timeout-bounded).
server/services/backlog_service_test.go Adds tests for UserModifiedFields diffing and sync-direction settings round-trips/clears/not-found.
server/services/backlog_service_sync.go Exposes plugin registry + sync loop helper for forward-sync subscriber wiring.
server/services/backlog_service_lifecycle.go Implements value-diff user-modified fields merge; updates UpdateItemSource error mapping and new fields.
server/services/backlog_service_encryption_test.go Updates test to use exported DecryptConfigToken.
server/services/backlog_service.go Maps provenance fields to proto and maps new ItemSource fields to proto.
server/services/backlog_github_forward_sync_test.go Adds integration tests for forward sync close/comment/watermark/failure persistence/no-op guards.
server/services/backlog_github_forward_sync.go Adds EventBus subscriber to close issues on done transitions with loop-prevention watermark.
server/server.go Wires forward-sync subscriber at server startup.
proto/session/v1/backlog.proto Adds new fields/RPCs for provenance, sync-direction settings, and backward sync impact preview.
project_plans/backlog-github-two-way-sync/implementation/validation.md Documents validation plan and requirement→test mapping for the feature.
project_plans/backlog-github-two-way-sync/implementation/pre-mortem.md Documents pre-mortem risks and mitigations; reflects fixes included in PR.
docs/registry/features/frontend/ui/settings-backlog-sources.json Updates frontend feature registry with new tests and lastModified.
docs/registry/features/frontend/ui/backlog-item-detail.json Updates feature registry to include Source section tests.
docs/registry/features/frontend/ui/backlog-item-detail-source-section.json Registers the new SourceSection component and tests.
docs/registry/features/frontend/ui/backlog-item-card.json Updates feature registry to include provenance badge tests.
docs/registry/features/backend/backlog/update-source.json Marks update-source backend feature as tested with new test IDs.
docs/registry/features/backend/backlog/update-item.json Adds test IDs for UserModifiedFields diffing to backend registry.
docs/registry/features/backend/backlog/preview-backward-sync-impact.json Registers and marks new preview RPC as tested.
Files not reviewed (1)
  • gen/proto/go/session/v1/sessionv1connect/backlog.connect.go: Generated file
Suppressed comments (7)

session/backlog_sync.go:1

  • updated/skipped counters can double-count the same item: a backward-sync status transition can increment updated++, but anyField remains false so the code later also increments skipped++. This will make the recorded SourceSyncEvent aggregates misleading. Consider tracking an anyChange (or statusChanged/watermarkChanged) flag that’s set when a status transition or watermark write happens, and only increment skipped when no change of any kind occurred.
package session

session/backlog_sync.go:480

  • updated/skipped counters can double-count the same item: a backward-sync status transition can increment updated++, but anyField remains false so the code later also increments skipped++. This will make the recorded SourceSyncEvent aggregates misleading. Consider tracking an anyChange (or statusChanged/watermarkChanged) flag that’s set when a status transition or watermark write happens, and only increment skipped when no change of any kind occurred.
		if !anyField {
			skipped++
			continue
		}

web-app/src/components/backlog/detail/SourceSection.tsx:1

  • externalId is optional but is rendered unconditionally (Issue #{externalId}), which can produce user-visible Issue #undefined when externalUrl is present but externalId is missing. Either make externalId required whenever this component is rendered, or render a fallback (e.g., omit the number / derive it from the URL) when externalId is not provided.
    web-app/src/components/backlog/detail/SourceSection.tsx:1
  • externalId is optional but is rendered unconditionally (Issue #{externalId}), which can produce user-visible Issue #undefined when externalUrl is present but externalId is missing. Either make externalId required whenever this component is rendered, or render a fallback (e.g., omit the number / derive it from the URL) when externalId is not provided.
    web-app/src/components/backlog/BacklogItemCard.tsx:1
  • The provenance badge is guarded only by item.externalUrl, but it renders and announces item.externalId. If externalId is absent, the badge will show/announce #undefined. Consider guarding on both externalUrl && externalId (or computing a safe label) so the link text/aria-label never contains an undefined identifier.
    web-app/src/components/settings/BacklogSourcesSettings.tsx:1
  • isAuthFailure treats any 403 as an auth failure, but GitHub rate limiting is commonly surfaced as HTTP 403 (and your own code produces errors containing rate limited + status 403). This will incorrectly show a persistent “check credentials” warning for transient rate-limit failures. Consider removing the generic 403 check and instead matching more specific auth signals (e.g. 401, 'bad credentials', 'revoked', 'forbidden: requires authentication') while explicitly excluding 'rate limited'.
    web-app/src/components/backlog/detail/SourceSection.test.tsx:1
  • This test name doesn’t follow the more descriptive naming convention used by the other new tests in this file (and elsewhere in the PR). Consider renaming it to match the established pattern (e.g. SourceSection_should_BeCollapsed_When_DefaultExpandedFalse) so failures are easier to interpret in CI output.

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

Comment thread session/backlog_sync.go Outdated
Comment thread session/backlog_sync.go Outdated
Comment thread server/services/backlog_service.go
…-backlog-github-two-way-sync

# Conflicts:
#	gen/proto/go/session/v1/backlog.pb.go
#	session/backlog.go
#	web-app/src/gen/session/v1/backlog_pb.ts
@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.vPyDkclzJE/backend
Wrote 15 feature files to /tmp/tmp.vPyDkclzJE/backend
Wrote 46 feature files to /tmp/tmp.vPyDkclzJE/backend
Wrote 8 feature files to /tmp/tmp.vPyDkclzJE/backend
Wrote 12 feature files to /tmp/tmp.vPyDkclzJE/backend

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

✅ Registry validation passed. Divergence: 0.0%

Test Coverage: 27/182 features have testIds (14.8%)

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

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Go Benchmarks (Tier 1)

benchmarks/go/tier1-baseline.txt:97: missing iteration count
benchmarks/go/tier1-baseline.txt:197: 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 9V74 80-Core Processor                
                                            │ tier1-bench.txt │
                                            │     sec/op      │
CircularBufferWrite_4KB-4                         80.18n ± 0%
CircularBufferWrite_4KB_Allocs-4                  79.17n ± 0%
CircularBufferGetRecent_4KB-4                     500.1n ± 3%
CircularBufferGetAll-4                            3.789µ ± 1%
GetTimeSinceLastMeaningfulOutput_HotPath-4        70.23n ± 1%
GetTimeSinceLastMeaningfulOutput_ColdPath-4       34.51n ± 0%
geomean                                           175.4n

                                            │ 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          47.58Gi ± 0%
CircularBufferGetRecent_4KB-4      7.628Gi ± 3%
geomean                            19.05Gi

cpu: INTEL(R) XEON(R) PLATINUM 8573C
                                            │ benchmarks/go/tier1-baseline.txt │
                                            │              sec/op              │
CircularBufferWrite_4KB-4                                          175.8n ± 2%
CircularBufferWrite_4KB_Allocs-4                                   172.5n ± 2%
CircularBufferGetRecent_4KB-4                                      520.8n ± 6%
CircularBufferGetAll-4                                             3.840µ ± 4%
GetTimeSinceLastMeaningfulOutput_HotPath-4                         52.87n ± 5%
GetTimeSinceLastMeaningfulOutput_ColdPath-4                        27.92n ± 8%
geomean                                                            211.5n

                                            │ 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                           21.70Gi ± 2%
CircularBufferGetRecent_4KB-4                       7.324Gi ± 6%
geomean                                             12.61Gi

pkg: github.com/tstapler/stapler-squad/session/detection/ratelimit
cpu: AMD EPYC 9V74 80-Core Processor                
                              │ tier1-bench.txt │
                              │     sec/op      │
StripANSI_PlainText-4               7.048n ± 5%
StripANSI_WithEscapes-4             653.8n ± 1%
ProcessOutput_InactiveState-4       6.623n ± 1%
geomean                             31.25n

                              │ 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: INTEL(R) XEON(R) PLATINUM 8573C
                              │ benchmarks/go/tier1-baseline.txt │
                              │              sec/op              │
StripANSI_PlainText-4                                5.373n ± 4%
StripANSI_WithEscapes-4                              593.6n ± 3%
ProcessOutput_InactiveState-4                        15.77n ± 2%
geomean                                              36.92n

                              │ 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 9V74 80-Core Processor                
                              │ tier1-bench.txt │
                              │     sec/op      │
ReviewQueue_ConcurrentReads-4      91.11n ± 29%
ReviewQueue_Add-4                  502.2n ±  1%
geomean                            213.9n

                              │ 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: INTEL(R) XEON(R) PLATINUM 8573C
                              │ benchmarks/go/tier1-baseline.txt │
                              │              sec/op              │
ReviewQueue_ConcurrentReads-4                        102.0n ± 6%
ReviewQueue_Add-4                                    438.4n ± 3%
geomean                                              211.5n

                              │ 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 9V74 80-Core Processor                
                                      │ tier1-bench.txt │
                                      │     sec/op      │
CircularBuffer_ConcurrentReadWrite-4        3.481µ ± 2%
CircularBuffer_BurstAppend-4                106.3µ ± 1%
CircularBuffer_GetLastN_LargeBuffer-4       19.97µ ± 1%
CircularBuffer_GetRange_Sequential-4        13.76µ ± 8%
CircularBufferAppend-4                      108.7n ± 0%
CircularBufferGetLastN-4                    2.519µ ± 2%
CircularBufferConcurrentAppend-4            137.4n ± 1%
geomean                                     3.250µ

                                      │ 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      574.3Mi ± 1%

cpu: INTEL(R) XEON(R) PLATINUM 8573C
                                      │ benchmarks/go/tier1-baseline.txt │
                                      │              sec/op              │
CircularBuffer_ConcurrentReadWrite-4                         3.114µ ± 2%
CircularBuffer_BurstAppend-4                                 115.8µ ± 3%
CircularBuffer_GetLastN_LargeBuffer-4                        16.96µ ± 4%
CircularBuffer_GetRange_Sequential-4                         12.87µ ± 7%
CircularBufferAppend-4                                       119.3n ± 5%
CircularBufferGetLastN-4                                     2.410µ ± 5%
CircularBufferConcurrentAppend-4                             167.1n ± 7%
geomean                                                      3.244µ

                                      │ 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                       526.9Mi ± 3%

pkg: github.com/tstapler/stapler-squad/session/tmux
cpu: AMD EPYC 9V74 80-Core Processor                
                             │ tier1-bench.txt │
                             │     sec/op      │
StripANSICodes_PlainText-4         7.282n ± 8%
StripANSICodes_WithEscapes-4       622.5n ± 1%
IsBanner_PlainText-4               465.2n ± 0%
geomean                            128.2n

                             │ 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: INTEL(R) XEON(R) PLATINUM 8573C
                             │ benchmarks/go/tier1-baseline.txt │
                             │              sec/op              │
StripANSICodes_PlainText-4                          4.568n ± 2%
StripANSICodes_WithEscapes-4                        535.3n ± 2%
IsBanner_PlainText-4                                376.2n ± 2%
geomean                                             97.26n

                             │ 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 9V74 80-Core Processor                
                                   │ tier1-bench.txt │
                                   │     sec/op      │
TokenParser_ProcessUserEntry-4           5.532m ± 2%
DetectCommandsInText/NoSlash-4           6.349n ± 5%
DetectCommandsInText/WithCommand-4       1.489µ ± 0%
geomean                                  3.740µ

                                   │ 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: INTEL(R) XEON(R) PLATINUM 8573C
                                   │ benchmarks/go/tier1-baseline.txt │
                                   │              sec/op              │
TokenParser_ProcessUserEntry-4                            4.170m ± 4%
DetectCommandsInText/NoSlash-4                            4.715n ± 5%
DetectCommandsInText/WithCommand-4                        1.460µ ± 3%
geomean                                                   3.061µ

                                   │ 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 9V74 80-Core Processor                
                               │ tier1-bench.txt │
                               │     sec/op      │
DiffShortstat/GitVCSReader-4         3.369m ± 1%
DiffShortstat/GoGitVCSReader-4       80.74n ± 0%
DiffShortstatCached-4                80.44n ± 0%
geomean                              2.797µ

                               │ tier1-bench.txt │
                               │      B/op       │
DiffShortstat/GitVCSReader-4      62.58Ki ± 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: INTEL(R) XEON(R) PLATINUM 8573C
                               │ benchmarks/go/tier1-baseline.txt │
                               │              sec/op              │
DiffShortstat/GitVCSReader-4                          2.238m ± 3%
DiffShortstat/GoGitVCSReader-4                        61.81n ± 2%
DiffShortstatCached-4                                 62.52n ± 1%
geomean                                               2.053µ

                               │ benchmarks/go/tier1-baseline.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

                               │ 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 4, 2026

Copy link
Copy Markdown
Contributor

E2E RPC Latency

list-sessions-ttfb-mean: 9ms (▲ slower +6.3%; baseline: 9ms)
list-sessions-total-mean: 11ms (▲ slower +2.9%; baseline: 11ms)

@github-actions

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

Copy link
Copy Markdown
Contributor

Frontend Terminal Throughput

terminal-throughput-mean: 16 KB/s ▼ -0.1% (baseline: 16 KB/s)
terminal-throughput-p50: 16 KB/s ▲ +0.3% (baseline: 16 KB/s)

@github-actions

github-actions Bot commented Aug 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 Aug 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.

- session/backlog_sync.go: guard both closed/reopened-issue backward-sync
  blocks against a zero (unparsed) IssueUpdatedAt, which would otherwise
  either false-short-circuit as already-reconciled against a real watermark
  or persist a garbage zero watermark.
- session/backlog_sync.go: track a per-item anyChange flag so a status
  transition/watermark write in the closed/reopened blocks isn't also
  double-counted by the generic `!anyField` skipped++ fallback, restoring
  the SourceSyncEvent aggregate's partition-of-item-count invariant.
- server/services/backlog_service.go: only set the optional ExternalUrl
  proto field when ExternalURL is non-empty, in both backlogItemToProto and
  backlogItemSummaryToProto, instead of always setting a non-nil pointer to
  an empty string.
- web-app SourceSection.tsx / BacklogItemCard.tsx: guard the "Issue #<id>"
  rendering so a present externalUrl with a missing externalId can't render
  a literal "Issue #undefined".
- web-app BacklogSourcesSettings.tsx: isAuthFailure no longer treats every
  403 uniformly — GitHub's rate-limit response is also a 403, so rate-limited
  messages are now explicitly excluded before matching on 401/403/bad
  credentials/revoked/requires authentication.
- Test naming nit: renamed a SourceSection test to the file's established
  Subject_should_ExpectedBehavior_When_Condition convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HWAXmtpHQXXZTKmnvLSSWb
@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.u9OVKOOKut/backend
Wrote 15 feature files to /tmp/tmp.u9OVKOOKut/backend
Wrote 46 feature files to /tmp/tmp.u9OVKOOKut/backend
Wrote 8 feature files to /tmp/tmp.u9OVKOOKut/backend
Wrote 12 feature files to /tmp/tmp.u9OVKOOKut/backend

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

✅ Registry validation passed. Divergence: 0.0%

Test Coverage: 27/182 features have testIds (14.8%)

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

…-backlog-github-two-way-sync

# Conflicts:
#	session/backlog_plugin_github.go
@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.WuCxLo9RMU/backend
Wrote 15 feature files to /tmp/tmp.WuCxLo9RMU/backend
Wrote 46 feature files to /tmp/tmp.WuCxLo9RMU/backend
Wrote 8 feature files to /tmp/tmp.WuCxLo9RMU/backend
Wrote 12 feature files to /tmp/tmp.WuCxLo9RMU/backend

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

✅ Registry validation passed. Divergence: 0.0%

Test Coverage: 27/182 features have testIds (14.8%)

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

@tstapler
tstapler merged commit 8a84747 into main Aug 4, 2026
24 checks passed
@tstapler
tstapler deleted the backlog/stapler-squad-backlog-github-two-way-sync branch August 4, 2026 23:16
@tstapler
tstapler restored the backlog/stapler-squad-backlog-github-two-way-sync branch August 4, 2026 23:27
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