t1k:performance
| Field | Value |
|---|---|
| Module | t1k-plane |
| Version | 1.2.1 |
| Effort | low |
| Tools | — |
Keywords: assigned hours, capacity, est hours, estimate hours, gio est, monthly hours, over capacity, performance, plane workload, thang nay lam duoc bao nhieu, utilization, workload
How to invoke
Section titled “How to invoke”/t1k:performance[--me|--user <name>] [--month YYYY-MM] [--json] [--include-cancelled]t1k:performance — assigned estimate hours vs monthly capacity
Section titled “t1k:performance — assigned estimate hours vs monthly capacity”Answers one question: how many estimated hours is this person carrying this month, against how many hours the month actually has? Renders as 337/168h : 200.60% — hours / capacity, then load as a percentage to two decimals.
The numerator is assigned estimate hours; the denominator is that month’s working capacity, which changes month to month (Feb 2026 = 160h, Aug 2026 = 168h, Dec 2026 = 184h). Both come from Plane, summed over every workspace the credentials reach.
Invoke
Section titled “Invoke”node "$HOME/.claude/scripts/t1k-performance.cjs" # you, this monthnode "$HOME/.claude/scripts/t1k-performance.cjs" --month 2026-07 # you, another monthnode "$HOME/.claude/scripts/t1k-performance.cjs" --user <name> # a colleagueWith no name, it reports you — identity is resolved from the configured Plane API key via /users/me/, whose id is the same UUID that appears as assignee_id on every workload row. Self mode therefore matches on that UUID, not on a display name: it survives a rename and cannot resolve to a colleague who shares a display name. Nothing needs configuring, and no person’s name is ever written into a file.
To look someone else up, name them: --user <name>, or the name as the flag (--hailh), or a bare positional. A named lookup matches on display name, which is all the caller supplied.
| Flag | Effect |
|---|---|
--me | You — the API key’s owner, matched by UUID. This is the default |
--user, -u | Look up a colleague by display name (or pass the name as the flag) |
--month, -m | YYYY-MM, month validated 01–12. Default: current month |
--workspace, -w | Comma-separated slugs. Default: every reachable workspace |
--json | Full machine-readable report |
--brief | Just 337/168h : 200.60% — what the status line consumes |
--include-cancelled | Count cancelled work too (off by default — see rule 3) |
--cache-only, --max-age | Read the cache without network I/O |
Exit codes: 0 ok · 1 every workspace failed (an outage, never reported as a zero) · 2 bad arguments · 3 --cache-only with no cache.
The five counting rules
Section titled “The five counting rules”These are fixed in the script, not options. Each exists because the naive alternative produces a wrong number.
- Estimate hours only. Items with no estimate contribute 0. They are not silently dropped — the report carries a workspace-wide unestimated count so a low total is never mistaken for a light month.
- Assigned to the named person. Unassigned work is never attributed to anyone. An item assigned to two people contributes its per-assignee share, which is how Plane already splits it.
- State-agnostic — assignment is the trigger, not completion. Backlog, Todo, In Progress and Done all count. Cancelled is excluded by default, and that is deliberate: cancelling is the only mechanism available for removing a duplicate or dropped item from the ledger, so counting cancelled work would defeat the correction.
--include-cancelledoverrides it when you genuinely want the gross figure. - Scheduled inside the month, by
target_datealone. Plane buckets an item on itstarget_date, not on whether it has any date at all — an item with astart_datebut notarget_datestill lands in Plane’sunscheduledbucket, while one with atarget_datebut nostart_datecounts normally. The report prints unscheduled hours as a separate warning line rather than dropping them, because a missing target date is a data-entry gap, not zero work. - Every workspace, summed. Workspaces are discovered at runtime via
/users/me/workspaces/; nothing is hardcoded. A workspace that errors is named in the output — a partial sum never passes itself off as a total.
Where capacity comes from
Section titled “Where capacity comes from”Plane returns capacity_buckets on the workload response: working days × 8h for the queried month. The script uses that value.
A local Mon–Fri weekday count × 8 is computed independently as a fallback for when the field is absent. When both are present and disagree, the report says so and uses Plane’s — a silent preference would hide a Plane-side calendar change (holidays, a different working week) that is exactly the thing worth knowing about.
Status line
Section titled “Status line”Opt-in via ~/.claude/t1k-performance.json:
{ "statusline": { "enabled": true, "ttlMinutes": 30, "includeCancelled": false, "requireKit": "cocos" }}No name appears here: the segment reports the API key’s owner. statusline.user (or a top-level defaultUser) may still name someone explicitly, but it is optional and only needed to watch a person other than yourself.
requireKit is an optional scope gate: the segment renders only where kit
theonekit-<short> is installed, so it can follow one engine’s sessions instead
of every session on the machine. It is keyed on that kit’s own
t1k-config-<short>.json fragment — installed and removed with the kit, so it
tracks the install rather than merely naming it — checked project-local first,
then global. Omit the field (or leave it empty) and the segment renders wherever
enabled is true, which is the default.
Note the consequence of scope: a kit installed globally satisfies the gate in
every session on that machine, so requireKit only narrows anything for a kit
installed per-project. That is the intended shape for engine kits, which
rules/kit-install-scope.md puts per-project anyway.
Renders 📊 337/168h : 200.60% in the stats segment — red below 100% of capacity, green at or above it.
hooks/lib/statusline-performance.cjs never performs network I/O on the render path. It reads the cache the CLI writes; when that cache is stale it spawns one detached refresh (lock-guarded, 5-minute stale-lock expiry) whose result appears on the next render. A stale-but-shown figure is prefixed ~. With no cache at all it renders nothing — an absent segment is honest, whereas a placeholder zero reads as a real measurement.
Remove the config file, or set enabled: false, and the segment disappears with no other effect.
Credentials
Section titled “Credentials”Read from PLANE_BASE_URL + PLANE_API_KEY when set, otherwise from the registered plane MCP server’s own env block in ~/.claude.json. The key lives in exactly one place on disk and is never written into the cache, the status line, or any log.
Gotchas
Section titled “Gotchas”- A name that matches nothing returns
0/168h, not an error. The report flags it explicitly (No assignee named "...") — check that line before believing a zero. Plane member names are what to match, not email addresses. Self mode cannot hit this, since it matches a UUID the server itself returned. - This is not an access control. Plane’s workload endpoint returns every assignee’s row to any member of the workspace, so a colleague with their own API key can read your hours whether or not you run this skill. Identity resolution here keeps your name out of shared config; it does not, and cannot, make the underlying numbers private. That is a Plane project-visibility question.
meta.zero_estimate_counton the Plane workload response is workspace-wide, not scoped to the queriedassignee_ids. Two calls with identical windows, differing only inassignee_ids, return the samezero_estimate_countwhileissues_countedchanges — it is a workspace-level diagnostic counter, not “how many of this person’s items are missing an estimate.” Do not read it as, or subtract it from, the named person’s total.get_workloaddoes NOT drop unestimated items fromtasks[]. They appear there withhours: 0andunestimated: true— a task’s absence fromtasksis never explained by “it had no estimate.”- A missing
target_dateis the most common source of a total that looks too low — even whenstart_dateis set. If the number disagrees with someone’s own tally, check the undated warning line first. An item with notarget_datestill appears intasks[]and is additionally listed in the top-levelunscheduledarray regardless ofstart_date— its hours are not silently dropped, just excluded from the month total (rule 4). --include-cancelledwill re-add work that was cancelled precisely because it was a duplicate. Only reach for it when you want a gross figure and know why.
Related
Section titled “Related”t1k-plane— work-item lifecycle, binding, state resolution bygroup. This skill only reads; it never mutates a work item.rules/plane-workitem-workflow.md— the enforced work-item gate behind plan/cook/fix/ship.rules/green-that-proves-nothing.md— why rule 5 names failed workspaces instead of quietly summing the rest.