diff --git a/.markdownlint-cli2.mjs b/.markdownlint-cli2.mjs new file mode 100644 index 0000000..e84bb0c --- /dev/null +++ b/.markdownlint-cli2.mjs @@ -0,0 +1,11 @@ +export default { + globs: ['**/*.md'], + gitignore: true, + config: { + MD013: false, + MD040: true, + MD060: { + style: 'aligned', + }, + }, +}; diff --git a/docs/config.md b/docs/config.md index e69de29..96297fb 100644 --- a/docs/config.md +++ b/docs/config.md @@ -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` diff --git a/docs/features/mcp.md b/docs/features/mcp.md index fd6a786..2d64e16 100644 --- a/docs/features/mcp.md +++ b/docs/features/mcp.md @@ -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 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. diff --git a/docs/index.md b/docs/index.md index f28b4c3..521e27b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,3 +1,25 @@ # 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. diff --git a/packages/cli/bin/dew.dart b/packages/cli/bin/dew.dart index 1496a4e..2913574 100644 --- a/packages/cli/bin/dew.dart +++ b/packages/cli/bin/dew.dart @@ -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 main(List args) async { + final registry = CommandRegistry(); + kanban.registerCommands(registry); + mcp.registerCommands(registry); -ArgParser buildParser() { - return ArgParser() - ..addFlag( - 'help', - abbr: 'h', - negatable: false, - help: 'Print this usage information.', - ) - ..addFlag( - 'verbose', - abbr: 'v', - negatable: false, - help: 'Show additional command output.', - ) - ..addFlag('version', negatable: false, help: 'Print the tool version.'); -} + final runner = CommandRunner( + 'dew', + 'A project management tool.', + ); -void printUsage(ArgParser argParser) { - print('Usage: dart dew.dart [arguments]'); - print(argParser.usage); -} - -void main(List 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); + for (final command in registry.commands) { + runner.addCommand(command); } + + await runner.run(args); } diff --git a/packages/cli/pubspec.yaml b/packages/cli/pubspec.yaml index f7c57ee..f812929 100644 --- a/packages/cli/pubspec.yaml +++ b/packages/cli/pubspec.yaml @@ -1,5 +1,5 @@ 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 # repository: https://github.com/my_org/my_repo publish_to: none @@ -10,6 +10,12 @@ environment: dependencies: args: ^2.7.0 + dew_core: + path: ../core + dew_kanban: + path: ../kanban + dew_mcp: + path: ../mcp dev_dependencies: lints: ^6.0.0 diff --git a/packages/core/lib/dew_core.dart b/packages/core/lib/dew_core.dart index 3637a82..9376088 100644 --- a/packages/core/lib/dew_core.dart +++ b/packages/core/lib/dew_core.dart @@ -1,8 +1,3 @@ -/// Support for doing something awesome. -/// -/// More dartdocs go here. library; export 'src/dew_core_base.dart'; - -// TODO: Export any libraries intended for clients of this package. diff --git a/packages/core/lib/src/dew_core_base.dart b/packages/core/lib/src/dew_core_base.dart index e8a6f15..abf6f98 100644 --- a/packages/core/lib/src/dew_core_base.dart +++ b/packages/core/lib/src/dew_core_base.dart @@ -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. -class Awesome { - bool get isAwesome => true; +/// Base class for all Dew CLI commands. +/// +/// 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 {} + +/// 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 _commands = []; + + /// Adds [command] to the registry. + void register(DewCommand command) => _commands.add(command); + + /// An unmodifiable view of all registered commands. + List get commands => List.unmodifiable(_commands); } diff --git a/packages/core/pubspec.yaml b/packages/core/pubspec.yaml index f83b20c..b85c23a 100644 --- a/packages/core/pubspec.yaml +++ b/packages/core/pubspec.yaml @@ -1,5 +1,5 @@ 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 # repository: https://github.com/my_org/my_repo publish_to: none @@ -10,6 +10,7 @@ environment: # Add regular dependencies here. dependencies: + args: ^2.7.0 path: ^1.9.0 dev_dependencies: diff --git a/packages/core/test/dew_core_test.dart b/packages/core/test/dew_core_test.dart index c95e82a..f8ca8e5 100644 --- a/packages/core/test/dew_core_test.dart +++ b/packages/core/test/dew_core_test.dart @@ -1,16 +1,33 @@ import 'package:dew_core/dew_core.dart'; import 'package:test/test.dart'; -void main() { - group('A group of tests', () { - final awesome = Awesome(); +// Minimal concrete command for testing the registry. +class _TestCommand extends DewCommand { + @override + final String name = 'test-cmd'; + @override + final String description = 'A test command.'; + @override + Future run() async {} +} - setUp(() { - // Additional setup goes here. +void main() { + group('CommandRegistry', () { + test('starts empty', () { + final registry = CommandRegistry(); + expect(registry.commands, isEmpty); }); - test('First Test', () { - expect(awesome.isAwesome, isTrue); + test('register adds a command', () { + 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); }); }); } diff --git a/packages/kanban/lib/dew_kanban.dart b/packages/kanban/lib/dew_kanban.dart index 07cae27..83ceff2 100644 --- a/packages/kanban/lib/dew_kanban.dart +++ b/packages/kanban/lib/dew_kanban.dart @@ -1,8 +1,11 @@ -/// Support for doing something awesome. -/// -/// More dartdocs go here. library; 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()); +} diff --git a/packages/kanban/lib/src/dew_kanban_base.dart b/packages/kanban/lib/src/dew_kanban_base.dart index e8a6f15..31cf860 100644 --- a/packages/kanban/lib/src/dew_kanban_base.dart +++ b/packages/kanban/lib/src/dew_kanban_base.dart @@ -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. -class Awesome { - bool get isAwesome => true; +/// Top-level CLI command for all Kanban board operations. +class KanbanCommand extends DewCommand { + @override + final String name = 'kanban'; + + @override + final String description = 'Manage the Kanban board.'; + + @override + Future run() async => printUsage(); } diff --git a/packages/kanban/pubspec.yaml b/packages/kanban/pubspec.yaml index 56ea559..2997417 100644 --- a/packages/kanban/pubspec.yaml +++ b/packages/kanban/pubspec.yaml @@ -1,5 +1,5 @@ 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 # repository: https://github.com/my_org/my_repo publish_to: none @@ -10,6 +10,8 @@ environment: # Add regular dependencies here. dependencies: + dew_core: + path: ../core path: ^1.9.0 dev_dependencies: diff --git a/packages/kanban/test/dew_kanban_test.dart b/packages/kanban/test/dew_kanban_test.dart index 072242f..6d163bb 100644 --- a/packages/kanban/test/dew_kanban_test.dart +++ b/packages/kanban/test/dew_kanban_test.dart @@ -1,16 +1,19 @@ import 'package:dew_kanban/dew_kanban.dart'; +import 'package:dew_core/dew_core.dart'; import 'package:test/test.dart'; void main() { - group('A group of tests', () { - final awesome = Awesome(); - - setUp(() { - // Additional setup goes here. + group('KanbanCommand', () { + test('has correct name and description', () { + final cmd = KanbanCommand(); + expect(cmd.name, 'kanban'); + expect(cmd.description, isNotEmpty); }); - test('First Test', () { - expect(awesome.isAwesome, isTrue); + test('registerCommands adds kanban command to registry', () { + final registry = CommandRegistry(); + registerCommands(registry); + expect(registry.commands.map((c) => c.name), contains('kanban')); }); }); } diff --git a/packages/mcp/.gitignore b/packages/mcp/.gitignore new file mode 100644 index 0000000..3cceda5 --- /dev/null +++ b/packages/mcp/.gitignore @@ -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 diff --git a/packages/mcp/lib/dew_mcp.dart b/packages/mcp/lib/dew_mcp.dart new file mode 100644 index 0000000..9a5a626 --- /dev/null +++ b/packages/mcp/lib/dew_mcp.dart @@ -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. diff --git a/packages/mcp/lib/src/dew_mcp_base.dart b/packages/mcp/lib/src/dew_mcp_base.dart new file mode 100644 index 0000000..3b4a66a --- /dev/null +++ b/packages/mcp/lib/src/dew_mcp_base.dart @@ -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 run() async => printUsage(); +} diff --git a/packages/mcp/pubspec.yaml b/packages/mcp/pubspec.yaml new file mode 100644 index 0000000..3d0fac7 --- /dev/null +++ b/packages/mcp/pubspec.yaml @@ -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 diff --git a/packages/mcp/test/mcp_test.dart b/packages/mcp/test/mcp_test.dart new file mode 100644 index 0000000..e12515b --- /dev/null +++ b/packages/mcp/test/mcp_test.dart @@ -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')); + }); + }); +} diff --git a/pubspec.yaml b/pubspec.yaml index d3b41d8..083efb5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,7 @@ workspace: - packages/cli - packages/core - packages/kanban + - packages/mcp dev_dependencies: lints: ^6.0.0 @@ -27,3 +28,9 @@ melos: format: description: Format the workspace. run: dart format . + dew: + description: >- + Run the Dew CLI. Pass subcommands and args directly + (e.g. melos run dew kanban). Use 'help ' for usage + (e.g. melos run dew help kanban). + run: dart run packages/cli/bin/dew.dart