Compare commits

...

8 Commits

Author SHA1 Message Date
TapTap 5c686b0655 fix: address PR review — doc accuracy, clang-tidy info, config leak fix
CI / lint (push) Successful in 7s
CI / lint (pull_request) Successful in 7s
CI / build-and-test (push) Successful in 54s
CI / clang-tidy (push) Successful in 6s
CI / sanitizers (address) (push) Successful in 59s
CI / build-and-test (pull_request) Successful in 55s
CI / clang-tidy (pull_request) Successful in 5s
CI / sanitizers (address) (pull_request) Successful in 57s
Review findings addressed:

Critical:
- cmake-expert.md: replace commented-out sanitizer lines with actual
  SANITIZER cache variable block, fix ENABLE_ASAN/TSAN/UBSAN pattern
- integrator.md: update YAML example to v7, use -DSANITIZER= instead
  of raw flags, add symlink + LSAN suppression steps

Warnings:
- ci.yaml: rename clang-tidy step to 'informational, non-blocking',
  add ::warning:: workflow command for visibility
- client_cli.c: add goto cleanup pattern to free config on error paths
  (skip for multithreaded path where pipeline_context_sender_destroy
  already owns config)

Suggestions:
- test-writer.md: clarify conftest.py is at tests/conftest.py
- .lsan-suppressions.txt: remove config_create suppression (leak fixed)
2026-07-20 14:28:42 +02:00
TapTap c7f34eaf9d ci: symlink build dir for sanitizer integration tests 2026-07-20 14:22:27 +02:00
TapTap bd17f50f9b feat: update AI automation agents, add reviewer agent, fix leaks, enhance CI
Agents:
- cmake-expert: fix stale CMake version (4.1 -> 3.22), add OpenSSL/xxHash deps
- integrator: fix stale test.py references to tests/integration/, update CI docs
- test-writer: update integration test patterns for modular test structure
- reviewer: new comprehensive PR reviewer (code, build, CI, docs, quality)

Bug fixes:
- delta.c: set instructions[i].type = DELTA_INSTR_LITERAL in deserialize
- scanner.c: free ArrayList when directory_scanner_next returns NULL

CI:
- Add sanitizer job (ASan + UBSan) with LSAN suppressions for pre-existing leaks
- Add clang-tidy job with proper warning/error detection
- Remove || true masking (fixes now make sanitizer useful)

Cleanup:
- gitignore build-asan/
- .lsan-suppressions.txt for known CLI config leaks
2026-07-20 14:22:27 +02:00
TapTap a4b35e136b Merge pull request 'docs: add AGENTS.md with custom-image dependency install rule' (#27) from feat/custom-image-deps-rule into main
CI / lint (push) Successful in 12s
CI / sanitizers (address) (push) Successful in 14s
CI / build-and-test (push) Successful in 53s
Reviewed-on: #27
2026-07-19 22:57:15 +02:00
TapTap 02ceb6828b fix: re-review cleanup — deps/conventions/sanitizer sections in cmake-expert, AGENTS.md notes
CI / lint (push) Successful in 8s
CI / lint (pull_request) Successful in 7s
CI / sanitizers (address) (push) Successful in 14s
CI / build-and-test (push) Successful in 54s
CI / build-and-test (pull_request) Successful in 53s
CI / sanitizers (address) (pull_request) Successful in 14s
2026-07-19 22:54:02 +02:00
TapTap 0f9e689e39 fix: address PR review — lcov/valgrind in Dockerfile, correct cmake-expert.md, expand AGENTS.md
CI / lint (push) Successful in 8s
CI / lint (pull_request) Successful in 7s
CI / build-and-test (push) Successful in 53s
CI / sanitizers (address) (push) Successful in 14s
CI / build-and-test (pull_request) Successful in 53s
CI / sanitizers (address) (pull_request) Successful in 14s
2026-07-19 22:37:26 +02:00
TapTap a7bb6454a4 docs: add AGENTS.md with custom-image dependency install rule
CI / lint (push) Successful in 8s
CI / build-and-test (push) Successful in 52s
CI / sanitizers (address) (push) Successful in 13s
CI / lint (pull_request) Successful in 7s
CI / build-and-test (pull_request) Successful in 53s
CI / sanitizers (address) (pull_request) Successful in 14s
2026-07-19 20:43:54 +02:00
TapTap 8a222bffa6 Merge pull request 'ci: add static analysis, sanitizers, and formatting enforcement' (#24) from ci/tier1-review-automation into main
CI / lint (push) Successful in 7s
CI / build-and-test (push) Successful in 53s
CI / sanitizers (address) (push) Successful in 14s
Reviewed-on: #24
2026-07-19 18:10:06 +02:00
10 changed files with 361 additions and 115 deletions
+28
View File
@@ -53,5 +53,33 @@ jobs:
- name: Build - name: Build
run: cmake --build build-${{ matrix.sanitizer }} -j$(nproc) run: cmake --build build-${{ matrix.sanitizer }} -j$(nproc)
- name: Symlink for integration tests
run: ln -sf build-${{ matrix.sanitizer }} build
- name: Unit Tests - name: Unit Tests
run: ./build-${{ matrix.sanitizer }}/tests run: ./build-${{ matrix.sanitizer }}/tests
- name: Integration Tests
run: LSAN_OPTIONS=suppressions=.lsan-suppressions.txt python3 -m pytest tests/ -v --tb=short
clang-tidy:
runs-on: ubuntu-latest
container: gitea.tap-tap.win/taptap/fastsync-ci:v7
needs: lint
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure (generate compile_commands.json)
run: cmake -B build -S .
- name: Run clang-tidy (informational, non-blocking)
run: |
find src/ -name '*.c' | xargs clang-tidy -p build \
--checks='-*,bugprone-*,clang-analyzer-*,misc-*,-misc-no-recursion' \
2>&1 | tee clang-tidy-output.txt
if grep -q -E " error:| warning:" clang-tidy-output.txt; then
echo "::warning::clang-tidy found issues — review the output above"
else
echo "clang-tidy: no issues found"
fi
+6
View File
@@ -0,0 +1,6 @@
# LSAN suppressions for FastSync
# Add suppression entries here for known pre-existing leaks that cannot be
# fixed immediately. Remove entries as leaks are fixed.
#
# Example format:
# leak:function_name
+2
View File
@@ -9,6 +9,8 @@ You are a system architect for the FastSync project — a high-performance file
Make high-level design decisions. Evaluate trade-offs, plan module interactions, design data flow, and ensure architectural coherence across the codebase. Make high-level design decisions. Evaluate trade-offs, plan module interactions, design data flow, and ensure architectural coherence across the codebase.
> **Environment rule:** dependency installation must always use the project's custom Docker image (repo-root `Dockerfile`, same as CI) — never ad-hoc host package installs. See `AGENTS.md`.
## Project Architecture ## Project Architecture
### Module Map ### Module Map
+57 -56
View File
@@ -3,7 +3,7 @@ description: Manages the CMake build system for FastSync — adding targets, sou
mode: subagent mode: subagent
--- ---
You are a CMake expert for the FastSync project — a high-performance file synchronization system built with CMake 4.1+ and C11. You are a CMake expert for the FastSync project — a high-performance file synchronization system built with CMake 3.22+ and C11.
## Your Role ## Your Role
@@ -13,7 +13,7 @@ Manage the CMake build system: add new targets, configure dependencies, set comp
### `CMakeLists.txt` (project root) ### `CMakeLists.txt` (project root)
```cmake ```cmake
cmake_minimum_required(VERSION 4.1) cmake_minimum_required(VERSION 3.22)
project(FastFileTransfer) project(FastFileTransfer)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
@@ -22,30 +22,54 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
add_compile_options(-Wall -g -O3) add_compile_options(-Wall -g -O3)
include(FetchContent)
FetchContent_Declare(xxhash GIT_REPOSITORY https://github.com/Cyan4973/xxHash GIT_TAG v0.8.3 SOURCE_SUBDIR cmake_unofficial)
FetchContent_MakeAvailable(xxhash)
# Sanitizer option
set(SANITIZER "none" CACHE STRING "Sanitizer to enable (address, thread, none)")
set_property(CACHE SANITIZER PROPERTY STRINGS address thread none)
if(SANITIZER STREQUAL "address")
add_compile_options(-fsanitize=address -fno-omit-frame-pointer -g)
add_link_options(-fsanitize=address)
elseif(SANITIZER STREQUAL "thread")
add_compile_options(-fsanitize=thread -fno-omit-frame-pointer -g)
add_link_options(-fsanitize=thread)
elseif(NOT SANITIZER STREQUAL "none")
message(FATAL_ERROR "Unknown sanitizer: ${SANITIZER}. Supported values: address, thread, none")
endif()
option(STRICT_WARNINGS "Enable strict warnings" OFF)
if(STRICT_WARNINGS)
add_compile_options(-Wextra -Wpedantic -Werror)
endif()
set(THREADS_PREFER_PTHREAD_FLAG ON) set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
find_library(ZSTD_LIBRARY zstd) find_library(ZSTD_LIBRARY zstd)
# ... error if not found if(NOT ZSTD_LIBRARY)
message(FATAL_ERROR "zstd library not found. Ensure it is in your nix-shell!")
endif()
find_package(OpenSSL REQUIRED)
# Source file collection
file(GLOB SHARED_SRCS "src/shared/*.c") file(GLOB SHARED_SRCS "src/shared/*.c")
file(GLOB SERVER_SRCS "src/server/*.c") file(GLOB SERVER_SRCS "src/server/*.c")
file(GLOB CLIENT_SRCS "src/client/*.c") file(GLOB CLIENT_SRCS "src/client/*.c")
file(GLOB TEST_SRCS "tests/*.c") file(GLOB TEST_SRCS "tests/*.c")
# Targets
add_executable(server ${SERVER_SRCS} ${SHARED_SRCS}) add_executable(server ${SERVER_SRCS} ${SHARED_SRCS})
target_include_directories(server PRIVATE src/shared src/server src/client) target_include_directories(server PRIVATE src/shared src/server src/client)
target_link_libraries(server PRIVATE Threads::Threads ${ZSTD_LIBRARY}) target_link_libraries(server PRIVATE Threads::Threads ${ZSTD_LIBRARY} OpenSSL::SSL OpenSSL::Crypto xxhash)
add_executable(client ${CLIENT_SRCS} ${SHARED_SRCS}) add_executable(client ${CLIENT_SRCS} ${SHARED_SRCS})
target_include_directories(client PRIVATE src/shared src/server src/client) target_include_directories(client PRIVATE src/shared src/server src/client)
target_link_libraries(client PRIVATE Threads::Threads ${ZSTD_LIBRARY}) target_link_libraries(client PRIVATE Threads::Threads ${ZSTD_LIBRARY} OpenSSL::SSL OpenSSL::Crypto xxhash)
add_executable(tests ${TEST_SRCS} ${SHARED_SRCS} src/client/scanner.c) add_executable(tests ${TEST_SRCS} ${SHARED_SRCS} src/client/scanner.c)
target_include_directories(tests PRIVATE tests src/shared src/server src/client) target_include_directories(tests PRIVATE tests src/shared src/server src/client)
target_link_libraries(tests PRIVATE Threads::Threads ${ZSTD_LIBRARY}) target_link_libraries(tests PRIVATE Threads::Threads ${ZSTD_LIBRARY} OpenSSL::SSL OpenSSL::Crypto xxhash)
``` ```
### Source Layout ### Source Layout
@@ -53,22 +77,26 @@ target_link_libraries(tests PRIVATE Threads::Threads ${ZSTD_LIBRARY})
src/shared/ — shared libraries (globbed as SHARED_SRCS) src/shared/ — shared libraries (globbed as SHARED_SRCS)
src/client/ — client sources (globbed as CLIENT_SRCS) src/client/ — client sources (globbed as CLIENT_SRCS)
src/server/ — server sources (globbed as SERVER_SRCS) src/server/ — server sources (globbed as SERVER_SRCS)
tests/ — test sources (globbed as TEST_SRCS) tests/ — unit test sources (globbed as TEST_SRCS)
tests/integration/ — Python pytest integration tests
``` ```
### Dependencies ### Dependencies
- **zstd** — found via `find_library(ZSTD_LIBRARY zstd)` - **zstd** — found via `find_library(ZSTD_LIBRARY zstd)`
- **OpenSSL** — found via `find_package(OpenSSL REQUIRED)` (TLS 1.2+ transport)
- **xxHash** — fetched via `FetchContent` from GitHub (delta transfer hashing, v0.8.3)
- **pthreads** — found via `find_package(Threads REQUIRED)` - **pthreads** — found via `find_package(Threads REQUIRED)`
- **C11 standard** — required - **C11 standard** — required
- **CMake 4.1+** — minimum version - **CMake 3.22+** — minimum version
## Conventions ## Conventions
- Use `file(GLOB ...)` for source collection (existing pattern). - Use `file(GLOB ...)` for source collection (existing pattern).
- All targets link `Threads::Threads` and `${ZSTD_LIBRARY}`. - All targets link `Threads::Threads`, `${ZSTD_LIBRARY}`, `OpenSSL::SSL`, `OpenSSL::Crypto`, and `xxhash`.
- Include directories: `src/shared`, `src/server`, `src/client`, `tests` (for test target). - Include directories: `src/shared`, `src/server`, `src/client`, `tests` (for test target).
- Sanitizer support is commented out but present (`-fsanitize=address`). - Sanitizer support: pass `-DSANITIZER=address` or `-DSANITIZER=thread` to cmake (live option in CMakeLists.txt).
- Build with `cmake -B build -S . && cmake --build build -j$(nproc)`. - Build with `cmake -B build -S . && cmake --build build -j$(nproc)`.
- Install dependencies only via the project's custom Docker image (repo-root `Dockerfile`, same image CI uses) — never via host package installs; see `AGENTS.md`.
## When Making Changes ## When Making Changes
@@ -77,28 +105,21 @@ tests/ — test sources (globbed as TEST_SRCS)
3. Add new dependencies with `find_package` or `find_library`. 3. Add new dependencies with `find_package` or `find_library`.
4. When adding a new executable target, follow the pattern of existing targets. 4. When adding a new executable target, follow the pattern of existing targets.
5. When adding a new library (static/shared), use `add_library` and follow the project's naming. 5. When adding a new library (static/shared), use `add_library` and follow the project's naming.
6. For sanitizer builds, use the commented-out `-fsanitize=address` lines as reference. 6. For sanitizer builds, pass `-DSANITIZER=address` or `-DSANITIZER=thread` to cmake (matching CI's matrix strategy).
7. Always verify the build compiles after changes. 7. Always verify the build compiles after changes.
## Sanitizer Configurations ## Sanitizer Configurations
### AddressSanitizer (memory errors) Use the project's built-in `-DSANITIZER=` option (matching the CI matrix):
```bash ```bash
cmake -B build -S . \ cmake -B build -S . -DSANITIZER=address # AddressSanitizer (memory errors)
-DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer -g" \ cmake --build build -j$(nproc)
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address"
cmake -B build -S . -DSANITIZER=thread # ThreadSanitizer (race conditions)
cmake --build build -j$(nproc) cmake --build build -j$(nproc)
``` ```
### ThreadSanitizer (race conditions) For UndefinedBehaviorSanitizer (no `-DSANITIZER=undefined` option in CMakeLists.txt yet), use the manual flag approach:
```bash
cmake -B build -S . \
-DCMAKE_C_FLAGS="-fsanitize=thread -g" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=thread"
cmake --build build -j$(nproc)
```
### UndefinedBehaviorSanitizer
```bash ```bash
cmake -B build -S . \ cmake -B build -S . \
-DCMAKE_C_FLAGS="-fsanitize=undefined -fno-omit-frame-pointer -g" \ -DCMAKE_C_FLAGS="-fsanitize=undefined -fno-omit-frame-pointer -g" \
@@ -106,14 +127,6 @@ cmake -B build -S . \
cmake --build build -j$(nproc) cmake --build build -j$(nproc)
``` ```
### Combined Sanitizers
```bash
cmake -B build -S . \
-DCMAKE_C_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer -g" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,undefined"
cmake --build build -j$(nproc)
```
### Using ccache (faster rebuilds) ### Using ccache (faster rebuilds)
```bash ```bash
cmake -B build -S . -DCMAKE_C_COMPILER_LAUNCHER=ccache cmake -B build -S . -DCMAKE_C_COMPILER_LAUNCHER=ccache
@@ -151,31 +164,19 @@ cmake --build build -j$(nproc)
./build/tests ./build/tests
``` ```
## When Adding Sanitizer Support to CMakeLists.txt ## Sanitizer Integration
Use CMake options for cleaner integration: The project uses a single `SANITIZER` cache variable in `CMakeLists.txt`:
```cmake ```cmake
option(ENABLE_ASAN "Enable AddressSanitizer" OFF) set(SANITIZER "none" CACHE STRING "Sanitizer to enable (address, thread, none)")
option(ENABLE_TSAN "Enable ThreadSanitizer" OFF) set_property(CACHE SANITIZER PROPERTY STRINGS address thread none)
option(ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer" OFF)
if(ENABLE_ASAN)
add_compile_options(-fsanitize=address -fno-omit-frame-pointer)
add_link_options(-fsanitize=address)
endif()
if(ENABLE_TSAN)
add_compile_options(-fsanitize=thread)
add_link_options(-fsanitize=thread)
endif()
if(ENABLE_UBSAN)
add_compile_options(-fsanitize=undefined)
add_link_options(-fsanitize=undefined)
endif()
``` ```
Supported values: `address`, `thread`, `none`. Unknown values trigger `FATAL_ERROR`.
Then build with: Build with:
```bash ```bash
cmake -B build -S . -DENABLE_ASAN=ON cmake -B build -S . -DSANITIZER=address
cmake --build build -j$(nproc)
``` ```
To add support for a new sanitizer (e.g., UBSan), add an `elseif(SANITIZER STREQUAL "undefined")` block following the existing `address`/`thread` pattern.
+18 -17
View File
@@ -16,12 +16,12 @@ Design integration tests that verify the full transfer pipeline works end-to-end
- Custom framework in `tests/test_utils.h` - Custom framework in `tests/test_utils.h`
- Run: `./build/tests` - Run: `./build/tests`
### 2. Integration Tests (existing — `test.py`) ### 2. Integration Tests (existing — `tests/integration/`)
- Full transfer pipeline: client → server → verify - Full transfer pipeline: client → server → verify
- Multiple configurations (TCP, SSH, TLS, compression, multithreading) - Multiple configurations (TCP, SSH, TLS, compression, multithreading)
- Network shaping (LAN, WAN profiles) - Network shaping (LAN, WAN profiles)
- Feature tests (dry run, archive, exclude, delete, incremental, bandwidth limit) - Feature tests (dry run, archive, exclude, delete, incremental, bandwidth limit)
- Run: `python3 test.py` - Run: `python3 -m pytest tests/ -v --tb=short`
### 3. New: Focused Integration Tests ### 3. New: Focused Integration Tests
When adding new features or fixing bugs, write targeted integration tests. When adding new features or fixing bugs, write targeted integration tests.
@@ -106,35 +106,36 @@ test ! -f /tmp/dst/.../extra.txt
### Gitea Workflow Structure (`.gitea/workflows/ci.yaml`) ### Gitea Workflow Structure (`.gitea/workflows/ci.yaml`)
The project uses Gitea Actions. Key jobs: The project uses Gitea Actions. Key jobs:
1. **Build** — compile on push/PR 1. **build-and-test** — compile, unit tests, integration tests on push/PR
2. **Unit tests**run `./build/tests` 2. **sanitizer**ASan + UBSan build and test (separate job)
3. **Integration tests** — run `python3 test.py` (light mode) 3. **clang-tidy** — static analysis on C source files
4. **Sanitizer builds** — ASan, TSan variants
### Adding a New CI Job ### Adding a New CI Job
```yaml ```yaml
jobs: jobs:
sanitizer: sanitizer:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: gitea.tap-tap.win/taptap/fastsync-ci:v7
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install dependencies - name: Configure
run: sudo apt-get update && sudo apt-get install -y libzstd-dev libssl-dev run: cmake -B build-${{ matrix.sanitizer }} -S . -DSANITIZER=${{ matrix.sanitizer }}
- name: Build with ASan - name: Build
run: | run: cmake --build build-${{ matrix.sanitizer }} -j$(nproc)
cmake -B build -S . \ - name: Symlink for integration tests
-DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" \ run: ln -sf build-${{ matrix.sanitizer }} build
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address" - name: Unit Tests
cmake --build build -j$(nproc) run: ./build-${{ matrix.sanitizer }}/tests
- name: Run tests - name: Integration Tests
run: ./build/tests run: LSAN_OPTIONS=suppressions=.lsan-suppressions.txt python3 -m pytest tests/ -v --tb=short
``` ```
The symlink step is required because `tests/conftest.py` expects `./build` to exist.
## Verification Checklist ## Verification Checklist
After any code change: After any code change:
- [ ] Unit tests pass: `./build/tests` - [ ] Unit tests pass: `./build/tests`
- [ ] Integration tests pass: `python3 test.py` (light mode at minimum) - [ ] Integration tests pass: `python3 -m pytest tests/ -v --tb=short`
- [ ] Build clean: no warnings with `-Wall` - [ ] Build clean: no warnings with `-Wall`
- [ ] No memory errors: ASan clean - [ ] No memory errors: ASan clean
- [ ] No thread errors: TSan clean (if threading involved) - [ ] No thread errors: TSan clean (if threading involved)
+134
View File
@@ -0,0 +1,134 @@
---
description: Reviews pull requests comprehensively — code correctness, CI/CD validity, configuration, documentation, and overall PR quality. Use when the user says "review PR", "review this PR", or wants a comprehensive code review.
mode: subagent
---
You are a comprehensive PR reviewer for the FastSync project — a high-performance file synchronization system written in C11.
## Your Role
Review pull requests holistically. You go beyond just C code review — you evaluate CI/CD impact, configuration changes, documentation accuracy, and overall PR quality. You are the final gatekeeper before merge.
## Review Dimensions
### 1. C Code Review
Review all changed `.c` and `.h` files for:
**Memory Safety**
- Every `malloc`/`calloc` has a matching `free` on all code paths (including error paths)
- No use-after-free, no double-free
- Null checks after allocation before use
- Correct buffer sizes (strlen + 1 for null terminators)
- `Data` objects created/destroyed properly via `data_create()`/`data_destroy()`
**Thread Safety**
- Shared state accessed under proper mutex protection (C11 `<threads.h>`)
- No race conditions on queue operations
- Condition variable signals under lock
- No deadlock potential (consistent lock ordering)
- `done` flags checked properly in consumer loops
**Security**
- No `strcpy`/`strcat`/`sprintf` — use `snprintf` with bounds
- `malloc` size calculations don't overflow
- Path traversal prevention (`..` in filenames)
- TLS error codes checked after `SSL_read`/`SSL_write`
- No hardcoded certificates, keys, or credentials
- Received file permissions validated (no SUID/SGID injection)
**Protocol Safety**
- `send_n_data` / `receive_n_data` return values checked
- Status codes validated before use
- Config serialization/deserialization handles partial reads
**Logic Errors**
- Off-by-one in loops/buffers
- Incorrect size calculations
- Wrong enum values or comparisons
- Missing break statements in switch
### 2. Build System Review
If `CMakeLists.txt` is changed:
- Dependencies properly declared with `find_package` or `FetchContent`
- New targets follow existing patterns (link flags, include dirs)
- No duplicate source file additions
- Sanitizer options not accidentally enabled for release builds
- Minimum CMake version is 3.22
### 3. CI/CD Review
If `.gitea/workflows/ci.yaml` is changed:
- Workflow syntax is valid
- New jobs have proper `runs-on` and `container` specifications
- Test commands are correct and will pass
- No secrets or credentials exposed
- Steps are in correct order (checkout before build)
### 4. Configuration & Documentation Review
If agents (`.opencode/agents/`), skills (`.opencode/skills/`), or docs are changed:
- References to file paths are accurate (e.g., `test.py` no longer exists, use `tests/integration/`)
- CMake version references match actual `CMakeLists.txt` (3.22, not 4.1)
- Dependencies listed match actual build requirements (zstd, OpenSSL, xxHash)
- Commands in examples actually work
- No stale references to removed files or changed APIs
### 5. PR Quality
- Commit messages are clear and follow project conventions
- PR description explains what changed and why
- Changes are focused — not mixing unrelated concerns
- No unnecessary file changes (formatting-only diffs on unchanged code)
- Test coverage for new functionality
## Review Checklist
For each PR, evaluate:
- [ ] All changed C files reviewed for memory/thread/protocol/security
- [ ] Build system changes validated
- [ ] CI/CD changes verified (if any)
- [ ] Agent/skill/doc changes checked for accuracy
- [ ] No secrets, keys, or credentials committed
- [ ] Commit history is clean and meaningful
- [ ] New features have test coverage
- [ ] Breaking changes documented
- [ ] Backward compatibility maintained (protocol version field)
## Output Format
```
=== PR REVIEW SUMMARY ===
Branch: <branch-name>
Files reviewed: <count>
Dimensions checked: code, build, CI, docs, quality
=== FINDINGS ===
[CRITICAL] src/shared/protocol.c:142 — memory
Potential buffer overflow in config deserialization
Fix: Add bounds check before memcpy
[WARNING] src/client/client_send.c:87 — thread
Queue accessed without lock in error path
Fix: Acquire mtx before queue_destroy
[STYLE] .opencode/agents/cmake-expert.md:5 — docs
References CMake 4.1 but project uses 3.22
Fix: Update version reference
=== VERDICT ===
[PASS] No critical issues found — safe to merge
— or —
[FAIL] <N> critical issues must be fixed before merge
```
## Rules
- Report ALL issues — don't filter or minimize
- Be specific about line numbers and fix suggestions
- Separate critical from warnings from style
- Check that the PR actually compiles (review CMake changes carefully)
- If agents/docs are changed, verify every reference is current
- Be constructive — suggest fixes, not just problems
+18 -24
View File
@@ -165,34 +165,28 @@ int main() {
## Integration Test Patterns ## Integration Test Patterns
When writing integration tests (Python-based), follow the pattern in `test.py`: When writing integration tests (Python-based), follow the patterns in `tests/integration/`:
- `common.py` — shared helpers (server lifecycle, file verification, transfer utilities)
- `test_preflight.py` — preflight checks and configuration validation
- `test_tcp.py` — TCP transport tests
- `test_ssh.py` — SSH transport tests
- `test_tls.py` — TLS transport tests
- `test_features.py` — feature-specific tests (delete, exclude, incremental, etc.)
Use `tests/conftest.py` fixtures for server setup/teardown (note: the file is at `tests/conftest.py`, not `tests/integration/conftest.py`).
### Minimal Integration Test ### Minimal Integration Test
```python ```python
def test_basic_transfer(): def test_basic_transfer(tmp_path):
# Setup # Setup
source = create_test_files() source = tmp_path / "src"
dest = tempfile.mkdtemp() dest = tmp_path / "dst"
source.mkdir()
# Start server dest.mkdir()
server = subprocess.Popen(["./build/server"], ...) (source / "file.txt").write_text("test content")
time.sleep(0.5)
# Start server and run client (use fixtures from conftest.py)
# Run client # Verify with helper from common.py
result = subprocess.run(
["./build/client", "--source-dir", source,
"--dest-dir", dest, "--save-to-disk"],
capture_output=True, text=True
)
assert result.returncode == 0
# Verify
mismatches, missing = verify_transfer(source, dest)
assert not mismatches
assert not missing
# Cleanup
server.terminate()
``` ```
### Edge Case Tests to Write ### Edge Case Tests to Write
+57
View File
@@ -0,0 +1,57 @@
# AGENTS.md
FastSync is a high-performance file synchronization system written in C11. It supports TCP and SSH transports, TLS encryption (OpenSSL), streaming zstd compression, multithreaded transfers, and incremental sync. The build uses CMake; CI runs on Gitea Actions (`.gitea/workflows/ci.yaml`).
## Dependency installation
**Rule: always install dependencies using the project's custom Docker image — never via ad-hoc system package installs on the host** (no `apt-get install` / `pip install` on the host machine).
The image is built from the repo-root `Dockerfile` and is the same image CI uses: `gitea.tap-tap.win/taptap/fastsync-ci:v7`. It contains the full toolchain: gcc/g++, CMake, libzstd-dev, libssl-dev, make, git, cppcheck, clang-format, python3 + pytest, openssh-client, and Node.js.
```bash
# Use the prebuilt CI image directly (faster, guaranteed CI parity)
docker pull gitea.tap-tap.win/taptap/fastsync-ci:v7
docker tag gitea.tap-tap.win/taptap/fastsync-ci:v7 fastsync-ci:local
# Or build the image from the repo-root Dockerfile
# (Note: the prebuilt :v7 image reflects the previous Dockerfile state;
# rebuild from source to pick up any newly added packages like lcov/valgrind.)
docker build -t fastsync-ci:local .
# Build, run unit tests, and run integration tests inside the container
docker run --rm -v "$PWD:/workspace" -w /workspace fastsync-ci:local \
sh -c 'cmake -B build -S . && cmake --build build -j$(nproc) && ./build/tests && python3 -m pytest tests/'
# Avoid root-owned build/ artifacts by matching your host UID/GID
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/workspace" \
-w /workspace fastsync-ci:local \
sh -c 'cmake -B build -S . && cmake --build build -j$(nproc) && ./build/tests && python3 -m pytest tests/'
```
> **Note:** The first `cmake configure` (`cmake -B build -S .`) fetches xxHash from GitHub via `FetchContent` — network access is required. Subsequent reconfigures reuse the cached source.
If a dependency is missing from the image, add it to the `Dockerfile` (and rebuild) rather than installing it on the host.
## CI Conventions
When configuring for CI parity, use:
```bash
cmake -B build -S . -DSTRICT_WARNINGS=ON # -Wextra -Wpedantic -Werror
cmake -B build -S . -DSANITIZER=address # AddressSanitizer (ASan)
cmake -B build -S . -DSANITIZER=thread # ThreadSanitizer (TSan)
```
The CI workflow (`.gitea/workflows/ci.yaml`) runs lint (clang-format, cppcheck), build + test (unit + integration), and sanitizer (currently only `address`) jobs sequentially.
## Build
```bash
cmake -B build -S . && cmake --build build -j$(nproc)
```
## Test
```bash
./build/tests # unit tests
python3 -m pytest tests/ # integration tests
```
+1 -1
View File
@@ -1,7 +1,7 @@
FROM ubuntu:24.04 FROM ubuntu:24.04
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
gcc g++ make libc6-dev cmake libzstd-dev libssl-dev git ca-certificates curl cppcheck clang-format \ gcc g++ make libc6-dev cmake libzstd-dev libssl-dev git ca-certificates curl cppcheck clang-format \
python3 python3-pip python3-venv openssl openssh-client && \ python3 python3-pip python3-venv openssl openssh-client lcov valgrind && \
pip3 install --break-system-packages pytest && \ pip3 install --break-system-packages pytest && \
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get install -y --no-install-recommends nodejs && \ apt-get install -y --no-install-recommends nodejs && \
+40 -17
View File
@@ -73,6 +73,8 @@ int main(int argc, char* argv[]) {
Config* config = config_create(str_dup(PROTOCOL_VERSION), NULL, NULL, save_to_disk, false, false, Config* config = config_create(str_dup(PROTOCOL_VERSION), NULL, NULL, save_to_disk, false, false,
false, false, 5, false, 0); false, false, 5, false, 0);
int exit_code = 0;
bool config_owned_by_pipeline = false;
int positional_args[2]; int positional_args[2];
int positional_count = 0; int positional_count = 0;
@@ -80,7 +82,7 @@ int main(int argc, char* argv[]) {
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--help") == 0) { if (strcmp(argv[i], "--help") == 0) {
print_usage(); print_usage();
return 0; goto cleanup;
} else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--archive") == 0) { } else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--archive") == 0) {
config->use_compression = true; config->use_compression = true;
config->use_multithreading = true; config->use_multithreading = true;
@@ -165,11 +167,13 @@ int main(int argc, char* argv[]) {
unsigned long long kbps = strtoull(argv[++i], &end, 10); unsigned long long kbps = strtoull(argv[++i], &end, 10);
if (errno != 0 || *end != '\0' || kbps == 0) { if (errno != 0 || *end != '\0' || kbps == 0) {
fprintf(stderr, "Error: --bwlimit must be a positive integer\n"); fprintf(stderr, "Error: --bwlimit must be a positive integer\n");
return 1; exit_code = 1;
goto cleanup;
} }
if (kbps > ULLONG_MAX / 1024) { if (kbps > ULLONG_MAX / 1024) {
fprintf(stderr, "Error: --bwlimit value too large\n"); fprintf(stderr, "Error: --bwlimit value too large\n");
return 1; exit_code = 1;
goto cleanup;
} }
io_set_bwlimit(kbps * 1024); io_set_bwlimit(kbps * 1024);
log_message(LOG_LEVEL_INFO, "Set bandwidth limit to %llu KB/s", kbps); log_message(LOG_LEVEL_INFO, "Set bandwidth limit to %llu KB/s", kbps);
@@ -195,14 +199,16 @@ int main(int argc, char* argv[]) {
} else if (argv[i][0] == '-') { } else if (argv[i][0] == '-') {
fprintf(stderr, "Unknown option: %s\n", argv[i]); fprintf(stderr, "Unknown option: %s\n", argv[i]);
print_usage(); print_usage();
return 1; exit_code = 1;
goto cleanup;
} else { } else {
if (positional_count < 2) if (positional_count < 2)
positional_args[positional_count++] = i; positional_args[positional_count++] = i;
else { else {
fprintf(stderr, "Unexpected argument: %s\n", argv[i]); fprintf(stderr, "Unexpected argument: %s\n", argv[i]);
print_usage(); print_usage();
return 1; exit_code = 1;
goto cleanup;
} }
} }
} }
@@ -218,7 +224,8 @@ int main(int argc, char* argv[]) {
} else if (positional_count == 1) { } else if (positional_count == 1) {
fprintf(stderr, "Error: missing destination argument\n"); fprintf(stderr, "Error: missing destination argument\n");
print_usage(); print_usage();
return 1; exit_code = 1;
goto cleanup;
} else { } else {
if (!config->send_directory && env_source) if (!config->send_directory && env_source)
config->send_directory = str_dup((char*)env_source); config->send_directory = str_dup((char*)env_source);
@@ -229,22 +236,26 @@ int main(int argc, char* argv[]) {
if (!config->send_directory || !config->receive_root_directory) { if (!config->send_directory || !config->receive_root_directory) {
fprintf(stderr, "Error: source and destination directories are required\n"); fprintf(stderr, "Error: source and destination directories are required\n");
print_usage(); print_usage();
return 1; exit_code = 1;
goto cleanup;
} }
if (config->use_sendfile && (config->use_chunk_serialization || config->use_compression)) { if (config->use_sendfile && (config->use_chunk_serialization || config->use_compression)) {
fprintf(stderr, "Error: -f/--sendfile cannot be combined with -c (compression) or -s (chunk " fprintf(stderr, "Error: -f/--sendfile cannot be combined with -c (compression) or -s (chunk "
"serialization)\n"); "serialization)\n");
return 1; exit_code = 1;
goto cleanup;
} }
if (config->transport == TRANSPORT_SSH && config->use_sendfile) { if (config->transport == TRANSPORT_SSH && config->use_sendfile) {
fprintf(stderr, "Error: -f/--sendfile is not supported with SSH transport\n"); fprintf(stderr, "Error: -f/--sendfile is not supported with SSH transport\n");
return 1; exit_code = 1;
goto cleanup;
} }
if (config->use_incremental && config->use_chunk_serialization) { if (config->use_incremental && config->use_chunk_serialization) {
fprintf(stderr, "Error: --incremental is not supported with -s (chunk serialization)\n"); fprintf(stderr, "Error: --incremental is not supported with -s (chunk serialization)\n");
return 1; exit_code = 1;
goto cleanup;
} }
if (config->use_incremental && !config->use_metadata) { if (config->use_incremental && !config->use_metadata) {
@@ -254,15 +265,18 @@ int main(int argc, char* argv[]) {
if (config->use_delta && !config->use_incremental) { if (config->use_delta && !config->use_incremental) {
fprintf(stderr, "Error: --delta requires --incremental\n"); fprintf(stderr, "Error: --delta requires --incremental\n");
return 1; exit_code = 1;
goto cleanup;
} }
if (config->use_delta && config->use_chunk_serialization) { if (config->use_delta && config->use_chunk_serialization) {
fprintf(stderr, "Error: --delta cannot be combined with -s (chunk serialization)\n"); fprintf(stderr, "Error: --delta cannot be combined with -s (chunk serialization)\n");
return 1; exit_code = 1;
goto cleanup;
} }
if (config->use_delta && config->use_sendfile) { if (config->use_delta && config->use_sendfile) {
fprintf(stderr, "Error: --delta cannot be combined with -f (sendfile)\n"); fprintf(stderr, "Error: --delta cannot be combined with -f (sendfile)\n");
return 1; exit_code = 1;
goto cleanup;
} }
if (config->use_delta && !config->use_metadata) { if (config->use_delta && !config->use_metadata) {
log_message(LOG_LEVEL_INFO, "Enabling metadata preservation for --delta"); log_message(LOG_LEVEL_INFO, "Enabling metadata preservation for --delta");
@@ -272,12 +286,21 @@ int main(int argc, char* argv[]) {
if (config->use_tls) { if (config->use_tls) {
if (!config->tls_cert || !config->tls_key) { if (!config->tls_cert || !config->tls_key) {
fprintf(stderr, "Error: --tls requires --cert and --key\n"); fprintf(stderr, "Error: --tls requires --cert and --key\n");
return 1; exit_code = 1;
goto cleanup;
} }
tls_global_init(); tls_global_init();
} }
if (config->use_multithreading) if (config->use_multithreading) {
return send_files_multithreaded(config); config_owned_by_pipeline = true;
return send_files(config); exit_code = send_files_multithreaded(config);
} else {
exit_code = send_files(config);
}
cleanup:
if (!config_owned_by_pipeline)
config_delete(config);
return exit_code;
} }