Help us improve
Share bugs, ideas, or general feedback.
From ocaml-dev
Provides OCaml project setup standards for dune files, .mli interfaces, ocamlformat, logging, licenses, CI, and structure. Use for new libraries, opam releases, or reviews.
npx claudepluginhub avsm/ocaml-claude-marketplace --plugin ocaml-devHow this skill is triggered — by the user, by Claude, or both
Slash command
/ocaml-dev:project-setupThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Every OCaml project needs:
Guides OCaml development with best practices for writing code, designing .mli interfaces, result-based error handling, dune builds, and libraries like eio, fmt, logs, cmdliner, yojson, cohttp-eio.
Guides OCaml development with opam for packages, dune for builds/tests, merlin for editor support, ocamlformat for formatting. Includes project setup workflows and examples.
Bootstraps new projects or improves existing ones with best practices for structure, git, documentation, testing, code quality, dependencies, dev workflow, and CI/CD.
Share bugs, ideas, or general feedback.
Every OCaml project needs:
| File | Purpose |
|---|---|
dune-project | Build configuration, opam generation |
dune (root) | Top-level build rules |
.ocamlformat | Code formatting (required) |
.gitignore | VCS ignores |
LICENSE.md | License file |
README.md | Project documentation |
| CI config | GitHub Actions / GitLab CI / Tangled |
Every library module must have an .mli file for:
(* lib/user.mli *)
(** User management.
This module provides types and functions for user operations. *)
type t
(** A user. *)
val create : name:string -> email:string -> t
(** [create ~name ~email] creates a new user. *)
val name : t -> string
(** [name u] is the user's name. *)
val pp : t Fmt.t
(** [pp] is a pretty-printer for users. *)
Documentation style:
[name args] is/does ...[name] is ...For modules with a central type t:
type t
val v : ... -> t (* pure constructor *)
val create : ... -> (t, Error.t) result (* constructor with I/O *)
val pp : t Fmt.t (* pretty-printer - required *)
val equal : t -> t -> bool (* equality *)
val compare : t -> t -> int (* comparison *)
val of_json : Yojson.Safe.t -> (t, string) result
val to_json : t -> Yojson.Safe.t
Required: .ocamlformat in project root.
version = 0.28.1
Run dune fmt before every commit.
Each module using logging should declare a source:
let log_src = Logs.Src.create "project.module"
module Log = (val Logs.src_log log_src : Logs.LOG)
Log levels:
Log.app - Always shown (startup)Log.err - Critical errorsLog.warn - Potential issuesLog.info - InformationalLog.debug - Verbose debuggingRead from ~/.claude/ocaml-config.json:
{
"author": { "name": "Name", "email": "email@example.com" },
"license": "ISC",
"ci_platform": "github",
"git_hosting": { "type": "github", "org": "username" },
"ocaml_version": "5.2.0"
}
Every source file starts with license header:
(*---------------------------------------------------------------------------
Copyright (c) {{YEAR}} {{AUTHOR}}. All rights reserved.
SPDX-License-Identifier: ISC
---------------------------------------------------------------------------*)
project/
├── dune-project
├── dune
├── .ocamlformat
├── .gitignore
├── LICENSE.md
├── README.md
├── lib/
│ ├── dune
│ ├── foo.ml
│ └── foo.mli # Required for every .ml
├── bin/
│ ├── dune
│ └── main.ml
├── test/
│ ├── dune
│ ├── test.ml
│ └── test_foo.ml
├── .github/workflows/ # GitHub Actions
├── .gitlab-ci.yml # GitLab CI
└── .tangled/workflows/ # Tangled CI
(lang dune 3.21)
(name project_name)
(source (tangled handle/project_name)) ; or (github user/repo)
(license ISC)
(authors "Name <email>")
(generate_opam_files true)
(license ISC)
(authors "Name <email@example.com>")
(maintainers "Name <email@example.com>")
(source (tangled user.domain/project_name))
(package
(name project_name)
(synopsis "Short description")
(description "Longer description")
(depends
(ocaml (>= 5.2))
(alcotest (and :with-test (>= 1.7.0)))))
Source options:
(source (tangled handle/repo)) - Tangled hosting (default for monopam)(source (github user/repo)) - GitHub hosting(source (gitlab user/repo)) - GitLab hostingNote: Don't add (version ...) - added at release time.
For projects hosted on tangled.org, use the succinct source stanza:
(source (tangled user.domain/project-name))
Examples:
(source (tangled anil.recoil.org/ocaml-brotli))(source (tangled user.example.org/my-library))For projects hosted on tangled.org, create .tangled/workflows/build.yml:
when:
- event: ["push", "pull_request"]
branch: ["main"]
engine: nixery
dependencies:
nixpkgs:
- shell
- stdenv
- findutils
- binutils
- libunwind
- ncurses
- opam
- git
- gawk
- gnupatch
- gnum4
- gnumake
- gnutar
- gnused
- gnugrep
- diffutils
- gzip
- bzip2
- gcc
- ocaml
- pkg-config
steps:
- name: opam
command: |
opam init --disable-sandboxing -a -y
- name: repo
command: |
opam repo add aoah https://tangled.org/anil.recoil.org/aoah-opam-repo.git
- name: deps
command: |
opam install . --confirm-level=unsafe-yes --deps-only
- name: build
command: |
opam exec -- dune build
- name: test
command: |
opam install . --confirm-level=unsafe-yes --deps-only --with-test
opam exec -- dune runtest --verbose
| Field | Description |
|---|---|
when | Trigger conditions: event (push/pull_request) and branch |
engine | Build engine, use nixery for Nix-based builds |
dependencies.nixpkgs | List of Nix packages to include |
environment | Global or per-step environment variables |
steps | Build steps with name and command |
Per-step environment variables:
steps:
- name: test
environment:
MY_VAR: value
command: |
echo $MY_VAR
See templates/ directory for:
dune-project.templatedune-root.templateci-github.ymlci-gitlab.ymlci-tangled.ymlgitignoreocamlformatLICENSE-ISC.mdLICENSE-MIT.mdREADME.template.md