initial skeleton
This commit is contained in:
parent
dc79fa2448
commit
71f89dde9c
60 changed files with 3480 additions and 0 deletions
|
|
@ -0,0 +1,469 @@
|
|||
# AppImage Manager Design
|
||||
|
||||
## Goal
|
||||
|
||||
Build AppImage Manager as a Rust workspace where `aim-core` contains the business logic and reusable APIs, and `aim-cli` is a thin terminal client. The first shipped application is the CLI, but the architecture must leave a clean path for a later GUI client to consume the same core install, update, registry, and adapter logic.
|
||||
|
||||
## Agreed Product Shape
|
||||
|
||||
### Command surface
|
||||
|
||||
- `aim {QUERY}`: search and add from a query source
|
||||
- `aim`: review-first update flow; aliases `aim update`
|
||||
- `aim update`: explicit update flow
|
||||
- `aim remove {QUERY}`: remove by registered app name
|
||||
- `aim list`: list installed AppImages
|
||||
|
||||
### Supported source types
|
||||
|
||||
1. GitHub Releases
|
||||
2. Direct URL / generic website downloads
|
||||
3. GitLab Releases
|
||||
4. zsync / embedded AppImage update info
|
||||
5. SourceForge
|
||||
6. Custom JSON feed adapters
|
||||
|
||||
### Install behavior
|
||||
|
||||
- Default installation mode is auto-detected by effective privileges
|
||||
- `--system` and `--user` override the auto-detected scope
|
||||
- The tool supports both user and system installations
|
||||
- The tool performs full desktop-style integration for installed apps
|
||||
|
||||
### Identity and update behavior
|
||||
|
||||
- The system should infer app identity and version when possible
|
||||
- If confidence is low, the client should prompt interactively for confirmation or edits
|
||||
- If identity still cannot be stabilized, the registry should fall back to the raw URL as the last-resort key
|
||||
- Running `aim` with no query should discover updates, present a review list, and then apply only selected updates
|
||||
- Architecture handling should remain generic: `aim-core` manages whatever AppImage artifact is resolved, while validating obvious mismatches at install time
|
||||
|
||||
## Recommended Architecture
|
||||
|
||||
Use typed source adapters behind a common update engine, packaged in `aim-core` and consumed by thin frontend clients.
|
||||
|
||||
This architecture fits the source diversity without forcing a plugin runtime into v1. Each upstream source gets an explicit Rust adapter that implements a shared contract for identity resolution, release discovery, artifact selection, and update metadata extraction. The shared update engine operates on normalized internal types rather than source-specific details.
|
||||
|
||||
This approach was selected over:
|
||||
|
||||
- a registry-centric-first design, which risks smearing source-specific logic across storage and service layers
|
||||
- a plugin-first design, which adds packaging, security, and testing complexity too early
|
||||
|
||||
## Workspace Architecture
|
||||
|
||||
The project should be a Cargo workspace with frontend clients over a shared core crate.
|
||||
|
||||
### Workspace crates
|
||||
|
||||
- `crates/aim-core`: all business logic and reusable APIs
|
||||
- `crates/aim-cli`: thin terminal frontend for the initial shipped application
|
||||
- `crates/aim-gui`: deferred future GUI client, planned but not implemented in v1
|
||||
|
||||
The critical rule is that `aim-cli` must not become the home for install, update, registry, or source logic. If behavior should be reusable by a future GUI, it belongs in `aim-core`.
|
||||
|
||||
## Architecture Layers
|
||||
|
||||
The system should be organized into four layers, with the bottom three living in `aim-core`.
|
||||
|
||||
### 1. Client layer
|
||||
|
||||
- Implemented first in `aim-cli`
|
||||
- Parses commands, flags, and defaults
|
||||
- Owns presentation only: prompts, colors, spinners, progress bars, and terminal summaries
|
||||
- Uses:
|
||||
- `clap` for CLI parsing
|
||||
- `dialoguer` for interactive prompts and multi-select review flows
|
||||
- `console` for styled output and readable summaries
|
||||
- `indicatif` for progress bars and spinners
|
||||
|
||||
This layer should translate user intent into calls into `aim-core` and render responses. It should not contain source-specific business logic, registry mutation logic, or install/update decision logic.
|
||||
|
||||
### 2. Application/service layer
|
||||
|
||||
- Lives in `aim-core`
|
||||
- Coordinates workflows like add, remove, list, and update
|
||||
- Applies product rules such as scope selection, update review behavior, and low-confidence identity confirmation
|
||||
- Suggested services:
|
||||
- `AddService`
|
||||
- `UpdateService`
|
||||
- `RegistryService`
|
||||
- `IntegrationService`
|
||||
|
||||
### 3. Domain model layer
|
||||
|
||||
- Lives in `aim-core`
|
||||
- Holds the canonical source-agnostic types used across the system
|
||||
|
||||
Suggested domain types:
|
||||
|
||||
- `AppRecord`
|
||||
- `InstallScope`
|
||||
- `SourceRef`
|
||||
- `SourceKind`
|
||||
- `ResolvedRelease`
|
||||
- `InstalledArtifact`
|
||||
- `UpdatePlan`
|
||||
- `DesktopIntegration`
|
||||
- `InteractionRequest`
|
||||
- `InteractionResponse`
|
||||
|
||||
### 4. Infrastructure layer
|
||||
|
||||
- Lives in `aim-core`
|
||||
- Source adapters
|
||||
- Filesystem and install location management
|
||||
- Registry persistence
|
||||
- Desktop integration helpers
|
||||
- Download and HTTP client behavior
|
||||
- Optional subprocess wrappers for system integration tasks
|
||||
|
||||
## Suggested Module Layout
|
||||
|
||||
Suggested workspace layout:
|
||||
|
||||
- `Cargo.toml`
|
||||
- `crates/aim-core/Cargo.toml`
|
||||
- `crates/aim-core/src/lib.rs`
|
||||
- `crates/aim-core/src/app/`
|
||||
- `crates/aim-core/src/domain/`
|
||||
- `crates/aim-core/src/adapters/`
|
||||
- `crates/aim-core/src/integration/`
|
||||
- `crates/aim-core/src/registry/`
|
||||
- `crates/aim-core/src/platform/`
|
||||
- `crates/aim-cli/Cargo.toml`
|
||||
- `crates/aim-cli/src/lib.rs`
|
||||
- `crates/aim-cli/src/main.rs`
|
||||
- `crates/aim-cli/src/cli/`
|
||||
- `crates/aim-cli/src/ui/`
|
||||
|
||||
Future-facing placeholder:
|
||||
|
||||
- `crates/aim-gui/`
|
||||
|
||||
This keeps terminal UX separate from the install/update engine and ensures the later GUI can reuse the same core APIs.
|
||||
|
||||
## Core Components
|
||||
|
||||
### Query resolver
|
||||
|
||||
Lives in `aim-core` and turns user input into a normalized `SourceRef`.
|
||||
|
||||
Accepted input forms:
|
||||
|
||||
- URL
|
||||
- `user_or_org/project`
|
||||
- file URI
|
||||
- bare `aim` with no query
|
||||
|
||||
Behavior:
|
||||
|
||||
- Resolve GitHub URLs and `owner/repo` forms to GitHub when unambiguous
|
||||
- Resolve GitLab URLs and explicit `gitlab:` references to GitLab
|
||||
- Resolve direct URLs and generic web pages to the direct URL / web adapter
|
||||
- Resolve `file://` inputs into local import flow
|
||||
|
||||
The query resolver should not perform install logic.
|
||||
|
||||
### Source adapter layer
|
||||
|
||||
Lives in `aim-core`, with one typed adapter per source:
|
||||
|
||||
- GitHub Releases adapter
|
||||
- GitLab Releases adapter
|
||||
- Direct URL / generic web adapter
|
||||
- zsync / embedded update info adapter
|
||||
- SourceForge adapter
|
||||
- Custom JSON feed adapter
|
||||
|
||||
Each adapter should expose a shared capability shape:
|
||||
|
||||
- identify app
|
||||
- enumerate candidate releases
|
||||
- choose preferred artifact
|
||||
- expose update metadata
|
||||
- download or resolve the artifact for download
|
||||
|
||||
Not every source needs to support true search. Some only support exact resolution. The contract should represent those differences honestly.
|
||||
|
||||
### Registry
|
||||
|
||||
Lives in `aim-core` and stores normalized installed app records across user and system scopes.
|
||||
|
||||
It should track:
|
||||
|
||||
- canonical app identity
|
||||
- display name
|
||||
- install scope
|
||||
- source type
|
||||
- source locator and source-specific update hints
|
||||
- installed version
|
||||
- installed artifact path
|
||||
- artifact fingerprint or hash
|
||||
- release metadata
|
||||
- integration artifact paths
|
||||
- timestamps
|
||||
|
||||
The registry is the bridge between one-time install and repeatable updates, so it must be migration-friendly.
|
||||
|
||||
### Installer and integrator
|
||||
|
||||
Live in `aim-core`.
|
||||
|
||||
Installer responsibilities:
|
||||
|
||||
- staging downloads
|
||||
- validating artifacts
|
||||
- moving binaries into managed locations
|
||||
- setting permissions
|
||||
- replacing installed artifacts atomically where possible
|
||||
|
||||
Integrator responsibilities:
|
||||
|
||||
- `.desktop` entry generation
|
||||
- icon extraction or acquisition
|
||||
- symlink creation
|
||||
- MIME and related registration where feasible
|
||||
- correct handling of user vs system targets
|
||||
|
||||
Installer and integration concerns should remain separate so updates can replace binaries without always rebuilding every integration artifact.
|
||||
|
||||
### Update planner and executor
|
||||
|
||||
Live in `aim-core`.
|
||||
|
||||
Planner responsibilities:
|
||||
|
||||
- load registry entries
|
||||
- ask adapters for update candidates
|
||||
- compare installed state to available state
|
||||
- build a reviewable `UpdatePlan`
|
||||
|
||||
Executor responsibilities:
|
||||
|
||||
- apply selected updates
|
||||
- download and validate updated artifacts
|
||||
- replace existing artifacts safely
|
||||
- refresh integration artifacts only when needed
|
||||
- update registry state
|
||||
- surface typed results and events for clients
|
||||
|
||||
### Client interaction boundary
|
||||
|
||||
Terminal-specific UI belongs in `aim-cli`, not `aim-core`.
|
||||
|
||||
`aim-core` should expose operation APIs and typed interaction or progress models that clients can render however they want. `aim-cli` should wrap all usage of `dialoguer`, `console`, and `indicatif`.
|
||||
|
||||
This keeps business logic testable without terminal coupling and makes a GUI frontend viable later.
|
||||
|
||||
### Custom JSON feed support
|
||||
|
||||
Custom JSON feeds should be declarative in v1, not arbitrary executable plugins.
|
||||
|
||||
The adapter should support field mapping and release selection rules against a constrained schema family, rather than loading arbitrary code. This delivers flexibility without turning the CLI into a plugin host.
|
||||
|
||||
## End-to-End Data Flow
|
||||
|
||||
### `aim {QUERY}` add flow
|
||||
|
||||
1. `aim-cli` parses CLI input and scope override flags
|
||||
2. `aim-cli` calls `aim-core` with a normalized request
|
||||
3. `aim-core` resolves the query into a `SourceRef`
|
||||
4. `aim-core` selects the appropriate adapter
|
||||
5. `aim-core` identifies the app and candidate releases
|
||||
6. `aim-core` returns an interaction state if confidence is low
|
||||
7. `aim-cli` prompts, then sends the decision back to `aim-core`
|
||||
8. `aim-core` falls back to raw URL identity if needed
|
||||
9. `aim-core` downloads to staging
|
||||
10. `aim-core` validates the artifact as an AppImage and inspects update metadata
|
||||
11. `aim-core` installs into the correct managed location
|
||||
12. `aim-core` generates integration artifacts and persists a normalized registry entry
|
||||
|
||||
### `aim` and `aim update` flow
|
||||
|
||||
1. `aim-cli` invokes update discovery in `aim-core`
|
||||
2. `aim-core` loads relevant registry entries
|
||||
3. `aim-core` asks each adapter for update candidates
|
||||
4. `aim-core` builds an `UpdatePlan`
|
||||
5. `aim-cli` renders the review list and collects selection
|
||||
6. `aim-core` applies selected updates
|
||||
7. `aim-core` refreshes registry state and integration artifacts as needed
|
||||
8. `aim-cli` prints a final success/failure summary
|
||||
|
||||
### `aim list` flow
|
||||
|
||||
- `aim-cli` requests installed app state from `aim-core` and renders the result grouped by scope, source, and version
|
||||
|
||||
### `aim remove {QUERY}` flow
|
||||
|
||||
1. `aim-cli` forwards the query to `aim-core`
|
||||
2. `aim-core` resolves the query against registered app names
|
||||
3. `aim-core` emits an interaction request if ambiguity must be resolved
|
||||
4. `aim-cli` prompts if needed and returns the selection
|
||||
5. `aim-core` removes artifacts and integration files in the correct order
|
||||
6. `aim-core` removes registry state while preserving uncertain shared resources conservatively
|
||||
|
||||
## Registry Data Shape
|
||||
|
||||
Each registry record should contain enough source-specific state to make updates reliable without re-deriving identity from filenames.
|
||||
|
||||
Recommended fields:
|
||||
|
||||
- stable app id
|
||||
- display name
|
||||
- install scope
|
||||
- source kind
|
||||
- source locator
|
||||
- installed version
|
||||
- installed file path
|
||||
- file hash or fingerprint
|
||||
- release metadata
|
||||
- updater metadata
|
||||
- integration artifact paths
|
||||
- created and updated timestamps
|
||||
|
||||
Examples of source-specific metadata:
|
||||
|
||||
- GitHub/GitLab: owner, repo, release/tag, asset selection hints
|
||||
- Direct URL: original URL, resolved URL, etag, last-modified when available
|
||||
- zsync: zsync URL or embedded update info extracted from the AppImage
|
||||
- SourceForge: project and file path hints
|
||||
- Custom JSON feed: feed URL plus mapping profile
|
||||
|
||||
## Error Handling Model
|
||||
|
||||
Error handling should be structured internally and concise externally.
|
||||
|
||||
`aim-core` should own structured error types and machine-readable outcomes. `aim-cli` should map those into concise terminal messages. A future GUI should be able to present the same failures without reparsing CLI text.
|
||||
|
||||
Suggested error categories:
|
||||
|
||||
- query resolution error
|
||||
- source adapter error
|
||||
- network/download error
|
||||
- artifact validation error
|
||||
- install permission or scope error
|
||||
- desktop integration error
|
||||
- registry persistence error
|
||||
- update planning error
|
||||
|
||||
Behavioral expectations:
|
||||
|
||||
- prompt on low-confidence identity rather than silently guessing
|
||||
- fail clearly on insufficient privileges for system install unless explicit elevation behavior is designed later
|
||||
- continue update processing across apps when one app fails
|
||||
- fail-fast within a single app transaction unless a step is intentionally non-fatal
|
||||
- either roll back on integration failure or explicitly record the app as installed-but-needing-repair
|
||||
|
||||
For v1, prefer:
|
||||
|
||||
- best-effort continuation across apps during update runs
|
||||
- fail-fast inside a single app update or install transaction
|
||||
- atomic replacement where possible
|
||||
- future room for an `aim repair` command, even if not implemented in v1
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
Testing should map directly to the architecture layers.
|
||||
|
||||
### `aim-core` unit tests
|
||||
|
||||
- query parsing and source resolution
|
||||
- identity normalization and fallback logic
|
||||
- version comparison and update selection logic
|
||||
- install scope resolution
|
||||
- registry serialization and migrations
|
||||
- adapter-specific parsing helpers
|
||||
|
||||
### Shared adapter contract tests
|
||||
|
||||
Every adapter should pass a common behavior suite where applicable:
|
||||
|
||||
- can identify app
|
||||
- can resolve latest candidate
|
||||
- reports unsupported capabilities honestly
|
||||
- produces normalized release metadata
|
||||
|
||||
This is the primary protection against drift across heterogeneous source implementations.
|
||||
|
||||
### `aim-core` integration tests
|
||||
|
||||
- add flow per source type using fixtures or mocked HTTP
|
||||
- update planning across mixed registry entries
|
||||
- remove flow cleaning registry and integration artifacts
|
||||
- user vs system path resolution
|
||||
- registry migration compatibility
|
||||
|
||||
### Filesystem tests
|
||||
|
||||
Use temp directories to simulate:
|
||||
|
||||
- user install roots
|
||||
- system install roots
|
||||
- desktop entry locations
|
||||
- icon and symlink generation
|
||||
|
||||
### `aim-cli` client behavior tests
|
||||
|
||||
- snapshot or golden tests for key terminal flows
|
||||
- update review list interaction
|
||||
- low-confidence identity prompt
|
||||
- success and failure summaries
|
||||
|
||||
Most behavioral coverage should target `aim-core`, with only thin client verification in `aim-cli`.
|
||||
|
||||
Avoid relying on live network tests in the main suite. Keep those as optional smoke coverage.
|
||||
|
||||
### Main risks the test plan must cover
|
||||
|
||||
1. Incorrect identity causing duplicate or non-updatable entries
|
||||
2. Source-specific regressions hidden behind a shared API surface
|
||||
3. Incomplete rollback leaving broken installs
|
||||
4. Scope confusion causing files to land in the wrong locations
|
||||
5. Business logic leaking into `aim-cli` and diverging from future GUI needs
|
||||
|
||||
## Recommended Persisted Formats And Key Decisions
|
||||
|
||||
### Persisted formats
|
||||
|
||||
- Use a structured registry file or registry store that is easy to migrate and inspect
|
||||
- Keep source-specific update metadata embedded in each app record rather than scattered across auxiliary files
|
||||
- Store integration artifact paths explicitly so removal and repair remain deterministic
|
||||
|
||||
### Key design decisions
|
||||
|
||||
- Use a Cargo workspace with `aim-core` and `aim-cli`
|
||||
- Put all business logic in `aim-core`
|
||||
- Keep `aim-cli` as a thin terminal adapter over `aim-core`
|
||||
- Design `aim-core` to be reusable by a future `aim-gui`
|
||||
- Use typed Rust adapters behind a common update engine
|
||||
- Normalize identity early and once
|
||||
- Separate update planning from update execution
|
||||
- Treat custom JSON feeds as declarative adapters, not executable plugins
|
||||
- Auto-detect scope by effective privileges, with `--system` and `--user` overrides
|
||||
- Make bare `aim` a review-first update path
|
||||
|
||||
## Explicit v1 Boundaries
|
||||
|
||||
Included in v1:
|
||||
|
||||
- Cargo workspace with `aim-core` and `aim-cli`
|
||||
- multi-source AppImage add flow
|
||||
- user and system scope support
|
||||
- update planning and selected update execution
|
||||
- desktop-style integration
|
||||
- typed adapters for the agreed source list
|
||||
- declarative custom JSON feed support
|
||||
|
||||
Deferred from v1:
|
||||
|
||||
- `aim-gui` implementation
|
||||
- general plugin runtime
|
||||
- arbitrary executable custom adapters
|
||||
- broad distro-specific deep integration beyond the agreed desktop registration model
|
||||
- live network-dependent test suite as the main verification strategy
|
||||
- repair and doctor commands, though the design should leave room for them
|
||||
|
||||
## Open Implementation Notes
|
||||
|
||||
- Because the current workspace is not a git repository, the design document can be saved but not committed yet
|
||||
- The next step should be an implementation plan that breaks this design into small TDD-oriented tasks
|
||||
|
|
@ -0,0 +1,870 @@
|
|||
# AppImage Manager Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Build a Rust CLI named `aim` that installs, lists, removes, and review-updates AppImages from multiple source types with full desktop-style integration for user and system scopes.
|
||||
|
||||
**Architecture:** Use a single Rust binary with a thin CLI layer over application services, typed source adapters, a normalized registry, and separate installer/integration/update subsystems. Build the project incrementally with test-first steps so the registry model, source resolution, and update planning remain stable as additional adapters land.
|
||||
|
||||
**Tech Stack:** Rust, Cargo, clap, dialoguer, console, indicatif, serde, toml or sqlite-backed persistence, reqwest, tokio, tempfile, assert_cmd, predicates, insta or similar snapshot tooling.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Scaffold the Cargo project and dependency baseline
|
||||
|
||||
**Files:**
|
||||
- Create: `Cargo.toml`
|
||||
- Create: `src/main.rs`
|
||||
- Create: `src/lib.rs`
|
||||
- Create: `tests/cli_smoke.rs`
|
||||
- Create: `.gitignore`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```rust
|
||||
use assert_cmd::Command;
|
||||
|
||||
#[test]
|
||||
fn cli_shows_help() {
|
||||
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||
cmd.arg("--help").assert().success();
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test cli_shows_help --test cli_smoke`
|
||||
Expected: FAIL because the crate and binary do not exist yet
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Create a minimal Cargo package with the `aim` binary, library entry point, and an empty `main` using `clap` derive to print help successfully.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test cli_shows_help --test cli_smoke`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add Cargo.toml src/main.rs src/lib.rs tests/cli_smoke.rs .gitignore
|
||||
git commit -m "chore: scaffold aim cargo project"
|
||||
```
|
||||
|
||||
### Task 2: Add the command surface and top-level CLI parsing
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/main.rs`
|
||||
- Create: `src/cli/mod.rs`
|
||||
- Create: `src/cli/args.rs`
|
||||
- Test: `tests/cli_commands.rs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```rust
|
||||
use assert_cmd::Command;
|
||||
use predicates::str::contains;
|
||||
|
||||
#[test]
|
||||
fn help_lists_expected_commands() {
|
||||
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||
cmd.arg("--help")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(contains("remove"))
|
||||
.stdout(contains("list"))
|
||||
.stdout(contains("update"));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test help_lists_expected_commands --test cli_commands`
|
||||
Expected: FAIL because subcommands and positional query parsing are not implemented
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Implement:
|
||||
- positional optional query for bare `aim {QUERY}`
|
||||
- `remove {QUERY}`
|
||||
- `list`
|
||||
- `update`
|
||||
- shared `--system` and `--user` scope override flags where appropriate
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test help_lists_expected_commands --test cli_commands`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add src/main.rs src/cli/mod.rs src/cli/args.rs tests/cli_commands.rs
|
||||
git commit -m "feat: add top-level cli command parsing"
|
||||
```
|
||||
|
||||
### Task 3: Define the core domain types and install scope resolution
|
||||
|
||||
**Files:**
|
||||
- Create: `src/domain/mod.rs`
|
||||
- Create: `src/domain/app.rs`
|
||||
- Create: `src/domain/source.rs`
|
||||
- Create: `src/domain/update.rs`
|
||||
- Create: `src/app/mod.rs`
|
||||
- Create: `src/app/scope.rs`
|
||||
- Test: `tests/install_scope.rs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```rust
|
||||
use aim::app::scope::{resolve_install_scope, ScopeOverride};
|
||||
use aim::domain::app::InstallScope;
|
||||
|
||||
#[test]
|
||||
fn explicit_scope_override_beats_effective_user() {
|
||||
let scope = resolve_install_scope(false, ScopeOverride::System);
|
||||
assert_eq!(scope, InstallScope::System);
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
# AppImage Manager Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Build a Rust workspace where `aim-core` implements AppImage management logic and `aim-cli` provides a thin terminal frontend for install, list, remove, and review-update flows.
|
||||
|
||||
**Architecture:** Use a Cargo workspace with `aim-core` holding domain models, services, adapters, registry, installer, and update logic, while `aim-cli` only parses arguments, renders terminal UX, and delegates to `aim-core`. Keep client-facing boundaries explicit so a later GUI crate can reuse `aim-core` without moving logic back out of the library.
|
||||
|
||||
**Tech Stack:** Rust, Cargo, clap, dialoguer, console, indicatif, serde, toml or sqlite-backed persistence, reqwest, tokio, tempfile, assert_cmd, predicates, insta or similar snapshot tooling.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Scaffold the Cargo workspace baseline
|
||||
|
||||
**Files:**
|
||||
- Create: `Cargo.toml`
|
||||
- Create: `crates/aim-core/Cargo.toml`
|
||||
- Create: `crates/aim-core/src/lib.rs`
|
||||
- Create: `crates/aim-cli/Cargo.toml`
|
||||
- Create: `crates/aim-cli/src/lib.rs`
|
||||
- Create: `crates/aim-cli/src/main.rs`
|
||||
- Create: `tests/cli_smoke.rs`
|
||||
- Create: `.gitignore`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```rust
|
||||
use assert_cmd::Command;
|
||||
|
||||
#[test]
|
||||
fn cli_shows_help() {
|
||||
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||
cmd.arg("--help").assert().success();
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test cli_shows_help --test cli_smoke`
|
||||
Expected: FAIL because the workspace and binary do not exist yet
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Create a minimal Cargo workspace with `aim-core` and `aim-cli`, wiring the `aim` binary through `aim-cli` and exposing a library entry point from `aim-core`.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test cli_shows_help --test cli_smoke`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add Cargo.toml crates/aim-core/Cargo.toml crates/aim-core/src/lib.rs crates/aim-cli/Cargo.toml crates/aim-cli/src/lib.rs crates/aim-cli/src/main.rs tests/cli_smoke.rs .gitignore
|
||||
git commit -m "chore: scaffold aim workspace"
|
||||
```
|
||||
|
||||
### Task 2: Add the thin CLI command surface
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/aim-cli/src/main.rs`
|
||||
- Create: `crates/aim-cli/src/cli/mod.rs`
|
||||
- Create: `crates/aim-cli/src/cli/args.rs`
|
||||
- Test: `tests/cli_commands.rs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```rust
|
||||
use assert_cmd::Command;
|
||||
use predicates::str::contains;
|
||||
|
||||
#[test]
|
||||
fn help_lists_expected_commands() {
|
||||
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||
cmd.arg("--help")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(contains("remove"))
|
||||
.stdout(contains("list"))
|
||||
.stdout(contains("update"));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test help_lists_expected_commands --test cli_commands`
|
||||
Expected: FAIL because subcommands and positional query parsing are not implemented
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Implement only:
|
||||
- positional optional query for bare `aim {QUERY}`
|
||||
- `remove {QUERY}`
|
||||
- `list`
|
||||
- `update`
|
||||
- shared `--system` and `--user` scope override flags where appropriate
|
||||
|
||||
Do not add business logic here beyond command parsing and delegation stubs.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test help_lists_expected_commands --test cli_commands`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-cli/src/main.rs crates/aim-cli/src/cli/mod.rs crates/aim-cli/src/cli/args.rs tests/cli_commands.rs
|
||||
git commit -m "feat: add thin cli command parsing"
|
||||
```
|
||||
|
||||
### Task 3: Define the core domain types and install scope resolution
|
||||
|
||||
**Files:**
|
||||
- Create: `crates/aim-core/src/domain/mod.rs`
|
||||
- Create: `crates/aim-core/src/domain/app.rs`
|
||||
- Create: `crates/aim-core/src/domain/source.rs`
|
||||
- Create: `crates/aim-core/src/domain/update.rs`
|
||||
- Create: `crates/aim-core/src/app/mod.rs`
|
||||
- Create: `crates/aim-core/src/app/scope.rs`
|
||||
- Test: `tests/install_scope.rs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```rust
|
||||
use aim_core::app::scope::{resolve_install_scope, ScopeOverride};
|
||||
use aim_core::domain::app::InstallScope;
|
||||
|
||||
#[test]
|
||||
fn explicit_scope_override_beats_effective_user() {
|
||||
let scope = resolve_install_scope(false, ScopeOverride::System);
|
||||
assert_eq!(scope, InstallScope::System);
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test explicit_scope_override_beats_effective_user --test install_scope`
|
||||
Expected: FAIL because core domain types and scope logic do not exist yet
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Add domain types for:
|
||||
- `InstallScope`
|
||||
- `AppRecord`
|
||||
- `SourceKind`
|
||||
- `SourceRef`
|
||||
- `ResolvedRelease`
|
||||
- `UpdatePlan`
|
||||
|
||||
Add scope resolution logic that:
|
||||
- auto-detects by effective privileges
|
||||
- honors `--system` and `--user` overrides
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test explicit_scope_override_beats_effective_user --test install_scope`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-core/src/domain crates/aim-core/src/app tests/install_scope.rs
|
||||
git commit -m "feat: add core domain types and scope resolution"
|
||||
```
|
||||
|
||||
### Task 4: Implement query parsing and source reference resolution in `aim-core`
|
||||
|
||||
**Files:**
|
||||
- Create: `crates/aim-core/src/app/query.rs`
|
||||
- Modify: `crates/aim-core/src/domain/source.rs`
|
||||
- Test: `tests/query_resolution.rs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```rust
|
||||
use aim_core::app::query::resolve_query;
|
||||
use aim_core::domain::source::SourceKind;
|
||||
|
||||
#[test]
|
||||
fn owner_repo_defaults_to_github() {
|
||||
let source = resolve_query("sharkdp/bat").unwrap();
|
||||
assert_eq!(source.kind, SourceKind::GitHub);
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test owner_repo_defaults_to_github --test query_resolution`
|
||||
Expected: FAIL because query resolution is not implemented
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Support parsing for:
|
||||
- `owner/repo` as GitHub by default
|
||||
- GitHub URLs
|
||||
- GitLab URLs and explicit `gitlab:` prefix
|
||||
- direct URLs
|
||||
- `file://` URIs
|
||||
|
||||
Return a normalized `SourceRef` without triggering downloads or installation.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test owner_repo_defaults_to_github --test query_resolution`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-core/src/app/query.rs crates/aim-core/src/domain/source.rs tests/query_resolution.rs
|
||||
git commit -m "feat: resolve user queries into source references"
|
||||
```
|
||||
|
||||
### Task 5: Add registry persistence and migration-friendly app records in `aim-core`
|
||||
|
||||
**Files:**
|
||||
- Create: `crates/aim-core/src/registry/mod.rs`
|
||||
- Create: `crates/aim-core/src/registry/store.rs`
|
||||
- Create: `crates/aim-core/src/registry/model.rs`
|
||||
- Test: `tests/registry_roundtrip.rs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```rust
|
||||
use aim_core::registry::store::RegistryStore;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn registry_round_trips_app_records() {
|
||||
let dir = tempdir().unwrap();
|
||||
let store = RegistryStore::new(dir.path().join("registry.toml"));
|
||||
let loaded = store.load().unwrap();
|
||||
assert!(loaded.apps.is_empty());
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test registry_round_trips_app_records --test registry_roundtrip`
|
||||
Expected: FAIL because no registry store exists
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Implement a registry store with:
|
||||
- serialized root structure
|
||||
- normalized `AppRecord` persistence
|
||||
- version field for future migrations
|
||||
- read and write APIs
|
||||
|
||||
Choose a storage format that is easy to inspect and migrate, such as TOML or SQLite.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test registry_round_trips_app_records --test registry_roundtrip`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-core/src/registry tests/registry_roundtrip.rs
|
||||
git commit -m "feat: add persistent core registry store"
|
||||
```
|
||||
|
||||
### Task 6: Build the source adapter trait and contract harness in `aim-core`
|
||||
|
||||
**Files:**
|
||||
- Create: `crates/aim-core/src/adapters/mod.rs`
|
||||
- Create: `crates/aim-core/src/adapters/traits.rs`
|
||||
- Create: `crates/aim-core/src/adapters/test_support.rs`
|
||||
- Test: `tests/adapter_contract.rs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```rust
|
||||
use aim_core::adapters::traits::AdapterCapabilities;
|
||||
|
||||
#[test]
|
||||
fn adapter_capabilities_can_report_exact_resolution_only() {
|
||||
let capabilities = AdapterCapabilities::exact_resolution_only();
|
||||
assert!(!capabilities.supports_search);
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test adapter_capabilities_can_report_exact_resolution_only --test adapter_contract`
|
||||
Expected: FAIL because adapter abstractions do not exist
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Define:
|
||||
- `SourceAdapter` trait
|
||||
- capability flags
|
||||
- normalized adapter response types
|
||||
- reusable test helpers for contract behavior
|
||||
|
||||
Do not implement network-backed adapters yet. Focus on the stable core trait surface.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test adapter_capabilities_can_report_exact_resolution_only --test adapter_contract`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-core/src/adapters tests/adapter_contract.rs
|
||||
git commit -m "feat: add source adapter trait and contract surface"
|
||||
```
|
||||
|
||||
### Task 7: Define client interaction models in `aim-core` and thin terminal rendering in `aim-cli`
|
||||
|
||||
**Files:**
|
||||
- Create: `crates/aim-core/src/app/interaction.rs`
|
||||
- Create: `crates/aim-cli/src/ui/mod.rs`
|
||||
- Create: `crates/aim-cli/src/ui/render.rs`
|
||||
- Create: `crates/aim-cli/src/ui/prompt.rs`
|
||||
- Test: `tests/ui_summary.rs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```rust
|
||||
use aim_cli::ui::render::render_update_summary;
|
||||
|
||||
#[test]
|
||||
fn update_summary_mentions_selected_count() {
|
||||
let output = render_update_summary(3, 2, 1);
|
||||
assert!(output.contains("selected: 2"));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test update_summary_mentions_selected_count --test ui_summary`
|
||||
Expected: FAIL because client rendering helpers do not exist
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Create:
|
||||
- typed interaction and progress models in `aim-core`
|
||||
- a thin CLI UI facade in `aim-cli` that centralizes styling with `console`
|
||||
- prompt orchestration using `dialoguer`
|
||||
|
||||
Do not move any business rules into `aim-cli`.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test update_summary_mentions_selected_count --test ui_summary`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-core/src/app/interaction.rs crates/aim-cli/src/ui tests/ui_summary.rs
|
||||
git commit -m "feat: add core interaction models and thin cli ui"
|
||||
```
|
||||
|
||||
### Task 8: Implement installer and desktop integration path resolution in `aim-core`
|
||||
|
||||
**Files:**
|
||||
- Create: `crates/aim-core/src/integration/mod.rs`
|
||||
- Create: `crates/aim-core/src/integration/paths.rs`
|
||||
- Create: `crates/aim-core/src/integration/install.rs`
|
||||
- Create: `crates/aim-core/src/platform/mod.rs`
|
||||
- Test: `tests/install_paths.rs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```rust
|
||||
use aim_core::domain::app::InstallScope;
|
||||
use aim_core::integration::paths::managed_appimage_path;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn user_scope_path_lands_under_home_managed_dir() {
|
||||
let path = managed_appimage_path(Path::new("/home/test"), InstallScope::User, "bat");
|
||||
assert!(path.to_string_lossy().contains("bat"));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test user_scope_path_lands_under_home_managed_dir --test install_paths`
|
||||
Expected: FAIL because install path logic does not exist
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Implement:
|
||||
- managed install path resolution for user and system scopes
|
||||
- integration artifact path calculation
|
||||
- atomic staging and replacement helpers
|
||||
|
||||
Keep actual desktop registration side effects behind abstractions so they remain testable.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test user_scope_path_lands_under_home_managed_dir --test install_paths`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-core/src/integration crates/aim-core/src/platform tests/install_paths.rs
|
||||
git commit -m "feat: add core install and integration path handling"
|
||||
```
|
||||
|
||||
### Task 9: Implement identity normalization and raw URL fallback in `aim-core`
|
||||
|
||||
**Files:**
|
||||
- Create: `crates/aim-core/src/app/identity.rs`
|
||||
- Modify: `crates/aim-core/src/domain/app.rs`
|
||||
- Test: `tests/identity_resolution.rs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```rust
|
||||
use aim_core::app::identity::{resolve_identity, IdentityFallback};
|
||||
|
||||
#[test]
|
||||
fn unresolved_identity_can_fall_back_to_url() {
|
||||
let identity = resolve_identity(None, None, Some("https://example.com/app.AppImage"), IdentityFallback::AllowRawUrl).unwrap();
|
||||
assert!(identity.stable_id.contains("example.com"));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test unresolved_identity_can_fall_back_to_url --test identity_resolution`
|
||||
Expected: FAIL because identity resolution does not exist
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Implement identity normalization with:
|
||||
- confident resolution path
|
||||
- low-confidence state handling
|
||||
- raw URL fallback when allowed
|
||||
|
||||
Keep the prompting decision outside this module so the logic remains deterministic and reusable across CLI and GUI clients.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test unresolved_identity_can_fall_back_to_url --test identity_resolution`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-core/src/app/identity.rs crates/aim-core/src/domain/app.rs tests/identity_resolution.rs
|
||||
git commit -m "feat: add core identity normalization and fallback logic"
|
||||
```
|
||||
|
||||
### Task 10: Implement update planning in `aim-core` and review-first dispatch in `aim-cli`
|
||||
|
||||
**Files:**
|
||||
- Create: `crates/aim-core/src/app/update.rs`
|
||||
- Modify: `crates/aim-cli/src/cli/args.rs`
|
||||
- Modify: `crates/aim-cli/src/main.rs`
|
||||
- Test: `tests/update_planning.rs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```rust
|
||||
use aim_core::app::update::build_update_plan;
|
||||
|
||||
#[test]
|
||||
fn empty_registry_produces_empty_plan() {
|
||||
let plan = build_update_plan(&[]).unwrap();
|
||||
assert!(plan.items.is_empty());
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test empty_registry_produces_empty_plan --test update_planning`
|
||||
Expected: FAIL because update planning does not exist
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Implement:
|
||||
- update plan model in `aim-core`
|
||||
- comparison of installed state against adapter-provided candidate data
|
||||
- bare `aim` dispatch in `aim-cli` into the `aim-core` update planning path when no positional query is present
|
||||
|
||||
Do not execute downloads yet in this task. Focus on planning and command dispatch.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test empty_registry_produces_empty_plan --test update_planning`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-core/src/app/update.rs crates/aim-cli/src/cli/args.rs crates/aim-cli/src/main.rs tests/update_planning.rs
|
||||
git commit -m "feat: add core update planning and cli dispatch"
|
||||
```
|
||||
|
||||
### Task 11: Add the GitHub adapter and one core add flow
|
||||
|
||||
**Files:**
|
||||
- Create: `crates/aim-core/src/adapters/github.rs`
|
||||
- Create: `crates/aim-core/src/app/add.rs`
|
||||
- Modify: `crates/aim-core/src/adapters/mod.rs`
|
||||
- Modify: `crates/aim-cli/src/main.rs`
|
||||
- Test: `tests/github_add_flow.rs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn github_adapter_can_normalize_owner_repo_source() {
|
||||
let source = aim_core::app::query::resolve_query("sharkdp/bat").unwrap();
|
||||
assert_eq!(source.kind.as_str(), "github");
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test github_adapter_can_normalize_owner_repo_source --test github_add_flow`
|
||||
Expected: FAIL because the add flow and GitHub adapter are not wired into the core services
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Implement:
|
||||
- GitHub adapter skeleton in `aim-core`
|
||||
- add orchestration flow in `aim-core` from query resolution to normalized release selection
|
||||
- minimal `aim-cli` wiring to invoke the add flow
|
||||
- fixture-backed or mocked HTTP path for tests
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test github_adapter_can_normalize_owner_repo_source --test github_add_flow`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-core/src/adapters/github.rs crates/aim-core/src/app/add.rs crates/aim-core/src/adapters/mod.rs crates/aim-cli/src/main.rs tests/github_add_flow.rs
|
||||
git commit -m "feat: add github source adapter and core add flow"
|
||||
```
|
||||
|
||||
### Task 12: Add remaining adapters behind the same core contract
|
||||
|
||||
**Files:**
|
||||
- Create: `crates/aim-core/src/adapters/gitlab.rs`
|
||||
- Create: `crates/aim-core/src/adapters/direct_url.rs`
|
||||
- Create: `crates/aim-core/src/adapters/zsync.rs`
|
||||
- Create: `crates/aim-core/src/adapters/sourceforge.rs`
|
||||
- Create: `crates/aim-core/src/adapters/custom_json.rs`
|
||||
- Modify: `crates/aim-core/src/adapters/mod.rs`
|
||||
- Test: `tests/adapter_smoke.rs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```rust
|
||||
use aim_core::adapters::all_adapter_kinds;
|
||||
|
||||
#[test]
|
||||
fn all_expected_adapter_kinds_are_registered() {
|
||||
let kinds = all_adapter_kinds();
|
||||
assert!(kinds.contains(&"gitlab"));
|
||||
assert!(kinds.contains(&"direct-url"));
|
||||
assert!(kinds.contains(&"zsync"));
|
||||
assert!(kinds.contains(&"sourceforge"));
|
||||
assert!(kinds.contains(&"custom-json"));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test all_expected_adapter_kinds_are_registered --test adapter_smoke`
|
||||
Expected: FAIL because the additional adapters do not exist
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Add adapter modules and register them behind the shared core trait. Keep each adapter bootstrapped with contract-valid behavior and fixture-friendly parsing paths before adding richer source-specific behaviors.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test all_expected_adapter_kinds_are_registered --test adapter_smoke`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-core/src/adapters tests/adapter_smoke.rs
|
||||
git commit -m "feat: add remaining core source adapter skeletons"
|
||||
```
|
||||
|
||||
### Task 13: Implement list and remove in `aim-core`, keep `aim-cli` thin
|
||||
|
||||
**Files:**
|
||||
- Create: `crates/aim-core/src/app/list.rs`
|
||||
- Create: `crates/aim-core/src/app/remove.rs`
|
||||
- Modify: `crates/aim-cli/src/main.rs`
|
||||
- Test: `tests/remove_flow.rs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn remove_flow_rejects_unknown_app_names() {
|
||||
let result = aim_core::app::remove::resolve_registered_app("bat", &[]);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test remove_flow_rejects_unknown_app_names --test remove_flow`
|
||||
Expected: FAIL because list and remove services do not exist
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Implement in `aim-core`:
|
||||
- list formatting input model
|
||||
- registered app name matching
|
||||
- ambiguity handling hooks through interaction requests
|
||||
- conservative removal sequencing for artifact and integration cleanup
|
||||
|
||||
Add only wiring and rendering in `aim-cli`.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test remove_flow_rejects_unknown_app_names --test remove_flow`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-core/src/app/list.rs crates/aim-core/src/app/remove.rs crates/aim-cli/src/main.rs tests/remove_flow.rs
|
||||
git commit -m "feat: add core list and remove services"
|
||||
```
|
||||
|
||||
### Task 14: Wire the binary end to end and document the workspace split
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/aim-cli/src/main.rs`
|
||||
- Modify: `crates/aim-core/src/lib.rs`
|
||||
- Test: `tests/end_to_end_cli.rs`
|
||||
- Modify: `README.md`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```rust
|
||||
use assert_cmd::Command;
|
||||
use predicates::str::contains;
|
||||
|
||||
#[test]
|
||||
fn list_command_runs_without_registry_entries() {
|
||||
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||
cmd.arg("list").assert().success().stdout(contains("installed"));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test list_command_runs_without_registry_entries --test end_to_end_cli`
|
||||
Expected: FAIL because services are not fully wired into the binary
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Wire all top-level commands through `aim-core` service APIs and add minimal README usage documentation for:
|
||||
- add/query flow
|
||||
- bare update flow
|
||||
- list
|
||||
- remove
|
||||
- scope overrides
|
||||
|
||||
Also document that the workspace is intentionally split so a future GUI can reuse `aim-core`.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test list_command_runs_without_registry_entries --test end_to_end_cli`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-cli/src/main.rs crates/aim-core/src/lib.rs tests/end_to_end_cli.rs README.md
|
||||
git commit -m "feat: wire aim cli to aim-core end to end"
|
||||
```
|
||||
|
||||
### Task 15: Verification sweep and architecture leak check
|
||||
|
||||
**Files:**
|
||||
- Modify: `README.md`
|
||||
- Modify: `.plans/appimage-manager/2026-03-19-appimage-manager-design.md`
|
||||
- Modify: `.plans/appimage-manager/2026-03-19-appimage-manager-implementation-plan.md`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
There is no new product behavior in this task. Instead, identify the highest-risk missing automated check from earlier tasks and add that test first, prioritizing any gap that suggests business logic is drifting into `aim-cli`.
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test`
|
||||
Expected: Identify at least one missing assertion or regression gap before making release-readiness claims
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Close the smallest meaningful remaining gap. Update docs only where behavior has materially changed from the plan.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test`
|
||||
Expected: PASS
|
||||
|
||||
Run: `cargo fmt --check`
|
||||
Expected: PASS
|
||||
|
||||
Run: `cargo clippy --workspace --all-targets --all-features -- -D warnings`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add README.md .plans/appimage-manager/2026-03-19-appimage-manager-design.md .plans/appimage-manager/2026-03-19-appimage-manager-implementation-plan.md
|
||||
git commit -m "chore: finalize appimage manager workspace implementation"
|
||||
```
|
||||
|
||||
## Notes For Execution
|
||||
|
||||
- This workspace is currently empty and not initialized as a git repository, so commit steps will remain blocked until `git init` or an equivalent repository setup occurs.
|
||||
- The execution session should create a Cargo workspace, not a single binary crate.
|
||||
- The first adapter should be GitHub because it exercises the `owner/repo` shorthand and the most likely early-user path.
|
||||
- Keep custom JSON feed support declarative in v1.
|
||||
- Do not add a plugin runtime.
|
||||
- Do not let `aim-cli` accumulate business logic; if a behavior could be reused by a future GUI, it belongs in `aim-core`.
|
||||
|
||||
Plan complete and saved to `.plans/appimage-manager/2026-03-19-appimage-manager-implementation-plan.md`. Two execution options:
|
||||
|
||||
**1. Subagent-Driven (this session)** - I dispatch a fresh subagent per task, review between tasks, and iterate in this session.
|
||||
|
||||
**2. Parallel Session (separate)** - Open a new session with executing-plans and execute the plan with checkpoints.
|
||||
|
||||
Which approach?
|
||||
Loading…
Add table
Add a link
Reference in a new issue