| name | Erlang |
| description | Execute these commands after EVERY implementation (see AGENT_AUTOMATION module for full workflow). |
| version | 1.0.0 |
| category | languages |
| author | Rulebook |
| tags | ["languages","language"] |
| dependencies | [] |
| conflicts | [] |
Erlang Project Rules
Agent Automation Commands
CRITICAL: Execute these commands after EVERY implementation (see AGENT_AUTOMATION module for full workflow).
rebar3 format --verify
rebar3 dialyzer
rebar3 eunit
rebar3 ct
rebar3 cover
rebar3 compile
rebar3 tree
Erlang Configuration
CRITICAL: Use Erlang/OTP 26+ with rebar3 and proper testing.
- Version: Erlang/OTP 26+
- Recommended: Erlang/OTP 27+
- Build Tool: rebar3
- Testing: EUnit, Common Test, PropEr
- Linter: Elvis
- Dialyzer: Type analysis
- Documentation: EDoc
rebar.config Requirements
{erl_opts, [
debug_info,
warnings_as_errors,
warn_export_all,
warn_unused_import,
warn_untyped_record
]}.
{deps, [
{jsx, "3.1.0"}
]}.
{plugins, [
rebar3_hex,
rebar3_ex_doc,
rebar3_proper,
rebar3_lint
]}.
{profiles, [
{test, [
{erl_opts, [debug_info, export_all, nowarn_export_all]},
{deps, [
{meck, "0.9.2"},
{proper, "1.4.0"}
]}
]},
{prod, [
{erl_opts, [no_debug_info, warnings_as_errors]},
{relx, [
{dev_mode, false},
{include_erts, true},
{include_src, false}
]}
]}
]}.
{cover_enabled, true}.
{cover_opts, [verbose]}.
{dialyzer, [
{warnings, [
error_handling,
underspecs,
unmatched_returns
]},
{get_warnings, true},
{plt_apps, top_level_deps},
{plt_extra_apps, []},
{plt_location, local},
{base_plt_apps, [erts, kernel, stdlib]},
{base_plt_location, global}
]}.
{xref_checks, [
undefined_function_calls,
undefined_functions,
locals_not_used,
deprecated_function_calls,
deprecated_functions
]}.
Code Quality Standards
Mandatory Quality Checks
CRITICAL: After implementing ANY feature, you MUST run these commands in order.
IMPORTANT: These commands MUST match your GitHub Actions workflows to prevent CI/CD failures!
rebar3 as test fmt --check
rebar3 as test lint
rebar3 compile
rebar3 dialyzer
rebar3 eunit
rebar3 ct
rebar3 proper
rebar3 cover
rebar3 xref
If ANY of these fail, you MUST fix the issues before committing.
Why This Matters:
- Running different commands locally than in CI causes OTP release failures
- Dialyzer errors caught in CI but not locally = type safety issues
- Example: Skipping Dialyzer locally = CI catches type discrepancies
- Example: Missing xref check = undefined function calls in production
- Example: Not running PropEr = property violations in production
Testing
- EUnit: Unit testing framework
- Common Test: Integration and system tests
- PropEr: Property-based testing
- Location:
test/ directory
Example EUnit test:
-module(data_processor_tests).
-include_lib("eunit/include/eunit.hrl").
process_valid_input_test() ->
Input = [1, 2, 3, 4, 5],
Result = data_processor:process(Input, 0.5),
?assert(length(Result) > 0).
process_empty_input_test() ->
Result = data_processor:process([], 0.5),
?assertEqual([], Result).
process_invalid_threshold_test() ->
?assertError(badarg, data_processor:process([1, 2, 3], -1)).
process_with_options_test() ->
Options = #{threshold => 0.5, verbose => true},
Result = data_processor:process([1, 2, 3], Options),
?assert(is_list(Result)).
Example Common Test:
-module(integration_SUITE).
-include_lib("common_test/include/ct.hrl").
-export([all/0, init_per_suite/1, end_per_suite/1]).
-export([test_basic_workflow/1, test_error_handling/1]).
all() ->
[test_basic_workflow, test_error_handling].
init_per_suite(Config) ->
application:ensure_all_started(your_app),
Config.
end_per_suite(_Config) ->
application:stop(your_app),
ok.
test_basic_workflow(_Config) ->
{ok, Pid} = data_processor:start_link(),
ok = data_processor:process(Pid, [1, 2, 3]),
{ok, Result} = data_processor:get_result(Pid),
true = length(Result) > 0,
ok = data_processor:stop(Pid).
test_error_handling(_Config) ->
{ok, Pid} = data_processor:start_link(),
{error, badarg} = data_processor:process(Pid, invalid_input),
ok = data_processor:stop(Pid).
Example PropEr property:
-module(prop_data_processor).
-include_lib("proper/include/proper.hrl").
-include_lib("eunit/include/eunit.hrl").
prop_process_preserves_length() ->
?FORALL(List, list(integer()),
begin
Result = data_processor:process(List, 0.5),
length(Result) =< length(List)
end).
prop_process_filters_correctly() ->
?FORALL({List, Threshold}, {list(number()), number()},
begin
Result = data_processor:process(List, Threshold),
lists:all(fun(X) -> X > Threshold end, Result)
end).
% Run properties
process_properties_test() ->
?assert(proper:quickcheck(prop_process_preserves_length(), 100)),
?assert(proper:quickcheck(prop_process_filters_correctly(), 100)).
OTP Patterns
CRITICAL: Follow OTP principles for robust systems.
-module(data_server).
-behaviour(gen_server).
%% API
-export([start_link/0, process/2, stop/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
-record(state, {
threshold :: float(),
cache :: map()
}).
%%% API
start_link() ->
gen_server:start_link(?MODULE, [], []).
process(Pid, Data) ->
gen_server:call(Pid, {process, Data}).
stop(Pid) ->
gen_server:stop(Pid).
%%% gen_server callbacks
init([]) ->
{ok, #state{threshold = 0.5, cache = #{}}}.
handle_call({process, Data}, _From, State) ->
Result = filter_data(Data, State#state.threshold),
NewCache = maps:put(Data, Result, State#state.cache),
{reply, {ok, Result}, State#state{cache = NewCache}};
handle_call(_Request, _From, State) ->
{reply, {error, unknown_request}, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
%%% Internal functions
filter_data(Data, Threshold) ->
[X || X <- Data, X > Threshold].
Best Practices
DO's ✅
- USE OTP behaviors (gen_server, gen_statem, supervisor)
- HANDLE all error cases with pattern matching
- USE typespecs for all exported functions
- TEST with PropEr for property-based testing
- USE supervisor trees for fault tolerance
- VALIDATE inputs in public APIs
- DOCUMENT with EDoc
DON'Ts ❌
- NEVER ignore Dialyzer warnings
- NEVER use catch-all patterns without logging
- NEVER create processes without supervision
- NEVER skip xref checks
- NEVER use deprecated functions
- NEVER ignore test failures
- NEVER deploy without Dialyzer clean
Example with typespecs:
-module(calculator).
-export([add/2, divide/2]).
-spec add(number(), number()) -> number().
add(A, B) -> A + B.
-spec divide(number(), number()) -> {ok, float()} | {error, divide_by_zero}.
divide(_A, 0) -> {error, divide_by_zero};
divide(A, B) -> {ok, A / B}.
CI/CD Requirements
Must include GitHub Actions workflows:
-
Testing (erlang-test.yml):
- OTP versions: 26, 27
- Run EUnit, CT, PropEr
- Coverage reporting
-
Linting (erlang-lint.yml):
- Elvis linting
- Dialyzer type checking
- xref verification
-
Build (erlang-build.yml):
- Create OTP release
- Verify all applications start
Publishing to Hex.pm
rebar3 dialyzer
rebar3 eunit
rebar3 ct
rebar3 hex publish