diff --git a/src/skillport/interfaces/cli/app.py b/src/skillport/interfaces/cli/app.py index 67d1943..7793a0a 100644 --- a/src/skillport/interfaces/cli/app.py +++ b/src/skillport/interfaces/cli/app.py @@ -44,8 +44,8 @@ def version_callback(value: bool): app = typer.Typer( name="skillport", help="[bold]⚓ SkillPort[/bold] - All Your Agent Skills in One Place\n\n" - "A CLI and MCP server for managing, searching, and serving skills to AI agents.\n\n" - "[dim]Docs: https://github.com/gotalab/skillport[/dim]", + "A CLI and MCP server for managing, searching, and serving skills to AI agents.\n\n" + "[dim]Docs: https://github.com/gotalab/skillport[/dim]", rich_markup_mode="rich", no_args_is_help=False, add_completion=True, @@ -110,100 +110,100 @@ def main( app.command( "init", help="Initialize SkillPort for a project.\n\n" - "[bold]Examples:[/bold]\n\n" - " skillport init\n\n" - " skillport init --yes\n\n" - " skillport init -d .agent/skills -i AGENTS.md", + "[bold]Examples:[/bold]\n\n" + " skillport init\n\n" + " skillport init --yes\n\n" + " skillport init -d .agent/skills -i AGENTS.md", )(init) app.command( "search", help="Search for skills matching a query.\n\n" - "[bold]Examples:[/bold]\n\n" - " skillport search 'PDF extraction'\n\n" - " skillport search code --limit 5\n\n" - " skillport search test --json", + "[bold]Examples:[/bold]\n\n" + " skillport search 'PDF extraction'\n\n" + " skillport search code --limit 5\n\n" + " skillport search test --json", )(search) app.command( "show", help="Show skill details and instructions.\n\n" - "[bold]Examples:[/bold]\n\n" - " skillport show hello-world\n\n" - " skillport show team/code-review\n\n" - " skillport show pdf --json", + "[bold]Examples:[/bold]\n\n" + " skillport show hello-world\n\n" + " skillport show team/code-review\n\n" + " skillport show pdf --json", )(show) app.command( "add", help="Add skills from various sources.\n\n" - "[bold]Sources:[/bold]\n\n" - " [dim]Built-in:[/dim] hello-world, template\n\n" - " [dim]Local:[/dim] ./my-skill/, ./collection/\n\n" - " [dim]GitHub:[/dim] https://github.com/user/repo\n\n" - "[bold]Examples:[/bold]\n\n" - " skillport add hello-world\n\n" - " skillport add ./my-skills/ --namespace team\n\n" - " skillport add https://github.com/user/repo --yes", + "[bold]Sources:[/bold]\n\n" + " [dim]Built-in:[/dim] hello-world, template\n\n" + " [dim]Local:[/dim] ./my-skill/, ./collection/\n\n" + " [dim]GitHub:[/dim] https://github.com/user/repo\n\n" + "[bold]Examples:[/bold]\n\n" + " skillport add hello-world\n\n" + " skillport add ./my-skills/ --namespace team\n\n" + " skillport add https://github.com/user/repo --yes", )(add) app.command( "list", help="List installed skills.\n\n" - "[bold]Examples:[/bold]\n\n" - " skillport list\n\n" - " skillport list --limit 20\n\n" - " skillport list --json", + "[bold]Examples:[/bold]\n\n" + " skillport list\n\n" + " skillport list --limit 20\n\n" + " skillport list --json", )(list_cmd) app.command( "remove", help="Remove an installed skill.\n\n" - "[bold]Examples:[/bold]\n\n" - " skillport remove hello-world\n\n" - " skillport remove team/skill --force", + "[bold]Examples:[/bold]\n\n" + " skillport remove hello-world\n\n" + " skillport remove team/skill --force", )(remove) app.command( "update", help="Update skills from their original sources.\n\n" - "By default shows available updates. Use --all to update all,\n" - "or specify a skill ID to update one.\n\n" - "[bold]Examples:[/bold]\n\n" - " skillport update\n\n" - " skillport update my-skill\n\n" - " skillport update --all\n\n" - " skillport update my-skill --force\n\n" - " skillport update --all --dry-run", + "By default shows available updates. Use --all to update all,\n" + "or specify a skill ID to update one.\n\n" + "[bold]Examples:[/bold]\n\n" + " skillport update\n\n" + " skillport update my-skill\n\n" + " skillport update --all\n\n" + " skillport update my-skill --force\n\n" + " skillport update --all --dry-run", )(update) app.command( "lint", help="Validate skill definitions.\n\n" - "[bold]Examples:[/bold]\n\n" - " skillport lint\n\n" - " skillport lint hello-world", + "[bold]Examples:[/bold]\n\n" + " skillport lint\n\n" + " skillport lint hello-world", )(lint) app.command( "serve", help="Start the MCP server.\n\n" - "By default, runs in stdio mode (Local) for direct agent integration.\n" - "Use --http for HTTP server (Remote) mode.\n\n" - "[bold]Examples:[/bold]\n\n" - " skillport serve\n\n" - " skillport serve --reindex\n\n" - " skillport serve --http --port 8080", + "By default, runs in stdio mode (Local) for direct agent integration.\n" + "Use --http for HTTP server (Remote) mode.\n\n" + "[bold]Examples:[/bold]\n\n" + " skillport serve\n\n" + " skillport serve --reindex\n\n" + " skillport serve --http --port 8080", )(serve) app.command( "doc", help="Generate skill documentation for AGENTS.md.\n\n" - "[bold]Examples:[/bold]\n\n" - " skillport doc\n\n" - " skillport doc --all\n\n" - " skillport doc -o .claude/AGENTS.md\n\n" - " skillport doc --category development,testing", + "[bold]Examples:[/bold]\n\n" + " skillport doc\n\n" + " skillport doc --all\n\n" + " skillport doc -o .claude/AGENTS.md\n\n" + " skillport doc --category development,testing", )(doc) diff --git a/src/skillport/interfaces/cli/auto_index.py b/src/skillport/interfaces/cli/auto_index.py index d9cca36..bdb7db8 100644 --- a/src/skillport/interfaces/cli/auto_index.py +++ b/src/skillport/interfaces/cli/auto_index.py @@ -53,9 +53,7 @@ def ensure_index_fresh(ctx, config: Config, *, force: bool = False) -> None: return reason = "force" if force else decision.reason - stderr_console.print( - f"[dim]Auto reindexing (reason={reason})[/dim]", highlight=False - ) + stderr_console.print(f"[dim]Auto reindexing (reason={reason})[/dim]", highlight=False) result = build_index(config=config, force=force) if not result.success: stderr_console.print(f"[error]Reindex failed: {result.message}[/error]") diff --git a/src/skillport/interfaces/cli/commands/add.py b/src/skillport/interfaces/cli/commands/add.py index 9599ab2..cbfd363 100644 --- a/src/skillport/interfaces/cli/commands/add.py +++ b/src/skillport/interfaces/cli/commands/add.py @@ -148,16 +148,26 @@ def add( namespace = namespace or _get_default_namespace(source) else: # Interactive mode - skill_display = skill_names[0] if is_single else ", ".join(skill_names[:3]) + ("..." if len(skill_names) > 3 else "") + skill_display = ( + skill_names[0] + if is_single + else ", ".join(skill_names[:3]) + ("..." if len(skill_names) > 3 else "") + ) console.print(f"\n[bold]Found {len(skill_names)} skill(s):[/bold] {skill_display}") console.print("[bold]Where to add?[/bold]") if is_single: console.print(f" [info][1][/info] Flat → skills/{skill_names[0]}/") - console.print(f" [info][2][/info] Namespace → skills/[dim][/dim]/{skill_names[0]}/") + console.print( + f" [info][2][/info] Namespace → skills/[dim][/dim]/{skill_names[0]}/" + ) else: - console.print(f" [info][1][/info] Flat → skills/{skill_names[0]}/, skills/{skill_names[1]}/, ...") - console.print(f" [info][2][/info] Namespace → skills/[dim][/dim]/{skill_names[0]}/, ...") + console.print( + f" [info][1][/info] Flat → skills/{skill_names[0]}/, skills/{skill_names[1]}/, ..." + ) + console.print( + f" [info][2][/info] Namespace → skills/[dim][/dim]/{skill_names[0]}/, ..." + ) console.print(" [info][3][/info] Skip") choice = Prompt.ask("Choice", choices=["1", "2", "3"], default="1") @@ -195,12 +205,14 @@ def add( # JSON output for programmatic use if json_output: - console.print_json(data={ - "added": result.added, - "skipped": result.skipped, - "message": result.message, - "details": [d.model_dump() for d in getattr(result, "details", [])], - }) + console.print_json( + data={ + "added": result.added, + "skipped": result.skipped, + "message": result.message, + "details": [d.model_dump() for d in getattr(result, "details", [])], + } + ) if not result.added and result.skipped: raise typer.Exit(code=1) return @@ -212,7 +224,11 @@ def add( if result.skipped: for skill_id in result.skipped: detail_reason = next( - (d.message for d in getattr(result, "details", []) if d.skill_id == skill_id and d.message), + ( + d.message + for d in getattr(result, "details", []) + if d.skill_id == skill_id and d.message + ), None, ) skip_reason = detail_reason or result.message or "skipped" @@ -222,7 +238,9 @@ def add( if result.added and not result.skipped: print_success(f"Added {len(result.added)} skill(s)") elif result.added and result.skipped: - print_warning(f"Added {len(result.added)}, skipped {len(result.skipped)} ({result.message})") + print_warning( + f"Added {len(result.added)}, skipped {len(result.skipped)} ({result.message})" + ) elif result.skipped: print_error( result.message or f"All {len(result.skipped)} skill(s) skipped", diff --git a/src/skillport/interfaces/cli/commands/doc.py b/src/skillport/interfaces/cli/commands/doc.py index b6fcf1d..47287a4 100644 --- a/src/skillport/interfaces/cli/commands/doc.py +++ b/src/skillport/interfaces/cli/commands/doc.py @@ -249,8 +249,7 @@ def doc( # Use instruction files from project config if not project_config.instructions: console.print( - "[warning]No instruction files in .skillportrc. " - "Using default AGENTS.md[/warning]" + "[warning]No instruction files in .skillportrc. Using default AGENTS.md[/warning]" ) output_files = [Path("./AGENTS.md")] else: diff --git a/src/skillport/interfaces/cli/commands/init.py b/src/skillport/interfaces/cli/commands/init.py index fc3caf9..9fbba02 100644 --- a/src/skillport/interfaces/cli/commands/init.py +++ b/src/skillport/interfaces/cli/commands/init.py @@ -19,7 +19,8 @@ # (display_name, actual_path) - None means "use display as path" DEFAULT_SKILLS_DIRS = [ ("~/.skillport/skills (default)", "~/.skillport/skills"), - (".claude/skills", None), + (".claude/skills (Claude Code)", ".claude/skills"), + ("~/.codex/skills (Codex)", "~/.codex/skills"), (".agent/skills", None), ] @@ -96,7 +97,7 @@ def _create_skillportrc( # Convert skills_dir to string (relative if possible) skills_dir_str = str(skills_dir) if skills_dir_str.startswith(str(Path.home())): - skills_dir_str = "~" + skills_dir_str[len(str(Path.home())):] + skills_dir_str = "~" + skills_dir_str[len(str(Path.home())) :] with open(path, "w", encoding="utf-8") as f: f.write("# SkillPort Configuration\n") @@ -214,4 +215,6 @@ def init( console.print("[bold green]✨ Ready![/bold green] Start your coding agent to use skills.") console.print("[dim] Run 'skillport add hello-world' to add your first skill[/dim]") if skill_count > 5 and instructions: - console.print("[dim] Tip: Edit instruction files to remove skills not relevant to this project[/dim]") + console.print( + "[dim] Tip: Edit instruction files to remove skills not relevant to this project[/dim]" + ) diff --git a/src/skillport/interfaces/cli/commands/lint.py b/src/skillport/interfaces/cli/commands/lint.py index ad213a0..701e8c9 100644 --- a/src/skillport/interfaces/cli/commands/lint.py +++ b/src/skillport/interfaces/cli/commands/lint.py @@ -27,17 +27,17 @@ def lint( skills = list_all(limit=1000, config=config) if skill_id: - skills = [ - s for s in skills if s.get("id") == skill_id or s.get("name") == skill_id - ] + skills = [s for s in skills if s.get("id") == skill_id or s.get("name") == skill_id] if not skills: if json_output: - console.print_json(data={ - "valid": False, - "message": "No skills found", - "skills": [], - }) + console.print_json( + data={ + "valid": False, + "message": "No skills found", + "skills": [], + } + ) else: print_warning("No skills found to validate.") raise typer.Exit(code=1) @@ -67,15 +67,17 @@ def lint( # JSON output if json_output: - console.print_json(data={ - "valid": total_fatal == 0, - "skills": all_results, - "summary": { - "total_skills": len(skills), - "fatal_issues": total_fatal, - "warning_issues": total_warning, - }, - }) + console.print_json( + data={ + "valid": total_fatal == 0, + "skills": all_results, + "summary": { + "total_skills": len(skills), + "fatal_issues": total_fatal, + "warning_issues": total_warning, + }, + } + ) if total_fatal > 0: raise typer.Exit(code=1) return @@ -106,10 +108,12 @@ def lint( if total_warning > 0: summary_parts.append(f"[warning]{total_warning} warning[/warning]") - console.print(Panel( - f"Checked {len(skills)} skill(s): {', '.join(summary_parts)}", - border_style=summary_style, - )) + console.print( + Panel( + f"Checked {len(skills)} skill(s): {', '.join(summary_parts)}", + border_style=summary_style, + ) + ) if total_fatal > 0: raise typer.Exit(code=1) diff --git a/src/skillport/interfaces/cli/commands/list.py b/src/skillport/interfaces/cli/commands/list.py index aa880f7..ff39623 100644 --- a/src/skillport/interfaces/cli/commands/list.py +++ b/src/skillport/interfaces/cli/commands/list.py @@ -62,4 +62,6 @@ def list_cmd( # Show truncation notice if applicable if result.total > limit: - console.print(f"[dim]Showing {limit} of {result.total} skills. Use --limit to show more.[/dim]") + console.print( + f"[dim]Showing {limit} of {result.total} skills. Use --limit to show more.[/dim]" + ) diff --git a/src/skillport/interfaces/cli/commands/remove.py b/src/skillport/interfaces/cli/commands/remove.py index d0949df..9b03110 100644 --- a/src/skillport/interfaces/cli/commands/remove.py +++ b/src/skillport/interfaces/cli/commands/remove.py @@ -43,10 +43,12 @@ def remove( confirm = typer.confirm(f"Remove '{skill_id}'?", default=False) if not confirm: if json_output: - console.print_json(data={ - "success": False, - "message": "Cancelled by user", - }) + console.print_json( + data={ + "success": False, + "message": "Cancelled by user", + } + ) else: console.print("[dim]Cancelled[/dim]") raise typer.Exit(code=1) @@ -66,10 +68,12 @@ def remove( build_index(config=config, force=False) if json_output: - console.print_json(data={ - "success": result.success, - "message": result.message, - }) + console.print_json( + data={ + "success": result.success, + "message": result.message, + } + ) if not result.success: raise typer.Exit(code=1) return diff --git a/src/skillport/interfaces/cli/commands/serve.py b/src/skillport/interfaces/cli/commands/serve.py index a36ca7b..870cd36 100644 --- a/src/skillport/interfaces/cli/commands/serve.py +++ b/src/skillport/interfaces/cli/commands/serve.py @@ -1,6 +1,5 @@ """Start MCP server command.""" - import typer from skillport.interfaces.mcp.server import run_server diff --git a/src/skillport/interfaces/cli/commands/show.py b/src/skillport/interfaces/cli/commands/show.py index 858b861..f5fe9cf 100644 --- a/src/skillport/interfaces/cli/commands/show.py +++ b/src/skillport/interfaces/cli/commands/show.py @@ -50,11 +50,13 @@ def show( header += f"\n\n[dim]Category:[/dim] [magenta]{detail.category}[/magenta]" header += f"\n[dim]Path:[/dim] {detail.path}" - console.print(Panel( - header, - title=f"[skill.id]{detail.id}[/skill.id]", - border_style="info", - )) + console.print( + Panel( + header, + title=f"[skill.id]{detail.id}[/skill.id]", + border_style="info", + ) + ) # Instructions as markdown console.print() diff --git a/src/skillport/interfaces/cli/commands/update.py b/src/skillport/interfaces/cli/commands/update.py index e15e65e..3a509fc 100644 --- a/src/skillport/interfaces/cli/commands/update.py +++ b/src/skillport/interfaces/cli/commands/update.py @@ -229,9 +229,7 @@ def _show_available_updates(config, json_output: bool, interactive: bool = False kind = origin.get("kind", "") if kind == "builtin": - not_updatable.append( - {"skill_id": skill_id, "reason": "Built-in skill"} - ) + not_updatable.append({"skill_id": skill_id, "reason": "Built-in skill"}) continue has_local_mods = detect_local_modification(skill_id, config=config) @@ -281,11 +279,7 @@ def _show_available_updates(config, json_output: bool, interactive: bool = False if updates_available: console.print("\n[bold]Updates available:[/bold]") for item in updates_available: - mod_marker = ( - " [warning](local changes)[/warning]" - if item.get("local_modified") - else "" - ) + mod_marker = " [warning](local changes)[/warning]" if item.get("local_modified") else "" commit = "" if item.get("new_commit"): commit = f" @ {item['new_commit']}" @@ -319,9 +313,7 @@ def _render_update_all_result(result, *, config, dry_run: bool): console.print(f"[success] + Updated '{detail.skill_id}'[/success]") if result.skipped: - console.print( - f"[dim] - {len(result.skipped)} skill(s) already up to date[/dim]" - ) + console.print(f"[dim] - {len(result.skipped)} skill(s) already up to date[/dim]") if result.errors: print_error("Errors encountered during update:") diff --git a/src/skillport/interfaces/cli/theme.py b/src/skillport/interfaces/cli/theme.py index bd2cf7e..0629b71 100644 --- a/src/skillport/interfaces/cli/theme.py +++ b/src/skillport/interfaces/cli/theme.py @@ -56,19 +56,21 @@ def print_banner(subtitle: str = ""): # Color scheme -THEME = Theme({ - "success": "green", - "warning": "yellow", - "error": "red", - "info": "cyan", - "dim": "dim", - "skill.id": "cyan bold", - "skill.name": "bold", - "skill.category": "magenta", - "score.high": "green", # >= 0.7 - "score.mid": "yellow", # >= 0.4 - "score.low": "dim", # < 0.4 -}) +THEME = Theme( + { + "success": "green", + "warning": "yellow", + "error": "red", + "info": "cyan", + "dim": "dim", + "skill.id": "cyan bold", + "skill.name": "bold", + "skill.category": "magenta", + "score.high": "green", # >= 0.7 + "score.mid": "yellow", # >= 0.4 + "score.low": "dim", # < 0.4 + } +) # Consoles: stdout for data, stderr for progress/logs @@ -100,7 +102,9 @@ def format_score(score: float) -> str: return f"[{style}]{score:.2f}[/{style}]" -def print_error(message: str, code: str | None = None, suggestion: str | None = None, json_output: bool = False): +def print_error( + message: str, code: str | None = None, suggestion: str | None = None, json_output: bool = False +): """Print error message consistently. For humans: Colored error with optional suggestion diff --git a/src/skillport/interfaces/mcp/instructions.py b/src/skillport/interfaces/mcp/instructions.py index 3398bc3..f0b8ed5 100644 --- a/src/skillport/interfaces/mcp/instructions.py +++ b/src/skillport/interfaces/mcp/instructions.py @@ -11,11 +11,7 @@ def _escape_xml(text: str) -> str: """Escape special characters for XML content.""" - return ( - text.replace("&", "&") - .replace("<", "<") - .replace(">", ">") - ) + return text.replace("&", "&").replace("<", "<").replace(">", ">") def build_xml_instructions(config: Config, registered_tools: list[str] | None = None) -> str: diff --git a/src/skillport/modules/indexing/internal/embeddings.py b/src/skillport/modules/indexing/internal/embeddings.py index 6c30c14..a3c3ea3 100644 --- a/src/skillport/modules/indexing/internal/embeddings.py +++ b/src/skillport/modules/indexing/internal/embeddings.py @@ -25,17 +25,13 @@ def get_embedding(text: str, config: Config) -> list[float] | None: if OpenAI: client = OpenAI(api_key=config.openai_api_key) - resp = client.embeddings.create( - input=[text], model=config.openai_embedding_model - ) + resp = client.embeddings.create(input=[text], model=config.openai_embedding_model) return resp.data[0].embedding import openai # lazy import for legacy <1.x openai.api_key = config.openai_api_key - resp = openai.Embedding.create( - input=[text], model=config.openai_embedding_model - ) + resp = openai.Embedding.create(input=[text], model=config.openai_embedding_model) return resp["data"][0]["embedding"] raise ValueError(f"Unsupported embedding_provider: {provider}") diff --git a/src/skillport/modules/indexing/internal/lancedb.py b/src/skillport/modules/indexing/internal/lancedb.py index b6e00ac..125bc9d 100644 --- a/src/skillport/modules/indexing/internal/lancedb.py +++ b/src/skillport/modules/indexing/internal/lancedb.py @@ -46,10 +46,7 @@ def _escape_sql(value: str) -> str: def _prefilter_clause(self) -> str: """Build WHERE clause reflecting enabled filters.""" if self.config.enabled_skills: - safe = [ - f"'{self._escape_sql(normalize_token(s))}'" - for s in self.config.enabled_skills - ] + safe = [f"'{self._escape_sql(normalize_token(s))}'" for s in self.config.enabled_skills] return f"id IN ({', '.join(safe)})" if self.config.enabled_namespaces: @@ -63,8 +60,7 @@ def _prefilter_clause(self) -> str: if self.config.enabled_categories: safe = [ - f"'{self._escape_sql(normalize_token(c))}'" - for c in self.config.enabled_categories + f"'{self._escape_sql(normalize_token(c))}'" for c in self.config.enabled_categories ] return f"category IN ({', '.join(safe)})" @@ -114,13 +110,8 @@ def _canonical_metadata( def initialize_index(self) -> None: """Scan skills_dir and (re)build the LanceDB table.""" # Fail fast for embeddings if needed (double-check even though Config validates) - if ( - self.config.embedding_provider == "openai" - and not self.config.openai_api_key - ): - raise ValueError( - "OPENAI_API_KEY is required when embedding_provider='openai'" - ) + if self.config.embedding_provider == "openai" and not self.config.openai_api_key: + raise ValueError("OPENAI_API_KEY is required when embedding_provider='openai'") skills_dir = self.config.skills_dir if not skills_dir.exists(): @@ -156,9 +147,7 @@ def _iter_skill_dirs(base: Path): for skill_path in _iter_skill_dirs(skills_dir): skill_md = skill_path / "SKILL.md" content = skill_md.read_text(encoding="utf-8") - line_count = content.count("\n") + ( - 1 if content and not content.endswith("\n") else 0 - ) + line_count = content.count("\n") + (1 if content and not content.endswith("\n") else 0) meta, body = parse_frontmatter(skill_md) if not isinstance(meta, dict): @@ -175,9 +164,7 @@ def _iter_skill_dirs(base: Path): name = meta.get("name") or skill_path.name description = meta.get("description") or "" skillport_meta = ( - metadata_block.get("skillport", {}) - if isinstance(metadata_block, dict) - else {} + metadata_block.get("skillport", {}) if isinstance(metadata_block, dict) else {} ) if not isinstance(skillport_meta, dict): skillport_meta = {} @@ -213,9 +200,7 @@ def _iter_skill_dirs(base: Path): continue ids_seen.add(skill_id) - text_to_embed = ( - f"{skill_id} {name} {description} {category_norm} {' '.join(tags_norm)}" - ) + text_to_embed = f"{skill_id} {name} {description} {category_norm} {' '.join(tags_norm)}" vec = self.search_service.embed_fn(text_to_embed) if vec: vectors_present = True @@ -256,9 +241,7 @@ def _iter_skill_dirs(base: Path): for r in records: d = r.model_dump() d["tags_text"] = ( - " ".join(d["tags"]) - if isinstance(d.get("tags"), list) - else str(d.get("tags", "")) + " ".join(d["tags"]) if isinstance(d.get("tags"), list) else str(d.get("tags", "")) ) if not vectors_present: d.pop("vector", None) @@ -294,17 +277,13 @@ def _iter_skill_dirs(base: Path): print(f"Tags scalar index creation failed: {exc}", file=sys.stderr) # --- state ----------------------------------------------------------- - def should_reindex( - self, *, force: bool = False, skip_auto: bool = False - ) -> dict[str, Any]: + def should_reindex(self, *, force: bool = False, skip_auto: bool = False) -> dict[str, Any]: return self.state_store.should_reindex( self._embedding_signature(), force=force, skip_auto=skip_auto ) def persist_state(self, state: dict[str, Any]) -> None: - self.state_store.persist( - state, skills_dir=self.config.skills_dir, db_path=self.db_path - ) + self.state_store.persist(state, skills_dir=self.config.skills_dir, db_path=self.db_path) # --- query ----------------------------------------------------------- def _table(self): diff --git a/src/skillport/modules/indexing/internal/models.py b/src/skillport/modules/indexing/internal/models.py index 62ee8c4..54b28bc 100644 --- a/src/skillport/modules/indexing/internal/models.py +++ b/src/skillport/modules/indexing/internal/models.py @@ -1,4 +1,3 @@ - from lancedb.pydantic import LanceModel from pydantic import Field diff --git a/src/skillport/modules/indexing/internal/search_service.py b/src/skillport/modules/indexing/internal/search_service.py index a07f524..1bf2104 100644 --- a/src/skillport/modules/indexing/internal/search_service.py +++ b/src/skillport/modules/indexing/internal/search_service.py @@ -71,9 +71,7 @@ def search( try: vec = self.embed_fn(query_norm) except Exception as exc: # pragma: no cover - defensive logging - print( - f"Embedding fetch failed, falling back to FTS: {exc}", file=sys.stderr - ) + print(f"Embedding fetch failed, falling back to FTS: {exc}", file=sys.stderr) vec = None try: @@ -85,14 +83,10 @@ def search( f"Vector search failed, falling back to FTS: {exc}", file=sys.stderr, ) - results = self._fts_then_substring( - table, query_norm, prefilter, limit - ) + results = self._fts_then_substring(table, query_norm, prefilter, limit) else: if not results: - results = self._fts_then_substring( - table, query_norm, prefilter, limit - ) + results = self._fts_then_substring(table, query_norm, prefilter, limit) else: results = self._fts_then_substring(table, query_norm, prefilter, limit) except Exception as exc: # pragma: no cover - defensive logging @@ -121,18 +115,14 @@ def _vector_search( rows = op.limit(limit).to_list() return [self._to_hit(row, "vector") for row in rows] - def _fts_search( - self, table, query: str, prefilter: str, limit: int - ) -> list[SearchHit]: + def _fts_search(self, table, query: str, prefilter: str, limit: int) -> list[SearchHit]: op = table.search(query, query_type="fts") if prefilter: op = op.where(prefilter) rows = op.limit(limit).to_list() return [self._to_hit(row, "fts") for row in rows] - def _substring_search( - self, table, query: str, prefilter: str, limit: int - ) -> list[SearchHit]: + def _substring_search(self, table, query: str, prefilter: str, limit: int) -> list[SearchHit]: op = table.search() if prefilter: op = op.where(prefilter) @@ -152,15 +142,11 @@ def _substring_search( return hits # --- helpers --- - def _fts_then_substring( - self, table, query: str, prefilter: str, limit: int - ) -> list[SearchHit]: + def _fts_then_substring(self, table, query: str, prefilter: str, limit: int) -> list[SearchHit]: try: return self._fts_search(table, query, prefilter, limit) except Exception as exc: - print( - f"FTS search failed, using substring fallback: {exc}", file=sys.stderr - ) + print(f"FTS search failed, using substring fallback: {exc}", file=sys.stderr) return self._substring_search(table, query, prefilter, limit) def _to_hit( diff --git a/src/skillport/modules/indexing/internal/state.py b/src/skillport/modules/indexing/internal/state.py index efc7e4d..1fbe2c1 100644 --- a/src/skillport/modules/indexing/internal/state.py +++ b/src/skillport/modules/indexing/internal/state.py @@ -62,9 +62,7 @@ def _write_state(self, state: dict[str, Any]) -> None: print(f"Failed to write index state: {exc}", file=sys.stderr) # --- public --- - def build_current_state( - self, embedding_signature: dict[str, Any] - ) -> dict[str, Any]: + def build_current_state(self, embedding_signature: dict[str, Any]) -> dict[str, Any]: current = self._hash_skills_dir() return { "schema_version": self.schema_version, @@ -113,9 +111,7 @@ def should_reindex( "state": current_state, "previous": prev, } - if prev.get("embedding_provider") != embedding_signature.get( - "embedding_provider" - ): + if prev.get("embedding_provider") != embedding_signature.get("embedding_provider"): return { "need": True, "reason": "provider_changed", @@ -144,9 +140,7 @@ def should_reindex( "previous": prev, } - def persist( - self, state: dict[str, Any], *, skills_dir: Path, db_path: Path - ) -> None: + def persist(self, state: dict[str, Any], *, skills_dir: Path, db_path: Path) -> None: payload = dict(state) payload["built_at"] = datetime.now(timezone.utc).isoformat() payload["skills_dir"] = str(skills_dir) diff --git a/src/skillport/modules/indexing/public/index.py b/src/skillport/modules/indexing/public/index.py index c9906ce..c7f24db 100644 --- a/src/skillport/modules/indexing/public/index.py +++ b/src/skillport/modules/indexing/public/index.py @@ -25,18 +25,14 @@ def build_index(*, config: Config, force: bool = False) -> IndexBuildResult: # No reindex needed, return current state table = store.list_all(limit=1_000_000) count = len(table) - return IndexBuildResult( - success=True, skill_count=count, message=decision["reason"] - ) + return IndexBuildResult(success=True, skill_count=count, message=decision["reason"]) try: store.initialize_index() store.persist_state(decision["state"]) table = store.list_all(limit=1_000_000) count = len(table) - return IndexBuildResult( - success=True, skill_count=count, message=decision["reason"] - ) + return IndexBuildResult(success=True, skill_count=count, message=decision["reason"]) except Exception as exc: return IndexBuildResult(success=False, skill_count=0, message=str(exc)) diff --git a/src/skillport/modules/indexing/public/query.py b/src/skillport/modules/indexing/public/query.py index a9fbc5b..a800107 100644 --- a/src/skillport/modules/indexing/public/query.py +++ b/src/skillport/modules/indexing/public/query.py @@ -1,6 +1,5 @@ """Query-facing public APIs.""" - from skillport.shared.config import Config from ..internal.lancedb import IndexStore diff --git a/src/skillport/modules/skills/internal/github.py b/src/skillport/modules/skills/internal/github.py index 536a297..ef6dc87 100644 --- a/src/skillport/modules/skills/internal/github.py +++ b/src/skillport/modules/skills/internal/github.py @@ -37,9 +37,7 @@ class ParsedGitHubURL: @property def tarball_url(self) -> str: - return ( - f"https://api.github.com/repos/{self.owner}/{self.repo}/tarball/{self.ref}" - ) + return f"https://api.github.com/repos/{self.owner}/{self.repo}/tarball/{self.ref}" @property def normalized_path(self) -> str: @@ -70,9 +68,7 @@ def _get_default_branch(owner: str, repo: str, token: str | None) -> str: return "main" -def parse_github_url( - url: str, *, resolve_default_branch: bool = False -) -> ParsedGitHubURL: +def parse_github_url(url: str, *, resolve_default_branch: bool = False) -> ParsedGitHubURL: match = GITHUB_URL_RE.match(url.strip()) if not match: raise ValueError( @@ -98,9 +94,7 @@ def parse_github_url( return ParsedGitHubURL(owner=owner, repo=repo, ref=ref, path=path) -def _iter_members_for_prefix( - tar: tarfile.TarFile, prefix: str -) -> Iterable[tarfile.TarInfo]: +def _iter_members_for_prefix(tar: tarfile.TarFile, prefix: str) -> Iterable[tarfile.TarInfo]: for member in tar.getmembers(): if not member.name.startswith(prefix): continue @@ -115,9 +109,7 @@ def download_tarball(parsed: ParsedGitHubURL, token: str | None) -> Path: resp = requests.get(parsed.tarball_url, headers=headers, stream=True, timeout=60) if resp.status_code == 404: - raise ValueError( - "Repository not found or private. Set GITHUB_TOKEN for private repos." - ) + raise ValueError("Repository not found or private. Set GITHUB_TOKEN for private repos.") if resp.status_code == 403: raise ValueError("GitHub API rate limit. Set GITHUB_TOKEN.") if not resp.ok: @@ -163,9 +155,7 @@ def extract_tarball(tar_path: Path, parsed: ParsedGitHubURL) -> tuple[Path, str] commit_sha = "" with tarfile.open(tar_path, "r:gz") as tar: - roots = { - member.name.split("/")[0] for member in tar.getmembers() if member.name - } + roots = {member.name.split("/")[0] for member in tar.getmembers() if member.name} if not roots: raise ValueError("Tarball is empty") root = sorted(roots)[0] @@ -187,9 +177,7 @@ def extract_tarball(tar_path: Path, parsed: ParsedGitHubURL) -> tuple[Path, str] if any(p in EXCLUDE_NAMES or p.startswith(".") for p in parts): continue if member.islnk() or member.issym(): - raise ValueError( - f"Symlinks are not allowed in GitHub source: {member.name}" - ) + raise ValueError(f"Symlinks are not allowed in GitHub source: {member.name}") dest_path = dest_root / member.name dest_path.parent.mkdir(parents=True, exist_ok=True) @@ -343,7 +331,7 @@ def get_remote_tree_hash(parsed: ParsedGitHubURL, token: str | None, path: str) entry_path = entry.get("path", "") if not entry_path.startswith(prefix): continue - rel = entry_path[len(prefix):] if prefix else entry_path + rel = entry_path[len(prefix) :] if prefix else entry_path if not rel: continue parts = Path(rel).parts diff --git a/src/skillport/modules/skills/internal/manager.py b/src/skillport/modules/skills/internal/manager.py index 8355b7d..16acc38 100644 --- a/src/skillport/modules/skills/internal/manager.py +++ b/src/skillport/modules/skills/internal/manager.py @@ -99,9 +99,7 @@ def _load_skill_info(skill_dir: Path) -> SkillInfo: raise FileNotFoundError(f"SKILL.md not found in {skill_dir}") meta, _ = parse_frontmatter(skill_md) if not isinstance(meta, dict): - raise ValueError( - f"Invalid SKILL.md in {skill_dir}: frontmatter must be a mapping" - ) + raise ValueError(f"Invalid SKILL.md in {skill_dir}: frontmatter must be a mapping") name = meta.get("name") or "" return SkillInfo(name=name, source_path=skill_dir) @@ -167,9 +165,7 @@ def _validate_skill_file(skill_dir: Path) -> None: raise FileNotFoundError(f"SKILL.md not found: {skill_dir}") meta, body = parse_frontmatter(skill_md) if not isinstance(meta, dict): - raise ValueError( - f"Invalid SKILL.md in {skill_dir}: frontmatter must be a mapping" - ) + raise ValueError(f"Invalid SKILL.md in {skill_dir}: frontmatter must be a mapping") name = meta.get("name") description = meta.get("description", "") @@ -178,9 +174,7 @@ def _validate_skill_file(skill_dir: Path) -> None: if not name or not str(name).strip(): raise ValueError(f"Invalid SKILL.md in {skill_dir}: frontmatter.name is required") if not description or not str(description).strip(): - raise ValueError( - f"Invalid SKILL.md in {skill_dir}: frontmatter.description is required" - ) + raise ValueError(f"Invalid SKILL.md in {skill_dir}: frontmatter.description is required") name = str(name).strip() description = str(description) @@ -190,9 +184,7 @@ def _validate_skill_file(skill_dir: Path) -> None: if "name" not in meta or not str(meta.get("name", "")).strip(): raise ValueError(f"Invalid SKILL.md in {skill_dir}: frontmatter.name is required") if "description" not in meta or not str(meta.get("description", "")).strip(): - raise ValueError( - f"Invalid SKILL.md in {skill_dir}: frontmatter.description is required" - ) + raise ValueError(f"Invalid SKILL.md in {skill_dir}: frontmatter.description is required") issues = validate_skill_record( { @@ -308,9 +300,7 @@ def add_local( _ensure_frontmatter_name(raw, skill_name), encoding="utf-8" ) results.append( - AddResult( - success=True, skill_id=skill_id, message=f"Added '{skill_id}'" - ) + AddResult(success=True, skill_id=skill_id, message=f"Added '{skill_id}'") ) except Exception as exc: if dest.exists(): @@ -334,10 +324,6 @@ def remove_skill(skill_id: str, *, config: Config) -> RemoveResult: success=False, skill_id=skill_id, message=f"Skill not found: {skill_id}" ) if not dest.is_dir(): - return RemoveResult( - success=False, skill_id=skill_id, message=f"Not a directory: {dest}" - ) + return RemoveResult(success=False, skill_id=skill_id, message=f"Not a directory: {dest}") shutil.rmtree(dest) - return RemoveResult( - success=True, skill_id=skill_id, message=f"Removed '{skill_id}'" - ) + return RemoveResult(success=True, skill_id=skill_id, message=f"Removed '{skill_id}'") diff --git a/src/skillport/modules/skills/internal/validation.py b/src/skillport/modules/skills/internal/validation.py index d2febc2..ebe6772 100644 --- a/src/skillport/modules/skills/internal/validation.py +++ b/src/skillport/modules/skills/internal/validation.py @@ -71,9 +71,7 @@ def validate_skill_record( # Required fields (value checks) if not name: issues.append( - ValidationIssue( - severity="fatal", message="frontmatter.name: missing", field="name" - ) + ValidationIssue(severity="fatal", message="frontmatter.name: missing", field="name") ) if not description: issues.append( diff --git a/src/skillport/modules/skills/public/add.py b/src/skillport/modules/skills/public/add.py index a52b9d6..1f80253 100644 --- a/src/skillport/modules/skills/public/add.py +++ b/src/skillport/modules/skills/public/add.py @@ -162,9 +162,7 @@ def _summarize_skipped(reasons: list[str]) -> str: # Show first other reason and count remainder first_other = others[0] extra = len(others) - 1 - parts.append( - first_other if extra == 0 else f"{first_other} (+{extra} more)" - ) + parts.append(first_other if extra == 0 else f"{first_other} (+{extra} more)") return "; ".join(parts) if parts else "No skills added" @@ -197,7 +195,9 @@ def _summarize_skipped(reasons: list[str]) -> str: rel_path = "" if source_path.exists(): try: - rel_path = str((source_path / sid.split("/")[-1]).relative_to(source_path)) + rel_path = str( + (source_path / sid.split("/")[-1]).relative_to(source_path) + ) except Exception: rel_path = sid.split("/")[-1] @@ -207,7 +207,12 @@ def _summarize_skipped(reasons: list[str]) -> str: # 複数スキル追加時もスキル単位のサブディレクトリを正しく記録する。 if origin_payload.get("kind") == "github": prefix = origin_payload.get("path", "").rstrip("/") - if prefix and rel_path and rel_path != prefix and not prefix.endswith(f"/{rel_path}"): + if ( + prefix + and rel_path + and rel_path != prefix + and not prefix.endswith(f"/{rel_path}") + ): enriched_payload["path"] = f"{prefix}/{rel_path}" elif prefix: enriched_payload["path"] = prefix diff --git a/src/skillport/modules/skills/public/list.py b/src/skillport/modules/skills/public/list.py index 3a4b8ab..aa83530 100644 --- a/src/skillport/modules/skills/public/list.py +++ b/src/skillport/modules/skills/public/list.py @@ -25,9 +25,7 @@ def list_skills(*, config: Config, limit: int | None = None) -> ListResult: name=row.get("name", skill_id), description=row.get("description", ""), category=normalize_token(category), - score=float(row.get("_score", 0.0)) - if row.get("_score") is not None - else 0.0, + score=float(row.get("_score", 0.0)) if row.get("_score") is not None else 0.0, ) ) if len(skills) >= effective_limit: diff --git a/src/skillport/modules/skills/public/load.py b/src/skillport/modules/skills/public/load.py index 2a96d2e..02b278c 100644 --- a/src/skillport/modules/skills/public/load.py +++ b/src/skillport/modules/skills/public/load.py @@ -26,11 +26,7 @@ def load_skill(skill_id: str, *, config: Config) -> SkillDetail: metadata_raw = record.get("metadata", "{}") try: - metadata = ( - json.loads(metadata_raw) - if isinstance(metadata_raw, str) - else dict(metadata_raw) - ) + metadata = json.loads(metadata_raw) if isinstance(metadata_raw, str) else dict(metadata_raw) except Exception: metadata = {} @@ -39,9 +35,7 @@ def load_skill(skill_id: str, *, config: Config) -> SkillDetail: name=record.get("name", identifier), description=record.get("description", ""), category=normalize_token(record.get("category", "")), - tags=[normalize_token(t) for t in record.get("tags", [])] - if record.get("tags") - else [], + tags=[normalize_token(t) for t in record.get("tags", [])] if record.get("tags") else [], instructions=record.get("instructions", ""), path=record.get("path", ""), metadata=metadata, diff --git a/src/skillport/modules/skills/public/read.py b/src/skillport/modules/skills/public/read.py index ab99185..c08406c 100644 --- a/src/skillport/modules/skills/public/read.py +++ b/src/skillport/modules/skills/public/read.py @@ -13,7 +13,24 @@ from .types import FileContent # Extensions that should be treated as text even if mimetypes doesn't recognize them -TEXT_EXTENSIONS = {".json", ".yaml", ".yml", ".md", ".xml", ".txt", ".csv", ".toml", ".ini", ".cfg", ".sh", ".py", ".js", ".ts", ".html", ".css"} +TEXT_EXTENSIONS = { + ".json", + ".yaml", + ".yml", + ".md", + ".xml", + ".txt", + ".csv", + ".toml", + ".ini", + ".cfg", + ".sh", + ".py", + ".js", + ".ts", + ".html", + ".css", +} def read_skill_file(skill_id: str, file_path: str, *, config: Config) -> FileContent: diff --git a/src/skillport/modules/skills/public/types.py b/src/skillport/modules/skills/public/types.py index 8340a92..a3e6b38 100644 --- a/src/skillport/modules/skills/public/types.py +++ b/src/skillport/modules/skills/public/types.py @@ -46,9 +46,7 @@ class SkillSummary(FrozenModel): name: str = Field(..., description="Skill display name") description: str = Field(..., description="Brief skill description") category: str = Field(default="", description="Skill category (normalized)") - score: float = Field( - default=0.0, ge=0.0, description="Search relevance score (raw)" - ) + score: float = Field(default=0.0, ge=0.0, description="Search relevance score (raw)") class SkillDetail(FrozenModel): @@ -106,7 +104,9 @@ class AddResult(FrozenModel): skill_id: str = Field(..., description="Added skill ID (empty if failed)") message: str = Field(..., description="Human-readable result message") added: list[str] = Field(default_factory=list, description="Successfully added skill IDs") - skipped: list[str] = Field(default_factory=list, description="Skipped skill IDs (already exist)") + skipped: list[str] = Field( + default_factory=list, description="Skipped skill IDs (already exist)" + ) details: list[AddResultItem] = Field( default_factory=list, description="Per-skill results for bulk adds", @@ -138,13 +138,19 @@ class UpdateResult(FrozenModel): skill_id: str = Field(..., description="Updated skill ID (empty if failed)") message: str = Field(..., description="Human-readable result message") updated: list[str] = Field(default_factory=list, description="Successfully updated skill IDs") - skipped: list[str] = Field(default_factory=list, description="Skipped skill IDs (no updates/errors)") + skipped: list[str] = Field( + default_factory=list, description="Skipped skill IDs (no updates/errors)" + ) details: list[UpdateResultItem] = Field( default_factory=list, description="Per-skill results for bulk updates", ) - local_modified: bool = Field(default=False, description="Whether local modifications were detected") - errors: list[str] = Field(default_factory=list, description="Errors encountered during update (if any)") + local_modified: bool = Field( + default=False, description="Whether local modifications were detected" + ) + errors: list[str] = Field( + default_factory=list, description="Errors encountered during update (if any)" + ) class ListResult(FrozenModel): diff --git a/src/skillport/modules/skills/public/update.py b/src/skillport/modules/skills/public/update.py index afdd400..09cf014 100644 --- a/src/skillport/modules/skills/public/update.py +++ b/src/skillport/modules/skills/public/update.py @@ -47,6 +47,7 @@ def detect_local_modification(skill_id: str, *, config: Config) -> bool: # --- common helpers --------------------------------------------------------- + def _installed_hash(skill_id: str, *, config: Config) -> tuple[str, str | None]: """Hash installed skill; returns (hash, reason).""" return compute_content_hash_with_reason(config.skills_dir / skill_id) @@ -57,9 +58,7 @@ def _resolve_origin_path(origin: Origin, parsed_path_fallback: str, skill_id: st return origin.get("path") or parsed_path_fallback or skill_id.split("/")[-1] -def _local_source_hash( - origin: Origin, skill_id: str, *, config: Config -) -> tuple[str, str | None]: +def _local_source_hash(origin: Origin, skill_id: str, *, config: Config) -> tuple[str, str | None]: """Compute source hash for local origin; returns (hash, reason).""" source_base = Path(origin.get("source", "")) if not source_base.exists(): @@ -83,9 +82,7 @@ def _local_source_hash( return compute_content_hash_with_reason(source_path) -def _github_source_hash( - origin: Origin, skill_id: str, *, config: Config -) -> tuple[str, str | None]: +def _github_source_hash(origin: Origin, skill_id: str, *, config: Config) -> tuple[str, str | None]: """Compute source hash for GitHub origin via tree API; returns (hash, reason).""" source_url = origin.get("source", "") if not source_url: @@ -117,9 +114,7 @@ def _github_source_hash( return remote_hash, None -def _source_hash( - origin: Origin, skill_id: str, *, config: Config -) -> tuple[str, str | None]: +def _source_hash(origin: Origin, skill_id: str, *, config: Config) -> tuple[str, str | None]: """Compute source-side hash; returns (hash, reason). Dispatches to _local_source_hash or _github_source_hash based on origin kind. @@ -193,9 +188,7 @@ def check_update_available(skill_id: str, *, config: Config) -> dict[str, Any]: return { "available": True, - "reason": "Remote content differs" - if kind == "github" - else "Local source changed", + "reason": "Remote content differs" if kind == "github" else "Local source changed", "origin": origin, "new_commit": source_hash.split(":", 1)[-1][:7] if source_hash.startswith("sha256:") @@ -252,14 +245,10 @@ def update_skill( ) if kind == "local": - return _update_from_local( - skill_id, origin, config=config, force=force, dry_run=dry_run - ) + return _update_from_local(skill_id, origin, config=config, force=force, dry_run=dry_run) if kind == "github": - return _update_from_github( - skill_id, origin, config=config, force=force, dry_run=dry_run - ) + return _update_from_github(skill_id, origin, config=config, force=force, dry_run=dry_run) return UpdateResult( success=False, @@ -347,9 +336,7 @@ def _update_from_local( message=f"Source not readable: {source_reason}", ) - current_hash, current_reason = compute_content_hash_with_reason( - config.skills_dir / skill_id - ) + current_hash, current_reason = compute_content_hash_with_reason(config.skills_dir / skill_id) if current_reason: return UpdateResult( success=False, @@ -452,9 +439,7 @@ def _update_from_github( # --- Phase 1: Check if update is needed (no download) --- # Get installed hash - current_hash, current_reason = compute_content_hash_with_reason( - config.skills_dir / skill_id - ) + current_hash, current_reason = compute_content_hash_with_reason(config.skills_dir / skill_id) if current_reason: return UpdateResult( success=False, diff --git a/src/skillport/shared/config.py b/src/skillport/shared/config.py index 25afb8f..30bee0a 100644 --- a/src/skillport/shared/config.py +++ b/src/skillport/shared/config.py @@ -93,17 +93,13 @@ class Config(BaseSettings): validation_alias="OPENAI_EMBEDDING_MODEL", ) # Search - search_limit: int = Field( - default=10, ge=1, le=100, description="Default search result limit" - ) + search_limit: int = Field(default=10, ge=1, le=100, description="Default search result limit") search_threshold: float = Field( default=0.2, ge=0.0, le=1.0, description="Minimum relevance score" ) # Filters (comma-separated strings from env, e.g., "cat1,cat2") - enabled_skills: list[str] = Field( - default_factory=list, description="Whitelist of skill IDs" - ) + enabled_skills: list[str] = Field(default_factory=list, description="Whitelist of skill IDs") enabled_categories: list[str] = Field( default_factory=list, description="Whitelist of categories" ) @@ -137,9 +133,7 @@ class Config(BaseSettings): description="Allowlist for executable commands", ) exec_timeout_seconds: int = Field(default=60, description="Command timeout seconds") - exec_max_output_bytes: int = Field( - default=65536, description="Max captured output in bytes" - ) + exec_max_output_bytes: int = Field(default=65536, description="Max captured output in bytes") max_file_bytes: int = Field(default=65536, description="Max file size to read") log_level: str | None = Field( default=None, description="Optional log level (e.g., DEBUG/INFO/WARN/ERROR)" @@ -180,9 +174,7 @@ def _slug_for_skills_dir(skills_dir: Path) -> str: @model_validator(mode="after") def validate_provider_keys(self): if self.embedding_provider == "openai" and not self.openai_api_key: - raise ValueError( - "OPENAI_API_KEY is required when embedding_provider='openai'" - ) + raise ValueError("OPENAI_API_KEY is required when embedding_provider='openai'") return self def model_post_init(self, __context: Any) -> None: diff --git a/src/skillport/shared/exceptions.py b/src/skillport/shared/exceptions.py index 8448c53..dffcad8 100644 --- a/src/skillport/shared/exceptions.py +++ b/src/skillport/shared/exceptions.py @@ -15,9 +15,7 @@ class AmbiguousSkillError(SkillPortError): def __init__(self, identifier: str, candidates: list[str]): self.identifier = identifier self.candidates = candidates - super().__init__( - f"Ambiguous skill: {identifier}. Candidates: {', '.join(candidates)}" - ) + super().__init__(f"Ambiguous skill: {identifier}. Candidates: {', '.join(candidates)}") class ValidationError(SkillPortError): diff --git a/src/skillport/shared/filters.py b/src/skillport/shared/filters.py index 554e763..1244986 100644 --- a/src/skillport/shared/filters.py +++ b/src/skillport/shared/filters.py @@ -28,9 +28,7 @@ def is_skill_enabled(skill_id: str, category: str | None, *, config: Config) -> leaf_norm = normalize_token(skill_id.split("/")[-1]) enabled_skills = [normalize_token(s) for s in config.enabled_skills] enabled_categories = [normalize_token(c) for c in config.enabled_categories] - enabled_namespaces = [ - normalize_token(ns).rstrip("/") for ns in config.enabled_namespaces - ] + enabled_namespaces = [normalize_token(ns).rstrip("/") for ns in config.enabled_namespaces] category_norm = normalize_token(category) if category is not None else None if enabled_skills: diff --git a/src/skillport/shared/types.py b/src/skillport/shared/types.py index e33a591..4e4ea84 100644 --- a/src/skillport/shared/types.py +++ b/src/skillport/shared/types.py @@ -27,13 +27,9 @@ class Severity(str, Enum): class ValidationIssue(FrozenModel): """Validation issue detected in a skill.""" - severity: Literal["fatal", "warning", "info"] = Field( - ..., description="Issue severity level" - ) + severity: Literal["fatal", "warning", "info"] = Field(..., description="Issue severity level") message: str = Field(..., description="Human-readable issue description") - field: str | None = Field( - default=None, description="Related field name if applicable" - ) + field: str | None = Field(default=None, description="Related field name if applicable") SkillId = Annotated[