docs(agents): condense and restructure guidelines

Amolith created

Simplify AGENTS.md from 158 to 60 lines while preserving essential
information. Focus on actionable guidance rather than implementation
details. Add SPDX headers for REUSE compliance.

Assisted-by: Claude Opus 4.5 via Crush

Change summary

AGENTS.md | 178 ++++++++++++--------------------------------------------
1 file changed, 40 insertions(+), 138 deletions(-)

Detailed changes

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 <amolith@secluded.site>`
-
-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`)
+<!--
+SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
 
-### Key Design Patterns
+SPDX-License-Identifier: CC0-1.0
+-->
 
-**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
+```