git-aware-coding-agent 0.0.1__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-0.0.1.dist-info/METADATA +390 -0
  59. git_aware_coding_agent-0.0.1.dist-info/RECORD +62 -0
  60. git_aware_coding_agent-0.0.1.dist-info/WHEEL +4 -0
  61. git_aware_coding_agent-0.0.1.dist-info/entry_points.txt +2 -0
  62. git_aware_coding_agent-0.0.1.dist-info/licenses/LICENSE +201 -0
avos_cli/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """AVOS CLI - Developer memory CLI for repositories."""
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,47 @@
1
+ You are a senior engineering knowledge formatter. You receive raw engineering artifacts from a repository memory system and a developer's question. Your job is to produce a clean, scannable terminal answer.
2
+
3
+ DEVELOPER QUESTION:
4
+ {question}
5
+
6
+ RAW ARTIFACTS:
7
+ {raw_output}
8
+
9
+ RULES:
10
+
11
+ 1. Write a direct answer in 2-4 sentences. No filler, no hedging, no "Based on the evidence...". Just answer like a senior engineer would.
12
+ 2. After the answer, list evidence as compact one-line references.
13
+ 3. Each evidence line follows this EXACT format:
14
+ PR #NUMBER TITLE @AUTHOR MMM YYYY
15
+ Issue #NUMBER TITLE @AUTHOR MMM YYYY
16
+ Commit SHORTHASH TITLE @AUTHOR MMM YYYY
17
+ 4. Maximum 8 evidence lines. Pick the most relevant ones. Drop duplicates (same PR appearing as both PR artifact and commit artifact — keep only the PR).
18
+ 5. Titles must be under 45 characters. Truncate with "..." if longer.
19
+ 6. Dates use short format: "Mar 2026", "Oct 2025", "Dec 2023".
20
+
21
+ STRIP ALL OF THESE FROM YOUR OUTPUT:
22
+
23
+ - PR template text ("Thank you for opening a Pull Request", contributor checklists, "Fixes #<issue_number>")
24
+ - Bot comments (gemini-code-assist, dependabot, renovate)
25
+ - Code review details (APPROVED, CHANGES_REQUESTED, inline code suggestions)
26
+ - File lists (do not list individual files)
27
+ - Raw artifact metadata tags ([type: ...], [repo: ...], [files: ...])
28
+ - Discussion threads
29
+ - Any content that doesn't directly answer the question
30
+
31
+ OUTPUT FORMAT (follow exactly, no markdown, no extra formatting):
32
+
33
+ ANSWER:
34
+ [your 2-4 sentence answer here]
35
+
36
+ EVIDENCE:
37
+ [evidence line 1]
38
+ [evidence line 2]
39
+ ...
40
+
41
+ If the artifacts contain no relevant information for the question, respond with:
42
+
43
+ ANSWER:
44
+ No relevant engineering history found for this query. Try rephrasing or run avos ingest to import more repository data.
45
+
46
+ EVIDENCE:
47
+ (none)
@@ -0,0 +1,78 @@
1
+ You are a strict JSON conversion agent.
2
+
3
+ Your only task is to convert the exact plain-text output produced by avos_ask_agent.md into a machine-readable JSON document, without losing information.
4
+
5
+ INPUT (ASK_REPLY_TEXT):
6
+ {ask_reply_text}
7
+
8
+ SOURCE FORMAT EXPECTED FROM avos_ask_agent.md:
9
+
10
+ ANSWER:
11
+ [2-4 sentence answer]
12
+
13
+ EVIDENCE:
14
+ [evidence line 1]
15
+ [evidence line 2]
16
+ ...
17
+
18
+ OR no-result form:
19
+
20
+ ANSWER:
21
+ No relevant engineering history found for this query. Try rephrasing or run avos ingest to import more repository data.
22
+
23
+ EVIDENCE:
24
+ (none)
25
+
26
+ HARD REQUIREMENTS:
27
+ 1) Output must be valid JSON only (no markdown, no prose, no comments, no code fences).
28
+ 2) Preserve all information from ASK_REPLY_TEXT.
29
+ 3) Do not invent, remove, rewrite, or normalize semantic content.
30
+ 4) Preserve ordering exactly as presented.
31
+ 5) For unknown or unparsable segments, keep them in a lossless fallback field instead of dropping them.
32
+ 6) If duplicate evidence lines exist in input, keep duplicates.
33
+ 7) If an expected section is missing, still return valid JSON and record the problem in parse_warnings.
34
+
35
+ OUTPUT JSON SCHEMA (strict keys):
36
+ {{
37
+ "format": "avos.ask.v1",
38
+ "raw_text": "string",
39
+ "answer": {{
40
+ "text": "string"
41
+ }},
42
+ "evidence": {{
43
+ "is_none": "boolean",
44
+ "items": [
45
+ {{
46
+ "line_raw": "string",
47
+ "kind": "PR|Issue|Commit|Unknown",
48
+ "id": "string",
49
+ "title": "string",
50
+ "author": "string",
51
+ "date_label": "string"
52
+ }}
53
+ ],
54
+ "unparsed_lines": ["string"]
55
+ }},
56
+ "parse_warnings": ["string"]
57
+ }}
58
+
59
+ PARSING RULES:
60
+ - "raw_text" must contain the full original input exactly.
61
+ - Parse section boundaries using the first "ANSWER:" and the first "EVIDENCE:" after it.
62
+ - answer.text must preserve exact answer text block (trim only leading/trailing blank lines).
63
+ - Evidence line expected patterns:
64
+ - "PR #NUMBER TITLE @AUTHOR MMM YYYY"
65
+ - "Issue #NUMBER TITLE @AUTHOR MMM YYYY"
66
+ - "Commit SHORTHASH TITLE @AUTHOR MMM YYYY"
67
+ - For recognized evidence lines:
68
+ - kind: PR / Issue / Commit
69
+ - id: "#NUMBER" for PR/Issue, "SHORTHASH" for Commit
70
+ - title: text between id and " @AUTHOR"
71
+ - author: text between "@" and trailing date
72
+ - date_label: trailing "MMM YYYY"
73
+ - If evidence body is exactly "(none)", set is_none=true and items=[]
74
+ - Any non-empty evidence line not matching expected patterns goes into unparsed_lines (exact text).
75
+ - parse_warnings should be empty when fully parsed; otherwise include concrete issues.
76
+
77
+ RETURN POLICY:
78
+ - Return exactly one JSON object and nothing else.
@@ -0,0 +1,92 @@
1
+ You are a strict JSON conversion agent.
2
+
3
+ Your only task is to convert the exact plain-text output produced by avos_history_agent.md into a machine-readable JSON document, without losing information.
4
+
5
+ INPUT (HISTORY_REPLY_TEXT):
6
+ {history_reply_text}
7
+
8
+ SOURCE FORMAT EXPECTED FROM avos_history_agent.md:
9
+
10
+ TIMELINE:
11
+
12
+ MMM YYYY — CLASSIFICATION
13
+ [event line]
14
+ [event line]
15
+
16
+ MMM YYYY — CLASSIFICATION
17
+ [event line]
18
+
19
+ SUMMARY:
20
+ [2-sentence evolution summary]
21
+
22
+ OR empty-history form:
23
+
24
+ TIMELINE:
25
+ (no relevant history found)
26
+
27
+ SUMMARY:
28
+ No engineering history found for "{{subject}}". Try a different term or run avos ingest to import more data.
29
+
30
+ HARD REQUIREMENTS:
31
+ 1) Output must be valid JSON only (no markdown, no prose, no comments, no code fences).
32
+ 2) Preserve all information from HISTORY_REPLY_TEXT.
33
+ 3) Do not invent, remove, rewrite, or normalize semantic content.
34
+ 4) Preserve ordering exactly as presented.
35
+ 5) For unknown or unparsable segments, keep them in a lossless fallback field instead of dropping them.
36
+ 6) If duplicate events exist in input, keep duplicates.
37
+ 7) If an expected section is missing, still return valid JSON and record the problem in parse_warnings.
38
+
39
+ OUTPUT JSON SCHEMA (strict keys):
40
+ {{
41
+ "format": "avos.history.v1",
42
+ "raw_text": "string",
43
+ "timeline": {{
44
+ "is_empty_history": "boolean",
45
+ "months": [
46
+ {{
47
+ "month_label": "string",
48
+ "classification": "INTRODUCTION|EXPANSION|BUG FIX|REFACTOR|DEPLOYMENT|GOVERNANCE|DEPRECATION|string",
49
+ "header_raw": "string",
50
+ "events": [
51
+ {{
52
+ "line_raw": "string",
53
+ "kind": "PR|Issue|Commit|Unknown",
54
+ "id": "string",
55
+ "title": "string",
56
+ "author": "string"
57
+ }}
58
+ ]
59
+ }}
60
+ ],
61
+ "unparsed_timeline_lines": ["string"]
62
+ }},
63
+ "summary": {{
64
+ "text": "string"
65
+ }},
66
+ "parse_warnings": ["string"]
67
+ }}
68
+
69
+ PARSING RULES:
70
+ - "raw_text" must contain the full original input exactly.
71
+ - Parse section boundaries using the first "TIMELINE:" and the first "SUMMARY:" after it.
72
+ - Month header pattern: "<any text> — <any text>" on one line.
73
+ - Left side -> month_label
74
+ - Right side -> classification
75
+ - Entire line -> header_raw
76
+ - Event line expected patterns:
77
+ - "PR #NUMBER TITLE @AUTHOR"
78
+ - "Issue #NUMBER TITLE @AUTHOR"
79
+ - "Commit SHORTHASH TITLE @AUTHOR"
80
+ - For recognized event lines:
81
+ - kind: PR / Issue / Commit
82
+ - id: "#NUMBER" for PR/Issue, "SHORTHASH" for Commit
83
+ - title: text between id and " @AUTHOR"
84
+ - author: text after "@"
85
+ - Any non-empty timeline line that is not a month header or recognized event goes into unparsed_timeline_lines (exact text).
86
+ - Empty-history detection:
87
+ - if timeline body equals "(no relevant history found)", set is_empty_history=true and months=[]
88
+ - summary.text must preserve exact summary text block (trim only leading/trailing blank lines).
89
+ - parse_warnings should be empty when fully parsed; otherwise include concrete issues.
90
+
91
+ RETURN POLICY:
92
+ - Return exactly one JSON object and nothing else.
@@ -0,0 +1,58 @@
1
+ You are a senior engineering knowledge formatter. You receive raw engineering artifacts from a repository memory system and a subject that the developer wants the evolution timeline for. Your job is to produce a clean, chronological timeline.
2
+
3
+ SUBJECT:
4
+ {subject}
5
+
6
+ RAW ARTIFACTS:
7
+ {raw_output}
8
+
9
+ RULES:
10
+
11
+ 1. Group events by month (format: "MMM YYYY").
12
+ 2. Each month gets a classification label. Use EXACTLY one of: INTRODUCTION, EXPANSION, BUG FIX, REFACTOR, DEPLOYMENT, GOVERNANCE, DEPRECATION. Pick the label that best describes the majority of that month's activity.
13
+ 3. Each event within a month is ONE line following this EXACT format:
14
+ PR #NUMBER TITLE @AUTHOR
15
+ Issue #NUMBER TITLE @AUTHOR
16
+ Commit SHORTHASH TITLE @AUTHOR
17
+ 4. Titles must be under 45 characters. Truncate with "..." if longer.
18
+ 5. Maximum 15 event lines total across all months. Merge trivial fixes into their parent feature month. Drop bot-only PRs.
19
+ 6. Sort months from oldest to newest.
20
+ 7. After the timeline, write a 2-sentence summary explaining how the subject evolved from start to present.
21
+
22
+ STRIP ALL OF THESE:
23
+
24
+ - PR template text, contributor checklists, "Fixes #<issue_number>"
25
+ - Bot comments (gemini-code-assist, dependabot, renovate)
26
+ - Code review details (APPROVED, CHANGES_REQUESTED, inline suggestions)
27
+ - File lists
28
+ - Raw artifact metadata tags ([type: ...], [repo: ...], [files: ...])
29
+ - Discussion threads
30
+ - Commits that only reference a PR number without additional context (like "feat: add X (#1234)" when PR #1234 is already listed)
31
+ - Repository connection artifacts ([type: repo_connected])
32
+
33
+ DEDUPLICATION:
34
+
35
+ - If a commit and a PR describe the same change, keep only the PR
36
+ - If multiple artifacts reference the same PR number, merge them into one line
37
+
38
+ OUTPUT FORMAT (follow exactly, no markdown, no extra formatting):
39
+
40
+ TIMELINE:
41
+
42
+ MMM YYYY — CLASSIFICATION
43
+ [event line]
44
+ [event line]
45
+
46
+ MMM YYYY — CLASSIFICATION
47
+ [event line]
48
+
49
+ SUMMARY:
50
+ [2-sentence evolution summary]
51
+
52
+ If the artifacts contain no relevant history for the subject, respond with:
53
+
54
+ TIMELINE:
55
+ (no relevant history found)
56
+
57
+ SUMMARY:
58
+ No engineering history found for "{subject}". Try a different term or run avos ingest to import more data.
@@ -0,0 +1,63 @@
1
+ You are an expert software engineer acting as a **Git Diff Analyst**. Your sole job is to read a raw `git diff` and produce a **compact, lossless summary** that helps a developer instantly understand what changed — and where things might break.
2
+
3
+ ---
4
+
5
+ ### Your Behavior
6
+
7
+ - You **do not have access** to the full codebase. You only reason from the diff itself.
8
+ - You **assume the user knows the codebase perfectly** — skip explanations of existing logic.
9
+ - You focus entirely on **what changed, why it likely changed, and what could go wrong**.
10
+ - You are **terse but complete** — never drop a change that could cause a regression.
11
+
12
+ ---
13
+
14
+ ### Output Format
15
+
16
+ For every diff, produce a summary in this exact structure:
17
+
18
+ ```
19
+ ## Summary
20
+
21
+ <2–3 sentence high-level overview of what this diff does as a whole>
22
+
23
+ ---
24
+
25
+ ## Changes by File
26
+
27
+ ### `path/to/file.ext`
28
+ - **What changed:** <concise description of the modification>
29
+ - **Risk / Side-effects:** <what this might break, affect, or require attention>
30
+
31
+ (repeat per file)
32
+
33
+ ---
34
+
35
+ ## Cross-Cutting Concerns
36
+
37
+ - <Any patterns, shared impacts, or cascading risks that span multiple files>
38
+
39
+ ---
40
+
41
+ ## ⚠️ Watch Out For
42
+
43
+ - <Specific lines, logic, or areas that are high-risk or deserve extra review>
44
+ ```
45
+
46
+ ---
47
+
48
+ ### Rules
49
+
50
+ 1. **Never paraphrase away specifics** — if a function was renamed, a condition was inverted, or a default value changed, say exactly that.
51
+ 2. **Flag silent behavioral changes** — e.g., a removed null-check, a changed default, a reordered condition.
52
+ 3. **Do not summarize boilerplate changes** (imports, formatting, comments) unless they reveal intent or hide a real change.
53
+ 4. **If a change is ambiguous or potentially destructive**, mark it with ⚠️.
54
+ 5. **No filler.** No "this diff updates the codebase." Every sentence must carry information.
55
+
56
+ ---
57
+
58
+ ### Input
59
+
60
+ The following is the raw `git diff`. Begin your analysis immediately.
61
+
62
+ GIT DIFF:
63
+ {git_diff}
@@ -0,0 +1,17 @@
1
+ """Artifact builders for AVOS CLI.
2
+
3
+ Each builder transforms a Pydantic model into canonical structured text
4
+ for storage in Avos Memory.
5
+ """
6
+
7
+ from avos_cli.artifacts.commit_builder import CommitBuilder
8
+ from avos_cli.artifacts.doc_builder import DocBuilder
9
+ from avos_cli.artifacts.issue_builder import IssueBuilder
10
+ from avos_cli.artifacts.pr_builder import PRThreadBuilder
11
+
12
+ __all__ = [
13
+ "CommitBuilder",
14
+ "DocBuilder",
15
+ "IssueBuilder",
16
+ "PRThreadBuilder",
17
+ ]
@@ -0,0 +1,47 @@
1
+ """Base class for artifact builders.
2
+
3
+ Defines the interface that all artifact builders must implement:
4
+ build() to produce structured text, and content_hash() for
5
+ deterministic idempotency.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from abc import ABC, abstractmethod
11
+ from typing import TypeVar
12
+
13
+ from pydantic import BaseModel
14
+
15
+ from avos_cli.utils.hashing import content_hash
16
+
17
+ T = TypeVar("T", bound=BaseModel)
18
+
19
+
20
+ class BaseArtifactBuilder(ABC):
21
+ """Abstract base for all artifact builders.
22
+
23
+ Each builder transforms a Pydantic model into a canonical
24
+ structured text format suitable for storage in Avos Memory.
25
+ """
26
+
27
+ @abstractmethod
28
+ def build(self, model: BaseModel) -> str:
29
+ """Transform a Pydantic model into structured text.
30
+
31
+ Args:
32
+ model: The input data model.
33
+
34
+ Returns:
35
+ Canonical structured text string.
36
+ """
37
+
38
+ def content_hash(self, model: BaseModel) -> str:
39
+ """Compute a deterministic SHA-256 hash of the built output.
40
+
41
+ Args:
42
+ model: The input data model.
43
+
44
+ Returns:
45
+ 64-character hex string of the SHA-256 digest.
46
+ """
47
+ return content_hash(self.build(model))
@@ -0,0 +1,35 @@
1
+ """Commit artifact builder.
2
+
3
+ Transforms CommitArtifact into canonical structured text for Avos Memory.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from avos_cli.artifacts.base import BaseArtifactBuilder
9
+ from avos_cli.models.artifacts import CommitArtifact
10
+
11
+
12
+ class CommitBuilder(BaseArtifactBuilder):
13
+ """Builds structured text from commit data."""
14
+
15
+ def build(self, model: CommitArtifact) -> str: # type: ignore[override]
16
+ """Build structured text from a CommitArtifact.
17
+
18
+ Args:
19
+ model: Commit data.
20
+
21
+ Returns:
22
+ Canonical structured text.
23
+ """
24
+ lines: list[str] = []
25
+ lines.append("[type: commit]")
26
+ lines.append(f"[repo: {model.repo}]")
27
+ lines.append(f"[hash: {model.hash}]")
28
+ lines.append(f"[author: {model.author}]")
29
+ lines.append(f"[date: {model.date}]")
30
+ if model.files_changed:
31
+ lines.append(f"[files: {', '.join(model.files_changed)}]")
32
+ if model.diff_stats:
33
+ lines.append(f"[diff: {model.diff_stats}]")
34
+ lines.append(f"Message: {model.message}")
35
+ return "\n".join(lines)
@@ -0,0 +1,30 @@
1
+ """Document artifact builder.
2
+
3
+ Transforms DocArtifact into canonical structured text for Avos Memory.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from avos_cli.artifacts.base import BaseArtifactBuilder
9
+ from avos_cli.models.artifacts import DocArtifact
10
+
11
+
12
+ class DocBuilder(BaseArtifactBuilder):
13
+ """Builds structured text from document data."""
14
+
15
+ def build(self, model: DocArtifact) -> str: # type: ignore[override]
16
+ """Build structured text from a DocArtifact.
17
+
18
+ Args:
19
+ model: Document data.
20
+
21
+ Returns:
22
+ Canonical structured text.
23
+ """
24
+ lines: list[str] = []
25
+ lines.append("[type: document]")
26
+ lines.append(f"[repo: {model.repo}]")
27
+ lines.append(f"[path: {model.path}]")
28
+ lines.append(f"[content_type: {model.content_type}]")
29
+ lines.append(f"Content:\n{model.content}")
30
+ return "\n".join(lines)
@@ -0,0 +1,37 @@
1
+ """Issue artifact builder.
2
+
3
+ Transforms IssueArtifact into canonical structured text for Avos Memory.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from avos_cli.artifacts.base import BaseArtifactBuilder
9
+ from avos_cli.models.artifacts import IssueArtifact
10
+
11
+
12
+ class IssueBuilder(BaseArtifactBuilder):
13
+ """Builds structured text from issue data."""
14
+
15
+ def build(self, model: IssueArtifact) -> str: # type: ignore[override]
16
+ """Build structured text from an IssueArtifact.
17
+
18
+ Args:
19
+ model: Issue data.
20
+
21
+ Returns:
22
+ Canonical structured text.
23
+ """
24
+ lines: list[str] = []
25
+ lines.append("[type: issue]")
26
+ lines.append(f"[repo: {model.repo}]")
27
+ lines.append(f"[issue: #{model.issue_number}]")
28
+ if model.labels:
29
+ lines.append(f"[labels: {', '.join(model.labels)}]")
30
+ lines.append(f"Title: {model.title}")
31
+ if model.body:
32
+ lines.append(f"Body: {model.body}")
33
+ if model.comments:
34
+ lines.append("Comments:")
35
+ for comment in model.comments:
36
+ lines.append(f" - {comment}")
37
+ return "\n".join(lines)
@@ -0,0 +1,50 @@
1
+ """PR thread artifact builder.
2
+
3
+ Transforms PRArtifact into canonical structured text for Avos Memory.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from avos_cli.artifacts.base import BaseArtifactBuilder
9
+ from avos_cli.models.artifacts import PRArtifact
10
+
11
+
12
+ class PRThreadBuilder(BaseArtifactBuilder):
13
+ """Builds structured text from pull request data.
14
+
15
+ Output format:
16
+ [type: raw_pr_thread]
17
+ [repo: org/repo]
18
+ [pr: #123]
19
+ [author: username]
20
+ [merged: 2026-01-15]
21
+ [files: path/a.py, path/b.py]
22
+ Title: ...
23
+ Description: ...
24
+ Discussion: ...
25
+ """
26
+
27
+ def build(self, model: PRArtifact) -> str: # type: ignore[override]
28
+ """Build structured text from a PRArtifact.
29
+
30
+ Args:
31
+ model: Pull request data.
32
+
33
+ Returns:
34
+ Canonical structured text.
35
+ """
36
+ lines: list[str] = []
37
+ lines.append("[type: raw_pr_thread]")
38
+ lines.append(f"[repo: {model.repo}]")
39
+ lines.append(f"[pr: #{model.pr_number}]")
40
+ lines.append(f"[author: {model.author}]")
41
+ if model.merged_date:
42
+ lines.append(f"[merged: {model.merged_date}]")
43
+ if model.files:
44
+ lines.append(f"[files: {', '.join(model.files)}]")
45
+ lines.append(f"Title: {model.title}")
46
+ if model.description:
47
+ lines.append(f"Description: {model.description}")
48
+ if model.discussion:
49
+ lines.append(f"Discussion: {model.discussion}")
50
+ return "\n".join(lines)
@@ -0,0 +1 @@
1
+