summaryrefslogtreecommitdiff
path: root/bin
Commit message (Collapse)AuthorAgeFilesLines
* release: v0.6.0v0.6.0Lukasz Kasprzak2026-08-181-1/+1
|
* docs: point --help and the man page at the new overlay workflowLukasz Kasprzak2026-08-181-0/+18
| | | | | | | | | | | | | Both listed check and new-overlay among the commands but neither told a reader how they fit together, which is the part that makes them useful. The overlay sections now carry the four-step loop -- new-overlay, edit, check, run -- and state what check does not do, since its name invites a stronger reading than it earns. Also documents what the format now permits: citations and layer optional, the signed Easter_offset, the negative nth counting from the end of the month, and the legal values of each of the six required fields, which previously appeared only in the shipped example.
* feat(cli): colitur check and colitur new-overlayLukasz Kasprzak2026-08-181-7/+154
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Writing a local calendar had no feedback loop. An overlay is applied, not validated -- that stays true, and the five test layers still cannot vouch for a user's file -- but before this the only way to learn whether yours did what you meant was to generate a year of output and grep for your own slug, and the only way to see that a directive matched nothing was to notice a warning scroll past among 365 lines. `check` loads each overlay, applies it to the real shipped calendar, and reports the directive counts, the slug each one targets, and any directive that found no target. It exits 2 when a file fails to load or a directive matched nothing, so it composes into a Makefile or a pre-commit hook rather than merely being readable. It is applied to the SHIPPED calendar and not to an empty layer on purpose: against an empty one every Suppress would fail trivially and the check would be worthless. It answers three narrow questions -- does the file parse, does every directive find its target, what does the merged result contain. It does not validate a calendar against the rubrics and cannot, and both the help text and the man page say so rather than letting the name imply more than it does. `new-overlay` prints a starter to stdout for redirection, rather than writing a file where it likes. Every value in it is a placeholder that will appear in `day` output if left unedited, so a half-finished overlay is visible rather than silently inert, and it documents the three date shapes and the legal values for each closed field inline. load_ef_layer now returns its diagnostics instead of printing them: day and readings still want them on stderr beside a year of output, while check wants them on stdout, attributed to the overlay that produced them, and counted. Printing at the source made the second impossible. The cram test round-trips new-overlay through check rather than pinning the template line by line -- editing its prose should not fail a test, but a syntax error in it still must.
* release: v0.5.0v0.5.0Lukasz Kasprzak2026-08-181-1/+1
|
* release: v0.4.0v0.4.0Lukasz Kasprzak2026-08-181-1/+1
|
* release: v0.3.1v0.3.1Lukasz Kasprzak2026-08-181-1/+1
|
* test(cli): the 3 January pin is a BVM Saturday, not a feriav0.3.0Lukasz Kasprzak2026-08-171-1/+1
| | | | | | | | | 3 January 2026 is an unoccupied IV-class Saturday, so its office is Our Lady's and now its Mass is too -- the second of the Missal's five, assigned to Christmas through the Purification. The pin held the Christmastide feria's readings, which is what colitur said before RG 309(a) was implemented. Caught by make check on the 0.3.0 release, which is what that gate is for.
* test(cli): pin the version's shape, not its literalv0.2.0Lukasz Kasprzak2026-08-171-1/+1
| | | | | | | | | | | The release target bumps bin/main.ml and dune-project, but cli.t pinned the literal too, so cutting 0.2.0 failed `make check` on a cram diff that was purely the bump working correctly. Every release would have had to edit a test for no gain. The shape is asserted here instead. The value is already checked where it can actually drift: the release target requires the freshly built binary to report the version it just wrote.
* feat(cli): --overlay FILE, so a local calendar can be suppliedLukasz Kasprzak2026-08-171-21/+108
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The Overlay algebra has been built and tested since Plan 2, and Date_spec grew movable variants last commit, but there was still no way to hand colitur a file: bin/main.ml applied exactly one overlay, the shipped data/ef/adjustments.sexp, with the path hardcoded. This is the plumbing. --overlay is repeatable and ordered, and applies ON TOP of the shipped adjustments rather than instead of them. That ordering is the whole point: adjustments.sexp carries RG 110's own 30 June companion, the Major Litanies, St Barbara and Rogation Wednesday, and a user file that replaced it would silently drop all four while appearing merely to add a local feast. Overlay.merge is last-writer-wins, so a local calendar can still override a universal entry deliberately, by naming its slug. The dispatch needed real argument parsing. It matched Sys.argv as an exact array, which does not survive a repeatable flag -- two --overlay arguments are a different array shape from one, and each further flag would multiply the patterns again. Flags are now stripped first and the remaining words matched as command plus year. Hand-rolled, because the dependency list is frozen and this is fifteen lines. One defect found by the cram suite on the first run: the unknown-option guard rejected --help and --version, having excluded only their short forms. Refused, not ignored, on easter and temporal. Neither reads sanctoral data -- temporal deliberately runs the cycle before any layer exists -- so accepting the flag there and silently doing nothing is the failure mode this project refuses everywhere else. A file that fails to load is fatal, exactly as the shipped overlay is; a directive naming a slug that does not exist warns on stderr and the run continues. The second matters more for a user file than for ours: a typo in a diocesan calendar should say so rather than quietly do nothing. test/fixtures/overlay-example-diocesan.sexp is a worked example and is labelled as invented, not calendar data. It carries both shapes a real local calendar needs: a fixed-date patron, and a dedication on "the first Sunday of October" -- which is the case the Date_spec work existed to unlock and which lands on 4 October in 2026, 3 October in 2027, 1 October in 2028. The dedication is I class because a church's own dedication anniversary is I class in that church; at III class it lost to the II-class Sunday every year, which a first draft demonstrated correctly and uselessly. The man page gains an OVERLAYS section saying plainly that an overlay is applied, NOT validated: the five test layers assert things about the shipped calendar and none of them can vouch for a file the user supplies. This is the first feature that lets someone change what colitur computes, and that distinction should not have to be inferred.
* feat(cli): --version, a CHANGELOG, and a release targetLukasz Kasprzak2026-08-171-0/+15
| | | | | | | | | | | | | | | | | | | | | | | | Groundwork for tagging. The project had no version anywhere: not in dune-project, not in the binary, no CHANGELOG, no tags. The version lives in two places -- dune-project, which generates colitur.opam, and a constant in bin/main.ml, which is what --version prints. Two rather than one because dune's watermarking only substitutes in a release tarball, so a binary built the ordinary way from a checkout would report a placeholder. The release target rewrites both and then re-checks both, and finally requires the freshly built binary to report the version it just wrote: a release that bumped one and not the other would ship a binary disagreeing with its own package metadata. --version is deliberately not embedded in the help text. cli.t pins help's first line, and a version there would mean editing a test expectation every release for no gain. The release target mirrors lectio's, refusals included: no release from a dirty tree, none without a CHANGELOG entry for that version, none whose version bump silently failed to apply, and `make check` -- the full 1583-9999 sweep, not the sampling suite -- must pass before the tag is created.
* feat(cli): a Makefile, a man page, and --helpLukasz Kasprzak2026-08-171-1/+49
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Three things the project had no answer for: how to install it without knowing dune, where to read about it, and what it does when asked. Makefile, same shape as lectio's -- PREFIX ?= $(HOME)/.local, BINDIR, MANDIR, and the '## '-comment help target -- so the two siblings are driven the same way. Every recipe wraps dune in `opam exec --`, which is the actual point of having one here: `make build` works from a plain shell with no `eval $(opam env)` first. install goes through `dune install` rather than a hand-rolled copy, because the binary finds its calendar data relative to its own path; the man page is installed separately to share/man/man1, matching lectio. install and uninstall were both run against a scratch prefix and checked: uninstall leaves zero files behind. PREFIX defaults to ~/.local because that is where lectio installs and where it actually lives on this machine, so colitur lands on an existing PATH with no shell change. An earlier install this session went to ~/opt/colitur, which was me over-applying a rule meant for third-party tools to one of the author's own projects; it has been removed rather than left as a second, staler binary competing on PATH. man/colitur.1 documents the four commands, both output formats and why they differ, COLITUR_DATA_DIR and its refusal to fall back, the data resolution order, exit statuses, and -- deliberately -- the limitations: EF only, Epistle and Gospel only with the chants unbuilt and rejected rather than guessed, and the BVM Saturday Mass-selection gap. A man page that only lists what works is half a man page. Renders clean under `groff -ww -z`, no warnings. --help prints to stdout and exits 0; a usage error prints one line to stderr and exits 2. That is the Unix convention rather than a preference: asking for help succeeded and should be pipeable, being invoked wrongly did not and must not pollute stdout. Both directions are asserted in cli.t, along with a loop confirming every command the help text advertises is one the dispatch actually accepts -- the check that catches help drifting away from the code.
* feat(cli): install the runtime data, and resolve it in three waysLukasz Kasprzak2026-08-171-6/+56
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | `dune install` produced a colitur that could not run: the binary locates its data relative to its own path, nothing installed the four .sexp files anywhere, and an installed colitur exited 2 unable to read sanctoral.sexp. `dune build @install` produced an empty tree. data/dune installs the four RUNTIME files into <prefix>/share/colitur/ef. Deliberately not the two allow-lists: those describe where colitur and a comparison oracle disagree, are read only by the differential and oracle tests, and are meaningless to a running colitur -- installing them would ship an assertion about lectio's and missalemeum's behaviour as though it were calendar data. No glob, on purpose: a glob would silently start shipping them again the moment one was renamed. Resolution now probes rather than computing one path and hoping. An installed prefix is tried first, the build tree second, and a candidate counts only if sanctoral.sexp is actually readable inside it -- so a failed or half-removed install falls through to a working tree instead of shadowing it and failing later with a per-file error. COLITUR_DATA_DIR overrides both and NEVER falls through. This was the one real design question and the first version got it wrong: it treated the override as just another candidate, and a deliberately bogus value produced a full, plausible, entirely un-flagged year computed off the build tree. Someone who names a directory has stated an intent, and silently calendaring off a different one is the silent substitution this project refuses everywhere else. It is now exit 2 naming the directory. Verified end to end, not inferred: installed to a scratch prefix, then ran the binary from an unrelated cwd with no build tree near it, and confirmed 2038-03-06 still resolves Perpetua and Felicitas through the Common route. All four paths exercised -- build tree, valid override, bogus override, and an empty share/ falling through. Environment reads stay confined to bin/. The kernel's contract forbids them and nothing below the CLI learns where the data came from; the loaders take a path.
* feat(cli): colitur readings, one line per dayLukasz Kasprzak2026-08-171-3/+59
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The lectionary has been resolvable since Task 4 but invisible from the command line: `colitur day` prints no citations, so the branch's whole deliverable could only be seen through the test suite. A separate command rather than extra columns on `day`, which is where the plan pointed. Its instruction was to append the Epistle and Gospel to each row "matching the existing column style", and that turns out not to be possible: a citation contains spaces and commas ("Ezech 34:11-16", "Ecclus 51:1-8, 12") while a day row is space-separated with a variable-length +slug commemoration tail, so appending them leaves the row unsplittable -- no awk or cut field number recovers where the Epistle ends. That is the opposite of what the row is shaped for. So `day` keeps its format byte-identical, asserted directly in cli.t rather than left implicit, and the citations get their own row with " | "-delimited fields, safe for values containing spaces. Both formats are a stopgap and say so in the source: the design calls for one schema rendered through a logic-less template engine, and two ad-hoc formats are easier to retire later than one overloaded format whose parsing rules nobody wrote down. The year walk is now shared. day_report and readings_report differ only in how a day is printed, and the two-liturgical-year indexing -- with its own reasoning about civil-versus-liturgical spans -- is exactly the part that must not be duplicated and drift. "-" for an absent part, though no EF day can currently print one: layer 2 asserts exactly one First and one Gospel on every day of every year 1583-9999. The CLI still does not assume a guarantee the kernel makes about data rather than about types. cli.t gains the four chain-step cases test_golden.ml pins against the scans, so the CLI path is checked to agree with the library path, plus the 2 January line, which reads the Circumcision's Mass under the Missal's own ferial rubric (scan1:6523-6526) and not RG 17(a). Verified the cram tests actually run and have teeth: corrupting one expectation produces a diff and exits 1. CLAUDE.md: state, test count (369, 370 with the sweep), the readings command and why it is separate, and What's next -- the lectionary landed early rather than inside Plan 4, so what remains there is OF's own lectionary, not the mechanism. Chants stay deliberately unbuilt, and Validate now rejects any part outside First/Gospel.
* ef(lectionary): the Commons, chain step 4Lukasz Kasprzak2026-08-151-40/+67
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Fifteen class-3 saints had no readings anywhere in colitur -- the 6 March - 5 April cluster whose RANKS lectio's generator fix of 2026-08-12 restored without backfilling readings, and lectio has no Commons concept to have backfilled them from. They are exactly the fifteen `status Feast` entries in data/ef/sanctoral.sexp carrying `(citations ())`. The Missal splits them eight/seven, and NOT as the plan predicted. It expected propers for Benedict, Patrick, Thomas Aquinas and Gregory the Great; only Thomas Aquinas has one. Benedict ("Missa Os iusti, de Communi Abbatum"), Patrick ("Missa Statuit, de Communi Confessoris Pontificis I loco") and Gregory the Great ("Missa Si diligis me, de Communi unius aut plurium Summorum Pontificum") are all sent to a Common, with only Oratio, Secreta and Postcommunio printed at the date. All three reversals verified on the page images (pp. 488, 492) as well as in both OCR text layers. PROPERS (data/ef/adjustments.sexp, `Edit ... Set_citation`): thomas-aquinas 7 Mar Sap 7:7-14 / Mt 5:13-19 john-of-god 8 Mar Eccli 31:8-11 / Mt 22:34-46 forty-holy-martyrs 10 Mar Hebr 11:33-39 / Luc 6:17-23 cyril-of-jerusalem 18 Mar Eccli 39:6-14 / Mt 10:23-28 gabriel-the-archangel 24 Mar Dan 9:21-26 / Luc 1:26-38 john-damascene 27 Mar Sap 10:10-17 / Luc 6:6-11 john-of-capistrano 28 Mar Sap 10:10-14 / Luc 9:1-6 francis-of-paola 2 Apr Philipp 3:7-12 / Luc 12:32-34 COMMONS (data/ef/commons.sexp), each quoting the Missal's own instruction at the saint's date: sts-felicitas-perpetua 6 Mar non Virginum I Eccli 51:1-8,12 / Mt 13:44-52 frances-rome 9 Mar non Virginum II Prov 31:10-31 / Mt 13:44-52 gregory-the-great 12 Mar Summ. Pontificum 1 Petri 5:1-4,10-11 / Mt 16:13-19 patrick 17 Mar Conf. Pont. I Eccli 44:16-27;45:3-20 / Mt 25:14-23 benedict 21 Mar Abbatum Eccli 45:1-6 / Mt 19:27-29 isidore-of-seville 4 Apr Doctorum 2 Tim 4:1-8 / Mt 5:13-19 vincent-ferrer 5 Apr Conf. non Pont. I Eccli 31:8-11 / Luc 12:35-40 Every value read in three independent places -- both OCR text layers (two DIFFERENT printings, whose Commune Sanctorum pagination differs by 1-2, so the data keys on heading plus Mass incipit and never on the bracketed page number) and the rendered page images. That caught a genuine typographical error in one printing: it gives St Gabriel's Gospel as "Luc. 2, 26-38" (on the page image, so not an OCR artefact). Luke 1:26-38 is settled by the other printing, by the pericope text in both, and by the SAME edition citing the identical pericope correctly two pages later at the Annunciation. Step 4 EXECUTES SECOND (order 1, 4, 2, 3), not last as the plan sketched. Measured: every day on which one of the fifteen is actually the observed office already has a non-empty step-2 or step-3 answer waiting, so a step 4 placed after the ferial fallback is unreachable on every date in 1583-9999 -- and wrong where it would fire (2038-03-06 would emit Septuagesima II Saturday's Mass on a day whose office is Sts Perpetua and Felicity). Step 4 is also the only step in the chain with a direct primary-source warrant: the Missal names the Mass at each such saint's own date, while steps 2 and 3 rest only on lectio's observed behaviour. Guarded so it can never divert a day whose observed office IS its temporal office -- ferias, Sundays, the Triduum and the RG 78 Saturday Office of the BVM (which deliberately shares the ferial slug) are structurally excluded. Step NUMBERS are unchanged so every "step 3" already written down still names the same branch. The Commons are caller-supplied, like the lectionary: `Rite_ef.context` gains a REQUIRED `~commons`, not an optional one -- a silently-defaulted empty table would give a rite whose class-3 saints quietly lose their Mass, and nothing in layers 3-5 compares citations, so that loss would be invisible. `Commons.load` rejects the four defects indistinguishable downstream from "this saint has no Common": duplicate common id, duplicate assignment, empty formulary, assignment naming a common that does not exist. KNOWN LIMITS, recorded rather than papered over. There is no oracle: lectio has no readings for these feasts and missalemeum's 2026-2027 window never observes one of the fifteen. Eight of the fifteen are NEVER the observed office anywhere in the domain (10-28 March always falls to a Lenten or Passiontide feria that outranks a III-class feast), so they have no end-to-end witness and are covered by dedicated table tests instead. The Commune Sanctorum's own opening rubric permits any Epistle or Gospel of a Common in any Mass of that Common unless one is assigned, so what is emitted is the reading PRINTED WITH the named formulary, not the only lawful one; labelled alternatives (e.g. the widow's Epistle 1 Tim 5:3-10, squarely available to St Frances of Rome) are noted in the data and deliberately not encoded. The BVM Common is NOT authored: the RG 78 Saturday Office shares the ordinary ferial slug, so assigning a Common to it would hand the BVM Mass to every feria sharing that slug; routing it needs a subject-keyed lookup and a season-keyed choice among the Missal's five "Missae de sancta Maria in sabbato" -- new behaviour, not new data. It currently emits its feria's Mass, now pinned as such. Blast radius, measured (git archive HEAD binary vs HEAD, full 1583-9999 `colitur day` sweep, diffed): ZERO differing lines, 3 074 246 lines each side, byte-identical -- the CLI prints no citations, so this change is invisible to layers 2-5 by construction, and the sanctoral overlay perturbed nothing else. 357 tests green (344 before), 358 with the exhaustive sweep. Mutation-tested six ways, all reverted after confirming: relegating step 4 to last reddens exactly the four step-4 pins and nothing else; corrupting a Common's citation, dropping an assignment, dropping a proper Edit, and corrupting either an unreachable proper or an unreachable Common each redden their own pins. The load-bearing assertion is stated over the loaded layer rather than a hard-coded list: every sanctoral entry that can ever BE the observed office now has either a proper or an assigned Common -- fifteen failures before, zero after, and a future re-bootstrap adding a readingless class-3 feast fails there instead of silently emitting its feria's Mass.
* kernel+ef: fix round 1 -- lectionary caller-supplied, not eagerLukasz Kasprzak2026-08-151-33/+58
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | Critical (coordinator review): a clean `dune build` produced a `colitur` that died at startup on EVERY subcommand, including ones touching no lectionary data at all. Root cause was two-fold: data/ef/lectionary.sexp was never added to the root default-build alias (only materialised as a side effect of the test suite's own deps, which is why every check in the prior report passed), and Rite_ef.context loaded it as a module-init side effect via failwith, undoing Lectionary.load's own "never raises" promise at a point no caller could catch. Fixed structurally: Rite_ef.context is now a function taking ~lectionary, Lectionary_ef.readings takes ~lectionary, and neither touches the filesystem any more -- the same caller-supplied discipline the sanctoral layer already had, restoring rite_ef.mli's own pre-existing claim about it and leaving a seam for a future diocesan lectionary overlay. bin/main.ml grows load_ef_lectionary, a sibling of load_ef_layer, routed through the same colitur: %s / exit 2 path. data/ef/lectionary.sexp added to the root default alias. Every caller of Rite_ef.context updated to supply it. Also: two new tests that genuinely distinguish chain step 1 from step 2 (19 March 2026, Joseph's own proper over a competing temporal entry; 13 January 2030, Holy Family reached only through the temporal slug, the Baptism entirely absent) -- the prior two tests both survived swapping the chain order. Both new pins verified directly against the real data. The chain's own comment now states plainly that its warrant is lectio's observed behaviour, not a confirmed Missal citation, per the rules register's own open item.
* cli: colitur day <year> dumps resolved liturgical daysLukasz Kasprzak2026-08-121-1/+136
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Assembles Rite_ef.context (lib/rites/rite_ef/rite_ef.ml[i]): temporal, anchors and vocab from Temporal_ef; rules from Precedence_ef's band, disposition and admit; season_runs = Vocab_ef.seasons; transfer_target newly implemented here. transfer_target (RG 96): the next following day that is not I or II class, with the Annunciation's own exception (Monday after Low Sunday). Terminates by a structural step bound on its internal search, independent of Calendar's own round guard, which bounds rounds across a year, not one call's walk; documented as an obligation on rite.mli's transfer_target field, which did not previously state it. Fixes the vigil-naming mismatch Task 7's review predicted: the sanctoral bootstrap names its vigils with a vigil-of-X prefix (lectio's own convention), while Precedence_ef's is_vigil only recognised the temporal cycle's own X-vigil suffix. Both are now recognised, fixing RG 91 entries 21/26 and RG 33's vigil omission for the four affected celebrations. Verified by unit test and by mutation-testing the fix (reverting it fails exactly the new rows) and against real output across several years. Suppresses data/ef/sanctoral.sexp's vigil-of-christmas via a new overlay, data/ef/adjustments.sexp: it is the same celebration as the temporal cycle's own ef-nativity-vigil, both dated 24 December. colitur day <year>: one line per civil-year day, temporal and sanctoral fully resolved through Layer, Overlay, Precedence_ef and Calendar -- the first CLI path exercising the whole Plan 3 pipeline against real data. Verified the All Souls transfer chain (Tasks 7-8-11) end to end against real output for both a Sunday year (2025, lands on 3 Nov) and a non-Sunday year (2026, observed directly on 2 Nov).
* cli: emit "-" for an absent week, keeping output column-safeLukasz Kasprzak2026-08-111-1/+7
| | | | | | | | | | | | | | | | | | | | | | | | | week is "" for roughly 30 days a year (any day outside a numbered week). Printed as-is among seven space-separated fields, that collapses two fields into a double space, so naive field-position parsing (e.g. awk '{print $4}') silently reads the wrong column on those days -- a real defect for a project whose stated design is Unix-composable CLIs. Emit "-" instead, so every line always has exactly seven single-space-separated fields. Record.headers/to_row exist for exactly this kind of column-safe encoding but use a different column set and order than this CLI's documented date/weekday/season/week/slug/rank/colour format (design spec ยง6); adopting them here would be a larger, unrequested format change, so this keeps the CLI's own field list and only substitutes the empty value. The cram test's pinned lines are re-verified against liturgical anchors before promoting, not blind-promoted: 1 Jan 2026 (Circumcision, class-1, white) and 2-3 Jan (ordinary class-4 Christmastide ferias, the finding-1 slug fix already re-pinned separately) are correct; 2026-04-05 remains the sole Easter Sunday line, Paschaltide week 1, class-1, white, matching the independently-computed Easter anchors printed by `colitur easter 2026` immediately above in the same file.
* cli: colitur temporal <year> dumps the EF temporal cycleLukasz Kasprzak2026-08-112-9/+35
| | | | | | | One line per day -- date, weekday, season, week, slug, rank, colour -- built through Record, the canonical output view. Until Plan 3 brings the differential and oracle layers, this dump is how a human checks EF temporal against missalemeum by eye.
* cli: colitur easter <year> prints Easter and its anchorsLukasz Kasprzak2026-07-311-2/+28
| | | | | Argv-parsed subcommand; validates the 1583..9999 domain (exit 2 otherwise); prints 'name YYYY-MM-DD' lines. Cram test covers the output and the exit codes.
* chore: scaffold dune project (kernel lib + cli + tests)Lukasz Kasprzak2026-07-312-0/+7
OCaml 5.2.0 local switch; colitur_kernel library, a colitur executable stub, and an alcotest+qcheck test runner. AGPL LICENSE, README, generated colitur.opam.