Scaffold docs, packages, and command registration

- Flesh out docs/index.md, docs/config.md, docs/features/mcp.md,
  docs/features/kanban.md with full content and aligned tables
- Fix .markdownlint-cli2.mjs config (rules were outside 'config' key)
- Add packages/mcp as a standalone MCP server package
- Update all pubspec descriptions
- Implement DewCommand + CommandRegistry in core
- Implement KanbanCommand and McpCommand stubs with registerCommands()
- Wire CLI entry point using CommandRunner + CommandRegistry
- Add 'melos run dew' script for running the CLI from workspace root
- Update tests across core, kanban, mcp packages

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Chris Hendrickson 2026-04-23 14:29:09 -04:00
parent b3201fde7b
commit 0723c7d996
20 changed files with 299 additions and 88 deletions

11
.markdownlint-cli2.mjs Normal file
View file

@ -0,0 +1,11 @@
export default {
globs: ['**/*.md'],
gitignore: true,
config: {
MD013: false,
MD040: true,
MD060: {
style: 'aligned',
},
},
};

View file

@ -0,0 +1,69 @@
# Dew Configuration
Dew is configured via a `dew.yaml` file stored in the `.project/` directory at the root of your project. Running `dew init .` will generate this file with sensible defaults.
## File Location
```text
your-project/
└── .project/
└── dew.yaml
```
## Full Schema
```yaml
dew:
mcp:
host: "localhost" # Hostname the MCP server binds to
port: 8080 # Port the MCP server listens on
kanban:
prefix: "PROJ" # Short prefix used for ticket IDs (e.g. PROJ-42)
ticket_types: # The types of tickets your board supports
- id: "epic"
name: "Epic"
- id: "story"
name: "Story"
- id: "task"
name: "Task"
- id: "bug"
name: "Bug"
- id: "spike"
name: "Spike"
columns: # Ordered list of columns on your Kanban board
- id: "todo"
name: "To Do"
color: "blue"
- id: "in-progress"
name: "In Progress"
color: "yellow"
- id: "done"
name: "Done"
color: "green"
```
## Reference
### `dew.mcp`
| Field | Type | Default | Description |
| ------ | ------- | ------------- | --------------------------------- |
| `host` | string | `"localhost"` | Hostname the MCP server binds to. |
| `port` | integer | `8080` | Port the MCP server listens on. |
### `dew.kanban`
| Field | Type | Description |
| -------------- | ------ | ------------------------------------------------------------------------------------------------------------ |
| `prefix` | string | Short uppercase prefix prepended to ticket IDs (e.g. `PROJ-1`). |
| `ticket_types` | list | The ticket types available on the board. Each entry requires an `id` and a `name`. |
| `columns` | list | The columns on the board, in order from left to right. Each entry requires an `id`, a `name`, and a `color`. |
#### Column colors
The following named colors are supported for column display:
`red`, `orange`, `yellow`, `green`, `blue`, `purple`, `pink`, `grey`

View file

@ -2,4 +2,22 @@
The Dew Model Context Protocol (MCP) Server is a feature that allows AI agents to interact with your project. This enables you to integrate AI capabilities into your project management workflow, such as automated task creation, progress tracking, and more. The Dew Model Context Protocol (MCP) Server is a feature that allows AI agents to interact with your project. This enables you to integrate AI capabilities into your project management workflow, such as automated task creation, progress tracking, and more.
The MCP server is implemented in the `packages/core` package, so other packages can interact with it. You can configure the MCP server's host and port in the `dew.yaml` configuration file under the `mcp` section. By default, the server will run on `localhost` at port `8080`. ## Package structure
The MCP feature is split across two packages to keep concerns separate:
- **`packages/core`** defines the `McpToolProvider` interface. Any feature package that wants to expose tools to AI agents implements this interface — without needing to depend on the MCP server itself.
- **`packages/mcp`** implements the actual server. It collects all registered `McpToolProvider` implementations and serves them over the configured host and port. Only the `cli` package depends on `packages/mcp`; feature packages like `kanban` remain decoupled from the transport layer.
## Configuration
The MCP server is configured under the `mcp` key in `.project/dew.yaml`. By default it runs on `localhost` at port `8080`.
```yaml
dew:
mcp:
host: "localhost"
port: 8080
```
See the [Configuration documentation](../config.md) for full details.

View file

@ -1,3 +1,25 @@
# Dew Documentation # Dew Documentation
Welcome to the documentation for the Dew project management tool! This documentation provides an overview of Dew's features, configuration options, and instructions for getting started. Welcome to the documentation for the Dew project management tool!
## Contents
- [Configuration](./config.md) — Configuring Dew via `dew.yaml`
### Features
- [Kanban Board](./features/kanban.md) — Visualize and manage tasks in a column-based workflow
- [MCP Server](./features/mcp.md) — AI agent integration via the Model Context Protocol
## Package Architecture
Dew is structured as a Dart workspace with the following packages:
| Package | Description |
| ----------------- | ------------------------------------------------------------------------------------------ |
| `packages/cli` | The `dew` command-line tool. Wires all packages together at startup. |
| `packages/core` | Shared types (tickets, columns, config) and the `McpToolProvider` interface. |
| `packages/kanban` | Kanban board logic. Implements `McpToolProvider` to expose its tools to the MCP server. |
| `packages/mcp` | The MCP server. Depends on `core`; collects and serves registered tools from all packages. |
`kanban` (and future feature packages) depend only on `core` — not on `mcp` — keeping them usable independently of the AI integration layer. The `cli` package is the only one that depends on `mcp` and is responsible for starting the server and registering all providers.

View file

@ -1,57 +1,21 @@
import 'package:args/args.dart'; import 'package:args/command_runner.dart';
import 'package:dew_core/dew_core.dart';
import 'package:dew_kanban/dew_kanban.dart' as kanban;
import 'package:dew_mcp/dew_mcp.dart' as mcp;
const String version = '0.0.1'; Future<void> main(List<String> args) async {
final registry = CommandRegistry();
kanban.registerCommands(registry);
mcp.registerCommands(registry);
ArgParser buildParser() { final runner = CommandRunner<void>(
return ArgParser() 'dew',
..addFlag( 'A project management tool.',
'help', );
abbr: 'h',
negatable: false, for (final command in registry.commands) {
help: 'Print this usage information.', runner.addCommand(command);
)
..addFlag(
'verbose',
abbr: 'v',
negatable: false,
help: 'Show additional command output.',
)
..addFlag('version', negatable: false, help: 'Print the tool version.');
} }
void printUsage(ArgParser argParser) { await runner.run(args);
print('Usage: dart dew.dart <flags> [arguments]');
print(argParser.usage);
}
void main(List<String> arguments) {
final ArgParser argParser = buildParser();
try {
final ArgResults results = argParser.parse(arguments);
bool verbose = false;
// Process the parsed arguments.
if (results.flag('help')) {
printUsage(argParser);
return;
}
if (results.flag('version')) {
print('dew version: $version');
return;
}
if (results.flag('verbose')) {
verbose = true;
}
// Act on the arguments provided.
print('Positional arguments: ${results.rest}');
if (verbose) {
print('[VERBOSE] All arguments: ${results.arguments}');
}
} on FormatException catch (e) {
// Print usage information if an invalid argument was provided.
print(e.message);
print('');
printUsage(argParser);
}
} }

View file

@ -1,5 +1,5 @@
name: dew name: dew
description: A sample command-line application with basic argument parsing. description: Command-line interface for the Dew project management tool.
version: 0.0.1 version: 0.0.1
# repository: https://github.com/my_org/my_repo # repository: https://github.com/my_org/my_repo
publish_to: none publish_to: none
@ -10,6 +10,12 @@ environment:
dependencies: dependencies:
args: ^2.7.0 args: ^2.7.0
dew_core:
path: ../core
dew_kanban:
path: ../kanban
dew_mcp:
path: ../mcp
dev_dependencies: dev_dependencies:
lints: ^6.0.0 lints: ^6.0.0

View file

@ -1,8 +1,3 @@
/// Support for doing something awesome.
///
/// More dartdocs go here.
library; library;
export 'src/dew_core_base.dart'; export 'src/dew_core_base.dart';
// TODO: Export any libraries intended for clients of this package.

View file

@ -1,6 +1,22 @@
// TODO: Put public facing types in this file. import 'package:args/command_runner.dart';
/// Checks if you are awesome. Spoiler: you are. /// Base class for all Dew CLI commands.
class Awesome { ///
bool get isAwesome => true; /// Feature packages extend this class to provide their commands, then register
/// them via [CommandRegistry] so the CLI can assemble them at startup.
abstract class DewCommand extends Command<void> {}
/// Holds the [DewCommand]s registered by feature packages.
///
/// The CLI creates an instance, passes it to each package's
/// `registerCommands` function, then iterates [commands] to populate a
/// [CommandRunner].
class CommandRegistry {
final List<DewCommand> _commands = [];
/// Adds [command] to the registry.
void register(DewCommand command) => _commands.add(command);
/// An unmodifiable view of all registered commands.
List<DewCommand> get commands => List.unmodifiable(_commands);
} }

View file

@ -1,5 +1,5 @@
name: dew_core name: dew_core
description: A starting point for Dart libraries or applications. description: Core shared types, interfaces, and configuration for the Dew project management tool.
version: 1.0.0 version: 1.0.0
# repository: https://github.com/my_org/my_repo # repository: https://github.com/my_org/my_repo
publish_to: none publish_to: none
@ -10,6 +10,7 @@ environment:
# Add regular dependencies here. # Add regular dependencies here.
dependencies: dependencies:
args: ^2.7.0
path: ^1.9.0 path: ^1.9.0
dev_dependencies: dev_dependencies:

View file

@ -1,16 +1,33 @@
import 'package:dew_core/dew_core.dart'; import 'package:dew_core/dew_core.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
void main() { // Minimal concrete command for testing the registry.
group('A group of tests', () { class _TestCommand extends DewCommand {
final awesome = Awesome(); @override
final String name = 'test-cmd';
@override
final String description = 'A test command.';
@override
Future<void> run() async {}
}
setUp(() { void main() {
// Additional setup goes here. group('CommandRegistry', () {
test('starts empty', () {
final registry = CommandRegistry();
expect(registry.commands, isEmpty);
}); });
test('First Test', () { test('register adds a command', () {
expect(awesome.isAwesome, isTrue); final registry = CommandRegistry();
registry.register(_TestCommand());
expect(registry.commands, hasLength(1));
expect(registry.commands.first.name, 'test-cmd');
});
test('commands list is unmodifiable', () {
final registry = CommandRegistry();
expect(() => registry.commands.add(_TestCommand()), throwsUnsupportedError);
}); });
}); });
} }

View file

@ -1,8 +1,11 @@
/// Support for doing something awesome.
///
/// More dartdocs go here.
library; library;
export 'src/dew_kanban_base.dart'; export 'src/dew_kanban_base.dart';
// TODO: Export any libraries intended for clients of this package. import 'package:dew_core/dew_core.dart';
import 'package:dew_kanban/src/dew_kanban_base.dart';
/// Registers all Kanban commands into [registry].
void registerCommands(CommandRegistry registry) {
registry.register(KanbanCommand());
}

View file

@ -1,6 +1,13 @@
// TODO: Put public facing types in this file. import 'package:dew_core/dew_core.dart';
/// Checks if you are awesome. Spoiler: you are. /// Top-level CLI command for all Kanban board operations.
class Awesome { class KanbanCommand extends DewCommand {
bool get isAwesome => true; @override
final String name = 'kanban';
@override
final String description = 'Manage the Kanban board.';
@override
Future<void> run() async => printUsage();
} }

View file

@ -1,5 +1,5 @@
name: dew_kanban name: dew_kanban
description: A starting point for Dart libraries or applications. description: Kanban board feature for the Dew project management tool. Implements McpToolProvider to expose board tools to the MCP server.
version: 1.0.0 version: 1.0.0
# repository: https://github.com/my_org/my_repo # repository: https://github.com/my_org/my_repo
publish_to: none publish_to: none
@ -10,6 +10,8 @@ environment:
# Add regular dependencies here. # Add regular dependencies here.
dependencies: dependencies:
dew_core:
path: ../core
path: ^1.9.0 path: ^1.9.0
dev_dependencies: dev_dependencies:

View file

@ -1,16 +1,19 @@
import 'package:dew_kanban/dew_kanban.dart'; import 'package:dew_kanban/dew_kanban.dart';
import 'package:dew_core/dew_core.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
void main() { void main() {
group('A group of tests', () { group('KanbanCommand', () {
final awesome = Awesome(); test('has correct name and description', () {
final cmd = KanbanCommand();
setUp(() { expect(cmd.name, 'kanban');
// Additional setup goes here. expect(cmd.description, isNotEmpty);
}); });
test('First Test', () { test('registerCommands adds kanban command to registry', () {
expect(awesome.isAwesome, isTrue); final registry = CommandRegistry();
registerCommands(registry);
expect(registry.commands.map((c) => c.name), contains('kanban'));
}); });
}); });
} }

7
packages/mcp/.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
# https://dart.dev/guides/libraries/private-files
# Created by `dart pub`
.dart_tool/
# Avoid committing pubspec.lock for library packages; see
# https://dart.dev/guides/libraries/private-files#pubspeclock.
pubspec.lock

View file

@ -0,0 +1,13 @@
library;
export 'src/dew_mcp_base.dart';
import 'package:dew_core/dew_core.dart';
import 'package:dew_mcp/src/dew_mcp_base.dart';
/// Registers all MCP commands into [registry].
void registerCommands(CommandRegistry registry) {
registry.register(McpCommand());
}
// TODO: Export any libraries intended for clients of this package.

View file

@ -0,0 +1,13 @@
import 'package:dew_core/dew_core.dart';
/// Top-level CLI command for MCP server operations.
class McpCommand extends DewCommand {
@override
final String name = 'mcp';
@override
final String description = 'Manage the MCP server.';
@override
Future<void> run() async => printUsage();
}

18
packages/mcp/pubspec.yaml Normal file
View file

@ -0,0 +1,18 @@
name: dew_mcp
description: MCP server for the Dew project management tool. Collects and serves tools registered by feature packages via the McpToolProvider interface.
version: 1.0.0
# repository: https://github.com/my_org/my_repo
publish_to: none
resolution: workspace
environment:
sdk: ^3.11.4
# Add regular dependencies here.
dependencies:
dew_core:
path: ../core
dev_dependencies:
lints: ^6.0.0
test: ^1.25.6

View file

@ -0,0 +1,19 @@
import 'package:dew_mcp/dew_mcp.dart';
import 'package:dew_core/dew_core.dart';
import 'package:test/test.dart';
void main() {
group('McpCommand', () {
test('has correct name and description', () {
final cmd = McpCommand();
expect(cmd.name, 'mcp');
expect(cmd.description, isNotEmpty);
});
test('registerCommands adds mcp command to registry', () {
final registry = CommandRegistry();
registerCommands(registry);
expect(registry.commands.map((c) => c.name), contains('mcp'));
});
});
}

View file

@ -11,6 +11,7 @@ workspace:
- packages/cli - packages/cli
- packages/core - packages/core
- packages/kanban - packages/kanban
- packages/mcp
dev_dependencies: dev_dependencies:
lints: ^6.0.0 lints: ^6.0.0
@ -27,3 +28,9 @@ melos:
format: format:
description: Format the workspace. description: Format the workspace.
run: dart format . run: dart format .
dew:
description: >-
Run the Dew CLI. Pass subcommands and args directly
(e.g. melos run dew kanban). Use 'help <command>' for usage
(e.g. melos run dew help kanban).
run: dart run packages/cli/bin/dew.dart