This skill should be used when the user asks to "track time", "parse time entries", "understand CSV format", "format time tracking data", or needs guidance on the CSV schema, field definitions, rounding rules, and pricing configuration for time tracking.
This skill defines the CSV format, field definitions, rounding rules, and pricing configuration for the Time-Tracking plugin.
Pipeline:
CSV (Raw Data) → Booking (Cumulation) → Reports (Output)
↑ ↑ ↑
THIS SKILL time-tracking-booking time-tracking-reports
Note: For booking suggestions and block cumulation, see skill time-tracking-booking.
/time-tracking.track-time - Create new time entries (uses time-tracking.track-time custom tool)/time-tracking.timesheet - Overview of tracked time/time-tracking.sync-worklogs - Preparation for JIRA syncThe /time-tracking.track-time command uses a custom tool that automatically:
context.agentopencode.json configurationopencode-project.jsonThis ensures model and agent fields are always populated correctly.
When no duration is provided, the tool uses smart duration calculation:
end_time = current time (with seconds)start_time = end_time of the last entry today (with seconds)duration = difference between start and end timeThis allows seamless continuation from the previous entry. If no entry exists today, it falls back to 15m duration.
[issue_key] [description] [duration] [start_time] [account_key]
Examples:
/time-tracking.track-time
/time-tracking.track-time SOSO-286
/time-tracking.track-time SOSO-286 "Feature done"
/time-tracking.track-time SOSO-286 "Feature done" 1h
/time-tracking.track-time SOSO-286 "Feature done" 1h 09:30
The user email is read from the environment variable OPENCODE_USER_EMAIL:
# .env file in project root
[email protected]
If OPENCODE_USER_EMAIL is not set → Inform user: "Please create .env file with OPENCODE_USER_EMAIL=..."
Configuration is read from .opencode/opencode-project.json:
{
"time_tracking": {
"csv_file": ".opencode/time_tracking/time-tracking.csv",
"default_account_key": "ACCOUNT_KEY",
"charts_dir": ".opencode/time_tracking/charts/"
}
}
| Field | Description | Required |
|---|---|---|
csv_file | Path to main CSV | Yes |
default_account_key | Default JIRA account | Yes |
charts_dir | Directory for charts | No (Default: .opencode/time_tracking/charts/) |
reports_dir | Directory for reports | No (Default: .opencode/time_tracking/reports/) |
bookings_dir | Directory for booking CSVs | No (Default: .opencode/time_tracking/bookings/) |
booking | Booking configuration | No |
sync | Sync endpoints configuration | No |
agent_defaults | Agent-specific default tickets | No |
global_default | Global fallback ticket | No |
If configuration is missing → Inform user: "Please run /time-tracking.init"
Settings for booking proposals and rounding:
{
"time_tracking": {
"booking": {
"rounding_minutes": 5,
"lunch_break": {
"start": "12:00",
"end": "13:00"
}
}
}
}
| Field | Type | Default | Description |
|---|---|---|---|
booking.rounding_minutes | integer | 5 | Rounding unit (5, 10, 15, or 30 minutes) |
booking.lunch_break.start | string | "12:00" | Lunch break start (HH:MM) |
booking.lunch_break.end | string | "13:00" | Lunch break end (HH:MM) |
Used by:
/time-tracking.booking-proposal - Generates booking CSV files/time-tracking.timesheet - Displays booking suggestionsSettings for syncing booking proposals to external systems:
{
"time_tracking": {
"sync": {
"calendar": {
"calendar_id": "[email protected]",
"color_id": "9"
},
"sheets": {
"folder_id": "your-drive-folder-id"
}
}
}
}
| Field | Type | Description |
|---|---|---|
sync.calendar.calendar_id | string | Google Calendar ID for booking events |
sync.calendar.color_id | string | Event color ID (optional, 1-11) |
sync.sheets.folder_id | string | Google Drive folder for booking sheets |
Used by:
/time-tracking.sync-calendar - Creates calendar events (future)/time-tracking.sync-sheets - Creates Google Sheets (future)Agent-specific default tickets for time tracking when no ticket is found in context.
The MCP plugin opencode-time-tracking uses the following fallback hierarchy:
1. Context Ticket → From current OpenCode event (Story, Task, etc.)
↓ if not found
2. Agent Default → agent_defaults["@developer"] etc.
↓ if agent not configured
3. Global Default → global_default
↓ if not configured
4. Error → "No ticket found"
{
"time_tracking": {
"agent_defaults": {
"@developer": {
"issue_key": "PROJ-101",
"account_key": "TD_DEVELOPMENT"
},
"@reviewer": {
"issue_key": "PROJ-102"
},
"@tester": {
"issue_key": "PROJ-103"
},
"@coordinator": {
"issue_key": "PROJ-104"
}
},
"global_default": {
"issue_key": "PROJ-100",
"account_key": "TD_GENERAL"
},
"ignored_agents": ["@time-tracking", "@internal"]
}
}
| Field | Type | Required | Description |
|---|---|---|---|
agent_defaults | object | No | Map of agent names to default tickets |
agent_defaults.<agent>.issue_key | string | Yes | JIRA Issue Key (e.g., PROJ-123) |
agent_defaults.<agent>.account_key | string | No | Tempo Account (overrides default_account_key) |
global_default | object | No | Fallback when agent not configured |
global_default.issue_key | string | Yes | Global default JIRA Issue Key |
global_default.account_key | string | No | Global default Tempo Account |
ignored_agents | array | No | Agent names excluded from automatic time tracking |
1. agent_defaults[agent].account_key (if present)
↓ if not set
2. global_default.account_key (if present)
↓ if not set
3. time_tracking.default_account_key (required fallback)
| Agent | Typical Work | Default Ticket |
|---|---|---|
@developer | Development without Story context | Development Bucket |
@reviewer | Code Reviews | Review Bucket |
@tester | Exploratory Tests | QA Bucket |
@coordinator | Meetings, Planning | PM Bucket |
@implementation | Orchestration | Development Bucket |
Use ignored_agents to exclude specific agents from automatic time tracking via the techdivision/opencode-time-tracking plugin.
{
"time_tracking": {
"ignored_agents": ["@time-tracking", "@internal", "@test"]
}
}
Common exclusions:
@time-tracking - The time tracking agent itself (avoids recursive tracking)@internal - Internal utility agents@test - Test/debug agentsNote: This only affects automatic tracking. Manual entries via /time-tracking.track-time are not affected.
id,start_date,end_date,user,ticket_name,issue_key,account_key,start_time,end_time,duration_seconds,tokens_used,tokens_remaining,story_points,description,notes,model,agent
| # | Field | Type | Description | Example |
|---|---|---|---|---|
| 1 | id | UUID | Unique ID | 1e74792a-b460-4b55-a2e6-36c0ad8d062b |
| 2 | start_date | Date | Start date (YYYY-MM-DD) | 2025-01-05 |
| 3 | end_date | Date | End date (YYYY-MM-DD) | 2025-01-05 |
| 4 | user | String | User email | [email protected] |
| 5 | ticket_name | String | Ticket title | Bug SOSO-178: Fix ESLint |
| 6 | issue_key | String | JIRA Issue Key | SOSO-178 |
| 7 | account_key | String | Tempo Account Key | TD_KS_1100_... |
| 8 | start_time | Time | Start time (HH:MM:SS) | 15:58:00 |
| 9 | end_time | Time | End time (HH:MM:SS) | 17:32:00 |
| 10 | duration_seconds | Integer | Duration in seconds | 5640 |
| 11 | tokens_used | Integer | Token usage (optional) | 92988 |
| 12 | tokens_remaining | Integer | Remaining tokens (optional) | 107012 |
| 13 | story_points | Integer | Story Points (optional) | 5 |
| 14 | description | String | Work description | Fixed ESLint blocking... |
| 15 | notes | String | Additional notes | CI config fixes completed |
| 16 | model | String | LLM model (auto-captured) | anthropic/claude-opus-4 |
| 17 | agent | String | Calling agent (auto-captured) | build |
"1e74792a-b460-4b55-a2e6-36c0ad8d062b","2025-01-05","2025-01-05","[email protected]","Bug SOSO-178: Fix ESLint","SOSO-178","TD_KS_1100_SYSTEM","15:58:00","17:32:00","5640","92988","107012","","Fixed ESLint blocking","CI fixes completed","anthropic/claude-opus-4","build"
The CSV file has all fields in quotes. These must be considered when parsing:
"uuid","2026-01-09","2026-01-09","[email protected]","","PROJ-110",...
# WRONG - finds nothing because quotes are missing
awk -F',' '$2=="2026-01-09"' .opencode/time_tracking/time-tracking.csv
# CORRECT - with quotes in comparison
awk -F',' '$2=="\"2026-01-09\""' .opencode/time_tracking/time-tracking.csv
# BETTER - remove quotes then filter
awk -F',' '{gsub(/"/, ""); if ($2=="2026-01-09") print}' .opencode/time_tracking/time-tracking.csv
# Remove quotes when extracting
awk -F',' '{
gsub(/"/, "", $2); # start_date
gsub(/"/, "", $8); # start_time
gsub(/"/, "", $9); # end_time
gsub(/"/, "", $10); # duration_seconds
gsub(/"/, "", $11); # tokens_used
gsub(/"/, "", $6); # issue_key
gsub(/"/, "", $14); # description
gsub(/"/, "", $16); # model
print $2, $8, $9, $10, $11, $6, $14, $16
}' .opencode/time_tracking/time-tracking.csv
# Complete: Filter + extract fields + remove quotes
awk -F',' '
NR > 1 { # Skip header
# Remove quotes from all relevant fields
for (i=1; i<=NF; i++) gsub(/"/, "", $i)
# Filter by date
if ($2 == "2026-01-09") {
print $8, $9, $10, $11, $6, $14, $16
}
}
' .opencode/time_tracking/time-tracking.csv
For /time-tracking.sync-worklogs - combines Time-Tracking + Calendar:
date,start_time,duration_hours,issue_key,account_key,calendar_event_id,description,source,sync_status
| Field | Description |
|---|---|
date | Date (YYYY-MM-DD) |
start_time | Rounded start time (HH:MM) |
duration_hours | Duration in hours (rounded) |
issue_key | JIRA Issue Key |
account_key | Tempo Account Key |
calendar_event_id | Google Calendar Event ID |
description | Description |
source | time-tracking or calendar |
sync_status | pending, draft, synced |
Round to nearest 5-minute mark:
| Original | Rounded |
|---|---|
| 09:03 | 09:05 |
| 09:07 | 09:05 |
| 09:08 | 09:10 |
| 14:23:00 | 14:25 |
| 14:22:29 | 14:20 |
Formula:
rounded_minutes = round(minutes / 5) * 5
Round seconds to nearest 5-minute unit:
| Seconds | Minutes | Rounded | Hours |
|---|---|---|---|
| 120 | 2 min | 5 min | 0.0833h |
| 180 | 3 min | 5 min | 0.0833h |
| 600 | 10 min | 10 min | 0.1667h |
| 5640 | 94 min | 95 min | 1.5833h |
Formula:
rounded_minutes = round(seconds / 60 / 5) * 5
duration_hours = rounded_minutes / 60
For raw display, directly consecutive entries with the same issue_key are merged:
Rules:
issue_key is not empty and identicaldescription: Combined with " + "Note: For extended cumulation with gap tolerance and value-oriented descriptions, see skill time-tracking-booking.
escape_csv() {
echo "$1" | sed 's/"/""/g'
}
uuid=$(uuidgen | tr '[:upper:]' '[:lower:]')
description=$(escape_csv "Fixed \"critical\" bug")
notes=$(escape_csv "All tests passing")
# ALL fields in quotes (17 fields including model and agent)
csv_line="\"${uuid}\",\"${start_date}\",\"${end_date}\",\"${user}\",\"${ticket_name}\",\"${issue_key}\",\"${account_key}\",\"${start_time}\",\"${end_time}\",\"${duration_seconds}\",\"${tokens_used}\",\"${tokens_remaining}\",\"${story_points}\",\"${description}\",\"${notes}\",\"${model}\",\"${agent}\""
echo "$csv_line" >> "$csv_file"
""" → """" (not empty commas)# Seconds → rounded hours
duration_seconds=5640
rounded_minutes=$(( (duration_seconds + 150) / 300 * 5 )) # +150 for rounding
duration_hours=$(echo "scale=4; $rounded_minutes / 60" | bc)
# 5640 sec → 95 min → 1.5833h
When start_date ≠ end_date: