← Back to Skills

Tawa PM — Project Planning with Forgejo

Plan and execute work using Forgejo as the source of truth. No local markdown files to overwrite — everything lives in the repo's milestones and issues.

Commandplatform

About

Plan and execute work using Forgejo as the source of truth. No local markdown files to overwrite — everything lives in the repo's milestones and issues.

Skill Content

This is the raw markdown that gets installed as a Claude Code command.

---
description: Plan and track work using Forgejo milestones and issues. Creates milestones for phases, issues for tasks, and labels for status. Use for any multi-step feature, epic, or sprint planning.
---

# Tawa PM — Project Planning with Forgejo

Plan and execute work using Forgejo as the source of truth. No local markdown files to overwrite — everything lives in the repo's milestones and issues.

## Core Principles

1. **Forgejo is the system of record** — milestones, issues, comments. Never local markdown.
2. **Re-read before deciding** — always fetch milestone status before major decisions (prevents stale context).
3. **Log as you go** — after every 2 significant actions, post a progress comment on the active task issue.
4. **Never repeat failures** — if an approach fails, log it as an issue comment before trying a different approach.
5. **Ready means ready** — a task is only `ready` when ALL its dependencies are closed AND its phase is active.

## The Flow This Skill Runs

A portal ticket is the functional ask. Forgejo holds the technical plan. The skill's job is to
carry the work between them without either side losing the other. Every command below is one
step of this loop; run them in this order and the ticket, the milestone and the approval stay
one thread.

| # | Step | Who | Command | Status today |
|---|------|-----|---------|--------------|
| 1 | A ticket is raised in the portal | Requester | (portal, or `create_task source:'portal'`) | live |
| 2 | Ticket becomes a milestone + issues; ticket is linked to the milestone | Developer | `/tawa-pm plan` | live |
| 3 | Milestone is handed to the analyst as an approval subtask on the ticket | Developer | `/tawa-pm approve` | live |
| 4 | Analyst reviews and **amends** the plan | Analyst | edit the milestone/issues, then `/tawa-pm refresh`; or `/tawa-pm changes` to send it back | live (jci-mcp#78, 2026-09-10) |
| 5 | Analyst approves | Analyst | complete the approval subtask (`complete_task`, or the portal) | live |
| 6 | Developer is told the decision landed | System | portal notification on the parent ticket, automatic | live (iec-portal#378, 2026-09-10) |
| 7 | Work proceeds; a **second** approval gates the deploy | Developer → Analyst | `/tawa-pm next` … `/tawa-pm done`, then `/tawa-pm ship` | live |

Two things to hold onto:

- **Plan approval and deploy approval are separate ticks.** Approving the milestone says the
  scope is right. Approving the deploy says this exact commit may ship. One never implies the other.
- **The ticket is the thread.** Approvals are subtasks *on the ticket*, never new tickets. The
  parent shows a derived "Waiting on …" badge while a subtask is open; do not move it by hand.

### Step 4 — amending a plan under review

The brief on the approval subtask is rendered from the milestone at a point in time, and a
footer line in it says when. Three tools keep it honest:

- **Scope changed in Forgejo?** Run `refresh_approval_subtask({ taskId })`. It re-reads the
  milestone and its issues, rewrites the brief, and posts **one** comment listing what moved
  (issues added, removed, closed, reopened; scope, due, title). An unchanged milestone writes
  nothing. Run it with `dryRun: true` to preview.
- **Approver wants changes, not approval?** `request_changes_on_approval({ taskId, reason })`.
  It posts "Changes requested by <you>: <reason>", leaves the subtask open (so the parent stays
  blocked), and flips the watch so the item now surfaces on the developer's side. Never
  complete the subtask to say "no".
- **Developer applied the change?** Fix the milestone and issues, run `refresh_approval_subtask`,
  then hand it back: `update_task({ taskId, waitingOn: 'them' })`.
- **Approving a brief that went stale?** `complete_task` compares the milestone's last change
  against the brief's render time. If the milestone moved after the brief was rendered it still
  completes, but posts a warning comment and returns `staleBrief: true` — surface that to the
  user and offer a refresh before treating the approval as final.

Briefs created before 2026-09-10 carry no render footer; refresh one once and the stale check
starts working for it.

### Step 6 — the developer hears back

Automatic. When a blocking subtask closes, the portal files a `blocker_closed` notification
against the **parent** ticket, so its assignee, reporter and collaborators are told — and the
notification says whether the parent is now unblocked or still waiting on other approvals.
Reopening a closed blocker sends the mirror. Nothing to do by hand; do not comment on the parent
just to notify, the system already did.

## How It Works

| Concept | Forgejo Backing | Example |
|---------|----------------|---------|
| Epic/Project | Milestone | `PolicyEco Versioning v1` |
| Phase | Issue with `phase` label | `Phase 1: SDK Consolidation` |
| Task | Issue linked to milestone | `Add tawa/testing export path` |
| Status | Labels | `status:backlog`, `status:in-progress`, `status:review` |
| Decisions | Issue comments | Architecture decision logged as comment |
| Progress | Milestone % complete | 4/12 issues closed = 33% |
| Discovery | New issue with `discovered-from: #N` | Found during work on #16 |

## Workflow

### 1. PLAN — Break work into a milestone + issues

When the user says `/tawa-pm plan <description>`:

0. **Start from the portal ticket.** The plan is the technical answer to a functional ask that
   already exists as a portal ticket. Find it before creating anything — `get_task_details`
   if the user gave a key (`INSURECO-22`), else `search_tasks` / `list_portal_tasks` on the
   description. Read its notes: that is the scope you are translating. If no ticket exists,
   say so and create one with `create_task` (`source: 'portal'`) before continuing — a
   milestone with no ticket has nobody to approve it in step 1b.
1. **Identify the target repo** — ask if not obvious from the working directory
2. **Check existing milestones** — `mcp__jci-mcp__git_milestone_list` to avoid duplicates.
   Also check `list_portal_tasks({ gitRepo })` — if the ticket already carries a `gitRef`
   for this repo, the milestone exists; extend it rather than creating a second.
3. **Create a milestone** — `mcp__jci-mcp__git_milestone_create` with title, description,
   optional due date. The description's first line names the ticket it answers:
   `Answers portal ticket INSURECO-22 — <ticket title>`. A reader of the milestone must be
   able to find the ask; a reader of the ticket must be able to find the plan.
4. **Link the milestone back to the ticket** — immediately, not at approval time:
   ```javascript
   update_task({ taskId: "<ticket>", gitRepo: "insureco/<repo>", gitMilestoneId: <id> })
   ```
   This stamps the ticket's `gitRef`, which is what `create_approval_subtask` matches on
   first (before service, before text) and what the portal's Git chip shows. Skip it and the
   approval step has to guess which ticket the milestone belongs to.
5. **Break into phases** — create one issue per phase with the `phase` label, assigned to the milestone
6. **Break phases into tasks** — create issues for each task, assigned to the milestone, labeled by type
7. **Present the plan** — show the milestone URL, the issue list, and the ticket it is linked to
8. **CHECKPOINT** — WAIT for user confirmation before creating any code. Show:
   - Total phases and tasks created
   - Dependency graph summary
   - Estimated scope (small/medium/large per phase)
   - Ask: "Ready to start Phase 1, or want to adjust?"

Phase issues should use this body format:
```markdown
## Objective
What this phase achieves.

## Tasks
- [ ] Task 1 (#N)
- [ ] Task 2 (#N)

## Depends On
- Phase X (#N)

## Acceptance Criteria
- [ ] Criteria 1
- [ ] Criteria 2

## Checkpoint
Before moving to the next phase, confirm:
- [ ] All task issues closed
- [ ] Tests passing
- [ ] No regressions in previous phases
```

Task issues should use this body format:
```markdown
## What
Brief description of the task.

## Why
How this connects to the phase/milestone goal.

## Acceptance Criteria
- [ ] Criteria 1

## Files Likely Affected
- `src/path/to/file.ts`

## Depends On
- #N (must be closed before this is ready)
```

### 1b. APPROVE — Get the plan signed off

When the user says `/tawa-pm approve` (or once a milestone is planned and ready for a decision):

The milestone you just created is a *proposal*. Turning it into agreed work means asking the person who requested it — and that request lands as a **subtask on their original portal ticket**, not as a new ticket, so the whole thread stays in one place.

```javascript
create_approval_subtask({ repo: "insureco/policypay", milestone: 25 })
```

That is the entire required input. **Do NOT pre-write a title or description** — the tool reads the milestone and its issues out of Forgejo and composes the brief itself: scope, a checklist of every issue with closed ones ticked, an approval checklist, and an explicit warning if issues are still open.

It works out for itself:

- **Approver** — whoever *filed* the parent ticket (falls back to Derek if unresolvable)
- **Due date** — the milestone's own
- **Project / client / service** — inherited from the parent ticket
- **Watch** — flagged as waiting on the approver, so it surfaces in a daily brief

Run it with `dryRun: true` first to show the user what would be created. Then call again to commit.

If you omit `parentTaskId` it searches the caller's open tickets: first those whose `gitRef` names this repo, then those linked to the service, then a text match. Step 4 of PLAN stamped the `gitRef`, so a planned ticket lands on the first tier. Exactly one match proceeds; 0 or 2+ returns candidates and writes nothing — it will not guess which ticket to attach an approval to.

**The parent ticket does not change status.** It gains a derived "Waiting on <name> · Nd" badge that clears itself when the approval is completed. Do not move the ticket to a "waiting" column by hand.

### 1c. REFRESH — Re-render an approval brief

When the user says `/tawa-pm refresh [ticket]` (or after anyone edits a milestone that has an
open approval subtask):

```javascript
refresh_approval_subtask({ taskId: "INSURECO-39", dryRun: true })   // preview
refresh_approval_subtask({ taskId: "INSURECO-39" })                 // commit
```

Returns `{ changed, diff, renderedAt, alreadyApproved }`. If `alreadyApproved` is true the
subtask was completed against the old brief — tell the user, and suggest a new approval if the
change is material.

### 1d. CHANGES — Send an approval back

When the approver says the plan is not right yet:

```javascript
request_changes_on_approval({ taskId: "INSURECO-39", reason: "Split phase 2; the migration needs its own approval" })
```

The subtask stays open, the parent stays blocked, the watch moves to the developer. When the
developer has amended the milestone: `refresh_approval_subtask`, then
`update_task({ taskId, waitingOn: 'them' })` to hand it back to the approver.

### 2. STATUS — Check progress on a milestone

When the user says `/tawa-pm status [repo]`:

1. **List open milestones** — `mcp__jci-mcp__git_milestone_list`
2. **For each milestone** — `mcp__jci-mcp__git_milestone_issues` to get open/closed counts
3. **Resolve readiness** — for each open task, check if dependencies are met
4. **Show progress** — milestone name, % complete, ready tasks, blockers, next actions

Output format:
```
## PolicyEco Versioning v1 (4/12 tasks, 33%)

Phase 1: SDK Consolidation — IN PROGRESS (2/4 tasks)
  ✓ #14 Create src/testing/ directory
  ✓ #15 Add exports map to package.json
  → #16 Migrate three-tier routing [in-progress]
  ○ #17 Update scaffold imports [blocked by #16]

Phase 2: Binary Compilation — PENDING (0/3 tasks)
  ◆ #18 Research bun compile [ready — no blockers]
  ○ #19 Set up build pipeline [blocked by #18]
  ○ #20 Test cross-compilation [blocked by #19]

Ready now: #18 (Phase 2 can start in parallel)
Blocked: #17 (waiting on #16), #19 (waiting on #18)
```

### 3. NEXT — Pick up the next ready task

When the user says `/tawa-pm next [repo]`:

1. **Re-read milestone status** — always fetch fresh state first
2. **Resolve the dependency graph** — find all open tasks, check which have ALL dependencies closed
3. **Pick the highest-priority ready task** — priority order:
   - Tasks in the earliest incomplete phase
   - Tasks with no dependencies (can start immediately)
   - Tasks that unblock the most other tasks
4. **Set it to in-progress** — remove `status:backlog`, add `status:in-progress`
5. **Present the task** — show the issue body, acceptance criteria, and what this unblocks
6. **Optionally start work** — if user confirms, begin implementation

A task is **ready** when:
- All issues listed in its "Depends On" section are closed
- Its parent phase is the current or an earlier phase
- It is not already `status:in-progress` by someone else

### 4. DONE — Complete a task

When the user says `/tawa-pm done [#issue]`:

1. **Close the issue** — `mcp__jci-mcp__git_issue_update` with state: closed
2. **Remove status labels** — clear `status:in-progress`
3. **Check newly unblocked tasks** — find tasks that depended on this one, mark them ready
4. **Update phase progress** — check if all tasks in the phase are done
5. **If phase complete** — close the phase issue, run phase checkpoint:
   - Post a comment on the phase issue summarizing what was done
   - Ask: "Phase N complete. Move to Phase N+1?" (CHECKPOINT)
6. **Show updated status** — milestone progress with newly ready tasks highlighted

### 4b. SHIP — Get the deploy approved

When the work is merged and ready to go to production, and the service is **gated** (see below).

**Merging is not gated — it stays autonomous.** `main` is not production; the builder only ships when a deploy is actually run. The approval sits on the *deploy*, where the real-world consequence is.

```javascript
create_deploy_approval_subtask({ repo: "insureco/iec-portal", service: "iec-portal" })
```

`candidateSha` defaults to current `main` HEAD — what `tawa deploy --prod` would ship right now.

The brief it composes lists every commit between what is *currently live* and the candidate, with authors, and warns when the deploy carries work from more than one person:

```
> This deploy carries work from 3 people — not only the change that
> prompted this request. Approving ships all of it.
```

That disclosure is the point. `tawa deploy --prod` ships `main` at its current SHA, so a deploy carries **everything merged since the last one**, not just your ticket's work.

**An approval names a commit** — not a PR, not a branch. It is **single-use** and **expires after 24 hours**. If `main` moves on before you deploy, the old approval no longer matches and you need a new one.

Once the approver completes the subtask, `tawa deploy --prod` proceeds. The builder verifies the approval matches the exact commit and burns it.

#### Which services are gated

Gating is per service, toggled at **Console → Platform → Operations → Deploy Approvals**.

Currently gated: `bio` · `septor` · `relay` · `iec-cron` · `iec-fantasy`

Everything else deploys exactly as before — for those, skip this step and just deploy.

A gated deploy without an approval fails in the build pipeline with the commit and the fix in the message. That is expected behaviour, not a broken build.

#### If a gated service must ship immediately

Break-glass: un-gate, deploy, re-gate. Every toggle is audited.

```bash
curl -X PATCH https://builder.tawa.pro/services/<service> \
  -H "x-internal-key: $INTERNAL_SERVICE_KEY" \
  -H 'Content-Type: application/json' -d '{"deployApprovalRequired": false}'
tawa deploy --prod
# then set it straight back to true
```

Prefer completing the approval subtask through JCI (`complete_task`) where you can — it uses a shared secret rather than Bio-ID, so it usually works even when the portal UI does not, and it records a real approval instead of removing the requirement.

### 5. DECISION — Log an architecture decision

When the user says `/tawa-pm decision <description>`:

1. **Find the active milestone**
2. **Create a comment on the milestone's parent issue** (or a dedicated decisions issue)
3. **Format as ADR-lite**:
```markdown
### Decision: [Title]
**Date:** 2026-03-15
**Status:** Accepted

**Context:** Why this decision was needed.
**Decision:** What we decided.
**Rationale:** Why this over alternatives.
**Consequences:** What changes as a result.
```

### 6. DISCOVER — Log work found during implementation

When new work is discovered while implementing a task, don't just silently add it — track the lineage:

1. **Create a new issue** with `discovered-from: #N` in the body (where N is the task you were working on)
2. **Assign to the current milestone** (or a future one if out of scope)
3. **Add a comment on the source issue** — "Discovered #M while working on this"
4. **Label appropriately** — `feature`, `bug`, `debt`, etc.

This preserves the "why" — future you (or Chaac) can trace how work emerged.

### 7. EXPLORE — Ephemeral investigation (wisp)

When the user says `/tawa-pm explore <question>`:

For time-boxed investigation that may or may not produce a task:

1. **Create a comment on the active task** — "Exploring: <question>"
2. **Do the investigation** (read code, search, prototype)
3. **Post findings as a comment** — what you learned, whether it spawns new work
4. **If new work found** — use DISCOVER flow above
5. **If dead end** — post "Explored <question> — no action needed. Reason: ..."

Explorations are ephemeral — they live as comments, not issues, unless they produce actionable work.

## Progress Logging Protocol

During implementation, keep the issue thread as a living log:

- **After every 2 significant actions** (file edits, test runs, discoveries): post a brief progress comment on the active issue
- **On errors or wrong turns**: post what happened and what you'll try instead — this prevents repeating failures across sessions
- **Before major decisions**: re-read the milestone and phase issues to refresh context

Comment format:
```markdown
**Progress:** Completed X, working on Y.
**Files touched:** `src/foo.ts`, `src/bar.ts`
**Next:** Z
```

Or for errors:
```markdown
**Attempted:** X approach
**Result:** Failed because Y
**Next:** Trying Z instead
```

## Labels Used

Create these labels if they don't exist:

| Label | Color | Purpose |
|-------|-------|---------|
| `phase` | `#6366f1` | Marks an issue as a phase (parent of tasks) |
| `feature` | `#22c55e` | New functionality |
| `bug` | `#ef4444` | Something broken |
| `infra` | `#8b5cf6` | DevOps, config, deployment |
| `debt` | `#f59e0b` | Technical debt cleanup |
| `status:backlog` | `#64748b` | Not started |
| `status:in-progress` | `#2563eb` | Currently working |
| `status:review` | `#d97706` | In review |
| `status:blocked` | `#dc2626` | Blocked by dependency |

## Cross-Repo Planning

For work spanning multiple repos (like PolicyEco which touches tawa-cli, iec-koko, iec-builder, iec-test):

1. **Create milestone in the primary repo** (where most code changes happen)
2. **Create issues in the repo where the code lives** — reference the primary milestone in the issue body
3. **Use cross-references** — `insureco/iec-koko#5` syntax to link across repos
4. **Track in primary milestone description** — list all cross-repo issues

## Anti-Patterns

| Don't | Do Instead | Why |
|-------|-----------|-----|
| Start coding without an issue | `/tawa-pm next` or create issue first | No traceability, work gets lost |
| Skip the phase checkpoint | Confirm before moving phases | Regressions compound across phases |
| Create tasks without "Depends On" | Always declare dependencies | `/next` can't resolve readiness without them |
| Silently fix a discovered bug | Use DISCOVER flow | Lineage is lost, scope creep is invisible |
| Retry same failed approach | Log the failure, try different approach | Wastes time, no learning captured |
| Plan in local markdown files | Everything in Forgejo issues | Local files get overwritten, no collaboration |
| Deploy a gated service without approval | `/tawa-pm ship` first | The build is refused; you lose the round trip |
| Pre-write the approval title/description | Pass only repo + milestone/service | The tool composes a better brief than you will |
| Move a blocked ticket to a "waiting" column | Leave the status alone | The badge is derived and clears itself; a moved card has to be moved back |
| Guess which ticket an approval belongs to | Pass `parentTaskId` explicitly | Wrong attachment blocks work that was not blocked |
| Plan a milestone with no portal ticket behind it | Find or create the ticket first (PLAN step 0) | Nobody is on the hook to approve it; the ask and the plan drift apart |
| Leave the ticket unlinked until approval | `update_task` with `gitRepo` + `gitMilestoneId` right after the milestone exists | The approval tool then has to guess the parent; the Git chip stays empty |
| Put 20+ tasks in one phase | Max 7 tasks per phase | Phases should be completable in 1-2 sessions |
| Create issues without acceptance criteria | Every issue needs "done" definition | No way to verify completion |

## Integration with Other Skills

| After planning | Use |
|---------------|-----|
| Starting a task | `/tdd` for test-driven development |
| Code written | `/code-review` for review |
| Build fails | `/build-fix` to resolve |
| Plan agreed? | `/tawa-pm approve` — sign-off on the milestone |
| Analyst changed the scope | `/tawa-pm refresh` — `refresh_approval_subtask`, re-render the brief and comment the diff |
| Analyst wants changes | `/tawa-pm changes <reason>` — `request_changes_on_approval`, send it back without completing |
| Ready to deploy (gated service) | `/tawa-pm ship` — sign-off on the commit, THEN `tawa deploy --prod` |
| Ready to deploy (ungated) | `tawa deploy --prod` |
| Task complete | `/tawa-pm done #N` |

## Example Usage

```
User: /tawa-pm plan PolicyEco versioning for tawa-cli

Claude: Creates milestone "PolicyEco Versioning v1" in tawa-cli repo,
        creates phase issues and task issues, presents the full plan.
        CHECKPOINT: "5 phases, 18 tasks created. Ready to start Phase 1?"

User: /tawa-pm next

Claude: Resolves dependency graph, finds highest-priority ready task,
        shows acceptance criteria and what completing it unblocks.

User: /tawa-pm status

Claude: Shows all milestones with progress, ready tasks, and blockers.

User: /tawa-pm done #16

Claude: Closes issue, finds newly unblocked tasks, checks phase completion.
        If phase done: CHECKPOINT before moving to next phase.

User: /tawa-pm explore "Can bun compile handle dynamic imports?"

Claude: Investigates, posts findings as comment on active task.
        If actionable: creates new issue with discovered-from link.

User: /tawa-pm decision "Use bun compile over pkg for binary"

Claude: Posts ADR-lite comment on the milestone's phase issue.
```

Install

Copy the skill content and save it to:

~/.claude/commands/tawa-pm.md
Download .md

Coming soon via CLI:

tawa chaac install tawa-pm

Details

Format
Command
Category
platform
Version
1.0.67788
Tokens
~5,758
Updated
2026-10-13
platform