Rust SDK
Embedding the lawlint engine — lawlint-core, LintOptions, and the WebAssembly build.
lawlint-core is the engine the CLI, the desktop app, and the
playground all run on. It is pure Rust, and performs no file or
stdin I/O of its own — you hand it a string.
use lawlint_core::{lint, LintOptions};
let result = lint(
"It is important to note that we delve into the landscape.",
&LintOptions::default(),
);
println!("score: {}", result.stats.score);
for diagnostic in &result.diagnostics {
println!("{}: {}", diagnostic.rule_id, diagnostic.message);
}
Add it as a path or git dependency on this repository:
[dependencies]
lawlint-core = { git = "https://github.com/sethtjf/lawlint" }
LintOptions
enable?Option<Vec<String>>
Run only these rules.
Option<Vec<String>>disable?Option<Vec<String>>
Run every rule except these.
Option<Vec<String>>severity?Option<HashMap<String, Severity>>
Per-rule severity overrides.
Option<HashMap<String, Severity>>thresholds?Option<HashMap<String, f64>>
Per-rule threshold overrides.
Option<HashMap<String, f64>>markdown?Option<bool>
Strip fenced code blocks before linting.
Option<bool>rule_dirs?Option<Vec<String>>
Consumed by the CLI and desktop app. Ignored by core `lint()`.
Option<Vec<String>>judge?Option<JudgeOptions>
Soft-rule judging. Requires a judge implementation.
Option<JudgeOptions>ai?Option<AiOptions>
Model preferences written by `lawlint init`. Consumed by the CLI and desktop app; ignored by core `lint()`.
Option<AiOptions>let result = lint(
text,
&LintOptions {
disable: Some(vec!["no-legalese".into()]),
..Default::default()
},
);
Two fields are carried on LintOptions purely so one deserialized config
covers every consumer: rule_dirs and ai are read by the CLI and desktop app,
and ignored by lint().
LintResult
pub struct LintResult {
pub diagnostics: Vec<Diagnostic>,
pub stats: Stats, // word_count, sentence_count, score
pub judge: Option<JudgeStats>,
}
The serde representation is documented field by field in JSON output — the same types, camelCased.
Rule packages
use lawlint_core::{lint_with, loader, RuleSet};
let mut rules = RuleSet::built_in();
loader::load_dir(&mut rules, "./house-style")?;
let result = lint_with(text, &rules, &LintOptions::default());
lint() is lint_with() over the built-in set. Loader errors are written to be
shown to a user verbatim — propagate them rather than rewording.
Soft rules and the judge
Core is inference-agnostic: it plans requests, grounds the findings that come
back, and never talks to a model. Supply a backend by implementing Judge.
pub trait Judge: Send + Sync {
fn evaluate(&self, req: &JudgeRequest) -> Result<Vec<JudgeFinding>, JudgeError>;
fn model_id(&self) -> &str;
/// Requests this backend can serve at once. Default 1.
fn max_concurrency(&self) -> usize { 1 }
}
lint_full plans, runs, and grounds in one call. To drive the stages yourself
— the wasm playground does, since it cannot make network calls from core —
use plan_judge_with and run_judge:
use lawlint_core::{plan_judge_with, run_judge, JudgePlan};
let plan = JudgePlan { context_chars: 24_000, per_rule: true };
let requests = plan_judge_with(&doc, text, &rubrics, &plan);
let (grounded, stats) = run_judge(&judge, cache, &requests, text);
context_chars?usize
Document text per request. Also the cache granule — an edit anywhere in a unit re-runs it.
usizeper_rule?bool
One request per rule instead of one per unit carrying every rubric.
boolJudgePlan::default() is the conservative small-model shape (1,200 chars,
rubrics bundled). JudgePlan::for_context(n) derives a profile from a context
budget. Mapping a model spec to a profile is lawlint-judge’s job
(plan_for_model), not core’s.
Requests run up to max_concurrency() at a time, and a backend that panics
fails only its own chunk. Ready-made backends — Anthropic, any
OpenAI-compatible endpoint, and Azure Foundry — live in lawlint-judge. All
of them are HTTP clients; lawlint runs no inference in-process.
Applying fixes
use lawlint_core::{apply_fixes, Applicability};
let fixed = apply_fixes(text, &result.diagnostics, Applicability::MachineApplicable);
.docx tracked-change writing lives in lawlint-docx, not in core — core deals
in strings.
WebAssembly
lawlint-wasm compiles the same engine for the browser. The
playground runs it client-side, so text typed there never leaves
the page.