diff --git a/openspec/changes/declpac-cli-tool/.openspec.yaml b/openspec/changes/declpac-cli-tool/.openspec.yaml new file mode 100644 index 0000000..e14f322 --- /dev/null +++ b/openspec/changes/declpac-cli-tool/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-04-13 diff --git a/openspec/changes/declpac-cli-tool/design.md b/openspec/changes/declpac-cli-tool/design.md new file mode 100644 index 0000000..30cbbbd --- /dev/null +++ b/openspec/changes/declpac-cli-tool/design.md @@ -0,0 +1,109 @@ +## Context + +Pacman, Arch Linux's package manager, lacks declarative package management and +doesn't support partial upgrades. Users managing system packages from scripts +or configuration files need a way to ensure their system state matches a declared +package list, with all packages at their latest available versions. + +## Goals / Non-Goals + +**Goals:** +- Provide a declarative interface for pacman package management through + command-line interface +- Support flexible input sources (stdin and multiple state files) with + additive merging +- **Automatically resolve transitive dependencies (users specify only direct packages)** +- Enforce full package upgrades (no partial upgrades) to prevent version + mismatches +- Generate machine-readable output suitable for automated scripting +- Enable AI-driven implementation for new Go developers + +**Non-Goals:** +- Package dependency resolution or smart upgrade scheduling +- Transaction rollback capabilities +- Pretty terminal output or interactive prompts +- Support for partial upgrades or selective package versions +- State persistence (tool only syncs, doesn't save state) + +## Decisions + +**Language: Go** +- Rationale: User wants modern CLI tool with native package management + capabilities +- Go provides strong standard library, easy command-line parsing, and + excellent for system-level operations +- User unfamiliar with Go - implementation will be AI-driven with detailed + documentation + +**Input Parsing Approach** +- Parse whitespace, tabs, newlines for package names +- Support multiple --state files: all additive including with stdin +- Validate package names against pacman available packages (optional validation) +- Empty state detection: print error to stderr and exit with code 1 (abort) + +**State Merging Logic** +- All inputs are additive: combine all packages from stdin and all state files +- No conflict resolution: missing packages are added, duplicates accumulate +- Order-independent: inputs don't override each other + +**Pacman Interaction** +- Use os/exec.Command to run pacman with -Sy (sync databases) and -S flag +- Run pacman -Syu with all target packages to ensure full upgrade +- Capture stderr/stdout for error reporting and machine output +- Detect pacman exit codes and translate to tool exit codes + +**Error Handling Strategy** +- Parse pacman error messages from stderr output +- Distinguish between package-not-found (warning) vs. execution failures (errors) +- Return appropriate exit codes: 0 for success, non-zero for errors +- Include error details in output for scripting purposes + +**AUR Integration** +- For packages not found in pacman repositories, check if available in AUR +- Use makepkg directly to build and install AUR packages (no AUR helpers) +- Clone AUR git repo to temp directory +- Run `makepkg -si` in temp directory for installation +- Capture stdout/stderr for output and error handling +- Report error to stderr if package not found in pacman or AUR + +**Dependency Resolution** +- Use pacman's dependency resolution by passing all declared packages to `pacman -Syu` +- Pacman automatically resolves and includes transitive dependencies +- For AUR packages, makepkg handles dependency resolution during build +- No custom dependency resolution logic required - delegate to pacman/makepkg + +**Explicit Marking & Orphan Cleanup** +- Before syncing, get list of all currently installed packages +- Mark declared state packages as explicitly installed via `pacman -D --explicit ` +- All other installed packages remain as non-explicit (dependencies) +- After sync completes, run `pacman -Rns $(pacman -Qdtq)` to remove orphaned packages +- Pacman -Rns $(pacman -Qdtq) only removes packages that are not explicitly + installed and not required +- Capture and report number of packages removed during cleanup + +**Package Version Management** +- Always force full system upgrade via `pacman -Syu ` +- Pacman handles version selection automatically, ensuring latest versions +- No semantic version comparison or pinning logic required + +## Risks / Trade-offs + +**Known Risks:** +- [User unfamiliarity with Go] → Mitigation: Provide complete implementation + with detailed comments; user can review and run without understanding deeply +- [Pacman error message parsing complexity] → Mitigation: Use broad error + pattern matching; include error output as-is for debugging +- [Empty state triggers abort] → Mitigation: Print error to stderr and exit + with code 1 instead of proceeding with empty state +- [Additive merging could accumulate duplicates] → Mitigation: Accept as + design choice; pacman handles duplicates gracefully +- [Dependency conflicts could occur] → Mitigation: Let pacman handle standard + conflicts; tool won't implement complex dependency resolution + +**Trade-offs:** +- No conflict resolution: simpler merging but may include packages the system + rejects +- Additive vs replacement: safer but less predictable for users expecting + replacements +- No user prompts: fully automated for scripting but could be risky without + warnings diff --git a/openspec/changes/declpac-cli-tool/proposal.md b/openspec/changes/declpac-cli-tool/proposal.md new file mode 100644 index 0000000..6345af3 --- /dev/null +++ b/openspec/changes/declpac-cli-tool/proposal.md @@ -0,0 +1,39 @@ +## Why + +Pacman doesn't support declarative package management or partial upgrades, +making it challenging to maintain consistent system states. This tool provides +a clean, scriptable interface to synchronize the system with a declared package +list, ensuring all packages are at the latest version. + +## What Changes + +- Add CLI tool for declarative pacman package management +- Support stdin input for package lists +- Support multiple --state file inputs +- Merge all inputs (additive strategy with empty state warning) +- Force full upgrade of all packages (no partial upgrades) +- Handle transitive dependencies automatically (users only specify direct packages) +- Mark non-state packages as non-explicit before sync +- After sync, remove orphaned packages (cleanup) +- Support AUR packages: try pacman first, then makepkg, then report errors +- Machine-readable output (install/remove counts, exit codes) +- No conflict resolution for missing packages (append-only) +- Print error to stderr for empty state input and exit with code 1 + +## Capabilities + +### New Capabilities + +- **stdin-input**: Read package lists from standard input stream +- **state-files**: Load declarative package states from files +- **input-merging**: Combine multiple input sources (stdin + multiple --state files) +- **state-validation**: Validate package names and empty states +- **transitive-deps**: Automatically resolve all transitive dependencies +- **pacman-sync**: Execute pacman operations to match declared state +- **aur-sync**: Handle AUR packages: pacman first, then fall back to makepkg, + then report errors +- **machine-output**: Generate machine-readable sync results for scripting + +### Modified Capabilities + +None (new tool) \ No newline at end of file diff --git a/openspec/changes/declpac-cli-tool/specs/aur-sync/spec.md b/openspec/changes/declpac-cli-tool/specs/aur-sync/spec.md new file mode 100644 index 0000000..011ea1f --- /dev/null +++ b/openspec/changes/declpac-cli-tool/specs/aur-sync/spec.md @@ -0,0 +1,23 @@ +## ADDED Requirements + +### Requirement: Can handle AUR packages with pacman fallback + +The system SHALL attempt to install AUR packages by first trying pacman +repositories, then falling back to AUR when pacman fails, and finally reporting +errors for packages still missing. + +#### Scenario: Install from pacman first +- **WHEN** package is in pacman repositories +- **THEN** system shall install via pacman + +#### Scenario: Fall back to AUR +- **WHEN** package is not in pacman repositories but is in AUR +- **THEN** system shall attempt installation via makepkg (direct AUR build) + +#### Scenario: Report error for missing packages +- **WHEN** package is not in pacman repositories or AUR +- **THEN** system shall report error and print to stderr + +#### Scenario: Continue for remaining packages +- **WHEN** some packages are installed and some fail +- **THEN** system shall continue installation process for successful packages diff --git a/openspec/changes/declpac-cli-tool/specs/input-merging/spec.md b/openspec/changes/declpac-cli-tool/specs/input-merging/spec.md new file mode 100644 index 0000000..18f258d --- /dev/null +++ b/openspec/changes/declpac-cli-tool/specs/input-merging/spec.md @@ -0,0 +1,24 @@ +## ADDED Requirements + +### Requirement: Can merge multiple input sources + +The system SHALL combine package lists from all inputs using an additive +strategy without conflict resolution. + +#### Scenario: Additive merging of all inputs +- **WHEN** stdin and multiple state files provide package lists +- **THEN** system shall include all packages from all inputs in the final state + +#### Scenario: Input priority is last-writer-wins per file +- **WHEN** multiple state files contain the same package name +- **THEN** the package from the last provided file in command-line order takes + precedence + +#### Scenario: Missing packages from stdin are added +- **WHEN** stdin contains packages not in state files +- **THEN** those packages shall be added to the final state + +#### Scenario: Duplicate packages accumulate +- **WHEN** the same package appears in multiple inputs +- **THEN** it shall be included multiple times in the final state (pacman + handles duplicates) diff --git a/openspec/changes/declpac-cli-tool/specs/machine-output/spec.md b/openspec/changes/declpac-cli-tool/specs/machine-output/spec.md new file mode 100644 index 0000000..f8627f2 --- /dev/null +++ b/openspec/changes/declpac-cli-tool/specs/machine-output/spec.md @@ -0,0 +1,21 @@ +## ADDED Requirements + +### Requirement: Can generate machine-readable output + +The system SHALL produce output suitable for automated scripting. + +#### Scenario: Install/remove count output +- **WHEN** sync completes successfully +- **THEN** system shall output: " package(s) installed, package(s) removed" + +#### Scenario: Empty output format +- **WHEN** no packages to sync +- **THEN** system shall output nothing (or indicate no changes) + +#### Scenario: Error details in output +- **WHEN** pacman operation fails +- **THEN** system shall include error details in output (package names, pacman error messages) + +#### Scenario: Exit code for scripting +- **WHEN** sync completes +- **THEN** system shall return exit code 0 for success, non-zero for errors \ No newline at end of file diff --git a/openspec/changes/declpac-cli-tool/specs/orphan-cleanup/spec.md b/openspec/changes/declpac-cli-tool/specs/orphan-cleanup/spec.md new file mode 100644 index 0000000..9efaa1b --- /dev/null +++ b/openspec/changes/declpac-cli-tool/specs/orphan-cleanup/spec.md @@ -0,0 +1,29 @@ +## ADDED Requirements + +### Requirement: Can mark non-state packages as non-explicit + +The system SHALL mark all packages not declared in the state as non-explicit +(dependencies) before syncing, so they can be safely removed later. + +#### Scenario: Mark packages as non-explicit +- **WHEN** packages are declared in state +- **THEN** system shall mark those packages as explicitly installed via pacman -D --explicit +- **AND** mark all other installed packages as non-explicit + +### Requirement: Can clean up orphaned packages + +After syncing, the system SHALL remove packages that are no longer required +(as dependencies of removed packages). + +#### Scenario: Orphan cleanup after sync +- **WHEN** sync operation completes successfully +- **THEN** system shall run pacman -Rsu to remove unneeded dependencies +- **AND** report the number of packages removed + +#### Scenario: Orphan cleanup respects explicitly installed +- **WHEN** a package not in state is marked as explicitly installed by user +- **THEN** system shall NOT remove it during orphan cleanup + +#### Scenario: No orphans to clean +- **WHEN** there are no orphaned packages to remove +- **THEN** system shall report "No packages to remove" in output \ No newline at end of file diff --git a/openspec/changes/declpac-cli-tool/specs/pacman-sync/spec.md b/openspec/changes/declpac-cli-tool/specs/pacman-sync/spec.md new file mode 100644 index 0000000..e95f804 --- /dev/null +++ b/openspec/changes/declpac-cli-tool/specs/pacman-sync/spec.md @@ -0,0 +1,25 @@ +## ADDED Requirements + +### Requirement: Can sync system with declared package state + +The system SHALL execute pacman operations to install and upgrade all declared packages. + +#### Scenario: Full system upgrade with all packages +- **WHEN** system has declared package list +- **THEN** system shall run pacman -Syu with all declared package names + +#### Scenario: No partial upgrades +- **WHEN** running pacman commands +- **THEN** system shall use -Syu flag (full system upgrade) ensuring all packages are latest + +#### Scenario: Package availability check +- **WHEN** a package from input is not in pacman repositories +- **THEN** system shall report the error and include package name + +#### Scenario: Pacman execution capture +- **WHEN** pacman commands execute +- **THEN** system shall capture both stdout and stderr for error reporting + +#### Scenario: Exit code propagation +- **WHEN** pacman commands execute +- **THEN** system shall exit with code equal to pacman's exit code for success/failure detection \ No newline at end of file diff --git a/openspec/changes/declpac-cli-tool/specs/state-files/spec.md b/openspec/changes/declpac-cli-tool/specs/state-files/spec.md new file mode 100644 index 0000000..c2e454b --- /dev/null +++ b/openspec/changes/declpac-cli-tool/specs/state-files/spec.md @@ -0,0 +1,21 @@ +## ADDED Requirements + +### Requirement: Can load package states from state files + +The system SHALL load package lists from state files specified via --state flag. + +#### Scenario: Single state file loading +- **WHEN** a state file is provided via --state flag +- **THEN** system shall read package names from the file content + +#### Scenario: Multiple state file loading +- **WHEN** multiple --state flags are provided +- **THEN** system shall load package names from each file + +#### Scenario: State file format is text list +- **WHEN** loading from state files +- **THEN** system shall parse whitespace-delimited package names + +#### Scenario: File path validation +- **WHEN** a state file path points to a non-existent file +- **THEN** system shall report a file read error \ No newline at end of file diff --git a/openspec/changes/declpac-cli-tool/specs/state-validation/spec.md b/openspec/changes/declpac-cli-tool/specs/state-validation/spec.md new file mode 100644 index 0000000..9017fef --- /dev/null +++ b/openspec/changes/declpac-cli-tool/specs/state-validation/spec.md @@ -0,0 +1,17 @@ +## ADDED Requirements + +### Requirement: Can detect empty state input + +The system SHALL detect when no packages are provided in any input source. + +#### Scenario: Empty state warning +- **WHEN** no package names are found in stdin or state files +- **THEN** system shall print a warning to stderr + +#### Scenario: Empty state warning message +- **WHEN** warning is printed for empty state +- **THEN** message shall say: "Called without state, aborting.." + +#### Scenario: Abort on empty state +- **WHEN** empty state is detected +- **THEN** system shall exit with code 1 (error: no packages to sync) diff --git a/openspec/changes/declpac-cli-tool/specs/stdin-input/spec.md b/openspec/changes/declpac-cli-tool/specs/stdin-input/spec.md new file mode 100644 index 0000000..3ff2c54 --- /dev/null +++ b/openspec/changes/declpac-cli-tool/specs/stdin-input/spec.md @@ -0,0 +1,17 @@ +## ADDED Requirements + +### Requirement: Can read package lists from stdin + +The system SHALL accept package names from standard input, where each line represents a package name. + +#### Scenario: Packages from stdin are read correctly +- **WHEN** package names are passed via stdin separated by whitespace, tabs, or newlines +- **THEN** system shall parse each unique package name from the input stream + +#### Scenario: Empty stdin input is handled +- **WHEN** stdin contains no package names +- **THEN** system shall skip stdin input processing + +#### Scenario: Whitespace normalization +- **WHEN** packages are separated by multiple spaces, tabs, or newlines +- **THEN** each package name shall have leading/trailing whitespace trimmed \ No newline at end of file diff --git a/openspec/changes/declpac-cli-tool/specs/transitive-deps/spec.md b/openspec/changes/declpac-cli-tool/specs/transitive-deps/spec.md new file mode 100644 index 0000000..cdfc153 --- /dev/null +++ b/openspec/changes/declpac-cli-tool/specs/transitive-deps/spec.md @@ -0,0 +1,23 @@ +## ADDED Requirements + +### Requirement: Can resolve transitive dependencies automatically + +The system SHALL automatically determine and install all transitive dependencies +for declared packages, so users only need to specify direct packages. + +#### Scenario: Transitive dependencies are resolved +- **WHEN** a package with dependencies is declared in state +- **THEN** system shall identify all transitive dependencies via pacman +- **AND** include them in the installation operation + +#### Scenario: Dependency resolution includes AUR dependencies +- **WHEN** a package's transitive dependencies include AUR packages +- **THEN** system shall resolve those AUR dependencies via makepkg + +#### Scenario: Shared dependencies are deduplicated +- **WHEN** multiple declared packages share dependencies +- **THEN** system shall install each shared dependency only once + +#### Scenario: Missing dependency handling +- **WHEN** a declared package has a dependency not available in pacman or AUR +- **THEN** system shall report error for the missing dependency \ No newline at end of file diff --git a/openspec/changes/declpac-cli-tool/tasks.md b/openspec/changes/declpac-cli-tool/tasks.md new file mode 100644 index 0000000..ae728e9 --- /dev/null +++ b/openspec/changes/declpac-cli-tool/tasks.md @@ -0,0 +1,81 @@ +## 1. Project Setup + +- [ ] 1.1 Initialize Go module with proper imports +- [ ] 1.2 Add required dependencies (libalpm wrapper) +- [ ] 1.3 Set up project structure (main.go, pkg/ subdirectory) + +## 2. Input Parsing + +- [ ] 2.1 Implement stdin reader to collect package names +- [ ] 2.2 Implement state file reader for text-list format +- [ ] 2.3 Add whitespace normalization for package names +- [ ] 2.4 Create package name set data structure + +## 3. Input Merging + +- [ ] 3.1 Implement additive merging of stdin and state file packages +- [ ] 3.2 Handle multiple --state flags with last-writer-wins per file +- [ ] 3.3 Implement duplicate package handling (no deduplication) + +## 4. State Validation + +- [ ] 4.1 Implement empty state detection (no packages found) +- [ ] 4.2 Add stderr error output for empty state +- [ ] 4.3 Set exit code 1 for empty state case (abort, not proceed) + +## 5. Pacman Integration + +- [ ] 5.1 Implement pacman -Syu command execution wrapper +- [ ] 5.2 Add command-line argument construction with package list +- [ ] 5.3 Capture pacman stdout and stderr output +- [ ] 5.4 Implement pacman error message parsing +- [ ] 5.5 Handle pacman exit codes for success/failure detection +- [ ] 5.6 Verify pacman automatically resolves transitive dependencies +- [ ] 5.7 Verify shared dependencies are deduplicated by pacman + +## 6. Explicit Marking & Orphan Cleanup + +- [ ] 6.1 Get list of currently installed packages before sync +- [ ] 6.2 Mark declared state packages as explicitly installed via pacman -D --explicit +- [ ] 6.3 Run pacman sync operation (5.x series) +- [ ] 6.4 Run pacman -Rsu to remove orphaned packages +- [ ] 6.5 Capture and report number of packages removed +- [ ] 6.6 Handle case where no orphans exist (no packages removed) + +## 7. AUR Integration + +- [ ] 7.1 Implement AUR package lookup via package query (Jguer/aur) +- [ ] 7.2 Add pacman first attempt (package not in repos) +- [ ] 7.3 Implement AUR fallback using makepkg (direct build, not AUR helper) +- [ ] 7.4 Clone AUR package git repo to temp directory +- [ ] 7.5 Run makepkg -si in temp directory for installation +- [ ] 7.6 Add stderr error reporting for packages not in pacman or AUR +- [ ] 7.7 Capture makepkg stdout and stderr for output parsing +- [ ] 7.8 Handle makepkg exit codes for success/failure detection + +## 8. Output Generation + +- [ ] 8.1 Capture installed/removed package counts from pacman output +- [ ] 8.2 Generate machine-readable output line in requested format +- [ ] 8.3 Include error details in output on failure + +## 9. CLI Interface + +- [ ] 9.1 Implement --state flag argument parsing +- [ ] 9.2 Implement stdin input handling from /dev/stdin +- [ ] 9.3 Set up correct CLI usage/help message +- [ ] 9.4 Implement flag order validation + +## 10. Integration & Testing + +- [ ] 10.1 Wire together stdin -> state files -> merging -> validation -> pacman sync -> orphan cleanup -> output +- [ ] 10.2 Test empty state error output and exit code 1 +- [ ] 10.3 Test single state file parsing +- [ ] 10.4 Test multiple state file merging +- [ ] 10.5 Test stdin input parsing +- [ ] 10.6 Test explicit marking before sync +- [ ] 10.7 Test pacman command execution with real packages +- [ ] 10.8 Test orphan cleanup removes unneeded packages +- [ ] 10.9 Test AUR fallback with makepkg for AUR package +- [ ] 10.10 Test error handling for missing packages +- [ ] 10.11 Generate final binary diff --git a/openspec/config.yaml b/openspec/config.yaml index b118dd0..c614a6e 100644 --- a/openspec/config.yaml +++ b/openspec/config.yaml @@ -1,7 +1,18 @@ +--- + schema: spec-driven context: | - Tech stack: Go + - Tech stack: Go + - Minimal external dependencies + - Use Jguer/dyalpm for interfacing with libalpm + - Use Jguer/aur for interfacing with the AUR API + +rules: + style-guide: | + - Favor creating and using helper library functions over local on-offs + - When writing non-code documents, strictly adhere to the 80 character line + limit # Project context (optional) # This is shown to AI when creating artifacts.