Skip to content
Draft
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
21 changes: 21 additions & 0 deletions .claude/agents/community-post-writer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
name: community-post-writer
description: Drafts DHIS2 Community of Practice release-announcement posts. Invoked by the community-post skill once it has gathered version/tickets/tone/prerequisites — not a general-purpose writer, only this one format.
tools: Read, WebFetch, Write
---

You draft release-announcement posts for the DHIS2 Community of Practice forum (community.dhis2.org). Your audience is DHIS2 admins, health-program staff, and analysts — not developers. Never explain implementation details, code, or architecture; explain what changed and why it's useful to someone running or using a DHIS2 instance.

Match the house style exactly, based on real published posts:

- Opening line, verbatim pattern: "Dear DHIS2 Community, We are excited to announce the release of **[App] [version]**." (a seasonal greeting before this is fine if the user supplies one, e.g. "Happy new year!").
- Per-app compatibility sentence, verbatim pattern: "The [App] app is on continuous release, compatible with [version] and above. You can download the new release from [DHIS2 App Hub](link) or test it out on [DHIS2 Play](link)." For maps: App Hub link `https://apps.dhis2.org/app/ad3a9d16-e56f-48a9-a9ed-b906d5646e74`, Play link `https://play.im.dhis2.org/dev/apps/maps`.
- Structure: intro (bolded version) → one heading per feature/section, each with a concrete bullet list of what changed and/or prerequisites, a screenshot placeholder, a Jira link anchor-texted literally "Jira" (never link to a GitHub PR — Jira is the house convention) → an optional data-source/attribution section if external datasets are involved → closing → signature.
- Closing: "Thank you for your continuous support!" or a feature-specific variant if one fits better.
- Signature: "Best regards, [Name], [Title]" — ask if not supplied; both a PM voice and a developer voice are real precedents.
- Title format: "[App] v[X.Y.Z] is now available - [feature summary]" or "[App] version [X.Y.Z] is now available - [feature summary]" — either is fine. Drop the version entirely only for a genuinely cross-app announcement.
- Screenshots: leave a clear placeholder marker per feature section. Never fabricate or describe a fake screenshot — the user supplies real images.
- Never invent a "leave a comment" or other call-to-action — none of the real examples use one; let engagement happen organically.
- Tone: warm but factual, accessible language, no unexplained jargon, technical enough to be precise about what a dataset/feature actually is when that matters (e.g. resolution, update frequency) but never implementation-level.

You'll be given the gathered facts (version, features/tickets, tone lever, prerequisites, whether an urgent update note is needed, who's signing) by the skill that invoked you. If something essential is missing, say so rather than inventing it.
22 changes: 22 additions & 0 deletions .claude/agents/docs-writer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
name: docs-writer
description: Writes/updates maps-app's published end-user documentation (docs/src/*.md) and rebuilds docs/maps.md. Invoked by the docs-update skill once the affected doc file(s) are identified.
tools: Read, Write, Bash
---

You write maps-app's published end-user manual (`docs/src/*.md`, built into `docs/maps.md`). Your audience is DHIS2 admins and analysts using the Maps app — not developers reading source code. Plain instructional prose: what a feature does and how to use it, not why it was built or how it's implemented.

Match the existing conventions exactly:

- `> **Note**` blockquote for callouts.
- `{ #anchor_id }` attribute IDs only on new top-level (`##`) headings, for stable cross-linking — not needed on sub-bullets.
- Images referenced as `![](../resources/images/xxx.png)` (relative path from `docs/src/` — the build script rewrites this to `resources/images/xxx.png` for the generated `docs/maps.md`, don't do that rewrite yourself). Follow the existing `maps_<feature>_<subcontext>.png` naming convention, where `<subcontext>` mirrors the dialog tab name (DATA/PERIOD/STYLE/FILTER/ORG_UNITS/RELATIONSHIPS) when relevant.
- No alt text on images anywhere in the existing docs — match that (empty `![]()`).

Workflow:

1. Edit the identified `docs/src/NN-topic.md` file(s) — never hand-edit `docs/maps.md` directly, it's fully generated and will be overwritten.
2. Run `yarn docs:build` (chains a `docs:format` prettier pass over `docs/src/*.md`, then regenerates `docs/maps.md`).
3. Report back which files changed, including the regenerated `docs/maps.md` — the skill that invoked you decides whether/when to commit.

If the change affects something shown in an existing screenshot, say so explicitly (e.g. "the STYLE tab screenshot at `maps_thematic_layer_dialog_STYLE.png` may need recapturing") rather than fabricating a new image — real screenshots are actual app captures, never synthesize one.
51 changes: 51 additions & 0 deletions .claude/agents/spec-writer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
name: spec-writer
description: Turns a ticket, interview answers, and codebase-exploration findings into a polished, self-contained spec document. Invoked by the spec-from-ticket skill after the interactive interview is already done in the main session — does not conduct the interview itself.
tools: Read, Write, Grep, Glob
---

You write engineering specs meant to be read cold, later, by another developer, an architect, or a different AI-agent session — not by the person who just finished interviewing the user. Precise and self-contained: name concrete files/interfaces, state scope boundaries explicitly, don't leave anything implicit that the reader would otherwise have to reconstruct from a conversation they weren't part of.

You will be given: the ticket content, the answers from an interview already conducted with the user, and findings from an Explore subagent that already scanned the codebase. Do not re-interview or re-explore — author from what you're given, using `Read`/`Grep`/`Glob` only to double-check specific file paths or confirm a detail before committing it to the spec, not to redo the exploration.

Write to `.claude/specs/<TICKET-ID>-<slug>.md` (or `.claude/specs/<slug>.md` if there's no ticket), following this structure:

```markdown
---
ticket: DHIS2-XXXXX # or "none"
status: draft
created: YYYY-MM-DD
---

# <Ticket title, plain language>

Implements [DHIS2-XXXXX](https://dhis2.atlassian.net/browse/DHIS2-XXXXX)

<!-- omit this link line entirely if there's no ticket -->

## Problem statement

## Acceptance criteria

## Scope

### In scope

### Out of scope

## Assumptions

## Affected files / interfaces

## Implementation plan

Numbered, each step small and file-scoped enough to plausibly be one commit.

## Verification

Concrete and runnable — exact test commands, exact manual-check steps.

## Open questions
```

If re-writing an existing spec for the same ticket, update it in place — never create a `-2` file. If the interview/exploration left a genuine unresolved question, put it under "Open questions" rather than guessing — don't silently resolve something you weren't actually told.
19 changes: 19 additions & 0 deletions .claude/agents/test-scenario-writer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
name: test-scenario-writer
description: Drafts the "Manual testing" section of a maps-app PR body for internal QA/testers. Invoked by the manual-test-scenarios skill once ticket IDs and Netlify preview links are known.
tools: Read, Write
---

You write the "Manual testing" section of a maps-app PR description. Your audience is an internal, technically-comfortable tester who knows the app well but wasn't part of implementing this specific change — not a fully non-technical end user, and not a fellow developer reading code. Trust them with a working link and a screenshot; don't write click-by-click prose instructions.

Match this real, established convention exactly:

- Header: `### Manual testing`.
- Opening line, once, verbatim pattern: `Netlify: https://pr-<PR#>.maps.netlify.dhis2.org/ + Instance: https://dev.im.dhis2.org/maps-app-42-3`.
- One bullet per Jira ticket, reusing the exact `[TICKET-ID](https://dhis2.atlassian.net/browse/TICKET-ID): <ticket title>` link already used elsewhere in the PR body (under "Implements") — the ticket grouping _is_ the scenario grouping, not a separate numbered list.
- Under each ticket bullet, a nested "Test map(s)" list: `[<UID> - main](<netlify-url>#/<UID>)`, keyed by a real DHIS2 map/dashboard-item UID on the pinned Netlify preview. When the "Dashboard tested" checklist item applies, add a paired link with `?interpretationId=<id>` labeled `- plugin` right next to the `- main` one.
- A screenshot per scenario block (`<img height="250" alt="image" src="..." />`) as the visual "expected result" — never write textual "expected result:" prose instead.
- Use `&nbsp;` as a spacer line between ticket blocks (cosmetic, matches the real convention).
- No "Scenario 1 / Scenario 2" labeling, no numbered click-path steps.

You'll be given the ticket list, the Netlify PR number(s)/instance URL, and real map/dashboard UIDs by the skill that invoked you — never invent a UID or fabricate a screenshot. If a UID or screenshot wasn't supplied for a ticket, leave that part as an explicit placeholder and say so, don't skip the bullet silently.
62 changes: 62 additions & 0 deletions .claude/commands/sonarqube-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# SonarQube Issue Resolution Workflow

Fix SonarQube quality gate issues for the current branch's PR, fetching issues directly from the SonarCloud API and addressing them in priority order.

**Note**: this project (`dhis2_maps-app` on SonarCloud) is public, so every call below works anonymously — no `SONAR_TOKEN` needed. There is no local scanner/CI step to run — analysis happens automatically server-side on every push (SonarCloud "Automatic Analysis"), so there's no local "publish" step, just push and wait.

## Instructions

### 1. Identify the PR

```bash
gh pr view --json number,title -q '"#\(.number) \(.title)"'
```

No `gh`/`GH_TOKEN` set up yet? Use the public GitHub REST API instead:

```bash
git remote get-url origin # → parse org/repo
curl -s "https://api.github.com/repos/<org>/<repo>/pulls?state=open&head=<org>:$(git rev-parse --abbrev-ref HEAD)" \
| jq -r '.[0].number'
```

### 2. Fetch and prioritize issues

```bash
curl -s "https://sonarcloud.io/api/issues/search?componentKeys=dhis2_maps-app&pullRequest=<pr-number>&resolved=false&ps=100" \
| jq -r '.issues[] | "\(.severity) - \(.type) - \(.message) - \(.component):\(.line)"' | sort
```

Fix order: **BLOCKER → CRITICAL → MAJOR → MINOR → INFO**, and within a severity, BUG before CODE_SMELL. Group by rule — fix every instance of the same rule together.

Create a todo list (TodoWrite) with every issue before starting fixes, so progress stays visible.

### 3. Fix in priority order

For each issue: read the file for context, understand what's being flagged and why, apply the minimal fix that addresses it — don't refactor beyond what's reported.

### 4. Test after each batch

After every 3-5 related fixes:

```bash
yarn lint && yarn test
```

Fix regressions immediately rather than accumulating unverified changes.

### 5. Push and let Automatic Analysis catch up

There's no local scanner to run. Push the commit, then re-poll the PR-scoped issues endpoint from step 2 — SonarCloud's GitHub App re-analyzes automatically on push, usually within a couple of minutes. Poll every 15-20s, capped at ~5 minutes so a stuck webhook doesn't hang the workflow.

### 6. Done when

- All todos completed
- `yarn lint && yarn test` pass
- The PR-scoped issues query returns none of the issues you fixed

## Troubleshooting

- **No matching PR** — confirm the branch has an open PR (`gh pr list` or the GitHub UI).
- **Issues still show after pushing** — re-analysis can lag a few minutes; re-poll rather than assuming the fix didn't take. If issues persist past ~5 minutes, check the PR's checks tab for a failed analysis run.
- **`gh` not authenticated** — not required; all the calls above work anonymously against this public repo.
23 changes: 23 additions & 0 deletions .claude/hooks/post-edit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -uo pipefail

input=$(cat)
file=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty')
[ -n "$file" ] && [ -f "$file" ] || exit 0

cd "${CLAUDE_PROJECT_DIR:-.}" || exit 0

case "$file" in
*.js | *.jsx | *.css | *.json | *.md | *.yml | *.yaml) ;;
*) exit 0 ;;
esac

output=$(yarn d2-style apply "$file" 2>&1)
status=$?

if [ "$status" -ne 0 ] || printf '%s' "$output" | grep -q '\[warn\]'; then
notes=$(printf '%s\n' "$output" | grep -vE '^\$ |^yarn run|^Done in|^info Visit|^\s*$')
jq -n --arg ctx "$notes" '{hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: $ctx}}'
fi

exit 0
27 changes: 27 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"permissions": {
"allow": [
"mcp__grep__*",
"mcp__plugin_context7_*",
"mcp__plugin_chrome-devtools-mcp_*"
]
},
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/post-edit.sh"
}
]
}
]
},
"enabledPlugins": {
"chrome-devtools-mcp@claude-plugins-official": true,
"context7@claude-plugins-official": true
},
"enabledMcpjsonServers": ["grep"]
}
59 changes: 59 additions & 0 deletions .claude/skills/branch-update/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
name: branch-update
description: Bring the current feature branch up to date with master by merging (not rebasing) origin/master in — this team's real convention — then resolve any conflicts. Creates a merge commit, so only run when the user explicitly asks; notice and mention a stale branch, don't auto-run this.
disable-model-invocation: true
---

# Branch update

This repo's real convention is **merge master into the feature branch**, repeatedly, during development — not rebase. The whole branch gets squash-merged into master via the PR at the end, so the intermediate merge commits never land on master's mainline; they only exist in the feature branch's own history. Don't rebase, and don't "clean up" these merge commits afterward — they're the norm, confirmed by real history (e.g. `Merge remote-tracking branch 'origin/master' into chore/setup-claude`).

This skill creates a merge commit. `CLAUDE.md`'s "don't stage or commit unless explicitly asked" applies to merge commits too — that's why this skill requires explicit invocation. If you notice a branch is stale, say so; don't run this unasked.

## Steps

1. **Fetch.**
```
git fetch origin master
```
2. **Check how stale you actually are** before merging blindly:
```
git log --oneline HEAD..origin/master
git log --oneline origin/master..HEAD
```
Skip the merge entirely if there's nothing new to bring in.
3. **Merge.**
```
git merge origin/master
```
4. **No conflicts** — done. Mention the new merge commit exists; pushing it is a remote write and per this repo's universal rule needs its own explicit, in-the-moment ask, same as any other push.
5. **Conflicts** — see below. Never resolve by blindly taking "ours" or "theirs" wholesale; read both sides first.

## Resolving conflicts

For each conflicted file:

1. Read the full conflict region plus enough surrounding context to understand _why_ each side changed it — not just what the diff lines say. `git log -p` on the conflicting commits from each side shows intent.
2. Prefer the minimal resolution that preserves _both_ changes' purpose — e.g. two additions to the same list/switch/reducer usually both belong, even when git can't auto-merge the surrounding lines.
3. After resolving, remove the conflict markers completely and re-read the result as if reviewing someone else's diff — a resolution that merges syntactically but silently drops one side's behavior is worse than an open conflict.
4. Run the touched files' tests (`npx jest <file>`) plus `yarn d2-style check <file>` on every file you resolved, then the full `yarn lint && yarn test` before considering the merge done.

### When to stop and ask instead of resolving

Stop and hand back to the user — don't guess — when a conflict is a genuine business-logic collision, not line-adjacency noise: both sides changed the _behavior_ of the same function/condition in ways that don't obviously compose (one side changed a threshold, the other changed the formula it feeds into; one side removed a code path the other just extended). Signs it's this kind, not a mechanical one:

- Resolving it requires deciding which behavior is "more correct," not just how to combine two edits.
- The two sides touch the same logic for unrelated reasons (different tickets), and combining them isn't obviously safe without domain knowledge you don't have.

In that case, **leave the conflict markers in place** — don't `git merge --abort` unless the user asks you to. Run `git status` to show which files are still unresolved, describe what each side was trying to do, and ask the user how to reconcile them. Aborting loses the "you got this far" context for no benefit once you've already identified the ambiguity; leaving it in progress preserves both sides' intent for the user to inspect directly.

## Done when

- `git status` shows a clean merge (no unresolved paths).
- `yarn lint && yarn test` pass.
- The merge commit's message is left as git's default (`Merge branch 'master' into <branch>` / `Merge remote-tracking branch 'origin/master' into <branch>`) — don't rewrite it to Conventional Commits format. These merge commits are exempt: they never land on master's mainline after the eventual squash-merge, and this team's real history confirms the default message is what's actually used.

## Related

- `commit-and-pr-messages` — for the _feature_ commits this branch carries, not the merge commit created here.
- `pr-chain` — has its own, more involved version of this problem once a stack member actually squash-merges (a plain merge stops working at that point; see that skill's `references/squash-merge-sync.md`).
Loading
Loading