The matrix table dropped the evidence — share, active minutes, hour range, and the outside-working-hours flags — which are what make a proposed number checkable rather than asserted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5.7 KiB
name, description, user-invocable, argument-hint, allowed-tools
| name | description | user-invocable | argument-hint | allowed-tools | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| hourlog | 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. | true | [--week last|this | --since YYYY-MM-DD [--until YYYY-MM-DD]] |
|
Hour log
Turn session activity into an hours-per-project-per-day proposal, check it against the timesheet, and submit only what the user approves.
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 proposal.
Setup (once per machine)
Three things must exist. Check them before anything else and stop with the missing step if not.
~/.config/hourlog/projects.json— path prefix to project mapping. Copy<skill-dir>/config.example.jsonand fill it in. Never commit a filled config, and never put a project, client, or host name in this repo — it is public.HOURLOG_APIandHOURLOG_TOKENin~/.env.claude. The token is a personal access token from the timesheet app's profile page. Scopes:profile:read,schedule:read, andschedule:writeonly if submitting. Never echo the token.- Project names in the config must match the app exactly. Verify with
me-api.py projectsand fix the config, not the app.
1. Scan the sessions
python3 <skill-dir>/scripts/scan-activity.py --week last
Prints one block per day — a line per project with suggested hours, share, active minutes, and the hour range — then a totals line. That output is the deliverable: show it as-is rather than restating it in prose.
The unit behind it is a 5-minute slot in which at least one message was written, deduplicated per project — so a 40-subagent swarm on one project counts once, and two projects worked in parallel each keep their own slots. Message counts would let one overnight autonomous run outweigh a real morning.
Two things in the output need judgement, not arithmetic:
outsidehours. Activity at 02:00 is usually an unattended run, not work. If most of a project's minutes came from 21h-02h, its share is inflated — say so and shift the split.unmapped:lines. A path with no rule, excluded from the split, so the affected days are wrong rather than merely incomplete. Either it is a new client directory the config is missing — say so and ask — or it is personal work that belongs inexclude. Never guess it into a client project.
--json carries the same fields if you need to compute against them.
2. Propose the hours
Active minutes are a floor on real work, never a measure of it. Meetings, review, reading, whiteboarding, and thinking leave no transcript at all. A day showing 160 active minutes was not a two-hour day. Read the numbers as proportions between projects, and get the day's length from the calendar.
The scan already does the arithmetic: it splits nominal_day_hours (config,
default 8) across the day's mapped projects by share, in 15-minute steps. Your
job is the judgement it cannot do:
- One project at 85% or more: round it up to the whole day rather than leaving a token 1h on the other.
- Days marked
under 30min, treat as emptyget nothing proposed. - Correct the split for the two things flagged in step 1 before proposing it.
Never scale a day down because its transcripts are thin. A quiet day is a normal working day unless the user says it was not, or the timesheet already marks it as time off or a holiday.
3. Reconcile against the timesheet
python3 <skill-dir>/scripts/me-api.py schedule --start YYYY-MM-DD --end YYYY-MM-DD
Each day comes back either already planned (an allocation with an entry id and planned hours) or absent. That splits the proposal in two:
- Planned and matching — confirm at the planned hours.
- Planned but the sessions disagree — confirm at the observed hours, and show both numbers in the table so the user sees what changed.
- Not planned at all — needs a new entry, which is a bigger claim. Flag it separately rather than folding it in.
4. Show the table, then ask
One row per project per day: date, weekday, project, proposed hours, what the timesheet says now, and the action (confirm / adjust / add / skip). Mark any day that needs a new entry rather than a confirmation.
Keep it to that table plus a line for anything you had to judge — a discounted overnight run, an unmapped path, a day you left empty. No commentary on days that were straightforward.
Then ask once, plainly, whether to submit. Wait for an answer. Silence, a timeout, or "user may be away" is not approval — leave the timesheet alone and say the run is waiting.
5. Submit what was approved
# confirm a planned day
python3 <skill-dir>/scripts/me-api.py confirm --entry ID --hours 8 --dry-run
# log a day with no allocation
python3 <skill-dir>/scripts/me-api.py log --project ID --dates D,D --hours 8 --dry-run
Run every write with --dry-run first and show the requests. Drop the flag
only for the rows the user 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; it is not a failure to retry.
Re-read the schedule afterwards and confirm what landed. Report the diff, not an assumption.
Scope
How a day divided between projects, and confirming hours already worked. Not future allocations, not time off, not anyone else's schedule.