@pjmendonca/devflow 1.9.0
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.
- package/CHANGELOG.md +526 -0
- package/LICENSE +21 -0
- package/README.md +620 -0
- package/bin/devflow-checkpoint.js +10 -0
- package/bin/devflow-collab.js +10 -0
- package/bin/devflow-cost.js +10 -0
- package/bin/devflow-create-persona.js +10 -0
- package/bin/devflow-init.js +10 -0
- package/bin/devflow-memory.js +10 -0
- package/bin/devflow-new-doc.js +10 -0
- package/bin/devflow-personalize.js +10 -0
- package/bin/devflow-setup-checkpoint.js +10 -0
- package/bin/devflow-story.js +10 -0
- package/bin/devflow-tech-debt.js +10 -0
- package/bin/devflow-validate-overrides.js +10 -0
- package/bin/devflow-validate.js +10 -0
- package/bin/devflow-version.js +10 -0
- package/lib/constants.js +30 -0
- package/lib/exec-python.js +78 -0
- package/lib/python-check.js +178 -0
- package/package.json +64 -0
- package/tooling/.automation/agents/architect.md +135 -0
- package/tooling/.automation/agents/ba.md +70 -0
- package/tooling/.automation/agents/dev.md +79 -0
- package/tooling/.automation/agents/maintainer.md +97 -0
- package/tooling/.automation/agents/pm.md +116 -0
- package/tooling/.automation/agents/reviewer.md +141 -0
- package/tooling/.automation/agents/sm.md +61 -0
- package/tooling/.automation/agents/writer.md +193 -0
- package/tooling/.automation/config.ps1.template +61 -0
- package/tooling/.automation/config.sh.template +48 -0
- package/tooling/.automation/memory/.gitkeep +6 -0
- package/tooling/.automation/memory/knowledge/kg_integration-test.json +94 -0
- package/tooling/.automation/memory/knowledge/kg_test-story.json +300 -0
- package/tooling/.automation/memory/shared/shared_integration-test.json +30 -0
- package/tooling/.automation/memory/shared/shared_test-story.json +78 -0
- package/tooling/.automation/overrides/templates/README.md +113 -0
- package/tooling/.automation/overrides/templates/architect/README.md +27 -0
- package/tooling/.automation/overrides/templates/architect/cloud-native.yaml +92 -0
- package/tooling/.automation/overrides/templates/architect/enterprise-architect.yaml +85 -0
- package/tooling/.automation/overrides/templates/architect/pragmatic-minimalist.yaml +88 -0
- package/tooling/.automation/overrides/templates/ba/README.md +27 -0
- package/tooling/.automation/overrides/templates/ba/agile-storyteller.yaml +86 -0
- package/tooling/.automation/overrides/templates/ba/domain-expert.yaml +91 -0
- package/tooling/.automation/overrides/templates/ba/requirements-engineer.yaml +89 -0
- package/tooling/.automation/overrides/templates/dev/README.md +32 -0
- package/tooling/.automation/overrides/templates/dev/junior-mentored.yaml +39 -0
- package/tooling/.automation/overrides/templates/dev/performance-engineer.yaml +43 -0
- package/tooling/.automation/overrides/templates/dev/rapid-prototyper.yaml +52 -0
- package/tooling/.automation/overrides/templates/dev/security-focused.yaml +43 -0
- package/tooling/.automation/overrides/templates/dev/senior-fullstack.yaml +39 -0
- package/tooling/.automation/overrides/templates/maintainer/README.md +27 -0
- package/tooling/.automation/overrides/templates/maintainer/devops-maintainer.yaml +113 -0
- package/tooling/.automation/overrides/templates/maintainer/legacy-steward.yaml +94 -0
- package/tooling/.automation/overrides/templates/maintainer/oss-maintainer.yaml +94 -0
- package/tooling/.automation/overrides/templates/pm/README.md +27 -0
- package/tooling/.automation/overrides/templates/pm/agile-pm.yaml +91 -0
- package/tooling/.automation/overrides/templates/pm/hybrid-delivery.yaml +87 -0
- package/tooling/.automation/overrides/templates/pm/traditional-pm.yaml +91 -0
- package/tooling/.automation/overrides/templates/reviewer/README.md +11 -0
- package/tooling/.automation/overrides/templates/reviewer/mentoring-reviewer.yaml +45 -0
- package/tooling/.automation/overrides/templates/reviewer/quick-sanity.yaml +50 -0
- package/tooling/.automation/overrides/templates/reviewer/thorough-critic.yaml +48 -0
- package/tooling/.automation/overrides/templates/sm/README.md +11 -0
- package/tooling/.automation/overrides/templates/sm/agile-coach.yaml +52 -0
- package/tooling/.automation/overrides/templates/sm/startup-pm.yaml +50 -0
- package/tooling/.automation/overrides/templates/sm/technical-lead.yaml +47 -0
- package/tooling/.automation/overrides/templates/user-profile.template.yaml +62 -0
- package/tooling/.automation/overrides/templates/writer/README.md +27 -0
- package/tooling/.automation/overrides/templates/writer/api-documentarian.yaml +99 -0
- package/tooling/.automation/overrides/templates/writer/docs-as-code.yaml +108 -0
- package/tooling/.automation/overrides/templates/writer/user-guide-author.yaml +100 -0
- package/tooling/completions/DevflowCompletion.ps1 +213 -0
- package/tooling/completions/_run-story +116 -0
- package/tooling/completions/run-story-completion.bash +136 -0
- package/tooling/docs/DOC-STANDARD.md +717 -0
- package/tooling/docs/sprint-status.yaml.template +24 -0
- package/tooling/docs/templates/bug-report.md +234 -0
- package/tooling/docs/templates/migration-spec.md +274 -0
- package/tooling/docs/templates/refactor-spec.md +86 -0
- package/tooling/docs/templates/tech-debt.md +86 -0
- package/tooling/scripts/context_checkpoint.py +556 -0
- package/tooling/scripts/cost_dashboard.py +617 -0
- package/tooling/scripts/create-persona.py +690 -0
- package/tooling/scripts/create-persona.sh +435 -0
- package/tooling/scripts/init-project-workflow.ps1 +651 -0
- package/tooling/scripts/init-project-workflow.py +70 -0
- package/tooling/scripts/init-project-workflow.sh +746 -0
- package/tooling/scripts/lib/__init__.py +35 -0
- package/tooling/scripts/lib/agent_handoff.py +526 -0
- package/tooling/scripts/lib/agent_router.py +698 -0
- package/tooling/scripts/lib/checkpoint-integration.ps1 +245 -0
- package/tooling/scripts/lib/checkpoint-integration.sh +191 -0
- package/tooling/scripts/lib/claude-cli.ps1 +952 -0
- package/tooling/scripts/lib/claude-cli.sh +1293 -0
- package/tooling/scripts/lib/cost_config.py +222 -0
- package/tooling/scripts/lib/cost_display.py +443 -0
- package/tooling/scripts/lib/cost_tracker.py +710 -0
- package/tooling/scripts/lib/currency_converter.py +328 -0
- package/tooling/scripts/lib/errors.py +438 -0
- package/tooling/scripts/lib/override-loader.sh +286 -0
- package/tooling/scripts/lib/pair_programming.py +589 -0
- package/tooling/scripts/lib/shared_memory.py +637 -0
- package/tooling/scripts/lib/swarm_orchestrator.py +689 -0
- package/tooling/scripts/memory_summarize.py +324 -0
- package/tooling/scripts/new-doc.ps1 +405 -0
- package/tooling/scripts/new-doc.py +93 -0
- package/tooling/scripts/new-doc.sh +534 -0
- package/tooling/scripts/personalize_agent.py +385 -0
- package/tooling/scripts/rollback-migration.sh +540 -0
- package/tooling/scripts/run-collab.ps1 +251 -0
- package/tooling/scripts/run-collab.py +605 -0
- package/tooling/scripts/run-collab.sh +110 -0
- package/tooling/scripts/run-story.ps1 +490 -0
- package/tooling/scripts/run-story.py +387 -0
- package/tooling/scripts/run-story.sh +467 -0
- package/tooling/scripts/setup-checkpoint-service.ps1 +219 -0
- package/tooling/scripts/setup-checkpoint-service.py +87 -0
- package/tooling/scripts/setup-checkpoint-service.sh +236 -0
- package/tooling/scripts/tech-debt-tracker.py +608 -0
- package/tooling/scripts/update_version.py +244 -0
- package/tooling/scripts/validate-overrides.py +511 -0
- package/tooling/scripts/validate-overrides.sh +432 -0
- package/tooling/scripts/validate_setup.py +539 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Error Handling - Enhanced error messages for better debugging.
|
|
4
|
+
|
|
5
|
+
Provides user-friendly error messages with context and suggested fixes.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from lib.errors import CostTrackingError, handle_error
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
# ... code ...
|
|
12
|
+
except Exception as e:
|
|
13
|
+
handle_error(e, context="loading session")
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import sys
|
|
18
|
+
import traceback
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from enum import Enum
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any, Optional
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ErrorCode(Enum):
|
|
26
|
+
"""Error codes for categorizing issues."""
|
|
27
|
+
|
|
28
|
+
# Configuration errors (1xx)
|
|
29
|
+
CONFIG_NOT_FOUND = 101
|
|
30
|
+
CONFIG_PARSE_ERROR = 102
|
|
31
|
+
CONFIG_INVALID_VALUE = 103
|
|
32
|
+
|
|
33
|
+
# File/storage errors (2xx)
|
|
34
|
+
FILE_NOT_FOUND = 201
|
|
35
|
+
FILE_READ_ERROR = 202
|
|
36
|
+
FILE_WRITE_ERROR = 203
|
|
37
|
+
DIRECTORY_NOT_FOUND = 204
|
|
38
|
+
PERMISSION_DENIED = 205
|
|
39
|
+
|
|
40
|
+
# Session errors (3xx)
|
|
41
|
+
SESSION_NOT_FOUND = 301
|
|
42
|
+
SESSION_CORRUPTED = 302
|
|
43
|
+
SESSION_SAVE_FAILED = 303
|
|
44
|
+
|
|
45
|
+
# Budget errors (4xx)
|
|
46
|
+
BUDGET_EXCEEDED = 401
|
|
47
|
+
BUDGET_INVALID = 402
|
|
48
|
+
|
|
49
|
+
# Calculation errors (5xx)
|
|
50
|
+
UNKNOWN_MODEL = 501
|
|
51
|
+
INVALID_TOKENS = 502
|
|
52
|
+
CALCULATION_ERROR = 503
|
|
53
|
+
|
|
54
|
+
# General errors (9xx)
|
|
55
|
+
UNKNOWN_ERROR = 999
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class ErrorContext:
|
|
60
|
+
"""Context information for an error."""
|
|
61
|
+
|
|
62
|
+
operation: str
|
|
63
|
+
file_path: Optional[Path] = None
|
|
64
|
+
model: Optional[str] = None
|
|
65
|
+
agent: Optional[str] = None
|
|
66
|
+
tokens: Optional[dict[str, int]] = None
|
|
67
|
+
budget: Optional[float] = None
|
|
68
|
+
additional: Optional[dict[str, Any]] = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class CostTrackingError(Exception):
|
|
72
|
+
"""Base exception for cost tracking errors."""
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
message: str,
|
|
77
|
+
code: ErrorCode = ErrorCode.UNKNOWN_ERROR,
|
|
78
|
+
context: Optional[ErrorContext] = None,
|
|
79
|
+
suggestion: Optional[str] = None,
|
|
80
|
+
cause: Optional[Exception] = None,
|
|
81
|
+
):
|
|
82
|
+
self.message = message
|
|
83
|
+
self.code = code
|
|
84
|
+
self.context = context
|
|
85
|
+
self.suggestion = suggestion
|
|
86
|
+
self.cause = cause
|
|
87
|
+
super().__init__(self.format_message())
|
|
88
|
+
|
|
89
|
+
def format_message(self) -> str:
|
|
90
|
+
"""Format the error message with context."""
|
|
91
|
+
parts = [f"[{self.code.name}] {self.message}"]
|
|
92
|
+
|
|
93
|
+
if self.context:
|
|
94
|
+
parts.append(f"\n Operation: {self.context.operation}")
|
|
95
|
+
if self.context.file_path:
|
|
96
|
+
parts.append(f" File: {self.context.file_path}")
|
|
97
|
+
if self.context.model:
|
|
98
|
+
parts.append(f" Model: {self.context.model}")
|
|
99
|
+
if self.context.agent:
|
|
100
|
+
parts.append(f" Agent: {self.context.agent}")
|
|
101
|
+
|
|
102
|
+
if self.suggestion:
|
|
103
|
+
parts.append(f"\n 💡 Suggestion: {self.suggestion}")
|
|
104
|
+
|
|
105
|
+
if self.cause:
|
|
106
|
+
parts.append(f"\n Caused by: {type(self.cause).__name__}: {self.cause}")
|
|
107
|
+
|
|
108
|
+
return "\n".join(parts)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class ConfigurationError(CostTrackingError):
|
|
112
|
+
"""Configuration-related errors."""
|
|
113
|
+
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class SessionError(CostTrackingError):
|
|
118
|
+
"""Session management errors."""
|
|
119
|
+
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class BudgetError(CostTrackingError):
|
|
124
|
+
"""Budget-related errors."""
|
|
125
|
+
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class CalculationError(CostTrackingError):
|
|
130
|
+
"""Calculation-related errors."""
|
|
131
|
+
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# Error message templates with suggestions
|
|
136
|
+
ERROR_MESSAGES = {
|
|
137
|
+
ErrorCode.CONFIG_NOT_FOUND: {
|
|
138
|
+
"message": "Configuration file not found",
|
|
139
|
+
"suggestion": "Create a config.json file or set environment variables. Run 'python validate_setup.py' for guidance.",
|
|
140
|
+
},
|
|
141
|
+
ErrorCode.CONFIG_PARSE_ERROR: {
|
|
142
|
+
"message": "Failed to parse configuration file",
|
|
143
|
+
"suggestion": "Check that your config.json is valid JSON. Use a JSON validator to find syntax errors.",
|
|
144
|
+
},
|
|
145
|
+
ErrorCode.CONFIG_INVALID_VALUE: {
|
|
146
|
+
"message": "Invalid configuration value",
|
|
147
|
+
"suggestion": "Check the configuration documentation for valid values and ranges.",
|
|
148
|
+
},
|
|
149
|
+
ErrorCode.FILE_NOT_FOUND: {
|
|
150
|
+
"message": "Required file not found",
|
|
151
|
+
"suggestion": "Ensure the file exists and the path is correct. Run 'python validate_setup.py' to check your setup.",
|
|
152
|
+
},
|
|
153
|
+
ErrorCode.FILE_READ_ERROR: {
|
|
154
|
+
"message": "Failed to read file",
|
|
155
|
+
"suggestion": "Check file permissions and ensure the file is not corrupted or locked by another process.",
|
|
156
|
+
},
|
|
157
|
+
ErrorCode.FILE_WRITE_ERROR: {
|
|
158
|
+
"message": "Failed to write file",
|
|
159
|
+
"suggestion": "Check directory permissions and ensure there's sufficient disk space.",
|
|
160
|
+
},
|
|
161
|
+
ErrorCode.DIRECTORY_NOT_FOUND: {
|
|
162
|
+
"message": "Required directory not found",
|
|
163
|
+
"suggestion": "The directory will be created automatically. If this persists, check your file system permissions.",
|
|
164
|
+
},
|
|
165
|
+
ErrorCode.PERMISSION_DENIED: {
|
|
166
|
+
"message": "Permission denied",
|
|
167
|
+
"suggestion": "Check file/directory permissions. On Unix, try: chmod 755 <path>",
|
|
168
|
+
},
|
|
169
|
+
ErrorCode.SESSION_NOT_FOUND: {
|
|
170
|
+
"message": "Session not found",
|
|
171
|
+
"suggestion": "The session may have expired or been deleted. Start a new tracking session.",
|
|
172
|
+
},
|
|
173
|
+
ErrorCode.SESSION_CORRUPTED: {
|
|
174
|
+
"message": "Session data is corrupted",
|
|
175
|
+
"suggestion": "The session file may be damaged. Remove the corrupted file and start a new session.",
|
|
176
|
+
},
|
|
177
|
+
ErrorCode.SESSION_SAVE_FAILED: {
|
|
178
|
+
"message": "Failed to save session",
|
|
179
|
+
"suggestion": "Check disk space and permissions. The session data is preserved in memory.",
|
|
180
|
+
},
|
|
181
|
+
ErrorCode.BUDGET_EXCEEDED: {
|
|
182
|
+
"message": "Budget limit exceeded",
|
|
183
|
+
"suggestion": "Increase the budget limit or optimize token usage. Consider using a cheaper model.",
|
|
184
|
+
},
|
|
185
|
+
ErrorCode.BUDGET_INVALID: {
|
|
186
|
+
"message": "Invalid budget value",
|
|
187
|
+
"suggestion": "Budget must be a positive number. Set to 0 for no limit.",
|
|
188
|
+
},
|
|
189
|
+
ErrorCode.UNKNOWN_MODEL: {
|
|
190
|
+
"message": "Unknown model specified",
|
|
191
|
+
"suggestion": "Use a supported model: opus, sonnet, haiku. Defaulting to sonnet pricing.",
|
|
192
|
+
},
|
|
193
|
+
ErrorCode.INVALID_TOKENS: {
|
|
194
|
+
"message": "Invalid token count",
|
|
195
|
+
"suggestion": "Token counts must be non-negative integers.",
|
|
196
|
+
},
|
|
197
|
+
ErrorCode.CALCULATION_ERROR: {
|
|
198
|
+
"message": "Error calculating cost",
|
|
199
|
+
"suggestion": "Check input values. Ensure token counts are valid numbers.",
|
|
200
|
+
},
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def create_error(
|
|
205
|
+
code: ErrorCode,
|
|
206
|
+
context: Optional[ErrorContext] = None,
|
|
207
|
+
custom_message: Optional[str] = None,
|
|
208
|
+
cause: Optional[Exception] = None,
|
|
209
|
+
) -> CostTrackingError:
|
|
210
|
+
"""Create an error with appropriate type and message."""
|
|
211
|
+
template = ERROR_MESSAGES.get(code, {"message": "Unknown error", "suggestion": None})
|
|
212
|
+
message = custom_message or template["message"]
|
|
213
|
+
suggestion = template.get("suggestion")
|
|
214
|
+
|
|
215
|
+
# Select appropriate error class
|
|
216
|
+
if code.value < 200:
|
|
217
|
+
error_class = ConfigurationError
|
|
218
|
+
elif code.value < 400:
|
|
219
|
+
error_class = SessionError
|
|
220
|
+
elif code.value < 500:
|
|
221
|
+
error_class = BudgetError
|
|
222
|
+
elif code.value < 600:
|
|
223
|
+
error_class = CalculationError
|
|
224
|
+
else:
|
|
225
|
+
error_class = CostTrackingError
|
|
226
|
+
|
|
227
|
+
return error_class(
|
|
228
|
+
message=message, code=code, context=context, suggestion=suggestion, cause=cause
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def format_error_for_user(error: Exception, verbose: bool = False) -> str:
|
|
233
|
+
"""Format an error for user-friendly display."""
|
|
234
|
+
lines = []
|
|
235
|
+
|
|
236
|
+
# Header
|
|
237
|
+
lines.append("━" * 60)
|
|
238
|
+
lines.append("❌ Error Occurred")
|
|
239
|
+
lines.append("━" * 60)
|
|
240
|
+
|
|
241
|
+
if isinstance(error, CostTrackingError):
|
|
242
|
+
lines.append(f"\n{error.format_message()}")
|
|
243
|
+
else:
|
|
244
|
+
lines.append(f"\n{type(error).__name__}: {error}")
|
|
245
|
+
|
|
246
|
+
if verbose:
|
|
247
|
+
lines.append("\n📋 Stack Trace:")
|
|
248
|
+
lines.append(traceback.format_exc())
|
|
249
|
+
|
|
250
|
+
lines.append("\n" + "━" * 60)
|
|
251
|
+
|
|
252
|
+
return "\n".join(lines)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def handle_error(
|
|
256
|
+
error: Exception,
|
|
257
|
+
context: str = "unknown operation",
|
|
258
|
+
exit_on_error: bool = False,
|
|
259
|
+
verbose: bool = False,
|
|
260
|
+
) -> None:
|
|
261
|
+
"""
|
|
262
|
+
Handle an error with user-friendly output.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
error: The exception that occurred
|
|
266
|
+
context: Description of what was being done
|
|
267
|
+
exit_on_error: Whether to exit the program
|
|
268
|
+
verbose: Whether to show stack trace
|
|
269
|
+
"""
|
|
270
|
+
# Create context if not a CostTrackingError
|
|
271
|
+
if not isinstance(error, CostTrackingError):
|
|
272
|
+
error_context = ErrorContext(operation=context)
|
|
273
|
+
|
|
274
|
+
# Try to determine error type
|
|
275
|
+
if isinstance(error, FileNotFoundError):
|
|
276
|
+
error = create_error(ErrorCode.FILE_NOT_FOUND, context=error_context, cause=error)
|
|
277
|
+
elif isinstance(error, PermissionError):
|
|
278
|
+
error = create_error(ErrorCode.PERMISSION_DENIED, context=error_context, cause=error)
|
|
279
|
+
elif isinstance(error, json.JSONDecodeError):
|
|
280
|
+
error = create_error(ErrorCode.CONFIG_PARSE_ERROR, context=error_context, cause=error)
|
|
281
|
+
|
|
282
|
+
# Output error
|
|
283
|
+
print(format_error_for_user(error, verbose=verbose), file=sys.stderr)
|
|
284
|
+
|
|
285
|
+
if exit_on_error:
|
|
286
|
+
sys.exit(1)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def wrap_errors(operation: str):
|
|
290
|
+
"""
|
|
291
|
+
Decorator to wrap function errors with context.
|
|
292
|
+
|
|
293
|
+
Usage:
|
|
294
|
+
@wrap_errors("loading session")
|
|
295
|
+
def load_session(path):
|
|
296
|
+
...
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
def decorator(func):
|
|
300
|
+
def wrapper(*args, **kwargs):
|
|
301
|
+
try:
|
|
302
|
+
return func(*args, **kwargs)
|
|
303
|
+
except CostTrackingError:
|
|
304
|
+
raise
|
|
305
|
+
except Exception as e:
|
|
306
|
+
context = ErrorContext(operation=operation)
|
|
307
|
+
raise create_error(
|
|
308
|
+
ErrorCode.UNKNOWN_ERROR, context=context, custom_message=str(e), cause=e
|
|
309
|
+
) from e
|
|
310
|
+
|
|
311
|
+
return wrapper
|
|
312
|
+
|
|
313
|
+
return decorator
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class ErrorReporter:
|
|
317
|
+
"""Collect and report multiple errors."""
|
|
318
|
+
|
|
319
|
+
def __init__(self):
|
|
320
|
+
self.errors: list = []
|
|
321
|
+
self.warnings: list = []
|
|
322
|
+
|
|
323
|
+
def add_error(self, error: Exception, context: str = ""):
|
|
324
|
+
"""Add an error to the collection."""
|
|
325
|
+
self.errors.append((error, context))
|
|
326
|
+
|
|
327
|
+
def add_warning(self, message: str, context: str = ""):
|
|
328
|
+
"""Add a warning to the collection."""
|
|
329
|
+
self.warnings.append((message, context))
|
|
330
|
+
|
|
331
|
+
def has_errors(self) -> bool:
|
|
332
|
+
"""Check if any errors were recorded."""
|
|
333
|
+
return len(self.errors) > 0
|
|
334
|
+
|
|
335
|
+
def has_warnings(self) -> bool:
|
|
336
|
+
"""Check if any warnings were recorded."""
|
|
337
|
+
return len(self.warnings) > 0
|
|
338
|
+
|
|
339
|
+
def format_report(self) -> str:
|
|
340
|
+
"""Format all errors and warnings as a report."""
|
|
341
|
+
lines = []
|
|
342
|
+
|
|
343
|
+
if self.errors:
|
|
344
|
+
lines.append(f"\n❌ {len(self.errors)} Error(s):")
|
|
345
|
+
for i, (error, context) in enumerate(self.errors, 1):
|
|
346
|
+
lines.append(f" {i}. [{context}] {error}")
|
|
347
|
+
|
|
348
|
+
if self.warnings:
|
|
349
|
+
lines.append(f"\n⚠️ {len(self.warnings)} Warning(s):")
|
|
350
|
+
for i, (message, context) in enumerate(self.warnings, 1):
|
|
351
|
+
lines.append(f" {i}. [{context}] {message}")
|
|
352
|
+
|
|
353
|
+
return "\n".join(lines)
|
|
354
|
+
|
|
355
|
+
def print_report(self):
|
|
356
|
+
"""Print the error report."""
|
|
357
|
+
if self.errors or self.warnings:
|
|
358
|
+
print(self.format_report(), file=sys.stderr)
|
|
359
|
+
|
|
360
|
+
def clear(self):
|
|
361
|
+
"""Clear all errors and warnings."""
|
|
362
|
+
self.errors.clear()
|
|
363
|
+
self.warnings.clear()
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
# Logging helpers
|
|
367
|
+
def log_debug(message: str, **kwargs):
|
|
368
|
+
"""Log a debug message (only in verbose mode)."""
|
|
369
|
+
if _verbose_mode:
|
|
370
|
+
extra = " ".join(f"{k}={v}" for k, v in kwargs.items())
|
|
371
|
+
print(f"[DEBUG] {message} {extra}", file=sys.stderr)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def log_info(message: str):
|
|
375
|
+
"""Log an info message."""
|
|
376
|
+
print(f"ℹ️ {message}")
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def log_warning(message: str):
|
|
380
|
+
"""Log a warning message."""
|
|
381
|
+
print(f"⚠️ {message}", file=sys.stderr)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def log_error(message: str):
|
|
385
|
+
"""Log an error message."""
|
|
386
|
+
print(f"❌ {message}", file=sys.stderr)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def log_success(message: str):
|
|
390
|
+
"""Log a success message."""
|
|
391
|
+
print(f"✅ {message}")
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
# Verbose mode flag
|
|
395
|
+
_verbose_mode = False
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def set_verbose(enabled: bool):
|
|
399
|
+
"""Enable or disable verbose mode."""
|
|
400
|
+
global _verbose_mode
|
|
401
|
+
_verbose_mode = enabled
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def is_verbose() -> bool:
|
|
405
|
+
"""Check if verbose mode is enabled."""
|
|
406
|
+
return _verbose_mode
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
if __name__ == "__main__":
|
|
410
|
+
# Demo error handling
|
|
411
|
+
print("Error Handling Demo\n")
|
|
412
|
+
|
|
413
|
+
# Create and display different error types
|
|
414
|
+
errors = [
|
|
415
|
+
create_error(
|
|
416
|
+
ErrorCode.BUDGET_EXCEEDED,
|
|
417
|
+
context=ErrorContext(
|
|
418
|
+
operation="logging usage", model="opus", agent="DEV", budget=15.00
|
|
419
|
+
),
|
|
420
|
+
),
|
|
421
|
+
create_error(
|
|
422
|
+
ErrorCode.SESSION_CORRUPTED,
|
|
423
|
+
context=ErrorContext(
|
|
424
|
+
operation="loading session", file_path=Path("/path/to/session.json")
|
|
425
|
+
),
|
|
426
|
+
),
|
|
427
|
+
create_error(
|
|
428
|
+
ErrorCode.UNKNOWN_MODEL,
|
|
429
|
+
context=ErrorContext(
|
|
430
|
+
operation="calculating cost",
|
|
431
|
+
model="gpt-4", # Wrong model
|
|
432
|
+
),
|
|
433
|
+
),
|
|
434
|
+
]
|
|
435
|
+
|
|
436
|
+
for error in errors:
|
|
437
|
+
print(format_error_for_user(error))
|
|
438
|
+
print()
|