Free SKILL.md scraped from GitHub. Clone the repo or copy the file directly into your Claude Code skills directory.
npx versuz@latest install jira-workloggit clone https://github.com/krzysztofkrolikowski/copilot-jira-worklog.gitcp -r copilot-jira-worklog/ ~/.claude/skills/jira-worklog/---
name: jira-worklog
description: Generate bash scripts for bulk logging work hours to Jira. Use when user asks to log hours, create worklog script, or automate Jira time tracking for a specific month. Handles task discovery via JQL, existing worklog detection, hour distribution, skip days (holidays/vacation), and API authentication.
---
# Jira Worklog Script Generation
This skill generates bash scripts for bulk logging work hours to Jira for a specific month.
## Prerequisites
1. **Atlassian MCP Server** must be running — verify that `mcp_atlassian_searchJiraIssuesUsingJql` is available. See [Atlassian MCP Server](https://github.com/atlassian/mcp-server-atlassian) for setup. If an `atlassian-mcp` skill exists in the project, run it first.
2. **`jq` must be installed** on the user's machine (required by the generated script for duplicate detection and JSON parsing).
3. **`python3` must be available** — used for changelog verification scripts and timestamp computation.
4. **Config file** — `.worklog.config.json` in the skill directory. See Configuration section.
## Configuration
Read `.worklog.config.json` from the skill directory. If it doesn't exist, ask the user to create one based on [.worklog.config.example.json](./.worklog.config.example.json).
The config file should be **added to `.gitignore`** (it contains personal settings). Add this entry:
```
.worklog.config.json
```
### Config Structure
```json
{
"email": "your.email@company.com",
"site": "your-company.atlassian.net",
"timezone": "auto",
"hoursPerDay": 8,
"startTime": "09:00:00",
"includeComments": false,
"recurringTasks": [
{
"issueKey": "PROJ-123",
"description": "Sprint Planning, Refinement, Retrospective",
"hours": 8,
"dayOfWeek": "friday",
"frequency": "biweekly"
}
]
}
```
### Config Fields
| Field | Required | Default | Description |
|-------|----------|---------|-------------|
| `email` | Yes | — | Atlassian account email |
| `site` | Yes | — | Atlassian site (e.g., `company.atlassian.net`) |
| `timezone` | No | `"auto"` | `"auto"` = detect from system via `date +%z`, or explicit like `"+0100"`, `"+0200"` |
| `hoursPerDay` | No | `8` | Expected hours per working day |
| `startTime` | No | `"09:00:00"` | Worklog start time |
| `includeComments` | No | `false` | Whether to include task description as worklog comment |
| `recurringTasks` | No | `[]` | Tasks that repeat on specific days (see below) |
### Recurring Tasks
Recurring tasks are a generic replacement for hardcoded sprint planning. Any regularly scheduled task can be configured:
| Field | Values | Description |
|-------|--------|-------------|
| `issueKey` | e.g., `"PROJ-5"` | Jira issue key |
| `description` | string | Display text in script output |
| `hours` | number | Hours to log (usually equals `hoursPerDay`) |
| `dayOfWeek` | `"monday"` – `"friday"` | Which day of the week |
| `frequency` | `"weekly"` or `"biweekly"` | Every week or every 2 weeks |
If `frequency` is `"biweekly"`, ask the user which weeks of the month apply (1st & 3rd, or 2nd & 4th).
## Workflow
### Step 1: Load Configuration
Read `.worklog.config.json` from the skill directory. Validate required fields (`email`, `site`). If missing, guide the user to create one from the example config in the same directory.
### Step 2: Ensure MCP Connectivity
Verify the Atlassian MCP server is running. If the project has an `atlassian-mcp` skill, run it. Otherwise, manually verify that `mcp_atlassian_searchJiraIssuesUsingJql` is available.
### Step 3: Fetch Existing Worklogs for the Month (CRITICAL — prevents duplicates)
**This is the most important safety step.** Before generating any script, check what's already logged.
#### 3a. Find issues with existing worklogs
```
Tool: mcp_atlassian_searchJiraIssuesUsingJql
JQL: worklogAuthor = currentUser() AND worklogDate >= "YYYY-MM-01" AND worklogDate <= "YYYY-MM-31"
```
#### 3b. Fetch worklog details for each issue
**Important**: The MCP `mcp_atlassian_fetch` tool does NOT support fetching worklogs. Use `run_in_terminal` with curl instead.
First, ask the user if `JIRA_API_TOKEN` is set in their environment. If yes, fetch details for each issue found in 3a:
```bash
# Compute month boundaries (Unix timestamps in ms) for date filtering
START_TS=$(python3 -c "import datetime; d=datetime.datetime(YYYY,M,1,tzinfo=datetime.timezone.utc); print(int(d.timestamp()*1000))")
# Handles December→January rollover (M+1=13 would crash)
END_TS=$(python3 -c "import datetime; y,m=(YYYY,M+1) if M<12 else (YYYY+1,1); d=datetime.datetime(y,m,1,tzinfo=datetime.timezone.utc); print(int(d.timestamp()*1000))")
# For each issue key from Step 3a (use startedAfter/startedBefore to avoid downloading full history):
curl -s -u "{email}:{JIRA_API_TOKEN}" \
-H "Accept: application/json" \
"https://{site}/rest/api/3/issue/{issueKey}/worklog?startedAfter=${START_TS}&startedBefore=${END_TS}" \
| jq '[.worklogs[] | select(.author.emailAddress == "{email}")]'
```
Run this in a single terminal command for all issues (batch with `&&` or a loop). Filter worklogs where:
- `author.emailAddress` matches the configured email
- `started` falls within the target month
**If JIRA_API_TOKEN is NOT available**, skip detailed fetching and:
- Inform the user that duplicate detection will happen at runtime (the script's `check_duplicate()` function handles this)
- Still present the list of issues that have worklogs from Step 3a
- Mark Step 3c summary as "partial — issue list only, no hour details"
#### 3c. Build and present existing worklogs summary
**Full summary** (when worklog details were fetched):
```
Already logged for January 2026:
2026-01-02 (Thu): 8h total
- PROJ-123: 4h
- PROJ-456: 4h
2026-01-03 (Fri): 8h total
- PROJ-5: 8h
...
Days fully logged (8h): 12
Days partially logged: 2 (2026-01-15: 4h, 2026-01-20: 6h)
Days with no worklogs: 6
```
**Partial summary** (when only issue list is available):
```
Issues with existing worklogs for January 2026:
PROJ-123, PROJ-456, PROJ-5, PROJ-789
(12 issues found — exact hours unknown, runtime duplicate detection will prevent re-logging)
```
Present this to the user **before proceeding**. The script's `check_duplicate()` function provides a runtime safety net regardless of whether this step fetched details or not.
### Step 4: Identify Tasks via JQL
Run multiple JQL queries to find tasks the user worked on:
**Query 1** — Currently assigned, status changed:
```jql
assignee = currentUser()
AND status changed DURING ("YYYY-MM-01", "YYYY-MM-31")
ORDER BY updated DESC
```
**Query 2** — Reassigned tasks (worked on before reassignment):
```jql
assignee changed FROM currentUser()
AND status changed DURING ("YYYY-MM-01", "YYYY-MM-31")
ORDER BY updated DESC
```
**Query 3** — Tasks with existing worklogs (catches tasks without status changes):
```jql
worklogAuthor = currentUser()
AND worklogDate >= "YYYY-MM-01" AND worklogDate <= "YYYY-MM-31"
ORDER BY updated DESC
```
Merge and deduplicate results from all three queries.
### Step 4b: Verify Assignment Dates via Changelog (MANDATORY GATE — HARD STOP if skipped)
> **🚨 THIS STEP IS NON-NEGOTIABLE. EVERY SINGLE TASK must pass changelog verification before it can appear in the generated script. NO EXCEPTIONS. NO SHORTCUTS.**
#### Why this exists
JQL queries are discovery tools, not proof of assignment. They return false positives:
- **Query 1** (`assignee = currentUser()`) — user could have been assigned 1 day then reassigned
- **Query 2** (`assignee changed FROM currentUser()`) — catches tasks from ANY month (e.g., assigned in December, reassigned in January, but Query 2 still returns it in February because status changed)
- **Query 3** (`worklogAuthor = currentUser()`) — only proves previous logs exist, not current assignment
**JQL IS DISCOVERY. CHANGELOG IS PROOF. Never trust JQL alone.**
#### Verification scope
**Verify ALL tasks from ALL queries** — not just Query 2. Every single task that could appear in the script must be changelog-verified. The only exception is recurring tasks from config.
#### How to verify — automated Python script
**ALWAYS use an automated script.** Never manually inspect changelogs one by one — this is error-prone and doesn't scale.
Generate and run a Python verification script via `run_in_terminal` that:
1. **Fetches** each issue with `expand=changelog&fields=assignee,summary`
2. **Parses** all `assignee` field changes from changelog histories
3. **Builds assignment ranges** — `(start_date, end_date)` tuples for when the user was active assignee
4. **Determines valid logging dates** — intersection of assignment ranges with target month working days
5. **Outputs a structured report** with pass/fail per task
##### Changelog parsing algorithm (v2 — bugfixed)
```python
# For each issue:
# 1. Collect all assignee changes from changelog histories, sort by created date
# 2. Walk through changes to build assignment ranges:
# - If change.toString == TARGET_USER:
# → assignment_start = change.created (the date of THIS changelog entry)
# → This is when the user BECAME the assignee
# - If change.fromString == TARGET_USER:
# → assignment_end = change.created (the date of THIS changelog entry)
# → This is when the user STOPPED being the assignee
# 3. Handle initial assignment (user was first assignee):
# - If the first assignee change has fromString == TARGET_USER,
# user was assigned from issue creation date until that change
# 4. Handle still-assigned (no reassignment after last assignment):
# - If last change assigned TO user and current assignee matches,
# range extends to today (or month end, whichever is earlier)
# 5. Build (start_date, end_date) tuples for each continuous assignment period
# 6. Intersect ranges with target month working days → valid_dates list
#
# CRITICAL BUG TO AVOID (v1 mistake):
# Do NOT use the date of the NEXT changelog entry as the start date.
# The start date is ALWAYS the date of the entry where toString == TARGET_USER.
# Example: if changelog shows "assigned to John" at Feb 4 13:44,
# the assignment starts on Feb 4, NOT on any later date.
```
##### What the script must output for each task
```
TASK | STATUS | ASSIGNED RANGES | VALID LOGGING DATES IN MONTH
-----------+--------+------------------------------+------------------------------
PROJ-110 | ✅ OK | Jan 28 → now | Feb 2,3,4,9,12,13,16,17,18,19,20
PROJ-231 | ✅ OK | Feb 3 → now | Feb 3,4,9,12,13,16,17,18,19,20
PROJ-436 | ⚠️ LTD | Feb 18 → Feb 18 | Feb 18 ONLY
PROJ-180 | ❌ OUT | Dec 19 → Jan 8 | NONE (last assigned in January)
PROJ-502 | ❌ OUT | never assigned | NONE
PROJ-373 | ❌ OUT | never assigned | NONE
```
##### Final summary the script must print
```
============================================================
ASSIGNMENT VERIFICATION SUMMARY — February 2026
============================================================
✅ Fully valid (assigned entire month): 12 tasks
⚠️ Limited dates (assigned part of month): 6 tasks
❌ REJECTED (not assigned in month): 8 tasks
❌ REJECTED TASKS (will NOT be included):
PROJ-180 — assigned Dec 19 → Jan 8 (NO February overlap)
PROJ-502 — never assigned to user
...
⚠️ LIMITED TASKS (can ONLY be logged on specific dates):
PROJ-436 — ONLY on Feb 18
PROJ-394 — ONLY on Feb 19, 20
...
============================================================
```
#### HARD RULES — violations are BLOCKING errors
| Rule | Description |
|------|-------------|
| **R1: No assignment = No logging** | If the user was NEVER assigned to a task during the target month, that task MUST NOT appear in the script. Period. |
| **R2: Date-locked logging** | A task can ONLY be logged on dates when the user was the ACTIVE assignee. If assigned Feb 18-20, it can ONLY appear on Feb 18, 19, 20 — never on Feb 2 or Feb 16. |
| **R3: No stale assignments** | The assignment range must overlap with at least one working day in the target month. A task assigned in December and **still assigned** in February is valid (continuous assignment spans months). But a task assigned in December and **reassigned away** in January has no February overlap — reject it. |
| **R4: Verify ALL sources** | Tasks from Query 1, Query 2, AND Query 3 must all be verified. Being "currently assigned" (Query 1) doesn't mean assigned for the whole month. |
| **R5: Automated verification only** | Always run the Python verification script. Never manually assume a task is valid based on JQL results alone. |
| **R6: User confirmation required** | Present the full verification report (with rejected tasks explicitly listed) and get user confirmation before proceeding. |
#### Anti-patterns — NEVER do these
| Anti-pattern | Why it's wrong |
|--------------|----------------|
| Trust Query 2 results without changelog check | Query 2 returns tasks from ANY month the user was ever assigned to |
| Assume "status changed in month" = "assigned in month" | Status can change by anyone; doesn't prove the user was assigned |
| Log task on arbitrary date within month | Must match actual assignment date range |
| Skip verification for "currently assigned" tasks | User could have been assigned yesterday but task is being logged for start of month |
| Log a task for the full month when assigned only 2 days | Assignment range limits which dates are valid |
#### Integration with Step 6 (script generation)
The verification script produces a **task-to-valid-dates mapping**. This mapping is the ONLY source of truth for Step 6:
```
VALID_TASK_DATES = {
"PROJ-110": ["2026-02-02", "2026-02-03", ..., "2026-02-20"], # full month
"PROJ-436": ["2026-02-18"], # single day
"PROJ-394": ["2026-02-19", "2026-02-20"], # partial
}
```
When distributing tasks across days in Step 6:
- **Only place a task on a date that exists in its valid dates list**
- If a task has very few valid dates, schedule it on those dates first (they're constrained)
- Schedule unconstrained tasks (full month assignment) to fill remaining slots
### Step 4c: Verify Actual Work via Status Changes (MANDATORY)
> **Assignment alone is NOT proof of work.** A task can be assigned to the user for the entire month, but if DEV DONE / QA / Code Review was reached BEFORE the target month, the user only clicked status buttons in the target month — no actual development work happened.
#### Why this exists
Common pattern: Task was assigned Jan 28, user coded it Jan 28-29, moved to DEV DONE Jan 29. In February, user just clicks "DEV DONE → Feature acceptance" — that's a 2-second click, not 8h of work. Logging hours for this in February is dishonest.
#### How to verify
Generate and run a Python script via `run_in_terminal` that checks **status change history** for each task:
1. **Fetch** each issue with `expand=changelog&fields=summary,status`
2. **Find all status changes** — especially transitions to "done" states: DEV DONE, QA, Code Review, Feature Acceptance, Done, Closed
3. **Determine when DEV work completed** — the first transition to a "done" state (Code Review, QA, DEV DONE, etc.)
4. **Compare to target month** — if DEV DONE happened BEFORE the target month, flag it
#### Decision criteria
| Scenario | Verdict |
|----------|----------|
| DEV DONE / QA / CR reached **before** target month, only status clicks in target month | ❌ **REMOVE** — no real work |
| DEV DONE / QA reached **before** target month, but task **returned** to development in target month (re-opened, rework) | ✅ **KEEP** — actual rework happened |
| DEV DONE reached **during** target month | ✅ **KEEP** — development work happened |
| No DEV DONE yet (still in development) | ✅ **KEEP** — work is ongoing |
| Task moved To Do → Development In Progress → QA/CR all within target month | ✅ **KEEP** — full dev cycle |
| Only "Feature acceptance" or "Done" click in target month (everything else was before) | ❌ **REMOVE** — just a status button |
#### Integration with Step 4b
Run this check AFTER Step 4b (assignment verification). Tasks that pass Step 4b but fail Step 4c are removed. The output should clearly state why each task was removed.
**Present suspicious tasks to the user** with their status timeline and ask for confirmation before removing.
### Step 5: Confirm with User (MANDATORY)
**ALWAYS ask ALL of the following before generating the script. NEVER skip this step.**
Use the `ask_questions` tool to ask all questions in a single call:
1. **Date range & cutoff**: Determine the cutoff date. **NEVER log future dates** — only log working days that have already passed as of today. If today is mid-month, the script should only cover days up to and including the last past working day. Calculate: if today is a weekday, cutoff = yesterday; if today is weekend, cutoff = last Friday. Ask the user to confirm: "Today is {date}. I'll log only up to {cutoff_date} ({N} working days, {N*8}h). Remaining days ({list}) will need a separate run later. OK?"
2. **Skip days**: Any holidays, vacation, sick days, or days to log manually within the date range?
3. **Recurring tasks**: Config has these recurring tasks: [list from config]. Are they still correct for this month? Which specific weeks? (Only include recurring days that fall within the cutoff range.)
4. **Existing worklogs**: Show the summary from Step 3. Ask: "These are already logged. Should I skip these days entirely, or fill in remaining hours on partial days?"
5. **Task corrections**: Present the full merged task list. Ask if anything should be added/removed.
**Wait for all answers before proceeding.**
### Step 6: Generate Script
Use the [worklog template](./worklog-template.sh) as the base. Fill in:
1. Config values (site, email, timezone, start time)
2. **Cutoff date** — if generating mid-month, only include `log_work` calls for past working days. Redistribute all tasks proportionally across the available (past) days. Each day must still total exactly `hoursPerDay`. Use smaller per-task durations (2h, 3h, 4h) to fit more tasks per day.
3. **`MONTH_START_TS` and `MONTH_END_TS`** — Unix timestamps in milliseconds for date range filtering. **CRITICAL**: Always compute with python3 to avoid errors:
```bash
# MONTH_START_TS — first day of target month
python3 -c "import datetime; d=datetime.datetime(YYYY,M,1,tzinfo=datetime.timezone.utc); print(int(d.timestamp()*1000))"
# MONTH_END_TS — first day of NEXT month (handles December→January rollover)
python3 -c "import datetime; y,m=(YYYY,M+1) if M<12 else (YYYY+1,1); d=datetime.datetime(y,m,1,tzinfo=datetime.timezone.utc); print(int(d.timestamp()*1000))"
```
- `MONTH_START_TS` = first day of the target month at 00:00 UTC
- `MONTH_END_TS` = first day of the NEXT month at 00:00 UTC
- **NEVER compute timestamps manually or approximate** — wrong timestamps cause duplicate detection to fail silently
4. `log_work` calls for each working day — **skip days already fully logged**
5. Recurring task entries on configured days (only those within cutoff range)
6. Regular task entries distributed across remaining days — **date-locked** (see below)
#### Date-Lock Enforcement (from Step 4b verification)
The `VALID_TASK_DATES` mapping from Step 4b is the **sole authority** for which tasks can appear on which dates. When distributing tasks across working days:
1. **Constrained tasks first** — Tasks with ≤3 valid dates MUST be scheduled first on those exact dates (they have no flexibility)
2. **Limited tasks next** — Tasks valid for part of the month fill slots on their valid dates
3. **Full-month tasks last** — Tasks valid for the entire month fill remaining slots as needed
4. **NEVER place a task outside its valid date range** — If a task is valid only for Feb 18-20, it can ONLY appear on those 3 days
5. **Each `log_work` call must include an inline comment** showing the task's assignment range for traceability
Example:
```bash
# Feb 18: PROJ-511 (assigned Feb 18+), PROJ-436 (assigned Feb 12+)
log_work "PROJ-511" "2026-02-18" 10800 "Implement first user login via link"
log_work "PROJ-436" "2026-02-18" 10800 "Redesign sign in flow"
```
**Present the user with a full distribution table** before generating the script. The table must show:
- Date, task, hours, description, and assignment range for each entry
- Daily totals (must equal `hoursPerDay`)
- Grand total (must equal working days × `hoursPerDay`)
- Rejected tasks with reasons
Get explicit user approval of the distribution before generating the script.
**Script naming**: `jira-worklog-{month}-{year}.sh` (e.g., `jira-worklog-january-2026.sh`)
### Step 7: Run the Script (MANDATORY — DO NOT SKIP)
> **🚨 After generating the script, you MUST offer to run it immediately. This step is the whole point of the skill — generating a script without executing it is incomplete work.**
#### Procedure
1. **Ask the user**: "Script is ready. Do you want me to run it now and log the hours to Jira?"
2. **If the user says yes**:
a. **Check `JIRA_API_TOKEN`**: Ask the user if the token is set in their environment. If not, guide them:
```
export JIRA_API_TOKEN="your-token-here"
# Generate at: https://id.atlassian.com/manage-profile/security/api-tokens
```
b. **Run dry-run first**: Execute `bash jira-worklog-{month}-{year}.sh --dry-run` to verify everything looks correct
c. **Show dry-run output** to the user and ask for final confirmation: "Dry-run looks good — {N} entries, {H}h total. Proceed with actual logging?"
d. **Execute for real**: Run `bash jira-worklog-{month}-{year}.sh` and show the output
e. **Report results**: Summarize successes, skipped duplicates, and any failures
3. **If the user says no**: Provide instructions for manual execution later (dry-run command, then real command, token setup)
#### Why this is mandatory
The user asked for hours to be logged, not for a script to be generated. The script is a means to an end. If the skill stops after generating the script, the user's request is **not fulfilled**.
#### Rollback — Deleting Wrong Worklogs
If the script logged incorrect hours, use this to find and delete them:
```bash
# List worklogs for a specific issue in the target month:
curl -s -u "$EMAIL:$JIRA_API_TOKEN" \
"https://{site}/rest/api/3/issue/{issueKey}/worklog?startedAfter={MONTH_START_TS}&startedBefore={MONTH_END_TS}" \
| jq '.worklogs[] | select(.author.emailAddress == "{email}") | {id, started, timeSpentSeconds}'
# Delete a specific worklog by ID:
curl -s -u "$EMAIL:$JIRA_API_TOKEN" \
-X DELETE \
"https://{site}/rest/api/3/issue/{issueKey}/worklog/{worklogId}"
```
## API Reference
### Endpoint
```
POST https://{site}/rest/api/3/issue/{issueKey}/worklog
```
Note: `{site}` is the full domain from config (e.g., `your-company.atlassian.net`).
### Authentication
**Always use curl `-u` flag** (NOT Base64 Authorization header — that causes 404 errors):
```bash
curl -u "$EMAIL:$API_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-X POST \
--data "$PAYLOAD" \
"$URL"
```
### Token Source
The script reads the API token from the `JIRA_API_TOKEN` environment variable. **NEVER hardcode tokens in the script.**
```bash
# User sets before running:
export JIRA_API_TOKEN="your-token-here"
```
Generate new tokens at: https://id.atlassian.com/manage-profile/security/api-tokens
### Payload Format
Without comments (`includeComments: false`, default):
```json
{
"started": "2026-01-05T09:00:00.000+0100",
"timeSpentSeconds": 7200
}
```
With comments (`includeComments: true`):
```json
{
"started": "2026-01-05T09:00:00.000+0100",
"timeSpentSeconds": 7200,
"comment": {
"type": "doc",
"version": 1,
"content": [{"type": "paragraph", "content": [{"type": "text", "text": "Description here"}]}]
}
}
```
### Timezone Handling
- `"auto"` → script uses `date +%z` at runtime (handles DST automatically)
- Explicit offset → used directly (e.g., `"+0100"` for CET winter, `"+0200"` for CEST summer)
**Important**: If a month spans a DST change (March/October in Europe), use `"auto"`.
### Time Calculations
- 1 hour = 3600 seconds
- 8 hours = 28800 seconds
## Hour Distribution Rules
### Daily Limit
- **Exactly `hoursPerDay` per working day** (default 8h = 28800s)
- Never exceed or go under
### Recurring Task Days
- Log configured hours to the recurring task issue
- If `recurringTask.hours < hoursPerDay`, distribute remaining hours to other tasks
### Regular Working Days — SP-Weighted Distribution
**Story Points (SP) determine how many hours a task gets.** Tasks with higher SP get proportionally more hours.
#### Fetching Story Points
Fetch SP for all tasks via Jira API:
```bash
curl -s -u "$EMAIL:$API_TOKEN" \
"https://{site}/rest/api/3/issue/{key}?fields=summary,story_points,customfield_10016,customfield_10028"
```
Try `story_points`, then `customfield_10016`, then `customfield_10028` (the field name varies by Jira instance).
#### Weighting tiers
| SP | Tier | Target hours per task | Rationale |
|----|------|-----------------------|-----------|
| 8+ | XL | 16-24h (3-4 entries) | Epic-level complexity |
| 5 | L | 10-14h (2-3 entries) | Major feature, multi-day effort |
| 3 | M | 4-8h (1-2 entries) | Standard task, ~1 day |
| 2 | S | 3-4h (1 entry) | Small change |
| 1 | XS | 2h (1 entry) | Trivial fix |
| None / bug | S | 2-4h (1 entry) | Treat as ~2 SP (typically quick fixes) |
#### How to calculate
1. **Assign default SP** to tasks without SP (default: 2 SP — these are typically bugs/quick fixes)
2. **Sum all SP** across all tasks in the pool (excluding recurring tasks)
3. **Calculate hours per SP unit**: `available_hours / total_SP`
4. **Multiply**: each task gets `task_SP × hours_per_SP_unit` hours
5. **Round to practical values** (2h, 3h, 4h, 6h, 8h) — avoid 1h entries
6. **Adjust for date constraints** — constrained tasks may need fewer hours if they have very few valid dates
7. **Balance** total to exactly match `working_days × hoursPerDay`
#### Constraints
- SP weighting is a **guideline**, not a strict formula
- Date constraints override SP weighting (a 13SP task with only 1 valid day can't get 24h)
- Minimum entry is 2h; avoid 1h entries (they don't feel natural)
- A task CAN get a full 8h day if it's the only one available on that date (e.g., constrained tasks)
- **Present the weighted hour allocation to the user** in a table before generating the script
### Distribution Patterns per Day
Vary distribution across days — don't use the same pattern every day:
- **4 tasks**: 2h + 2h + 2h + 2h
- **3 tasks**: 3h + 3h + 2h
- **2 tasks**: 4h + 4h, or 6h + 2h
- **1 task**: full day (only for focused investigations or workshops)
### Multi-day Tasks
When a task spans multiple days, vary the description:
- Day 1: "analysis and implementation"
- Day 2: "testing and fixes"
- Day 3: "code review fixes"
- Day 4: "QA feedback and final touches"
## Script Safety Features
The generated script (from the template) includes:
| Feature | Description |
|---------|-------------|
| `--dry-run` flag | Fully offline preview — no API token, jq, or network required |
| Duplicate detection | GET existing worklogs before each POST; skip if same issue+date+seconds already exists |
| Pre-flight validation | Verify all issue keys exist via GET before logging anything |
| Error body output | On failure, print the full API error response (not just HTTP code) |
| Retry on 5xx/429 | One retry with 3s delay on 5xx; up to 3 retries with exponential backoff on 429 |
| Dynamic summary | Count entries, hours, and working days automatically at the end |
| Token from env var | Reads `$JIRA_API_TOKEN`; refuses to run if not set |
## Common Errors
| HTTP Code | Cause | Solution |
|-----------|-------|----------|
| 401 | Expired/invalid API token | Generate new token at https://id.atlassian.com/manage-profile/security/api-tokens |
| 400 | Invalid date format or payload | Check `started` format and JSON structure |
| 403 | No permission on issue | Verify project access |
| 404 | Issue not found or wrong URL | Verify issue key exists and URL construction |
| 429 | Rate limited | Script retries with exponential backoff (up to 3 retries) |
## Checklist Before Generating Script
- [ ] Config file loaded and validated
- [ ] MCP connectivity verified
- [ ] Existing worklogs fetched and presented to user
- [ ] All tasks verified with user (including reassigned)
- [ ] **Assignment dates verified via changelog (v2 parser)** — each task logged ONLY on dates when user was active assignee
- [ ] **Work verified via status changes** — tasks where DEV DONE was before target month are removed (Step 4c)
- [ ] **SP-weighted distribution** — hours per task proportional to story points
- [ ] **Date-lock enforced** — every `log_work` call is on a date within the task's verified assignment range
- [ ] **Distribution table presented and approved by user** — full table with date, task, hours, SP, description, assignment range
- [ ] Skip days confirmed (holidays, vacation, sick days, manual days)
- [ ] **Cutoff date applied — NO future dates** (only past working days as of today)
- [ ] Recurring task days identified and confirmed for this month (within cutoff range)
- [ ] Only generating entries for days NOT already fully logged
- [ ] Hours balance to exactly `hoursPerDay` per working day
- [ ] Task count matches available hours (tasks redistributed proportionally if fewer days than full month)
- [ ] Script uses `$JIRA_API_TOKEN` env var (no hardcoded tokens)
- [ ] `MONTH_START_TS`/`MONTH_END_TS` computed with python3 (NEVER manually)
- [ ] **Script executed (Step 7)** — offered to run, dry-run shown, real execution completed (or user explicitly declined)