5792d6454b
Scan and report had grown into two sections saying the same thing, and
step 4 repeated step 1's "show the table". Down to four steps.
Adds what nearly went wrong on the first real run: a day already
confirmed weeks ago looks identical to a planned one in a bulk approval,
and overwriting it needs its own yes. Also records the 15-minute
rounding the app enforces, and the ${VAR:-x} expansion that prints a
token instead of hiding it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
124 lines
5.1 KiB
Markdown
124 lines
5.1 KiB
Markdown
---
|
|
name: hourlog
|
|
description: Work out how many hours each client project got on each day, by reading Claude Code and Codex session activity, then reconcile that against the company timesheet and confirm the week's hours. Use when the user asks to log hours, fill in their timesheet, check what they worked on last week, or runs the Friday hour-logging session.
|
|
user-invocable: true
|
|
argument-hint: "[--week last|this | --since YYYY-MM-DD [--until YYYY-MM-DD]]"
|
|
allowed-tools:
|
|
- Read
|
|
- Grep
|
|
- Glob
|
|
- Bash
|
|
- Edit
|
|
---
|
|
|
|
# Hour log
|
|
|
|
Report how long each client project was active on each day, check that against
|
|
the timesheet, and submit only the hours the user gives you.
|
|
|
|
The timesheet is a company record. Nothing is written to it without the user
|
|
saying go, in this session, after seeing the table. An unattended run stops at
|
|
the table.
|
|
|
|
## Setup (once per machine)
|
|
|
|
Check all three before anything else; stop with the missing step if not.
|
|
|
|
1. `~/.config/hourlog/projects.json` — path prefix to project mapping, copied
|
|
from `<skill-dir>/config.example.json`. **Never commit a filled config, and
|
|
never put a project, client, or host name in this repo** — it is public.
|
|
2. `HOURLOG_API` and `HOURLOG_TOKEN` in `~/.env.claude`. The token is a
|
|
personal access token from the timesheet app's profile page: `profile:read`,
|
|
`schedule:read`, and `schedule:write` only if submitting. Never echo it —
|
|
`${VAR:-x}` prints the value when the variable is set; use `${VAR:+set}`.
|
|
3. Config project names must match the app exactly. Verify with
|
|
`me-api.py projects` and fix the config, not the app.
|
|
|
|
## 1. Scan
|
|
|
|
```sh
|
|
python3 <skill-dir>/scripts/scan-activity.py --week last
|
|
```
|
|
|
|
A markdown table, one row per day, one column per project. Paste it into the
|
|
reply as plain markdown — never in a code fence, which shows raw pipes instead
|
|
of a rendered table — and don't restate it in prose.
|
|
|
|
What the numbers mean, and their limits:
|
|
|
|
- The unit is a 5-minute slot containing at least one message, deduplicated
|
|
per project, so a 40-subagent swarm counts once. Overlapping minutes are
|
|
split evenly between the projects live in them, so the columns add up to
|
|
`total`, which is wall-clock presence.
|
|
- **Measured time is a floor, never a total.** Meetings, review, reading and
|
|
thinking leave no transcript. Say so; the user adds them back.
|
|
- **Do not extrapolate.** No fitting to an 8-hour day, no scaling a thin day
|
|
up, no rounding a dominant project to the whole day. Days start at 06:00 and
|
|
run past midnight, and a guessed number is worse than a small true one
|
|
because the user cannot tell it was guessed.
|
|
|
|
Two flags need a judgement call:
|
|
|
|
- **`outside HHh`** — time at 02:00 is usually an unattended run, not work.
|
|
Name it so the user can discount it; never silently drop it.
|
|
- **`unmapped time excluded`** — a path with no rule, left out of that day.
|
|
Either a client directory the config is missing, or personal work belonging
|
|
in `exclude`. Ask; never guess it into a client project.
|
|
|
|
`--json` carries the same fields for computing against.
|
|
|
|
## 2. Reconcile
|
|
|
|
```sh
|
|
python3 <skill-dir>/scripts/me-api.py schedule --start YYYY-MM-DD --end YYYY-MM-DD
|
|
```
|
|
|
|
Each day is either planned — an allocation carrying an entry id, planned
|
|
hours, and a status — or absent, which needs a new entry rather than a
|
|
confirmation.
|
|
|
|
**Check the status of every entry before proposing a value.** `planned` with
|
|
`actual_hours: null` is untouched and safe to fill. `confirmed` or `edited`
|
|
means the user already set that number, possibly weeks ago. Overwriting one is
|
|
a separate decision: show the current value against the proposed value, say
|
|
which day it is, and get a specific yes for that entry. Do not fold it into a
|
|
bulk approval.
|
|
|
|
Show planned hours beside measured ones and let the user set the number. The
|
|
measured figure is almost always lower, and that gap is work off the keyboard,
|
|
not evidence the plan is wrong.
|
|
|
|
## 3. Ask
|
|
|
|
The table, plus one line for anything flagged. No commentary on days that were
|
|
straightforward, and no totals the user did not give you.
|
|
|
|
Then ask once, plainly, whether to submit. Wait. Silence, a timeout, or "user
|
|
may be away" is not approval — leave the timesheet alone and say the run is
|
|
waiting.
|
|
|
|
## 4. Submit
|
|
|
|
```sh
|
|
# confirm a planned day
|
|
python3 <skill-dir>/scripts/me-api.py confirm --entry ID --hours 5.25 --dry-run
|
|
# log a day with no allocation
|
|
python3 <skill-dir>/scripts/me-api.py log --project ID --dates D,D --hours 4 --dry-run
|
|
```
|
|
|
|
Hours are decimal and the app enforces 15-minute steps, so round to a multiple
|
|
of 0.25 before sending. When the user gives a day's total rather than
|
|
per-project numbers, split it by the measured proportions.
|
|
|
|
Run every write with `--dry-run` first and show the requests. Drop the flag
|
|
only for the rows approved — not the whole table, if they approved part of it.
|
|
|
|
A `423` means the period is locked and ops has to reopen it; report it and move
|
|
on. Re-read the schedule afterwards and report what actually landed, not what
|
|
was sent.
|
|
|
|
## Scope
|
|
|
|
How a day divided between projects, and confirming hours already worked. Not
|
|
future allocations, not time off, not anyone else's schedule.
|