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,260 @@
1
+ """Hook install command for setting up automatic commit sync on git push.
2
+
3
+ Installs a pre-push git hook that automatically syncs pushed commits
4
+ to Avos Memory. This enables real-time team synchronization without
5
+ requiring manual `avos ingest` runs.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import stat
11
+ from pathlib import Path
12
+
13
+ from avos_cli.config.manager import load_config
14
+ from avos_cli.exceptions import (
15
+ AvosError,
16
+ ConfigurationNotInitializedError,
17
+ )
18
+ from avos_cli.services.git_client import GitClient
19
+ from avos_cli.utils.logger import get_logger
20
+ from avos_cli.utils.output import print_error, print_info, print_success, print_warning
21
+
22
+ _log = get_logger("commands.hook_install")
23
+
24
+ _PRE_PUSH_HOOK_SCRIPT = """\
25
+ #!/bin/sh
26
+ # =============================================================================
27
+ # Avos Memory Auto-Sync Hook
28
+ # =============================================================================
29
+ # Installed by: avos hook-install
30
+ # Purpose: Automatically sync pushed commits to Avos Memory
31
+ #
32
+ # This hook runs before git push and extracts commits being pushed,
33
+ # then inserts them into Avos Memory for team synchronization.
34
+ #
35
+ # To uninstall: rm .git/hooks/pre-push
36
+ # =============================================================================
37
+
38
+ remote="$1"
39
+ url="$2"
40
+
41
+ # Read refs from stdin (format: local_ref local_sha remote_ref remote_sha)
42
+ while read local_ref local_sha remote_ref remote_sha; do
43
+ # Skip if deleting a ref (local_sha is null)
44
+ if [ "$local_sha" = "0000000000000000000000000000000000000000" ]; then
45
+ continue
46
+ fi
47
+
48
+ # For new branches, remote_sha is null - use empty string
49
+ if [ "$remote_sha" = "0000000000000000000000000000000000000000" ]; then
50
+ remote_sha=""
51
+ fi
52
+
53
+ # Sync commits to Avos Memory (never blocks push)
54
+ avos hook-sync "$remote_sha" "$local_sha" 2>&1 || true
55
+ done
56
+
57
+ # Always allow push to proceed
58
+ exit 0
59
+ """
60
+
61
+
62
+ class HookInstallOrchestrator:
63
+ """Orchestrates installation of the pre-push git hook.
64
+
65
+ The hook enables automatic commit sync to Avos Memory on every
66
+ git push, allowing team members to see each other's work in
67
+ real-time via `avos ask` and `avos history`.
68
+
69
+ Args:
70
+ git_client: Local git operations wrapper.
71
+ repo_root: Path to the repository root.
72
+ """
73
+
74
+ def __init__(self, git_client: GitClient, repo_root: Path) -> None:
75
+ self._git = git_client
76
+ self._repo_root = repo_root
77
+
78
+ def run(self, force: bool = False, quiet: bool = False) -> int:
79
+ """Install the pre-push hook for automatic commit sync.
80
+
81
+ Args:
82
+ force: If True, overwrite existing hook without prompting.
83
+ quiet: If True, suppress all output (for auto-install from connect).
84
+
85
+ Returns:
86
+ Exit code: 0 on success, 1 on failure.
87
+ """
88
+ try:
89
+ config = load_config(self._repo_root)
90
+ except ConfigurationNotInitializedError:
91
+ if not quiet:
92
+ print_error(
93
+ "[CONFIG_NOT_INITIALIZED] Repository not connected. "
94
+ "Run 'avos connect org/repo' first."
95
+ )
96
+ return 1
97
+ except AvosError as e:
98
+ if not quiet:
99
+ print_error(f"[{e.code}] {e}")
100
+ return 1
101
+
102
+ hooks_dir = self._get_hooks_dir()
103
+ if hooks_dir is None:
104
+ if not quiet:
105
+ print_error("[REPOSITORY_CONTEXT_ERROR] Could not locate .git/hooks directory.")
106
+ return 1
107
+
108
+ hook_path = hooks_dir / "pre-push"
109
+
110
+ if hook_path.exists() and not force:
111
+ if self._is_avos_hook(hook_path):
112
+ if not quiet:
113
+ print_info("Avos pre-push hook is already installed.")
114
+ return 0
115
+ else:
116
+ if not quiet:
117
+ print_warning(
118
+ "A pre-push hook already exists and was not installed by avos.\n"
119
+ "Use --force to overwrite, or manually integrate avos hook-sync."
120
+ )
121
+ _log.debug("Existing non-avos hook found, skipping auto-install")
122
+ return 1
123
+
124
+ try:
125
+ self._install_hook(hook_path)
126
+ except OSError as e:
127
+ if not quiet:
128
+ print_error(f"[HOOK_INSTALL_ERROR] Failed to install hook: {e}")
129
+ return 1
130
+
131
+ if not quiet:
132
+ print_success(f"Installed pre-push hook: {hook_path}")
133
+ print_info(
134
+ "Commits will now auto-sync to Avos Memory on every git push.\n"
135
+ f"Connected to memory: {config.memory_id}"
136
+ )
137
+ else:
138
+ _log.debug("Auto-installed pre-push hook at %s", hook_path)
139
+ return 0
140
+
141
+ def _get_hooks_dir(self) -> Path | None:
142
+ """Get the path to the git hooks directory.
143
+
144
+ Handles both regular repos (.git/hooks) and worktrees.
145
+
146
+ Returns:
147
+ Path to hooks directory, or None if not found.
148
+ """
149
+ git_path = self._repo_root / ".git"
150
+
151
+ if git_path.is_dir():
152
+ hooks_dir = git_path / "hooks"
153
+ hooks_dir.mkdir(exist_ok=True)
154
+ return hooks_dir
155
+
156
+ if git_path.is_file():
157
+ try:
158
+ content = git_path.read_text().strip()
159
+ if content.startswith("gitdir:"):
160
+ gitdir = content[7:].strip()
161
+ gitdir_path = Path(gitdir)
162
+ if not gitdir_path.is_absolute():
163
+ gitdir_path = (self._repo_root / gitdir_path).resolve()
164
+ hooks_dir = gitdir_path / "hooks"
165
+ hooks_dir.mkdir(exist_ok=True)
166
+ return hooks_dir
167
+ except (OSError, ValueError):
168
+ pass
169
+
170
+ return None
171
+
172
+ def _is_avos_hook(self, hook_path: Path) -> bool:
173
+ """Check if an existing hook was installed by avos.
174
+
175
+ Args:
176
+ hook_path: Path to the hook file.
177
+
178
+ Returns:
179
+ True if the hook contains avos signature.
180
+ """
181
+ try:
182
+ content = hook_path.read_text()
183
+ return "avos hook-sync" in content or "Avos Memory Auto-Sync" in content
184
+ except OSError:
185
+ return False
186
+
187
+ def _install_hook(self, hook_path: Path) -> None:
188
+ """Write the pre-push hook script and make it executable.
189
+
190
+ Args:
191
+ hook_path: Path where the hook should be installed.
192
+
193
+ Raises:
194
+ OSError: If writing or chmod fails.
195
+ """
196
+ hook_path.write_text(_PRE_PUSH_HOOK_SCRIPT)
197
+ hook_path.chmod(hook_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
198
+ _log.info("Installed pre-push hook at %s", hook_path)
199
+
200
+
201
+ class HookUninstallOrchestrator:
202
+ """Orchestrates removal of the pre-push git hook.
203
+
204
+ Args:
205
+ repo_root: Path to the repository root.
206
+ """
207
+
208
+ def __init__(self, repo_root: Path) -> None:
209
+ self._repo_root = repo_root
210
+
211
+ def run(self) -> int:
212
+ """Remove the avos pre-push hook if it exists.
213
+
214
+ Returns:
215
+ Exit code: 0 on success, 1 if hook wasn't avos-installed.
216
+ """
217
+ git_path = self._repo_root / ".git"
218
+ if git_path.is_dir():
219
+ hook_path = git_path / "hooks" / "pre-push"
220
+ elif git_path.is_file():
221
+ try:
222
+ content = git_path.read_text().strip()
223
+ if content.startswith("gitdir:"):
224
+ gitdir = content[7:].strip()
225
+ gitdir_path = Path(gitdir)
226
+ if not gitdir_path.is_absolute():
227
+ gitdir_path = (self._repo_root / gitdir_path).resolve()
228
+ hook_path = gitdir_path / "hooks" / "pre-push"
229
+ else:
230
+ print_error("[REPOSITORY_CONTEXT_ERROR] Invalid .git file format.")
231
+ return 1
232
+ except OSError:
233
+ print_error("[REPOSITORY_CONTEXT_ERROR] Could not read .git file.")
234
+ return 1
235
+ else:
236
+ print_error("[REPOSITORY_CONTEXT_ERROR] No .git found.")
237
+ return 1
238
+
239
+ if not hook_path.exists():
240
+ print_info("No pre-push hook installed.")
241
+ return 0
242
+
243
+ try:
244
+ content = hook_path.read_text()
245
+ if "avos hook-sync" not in content and "Avos Memory Auto-Sync" not in content:
246
+ print_warning(
247
+ "The existing pre-push hook was not installed by avos.\n"
248
+ "Remove it manually if needed."
249
+ )
250
+ return 1
251
+ except OSError:
252
+ pass
253
+
254
+ try:
255
+ hook_path.unlink()
256
+ print_success("Removed avos pre-push hook.")
257
+ return 0
258
+ except OSError as e:
259
+ print_error(f"[HOOK_UNINSTALL_ERROR] Failed to remove hook: {e}")
260
+ return 1
@@ -0,0 +1,231 @@
1
+ """Hook sync command for automatic commit memory insertion on git push.
2
+
3
+ # =============================================================================
4
+ # IMPORTANT: INGEST PIPELINE BYPASS
5
+ # =============================================================================
6
+ # This module bypasses the avos ingest pipeline and directly calls add_memory().
7
+ #
8
+ # WHY: To enable real-time commit sync on git push without GitHub API polling.
9
+ #
10
+ # CONSTRAINT: This MUST use the exact same data schema as avos ingest:
11
+ # - CommitArtifact model (avos_cli/models/artifacts.py)
12
+ # - CommitBuilder format (avos_cli/artifacts/commit_builder.py)
13
+ # - Hash store deduplication (avos_cli/config/hash_store.py)
14
+ #
15
+ # IF YOU CHANGE avos ingest commit handling, UPDATE THIS MODULE.
16
+ # See: avos_cli/commands/ingest.py::_ingest_commits()
17
+ # =============================================================================
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from dataclasses import dataclass
23
+ from pathlib import Path
24
+
25
+ from avos_cli.artifacts.commit_builder import CommitBuilder
26
+ from avos_cli.config.hash_store import IngestHashStore
27
+ from avos_cli.config.manager import load_config
28
+ from avos_cli.exceptions import (
29
+ AvosError,
30
+ ConfigurationNotInitializedError,
31
+ )
32
+ from avos_cli.models.artifacts import CommitArtifact
33
+ from avos_cli.services.git_client import GitClient
34
+ from avos_cli.services.memory_client import AvosMemoryClient
35
+ from avos_cli.utils.logger import get_logger
36
+ from avos_cli.utils.output import print_info, print_success
37
+
38
+ _log = get_logger("commands.hook_sync")
39
+
40
+
41
+ @dataclass
42
+ class HookSyncResult:
43
+ """Result of hook sync operation.
44
+
45
+ Attributes:
46
+ processed: Total commits attempted.
47
+ stored: Commits successfully stored in Avos Memory.
48
+ skipped: Commits skipped due to deduplication.
49
+ failed: Commits that failed to store.
50
+ """
51
+
52
+ processed: int = 0
53
+ stored: int = 0
54
+ skipped: int = 0
55
+ failed: int = 0
56
+
57
+
58
+ class HookSyncOrchestrator:
59
+ """Orchestrates commit sync on git push via pre-push hook.
60
+
61
+ # =========================================================================
62
+ # INGEST BYPASS NOTICE
63
+ # =========================================================================
64
+ # This orchestrator bypasses the full avos ingest pipeline.
65
+ # It directly inserts commits using the same schema as:
66
+ # avos_cli/commands/ingest.py::_ingest_commits()
67
+ #
68
+ # If the ingest commit schema changes, this module MUST be updated.
69
+ # =========================================================================
70
+
71
+ Args:
72
+ memory_client: Avos Memory API client.
73
+ git_client: Local git operations wrapper.
74
+ hash_store: Content hash store for deduplication.
75
+ repo_root: Path to the repository root.
76
+ """
77
+
78
+ def __init__(
79
+ self,
80
+ memory_client: AvosMemoryClient,
81
+ git_client: GitClient,
82
+ hash_store: IngestHashStore,
83
+ repo_root: Path,
84
+ ) -> None:
85
+ self._memory = memory_client
86
+ self._git = git_client
87
+ self._hash_store = hash_store
88
+ self._repo_root = repo_root
89
+ self._commit_builder = CommitBuilder()
90
+
91
+ def run(self, old_sha: str, new_sha: str) -> int:
92
+ """Execute the hook sync flow for commits between two SHAs.
93
+
94
+ This is called by the pre-push hook with the old and new SHA
95
+ of the ref being pushed. It extracts commits in the range and
96
+ inserts them into Avos Memory.
97
+
98
+ Args:
99
+ old_sha: Base commit SHA (what remote has). Empty for new branches.
100
+ new_sha: Target commit SHA (what we're pushing).
101
+
102
+ Returns:
103
+ Exit code: 0 on success, 1 on precondition failure.
104
+ Note: We always return 0 to avoid blocking git push.
105
+ """
106
+ try:
107
+ config = load_config(self._repo_root)
108
+ except ConfigurationNotInitializedError:
109
+ _log.debug("No avos config found, skipping hook sync")
110
+ return 0
111
+ except AvosError as e:
112
+ _log.warning("Failed to load avos config: %s", e)
113
+ return 0
114
+
115
+ repo_slug = config.repo
116
+ memory_id = config.memory_id
117
+
118
+ if not repo_slug or not memory_id:
119
+ _log.debug("Missing repo or memory_id in config, skipping")
120
+ return 0
121
+
122
+ normalized_old = self._normalize_sha(old_sha)
123
+ normalized_new = self._normalize_sha(new_sha)
124
+
125
+ if not normalized_new:
126
+ _log.debug("No new SHA provided, skipping")
127
+ return 0
128
+
129
+ result = self._sync_commits(normalized_old, normalized_new, repo_slug, memory_id)
130
+
131
+ if result.stored > 0 or result.skipped > 0:
132
+ self._hash_store.save()
133
+
134
+ if result.stored > 0:
135
+ print_success(
136
+ f"[avos] Synced {result.stored} commit(s) to memory "
137
+ f"({result.skipped} skipped, {result.failed} failed)"
138
+ )
139
+ elif result.processed > 0 and result.skipped == result.processed:
140
+ print_info(f"[avos] All {result.skipped} commit(s) already in memory")
141
+
142
+ return 0
143
+
144
+ def _normalize_sha(self, sha: str) -> str:
145
+ """Normalize SHA, treating null SHA as empty string.
146
+
147
+ Args:
148
+ sha: SHA string, possibly the null SHA (all zeros).
149
+
150
+ Returns:
151
+ Empty string if null SHA, otherwise the original SHA.
152
+ """
153
+ if not sha:
154
+ return ""
155
+ if sha == "0" * 40:
156
+ return ""
157
+ return sha.strip()
158
+
159
+ def _sync_commits(
160
+ self, old_sha: str, new_sha: str, repo_slug: str, memory_id: str
161
+ ) -> HookSyncResult:
162
+ """Sync commits in the given range to Avos Memory.
163
+
164
+ # INGEST BYPASS: Uses same artifact schema as ingest._ingest_commits()
165
+
166
+ Args:
167
+ old_sha: Base SHA (exclusive). Empty for new branches.
168
+ new_sha: Target SHA (inclusive).
169
+ repo_slug: Repository identifier 'org/repo'.
170
+ memory_id: Target memory ID.
171
+
172
+ Returns:
173
+ HookSyncResult with counts.
174
+ """
175
+ result = HookSyncResult()
176
+
177
+ try:
178
+ commits = self._git.commit_log_range(self._repo_root, old_sha, new_sha)
179
+ except AvosError as e:
180
+ _log.warning("Failed to get commit range: %s", e)
181
+ return result
182
+
183
+ if not commits:
184
+ _log.debug("No commits in range %s..%s", old_sha[:8] if old_sha else "ROOT", new_sha[:8])
185
+ return result
186
+
187
+ for commit_data in commits:
188
+ result.processed += 1
189
+ try:
190
+ self._sync_single_commit(commit_data, repo_slug, memory_id, result)
191
+ except Exception as e:
192
+ _log.warning("Failed to sync commit %s: %s", commit_data.get("hash", "?")[:8], e)
193
+ result.failed += 1
194
+
195
+ return result
196
+
197
+ def _sync_single_commit(
198
+ self,
199
+ commit_data: dict[str, str],
200
+ repo_slug: str,
201
+ memory_id: str,
202
+ result: HookSyncResult,
203
+ ) -> None:
204
+ """Sync a single commit to Avos Memory.
205
+
206
+ # INGEST BYPASS: This follows the exact same pattern as
207
+ # avos_cli/commands/ingest.py::_ingest_commits() lines 282-298
208
+
209
+ Args:
210
+ commit_data: Dict with hash, message, author, date.
211
+ repo_slug: Repository identifier.
212
+ memory_id: Target memory ID.
213
+ result: Result object to update.
214
+ """
215
+ artifact = CommitArtifact(
216
+ repo=repo_slug,
217
+ hash=commit_data["hash"],
218
+ message=commit_data["message"],
219
+ author=commit_data["author"],
220
+ date=commit_data["date"],
221
+ )
222
+ text = self._commit_builder.build(artifact)
223
+ content_hash = self._commit_builder.content_hash(artifact)
224
+
225
+ if self._hash_store.contains(content_hash):
226
+ result.skipped += 1
227
+ return
228
+
229
+ self._memory.add_memory(memory_id=memory_id, content=text)
230
+ self._hash_store.add(content_hash, "commit", commit_data["hash"])
231
+ result.stored += 1