git-aware-coding-agent 1.0.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.
- avos_cli/__init__.py +3 -0
- avos_cli/agents/avos_ask_agent.md +47 -0
- avos_cli/agents/avos_ask_agent_JSON_converter.md +78 -0
- avos_cli/agents/avos_hisotry_agent_JSON_converter.md +92 -0
- avos_cli/agents/avos_history_agent.md +58 -0
- avos_cli/agents/git_diff_agent.md +63 -0
- avos_cli/artifacts/__init__.py +17 -0
- avos_cli/artifacts/base.py +47 -0
- avos_cli/artifacts/commit_builder.py +35 -0
- avos_cli/artifacts/doc_builder.py +30 -0
- avos_cli/artifacts/issue_builder.py +37 -0
- avos_cli/artifacts/pr_builder.py +50 -0
- avos_cli/cli/__init__.py +1 -0
- avos_cli/cli/main.py +504 -0
- avos_cli/commands/__init__.py +1 -0
- avos_cli/commands/ask.py +541 -0
- avos_cli/commands/connect.py +363 -0
- avos_cli/commands/history.py +549 -0
- avos_cli/commands/hook_install.py +260 -0
- avos_cli/commands/hook_sync.py +231 -0
- avos_cli/commands/ingest.py +506 -0
- avos_cli/commands/ingest_pr.py +239 -0
- avos_cli/config/__init__.py +1 -0
- avos_cli/config/hash_store.py +93 -0
- avos_cli/config/lock.py +122 -0
- avos_cli/config/manager.py +180 -0
- avos_cli/config/state.py +90 -0
- avos_cli/exceptions.py +272 -0
- avos_cli/models/__init__.py +58 -0
- avos_cli/models/api.py +75 -0
- avos_cli/models/artifacts.py +99 -0
- avos_cli/models/config.py +56 -0
- avos_cli/models/diff.py +117 -0
- avos_cli/models/query.py +234 -0
- avos_cli/parsers/__init__.py +21 -0
- avos_cli/parsers/artifact_ref_extractor.py +173 -0
- avos_cli/parsers/reference_parser.py +117 -0
- avos_cli/services/__init__.py +1 -0
- avos_cli/services/chronology_service.py +68 -0
- avos_cli/services/citation_validator.py +134 -0
- avos_cli/services/context_budget_service.py +104 -0
- avos_cli/services/diff_resolver.py +398 -0
- avos_cli/services/diff_summary_service.py +141 -0
- avos_cli/services/git_client.py +351 -0
- avos_cli/services/github_client.py +443 -0
- avos_cli/services/llm_client.py +312 -0
- avos_cli/services/memory_client.py +323 -0
- avos_cli/services/query_fallback_formatter.py +108 -0
- avos_cli/services/reply_output_service.py +341 -0
- avos_cli/services/sanitization_service.py +218 -0
- avos_cli/utils/__init__.py +1 -0
- avos_cli/utils/dotenv_load.py +50 -0
- avos_cli/utils/hashing.py +22 -0
- avos_cli/utils/logger.py +77 -0
- avos_cli/utils/output.py +232 -0
- avos_cli/utils/sanitization_diagnostics.py +81 -0
- avos_cli/utils/time_helpers.py +56 -0
- git_aware_coding_agent-1.0.0.dist-info/METADATA +390 -0
- git_aware_coding_agent-1.0.0.dist-info/RECORD +62 -0
- git_aware_coding_agent-1.0.0.dist-info/WHEEL +4 -0
- git_aware_coding_agent-1.0.0.dist-info/entry_points.txt +2 -0
- git_aware_coding_agent-1.0.0.dist-info/licenses/LICENSE +201 -0
avos_cli/config/state.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Atomic file I/O and corruption handling for .avos state files.
|
|
2
|
+
|
|
3
|
+
Provides safe write (temp + fsync + rename) and safe read (with
|
|
4
|
+
corruption detection and quarantine) for JSON state files.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import contextlib
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import tempfile
|
|
13
|
+
import time
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from avos_cli.utils.logger import get_logger
|
|
17
|
+
|
|
18
|
+
_log = get_logger("config.state")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def atomic_write(path: Path, content: str, permissions: int = 0o600) -> None:
|
|
22
|
+
"""Write content to a file atomically using temp+fsync+rename.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
path: Target file path.
|
|
26
|
+
content: String content to write.
|
|
27
|
+
permissions: File permission mode (default 0o600 for secrets).
|
|
28
|
+
"""
|
|
29
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
fd, tmp_path = tempfile.mkstemp(
|
|
31
|
+
dir=str(path.parent),
|
|
32
|
+
prefix=f".{path.name}.",
|
|
33
|
+
suffix=".tmp",
|
|
34
|
+
)
|
|
35
|
+
try:
|
|
36
|
+
os.write(fd, content.encode("utf-8"))
|
|
37
|
+
os.fsync(fd)
|
|
38
|
+
os.close(fd)
|
|
39
|
+
os.chmod(tmp_path, permissions)
|
|
40
|
+
os.replace(tmp_path, str(path))
|
|
41
|
+
except BaseException:
|
|
42
|
+
os.close(fd) if not _is_fd_closed(fd) else None
|
|
43
|
+
with contextlib.suppress(OSError):
|
|
44
|
+
os.unlink(tmp_path)
|
|
45
|
+
raise
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _is_fd_closed(fd: int) -> bool:
|
|
49
|
+
"""Check if a file descriptor is already closed."""
|
|
50
|
+
try:
|
|
51
|
+
os.fstat(fd)
|
|
52
|
+
return False
|
|
53
|
+
except OSError:
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def read_json_safe(path: Path) -> dict[str, object] | None:
|
|
58
|
+
"""Read and parse a JSON file, quarantining corrupt files.
|
|
59
|
+
|
|
60
|
+
If the file doesn't exist, returns None.
|
|
61
|
+
If the file contains invalid JSON, it is renamed to
|
|
62
|
+
`<name>.corrupt.<timestamp>` and None is returned.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
path: Path to the JSON file.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Parsed dict or None if missing/corrupt.
|
|
69
|
+
"""
|
|
70
|
+
if not path.exists():
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
content = path.read_text(encoding="utf-8")
|
|
75
|
+
data: dict[str, object] = json.loads(content)
|
|
76
|
+
return data
|
|
77
|
+
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
78
|
+
_quarantine(path)
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _quarantine(path: Path) -> None:
|
|
83
|
+
"""Move a corrupt file to a .corrupt.<timestamp> backup."""
|
|
84
|
+
ts = int(time.time())
|
|
85
|
+
corrupt_path = path.with_suffix(f"{path.suffix}.corrupt.{ts}")
|
|
86
|
+
try:
|
|
87
|
+
path.rename(corrupt_path)
|
|
88
|
+
_log.warning("Quarantined corrupt file: %s -> %s", path, corrupt_path)
|
|
89
|
+
except OSError as e:
|
|
90
|
+
_log.error("Failed to quarantine corrupt file %s: %s", path, e)
|
avos_cli/exceptions.py
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""Exception hierarchy and error codes for the AVOS CLI.
|
|
2
|
+
|
|
3
|
+
All exceptions inherit from AvosError. Each exception carries a machine-readable
|
|
4
|
+
error code, a human-readable message, and an optional action hint for the user.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from enum import Enum
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ErrorCode(str, Enum):
|
|
13
|
+
"""Machine-readable error codes for structured error handling."""
|
|
14
|
+
|
|
15
|
+
CONFIG_NOT_INITIALIZED = "CONFIG_NOT_INITIALIZED"
|
|
16
|
+
CONFIG_VALIDATION_ERROR = "CONFIG_VALIDATION_ERROR"
|
|
17
|
+
REPOSITORY_CONTEXT_ERROR = "REPOSITORY_CONTEXT_ERROR"
|
|
18
|
+
AUTH_ERROR = "AUTH_ERROR"
|
|
19
|
+
RATE_LIMIT_ERROR = "RATE_LIMIT_ERROR"
|
|
20
|
+
UPSTREAM_UNAVAILABLE = "UPSTREAM_UNAVAILABLE"
|
|
21
|
+
REQUEST_CONTRACT_ERROR = "REQUEST_CONTRACT_ERROR"
|
|
22
|
+
DEPENDENCY_UNAVAILABLE = "DEPENDENCY_UNAVAILABLE"
|
|
23
|
+
SERVICE_PARSE_ERROR = "SERVICE_PARSE_ERROR"
|
|
24
|
+
ARTIFACT_BUILD_ERROR = "ARTIFACT_BUILD_ERROR"
|
|
25
|
+
RESOURCE_NOT_FOUND = "RESOURCE_NOT_FOUND"
|
|
26
|
+
STATE_FILE_CONFLICT = "STATE_FILE_CONFLICT"
|
|
27
|
+
INGEST_LOCK_CONFLICT = "INGEST_LOCK_CONFLICT"
|
|
28
|
+
SANITIZATION_FAILED = "SANITIZATION_FAILED"
|
|
29
|
+
GROUNDING_FAILED = "GROUNDING_FAILED"
|
|
30
|
+
LLM_SYNTHESIS_ERROR = "LLM_SYNTHESIS_ERROR"
|
|
31
|
+
CONTEXT_BUDGET_ERROR = "CONTEXT_BUDGET_ERROR"
|
|
32
|
+
QUERY_EMPTY_RESULT = "QUERY_EMPTY_RESULT"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AvosError(Exception):
|
|
36
|
+
"""Base exception for all AVOS CLI errors.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
message: Human-readable error description.
|
|
40
|
+
code: Machine-readable error code from ErrorCode enum.
|
|
41
|
+
hint: Optional action hint for the user.
|
|
42
|
+
retryable: Whether the operation can be retried.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
message: str,
|
|
48
|
+
code: ErrorCode,
|
|
49
|
+
hint: str | None = None,
|
|
50
|
+
retryable: bool = False,
|
|
51
|
+
) -> None:
|
|
52
|
+
self.code = code
|
|
53
|
+
self.hint = hint
|
|
54
|
+
self.retryable = retryable
|
|
55
|
+
super().__init__(message)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ConfigurationNotInitializedError(AvosError):
|
|
59
|
+
"""Raised when .avos/config.json is missing or memory_id is not set."""
|
|
60
|
+
|
|
61
|
+
def __init__(self, message: str | None = None) -> None:
|
|
62
|
+
super().__init__(
|
|
63
|
+
message=message or "Repository not connected. No .avos/config.json found.",
|
|
64
|
+
code=ErrorCode.CONFIG_NOT_INITIALIZED,
|
|
65
|
+
hint="Run 'avos connect <org/repo>' to initialize this repository.",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ConfigurationValidationError(AvosError):
|
|
70
|
+
"""Raised when config file exists but contains invalid data."""
|
|
71
|
+
|
|
72
|
+
def __init__(self, message: str) -> None:
|
|
73
|
+
super().__init__(
|
|
74
|
+
message=message,
|
|
75
|
+
code=ErrorCode.CONFIG_VALIDATION_ERROR,
|
|
76
|
+
hint="Inspect and fix .avos/config.json, or re-run 'avos connect'.",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class RepositoryContextError(AvosError):
|
|
81
|
+
"""Raised when the current directory is not inside a valid Git repository."""
|
|
82
|
+
|
|
83
|
+
def __init__(self, message: str | None = None) -> None:
|
|
84
|
+
super().__init__(
|
|
85
|
+
message=message or "Not inside a Git repository.",
|
|
86
|
+
code=ErrorCode.REPOSITORY_CONTEXT_ERROR,
|
|
87
|
+
hint="Run this command from within a Git repository.",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class AuthError(AvosError):
|
|
92
|
+
"""Raised when API authentication fails."""
|
|
93
|
+
|
|
94
|
+
def __init__(self, message: str, service: str = "API") -> None:
|
|
95
|
+
super().__init__(
|
|
96
|
+
message=message,
|
|
97
|
+
code=ErrorCode.AUTH_ERROR,
|
|
98
|
+
hint=f"Verify your {service} credentials are correct and have required permissions.",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class RateLimitError(AvosError):
|
|
103
|
+
"""Raised when API rate limit is exhausted."""
|
|
104
|
+
|
|
105
|
+
def __init__(self, message: str, retry_after: float | None = None) -> None:
|
|
106
|
+
hint = "Rate limit exceeded."
|
|
107
|
+
if retry_after is not None:
|
|
108
|
+
hint = f"Rate limit exceeded. Retry after {retry_after:.0f} seconds."
|
|
109
|
+
super().__init__(
|
|
110
|
+
message=message,
|
|
111
|
+
code=ErrorCode.RATE_LIMIT_ERROR,
|
|
112
|
+
hint=hint,
|
|
113
|
+
retryable=True,
|
|
114
|
+
)
|
|
115
|
+
self.retry_after = retry_after
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class UpstreamUnavailableError(AvosError):
|
|
119
|
+
"""Raised when an external service is unreachable after retries."""
|
|
120
|
+
|
|
121
|
+
def __init__(self, message: str) -> None:
|
|
122
|
+
super().__init__(
|
|
123
|
+
message=message,
|
|
124
|
+
code=ErrorCode.UPSTREAM_UNAVAILABLE,
|
|
125
|
+
hint="The service may be temporarily unavailable. Try again later.",
|
|
126
|
+
retryable=True,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class RequestContractError(AvosError):
|
|
131
|
+
"""Raised when a request violates the API contract."""
|
|
132
|
+
|
|
133
|
+
def __init__(self, message: str) -> None:
|
|
134
|
+
super().__init__(
|
|
135
|
+
message=message,
|
|
136
|
+
code=ErrorCode.REQUEST_CONTRACT_ERROR,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class ResourceNotFoundError(AvosError):
|
|
141
|
+
"""Raised when a requested resource does not exist (404)."""
|
|
142
|
+
|
|
143
|
+
def __init__(self, message: str) -> None:
|
|
144
|
+
super().__init__(
|
|
145
|
+
message=message,
|
|
146
|
+
code=ErrorCode.RESOURCE_NOT_FOUND,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class DependencyUnavailableError(AvosError):
|
|
151
|
+
"""Raised when a required local dependency is missing."""
|
|
152
|
+
|
|
153
|
+
def __init__(self, dependency: str) -> None:
|
|
154
|
+
super().__init__(
|
|
155
|
+
message=f"Required dependency not found: {dependency}",
|
|
156
|
+
code=ErrorCode.DEPENDENCY_UNAVAILABLE,
|
|
157
|
+
hint=f"Install or configure '{dependency}' and ensure it is on your PATH.",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class ServiceParseError(AvosError):
|
|
162
|
+
"""Raised when service output cannot be parsed."""
|
|
163
|
+
|
|
164
|
+
def __init__(self, message: str) -> None:
|
|
165
|
+
super().__init__(
|
|
166
|
+
message=message,
|
|
167
|
+
code=ErrorCode.SERVICE_PARSE_ERROR,
|
|
168
|
+
hint="This may indicate an upstream format change or a parser defect.",
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class ArtifactBuildError(AvosError):
|
|
173
|
+
"""Raised when an artifact cannot be built from its input model."""
|
|
174
|
+
|
|
175
|
+
def __init__(self, message: str) -> None:
|
|
176
|
+
super().__init__(
|
|
177
|
+
message=message,
|
|
178
|
+
code=ErrorCode.ARTIFACT_BUILD_ERROR,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class StateFileConflictError(AvosError):
|
|
183
|
+
"""Raised when a local state file has a concurrent ownership conflict."""
|
|
184
|
+
|
|
185
|
+
def __init__(self, message: str) -> None:
|
|
186
|
+
super().__init__(
|
|
187
|
+
message=message,
|
|
188
|
+
code=ErrorCode.STATE_FILE_CONFLICT,
|
|
189
|
+
hint="Another process may be writing to this file. Wait and retry.",
|
|
190
|
+
retryable=True,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class IngestLockError(AvosError):
|
|
195
|
+
"""Raised when the ingest lock cannot be acquired."""
|
|
196
|
+
|
|
197
|
+
def __init__(self, message: str | None = None, holder_pid: int | None = None) -> None:
|
|
198
|
+
self.holder_pid = holder_pid
|
|
199
|
+
super().__init__(
|
|
200
|
+
message=message or "Another ingest process is running.",
|
|
201
|
+
code=ErrorCode.INGEST_LOCK_CONFLICT,
|
|
202
|
+
hint="Wait for the other ingest to finish, or remove .avos/ingest.lock if stale.",
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class SanitizationError(AvosError):
|
|
207
|
+
"""Raised when sanitization confidence is below threshold.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
message: Description of the sanitization failure.
|
|
211
|
+
confidence_score: The computed confidence score that triggered the failure.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
def __init__(self, message: str, confidence_score: int | None = None) -> None:
|
|
215
|
+
self.confidence_score = confidence_score
|
|
216
|
+
super().__init__(
|
|
217
|
+
message=message,
|
|
218
|
+
code=ErrorCode.SANITIZATION_FAILED,
|
|
219
|
+
hint="Sanitization confidence too low for safe synthesis. Falling back to raw results.",
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class GroundingError(AvosError):
|
|
224
|
+
"""Raised when citation grounding fails minimum threshold.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
message: Description of the grounding failure.
|
|
228
|
+
grounded_count: Number of successfully grounded citations.
|
|
229
|
+
total_count: Total number of citations attempted.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
def __init__(
|
|
233
|
+
self, message: str, grounded_count: int = 0, total_count: int = 0
|
|
234
|
+
) -> None:
|
|
235
|
+
self.grounded_count = grounded_count
|
|
236
|
+
self.total_count = total_count
|
|
237
|
+
super().__init__(
|
|
238
|
+
message=message,
|
|
239
|
+
code=ErrorCode.GROUNDING_FAILED,
|
|
240
|
+
hint="Synthesis citations could not be verified against retrieved artifacts.",
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class LLMSynthesisError(AvosError):
|
|
245
|
+
"""Raised when LLM synthesis fails.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
message: Description of the synthesis failure.
|
|
249
|
+
failure_class: 'transient' (retryable) or 'non_transient' or 'unknown'.
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
def __init__(self, message: str, failure_class: str = "unknown") -> None:
|
|
253
|
+
self.failure_class = failure_class
|
|
254
|
+
retryable = failure_class == "transient"
|
|
255
|
+
super().__init__(
|
|
256
|
+
message=message,
|
|
257
|
+
code=ErrorCode.LLM_SYNTHESIS_ERROR,
|
|
258
|
+
hint="LLM synthesis failed. Falling back to raw search results.",
|
|
259
|
+
retryable=retryable,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class ContextBudgetError(AvosError):
|
|
264
|
+
"""Raised when context budget cannot meet minimum evidence floor."""
|
|
265
|
+
|
|
266
|
+
def __init__(self, message: str) -> None:
|
|
267
|
+
super().__init__(
|
|
268
|
+
message=message,
|
|
269
|
+
code=ErrorCode.CONTEXT_BUDGET_ERROR,
|
|
270
|
+
hint="Insufficient evidence artifacts for synthesis. Using fallback output.",
|
|
271
|
+
)
|
|
272
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Public model re-exports for avos_cli.models.
|
|
2
|
+
|
|
3
|
+
Import key types from here for convenience:
|
|
4
|
+
from avos_cli.models import RepoConfig, PRArtifact, SearchResult
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from avos_cli.models.api import NoteResponse, SearchHit, SearchRequest, SearchResult
|
|
8
|
+
from avos_cli.models.artifacts import (
|
|
9
|
+
CommitArtifact,
|
|
10
|
+
DocArtifact,
|
|
11
|
+
IssueArtifact,
|
|
12
|
+
PRArtifact,
|
|
13
|
+
)
|
|
14
|
+
from avos_cli.models.config import (
|
|
15
|
+
LLMConfig,
|
|
16
|
+
RepoConfig,
|
|
17
|
+
)
|
|
18
|
+
from avos_cli.models.query import (
|
|
19
|
+
BudgetResult,
|
|
20
|
+
FallbackReason,
|
|
21
|
+
GroundedCitation,
|
|
22
|
+
GroundingStatus,
|
|
23
|
+
QueryMode,
|
|
24
|
+
QueryResultEnvelope,
|
|
25
|
+
ReferenceType,
|
|
26
|
+
RetrievedArtifact,
|
|
27
|
+
SanitizationResult,
|
|
28
|
+
SanitizedArtifact,
|
|
29
|
+
SynthesisRequest,
|
|
30
|
+
SynthesisResponse,
|
|
31
|
+
TimelineEvent,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"BudgetResult",
|
|
36
|
+
"CommitArtifact",
|
|
37
|
+
"DocArtifact",
|
|
38
|
+
"FallbackReason",
|
|
39
|
+
"GroundedCitation",
|
|
40
|
+
"GroundingStatus",
|
|
41
|
+
"IssueArtifact",
|
|
42
|
+
"LLMConfig",
|
|
43
|
+
"NoteResponse",
|
|
44
|
+
"PRArtifact",
|
|
45
|
+
"QueryMode",
|
|
46
|
+
"QueryResultEnvelope",
|
|
47
|
+
"ReferenceType",
|
|
48
|
+
"RepoConfig",
|
|
49
|
+
"RetrievedArtifact",
|
|
50
|
+
"SanitizationResult",
|
|
51
|
+
"SanitizedArtifact",
|
|
52
|
+
"SearchHit",
|
|
53
|
+
"SearchRequest",
|
|
54
|
+
"SearchResult",
|
|
55
|
+
"SynthesisRequest",
|
|
56
|
+
"SynthesisResponse",
|
|
57
|
+
"TimelineEvent",
|
|
58
|
+
]
|
avos_cli/models/api.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""API request and response models for the Avos Memory API.
|
|
2
|
+
|
|
3
|
+
These models define the typed contracts for communication with the
|
|
4
|
+
closed-source Avos Memory API (search, add_memory, delete_note).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Literal
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SearchRequest(BaseModel):
|
|
15
|
+
"""Search request payload for the Avos Memory API.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
query: Natural language search query (min 1 char).
|
|
19
|
+
k: Number of results to return (1-50, default 5).
|
|
20
|
+
mode: Search strategy -- 'semantic', 'keyword', or 'hybrid'.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
model_config = ConfigDict(frozen=True)
|
|
24
|
+
|
|
25
|
+
query: str = Field(..., min_length=1)
|
|
26
|
+
k: int = Field(default=5, ge=1, le=50)
|
|
27
|
+
mode: Literal["semantic", "keyword", "hybrid"] = "semantic"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SearchHit(BaseModel):
|
|
31
|
+
"""A single search result from the Avos Memory API.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
note_id: Unique identifier for the matched note.
|
|
35
|
+
content: Full text content of the note.
|
|
36
|
+
created_at: ISO 8601 creation timestamp.
|
|
37
|
+
rank: Relevance rank (1 = best match).
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
model_config = ConfigDict(frozen=True)
|
|
41
|
+
|
|
42
|
+
note_id: str
|
|
43
|
+
content: str
|
|
44
|
+
created_at: str
|
|
45
|
+
rank: int
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class SearchResult(BaseModel):
|
|
49
|
+
"""Search response from the Avos Memory API.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
results: List of search hits ordered by relevance.
|
|
53
|
+
total_count: Total number of notes in the memory.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
model_config = ConfigDict(frozen=True)
|
|
57
|
+
|
|
58
|
+
results: list[SearchHit]
|
|
59
|
+
total_count: int
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class NoteResponse(BaseModel):
|
|
63
|
+
"""Response from add_memory (note creation) in the Avos Memory API.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
note_id: Unique identifier for the created note.
|
|
67
|
+
content: Text content of the note (or auto-generated description for media).
|
|
68
|
+
created_at: ISO 8601 server-side creation timestamp.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
model_config = ConfigDict(frozen=True)
|
|
72
|
+
|
|
73
|
+
note_id: str
|
|
74
|
+
content: str
|
|
75
|
+
created_at: str
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Artifact models for AVOS CLI.
|
|
2
|
+
|
|
3
|
+
These Pydantic models define the input shape for each artifact builder.
|
|
4
|
+
They carry no behavior -- builders consume them to produce structured text.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, ConfigDict
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PRArtifact(BaseModel):
|
|
13
|
+
"""Pull request data for the PRThreadBuilder.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
repo: Repository slug 'org/repo'.
|
|
17
|
+
pr_number: Pull request number.
|
|
18
|
+
title: PR title.
|
|
19
|
+
author: PR author username.
|
|
20
|
+
merged_date: Date the PR was merged (ISO format string, optional).
|
|
21
|
+
files: List of file paths changed in the PR.
|
|
22
|
+
description: PR body/description text.
|
|
23
|
+
discussion: Aggregated comments and review discussion.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
model_config = ConfigDict(frozen=True)
|
|
27
|
+
|
|
28
|
+
repo: str
|
|
29
|
+
pr_number: int
|
|
30
|
+
title: str
|
|
31
|
+
author: str
|
|
32
|
+
merged_date: str | None = None
|
|
33
|
+
files: list[str] = []
|
|
34
|
+
description: str | None = None
|
|
35
|
+
discussion: str | None = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class IssueArtifact(BaseModel):
|
|
39
|
+
"""Issue data for the IssueBuilder.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
repo: Repository slug 'org/repo'.
|
|
43
|
+
issue_number: Issue number.
|
|
44
|
+
title: Issue title.
|
|
45
|
+
labels: List of label strings.
|
|
46
|
+
body: Issue body text.
|
|
47
|
+
comments: List of comment strings.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
model_config = ConfigDict(frozen=True)
|
|
51
|
+
|
|
52
|
+
repo: str
|
|
53
|
+
issue_number: int
|
|
54
|
+
title: str
|
|
55
|
+
labels: list[str] = []
|
|
56
|
+
body: str | None = None
|
|
57
|
+
comments: list[str] = []
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class CommitArtifact(BaseModel):
|
|
61
|
+
"""Commit data for the CommitBuilder.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
repo: Repository slug 'org/repo'.
|
|
65
|
+
hash: Commit hash (short or full).
|
|
66
|
+
message: Commit message.
|
|
67
|
+
author: Commit author name.
|
|
68
|
+
date: Commit date (ISO format string).
|
|
69
|
+
files_changed: List of file paths changed.
|
|
70
|
+
diff_stats: Summary of lines added/removed.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
model_config = ConfigDict(frozen=True)
|
|
74
|
+
|
|
75
|
+
repo: str
|
|
76
|
+
hash: str
|
|
77
|
+
message: str
|
|
78
|
+
author: str
|
|
79
|
+
date: str
|
|
80
|
+
files_changed: list[str] = []
|
|
81
|
+
diff_stats: str | None = None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class DocArtifact(BaseModel):
|
|
85
|
+
"""Documentation data for the DocBuilder.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
repo: Repository slug 'org/repo'.
|
|
89
|
+
path: File path of the document within the repo.
|
|
90
|
+
content_type: Type of document (e.g. 'readme', 'adr', 'design_doc').
|
|
91
|
+
content: Full text content of the document.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
model_config = ConfigDict(frozen=True)
|
|
95
|
+
|
|
96
|
+
repo: str
|
|
97
|
+
path: str
|
|
98
|
+
content_type: str
|
|
99
|
+
content: str
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Configuration and state models for AVOS CLI.
|
|
2
|
+
|
|
3
|
+
Defines RepoConfig (repository-scoped runtime configuration)
|
|
4
|
+
and LLMConfig (LLM provider settings).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, SecretStr
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LLMConfig(BaseModel):
|
|
15
|
+
"""LLM provider configuration.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
provider: LLM provider name (e.g. 'openai', 'anthropic').
|
|
19
|
+
model: Model identifier string.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
model_config = ConfigDict(frozen=True)
|
|
23
|
+
|
|
24
|
+
provider: str = "openai"
|
|
25
|
+
model: str = "gpt-4o"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class RepoConfig(BaseModel):
|
|
29
|
+
"""Repository-scoped runtime configuration.
|
|
30
|
+
|
|
31
|
+
Loaded from .avos/config.json with env var overlay.
|
|
32
|
+
Sensitive fields use SecretStr to prevent accidental exposure.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
repo: Repository slug in 'org/repo' format.
|
|
36
|
+
memory_id: Memory identifier - 'repo:org/repo' for PR history, commits, issues, docs.
|
|
37
|
+
api_url: Base URL for the Avos Memory API.
|
|
38
|
+
api_key: API key for Avos Memory (secret).
|
|
39
|
+
github_token: GitHub personal access token (secret, optional).
|
|
40
|
+
developer: Developer display name (defaults to git user.name).
|
|
41
|
+
llm: LLM provider configuration.
|
|
42
|
+
connected_at: UTC timestamp when the repo was first connected.
|
|
43
|
+
schema_version: Config schema version for forward compatibility.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
model_config = ConfigDict(frozen=True)
|
|
47
|
+
|
|
48
|
+
repo: str
|
|
49
|
+
memory_id: str
|
|
50
|
+
api_url: str
|
|
51
|
+
api_key: SecretStr
|
|
52
|
+
github_token: SecretStr | None = None
|
|
53
|
+
developer: str | None = None
|
|
54
|
+
llm: LLMConfig = LLMConfig()
|
|
55
|
+
connected_at: datetime | None = None
|
|
56
|
+
schema_version: str = "1"
|