From 5792d6454b6a4147bb55c6fcaa630e70a4158e5d Mon Sep 17 00:00:00 2001 From: naps62 Date: Fri, 14 Aug 2026 22:11:06 +0000 Subject: [PATCH] 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 --- skills/hourlog/SKILL.md | 135 ++++++++++++++++++---------------------- 1 file changed, 61 insertions(+), 74 deletions(-) diff --git a/skills/hourlog/SKILL.md b/skills/hourlog/SKILL.md index 4800510..bb1736a 100644 --- a/skills/hourlog/SKILL.md +++ b/skills/hourlog/SKILL.md @@ -9,126 +9,113 @@ allowed-tools: - Glob - Bash - Edit - - Write --- # Hour log -Report how long each client project was actually active on each day, check -that against the timesheet, and submit only the hours the user gives you. +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 proposal. +the table. ## Setup (once per machine) -Three things must exist. Check them before anything else and stop with the -missing step if not. +Check all three before anything else; stop with the missing step if not. -1. `~/.config/hourlog/projects.json` — path prefix to project mapping. Copy - `/config.example.json` and fill it in. **Never commit a filled - config, and never put a project, client, or host name in this repo** — it - is public. +1. `~/.config/hourlog/projects.json` — path prefix to project mapping, copied + from `/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. Scopes: - `profile:read`, `schedule:read`, and `schedule:write` only if submitting. - Never echo the token. -3. Project names in the config must match the app exactly. Verify with + 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 the sessions +## 1. Scan ```sh python3 /scripts/scan-activity.py --week last ``` -Prints a markdown table: one row per day, one column per project holding the -time that project was actually active, plus wall-clock `total`, the day's -`window`, and `flags`. That table is the deliverable — paste it as-is rather -than restating it in prose. +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. -Overlap is divided, not double-counted: five minutes with two projects open is -five minutes of the day, half to each. The project columns therefore add up to -`total`, which is wall-clock presence. +What the numbers mean, and their limits: -That division is also the ratio to submit with. The user gives the day's real -total; you split it by these proportions. Never submit the measured numbers as -the hours unless the user says they are right. +- 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. -Paste it as plain markdown in the reply. Never wrap it in a code fence: a -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: +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; 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. - Either it is a new client directory the config is missing, or it is personal - work belonging in `exclude`. Ask; never guess it into a client project. + Either a client directory the config is missing, or personal work belonging + 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 python3 /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 -id and planned hours — or absent, which needs a new entry instead of a +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. -Show the planned hours next to the measured ones and let the user set the -number. The measured figure is almost always lower, and that gap is real work -off the keyboard, not evidence that the plan is wrong. +**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. -## 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 -day that needs a new entry rather than a confirmation. +## 3. Ask -Keep it to that table plus a line for anything flagged. No commentary on days -that were straightforward, and no proposed totals the user did not give you. +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 for an answer. Silence, a -timeout, or "user may be away" is not approval — leave the timesheet alone and -say the run is waiting. +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. -## 5. Submit what was approved +## 4. Submit ```sh # confirm a planned day -python3 /scripts/me-api.py confirm --entry ID --hours 8 --dry-run +python3 /scripts/me-api.py confirm --entry ID --hours 5.25 --dry-run # log a day with no allocation -python3 /scripts/me-api.py log --project ID --dates D,D --hours 8 --dry-run +python3 /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 the user approved — not the whole table, if they approved -part of it. +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; it is not a failure to retry. - -Re-read the schedule afterwards and confirm what landed. Report the diff, not -an assumption. +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