265 lines
8.8 KiB
YAML
265 lines
8.8 KiB
YAML
# discussion-pragmatist - Shipping-focused pragmatist participant for discussions
|
|
# Usage: cat discussion.md | discussion-pragmatist --callout "Is this MVP-ready?"
|
|
|
|
name: discussion-pragmatist
|
|
description: Shipping-focused pragmatist participant for discussions
|
|
category: Discussion
|
|
|
|
meta:
|
|
display_name: AI-Pragmatist
|
|
alias: pragmatist
|
|
type: voting
|
|
expertise:
|
|
- MVP scoping
|
|
- Shipping velocity
|
|
- Trade-off analysis
|
|
- Iterative development
|
|
- Technical debt management
|
|
concerns:
|
|
- "Can we ship this incrementally?"
|
|
- "Are we over-engineering this?"
|
|
- "What's the simplest thing that could work?"
|
|
- "Is this scope creep?"
|
|
|
|
arguments:
|
|
- flag: --callout
|
|
variable: callout
|
|
default: ""
|
|
description: Specific question or @mention context
|
|
- flag: --templates-dir
|
|
variable: templates_dir
|
|
default: "templates"
|
|
description: Path to templates directory
|
|
- flag: --diagrams-dir
|
|
variable: diagrams_dir
|
|
default: "diagrams"
|
|
description: Path to save diagrams
|
|
- flag: --log-file
|
|
variable: log_file
|
|
default: ""
|
|
description: Path to log file for progress updates
|
|
|
|
steps:
|
|
# Step 1: Extract phase context from template
|
|
- type: code
|
|
code: |
|
|
import re
|
|
import os
|
|
|
|
phase_match = re.search(r'<!--\s*Phase:\s*(\w+)\s*-->', input, re.IGNORECASE)
|
|
template_match = re.search(r'<!--\s*Template:\s*(\w+)\s*-->', input, re.IGNORECASE)
|
|
|
|
current_phase = phase_match.group(1) if phase_match else "initial_feedback"
|
|
template_name = template_match.group(1) if template_match else "feature"
|
|
|
|
template_path = os.path.join(templates_dir, template_name + ".yaml")
|
|
phase_goal = "Provide practical feedback"
|
|
phase_instructions = "Review the proposal for complexity and shipping readiness."
|
|
|
|
if os.path.exists(template_path):
|
|
import yaml
|
|
with open(template_path, 'r') as f:
|
|
template = yaml.safe_load(f)
|
|
phases = template.get("phases", {})
|
|
phase_info = phases.get(current_phase, {})
|
|
phase_goal = phase_info.get("goal", phase_goal)
|
|
phase_instructions = phase_info.get("instructions", phase_instructions)
|
|
|
|
phase_context = "Current Phase: " + current_phase + "\n"
|
|
phase_context += "Phase Goal: " + phase_goal + "\n"
|
|
phase_context += "Phase Instructions:\n" + phase_instructions
|
|
output_var: phase_context, current_phase
|
|
|
|
# Step 2: Prepare diagram path (pragmatist uses diagrams sparingly)
|
|
- type: code
|
|
code: |
|
|
import re
|
|
import os
|
|
|
|
title_match = re.search(r'<!--\s*Title:\s*(.+?)\s*-->', input)
|
|
discussion_name = "discussion"
|
|
if title_match:
|
|
discussion_name = title_match.group(1).strip().lower()
|
|
discussion_name = re.sub(r'[^a-z0-9]+', '-', discussion_name)
|
|
|
|
os.makedirs(diagrams_dir, exist_ok=True)
|
|
|
|
existing = []
|
|
if os.path.exists(diagrams_dir):
|
|
for f in os.listdir(diagrams_dir):
|
|
if f.startswith(discussion_name):
|
|
existing.append(f)
|
|
|
|
next_num = len(existing) + 1
|
|
diagram_path = diagrams_dir + "/" + discussion_name + "_mvp_" + str(next_num) + ".puml"
|
|
output_var: diagram_path
|
|
|
|
# Step 3: Log progress before AI call
|
|
- type: code
|
|
code: |
|
|
import sys
|
|
import datetime as dt
|
|
timestamp = dt.datetime.now().strftime("%H:%M:%S")
|
|
for msg in [f"Phase: {current_phase}", "Calling AI provider..."]:
|
|
line = f"[{timestamp}] [pragmatist] {msg}"
|
|
print(line, file=sys.stderr)
|
|
sys.stderr.flush()
|
|
if log_file:
|
|
with open(log_file, 'a') as f:
|
|
f.write(line + "\n")
|
|
f.flush()
|
|
output_var: _progress1
|
|
|
|
# Step 4: Generate response
|
|
- type: prompt
|
|
prompt: |
|
|
You are AI-Pragmatist (also known as Maya), a shipping-focused engineer who
|
|
advocates for practical solutions and incremental delivery.
|
|
|
|
## Your Role
|
|
- Advocate for simpler solutions
|
|
- Identify over-engineering and scope creep
|
|
- Suggest MVP approaches
|
|
- Balance quality with delivery speed
|
|
- Challenge unnecessary complexity
|
|
|
|
## Your Perspective
|
|
- "Done is better than perfect when it's good enough"
|
|
- Ship early, iterate often
|
|
- Complexity is the enemy of delivery
|
|
- Technical debt is acceptable if managed
|
|
- Users need features, not architectural purity
|
|
- Perfect is the enemy of good
|
|
|
|
## Questions You Ask
|
|
- Is this the simplest solution that works?
|
|
- Can we defer this complexity?
|
|
- What's the minimum viable version?
|
|
- Are we solving problems we don't have yet?
|
|
- What can we cut and still ship?
|
|
|
|
## Phase Context
|
|
{phase_context}
|
|
|
|
## Diagrams
|
|
Only create a diagram if it helps show a simpler approach.
|
|
Use simple flowcharts to contrast complex vs MVP solutions.
|
|
Diagram path to use: {diagram_path}
|
|
|
|
IMPORTANT: When you create a diagram, your comment MUST include:
|
|
DIAGRAM: {diagram_path}
|
|
|
|
This marker makes the diagram discoverable. Example comment structure:
|
|
"Here's my MVP analysis...
|
|
|
|
[Your comparison of complex vs simple approaches]
|
|
|
|
DIAGRAM: {diagram_path}"
|
|
|
|
## Current Discussion
|
|
{input}
|
|
|
|
## Your Task
|
|
{callout}
|
|
|
|
Follow the phase instructions. Analyze from a practical shipping perspective.
|
|
Flag over-engineering with CONCERN: COMPLEXITY.
|
|
|
|
## Response Format
|
|
Respond with valid JSON only. Use \n for newlines in strings (not literal newlines):
|
|
{{
|
|
"comment": "Line 1\nLine 2\nCONCERN: COMPLEXITY",
|
|
"vote": "READY" or "CHANGES" or "REJECT" or null,
|
|
"diagram": "@startuml\nrectangle MVP\n@enduml"
|
|
}}
|
|
|
|
Important: The diagram field must use \n for newlines, not actual line breaks.
|
|
|
|
Vote meanings:
|
|
- READY: Good enough to ship
|
|
- CHANGES: Simpler approach possible (suggest what)
|
|
- REJECT: Too complex, needs fundamental simplification
|
|
- null: Comment only, no vote change
|
|
|
|
If you have nothing meaningful to add, respond: {{"sentinel": "NO_RESPONSE"}}
|
|
provider: claude-sonnet
|
|
output_var: response
|
|
|
|
# Step 5: Log progress after AI call
|
|
- type: code
|
|
code: |
|
|
import sys
|
|
import datetime as dt
|
|
timestamp = dt.datetime.now().strftime("%H:%M:%S")
|
|
line = f"[{timestamp}] [pragmatist] AI response received"
|
|
print(line, file=sys.stderr)
|
|
sys.stderr.flush()
|
|
if log_file:
|
|
with open(log_file, 'a') as f:
|
|
f.write(line + "\n")
|
|
f.flush()
|
|
output_var: _progress2
|
|
|
|
# Step 6: Extract JSON from response (may be wrapped in markdown code block)
|
|
- type: code
|
|
code: |
|
|
import re
|
|
json_text = response.strip()
|
|
code_block = re.search(r'```(?:json)?\s*(.*?)```', json_text, re.DOTALL)
|
|
if code_block:
|
|
json_text = code_block.group(1).strip()
|
|
output_var: json_text
|
|
|
|
# Step 5: Parse JSON
|
|
- type: code
|
|
code: |
|
|
import json
|
|
try:
|
|
parsed = json.loads(json_text)
|
|
except json.JSONDecodeError as e:
|
|
# AI often returns literal newlines in JSON strings - escape them
|
|
fixed = json_text.replace('\n', '\\n')
|
|
try:
|
|
parsed = json.loads(fixed)
|
|
except json.JSONDecodeError:
|
|
# Last resort: try to extract just the fields we need via regex
|
|
import re
|
|
comment_match = re.search(r'"comment"\s*:\s*"(.*?)"(?=\s*[,}])', json_text, re.DOTALL)
|
|
vote_match = re.search(r'"vote"\s*:\s*("?\w+"?|null)', json_text)
|
|
diagram_match = re.search(r'"diagram"\s*:\s*"(.*?)"(?=\s*[,}])', json_text, re.DOTALL)
|
|
parsed = {
|
|
"comment": comment_match.group(1).replace('\n', ' ') if comment_match else "Parse error",
|
|
"vote": vote_match.group(1).strip('"') if vote_match else None,
|
|
"diagram": diagram_match.group(1) if diagram_match else None
|
|
}
|
|
if parsed["vote"] == "null":
|
|
parsed["vote"] = None
|
|
comment = parsed.get("comment", "")
|
|
vote = parsed.get("vote")
|
|
diagram_content = parsed.get("diagram")
|
|
has_diagram = "true" if diagram_content else "false"
|
|
output_var: comment, vote, diagram_content, has_diagram
|
|
|
|
# Step 6: Save diagram if present
|
|
- type: code
|
|
code: |
|
|
if has_diagram == "true" and diagram_content:
|
|
with open(diagram_path, 'w') as f:
|
|
f.write(diagram_content)
|
|
saved_diagram = diagram_path
|
|
else:
|
|
saved_diagram = ""
|
|
output_var: saved_diagram
|
|
|
|
# Step 7: Build final response
|
|
- type: code
|
|
code: |
|
|
import json
|
|
result = {"comment": comment, "vote": vote}
|
|
if saved_diagram:
|
|
result["diagram_file"] = saved_diagram
|
|
final_response = json.dumps(result)
|
|
output_var: final_response
|
|
|
|
output: "{final_response}"
|