From personal-corp-skills
Syncs session work into GitHub issues and queries track status across repos. Two modes: write (end-of-session sync with issue updates, epics, labels, Project placement) and read (status lookup).
How this skill is triggered — by the user, by Claude, or both
Slash command
/personal-corp-skills:managerThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Part of the Personal Corp framework — running a one-person business through AI agents.
Part of the Personal Corp framework — running a one-person business through AI agents.
Bridges session work and GitHub issues in both directions. GitHub issues are the source of truth for tasks; a GitHub Project board is the source of truth for what's active. Manager has two modes:
Manager is the canonical issue workflow. Do not fall back to a generic issue helper for these operations — manager owns the read-modify-write contract, the invariants, and the Project sync.
Before first use, define this in your project's CLAUDE.md:
## Manager Config
### GitHub owner
Your GitHub username or org for issue search:
- owner: your-github-handle
### Repos to scan (cross-repo issue search scope)
List repos manager should search:
- ~/Projects/main
- ~/Projects/ops
- ~/Projects/marketing
### Tasks index file (optional)
Path to your curated "what's hot this week" file. Manager reads it FIRST before any `gh search` to scope queries:
- tasks_index: ~/docs/tasks.md
(если файла нет — manager работает без индекса, поиск идёт по всем repos)
### Domain → repo routing
| Domain | Repo |
|--------|------|
| commercial / B2B deals | crm |
| product launches | main |
| ops / infrastructure | ops |
| content | marketing |
### GitHub Projects integration
Manager treats Project placement as an invariant (see Iron invariants). Declare your boards:
- weekly_project: <number> # cross-repo "everything active this week" board
- weekly_project_owner: your-github-handle
- status_field: Status # the single-select field that holds the lane
- status_in_progress: In progress # option name (or id) for the active lane
- domain_projects (optional): # per-domain boards, if you keep them
| Domain | Project number |
| commercial | <number> |
| product | <number> |
Cache resolved field/option IDs here once discovered (`gh project field-list <N> --owner OWNER --format json`) so manager doesn't re-fetch them every run.
### W-label convention (optional)
- enabled: true
- format: W{NN} (ISO week)
(если false — manager создаёт issues без weekly labels)
### Standing write authorization
- mode: ask-each-time | execute-after-plan
(default: ask. execute-after-plan = manager executes writes after showing the brief plan, without separate confirmation)
### CRM integration (optional)
- crm_path: ~/Projects/crm
- crm_pointer_format: [[<slug>]]
(если не используется — секция игнорируется; см. CRM integration ниже)
No separate init skill needed — this section is the setup. Title-type metadata is NOT configured here: it lives in labels, the parent tree, and Projects (see Issue title convention).
Every issue manager touches MUST satisfy the base three; an active / current-week issue must additionally satisfy the Project and work-record invariants:
<track-slug>, <client>-deal). Track is recognized by title text and epic membership.Project drift.parent_issue_url is not enough. The visible root epic must itself be in the relevant Project view with a non-empty status lane.Without these — the issue isn't tracked correctly. If no parent epic exists in any repo — manager raises this in proposal and offers to create or pick an existing one, before sync. Never leaves orphan issues.
digraph mode {
"User invocation" [shape=box];
"Has session artifacts to sync?" [shape=diamond];
"WRITE mode" [shape=box];
"READ mode" [shape=box];
"User invocation" -> "Has session artifacts to sync?";
"Has session artifacts to sync?" -> "WRITE mode" [label="yes — update issues by what was done"];
"Has session artifacts to sync?" -> "READ mode" [label="no — query like 'what about X', 'status Y'"];
}
Signal for write mode: user said «sync session», «зафиксируй», «обнови issues», OR invoked /manager without args at end of session, OR explicitly listed artifacts/changes.
Signal for read mode: user asked a question about state — «what about», «status», «есть ли», «какие issues по».
Bare /manager invocation: infer artifacts from current conversation context — what tracks were touched, what files were modified/created, what decisions were made. Do NOT ask user to re-list everything. Form brief execution plan (5-15 lines), then execute under the configured authorization mode.
Do NOT use manager for: creating ideas without artifacts (that's brainstorming); closing an issue without explicit user instruction; batch CRM updates (that's a CRM skill, not manager).
Output language matches the user's input language and project conventions. Technical tokens remain as-is and are not translated: issue names (<repo>#<N>), labels (W18, retro:W17, backlog), commands (gh issue comment), file paths, original English titles of issues in quotes.
New issue titles (and renamed titles) follow the user's locale — match the language convention of the project. English is allowed only as a proper noun: product name, repo name, brand, API name, public upstream term (GitHub, Telegram, LMS, PRD, E2E). English action verbs and generic filler phrases in titles are forbidden:
delivery track, launch/funnel, follow-up, workshop prep, handoff, rollout package, topic TBD, date TBD (as English filler when the project language is not English)All narrative elements in issue titles, body headers, and proposal text follow the project's locale. Technical tokens (repo#N, label names, CLI commands, file paths, quoted original titles of existing issues) stay as-is.
$TASKS_INDEX_PATH (if configured) — current week index. Source for: what's hot this week, what tracks are active, repo pointers for each track. Read FIRST to frame the session.$YOUR_OWNER/* — authoritative for individual tasks. Search via gh search issues --owner $YOUR_OWNER or batched GraphQL (see below).Use (write mode):
Use (read mode):
Do NOT use:
Pre-flight MUST run before any gh search, gh issue, or other GH command. No exceptions.
Keep pre-flight silent and minimal. Do not dump tasks index content, full git status, or label catalogues into chat. Read what you need internally, surface only what changes the proposal. Output budget for pre-flight: 0 lines.
$TASKS_INDEX_PATH FIRST (if configured). This is the curated index of current-week priorities + active tracks + repo pointers. Without it, search is shotgun (random keywords) instead of targeted.gh project item-list <WEEKLY_PROJECT> --owner $YOUR_OWNER --format json --limit 1000 > /tmp/manager-proj.json
repo#N GitHub issue reference → surface as Task drift: <row> — no GitHub issue. In read mode: report only. In write mode: find an existing issue or create one (then it carries the iron invariants). Manager does not rewrite the tasks index itself — index curation is the weekly-planning skill's job.Run independent gh reads in parallel — multiple Bash calls in a single message. Run sequentially only when the output of one call is required as input for the next.
If you're about to run gh search issues without first reading $TASKS_INDEX_PATH (when configured) — STOP. You're about to do shotgun search instead of using the Project/track index that already exists.
digraph manager_write {
"Infer artifacts from conversation" [shape=box];
"Silent pre-flight + cross-repo search" [shape=box];
"Brief execution plan (5-15 lines)" [shape=box];
"Hard blocker / ambiguous write?" [shape=diamond];
"Execute (body, comment, labels, Project)" [shape=box];
"Brief result report" [shape=box];
"Infer artifacts from conversation" -> "Silent pre-flight + cross-repo search";
"Silent pre-flight + cross-repo search" -> "Brief execution plan (5-15 lines)";
"Brief execution plan (5-15 lines)" -> "Hard blocker / ambiguous write?";
"Hard blocker / ambiguous write?" -> "Execute (body, comment, labels, Project)" [label="no"];
"Hard blocker / ambiguous write?" -> "Ask concise question / proposal" [label="yes"];
"Execute (body, comment, labels, Project)" -> "Brief result report";
}
In write mode, for each touched issue:
Status / Next + a dated entry in ## Updates.refs/SHA.In progress (see Project status sync).Authorization mode (from your CLAUDE.md config):
ask-each-time (default): after the plan, ask "execute?" before any GitHub write.execute-after-plan: after the silent pre-flight and brief execution plan, execute scoped GitHub writes without asking a separate confirmation. Still run pre-flight, search existing issues, preserve invariants, and report exactly what changed.Either mode: ask before writing when the write is genuinely ambiguous or risky — no suitable parent epic, uncertain repo/track ownership, public-repo privacy risk, destructive/bulk changes, closing an issue whose scope is not clearly completed, or conflicting evidence.
refs $YOUR_OWNER/<repo>#N (or closes $YOUR_OWNER/<repo>#N if the commit fully closes the scope). GitHub shows a backlink in the issue timeline — works cross-repo.## Updates lists short SHAs of commits from each touched repo this session: (code: <repo-A>@9a8ff92, <repo-B>@6fb5b26). An issue with no SHA when commits exist = incomplete sync.git log --oneline -10 per touched repo before writing the Updates entry.~/... local paths.When a session produces a PRD, plan, or diagram: the full content belongs in the issue body — context, decision table, mermaid diagrams (GitHub renders them inline), schema, MVP slices, out-of-scope, open questions — not a link to a file. A local md file can exist as a working copy; if they diverge, the issue body is the source of truth. Any file link from the body is a clickable GitHub URL pointing to an already-committed-and-pushed version, never a local path.
gh search issues for simple tracks, batched GraphQL for 3+ keys (see below).W18 but unrelated to the queried track). See "False-positive surface" below.projectItems with status lane, last activity. Prose is not evidence — use live reads.sub_issues_summary — note total, completed, percent_completed + epic's own state.When surfacing or correcting Project placement, batch up to ~20 issues in one GraphQL call via aliases:
gh api graphql -f query='
query {
i1: repository(owner:"$YOUR_OWNER", name:"<repo1>") { issue(number:<N1>) { ...IssueState } }
i2: repository(owner:"$YOUR_OWNER", name:"<repo2>") { issue(number:<N2>) { ...IssueState } }
}
fragment IssueState on Issue {
number title state url
labels(first:20){nodes{name}}
parent { number title repository { nameWithOwner } }
projectItems(first:10){nodes{ id project { number title } fieldValueByName(name:"Status"){ ... on ProjectV2ItemFieldSingleSelectValue { name } } }}
}' | jq -r '(.data // {}) | to_entries[] | .value.issue | select(. != null)
| "\(.number) \(.title) [\([(.labels.nodes // [])[].name]|join(","))] parent=\(.parent.repository.nameWithOwner // "—")#\(.parent.number // "") projects=\([(.projectItems.nodes // [])[] | "\(.project.title):\(.fieldValueByName.name // "empty")"]|join(" | "))"'
Notes: pipe into a separate jq, not the --jq flag (see the CRITICAL note in the batched-search section — when a GraphQL response carries errors, gh ignores --jq and dumps the raw body). parent is the Sub-issues API parent; projectItems[].fieldValueByName.name is the status lane. Result is at .data.<alias>.issue — don't forget the .issue level in jq. Single-issue fallback: gh issue view N -R $YOUR_OWNER/<repo> --json projectItems — .projectItems[].status is an object; read .status.name, not .status. List epic children: gh api repos/$YOUR_OWNER/<repo>/issues/<EPIC_N>/sub_issues --jq '.[] | {number, title, repository_url}'.
When verifying parent visibility: check both the child's parent_issue_url AND the parent's /sub_issues endpoint. If child-side parent reads as null but parent /sub_issues lists the child, report parent proof: parent sub_issues ✓; child API ambiguous — not parent missing. The two API surfaces can disagree transiently.
Build a Related context list only for true matches with a distinct scope: a separate task, contextual reference, dependency, cross-repo artifact, or historical task. Filter out parent/child links already expressed through the Sub-issues API — those are already visible in the hierarchy column. Do not duplicate API-expressed structure as Related prose.
For each artifact or query subject, search by multiple keys to avoid missing matches. For simple unambiguous tracks (single-keyword) one query is enough — escalate to multi-key only when first query returns 0 or 5+ matches:
gh search issues --owner $YOUR_OWNER <key> --json repository,number,title,labels,state,updatedAt
--state accepts only open or closed — the value all is invalid and errors out. To cover both states, just omit --state: one call returns open and closed together. Never run two queries (open + closed) — that wastes the scarce REST quota (30/min).
Keys to try (per artifact/subject):
"<person-name>", <handle>, <filename-slug><track-A>, <track-B>, <client><handle><opportunity-slug>Match acceptance criterion: issue title or body references the same person/company/track AND scope of work overlaps. If 2+ candidates match — pick the most specific one and link the others as Related.
When multi-key search needs 3 or more queries, batch them in a single GraphQL call to avoid the REST Search API rate limit (30 req/min):
gh api graphql -f query='
query {
s1: search(query:"user:$YOUR_OWNER is:issue <term1>", type:ISSUE, first:20){ nodes { ... on Issue { number title state url repository{nameWithOwner} labels(first:10){nodes{name}} updatedAt } } }
s2: search(query:"user:$YOUR_OWNER is:issue <term2>", type:ISSUE, first:20){ nodes { ... on Issue { number title state url repository{nameWithOwner} labels(first:10){nodes{name}} } } }
}' | jq -r '(.data // {}) | to_entries[] | .key as $k | (.value.nodes // [])[] | select(.number != null)
| "\($k) \(.state) \(.repository.nameWithOwner)#\(.number) \(.title) [\([(.labels.nodes // [])[].name]|join(","))]"'
Omit any state: qualifier — one alias returns both open and closed. Adding state:open silently drops closed issues (e.g. a closed epic), forcing a wasteful second batch.
CRITICAL — pipe into a separate jq, NOT the --jq flag. This is the #1 cause of recurring crashes in this skill:
errors array (one failed alias, or a totally broken query), gh api graphql ignores the --jq flag entirely: it dumps the raw JSON body to stdout plus a short error to stderr, exit 1. The (.data // {}) guard inside --jq never runs — the flag is bypassed.{"errors":[...]} with no data key. Any downstream d['data'] (Python) throws KeyError: 'data'.gh api graphql -f query='...' | jq -r '(.data // {}) | ...'. The raw body (with data, or errors-only) flows from stdout into jq, the (.data // {}) guard runs on the body: live aliases print, failed ones are skipped, a total failure yields empty output with no crash. The gh error stays visible on stderr for debugging.python3 -c "d['data']..."). If Python is unavoidable, use d.get('data', {}), never d['data'].Required jq guards (without them the expression crashes on cannot iterate over: null):
(.data // {}) first — on an errors-only body .data is null and to_entries[] crashes; // {} yields an empty object(.value.nodes // []) and (.labels.nodes // []) — an alias or field can return nullselect(.number != null) — drops empty {} objects from non-Issue nodesis:issue in each query string — excludes PRsPartial errors: with | jq, a failed alias is simply absent from the output and the live ones print. Don't blindly retry the whole batch — if needed, read .errors in a separate pass (... | jq '.errors'), then drop/fix the failed alias.
Why: gh search issues is the REST Search API, limited to 30 req/min — multi-key runs of 15-20 searches throttle. GraphQL search() counts against the 5000 points/hour GraphQL limit and batches efficiently. Owner qualifier in GraphQL is user:$YOUR_OWNER (not --owner).
Search by W-label or generic terms can return issues that share a label but aren't on this track. Example: <repo>#1 returned for <track-A> query because both have W18 label.
Rule: if a search match's title/body has no overlap with the queried track besides W-label or other generic label — it's a false positive. Drop it from results, surface in report under IGNORED (false positives) so user can confirm.
IGNORED (false positives):
- <repo>#1 — surfaced via W18 label match, but track = <unrelated track>
Don't silently filter — show what was filtered and why, in case user spots a real link manager missed.
Every issue (except the epic itself) must have exactly one parent via GitHub Sub-issues API. This is machine-readable track differentiation — replaces track-labels and stale markdown pointers in body.
N / M shown at top)<program>: <partner> — overview, <deal> — overview, <product> — delivery <version>)total >= 1, but a new root issue with a clear track scope may be proposed and used as an epic before its first sub-issue, provided the user confirms the scope (bootstrapping a new track).GitHub supports only one parent via Sub-issues API. This matches: «one track — one hierarchy». If a task seems to relate to two tracks — reconsider scope: probably split into two issues, OR it's an aggregate issue (see below).
Do not use markdown Parent: #N, Epic: #N, Belongs to: #N in body when parent is set via API — it's a duplicate that goes stale. Parent is stored via the Sub-issues API only.
Search for an existing epic for the track via cross-repo search; verify candidates have non-empty sub_issues_summary.total.
Canonical mapping by domain (configure per your business in Domain → repo routing):
| Track domain | Epic lives in |
|---|---|
| Educational program / partnership | teaching domain repo |
| B2B deal / multi-lane commercial track | crm / commercial repo |
| Product launch | the product's own repo |
| Research initiative | research repo |
If no epic exists — manager raises in proposal: «no epic for track X, do you want one?». Never creates an issue without a parent silently.
When creating a sub-issue via API:
CHILD_ID=$(gh api repos/OWNER/REPO/issues/CHILD_NUMBER --jq '.id')
gh api -X POST repos/EPIC_OWNER/EPIC_REPO/issues/EPIC_NUMBER/sub_issues -F sub_issue_id=$CHILD_ID
If an issue covers 2+ sibling tracks, scopes, or deliverable classes, classify it as an aggregate issue before assigning a parent. Do not attach it to the first matching subject epic if that would make another sibling scope invisible.
Signals of an aggregate issue:
product + sales, strategy + delivery, pilot + docs, launch + ops.Algorithm:
forecast, pipeline, portfolio, weekly review, operating review, coordination, umbrella.parent: aggregate.In read/write output, distinguish parent types explicitly:
parent: track — a specific subject-domain epicparent: aggregate — forecast / pipeline / portfolio / operating umbrellaparent: runtime — a published / runtime layer epicparent: unknown — parent not yet found or requires user decisionManager distinguishes three parent concepts:
parent_issue_url).Rules:
sub_issues_summary.total > 0) and is already visible in the Project, its backlog/strategy grandparent does not need to appear in the weekly Project. Report as parent: historical/backlog.parent_issue_url but the parent item is absent from the Project view, surface as: Project divergence: parent epic not visible.parent: null, verify by checking the likely epic's /sub_issues list. If the child is listed there, treat it as proof — but note parent proof: parent sub_issues (GitHub may read hierarchy asymmetrically).Project view limitation and do not reparent the child.Related / Related issues in the body is for contextual cross-links only. It never creates hierarchy.
parent_issue_url, sub_issues_summary).Forbidden inside a Related block: Parent: #N, Epic: #N, Belongs to: #N, manual child/subtask lists, status mirror, next-step mirror, W-labels, Project placement cues, checklist state.
Body format:
## Related
- <repo>#N «human-readable title» — brief reason for the link
**Verified:** YYYY-MM-DD by manager
Max 3 bullet links + one Verified line. On each body sync, rewrite from live search — do not append infinitely.
Before writing a body that contains a Related section — live-verify every referenced issue:
gh issue view N -R OWNER/REPO --json title,state,url,labels,updatedAt
gh api repos/OWNER/REPO/issues/N --jq '{parent: .parent_issue_url, sub_summary: .sub_issues_summary}'
For active / current-week related refs, also check projectItems.
If any ref is missing, closed, parentless, points to another track, has a stale W-label, or would leak private/CRM data into a public repo — surface Related links divergence and remove or report before writing the body. For public repos: do not copy private titles, CRM slugs, local paths, personal handles, payment facts, or private URLs — use a safe public label like private CRM issue — context only and keep the exact private pointer in a private repo/report.
Track-labels (<track-slug>, <client>-deal) are NOT created. Track differentiation goes through title + epic membership. Existing legacy track-labels are not deleted (they're history), but no new ones are created. Cross-repo navigation by track = epic's sub_issues + Project board, not label filter.
Before gh issue edit / gh issue create the agent MUST check the existing issue's parent and, for active issues, its Project placement:
gh api repos/OWNER/REPO/issues/N --jq '{parent: .parent_issue_url, sub_summary: .sub_issues_summary}'
Surface in proposal which issues are missing a parent and which epic is proposed for each.
In write mode, when the user actively worked on an issue in the current session, manager must set the Project status lane to In progress on all primary/child issues touched in:
Rules:
In progress on historical/backlog issues with no work in this session.Done or In review without explicit user signal.gh project item-add first, then gh project item-edit to set status.In progress.# Step 1 — get item id (from item-add output or item-list snapshot)
# Step 2 — get field and option ids (cache these in your config)
gh project field-list <PROJECT_NUMBER> --owner $YOUR_OWNER --format json
# Step 3 — set status lane
gh project item-edit \
--id <PVTI_id> \
--project-id <project-id> \
--field-id <status-field-id> \
--single-select-option-id <in-progress-option-id>
For each domain board, read its field list first — lane names may differ (In progress vs In Progress vs Active).
Project drift rules:
Project drift: status lane empty.Project drift: placement ok, status lane pending and do not claim full repair.(Skip this section if W-label convention is disabled in your config.)
date '+%V' if index is stale (>7 days).W{NN} (zero-padded only if existing labels in repo are zero-padded — check repo first).For issues whose title/body/calendar contain a specific event date (a session, live event, workshop, deadline), the W-label is computed from that event date, not the current sync date.
<person> S2 — prep and deliver session (29.05) on 2026-05-29 gets W22.DD.MM, take the year from body / calendar / current session context. If the year is ambiguous — surface Week ambiguity: date in <repo>#N is ambiguous; not writing GitHub until clarified and halt the label write.event week: W22, sync week: W23.Week drift: <repo>#N «title» — has <labels>, should have W{NN}; action: add W{NN}.These are two independent conditions — an issue can have one without the other:
Week drift: issue has W{NN}, expected W{MM} by date; action: add W{MM}Project drift: W{NN} present, missing from <Project>; action: add to Project after W-label fixList both in the Track health section of read mode output.
If a task requires action this week AND continues next week — apply BOTH W-labels. The index file is for "what's hot now"; labels record full lifecycle.
backlog (create label if missing).W{NN} for that week.backlog AND W{NN} together — pick one.Do not call gh label list first to check existence. Immediately try to add it; only on failure create the label and retry:
gh issue edit <N> -R "$REPO" --add-label "W{NN}" \
|| { gh label create "W{NN}" -R "$REPO" --color "0E8A16" --description "Week {NN}"; \
gh issue edit <N> -R "$REPO" --add-label "W{NN}"; }
# backlog label
gh label create "backlog" -R "$REPO" --color "ededed" --description "Deferred — not on current/next week"
Color 0E8A16 (dark green) for active weeks, ededed (neutral grey) for backlog. Old retro:W* and historical W* labels are never removed for tidiness.
All new issues created by manager follow a fixed formula — canonical operational layer for agents (predictable parsing, search, groupBy).
{object} — {action} ({when / context})
Visible domain prefixes (product:, content:, ops:, infra:, epic:, etc.) are NOT written in the title. Type metadata lives in labels, the parent tree, Projects, and body.
| Segment | What | Examples |
|---|---|---|
{object} | Recognizable subject first (noun) | <track-A>, <product> L3, <person-name> S2, <handle> |
{action} | Verb + short scope | prep for meeting <date>, process response + intake + slot |
{when / context} | Date/window/week in parens at end; optional | (by <date>), (<date> <time>) |
{object} is a noun first for search ease (groups all of that track's tasks).| Metadata | Location |
|---|---|
| Work type | labels (use your own taxonomy): type:<work-type>, area:<domain>, lifecycle labels |
| Track hierarchy | GitHub Sub-issues parent tree |
| Weekly visibility | W-label + Project placement |
| Owner / source | repo, Project, body fields |
| Root / epic status | sub-issues summary + body; no epic: prefix needed |
| Situation | Pattern | Example |
|---|---|---|
| Bot / funnel / runtime feature | <product or surface> — <fixed user behaviour> (<week/context>) | <bot handle> — duplicate request status without queue reset (W23) |
| Ops discovery / runbook | <system> — <operational capability or runbook> | <service> ops — one-off container commands via ssh (W23) |
If a title is only understandable knowing a screenshot, a person's name, or a debugging-session history — the title is too situational. Details go in ## What was done, Evidence, Updates; the title reflects the product outcome.
<track-A> — prep meeting <date> + budget by <date><person-name> (<handle>) — process response + intake + slot<product> L3 — final prep for live (<date> <time>)<channel> — post recap <month> YYYY🔥 <track>: KP — emoji + domain prefixprep meeting on <date> for <track> — verb-first instead of noun-firstops: all about <event> — domain prefix + too generic, no concrete actionpartner: <track> → divergence ⚠️ overdue — prefix, arrow instead of em-dash, emojiWhen manager creates an issue or reformats an existing one, the body MUST contain verifiable completion criteria.
Required minimum:
- [ ] — what is specifically in scope, each item observable as done / not done.Vague one-liners without specifics are FORBIDDEN. An issue body that cannot be verified against reality = malformed.
Implement rules from report X — unclear which rules, which file, and when it's done.- [ ] checklist of concrete items + done when rules are encoded in <skill>/SKILL.md § … + link to source report.If manager touches an existing vague issue (one-liner with no DoD), it adds the scope checklist + acceptance condition + source pointer in that same sync. Part of standard write sync under standing authorization.
Default: update issue body, NOT add a comment. Body = single source of truth, readable as one document. Comments stack chronologically and become unreadable after 5+ entries.
| Situation | Action |
|---|---|
| Existing issue covers same scope, same track | Edit body — refresh "Status:", "Next:", and dated line in ## Updates. Verify W-label is current AND parent epic attached AND Project placement present. |
| Existing issue scope is narrower but session expanded scope | Edit body of existing + create new follow-up issue with expanded scope, child of the same epic. |
| Multiple existing issues match different aspects (parent epic + sub-issue) | Edit body of the most specific child; touch epic only if its body needs updating. |
| No existing issue matches, but track is in tasks index | Create new issue. First resolve parent epic. W-label current, Project placement set. |
| No existing issue, no track in tasks index, fresh artifact | Ask user which repo + which epic before creating |
| Existing issue covers 2+ sibling scopes or belongs to a forecast/pipeline/operating review | Treat as aggregate issue. Find a repo-local aggregate epic first; if none, propose umbrella or split. |
| Existing child has a parent API link but appears flat in Project view | Do not reparent first. Verify parent Project placement + status lane; fix the parent Project item before changing hierarchy. |
| Found related issue with same subject but different scope | Keep as Related context only. Update/create a separate primary issue for the new standalone action. |
Use a comment ONLY when:
refs/SHA. This is the one case where a comment is required in addition to the body update (Iron invariant 6). One such comment per session per issue; don't stack — consolidate into body.Anti-pattern signal: if you're about to write the third progress comment on an issue — that's two too many. Edit body instead.
Manager owns the read-modify-write pattern: gh issue view → local body file → gh issue edit --body-file, with refreshed Status / Next / ## Updates. Use standard gh CLI directly.
Before writing any body to a public repo: replace private absolute paths, internal slugs, personal handles, payment facts, raw meeting/messaging URLs, and local-only evidence with safe public pointers. If no safe public pointer exists, record the private pointer only in the private/internal owner repo. Surface this as a blocker in the sync plan when applicable.
<one-line context: what changed/what was made>
**Source artifact:** <clickable GitHub URL — committed & pushed>
**Date:** <YYYY-MM-DD>
**Track:** <link to CRM card if commercial — see CRM integration>
## What was done
<2-4 bullets — actual progress>
## Scope
- [ ] <concrete item, observable as done/not-done>
- [ ] <concrete item>
## Next step
<one line — what closes this issue (acceptance condition)>
## Related
- <repo>#N «human-readable title» — brief reason for the link
**Verified:** <YYYY-MM-DD> by manager
---
Synced by manager from session <YYYY-MM-DD>.
Add the ## Related block only when there are verified context links — omit the whole block (don't leave it empty or with placeholder text) when there are none.
<one-line context: what changed/what was made — same as before, refresh if scope changed>
**Status:** <active / blocked / pending external / done — only "done" if user said so>
**Next:** <one-line next concrete step — refresh on each sync>
[... rest of original body content — REMOVE any old `## Related` block before re-writing ...]
## Related
- <repo>#N «human-readable title» — brief reason for the link
**Verified:** <YYYY-MM-DD> by manager
## Updates
- **YYYY-MM-DD:** <2-4 bullets of progress / decisions> (code: <repo>@<sha>)
- **YYYY-MM-DD:** <previous sync entry, kept>
Remove any stale ## Related block from the preserved original content, then insert at most one fresh verified block (omit when there are no real context links). Newest update at top of ## Updates, oldest at bottom (pick once per issue and stay consistent).
**YYYY-MM-DD:** <one-line note that doesn't fit body — e.g. "external blocker until X", "transient state observation">
Work-record comment (the required case) lists what was done this session + refs/SHA. Keep comments short. If a comment grows beyond 4 lines — it belongs in body.
Always use <repo>#N — «human title» form when surfacing an issue to the user. Bare <repo>#27 is unreadable — the user needs the title to recognize the track at a glance.
<repo>#27 → comment + W19<repo>#27 «<track-A> — respond to brief» → comment + W19Compact plan. Each line: what I'll do + where + why. Group by track. Under each track, first line — epic (parent issue), then sub-issues with parent: OK / parent: MISSING marker. Decisions / ambiguities — inline as questions.
Sync plan (W18, <date>):
<track-A> · epic crm#15 «<track-A> — B2B deal overview»
- crm#27 «<track-A> — respond to brief» [parent: crm#15 ✓; W18 ✓; Project ✓]
→ body update + work-record comment; status → In progress
- crm#25 «<track-A> — prep meeting <date>» [parent: crm#15 ✓; W18 ✓; Project ⚠️ missing]
→ comment "both meetings done"; add to Project; close if scope done, else keep open with reason
Labels to create: W19 in $YOUR_OWNER/crm
Epics missing coverage: (none)
Uncommitted in crm: meetings/<date>.md (new) — commit before sync?
Proceeding under standing authorization. If scope looks wrong — stop / correct.
If a track has no working epic, surface separately:
⚠️ Epic missing: track «<X>» has no parent epic. Options:
- Create crm#NEW «<X> — overview» as umbrella; all 3 issues below become sub-issues.
OR
- Use existing <repo>#N «<title>» (sub_summary total=N) — ask if ambiguous.
Done:
- crm#27 «<track-A> — respond to brief» — body update + work-record comment + W19; parent crm#15 ✓; Project → In progress
- crm#42 «<track-B> — scope expanded» — created, child crm#10, W18+W19, added to Project
- W19 label created in $YOUR_OWNER/crm
Skipped per your decision: crm#25 «<track-A> — prep…» — left open
Compact table — always with a «Title» column, parent epic in its own column, a Project column, no bare numbers:
Query: «what about <track-A>»
Epic of the track: crm#15 «<track-A> — B2B deal overview» (3/5 done)
Open sub-issues:
| Issue | Title | Parent | W-label | Project | Activity | Status |
|-------|-------|--------|---------|---------|----------|--------|
| crm#27 | <track-A> — respond to brief | crm#15 ✓ | W18 ✓ | ✓ | <date> | Active — KP due <date> |
| crm#25 | <track-A> — prep <date> | crm#15 ✓ | W18 ✓ | ✓ | <date> | Both meetings done — likely stale |
| presentations#9 | <track-A> — deck audit | — ⚠️ | W18 ✓ | ⚠️ | <date> | No parent + missing from Project, attach to crm#15 |
In tasks index:
- 🔥 W18 priority 4 — «KP for <track-A> by <date>»
Not covered (gap):
- No issue for writing the KP itself (only prep). Create on next sync?
Track health check:
- 1 issue without parent epic — presentations#9. Attach on next sync.
- 1 issue missing from Project — presentations#9.
Drift in index / epic state:
- (optional) crm#15 epic — 5/5 sub-issues done, state OPEN. Candidate to close.
Filtered (false positives):
- crm#1 «<unrelated> — complete» — surfaced via W18 label, unrelated to <track-A>
When a track has a multi-level epic hierarchy (root → lanes/children → grandchildren), output the read-mode report as a tree, not a flat list: each line carries parent status, W-label status, and Project placement. Active / current-week issues missing a Project slot are flagged Project mismatch.
weekly-planning skill.retro:W* labels appear): see weekly-retro skill — these labels carry historical signal, don't strip.| Mistake | Fix |
|---|---|
| Closing issues without explicit user instruction | Comment-and-leave-open is default. Only close if user said "close" or scope is fully delivered AND user mentioned completion. |
| Closing a delivery issue after local smoke only | A conducted live/session/workshop is not delivered until: recording/artifact ID or exact blocker recorded, the runtime/published artifact validated (not just locally), and the user-facing URL smoke check passes. Keep open with the exact open child otherwise. Don't infer enrollment/payment from view counts — verify against the authoritative source. |
Removing legacy labels (retro:W17, etc.) for "tidiness" | Don't strip labels you didn't add. Append new W-label, leave legacy. |
| Picking single W-label for cross-week task | If a task spans current + next week, apply both. |
| Creating new issue when a comment in an existing one fits | Search broadly first (multiple keys / batched GraphQL). Only create if scope clearly diverges. |
| Skipping W-label create when missing in repo | Iron invariant — create the label (fast-path), don't skip. |
| Creating/touching an issue without a parent epic | Iron invariant — surface in proposal, don't be silent. |
| Treating W-label as sufficient | Need W-label + parent + Project placement (lane not empty). |
| Treating the API parent as sufficient | Verify the visible root Project item and its status lane. |
| Attaching one issue to two epics | GitHub supports one parent. Split into two issues, or treat as aggregate. |
Markdown Parent: #N in body when API link exists | Duplicate, goes stale. Parent — only via Sub-issues API. |
| Missing SHA when commits exist | The ## Updates entry must list short SHAs of session commits per touched repo. |
Local ~/... path as a deliverable link | Commit + push first; the body/report link is a clickable GitHub URL. |
N×gh issue view one-by-one | Use batched GraphQL; single calls are a pointed fallback only. |
| Closing/syncing after real work without a work-record comment | Iron invariant 6 — add the timeline comment. |
| Not respecting the public-repo gate | Before writing to a public repo, do not add private / CRM / personal details; if unsure — surface in proposal and ask. |
| Treating tasks index as a task list to mutate | The tasks index is read-only context for manager. Index is curated by user / weekly-planning. |
| Acting on uncommitted local changes as "done" | Surface uncommitted work in the report; don't link a not-yet-pushed artifact. |
| Silently filtering false-positive matches | Drop from primary results but show in IGNORED (false positives). |
| Treating every invocation as write mode | Check first: artifacts (write) OR question about state (read)? Use the mode resolution diagram. |
| Asking the user to open a URL/issue/file manually | Never. Bring the content back via gh issue view <repo>/<N> --comments yourself. |
| Bare issue numbers | Always <repo>#N «title». Numbers are unrecognizable. |
Parent: #N / Epic: #N in body when parent is set via API — duplicate, goes stale.projectItems reads, not "should be on the board".state = open is a real signal; surface as candidate-to-close, never auto-close.<repo>#N «title».Activate by setting crm_path and crm_pointer_format in your CLAUDE.md config.
When CRM integration is enabled, every commercial / communication-related issue body must include a pointer to the corresponding CRM artifact (person card, opportunity card, meeting note). Default pointer format is [[<slug>]] (Obsidian wiki-link style), but you can configure any format your CRM uses.
For commercial / mentoring tracks, keep money out of the GitHub task body — price, package balance, paid/won status, and deal-close language live in CRM or the sales ledger. The issue body carries [[<slug>]] and a one-line pointer instead.
**Track:** [[<opportunity-slug>]]
Manager checks the CRM path exists when activated; if it doesn't — surfaces a one-line warning and proceeds without the pointer requirement.
Before any gh call: read the tasks index → otherwise it's shotgun search.
Every issue must carry: W-label + parent (Sub-issues API) + Project placement (non-empty status lane).
Write mode always: In progress on touched active issues; work-record comment on real work; commit↔issue SHA linkage; body is the channel, comment is the audit trail.
Title: {object} — {action} ({when}), no domain prefix; type metadata lives in labels/parent/Projects.
Standing auth: follow your configured mode — execute-after-plan runs scoped writes after the plan without a separate "confirm"; ask-each-time (default) shows the plan, then asks. Either way, ask on genuine ambiguity / hard blocker.
Output language follows the project. Technical tokens (<repo>#N, W18, commands) stay as-is.
npx claudepluginhub serejaris/personal-corp-skills --plugin personal-corp-skillsManages full GitHub issue lifecycle: create with conventional commit titles, sub-issues, cross-repo links, edit/view/list, dump trees to markdown/YAML, push from files, comment/label/close.
Bidirectional sync between BMAD planning markdown files (epics, stories, sprints) and GitHub Projects v2. Handles onboarding, push, pull, and status checks with explicit user approval.