libcontext 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.
libcontext/renderer.py ADDED
@@ -0,0 +1,366 @@
1
+ """Markdown renderer — converts PackageInfo into LLM-optimized context.
2
+
3
+ Produces a structured Markdown document designed to give GitHub Copilot
4
+ (or any LLM) the best possible understanding of a library's public API.
5
+
6
+ The output format prioritises:
7
+ - Complete function/method signatures with type hints
8
+ - Concise docstrings (first paragraph only by default)
9
+ - Clear module hierarchy for correct import generation
10
+ - Compact representation to maximise useful content within context limits
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from .inspector import is_public_member
16
+ from .models import (
17
+ ClassInfo,
18
+ FunctionInfo,
19
+ ModuleInfo,
20
+ PackageInfo,
21
+ ParameterInfo,
22
+ VariableInfo,
23
+ )
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Parameter & signature formatting
27
+ # ---------------------------------------------------------------------------
28
+
29
+
30
+ def _format_param(param: ParameterInfo) -> str:
31
+ """Format a single parameter for display in a signature."""
32
+ parts: list[str] = []
33
+
34
+ parts.append(param.name)
35
+
36
+ if param.annotation:
37
+ parts.append(f": {param.annotation}")
38
+
39
+ if param.default is not None:
40
+ parts.append(f" = {param.default}")
41
+
42
+ return "".join(parts)
43
+
44
+
45
+ def _format_signature(func: FunctionInfo, *, compact: bool = False) -> str:
46
+ """Build a human-readable function signature string.
47
+
48
+ Args:
49
+ func: The function to format.
50
+ compact: If True, omit ``self``/``cls`` and decorators.
51
+ """
52
+ # Filter implicit params
53
+ params = func.parameters
54
+ if compact:
55
+ params = [p for p in params if p.name not in ("self", "cls")]
56
+
57
+ # Detect positional-only / keyword-only boundaries
58
+ formatted_parts: list[str] = []
59
+ prev_kind: str | None = None
60
+
61
+ for param in params:
62
+ # Insert / separator after positional-only params
63
+ if prev_kind == "POSITIONAL_ONLY" and param.kind != "POSITIONAL_ONLY":
64
+ formatted_parts.append("/")
65
+
66
+ # Insert * separator before keyword-only (when no *args)
67
+ if param.kind == "KEYWORD_ONLY" and prev_kind not in (
68
+ "VAR_POSITIONAL",
69
+ "KEYWORD_ONLY",
70
+ ):
71
+ formatted_parts.append("*")
72
+
73
+ formatted_parts.append(_format_param(param))
74
+ prev_kind = param.kind
75
+
76
+ # Trailing / if ALL params are positional-only
77
+ if params and all(p.kind == "POSITIONAL_ONLY" for p in params):
78
+ formatted_parts.append("/")
79
+
80
+ params_str = ", ".join(formatted_parts)
81
+ prefix = "async def" if func.is_async else "def"
82
+ ret = f" -> {func.return_annotation}" if func.return_annotation else ""
83
+
84
+ return f"{prefix} {func.name}({params_str}){ret}"
85
+
86
+
87
+ def _first_paragraph(text: str | None) -> str | None:
88
+ """Extract the first paragraph of a docstring."""
89
+ if not text:
90
+ return None
91
+ lines: list[str] = []
92
+ for line in text.strip().splitlines():
93
+ stripped = line.strip()
94
+ if not stripped and lines:
95
+ break
96
+ if stripped:
97
+ lines.append(stripped)
98
+ return " ".join(lines) if lines else None
99
+
100
+
101
+ # ---------------------------------------------------------------------------
102
+ # Component renderers
103
+ # ---------------------------------------------------------------------------
104
+
105
+
106
+ def _render_variable(var: VariableInfo) -> str:
107
+ """Render a variable/constant as a Markdown list item."""
108
+ parts = [f"`{var.name}"]
109
+ if var.annotation:
110
+ parts.append(f": {var.annotation}")
111
+ if var.value is not None:
112
+ # Truncate very long values
113
+ val = var.value if len(var.value) <= 80 else var.value[:77] + "..."
114
+ parts.append(f" = {val}")
115
+ parts.append("`")
116
+ return f"- {''.join(parts)}"
117
+
118
+
119
+ def _render_function(func: FunctionInfo, *, heading: str = "-") -> str:
120
+ """Render a function as a Markdown block."""
121
+ lines: list[str] = []
122
+
123
+ # Decorators (only non-trivial ones)
124
+ notable_decorators = [
125
+ d
126
+ for d in func.decorators
127
+ if d not in ("property", "classmethod", "staticmethod")
128
+ ]
129
+ for dec in notable_decorators:
130
+ lines.append(f"{heading} `@{dec}`")
131
+
132
+ sig = _format_signature(func, compact=True)
133
+ lines.append(f"{heading} `{sig}`")
134
+
135
+ doc = _first_paragraph(func.docstring)
136
+ if doc:
137
+ indent = " " if heading == "-" else ""
138
+ lines.append(f"{indent}{doc}")
139
+
140
+ return "\n".join(lines)
141
+
142
+
143
+ def _render_class(cls: ClassInfo) -> str:
144
+ """Render a class as a Markdown section."""
145
+ lines: list[str] = []
146
+
147
+ # Class header
148
+ bases_str = f"({', '.join(cls.bases)})" if cls.bases else ""
149
+ dec_str = ""
150
+ for dec in cls.decorators:
151
+ dec_str += f"`@{dec}` "
152
+ lines.append(f"#### {dec_str}`class {cls.name}{bases_str}`")
153
+
154
+ # Docstring
155
+ doc = _first_paragraph(cls.docstring)
156
+ if doc:
157
+ lines.append("")
158
+ lines.append(doc)
159
+
160
+ # Class variables (public only)
161
+ public_vars = [v for v in cls.class_variables if is_public_member(v.name)]
162
+ if public_vars:
163
+ lines.append("")
164
+ lines.append("**Attributes:**")
165
+ for var in public_vars:
166
+ lines.append(_render_variable(var))
167
+
168
+ # Methods — include public + useful dunders
169
+ visible_methods = [
170
+ m for m in cls.methods if is_public_member(m.name, is_method=True)
171
+ ]
172
+ if visible_methods:
173
+ lines.append("")
174
+ lines.append("**Methods:**")
175
+ for method in visible_methods:
176
+ lines.append(_render_function(method))
177
+
178
+ # Inner classes
179
+ public_inner = [c for c in cls.inner_classes if is_public_member(c.name)]
180
+ for inner in public_inner:
181
+ lines.append("")
182
+ lines.append(_render_class(inner))
183
+
184
+ return "\n".join(lines)
185
+
186
+
187
+ def _render_module(module: ModuleInfo) -> str:
188
+ """Render a module as a Markdown section."""
189
+ lines: list[str] = []
190
+
191
+ lines.append(f"### `{module.name}`")
192
+
193
+ doc = _first_paragraph(module.docstring)
194
+ if doc:
195
+ lines.append("")
196
+ lines.append(doc)
197
+
198
+ # Determine public API boundary
199
+ exports = set(module.all_exports) if module.all_exports is not None else None
200
+
201
+ def _is_public(name: str) -> bool:
202
+ if exports is not None:
203
+ return name in exports
204
+ return is_public_member(name)
205
+
206
+ # Classes
207
+ public_classes = [c for c in module.classes if _is_public(c.name)]
208
+ for cls in public_classes:
209
+ lines.append("")
210
+ lines.append(_render_class(cls))
211
+
212
+ # Functions
213
+ public_functions = [f for f in module.functions if _is_public(f.name)]
214
+ if public_functions:
215
+ lines.append("")
216
+ lines.append("**Functions:**")
217
+ for func in public_functions:
218
+ lines.append("")
219
+ lines.append(_render_function(func))
220
+
221
+ # Constants (UPPER_CASE variables)
222
+ public_constants = [
223
+ v for v in module.variables if _is_public(v.name) and v.name.isupper()
224
+ ]
225
+ if public_constants:
226
+ lines.append("")
227
+ lines.append("**Constants:**")
228
+ for var in public_constants:
229
+ lines.append(_render_variable(var))
230
+
231
+ # Module-level variables (non-constant public variables)
232
+ public_vars = [
233
+ v for v in module.variables if _is_public(v.name) and not v.name.isupper()
234
+ ]
235
+ if public_vars:
236
+ lines.append("")
237
+ lines.append("**Module Variables:**")
238
+ for var in public_vars:
239
+ lines.append(_render_variable(var))
240
+
241
+ return "\n".join(lines)
242
+
243
+
244
+ # ---------------------------------------------------------------------------
245
+ # Public API
246
+ # ---------------------------------------------------------------------------
247
+
248
+ # Markers used to delimit auto-generated sections in existing files
249
+ BEGIN_MARKER = "<!-- BEGIN LIBCONTEXT: {name} -->"
250
+ END_MARKER = "<!-- END LIBCONTEXT: {name} -->"
251
+
252
+
253
+ def render_package(
254
+ package: PackageInfo,
255
+ *,
256
+ include_readme: bool = True,
257
+ max_readme_lines: int = 100,
258
+ extra_context: str | None = None,
259
+ ) -> str:
260
+ """Render a :class:`PackageInfo` as Markdown optimised for LLM context.
261
+
262
+ Args:
263
+ package: The collected package information.
264
+ include_readme: Whether to include the README overview section.
265
+ max_readme_lines: Truncate the README after this many lines.
266
+ extra_context: Additional free-form context to append (e.g. from
267
+ ``[tool.libcontext] extra_context``).
268
+
269
+ Returns:
270
+ A complete Markdown string ready to be written to a file or stdout.
271
+ """
272
+ lines: list[str] = []
273
+
274
+ # --- Header --------------------------------------------------------
275
+ version = f" v{package.version}" if package.version else ""
276
+ lines.append(f"# {package.name}{version} — API Reference")
277
+ lines.append("")
278
+
279
+ if package.summary:
280
+ lines.append(f"> {package.summary}")
281
+ lines.append("")
282
+
283
+ # --- README overview -----------------------------------------------
284
+ if include_readme and package.readme:
285
+ lines.append("## Overview")
286
+ lines.append("")
287
+ readme_lines = package.readme.strip().splitlines()
288
+ if len(readme_lines) > max_readme_lines:
289
+ readme_lines = readme_lines[:max_readme_lines]
290
+ readme_lines.append("")
291
+ readme_lines.append("*(README truncated)*")
292
+ lines.extend(readme_lines)
293
+ lines.append("")
294
+
295
+ # --- Extra context -------------------------------------------------
296
+ if extra_context:
297
+ lines.append("## Notes")
298
+ lines.append("")
299
+ lines.append(extra_context.strip())
300
+ lines.append("")
301
+
302
+ # --- API Reference -------------------------------------------------
303
+ modules = package.non_empty_modules
304
+ if modules:
305
+ lines.append("## API Reference")
306
+ lines.append("")
307
+
308
+ for module in modules:
309
+ lines.append(_render_module(module))
310
+ lines.append("")
311
+ lines.append("---")
312
+ lines.append("")
313
+
314
+ return "\n".join(lines)
315
+
316
+
317
+ def inject_into_file(
318
+ content: str,
319
+ package_name: str,
320
+ existing: str | None = None,
321
+ ) -> str:
322
+ """Inject generated context into an existing file using markers.
323
+
324
+ If the file already contains a ``<!-- BEGIN LIBCONTEXT: {name} -->`` /
325
+ ``<!-- END LIBCONTEXT: {name} -->`` block for this package, that block
326
+ is replaced. Otherwise, the content is appended at the end.
327
+
328
+ Args:
329
+ content: The generated Markdown context.
330
+ package_name: Package name used in the markers.
331
+ existing: Current file contents (if any).
332
+
333
+ Returns:
334
+ The updated file contents.
335
+ """
336
+ begin = BEGIN_MARKER.format(name=package_name)
337
+ end = END_MARKER.format(name=package_name)
338
+ block = f"{begin}\n{content}\n{end}"
339
+
340
+ if existing is None:
341
+ return block
342
+
343
+ begin_idx = existing.find(begin)
344
+ end_idx = existing.find(end)
345
+
346
+ if begin_idx != -1 and end_idx != -1 and begin_idx < end_idx:
347
+ # Well-formed existing section — replace it
348
+ before = existing[:begin_idx]
349
+ after = existing[end_idx + len(end) :]
350
+ return f"{before}{block}{after}"
351
+
352
+ if begin_idx != -1 or end_idx != -1:
353
+ # Malformed markers (only one present, or reversed order).
354
+ # Remove any stale markers before appending the clean block.
355
+ cleaned = existing
356
+ if begin_idx != -1:
357
+ cleaned = cleaned[:begin_idx] + cleaned[begin_idx + len(begin) :]
358
+ # Recalculate end_idx after potential removal
359
+ end_idx_clean = cleaned.find(end)
360
+ if end_idx_clean != -1:
361
+ cleaned = cleaned[:end_idx_clean] + cleaned[end_idx_clean + len(end) :]
362
+ existing = cleaned
363
+
364
+ # Append
365
+ separator = "\n\n" if existing.strip() else ""
366
+ return f"{existing.rstrip()}{separator}{block}\n"
@@ -0,0 +1,282 @@
1
+ Metadata-Version: 2.4
2
+ Name: libcontext
3
+ Version: 0.1.0
4
+ Summary: Generate optimized LLM context from Python library APIs for GitHub Copilot
5
+ Project-URL: Homepage, https://github.com/Syclaw/libcontext
6
+ Project-URL: Repository, https://github.com/Syclaw/libcontext
7
+ Project-URL: Issues, https://github.com/Syclaw/libcontext/issues
8
+ Author: Jonathan VARELA
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ast,context,copilot,documentation,introspection
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Documentation
22
+ Classifier: Topic :: Software Development :: Libraries
23
+ Requires-Python: >=3.9
24
+ Requires-Dist: click>=8.0
25
+ Requires-Dist: tomli>=1.0; python_version < '3.11'
26
+ Provides-Extra: dev
27
+ Requires-Dist: mypy>=1.10; extra == 'dev'
28
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
29
+ Requires-Dist: pytest>=8.0; extra == 'dev'
30
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # libcontext
34
+
35
+ [![CI](https://github.com/Syclaw/libcontext/actions/workflows/ci.yml/badge.svg)](https://github.com/Syclaw/libcontext/actions/workflows/ci.yml)
36
+ [![PyPI version](https://img.shields.io/pypi/v/libcontext)](https://pypi.org/project/libcontext/)
37
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
38
+ [![Python](https://img.shields.io/pypi/pyversions/libcontext)](https://pypi.org/project/libcontext/)
39
+
40
+ > Make your AI coding assistant aware of any Python library's API — especially the ones it doesn't already know.
41
+
42
+ **libcontext** inspects any installed Python package via static AST analysis (no code execution) and generates a compact Markdown API reference. Add it to your [`.github/copilot-instructions.md`](https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions) and GitHub Copilot will automatically include it as context in **Chat, Agent, and Code Review** interactions.
43
+
44
+ ## Why This Exists
45
+
46
+ When you ask Copilot Chat how to use a library, or when Copilot Agent generates code that depends on one, the quality of the output depends entirely on what the model knows about that library's API.
47
+
48
+ For popular, well-established libraries, LLMs generally have good knowledge from training data. But for many real-world scenarios, the model is working blind:
49
+
50
+ - **Internal / private libraries** — Zero training data exists. The model has never seen the API.
51
+ - **Niche open-source packages** — Sparse or outdated training data leads to hallucinated methods and wrong signatures.
52
+ - **New versions of any library** — Training data has a cutoff. The model knows v2, you're using v3.
53
+
54
+ GitHub Copilot supports [repository custom instructions](https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions) — a `.github/copilot-instructions.md` file that is automatically included as context. According to GitHub's [support matrix](https://docs.github.com/en/copilot/reference/custom-instructions-support), this file is loaded by:
55
+
56
+ | Copilot feature | Uses custom instructions |
57
+ |---|---|
58
+ | **Copilot Chat** (VS Code, JetBrains, Visual Studio, Eclipse, Xcode, github.com) | ✅ Yes |
59
+ | **Copilot coding agent** (PR generation, agent mode) | ✅ Yes |
60
+ | **Copilot code review** | ✅ Yes |
61
+ | **Inline code completion** (autocomplete as you type) | ❌ Not currently |
62
+
63
+ libcontext bridges the knowledge gap by pre-generating a structured API reference from installed packages and placing it where Copilot can find it.
64
+
65
+ ## When libcontext makes the biggest difference
66
+
67
+ | Scenario | Impact | Why |
68
+ |---|---|---|
69
+ | **Internal / private libraries** | 🔴 Critical | Zero training data exists for proprietary code |
70
+ | **Niche open-source packages** | 🟠 High | Sparse training data → hallucinated methods and wrong signatures |
71
+ | **New versions of any library** | 🟠 High | Training data has a cutoff — the LLM knows v2, you're using v3 |
72
+ | **Popular, stable libraries** | ⚪ Low | The LLM already has good knowledge from training data |
73
+
74
+ > **If Copilot has ever suggested a function that doesn't exist** in one of your dependencies, or got the parameters wrong — libcontext can help prevent that.
75
+
76
+ ## Quick Start
77
+
78
+ ```bash
79
+ pip install libcontext
80
+
81
+ # Generate context for any installed package
82
+ libctx requests -o .github/copilot-instructions.md
83
+
84
+ # Done — Copilot Chat and Agent now know the complete requests API
85
+ # (15 modules, 44 classes, 70 functions → ~800 lines of compact reference)
86
+ ```
87
+
88
+ ## How It Works
89
+
90
+ 1. **AST parsing** — Reads source files of installed packages using Python's `ast` module. No code is ever executed, making it safe for any package.
91
+ 2. **Extraction** — Classes, functions, methods, parameters, type annotations, decorators, and docstrings are collected.
92
+ 3. **Compact rendering** — Everything is rendered into structured Markdown optimised for LLM context windows (signatures and docstrings only, no implementation code).
93
+ 4. **Marker injection** — Output is wrapped in `<!-- BEGIN/END LIBCONTEXT -->` markers, so re-running updates only its section without touching the rest of the file.
94
+
95
+ ```
96
+ installed package libcontext .github/copilot-instructions.md
97
+ (source files) ──▶ (AST analysis) ──▶ (compact API reference)
98
+
99
+
100
+ Copilot Chat, Agent &
101
+ Code Review now know
102
+ the full API
103
+ ```
104
+
105
+ ## Installation
106
+
107
+ ```bash
108
+ pip install libcontext
109
+ ```
110
+
111
+ Or with [uv](https://docs.astral.sh/uv/):
112
+
113
+ ```bash
114
+ uv add libcontext
115
+ ```
116
+
117
+ For development:
118
+
119
+ ```bash
120
+ git clone https://github.com/Syclaw/libcontext.git
121
+ cd libcontext
122
+ uv sync --all-extras # or: pip install -e ".[dev]"
123
+ ```
124
+
125
+ ## Usage
126
+
127
+ ### Command Line
128
+
129
+ ```bash
130
+ # Generate context for an installed library (stdout)
131
+ libctx requests
132
+
133
+ # Write to the Copilot instructions file
134
+ libctx requests -o .github/copilot-instructions.md
135
+
136
+ # Multiple libraries at once
137
+ libctx requests httpx pydantic -o .github/copilot-instructions.md
138
+
139
+ # Include private members
140
+ libctx mypackage --include-private
141
+
142
+ # Without the README
143
+ libctx mypackage --no-readme
144
+
145
+ # With an explicit configuration file
146
+ libctx mypackage --config path/to/pyproject.toml
147
+ ```
148
+
149
+ ### Python API
150
+
151
+ ```python
152
+ from libcontext import collect_package, render_package
153
+
154
+ # Collect the API of an installed package
155
+ pkg = collect_package("requests")
156
+
157
+ # Generate the Markdown
158
+ context = render_package(pkg)
159
+ print(context)
160
+ ```
161
+
162
+ ### Injection into an Existing File
163
+
164
+ When using `-o`, libcontext injects content between markers:
165
+
166
+ ```markdown
167
+ <!-- BEGIN LIBCONTEXT: requests -->
168
+ ... generated content ...
169
+ <!-- END LIBCONTEXT: requests -->
170
+ ```
171
+
172
+ Subsequent runs update only that section, preserving the rest of the file.
173
+
174
+ ## Configuration (Optional)
175
+
176
+ Library authors can customise what libcontext exposes from their package by adding a `[tool.libcontext]` section to their `pyproject.toml`. **The library does not need to depend on libcontext** — this is purely opt-in metadata that libcontext reads at generation time.
177
+
178
+ ```
179
+ ┌──────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐
180
+ │ libcontext │ │ Library B │ │ Your project │
181
+ │ (CLI tool) │ │ (any Python pkg) │ │ (end user) │
182
+ │ │ │ │ │ │
183
+ │ Reads │ │ Can optionally add │ │ Runs: │
184
+ │ [tool.libcontext]│◀────│ [tool.libcontext] │ │ libctx lib_b │
185
+ │ from library B │ │ to pyproject.toml │ │ │
186
+ └──────────────────┘ └──────────────────────┘ └──────────────────────┘
187
+ ```
188
+
189
+ ```toml
190
+ [tool.libcontext]
191
+ # Only include specific modules
192
+ include_modules = ["mylib.core", "mylib.models"]
193
+
194
+ # Exclude modules
195
+ exclude_modules = ["mylib._internal", "mylib.tests"]
196
+
197
+ # Include private members
198
+ include_private = false
199
+
200
+ # Free-form extra context
201
+ extra_context = """
202
+ This library uses the Repository pattern for data access.
203
+ All async operations use httpx internally.
204
+ """
205
+
206
+ # Maximum README lines
207
+ max_readme_lines = 150
208
+ ```
209
+
210
+ ## Output Example
211
+
212
+ ```markdown
213
+ # requests v2.31.0 — API Reference
214
+
215
+ > Python HTTP for Humans.
216
+
217
+ ## Overview
218
+
219
+ # Requests
220
+ Requests is a simple HTTP library for Python...
221
+
222
+ ## API Reference
223
+
224
+ ### `requests`
225
+
226
+ #### `class Session()`
227
+ A Requests session. Provides cookie persistence, connection-pooling, and configuration.
228
+
229
+ **Methods:**
230
+ - `def get(url: str, **kwargs) -> Response`
231
+ Sends a GET request.
232
+ - `def post(url: str, data: Any = None, json: Any = None, **kwargs) -> Response`
233
+ Sends a POST request.
234
+
235
+ **Functions:**
236
+
237
+ - `def get(url: str, params: dict | None = None, **kwargs) -> Response`
238
+ Sends a GET request.
239
+ - `def post(url: str, data: Any = None, **kwargs) -> Response`
240
+ Sends a POST request.
241
+ ```
242
+
243
+ ## Architecture
244
+
245
+ | Module | Description |
246
+ |---|---|
247
+ | `models.py` | Dataclasses to represent Python components |
248
+ | `inspector.py` | Static AST analysis (no code execution) |
249
+ | `collector.py` | Discovery and collection of all modules in a package |
250
+ | `config.py` | Reads `[tool.libcontext]` from pyproject.toml |
251
+ | `renderer.py` | LLM-optimised Markdown generation |
252
+ | `cli.py` | CLI entry point (`libctx`) |
253
+
254
+ ## Development
255
+
256
+ ```bash
257
+ # Install in development mode
258
+ uv sync --all-extras
259
+
260
+ # Run tests
261
+ uv run pytest
262
+
263
+ # Run tests with coverage
264
+ uv run pytest --cov=libcontext
265
+
266
+ # Lint & format
267
+ uv run ruff check src/ tests/
268
+ uv run ruff format src/ tests/
269
+
270
+ # Type checking
271
+ uv run mypy src/libcontext
272
+ ```
273
+
274
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed contribution guidelines.
275
+
276
+ ## Dependencies
277
+
278
+ See [DEPENDENCIES.md](DEPENDENCIES.md) for the full list of dependencies and their licenses.
279
+
280
+ ## License
281
+
282
+ MIT — see [LICENSE](LICENSE) for details.
@@ -0,0 +1,13 @@
1
+ libcontext/__init__.py,sha256=3VbCO4BeksaABIhiMgjz3WQNgZ-tRtXjIdQ923HU6_A,1489
2
+ libcontext/cli.py,sha256=Xov4bd5ZiYlL3DnDD_h46Z7Nt3PGJnAcjJndtuodNkk,6676
3
+ libcontext/collector.py,sha256=sVgZav9lspoKUIXVTnatYiDzebBc3svo6bu__e8qv14,9961
4
+ libcontext/config.py,sha256=E6EzxigCBQfD8dzFmOK-JK9QxTryRiCfrRvR2reBebM,5363
5
+ libcontext/inspector.py,sha256=aIQ_fAx9B0RNZKQMhet96eYMqJgiCioDAXCCLiyScDs,12511
6
+ libcontext/models.py,sha256=XL9ohheI9GN12ui9AKknKYZKzICtOTdt0lnmZvOCEC0,2644
7
+ libcontext/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ libcontext/renderer.py,sha256=XWQfH101_nffunusOekIXI8n1BWp2OZzDqel0pxGIIw,12016
9
+ libcontext-0.1.0.dist-info/METADATA,sha256=EExmcaFEIEyhL7iL9itNOLs0CiIXqQTAWeh_VsZm2Kg,10678
10
+ libcontext-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
11
+ libcontext-0.1.0.dist-info/entry_points.txt,sha256=a5nWJ2NNcQKxys77Ov84qZjFj7fsmi7fVHuQL3ulcsw,47
12
+ libcontext-0.1.0.dist-info/licenses/LICENSE,sha256=xogpsEBXhxtnSGpSzzCsA9qGXEpoZACBfEZW4clFXNc,1093
13
+ libcontext-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ libctx = libcontext.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jonathan VARELA
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.