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/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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ devscontext = devscontext.cli:main
@@ -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.