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
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Custom exceptions for DevsContext.
|
|
2
|
+
|
|
3
|
+
This module defines a hierarchy of exceptions used throughout DevsContext.
|
|
4
|
+
All exceptions inherit from DevsContextError, making it easy to catch
|
|
5
|
+
all DevsContext-related errors in one place.
|
|
6
|
+
|
|
7
|
+
Exception Hierarchy:
|
|
8
|
+
DevsContextError (base)
|
|
9
|
+
├── ConfigError - Configuration loading/validation failures
|
|
10
|
+
├── AdapterError (base for adapter failures)
|
|
11
|
+
│ ├── JiraAdapterError
|
|
12
|
+
│ ├── FirefliesAdapterError
|
|
13
|
+
│ └── LocalDocsAdapterError
|
|
14
|
+
├── SynthesisError - LLM synthesis failures
|
|
15
|
+
└── CacheError - Cache operation failures
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DevsContextError(Exception):
|
|
22
|
+
"""Base exception for all DevsContext errors.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
message: Human-readable error message.
|
|
26
|
+
details: Optional dictionary with additional error context.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, message: str, details: dict[str, Any] | None = None) -> None:
|
|
30
|
+
super().__init__(message)
|
|
31
|
+
self.message = message
|
|
32
|
+
self.details = details or {}
|
|
33
|
+
|
|
34
|
+
def __str__(self) -> str:
|
|
35
|
+
if self.details:
|
|
36
|
+
return f"{self.message} | Details: {self.details}"
|
|
37
|
+
return self.message
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ConfigError(DevsContextError):
|
|
41
|
+
"""Raised when configuration loading or validation fails.
|
|
42
|
+
|
|
43
|
+
Examples:
|
|
44
|
+
- Invalid YAML syntax in .devscontext.yaml
|
|
45
|
+
- Missing required configuration values
|
|
46
|
+
- Environment variable not found
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AdapterError(DevsContextError):
|
|
51
|
+
"""Base exception for adapter-related errors.
|
|
52
|
+
|
|
53
|
+
All adapter-specific exceptions should inherit from this class.
|
|
54
|
+
This allows catching all adapter errors with a single except clause.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
message: Human-readable error message.
|
|
58
|
+
adapter_name: Name of the adapter that raised the error.
|
|
59
|
+
details: Optional dictionary with additional error context.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
message: str,
|
|
65
|
+
adapter_name: str,
|
|
66
|
+
details: dict[str, Any] | None = None,
|
|
67
|
+
) -> None:
|
|
68
|
+
super().__init__(message, details)
|
|
69
|
+
self.adapter_name = adapter_name
|
|
70
|
+
|
|
71
|
+
def __str__(self) -> str:
|
|
72
|
+
base = f"[{self.adapter_name}] {self.message}"
|
|
73
|
+
if self.details:
|
|
74
|
+
return f"{base} | Details: {self.details}"
|
|
75
|
+
return base
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class JiraAdapterError(AdapterError):
|
|
79
|
+
"""Raised when Jira API operations fail.
|
|
80
|
+
|
|
81
|
+
Examples:
|
|
82
|
+
- Authentication failure (401)
|
|
83
|
+
- Ticket not found (404)
|
|
84
|
+
- Rate limiting (429)
|
|
85
|
+
- Network errors
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
message: str,
|
|
91
|
+
details: dict[str, Any] | None = None,
|
|
92
|
+
) -> None:
|
|
93
|
+
super().__init__(message, adapter_name="jira", details=details)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class FirefliesAdapterError(AdapterError):
|
|
97
|
+
"""Raised when Fireflies API operations fail.
|
|
98
|
+
|
|
99
|
+
Examples:
|
|
100
|
+
- Authentication failure
|
|
101
|
+
- GraphQL query errors
|
|
102
|
+
- Rate limiting
|
|
103
|
+
- Network errors
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
def __init__(
|
|
107
|
+
self,
|
|
108
|
+
message: str,
|
|
109
|
+
details: dict[str, Any] | None = None,
|
|
110
|
+
) -> None:
|
|
111
|
+
super().__init__(message, adapter_name="fireflies", details=details)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class LocalDocsAdapterError(AdapterError):
|
|
115
|
+
"""Raised when local documentation operations fail.
|
|
116
|
+
|
|
117
|
+
Examples:
|
|
118
|
+
- Directory not found
|
|
119
|
+
- Permission denied
|
|
120
|
+
- Invalid file encoding
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
def __init__(
|
|
124
|
+
self,
|
|
125
|
+
message: str,
|
|
126
|
+
details: dict[str, Any] | None = None,
|
|
127
|
+
) -> None:
|
|
128
|
+
super().__init__(message, adapter_name="local_docs", details=details)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class SynthesisError(DevsContextError):
|
|
132
|
+
"""Raised when LLM synthesis operations fail.
|
|
133
|
+
|
|
134
|
+
Examples:
|
|
135
|
+
- LLM API rate limiting
|
|
136
|
+
- Invalid response format
|
|
137
|
+
- Context too long
|
|
138
|
+
- LLM not configured
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class CacheError(DevsContextError):
|
|
143
|
+
"""Raised when cache operations fail.
|
|
144
|
+
|
|
145
|
+
Examples:
|
|
146
|
+
- Serialization errors
|
|
147
|
+
- Memory allocation failures
|
|
148
|
+
"""
|
devscontext/logging.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Logging configuration for DevsContext.
|
|
2
|
+
|
|
3
|
+
This module provides structured logging setup for the entire application.
|
|
4
|
+
All modules should use `logging.getLogger(__name__)` to get their logger.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from devscontext.logging import setup_logging, get_logger
|
|
8
|
+
|
|
9
|
+
# At application startup
|
|
10
|
+
setup_logging()
|
|
11
|
+
|
|
12
|
+
# In each module
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
logger.info("Operation completed", extra={"duration_ms": 150, "source": "jira"})
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
import sys
|
|
19
|
+
from typing import TYPE_CHECKING, Any
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from collections.abc import Callable
|
|
23
|
+
|
|
24
|
+
from devscontext.constants import LOG_DATE_FORMAT, LOG_FORMAT
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class StructuredFormatter(logging.Formatter):
|
|
28
|
+
"""A formatter that outputs structured log messages.
|
|
29
|
+
|
|
30
|
+
Includes extra fields in the log output for better observability.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
34
|
+
"""Format a log record with structured fields.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
record: The log record to format.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Formatted log string.
|
|
41
|
+
"""
|
|
42
|
+
# Get the base formatted message
|
|
43
|
+
base_message = super().format(record)
|
|
44
|
+
|
|
45
|
+
# Extract extra fields (exclude standard LogRecord attributes)
|
|
46
|
+
standard_attrs = {
|
|
47
|
+
"name",
|
|
48
|
+
"msg",
|
|
49
|
+
"args",
|
|
50
|
+
"created",
|
|
51
|
+
"filename",
|
|
52
|
+
"funcName",
|
|
53
|
+
"levelname",
|
|
54
|
+
"levelno",
|
|
55
|
+
"lineno",
|
|
56
|
+
"module",
|
|
57
|
+
"msecs",
|
|
58
|
+
"pathname",
|
|
59
|
+
"process",
|
|
60
|
+
"processName",
|
|
61
|
+
"relativeCreated",
|
|
62
|
+
"stack_info",
|
|
63
|
+
"exc_info",
|
|
64
|
+
"exc_text",
|
|
65
|
+
"thread",
|
|
66
|
+
"threadName",
|
|
67
|
+
"taskName",
|
|
68
|
+
"message",
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
extra_fields: dict[str, Any] = {}
|
|
72
|
+
for key, value in record.__dict__.items():
|
|
73
|
+
if key not in standard_attrs and not key.startswith("_"):
|
|
74
|
+
extra_fields[key] = value
|
|
75
|
+
|
|
76
|
+
# Append extra fields if present
|
|
77
|
+
if extra_fields:
|
|
78
|
+
fields_str = " | ".join(f"{k}={v}" for k, v in extra_fields.items())
|
|
79
|
+
return f"{base_message} | {fields_str}"
|
|
80
|
+
|
|
81
|
+
return base_message
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def setup_logging(
|
|
85
|
+
level: int = logging.INFO,
|
|
86
|
+
*,
|
|
87
|
+
include_timestamp: bool = True,
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Configure logging for the application.
|
|
90
|
+
|
|
91
|
+
Sets up a structured formatter with consistent output format.
|
|
92
|
+
Should be called once at application startup.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
level: The logging level (default: INFO).
|
|
96
|
+
include_timestamp: Whether to include timestamps in output.
|
|
97
|
+
"""
|
|
98
|
+
# Create formatter
|
|
99
|
+
if include_timestamp:
|
|
100
|
+
formatter = StructuredFormatter(LOG_FORMAT, datefmt=LOG_DATE_FORMAT)
|
|
101
|
+
else:
|
|
102
|
+
formatter = StructuredFormatter("%(levelname)-8s | %(name)s | %(message)s")
|
|
103
|
+
|
|
104
|
+
# Configure root logger
|
|
105
|
+
root_logger = logging.getLogger()
|
|
106
|
+
root_logger.setLevel(level)
|
|
107
|
+
|
|
108
|
+
# Remove existing handlers
|
|
109
|
+
for handler in root_logger.handlers[:]:
|
|
110
|
+
root_logger.removeHandler(handler)
|
|
111
|
+
|
|
112
|
+
# Add stderr handler
|
|
113
|
+
handler = logging.StreamHandler(sys.stderr)
|
|
114
|
+
handler.setFormatter(formatter)
|
|
115
|
+
root_logger.addHandler(handler)
|
|
116
|
+
|
|
117
|
+
# Set devscontext loggers to the specified level
|
|
118
|
+
logging.getLogger("devscontext").setLevel(level)
|
|
119
|
+
|
|
120
|
+
# Reduce noise from third-party libraries
|
|
121
|
+
logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
122
|
+
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def get_logger(name: str) -> logging.Logger:
|
|
126
|
+
"""Get a logger for the given module.
|
|
127
|
+
|
|
128
|
+
This is a convenience wrapper around logging.getLogger that
|
|
129
|
+
ensures consistent naming.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
name: The module name (typically __name__).
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
A configured Logger instance.
|
|
136
|
+
"""
|
|
137
|
+
return logging.getLogger(name)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class LogContext:
|
|
141
|
+
"""Context manager for adding structured fields to log messages.
|
|
142
|
+
|
|
143
|
+
Usage:
|
|
144
|
+
with LogContext(logger, adapter="jira", ticket_id="PROJ-123"):
|
|
145
|
+
logger.info("Fetching ticket")
|
|
146
|
+
# The log will include: adapter=jira | ticket_id=PROJ-123
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def __init__(self, logger: logging.Logger, **fields: Any) -> None:
|
|
150
|
+
"""Initialize the log context.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
logger: The logger to use.
|
|
154
|
+
**fields: Extra fields to include in all log messages.
|
|
155
|
+
"""
|
|
156
|
+
self.logger = logger
|
|
157
|
+
self.fields = fields
|
|
158
|
+
self._old_factory: Callable[..., logging.LogRecord] | None = None
|
|
159
|
+
|
|
160
|
+
def __enter__(self) -> "LogContext":
|
|
161
|
+
"""Enter the context and set up the log record factory."""
|
|
162
|
+
old_factory = logging.getLogRecordFactory()
|
|
163
|
+
self._old_factory = old_factory
|
|
164
|
+
fields = self.fields
|
|
165
|
+
|
|
166
|
+
def record_factory(
|
|
167
|
+
*args: Any,
|
|
168
|
+
**kwargs: Any,
|
|
169
|
+
) -> logging.LogRecord:
|
|
170
|
+
record = old_factory(*args, **kwargs)
|
|
171
|
+
for key, value in fields.items():
|
|
172
|
+
setattr(record, key, value)
|
|
173
|
+
return record
|
|
174
|
+
|
|
175
|
+
logging.setLogRecordFactory(record_factory)
|
|
176
|
+
return self
|
|
177
|
+
|
|
178
|
+
def __exit__(self, *args: Any) -> None:
|
|
179
|
+
"""Exit the context and restore the original log record factory."""
|
|
180
|
+
if self._old_factory is not None:
|
|
181
|
+
logging.setLogRecordFactory(self._old_factory)
|