From 9aeedb39b8ac523a9ff28b1e2f840f99e5e7cc86 Mon Sep 17 00:00:00 2001 From: AI Bot Date: Tue, 14 Apr 2026 22:23:12 +0200 Subject: [PATCH] Add OpenSpec change for pkg modularization refactor --- .../refactor-modularize-pkg/.openspec.yaml | 2 + .../changes/refactor-modularize-pkg/design.md | 141 ++++++++++++++++++ .../refactor-modularize-pkg/proposal.md | 30 ++++ .../specs/scope/scope.md | 15 ++ .../changes/refactor-modularize-pkg/tasks.md | 38 +++++ 5 files changed, 226 insertions(+) create mode 100644 openspec/changes/refactor-modularize-pkg/.openspec.yaml create mode 100644 openspec/changes/refactor-modularize-pkg/design.md create mode 100644 openspec/changes/refactor-modularize-pkg/proposal.md create mode 100644 openspec/changes/refactor-modularize-pkg/specs/scope/scope.md create mode 100644 openspec/changes/refactor-modularize-pkg/tasks.md diff --git a/openspec/changes/refactor-modularize-pkg/.openspec.yaml b/openspec/changes/refactor-modularize-pkg/.openspec.yaml new file mode 100644 index 0000000..76a85e8 --- /dev/null +++ b/openspec/changes/refactor-modularize-pkg/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-04-14 diff --git a/openspec/changes/refactor-modularize-pkg/design.md b/openspec/changes/refactor-modularize-pkg/design.md new file mode 100644 index 0000000..8b05bc8 --- /dev/null +++ b/openspec/changes/refactor-modularize-pkg/design.md @@ -0,0 +1,141 @@ +# Design: Refactor pkg Into Modular Packages + +## New Structure + +``` +pkg/ +├── pacman/ # write operations only +│ └── pacman.go +├── fetch/ # package resolution (NEW) +│ └── fetch.go +├── validation/ # DB freshness (existing, expand) +│ └── validation.go +├── output/ # (unchanged) +│ └── output.go +├── merge/ # (unchanged) +│ └── merge.go +└── input/ # (unchanged) + └── input.go +``` + +## Package Responsibilities + +### pkg/fetch (NEW) + +``` +type Fetcher struct { + handle dyalpm.Handle + localDB dyalpm.Database + syncDBs []dyalpm.Database + aurCache map[string]AURPackage +} + +func New() *Fetcher +func (f *Fetcher) Close() error +func (f *Fetcher) Resolve(packages []string) (map[string]*PackageInfo, error) +func (f *Fetcher) ListOrphans() ([]string, error) +``` + +Extracted from `pacman.go`: +- `buildLocalPkgMap()` +- `checkSyncDBs()` +- `resolvePackages()` +- AUR cache (`ensureAURCache()`, `fetchAURInfo()`) + +### pkg/pacman (REFACTORED) + +``` +func Sync(packages []string) (*output.Result, error) +func DryRun(packages []string) (*output.Result, error) +func MarkAsExplicit(packages []string) error +func MarkAllAsDeps() error +func CleanupOrphans() (int, error) +``` + +Write actions only: +- `Sync()` - calls `Fetcher` for resolution, then pacman commands +- `DryRun()` - calls `Fetcher.Resolve()` + `ListOrphans()` +- `MarkAsExplicit()`, `MarkAllAsDeps()` +- `CleanupOrphans()` - calls `ListOrphans()` then removes + +### pkg/validation (REFACTORED) + +``` +func CheckDBFreshness() error +``` + +Keep as-is: checks lock file age, auto-synces if stale. + +Remove from `pacman.go`: +- `IsDBFresh()` - replaced by `CheckDBFreshness()` +- `SyncDB()` - called by validation when stale + +### Orphan Deduplication + +```go +// In fetch/fetch.go +func (f *Fetcher) ListOrphans() ([]string, error) { + cmd := exec.Command("pacman", "-Qdtq") + // ... +} + +// In pacman/pacman.go +func CleanupOrphans() (int, error) { + orphans, err := fetcher.ListOrphans() // reuse + if err != nil || len(orphans) == 0 { + return 0, nil + } + // ... remove +} + +func DryRun(...) (*output.Result, error) { + orphans, err := fetcher.ListOrphans() // reuse + // ... +} +``` + +## Data Structures Move to fetch + +```go +// In fetch/fetch.go +type PackageInfo struct { + Name string + InAUR bool + Exists bool + Installed bool + AURInfo *AURPackage + syncPkg dyalpm.Package +} + +type AURResponse struct { + Results []AURPackage `json:"results"` +} + +type AURPackage struct { + Name string `json:"Name"` + PackageBase string `json:"PackageBase"` + Version string `json:"Version"` + URL string `json:"URL"` +} +``` + +## Import Changes + +`pkg/pacman/fetch.go` will need: +- `github.com/Jguer/dyalpm` +- `github.com/Riyyi/declpac/pkg/output` + +`pkg/pacman/pacman.go` will need: +- `github.com/Riyyi/declpac/pkg/fetch` +- `github.com/Riyyi/declpac/pkg/output` + +## Dependencies to Add + +- New import: `pkg/fetch` in `pacman` package + +## No Changes To + +- CLI entry points +- `output.Result` struct +- `input.ReadPackages()` +- `merge.Merge()` \ No newline at end of file diff --git a/openspec/changes/refactor-modularize-pkg/proposal.md b/openspec/changes/refactor-modularize-pkg/proposal.md new file mode 100644 index 0000000..096fc3e --- /dev/null +++ b/openspec/changes/refactor-modularize-pkg/proposal.md @@ -0,0 +1,30 @@ +# Proposal: Refactor pkg Into Modular Packages + +## Summary + +Split monolithic `pkg/pacman/pacman.go` into focused packages: `fetch` (package resolution), `validation` (DB checks), and keep `pacman` for write actions only. Also deduplicate orphan detection logic. + +## Motivation + +`pacman.go` is 645 lines doing too much: +- Package resolution (local + sync DBs + AUR) +- DB freshness checks +- Pacman write operations (sync, mark, clean) +- Orphan listing/cleanup + +This violates single responsibility. Hard to test, reason about, or reuse. Also has duplication: +- `validation.CheckDBFreshness()` and `pacman.IsDBFresh()` both check DB freshness +- `listOrphans()` and `CleanupOrphans()` duplicate orphan detection + +## Scope + +- Extract package resolution to new `pkg/fetch/` +- Move DB freshness to `pkg/validation/` (keep `CheckDBFreshness()`) +- Keep only write actions in `pkg/pacman/` +- Deduplicate orphan logic: one function for listing, reuse in cleanup and dry-run + +## Out of Scope + +- No new features +- No API changes to CLI +- No changes to `pkg/output/`, `pkg/merge/`, `pkg/input/` \ No newline at end of file diff --git a/openspec/changes/refactor-modularize-pkg/specs/scope/scope.md b/openspec/changes/refactor-modularize-pkg/specs/scope/scope.md new file mode 100644 index 0000000..aa41ded --- /dev/null +++ b/openspec/changes/refactor-modularize-pkg/specs/scope/scope.md @@ -0,0 +1,15 @@ +# Scope + +## In Scope + +- Extract package resolution from pacman.go to pkg/fetch +- Deduplicate orphan listing +- Keep pacman write operations in pacman package +- Maintain existing CLI API + +## Out of Scope + +- New features +- New package management backends (e.g., libalpm alternatives) +- Config file changes +- State file format changes \ No newline at end of file diff --git a/openspec/changes/refactor-modularize-pkg/tasks.md b/openspec/changes/refactor-modularize-pkg/tasks.md new file mode 100644 index 0000000..19deb21 --- /dev/null +++ b/openspec/changes/refactor-modularize-pkg/tasks.md @@ -0,0 +1,38 @@ +# Tasks: Refactor pkg Into Modular Packages + +## Phase 1: Create pkg/fetch + +- [ ] 1.1 Create `pkg/fetch/fetch.go` +- [ ] 1.2 Move `AURResponse`, `AURPackage`, `PackageInfo` structs to fetch +- [ ] 1.3 Move `buildLocalPkgMap()` to fetch as `Fetcher.buildLocalPkgMap()` +- [ ] 1.4 Move `checkSyncDBs()` to fetch as `Fetcher.checkSyncDBs()` +- [ ] 1.5 Move `resolvePackages()` to fetch as `Fetcher.Resolve()` +- [ ] 1.6 Move AUR cache methods (`ensureAURCache`, `fetchAURInfo`) to fetch +- [ ] 1.7 Add `New()` and `Close()` to Fetcher +- [ ] 1.8 Add `ListOrphans()` to Fetcher + +## Phase 2: Refactor pkg/pacman + +- [ ] 2.1 Remove from pacman.go (now in fetch): + - `buildLocalPkgMap()` + - `checkSyncDBs()` + - `resolvePackages()` + - `ensureAURCache()` + - `fetchAURInfo()` + - `AURResponse`, `AURPackage`, `PackageInfo` structs +- [ ] 2.2 Remove `IsDBFresh()` and `SyncDB()` (use validation instead) +- [ ] 2.3 Update imports in pacman.go to include fetch package +- [ ] 2.4 Update `Sync()` to use `fetch.Fetcher` for resolution +- [ ] 2.5 Update `DryRun()` to call `fetcher.ListOrphans()` instead of duplicate call +- [ ] 2.6 Update `CleanupOrphans()` to call `fetcher.ListOrphans()` instead of duplicate call + +## Phase 3: Clean Up Validation + +- [ ] 3.1 Keep `validation.CheckDBFreshness()` as-is +- [ ] 3.2 Remove any remaining DB freshness duplication + +## Phase 4: Verify + +- [ ] 4.1 Run tests (if any exist) +- [ ] 4.2 Build: `go build ./...` +- [ ] 4.3 Verify CLI still works: test dry-run, sync, orphan cleanup \ No newline at end of file