Skip to content

Latest commit

 

History

History
586 lines (442 loc) · 16.2 KB

File metadata and controls

586 lines (442 loc) · 16.2 KB

Automatic Time Tracking

← Back to Index


Introduced in v7.1.0 - Automatically track work session duration and log hours to HacknPlan without manual calculation.

Table of Contents

  1. Quick Start
  2. How It Works
  3. Configuration
  4. Usage Examples
  5. Edge Cases & Behavior
  6. Best Practices
  7. Troubleshooting

Quick Start

// 1. Start working on a task
await callTool('start_task', {
  workItemId: 123,
  comment: 'Starting implementation of OAuth authentication'
});
// ✅ Session started

// 2. Work on the task for 2.5 hours...

// 3. Complete the task
await callTool('complete_task', {
  workItemId: 123,
  comment: 'OAuth complete. All tests passing.'
});
// ✅ Automatically logged 2.5h to HacknPlan

That's it! No manual time calculation required.

Key Features

  • 🕒 Automatic Duration Tracking - Records start time, calculates elapsed time on completion
  • 💾 Persistent Storage - Sessions survive server restarts via .cache/time-tracking.json
  • 🔒 Session Limits - 4-hour maximum for automatic logging (configurable)
  • 📊 Multiple Concurrent Tasks - Track several tasks independently
  • Zero Configuration - Works automatically when using workflow shortcuts

How It Works

1. Start Tracking

When you call start_task, the server:

  1. Records the current timestamp (Date.now()) in the time tracking cache
  2. Persists the entry to .cache/time-tracking.json (survives restarts)
  3. Marks the session as 'active'
await callTool('start_task', {
  workItemId: 123,
  comment: 'Starting implementation of user authentication'
});
// ✅ Time tracking session started at 2025-12-21T10:30:00Z

Behind the scenes:

{
  "version": 1,
  "lastSaved": "2025-12-21T10:30:00.000Z",
  "entries": [
    {
      "projectId": 230954,
      "workItemId": 123,
      "startedAtMs": 1734781800000,
      "lastActivityAtMs": 1734781800000,
      "accumulatedHours": 0,
      "status": "active"
    }
  ]
}

2. Work on Task

The tracking session remains active while you work. The cache:

  • ✅ Persists to disk every 100ms after changes (debounced)
  • ✅ Performs periodic saves every 5 minutes
  • ✅ Survives server restarts (auto-loads on first access)
  • ✅ Has 7-day TTL for active entries

3. Complete and Auto-Log

When you call complete_task without hoursWorked:

  1. Retrieves the active session from cache
  2. Calculates elapsed time: Date.now() - startedAtMs
  3. Converts to hours: elapsedMs / 3600000
  4. Checks 4-hour limit (configurable via MAX_AUTO_LOG_HOURS)
  5. If within limit: automatically logs the hours to HacknPlan
  6. If exceeded: returns warning, requires manual hoursWorked
// Scenario: 2.5 hours elapsed since start_task
await callTool('complete_task', {
  workItemId: 123,
  comment: 'Authentication complete, all tests passing'
});

// Response:
{
  "workItem": { ... },
  "workSession": {
    "hours": 2.5,
    "date": "2025-12-21"
  },
  "timeTracking": {
    "autoLogged": true,
    "hours": 2.5,
    "elapsedMs": 9000000,
    "elapsedFormatted": "2h 30m"
  },
  "success": true
}
// ✅ Automatically logged 2.5h to HacknPlan

Configuration

Environment Variables

Variable Default Description
MAX_AUTO_LOG_HOURS 4 Maximum hours for automatic logging. Sessions exceeding this require manual hoursWorked parameter.
HACKNPLAN_TIME_TRACKING_CACHE_SIZE 500 Maximum number of tracked sessions in cache (LRU eviction).

Why 4-hour limit? Prevents accidental logging of unrealistic time if you forget to complete a task overnight or over a weekend.

Example Configuration

# In ~/.claude.json
{
  "mcpServers": {
    "hacknplan": {
      "env": {
        "MAX_AUTO_LOG_HOURS": "8",
        "HACKNPLAN_TIME_TRACKING_CACHE_SIZE": "1000"
      }
    }
  }
}

Or via shell:

export MAX_AUTO_LOG_HOURS=8
export HACKNPLAN_TIME_TRACKING_CACHE_SIZE=1000
node dist/index.js

Cache Persistence

  • Location: .cache/time-tracking.json (relative to server working directory)
  • Format: JSON with schema version for future migrations
  • Write Strategy: Atomic writes (.tmp → rename) for crash safety
  • Auto-Save: Debounced 100ms after modifications
  • Periodic Save: Every 5 minutes (prevents data loss during light activity)
  • Load Strategy: Non-blocking on first cache access

TTL (Time to Live)

Status TTL Purpose
Active 7 days Allows resuming long-running tasks
Completed 30 days Keeps history for reports/auditing

Expired entries are automatically removed when accessed.


Usage Examples

Basic Workflow

// 1. Start working on a task
await callTool('start_task', {
  workItemId: 123,
  comment: 'Starting implementation using bcrypt + nodemailer'
});
// ✅ Session started

// ... work on the task for 3 hours ...

// 2. Complete the task (auto-logs 3 hours)
await callTool('complete_task', {
  workItemId: 123,
  comment: 'Password reset complete. Email delivery verified.'
});
// ✅ Automatically logged 3h

Manual Time Override

Use hoursWorked to bypass auto-tracking and specify exact hours:

// Scenario: Long session (>4h) or need specific hours
await callTool('complete_task', {
  workItemId: 123,
  hoursWorked: 6.5,  // Override auto-calculation
  comment: 'Complex refactoring completed'
});
// ✅ Logs exactly 6.5h (ignores tracked session)

Multiple Sessions (Accumulated Hours)

The cache supports multiple work sessions on the same task:

// Day 1: Work for 2 hours
await callTool('start_task', { workItemId: 123 });
// ... 2 hours later ...
await callTool('complete_task', { workItemId: 123 });
// ✅ Logged 2h

// Day 2: Resume work for another 3 hours
await callTool('start_task', { workItemId: 123 });  // Reactivates entry
// ... 3 hours later ...
await callTool('complete_task', { workItemId: 123 });
// ✅ Logged 3h (total: 5h across both sessions)

Note: Accumulated hours are tracked in the cache entry's accumulatedHours field but each complete_task only logs the current session duration.


Session Exceeds Limit

// Scenario: Started task, worked for 5 hours (exceeds 4h limit)
await callTool('complete_task', {
  workItemId: 123
  // No hoursWorked parameter
});

// Response:
{
  "workItem": { "isCompleted": true, ... },
  "timeTracking": {
    "autoLogged": false,
    "elapsedMs": 18000000,
    "elapsedFormatted": "5h 0m",
    "limitExceeded": true,
    "warning": "Elapsed time 5h 0m exceeds 4h limit. Task completed without auto-logging hours."
  },
  "success": true
}
// ⚠️ Task completed but NO hours logged (manual log required)

Fix:

// Manually log the hours
await callTool('log_work_session', {
  workItemId: 123,
  hours: 5,
  description: 'Complex bug investigation and fix'
});

Multi-Day Tasks

For tasks spanning multiple days, complete and restart daily for accurate per-day tracking:

// Day 1: Work 3 hours
await callTool('start_task', { workItemId: 123 });
// ... 3 hours later ...
await callTool('complete_task', { workItemId: 123 });  // Logs 3h on Day 1

// Day 2: Work 4 hours
await callTool('start_task', { workItemId: 123 });
// ... 4 hours later ...
await callTool('complete_task', { workItemId: 123 });  // Logs 4h on Day 2

// Day 3: Finish task (2 hours)
await callTool('start_task', { workItemId: 123 });
// ... 2 hours later ...
await callTool('complete_task', {
  workItemId: 123,
  moveToStage: 4  // Move to "Complete" stage
});  // Logs 2h on Day 3, marks task complete

Benefit: Accurate per-day time logs, better reporting.


Multiple Concurrent Tasks

Track multiple tasks independently:

// Start both tasks
await callTool('start_task', { workItemId: 123 });  // Backend task
await callTool('start_task', { workItemId: 456 });  // Frontend task

// Complete backend (logs time for task 123 only)
await callTool('complete_task', { workItemId: 123 });

// Complete frontend (logs time for task 456 only)
await callTool('complete_task', { workItemId: 456 });

Each task maintains its own independent session timer.


Edge Cases & Behavior

1. start_task Called Multiple Times

Behavior: Resets the session timer to current time.

await callTool('start_task', { workItemId: 123 });  // Started at 10:00 AM

// ... user interruption ...

await callTool('start_task', { workItemId: 123 });  // Restarted at 2:00 PM
// ✅ Previous session discarded, new start time is 2:00 PM

Use Case: Handling interruptions or switching context.


2. complete_task Without Prior start_task

Behavior: No auto-logging occurs (no error thrown).

// Never called start_task
await callTool('complete_task', {
  workItemId: 123,
  comment: 'Task completed'
  // No hoursWorked
});

// Response:
{
  "workItem": { "isCompleted": true, ... },
  "timeTracking": {
    "autoLogged": false
    // No hours, no warning
  },
  "success": true
}
// ✅ Task completed, but no hours logged

Fix: Either call start_task first, or provide manual hoursWorked.


3. Server Restart Mid-Session

Behavior: Session persists and resumes after restart.

// Before restart:
await callTool('start_task', { workItemId: 123 });  // 10:00 AM

// *** Server crashes and restarts at 10:30 AM ***

// After restart:
await callTool('complete_task', { workItemId: 123 });  // 11:00 AM
// ✅ Auto-logs 1h (elapsed time from original start at 10:00 AM)

Implementation: Cache auto-loads from .cache/time-tracking.json on first access.


4. Cache Eviction (LRU)

Behavior: Least recently used entries evicted when cache reaches capacity.

  • Capacity: 500 entries by default (HACKNPLAN_TIME_TRACKING_CACHE_SIZE)
  • Eviction: Oldest entry (first in Map iteration order)
  • Warning: No warning logged for evicted entries

Mitigation:

  • Increase HACKNPLAN_TIME_TRACKING_CACHE_SIZE for larger teams
  • Complete tasks regularly to free up cache space
  • Eviction primarily affects very old or inactive entries

5. Time Zone Considerations

Behavior: All timestamps use UTC milliseconds (Date.now()).

  • startedAtMs: UTC timestamp when session started
  • lastActivityAtMs: UTC timestamp of last activity
  • Work log date: Server's local date when complete_task called

Example:

// Server in PST (UTC-8), task started at 5:00 PM local time
await callTool('start_task', { workItemId: 123 });
// Stores: startedAtMs = 1734825600000 (2025-12-21T01:00:00Z UTC)

// Completed next day at 9:00 AM local time (4h elapsed)
await callTool('complete_task', { workItemId: 123 });
// Logs: 4h on date 2025-12-21 (server local date)

Best Practices

When to Use Auto-Tracking vs Manual Hours

Use Auto-Tracking (no hoursWorked):

  • ✅ Focused work sessions under 4 hours
  • ✅ Uninterrupted task completion
  • ✅ Real-time tracking for accurate hours
  • ✅ Simple "start → work → complete" workflows

Use Manual Hours (explicit hoursWorked):

  • ✅ Sessions exceeding 4 hours
  • ✅ Multi-day tasks with scattered work
  • ✅ Retrospective time logging (completing tasks from memory)
  • ✅ Batch completions where exact hours are known

Handling Interrupted Work

Option A: Restart the session

// Started at 10 AM, interrupted at 11 AM
await callTool('start_task', { workItemId: 123 });  // Resets timer
// Resume at 2 PM
await callTool('start_task', { workItemId: 123 });  // Resets again
// Complete at 4 PM
await callTool('complete_task', { workItemId: 123 });
// ✅ Logs 2h (only counts 2 PM → 4 PM)

Option B: Complete and restart

// First session: 10 AM - 11 AM
await callTool('complete_task', { workItemId: 123 });  // Logs 1h

// Second session: 2 PM - 4 PM
await callTool('start_task', { workItemId: 123 });
await callTool('complete_task', { workItemId: 123 });  // Logs 2h

Session Management Tips

  • Check active sessions: Use get_my_current_tasks to see tasks with active tracking
  • Avoid multiple active sessions: Complete or abandon old sessions before starting new ones
  • Use comments: Add context when starting/completing tasks for better work history
  • Monitor limits: If frequently hitting 4h limit, consider increasing MAX_AUTO_LOG_HOURS

Cache Maintenance

The cache is self-maintaining, but you can monitor it:

  • Active entries expire after 7 days - Long-running tasks should be completed regularly
  • Completed entries expire after 30 days - Historical data eventually purges
  • LRU eviction at capacity - Increase HACKNPLAN_TIME_TRACKING_CACHE_SIZE for larger teams

No manual cleanup required - the cache handles expiration and eviction automatically.


Troubleshooting

Issue Cause Solution
Hours not logged on complete_task No prior start_task call Call start_task before working, or provide hoursWorked
"Exceeds 4h limit" warning Session longer than MAX_AUTO_LOG_HOURS Increase env var or use manual hoursWorked parameter
Session lost after restart Disk write failure (ENOSPC, permissions) Check .cache/ directory permissions and disk space
Incorrect elapsed time Multiple start_task calls reset timer Use single start_task per session, or track time externally
Cache file corrupted Server crash during write File auto-backed up (.corrupt-<timestamp>), cache starts fresh

Common Questions

Q: Hours not logged on complete_task?

  • Ensure you called start_task first
  • Check if session exceeded 4h limit (use manual hoursWorked if so)
  • Verify task wasn't completed without time tracking enabled

Q: Time seems incorrect?

  • Time calculates from last start_task call (not first)
  • Calling start_task multiple times resets the timer
  • Check .cache/time-tracking.json for session details

Q: Session lost after restart?

  • Check .cache/ directory exists and is writable
  • Verify disk space (auto-save fails on ENOSPC)
  • Check file permissions for .cache/time-tracking.json

Q: How do I see active sessions?

  • Use get_my_current_tasks to see tasks you're currently working on
  • Check .cache/time-tracking.json directly for all active sessions

Advanced: Manual Logging (No Auto-Tracking)

For workflows that don't fit automatic tracking:

// Log time without starting/completing
await callTool('log_work_session', {
  workItemId: 123,
  hours: 2.5,
  date: '2025-12-20',  // Optional: defaults to today
  description: 'Implemented feature X and wrote tests'
});
// ✅ Logs 2.5h, task remains in current state

This is useful for:

  • Retrospective time logging
  • Adjusting previously logged hours
  • Logging time without changing task status

Implementation Details

For developers integrating or debugging:

Source Files:

  • src/cache/time-tracking-cache.ts - LRU cache with persistence
  • src/types/time-tracking.ts - Type definitions and constants
  • src/tools/workflow.ts - start_task and complete_task handlers

Cache Schema:

interface TimeTrackingEntry {
  projectId: number;
  workItemId: number;
  startedAtMs: number;          // Date.now() when session started
  lastActivityAtMs: number;     // Updated on each activity
  accumulatedHours: number;     // Sum of logged hours across sessions
  status: 'active' | 'completed';
}

Persistence Strategy:

  • Atomic writes: .cache/time-tracking.json.tmp → rename to .cache/time-tracking.json
  • Debounced auto-save: 100ms after modifications
  • Periodic save: Every 5 minutes
  • Crash recovery: Auto-loads on server start

See Also: