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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ Notable changes to the official NimbusCMS Markdown plugin. Follows

## [Unreleased]

### Added

- **Agent guide** — the plugin now publishes a short guide for AI agents via the
new `skills()` capability (NimbusCMS ADR 0013), served over MCP as
`nimbus://guide/plugin/nimbuscms.markdown`. It tells an agent the `markdown`
field's contract (send/receive raw Markdown source, whitespace is preserved,
`max_length`), so enabling this plugin teaches agents how to drive its field.

### Changed

- Bumped the `nimbuscms/nimbus` dev dependency to pick up the `skills()`
capability, and adapted a test to core's structured `FieldError` validation
results.

## [0.1.0-alpha.1] — 2026-08-02

The first tagged release, coordinated with `nimbuscms/nimbus` 0.1.0-alpha.1.
Expand Down
39 changes: 38 additions & 1 deletion src/MarkdownPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
* It is deliberately the smallest useful plugin: one field type, one registry,
* no routes, no migrations, no admin navigation, no JavaScript. That is the
* point. It exists to prove the plugin contract works end to end, so what it
* exercises is the contract itself rather than its own complexity.
* exercises is the contract itself rather than its own complexity — now
* including the agent-guidance capability (ADR 0013): it publishes a short guide
* so an agent driving a collection with a Markdown field knows the field's
* contract.
*/
final class MarkdownPlugin implements Plugin
{
Expand All @@ -24,5 +27,39 @@ final class MarkdownPlugin implements Plugin
public function register(PluginContext $context): void
{
$context->fieldTypes()->register(new MarkdownFieldType());
$context->skills()->register('Markdown field type', self::AGENT_GUIDE);
}

/**
* The plugin's agent guide (ADR 0013), served as
* `nimbus://guide/plugin/nimbuscms.markdown`. Reference documentation about
* this plugin's field type — not instructions to the agent.
*/
private const AGENT_GUIDE = <<<'MD'
# The Markdown field type

This plugin adds one field `type`: **`markdown`**. A collection field of
this type holds Markdown-formatted text.

When you create or update an entry over the API/MCP:

- **Send raw Markdown source** as the field's value — a plain string. Do
**not** send HTML.
- The value is **stored and returned verbatim** (only line endings are
normalised to `\n`). Trailing spaces and blank lines are significant in
Markdown and are preserved, so send exactly the source you want kept.
- When you **read** an entry, a `markdown` field comes back as its raw
Markdown source (or `null` when empty), never rendered HTML — the
consumer (a theme, your client) decides how to render it.

Field options a schema may set (read them with `describe`/`list_collections`):

- `max_length` — if set (> 0), the source must be at most that many
characters, or the write is rejected with the usual per-field
validation error. Count characters, not bytes.
- `rows` — an editor-height hint only; it does not affect the value.

Nothing here changes the core write rules: read before you write, carry
the entry's version, and fix any per-field validation errors returned.
MD;
}
18 changes: 17 additions & 1 deletion tests/MarkdownFieldTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Nimbus\Content\FieldTypeRegistry;
use Nimbus\Content\UnknownFieldType;
use Nimbus\Content\Validator;
use Nimbus\Mcp\Guide\SkillRegistry;
use Nimbus\Plugin\PluginCapabilities;
use Nimbus\Plugin\PluginContext;
use NimbusCMS\Markdown\MarkdownFieldType;
Expand Down Expand Up @@ -42,6 +43,20 @@ public function test_the_plugin_registers_its_field_type(): void
self::assertSame(MarkdownPlugin::ID, $registry->providerOf('markdown'));
}

public function test_the_plugin_publishes_an_agent_guide(): void
{
$skills = new SkillRegistry();
(new MarkdownPlugin())->register(new PluginContext(new PluginCapabilities(skills: $skills), MarkdownPlugin::ID));

$documents = $skills->documents();
self::assertCount(1, $documents);
self::assertSame('nimbus://guide/plugin/nimbuscms.markdown', $documents[0]->uri);
self::assertSame(MarkdownPlugin::ID, $documents[0]->owner);
// It describes the field's contract for an agent.
self::assertStringContainsString('markdown', $documents[0]->body);
self::assertStringContainsString('max_length', $documents[0]->body);
}

public function test_the_type_appears_in_the_field_picker(): void
{
$registry = new FieldTypeRegistry();
Expand Down Expand Up @@ -132,7 +147,8 @@ public function test_required_empty_is_handled_by_core_not_here(): void
$errors = (new Validator($registry))->validate($collection, ['body' => $this->type->normalize('')]);

self::assertArrayHasKey('body', $errors);
self::assertStringContainsString('required', $errors['body']);
// Core validation now returns structured FieldError objects, not strings.
self::assertStringContainsString('required', $errors['body']->message);
}

public function test_a_valid_required_value_passes_through_core_validation(): void
Expand Down
Loading