Commit 959c045

bryfry <bryon@fryer.io>
2025-09-23 23:15:27
init
docs/config.md
@@ -0,0 +1,539 @@
+# tissue config
+
+Configure tissue settings for the project.
+
+## Tissue Commands
+```bash
+# Set local config
+tissue config editor.default "nvim"
+
+# Set global config
+tissue config --global editor.default "nvim"
+
+# List configuration
+tissue config --list
+tissue config --list --global
+```
+
+## Manual Workflow (bash + git)
+
+### Create configuration file
+```bash
+# Initialize tissue configuration
+init_tissue_config() {
+    CONFIG_DIR="${HOME}/.config/tissue"
+    LOCAL_CONFIG=".tissue/config"
+
+    # Create global config directory
+    mkdir -p "$CONFIG_DIR"
+
+    # Create global config file if not exists
+    if [ ! -f "$CONFIG_DIR/config" ]; then
+        cat > "$CONFIG_DIR/config" << 'EOF'
+# Tissue Global Configuration
+
+[user]
+name = $(git config user.name)
+email = $(git config user.email)
+
+[editor]
+default = ${EDITOR:-vim}
+
+[defaults]
+priority = medium
+status = open
+
+[format]
+date = %Y-%m-%dT%H:%M:%SZ
+issue_number = %03d
+
+[behavior]
+auto_commit = true
+auto_push = false
+confirm_destructive = true
+EOF
+        echo "Created global config: $CONFIG_DIR/config"
+    fi
+
+    # Create local config directory
+    mkdir -p "$(dirname "$LOCAL_CONFIG")"
+
+    # Create local config file
+    if [ ! -f "$LOCAL_CONFIG" ]; then
+        cat > "$LOCAL_CONFIG" << 'EOF'
+# Tissue Local Configuration
+# This overrides global settings for this repository
+
+[project]
+name = $(basename $(git rev-parse --show-toplevel))
+branch = issues
+
+[defaults]
+# priority = high
+# status = open
+
+[workflow]
+# auto_assign = true
+# require_priority = true
+EOF
+        echo "Created local config: $LOCAL_CONFIG"
+    fi
+}
+
+init_tissue_config
+```
+
+### Set configuration values
+```bash
+# Set a configuration value
+tissue_config_set() {
+    KEY="$1"
+    VALUE="$2"
+    GLOBAL="${3:-}"
+
+    if [ "$GLOBAL" = "--global" ]; then
+        CONFIG_FILE="${HOME}/.config/tissue/config"
+    else
+        CONFIG_FILE=".tissue/config"
+    fi
+
+    # Ensure config file exists
+    if [ ! -f "$CONFIG_FILE" ]; then
+        echo "Config file not found: $CONFIG_FILE"
+        echo "Run: tissue_config_init"
+        return 1
+    fi
+
+    # Parse key (section.key format)
+    SECTION=$(echo "$KEY" | cut -d'.' -f1)
+    KEY_NAME=$(echo "$KEY" | cut -d'.' -f2)
+
+    # Check if section exists
+    if ! grep -q "^\[$SECTION\]" "$CONFIG_FILE"; then
+        # Add new section
+        echo "" >> "$CONFIG_FILE"
+        echo "[$SECTION]" >> "$CONFIG_FILE"
+    fi
+
+    # Check if key exists in section
+    if grep -q "^$KEY_NAME = " "$CONFIG_FILE"; then
+        # Update existing key
+        sed -i.bak "/^\[$SECTION\]/,/^\[/{s/^$KEY_NAME = .*/$KEY_NAME = $VALUE/}" "$CONFIG_FILE"
+    else
+        # Add new key to section
+        awk -v section="[$SECTION]" -v key="$KEY_NAME = $VALUE" '
+            $0 == section {print; print key; next}
+            {print}
+        ' "$CONFIG_FILE" > "$CONFIG_FILE.tmp"
+        mv "$CONFIG_FILE.tmp" "$CONFIG_FILE"
+    fi
+
+    echo "Set $KEY = $VALUE in $CONFIG_FILE"
+}
+
+# Examples
+tissue_config_set "editor.default" "vim"
+tissue_config_set "defaults.priority" "high" --global
+```
+
+### Get configuration values
+```bash
+# Get a configuration value
+tissue_config_get() {
+    KEY="$1"
+    GLOBAL="${2:-}"
+
+    # Check both local and global configs
+    LOCAL_CONFIG=".tissue/config"
+    GLOBAL_CONFIG="${HOME}/.config/tissue/config"
+
+    # Parse key
+    SECTION=$(echo "$KEY" | cut -d'.' -f1)
+    KEY_NAME=$(echo "$KEY" | cut -d'.' -f2)
+
+    # Try local config first (unless --global specified)
+    if [ "$GLOBAL" != "--global" ] && [ -f "$LOCAL_CONFIG" ]; then
+        VALUE=$(awk -v section="[$SECTION]" -v key="$KEY_NAME" '
+            $0 == section {in_section=1; next}
+            /^\[/ {in_section=0}
+            in_section && $1 == key {print $3; exit}
+        ' "$LOCAL_CONFIG")
+
+        if [ -n "$VALUE" ]; then
+            echo "$VALUE"
+            return 0
+        fi
+    fi
+
+    # Fall back to global config
+    if [ -f "$GLOBAL_CONFIG" ]; then
+        VALUE=$(awk -v section="[$SECTION]" -v key="$KEY_NAME" '
+            $0 == section {in_section=1; next}
+            /^\[/ {in_section=0}
+            in_section && $1 == key {print $3; exit}
+        ' "$GLOBAL_CONFIG")
+
+        if [ -n "$VALUE" ]; then
+            echo "$VALUE"
+            return 0
+        fi
+    fi
+
+    # Return default or empty
+    return 1
+}
+
+# Examples
+EDITOR=$(tissue_config_get "editor.default")
+PRIORITY=$(tissue_config_get "defaults.priority")
+```
+
+### List all configuration
+```bash
+# List all configuration settings
+tissue_config_list() {
+    GLOBAL="${1:-}"
+
+    echo "Tissue Configuration"
+    echo "===================="
+    echo ""
+
+    if [ "$GLOBAL" = "--global" ]; then
+        CONFIG_FILE="${HOME}/.config/tissue/config"
+        echo "Global settings ($CONFIG_FILE):"
+    else
+        # Show both local and global
+        LOCAL_CONFIG=".tissue/config"
+        GLOBAL_CONFIG="${HOME}/.config/tissue/config"
+
+        if [ -f "$LOCAL_CONFIG" ]; then
+            echo "Local settings ($LOCAL_CONFIG):"
+            echo "--------------------------------"
+            grep -E "^[a-z]+ = " "$LOCAL_CONFIG" | while read line; do
+                echo "  $line"
+            done
+            echo ""
+        fi
+
+        if [ -f "$GLOBAL_CONFIG" ]; then
+            echo "Global settings ($GLOBAL_CONFIG):"
+            echo "---------------------------------"
+            CONFIG_FILE="$GLOBAL_CONFIG"
+        fi
+    fi
+
+    if [ -f "$CONFIG_FILE" ]; then
+        CURRENT_SECTION=""
+        while IFS= read -r line; do
+            # Check for section header
+            if [[ "$line" =~ ^\[.*\]$ ]]; then
+                CURRENT_SECTION=$(echo "$line" | tr -d '[]')
+                echo ""
+                echo "[$CURRENT_SECTION]"
+            elif [[ "$line" =~ ^[a-z_]+[[:space:]]*=[[:space:]]*.+ ]]; then
+                KEY=$(echo "$line" | cut -d'=' -f1 | tr -d ' ')
+                VALUE=$(echo "$line" | cut -d'=' -f2- | sed 's/^ *//')
+                echo "  ${CURRENT_SECTION}.${KEY} = ${VALUE}"
+            fi
+        done < "$CONFIG_FILE"
+    else
+        echo "No configuration file found"
+    fi
+}
+
+tissue_config_list
+```
+
+### Configuration with environment variables
+```bash
+# Load configuration with env var overrides
+load_tissue_config() {
+    # Set defaults
+    TISSUE_EDITOR="${TISSUE_EDITOR:-$(tissue_config_get editor.default || echo vim)}"
+    TISSUE_PRIORITY="${TISSUE_PRIORITY:-$(tissue_config_get defaults.priority || echo medium)}"
+    TISSUE_STATUS="${TISSUE_STATUS:-$(tissue_config_get defaults.status || echo open)}"
+    TISSUE_BRANCH="${TISSUE_BRANCH:-$(tissue_config_get project.branch || echo issues)}"
+    TISSUE_AUTO_COMMIT="${TISSUE_AUTO_COMMIT:-$(tissue_config_get behavior.auto_commit || echo true)}"
+    TISSUE_AUTO_PUSH="${TISSUE_AUTO_PUSH:-$(tissue_config_get behavior.auto_push || echo false)}"
+
+    # Export for use in scripts
+    export TISSUE_EDITOR
+    export TISSUE_PRIORITY
+    export TISSUE_STATUS
+    export TISSUE_BRANCH
+    export TISSUE_AUTO_COMMIT
+    export TISSUE_AUTO_PUSH
+}
+
+# Show current configuration
+show_tissue_env() {
+    load_tissue_config
+
+    echo "Current Tissue Environment:"
+    echo "=========================="
+    echo "TISSUE_EDITOR      = $TISSUE_EDITOR"
+    echo "TISSUE_PRIORITY    = $TISSUE_PRIORITY"
+    echo "TISSUE_STATUS      = $TISSUE_STATUS"
+    echo "TISSUE_BRANCH      = $TISSUE_BRANCH"
+    echo "TISSUE_AUTO_COMMIT = $TISSUE_AUTO_COMMIT"
+    echo "TISSUE_AUTO_PUSH   = $TISSUE_AUTO_PUSH"
+}
+
+show_tissue_env
+```
+
+### Templates configuration
+```bash
+# Configure issue templates
+configure_templates() {
+    TEMPLATE_DIR=".tissue/templates"
+    mkdir -p "$TEMPLATE_DIR"
+
+    # Create bug report template
+    cat > "$TEMPLATE_DIR/bug.md" << 'EOF'
+---
+title:
+status: open
+priority: high
+tags: [bug]
+created: {{DATE}}
+updated: {{DATE}}
+author: {{AUTHOR}}
+assignee:
+---
+
+# Bug Report: {{TITLE}}
+
+## Description
+Brief description of the bug
+
+## Steps to Reproduce
+1.
+2.
+3.
+
+## Expected Behavior
+
+
+## Actual Behavior
+
+
+## Environment
+- OS:
+- Version:
+
+## Additional Context
+
+EOF
+
+    # Create feature request template
+    cat > "$TEMPLATE_DIR/feature.md" << 'EOF'
+---
+title:
+status: open
+priority: medium
+tags: [enhancement]
+created: {{DATE}}
+updated: {{DATE}}
+author: {{AUTHOR}}
+assignee:
+---
+
+# Feature Request: {{TITLE}}
+
+## Problem Statement
+
+
+## Proposed Solution
+
+
+## Alternatives Considered
+
+
+## Acceptance Criteria
+- [ ]
+- [ ]
+
+## Additional Notes
+
+EOF
+
+    echo "Created templates in $TEMPLATE_DIR"
+}
+
+# Use template for new issue
+use_template() {
+    TEMPLATE_NAME="$1"
+    TEMPLATE_FILE=".tissue/templates/${TEMPLATE_NAME}.md"
+
+    if [ ! -f "$TEMPLATE_FILE" ]; then
+        echo "Template not found: $TEMPLATE_NAME"
+        echo "Available templates:"
+        ls -1 .tissue/templates/*.md 2>/dev/null | xargs -n1 basename | sed 's/\.md$//'
+        return 1
+    fi
+
+    # Process template
+    DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
+    AUTHOR=$(git config user.name)
+
+    # Create issue from template
+    cat "$TEMPLATE_FILE" | \
+        sed "s/{{DATE}}/$DATE/g" | \
+        sed "s/{{AUTHOR}}/$AUTHOR/g"
+}
+```
+
+### Hooks configuration
+```bash
+# Configure git hooks for tissue
+configure_hooks() {
+    HOOKS_DIR=".tissue/hooks"
+    GIT_HOOKS_DIR=".git/hooks"
+
+    mkdir -p "$HOOKS_DIR"
+
+    # Create post-commit hook
+    cat > "$HOOKS_DIR/post-commit" << 'EOF'
+#!/bin/bash
+# Tissue post-commit hook
+
+# Check for issue references in commit message
+COMMIT_MSG=$(git log -1 --pretty=%B)
+
+# Look for issue references (#NUM)
+echo "$COMMIT_MSG" | grep -o '#[0-9]\+' | while read -r ref; do
+    ISSUE_NUM=$(echo "$ref" | tr -d '#')
+    ISSUE_FILE=$(find ../issues -name "${ISSUE_NUM}_*.md" 2>/dev/null | head -1)
+
+    if [ -n "$ISSUE_FILE" ]; then
+        echo "Referenced issue $ref in commit"
+
+        # Check for closing keywords
+        if echo "$COMMIT_MSG" | grep -qiE "(close[sd]?|fix(es|ed)?|resolve[sd]?) +$ref"; then
+            echo "Auto-closing issue $ref"
+            # Update issue status to closed
+            sed -i 's/^status: .*/status: closed/' "$ISSUE_FILE"
+            cd ../issues && git add "$ISSUE_FILE" && git commit -m "Auto-close issue $ref" && cd -
+        fi
+    fi
+done
+EOF
+
+    chmod +x "$HOOKS_DIR/post-commit"
+
+    # Install hook
+    if [ ! -f "$GIT_HOOKS_DIR/post-commit" ]; then
+        ln -s "../../.tissue/hooks/post-commit" "$GIT_HOOKS_DIR/post-commit"
+        echo "Installed post-commit hook"
+    fi
+
+    # Create pre-push hook
+    cat > "$HOOKS_DIR/pre-push" << 'EOF'
+#!/bin/bash
+# Tissue pre-push hook
+
+# Sync issues branch before pushing main
+if [ "$(git branch --show-current)" = "main" ]; then
+    echo "Syncing issues branch..."
+    cd ../issues 2>/dev/null && git push && cd -
+fi
+EOF
+
+    chmod +x "$HOOKS_DIR/pre-push"
+
+    echo "Configured hooks in $HOOKS_DIR"
+}
+```
+
+### Validate configuration
+```bash
+# Validate tissue configuration
+validate_config() {
+    echo "Validating Tissue Configuration"
+    echo "================================"
+    echo ""
+
+    ERRORS=0
+    WARNINGS=0
+
+    # Check for config files
+    if [ -f ".tissue/config" ]; then
+        echo "✓ Local config found"
+    else
+        echo "⚠ No local config file"
+        ((WARNINGS++))
+    fi
+
+    if [ -f "${HOME}/.config/tissue/config" ]; then
+        echo "✓ Global config found"
+    else
+        echo "⚠ No global config file"
+        ((WARNINGS++))
+    fi
+
+    # Check required settings
+    load_tissue_config
+
+    if [ -z "$TISSUE_EDITOR" ]; then
+        echo "✗ No editor configured"
+        ((ERRORS++))
+    else
+        if ! command -v "$TISSUE_EDITOR" &> /dev/null; then
+            echo "✗ Configured editor not found: $TISSUE_EDITOR"
+            ((ERRORS++))
+        else
+            echo "✓ Editor configured: $TISSUE_EDITOR"
+        fi
+    fi
+
+    # Check branch configuration
+    if [ -z "$TISSUE_BRANCH" ]; then
+        echo "✗ No branch configured"
+        ((ERRORS++))
+    else
+        if git show-ref --verify --quiet "refs/heads/$TISSUE_BRANCH"; then
+            echo "✓ Issues branch exists: $TISSUE_BRANCH"
+        else
+            echo "✗ Configured branch does not exist: $TISSUE_BRANCH"
+            ((ERRORS++))
+        fi
+    fi
+
+    # Check templates
+    if [ -d ".tissue/templates" ]; then
+        TEMPLATE_COUNT=$(ls -1 .tissue/templates/*.md 2>/dev/null | wc -l)
+        echo "✓ Templates directory exists ($TEMPLATE_COUNT templates)"
+    else
+        echo "⚠ No templates directory"
+        ((WARNINGS++))
+    fi
+
+    # Summary
+    echo ""
+    echo "Validation Summary:"
+    echo "------------------"
+    if [ "$ERRORS" -eq 0 ] && [ "$WARNINGS" -eq 0 ]; then
+        echo "✓ Configuration valid"
+    else
+        [ "$ERRORS" -gt 0 ] && echo "✗ Errors: $ERRORS"
+        [ "$WARNINGS" -gt 0 ] && echo "⚠ Warnings: $WARNINGS"
+    fi
+
+    return "$ERRORS"
+}
+
+validate_config
+```
+
+## What tissue does for you:
+1. Manages local and global configuration files
+2. Provides cascading configuration (local overrides global)
+3. Supports environment variable overrides
+4. Manages issue templates
+5. Configures git hooks for automation
+6. Validates configuration integrity
+7. Provides sensible defaults
+8. Stores user preferences
+9. Enables workflow customization
+10. Maintains configuration versioning
\ No newline at end of file
docs/export.md
@@ -0,0 +1,584 @@
+# tissue export
+
+Export issues to various formats.
+
+## Tissue Command
+```bash
+# Export to JSON
+tissue export > issues.json
+```
+
+## Manual Workflow (bash + git)
+
+### Export to JSON
+```bash
+# Export all issues to JSON format
+export_to_json() {
+    OUTPUT_FILE="${1:-issues.json}"
+
+    cd ../issues 2>/dev/null || return 1
+
+    echo "Exporting issues to JSON: $OUTPUT_FILE"
+
+    # Start JSON array
+    echo "[" > "$OUTPUT_FILE"
+
+    FIRST=true
+    for file in *.md; do
+        [ "$file" = "README.md" ] && continue
+        [ -f "$file" ] || continue
+
+        # Add comma separator after first item
+        if [ "$FIRST" = true ]; then
+            FIRST=false
+        else
+            echo "," >> "$OUTPUT_FILE"
+        fi
+
+        # Extract frontmatter fields
+        ID=$(awk '/^id:/ {print $2}' "$file")
+        TITLE=$(awk '/^title:/ {$1=""; print substr($0,2)}' "$file" | sed 's/"/\\"/g')
+        STATUS=$(yq --front-matter '.status // ""' "$file")
+        PRIORITY=$(yq --front-matter '.priority // ""' "$file")
+        TAGS=$(awk '/^tags:/ {$1=""; print substr($0,2)}' "$file")
+        CREATED=$(awk '/^created:/ {print $2, $3}' "$file")
+        UPDATED=$(awk '/^updated:/ {print $2, $3}' "$file")
+        AUTHOR=$(awk '/^author:/ {$1=""; print substr($0,2)}' "$file" | sed 's/"/\\"/g')
+        ASSIGNEE=$(awk '/^assignee:/ {$1=""; print substr($0,2)}' "$file" | sed 's/"/\\"/g')
+
+        # Extract body (everything after second ---)
+        BODY=$(awk '/^---$/ {count++} count==2 {body=1; next} body' "$file" | \
+               sed 's/\\/\\\\/g; s/"/\\"/g; s/$/\\n/' | tr -d '\n' | sed 's/\\n$//')
+
+        # Write JSON object (without trailing comma)
+        cat >> "$OUTPUT_FILE" << EOF
+  {
+    "file": "${file}",
+    "id": "${ID}",
+    "title": "${TITLE}",
+    "status": "${STATUS}",
+    "priority": "${PRIORITY}",
+    "tags": ${TAGS:-[]},
+    "created": "${CREATED}",
+    "updated": "${UPDATED}",
+    "author": "${AUTHOR}",
+    "assignee": "${ASSIGNEE}",
+    "body": "${BODY}"
+  }
+EOF
+    done
+
+    # Close JSON array
+    echo "" >> "$OUTPUT_FILE"
+    echo "]" >> "$OUTPUT_FILE"
+
+    echo "Export complete: $OUTPUT_FILE"
+    cd - > /dev/null
+}
+
+export_to_json
+```
+
+### Export to CSV
+```bash
+# Export issues to CSV format
+export_to_csv() {
+    OUTPUT_FILE="${1:-issues.csv}"
+
+    cd ../issues 2>/dev/null || return 1
+
+    echo "Exporting issues to CSV: $OUTPUT_FILE"
+
+    # Write CSV header
+    echo '"ID","Title","Status","Priority","Tags","Assignee","Created","Updated","Description"' > "$OUTPUT_FILE"
+
+    # Process each issue
+    for file in *.md; do
+        [ "$file" = "README.md" ] && continue
+        [ -f "$file" ] || continue
+
+        # Extract fields
+        ID=$(awk '/^id:/ {print $2}' "$file")
+        TITLE=$(awk '/^title:/ {$1=""; print substr($0,2)}' "$file")
+        STATUS=$(yq --front-matter '.status // ""' "$file")
+        PRIORITY=$(yq --front-matter '.priority // ""' "$file")
+        TAGS=$(awk '/^tags:/ {$1=""; print substr($0,2)}' "$file" | tr -d '[]')
+        ASSIGNEE=$(awk '/^assignee:/ {print $2}' "$file")
+        CREATED=$(awk '/^created:/ {print $2}' "$file")
+        UPDATED=$(awk '/^updated:/ {print $2}' "$file")
+
+        # Extract first line of body as description
+        DESCRIPTION=$(awk '/^---$/ {count++} count==2 {body=1; next} body && NF {print; exit}' "$file")
+
+        # Escape quotes in fields
+        TITLE=$(echo "$TITLE" | sed 's/"/""/g')
+        DESCRIPTION=$(echo "$DESCRIPTION" | sed 's/"/""/g')
+
+        # Write CSV row
+        echo "\"${ID}\",\"${TITLE}\",\"${STATUS}\",\"${PRIORITY}\",\"${TAGS}\",\"${ASSIGNEE}\",\"${CREATED}\",\"${UPDATED}\",\"${DESCRIPTION}\"" >> "$OUTPUT_FILE"
+    done
+
+    echo "CSV export complete: $OUTPUT_FILE"
+    cd - > /dev/null
+}
+
+export_to_csv
+```
+
+### Export to Markdown summary
+```bash
+# Export all issues to single markdown file
+export_to_markdown() {
+    OUTPUT_FILE="${1:-all-issues.md}"
+
+    cd ../issues 2>/dev/null || return 1
+
+    echo "Exporting issues to Markdown: $OUTPUT_FILE"
+
+    cat > "$OUTPUT_FILE" << 'EOF'
+# All Issues Export
+
+Generated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
+
+---
+
+EOF
+
+    # Process each issue
+    for file in *.md; do
+        [ "$file" = "README.md" ] && continue
+        [ -f "$file" ] || continue
+
+        NUM=$(echo "$file" | cut -d'_' -f1)
+        TITLE=$(awk '/^title:/ {$1=""; print substr($0,2)}' "$file")
+        STATUS=$(yq --front-matter '.status // ""' "$file")
+        PRIORITY=$(yq --front-matter '.priority // ""' "$file")
+        TAGS=$(awk '/^tags:/ {$1=""; print substr($0,2)}' "$file")
+        ASSIGNEE=$(awk '/^assignee:/ {print $2}' "$file")
+        CREATED=$(awk '/^created:/ {print $2}' "$file")
+        UPDATED=$(awk '/^updated:/ {print $2}' "$file")
+
+        # Get full content
+        CONTENT=$(cat "$file")
+
+        cat >> "$OUTPUT_FILE" << EOF
+## Issue #${NUM}: ${TITLE}
+
+**Status:** ${STATUS}
+**Priority:** ${PRIORITY}
+**Tags:** ${TAGS}
+**Assignee:** ${ASSIGNEE:-unassigned}
+**Created:** ${CREATED}
+**Updated:** ${UPDATED}
+
+### Content
+
+\`\`\`markdown
+${CONTENT}
+\`\`\`
+
+---
+
+EOF
+    done
+
+    echo "Markdown export complete: $OUTPUT_FILE"
+    cd - > /dev/null
+}
+
+export_to_markdown
+```
+
+### Export to HTML
+```bash
+# Export issues to standalone HTML file
+export_to_html() {
+    OUTPUT_FILE="${1:-issues.html}"
+
+    cd ../issues 2>/dev/null || return 1
+
+    echo "Exporting issues to HTML: $OUTPUT_FILE"
+
+    cat > "$OUTPUT_FILE" << 'EOF'
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Issues Export</title>
+    <style>
+        body {
+            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+            max-width: 1200px;
+            margin: 0 auto;
+            padding: 20px;
+            background: #f5f5f5;
+        }
+        .header {
+            background: white;
+            padding: 20px;
+            border-radius: 8px;
+            margin-bottom: 20px;
+            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+        }
+        .issue {
+            background: white;
+            padding: 20px;
+            margin-bottom: 15px;
+            border-radius: 8px;
+            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+        }
+        .issue-header {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            margin-bottom: 15px;
+            padding-bottom: 10px;
+            border-bottom: 1px solid #e0e0e0;
+        }
+        .issue-number {
+            font-size: 14px;
+            color: #666;
+        }
+        .issue-title {
+            font-size: 18px;
+            font-weight: 600;
+            margin: 0;
+        }
+        .metadata {
+            display: flex;
+            gap: 15px;
+            font-size: 14px;
+            color: #666;
+            margin-bottom: 10px;
+        }
+        .metadata span {
+            padding: 2px 8px;
+            background: #f0f0f0;
+            border-radius: 4px;
+        }
+        .status-open { background: #d4edda !important; color: #155724 !important; }
+        .status-in-progress { background: #fff3cd !important; color: #856404 !important; }
+        .status-closed { background: #f8d7da !important; color: #721c24 !important; }
+        .priority-critical { background: #dc3545 !important; color: white !important; }
+        .priority-high { background: #fd7e14 !important; color: white !important; }
+        .priority-medium { background: #ffc107 !important; color: black !important; }
+        .priority-low { background: #28a745 !important; color: white !important; }
+        .body {
+            padding: 10px;
+            background: #f9f9f9;
+            border-radius: 4px;
+            white-space: pre-wrap;
+        }
+    </style>
+</head>
+<body>
+    <div class="header">
+        <h1>Issues Export</h1>
+        <p>Generated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")</p>
+    </div>
+EOF
+
+    # Process each issue
+    for file in *.md; do
+        [ "$file" = "README.md" ] && continue
+        [ -f "$file" ] || continue
+
+        NUM=$(echo "$file" | cut -d'_' -f1)
+        TITLE=$(awk '/^title:/ {$1=""; print substr($0,2)}' "$file")
+        STATUS=$(yq --front-matter '.status // ""' "$file")
+        PRIORITY=$(yq --front-matter '.priority // ""' "$file")
+        TAGS=$(awk '/^tags:/ {$1=""; print substr($0,2)}' "$file")
+        ASSIGNEE=$(awk '/^assignee:/ {print $2}' "$file")
+        CREATED=$(awk '/^created:/ {print $2}' "$file")
+        UPDATED=$(awk '/^updated:/ {print $2}' "$file")
+
+        # Extract body
+        BODY=$(awk '/^---$/ {count++} count==2 {body=1; next} body' "$file" | \
+               sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g')
+
+        cat >> "$OUTPUT_FILE" << EOF
+    <div class="issue">
+        <div class="issue-header">
+            <h2 class="issue-title">${TITLE}</h2>
+            <span class="issue-number">#${NUM}</span>
+        </div>
+        <div class="metadata">
+            <span class="status-${STATUS}">Status: ${STATUS}</span>
+            <span class="priority-${PRIORITY}">Priority: ${PRIORITY}</span>
+            <span>Assignee: ${ASSIGNEE:-unassigned}</span>
+            <span>Updated: ${UPDATED}</span>
+        </div>
+        <div class="body">${BODY}</div>
+    </div>
+EOF
+    done
+
+    echo "</body></html>" >> "$OUTPUT_FILE"
+
+    echo "HTML export complete: $OUTPUT_FILE"
+    cd - > /dev/null
+}
+
+export_to_html
+```
+
+### Export for GitHub import
+```bash
+# Export in format suitable for GitHub import
+export_for_github() {
+    OUTPUT_FILE="${1:-github-import.json}"
+
+    cd ../issues 2>/dev/null || return 1
+
+    echo "Exporting for GitHub import: $OUTPUT_FILE"
+
+    echo "[" > "$OUTPUT_FILE"
+
+    FIRST=true
+    for file in *.md; do
+        [ "$file" = "README.md" ] && continue
+        [ -f "$file" ] || continue
+
+        if [ "$FIRST" = true ]; then
+            FIRST=false
+        else
+            echo "," >> "$OUTPUT_FILE"
+        fi
+
+        # Extract fields
+        TITLE=$(awk '/^title:/ {$1=""; print substr($0,2)}' "$file" | sed 's/"/\\"/g')
+        STATUS=$(yq --front-matter '.status // ""' "$file")
+        PRIORITY=$(yq --front-matter '.priority // ""' "$file")
+        TAGS=$(awk '/^tags:/ {$1=""; print substr($0,2)}' "$file" | tr -d '[]')
+        ASSIGNEE=$(awk '/^assignee:/ {$1=""; print substr($0,2)}' "$file" | sed 's/"/\\"/g')
+
+        # Map status to GitHub state
+        case "$STATUS" in
+            closed) STATE="closed" ;;
+            *) STATE="open" ;;
+        esac
+
+        # Extract body
+        BODY=$(awk '/^---$/ {count++} count==2 {body=1; next} body' "$file" | \
+               sed 's/\\/\\\\/g; s/"/\\"/g; s/$/\\n/' | tr -d '\n' | sed 's/\\n$//')
+
+        # Create GitHub-compatible issue object
+        cat >> "$OUTPUT_FILE" << EOF
+  {
+    "title": "${TITLE}",
+    "body": "${BODY}\\n\\n---\\nPriority: ${PRIORITY}\\nOriginal Status: ${STATUS}",
+    "state": "${STATE}",
+    "labels": [$(echo "$TAGS" | sed 's/,/","/g' | sed 's/^/"/; s/$/"/')]
+  }
+EOF
+    done
+
+    echo "" >> "$OUTPUT_FILE"
+    echo "]" >> "$OUTPUT_FILE"
+
+    echo "GitHub export complete: $OUTPUT_FILE"
+    echo "Use GitHub API or gh CLI to import:"
+    echo "  gh issue create --title \"\$title\" --body \"\$body\""
+    cd - > /dev/null
+}
+
+export_for_github
+```
+
+### Export filtered issues
+```bash
+# Export only specific issues based on criteria
+export_filtered() {
+    FILTER_STATUS="${1:-open}"
+    FILTER_PRIORITY="${2:-all}"
+    OUTPUT_FILE="${3:-filtered-export.json}"
+
+    cd ../issues 2>/dev/null || return 1
+
+    echo "Exporting filtered issues (status=$FILTER_STATUS, priority=$FILTER_PRIORITY)"
+
+    echo "[" > "$OUTPUT_FILE"
+
+    FIRST=true
+    for file in *.md; do
+        [ "$file" = "README.md" ] && continue
+        [ -f "$file" ] || continue
+
+        STATUS=$(yq --front-matter '.status // ""' "$file")
+        PRIORITY=$(yq --front-matter '.priority // ""' "$file")
+
+        # Apply filters
+        if [ "$FILTER_STATUS" != "all" ] && [ "$STATUS" != "$FILTER_STATUS" ]; then
+            continue
+        fi
+
+        if [ "$FILTER_PRIORITY" != "all" ] && [ "$PRIORITY" != "$FILTER_PRIORITY" ]; then
+            continue
+        fi
+
+        # Export matching issue
+        if [ "$FIRST" = true ]; then
+            FIRST=false
+        else
+            echo "," >> "$OUTPUT_FILE"
+        fi
+
+        # Extract and format issue data
+        ID=$(awk '/^id:/ {print $2}' "$file")
+        TITLE=$(awk '/^title:/ {$1=""; print substr($0,2)}' "$file" | sed 's/"/\\"/g')
+
+        cat >> "$OUTPUT_FILE" << EOF
+  {
+    "id": "${ID}",
+    "title": "${TITLE}",
+    "status": "${STATUS}",
+    "priority": "${PRIORITY}",
+    "file": "${file}"
+  }
+EOF
+    done
+
+    echo "" >> "$OUTPUT_FILE"
+    echo "]" >> "$OUTPUT_FILE"
+
+    echo "Filtered export complete: $OUTPUT_FILE"
+    cd - > /dev/null
+}
+
+export_filtered "open" "high"
+```
+
+### Export with attachments
+```bash
+# Export issues with any related files
+export_with_attachments() {
+    OUTPUT_DIR="${1:-export-$(date +%Y%m%d-%H%M%S)}"
+
+    cd ../issues 2>/dev/null || return 1
+
+    echo "Exporting issues with attachments to: $OUTPUT_DIR"
+
+    # Create export directory
+    mkdir -p "$OUTPUT_DIR"
+    mkdir -p "$OUTPUT_DIR/issues"
+    mkdir -p "$OUTPUT_DIR/attachments"
+
+    # Copy all issue files
+    cp *.md "$OUTPUT_DIR/issues/" 2>/dev/null
+
+    # Export metadata
+    export_to_json "$OUTPUT_DIR/issues.json"
+
+    # Look for referenced attachments in issues
+    for file in *.md; do
+        [ -f "$file" ] || continue
+
+        # Find image references
+        grep -o '!\[.*\](.*)' "$file" | sed 's/.*(\(.*\))/\1/' | while read -r attachment; do
+            if [ -f "$attachment" ]; then
+                cp "$attachment" "$OUTPUT_DIR/attachments/"
+                echo "Copied attachment: $attachment"
+            fi
+        done
+    done
+
+    # Create export manifest
+    cat > "$OUTPUT_DIR/manifest.txt" << EOF
+Tissue Export Manifest
+======================
+Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
+Issues: $(ls -1 "$OUTPUT_DIR/issues/"*.md | wc -l)
+Attachments: $(ls -1 "$OUTPUT_DIR/attachments/" 2>/dev/null | wc -l)
+Format Version: 1.0
+EOF
+
+    # Create ZIP archive
+    if command -v zip &> /dev/null; then
+        cd ..
+        zip -r "${OUTPUT_DIR}.zip" "$(basename "$OUTPUT_DIR")"
+        echo "Created archive: ${OUTPUT_DIR}.zip"
+    fi
+
+    echo "Export complete: $OUTPUT_DIR"
+    cd - > /dev/null
+}
+
+export_with_attachments
+```
+
+### Export statistics
+```bash
+# Export issue statistics and metrics
+export_statistics() {
+    OUTPUT_FILE="${1:-statistics.json}"
+
+    cd ../issues 2>/dev/null || return 1
+
+    echo "Exporting statistics to: $OUTPUT_FILE"
+
+    # Calculate statistics
+    TOTAL=$(ls -1 *.md 2>/dev/null | grep -v README | wc -l)
+    OPEN=$(grep -l "^status: open" *.md 2>/dev/null | wc -l)
+    IN_PROGRESS=$(grep -l "^status: in-progress" *.md 2>/dev/null | wc -l)
+    CLOSED=$(grep -l "^status: closed" *.md 2>/dev/null | wc -l)
+
+    CRITICAL=$(grep -l "^priority: critical" *.md 2>/dev/null | wc -l)
+    HIGH=$(grep -l "^priority: high" *.md 2>/dev/null | wc -l)
+    MEDIUM=$(grep -l "^priority: medium" *.md 2>/dev/null | wc -l)
+    LOW=$(grep -l "^priority: low" *.md 2>/dev/null | wc -l)
+
+    # Get date range
+    OLDEST=$(ls -t *.md 2>/dev/null | tail -1)
+    NEWEST=$(ls -t *.md 2>/dev/null | head -1)
+
+    if [ -n "$OLDEST" ]; then
+        OLDEST_DATE=$(awk '/^created:/ {print $2}' "$OLDEST")
+    fi
+
+    if [ -n "$NEWEST" ]; then
+        NEWEST_DATE=$(awk '/^created:/ {print $2}' "$NEWEST")
+    fi
+
+    # Generate statistics JSON
+    cat > "$OUTPUT_FILE" << EOF
+{
+  "generated": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
+  "total_issues": $TOTAL,
+  "by_status": {
+    "open": $OPEN,
+    "in_progress": $IN_PROGRESS,
+    "closed": $CLOSED
+  },
+  "by_priority": {
+    "critical": $CRITICAL,
+    "high": $HIGH,
+    "medium": $MEDIUM,
+    "low": $LOW
+  },
+  "date_range": {
+    "oldest": "${OLDEST_DATE:-null}",
+    "newest": "${NEWEST_DATE:-null}"
+  },
+  "completion_rate": $(echo "scale=2; $CLOSED*100/$TOTAL" | bc)
+}
+EOF
+
+    echo "Statistics export complete: $OUTPUT_FILE"
+    cd - > /dev/null
+}
+
+export_statistics
+```
+
+## What tissue does for you:
+1. Exports to multiple formats (JSON, CSV, HTML, Markdown)
+2. Handles special characters and escaping
+3. Provides filtered exports
+4. Creates GitHub-compatible exports
+5. Includes attachments and related files
+6. Generates statistics and metrics
+7. Creates portable archives
+8. Maintains data integrity
+9. Provides format conversion
+10. Supports bulk operations
\ No newline at end of file
docs/import.md
@@ -0,0 +1,551 @@
+# tissue import
+
+Import issues from external sources.
+
+## Tissue Commands
+```bash
+# Import from GitHub
+tissue import github --repo user/repo
+tissue import github --repo user/repo --token $GITHUB_TOKEN
+
+# Import from JSON
+tissue import issues.json
+```
+
+## Manual Workflow (bash + git)
+
+### Import from GitHub Issues
+```bash
+# Import GitHub issues using GitHub CLI or API
+import_from_github() {
+    REPO="$1"
+    TOKEN="${2:-$GITHUB_TOKEN}"
+
+    cd ../issues 2>/dev/null || return 1
+
+    echo "Importing issues from GitHub: $REPO"
+
+    # Using GitHub CLI (if installed)
+    if command -v gh &> /dev/null; then
+        # List all issues and export as JSON
+        gh issue list --repo "$REPO" --limit 1000 --json number,title,state,body,labels,assignees,createdAt,updatedAt > github_issues.json
+
+        # Process each issue
+        jq -c '.[]' github_issues.json | while read -r issue; do
+            NUMBER=$(echo "$issue" | jq -r '.number')
+            TITLE=$(echo "$issue" | jq -r '.title')
+            STATE=$(echo "$issue" | jq -r '.state')
+            BODY=$(echo "$issue" | jq -r '.body // ""')
+            CREATED=$(echo "$issue" | jq -r '.createdAt')
+            UPDATED=$(echo "$issue" | jq -r '.updatedAt')
+
+            # Map GitHub state to tissue status
+            case "$STATE" in
+                open) STATUS="open" ;;
+                closed) STATUS="closed" ;;
+                *) STATUS="open" ;;
+            esac
+
+            # Extract labels
+            LABELS=$(echo "$issue" | jq -r '.labels[].name' | tr '\n' ',' | sed 's/,$//')
+
+            # Extract assignees
+            ASSIGNEES=$(echo "$issue" | jq -r '.assignees[].login' | tr '\n' ',' | sed 's/,$//')
+
+            # Create issue file with empty frontmatter
+            PADDED_NUM=$(printf "%03d" "$NUMBER")
+            SAFE_TITLE=$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | \
+                         tr ' ' '-' | tr -cd '[:alnum:]-' | cut -c1-50)
+            FILENAME="${PADDED_NUM}_${SAFE_TITLE}.md"
+
+            # Create minimal file
+            echo -e "---\n---\n\n# ${TITLE}\n\n${BODY}\n\n---\n*Imported from GitHub: ${REPO}#${NUMBER}*" > "$FILENAME"
+
+            # Build frontmatter using yq
+            yq --front-matter '.id = "github-'"$NUMBER"'"' -i "$FILENAME"
+            yq --front-matter '.title = "'"$TITLE"'"' -i "$FILENAME"
+            yq --front-matter '.status = "'"$STATUS"'"' -i "$FILENAME"
+            yq --front-matter '.priority = "medium"' -i "$FILENAME"
+
+            # Convert labels to array
+            if [ -n "$LABELS" ]; then
+                LABELS_JSON=$(echo "$LABELS" | jq -R 'split(",") | map(ltrimstr(" ") | rtrimstr(" "))')
+                yq --front-matter ".tags = $LABELS_JSON" -i "$FILENAME"
+            else
+                yq --front-matter '.tags = []' -i "$FILENAME"
+            fi
+
+            yq --front-matter '.created = "'"$CREATED"'"' -i "$FILENAME"
+            yq --front-matter '.updated = "'"$UPDATED"'"' -i "$FILENAME"
+            yq --front-matter '.author = "imported"' -i "$FILENAME"
+            yq --front-matter '.assignee = "'"${ASSIGNEES:-unassigned}"'"' -i "$FILENAME"
+            yq --front-matter '.source = "github:'"$REPO"'#'"$NUMBER"'"' -i "$FILENAME"
+
+            echo "Imported issue #${NUMBER}: ${TITLE}"
+        done
+
+        rm github_issues.json
+    else
+        # Using curl and GitHub API
+        echo "Using GitHub API directly..."
+
+        # Fetch issues via API
+        curl -s \
+            ${TOKEN:+-H "Authorization: token $TOKEN"} \
+            "https://api.github.com/repos/${REPO}/issues?state=all&per_page=100" \
+            > github_issues.json
+
+        # Process with jq (same as above)
+        # ... (similar processing logic)
+    fi
+
+    # Commit imported issues
+    git add -A
+    git commit -m "Import issues from GitHub: $REPO"
+
+    echo "Import complete!"
+    cd - > /dev/null
+}
+
+import_from_github "user/repo"
+```
+
+### Import from JSON
+```bash
+# Import issues from JSON file
+import_from_json() {
+    JSON_FILE="$1"
+
+    if [ ! -f "$JSON_FILE" ]; then
+        echo "File not found: $JSON_FILE"
+        return 1
+    fi
+
+    cd ../issues 2>/dev/null || return 1
+
+    echo "Importing issues from: $JSON_FILE"
+
+    # Process each issue in JSON
+    jq -c '.[]' "$JSON_FILE" | while read -r issue; do
+        # Extract fields (with defaults)
+        TITLE=$(echo "$issue" | jq -r '.title // "Untitled Issue"')
+        STATUS=$(echo "$issue" | jq -r '.status // "open"')
+        PRIORITY=$(echo "$issue" | jq -r '.priority // "medium"')
+        DESCRIPTION=$(echo "$issue" | jq -r '.description // .body // ""')
+        TAGS=$(echo "$issue" | jq -r '.tags // [] | join(",")')
+        ASSIGNEE=$(echo "$issue" | jq -r '.assignee // ""')
+        CREATED=$(echo "$issue" | jq -r '.created // "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"')
+        UPDATED=$(echo "$issue" | jq -r '.updated // "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"')
+
+        # Generate issue number
+        ISSUE_NUM=$(($(ls -1 *.md 2>/dev/null | wc -l) + 1))
+        PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+        SAFE_TITLE=$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | \
+                     tr ' ' '-' | tr -cd '[:alnum:]-' | cut -c1-50)
+        FILENAME="${PADDED_NUM}_${SAFE_TITLE}.md"
+
+        # Create minimal file
+        echo -e "---\n---\n\n# ${TITLE}\n\n${DESCRIPTION}" > "$FILENAME"
+
+        # Build frontmatter using yq
+        IMPORT_ID="import-$(date +%s%N | sha256sum | cut -c1-8)"
+        yq --front-matter '
+            .id = "'"$IMPORT_ID"'" |
+            .title = "'"$TITLE"'" |
+            .status = "'"$STATUS"'" |
+            .priority = "'"$PRIORITY"'" |
+            .created = "'"$CREATED"'" |
+            .updated = "'"$UPDATED"'" |
+            .author = "imported"
+        ' -i "$FILENAME"
+
+        # Handle tags
+        if [ -n "$TAGS" ]; then
+            TAGS_JSON=$(echo "$TAGS" | jq -R 'split(",") | map(ltrimstr(" ") | rtrimstr(" "))')
+            yq --front-matter ".tags = $TAGS_JSON" -i "$FILENAME"
+        else
+            yq --front-matter '.tags = []' -i "$FILENAME"
+        fi
+
+        # Handle assignee
+        if [ -n "$ASSIGNEE" ]; then
+            yq --front-matter '.assignee = "'"$ASSIGNEE"'"' -i "$FILENAME"
+        else
+            yq --front-matter '.assignee = null' -i "$FILENAME"
+        fi
+
+        echo "Imported: ${FILENAME}"
+    done
+
+    git add -A
+    git commit -m "Import issues from JSON: $(basename "$JSON_FILE")"
+
+    echo "Import complete!"
+    cd - > /dev/null
+}
+
+import_from_json "issues.json"
+```
+
+### Import from CSV
+```bash
+# Import issues from CSV file
+import_from_csv() {
+    CSV_FILE="$1"
+
+    if [ ! -f "$CSV_FILE" ]; then
+        echo "File not found: $CSV_FILE"
+        return 1
+    fi
+
+    cd ../issues 2>/dev/null || return 1
+
+    echo "Importing issues from CSV: $CSV_FILE"
+
+    # Skip header and process each line
+    tail -n +2 "$CSV_FILE" | while IFS=',' read -r title status priority tags assignee description; do
+        # Clean up fields (remove quotes)
+        title=$(echo "$title" | tr -d '"')
+        status=$(echo "$status" | tr -d '"')
+        priority=$(echo "$priority" | tr -d '"')
+        tags=$(echo "$tags" | tr -d '"')
+        assignee=$(echo "$assignee" | tr -d '"')
+        description=$(echo "$description" | tr -d '"')
+
+        # Generate issue number
+        ISSUE_NUM=$(($(ls -1 *.md 2>/dev/null | wc -l) + 1))
+        PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+        SAFE_TITLE=$(echo "$title" | tr '[:upper:]' '[:lower:]' | \
+                     tr ' ' '-' | tr -cd '[:alnum:]-' | cut -c1-50)
+        FILENAME="${PADDED_NUM}_${SAFE_TITLE}.md"
+
+        # Create minimal file
+        echo -e "---\n---\n\n# ${title}\n\n${description}" > "$FILENAME"
+
+        # Build frontmatter using yq
+        CSV_ID="csv-$(date +%s%N | sha256sum | cut -c1-8)"
+        yq --front-matter '
+            .id = "'"$CSV_ID"'" |
+            .title = "'"$title"'" |
+            .status = "'"${status:-open}"'" |
+            .priority = "'"${priority:-medium}"'" |
+            .created = now |
+            .updated = now |
+            .author = "imported"
+        ' -i "$FILENAME"
+
+        # Handle tags
+        if [ -n "$tags" ]; then
+            TAGS_JSON=$(echo "$tags" | jq -R 'split(",") | map(ltrimstr(" ") | rtrimstr(" "))')
+            yq --front-matter ".tags = $TAGS_JSON" -i "$FILENAME"
+        else
+            yq --front-matter '.tags = []' -i "$FILENAME"
+        fi
+
+        # Handle assignee
+        if [ -n "$assignee" ]; then
+            yq --front-matter '.assignee = "'"$assignee"'"' -i "$FILENAME"
+        else
+            yq --front-matter '.assignee = null' -i "$FILENAME"
+        fi
+
+        echo "Imported: ${FILENAME}"
+    done
+
+    git add -A
+    git commit -m "Import issues from CSV: $(basename "$CSV_FILE")"
+
+    echo "CSV import complete!"
+    cd - > /dev/null
+}
+
+# Example CSV format:
+# title,status,priority,tags,assignee,description
+# "Bug in login","open","high","bug,auth","alice","Users cannot log in"
+
+import_from_csv "issues.csv"
+```
+
+### Import from GitLab
+```bash
+# Import issues from GitLab project
+import_from_gitlab() {
+    PROJECT_ID="$1"
+    GITLAB_URL="${2:-https://gitlab.com}"
+    TOKEN="${3:-$GITLAB_TOKEN}"
+
+    cd ../issues 2>/dev/null || return 1
+
+    echo "Importing issues from GitLab project: $PROJECT_ID"
+
+    # Fetch issues from GitLab API
+    curl -s \
+        ${TOKEN:+-H "PRIVATE-TOKEN: $TOKEN"} \
+        "${GITLAB_URL}/api/v4/projects/${PROJECT_ID}/issues?per_page=100" \
+        > gitlab_issues.json
+
+    # Process each issue
+    jq -c '.[]' gitlab_issues.json | while read -r issue; do
+        IID=$(echo "$issue" | jq -r '.iid')
+        TITLE=$(echo "$issue" | jq -r '.title')
+        STATE=$(echo "$issue" | jq -r '.state')
+        DESCRIPTION=$(echo "$issue" | jq -r '.description // ""')
+        CREATED=$(echo "$issue" | jq -r '.created_at')
+        UPDATED=$(echo "$issue" | jq -r '.updated_at')
+        LABELS=$(echo "$issue" | jq -r '.labels[]' | tr '\n' ',' | sed 's/,$//')
+        ASSIGNEE=$(echo "$issue" | jq -r '.assignee.username // ""')
+
+        # Map GitLab state to tissue status
+        case "$STATE" in
+            opened) STATUS="open" ;;
+            closed) STATUS="closed" ;;
+            *) STATUS="open" ;;
+        esac
+
+        # Create issue file
+        PADDED_NUM=$(printf "%03d" "$IID")
+        SAFE_TITLE=$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | \
+                     tr ' ' '-' | tr -cd '[:alnum:]-' | cut -c1-50)
+        FILENAME="${PADDED_NUM}_${SAFE_TITLE}.md"
+
+        # Create minimal file
+        echo -e "---\n---\n\n# ${TITLE}\n\n${DESCRIPTION}\n\n---\n*Imported from GitLab: Project ${PROJECT_ID} Issue #${IID}*" > "$FILENAME"
+
+        # Build frontmatter using yq
+        yq --front-matter '
+            .id = "gitlab-'"$IID"'" |
+            .title = "'"$TITLE"'" |
+            .status = "'"$STATUS"'" |
+            .priority = "medium" |
+            .created = "'"$CREATED"'" |
+            .updated = "'"$UPDATED"'" |
+            .author = "imported" |
+            .source = "gitlab:'"$PROJECT_ID"'#'"$IID"'"
+        ' -i "$FILENAME"
+
+        # Handle labels
+        if [ -n "$LABELS" ]; then
+            LABELS_JSON=$(echo "$LABELS" | jq -R 'split(",") | map(ltrimstr(" ") | rtrimstr(" "))')
+            yq --front-matter ".tags = $LABELS_JSON" -i "$FILENAME"
+        else
+            yq --front-matter '.tags = []' -i "$FILENAME"
+        fi
+
+        # Handle assignee
+        if [ -n "$ASSIGNEE" ]; then
+            yq --front-matter '.assignee = "'"$ASSIGNEE"'"' -i "$FILENAME"
+        else
+            yq --front-matter '.assignee = null' -i "$FILENAME"
+        fi
+
+        echo "Imported GitLab issue #${IID}: ${TITLE}"
+    done
+
+    rm gitlab_issues.json
+
+    git add -A
+    git commit -m "Import issues from GitLab project: $PROJECT_ID"
+
+    echo "GitLab import complete!"
+    cd - > /dev/null
+}
+
+import_from_gitlab "12345678"
+```
+
+### Import from Jira
+```bash
+# Import issues from Jira (via CSV export)
+import_from_jira_csv() {
+    CSV_FILE="$1"
+
+    cd ../issues 2>/dev/null || return 1
+
+    echo "Importing issues from Jira CSV: $CSV_FILE"
+
+    # Jira CSV typically has specific columns
+    # Adjust field positions based on actual export
+    tail -n +2 "$CSV_FILE" | while IFS=',' read -r key summary type status priority assignee reporter created updated description; do
+        # Clean fields
+        key=$(echo "$key" | tr -d '"')
+        summary=$(echo "$summary" | tr -d '"')
+        status=$(echo "$status" | tr -d '"' | tr '[:upper:]' '[:lower:]')
+        priority=$(echo "$priority" | tr -d '"' | tr '[:upper:]' '[:lower:]')
+        assignee=$(echo "$assignee" | tr -d '"')
+
+        # Map Jira status to tissue status
+        case "$status" in
+            "to do"|"open"|"new") STATUS="open" ;;
+            "in progress"|"doing") STATUS="in-progress" ;;
+            "done"|"closed"|"resolved") STATUS="closed" ;;
+            *) STATUS="open" ;;
+        esac
+
+        # Map Jira priority
+        case "$priority" in
+            "blocker"|"critical") PRIORITY="critical" ;;
+            "major"|"high") PRIORITY="high" ;;
+            "minor"|"low") PRIORITY="low" ;;
+            *) PRIORITY="medium" ;;
+        esac
+
+        # Generate filename
+        ISSUE_NUM=$(($(ls -1 *.md 2>/dev/null | wc -l) + 1))
+        PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+        SAFE_TITLE=$(echo "$summary" | tr '[:upper:]' '[:lower:]' | \
+                     tr ' ' '-' | tr -cd '[:alnum:]-' | cut -c1-50)
+        FILENAME="${PADDED_NUM}_${SAFE_TITLE}.md"
+
+        # Create minimal file
+        echo -e "---\n---\n\n# ${summary}\n\n${description}\n\n---\n*Imported from Jira: ${key}*" > "$FILENAME"
+
+        # Build frontmatter using yq
+        yq --front-matter '
+            .id = "jira-'"$key"'" |
+            .title = "'"$summary"'" |
+            .status = "'"$STATUS"'" |
+            .priority = "'"$PRIORITY"'" |
+            .author = "'"${reporter:-imported}"'" |
+            .source = "jira:'"$key"'"
+        ' -i "$FILENAME"
+
+        # Handle dates
+        if [ -n "$created" ]; then
+            yq --front-matter '.created = "'"$created"'"' -i "$FILENAME"
+        else
+            yq --front-matter '.created = now' -i "$FILENAME"
+        fi
+
+        if [ -n "$updated" ]; then
+            yq --front-matter '.updated = "'"$updated"'"' -i "$FILENAME"
+        else
+            yq --front-matter '.updated = now' -i "$FILENAME"
+        fi
+
+        # Handle tags including type
+        TAG_ARRAY="[\"jira\"]"
+        if [ -n "$type" ]; then
+            TAG_ARRAY="[\"jira\", \"$type\"]"
+        fi
+        yq --front-matter ".tags = $TAG_ARRAY" -i "$FILENAME"
+
+        # Handle assignee
+        if [ -n "$assignee" ]; then
+            yq --front-matter '.assignee = "'"$assignee"'"' -i "$FILENAME"
+        else
+            yq --front-matter '.assignee = null' -i "$FILENAME"
+        fi
+
+        echo "Imported Jira issue: ${key} - ${summary}"
+    done
+
+    git add -A
+    git commit -m "Import issues from Jira CSV"
+
+    echo "Jira import complete!"
+    cd - > /dev/null
+}
+```
+
+### Bulk import with validation
+```bash
+# Import with validation and deduplication
+safe_import() {
+    SOURCE_FILE="$1"
+    SOURCE_TYPE="${2:-json}"  # json, csv, github
+
+    cd ../issues 2>/dev/null || return 1
+
+    # Create backup before import
+    BACKUP_DIR="../issues-backup-$(date +%Y%m%d-%H%M%S)"
+    cp -r . "$BACKUP_DIR"
+    echo "Created backup: $BACKUP_DIR"
+
+    # Track imported issues
+    IMPORTED_COUNT=0
+    SKIPPED_COUNT=0
+    ERROR_COUNT=0
+
+    # Import based on type
+    case "$SOURCE_TYPE" in
+        json)
+            import_from_json "$SOURCE_FILE"
+            ;;
+        csv)
+            import_from_csv "$SOURCE_FILE"
+            ;;
+        github)
+            import_from_github "$SOURCE_FILE"
+            ;;
+        *)
+            echo "Unknown import type: $SOURCE_TYPE"
+            return 1
+            ;;
+    esac
+
+    # Validate imported issues
+    echo ""
+    echo "Validating imported issues..."
+
+    for file in *.md; do
+        [ "$file" = "README.md" ] && continue
+        [ -f "$file" ] || continue
+
+        # Check required fields using yq
+        TITLE=$(yq --front-matter '.title // null' "$file" 2>/dev/null)
+        if [ "$TITLE" = "null" ]; then
+            echo "WARNING: Missing title in $file"
+            ((ERROR_COUNT++))
+        fi
+
+        STATUS=$(yq --front-matter '.status // null' "$file" 2>/dev/null)
+        if [ "$STATUS" = "null" ]; then
+            echo "WARNING: Missing status in $file"
+            ((ERROR_COUNT++))
+        fi
+
+        # Validate status value
+        if [ "$STATUS" != "null" ]; then
+            VALID=$(echo "$STATUS" | yq '. as $s | ["open", "in-progress", "closed", "blocked"] | contains([$s])')
+            if [ "$VALID" != "true" ]; then
+                echo "WARNING: Invalid status '$STATUS' in $file"
+                ((ERROR_COUNT++))
+            fi
+        fi
+    done
+
+    # Summary
+    echo ""
+    echo "Import Summary:"
+    echo "---------------"
+    echo "Imported: $IMPORTED_COUNT"
+    echo "Skipped: $SKIPPED_COUNT"
+    echo "Errors: $ERROR_COUNT"
+
+    if [ "$ERROR_COUNT" -gt 0 ]; then
+        echo ""
+        read -p "Issues found. Keep import? (y/n) " -n 1 -r
+        echo
+        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+            echo "Rolling back..."
+            rm -rf ./*
+            cp -r "$BACKUP_DIR"/* .
+            echo "Rollback complete"
+        fi
+    fi
+
+    cd - > /dev/null
+}
+```
+
+## What tissue does for you:
+1. Imports from multiple platforms (GitHub, GitLab, Jira)
+2. Handles various formats (JSON, CSV, API)
+3. Maps external fields to tissue format
+4. Validates imported data
+5. Prevents duplicate imports
+6. Creates backups before importing
+7. Maintains source references
+8. Handles authentication for APIs
+9. Provides rollback capability
+10. Generates import summaries
\ No newline at end of file
docs/index.md
@@ -0,0 +1,562 @@
+# tissue index
+
+Generate an index/summary of all issues.
+
+## Tissue Commands
+```bash
+# Generate default index
+tissue index
+
+# Output to specific file
+tissue index --output ISSUES.md
+```
+
+## Manual Workflow (bash + git)
+
+### Generate basic index
+```bash
+# Create README index of all issues
+generate_index() {
+    cd ../issues 2>/dev/null || return 1
+
+    OUTPUT_FILE="${1:-README.md}"
+
+    cat > "$OUTPUT_FILE" << 'EOF'
+# Issues Index
+
+This is an automatically generated index of all issues in this repository.
+
+Last updated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
+
+## Summary
+
+EOF
+
+    # Add statistics using yq
+    TOTAL=0
+    OPEN=0
+    IN_PROGRESS=0
+    CLOSED=0
+    BLOCKED=0
+
+    for file in *.md; do
+        [ -f "$file" ] || continue
+        [ "$file" = "README.md" ] && continue
+
+        ((TOTAL++))
+        STATUS=$(yq --front-matter '.status // "unknown"' "$file" 2>/dev/null)
+        case "$STATUS" in
+            open) ((OPEN++)) ;;
+            in-progress) ((IN_PROGRESS++)) ;;
+            closed) ((CLOSED++)) ;;
+            blocked) ((BLOCKED++)) ;;
+        esac
+    done
+
+    cat >> "$OUTPUT_FILE" << EOF
+- **Total Issues**: $TOTAL
+- **Open**: $OPEN
+- **In Progress**: $IN_PROGRESS
+- **Closed**: $CLOSED
+
+## Issues by Status
+
+EOF
+
+    # Generate sections by status
+    for status in "open" "in-progress" "closed"; do
+        case "$status" in
+            open) HEADING="### 📂 Open Issues" ;;
+            in-progress) HEADING="### 🚧 In Progress" ;;
+            closed) HEADING="### ✅ Closed Issues" ;;
+        esac
+
+        echo "$HEADING" >> "$OUTPUT_FILE"
+        echo "" >> "$OUTPUT_FILE"
+
+        COUNT=0
+        for file in *.md; do
+            [ "$file" = "README.md" ] && continue
+            [ -f "$file" ] || continue
+
+            FILE_STATUS=$(yq --front-matter '.status // ""' "$file" 2>/dev/null)
+            if [ "$FILE_STATUS" = "$status" ]; then
+                NUM=$(echo "$file" | cut -d'_' -f1)
+                TITLE=$(yq --front-matter '.title // ""' "$file")
+                PRIORITY=$(yq --front-matter '.priority // "medium"' "$file")
+                ASSIGNEE=$(yq --front-matter '.assignee // null' "$file")
+
+                # Priority emoji
+                case "$PRIORITY" in
+                    critical) EMOJI="🔴" ;;
+                    high) EMOJI="🟠" ;;
+                    medium) EMOJI="🟡" ;;
+                    low) EMOJI="🟢" ;;
+                    *) EMOJI="⚪" ;;
+                esac
+
+                echo -n "- $EMOJI **#${NUM}**: [${TITLE}](./${file})" >> "$OUTPUT_FILE"
+                [ "$ASSIGNEE" != "null" ] && echo -n " (@${ASSIGNEE})" >> "$OUTPUT_FILE"
+                echo "" >> "$OUTPUT_FILE"
+
+                ((COUNT++))
+            fi
+        done
+
+        [ "$COUNT" -eq 0 ] && echo "*No issues*" >> "$OUTPUT_FILE"
+        echo "" >> "$OUTPUT_FILE"
+    done
+
+    echo "Index generated: $OUTPUT_FILE"
+    cd - > /dev/null
+}
+
+generate_index
+```
+
+### Generate detailed index with tables
+```bash
+# Create detailed table-format index
+generate_detailed_index() {
+    cd ../issues 2>/dev/null || return 1
+
+    OUTPUT_FILE="${1:-README.md}"
+
+    cat > "$OUTPUT_FILE" << 'EOF'
+# Issues Index
+
+Generated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
+
+## Overview
+
+EOF
+
+    # Statistics section using yq
+    TOTAL=0
+    OPEN=0
+    IN_PROGRESS=0
+    CLOSED=0
+    BLOCKED=0
+
+    for file in *.md; do
+        [ -f "$file" ] || continue
+        [ "$file" = "README.md" ] && continue
+
+        ((TOTAL++))
+        STATUS=$(yq --front-matter '.status // "unknown"' "$file" 2>/dev/null)
+        case "$STATUS" in
+            open) ((OPEN++)) ;;
+            in-progress) ((IN_PROGRESS++)) ;;
+            closed) ((CLOSED++)) ;;
+            blocked) ((BLOCKED++)) ;;
+        esac
+    done
+
+    cat >> "$OUTPUT_FILE" << EOF
+| Status | Count | Percentage |
+|--------|-------|------------|
+| Open | $OPEN | $(echo "scale=1; $OPEN*100/$TOTAL" | bc)% |
+| In Progress | $IN_PROGRESS | $(echo "scale=1; $IN_PROGRESS*100/$TOTAL" | bc)% |
+| Blocked | $BLOCKED | $(echo "scale=1; $BLOCKED*100/$TOTAL" | bc)% |
+| Closed | $CLOSED | $(echo "scale=1; $CLOSED*100/$TOTAL" | bc)% |
+| **Total** | **$TOTAL** | **100%** |
+
+## All Issues
+
+| # | Title | Status | Priority | Assignee | Updated |
+|---|-------|--------|----------|----------|---------|
+EOF
+
+    # Generate table rows
+    for file in *.md; do
+        [ "$file" = "README.md" ] && continue
+        [ -f "$file" ] || continue
+
+        NUM=$(echo "$file" | cut -d'_' -f1)
+        TITLE=$(yq --front-matter '.title // ""' "$file")
+        STATUS=$(yq --front-matter '.status // "unknown"' "$file")
+        PRIORITY=$(yq --front-matter '.priority // ""' "$file")
+        ASSIGNEE=$(yq --front-matter '.assignee // null' "$file")
+        UPDATED=$(yq --front-matter '.updated // .created // ""' "$file" | cut -d'T' -f1)
+
+        # Status badge
+        case "$STATUS" in
+            open) STATUS_BADGE="🟢 Open" ;;
+            in-progress) STATUS_BADGE="🟡 In Progress" ;;
+            blocked) STATUS_BADGE="🔴 Blocked" ;;
+            closed) STATUS_BADGE="✅ Closed" ;;
+            *) STATUS_BADGE="$STATUS" ;;
+        esac
+
+        # Priority badge
+        case "$PRIORITY" in
+            critical) PRIORITY_BADGE="🔴 Critical" ;;
+            high) PRIORITY_BADGE="🟠 High" ;;
+            medium) PRIORITY_BADGE="🟡 Medium" ;;
+            low) PRIORITY_BADGE="🟢 Low" ;;
+            *) PRIORITY_BADGE="$PRIORITY" ;;
+        esac
+
+        [ "$ASSIGNEE" = "null" ] && ASSIGNEE="-"
+        echo "| [#${NUM}](./${file}) | ${TITLE:0:40} | $STATUS_BADGE | $PRIORITY_BADGE | ${ASSIGNEE} | $UPDATED |" >> "$OUTPUT_FILE"
+    done
+
+    echo "" >> "$OUTPUT_FILE"
+    echo "---" >> "$OUTPUT_FILE"
+    echo "*This file is automatically generated. Do not edit manually.*" >> "$OUTPUT_FILE"
+
+    echo "Detailed index generated: $OUTPUT_FILE"
+    cd - > /dev/null
+}
+
+generate_detailed_index
+```
+
+### Generate index by category
+```bash
+# Group issues by tags/categories
+generate_categorized_index() {
+    cd ../issues 2>/dev/null || return 1
+
+    OUTPUT_FILE="${1:-README.md}"
+
+    cat > "$OUTPUT_FILE" << 'EOF'
+# Issues by Category
+
+Last updated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
+
+EOF
+
+    # Collect all unique tags using yq
+    TEMP_TAGS=$(mktemp)
+    for file in *.md; do
+        [ "$file" = "README.md" ] && continue
+        [ -f "$file" ] || continue
+
+        yq --front-matter '.tags[]' "$file" 2>/dev/null
+    done | sort -u > "$TEMP_TAGS"
+
+    # Generate section for each tag
+    while IFS= read -r tag; do
+        [ -z "$tag" ] && continue
+
+        echo "## 🏷️ $tag" >> "$OUTPUT_FILE"
+        echo "" >> "$OUTPUT_FILE"
+
+        COUNT=0
+        for file in *.md; do
+            [ "$file" = "README.md" ] && continue
+            [ -f "$file" ] || continue
+
+            HAS_TAG=$(yq --front-matter '.tags // [] | contains(["'"$tag"'"])' "$file" 2>/dev/null)
+            if [ "$HAS_TAG" = "true" ]; then
+                NUM=$(echo "$file" | cut -d'_' -f1)
+                TITLE=$(yq --front-matter '.title // ""' "$file")
+                STATUS=$(yq --front-matter '.status // "unknown"' "$file")
+
+                # Skip closed issues in category view
+                [ "$STATUS" = "closed" ] && continue
+
+                echo "- [#${NUM}: ${TITLE}](./${file})" >> "$OUTPUT_FILE"
+                ((COUNT++))
+            fi
+        done
+
+        [ "$COUNT" -eq 0 ] && echo "*No open issues with this tag*" >> "$OUTPUT_FILE"
+        echo "" >> "$OUTPUT_FILE"
+    done < "$TEMP_TAGS"
+
+    rm "$TEMP_TAGS"
+
+    # Add untagged issues section
+    echo "## 📋 Untagged Issues" >> "$OUTPUT_FILE"
+    echo "" >> "$OUTPUT_FILE"
+
+    for file in *.md; do
+        [ "$file" = "README.md" ] && continue
+        [ -f "$file" ] || continue
+
+        TAGS=$(yq --front-matter '.tags // []' "$file" 2>/dev/null)
+        if [ "$TAGS" = "[]" ] || [ "$TAGS" = "null" ]; then
+            NUM=$(echo "$file" | cut -d'_' -f1)
+            TITLE=$(yq --front-matter '.title // ""' "$file")
+            echo "- [#${NUM}: ${TITLE}](./${file})" >> "$OUTPUT_FILE"
+        fi
+    done
+
+    echo "Categorized index generated: $OUTPUT_FILE"
+    cd - > /dev/null
+}
+
+generate_categorized_index
+```
+
+### Generate HTML index
+```bash
+# Create HTML version of index
+generate_html_index() {
+    cd ../issues 2>/dev/null || return 1
+
+    OUTPUT_FILE="${1:-index.html}"
+
+    cat > "$OUTPUT_FILE" << 'EOF'
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Issues Index</title>
+    <style>
+        body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; margin: 40px; }
+        h1 { color: #333; }
+        .stats { display: flex; gap: 20px; margin: 20px 0; }
+        .stat { padding: 10px 20px; border-radius: 5px; background: #f0f0f0; }
+        .stat-number { font-size: 24px; font-weight: bold; }
+        table { width: 100%; border-collapse: collapse; margin-top: 20px; }
+        th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
+        th { background: #f5f5f5; font-weight: 600; }
+        .status-open { color: #28a745; }
+        .status-in-progress { color: #ffc107; }
+        .status-closed { color: #6c757d; text-decoration: line-through; }
+        .priority-critical { color: #dc3545; font-weight: bold; }
+        .priority-high { color: #fd7e14; }
+        .priority-medium { color: #ffc107; }
+        .priority-low { color: #28a745; }
+    </style>
+</head>
+<body>
+    <h1>Issues Index</h1>
+    <p>Last updated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")</p>
+
+    <div class="stats">
+EOF
+
+    # Add statistics using yq
+    TOTAL=0
+    OPEN=0
+    IN_PROGRESS=0
+    CLOSED=0
+    BLOCKED=0
+
+    for file in *.md; do
+        [ -f "$file" ] || continue
+        [ "$file" = "README.md" ] && continue
+
+        ((TOTAL++))
+        STATUS=$(yq --front-matter '.status // "unknown"' "$file" 2>/dev/null)
+        case "$STATUS" in
+            open) ((OPEN++)) ;;
+            in-progress) ((IN_PROGRESS++)) ;;
+            closed) ((CLOSED++)) ;;
+            blocked) ((BLOCKED++)) ;;
+        esac
+    done
+
+    cat >> "$OUTPUT_FILE" << EOF
+        <div class="stat">
+            <div class="stat-number">$TOTAL</div>
+            <div>Total Issues</div>
+        </div>
+        <div class="stat">
+            <div class="stat-number">$OPEN</div>
+            <div>Open</div>
+        </div>
+        <div class="stat">
+            <div class="stat-number">$IN_PROGRESS</div>
+            <div>In Progress</div>
+        </div>
+        <div class="stat">
+            <div class="stat-number">$CLOSED</div>
+            <div>Closed</div>
+        </div>
+    </div>
+
+    <table>
+        <thead>
+            <tr>
+                <th>#</th>
+                <th>Title</th>
+                <th>Status</th>
+                <th>Priority</th>
+                <th>Assignee</th>
+                <th>Updated</th>
+            </tr>
+        </thead>
+        <tbody>
+EOF
+
+    # Generate table rows
+    for file in *.md; do
+        [ "$file" = "README.md" ] && continue
+        [ -f "$file" ] || continue
+
+        NUM=$(echo "$file" | cut -d'_' -f1)
+        TITLE=$(yq --front-matter '.title // ""' "$file")
+        STATUS=$(yq --front-matter '.status // "unknown"' "$file")
+        PRIORITY=$(yq --front-matter '.priority // ""' "$file")
+        ASSIGNEE=$(yq --front-matter '.assignee // null' "$file")
+        UPDATED=$(yq --front-matter '.updated // .created // ""' "$file" | cut -d'T' -f1)
+
+        echo "            <tr>" >> "$OUTPUT_FILE"
+        echo "                <td><a href=\"./${file}\">#${NUM}</a></td>" >> "$OUTPUT_FILE"
+        echo "                <td>${TITLE}</td>" >> "$OUTPUT_FILE"
+        echo "                <td class=\"status-${STATUS}\">${STATUS}</td>" >> "$OUTPUT_FILE"
+        echo "                <td class=\"priority-${PRIORITY}\">${PRIORITY}</td>" >> "$OUTPUT_FILE"
+        echo "                <td>${ASSIGNEE:--}</td>" >> "$OUTPUT_FILE"
+        echo "                <td>${UPDATED}</td>" >> "$OUTPUT_FILE"
+        echo "            </tr>" >> "$OUTPUT_FILE"
+    done
+
+    cat >> "$OUTPUT_FILE" << 'EOF'
+        </tbody>
+    </table>
+</body>
+</html>
+EOF
+
+    echo "HTML index generated: $OUTPUT_FILE"
+    cd - > /dev/null
+}
+
+generate_html_index
+```
+
+### Generate index with charts
+```bash
+# Generate index with ASCII charts
+generate_index_with_charts() {
+    cd ../issues 2>/dev/null || return 1
+
+    OUTPUT_FILE="${1:-README.md}"
+
+    cat > "$OUTPUT_FILE" << 'EOF'
+# Issues Dashboard
+
+Generated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
+
+## Status Distribution
+
+EOF
+
+    # Calculate percentages using yq
+    TOTAL=0
+    OPEN=0
+    IN_PROGRESS=0
+    CLOSED=0
+
+    for file in *.md; do
+        [ -f "$file" ] || continue
+        [ "$file" = "README.md" ] && continue
+
+        ((TOTAL++))
+        STATUS=$(yq --front-matter '.status // "unknown"' "$file" 2>/dev/null)
+        case "$STATUS" in
+            open) ((OPEN++)) ;;
+            in-progress) ((IN_PROGRESS++)) ;;
+            closed) ((CLOSED++)) ;;
+        esac
+    done
+
+    # ASCII bar chart
+    echo '```' >> "$OUTPUT_FILE"
+    printf "Open        [%-20s] %3d (%d%%)\n" \
+        $(printf '=%.0s' $(seq 1 $((OPEN*20/TOTAL)))) \
+        $OPEN $((OPEN*100/TOTAL)) >> "$OUTPUT_FILE"
+    printf "In Progress [%-20s] %3d (%d%%)\n" \
+        $(printf '=%.0s' $(seq 1 $((IN_PROGRESS*20/TOTAL)))) \
+        $IN_PROGRESS $((IN_PROGRESS*100/TOTAL)) >> "$OUTPUT_FILE"
+    printf "Closed      [%-20s] %3d (%d%%)\n" \
+        $(printf '=%.0s' $(seq 1 $((CLOSED*20/TOTAL)))) \
+        $CLOSED $((CLOSED*100/TOTAL)) >> "$OUTPUT_FILE"
+    echo '```' >> "$OUTPUT_FILE"
+
+    # Priority distribution
+    echo "" >> "$OUTPUT_FILE"
+    echo "## Priority Distribution" >> "$OUTPUT_FILE"
+    echo "" >> "$OUTPUT_FILE"
+
+    CRITICAL=0
+    HIGH=0
+    MEDIUM=0
+    LOW=0
+
+    for file in *.md; do
+        [ -f "$file" ] || continue
+        [ "$file" = "README.md" ] && continue
+
+        PRIORITY=$(yq --front-matter '.priority // ""' "$file" 2>/dev/null)
+        case "$PRIORITY" in
+            critical) ((CRITICAL++)) ;;
+            high) ((HIGH++)) ;;
+            medium) ((MEDIUM++)) ;;
+            low) ((LOW++)) ;;
+        esac
+    done
+
+    echo '```' >> "$OUTPUT_FILE"
+    echo "Critical : $(printf '*%.0s' $(seq 1 $CRITICAL)) ($CRITICAL)" >> "$OUTPUT_FILE"
+    echo "High     : $(printf '*%.0s' $(seq 1 $HIGH)) ($HIGH)" >> "$OUTPUT_FILE"
+    echo "Medium   : $(printf '*%.0s' $(seq 1 $MEDIUM)) ($MEDIUM)" >> "$OUTPUT_FILE"
+    echo "Low      : $(printf '*%.0s' $(seq 1 $LOW)) ($LOW)" >> "$OUTPUT_FILE"
+    echo '```' >> "$OUTPUT_FILE"
+
+    # Recent activity
+    echo "" >> "$OUTPUT_FILE"
+    echo "## Recent Activity (Last 7 Days)" >> "$OUTPUT_FILE"
+    echo "" >> "$OUTPUT_FILE"
+
+    SEVEN_DAYS_AGO=$(date -d "7 days ago" +%Y-%m-%d 2>/dev/null || \
+                     date -v-7d +%Y-%m-%d 2>/dev/null)
+
+    for file in *.md; do
+        [ "$file" = "README.md" ] && continue
+        [ -f "$file" ] || continue
+
+        UPDATED=$(yq --front-matter '.updated // .created // ""' "$file" 2>/dev/null | cut -d'T' -f1)
+        if [ -n "$UPDATED" ] && [ "$UPDATED" \> "$SEVEN_DAYS_AGO" ]; then
+            NUM=$(echo "$file" | cut -d'_' -f1)
+            TITLE=$(yq --front-matter '.title // ""' "$file")
+            echo "- [#${NUM}: ${TITLE}](./${file}) - Updated: $UPDATED" >> "$OUTPUT_FILE"
+        fi
+    done
+
+    echo "Dashboard index generated: $OUTPUT_FILE"
+    cd - > /dev/null
+}
+
+generate_index_with_charts
+```
+
+### Auto-commit index updates
+```bash
+# Generate and commit index
+update_and_commit_index() {
+    cd ../issues 2>/dev/null || return 1
+
+    # Generate index
+    generate_index README.md
+
+    # Check if index changed
+    if git diff --quiet README.md; then
+        echo "Index unchanged"
+    else
+        git add README.md
+        git commit -m "Update issues index - $(date +%Y-%m-%d)"
+        echo "Index committed"
+    fi
+
+    cd - > /dev/null
+}
+```
+
+## What tissue does for you:
+1. Automatically generates formatted indexes
+2. Provides multiple output formats (Markdown, HTML, etc.)
+3. Creates visual charts and statistics
+4. Groups issues by various criteria
+5. Updates timestamps automatically
+6. Generates both summary and detailed views
+7. Handles large numbers of issues efficiently
+8. Maintains consistent formatting
+9. Auto-commits index updates
+10. Supports custom templates for index generation
\ No newline at end of file
docs/init.md
@@ -0,0 +1,83 @@
+# tissue init
+
+Initialize tissue issue tracking in a git repository.
+
+## Tissue Command
+```bash
+tissue init
+tissue init --setup-worktree
+```
+
+## Manual Workflow (bash + git)
+
+### Basic initialization
+
+Assumed starting in an existing repo with a remote origin configured
+
+```bash
+# Create orphan branch for issues
+git checkout --orphan issues
+
+# Clear the index
+git rm -rf . 2>/dev/null || true
+
+# Create initial README for the issues branch
+cat > README.md << 'EOF'
+# Issue Tracker
+
+This branch contains all issues for this repository.
+Each issue is a markdown file with YAML frontmatter.
+EOF
+
+# Create a .gitignore for the issues branch
+cat > .gitignore << 'EOF'
+# Ignore everything except issues directory
+/*
+!/*.md
+!/.gitignore
+EOF
+
+# Commit the initial structure
+git add .gitignore README.md
+git commit -m "Initialize issues branch for issue tracking"
+
+# Push to remote (if exists)
+git push -u origin issues
+
+# Return to main branch
+git checkout main
+```
+
+### With worktree setup
+```bash
+# Set up a worktree for the tissues branch
+git worktree add issues issues
+
+# Now you can access issues without switching branches
+ls -la issues
+
+# Add to main branch .gitignore
+echo "issues/" >> .gitignore
+git add .gitignore
+git commit -m "Add issues worktree to gitignore"
+```
+
+### Verify setup
+```bash
+# Check that tissues branch exists
+git branch -a | grep issues
+
+# Check worktree status
+git worktree list
+
+# Verify you can access issues
+ls -la issues
+```
+
+## What tissue does for you:
+1. Automates the creation of the orphan branch
+2. Sets up the proper directory structure
+3. Creates default templates and configuration
+4. Handles worktree setup if requested
+5. Ensures proper .gitignore configuration
+6. Validates the setup is correct
docs/list.md
@@ -0,0 +1,380 @@
+# tissue list / tissue ls
+
+List and filter issues in the repository.
+
+## Tissue Commands
+```bash
+# List all issues
+tissue list
+tissue ls
+
+# Filter by status
+tissue list --status open
+tissue list --status closed
+
+# Filter by multiple criteria
+tissue list --priority high --tags bug
+
+# Sort issues
+tissue list --sort priority
+tissue list --sort updated --reverse
+```
+
+## Manual Workflow (bash + git)
+
+### List all issues
+```bash
+# Simple listing
+cd ../issues
+ls -la *.md
+
+# Or from main branch
+git ls-tree issues: --name-only | grep '\.md$'
+```
+
+### List with details using yq
+```bash
+# Display all issues with metadata
+list_issues() {
+    cd ../issues
+
+    # Header
+    printf "%-4s %-10s %-8s %-40s %-15s\n" "NUM" "STATUS" "PRIORITY" "TITLE" "TAGS"
+    echo "--------------------------------------------------------------------------------"
+
+    # Process each issue file
+    for file in *.md; do
+        [ "$file" = "README.md" ] && continue
+        [ -f "$file" ] || continue
+
+        # Extract issue number from filename
+        NUM=$(echo "$file" | cut -d'_' -f1)
+
+        # Extract fields using yq
+        TITLE=$(yq --front-matter '.title // ""' "$file" | head -c 40)
+        STATUS=$(yq --front-matter '.status // "unknown"' "$file")
+        PRIORITY=$(yq --front-matter '.priority // ""' "$file")
+        TAGS=$(yq --front-matter '.tags | join(", ") // ""' "$file" | head -c 15)
+
+        # Print row
+        printf "%-4s %-10s %-8s %-40s %-15s\n" \
+            "$NUM" "$STATUS" "$PRIORITY" "$TITLE" "$TAGS"
+    done
+}
+
+list_issues
+```
+
+### Filter by status
+```bash
+# List issues with specific status
+filter_by_status() {
+    STATUS_FILTER="$1"
+    cd ../issues
+
+    for file in *.md; do
+        [ "$file" = "README.md" ] && continue
+        [ -f "$file" ] || continue
+
+        # Check status using yq
+        STATUS=$(yq --front-matter '.status' "$file" 2>/dev/null)
+        if [ "$STATUS" = "$STATUS_FILTER" ]; then
+            TITLE=$(yq --front-matter '.title' "$file")
+            echo "${file}: ${TITLE}"
+        fi
+    done
+}
+
+filter_by_status "open"
+```
+
+### Advanced filtering with yq
+```bash
+# Filter by multiple criteria using yq expressions
+filter_issues() {
+    cd ../issues
+
+    local status_filter="${1:-.*}"
+    local priority_filter="${2:-.*}"
+    local tag_filter="${3:-}"
+
+    for file in *.md; do
+        [ "$file" = "README.md" ] && continue
+        [ -f "$file" ] || continue
+
+        # Build and evaluate filter expression
+        if [ -n "$tag_filter" ]; then
+            MATCHES=$(yq --front-matter "
+                .status == \"$status_filter\" and
+                .priority == \"$priority_filter\" and
+                (.tags // [] | contains([\"$tag_filter\"]))
+            " "$file" 2>/dev/null)
+        else
+            MATCHES=$(yq --front-matter "
+                .status == \"$status_filter\" and
+                .priority == \"$priority_filter\"
+            " "$file" 2>/dev/null)
+        fi
+
+        if [ "$MATCHES" = "true" ]; then
+            TITLE=$(yq --front-matter '.title' "$file")
+            echo "${file}: ${TITLE}"
+        fi
+    done
+}
+
+# Usage examples
+filter_issues "open" "high"
+filter_issues "open" "high" "bug"
+```
+
+### Sort issues by field
+```bash
+# Sort by priority using yq
+sort_by_priority() {
+    cd ../issues
+
+    # Create temp file with sortable data
+    TMPFILE=$(mktemp)
+
+    for file in *.md; do
+        [ "$file" = "README.md" ] && continue
+        [ -f "$file" ] || continue
+
+        PRIORITY=$(yq --front-matter '.priority // "low"' "$file")
+        TITLE=$(yq --front-matter '.title // ""' "$file")
+
+        # Assign numeric values for sorting
+        case "$PRIORITY" in
+            critical) PNUM=4 ;;
+            high)     PNUM=3 ;;
+            medium)   PNUM=2 ;;
+            low)      PNUM=1 ;;
+            *)        PNUM=0 ;;
+        esac
+
+        echo "${PNUM}|${file}|${PRIORITY}|${TITLE}" >> "$TMPFILE"
+    done
+
+    # Sort and display
+    sort -t'|' -k1 -rn "$TMPFILE" | while IFS='|' read -r num file priority title; do
+        printf "%-20s %-8s %s\n" "$file" "$priority" "$title"
+    done
+
+    rm "$TMPFILE"
+}
+
+sort_by_priority
+```
+
+### Sort by date modified
+```bash
+# Sort by last updated using yq
+sort_by_updated() {
+    cd ../issues
+
+    # Create sortable list with timestamps
+    for file in *.md; do
+        [ "$file" = "README.md" ] && continue
+        [ -f "$file" ] || continue
+
+        UPDATED=$(yq --front-matter '.updated // .created // ""' "$file")
+        TITLE=$(yq --front-matter '.title' "$file")
+        STATUS=$(yq --front-matter '.status' "$file")
+
+        echo "${UPDATED}|${file}|${STATUS}|${TITLE}"
+    done | sort -r | while IFS='|' read -r updated file status title; do
+        printf "%-20s %-10s %-10s %s\n" "$file" "$updated" "$status" "$title"
+    done
+}
+
+sort_by_updated
+```
+
+### Create a formatted table view
+```bash
+# Pretty table output using yq
+list_issues_table() {
+    cd ../issues
+
+    # Collect all data first for column width calculation
+    {
+        # Header
+        echo "NUM,STATUS,PRIORITY,TITLE,ASSIGNEE"
+
+        # Data rows
+        for file in *.md; do
+            [ "$file" = "README.md" ] && continue
+            [ -f "$file" ] || continue
+
+            NUM=$(echo "$file" | cut -d'_' -f1)
+
+            # Use yq to extract all fields at once as JSON
+            yq --front-matter -o json '
+                {
+                    "title": (.title // ""),
+                    "status": (.status // "unknown"),
+                    "priority": (.priority // ""),
+                    "assignee": (.assignee // "unassigned")
+                }
+            ' "$file" | jq -r '
+                [.title[0:40], .status, .priority, .assignee] | @csv
+            ' | sed "s/^/${NUM},/"
+        done
+    } | column -t -s','
+}
+
+list_issues_table
+```
+
+### Count issues by status
+```bash
+# Summary statistics using yq
+issue_stats() {
+    cd ../issues
+
+    echo "Issue Statistics:"
+    echo "-----------------"
+
+    # Count by status
+    for status in open in-progress blocked closed; do
+        COUNT=0
+        for file in *.md; do
+            [ "$file" = "README.md" ] && continue
+            [ -f "$file" ] || continue
+
+            FILE_STATUS=$(yq --front-matter '.status' "$file" 2>/dev/null)
+            [ "$FILE_STATUS" = "$status" ] && ((COUNT++))
+        done
+        echo "${status}: ${COUNT}"
+    done
+
+    echo ""
+    echo "By Priority:"
+    for priority in critical high medium low; do
+        COUNT=0
+        for file in *.md; do
+            [ "$file" = "README.md" ] && continue
+            [ -f "$file" ] || continue
+
+            FILE_PRIORITY=$(yq --front-matter '.priority' "$file" 2>/dev/null)
+            [ "$FILE_PRIORITY" = "$priority" ] && ((COUNT++))
+        done
+        echo "${priority}: ${COUNT}"
+    done
+
+    echo ""
+    TOTAL=$(ls -1 *.md 2>/dev/null | grep -v README | wc -l)
+    echo "Total issues: $TOTAL"
+}
+
+issue_stats
+```
+
+### Group by assignee
+```bash
+# List issues grouped by assignee
+list_by_assignee() {
+    cd ../issues
+
+    # Get unique assignees
+    ASSIGNEES=$(
+        for file in *.md; do
+            [ "$file" = "README.md" ] && continue
+            [ -f "$file" ] || continue
+            yq --front-matter '.assignee // "unassigned"' "$file"
+        done | sort -u
+    )
+
+    # Group issues by assignee
+    for assignee in $ASSIGNEES; do
+        echo ""
+        echo "=== ${assignee} ==="
+        echo "-------------------"
+
+        for file in *.md; do
+            [ "$file" = "README.md" ] && continue
+            [ -f "$file" ] || continue
+
+            FILE_ASSIGNEE=$(yq --front-matter '.assignee // "unassigned"' "$file")
+            if [ "$FILE_ASSIGNEE" = "$assignee" ]; then
+                TITLE=$(yq --front-matter '.title' "$file")
+                STATUS=$(yq --front-matter '.status' "$file")
+                PRIORITY=$(yq --front-matter '.priority' "$file")
+                echo "  $file: [$STATUS/$PRIORITY] $TITLE"
+            fi
+        done
+    done
+}
+
+list_by_assignee
+```
+
+### Export list as JSON
+```bash
+# Generate JSON array of all issues
+list_as_json() {
+    cd ../issues
+
+    echo '['
+    FIRST=true
+
+    for file in *.md; do
+        [ "$file" = "README.md" ] && continue
+        [ -f "$file" ] || continue
+
+        # Add comma separator
+        $FIRST && FIRST=false || echo ','
+
+        # Extract all frontmatter as JSON and add filename
+        yq --front-matter -o json '. + {"file": "'"$file"'"}' "$file" | jq -c '.'
+    done
+
+    echo ']'
+}
+
+list_as_json | jq '.' > issues-list.json
+```
+
+### Complex queries
+```bash
+# Find all high-priority unassigned issues
+urgent_unassigned() {
+    cd ../issues
+
+    echo "Urgent Unassigned Issues:"
+    echo "========================"
+
+    for file in *.md; do
+        [ "$file" = "README.md" ] && continue
+        [ -f "$file" ] || continue
+
+        # Use yq to check multiple conditions
+        URGENT=$(yq --front-matter '
+            (.priority == "high" or .priority == "critical") and
+            .status != "closed" and
+            (.assignee == null or .assignee == "" or .assignee == "unassigned")
+        ' "$file" 2>/dev/null)
+
+        if [ "$URGENT" = "true" ]; then
+            TITLE=$(yq --front-matter '.title' "$file")
+            PRIORITY=$(yq --front-matter '.priority' "$file")
+            echo "  [$PRIORITY] $file: $TITLE"
+        fi
+    done
+}
+
+urgent_unassigned
+```
+
+## What tissue does for you:
+1. Provides consistent formatted output
+2. Fast filtering without parsing every file
+3. Configurable display formats (table, compact, detailed)
+4. Efficient sorting algorithms
+5. Caching for repeated queries
+6. Color-coded output for terminals
+7. Pagination for long lists
+8. Export to various formats
+9. Integration with shell completion
+10. Performance optimization for large issue sets
\ No newline at end of file
docs/new.md
@@ -0,0 +1,318 @@
+# tissue new
+
+Create a new issue in the tissue tracking system.
+
+## Tissue Commands
+```bash
+# Interactive creation (prompted metadata, skipable)
+tissue new
+
+# Create issue with inline options (skips prompts)
+tissue new --title "Fix memory leak in parser" --priority high --tags "bug,performance"
+
+# Push to remote on completion of edit
+tissue new --push
+
+# Quick issue creation, editor doesn't open, --push is set to true by default with --quick
+tissue new "TODO: Add unit tests for auth module" --quick
+```
+
+## Manual Workflow (bash + git)
+
+### Basic issue creation
+```bash
+# Switch to issues worktree
+cd ../issues
+
+# Generate next issue number
+ISSUE_NUM=$(printf "%03d" $(($(ls -1 *.md 2>/dev/null | grep -v README | wc -l) + 1)))
+TITLE="Fix memory leak"
+
+# Create safe filename
+SAFE_TITLE=$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd '[:alnum:]-')
+ISSUE_FILE="${ISSUE_NUM}_${SAFE_TITLE}.md"
+
+# Create minimal markdown file with empty frontmatter
+echo -e "---\n---\n\n# $TITLE\n\n## Description\n\n## Notes" > "$ISSUE_FILE"
+
+# Build frontmatter using yq
+yq --front-matter '.id = "issue-'$(uuidgen | tr '[:upper:]' '[:lower:]')'"' -i "$ISSUE_FILE"
+yq --front-matter '.title = "'"$TITLE"'"' -i "$ISSUE_FILE"
+yq --front-matter '.status = "open"' -i "$ISSUE_FILE"
+yq --front-matter '.priority = "medium"' -i "$ISSUE_FILE"
+yq --front-matter '.tags = []' -i "$ISSUE_FILE"
+yq --front-matter '.created = now' -i "$ISSUE_FILE"
+yq --front-matter '.updated = now' -i "$ISSUE_FILE"
+yq --front-matter '.author = "'$(git config user.name)'"' -i "$ISSUE_FILE"
+yq --front-matter '.assignee = null' -i "$ISSUE_FILE"
+
+# Open in editor
+${EDITOR:-vim} "$ISSUE_FILE"
+
+# Update timestamp after editing
+yq --front-matter '.updated = now' -i "$ISSUE_FILE"
+
+# Commit
+git add "$ISSUE_FILE"
+git commit -m "Add issue #${ISSUE_NUM}: ${TITLE}"
+```
+
+### Interactive creation with prompts
+```bash
+# Interactive issue creation
+create_issue_interactive() {
+    # Prompt for metadata
+    read -p "Title: " TITLE
+    read -p "Priority (low/medium/high/critical): " PRIORITY
+    read -p "Tags (comma-separated): " TAGS_INPUT
+    read -p "Assignee (optional): " ASSIGNEE
+
+    cd ../issues
+
+    # Generate issue number
+    ISSUE_NUM=$(printf "%03d" $(($(ls -1 *.md 2>/dev/null | grep -v README | wc -l) + 1)))
+
+    # Create filename
+    SAFE_TITLE=$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd '[:alnum:]-')
+    ISSUE_FILE="${ISSUE_NUM}_${SAFE_TITLE}.md"
+
+    # Create minimal file
+    echo -e "---\n---\n\n# $TITLE\n\n## Description\n\n## Notes" > "$ISSUE_FILE"
+
+    # Build frontmatter
+    yq --front-matter '.id = "issue-'$(uuidgen | tr '[:upper:]' '[:lower:]')'"' -i "$ISSUE_FILE"
+    yq --front-matter '.title = "'"$TITLE"'"' -i "$ISSUE_FILE"
+    yq --front-matter '.status = "open"' -i "$ISSUE_FILE"
+    yq --front-matter '.priority = "'${PRIORITY:-medium}'"' -i "$ISSUE_FILE"
+
+    # Handle tags as array
+    if [ -n "$TAGS_INPUT" ]; then
+        # Convert comma-separated to JSON array format
+        TAGS_JSON=$(echo "$TAGS_INPUT" | jq -R 'split(",") | map(ltrimstr(" ") | rtrimstr(" "))')
+        yq --front-matter ".tags = $TAGS_JSON" -i "$ISSUE_FILE"
+    else
+        yq --front-matter '.tags = []' -i "$ISSUE_FILE"
+    fi
+
+    yq --front-matter '.created = now' -i "$ISSUE_FILE"
+    yq --front-matter '.updated = now' -i "$ISSUE_FILE"
+    yq --front-matter '.author = "'$(git config user.name)'"' -i "$ISSUE_FILE"
+
+    if [ -n "$ASSIGNEE" ]; then
+        yq --front-matter '.assignee = "'"$ASSIGNEE"'"' -i "$ISSUE_FILE"
+    else
+        yq --front-matter '.assignee = null' -i "$ISSUE_FILE"
+    fi
+
+    # Open in editor
+    ${EDITOR:-vim} "$ISSUE_FILE"
+
+    # Update timestamp after editing
+    yq --front-matter '.updated = now' -i "$ISSUE_FILE"
+
+    # Commit
+    git add "$ISSUE_FILE"
+    git commit -m "Add issue #${ISSUE_NUM}: ${TITLE}"
+
+    echo "Created issue: $ISSUE_FILE"
+}
+
+create_issue_interactive
+```
+
+### Quick issue creation
+```bash
+# Quick TODO creation
+quick_issue() {
+    TITLE="$1"
+    cd ../issues
+
+    # Generate filename
+    NUM=$(printf "%03d" $(($(ls -1 *.md 2>/dev/null | grep -v README | wc -l) + 1)))
+    FILE="${NUM}_quick_$(date +%s).md"
+
+    # Create minimal issue
+    echo -e "---\n---\n\n# $TITLE" > "$FILE"
+
+    # Set all fields at once using yq
+    yq --front-matter '
+        .id = "quick-'$(date +%s)'" |
+        .title = "'"$TITLE"'" |
+        .status = "open" |
+        .priority = "low" |
+        .tags = ["todo"] |
+        .created = now |
+        .updated = now |
+        .author = "'$(git config user.name)'"
+    ' -i "$FILE"
+
+    # Commit and push
+    git add "$FILE" && git commit -m "Quick issue: $TITLE" && git push
+    echo "Created: $FILE"
+}
+
+quick_issue "TODO: Add unit tests"
+```
+
+### Create from template
+```bash
+# Use a template for new issues
+create_from_template() {
+    TEMPLATE="$1"
+    TITLE="$2"
+
+    cd ../issues
+
+    # Templates stored in .tissue/templates/
+    TEMPLATE_FILE="../.tissue/templates/${TEMPLATE}.md"
+
+    if [ ! -f "$TEMPLATE_FILE" ]; then
+        echo "Template not found: $TEMPLATE"
+        return 1
+    fi
+
+    # Generate filename
+    NUM=$(printf "%03d" $(($(ls -1 *.md 2>/dev/null | grep -v README | wc -l) + 1)))
+    SAFE_TITLE=$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd '[:alnum:]-')
+    ISSUE_FILE="${NUM}_${SAFE_TITLE}.md"
+
+    # Copy template
+    cp "$TEMPLATE_FILE" "$ISSUE_FILE"
+
+    # Update frontmatter
+    yq --front-matter '
+        .id = "issue-'$(uuidgen | tr '[:upper:]' '[:lower:]')'" |
+        .title = "'"$TITLE"'" |
+        .created = now |
+        .updated = now |
+        .author = "'$(git config user.name)'"
+    ' -i "$ISSUE_FILE"
+
+    # Replace title in body
+    sed -i "s/{{TITLE}}/$TITLE/g" "$ISSUE_FILE"
+
+    ${EDITOR:-vim} "$ISSUE_FILE"
+
+    git add "$ISSUE_FILE"
+    git commit -m "Add issue from template: $TITLE"
+}
+
+create_from_template "bug-report" "Memory leak in parser"
+```
+
+### Validate frontmatter
+```bash
+# Validate issue using yq
+validate_issue() {
+    FILE="$1"
+    ERRORS=0
+
+    # Check required fields
+    for field in id title status priority created; do
+        VALUE=$(yq --front-matter ".$field" "$FILE" 2>/dev/null)
+        if [ "$VALUE" = "null" ]; then
+            echo "ERROR: Missing required field: $field"
+            ((ERRORS++))
+        fi
+    done
+
+    # Validate enum fields using yq expressions
+    STATUS=$(yq --front-matter '.status' "$FILE")
+    VALID_STATUS=$(echo "$STATUS" | yq '. as $s | ["open", "in-progress", "closed", "blocked"] | contains([$s])')
+    if [ "$VALID_STATUS" != "true" ]; then
+        echo "ERROR: Invalid status: $STATUS"
+        ((ERRORS++))
+    fi
+
+    PRIORITY=$(yq --front-matter '.priority' "$FILE")
+    VALID_PRIORITY=$(echo "$PRIORITY" | yq '. as $p | ["low", "medium", "high", "critical"] | contains([$p])')
+    if [ "$VALID_PRIORITY" != "true" ]; then
+        echo "ERROR: Invalid priority: $PRIORITY"
+        ((ERRORS++))
+    fi
+
+    # Check tags is array
+    TAGS_TYPE=$(yq --front-matter '.tags | type' "$FILE" 2>/dev/null)
+    if [ "$TAGS_TYPE" != "!!seq" ] && [ "$TAGS_TYPE" != "null" ]; then
+        echo "ERROR: Tags must be an array"
+        ((ERRORS++))
+    fi
+
+    if [ "$ERRORS" -eq 0 ]; then
+        echo "✓ Issue valid: $FILE"
+        return 0
+    else
+        return 1
+    fi
+}
+
+validate_issue "001-example.md"
+```
+
+### Bulk issue creation
+```bash
+# Create multiple related issues
+bulk_create_issues() {
+    PARENT_TITLE="$1"
+    shift
+    SUBTASKS=("$@")
+
+    cd ../issues
+
+    # Create parent issue
+    PARENT_NUM=$(printf "%03d" $(($(ls -1 *.md 2>/dev/null | grep -v README | wc -l) + 1)))
+    PARENT_FILE="${PARENT_NUM}_parent.md"
+
+    echo -e "---\n---\n\n# $PARENT_TITLE" > "$PARENT_FILE"
+
+    PARENT_ID="issue-$(uuidgen | tr '[:upper:]' '[:lower:]')"
+    yq --front-matter '
+        .id = "'"$PARENT_ID"'" |
+        .title = "'"$PARENT_TITLE"'" |
+        .status = "open" |
+        .priority = "high" |
+        .type = "epic" |
+        .created = now |
+        .updated = now
+    ' -i "$PARENT_FILE"
+
+    # Create subtasks
+    for task in "${SUBTASKS[@]}"; do
+        NUM=$(printf "%03d" $(($(ls -1 *.md 2>/dev/null | grep -v README | wc -l) + 1)))
+        FILE="${NUM}_subtask.md"
+
+        echo -e "---\n---\n\n# $task" > "$FILE"
+
+        yq --front-matter '
+            .id = "issue-'$(uuidgen | tr '[:upper:]' '[:lower:]')'" |
+            .title = "'"$task"'" |
+            .status = "open" |
+            .priority = "medium" |
+            .parent = "'"$PARENT_ID"'" |
+            .created = now |
+            .updated = now
+        ' -i "$FILE"
+    done
+
+    git add *.md
+    git commit -m "Add epic: $PARENT_TITLE with ${#SUBTASKS[@]} subtasks"
+}
+
+bulk_create_issues "Implement authentication" \
+    "Design auth schema" \
+    "Implement login endpoint" \
+    "Add session management" \
+    "Write auth tests"
+```
+
+## What tissue does for you:
+1. Automatically generates unique issue IDs
+2. Provides interactive prompts with validation
+3. Enforces consistent frontmatter structure
+4. Handles safe filename generation
+5. Templates for different issue types
+6. Automatic git operations (add, commit, push)
+7. Validates metadata before saving
+8. Maintains consistent timestamp formats
+9. Links to user configuration for defaults
+10. Supports batch operations and relationships
\ No newline at end of file
docs/pull-push.md
@@ -0,0 +1,380 @@
+# tissue pull / tissue push
+
+Sync issues with remote repository.
+
+## Tissue Commands
+```bash
+# Pull latest changes from remote
+tissue pull
+
+# Push local changes to remote
+tissue push
+```
+
+## Manual Workflow (bash + git)
+
+### Pull changes from remote
+```bash
+# Basic pull operation
+tissue_pull() {
+    # Navigate to issues directory
+    cd ../issues 2>/dev/null || {
+        echo "Issues directory not found"
+        return 1
+    }
+
+    # Fetch and merge changes
+    echo "Pulling latest issues from remote..."
+    git pull origin issues
+
+    # Show what changed
+    echo ""
+    echo "Recent changes:"
+    git log --oneline -5
+
+    cd - > /dev/null
+}
+
+tissue_pull
+```
+
+### Pull with conflict handling
+```bash
+# Pull with merge strategy options
+tissue_pull_safe() {
+    cd ../issues 2>/dev/null || return 1
+
+    # Check for uncommitted changes
+    if ! git diff-index --quiet HEAD --; then
+        echo "You have uncommitted changes. Please commit or stash them first."
+        git status --short
+        return 1
+    fi
+
+    # Fetch first to see what's coming
+    echo "Fetching from remote..."
+    git fetch origin issues
+
+    # Check if fast-forward is possible
+    LOCAL=$(git rev-parse issues)
+    REMOTE=$(git rev-parse origin/issues)
+    BASE=$(git merge-base issues origin/issues)
+
+    if [ "$LOCAL" = "$REMOTE" ]; then
+        echo "Already up to date."
+    elif [ "$LOCAL" = "$BASE" ]; then
+        # Fast-forward possible
+        echo "Fast-forwarding..."
+        git merge --ff-only origin/issues
+    elif [ "$REMOTE" = "$BASE" ]; then
+        echo "You are ahead of remote. Consider pushing your changes."
+    else
+        # Merge required
+        echo "Merge required. Attempting automatic merge..."
+        git merge origin/issues --no-edit
+
+        if [ $? -ne 0 ]; then
+            echo "Merge conflicts detected!"
+            echo "Conflicted files:"
+            git diff --name-only --diff-filter=U
+
+            echo ""
+            echo "To resolve:"
+            echo "1. Edit conflicted files"
+            echo "2. git add <resolved files>"
+            echo "3. git commit"
+        fi
+    fi
+
+    cd - > /dev/null
+}
+
+tissue_pull_safe
+```
+
+### Push changes to remote
+```bash
+# Basic push operation
+tissue_push() {
+    cd ../issues 2>/dev/null || {
+        echo "Issues directory not found"
+        return 1
+    }
+
+    # Check for uncommitted changes
+    UNCOMMITTED=$(git status --porcelain | wc -l)
+    if [ "$UNCOMMITTED" -gt 0 ]; then
+        echo "You have uncommitted changes:"
+        git status --short
+        read -p "Commit all changes before pushing? (y/n) " -n 1 -r
+        echo
+        if [[ $REPLY =~ ^[Yy]$ ]]; then
+            git add -A
+            git commit -m "Update issues"
+        else
+            echo "Push cancelled. Please commit your changes first."
+            return 1
+        fi
+    fi
+
+    # Push to remote
+    echo "Pushing issues to remote..."
+    git push origin issues
+
+    if [ $? -eq 0 ]; then
+        echo "✓ Successfully pushed to remote"
+    else
+        echo "✗ Push failed. You may need to pull first."
+    fi
+
+    cd - > /dev/null
+}
+
+tissue_push
+```
+
+### Push with automatic pull if needed
+```bash
+# Smart push that handles being behind
+tissue_push_smart() {
+    cd ../issues 2>/dev/null || return 1
+
+    # Commit any pending changes
+    if ! git diff-index --quiet HEAD --; then
+        echo "Committing local changes..."
+        git add -A
+        git commit -m "Update issues - $(date +%Y-%m-%d)"
+    fi
+
+    # Try to push
+    echo "Attempting to push..."
+    git push origin issues 2>/dev/null
+
+    if [ $? -ne 0 ]; then
+        echo "Push failed. Attempting to pull and retry..."
+
+        # Pull with rebase to keep history clean
+        git pull --rebase origin issues
+
+        if [ $? -eq 0 ]; then
+            echo "Retrying push after rebase..."
+            git push origin issues
+        else
+            echo "Rebase failed. Manual intervention required."
+            echo "Run: git rebase --abort to cancel"
+            echo "Or resolve conflicts and run: git rebase --continue"
+        fi
+    fi
+
+    cd - > /dev/null
+}
+
+tissue_push_smart
+```
+
+### Sync (pull then push)
+```bash
+# Full sync operation
+tissue_sync() {
+    cd ../issues 2>/dev/null || return 1
+
+    echo "Syncing issues with remote..."
+    echo "=============================="
+    echo ""
+
+    # Step 1: Commit local changes
+    UNCOMMITTED=$(git status --porcelain | wc -l)
+    if [ "$UNCOMMITTED" -gt 0 ]; then
+        echo "Step 1: Committing local changes..."
+        git add -A
+        git commit -m "Sync: Update issues - $(date +%Y-%m-%d)"
+    else
+        echo "Step 1: No local changes to commit"
+    fi
+
+    # Step 2: Pull from remote
+    echo "Step 2: Pulling from remote..."
+    git pull --rebase origin issues
+
+    if [ $? -ne 0 ]; then
+        echo "Pull failed. Resolve conflicts and run sync again."
+        return 1
+    fi
+
+    # Step 3: Push to remote
+    echo "Step 3: Pushing to remote..."
+    git push origin issues
+
+    if [ $? -eq 0 ]; then
+        echo ""
+        echo "✓ Sync complete!"
+    else
+        echo ""
+        echo "✗ Push failed"
+    fi
+
+    cd - > /dev/null
+}
+
+tissue_sync
+```
+
+### Force push/pull (dangerous)
+```bash
+# Force push - overwrites remote
+tissue_force_push() {
+    cd ../issues 2>/dev/null || return 1
+
+    echo "WARNING: This will overwrite the remote branch!"
+    read -p "Are you sure? (type 'yes' to confirm) " -r
+    if [ "$REPLY" != "yes" ]; then
+        echo "Cancelled"
+        return 1
+    fi
+
+    git push --force origin issues
+    echo "Force pushed to remote"
+
+    cd - > /dev/null
+}
+
+# Force pull - overwrites local
+tissue_force_pull() {
+    cd ../issues 2>/dev/null || return 1
+
+    echo "WARNING: This will overwrite your local changes!"
+    read -p "Are you sure? (type 'yes' to confirm) " -r
+    if [ "$REPLY" != "yes" ]; then
+        echo "Cancelled"
+        return 1
+    fi
+
+    # Reset to remote state
+    git fetch origin issues
+    git reset --hard origin/issues
+    echo "Force pulled from remote"
+
+    cd - > /dev/null
+}
+```
+
+### Check what would be pushed/pulled
+```bash
+# Preview what would be pushed
+tissue_push_preview() {
+    cd ../issues 2>/dev/null || return 1
+
+    echo "Changes that would be pushed:"
+    echo "-----------------------------"
+
+    # Fetch to ensure we have latest remote info
+    git fetch origin issues --quiet
+
+    # Show commits to be pushed
+    COMMITS=$(git rev-list origin/issues..issues)
+    if [ -z "$COMMITS" ]; then
+        echo "Nothing to push"
+    else
+        git log --oneline origin/issues..issues
+        echo ""
+        echo "Files changed:"
+        git diff --stat origin/issues..issues
+    fi
+
+    cd - > /dev/null
+}
+
+# Preview what would be pulled
+tissue_pull_preview() {
+    cd ../issues 2>/dev/null || return 1
+
+    echo "Changes that would be pulled:"
+    echo "-----------------------------"
+
+    # Fetch latest
+    git fetch origin issues --quiet
+
+    # Show commits to be pulled
+    COMMITS=$(git rev-list issues..origin/issues)
+    if [ -z "$COMMITS" ]; then
+        echo "Nothing to pull"
+    else
+        git log --oneline issues..origin/issues
+        echo ""
+        echo "Files changed:"
+        git diff --stat issues..origin/issues
+    fi
+
+    cd - > /dev/null
+}
+```
+
+### Backup before sync
+```bash
+# Create backup before pulling
+tissue_pull_with_backup() {
+    cd ../issues 2>/dev/null || return 1
+
+    # Create backup branch
+    BACKUP_BRANCH="backup-$(date +%Y%m%d-%H%M%S)"
+    git branch "$BACKUP_BRANCH"
+    echo "Created backup branch: $BACKUP_BRANCH"
+
+    # Now pull
+    git pull origin issues
+
+    if [ $? -eq 0 ]; then
+        echo "Pull successful"
+        # Optionally delete backup if successful
+        read -p "Delete backup branch? (y/n) " -n 1 -r
+        echo
+        if [[ $REPLY =~ ^[Yy]$ ]]; then
+            git branch -D "$BACKUP_BRANCH"
+        fi
+    else
+        echo "Pull failed. Your previous state is saved in: $BACKUP_BRANCH"
+        echo "To restore: git reset --hard $BACKUP_BRANCH"
+    fi
+
+    cd - > /dev/null
+}
+```
+
+### Setup tracking for push/pull
+```bash
+# Ensure branch is tracking remote
+tissue_setup_tracking() {
+    cd ../issues 2>/dev/null || return 1
+
+    # Check if tracking is set up
+    TRACKING=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null)
+
+    if [ -z "$TRACKING" ]; then
+        echo "Setting up tracking for issues branch..."
+        git branch --set-upstream-to=origin/issues issues
+        echo "✓ Tracking configured"
+    else
+        echo "Already tracking: $TRACKING"
+    fi
+
+    # Verify remote exists
+    if ! git ls-remote --heads origin issues > /dev/null 2>&1; then
+        echo "Remote branch doesn't exist. Creating..."
+        git push -u origin issues
+    fi
+
+    cd - > /dev/null
+}
+```
+
+## What tissue does for you:
+1. Handles merge conflicts gracefully
+2. Provides atomic sync operations
+3. Auto-commits before pushing if needed
+4. Shows preview of changes
+5. Creates backups when pulling
+6. Manages branch tracking setup
+7. Provides force options with safety prompts
+8. Optimizes with rebase when appropriate
+9. Gives clear status feedback
+10. Handles authentication seamlessly
\ No newline at end of file
docs/search.md
@@ -0,0 +1,340 @@
+# tissue search / tissue grep
+
+Search through issues for specific content.
+
+## Tissue Commands
+```bash
+# Search all issue content
+tissue search "memory leak"
+
+# Search specific fields
+tissue search "auth" --in title,tags
+
+# Grep with regex
+tissue grep "TODO|FIXME" --body-only
+```
+
+## Manual Workflow (bash + git)
+
+### Basic text search
+```bash
+# Search in all issues
+cd ../issues
+grep -r "memory leak" *.md
+
+# Case-insensitive search
+grep -ri "memory leak" *.md
+
+# Show line numbers
+grep -rn "memory leak" *.md
+```
+
+### Search with context
+```bash
+# Show lines before and after match
+cd ../issues
+grep -B 2 -A 2 "memory leak" *.md
+
+# Search and show filename only
+grep -l "memory leak" *.md
+```
+
+### Search in specific parts of issues
+```bash
+# Search only in titles using yq
+search_titles() {
+    SEARCH_TERM="$1"
+    cd ../issues
+
+    for file in *.md; do
+        [ -f "$file" ] || continue
+        [ "$file" = "README.md" ] && continue
+
+        # Extract title using yq and check for match
+        TITLE=$(yq --front-matter '.title // ""' "$file" 2>/dev/null)
+        if echo "$TITLE" | grep -qi "$SEARCH_TERM"; then
+            echo "${file}: ${TITLE}"
+        fi
+    done
+}
+
+search_titles "auth"
+```
+
+### Search in issue body only
+```bash
+# Search body content (skip frontmatter)
+search_body() {
+    SEARCH_TERM="$1"
+    cd ../issues
+
+    for file in *.md; do
+        [ -f "$file" ] || continue
+        [ "$file" = "README.md" ] && continue
+
+        # Extract body (everything after frontmatter)
+        # Note: yq processes frontmatter, so we use sed to get body
+        BODY=$(sed -n '/^---$/,/^---$/d; p' "$file")
+
+        if echo "$BODY" | grep -qi "$SEARCH_TERM"; then
+            echo "=== ${file} ==="
+            echo "$BODY" | grep -i --color=auto "$SEARCH_TERM"
+            echo ""
+        fi
+    done
+}
+
+search_body "TODO"
+```
+
+### Search in tags
+```bash
+# Search for issues with specific tags using yq
+search_by_tag() {
+    TAG="$1"
+    cd ../issues
+
+    for file in *.md; do
+        [ -f "$file" ] || continue
+        [ "$file" = "README.md" ] && continue
+
+        # Check if tag exists using yq
+        HAS_TAG=$(yq --front-matter '.tags // [] | contains(["'"$TAG"'"])' "$file" 2>/dev/null)
+
+        if [ "$HAS_TAG" = "true" ]; then
+            TITLE=$(yq --front-matter '.title // ""' "$file")
+            TAGS=$(yq --front-matter '.tags | join(", ") // ""' "$file")
+            echo "${file}: ${TITLE} [${TAGS}]"
+        fi
+    done
+}
+
+search_by_tag "bug"
+```
+
+### Search with yq expressions
+```bash
+# Complex field queries using yq
+search_by_expression() {
+    cd ../issues
+
+    # Find all high-priority issues with specific tags
+    for file in *.md; do
+        [ -f "$file" ] || continue
+        [ "$file" = "README.md" ] && continue
+
+        MATCHES=$(yq --front-matter '
+            .priority == "high" and
+            (.tags // [] | contains(["bug"]))
+        ' "$file" 2>/dev/null)
+
+        if [ "$MATCHES" = "true" ]; then
+            TITLE=$(yq --front-matter '.title' "$file")
+            echo "${file}: ${TITLE}"
+        fi
+    done
+}
+
+# Search for issues by assignee
+search_by_assignee() {
+    ASSIGNEE="$1"
+    cd ../issues
+
+    for file in *.md; do
+        [ -f "$file" ] || continue
+        [ "$file" = "README.md" ] && continue
+
+        FILE_ASSIGNEE=$(yq --front-matter '.assignee // "unassigned"' "$file" 2>/dev/null)
+        if [ "$FILE_ASSIGNEE" = "$ASSIGNEE" ]; then
+            TITLE=$(yq --front-matter '.title' "$file")
+            STATUS=$(yq --front-matter '.status' "$file")
+            echo "${file}: [${STATUS}] ${TITLE}"
+        fi
+    done
+}
+```
+
+### Advanced regex search
+```bash
+# Using extended regex
+cd ../issues
+grep -E "(TODO|FIXME|HACK|XXX)" *.md
+
+# Search for patterns
+grep -E "^[[:space:]]*- \[ \]" *.md  # Find unchecked checkboxes
+grep -E "^## (Problem|Solution)" *.md # Find sections
+```
+
+### Full-text search with ripgrep
+```bash
+# If ripgrep is installed (much faster)
+cd ../issues
+rg "memory leak"
+
+# Search with type filtering
+rg -t md "memory leak"
+
+# Search with pretty output
+rg --pretty "memory leak"
+```
+
+### Search across multiple fields
+```bash
+# Combined search function using yq
+search_issues() {
+    local search_term="$1"
+    local search_in="${2:-all}"  # Default to all
+
+    cd ../issues
+
+    for file in *.md; do
+        [ -f "$file" ] || continue
+        [ "$file" = "README.md" ] && continue
+
+        local found=false
+        local matches=""
+
+        case "$search_in" in
+            title|titles)
+                TITLE=$(yq --front-matter '.title // ""' "$file" 2>/dev/null)
+                if echo "$TITLE" | grep -qi "$search_term"; then
+                    found=true
+                    matches="Title: $TITLE"
+                fi
+                ;;
+
+            tags|tag)
+                TAGS=$(yq --front-matter '.tags | join(", ") // ""' "$file" 2>/dev/null)
+                if echo "$TAGS" | grep -qi "$search_term"; then
+                    found=true
+                    matches="Tags: $TAGS"
+                fi
+                ;;
+
+            body)
+                BODY=$(sed -n '/^---$/,/^---$/d; p' "$file")
+                if echo "$BODY" | grep -qi "$search_term"; then
+                    found=true
+                    matches=$(echo "$BODY" | grep -i "$search_term" | head -3)
+                fi
+                ;;
+
+            all|*)
+                # Search everywhere
+                if grep -qi "$search_term" "$file"; then
+                    found=true
+                    matches=$(grep -i "$search_term" "$file" | head -3)
+                fi
+                ;;
+        esac
+
+        if $found; then
+            echo "=== $file ==="
+            echo "$matches"
+            echo ""
+        fi
+    done
+}
+
+# Usage examples
+search_issues "auth" "title"
+search_issues "bug" "tags"
+search_issues "TODO" "body"
+```
+
+### Create search index for faster searching
+```bash
+# Build search index using yq
+build_search_index() {
+    cd ../issues
+    INDEX_FILE=".search_index"
+
+    > "$INDEX_FILE"  # Clear index
+
+    for file in *.md; do
+        [ -f "$file" ] || continue
+        [ "$file" = "README.md" ] && continue
+
+        # Extract searchable content using yq
+        TITLE=$(yq --front-matter '.title // ""' "$file" 2>/dev/null)
+        TAGS=$(yq --front-matter '.tags | join(",") // ""' "$file" 2>/dev/null)
+        STATUS=$(yq --front-matter '.status // "unknown"' "$file" 2>/dev/null)
+        PRIORITY=$(yq --front-matter '.priority // ""' "$file" 2>/dev/null)
+        ASSIGNEE=$(yq --front-matter '.assignee // ""' "$file" 2>/dev/null)
+
+        # Add to index
+        echo "${file}|${TITLE}|${STATUS}|${PRIORITY}|${TAGS}|${ASSIGNEE}" >> "$INDEX_FILE"
+    done
+
+    echo "Search index built: $INDEX_FILE"
+}
+
+# Quick search using index
+quick_search() {
+    SEARCH_TERM="$1"
+    cd ../issues
+
+    if [ ! -f ".search_index" ]; then
+        build_search_index
+    fi
+
+    grep -i "$SEARCH_TERM" .search_index | while IFS='|' read -r file title status priority tags assignee; do
+        echo "${file}: ${title} [${status}/${priority}] Tags: ${tags}"
+    done
+}
+```
+
+### Search with scoring/ranking
+```bash
+# Rank search results by relevance using yq
+ranked_search() {
+    SEARCH_TERM="$1"
+    cd ../issues
+
+    TMPFILE=$(mktemp)
+
+    for file in *.md; do
+        [ -f "$file" ] || continue
+        [ "$file" = "README.md" ] && continue
+
+        # Count occurrences
+        COUNT=$(grep -ic "$SEARCH_TERM" "$file" 2>/dev/null || echo 0)
+
+        if [ "$COUNT" -gt 0 ]; then
+            TITLE=$(yq --front-matter '.title // ""' "$file" 2>/dev/null)
+            PRIORITY=$(yq --front-matter '.priority // "low"' "$file" 2>/dev/null)
+
+            # Weight by priority
+            case "$PRIORITY" in
+                critical) WEIGHT=$((COUNT * 4)) ;;
+                high)     WEIGHT=$((COUNT * 3)) ;;
+                medium)   WEIGHT=$((COUNT * 2)) ;;
+                *)        WEIGHT=$COUNT ;;
+            esac
+
+            echo "${WEIGHT}|${COUNT}|${file}|${TITLE}|${PRIORITY}" >> "$TMPFILE"
+        fi
+    done
+
+    # Sort by weight (descending) and display
+    sort -t'|' -k1 -rn "$TMPFILE" | while IFS='|' read -r weight count file title priority; do
+        echo "${file} (${count} matches, ${priority}): ${title}"
+    done
+
+    rm "$TMPFILE"
+}
+
+ranked_search "test"
+```
+
+## What tissue does for you:
+1. Indexes issues for instant searching
+2. Provides ranked/scored results
+3. Supports complex query syntax
+4. Searches across fields intelligently
+5. Highlights matches in output
+6. Provides search history
+7. Integrates with fuzzy finding
+8. Caches search results
+9. Supports saved searches
+10. Provides search suggestions
\ No newline at end of file
docs/start.md
@@ -0,0 +1,367 @@
+# tissue start
+
+Start working on an issue - sets status to in-progress and checks out appropriate branch.
+
+## Tissue Command
+```bash
+tissue start 42  # Sets status to in-progress, checks out branch
+```
+
+## Manual Workflow (bash + git)
+
+### Basic start workflow
+```bash
+# Start working on an issue using yq
+start_issue() {
+    ISSUE_NUM="$1"
+    cd ../issues
+
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo "Issue #${ISSUE_NUM} not found"
+        return 1
+    fi
+
+    # Extract title for branch name using yq
+    TITLE=$(yq --front-matter '.title // ""' "$FILE")
+    SAFE_TITLE=$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | \
+                 tr ' ' '-' | tr -cd '[:alnum:]-' | cut -c1-50)
+
+    # Update status to in-progress and assign if needed
+    CURRENT_USER=$(git config user.name)
+    CURRENT_ASSIGNEE=$(yq --front-matter '.assignee // null' "$FILE")
+
+    if [ "$CURRENT_ASSIGNEE" = "null" ]; then
+        # Assign to current user if unassigned
+        yq --front-matter '
+            .status = "in-progress" |
+            .assignee = "'"$CURRENT_USER"'" |
+            .updated = now
+        ' -i "$FILE"
+    else
+        # Just update status
+        yq --front-matter '
+            .status = "in-progress" |
+            .updated = now
+        ' -i "$FILE"
+    fi
+
+    # Commit status change in issues branch
+    git add "$FILE"
+    git commit -m "Start issue #${ISSUE_NUM}: ${TITLE}"
+    git push
+
+    # Return to main repository
+    cd $(git rev-parse --show-toplevel)
+
+    # Create and checkout feature branch
+    BRANCH_NAME="issue-${ISSUE_NUM}-${SAFE_TITLE}"
+    git checkout -b "$BRANCH_NAME" main
+
+    echo "Started issue #${ISSUE_NUM}"
+    echo "Created branch: ${BRANCH_NAME}"
+    echo "Status: in-progress"
+}
+
+start_issue 42
+```
+
+### Start with automatic assignment
+```bash
+# Start and assign to current user using yq
+start_and_assign() {
+    ISSUE_NUM="$1"
+    cd ../issues
+
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo "Issue #${ISSUE_NUM} not found"
+        return 1
+    fi
+
+    # Get current user
+    CURRENT_USER=$(git config user.name)
+    if [ -z "$CURRENT_USER" ]; then
+        CURRENT_USER=$(whoami)
+    fi
+
+    # Check if already assigned using yq
+    ASSIGNEE=$(yq --front-matter '.assignee // null' "$FILE")
+    if [ "$ASSIGNEE" != "null" ] && [ "$ASSIGNEE" != "$CURRENT_USER" ]; then
+        echo "Warning: Issue already assigned to: $ASSIGNEE"
+        read -p "Reassign to you? (y/n) " -n 1 -r
+        echo
+        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+            return 1
+        fi
+    fi
+
+    # Update status and assignee using yq
+    yq --front-matter '
+        .status = "in-progress" |
+        .assignee = "'"$CURRENT_USER"'" |
+        .updated = now
+    ' -i "$FILE"
+
+    git add "$FILE"
+    git commit -m "Start issue #${ISSUE_NUM} (assigned to ${CURRENT_USER})"
+}
+
+start_and_assign 42
+```
+
+### Start with branch strategy options
+```bash
+# Start with different branch strategies
+start_issue_advanced() {
+    ISSUE_NUM="$1"
+    BRANCH_STRATEGY="${2:-feature}"  # feature, hotfix, or custom
+    BASE_BRANCH="${3:-main}"
+
+    cd ../issues
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo "Issue #${ISSUE_NUM} not found"
+        return 1
+    fi
+
+    # Get issue details using yq
+    TITLE=$(yq --front-matter '.title // ""' "$FILE")
+    PRIORITY=$(yq --front-matter '.priority // "medium"' "$FILE")
+    TAGS=$(yq --front-matter '.tags | join(", ") // ""' "$FILE")
+
+    # Generate branch name based on strategy
+    SAFE_TITLE=$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | \
+                 tr ' ' '-' | tr -cd '[:alnum:]-' | cut -c1-40)
+
+    case "$BRANCH_STRATEGY" in
+        feature)
+            BRANCH_NAME="feature/${ISSUE_NUM}-${SAFE_TITLE}"
+            ;;
+        hotfix)
+            BRANCH_NAME="hotfix/${ISSUE_NUM}-${SAFE_TITLE}"
+            BASE_BRANCH="main"  # Hotfixes always from main
+            ;;
+        bugfix)
+            BRANCH_NAME="bugfix/${ISSUE_NUM}-${SAFE_TITLE}"
+            ;;
+        custom)
+            read -p "Enter branch name: " BRANCH_NAME
+            ;;
+        *)
+            BRANCH_NAME="${BRANCH_STRATEGY}/${ISSUE_NUM}-${SAFE_TITLE}"
+            ;;
+    esac
+
+    # Update issue status using yq
+    yq --front-matter '
+        .status = "in-progress" |
+        .updated = now
+    ' -i "$FILE"
+
+    # Add start comment
+    echo "
+
+### Log
+
+**[$(date -u +"%Y-%m-%d %H:%M:%S UTC")] $(git config user.name):**
+Started work on this issue. Branch: ${BRANCH_NAME}" >> "$FILE"
+
+    git add "$FILE"
+    git commit -m "Start issue #${ISSUE_NUM}: ${TITLE}"
+    git push
+
+    # Switch to main repo and create branch
+    cd $(git rev-parse --show-toplevel)
+    git checkout "$BASE_BRANCH"
+    git pull origin "$BASE_BRANCH"
+    git checkout -b "$BRANCH_NAME"
+
+    echo "Issue #${ISSUE_NUM} started"
+    echo "Branch: ${BRANCH_NAME} (from ${BASE_BRANCH})"
+    echo "Priority: ${PRIORITY}"
+}
+
+start_issue_advanced 42 "hotfix" "main"
+```
+
+### Start with pre-checks
+```bash
+# Start with validation checks using yq
+safe_start_issue() {
+    ISSUE_NUM="$1"
+
+    # Check for uncommitted changes
+    if ! git diff-index --quiet HEAD --; then
+        echo "Error: You have uncommitted changes"
+        echo "Please commit or stash them first"
+        return 1
+    fi
+
+    cd ../issues
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo "Issue #${ISSUE_NUM} not found"
+        return 1
+    fi
+
+    # Check current status using yq
+    STATUS=$(yq --front-matter '.status // "unknown"' "$FILE")
+    if [ "$STATUS" = "in-progress" ]; then
+        ASSIGNEE=$(yq --front-matter '.assignee // "unassigned"' "$FILE")
+        echo "Issue already in progress"
+        echo "Assigned to: ${ASSIGNEE}"
+        read -p "Continue anyway? (y/n) " -n 1 -r
+        echo
+        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+            return 1
+        fi
+    fi
+
+    if [ "$STATUS" = "closed" ]; then
+        echo "Issue is closed. Reopening..."
+        yq --front-matter '.status = "open"' -i "$FILE"
+    fi
+
+    # Proceed with starting the issue
+    TITLE=$(yq --front-matter '.title // ""' "$FILE")
+    SAFE_TITLE=$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | \
+                 tr ' ' '-' | tr -cd '[:alnum:]-' | cut -c1-50)
+    BRANCH_NAME="issue-${ISSUE_NUM}-${SAFE_TITLE}"
+
+    # Check if branch already exists
+    if git show-ref --verify --quiet "refs/heads/${BRANCH_NAME}"; then
+        echo "Branch ${BRANCH_NAME} already exists"
+        git checkout "$BRANCH_NAME"
+    else
+        # Create new branch
+        cd $(git rev-parse --show-toplevel)
+        git checkout -b "$BRANCH_NAME" main
+    fi
+
+    # Update issue status using yq
+    cd ../issues
+    CURRENT_USER=$(git config user.name)
+    CURRENT_ASSIGNEE=$(yq --front-matter '.assignee // null' "$FILE")
+
+    if [ "$CURRENT_ASSIGNEE" = "null" ]; then
+        yq --front-matter '
+            .status = "in-progress" |
+            .assignee = "'"$CURRENT_USER"'" |
+            .updated = now
+        ' -i "$FILE"
+    else
+        yq --front-matter '
+            .status = "in-progress" |
+            .updated = now
+        ' -i "$FILE"
+    fi
+
+    git add "$FILE"
+    git commit -m "Start issue #${ISSUE_NUM}: ${TITLE}"
+    git push
+
+    echo "Successfully started issue #${ISSUE_NUM}"
+}
+
+safe_start_issue 42
+```
+
+### Start multiple related issues
+```bash
+# Start a group of related issues
+start_issue_group() {
+    PARENT_ISSUE="$1"
+    shift
+    RELATED_ISSUES="$@"
+
+    # Start parent issue first
+    echo "Starting parent issue #${PARENT_ISSUE}..."
+    start_issue "$PARENT_ISSUE"
+
+    # Get parent branch name
+    PARENT_BRANCH=$(git branch --show-current)
+
+    # Start related issues
+    for issue in $RELATED_ISSUES; do
+        echo "Starting related issue #${issue}..."
+
+        cd ../issues
+        PADDED_NUM=$(printf "%03d" "$issue")
+        FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+        if [ -z "$FILE" ]; then
+            echo "Issue #${issue} not found, skipping"
+            continue
+        fi
+
+        # Update status and add reference to parent using yq
+        yq --front-matter '
+            .status = "in-progress" |
+            .parent_issue = "'"$PARENT_ISSUE"'" |
+            .updated = now
+        ' -i "$FILE"
+
+        git add "$FILE"
+        git commit -m "Start related issue #${issue} (parent: #${PARENT_ISSUE})"
+    done
+
+    git push
+    cd $(git rev-parse --show-toplevel)
+
+    echo "Started parent issue #${PARENT_ISSUE} and related issues: ${RELATED_ISSUES}"
+}
+
+start_issue_group 42 43 44
+```
+
+### Integration with time tracking
+```bash
+# Start issue with time tracking using yq
+start_with_timetrack() {
+    ISSUE_NUM="$1"
+
+    cd ../issues
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo "Issue #${ISSUE_NUM} not found"
+        return 1
+    fi
+
+    # Add time tracking to frontmatter using yq
+    yq --front-matter '
+        .status = "in-progress" |
+        .time_started = now |
+        .updated = now
+    ' -i "$FILE"
+
+    git add "$FILE"
+    git commit -m "Start issue #${ISSUE_NUM} with time tracking"
+
+    START_TIME=$(yq --front-matter '.time_started' "$FILE")
+    echo "Started issue #${ISSUE_NUM} at ${START_TIME}"
+    echo "Remember to run 'stop_issue ${ISSUE_NUM}' when done"
+}
+```
+
+## What tissue does for you:
+1. Automatically updates issue status to in-progress
+2. Creates appropriately named feature branches
+3. Assigns issue to current user if unassigned
+4. Validates prerequisites before starting
+5. Handles branch naming conventions
+6. Adds audit trail comments
+7. Manages git operations seamlessly
+8. Supports different branching strategies
+9. Integrates with time tracking
+10. Links related issues together
\ No newline at end of file
docs/status.md
@@ -0,0 +1,480 @@
+# tissue status
+
+Show tissue branch details and status.
+
+## Tissue Command
+```bash
+tissue status
+```
+
+## Manual Workflow (bash + git)
+
+### Basic status check
+```bash
+# Check tissue setup status
+tissue_status() {
+    echo "Tissue Status Report"
+    echo "===================="
+    echo ""
+
+    # Check if issues branch exists
+    if git show-ref --verify --quiet refs/heads/issues; then
+        echo "✓ Issues branch exists"
+    else
+        echo "✗ Issues branch not found"
+        return 1
+    fi
+
+    # Check if worktree is set up
+    WORKTREE_PATH=$(git worktree list | grep "issues" | awk '{print $1}')
+    if [ -n "$WORKTREE_PATH" ]; then
+        echo "✓ Worktree configured at: $WORKTREE_PATH"
+    else
+        echo "✗ No worktree found for issues branch"
+    fi
+
+    # Count issues by status using yq
+    if [ -d "../issues" ] || [ -n "$WORKTREE_PATH" ]; then
+        ISSUES_DIR="${WORKTREE_PATH:-../issues}"
+
+        echo ""
+        echo "Issue Statistics:"
+        echo "-----------------"
+
+        TOTAL=0
+        OPEN=0
+        IN_PROGRESS=0
+        CLOSED=0
+        BLOCKED=0
+
+        for file in "$ISSUES_DIR"/*.md; do
+            [ -f "$file" ] || continue
+            [ "$(basename "$file")" = "README.md" ] && continue
+
+            ((TOTAL++))
+
+            STATUS=$(yq --front-matter '.status // "unknown"' "$file" 2>/dev/null)
+            case "$STATUS" in
+                open) ((OPEN++)) ;;
+                in-progress) ((IN_PROGRESS++)) ;;
+                closed) ((CLOSED++)) ;;
+                blocked) ((BLOCKED++)) ;;
+            esac
+        done
+
+        echo "Total issues: $TOTAL"
+        if [ "$TOTAL" -gt 0 ]; then
+            echo "  Open:        $OPEN"
+            echo "  In Progress: $IN_PROGRESS"
+            echo "  Closed:      $CLOSED"
+            echo "  Blocked:     $BLOCKED"
+        fi
+    fi
+
+    # Check sync status with remote
+    echo ""
+    echo "Remote Status:"
+    echo "--------------"
+
+    cd "$ISSUES_DIR" 2>/dev/null || cd ../issues 2>/dev/null || {
+        echo "Cannot access issues directory"
+        return 1
+    }
+
+    # Fetch latest (without merging)
+    git fetch origin issues 2>/dev/null
+
+    # Check if we're behind or ahead
+    LOCAL=$(git rev-parse issues 2>/dev/null)
+    REMOTE=$(git rev-parse origin/issues 2>/dev/null)
+    BASE=$(git merge-base issues origin/issues 2>/dev/null)
+
+    if [ "$LOCAL" = "$REMOTE" ]; then
+        echo "✓ In sync with remote"
+    elif [ "$LOCAL" = "$BASE" ]; then
+        BEHIND=$(git rev-list --count issues..origin/issues)
+        echo "⚠ Behind remote by $BEHIND commits"
+        echo "  Run: tissue pull"
+    elif [ "$REMOTE" = "$BASE" ]; then
+        AHEAD=$(git rev-list --count origin/issues..issues)
+        echo "⚠ Ahead of remote by $AHEAD commits"
+        echo "  Run: tissue push"
+    else
+        echo "⚠ Diverged from remote"
+        echo "  Local and remote have diverged. Manual merge required."
+    fi
+}
+
+tissue_status
+```
+
+### Detailed status with recent activity
+```bash
+# Extended status report
+tissue_status_detailed() {
+    echo "Tissue Detailed Status"
+    echo "======================"
+    echo ""
+
+    # Basic checks
+    echo "Configuration:"
+    echo "--------------"
+
+    # Check branch
+    if git show-ref --verify --quiet refs/heads/issues; then
+        echo "✓ Issues branch: exists"
+        BRANCH_CREATED=$(git log --format="%ci" --reverse issues | head -1)
+        echo "  Created: $BRANCH_CREATED"
+    else
+        echo "✗ Issues branch: not found"
+        return 1
+    fi
+
+    # Check worktree
+    WORKTREE_INFO=$(git worktree list | grep "issues")
+    if [ -n "$WORKTREE_INFO" ]; then
+        WORKTREE_PATH=$(echo "$WORKTREE_INFO" | awk '{print $1}')
+        WORKTREE_COMMIT=$(echo "$WORKTREE_INFO" | awk '{print $2}')
+        echo "✓ Worktree: $WORKTREE_PATH"
+        echo "  At commit: $WORKTREE_COMMIT"
+    else
+        echo "✗ Worktree: not configured"
+    fi
+
+    # Issue statistics by priority using yq
+    echo ""
+    echo "Issues by Priority:"
+    echo "------------------"
+
+    ISSUES_DIR="${WORKTREE_PATH:-../issues}"
+    for priority in critical high medium low; do
+        COUNT=0
+        for file in "$ISSUES_DIR"/*.md; do
+            [ -f "$file" ] || continue
+            [ "$(basename "$file")" = "README.md" ] && continue
+
+            FILE_PRIORITY=$(yq --front-matter '.priority // ""' "$file" 2>/dev/null)
+            [ "$FILE_PRIORITY" = "$priority" ] && ((COUNT++))
+        done
+        printf "  %-10s: %3d\n" "$priority" "$COUNT"
+    done
+
+    # Recent activity
+    echo ""
+    echo "Recent Activity (last 5 changes):"
+    echo "---------------------------------"
+
+    cd "$ISSUES_DIR" 2>/dev/null || return 1
+
+    git log --oneline -5 --pretty=format:"%h %s [%ar by %an]"
+    echo ""
+
+    # Modified but not committed
+    echo ""
+    echo "Uncommitted Changes:"
+    echo "-------------------"
+
+    MODIFIED=$(git status --porcelain | grep "^ M" | wc -l)
+    UNTRACKED=$(git status --porcelain | grep "^??" | wc -l)
+
+    if [ "$MODIFIED" -gt 0 ] || [ "$UNTRACKED" -gt 0 ]; then
+        echo "⚠ Found uncommitted changes:"
+        [ "$MODIFIED" -gt 0 ] && echo "  Modified files: $MODIFIED"
+        [ "$UNTRACKED" -gt 0 ] && echo "  Untracked files: $UNTRACKED"
+        echo ""
+        git status --short
+    else
+        echo "✓ No uncommitted changes"
+    fi
+
+    # Active issues (in-progress) using yq
+    echo ""
+    echo "Active Issues:"
+    echo "-------------"
+
+    for file in *.md; do
+        [ -f "$file" ] || continue
+        [ "$file" = "README.md" ] && continue
+
+        STATUS=$(yq --front-matter '.status // ""' "$file" 2>/dev/null)
+        if [ "$STATUS" = "in-progress" ]; then
+            NUM=$(echo "$file" | cut -d'_' -f1)
+            TITLE=$(yq --front-matter '.title // ""' "$file")
+            ASSIGNEE=$(yq --front-matter '.assignee // "unassigned"' "$file")
+            echo "  #${NUM}: ${TITLE}"
+            echo "         Assigned to: ${ASSIGNEE}"
+        fi
+    done
+
+    cd - > /dev/null
+}
+
+tissue_status_detailed
+```
+
+### Check for issues needing attention
+```bash
+# Find issues that need attention
+tissue_status_alerts() {
+    echo "Tissue Alerts"
+    echo "============="
+    echo ""
+
+    ISSUES_DIR="${1:-../issues}"
+    cd "$ISSUES_DIR" 2>/dev/null || return 1
+
+    # Stale issues (not updated in 30 days) using yq
+    echo "Stale Issues (>30 days):"
+    echo "------------------------"
+
+    THIRTY_DAYS_AGO=$(date -d "30 days ago" +%Y-%m-%d 2>/dev/null || \
+                      date -v-30d +%Y-%m-%d 2>/dev/null)
+
+    for file in *.md; do
+        [ -f "$file" ] || continue
+        [ "$file" = "README.md" ] && continue
+
+        UPDATED=$(yq --front-matter '.updated // .created // ""' "$file" 2>/dev/null | cut -d'T' -f1)
+        STATUS=$(yq --front-matter '.status // "unknown"' "$file" 2>/dev/null)
+
+        if [ "$STATUS" != "closed" ] && [ -n "$UPDATED" ] && [ "$UPDATED" \< "$THIRTY_DAYS_AGO" ]; then
+            NUM=$(echo "$file" | cut -d'_' -f1)
+            TITLE=$(yq --front-matter '.title // ""' "$file")
+            echo "  #${NUM}: ${TITLE} (last updated: ${UPDATED})"
+        fi
+    done
+
+    # Unassigned high priority issues using yq
+    echo ""
+    echo "Unassigned High Priority:"
+    echo "-------------------------"
+
+    for file in *.md; do
+        [ -f "$file" ] || continue
+        [ "$file" = "README.md" ] && continue
+
+        # Check for high priority and unassigned using yq
+        IS_URGENT=$(yq --front-matter '
+            (.priority == "high" or .priority == "critical") and
+            .status != "closed" and
+            (.assignee == null or .assignee == "" or .assignee == "unassigned")
+        ' "$file" 2>/dev/null)
+
+        if [ "$IS_URGENT" = "true" ]; then
+            NUM=$(echo "$file" | cut -d'_' -f1)
+            TITLE=$(yq --front-matter '.title // ""' "$file")
+            PRIORITY=$(yq --front-matter '.priority' "$file")
+            echo "  #${NUM}: ${TITLE} [${PRIORITY}]"
+        fi
+    done
+
+    # Blocked issues using yq
+    echo ""
+    echo "Blocked Issues:"
+    echo "---------------"
+
+    for file in *.md; do
+        [ -f "$file" ] || continue
+        [ "$file" = "README.md" ] && continue
+
+        STATUS=$(yq --front-matter '.status // ""' "$file" 2>/dev/null)
+        if [ "$STATUS" = "blocked" ]; then
+            NUM=$(echo "$file" | cut -d'_' -f1)
+            TITLE=$(yq --front-matter '.title // ""' "$file")
+            BLOCKER=$(yq --front-matter '.blocked_by // ""' "$file" 2>/dev/null)
+            echo "  #${NUM}: ${TITLE}"
+            [ -n "$BLOCKER" ] && echo "         Blocked by: ${BLOCKER}"
+        fi
+    done
+
+    cd - > /dev/null
+}
+
+tissue_status_alerts
+```
+
+### Compare local vs remote
+```bash
+# Detailed sync status
+tissue_sync_status() {
+    echo "Tissue Sync Status"
+    echo "=================="
+    echo ""
+
+    ISSUES_DIR="${1:-../issues}"
+    cd "$ISSUES_DIR" 2>/dev/null || return 1
+
+    # Fetch latest without merging
+    echo "Fetching remote..."
+    git fetch origin issues --quiet
+
+    # Get commit counts
+    LOCAL_COMMIT=$(git rev-parse issues)
+    REMOTE_COMMIT=$(git rev-parse origin/issues)
+
+    if [ "$LOCAL_COMMIT" = "$REMOTE_COMMIT" ]; then
+        echo "✓ Local and remote are in sync"
+        echo "  Latest commit: $(git log -1 --oneline)"
+    else
+        echo "Local vs Remote Differences:"
+        echo "----------------------------"
+
+        # Show commits in local but not remote
+        LOCAL_ONLY=$(git rev-list origin/issues..issues)
+        if [ -n "$LOCAL_ONLY" ]; then
+            echo ""
+            echo "Commits in local but not remote:"
+            git log --oneline origin/issues..issues
+        fi
+
+        # Show commits in remote but not local
+        REMOTE_ONLY=$(git rev-list issues..origin/issues)
+        if [ -n "$REMOTE_ONLY" ]; then
+            echo ""
+            echo "Commits in remote but not local:"
+            git log --oneline issues..origin/issues
+        fi
+
+        # Show file differences
+        echo ""
+        echo "File differences:"
+        git diff --stat origin/issues..issues
+    fi
+
+    cd - > /dev/null
+}
+
+tissue_sync_status
+```
+
+### Health check
+```bash
+# Complete health check
+tissue_health_check() {
+    echo "Tissue Health Check"
+    echo "==================="
+    echo ""
+
+    ERRORS=0
+    WARNINGS=0
+
+    # Check git repository
+    if ! git rev-parse --git-dir > /dev/null 2>&1; then
+        echo "✗ ERROR: Not in a git repository"
+        ((ERRORS++))
+        return 1
+    fi
+    echo "✓ In git repository"
+
+    # Check issues branch exists
+    if ! git show-ref --verify --quiet refs/heads/issues; then
+        echo "✗ ERROR: Issues branch does not exist"
+        echo "  Run: tissue init"
+        ((ERRORS++))
+    else
+        echo "✓ Issues branch exists"
+    fi
+
+    # Check worktree or access to issues
+    WORKTREE_PATH=$(git worktree list | grep "issues" | awk '{print $1}')
+    if [ -n "$WORKTREE_PATH" ]; then
+        if [ -d "$WORKTREE_PATH" ]; then
+            echo "✓ Worktree accessible at: $WORKTREE_PATH"
+        else
+            echo "✗ ERROR: Worktree path not accessible: $WORKTREE_PATH"
+            ((ERRORS++))
+        fi
+    elif [ -d "../issues" ]; then
+        echo "⚠ WARNING: Using issues directory without worktree"
+        echo "  Consider: tissue init --setup-worktree"
+        ((WARNINGS++))
+    else
+        echo "✗ ERROR: No access to issues directory"
+        ((ERRORS++))
+    fi
+
+    # Check for valid issue files
+    ISSUES_DIR="${WORKTREE_PATH:-../issues}"
+    if [ -d "$ISSUES_DIR" ]; then
+        INVALID_COUNT=0
+
+        for file in "$ISSUES_DIR"/*.md; do
+            [ -f "$file" ] || continue
+            [ "$(basename "$file")" = "README.md" ] && continue
+
+            # Check for required frontmatter fields using yq
+            TITLE=$(yq --front-matter '.title // null' "$file" 2>/dev/null)
+            if [ "$TITLE" = "null" ]; then
+                echo "⚠ WARNING: Missing title in $(basename "$file")"
+                ((WARNINGS++))
+                ((INVALID_COUNT++))
+            fi
+
+            STATUS=$(yq --front-matter '.status // null' "$file" 2>/dev/null)
+            if [ "$STATUS" = "null" ]; then
+                echo "⚠ WARNING: Missing status in $(basename "$file")"
+                ((WARNINGS++))
+                ((INVALID_COUNT++))
+            fi
+
+            # Validate status value
+            if [ "$STATUS" != "null" ]; then
+                VALID=$(echo "$STATUS" | yq '. as $s | ["open", "in-progress", "closed", "blocked"] | contains([$s])')
+                if [ "$VALID" != "true" ]; then
+                    echo "⚠ WARNING: Invalid status '$STATUS' in $(basename "$file")"
+                    ((WARNINGS++))
+                    ((INVALID_COUNT++))
+                fi
+            fi
+        done
+
+        if [ "$INVALID_COUNT" -eq 0 ]; then
+            echo "✓ All issue files valid"
+        fi
+    fi
+
+    # Check remote configuration
+    if git remote get-url origin > /dev/null 2>&1; then
+        echo "✓ Remote 'origin' configured"
+
+        # Check if remote has issues branch
+        if git ls-remote --heads origin issues > /dev/null 2>&1; then
+            echo "✓ Remote has issues branch"
+        else
+            echo "⚠ WARNING: Remote does not have issues branch"
+            echo "  Run: tissue push"
+            ((WARNINGS++))
+        fi
+    else
+        echo "⚠ WARNING: No remote 'origin' configured"
+        ((WARNINGS++))
+    fi
+
+    # Summary
+    echo ""
+    echo "Health Check Summary:"
+    echo "--------------------"
+    if [ "$ERRORS" -eq 0 ] && [ "$WARNINGS" -eq 0 ]; then
+        echo "✓ All checks passed!"
+    else
+        [ "$ERRORS" -gt 0 ] && echo "✗ Errors: $ERRORS"
+        [ "$WARNINGS" -gt 0 ] && echo "⚠ Warnings: $WARNINGS"
+    fi
+
+    return "$ERRORS"
+}
+
+tissue_health_check
+```
+
+## What tissue does for you:
+1. Provides comprehensive status overview
+2. Checks configuration integrity
+3. Shows sync status with remote
+4. Identifies issues needing attention
+5. Validates issue file structure
+6. Reports on recent activity
+7. Performs health checks
+8. Shows statistics and metrics
+9. Alerts on stale or blocked issues
+10. Manages worktree status
\ No newline at end of file
docs/update.md
@@ -0,0 +1,429 @@
+# tissue update / tissue close / tissue reopen
+
+Update issue metadata and status.
+
+## Tissue Commands
+```bash
+# Update status
+tissue update 42 --status "in-progress"
+tissue close 42
+tissue reopen 42
+
+# Update priority and tags
+tissue update 42 --priority critical
+tissue tag 42 "needs-review" "v2.0"
+tissue untag 42 "wontfix"
+
+# Add comment
+tissue comment 42 "Started working on this"
+```
+
+## Manual Workflow (bash + git)
+
+### Update issue status
+```bash
+# Change status field using yq
+update_status() {
+    ISSUE_NUM="$1"
+    NEW_STATUS="$2"
+    cd ../issues
+
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo "Issue #${ISSUE_NUM} not found"
+        return 1
+    fi
+
+    # Validate status
+    VALID=$(echo "$NEW_STATUS" | yq '. as $s | ["open", "in-progress", "closed", "blocked"] | contains([$s])')
+    if [ "$VALID" != "true" ]; then
+        echo "Invalid status: $NEW_STATUS"
+        return 1
+    fi
+
+    # Update status and timestamp using yq
+    yq --front-matter '
+        .status = "'"$NEW_STATUS"'" |
+        .updated = now
+    ' -i "$FILE"
+
+    # Commit the change
+    git add "$FILE"
+    git commit -m "Update issue #${ISSUE_NUM}: status -> ${NEW_STATUS}"
+
+    echo "Issue #${ISSUE_NUM} status updated to: ${NEW_STATUS}"
+}
+
+update_status 42 "in-progress"
+```
+
+### Close and reopen shortcuts
+```bash
+# Close issue
+close_issue() {
+    ISSUE_NUM="$1"
+    update_status "$ISSUE_NUM" "closed"
+
+    # Optionally add closing comment
+    if [ -n "$2" ]; then
+        add_comment "$ISSUE_NUM" "Closed: $2"
+    fi
+}
+
+# Reopen issue
+reopen_issue() {
+    ISSUE_NUM="$1"
+    update_status "$ISSUE_NUM" "open"
+
+    # Add reopening comment
+    add_comment "$ISSUE_NUM" "Reopened"
+}
+
+close_issue 42 "Fixed in commit abc123"
+reopen_issue 42
+```
+
+### Update priority
+```bash
+# Change priority field using yq
+update_priority() {
+    ISSUE_NUM="$1"
+    NEW_PRIORITY="$2"
+    cd ../issues
+
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo "Issue #${ISSUE_NUM} not found"
+        return 1
+    fi
+
+    # Validate priority using yq
+    VALID=$(echo "$NEW_PRIORITY" | yq '. as $p | ["low", "medium", "high", "critical"] | contains([$p])')
+    if [ "$VALID" != "true" ]; then
+        echo "Invalid priority. Use: low, medium, high, or critical"
+        return 1
+    fi
+
+    # Update priority and timestamp
+    yq --front-matter '
+        .priority = "'"$NEW_PRIORITY"'" |
+        .updated = now
+    ' -i "$FILE"
+
+    git add "$FILE"
+    git commit -m "Update issue #${ISSUE_NUM}: priority -> ${NEW_PRIORITY}"
+}
+
+update_priority 42 "critical"
+```
+
+### Add and remove tags
+```bash
+# Add tags to issue using yq
+add_tags() {
+    ISSUE_NUM="$1"
+    shift  # Remove first argument
+    NEW_TAGS="$@"
+    cd ../issues
+
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo "Issue #${ISSUE_NUM} not found"
+        return 1
+    fi
+
+    # Add each new tag using yq
+    for tag in $NEW_TAGS; do
+        yq --front-matter '.tags += ["'"$tag"'"]' -i "$FILE"
+    done
+
+    # Remove duplicates and update timestamp
+    yq --front-matter '
+        .tags |= unique |
+        .updated = now
+    ' -i "$FILE"
+
+    git add "$FILE"
+    git commit -m "Add tags to issue #${ISSUE_NUM}: ${NEW_TAGS}"
+}
+
+# Remove tags from issue using yq
+remove_tags() {
+    ISSUE_NUM="$1"
+    shift
+    REMOVE_TAGS="$@"
+    cd ../issues
+
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo "Issue #${ISSUE_NUM} not found"
+        return 1
+    fi
+
+    # Remove each tag using yq
+    for tag in $REMOVE_TAGS; do
+        yq --front-matter '.tags -= ["'"$tag"'"]' -i "$FILE"
+    done
+
+    # Update timestamp
+    yq --front-matter '.updated = now' -i "$FILE"
+
+    git add "$FILE"
+    git commit -m "Remove tags from issue #${ISSUE_NUM}: ${REMOVE_TAGS}"
+}
+
+add_tags 42 "needs-review" "v2.0"
+remove_tags 42 "wontfix"
+```
+
+### Add comments to issue
+```bash
+# Add timestamped comment to issue body
+add_comment() {
+    ISSUE_NUM="$1"
+    COMMENT="$2"
+    cd ../issues
+
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo "Issue #${ISSUE_NUM} not found"
+        return 1
+    fi
+
+    # Create comment with timestamp
+    TIMESTAMP=$(date -u +"%Y-%m-%d %H:%M:%S UTC")
+    AUTHOR=$(git config user.name)
+    COMMENT_BLOCK="
+
+### Log
+
+**[${TIMESTAMP}] ${AUTHOR}:**
+${COMMENT}"
+
+    # Append comment to file
+    if ! grep -q "^### Log" "$FILE"; then
+        # Add Log section if it doesn't exist
+        echo "$COMMENT_BLOCK" >> "$FILE"
+    else
+        # Add comment in Log section
+        echo "
+**[${TIMESTAMP}] ${AUTHOR}:**
+${COMMENT}" >> "$FILE"
+    fi
+
+    # Update the timestamp using yq
+    yq --front-matter '.updated = now' -i "$FILE"
+
+    git add "$FILE"
+    git commit -m "Add comment to issue #${ISSUE_NUM}"
+
+    echo "Comment added to issue #${ISSUE_NUM}"
+}
+
+add_comment 42 "Started working on this issue. Found the root cause in module X."
+```
+
+### Update assignee
+```bash
+# Assign issue to user using yq
+assign_issue() {
+    ISSUE_NUM="$1"
+    ASSIGNEE="$2"
+    cd ../issues
+
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo "Issue #${ISSUE_NUM} not found"
+        return 1
+    fi
+
+    # Update assignee field using yq
+    if [ -n "$ASSIGNEE" ]; then
+        yq --front-matter '
+            .assignee = "'"$ASSIGNEE"'" |
+            .updated = now
+        ' -i "$FILE"
+        COMMIT_MSG="Assign issue #${ISSUE_NUM} to ${ASSIGNEE}"
+    else
+        yq --front-matter '
+            .assignee = null |
+            .updated = now
+        ' -i "$FILE"
+        COMMIT_MSG="Unassign issue #${ISSUE_NUM}"
+    fi
+
+    git add "$FILE"
+    git commit -m "$COMMIT_MSG"
+}
+
+# Unassign issue
+unassign_issue() {
+    ISSUE_NUM="$1"
+    assign_issue "$ISSUE_NUM" ""
+}
+
+assign_issue 42 "@alice"
+unassign_issue 42
+```
+
+### Update multiple fields at once
+```bash
+# Update multiple fields in one operation using yq
+update_multiple() {
+    ISSUE_NUM="$1"
+    shift
+    cd ../issues
+
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo "Issue #${ISSUE_NUM} not found"
+        return 1
+    fi
+
+    # Build yq update expression
+    UPDATE_EXPR=".updated = now"
+    COMMIT_MSG="Update issue #${ISSUE_NUM}:"
+
+    while [ $# -gt 0 ]; do
+        FIELD="$1"
+        VALUE="$2"
+        shift 2
+
+        # Add to update expression
+        UPDATE_EXPR="${UPDATE_EXPR} | .${FIELD} = \"${VALUE}\""
+        COMMIT_MSG="${COMMIT_MSG} ${FIELD}=${VALUE}"
+    done
+
+    # Apply all updates at once
+    yq --front-matter "$UPDATE_EXPR" -i "$FILE"
+
+    git add "$FILE"
+    git commit -m "$COMMIT_MSG"
+}
+
+# Example: Update status, priority, and assignee
+update_multiple 42 status "in-progress" priority "high" assignee "alice"
+```
+
+### Bulk update operations
+```bash
+# Update multiple issues at once
+bulk_update() {
+    FIELD="$1"
+    VALUE="$2"
+    shift 2
+    ISSUE_NUMS="$@"
+
+    for num in $ISSUE_NUMS; do
+        case "$FIELD" in
+            status)
+                update_status "$num" "$VALUE"
+                ;;
+            priority)
+                update_priority "$num" "$VALUE"
+                ;;
+            assignee)
+                assign_issue "$num" "$VALUE"
+                ;;
+            *)
+                echo "Unknown field: $FIELD"
+                return 1
+                ;;
+        esac
+    done
+
+    echo "Bulk update completed for issues: $ISSUE_NUMS"
+}
+
+# Close multiple issues
+bulk_close() {
+    for num in "$@"; do
+        close_issue "$num"
+    done
+}
+
+bulk_update status "in-progress" 42 43 44
+bulk_close 45 46 47
+```
+
+### Update with validation
+```bash
+# Validate and update issue using yq
+safe_update() {
+    ISSUE_NUM="$1"
+    FIELD="$2"
+    VALUE="$3"
+    cd ../issues
+
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo "Issue #${ISSUE_NUM} not found"
+        return 1
+    fi
+
+    # Backup file before update
+    cp "$FILE" "${FILE}.bak"
+
+    # Validate and update based on field
+    case "$FIELD" in
+        status)
+            VALID=$(echo "$VALUE" | yq '. as $s | ["open", "in-progress", "closed", "blocked"] | contains([$s])')
+            if [ "$VALID" != "true" ]; then
+                echo "Invalid status: $VALUE"
+                rm "${FILE}.bak"
+                return 1
+            fi
+            yq --front-matter '.status = "'"$VALUE"'" | .updated = now' -i "$FILE"
+            ;;
+        priority)
+            VALID=$(echo "$VALUE" | yq '. as $p | ["low", "medium", "high", "critical"] | contains([$p])')
+            if [ "$VALID" != "true" ]; then
+                echo "Invalid priority: $VALUE"
+                rm "${FILE}.bak"
+                return 1
+            fi
+            yq --front-matter '.priority = "'"$VALUE"'" | .updated = now' -i "$FILE"
+            ;;
+        assignee)
+            yq --front-matter '.assignee = "'"$VALUE"'" | .updated = now' -i "$FILE"
+            ;;
+        *)
+            # Generic field update
+            yq --front-matter '.'"$FIELD"' = "'"$VALUE"'" | .updated = now' -i "$FILE"
+            ;;
+    esac
+
+    rm "${FILE}.bak"  # Remove backup on success
+
+    git add "$FILE"
+    git commit -m "Update issue #${ISSUE_NUM}: ${FIELD} -> ${VALUE}"
+}
+```
+
+## What tissue does for you:
+1. Validates field values before updating
+2. Automatically updates timestamps
+3. Maintains audit trail through git history
+4. Supports bulk operations efficiently
+5. Handles comment threading and formatting
+6. Provides shortcuts for common operations
+7. Ensures data integrity during updates
+8. Manages assignee references consistently
+9. Supports undo through git mechanisms
+10. Integrates with hooks for notifications
\ No newline at end of file
docs/view.md
@@ -0,0 +1,331 @@
+# tissue view (tissue [id])
+
+View a specific issue by ID or filename.
+
+## Tissue Commands
+```bash
+# View issue by ID (pretty table output)
+tissue 42
+tissue issues/042-memory_leak.md
+
+# View with different formats
+tissue 42 --format        # default pretty format
+tissue 42 --format json
+tissue 42 --raw           # raw markdown with frontmatter
+
+# Open in editor
+tissue 42 --edit
+tissue 42 -e
+tissue 42 --editor vim
+```
+
+## Manual Workflow (bash + git)
+
+### View issue by number
+```bash
+# Find and display issue by number
+view_issue() {
+    ISSUE_NUM="$1"
+    cd ../issues
+
+    # Pad number with zeros
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+
+    # Find the file
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo "Issue #${ISSUE_NUM} not found"
+        return 1
+    fi
+
+    # Display the file
+    cat "$FILE"
+}
+
+view_issue 42
+```
+
+### View issue with formatted output
+```bash
+# Pretty print issue using yq
+pretty_view_issue() {
+    ISSUE_NUM="$1"
+    cd ../issues
+
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo "Issue #${ISSUE_NUM} not found"
+        return 1
+    fi
+
+    # Extract frontmatter fields using yq
+    TITLE=$(yq --front-matter '.title // ""' "$FILE")
+    STATUS=$(yq --front-matter '.status // "unknown"' "$FILE")
+    PRIORITY=$(yq --front-matter '.priority // ""' "$FILE")
+    TAGS=$(yq --front-matter '.tags | join(", ") // ""' "$FILE")
+    CREATED=$(yq --front-matter '.created // ""' "$FILE")
+    UPDATED=$(yq --front-matter '.updated // ""' "$FILE")
+    AUTHOR=$(yq --front-matter '.author // ""' "$FILE")
+    ASSIGNEE=$(yq --front-matter '.assignee // "unassigned"' "$FILE")
+
+    # Extract body (content after frontmatter)
+    BODY=$(sed -n '/^---$/,/^---$/d; p' "$FILE")
+
+    # Display formatted output
+    echo "════════════════════════════════════════════════════════════════"
+    echo " Issue #${ISSUE_NUM}: ${TITLE}"
+    echo "════════════════════════════════════════════════════════════════"
+    echo ""
+    echo "Status:    ${STATUS}"
+    echo "Priority:  ${PRIORITY}"
+    echo "Tags:      ${TAGS}"
+    echo "Author:    ${AUTHOR}"
+    echo "Assignee:  ${ASSIGNEE:-unassigned}"
+    echo "Created:   ${CREATED}"
+    echo "Updated:   ${UPDATED}"
+    echo ""
+    echo "────────────────────────────────────────────────────────────────"
+    echo ""
+    echo "$BODY"
+}
+
+pretty_view_issue 42
+```
+
+### View as JSON
+```bash
+# Convert issue to JSON format using yq
+issue_to_json() {
+    ISSUE_NUM="$1"
+    cd ../issues
+
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo '{"error": "Issue not found"}'
+        return 1
+    fi
+
+    # Extract all frontmatter as JSON using yq
+    FRONTMATTER=$(yq --front-matter -o json '.' "$FILE")
+
+    # Extract body and escape for JSON
+    BODY=$(sed -n '/^---$/,/^---$/d; p' "$FILE" | \
+           jq -Rs '.')
+
+    # Combine frontmatter with additional fields
+    echo "$FRONTMATTER" | jq --arg num "$ISSUE_NUM" \
+                              --arg file "$FILE" \
+                              --argjson body "$BODY" '
+        . + {
+            "number": ($num | tonumber),
+            "file": $file,
+            "body": $body
+        }
+    '
+}
+
+issue_to_json 42 | jq .  # Pretty print with jq if available
+```
+
+### View raw markdown
+```bash
+# Show raw file with syntax highlighting (if available)
+view_raw() {
+    ISSUE_NUM="$1"
+    cd ../issues
+
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo "Issue #${ISSUE_NUM} not found"
+        return 1
+    fi
+
+    # Use bat for syntax highlighting if available
+    if command -v bat &> /dev/null; then
+        bat --style=full --language=markdown "$FILE"
+    elif command -v pygmentize &> /dev/null; then
+        pygmentize -l markdown "$FILE"
+    else
+        # Fallback to cat with line numbers
+        cat -n "$FILE"
+    fi
+}
+
+view_raw 42
+```
+
+### Open in editor
+```bash
+# Edit issue with yq timestamp update
+edit_issue() {
+    ISSUE_NUM="$1"
+    EDITOR="${2:-${EDITOR:-vim}}"
+    cd ../issues
+
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo "Issue #${ISSUE_NUM} not found"
+        return 1
+    fi
+
+    # Open in editor
+    $EDITOR "$FILE"
+
+    # Update the 'updated' timestamp after editing
+    yq --front-matter '.updated = now' -i "$FILE"
+
+    # Commit changes
+    git add "$FILE"
+    git commit -m "Update issue #${ISSUE_NUM}"
+}
+
+edit_issue 42 vim
+```
+
+### View issue by filename pattern
+```bash
+# Find and view by partial filename using yq
+view_by_pattern() {
+    PATTERN="$1"
+    cd ../issues
+
+    # Find matching files
+    FILES=$(ls *${PATTERN}*.md 2>/dev/null)
+
+    if [ -z "$FILES" ]; then
+        echo "No issues matching pattern: ${PATTERN}"
+        return 1
+    fi
+
+    # If multiple matches, list them
+    FILE_COUNT=$(echo "$FILES" | wc -l)
+    if [ "$FILE_COUNT" -gt 1 ]; then
+        echo "Multiple issues found:"
+        echo "$FILES" | while read f; do
+            TITLE=$(yq --front-matter '.title // ""' "$f" 2>/dev/null)
+            STATUS=$(yq --front-matter '.status // ""' "$f" 2>/dev/null)
+            echo "  ${f}: [${STATUS}] ${TITLE}"
+        done
+        return 1
+    fi
+
+    # Single match - display it
+    cat "$FILES"
+}
+
+view_by_pattern "memory_leak"
+```
+
+### View summary of multiple issues
+```bash
+# View multiple issues in summary format using yq
+view_multiple() {
+    cd ../issues
+
+    # Process each argument as issue number
+    for ISSUE_NUM in "$@"; do
+        PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+        FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+        if [ -z "$FILE" ]; then
+            echo "Issue #${ISSUE_NUM} not found"
+            continue
+        fi
+
+        # Extract key fields using yq
+        TITLE=$(yq --front-matter '.title' "$FILE")
+        STATUS=$(yq --front-matter '.status' "$FILE")
+        PRIORITY=$(yq --front-matter '.priority // ""' "$FILE")
+        ASSIGNEE=$(yq --front-matter '.assignee // "unassigned"' "$FILE")
+
+        echo "#${ISSUE_NUM}: ${TITLE}"
+        echo "  Status: ${STATUS} | Priority: ${PRIORITY} | Assignee: ${ASSIGNEE}"
+        echo ""
+    done
+}
+
+view_multiple 42 43 44
+```
+
+### View with pager for long issues
+```bash
+# View with less/more for pagination
+view_with_pager() {
+    ISSUE_NUM="$1"
+    cd ../issues
+
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo "Issue #${ISSUE_NUM} not found"
+        return 1
+    fi
+
+    # Use less if available, otherwise more
+    if command -v less &> /dev/null; then
+        less "$FILE"
+    elif command -v more &> /dev/null; then
+        more "$FILE"
+    else
+        cat "$FILE"
+    fi
+}
+```
+
+### View issue history with field changes
+```bash
+# Show git history with field diffs using yq
+view_issue_history() {
+    ISSUE_NUM="$1"
+    cd ../issues
+
+    PADDED_NUM=$(printf "%03d" "$ISSUE_NUM")
+    FILE=$(ls ${PADDED_NUM}_*.md 2>/dev/null | head -1)
+
+    if [ -z "$FILE" ]; then
+        echo "Issue #${ISSUE_NUM} not found"
+        return 1
+    fi
+
+    echo "History for issue #${ISSUE_NUM} (${FILE}):"
+    echo "────────────────────────────────────────────"
+
+    # Show current state
+    echo "Current state:"
+    echo "  Status: $(yq --front-matter '.status' "$FILE")"
+    echo "  Priority: $(yq --front-matter '.priority' "$FILE")"
+    echo "  Assignee: $(yq --front-matter '.assignee // "unassigned"' "$FILE")"
+    echo ""
+
+    # Show commit history
+    git log --oneline --follow "$FILE"
+
+    echo ""
+    echo "View full diff of a commit with:"
+    echo "  git show <commit-hash>"
+}
+
+view_issue_history 42
+```
+
+## What tissue does for you:
+1. Automatically finds issues by number or pattern
+2. Provides multiple output formats
+3. Handles editor integration seamlessly
+4. Updates timestamps automatically
+5. Validates issue structure before display
+6. Provides syntax highlighting
+7. Manages git operations after edits
+8. Shows issue history and changes
+9. Supports templates for consistent viewing
+10. Integrates with shell completion for issue IDs
\ No newline at end of file
README.md
@@ -0,0 +1,203 @@
+# Tissue CLI Project Overview
+
+## Project Name
+
+Tissue (Terminal Issues)
+
+## General Idea
+
+Tissue is a command-line tool designed to manage issue tracking directly within a Git repository, using Markdown files with front matter. Instead of relying on external forges like GitHub or GitLab, Tissue lets you keep all your issues in a dedicated Git branch right alongside your code.
+
+## Core Concept
+
+- **Issues as Markdown**: Each issue is a Markdown file with front matter (like YAML) for metadata and a body for the description.
+
+- **Dedicated Branch**: All issues live in a single "tissues" branch, which acts as the single source of truth for issue tracking.
+
+- **Worktree Integration**: The "tissues" branch can be managed via a Git worktree, ensuring the issues directory is always synced and easily accessible.
+
+
+## Implementation Details
+
+- **Front Matter Structure**: Each issue file starts with a YAML front matter block that includes fields like status, priority, tags, and assignee.
+
+- **CLI Commands**: Tissue provides commands to list issues, sort them by category or status, and generate an index file (like a README) summarizing current issues.
+
+- **Editing and Updates**: When editing issues, the CLI can use the worktree to ensure the user is always working off the latest "tissues" branch version. Direct edits are made there and then merged as needed.
+
+
+## Collaboration
+
+- **Multiple Users**: Multiple people can work on the "tissues" branch, and normal Git merge conflict resolution applies if more than one person edits the same issue file.
+
+
+## Usage Examples
+
+### Initial Setup
+
+```bash
+# Initialize tissue in current git repository
+tissue init
+
+# Clone a repository and set up tissue branch
+tissue clone https://github.com/user/repo.git
+tissue init --setup-worktree
+```
+
+### Creating Issues
+
+```bash
+# Create a new issue interactively (prompted metadata, skipable)
+tissue new
+
+# Create issue with inline options (skips prompts)
+tissue new --title "Fix memory leak in parser" --priority high --tags "bug,performance"
+
+# Push to remote on completion of edit
+tissue new --push
+
+# Quick issue creation, editor doesn't open, --push is set to true by default with --quick
+tissue new "TODO: Add unit tests for auth module" --quick
+```
+
+### Listing and Viewing Issues
+
+```bash
+# List all issues (default view)
+tissue list
+tissue ls
+
+# Filter by status
+tissue list --status open
+tissue list --status closed
+tissue list --status "in-progress"
+
+# Filter by multiple criteria
+tissue list --priority high --tags bug 
+
+# Sort issues
+tissue list --sort priority
+tissue list --sort updated --reverse
+
+# Search issues
+tissue search "memory leak"
+tissue search "auth" --in title,tags
+tissue grep "TODO|FIXME" --body-only
+```
+
+### Viewing Specific Issues
+
+```bash
+# View issue by ID, a pretty, table output
+tissue 42
+tissue issues/042-bad_id.md
+
+# View with different output formats
+tissue 42 --format  #(default)
+tissue 42 --format json
+tissue 42 --raw  # Show raw md file with frontmatter
+
+# Open issue in editor
+tissue 42 --edit
+tissue 42 -e
+tissue 42 --editor vim
+```
+
+### Updating Issues
+
+```bash
+# Update issue status
+tissue update 42 --status "in-progress"
+tissue close 42
+tissue reopen 42
+
+# Update priority and tags
+tissue update 42 --priority critical
+tissue tag 42 "needs-review" "v2.0"
+tissue untag 42 "wontfix"
+
+# Add a comment (timestamped into ### Log section)
+tissue comment 42 "Started working on this"
+
+# Start working on an issue
+tissue start 42  # Sets status to in-progress, checks out appropriate branch
+```
+```
+
+### Working with Branches and Worktrees
+
+```bash
+# Show tissue branch details and status
+tissue status
+
+# Sync with remote
+tissue pull # alias for git update of issues working tree branch
+tissue push # alias for git push into issues working tree branch 
+```
+
+### Generating Reports
+
+```bash
+# Generate index/summary
+tissue index # default output to issues/README.md
+tissue index --output ISSUES.md
+```
+
+### Import/Export
+
+```bash
+# Import from GitHub Issues
+tissue import github --repo user/repo
+tissue import github --repo user/repo --token $GITHUB_TOKEN
+
+# Import from json
+tissue import issues.json
+
+# Export to different formats
+tissue export > issues.json
+```
+
+### Configuration
+
+```bash
+# Configure tissue in this project
+tissue config editor.default "nvim"
+# Configure tissue global
+tissue config --global editor.default "nvim"
+
+# List configuration
+tissue config --list
+tissue config --list --global
+```
+
+### Advanced Operations
+
+
+### Integration Examples
+
+See [Linking a pull request to an issue using a keyword](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) for the full list of commit keywords
+
+```bash
+# Git commit integration
+git commit -m 'closes #42'
+# parses history for keyword issue phrases
+# checks for un-matchinig issues in tissue
+# proposes updates to the issues 
+# could be a good git hook (?)
+tissue history 
+
+# CLI integration
+tissue complete --shell bash > tissue-completion.bash
+```
+
+
+### Utility Commands
+
+```bash
+# Help and documentation
+tissue --help
+tissue new --help
+
+# Version and updates
+tissue version
+```