Post-merge cleanup for the SCM observer (#76, merged as 19b6ca5). The observer goroutine runs, persists, and projects to lifecycle, but three wiring gaps still suppress the user-visible nudge behavior end to end. Bundling them because the first two are blocking and the third is a small follow-up that lands cleanest in the same touch.
1) Thread the runtime messenger into the Lifecycle Manager
backend/internal/daemon/lifecycle_wiring.go:38 still constructs the LCM with a nil messenger:
lcm := lifecycle.New(store, nil)
The real runtimeMessenger is built downstream at backend/internal/daemon/daemon.go:87 for startSession. Effect: SCM observer → lifecycle.ApplySCMObservation → ApplyPRObservation → sendOnce → m.messenger == nil short-circuit at backend/internal/lifecycle/reactions.go:174-176. Every nudge is silently dropped.
Change: move messenger := newSessionMessenger(...) above the startLifecycle call in daemon.go, add messenger ports.AgentMessenger to the startLifecycle signature, replace the nil in lifecycle_wiring.go:38 with the real messenger. ~5 lines.
2) Populate RepoOriginURL at project add (plus lazy backfill)
backend/internal/service/project/service.go:122-128 constructs the project row with RepoOriginURL left as "":
row := domain.ProjectRecord{
ID: string(id),
Path: path,
DisplayName: name,
RegisteredAt: time.Now(),
}
The schema already has the column (backend/internal/storage/sqlite/migrations/0001_init.sql:11). The SCM observer reads it at backend/internal/observe/scm/observer.go:453, hands "" to provider.ParseRepository, gets false, logs Debug, and skips. In production every registered project is invisible to the observer.
Change: at project-add time run git -C path remote get-url origin and assign to row.RepoOriginURL. Add a lazy backfill in the observer's discoverSubjects (or in service.Get): if RepoOriginURL == "" && Path != "", shell out, persist back, proceed.
Alternative (PR #87's approach): resolve at poll time per project. Easier on the schema but adds a fork-exec per project per poll. Pick A unless we hear otherwise.
3) Persist reaction-dedup signature so nudges survive daemon restart
lifecycle.Manager.react (the seen/attempts maps at backend/internal/lifecycle/reactions.go:14-22) lives on the Manager struct and is lost on restart. After a bounce, every still-failing CI / unresolved review nudges the agent again on the first poll, even if the agent was already prompted before the restart.
Change: add a last_nudge_signature TEXT NOT NULL DEFAULT '' (or similar) column on pr. sendOnce consults the column before sending and updates it after a successful send. Migration is a single ALTER TABLE pr ADD COLUMN.
This part is scoped together with (1) and (2) because all three touch the same end-to-end nudge path; landing them separately invites partial fixes.
Acceptance
Related
Post-merge cleanup for the SCM observer (#76, merged as 19b6ca5). The observer goroutine runs, persists, and projects to lifecycle, but three wiring gaps still suppress the user-visible nudge behavior end to end. Bundling them because the first two are blocking and the third is a small follow-up that lands cleanest in the same touch.
1) Thread the runtime messenger into the Lifecycle Manager
backend/internal/daemon/lifecycle_wiring.go:38still constructs the LCM with a nil messenger:The real
runtimeMessengeris built downstream atbackend/internal/daemon/daemon.go:87forstartSession. Effect: SCM observer →lifecycle.ApplySCMObservation→ApplyPRObservation→sendOnce→m.messenger == nilshort-circuit atbackend/internal/lifecycle/reactions.go:174-176. Every nudge is silently dropped.Change: move
messenger := newSessionMessenger(...)above thestartLifecyclecall indaemon.go, addmessenger ports.AgentMessengerto thestartLifecyclesignature, replace thenilinlifecycle_wiring.go:38with the real messenger. ~5 lines.2) Populate
RepoOriginURLat project add (plus lazy backfill)backend/internal/service/project/service.go:122-128constructs the project row withRepoOriginURLleft as"":The schema already has the column (
backend/internal/storage/sqlite/migrations/0001_init.sql:11). The SCM observer reads it atbackend/internal/observe/scm/observer.go:453, hands""toprovider.ParseRepository, getsfalse, logs Debug, and skips. In production every registered project is invisible to the observer.Change: at project-add time run
git -C path remote get-url originand assign torow.RepoOriginURL. Add a lazy backfill in the observer'sdiscoverSubjects(or inservice.Get): ifRepoOriginURL == "" && Path != "", shell out, persist back, proceed.Alternative (PR #87's approach): resolve at poll time per project. Easier on the schema but adds a fork-exec per project per poll. Pick A unless we hear otherwise.
3) Persist reaction-dedup signature so nudges survive daemon restart
lifecycle.Manager.react(theseen/attemptsmaps atbackend/internal/lifecycle/reactions.go:14-22) lives on the Manager struct and is lost on restart. After a bounce, every still-failing CI / unresolved review nudges the agent again on the first poll, even if the agent was already prompted before the restart.Change: add a
last_nudge_signature TEXT NOT NULL DEFAULT ''(or similar) column onpr.sendOnceconsults the column before sending and updates it after a successful send. Migration is a singleALTER TABLE pr ADD COLUMN.This part is scoped together with (1) and (2) because all three touch the same end-to-end nudge path; landing them separately invites partial fixes.
Acceptance
nilatlifecycle_wiring.go:38).ao project addpopulatesRepoOriginURLfromgit remote get-url origin.RepoOriginURLget backfilled lazily on first observer poll.pr.last_nudge_signaturecolumn exists;sendOnceconsults + updates it.go build ./... && go test -race ./...clean.Related
service/pr.Managerdesign in mind; this issue restates the post-feat: add provider-neutral SCM observer #76 reality.gitRepoResolverare the same delta on a now-superseded scaffolding (observe/prpollervsobserve/scm)./tmp/aa-scm-postmerge-understanding.mdand/tmp/aa-wiring-inventory.md.