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.
Files changed (62) hide show
  1. avos_cli/__init__.py +3 -0
  2. avos_cli/agents/avos_ask_agent.md +47 -0
  3. avos_cli/agents/avos_ask_agent_JSON_converter.md +78 -0
  4. avos_cli/agents/avos_hisotry_agent_JSON_converter.md +92 -0
  5. avos_cli/agents/avos_history_agent.md +58 -0
  6. avos_cli/agents/git_diff_agent.md +63 -0
  7. avos_cli/artifacts/__init__.py +17 -0
  8. avos_cli/artifacts/base.py +47 -0
  9. avos_cli/artifacts/commit_builder.py +35 -0
  10. avos_cli/artifacts/doc_builder.py +30 -0
  11. avos_cli/artifacts/issue_builder.py +37 -0
  12. avos_cli/artifacts/pr_builder.py +50 -0
  13. avos_cli/cli/__init__.py +1 -0
  14. avos_cli/cli/main.py +504 -0
  15. avos_cli/commands/__init__.py +1 -0
  16. avos_cli/commands/ask.py +541 -0
  17. avos_cli/commands/connect.py +363 -0
  18. avos_cli/commands/history.py +549 -0
  19. avos_cli/commands/hook_install.py +260 -0
  20. avos_cli/commands/hook_sync.py +231 -0
  21. avos_cli/commands/ingest.py +506 -0
  22. avos_cli/commands/ingest_pr.py +239 -0
  23. avos_cli/config/__init__.py +1 -0
  24. avos_cli/config/hash_store.py +93 -0
  25. avos_cli/config/lock.py +122 -0
  26. avos_cli/config/manager.py +180 -0
  27. avos_cli/config/state.py +90 -0
  28. avos_cli/exceptions.py +272 -0
  29. avos_cli/models/__init__.py +58 -0
  30. avos_cli/models/api.py +75 -0
  31. avos_cli/models/artifacts.py +99 -0
  32. avos_cli/models/config.py +56 -0
  33. avos_cli/models/diff.py +117 -0
  34. avos_cli/models/query.py +234 -0
  35. avos_cli/parsers/__init__.py +21 -0
  36. avos_cli/parsers/artifact_ref_extractor.py +173 -0
  37. avos_cli/parsers/reference_parser.py +117 -0
  38. avos_cli/services/__init__.py +1 -0
  39. avos_cli/services/chronology_service.py +68 -0
  40. avos_cli/services/citation_validator.py +134 -0
  41. avos_cli/services/context_budget_service.py +104 -0
  42. avos_cli/services/diff_resolver.py +398 -0
  43. avos_cli/services/diff_summary_service.py +141 -0
  44. avos_cli/services/git_client.py +351 -0
  45. avos_cli/services/github_client.py +443 -0
  46. avos_cli/services/llm_client.py +312 -0
  47. avos_cli/services/memory_client.py +323 -0
  48. avos_cli/services/query_fallback_formatter.py +108 -0
  49. avos_cli/services/reply_output_service.py +341 -0
  50. avos_cli/services/sanitization_service.py +218 -0
  51. avos_cli/utils/__init__.py +1 -0
  52. avos_cli/utils/dotenv_load.py +50 -0
  53. avos_cli/utils/hashing.py +22 -0
  54. avos_cli/utils/logger.py +77 -0
  55. avos_cli/utils/output.py +232 -0
  56. avos_cli/utils/sanitization_diagnostics.py +81 -0
  57. avos_cli/utils/time_helpers.py +56 -0
  58. git_aware_coding_agent-1.0.0.dist-info/METADATA +390 -0
  59. git_aware_coding_agent-1.0.0.dist-info/RECORD +62 -0
  60. git_aware_coding_agent-1.0.0.dist-info/WHEEL +4 -0
  61. git_aware_coding_agent-1.0.0.dist-info/entry_points.txt +2 -0
  62. git_aware_coding_agent-1.0.0.dist-info/licenses/LICENSE +201 -0
@@ -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"