devscontext 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.
- devscontext/__init__.py +3 -0
- devscontext/adapters/__init__.py +23 -0
- devscontext/adapters/base.py +105 -0
- devscontext/adapters/fireflies.py +585 -0
- devscontext/adapters/gmail.py +580 -0
- devscontext/adapters/jira.py +639 -0
- devscontext/adapters/local_docs.py +984 -0
- devscontext/adapters/slack.py +804 -0
- devscontext/agents/__init__.py +28 -0
- devscontext/agents/preprocessor.py +775 -0
- devscontext/agents/watcher.py +265 -0
- devscontext/cache.py +151 -0
- devscontext/cli.py +727 -0
- devscontext/config.py +264 -0
- devscontext/constants.py +107 -0
- devscontext/core.py +582 -0
- devscontext/exceptions.py +148 -0
- devscontext/logging.py +181 -0
- devscontext/models.py +504 -0
- devscontext/plugins/__init__.py +49 -0
- devscontext/plugins/base.py +321 -0
- devscontext/plugins/registry.py +544 -0
- devscontext/py.typed +0 -0
- devscontext/rag/__init__.py +113 -0
- devscontext/rag/embeddings.py +296 -0
- devscontext/rag/index.py +323 -0
- devscontext/server.py +374 -0
- devscontext/storage.py +321 -0
- devscontext/synthesis.py +1057 -0
- devscontext/utils.py +297 -0
- devscontext-0.1.0.dist-info/METADATA +253 -0
- devscontext-0.1.0.dist-info/RECORD +35 -0
- devscontext-0.1.0.dist-info/WHEEL +4 -0
- devscontext-0.1.0.dist-info/entry_points.txt +2 -0
- devscontext-0.1.0.dist-info/licenses/LICENSE +21 -0
devscontext/utils.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""Utility functions for text processing and formatting."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
# Common English stop words
|
|
6
|
+
STOP_WORDS = frozenset(
|
|
7
|
+
{
|
|
8
|
+
"a",
|
|
9
|
+
"an",
|
|
10
|
+
"the",
|
|
11
|
+
"and",
|
|
12
|
+
"or",
|
|
13
|
+
"but",
|
|
14
|
+
"in",
|
|
15
|
+
"on",
|
|
16
|
+
"at",
|
|
17
|
+
"to",
|
|
18
|
+
"for",
|
|
19
|
+
"of",
|
|
20
|
+
"with",
|
|
21
|
+
"by",
|
|
22
|
+
"from",
|
|
23
|
+
"as",
|
|
24
|
+
"is",
|
|
25
|
+
"was",
|
|
26
|
+
"are",
|
|
27
|
+
"were",
|
|
28
|
+
"been",
|
|
29
|
+
"be",
|
|
30
|
+
"have",
|
|
31
|
+
"has",
|
|
32
|
+
"had",
|
|
33
|
+
"do",
|
|
34
|
+
"does",
|
|
35
|
+
"did",
|
|
36
|
+
"will",
|
|
37
|
+
"would",
|
|
38
|
+
"could",
|
|
39
|
+
"should",
|
|
40
|
+
"may",
|
|
41
|
+
"might",
|
|
42
|
+
"must",
|
|
43
|
+
"shall",
|
|
44
|
+
"can",
|
|
45
|
+
"need",
|
|
46
|
+
"it",
|
|
47
|
+
"its",
|
|
48
|
+
"this",
|
|
49
|
+
"that",
|
|
50
|
+
"these",
|
|
51
|
+
"those",
|
|
52
|
+
"i",
|
|
53
|
+
"you",
|
|
54
|
+
"he",
|
|
55
|
+
"she",
|
|
56
|
+
"we",
|
|
57
|
+
"they",
|
|
58
|
+
"what",
|
|
59
|
+
"which",
|
|
60
|
+
"who",
|
|
61
|
+
"when",
|
|
62
|
+
"where",
|
|
63
|
+
"why",
|
|
64
|
+
"how",
|
|
65
|
+
"all",
|
|
66
|
+
"each",
|
|
67
|
+
"every",
|
|
68
|
+
"both",
|
|
69
|
+
"few",
|
|
70
|
+
"more",
|
|
71
|
+
"most",
|
|
72
|
+
"other",
|
|
73
|
+
"some",
|
|
74
|
+
"such",
|
|
75
|
+
"no",
|
|
76
|
+
"nor",
|
|
77
|
+
"not",
|
|
78
|
+
"only",
|
|
79
|
+
"own",
|
|
80
|
+
"same",
|
|
81
|
+
"so",
|
|
82
|
+
"than",
|
|
83
|
+
"too",
|
|
84
|
+
"very",
|
|
85
|
+
"just",
|
|
86
|
+
"also",
|
|
87
|
+
"now",
|
|
88
|
+
"here",
|
|
89
|
+
"there",
|
|
90
|
+
"then",
|
|
91
|
+
"if",
|
|
92
|
+
"else",
|
|
93
|
+
"because",
|
|
94
|
+
"about",
|
|
95
|
+
"into",
|
|
96
|
+
"through",
|
|
97
|
+
"during",
|
|
98
|
+
"before",
|
|
99
|
+
"after",
|
|
100
|
+
"above",
|
|
101
|
+
"below",
|
|
102
|
+
"between",
|
|
103
|
+
"under",
|
|
104
|
+
"again",
|
|
105
|
+
"further",
|
|
106
|
+
"once",
|
|
107
|
+
"any",
|
|
108
|
+
"out",
|
|
109
|
+
"up",
|
|
110
|
+
"down",
|
|
111
|
+
"off",
|
|
112
|
+
"over",
|
|
113
|
+
"our",
|
|
114
|
+
"your",
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Common ticket action verbs to filter out
|
|
119
|
+
ACTION_VERBS = frozenset(
|
|
120
|
+
{
|
|
121
|
+
"add",
|
|
122
|
+
"fix",
|
|
123
|
+
"update",
|
|
124
|
+
"implement",
|
|
125
|
+
"create",
|
|
126
|
+
"remove",
|
|
127
|
+
"delete",
|
|
128
|
+
"change",
|
|
129
|
+
"modify",
|
|
130
|
+
"refactor",
|
|
131
|
+
"improve",
|
|
132
|
+
"enhance",
|
|
133
|
+
"optimize",
|
|
134
|
+
"handle",
|
|
135
|
+
"support",
|
|
136
|
+
"enable",
|
|
137
|
+
"disable",
|
|
138
|
+
"configure",
|
|
139
|
+
"setup",
|
|
140
|
+
"make",
|
|
141
|
+
"get",
|
|
142
|
+
"set",
|
|
143
|
+
"use",
|
|
144
|
+
"move",
|
|
145
|
+
"rename",
|
|
146
|
+
"replace",
|
|
147
|
+
"resolve",
|
|
148
|
+
"ensure",
|
|
149
|
+
"allow",
|
|
150
|
+
"prevent",
|
|
151
|
+
"check",
|
|
152
|
+
"verify",
|
|
153
|
+
"validate",
|
|
154
|
+
"test",
|
|
155
|
+
"debug",
|
|
156
|
+
"investigate",
|
|
157
|
+
"review",
|
|
158
|
+
"clean",
|
|
159
|
+
"cleanup",
|
|
160
|
+
"simplify",
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def extract_keywords(text: str) -> list[str]:
|
|
166
|
+
"""
|
|
167
|
+
Extract meaningful keywords from text for document matching.
|
|
168
|
+
|
|
169
|
+
Removes stop words, common action verbs, short words (<3 chars),
|
|
170
|
+
and deduplicates. Returns top 10 keywords, longest first.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
text: Input text (typically ticket title + description).
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
List of up to 10 keywords, ordered by length (most specific first).
|
|
177
|
+
|
|
178
|
+
Example:
|
|
179
|
+
>>> extract_keywords("Add retry logic to payment webhook handler")
|
|
180
|
+
['webhook', 'payment', 'handler', 'retry', 'logic']
|
|
181
|
+
"""
|
|
182
|
+
if not text:
|
|
183
|
+
return []
|
|
184
|
+
|
|
185
|
+
# Convert to lowercase and extract words (alphanumeric only)
|
|
186
|
+
words = re.findall(r"[a-z0-9]+", text.lower())
|
|
187
|
+
|
|
188
|
+
# Filter out stop words, action verbs, and short words
|
|
189
|
+
keywords = []
|
|
190
|
+
seen = set()
|
|
191
|
+
for word in words:
|
|
192
|
+
if (
|
|
193
|
+
len(word) >= 3
|
|
194
|
+
and word not in STOP_WORDS
|
|
195
|
+
and word not in ACTION_VERBS
|
|
196
|
+
and word not in seen
|
|
197
|
+
):
|
|
198
|
+
keywords.append(word)
|
|
199
|
+
seen.add(word)
|
|
200
|
+
|
|
201
|
+
# Sort by length (longest/most specific first), then alphabetically for stability
|
|
202
|
+
keywords.sort(key=lambda w: (-len(w), w))
|
|
203
|
+
|
|
204
|
+
# Return top 10
|
|
205
|
+
return keywords[:10]
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def truncate_text(text: str, max_chars: int) -> str:
|
|
209
|
+
"""
|
|
210
|
+
Truncate text to max_chars, breaking at sentence boundary if possible.
|
|
211
|
+
|
|
212
|
+
Appends "... [truncated]" if text was cut.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
text: Input text to truncate.
|
|
216
|
+
max_chars: Maximum character limit.
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
Truncated text with suffix if cut, or original if within limit.
|
|
220
|
+
"""
|
|
221
|
+
if not text or len(text) <= max_chars:
|
|
222
|
+
return text
|
|
223
|
+
|
|
224
|
+
suffix = "... [truncated]"
|
|
225
|
+
suffix_len = len(suffix)
|
|
226
|
+
|
|
227
|
+
# Need at least some content before suffix
|
|
228
|
+
if max_chars <= suffix_len:
|
|
229
|
+
return text[:max_chars]
|
|
230
|
+
|
|
231
|
+
available = max_chars - suffix_len
|
|
232
|
+
|
|
233
|
+
# Try to find sentence boundary (. ! ?) within the available space
|
|
234
|
+
# Look for the last sentence end before the cutoff
|
|
235
|
+
truncated = text[:available]
|
|
236
|
+
|
|
237
|
+
# Find last sentence-ending punctuation followed by space or end
|
|
238
|
+
sentence_end = -1
|
|
239
|
+
for i in range(len(truncated) - 1, -1, -1):
|
|
240
|
+
# Check if it's likely a sentence end (followed by space, end, or quote)
|
|
241
|
+
if truncated[i] in ".!?" and (i == len(truncated) - 1 or truncated[i + 1] in " \n\t\"'"):
|
|
242
|
+
sentence_end = i + 1
|
|
243
|
+
break
|
|
244
|
+
|
|
245
|
+
# Use sentence boundary if it captures at least 50% of available space
|
|
246
|
+
if sentence_end > available * 0.5:
|
|
247
|
+
return truncated[:sentence_end].rstrip() + suffix
|
|
248
|
+
|
|
249
|
+
# Otherwise, try to break at word boundary
|
|
250
|
+
last_space = truncated.rfind(" ")
|
|
251
|
+
if last_space > available * 0.5:
|
|
252
|
+
return truncated[:last_space].rstrip() + suffix
|
|
253
|
+
|
|
254
|
+
# Fall back to hard cut
|
|
255
|
+
return truncated + suffix
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def format_duration(ms: int) -> str:
|
|
259
|
+
"""
|
|
260
|
+
Format milliseconds to human-readable duration.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
ms: Duration in milliseconds.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
Formatted string: "150ms", "2.5s", "1m 5s", etc.
|
|
267
|
+
|
|
268
|
+
Examples:
|
|
269
|
+
>>> format_duration(150)
|
|
270
|
+
'150ms'
|
|
271
|
+
>>> format_duration(2500)
|
|
272
|
+
'2.5s'
|
|
273
|
+
>>> format_duration(65000)
|
|
274
|
+
'1m 5s'
|
|
275
|
+
"""
|
|
276
|
+
if ms < 0:
|
|
277
|
+
return "0ms"
|
|
278
|
+
|
|
279
|
+
if ms < 1000:
|
|
280
|
+
return f"{ms}ms"
|
|
281
|
+
|
|
282
|
+
seconds = ms / 1000
|
|
283
|
+
|
|
284
|
+
if seconds < 60:
|
|
285
|
+
# Format as seconds, remove trailing zeros
|
|
286
|
+
if seconds == int(seconds):
|
|
287
|
+
return f"{int(seconds)}s"
|
|
288
|
+
formatted = f"{seconds:.1f}".rstrip("0").rstrip(".")
|
|
289
|
+
return f"{formatted}s"
|
|
290
|
+
|
|
291
|
+
minutes = int(seconds // 60)
|
|
292
|
+
remaining_seconds = int(seconds % 60)
|
|
293
|
+
|
|
294
|
+
if remaining_seconds == 0:
|
|
295
|
+
return f"{minutes}m"
|
|
296
|
+
|
|
297
|
+
return f"{minutes}m {remaining_seconds}s"
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: devscontext
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server that provides AI coding agents with synthesized engineering context from SDLC tools
|
|
5
|
+
Project-URL: Homepage, https://github.com/Pro0f/devscontext
|
|
6
|
+
Project-URL: Repository, https://github.com/Pro0f/devscontext
|
|
7
|
+
Project-URL: Issues, https://github.com/Pro0f/devscontext/issues
|
|
8
|
+
Author: DevsContext Contributors
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai,claude-code,context,developer-tools,jira,mcp
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: aiosqlite>=0.20
|
|
21
|
+
Requires-Dist: click>=8.0
|
|
22
|
+
Requires-Dist: httpx>=0.27
|
|
23
|
+
Requires-Dist: mcp>=1.0
|
|
24
|
+
Requires-Dist: pydantic>=2.0
|
|
25
|
+
Requires-Dist: pyyaml>=6.0
|
|
26
|
+
Provides-Extra: all
|
|
27
|
+
Requires-Dist: anthropic>=0.40; extra == 'all'
|
|
28
|
+
Requires-Dist: google-api-python-client>=2.0.0; extra == 'all'
|
|
29
|
+
Requires-Dist: google-auth-oauthlib>=1.0.0; extra == 'all'
|
|
30
|
+
Requires-Dist: numpy>=1.24.0; extra == 'all'
|
|
31
|
+
Requires-Dist: openai>=1.50; extra == 'all'
|
|
32
|
+
Requires-Dist: sentence-transformers>=2.2.0; extra == 'all'
|
|
33
|
+
Requires-Dist: slack-sdk>=3.0.0; extra == 'all'
|
|
34
|
+
Provides-Extra: anthropic
|
|
35
|
+
Requires-Dist: anthropic>=0.40; extra == 'anthropic'
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
|
|
40
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
42
|
+
Provides-Extra: gmail
|
|
43
|
+
Requires-Dist: google-api-python-client>=2.0.0; extra == 'gmail'
|
|
44
|
+
Requires-Dist: google-auth-oauthlib>=1.0.0; extra == 'gmail'
|
|
45
|
+
Provides-Extra: openai
|
|
46
|
+
Requires-Dist: openai>=1.50; extra == 'openai'
|
|
47
|
+
Provides-Extra: rag
|
|
48
|
+
Requires-Dist: numpy>=1.24.0; extra == 'rag'
|
|
49
|
+
Requires-Dist: sentence-transformers>=2.2.0; extra == 'rag'
|
|
50
|
+
Provides-Extra: slack
|
|
51
|
+
Requires-Dist: slack-sdk>=3.0.0; extra == 'slack'
|
|
52
|
+
Description-Content-Type: text/markdown
|
|
53
|
+
|
|
54
|
+
# DevsContext
|
|
55
|
+
|
|
56
|
+
MCP server that gives AI coding agents synthesized engineering context — requirements, decisions, architecture, and standards — from your actual tools.
|
|
57
|
+
|
|
58
|
+
## The Problem
|
|
59
|
+
|
|
60
|
+
AI coding agents lack context. They don't know your team's decisions, architecture patterns, or coding standards. Connecting raw MCP servers floods them with irrelevant data they can't prioritize. Large companies build internal context infrastructure. DevsContext brings that to everyone.
|
|
61
|
+
|
|
62
|
+
## What You Get
|
|
63
|
+
|
|
64
|
+
When you say "work on PROJ-123" in Claude Code, DevsContext fetches from Jira, meeting transcripts, and your docs, then synthesizes it into this:
|
|
65
|
+
|
|
66
|
+
```markdown
|
|
67
|
+
## Task: PROJ-123 — Add retry logic to payment webhook handler
|
|
68
|
+
|
|
69
|
+
### Requirements
|
|
70
|
+
1. Implement exponential backoff for failed webhook deliveries
|
|
71
|
+
2. Max 5 retry attempts over 24 hours
|
|
72
|
+
3. Dead-letter queue for permanently failed webhooks
|
|
73
|
+
4. Metrics for retry success/failure rates
|
|
74
|
+
|
|
75
|
+
Acceptance criteria: [Jira PROJ-123]
|
|
76
|
+
- [ ] Webhooks retry with exponential backoff (1min, 5min, 30min, 2hr, 12hr)
|
|
77
|
+
- [ ] Failed webhooks move to DLQ after 5 attempts
|
|
78
|
+
- [ ] Dashboard shows retry metrics
|
|
79
|
+
|
|
80
|
+
### Key Decisions
|
|
81
|
+
- **Use SQS with visibility timeout** for retry scheduling, not cron jobs.
|
|
82
|
+
Decided by @sarah in March 15 sprint planning. Rationale: SQS handles
|
|
83
|
+
timing natively, reduces operational overhead. [Meeting: Sprint 23 Planning]
|
|
84
|
+
|
|
85
|
+
- **Exponential backoff schedule**: 1min → 5min → 30min → 2hr → 12hr.
|
|
86
|
+
Based on payment processor rate limits. [Comment by @mike, Mar 16]
|
|
87
|
+
|
|
88
|
+
### Architecture Context
|
|
89
|
+
Webhook flow: `PaymentController` → `WebhookService.dispatch()` → SQS queue
|
|
90
|
+
→ `WebhookWorker.process()` → external endpoint.
|
|
91
|
+
|
|
92
|
+
Add retry logic in `WebhookWorker.process()` at:
|
|
93
|
+
`src/workers/webhook_worker.ts:45-80`
|
|
94
|
+
|
|
95
|
+
DLQ table schema in `migrations/004_webhook_dlq.sql`. [Architecture: payments-service.md]
|
|
96
|
+
|
|
97
|
+
### Coding Standards
|
|
98
|
+
- Use `Result<T, WebhookError>` pattern, don't throw exceptions
|
|
99
|
+
- Retry delays: use `calculateBackoff(attempt)` helper from `src/utils/retry.ts`
|
|
100
|
+
- Tests: mock SQS with `@aws-sdk/client-sqs-mock`, see `tests/workers/` for examples
|
|
101
|
+
[Standards: typescript.md, testing.md]
|
|
102
|
+
|
|
103
|
+
### Related Work
|
|
104
|
+
- PROJ-456: "Payment webhook initial implementation" (Done) — base implementation
|
|
105
|
+
- PROJ-789: "Add webhook monitoring dashboard" (In Progress) — will consume the metrics
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
One synthesized block. Everything the AI needs to write correct code.
|
|
109
|
+
|
|
110
|
+
## Quick Start
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
pip install devscontext
|
|
114
|
+
devscontext init
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Set your credentials:
|
|
118
|
+
```bash
|
|
119
|
+
export JIRA_EMAIL="you@company.com"
|
|
120
|
+
export JIRA_API_TOKEN="your-token"
|
|
121
|
+
export ANTHROPIC_API_KEY="your-key" # for synthesis
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Connect to Claude Code:
|
|
125
|
+
```bash
|
|
126
|
+
claude mcp add devscontext -- devscontext serve
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Then in Claude Code:
|
|
130
|
+
```
|
|
131
|
+
> work on PROJ-123
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Supported Sources
|
|
135
|
+
|
|
136
|
+
| Source | What's Fetched | Status |
|
|
137
|
+
|--------|----------------|--------|
|
|
138
|
+
| **Jira** | Ticket details, comments, linked issues, acceptance criteria | Stable |
|
|
139
|
+
| **Fireflies** | Meeting transcripts, decisions, action items | Stable |
|
|
140
|
+
| **Local Docs** | Architecture docs, coding standards, ADRs | Stable |
|
|
141
|
+
| **Slack** | Channel discussions, threads, decisions | New |
|
|
142
|
+
| **Gmail** | Email threads related to tickets | New |
|
|
143
|
+
|
|
144
|
+
Coming soon: Linear, Notion, Confluence
|
|
145
|
+
|
|
146
|
+
## Pre-processing Agent
|
|
147
|
+
|
|
148
|
+
Build context proactively before developers pick up tickets:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
# Start the agent (polls Jira for ready tickets)
|
|
152
|
+
devscontext agent start
|
|
153
|
+
|
|
154
|
+
# Single run for CI/cron
|
|
155
|
+
devscontext agent run-once
|
|
156
|
+
|
|
157
|
+
# Check pre-built context status
|
|
158
|
+
devscontext agent status
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Configure in `.devscontext.yaml`:
|
|
162
|
+
|
|
163
|
+
```yaml
|
|
164
|
+
agents:
|
|
165
|
+
preprocessor:
|
|
166
|
+
enabled: true
|
|
167
|
+
jira_status: "Ready for Development"
|
|
168
|
+
jira_project: "PROJ"
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
See [docs/pre-processing.md](docs/pre-processing.md) for the full guide.
|
|
172
|
+
|
|
173
|
+
## Plugin System
|
|
174
|
+
|
|
175
|
+
DevsContext uses a plugin architecture for adapters and synthesis:
|
|
176
|
+
|
|
177
|
+
- **Adapters**: Fetch context from sources (Jira, Slack, docs, etc.)
|
|
178
|
+
- **Synthesis Plugins**: Combine context (LLM, template, passthrough)
|
|
179
|
+
|
|
180
|
+
See [docs/plugins.md](docs/plugins.md) for creating custom plugins.
|
|
181
|
+
|
|
182
|
+
## Configuration
|
|
183
|
+
|
|
184
|
+
DevsContext uses `.devscontext.yaml` in your project root:
|
|
185
|
+
|
|
186
|
+
```yaml
|
|
187
|
+
sources:
|
|
188
|
+
jira:
|
|
189
|
+
enabled: true
|
|
190
|
+
base_url: "https://your-company.atlassian.net"
|
|
191
|
+
email: "${JIRA_EMAIL}"
|
|
192
|
+
api_token: "${JIRA_API_TOKEN}"
|
|
193
|
+
|
|
194
|
+
docs:
|
|
195
|
+
enabled: true
|
|
196
|
+
paths:
|
|
197
|
+
- "./docs"
|
|
198
|
+
- "./CLAUDE.md"
|
|
199
|
+
|
|
200
|
+
slack:
|
|
201
|
+
enabled: true
|
|
202
|
+
bot_token: "${SLACK_BOT_TOKEN}"
|
|
203
|
+
channels: ["engineering", "payments-team"]
|
|
204
|
+
|
|
205
|
+
synthesis:
|
|
206
|
+
provider: "anthropic"
|
|
207
|
+
model: "claude-haiku-4-5"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Full configuration reference: [docs/configuration.md](docs/configuration.md)
|
|
211
|
+
|
|
212
|
+
## How It Works
|
|
213
|
+
|
|
214
|
+
1. **Fetch**: When you mention a ticket, DevsContext fetches from all configured sources in parallel
|
|
215
|
+
2. **Extract**: It finds relevant content — ticket matches docs by component/label, searches meeting transcripts for keywords
|
|
216
|
+
3. **Synthesize**: An LLM combines raw data into a structured context block with sources cited
|
|
217
|
+
|
|
218
|
+
No background processes. No vector database. Just on-demand fetching and synthesis.
|
|
219
|
+
|
|
220
|
+
## MCP Tools
|
|
221
|
+
|
|
222
|
+
| Tool | When to Use | Example |
|
|
223
|
+
|------|-------------|---------|
|
|
224
|
+
| `get_task_context` | Starting work on a ticket | "work on PROJ-123" |
|
|
225
|
+
| `search_context` | Questions about architecture or past decisions | "how do we handle payment retries?" |
|
|
226
|
+
| `get_standards` | Checking coding conventions | "what are our testing standards?" |
|
|
227
|
+
|
|
228
|
+
## Development
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
git clone https://github.com/Pro0f/devscontext.git
|
|
232
|
+
cd devscontext
|
|
233
|
+
pip install -e ".[dev]"
|
|
234
|
+
|
|
235
|
+
# Run tests
|
|
236
|
+
pytest
|
|
237
|
+
|
|
238
|
+
# Lint
|
|
239
|
+
ruff check . && mypy src/
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Contributing
|
|
243
|
+
|
|
244
|
+
Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
245
|
+
|
|
246
|
+
Ideas for contributions:
|
|
247
|
+
- New adapters (Linear, Notion, Confluence)
|
|
248
|
+
- Better keyword extraction
|
|
249
|
+
- Caching improvements
|
|
250
|
+
|
|
251
|
+
## License
|
|
252
|
+
|
|
253
|
+
MIT
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
devscontext/__init__.py,sha256=vRbhZpVn7QyzeZfaWFaIE-XeD2UI43hjilSdt8CoFlU,91
|
|
2
|
+
devscontext/cache.py,sha256=tJpAMw-t_ni8Qxl7ZfbnbjqqFTkWbXq_FVfAxAfVofE,4151
|
|
3
|
+
devscontext/cli.py,sha256=5rBKpCeNeS0HO-uhorv_s77VSfQaiJ-0-FareYcma5w,22318
|
|
4
|
+
devscontext/config.py,sha256=jE_0S1CFAWsR_TOaUXNR472Zw2hRI0Rqn5ixW8yZSNs,7725
|
|
5
|
+
devscontext/constants.py,sha256=y1yaECG0W41fU2BZUZpvhJ52wItRQz5omkiWTCqSQh0,4967
|
|
6
|
+
devscontext/core.py,sha256=f4o_omcxFPDEIFvD2dEwBv1FxJ_jlJ_jpWuPaum9mfU,21642
|
|
7
|
+
devscontext/exceptions.py,sha256=wO0X42a98PX7RS5wwXvoMZ0Ts0zK_ZjqgHKzu7IqzjY,3990
|
|
8
|
+
devscontext/logging.py,sha256=Iptgq29RAnjCv6Z7rw9UNVGgaSSCvE7h7gC6HH73TOs,5289
|
|
9
|
+
devscontext/models.py,sha256=i_02qU3YhipypMQf3A8zuhUZ46BRx0DqfFr5Jfd0TvQ,19882
|
|
10
|
+
devscontext/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
devscontext/server.py,sha256=0q6D-7vFxMHbfy66GBJcM4jparamGvZ7p2G00Ep2gU0,11726
|
|
12
|
+
devscontext/storage.py,sha256=3ERUh8Y7MtN9rk9eILoX8Q78GZO5yUjo9nyZSUQvXkY,10227
|
|
13
|
+
devscontext/synthesis.py,sha256=5lipuCHwtoVOi0dSweoVIRl6pbM-04Pug1TIJoIIUWY,38221
|
|
14
|
+
devscontext/utils.py,sha256=EhKP5Whz3U6-a1VdSwanQQdr-8DFT5ymtRk74B8ltf8,6377
|
|
15
|
+
devscontext/adapters/__init__.py,sha256=BNVOpZefWn_IgOThIBKIWhyMaj7akSHfwXStyd1WrBo,745
|
|
16
|
+
devscontext/adapters/base.py,sha256=TC2vI4kYY9Q890DdEneaf03zOxEqDl9JPHo95OhWhNE,3128
|
|
17
|
+
devscontext/adapters/fireflies.py,sha256=TQ1u1khk4xnQ-wisu03mbNTJpBYMW9TjkX6oXmJS00c,19618
|
|
18
|
+
devscontext/adapters/gmail.py,sha256=ldmBDePmUg78J3M-94gnXJ3FFYpbkZmR_3UwUVf2Phs,18804
|
|
19
|
+
devscontext/adapters/jira.py,sha256=mKo8A_WsFd9s30GuYDUxjUf6a6hczLwNtlUJ5bwfNss,22402
|
|
20
|
+
devscontext/adapters/local_docs.py,sha256=oE54sh0yQrqdq1o6IhcHpW1l2O7fLFHChpS8RvsBVJI,32766
|
|
21
|
+
devscontext/adapters/slack.py,sha256=IJ5xzn77PNooiIRw-1qIb7yEnQT0iAvOQs7OH4iJ1N8,26844
|
|
22
|
+
devscontext/agents/__init__.py,sha256=UxarP1ECJsdhszBlZqUB-yxtzoEN1TcwyZrF0HC5OlE,919
|
|
23
|
+
devscontext/agents/preprocessor.py,sha256=-OxhXiJN1oaCNQBnyg9yUIH1KPDKQ9s_I3EQ95GZBP4,26546
|
|
24
|
+
devscontext/agents/watcher.py,sha256=tJcg5lUy0uRhW73e3aLUwbgMMRoy9ku171REkejhNY4,8408
|
|
25
|
+
devscontext/plugins/__init__.py,sha256=RKBw6i4pepydq3PrB8hYi_wryKNaOSurwf9QmOtM0GA,1411
|
|
26
|
+
devscontext/plugins/base.py,sha256=5TBcoTPIxINlltbxQ-5N9NSZ0MQVfQ_hG6V_IZ2Qelk,11692
|
|
27
|
+
devscontext/plugins/registry.py,sha256=66j_N0j4G2TRWPSko0s_BpWWb5N7JYyIf4EKqmVC4J0,19733
|
|
28
|
+
devscontext/rag/__init__.py,sha256=U735Bl88IxqmOY1LUOUd6ufziLmzU63xjL4Slfd2CeY,3358
|
|
29
|
+
devscontext/rag/embeddings.py,sha256=wEZD9SQNtafCsJ_5VZZLlPPIXriw0RYYCCZBghWw5PI,9303
|
|
30
|
+
devscontext/rag/index.py,sha256=k4F3p1Rasf_btlmq67LlrDOUEObvfdrj5b-Rybox1cg,10280
|
|
31
|
+
devscontext-0.1.0.dist-info/METADATA,sha256=BQlmSRGuIqBeQhfUeYAL37eQauc4o-NjN0RzZhPX26w,7994
|
|
32
|
+
devscontext-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
33
|
+
devscontext-0.1.0.dist-info/entry_points.txt,sha256=daznxmPA5blRsUUTydk7VIbKjFeQ3c6zAap9yQZX_LM,53
|
|
34
|
+
devscontext-0.1.0.dist-info/licenses/LICENSE,sha256=zQEKy62rhAAlkXsFBkoZHFb6V9sBDbd7bf4fm5o_P3M,1066
|
|
35
|
+
devscontext-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Anthropic
|
|
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.
|