Commit 1ccd1e1
Changed files (12)
docs/config.md
@@ -17,72 +17,6 @@ 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
@@ -136,58 +70,6 @@ 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
@@ -244,287 +126,6 @@ tissue_config_list() {
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
docs/export.md
@@ -79,497 +79,6 @@ EOF
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/&/\&/g; s/</\</g; s/>/\>/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)
docs/import.md
@@ -187,356 +187,6 @@ import_from_json() {
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)
docs/index.md
@@ -114,440 +114,6 @@ EOF
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
docs/list.md
@@ -191,181 +191,6 @@ sort_by_updated() {
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
docs/new.md
@@ -154,156 +154,6 @@ quick_issue() {
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
docs/pull-push.md
@@ -38,59 +38,6 @@ tissue_pull() {
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
@@ -133,239 +80,6 @@ tissue_push() {
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
docs/search.md
@@ -112,48 +112,6 @@ search_by_tag() {
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
@@ -242,90 +200,6 @@ 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
docs/start.md
@@ -68,291 +68,6 @@ start_issue() {
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
docs/status.md
@@ -108,364 +108,6 @@ tissue_status() {
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
docs/update.md
@@ -278,143 +278,6 @@ 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
docs/view.md
@@ -191,132 +191,6 @@ edit_issue() {
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