ai-context-injector 1.0.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dark
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,680 @@
1
+ Metadata-Version: 2.4
2
+ Name: ai-context-injector
3
+ Version: 1.0.0
4
+ Summary: Lightweight Python library for tag-based context injection with anti-hallucination safeguards. Zero dependencies, 100% type-safe, <25ms performance.
5
+ Author-email: Dark <jdis03@proton.me>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/JDis03/ai-context-injector
8
+ Project-URL: Documentation, https://github.com/JDis03/ai-context-injector#readme
9
+ Project-URL: Repository, https://github.com/JDis03/ai-context-injector
10
+ Project-URL: Issues, https://github.com/JDis03/ai-context-injector/issues
11
+ Project-URL: Changelog, https://github.com/JDis03/ai-context-injector/releases
12
+ Keywords: ai,llm,context,injection,rag,memory,hallucination,openai,anthropic,claude
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.9
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7.0; extra == "dev"
31
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
32
+ Requires-Dist: mypy>=1.0; extra == "dev"
33
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
34
+ Provides-Extra: examples
35
+ Requires-Dist: openai>=1.0; extra == "examples"
36
+ Requires-Dist: anthropic>=0.18; extra == "examples"
37
+ Dynamic: license-file
38
+
39
+ # AI Context Injector
40
+
41
+ A lightweight Python library for tag-based context injection into LLM prompts with built-in anti-hallucination safeguards.
42
+
43
+ Extract relevant context from your codebase, memory systems, or custom data sources using simple tags like `@memory`, `@code`, or `@session` in user queries.
44
+
45
+ ## Key Features
46
+
47
+ - **Tag-based retrieval**: `@memory dark keyboard`, `@code UserService`, `@session last decision`
48
+ - **Anti-hallucination safeguards**: 5 critical rules + automatic citations to prevent LLM fabrication
49
+ - **Hard project isolation**: Never mix contexts from different projects without explicit opt-in
50
+ - **Plugin architecture**: Write custom providers for any data source
51
+ - **Blazing fast**: <25ms total pipeline (proven at 17.7ms in production)
52
+ - **Zero dependencies**: Core library uses only Python stdlib
53
+ - **100% type-safe**: Full type hints with py.typed marker
54
+ - **Battle-tested**: 219 tests including real-world integration scenarios
55
+
56
+ ## Installation
57
+
58
+ ```bash
59
+ pip install ai-context-injector
60
+ ```
61
+
62
+ ## Quick Start (5 lines)
63
+
64
+ ```python
65
+ from ai_context_injector import ContextInjector
66
+
67
+ injector = ContextInjector(current_project="my-app")
68
+ injector.register_provider("@memory", my_memory_provider)
69
+
70
+ context = injector.inject("@memory user authentication flow")
71
+ # Ready to inject into LLM prompt!
72
+ ```
73
+
74
+ ## Why Context Injection?
75
+
76
+ LLMs need relevant context to provide accurate answers. Instead of manually copying code snippets or documentation, use tags to automatically retrieve and format context:
77
+
78
+ **Without context injection:**
79
+ ```
80
+ User: "How does our authentication work?"
81
+ LLM: *hallucinates based on general knowledge*
82
+ ```
83
+
84
+ **With context injection:**
85
+ ```
86
+ User: "@memory authentication @code AuthService"
87
+ System: *retrieves actual decisions + code*
88
+ LLM: *answers based on YOUR actual implementation*
89
+ ```
90
+
91
+ ## Complete Example
92
+
93
+ ```python
94
+ from ai_context_injector import ContextInjector, ContextItem, IContextProvider
95
+ from datetime import datetime
96
+
97
+ # 1. Create a custom provider
98
+ class MyMemoryProvider(IContextProvider):
99
+ @property
100
+ def name(self) -> str:
101
+ return "MyMemory"
102
+
103
+ @property
104
+ def source_type(self) -> str:
105
+ return "memory"
106
+
107
+ def is_available(self) -> bool:
108
+ return True
109
+
110
+ def retrieve(self, request):
111
+ # Your retrieval logic here (database, files, API, etc.)
112
+ results = search_my_database(request.query, request.project)
113
+
114
+ return [
115
+ ContextItem(
116
+ content=result.text,
117
+ source="memory",
118
+ project=request.project,
119
+ metadata={"id": result.id},
120
+ relevance_score=result.score,
121
+ timestamp=result.created_at
122
+ )
123
+ for result in results
124
+ ]
125
+
126
+ # 2. Initialize injector
127
+ injector = ContextInjector(current_project="my-app")
128
+ injector.register_provider("@memory", MyMemoryProvider())
129
+
130
+ # 3. Inject context from user queries
131
+ user_query = "Tell me @memory what we decided about the database"
132
+
133
+ if injector.has_tags(user_query):
134
+ context = injector.inject(user_query)
135
+
136
+ # Inject into LLM prompt
137
+ llm_prompt = f"""
138
+ {context}
139
+
140
+ User Question: {injector.extract_query_only(user_query)}
141
+
142
+ Answer based ONLY on the context above.
143
+ """
144
+
145
+ response = llm.generate(llm_prompt)
146
+ ```
147
+
148
+ ## Anti-Hallucination Safeguards
149
+
150
+ Every injected context includes 5 critical rules to prevent LLM fabrication:
151
+
152
+ ```
153
+ === BEGIN CONTEXT ===
154
+
155
+ CRITICAL RULES FOR USING THIS CONTEXT:
156
+ 1. ONLY cite information that appears in the context sections below
157
+ 2. If context is from a different project, CLEARLY state which project
158
+ 3. Include source citations [memory:project:date] or [code:file:line]
159
+ 4. If unsure or context missing, say "I don't have information about this"
160
+ 5. NEVER mix information from different projects without explicit warning
161
+
162
+ Retrieved Context for: my-app
163
+ Found 2 relevant item(s)
164
+
165
+ --- Context Item 1/2 ---
166
+ Source: memory | Project: my-app | Relevance: 0.95 | Date: 2026-05-30
167
+ Citation: [memory:my-app:2026-05-30]
168
+
169
+ Decision: Use PostgreSQL with WAL mode for better performance
170
+
171
+ --- Context Item 2/2 ---
172
+ Source: code | Project: my-app | Relevance: 0.88 | Date: 2026-05-30
173
+ Citation: [code:src/db/connection.py:15-23]
174
+
175
+ def create_connection():
176
+ return psycopg2.connect(
177
+ host="localhost",
178
+ database="myapp",
179
+ options="-c wal_level=replica"
180
+ )
181
+
182
+ === END CONTEXT ===
183
+ ```
184
+
185
+ ## Tag Syntax
186
+
187
+ ### Basic Tags
188
+
189
+ ```python
190
+ "@memory user authentication" # Search memory for "user authentication"
191
+ "@code AuthService class" # Search code for "AuthService class"
192
+ "@session last decision" # Search current session
193
+ ```
194
+
195
+ ### Modifiers
196
+
197
+ ```python
198
+ "@memory:all database design" # Search across ALL projects (with warning)
199
+ ```
200
+
201
+ ### Multiple Tags
202
+
203
+ ```python
204
+ "@memory architecture and @code UserService" # Aggregates from both sources
205
+ ```
206
+
207
+ ## Project Isolation (Key Differentiator)
208
+
209
+ By default, context is **strictly isolated** to the current project:
210
+
211
+ ```python
212
+ injector = ContextInjector(current_project="frontend")
213
+
214
+ # Only retrieves from "frontend" project
215
+ context = injector.inject("@memory React components")
216
+
217
+ # To search across projects, use :all modifier (generates warning)
218
+ context = injector.inject("@memory:all authentication patterns")
219
+ ```
220
+
221
+ **Why this matters:** Prevents LLMs from mixing context across different projects, which is a major source of hallucinations and incorrect answers.
222
+
223
+ ## Advanced Usage
224
+
225
+ ### With Performance Metrics
226
+
227
+ ```python
228
+ response = injector.inject_with_metrics("@memory query")
229
+
230
+ print(f"Found: {response.total_found} items")
231
+ print(f"Filtered: {response.filtered_count} items")
232
+ print(f"Performance: {response.performance_ms:.2f}ms")
233
+ print(f"Filter ratio: {response.filter_ratio:.1%}")
234
+ ```
235
+
236
+ ### Custom Parser and Formatter
237
+
238
+ ```python
239
+ from ai_context_injector import TagParser, ContextFormatter
240
+
241
+ # Custom parser with additional tags
242
+ parser = TagParser(custom_tags={"@docs", "@tickets"})
243
+
244
+ # Compact formatter for tight token budgets
245
+ formatter = ContextFormatter(
246
+ include_metadata=False,
247
+ include_anti_hallucination_rules=False
248
+ )
249
+
250
+ injector = ContextInjector(
251
+ current_project="my-app",
252
+ parser=parser,
253
+ formatter=formatter
254
+ )
255
+ ```
256
+
257
+ ### Relevance Filtering
258
+
259
+ ```python
260
+ # Only return items with relevance >= 0.80
261
+ context = injector.inject(
262
+ "@memory database optimization",
263
+ min_relevance=0.80,
264
+ max_items=5
265
+ )
266
+ ```
267
+
268
+ ## Custom Provider Examples
269
+
270
+ ### Simple In-Memory Provider
271
+
272
+ ```python
273
+ from ai_context_injector import IContextProvider, ContextItem
274
+ from datetime import datetime
275
+
276
+ class SimpleProvider(IContextProvider):
277
+ def __init__(self, data):
278
+ self.data = data # Dict[str, List[tuple]]
279
+
280
+ @property
281
+ def name(self) -> str:
282
+ return "Simple"
283
+
284
+ @property
285
+ def source_type(self) -> str:
286
+ return "memory"
287
+
288
+ def is_available(self) -> bool:
289
+ return True
290
+
291
+ def retrieve(self, request):
292
+ items = self.data.get(request.project, [])
293
+
294
+ return [
295
+ ContextItem(
296
+ content=content,
297
+ source="memory",
298
+ project=request.project,
299
+ metadata={},
300
+ relevance_score=score,
301
+ timestamp=datetime.now()
302
+ )
303
+ for content, score in items
304
+ if score >= request.min_relevance
305
+ ][:request.max_items]
306
+
307
+ # Usage
308
+ data = {
309
+ "my-app": [
310
+ ("Decision: Use Redis for caching", 0.95),
311
+ ("Learning: Connection pooling improved latency", 0.90),
312
+ ]
313
+ }
314
+
315
+ provider = SimpleProvider(data)
316
+ injector.register_provider("@memory", provider)
317
+ ```
318
+
319
+ ### SQLite Provider
320
+
321
+ ```python
322
+ import sqlite3
323
+ from ai_context_injector import IContextProvider, ContextItem
324
+ from datetime import datetime
325
+
326
+ class SQLiteProvider(IContextProvider):
327
+ def __init__(self, db_path):
328
+ self.db_path = db_path
329
+
330
+ @property
331
+ def name(self) -> str:
332
+ return "SQLite"
333
+
334
+ @property
335
+ def source_type(self) -> str:
336
+ return "memory"
337
+
338
+ def is_available(self) -> bool:
339
+ try:
340
+ conn = sqlite3.connect(self.db_path)
341
+ conn.close()
342
+ return True
343
+ except:
344
+ return False
345
+
346
+ def retrieve(self, request):
347
+ conn = sqlite3.connect(self.db_path)
348
+ cursor = conn.cursor()
349
+
350
+ # Simple full-text search
351
+ cursor.execute("""
352
+ SELECT content, relevance, timestamp
353
+ FROM memories
354
+ WHERE project = ?
355
+ AND content LIKE ?
356
+ AND relevance >= ?
357
+ ORDER BY relevance DESC
358
+ LIMIT ?
359
+ """, (
360
+ request.project,
361
+ f"%{request.query}%",
362
+ request.min_relevance,
363
+ request.max_items
364
+ ))
365
+
366
+ results = [
367
+ ContextItem(
368
+ content=row[0],
369
+ source="memory",
370
+ project=request.project,
371
+ metadata={},
372
+ relevance_score=row[1],
373
+ timestamp=datetime.fromisoformat(row[2])
374
+ )
375
+ for row in cursor.fetchall()
376
+ ]
377
+
378
+ conn.close()
379
+ return results
380
+ ```
381
+
382
+ ### Filesystem Code Provider
383
+
384
+ ```python
385
+ import subprocess
386
+ from pathlib import Path
387
+ from ai_context_injector import IContextProvider, ContextItem
388
+ from datetime import datetime
389
+
390
+ class FilesystemCodeProvider(IContextProvider):
391
+ def __init__(self, repo_path):
392
+ self.repo_path = Path(repo_path)
393
+
394
+ @property
395
+ def name(self) -> str:
396
+ return "Filesystem"
397
+
398
+ @property
399
+ def source_type(self) -> str:
400
+ return "code"
401
+
402
+ def is_available(self) -> bool:
403
+ return self.repo_path.exists()
404
+
405
+ def retrieve(self, request):
406
+ # Use ripgrep for fast search
407
+ result = subprocess.run(
408
+ ["rg", "--json", request.query, str(self.repo_path)],
409
+ capture_output=True,
410
+ text=True
411
+ )
412
+
413
+ # Parse ripgrep JSON output and convert to ContextItem
414
+ # (implementation details omitted for brevity)
415
+
416
+ return items[:request.max_items]
417
+ ```
418
+
419
+ ## API Reference
420
+
421
+ ### Core Classes
422
+
423
+ #### `ContextInjector`
424
+
425
+ Main orchestrator for the entire pipeline.
426
+
427
+ ```python
428
+ injector = ContextInjector(
429
+ current_project="my-app", # Project name (auto-detected from cwd if None)
430
+ parser=None, # Custom TagParser instance
431
+ formatter=None # Custom ContextFormatter instance
432
+ )
433
+
434
+ # Register providers
435
+ injector.register_provider(tag, provider)
436
+
437
+ # Inject context
438
+ context = injector.inject(
439
+ user_input, # String with tags
440
+ max_items=10, # Max total items
441
+ min_relevance=0.70 # Min relevance score (0.0-1.0)
442
+ )
443
+
444
+ # With metrics
445
+ response = injector.inject_with_metrics(user_input, max_items=10, min_relevance=0.70)
446
+
447
+ # Utility methods
448
+ injector.has_tags(user_input) # bool
449
+ injector.extract_query_only(user_input) # str (tags removed)
450
+ ```
451
+
452
+ #### `IContextProvider`
453
+
454
+ Abstract base class for custom providers.
455
+
456
+ ```python
457
+ class MyProvider(IContextProvider):
458
+ @property
459
+ def name(self) -> str:
460
+ """Provider name for logging/debugging."""
461
+ return "MyProvider"
462
+
463
+ @property
464
+ def source_type(self) -> str:
465
+ """Source type: 'memory', 'code', 'session', 'custom', etc."""
466
+ return "memory"
467
+
468
+ def is_available(self) -> bool:
469
+ """Check if provider is available (DB connected, files exist, etc.)."""
470
+ return True
471
+
472
+ def retrieve(self, request: ContextRequest) -> List[ContextItem]:
473
+ """Retrieve context items matching the request."""
474
+ # Your logic here
475
+ return items
476
+ ```
477
+
478
+ #### `ContextItem`
479
+
480
+ Represents a single piece of context.
481
+
482
+ ```python
483
+ item = ContextItem(
484
+ content="Decision: Use PostgreSQL", # Required
485
+ source="memory", # Required
486
+ project="my-app", # Required
487
+ metadata={}, # Required (can be empty)
488
+ relevance_score=0.95, # Required (0.0-1.0)
489
+ timestamp=datetime.now(), # Required
490
+ file_path=None, # Optional (for code)
491
+ line_range=None # Optional (for code, tuple)
492
+ )
493
+
494
+ # Generate citation
495
+ citation = item.citation() # "[memory:my-app:2026-05-30]"
496
+ ```
497
+
498
+ #### `TagParser`
499
+
500
+ Parses tags from user input.
501
+
502
+ ```python
503
+ parser = TagParser(custom_tags={"@docs", "@tickets"})
504
+
505
+ tags = parser.parse("@memory query") # List[ParsedTag]
506
+ has_tags = parser.has_tags("@memory query") # bool
507
+ clean = parser.remove_tags("@memory query") # str
508
+ parser.register_tag("@custom") # None
509
+ ```
510
+
511
+ #### `ContextFormatter`
512
+
513
+ Formats context items for LLM injection.
514
+
515
+ ```python
516
+ formatter = ContextFormatter(
517
+ include_metadata=True,
518
+ include_citations=True,
519
+ include_anti_hallucination_rules=True
520
+ )
521
+
522
+ response = formatter.format(items, current_project)
523
+ compact = formatter.format_compact(items)
524
+ single = formatter.format_single(item, include_delimiters=True)
525
+ ```
526
+
527
+ ### Convenience Functions
528
+
529
+ ```python
530
+ from ai_context_injector import inject_context, parse_tags, format_context
531
+
532
+ # Quick injection
533
+ context = inject_context(
534
+ user_input="@memory query",
535
+ providers={"@memory": my_provider},
536
+ project="my-app",
537
+ max_items=10,
538
+ min_relevance=0.70
539
+ )
540
+
541
+ # Quick parsing
542
+ tags = parse_tags("@memory query", custom_tags={"@docs"})
543
+
544
+ # Quick formatting
545
+ response = format_context(items, current_project="my-app")
546
+ ```
547
+
548
+ ## Architecture
549
+
550
+ ```
551
+ User Input: "@memory dark keyboard @code KeyboardView"
552
+
553
+ ├─> TagParser
554
+ │ ├─> ParsedTag(tag="@memory", query="dark keyboard")
555
+ │ └─> ParsedTag(tag="@code", query="KeyboardView")
556
+
557
+ ├─> ContextInjector
558
+ │ ├─> Route to providers
559
+ │ │ ├─> MemoryProvider.retrieve() → [ContextItem, ...]
560
+ │ │ └─> CodeProvider.retrieve() → [ContextItem, ...]
561
+ │ │
562
+ │ ├─> Aggregate results
563
+ │ ├─> Deduplicate (first 100 chars hash)
564
+ │ ├─> Sort by relevance
565
+ │ └─> Limit to max_items
566
+
567
+ └─> ContextFormatter
568
+ ├─> Add delimiters
569
+ ├─> Add anti-hallucination rules
570
+ ├─> Add metadata + citations
571
+ ├─> Check cross-project warnings
572
+ └─> Format items
573
+
574
+ └─> Formatted context string (ready for LLM)
575
+ ```
576
+
577
+ ## Performance
578
+
579
+ Target: <25ms total pipeline
580
+ Proven: 17.7ms in production (memory-system)
581
+
582
+ Breakdown:
583
+ - Parsing: <1ms
584
+ - Provider retrieval: ~10ms (depends on your provider)
585
+ - Aggregation/dedup: ~2ms
586
+ - Sorting: ~2ms
587
+ - Formatting: ~2ms
588
+
589
+ **Tip:** Provider performance is usually the bottleneck. Use indexes, caching, and efficient queries.
590
+
591
+ ## Testing
592
+
593
+ ```bash
594
+ # Run all tests
595
+ pytest
596
+
597
+ # Run with coverage
598
+ pytest --cov=ai_context_injector
599
+
600
+ # Run only unit tests
601
+ pytest tests/core/
602
+
603
+ # Run only integration tests
604
+ pytest tests/test_integration.py
605
+
606
+ # Run specific test
607
+ pytest tests/core/test_injector.py::TestBasicInjection::test_inject_with_single_tag
608
+ ```
609
+
610
+ ## Design Principles
611
+
612
+ 1. **Explicit over implicit**: Empty providers dict by default, users must register
613
+ 2. **Project isolation by default**: Never auto-mix contexts across projects
614
+ 3. **Zero magic**: No auto-discovery, no global state, no hidden dependencies
615
+ 4. **Library not framework**: Integrate into your app, don't build around it
616
+ 5. **Performance matters**: <25ms target, every millisecond counts
617
+ 6. **Type safety**: Full type hints, no `Any` types in public API
618
+ 7. **Fail gracefully**: Missing/unavailable providers return None, not exceptions
619
+
620
+ ## FAQ
621
+
622
+ ### Why not just use RAG?
623
+
624
+ RAG is great for semantic search, but context injection solves a different problem:
625
+ - **Structured retrieval**: Tag-based routing to different data sources
626
+ - **Multi-source aggregation**: Combine memory + code + session in one query
627
+ - **Project isolation**: Hard boundaries prevent context mixing
628
+ - **Anti-hallucination**: Built-in safeguards with citations
629
+
630
+ You can use RAG *as a provider* in this system!
631
+
632
+ ### Why project isolation by default?
633
+
634
+ In multi-project environments, mixing contexts causes LLMs to:
635
+ - Suggest code patterns from wrong project
636
+ - Reference APIs that don't exist in current project
637
+ - Mix architectural decisions across boundaries
638
+
639
+ Hard isolation by default prevents these errors. Use `:all` modifier when you actually want cross-project search.
640
+
641
+ ### Can I use this with any LLM?
642
+
643
+ Yes! This library just generates formatted context strings. Use them with:
644
+ - OpenAI (GPT-4, GPT-3.5)
645
+ - Anthropic (Claude)
646
+ - Local models (Ollama, LM Studio)
647
+ - Any LLM API that accepts text prompts
648
+
649
+ ### How do I handle large codebases?
650
+
651
+ 1. **Smart providers**: Use search indexes (ripgrep, SQLite FTS, Elasticsearch)
652
+ 2. **Relevance filtering**: Set `min_relevance=0.80` to reduce noise
653
+ 3. **Limit results**: Use `max_items=5` for tight token budgets
654
+ 4. **Compact format**: Disable metadata with `ContextFormatter(include_metadata=False)`
655
+
656
+ ### Can I use this in production?
657
+
658
+ Yes! The core library has:
659
+ - 219 tests (100% passing)
660
+ - No external dependencies
661
+ - Battle-tested in memory-system project
662
+ - Proven <20ms performance
663
+
664
+ Just write solid providers and you're good to go.
665
+
666
+ ## Contributing
667
+
668
+ Contributions welcome! Please:
669
+ 1. Add tests for new features
670
+ 2. Follow existing code style
671
+ 3. Update documentation
672
+ 4. Run `pytest` before submitting
673
+
674
+ ## License
675
+
676
+ MIT License - see LICENSE file for details
677
+
678
+ ## Acknowledgments
679
+
680
+ Ported from the context injection system in memory-system project, which has been battle-tested in production for multi-project development workflows.