Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 34 additions & 6 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ requested review too; bot messages do not create a human response obligation.

Response timing uses the earliest external message after the last maintainer
message, including PR review comments and submitted reviews. A reply is not
proof of resolution. Missing timestamps/order mean unknown. Accepted answers
and broadcast categories preserve the existing hub settlement semantics.
proof of resolution. Missing timestamps/order mean unknown. Accepted answers settle earlier activity; later external comments require follow-up review.
Broadcast categories retain their existing non-response semantics.
Discussion activity uses fixed read-only GraphQL queries for the complete
top-level comment connection and each comment's reply connection. Every
connection follows `pageInfo` cursors independently up to `max_pages`; a
Expand All @@ -60,9 +60,8 @@ read is distinct from a successful empty read. REST PR review comments include
inline replies; submitted reviews and issue comments are separately paginated.
This measures response activity, not review-thread resolution or delivery.
An accepted proposal settles the hub response obligation only; implementation
and release evidence belong to the separate follow-through projection. Accepted-answer
and broadcast policy can settle response obligation even while a coverage gap
remains visible.
and release evidence belong to the separate follow-through projection. Broadcast policy can settle response obligation while a coverage gap remains visible.
Settled non-broadcast threads with incomplete follow-up evidence stay unknown.

The CLI returns 2 for invalid configuration, input or render failure. Source
read failures appear in a valid degraded snapshot so the dashboard can expose
Expand Down Expand Up @@ -119,7 +118,8 @@ agent session is created. The workflow is ordinary repository automation.
(publicly verified URL/repo/kind triples) and bounded `gaps`. No source bodies
or manual task assignments are stored. Old snapshots without the extension
remain valid. Conversations may carry boolean `closed`; closed Discussions
are collected separately, remain settled, and do not count as open in Brain.
are collected separately and do not count as open in Brain; new external activity
after settlement can require follow-up review independently of delivery status.

A configured maintainer records explicit links on their own source body or
comment/reply, one unquoted standalone line per link:
Expand Down Expand Up @@ -204,3 +204,31 @@ URL mentions do not qualify. This means a plan exists, not that it is approved
or implemented. This hint is render-time only: snapshot v1, delivery states and
Brain's cockpit feed remain unchanged. Rendering a snapshot without `--mind`
keeps source/delivery badges and makes no plan claim.

## Post-settlement activity (optional snapshot v1 extension)

A settled non-broadcast Discussion or closed issue carries `follow_up` with
nullable boolean `review_needed`, nullable timestamp `since`, and nullable `url`.
True means an external human comment after the latest closure/answer boundary
and after the last maintainer response needs review. It does not assert semantic
actionability: even thanks can need contextual inspection. The timestamp is the
oldest pending comment, and the URL links to that comment when an identity is
available, otherwise the thread. False/null carry no timestamp or URL. Response
state mirrors this observation; unknown activity or settlement time is partial,
never checked clear. Discussion closure time comes from a fixed read-only
GraphQL query because REST does not supply it. No permissions or bodies are
published. Readers accept old snapshots without this optional field.

Closed issues and Discussions are scanned by most recently updated first, under
`max_pages`; truncated history is an explicit coverage gap. Closed PRs are not
read. Closed maintainer-authored issues are checked for external comments too;
quiet closed issues are omitted. New activity can bring an old thread into the
bounded window. This is not an unlimited historical audit.

Brain Community reviews the source comments and nested replies for requests,
acknowledgements or ambiguity and recommends the next step. A contributor's
ability to reopen is never a prerequisite. Where a reopen is appropriate, verify
the acting account's permission or refer it to a maintainer; never infer the
commenter's permissions from the collector token. Reopening, unlocking and
clearing an answer are separate, explicitly authorized actions. Ears performs
none of them. Stale/cached evidence must be refreshed before acting.
14 changes: 11 additions & 3 deletions ears/board.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pathlib import Path

from .collect import line, now, utc, validate
from .presentation import CHECKIN, CSS, copy_button, pill, progress, waiting_label
from .presentation import CHECKIN, CSS, copy_button, pill, progress, waiting_label, triage_prompt


def load_module(path, name):
Expand Down Expand Up @@ -56,7 +56,7 @@ def render(snapshot, config, brain, rendered_at=None, plans=()):
headline = "STALE — " + headline
items = []
for row in attention + [r for r in unknown if r not in attention]:
prompt = theme.portable_prompt(f"/community triage {row['url']}")
prompt = theme.portable_prompt(triage_prompt(row))
items.append({"id": row["id"], "severity": "yellow", "text": line(row["title"]),
"url": row["url"], "prompt": prompt,
"state": "unknown" if row in unknown else "action_required",
Expand Down Expand Up @@ -133,8 +133,16 @@ def section(title, key, selected, empty):
label, tone = progress(row, d, stale, plans)
timing = waiting_label(row, snapshot['generated'])
kind = {'issue': 'Issue', 'pr': 'PR', 'discussion': 'Discussion'}[row['kind']]
prompt = theme.portable_prompt(f"/community triage {row['url']}")
prompt = theme.portable_prompt(triage_prompt(row))
details = '<p><a href="' + esc(row['url'], quote=True) + '">Open ' + kind.lower() + ' ↗</a></p>'
if 'follow_up' in row:
settled = 'Closed' if row.get('closed') else 'Open'
settled += ' · Answered' if row['answered'] else ''
details += '<p>Thread status: ' + settled + '</p>'
candidate = row['follow_up'].get('url')
if candidate:
details += '<p><a href="' + esc(candidate, quote=True) + '">Read follow-up ↗</a></p>'
details += '<p>Activity needs review; this is not a decision to reopen. Contributors may lack reopening permission.</p>'
if row.get('waiting_since'):
details += '<p>Waiting since <time datetime="' + esc(row['waiting_since'], quote=True) + '">' + esc(utc(row['waiting_since']).strftime('%d %b %Y, %H:%M UTC')) + '</time></p>'
if stale:
Expand Down
Loading
Loading