fix(api,watcher): name the queue, list parked non-markdown, reserved wings, top-level-only frontmatter key; document the inbox (review of #211)
- brain_promote/brain_reject take an optional queue (inbox | needs-review); a name present in both is refused instead of silently acting on the top-level note; brain_pending labels every entry with its queue - parked pdf/txt files are listed (they were invisible); only markdown can be promoted, the others are moved or rejected - promote refuses sources/concepts/entities/wiki as a wing - frontmatterWing reads only a top-level key inside a properly fenced block - docs/raw-inbox.md: the contract, the three env vars, the known limits Each behaviour has a test that fails without it (mutation-checked). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -94,6 +94,7 @@ func (s *Server) tools() []map[string]any {
|
||||
"wing": str("target wing, e.g. jepa-fx"),
|
||||
"hall": enum("target hall", halls...),
|
||||
"slug": str("optional target slug; defaults to filename minus date prefix"),
|
||||
"queue": enum("which brain_pending listing the note is in; required only when the name exists in both", "inbox", "needs-review"),
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -102,6 +103,7 @@ func (s *Server) tools() []map[string]any {
|
||||
"inputSchema": schema([]string{"filename"}, map[string]any{
|
||||
"filename": str("basename in brain/raw/, e.g. 2026-04-23-fed-sentiment-scrape.md"),
|
||||
"reason": str("optional; why this isn't worth promoting"),
|
||||
"queue": enum("which brain_pending listing the note is in; required only when the name exists in both", "inbox", "needs-review"),
|
||||
}),
|
||||
},
|
||||
noteToolDescriptors()[0],
|
||||
@@ -383,6 +385,7 @@ type brainPromoteArgs struct {
|
||||
Wing string `json:"wing"`
|
||||
Hall string `json:"hall"`
|
||||
Slug string `json:"slug,omitempty"`
|
||||
Queue string `json:"queue,omitempty"`
|
||||
}
|
||||
|
||||
// brainPromote moves a raw/ note into the structured wiki (frontmatter
|
||||
@@ -395,7 +398,7 @@ func (s *Server) brainPromote(ctx context.Context, args json.RawMessage) (json.R
|
||||
}
|
||||
origin, writtenBy := provenanceFrom(ctx)
|
||||
relPath, err := api.PromoteNote(s.brainDir, api.PromoteOptions{
|
||||
Filename: a.Filename, Wing: a.Wing, Hall: a.Hall, Slug: a.Slug,
|
||||
Filename: a.Filename, Wing: a.Wing, Hall: a.Hall, Slug: a.Slug, Queue: a.Queue,
|
||||
Origin: origin, WrittenBy: writtenBy,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -408,6 +411,7 @@ func (s *Server) brainPromote(ctx context.Context, args json.RawMessage) (json.R
|
||||
type brainRejectArgs struct {
|
||||
Filename string `json:"filename"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Queue string `json:"queue,omitempty"`
|
||||
}
|
||||
|
||||
// brainReject records a decision not to promote a raw/ note (api.RejectNote:
|
||||
@@ -418,7 +422,7 @@ func (s *Server) brainReject(_ context.Context, args json.RawMessage) (json.RawM
|
||||
if err := json.Unmarshal(args, &a); err != nil {
|
||||
return nil, fmt.Errorf("parse args: %w", err)
|
||||
}
|
||||
relPath, err := api.RejectNote(s.brainDir, api.RejectOptions{Filename: a.Filename, Reason: a.Reason})
|
||||
relPath, err := api.RejectNote(s.brainDir, api.RejectOptions{Filename: a.Filename, Reason: a.Reason, Queue: a.Queue})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -431,3 +431,28 @@ func TestBrainRejectMissingSourceErrors(t *testing.T) {
|
||||
resp := toolCall(t, srv, "brain_reject", map[string]any{"filename": "ghost.md"})
|
||||
require.NotNil(t, resp["error"])
|
||||
}
|
||||
|
||||
// brain_pending lists two entries for one name when a top-level note and a
|
||||
// parked one share it; the tool must let the caller say which it means.
|
||||
func TestBrainPromoteAndRejectTakeTheQueueOfAnAmbiguousName(t *testing.T) {
|
||||
brainDir := t.TempDir()
|
||||
raw := filepath.Join(brainDir, "raw")
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(raw, "needs-review"), 0o755))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(raw, "a.md"), []byte("---\ncreated_at: 2026-10-20T00:00:00Z\n---\nTOP\n"), 0o644))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(raw, "needs-review", "a.md"), []byte("---\nproposed_wing: homelab\n---\nPARKED\n"), 0o644))
|
||||
srv := mcp.NewServer(brainDir, nil, nil, nil)
|
||||
|
||||
resp := toolCall(t, srv, "brain_promote", map[string]any{"filename": "a.md", "wing": "homelab", "hall": "facts"})
|
||||
require.NotNil(t, resp["error"], "an ambiguous name is refused, not guessed")
|
||||
|
||||
resp = toolCall(t, srv, "brain_promote", map[string]any{"filename": "a.md", "wing": "homelab", "hall": "facts", "queue": "needs-review"})
|
||||
require.Nil(t, resp["error"], "got: %v", resp["error"])
|
||||
got, err := os.ReadFile(filepath.Join(brainDir, "wiki/homelab/facts/a.md"))
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(got), "PARKED")
|
||||
|
||||
resp = toolCall(t, srv, "brain_reject", map[string]any{"filename": "a.md", "queue": "inbox", "reason": "no"})
|
||||
require.Nil(t, resp["error"], "got: %v", resp["error"])
|
||||
_, err = os.Stat(filepath.Join(brainDir, "raw/rejected/a.md"))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user