engsys 1.0.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/LICENSE +21 -0
- package/README.md +202 -0
- package/core/agents/aaron.md +152 -0
- package/core/agents/bert.md +115 -0
- package/core/agents/isabelle.md +136 -0
- package/core/agents/jody.md +150 -0
- package/core/agents/leith.md +111 -0
- package/core/agents/marcelo.md +282 -0
- package/core/agents/melvin.md +101 -0
- package/core/agents/nyx.md +152 -0
- package/core/agents/otto.md +168 -0
- package/core/agents/patricia.md +283 -0
- package/core/commands/design-audit-local.md +155 -0
- package/core/commands/design-audit.md +235 -0
- package/core/commands/design-critique.md +96 -0
- package/core/commands/file-issue.md +22 -0
- package/core/commands/generate-project.md +45 -0
- package/core/commands/implement-issue.md +37 -0
- package/core/commands/implement-project.md +40 -0
- package/core/commands/naturalize.md +61 -0
- package/core/commands/pre-push.md +29 -0
- package/core/commands/prep-review-collect.md +130 -0
- package/core/commands/prep-review-finalize.md +121 -0
- package/core/commands/prep-review-publish.md +113 -0
- package/core/commands/prep-review.md +65 -0
- package/core/commands/project-closeout.md +25 -0
- package/core/skills/agentic-eval/SKILL.md +195 -0
- package/core/skills/chrome-devtools/SKILL.md +97 -0
- package/core/skills/code-review/SKILL.md +26 -0
- package/core/skills/gh-cli/SKILL.md +2202 -0
- package/core/skills/git-commit/SKILL.md +124 -0
- package/core/skills/git-workflow-agents/SKILL.md +462 -0
- package/core/skills/git-workflow-agents/reference.md +220 -0
- package/core/skills/github-actions/SKILL.md +190 -0
- package/core/skills/github-issues/SKILL.md +154 -0
- package/core/skills/llm-structured-outputs/SKILL.md +323 -0
- package/core/skills/llm-structured-outputs/references/provider-details.md +392 -0
- package/core/skills/pre-push/SKILL.md +115 -0
- package/core/skills/refactor/SKILL.md +645 -0
- package/core/skills/web-design-reviewer/SKILL.md +371 -0
- package/core/skills/webapp-testing/SKILL.md +127 -0
- package/core/skills/webapp-testing/test-helper.js +56 -0
- package/core/templates/CLAUDE.md.tmpl +98 -0
- package/core/templates/adr-template.md +67 -0
- package/core/templates/gh-issue-templates/bug.md +39 -0
- package/core/templates/gh-issue-templates/content.md +42 -0
- package/core/templates/gh-issue-templates/enhancement.md +36 -0
- package/core/templates/gh-issue-templates/feature.md +39 -0
- package/core/templates/gh-issue-templates/infrastructure.md +41 -0
- package/core/templates/post-edit-reminders.sh.tmpl +19 -0
- package/core/templates/settings.json.tmpl +90 -0
- package/core/templates/settings.local.json.tmpl +3 -0
- package/core/workflows/agent-implementation-workflow.md +346 -0
- package/core/workflows/generate-project.md +258 -0
- package/core/workflows/implement-project-workflow.md +190 -0
- package/core/workflows/issue-tracking.md +89 -0
- package/core/workflows/project-closeout-ceremony.md +77 -0
- package/core/workflows/review-workflow.md +266 -0
- package/engsys.config.example.yaml +46 -0
- package/install +202 -0
- package/lessons-library/README.md +80 -0
- package/lessons-library/async-callbacks-verify-liveness.md +15 -0
- package/lessons-library/change-isnt-done-until-every-surface-updated.md +15 -0
- package/lessons-library/claim-then-act-for-irreversible-ops.md +16 -0
- package/lessons-library/co-commit-entangled-work.md +15 -0
- package/lessons-library/dependabot-triage-playbook.md +17 -0
- package/lessons-library/deploy-by-digest-and-verify-the-running-revision.md +15 -0
- package/lessons-library/enforce-your-guarantee-at-your-boundary.md +16 -0
- package/lessons-library/gate-changes-on-measurement-not-vibes.md +15 -0
- package/lessons-library/iac-first-no-console-changes.md +15 -0
- package/lessons-library/independent-objective-review-gate.md +15 -0
- package/lessons-library/keep-an-immutable-source-of-truth.md +15 -0
- package/lessons-library/long-agent-runs-checkpoint-not-poll.md +15 -0
- package/lessons-library/model-identity-with-stable-ids-and-provenance.md +15 -0
- package/lessons-library/operator-choices-are-first-class.md +15 -0
- package/lessons-library/prefer-tool-enforced-structured-output.md +15 -0
- package/lessons-library/prove-causation-before-acting.md +15 -0
- package/lessons-library/re-read-state-before-acting.md +14 -0
- package/lessons-library/read-layer-tolerates-unbackfilled-rows.md +15 -0
- package/lessons-library/shell-safety-pipefail-and-validate-before-teardown.md +14 -0
- package/lessons-library/shift-correctness-left-and-distrust-false-greens.md +15 -0
- package/lessons-library/stray-control-bytes-hide-changes.md +14 -0
- package/lessons-library/tests-can-assert-the-bug.md +15 -0
- package/lessons-library/verify-ground-truth-not-reports.md +15 -0
- package/lessons-library/worktrees-need-bootstrap-from-origin-main.md +15 -0
- package/lib/commands.js +356 -0
- package/lib/generate-team-avatars.mjs +251 -0
- package/lib/manifest.js +155 -0
- package/lib/render.js +135 -0
- package/lib/selftest.js +90 -0
- package/lib/util.js +89 -0
- package/lib/yaml.js +156 -0
- package/optional-agents/gary.md +86 -0
- package/optional-agents/jos.md +136 -0
- package/optional-agents/sandy.md +101 -0
- package/optional-agents/steve.md +161 -0
- package/package.json +43 -0
- package/stacks/cloud/aws/claude.fragment.md +17 -0
- package/stacks/cloud/aws/settings.fragment.json +39 -0
- package/stacks/cloud/aws/skills/aws-deployment-preflight/SKILL.md +165 -0
- package/stacks/cloud/aws/skills/cloud-architecture-aws/SKILL.md +265 -0
- package/stacks/cloud/azure/claude.fragment.md +17 -0
- package/stacks/cloud/azure/settings.fragment.json +45 -0
- package/stacks/cloud/azure/skills/azure-deployment-preflight/SKILL.md +175 -0
- package/stacks/cloud/azure/skills/cloud-architecture-azure/SKILL.md +211 -0
- package/stacks/cloud/cloudflare/claude.fragment.md +21 -0
- package/stacks/cloud/cloudflare/settings.fragment.json +31 -0
- package/stacks/cloud/cloudflare/skills/cloud-architecture-cloudflare/SKILL.md +294 -0
- package/stacks/cloud/cloudflare/skills/cloudflare-deployment-preflight/SKILL.md +175 -0
- package/stacks/cloud/gcp/claude.fragment.md +17 -0
- package/stacks/cloud/gcp/settings.fragment.json +40 -0
- package/stacks/cloud/gcp/skills/cloud-architecture-gcp/SKILL.md +208 -0
- package/stacks/cloud/gcp/skills/gcp-deployment-preflight/SKILL.md +137 -0
- package/stacks/db/mongo/skills/mongo-conventions/SKILL.md +96 -0
- package/stacks/db/prisma/claude.fragment.md +49 -0
- package/stacks/db/prisma/skills/docker-database-package-copy/SKILL.md +44 -0
- package/stacks/db/prisma/skills/prisma-conventions/SKILL.md +37 -0
- package/stacks/domain/mobile-growth/skills/apple-ads/SKILL.md +184 -0
- package/stacks/domain/mobile-growth/skills/apple-ads/references/benchmark-notes.md +47 -0
- package/stacks/domain/mobile-growth/skills/apple-ads/references/official-links.md +53 -0
- package/stacks/domain/mobile-growth/skills/google-play-growth/SKILL.md +197 -0
- package/stacks/domain/mobile-growth/skills/google-play-growth/references/benchmark-notes.md +47 -0
- package/stacks/domain/mobile-growth/skills/google-play-growth/references/official-links.md +45 -0
- package/stacks/iac/bicep/claude.fragment.md +14 -0
- package/stacks/iac/bicep/settings.fragment.json +20 -0
- package/stacks/iac/bicep/skills/iac-bicep/SKILL.md +113 -0
- package/stacks/iac/cdk/claude.fragment.md +14 -0
- package/stacks/iac/cdk/settings.fragment.json +23 -0
- package/stacks/iac/cdk/skills/iac-cdk/SKILL.md +104 -0
- package/stacks/iac/terraform/claude.fragment.md +13 -0
- package/stacks/iac/terraform/settings.fragment.json +25 -0
- package/stacks/iac/terraform/skills/iac-terraform/SKILL.md +93 -0
- package/stacks/iac/terraform/skills/terraform-conventions/SKILL.md +87 -0
- package/stacks/lang/kotlin/skills/android-testing/SKILL.md +263 -0
- package/stacks/lang/kotlin/skills/jetpack-compose/SKILL.md +264 -0
- package/stacks/lang/kotlin/skills/kotlin-coroutines/SKILL.md +329 -0
- package/stacks/lang/python/skills/python-conventions/SKILL.md +61 -0
- package/stacks/lang/shell/skills/shell-scripting/SKILL.md +110 -0
- package/stacks/lang/swift/skills/swift-concurrency/SKILL.md +423 -0
- package/stacks/lang/swift/skills/swift-concurrency/references/approachable-concurrency.md +80 -0
- package/stacks/lang/swift/skills/swift-concurrency/references/concurrency-patterns.md +233 -0
- package/stacks/lang/swift/skills/swift-concurrency/references/swiftui-concurrency.md +187 -0
- package/stacks/lang/swift/skills/swift-concurrency/references/synchronization-primitives.md +341 -0
- package/stacks/lang/swift/skills/swift-testing/SKILL.md +497 -0
- package/stacks/lang/swift/skills/swift-testing/references/testing-advanced.md +106 -0
- package/stacks/lang/swift/skills/swift-testing/references/testing-patterns.md +504 -0
- package/stacks/lang/swift/skills/swiftdata/SKILL.md +334 -0
- package/stacks/lang/swift/skills/swiftdata/references/core-data-coexistence.md +504 -0
- package/stacks/lang/swift/skills/swiftdata/references/swiftdata-advanced.md +975 -0
- package/stacks/lang/swift/skills/swiftdata/references/swiftdata-queries.md +675 -0
- package/stacks/lang/swift/skills/swiftui-patterns/SKILL.md +371 -0
- package/stacks/lang/swift/skills/swiftui-patterns/references/architecture-patterns.md +486 -0
- package/stacks/lang/swift/skills/swiftui-patterns/references/deprecated-migration.md +1097 -0
- package/stacks/lang/swift/skills/swiftui-patterns/references/design-polish.md +780 -0
- package/stacks/lang/swift/skills/swiftui-patterns/references/platform-and-sharing.md +696 -0
- package/stacks/lang/typescript/skills/typescript-conventions/SKILL.md +91 -0
- package/stacks/platform/android/claude.fragment.md +40 -0
- package/stacks/platform/android/hooks/pre-push-gradle.sh +70 -0
- package/stacks/platform/android/settings.fragment.json +13 -0
- package/stacks/platform/android/skills/android-build-conventions/SKILL.md +247 -0
- package/stacks/platform/ios/claude.fragment.md +24 -0
- package/stacks/platform/ios/hooks/pre-push-xcodebuild.sh +82 -0
- package/stacks/platform/ios/settings.fragment.json +21 -0
- package/stacks/platform/ios/skills/xcodebuildmcp-simulator-logs/SKILL.md +76 -0
- package/stacks/platform/web/skills/frontend-testing/SKILL.md +246 -0
- package/stacks/platform/web/skills/react-conventions/SKILL.md +261 -0
- package/stacks/platform/web/skills/web-platform-conventions/SKILL.md +55 -0
- package/stacks/tooling/issue-tracker-github/claude.fragment.md +10 -0
- package/stacks/tooling/issue-tracker-github/settings.fragment.json +24 -0
- package/stacks/tooling/issue-tracker-github/skills/issue-tracker-github/SKILL.md +278 -0
- package/stacks/tooling/issue-tracker-linear/claude.fragment.md +17 -0
- package/stacks/tooling/issue-tracker-linear/settings.fragment.json +9 -0
- package/stacks/tooling/issue-tracker-linear/skills/issue-tracker-linear/SKILL.md +183 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: issue-tracker-github
|
|
3
|
+
description: Issue tracking and project-board operations on GitHub Issues + GitHub Projects (ProjectV2). Implements the shared issue-tracker operation contract (create/list/get/update/comment/close issue; create/add-to/query board; set board field; link PR) with concrete `gh` CLI and `gh api graphql` commands. Activate when the active tracker is GitHub and the work involves filing issues, managing a project board (Phase/Priority/Owner), or linking a PR to close a work item.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Issue tracker: GitHub Issues + Projects
|
|
7
|
+
|
|
8
|
+
This skill is the GitHub implementation of the **shared issue-tracker operation
|
|
9
|
+
contract**. Commands and agents call these operations *by name* without knowing the
|
|
10
|
+
backend; the Linear pack (`issue-tracker-linear`) implements the same names so the two
|
|
11
|
+
are swappable. Keep the operation names below stable.
|
|
12
|
+
|
|
13
|
+
Builds on the knowledge already in the core `gh-cli` and `github-issues` skills — read
|
|
14
|
+
those for full `gh` flag coverage and issue-body templates. This skill does not repeat
|
|
15
|
+
them; it maps the **contract operations** onto concrete commands.
|
|
16
|
+
|
|
17
|
+
**Identifiers:** GitHub issues are referenced as `#123`. Boards are GitHub ProjectV2
|
|
18
|
+
("Projects"). The repo (`<owner>/<repo>`) and the Project number/owner are project facts —
|
|
19
|
+
read them from `CLAUDE.md`.
|
|
20
|
+
|
|
21
|
+
## Discipline: bodies come from tmp/ files, never heredocs
|
|
22
|
+
|
|
23
|
+
Every operation that writes multi-line markdown (issue body, comment, PR body) writes
|
|
24
|
+
the content to a `tmp/` file first and passes it with `--body-file`. Do **not** inline
|
|
25
|
+
multi-line bodies via heredoc/`echo` — it mangles backticks, quotes, and `$`. One-line
|
|
26
|
+
bodies may use `--body "..."`.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Write the body, then reference it.
|
|
30
|
+
# tmp/issue-body-<slug>.md
|
|
31
|
+
# tmp/comment-<slug>.md
|
|
32
|
+
# tmp/pr-body-<slug>.md
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Contract operations
|
|
36
|
+
|
|
37
|
+
### create-issue(title, body-from-tmp-file, type/label, [project, phase]) -> id + URL
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# 1. Write the body to tmp/issue-body-<slug>.md (see github-issues for templates).
|
|
41
|
+
# 2. Create. --type/--label carries the contract "type/label". Prints the issue URL.
|
|
42
|
+
gh issue create \
|
|
43
|
+
--repo <owner>/<repo> \
|
|
44
|
+
--title "[Feature] <title>" \
|
|
45
|
+
--body-file tmp/issue-body-<slug>.md \
|
|
46
|
+
--label "<type-or-label>"
|
|
47
|
+
# Capture the number for downstream ops:
|
|
48
|
+
ISSUE_URL=$(gh issue create --repo <owner>/<repo> --title "<title>" \
|
|
49
|
+
--body-file tmp/issue-body-<slug>.md --label "<label>")
|
|
50
|
+
ISSUE_NUM=$(basename "$ISSUE_URL")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If `[project, phase]` were supplied, follow with **add-to-board** below.
|
|
54
|
+
|
|
55
|
+
### list-issues(filter: state | label/type | assignee | project/phase)
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
gh issue list --repo <owner>/<repo> --state open
|
|
59
|
+
gh issue list --repo <owner>/<repo> --label bug
|
|
60
|
+
gh issue list --repo <owner>/<repo> --assignee @me
|
|
61
|
+
gh issue list --repo <owner>/<repo> --search "is:open label:bug" \
|
|
62
|
+
--json number,title,labels,assignees
|
|
63
|
+
# Filter by project/phase => read the board (see query-board); Phase is a Project field,
|
|
64
|
+
# not an issue field, so it is queried through the Project, not `gh issue list`.
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### get-issue(id)
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
gh issue view <id> --repo <owner>/<repo> \
|
|
71
|
+
--json number,title,body,state,labels,assignees,comments
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### update-issue(id, fields | labels | assignee)
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
gh issue edit <id> --repo <owner>/<repo> --title "<new title>"
|
|
78
|
+
gh issue edit <id> --repo <owner>/<repo> --body-file tmp/issue-body-<slug>.md
|
|
79
|
+
gh issue edit <id> --repo <owner>/<repo> --add-label ready --remove-label triage
|
|
80
|
+
gh issue edit <id> --repo <owner>/<repo> --add-assignee <user>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### comment-issue(id, body) — records local-review findings on the work item
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Write findings to tmp/comment-<slug>.md first, then:
|
|
87
|
+
gh issue comment <id> --repo <owner>/<repo> --body-file tmp/comment-<slug>.md
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### close-issue(id)
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
gh issue close <id> --repo <owner>/<repo>
|
|
94
|
+
gh issue close <id> --repo <owner>/<repo> --comment "Resolved by #<pr>"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Boards (GitHub ProjectV2)
|
|
98
|
+
|
|
99
|
+
The GitHub MCP does **not** support Projects — always use `gh project` / `gh api graphql`
|
|
100
|
+
for board operations. ProjectV2 lives at the **user or org** level (not the repo), so
|
|
101
|
+
board ops take `--owner <owner>` and the project **number**. Custom fields (Phase,
|
|
102
|
+
Priority, Owner) are ProjectV2 fields.
|
|
103
|
+
|
|
104
|
+
### create-board(name, fields: Phase, Priority, Owner)
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Create the project (board). --format json yields its number + id.
|
|
108
|
+
gh project create --owner <owner> --title "<name>" --format json
|
|
109
|
+
PROJECT_NUM=<number from output>
|
|
110
|
+
|
|
111
|
+
# Add the three contract fields.
|
|
112
|
+
# Phase + Priority => single-select (their options are the allowed values)
|
|
113
|
+
# Owner => single-select of usernames, or text
|
|
114
|
+
gh project field-create <PROJECT_NUM> --owner <owner> \
|
|
115
|
+
--name "Phase" --data-type SINGLE_SELECT \
|
|
116
|
+
--single-select-options "Phase 1,Phase 2,Phase 3,Phase 4"
|
|
117
|
+
gh project field-create <PROJECT_NUM> --owner <owner> \
|
|
118
|
+
--name "Priority" --data-type SINGLE_SELECT \
|
|
119
|
+
--single-select-options "P0,P1,P2,P3"
|
|
120
|
+
gh project field-create <PROJECT_NUM> --owner <owner> \
|
|
121
|
+
--name "Owner" --data-type TEXT
|
|
122
|
+
gh project field-list <PROJECT_NUM> --owner <owner> # capture field ids + option ids
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### add-to-board(issue, phase, priority, owner)
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# Add the issue to the board; capture the item id it returns.
|
|
129
|
+
gh project item-add <PROJECT_NUM> --owner <owner> \
|
|
130
|
+
--url https://github.com/<owner>/<repo>/issues/<issue>
|
|
131
|
+
ITEM_ID=<item id from item-add --format json>
|
|
132
|
+
|
|
133
|
+
# Set the three fields on that item (see set-board-field for the field/option ids).
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### set-board-field(item, field, value)
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# Single-select field (Phase, Priority): pass the option id.
|
|
140
|
+
gh project item-edit --id <ITEM_ID> --project-id <PROJECT_ID> \
|
|
141
|
+
--field-id <PHASE_FIELD_ID> --single-select-option-id <PHASE_OPTION_ID>
|
|
142
|
+
# Text field (Owner):
|
|
143
|
+
gh project item-edit --id <ITEM_ID> --project-id <PROJECT_ID> \
|
|
144
|
+
--field-id <OWNER_FIELD_ID> --text "<owner-username>"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`add-to-board` = `item-add` followed by one `set-board-field` per field (Phase, Priority,
|
|
148
|
+
Owner). Get the `<PROJECT_ID>`, field ids, and option ids from `field-list --format json`
|
|
149
|
+
once and reuse them.
|
|
150
|
+
|
|
151
|
+
### query-board(board) -> items grouped by Phase
|
|
152
|
+
|
|
153
|
+
The "pick the lowest open phase" read. `gh project item-list` flattens fields; group by
|
|
154
|
+
the Phase value. For full fidelity use GraphQL:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
gh project item-list <PROJECT_NUM> --owner <owner> --format json \
|
|
158
|
+
--jq '.items
|
|
159
|
+
| map({num: .content.number, title: .content.title, phase: .phase, status: .status})
|
|
160
|
+
| group_by(.phase)'
|
|
161
|
+
|
|
162
|
+
# GraphQL equivalent (open items, their Phase field value):
|
|
163
|
+
gh api graphql -f query='
|
|
164
|
+
query($org:String!, $num:Int!){
|
|
165
|
+
organization(login:$org){
|
|
166
|
+
projectV2(number:$num){
|
|
167
|
+
items(first:100){
|
|
168
|
+
nodes{
|
|
169
|
+
content{ ... on Issue { number title state } }
|
|
170
|
+
fieldValueByName(name:"Phase"){
|
|
171
|
+
... on ProjectV2ItemFieldSingleSelectValue { name }
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}' -f org=<owner> -F num=<PROJECT_NUM>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
To pick the lowest open phase: filter to open issues, take the minimum `Phase` value
|
|
181
|
+
present.
|
|
182
|
+
|
|
183
|
+
### close-board(board) — close the project once every issue is done
|
|
184
|
+
|
|
185
|
+
Close the ProjectV2 so the board drops out of the active list — but **only when every
|
|
186
|
+
issue on it is closed**. The project `item-list` `state` field is UNRELIABLE (often
|
|
187
|
+
returns null/`?`), so do not trust it: intersect the board's issue numbers with the
|
|
188
|
+
actually-open issues and require the intersection to be empty.
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# issue numbers on the board…
|
|
192
|
+
gh project item-list <PROJECT_NUM> --owner <owner> --format json --limit 400 \
|
|
193
|
+
| jq -r '.items[].content | select(.type=="Issue") | .number' | sort -u > /tmp/proj_issues.txt
|
|
194
|
+
# …cross-checked against actually-open issues (NOT the board's state field)
|
|
195
|
+
gh issue list --repo <owner>/<repo> --state open --limit 900 --json number --jq '.[].number' \
|
|
196
|
+
| sort -u > /tmp/open_issues.txt
|
|
197
|
+
comm -12 /tmp/proj_issues.txt /tmp/open_issues.txt # ← MUST be empty before closing
|
|
198
|
+
|
|
199
|
+
# if the intersection is empty:
|
|
200
|
+
gh project close <PROJECT_NUM> --owner <owner>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
- **Gate:** any issue still open → do NOT close (the project isn't done; stop and
|
|
204
|
+
report). Draft cards / notes (non-issue items) do **not** gate — only real issues do.
|
|
205
|
+
- Reversible: `gh project close <PROJECT_NUM> --owner <owner> --undo` reopens it.
|
|
206
|
+
Closing archives the board from the active view; it does not delete the project or its
|
|
207
|
+
items.
|
|
208
|
+
|
|
209
|
+
## link-pr(issue, pr) — how a merged PR closes/links the work item
|
|
210
|
+
|
|
211
|
+
GitHub auto-closes linked issues when a PR merges if the PR body contains a closing
|
|
212
|
+
keyword. **One keyword per line — never comma-separated** (GitHub closes only the first
|
|
213
|
+
issue on a comma-separated line).
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
# Write the PR body to tmp/pr-body-<slug>.md with one closing keyword per line:
|
|
217
|
+
# Closes #<issue>
|
|
218
|
+
# Closes #<other-issue>
|
|
219
|
+
gh pr create --repo <owner>/<repo> --title "<title>" \
|
|
220
|
+
--body-file tmp/pr-body-<slug>.md
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
On merge, GitHub closes each referenced issue and records the PR link on it. PRs and CI
|
|
224
|
+
stay on GitHub (`gh`).
|
|
225
|
+
|
|
226
|
+
## Hard-won lessons
|
|
227
|
+
|
|
228
|
+
GitHub Issues/Projects API gotchas that fail silently or destructively. Each is
|
|
229
|
+
Symptom / Cause / Fix.
|
|
230
|
+
|
|
231
|
+
### Verify a label exists before filing an issue with it
|
|
232
|
+
|
|
233
|
+
- **Symptom:** `gh issue create --label X` errors with `could not add label: 'X' not found`
|
|
234
|
+
and the issue is **not** created — no partial success. Batch issue-filing runs waste every
|
|
235
|
+
`gh` call when an assumed label (`dashboard`, `recommendation-engine`) isn't the repo's
|
|
236
|
+
actual label (`frontend`, `recommendation`).
|
|
237
|
+
- **Cause:** `gh` rejects the whole create when any `--label` doesn't exist in the repo.
|
|
238
|
+
- **Fix:** List labels first and map your intent → real names before filing:
|
|
239
|
+
```bash
|
|
240
|
+
gh label list --repo <owner>/<repo> --limit 100 --json name --jq '.[].name' | sort
|
|
241
|
+
```
|
|
242
|
+
Don't trust cached label lists from prior runs — query the live list each time.
|
|
243
|
+
|
|
244
|
+
### ProjectV2 single-select fields are GraphQL-only
|
|
245
|
+
|
|
246
|
+
- **Symptom:** A fresh `gh project create` board has only built-in fields (`Title`,
|
|
247
|
+
`Assignees`, `Status`, `Labels`, …). There's no `gh` shortcut to add a single-select field
|
|
248
|
+
with options (Phase / Priority / Owner), so operators end up configuring them by hand in the
|
|
249
|
+
UI for every issue.
|
|
250
|
+
- **Cause:** The `gh` CLI has no subcommand to create or edit a single-select field's option
|
|
251
|
+
list — `field-create` makes a field, `field-delete` removes one, `item-edit` sets a value on
|
|
252
|
+
an item; none append/rename options on an existing single-select field.
|
|
253
|
+
- **Fix:** Create and edit single-select fields with `gh api graphql` (`createProjectV2Field`
|
|
254
|
+
with `dataType: SINGLE_SELECT` and `singleSelectOptions`). Verify with
|
|
255
|
+
`gh project field-list <num> --owner <owner>` before assuming `Status` is the only field.
|
|
256
|
+
|
|
257
|
+
### updateProjectV2Field preserves option IDs verbatim — pass the full set
|
|
258
|
+
|
|
259
|
+
- **Symptom:** Adding one new option to an existing single-select field (e.g. a new Phase)
|
|
260
|
+
silently orphans **every** previously-categorized item to "(no phase)" — the value vanishes
|
|
261
|
+
from each item even though the field still exists. Observed: 21 items lost their Phase on one
|
|
262
|
+
add.
|
|
263
|
+
- **Cause:** `updateProjectV2Field` treats `singleSelectOptions` as the **authoritative state**,
|
|
264
|
+
not a delta. Omitting existing options regenerates all option IDs, orphaning every board item
|
|
265
|
+
that referenced an old ID.
|
|
266
|
+
- **Fix:** First fetch the existing options + IDs, then call `updateProjectV2Field` with the
|
|
267
|
+
**full** array — every existing option's `id` reused verbatim plus the new one appended.
|
|
268
|
+
Afterward audit immediately and re-set any orphans:
|
|
269
|
+
```bash
|
|
270
|
+
gh project item-list <num> --owner <owner> --format json \
|
|
271
|
+
| jq '[.items[] | select(.phase == null)] | length' # must be 0
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## See also
|
|
275
|
+
|
|
276
|
+
- core `gh-cli` — full `gh` command/flag reference.
|
|
277
|
+
- core `github-issues` — issue-body templates, labels, MCP fallback.
|
|
278
|
+
- core `github-actions` — workflow expression/injection/draft-gate/rate-limit lessons.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## Issue tracking
|
|
2
|
+
|
|
3
|
+
- **Active tracker: Linear.** Agents use the `issue-tracker-linear` skill for all issue
|
|
4
|
+
and board operations (via the Linear MCP): create/list/get/update/comment/close issue;
|
|
5
|
+
create/add-to/query board; set board field; link PR.
|
|
6
|
+
- PRs and CI stay on GitHub via `gh`; Linear auto-links them through its GitHub
|
|
7
|
+
integration (magic words like `Fixes ABC-123` in the PR title/description, or a branch
|
|
8
|
+
named `<team>-<n>-slug`).
|
|
9
|
+
- The board maps to a Linear **Project**; **Phase**→milestone, **Priority**→native issue
|
|
10
|
+
priority, **Owner**→assignee.
|
|
11
|
+
|
|
12
|
+
The Linear MCP connector (`https://mcp.linear.app/mcp`, configured in this pack's
|
|
13
|
+
`settings.fragment.json`) must be enabled and authorized in the session for the skill to
|
|
14
|
+
reach Linear.
|
|
15
|
+
|
|
16
|
+
<!-- naturalize: set the Linear team key (e.g. ABC) and the project that holds the board.
|
|
17
|
+
team-key: <TEAM_KEY> -->
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: issue-tracker-linear
|
|
3
|
+
description: Issue tracking and project-board operations on Linear, via the Linear MCP. Implements the shared issue-tracker operation contract (create/list/get/update/comment/close issue; create/add-to/query board; set board field; link PR) mapped onto Linear issues, projects, milestones, priorities, and assignees. Activate when the active tracker is Linear and the work involves filing issues, managing a Linear project/board (Phase/Priority/Owner), or linking a PR to close a work item.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Issue tracker: Linear
|
|
7
|
+
|
|
8
|
+
This skill is the Linear implementation of the **shared issue-tracker operation
|
|
9
|
+
contract**. Commands and agents call these operations *by name* without knowing the
|
|
10
|
+
backend; the GitHub pack (`issue-tracker-github`) implements the same names, so the two
|
|
11
|
+
are swappable. Keep the operation names below stable.
|
|
12
|
+
|
|
13
|
+
Linear is reached through the **Linear MCP** (tools prefixed `mcp__claude_ai_Linear__`).
|
|
14
|
+
There is no Linear CLI — every operation is an MCP tool call, so this pack adds no Bash
|
|
15
|
+
permissions. If the tools are not visible in the session, discover them via tool search;
|
|
16
|
+
the MCP connector must be enabled (see the pack's `claude.fragment.md`).
|
|
17
|
+
|
|
18
|
+
**Note — no separate create tool:** `save_issue`, `save_project`, and `save_milestone`
|
|
19
|
+
each do **both create and update** (omit the id to create, pass it to update).
|
|
20
|
+
|
|
21
|
+
## Project facts (naturalize)
|
|
22
|
+
|
|
23
|
+
The Linear **team** and **project** are project facts. Read the **team key** (e.g.
|
|
24
|
+
`ABC`) from `CLAUDE.md`. Resolve the team/project to their ids once via `list_teams` /
|
|
25
|
+
`list_projects` and reuse them.
|
|
26
|
+
|
|
27
|
+
### Identifier mapping
|
|
28
|
+
|
|
29
|
+
| Backend | Issue id form |
|
|
30
|
+
| ------- | -------------------- |
|
|
31
|
+
| GitHub | `#123` |
|
|
32
|
+
| Linear | `ABC-123` (team-prefixed) |
|
|
33
|
+
|
|
34
|
+
Callers may pass either the human identifier (`ABC-123`) or the issue's internal id;
|
|
35
|
+
`get_issue` / `save_issue` accept the identifier.
|
|
36
|
+
|
|
37
|
+
## Contract -> Linear mapping (board fields)
|
|
38
|
+
|
|
39
|
+
A contract "board" is a **Linear Project**. The board's three custom fields map onto
|
|
40
|
+
native Linear concepts — there are no ad-hoc custom fields:
|
|
41
|
+
|
|
42
|
+
| Contract field | Linear concept | Tool to set it |
|
|
43
|
+
| -------------- | ------------------------------------- | --------------------------------------- |
|
|
44
|
+
| Board | **Project** | `save_project` |
|
|
45
|
+
| Phase | **Milestone** (project milestone) | `save_milestone` + `save_issue` (milestone) |
|
|
46
|
+
| Priority | **Issue priority** (native, 0–4) | `save_issue` (priority) |
|
|
47
|
+
| Owner | **Assignee** | `save_issue` (assignee) |
|
|
48
|
+
| State/Status | **Workflow state** (Todo/Done/…) | `save_issue` (state via `list_issue_statuses`) |
|
|
49
|
+
|
|
50
|
+
(Phase may alternatively map to a **label set** instead of milestones; prefer milestones
|
|
51
|
+
— they are ordered, which makes "lowest open phase" a natural sort.)
|
|
52
|
+
|
|
53
|
+
## Contract operations
|
|
54
|
+
|
|
55
|
+
### create-issue(title, body-from-tmp-file, type/label, [project, phase]) -> id + URL
|
|
56
|
+
|
|
57
|
+
`save_issue` with no id creates. Set `team` (the team key), `title`, `description`
|
|
58
|
+
(the body), `labels` (the contract type/label), and optionally `assignee`, `project`,
|
|
59
|
+
`priority`. Read the long markdown body from `tmp/issue-body-<slug>.md` and pass its
|
|
60
|
+
contents as `description` (real newlines, not `\n`).
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
save_issue(team: "ABC", title: "<title>", description: <contents of tmp/issue-body-<slug>.md>,
|
|
64
|
+
labels: ["<type-or-label>"], assignee: <owner?>, project: <project?>)
|
|
65
|
+
-> returns the issue identifier (ABC-123) and URL.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
If `[project, phase]` were supplied, follow with **add-to-board**.
|
|
69
|
+
|
|
70
|
+
### list-issues(filter: state | label/type | assignee | project/phase)
|
|
71
|
+
|
|
72
|
+
`list_issues` with the matching filter (filter by `team`, `project`, `state`/status,
|
|
73
|
+
`assignee`, `label`, or `cycle`). For a project/phase filter, list by `project` and
|
|
74
|
+
read each issue's milestone (see query-board).
|
|
75
|
+
|
|
76
|
+
### get-issue(id)
|
|
77
|
+
|
|
78
|
+
`get_issue(id: "ABC-123")` -> full issue (title, description, state, labels, assignee,
|
|
79
|
+
priority, project, milestone).
|
|
80
|
+
|
|
81
|
+
### update-issue(id, fields | labels | assignee)
|
|
82
|
+
|
|
83
|
+
`save_issue` **with the id** updates only the passed fields:
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
save_issue(id: "ABC-123", title?: ..., description?: ..., labels?: [...], assignee?: ...)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### comment-issue(id, body) — records local-review findings on the work item
|
|
90
|
+
|
|
91
|
+
`save_comment(issueId: "ABC-123", body: <contents of tmp/comment-<slug>.md>)`.
|
|
92
|
+
|
|
93
|
+
### close-issue(id)
|
|
94
|
+
|
|
95
|
+
Linear has no "close" verb — set the issue to a **Done** (or **Canceled**) workflow
|
|
96
|
+
state. Resolve the state id once via `list_issue_statuses(team: "ABC")`, then:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
save_issue(id: "ABC-123", state: "Done") # or "Canceled"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Boards (Linear Projects + Milestones)
|
|
103
|
+
|
|
104
|
+
### create-board(name, fields: Phase, Priority, Owner)
|
|
105
|
+
|
|
106
|
+
`save_project(name: "<name>", teamId: <team>)` creates the project (board). Then create
|
|
107
|
+
one **milestone per Phase** (these are the Phase field's values):
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
save_project(name: "<name>", teams: ["ABC"]) -> project id
|
|
111
|
+
save_milestone(projectId: <project>, name: "Phase 1")
|
|
112
|
+
save_milestone(projectId: <project>, name: "Phase 2")
|
|
113
|
+
save_milestone(projectId: <project>, name: "Phase 3")
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Priority and Owner need no setup — they are native issue fields (priority, assignee).
|
|
117
|
+
|
|
118
|
+
### add-to-board(issue, phase, priority, owner)
|
|
119
|
+
|
|
120
|
+
One `save_issue` setting all four:
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
save_issue(id: "ABC-123",
|
|
124
|
+
project: <project>,
|
|
125
|
+
milestone: <milestone for "phase">, # Phase
|
|
126
|
+
priority: <0–4>, # Priority (1=Urgent … 4=Low, 0=None)
|
|
127
|
+
assignee: "<owner>") # Owner
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Resolve the milestone for a phase via `list_milestones(projectId: <project>)`.
|
|
131
|
+
|
|
132
|
+
### set-board-field(item, field, value)
|
|
133
|
+
|
|
134
|
+
All board fields are issue fields, so this is `save_issue` with the one field:
|
|
135
|
+
|
|
136
|
+
```text
|
|
137
|
+
save_issue(id: "ABC-123", milestone: <…>) # Phase
|
|
138
|
+
save_issue(id: "ABC-123", priority: <0–4>) # Priority
|
|
139
|
+
save_issue(id: "ABC-123", assignee: <…>) # Owner
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### query-board(board) -> items grouped by Phase
|
|
143
|
+
|
|
144
|
+
`list_issues(project: <project>)`, then group the results by each issue's **milestone**
|
|
145
|
+
(Phase). To "pick the lowest open phase": filter to non-Done issues, group by milestone,
|
|
146
|
+
and take the earliest milestone (milestones are ordered) that still has open issues.
|
|
147
|
+
Use `list_milestones(projectId: <project>)` for the canonical phase order.
|
|
148
|
+
|
|
149
|
+
### close-board(board) — close the project once every issue is done
|
|
150
|
+
|
|
151
|
+
Mark the Linear **project** Completed so it drops off the active board — but **only when
|
|
152
|
+
every issue in it is Done or Canceled**. Check with `list_issues(project: <project>)` and
|
|
153
|
+
confirm none remain in a non-terminal state (use `list_issue_statuses(team: "ABC")` to
|
|
154
|
+
know which states are terminal); the count of non-terminal issues must be zero.
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
save_project(id: <project>, state: "completed") # Linear project status → Completed
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
- **Gate:** any issue still open / in progress → do NOT complete the project (it isn't
|
|
161
|
+
done; stop and report).
|
|
162
|
+
- Reversible: re-open by setting the project state back (e.g. `"started"`). Completing
|
|
163
|
+
drops it from the active view; it does not delete the project or its issues.
|
|
164
|
+
|
|
165
|
+
## link-pr(issue, pr) — how a merged PR closes/links the work item
|
|
166
|
+
|
|
167
|
+
PRs and CI stay on **GitHub** (`gh`). Linear's GitHub integration auto-links and
|
|
168
|
+
auto-closes the issue via **magic words**, no MCP call needed:
|
|
169
|
+
|
|
170
|
+
- Branch named `<team>-<n>-slug` (e.g. `abc-123-fix-login`) — Linear links it
|
|
171
|
+
automatically, and
|
|
172
|
+
- a closing magic word in the **PR title or description**: `Fixes ABC-123`,
|
|
173
|
+
`Closes ABC-123`, `Resolves ABC-123`.
|
|
174
|
+
|
|
175
|
+
When the PR merges, Linear moves `ABC-123` to its Done state and records the PR link.
|
|
176
|
+
So `link-pr` is realized by naming the branch / writing the magic word when the PR is
|
|
177
|
+
created via `gh` — there is no separate Linear tool call.
|
|
178
|
+
|
|
179
|
+
## See also
|
|
180
|
+
|
|
181
|
+
- The GitHub pack `issue-tracker-github` implements the same contract on GitHub Issues +
|
|
182
|
+
Projects — same operation names, swappable backend.
|
|
183
|
+
- core `gh-cli` — PR creation (`gh pr create`) still goes through GitHub.
|