Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:24:52 +08:00
commit 737328c2bd
37 changed files with 6287 additions and 0 deletions

View File

@@ -0,0 +1,506 @@
---
name: github-task-sync
description: Manage task documentation by syncing between local task directories and GitHub issues
---
# GitHub Task Sync Skill
Seamlessly manage task documentation by syncing between local task directories and GitHub issues. All task documentation (SPEC, PLAN, TEST_PLAN, COMMIT_MESSAGE) lives both locally and on GitHub, with easy push/pull synchronization.
## Overview
This skill provides a complete workflow for managing tasks:
1. **Create** a new GitHub issue with `create-issue.sh`
2. **Push** local task files to GitHub with `push.sh` or `push-file.sh`
3. **Pull** task files from GitHub with `pull.sh` or `pull-file.sh`
4. **Read** task files from GitHub to stdout with `read-issue-file.sh`
5. **Log** work progress with `log-entry.sh` (creates AI Work Log comment with timestamped entries)
## Quick Start
```bash
# Create a new GitHub issue and task directory
./create-issue.sh "Add dark mode toggle" "Implement dark/light theme switcher"
# Work on files locally (SPEC.md, PLAN.md, etc.)
# Push all files to GitHub
./push.sh 188 ./tasks/188-add-dark-mode-toggle
# Or pull the latest from GitHub (automatically creates task directory from issue title)
./pull.sh 188
```
## Scripts
There are 7 scripts in this skill:
1. **create-issue.sh** - Create GitHub issue and initialize task directory
2. **push.sh** - Push all task files to GitHub
3. **push-file.sh** - Push single task file with status summary
4. **pull.sh** - Pull all task files from GitHub
5. **pull-file.sh** - Pull single task file from GitHub
6. **read-issue-file.sh** - Read task file from GitHub to stdout
7. **log-entry.sh** - Add timestamped entry to AI Work Log
### create-issue.sh
Create a new GitHub issue and initialize a task directory. Can also convert existing task directories to GitHub issues. Automatically applies GitHub labels based on issue context.
**Usage:**
```bash
./create-issue.sh <title> [description] [existing-task-dir] [labels]
```
**Arguments:**
- `title` - GitHub issue title
- `description` - Issue description (optional)
- `existing-task-dir` - Path to existing task directory to convert (optional)
- `labels` - Comma-separated labels to apply (optional, e.g., "UI,bug" or "CLI,feature")
**Available labels:**
- `UI` - User interface related issues
- `CLI` - Command-line interface related issues
- `bug` - Bug fixes and issue resolutions
- `feature` - New features and enhancements
**Examples:**
```bash
# Create new issue with title only
./create-issue.sh "Add dark mode toggle"
# Create new issue with title, description, and labels
./create-issue.sh "Add dark mode toggle" "Implement dark/light theme switcher in settings" "" "UI,feature"
# Convert existing task directory to GitHub issue with labels
./create-issue.sh "Fix login button styling" "" ./tasks/login-styling "UI,bug"
# Create issue with description and labels (no existing task dir)
./create-issue.sh "Add date filter to extract" "Filter commits by date range" "" "CLI,feature"
```
**What it does:**
1. Analyzes issue content to determine appropriate labels (optional)
2. Creates a new GitHub issue with the provided title, description, and labels
3. Creates local task directory named `{issue-number}-{title-slug}/`
4. If task files exist, automatically syncs them to GitHub
5. Outputs issue URL and task directory path
**Output:**
```
Creating GitHub issue...
Applying labels: UI,feature
✓ GitHub issue created: https://github.com/<github-user>/<repo-name>/issues/189
✓ Created task directory: tasks/189-add-dark-mode-toggle
✅ Task setup complete!
Issue: https://github.com/<github-user>/<repo-name>/issues/189
Task Directory: tasks/189-add-dark-mode-toggle
Task Number: 189
```
### push.sh
Push all task documentation files (SPEC.md, PLAN.md, TEST_PLAN.md, COMMIT_MESSAGE.md) to a GitHub issue as collapsible comments.
**Usage:**
```bash
./push.sh <issue-url-or-number> [task-directory]
```
**Arguments:**
- `issue-url-or-number` - Full GitHub URL or just the issue number
- `task-directory` - Directory containing task files (optional, defaults to current directory)
**Examples:**
```bash
# Using issue number
./push.sh 188 ./tasks/188-account-deletion
# Using full URL
./push.sh https://github.com/<github-user>/<repo-name>/issues/188 ./tasks/188-account-deletion
# Using current directory
./push.sh 188
```
**What it does:**
- Uploads all four task file types as separate collapsible comments
- Each file type gets a unique marker so it can be updated independently
- Creates new comments or updates existing ones
- Each file wrapped in `<details>` section that starts collapsed
**Output:**
```
📤 Syncing task files to GitHub issue #188 in <github-user>/<repo-name>
Processing SPEC.md...
+ Creating new comment...
✓ Created
Processing PLAN.md...
↻ Updating existing comment (ID: 123456789)...
✓ Updated
...
✅ Sync complete!
View the issue: https://github.com/<github-user>/<repo-name>/issues/188
```
### push-file.sh
Update a single task file comment on a GitHub issue with a status summary and file content.
**Usage:**
```bash
./push-file.sh <issue-url-or-number> <file-type> <status-file> <content-file>
```
**Arguments:**
- `issue-url-or-number` - GitHub issue URL or issue number
- `file-type` - One of: `SPEC`, `PLAN`, `TEST_PLAN`, `COMMIT_MESSAGE`
- `status-file` - File containing status summary (2 paragraphs + optional bullets)
- `content-file` - File containing the full file content
**Examples:**
```bash
# Update SPEC with status and content
./push-file.sh 188 SPEC SPEC-STATUS.md SPEC.md
# Update PLAN after review
./push-file.sh 188 PLAN plan-status.txt PLAN.md
```
**Status File Format:**
The status file should contain a 2-paragraph summary describing the document state:
```markdown
**Status:** [Draft | Complete | Review Needed | etc.]
This is the first paragraph explaining the current state of the document.
It should describe what has been completed, what's pending, or any key status information.
This is the second paragraph providing additional context or details about the document state.
- Key point 1 (optional)
- Key point 2 (optional)
```
**What it does:**
- Creates or updates a single comment for the specified file type
- Combines the status summary with the file content in a collapsible section
- Each file type has a unique marker for independent updates
**Output:**
```
↻ Updating SPEC comment on issue #188 (ID: 123456789)...
✓ Updated successfully
View the issue: https://github.com/<github-user>/<repo-name>/issues/188
```
### pull.sh
Pull all task documentation files from a GitHub issue to a local task directory. **Automatically determines the task directory name** from the issue title.
**Usage:**
```bash
./pull.sh <issue-url-or-number>
```
**Arguments:**
- `issue-url-or-number` - GitHub issue URL or issue number
**Examples:**
```bash
# Pull using issue number
./pull.sh 188
# Pull using full URL
./pull.sh https://github.com/<github-user>/<repo-name>/issues/188
```
**What it does:**
1. Fetches the issue title from GitHub
2. Converts the title to a URL-safe slug
3. Creates task directory as `tasks/{issue-number}-{title-slug}/`
4. Fetches all four task files from GitHub issue comments
5. Extracts content from collapsible sections
6. Writes each to local file (SPEC.md, PLAN.md, etc.)
**Output:**
```
📥 Fetching issue #188 from <github-user>/<repo-name>...
📥 Pulling task files from GitHub issue #188: "Account deletion and data export"
📁 Task directory: tasks/188-account-deletion-and-data-export
Pulling SPEC.md...
✓ Pulled to SPEC.md
Pulling PLAN.md...
✓ Pulled to PLAN.md
...
✅ Pull complete!
Task directory: tasks/188-account-deletion-and-data-export
```
### pull-file.sh
Pull a single task file from a GitHub issue to a local file.
**Usage:**
```bash
./pull-file.sh <issue-url-or-number> <file-type> [output-file]
```
**Arguments:**
- `issue-url-or-number` - GitHub issue URL or issue number
- `file-type` - One of: `SPEC`, `PLAN`, `TEST_PLAN`, `COMMIT_MESSAGE`
- `output-file` - File to write to (default: `{file-type}.md` in current directory)
**Examples:**
```bash
# Pull SPEC to SPEC.md
./pull-file.sh 188 SPEC
# Pull PLAN to specific file
./pull-file.sh 188 PLAN ./my-plan.md
# Pull and pipe to stdout
./pull-file.sh 188 SPEC | head -20
```
**Output:**
Pure file content (great for piping or redirecting)
### read-issue-file.sh
Read a task file from a GitHub issue and output to stdout. Useful for debugging, piping, or quick content inspection.
**Usage:**
```bash
./read-issue-file.sh <issue-url-or-number> <file-type>
```
**Arguments:**
- `issue-url-or-number` - GitHub issue URL or issue number
- `file-type` - One of: `SPEC`, `PLAN`, `TEST_PLAN`, `COMMIT_MESSAGE`
**Examples:**
```bash
# Read SPEC to stdout
./read-issue-file.sh 188 SPEC
# Pipe to file
./read-issue-file.sh 188 PLAN > my-plan.md
# View first 20 lines
./read-issue-file.sh 188 SPEC | head -20
```
**Output:**
Pure file content sent to stdout
### log-entry.sh
Add timestamped entries to a task's AI Work Log on a GitHub issue. Creates or updates a running log of work progress throughout the task lifecycle.
**Usage:**
```bash
./log-entry.sh <issue-url-or-number> <entry-text>
```
**Arguments:**
- `issue-url-or-number` - GitHub issue URL or issue number
- `entry-text` - Description of work being done (e.g., "Started writing spec")
**Examples:**
```bash
# Log that spec writing started
./log-entry.sh 188 "Started writing spec"
# Log that plan writing finished
./log-entry.sh 188 "Finished writing plan"
# Use full URL
./log-entry.sh https://github.com/<github-user>/<repo-name>/issues/190 "Started implementation"
```
**What it does:**
- Creates a new "AI Work Log" comment on the issue if it doesn't exist
- Appends timestamped entries to the work log (one per line with format: `- YYYY-MM-DD HH:MM:SS: entry-text`)
- Each entry is timestamped and represents a work milestone
- Useful for tracking progress through spec writing, planning, implementation, testing, and completion
**Output:**
```
↻ Adding entry to work log on issue #188...
✓ Entry added
View the issue: https://github.com/<github-user>/<repo-name>/issues/188
```
## Task Directory Structure
When using `create-issue.sh`, directories are automatically named with the issue number:
```
tasks/
├── 188-account-deletion/
│ ├── SPEC.md (Specification)
│ ├── PLAN.md (Implementation plan)
│ ├── TEST_PLAN.md (Test scenarios)
│ └── COMMIT_MESSAGE.md (Git commit message)
├── 189-add-dark-mode/
│ └── [similar structure]
└── archive/
└── [completed tasks]
```
**Naming Convention:** `{issue-number}-{task-name-slug}`
The issue number in the directory name provides direct reference to the GitHub issue.
## Workflow
### Creating a New Task
```bash
# 1. Create GitHub issue and task directory
./create-issue.sh "Add authentication" "Implement magic link authentication"
# 2. Log that work is starting
./log-entry.sh 190 "Started writing spec"
# 3. Work on files locally
# - Create SPEC.md
# - Create PLAN.md
# - Create TEST_PLAN.md
# - Create COMMIT_MESSAGE.md
# 4. Push files to GitHub
./push.sh 190 ./tasks/190-add-authentication
# 5. Log work progress
./log-entry.sh 190 "Finished writing spec"
./log-entry.sh 190 "Started writing plan"
# 6. Continue development...
# When you update files, push again
./push.sh 190 ./tasks/190-add-authentication
./log-entry.sh 190 "Finished writing plan"
./log-entry.sh 190 "Started implementation"
```
### Converting Existing Tasks to GitHub Issues
If you have an existing task directory without a GitHub issue:
```bash
# 1. Create GitHub issue from existing directory
./create-issue.sh "My feature" "Description" ./tasks/my-feature
# 2. Files are automatically synced to GitHub
# Task directory renamed to: tasks/191-my-feature
```
### Syncing During Work
**Push workflow** (local → GitHub):
```bash
# Log that you're starting work
./log-entry.sh 188 "Started writing code"
# Update single file on GitHub with status
./push-file.sh 188 SPEC SPEC-STATUS.md SPEC.md
# Update all files on GitHub
./push.sh 188 ./tasks/188-account-deletion
# Log when work is complete
./log-entry.sh 188 "Finished writing code"
```
**Pull workflow** (GitHub → local):
```bash
# Pull all files from GitHub
./pull.sh 188 ./tasks/188-account-deletion
# Pull single file from GitHub
./pull-file.sh 188 PLAN
# Log that you pulled latest
./log-entry.sh 188 "Pulled latest files from GitHub"
```
### Task Completion
When finishing a task (via `/finish` command):
1. All work is complete and tested
2. Run `push.sh` one final time to sync latest versions
3. Task directory is archived from `tasks/` to `tasks/archive/`
4. GitHub issue remains as permanent record
## Key Features
-**Bidirectional sync** - Push changes to GitHub or pull from GitHub
-**Selective updates** - Push/pull individual files or all at once
-**Status tracking** - Each file can have a 2-paragraph status summary
-**Collapsible display** - Large files stay organized on GitHub
-**AI Work Log** - Timestamped activity log tracking progress (spec writing, planning, implementation, etc.)
-**Issue creation** - Automatically initialize task structure
-**Directory conversion** - Convert existing tasks to GitHub issues
-**No git commits** - Task files never committed (in `.gitignore`)
-**GitHub-centric** - Documentation source of truth lives on GitHub
## Requirements
- `gh` CLI installed and authenticated
- Bash shell
- Read/write access to the GitHub repository
- Git repository with GitHub remote (for auto-detection)
## Integration with Other Commands
**With `/write-spec`:**
- Creates SPEC.md locally
- Agent calls `push-file.sh` to sync status + content to GitHub
**With `/write-plan`:**
- Creates PLAN.md locally
- Agent calls `push-file.sh` to sync to GitHub
**With `/finish`:**
- Calls `push.sh` to sync all files to GitHub as final step
- Task archived and GitHub issue contains complete documentation
## Setup & Configuration
The scripts **automatically detect** the GitHub repository from your current git remote (origin). No configuration needed!
**Repository Detection:**
1. **Auto-detect** (recommended): Scripts automatically extract owner/repo from `git remote get-url origin`
- Supports both HTTPS: `https://github.com/owner/repo.git`
- Supports SSH: `git@github.com:owner/repo.git`
2. **Environment variables** (optional override):
```bash
export GITHUB_OWNER="myorg"
export GITHUB_REPO="myrepo"
```
3. **Full URLs** (always works):
```bash
# Use full URL instead of issue number to override auto-detection
./push.sh "https://github.com/otherorg/otherrepo/issues/42" ./tasks/42-myfeature
```
**Error Handling:**
If you run scripts outside a git repository or without a GitHub remote, you'll see a helpful error:
```
Error: Not in a git repository
Please run this command from within a git repository, or set GITHUB_OWNER and GITHUB_REPO environment variables
```

View File

@@ -0,0 +1,141 @@
#!/bin/bash
set -euo pipefail
# Script to create a GitHub issue and initialize a task directory
# Can also convert an existing task directory to a GitHub issue
# Usage: ./create-issue.sh <title> [description] [existing-task-dir] [labels]
# If existing-task-dir is provided, converts that directory to a GitHub issue
# Labels should be comma-separated (e.g., "UI,bug" or "CLI,feature")
if [ $# -lt 1 ]; then
echo "Usage: $0 <title> [description] [existing-task-dir] [labels]"
echo ""
echo "Arguments:"
echo " title Issue title"
echo " description Issue description (optional)"
echo " existing-task-dir Path to existing task directory to convert (optional)"
echo " labels Comma-separated labels to apply (optional)"
echo ""
echo "Available labels: UI, CLI, bug, feature"
echo ""
echo "Examples:"
echo " $0 'Add dark mode toggle'"
echo " $0 'Add dark mode toggle' 'Implement dark/light theme switcher' '' 'UI,feature'"
echo " $0 'Fix authentication bug' '' ./tasks/existing-task 'bug'"
exit 1
fi
TITLE="$1"
DESCRIPTION="${2:-}"
EXISTING_TASK_DIR="${3:-}"
LABELS="${4:-}"
# Detect GitHub repository from git remote or environment
SCRIPT_DIR="$(dirname "$0")"
# shellcheck source=lib-repo-detect.sh
source "$SCRIPT_DIR/lib-repo-detect.sh"
if ! detect_github_repo; then
exit 1
fi
OWNER="$REPO_OWNER"
REPO="$REPO_NAME"
REPO_FULL="$OWNER/$REPO"
echo "Creating GitHub issue..."
[ -n "$LABELS" ] && echo "Applying labels: $LABELS"
# Build gh issue create command with optional labels
GH_ARGS=("issue" "create" "--repo" "$REPO_FULL" "--title" "$TITLE")
if [ -n "$DESCRIPTION" ]; then
GH_ARGS+=("--body" "$DESCRIPTION")
fi
if [ -n "$LABELS" ]; then
GH_ARGS+=("--label" "$LABELS")
fi
ISSUE_URL=$(gh "${GH_ARGS[@]}" 2>/dev/null)
if [ -z "$ISSUE_URL" ]; then
echo "Error: Failed to create GitHub issue"
exit 1
fi
echo "✓ GitHub issue created: $ISSUE_URL"
# Extract issue number from URL
if [[ $ISSUE_URL =~ /issues/([0-9]+)$ ]]; then
ISSUE_NUM="${BASH_REMATCH[1]}"
else
echo "Error: Could not extract issue number from URL: $ISSUE_URL"
exit 1
fi
# Determine task directory name
TASK_NAME=$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/-$//')
TASK_DIR="tasks/${ISSUE_NUM}-${TASK_NAME}"
# Handle existing task directory conversion
if [ -n "$EXISTING_TASK_DIR" ]; then
if [ ! -d "$EXISTING_TASK_DIR" ]; then
echo "Error: Existing task directory not found: $EXISTING_TASK_DIR"
exit 1
fi
echo "Converting existing task directory..."
# Check if task directory already exists with the new name
if [ -d "$TASK_DIR" ]; then
echo "Error: Task directory already exists: $TASK_DIR"
exit 1
fi
# Move existing directory to new location
mv "$EXISTING_TASK_DIR" "$TASK_DIR"
echo "✓ Renamed $EXISTING_TASK_DIR to $TASK_DIR"
else
# Create new task directory
mkdir -p "$TASK_DIR"
echo "✓ Created task directory: $TASK_DIR"
fi
# Check if task files exist and push them
echo ""
echo "Checking for task files to sync..."
HAS_FILES=0
if [ -f "$TASK_DIR/SPEC.md" ] || [ -f "$TASK_DIR/PLAN.md" ] || \
[ -f "$TASK_DIR/TEST_PLAN.md" ] || [ -f "$TASK_DIR/COMMIT_MESSAGE.md" ]; then
HAS_FILES=1
fi
if [ $HAS_FILES -eq 1 ]; then
echo "Syncing task files to GitHub issue..."
# Use the push.sh script to sync all files
PUSH_SCRIPT="$(dirname "$0")/push.sh"
if [ -x "$PUSH_SCRIPT" ]; then
"$PUSH_SCRIPT" "$ISSUE_NUM" "$TASK_DIR"
else
echo "Warning: Could not find push.sh script to sync files"
fi
else
echo "No task files found to sync (create SPEC.md, PLAN.md, etc. to sync them)"
fi
echo ""
echo "========================================="
echo "✅ Task setup complete!"
echo "========================================="
echo "Issue: $ISSUE_URL"
echo "Task Directory: $TASK_DIR"
echo "Task Number: $ISSUE_NUM"
echo ""
echo "Next steps:"
echo "1. Create SPEC.md, PLAN.md, TEST_PLAN.md, COMMIT_MESSAGE.md in $TASK_DIR"
echo "2. Run 'push.sh $ISSUE_NUM $TASK_DIR' to sync files to GitHub"
echo "3. Run '/write-spec' or other commands to begin work"

View File

@@ -0,0 +1,86 @@
#!/bin/bash
# Shared library for detecting GitHub repository from git remote
# Usage: source this file and call detect_github_repo
# Extracts owner and repo from a GitHub URL
# Supports both HTTPS and SSH formats:
# https://github.com/owner/repo.git
# git@github.com:owner/repo.git
extract_repo_from_url() {
local url="$1"
# Remove .git suffix if present
url="${url%.git}"
# Handle HTTPS format: https://github.com/owner/repo
if [[ $url =~ https://github\.com/([^/]+)/([^/]+) ]]; then
echo "${BASH_REMATCH[1]}/${BASH_REMATCH[2]}"
return 0
fi
# Handle SSH format: git@github.com:owner/repo
if [[ $url =~ git@github\.com:([^/]+)/(.+) ]]; then
echo "${BASH_REMATCH[1]}/${BASH_REMATCH[2]}"
return 0
fi
return 1
}
# Detects GitHub repository from environment or git remote
# Sets REPO_OWNER and REPO_NAME global variables
# Returns 0 on success, 1 on failure
detect_github_repo() {
# Method 1: Use environment variables if set
if [ -n "${GITHUB_OWNER:-}" ] && [ -n "${GITHUB_REPO:-}" ]; then
REPO_OWNER="$GITHUB_OWNER"
REPO_NAME="$GITHUB_REPO"
return 0
fi
# Method 2: Extract from git remote
if ! command -v git &> /dev/null; then
echo "Error: git command not found" >&2
return 1
fi
# Check if we're in a git repository
if ! git rev-parse --git-dir &> /dev/null; then
echo "Error: Not in a git repository" >&2
echo "Please run this command from within a git repository, or set GITHUB_OWNER and GITHUB_REPO environment variables" >&2
return 1
fi
# Get the origin remote URL
local remote_url
remote_url=$(git config --get remote.origin.url 2>/dev/null)
if [ -z "$remote_url" ]; then
echo "Error: No 'origin' remote found in git repository" >&2
echo "Please add a GitHub remote or set GITHUB_OWNER and GITHUB_REPO environment variables" >&2
return 1
fi
# Extract owner/repo from URL
local repo_full
repo_full=$(extract_repo_from_url "$remote_url")
if [ -z "$repo_full" ]; then
echo "Error: Could not extract GitHub repository from remote URL: $remote_url" >&2
echo "Please ensure the remote is a GitHub URL, or set GITHUB_OWNER and GITHUB_REPO environment variables" >&2
return 1
fi
# Split into owner and repo
REPO_OWNER="${repo_full%%/*}"
REPO_NAME="${repo_full#*/}"
# Validate we got both parts
if [ -z "$REPO_OWNER" ] || [ -z "$REPO_NAME" ]; then
echo "Error: Failed to parse repository owner and name from: $repo_full" >&2
return 1
fi
return 0
}

View File

@@ -0,0 +1,113 @@
#!/bin/bash
set -euo pipefail
# Script to add an entry to a task's work log on a GitHub issue
# Usage: ./log-entry.sh <issue-url-or-number> <entry-text>
# Creates or updates a WORK_LOG comment with timestamped entries
if [ $# -lt 2 ]; then
echo "Usage: $0 <issue-url-or-number> <entry-text>"
echo ""
echo "Arguments:"
echo " issue-url-or-number GitHub issue URL or issue number"
echo " entry-text Text describing the work (e.g., 'Started writing spec')"
echo ""
echo "Examples:"
echo " $0 188 'Started writing spec'"
echo " $0 190 'Finished writing plan'"
exit 1
fi
ISSUE_INPUT="$1"
ENTRY_TEXT="$2"
# Detect GitHub repository from git remote or environment
SCRIPT_DIR="$(dirname "$0")"
# shellcheck source=lib-repo-detect.sh
source "$SCRIPT_DIR/lib-repo-detect.sh"
# Normalize the issue URL/number
if [[ $ISSUE_INPUT =~ ^https?://github\.com/ ]]; then
ISSUE_URL="$ISSUE_INPUT"
else
# Need repo info for building URL from issue number
if ! detect_github_repo; then
exit 1
fi
ISSUE_URL="https://github.com/$REPO_OWNER/$REPO_NAME/issues/$ISSUE_INPUT"
fi
# Parse the URL to extract owner, repo, and issue number
if [[ $ISSUE_URL =~ github\.com/([^/]+)/([^/]+)/issues/([0-9]+) ]]; then
OWNER="${BASH_REMATCH[1]}"
REPO="${BASH_REMATCH[2]}"
ISSUE_NUM="${BASH_REMATCH[3]}"
else
echo "Error: Invalid GitHub issue URL"
echo "Expected format: https://github.com/owner/repo/issues/NUMBER"
exit 1
fi
REPO_FULL="$OWNER/$REPO"
MARKER="WORK_LOG_MARKER"
# Get current timestamp
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
# Check if WORK_LOG comment already exists
existing_comment_id=$(gh api repos/$REPO_FULL/issues/$ISSUE_NUM/comments \
--jq ".[] | select(.body | contains(\"<!-- ${MARKER} -->\")) | .id" 2>/dev/null || echo "")
if [ -n "$existing_comment_id" ]; then
# Get existing comment body
existing_body=$(gh api repos/$REPO_FULL/issues/$ISSUE_NUM/comments/$existing_comment_id \
--jq '.body' 2>/dev/null)
# Append new entry (insert before closing of last list item or at end)
# Format: - TIMESTAMP: entry-text
new_entry="- $TIMESTAMP: $ENTRY_TEXT"
# Insert new entry before the last closing detail tag if it exists, or just append
if [[ $existing_body == *"</details>"* ]]; then
new_body="${existing_body%</details>*}$new_entry
</details>"
else
new_body="$existing_body
$new_entry"
fi
# Update existing comment
echo "↻ Adding entry to work log on issue #$ISSUE_NUM..."
temp_body_file=$(mktemp)
echo "$new_body" > "$temp_body_file"
gh api repos/$REPO_FULL/issues/$ISSUE_NUM/comments/$existing_comment_id \
-X PATCH \
-F body=@"$temp_body_file" > /dev/null
rm "$temp_body_file"
echo "✓ Entry added"
else
# Create new WORK_LOG comment
echo "+ Creating work log on issue #$ISSUE_NUM..."
new_entry="- $TIMESTAMP: $ENTRY_TEXT"
body="<!-- ${MARKER} -->
## AI Work Log
$new_entry"
temp_body_file=$(mktemp)
echo "$body" > "$temp_body_file"
gh api repos/$REPO_FULL/issues/$ISSUE_NUM/comments \
-X POST \
-F body=@"$temp_body_file" > /dev/null
rm "$temp_body_file"
echo "✓ Work log created"
fi
echo ""
echo "View the issue: $ISSUE_URL"

View File

@@ -0,0 +1,98 @@
#!/bin/bash
set -euo pipefail
# Script to pull a single task file from a GitHub issue to local file
# Usage: ./pull-file.sh <issue-url-or-number> <file-type> [output-file]
# File types: SPEC, PLAN, TEST_PLAN, COMMIT_MESSAGE
if [ $# -lt 2 ]; then
echo "Usage: $0 <issue-url-or-number> <file-type> [output-file]"
echo ""
echo "Arguments:"
echo " issue-url-or-number GitHub issue URL or issue number"
echo " file-type One of: SPEC, PLAN, TEST_PLAN, COMMIT_MESSAGE"
echo " output-file File to write to (default: {file-type}.md in current directory)"
echo ""
echo "Examples:"
echo " $0 188 SPEC"
echo " $0 188 PLAN PLAN.md"
echo " $0 https://github.com/owner/repo/issues/188 TEST_PLAN ./test-plan.md"
exit 1
fi
ISSUE_INPUT="$1"
FILE_TYPE="$2"
OUTPUT_FILE="${3:-./${FILE_TYPE}.md}"
# Validate file type
case "$FILE_TYPE" in
SPEC|PLAN|TEST_PLAN|COMMIT_MESSAGE)
;;
*)
echo "Error: Invalid file type '$FILE_TYPE'"
echo "Valid types: SPEC, PLAN, TEST_PLAN, COMMIT_MESSAGE"
exit 1
;;
esac
# Detect GitHub repository from git remote or environment
SCRIPT_DIR="$(dirname "$0")"
# shellcheck source=lib-repo-detect.sh
source "$SCRIPT_DIR/lib-repo-detect.sh"
# Normalize the issue URL/number
if [[ $ISSUE_INPUT =~ ^https?://github\.com/ ]]; then
ISSUE_URL="$ISSUE_INPUT"
else
# Need repo info for building URL from issue number
if ! detect_github_repo; then
exit 1
fi
ISSUE_URL="https://github.com/$REPO_OWNER/$REPO_NAME/issues/$ISSUE_INPUT"
fi
# Parse the URL to extract owner, repo, and issue number
if [[ $ISSUE_URL =~ github\.com/([^/]+)/([^/]+)/issues/([0-9]+) ]]; then
OWNER="${BASH_REMATCH[1]}"
REPO="${BASH_REMATCH[2]}"
ISSUE_NUM="${BASH_REMATCH[3]}"
else
echo "Error: Invalid GitHub issue URL"
echo "Expected format: https://github.com/owner/repo/issues/NUMBER"
exit 1
fi
REPO_FULL="$OWNER/$REPO"
MARKER="${FILE_TYPE}_MARKER"
echo "Pulling $FILE_TYPE from issue #$ISSUE_NUM..."
# Fetch the comment with the matching marker and extract the content
# Use startswith to match the exact marker (not substrings like PLAN_MARKER vs TEST_PLAN_MARKER)
comment_body=$(gh api repos/$REPO_FULL/issues/$ISSUE_NUM/comments \
--jq ".[] | select(.body | startswith(\"<!-- $MARKER -->\")) | .body" 2>/dev/null || echo "")
if [ -z "$comment_body" ]; then
echo "Error: Could not find $FILE_TYPE comment on issue #$ISSUE_NUM" >&2
exit 1
fi
# Extract the content between the markdown code fences
# Handle both plain markdown blocks and those wrapped in <details> tags
if echo "$comment_body" | grep -q '<details>'; then
# For content in <details>, extract from ```markdown to </details>, then remove first line and last 2 lines
extracted=$(echo "$comment_body" | sed -n '/```markdown/,/<\/details>/p' | sed '1d' | sed '$d' | sed '$d')
else
# For unwrapped content, extract between ```markdown and the LAST ``` (to handle nested code blocks)
extracted=$(echo "$comment_body" | awk '/```markdown/{flag=1; next} /^```$/ && flag{exit} flag')
fi
if [ -z "$extracted" ]; then
echo "Error: Could not extract content from $FILE_TYPE comment" >&2
exit 1
fi
# Write to output file
echo "$extracted" > "$OUTPUT_FILE"
echo "✓ Pulled to $OUTPUT_FILE"

128
skills/github-task-sync/pull.sh Executable file
View File

@@ -0,0 +1,128 @@
#!/bin/bash
set -euo pipefail
# Script to pull task documentation files from a GitHub issue to local task directory
# Usage: ./pull.sh <issue-url-or-number>
# Pulls SPEC.md, PLAN.md, TEST_PLAN.md, COMMIT_MESSAGE.md from issue comments
# Automatically creates directory as tasks/{issue-number}-{title-slug}
if [ $# -lt 1 ]; then
echo "Usage: $0 <issue-url-or-number>"
echo ""
echo "Arguments:"
echo " issue-url-or-number GitHub issue URL or issue number"
echo ""
echo "Examples:"
echo " $0 188"
echo " $0 https://github.com/<github-user>/<repo-name>/issues/188"
echo ""
echo "The script will automatically create a directory named:"
echo " tasks/{issue-number}-{title-slug}"
exit 1
fi
ISSUE_INPUT="$1"
# Detect GitHub repository from git remote or environment
SCRIPT_DIR="$(dirname "$0")"
# shellcheck source=lib-repo-detect.sh
source "$SCRIPT_DIR/lib-repo-detect.sh"
# Normalize the issue URL/number
if [[ $ISSUE_INPUT =~ ^https?://github\.com/ ]]; then
ISSUE_URL="$ISSUE_INPUT"
else
# Need repo info for building URL from issue number
if ! detect_github_repo; then
exit 1
fi
ISSUE_URL="https://github.com/$REPO_OWNER/$REPO_NAME/issues/$ISSUE_INPUT"
fi
# Parse the URL to extract owner, repo, and issue number
if [[ $ISSUE_URL =~ github\.com/([^/]+)/([^/]+)/issues/([0-9]+) ]]; then
OWNER="${BASH_REMATCH[1]}"
REPO="${BASH_REMATCH[2]}"
ISSUE_NUM="${BASH_REMATCH[3]}"
else
echo "Error: Invalid GitHub issue URL"
echo "Expected format: https://github.com/owner/repo/issues/NUMBER"
exit 1
fi
REPO_FULL="$OWNER/$REPO"
# Fetch the issue title from GitHub
echo "📥 Fetching issue #$ISSUE_NUM from $REPO_FULL..."
ISSUE_TITLE=$(gh api repos/$REPO_FULL/issues/$ISSUE_NUM --jq '.title')
if [ -z "$ISSUE_TITLE" ]; then
echo "Error: Could not fetch issue title"
exit 1
fi
# Convert title to URL-safe slug
# - Convert to lowercase
# - Replace spaces and special characters with dashes
# - Remove consecutive dashes
# - Trim leading/trailing dashes
SLUG=$(echo "$ISSUE_TITLE" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//')
# Create task directory as tasks/{issue-number}-{slug}
TASK_DIR="tasks/$ISSUE_NUM-$SLUG"
mkdir -p "$TASK_DIR"
echo "📥 Pulling task files from GitHub issue #$ISSUE_NUM: \"$ISSUE_TITLE\""
echo "📁 Task directory: $TASK_DIR"
echo ""
# Function to pull a file from GitHub
pull_file() {
local file=$1
local marker=$2
local file_path="$TASK_DIR/$file"
echo "Pulling $file..."
# Fetch the comment with the matching marker and extract the content
# Use startswith to match the exact marker (not substrings like PLAN_MARKER vs TEST_PLAN_MARKER)
comment_body=$(gh api repos/$REPO_FULL/issues/$ISSUE_NUM/comments \
--jq ".[] | select(.body | startswith(\"<!-- $marker -->\")) | .body" 2>/dev/null || echo "")
if [ -z "$comment_body" ]; then
echo " ⏭️ Skipping $file (not found on issue)"
return
fi
# Extract the content between the markdown code fences
# Handle both plain markdown blocks and those wrapped in <details> tags
if echo "$comment_body" | grep -q '<details>'; then
# For content in <details>, extract from ```markdown to </details>, then remove first line and last 2 lines
extracted=$(echo "$comment_body" | sed -n '/```markdown/,/<\/details>/p' | sed '1d' | sed '$d' | sed '$d')
else
# For unwrapped content, extract between ```markdown and the LAST ``` (to handle nested code blocks)
# This is trickier - we need to find the matching closing fence
# For now, use a simple approach: extract from ```markdown to end, then find the last ``` and trim from there
extracted=$(echo "$comment_body" | awk '/```markdown/{flag=1; next} /^```$/ && flag{exit} flag')
fi
if [ -z "$extracted" ]; then
echo " ⚠️ Warning: Could not extract content from $file comment"
return
fi
# Write to file
echo "$extracted" > "$file_path"
echo " ✓ Pulled to $file"
}
# Pull all four files
pull_file "SPEC.md" "SPEC_MARKER"
pull_file "PLAN.md" "PLAN_MARKER"
pull_file "TEST_PLAN.md" "TEST_PLAN_MARKER"
pull_file "COMMIT_MESSAGE.md" "COMMIT_MESSAGE_MARKER"
echo ""
echo "✅ Pull complete!"
echo "Task directory: $TASK_DIR"

View File

@@ -0,0 +1,155 @@
#!/bin/bash
set -euo pipefail
# Script to update a single task file comment on a GitHub issue with status and content
# Usage: ./update-issue-file.sh <issue-url-or-number> <file-type> <status-file> <content-file>
# File types: SPEC, PLAN, TEST_PLAN, COMMIT_MESSAGE
# The status file should contain a 2-paragraph summary with optional bullet points
if [ $# -lt 4 ]; then
echo "Usage: $0 <issue-url-or-number> <file-type> <status-file> <content-file>"
echo ""
echo "Arguments:"
echo " issue-url-or-number GitHub issue URL or issue number"
echo " file-type One of: SPEC, PLAN, TEST_PLAN, COMMIT_MESSAGE"
echo " status-file File containing status summary (2 paragraphs + optional bullets)"
echo " content-file File containing the full content to display"
echo ""
echo "Examples:"
echo " $0 188 SPEC SPEC-STATUS.md SPEC.md"
echo " $0 https://github.com/owner/repo/issues/188 PLAN plan-status.txt PLAN.md"
exit 1
fi
ISSUE_INPUT="$1"
FILE_TYPE="$2"
STATUS_FILE="$3"
CONTENT_FILE="$4"
# Validate file type
case "$FILE_TYPE" in
SPEC|PLAN|TEST_PLAN|COMMIT_MESSAGE)
;;
*)
echo "Error: Invalid file type '$FILE_TYPE'"
echo "Valid types: SPEC, PLAN, TEST_PLAN, COMMIT_MESSAGE"
exit 1
;;
esac
# Validate files exist
if [ ! -f "$STATUS_FILE" ]; then
echo "Error: Status file not found: $STATUS_FILE"
exit 1
fi
if [ ! -f "$CONTENT_FILE" ]; then
echo "Error: Content file not found: $CONTENT_FILE"
exit 1
fi
# Detect GitHub repository from git remote or environment
SCRIPT_DIR="$(dirname "$0")"
# shellcheck source=lib-repo-detect.sh
source "$SCRIPT_DIR/lib-repo-detect.sh"
# Normalize the issue URL/number
if [[ $ISSUE_INPUT =~ ^https?://github\.com/ ]]; then
ISSUE_URL="$ISSUE_INPUT"
else
# Need repo info for building URL from issue number
if ! detect_github_repo; then
exit 1
fi
ISSUE_URL="https://github.com/$REPO_OWNER/$REPO_NAME/issues/$ISSUE_INPUT"
fi
# Parse the URL to extract owner, repo, and issue number
if [[ $ISSUE_URL =~ github\.com/([^/]+)/([^/]+)/issues/([0-9]+) ]]; then
OWNER="${BASH_REMATCH[1]}"
REPO="${BASH_REMATCH[2]}"
ISSUE_NUM="${BASH_REMATCH[3]}"
else
echo "Error: Invalid GitHub issue URL"
echo "Expected format: https://github.com/owner/repo/issues/NUMBER"
exit 1
fi
REPO_FULL="$OWNER/$REPO"
MARKER="${FILE_TYPE}_MARKER"
# Read status and content
status_content=$(cat "$STATUS_FILE")
file_content=$(cat "$CONTENT_FILE")
# Extract status value from first line (e.g., "**Status:** Complete")
status_line=$(echo "$status_content" | head -1)
status_value=$(echo "$status_line" | sed 's/.*\*\*Status:\*\* *//' | sed 's/\*\*//')
# Remove the first line (status line) from status_content to avoid duplication
remaining_content=$(echo "$status_content" | tail -n +2)
# Create the title from file type
case "$FILE_TYPE" in
SPEC)
title="Specification"
;;
PLAN)
title="Implementation Plan"
;;
TEST_PLAN)
title="Test Plan"
;;
COMMIT_MESSAGE)
title="Commit Message"
;;
esac
# Create body with marker, heading with status, and collapsible section
body="<!-- ${MARKER} -->
## $title: $status_value
$remaining_content
<details>
<summary>📋 $title</summary>
\`\`\`markdown
$file_content
\`\`\`
</details>"
# Check if comment with this marker already exists
# Use exact marker match including HTML comment tags to avoid matching TEST_PLAN_MARKER when looking for PLAN_MARKER
existing_comment_id=$(gh api repos/$REPO_FULL/issues/$ISSUE_NUM/comments \
--jq ".[] | select(.body | contains(\"<!-- ${MARKER} -->\")) | .id" 2>/dev/null || echo "")
if [ -n "$existing_comment_id" ]; then
# Update existing comment
echo "↻ Updating $FILE_TYPE comment on issue #$ISSUE_NUM (ID: $existing_comment_id)..."
# Use a temporary file to avoid shell escaping issues with large bodies
temp_body_file=$(mktemp)
echo "$body" > "$temp_body_file"
gh api repos/$REPO_FULL/issues/comments/$existing_comment_id \
-X PATCH \
-F body=@"$temp_body_file" > /dev/null
rm "$temp_body_file"
echo "✓ Updated successfully"
else
# Create new comment
echo "+ Creating new $FILE_TYPE comment on issue #$ISSUE_NUM..."
# Use a temporary file to avoid shell escaping issues with large bodies
temp_body_file=$(mktemp)
echo "$body" > "$temp_body_file"
gh api repos/$REPO_FULL/issues/$ISSUE_NUM/comments \
-X POST \
-F body=@"$temp_body_file" > /dev/null
rm "$temp_body_file"
echo "✓ Created successfully"
fi
echo ""
echo "View the issue: $ISSUE_URL"

181
skills/github-task-sync/push.sh Executable file
View File

@@ -0,0 +1,181 @@
#!/bin/bash
set -euo pipefail
# Script to sync task documentation (SPEC, PLAN, TEST_PLAN, COMMIT_MESSAGE) to a GitHub issue
# Usage: ./sync-to-github.sh <issue-url-or-number> [task-directory]
# Examples:
# ./sync-to-github.sh https://github.com/owner/repo/issues/188 /path/to/tasks/account-deletion
# ./sync-to-github.sh 188 /path/to/tasks/account-deletion
if [ $# -lt 1 ]; then
echo "Usage: $0 <issue-url-or-number> [task-directory]"
echo ""
echo "Arguments:"
echo " issue-url-or-number GitHub issue URL or issue number"
echo " task-directory Directory containing SPEC.md, PLAN.md, etc. (default: current directory)"
echo ""
echo "Examples:"
echo " $0 https://github.com/owner/repo/issues/188"
echo " $0 188 ./tasks/account-deletion"
exit 1
fi
ISSUE_INPUT="$1"
TASK_DIR="${2:-.}"
# Detect GitHub repository from git remote or environment
SCRIPT_DIR="$(dirname "$0")"
# shellcheck source=lib-repo-detect.sh
source "$SCRIPT_DIR/lib-repo-detect.sh"
# Normalize the issue URL/number
if [[ $ISSUE_INPUT =~ ^https?://github\.com/ ]]; then
ISSUE_URL="$ISSUE_INPUT"
else
# Need repo info for building URL from issue number
if ! detect_github_repo; then
exit 1
fi
ISSUE_URL="https://github.com/$REPO_OWNER/$REPO_NAME/issues/$ISSUE_INPUT"
fi
# Parse the URL to extract owner, repo, and issue number
# Expected format: https://github.com/owner/repo/issues/NUMBER
if [[ $ISSUE_URL =~ github\.com/([^/]+)/([^/]+)/issues/([0-9]+) ]]; then
OWNER="${BASH_REMATCH[1]}"
REPO="${BASH_REMATCH[2]}"
ISSUE_NUM="${BASH_REMATCH[3]}"
else
echo "Error: Invalid GitHub issue URL"
echo "Expected format: https://github.com/owner/repo/issues/NUMBER"
exit 1
fi
REPO_FULL="$OWNER/$REPO"
echo "📤 Syncing task files to GitHub issue #$ISSUE_NUM in $REPO_FULL"
echo ""
# Function to sync a file to GitHub
sync_file() {
local file=$1
local marker=$2
local title=$3
local file_path="$TASK_DIR/$file"
if [ ! -f "$file_path" ]; then
echo "⏭️ Skipping $file (not found)"
return
fi
echo "Processing $file..."
# Read file content
local content=$(cat "$file_path")
# Check if comment with this marker already exists
local existing_comment_id=$(gh api repos/$REPO_FULL/issues/$ISSUE_NUM/comments \
--jq ".[] | select(.body | contains(\"<!-- ${marker}_MARKER -->\")) | .id" 2>/dev/null || echo "")
local body
if [ -n "$existing_comment_id" ]; then
# Fetch existing comment body to preserve summary/status
echo " ↻ Fetching existing comment (ID: $existing_comment_id)..."
local existing_body=$(gh api repos/$REPO_FULL/issues/comments/$existing_comment_id --jq '.body' 2>/dev/null || echo "")
if [ -n "$existing_body" ]; then
# Check if there's a <details> tag in the existing body
if echo "$existing_body" | grep -q "<details>"; then
# Extract everything before the <details> tag (this is the summary/status section)
# Use sed to get everything up to but not including the <details> line
local summary_section=$(echo "$existing_body" | sed '/<details>/,$d')
else
# No <details> tag, use the whole body as summary (minus marker if present)
local summary_section=$(echo "$existing_body")
fi
# Check if there's a summary section (more than just the marker comment)
if echo "$summary_section" | grep -q -v "^<!-- ${marker}_MARKER -->$" && [ -n "$(echo "$summary_section" | sed '/^[[:space:]]*$/d' | tail -n +2)" ]; then
# Preserve the summary section and add updated content
body="$summary_section
<details>
<summary>📋 $title</summary>
\`\`\`markdown
$content
\`\`\`
</details>"
else
# No meaningful summary, use simple body
body="<!-- ${marker}_MARKER -->
<details>
<summary>📋 $title</summary>
\`\`\`markdown
$content
\`\`\`
</details>"
fi
else
# Couldn't fetch existing body, use simple body
body="<!-- ${marker}_MARKER -->
<details>
<summary>📋 $title</summary>
\`\`\`markdown
$content
\`\`\`
</details>"
fi
# Update existing comment
echo " ↻ Updating existing comment (preserving summary)..."
local temp_body_file=$(mktemp)
echo "$body" > "$temp_body_file"
gh api repos/$REPO_FULL/issues/comments/$existing_comment_id \
-X PATCH \
-F body=@"$temp_body_file" > /dev/null
rm "$temp_body_file"
echo " ✓ Updated"
else
# Create new comment with simple body (no existing summary to preserve)
body="<!-- ${marker}_MARKER -->
<details>
<summary>📋 $title</summary>
\`\`\`markdown
$content
\`\`\`
</details>"
echo " + Creating new comment..."
local temp_body_file=$(mktemp)
echo "$body" > "$temp_body_file"
gh api repos/$REPO_FULL/issues/$ISSUE_NUM/comments \
-X POST \
-F body=@"$temp_body_file" > /dev/null
rm "$temp_body_file"
echo " ✓ Created"
fi
echo ""
}
# Sync all four files
sync_file "SPEC.md" "SPEC" "Specification"
sync_file "PLAN.md" "PLAN" "Implementation Plan"
sync_file "TEST_PLAN.md" "TEST_PLAN" "Test Plan"
sync_file "COMMIT_MESSAGE.md" "COMMIT_MESSAGE" "Commit Message"
echo "✅ Sync complete!"
echo "View the issue: $ISSUE_URL"

View File

@@ -0,0 +1,93 @@
#!/bin/bash
set -euo pipefail
# Script to read task documentation files from a GitHub issue comment
# Usage: ./read-issue-file.sh <issue-url-or-number> <file-type>
# File types: SPEC, PLAN, TEST_PLAN, COMMIT_MESSAGE
# Output: File contents to stdout
if [ $# -lt 2 ]; then
echo "Usage: $0 <issue-url-or-number> <file-type>"
echo ""
echo "Arguments:"
echo " issue-url-or-number GitHub issue URL or issue number"
echo " file-type One of: SPEC, PLAN, TEST_PLAN, COMMIT_MESSAGE"
echo ""
echo "Examples:"
echo " $0 https://github.com/owner/repo/issues/188 SPEC"
echo " $0 188 PLAN"
exit 1
fi
ISSUE_INPUT="$1"
FILE_TYPE="$2"
# Validate file type
case "$FILE_TYPE" in
SPEC|PLAN|TEST_PLAN|COMMIT_MESSAGE)
;;
*)
echo "Error: Invalid file type '$FILE_TYPE'"
echo "Valid types: SPEC, PLAN, TEST_PLAN, COMMIT_MESSAGE"
exit 1
;;
esac
# Detect GitHub repository from git remote or environment
SCRIPT_DIR="$(dirname "$0")"
# shellcheck source=lib-repo-detect.sh
source "$SCRIPT_DIR/lib-repo-detect.sh"
# Normalize the issue URL/number
if [[ $ISSUE_INPUT =~ ^https?://github\.com/ ]]; then
ISSUE_URL="$ISSUE_INPUT"
else
# Need repo info for building URL from issue number
if ! detect_github_repo; then
exit 1
fi
ISSUE_URL="https://github.com/$REPO_OWNER/$REPO_NAME/issues/$ISSUE_INPUT"
fi
# Parse the URL to extract owner, repo, and issue number
if [[ $ISSUE_URL =~ github\.com/([^/]+)/([^/]+)/issues/([0-9]+) ]]; then
OWNER="${BASH_REMATCH[1]}"
REPO="${BASH_REMATCH[2]}"
ISSUE_NUM="${BASH_REMATCH[3]}"
else
echo "Error: Invalid GitHub issue URL"
echo "Expected format: https://github.com/owner/repo/issues/NUMBER"
exit 1
fi
REPO_FULL="$OWNER/$REPO"
MARKER="${FILE_TYPE}_MARKER"
# Fetch the comment with the matching marker and extract the content
# Use startswith to match the exact marker (not substrings like PLAN_MARKER vs TEST_PLAN_MARKER)
comment_body=$(gh api repos/$REPO_FULL/issues/$ISSUE_NUM/comments \
--jq ".[] | select(.body | startswith(\"<!-- $MARKER -->\")) | .body" 2>/dev/null || echo "")
if [ -z "$comment_body" ]; then
echo "Error: Could not find $FILE_TYPE comment on issue #$ISSUE_NUM" >&2
exit 1
fi
# Extract the content between the markdown code fences
# Handle both plain markdown blocks and those wrapped in <details> tags
if echo "$comment_body" | grep -q '<details>'; then
# For content in <details>, extract from ```markdown to </details>, then remove first line and last 2 lines
extracted=$(echo "$comment_body" | sed -n '/```markdown/,/<\/details>/p' | sed '1d' | sed '$d' | sed '$d')
else
# For unwrapped content, extract between ```markdown and the LAST ``` (to handle nested code blocks)
extracted=$(echo "$comment_body" | awk '/```markdown/{flag=1; next} /^```$/ && flag{exit} flag')
fi
if [ -z "$extracted" ]; then
echo "Error: Could not extract content from $FILE_TYPE comment" >&2
exit 1
fi
# Output to stdout
echo "$extracted"