|
| 1 | +# Team-Level Claude Code Instructions |
| 2 | + |
| 3 | +This file contains development guidelines that apply across all projects in this directory. |
| 4 | + |
| 5 | +## OCaml Type-Driven Development Workflow |
| 6 | + |
| 7 | +**CRITICAL**: When working with OCaml modules that have signatures (`.mli` files), follow this workflow: |
| 8 | + |
| 9 | +### 1. Signature First, Implementation Second |
| 10 | + |
| 11 | +When adding new functions to a module with a `.mli`, ALWAYS add the signature to the `.mli` file FIRST: |
| 12 | + |
| 13 | +- Define the function signature in the `.mli` file |
| 14 | +- Use stub implementations (`failwith "TODO: implement function_name"`) in the `.ml` file to make everything compile |
| 15 | +- Only implement the real functionality after the signature is correct everywhere |
| 16 | +- **Never** write implementations in `.ml` without first defining the signature in `.mli` |
| 17 | + |
| 18 | +**Why:** The signature is the contract. Define the contract first, then fulfill it. This prevents type inference from guessing the wrong thing and makes refactoring much easier. |
| 19 | + |
| 20 | +### 2. Getting Type Information |
| 21 | + |
| 22 | +**FIRST: Use the ocaml-mcp tools to query type information** |
| 23 | + |
| 24 | +When you need to understand what type something has: |
| 25 | + |
| 26 | +- **DO:** Use `mcp__ocaml-mcp__ocaml_type_at_pos` to get the type at a specific location |
| 27 | +- This works after running `dune build` to ensure artifacts are up-to-date |
| 28 | +- Use `mcp__ocaml-mcp__ocaml_find_definition` to jump to where symbols are defined |
| 29 | +- Use `mcp__ocaml-mcp__dune_build_status` to check for compilation errors |
| 30 | + |
| 31 | +**ONLY WHEN NEEDED: Add type annotations to localize errors** |
| 32 | + |
| 33 | +When you encounter type errors you don't understand or need to narrow down where the issue is: |
| 34 | + |
| 35 | +- **DO:** Add type annotations at the call site reflecting what you EXPECT the types to be |
| 36 | +- Use abstract types from module signatures (not their expansions) |
| 37 | +- Let the compiler show you exactly where your expectations differ from reality |
| 38 | +- Read the error message carefully - it tells you precisely what's wrong |
| 39 | + |
| 40 | +- **DON'T:** Try to manually reason through complex type unification |
| 41 | +- Don't guess at fixes without understanding the actual type mismatch |
| 42 | +- Don't remove type annotations when they cause errors (they're revealing the problem!) |
| 43 | + |
| 44 | +**Why:** The ocaml-mcp server gives you immediate access to type information from compiled artifacts. Type annotations are still useful as "checkpoints" to localize where type mismatches occur, but you should query the MCP server first to understand what types you're actually working with. |
| 45 | + |
| 46 | +### 3. Iterate on Types with Stubs |
| 47 | + |
| 48 | +The recommended workflow when designing new functions: |
| 49 | + |
| 50 | +```ocaml |
| 51 | +(* Step 1: Add to .mli with your desired signature *) |
| 52 | +val my_function : input -> output |
| 53 | +
|
| 54 | +(* Step 2: Add stub to .ml *) |
| 55 | +let my_function input = failwith "TODO: implement my_function" |
| 56 | +
|
| 57 | +(* Step 3: Use it at call sites with type annotations *) |
| 58 | +let result : expected_type = my_function my_input |
| 59 | +
|
| 60 | +(* Step 4: If you get type errors, revise the signature in .mli *) |
| 61 | +val my_function : better_input -> better_output |
| 62 | +
|
| 63 | +(* Step 5: Once types are right everywhere, implement for real *) |
| 64 | +let my_function input = (* actual implementation *) |
| 65 | +``` |
| 66 | + |
| 67 | +**Why:** This workflow separates concerns - first get the API right, then implement it. It's much easier to change a `failwith` stub than to refactor a full implementation. |
| 68 | + |
| 69 | +### 4. Query Types First, Annotate When Localizing Errors |
| 70 | + |
| 71 | +**Preferred approach - Query the MCP server:** |
| 72 | +```ocaml |
| 73 | +let process_deltas deltas = |
| 74 | + (* First, use mcp__ocaml-mcp__ocaml_type_at_pos to understand what types you have *) |
| 75 | + let pattern = Map.Pattern.add __ ignore in |
| 76 | + match_list pattern deltas () |
| 77 | +``` |
| 78 | + |
| 79 | +**When debugging type errors - Add annotations to localize:** |
| 80 | +```ocaml |
| 81 | +let process_deltas deltas = |
| 82 | + (* Add annotation to narrow down where the type error occurs *) |
| 83 | + let pattern : (Delta.t, unit, result) Pattern.t = |
| 84 | + Map.Pattern.add __ ignore |
| 85 | + in |
| 86 | + (* If this causes an error, you know the problem is in this binding *) |
| 87 | + match_list pattern deltas () |
| 88 | +``` |
| 89 | + |
| 90 | +**Why:** Use the MCP server to understand types interactively without cluttering code. Add annotations strategically when you need to pinpoint exactly where a type mismatch occurs. |
| 91 | + |
| 92 | +## OCaml Code Style Guidelines |
| 93 | + |
| 94 | +### Sequencing Expressions |
| 95 | +- **Always use** `let () = a in b` instead of `a ; b` for sequencing |
| 96 | +- Makes intent explicit and avoids type inference issues |
| 97 | + |
| 98 | +### Custom Let Operators |
| 99 | +- Prefer custom let operators for applicative/monadic patterns |
| 100 | +- Examples: |
| 101 | + - `let*` for monads |
| 102 | + - `let+` for functors/applicatives |
| 103 | + - `let*!` for `('a, 'err) Result.t Monad.t` (e.g., `Result.t Lwt.t`) |
| 104 | +- Makes control flow clear and reduces nesting |
| 105 | + |
| 106 | +### Module Documentation |
| 107 | +- **Always add `.mli` files** for modules you create |
| 108 | +- Include docstrings for all public functions using `(** *)` style comments |
| 109 | +- Document parameters, return values, and any side effects |
| 110 | + |
| 111 | +### Data Structure Design |
| 112 | +- **If a tuple has 3+ elements, use a record type instead** |
| 113 | +- This applies to both standalone values and variant constructors |
| 114 | +- Use inline record syntax for variants: |
| 115 | + ```ocaml |
| 116 | + (* Bad - positional arguments are error-prone *) |
| 117 | + | Constructor of string * int * bool * float |
| 118 | +
|
| 119 | + (* Good - named fields are self-documenting *) |
| 120 | + | Constructor of { |
| 121 | + name : string; |
| 122 | + count : int; |
| 123 | + enabled : bool; |
| 124 | + threshold : float; |
| 125 | + } |
| 126 | + ``` |
| 127 | + |
| 128 | +**Why:** Named fields make code self-documenting, easier to refactor (can add fields without breaking all call sites), and prevent subtle bugs from swapping arguments. |
| 129 | + |
| 130 | +### Error Handling: Never Use `_exn` Functions |
| 131 | + |
| 132 | +**DO NOT use `_exn` functions** (like `List.hd_exn`, `Map.of_sequence_exn`, `Option.value_exn`). |
| 133 | + |
| 134 | +- Use `Result.t` or `Option.t` to propagate errors explicitly |
| 135 | +- Use `_reduce` variants with error logging when handling duplicates: |
| 136 | + ```ocaml |
| 137 | + Map.of_sequence_reduce (module String) ~f:(fun first second -> |
| 138 | + Logs.err (fun m -> m "duplicate: %s" key); |
| 139 | + first) |
| 140 | + ``` |
| 141 | +- **Only exception**: When the code structure guarantees the precondition holds (rare) |
| 142 | + |
| 143 | +**Why:** `_exn` functions crash at runtime. Explicit error handling makes failures visible in types. |
| 144 | + |
| 145 | +## General Principles |
| 146 | + |
| 147 | +### Work with Module Signatures |
| 148 | + |
| 149 | +- When working with modules, always refer to the `.mli` file first |
| 150 | +- The abstract types defined in the interface are what you should use in annotations |
| 151 | +- Don't try to work with concrete implementation types unless absolutely necessary |
| 152 | + |
| 153 | +### Let the Type System Help You |
| 154 | + |
| 155 | +- OCaml's type system is one of its greatest strengths |
| 156 | +- Type errors are your friend - they prevent runtime bugs |
| 157 | +- The more precise your types, the more the compiler can help you |
| 158 | +- When refactoring, let type errors guide you to all the places that need changes |
| 159 | + |
| 160 | +## Build Process |
| 161 | + |
| 162 | +### Using Dune |
| 163 | + |
| 164 | +- **ALWAYS** use `dune build` to build the project |
| 165 | +- **NEVER** run `dune clean` - rely on the incremental build process |
| 166 | +- **NEVER** try to remove `_build/.lock` manually |
| 167 | +- **NEVER** run `dune runtest` - use `dune build @runtest` instead since a build daemon is running and holds the lock |
| 168 | +- The build system has a running watch process that handles incremental compilation |
| 169 | +- Just call `dune build` and let it handle everything |
| 170 | +- After editing code, run `dune build @fmt || dune promote` to format it correctly and pass CI |
| 171 | + |
| 172 | +**Why:** The project uses a persistent build watcher that maintains incremental build state. Cleaning or removing locks disrupts this process and wastes time. |
| 173 | + |
| 174 | +## Summary |
| 175 | + |
| 176 | +**The golden rule: Signature → Stub → Query Types (via MCP) → Implement** |
| 177 | + |
| 178 | +This approach will save time, reduce bugs, and make code easier to maintain. Use `mcp__ocaml-mcp__ocaml_type_at_pos` to understand types interactively without cluttering code. Add type annotations strategically only when you need to localize where type errors occur. |
| 179 | + |
| 180 | +**For setting up ocaml-mcp**, see `~/.claude/OCAML_MCP_SETUP.md` for detailed installation and configuration instructions. |
0 commit comments