commits
Sweep through 23 more packages flagged by [monopam lint] for
test-stanza references not declared in opam. Each verified by
[dune build] + [dune runtest] before moving on.
- irmin add (astring :with-test)
- ocaml-atproto-oauth add (eio_main :with-test) (nox-crypto-rng :with-test)
- ocaml-auth add (alcotest :with-test) (eio :with-test) (eio_main :with-test)
- ocaml-cam add (odm :with-test)
- ocaml-cbor add (alcotest :with-test) (ohex :with-test)
- ocaml-cfdp add (nox-csv :with-test)
- ocaml-claude add (vlog :with-test)
- ocaml-collision add (alcotest :with-test) (odm :with-test) (ptime :with-test)
- ocaml-cookie add (re :with-test)
- ocaml-cop1 add (nox-csv :with-test)
- ocaml-crc add (nox-csv :with-test) (nox-memtrace :with-test)
- ocaml-dns-eio add (mdx :with-test); also fix a misplaced
paren in the depends list.
- ocaml-gauth add (nox-crypto-rng :with-test)
- ocaml-http add (alcotest :with-test) (eio_main :with-test) (nox-csv :with-test)
- ocaml-ltp add (nox-csv :with-test)
- ocaml-matter add (ohex :with-test) (ptime :with-test)
- ocaml-oauth add (eio_main :with-test) (nox-crypto-ec :with-test)
- ocaml-ocm add (alcotest :with-test) (nox-csv :with-test)
- ocaml-oem add (alcotest :with-test) (nox-csv :with-test)
- ocaml-opm add (alcotest :with-test) (nox-csv :with-test)
- ocaml-pbkdf2 add (ohex :with-test)
- ocaml-requests add (astring :with-test) (nox-csv :with-test)
- ocaml-retry add (re :with-test)
Per-package removal of opam runtime [depends] entries that
[monopam lint] flagged as unused -- packages declared as runtime deps
but referenced by no library [(libraries ...)] stanza in any of the
package's main lib/exec dune files (and not pulled in transitively
via META).
Verified each removal by running [dune build] (and [dune test] for
packages with a test suite) before moving on. No code changes -- only
dune-project [(depends ...)] lists.
Packages touched:
- ca-certs drop nox-crypto
- dupfind drop bos
- merlint drop nox-json (still pulled in via nox-opam.bytesrw)
- monopam drop bytesrw, requests
- monopam-info drop nox-loc, nox-sexp
- ocaml-agent drop nox-tty
- ocaml-atproto-oauth drop did-plc, did-web
- ocaml-auth drop nox-json
- ocaml-cam drop ptime
- ocaml-claude drop nox-loc
- ocaml-cop1 drop logs
- ocaml-did drop bytesrw
- ocaml-ewah drop bytesrw
- ocaml-freebox drop ipaddr
- ocaml-gauth drop base64, ptime
- ocaml-gdocs drop oauth
- ocaml-gsheets drop oauth
- ocaml-gslides drop oauth
- ocaml-hap drop nox-crypto-rng
- ocaml-jailhouse drop fmt
Generated [.opam] files updated by dune accordingly.
Remaining unused warnings ([ocaml-jwt], [ocaml-meross], [ocaml-oci],
[ocaml-pid1], [ocaml-publicsuffix], [ocaml-punycode], [ocaml-pus],
[ocaml-qemu], [ocaml-rego], [ocaml-requests], [ocaml-sbom],
[ocaml-scc], [ocaml-sdls], [ocaml-sigstore], [ocaml-sle],
[ocaml-spacedata], [ocaml-stix], [ocaml-vz], [prune], plus the
[ocaml-ccsds] meta-package and a handful of others) will follow in a
later pass once each is verified against its tests; this commit
covers only the ones I built and tested in this session.
The three usage examples opened with [let () = Eio_main.run ...] which
mdx happily executed at test time, spawning the Claude Code CLI and
hanging dune test for 90s. Wrapping each in [let run () = ...] keeps
the examples type-checked against the real API without invoking the
Eio mainloop, restoring sub-second runtime.
While here, switch [Printf.printf] / [print_string] to [Fmt.pr] to
match the monorepo convention.
dune fmt cleanup of code blocks now checked by mdx.
ocaml-claude/examples/ has '(library (name json_utils))' without a
public_name — a workspace-private library. The opam regen pre-existing
the local-private filter pulled it into ocaml-claude/dune-project's
(depends ...) and the workspace-root dune-project. From there 'json_utils'
shipped in ocaml-claude/claude.opam (and indirectly in root.opam) as if
it were a real opam package, breaking opam install of the package.
Drop it from both dune-project files and let dune regenerate the .opam
files clean. The lint no longer re-introduces it because of 061e856ee.
Run mdx on lib/control.mli so the two {[ ... ]} odoc blocks now
type-check. Used `open Claude` per the in-package convention,
qualified the error_detail call to its real path
(`Control.Response.error_detail`), and wrapped the
server_info-printing snippet in `let report client = ...` so the
example takes the client as input rather than referencing a free
binding. Switched Printf to Fmt.pr "%s@." for consistency.
The deeply prefixed `Claude.Module.X` style across every line was
hard to read at a glance, especially in the multi-block client.mli
and claude.mli files. Switched to a single `open Claude` at the top
of each {[ ... ]} block and short call sites underneath:
open Claude
let cfg =
Options.default
|> Options.with_model `Sonnet_4_5
|> Options.with_permission_mode Permissions.Mode.Accept_edits
Touches all six claude .mli files I'd added or amended for mdx
coverage (claude.mli, client.mli, options.mli, handler.mli, hooks.mli,
tool.mli, mcp_server.mli). Wired options.mli into the (mdx ...) stanza
since the new examples now compile.
Run mdx on lib/tool.mli so the two {[ ... ]} odoc blocks now
type-check.
The Basic Usage block was constructing the tool's input schema by
hand as raw `[`O ...]` JSON arrays. Replaced with the typed
`Claude.Tool.schema_object` / `schema_string` / `text_result`
helpers exposed by this same module -- the same shape, but the
example now demonstrates the helpers a user should actually call.
Dropped the "Tool Response Format" placeholder block (raw JSON
illustration) and inlined the explanation as prose. The
schema_object block under val schema_object now wraps in
`let schema = ...` so the binding is consumable.
Run mdx on lib/handler.mli so the four {[ ... ]} odoc blocks now
type-check.
Replaced placeholder-comment bodies (`(* required *)`,
`(* must implement *)`) with real bodies that show what each
handler method receives and prints. The `on_init` example threads
through `Option.value` since `Response.Init.session_id` returns
`string option`. The `dispatch` and `dispatch_all` blocks are now
named functions (`print_text response`, `drain_all client handler`)
so the example doesn't depend on free `client` / `response` /
`handler` / `responses` bindings.
Switched all printers from Printf to Fmt.pr "%a@." and qualified
references to `Claude.Response.X` / `Claude.Content_block.X` so the
toploop resolves them through the `claude` package.
Trimmed the second prose block describing the abstract class --
the inline `(* required *)` placeholder snippet won't compile, and
the surrounding text already explains the type-error guarantee.
Run mdx on lib/hooks.mli so the two {[ ... ]} odoc blocks now
type-check.
The first block referenced free `input` of unknown record type and
unqualified `Hooks.X` / `Tool_input.X`; annotated `(input :
Claude.Hooks.Pre_tool_use.input)` so field access resolves and
qualified all module paths to `Claude.Hooks.X` / `Claude.Tool_input.X`.
The second block (configure-hooks builder pattern) was a chained
expression with free `bash_handler` / `post_handler`; wrapped in
`let configure ~bash_handler ~post_handler = ...` so the example
takes the handlers as parameters.
Replaced the `String.contains` substring check with a length+sub
prefix check that reads as plausible production code.
The top-level claude.mli had eight {[ ... ]} blocks doing a tour of
every submodule -- Handler, sequence iteration, Tool Permissions,
Permission Callbacks, Hooks, Error handling, Logging, MCP. Each was
its own deeply-nested workflow that did not copy-paste cleanly.
Trimmed to one Quick Start showing the canonical client-spawn +
handler dispatch flow, and replaced each detail tour with a
prose pointer (\"see {!Handler} for response handling, ...\"). The
focused per-module .mli's (handler.mli, hooks.mli, mcp_server.mli,
etc.) still document each piece individually.
Run mdx on lib/client.mli so the six {[ ... ]} odoc blocks now
type-check.
Several drift fixes against the current API:
- `receive_all` returns `Claude.Response.t list`, not
`Claude.Message.t list`; the basic example matches against
Response.Text and uses Response.Text.content.
- `Claude.Message.Assistant.text` doesn't exist; the only call I
needed was `combined_text`.
- `Claude.Client.set_model` and `Claude.Options.with_model` both
take `Claude.Model.t`, not string -- went through `Model.of_string`.
- `Claude.Server_info` is the right path (the previous text used
`Claude.Control.Server_info`).
Each example wrapped in a named function (`with_handler`,
`with_adaptive_permissions`, `with_model_switch`, `print_server_info`,
`run`) so the spawn-Claude-CLI side effect doesn't fire at mdx test
time, and dropped trailing `ignore (...) : Response.t list` lines in
favour of `let messages = ... in Fmt.pr "%d events@." (List.length
messages)` so the example shows what the receive_all output looks
like.
Run mdx on lib/mcp_server.mli so the {[ ... ]} odoc block now
type-checks against the public Claude module path. Qualified Tool /
Tool_input / Mcp_server / Options uses to `Claude.X` so the toploop
resolves them through the `claude` package.
Remove libraries declared in '(libraries ...)' clauses but unreferenced
by any module in the same source tree, as flagged by 'monopam lint'
after the new Dead_lib detection landed. Touches 131 dune files across
~80 packages.
A few stanzas needed a positive correction instead of a pure removal:
- ocaml-git/bin/diag: depended on eio_main + bytesrw-eio for an
Eio_posix.run call site; the umbrella was overkill, switch to the
precise eio_posix package.
- ocaml-scaleway/lib, ocaml-s3/lib: scaleway.mli / s3.mli reference
Eio_unix.Stdenv.base; eio.unix is required and was missing.
- merlint/lib: pulled bytesrw + nox-opam.bytesrw to surface
Opam_bytesrw, used by rule e915 and lint helpers.
Stanzas where Dead_lib was a false positive (transitive dep needed
for module visibility, virtual-library impls) are left untouched —
e.g. helix.jx.jsoo for ocaml-globe/demo retains its (libraries ...)
entry because it provides the impl of the helix.jx virtual lib.
The Basic-query and streaming examples referenced the older
Message.Assistant variant. The current type is Response.Text wrapping a
Response.Text.t value with .content. Rewrite the iter clauses against
the new API and 'open Claude' so module references stay short.
Also extend the (mdx (libraries ...)) stanza with eio.unix and fmt so
the examples compile in the test sandbox.
Several packages had alcobar/alcotest/mdx/bytesrw/etc. used in
test/ or fuzz/ but undeclared in dune-project, leaving the opam
metadata silently incomplete. Sync the dune-project depends and
regenerate the opam files.
The READMEs all share the standard install/overlay snippet, but the
sh blocks lacked the "<!-- $MDX skip -->" directive. `dune test`
would shell out to `opam install` against the live switch, which
either prompts interactively or fails with a package conflict —
either way diffing as a test failure.
Bulk-add skip directives in front of every install/overlay block.
Also collapse the doubled "non-deterministic + skip" stack on three
READMEs (memtrace, ocaml-dpop, ocaml-pid1, ocaml-yaml, merlint) where
`skip` already implies the runtime is bypassed.
Extends the nox- prefix to the remaining encoding/codec packages —
none clash with opam-repository today, but the rule "blacksun forks
get nox-" applies the same way regardless of conflict status.
Renamed: json, xml, meta, opam, protobuf -> nox-*
Renames 35 packages to make blacksun forks distinguishable from their
opam-repository upstreams. Module names (Git.x, Tls.x, ...) stay bare;
opam package names and dune (public_name) findlib references move to
nox-X. After this commit, zero local package names overlap with
opam-repository.
Renamed:
- nox-git, nox-irmin
- nox-crypto, nox-crypto-pk, nox-crypto-rng, nox-crypto-ec
- nox-tls, nox-tls-eio, nox-tar, nox-tar-eio, nox-tty, nox-tty-eio
- nox-arp, nox-ca-certs, nox-cbor, nox-cookie, nox-crc, nox-csv
- nox-gpt, nox-hkdf, nox-http, nox-jwt, nox-kdf, nox-loc
- nox-memtrace, nox-pds, nox-sexp, nox-slack, nox-toml
- nox-websocket, nox-x509, nox-xdge, nox-yaml
Also drops orphan tar-mirage and tar-unix opam templates that had no
matching package stanza.
Pure formatting changes from `dune fmt`: doc comment placement moves
from above the binding to below it for `type`s, multi-line `match`
expressions collapse onto one line where they fit, and infix operator
applications pick up spaces (`Soup.($?)` -> `Soup.( $? )`). No
semantic changes.
[let open Json.Codec in; map ~kind ~dec:of_string ~enc:to_string
string] made OCaml mis-resolve [string] against another [Codec.*]
path when [of_string] and [to_string] were both labeled arguments.
Pulling [enc = to_string] into a local binding before the [open]
pins the inference without any label-order sensitivity, matches
the other encodings helpers in the repo, and reads a touch better.
Per the ocaml-encodings convention, encoding never has a meaningful
runtime failure mode: the encoder walks the GADT and invokes
user-supplied [enc] callbacks. The only failure cases are (a) a codec
built around [Codec.ignore] (programmer error), or (b) a user-
supplied [enc] that raises (user's own bug). Neither is something a
caller should recover from, so the [result] variant was dead API
surface and every caller I've seen either [get_ok]-ed it or fell
back to [Json.Null] on error.
Drop [Codec.encode] / [Json.encode] result-returning, rename the
exception-raising [encode_exn] to just [encode]. [decode] keeps both
forms since malformed JSON is a legitimate runtime condition.
Downstream sweep: 28 files across claude / oci / atp / qemu / scitt /
yaml. Most were pattern A ([Error _ -> Json.Null ((), Meta.none)]) or
pattern B (print the error); both collapse to a direct call. Tests
using pattern F ([match Json.encode ... with Ok j -> ... | Error e
-> Alcotest.fail]) collapse to [let j = Json.encode ... in]. A few
helpers in claude/client.ml and the atp shims kept their names as
thin aliases to preserve grep targets.
Object combinators: [Object.mem] -> [Object.member], [Object.opt_mem]
-> [Object.opt_member], [Object.case_mem] -> [Object.case_member]. The
sibling submodules [Object.Mem] / [Object.Mems] become
[Object.Member] / [Object.Members]. RFC 8259 §4 calls these
"name/value pairs, referred to as the members", so mirror the spec
name rather than the shortened [mem].
[Object.finish] -> [Object.seal]. "Seal" reads as "close the map, no
more members added", which is what the operation does.
Value constructors/queries: [Value.mem] (function) -> [Value.member];
[Value.mem_find] -> [Value.member_key]; [Value.mem_names] ->
[Value.member_names]; [Value.mem_keys] -> [Value.member_keys].
[type mem = ...] -> [type member = ...]; [type object'] still points
at [member list].
Downstream (~80 files across slack, sbom, stripe, sigstore, requests,
claude, irmin, freebox) updated via perl-pie. dune build clean,
dune test ocaml-json clean.
Migrate every `to_string` / `to_writer` from the three-variant enum
`?format:format = Minify | Indent | Layout` to two orthogonal knobs:
?indent:int -- omit for compact; pass 2 for pretty (two-space indent).
Inside the function the value is `int option`.
?preserve:bool -- default false; honor per-node Loc.Meta whitespace when
true, with the ?indent path as fallback for new nodes.
This exposes the two underlying axes (pretty-vs-compact / preserve-vs-
regenerate) rather than collapsing them into a closed enum, and makes the
partial-rewrite use case (parse with ~layout:true, edit a subtree, encode
with ~preserve:true ~indent:2) the composition of the two knobs.
Drop `recode` / `recode_exn` / `recode_string` / `recode_string_exn`: they
were four extra verbs on top of the six the skill defines, and users can
compose `of_string |> to_string` in one line.
Rework json.brr to mirror the core six-verb shape exactly:
of_jstr / of_jstr_exn / to_jstr -- Jstr.t replaces string
of_jv / of_jv_exn / to_jv -- Jv.t (zero-copy JS value)
Dropping the jsont-era `decode`/`encode`/`'`/`recode*` verbs and the
dual Jv.Error.t / Json.Error.t return types -- everything returns
Loc.Error.t now.
Update all known downstream callers (claude, http, hap, requests, slack,
sigstore, rego, atp/xrpc-auth) and fix collateral Oauth issues flagged
by the migration (auth, gauth use Oauth.Client_auth.post now).
Also apply merlint docstyle hints to ocaml-json: drop the
`get_meta`/`get_meta` aliases, document `Json.Dict.{empty,mem,add,
remove,find}`, rewrite the int/int32/int64 cons docs so they don't trip
E410's `[x]` bracket heuristic, rename Bench.bench_file to Bench.run_file.
Drive-by: restore did/test/test_did.ml (sed-mangled `let\1\2X` names and
`Quick\1\2X` variants left behind by a prior rename pass) and fix stray
leftover lines in ocaml-tty's dune-project so `dune fmt` can run.
Apply dune fmt across the files reformatted by the Json.Codec
cleanup, and document that --password leaks via ps(1) / shell
history so users prefer the interactive prompt. Add unix to the
xrpc-auth library list (needed for read_line on the prompt path).
Reduce Json.Codec. qualifier noise in codec-heavy bodies. Pattern:
each [let X : ... Json.codec = ...] body opens [Json.Codec] at the
top of its expression, so combinators ([Object.map], [string], [int],
[list], [bool], [enum], etc.) resolve unqualified.
Files cleaned:
- ocaml-claude/lib/{outgoing,options,client,model}.ml -- the
case_mem chains and Object.map pipelines now read like the
encodings-skill examples instead of being hidden behind the
Json.Codec. prefix.
- bottler/lib/config.ml -- six Object.map pipelines (dep_type,
head_dep, linux_mode, build, package, storage, tap, jsont) all
use the open. The package one needed explicit (p : package) and
: package annotations because Json.Codec.mem_map carries fields
named [name] / [enc] that would otherwise shadow the package
record's fields under the open.
The lesson for future cleanups: any record whose field names clash
with mem_map's fields ([name], [doc], [type'], [id], [dec_absent],
[enc], [enc_omit]) needs anchoring annotations on the constructor
function and on each [~enc] lambda. Most records in this repo
don't clash, but the [name]/[enc] case is common enough to call
out.
Previously [open_in file] leaked Sys_error to the caller, which broke
tests asserting load errors return [Error _]. Wrap the read in
[Fun.protect] and catch [Sys_error].
Follow up to the module rename: update the remaining callers that
still referenced [Err] (library [claude.ml{,i}], [client.ml], the test
driver [test.ml]), and fix one stray [^ e] string concatenation in
hermest's CLI that needed [Json.Error.to_string e] now that
[Json.of_string] yields a structured error.
Make it work to host homebrew.yml files in several repos all releasing
to the same shared tap (e.g. samoht/brew). Previously, each release
overwrote the tap's README with only its own packages, and a concurrent
push was rejected non-fast-forward with no retry, so multi-source use
was not viable.
Library changes
---------------
ocaml-homebrew:
- Formula.desc_of_file reads an existing .rb's [desc "..."] line so a
README can describe formulas written by a different source.
- Tap.reset_to_remote fetches + hard-resets + cleans the local tap
checkout, for retry-after-race flows.
bottler: split release.ml per feature, add helpers:
- Formula (of_package, ensure, update_checksums, bottle_checksums,
regen) -- formula file construction and in-place updates.
- Readme (entries, generate) -- union scan of all Formula/*.rb so
each release produces the same README content regardless of which
source is pushing. Description for a locally-owned formula comes
from the config; for others it's parsed from the .rb.
- Retry.with_tap_push -- reset-to-remote + retry loop used by both
release and remove. Shared tap state makes concurrent pushes safe:
on non-ff rejection we fetch the competing commit and regenerate
our changes on top.
- Release.run now drives the pipeline and delegates to Formula,
Readme, and Retry; scopes the commit message with config.handle
("user/pkg: update bottles YYYYMMDD-SHA") so the tap log shows
which source pushed.
- Stdlib.List.* used where stdlib's List is needed, since Bottler.List
shadows inside the wrapped library.
New commands
------------
- bottler list -- enumerate every formula in the tap, flagging rows
owned by the current homebrew.yml. Lets a maintainer see sibling
sources' formulas.
- bottler remove NAME -- delete Formula/NAME.rb, regenerate the
union README, commit+push with retry. Orphan bottle blobs on S3
are left for bottler gc.
Fixes
-----
- install (both run and --local) now upgrades in place when the
target is already installed: brew upgrade tap/name for tap
installs, brew reinstall <path> for local-bottle reinstalls.
- New No_formula variant + err_no_formula helper instead of an
inline Error (Fmt.str ...) in remove.
Tests
-----
Eight new tests across Formula, Readme, Retry, List, Remove; Release
test reduced to a signature check since the pipeline is exercised by
bottler release end-to-end.
[Json.of_string] returns [(_, Json.Error.t) result]; [Alcotest.fail]
takes a [string], so pipe through [Json.Error.to_string] at the call
site. Also fix a stray typo in [test/dune] that put [loc] outside the
[(libraries ...)] list, and let [dune fmt] reflow the codec blocks in
[control] / [hooks] / [message] / [permissions] now that they sit
inside [let open Json.Codec in].
Add [open Json.Codec] at the top of files where codec combinators
dominate (stix.ml has 114 refs, claude/control/message/hooks/permissions
40-110 each). Strips the [Json.Codec.] prefix from [Object.map], [string],
[int], [bool], [list], [option], etc. — matching the spec skill's
[let open Foo.Codec in ...] recommendation applied at file scope.
Also: Json.to_string now returns plain string (not result), so collapse
the stale [match Json.to_string with | Ok s -> ... | Error _ -> ...]
patterns to direct [let s = Json.to_string ... in] use.
- Rename [let jsont] bindings to [let json] across lib/tests/examples,
since the library is now called json (not jsont).
- Drop the [module J = Json.Json] aliases — the AST builders and meta
module sit at the top level of [Json] now.
- Replace [Json.t] used as a codec with [Json.Codec.Value.t]; Json.t is
just the AST type.
- Use [let open Json.Codec in ...] inside schema definitions so the
resulting code reads closer to the spec it encodes.
- Swap [Jsont] references for [Json.Codec] (codec combinators) or plain
[Json] (AST builders) depending on what the call site actually builds.
- Hand-build JSON Schema examples with Json.object' / Json.list / Json.mem
rather than the half-decoded GADT form that used to sit under
Json.Codec.Object.
- Codec callers now see [(_, Json.Error.t) result]; surface the structured
error via [Json.Error.to_string] at process boundaries, and introduce
[encode_or_raise] / [decode_or_raise] helpers in [client.ml] so the
only [Err.ok] call sites carry a meaningful ~msg tag.
- [Json.to_string_exn]/[of_string_exn] round-trips where the caller was
pattern-matching Ok/Error collapse to the plain form that raises (the
exn is the callers' intent; returning the error and then pattern
matching was just dead code).
Warning 69 (unused-field, mutable-never-assigned). Four independent
record fields were flagged as mutable but the code only mutates their
referents in place, never rebinds the record slot itself:
- ocaml-wal/lib/wal.ml: [t.file] (the Eio file resource; methods call
Eio.File.pwrite_all etc., the slot is set once at open time).
- ocaml-block/lib/block.ml: [Memory.state.data] (the backing bytes,
written via Bytes.blit_string; [Bytes.t] is already mutable).
- ocaml-sse/lib/sse.ml: [Parser.t.data_buf] (a Buffer.t, written via
Buffer.add_*; the slot never changes).
- ocaml-zephyr/lib/zephyr.ml: drop [mode : Read | Write] entirely —
set at open-time, read nowhere. The open_read / open_write
constructors already distinguish the two call shapes, so mode
tracking was redundant.
Aligns the local subtree path with the opam package name (`claude`),
since `io` was redundant and the SDK no longer surfaces a separate
proto sublibrary.
- git mv ocaml-claudeio -> ocaml-claude
- sources.toml: section [ocaml-claude]; URLs still point at the
current upstream `ocaml-claudeio` on tangled.org (rename pending)
- llms.txt, README.md: paths updated
- lib/options.ml: log src claudeio.options -> claude.options
(matches every other src in the library)
- lib/err.{ml,mli}: doc comment dropped the old name
- test/interop/python_sdk/test.ml: regen-traces alias path
The dune-project source field and claude.opam URLs still point at
ocaml-claudeio. They will sync once the upstream remote is renamed.
Sweep through 23 more packages flagged by [monopam lint] for
test-stanza references not declared in opam. Each verified by
[dune build] + [dune runtest] before moving on.
- irmin add (astring :with-test)
- ocaml-atproto-oauth add (eio_main :with-test) (nox-crypto-rng :with-test)
- ocaml-auth add (alcotest :with-test) (eio :with-test) (eio_main :with-test)
- ocaml-cam add (odm :with-test)
- ocaml-cbor add (alcotest :with-test) (ohex :with-test)
- ocaml-cfdp add (nox-csv :with-test)
- ocaml-claude add (vlog :with-test)
- ocaml-collision add (alcotest :with-test) (odm :with-test) (ptime :with-test)
- ocaml-cookie add (re :with-test)
- ocaml-cop1 add (nox-csv :with-test)
- ocaml-crc add (nox-csv :with-test) (nox-memtrace :with-test)
- ocaml-dns-eio add (mdx :with-test); also fix a misplaced
paren in the depends list.
- ocaml-gauth add (nox-crypto-rng :with-test)
- ocaml-http add (alcotest :with-test) (eio_main :with-test) (nox-csv :with-test)
- ocaml-ltp add (nox-csv :with-test)
- ocaml-matter add (ohex :with-test) (ptime :with-test)
- ocaml-oauth add (eio_main :with-test) (nox-crypto-ec :with-test)
- ocaml-ocm add (alcotest :with-test) (nox-csv :with-test)
- ocaml-oem add (alcotest :with-test) (nox-csv :with-test)
- ocaml-opm add (alcotest :with-test) (nox-csv :with-test)
- ocaml-pbkdf2 add (ohex :with-test)
- ocaml-requests add (astring :with-test) (nox-csv :with-test)
- ocaml-retry add (re :with-test)
Per-package removal of opam runtime [depends] entries that
[monopam lint] flagged as unused -- packages declared as runtime deps
but referenced by no library [(libraries ...)] stanza in any of the
package's main lib/exec dune files (and not pulled in transitively
via META).
Verified each removal by running [dune build] (and [dune test] for
packages with a test suite) before moving on. No code changes -- only
dune-project [(depends ...)] lists.
Packages touched:
- ca-certs drop nox-crypto
- dupfind drop bos
- merlint drop nox-json (still pulled in via nox-opam.bytesrw)
- monopam drop bytesrw, requests
- monopam-info drop nox-loc, nox-sexp
- ocaml-agent drop nox-tty
- ocaml-atproto-oauth drop did-plc, did-web
- ocaml-auth drop nox-json
- ocaml-cam drop ptime
- ocaml-claude drop nox-loc
- ocaml-cop1 drop logs
- ocaml-did drop bytesrw
- ocaml-ewah drop bytesrw
- ocaml-freebox drop ipaddr
- ocaml-gauth drop base64, ptime
- ocaml-gdocs drop oauth
- ocaml-gsheets drop oauth
- ocaml-gslides drop oauth
- ocaml-hap drop nox-crypto-rng
- ocaml-jailhouse drop fmt
Generated [.opam] files updated by dune accordingly.
Remaining unused warnings ([ocaml-jwt], [ocaml-meross], [ocaml-oci],
[ocaml-pid1], [ocaml-publicsuffix], [ocaml-punycode], [ocaml-pus],
[ocaml-qemu], [ocaml-rego], [ocaml-requests], [ocaml-sbom],
[ocaml-scc], [ocaml-sdls], [ocaml-sigstore], [ocaml-sle],
[ocaml-spacedata], [ocaml-stix], [ocaml-vz], [prune], plus the
[ocaml-ccsds] meta-package and a handful of others) will follow in a
later pass once each is verified against its tests; this commit
covers only the ones I built and tested in this session.
The three usage examples opened with [let () = Eio_main.run ...] which
mdx happily executed at test time, spawning the Claude Code CLI and
hanging dune test for 90s. Wrapping each in [let run () = ...] keeps
the examples type-checked against the real API without invoking the
Eio mainloop, restoring sub-second runtime.
While here, switch [Printf.printf] / [print_string] to [Fmt.pr] to
match the monorepo convention.
ocaml-claude/examples/ has '(library (name json_utils))' without a
public_name — a workspace-private library. The opam regen pre-existing
the local-private filter pulled it into ocaml-claude/dune-project's
(depends ...) and the workspace-root dune-project. From there 'json_utils'
shipped in ocaml-claude/claude.opam (and indirectly in root.opam) as if
it were a real opam package, breaking opam install of the package.
Drop it from both dune-project files and let dune regenerate the .opam
files clean. The lint no longer re-introduces it because of 061e856ee.
Run mdx on lib/control.mli so the two {[ ... ]} odoc blocks now
type-check. Used `open Claude` per the in-package convention,
qualified the error_detail call to its real path
(`Control.Response.error_detail`), and wrapped the
server_info-printing snippet in `let report client = ...` so the
example takes the client as input rather than referencing a free
binding. Switched Printf to Fmt.pr "%s@." for consistency.
The deeply prefixed `Claude.Module.X` style across every line was
hard to read at a glance, especially in the multi-block client.mli
and claude.mli files. Switched to a single `open Claude` at the top
of each {[ ... ]} block and short call sites underneath:
open Claude
let cfg =
Options.default
|> Options.with_model `Sonnet_4_5
|> Options.with_permission_mode Permissions.Mode.Accept_edits
Touches all six claude .mli files I'd added or amended for mdx
coverage (claude.mli, client.mli, options.mli, handler.mli, hooks.mli,
tool.mli, mcp_server.mli). Wired options.mli into the (mdx ...) stanza
since the new examples now compile.
Run mdx on lib/tool.mli so the two {[ ... ]} odoc blocks now
type-check.
The Basic Usage block was constructing the tool's input schema by
hand as raw `[`O ...]` JSON arrays. Replaced with the typed
`Claude.Tool.schema_object` / `schema_string` / `text_result`
helpers exposed by this same module -- the same shape, but the
example now demonstrates the helpers a user should actually call.
Dropped the "Tool Response Format" placeholder block (raw JSON
illustration) and inlined the explanation as prose. The
schema_object block under val schema_object now wraps in
`let schema = ...` so the binding is consumable.
Run mdx on lib/handler.mli so the four {[ ... ]} odoc blocks now
type-check.
Replaced placeholder-comment bodies (`(* required *)`,
`(* must implement *)`) with real bodies that show what each
handler method receives and prints. The `on_init` example threads
through `Option.value` since `Response.Init.session_id` returns
`string option`. The `dispatch` and `dispatch_all` blocks are now
named functions (`print_text response`, `drain_all client handler`)
so the example doesn't depend on free `client` / `response` /
`handler` / `responses` bindings.
Switched all printers from Printf to Fmt.pr "%a@." and qualified
references to `Claude.Response.X` / `Claude.Content_block.X` so the
toploop resolves them through the `claude` package.
Trimmed the second prose block describing the abstract class --
the inline `(* required *)` placeholder snippet won't compile, and
the surrounding text already explains the type-error guarantee.
Run mdx on lib/hooks.mli so the two {[ ... ]} odoc blocks now
type-check.
The first block referenced free `input` of unknown record type and
unqualified `Hooks.X` / `Tool_input.X`; annotated `(input :
Claude.Hooks.Pre_tool_use.input)` so field access resolves and
qualified all module paths to `Claude.Hooks.X` / `Claude.Tool_input.X`.
The second block (configure-hooks builder pattern) was a chained
expression with free `bash_handler` / `post_handler`; wrapped in
`let configure ~bash_handler ~post_handler = ...` so the example
takes the handlers as parameters.
Replaced the `String.contains` substring check with a length+sub
prefix check that reads as plausible production code.
The top-level claude.mli had eight {[ ... ]} blocks doing a tour of
every submodule -- Handler, sequence iteration, Tool Permissions,
Permission Callbacks, Hooks, Error handling, Logging, MCP. Each was
its own deeply-nested workflow that did not copy-paste cleanly.
Trimmed to one Quick Start showing the canonical client-spawn +
handler dispatch flow, and replaced each detail tour with a
prose pointer (\"see {!Handler} for response handling, ...\"). The
focused per-module .mli's (handler.mli, hooks.mli, mcp_server.mli,
etc.) still document each piece individually.
Run mdx on lib/client.mli so the six {[ ... ]} odoc blocks now
type-check.
Several drift fixes against the current API:
- `receive_all` returns `Claude.Response.t list`, not
`Claude.Message.t list`; the basic example matches against
Response.Text and uses Response.Text.content.
- `Claude.Message.Assistant.text` doesn't exist; the only call I
needed was `combined_text`.
- `Claude.Client.set_model` and `Claude.Options.with_model` both
take `Claude.Model.t`, not string -- went through `Model.of_string`.
- `Claude.Server_info` is the right path (the previous text used
`Claude.Control.Server_info`).
Each example wrapped in a named function (`with_handler`,
`with_adaptive_permissions`, `with_model_switch`, `print_server_info`,
`run`) so the spawn-Claude-CLI side effect doesn't fire at mdx test
time, and dropped trailing `ignore (...) : Response.t list` lines in
favour of `let messages = ... in Fmt.pr "%d events@." (List.length
messages)` so the example shows what the receive_all output looks
like.
Remove libraries declared in '(libraries ...)' clauses but unreferenced
by any module in the same source tree, as flagged by 'monopam lint'
after the new Dead_lib detection landed. Touches 131 dune files across
~80 packages.
A few stanzas needed a positive correction instead of a pure removal:
- ocaml-git/bin/diag: depended on eio_main + bytesrw-eio for an
Eio_posix.run call site; the umbrella was overkill, switch to the
precise eio_posix package.
- ocaml-scaleway/lib, ocaml-s3/lib: scaleway.mli / s3.mli reference
Eio_unix.Stdenv.base; eio.unix is required and was missing.
- merlint/lib: pulled bytesrw + nox-opam.bytesrw to surface
Opam_bytesrw, used by rule e915 and lint helpers.
Stanzas where Dead_lib was a false positive (transitive dep needed
for module visibility, virtual-library impls) are left untouched —
e.g. helix.jx.jsoo for ocaml-globe/demo retains its (libraries ...)
entry because it provides the impl of the helix.jx virtual lib.
The Basic-query and streaming examples referenced the older
Message.Assistant variant. The current type is Response.Text wrapping a
Response.Text.t value with .content. Rewrite the iter clauses against
the new API and 'open Claude' so module references stay short.
Also extend the (mdx (libraries ...)) stanza with eio.unix and fmt so
the examples compile in the test sandbox.
The READMEs all share the standard install/overlay snippet, but the
sh blocks lacked the "<!-- $MDX skip -->" directive. `dune test`
would shell out to `opam install` against the live switch, which
either prompts interactively or fails with a package conflict —
either way diffing as a test failure.
Bulk-add skip directives in front of every install/overlay block.
Also collapse the doubled "non-deterministic + skip" stack on three
READMEs (memtrace, ocaml-dpop, ocaml-pid1, ocaml-yaml, merlint) where
`skip` already implies the runtime is bypassed.
Renames 35 packages to make blacksun forks distinguishable from their
opam-repository upstreams. Module names (Git.x, Tls.x, ...) stay bare;
opam package names and dune (public_name) findlib references move to
nox-X. After this commit, zero local package names overlap with
opam-repository.
Renamed:
- nox-git, nox-irmin
- nox-crypto, nox-crypto-pk, nox-crypto-rng, nox-crypto-ec
- nox-tls, nox-tls-eio, nox-tar, nox-tar-eio, nox-tty, nox-tty-eio
- nox-arp, nox-ca-certs, nox-cbor, nox-cookie, nox-crc, nox-csv
- nox-gpt, nox-hkdf, nox-http, nox-jwt, nox-kdf, nox-loc
- nox-memtrace, nox-pds, nox-sexp, nox-slack, nox-toml
- nox-websocket, nox-x509, nox-xdge, nox-yaml
Also drops orphan tar-mirage and tar-unix opam templates that had no
matching package stanza.
[let open Json.Codec in; map ~kind ~dec:of_string ~enc:to_string
string] made OCaml mis-resolve [string] against another [Codec.*]
path when [of_string] and [to_string] were both labeled arguments.
Pulling [enc = to_string] into a local binding before the [open]
pins the inference without any label-order sensitivity, matches
the other encodings helpers in the repo, and reads a touch better.
Per the ocaml-encodings convention, encoding never has a meaningful
runtime failure mode: the encoder walks the GADT and invokes
user-supplied [enc] callbacks. The only failure cases are (a) a codec
built around [Codec.ignore] (programmer error), or (b) a user-
supplied [enc] that raises (user's own bug). Neither is something a
caller should recover from, so the [result] variant was dead API
surface and every caller I've seen either [get_ok]-ed it or fell
back to [Json.Null] on error.
Drop [Codec.encode] / [Json.encode] result-returning, rename the
exception-raising [encode_exn] to just [encode]. [decode] keeps both
forms since malformed JSON is a legitimate runtime condition.
Downstream sweep: 28 files across claude / oci / atp / qemu / scitt /
yaml. Most were pattern A ([Error _ -> Json.Null ((), Meta.none)]) or
pattern B (print the error); both collapse to a direct call. Tests
using pattern F ([match Json.encode ... with Ok j -> ... | Error e
-> Alcotest.fail]) collapse to [let j = Json.encode ... in]. A few
helpers in claude/client.ml and the atp shims kept their names as
thin aliases to preserve grep targets.
Object combinators: [Object.mem] -> [Object.member], [Object.opt_mem]
-> [Object.opt_member], [Object.case_mem] -> [Object.case_member]. The
sibling submodules [Object.Mem] / [Object.Mems] become
[Object.Member] / [Object.Members]. RFC 8259 §4 calls these
"name/value pairs, referred to as the members", so mirror the spec
name rather than the shortened [mem].
[Object.finish] -> [Object.seal]. "Seal" reads as "close the map, no
more members added", which is what the operation does.
Value constructors/queries: [Value.mem] (function) -> [Value.member];
[Value.mem_find] -> [Value.member_key]; [Value.mem_names] ->
[Value.member_names]; [Value.mem_keys] -> [Value.member_keys].
[type mem = ...] -> [type member = ...]; [type object'] still points
at [member list].
Downstream (~80 files across slack, sbom, stripe, sigstore, requests,
claude, irmin, freebox) updated via perl-pie. dune build clean,
dune test ocaml-json clean.
Migrate every `to_string` / `to_writer` from the three-variant enum
`?format:format = Minify | Indent | Layout` to two orthogonal knobs:
?indent:int -- omit for compact; pass 2 for pretty (two-space indent).
Inside the function the value is `int option`.
?preserve:bool -- default false; honor per-node Loc.Meta whitespace when
true, with the ?indent path as fallback for new nodes.
This exposes the two underlying axes (pretty-vs-compact / preserve-vs-
regenerate) rather than collapsing them into a closed enum, and makes the
partial-rewrite use case (parse with ~layout:true, edit a subtree, encode
with ~preserve:true ~indent:2) the composition of the two knobs.
Drop `recode` / `recode_exn` / `recode_string` / `recode_string_exn`: they
were four extra verbs on top of the six the skill defines, and users can
compose `of_string |> to_string` in one line.
Rework json.brr to mirror the core six-verb shape exactly:
of_jstr / of_jstr_exn / to_jstr -- Jstr.t replaces string
of_jv / of_jv_exn / to_jv -- Jv.t (zero-copy JS value)
Dropping the jsont-era `decode`/`encode`/`'`/`recode*` verbs and the
dual Jv.Error.t / Json.Error.t return types -- everything returns
Loc.Error.t now.
Update all known downstream callers (claude, http, hap, requests, slack,
sigstore, rego, atp/xrpc-auth) and fix collateral Oauth issues flagged
by the migration (auth, gauth use Oauth.Client_auth.post now).
Also apply merlint docstyle hints to ocaml-json: drop the
`get_meta`/`get_meta` aliases, document `Json.Dict.{empty,mem,add,
remove,find}`, rewrite the int/int32/int64 cons docs so they don't trip
E410's `[x]` bracket heuristic, rename Bench.bench_file to Bench.run_file.
Drive-by: restore did/test/test_did.ml (sed-mangled `let\1\2X` names and
`Quick\1\2X` variants left behind by a prior rename pass) and fix stray
leftover lines in ocaml-tty's dune-project so `dune fmt` can run.
Reduce Json.Codec. qualifier noise in codec-heavy bodies. Pattern:
each [let X : ... Json.codec = ...] body opens [Json.Codec] at the
top of its expression, so combinators ([Object.map], [string], [int],
[list], [bool], [enum], etc.) resolve unqualified.
Files cleaned:
- ocaml-claude/lib/{outgoing,options,client,model}.ml -- the
case_mem chains and Object.map pipelines now read like the
encodings-skill examples instead of being hidden behind the
Json.Codec. prefix.
- bottler/lib/config.ml -- six Object.map pipelines (dep_type,
head_dep, linux_mode, build, package, storage, tap, jsont) all
use the open. The package one needed explicit (p : package) and
: package annotations because Json.Codec.mem_map carries fields
named [name] / [enc] that would otherwise shadow the package
record's fields under the open.
The lesson for future cleanups: any record whose field names clash
with mem_map's fields ([name], [doc], [type'], [id], [dec_absent],
[enc], [enc_omit]) needs anchoring annotations on the constructor
function and on each [~enc] lambda. Most records in this repo
don't clash, but the [name]/[enc] case is common enough to call
out.
Follow up to the module rename: update the remaining callers that
still referenced [Err] (library [claude.ml{,i}], [client.ml], the test
driver [test.ml]), and fix one stray [^ e] string concatenation in
hermest's CLI that needed [Json.Error.to_string e] now that
[Json.of_string] yields a structured error.
Make it work to host homebrew.yml files in several repos all releasing
to the same shared tap (e.g. samoht/brew). Previously, each release
overwrote the tap's README with only its own packages, and a concurrent
push was rejected non-fast-forward with no retry, so multi-source use
was not viable.
Library changes
---------------
ocaml-homebrew:
- Formula.desc_of_file reads an existing .rb's [desc "..."] line so a
README can describe formulas written by a different source.
- Tap.reset_to_remote fetches + hard-resets + cleans the local tap
checkout, for retry-after-race flows.
bottler: split release.ml per feature, add helpers:
- Formula (of_package, ensure, update_checksums, bottle_checksums,
regen) -- formula file construction and in-place updates.
- Readme (entries, generate) -- union scan of all Formula/*.rb so
each release produces the same README content regardless of which
source is pushing. Description for a locally-owned formula comes
from the config; for others it's parsed from the .rb.
- Retry.with_tap_push -- reset-to-remote + retry loop used by both
release and remove. Shared tap state makes concurrent pushes safe:
on non-ff rejection we fetch the competing commit and regenerate
our changes on top.
- Release.run now drives the pipeline and delegates to Formula,
Readme, and Retry; scopes the commit message with config.handle
("user/pkg: update bottles YYYYMMDD-SHA") so the tap log shows
which source pushed.
- Stdlib.List.* used where stdlib's List is needed, since Bottler.List
shadows inside the wrapped library.
New commands
------------
- bottler list -- enumerate every formula in the tap, flagging rows
owned by the current homebrew.yml. Lets a maintainer see sibling
sources' formulas.
- bottler remove NAME -- delete Formula/NAME.rb, regenerate the
union README, commit+push with retry. Orphan bottle blobs on S3
are left for bottler gc.
Fixes
-----
- install (both run and --local) now upgrades in place when the
target is already installed: brew upgrade tap/name for tap
installs, brew reinstall <path> for local-bottle reinstalls.
- New No_formula variant + err_no_formula helper instead of an
inline Error (Fmt.str ...) in remove.
Tests
-----
Eight new tests across Formula, Readme, Retry, List, Remove; Release
test reduced to a signature check since the pipeline is exercised by
bottler release end-to-end.
[Json.of_string] returns [(_, Json.Error.t) result]; [Alcotest.fail]
takes a [string], so pipe through [Json.Error.to_string] at the call
site. Also fix a stray typo in [test/dune] that put [loc] outside the
[(libraries ...)] list, and let [dune fmt] reflow the codec blocks in
[control] / [hooks] / [message] / [permissions] now that they sit
inside [let open Json.Codec in].
Add [open Json.Codec] at the top of files where codec combinators
dominate (stix.ml has 114 refs, claude/control/message/hooks/permissions
40-110 each). Strips the [Json.Codec.] prefix from [Object.map], [string],
[int], [bool], [list], [option], etc. — matching the spec skill's
[let open Foo.Codec in ...] recommendation applied at file scope.
Also: Json.to_string now returns plain string (not result), so collapse
the stale [match Json.to_string with | Ok s -> ... | Error _ -> ...]
patterns to direct [let s = Json.to_string ... in] use.
- Rename [let jsont] bindings to [let json] across lib/tests/examples,
since the library is now called json (not jsont).
- Drop the [module J = Json.Json] aliases — the AST builders and meta
module sit at the top level of [Json] now.
- Replace [Json.t] used as a codec with [Json.Codec.Value.t]; Json.t is
just the AST type.
- Use [let open Json.Codec in ...] inside schema definitions so the
resulting code reads closer to the spec it encodes.
- Swap [Jsont] references for [Json.Codec] (codec combinators) or plain
[Json] (AST builders) depending on what the call site actually builds.
- Hand-build JSON Schema examples with Json.object' / Json.list / Json.mem
rather than the half-decoded GADT form that used to sit under
Json.Codec.Object.
- Codec callers now see [(_, Json.Error.t) result]; surface the structured
error via [Json.Error.to_string] at process boundaries, and introduce
[encode_or_raise] / [decode_or_raise] helpers in [client.ml] so the
only [Err.ok] call sites carry a meaningful ~msg tag.
- [Json.to_string_exn]/[of_string_exn] round-trips where the caller was
pattern-matching Ok/Error collapse to the plain form that raises (the
exn is the callers' intent; returning the error and then pattern
matching was just dead code).
Warning 69 (unused-field, mutable-never-assigned). Four independent
record fields were flagged as mutable but the code only mutates their
referents in place, never rebinds the record slot itself:
- ocaml-wal/lib/wal.ml: [t.file] (the Eio file resource; methods call
Eio.File.pwrite_all etc., the slot is set once at open time).
- ocaml-block/lib/block.ml: [Memory.state.data] (the backing bytes,
written via Bytes.blit_string; [Bytes.t] is already mutable).
- ocaml-sse/lib/sse.ml: [Parser.t.data_buf] (a Buffer.t, written via
Buffer.add_*; the slot never changes).
- ocaml-zephyr/lib/zephyr.ml: drop [mode : Read | Write] entirely —
set at open-time, read nowhere. The open_read / open_write
constructors already distinguish the two call shapes, so mode
tracking was redundant.
Aligns the local subtree path with the opam package name (`claude`),
since `io` was redundant and the SDK no longer surfaces a separate
proto sublibrary.
- git mv ocaml-claudeio -> ocaml-claude
- sources.toml: section [ocaml-claude]; URLs still point at the
current upstream `ocaml-claudeio` on tangled.org (rename pending)
- llms.txt, README.md: paths updated
- lib/options.ml: log src claudeio.options -> claude.options
(matches every other src in the library)
- lib/err.{ml,mli}: doc comment dropped the old name
- test/interop/python_sdk/test.ml: regen-traces alias path
The dune-project source field and claude.opam URLs still point at
ocaml-claudeio. They will sync once the upstream remote is renamed.