Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ Cargo.lock
*~
.DS_Store

# Specs are temporary - reviewed, implemented, then deleted
# Specs: tracked in git, archived after merge
.specs/*.md
!.specs/TEMPLATE.md
27 changes: 10 additions & 17 deletions .specs/TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Spec Template

## Feature: [Name]
## Small Change (bug fix, tweak)
Skip this template. Write a clear PR description instead.

## Feature / New Behavior

### Problem
[What problem does this solve?]
Expand All @@ -23,27 +26,17 @@
- Manual verification: [steps]

### Files to Modify
- [ ] file1.rs
- [ ] file2.rs
- [ ] file.rs

### Documentation Updates
- [ ] CHANGELOG.md
- [ ] README.md (if user-facing)
- [ ] AGENTS.md (if process changes)

---

## Checkpoint Reviews

- [ ] 25%: [what's done, blockers?]
- [ ] 50%: [what's done, blockers?]
- [ ] 75%: [what's done, blockers?]
- [ ] 100%: [final review before PR]

## Post-Mortem (fill after merge)

**What went well:**

**What could improve:**
## Checkpoints (tied to deliverables, not percentages)

**Lessons learned:**
- [ ] [Deliverable 1 — e.g., "Semver parsing compiles and passes tests"]
- [ ] [Deliverable 2 — e.g., "Lockfile generates valid TOML"]
- [ ] [Deliverable 3 — e.g., "Alias cycle works end-to-end"]
- [ ] [Final review before PR]
110 changes: 110 additions & 0 deletions .specs/v0.4.0-roadmap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Spec: gitclaw v0.4.0 — Dependency Management

## Feature: Semver ranges, lockfile, and package aliases

### Problem
gitclaw v0.3.x installs the latest release by default with no way to:
- Pin or constrain versions (e.g., "install >=1.0.0 but <2.0.0")
- Reproduce installs across machines (no lockfile)
- Use short names for frequently installed packages

This makes gitclaw unreliable for CI/CD and team environments where
reproducibility matters.

### Solution
Implement the three features from ROADMAP.md v0.4.0:

**1. Semver range support**
- Parse semver constraints: `>=1.0.0`, `^1.2.3`, `~1.2.3`
- Find the best matching release from GitHub
- `gitclaw install user/repo "^1.2.3"` installs latest 1.x.x

**2. Lockfile support**
- `gitclaw lock` generates `gitclaw.lock` from installed packages
- `gitclaw install --locked` installs exact versions from lockfile
- Lockfile format: TOML with owner, repo, version, checksum

**3. Package aliases**
- `gitclaw alias add rg BurntSushi/ripgrep`
- `gitclaw alias list`
- `gitclaw alias remove rg`
- `gitclaw install rg` resolves alias to full owner/repo

### Acceptance Criteria
- [ ] `gitclaw install user/repo "^1.0.0"` installs matching version
- [ ] `gitclaw install user/repo ">=2.0.0"` installs matching version
- [ ] `gitclaw install user/repo "~1.2.3"` installs matching version
- [ ] Exact version `gitclaw install user/repo@1.2.3` still works
- [ ] No matching version: clear error message
- [ ] `gitclaw lock` creates `gitclaw.lock` in current directory
- [ ] `gitclaw install --locked` installs exact versions from lockfile
- [ ] `gitclaw alias add <name> <owner/repo>` creates alias
- [ ] `gitclaw install <alias>` resolves and installs
- [ ] Aliases persist in config file
- [ ] All new features have integration tests

### Edge Cases
- [ ] Semver constraint with no matching release: error with available versions
- [ ] Lockfile with missing/invalid entries: clear error, skip or fail
- [ ] Alias name conflicts with owner/repo format: reject with explanation
- [ ] Circular alias: detect and reject
- [ ] Lockfile in project-local config vs global: precedence rules

### Test Plan

**Unit tests:**
- Semver constraint parsing (^, ~, >=, <=, >, <, =)
- Version matching against release list
- Lockfile serialization/deserialization
- Alias resolution and conflict detection

**Integration tests:**
- Install with semver constraint installs correct version
- Lock command generates valid TOML
- Install --locked reproduces exact versions
- Alias add/list/remove cycle
- Install by alias resolves correctly

**Manual verification:**
- `gitclaw install BurntSushi/ripgrep "^14"` installs latest 14.x.x
- `gitclaw lock` then `gitclaw install --locked` on another machine
- `gitclaw alias add rg BurntSushi/ripgrep && gitclaw install rg`

### Files to Modify
- [ ] src/cli/mod.rs (new args: semver constraints, --locked, alias subcommand)
- [ ] src/core/install.rs (semver matching in install flow)
- [ ] src/core/lockfile.rs (new: lockfile generation and parsing)
- [ ] src/core/alias.rs (new: alias management)
- [ ] src/core/config.rs (alias storage in config)
- [ ] src/network/github.rs (version listing for semver matching)
- [ ] tests/lockfile.rs (new: lockfile tests)
- [ ] tests/alias.rs (new: alias tests)
- [ ] Cargo.toml (add `semver` crate)

### Documentation Updates
- [ ] CHANGELOG.md (v0.4.0 section)
- [ ] README.md (semver syntax, lockfile usage, alias commands)
- [ ] ROADMAP.md (mark v0.4.0 as complete)

### Dependencies
- `semver` crate for version constraint parsing

---

## Checkpoints (tied to deliverables)

- [ ] Semver constraint parsing compiles and passes tests
- [ ] Install with semver constraint works end-to-end
- [ ] `gitclaw lock` generates valid TOML lockfile
- [ ] `gitclaw install --locked` reproduces exact versions
- [ ] Alias add/list/remove cycle works
- [ ] `gitclaw install <alias>` resolves and installs correctly
- [ ] Final review: all features integrated, documented

## Lessons (add to AGENTS.md after merge)

**What went well:**

**What could improve:**

**Lessons learned:**
21 changes: 13 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,12 @@ Verify → Test → Build
## Spec-Driven Development

1. Create `.specs/feature-name.md` from TEMPLATE.md before coding
2. Define acceptance criteria and test plan
3. Review spec with user before implementation
4. Checkpoint at 25%, 50%, 75% for feedback
5. Delete spec after merge (gitignored, temporary)
2. Small fixes: skip spec, write a clear PR description
3. Features: full spec with acceptance criteria
4. Review spec with user before implementation
5. Checkpoints tied to deliverables, not percentages
6. Keep specs in git — archive after merge
7. Post-mortem lessons go to AGENTS.md, not the spec

## PR Discipline

Expand All @@ -101,18 +103,21 @@ Verify → Test → Build
## Definition of Done

- [ ] Code complete
- [ ] Tests pass (`cargo test`)
- [ ] Lint clean (`cargo fmt && cargo clippy`)
- [ ] `cargo test` passes
- [ ] `cargo clippy -- -D warnings` clean
- [ ] `cargo fmt --check` clean
- [ ] Run all three locally before pushing
- [ ] Documentation updated (CHANGELOG, README if needed)
- [ ] Manual verification done
- [ ] PR opened and reviewed

## Post-Mortems

After any significant issue or rework, document:
After rework or significant issues:
- What went wrong
- Root cause
- Prevention for next time
- Prevention
- Add to AGENTS.md so it persists

*Last updated: 2026-04-23*

10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.4.0] - 2026-04-23

### Added
- Semver range support for install: `gitclaw install user/repo "^1.2.3"`
- Lockfile: `gitclaw lock` generates `gitclaw.lock` from installed packages
- Locked install: `gitclaw install --locked` reproduces exact versions from lockfile
- Package aliases: `gitclaw alias add rg BurntSushi/ripgrep` then `gitclaw install rg`
- `gitclaw alias list` and `gitclaw alias remove` commands
- `semver` crate dependency for version constraint parsing

## [0.3.2] - 2026-04-23

### Added
Expand Down
9 changes: 8 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gitclaw"
version = "0.3.2"
version = "0.4.0"
edition = "2021"

[[bin]]
Expand Down Expand Up @@ -40,6 +40,7 @@ sha2 = "=0.10.8"
md5 = "=0.7.0"
colored = "=2.1.0"
rand = "=0.8.5"
semver = "1"

[dev-dependencies]
assert_cmd = "2.0.12"
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@ cargo install --path .

```bash
gcw install sharkdp/bat

gcw install BurntSushi/ripgrep "^14"
gcw lock
gcw install --locked
gcw alias add rg BurntSushi/ripgrep
gcw install rg
gcw list
gcw update sharkdp/bat
gcw uninstall gitclaw
gcw uninstall bat
```

## Configuration
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Planned features and improvements toward gitclaw 1.0.0.

## 0.4.0 — Dependency Management
## 0.4.0 — Dependency Management

**Semver range support**

Expand Down
35 changes: 35 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,31 @@ pub struct Cli {
pub token: Option<String>,
}

#[derive(Subcommand)]
pub enum AliasAction {
#[command(about = "Add a package alias.")]
Add {
#[arg(help = "Short alias name.")]
alias: String,
#[arg(help = "Full package name (owner/repo).")]
target: String,
},
#[command(about = "Remove a package alias.")]
Remove {
#[arg(help = "Alias to remove.")]
alias: String,
},
#[command(about = "List all aliases.")]
List {},
}

#[derive(Subcommand)]
pub enum Commands {
#[command(about = "Manage package aliases.")]
Alias {
#[command(subcommand)]
action: AliasAction,
},
#[command(about = "Install packages from GitHub releases.")]
Install {
#[arg(num_args = 1.., help = "Package(s) to install (format: owner/repo or owner/repo@version).")]
Expand All @@ -36,6 +59,18 @@ pub enum Commands {
dry_run: bool,
#[arg(long, help = "Verify checksums after download.")]
verify: bool,
#[arg(long, help = "Install exact versions from gitclaw.lock.")]
locked: bool,
},
#[command(about = "Generate a lockfile from installed packages.")]
Lock {
#[arg(
short,
long,
default_value = ".",
help = "Directory to write gitclaw.lock to."
)]
dir: String,
},
#[command(about = "List installed packages.")]
List {
Expand Down
Loading
Loading