librarian-code 0.1.0__tar.gz
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.
- librarian_code-0.1.0/.gitignore +14 -0
- librarian_code-0.1.0/.mimocode/plans/1782009063038-gentle-garden.md +95 -0
- librarian_code-0.1.0/LICENSE.md +21 -0
- librarian_code-0.1.0/PKG-INFO +180 -0
- librarian_code-0.1.0/README.md +150 -0
- librarian_code-0.1.0/librarian/__init__.py +3 -0
- librarian_code-0.1.0/librarian/__main__.py +3 -0
- librarian_code-0.1.0/librarian/actions/__init__.py +0 -0
- librarian_code-0.1.0/librarian/actions/file_ops.py +47 -0
- librarian_code-0.1.0/librarian/actions/safety.py +29 -0
- librarian_code-0.1.0/librarian/actions/shell_ops.py +49 -0
- librarian_code-0.1.0/librarian/adapter/__init__.py +0 -0
- librarian_code-0.1.0/librarian/adapter/base.py +11 -0
- librarian_code-0.1.0/librarian/adapter/groq_adapter.py +40 -0
- librarian_code-0.1.0/librarian/adapter/openrouter_adapter.py +58 -0
- librarian_code-0.1.0/librarian/cli.py +26 -0
- librarian_code-0.1.0/librarian/commands/__init__.py +0 -0
- librarian_code-0.1.0/librarian/commands/ask.py +46 -0
- librarian_code-0.1.0/librarian/commands/do.py +232 -0
- librarian_code-0.1.0/librarian/commands/init.py +96 -0
- librarian_code-0.1.0/librarian/commands/status.py +71 -0
- librarian_code-0.1.0/librarian/commands/undo.py +85 -0
- librarian_code-0.1.0/librarian/commands/why.py +47 -0
- librarian_code-0.1.0/librarian/exceptions.py +22 -0
- librarian_code-0.1.0/librarian/memory/__init__.py +0 -0
- librarian_code-0.1.0/librarian/memory/capsule.py +94 -0
- librarian_code-0.1.0/librarian/memory/chunker.py +183 -0
- librarian_code-0.1.0/librarian/memory/decision_log.py +36 -0
- librarian_code-0.1.0/librarian/memory/indexer.py +96 -0
- librarian_code-0.1.0/librarian/memory/retriever.py +62 -0
- librarian_code-0.1.0/librarian/orchestrator/__init__.py +0 -0
- librarian_code-0.1.0/librarian/orchestrator/core.py +47 -0
- librarian_code-0.1.0/librarian/orchestrator/router.py +17 -0
- librarian_code-0.1.0/librarian/skills/__init__.py +0 -0
- librarian_code-0.1.0/librarian/skills/bundled/__init__.py +0 -0
- librarian_code-0.1.0/librarian/skills/bundled/api-design/conventions.md +93 -0
- librarian_code-0.1.0/librarian/skills/bundled/python/conventions.md +59 -0
- librarian_code-0.1.0/librarian/skills/bundled/react/conventions.md +83 -0
- librarian_code-0.1.0/librarian/skills/bundled/web-dev/conventions.md +54 -0
- librarian_code-0.1.0/librarian/skills/loader.py +109 -0
- librarian_code-0.1.0/librarian/utils/__init__.py +0 -0
- librarian_code-0.1.0/librarian/utils/config.py +15 -0
- librarian_code-0.1.0/librarian/utils/logger.py +32 -0
- librarian_code-0.1.0/librarian/utils/token_tracker.py +16 -0
- librarian_code-0.1.0/librarian/utils/ui.py +97 -0
- librarian_code-0.1.0/pyproject.toml +48 -0
- librarian_code-0.1.0/tests/__init__.py +0 -0
- librarian_code-0.1.0/tests/test_actions.py +56 -0
- librarian_code-0.1.0/tests/test_adapter.py +69 -0
- librarian_code-0.1.0/tests/test_commands.py +40 -0
- librarian_code-0.1.0/tests/test_memory.py +69 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Plan: Publish to PyPI for `pip install -g librarian-code`
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
Make the package installable globally via `pip install -g librarian-code`.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Phase 1: Fix Package Metadata
|
|
9
|
+
|
|
10
|
+
### 1. Update `pyproject.toml`
|
|
11
|
+
Add missing fields: `readme`, `license`, `authors`, `classifiers`, `urls`, and build artifacts for bundled skills.
|
|
12
|
+
|
|
13
|
+
### 2. Add `__version__` to `librarian/__init__.py`
|
|
14
|
+
```python
|
|
15
|
+
__version__ = "0.1.0"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### 3. Fix `README.md`
|
|
19
|
+
Remove `.env.example` reference (file doesn't exist).
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Phase 2: Set Up PyPI Account
|
|
24
|
+
|
|
25
|
+
1. Go to https://pypi.org/account/register/
|
|
26
|
+
2. Create account (use a dedicated email)
|
|
27
|
+
3. Verify email
|
|
28
|
+
|
|
29
|
+
### Generate API Token
|
|
30
|
+
1. Go to https://pypi.org/manage/account/token/
|
|
31
|
+
2. Click "Add API token"
|
|
32
|
+
3. Name: `librarian-code-publish`
|
|
33
|
+
4. Scope: "Entire account" (or project-specific after first upload)
|
|
34
|
+
5. Copy token — **it won't be shown again**
|
|
35
|
+
|
|
36
|
+
### Store Token Locally
|
|
37
|
+
```bash
|
|
38
|
+
pip install twine
|
|
39
|
+
# Create ~/.pypirc (Windows: %USERPROFILE%\.pypirc)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Contents:
|
|
43
|
+
```ini
|
|
44
|
+
[pypi]
|
|
45
|
+
username = __token__
|
|
46
|
+
password = pypi-YOUR_TOKEN_HERE
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Phase 3: Build & Publish
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# 1. Install build tools
|
|
55
|
+
pip install build twine
|
|
56
|
+
|
|
57
|
+
# 2. Clean old builds
|
|
58
|
+
rm -rf dist/ build/ *.egg-info
|
|
59
|
+
|
|
60
|
+
# 3. Build
|
|
61
|
+
python -m build
|
|
62
|
+
|
|
63
|
+
# 4. Verify package contents
|
|
64
|
+
twine check dist/*
|
|
65
|
+
|
|
66
|
+
# 5. Upload to PyPI
|
|
67
|
+
twine upload dist/*
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Phase 4: Verify
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Test install from PyPI
|
|
76
|
+
pip install -g librarian-code
|
|
77
|
+
|
|
78
|
+
# Should work
|
|
79
|
+
librarian --version
|
|
80
|
+
librarian --help
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Files to Modify
|
|
86
|
+
- `pyproject.toml`
|
|
87
|
+
- `librarian/__init__.py`
|
|
88
|
+
- `README.md`
|
|
89
|
+
|
|
90
|
+
## Verification
|
|
91
|
+
1. `python -m build` — produces `.whl` + `.tar.gz`
|
|
92
|
+
2. `twine check dist/*` — passes
|
|
93
|
+
3. `pip install dist/*.whl` — installs correctly
|
|
94
|
+
4. `librarian --version` — prints `0.1.0`
|
|
95
|
+
5. Bundled skills `.md` files included in wheel
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Neel Sorathiya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: librarian-code
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A local-first CLI coding agent with persistent project memory
|
|
5
|
+
Project-URL: Homepage, https://github.com/Humble-Librarian/Librarian-Code
|
|
6
|
+
Project-URL: Repository, https://github.com/Humble-Librarian/Librarian-Code
|
|
7
|
+
Project-URL: Issues, https://github.com/Humble-Librarian/Librarian-Code/issues
|
|
8
|
+
Author: Neel Sorathiya
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE.md
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: chromadb>=0.5.0
|
|
21
|
+
Requires-Dist: gitpython>=3.1.0
|
|
22
|
+
Requires-Dist: groq>=0.11.0
|
|
23
|
+
Requires-Dist: httpx>=0.27.0
|
|
24
|
+
Requires-Dist: pyfiglet>=1.0.0
|
|
25
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
26
|
+
Requires-Dist: rich>=13.0.0
|
|
27
|
+
Requires-Dist: sentence-transformers>=3.0.0
|
|
28
|
+
Requires-Dist: typer>=0.12.0
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# librarian
|
|
32
|
+
|
|
33
|
+
> a CLI coding agent that remembers your project
|
|
34
|
+
|
|
35
|
+
## what it does differently
|
|
36
|
+
|
|
37
|
+
Librarian is a local-first CLI coding agent with persistent project memory. Unlike tools that call an LLM on every request regardless of confidence, Librarian builds a capsule-based memory of your project's decisions — remembering why edits were made, adjusting confidence over time, and routing between Groq and OpenRouter intelligently when rate limits hit.
|
|
38
|
+
|
|
39
|
+
Built without LangChain — pure Python, owned stack. Every file operation, every LLM call, every decision is transparent and logged.
|
|
40
|
+
|
|
41
|
+
## install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install -g librarian-code
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## setup
|
|
48
|
+
|
|
49
|
+
You need at least one free API key:
|
|
50
|
+
|
|
51
|
+
| Provider | Get Key | Cost |
|
|
52
|
+
|----------|---------|------|
|
|
53
|
+
| **Groq** | https://console.groq.com | Free tier available |
|
|
54
|
+
| **OpenRouter** | https://openrouter.ai | Free models available |
|
|
55
|
+
|
|
56
|
+
Create `.env` in your project root:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# option 1: groq (fast)
|
|
60
|
+
echo "GROQ_API_KEY=gsk_..." > .env
|
|
61
|
+
|
|
62
|
+
# option 2: openrouter
|
|
63
|
+
echo "OPENROUTER_API_KEY=sk-or-..." > .env
|
|
64
|
+
|
|
65
|
+
# or both (openrouter used as fallback)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## quick start
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
librarian init # index your project
|
|
72
|
+
librarian ask "what does this project do?"
|
|
73
|
+
librarian do "add input validation to login()"
|
|
74
|
+
librarian why # see decision history
|
|
75
|
+
librarian undo # revert last action
|
|
76
|
+
librarian status # project overview
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## commands
|
|
80
|
+
|
|
81
|
+
| command | what it does |
|
|
82
|
+
|---------|-------------|
|
|
83
|
+
| `librarian init` | indexes project files, generates LIBRARIAN.md conventions |
|
|
84
|
+
| `librarian ask` | asks a question about your codebase, returns answer with sources |
|
|
85
|
+
| `librarian do` | gives librarian a task, shows plan preview, executes with your approval |
|
|
86
|
+
| `librarian why` | shows last decisions with reasoning |
|
|
87
|
+
| `librarian undo` | reverts the last agent action |
|
|
88
|
+
| `librarian status` | shows project info, memory stats, token usage |
|
|
89
|
+
|
|
90
|
+
## how memory works
|
|
91
|
+
|
|
92
|
+
Librarian uses a **capsule-based memory system**:
|
|
93
|
+
|
|
94
|
+
- Every action creates a **capsule** with a confidence score (starts at 0.5)
|
|
95
|
+
- When you approve an action: confidence × 1.15
|
|
96
|
+
- When you undo an action: confidence × 0.6
|
|
97
|
+
- Unused capsules decay: × 0.98 per day
|
|
98
|
+
- Capsules below 0.4 confidence are archived
|
|
99
|
+
|
|
100
|
+
This means Librarian learns from your feedback over time — actions you approve become more confident, actions you undo become less likely to be repeated.
|
|
101
|
+
|
|
102
|
+
## skills
|
|
103
|
+
|
|
104
|
+
Librarian auto-detects your project type and loads relevant conventions:
|
|
105
|
+
|
|
106
|
+
- **python**: pyproject.toml, setup.py, requirements.txt
|
|
107
|
+
- **react**: next.config.*, .tsx/.jsx files
|
|
108
|
+
- **web-dev**: .html files, CSS/SCSS
|
|
109
|
+
- **api-design**: routes.py, models.py, schemas.py
|
|
110
|
+
|
|
111
|
+
Skills provide domain-specific best practices that are injected into the LLM context for more relevant suggestions.
|
|
112
|
+
|
|
113
|
+
## architecture
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
librarian/
|
|
117
|
+
├── adapter/ # LLM adapters (Groq primary, OpenRouter fallback)
|
|
118
|
+
│ ├── base.py # abstract adapter interface
|
|
119
|
+
│ ├── groq_adapter.py
|
|
120
|
+
│ └── openrouter_adapter.py
|
|
121
|
+
├── orchestrator/ # routing and system prompt building
|
|
122
|
+
│ ├── router.py # Groq → OpenRouter fallback
|
|
123
|
+
│ └── core.py # prompt construction
|
|
124
|
+
├── memory/ # persistent project memory
|
|
125
|
+
│ ├── chunker.py # AST-based code splitting
|
|
126
|
+
│ ├── indexer.py # ChromaDB + sentence-transformers
|
|
127
|
+
│ ├── retriever.py # semantic search (cached model)
|
|
128
|
+
│ ├── capsule.py # decision memory with confidence
|
|
129
|
+
│ └── decision_log.py # append-only action log
|
|
130
|
+
├── skills/ # auto-detected project conventions
|
|
131
|
+
│ ├── loader.py # project type detection with caching
|
|
132
|
+
│ └── bundled/ # skill convention files
|
|
133
|
+
├── actions/ # file and shell operations
|
|
134
|
+
│ ├── file_ops.py # read, write, edit files
|
|
135
|
+
│ ├── shell_ops.py # git and shell commands (shell=False)
|
|
136
|
+
│ └── safety.py # risk classification
|
|
137
|
+
├── commands/ # CLI commands
|
|
138
|
+
│ ├── init.py
|
|
139
|
+
│ ├── ask.py
|
|
140
|
+
│ ├── do.py
|
|
141
|
+
│ ├── why.py
|
|
142
|
+
│ ├── undo.py
|
|
143
|
+
│ └── status.py
|
|
144
|
+
├── utils/ # shared utilities
|
|
145
|
+
│ ├── config.py # env var loading
|
|
146
|
+
│ ├── ui.py # Terminal Luxury output
|
|
147
|
+
│ ├── logger.py # structured logging
|
|
148
|
+
│ └── token_tracker.py
|
|
149
|
+
├── cli.py # typer entry point
|
|
150
|
+
└── exceptions.py # custom exception types
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## providers
|
|
154
|
+
|
|
155
|
+
- **Groq** (primary): `llama-3.3-70b-versatile`, fast inference
|
|
156
|
+
- **OpenRouter** (fallback): `qwen/qwen3-coder:free`, automatic on rate limit
|
|
157
|
+
|
|
158
|
+
## security
|
|
159
|
+
|
|
160
|
+
- Shell commands use `shell=False` with argument lists to prevent injection
|
|
161
|
+
- File operations use proper context managers to prevent handle leaks
|
|
162
|
+
- API responses validated before access
|
|
163
|
+
- LLM-generated delete operations require confirmation
|
|
164
|
+
|
|
165
|
+
## performance
|
|
166
|
+
|
|
167
|
+
- SentenceTransformer model cached as singleton (~2-3s saved per invocation)
|
|
168
|
+
- ChromaDB client reused across calls
|
|
169
|
+
- Project type detection cached with `@lru_cache`
|
|
170
|
+
- Heavy dependencies lazy-loaded at function call time
|
|
171
|
+
|
|
172
|
+
## testing
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
python -m pytest tests/ -v
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## license
|
|
179
|
+
|
|
180
|
+
MIT
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# librarian
|
|
2
|
+
|
|
3
|
+
> a CLI coding agent that remembers your project
|
|
4
|
+
|
|
5
|
+
## what it does differently
|
|
6
|
+
|
|
7
|
+
Librarian is a local-first CLI coding agent with persistent project memory. Unlike tools that call an LLM on every request regardless of confidence, Librarian builds a capsule-based memory of your project's decisions — remembering why edits were made, adjusting confidence over time, and routing between Groq and OpenRouter intelligently when rate limits hit.
|
|
8
|
+
|
|
9
|
+
Built without LangChain — pure Python, owned stack. Every file operation, every LLM call, every decision is transparent and logged.
|
|
10
|
+
|
|
11
|
+
## install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install -g librarian-code
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## setup
|
|
18
|
+
|
|
19
|
+
You need at least one free API key:
|
|
20
|
+
|
|
21
|
+
| Provider | Get Key | Cost |
|
|
22
|
+
|----------|---------|------|
|
|
23
|
+
| **Groq** | https://console.groq.com | Free tier available |
|
|
24
|
+
| **OpenRouter** | https://openrouter.ai | Free models available |
|
|
25
|
+
|
|
26
|
+
Create `.env` in your project root:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# option 1: groq (fast)
|
|
30
|
+
echo "GROQ_API_KEY=gsk_..." > .env
|
|
31
|
+
|
|
32
|
+
# option 2: openrouter
|
|
33
|
+
echo "OPENROUTER_API_KEY=sk-or-..." > .env
|
|
34
|
+
|
|
35
|
+
# or both (openrouter used as fallback)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## quick start
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
librarian init # index your project
|
|
42
|
+
librarian ask "what does this project do?"
|
|
43
|
+
librarian do "add input validation to login()"
|
|
44
|
+
librarian why # see decision history
|
|
45
|
+
librarian undo # revert last action
|
|
46
|
+
librarian status # project overview
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## commands
|
|
50
|
+
|
|
51
|
+
| command | what it does |
|
|
52
|
+
|---------|-------------|
|
|
53
|
+
| `librarian init` | indexes project files, generates LIBRARIAN.md conventions |
|
|
54
|
+
| `librarian ask` | asks a question about your codebase, returns answer with sources |
|
|
55
|
+
| `librarian do` | gives librarian a task, shows plan preview, executes with your approval |
|
|
56
|
+
| `librarian why` | shows last decisions with reasoning |
|
|
57
|
+
| `librarian undo` | reverts the last agent action |
|
|
58
|
+
| `librarian status` | shows project info, memory stats, token usage |
|
|
59
|
+
|
|
60
|
+
## how memory works
|
|
61
|
+
|
|
62
|
+
Librarian uses a **capsule-based memory system**:
|
|
63
|
+
|
|
64
|
+
- Every action creates a **capsule** with a confidence score (starts at 0.5)
|
|
65
|
+
- When you approve an action: confidence × 1.15
|
|
66
|
+
- When you undo an action: confidence × 0.6
|
|
67
|
+
- Unused capsules decay: × 0.98 per day
|
|
68
|
+
- Capsules below 0.4 confidence are archived
|
|
69
|
+
|
|
70
|
+
This means Librarian learns from your feedback over time — actions you approve become more confident, actions you undo become less likely to be repeated.
|
|
71
|
+
|
|
72
|
+
## skills
|
|
73
|
+
|
|
74
|
+
Librarian auto-detects your project type and loads relevant conventions:
|
|
75
|
+
|
|
76
|
+
- **python**: pyproject.toml, setup.py, requirements.txt
|
|
77
|
+
- **react**: next.config.*, .tsx/.jsx files
|
|
78
|
+
- **web-dev**: .html files, CSS/SCSS
|
|
79
|
+
- **api-design**: routes.py, models.py, schemas.py
|
|
80
|
+
|
|
81
|
+
Skills provide domain-specific best practices that are injected into the LLM context for more relevant suggestions.
|
|
82
|
+
|
|
83
|
+
## architecture
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
librarian/
|
|
87
|
+
├── adapter/ # LLM adapters (Groq primary, OpenRouter fallback)
|
|
88
|
+
│ ├── base.py # abstract adapter interface
|
|
89
|
+
│ ├── groq_adapter.py
|
|
90
|
+
│ └── openrouter_adapter.py
|
|
91
|
+
├── orchestrator/ # routing and system prompt building
|
|
92
|
+
│ ├── router.py # Groq → OpenRouter fallback
|
|
93
|
+
│ └── core.py # prompt construction
|
|
94
|
+
├── memory/ # persistent project memory
|
|
95
|
+
│ ├── chunker.py # AST-based code splitting
|
|
96
|
+
│ ├── indexer.py # ChromaDB + sentence-transformers
|
|
97
|
+
│ ├── retriever.py # semantic search (cached model)
|
|
98
|
+
│ ├── capsule.py # decision memory with confidence
|
|
99
|
+
│ └── decision_log.py # append-only action log
|
|
100
|
+
├── skills/ # auto-detected project conventions
|
|
101
|
+
│ ├── loader.py # project type detection with caching
|
|
102
|
+
│ └── bundled/ # skill convention files
|
|
103
|
+
├── actions/ # file and shell operations
|
|
104
|
+
│ ├── file_ops.py # read, write, edit files
|
|
105
|
+
│ ├── shell_ops.py # git and shell commands (shell=False)
|
|
106
|
+
│ └── safety.py # risk classification
|
|
107
|
+
├── commands/ # CLI commands
|
|
108
|
+
│ ├── init.py
|
|
109
|
+
│ ├── ask.py
|
|
110
|
+
│ ├── do.py
|
|
111
|
+
│ ├── why.py
|
|
112
|
+
│ ├── undo.py
|
|
113
|
+
│ └── status.py
|
|
114
|
+
├── utils/ # shared utilities
|
|
115
|
+
│ ├── config.py # env var loading
|
|
116
|
+
│ ├── ui.py # Terminal Luxury output
|
|
117
|
+
│ ├── logger.py # structured logging
|
|
118
|
+
│ └── token_tracker.py
|
|
119
|
+
├── cli.py # typer entry point
|
|
120
|
+
└── exceptions.py # custom exception types
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## providers
|
|
124
|
+
|
|
125
|
+
- **Groq** (primary): `llama-3.3-70b-versatile`, fast inference
|
|
126
|
+
- **OpenRouter** (fallback): `qwen/qwen3-coder:free`, automatic on rate limit
|
|
127
|
+
|
|
128
|
+
## security
|
|
129
|
+
|
|
130
|
+
- Shell commands use `shell=False` with argument lists to prevent injection
|
|
131
|
+
- File operations use proper context managers to prevent handle leaks
|
|
132
|
+
- API responses validated before access
|
|
133
|
+
- LLM-generated delete operations require confirmation
|
|
134
|
+
|
|
135
|
+
## performance
|
|
136
|
+
|
|
137
|
+
- SentenceTransformer model cached as singleton (~2-3s saved per invocation)
|
|
138
|
+
- ChromaDB client reused across calls
|
|
139
|
+
- Project type detection cached with `@lru_cache`
|
|
140
|
+
- Heavy dependencies lazy-loaded at function call time
|
|
141
|
+
|
|
142
|
+
## testing
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
python -m pytest tests/ -v
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## license
|
|
149
|
+
|
|
150
|
+
MIT
|
|
File without changes
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
IGNORED_PATHS = [".git", "node_modules", "__pycache__", ".librarian", "venv", ".env"]
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def read_file(path: str) -> str:
|
|
8
|
+
try:
|
|
9
|
+
return Path(path).read_text(encoding="utf-8")
|
|
10
|
+
except UnicodeDecodeError:
|
|
11
|
+
return Path(path).read_text(encoding="latin-1")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def write_file(path: str, content: str) -> bool:
|
|
15
|
+
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
|
16
|
+
Path(path).write_text(content, encoding="utf-8")
|
|
17
|
+
return True
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def edit_file(path: str, old: str, new: str) -> bool:
|
|
21
|
+
content = read_file(path)
|
|
22
|
+
count = content.count(old)
|
|
23
|
+
if count == 0:
|
|
24
|
+
raise ValueError(f"String not found in {path}")
|
|
25
|
+
if count > 1:
|
|
26
|
+
raise ValueError(f"Ambiguous edit: string appears {count} times in {path}")
|
|
27
|
+
content = content.replace(old, new, 1)
|
|
28
|
+
write_file(path, content)
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def list_files(directory: str, extensions: list[str] = None) -> list[str]:
|
|
33
|
+
ignored = get_ignored_paths()
|
|
34
|
+
results = []
|
|
35
|
+
for root, dirs, files in os.walk(directory):
|
|
36
|
+
dirs[:] = [d for d in dirs if d not in ignored]
|
|
37
|
+
for f in files:
|
|
38
|
+
if extensions:
|
|
39
|
+
ext = os.path.splitext(f)[1].lower()
|
|
40
|
+
if ext not in extensions:
|
|
41
|
+
continue
|
|
42
|
+
results.append(os.path.join(root, f))
|
|
43
|
+
return sorted(results)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_ignored_paths() -> list[str]:
|
|
47
|
+
return IGNORED_PATHS
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RiskLevel(Enum):
|
|
5
|
+
SAFE = "safe"
|
|
6
|
+
CONFIRM = "confirm"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
CONFIRM_ACTIONS = [
|
|
10
|
+
"git push",
|
|
11
|
+
"git reset --hard",
|
|
12
|
+
"rm ",
|
|
13
|
+
"delete",
|
|
14
|
+
"drop table",
|
|
15
|
+
"truncate",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def classify_action(action: str) -> RiskLevel:
|
|
20
|
+
action_lower = action.lower()
|
|
21
|
+
for pattern in CONFIRM_ACTIONS:
|
|
22
|
+
if pattern in action_lower:
|
|
23
|
+
return RiskLevel.CONFIRM
|
|
24
|
+
return RiskLevel.SAFE
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def request_confirm(action: str) -> bool:
|
|
28
|
+
from rich.prompt import Confirm
|
|
29
|
+
return Confirm.ask(f"[bold #F59E0B]confirm:[/bold #F59E0B] {action}")
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import shlex
|
|
3
|
+
from librarian.actions.safety import classify_action, RiskLevel, request_confirm
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def run_command(cmd: str, cwd: str = None) -> tuple[int, str, str]:
|
|
7
|
+
if isinstance(cmd, str):
|
|
8
|
+
args = shlex.split(cmd)
|
|
9
|
+
else:
|
|
10
|
+
args = cmd
|
|
11
|
+
result = subprocess.run(
|
|
12
|
+
args, shell=False, cwd=cwd,
|
|
13
|
+
capture_output=True, text=True,
|
|
14
|
+
)
|
|
15
|
+
return result.returncode, result.stdout, result.stderr
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def git_stage(files: list[str]) -> bool:
|
|
19
|
+
args = ["git", "add"] + files
|
|
20
|
+
code, _, err = run_command(args)
|
|
21
|
+
if code != 0:
|
|
22
|
+
raise RuntimeError(f"git add failed: {err}")
|
|
23
|
+
return True
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def git_commit(message: str) -> bool:
|
|
27
|
+
args = ["git", "commit", "-m", message]
|
|
28
|
+
code, _, err = run_command(args)
|
|
29
|
+
if code != 0:
|
|
30
|
+
raise RuntimeError(f"git commit failed: {err}")
|
|
31
|
+
return True
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def git_push() -> bool:
|
|
35
|
+
risk = classify_action("git push")
|
|
36
|
+
if risk == RiskLevel.CONFIRM:
|
|
37
|
+
if not request_confirm("push to remote?"):
|
|
38
|
+
return False
|
|
39
|
+
code, _, err = run_command("git push")
|
|
40
|
+
if code != 0:
|
|
41
|
+
raise RuntimeError(f"git push failed: {err}")
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def git_status() -> str:
|
|
46
|
+
code, out, err = run_command("git status --short")
|
|
47
|
+
if code != 0:
|
|
48
|
+
return err
|
|
49
|
+
return out.strip()
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from groq import Groq, RateLimitError as GroqRateLimitError, APIConnectionError
|
|
2
|
+
from librarian.adapter.base import LLMAdapter
|
|
3
|
+
from librarian.exceptions import RateLimitError, ProviderUnavailableError
|
|
4
|
+
from librarian.utils.config import GROQ_API_KEY
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GroqAdapter(LLMAdapter):
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self.client = Groq(api_key=GROQ_API_KEY) if GROQ_API_KEY else None
|
|
10
|
+
self.model = "llama-3.3-70b-versatile"
|
|
11
|
+
self.tokens_used = 0
|
|
12
|
+
|
|
13
|
+
def complete(self, system: str, prompt: str) -> str:
|
|
14
|
+
if not self.client:
|
|
15
|
+
raise ProviderUnavailableError("GROQ_API_KEY not set")
|
|
16
|
+
try:
|
|
17
|
+
response = self.client.chat.completions.create(
|
|
18
|
+
model=self.model,
|
|
19
|
+
messages=[
|
|
20
|
+
{"role": "system", "content": system},
|
|
21
|
+
{"role": "user", "content": prompt},
|
|
22
|
+
],
|
|
23
|
+
temperature=0.2,
|
|
24
|
+
max_tokens=4096,
|
|
25
|
+
)
|
|
26
|
+
self.tokens_used += response.usage.total_tokens
|
|
27
|
+
return response.choices[0].message.content
|
|
28
|
+
except GroqRateLimitError:
|
|
29
|
+
raise RateLimitError("Groq rate limit exceeded")
|
|
30
|
+
except APIConnectionError:
|
|
31
|
+
raise ProviderUnavailableError("Cannot connect to Groq")
|
|
32
|
+
|
|
33
|
+
def is_available(self) -> bool:
|
|
34
|
+
if not self.client:
|
|
35
|
+
return False
|
|
36
|
+
try:
|
|
37
|
+
self.client.models.list()
|
|
38
|
+
return True
|
|
39
|
+
except Exception:
|
|
40
|
+
return False
|