@jesdi/skills 0.0.6 → 0.0.8
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
package/skills/backlog/SKILL.md
CHANGED
|
@@ -42,6 +42,35 @@ not in the default `repo` scope).
|
|
|
42
42
|
`<owner>`, `<repo>`, `<projectNumber>` below all come from that
|
|
43
43
|
`.backlog/project-meta.json`.
|
|
44
44
|
|
|
45
|
+
## Label taxonomy (capture infers, triage confirms)
|
|
46
|
+
|
|
47
|
+
Issues carry conventional repo labels alongside `inbox`. They are orthogonal to
|
|
48
|
+
the board's `Area` field — never derive one from the other. Apply every label
|
|
49
|
+
that clearly fits, typically 1–3 total; when unsure, apply fewer. Use **only**
|
|
50
|
+
these labels (`setup` provisions them all) — never invent new ones.
|
|
51
|
+
|
|
52
|
+
**Type — at most one:**
|
|
53
|
+
|
|
54
|
+
| Label | Apply when |
|
|
55
|
+
|-------|-----------|
|
|
56
|
+
| `bug` | Something is broken or behaves incorrectly |
|
|
57
|
+
| `enhancement` | New feature or improvement to existing behavior |
|
|
58
|
+
| `documentation` | Docs, READMEs, comments, guides |
|
|
59
|
+
| `question` | Open question or decision needed, not yet actionable work |
|
|
60
|
+
|
|
61
|
+
**Area — pick 0–2:**
|
|
62
|
+
|
|
63
|
+
| Label | Apply when |
|
|
64
|
+
|-------|-----------|
|
|
65
|
+
| `frontend` | UI, components, styling, client/browser behavior |
|
|
66
|
+
| `backend` | Server logic, APIs, data models, services |
|
|
67
|
+
| `infra` | Deployment, hosting, environments, tooling, build |
|
|
68
|
+
| `ci` | CI/CD pipelines, workflows, checks |
|
|
69
|
+
| `security` | Vulnerabilities, auth, permissions, hardening |
|
|
70
|
+
| `performance` | Speed, memory, efficiency, scalability |
|
|
71
|
+
| `testing` | Test coverage, test infrastructure, flaky tests |
|
|
72
|
+
| `dependencies` | Upgrading or managing third-party dependencies |
|
|
73
|
+
|
|
45
74
|
## `setup` — create the board once (idempotent)
|
|
46
75
|
|
|
47
76
|
Run when `.backlog/project-meta.json` is absent or the board is being
|
|
@@ -52,7 +81,9 @@ dir.
|
|
|
52
81
|
1. Ensure the `project` scope: `gh auth status`; if Projects calls 403, run
|
|
53
82
|
`gh auth refresh -s project`.
|
|
54
83
|
2. Follow `references/graphql.md` → **Setup**: create the Project, link the repo,
|
|
55
|
-
add the
|
|
84
|
+
add the six fields, create the `inbox` label plus the 12 taxonomy labels
|
|
85
|
+
(idempotent — `--force` reconciles color/description on re-runs). On a
|
|
86
|
+
brand-new board the `Area`
|
|
56
87
|
single-select is created with the generic default options
|
|
57
88
|
`feature,bug,infra,docs,research`. If any step reports "already exists", treat
|
|
58
89
|
it as done and continue. Projects that want different Areas edit the option set
|
|
@@ -126,17 +157,30 @@ Reuse the **dedup discipline** from the `file-bug-issue` skill, but take the
|
|
|
126
157
|
2. Dedup: `gh issue list --repo <repo> --state open --search "<distinctive terms>" --json number,title,url`.
|
|
127
158
|
If a genuine duplicate exists, add the new angle as a comment instead of a new
|
|
128
159
|
issue and report which. Different-but-related → create new and cross-link `#N`.
|
|
129
|
-
3. Otherwise
|
|
130
|
-
|
|
131
|
-
|
|
160
|
+
3. Otherwise infer labels from the idea text per the **Label taxonomy** above,
|
|
161
|
+
then create it (see `references/graphql.md` → Capture): `--label inbox` plus
|
|
162
|
+
one `--label` per inferred label, body = the idea as-is (one loose sentence
|
|
163
|
+
is fine; no required structure).
|
|
164
|
+
4. Report the new issue URL and the labels applied. If mid-task, treat as a
|
|
165
|
+
side-quest and return.
|
|
132
166
|
|
|
133
167
|
## `triage` — turn inbox issues into scored graph nodes
|
|
134
168
|
|
|
135
|
-
1. List the inbox: `gh issue list --repo <repo> --state open --label inbox --json number,title,url,body`.
|
|
136
|
-
2. For each, present title + body and ask whether to promote
|
|
169
|
+
1. List the inbox: `gh issue list --repo <repo> --state open --label inbox --json number,title,url,body,labels`.
|
|
170
|
+
2. For each, present title + body + current labels and ask whether to promote
|
|
171
|
+
(skip = leave in inbox). Offer label corrections per the **Label taxonomy**
|
|
172
|
+
above; apply via `gh issue edit N --add-label … --remove-label …`
|
|
173
|
+
(graphql.md → Triage). Labels stay independent of the board `Area` gathered
|
|
174
|
+
next.
|
|
137
175
|
3. For a promoted issue, gather `Impact` (1–5), `Effort` (1–5), `Area` (**pick one
|
|
138
176
|
from the options present in `.backlog/project-meta.json`** → `fields.Area.options`),
|
|
139
177
|
and optional blockers.
|
|
178
|
+
|
|
179
|
+
- **Boost** (number, optional, default 0): manual priority override. Ranking is
|
|
180
|
+
band-dominant — any issue with higher Boost outranks every issue with lower
|
|
181
|
+
Boost regardless of score; score orders issues within a band. Negative values
|
|
182
|
+
sink an issue below the unboosted pack. Dispatchers set 99 for "work on this
|
|
183
|
+
next". `--json` rows carry `"boost"`.
|
|
140
184
|
4. Apply, in order, the `references/graphql.md` → Triage commands:
|
|
141
185
|
add to project → set Impact/Effort (`--number`) → set
|
|
142
186
|
`Score` = round(Impact ÷ Effort, 1) (`--number`) → set Area +
|
|
@@ -155,7 +199,7 @@ python3 .claude/skills/backlog/rank.py # human-readable table
|
|
|
155
199
|
python3 .claude/skills/backlog/rank.py --json # machine-readable rows for
|
|
156
200
|
# dispatchers: number, title,
|
|
157
201
|
# url, status, labels, blocked,
|
|
158
|
-
# score
|
|
202
|
+
# score, boost
|
|
159
203
|
```
|
|
160
204
|
|
|
161
205
|
(Use whichever agent-skills path the store symlinked the skill into — e.g.
|
|
@@ -165,10 +209,11 @@ or below the repo root so `rank.py` can walk up to `.backlog/`.)
|
|
|
165
209
|
`rank.py` pulls Project items + issue bodies via `gh`, parses `Blocked by:`
|
|
166
210
|
edges, drops closed/`Done` blockers, and prints:
|
|
167
211
|
|
|
168
|
-
- **Available** issues ranked
|
|
169
|
-
`[needs triage]`).
|
|
212
|
+
- **Available** issues ranked boost-band first, then Impact÷Effort within a band
|
|
213
|
+
(unscored sort last within a band, flagged `[needs triage]`).
|
|
170
214
|
- **Blocked** issues, unranked, annotated with what they wait on.
|
|
171
215
|
|
|
172
|
-
Present the table as-is. If the user asks "why isn't #N at the top",
|
|
173
|
-
|
|
216
|
+
Present the table as-is. If the user asks "why isn't #N at the top", check first
|
|
217
|
+
whether another issue has a higher Boost band (shown as `↑n`), then its score, then
|
|
218
|
+
its blockers from the output. This in-memory ranking is the reason the
|
|
174
219
|
skill exists over a bare `gh issue list`.
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"Impact": {"id": "PVTF_REPLACE"},
|
|
12
12
|
"Effort": {"id": "PVTF_REPLACE"},
|
|
13
13
|
"Score": {"id": "PVTF_REPLACE"},
|
|
14
|
+
"Boost": {"id": "PVTF_REPLACE"},
|
|
14
15
|
"Area": {
|
|
15
16
|
"id": "PVTSSF_REPLACE",
|
|
16
17
|
"options": {"feature": "...", "bug": "...", "infra": "...", "docs": "...", "research": "..."}
|
package/skills/backlog/rank.py
CHANGED
|
@@ -66,6 +66,7 @@ def merge_sources(project_items, issue_rows):
|
|
|
66
66
|
"effort": _as_int(item.get("effort")),
|
|
67
67
|
"area": item.get("area"),
|
|
68
68
|
"labels": [l["name"] for l in row.get("labels") or []],
|
|
69
|
+
"boost": (lambda v: 0 if v is None else v)(_as_int(item.get("boost"))),
|
|
69
70
|
})
|
|
70
71
|
return merged
|
|
71
72
|
|
|
@@ -100,7 +101,8 @@ def score(issue):
|
|
|
100
101
|
def _sort_key(issue):
|
|
101
102
|
value = score(issue)
|
|
102
103
|
return (
|
|
103
|
-
0
|
|
104
|
+
-issue.get("boost", 0), # band first: higher boost above everything
|
|
105
|
+
0 if value is not None else 1, # scored issues first within a band
|
|
104
106
|
-(value or 0.0), # higher score first
|
|
105
107
|
-(issue.get("impact") or 0), # tiebreak: higher impact
|
|
106
108
|
issue["number"], # stable, deterministic
|
|
@@ -146,6 +148,7 @@ def to_json_rows(result):
|
|
|
146
148
|
"labels": issue.get("labels", []),
|
|
147
149
|
"blocked": blocked,
|
|
148
150
|
"score": score(issue),
|
|
151
|
+
"boost": issue.get("boost", 0),
|
|
149
152
|
})
|
|
150
153
|
return rows
|
|
151
154
|
|
|
@@ -165,6 +168,11 @@ def render(result):
|
|
|
165
168
|
value = score(issue)
|
|
166
169
|
score_str = f"{value:.2f}" if value is not None else " — "
|
|
167
170
|
flag = "" if value is not None else " [needs triage]"
|
|
171
|
+
boost = issue.get("boost", 0)
|
|
172
|
+
if boost > 0:
|
|
173
|
+
flag += f" ↑{boost}"
|
|
174
|
+
elif boost < 0:
|
|
175
|
+
flag += f" ↓{-boost}"
|
|
168
176
|
label = f"#{issue['number']} {issue['title']}"[:38].ljust(38)
|
|
169
177
|
area = (issue.get("area") or "-")
|
|
170
178
|
lines.append(f"{idx:>4} {score_str:>5} {label} {area:<10}{flag}")
|
|
@@ -26,13 +26,27 @@ gh project field-create <projectNumber> --owner <owner> --name "Status" \
|
|
|
26
26
|
gh project field-create <projectNumber> --owner <owner> --name "Impact" --data-type NUMBER
|
|
27
27
|
gh project field-create <projectNumber> --owner <owner> --name "Effort" --data-type NUMBER
|
|
28
28
|
gh project field-create <projectNumber> --owner <owner> --name "Score" --data-type NUMBER
|
|
29
|
+
gh project field-create <projectNumber> --owner <owner> --name "Boost" --data-type NUMBER
|
|
29
30
|
gh project field-create <projectNumber> --owner <owner> --name "Area" \
|
|
30
31
|
--data-type SINGLE_SELECT \
|
|
31
32
|
--single-select-options "feature,bug,infra,docs,research"
|
|
32
33
|
|
|
33
|
-
# 4. Create the
|
|
34
|
-
gh label create inbox --repo <repo>
|
|
35
|
-
|
|
34
|
+
# 4. Create the labels on the repo (idempotent: --force updates color/description)
|
|
35
|
+
gh label create inbox --repo <repo> --description "Un-triaged capture" --color BFD4F2 --force
|
|
36
|
+
# Type labels (at most one per issue)
|
|
37
|
+
gh label create bug --repo <repo> --description "Something is broken or behaves incorrectly" --color d73a4a --force
|
|
38
|
+
gh label create enhancement --repo <repo> --description "New feature or improvement to existing behavior" --color a2eeef --force
|
|
39
|
+
gh label create documentation --repo <repo> --description "Docs, READMEs, comments, guides" --color 0075ca --force
|
|
40
|
+
gh label create question --repo <repo> --description "Open question or decision needed, not yet actionable work" --color d876e3 --force
|
|
41
|
+
# Area labels (0-2 per issue)
|
|
42
|
+
gh label create frontend --repo <repo> --description "UI, components, styling, client/browser behavior" --color fbca04 --force
|
|
43
|
+
gh label create backend --repo <repo> --description "Server logic, APIs, data models, services" --color 1d76db --force
|
|
44
|
+
gh label create infra --repo <repo> --description "Deployment, hosting, environments, tooling, build" --color c2e0c6 --force
|
|
45
|
+
gh label create ci --repo <repo> --description "CI/CD pipelines, workflows, checks" --color bfdadc --force
|
|
46
|
+
gh label create security --repo <repo> --description "Vulnerabilities, auth, permissions, hardening" --color ee0701 --force
|
|
47
|
+
gh label create performance --repo <repo> --description "Speed, memory, efficiency, scalability" --color f9d0c4 --force
|
|
48
|
+
gh label create testing --repo <repo> --description "Test coverage, test infrastructure, flaky tests" --color 0e8a16 --force
|
|
49
|
+
gh label create dependencies --repo <repo> --description "Upgrading or managing third-party dependencies" --color 0366d6 --force
|
|
36
50
|
|
|
37
51
|
# 5. Discover IDs to build .backlog/project-meta.json
|
|
38
52
|
gh project view <projectNumber> --owner <owner> --format json # -> id (projectId)
|
|
@@ -48,7 +62,8 @@ IDs into `.backlog/project-meta.json`.
|
|
|
48
62
|
|
|
49
63
|
```bash
|
|
50
64
|
gh issue create --repo <repo> \
|
|
51
|
-
--title "<concise idea title>"
|
|
65
|
+
--title "<concise idea title>" \
|
|
66
|
+
--label inbox --label "<inferred>" [--label "<inferred>" ...] \
|
|
52
67
|
--body "<one loose sentence is fine>"
|
|
53
68
|
```
|
|
54
69
|
|
|
@@ -65,6 +80,9 @@ gh project item-edit --project-id <projectId> --id <itemId> \
|
|
|
65
80
|
--field-id <fields.Effort.id> --number <1-5>
|
|
66
81
|
gh project item-edit --project-id <projectId> --id <itemId> \
|
|
67
82
|
--field-id <fields.Score.id> --number <round(impact/effort, 1)>
|
|
83
|
+
# Optional: manual priority override (default 0; positive boosts above unboosted, negative sinks below)
|
|
84
|
+
gh project item-edit --project-id <projectId> --id <itemId> \
|
|
85
|
+
--field-id <fields.Boost.id> --number <n>
|
|
68
86
|
|
|
69
87
|
# Set single-selects (use the option id from .backlog/project-meta.json)
|
|
70
88
|
gh project item-edit --project-id <projectId> --id <itemId> \
|
|
@@ -77,6 +95,8 @@ gh issue edit <number> --repo <repo> --body "<body>\n\nBlocked by: #<a>, #<b>"
|
|
|
77
95
|
|
|
78
96
|
# Drop the inbox label
|
|
79
97
|
gh issue edit <number> --repo <repo> --remove-label inbox
|
|
98
|
+
# Correct taxonomy labels if the user adjusts them during review
|
|
99
|
+
gh issue edit <number> --repo <repo> --add-label "<label>,<label>" --remove-label "<label>"
|
|
80
100
|
```
|
|
81
101
|
|
|
82
102
|
## Sub-issues (native parent/child) — GraphQL, no field IDs needed
|
|
@@ -302,7 +302,7 @@ def test_main_json_emits_machine_readable_rows(monkeypatch, capsys):
|
|
|
302
302
|
rows = jsonlib.loads(capsys.readouterr().out)
|
|
303
303
|
assert rows == [{"number": 5, "title": "Task", "url": "u/5",
|
|
304
304
|
"status": "Ready", "labels": ["auto"],
|
|
305
|
-
"blocked": False, "score": 2.0}]
|
|
305
|
+
"blocked": False, "score": 2.0, "boost": 0}]
|
|
306
306
|
|
|
307
307
|
|
|
308
308
|
def test_project_env_absent_without_token(monkeypatch):
|
|
@@ -343,3 +343,72 @@ def test_find_project_meta_errors_clearly_when_absent(tmp_path, monkeypatch):
|
|
|
343
343
|
rank.find_project_meta()
|
|
344
344
|
assert ".backlog/project-meta.json" in str(exc.value)
|
|
345
345
|
assert "backlog setup" in str(exc.value)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def test_merge_sources_parses_boost_and_defaults_zero():
|
|
349
|
+
out = rank.merge_sources(
|
|
350
|
+
[{"content": {"number": 7, "title": "T", "url": "u/7"}, "boost": 2},
|
|
351
|
+
{"content": {"number": 8, "title": "U", "url": "u/8"}, "boost": ""},
|
|
352
|
+
{"content": {"number": 9, "title": "V", "url": "u/9"}},
|
|
353
|
+
{"content": {"number": 10, "title": "W", "url": "u/10"}, "boost": -3}],
|
|
354
|
+
[])
|
|
355
|
+
assert [i["boost"] for i in out] == [2, 0, 0, -3]
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def test_boost_band_dominates_score():
|
|
359
|
+
lo = _scored(1, 5, 1) # score 5.0, unboosted
|
|
360
|
+
hi = _scored(2, 1, 2) # score 0.5, boosted
|
|
361
|
+
hi["boost"] = 1
|
|
362
|
+
result = rank.rank_issues([lo, hi])
|
|
363
|
+
assert [i["number"] for i in result["available"]] == [2, 1]
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def test_score_orders_within_boost_band():
|
|
367
|
+
a = _scored(1, 2, 2) # score 1.0
|
|
368
|
+
b = _scored(2, 5, 1) # score 5.0
|
|
369
|
+
a["boost"] = b["boost"] = 1
|
|
370
|
+
result = rank.rank_issues([a, b])
|
|
371
|
+
assert [i["number"] for i in result["available"]] == [2, 1]
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def test_negative_boost_sinks_below_unboosted():
|
|
375
|
+
demoted = _scored(1, 9, 1) # score 9.0 but demoted
|
|
376
|
+
demoted["boost"] = -1
|
|
377
|
+
plain = _scored(2, 1, 1) # score 1.0
|
|
378
|
+
result = rank.rank_issues([demoted, plain])
|
|
379
|
+
assert [i["number"] for i in result["available"]] == [2, 1]
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def test_unscored_boosted_enters_band_after_scored_in_band():
|
|
383
|
+
scored_boosted = _scored(1, 2, 1)
|
|
384
|
+
scored_boosted["boost"] = 1
|
|
385
|
+
unscored_boosted = _scored(2, None, None)
|
|
386
|
+
unscored_boosted["boost"] = 1
|
|
387
|
+
scored_plain = _scored(3, 5, 1)
|
|
388
|
+
result = rank.rank_issues([scored_plain, unscored_boosted, scored_boosted])
|
|
389
|
+
assert [i["number"] for i in result["available"]] == [1, 2, 3]
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def test_json_rows_include_boost():
|
|
393
|
+
a = _scored(1, 2, 1)
|
|
394
|
+
a["boost"] = 2
|
|
395
|
+
a["labels"] = ["auto"]
|
|
396
|
+
b = _scored(2, 5, 1)
|
|
397
|
+
b["labels"] = ["auto"]
|
|
398
|
+
rows = rank.to_json_rows(rank.rank_issues([a, b]))
|
|
399
|
+
assert [(r["number"], r["boost"]) for r in rows] == [(1, 2), (2, 0)]
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def test_render_marks_boost_up_and_down():
|
|
403
|
+
up = _scored(1, 2, 1)
|
|
404
|
+
up["boost"] = 2
|
|
405
|
+
down = _scored(2, 5, 1)
|
|
406
|
+
down["boost"] = -1
|
|
407
|
+
plain = _scored(3, 1, 1)
|
|
408
|
+
text = rank.render(rank.rank_issues([up, down, plain]))
|
|
409
|
+
line_for_up = next(l for l in text.splitlines() if "#1 i1" in l)
|
|
410
|
+
assert "↑2" in line_for_up
|
|
411
|
+
line_for_down = next(l for l in text.splitlines() if "#2 i2" in l)
|
|
412
|
+
assert "↓1" in line_for_down
|
|
413
|
+
line_for_plain = next(l for l in text.splitlines() if "#3 i3" in l)
|
|
414
|
+
assert "↑" not in line_for_plain and "↓" not in line_for_plain
|
package/skills-manifest.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-07-
|
|
3
|
+
"generatedAt": "2026-07-29T09:24:37.867Z",
|
|
4
4
|
"skills": [
|
|
5
5
|
{
|
|
6
6
|
"name": "backlog",
|
|
7
|
-
"version": "0.1.
|
|
8
|
-
"hash": "sha256-
|
|
7
|
+
"version": "0.1.5",
|
|
8
|
+
"hash": "sha256-427fff6fc5005849a85e750568940b52375c823a9ad26b1827890dc0cabab499",
|
|
9
9
|
"description": "Manage a GitHub-Issues-backed backlog and a Projects-v2 work graph via five verbs. Use when the user wants to CAPTURE an idea/research thread (\"add to backlog\", \"capture this\", \"note this idea\", \"log a task\"), TRIAGE the inbox (\"triage the backlog\", \"score the inbox\"), see what to work on NEXT (\"what's next\", \"what should I work on\", \"ranked backlog\", \"next actionable task\"), LAND a finished item (\"mark done\", \"close out #N\"), or SETUP the board the first time. Ranks open issues by impact÷effort, respecting `Blocked by:` edges, so only actionable work surfaces."
|
|
10
10
|
},
|
|
11
11
|
{
|