docs: tighten hourlog, add the confirmed-entry check

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>
This commit is contained in:
naps62
2026-08-14 22:11:06 +00:00
parent b485cee634
commit 5792d6454b
+61 -74
View File
@@ -9,126 +9,113 @@ allowed-tools:
- Glob - Glob
- Bash - Bash
- Edit - Edit
- Write
--- ---
# Hour log # Hour log
Report how long each client project was actually active on each day, check Report how long each client project was active on each day, check that against
that against the timesheet, and submit only the hours the user gives you. 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 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 saying go, in this session, after seeing the table. An unattended run stops at
the proposal. the table.
## Setup (once per machine) ## Setup (once per machine)
Three things must exist. Check them before anything else and stop with the Check all three before anything else; stop with the missing step if not.
missing step if not.
1. `~/.config/hourlog/projects.json` — path prefix to project mapping. Copy 1. `~/.config/hourlog/projects.json` — path prefix to project mapping, copied
`<skill-dir>/config.example.json` and fill it in. **Never commit a filled from `<skill-dir>/config.example.json`. **Never commit a filled config, and
config, and never put a project, client, or host name in this repo** — it never put a project, client, or host name in this repo** — it is public.
is public.
2. `HOURLOG_API` and `HOURLOG_TOKEN` in `~/.env.claude`. The token is a 2. `HOURLOG_API` and `HOURLOG_TOKEN` in `~/.env.claude`. The token is a
personal access token from the timesheet app's profile page. Scopes: personal access token from the timesheet app's profile page: `profile:read`,
`profile:read`, `schedule:read`, and `schedule:write` only if submitting. `schedule:read`, and `schedule:write` only if submitting. Never echo it —
Never echo the token. `${VAR:-x}` prints the value when the variable is set; use `${VAR:+set}`.
3. Project names in the config must match the app exactly. Verify with 3. Config project names must match the app exactly. Verify with
`me-api.py projects` and fix the config, not the app. `me-api.py projects` and fix the config, not the app.
## 1. Scan the sessions ## 1. Scan
```sh ```sh
python3 <skill-dir>/scripts/scan-activity.py --week last python3 <skill-dir>/scripts/scan-activity.py --week last
``` ```
Prints a markdown table: one row per day, one column per project holding the A markdown table, one row per day, one column per project. Paste it into the
time that project was actually active, plus wall-clock `total`, the day's reply as plain markdown — never in a code fence, which shows raw pipes instead
`window`, and `flags`. That table is the deliverable — paste it as-is rather of a rendered table — and don't restate it in prose.
than restating it in prose.
Overlap is divided, not double-counted: five minutes with two projects open is What the numbers mean, and their limits:
five minutes of the day, half to each. The project columns therefore add up to
`total`, which is wall-clock presence.
That division is also the ratio to submit with. The user gives the day's real - The unit is a 5-minute slot containing at least one message, deduplicated
total; you split it by these proportions. Never submit the measured numbers as per project, so a 40-subagent swarm counts once. Overlapping minutes are
the hours unless the user says they are right. 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.
Paste it as plain markdown in the reply. Never wrap it in a code fence: a Two flags need a judgement call:
fence shows the raw pipes and dashes instead of a rendered table.
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.
`--json` carries the same fields if you need to compute against them.
## 2. Report what was measured
**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. Some days start at 06:00,
some run past midnight, and none of them are the same length. A guessed number
is worse than a small true one, because the user cannot tell it was guessed.
State the measured time and say plainly that it is a floor: meetings, review,
reading and thinking leave no transcript, so real hours are higher by an
amount only the user knows. They add that back — you never do.
Where judgement genuinely belongs is the `flags` column:
- **`outside HHh`** — time at 02:00 is usually an unattended run, not work. - **`outside HHh`** — time at 02:00 is usually an unattended run, not work.
Name it so the user can discount it; do not silently drop it. 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. - **`unmapped time excluded`** — a path with no rule, left out of that day.
Either it is a new client directory the config is missing, or it is personal Either a client directory the config is missing, or personal work belonging
work belonging in `exclude`. Ask; never guess it into a client project. in `exclude`. Ask; never guess it into a client project.
## 3. Reconcile against the timesheet `--json` carries the same fields for computing against.
## 2. Reconcile
```sh ```sh
python3 <skill-dir>/scripts/me-api.py schedule --start YYYY-MM-DD --end YYYY-MM-DD 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 carrying an entry Each day is either planned — an allocation carrying an entry id, planned
id and planned hours — or absent, which needs a new entry instead of a hours, and a status — or absent, which needs a new entry rather than a
confirmation. confirmation.
Show the planned hours next to the measured ones and let the user set the **Check the status of every entry before proposing a value.** `planned` with
number. The measured figure is almost always lower, and that gap is real work `actual_hours: null` is untouched and safe to fill. `confirmed` or `edited`
off the keyboard, not evidence that the plan is wrong. 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.
## 4. Show the table, then ask 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.
The scan's table with the timesheet's planned hours beside each cell. Mark any ## 3. Ask
day that needs a new entry rather than a confirmation.
Keep it to that table plus a line for anything flagged. No commentary on days The table, plus one line for anything flagged. No commentary on days that were
that were straightforward, and no proposed totals the user did not give you. straightforward, and no totals the user did not give you.
Then ask once, plainly, whether to submit. Wait for an answer. Silence, a Then ask once, plainly, whether to submit. Wait. Silence, a timeout, or "user
timeout, or "user may be away" is not approval — leave the timesheet alone and may be away" is not approval — leave the timesheet alone and say the run is
say the run is waiting. waiting.
## 5. Submit what was approved ## 4. Submit
```sh ```sh
# confirm a planned day # confirm a planned day
python3 <skill-dir>/scripts/me-api.py confirm --entry ID --hours 8 --dry-run python3 <skill-dir>/scripts/me-api.py confirm --entry ID --hours 5.25 --dry-run
# log a day with no allocation # log a day with no allocation
python3 <skill-dir>/scripts/me-api.py log --project ID --dates D,D --hours 8 --dry-run 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 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 only for the rows approved — not the whole table, if they approved part of it.
part of it.
A `423` means the period is locked and ops has to reopen it. Report it and A `423` means the period is locked and ops has to reopen it; report it and move
move on; it is not a failure to retry. on. Re-read the schedule afterwards and report what actually landed, not what
was sent.
Re-read the schedule afterwards and confirm what landed. Report the diff, not
an assumption.
## Scope ## Scope