Chaperone is an editor for a pandas-like method-chain DSL that gives you live, data-derived feedback as you type, built in the style of Hazel's hole-driven editors. Write a short pipeline like df.groupby("region")["amount_usd"].sum() against a real CSV and Chaperone infers a rich column schema (key, email, currency, categorical, nullability, cardinality) straight from the data. Then it hands you squiggles, a ranked completion inspector, and a live result pane as you type, before anything actually runs.
The core idea is that a hole doesn't just carry a type expectation, it closes over the live DataFrame in scope. Every op has a matched-elimination rule that says what a hole must look like and what comes out the other side; meet is the gradual-typing consistency check underneath all of them, returning the glb of two schema types or null on a squiggle:
export function meet(a: SchemaType, b: SchemaType): SchemaType | null {
if (isUnknown(a)) return b; // ? ~ anything
if (isUnknown(b)) return a;
if (a.kind === "numeric-column" && b.kind === "column")
return isNumericElem(b.elem) ? b : null;
if (b.kind === "numeric-column" && a.kind === "column")
return isNumericElem(a.elem) ? a : null;
if (a.kind === "column" && b.kind === "column")
return meetElem(a.elem, b.elem) ? a : null;
if (a.kind === "column-name" && b.kind === "column-name") return a;
if (a.kind === "frame" && b.kind === "frame") return a;
return a.kind === b.kind ? a : null;
}
Because the type walk and the value walk mirror each other over the same parsed program, swapping the underlying CSV re-runs both at once: a drift pass diffs schema A against schema B and names exactly which op's check flips from pass to fail, while the result pane shows the numbers actually moving. A hole with no column picked yet is still hole-tolerant: aggregations show a ⬚ gap that fills in the instant you choose one.