odoo-timesheets/.claude/skills/weekly-review/SKILL.md

366 lines
19 KiB
Markdown

---
name: weekly-review
description: >-
Generate a weekly timesheet review (narrative summary + loose ends) from the
Joplin week note and write it into that note's Review section, and process the
knowledge queue (notes tagged status/distill and status/refine). Use when the
user asks for a weekly review, a week recap, to "review my week", to fill in
the Review section of a timesheet week note, or to "process distill/refine
tags", "distill my notes", or "clear the knowledge queue". Optionally takes a
date or week to target a week other than the current one.
---
# Weekly timesheet review
Build a review of one week of timesheets and write it into the Review section
of the corresponding Joplin week note. The point is that reading it tells you
what the week was about. The review has these parts: a narrative **Summary** of
the work done, a short **Reviews** section for work that was only reviewed (not
the user's own tickets), and a **Loose ends** section for carry-over.
Altitude matters: explain what happened and the conclusions, not the precise
technical detail. The user has explicitly trimmed hard metrics, config values,
and internal symbol names out of the summary (the story-log notes already hold
those). Aim for "I can read this and immediately know what the week was about",
not a technical report. See the Voice section.
The full weekly ritual has **two parts**:
1. **Part 1 — Week review**: build the Summary / Reviews / Loose ends and write
it into the week note (steps below).
2. **Part 2 — Distill & refine**: process the knowledge queue, i.e. notes tagged
`status/distill` and `status/refine` (at the end of this file).
Part 2 is **independent of the week** chosen in Part 1: the tags are a standing
queue, not week-scoped. It can be run on its own. Do both parts when running a
full weekly review unless the user only asked for one.
Read `README.md` and `CLAUDE.md` first if you are not already familiar with the
project. The timesheet structure, table format, and Joplin notebook layout are
described there.
## Inputs
- Optional target week. Accept a date (`2026-05-29`, `yesterday`, `last week`)
or an ISO week. Default to the current week (today's date).
- The CLI reads timesheets from Joplin with `--joplin`. Config (token, project
map) is already set up; do not ask for it.
## Steps
### 1. Resolve the target week and locate the note
- Determine the target date. Default to today.
- Get the structured data straight from the CLI (it resolves the week itself):
```sh
uv run timesheets stories -w --joplin <date> # themes + per-story time
uv run timesheets summary -w -ss --joplin <date> # per-day totals + week total
```
`<date>` is optional; omit it for the current week. The `stories` output is
the **source of truth** for the `## Stories` block; do not re-derive it by
hand.
- Find the Joplin note to write into. Week notes are titled `YYYY - WNN` in
`Work / Timesheets / YYYY`. Compute the ISO year and week from the target
date, then:
```
find_notes_in_notebook("Work/Timesheets/<year>")
```
Match the title `YYYY - WNN` and keep its `note_id`.
### 2. Read context
- `get_note(note_id, force_full=true)` for the target week. From it, collect:
- Free-text notes under each day (often Dutch; that is fine).
- Checklist items: `- [ ]` (open) and `- [x]` (done).
- The `Note` column of each table row. This is NOT a personal scratchpad: it
is part of the timesheet and gets pushed to the timesheet system (Odoo) as
the entry description. The user uses it to section off parts of a longer
ticket ("fix tests", "respond to comments", "rebase", "review"). Read it for
the *what/why* of each entry, but never suggest the user put extra detail
there. Richer per-ticket detail belongs in the story-log notes, not here.
- **`review` as the note marks reviewed work, not the user's own work.** An
entry whose note is "review" means the user was reviewing someone else's MR
on that ticket, not building the ticket themselves. These are handled
separately (see step 3, Reviews). Treat the literal note "review" as the
signal.
- **Read the linked story notes.** The week note contains Joplin links of the
form `[label](:/<note_id>)` in the Story column, the Note column, and the
free text. Extract every `(:/<32-hex-id>)` link, dedupe the ids, and
`get_note(id, force_full=true)` each one **before** writing the Summary.
- **Find story notes that are not linked in the timesheet.** Not every ticket is
linked inline, but a note may still exist. For each PBI/Bug worked on this
week, look for its backing note in `Work / Story Logs` (and
`Work / Story Logs / Archive / <year>`): list the notebook and/or
`find_notes("<ticket-number> <few title words>")`, and match by ticket number.
Build a map of `ticket -> note_id` for every story that has a note, and record
**where** the note lives, because location signals whether the ticket is
finished:
- A note in the **top-level `Work / Story Logs`** notebook is an **active**
ticket: still in flight, not finished.
- A note in an **archive** subnotebook (`Work / Story Logs / Archive / <year>`)
is a **finished** ticket: the user archives a story log once its work is done.
Read the ones with real content. Note titles follow `PBI <num>: <title>` /
`Bug <num>: <title>`, or a descriptive title for research notes
(e.g. "Recticel profiling research"). Many notes are near-empty section
templates; link them but do not mine them for narrative.
Story-log notes carry the substance (Context / Investigation / Decision /
Solution / Learnings, or research findings). The narrative should draw on
that, not just the one-line story titles.
- Read the **previous one or two** week notes the same way (the next-lower
`WNN`). You need them only to detect carry-over in step 4.
### 3. Write the narrative Summary (and split out Reviews)
**First, separate reviewed work from own work.** Go through the raw rows and
classify each story:
- A story is **reviewed work** if all its entries that week have the note
"review" (the user only checked someone else's MR, did not build it).
- Everything else is **own work**.
Own work goes in the Summary. Reviewed-only work goes in a separate, short
`## Reviews` section (see step 5). Do not mix the two: the user does not count
reviewing someone else's MR as part of their own work done. A story with a mix
of real work and review entries stays own work.
Then write the Summary:
- Group own work by the project/task labels that `stories` produced (these are
the themes). Lead with the themes that took the most time.
- For each significant theme, write a few plain sentences on what happened that
week and where it landed. Convey the gist, not the precise detail (no exact
percentages, config values, or internal symbol names; the story-log note holds
those). Someone reading it should know what the week was about. Use the story
note's location (from step 2) to say whether the ticket finished: a ticket
whose note has moved to an archive subnotebook is done (say it landed / merged
/ closed); one whose note is still in the top-level `Story Logs` is still
active, so do not imply it is finished.
- Call out the shape of the week where it is real: a dominant feature, a heavy
bug-fixing stretch, lots of context switching, an interruption (incoming
requests, customer work like Recticel), or a spike/research day.
- **Mention noteworthy meetings explicitly.** The daily standup and Factry Flow
are routine, fold them into a brief catch-all line at most. Every *other*
meeting is often noteworthy and should be named: brainstorms (e.g. Historian
BrAInStorm, AI-usecases), sprint retros, growth-path sessions, customer or
technical meetings (e.g. the Recticel stress-test meeting). Identify these
from `internal`, `product`, `incoming`, customer projects, and the `Note`
column ("brainstorm", "meeting", "retro", "Growth Path", etc.). Say what the
meeting was about if the notes make it clear.
- Mention the week total and anything notable about it (e.g. leave days, a short
or long week) using the `summary` output.
#### Voice — write it in the user's polished English
The review is the user's own log, written for themselves, so it can sit a notch
looser than a doc meant for others. But it must be **clean English in the user's
polished voice**, not the rough Dutch/English mix they use while drafting
timesheet free-text. The reference for the target voice is their knowledge notes
(e.g. the "Express Guide" OpenTelemetry note); the self-notes (e.g. the looser
"OpenTelemetry" note) show the acceptable looser end.
Their polished style:
- **English, full sentences, normal capitalization.** No Dutch. No
lowercase-everything. No `gwn`/`da`/`ge`/`et`.
- **Direct, at the right altitude.** Active voice. Explain plainly what
happened and the conclusion ("ArgoCD is gitops CICD for Kubernetes, it
reconciles the cluster against the git repo; got it running, plenty still to
do"). A short conceptual explanation is welcome. Hard numbers, exact config
values, and internal symbol names are not: the user trims those out (e.g. they
cut `GOGC=200` / `~50%` / `buildRange` down to "changed GC to be less
aggressive with `GOGC`"). When in doubt, go one level more abstract.
- **Terse, no throat-clearing.** Skip "This week was dominated by...". Lead with
the substance. Bullets are fine where they read better than prose.
- **`->` for results/consequences** is fine and matches their notes.
- **No em dashes.** Use commas, parentheses, `->`, or new sentences.
- Light first person is fine ("I got ArgoCD running via port-forward"), matching
how they write in their own logs.
- Do not over-polish. The user's own prose has the odd typo and loose phrasing;
clean readable English is the target, not editorial perfection.
Pull the substance and phrasing from the linked story notes, but render it in
this clean English voice. Accurate before polished.
#### Referencing tickets — paraphrase, then ticket in brackets
Never drop a literal story title into the prose; many are too long. Paraphrase
what the ticket was about in your own short words, then put the ticket id in
round brackets after it. Put the note link on the bracketed id when a note
exists.
- With a note: `setting up ArgoCD for the Hatchry demos ([PBI 34839](:/<note_id>))`
- Without a note: `the homepage audit logs not checking privileges (Bug 34960)`
Only link tickets you actually found a note for (the `ticket -> note_id` map from
step 2). Never invent note ids. Reference each ticket this way the first time it
appears; afterwards a short phrase is fine.
### 4. Build the Loose ends section
Carry-over is the point of this section. Include:
- **Open checklist items** (`- [ ]`) still present in the target week note.
- **Recurring unclosed stories**: stories worked on this week that also appear
in the previous week(s) and are clearly still in flight (still being reviewed,
rebased, "respond to comments", failing tests, etc.). Use judgement from the
`Note` column; do not flag a story as carry-over just because the number
recurs. The story note's location corroborates this: a note still in the
top-level `Story Logs` confirms the ticket is active and a real carry-over
candidate, while one already moved to an archive subnotebook is finished and
should not be listed as a loose end.
- **Still-open todos** from the previous week note that were never checked off
and are still relevant.
If there is genuinely nothing to carry over, write a single line saying so
rather than padding.
### 5. Assemble the review block
Produce this structure:
```markdown
## Summary
<narrative of own work, grouped by theme, most time first; meetings line near the end>
## Reviews
<short list of stories that were only reviewed (note = "review"), with rough total time>
## Loose ends
<open checklist items, recurring unclosed stories, carry-over todos>
```
- The **Reviews** section is a short bullet list, not prose. Paraphrase each
reviewed ticket and put the id in brackets, same format as the Summary. If
nothing was only-reviewed that week, drop the section.
- Theme times in the Summary should reflect **own work only**; a story that
moved to Reviews takes its time with it (its per-story total from `stories`
is its review time, since it is review-only).
- The older notes (W20, W21) carried a verbatim `## Stories` block (the raw CLI
output with full ticket titles). The user prefers paraphrased references and
does not want literal story names dumped in, so do **not** add that block by
default. Only include it if the user asks for the raw ticket/time ledger.
### 6. Confirm, then write into the note
- **Show the full draft to the user first and ask for confirmation.** This is
LLM-generated prose going into a durable note; do not write blindly. Apply any
edits the user asks for.
- Write it into the Review section with `edit_note` in Replace mode, anchored on
the review heading so the day tables below are untouched. The review heading is
the first heading of the note, of the form `# Week of YYYY-MM-DD - Review` (or
just `# Review` in older notes). Replace from that heading down to (but not
including) the first day heading (`# <weekday> - ...`).
Concretely: read the current text between the review heading and the first day
heading, pass it as `old_string`, and pass the heading plus the new block as
`new_string`. If the Review section is an empty placeholder (heading
immediately followed by the first day heading), anchor on the heading line
alone and insert the block after it.
- After writing, re-fetch the note (or report the diff) so the user can verify.
## Notes and edge cases
- Tables have no `Duration` column; the CLI computes duration from Start/End.
Trust the CLI's times, not a hand count.
- A leave day shows as `~verlof` / a `VERLOF:` heading prefix and counts toward
totals but has no stories. Mention leave in the Summary when it shortened the
week.
- Overlapping entries exist in the raw tables; the CLI already resolves them.
Never sum durations by hand from the raw note.
- Do not modify the day tables or their `Note` columns. Only the Review section.
---
# Part 2 — Distill and refine the knowledge queue
Clearing the knowledge queue: notes the user flagged during the week for
knowledge work. This is **week-independent** (it processes whatever is currently
tagged, a standing queue) and can be run on its own.
## What the tags mean
- **`status/distill`**: the note holds general, reusable knowledge (a gotcha, a
fact, how something works) that should be promoted into a proper Knowledge
page. Example: OpenAPI gotchas written down while doing a REST-migration ticket
were distilled into the `OpenAPI Cheat Sheet`. Ticket-specific debugging detail
stays in the story log; only the generalizable knowledge gets distilled.
- **`status/refine`**: the note itself needs rewriting/cleanup. The content
stays in the same note, it just gets cleaned up. This applies mainly to
story-log and similar notes. Important exclusions:
- **Checkin notes** (in `Work / Meetings / Checkins`) are never refined by the
skill. The user does those themselves. Skip them entirely.
- For any **meeting note**, never touch the `## Topics` section. Those are the
user's own words and stay as-is. Refine only the other sections if they hold
rough content that needs cleanup.
- Refine in the note's **original language**. If the note mixes languages, use
English.
Note: `status/read` is unrelated to this queue. It means the note contains a
link to something that still needs to be read, NOT "already processed". Do not
treat it as a done-state and do not touch it here.
A note can carry both tags.
## Steps
1. **Collect the queue.** `find_notes_with_tag("status/distill")` and
`find_notes_with_tag("status/refine")`. Read each note in full.
2. **Know the knowledge base.** Read `Knowledge/_Index` for the existing pages
(cheat sheets, concept pages, infra, Factry-internal, etc.). Distilled content
should land in an existing page; only propose a new page when nothing fits,
and mark it clearly as new.
3. **For each `distill` note, propose:**
- the target Knowledge page: an existing page when one fits, or a new page
when the topic deserves its own (e.g. ArgoCD got its own page because the
user will keep researching it). Mark new pages as new and place a link under
the right `Knowledge/_Index` heading. **Before proposing a new page, search
(`find_notes`) for an existing one on the topic and check `Knowledge/_Index`
— merge into it rather than creating a duplicate.** Note that the available
Joplin tools cannot delete notes, so a wrongly-created duplicate can only be
blanked, not removed; check first.
- the actual distilled content, written in the user's knowledge-note voice.
The reference for knowledge pages is the "Express Guide" OpenTelemetry note:
fuller and more explanatory than the weekly summary, well-structured, clean
English, no em dashes.
- Quote the source lines you are distilling so the user can verify nothing was
invented or distorted.
4. **For each `refine` note, propose** the rewritten note (or rewritten
sections), preserving the user's stances, opinions, and meaning exactly. Do
not soften or invent content. Honor the exclusions above (skip checkin notes,
never touch a meeting note's `## Topics`, keep the original language / English
if mixed). If a note's only content is in an off-limits section, there is
nothing to refine: leave it for the user and do not remove its tag.
5. **List everything and get approval before writing anything.** Present one
list: per note, what would be written and where (knowledge page edit, or note
rewrite). The user approves or rejects each item individually. This mirrors
Part 1's confirm-before-write rule and is a hard requirement here.
6. **After approval**, for each approved item:
- distill: merge the content into the target Knowledge page with `edit_note`
(and add a link in `Knowledge/_Index` if a new page was created). Leave the
source note's content intact.
- refine: rewrite the note in place with `edit_note`.
- **Tag handling:** once a note has actually been processed, remove the
processed tag with `untag_note` (drop `status/distill` and/or
`status/refine`). Only remove a tag for work that was actually done and
approved. Never move it to `status/read` (that tag means something else).
## Edge cases
- **`Inbox` is intentionally tagged `distill`** so the user does not lose track
of it; it is a triage list, not knowledge. Ignore it: do not distill it and do
not remove its tag.
- More generally, **not everything tagged `distill` is promotable knowledge.** If
a tagged note holds none, say so and leave it (and its tag) alone rather than
forcing a Knowledge edit.
- If the queue is empty (or everything in it is excluded), say so. Do not invent
work.
- Never write to a Knowledge page or rewrite a note without explicit approval.