Audience: human developers and AI coding agents who file issues in any
threecorp/extremo*repository. Scope:extremo,extremo-api,extremo-db,extremo-proto,extremo-view. Central project board: Extremo Platform (org-level Project #1).
All issues filed in these repositories MUST follow this guide. The rules exist so that the cross-repo project board stays usable — search, filter, roadmap views, sub-issue hierarchies, and automations all depend on consistent metadata.
| # | Metadata | Rule |
|---|---|---|
| 1 | Title | Start with a [Area] tag, e.g. [booking] Fix double-booking race. Use English. |
| 2 | Type (GitHub Issue Type) | One of Task, Bug, Feature. Never leave empty. |
| 3 | Labels | Pick from the shared taxonomy in §3. At least one label beyond the type. |
| 4 | Project | Link to threecorp/projects/1 (Extremo Platform) — auto if Auto-add workflow is on. |
| 5 | Status | Set in Project. New items default to Backlog. |
| 6 | Priority | Set in Project (P0–P3). Required if Status ≥ Ready. |
| 7 | Size | Set in Project (XS–XL). Required if Status ≥ Ready. |
| 8 | Area | Set in Project single-select. Should match the title tag. |
| 9 | Iteration | Set only when the issue is committed to a sprint. |
| 10 | Milestone | Use release or epic-level milestones when applicable. See §5. |
| 11 | Parent / sub-issues | If this is part of a larger initiative, link as a sub-issue of the tracking parent. See §6. |
| 12 | Development | Link related PRs, branches, and commits via the native Development sidebar. See §7. |
| 13 | Body | Use the template for the chosen Type (Task/Bug/Feature). See §8. |
| Repository | Stack | Responsibility |
|---|---|---|
extremo |
Flutter/Dart | Mobile app (iOS / Android / macOS / Web) |
extremo-api |
Go + gRPC | Backend API server |
extremo-db |
MySQL + sqldef | Database schema |
extremo-proto |
Protobuf + Buf | Shared API definitions |
extremo-view |
Remix + React + TS | Web frontend |
Cross-project changes must be ordered DB → proto → api → view → mobile. If an issue touches multiple repos, create a parent issue in the most-upstream repo and sub-issues in each downstream repo. Sub-issues can be cross-repo (see §6).
The threecorp organization uses the three default issue types. Select exactly one when creating any issue.
| Type | When to use |
|---|---|
| Task | A defined unit of work with clear acceptance criteria: refactors, chores, tech-debt cleanup, migrations, test additions, DB schema changes, proto updates, build/CI changes. |
| Bug | Unexpected behavior in existing functionality. Includes regressions, crashes, wrong output, performance regressions, security defects. |
| Feature | New user-visible capability or API. Anything that extends the product surface area. |
Type is set via the Issue sidebar → Type field, not via labels. The legacy bug/enhancement labels are kept for backward compatibility but should be considered deprecated — set the Type field instead.
Labels are orthogonal to the Type field and to the Project Area. Use as many as apply. Every label listed here exists in all 5 repos.
| Label | Meaning |
|---|---|
security |
Security-related: vulnerability, hardening, auth/authz concern, secrets, privacy leak. |
tech-debt |
Refactoring needed. Does not change behavior. |
architecture |
Affects module boundaries, layering, DI, cross-service contract. |
performance |
Latency, throughput, memory, bundle size, query cost. |
ui-ux |
Frontend layout, interaction, accessibility, copy. |
breaking-change |
Changes an API/behavior in an incompatible way. Requires coordinated rollout. |
needs-triage |
Not yet groomed. Blocks nothing but flags the issue for a maintainer pass. |
blocked |
Cannot proceed until an external dependency resolves. Must reference the blocker in the body. |
documentation, duplicate, good first issue, help wanted, invalid, question, wontfix — use as per their standard meaning. bug and enhancement still exist but prefer the Type field.
Stack labels to describe the issue fully:
- A security bug that also indicates refactoring:
security+tech-debt. - A cross-service breaking change in the booking domain:
breaking-change+architecture. - A documentation-only PR:
documentation.
Priority, Size, and Status are Project fields — set them there, not as labels. Having two sources of truth breaks filtered views.
Every issue that lives on the Extremo Platform project board (i.e. all issues filed in extremo* repos) MUST carry the four roadmap-axis labels in addition to the cross-cutting labels above. The org-level Project #1 README is the canonical source for the label values; this section pins the rules that govern which labels to set.
| Axis | Label prefix | Cardinality | Meaning |
|---|---|---|---|
| Epic | epic:* |
exactly 1 | Roadmap epic. Pick from the list defined in the Project #1 README (e.g. epic:blueprinting). |
| Tier | tier:梅 / tier:竹 / tier:松 |
1, 2, or 3 (cumulative — see below) | Release tier in which the work first becomes necessary. |
| Phase | phase:A-foundation … phase:J-platform |
exactly 1 | One of the 10 phase epics. |
| Feature | feature:F-* |
exactly 1 | Vertical/foundation feature group (e.g. feature:F-Stripe). The Project #1 README maintains the authoritative list. |
The tier:* labeling follows two distinct rules depending on whether the issue belongs to a Foundation feature or a Vertical feature. Decide which case applies first, then label accordingly.
Case 1 — Foundation features (F-Audit / F-EnumRollout / F-AttendanceBreak / F-NameMatching, 4 features only)
Foundation features are cross-tier infrastructure (audit log / enum rollout / attendance break / name matching). They are not scoped to one release tier — every tier consumes them. Therefore every Foundation issue carries all 3 tier labels regardless of milestone:
tier:梅 + tier:竹 + tier:松 (always 3, no exceptions)
This is what makes the Project #1 Foundation × 梅 / × 竹 / × 松 per-feature filters return the same issue set.
Case 2 — Vertical features (all features other than the 4 Foundation features — authoritative catalog in the Project #1 README) — standard cumulative rule
Vertical features are scoped to a release tier of first appearance. Tier labels are cumulative from that tier upward, never single-tier. Picking a tier means "this work is needed from this tier onward", which implies it is also needed in every later tier. Therefore:
| If the Vertical work first appears in… | Add all these tier:* labels |
|---|---|
| Tier 梅 (foundation parity) | tier:梅 + tier:竹 + tier:松 (3 labels) |
| Tier 竹 (beauty middleware) | tier:竹 + tier:松 (2 labels) |
| Tier 松 (full platform) | tier:松 (1 label) |
The Project board's Tier 梅 / Tier 竹 / Tier 松 sequential views rely on the cumulative semantics: "all issues that must be done by the time we ship 松" must include everything from 梅 and 竹 too. A Vertical tier 梅 issue with only tier:梅 silently disappears from the 竹 and 松 filtered views.
Note: a Vertical 梅 issue and a Foundation issue both end up with 3 tier labels, but the reason differs (Foundation = tier-agnostic by definition; Vertical 梅 = earliest tier cumulated upward). Determine the feature type first.
Conversely, the Milestone is set to the single tier you actually plan to ship the work in: v0.1-梅 / v0.2-竹 / v0.3-松. Tier labels = scope coverage; Milestone = ship target.
Worked example — Foundation feature (always 3 tier labels):
gh issue create -R threecorp/extremo-api \
--title "[booking] F-Audit follow-up: add admin filter for noshow events" \
--body-file /tmp/body.md \
--label "epic:f-audit" \
--label "phase:A-foundation" \
--label "tier:梅" --label "tier:竹" --label "tier:松" \
--label "feature:F-Audit" \
--milestone "v0.2-竹" # shipping in 竹, but tier coverage is all 3Worked example — Vertical 梅 feature (cumulative — same 3 labels, different reason):
gh issue create -R threecorp/extremo-view \
--title "[tenant] Add custom landing page slot" \
--body-file /tmp/body.md \
--label "epic:blueprinting" \
--label "phase:J-platform" \
--label "tier:梅" --label "tier:竹" --label "tier:松" \
--label "feature:F-CustomCMS" \
--milestone "v0.1-梅"If the same Vertical work were instead scoped to first appear in tier 竹, the labels would be tier:竹 + tier:松 (no tier:梅) and the milestone v0.2-竹.
- Foundation or Vertical? Foundation (4 features) → 3 tier labels always. Vertical → cumulative from first-appearance tier.
- If Vertical, is
tier:*cumulative? (1 label only ⇒ likely wrong unless it's pure tier:松 work) - Does the Milestone match exactly one of the tier labels? (
v0.1-梅↔tier:梅, etc.) - Is
feature:F-*listed in the Project #1 README? (If not, file a master-tracking discussion before proceeding.) - Is
phase:*set? (Missingphase:is the second-most-violated rule.)
When opening a PR that implements a roadmap-tracked issue, the PR must inherit these labels from the linked issue (see §7 for linking):
feature:F-*(which feature this PR delivers part of)tier:*(matching the linked issue's full tier set — Foundation: 3; Vertical: cumulative)phase:*(matching the linked issue)
Do not add epic:* to PRs (issue-only). Use Closes #<num> in the PR body so Project #1 automation moves Status → Done on merge. PRs do not get a milestone (the linked issue carries the milestone).
The canonical sources of truth for the roadmap and the labels above:
- Project #1 board + label catalog: https://github.com/orgs/threecorp/projects/1
- Master tracking issue: https://github.com/threecorp/extremo-spec/issues/1
- ROADMAP source of truth: https://github.com/threecorp/extremo-spec/blob/main/ROADMAP.md
- Parallel session onboarding: https://github.com/threecorp/extremo-spec/blob/main/docs/onboarding-tier-ume.md
- Phase / feature dependency map: https://github.com/threecorp/extremo-spec/blob/main/docs/dependencies.md
When feature:F-* values, phase definitions, or tier/milestone schemes change, update Project #1 README first, then this section.
All items on the Extremo Platform project have these fields.
| Field | Type | Values | Who sets |
|---|---|---|---|
| Status | single-select | Backlog / Ready / In Progress / In Review / Blocked / Done | Author sets Backlog. Automation advances through In Progress/In Review/Done. |
| Priority | single-select | P0-Urgent / P1-High / P2-Medium / P3-Low | Triage. Required before Ready. |
| Size | single-select | XS / S / M / L / XL | Triage / author estimate. |
| Area | single-select | auth / booking / attendance / schedule / tenant / chat / notification / infra / ci / docs / security / testing | Author. Must match the [Area] tag in the title. |
| Iteration | iteration | 2-week sprints | Sprint planning. Leave empty if backlog. |
| Start date | date | — | Set when Status = In Progress. |
| Target date | date | — | Set when committed to an iteration or milestone. |
| Value | Definition |
|---|---|
P0-Urgent |
Production outage, data loss, security breach in progress. Stops the current sprint. |
P1-High |
Blocks a release, affects many users, or a critical path dependency. Must ship this iteration. |
P2-Medium |
Should ship soon but the release can proceed without it. Default for most new work. |
P3-Low |
Nice-to-have. No deadline. |
| Value | Typical effort |
|---|---|
XS |
<1 hour. Config change, copy fix, single-line bug. |
S |
Half-day. One file or one test. |
M |
1–2 days. Contained change within one sub-project. |
L |
~1 week. Multi-file, possibly multi-repo but no schema change. |
XL |
>1 week. Must be broken into sub-issues before starting. |
Rule: an XL issue must be split into sub-issues and tracked as a parent.
| Area | Covers |
|---|---|
auth |
Login, identity, magic links, phone OTP, permission/authz, session, cookies. |
booking |
Reservation CRUD, draft→reserve→order lifecycle, booking search. |
attendance |
Staff shifts, availability, calendar sync for staff. |
schedule |
Slot/coops/hybrid scheduling algorithms, calendar rendering. |
tenant |
Tenant/store lifecycle, settings, profiles, addresses, branding. |
chat |
Messaging between tenants and clients. |
notification |
Email, push, in-app notifications. |
infra |
Deploy, Docker, Nix, flake.nix, env config, observability, Sentry. |
ci |
GitHub Actions, build pipelines, release automation. |
docs |
README, CLAUDE.md, this file, inline docs. |
security |
Cross-cutting security work that is not auth-specific. |
testing |
Test infrastructure, fixtures, coverage, E2E harness. |
If no area matches, propose a new one in a triage issue before filing.
Milestones track releases or epics. They are optional for individual issues but strongly recommended for features heading to a named release.
Naming conventions:
- Release milestones:
vX.Y.Z(semver) orYYYY-MM-sprint-Nif no semver tag exists yet. - Epic milestones:
epic: <short-name>— used when a body of work spans several iterations and cannot be captured as a single tracking issue with sub-issues.
Milestones live per repository. For cross-repo epics, prefer the parent issue + sub-issue pattern (§6) over duplicating a milestone in each repo.
Use sub-issues to break down any issue that cannot be finished by one person in one iteration.
- Create the parent issue first. Write the acceptance criteria and the decomposition plan in the body.
- Add children via Issue sidebar → Sub-issues → Add sub-issue. Children can live in a different repository (e.g., parent in
extremo-api, sub-issues inextremo-viewandextremo). - Limits: 50 sub-issues per parent, 8 levels deep.
- The Project board exposes
Parent issueandSub-issues progressfields; use them to filter/group.
When to use sub-issues vs. task lists: sub-issues have metadata (type, status, assignee, project linkage). Task lists (checkbox lists in the body) don't. Prefer sub-issues whenever each task deserves its own lifecycle.
For a feature that spans DB → proto → api → view → mobile:
- Create the tracking issue in the most-upstream repo that holds the contract change (usually
extremo-protoorextremo-db). - Set Type =
Feature, labelarchitecture, add to Project, set Area. - Add sub-issues in each affected repo with Type =
Task. Link their PRs. - The parent closes when all sub-issues close.
GitHub is rolling out typed dependencies; until then use:
- The
blockedlabel on the dependent issue, plus a body lineBlocked by #123(orthreecorp/other-repo#123for cross-repo). - Sub-issue relationships for decomposition, not for dependencies.
Set Project Status = Blocked while blocked; revert to Ready once unblocked.
The right-hand Development panel on an issue links branches and PRs. Use it — the project board's Linked pull requests column depends on it.
<type>/<issue-number>-<slug> — example: feat/75-scope-books-list, fix/85-abort-signal-typing.
Types: feat, fix, refactor, chore, docs, test, perf, ci.
On the issue sidebar, Development → Create a branch — GitHub creates the correctly named branch and links it automatically. If the branch already exists, Development → Link a branch.
The recommended way is to include one of the GitHub closing keywords in the PR body:
Closes #75
Fixes threecorp/extremo-view#85
Cross-repo closes are supported. When the PR merges, the linked issue auto-closes and the Project Item closed workflow sets Status = Done.
In commit messages, reference issues with #<num> (same repo) or <owner>/<repo>#<num> (cross-repo). Avoid closing keywords in commit messages — reserve those for the PR description so review comments gate the close.
The repos ship issue forms at .github/ISSUE_TEMPLATE/ that enforce the structure below. If you file an issue via the API, still follow the structure.
For roadmap-tracked issues (= epic:* set), the Related section MUST include a 親: line pointing to the Phase epic issue (e.g. extremo-db#10 for Phase A, extremo-api#87 for Phase B, etc.). The Phase epic issue numbers are listed in the extremo-spec Master tracking issue.
## Summary
<one-paragraph user-facing description>
## Motivation / Why
<problem it solves, metric it moves, decision record link>
## Acceptance criteria
- [ ] <testable criterion>
- [ ] <testable criterion>
## Design notes
<API/schema/UX sketches, links to proto/diagrams/screenshots>
## Out of scope
<explicit non-goals>
## Related
- 親: <repo>#<n> (Phase epic issue, mandatory if epic:* is set)
- Parent: #<n> (sub-issue parent, if different from Phase epic)
- Sub-issues: #<n>, #<n>
- Blocks / Blocked by: #<n>## Summary
<what's broken, one sentence>
## Reproduction
1. <step>
2. <step>
## Expected behavior
<what should happen>
## Actual behavior
<what happens, with error text / stack / screenshot>
## Environment
- Repo: <extremo-api/view/etc>
- Commit: <sha>
- Env: <local/stg/prod>
## Suspected root cause
<optional; link to code>
## Related
- Blocked by: #<n>## Objective
<what must be done>
## Scope
<files / modules / layers affected>
## Acceptance criteria
- [ ] <criterion>
## Notes
<references, prior art, migration considerations>
## Related
- 親: <repo>#<n> (Phase epic issue, mandatory if epic:* is set)
- Parent: #<n> (sub-issue parent, if different from Phase epic)Follow this exact sequence. It is idempotent and produces a fully-compliant issue.
-
Pick the repository based on where the change lands. Cross-repo work → file the parent in the upstream repo.
-
Search for duplicates first:
gh issue list -R threecorp/<repo> --search "<keywords>" --state all. Link the found issue instead of creating a new one. -
Draft the body using the §8 template for the chosen Type.
-
Create the issue with the
ghCLI:gh issue create \ -R threecorp/<repo> \ --title "[<area>] <imperative summary>" \ --body-file /tmp/issue.md \ --label "<label1>,<label2>"
-
Set the Type (not settable via CLI yet; use the GraphQL mutation below or the UI):
gh api graphql -f query=' mutation($issueId: ID!, $typeId: ID!) { updateIssueIssueType(input: { issueId: $issueId, issueTypeId: $typeId }) { issue { number issueType { name } } } }' -f issueId=<issue-node-id> -f typeId=<type-id>
Type IDs (org-level, stable):
Task:IT_kwDOAuF21c4AmJJRBug:IT_kwDOAuF21c4AmJJTFeature:IT_kwDOAuF21c4AmJJW
-
Add to Project #1 (if Auto-add isn't configured):
gh project item-add 1 --owner threecorp --url <issue-url>
-
Set Project fields via
gh project item-editor the GraphQLupdateProjectV2ItemFieldValuemutation. See §4 for field IDs. -
Link to a parent issue (if applicable) via the sub-issue UI or the REST endpoint:
gh api -X POST /repos/<owner>/<repo>/issues/<parent-number>/sub_issues \ -f sub_issue_id=<child-issue-id>
-
Create a branch from the issue (Development panel) before opening a PR, or ensure the PR body includes
Closes #<n>.
These IDs are stable but may change if a field is deleted and recreated. Re-fetch with
gh project field-list 1 --owner threecorp --format jsonif a call returnsfield not found.
Project: PVT_kwDOAuF21c4BU-OD
| Field | Field ID | Option IDs |
|---|---|---|
| Status | PVTSSF_lADOAuF21c4BU-ODzhPEq9M |
Backlog 3fc7321d / Ready 7cd48b6b / In Progress 377946c0 / In Review eb363c6d / Blocked 224a4dc5 / Done fe7d264a |
| Priority | PVTSSF_lADOAuF21c4BU-ODzhPOq3U |
P0-Urgent c917c49c / P1-High fa6b4dd8 / P2-Medium 42eefee0 / P3-Low 2f658f7b |
| Size | PVTSSF_lADOAuF21c4BU-ODzhPOr-A |
XS e5c1c5c2 / S b8135436 / M 6ee1db25 / L bf18d7c1 / XL e5d972ee |
| Area | PVTSSF_lADOAuF21c4BU-ODzhPOs8A |
auth a0fbbbae / booking 57bf83a1 / attendance 780327e3 / schedule a9992512 / tenant 45636914 / chat 297e8bf5 / notification 9c82de90 / infra 536755d7 / ci ab34fbb3 / docs c37d7690 / security 029cd318 / testing fe8538dc |
| Start date | PVTF_lADOAuF21c4BU-ODzhPOt_I |
— (date) |
| Target date | PVTF_lADOAuF21c4BU-ODzhPOvDI |
— (date) |
| Iteration | PVTIF_lADOAuF21c4BU-ODzhPO4Vo |
fetch via API |
- Filing an issue without a Type.
- Setting Priority/Size as labels instead of Project fields.
- Creating an
XLissue without sub-issues. - Cross-repo epics managed only by milestones (use parent/sub-issue).
- Closing an issue via commit message instead of via PR body.
- Duplicating a milestone across repos for the same epic.
- Using the body to hold acceptance criteria for multiple sub-features instead of creating sub-issues.
- Leaving Status = Backlog after work has started — always advance.
- Setting only one
tier:*label for a Vertical feature when the work first appears in tier 梅 or tier 竹 — tier labels are cumulative (see §3.5). A Vertical tier 梅 issue must carry all 3 tier labels. - Treating a Foundation feature issue with single-tier or partial-tier labels — Foundation issues always carry all 3 tier labels regardless of milestone (see §3.5 Case 1).
- Filing an issue without
phase:*/feature:F-*/epic:*/tier:*— all four roadmap axes are mandatory for any roadmap-tracked work (see §3.5). - Adding a brand-new
feature:F-*value without first updating the Project #1 README feature-vertical table and the existing-features list. The README is the canonical source.
This document lives at ISSUE_GUIDELINES.md in the threecorp/.github org-defaults repo. The org-level Project README links to it. When Project fields or labels change, update:
ISSUE_GUIDELINES.md(this file) inthreecorp/.github- The Project README at
orgs/threecorp/projects/1 .github/ISSUE_TEMPLATE/*.ymlinthreecorp/.github(org-level defaults; inherited by any extremo* repo that doesn't ship its own)