Skip to content

Commit f41f8ee

Browse files
committed
ArghDA M8: Coq/Rocq adapter — first heavy-tail backend (foundation-first)
Provisioned the toolchain BEFORE the adapter (opam 2.1.5 → coq.8.18.0), then built and dogfooded the backend against the real `coqc`. arghda is now a seven-backend prover-parametric workspace. Adapter (src/prover/rocq.rs, `Coq`): - Assistant kind, extensions [v], command `coqc`. - Verdict logic ground-truthed vs Coq 8.18.0: exit≠0 → Error; exit 0 + Admitted/admit → Admitted; exit 0 + genuine postulate → Postulated; exit 0 clean → Proven. `coqc` kernel-checks every `Qed` at compile, so a clean compile with no escape hatch is an honest Proven (stronger than Lean, which needs a #print axioms audit). Never fabricates a verdict. - `count_genuine_postulates`: the Section-aware classifier ported from panic-attack's `count_rocq_unverified_postulates` — Section-scoped Variable/Hypothesis/Parameter discharge at End; module-level Parameter of carrier / decidability-witness / non-Prop-function type are abstractions; Prop-valued Axiom/Parameter are counted. - check_file redirects the `.vo` into a temp dir keeping the source stem (coqc `-o` requires target basename == source; a real-coqc dogfood caught the first attempt renaming it). - Imports: `Require [Import|Export]` + `From P Require …`; bare Import/Export is a namespace directive, not an edge (as Lean `open` is). Roots: the `All.v` aggregator convention. Lint (src/lint/coq.rs, `CoqEscapeHatch`): warn under the shared `escape-hatch` name (caps at amber in the reasoning graph); flags Admitted/admit/Axiom/ Conjecture. Parameter is left to the backend's Section-aware classifier. Wired: backend_for "coq", KNOWN_BACKENDS, all four `--backend` help strings, the CLI about line. Verification (actually run): cargo fmt --check, clippy -D warnings, cargo test (146 pass: +7 rocq unit, +5 coq lint unit, +3 coq integration; graph tests hermetic — text only, run tool-less), check-spdx.sh — all green. Dogfooded vs real coqc 8.18.0: `check` → Clean=proven / Admits=admitted / Postulate= postulated / Broken=error; `reason --check --backend coq` reproduces those; import edge All→Base parsed, All.v discovered as crt_root, Base+Util wired, Orphan unwired; `doctor` → [OK] coq assistant 8.18.0. Remaining (M8 at 90%): _CoqProject-driven logical-path resolution + dependency-ordered compilation (a bare single-file coqc reports Error when an uncompiled dependency is Required — documented limitation, same class as Lean's lake-env gap). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MpYSh6Wy8YMBH2E3qVyT7
1 parent 0aa9ff2 commit f41f8ee

12 files changed

Lines changed: 850 additions & 23 deletions

File tree

.machine_readable/6a2/STATE.a2ml

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ status = "active" # active | paused | archived
1111

1212
[project-context]
1313
name = "arghda-core"
14-
purpose = "Multi-backend proof-workspace manager — the 'Flying Logic for provers/solvers' engine. A four-state triage workspace (inbox/working/proven/rejected) as file moves, per-backend lint packs targeting the silent-failure class, a backend-parametric import-graph DAG, a demote-only status-propagating reasoning graph, and a JSON + event-stream contract a visual layer (arghda-studio) consumes. Six backends across both interaction models (Agda, Cubical-Agda, Idris2, Lean4 assistants; Z3, CVC5 solvers). It never proves anything itself; the tools do."
14+
purpose = "Multi-backend proof-workspace manager — the 'Flying Logic for provers/solvers' engine. A four-state triage workspace (inbox/working/proven/rejected) as file moves, per-backend lint packs targeting the silent-failure class, a backend-parametric import-graph DAG, a demote-only status-propagating reasoning graph, and a JSON + event-stream contract a visual layer (arghda-studio) consumes. Seven backends across both interaction models (Agda, Cubical-Agda, Idris2, Lean4, Coq/Rocq assistants; Z3, CVC5 solvers). It never proves anything itself; the tools do."
1515
completion-percentage = 92
1616

1717
[position]
@@ -48,15 +48,18 @@ milestones = [
4848
{ name = "M5: SMT solver backends (Z3, CVC5): SMT-LIB2 -> sat/unsat/unknown -> Verdict", completion = 100 },
4949
{ name = "M6: Lean4 adapter (lake build + #print axioms audit)", completion = 92 },
5050
{ name = "M7: Echidna dispatch seam (optional route to orchestrator :8090, same Outcome)", completion = 70 },
51-
{ name = "M8: Coq/Rocq adapter (--heavy provisioning; Section-aware postulate classifier)", completion = 0 },
51+
{ name = "M8: Coq/Rocq adapter (--heavy provisioning; Section-aware postulate classifier)", completion = 90 },
5252
{ name = "M9: Isabelle adapter (--heavy; dogfood tropical-resource-typing .thy)", completion = 0 },
5353
{ name = "M10: Mizar adapter (detect-only first; highest uncertainty)", completion = 0 },
5454
{ name = "M11: studio JSON freeze (reason/0.1) + Groove manifest", completion = 100 },
5555
]
5656
# M0 provisioning verified in-container 2026-07-01: agda 2.6.3 (+stdlib v2.3
5757
# +cubical lib), zig 0.15.2, z3 4.8.12, cvc5 1.2.0, lean 4.13.0 all report
58-
# real --version. idris2 v0.7.0 builds via chez bootstrap. Coq/Isabelle/Mizar
59-
# are scripted (behind --heavy/--mizar) but not installed this session.
58+
# real --version. idris2 v0.7.0 builds via chez bootstrap. COQ NOW INSTALLED
59+
# (2026-07-01, heavy-tail foundation): opam 2.1.5 via apt → `opam install
60+
# coq.8.18.0` → coqc reports real --version; symlinked to /usr/local/bin/coqc
61+
# so `arghda doctor` probes it ([OK] coq assistant). Isabelle/Mizar remain
62+
# scripted (behind --heavy/--mizar) but not installed this session.
6063
# Remaining M0: `arghda doctor` Rust subcommand (interim: `just doctor` shells
6164
# the script's --verify-only table).
6265
#
@@ -116,6 +119,36 @@ milestones = [
116119
# proven root → unknown; edit the proven file on disk → Good demoted to unknown
117120
# with the stale evidence string. M3 base was 100%; this is pure additive refinement.
118121
#
122+
# M8 landed 2026-07-01 (90%, first heavy-tail backend): the Coq/Rocq adapter.
123+
# Foundation-first — provisioned coqc 8.18.0 (opam) BEFORE the adapter, then
124+
# built + dogfooded against the REAL tool. src/prover/rocq.rs (`Coq`): Assistant
125+
# kind, extensions [v], command `coqc`. Verdict logic, ground-truthed vs Coq
126+
# 8.18.0: exit≠0 → Error; exit 0 + `Admitted`/`admit` → Admitted; exit 0 +
127+
# genuine postulate → Postulated; exit 0 clean → Proven (coqc kernel-checks
128+
# every `Qed` at compile, so this is honest — stronger than Lean's need for a
129+
# #print axioms audit). "Genuine postulate" = `count_genuine_postulates`, the
130+
# Section-aware classifier PORTED from panic-attack's
131+
# `count_rocq_unverified_postulates`: Section-scoped Variable/Hypothesis/
132+
# Parameter discharge at End; module-level Parameter of carrier/decidability/
133+
# non-Prop-function type are abstractions; Prop-valued Axiom/Parameter counted.
134+
# check_file redirects the .vo to a temp DIR keeping the source stem (coqc `-o`
135+
# insists target basename == source; a REAL-coqc dogfood caught my first
136+
# attempt renaming it). Imports: `Require [Import|Export]` + `From P Require …`
137+
# (bare Import/Export = namespace directive, not an edge, like Lean `open`).
138+
# Roots: `All.v` aggregator convention (mirrors Agda). Lint: src/lint/coq.rs
139+
# `CoqEscapeHatch` (warn, shared name `escape-hatch`) flags Admitted/admit/
140+
# Axiom/Conjecture (Parameter left to the backend classifier). Wired: backend_for
141+
# "coq" + KNOWN_BACKENDS + all four `--backend` help strings + about line.
142+
# 146 tests (+7 rocq unit +5 coq lint unit +3 coq integration; graph tests
143+
# hermetic — text only, run tool-less). DOGFOODED vs real coqc 8.18.0: check
144+
# → Clean=proven / Admits=admitted / Postulate=postulated / Broken=error;
145+
# `reason --check --backend coq` reproduces those; import edge All→Base parsed,
146+
# All.v discovered as crt_root, Base+Util wired, Orphan unwired; `doctor` →
147+
# [OK] coq assistant 8.18.0. Remaining 10%: `_CoqProject`-driven logical-path
148+
# resolution + dependency-ordered compilation (a bare single-file coqc reports
149+
# Error when an uncompiled dependency is Required — documented limitation, same
150+
# class as Lean's lake-env gap).
151+
#
119152
# M7 landed 2026-07-01 (70%): the Echidna dispatch seam — completes the
120153
# engine-spine deliverable (M0-M7 + M11). src/dispatch.rs: `Dispatch { Local,
121154
# Echidna { base_url } }` + `run(backend, file, include_root) -> Outcome`
@@ -277,7 +310,8 @@ actions = [
277310
"M6 follow-on: #print axioms audit DONE (import-free files promote honestly); remaining = lake-env/LEAN_PATH so imported files can be checked + audited too.",
278311
"M4 follow-on: .ipkg-declared roots DONE; remaining = totality-hole (?name) + per-def `partial` lint.",
279312
"M3 follow-on: DONE — `reason --workspace <ws>` sources real verdicts from a `proven/` state (workspace_verdicts) + demotes stale-proven via the content-hash closure. Refinement follow-ons bucket now complete.",
280-
"M8-M10 heavy tail (Coq/Rocq, Isabelle, Mizar) — gated on --heavy provisioning (Coq via opam ~1hr; Isabelle ~4GB; Mizar niche).",
313+
"M8 (Coq/Rocq): DONE (90%) — coqc 8.18.0 provisioned + adapter built + dogfooded (proven/admitted/postulated/error) with Section-aware postulate classifier. Remaining: _CoqProject logical paths + dependency-ordered compilation.",
314+
"M9-M10 heavy tail (Isabelle, Mizar) — next, foundation-first: Isabelle ~4GB tarball then adapter; Mizar niche/detect-only. Then climb to M6 lake-env, M4 totality lint, M7 Echidna client.",
281315
]
282316

283317
[maintenance-status]
@@ -286,13 +320,13 @@ last-result = "pass" # unknown | pass | warn | fail
286320
open-warnings = 0
287321
open-failures = 0
288322
# `just check` (fmt-check + clippy -D warnings + build + test + SPDX) green
289-
# post-M3-followon: 96 lib + 34 integration = 130 tests pass. codeql.yml added
290-
# (code-scanning gate fix). reason --workspace verdicts+staleness dogfooded.
291-
# Lean #print axioms audit dogfooded. doctor/groove/dispatch
292-
# dogfooded: 6/6 backends runnable; groove writes /.well-known/groove; --dispatch
293-
# local real, echidna honest-stub. All six backends dogfooded via real CLI: agda
294-
# + agda-cubical + idris2 + lean4 (Assistant), z3 + cvc5 (Solver). Dogfooded
295-
# historically vs echo-types + agda-unused.
323+
# post-M8: 108 lib + 38 integration = 146 tests pass. codeql.yml added
324+
# (code-scanning gate fix). M8 Coq adapter dogfooded vs real coqc 8.18.0
325+
# (proven/admitted/postulated/error; reason --check; import edges; roots; wired;
326+
# doctor [OK] coq). reason --workspace verdicts+staleness dogfooded. Lean
327+
# #print axioms audit dogfooded. doctor/groove/dispatch dogfooded. SEVEN
328+
# backends now: agda + agda-cubical + idris2 + lean4 + coq (Assistant), z3 +
329+
# cvc5 (Solver). Dogfooded historically vs echo-types + agda-unused.
296330

297331
[ecosystem]
298332
part-of = ["arghda"]

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ pub use graph::{build as build_graph, ImportGraph};
3535
pub use groove::{manifest as groove_manifest, GrooveManifest};
3636
pub use lint::{default_rules, rules_with_config, run_lints, LintRule, RuleConfig};
3737
pub use prover::{
38-
default_backend, Agda, AgdaCubical, Backend, BackendKind, Idris2, Lean, Outcome, Probe, Smt,
39-
Verdict,
38+
default_backend, Agda, AgdaCubical, Backend, BackendKind, Coq, Idris2, Lean, Outcome, Probe,
39+
Smt, Verdict,
4040
};
4141
pub use reason::{
4242
build as build_reason, workspace_verdicts, Junct, ReasonDocument, ReasonEdge, ReasonNode,

src/lint/coq.rs

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
4+
//! Coq / Rocq `escape-hatch` (warn) — surface soundness escape hatches.
5+
//!
6+
//! The Coq analogue of the Agda/Idris2/Lean escape-hatch rules; shares the
7+
//! rule *name* `escape-hatch` so the reasoning graph caps any hit at amber.
8+
//!
9+
//! Flags the unambiguous holes: `Admitted`/`admit` (a proof closed as an
10+
//! axiom / left as a placeholder) and `Axiom`/`Conjecture` (declared, not
11+
//! proved). `coqc` compiles all of these with exit 0, so without this warn —
12+
//! and without the backend's Section-aware postulate classification — they
13+
//! ride along invisibly. `Parameter`/`Variable`/`Hypothesis` are deliberately
14+
//! NOT flagged here: they are legitimate scaffold shapes far more often than
15+
//! not, and the genuine module-level `Parameter` postulate is caught by the
16+
//! backend's `count_genuine_postulates` (Section-aware) instead.
17+
18+
use super::{LintContext, LintRule};
19+
use crate::diagnostic::{Diagnostic, LintReport, Severity};
20+
use anyhow::{Context, Result};
21+
use std::fs;
22+
use std::path::Path;
23+
24+
pub struct CoqEscapeHatch;
25+
26+
const ESCAPE_TOKENS: &[&str] = &["Admitted", "admit", "Axiom", "Conjecture"];
27+
28+
impl LintRule for CoqEscapeHatch {
29+
fn name(&self) -> &'static str {
30+
"escape-hatch"
31+
}
32+
33+
fn run(&self, file: &Path, _ctx: &LintContext<'_>, report: &mut LintReport) -> Result<()> {
34+
let contents =
35+
fs::read_to_string(file).with_context(|| format!("reading {}", file.display()))?;
36+
for (i, raw) in contents.lines().enumerate() {
37+
let trimmed = raw.trim_start();
38+
if trimmed.starts_with("(*") {
39+
continue; // whole-line comment (common case)
40+
}
41+
let code = strip_line_comments(raw);
42+
for tok in ESCAPE_TOKENS {
43+
if has_token(&code, tok) {
44+
report.push(warn(
45+
self.name(),
46+
file,
47+
i + 1,
48+
format!("escape primitive `{tok}`"),
49+
));
50+
}
51+
}
52+
}
53+
Ok(())
54+
}
55+
}
56+
57+
fn warn(rule: &str, file: &Path, line: usize, message: String) -> Diagnostic {
58+
Diagnostic {
59+
rule: rule.to_string(),
60+
severity: Severity::Warn,
61+
file: file.to_path_buf(),
62+
message,
63+
line: Some(line),
64+
}
65+
}
66+
67+
/// Remove balanced `(* … *)` spans; an unmatched `(*` truncates the rest of
68+
/// the line.
69+
fn strip_line_comments(line: &str) -> String {
70+
let mut out = String::with_capacity(line.len());
71+
let bytes = line.as_bytes();
72+
let mut i = 0;
73+
let mut depth = 0u32;
74+
while i < bytes.len() {
75+
if i + 1 < bytes.len() && bytes[i] == b'(' && bytes[i + 1] == b'*' {
76+
depth += 1;
77+
i += 2;
78+
continue;
79+
}
80+
if depth > 0 && i + 1 < bytes.len() && bytes[i] == b'*' && bytes[i + 1] == b')' {
81+
depth -= 1;
82+
i += 2;
83+
continue;
84+
}
85+
if depth == 0 {
86+
out.push(bytes[i] as char);
87+
}
88+
i += 1;
89+
}
90+
out
91+
}
92+
93+
/// `tok` appears in `s` as a delimited token (so `admittedly` or `Foo.admit`
94+
/// do not match the bare keyword).
95+
fn has_token(s: &str, tok: &str) -> bool {
96+
s.split(|c: char| c.is_whitespace() || "(){};,.".contains(c))
97+
.any(|w| w == tok)
98+
}
99+
100+
#[cfg(test)]
101+
mod tests {
102+
use super::*;
103+
use crate::lint::run_lints;
104+
use std::path::PathBuf;
105+
106+
fn lint_str(body: &str) -> LintReport {
107+
let tmp = tempfile::NamedTempFile::new().unwrap();
108+
std::fs::write(tmp.path(), body).unwrap();
109+
let roots: [PathBuf; 0] = [];
110+
let ctx = LintContext {
111+
include_root: tmp.path().parent().unwrap(),
112+
entry_modules: &roots,
113+
};
114+
let rules: Vec<Box<dyn LintRule>> = vec![Box::new(CoqEscapeHatch)];
115+
run_lints(tmp.path(), &ctx, &rules).unwrap()
116+
}
117+
118+
#[test]
119+
fn admitted_is_warned() {
120+
let r = lint_str("Theorem t : True.\nProof.\nAdmitted.\n");
121+
assert_eq!(r.warns().count(), 1);
122+
assert_eq!(r.diagnostics[0].rule, "escape-hatch");
123+
assert!(r.diagnostics[0].message.contains("Admitted"));
124+
}
125+
126+
#[test]
127+
fn axiom_and_admit_tactic_are_warned() {
128+
let r = lint_str(
129+
"Axiom em : forall P : Prop, P \\/ ~P.\nTheorem t : True. Proof. admit. Defined.\n",
130+
);
131+
assert_eq!(r.warns().count(), 2);
132+
}
133+
134+
#[test]
135+
fn escape_token_in_comment_is_ignored() {
136+
let r = lint_str("Definition x := 0. (* no Admitted here *)\n");
137+
assert!(r.diagnostics.is_empty());
138+
let r2 = lint_str("(* Axiom foo : bar. *)\n");
139+
assert!(r2.diagnostics.is_empty());
140+
}
141+
142+
#[test]
143+
fn clean_coq_file_is_silent() {
144+
let r = lint_str("Theorem t : True.\nProof.\nexact I.\nQed.\n");
145+
assert!(r.diagnostics.is_empty());
146+
}
147+
148+
#[test]
149+
fn parameter_is_not_flagged_by_the_lint() {
150+
// Parameter is left to the backend's Section-aware classifier.
151+
let r = lint_str("Parameter State : Type.\n");
152+
assert!(r.diagnostics.is_empty());
153+
}
154+
}

src/lint/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::diagnostic::LintReport;
55
use anyhow::Result;
66
use std::path::{Path, PathBuf};
77

8+
pub mod coq;
89
pub mod escape_hatch;
910
pub mod idris2;
1011
pub mod lean;

src/main.rs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use anyhow::{Context, Result};
55
use arghda_core::lint::LintContext;
66
use arghda_core::{
77
build_dag, build_reason, event, groove_manifest, run_lints, unused, watcher, Agda, AgdaCubical,
8-
Backend, BackendKind, Dispatch, Idris2, Lean, LintRule, Probe, RuleConfig, Smt, State, Verdict,
9-
Workspace,
8+
Backend, BackendKind, Coq, Dispatch, Idris2, Lean, LintRule, Probe, RuleConfig, Smt, State,
9+
Verdict, Workspace,
1010
};
1111
use clap::{Parser, Subcommand};
1212
use std::path::{Path, PathBuf};
@@ -17,7 +17,15 @@ use walkdir::WalkDir;
1717
type RuleSet = Vec<Box<dyn LintRule>>;
1818

1919
/// Every backend `--backend` accepts and `doctor` probes.
20-
const KNOWN_BACKENDS: &[&str] = &["agda", "agda-cubical", "idris2", "lean4", "z3", "cvc5"];
20+
const KNOWN_BACKENDS: &[&str] = &[
21+
"agda",
22+
"agda-cubical",
23+
"idris2",
24+
"lean4",
25+
"coq",
26+
"z3",
27+
"cvc5",
28+
];
2129

2230
/// Resolve a `--backend` name to a backend instance. Agda is the default and
2331
/// v0.1 reference; Idris2 is the estate ABI language.
@@ -27,10 +35,11 @@ fn backend_for(name: &str) -> Result<Box<dyn Backend>> {
2735
"agda-cubical" => Ok(Box::new(AgdaCubical)),
2836
"idris2" => Ok(Box::new(Idris2)),
2937
"lean4" => Ok(Box::new(Lean)),
38+
"coq" => Ok(Box::new(Coq)),
3039
"z3" => Ok(Box::new(Smt::z3())),
3140
"cvc5" => Ok(Box::new(Smt::cvc5())),
3241
other => anyhow::bail!(
33-
"unknown backend `{other}` (known: agda, agda-cubical, idris2, lean4, z3, cvc5)"
42+
"unknown backend `{other}` (known: agda, agda-cubical, idris2, lean4, coq, z3, cvc5)"
3443
),
3544
}
3645
}
@@ -39,7 +48,7 @@ fn backend_for(name: &str) -> Result<Box<dyn Backend>> {
3948
#[command(
4049
name = "arghda",
4150
version,
42-
about = "Proof-workspace manager for provers/solvers (Agda, Cubical, Idris2, Lean4, Z3, CVC5)"
51+
about = "Proof-workspace manager for provers/solvers (Agda, Cubical, Idris2, Lean4, Coq, Z3, CVC5)"
4352
)]
4453
struct Cli {
4554
#[command(subcommand)]
@@ -67,7 +76,7 @@ enum Cmd {
6776
#[arg(long)]
6877
config: Option<PathBuf>,
6978
/// Backend: `agda` (default), `agda-cubical`, `idris2`, `lean4`,
70-
/// `z3`, `cvc5`.
79+
/// `coq`, `z3`, `cvc5`.
7180
#[arg(long, default_value = "agda")]
7281
backend: String,
7382
/// Also run the external `agda-unused` analyser and re-emit its
@@ -87,7 +96,7 @@ enum Cmd {
8796
#[arg(long)]
8897
include_root: Option<PathBuf>,
8998
/// Backend: `agda` (default), `agda-cubical`, `idris2`, `lean4`,
90-
/// `z3`, `cvc5`.
99+
/// `coq`, `z3`, `cvc5`.
91100
#[arg(long, default_value = "agda")]
92101
backend: String,
93102
/// Where the check runs: `local` (default) or `echidna[=<url>]`
@@ -115,7 +124,7 @@ enum Cmd {
115124
#[arg(long)]
116125
config: Option<PathBuf>,
117126
/// Backend: `agda` (default), `agda-cubical`, `idris2`, `lean4`,
118-
/// `z3`, `cvc5`.
127+
/// `coq`, `z3`, `cvc5`.
119128
#[arg(long, default_value = "agda")]
120129
backend: String,
121130
},
@@ -139,7 +148,7 @@ enum Cmd {
139148
#[arg(long)]
140149
config: Option<PathBuf>,
141150
/// Backend: `agda` (default), `agda-cubical`, `idris2`, `lean4`,
142-
/// `z3`, `cvc5`.
151+
/// `coq`, `z3`, `cvc5`.
143152
#[arg(long, default_value = "agda")]
144153
backend: String,
145154
/// Run the backend on every node to populate REAL prover verdicts

src/prover/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@ use std::process::Command;
3030
pub mod agda;
3131
pub mod idris2;
3232
pub mod lean;
33+
pub mod rocq;
3334
pub mod smt;
3435

3536
pub use agda::{Agda, AgdaCubical};
3637
pub use idris2::Idris2;
3738
pub use lean::Lean;
39+
pub use rocq::Coq;
3840
pub use smt::Smt;
3941

4042
/// The two backend interaction models.

0 commit comments

Comments
 (0)