Skip to content

Instantly share code, notes, and snippets.

@s-celles
Created December 22, 2025 14:39
Show Gist options
  • Select an option

  • Save s-celles/91a77d75faf3b8ed0f9704c70f02c06c to your computer and use it in GitHub Desktop.

Select an option

Save s-celles/91a77d75faf3b8ed0f9704c70f02c06c to your computer and use it in GitHub Desktop.
Julialang constitution

Julia Software Constitution

A Specification Kit for Creating Professional Julia Packages

Version: 1.0.0
Last Updated: December 2025


Table of Contents

  1. Philosophy & Principles
  2. Project Structure
  3. Naming Conventions
  4. Code Style Guide
  5. Type System & Multiple Dispatch
  6. Module Organization
  7. Documentation Standards
  8. Testing Requirements
  9. Error Handling
  10. Performance Guidelines
  11. Dependencies & Compatibility
  12. CI/CD Configuration
  13. Version Control Practices
  14. Licensing & Legal
  15. Release Process
  16. Community Standards

1. Philosophy & Principles

1.1 Core Values

Julia software developed under this constitution adheres to:

  • Composability: Design for interoperability with the broader Julia ecosystem
  • Type Stability: Prioritize predictable, inferrable return types
  • Zero-Cost Abstractions: High-level code should compile to efficient machine code
  • Reproducibility: All results must be reproducible given the same inputs and environment
  • Transparency: Code should be readable and self-documenting

1.2 The Rule of Least Surprise

Functions and types should behave as users expect. When in doubt, follow conventions established by Base Julia and major ecosystem packages.

1.3 Correctness Before Optimization

Write correct, clear code first. Optimize only after profiling identifies actual bottlenecks.


2. Project Structure

2.1 Standard Directory Layout

MyPackage/
├── .github/
│   ├── workflows/
│   │   ├── CI.yml
│   │   ├── Documentation.yml
│   │   └── CompatHelper.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── dependabot.yml
├── docs/
│   ├── src/
│   │   ├── index.md
│   │   ├── getting_started.md
│   │   ├── api.md
│   │   └── examples.md
│   ├── make.jl
│   └── Project.toml
├── src/
│   ├── MyPackage.jl          # Main module file
│   ├── types.jl              # Type definitions
│   ├── core.jl               # Core functionality
│   ├── utils.jl              # Utility functions
│   └── deprecated.jl         # Deprecated functionality
├── test/
│   ├── runtests.jl           # Test entry point
│   ├── test_types.jl
│   ├── test_core.jl
│   └── Project.toml          # Test-specific dependencies
├── benchmark/
│   ├── benchmarks.jl
│   └── Project.toml
├── examples/
│   └── basic_usage.jl
├── .gitignore
├── .JuliaFormatter.toml
├── CHANGELOG.md
├── CITATION.cff
├── CONTRIBUTING.md
├── LICENSE
├── Project.toml
└── README.md

2.2 Required Files

Every package MUST include:

  • Project.toml - Package metadata and dependencies
  • src/PackageName.jl - Main module file
  • test/runtests.jl - Test suite entry point
  • README.md - Package overview and quick start
  • LICENSE - Open source license
  • .gitignore - Git ignore patterns

2.3 Project.toml Template

name = "MyPackage"
uuid = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
authors = ["Author Name <author@example.com>"]
version = "0.1.0"

[deps]
# Direct dependencies only

[weakdeps]
# Optional dependencies for extensions

[extensions]
# Package extensions

[compat]
julia = "1.10"
# Explicit compat bounds for all dependencies

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]

3. Naming Conventions

3.1 Package Names

  • Use PascalCase for package names
  • Names should be descriptive but concise
  • Avoid prefixes like Jl or suffixes like .jl in the package name
  • Check JuliaHub/General registry for name conflicts
# Good
DataFrames, Plots, HTTP, JSON3

# Avoid
JuliaDataFrames, PlotPackage, my_package

3.2 Module Names

  • Match the package name exactly
  • Submodules use PascalCase
module MyPackage
    module SubModule
    end
end

3.3 Type Names

  • Use PascalCase for all types
  • Abstract types should describe a category: AbstractContainer, AbstractSolver
  • Concrete types should be specific: LinearSolver, HashContainer
# Abstract types
abstract type AbstractModel end
abstract type AbstractOptimizer <: AbstractModel end

# Concrete types
struct GradientDescent <: AbstractOptimizer
    learning_rate::Float64
    momentum::Float64
end

# Parametric types
struct Container{T} <: AbstractContainer
    data::Vector{T}
end

3.4 Function Names

  • Use snake_case for functions
  • Predicates (returning Bool) end with ? (not is_)
  • Mutating functions end with !
  • Use verbs for actions, nouns for accessors
# Good
calculate_distance(a, b)
isvalid(x)           # Predicate - returns Bool
normalize!(vector)   # Mutates argument

# Avoid  
calculateDistance(a, b)  # camelCase
is_valid(x)              # Use isvalid
Normalize(vector)        # PascalCase for functions

3.5 Variable Names

  • Use snake_case for variables
  • Single-letter variables only for mathematical conventions or very short scope
  • Constants use SCREAMING_SNAKE_CASE
# Good
user_count = 10
matrix_size = (m, n)
const DEFAULT_TOLERANCE = 1e-8
const MAX_ITERATIONS = 1000

# Mathematical conventions (acceptable in limited scope)
for i in 1:n
    x = A[i, :]
end

3.6 File Names

  • Use snake_case.jl for source files
  • File names should reflect content
  • Main module file matches package name exactly
# Good
src/MyPackage.jl      # Main module
src/linear_algebra.jl
src/io_utils.jl
test/test_core.jl

# Avoid
src/LinearAlgebra.jl  # Conflicts with stdlib
src/utils.JL          # Wrong extension case

4. Code Style Guide

4.1 Formatting Standards

Use JuliaFormatter.jl with this configuration:

# .JuliaFormatter.toml
style = "sciml"
indent = 4
margin = 92
always_for_in = true
whitespace_typedefs = true
whitespace_ops_in_indices = true
remove_extra_newlines = true
short_function_def = true
always_use_return = false
whitespace_in_kwargs = true
annotate_untyped_fields_with_any = false
format_docstrings = true
align_struct_field = false
align_assignment = false
align_pair_arrow = false
normalize_line_endings = "unix"
trailing_comma = true
join_lines_based_on_source = true
indent_submodule = true
separate_kwargs_with_semicolon = true
surround_wherecolon = false

4.2 Indentation and Whitespace

# 4 spaces for indentation (no tabs)
function calculate_result(x, y)
    if x > 0
        return x + y
    else
        return x - y
    end
end

# Space after commas, around operators
result = func(a, b, c)
x = a + b * c

# No space inside brackets
array = [1, 2, 3]
dict = Dict("a" => 1, "b" => 2)

# Space around = in keyword arguments
func(x; keyword=value)  # SciML style

4.3 Line Length

  • Maximum 92 characters per line
  • Break long lines at logical points
# Good - break at operators or after opening parenthesis
result = very_long_function_name(
    argument_one,
    argument_two,
    keyword_argument=value,
)

# Good - break method chains
result = data |>
    transform_step_one |>
    transform_step_two |>
    final_aggregation

4.4 Function Definitions

# Short functions - single line acceptable
square(x) = x^2
add(a, b) = a + b

# Multi-line functions
function complex_calculation(
    input_data::AbstractVector{T},
    parameters::Dict{Symbol,Any};
    tolerance::Float64=1e-8,
    max_iterations::Int=100,
) where {T<:Number}
    # Implementation
    result = zero(T)
    for i in 1:max_iterations
        # ...
    end
    return result
end

# Multiple dispatch - explicit return type when non-obvious
function Base.convert(::Type{MyType}, x::Int)::MyType
    return MyType(x)
end

4.5 Control Flow

# Prefer ternary for simple conditionals
status = isvalid(x) ? :valid : :invalid

# Use if-else for complex logic
if condition_one
    action_one()
elseif condition_two
    action_two()
else
    default_action()
end

# Guard clauses for early returns
function process(x)
    isnothing(x) && return nothing
    !isvalid(x) && throw(ArgumentError("Invalid input"))
    
    # Main logic
    return transform(x)
end

4.6 Imports and Using

module MyPackage

# Standard library imports first
using LinearAlgebra: norm, dot, ×
using Statistics: mean, std

# External packages second
using DataStructures: OrderedDict
using StaticArrays: SVector, SMatrix

# Explicit imports for extension
import Base: show, convert, iterate

# Export public API
export MyType, calculate, transform!

end

5. Type System & Multiple Dispatch

5.1 Type Hierarchy Design

# Define clear abstract type hierarchies
abstract type AbstractSolver end
abstract type AbstractIterativeSolver <: AbstractSolver end
abstract type AbstractDirectSolver <: AbstractSolver end

# Concrete implementations
struct ConjugateGradient <: AbstractIterativeSolver
    tolerance::Float64
    max_iterations::Int
end

struct LUFactorization <: AbstractDirectSolver
    pivot::Bool
end

5.2 Struct Design Principles

# Prefer immutable structs by default
struct Point{T<:Real}
    x::T
    y::T
end

# Use mutable only when necessary
mutable struct Counter
    value::Int
end

# Use inner constructors for validation
struct PositiveReal
    value::Float64
    
    function PositiveReal(x::Real)
        x > 0 || throw(DomainError(x, "Value must be positive"))
        return new(Float64(x))
    end
end

# Outer constructors for convenience
Point(x::Real, y::Real) = Point(promote(x, y)...)

5.3 Type Annotations

# Annotate struct fields
struct Config
    name::String
    value::Float64
    options::Dict{Symbol,Any}
end

# Annotate function arguments for dispatch
function process(data::AbstractVector{<:Number})
    # ...
end

# Avoid over-constraining - use abstract types
# Good
function compute(x::AbstractArray)
    # Works with Array, SubArray, etc.
end

# Avoid
function compute(x::Array{Float64,1})
    # Only works with Vector{Float64}
end

5.4 Type Stability

# Type-stable function
function add_one(x::T) where {T<:Number}
    return x + one(T)
end

# Avoid type instability
# Bad - return type depends on runtime value
function unstable(x)
    if x > 0
        return 1      # Int
    else
        return 1.0    # Float64
    end
end

# Good - consistent return type
function stable(x)
    if x > 0
        return 1.0
    else
        return 1.0
    end
end

# Use @code_warntype to check
@code_warntype stable(1.0)

5.5 Multiple Dispatch Best Practices

# Define generic fallback
function process(x)
    throw(MethodError(process, (x,)))
end

# Specialize for specific types
function process(x::Number)
    return x^2
end

function process(x::AbstractString)
    return uppercase(x)
end

function process(x::AbstractVector)
    return map(process, x)
end

# Use traits for behavior dispatch
abstract type SizeCategory end
struct SmallSize <: SizeCategory end
struct LargeSize <: SizeCategory end

size_category(x) = length(x) < 100 ? SmallSize() : LargeSize()

function optimize(x)
    return _optimize(size_category(x), x)
end

_optimize(::SmallSize, x) = direct_method(x)
_optimize(::LargeSize, x) = iterative_method(x)

6. Module Organization

6.1 Main Module Structure

# src/MyPackage.jl
module MyPackage

# Version
const VERSION = v"0.1.0"

# Dependencies
using LinearAlgebra
using Statistics

# Internal imports
import Base: show, convert

# Include source files in dependency order
include("types.jl")
include("core.jl")
include("utils.jl")
include("io.jl")

# Public API exports
export AbstractModel, ConcreteModel
export fit!, predict, evaluate
export load_model, save_model

# Precompilation (Julia 1.9+)
using PrecompileTools
@setup_workload begin
    @compile_workload begin
        model = ConcreteModel()
        fit!(model, rand(100, 10), rand(100))
    end
end

end # module

6.2 Submodule Organization

# For large packages, use submodules
module MyPackage

# Core submodule
module Core
    export AbstractType, CoreFunction
    include("core/types.jl")
    include("core/functions.jl")
end

# IO submodule
module IO
    using ..Core: AbstractType
    export read_data, write_data
    include("io/readers.jl")
    include("io/writers.jl")
end

# Re-export commonly used items
using .Core
using .IO

export AbstractType, CoreFunction
export read_data, write_data

end

6.3 Package Extensions (Julia 1.9+)

# Project.toml
[weakdeps]
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"

[extensions]
MyPackagePlotsExt = "Plots"
MyPackageMakieExt = "Makie"

# ext/MyPackagePlotsExt.jl
module MyPackagePlotsExt

using MyPackage
using Plots

function MyPackage.plot_result(result::MyPackage.Result)
    # Plots-specific implementation
end

end

7. Documentation Standards

7.1 Docstring Format

"""
    function_name(arg1, arg2; keyword=default) -> ReturnType

Brief one-line description of the function.

Extended description providing more context, explaining the algorithm,
or describing edge cases. This can span multiple paragraphs.

# Arguments
- `arg1::Type`: Description of first argument.
- `arg2::Type`: Description of second argument.

# Keywords
- `keyword::Type=default`: Description of keyword argument.

# Returns
- `ReturnType`: Description of return value.

# Throws
- `ArgumentError`: When inputs are invalid.
- `DomainError`: When values are out of bounds.

# Examples
```julia
julia> result = function_name(1, 2)
3

julia> function_name(1, 2; keyword=true)
4

Extended Help

Additional details for ??function_name:

  • Implementation notes
  • Performance characteristics
  • Related functions

See also: related_function, another_function """ function function_name(arg1::Type1, arg2::Type2; keyword::Bool=false) # Implementation end


### 7.2 Type Documentation

```julia
"""
    MyType{T} <: AbstractType

Short description of the type.

Extended description explaining the purpose, use cases,
and design decisions for this type.

# Fields
- `field1::T`: Description of field.
- `field2::Int`: Description of field.

# Constructors
    MyType(field1, field2)
    MyType(field1)  # Uses default field2

# Examples
```julia
julia> obj = MyType(1.0, 2)
MyType{Float64}(1.0, 2)

julia> obj.field1
1.0

See also: RelatedType """ struct MyType{T} <: AbstractType field1::T field2::Int end


### 7.3 Documentation Website

Use Documenter.jl with this structure:

```julia
# docs/make.jl
using Documenter
using MyPackage

DocMeta.setdocmeta!(
    MyPackage,
    :DocTestSetup,
    :(using MyPackage);
    recursive=true,
)

makedocs(
    sitename="MyPackage.jl",
    modules=[MyPackage],
    authors="Author Name",
    format=Documenter.HTML(
        prettyurls=get(ENV, "CI", nothing) == "true",
        canonical="https://username.github.io/MyPackage.jl",
        assets=["assets/favicon.ico"],
        sidebar_sitename=true,
    ),
    pages=[
        "Home" => "index.md",
        "Getting Started" => "getting_started.md",
        "Tutorials" => [
            "Basic Usage" => "tutorials/basic.md",
            "Advanced Topics" => "tutorials/advanced.md",
        ],
        "API Reference" => "api.md",
        "Contributing" => "contributing.md",
        "Changelog" => "changelog.md",
    ],
    checkdocs=:exports,
    linkcheck=true,
    warnonly=[:missing_docs],
)

deploydocs(
    repo="github.com/username/MyPackage.jl.git",
    devbranch="main",
    push_preview=true,
)

7.4 README Template

# MyPackage.jl

[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://username.github.io/MyPackage.jl/stable/)
[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://username.github.io/MyPackage.jl/dev/)
[![Build Status](https://github.com/username/MyPackage.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/username/MyPackage.jl/actions/workflows/CI.yml?query=branch%3Amain)
[![Coverage](https://codecov.io/gh/username/MyPackage.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/username/MyPackage.jl)
[![Code Style: Blue](https://img.shields.io/badge/code%20style-blue-4495d1.svg)](https://github.com/invenia/BlueStyle)

Brief description of what the package does.

## Installation

```julia
using Pkg
Pkg.add("MyPackage")

Quick Start

using MyPackage

# Basic example
result = my_function(data)

Features

  • Feature 1
  • Feature 2
  • Feature 3

Documentation

For detailed documentation, see the stable docs.

Contributing

Contributions are welcome! Please read our Contributing Guide.

License

This project is licensed under the MIT License - see the LICENSE file.

Citation

If you use this package in your research, please cite:

@software{mypackage,
  author = {Author Name},
  title = {MyPackage.jl},
  year = {2025},
  url = {https://github.com/username/MyPackage.jl}
}

---

## 8. Testing Requirements

### 8.1 Test Structure

```julia
# test/runtests.jl
using MyPackage
using Test
using Aqua
using JET

@testset "MyPackage.jl" begin
    # Code quality tests
    @testset "Code Quality (Aqua.jl)" begin
        Aqua.test_all(
            MyPackage;
            ambiguities=false,  # Customize as needed
        )
    end
    
    # Static analysis (optional but recommended)
    @testset "Static Analysis (JET.jl)" begin
        JET.test_package(MyPackage; target_defined_modules=true)
    end
    
    # Unit tests
    include("test_types.jl")
    include("test_core.jl")
    include("test_utils.jl")
    
    # Integration tests
    include("test_integration.jl")
    
    # Doctests
    @testset "Doctests" begin
        DocMeta.setdocmeta!(
            MyPackage,
            :DocTestSetup,
            :(using MyPackage);
            recursive=true,
        )
        doctest(MyPackage)
    end
end

8.2 Test File Template

# test/test_core.jl
@testset "Core Functions" begin
    @testset "function_name" begin
        # Test basic functionality
        @test function_name(1, 2) == 3
        
        # Test edge cases
        @test function_name(0, 0) == 0
        @test function_name(-1, 1) == 0
        
        # Test type stability
        @test @inferred function_name(1.0, 2.0) == 3.0
        
        # Test errors
        @test_throws ArgumentError function_name(nothing, 1)
        @test_throws DomainError function_name(-1, -1)
        
        # Test with different types
        @testset "Type: $T" for T in [Int32, Int64, Float32, Float64]
            x, y = T(1), T(2)
            result = function_name(x, y)
            @test result isa T
            @test result == T(3)
        end
    end
    
    @testset "mutating_function!" begin
        # Test mutation
        data = [1, 2, 3]
        result = mutating_function!(data)
        @test result === data  # Same object
        @test data == [2, 4, 6]  # Modified
    end
end

8.3 Coverage Requirements

  • Minimum 80% code coverage required
  • 90%+ coverage recommended
  • All public API functions must have tests
  • Edge cases and error conditions must be tested

8.4 Test Dependencies

# test/Project.toml
[deps]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"

9. Error Handling

9.1 Exception Types

# Define custom exceptions when appropriate
struct MyPackageError <: Exception
    msg::String
end

Base.showerror(io::IO, e::MyPackageError) = print(io, "MyPackageError: ", e.msg)

# Use specific exception types
struct ValidationError <: Exception
    field::Symbol
    value::Any
    reason::String
end

function Base.showerror(io::IO, e::ValidationError)
    print(io, "ValidationError: field '", e.field, "' ")
    print(io, "with value '", e.value, "' - ", e.reason)
end

9.2 Error Handling Patterns

# Use built-in exceptions appropriately
function process(x::Number)
    x >= 0 || throw(DomainError(x, "Input must be non-negative"))
    return sqrt(x)
end

# Argument validation
function configure(; tolerance::Real=1e-8, max_iter::Integer=100)
    tolerance > 0 || throw(ArgumentError("tolerance must be positive, got $tolerance"))
    max_iter > 0 || throw(ArgumentError("max_iter must be positive, got $max_iter"))
    # ...
end

# Use @assert only for internal invariants, not user input
function internal_function(x)
    @assert x !== nothing "Internal error: x should never be nothing here"
    # ...
end

9.3 Result Types (Alternative to Exceptions)

# For recoverable errors, consider Result types
struct Result{T,E}
    value::Union{T,Nothing}
    error::Union{E,Nothing}
end

success(value::T) where {T} = Result{T,Nothing}(value, nothing)
failure(error::E) where {E} = Result{Nothing,E}(nothing, error)

issuccess(r::Result) = r.value !== nothing
isfailure(r::Result) = r.error !== nothing

function safe_parse(str::AbstractString)::Result{Int,String}
    try
        return success(parse(Int, str))
    catch
        return failure("Failed to parse '$str' as Int")
    end
end

9.4 Logging

using Logging

function complex_operation(data)
    @debug "Starting operation" data_size=length(data)
    
    @info "Processing data"
    result = process(data)
    
    if isempty(result)
        @warn "Operation produced empty result"
    end
    
    @debug "Operation complete" result_size=length(result)
    return result
end

10. Performance Guidelines

10.1 Memory Allocation

# Avoid allocations in hot loops
# Bad
function sum_squares_bad(x)
    return sum([xi^2 for xi in x])  # Allocates array
end

# Good
function sum_squares_good(x)
    return sum(xi^2 for xi in x)  # Generator, no allocation
end

# Pre-allocate outputs
function transform!(output, input)
    @assert length(output) == length(input)
    for i in eachindex(input)
        output[i] = process(input[i])
    end
    return output
end

# Use views instead of copies
function process_slice(A, i)
    row = @view A[i, :]  # No copy
    return sum(row)
end

10.2 Type Stability

# Check type stability
using Test
@inferred function_name(args...)

# Avoid containers with abstract element types
# Bad
data = Any[1, 2.0, "three"]

# Good
data = Union{Int,Float64,String}[1, 2.0, "three"]
# Better - use homogeneous types when possible
data = Float64[1.0, 2.0, 3.0]

# Annotate struct fields
struct Container
    data::Vector{Float64}  # Not Vector or Vector{Any}
end

10.3 SIMD and Vectorization

using LoopVectorization

# Enable SIMD with @turbo (LoopVectorization.jl)
function dot_product(x, y)
    s = zero(eltype(x))
    @turbo for i in eachindex(x, y)
        s += x[i] * y[i]
    end
    return s
end

# Use @simd for simple cases
function simple_sum(x)
    s = zero(eltype(x))
    @simd for i in eachindex(x)
        @inbounds s += x[i]
    end
    return s
end

10.4 Benchmarking

# benchmark/benchmarks.jl
using BenchmarkTools
using MyPackage

const SUITE = BenchmarkGroup()

SUITE["core"] = BenchmarkGroup()
SUITE["core"]["function_name"] = @benchmarkable function_name($data) setup=(data=rand(1000))

# Run benchmarks
# julia --project=benchmark benchmark/run_benchmarks.jl

10.5 Profiling

# Profile before optimizing
using Profile

function target_function(n)
    # ... implementation
end

# Profile
@profile target_function(10000)
Profile.print()

# For detailed analysis
using ProfileView  # or PProf
@profview target_function(10000)

11. Dependencies & Compatibility

11.1 Dependency Principles

  • Minimize direct dependencies
  • Prefer packages from the General registry
  • Use package extensions for optional features
  • Pin compat bounds for all dependencies

11.2 Compat Bounds

# Project.toml
[compat]
julia = "1.10"
DataStructures = "0.18"
JSON3 = "1.13"
StaticArrays = "1.5"

Rules for compat bounds:

  • Always specify Julia version
  • Use semver: "1.2" means >= 1.2.0, < 2.0.0
  • Be as permissive as possible while maintaining compatibility
  • Test against minimum supported versions in CI

11.3 Conditional Dependencies

# Use package extensions (Julia 1.9+)
# Project.toml
[weakdeps]
CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"

[extensions]
MyPackageCUDAExt = "CUDA"

# ext/MyPackageCUDAExt.jl
module MyPackageCUDAExt
using MyPackage
using CUDA

# GPU-specific implementations
function MyPackage.process(x::CuArray)
    # CUDA implementation
end

end

12. CI/CD Configuration

12.1 GitHub Actions CI

# .github/workflows/CI.yml
name: CI

on:
  push:
    branches:
      - main
    tags: ['*']
  pull_request:
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}

jobs:
  test:
    name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
    runs-on: ${{ matrix.os }}
    timeout-minutes: 60
    permissions:
      actions: write
      contents: read
    strategy:
      fail-fast: false
      matrix:
        version:
          - '1.10'    # Minimum supported
          - '1'       # Latest stable
          - 'nightly' # Development
        os:
          - ubuntu-latest
          - macos-latest
          - windows-latest
        arch:
          - x64
        exclude:
          - os: macos-latest
            version: 'nightly'
    steps:
      - uses: actions/checkout@v4
      
      - uses: julia-actions/setup-julia@v2
        with:
          version: ${{ matrix.version }}
          arch: ${{ matrix.arch }}
      
      - uses: julia-actions/cache@v2
      
      - uses: julia-actions/julia-buildpkg@v1
      
      - uses: julia-actions/julia-runtest@v1
      
      - uses: julia-actions/julia-processcoverage@v1
        if: matrix.version == '1' && matrix.os == 'ubuntu-latest'
      
      - uses: codecov/codecov-action@v4
        if: matrix.version == '1' && matrix.os == 'ubuntu-latest'
        with:
          files: lcov.info
          token: ${{ secrets.CODECOV_TOKEN }}

  docs:
    name: Documentation
    runs-on: ubuntu-latest
    permissions:
      actions: write
      contents: write
      statuses: write
    steps:
      - uses: actions/checkout@v4
      
      - uses: julia-actions/setup-julia@v2
        with:
          version: '1'
      
      - uses: julia-actions/cache@v2
      
      - name: Install dependencies
        run: julia --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'
      
      - name: Build and deploy
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}
        run: julia --project=docs docs/make.jl

12.2 CompatHelper

# .github/workflows/CompatHelper.yml
name: CompatHelper

on:
  schedule:
    - cron: '0 0 * * *'
  workflow_dispatch:

jobs:
  CompatHelper:
    runs-on: ubuntu-latest
    steps:
      - uses: julia-actions/setup-julia@v2
        with:
          version: '1'
      
      - name: Run CompatHelper
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          COMPATHELPER_PRIV: ${{ secrets.COMPATHELPER_PRIV }}
        run: |
          julia -e '
            using Pkg
            Pkg.add("CompatHelper")
            using CompatHelper
            CompatHelper.main()
          '

12.3 TagBot

# .github/workflows/TagBot.yml
name: TagBot

on:
  issue_comment:
    types: [created]
  workflow_dispatch:
    inputs:
      lookback:
        default: 3

permissions:
  actions: read
  checks: read
  contents: write
  deployments: read
  issues: read
  discussions: read
  packages: read
  pages: read
  pull-requests: read
  repository-projects: read
  security-events: read
  statuses: read

jobs:
  TagBot:
    if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
    runs-on: ubuntu-latest
    steps:
      - uses: JuliaRegistries/TagBot@v1
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          ssh: ${{ secrets.DOCUMENTER_KEY }}

13. Version Control Practices

13.1 Git Ignore

# .gitignore

# Julia
*.jl.cov
*.jl.*.cov
*.jl.mem
Manifest.toml
docs/Manifest.toml
docs/build/
test/Manifest.toml

# Build artifacts
deps/deps.jl
deps/build.log
deps/usr/

# IDE
.idea/
*.swp
*.swo
.vscode/
*.sublime-*

# OS
.DS_Store
Thumbs.db

# Local development
LocalPreferences.toml
.env
*.local.jl

13.2 Branch Strategy

  • main - Stable, release-ready code
  • develop - Integration branch (optional)
  • feature/* - New features
  • fix/* - Bug fixes
  • docs/* - Documentation updates

13.3 Commit Messages

Follow Conventional Commits:

<type>(<scope>): <description>

[optional body]

[optional footer(s)]

Types:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation only
  • style: Formatting, no code change
  • refactor: Code change, no feature/fix
  • perf: Performance improvement
  • test: Adding/updating tests
  • chore: Maintenance tasks
  • ci: CI configuration changes

Examples:

feat(solver): add conjugate gradient method

fix(io): handle empty file input correctly

docs: update installation instructions

refactor!: rename process to transform

BREAKING CHANGE: The process function has been renamed to transform.
All existing code using process() must be updated.

13.4 Pull Request Template

<!-- .github/PULL_REQUEST_TEMPLATE.md -->

## Description

Brief description of changes.

## Type of Change

- [ ] Bug fix (non-breaking change fixing an issue)
- [ ] New feature (non-breaking change adding functionality)
- [ ] Breaking change (fix or feature causing existing functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring

## Checklist

- [ ] My code follows the project style guidelines
- [ ] I have performed a self-review of my code
- [ ] I have commented hard-to-understand areas
- [ ] I have updated the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests proving my fix/feature works
- [ ] New and existing tests pass locally
- [ ] I have updated CHANGELOG.md

## Related Issues

Closes #

## Additional Notes

Any additional information or context.

14. Licensing & Legal

14.1 Recommended Licenses

For open source Julia packages:

  • MIT License - Permissive, widely used in Julia ecosystem
  • BSD 3-Clause - Similar to MIT with attribution clause
  • Apache 2.0 - Includes patent grant
  • GPL v3 - Copyleft, requires derivative works to be open source

14.2 License File Template (MIT)

MIT License

Copyright (c) 2025 Author Name

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

14.3 CITATION.cff

# CITATION.cff
cff-version: 1.2.0
message: "If you use this software, please cite it as below."
type: software
title: "MyPackage.jl"
version: 0.1.0
date-released: 2025-01-01
url: "https://github.com/username/MyPackage.jl"
repository-code: "https://github.com/username/MyPackage.jl"
license: MIT
authors:
  - family-names: "Lastname"
    given-names: "Firstname"
    email: "email@example.com"
    orcid: "https://orcid.org/0000-0000-0000-0000"
keywords:
  - julia
  - scientific-computing
abstract: "Brief description of the package."

15. Release Process

15.1 Version Numbering

Follow Semantic Versioning (SemVer):

  • MAJOR.MINOR.PATCH (e.g., 1.2.3)
  • MAJOR: Breaking changes
  • MINOR: New features, backward compatible
  • PATCH: Bug fixes, backward compatible

Pre-release versions:

  • 0.x.y: Initial development, API may change
  • 1.0.0-alpha.1: Alpha release
  • 1.0.0-beta.1: Beta release
  • 1.0.0-rc.1: Release candidate

15.2 CHANGELOG Format

# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- New feature X

### Changed
- Modified behavior Y

### Deprecated
- Function Z is deprecated, use W instead

### Removed
- Removed obsolete function

### Fixed
- Bug fix description

### Security
- Security fix description

## [0.2.0] - 2025-06-15

### Added
- Feature A (#123)
- Feature B (#124)

### Fixed
- Critical bug in function C (#125)

## [0.1.0] - 2025-01-01

### Added
- Initial release
- Core functionality
- Basic documentation

[Unreleased]: https://github.com/username/MyPackage.jl/compare/v0.2.0...HEAD
[0.2.0]: https://github.com/username/MyPackage.jl/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/username/MyPackage.jl/releases/tag/v0.1.0

15.3 Release Checklist

  1. Pre-release

    • All tests pass on CI
    • Documentation is up to date
    • CHANGELOG.md updated
    • Version bumped in Project.toml
    • Breaking changes documented
  2. Release

    • Create release commit
    • Register with General registry via JuliaRegistrator
    • Wait for registry PR to merge
    • TagBot creates GitHub release
  3. Post-release

    • Verify package installs correctly
    • Announce release (Discourse, social media)
    • Update any dependent packages

15.4 Registration Command

# In GitHub PR or Issue comment:
@JuliaRegistrator register

# For a specific branch:
@JuliaRegistrator register branch=release-1.0

# For a subdirectory package:
@JuliaRegistrator register subdir=packages/MyPackage

16. Community Standards

16.1 Code of Conduct

Adopt the Julia Community Standards or Contributor Covenant:

<!-- CODE_OF_CONDUCT.md -->

# Code of Conduct

## Our Pledge

We pledge to make participation in our project a harassment-free experience
for everyone, regardless of age, body size, disability, ethnicity, gender
identity, level of experience, nationality, personal appearance, race,
religion, or sexual orientation.

## Our Standards

Examples of behavior that contributes to a positive environment:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior:

* Trolling, insulting/derogatory comments, and personal attacks
* Public or private harassment
* Publishing others' private information without permission
* Other conduct which could reasonably be considered inappropriate

## Enforcement

Project maintainers are responsible for clarifying standards of acceptable
behavior and will take appropriate action in response to unacceptable behavior.

## Attribution

This Code of Conduct is adapted from the Contributor Covenant, version 2.0.

16.2 Contributing Guide

<!-- CONTRIBUTING.md -->

# Contributing to MyPackage.jl

Thank you for your interest in contributing!

## Getting Started

1. Fork the repository
2. Clone your fork: `git clone https://github.com/your-username/MyPackage.jl`
3. Create a branch: `git checkout -b feature/your-feature`
4. Install development dependencies:
   ```julia
   using Pkg
   Pkg.develop(path=".")
   Pkg.instantiate()

Development Workflow

Running Tests

using Pkg
Pkg.test("MyPackage")

Code Formatting

using JuliaFormatter
format(".")

Building Documentation

julia --project=docs docs/make.jl

Pull Request Process

  1. Update documentation for any changed functionality
  2. Add tests for new features
  3. Update CHANGELOG.md
  4. Ensure all CI checks pass
  5. Request review from maintainers

Style Guidelines

This project follows the conventions outlined in our Julia Constitution. Please ensure your code:

  • Uses JuliaFormatter with our configuration
  • Has complete docstrings for public functions
  • Includes appropriate tests
  • Is type-stable where performance matters

Questions?

Open an issue or reach out on the Julia Discourse/Slack/Zulip.


### 16.3 Issue Templates

```markdown
<!-- .github/ISSUE_TEMPLATE/bug_report.md -->
---
name: Bug Report
about: Report a bug to help us improve
title: '[BUG] '
labels: 'bug'
---

## Bug Description
A clear description of the bug.

## To Reproduce
```julia
using MyPackage
# Code that reproduces the bug

Expected Behavior

What you expected to happen.

Environment

  • Julia version:
  • MyPackage version:
  • OS:

Additional Context

Any other relevant information.


```markdown
<!-- .github/ISSUE_TEMPLATE/feature_request.md -->
---
name: Feature Request
about: Suggest a new feature
title: '[FEATURE] '
labels: 'enhancement'
---

## Problem Statement
What problem does this feature solve?

## Proposed Solution
Your idea for the feature.

## Alternatives Considered
Other approaches you've thought about.

## Additional Context
Any other relevant information.

Appendix A: Quick Reference

Essential Commands

# Create new package
using PkgTemplates
t = Template(;
    user="username",
    dir="~/code",
    plugins=[
        Git(; manifest=true, ssh=true),
        GitHubActions(),
        Codecov(),
        Documenter{GitHubActions}(),
        Formatter(; style="sciml"),
    ],
)
t("MyPackage")

# Develop package locally
using Pkg
Pkg.develop(path="path/to/MyPackage")

# Run tests
Pkg.test("MyPackage")

# Build docs locally
julia --project=docs -e 'using Pkg; Pkg.instantiate()'
julia --project=docs docs/make.jl

# Format code
using JuliaFormatter
format("src")

# Check type stability
@code_warntype function_name(args...)

# Profile performance
using Profile
@profile function_name(args...)
Profile.print()

Useful Packages for Development

Package Purpose
PkgTemplates.jl Package scaffolding
JuliaFormatter.jl Code formatting
Documenter.jl Documentation generation
Aqua.jl Code quality checks
JET.jl Static analysis
BenchmarkTools.jl Performance benchmarking
ProfileView.jl Profiling visualization
Revise.jl Live code reloading
TestItemRunner.jl VS Code test integration

Appendix B: Checklist for New Packages

Initial Setup

  • Generate package with PkgTemplates
  • Configure .JuliaFormatter.toml
  • Set up GitHub repository
  • Configure branch protection rules
  • Add CODEOWNERS file

Code Quality

  • Aqua.jl tests pass
  • JuliaFormatter applied
  • Docstrings for all exports
  • Type annotations on struct fields
  • Type-stable critical paths

Testing

  • 80%+ code coverage
  • Tests for all public API
  • Edge cases covered
  • Doctests included

Documentation

  • README with badges
  • Getting started guide
  • API reference
  • Examples/tutorials
  • CHANGELOG.md

CI/CD

  • GitHub Actions CI
  • Multi-version testing
  • Documentation deployment
  • CompatHelper
  • TagBot

Community

  • LICENSE file
  • CONTRIBUTING.md
  • CODE_OF_CONDUCT.md
  • Issue templates
  • PR template

This constitution is a living document. Contributions and suggestions for improvement are welcome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment