claude-code-session-manager 0.37.2 → 0.38.1

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.
Files changed (53) hide show
  1. package/dist/assets/{TiptapBody-BtrSXTRp.js → TiptapBody-DlPTesio.js} +1 -1
  2. package/dist/assets/{index-uVGdpAGF.js → index-CBSyk1aI.js} +626 -626
  3. package/dist/assets/index-DyOGjslF.css +32 -0
  4. package/dist/index.html +2 -2
  5. package/package.json +1 -1
  6. package/plugins/session-manager-dev/skills/blog-for-project-feature/0-select/SKILL.md +39 -0
  7. package/plugins/session-manager-dev/skills/blog-for-project-feature/1-research/SKILL.md +37 -0
  8. package/plugins/session-manager-dev/skills/blog-for-project-feature/2-storyboard/SKILL.md +38 -0
  9. package/plugins/session-manager-dev/skills/blog-for-project-feature/3-capture/SKILL.md +43 -0
  10. package/plugins/session-manager-dev/skills/blog-for-project-feature/4-draft-copy/SKILL.md +37 -0
  11. package/plugins/session-manager-dev/skills/blog-for-project-feature/5-illustrate/SKILL.md +50 -0
  12. package/plugins/session-manager-dev/skills/blog-for-project-feature/6-compose/SKILL.md +48 -0
  13. package/plugins/session-manager-dev/skills/blog-for-project-feature/7-review/SKILL.md +51 -0
  14. package/plugins/session-manager-dev/skills/blog-for-project-feature/SKILL.md +140 -0
  15. package/plugins/session-manager-dev/skills/blog-for-project-feature/feature.config.yaml +74 -0
  16. package/plugins/session-manager-dev/skills/develop/standards.md +1 -0
  17. package/plugins/session-manager-dev/skills/issue-address/0-select/0-fetch-pool/SKILL.md +38 -0
  18. package/plugins/session-manager-dev/skills/issue-address/0-select/1-reject-fixed/SKILL.md +64 -0
  19. package/plugins/session-manager-dev/skills/issue-address/0-select/2-reject-claimed/SKILL.md +71 -0
  20. package/plugins/session-manager-dev/skills/issue-address/0-select/3-rank-impact/SKILL.md +39 -0
  21. package/plugins/session-manager-dev/skills/issue-address/0-select/SKILL.md +75 -0
  22. package/plugins/session-manager-dev/skills/issue-address/1-confirm-open/SKILL.md +39 -0
  23. package/plugins/session-manager-dev/skills/issue-address/2-claim/SKILL.md +57 -0
  24. package/plugins/session-manager-dev/skills/issue-address/3-reproduce/SKILL.md +40 -0
  25. package/plugins/session-manager-dev/skills/issue-address/4-fix/SKILL.md +33 -0
  26. package/plugins/session-manager-dev/skills/issue-address/5-verify/SKILL.md +40 -0
  27. package/plugins/session-manager-dev/skills/issue-address/SKILL.md +120 -0
  28. package/plugins/session-manager-dev/skills/pr-review-sweep/0-fetch/SKILL.md +41 -0
  29. package/plugins/session-manager-dev/skills/pr-review-sweep/1-classify/SKILL.md +45 -0
  30. package/plugins/session-manager-dev/skills/pr-review-sweep/2-check-fixed/SKILL.md +49 -0
  31. package/plugins/session-manager-dev/skills/pr-review-sweep/3-queue/SKILL.md +50 -0
  32. package/plugins/session-manager-dev/skills/pr-review-sweep/4-land-and-resolve/SKILL.md +60 -0
  33. package/plugins/session-manager-dev/skills/pr-review-sweep/SKILL.md +124 -0
  34. package/plugins/session-manager-dev/skills/pr-signal/SKILL.md +67 -0
  35. package/src/main/__tests__/chat-cancel-terminal.test.cjs +31 -0
  36. package/src/main/__tests__/chat-exit-close-race.test.cjs +140 -0
  37. package/src/main/__tests__/classifyPromptTicket.test.cjs +101 -0
  38. package/src/main/__tests__/exchangesPromptId.test.cjs +70 -0
  39. package/src/main/__tests__/prdCreate.test.cjs +34 -0
  40. package/src/main/__tests__/prdParserSourcePromptId.test.cjs +65 -0
  41. package/src/main/__tests__/scheduler-committed-in-window.test.cjs +64 -0
  42. package/src/main/chatRunner.cjs +67 -46
  43. package/src/main/exchanges.cjs +9 -3
  44. package/src/main/ipcSchemas.cjs +18 -3
  45. package/src/main/lib/classifyPromptTicket.cjs +116 -0
  46. package/src/main/lib/prdCreate.cjs +6 -2
  47. package/src/main/scheduler/prdParser.cjs +4 -0
  48. package/src/main/scheduler.cjs +30 -4
  49. package/src/main/sessionsStore.cjs +4 -5
  50. package/src/main/webRemote.cjs +3 -3
  51. package/src/preload/api.d.ts +15 -5
  52. package/src/preload/index.cjs +5 -2
  53. package/dist/assets/index-CD_LuJZF.css +0 -32
@@ -0,0 +1,140 @@
1
+ ---
2
+ name: blog-for-project-feature
3
+ description: Produce a self-contained, interactive HTML "feature showcase" for one project + one feature — Project Mission & Description, Feature Description (with an optional generated companion illustration), and a Click-Through Demo built from REAL screenshots (never mockups) — one aspect revealed at a time. Output is a single portable HTML file uploadable anywhere (no server, no build step). Distinct from blog-from-git (which drafts narrative bilko.run changelog posts from git history) — this is an evergreen, per-feature product page, not a dated post. Orchestrates 8 nested sub-skills (blog-for-project-feature:select, :research, :storyboard, :capture, :draft-copy, :illustrate, :compose, :review) so each step is independently invokable and inspectable. Use for "make a feature page for X", "showcase this feature", "/blog-for-project-feature", "interactive demo of X", or turning a shipped feature in Session Manager / Social Signal Builder (or any project) into something you can hand someone a link to.
4
+ ---
5
+
6
+ # blog-for-project-feature (orchestrator)
7
+
8
+ Project-agnostic. Runs against whatever project the current working directory (or a named
9
+ project path) points to — built first for **Session Manager** and **Social Signal Builder**,
10
+ but nothing here is hardcoded to either.
11
+
12
+ **Not `blog-from-git`.** That skill drafts a dated, narrative bilko.run post from git activity
13
+ ("here's what shipped this week"). This skill produces an **evergreen, single-feature product
14
+ page** — no date, no changelog framing, not tied to a publishing cadence. One project, one
15
+ feature, three things: what the project is for, what this feature does, and a real click-through
16
+ demo of it working. The two skills never share output or ledger state.
17
+
18
+ **`feature.config.yaml` in this folder is the grounding authority** for output conventions
19
+ (where the HTML lands, image size/compression limits, single-file-vs-folder default, theme
20
+ tokens). Read it first, before any sub-skill file. If prose here ever disagrees with the config,
21
+ the config wins.
22
+
23
+ ## Pipeline DAG
24
+
25
+ ```
26
+ project (+ optional feature name)
27
+
28
+
29
+ ┌───────────────────────────────┐
30
+ │ 0. blog-for-project-feature:select │──▶ one confirmed project + one confirmed feature
31
+ └───────────────────────────────┘
32
+
33
+
34
+ ┌───────────────────────────────┐
35
+ │ 1. :research │──▶ mission, feature description, key files/entry points
36
+ └───────────────────────────────┘
37
+
38
+
39
+ ┌───────────────────────────────┐
40
+ │ 2. :storyboard │──▶ ordered demo steps (action, expected screen, caption)
41
+ └───────────────────────────────┘
42
+
43
+
44
+ ┌───────────────────────────────┐
45
+ │ 3. :capture │──▶ real screenshots per step (+ storyboard corrections)
46
+ └───────────────────────────────┘ │ no real screenshot obtainable for a step
47
+ │ all steps captured ▼
48
+ ▼ STOP — report which step blocked, don't fabricate
49
+ ┌───────────────────────────────┐
50
+ │ 4. :draft-copy │──▶ FINAL mission/feature text + one-sentence "essence" line
51
+ └───────────────────────────────┘
52
+
53
+
54
+ ┌───────────────────────────────┐
55
+ │ 5. :illustrate │──▶ one companion illustration from the essence line
56
+ └───────────────────────────────┘ (or a clean "omitted" note — never blocks the pipeline)
57
+
58
+
59
+ ┌───────────────────────────────┐
60
+ │ 6. :compose │──▶ one self-contained interactive HTML file
61
+ └───────────────────────────────┘
62
+
63
+
64
+ ┌───────────────────────────────┐
65
+ │ 7. :review │──── fails a check ────┐
66
+ └───────────────────────────────┘ │
67
+ │ passes │
68
+ ▼ │
69
+ report path, wait for explicit │
70
+ "publish/upload" instruction loop back to :compose / :capture / :illustrate
71
+ ```
72
+
73
+ | Step | Input | Output | On failure |
74
+ |---|---|---|---|
75
+ | 0. `:select` | project (cwd or named) + optional feature hint | one confirmed project + one confirmed feature name + why it's demo-worthy | **STOP** — report if no clear feature can be confirmed (don't guess) |
76
+ | 1. `:research` | confirmed project + feature | mission/description (project-level), feature description, key files, how it actually works | n/a — always produces notes, flags gaps explicitly rather than inventing claims |
77
+ | 2. `:storyboard` | mission + feature notes | ordered list of `{step, action, expected_screen, caption}` — one aspect per step | n/a — storyboard is a draft, `:capture` is allowed to correct it |
78
+ | 3. `:capture` | storyboard | one real screenshot file per step + any corrected captions | **STOP** on that step — report what's missing; never substitute a placeholder/mockup image |
79
+ | 4. `:draft-copy` | research notes + captured captions | final Mission & Feature Description prose + one-sentence essence line | n/a — always produces final text, gaps stay explicit |
80
+ | 5. `:illustrate` | the essence line (text must exist first) | one companion illustration, tagged distinct from screenshots — or an explicit omission note | never blocks — skips cleanly if no image generator is configured, or after one off-brief retry |
81
+ | 6. `:compose` | final copy + captured steps + optional illustration | one self-contained HTML file (or html+images folder per config) | loop back to `:capture` if a required image is missing, else fix and retry |
82
+ | 7. `:review` | the composed HTML | pass/fail against the gate below | loop back to `:compose` (content), `:capture` (screenshot), or `:illustrate` (mislabeled companion image) — never ship on a failed gate |
83
+
84
+ ## Golden rule: screenshots are real or the step doesn't exist
85
+
86
+ This skill exists specifically because "feature demo" content is worthless if the images are
87
+ invented, stale, or from the wrong build. `:capture` uses `mcp__playwright__*` (or the project's
88
+ own screenshot tooling if `run`/a project skill already drives it — check for one first) against
89
+ a running instance of the actual project. If a step can't be captured for real (app won't start,
90
+ feature is behind auth you don't have, flow requires data that doesn't exist locally), **that
91
+ step is dropped and reported, not faked.** A shorter, honest demo beats a complete, fabricated one.
92
+
93
+ ## Output
94
+
95
+ Default: one self-contained `.html` file (inline CSS/JS, base64-embedded images) written to
96
+ `docs/feature-showcase/<feature-slug>.html` inside the target project — see
97
+ `feature.config.yaml` for the exact path convention, size ceiling, and the single-file vs.
98
+ folder fallback (used when embedded images would blow past the size ceiling). This is what
99
+ "uploadable anywhere" means: no server, no relative asset paths, no build step — open the file
100
+ or drop it on any static host and it works.
101
+
102
+ **Only push, deploy, or upload when explicitly asked.** Report the local path and a one-line
103
+ description of what the page shows, and wait — same convention as `blog-from-git`'s seed gate
104
+ and `issue-address`'s "only push when asked" rule.
105
+
106
+ ## Why nested skills instead of one inline pass
107
+
108
+ Same reasoning as `issue-address` and `pr-review-sweep`: each phase is a real, independently
109
+ invokable `SKILL.md` so progress is visible step by step ("storyboard produced 5 steps, capture
110
+ got 4 real screenshots, 1 dropped") instead of one opaque generation pass, and a single phase
111
+ (e.g. swapping the screenshot tool, changing the HTML theme) can be edited without touching the
112
+ others. If a step is still doing too much once this ships, decompose it further the same way
113
+ `issue-address:select` was (nested sub-DAG) — continuously, wherever one file covers too much to
114
+ inspect as one unit.
115
+
116
+ ## Folder map
117
+
118
+ ```
119
+ blog-for-project-feature/
120
+ feature.config.yaml ← output path convention, size ceiling, theme tokens — read first
121
+ SKILL.md ← this file: pipeline, DAG, output contract
122
+ 0-select/SKILL.md
123
+ 1-research/SKILL.md
124
+ 2-storyboard/SKILL.md
125
+ 3-capture/SKILL.md
126
+ 4-draft-copy/SKILL.md
127
+ 5-illustrate/SKILL.md
128
+ 6-compose/SKILL.md
129
+ 7-review/SKILL.md
130
+ ```
131
+
132
+ ## What this skill is not
133
+
134
+ - Not `blog-from-git` — no dated post, no rotation ledger, no git-diff scan. This is
135
+ feature-scoped and evergreen; that skill is time-scoped and narrative.
136
+ - Not a marketing copy generator — every claim in `:research`'s output must trace to real code,
137
+ docs, or a live screenshot, same evidentiary bar as `blog-from-git`'s `ground.md`.
138
+ - Not a full site generator — one feature, one page, per run. A project with N features worth
139
+ showcasing gets N runs (and, if wanted later, a separate index/landing skill — not built yet,
140
+ don't invent one inline).
@@ -0,0 +1,74 @@
1
+ # Grounding authority for blog-for-project-feature. Read this before any sub-skill file.
2
+ # If prose in a SKILL.md ever disagrees with this file, this file wins.
3
+
4
+ output:
5
+ # Path is relative to the TARGET project's repo root (the project being showcased),
6
+ # not this plugin's own repo.
7
+ path_template: "docs/feature-showcase/{feature-slug}.html"
8
+ feature_slug_rule: "kebab-case, derived from the confirmed feature name in :select"
9
+ # Prefer one self-contained file. Only fall back to a folder when the size ceiling
10
+ # below would be exceeded by inlining images.
11
+ default_mode: single_file # single_file | folder
12
+ folder_fallback_layout:
13
+ html: "docs/feature-showcase/{feature-slug}/index.html"
14
+ images: "docs/feature-showcase/{feature-slug}/screenshots/"
15
+ single_file_size_ceiling_mb: 8 # switch to folder_fallback_layout above this
16
+ never_upload_or_deploy_without_explicit_ask: true
17
+
18
+ images:
19
+ format: png
20
+ max_width_px: 1600 # downscale before embedding; demo clarity, not pixel-perfect
21
+ compression: "reasonable lossy ok for PNG->WebP if it keeps text legible; never so lossy captions become unreadable"
22
+ source_of_truth: "mcp__playwright__browser_take_screenshot against a real running instance"
23
+ placeholder_or_mockup_images: forbidden # a dropped step beats a fabricated one
24
+
25
+ illustration:
26
+ # The ONE companion image, generated by :illustrate — a distinct image class from the
27
+ # click-through screenshots above. Optional: omitted cleanly if no generator is configured.
28
+ runs_after: draft-copy # never generate from anything but finalized copy
29
+ generator: "project-local generator if the target project has one, else Gemini
30
+ (gemini-2.5-flash-image via google-genai — same provider/pattern as Burrow's
31
+ app/shared/image_gen.py) using GEMINI_API_KEY / GOOGLE_API_KEY from the environment"
32
+ count: 1
33
+ placement: "Feature Description section header only — never inside the Click-Through Demo"
34
+ format: png
35
+ max_width_px: 1200
36
+ style: "conceptual/editorial illustration — mood/palette should follow the project's own
37
+ theme tokens above if it has brand colors, otherwise a clean flat/geometric style"
38
+ must_not_resemble: "a UI mockup, screenshot, or contain invented interface text/window chrome
39
+ — must be visually distinguishable from a real captured screen at a glance"
40
+ max_regeneration_attempts: 1
41
+ on_no_generator_available: "skip silently, no page-visible note, report to the operator only"
42
+
43
+ content:
44
+ sections_required:
45
+ - "Project Mission & Description" # 2-4 sentences, from :research, project-level not feature-level
46
+ - "Feature Description" # what it does, why it matters, 1-2 short paragraphs
47
+ - "Click-Through Demo" # the storyboard steps, one aspect revealed at a time
48
+ demo_interaction: "one-step-at-a-time reveal (next/prev controls or numbered tabs) — never a single long scroll dump of every screenshot at once"
49
+ tone: "same plain, non-salesy, show-don't-tell bar as blog-from-git/voice.md bot-tell blocklist — no 'excited to share', no grand-implication filler"
50
+ caption_rule: "every screenshot has a one-sentence caption naming what changed on screen, not what the code does internally"
51
+
52
+ theme:
53
+ # Minimal neutral tokens so every showcase looks consistent without importing a design system.
54
+ # Swap these per-project only if the project has its own documented brand tokens.
55
+ font_stack: "system-ui, -apple-system, 'Segoe UI', sans-serif"
56
+ bg: "#0b0d10"
57
+ surface: "#15181d"
58
+ text: "#e8eaed"
59
+ text_muted: "#9aa0a6"
60
+ accent: "#5b9dff"
61
+ radius_px: 10
62
+ max_width_px: 900
63
+
64
+ gates:
65
+ # Checked in step 7 (:review). All must pass before reporting the page ready.
66
+ - "every image referenced actually loads (no broken src, no 404 in a local file:// open)"
67
+ - "zero placeholder/mockup images — every screenshot traces to a real :capture run"
68
+ - "if present, the companion illustration sits only in the Feature Description header and is
69
+ visually distinguishable from a real screenshot (no UI chrome/invented interface text)"
70
+ - "all three required sections present and non-empty (illustration is optional, never gates)"
71
+ - "demo is click-through (has working next/prev or tab controls), not a flat scroll"
72
+ - "file opens correctly via file:// with no console errors (network calls, external CDN fonts/JS forbidden — must be fully offline-capable)"
73
+ - "single_file_size_ceiling_mb respected, or folder fallback used instead"
74
+ - "no invented claims — every sentence in Mission/Feature sections traces to :research notes"
@@ -88,6 +88,7 @@ Data-driven from 400+ scheduler runs: long hangs (not bad code) are the dominant
88
88
  - **Negative-assertion checks must exit 0 when clean.** A check that verifies the *absence* of something (a `grep` that should find nothing, "no leftover X", `diff` expecting no change) must return exit 0 on the clean case. A bare `grep` exits **1 on no-match** — so the *success* path surfaces as `is_error=true` and the verifier downgrades a perfect run to `needs_review`. Always invert: `if <detector>; then echo "HALT: <what was found>"; exit 1; fi; echo clean`. Never let the no-match/empty path carry the non-zero exit.
89
89
  - **Recover or annotate every error — don't strand a Traceback in the transcript.** The verifier downgrades an otherwise-perfect run to `needs_review` when a `Traceback`/`Error` appears with *no visible recovery within ~10 lines* (the `transcript_errors` heuristic — the single most common false-positive on green deliverables). Two executor habits cause it: (1) **throwaway probes that error** — an inline `python -c` with a quoting/f-string slip, a wrong kwarg, a bad path. When a probe errors, immediately re-run the corrected version *or* print one line `# expected/handled: <why>` right after, so recovery is adjacent. Don't move on leaving a bare error as the last thing in that step. Prefer a small temp `.py` file over a fragile multi-quote `python -c` one-liner (inline f-string errors are the top source of stranded tracebacks). (2) See the timeout rule below.
90
90
  - **An *expected* bounded-timeout (exit 124) must be annotated, not bare.** `timeout`-capping a genuinely long task you expect to hit the cap (a full-universe ingest, a long scan) is correct — but a bare `Exit code 124` reads as a failure to the verifier. Wrap it so the cap is a success-with-note: `timeout 120 <cmd> || { rc=$?; [ $rc -eq 124 ] && echo "hit time cap — idempotent/partial, rows persist incrementally; OK" || { echo "HALT: <cmd> failed rc=$rc"; exit 1; }; }`. (Distinguish 124 = expected cap from a real non-zero.) For work that legitimately needs longer than a safe cap, run it in the background and poll a bounded number of times rather than capping the foreground command.
91
+ - **Polling remote CI/job status: never `sleep N && <cmd>`, and annotate the pending exit code.** The harness hard-blocks a `sleep` chained to another command (`Blocked: sleep 90 followed by: gh pr checks ...`) and that block lands in the transcript as a bare `is_error=true` — usually in the last 20% of the run, right where the verifier weighs errors most. To wait for a remote run, use the tool's own blocking watcher under a hard cap: `timeout 600 gh run watch <run-id> --repo <owner>/<repo> --exit-status`. Also note `gh pr checks` is a **negative-assertion-shaped command**: it exits `8` while checks are pending and `1` when a check failed or none are reported — so the ordinary "still running" path is non-zero. Wrap it so the expected cases print a clean token rather than a bare error: `if out=$(timeout 60 gh pr checks <n> --repo <r> 2>&1); then echo "CI GREEN"; else rc=$?; echo "gh pr checks rc=$rc (8=pending, 1=fail/none) — expected/handled"; fi`. (Incident: PRD 745 fixed PR #188's Lint + Docs-integrity failures, pushed, and CI went fully green — but its `sleep 20 && gh pr checks` (exit 8) and `sleep 90 && gh pr checks` (harness-blocked) sat unannotated at the very end of the transcript and the run was flagged despite a truthful PASS and a landed commit.)
91
92
  - **Finish so the verifier auto-clears you.** The scheduler appends a finish protocol that requires you to COMMIT your work and emit `SCHEDULER_VERDICT: PASS` (or `FAIL <reason>` + `exit 1`) as the literal last line. Honor it exactly: a *truthful* PASS plus a commit that landed during the run is what lets the verifier override incidental transcript noise (a grep hit containing "Error", a TDD red-phase run, a debug Traceback) instead of parking the job in `needs_review` for a human. A job that exits 0 with **uncommitted** changes, or with no PASS sentinel, is the #1 cause of needless `needs_review`. Never print PASS on a red gate — a lying PASS turns the verifier into a silent-failure shipper.
92
93
  - **Don't leak expected-error text into tool output.** The verifier pattern-matches transcript content for `Traceback`/`FAIL`/`Error:`. When a step is *expected* to error (a TDD red-phase test, an availability/existence probe, a "should raise" assertion), don't let the raw exception land verbatim — capture it and surface a clean token instead: `if python -c '…' 2>/dev/null; then echo PROBE_OK; else echo PROBE_ABSENT; fi`, or pipe the noisy run through a matcher that prints only `RED (expected)` / `GREEN`. When you retry a transient failure, re-run the **same command with the same description** — the verifier's self-recovery detector pairs a failed call with a later identical-description call that succeeds and clears it.
93
94
  - **End green: run the acceptance/test gate LAST, and let nothing error after it.** The post-run verifier scans the transcript and downgrades to `needs_review` on error markers — and weighs the *final* portion of the run most heavily (a tool error in the last ~20% trips it even if everything actually passed). So order the run so the last command is the green AC gate: do any intentionally-failing step (e.g. a TDD red test, an expected-nonzero probe) **early**, never after the gate. If you must demonstrate a failure late, capture it so it doesn't surface as a raw `is_error`/`Traceback` (`… 2>&1 | tail` inside a conditional, or assert on the captured text) rather than letting it hit the transcript bare.
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: issue-address:select:fetch-pool
3
+ description: Fetch the FULL open-issue pool on the current project's repo with bodies, with verified completeness (not just a fixed --limit guess). First sub-step of issue-address:select.
4
+ ---
5
+
6
+ # issue-address:select:fetch-pool
7
+
8
+ ## Steps
9
+
10
+ 1. **Fetch with a generous limit, then verify nothing was truncated** — don't trust a
11
+ single fixed `--limit` the way an earlier version of this skill (and, separately,
12
+ `pr-review-sweep`'s original GraphQL query) did; both silently truncated real data on
13
+ this repo before. Converge instead of guessing:
14
+ ```bash
15
+ LIMIT=100
16
+ while true; do
17
+ COUNT=$(gh issue list --repo "$(gh repo view --json nameWithOwner -q .nameWithOwner)" --state open --limit "$LIMIT" --json number --jq 'length')
18
+ if [ "$COUNT" -lt "$LIMIT" ]; then break; fi # got everything — count came in under the cap
19
+ LIMIT=$((LIMIT * 2)) # cap was hit exactly — double and re-check
20
+ done
21
+ gh issue list --repo "$(gh repo view --json nameWithOwner -q .nameWithOwner)" --state open --limit "$LIMIT" \
22
+ --json number,title,body,labels,assignees,comments,createdAt,reactionGroups
23
+ ```
24
+ `COUNT == LIMIT` is the tell that you hit the ceiling, not the true end of the list —
25
+ never treat that as "done." Only stop once a fetch returns fewer results than it was
26
+ capped at.
27
+
28
+ 2. **Fetch bodies, not just titles.** Title-only scanning is what let issue #180 get
29
+ picked without anyone reading its file/line citations first. `body` and `comments` are
30
+ required output fields, not optional — the next sub-steps depend on them.
31
+
32
+ 3. **Also fetch `reactionGroups`** (👍/👎 counts) — a real, if imperfect, community-signal
33
+ input for `issue-address:select:rank-impact` later, beyond just label text.
34
+
35
+ ## Output
36
+
37
+ The full, verified-complete list of open issues with bodies, labels, assignees, comments,
38
+ and reaction counts — handed to `issue-address:select:reject-fixed` next.
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: issue-address:select:reject-fixed
3
+ description: Reject candidates from issue-address:select:fetch-pool's pool that are already fixed in current code, by checking the actual file/behavior each issue cites against a freshly-fetched origin/main — semantically, not just a literal string grep. Second sub-step of issue-address:select.
4
+ ---
5
+
6
+ # issue-address:select:reject-fixed
7
+
8
+ The check that would have caught issue #180 before any other work started — its body
9
+ cited `apps/web/app/app.css` around specific line numbers, and the current code already
10
+ had the exact fix (`min-width: 0`) the issue was reporting the absence of.
11
+
12
+ ## Steps
13
+
14
+ 0. **Fetch fresh before checking anything.** `git fetch origin main` first, and read
15
+ against `origin/main`, not a local branch — a local checkout can be stale or on an
16
+ unrelated feature branch, and a stale read produces a false "still broken" verdict.
17
+ (Found live: `#194` looked unfixed against a stale local branch, but `origin/main` had
18
+ merged the fix days earlier.)
19
+
20
+ 1. **For each candidate whose body cites a specific file, line, function, or CSS
21
+ selector/rule**, read the actual current code at that location:
22
+ ```bash
23
+ git show origin/main:<cited-path> | sed -n '<start>,<end>p'
24
+ ```
25
+ or grep for the cited selector/function name if line numbers have drifted (files get
26
+ reorganized — issue #180's citation was for `apps/web/app/app.css`, which had since
27
+ been split into `apps/web/app/styles/*.css`; searching by class/function name across
28
+ the styles directory found the real current location).
29
+
30
+ 2. **Check the behavior semantically, not just for the literal string the issue used.** A
31
+ fix can land in a different code *shape* than the issue's own words. (Found live: `#57`
32
+ asked for a `<link rel="canonical">` tag; a literal grep for `rel="canonical"` found
33
+ nothing, so the naive check would have called it unfixed — but the actual fix was a
34
+ data-driven React Router `meta()` descriptor, `{ tagName: 'link', rel: 'canonical', href
35
+ }`, which produces the same tag at render time without ever containing that literal
36
+ string in source.) Before concluding "not fixed" on a bare grep miss:
37
+ - Search one level broader — the surrounding directory/module, a shared
38
+ helper/util file, a `meta.ts`/`config.ts`-style indirection layer — for the *behavior*
39
+ the issue describes, not just its exact wording.
40
+ - If the issue names a concrete symptom (a missing tag, a wrong computed value, a
41
+ missing gate), look for whatever code *produces* that output, however it's
42
+ structured, before deciding the symptom is still present.
43
+
44
+ 3. **Compare what the issue describes as broken against what the code currently does.**
45
+ If the issue's own hypothesized fix (or an equivalent one, per step 2) is already
46
+ present — reject, log as `already-fixed`, cite the exact current file:line as evidence.
47
+ Don't spend a full `issue-address:reproduce` pass confirming what a direct code read
48
+ already answered.
49
+
50
+ 4. **If the citation is vague, or the file has moved/renamed and a reasonably broadened
51
+ search (step 2) still finds nothing**, don't reject on inconclusive evidence — mark the
52
+ candidate `unconfirmed` and let it pass through to the next sub-step. This step only
53
+ rejects on positive evidence of an existing fix, never on failure-to-locate.
54
+
55
+ 5. **If the git history around that file shows the fix landed suspiciously close to the
56
+ issue's `createdAt`** (within a day or two, either direction), note that explicitly —
57
+ it's a strong signal the report and the fix crossed in flight (exactly what happened
58
+ with #180: fix committed 2026-06-29, issue filed 2026-06-30).
59
+
60
+ ## Output
61
+
62
+ Two lists: `already-fixed` (rejected, with evidence) and `survives` (candidates to pass to
63
+ `issue-address:select:reject-claimed` next, tagged `unconfirmed` where this step couldn't
64
+ conclusively check).
@@ -0,0 +1,71 @@
1
+ ---
2
+ name: issue-address:select:reject-claimed
3
+ description: Reject candidates already covered by a PR (open, OR closed-but-superseded-by-a-merged-successor) or already claimed by an assignee/comment/supersede-note — with every text-search hit manually confirmed before being trusted. Third sub-step of issue-address:select.
4
+ ---
5
+
6
+ # issue-address:select:reject-claimed
7
+
8
+ ## Steps
9
+
10
+ 1. **Search for a PR that might already cover each surviving candidate — open AND
11
+ closed, not just open:**
12
+ ```bash
13
+ gh pr list --repo "$(gh repo view --json nameWithOwner -q .nameWithOwner)" --state all --search "<N> in:body"
14
+ gh api search/issues -f q='repo:$(gh repo view --json nameWithOwner -q .nameWithOwner) is:pr <N>'
15
+ ```
16
+ Also skim PR titles for matching subject matter (same file area, same symptom) — a PR
17
+ can fix an issue's bug without a formal `Closes #N` link. Searching only `state:open`
18
+ misses the real case found live: `#194` had two closed (not merged) PRs, `#203`/`#215`,
19
+ each superseded — but the actual fix landed in a *third*, later PR (`#251`) that neither
20
+ text-search-by-issue-number pass would find unless the closed ones are read first and
21
+ followed to what replaced them (step 3).
22
+
23
+ 2. **Never trust a text-search hit without reading it.** This already produced a false
24
+ positive once: searching `"180 in:body"` matched PR #239 ("add automated price-anomaly
25
+ screen"), which had nothing to do with issue #180 — the digit string almost certainly
26
+ matched a line number or unrelated number elsewhere in that PR's body. For every
27
+ text-search hit:
28
+ - Open the matched PR's actual title + description.
29
+ - Confirm it genuinely addresses the same file area / same symptom the issue describes
30
+ — not just that the issue number appears somewhere in the text.
31
+ - If it's a false positive (number coincidence, unrelated context), **discard the match
32
+ and keep the candidate as unclaimed** — don't reject on a search artifact.
33
+
34
+ 3. **For every closed-but-not-merged PR found in step 1, read its closing comment before
35
+ deciding the candidate survives.** A PR closed without merging is not automatically
36
+ "unclaimed again" — read why it closed:
37
+ ```bash
38
+ gh pr view <PR#> --repo "$(gh repo view --json nameWithOwner -q .nameWithOwner)" --json state,mergedAt,closedAt,comments
39
+ ```
40
+ - If the closing comment says the work was split/replaced ("затварям... но не защото
41
+ работата е отпаднала", "closing in favor of #N", "superseded by #N", "merged into
42
+ #N"), **follow the chain to whatever PR(s) it names**, and check *their* merge state
43
+ the same way. Keep following until you reach a terminal state (merged = reject as
44
+ already-fixed; still open = reject as already-covered; abandoned with no successor =
45
+ candidate survives).
46
+ - This chain can be more than one hop deep (found live: `#194`'s PR `#203` was split
47
+ into `#251`/`#252`/`#253`; a sibling PR `#215` was independently closed in favor of
48
+ the same `#251`) — don't stop at the first successor if its own comments point
49
+ further.
50
+
51
+ 4. **Reject candidates already claimed by a person, or already noted as superseded in the
52
+ issue's OWN comments** (not just a linked PR's). Check `assignees` (non-empty =
53
+ explicit claim) and skim the issue's `comments` for:
54
+ - Claim language: "работя по това", "assigned to me", "I'll pick this up".
55
+ - Supersede/decompose language: "предлагам да се затвори в полза на", "closing in
56
+ favor of", "split into #N / #M / #P", "superseded by". (Found live: `#154`'s own
57
+ comment thread explicitly proposed closing it in favor of child issues `#156`/`#158`/
58
+ `#163` — a signal that lives in the issue's comments, not in any PR search at all.)
59
+ Reject on either signal — don't take work someone else already started or already
60
+ restructured, even informally.
61
+
62
+ 5. **Search for a duplicate open issue** describing the same underlying bug under a
63
+ different number — `gh issue list --state open --search "<keywords>"`, same
64
+ confirm-before-trusting rule as step 2 applies here too. If a better-established
65
+ duplicate exists, flag it rather than silently picking one.
66
+
67
+ ## Output
68
+
69
+ `survives` list (candidates cleared of PR/claim/supersede/duplicate overlap — including
70
+ resolved multi-hop PR chains — with any false-positive search hits explicitly noted as
71
+ discarded) handed to `issue-address:select:rank-impact`.
@@ -0,0 +1,39 @@
1
+ ---
2
+ name: issue-address:select:rank-impact
3
+ description: Rank surviving candidates by a composite impact signal (priority label + community reactions + cross-references + repro quality), not label text alone, and output exactly one issue number. Final sub-step of issue-address:select.
4
+ ---
5
+
6
+ # issue-address:select:rank-impact
7
+
8
+ ## Steps
9
+
10
+ 1. **Score each surviving candidate on multiple signals, not just its `priority:` label**
11
+ (a label-only heuristic silently mis-ranks an unlabeled-but-severe issue below a
12
+ labeled-but-minor one):
13
+ - **Priority label**: `high` > `medium` > `low` > none (weakest single signal alone).
14
+ - **Category label**: `bug`/`security`/`data-quality` outrank `enhancement`/
15
+ `discussion`/`docs` for this skill's purpose (fixing something broken, not building
16
+ something new).
17
+ - **Community reaction count** (from `fetch-pool`'s `reactionGroups`) — more 👍 reactions
18
+ is a real signal of user-felt impact independent of whatever label was applied at
19
+ triage time.
20
+ - **Cross-references** — check `gh issue view <N> --json timelineItems` (or equivalent)
21
+ for how many other issues/PRs reference this one; a heavily-cross-referenced issue is
22
+ blocking or informing other work, raising its effective priority above its label.
23
+ - **Repro quality** — concrete repro steps / cited file-line locations beat a vague
24
+ report; cheaper to reproduce reliably, lower risk of another #180-style dead end.
25
+
26
+ 2. **Combine into a ranked order** — no single signal should override all others (e.g.
27
+ don't let one 👍 reaction alone outrank an explicit `priority: high` label; use
28
+ priority + category as the primary sort, reaction count + cross-references as
29
+ tie-breakers within a tier, repro quality as the final tie-breaker).
30
+
31
+ 3. **Output exactly one issue number** — the top of the ranked list — with a one-line
32
+ justification citing which signals won, plus the full ranked list (not just the
33
+ winner) and the rejected list from the earlier two sub-steps, so the whole selection
34
+ is auditable, not just its conclusion.
35
+
36
+ ## Output
37
+
38
+ Hand off the single selected issue number to `issue-address:confirm-open`, which
39
+ re-verifies it fresh (state, PR/duplicate claims) immediately before work starts.
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: issue-address:select
3
+ description: Step 0 of issue-address — select exactly ONE open issue on the current project's repo to work, by orchestrating 4 nested sub-skills (fetch-pool, reject-fixed, reject-claimed, rank-impact) in sequence. Only invoked when the caller hasn't already named a specific issue number.
4
+ ---
5
+
6
+ # issue-address:select (sub-orchestrator)
7
+
8
+ Only runs when `issue-address` is invoked without a specific issue number already named
9
+ (if the caller already has one, skip straight to `issue-address:confirm-open` — that step
10
+ re-verifies the single chosen issue right before work starts, as a freshness guard; this
11
+ skill's job is choosing which issue out of many, cheaply and verifiably, across the whole
12
+ pool).
13
+
14
+ Exists because of a real incident: issue #180 was picked by title/label alone, and 2 full
15
+ `issue-address` steps ran before discovering — only once `issue-address:reproduce`
16
+ actually tried to reproduce it — that the fix had already shipped a day before the issue
17
+ was even filed. The information needed to catch that (the issue's own cited file/line + a
18
+ grep of current code) was available for free at selection time and simply wasn't checked.
19
+
20
+ This skill is itself further decomposed into 4 nested sub-skills, for the same reason
21
+ `issue-address` is decomposed: so each check is independently invokable and its result
22
+ inspectable, instead of one opaque scan-and-pick pass. It also closes two gaps found after
23
+ the first version shipped: an unverified fetch limit (the same class of silent-truncation
24
+ bug that hit `pr-review-sweep`), and an unconfirmed PR-search false positive (seen live in
25
+ the #180 trial — a text search on "180" matched an unrelated PR by number coincidence).
26
+
27
+ **Naming convention:** sub-directories are prefixed `0-`, `1-`, `2-`, `3-` in execution
28
+ order (see `issue-address/SKILL.md` for the repo-wide rule) — a plain directory listing of
29
+ this folder sorts in DAG order without opening any file.
30
+
31
+ ## Pipeline DAG
32
+
33
+ ```
34
+ open-issue pool
35
+
36
+
37
+ ┌─────────────────────────────┐
38
+ │ 0. select:fetch-pool │ verified-complete fetch (no silent truncation)
39
+ └─────────────────────────────┘
40
+ │ full pool w/ bodies, labels, assignees, comments, reactions
41
+
42
+ ┌─────────────────────────────┐
43
+ │ 1. select:reject-fixed │ grep current code at each cited file/line
44
+ └─────────────────────────────┘
45
+ │ survives (already-fixed candidates dropped, evidence logged)
46
+
47
+ ┌─────────────────────────────┐
48
+ │ 2. select:reject-claimed │ PR-search hits confirmed, not trusted blind
49
+ └─────────────────────────────┘
50
+ │ survives (PR'd/claimed/duplicate candidates dropped, evidence logged)
51
+
52
+ ┌─────────────────────────────┐
53
+ │ 3. select:rank-impact │ composite score, not label text alone
54
+ └─────────────────────────────┘
55
+
56
+
57
+ one issue number + full audit trail (ranked list, rejected list, why)
58
+
59
+
60
+ issue-address:confirm-open (next skill up the chain, outside this sub-orchestrator)
61
+ ```
62
+
63
+ | Step | Input | Output | On failure/empty |
64
+ |---|---|---|---|
65
+ | 0. `select:fetch-pool` | none (repo-wide `gh issue list`) | full open-issue pool, verified-complete | n/a — loops its own fetch until verified complete |
66
+ | 1. `select:reject-fixed` | full pool | `already-fixed` list (w/ evidence) + `survives` list | if pool empties out here, report "no candidates, all already fixed" |
67
+ | 2. `select:reject-claimed` | `survives` from step 1 | `already-claimed` list (w/ evidence) + `survives` list | if pool empties out here, report "no candidates, all claimed/PR'd" |
68
+ | 3. `select:rank-impact` | `survives` from step 2 | one issue number + ranked list + justification | if `survives` was already empty, report the empty pool rather than fabricating a pick |
69
+
70
+ ## Output
71
+
72
+ Hand off the single selected issue number to `issue-address:confirm-open`, which
73
+ re-verifies it fresh (state, PR/duplicate claims) immediately before work starts — this
74
+ skill's checks are about narrowing a big pool cheaply and verifiably; confirm-open's
75
+ checks are about not acting on stale information for the one issue actually chosen.
@@ -0,0 +1,39 @@
1
+ ---
2
+ name: issue-address:confirm-open
3
+ description: Step 1 of issue-address — confirm a named GitHub issue on the current project's repo is genuinely OPEN and not already claimed/fixed by another PR or duplicate issue, before any work starts. Returns a clear go/no-go.
4
+ ---
5
+
6
+ # issue-address:confirm-open
7
+
8
+ Called with one issue number `<N>`. Produces a go/no-go verdict before any code work begins.
9
+
10
+ ## Steps
11
+
12
+ 1. **Fetch the issue.**
13
+ ```bash
14
+ gh issue view <N> --repo "$(gh repo view --json nameWithOwner -q .nameWithOwner)" --json state,title,body,comments,url
15
+ ```
16
+ If `state` is not `OPEN` — **stop, no-go.** Report the actual state (closed/merged
17
+ reference) and why. Do not proceed to reproduce or fix a closed issue.
18
+
19
+ 2. **Search for an existing open PR that already covers it** — check both explicit links
20
+ and subject-matter overlap, since a PR can fix the bug without a formal `Closes #N`:
21
+ ```bash
22
+ gh pr list --repo "$(gh repo view --json nameWithOwner -q .nameWithOwner)" --state open --search "<N> in:body"
23
+ gh api search/issues -f q='repo:$(gh repo view --json nameWithOwner -q .nameWithOwner) is:pr is:open <N>'
24
+ ```
25
+ Also skim open PR titles for matching subject matter (same file area, same symptom).
26
+ If found — **stop, no-go.** Report which PR number and why it looks like a match; don't
27
+ duplicate work already in flight.
28
+
29
+ 3. **Search for a duplicate open issue** covering the same underlying bug (different
30
+ number, same root cause) — `gh issue list --repo "$(gh repo view --json nameWithOwner -q .nameWithOwner)" --state open --search
31
+ "<keywords>"`. If a duplicate exists and is a better-established report, flag it — the
32
+ caller decides which one to actually work.
33
+
34
+ 4. **Verdict.** Return one of:
35
+ - **GO** — issue is open, unclaimed, no duplicate blocking it. Include the issue title,
36
+ body, and any repro steps mentioned in comments, so the next step doesn't have to
37
+ re-fetch them.
38
+ - **NO-GO** — state why (closed / already covered by PR #X / duplicate of #Y), and stop
39
+ the whole `issue-address` sequence here. Do not continue to reproduce/fix.
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: issue-address:claim
3
+ description: Step 2 of issue-address — claim a confirmed-open, confirmed-unclaimed issue on the current project's repo before reproducing/fixing it, by self-assigning and posting a claim comment via the gh CLI (no dedicated GitHub MCP server is connected in this environment — gh is the actual mechanism). Closes the race condition where two runs could pick the same issue.
4
+ ---
5
+
6
+ # issue-address:claim
7
+
8
+ Runs immediately after `issue-address:confirm-open` returns GO, and before
9
+ `issue-address:reproduce` starts any work. Exists because confirming an issue is
10
+ open/unclaimed and then *starting work on it* are two different moments in time — without
11
+ an explicit claim in between, a second concurrent run of this same skill (or a human
12
+ contributor) could pick the same issue, and nothing in `issue-address:select`'s
13
+ `reject-claimed` check would have caught it, since that check only looks for assignees and
14
+ existing claim-language comments — which don't exist yet for an issue nobody has started.
15
+
16
+ **No dedicated GitHub/issue-tracking MCP server is connected in this environment** — every
17
+ GitHub interaction in this whole skill chain (and everywhere else this session) goes
18
+ through the `gh` CLI via Bash. If a GitHub MCP server becomes available later, swap the
19
+ commands below for its equivalent calls; until then, `gh` is the actual mechanism, not a
20
+ gap to work around.
21
+
22
+ ## Steps
23
+
24
+ 1. **Self-assign**, so the issue shows a claim to anyone else's `select:reject-claimed`
25
+ pass or manual look:
26
+ ```bash
27
+ gh issue edit <N> --repo "$(gh repo view --json nameWithOwner -q .nameWithOwner)" --add-assignee "@me"
28
+ ```
29
+ If self-assignment fails (e.g. insufficient repo permissions — a common case for
30
+ external contributors on a repo they don't have write access to), don't halt the
31
+ sequence on that alone; fall back to step 2's comment as the claim signal, and note in
32
+ the report that assignment wasn't possible so a human knows to check.
33
+
34
+ 2. **Post a claim comment** — visible even to someone who doesn't check assignees,
35
+ and consistent with the claim-language `select:reject-claimed` already scans for
36
+ ("I'll pick this up", "working on this"; a project with a non-English contributor base,
37
+ e.g. `midt-bg/sigma`'s Bulgarian-speaking reviewers, may localize this string — check
38
+ the project's own `AGENTS.md`/`CLAUDE.local.md` for a house phrase before defaulting to
39
+ English):
40
+ ```bash
41
+ gh issue comment <N> --repo "$(gh repo view --json nameWithOwner -q .nameWithOwner)" --body "Working on this issue (issue-address skill chain). Will link the PR here after fix + verify."
42
+ ```
43
+
44
+ 3. **Re-confirm the claim actually landed** before proceeding — read the issue back and
45
+ check the assignee list and/or the posted comment are present:
46
+ ```bash
47
+ gh issue view <N> --repo "$(gh repo view --json nameWithOwner -q .nameWithOwner)" --json assignees,comments
48
+ ```
49
+ If neither the assignment nor the comment is visible, don't silently proceed as if
50
+ claimed — report the failure; a claim that didn't actually post protects nobody.
51
+
52
+ ## Output
53
+
54
+ Hand off to `issue-address:reproduce` — same issue number, now with a real claim on
55
+ record. If this step's own re-confirmation fails (step 3), stop the sequence and report,
56
+ same severity as a `confirm-open` NO-GO — proceeding to reproduce/fix on an issue that
57
+ isn't actually marked claimed defeats the point of adding this step.