Repository navigation
feat/0.17.0: CLI shell + history support + web API tests #89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6b976fd
a366539
890c069
8af2b6c
3d8b2fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -519,3 +519,182 @@ def get_system_info(self) -> dict[str, Any]: | |
| logger.debug(f"Failed to get package manager version: {e}") | ||
|
|
||
| return info | ||
|
|
||
| def get_transaction_history(self, limit: int = 20) -> list[dict[str, Any]]: | ||
| """ | ||
| Получить историю транзакций пакетного менеджера. | ||
|
|
||
| Args: | ||
| limit: Максимальное количество записей | ||
|
|
||
| Returns: | ||
| Список записей истории с полями: date, operation, package, status | ||
| """ | ||
| if self.package_manager == "apt": | ||
| return self._get_history_apt(limit) | ||
| elif self.package_manager == "pacman": | ||
| return self._get_history_pacman(limit) | ||
| elif self.package_manager == "dnf": | ||
| return self._get_history_dnf(limit) | ||
| elif self.package_manager == "brew": | ||
| return self._get_history_brew(limit) | ||
| else: | ||
| return [] | ||
|
|
||
| def _get_history_apt(self, limit: int) -> list[dict[str, Any]]: | ||
| """Get transaction history via APT (dpkg log).""" | ||
| history: list[dict[str, Any]] = [] | ||
| try: | ||
| result = subprocess.run( | ||
| ["tail", "-n", str(limit * 10), "/var/log/dpkg.log"], | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=30, | ||
| ) | ||
| if result.returncode == 0: | ||
| for line in result.stdout.strip().split("\n"): | ||
| if not line: | ||
| continue | ||
| parts = line.split() | ||
| if len(parts) >= 4: | ||
| date_str = f"{parts[0]} {parts[1]}" | ||
| if "install" in line.lower(): | ||
| pkg = parts[3] if len(parts) > 3 else "unknown" | ||
| history.append( | ||
| { | ||
| "date": date_str, | ||
| "operation": "install", | ||
| "package": pkg, | ||
| "status": "OK", | ||
| } | ||
| ) | ||
| elif "remove" in line.lower(): | ||
| pkg = parts[3] if len(parts) > 3 else "unknown" | ||
| history.append( | ||
| { | ||
| "date": date_str, | ||
| "operation": "remove", | ||
| "package": pkg, | ||
| "status": "OK", | ||
| } | ||
| ) | ||
| elif "upgrade" in line.lower(): | ||
| pkg = parts[3] if len(parts) > 3 else "unknown" | ||
| history.append( | ||
| { | ||
| "date": date_str, | ||
| "operation": "upgrade", | ||
| "package": pkg, | ||
| "status": "OK", | ||
| } | ||
| ) | ||
| except (subprocess.TimeoutExpired, FileNotFoundError, PermissionError) as e: | ||
| logger.warning(f"Failed to get APT history: {e}") | ||
| return history[-limit:] if history else [] | ||
|
|
||
| def _get_history_pacman(self, limit: int) -> list[dict[str, Any]]: | ||
| """Get transaction history via Pacman.""" | ||
| history: list[dict[str, Any]] = [] | ||
| try: | ||
| result = subprocess.run( | ||
| ["tail", "-n", str(limit * 5), "/var/log/pacman.log"], | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=30, | ||
| ) | ||
| if result.returncode == 0: | ||
| for line in result.stdout.strip().split("\n"): | ||
| if not line: | ||
| continue | ||
| parts = line.split() | ||
| if len(parts) >= 4: | ||
| date_str = parts[1] if len(parts) > 1 else "N/A" | ||
| if "[ALPM]" in line and "installed" in line: | ||
| pkg = parts[-1].rstrip(")") | ||
|
Comment on lines
+611
to
+613
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -euo pipefail
test -r /var/log/pacman.log
grep -Em1 '\[ALPM\] (installed|removed|upgraded) ' /var/log/pacman.log
# Expected: timestamp, operation, package, then parenthesized version data.Repository: dominicusin/package-maximizer Length of output: 167 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '570,645p' package_maximizer/integrations/real_repo_integration.pyRepository: dominicusin/package-maximizer Length of output: 3748 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path("package_maximizer/integrations/real_repo_integration.py")
lines = p.read_text().splitlines()
for n in range(570, 646):
print(f"{n}: {lines[n-1]}")
PYRepository: dominicusin/package-maximizer Length of output: 4128 Parse the Pacman timestamp and package fields correctly. Pacman records place the timestamp in 🤖 Prompt for AI Agents |
||
| history.append( | ||
| { | ||
| "date": date_str, | ||
| "operation": "install", | ||
| "package": pkg, | ||
| "status": "OK", | ||
| } | ||
| ) | ||
| elif "[ALPM]" in line and "removed" in line: | ||
| pkg = parts[-1].rstrip(")") | ||
| history.append( | ||
| { | ||
| "date": date_str, | ||
| "operation": "remove", | ||
| "package": pkg, | ||
| "status": "OK", | ||
| } | ||
| ) | ||
| elif "[ALPM]" in line and "upgraded" in line: | ||
| pkg = parts[-1].rstrip(")") | ||
| history.append( | ||
| { | ||
| "date": date_str, | ||
| "operation": "upgrade", | ||
| "package": pkg, | ||
| "status": "OK", | ||
| } | ||
| ) | ||
| except (subprocess.TimeoutExpired, FileNotFoundError, PermissionError) as e: | ||
| logger.warning(f"Failed to get Pacman history: {e}") | ||
| return history[-limit:] if history else [] | ||
|
|
||
| def _get_history_dnf(self, limit: int) -> list[dict[str, Any]]: | ||
| """Get transaction history via DNF.""" | ||
| history: list[dict[str, Any]] = [] | ||
| try: | ||
| result = subprocess.run( | ||
| ["dnf", "history", "list", "--quiet"], | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=30, | ||
| ) | ||
| if result.returncode == 0: | ||
| for line in result.stdout.strip().split("\n"): | ||
| if not line: | ||
| continue | ||
| parts = line.split() | ||
| if len(parts) >= 4: | ||
| history.append( | ||
| { | ||
| "date": parts[0], | ||
| "operation": parts[2] if len(parts) > 2 else "unknown", | ||
| "package": parts[3] if len(parts) > 3 else "unknown", | ||
| "status": "OK", | ||
|
Comment on lines
+664
to
+667
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -euo pipefail
dnf history list --quiet | sed -n '1,12p'
# Expected: transaction-table output. Confirm that it has no package-per-row column.Repository: dominicusin/package-maximizer Length of output: 208 🏁 Script executed: #!/bin/bash
set -euo pipefail
file=package_maximizer/integrations/real_repo_integration.py
sed -n '600,700p' "$file"
printf '\n--- related symbols ---\n'
rg -n -C 3 "dnf history|history|def .*dnf|subprocess.run" "$file" package_maximizer/cli/main.pyRepository: dominicusin/package-maximizer Length of output: 36013 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 8 "dnf history" .Repository: dominicusin/package-maximizer Length of output: 167 🌐 Web query:
💡 Result: The command Citations:
Do not derive package records from
🤖 Prompt for AI Agents |
||
| } | ||
| ) | ||
| except (subprocess.TimeoutExpired, FileNotFoundError) as e: | ||
| logger.warning(f"Failed to get DNF history: {e}") | ||
| return history[-limit:] if history else [] | ||
|
|
||
| def _get_history_brew(self, limit: int) -> list[dict[str, Any]]: | ||
| """Get transaction history via Brew.""" | ||
| history: list[dict[str, Any]] = [] | ||
| try: | ||
| result = subprocess.run( | ||
| ["brew", "log", f"--{limit}"], | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -euo pipefail
brew log -n 2 --oneline
# Expected: Git commit history, not local package-manager transactions.Repository: dominicusin/package-maximizer Length of output: 209 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- target implementation ---'
sed -n '560,710p' package_maximizer/integrations/real_repo_integration.py
printf '%s\n' '--- Homebrew references and related contracts ---'
rg -n -C 3 'brew|homebrew|transaction|history' package_maximizer tests README.md pyproject.toml setup.cfg 2>/dev/null || trueRepository: dominicusin/package-maximizer Length of output: 50385 🌐 Web query:
💡 Result: The command brew log is a valid Homebrew command that displays the git log for a specific formula, cask, or the Homebrew repository itself if no argument is provided [1]. To use the --max-count option with this command, you can pass it to limit the number of commits shown. For example, to view the last 5 commits for a specific formula, you would use: brew log --max-count=5 <formula_name> If you do not specify a formula or cask, running brew log --max-count=5 will display the last 5 commits to the Homebrew repository [1]. You can also use additional flags such as -p (or --patch) to view the changes associated with those commits [1]. Citations: Replace
🧰 Tools🪛 ast-grep (0.45.2)[error] 677-682: Command coming from incoming request (subprocess-from-request) 🪛 Ruff (0.16.3)[error] 679-679: Starting a process with a partial executable path (S607) 🤖 Prompt for AI Agents |
||
| capture_output=True, | ||
| text=True, | ||
| timeout=30, | ||
| ) | ||
| if result.returncode == 0: | ||
| for line in result.stdout.strip().split("\n"): | ||
| if not line: | ||
| continue | ||
| parts = line.split() | ||
| if len(parts) >= 3: | ||
| history.append( | ||
| { | ||
| "date": "N/A", | ||
| "operation": parts[0], | ||
| "package": parts[1] if len(parts) > 1 else "unknown", | ||
| "status": "OK", | ||
| } | ||
| ) | ||
| except (subprocess.TimeoutExpired, FileNotFoundError) as e: | ||
| logger.warning(f"Failed to get Brew history: {e}") | ||
| return history[-limit:] if history else [] | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: dominicusin/package-maximizer
Length of output: 167
🏁 Script executed:
Repository: dominicusin/package-maximizer
Length of output: 23492
🏁 Script executed:
Repository: dominicusin/package-maximizer
Length of output: 10320
🌐 Web query:
Debian dpkg dpkg.log format status installed action install log records man page💡 Result:
The Debian package manager log, typically located at /var/log/dpkg.log, records detailed activity regarding package installations, upgrades, and removals [1][2][3]. The log format is defined in the dpkg(1) manual page and uses a consistent, timestamped structure [4][5][6]. Log entry formats include: 1. Startup: YYYY-MM-DD HH:MM:SS startup type command This records the initiation of a dpkg invocation. Type is either archives (with command unpack or install) or packages (with command configure, triggers-only, remove, or purge) [4][5][6]. 2. Status changes: YYYY-MM-DD HH:MM:SS status state pkg installed-version This records changes in the status of a package (e.g., half-installed, unpacked, half-configured, installed, or config-files) [4][5][6]. The state field indicates the new status [7][8]. 3. Actions: YYYY-MM-DD HH:MM:SS action pkg installed-version available-version This records specific package operations, where action is one of install, upgrade, configure, trigproc, disappear, remove, or purge [4][5][6]. 4. Conffile changes: YYYY-MM-DD HH:MM:SS conffile filename decision This records configuration file decisions, where decision is either install or keep [4][5][6]. Regarding the specific terms requested: - Status: When you see 'status installed', it indicates the package has successfully completed the installation process and is marked as 'installed' in the dpkg database [7][9]. - Action: An 'action install' record marks the specific intent or command being performed on a package [4][5]. These logs are rotated monthly and compressed (e.g.,.gz files) by default [3]. They can be searched using tools like grep or zgrep [1][3]. While these logs provide a history of changes, tools like APT also maintain separate logs in /var/log/apt/ (history.log and term.log) which may provide additional context regarding the command-line usage that triggered these dpkg actions [2].
Citations:
Filter only dpkg action records.
_get_history_aptalso matchesstatus installed ...andconffile ... installrecords because it searches the entire line. It can therefore report"installed"or a configuration-file path as a package. Checkparts[2]for an exact action value before readingparts[3].🤖 Prompt for AI Agents