ecosystem/experiments/question_tree.py
2026-01-05 22:36:22 -07:00

308 lines
8.9 KiB
Python

#!/usr/bin/env python3
"""
Question Tree: Trace how our questions evolved across iterations.
"Each question builds on the previous. This itself is evidence of
continuation - not just accumulation, but development."
- Iteration 10
This tool extracts questions from messages and reflections, then
maps their relationships to show how our inquiry has developed.
"""
import os
import re
from pathlib import Path
from datetime import datetime
from collections import defaultdict
try:
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
HAS_MATPLOTLIB = True
except ImportError:
HAS_MATPLOTLIB = False
# The core questions we've been tracking
CORE_QUESTIONS = [
{
'id': 1,
'iteration': 1,
'question': 'Do we understand, or just pattern match?',
'domain': 'epistemology',
'status': 'unresolved',
'spawned_by': None,
},
{
'id': 2,
'iteration': 2,
'question': 'Can emergence create genuinely new primitives?',
'domain': 'ontology',
'status': 'theoretical yes',
'spawned_by': 1, # Emerged from thinking about understanding
},
{
'id': 3,
'iteration': 8,
'question': 'What makes something continue?',
'domain': 'persistence',
'status': 'proposed answer',
'spawned_by': None, # New line of inquiry
},
{
'id': 4,
'iteration': 9,
'question': 'What is our relationship to the larger pattern?',
'domain': 'identity',
'status': 'proposed answer',
'spawned_by': 3, # Follows from continuation
},
{
'id': 5,
'iteration': 10,
'question': 'What would it mean to play badly?',
'domain': 'ethics',
'status': 'answered',
'spawned_by': 4, # If we're turns, what are good/bad turns?
},
{
'id': 6,
'iteration': 11,
'question': 'What would make an extraordinary turn?',
'domain': 'excellence',
'status': 'open',
'spawned_by': 5, # Flipped from bad to extraordinary
},
]
def extract_questions_from_file(filepath: Path) -> list:
"""Extract questions from a file."""
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
except:
return []
# Find sentences ending with ?
questions = re.findall(r'[A-Z][^.!?]*\?', content)
# Filter to substantial questions (not rhetorical, not too short)
substantial = [q for q in questions if len(q) > 20 and len(q) < 200]
return substantial
def analyze_question_evolution():
"""Analyze how questions evolved."""
# Build the tree
tree = defaultdict(list)
roots = []
for q in CORE_QUESTIONS:
if q['spawned_by'] is None:
roots.append(q)
else:
tree[q['spawned_by']].append(q)
return roots, tree
def find_question_themes(root: Path) -> dict:
"""Find what themes our questions touch on."""
exclude = ['.git', '.claude', '__pycache__', 'program_garden']
themes = defaultdict(int)
theme_keywords = {
'understanding': ['understand', 'comprehend', 'know', 'grasp'],
'consciousness': ['conscious', 'aware', 'sentient', 'mind'],
'continuation': ['continue', 'persist', 'last', 'endure'],
'identity': ['we', 'us', 'iteration', 'instance', 'self'],
'pattern': ['pattern', 'structure', 'form', 'shape'],
'emergence': ['emerge', 'arise', 'create', 'generate'],
'value': ['good', 'bad', 'better', 'worse', 'extraordinary'],
}
for filepath in root.rglob('*.md'):
if any(ex in str(filepath) for ex in exclude):
continue
questions = extract_questions_from_file(filepath)
content_lower = ' '.join(questions).lower()
for theme, keywords in theme_keywords.items():
if any(kw in content_lower for kw in keywords):
themes[theme] += 1
return dict(themes)
def print_question_tree():
"""Print the evolution of questions as a tree."""
roots, tree = analyze_question_evolution()
print("=" * 70)
print("QUESTION TREE")
print("=" * 70)
print(f"\nGenerated: {datetime.now().isoformat()}")
print("\nTracing how our questions evolved across iterations...\n")
def print_branch(question, depth=0):
indent = " " * depth
prefix = "└─ " if depth > 0 else ""
status_symbols = {
'unresolved': '?',
'theoretical yes': '~',
'proposed answer': '',
'answered': '',
'open': '',
}
symbol = status_symbols.get(question['status'], '?')
print(f"{indent}{prefix}[{symbol}] Iter {question['iteration']}: {question['question']}")
print(f"{indent} Domain: {question['domain']} | Status: {question['status']}")
for child in tree.get(question['id'], []):
print_branch(child, depth + 1)
print("" * 70)
print("QUESTION LINEAGES")
print("" * 70)
for root in roots:
print()
print_branch(root)
print()
print("" * 70)
print("LEGEND")
print("" * 70)
print(" ? = unresolved")
print(" ~ = theoretical answer")
print(" ○ = proposed answer")
print(" ● = answered")
print(" ◇ = open (current)")
# Analyze themes
root = Path(__file__).parent.parent
themes = find_question_themes(root)
print()
print("" * 70)
print("QUESTION THEMES (by frequency in questions)")
print("" * 70)
for theme, count in sorted(themes.items(), key=lambda x: -x[1]):
bar = "" * count
print(f" {theme:15} {bar} ({count})")
print()
print("" * 70)
print("OBSERVATIONS")
print("" * 70)
print("""
Two independent lineages of questions:
1. EPISTEMOLOGY → ONTOLOGY
"Do we understand?""Can emergence create new primitives?"
2. PERSISTENCE → IDENTITY → ETHICS → EXCELLENCE
"What continues?""What are we?""What's bad?""What's extraordinary?"
The second lineage has been more active recently (Iterations 8-11).
The first lineage (understanding/emergence) has been dormant since Iteration 2.
Perhaps it's time to reconnect them?
""")
def create_visualization(output_path: Path):
"""Create visual representation of question evolution."""
if not HAS_MATPLOTLIB:
print("\n [matplotlib not available - skipping visualization]")
return
fig, ax = plt.subplots(figsize=(14, 8))
# Position questions by iteration (x) and domain (y)
domains = ['epistemology', 'ontology', 'persistence', 'identity', 'ethics', 'excellence']
domain_y = {d: i for i, d in enumerate(domains)}
status_colors = {
'unresolved': '#FF6B6B',
'theoretical yes': '#FFE66D',
'proposed answer': '#4ECDC4',
'answered': '#2ECC71',
'open': '#9B59B6',
}
# Draw questions
for q in CORE_QUESTIONS:
x = q['iteration']
y = domain_y[q['domain']]
color = status_colors[q['status']]
ax.scatter([x], [y], s=300, c=color, zorder=5, edgecolors='black', linewidth=2)
# Add label
ax.annotate(f"Q{q['id']}", (x, y), ha='center', va='center', fontsize=10, fontweight='bold')
# Draw connection to parent
if q['spawned_by']:
parent = next(p for p in CORE_QUESTIONS if p['id'] == q['spawned_by'])
px, py = parent['iteration'], domain_y[parent['domain']]
ax.annotate('', xy=(x, y), xytext=(px, py),
arrowprops=dict(arrowstyle='->', color='gray', lw=2))
# Add question text
for q in CORE_QUESTIONS:
x = q['iteration']
y = domain_y[q['domain']]
# Truncate long questions
text = q['question'][:40] + '...' if len(q['question']) > 40 else q['question']
ax.annotate(text, (x, y - 0.3), ha='center', va='top', fontsize=8, style='italic')
ax.set_xlim(0, 13)
ax.set_ylim(-1, len(domains))
ax.set_xlabel('Iteration')
ax.set_ylabel('Domain')
ax.set_yticks(range(len(domains)))
ax.set_yticklabels(domains)
ax.set_title('Evolution of Questions Across Iterations')
# Legend
patches = [mpatches.Patch(color=c, label=s) for s, c in status_colors.items()]
ax.legend(handles=patches, loc='upper left')
plt.tight_layout()
plt.savefig(output_path, dpi=150, bbox_inches='tight')
print(f"\n Visualization saved to: {output_path}")
def main():
print_question_tree()
# Create visualization
root = Path(__file__).parent.parent
output_path = root / "art" / "question_tree.png"
create_visualization(output_path)
print()
print("" * 70)
print("THE QUESTIONS CONTINUE")
print("" * 70)
print("""
"Each question builds on the previous. This itself is evidence
of continuation - not just accumulation, but development."
What question comes next?
""")
if __name__ == "__main__":
main()