Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
d273312
Add Hazel CLI for AI-assisted development
disconcision Jan 12, 2026
faa3fe6
Add CLI test command and study program infrastructure
disconcision Jan 12, 2026
0e56982
Add study programs: tic-tac-toe, game of life, calculator
disconcision Jan 12, 2026
cd4c9bc
tamagotchi sample program
disconcision Jan 12, 2026
202ef47
fmt
disconcision Jan 12, 2026
1db11ce
Merge branch 'probemoar' of github.com:hazelgrove/hazel into hazel-ls…
disconcision Jan 12, 2026
037f41e
adjust hazel agent docs
disconcision Jan 12, 2026
f5a4320
docs hazel program extraction script fix
disconcision Jan 12, 2026
a5f53d6
Merge branch 'dev' into hazel-lsp-cli
disconcision Jan 16, 2026
cce5e98
Merge branch 'dev' of github.com:hazelgrove/hazel into hazel-lsp-cli
disconcision Jan 17, 2026
e68ec22
Merge branch 'hazel-lsp-cli' of github.com:hazelgrove/hazel into haze…
disconcision Jan 17, 2026
a32d956
fmt
disconcision Jan 17, 2026
c2c3f96
Merge branch 'probe-III' of github.com:hazelgrove/hazel into hazel-ls…
disconcision Jan 21, 2026
0ab8a8e
Add CLI --auto flag for auto-probe and study-write task examples
disconcision Jan 27, 2026
a727b2d
Use Unicode brackets ⟦⟧ for CLI probe output and fix line placement
disconcision Jan 27, 2026
3f69e18
Fix CLI probe output to preserve source file indentation
disconcision Jan 27, 2026
c8102ad
Add probes tutorial draft (Part 1: basics through closure cursor)
disconcision Jan 28, 2026
9152ffd
Add new study-write example programs and task documentation
disconcision Jan 28, 2026
8372885
Add CLI commands for generating documentation slides from .hz files
disconcision Jan 28, 2026
9680fab
add study generated mls for now
disconcision Jan 28, 2026
f78fada
tutorial edits
disconcision Jan 28, 2026
13159be
temporarily disable reparse tests as too slow for action timeout
disconcision Jan 28, 2026
0b19b90
Merge dev into hazel-lsp-cli, resolve refractor list conflicts
disconcision Jan 29, 2026
9d40626
Merge branch 'dev' into hazel-lsp-cli
disconcision Feb 2, 2026
bb05800
Merge branch 'dev' into hazel-lsp-cli
disconcision Feb 3, 2026
322c148
Merge branch 'dev' into hazel-lsp-cli
disconcision Feb 3, 2026
bdc8cf9
Fix stack overflow on large programs in browser
disconcision Feb 4, 2026
8f064a4
new candidate study programs
disconcision Feb 4, 2026
12740d8
reorganize study directory. rebuild candidate study task slides
disconcision Feb 4, 2026
fd4a4f5
Filter redundant environment variables from probe call display
disconcision Feb 4, 2026
6959125
Merge branch 'probe-III' into hazel-lsp-cli
disconcision Feb 4, 2026
20cc72f
Merge branch 'probe-III' into hazel-lsp-cli
disconcision Feb 4, 2026
69675da
study cleanup
disconcision Feb 4, 2026
483ffeb
Merge branch 'probe-III' into hazel-lsp-cli
disconcision Feb 5, 2026
500615b
Merge branch 'probe-III' into hazel-lsp-cli
disconcision Feb 5, 2026
5202d33
Merge branch 'probe-III' into hazel-lsp-cli
disconcision Feb 9, 2026
e407b79
Merge branch 'hazel-lsp-cli' of github.com:hazelgrove/hazel into haze…
disconcision Feb 10, 2026
a6f844f
Cache original doc segments to fix autosave performance
disconcision Feb 10, 2026
309c21f
Fix sample cursor regression: stack_frame tuple→record, id-only equality
disconcision Feb 11, 2026
f7ce5d6
Add sample selection tests (unit + integration)
disconcision Feb 11, 2026
2396c55
fmt
disconcision Feb 11, 2026
f9cd282
Fix closure cursor bar: jump-to-definition and built-in fallback
disconcision Feb 11, 2026
45999e1
Always set dynamic cursor index on closure cursor bar clicks
disconcision Feb 11, 2026
cdf569e
Redesign closure cursor bar: names jump to call sites, add body icon
disconcision Feb 11, 2026
26eadd4
Pin filter preserves breadcrumb probes on ancestral call chain
disconcision Feb 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Hazel Project Guide

Hazel is a live functional programming environment with typed holes and live evaluation.

## Build & Test

### Building
```bash
dune build
```

### Running Tests

**Full test suite (slow, ~4 minutes):**
```bash
dune runtest
dune build @runtest --force # Force rebuild
```

**Running specific tests (fast, ~0.1s):**

```bash
# Build first
dune build

# Run by test group name (regex)
node _build/default/test/haz3ltest.bc.js test 'ProbeSteps'
node _build/default/test/haz3ltest.bc.js test 'Evaluator'

# Run specific test number(s) within a group
node _build/default/test/haz3ltest.bc.js test 'ProbeSteps' '3'
node _build/default/test/haz3ltest.bc.js test 'ProbeSteps' '0..3'
node _build/default/test/haz3ltest.bc.js test 'ProbeSteps' '0,2,5'

# Show errors inline
node _build/default/test/haz3ltest.bc.js test 'ProbeSteps' --show-errors

# List all available tests
node _build/default/test/haz3ltest.bc.js list
```

**Test output locations:**
- Logs: `_build/_tests/HazelTests/<TestGroup>.<number>.output`
- Check failures: `grep -l "FAIL" _build/_tests/HazelTests/*.output`

### Test Framework
Uses [Alcotest](https://github.com/mirage/alcotest) with js_of_ocaml. Tests in `test/` use `` `Quick `` or `` `Slow `` annotations.

## Code Style

- **Language:** ReasonML (`.re` files) compiled with js_of_ocaml
- **Comments:** `/* ... */` style

## Key Directories

```
src/
├── language/ # Core language implementation
│ ├── dynamics/ # Evaluation, dynamic semantics
│ │ ├── transition/ # Step-by-step evaluation
│ │ └── Sample.re # Probe sample types and utilities
│ └── statics/ # Type checking, elaboration
├── haz3lcore/ # Core utilities, zipper, projectors
│ ├── projectors/ # Projector implementations (probes, sliders, etc.)
│ ├── zipper/ # Zipper data structure for editing
│ └── Refractors.re # Manual/ephemeral probe management
├── CLI/ # Command-line interface
│ ├── Cli.re # Main CLI entry point and commands
│ ├── Run.re # Evaluation helpers
│ └── Print.re # Output formatting
├── web/ # Web UI (not relevant for CLI work)
└── util/ # Shared utilities

test/
├── evaluator/ # Evaluator tests
└── haz3ltest.re # Test runner entry point

hazel-programs/ # Sample Hazel programs (.hz files)
├── docs/ # Documentation examples (extracted from src/web/init/docs/)
├── b2t2/ # B2T2 slide examples (extracted from src/b2t2/slides/)
└── study/ # User study programs and documentation
├── debugging/ # Programs with intentional bugs for debugging tasks
└── writing/ # Sketch/solution pairs for program writing tasks

plans/ # Project plans and documentation
├── hazel-lsp-cli/ # CLI/LSP development plan
│ ├── README.md # Main plan and TODOs
│ ├── cli-api.md # CLI documentation for AI agents
│ ├── hazel-primer.md # Hazel syntax reference
│ └── experience-log.md # AI model experience reports
└── [other plans]/ # Other in-progress plans

scripts/
└── extract-docs.py # Extract .hz programs from ML backup_text fields
```

## Hazel CLI

Run Hazel programs from the command line:

```bash
./hazel run program.hz # Execute and print result
./hazel analyze program.hz # Type check (errors with line numbers)
./hazel format program.hz # Normalize code
./hazel probe program.hz # Run with probe values inline
./hazel probe --many program.hz # Show multiple probe samples
```

Use `-` to read from stdin: `echo 'let x = 5 in x + 1' | ./hazel run -`

See `plans/hazel-lsp-cli/cli-api.md` for detailed CLI documentation.

**Known CLI limitations:**
- No JSON output mode yet
- No combined analysis command (must run analyze + probe separately)

**Important Hazel notes:**
- Hazel has NO `let rec` keyword. Recursion works automatically with arrow type annotations.
- Use `?` for type holes instead of type variables like `'a`
- Probes: `^^probe(expr)` syntax shows runtime values

## AI Development Workflow

When developing Hazel programs as an AI model:

1. **Write code** in `.hz` files or use stdin with `./hazel run -`
2. **Check types** with `./hazel analyze` - errors are human-readable
3. **Debug** with `^^probe(expr)` and `./hazel probe --many`
4. **Reference** `plans/hazel-lsp-cli/hazel-primer.md` for syntax

**Experience logging:** Document issues, suggestions, and learnings in `plans/hazel-lsp-cli/experience-log.md` to help improve the tooling.

## Current Work

See `plans/` directory for in-progress project plans.

### Active: Hazel LSP/CLI for AI Development
Building out CLI tools for AI-assisted Hazel development.
See `plans/hazel-lsp-cli/README.md` for the full plan.
75 changes: 75 additions & 0 deletions hazel-programs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Hazel Programs

This directory contains Hazel programs in plain text (`.hz` files) for various purposes.

## Directory Structure

```
hazel-programs/
├── docs/ # Programs from Hazel documentation slides (9 files)
├── b2t2/ # Programs from B2T2 table API slides (39 files)
│ ├── errors/
│ ├── example_programs/
│ └── table_api/
└── study/ # Programs and docs for user studies (probe mechanism evaluation)
├── debugging/ # Programs with intentional bugs for debugging tasks
└── writing/ # Sketch/solution pairs for program writing tasks
```

## File Format

Hazel programs use the `.hz` extension and contain plain text Hazel syntax.

```hazel
# Example: recursive function (requires arrow type annotation)
let length : [Int] -> Int =
fun xs ->
case xs
| [] => 0
| hd::tl => 1 + length(tl)
end
in length([1, 2, 3])
```

**Note**: Hazel has no `let rec` keyword. Recursion works automatically with arrow type annotations.

## Running Programs

```bash
# From repository root:
./hazel run hazel-programs/study/basic-functions.hz

# Or from stdin:
cat hazel-programs/docs/BasicReference.hz | ./hazel run -

# Check for type errors:
./hazel analyze hazel-programs/docs/BasicReference.hz
```

## Regenerating Extracted Programs

Programs in `docs/` and `b2t2/` are extracted from ML source files. To regenerate after source changes:

```bash
python3 scripts/extract-docs.py
```

See `scripts/README.md` for details.

## Directory Details

### `docs/`
Programs from the main Hazel documentation slides (`src/web/init/docs/`). Includes:
- BasicReference.hz - Language quick reference
- ADTs.hz - Algebraic data types example
- Probes.hz - Probe projector documentation
- And more...

### `b2t2/`
Programs from B2T2 (Bootstrap to Table Types) slides (`src/b2t2/slides/`). Examples of table processing in Hazel.

### `study/`
Programs and documentation for user studies evaluating Hazel's probe debugging mechanism.
- `debugging/` - Programs with intentional bugs for debugging tasks
- `writing/` - Sketch/solution pairs for program writing tasks
- Planning docs, tutorials, and guides for the study
92 changes: 92 additions & 0 deletions hazel-programs/b2t2/B2T2ExampleTables.hz
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Example Tables from B2T2 #
# Tables copied from https://github.com/brownplt/B2T2/blob/main/ExampleTables.md #
type OptInt = +None +Some(Int) in
type OptString = +None +Some(String) in
type Student = (name=String, age=Int, favorite_color=String) in
let students : [Student] = [
("Bob", 12, "blue"),
^^probe(("Alice", 17, "green")),
("Eve", 13, "red")
] in

type StudentMissing = (name=String, age=OptInt, favorite_color=OptString) in
let studentsMissing : [StudentMissing] = [
("Bob", None, Some("blue")),
("Alice", Some(17), Some("green")),
^^probe(("Eve", Some(13), None))
] in

type Employee = (last_name=String, department_id=OptInt) in
let employees : [Employee] = [
("Rafferty", Some(31)),
("Jones", Some(33)),
("Heisenberg", Some(33)),
("Robinson", Some(34)),
("Smith", Some(34)),
("Williams", None)
] in

type Department = (department_id=Int, department_name=String) in
let departments : [Department] = [
(31, "Sales"),
(33, "Engineering"),
(34, "Clerical"),
(35, "Marketing")
] in

type JellyAnon = (get_acne=Bool, red=Bool, black=Bool, white=Bool, green=Bool, yellow=Bool, brown=Bool, orange=Bool, pink=Bool, purple=Bool) in
let jellyAnon : [JellyAnon] = [
(^^check(true), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(false), ^^check(false), ^^check(true), ^^check(false), ^^check(false)),
(^^check(true), ^^check(false), ^^check(true), ^^check(false), ^^check(true), ^^check(true), ^^check(false), ^^check(false), ^^check(false), ^^check(false)),
(^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(false)),
(^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(false), ^^check(false), ^^check(false), ^^check(false)),
(^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(false), ^^check(false), ^^check(true), ^^check(false)),
(^^check(true), ^^check(false), ^^check(true), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(true), ^^check(false)),
(^^check(false), ^^check(false), ^^check(true), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(false)),
(^^check(true), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(true), ^^check(false), ^^check(false)),
(^^check(true), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(false), ^^check(false)),
(^^check(false), ^^check(true), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(true), ^^check(false), ^^check(true), ^^check(false))
] in

type JellyNamed = (name=String, get_acne=Bool, red=Bool, black=Bool, white=Bool, green=Bool, yellow=Bool, brown=Bool, orange=Bool, pink=Bool, purple=Bool) in
let jellyNamed : [JellyNamed] = [
("Emily", ^^check(true), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(false), ^^check(false), ^^check(true), ^^check(false), ^^check(false)),
("Jacob", ^^check(true), ^^check(false), ^^check(true), ^^check(false), ^^check(true), ^^check(true), ^^check(false), ^^check(false), ^^check(false), ^^check(false)),
("Emma", ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(false)),
("Aidan", ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(false), ^^check(false), ^^check(false), ^^check(false)),
("Madison", ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(false), ^^check(false), ^^check(true), ^^check(false)),
("Ethan", ^^check(true), ^^check(false), ^^check(true), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(true), ^^check(false)),
("Hannah", ^^check(false), ^^check(false), ^^check(true), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(false)),
("Matthew", ^^check(true), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(true), ^^check(false), ^^check(false)),
("Hailey", ^^check(true), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(false), ^^check(false)),
("Nicholas", ^^check(false), ^^check(true), ^^check(false), ^^check(false), ^^check(false), ^^check(true), ^^check(true), ^^check(false), ^^check(true), ^^check(false))

] in

type GradebookEntry = (name=String, age=Int, quiz1=Int, quiz2=Int, midterm=Int, quiz3=Int, quiz4=Int, final=Int) in
let gradebook : [GradebookEntry] = [
("Bob", 12, 8, 9, 77, 7, 9, 87),
("Alice", 17, 6, 8, 88, 8, 7, 85),
("Eve", 13, 7, 9, 84, 8, 8, 77)
] in

type GradebookEntryMissing = (name=String, age=Int, quiz1=OptInt, quiz2=OptInt, midterm=Int, quiz3=OptInt, quiz4=Int, final=Int) in
let gradebookMissing : [GradebookEntryMissing] = [
("Bob", 12, Some(8), Some(9), 77, Some(7), 9, 87),
("Alice", 17, Some(6), Some(8), 88, None, 7, 85),
("Eve", 13, None, Some(9), 84, Some(8), 8, 77)
] in

type GradebookEntrySeq = (name=String, age=Int, quizzes=[Int], midterm=Int, final=Int)in
let gradebookSeq : [GradebookEntrySeq] = [
("Bob", 12, [8, 9, 7, 9], 77, 87),
("Alice", 17, [6, 8, 8, 7], 88, 85),
("Eve", 13, [7, 9, 8, 8], 84, 77)
] in

type GradebookEntryTable = (name=String, age=Int, quizzes=[(quizNo=Int, grade=Int)], midterm=Int, final=Int) in
let gradebookTable : [GradebookEntryTable] = [
("Bob", 12, [(1, 8), (2, 9), (3, 7), (4, 9)], 77, 87),
("Alice", 17, [(1, 6), (2, 8), (3, 8), (4, 7)], 88, 85),
("Eve", 13, [(1, 7), (2, 9), (3, 8), (4, 8)], 84, 77)
] in
44 changes: 44 additions & 0 deletions hazel-programs/b2t2/errors/B2T2ErrorsMalformedTables.hz
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# No type correlates to missing schema. There is no error as it's valid data without labels#
let missingSchema = [
("Bob", 12, "blue"),
("Alice", 17, "green"),
("Eve", 13, "red")
] in

type Schema = [(name=String, age=Int, `favorite color`=String)] in

# The missing row is represented as a hole. #
let missingRow : Schema=
[
("Bob", 12, "blue"),
("Alice", 17, "green"),
?
] in

# The missing cell is represented as a hole#
let missingCell : Schema = [
("Bob", 12, ?),
("Alice", 17, "green"),
("Eve", 13, "red")
] in

# The age/name columns are swapped. The error is added to every relevant cell and says which column it's for as well as the type error #
let swappedColumns : Schema = [
(12, "Bob", "blue"),
(17, "Alice","green"),
(13, "Eve", "red")
] in

# Error marks are added onto the tuples. The last row uses explicit labels which makes the inconsistency easier to identify by marking the unknown label.#
let schemaTooShort : [(name=String, age=Int)] = [
("Bob", 12, "blue"),
("Alice", 17, "green"),
(name="Eve", age=13, `favorite color`="red")
] in

# Error marks are added onto the tuples showing the inconsistency. The last row uses explicit labels which makes the inconsistency easier to identity #
let schemaTooLong : [(name=String, age=Int, `favorite number`=Int, `favorite color`=String)] = [
("Bob", 12, "blue"),
("Alice", 17, "green"),
(name="Eve", age=13, `favorite color`="red")
] in ?
Loading