librarian-code 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. librarian/__init__.py +3 -0
  2. librarian/__main__.py +3 -0
  3. librarian/actions/__init__.py +0 -0
  4. librarian/actions/file_ops.py +47 -0
  5. librarian/actions/safety.py +29 -0
  6. librarian/actions/shell_ops.py +49 -0
  7. librarian/adapter/__init__.py +0 -0
  8. librarian/adapter/base.py +11 -0
  9. librarian/adapter/groq_adapter.py +40 -0
  10. librarian/adapter/openrouter_adapter.py +58 -0
  11. librarian/cli.py +26 -0
  12. librarian/commands/__init__.py +0 -0
  13. librarian/commands/ask.py +46 -0
  14. librarian/commands/do.py +232 -0
  15. librarian/commands/init.py +96 -0
  16. librarian/commands/status.py +71 -0
  17. librarian/commands/undo.py +85 -0
  18. librarian/commands/why.py +47 -0
  19. librarian/exceptions.py +22 -0
  20. librarian/memory/__init__.py +0 -0
  21. librarian/memory/capsule.py +94 -0
  22. librarian/memory/chunker.py +183 -0
  23. librarian/memory/decision_log.py +36 -0
  24. librarian/memory/indexer.py +96 -0
  25. librarian/memory/retriever.py +62 -0
  26. librarian/orchestrator/__init__.py +0 -0
  27. librarian/orchestrator/core.py +47 -0
  28. librarian/orchestrator/router.py +17 -0
  29. librarian/skills/__init__.py +0 -0
  30. librarian/skills/bundled/__init__.py +0 -0
  31. librarian/skills/bundled/api-design/conventions.md +93 -0
  32. librarian/skills/bundled/python/conventions.md +59 -0
  33. librarian/skills/bundled/react/conventions.md +83 -0
  34. librarian/skills/bundled/web-dev/conventions.md +54 -0
  35. librarian/skills/loader.py +109 -0
  36. librarian/utils/__init__.py +0 -0
  37. librarian/utils/config.py +15 -0
  38. librarian/utils/logger.py +32 -0
  39. librarian/utils/token_tracker.py +16 -0
  40. librarian/utils/ui.py +97 -0
  41. librarian_code-0.1.0.dist-info/METADATA +180 -0
  42. librarian_code-0.1.0.dist-info/RECORD +45 -0
  43. librarian_code-0.1.0.dist-info/WHEEL +4 -0
  44. librarian_code-0.1.0.dist-info/entry_points.txt +2 -0
  45. librarian_code-0.1.0.dist-info/licenses/LICENSE.md +21 -0
@@ -0,0 +1,62 @@
1
+ import os
2
+ import chromadb
3
+ from sentence_transformers import SentenceTransformer
4
+ from librarian.utils.config import CHROMA_PERSIST_DIR, EMBED_MODEL
5
+ from librarian.memory.indexer import _sanitize_collection_name
6
+
7
+ _model = None
8
+ _client = None
9
+
10
+
11
+ def _get_model():
12
+ global _model
13
+ if _model is None:
14
+ _model = SentenceTransformer(EMBED_MODEL)
15
+ return _model
16
+
17
+
18
+ def _get_client():
19
+ global _client
20
+ if _client is None:
21
+ _client = chromadb.PersistentClient(path=CHROMA_PERSIST_DIR)
22
+ return _client
23
+
24
+
25
+ def retrieve(query: str, n_results: int = 5) -> list[dict]:
26
+ model = _get_model()
27
+ client = _get_client()
28
+ project_name = _sanitize_collection_name(os.path.basename(os.getcwd()))
29
+
30
+ try:
31
+ collection = client.get_collection(name=project_name)
32
+ except Exception:
33
+ return []
34
+
35
+ query_embedding = model.encode([query]).tolist()
36
+ results = collection.query(
37
+ query_embeddings=query_embedding,
38
+ n_results=n_results,
39
+ include=["documents", "metadatas", "distances"],
40
+ )
41
+
42
+ if not results.get("documents") or not results["documents"][0]:
43
+ return []
44
+
45
+ chunks = []
46
+ for doc, meta, dist in zip(
47
+ results["documents"][0],
48
+ results["metadatas"][0],
49
+ results["distances"][0],
50
+ ):
51
+ if dist > 2.5:
52
+ continue
53
+ chunks.append({
54
+ "content": doc,
55
+ "metadata": meta,
56
+ "distance": dist,
57
+ })
58
+
59
+ if chunks and sum(c["distance"] for c in chunks) / len(chunks) > 2.0:
60
+ return []
61
+
62
+ return chunks
File without changes
@@ -0,0 +1,47 @@
1
+ from pathlib import Path
2
+ from librarian.orchestrator.router import get_response
3
+ from librarian.memory.retriever import retrieve
4
+ from librarian.skills.loader import build_skill_context
5
+
6
+
7
+ def build_system_prompt(project_conventions: str, skill_context: str = "") -> str:
8
+ parts = [
9
+ "You are Librarian, a CLI coding agent with memory of this project.",
10
+ f"Project conventions:\n{project_conventions}",
11
+ ]
12
+ if skill_context:
13
+ parts.append(f"Domain-specific best practices:\n{skill_context}")
14
+ parts.append("""Rules:
15
+ - Always explain what you are about to do before doing it
16
+ - Never delete files without explicit confirmation
17
+ - Prefer editing existing code over rewriting from scratch
18
+ - When uncertain, ask rather than assume
19
+ - Be concise and direct in your answers
20
+ - Cite source files when referencing code (e.g. "in auth.py line 42")
21
+ """)
22
+ return "\n\n".join(parts)
23
+
24
+
25
+ def read_librarian_md() -> str:
26
+ path = Path("LIBRARIAN.md")
27
+ if path.exists():
28
+ return path.read_text(encoding="utf-8")
29
+ return "No project conventions file found."
30
+
31
+
32
+ def ask(question: str) -> tuple[str, str, int]:
33
+ conventions = read_librarian_md()
34
+ skill_ctx = build_skill_context()
35
+ system = build_system_prompt(conventions, skill_ctx)
36
+
37
+ chunks = retrieve(question, n_results=5)
38
+ context = ""
39
+ if chunks:
40
+ parts = []
41
+ for c in chunks:
42
+ meta = c["metadata"]
43
+ parts.append(f"--- {meta['file_path']}:{meta.get('start_line', '?')}-{meta.get('end_line', '?')} ---\n{c['content']}")
44
+ context = "\n\n".join(parts)
45
+
46
+ prompt = f"Relevant code context:\n{context}\n\nQuestion: {question}" if context else question
47
+ return get_response(system, prompt)
@@ -0,0 +1,17 @@
1
+ from librarian.adapter.groq_adapter import GroqAdapter
2
+ from librarian.adapter.openrouter_adapter import OpenRouterAdapter
3
+ from librarian.exceptions import RateLimitError, ProviderUnavailableError
4
+ from librarian.utils.logger import log_warning
5
+
6
+
7
+ def get_response(system: str, prompt: str) -> tuple[str, str, int]:
8
+ primary = GroqAdapter()
9
+ fallback = OpenRouterAdapter()
10
+
11
+ try:
12
+ response = primary.complete(system, prompt)
13
+ return response, "groq", primary.tokens_used
14
+ except (RateLimitError, ProviderUnavailableError) as e:
15
+ log_warning(f"{e} — switching to OpenRouter")
16
+ response = fallback.complete(system, prompt)
17
+ return response, "openrouter", fallback.tokens_used
File without changes
File without changes
@@ -0,0 +1,93 @@
1
+ # API Design Conventions
2
+
3
+ ## URL Structure
4
+ - Use nouns, not verbs (HTTP methods are the verbs)
5
+ - Plural nouns for collections: `/users`, `/orders`
6
+ - Hyphens for multi-word segments: `/managed-devices`
7
+ - Lowercase consistently
8
+ - No trailing slashes, no file extensions
9
+ - Limit nesting depth to 3 levels max
10
+
11
+ ## HTTP Methods
12
+ | Method | Purpose | Success Code |
13
+ |--------|---------|-------------|
14
+ | GET | Read resource | 200 |
15
+ | POST | Create resource | 201 (with Location header) |
16
+ | PUT | Full replace | 200, 201, 204 |
17
+ | PATCH | Partial update | 200, 204 |
18
+ | DELETE | Remove resource | 200, 204 |
19
+
20
+ - POST to collection → 201 Created + Location header
21
+ - PUT is idempotent, POST is not
22
+ - Use PATCH for partial updates, not PUT
23
+
24
+ ## Status Codes
25
+ - 200: Generic success (GET, PUT, PATCH)
26
+ - 201: Resource created (POST) — always include Location header
27
+ - 204: Success with no body (DELETE)
28
+ - 400: Bad request / validation error
29
+ - 401: Not authenticated (missing/invalid credentials)
30
+ - 403: Not authorized (insufficient permissions)
31
+ - 404: Resource not found
32
+ - 409: Conflict (duplicate creation)
33
+ - 422: Semantically invalid input
34
+ - 429: Rate limited — include Retry-After header
35
+ - 500: Server error
36
+
37
+ 401 ≠ 403: 401 = not logged in, 403 = logged in but no permission
38
+
39
+ ## Request/Response Patterns
40
+ - Always use `Content-Type: application/json`
41
+ - Collection responses: `{ "data": [...], "meta": { "total", "page", "hasNext" } }`
42
+ - Single resource: `{ "id": 1, "name": "..." }`
43
+ - Creation response: 201 + Location header + resource body
44
+ - Error response: `{ "error": { "code": "VALIDATION_ERROR", "message": "...", "details": [...] } }`
45
+ - Never expose stack traces or internal errors in production
46
+
47
+ ## Pagination
48
+ - Offset-based: `?page=2&limit=25` (simple)
49
+ - Cursor-based: `?after=<cursor>&first=25` (preferred for large datasets)
50
+ - Always include: total, hasNext/hasPrev
51
+
52
+ ## Filtering & Sorting
53
+ - Filter via query params: `?category=electronics&status=active`
54
+ - Sort: `sort=field` (asc), `sort=-field` (desc)
55
+ - Search: `?search=john`
56
+
57
+ ## Authentication
58
+ - JWT: `Authorization: Bearer <token>` header
59
+ - API Key: `X-API-Key: <key>` header (never in URLs)
60
+ - Always use HTTPS
61
+ - Hash passwords with bcrypt/scrypt
62
+ - Token refresh: short-lived access + long-lived refresh
63
+
64
+ ## Rate Limiting
65
+ - Return 429 with headers: `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`
66
+ - Implement per-user, per-IP, or per-API-key
67
+
68
+ ## CORS
69
+ - Set explicit origins (never `*` with credentials)
70
+ - Handle OPTIONS preflight
71
+ - Return CORS headers on error responses too
72
+
73
+ ## Folder Structure
74
+ ```
75
+ api/
76
+ routes/ # Route definitions (thin controllers)
77
+ services/ # Business logic
78
+ models/ # Database models
79
+ schemas/ # Request/response schemas (Pydantic/Zod)
80
+ middleware/ # Auth, rate limiting, CORS
81
+ ```
82
+
83
+ ## Anti-Patterns to Avoid
84
+ - Verbs in URIs: `/getUser` → use `GET /users/{id}`
85
+ - Returning 200 for errors → use proper 4xx/5xx
86
+ - Missing Location header on 201
87
+ - Exposing database internals in API responses
88
+ - No input validation
89
+ - Hardcoded credentials
90
+ - Missing CORS configuration
91
+ - No pagination on collection endpoints
92
+ - GET for write operations
93
+ - Not using dependency injection for DB connections
@@ -0,0 +1,59 @@
1
+ # Python Conventions
2
+
3
+ ## Project Structure
4
+ - Use `src/` layout for libraries, flat layout for apps
5
+ - Package dirs: lowercase with underscores (`my_package`)
6
+ - Always include `__init__.py` in every package dir
7
+ - Include `py.typed` marker for PEP 561
8
+ - Config in `pyproject.toml` (never `setup.py` for new projects)
9
+ - Build backend: `hatchling` or `uv_build`
10
+ - Package manager: `uv` recommended, `poetry` acceptable
11
+
12
+ ## Code Style (PEP 8)
13
+ - 4 spaces indentation, no tabs
14
+ - 88 char line length (Black/ruff default)
15
+ - 2 blank lines before top-level functions/classes
16
+ - 1 blank line between methods inside classes
17
+ - snake_case for functions/variables, PascalCase for classes, UPPER_CASE for constants
18
+
19
+ ## Imports
20
+ - Order: stdlib → third-party → local (separate with blank lines)
21
+ - One import per line
22
+ - Prefer absolute imports over relative
23
+ - Never use `from x import *`
24
+
25
+ ## Type Hints (Modern Python 3.10+)
26
+ - Use built-in generics: `list[int]` not `List[int]`, `dict[str, Any]` not `Dict`
27
+ - Use `X | None` not `Optional[X]`
28
+ - Use `X | Y` not `Union[X, Y]`
29
+ - Always annotate return types and parameters
30
+ - Use `type` keyword for type aliases (Python 3.12+)
31
+
32
+ ## Patterns
33
+ - Use `pathlib.Path` over `os.path`
34
+ - Use f-strings over `.format()` or `%` formatting
35
+ - Use `with` context managers for file I/O (always specify `encoding="utf-8"`)
36
+ - Use `dataclasses` over manual `__init__`/`__repr__`/`__eq__`
37
+ - Use `match/case` (Python 3.10+) over long if/elif chains
38
+ - Use `@contextmanager` for custom context managers
39
+ - Check `is None` / `is not None`, never `== None`
40
+ - Check `if x:` not `if len(x) > 0:`
41
+
42
+ ## Testing (pytest)
43
+ - Test files: `test_*.py`
44
+ - Test functions: `test_<description>`
45
+ - Fixtures in `conftest.py`
46
+ - Use `@pytest.mark.parametrize` for multiple test cases
47
+ - Use `tmp_path` fixture for file operations
48
+ - Use `monkeypatch` for mocking
49
+
50
+ ## Anti-Patterns to Avoid
51
+ - Bare `except:` → use `except Exception:`
52
+ - Mutable default args `def f(x=[])` → use `None` + create inside
53
+ - `os.path` → use `pathlib.Path`
54
+ - `List[int]` → use `list[int]`
55
+ - `print()` in libraries → use `logging`
56
+ - `os.system()` → use `subprocess`
57
+ - `eval()`/`exec()` → use `ast.literal_eval()` if needed
58
+ - Missing `encoding=` on `open()` → always specify `encoding="utf-8"`
59
+ - String concatenation in loops → use `"".join()`
@@ -0,0 +1,83 @@
1
+ # React / Next.js Conventions
2
+
3
+ ## Project Structure (App Router — always prefer over Pages Router)
4
+ ```
5
+ src/
6
+ app/
7
+ layout.tsx # Root layout (REQUIRED, must have <html> and <body>)
8
+ page.tsx # Home page
9
+ loading.tsx # Suspense fallback
10
+ error.tsx # Error boundary (MUST be Client Component)
11
+ not-found.tsx # 404 UI
12
+ (route-group)/ # Organize without affecting URL
13
+ [param]/ # Dynamic segments
14
+ api/route.ts # API routes (.ts NOT .tsx)
15
+ components/
16
+ ui/ # Low-level primitives
17
+ lib/ # Utilities, helpers
18
+ hooks/ # Shared custom hooks
19
+ public/ # Static assets
20
+ ```
21
+
22
+ ## Component Rules
23
+ - Functional components ONLY — never class components
24
+ - Server Components by default (no directive needed)
25
+ - Add `'use client'` only to leaf interactive components (NOT layouts/pages)
26
+ - Server Components can be `async`, use `await` directly
27
+ - Client Components need `'use client'` at top, can use hooks/events
28
+
29
+ ## File Naming
30
+ - Components: PascalCase (`Button.tsx`)
31
+ - Pages/Routes: lowercase (`page.tsx`, `layout.tsx`)
32
+ - Hooks: camelCase with `use` prefix (`useMediaQuery.ts`)
33
+ - Utilities: camelCase (`formatDate.ts`)
34
+ - API routes: `route.ts` (NOT .tsx)
35
+
36
+ ## Import Order
37
+ 1. React/Next.js core (`react`, `next/navigation`, `next/link`)
38
+ 2. Third-party libraries
39
+ 3. Internal components (`@/components/...`)
40
+ 4. Internal hooks/utils (`@/hooks/...`, `@/lib/...`)
41
+ 5. Types (`import type { ... }`)
42
+
43
+ Use `@/` path alias for all internal imports.
44
+
45
+ ## State Management
46
+ - Local state: `useState` for simple, `useReducer` for complex
47
+ - Global state: Context (sparingly) or Zustand
48
+ - Server state: React Query / TanStack Query
49
+ - Lift state up to closest common parent when shared
50
+
51
+ ## Styling
52
+ - Tailwind CSS as default
53
+ - Use `cn()` utility (clsx + tailwind-merge) for conditional classes
54
+ - Use `className` not `class`
55
+ - Avoid inline styles except for dynamic values
56
+
57
+ ## TypeScript
58
+ - Never use `any` — use `unknown` and narrow
59
+ - Use `React.ReactNode` for children props
60
+ - Use type imports: `import type { User } from '@/types'`
61
+ - Use Zod for runtime validation
62
+
63
+ ## Data Fetching
64
+ - Server Components: direct `await fetch()` or DB queries
65
+ - Client Components: React Query or SWR
66
+ - Never use `useEffect` for data fetching in Server Components
67
+ - Parallel fetching: `Promise.all()`
68
+
69
+ ## Performance
70
+ - Don't overuse `memo`, `useMemo`, `useCallback` — only when measured
71
+ - Lazy load with `dynamic()` for heavy components
72
+ - Use `loading.tsx` for instant loading states
73
+
74
+ ## Anti-Patterns to Avoid
75
+ - Class components → functional only
76
+ - `'use client'` on root layout → only on leaf components
77
+ - `useEffect` for data fetching → Server Components or React Query
78
+ - `class` attribute → use `className`
79
+ - Barrel exports (`index.ts`) → import directly from files
80
+ - `.tsx` for API routes → use `.ts`
81
+ - Passing non-serializable props Server→Client
82
+ - Missing `<html>` and `<body>` in root layout
83
+ - Using Pages Router for new projects
@@ -0,0 +1,54 @@
1
+ # Web Development Conventions
2
+
3
+ ## File Structure
4
+ - Single-file apps: `< 300 LOC` → single `index.html` with inline `<style>` and `<script>`
5
+ - Multi-file apps: `index.html` + `css/` + `js/` + `assets/`
6
+ - Files: lowercase-kebab-case (`my-component.js`)
7
+ - CSS classes: BEM or kebab-case (`.card__title--large`)
8
+ - JS variables/functions: camelCase
9
+
10
+ ## HTML Rules
11
+ - Always include: `<!DOCTYPE html>`, `<html lang="en">`, `<meta charset="UTF-8">`, viewport meta
12
+ - Use semantic elements: `<header>`, `<nav>`, `<main>`, `<article>`, `<section>`, `<footer>`
13
+ - Every `<img>` must have `alt` attribute
14
+ - Use `<button>` for actions, `<a>` for navigation (not `<div onclick>`)
15
+ - Maintain heading hierarchy: h1 > h2 > h3 (never skip)
16
+ - Add `rel="noopener noreferrer"` on `target="_blank"` links
17
+
18
+ ## CSS Rules
19
+ - Use CSS custom properties for repeated values
20
+ - Use `box-sizing: border-box` universally
21
+ - Use `rem` for spacing/sizing, `px` only for borders
22
+ - Mobile-first responsive design with `min-width` media queries
23
+ - Flexbox for 1D layouts, Grid for 2D layouts
24
+ - Use `gap` property over margin hacks
25
+ - Never use `float` for layout, never use `!important`
26
+ - Max selector depth: 3 levels
27
+ - Use `clamp()` for fluid typography
28
+
29
+ ## JavaScript Rules
30
+ - Always use `const` by default, `let` when reassignment needed, never `var`
31
+ - Always use `addEventListener`, never inline handlers
32
+ - Use `textContent` for user-provided text (XSS prevention), never `innerHTML` with unsanitized input
33
+ - Always use `try/catch` with async operations
34
+ - Use `===` not `==`
35
+ - Use arrow functions for callbacks
36
+ - Use template literals over string concatenation
37
+ - Use optional chaining `?.` and nullish coalescing `??`
38
+ - Never use `eval()`
39
+
40
+ ## Security
41
+ - Never insert user input as HTML (use `textContent`)
42
+ - Validate client-side AND server-side
43
+ - Never store secrets in localStorage
44
+ - Use HTTPS for all external resources
45
+ - Don't expose API keys in client-side code
46
+
47
+ ## Anti-Patterns to Avoid
48
+ - `<div onclick>` → use `<button>` or `addEventListener`
49
+ - `float: left` for grid → use flexbox or grid
50
+ - `var` → use `const`/`let`
51
+ - `==` → use `===`
52
+ - `innerHTML = userInput` → use `textContent`
53
+ - Global variables → use modules or block scope
54
+ - `.then()` chains → use `async/await`
@@ -0,0 +1,109 @@
1
+ """Skill loader — auto-detects project type and loads relevant conventions."""
2
+
3
+ import os
4
+ import json
5
+ import functools
6
+ from pathlib import Path
7
+
8
+ SKILLS_DIR = Path(__file__).parent / "bundled"
9
+
10
+
11
+ @functools.lru_cache(maxsize=1)
12
+ def _detect_project_type() -> list[str]:
13
+ cwd = Path.cwd()
14
+ detected = []
15
+
16
+ indicators = {
17
+ "web-dev": {
18
+ "files": ["index.html", "*.html"],
19
+ "extensions": [".html", ".htm", ".css", ".scss", ".less"],
20
+ "keywords": ["html", "css", "javascript", "website", "webpage", "landing page"],
21
+ },
22
+ "python": {
23
+ "files": ["pyproject.toml", "setup.py", "requirements.txt", "Pipfile", "uv.lock"],
24
+ "extensions": [".py", ".pyi", ".ipynb"],
25
+ "keywords": ["python", "pip", "uv", "poetry", "pytest", "django", "flask", "fastapi"],
26
+ },
27
+ "react": {
28
+ "files": ["next.config.ts", "next.config.js", "next.config.mjs"],
29
+ "extensions": [".tsx", ".jsx"],
30
+ "keywords": ["react", "next.js", "nextjs", "remix", "vite"],
31
+ "package_deps": ["react", "next"],
32
+ },
33
+ "api-design": {
34
+ "files": ["routes.py", "models.py", "schemas.py"],
35
+ "extensions": [],
36
+ "keywords": ["api", "endpoint", "rest", "graphql", "server", "backend", "fastapi", "express", "flask"],
37
+ },
38
+ }
39
+
40
+ html_files = list(cwd.rglob("*.html"))
41
+ py_files = list(cwd.rglob("*.py"))
42
+ tsx_files = list(cwd.rglob("*.tsx")) + list(cwd.rglob("*.jsx"))
43
+
44
+ for skill_name, config in indicators.items():
45
+ score = 0
46
+
47
+ for pattern in config["files"]:
48
+ if list(cwd.glob(pattern)):
49
+ score += 3
50
+
51
+ for ext in config["extensions"]:
52
+ if skill_name == "web-dev" and ext == ".html" and len(html_files) > 0:
53
+ score += min(len(html_files), 3)
54
+ elif skill_name == "python" and ext == ".py" and len(py_files) > 0:
55
+ score += min(len(py_files), 3)
56
+ elif skill_name in ("react",) and ext in (".tsx", ".jsx") and len(tsx_files) > 0:
57
+ score += min(len(tsx_files), 3)
58
+
59
+ if skill_name == "react":
60
+ pkg_json = cwd / "package.json"
61
+ if pkg_json.exists():
62
+ try:
63
+ pkg = json.loads(pkg_json.read_text())
64
+ deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
65
+ for dep in config.get("package_deps", []):
66
+ if dep in deps:
67
+ score += 5
68
+ except Exception:
69
+ pass
70
+
71
+ if skill_name == "python":
72
+ if (cwd / "pyproject.toml").exists():
73
+ score += 5
74
+ if any(cwd.rglob("fastapi")) or any((cwd / f).exists() for f in ["main.py", "app.py"]):
75
+ score += 2
76
+
77
+ if score >= 2:
78
+ detected.append((skill_name, score))
79
+
80
+ detected.sort(key=lambda x: x[1], reverse=True)
81
+ return [name for name, _ in detected]
82
+
83
+
84
+ def load_skill(skill_name: str) -> str | None:
85
+ skill_dir = SKILLS_DIR / skill_name
86
+ if not skill_dir.exists():
87
+ return None
88
+ conventions_file = skill_dir / "conventions.md"
89
+ if conventions_file.exists():
90
+ return conventions_file.read_text(encoding="utf-8")
91
+ return None
92
+
93
+
94
+ def get_relevant_skills() -> list[str]:
95
+ return _detect_project_type()
96
+
97
+
98
+ def build_skill_context() -> str:
99
+ skills = get_relevant_skills()
100
+ if not skills:
101
+ return ""
102
+
103
+ parts = []
104
+ for skill_name in skills:
105
+ content = load_skill(skill_name)
106
+ if content:
107
+ parts.append(f"## {skill_name} conventions\n{content}")
108
+
109
+ return "\n\n".join(parts)
File without changes
@@ -0,0 +1,15 @@
1
+ from dotenv import load_dotenv
2
+ import os
3
+ import warnings
4
+
5
+ os.environ.setdefault("HF_HUB_DISABLE_SYMLINKS_WARNING", "1")
6
+ os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
7
+ warnings.filterwarnings("ignore", module="huggingface_hub")
8
+ warnings.filterwarnings("ignore", module="sentence_transformers")
9
+
10
+ load_dotenv()
11
+
12
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY")
13
+ OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
14
+ CHROMA_PERSIST_DIR = os.getenv("CHROMA_PERSIST_DIR", ".librarian/memory")
15
+ EMBED_MODEL = os.getenv("EMBED_MODEL", "all-MiniLM-L6-v2")
@@ -0,0 +1,32 @@
1
+ from rich.console import Console
2
+ from rich.theme import Theme
3
+
4
+ theme = Theme({
5
+ "info": "bold #6366F1",
6
+ "success": "bold #10B981",
7
+ "warning": "bold #F59E0B",
8
+ "error": "bold #EF4444",
9
+ "muted": "#6B7280",
10
+ })
11
+
12
+ console = Console(theme=theme)
13
+
14
+
15
+ def log_info(msg):
16
+ console.print(f"[info]→[/info] {msg}")
17
+
18
+
19
+ def log_success(msg):
20
+ console.print(f"[success]✓[/success] {msg}")
21
+
22
+
23
+ def log_warning(msg):
24
+ console.print(f"[warning]![/warning] {msg}")
25
+
26
+
27
+ def log_error(msg):
28
+ console.print(f"[error]✗[/error] {msg}")
29
+
30
+
31
+ def log_muted(msg):
32
+ console.print(f"[muted]{msg}[/muted]")
@@ -0,0 +1,16 @@
1
+ class TokenTracker:
2
+ def __init__(self):
3
+ self.session_tokens = {"groq": 0, "openrouter": 0}
4
+
5
+ def add(self, provider: str, tokens: int):
6
+ if provider in self.session_tokens:
7
+ self.session_tokens[provider] += tokens
8
+
9
+ def total(self) -> int:
10
+ return sum(self.session_tokens.values())
11
+
12
+ def report(self) -> str:
13
+ return f"groq: {self.session_tokens['groq']} · openrouter: {self.session_tokens['openrouter']}"
14
+
15
+
16
+ tracker = TokenTracker()