package mcp import ( "time" "github.com/mathiasbq/hyperguild/ingestion/internal/api" ) // pendingNudgeThreshold is how old a raw/ note has to be before brain_context // mentions the queue. Two weeks: long enough that a note written this sprint // does not nag, short enough that four items cannot sit unnoticed for three // months again (#82). const pendingNudgeThreshold = 14 * 24 * time.Hour // PendingNudge is the in-session reminder that the promote/reject queue has // items waiting. It is advisory: a count, an age, and how to look. type PendingNudge struct { Pending int `json:"pending"` // everything in the queue Aging int `json:"aging"` // those past the threshold Oldest string `json:"oldest"` // filename of the oldest pending note OldestDays int `json:"oldest_days"` // its age, in whole days Hint string `json:"hint"` } // pendingNudge summarises the raw/ review queue, or returns nil when there is // nothing worth saying. // // The trigger is the missing half of the queue (#82): brain_pending has always // existed, and four real items still sat unreviewed from 2026-04-23 to // 2026-07-15 because nothing ever prompted anyone to call it. The system // should be what remembers the queue exists. // // Threshold-gated on purpose. A notice on every context call is one people // learn to skip, which reproduces the original failure with extra steps. // // It never promotes, rejects, or reorders anything. Age is a reason to ask a // human, not a verdict — an old note may be exactly right to promote once // someone finally looks at it. func pendingNudge(brainDir string, now time.Time, threshold time.Duration) *PendingNudge { pending, err := api.ListPending(brainDir) if err != nil || len(pending) == 0 { // brain_context is a hot path; a broken queue degrades to silence // rather than failing the call that asked for project context. return nil } var aging int var oldestName string var oldestAge time.Duration for _, p := range pending { created, perr := time.Parse(time.RFC3339, p.CreatedAt) if perr != nil { continue // an unparseable timestamp is not evidence of age } age := now.Sub(created) if age >= threshold { aging++ } if age > oldestAge { oldestAge, oldestName = age, p.Filename } } if aging == 0 { return nil } return &PendingNudge{ Pending: len(pending), Aging: aging, Oldest: oldestName, OldestDays: int(oldestAge.Hours() / 24), Hint: "call brain_pending to review; nothing is promoted or rejected automatically", } }