lybrary 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.
- lybrary-0.1.0/.gitignore +47 -0
- lybrary-0.1.0/CHANGELOG.md +50 -0
- lybrary-0.1.0/PKG-INFO +161 -0
- lybrary-0.1.0/README.md +118 -0
- lybrary-0.1.0/lybrary/__init__.py +3 -0
- lybrary-0.1.0/lybrary/_daemon_worker.py +39 -0
- lybrary-0.1.0/lybrary/cli.py +247 -0
- lybrary-0.1.0/lybrary/config.py +98 -0
- lybrary-0.1.0/lybrary/daemon.py +194 -0
- lybrary-0.1.0/lybrary/indexer/__init__.py +1 -0
- lybrary-0.1.0/lybrary/indexer/ast_chunker.py +456 -0
- lybrary-0.1.0/lybrary/indexer/embedder.py +44 -0
- lybrary-0.1.0/lybrary/indexer/graph.py +7 -0
- lybrary-0.1.0/lybrary/indexer/store.py +221 -0
- lybrary-0.1.0/lybrary/indexer/walker.py +115 -0
- lybrary-0.1.0/lybrary/mcp/__init__.py +5 -0
- lybrary-0.1.0/lybrary/mcp/server.py +372 -0
- lybrary-0.1.0/lybrary/py.typed +0 -0
- lybrary-0.1.0/lybrary/query.py +74 -0
- lybrary-0.1.0/lybrary/utils.py +32 -0
- lybrary-0.1.0/lybrary/watcher.py +138 -0
- lybrary-0.1.0/pyproject.toml +85 -0
- lybrary-0.1.0/tests/__init__.py +0 -0
- lybrary-0.1.0/tests/test_chunker.py +286 -0
- lybrary-0.1.0/tests/test_query.py +154 -0
- lybrary-0.1.0/tests/test_store.py +268 -0
- lybrary-0.1.0/tests/test_watcher.py +199 -0
lybrary-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
*.egg
|
|
7
|
+
*.egg-info/
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
.eggs/
|
|
11
|
+
wheels/
|
|
12
|
+
|
|
13
|
+
# Virtual environments
|
|
14
|
+
.venv/
|
|
15
|
+
venv/
|
|
16
|
+
env/
|
|
17
|
+
ENV/
|
|
18
|
+
|
|
19
|
+
# Testing & coverage
|
|
20
|
+
.pytest_cache/
|
|
21
|
+
.coverage
|
|
22
|
+
.coverage.*
|
|
23
|
+
htmlcov/
|
|
24
|
+
*.xml
|
|
25
|
+
|
|
26
|
+
# Type checking
|
|
27
|
+
.mypy_cache/
|
|
28
|
+
.pyright/
|
|
29
|
+
|
|
30
|
+
# lybrary runtime data — never commit a repo's index
|
|
31
|
+
.lybrary/
|
|
32
|
+
|
|
33
|
+
# IDE
|
|
34
|
+
.idea/
|
|
35
|
+
.vscode/
|
|
36
|
+
*.swp
|
|
37
|
+
*.swo
|
|
38
|
+
*.DS_Store
|
|
39
|
+
|
|
40
|
+
# Distribution / packaging
|
|
41
|
+
MANIFEST
|
|
42
|
+
pip-wheel-metadata/
|
|
43
|
+
share/python-wheels/
|
|
44
|
+
*.dist-info/
|
|
45
|
+
|
|
46
|
+
# Logs
|
|
47
|
+
*.log
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to lybrary are documented here.
|
|
4
|
+
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## [0.1.0] — 2026-08-07
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- AST-aware chunking (cAST-style) via tree-sitter for Python, JavaScript,
|
|
12
|
+
TypeScript, TSX, Go, Rust, Java, C, and C++.
|
|
13
|
+
- Incremental indexing via xxhash content-hash tracking (`file_hashes.json`).
|
|
14
|
+
- LanceDB-backed vector store with MiniLM-L6-v2 embeddings
|
|
15
|
+
(sentence-transformers, fully local).
|
|
16
|
+
- Background daemon with watchdog file watcher and configurable debounce.
|
|
17
|
+
- CLI commands: `init`, `start`, `stop`, `status`, `index`, `query`, `logs`,
|
|
18
|
+
`mcp`, `version`.
|
|
19
|
+
- MCP server (`lybrary mcp`) exposing three tools over stdio transport:
|
|
20
|
+
- `memory_query` — semantic search over the indexed code memory.
|
|
21
|
+
- `memory_status` — daemon/index health check.
|
|
22
|
+
- `memory_update` — incremental or full re-index, optionally scoped to
|
|
23
|
+
specific files.
|
|
24
|
+
- `py.typed` marker for PEP 561 compliance.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- TSX files now use the tsx tree-sitter grammar instead of being silently
|
|
28
|
+
downgraded to the TypeScript grammar.
|
|
29
|
+
- Nested definitions (methods inside classes) are no longer emitted twice.
|
|
30
|
+
- Overlapping byte ranges are properly merged before computing leftover
|
|
31
|
+
module-level content.
|
|
32
|
+
- Chunk IDs include the byte offset, preventing collisions for same-named
|
|
33
|
+
symbols at the same line.
|
|
34
|
+
- `_sanitize_name` strips characters that would corrupt chunk IDs or
|
|
35
|
+
LanceDB filter strings.
|
|
36
|
+
- `delete_path` escapes double-quotes in paths to prevent filter injection.
|
|
37
|
+
- `search_vector` no longer returns the raw 384-float vector column to callers.
|
|
38
|
+
- Full re-index uses `truncate()` (drop + recreate) instead of iterating
|
|
39
|
+
`all_paths()`, eliminating stale row leakage.
|
|
40
|
+
- File-move/rename events are handled correctly: the new path is indexed and
|
|
41
|
+
the old path is deleted from the store.
|
|
42
|
+
- `_update_file` reads the file exactly once, eliminating the race condition
|
|
43
|
+
between chunk generation and hash storage.
|
|
44
|
+
- Windows daemon now spawns a detached subprocess and writes a PID file so
|
|
45
|
+
that `lybrary stop` and `lybrary status` work on Windows.
|
|
46
|
+
- Score formula uses proper cosine similarity (`1 - d²/2`) for unit-normalised
|
|
47
|
+
vectors instead of the previous `1/(1+d)` approximation.
|
|
48
|
+
- Unused `ThreadPoolExecutor` import removed from `walker.py`.
|
|
49
|
+
- Dead `merge_siblings` config option removed from `ChunkConfig`.
|
|
50
|
+
- Embedder normalises the device string before using it as an `lru_cache` key.
|
lybrary-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lybrary
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Living structure-aware code memory for AI coding agents
|
|
5
|
+
Author: lybrary contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: agent,ast,code,indexing,mcp,memory,rag
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Requires-Dist: fastembed<2.0.0,>=0.4.0
|
|
19
|
+
Requires-Dist: mcp<2.0.0,>=1.0.0
|
|
20
|
+
Requires-Dist: numpy<3.0.0,>=1.26.0
|
|
21
|
+
Requires-Dist: pathspec<1.0.0,>=0.12.0
|
|
22
|
+
Requires-Dist: pydantic-settings<3.0.0,>=2.2.0
|
|
23
|
+
Requires-Dist: pydantic<3.0.0,>=2.6.0
|
|
24
|
+
Requires-Dist: rich<14.0.0,>=13.7.0
|
|
25
|
+
Requires-Dist: tomli-w<2.0.0,>=1.0.0
|
|
26
|
+
Requires-Dist: tree-sitter-c<1.0.0,>=0.21.0
|
|
27
|
+
Requires-Dist: tree-sitter-cpp<1.0.0,>=0.22.0
|
|
28
|
+
Requires-Dist: tree-sitter-go<1.0.0,>=0.21.0
|
|
29
|
+
Requires-Dist: tree-sitter-java<1.0.0,>=0.21.0
|
|
30
|
+
Requires-Dist: tree-sitter-javascript<1.0.0,>=0.21.0
|
|
31
|
+
Requires-Dist: tree-sitter-python<1.0.0,>=0.21.0
|
|
32
|
+
Requires-Dist: tree-sitter-rust<1.0.0,>=0.21.0
|
|
33
|
+
Requires-Dist: tree-sitter-typescript<1.0.0,>=0.21.0
|
|
34
|
+
Requires-Dist: tree-sitter<0.26.0,>=0.24.0
|
|
35
|
+
Requires-Dist: typer[all]<1.0.0,>=0.12.0
|
|
36
|
+
Requires-Dist: watchdog<6.0.0,>=4.0.0
|
|
37
|
+
Requires-Dist: xxhash<4.0.0,>=3.4.0
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: pytest-asyncio<1.0.0,>=0.23.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: pytest<9.0.0,>=8.0.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: ruff<1.0.0,>=0.4.0; extra == 'dev'
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
# lybrary
|
|
45
|
+
|
|
46
|
+
**Living structure-aware code memory for AI coding agents.**
|
|
47
|
+
|
|
48
|
+
`lybrary` indexes your repository with real AST boundaries (functions, classes, methods), keeps the index fresh via a background daemon, and gives AI agents a high-signal memory they can query instead of reading raw files.
|
|
49
|
+
|
|
50
|
+
This reduces token burn and hallucinations compared to pure agentic search or flat embedding RAG.
|
|
51
|
+
|
|
52
|
+
## Why lybrary?
|
|
53
|
+
|
|
54
|
+
Current IDE agents either:
|
|
55
|
+
|
|
56
|
+
- Re-explore the repo with grep/read every session (expensive, amnesiac), or
|
|
57
|
+
- Use flat semantic search that doesn't understand code structure.
|
|
58
|
+
|
|
59
|
+
`lybrary` sits in the middle:
|
|
60
|
+
|
|
61
|
+
- **AST-aware chunks** (cAST-style) — never splits a function in half
|
|
62
|
+
- **Persistent daemon** that auto-updates on every file change
|
|
63
|
+
- **Hybrid retrieval** (vector search + token-budget packing)
|
|
64
|
+
- **Agent-first design** — agents query memory instead of walking the tree
|
|
65
|
+
- **MCP server** — works natively with Kiro, Cursor, Claude Desktop, Windsurf, and any MCP-compatible IDE
|
|
66
|
+
- **Fully local** — no cloud, no API keys, embeddings run on your machine
|
|
67
|
+
|
|
68
|
+
## Quick start
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install lybrary
|
|
72
|
+
|
|
73
|
+
cd /path/to/your/repo
|
|
74
|
+
lybrary init
|
|
75
|
+
lybrary start # builds index + starts background daemon
|
|
76
|
+
lybrary query "authentication flow"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
After `lybrary start`, the daemon keeps running even if you close the terminal. File changes are picked up automatically and only the affected chunks are re-indexed.
|
|
80
|
+
|
|
81
|
+
## MCP integration (AI IDEs)
|
|
82
|
+
|
|
83
|
+
Add this to your MCP config (works with Kiro, Cursor, Claude Desktop, Windsurf):
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"mcpServers": {
|
|
88
|
+
"lybrary": {
|
|
89
|
+
"command": "lybrary",
|
|
90
|
+
"args": ["mcp"]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The agent then has three tools available:
|
|
97
|
+
|
|
98
|
+
| Tool | What it does |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `memory_query` | Semantic search — returns ranked code chunks with full source, file path, and line numbers |
|
|
101
|
+
| `memory_status` | Reports daemon state, chunk count, and tracked files |
|
|
102
|
+
| `memory_update` | Triggers incremental or full re-index, optionally scoped to specific files |
|
|
103
|
+
|
|
104
|
+
**Agents should call `memory_query` before reading any files.** This replaces multi-file reads with a single targeted query, cutting token usage by 80–90% on large codebases.
|
|
105
|
+
|
|
106
|
+
## CLI
|
|
107
|
+
|
|
108
|
+
| Command | Description |
|
|
109
|
+
|--------------------|--------------------------------------------------|
|
|
110
|
+
| `lybrary init` | Create `.lybrary/` and default config |
|
|
111
|
+
| `lybrary start` | Index (if needed) + start persistent daemon |
|
|
112
|
+
| `lybrary stop` | Stop the daemon |
|
|
113
|
+
| `lybrary status` | Show running state, chunk count, tracked files |
|
|
114
|
+
| `lybrary index` | Force (re)index |
|
|
115
|
+
| `lybrary query` | Semantic search over the memory |
|
|
116
|
+
| `lybrary logs` | View / follow daemon log |
|
|
117
|
+
| `lybrary mcp` | Start MCP server (stdio transport) |
|
|
118
|
+
|
|
119
|
+
## How chunking works
|
|
120
|
+
|
|
121
|
+
1. Detect language from file extension
|
|
122
|
+
2. Parse with **tree-sitter**
|
|
123
|
+
3. Extract definition nodes (functions, classes, methods, interfaces, impls…)
|
|
124
|
+
4. Container nodes (classes, impls) emit as a chunk **and** recurse so nested methods get their own chunks
|
|
125
|
+
5. Attach rich metadata + a context header used for embedding
|
|
126
|
+
6. Fall back to careful line-based splitting only when a grammar is missing
|
|
127
|
+
|
|
128
|
+
Supported languages: Python, JavaScript, TypeScript, TSX, Go, Rust, Java, C, C++.
|
|
129
|
+
|
|
130
|
+
## Architecture (v0.1)
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
.lybrary/
|
|
134
|
+
├── config.toml
|
|
135
|
+
├── index.db # SQLite: chunks + vectors (numpy float32 blobs)
|
|
136
|
+
├── file_hashes.json # content-hash map for incremental updates
|
|
137
|
+
├── daemon.pid
|
|
138
|
+
└── daemon.log
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- **Indexer**: tree-sitter → AST chunks → local embeddings (fastembed / all-MiniLM-L6-v2, ONNX Runtime)
|
|
142
|
+
- **Store**: SQLite + numpy (cosine similarity via batched dot product)
|
|
143
|
+
- **Daemon**: watchdog file watcher + debounce + incremental re-chunk/embed
|
|
144
|
+
- **Query**: vector search + token-budget packing
|
|
145
|
+
- **MCP**: FastMCP server over stdio
|
|
146
|
+
|
|
147
|
+
## Roadmap
|
|
148
|
+
|
|
149
|
+
- [x] AST chunker (multi-language, cAST-style)
|
|
150
|
+
- [x] Incremental indexing via content hashes
|
|
151
|
+
- [x] Background daemon + file watcher (Windows + Unix)
|
|
152
|
+
- [x] CLI (`init/start/stop/status/index/query/logs/mcp`)
|
|
153
|
+
- [x] MCP server (`memory_query`, `memory_status`, `memory_update`)
|
|
154
|
+
- [ ] Call/import graph + expansion
|
|
155
|
+
- [ ] Hierarchical file/package summaries
|
|
156
|
+
- [ ] Cross-session decision memory
|
|
157
|
+
- [ ] systemd/launchd user service helper
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT
|
lybrary-0.1.0/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# lybrary
|
|
2
|
+
|
|
3
|
+
**Living structure-aware code memory for AI coding agents.**
|
|
4
|
+
|
|
5
|
+
`lybrary` indexes your repository with real AST boundaries (functions, classes, methods), keeps the index fresh via a background daemon, and gives AI agents a high-signal memory they can query instead of reading raw files.
|
|
6
|
+
|
|
7
|
+
This reduces token burn and hallucinations compared to pure agentic search or flat embedding RAG.
|
|
8
|
+
|
|
9
|
+
## Why lybrary?
|
|
10
|
+
|
|
11
|
+
Current IDE agents either:
|
|
12
|
+
|
|
13
|
+
- Re-explore the repo with grep/read every session (expensive, amnesiac), or
|
|
14
|
+
- Use flat semantic search that doesn't understand code structure.
|
|
15
|
+
|
|
16
|
+
`lybrary` sits in the middle:
|
|
17
|
+
|
|
18
|
+
- **AST-aware chunks** (cAST-style) — never splits a function in half
|
|
19
|
+
- **Persistent daemon** that auto-updates on every file change
|
|
20
|
+
- **Hybrid retrieval** (vector search + token-budget packing)
|
|
21
|
+
- **Agent-first design** — agents query memory instead of walking the tree
|
|
22
|
+
- **MCP server** — works natively with Kiro, Cursor, Claude Desktop, Windsurf, and any MCP-compatible IDE
|
|
23
|
+
- **Fully local** — no cloud, no API keys, embeddings run on your machine
|
|
24
|
+
|
|
25
|
+
## Quick start
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install lybrary
|
|
29
|
+
|
|
30
|
+
cd /path/to/your/repo
|
|
31
|
+
lybrary init
|
|
32
|
+
lybrary start # builds index + starts background daemon
|
|
33
|
+
lybrary query "authentication flow"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
After `lybrary start`, the daemon keeps running even if you close the terminal. File changes are picked up automatically and only the affected chunks are re-indexed.
|
|
37
|
+
|
|
38
|
+
## MCP integration (AI IDEs)
|
|
39
|
+
|
|
40
|
+
Add this to your MCP config (works with Kiro, Cursor, Claude Desktop, Windsurf):
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"lybrary": {
|
|
46
|
+
"command": "lybrary",
|
|
47
|
+
"args": ["mcp"]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The agent then has three tools available:
|
|
54
|
+
|
|
55
|
+
| Tool | What it does |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `memory_query` | Semantic search — returns ranked code chunks with full source, file path, and line numbers |
|
|
58
|
+
| `memory_status` | Reports daemon state, chunk count, and tracked files |
|
|
59
|
+
| `memory_update` | Triggers incremental or full re-index, optionally scoped to specific files |
|
|
60
|
+
|
|
61
|
+
**Agents should call `memory_query` before reading any files.** This replaces multi-file reads with a single targeted query, cutting token usage by 80–90% on large codebases.
|
|
62
|
+
|
|
63
|
+
## CLI
|
|
64
|
+
|
|
65
|
+
| Command | Description |
|
|
66
|
+
|--------------------|--------------------------------------------------|
|
|
67
|
+
| `lybrary init` | Create `.lybrary/` and default config |
|
|
68
|
+
| `lybrary start` | Index (if needed) + start persistent daemon |
|
|
69
|
+
| `lybrary stop` | Stop the daemon |
|
|
70
|
+
| `lybrary status` | Show running state, chunk count, tracked files |
|
|
71
|
+
| `lybrary index` | Force (re)index |
|
|
72
|
+
| `lybrary query` | Semantic search over the memory |
|
|
73
|
+
| `lybrary logs` | View / follow daemon log |
|
|
74
|
+
| `lybrary mcp` | Start MCP server (stdio transport) |
|
|
75
|
+
|
|
76
|
+
## How chunking works
|
|
77
|
+
|
|
78
|
+
1. Detect language from file extension
|
|
79
|
+
2. Parse with **tree-sitter**
|
|
80
|
+
3. Extract definition nodes (functions, classes, methods, interfaces, impls…)
|
|
81
|
+
4. Container nodes (classes, impls) emit as a chunk **and** recurse so nested methods get their own chunks
|
|
82
|
+
5. Attach rich metadata + a context header used for embedding
|
|
83
|
+
6. Fall back to careful line-based splitting only when a grammar is missing
|
|
84
|
+
|
|
85
|
+
Supported languages: Python, JavaScript, TypeScript, TSX, Go, Rust, Java, C, C++.
|
|
86
|
+
|
|
87
|
+
## Architecture (v0.1)
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
.lybrary/
|
|
91
|
+
├── config.toml
|
|
92
|
+
├── index.db # SQLite: chunks + vectors (numpy float32 blobs)
|
|
93
|
+
├── file_hashes.json # content-hash map for incremental updates
|
|
94
|
+
├── daemon.pid
|
|
95
|
+
└── daemon.log
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
- **Indexer**: tree-sitter → AST chunks → local embeddings (fastembed / all-MiniLM-L6-v2, ONNX Runtime)
|
|
99
|
+
- **Store**: SQLite + numpy (cosine similarity via batched dot product)
|
|
100
|
+
- **Daemon**: watchdog file watcher + debounce + incremental re-chunk/embed
|
|
101
|
+
- **Query**: vector search + token-budget packing
|
|
102
|
+
- **MCP**: FastMCP server over stdio
|
|
103
|
+
|
|
104
|
+
## Roadmap
|
|
105
|
+
|
|
106
|
+
- [x] AST chunker (multi-language, cAST-style)
|
|
107
|
+
- [x] Incremental indexing via content hashes
|
|
108
|
+
- [x] Background daemon + file watcher (Windows + Unix)
|
|
109
|
+
- [x] CLI (`init/start/stop/status/index/query/logs/mcp`)
|
|
110
|
+
- [x] MCP server (`memory_query`, `memory_status`, `memory_update`)
|
|
111
|
+
- [ ] Call/import graph + expansion
|
|
112
|
+
- [ ] Hierarchical file/package summaries
|
|
113
|
+
- [ ] Cross-session decision memory
|
|
114
|
+
- [ ] systemd/launchd user service helper
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
MIT
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Entry point for the Windows background daemon subprocess.
|
|
2
|
+
|
|
3
|
+
Invoked as: python -m lybrary._daemon_worker <root>
|
|
4
|
+
|
|
5
|
+
Writes its own PID to the pid file (overwriting the one set by the parent
|
|
6
|
+
just before spawn, in case they differ), then starts the file watcher.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main() -> None:
|
|
17
|
+
if len(sys.argv) < 2:
|
|
18
|
+
print("Usage: python -m lybrary._daemon_worker <repo_root>", file=sys.stderr)
|
|
19
|
+
sys.exit(1)
|
|
20
|
+
|
|
21
|
+
root = Path(sys.argv[1]).resolve()
|
|
22
|
+
|
|
23
|
+
from lybrary.config import LybraryConfig, get_lybrary_dir
|
|
24
|
+
|
|
25
|
+
pid_file = get_lybrary_dir(root) / "daemon.pid"
|
|
26
|
+
# Overwrite with our actual PID (the parent wrote its own PID first).
|
|
27
|
+
pid_file.write_text(str(os.getpid()))
|
|
28
|
+
|
|
29
|
+
config = LybraryConfig.load(root)
|
|
30
|
+
|
|
31
|
+
print(f"lybrary daemon watching {root}", flush=True)
|
|
32
|
+
|
|
33
|
+
from lybrary.watcher import start_watching
|
|
34
|
+
|
|
35
|
+
start_watching(root, config)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
main()
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""lybrary CLI — living code memory for AI agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
|
|
13
|
+
from lybrary import __version__
|
|
14
|
+
from lybrary.config import LybraryConfig, find_repo_root, get_lybrary_dir
|
|
15
|
+
|
|
16
|
+
app = typer.Typer(
|
|
17
|
+
name="lybrary",
|
|
18
|
+
help="Living structure-aware code memory for AI coding agents.",
|
|
19
|
+
no_args_is_help=True,
|
|
20
|
+
rich_markup_mode="rich",
|
|
21
|
+
)
|
|
22
|
+
console = Console()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _root(path: Optional[Path] = None) -> Path:
|
|
26
|
+
return find_repo_root(path)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.command()
|
|
30
|
+
def init(
|
|
31
|
+
path: Optional[Path] = typer.Argument(None, help="Repository path (default: cwd)"),
|
|
32
|
+
) -> None:
|
|
33
|
+
"""Initialize lybrary in a repository."""
|
|
34
|
+
root = _root(path)
|
|
35
|
+
ly_dir = get_lybrary_dir(root)
|
|
36
|
+
ly_dir.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
|
|
38
|
+
config = LybraryConfig()
|
|
39
|
+
config.save(root)
|
|
40
|
+
|
|
41
|
+
# Create a basic ignore file if missing
|
|
42
|
+
ignore = ly_dir / "ignore"
|
|
43
|
+
if not ignore.exists():
|
|
44
|
+
ignore.write_text(
|
|
45
|
+
"\n".join(
|
|
46
|
+
[
|
|
47
|
+
"# Extra ignore patterns (gitignore syntax)",
|
|
48
|
+
"*.min.js",
|
|
49
|
+
"*.map",
|
|
50
|
+
"dist/",
|
|
51
|
+
"build/",
|
|
52
|
+
"",
|
|
53
|
+
]
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
console.print(
|
|
58
|
+
Panel.fit(
|
|
59
|
+
f"[bold green]lybrary initialized[/bold green]\n\n"
|
|
60
|
+
f"Root: [cyan]{root}[/cyan]\n"
|
|
61
|
+
f"Data: [cyan]{ly_dir}[/cyan]\n\n"
|
|
62
|
+
f"Next: [bold]lybrary start[/bold]",
|
|
63
|
+
title="lybrary",
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@app.command()
|
|
69
|
+
def start(
|
|
70
|
+
path: Optional[Path] = typer.Argument(None, help="Repository path"),
|
|
71
|
+
foreground: bool = typer.Option(False, "--foreground", "-f", help="Run in foreground"),
|
|
72
|
+
) -> None:
|
|
73
|
+
"""Start the lybrary daemon (indexes if needed, then watches for changes)."""
|
|
74
|
+
root = _root(path)
|
|
75
|
+
ly_dir = get_lybrary_dir(root)
|
|
76
|
+
|
|
77
|
+
if not ly_dir.exists():
|
|
78
|
+
console.print("[yellow]No .lybrary/ found. Running init first...[/yellow]")
|
|
79
|
+
init(path)
|
|
80
|
+
|
|
81
|
+
from lybrary.daemon import start_daemon
|
|
82
|
+
|
|
83
|
+
start_daemon(root, foreground=foreground)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@app.command()
|
|
87
|
+
def stop(
|
|
88
|
+
path: Optional[Path] = typer.Argument(None, help="Repository path"),
|
|
89
|
+
) -> None:
|
|
90
|
+
"""Stop the running lybrary daemon."""
|
|
91
|
+
root = _root(path)
|
|
92
|
+
from lybrary.daemon import stop_daemon
|
|
93
|
+
|
|
94
|
+
stop_daemon(root)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@app.command()
|
|
98
|
+
def status(
|
|
99
|
+
path: Optional[Path] = typer.Argument(None, help="Repository path"),
|
|
100
|
+
) -> None:
|
|
101
|
+
"""Show daemon and index status."""
|
|
102
|
+
root = _root(path)
|
|
103
|
+
from lybrary.daemon import daemon_status
|
|
104
|
+
|
|
105
|
+
info = daemon_status(root)
|
|
106
|
+
|
|
107
|
+
table = Table(title="lybrary status", show_header=False)
|
|
108
|
+
table.add_column("Key", style="bold")
|
|
109
|
+
table.add_column("Value")
|
|
110
|
+
|
|
111
|
+
for k, v in info.items():
|
|
112
|
+
table.add_row(k, str(v))
|
|
113
|
+
|
|
114
|
+
console.print(table)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@app.command()
|
|
118
|
+
def index(
|
|
119
|
+
path: Optional[Path] = typer.Argument(None, help="Repository path"),
|
|
120
|
+
full: bool = typer.Option(False, "--full", help="Force full re-index"),
|
|
121
|
+
) -> None:
|
|
122
|
+
"""Index (or re-index) the repository."""
|
|
123
|
+
root = _root(path)
|
|
124
|
+
ly_dir = get_lybrary_dir(root)
|
|
125
|
+
if not ly_dir.exists():
|
|
126
|
+
init(path)
|
|
127
|
+
|
|
128
|
+
from lybrary.indexer.store import IndexStore
|
|
129
|
+
from lybrary.indexer.walker import index_repository
|
|
130
|
+
|
|
131
|
+
config = LybraryConfig.load(root)
|
|
132
|
+
store = IndexStore(root)
|
|
133
|
+
|
|
134
|
+
console.print(f"[bold]Indexing[/bold] [cyan]{root}[/cyan] ...")
|
|
135
|
+
stats = index_repository(root, store, config, full=full)
|
|
136
|
+
|
|
137
|
+
console.print(
|
|
138
|
+
Panel.fit(
|
|
139
|
+
f"[green]Index complete[/green]\n\n"
|
|
140
|
+
f"Files: {stats.get('files', 0)}\n"
|
|
141
|
+
f"Chunks: {stats.get('chunks', 0)}\n"
|
|
142
|
+
f"Added/updated: {stats.get('updated', 0)}",
|
|
143
|
+
title="lybrary index",
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@app.command()
|
|
149
|
+
def query(
|
|
150
|
+
q: str = typer.Argument(..., help="Search query"),
|
|
151
|
+
path: Optional[Path] = typer.Option(None, "--path", "-p", help="Repository path"),
|
|
152
|
+
limit: int = typer.Option(8, "--limit", "-n", help="Max results"),
|
|
153
|
+
max_tokens: int = typer.Option(6000, "--max-tokens", help="Token budget for context"),
|
|
154
|
+
) -> None:
|
|
155
|
+
"""Search the code memory."""
|
|
156
|
+
root = _root(path)
|
|
157
|
+
from lybrary.query import search
|
|
158
|
+
|
|
159
|
+
results = search(root, q, limit=limit, max_tokens=max_tokens)
|
|
160
|
+
|
|
161
|
+
if not results:
|
|
162
|
+
console.print("[yellow]No results.[/yellow]")
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
for i, r in enumerate(results, 1):
|
|
166
|
+
console.print(
|
|
167
|
+
Panel(
|
|
168
|
+
f"[dim]{r.get('path')} :: {r.get('symbol', '')}[/dim]\n\n"
|
|
169
|
+
f"{r.get('content', '')[:800]}{'...' if len(r.get('content', '')) > 800 else ''}",
|
|
170
|
+
title=f"[{i}] {r.get('score', 0):.3f}",
|
|
171
|
+
border_style="blue",
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@app.command()
|
|
177
|
+
def logs(
|
|
178
|
+
path: Optional[Path] = typer.Argument(None, help="Repository path"),
|
|
179
|
+
follow: bool = typer.Option(False, "--follow", "-f", help="Follow log output"),
|
|
180
|
+
) -> None:
|
|
181
|
+
"""Show daemon logs."""
|
|
182
|
+
root = _root(path)
|
|
183
|
+
log_file = get_lybrary_dir(root) / "daemon.log"
|
|
184
|
+
if not log_file.exists():
|
|
185
|
+
console.print("[yellow]No log file yet.[/yellow]")
|
|
186
|
+
raise typer.Exit(1)
|
|
187
|
+
|
|
188
|
+
if follow:
|
|
189
|
+
import time
|
|
190
|
+
|
|
191
|
+
with open(log_file) as f:
|
|
192
|
+
f.seek(0, 2)
|
|
193
|
+
while True:
|
|
194
|
+
line = f.readline()
|
|
195
|
+
if line:
|
|
196
|
+
console.print(line.rstrip())
|
|
197
|
+
else:
|
|
198
|
+
time.sleep(0.3)
|
|
199
|
+
else:
|
|
200
|
+
console.print(log_file.read_text()[-8000:])
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@app.command()
|
|
204
|
+
def mcp(
|
|
205
|
+
repo_root: Optional[Path] = typer.Option(
|
|
206
|
+
None, "--root", "-r", help="Repository root (default: auto-detect)"
|
|
207
|
+
),
|
|
208
|
+
) -> None:
|
|
209
|
+
"""Start the MCP server (stdio transport) for AI IDE integration.
|
|
210
|
+
|
|
211
|
+
Configure your IDE / MCP host to run: lybrary mcp
|
|
212
|
+
|
|
213
|
+
Kiro / Cursor / Claude Desktop example config:
|
|
214
|
+
{
|
|
215
|
+
"mcpServers": {
|
|
216
|
+
"lybrary": {
|
|
217
|
+
"command": "lybrary",
|
|
218
|
+
"args": ["mcp"]
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
"""
|
|
223
|
+
try:
|
|
224
|
+
from lybrary.mcp.server import mcp as _mcp_server # noqa: F401
|
|
225
|
+
except ImportError:
|
|
226
|
+
console.print(
|
|
227
|
+
"[red]MCP dependencies not installed.[/red]\n"
|
|
228
|
+
"Run: [bold]pip install 'lybrary[mcp]'[/bold]"
|
|
229
|
+
)
|
|
230
|
+
raise typer.Exit(1)
|
|
231
|
+
|
|
232
|
+
if repo_root:
|
|
233
|
+
import os
|
|
234
|
+
os.chdir(repo_root)
|
|
235
|
+
|
|
236
|
+
from lybrary.mcp.server import run
|
|
237
|
+
run()
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@app.command()
|
|
241
|
+
def version() -> None:
|
|
242
|
+
"""Show version."""
|
|
243
|
+
console.print(f"lybrary {__version__}")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
if __name__ == "__main__":
|
|
247
|
+
app()
|