Coverage for session_mgmt_mcp/team_knowledge.py: 0.00%

284 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-01 05:22 -0700

1"""Team Knowledge Base module for shared reflection database with permissions. 

2 

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""" 

9 

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 

22 

23DUCKDB_AVAILABLE = importlib.util.find_spec("duckdb") is not None 

24AIOFILES_AVAILABLE = importlib.util.find_spec("aiofiles") is not None 

25 

26logger = logging.getLogger(__name__) 

27 

28 

29class UserRole(Enum): 

30 """User roles in team knowledge base.""" 

31 

32 VIEWER = "viewer" 

33 CONTRIBUTOR = "contributor" 

34 MODERATOR = "moderator" 

35 ADMIN = "admin" 

36 

37 

38class AccessLevel(Enum): 

39 """Access levels for knowledge base content.""" 

40 

41 PRIVATE = "private" 

42 TEAM = "team" 

43 PROJECT = "project" 

44 PUBLIC = "public" 

45 

46 

47@dataclass 

48class TeamUser: 

49 """Team user information.""" 

50 

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] 

59 

60 

61@dataclass 

62class TeamReflection: 

63 """Team-shared reflection with access control.""" 

64 

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] 

77 

78 

79@dataclass 

80class Team: 

81 """Team information and configuration.""" 

82 

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] 

91 

92 

93class TeamKnowledgeManager: 

94 """Manages team knowledge base with permissions and access control.""" 

95 

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() 

103 

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) 

107 

108 with sqlite3.connect(self.db_path) as conn: 

109 conn.execute(""" 

110 CREATE TABLE IF NOT EXISTS users ( 

111 user_id TEXT PRIMARY KEY, 

112 username TEXT UNIQUE NOT NULL, 

113 email TEXT, 

114 role TEXT NOT NULL, 

115 teams TEXT, -- JSON array 

116 created_at TIMESTAMP, 

117 last_active TIMESTAMP, 

118 permissions TEXT -- JSON object 

119 ) 

120 """) 

121 

122 conn.execute(""" 

123 CREATE TABLE IF NOT EXISTS teams ( 

124 team_id TEXT PRIMARY KEY, 

125 name TEXT NOT NULL, 

126 description TEXT, 

127 owner_id TEXT NOT NULL, 

128 members TEXT, -- JSON array 

129 projects TEXT, -- JSON array 

130 created_at TIMESTAMP, 

131 settings TEXT, -- JSON object 

132 FOREIGN KEY (owner_id) REFERENCES users(user_id) 

133 ) 

134 """) 

135 

136 conn.execute(""" 

137 CREATE TABLE IF NOT EXISTS team_reflections ( 

138 id TEXT PRIMARY KEY, 

139 content TEXT NOT NULL, 

140 tags TEXT, -- JSON array 

141 access_level TEXT NOT NULL, 

142 team_id TEXT, 

143 project_id TEXT, 

144 author_id TEXT NOT NULL, 

145 created_at TIMESTAMP, 

146 updated_at TIMESTAMP, 

147 votes INTEGER DEFAULT 0, 

148 viewers TEXT, -- JSON array 

149 editors TEXT, -- JSON array 

150 FOREIGN KEY (author_id) REFERENCES users(user_id), 

151 FOREIGN KEY (team_id) REFERENCES teams(team_id) 

152 ) 

153 """) 

154 

155 conn.execute(""" 

156 CREATE TABLE IF NOT EXISTS access_logs ( 

157 id INTEGER PRIMARY KEY AUTOINCREMENT, 

158 user_id TEXT NOT NULL, 

159 action TEXT NOT NULL, 

160 resource_id TEXT, 

161 resource_type TEXT, 

162 timestamp TIMESTAMP, 

163 details TEXT -- JSON object 

164 ) 

165 """) 

166 

167 # Create indices 

168 conn.execute( 

169 "CREATE INDEX IF NOT EXISTS idx_reflections_team ON team_reflections(team_id)", 

170 ) 

171 conn.execute( 

172 "CREATE INDEX IF NOT EXISTS idx_reflections_project ON team_reflections(project_id)", 

173 ) 

174 conn.execute( 

175 "CREATE INDEX IF NOT EXISTS idx_reflections_author ON team_reflections(author_id)", 

176 ) 

177 conn.execute( 

178 "CREATE INDEX IF NOT EXISTS idx_reflections_access ON team_reflections(access_level)", 

179 ) 

180 conn.execute( 

181 "CREATE INDEX IF NOT EXISTS idx_access_logs_user ON access_logs(user_id)", 

182 ) 

183 conn.execute( 

184 "CREATE INDEX IF NOT EXISTS idx_access_logs_timestamp ON access_logs(timestamp)", 

185 ) 

186 

187 async def create_user( 

188 self, 

189 user_id: str, 

190 username: str, 

191 email: str | None = None, 

192 role: UserRole = UserRole.CONTRIBUTOR, 

193 ) -> TeamUser: 

194 """Create a new team user.""" 

195 user = TeamUser( 

196 user_id=user_id, 

197 username=username, 

198 email=email, 

199 role=role, 

200 teams=[], 

201 created_at=datetime.now(), 

202 last_active=datetime.now(), 

203 permissions=self._get_default_permissions(role), 

204 ) 

205 

206 with sqlite3.connect(self.db_path) as conn: 

207 conn.execute( 

208 """ 

209 INSERT INTO users (user_id, username, email, role, teams, created_at, last_active, permissions) 

210 VALUES (?, ?, ?, ?, ?, ?, ?, ?) 

211 """, 

212 ( 

213 user.user_id, 

214 user.username, 

215 user.email, 

216 user.role.value, 

217 json.dumps(user.teams), 

218 user.created_at, 

219 user.last_active, 

220 json.dumps(user.permissions), 

221 ), 

222 ) 

223 

224 await self._log_access( 

225 user_id, 

226 "user_created", 

227 user_id, 

228 "user", 

229 {"role": role.value}, 

230 ) 

231 return user 

232 

233 async def create_team( 

234 self, 

235 team_id: str, 

236 name: str, 

237 description: str, 

238 owner_id: str, 

239 ) -> Team: 

240 """Create a new team.""" 

241 team = Team( 

242 team_id=team_id, 

243 name=name, 

244 description=description, 

245 owner_id=owner_id, 

246 members={owner_id}, 

247 projects=set(), 

248 created_at=datetime.now(), 

249 settings={"auto_approve_members": False, "public_reflections": True}, 

250 ) 

251 

252 with sqlite3.connect(self.db_path) as conn: 

253 conn.execute( 

254 """ 

255 INSERT INTO teams (team_id, name, description, owner_id, members, projects, created_at, settings) 

256 VALUES (?, ?, ?, ?, ?, ?, ?, ?) 

257 """, 

258 ( 

259 team.team_id, 

260 team.name, 

261 team.description, 

262 team.owner_id, 

263 json.dumps(list(team.members)), 

264 json.dumps(list(team.projects)), 

265 team.created_at, 

266 json.dumps(team.settings), 

267 ), 

268 ) 

269 

270 # Add owner to team 

271 await self._add_user_to_team(owner_id, team_id) 

272 await self._log_access( 

273 owner_id, 

274 "team_created", 

275 team_id, 

276 "team", 

277 {"name": name}, 

278 ) 

279 return team 

280 

281 async def add_team_reflection( 

282 self, 

283 content: str, 

284 author_id: str, 

285 tags: list[str] | None = None, 

286 access_level: AccessLevel = AccessLevel.TEAM, 

287 team_id: str | None = None, 

288 project_id: str | None = None, 

289 ) -> str: 

290 """Add reflection to team knowledge base.""" 

291 reflection_id = hashlib.sha256( 

292 f"{content}{author_id}{time.time()}".encode(), 

293 ).hexdigest()[:16] 

294 

295 reflection = TeamReflection( 

296 id=reflection_id, 

297 content=content, 

298 tags=tags or [], 

299 access_level=access_level, 

300 team_id=team_id, 

301 project_id=project_id, 

302 author_id=author_id, 

303 created_at=datetime.now(), 

304 updated_at=datetime.now(), 

305 votes=0, 

306 viewers=set(), 

307 editors=set(), 

308 ) 

309 

310 with sqlite3.connect(self.db_path) as conn: 

311 conn.execute( 

312 """ 

313 INSERT INTO team_reflections 

314 (id, content, tags, access_level, team_id, project_id, author_id, created_at, updated_at, votes, viewers, editors) 

315 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 

316 """, 

317 ( 

318 reflection.id, 

319 reflection.content, 

320 json.dumps(reflection.tags), 

321 reflection.access_level.value, 

322 reflection.team_id, 

323 reflection.project_id, 

324 reflection.author_id, 

325 reflection.created_at, 

326 reflection.updated_at, 

327 reflection.votes, 

328 json.dumps(list(reflection.viewers)), 

329 json.dumps(list(reflection.editors)), 

330 ), 

331 ) 

332 

333 await self._log_access( 

334 author_id, 

335 "reflection_created", 

336 reflection_id, 

337 "reflection", 

338 {"team_id": team_id, "access_level": access_level.value}, 

339 ) 

340 return reflection_id 

341 

342 async def search_team_reflections( 

343 self, 

344 query: str, 

345 user_id: str, 

346 team_id: str | None = None, 

347 project_id: str | None = None, 

348 tags: list[str] | None = None, 

349 limit: int = 20, 

350 ) -> list[dict[str, Any]]: 

351 """Search team reflections with access control.""" 

352 user_teams = await self._get_user_teams(user_id) 

353 

354 with sqlite3.connect(self.db_path) as conn: 

355 conn.row_factory = sqlite3.Row 

356 

357 # Build query with access control 

358 where_conditions = ["1=1"] 

359 params = [] 

360 

361 # Access control: user can see public, team reflections they have access to, or their own 

362 access_condition = """( 

363 access_level = 'public' OR 

364 (access_level = 'team' AND team_id IN ({}) AND team_id IS NOT NULL) OR 

365 author_id = ? 

366 )""".format(",".join("?" * len(user_teams))) 

367 where_conditions.append(access_condition) 

368 params.extend(user_teams) 

369 params.append(user_id) 

370 

371 # Content search 

372 if query: 

373 where_conditions.append("(content LIKE ? OR tags LIKE ?)") 

374 params.extend([f"%{query}%", f"%{query}%"]) 

375 

376 # Team filter 

377 if team_id: 

378 where_conditions.append("team_id = ?") 

379 params.append(team_id) 

380 

381 # Project filter 

382 if project_id: 

383 where_conditions.append("project_id = ?") 

384 params.append(project_id) 

385 

386 # Tag filter 

387 if tags: 

388 tag_conditions = [] 

389 for tag in tags: 

390 tag_conditions.append("tags LIKE ?") 

391 params.append(f"%{tag}%") 

392 where_conditions.append(f"({' OR '.join(tag_conditions)})") 

393 

394 query_sql = f""" 

395 SELECT * FROM team_reflections 

396 WHERE {" AND ".join(where_conditions)} 

397 ORDER BY votes DESC, created_at DESC 

398 LIMIT ? 

399 """ 

400 params.append(limit) 

401 

402 cursor = conn.execute(query_sql, params) 

403 results = [] 

404 

405 for row in cursor.fetchall(): 

406 result = dict(row) 

407 result["tags"] = json.loads(result["tags"] or "[]") 

408 result["viewers"] = json.loads(result["viewers"] or "[]") 

409 result["editors"] = json.loads(result["editors"] or "[]") 

410 results.append(result) 

411 

412 await self._log_access( 

413 user_id, 

414 "reflections_searched", 

415 None, 

416 "search", 

417 {"query": query, "results_count": len(results)}, 

418 ) 

419 return results 

420 

421 async def vote_reflection( 

422 self, 

423 reflection_id: str, 

424 user_id: str, 

425 vote_delta: int = 1, 

426 ) -> bool: 

427 """Vote on a team reflection.""" 

428 if not await self._can_access_reflection(reflection_id, user_id): 

429 return False 

430 

431 with sqlite3.connect(self.db_path) as conn: 

432 conn.execute( 

433 """ 

434 UPDATE team_reflections 

435 SET votes = votes + ?, updated_at = ? 

436 WHERE id = ? 

437 """, 

438 (vote_delta, datetime.now(), reflection_id), 

439 ) 

440 

441 await self._log_access( 

442 user_id, 

443 "reflection_voted", 

444 reflection_id, 

445 "reflection", 

446 {"vote_delta": vote_delta}, 

447 ) 

448 return True 

449 

450 async def join_team( 

451 self, 

452 user_id: str, 

453 team_id: str, 

454 requester_id: str | None = None, 

455 ) -> bool: 

456 """Request to join a team or add user to team.""" 

457 team = await self._get_team(team_id) 

458 if not team: 

459 return False 

460 

461 # Check if requester has permission to add users 

462 if requester_id and requester_id != user_id: 

463 if not await self._can_manage_team(requester_id, team_id): 

464 return False 

465 

466 await self._add_user_to_team(user_id, team_id) 

467 await self._log_access( 

468 user_id, 

469 "team_joined", 

470 team_id, 

471 "team", 

472 {"requester_id": requester_id}, 

473 ) 

474 return True 

475 

476 async def get_team_stats(self, team_id: str, user_id: str) -> dict[str, Any] | None: 

477 """Get team statistics and activity.""" 

478 if not await self._can_access_team(user_id, team_id): 

479 return None 

480 

481 with sqlite3.connect(self.db_path) as conn: 

482 conn.row_factory = sqlite3.Row 

483 

484 # Get team info 

485 team_row = conn.execute( 

486 "SELECT * FROM teams WHERE team_id = ?", 

487 (team_id,), 

488 ).fetchone() 

489 if not team_row: 

490 return None 

491 

492 team_data = dict(team_row) 

493 team_data["members"] = json.loads(team_data["members"] or "[]") 

494 team_data["projects"] = json.loads(team_data["projects"] or "[]") 

495 

496 # Get reflection stats 

497 reflection_stats = conn.execute( 

498 """ 

499 SELECT 

500 COUNT(*) as total_reflections, 

501 COUNT(DISTINCT author_id) as active_contributors, 

502 SUM(votes) as total_votes, 

503 AVG(votes) as avg_votes 

504 FROM team_reflections 

505 WHERE team_id = ? 

506 """, 

507 (team_id,), 

508 ).fetchone() 

509 

510 # Get recent activity 

511 recent_activity = conn.execute( 

512 """ 

513 SELECT COUNT(*) as recent_reflections 

514 FROM team_reflections 

515 WHERE team_id = ? AND created_at > ? 

516 """, 

517 (team_id, datetime.now() - timedelta(days=7)), 

518 ).fetchone() 

519 

520 stats = { 

521 "team": dict(team_data), 

522 "member_count": len(team_data["members"]), 

523 "project_count": len(team_data["projects"]), 

524 "reflection_stats": dict(reflection_stats), 

525 "recent_activity": dict(recent_activity), 

526 } 

527 

528 await self._log_access(user_id, "team_stats_viewed", team_id, "team", {}) 

529 return stats 

530 

531 async def get_user_permissions(self, user_id: str) -> dict[str, Any]: 

532 """Get user's current permissions and team memberships.""" 

533 with sqlite3.connect(self.db_path) as conn: 

534 conn.row_factory = sqlite3.Row 

535 user_row = conn.execute( 

536 "SELECT * FROM users WHERE user_id = ?", 

537 (user_id,), 

538 ).fetchone() 

539 

540 if not user_row: 

541 return {} 

542 

543 user_data = dict(user_row) 

544 user_data["teams"] = json.loads(user_data["teams"] or "[]") 

545 user_data["permissions"] = json.loads(user_data["permissions"] or "{}") 

546 

547 # Get team details 

548 team_details = [] 

549 if user_data["teams"]: 

550 placeholders = ",".join("?" * len(user_data["teams"])) 

551 team_rows = conn.execute( 

552 f"SELECT team_id, name, description FROM teams WHERE team_id IN ({placeholders})", 

553 user_data["teams"], 

554 ).fetchall() 

555 team_details = [dict(row) for row in team_rows] 

556 

557 return { 

558 "user": user_data, 

559 "teams": team_details, 

560 "can_create_teams": user_data["permissions"].get("create_teams", False), 

561 "can_moderate": user_data["permissions"].get("moderate_content", False), 

562 } 

563 

564 # Private helper methods 

565 

566 def _get_default_permissions(self, role: UserRole) -> dict[str, bool]: 

567 """Get default permissions for user role.""" 

568 base_permissions = { 

569 "read_reflections": True, 

570 "create_reflections": False, 

571 "vote_reflections": False, 

572 "join_teams": False, 

573 "create_teams": False, 

574 "moderate_content": False, 

575 "admin_access": False, 

576 } 

577 

578 if role == UserRole.CONTRIBUTOR: 

579 base_permissions.update( 

580 { 

581 "create_reflections": True, 

582 "vote_reflections": True, 

583 "join_teams": True, 

584 }, 

585 ) 

586 elif role == UserRole.MODERATOR: 

587 base_permissions.update( 

588 { 

589 "create_reflections": True, 

590 "vote_reflections": True, 

591 "join_teams": True, 

592 "create_teams": True, 

593 "moderate_content": True, 

594 }, 

595 ) 

596 elif role == UserRole.ADMIN: 

597 base_permissions.update(dict.fromkeys(base_permissions.keys(), True)) 

598 

599 return base_permissions 

600 

601 async def _add_user_to_team(self, user_id: str, team_id: str) -> None: 

602 """Add user to team.""" 

603 with sqlite3.connect(self.db_path) as conn: 

604 # Update team members 

605 team_row = conn.execute( 

606 "SELECT members FROM teams WHERE team_id = ?", 

607 (team_id,), 

608 ).fetchone() 

609 if team_row: 

610 members = set(json.loads(team_row[0] or "[]")) 

611 members.add(user_id) 

612 conn.execute( 

613 "UPDATE teams SET members = ? WHERE team_id = ?", 

614 (json.dumps(list(members)), team_id), 

615 ) 

616 

617 # Update user teams 

618 user_row = conn.execute( 

619 "SELECT teams FROM users WHERE user_id = ?", 

620 (user_id,), 

621 ).fetchone() 

622 if user_row: 

623 teams = json.loads(user_row[0] or "[]") 

624 if team_id not in teams: 

625 teams.append(team_id) 

626 conn.execute( 

627 "UPDATE users SET teams = ?, last_active = ? WHERE user_id = ?", 

628 (json.dumps(teams), datetime.now(), user_id), 

629 ) 

630 

631 async def _get_user_teams(self, user_id: str) -> list[str]: 

632 """Get teams user belongs to.""" 

633 with sqlite3.connect(self.db_path) as conn: 

634 row = conn.execute( 

635 "SELECT teams FROM users WHERE user_id = ?", 

636 (user_id,), 

637 ).fetchone() 

638 return json.loads(row[0] or "[]") if row else [] 

639 

640 async def _get_team(self, team_id: str) -> dict[str, Any] | None: 

641 """Get team information.""" 

642 with sqlite3.connect(self.db_path) as conn: 

643 conn.row_factory = sqlite3.Row 

644 row = conn.execute( 

645 "SELECT * FROM teams WHERE team_id = ?", 

646 (team_id,), 

647 ).fetchone() 

648 if row: 

649 team_data = dict(row) 

650 team_data["members"] = set(json.loads(team_data["members"] or "[]")) 

651 team_data["projects"] = set(json.loads(team_data["projects"] or "[]")) 

652 team_data["settings"] = json.loads(team_data["settings"] or "{}") 

653 return team_data 

654 return None 

655 

656 async def _can_access_reflection(self, reflection_id: str, user_id: str) -> bool: 

657 """Check if user can access reflection.""" 

658 with sqlite3.connect(self.db_path) as conn: 

659 row = conn.execute( 

660 """ 

661 SELECT access_level, team_id, author_id FROM team_reflections 

662 WHERE id = ? 

663 """, 

664 (reflection_id,), 

665 ).fetchone() 

666 

667 if not row: 

668 return False 

669 

670 access_level, team_id, author_id = row 

671 

672 # Author can always access 

673 if author_id == user_id: 

674 return True 

675 

676 # Public reflections accessible to all 

677 if access_level == AccessLevel.PUBLIC.value: 

678 return True 

679 

680 # Team reflections require team membership 

681 if access_level == AccessLevel.TEAM.value and team_id: 

682 user_teams = await self._get_user_teams(user_id) 

683 return team_id in user_teams 

684 

685 return False 

686 

687 async def _can_access_team(self, user_id: str, team_id: str) -> bool: 

688 """Check if user can access team.""" 

689 user_teams = await self._get_user_teams(user_id) 

690 return team_id in user_teams 

691 

692 async def _can_manage_team(self, user_id: str, team_id: str) -> bool: 

693 """Check if user can manage team.""" 

694 team = await self._get_team(team_id) 

695 if not team: 

696 return False 

697 

698 # Team owner can manage 

699 if team["owner_id"] == user_id: 

700 return True 

701 

702 # Check if user has admin permissions 

703 with sqlite3.connect(self.db_path) as conn: 

704 row = conn.execute( 

705 "SELECT permissions FROM users WHERE user_id = ?", 

706 (user_id,), 

707 ).fetchone() 

708 if row: 

709 permissions = json.loads(row[0] or "{}") 

710 return permissions.get("admin_access", False) or permissions.get( 

711 "moderate_content", 

712 False, 

713 ) 

714 

715 return False 

716 

717 async def _log_access( 

718 self, 

719 user_id: str, 

720 action: str, 

721 resource_id: str | None, 

722 resource_type: str, 

723 details: dict[str, Any], 

724 ) -> None: 

725 """Log user access for audit trail.""" 

726 with sqlite3.connect(self.db_path) as conn: 

727 conn.execute( 

728 """ 

729 INSERT INTO access_logs (user_id, action, resource_id, resource_type, timestamp, details) 

730 VALUES (?, ?, ?, ?, ?, ?) 

731 """, 

732 ( 

733 user_id, 

734 action, 

735 resource_id, 

736 resource_type, 

737 datetime.now(), 

738 json.dumps(details), 

739 ), 

740 ) 

741 

742 

743# Global instance 

744_team_knowledge_manager = None 

745 

746 

747def get_team_knowledge_manager() -> TeamKnowledgeManager: 

748 """Get global team knowledge manager instance.""" 

749 global _team_knowledge_manager 

750 if _team_knowledge_manager is None: 

751 _team_knowledge_manager = TeamKnowledgeManager() 

752 return _team_knowledge_manager 

753 

754 

755# Public API functions for MCP tools 

756async def create_team_user( 

757 user_id: str, 

758 username: str, 

759 email: str | None = None, 

760 role: str = "contributor", 

761) -> dict[str, Any]: 

762 """Create a new team user.""" 

763 manager = get_team_knowledge_manager() 

764 user_role = UserRole(role.lower()) 

765 user = await manager.create_user(user_id, username, email, user_role) 

766 return asdict(user) 

767 

768 

769async def create_team( 

770 team_id: str, 

771 name: str, 

772 description: str, 

773 owner_id: str, 

774) -> dict[str, Any]: 

775 """Create a new team.""" 

776 manager = get_team_knowledge_manager() 

777 team = await manager.create_team(team_id, name, description, owner_id) 

778 return { 

779 "team_id": team.team_id, 

780 "name": team.name, 

781 "description": team.description, 

782 "owner_id": team.owner_id, 

783 "member_count": len(team.members), 

784 "project_count": len(team.projects), 

785 "created_at": team.created_at.isoformat(), 

786 "settings": team.settings, 

787 } 

788 

789 

790async def add_team_reflection( 

791 content: str, 

792 author_id: str, 

793 tags: list[str] | None = None, 

794 access_level: str = "team", 

795 team_id: str | None = None, 

796 project_id: str | None = None, 

797) -> str: 

798 """Add reflection to team knowledge base.""" 

799 manager = get_team_knowledge_manager() 

800 level = AccessLevel(access_level.lower()) 

801 return await manager.add_team_reflection( 

802 content, 

803 author_id, 

804 tags, 

805 level, 

806 team_id, 

807 project_id, 

808 ) 

809 

810 

811async def search_team_knowledge( 

812 query: str, 

813 user_id: str, 

814 team_id: str | None = None, 

815 project_id: str | None = None, 

816 tags: list[str] | None = None, 

817 limit: int = 20, 

818) -> list[dict[str, Any]]: 

819 """Search team reflections with access control.""" 

820 manager = get_team_knowledge_manager() 

821 return await manager.search_team_reflections( 

822 query, 

823 user_id, 

824 team_id, 

825 project_id, 

826 tags, 

827 limit, 

828 ) 

829 

830 

831async def join_team( 

832 user_id: str, 

833 team_id: str, 

834 requester_id: str | None = None, 

835) -> bool: 

836 """Join a team or add user to team.""" 

837 manager = get_team_knowledge_manager() 

838 return await manager.join_team(user_id, team_id, requester_id) 

839 

840 

841async def get_team_statistics(team_id: str, user_id: str) -> dict[str, Any] | None: 

842 """Get team statistics and activity.""" 

843 manager = get_team_knowledge_manager() 

844 return await manager.get_team_stats(team_id, user_id) 

845 

846 

847async def get_user_team_permissions(user_id: str) -> dict[str, Any]: 

848 """Get user's permissions and team memberships.""" 

849 manager = get_team_knowledge_manager() 

850 return await manager.get_user_permissions(user_id) 

851 

852 

853async def vote_on_reflection( 

854 reflection_id: str, 

855 user_id: str, 

856 vote_delta: int = 1, 

857) -> bool: 

858 """Vote on a team reflection.""" 

859 manager = get_team_knowledge_manager() 

860 return await manager.vote_reflection(reflection_id, user_id, vote_delta)