Closes#81. This was status:dispatched since 2026-08-24 but never actually shipped — the one dispatch attempt errored and was never retried (confirmed by grepping the codebase: no RejectNote, no reject.go, no brain_reject tool anywhere).
Why now
Investigating "raw/ items should get ingested and then moved somewhere clear" (a fresh ask), the live brain_pending/brain_context calls this session showed the queue has grown to 11 items, oldest 152 days old — several are generic web scrapes (a Google Ads video transcript, ML blog posts) nobody is realistically going to promote, but there was no way to clear them except an un-audited rm bypassing the tool surface entirely.
Same write-then-delete ordering — the source survives any write failure.
Same collision check — never silently overwrite an existing rejected note.
Stamps rejected_at (+ reject_reason if given) into frontmatter, moves brain/raw/<filename> → brain/raw/rejected/<filename>.
No index rebuild / auto-tunnel — a rejected note never entered the wiki.
raw/rejected/ is a subdirectory ListPending's non-recursive os.ReadDir never walks into, so a rejected note is never re-surfaced as pending.
brain_reject wired into the MCP tool list + dispatch, same schema shape as brain_promote (filename required, reason optional). Human-invoked only, never automatic — matches the existing promote/pending model exactly, per the issue's own framing (a reject is the same shape of judgment call as promote, one level earlier).
Verification
task check exit 0, zero FAIL. New tests mirror PromoteNote's existing coverage at both the api and mcp layers (happy path, missing source, traversal rejection, collision-no-overwrite, and confirming a rejected note doesn't leak back into brain_pending).
Live backlog clearing
The issue's own acceptance criteria asks to live-run this against the real stale queue once shipped. Once this is deployed, I'll use brain_reject/brain_promote to actually triage the 11 real pending items as the live verification step.
Closes #81. This was `status:dispatched` since 2026-08-24 but never actually shipped — the one dispatch attempt errored and was never retried (confirmed by grepping the codebase: no `RejectNote`, no `reject.go`, no `brain_reject` tool anywhere).
## Why now
Investigating "raw/ items should get ingested and then moved somewhere clear" (a fresh ask), the live `brain_pending`/`brain_context` calls this session showed the queue has grown to **11 items, oldest 152 days old** — several are generic web scrapes (a Google Ads video transcript, ML blog posts) nobody is realistically going to promote, but there was no way to clear them except an un-audited `rm` bypassing the tool surface entirely.
## What this adds
`RejectNote` (`internal/api/promote.go`) mirrors `PromoteNote`'s exact safety shape:
- Same basename-only filename validation.
- Same write-then-delete ordering — the source survives any write failure.
- Same collision check — never silently overwrite an existing rejected note.
- Stamps `rejected_at` (+ `reject_reason` if given) into frontmatter, moves `brain/raw/<filename>` → `brain/raw/rejected/<filename>`.
- No index rebuild / auto-tunnel — a rejected note never entered the wiki.
- `raw/rejected/` is a subdirectory `ListPending`'s non-recursive `os.ReadDir` never walks into, so a rejected note is never re-surfaced as pending.
`brain_reject` wired into the MCP tool list + dispatch, same schema shape as `brain_promote` (`filename` required, `reason` optional). Human-invoked only, never automatic — matches the existing promote/pending model exactly, per the issue's own framing (a reject is the same shape of judgment call as promote, one level earlier).
## Verification
`task check` exit 0, zero FAIL. New tests mirror `PromoteNote`'s existing coverage at both the `api` and `mcp` layers (happy path, missing source, traversal rejection, collision-no-overwrite, and confirming a rejected note doesn't leak back into `brain_pending`).
## Live backlog clearing
The issue's own acceptance criteria asks to live-run this against the real stale queue once shipped. Once this is deployed, I'll use `brain_reject`/`brain_promote` to actually triage the 11 real pending items as the live verification step.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
_Created via git-mcp on behalf of @mathias_
hyperguild#81. brain_pending lists brain/raw/ notes awaiting review;
brain_promote moves one into the wiki. There was no way to say "reviewed,
not worth promoting" — the only options were promote it or let it sit
forever. Live consequence, confirmed this session: the pending queue has
grown to 11 items, the oldest 152 days old, several of which are generic
web scrapes nobody is ever going to promote but nobody could clear either
except an un-audited rm bypassing the tool surface entirely.
RejectNote (internal/api/promote.go) mirrors PromoteNote's exact safety
shape: same basename-only filename validation, same write-then-delete
ordering (the source survives any write failure), same collision check
(never silently overwrite an existing rejected note under the same
name). Stamps rejected_at (+ reject_reason if given) into frontmatter,
moves brain/raw/<filename> -> brain/raw/rejected/<filename>. No index
rebuild or auto-tunnel -- a rejected note never entered the wiki, so
there's nothing to index. raw/rejected/ is a subdirectory ListPending's
non-recursive os.ReadDir never walks into, so a rejected note is never
re-surfaced as pending -- the human decision, once made, stays made.
brain_reject wired into the MCP tool list and dispatch, mirroring
brain_promote's schema shape (filename required, reason optional).
Human-invoked only, same as promote -- never called automatically.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Review of PR #133 found a serious bug: the file watcher's processDir
walks brain/raw/ with filepath.WalkDir (recurses by default) and only
skipped processed/ and failed/ by name. brain_reject moves a declined
note to raw/rejected/, which the watcher had no reason to know about --
so on its next tick (30s default), it picked the note back up, ran it
through the full LLM extraction pipeline, and published it into the
wiki. A human's explicit "not worth promoting" decision was silently
reversed by a background job 30 seconds later. Reproduced directly:
flip-testing the fix back out shows the watcher's own log line, "file
processed file=declined-file.md", proving this wasn't hypothetical.
Fixed by adding "rejected" to the same subdirectory skip-list
processed/failed already use. Checked the other two consumers of raw/
content for the same class of bug and found neither is affected:
graphsync.BackfillFromBrainDir only walks wiki/ and knowledge/; search's
resolveRoots only ever returns knowledge/ and wiki/. Neither touches
raw/ at all, rejected or not.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
mathias
merged commit 674858fe5e into main2026-09-23 06:06:35 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #81. This was
status:dispatchedsince 2026-08-24 but never actually shipped — the one dispatch attempt errored and was never retried (confirmed by grepping the codebase: noRejectNote, noreject.go, nobrain_rejecttool anywhere).Why now
Investigating "raw/ items should get ingested and then moved somewhere clear" (a fresh ask), the live
brain_pending/brain_contextcalls this session showed the queue has grown to 11 items, oldest 152 days old — several are generic web scrapes (a Google Ads video transcript, ML blog posts) nobody is realistically going to promote, but there was no way to clear them except an un-auditedrmbypassing the tool surface entirely.What this adds
RejectNote(internal/api/promote.go) mirrorsPromoteNote's exact safety shape:rejected_at(+reject_reasonif given) into frontmatter, movesbrain/raw/<filename>→brain/raw/rejected/<filename>.raw/rejected/is a subdirectoryListPending's non-recursiveos.ReadDirnever walks into, so a rejected note is never re-surfaced as pending.brain_rejectwired into the MCP tool list + dispatch, same schema shape asbrain_promote(filenamerequired,reasonoptional). Human-invoked only, never automatic — matches the existing promote/pending model exactly, per the issue's own framing (a reject is the same shape of judgment call as promote, one level earlier).Verification
task checkexit 0, zero FAIL. New tests mirrorPromoteNote's existing coverage at both theapiandmcplayers (happy path, missing source, traversal rejection, collision-no-overwrite, and confirming a rejected note doesn't leak back intobrain_pending).Live backlog clearing
The issue's own acceptance criteria asks to live-run this against the real stale queue once shipped. Once this is deployed, I'll use
brain_reject/brain_promoteto actually triage the 11 real pending items as the live verification step.Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Created via git-mcp on behalf of @mathias