Adopt Spacecraft Software Standard posture: GPL-3.0-or-later + REUSE#9
Conversation
Migrate `copy` from MIT-only to the Spacecraft Software Standard
licensing/compliance posture, preserving upstream cpx MIT attribution.
- Licensing: fork modifications are now GPL-3.0-or-later (Cargo.toml
`license`, root LICENSE); upstream MIT is preserved in LICENSES/MIT.txt.
- REUSE 3.3 compliance: add LICENSES/{GPL-3.0-or-later,MIT}.txt, REUSE.toml,
NOTICE.md, CREDITS.md, and per-file SPDX headers. `reuse lint` passes
(54/54 files with copyright + license info).
- Agent context: add AGENTS.md (generic-agent source) and rewrite CLAUDE.md
as its strict superset; both document build/test/lint, architectural
invariants, packaging, and the release workflow. Add a daily-drive install
note (`cargo install --path . --locked` -> ~/.cargo/bin/copy, runnable from
any directory) to the shared Build/Test/Lint section of both.
- Accompanying source and doc updates across config (schema/loader), core
copy, preprocess, preserve, both READMEs, docs, and packaging metadata.
Verified green locally: cargo build, fmt --check, clippy --all-targets
-D warnings, cargo test (69 unit + 68 integration), and reuse lint.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe PR migrates the project license from MIT to GPL-3.0-or-later with REUSE compliance across all source, packaging, and documentation files. Independently, it rewrites config loading from first-match to ordered multi-layer merging via new ChangesLicense Migration and Project Documentation
Config Layer Merging and Copy Error Hardening
Sequence Diagram(s)sequenceDiagram
participant Caller
participant load_config
participant merge_ordered_config_files
participant load_config_file
participant Config
rect rgba(70, 130, 180, 0.5)
Note over load_config,Config: Config layer merge (new behavior)
Caller->>load_config: load_config()
load_config->>merge_ordered_config_files: get ordered config paths
merge_ordered_config_files-->>load_config: [system, user, project] (existing paths only)
loop each path
load_config->>load_config_file: load_config_file(path)
load_config_file-->>load_config: PartialConfig
load_config->>Config: merge_partial(partial)
end
load_config-->>Caller: ConfigResult<Config>
end
sequenceDiagram
participant copy_parallel
participant failure_abort as failure_abort: AtomicBool
participant TaskN as copy_core (task N)
participant TaskM as copy_core (task M)
rect rgba(180, 70, 70, 0.5)
Note over copy_parallel,TaskM: Parallel copy with cross-task abort (new behavior)
copy_parallel->>failure_abort: initialize(false)
copy_parallel->>TaskN: spawn, pass &failure_abort
copy_parallel->>TaskM: spawn, pass &failure_abort
TaskN->>failure_abort: check on entry → false, proceed
TaskN-->>copy_parallel: Err(CopyFailed)
copy_parallel->>failure_abort: store(true)
TaskM->>failure_abort: check in write loop → true
TaskM-->>copy_parallel: Err(Interrupted "Operation aborted")
copy_parallel-->>copy_parallel: return CopyError::CopyFailed
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CLAUDE.md`:
- Around line 1-120: The AGENTS.md file at the repository root is not covered by
any REUSE annotation, even though it is documented in CLAUDE.md as a file to
keep in sync. Find the REUSE.toml file and locate the annotation block that
covers root documentation files like CLAUDE.md and README.md, then add
"AGENTS.md" to the path array in that annotation block so that AGENTS.md
receives the same SPDX headers and licensing coverage as the other root
documentation files.
In `@src/config/loader.rs`:
- Around line 49-72: The fix requires updating both
`merge_ordered_config_files()` and `find_config_files()` to propagate permission
errors instead of silently collapsing them. Replace all instances of `.exists()`
with `.try_exists().map_err(ConfigError::Io)?` in both functions. Change
`merge_ordered_config_files()` to return `ConfigResult<Vec<PathBuf>>` instead of
`Vec<PathBuf>`, and update its caller `load_config()` to handle the new return
type using the `?` operator. Apply the same `.try_exists()` change to
`find_config_files()` which is public and used by `show_config()`.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e22c099c-78e5-42e0-a29e-8cb4efa5f354
📒 Files selected for processing (25)
AGENTS.mdCLAUDE.mdCONTRIBUTING.mdCREDITS.mdCargo.tomlLICENSELICENSES/GPL-3.0-or-later.txtLICENSES/MIT.txtNOTICE.mdREADME.mdREADME_CRATES.mdREUSE.tomldocs/configuration.mdguix.scmnix/package.nixpackaging/aur/PKGBUILDsrc/cli/args.rssrc/config/config_command.rssrc/config/loader.rssrc/config/schema.rssrc/core/copy.rssrc/core/fast_copy.rssrc/utility/preprocess.rssrc/utility/preserve.rstests/intergration.rs
| <!-- | ||
| SPDX-FileCopyrightText: 2026 Mohamed Hammad <Mohamed.Hammad@SpacecraftSoftware.org> | ||
| SPDX-License-Identifier: GPL-3.0-or-later | ||
| --> | ||
|
|
||
| # CLAUDE.md — copy | ||
|
|
||
| This file is a strict superset of `AGENTS.md`. Keep the shared sections in sync | ||
| when project invariants, commands, packaging, or release workflow change. | ||
|
|
||
| ## Project Identity | ||
|
|
||
| `copy` is a Linux-first Rust 2024 CLI that replaces `cp` with parallel copying, | ||
| resume support, reflinks, symlink/hardlink modes, and configurable preservation | ||
| behavior. It is a Spacecraft Software-maintained fork of upstream `cpx`; upstream | ||
| MIT attribution is preserved in `LICENSES/MIT.txt`, and fork modifications are | ||
| distributed under GPL-3.0-or-later. | ||
|
|
||
| ## Build, Test, Lint | ||
|
|
||
| - Build: `cargo build` | ||
| - Release build: `cargo build --release` | ||
| - Test: `cargo test` | ||
| - Integration tests only: `cargo test --test intergration` | ||
| - One test by name: `cargo test test_copy_single_file` | ||
| - Lint: `cargo clippy --all-targets -- -D warnings` | ||
| - Format check: `cargo fmt --check` | ||
| - REUSE compliance: `nix-shell -p reuse --run "reuse lint"` | ||
| - Optional SELinux feature build: `cargo build --features selinux-support` after installing libselinux development headers that provide `selinux/selinux.h` | ||
| - Daily-drive install: always build and install the latest binary locally so the user can run `copy` from any directory — `cargo install --path . --locked` puts it on `PATH` at `~/.cargo/bin/copy`. Re-run after changes so the daily driver stays current. | ||
|
|
||
| CI runs build, test, clippy with warnings denied, and format check. Keep all four | ||
| green before handing work back. | ||
|
|
||
| ## Architectural Invariants | ||
|
|
||
| - The binary in `src/main.rs` is intentionally thin: parse args, validate, set the signal abort flag, then dispatch to `copy()` or `multiple_copy()`. | ||
| - Copying is a two-phase pipeline: build a complete `CopyPlan` in `utility/preprocess.rs`, then execute it in `core/copy.rs`. | ||
| - `copy` is the root action. `config` is the only subcommand and is special-cased in `CLIArgs::parse()` because greedy source positionals would otherwise swallow `copy config show`. | ||
| - Config precedence is defaults → system → user → project → CLI. Invalid discovered config files are errors, not ignored fallbacks. `--config PATH` means defaults → that file → CLI. | ||
| - `CopyOptions::none()` and `CopyOptions::from_config()` must stay in sync when adding fields. | ||
| - `preprocess.rs` decides whether each item becomes a file, symlink, hardlink, resume skip, or directory task. Do not duplicate that routing in execution. | ||
| - Directory creation is sequential. Non-interactive file copies use a Rayon thread pool sized by `options.parallel`; `--interactive` is sequential. | ||
| - `copy_core()` tries hardlink preservation, reflink, Linux `copy_file_range`, then buffered fallback. | ||
| - Worker failures request cooperative cancellation for remaining parallel work. User SIGINT/SIGTERM uses the separate `options.abort` flag and maps to exit code 130 in `main.rs`. | ||
| - `README.md` and `README_CRATES.md` must stay in sync for user-facing behavior, install instructions, licensing, and release references. | ||
|
|
||
| ## Forbidden Patterns | ||
|
|
||
| - Do not ignore filesystem errors in production copy paths. Backup creation, destination removal, xattr preservation, and destination creation failures must propagate. | ||
| - Do not print progress, logs, or diagnostics to stdout when stdout is data. Current code is not fully SFRS-compliant yet; do not make this worse. | ||
| - Do not add `unwrap()` or `expect()` in production paths for user input, filesystem state, config parsing, or CLI parsing. | ||
| - Do not make `selinux-support` a default feature; it requires system headers. | ||
| - Do not rename `tests/intergration.rs`; the misspelling is part of existing command/docs references. | ||
| - Do not remove upstream MIT attribution or `LICENSES/MIT.txt`. | ||
| - Do not touch unrelated dirty worktree files. In particular, inspect existing changes before editing `CLAUDE.md` or other root docs. | ||
|
|
||
| ## Environment Expectations | ||
|
|
||
| - Rust stable with edition 2024 support; README documents Rust 1.85 or later. | ||
| - `cargo`, `rustc`, `clippy`, and `rustfmt` are expected. | ||
| - Nix may be available for ephemeral tooling such as `reuse`; prefer `nix-shell -p reuse --run "reuse lint"` over permanent installs. | ||
| - Default builds need no SELinux system library. All-features builds require libselinux development headers. | ||
| - GNU compatibility scripts in `tests/gnu/*.sh` are standalone and require a built `copy` binary on `PATH`; they are not part of `cargo test`. | ||
|
|
||
| ## Where To Look | ||
|
|
||
| - CLI args and validation: `src/cli/args.rs` | ||
| - Config schema and merge behavior: `src/config/schema.rs`, `src/config/loader.rs` | ||
| - Copy planning: `src/utility/preprocess.rs` | ||
| - Copy execution and cancellation: `src/core/copy.rs` | ||
| - Linux fast path: `src/core/fast_copy.rs` | ||
| - Preservation logic: `src/utility/preserve.rs` | ||
| - Error types: `src/error.rs` | ||
| - Integration tests: `tests/intergration.rs` | ||
| - Standalone GNU compatibility scripts: `tests/gnu/` | ||
| - Packaging: `nix/package.nix`, `guix.scm`, `packaging/aur/PKGBUILD` | ||
|
|
||
| ## Release Notes For Agents | ||
|
|
||
| Version bumps are manual. Update `Cargo.toml`, `Cargo.lock`, `nix/package.nix`, | ||
| `guix.scm`, `packaging/aur/PKGBUILD`, `packaging/aur/.SRCINFO`, and release | ||
| references in both READMEs. The AUR source tarball hash exists only after the | ||
| GitHub tag is created, so use `SKIP` during the bump and fill the real hash in | ||
| a follow-up commit. | ||
|
|
||
| ## Standards Compliance | ||
|
|
||
| This project is moving toward the Spacecraft Software CLI Standard but still has | ||
| legacy `cp`-compatible behavior. The `spacecraft-cli-standard` and | ||
| `spacecraft-agentic-cli` skills are authoritative for new CLI surface design; | ||
| this file records project-specific invariants and current exceptions only. | ||
|
|
||
| ## Skills Referenced | ||
|
|
||
| - `spacecraft-standard` — Spacecraft Software project standard, licensing, posture, REUSE, time/unit policy. | ||
| - `microsoft-rust-guidelines` — required before writing or modifying Rust code. | ||
| - `spacecraft-cli-standard` — structural SFRS rules for future CLI surface work. | ||
| - `spacecraft-agentic-cli` — agent-facing UX and context-file guidance. | ||
| - `spacecraft-cli-preference` — preferred command-line tools, such as `rg`, `fd`, `bat`, `jaq`. | ||
| - `spacecraft-cli-shell` — shell syntax and portability checks for command snippets and scripts. | ||
| - `spacecraft-missing-pkg` — use when a needed tool is missing; prefer ephemeral Nix/Guix runners. | ||
|
|
||
| ## MCP Servers Expected | ||
|
|
||
| No project-specific MCP server is implemented yet. Do not assume `copy mcp`, | ||
| `copy schema`, or `copy describe` exists unless implementing SFRS work explicitly. | ||
|
|
||
| ## Tool Preferences For Claude | ||
|
|
||
| - Use `rg` for search and `fd` for file discovery. | ||
| - Use `cargo test` as the baseline; `cargo nextest` is not currently required by the repo. | ||
| - Use `nix-shell -p reuse --run "reuse lint"` for REUSE checks when `reuse` is not on `PATH`. | ||
| - Avoid broad auto-formatting outside Rustfmt-managed Rust files unless the task explicitly calls for it. | ||
|
|
||
| ## Notes For Claude Specifically | ||
|
|
||
| - Treat `AGENTS.md` as the generic-agent source. If project facts change, update both files in the same patch. | ||
| - Claude Code may load skills, but this repo should not rely on Claude-only tooling for ordinary development. | ||
| - Do not add Claude-specific TODO markers unless the user asks for a Claude workflow. |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Verify AGENTS.md is covered by REUSE.toml annotations
# Check if AGENTS.md is covered by any REUSE annotation
rg -A 15 '\[\[annotations\]\]' REUSE.toml | grep -E '(AGENTS\.md|docs/\*\.md)'
# Run reuse lint to confirm full compliance (expects 54/54 files)
# Note: reuse command may not be available; use nix-shell if needed
if command -v reuse &> /dev/null; then
reuse lint
else
echo "reuse not on PATH; try: nix-shell -p reuse --run 'reuse lint'"
fiRepository: UnbreakableMJ/copy
Length of output: 139
🏁 Script executed:
#!/bin/bash
# Find AGENTS.md in the repository
fd -t f 'AGENTS\.md' .
# Display the relevant section of REUSE.toml with context
echo "=== Full REUSE.toml annotations section ==="
rg -A 20 '\[\[annotations\]\]' REUSE.toml
# Check the structure of docs directory if it exists
echo "=== docs directory structure ==="
if [ -d "docs" ]; then
ls -la docs/ | head -20
else
echo "No docs/ directory found"
fiRepository: UnbreakableMJ/copy
Length of output: 2006
Add AGENTS.md to REUSE.toml annotations.
AGENTS.md exists at the repository root but is not covered by any REUSE annotation. The docs/*.md glob in the third annotation block covers only files within the docs/ directory, not root-level files. Since AGENTS.md is documented in CLAUDE.md as a file to keep in sync (line 10), it should be explicitly added to the annotation covering root documentation files.
Add "AGENTS.md" to the path array in the annotation block that covers CLAUDE.md, README.md, and other root documentation.
🧰 Tools
🪛 LanguageTool
[style] ~51-~51: Consider using a shorter alternative to avoid wordiness.
Context: ...is not fully SFRS-compliant yet; do not make this worse. - Do not add unwrap() or expect() ...
(MADE_IT_JJR)
[style] ~54-~54: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... feature; it requires system headers. - Do not rename tests/intergration.rs; the...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
[style] ~55-~55: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... of existing command/docs references. - Do not remove upstream MIT attribution or ...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
[style] ~56-~56: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...IT attribution or LICENSES/MIT.txt. - Do not touch unrelated dirty worktree file...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
[grammar] ~100-~100: Ensure spelling is correct
Context: ...-line tools, such as rg, fd, bat, jaq. - spacecraft-cli-shell — shell synta...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
[style] ~113-~113: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...s not currently required by the repo. - Use nix-shell -p reuse --run "reuse lint"...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@CLAUDE.md` around lines 1 - 120, The AGENTS.md file at the repository root is
not covered by any REUSE annotation, even though it is documented in CLAUDE.md
as a file to keep in sync. Find the REUSE.toml file and locate the annotation
block that covers root documentation files like CLAUDE.md and README.md, then
add "AGENTS.md" to the path array in that annotation block so that AGENTS.md
receives the same SPDX headers and licensing coverage as the other root
documentation files.
Source: Learnings
| fn merge_ordered_config_files() -> Vec<PathBuf> { | ||
| let mut paths = Vec::new(); | ||
|
|
||
| #[cfg(unix)] | ||
| { | ||
| return config; | ||
| let system = PathBuf::from("/etc/copy/copyconfig.toml"); | ||
| if system.exists() { | ||
| paths.push(system); | ||
| } | ||
| } | ||
|
|
||
| if let Some(config_dir) = dirs::config_dir() { | ||
| let user = config_dir.join("copy").join("copyconfig.toml"); | ||
| if user.exists() | ||
| && let Ok(config) = load_config_file(&user) | ||
| { | ||
| return config; | ||
| if user.exists() { | ||
| paths.push(user); | ||
| } | ||
| } | ||
|
|
||
| #[cfg(unix)] | ||
| { | ||
| let system = PathBuf::from("/etc/copy/copyconfig.toml"); | ||
| if system.exists() | ||
| && let Ok(config) = load_config_file(&system) | ||
| { | ||
| return config; | ||
| } | ||
| let project = PathBuf::from("./copyconfig.toml"); | ||
| if project.exists() { | ||
| paths.push(project); | ||
| } | ||
|
|
||
| Config::default() | ||
| paths |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C3 'fn merge_ordered_config_files|\.exists\(|try_exists\(' src/config/loader.rs
rg -n -C2 'cannot be read or parsed|not silently ignore|Defaults → System Config → User Config → Project Config → CLI Flags' docs/configuration.mdRepository: UnbreakableMJ/copy
Length of output: 1785
🏁 Script executed:
# Check which function is used in load_config()
rg -n -A10 'fn load_config' src/config/loader.rs
# Find all usages of both functions
rg -n 'merge_ordered_config_files\|find_config_files' src/config/loader.rs
# Check if find_config_files is used elsewhere in the codebase
rg -r 'find_config_files\|merge_ordered_config_files' src/ --type rsRepository: UnbreakableMJ/copy
Length of output: 930
🏁 Script executed:
# Find all usages of both functions without type filter
rg -n 'merge_ordered_config_files\|find_config_files' src/
# Get the full loader.rs file to see context
wc -l src/config/loader.rs
head -80 src/config/loader.rsRepository: UnbreakableMJ/copy
Length of output: 2182
🏁 Script executed:
# Check if find_config_files is exported from config module
rg -n 'find_config_files\|pub.*mod' src/config/
# Check all usages of find_config_files across entire codebase
rg -n 'find_config_files' src/ --glob '*.rs' -i
# Check what's exported from the config module
cat src/config/mod.rsRepository: UnbreakableMJ/copy
Length of output: 346
🏁 Script executed:
# Check how find_config_files is used in config_command.rs
sed -n '80,95p' src/config/config_command.rs
# Show more context around that usage
sed -n '75,100p' src/config/config_command.rsRepository: UnbreakableMJ/copy
Length of output: 1351
🏁 Script executed:
# Check the complete config_command.rs to understand find_config_files usage
rg -n 'fn.*config' src/config/config_command.rs | head -20
# Check if there are other callers of load_config or merge_ordered_config_files
rg -n 'load_config\|merge_ordered_config_files' src/ --glob '*.rs' -i
# Verify the coding guideline requirement about precedence
rg -n 'defaults.*system.*user.*project' docs/ -i -A2 -B2Repository: UnbreakableMJ/copy
Length of output: 492
Expand .try_exists() fix to both config discovery functions; proposed diff is incomplete.
merge_ordered_config_files() and the public find_config_files() both use Path::exists(), which silently collapses permission errors to false. Per coding guidelines, invalid discovered config files must be errors, not silent fallbacks.
This affects two paths:
load_config()path:merge_ordered_config_files()must returnConfigResult<Vec<PathBuf>>and propagate errors viatry_exists().find_config_files()path: Also public and used byshow_config(). Must propagate metadata errors so unreadable paths aren't hidden from the user.
The proposed diff in the original comment only fixes merge_ordered_config_files(). Both functions require the same .exists() → .try_exists().map_err(ConfigError::Io)? change.
Extended fix scope
Both find_config_files() (line 6) and merge_ordered_config_files() (line 49) must be updated. The latter's caller load_config() (line 37) must also propagate the result type change.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/config/loader.rs` around lines 49 - 72, The fix requires updating both
`merge_ordered_config_files()` and `find_config_files()` to propagate permission
errors instead of silently collapsing them. Replace all instances of `.exists()`
with `.try_exists().map_err(ConfigError::Io)?` in both functions. Change
`merge_ordered_config_files()` to return `ConfigResult<Vec<PathBuf>>` instead of
`Vec<PathBuf>`, and update its caller `load_config()` to handle the new return
type using the `?` operator. Apply the same `.try_exists()` change to
`find_config_files()` which is public and used by `show_config()`.
Source: Coding guidelines
Commits the in-progress working-tree migration that moves
copyfrom MIT-only to the Spacecraft Software Standard licensing/compliance posture, while preserving upstreamcpxMIT attribution. 19 files modified, 6 added (+1253/−243).Licensing
Cargo.tomllicense, rootLICENSE); upstream MIT preserved inLICENSES/MIT.txt.LICENSES/{GPL-3.0-or-later,MIT}.txt,REUSE.toml,NOTICE.md,CREDITS.md, and per-file SPDX headers.Agent context
AGENTS.md(generic-agent source) +CLAUDE.mdrewritten as its strict superset — build/test/lint, architectural invariants, packaging, release workflow.cargo install --path . --locked→~/.cargo/bin/copy, runnable from any directory.Source / docs
schema.rs+192,loader.rs),core/copy.rs,preprocess.rs,preserve.rs, both READMEs,docs/, and packaging metadata. Integration tests grew (intergration.rs+83).Verification (local, all green)
cargo build,cargo fmt --check,cargo clippy --all-targets -- -D warningscargo test→ 69 unit + 68 integration passreuse lint→ REUSE 3.3 compliant (54/54 files)🤖 Generated with Claude Code
Summary by CodeRabbit
Documentation
License & Compliance
New Features
Bug Fixes
Tests