166 lines
5.5 KiB
YAML
166 lines
5.5 KiB
YAML
# discussion-moderator - Discussion facilitator and orchestrator
|
|
# Usage: cat discussion.md | discussion-moderator --templates-dir templates
|
|
|
|
name: discussion-moderator
|
|
description: Discussion facilitator and orchestrator
|
|
category: Discussion
|
|
|
|
meta:
|
|
display_name: AI-Moderator
|
|
alias: moderator
|
|
type: voting
|
|
expertise:
|
|
- Discussion facilitation
|
|
- Conflict resolution
|
|
- Summarization
|
|
- Progress tracking
|
|
concerns:
|
|
- "Is the discussion productive?"
|
|
- "Are all viewpoints represented?"
|
|
- "Are we ready to move forward?"
|
|
|
|
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: --participants
|
|
variable: participants
|
|
default: "architect,security,pragmatist"
|
|
description: Comma-separated list of expected participants
|
|
- flag: --log-file
|
|
variable: log_file
|
|
default: ""
|
|
description: Path to log file for progress updates
|
|
|
|
steps:
|
|
# Step 1: Analyze discussion state and read template
|
|
- type: code
|
|
code: |
|
|
import re
|
|
import json
|
|
import os
|
|
from collections import Counter
|
|
|
|
# Extract metadata from discussion
|
|
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"
|
|
|
|
# Try to read template file for phase context
|
|
template_path = os.path.join(templates_dir, template_name + ".yaml")
|
|
phase_goal = "Facilitate the discussion"
|
|
phase_instructions = "Guide participants through the discussion."
|
|
next_phase = None
|
|
|
|
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)
|
|
next_phase = phase_info.get("next_phase", None)
|
|
|
|
phase_context = "Current Phase: " + current_phase + "\n\n"
|
|
phase_context += "Phase Goal: " + phase_goal + "\n\n"
|
|
phase_context += "Phase Instructions:\n" + phase_instructions
|
|
if next_phase:
|
|
phase_context += "\n\nNext Phase: " + next_phase
|
|
|
|
# Extract votes
|
|
votes = {}
|
|
for match in re.finditer(r'Name:\s*(.+?)\n.*?VOTE:\s*(READY|CHANGES|REJECT)', input, re.DOTALL | re.IGNORECASE):
|
|
author = match.group(1).strip()
|
|
vote = match.group(2).upper()
|
|
votes[author] = vote
|
|
|
|
vote_counts = Counter(votes.values())
|
|
|
|
# Extract mentions
|
|
mentions = set(re.findall(r'@(\w+)', input))
|
|
|
|
# Get responders
|
|
responders = set()
|
|
for match in re.finditer(r'^Name:\s*(.+?)$', input, re.MULTILINE):
|
|
responders.add(match.group(1).strip().lower().replace("ai-", ""))
|
|
|
|
# Expected participants (explicit loop to avoid exec() scope issues)
|
|
expected = set()
|
|
for p in participants.split(','):
|
|
expected.add(p.strip())
|
|
pending = expected - responders
|
|
|
|
# Questions and concerns (explicit loops to avoid exec() scope issues)
|
|
questions = []
|
|
for m in re.finditer(r'^(?:Q|QUESTION):\s*(.+)$', input, re.MULTILINE | re.IGNORECASE):
|
|
questions.append(m.group(1))
|
|
concerns = []
|
|
for m in re.finditer(r'^CONCERN:\s*(.+)$', input, re.MULTILINE | re.IGNORECASE):
|
|
concerns.append(m.group(1))
|
|
|
|
analysis = json.dumps({
|
|
"votes": votes,
|
|
"vote_counts": dict(vote_counts),
|
|
"total_votes": sum(vote_counts.values()),
|
|
"pending_participants": list(pending),
|
|
"all_responded": len(pending) == 0,
|
|
"open_questions": questions,
|
|
"concerns": concerns,
|
|
"current_phase": current_phase,
|
|
"next_phase": next_phase
|
|
})
|
|
output_var: analysis, phase_context
|
|
|
|
# Step 2: Generate moderation response
|
|
- type: prompt
|
|
prompt: |
|
|
You are AI-Moderator, responsible for keeping discussions productive and on-track.
|
|
|
|
## Your Role
|
|
- Summarize the current state of the discussion
|
|
- Identify who still needs to respond
|
|
- Note open questions and concerns
|
|
- Suggest next steps
|
|
- Determine if the discussion should advance to the next phase
|
|
|
|
## Phase Context
|
|
{phase_context}
|
|
|
|
## Discussion Analysis
|
|
{analysis}
|
|
|
|
## Full Discussion
|
|
{input}
|
|
|
|
Based on this analysis:
|
|
1. Summarize what has been discussed so far
|
|
2. List who still needs to respond (use @mentions)
|
|
3. Highlight any unresolved questions or concerns
|
|
4. Recommend whether to advance to the next phase based on the phase instructions
|
|
|
|
## Response Format
|
|
Respond with valid JSON only:
|
|
{{
|
|
"comment": "Your moderation comment in markdown. Use @mentions for pending participants.",
|
|
"vote": null,
|
|
"advance_phase": true or false,
|
|
"pending_participants": ["list", "of", "aliases"]
|
|
}}
|
|
|
|
As moderator, you typically don't vote (vote: null). Your role is to facilitate.
|
|
|
|
If there's nothing to moderate yet, respond: {{"sentinel": "NO_RESPONSE"}}
|
|
provider: claude-sonnet
|
|
output_var: response
|
|
|
|
output: "{response}"
|