@gallopsystems/agent-skills 1.26.0 → 1.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: git-github
|
|
3
|
-
description: Git and GitHub (gh CLI) workflows for agents - the branch-to-PR loop, reading PR and CI state, debugging failed GitHub Actions runs, getting unstuck from rejected pushes and rebase messes, gh api recipes, and release flows.
|
|
3
|
+
description: Git and GitHub (gh CLI) workflows for agents - the branch-to-PR loop, stacked PRs with gh stack, reading PR and CI state, debugging failed GitHub Actions runs, getting unstuck from rejected pushes and rebase messes, gh api recipes, and release flows.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Git + GitHub Workflows
|
|
@@ -50,7 +50,7 @@ EOF
|
|
|
50
50
|
- Merge style: `gh pr merge <n> --squash --delete-branch`; verify with `gh pr view <n> --json state,mergedAt`.
|
|
51
51
|
- After merge: `git switch main && git pull --ff-only`, clean up `[gone]` branches, start the next branch from fresh main.
|
|
52
52
|
- One concern per PR — hotfixes and review findings go in separate PRs unless told otherwise.
|
|
53
|
-
- Stacked PRs: `gh
|
|
53
|
+
- **Stacked PRs: use `gh stack` (github/gh-stack), never hand-set a PR's base to another feature branch.** Stack only when the child truly depends on the parent; otherwise branch from main. Core loop: `gh stack init <b1>` → `gh stack add <b2>` → `gh stack submit --auto --open` → `gh stack sync` after merges → `gh stack merge <n> --yes --squash` (the user's call). Plain `gh pr merge` doesn't work on stacked PRs. Full playbook, agent flags and error table: [stacked-prs.md](stacked-prs.md).
|
|
54
54
|
- If you discover uncommitted work on the wrong branch and the PR must be "off main", do not commit to the wrong branch. With a cleanly applicable worktree, `git fetch origin main && git switch -c feat/<short-description> origin/main` carries the unstaged changes onto a new branch from `origin/main`. Verify with `git status` and tests. If checkout would overwrite/conflict, stash with `-u`. Only resort to worktree if stash gets too complicated.
|
|
55
55
|
|
|
56
56
|
## Reading PR and CI State
|
|
@@ -81,6 +81,7 @@ Do not bypass failing hooks with `--no-verify` unless the user says to.
|
|
|
81
81
|
|
|
82
82
|
- **Debugging failed Actions runs** (the full playbook): [actions-debugging.md](actions-debugging.md)
|
|
83
83
|
- **Repair ladders** — rejected pushes, blocked checkouts, rebase/conflict recovery, shallow clones, worktrees: [getting-unstuck.md](getting-unstuck.md)
|
|
84
|
+
- **Stacked PRs with `gh stack`**: build, adopt, rebase, merge, and fix the errors it throws: [stacked-prs.md](stacked-prs.md)
|
|
84
85
|
- **gh api recipes** — PR comments, reading files without checkout, repo settings, PAT gotchas: [gh-api-recipes.md](gh-api-recipes.md)
|
|
85
86
|
- **Releases & publishing** — tags, gh release, npm Trusted Publishing, release-please: [releases.md](releases.md)
|
|
86
87
|
- **External review loop** — using the codex CLI as an adversarial pre-merge reviewer: [external-review.md](external-review.md)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Stacked PRs with `gh stack`
|
|
2
|
+
|
|
3
|
+
Use the [`github/gh-stack`](https://gh.io/stacks) extension for any chain of dependent PRs. It tracks the chain locally, keeps PR bases right, and registers a native **stack** on GitHub. GitHub merges a stack atomically, bottom-up.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
gh extension list | grep stack || gh extension install github/gh-stack # needs gh >= 2.90
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Stack only when the child truly depends on the parent
|
|
10
|
+
|
|
11
|
+
If the follow-up doesn't need the parent's code, branch it from main and skip stacking. When it does depend, **never hand-set a PR's base to another feature branch** (`gh pr create --base <parent-branch>`):
|
|
12
|
+
|
|
13
|
+
- **Merge order decides what reaches main.** Merge the child first and it lands on the parent's branch, not main. It only reaches main if the parent is merged *afterwards*. Nothing warns you either way.
|
|
14
|
+
- **GitHub may lock hand-chained PRs.** It can group PRs whose bases chain into a stack object. After that, `gh pr edit --base`, REST and GraphQL all refuse with `Cannot change the base branch because the pull request is part of a stack`. Deleting a merged base branch then **closes** the child PR instead of retargeting it.
|
|
15
|
+
|
|
16
|
+
## Command map
|
|
17
|
+
|
|
18
|
+
| Command | Does |
|
|
19
|
+
|---|---|
|
|
20
|
+
| `gh stack init [b1 b2 …]` | Start a stack, or adopt existing branches bottom→top (missing branches are created, existing PRs are found). `--base <trunk>` for a non-default trunk. |
|
|
21
|
+
| `gh stack add <branch>` | Create a branch on top of the current stack and check it out. |
|
|
22
|
+
| `gh stack submit` | Push every branch, create missing PRs, fix bases, create/update the GitHub stack. |
|
|
23
|
+
| `gh stack push` | Push every branch only. No PR or stack changes. |
|
|
24
|
+
| `gh stack rebase` | Fetch trunk, then cascade-rebase every layer. Flags: `--upstack`, `--downstack`, `--no-trunk`, `--continue`, `--abort`. |
|
|
25
|
+
| `gh stack sync` | `rebase` + push + refresh PR state in one go (use after a PR merges). |
|
|
26
|
+
| `gh stack view` | Show the stack. `--short` or `--json` for parsing. `⚠` means the branch needs a rebase. |
|
|
27
|
+
| `gh stack checkout <stack#\|pr#\|url>` | Import a stack from GitHub (e.g. a teammate's) and set up local tracking. |
|
|
28
|
+
| `gh stack link <b\|pr> …` | Create or extend a GitHub stack from branches/PR numbers, with no local tracking. |
|
|
29
|
+
| `gh stack merge [<stack#\|pr#>]` | Atomic merge of the stack, up to and including the given PR. |
|
|
30
|
+
| `gh stack trunk` / `top` / `bottom` / `up` / `down` | Navigate. |
|
|
31
|
+
|
|
32
|
+
## Building a stack
|
|
33
|
+
|
|
34
|
+
**From scratch:**
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
git switch main && git pull --ff-only
|
|
38
|
+
gh stack init feat/<part-1> # bottom branch, based on main
|
|
39
|
+
# …commit…
|
|
40
|
+
gh stack add feat/<part-2> # next layer on top
|
|
41
|
+
# …commit…
|
|
42
|
+
gh stack submit --auto --open # push all, open PRs, create the stack
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Adopt branches (and PRs) that already exist:** `gh stack init b1 b2 b3` (bottom→top), then `gh stack submit --auto --open`. Submit reports `Updated base branch for PR #n …` for any wrong base and creates the stack. This is also how you rescue a hand-chained set of PRs. If a branch exists only on `origin`, create it locally first (`git branch <b> origin/<b>`). Otherwise submit fails with `src refspec refs/heads/<b> does not match any`.
|
|
46
|
+
|
|
47
|
+
**Without local tracking:** `gh stack link <bottom> … <top>` takes branch names or PR numbers. It pushes the branches, creates missing PRs with chained bases, and creates or extends the stack.
|
|
48
|
+
- Grow an existing stack with `gh stack link <stack#> <new-branch-or-pr>`.
|
|
49
|
+
- Re-running `link` with the full list is idempotent (`Stack with N PRs is already up to date`). Use it to re-register the chain after hand-rebasing branches.
|
|
50
|
+
- PRs that `link` creates are **drafts**, so run `gh pr ready <n>`.
|
|
51
|
+
- `link` leaves nothing tracked locally, so `gh stack view` then says `not part of a stack`. Run `gh stack checkout <stack#>` to import tracking.
|
|
52
|
+
|
|
53
|
+
## Non-interactive use (agents)
|
|
54
|
+
|
|
55
|
+
- `gh stack submit` opens a TUI editor in a terminal. Pass `--auto` to skip it and use auto-generated titles. Fix those titles and bodies afterwards with `gh pr edit <n> --title … --body-file …`.
|
|
56
|
+
- `--auto` creates **drafts** unless you also pass `--open`. `--open` also flips **existing** draft PRs in the stack to ready. Leave it off if a PR must stay a draft.
|
|
57
|
+
- Conflict continue without an editor: `git add <files> && GIT_EDITOR=true gh stack rebase --continue`.
|
|
58
|
+
- Output carries ANSI codes and pre-push hook banners. Filter with `sed 's/\x1b\[[0-9;]*m//g'`, or read `gh stack view --json`.
|
|
59
|
+
|
|
60
|
+
## Changing a stack
|
|
61
|
+
|
|
62
|
+
1. Fix each issue **on the branch that introduced it**, committing bottom-up.
|
|
63
|
+
2. From that branch, run `gh stack rebase --upstack` (or plain `gh stack rebase` for the whole stack, trunk included).
|
|
64
|
+
3. On conflict, resolve, `git add`, then `gh stack rebase --continue`. `gh stack rebase --abort` restores every branch.
|
|
65
|
+
4. Run the checks on the **top** branch. A resolution that compiles on its own layer can still break a test a layer up.
|
|
66
|
+
5. Run `gh stack push` (branches only) or `gh stack submit` (also updates PRs/stack).
|
|
67
|
+
|
|
68
|
+
`gh stack rebase` refuses a dirty tree (`cannot rebase: You have unstaged changes`), so commit or stash first.
|
|
69
|
+
|
|
70
|
+
## After a PR in the stack merges
|
|
71
|
+
|
|
72
|
+
Run `gh stack sync` (or `gh stack rebase` then `gh stack push`). Merged branches are skipped (`Skipping <b> (PR #n merged)`). The next layer is replayed onto trunk with only its own commits (`adjusted for merged PR`), so a squash-merged parent causes none of the usual duplicate-commit conflicts. Verify with `git log --oneline main..HEAD`.
|
|
73
|
+
|
|
74
|
+
If the bottom PR merged before the stack object existed, `submit` prints `Could not create stack: Pull request #n is merged`. That is harmless: the remaining PR simply targets main.
|
|
75
|
+
|
|
76
|
+
## Merging
|
|
77
|
+
|
|
78
|
+
**Merging is the user's call.** Permission classifiers block agent-initiated merges as "Merge Without Review". Hand the user the exact command to run, e.g. `! gh stack merge <n> --yes --squash`.
|
|
79
|
+
|
|
80
|
+
- **Plain `gh pr merge` doesn't work on a stacked PR.** Use `gh stack merge`.
|
|
81
|
+
- **`gh stack merge <n>`** merges everything up to and including PR `<n>`. A bare number is tried as a stack number first, then as a PR number.
|
|
82
|
+
- **Flags:** `--yes` plus `--squash`, `--merge` or `--rebase` (or `--merge-method <m>`). There is no `--method`. Without a method flag it reuses your last method.
|
|
83
|
+
- **All-or-nothing.** `merge failed: … has a merge conflict` then `Stack merges are atomic, so nothing was merged`. Rebase, push and retry.
|
|
84
|
+
- **A draft anywhere blocks it:** `cannot merge the whole stack: pull request #n is a draft`. Run `gh pr ready <n>` first.
|
|
85
|
+
- **Only open/not-draft is checked locally.** Branch protection and required checks are evaluated at merge time. Wait for CI on every PR first (`gh pr checks <n>` per PR).
|
|
86
|
+
- **CI may not run on upper PRs.** A workflow filtered to `pull_request: branches: [main]` doesn't run on PRs based on another stack branch. Those PRs only get checks once their base merges.
|
|
87
|
+
- **A single PR is not a stack.** `gh stack merge` says `#n is not a stack number or a stacked pull request`, so use `gh pr merge`.
|
|
88
|
+
- **Afterwards:** `gh stack trunk && git pull --ff-only`, then delete the merged local branches.
|
|
89
|
+
|
|
90
|
+
## Errors → fixes
|
|
91
|
+
|
|
92
|
+
| Error | Cause / fix |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `current branch "<b>" is not part of a stack` | Not tracked locally (fresh worktree, stack made with `link`, other clone). Run `gh stack init <b1> <b2> …` to adopt, or `gh stack checkout <stack#\|pr#>`. |
|
|
95
|
+
| `branch "main" belongs to multiple stacks; use an interactive terminal …` | You're on trunk. Check out a stack branch, or pass the number (`gh stack merge <stack#>`). |
|
|
96
|
+
| `… is already used by worktree at <path>` | `init`/`checkout` must switch branches, and another worktree holds that branch. Run from that worktree or remove it. Note that `checkout` may already have imported the stack before failing. |
|
|
97
|
+
| `branch "<b>" already exists in a stack` | An earlier (possibly half-finished) `init` tracked it. Use `gh stack checkout`. Don't `unstack` unless you mean it, because it also removes the stack on GitHub. |
|
|
98
|
+
| `✗ failed to push <b>: … failed to push some refs` (no detail) | Almost always the pre-push hook failed, and gh stack swallows its output. Run `git push origin <b>` to see it, fix it (often env vars the hook's tests need, which you then export in the same shell), and resubmit. |
|
|
99
|
+
| `src refspec refs/heads/<b> does not match any` | The branch exists only on the remote. Run `git branch <b> origin/<b>`. |
|
|
100
|
+
| `unknown flag: --method` | Use `--squash` / `--merge` / `--rebase` or `--merge-method`. |
|
|
101
|
+
|
|
102
|
+
## Several agents or worktrees on one repo
|
|
103
|
+
|
|
104
|
+
Stack tracking lives in the repo's shared git dir, so all worktrees see and mutate it. Give **one** agent ownership of `gh stack` commands. Other agents build branches with plain git (`git switch -c <b> origin/<parent>`), push, and report. The owner then runs `gh stack link <all PRs bottom→top>` (or `init` + `submit`) to register them.
|
|
105
|
+
|
|
106
|
+
## Recovering a hand-chained stack
|
|
107
|
+
|
|
108
|
+
- **PRs are open but chained by hand:** adopt them with `gh stack init <b1> <b2> …` then `gh stack submit`. Don't fight locked bases with `gh pr edit --base`.
|
|
109
|
+
- **The parent already squash-merged:** replay only the child's commits with `git rebase --onto origin/main origin/<parent-branch> <child-branch>` (see [getting-unstuck.md](getting-unstuck.md)). Then `git push --force-with-lease`, retarget, and fix any "stacked on #…" wording in the PR body. Confirm what actually reached main with `git diff origin/<parent-branch> origin/main --stat`.
|
|
110
|
+
- **GitHub has locked the bases and the parent is gone:** merge main into the child (resolve toward the child where it is a superset), then close the trapped PR and recreate it off main.
|
|
@@ -203,7 +203,7 @@ The CLI resolves friendly names against `workspace.json`, so you rarely need raw
|
|
|
203
203
|
|
|
204
204
|
> **Important:** When assigning an issue to a cycle, always set `--state todo`. Issues default to Backlog, which doesn't work with cycles — they must be in Todo status.
|
|
205
205
|
>
|
|
206
|
-
> **Required placement rule:** Never create an issue without both `--project` and `--milestone`.
|
|
206
|
+
> **Required placement rule:** Never create an issue without both `--project` and `--milestone`. **The project must already exist** — place the issue in the initiative's existing `M` project for the milestone it falls under, and never conjure a project to hold it (see "Never invent a project"). Creating a project is only correct for a confirmed out-of-scope revision. If the project exists but the right milestone does not, create the milestone first. Do not leave issues unscoped or unmilestoned.
|
|
207
207
|
>
|
|
208
208
|
> **Never target a completed milestone.** New work never belongs in a milestone that is already done — it distorts the completed phase and hides the issue from the team's current view. Only place an issue in an **open** milestone. If no open milestone matches the issue, create a new one and use that; do not reopen or reuse a completed milestone.
|
|
209
209
|
>
|
|
@@ -241,8 +241,11 @@ node linear.mjs create-issue --title 'Investigate perf issue' --state todo \
|
|
|
241
241
|
--description-file ./issue-body.md \
|
|
242
242
|
--project 'project-uuid' --milestone 'milestone-uuid'
|
|
243
243
|
|
|
244
|
-
#
|
|
245
|
-
|
|
244
|
+
# Find the existing M project for the milestone this work falls under — do not create one.
|
|
245
|
+
# (Prefer the MCP `get_initiative` with includeProjects; this lists them via the CLI.)
|
|
246
|
+
node linear.mjs list-projects # copy the [KEY] M<n> project's UUID
|
|
247
|
+
PROJECT_ID='project-uuid-here'
|
|
248
|
+
# Only the milestone may be created as part of intake
|
|
246
249
|
MILESTONE_ID="$(node linear.mjs create-milestone "$PROJECT_ID" 'Phase 1' | node -e "process.stdin.once('data',d=>{const n=JSON.parse(d).data.projectMilestoneCreate.projectMilestone;console.log(n.id)})")"
|
|
247
250
|
node linear.mjs create-issue --title 'Investigate performance issue' --state todo \
|
|
248
251
|
--project "$PROJECT_ID" --milestone "$MILESTONE_ID" --cycle current
|
|
@@ -319,7 +322,9 @@ node linear.mjs search-issues "login bug"
|
|
|
319
322
|
### Projects & Milestones
|
|
320
323
|
```bash
|
|
321
324
|
# Create a new project (linked to an initiative)
|
|
322
|
-
|
|
325
|
+
# Projects mirror the signed proposal's milestones ([KEY] M<n>) or a confirmed revision ([KEY] R<n>).
|
|
326
|
+
# Never create one to hold work you couldn't place — see "Never invent a project".
|
|
327
|
+
node linear.mjs create-project --name "[KEY] M1 — Milestone Name" --initiative "$INITIATIVE_ID" --description "Short description"
|
|
323
328
|
|
|
324
329
|
# List all projects (pretty table with initiative, state, progress)
|
|
325
330
|
node linear.mjs list-projects
|
|
@@ -347,7 +352,7 @@ node linear.mjs create-issue \
|
|
|
347
352
|
|
|
348
353
|
### Initiatives
|
|
349
354
|
```bash
|
|
350
|
-
# Create a new initiative (=
|
|
355
|
+
# Create a new initiative (= a newly signed proposal; a repeat client gets another one)
|
|
351
356
|
node linear.mjs create-initiative --name "ClientName" --description "Short description"
|
|
352
357
|
|
|
353
358
|
# List all initiatives (pretty table with ID, status, description)
|
|
@@ -601,11 +606,11 @@ node linear.mjs update-initiative "$INITIATIVE_ID" --content-file ./initiative-n
|
|
|
601
606
|
|
|
602
607
|
Linear organizes work in a top-down hierarchy: **Initiative → Project → Milestone → Issue**. Here's how the Gallop team uses each level.
|
|
603
608
|
|
|
604
|
-
### Initiative (=
|
|
609
|
+
### Initiative (= Signed Proposal)
|
|
605
610
|
|
|
606
|
-
An **Initiative** represents a client
|
|
611
|
+
An **Initiative** represents **one signed proposal** for a client, not the client itself. A client who signs a second proposal (a later phase, a separate engagement) gets a **second initiative** — never a second set of projects bolted onto the first. The initiative's scope is fixed by what was signed.
|
|
607
612
|
|
|
608
|
-
**The current
|
|
613
|
+
**The current roster is not stored in this repo — fetch it live from Linear.** Initiatives are the source of truth for which engagements exist, their descriptions, and their repo links:
|
|
609
614
|
|
|
610
615
|
- **List all clients:** `mcp__linear-server__list_initiatives`
|
|
611
616
|
- **Read a client's full details (overview, repo structure, domain notes):** `mcp__linear-server__get_initiative` — these live in the initiative's `content` field
|
|
@@ -615,33 +620,69 @@ When you start any task that needs client context, query Linear instead of looki
|
|
|
615
620
|
|
|
616
621
|
- One initiative can contain **multiple projects**
|
|
617
622
|
|
|
618
|
-
### Project (=
|
|
623
|
+
### Project (= One Proposed Milestone, or a Revision)
|
|
619
624
|
|
|
620
|
-
A **Project** is
|
|
625
|
+
A **Project** is **one of the milestones the signed proposal committed to** — not a
|
|
626
|
+
product, not a workstream, not a theme you invented. The initiative's project list
|
|
627
|
+
*is* the proposal's milestone list: if the proposal promised three milestones, the
|
|
628
|
+
initiative has three projects, numbered and named after them.
|
|
621
629
|
|
|
622
|
-
**
|
|
623
|
-
|
|
624
|
-
- `[ACME] Analytics Demo` — separate product under the same client
|
|
625
|
-
- `[CLIENT] Migration Workstream` — the single workstream for that client
|
|
626
|
-
- `[CLIENT] Scheduling Platform` — the main product for that client
|
|
630
|
+
**Naming convention:** `[KEY] M<n> — <Milestone name>`, where `<n>` is the
|
|
631
|
+
milestone's number in the signed proposal.
|
|
627
632
|
|
|
628
|
-
|
|
629
|
-
-
|
|
630
|
-
-
|
|
631
|
-
- It has a distinct "done" state separate from other work
|
|
633
|
+
- `[KEY] M1 — Data Model`
|
|
634
|
+
- `[KEY] M2 — Pricing Engine`
|
|
635
|
+
- `[KEY] M3 — Reporting`
|
|
632
636
|
|
|
633
|
-
**
|
|
637
|
+
**Work outside the signed scope is a revision, and gets its own project** —
|
|
638
|
+
attached to the **same initiative**, named with an `R` prefix instead of `M`:
|
|
639
|
+
|
|
640
|
+
- `[KEY] R1 — <Name>`, `[KEY] R2 — <Name>`, …
|
|
641
|
+
|
|
642
|
+
`R` numbering is sequential across the whole proposal in the order revisions are
|
|
643
|
+
taken on, independent of which milestone the revision relates to. Never fold
|
|
644
|
+
out-of-scope work into an `M` project — that silently rewrites what was signed.
|
|
645
|
+
|
|
646
|
+
#### Never invent a project
|
|
647
|
+
|
|
648
|
+
**The default is that the right project already exists.** Creating one is a
|
|
649
|
+
structural change to a signed engagement, so it is never a side effect of intake.
|
|
650
|
+
|
|
651
|
+
Before placing any work, answer this question — and **ask the requester if they
|
|
652
|
+
are in the conversation, do not infer it**:
|
|
653
|
+
|
|
654
|
+
> Is this within the signed scope, or is it a revision?
|
|
655
|
+
|
|
656
|
+
- **Within scope** → **find the existing `M` project yourself.** List the
|
|
657
|
+
initiative's projects (`get_initiative` with `includeProjects: true`), read what
|
|
658
|
+
each milestone covers, and place the work in the one it falls under. Only ask
|
|
659
|
+
which project if two genuinely both fit. Do **not** create a project because none
|
|
660
|
+
of the names happens to match the request's wording — milestone names are broad
|
|
661
|
+
by design.
|
|
662
|
+
- **A revision** → determine the next `R<n>` (one past the highest existing `R`,
|
|
663
|
+
or `R1`), **confirm the name and the revision framing with the requester**, then
|
|
664
|
+
create it under the same initiative.
|
|
665
|
+
|
|
666
|
+
Never create a project to hold work you were unsure how to place, to mirror a repo
|
|
667
|
+
or deployment, to group work by theme or domain (that is what milestones and labels
|
|
668
|
+
are for), or because the initiative looked empty. If you cannot place the work and
|
|
669
|
+
the requester is unavailable, leave it unplaced and say so — an invented project is
|
|
670
|
+
harder to undo than an unplaced issue.
|
|
634
671
|
|
|
635
672
|
### Milestone (= Phase / Epic)
|
|
636
673
|
|
|
637
674
|
A **Milestone** is a phase or epic within a project — a meaningful chunk of progress that can be demoed or shipped incrementally.
|
|
638
675
|
|
|
639
|
-
|
|
676
|
+
A milestone is the level where grouping decisions actually belong — **unlike
|
|
677
|
+
projects, milestones may be created freely as part of intake.** If a request needs
|
|
678
|
+
a new home inside its `M` project, that home is a milestone, never a new project.
|
|
679
|
+
|
|
680
|
+
**Examples within `[KEY] M2 — Billing`:**
|
|
640
681
|
- `Core Billing` — create, edit, send invoices (done)
|
|
641
682
|
- `Quotes` — quote workflow, create/edit/convert to invoice
|
|
642
683
|
- `Payments` — payment methods, receipts, balance due display
|
|
643
684
|
|
|
644
|
-
**Examples within `[
|
|
685
|
+
**Examples within `[KEY] M1 — Scheduling`:**
|
|
645
686
|
- `Providers Module` — list, create, edit, deactivate providers
|
|
646
687
|
- `Booking Requests` — request creation, accept/reject workflow
|
|
647
688
|
- `Scheduling & Calendar` — availability, scheduling UI
|
|
@@ -661,24 +702,30 @@ Individual work items live at the bottom of the hierarchy. Every issue belongs t
|
|
|
661
702
|
### Hierarchy in Practice
|
|
662
703
|
|
|
663
704
|
```
|
|
664
|
-
Initiative:
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
705
|
+
Initiative: <Client> — Phase 1 ← one signed proposal
|
|
706
|
+
├── Project: [KEY] M1 — Scheduling ← proposal milestone 1
|
|
707
|
+
│ ├── Milestone: Providers Module
|
|
708
|
+
│ │ ├── KEY-101: Create providers list page
|
|
709
|
+
│ │ ├── KEY-102: Add provider create/edit form
|
|
710
|
+
│ │ └── KEY-103: Provider deactivation support
|
|
711
|
+
│ ├── Milestone: Booking Requests
|
|
712
|
+
│ │ ├── KEY-110: Request creation form
|
|
713
|
+
│ │ └── KEY-111: Accept/reject API endpoints
|
|
714
|
+
│ └── Milestone: Notifications
|
|
715
|
+
│ └── KEY-120: Set up email service
|
|
716
|
+
├── Project: [KEY] M2 — Billing ← proposal milestone 2
|
|
717
|
+
└── Project: [KEY] R1 — SSO Integration ← out-of-scope revision
|
|
675
718
|
```
|
|
676
719
|
|
|
720
|
+
The project row is fixed by the proposal (`M1`, `M2`) plus whatever revisions have
|
|
721
|
+
been agreed (`R1`). New requests land as **issues in a milestone** inside an
|
|
722
|
+
existing project — the project row only grows when a revision is confirmed.
|
|
723
|
+
|
|
677
724
|
### Guidelines for the Team
|
|
678
725
|
|
|
679
726
|
1. **Every issue must be placed into a cycle with Todo status.** **Do NOT default to the current/active cycle.** Follow this procedure: (a) Run `cycle-capacity` to see each cycle's capacity % (velocity-based, from last 3 completed cycles). (b) Starting from the earliest (current) cycle, find the first cycle that is **strictly under 100%** capacity. (c) If the current cycle is at or above 100%, **skip it** and use the next cycle with room. Assign the issue there via `--cycle`. **Always set `--state todo`** — issues in Backlog don't work with cycles. **Exception:** High priority or above (priority ≤ 2: Urgent, High) always go into the current active cycle regardless of capacity.
|
|
680
727
|
2. **Every issue must belong to a project and a milestone.** Never create orphan issues and never leave an issue outside a milestone.
|
|
681
|
-
3. **
|
|
728
|
+
3. **Place the issue in an existing project — never invent one.** The initiative's `M` projects are the signed proposal's milestones; find the one the work falls under. A new project is correct *only* for work the requester confirmed is out of scope, and then only as the next `[KEY] R<n> — <Name>` revision project (see "Never invent a project"). Don't park work in a generic team backlog either — if you truly cannot place it, say so rather than manufacturing a home for it.
|
|
682
729
|
4. **If the correct milestone does not exist, create it before creating the issue.** Milestone creation is part of issue intake, not optional cleanup. **Never add an issue to a completed milestone** — only open milestones may receive new issues. If no open milestone matches the issue, create a new one; do not reuse a completed one.
|
|
683
730
|
5. **Use milestones for sequencing.** Milestones can have target dates, making them useful for communicating delivery phases to clients.
|
|
684
731
|
6. **Track progress in Linear.** After creating/updating projects or milestones, update the initiative's content in Linear to reflect the current structure (see "Post-Organization: Update Initiative in Linear" below).
|
|
@@ -693,10 +740,12 @@ node linear.mjs list-projects
|
|
|
693
740
|
# List milestones within a project
|
|
694
741
|
node linear.mjs list-milestones "$PROJECT_ID"
|
|
695
742
|
|
|
696
|
-
# If
|
|
697
|
-
PROJECT_ID="$(node linear.mjs create-project --name "[CLIENT] Feature Area" --description "Short description" | node -e "process.stdin.once('data',d=>console.log(JSON.parse(d).data.projectCreate.project.id))")"
|
|
743
|
+
# If the milestone is missing, create it inside the EXISTING M project (never a new project)
|
|
698
744
|
MILESTONE_ID="$(node linear.mjs create-milestone "$PROJECT_ID" "Phase 1" | node -e "process.stdin.once('data',d=>console.log(JSON.parse(d).data.projectMilestoneCreate.projectMilestone.id))")"
|
|
699
745
|
|
|
746
|
+
# Creating a project is only for a CONFIRMED out-of-scope revision — next R<n>, same initiative
|
|
747
|
+
node linear.mjs create-project --name "[KEY] R1 — Revision Name" --initiative "$INITIATIVE_ID" --description "Short description"
|
|
748
|
+
|
|
700
749
|
# Create an issue within a project and milestone (with cycle)
|
|
701
750
|
node linear.mjs create-issue \
|
|
702
751
|
--title 'Add provider create form' \
|
|
@@ -146,3 +146,9 @@ The model's prior is mostly Zod 3 — these are the idioms that changed. Get the
|
|
|
146
146
|
- **Format errors with the built-ins**, not `zod-validation-error`: `z.prettifyError()` (human string), `z.treeifyError()` (nested, replaces deprecated `.format()`), `z.flattenError()` (replaces deprecated `.flatten()`).
|
|
147
147
|
- **`.default()` applies to the *output* type** and short-circuits parsing when input is `undefined`. For the old "run the default through the schema" behavior, use `.prefault()`.
|
|
148
148
|
- **`z.coerce.*` input type is now `unknown`** (not the output type) — fine for h3 query/body parsing, but affects schemas you consume elsewhere.
|
|
149
|
+
- **`.default()` fires even under `.optional()` (and through `.partial()`)** — `someDefaulted.optional()` still emits the default for a missing key, so a defaulted field can never signal "not provided". The classic trap is deriving a PATCH body from a create schema: `CreateSchema.partial()` silently fills every defaulted field, so `body.someField !== undefined` is always true and "was this field sent?" logic (partial updates, tree-replace triggers) misfires on every request. For PATCH schemas, compose from bare **undefaulted** field schemas and add `.optional()` per field:
|
|
150
|
+
```typescript
|
|
151
|
+
const itemsSchema = z.array(ItemSchema); // no .default([]) here
|
|
152
|
+
const createSchema = z.object({ items: itemsSchema.default([]) });
|
|
153
|
+
const patchSchema = z.object({ items: itemsSchema.optional() }); // undefined ⇢ "not sent"
|
|
154
|
+
```
|