Skip to content

A4: board MCP facet (opt-in) — board_put/query/move/close#45

Merged
hartsock merged 3 commits into
mainfrom
feat/board-mcp-facet
Jun 9, 2026
Merged

A4: board MCP facet (opt-in) — board_put/query/move/close#45
hartsock merged 3 commits into
mainfrom
feat/board-mcp-facet

Conversation

@hartsock

@hartsock hartsock commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Summary

Stacked on #44. Completes the knowledge-board capability by exposing cards over MCP, mirroring the store dispatch trio — under a new opt-in board facet.

tool mutates dispatch
board_put yes create/update a card (upsert by card_id); accepts refs {label:url} + blocked_on [url]
board_query no by lane / project / status (omit all = every card)
board_move yes change lane (and optionally context)
board_close yes move to done (default) / dropped, stamp closed_gen

Generation stamps use current_generation() (a counter, never a clock).

Tool budget (the key constraint): board is opt-in — NOT in DEFAULT_FACETS, so the default tools/list stays at exactly 12 (budget pin unchanged). The four tools remain reachable by default via tool_search/tool_invoke, the board step, and routine_eval — at zero tools/list cost. Opting in ([mcp] expose=[...,"board"] or MODULEX_TOOLS=core,store,board) lists them. facets.rs needs no change.

Disclosure tier: facet tool, opt-in.

Test plan

  • just check green (fmt + clippy -D warnings + all tests; EXIT=0).
  • surface_policy_is_pinned extended with the four board rows.
  • default_surface_fits_the_budget unchanged — proves board absent by default.
  • board_facet_is_opt_in_but_discoverable — not listed by default, lists 4 when exposed, discoverable always.
  • board_tools_round_trip_via_invoke — server-level put → query → move → close through tool_invoke.

Note: targets feat/board-step (#44). Rebase down the stack as #42/#43/#44 merge.

🤖 Generated with Claude Code

hartsock and others added 3 commits June 9, 2026 15:22
WHAT: Add board_md (card_to_markdown / card_from_markdown) and the
Store::import_dir / export_dir directory sync, so the operational SQLite
cards round-trip to/from the portable markdown+frontmatter form.

- board_md.rs: emits the canonical frontmatter field order (id, project,
  created, updated, summary, size, status, recurs, expires, blocked_on,
  refs, author/source) + body; parses tolerantly (free-text status,
  trailing `# comments` on list items, a `blocked_by` alias for
  blocked_on, refs label:url map). lane/context are directory facts, left
  empty by the parser and filled by the walker.
- Store::import_dir walks <root>/[<context>/]<lane>/*.md (lanes
  p0|p1|p2|done|dropped), follows symlinks (the lane-view convention) but
  dedups by card_id so a source file and its lane symlink import once;
  returns {added, updated, skipped}.
- Store::export_dir writes flat <context>/<lane>/<card_id>.md real files
  (non-destructive; never deletes files it didn't write).
- BoardConfig gains sync_dir / default_lane / default_context (all
  #[serde(default)], existing configs parse unchanged).
- New dependency: serde_yaml_ng (maintained drop-in for the deprecated
  serde_yaml) — parses the frontmatter map + list cleanly.

WHY: Markdown stays the portable, sovereign artifact (no lock-in); SQLite
is the fast operational store. v1 round-trip is at the card-content level
(frontmatter + body survive SQLite -> md -> SQLite) — it does NOT
reconstruct the numbered relative-symlink lane views; that's a follow-up.

Disclosure tier: internal capability + one new dependency. No MCP surface
change, no listed step yet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WHAT: Add a `board` step type that reads open knowledge-board cards from
the agent state store and renders them grouped by lane — the operational
counterpart of the filesystem `board-scan` step (which stays as-is).

- steps/board::Board: pure step (no subprocess), soft-skips without a
  store. Params (optional): `lane` (a single lane), `project` (filter).
  With no `lane`, shows the open work lanes p0/p1/p2 (closed lanes only on
  request). Flags cards carrying blocked_on refs as `[blocked]`.
- data_schema(): {lanes:[{lane, cards:[{card_id, project, summary, status,
  size, blocked}]}], open} — a new versioned contract (golden pinned).
- Registered in builtin_registry; documented-types test + module table
  updated. board-scan vs board distinction documented (filesystem vs store).
- data-contract harness: the `board` step joins CONTRACT_CONFIG and the
  golden set; a seeded card proves executed output validates against the
  schema.

WHY: The store-backed board is what makes "good morning" surface the local
knowledge board with no git or filesystem layout required — cards live in
SQLite (synced from/to markdown via A2). Preferred disclosure path per
FOUNDATION: a read-only step, zero MCP tools/list cost.

Disclosure tier: step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WHAT: Expose the knowledge-board cards over MCP via four tools under a new
`board` facet, mirroring the store dispatch trio:
  - board_put   (mutates) — create/update a card (upsert by card_id);
                 accepts refs {label:url} + blocked_on [url]
  - board_query (read)    — by lane / project / status (omit all = every card)
  - board_move  (mutates) — change lane (and optionally context)
  - board_close (mutates) — move to done (default) / dropped, stamp closed_gen
Generation stamps use current_generation() (a counter, never a clock).

WHY / budget: the `board` facet is OPT-IN — it is NOT in DEFAULT_FACETS, so
the default tools/list stays at exactly 12 (the budget pin is unchanged).
The four tools remain reachable by default via tool_search/tool_invoke,
the `board` step, and routine_eval — at zero tools/list cost. Opting in
(`[mcp] expose=[...,"board"]` or MODULEX_TOOLS=core,store,board) lists them.

Disclosure tier: facet tool, opt-in. facets.rs needs no change (expose/deny
already accept arbitrary facet strings).

Tests: surface_policy_is_pinned extended with the four rows;
default_surface_fits_the_budget unchanged (proves board absent by default);
board_facet_is_opt_in_but_discoverable; and a server-level
board_tools_round_trip_via_invoke (put -> query -> move -> close).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@hartsock hartsock added the risk:low Scoped, tested, no CI/build changes label Jun 9, 2026
@hartsock hartsock changed the base branch from feat/board-step to main June 9, 2026 20:23
@hartsock hartsock merged commit 57990ef into main Jun 9, 2026
1 check passed
@hartsock hartsock deleted the feat/board-mcp-facet branch June 9, 2026 20:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:low Scoped, tested, no CI/build changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant