The crate uses Rust edition 2024, as declared in Cargo.toml.
The repository selects the nightly Rust toolchain with the rustfmt and
clippy components in rust-toolchain.toml.
For complete contribution guidelines, environment setup, and contributor
expectations, see CONTRIBUTING.md.
Install rustup, just, lefthook, and
actionlint separately. Install semgrep and lizard separately to run the
configured quality checks.
Then install the Rust development components, coverage tool, and Git hooks:
just tools
just install-hooksjust tools installs rustfmt, clippy, and cargo-llvm-cov.
just install-hooks invokes the separately installed lefthook executable.
The installed pre-push hook runs Semgrep, locked Clippy, and Lizard checks
sequentially, and blocks pushes when any check fails.
The project uses just for task automation:
| Recipe | Description | Underlying command |
|---|---|---|
just |
List the available recipes | just --list |
just build |
Build the project in debug mode | cargo build |
just run *args |
Run the application with passthrough arguments | cargo run -- run {{args}} |
just run-all-workflows *args |
Run all workflows in the repository | cargo run -- run --all-workflows {{args}} |
just list-workflows |
List workflows discovered in the repository | cargo run -- list-workflows |
just list-actions |
List actions referenced across workflows | cargo run -- list-actions |
just test |
Run all default-feature test targets with aggregate line-coverage enforcement at 80%; excludes feature-gated container integration | COVERAGE_THRESHOLD=80 ./scripts/check_coverage.sh |
just test --crate domain |
Run coverage for one crate; supported names are root, domain, application, infrastructure, and presentation |
COVERAGE_THRESHOLD=80 ./scripts/check_coverage.sh --crate domain |
just test-local |
Run all default-feature test targets without coverage; excludes feature-gated container integration | cargo test |
just lint |
Run the same locked, all-target Clippy check used by CI | cargo clippy --all-targets --locked -- -D warnings |
just lint-fix *args |
Preserve staged files, then run the configured Clippy fix command with optional arguments; no arguments perform no work | bash scripts/lint-fix-staged.sh {{args}} |
just fmt |
Format the entire workspace | cargo fmt |
just fmt-check |
Check formatting without modifying files | cargo fmt --check |
just ci |
Run the local Validate, Test, and Lint checks in CI order | just fmt-check && just test && just lint |
just tools |
Install rustfmt, clippy, and cargo-llvm-cov |
rustup component add rustfmt clippy && cargo install cargo-llvm-cov --locked --force |
just clean |
Remove Cargo build artifacts | cargo clean |
just lint-workflows |
Lint Forgejo Actions workflows | actionlint -config-file .actionlint.yaml .forgejo/workflows/*.yml |
just semgrep |
Run the configured Semgrep rules and report findings as errors | semgrep scan --config .semgrep --error . |
just lizard |
Run Lizard with the repository's complexity, length, argument-count, and ignore thresholds | lizard -C 5 -L 50 -a 5 -i 0 . |
just install-hooks |
Install the configured lefthook Git hooks |
lefthook install |
just install-dev |
Install a debug binary for local iteration | cargo install --path . --debug |
just install |
Install a release binary to ~/.cargo/bin |
cargo install --path . |
CLI commands are defined with clap in crates/presentation/src/cli.
When adding a command, keep parsing and execution separate:
- Add an argument type in its own
*_args.rsmodule using#[derive(clap::Args)]. - Register the type as a variant in
cli/command.rswith#[derive(clap::Subcommand)]. - Add the corresponding match arm in
Cli::execute_commandincli/cli_app.rs. - Add parser and handler tests, then document the command in
README.mdanddocs/usage.mdwhen it is user-facing.
For example, a new inspect command would be registered alongside run:
#[derive(clap::Subcommand)]
enum Command {
Run(Box<RunArgs>),
Inspect(InspectArgs), // define InspectArgs in inspect_args.rs
}The existing run command is invoked through Cargo's argument separator:
cargo run -- run --event pull_request --workflow CIThe first -- passes arguments to ephact; run selects the application
subcommand and --workflow CI is parsed by RunArgs. Follow the same pattern
for a new command: register it in Command, add its arguments, and dispatch it
from Cli. Noninteractive workflow runs require --event.
Use the existing just recipe names to run the checks that correspond to the
Forgejo CI jobs. The repository names these recipes ci, lint, and test
(not run-ci, run-lint, or run-test):
# Run the local Validate, Test, and Lint checks in CI order.
just ci
# Run one check independently.
just lint
just test
just fmt-check
just lint-workflowsRun just test --crate domain to limit coverage to one crate. Run just lint-workflows to validate Forgejo workflow syntax with actionlint.
| Local command | CI job | What it checks |
|---|---|---|
just fmt-check |
Validate | Confirms the workspace is formatted with Rustfmt. |
just test |
Test | Runs the default-feature test suite with the configured 80% coverage threshold. |
just lint |
Lint | Runs locked Clippy checks for all targets with warnings denied. |
just lint-workflows |
Workflow lint | Validates Forgejo workflow syntax with actionlint. |
just ci |
Validate, Test, Lint | Runs the three local checks in the same order as CI. |
Run just ci before opening a pull request when you want the local equivalent
of the main CI checks. Install the tools from the Setup section
first. The Verify secrets CI job is intentionally not part of just ci:
it validates repository credentials and can only run reliably inside Forgejo.
Run all default-feature test targets with aggregate line coverage enforced at 80%:
just testRun coverage for one crate with the explicit --crate option:
just test --crate domainSupported crate names are root, domain, application, infrastructure, and
presentation.
Run the same default-feature scope without calculating coverage:
just test-localNeither command enables the feature-gated container integration target.
The real container integration suite requires at least one available Docker or
Podman daemon on Linux and is gated behind the container-integration feature:
cargo test --features container-integrationThe CI workflow in .forgejo/workflows/ci.yml runs on pushes to main, pull
requests targeting main, and manual dispatch:
- Validate runs
cargo fmt --all -- --check. - Workflow lint downloads the pinned actionlint release and validates every
.forgejo/workflows/*.ymlfile with the repository configuration. - Test runs
just test, covering default-feature test targets with the 80% aggregate line threshold while excluding feature-gated container integration. - Lint runs
cargo clippy --all-targets --locked -- -D warnings. - Verify secrets verifies repository secrets for same-repository runs.
The Validate, Test, and Lint jobs share the
.forgejo/actions/setup-rust-environment composite action. It combines
install-dependencies with cache-rust-deps and accepts the same cache inputs:
- uses: ./.forgejo/actions/setup-rust-environment
with:
cache-key-prefix: ephact
rustc-version: stable
include-sysroot: "false"Workflow linting is available locally through just lint-workflows and runs in
CI as the Workflow lint job.
Merging a pull request into main triggers
.forgejo/workflows/update-changelog.yml. The workflow:
- Checks that the pull request was merged into
main. - Ignores
chore/changelog/andrelease/vbranches to prevent recursion. - Checks out the updated default branch with the built-in Forgejo token.
- Generates the unreleased changelog with
git-cliff. - Updates the stable
chore/changelog/mainbranch with the generated commit. - Creates or reuses a pull request from that branch into protected
mainthrough the Forgejo API.
Versioned releases use prepare-release.yml instead. That workflow passes a
release version and target release branch to the same action, which updates the
versioned changelog directly on the release branch.
Configure the following secrets in Codeberg repository settings under Settings > Actions > Secrets:
| Secret | Purpose | Source / Value |
|---|---|---|
CARGO_REGISTRY_TOKEN |
Crates.io production publishing | Crates.io API token |
GH_RELEASE_TOKEN |
GitHub mirror release publishing | GitHub personal access token |
FORGEJO_TOKEN is automatically created and injected by Forgejo Actions for each
workflow run. Do not add FORGEJO_TOKEN to repository secrets; use the built-in
${{ github.token }} context when a workflow needs the default token.
The changelog workflow passes the built-in token to both checkout and the Forgejo pull-request API call. Versioned release updates continue to push directly to their release branch.
GitHub-style permissions: declarations are unsupported by Forgejo Actions and
must not be placed in repository workflows. Capabilities beyond the built-in
token require a dedicated repository secret or a Forgejo Authorized
Integration configured in the Forgejo user settings. See the official
Authorized Integrations
documentation.
The integration must be narrowly restricted to the source repository, workflow file, reference, and event. The workflow then enables OIDC, requests a JWT for the generated audience, and sends that JWT as a Bearer token. The generated audience is an identifier, not a secret; grant integration capabilities only for the resources required.