brain_pending existed the whole time four real items sat unreviewed from 2026-04-23 to 2026-07-15. The tool was never the gap — nothing ever prompted anyone to call it. The system should be what remembers the queue exists. Option 2 from the issue: ride along with brain_context, which sessions already call, so noticing an aging note costs nobody a decision to go looking. No scheduled job, no ntfy wiring, nothing new to keep running. Threshold-gated at 14 days, and silent below it — a notice on every context call is one people learn to skip, which reproduces the original failure with extra steps. It reports the whole queue once it speaks: the threshold decides whether to talk, not what to count. It never promotes, rejects or reorders. Age is a reason to ask a human, not a verdict; an old note may be exactly right to promote once someone looks. A test asserts the queue is untouched and that the payload carries a hint rather than an action. An unreadable raw/ dir degrades to silence — brain_context is a hot path and must not fail because the review queue is broken. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BeAp5LTscnz2W7Ubt4eJ6m
77 lines
2.5 KiB
Go
77 lines
2.5 KiB
Go
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",
|
|
}
|
|
}
|