feat(mcp): brain_pending + brain_promote tools + REST routes (#38)
Wires the curation primitives into the MCP surface (all three sites: tools() descriptors, handleCall dispatch, package doc) and registers the GET /pending + POST /promote REST routes. brain_promote additionally re-indexes the promoted note into the graph (best-effort), matching the other write paths. brain_pending is the human-review-queue complement to the agent-facing brain_write. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mathiasbq/hyperguild/ingestion/internal/api"
|
||||
"github.com/mathiasbq/hyperguild/ingestion/internal/brain"
|
||||
"github.com/mathiasbq/hyperguild/ingestion/internal/capture"
|
||||
"github.com/mathiasbq/hyperguild/ingestion/internal/extract"
|
||||
@@ -81,6 +82,21 @@ func (s *Server) tools() []map[string]any {
|
||||
"path": str("brain-relative path to the note; equivalent to id"),
|
||||
}),
|
||||
},
|
||||
{
|
||||
"name": "brain_pending",
|
||||
"description": "List notes in brain/raw/ awaiting human promotion to the wiki, oldest-first. Returns filename, created_at, size_bytes, excerpt. The human-review queue complement to brain_promote.",
|
||||
"inputSchema": schema([]string{}, map[string]any{}),
|
||||
},
|
||||
{
|
||||
"name": "brain_promote",
|
||||
"description": "Promote a brain/raw/ note into brain/wiki/<wing>/<hall>/: rewrites frontmatter (sets wing/hall/promoted_at, preserves created_at + custom fields), deletes the source, rebuilds the wing index, runs auto-tunnel. Errors (without touching the fs) on invalid hall or a slug collision. Returns {path}.",
|
||||
"inputSchema": schema([]string{"filename", "wing", "hall"}, map[string]any{
|
||||
"filename": str("basename in brain/raw/, e.g. 2026-06-01-lejpa-decision.md"),
|
||||
"wing": str("target wing, e.g. jepa-fx"),
|
||||
"hall": enum("target hall", halls...),
|
||||
"slug": str("optional target slug; defaults to filename minus date prefix"),
|
||||
}),
|
||||
},
|
||||
{
|
||||
"name": "brain_tunnel",
|
||||
"description": "Create an explicit bidirectional [[wikilink]] between two notes in different wings. Idempotent.",
|
||||
@@ -321,6 +337,40 @@ func (s *Server) brainGet(ctx context.Context, args json.RawMessage) (json.RawMe
|
||||
})
|
||||
}
|
||||
|
||||
// brainPending lists the raw/ review queue (oldest-first).
|
||||
func (s *Server) brainPending(_ context.Context, _ json.RawMessage) (json.RawMessage, error) {
|
||||
pending, err := api.ListPending(s.brainDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(map[string]any{"pending": pending})
|
||||
}
|
||||
|
||||
type brainPromoteArgs struct {
|
||||
Filename string `json:"filename"`
|
||||
Wing string `json:"wing"`
|
||||
Hall string `json:"hall"`
|
||||
Slug string `json:"slug,omitempty"`
|
||||
}
|
||||
|
||||
// brainPromote moves a raw/ note into the structured wiki (frontmatter
|
||||
// rewrite + index + auto-tunnel, all owned by api.PromoteNote) and then
|
||||
// re-indexes it into the graph. The human-facing complement to brain_write.
|
||||
func (s *Server) brainPromote(ctx context.Context, args json.RawMessage) (json.RawMessage, error) {
|
||||
var a brainPromoteArgs
|
||||
if err := json.Unmarshal(args, &a); err != nil {
|
||||
return nil, fmt.Errorf("parse args: %w", err)
|
||||
}
|
||||
relPath, err := api.PromoteNote(s.brainDir, api.PromoteOptions{
|
||||
Filename: a.Filename, Wing: a.Wing, Hall: a.Hall, Slug: a.Slug,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.indexInGraph(ctx, "brain_promote", relPath)
|
||||
return json.Marshal(map[string]string{"path": relPath})
|
||||
}
|
||||
|
||||
// indexInGraph is a best-effort wrapper around graphsync.IndexDoc that
|
||||
// logs failures but never propagates them — the underlying write/ingest
|
||||
// has already succeeded and the graph is an augmentation, not a
|
||||
|
||||
Reference in New Issue
Block a user