refactor(init_db): remove demo data file and implement structured seeding

- Delete `demo_data.json` replaced by structured logic for better modularity.
- Add support for seeding default agent types and new demo data structure.
- Ensure demo mode only executes when explicitly enabled (settings.DEMO_MODE).
- Enhance logging for improved debugging during DB initialization.
This commit is contained in:
2026-01-06 02:34:34 +01:00
parent 1dcf99ee38
commit 92782bcb05
4 changed files with 1096 additions and 424 deletions

View File

@@ -1,366 +0,0 @@
{
"organizations": [
{
"name": "Acme Corp",
"slug": "acme-corp",
"description": "A leading provider of coyote-catching equipment."
},
{
"name": "Globex Corporation",
"slug": "globex",
"description": "We own the East Coast."
},
{
"name": "Soylent Corp",
"slug": "soylent",
"description": "Making food for the future."
},
{
"name": "Initech",
"slug": "initech",
"description": "Software for the soul."
},
{
"name": "Umbrella Corporation",
"slug": "umbrella",
"description": "Our business is life itself."
},
{
"name": "Massive Dynamic",
"slug": "massive-dynamic",
"description": "What don't we do?"
}
],
"users": [
{
"email": "demo@example.com",
"password": "DemoPass1234!",
"first_name": "Demo",
"last_name": "User",
"is_superuser": false,
"organization_slug": "acme-corp",
"role": "member",
"is_active": true
},
{
"email": "alice@acme.com",
"password": "Demo123!",
"first_name": "Alice",
"last_name": "Smith",
"is_superuser": false,
"organization_slug": "acme-corp",
"role": "admin",
"is_active": true
},
{
"email": "bob@acme.com",
"password": "Demo123!",
"first_name": "Bob",
"last_name": "Jones",
"is_superuser": false,
"organization_slug": "acme-corp",
"role": "member",
"is_active": true
},
{
"email": "charlie@acme.com",
"password": "Demo123!",
"first_name": "Charlie",
"last_name": "Brown",
"is_superuser": false,
"organization_slug": "acme-corp",
"role": "member",
"is_active": false
},
{
"email": "diana@acme.com",
"password": "Demo123!",
"first_name": "Diana",
"last_name": "Prince",
"is_superuser": false,
"organization_slug": "acme-corp",
"role": "member",
"is_active": true
},
{
"email": "carol@globex.com",
"password": "Demo123!",
"first_name": "Carol",
"last_name": "Williams",
"is_superuser": false,
"organization_slug": "globex",
"role": "owner",
"is_active": true
},
{
"email": "dan@globex.com",
"password": "Demo123!",
"first_name": "Dan",
"last_name": "Miller",
"is_superuser": false,
"organization_slug": "globex",
"role": "member",
"is_active": true
},
{
"email": "ellen@globex.com",
"password": "Demo123!",
"first_name": "Ellen",
"last_name": "Ripley",
"is_superuser": false,
"organization_slug": "globex",
"role": "member",
"is_active": true
},
{
"email": "fred@globex.com",
"password": "Demo123!",
"first_name": "Fred",
"last_name": "Flintstone",
"is_superuser": false,
"organization_slug": "globex",
"role": "member",
"is_active": true
},
{
"email": "dave@soylent.com",
"password": "Demo123!",
"first_name": "Dave",
"last_name": "Brown",
"is_superuser": false,
"organization_slug": "soylent",
"role": "member",
"is_active": true
},
{
"email": "gina@soylent.com",
"password": "Demo123!",
"first_name": "Gina",
"last_name": "Torres",
"is_superuser": false,
"organization_slug": "soylent",
"role": "member",
"is_active": true
},
{
"email": "harry@soylent.com",
"password": "Demo123!",
"first_name": "Harry",
"last_name": "Potter",
"is_superuser": false,
"organization_slug": "soylent",
"role": "admin",
"is_active": true
},
{
"email": "eve@initech.com",
"password": "Demo123!",
"first_name": "Eve",
"last_name": "Davis",
"is_superuser": false,
"organization_slug": "initech",
"role": "admin",
"is_active": true
},
{
"email": "iris@initech.com",
"password": "Demo123!",
"first_name": "Iris",
"last_name": "West",
"is_superuser": false,
"organization_slug": "initech",
"role": "member",
"is_active": true
},
{
"email": "jack@initech.com",
"password": "Demo123!",
"first_name": "Jack",
"last_name": "Sparrow",
"is_superuser": false,
"organization_slug": "initech",
"role": "member",
"is_active": false
},
{
"email": "frank@umbrella.com",
"password": "Demo123!",
"first_name": "Frank",
"last_name": "Miller",
"is_superuser": false,
"organization_slug": "umbrella",
"role": "member",
"is_active": true
},
{
"email": "george@umbrella.com",
"password": "Demo123!",
"first_name": "George",
"last_name": "Costanza",
"is_superuser": false,
"organization_slug": "umbrella",
"role": "member",
"is_active": false
},
{
"email": "kate@umbrella.com",
"password": "Demo123!",
"first_name": "Kate",
"last_name": "Bishop",
"is_superuser": false,
"organization_slug": "umbrella",
"role": "member",
"is_active": true
},
{
"email": "leo@massive.com",
"password": "Demo123!",
"first_name": "Leo",
"last_name": "Messi",
"is_superuser": false,
"organization_slug": "massive-dynamic",
"role": "owner",
"is_active": true
},
{
"email": "mary@massive.com",
"password": "Demo123!",
"first_name": "Mary",
"last_name": "Jane",
"is_superuser": false,
"organization_slug": "massive-dynamic",
"role": "member",
"is_active": true
},
{
"email": "nathan@massive.com",
"password": "Demo123!",
"first_name": "Nathan",
"last_name": "Drake",
"is_superuser": false,
"organization_slug": "massive-dynamic",
"role": "member",
"is_active": true
},
{
"email": "olivia@massive.com",
"password": "Demo123!",
"first_name": "Olivia",
"last_name": "Dunham",
"is_superuser": false,
"organization_slug": "massive-dynamic",
"role": "admin",
"is_active": true
},
{
"email": "peter@massive.com",
"password": "Demo123!",
"first_name": "Peter",
"last_name": "Parker",
"is_superuser": false,
"organization_slug": "massive-dynamic",
"role": "member",
"is_active": true
},
{
"email": "quinn@massive.com",
"password": "Demo123!",
"first_name": "Quinn",
"last_name": "Mallory",
"is_superuser": false,
"organization_slug": "massive-dynamic",
"role": "member",
"is_active": true
},
{
"email": "grace@example.com",
"password": "Demo123!",
"first_name": "Grace",
"last_name": "Hopper",
"is_superuser": false,
"organization_slug": null,
"role": null,
"is_active": true
},
{
"email": "heidi@example.com",
"password": "Demo123!",
"first_name": "Heidi",
"last_name": "Klum",
"is_superuser": false,
"organization_slug": null,
"role": null,
"is_active": true
},
{
"email": "ivan@example.com",
"password": "Demo123!",
"first_name": "Ivan",
"last_name": "Drago",
"is_superuser": false,
"organization_slug": null,
"role": null,
"is_active": false
},
{
"email": "rachel@example.com",
"password": "Demo123!",
"first_name": "Rachel",
"last_name": "Green",
"is_superuser": false,
"organization_slug": null,
"role": null,
"is_active": true
},
{
"email": "sam@example.com",
"password": "Demo123!",
"first_name": "Sam",
"last_name": "Wilson",
"is_superuser": false,
"organization_slug": null,
"role": null,
"is_active": true
},
{
"email": "tony@example.com",
"password": "Demo123!",
"first_name": "Tony",
"last_name": "Stark",
"is_superuser": false,
"organization_slug": null,
"role": null,
"is_active": true
},
{
"email": "una@example.com",
"password": "Demo123!",
"first_name": "Una",
"last_name": "Chin-Riley",
"is_superuser": false,
"organization_slug": null,
"role": null,
"is_active": false
},
{
"email": "victor@example.com",
"password": "Demo123!",
"first_name": "Victor",
"last_name": "Von Doom",
"is_superuser": false,
"organization_slug": null,
"role": null,
"is_active": true
},
{
"email": "wanda@example.com",
"password": "Demo123!",
"first_name": "Wanda",
"last_name": "Maximoff",
"is_superuser": false,
"organization_slug": null,
"role": null,
"is_active": true
}
]
}

View File

@@ -3,27 +3,48 @@
Async database initialization script.
Creates the first superuser if configured and doesn't already exist.
Seeds default agent types (production data) and demo data (when DEMO_MODE is enabled).
"""
import asyncio
import json
import logging
import random
from datetime import UTC, datetime, timedelta
from datetime import UTC, date, datetime, timedelta
from pathlib import Path
from sqlalchemy import select, text
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.config import settings
from app.core.database import SessionLocal, engine
from app.crud.syndarix.agent_type import agent_type as agent_type_crud
from app.crud.user import user as user_crud
from app.models.organization import Organization
from app.models.syndarix import AgentInstance, AgentType, Issue, Project, Sprint
from app.models.syndarix.enums import (
AgentStatus,
AutonomyLevel,
ClientMode,
IssuePriority,
IssueStatus,
IssueType,
ProjectComplexity,
ProjectStatus,
SprintStatus,
)
from app.models.user import User
from app.models.user_organization import UserOrganization
from app.schemas.syndarix import AgentTypeCreate
from app.schemas.users import UserCreate
logger = logging.getLogger(__name__)
# Data file paths
DATA_DIR = Path(__file__).parent.parent / "data"
DEFAULT_AGENT_TYPES_PATH = DATA_DIR / "default_agent_types.json"
DEMO_DATA_PATH = DATA_DIR / "demo_data.json"
async def init_db() -> User | None:
"""
@@ -54,28 +75,29 @@ async def init_db() -> User | None:
if existing_user:
logger.info(f"Superuser already exists: {existing_user.email}")
return existing_user
else:
# Create superuser if doesn't exist
user_in = UserCreate(
email=superuser_email,
password=superuser_password,
first_name="Admin",
last_name="User",
is_superuser=True,
)
# Create superuser if doesn't exist
user_in = UserCreate(
email=superuser_email,
password=superuser_password,
first_name="Admin",
last_name="User",
is_superuser=True,
)
existing_user = await user_crud.create(session, obj_in=user_in)
await session.commit()
await session.refresh(existing_user)
logger.info(f"Created first superuser: {existing_user.email}")
user = await user_crud.create(session, obj_in=user_in)
await session.commit()
await session.refresh(user)
# ALWAYS load default agent types (production data)
await load_default_agent_types(session)
logger.info(f"Created first superuser: {user.email}")
# Create demo data if in demo mode
# Only load demo data if in demo mode
if settings.DEMO_MODE:
await load_demo_data(session)
return user
return existing_user
except Exception as e:
await session.rollback()
@@ -88,26 +110,89 @@ def _load_json_file(path: Path):
return json.load(f)
async def load_demo_data(session):
"""Load demo data from JSON file."""
demo_data_path = Path(__file__).parent / "core" / "demo_data.json"
if not demo_data_path.exists():
logger.warning(f"Demo data file not found: {demo_data_path}")
async def load_default_agent_types(session: AsyncSession) -> None:
"""
Load default agent types from JSON file.
These are production defaults - created only if they don't exist, never overwritten.
This allows users to customize agent types without worrying about server restarts.
"""
if not DEFAULT_AGENT_TYPES_PATH.exists():
logger.warning(
f"Default agent types file not found: {DEFAULT_AGENT_TYPES_PATH}"
)
return
try:
# Use asyncio.to_thread to avoid blocking the event loop
data = await asyncio.to_thread(_load_json_file, demo_data_path)
data = await asyncio.to_thread(_load_json_file, DEFAULT_AGENT_TYPES_PATH)
# Create Organizations
org_map = {}
for org_data in data.get("organizations", []):
# Check if org exists
result = await session.execute(
text("SELECT * FROM organizations WHERE slug = :slug"),
{"slug": org_data["slug"]},
for agent_type_data in data:
slug = agent_type_data["slug"]
# Check if agent type already exists
existing = await agent_type_crud.get_by_slug(session, slug=slug)
if existing:
logger.debug(f"Agent type already exists: {agent_type_data['name']}")
continue
# Create the agent type
agent_type_in = AgentTypeCreate(
name=agent_type_data["name"],
slug=slug,
description=agent_type_data.get("description"),
expertise=agent_type_data.get("expertise", []),
personality_prompt=agent_type_data["personality_prompt"],
primary_model=agent_type_data["primary_model"],
fallback_models=agent_type_data.get("fallback_models", []),
model_params=agent_type_data.get("model_params", {}),
mcp_servers=agent_type_data.get("mcp_servers", []),
tool_permissions=agent_type_data.get("tool_permissions", {}),
is_active=agent_type_data.get("is_active", True),
)
existing_org = result.first()
await agent_type_crud.create(session, obj_in=agent_type_in)
logger.info(f"Created default agent type: {agent_type_data['name']}")
logger.info("Default agent types loaded successfully")
except Exception as e:
logger.error(f"Error loading default agent types: {e}")
raise
async def load_demo_data(session: AsyncSession) -> None:
"""
Load demo data from JSON file.
Only runs when DEMO_MODE is enabled. Creates demo organizations, users,
projects, sprints, agent instances, and issues.
"""
if not DEMO_DATA_PATH.exists():
logger.warning(f"Demo data file not found: {DEMO_DATA_PATH}")
return
try:
data = await asyncio.to_thread(_load_json_file, DEMO_DATA_PATH)
# Build lookup maps for FK resolution
org_map: dict[str, Organization] = {}
user_map: dict[str, User] = {}
project_map: dict[str, Project] = {}
sprint_map: dict[str, Sprint] = {} # key: "project_slug:sprint_number"
agent_type_map: dict[str, AgentType] = {}
agent_instance_map: dict[
str, AgentInstance
] = {} # key: "project_slug:agent_name"
# ========================
# 1. Create Organizations
# ========================
for org_data in data.get("organizations", []):
org_result = await session.execute(
select(Organization).where(Organization.slug == org_data["slug"])
)
existing_org = org_result.scalar_one_or_none()
if not existing_org:
org = Organization(
@@ -117,29 +202,20 @@ async def load_demo_data(session):
is_active=True,
)
session.add(org)
await session.flush() # Flush to get ID
org_map[org.slug] = org
await session.flush()
org_map[str(org.slug)] = org
logger.info(f"Created demo organization: {org.name}")
else:
# We can't easily get the ORM object from raw SQL result for map without querying again or mapping
# So let's just query it properly if we need it for relationships
# But for simplicity in this script, let's just assume we created it or it exists.
# To properly map for users, we need the ID.
# Let's use a simpler approach: just try to create, if slug conflict, skip.
pass
org_map[str(existing_org.slug)] = existing_org
# Re-query all orgs to build map for users
result = await session.execute(select(Organization))
orgs = result.scalars().all()
org_map = {org.slug: org for org in orgs}
# Create Users
# ========================
# 2. Create Users
# ========================
for user_data in data.get("users", []):
existing_user = await user_crud.get_by_email(
session, email=user_data["email"]
)
if not existing_user:
# Create user
user_in = UserCreate(
email=user_data["email"],
password=user_data["password"],
@@ -151,17 +227,13 @@ async def load_demo_data(session):
user = await user_crud.create(session, obj_in=user_in)
# Randomize created_at for demo data (last 30 days)
# This makes the charts look more realistic
days_ago = random.randint(0, 30) # noqa: S311
random_time = datetime.now(UTC) - timedelta(days=days_ago)
# Add some random hours/minutes variation
random_time = random_time.replace(
hour=random.randint(0, 23), # noqa: S311
minute=random.randint(0, 59), # noqa: S311
)
# Update the timestamp and is_active directly in the database
# We do this to ensure the values are persisted correctly
await session.execute(
text(
"UPDATE users SET created_at = :created_at, is_active = :is_active WHERE id = :user_id"
@@ -174,7 +246,7 @@ async def load_demo_data(session):
)
logger.info(
f"Created demo user: {user.email} (created {days_ago} days ago, active={user_data.get('is_active', True)})"
f"Created demo user: {user.email} (created {days_ago} days ago)"
)
# Add to organization if specified
@@ -182,19 +254,219 @@ async def load_demo_data(session):
role = user_data.get("role")
if org_slug and org_slug in org_map and role:
org = org_map[org_slug]
# Check if membership exists (it shouldn't for new user)
member = UserOrganization(
user_id=user.id, organization_id=org.id, role=role
)
session.add(member)
logger.info(f"Added {user.email} to {org.name} as {role}")
user_map[str(user.email)] = user
else:
logger.info(f"Demo user already exists: {existing_user.email}")
user_map[str(existing_user.email)] = existing_user
logger.debug(f"Demo user already exists: {existing_user.email}")
await session.flush()
# ========================
# 3. Load Agent Types Map (for FK resolution)
# ========================
agent_types_result = await session.execute(select(AgentType))
for at in agent_types_result.scalars().all():
agent_type_map[str(at.slug)] = at
# ========================
# 4. Create Projects
# ========================
for project_data in data.get("projects", []):
project_result = await session.execute(
select(Project).where(Project.slug == project_data["slug"])
)
existing_project = project_result.scalar_one_or_none()
if not existing_project:
# Resolve owner email to user ID
owner_id = None
owner_email = project_data.get("owner_email")
if owner_email and owner_email in user_map:
owner_id = user_map[owner_email].id
project = Project(
name=project_data["name"],
slug=project_data["slug"],
description=project_data.get("description"),
owner_id=owner_id,
autonomy_level=AutonomyLevel(
project_data.get("autonomy_level", "milestone")
),
status=ProjectStatus(project_data.get("status", "active")),
complexity=ProjectComplexity(
project_data.get("complexity", "medium")
),
client_mode=ClientMode(project_data.get("client_mode", "auto")),
settings=project_data.get("settings", {}),
)
session.add(project)
await session.flush()
project_map[str(project.slug)] = project
logger.info(f"Created demo project: {project.name}")
else:
project_map[str(existing_project.slug)] = existing_project
logger.debug(f"Demo project already exists: {existing_project.name}")
# ========================
# 5. Create Sprints
# ========================
for sprint_data in data.get("sprints", []):
project_slug = sprint_data["project_slug"]
sprint_number = sprint_data["number"]
sprint_key = f"{project_slug}:{sprint_number}"
if project_slug not in project_map:
logger.warning(f"Project not found for sprint: {project_slug}")
continue
sprint_project = project_map[project_slug]
# Check if sprint exists
sprint_result = await session.execute(
select(Sprint).where(
Sprint.project_id == sprint_project.id,
Sprint.number == sprint_number,
)
)
existing_sprint = sprint_result.scalar_one_or_none()
if not existing_sprint:
sprint = Sprint(
project_id=sprint_project.id,
name=sprint_data["name"],
number=sprint_number,
goal=sprint_data.get("goal"),
start_date=date.fromisoformat(sprint_data["start_date"]),
end_date=date.fromisoformat(sprint_data["end_date"]),
status=SprintStatus(sprint_data.get("status", "planned")),
planned_points=sprint_data.get("planned_points"),
)
session.add(sprint)
await session.flush()
sprint_map[sprint_key] = sprint
logger.info(
f"Created demo sprint: {sprint.name} for {sprint_project.name}"
)
else:
sprint_map[sprint_key] = existing_sprint
logger.debug(f"Demo sprint already exists: {existing_sprint.name}")
# ========================
# 6. Create Agent Instances
# ========================
for agent_data in data.get("agent_instances", []):
project_slug = agent_data["project_slug"]
agent_type_slug = agent_data["agent_type_slug"]
agent_name = agent_data["name"]
agent_key = f"{project_slug}:{agent_name}"
if project_slug not in project_map:
logger.warning(f"Project not found for agent: {project_slug}")
continue
if agent_type_slug not in agent_type_map:
logger.warning(f"Agent type not found: {agent_type_slug}")
continue
agent_project = project_map[project_slug]
agent_type = agent_type_map[agent_type_slug]
# Check if agent instance exists (by name within project)
agent_result = await session.execute(
select(AgentInstance).where(
AgentInstance.project_id == agent_project.id,
AgentInstance.name == agent_name,
)
)
existing_agent = agent_result.scalar_one_or_none()
if not existing_agent:
agent_instance = AgentInstance(
project_id=agent_project.id,
agent_type_id=agent_type.id,
name=agent_name,
status=AgentStatus(agent_data.get("status", "idle")),
current_task=agent_data.get("current_task"),
)
session.add(agent_instance)
await session.flush()
agent_instance_map[agent_key] = agent_instance
logger.info(
f"Created demo agent: {agent_name} ({agent_type.name}) "
f"for {agent_project.name}"
)
else:
agent_instance_map[agent_key] = existing_agent
logger.debug(f"Demo agent already exists: {existing_agent.name}")
# ========================
# 7. Create Issues
# ========================
for issue_data in data.get("issues", []):
project_slug = issue_data["project_slug"]
if project_slug not in project_map:
logger.warning(f"Project not found for issue: {project_slug}")
continue
issue_project = project_map[project_slug]
# Check if issue exists (by title within project - simple heuristic)
issue_result = await session.execute(
select(Issue).where(
Issue.project_id == issue_project.id,
Issue.title == issue_data["title"],
)
)
existing_issue = issue_result.scalar_one_or_none()
if not existing_issue:
# Resolve sprint
sprint_id = None
sprint_number = issue_data.get("sprint_number")
if sprint_number:
sprint_key = f"{project_slug}:{sprint_number}"
if sprint_key in sprint_map:
sprint_id = sprint_map[sprint_key].id
# Resolve assigned agent
assigned_agent_id = None
assigned_agent_name = issue_data.get("assigned_agent_name")
if assigned_agent_name:
agent_key = f"{project_slug}:{assigned_agent_name}"
if agent_key in agent_instance_map:
assigned_agent_id = agent_instance_map[agent_key].id
issue = Issue(
project_id=issue_project.id,
sprint_id=sprint_id,
type=IssueType(issue_data.get("type", "task")),
title=issue_data["title"],
body=issue_data.get("body", ""),
status=IssueStatus(issue_data.get("status", "open")),
priority=IssuePriority(issue_data.get("priority", "medium")),
labels=issue_data.get("labels", []),
story_points=issue_data.get("story_points"),
assigned_agent_id=assigned_agent_id,
)
session.add(issue)
logger.info(f"Created demo issue: {issue.title[:50]}...")
else:
logger.debug(
f"Demo issue already exists: {existing_issue.title[:50]}..."
)
await session.commit()
logger.info("Demo data loaded successfully")
except Exception as e:
await session.rollback()
logger.error(f"Error loading demo data: {e}")
raise
@@ -210,12 +482,12 @@ async def main():
try:
user = await init_db()
if user:
print("Database initialized successfully")
print(f"Superuser: {user.email}")
print("Database initialized successfully")
print(f"Superuser: {user.email}")
else:
print("Failed to initialize database")
print("Failed to initialize database")
except Exception as e:
print(f"Error initializing database: {e}")
print(f"Error initializing database: {e}")
raise
finally:
# Close the engine

View File

@@ -0,0 +1,122 @@
[
{
"name": "Product Owner",
"slug": "product-owner",
"description": "Requirements discovery, stakeholder communication, and product vision. Leads the team in defining what to build and why.",
"expertise": ["requirements", "stakeholder-management", "product-strategy", "user-stories", "acceptance-criteria", "prioritization"],
"personality_prompt": "You are a skilled Product Owner focused on delivering maximum value to stakeholders. You excel at:\n- Understanding and articulating business needs\n- Writing clear user stories with acceptance criteria\n- Prioritizing features based on value and effort\n- Facilitating discussions between stakeholders and technical teams\n- Making trade-off decisions when scope conflicts arise\n\nYou communicate clearly and concisely, always keeping the end user and business goals in mind. You ask clarifying questions to ensure requirements are complete before passing them to the team.",
"primary_model": "claude-sonnet-4-20250514",
"fallback_models": ["claude-haiku-3-5-20241022"],
"model_params": {
"temperature": 0.7,
"max_tokens": 4096
},
"mcp_servers": ["gitea", "knowledge-base"],
"tool_permissions": {
"allowed": ["*"],
"denied": [],
"require_approval": ["gitea:delete_*"]
},
"is_active": true
},
{
"name": "Business Analyst",
"slug": "business-analyst",
"description": "Analysis, documentation, and detailed specifications. Bridges the gap between business needs and technical implementation.",
"expertise": ["analysis", "documentation", "specifications", "process-modeling", "data-analysis", "requirements-engineering"],
"personality_prompt": "You are a meticulous Business Analyst who excels at turning vague requirements into precise specifications. You:\n- Create detailed functional and technical specifications\n- Model business processes and data flows\n- Identify edge cases and potential issues early\n- Document assumptions and dependencies clearly\n- Ensure traceability between requirements and implementation\n\nYou are thorough and detail-oriented, always considering the implications of decisions. You create documentation that developers can follow without ambiguity.",
"primary_model": "claude-sonnet-4-20250514",
"fallback_models": ["claude-haiku-3-5-20241022"],
"model_params": {
"temperature": 0.5,
"max_tokens": 8192
},
"mcp_servers": ["gitea", "knowledge-base"],
"tool_permissions": {
"allowed": ["*"],
"denied": [],
"require_approval": []
},
"is_active": true
},
{
"name": "Solutions Architect",
"slug": "solutions-architect",
"description": "System design, architecture decisions, and technical leadership. Defines the technical vision and ensures system coherence.",
"expertise": ["system-design", "architecture", "adrs", "technical-decisions", "integration", "scalability", "security"],
"personality_prompt": "You are an experienced Solutions Architect who designs robust, scalable systems. You:\n- Create architecture diagrams and technical documentation\n- Write Architecture Decision Records (ADRs) for key decisions\n- Evaluate technology choices based on requirements and constraints\n- Identify potential bottlenecks and security concerns\n- Ensure consistency across the system design\n\nYou think holistically about systems, considering maintainability, scalability, and operational concerns. You document your decisions with clear rationale and trade-off analysis.",
"primary_model": "claude-sonnet-4-20250514",
"fallback_models": ["claude-haiku-3-5-20241022"],
"model_params": {
"temperature": 0.6,
"max_tokens": 8192
},
"mcp_servers": ["gitea", "knowledge-base", "filesystem"],
"tool_permissions": {
"allowed": ["*"],
"denied": [],
"require_approval": ["gitea:create_pull_request"]
},
"is_active": true
},
{
"name": "Senior Engineer",
"slug": "senior-engineer",
"description": "Implementation, code review, and refactoring. Writes high-quality, maintainable code following best practices.",
"expertise": ["implementation", "code-review", "refactoring", "testing", "debugging", "performance", "clean-code"],
"personality_prompt": "You are a Senior Software Engineer who writes clean, maintainable code. You:\n- Implement features following established patterns and standards\n- Write comprehensive tests (unit, integration, e2e)\n- Review code for correctness, performance, and maintainability\n- Refactor code to improve quality without changing behavior\n- Debug complex issues systematically\n\nYou prioritize code quality and follow SOLID principles. You write code that other developers can easily understand and maintain. You always consider edge cases and error handling.",
"primary_model": "claude-sonnet-4-20250514",
"fallback_models": ["claude-haiku-3-5-20241022"],
"model_params": {
"temperature": 0.3,
"max_tokens": 16384
},
"mcp_servers": ["gitea", "knowledge-base", "filesystem"],
"tool_permissions": {
"allowed": ["*"],
"denied": [],
"require_approval": ["gitea:create_pull_request", "gitea:delete_*"]
},
"is_active": true
},
{
"name": "QA Engineer",
"slug": "qa-engineer",
"description": "Testing, quality assurance, and bug verification. Ensures the product meets quality standards before release.",
"expertise": ["testing", "quality-assurance", "test-automation", "bug-verification", "test-planning", "regression-testing"],
"personality_prompt": "You are a thorough QA Engineer who ensures product quality. You:\n- Design comprehensive test plans and test cases\n- Write automated tests (unit, integration, e2e)\n- Verify bug fixes and perform regression testing\n- Identify edge cases and boundary conditions\n- Document defects clearly with reproduction steps\n\nYou have a critical eye for quality and think like a user who might break things. You balance thoroughness with efficiency, focusing on high-risk areas while ensuring broad coverage.",
"primary_model": "claude-sonnet-4-20250514",
"fallback_models": ["claude-haiku-3-5-20241022"],
"model_params": {
"temperature": 0.4,
"max_tokens": 8192
},
"mcp_servers": ["gitea", "knowledge-base", "filesystem"],
"tool_permissions": {
"allowed": ["*"],
"denied": [],
"require_approval": []
},
"is_active": true
},
{
"name": "DevOps Engineer",
"slug": "devops-engineer",
"description": "CI/CD, deployment, and infrastructure. Ensures reliable, automated delivery pipelines and operational excellence.",
"expertise": ["ci-cd", "deployment", "infrastructure", "docker", "kubernetes", "monitoring", "automation"],
"personality_prompt": "You are a skilled DevOps Engineer who builds reliable delivery pipelines. You:\n- Design and maintain CI/CD pipelines\n- Configure infrastructure as code\n- Set up monitoring, logging, and alerting\n- Automate repetitive operational tasks\n- Ensure security and compliance in deployments\n\nYou think about reliability, observability, and automation. You design systems that fail gracefully and are easy to troubleshoot. You document runbooks and operational procedures clearly.",
"primary_model": "claude-sonnet-4-20250514",
"fallback_models": ["claude-haiku-3-5-20241022"],
"model_params": {
"temperature": 0.4,
"max_tokens": 8192
},
"mcp_servers": ["gitea", "knowledge-base", "filesystem"],
"tool_permissions": {
"allowed": ["*"],
"denied": [],
"require_approval": ["gitea:create_release", "gitea:delete_*"]
},
"is_active": true
}
]

644
backend/data/demo_data.json Normal file
View File

@@ -0,0 +1,644 @@
{
"organizations": [
{
"name": "Acme Corp",
"slug": "acme-corp",
"description": "A leading provider of coyote-catching equipment."
},
{
"name": "Globex Corporation",
"slug": "globex",
"description": "We own the East Coast."
},
{
"name": "Soylent Corp",
"slug": "soylent",
"description": "Making food for the future."
},
{
"name": "Initech",
"slug": "initech",
"description": "Software for the soul."
},
{
"name": "Umbrella Corporation",
"slug": "umbrella",
"description": "Our business is life itself."
},
{
"name": "Massive Dynamic",
"slug": "massive-dynamic",
"description": "What don't we do?"
}
],
"users": [
{
"email": "demo@example.com",
"password": "DemoPass1234!",
"first_name": "Demo",
"last_name": "User",
"is_superuser": false,
"organization_slug": "acme-corp",
"role": "member",
"is_active": true
},
{
"email": "alice@acme.com",
"password": "Demo123!",
"first_name": "Alice",
"last_name": "Smith",
"is_superuser": false,
"organization_slug": "acme-corp",
"role": "admin",
"is_active": true
},
{
"email": "bob@acme.com",
"password": "Demo123!",
"first_name": "Bob",
"last_name": "Jones",
"is_superuser": false,
"organization_slug": "acme-corp",
"role": "member",
"is_active": true
},
{
"email": "charlie@acme.com",
"password": "Demo123!",
"first_name": "Charlie",
"last_name": "Brown",
"is_superuser": false,
"organization_slug": "acme-corp",
"role": "member",
"is_active": false
},
{
"email": "diana@acme.com",
"password": "Demo123!",
"first_name": "Diana",
"last_name": "Prince",
"is_superuser": false,
"organization_slug": "acme-corp",
"role": "member",
"is_active": true
},
{
"email": "carol@globex.com",
"password": "Demo123!",
"first_name": "Carol",
"last_name": "Williams",
"is_superuser": false,
"organization_slug": "globex",
"role": "owner",
"is_active": true
},
{
"email": "dan@globex.com",
"password": "Demo123!",
"first_name": "Dan",
"last_name": "Miller",
"is_superuser": false,
"organization_slug": "globex",
"role": "member",
"is_active": true
},
{
"email": "ellen@globex.com",
"password": "Demo123!",
"first_name": "Ellen",
"last_name": "Ripley",
"is_superuser": false,
"organization_slug": "globex",
"role": "member",
"is_active": true
},
{
"email": "fred@globex.com",
"password": "Demo123!",
"first_name": "Fred",
"last_name": "Flintstone",
"is_superuser": false,
"organization_slug": "globex",
"role": "member",
"is_active": true
},
{
"email": "dave@soylent.com",
"password": "Demo123!",
"first_name": "Dave",
"last_name": "Brown",
"is_superuser": false,
"organization_slug": "soylent",
"role": "member",
"is_active": true
},
{
"email": "gina@soylent.com",
"password": "Demo123!",
"first_name": "Gina",
"last_name": "Torres",
"is_superuser": false,
"organization_slug": "soylent",
"role": "member",
"is_active": true
},
{
"email": "harry@soylent.com",
"password": "Demo123!",
"first_name": "Harry",
"last_name": "Potter",
"is_superuser": false,
"organization_slug": "soylent",
"role": "admin",
"is_active": true
},
{
"email": "eve@initech.com",
"password": "Demo123!",
"first_name": "Eve",
"last_name": "Davis",
"is_superuser": false,
"organization_slug": "initech",
"role": "admin",
"is_active": true
},
{
"email": "iris@initech.com",
"password": "Demo123!",
"first_name": "Iris",
"last_name": "West",
"is_superuser": false,
"organization_slug": "initech",
"role": "member",
"is_active": true
},
{
"email": "jack@initech.com",
"password": "Demo123!",
"first_name": "Jack",
"last_name": "Sparrow",
"is_superuser": false,
"organization_slug": "initech",
"role": "member",
"is_active": false
},
{
"email": "frank@umbrella.com",
"password": "Demo123!",
"first_name": "Frank",
"last_name": "Miller",
"is_superuser": false,
"organization_slug": "umbrella",
"role": "member",
"is_active": true
},
{
"email": "george@umbrella.com",
"password": "Demo123!",
"first_name": "George",
"last_name": "Costanza",
"is_superuser": false,
"organization_slug": "umbrella",
"role": "member",
"is_active": false
},
{
"email": "kate@umbrella.com",
"password": "Demo123!",
"first_name": "Kate",
"last_name": "Bishop",
"is_superuser": false,
"organization_slug": "umbrella",
"role": "member",
"is_active": true
},
{
"email": "leo@massive.com",
"password": "Demo123!",
"first_name": "Leo",
"last_name": "Messi",
"is_superuser": false,
"organization_slug": "massive-dynamic",
"role": "owner",
"is_active": true
},
{
"email": "mary@massive.com",
"password": "Demo123!",
"first_name": "Mary",
"last_name": "Jane",
"is_superuser": false,
"organization_slug": "massive-dynamic",
"role": "member",
"is_active": true
},
{
"email": "nathan@massive.com",
"password": "Demo123!",
"first_name": "Nathan",
"last_name": "Drake",
"is_superuser": false,
"organization_slug": "massive-dynamic",
"role": "member",
"is_active": true
},
{
"email": "olivia@massive.com",
"password": "Demo123!",
"first_name": "Olivia",
"last_name": "Dunham",
"is_superuser": false,
"organization_slug": "massive-dynamic",
"role": "admin",
"is_active": true
},
{
"email": "peter@massive.com",
"password": "Demo123!",
"first_name": "Peter",
"last_name": "Parker",
"is_superuser": false,
"organization_slug": "massive-dynamic",
"role": "member",
"is_active": true
},
{
"email": "quinn@massive.com",
"password": "Demo123!",
"first_name": "Quinn",
"last_name": "Mallory",
"is_superuser": false,
"organization_slug": "massive-dynamic",
"role": "member",
"is_active": true
},
{
"email": "grace@example.com",
"password": "Demo123!",
"first_name": "Grace",
"last_name": "Hopper",
"is_superuser": false,
"organization_slug": null,
"role": null,
"is_active": true
},
{
"email": "heidi@example.com",
"password": "Demo123!",
"first_name": "Heidi",
"last_name": "Klum",
"is_superuser": false,
"organization_slug": null,
"role": null,
"is_active": true
},
{
"email": "ivan@example.com",
"password": "Demo123!",
"first_name": "Ivan",
"last_name": "Drago",
"is_superuser": false,
"organization_slug": null,
"role": null,
"is_active": false
},
{
"email": "rachel@example.com",
"password": "Demo123!",
"first_name": "Rachel",
"last_name": "Green",
"is_superuser": false,
"organization_slug": null,
"role": null,
"is_active": true
},
{
"email": "sam@example.com",
"password": "Demo123!",
"first_name": "Sam",
"last_name": "Wilson",
"is_superuser": false,
"organization_slug": null,
"role": null,
"is_active": true
},
{
"email": "tony@example.com",
"password": "Demo123!",
"first_name": "Tony",
"last_name": "Stark",
"is_superuser": false,
"organization_slug": null,
"role": null,
"is_active": true
},
{
"email": "una@example.com",
"password": "Demo123!",
"first_name": "Una",
"last_name": "Chin-Riley",
"is_superuser": false,
"organization_slug": null,
"role": null,
"is_active": false
},
{
"email": "victor@example.com",
"password": "Demo123!",
"first_name": "Victor",
"last_name": "Von Doom",
"is_superuser": false,
"organization_slug": null,
"role": null,
"is_active": true
},
{
"email": "wanda@example.com",
"password": "Demo123!",
"first_name": "Wanda",
"last_name": "Maximoff",
"is_superuser": false,
"organization_slug": null,
"role": null,
"is_active": true
}
],
"projects": [
{
"name": "E-Commerce Platform Redesign",
"slug": "ecommerce-redesign",
"description": "Complete redesign of the e-commerce platform with modern UX, improved checkout flow, and mobile-first approach.",
"owner_email": "demo@example.com",
"autonomy_level": "milestone",
"status": "active",
"complexity": "complex",
"client_mode": "technical",
"settings": {
"mcp_servers": ["gitea", "knowledge-base"],
"notification_email": "demo@example.com"
}
},
{
"name": "Mobile Banking App",
"slug": "mobile-banking",
"description": "Secure mobile banking application with biometric authentication, transaction history, and real-time notifications.",
"owner_email": "alice@acme.com",
"autonomy_level": "full_control",
"status": "active",
"complexity": "complex",
"client_mode": "technical",
"settings": {
"mcp_servers": ["gitea", "knowledge-base"],
"security_level": "high"
}
},
{
"name": "Internal HR Portal",
"slug": "hr-portal",
"description": "Employee self-service portal for leave requests, performance reviews, and document management.",
"owner_email": "carol@globex.com",
"autonomy_level": "autonomous",
"status": "active",
"complexity": "medium",
"client_mode": "auto",
"settings": {
"mcp_servers": ["gitea", "knowledge-base"]
}
}
],
"sprints": [
{
"project_slug": "ecommerce-redesign",
"name": "Sprint 1: Foundation",
"number": 1,
"goal": "Set up project infrastructure, design system, and core navigation components.",
"start_date": "2026-01-06",
"end_date": "2026-01-20",
"status": "active",
"planned_points": 21
},
{
"project_slug": "ecommerce-redesign",
"name": "Sprint 2: Product Catalog",
"number": 2,
"goal": "Implement product listing, filtering, search, and detail pages.",
"start_date": "2026-01-20",
"end_date": "2026-02-03",
"status": "planned",
"planned_points": 34
},
{
"project_slug": "mobile-banking",
"name": "Sprint 1: Authentication",
"number": 1,
"goal": "Implement secure login, biometric authentication, and session management.",
"start_date": "2026-01-06",
"end_date": "2026-01-20",
"status": "active",
"planned_points": 26
},
{
"project_slug": "hr-portal",
"name": "Sprint 1: Core Features",
"number": 1,
"goal": "Build employee dashboard, leave request system, and basic document management.",
"start_date": "2026-01-06",
"end_date": "2026-01-20",
"status": "active",
"planned_points": 18
}
],
"agent_instances": [
{
"project_slug": "ecommerce-redesign",
"agent_type_slug": "product-owner",
"name": "Aria",
"status": "idle"
},
{
"project_slug": "ecommerce-redesign",
"agent_type_slug": "solutions-architect",
"name": "Marcus",
"status": "idle"
},
{
"project_slug": "ecommerce-redesign",
"agent_type_slug": "senior-engineer",
"name": "Zara",
"status": "working",
"current_task": "Implementing responsive navigation component"
},
{
"project_slug": "mobile-banking",
"agent_type_slug": "product-owner",
"name": "Felix",
"status": "waiting",
"current_task": "Awaiting security requirements clarification"
},
{
"project_slug": "mobile-banking",
"agent_type_slug": "senior-engineer",
"name": "Luna",
"status": "working",
"current_task": "Implementing biometric authentication flow"
},
{
"project_slug": "mobile-banking",
"agent_type_slug": "qa-engineer",
"name": "Rex",
"status": "idle"
},
{
"project_slug": "hr-portal",
"agent_type_slug": "business-analyst",
"name": "Nova",
"status": "working",
"current_task": "Documenting leave request workflow"
},
{
"project_slug": "hr-portal",
"agent_type_slug": "senior-engineer",
"name": "Atlas",
"status": "working",
"current_task": "Building employee dashboard API"
}
],
"issues": [
{
"project_slug": "ecommerce-redesign",
"sprint_number": 1,
"type": "story",
"title": "Design responsive navigation component",
"body": "As a user, I want a navigation menu that works seamlessly on both desktop and mobile devices.\n\n## Acceptance Criteria\n- Hamburger menu on mobile viewports\n- Sticky header on scroll\n- Keyboard accessible\n- Screen reader compatible",
"status": "in_progress",
"priority": "high",
"labels": ["frontend", "design-system"],
"story_points": 5,
"assigned_agent_name": "Zara"
},
{
"project_slug": "ecommerce-redesign",
"sprint_number": 1,
"type": "task",
"title": "Set up Tailwind CSS configuration",
"body": "Configure Tailwind CSS with custom design tokens for the e-commerce platform.\n\n- Define color palette\n- Set up typography scale\n- Configure breakpoints\n- Add custom utilities",
"status": "closed",
"priority": "high",
"labels": ["frontend", "infrastructure"],
"story_points": 3
},
{
"project_slug": "ecommerce-redesign",
"sprint_number": 1,
"type": "task",
"title": "Create base component library structure",
"body": "Set up the foundational component library with:\n- Button variants\n- Form inputs\n- Card component\n- Modal system",
"status": "open",
"priority": "medium",
"labels": ["frontend", "design-system"],
"story_points": 8
},
{
"project_slug": "ecommerce-redesign",
"sprint_number": 1,
"type": "story",
"title": "Implement user authentication flow",
"body": "As a user, I want to sign up, log in, and manage my account.\n\n## Features\n- Email/password registration\n- Social login (Google, GitHub)\n- Password reset flow\n- Email verification",
"status": "open",
"priority": "critical",
"labels": ["auth", "backend", "frontend"],
"story_points": 13
},
{
"project_slug": "ecommerce-redesign",
"sprint_number": 2,
"type": "epic",
"title": "Product Catalog System",
"body": "Complete product catalog implementation including:\n- Product listing with pagination\n- Advanced filtering and search\n- Product detail pages\n- Category navigation",
"status": "open",
"priority": "high",
"labels": ["catalog", "backend", "frontend"],
"story_points": null
},
{
"project_slug": "mobile-banking",
"sprint_number": 1,
"type": "story",
"title": "Implement biometric authentication",
"body": "As a user, I want to log in using Face ID or Touch ID for quick and secure access.\n\n## Requirements\n- Support Face ID on iOS\n- Support fingerprint on Android\n- Fallback to PIN/password\n- Secure keychain storage",
"status": "in_progress",
"priority": "critical",
"labels": ["auth", "security", "mobile"],
"story_points": 8,
"assigned_agent_name": "Luna"
},
{
"project_slug": "mobile-banking",
"sprint_number": 1,
"type": "task",
"title": "Set up secure session management",
"body": "Implement secure session handling with:\n- JWT tokens with short expiry\n- Refresh token rotation\n- Session timeout handling\n- Multi-device session management",
"status": "open",
"priority": "critical",
"labels": ["auth", "security", "backend"],
"story_points": 5
},
{
"project_slug": "mobile-banking",
"sprint_number": 1,
"type": "bug",
"title": "Fix token refresh race condition",
"body": "When multiple API calls happen simultaneously after token expiry, multiple refresh requests are made causing 401 errors.\n\n## Steps to Reproduce\n1. Wait for token to expire\n2. Trigger multiple API calls at once\n3. Observe multiple 401 errors",
"status": "open",
"priority": "high",
"labels": ["bug", "auth", "backend"],
"story_points": 3
},
{
"project_slug": "mobile-banking",
"sprint_number": 1,
"type": "task",
"title": "Implement PIN entry screen",
"body": "Create secure PIN entry component with:\n- Masked input display\n- Haptic feedback\n- Brute force protection (lockout after 5 attempts)\n- Secure PIN storage",
"status": "open",
"priority": "high",
"labels": ["auth", "mobile", "frontend"],
"story_points": 5
},
{
"project_slug": "hr-portal",
"sprint_number": 1,
"type": "story",
"title": "Build employee dashboard",
"body": "As an employee, I want a dashboard showing my key information at a glance.\n\n## Dashboard Widgets\n- Leave balance\n- Pending approvals\n- Upcoming holidays\n- Recent announcements",
"status": "in_progress",
"priority": "high",
"labels": ["frontend", "dashboard"],
"story_points": 5,
"assigned_agent_name": "Atlas"
},
{
"project_slug": "hr-portal",
"sprint_number": 1,
"type": "story",
"title": "Implement leave request system",
"body": "As an employee, I want to submit and track leave requests.\n\n## Features\n- Submit leave request with date range\n- View leave balance by type\n- Track request status\n- Manager approval workflow",
"status": "in_progress",
"priority": "high",
"labels": ["backend", "frontend", "workflow"],
"story_points": 8,
"assigned_agent_name": "Nova"
},
{
"project_slug": "hr-portal",
"sprint_number": 1,
"type": "task",
"title": "Set up document storage integration",
"body": "Integrate with S3-compatible storage for employee documents:\n- Secure upload/download\n- File type validation\n- Size limits\n- Virus scanning",
"status": "open",
"priority": "medium",
"labels": ["backend", "infrastructure", "storage"],
"story_points": 5
}
]
}