Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions skills.sh.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@
"setup-formsg-ai-skills"
]
},
{
"title": "Product",
"description": "Product management workflows.",
"skills": [
"reportcard"
]
},
{
"title": "General",
"description": "Thinking and communication skills that work across any domain.",
Expand Down
3 changes: 2 additions & 1 deletion skills/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"./skills/general/caveman",
"./skills/general/grill-me",
"./skills/general/handoff",
"./skills/general/write-a-skill"
"./skills/general/write-a-skill",
"./skills/product/reportcard"
]
}
4 changes: 2 additions & 2 deletions skills/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
Skills are organized into bucket folders under `skills/`:

- `engineering/` — daily code work
- `productivity/` — daily non-code workflow tools
- `product/` — product management workflows

Every skill in `engineering/`, `productivity/`, or `misc/` must have a reference in the top-level `README.md` and an entry in `.claude-plugin/plugin.json`. Skills in `personal/`, `in-progress/`, and `deprecated/` must not appear in either.
Every skill in `engineering/`, `product/`, or `misc/` must have a reference in the top-level `README.md` and an entry in `skills/.claude-plugin/plugin.json`. Skills in `personal/`, `in-progress/`, and `deprecated/` must not appear in either.

Each skill entry in the top-level `README.md` must link the skill name to its `SKILL.md`.

Expand Down
9 changes: 8 additions & 1 deletion skills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ No per-repo setup required — available immediately after installing the plugin
| [`/write-a-skill`](general/write-a-skill/SKILL.md) | Create new skills with proper structure and bundled resources |
| [`/humanizer`](general/humanizer/SKILL.md) | Remove signs of AI writing from text, making it more natural and human |

## Product skills

Product management workflows. No per-repo setup required. See [product/README.md](product/README.md).

| Skill | Description |
|-------|-------------|
| [`/reportcard`](product/reportcard/SKILL.md) | Add a quarter's team, cost and metric values to an OGP product report card by auditing the last recorded quarter, then asking the PM for each missing number |

## Engineering skills

Requires `/setup-formsg-ai-skills` per repo. See [engineering/README.md](engineering/README.md) for setup, the full skill list, and the recommended workflow.
f
11 changes: 11 additions & 0 deletions skills/product/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Product skills

Product management workflows. No per-repo setup required — available immediately after installing the plugin.

These skills operate on a product's own repository (for example the products.open.gov.sg content repo), so run them from that checkout rather than from here.

## Skills

| Skill | Description |
|-------|-------------|
| [`/reportcard`](reportcard/SKILL.md) | Add a quarter's team, cost and metric values to an OGP product report card by auditing the last recorded quarter, then asking the PM for each missing number |
137 changes: 137 additions & 0 deletions skills/product/reportcard/ASK-FORMAT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Ask format

How to present the audit to a PM and collect their numbers.

## Output order

**Tables first, prose last.** Four tables back to back, one-line captions
only, then a single Notes block, then the reply block. Never interleave
commentary between tables — it breaks the scan, and the tables are what the
PM is actually reading.

1. One-line header — product and target quarter
2. **Team** table
3. **Cost** table
4. **Metrics** table
5. **Dormant** table (omit if empty)
6. **Notes** (omit if nothing to flag)
7. **Reply block**

Order rows the way `reportcard.yml` orders them — `team`, then `cost` keys,
then metrics in `metricGroups` order, then `changelog`. The audit already
prints them in file order, so the PM can read the file and the ask in step.

## Tables

Every table shows the previous quarter filled and the target quarter blank.
Use **quarter labels as headers, never ISO dates**. The audit prints the exact
pair as `table columns:` — copy it rather than deriving it.

Team:

| Person | Role | Q1 2026 | Q2 2026 |
| --- | --- | --- | --- |
| alex | eng | full | |
| bo | ops | 2/3 | |

Quarter columns hold involvement (`full` when the YAML omits `involvement`).
Blank means carry forward; `-` means the person left. Caption with the quarter
the roster came from, and ask only for the **delta** — "unchanged" is a valid
and common answer. Never render a roster as a prose sentence; it is the
hardest bucket to check by eye.

Cost and Metrics:

| id | Q1 2026 | Q2 2026 |
| --- | --- | --- |
| infra | 12,345.67 | |
| paper-forms-left | 12,000 | |
| cost-per-form-submission-manual | $0.25 | |

Dormant uses `id` + `Last value` + `Quarters missing` — **no target-quarter
column**, since nothing is being collected. Caption it as awareness only, e.g.
"not collected — say the word to revive". It is a record that these once
existed, not a request.

### Formatting values

- Thousands separators and prefixes are for readability only. Write the raw
number into the YAML.
- Take a prefix **verbatim from the metric's own `prefix` field**. A metric
with `prefix: '$'` renders as `$0.25`, never `FormSG$0.25`. The product name
belongs in the header line and nowhere else.
- Use each id exactly as it appears in the file, including odd casing like
`Uptime`.

## Notes

One bullet per flag, only things that change what the PM should do:

- Roster empty, or carried forward from more than one quarter back
- Cost fields at `0` — real, or a placeholder? `0` renders as a genuine
datapoint on the public chart
- **Cost gates the whole quarter.** The quarter dropdown is built from
`cost.values` alone, so until a cost entry exists the new quarter will not
appear on the page at all. Say so when asking, so the PM knows these six
numbers unblock everything else
- `CHANGELOG.md` missing intervening quarters
- Dormant metrics — one bullet total, not one per metric

Nothing to flag means no Notes block. Do not pad it.

## Reply block

A PM gathers these numbers from finance, Datadog, and a survey, so they will
answer in pieces. Always end with a copy-paste block keyed by id, in file
order:

```
team: unchanged
infra: 0
security: 0
manpower: 0
corporate: 0
tools: 0
others: 0
paper-forms-left:
cost-per-form-submission-manual:
respondent-satisfaction-rating:
admin-satisfaction-rating:
Uptime:
paper-forms-left-moe-schools:
percentage-forms-converted-schools:
changelog:
```

The `changelog:` line is optional and exists only so a PM who already has the
text can hand it over. Leaving it blank is normal — writing a proper entry
needs a scan of Slack, GitHub and the planning doc, which is out of scope
for this skill. Never write one yourself from the metric values.

**Cost keys are pre-filled with `0`** — not because zero is likely right, but
because a quarter with no cost entry does not render at all. The default gets
the PM to a previewable page without waiting on finance.

Say this when you send the block: *"cost is pre-filled with 0 so the quarter
renders — overwrite with real figures when finance sends them."*

Treat cost as unanswered whenever **all six come back as `0`**. That is the
default echoed back, not a decision, so it means placeholder: draft PR, flagged
in the description. Individual zeros are ordinary — most products genuinely
have `security: 0` — so only the all-zero case is the signal.

**Only `NEED` items appear here.** Never put a dormant metric in the reply
block, not even commented out — a blank line reads as an obligation, and the
whole point of dormant is that the PM owes nothing. Reviving one is opt-in:
they name it explicitly ("bring back officer-satisfaction"), and only then
does it join the block on the next round.

- **Partial is fine.** A blank line is unanswered. Re-ask only the blanks;
never re-ask what is already filled.
- **Order-independent and unambiguous** — keys are ids, so nothing misaligns
the way a bare list of numbers does.
- Loose replies are fine too (`total-users 12500`, or one value at a time).
The block is a convenience, not a required format.

Keep asking in rounds until every line is filled or the PM stops, then write
`TBC` for the rest and list them at the end.
69 changes: 69 additions & 0 deletions skills/product/reportcard/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
name: reportcard
description: Add a new quarter's team, cost and metric values to an OGP product report card (_products/<product>/reportcard.yml) by auditing the last recorded quarter, then asking the PM for each missing number. Use when someone wants to update, add, or draft a quarterly report card, add a new quarter's metrics/cost/team, or mentions reportcard.yml, "Q1/Q2/Q3/Q4 report card", or the products.open.gov.sg draft editor. Writing the CHANGELOG entry is out of scope for this skill.
---

# Report card: add a quarter

Collect a quarter's numbers from the PM and append them to
`_products/<product>/reportcard.yml`.

Does not source data, does not open PRs, and does not write the changelog —
that needs a scan of Slack, GitHub and the team's planning doc, which is
out of scope here. **Never invent, interpolate, or
carry forward a metric value** — every number comes from the PM, anything
unanswered stays `TBC`, and past quarters are never edited.

## 1. Audit — never read the YAML by eye

```bash
python3 scripts/audit.py _products/<product>/reportcard.yml
```

Stdlib only, so it runs before `npm i`. `--quarter 2026-04-01` targets a
specific quarter; otherwise it infers the next one. `--active-only` hides
retired metrics.

It prints the target quarter, the table headers to use, the branch name and
its status, the last roster and cost entry, and sorts every metric into:

| Bucket | Meaning | What to do |
| --- | --- | --- |
| `NEED` | Carried a value last quarter | Ask the PM |
| `auto` | Derived from other fields | Never ask |
| `DORMANT` | No value last quarter | Offer once, then drop |

PMs retire metrics all the time, so a quiet metric is usually a deliberate
choice rather than an oversight. Offer the dormant list once so nothing is
lost silently, accept "skip them", and never raise it again.

Heed the cost warning if it appears — a quarter without a `cost.values` entry
does not render at all.

## 2. Ask

Follow [ASK-FORMAT.md](ASK-FORMAT.md): four tables, then Notes, then a
copy-paste reply block keyed by metric id in file order. Show every last-known
value so the PM can answer with a delta rather than hunting for context.

## 3. Write

Follow [WRITING.md](WRITING.md): branch at the first write, append-only edits,
and what to do when the cost figures have not arrived.

## 4. Verify

```bash
git diff _products/<product>/reportcard.yml
```

**Every hunk must be pure additions.** One deleted line means history was
rewritten instead of appended — stop and fix it. Re-run the audit; every
`NEED` should now read `OK`.

Then have the PM preview at `products.open.gov.sg/draft/<product>` before the
PR. The diff catches structural damage; only the preview catches a plausible
number in the wrong row.

Handing off for review is `prepare-for-review`'s job, not this skill's. Any
screenshot goes in `.scratch/` and is never committed.
66 changes: 66 additions & 0 deletions skills/product/reportcard/WRITING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Writing the draft

## Branch — created at the first write, not at launch

The audit prints the conventional name as `branch:`
(`<product>-q<n>-<year>-report-card`). Running this skill is not a commitment
to change anything, so a read-only question must not leave a branch behind.

Create it immediately before the first write. If the PM replies and nothing
gets written ("hold off", or every answer is "skip"), no branch. A partial
draft containing `TBC` is a write, so it does get one.

```bash
git switch -c <branch> # only if it does not already exist
```

Already on that branch, or on a non-`main` branch the PM is clearly using?
Stay there. Otherwise act on what the audit reported:

| Audit says | Do |
| --- | --- |
| `exists locally` | Switch to it, keep going |
| `exists on REMOTE only` | **Stop and ask.** Someone else likely started this quarter |
| `not found` | Create it here |
| `git unavailable` | Carry on, and say the branch step was skipped |

## Appending

New entry keyed by `startDate: 'YYYY-MM-01'`, month `01`/`04`/`07`/`10`.

- Match surrounding style exactly — indentation, date quoting, decimal places.
- `team.members`: omit the block entirely to carry the roster forward. Add one
only if it actually changed.
- `cost.values`: all six keys — `infra`, `security`, `manpower`, `corporate`,
`tools`, `others`.
- `CHANGELOG.md`: **not this skill's job** — a good entry needs a scan of
Slack, GitHub and the team's planning doc, which is out of scope here.
- If the PM supplies changelog text directly, add it at the top as
`## Q<n> <year> <Mon-Mon>`, matching the previous entry's structure.
- If they do not, leave the file alone and say the quarter is missing an
entry. Do not improvise one from the metric values — numbers are not a
story.

## When the cost figures are not in yet

**A quarter exists only if it has a `cost.values` entry.** The quarter
dropdown is built from `cost.values` keys alone
(`src/app/services/calculate-costs.ts`); metrics do not contribute. Add
metrics without a cost entry and the page silently keeps showing the previous
quarter — no error, nothing to notice.

So cost blocks *preview*, never *progress*:

1. Write the metrics as normal.
2. Add the cost entry with zeros so the quarter renders, and say plainly that
you have done so.
3. Open the PR as a **draft**, with `cost figures are placeholder zeros` in
the description as the one outstanding item.

A draft carrying zeros is normal — it is what makes the quarter previewable.
**Approval is the gate:** reviewers do not approve until the real figures land
in a follow-up commit.

The danger is silence, not the zeros. On the public chart a `0` reads as
"spending collapsed to nothing" rather than "not known yet", so an unflagged
draft can be approved by someone assuming the numbers are real.
Loading