---
phase: 02-core-archiver
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - Cargo.toml
  - src/main.rs
  - src/cli.rs
  - src/key.rs
  - src/format.rs
  - src/crypto.rs
  - src/compression.rs
autonomous: true
requirements: [FMT-01, FMT-02, FMT-03, FMT-04, ENC-01, ENC-02, ENC-03, ENC-04, ENC-05, CMP-01, CMP-02, INT-01, CLI-01]

must_haves:
  truths:
    - "cargo build compiles the project with zero errors and zero warnings"
    - "All binary format types (Header, TocEntry) match FORMAT.md byte-for-byte field definitions"
    - "encrypt then decrypt with known key/IV produces original data"
    - "compress then decompress produces original data"
    - "HMAC computed over IV||ciphertext matches recomputation"
    - "SHA-256 of original data is correctly computed and stored"
  artifacts:
    - path: "Cargo.toml"
      provides: "Project manifest with all dependencies"
      contains: "aes"
    - path: "src/main.rs"
      provides: "CLI entry point with clap dispatch"
      contains: "clap"
    - path: "src/cli.rs"
      provides: "Clap derive structs for Pack/Unpack/Inspect subcommands"
      exports: ["Cli", "Commands"]
    - path: "src/key.rs"
      provides: "Hardcoded 32-byte encryption key"
      exports: ["KEY"]
    - path: "src/format.rs"
      provides: "Header and TocEntry structs with serialize/deserialize"
      exports: ["Header", "TocEntry", "MAGIC", "VERSION"]
    - path: "src/crypto.rs"
      provides: "AES-256-CBC encrypt/decrypt, HMAC-SHA-256, SHA-256"
      exports: ["encrypt_data", "decrypt_data", "compute_hmac", "verify_hmac", "sha256_hash"]
    - path: "src/compression.rs"
      provides: "Gzip compress/decompress and should_compress heuristic"
      exports: ["compress", "decompress", "should_compress"]
  key_links:
    - from: "src/crypto.rs"
      to: "src/key.rs"
      via: "imports KEY constant"
      pattern: "use crate::key::KEY"
    - from: "src/format.rs"
      to: "docs/FORMAT.md"
      via: "byte-for-byte field layout match"
      pattern: "0x00.*0xEA.*0x72.*0x63"
    - from: "src/main.rs"
      to: "src/cli.rs"
      via: "clap parse and dispatch"
      pattern: "Cli::parse"
---

<objective>
Create the Rust project foundation with all library modules: CLI skeleton, binary format types, crypto pipeline, and compression.

Purpose: Establish the complete module structure and all building-block functions that the pack/unpack/inspect commands will orchestrate. Every individual operation (encrypt, decrypt, compress, decompress, HMAC, SHA-256, serialize header, serialize TOC entry) must work correctly in isolation before being wired together.

Output: A compiling Rust project with 7 source files covering CLI parsing, binary format types, cryptographic operations, compression, and the hardcoded key.
</objective>

<execution_context>
@/home/nick/.claude/get-shit-done/workflows/execute-plan.md
@/home/nick/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/02-core-archiver/02-RESEARCH.md
@docs/FORMAT.md
</context>

<tasks>

<task type="auto">
  <name>Task 1: Project scaffolding with Cargo, CLI skeleton, and key module</name>
  <files>Cargo.toml, src/main.rs, src/cli.rs, src/key.rs</files>
  <action>
1. Initialize the Rust project:
   ```
   cargo init --name encrypted_archive /home/nick/Projects/Rust/encrypted_archive
   ```
   If Cargo.toml already exists, just update it.

2. Set up Cargo.toml with exact dependency versions from research:
   ```toml
   [package]
   name = "encrypted_archive"
   version = "0.1.0"
   edition = "2021"

   [dependencies]
   aes = "0.8"
   cbc = "0.1"
   hmac = "0.12"
   sha2 = "0.10"
   flate2 = "1.1"
   clap = { version = "4.5", features = ["derive"] }
   rand = "0.9"
   anyhow = "1.0"
   ```

3. Create `src/cli.rs` with clap derive structs matching the research pattern:
   - `Cli` struct with `#[command(subcommand)]`
   - `Commands` enum with three variants:
     - `Pack { files: Vec<PathBuf>, output: PathBuf, no_compress: Vec<String> }`
     - `Unpack { archive: PathBuf, output_dir: PathBuf }` (output_dir defaults to ".")
     - `Inspect { archive: PathBuf }`
   - Use `#[arg(required = true)]` for files in Pack
   - Use `#[arg(short, long)]` for output paths
   - Use `#[arg(long)]` for no_compress (Vec<String> of filename patterns to skip compression for)

4. Create `src/key.rs` with the hardcoded 32-byte key:
   ```rust
   /// Hardcoded 32-byte AES-256 key.
   /// Same key is used for AES-256-CBC encryption and HMAC-SHA-256 authentication (v1).
   /// v2 will derive separate subkeys using HKDF.
   pub const KEY: [u8; 32] = [
       0x7A, 0x35, 0xC1, 0xD9, 0x4F, 0xE8, 0x2B, 0x6A,
       0x91, 0x0D, 0xF3, 0x58, 0xBC, 0x74, 0xA6, 0x1E,
       0x42, 0x8F, 0xD0, 0x63, 0xE5, 0x17, 0x9B, 0x2C,
       0xFA, 0x84, 0x06, 0xCD, 0x3E, 0x79, 0xB5, 0x50,
   ];
   ```
   Use a non-trivial key (not the example key 00 01 02 ... 1F from FORMAT.md worked example).

5. Create `src/main.rs`:
   - Parse CLI with `Cli::parse()`
   - Match on `Commands` variants, calling placeholder functions that print "not implemented yet" and return `Ok(())`
   - Use `anyhow::Result<()>` as the return type for main
   - Declare modules: `mod cli; mod key; mod format; mod crypto; mod compression; mod archive;`

IMPORTANT: Use Context7 to verify clap 4.5 derive API before writing cli.rs. Call `mcp__context7__resolve-library-id` for "clap" and then `mcp__context7__query-docs` for the derive subcommand pattern.

Do NOT use `rand::thread_rng()` -- it was renamed to `rand::rng()` in rand 0.9.
Do NOT use `block-modes` crate -- it is deprecated; use `cbc` directly.
  </action>
  <verify>
    <automated>cd /home/nick/Projects/Rust/encrypted_archive && cargo build 2>&1</automated>
    <manual>Verify Cargo.toml has correct dependencies, src/main.rs declares all modules, CLI help text shows pack/unpack/inspect</manual>
    <sampling_rate>run after this task commits</sampling_rate>
  </verify>
  <done>
    - cargo build succeeds with no errors
    - `cargo run -- --help` shows three subcommands: pack, unpack, inspect
    - `cargo run -- pack --help` shows files (required), --output, --no-compress arguments
    - All 7 module files exist (main.rs, cli.rs, key.rs, format.rs, crypto.rs, compression.rs, archive.rs) even if some are stubs
  </done>
</task>

<task type="auto">
  <name>Task 2: Format types, crypto pipeline, and compression module</name>
  <files>src/format.rs, src/crypto.rs, src/compression.rs</files>
  <action>
1. Create `src/format.rs` implementing the binary format from FORMAT.md:

   Constants:
   ```rust
   pub const MAGIC: [u8; 4] = [0x00, 0xEA, 0x72, 0x63];
   pub const VERSION: u8 = 1;
   pub const HEADER_SIZE: u32 = 40;
   ```

   Structs:
   - `Header` with fields: version (u8), flags (u8), file_count (u16), toc_offset (u32), toc_size (u32), toc_iv ([u8; 16]), reserved ([u8; 8])
   - `TocEntry` with fields: name (String), original_size (u32), compressed_size (u32), encrypted_size (u32), data_offset (u32), iv ([u8; 16]), hmac ([u8; 32]), sha256 ([u8; 32]), compression_flag (u8), padding_after (u16)

   Serialization (write functions):
   - `write_header(writer: &mut impl Write, header: &Header) -> anyhow::Result<()>`:
     Writes all 40 bytes in exact FORMAT.md order. Magic bytes first, then version, flags, file_count (LE), toc_offset (LE), toc_size (LE), toc_iv (16 bytes), reserved (8 bytes of zero).
   - `write_toc_entry(writer: &mut impl Write, entry: &TocEntry) -> anyhow::Result<()>`:
     Writes: name_length (u16 LE) + name bytes + original_size (u32 LE) + compressed_size (u32 LE) + encrypted_size (u32 LE) + data_offset (u32 LE) + iv (16 bytes) + hmac (32 bytes) + sha256 (32 bytes) + compression_flag (u8) + padding_after (u16 LE).
     Entry size = 101 + name.len() bytes.

   Deserialization (read functions):
   - `read_header(reader: &mut impl Read) -> anyhow::Result<Header>`:
     Reads 40 bytes, verifies magic == MAGIC, verifies version == 1, checks flags bits 4-7 are zero (reject if not). Returns parsed Header.
   - `read_toc_entry(reader: &mut impl Read) -> anyhow::Result<TocEntry>`:
     Reads name_length (u16 LE), then name_length bytes as UTF-8 string, then all fixed fields. Uses `from_le_bytes()` for all multi-byte integers.
   - `read_toc(reader: &mut impl Read, file_count: u16) -> anyhow::Result<Vec<TocEntry>>`:
     Reads file_count entries sequentially.

   Helper:
   - `entry_size(entry: &TocEntry) -> u32`: Returns `101 + entry.name.len() as u32`
   - `compute_toc_size(entries: &[TocEntry]) -> u32`: Sum of all entry_size values

   ALL multi-byte fields MUST use `to_le_bytes()` for writing and `from_le_bytes()` for reading. Do NOT use `to_ne_bytes()` or `to_be_bytes()`.
   Filenames: Use `name.len()` (byte count) NOT `name.chars().count()` (character count). FORMAT.md specifies byte count.

2. Create `src/crypto.rs` implementing the encryption pipeline:

   Type aliases:
   ```rust
   use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
   type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;
   type Aes256CbcDec = cbc::Decryptor<aes::Aes256>;
   type HmacSha256 = hmac::Hmac<sha2::Sha256>;
   ```

   Functions:
   - `generate_iv() -> [u8; 16]`:
     Uses `rand::rng().fill(&mut iv)` (NOT thread_rng -- renamed in rand 0.9).
   - `encrypt_data(plaintext: &[u8], key: &[u8; 32], iv: &[u8; 16]) -> Vec<u8>`:
     Computes encrypted_size = ((plaintext.len() / 16) + 1) * 16.
     Allocates buffer of encrypted_size, copies plaintext to start.
     Calls `Aes256CbcEnc::new(key.into(), iv.into()).encrypt_padded_mut::<Pkcs7>(&mut buf, plaintext.len())`.
     Returns the buffer (full encrypted_size bytes).
   - `decrypt_data(ciphertext: &[u8], key: &[u8; 32], iv: &[u8; 16]) -> anyhow::Result<Vec<u8>>`:
     Allocates mutable buffer from ciphertext.
     Calls `Aes256CbcDec::new(key.into(), iv.into()).decrypt_padded_mut::<Pkcs7>(&mut buf)`.
     Returns decrypted data as Vec<u8>.
   - `compute_hmac(key: &[u8; 32], iv: &[u8; 16], ciphertext: &[u8]) -> [u8; 32]`:
     Creates HmacSha256, updates with iv then ciphertext. Returns `finalize().into_bytes().into()`.
     HMAC input = IV (16 bytes) || ciphertext (encrypted_size bytes). Nothing else.
   - `verify_hmac(key: &[u8; 32], iv: &[u8; 16], ciphertext: &[u8], expected: &[u8; 32]) -> bool`:
     Creates HmacSha256, updates with iv then ciphertext. Uses `verify_slice(expected)` for constant-time comparison. Returns true on success.
   - `sha256_hash(data: &[u8]) -> [u8; 32]`:
     Returns `sha2::Sha256::digest(data).into()`.

   CRITICAL: Use `hmac::Mac` trait for `new_from_slice()`, `update()`, `finalize()`, `verify_slice()`.
   CRITICAL: encrypted_size formula: `((input_len / 16) + 1) * 16` -- PKCS7 ALWAYS adds at least 1 byte.

3. Create `src/compression.rs`:

   Functions:
   - `compress(data: &[u8]) -> anyhow::Result<Vec<u8>>`:
     Uses `flate2::write::GzEncoder` with `Compression::default()`.
     IMPORTANT: Use `GzBuilder::new().mtime(0)` to zero the gzip timestamp for reproducible output in tests.
     Writes all data, finishes encoder, returns compressed bytes.
   - `decompress(data: &[u8]) -> anyhow::Result<Vec<u8>>`:
     Uses `flate2::read::GzDecoder`. Reads all bytes to a Vec<u8>.
   - `should_compress(filename: &str, no_compress_list: &[String]) -> bool`:
     Returns false if filename matches any entry in no_compress_list (by suffix or exact match).
     Returns false for known compressed extensions: apk, zip, gz, bz2, xz, zst, png, jpg, jpeg, gif, webp, mp4, mp3, aac, ogg, flac, 7z, rar, jar.
     Returns true otherwise.
     Uses `filename.rsplit('.').next()` for extension extraction.

IMPORTANT: Use Context7 to verify the `aes`, `cbc`, `hmac`, `sha2`, `flate2`, and `rand` crate APIs before writing. Resolve library IDs and query docs for encrypt/decrypt patterns, HMAC usage, and GzEncoder/GzDecoder usage.
  </action>
  <verify>
    <automated>cd /home/nick/Projects/Rust/encrypted_archive && cargo build 2>&1 && cargo run -- --help 2>&1</automated>
    <manual>Review format.rs field order against FORMAT.md sections 4 and 5 to confirm byte-level match</manual>
    <sampling_rate>run after this task commits</sampling_rate>
  </verify>
  <done>
    - cargo build succeeds with no errors
    - format.rs exports Header, TocEntry, MAGIC, VERSION, HEADER_SIZE, and all read/write functions
    - crypto.rs exports encrypt_data, decrypt_data, compute_hmac, verify_hmac, sha256_hash, generate_iv
    - compression.rs exports compress, decompress, should_compress
    - Header serialization writes exactly 40 bytes with correct field order per FORMAT.md Section 4
    - TocEntry serialization writes exactly (101 + name_length) bytes per FORMAT.md Section 5
    - All multi-byte integers use little-endian encoding
    - encrypt_data output size matches formula: ((input_len / 16) + 1) * 16
  </done>
</task>

</tasks>

<verification>
- `cargo build` succeeds with zero errors
- `cargo run -- --help` shows pack, unpack, inspect subcommands
- All 7 source files exist: main.rs, cli.rs, key.rs, format.rs, crypto.rs, compression.rs, archive.rs
- format.rs Header struct has fields matching FORMAT.md Section 4 (magic, version, flags, file_count, toc_offset, toc_size, toc_iv, reserved)
- format.rs TocEntry struct has fields matching FORMAT.md Section 5 (name, original_size, compressed_size, encrypted_size, data_offset, iv, hmac, sha256, compression_flag, padding_after)
- crypto.rs uses `cbc::Encryptor<aes::Aes256>` (NOT deprecated block-modes)
- crypto.rs uses `rand::rng()` (NOT thread_rng)
- crypto.rs HMAC input is IV || ciphertext only
- compression.rs uses `GzBuilder::new().mtime(0)` for reproducible gzip
</verification>

<success_criteria>
A compiling Rust project with complete module structure where every building-block operation (format read/write, encrypt/decrypt, HMAC compute/verify, SHA-256 hash, compress/decompress) is implemented and ready for the pack/unpack/inspect commands to orchestrate.
</success_criteria>

<output>
After completion, create `.planning/phases/02-core-archiver/02-01-SUMMARY.md`
</output>
