Skip to content
cccskills
FreePublished on GitHub

story-issue-sync

Maintain bidirectional consistency between story markdown files and GitHub Issues. Use when syncing story status with GitHub.

The English summary is being prepared. Showing the original description.

Works withGitHub
See how to install

Files included (1)

  • SKILL.md5.8 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

SKILL: story-issue-sync

Purpose

Maintain bidirectional consistency between story files (docs/stories/*.md) and GitHub Issues. The file is authoritative for narrative and Gherkin. The issue is authoritative for labels, status, project board position, and DoD checklist state.

Ownership Model

AttributeAuthoritative sourceSync direction
TitleStory file (frontmatter title)File → Issue
Body / GherkinStory fileFile → Issue (on update)
issue_numberIssue (assigned at create)Issue → File
issue_urlIssueIssue → File
LabelsIssueIssue → File (labels frontmatter)
Status / PhaseIssue labelsIssue → File (labels frontmatter)
DoD checklistIssue body (task list)Issue → File (on review)
Project board positionProject v2Not persisted to file

Inputs / Outputs

FieldSourceNotes
story_filedocs/stories/*.mdPath to the story markdown file
issue_numberIssue (post-create)Written back to story frontmatter
issue_node_idgh issue create --json idPassed to gh-projects skill for board add
issue_urlIssue (post-create)Written back to story frontmatter

Create: File → Issue

When a new story file exists with issue_number: null:

STORY_FILE="docs/stories/user-auth-reset-password-20250416143000-0001.md"

# Extract frontmatter fields
TITLE=$(python3 -c "
import sys, re
text = open('$STORY_FILE').read()
m = re.search(r'^title:\s*[\"\'](.*?)[\"\']', text, re.MULTILINE)
print(m.group(1) if m else '')
")

LABELS=$(python3 -c "
import sys, re
text = open('$STORY_FILE').read()
m = re.findall(r'^\s+- [\"\'](.*?)[\"\']\s*$', text, re.MULTILINE)
print(','.join(m))
")

MILESTONE=$(python3 -c "
import sys, re
text = open('$STORY_FILE').read()
m = re.search(r'^milestone:\s*[\"\'](.*?)[\"\']', text, re.MULTILINE)
print(m.group(1) if m else '')
")

# Create the issue
RESULT=$(gh issue create \
  --title "$TITLE" \
  --body-file "$STORY_FILE" \
  --label "$LABELS" \
  --milestone "$MILESTONE" \
  --json number,url,id)

ISSUE_NUMBER=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin)['number'])")
ISSUE_URL=$(echo "$RESULT"    | python3 -c "import sys,json; print(json.load(sys.stdin)['url'])")

# Write back to story frontmatter
python3 - <<EOF
import re
text = open('$STORY_FILE').read()
text = re.sub(r'^issue_number: null', f'issue_number: $ISSUE_NUMBER', text, flags=re.MULTILINE)
text = re.sub(r'^issue_url: null', f'issue_url: "$ISSUE_URL"', text, flags=re.MULTILINE)
open('$STORY_FILE', 'w').write(text)
EOF

echo "[story-sync] CREATED  #$ISSUE_NUMBER  ← $STORY_FILE"

Update: File → Issue (body changed)

When story Gherkin or acceptance criteria are edited:

ISSUE_NUMBER=42
STORY_FILE="docs/stories/user-auth-reset-password-20250416143000-0001.md"

gh issue edit "$ISSUE_NUMBER" \
  --body-file "$STORY_FILE"

echo "[story-sync] BODY_UPDATE  #$ISSUE_NUMBER  ← $STORY_FILE"

Sync: Issue → File (labels changed)

When labels on the issue change (e.g., after PO updates priority or phase advances):

ISSUE_NUMBER=42
STORY_FILE="docs/stories/user-auth-reset-password-20250416143000-0001.md"

# Fetch current labels from issue
CURRENT_LABELS=$(gh issue view "$ISSUE_NUMBER" \
  --json labels \
  --jq '[.labels[].name] | join(",")')

# Rewrite labels block in frontmatter
python3 - <<EOF
import re
text = open('$STORY_FILE').read()
label_lines = '\n'.join(f'  - "{l}"' for l in "$CURRENT_LABELS".split(',') if l)
text = re.sub(
    r'^labels:\n(  - .*\n)+',
    f'labels:\n{label_lines}\n',
    text,
    flags=re.MULTILINE
)
open('$STORY_FILE', 'w').write(text)
EOF

echo "[story-sync] LABELS_SYNC  #$ISSUE_NUMBER  → $STORY_FILE"

Detect Drift (file vs issue)

Run before any update to check whether file and issue are in sync:

ISSUE_NUMBER=42
STORY_FILE="docs/stories/user-auth-reset-password-20250416143000-0001.md"

FILE_TITLE=$(python3 -c "
import re
m = re.search(r'^title:\s*[\"\'](.*?)[\"\']', open('$STORY_FILE').read(), re.MULTILINE)
print(m.group(1) if m else '')
")

ISSUE_TITLE=$(gh issue view "$ISSUE_NUMBER" --json title --jq '.title')

if [ "$FILE_TITLE" != "$ISSUE_TITLE" ]; then
  echo "[story-sync] DRIFT  title mismatch: file='$FILE_TITLE'  issue='$ISSUE_TITLE'"
  # File is authoritative for title — update the issue
  gh issue edit "$ISSUE_NUMBER" --title "$FILE_TITLE"
fi

Batch Sync (all stories)

find docs/stories -name "*.md" ! -name "_template.md" | while read -r f; do
  NUMBER=$(python3 -c "
import re
m = re.search(r'^issue_number:\s*(\d+)', open('$f').read(), re.MULTILINE)
print(m.group(1) if m else '')
")
  if [ -z "$NUMBER" ]; then
    echo "[story-sync] NEEDS_CREATE  $f"
  else
    echo "[story-sync] HAS_ISSUE    $f  → #$NUMBER"
  fi
done

Failure Modes

ConditionAction
Story has issue_number: nullCreate the issue. Never skip.
Issue number in file but issue is closedLog warning. Do not reopen automatically — escalate to human.
Title drift detectedFile wins. Update issue title.
Labels on issue unknown to label setLog: [story-sync] UNKNOWN_LABEL {name}. Do not remove.
Story file missing after issue existsLog warning. Issue is not deleted. Human decides.

Logging

[story-sync] CREATED       #42  ← docs/stories/user-auth-reset-0001.md
[story-sync] BODY_UPDATE   #42  ← docs/stories/user-auth-reset-0001.md
[story-sync] LABELS_SYNC   #42  → docs/stories/user-auth-reset-0001.md
[story-sync] DRIFT         #42  title mismatch — issue updated
[story-sync] SKIP          #42  no changes detected

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Run WCAG 2.2 Level AA accessibility audits against generated UI. Use when a story has ui:true or when asked to audit accessibility.

The English summary is being prepared. Showing the original description.

kinncj/Heimdall70Updated Sep 2, 2026

Generate a complete component file tree wired to design tokens with tests and Gherkin spec. Use when scaffolding a new UI component.

The English summary is being prepared. Showing the original description.

kinncj/Heimdall70Updated Sep 2, 2026

Extract Gherkin scenarios from story markdown files into runnable .feature files and generate step definition stubs. Use when syncing stories to test suites.

The English summary is being prepared. Showing the original description.

kinncj/Heimdall70Updated Sep 2, 2026

Read and write design tokens in W3C DTCG format (tokens.json) and emit CSS, Tailwind, and Mantine outputs. Use when modifying or generating design tokens.

The English summary is being prepared. Showing the original description.

kinncj/Heimdall70Updated Sep 2, 2026

Apply Docker and Docker Compose best practices for containerising services. Use when writing or reviewing Dockerfiles and compose configs.

The English summary is being prepared. Showing the original description.

kinncj/Heimdall70Updated Sep 2, 2026

Identify and annotate cloud cost implications of architectural decisions. Use when reviewing infrastructure changes or designing new services.

The English summary is being prepared. Showing the original description.

kinncj/Heimdall70Updated Sep 2, 2026

See all skills by kinncj

Report a problem with this skill