diff --git a/AGENTS.md b/AGENTS.md index 5cba66e79c3df483a3485122ac0a6449f34c2e58..fd491e6a7e774d6e8ddaa397f7fdc193f725b3bb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,158 +1,60 @@ -# AGENTS.md - -This file provides guidance to AI coding agents when working with code in this repository. - -## Build Commands - -All commands use `just` task runner: - -- **Development workflow**: `just` (runs fmt, lint, staticcheck, test, vuln, reuse) -- **Format code**: `just fmt` (uses gofumpt) -- **Lint**: `just lint` (uses golangci-lint) -- **Static analysis**: `just staticcheck` (uses staticcheck) -- **Test**: `just test` (runs all tests with verbose output) -- **Single test**: `go test -v ./path/to/package -run TestName` -- **Vulnerability check**: `just vuln` (uses govulncheck) -- **License compliance**: `just reuse` (REUSE specification) -- **Build**: `just build` (outputs to `./lunatask-mcp-server`, supports GOOS/GOARCH env vars) -- **Run**: `just run` (builds and runs with version injection) -- **Compress binary**: `just pack` (requires upx, run after build) -- **Clean**: `just clean` (removes binary) or `just clean-all` (removes binary and config) - -Version is injected at build time via git describe, so always use `just build` or `just run` rather than plain `go build`. - -## Licensing - -This project is REUSE compliant. All files must have SPDX headers: - -- **Code files** (`.go`): `SPDX-License-Identifier: AGPL-3.0-or-later` -- **Documentation** (`.md`, `.toml`): `SPDX-License-Identifier: CC0-1.0` -- **Copyright**: `SPDX-FileCopyrightText: Amolith ` - -Always include both lines in new files. - -## Architecture Overview - -This is an MCP (Model Context Protocol) server that exposes Lunatask API functionality to LLMs. The codebase uses a clean three-layer architecture: - -### Layer 1: Main Entry Point (`cmd/lunatask-mcp-server.go`) - -- Loads TOML configuration from `config.toml` (or custom path via `-c/--config`) -- Creates and configures the MCP SSE server (Server-Sent Events transport) -- Implements Provider interfaces (`AreaProvider`, `GoalProvider`, `HabitProvider`) that wrap config structs -- Registers all MCP tools with their handlers -- Validates configuration on startup (areas/goals must have names and IDs, timezone must be valid) - -### Layer 2: Tools Package (`tools/`) - -- Bridges MCP tool calls to Lunatask API client -- Handles MCP request/response formatting -- Performs pre-validation and argument transformation (e.g., string priorities → integers) -- Consumes Provider interfaces to access configuration without tight coupling -- Contains handlers for: timestamp parsing, area/goal listing, task CRUD, habit tracking - -### Layer 3: Lunatask Package (`lunatask/`) - -- Low-level HTTP client for Lunatask REST API (`https://api.lunatask.app/v1`) -- Defines request/response types with JSON tags -- Uses `go-playground/validator` for request validation -- Contains API methods for tasks (`CreateTask`, `UpdateTask`, `DeleteTask`) and habits (`TrackHabitActivity`) + -**Provider Interfaces**: The tools package defines `AreaProvider`, `GoalProvider`, and `HabitProvider` interfaces. The main package's config structs implement these, allowing tools to access config data without importing main or knowing config structure details. - -**String-to-Integer Mapping**: MCP tools accept human-readable strings that get translated to API integers: -- Priority: `"lowest"`=-2, `"low"`=-1, `"neutral"`=0, `"high"`=1, `"highest"`=2 -- Eisenhower matrix: `"uncategorised"`=0, `"both urgent and important"`=1, `"urgent, but not important"`=2, `"important, but not urgent"`=3, `"neither urgent nor important"`=4 - -**Natural Language Date Parsing**: Uses `github.com/ijt/go-anytime` with timezone support. The `get_timestamp` tool parses expressions like "tomorrow at 3pm" into RFC3339 timestamps. Timezone is configured in `config.toml` using IANA/Olson format (e.g., `America/New_York`). - -## Configuration System - -On first run, the server generates `config.toml` with placeholder values and exits. Users must: - -1. Add their Lunatask API access token -2. Configure areas with real IDs from Lunatask (areas can have nested goals) -3. Configure habits with real IDs -4. Set timezone in IANA format (e.g., `America/New_York`, not `EST`) -5. Optionally customize server host/port (defaults to `localhost:8080`) - -The config uses TOML (not JSON) per project philosophy. Example structure: - -```toml -access_token = "your-token" -timezone = "America/New_York" - -[server] -host = "localhost" -port = 8080 +# AGENTS.md -[[area]] -name = "Work" -id = "uuid-here" +MCP server exposing Lunatask (task/habit tracker) to LLMs via SSE. Primary use case: Home Assistant voice assistant says "remind me to call mom tomorrow" → HA's LLM calls this server's `create_task` tool → task appears in Lunatask. - [[area.goal]] - name = "Q1 Project" - id = "uuid-here" +## Commands -[[habit]] -name = "Exercise" -id = "uuid-here" +```sh +just # Run all checks (fmt, lint, staticcheck, test, vuln, reuse) +just build # Build with version info +just run # Build and run ``` -## Critical Implementation Details +## Architecture -### Goal Validation in Task Operations - -When creating or updating tasks with a `goal_id`, the tools package validates that the goal belongs to the specified area. This prevents associating tasks with goals from different areas, which Lunatask may reject or handle incorrectly. - -### Update vs Create Semantics - -`UpdateTask` reuses `CreateTaskRequest` type but has different semantics: -- Only provided fields are updated (partial updates) -- Empty strings for text fields (name, note, scheduled_on) clear existing values -- Empty strings for enum fields (status, motivation) also clear values -- The tools handler only sends fields present in MCP request arguments - -### Empty Package Files - -`lunatask/notes.go`, `lunatask/people.go`, and `lunatask/timeline.go` exist but are empty. These are placeholders for future Lunatask API features. Don't remove them without checking if they're imported elsewhere. - -### MCP Tool Workflow Dependencies +``` +cmd/lunatask-mcp-server.go → Config, tool registration, SSE server +tools/ → MCP tool handlers +lunatask/ → HTTP client for Lunatask API +``` -Several tools must be called in specific order: -1. `list_areas_and_goals` → provides IDs for `create_task`/`update_task` -2. `get_timestamp` → provides formatted timestamps for task scheduling and habit tracking -3. `list_habits_and_activities` → provides IDs for `track_habit_activity` +**Data flow**: SSE request → MCP server → tool handler → lunatask client → Lunatask API -The MCP tool descriptions document these workflows, and the LLM system prompts (in README.md) reinforce the pattern. +**Domain model**: Areas contain Goals. Habits are separate. Tasks belong to an Area and optionally a Goal. -### Validation Happens at Two Levels +## Adding New Functionality -1. **Tools layer**: Type checking, area/goal relationship validation, string-to-int translation -2. **Lunatask layer**: go-playground/validator tags enforce API constraints (UUID format, string lengths, numeric ranges, enum values) +**New MCP tool**: +1. Add handler method to `tools/` (on `Handlers` struct) +2. Register tool with `mcpServer.AddTool()` in `cmd/lunatask-mcp-server.go` -This prevents invalid requests from reaching the API and provides better error messages. +**New API endpoint**: Add method to `lunatask/Client` with request/response types -### Version Injection +## Key Patterns -The `version` variable in main is set via ldflags during build: `-ldflags "-X main.version=..."`. The justfile extracts version from `git describe --long`. Don't hardcode version strings; if version is empty, it displays a helpful message about using `just build`. +- **Tool descriptions are prompts**: The verbose `mcp.WithDescription()` strings guide the *calling* LLM's behavior—they explain workflows, valid values, and when to use each tool +- **Error returns**: Handlers return `(result, nil)` with `IsError: true`—application errors go in the result, not the Go error +- **Enum translation**: Human strings (`"high"`) → API integers (`1`) in handlers before calling client +- **Provider interfaces**: `tools/` defines interfaces (`AreaProvider`, etc.) that `cmd/` types implement, keeping packages decoupled -## Testing Considerations +## Gotchas -- Tests should use `go test -v ./...` to run all packages -- Mock the HTTP client in lunatask package tests (set `Client.HTTPClient` to custom `*http.Client`) -- Mock Provider interfaces in tools package tests -- Timezone-dependent tests should explicitly set timezone in config -- Natural language date parsing is non-deterministic (relative to "now"), so test with absolute dates or mock time +- Area/goal/habit IDs are static in config, not fetched—Lunatask API has no list endpoint +- `CreateTask` returns `(nil, nil)` on HTTP 204 (task already exists)—not an error +- `update_task` MCP tool requires `name` even for partial updates +- No tests exist yet +- All files need SPDX headers (`just reuse` checks this) -## Related Documentation +## Contributing -The README.md contains extensive documentation about: -- MCP tool descriptions and parameters (authoritative reference for tool behavior) -- Example LLM system prompts showing real-world usage patterns -- User-specific workflow rules (area workflows, estimate requirements, status defaults) -- Contribution workflow using pr.pico.sh (SSH-based patches, no GitHub account needed) +Patches via [pr.pico.sh](https://pr.pico.sh/r/amolith/llm-projects): -When modifying tools, ensure MCP descriptions in `cmd/lunatask-mcp-server.go` stay synchronized with actual behavior. +```sh +git format-patch origin/main --stdout | ssh pr.pico.sh pr create amolith/llm-projects +```