refactor: resolve isolated lint issues

Amolith created

- Add package doc comments
- Rename unused parameters to _
- Define static ErrTimezoneNotConfigured
- Rename short variables (b→builder, i→areaIdx/goalIdx)
- Fix exitAfterDefer by closing file before fatal
- Switch fmt.Print* to slog (fixes potential stdio conflict)

Assisted-by: Claude Sonnet 4 via Crush

Change summary

cmd/lunatask-mcp-server.go | 42 ++++++++++++++++++++++-----------------
tools/habits.go            |  9 ++++---
tools/tools.go             | 20 ++++++++++++------
3 files changed, 42 insertions(+), 29 deletions(-)

Detailed changes

cmd/lunatask-mcp-server.go 🔗

@@ -2,12 +2,13 @@
 //
 // SPDX-License-Identifier: AGPL-3.0-or-later
 
+// lunatask-mcp-server exposes Lunatask to LLMs via the Model Context Protocol.
 package main
 
 import (
 	"context"
-	"fmt"
 	"log"
+	"log/slog"
 	"net"
 	"os"
 	"strconv"
@@ -85,18 +86,18 @@ var version = ""
 func main() {
 	configPath := "./config.toml"
 
-	for i, arg := range os.Args {
+	for argIdx, arg := range os.Args {
 		switch arg {
 		case "-v", "--version":
 			if version == "" {
 				version = "unknown, build with `just build` or copy/paste the build command from ./justfile"
 			}
 
-			fmt.Println("lunatask-mcp-server:", version)
+			slog.Info("version", "name", "lunatask-mcp-server", "version", version)
 			os.Exit(0)
 		case "-c", "--config":
-			if i+1 < len(os.Args) {
-				configPath = os.Args[i+1]
+			if argIdx+1 < len(os.Args) {
+				configPath = os.Args[argIdx+1]
 			}
 		}
 	}
@@ -114,14 +115,14 @@ func main() {
 		log.Fatalf("Config file must provide access_token and at least one area.")
 	}
 
-	for i, area := range config.Areas {
+	for areaIdx, area := range config.Areas {
 		if area.Name == "" || area.ID == "" {
-			log.Fatalf("All areas (areas[%d]) must have both a name and id", i)
+			log.Fatalf("All areas (areas[%d]) must have both a name and id", areaIdx)
 		}
 
-		for j, goal := range area.Goals {
+		for goalIdx, goal := range area.Goals {
 			if goal.Name == "" || goal.ID == "" {
-				log.Fatalf("All goals (areas[%d].goals[%d]) must have both a name and id", i, j)
+				log.Fatalf("All goals (areas[%d].goals[%d]) must have both a name and id", areaIdx, goalIdx)
 			}
 		}
 	}
@@ -156,25 +157,25 @@ func NewMCPServer(appConfig *Config) *server.MCPServer {
 	hooks := &server.Hooks{}
 
 	hooks.AddBeforeAny(func(ctx context.Context, id any, method mcp.MCPMethod, message any) {
-		fmt.Printf("beforeAny: %s, %v, %v\n", method, id, message)
+		slog.Debug("beforeAny", "method", method, "id", id, "message", message)
 	})
 	hooks.AddOnSuccess(func(ctx context.Context, id any, method mcp.MCPMethod, message any, result any) {
-		fmt.Printf("onSuccess: %s, %v, %v, %v\n", method, id, message, result)
+		slog.Debug("onSuccess", "method", method, "id", id, "message", message, "result", result)
 	})
 	hooks.AddOnError(func(ctx context.Context, id any, method mcp.MCPMethod, message any, err error) {
-		fmt.Printf("onError: %s, %v, %v, %v\n", method, id, message, err)
+		slog.Error("onError", "method", method, "id", id, "message", message, "error", err)
 	})
 	hooks.AddBeforeInitialize(func(ctx context.Context, id any, message *mcp.InitializeRequest) {
-		fmt.Printf("beforeInitialize: %v, %v\n", id, message)
+		slog.Debug("beforeInitialize", "id", id, "message", message)
 	})
 	hooks.AddAfterInitialize(func(ctx context.Context, id any, message *mcp.InitializeRequest, result *mcp.InitializeResult) {
-		fmt.Printf("afterInitialize: %v, %v, %v\n", id, message, result)
+		slog.Debug("afterInitialize", "id", id, "message", message, "result", result)
 	})
 	hooks.AddAfterCallTool(func(ctx context.Context, id any, message *mcp.CallToolRequest, result *mcp.CallToolResult) {
-		fmt.Printf("afterCallTool: %v, %v, %v\n", id, message, result)
+		slog.Debug("afterCallTool", "id", id, "message", message, "result", result)
 	})
 	hooks.AddBeforeCallTool(func(ctx context.Context, id any, message *mcp.CallToolRequest) {
-		fmt.Printf("beforeCallTool: %v, %v\n", id, message)
+		slog.Debug("beforeCallTool", "id", id, "message", message)
 	})
 
 	mcpServer := server.NewMCPServer(
@@ -364,12 +365,17 @@ func createDefaultConfigFile(configPath string) {
 	if err != nil {
 		log.Fatalf("Failed to create default config at %s: %v", configPath, err)
 	}
-	defer closeFile(file)
 
 	if err := toml.NewEncoder(file).Encode(defaultConfig); err != nil {
+		closeFile(file)
 		log.Fatalf("Failed to encode default config to %s: %v", configPath, err)
 	}
 
-	fmt.Printf("A default config has been created at %s.\nPlease edit it to provide your Lunatask access token, correct area/goal IDs, and your timezone (IANA/Olson format, e.g. 'America/New_York'), then restart the server.\n", configPath)
+	closeFile(file)
+
+	slog.Info("default config created",
+		"path", configPath,
+		"next_steps", "edit the config to provide your Lunatask access token, area/goal IDs, and timezone (IANA format, e.g. 'America/New_York'), then restart",
+	)
 	os.Exit(1)
 }

tools/habits.go 🔗

@@ -2,6 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0-or-later
 
+// Package tools provides MCP tool handlers for Lunatask operations.
 package tools
 
 import (
@@ -14,17 +15,17 @@ import (
 )
 
 // HandleListHabitsAndActivities handles the list_habits_and_activities tool call.
-func (h *Handlers) HandleListHabitsAndActivities(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
-	var b strings.Builder
+func (h *Handlers) HandleListHabitsAndActivities(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+	var builder strings.Builder
 	for _, habit := range h.config.Habits {
-		fmt.Fprintf(&b, "- %s: %s\n", habit.GetName(), habit.GetID())
+		fmt.Fprintf(&builder, "- %s: %s\n", habit.GetName(), habit.GetID())
 	}
 
 	return &mcp.CallToolResult{
 		Content: []mcp.Content{
 			mcp.TextContent{
 				Type: "text",
-				Text: b.String(),
+				Text: builder.String(),
 			},
 		},
 	}, nil

tools/tools.go 🔗

@@ -15,6 +15,12 @@ import (
 	"github.com/mark3labs/mcp-go/mcp"
 )
 
+// ErrTimezoneNotConfigured is returned when the timezone config value is empty.
+var ErrTimezoneNotConfigured = errors.New(
+	"timezone is not configured; please set the 'timezone' value in your config file " +
+		"(e.g. 'UTC' or 'America/New_York')",
+)
+
 // AreaProvider defines the interface for accessing area data.
 type AreaProvider interface {
 	GetName() string
@@ -63,7 +69,7 @@ func reportMCPError(msg string) (*mcp.CallToolResult, error) {
 // LoadLocation loads a timezone location string, returning a *time.Location or error.
 func LoadLocation(timezone string) (*time.Location, error) {
 	if timezone == "" {
-		return nil, errors.New("timezone is not configured; please set the 'timezone' value in your config file (e.g. 'UTC' or 'America/New_York')")
+		return nil, ErrTimezoneNotConfigured
 	}
 
 	loc, err := time.LoadLocation(timezone)
@@ -75,7 +81,7 @@ func LoadLocation(timezone string) (*time.Location, error) {
 }
 
 // HandleGetTimestamp handles the get_timestamp tool call.
-func (h *Handlers) HandleGetTimestamp(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+func (h *Handlers) HandleGetTimestamp(_ context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 	natLangDate, ok := request.Params.Arguments["natural_language_date"].(string)
 	if !ok || natLangDate == "" {
 		return reportMCPError("Missing or invalid required argument: natural_language_date")
@@ -102,13 +108,13 @@ func (h *Handlers) HandleGetTimestamp(ctx context.Context, request mcp.CallToolR
 }
 
 // HandleListAreasAndGoals handles the list_areas_and_goals tool call.
-func (h *Handlers) HandleListAreasAndGoals(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
-	var b strings.Builder
+func (h *Handlers) HandleListAreasAndGoals(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+	var builder strings.Builder
 	for _, area := range h.config.Areas {
-		fmt.Fprintf(&b, "- %s: %s\n", area.GetName(), area.GetID())
+		fmt.Fprintf(&builder, "- %s: %s\n", area.GetName(), area.GetID())
 
 		for _, goal := range area.GetGoals() {
-			fmt.Fprintf(&b, "  - %s: %s\n", goal.GetName(), goal.GetID())
+			fmt.Fprintf(&builder, "  - %s: %s\n", goal.GetName(), goal.GetID())
 		}
 	}
 
@@ -116,7 +122,7 @@ func (h *Handlers) HandleListAreasAndGoals(ctx context.Context, request mcp.Call
 		Content: []mcp.Content{
 			mcp.TextContent{
 				Type: "text",
-				Text: b.String(),
+				Text: builder.String(),
 			},
 		},
 	}, nil