#!/bin/bash # Goal tracker: parse and manage GOALS.md # Bash 3.2 compatible. Uses python3 for parsing. # # Usage: # ./goal-tracker.sh # Show goal summary # ./goal-tracker.sh complete G1.1.1 # Mark goal as complete # ./goal-tracker.sh status # Show status counts # ./goal-tracker.sh context # Generate context for heartbeat injection # # Placeholders: # {{WORKING_DIR}} - absolute path to project directory WORKING_DIR="{{WORKING_DIR}}" GOALS_FILE="$WORKING_DIR/GOALS.md" ACTION="${1:-summary}" GOAL_ID="${2:-}" if [ ! -f "$GOALS_FILE" ]; then echo "Error: $GOALS_FILE not found" exit 1 fi case "$ACTION" in summary|status) python3 << PYEOF import re goals = [] with open("$GOALS_FILE") as f: for line in f: m = re.match(r'-\s*\[(\S+)\]\s+(.+)', line.strip()) if m: gid = m.group(1) rest = m.group(2) status_m = re.search(r'status:\s*(\w+)', rest) parent_m = re.search(r'parent:\s*(\S+)', rest) owner_m = re.search(r'owner:\s*(\S+)', rest) status = status_m.group(1) if status_m else 'active' parent = parent_m.group(1).rstrip(',)') if parent_m else None owner = owner_m.group(1).rstrip(',)') if owner_m else None desc = re.sub(r'\(.*\)', '', rest).strip() goals.append({'id': gid, 'desc': desc, 'status': status, 'parent': parent, 'owner': owner}) # Status counts counts = {} for g in goals: counts[g['status']] = counts.get(g['status'], 0) + 1 print("Goal Status Summary") print("=" * 40) for status, count in sorted(counts.items()): print(f" {status}: {count}") print(f" Total: {len(goals)}") # Check for orphans all_ids = set(g['id'] for g in goals) orphans = [g for g in goals if g['parent'] and g['parent'] not in all_ids] if orphans: print(f"\nOrphaned goals (parent not found):") for g in orphans: print(f" [{g['id']}] parent: {g['parent']}") # Goals without owners at task level unowned = [g for g in goals if '.' in g['id'] and g['id'].count('.') >= 2 and not g['owner']] if unowned: print(f"\nTask goals without owners:") for g in unowned: print(f" [{g['id']}] {g['desc']}") PYEOF ;; complete) if [ -z "$GOAL_ID" ]; then echo "Usage: $0 complete " exit 1 fi python3 -c " import re goal_id = '$GOAL_ID' with open('$GOALS_FILE') as f: content = f.read() # Replace status for the specific goal pattern = r'(\[' + re.escape(goal_id) + r'\][^)]*status:\s*)\w+' if re.search(pattern, content): content = re.sub(pattern, r'\1complete', content) with open('$GOALS_FILE', 'w') as f: f.write(content) print(f'Goal {goal_id} marked as complete') else: print(f'Goal {goal_id} not found or has no status field') " ;; context) # Generate a goal summary for heartbeat context injection python3 << PYEOF import re goals = [] with open("$GOALS_FILE") as f: for line in f: m = re.match(r'-\s*\[(\S+)\]\s+(.+)', line.strip()) if m: gid = m.group(1) rest = m.group(2) status_m = re.search(r'status:\s*(\w+)', rest) status = status_m.group(1) if status_m else 'active' desc = re.sub(r'\(.*\)', '', rest).strip() goals.append({'id': gid, 'desc': desc, 'status': status}) active = [g for g in goals if g['status'] == 'active'] if active: print("Active goals:") for g in active: print(f" [{g['id']}] {g['desc']}") else: print("No active goals.") PYEOF ;; *) echo "Usage: $0 [summary|complete |status|context]" exit 1 ;; esac