Coverage for session_buddy / team_knowledge.py: 28.25%
303 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-04 00:43 -0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-04 00:43 -0800
1"""Team Knowledge Base module for shared reflection database with permissions.
3This module provides team collaboration features including:
4- Shared reflection database with user permissions
5- Team access control and role management
6- Collaborative knowledge sharing
7- Team-specific conversation organization
8"""
10import hashlib
11import importlib.util
12import json
13import logging
14import sqlite3
15import threading
16import time
17from dataclasses import asdict, dataclass
18from datetime import datetime, timedelta
19from enum import Enum
20from pathlib import Path
21from typing import Any
23DUCKDB_AVAILABLE = importlib.util.find_spec("duckdb") is not None
24AIOFILES_AVAILABLE = importlib.util.find_spec("aiofiles") is not None
26logger = logging.getLogger(__name__)
29class UserRole(Enum):
30 """User roles in team knowledge base."""
32 VIEWER = "viewer"
33 CONTRIBUTOR = "contributor"
34 MODERATOR = "moderator"
35 ADMIN = "admin"
38class AccessLevel(Enum):
39 """Access levels for knowledge base content."""
41 PRIVATE = "private"
42 TEAM = "team"
43 PROJECT = "project"
44 PUBLIC = "public"
47@dataclass
48class TeamUser:
49 """Team user information."""
51 user_id: str
52 username: str
53 email: str | None
54 role: UserRole
55 teams: list[str]
56 created_at: datetime
57 last_active: datetime
58 permissions: dict[str, bool]
61@dataclass
62class TeamReflection:
63 """Team-shared reflection with access control."""
65 id: str
66 content: str
67 tags: list[str]
68 access_level: AccessLevel
69 team_id: str | None
70 project_id: str | None
71 author_id: str
72 created_at: datetime
73 updated_at: datetime
74 votes: int
75 viewers: set[str]
76 editors: set[str]
79@dataclass
80class Team:
81 """Team information and configuration."""
83 team_id: str
84 name: str
85 description: str
86 owner_id: str
87 members: set[str]
88 projects: set[str]
89 created_at: datetime
90 settings: dict[str, Any]
93class TeamKnowledgeManager:
94 """Manages team knowledge base with permissions and access control."""
96 def __init__(self, db_path: str | None = None) -> None:
97 """Initialize team knowledge manager."""
98 self.db_path = db_path or str(
99 Path.home() / ".claude" / "data" / "team_knowledge.db",
100 )
101 self._lock = threading.Lock()
102 self._init_database()
104 def _init_database(self) -> None:
105 """Initialize SQLite database for team knowledge."""
106 Path(self.db_path).parent.mkdir(parents=True, exist_ok=True)
108 with sqlite3.connect(self.db_path) as conn:
109 self._create_tables(conn)
110 self._create_indices(conn)
112 def _create_tables(self, conn: sqlite3.Connection) -> None:
113 """Create database tables."""
114 self._create_users_table(conn)
115 self._create_teams_table(conn)
116 self._create_reflections_table(conn)
117 self._create_access_logs_table(conn)
119 def _create_users_table(self, conn: sqlite3.Connection) -> None:
120 """Create users table."""
121 conn.execute("""
122 CREATE TABLE IF NOT EXISTS users (
123 user_id TEXT PRIMARY KEY,
124 username TEXT UNIQUE NOT NULL,
125 email TEXT,
126 role TEXT NOT NULL,
127 teams TEXT, -- JSON array
128 created_at TIMESTAMP,
129 last_active TIMESTAMP,
130 permissions TEXT -- JSON object
131 )
132 """)
134 def _create_teams_table(self, conn: sqlite3.Connection) -> None:
135 """Create teams table."""
136 conn.execute("""
137 CREATE TABLE IF NOT EXISTS teams (
138 team_id TEXT PRIMARY KEY,
139 name TEXT NOT NULL,
140 description TEXT,
141 owner_id TEXT NOT NULL,
142 members TEXT, -- JSON array
143 projects TEXT, -- JSON array
144 created_at TIMESTAMP,
145 settings TEXT, -- JSON object
146 FOREIGN KEY (owner_id) REFERENCES users(user_id)
147 )
148 """)
150 def _create_reflections_table(self, conn: sqlite3.Connection) -> None:
151 """Create team_reflections table."""
152 conn.execute("""
153 CREATE TABLE IF NOT EXISTS team_reflections (
154 id TEXT PRIMARY KEY,
155 content TEXT NOT NULL,
156 tags TEXT, -- JSON array
157 access_level TEXT NOT NULL,
158 team_id TEXT,
159 project_id TEXT,
160 author_id TEXT NOT NULL,
161 created_at TIMESTAMP,
162 updated_at TIMESTAMP,
163 votes INTEGER DEFAULT 0,
164 viewers TEXT, -- JSON array
165 editors TEXT, -- JSON array
166 FOREIGN KEY (author_id) REFERENCES users(user_id),
167 FOREIGN KEY (team_id) REFERENCES teams(team_id)
168 )
169 """)
171 def _create_access_logs_table(self, conn: sqlite3.Connection) -> None:
172 """Create access_logs table."""
173 conn.execute("""
174 CREATE TABLE IF NOT EXISTS access_logs (
175 id INTEGER PRIMARY KEY AUTOINCREMENT,
176 user_id TEXT NOT NULL,
177 action TEXT NOT NULL,
178 resource_id TEXT,
179 resource_type TEXT,
180 timestamp TIMESTAMP,
181 details TEXT -- JSON object
182 )
183 """)
185 def _create_indices(self, conn: sqlite3.Connection) -> None:
186 """Create database indices."""
187 indices = [
188 "CREATE INDEX IF NOT EXISTS idx_reflections_team ON team_reflections(team_id)",
189 "CREATE INDEX IF NOT EXISTS idx_reflections_project ON team_reflections(project_id)",
190 "CREATE INDEX IF NOT EXISTS idx_reflections_author ON team_reflections(author_id)",
191 "CREATE INDEX IF NOT EXISTS idx_reflections_access ON team_reflections(access_level)",
192 "CREATE INDEX IF NOT EXISTS idx_access_logs_user ON access_logs(user_id)",
193 "CREATE INDEX IF NOT EXISTS idx_access_logs_timestamp ON access_logs(timestamp)",
194 ]
196 for index_sql in indices:
197 conn.execute(index_sql)
199 async def create_user(
200 self,
201 user_id: str,
202 username: str,
203 email: str | None = None,
204 role: UserRole = UserRole.CONTRIBUTOR,
205 ) -> TeamUser:
206 """Create a new team user."""
207 user = TeamUser(
208 user_id=user_id,
209 username=username,
210 email=email,
211 role=role,
212 teams=[],
213 created_at=datetime.now(),
214 last_active=datetime.now(),
215 permissions=self._get_default_permissions(role),
216 )
218 with sqlite3.connect(self.db_path) as conn:
219 conn.execute(
220 """
221 INSERT INTO users (user_id, username, email, role, teams, created_at, last_active, permissions)
222 VALUES (?, ?, ?, ?, ?, ?, ?, ?)
223 """,
224 (
225 user.user_id,
226 user.username,
227 user.email,
228 user.role.value,
229 json.dumps(user.teams),
230 user.created_at,
231 user.last_active,
232 json.dumps(user.permissions),
233 ),
234 )
236 await self._log_access(
237 user_id,
238 "user_created",
239 user_id,
240 "user",
241 {"role": role.value},
242 )
243 return user
245 async def create_team(
246 self,
247 team_id: str,
248 name: str,
249 description: str,
250 owner_id: str,
251 ) -> Team:
252 """Create a new team."""
253 team = Team(
254 team_id=team_id,
255 name=name,
256 description=description,
257 owner_id=owner_id,
258 members={owner_id},
259 projects=set(),
260 created_at=datetime.now(),
261 settings={"auto_approve_members": False, "public_reflections": True},
262 )
264 with sqlite3.connect(self.db_path) as conn:
265 conn.execute(
266 """
267 INSERT INTO teams (team_id, name, description, owner_id, members, projects, created_at, settings)
268 VALUES (?, ?, ?, ?, ?, ?, ?, ?)
269 """,
270 (
271 team.team_id,
272 team.name,
273 team.description,
274 team.owner_id,
275 json.dumps(list(team.members)),
276 json.dumps(list(team.projects)),
277 team.created_at,
278 json.dumps(team.settings),
279 ),
280 )
282 # Add owner to team
283 await self._add_user_to_team(owner_id, team_id)
284 await self._log_access(
285 owner_id,
286 "team_created",
287 team_id,
288 "team",
289 {"name": name},
290 )
291 return team
293 async def add_team_reflection(
294 self,
295 content: str,
296 author_id: str,
297 tags: list[str] | None = None,
298 access_level: AccessLevel = AccessLevel.TEAM,
299 team_id: str | None = None,
300 project_id: str | None = None,
301 ) -> str:
302 """Add reflection to team knowledge base."""
303 reflection_id = hashlib.sha256(
304 f"{content}{author_id}{time.time()}".encode(),
305 ).hexdigest()[:16]
307 reflection = TeamReflection(
308 id=reflection_id,
309 content=content,
310 tags=tags or [],
311 access_level=access_level,
312 team_id=team_id,
313 project_id=project_id,
314 author_id=author_id,
315 created_at=datetime.now(),
316 updated_at=datetime.now(),
317 votes=0,
318 viewers=set(),
319 editors=set(),
320 )
322 with sqlite3.connect(self.db_path) as conn:
323 conn.execute(
324 """
325 INSERT INTO team_reflections
326 (id, content, tags, access_level, team_id, project_id, author_id, created_at, updated_at, votes, viewers, editors)
327 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
328 """,
329 (
330 reflection.id,
331 reflection.content,
332 json.dumps(reflection.tags),
333 reflection.access_level.value,
334 reflection.team_id,
335 reflection.project_id,
336 reflection.author_id,
337 reflection.created_at,
338 reflection.updated_at,
339 reflection.votes,
340 json.dumps(list(reflection.viewers)),
341 json.dumps(list(reflection.editors)),
342 ),
343 )
345 await self._log_access(
346 author_id,
347 "reflection_created",
348 reflection_id,
349 "reflection",
350 {"team_id": team_id, "access_level": access_level.value},
351 )
352 return reflection_id
354 async def search_team_reflections(
355 self,
356 query: str,
357 user_id: str,
358 team_id: str | None = None,
359 project_id: str | None = None,
360 tags: list[str] | None = None,
361 limit: int = 20,
362 ) -> list[dict[str, Any]]:
363 """Search team reflections with access control."""
364 user_teams = await self._get_user_teams(user_id)
366 with sqlite3.connect(self.db_path) as conn:
367 conn.row_factory = sqlite3.Row
369 query_builder = self._build_search_query(
370 user_teams,
371 user_id,
372 query,
373 team_id,
374 project_id,
375 tags,
376 limit,
377 )
378 cursor = conn.execute(query_builder.sql, query_builder.params)
379 results = self._process_search_results(cursor.fetchall())
381 await self._log_access(
382 user_id,
383 "reflections_searched",
384 None,
385 "search",
386 {"query": query, "results_count": len(results)},
387 )
388 return results
390 @dataclass(frozen=True)
391 class _SearchQueryBuilder:
392 """Immutable search query builder result."""
394 sql: str
395 params: list[str | int]
397 def _build_search_query(
398 self,
399 user_teams: list[str],
400 user_id: str,
401 query: str,
402 team_id: str | None,
403 project_id: str | None,
404 tags: list[str] | None,
405 limit: int,
406 ) -> _SearchQueryBuilder:
407 """Build parameterized search query with conditions."""
408 where_conditions = ["1=1"]
409 params: list[str | int] = []
411 # Add access control conditions
412 access_condition, access_params = self._build_access_condition(
413 user_teams,
414 user_id,
415 )
416 where_conditions.append(access_condition)
417 params.extend(access_params)
419 # Add content search conditions
420 if query:
421 where_conditions.append("(content LIKE ? OR tags LIKE ?)")
422 params.extend([f"%{query}%", f"%{query}%"])
424 # Add filter conditions
425 self._add_filter_conditions(where_conditions, params, team_id, project_id, tags)
427 # Build SQL safely - all user input is parameterized via params list
428 query_sql = (
429 "SELECT * FROM team_reflections WHERE "
430 + " AND ".join(where_conditions)
431 + " ORDER BY votes DESC, created_at DESC LIMIT ?"
432 )
433 params.append(limit)
435 return self._SearchQueryBuilder(sql=query_sql, params=params)
437 def _build_access_condition(
438 self,
439 user_teams: list[str],
440 user_id: str,
441 ) -> tuple[str, list[str]]:
442 """Build access control condition for query."""
443 placeholders = ",".join("?" * len(user_teams))
444 access_condition = f"""(
445 access_level = 'public' OR
446 (access_level = 'team' AND team_id IN ({placeholders}) AND team_id IS NOT NULL) OR
447 author_id = ?
448 )"""
449 access_params = [*user_teams, user_id]
450 return access_condition, access_params
452 def _add_filter_conditions(
453 self,
454 where_conditions: list[str],
455 params: list[str | int],
456 team_id: str | None,
457 project_id: str | None,
458 tags: list[str] | None,
459 ) -> None:
460 """Add filter conditions to query builder."""
461 if team_id:
462 where_conditions.append("team_id = ?")
463 params.append(team_id)
465 if project_id:
466 where_conditions.append("project_id = ?")
467 params.append(project_id)
469 if tags:
470 tag_conditions = []
471 for tag in tags:
472 params.append(f"%{tag}%") # type: ignore[func-returns-value]
473 tag_conditions.append("tags LIKE ?")
474 where_conditions.append(f"({' OR '.join(tag_conditions)})")
476 def _process_search_results(self, rows: list[sqlite3.Row]) -> list[dict[str, Any]]:
477 """Process and transform search results."""
478 return [
479 dict(row)
480 | {
481 "tags": json.loads(row["tags"] or "[]"),
482 "viewers": json.loads(row["viewers"] or "[]"),
483 "editors": json.loads(row["editors"] or "[]"),
484 }
485 for row in rows
486 ]
488 async def vote_reflection(
489 self,
490 reflection_id: str,
491 user_id: str,
492 vote_delta: int = 1,
493 ) -> bool:
494 """Vote on a team reflection."""
495 if not await self._can_access_reflection(reflection_id, user_id):
496 return False
498 with sqlite3.connect(self.db_path) as conn:
499 conn.execute(
500 """
501 UPDATE team_reflections
502 SET votes = votes + ?, updated_at = ?
503 WHERE id = ?
504 """,
505 (vote_delta, datetime.now(), reflection_id),
506 )
508 await self._log_access(
509 user_id,
510 "reflection_voted",
511 reflection_id,
512 "reflection",
513 {"vote_delta": vote_delta},
514 )
515 return True
517 async def join_team(
518 self,
519 user_id: str,
520 team_id: str,
521 requester_id: str | None = None,
522 ) -> bool:
523 """Request to join a team or add user to team."""
524 team = await self._get_team(team_id)
525 if not team:
526 return False
528 # Check if requester has permission to add users
529 if requester_id and requester_id != user_id:
530 if not await self._can_manage_team(requester_id, team_id):
531 return False
533 await self._add_user_to_team(user_id, team_id)
534 await self._log_access(
535 user_id,
536 "team_joined",
537 team_id,
538 "team",
539 {"requester_id": requester_id},
540 )
541 return True
543 async def get_team_stats(self, team_id: str, user_id: str) -> dict[str, Any] | None:
544 """Get team statistics and activity."""
545 if not await self._can_access_team(user_id, team_id):
546 return None
548 with sqlite3.connect(self.db_path) as conn:
549 conn.row_factory = sqlite3.Row
551 # Get team info
552 team_row = conn.execute(
553 "SELECT * FROM teams WHERE team_id = ?",
554 (team_id,),
555 ).fetchone()
556 if not team_row:
557 return None
559 team_data = dict(team_row)
560 team_data["members"] = json.loads(team_data["members"] or "[]")
561 team_data["projects"] = json.loads(team_data["projects"] or "[]")
563 # Get reflection stats
564 reflection_stats = conn.execute(
565 """
566 SELECT
567 COUNT(*) as total_reflections,
568 COUNT(DISTINCT author_id) as active_contributors,
569 SUM(votes) as total_votes,
570 AVG(votes) as avg_votes
571 FROM team_reflections
572 WHERE team_id = ?
573 """,
574 (team_id,),
575 ).fetchone()
577 # Get recent activity
578 recent_activity = conn.execute(
579 """
580 SELECT COUNT(*) as recent_reflections
581 FROM team_reflections
582 WHERE team_id = ? AND created_at > ?
583 """,
584 (team_id, datetime.now() - timedelta(days=7)),
585 ).fetchone()
587 stats = {
588 "team": team_data.copy(),
589 "member_count": len(team_data["members"]),
590 "project_count": len(team_data["projects"]),
591 "reflection_stats": dict(reflection_stats),
592 "recent_activity": dict(recent_activity),
593 }
595 await self._log_access(user_id, "team_stats_viewed", team_id, "team", {})
596 return stats
598 async def get_user_permissions(self, user_id: str) -> dict[str, Any]:
599 """Get user's current permissions and team memberships."""
600 with sqlite3.connect(self.db_path) as conn:
601 conn.row_factory = sqlite3.Row
602 user_row = conn.execute(
603 "SELECT * FROM users WHERE user_id = ?",
604 (user_id,),
605 ).fetchone()
607 if not user_row:
608 return {}
610 user_data = dict(user_row)
611 user_data["teams"] = json.loads(user_data["teams"] or "[]")
612 user_data["permissions"] = json.loads(user_data["permissions"] or "{}")
614 # Get team details
615 team_details = []
616 if user_data["teams"]:
617 placeholders = ",".join("?" * len(user_data["teams"]))
618 # Build SQL safely - placeholders generated from list length, not user input
619 query = (
620 "SELECT team_id, name, description FROM teams WHERE team_id IN ("
621 + placeholders
622 + ")"
623 )
624 team_rows = conn.execute(query, user_data["teams"]).fetchall()
625 team_details = [dict(row) for row in team_rows]
627 return {
628 "user": user_data,
629 "teams": team_details,
630 "can_create_teams": user_data["permissions"].get("create_teams", False),
631 "can_moderate": user_data["permissions"].get("moderate_content", False),
632 }
634 # Private helper methods
636 def _get_default_permissions(self, role: UserRole) -> dict[str, bool]:
637 """Get default permissions for user role."""
638 base_permissions = {
639 "read_reflections": True,
640 "create_reflections": False,
641 "vote_reflections": False,
642 "join_teams": False,
643 "create_teams": False,
644 "moderate_content": False,
645 "admin_access": False,
646 }
648 if role == UserRole.CONTRIBUTOR:
649 base_permissions.update(
650 {
651 "create_reflections": True,
652 "vote_reflections": True,
653 "join_teams": True,
654 },
655 )
656 elif role == UserRole.MODERATOR:
657 base_permissions.update(
658 {
659 "create_reflections": True,
660 "vote_reflections": True,
661 "join_teams": True,
662 "create_teams": True,
663 "moderate_content": True,
664 },
665 )
666 elif role == UserRole.ADMIN:
667 base_permissions.update(dict.fromkeys(base_permissions.keys(), True))
669 return base_permissions
671 async def _add_user_to_team(self, user_id: str, team_id: str) -> None:
672 """Add user to team."""
673 with sqlite3.connect(self.db_path) as conn:
674 # Update team members
675 team_row = conn.execute(
676 "SELECT members FROM teams WHERE team_id = ?",
677 (team_id,),
678 ).fetchone()
679 if team_row:
680 members = set(json.loads(team_row[0] or "[]"))
681 members.add(user_id)
682 conn.execute(
683 "UPDATE teams SET members = ? WHERE team_id = ?",
684 (json.dumps(list(members)), team_id),
685 )
687 # Update user teams
688 user_row = conn.execute(
689 "SELECT teams FROM users WHERE user_id = ?",
690 (user_id,),
691 ).fetchone()
692 if user_row:
693 teams = json.loads(user_row[0] or "[]")
694 if team_id not in teams:
695 teams.append(team_id)
696 conn.execute(
697 "UPDATE users SET teams = ?, last_active = ? WHERE user_id = ?",
698 (json.dumps(teams), datetime.now(), user_id),
699 )
701 async def _get_user_teams(self, user_id: str) -> list[str]:
702 """Get teams user belongs to."""
703 with sqlite3.connect(self.db_path) as conn:
704 row = conn.execute(
705 "SELECT teams FROM users WHERE user_id = ?",
706 (user_id,),
707 ).fetchone()
708 return json.loads(row[0] or "[]") if row else []
710 async def _get_team(self, team_id: str) -> dict[str, Any] | None:
711 """Get team information."""
712 with sqlite3.connect(self.db_path) as conn:
713 conn.row_factory = sqlite3.Row
714 row = conn.execute(
715 "SELECT * FROM teams WHERE team_id = ?",
716 (team_id,),
717 ).fetchone()
718 if row:
719 team_data = dict(row)
720 team_data["members"] = set(json.loads(team_data["members"] or "[]"))
721 team_data["projects"] = set(json.loads(team_data["projects"] or "[]"))
722 team_data["settings"] = json.loads(team_data["settings"] or "{}")
723 return team_data
724 return None
726 async def _can_access_reflection(self, reflection_id: str, user_id: str) -> bool:
727 """Check if user can access reflection."""
728 with sqlite3.connect(self.db_path) as conn:
729 row = conn.execute(
730 """
731 SELECT access_level, team_id, author_id FROM team_reflections
732 WHERE id = ?
733 """,
734 (reflection_id,),
735 ).fetchone()
737 if not row:
738 return False
740 access_level, team_id, author_id = row
742 # Author can always access
743 if author_id == user_id:
744 return True
746 # Public reflections accessible to all
747 if access_level == AccessLevel.PUBLIC.value:
748 return True
750 # Team reflections require team membership
751 if access_level == AccessLevel.TEAM.value and team_id:
752 user_teams = await self._get_user_teams(user_id)
753 return team_id in user_teams
755 return False
757 async def _can_access_team(self, user_id: str, team_id: str) -> bool:
758 """Check if user can access team."""
759 user_teams = await self._get_user_teams(user_id)
760 return team_id in user_teams
762 async def _can_manage_team(self, user_id: str, team_id: str) -> bool:
763 """Check if user can manage team."""
764 team = await self._get_team(team_id)
765 if not team:
766 return False
768 # Team owner can manage
769 if team["owner_id"] == user_id:
770 return True
772 # Check if user has admin permissions
773 with sqlite3.connect(self.db_path) as conn:
774 row = conn.execute(
775 "SELECT permissions FROM users WHERE user_id = ?",
776 (user_id,),
777 ).fetchone()
778 if row:
779 permissions = json.loads(row[0] or "{}")
780 return bool(
781 permissions.get("admin_access", False)
782 or permissions.get(
783 "moderate_content",
784 False,
785 ),
786 )
788 return False
790 async def _log_access(
791 self,
792 user_id: str,
793 action: str,
794 resource_id: str | None,
795 resource_type: str,
796 details: dict[str, Any],
797 ) -> None:
798 """Log user access for audit trail."""
799 with sqlite3.connect(self.db_path) as conn:
800 conn.execute(
801 """
802 INSERT INTO access_logs (user_id, action, resource_id, resource_type, timestamp, details)
803 VALUES (?, ?, ?, ?, ?, ?)
804 """,
805 (
806 user_id,
807 action,
808 resource_id,
809 resource_type,
810 datetime.now(),
811 json.dumps(details),
812 ),
813 )
816# Global instance
817_team_knowledge_manager = None
820def get_team_knowledge_manager() -> TeamKnowledgeManager:
821 """Get global team knowledge manager instance."""
822 global _team_knowledge_manager
823 if _team_knowledge_manager is None:
824 _team_knowledge_manager = TeamKnowledgeManager()
825 return _team_knowledge_manager
828# Public API functions for MCP tools
829async def create_team_user(
830 user_id: str,
831 username: str,
832 email: str | None = None,
833 role: str = "contributor",
834) -> dict[str, Any]:
835 """Create a new team user."""
836 manager = get_team_knowledge_manager()
837 user_role = UserRole(role.lower())
838 user = await manager.create_user(user_id, username, email, user_role)
839 return asdict(user)
842async def create_team(
843 team_id: str,
844 name: str,
845 description: str,
846 owner_id: str,
847) -> dict[str, Any]:
848 """Create a new team."""
849 manager = get_team_knowledge_manager()
850 team = await manager.create_team(team_id, name, description, owner_id)
851 return {
852 "team_id": team.team_id,
853 "name": team.name,
854 "description": team.description,
855 "owner_id": team.owner_id,
856 "member_count": len(team.members),
857 "project_count": len(team.projects),
858 "created_at": team.created_at.isoformat(),
859 "settings": team.settings,
860 }
863async def add_team_reflection(
864 content: str,
865 author_id: str,
866 tags: list[str] | None = None,
867 access_level: str = "team",
868 team_id: str | None = None,
869 project_id: str | None = None,
870) -> str:
871 """Add reflection to team knowledge base."""
872 manager = get_team_knowledge_manager()
873 level = AccessLevel(access_level.lower())
874 return await manager.add_team_reflection(
875 content,
876 author_id,
877 tags,
878 level,
879 team_id,
880 project_id,
881 )
884async def search_team_knowledge(
885 query: str,
886 user_id: str,
887 team_id: str | None = None,
888 project_id: str | None = None,
889 tags: list[str] | None = None,
890 limit: int = 20,
891) -> list[dict[str, Any]]:
892 """Search team reflections with access control."""
893 manager = get_team_knowledge_manager()
894 return await manager.search_team_reflections(
895 query,
896 user_id,
897 team_id,
898 project_id,
899 tags,
900 limit,
901 )
904async def join_team(
905 user_id: str,
906 team_id: str,
907 requester_id: str | None = None,
908) -> bool:
909 """Join a team or add user to team."""
910 manager = get_team_knowledge_manager()
911 return await manager.join_team(user_id, team_id, requester_id)
914async def get_team_statistics(team_id: str, user_id: str) -> dict[str, Any] | None:
915 """Get team statistics and activity."""
916 manager = get_team_knowledge_manager()
917 return await manager.get_team_stats(team_id, user_id)
920async def get_user_team_permissions(user_id: str) -> dict[str, Any]:
921 """Get user's permissions and team memberships."""
922 manager = get_team_knowledge_manager()
923 return await manager.get_user_permissions(user_id)
926async def vote_on_reflection(
927 reflection_id: str,
928 user_id: str,
929 vote_delta: int = 1,
930) -> bool:
931 """Vote on a team reflection."""
932 manager = get_team_knowledge_manager()
933 return await manager.vote_reflection(reflection_id, user_id, vote_delta)