alix 0.1.0

Your personal AI tutor — built for understanding, not just remembering.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
# `alix`

Your **personal AI tutor**, built for understanding — not just remembering.
Under the hood it's a fast, plain-text spaced-repetition trainer — Leitner and
SM-2 scheduling, several answer modes, cloze and dual-direction cards, images,
and deck dependencies — and what sets it apart is the **Claude integration**: an
*ask-Claude tutor* on any card, *AI deck generation* from a web page,
*understanding cards*, and an **AI exam** (`alix exam`) that verifies you
actually grasped the material and gates your progress on passing it — so you're
not just memorizing, you're being checked. Decks stay simple plain-text files
you own, reviewable in a ratatui terminal UI or a local web app.

## Requirements

The flashcard **core** — review, scheduling, every answer mode, browse, the TUI
and the web frontend — runs standalone, with no external services or accounts.

The **AI features** shell out to the [Claude Code](https://www.anthropic.com/claude-code)
CLI, so they need it **installed and logged in** (which in turn needs a Claude
subscription or API access). Install the CLI and run `claude` once to
authenticate. The features that require it:

- `alix deck` — generate a facts deck from a URL or a local file/directory;
- `alix exam` — the AI exam;
- `alix trace --build` / `--suggest` / `--grade` — discover, suggest, and grade
  traces;
- `alix explore` — goal-driven learning plans;
- ask-Claude (`?`) — the in-session tutor, in both the TUI and the web frontend.

`alix` invokes the CLI headless (`claude -p`) under a locked-down permission
model (see [Ask Claude](#ask-claude-about-a-card)); the command, model and
timeouts are configurable per feature in the [config file](#configuration).

## Learn a codebase (the main workflow)

`alix`'s flagship use: point it at a repo (or any source) and it builds a
self-contained **learning workspace** — facts decks and predict-and-verify
[traces](#traces-alix-trace), dependency-ordered — that you then study with
spaced repetition, the AI tutor, and the [exam](#the-ai-exam-alix-exam). Three
steps:

```sh
# 1. Preview the plan (read-only): the decks and traces worth authoring.
alix explore ./my-crate --goal "how the request pipeline works"

# 2. Build the workspace in one pass: stubs filled, sources frozen into assets/.
alix explore ./my-crate --goal "how the request pipeline works" \
  --into ~/decks/request-pipeline --title "Request pipeline" --build

# 3. Study it — in the terminal, or the browser with --serve.
alix workspace ~/decks/request-pipeline    # or: alix --serve, then open it
```

`--goal` scopes what gets authored (and becomes the workspace's description),
`--title` names it, and `--build` fills every facts deck and trace in one
coherent pass — freezing the cited source into the
workspace so its line locators never drift. Inside the workspace a **facts deck reviews** and a
**trace walks** (predict → reveal → judge the gap), unlocking in dependency
order, with progress kept in the workspace's own store. See
[Exploring a source](#exploring-a-source--alix-explore) and
[Workspaces](#workspaces) for the full details. (The AI steps need the Claude
CLI — see [Requirements](#requirements).)

## Usage

The binary is called `alix`:

```sh
alix                            # pick decks interactively (recent + ~/decks)
alix mydeck.txt                 # review due cards (flip mode, Leitner)
alix --mode typing mydeck.txt   # type the answer character by character
alix --mode fuzzy mydeck.txt    # whole-line input, small typos tolerated
alix --mode choice mydeck.txt   # multiple choice (distractors from the deck)
alix --mode line mydeck.txt     # reveal the answer one line at a time (lyrics)
alix --scheduler sm2 mydeck.txt # SM-2 intervals instead of Leitner
alix --cram mydeck.txt          # ignore cooldowns, review everything
alix browse mydeck.txt          # read through cards, no grading or scheduling
alix deck <url-or-path>         # generate a facts deck from a web page or a file/dir
alix import cards.tsv            # import an Anki TSV (front<TAB>back) into a deck
alix exam mydeck.txt            # AI exam against the deck's % source: (gates unlocks)
alix trace mytrace.txt          # walk a predict-and-verify path through a % source:
alix trace --build mytrace.txt  # let Claude discover the path (writes checkpoints back)
alix trace --suggest .          # recon a source for candidate traces worth authoring
alix explore .                  # an ordered learning plan (decks + traces) toward a goal
alix explore --walk .           # walk an explore tour of the source's shape
alix deps mydeck.txt            # edit a deck's prerequisites (checkbox picker)
alix stats mydeck.txt           # progress overview
alix list mydeck.txt            # every card with stage and due time
alix check mydeck.txt           # lint a deck (syntax, duplicates, trace locators)
alix reset mydeck.txt           # clear stored progress (also --card / --all)
```

Several decks can be given at once; their due cards are merged into one
session. Useful flags for `review`: `--new N` (max unseen cards to introduce,
default 10), `--limit N` (cap session size), `--max-typos N` (fuzzy tolerance
per line, default 2).

Run `alix` with no deck arguments (as the desktop launcher does) to open the
**deck picker**, grouped into three sections: **[Workspaces](#workspaces)**
(each showing when it last made progress) · **Recent** (loose decks you reviewed
lately) · **Folders** (plain decks folders). A deck that lives inside a workspace
stays out of Recent — you reach it by opening its workspace. Mastered/done and
locked decks are also kept out of Recent (it's a quick launchpad) but stay
reachable by filtering. Decks live in the decks directory (`~/decks` by default,
set `decks_dir` in the config). The focus is on the **list** by default, with
Vim-style keys (rebindable in the config's `[picker]` section): `j`/`k` (or
`↑`/`↓`) move, `l` (or `Enter`) opens the focused row, `h` (or `Esc`/`Backspace`)
steps back, `m` opens the **Mastered** window (your completed decks, kept out of
Recent), and `/` (or `Ctrl-F`) starts **filtering** by name (searching *every*
loose deck, not just the recent ones); `Esc` leaves the filter. Jumping to the
first/last row stays fixed at `g`/`G` (or Home/End), like the `[browse]` pager.
A deck you
can't start right
now is dimmed and `Enter` on it does nothing: 🔒 a
[locked](#completion-states--unlocks) deck (an unfinished `% requires:`), 🕒 a deck
with nothing due (all on cooldown — `--cram` reviews it anyway); a mastered deck
reads `mastered 🎉`. `Enter` on a **Workspace** or **Folder** opens it (drills in) — `Esc`
or `Backspace` steps back out to the list, all within one screen; a
[trace](#traces-alix-trace) opened from the picker **walks** instead of being
reviewed as cards. When you finish a deck/walk/exam launched from a workspace, you
land **back in that workspace** to pick the next one.

## Deck format

The deck format:

```
% This line is a comment.

# What is the front of the card? (this is the front)
    The answer that must be typed. (back, may span several lines)
    A second answer line.
    ! An optional note, shown after answering.
    ! Further !-lines extend the note (multi-line notes).

# Next card
    \# A back line starting with a markup char is escaped with a backslash.
```

A note is shown as a quoted block: each sentence on its own line. A
` ```fenced``` ` block inside a note is rendered verbatim instead — its
indentation is preserved and it is not reflowed, so code stays readable.

Lines are trimmed, so indentation never has to be typed. A `#` only starts
a new card at column 0 — indented `#` lines are answer content (shell
comments, Rust attributes, Dockerfile comments...), no escaping needed.
Notes (`!`) and comments (`%`) work at any indentation.

### Directives at a glance

Every card marker and `% key: value` directive in one place. **Scope** is where
each may appear — *deck* = the header (before the first card), *card* = after a
card's front. Follow a link for the full explanation.

| Token | Scope | Meaning |
| --- | --- | --- |
| `#` front | card | Starts a card at column 0; the indented lines below are the answer. |
| `#?` front | card | [Cloze card]#cloze-cards-fill-in-the-blank — blanks are `{{spans}}` in the answer line. |
| `!` line | card | A note shown after you answer. |
| `%` line | anywhere | A comment — ignored, unless it is one of the directives below. |
| `% mode:` | deck · card | [Answer mode]#deck-directives: `flip`, `typing`, `fuzzy`, `choice`, `line`, `explain`. |
| `% order:` | deck | [Card order]#deck-directives: `scheduled` (default) or `sequential`. |
| `% scheduler:` | deck | [Scheduler]#deck-directives: `leitner` (default) or `sm2`. |
| `% direction:` | deck · card | [Review direction]#dual-direction-cards--direction: `forward`, `reverse`, or `both`. |
| `% unlock-stage:` | deck | [Stage that opens the gate]#deck-directives `1`–`5`: the exam/unlock fires once every card reaches it (cards keep drilling, no early retirement). |
| `% frontend:` | deck · card | [Restrict]#deck-directives a card/deck to `any`, `tui`, or `web`. |
| `% img:` / `% img-back:` | card | [Image]#images--img--img-back on the front / back (web frontend). |
| `% img-dir:` | deck | [Base directory]#images--img--img-back that image filenames resolve against. |
| `% strictness:` | deck | [Exam grading rigor]#the-ai-exam-alix-exam: `strict`, `balanced`, or `lenient`. |
| `% requires:` | deck | [Prerequisite deck]#deck-dependencies that gates unlocks (repeatable). |
| `% link:` | deck | [ask-Claude reference]#ask-claude-about-a-card URL — **tutor only** (repeatable). |
| `% source:` | deck | [Exam ground truth]#the-ai-exam-alix-exam — a URL or file (repeatable). For a [trace]#traces-alix-trace, the source the path runs through (the frozen `assets/` copy in an explored workspace). Also a tutor reference. |
| `% trace:` | deck | What a [trace]#traces-alix-trace walks — a path description ("how X becomes Y"); its presence makes the deck a trace. |
| `% at:` | card | A locator into the `% source:` (`file:lines`, or just `lines` for a single-file source): a [trace checkpoint's]#traces-alix-trace reveal target, or a [fact card's source citation]#source-citations--at-on-a-fact-card shown on reveal. |
| `% given:` | card | A [trace checkpoint's]#traces-alix-trace "given" (repeatable) — an off-screen symbol the question leans on, as `name — meaning`; shown as a list under the question. |
| `% title:` | deck | [Display name]#workspaces shown instead of the file name (a workspace sets `title` in its `alix.toml`). |

**`% link:` vs `% source:`** — both point at the material a deck is about, but
they are not interchangeable. `% source:` is the **exam's ground truth**:
questions are generated from it and answers graded against it, and a URL source
*also* doubles as an ask-Claude reference. `% link:` is **only** a tutor
reference and never becomes exam material — use it for supplementary reading (a
blog post, a Stack Overflow answer) you don't want the exam to test. The
implication runs one way: a `% source:` URL is offered to the tutor, but a
`% link:` is never promoted to an exam source.

### Deck directives

A deck can set its own defaults with `% key: value` comment lines in the deck
header (before the first card), so you do not have to repeat flags on the
command line:

```
% mode: line
% order: sequential
% scheduler: sm2
```

- `mode` — default answer mode (`flip`, `typing`, `fuzzy`, `choice`, `line`,
  `explain`);
  can also be overridden per card (see below).
- `order` — `scheduled` (the default) or `sequential` to walk the deck in
  file order, top to bottom (ideal for lyrics with `% mode: line`).
- `scheduler` — `leitner` (default) or `sm2`.
- `direction` — `forward` (default), `reverse`, or `both`; per card or deck-wide
  (see below).
- `frontend` — `any` (default), `tui`, or `web`; restricts a card (or deck) to a
  frontend. Image cards are `web` automatically (see Images below).
- `img-dir` — directory that card `% img:` / `% img-back:` filenames resolve
  against (deck header only; see Images below).
- `unlock-stage` — the Leitner stage (`1`–`5`) every card must reach for the deck
  to **unlock**: a `% source:` deck becomes *exam due* (its exam opens), a
  source-less one becomes *finished* (its dependents unlock). The cards are
  **not** retired — they keep drilling to the top stage; the directive only lowers
  the unlock bar. Default: the deck unlocks only when every card retires at the
  top stage.
- `strictness` — how strictly the AI exam grades answers (`strict`, `balanced`,
  `lenient`); only affects `alix exam` (see [the AI exam]#the-ai-exam-alix-exam).

These are ordinary `%` comments, so they don't affect parsing and card hashes
are unaffected. An explicit CLI flag always wins over a directive, which wins over
the built-in default. Directives are read only from the deck(s) you ask to
review — never from prerequisites pulled in via `% requires:` (see below). When
several requested decks disagree on a setting, the default is used. `alix check
<deck>` prints a deck's directives.

**Per-card mode.** A `% mode:` directive placed *after* a card's front (and
before the next one) overrides the deck's mode for that card only, so one deck
can mix modes — e.g. a `line` lyrics card among `flip` cards. The effective mode
is resolved per card: CLI `--mode` > the card's `% mode:` > the deck's
`% mode:` > the default (so `--mode` still forces every card). Other directives
(`order`, `scheduler`) stay deck-level.

### Deck dependencies

A deck can declare prerequisite decks with `% requires:` lines (repeatable):

```
% requires: rust-basics
% requires: rust-ownership
```

When you review a deck, its prerequisites are pulled in automatically —
transitively, de-duplicated — and the session is ordered **foundations
first**: a prerequisite deck's cards (both due reviews and newly introduced
ones) come before the cards of the deck that depends on it, while normal
scheduler order is kept within each deck. A prerequisite name resolves next to
the requiring deck or in the decks directory, with or without `.txt`. Missing
prerequisites and dependency cycles are reported as errors. They are ordinary
`%` comments, so hashes are unaffected.

A prerequisite contributes only its **cards** to the session, not its
directives: the `mode`, `order` and `scheduler` come from the deck you asked to
review, so requiring a `% mode: line` lyrics deck as a prerequisite won't switch
your session into line mode. Dependencies apply to `review` only — `browse` and
`stats` operate on exactly the decks you name.

You can edit a deck's prerequisites without hand-typing (and without typos)
with `alix deps <deck>` (alias `alix require`): it opens the deck picker over your
decks directory, pre-ticked to the current prerequisites. `Space` toggles,
`Enter` saves (rewriting the `% requires:` lines), `Esc` cancels; unticking
everything clears them. Since the lines are comments, editing dependencies
never affects card progress.

### Completion states & unlocks

Every deck has a **completion state**, derived from its cards' stages:
*not started* (no card reviewed), *finished* (every card at the top stage), or
*started* (in between). The deck picker (terminal and web) shows it on each row
— `new`, `m/total` (cards at the top stage), or `done ✓` — and `alix stats`
prints it too. A deck that declares a `% source:` adds one more state between
drilled and finished — *exam due* (`exam due`, tinted) — because drilling alone
no longer finishes it; see [the AI exam](#the-ai-exam-alix-exam).

Completion drives **unlocks**, with no extra syntax: a deck is **locked** while
any of its `% requires:` prerequisites isn't finished, so finishing a foundation
unlocks the decks that build on it. Locked decks are shown dimmed with a 🔒, but
the lock is **advisory** — you can still pick one (its prerequisite cards are
pulled in foundations-first anyway). State and locks are recomputed live, so if a
finished deck later lapses below the top stage, its dependents lock again.

### Workspaces

A **workspace** is a folder of decks reviewed together with shared directives —
ideal for a cluster like all your vocabulary decks. A folder becomes a workspace
when you drop a **`alix.toml`** in it — a scoped version of the
[config file](#configuration) — setting a `title`, an optional one-line
`description`, an optional `source_access` override (whether the
[grounded ask-tutor](#ask-claude-about-a-card) may read this workspace's source,
beating the global `[ask] source_access`), and a `[defaults]` table of directives
shared by every deck:

```toml
# ~/decks/english/alix.toml
title = "English"
description = "everyday conversational vocabulary"
# source_access = true   # let the ask-tutor read this workspace's % source:

[defaults]
direction = "both"
mode = "typing"
```

```
alix review ~/decks/english/        # review every deck in the folder, together
```

The `[defaults]` keys are the deck directive names, and they fill in only what a
deck *doesn't* set itself, so precedence runs **CLI flag > card > deck >
workspace > default** — set `direction = "both"` once for the whole cluster, and
an individual deck can still override it with its own `% direction:`.

**A workspace keeps its own progress.** Its decks track their stages in a
`progress.json` *inside the workspace folder* (override the path with a
`store = "..."` line in the `alix.toml`), separate from the global store every
loose deck shares. So a workspace is a **self-contained, portable unit** — its
decks, its `assets/` (frozen trace excerpts), and its progress all live in one
folder you can move or share, and its history stays isolated from everything
else. Decks *outside* a workspace keep using the global store; `--store <path>`
overrides either.

**Workspaces** and plain **Folders** appear in their own picker sections
(terminal and web): a folder *with* an `alix.toml` shows under **Workspaces**, one
*without* as a plain **Folder**. Both open (drill in) to their
members drawn as an **unlock dependency tree**: a deck nests under the
`% requires:` prerequisite that gates it, foundations at the roots, and siblings
ordered startable-first. Each row is badged `· trace ·` or `· deck ·`. The drill-in
is a **single-launch list** (no checkboxes): `Enter` on a facts deck reviews it,
`Enter` on a trace **walks** it. (Typing a filter flattens the tree to a plain
search; `alix review <folder>` reviews the whole cluster merged.) A manifest-less
folder is still reviewable (`alix review <folder>`), it just applies no shared
directives.

**`alix workspace <dir>`** opens a workspace straight into that same drill-in
picker, routing each member to the right experience — a **facts deck** to a
review, a **trace deck** to a [predict-verify walk](#traces-alix-trace) —
returning you to the picker when done. (`alix review <dir>` instead flattens the
whole folder into one review, so trace decks get quizzed as flat cards.)

**`% title:`** (on a deck) or **`title`** (in a workspace's `alix.toml`) gives a
display name, shown in the picker, the session header, `alix list` and `alix
stats` instead of the file name. It's display-only — you still refer to decks by
file path on the command line — and never affects a card's identity.

### Cloze cards (fill in the blank)

A front marked `#?` (no space) turns the card into a cloze card: every
`{{...}}` in its answer lines is a hole, and the card expands into one card
per hole. Each one shows the answer with that hole blanked out and the
others filled in, and you only produce the hidden text:

```
#? Complete the Rust declaration
    let {{mut}} x: {{u64}} = 0;
```

This makes two cards: `let ____ x: […] = 0;` (type `mut`) and
`let […] x: ____ = 0;` (type `u64`). The asked hole shows `____`; the other
holes are hidden as `[…]` so no card reveals its siblings' answers, and the
session queue keeps sub-cards of the same source card apart whenever other
cards are available. Only the doubled `{{` / `}}` are special — a lone `{` or
`}` is literal, so code like `let p = Foo {};` is fine in a cloze answer (write
a literal `{{` as `\{\{` if you ever need one). Progress of a cloze card
survives rewording its front and even a future change to the hole markup, but
editing its answer text or hole contents resets the affected holes.

### Dual-direction cards (`% direction:`)

A `% direction:` directive reviews a card both ways — useful for vocabulary and
other reversible facts:

```
# purported
    angeblich
    % direction: both
```

`both` makes two cards (`purported → angeblich` and `angeblich → purported`);
`reverse` keeps only the swapped one; `forward` (the default) is the card as
written. It works per card, or deck-wide as a header directive (`% direction:
both` before the first card) with per-card overrides — like `mode`. The two
directions get distinct progress, are kept apart in the queue (you won't see one
right after the other), and are removed together. The reversed card keeps the
note. Best for single-line cards; it does not apply to cloze cards.

### Images (`% img:`, `% img-back:`)

A card can show an image on the question side with `% img:`, the answer side
(revealed with the back) with `% img-back:`, or both:

```
% img-dir: /home/me/decks/img

# What phase is this?
    % img: moon-waxing.png
    Waxing gibbous

# Play this chord
    G major
    % img-back: g-major-tab.png
```

The deck-level `% img-dir:` (header only) is the directory filenames resolve
against; it may be absolute or relative to the deck file. Without it, filenames
resolve against the deck file's own folder. A card value that is itself an
absolute path is used as-is. One image per side.

Images render in the **web frontend only** — the terminal can't draw them — so
an image card is automatically *web-only* (as if it declared `% frontend: web`).
In the terminal, `alix review` skips such cards with a note, and if a whole
deck is web-only it points you at `--serve`. Use `% frontend:` to force a card
or deck to a frontend explicitly. `alix check` warns about a referenced image
file that doesn't exist (it doesn't fail the check).

### Source citations (`% at:` on a fact card)

A plain fact card can cite where its answer comes from. Add a `% at:` locator
pointing into the deck's `% source:`, and on reveal the card offers to swap its
worded answer for the exact source lines:

```
% source: src/string.rs

# What does the `String` struct hold?
    A `Vec<u8>` (its bytes).
    % at: src/string.rs:1-3
```

The locator takes the same form a [trace checkpoint](#traces-alix-trace) uses —
`file:lines` (e.g. `src/string.rs:1-3`), or just `lines` when the `% source:` is
a single file. On reveal a `</>` marker appears on the answer: in the web,
**click the answer** (or press `s`) to swap it for the line-numbered excerpt, and
again to swap back; in the terminal, press **`s`**. The excerpt is read **live**
from the source, so a moved or missing file shows *"source unavailable"* rather
than a stale quote. `% at:` is a comment to the scheduler, so adding it never
changes a card's identity or resets its progress.

You can write `% at:` by hand, but the deck generator adds them for you:
[`alix deck <local source>`](#generate-a-facts-deck--alix-deck) and
[`alix explore --build`](#exploring-a-source--alix-explore) cite the lines each
fact came from, and `alix check` warns about a citation that no longer resolves
(a moved or shrunk file). In a workspace built with `alix explore --into
--build`, the cited excerpts are also **frozen** into `assets/` (like trace
excerpts), so they never drift and the workspace travels without the upstream
source.

## Review

The default **flip** mode is Anki-style: you reveal the answer and grade
yourself (again / good / easy — easy jumps two Leitner stages). The
**typing** mode has you type the back of the card character by
character with instant green/red feedback; `TAB` reveals the next two
characters as a hint, and pressing it again uncovers two more each time until
the line is fully shown, but a hinted card counts as failed. In **fuzzy** mode
you submit whole lines with Enter and small typos are tolerated. A wrong
card goes back to stage 1 and reappears later in the same session until you
get it right. Whichever mode a card uses is shown as a small badge above the
answer (`flip`, `typing exact`, `typing fuzzy`, `choice`, `line by line`).

In **choice** mode you pick the answer out of four options with `1`–`4`.
The three wrong options are sampled from the answers of the other cards in
the session, preferring similar-looking ones (years compete with years), so
no distractors ever have to be written. Recognition is easier than recall: a
correct pick grades as *good* (never *easy*), a wrong pick fails the card.
Cloze siblings are never used as distractors, and if a session has fewer
than four distinct answers the card falls back to flip mode.

In **line** mode the back is revealed one line at a time: press the reveal
key (`Space`) to uncover the next line, recalling it first. It is meant for
lyrics, poems, or any ordered list. Once every line is shown you grade
yourself again / good / easy, exactly like flip mode. Pair it with
`--order sequential` (or `% order: sequential` in the deck) to walk the
sections top to bottom — e.g. one card per verse/chorus of a song.

In **explain** mode the card is an open prompt and its back lines are the **key
points** a good answer should cover (not a string to reproduce). You optionally
type your explanation, reveal the points, and grade yourself on whether you
covered them — for cards aimed at *understanding* rather than recall. Set it with
`% mode: explain` (per card or deck-wide). The typing is optional and never
checked: a self-graded mode can't verify your answer, so it doesn't pretend to
(in the web frontend your typed answer is shown next to the points for honest
comparison). It pairs with the ask-Claude helper, and is the day-to-day,
self-graded tier below the [AI exam](#the-ai-exam-alix-exam).

To throw a card away, press the **remove** key (`Ctrl-X` by default) on it
instead of grading — it is dropped from the session without being asked again
(cloze siblings go too). The marked cards are deleted from their deck files,
and their progress is pruned, when the session ends. The same key works in
`alix browse`.

Schedulers:

- **leitner** (default): a 6-stage box system with cooldowns
  0 / 1 h / 6 h / 24 h / 1 week. Pass moves a card up one stage, fail resets
  it to stage 1.
- **sm2**: SuperMemo-2 style with per-card ease factors and growing
  intervals. Existing Leitner progress is used as a starting point, and the
  Leitner stage is kept in sync, so you can switch back and forth.

## Browse

`alix browse <deck>...` is a walk through every card — front and back shown
together, in file order — without grading or scheduling. It is for a first
read-through of a new deck or just checking its contents, without affecting
your schedule. Navigate with `l`/`h` (next/previous, vim-style — `n`/`p`, the
arrow keys, and `Space` also work), `g`/`G` (first/last, also Home/End), and
`q` to quit. Pressing the remove key (`x` by default) marks the current card;
on quit the marked cards are deleted from their deck files and their progress
is pruned — the only thing browsing ever writes. The next/previous/remove/quit
keys are configurable in the `[browse]` section of the config file (see below);
first/last stay `g`/`G`. Run `alix browse` with no deck argument to choose
decks from the same picker `alix` uses.

## Web frontend

Add `--serve` to `review` or `browse` to run it in the browser instead of the
terminal — useful on a tablet or phone, where touch (and images) beats a TUI.
It runs the same session logic and writes to the same progress store, so
a card you grade or remove in the browser shows up on the command line and vice
versa.

```
alix review rust.txt --serve              # open http://127.0.0.1:7777
alix review rust.txt --serve --port 8080
alix review rust.txt --serve --lan        # reachable from other devices on your network
alix browse rust.txt --serve              # the browse view in the browser
alix --serve                              # no decks -> pick them in the browser
```

Run `--serve` **without** naming any decks and the browser opens a
deck-selection screen that mirrors the terminal [picker](#getting-started): the
same three sections — **Workspaces** (each with its last-progress time) ·
**Recent** loose decks · **Folders** — and the same **single-launch**, so you
**click a deck to start it** (an exam-due deck sits its exam, and a
[trace](#traces-alix-trace) **walks** — predict → verify — at `/walk`, with a
**Back to decks** to return to the picker). Open a **Workspace** or **Folder** to
drill into its **unlock dependency tree**, where each deck nests under the
prerequisite that gates it. A deck you can't start is
dimmed: 🔒 locked (an unfinished `% requires:`), 🕒 nothing due; a `mastered 🎉`
deck is tucked into the **Mastered window** (press `m`), and mastered/done/locked
decks stay out of Recent (a quick launchpad) but are reachable by filtering — the
filter searches *every* loose deck. `browse` ignores locking, so any deck opens
there. Keyboard nav follows your `[picker]` config (`j`/`k` or arrows move, `/`
or `Ctrl-F` filter, `m` the Mastered window). When you finish a session, "Choose
other decks" (on the summary, or in the ⋮ menu) returns here — and a session
launched inside a workspace returns **into that workspace**. Naming decks on the
command line skips the screen and goes straight to review/browse.

Every answer mode works in the browser: **flip** (reveal, then self-grade
Again / Good / Easy), **line** (reveal a verse one line at a time — it
auto-scrolls to follow the newest line), **typing** / **fuzzy** (type your
answer and submit; checked exactly or with your configured typo tolerance, each
line marked ✓/✗ with the correct answer shown), and **choice** (tap one of the
options). The note appears once the answer is shown. Controls are big tap
targets and follow your configured key bindings — the page reads them from the
server, so the chips show your own keys. The overflow menu (⋮) holds **Remove**,
which deletes the current card from its deck file and prunes its progress, and
**Choose decks**, which returns to the deck-selection screen.

It is deliberately local-only — no accounts, no database. By default it binds
to `127.0.0.1` (this machine only); `--lan` binds all interfaces so a device on
the same network can reach it at `http://<your-machine-ip>:<port>` (no
authentication, so only use `--lan` on a network you trust). `--port` and
`--lan` require `--serve`; the default port lives in the `[serve]` section of
the config file and `--port` overrides it.

## Ask Claude about a card

On any post-answer screen (feedback, revealed flip card, answered choice),
press `?` to ask Claude about the card without leaving the session. The
card (front, answer, note, deck name) is sent as context to the Claude Code
CLI (`claude -p`), so you can ask "why is that the answer?" and follow up.
One CLI conversation spans the whole review run (`--session-id` on the
first question, `--resume` afterwards), so Claude remembers earlier cards
and questions — but the panel shows only the **current card's** exchanges, not
the whole history. By default the tutor answers from the card text plus its own
knowledge (tools: `WebFetch`/`WebSearch`), and it uses the **CLI's default
model** — set `[ask] model`/`effort` to pin a stronger one (the web panel shows
which model is answering). For a deck built from source, set **`[ask]
source_access = true`** to let the tutor **read the card's source** to verify its
answer: it runs `Read`/`Glob`/`Grep` with its working directory at the deck's
`% source:` project root (the nearest `Cargo.toml`/`.git`/… above the cited
files) and is told to check the real files before answering. It's off by default
because it grants the tutor file-read access — only enable it on a machine and
network you trust (especially with `--serve --lan`). A [workspace](#workspaces)
can override it per-folder: put `source_access = true` (or `false`) in its
`alix.toml` to decide for that crate alone, beating the global default. While
Claude thinks, the session stays responsive; Esc returns exactly where you were.

This works in the **web frontend** too (`--serve`): an "Ask" button (and the
`?` key) on an answered card opens a chat panel — type a question, **Send**,
**Save note**, **Close**. The server runs `claude -p` on a background thread and
the page polls for the reply, so the single-threaded server never blocks. Ask is
reachable wherever you serve, including `--lan` (the request runs `claude` on the
host, so — like `--lan` generally — only use it on a network you trust).

While typing a question you can edit it like a normal input line: `←`/`→`
move the caret, `Home`/`End` (or `Ctrl-A`/`Ctrl-E`) jump to the ends, and
`Backspace`/`Delete` remove the character before/under it.

Decks can carry reference links as comment lines:

```
% link: https://docs.rs/async-compat
% link: https://tokio.rs/tokio/tutorial
```

They are handed to Claude with the first question as background material to
consult when useful — fetched once, remembered for the rest of the run. These
lines do not affect card hashes.

Because the CLI runs headless (`-p`), it cannot show interactive permission
prompts — an unanswerable prompt would hang the call. `alix` therefore
runs it with `--permission-mode dontAsk` and an exclusive tool allowlist
(`WebFetch`, `WebSearch` by default): the listed tools work without
prompting, and every other tool is silently denied, so a malicious page
behind a deck link cannot make the tutor run shell commands. Both the
permission mode and the allowlist are configurable in `[ask]`.

`Ctrl-N` condenses the conversation into at most three short note lines and
appends them to the card in the deck file (notes are not hashed, so the
card's progress is untouched). Requires the `claude` CLI to be installed
and logged in; the command, a `--model` override and the timeout are
configurable in the `[ask]` section of the config file.

## Generate a facts deck — `alix deck`

`alix deck <source>` turns a **source** into a deck of fact cards using the
Claude CLI. The source is a web page URL *or* a local file/directory path (the
deck-side mirror of `alix trace`):

```sh
alix deck https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html
alix deck src/scheduler.rs            # a local file (or a directory)
alix deck <source> -o ownership       # choose the file name
alix deck <source> --cards 15         # cap the number of cards
alix deck <source> --review           # add a 2nd pass to remove redundant cards
alix deck <source> --print            # print to stdout instead of writing
```

For a **web page**, Claude reads it with the **WebFetch** tool and the deck
starts with a `% link:` line back to it (so you can use the *ask* feature on the
cards). For a **local source**, Claude explores it read-only with
`Read`/`Glob`/`Grep` at the source root and the deck starts with a `% source:`
line (so `alix exam` can later grade your understanding against it); it also
adds a [`% at:` citation](#source-citations--at-on-a-fact-card) to each fact that
maps to specific lines, so the card can show its source on reveal. Either way
Claude only returns text — never a write or shell tool — and `alix` validates it
(`parse_str`) and writes `~/decks/<slug>.txt`. The cards are spread across four
layers of understanding (facts → concepts → application → connections) and use
cloze (`#?`) cards for terminology.

The prompt tells Claude to draft, then re-read the whole set and merge or drop
cards that test the same fact, so the deck doesn't repeat itself. For a
stronger pass, `--review` (or `generate.review = true`) runs a **second**
Claude call that takes the draft and returns a deduplicated, tightened version
— an extra call, worth it when the source is repetitive.

The prompt and limits live in the `[generate]` section of the config:
`model`, `timeout_secs` (default 300), `max_cards` (default 30), `extra`
(guidance appended to the built-in prompt), `prompt` (a full override, with
`{url}` and `{max_cards}` placeholders), and `review`. It reuses the `[ask]`
command and permission settings. Review the result before relying on it — it is a starting
point, not a final deck. Generation needs the `claude` CLI installed and
logged in, and works best on a single page or chapter (a whole book overruns
the context budget).

## Import an Anki deck (`alix import`)

`alix import <file.tsv>` turns an Anki export into an `alix` deck — no Claude
needed. Export your notes from Anki as **Notes in Plain Text** (`.txt`/`.tsv`)
with fields separated by a tab; the first field becomes the front, the second
the back, and any further fields are ignored:

```sh
alix import french.tsv                 # writes ~/decks/french.txt
alix import french.tsv -o vocab        # choose the deck name
alix import french.tsv --print         # print to stdout instead of writing
alix import french.tsv --force          # overwrite an existing deck
```

It skips Anki's `#`-prefixed header lines (`#separator:tab`, `#html:true`, …),
turns `<br>` tags into separate answer lines, decodes the common HTML entities
(`&amp;`, `&lt;`, `&nbsp;`, …), and backslash-escapes a back line that would
otherwise read as an `alix` comment or note. Rows missing a side are dropped. The
result is validated (`parse_str`) and written to `~/decks/<name>.txt`; review it
and clean up any leftover HTML by hand. It works best on a plain two-field
export — rich notetypes, media, and tags don't carry over.

## The AI exam (`alix exam`)

Mechanical review *loads* a deck's material into memory; the **AI exam**
*verifies you understood it* and is what gates progression. The idea: drilling
cards proves recall, but not that the ideas connected — so a deck can declare a
ground-truth **source** and require you to pass an exam against it before it
counts as done.

Declare one or more sources in the deck header (a URL or a local file path,
repeatable):

```
% source: https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html
% source: notes/ownership.md
```

A URL `% source:` doubles as an [ask-Claude](#ask-claude-about-a-card) reference,
so you don't need to repeat it as a `% link:`. The reverse isn't true: a
`% link:` stays a tutor reference and never becomes exam ground truth — keep
supplementary links (a blog, an SO answer) as `% link:` so the exam ignores them.

Once every card in a `% source:` deck reaches the top stage, the deck is **exam
due** rather than finished (so it does not yet unlock its dependents). To open
the exam earlier — while the cards keep drilling — set `% unlock-stage: N`: the
deck turns exam due once every card reaches stage `N` (see
[deck directives](#deck-directives)).

**Sitting the exam — interactive, in either frontend.** The exam is a guided,
one-question-at-a-time flow (Back/Next, then a per-question breakdown), the same
in the terminal and the browser:

- **Terminal**: `alix exam ownership.txt` (or `--questions 8`, `--strictness …`)
  opens the exam in the TUI. You also reach it by **picking an `exam due` deck**
  in the launcher (it starts the exam instead of an empty review), or from the
  **session-end summary** — when you drill a deck's last cards and it turns exam
  due, the summary offers "press `x` to take it" (or `b` to browse the deck).
- **Web** (`alix serve`): picking an `exam due` deck in the deck list launches
  the exam in the page; a finished session likewise offers it at the summary.

`alix` asks Claude to read the source (URLs via the **WebFetch** tool; local
files are embedded) and write fresh **open understanding** questions —
application and connections, not the card facts — each with the key points a
correct answer must contain. You type a prose answer per question, and an
examiner grades them Pass / Partial / Fail **against the source's rubric, never
against your cards** (grading the cards would be circular), at the deck's
configured strictness (below). The Claude calls run
on a background thread so the UI stays responsive while it thinks.

- **Pass** (every question by default; tune with `pass_threshold`) marks the
  deck **mastered** (shown as `mastered ✓`). Mastery — not mere drilling — is
  what unlocks decks that `% requires:` it. Source-less decks are unchanged:
  finishing = drilled (`done ✓`).
- **Fail** lists the gaps and offers to turn them into remediation cards
  appended to the deck — the card type is picked per gap (a cloze/plain card for
  a missed fact, a `% mode: explain` card for a missed concept), with overlapping
  gaps merged into one card. Re-drill those and re-sit.

Resetting a whole deck's progress (`alix reset <deck>`) also clears its mastered
state, so a re-drilled deck must pass the exam again (resetting only individual
cards with `--card`/`--cards` leaves the mastered state intact).

**Grading strictness** is a property of the *material*, so it's per deck. A
checklist-style topic (a procedure, exact syntax, a security drill) should fail
you for omitting a step; a conceptual topic shouldn't. Set it with a
`% strictness:` header directive (or `alix exam --strictness …`, or the
`[exam]` default):

- `strict` — completeness required: every rubric point must be present, so
  omitting one is a gap.
- `balanced` (default) — judges *understanding*, not phrasing: a point counts as
  covered if your answer shows you grasp it (even briefly), and only a wrong or
  genuinely-absent idea is a gap.
- `lenient` — benefit of the doubt: only clearly wrong or unanswered points are
  gaps.

This dial (how hard each answer is judged) is independent of `pass_threshold`
(how many answers must pass).

Settings live in the `[exam]` section: `model`, `timeout_secs` (default 300),
`num_questions` (default 5), `pass_threshold` (default 1.0 = all must pass),
`strictness` (default `balanced`), and `extra` (guidance appended to question
generation). It reuses the `[ask]` command, permission mode and tool allowlist,
and needs the `claude` CLI installed and logged in.

## Traces (`alix trace`)

> **Experimental.** Traces are a new, evolving feature — the deck format and the
> flow may still change.

Cards drill *facts* (the nodes of what you know); a **trace** drills the
*connections between them* (the edges) by walking a **path** through a real
source and making you **predict each hop before it's revealed**. Where the
[AI exam](#the-ai-exam-alix-exam) verifies a *set* of independent answers, a
trace verifies that you can follow one chain of reasoning — and the gap between
your prediction and the truth is where the understanding forms.

A trace is just a deck with a `% trace:` (a path description — what it walks,
which also marks it a trace) and a `% source:` (the path's origin), then a
sequence of **checkpoint** cards. Each checkpoint is an `explain`-style card — an
open *predict* prompt, the key points a good prediction should hit — plus a
`% at:` locator pointing at the real lines in the source:

```
% trace: how pressing the Good key in the browser becomes a saved grade
% source: ..

# You press Good. What does the page send the server — and what does it not?
	grade(g) POSTs to /api/grade with a body of just { grade: g } — no card id.
	% at: assets/serve/review.html:338-341
	! The page is a thin view; it doesn't even track card identity.

# So the request has no card id. How does the server know which card you graded?
	The handler grabs the live, server-side review session and grades on it.
	% at: src/serve.rs:682-689
	! State lives server-side; the page only ever names the grade.
```

The locator is a single contiguous range `file:start-end` (e.g.
`src/serve.rs:682-689`), or — when `% source:` is a single file — just the line
numbers; it is never comma-separated (a stitched excerpt makes disjoint code look
adjacent). The lines are **read live from the source** each time you walk, so the excerpt is
always current and the deck stays small; the source is the oracle, not an
invented answer. When a tight excerpt uses a symbol defined off-screen, name it
with a `% given:` line (`% given: state — the parser's position so far`,
repeatable) — these show as a list under the question, so the excerpt stays
focused without orphaning the names it leans on.

**Building it with Claude.** Instead of hand-writing the checkpoints, declare
just the `% trace:` and `% source:`, then run `alix trace --build <deck>`:
Claude explores the source — with **read-only** `Read`/`Glob`/`Grep` and the
source root as its working directory (no write or shell access) — traces the
single load-bearing path, and writes the checkpoints (with their `% at:`
locators) back into the deck file. The result is cached and version-controlled
there, so review it — especially the locators — and edit freely; re-run
`--build` to regenerate. The build prompt encodes the chain rules below, so a
generated trace comes out a path, not a quiz.

**Snapshotting the source.** Because `% at: file:lines` reads the **live** source,
editing a traced file shifts every excerpt to the wrong lines. So when you create
a workspace by exploring a source ([`alix explore --into --build`](#exploring-a-source--alix-explore)),
its final step **freezes the cited excerpts** into the workspace's `assets/`
folder — one tiny snippet per checkpoint — and repoints each `% at:` (and the
trace's `% source:`) at them. The excerpts never drift and the workspace is
self-contained, **without copying whole source files**. A re-based snippet loses
its original line numbers, so when those matter the original `file:lines` is kept
in the card's note (`! from scheduler.rs:90-98`). It's automatic for explored
workspaces, not a command; a loose trace over a live `% source:` is left as-is.
See [docs/traces.md](docs/traces.md) for the rationale.

**Checking the locators.** For a trace that *isn't* frozen — a loose `.txt` over
a live `% source:` — **`alix check`** validates that every `% at:` still
resolves into its source: it warns about a locator that names a missing file,
runs past the end of the file, or (for a single-file source) gives bare line
numbers it can't place. It's a quick structural check — *does this excerpt still
exist?* — so a moved or trimmed source is caught before you walk into it, not
mid-hop. (Frozen snapshots don't move, but their snippets are validated the same
way.)

Because building is one-shot, correctness-critical, and **fails silently** when
the model is weak (you still get parseable checkpoints, just a loose chain you
then drill), the `[trace]` config section defaults it to a strong model
(`model = "opus"`) and high reasoning effort (`effort = "high"`) — slower than
the other AI features, but it runs once and is amortized over many reviews.
Override `model`, `effort` (`low`–`max`) and `timeout_secs` there. `--suggest`
shares these `[trace]` settings (it's also one-shot recon), but **`--grade` does
not**: judging a prediction is a light, interactive, per-hop call, so it runs at
the tutor tier — the `[ask]` model, effort and timeout — instead.

**Don't know what to trace?** `alix trace --suggest <source>` does a single
read-only recon pass over a source (a repo `.`, a directory, a file, or a URL)
and prints a **ranked menu of candidate traces** — each a path-question, a
one-line spine sketch, and a suggested `% source:` scope. The list is sized by
**coverage** — the central spine plus one main path per major subsystem — so it's
as long as the source needs, not a fixed number, and it's the central *starting
points*, not an exhaustive curriculum. It also names the *node-shaped* subsystems
it skips (a config table, a store's on-disk format) as **facts-deck material**
rather than forcing them into a fake path — facts are a deck's job, edges are a
trace's. It writes nothing: pick one, paste its
header into a new deck, and `--build` it. Knowing *what* is worth
tracing — and how deep — is itself the hard part (it needs you to already
understand the source), so this hands that judgment to Claude.

**Write it as a chain, not a quiz.** A trace's whole value is that it's a *path*:
each checkpoint should pick up where the last *reveal* left off (note how hop 2
above opens with hop 1's conclusion, "the request has no card id"), so you're
following one thread — a data flow, a control flow, a derivation — to the
outcome. If the checkpoints are independent facts that all hang off one thing, you've
written a *set*, which is what cards and the exam already do; reach for a subject
that has a real sequence.

**Walking it** (`alix trace keypress-to-grade.txt`) goes hop by hop:

1. **Predict** — you type a guess before anything reveals (committing is the
   point).
2. **Reveal** — `alix` prints the real excerpt from the source, then the
   checkpoint's key points and note.
3. **Gap** — you judge yourself **Got it / Partial / Missed**. Grading is
   self-judged and offline (no model call) by default; pass **`--grade`** to have
   Claude judge your typed prediction against the key points and return the
   verdict plus a line of feedback (a model call per hop). Either way, a Partial
   or Missed is a **weak edge** that resets so it resurfaces sooner, while a
   nailed hop advances and fades. Each checkpoint is an ordinary card under the
   hood, so this scheduling is the normal per-card SRS.
4. **Compress** — after the last hop you restate the whole path in two
   sentences: if you can re-derive it, you understood it.

**In the browser** — add **`--serve`** (`alix trace <deck> --serve`) to walk it
in the web frontend instead of the terminal, the same way `review`/`browse`
serve. The walk page shows the **path** as a rail you descend (its nodes color in
by Got / Partial / Missed) and reveals each checkpoint's real source in a
line-numbered excerpt; `--serve --grade` runs the live grading and the page waits
on Claude per hop. `--port`/`--lan` work as elsewhere. Progress saves to the same
store, so a walk started in the terminal continues in the browser.

`alix trace <deck> --map` prints the path (every prompt, its key points and
locator) without quizzing — a quick "just show me the route". The generic AI
exam refuses a trace (`alix exam <trace>` points you here): a trace's
verification *is* its predict-verify walk plus the compression, correctly scoped
to the path.

A trace deck degrades gracefully — even without `alix trace` it is a valid deck
of `explain` cards. See `examples/keypress-to-grade.txt` for a complete trace
over this repo's own source.

## Exploring a source — `alix explore`

`--suggest` lists central *traces*; **`alix explore <source>`** goes one layer
up and prints an ordered **learning plan** toward a goal — the facts decks **and**
traces worth authoring, dependency-ordered:

```
alix explore .                                      # plan to understand the whole source
alix explore . --goal "how review scheduling works" # a narrow goal → a focused subset
```

Each item is tagged `[trace]` or `[deck]` — chosen by the shape of the knowledge
(a *path* you predict hop by hop becomes a trace; a *table of facts*, like a
config's knobs or a store's on-disk format, becomes a facts deck) — carries its
`% requires:` prerequisites (the list is dependency-ordered, foundations first),
and a `% source:` scope. The `--goal` scopes coverage: a broad goal covers every
subsystem, a narrow one collapses to just its slice (and traces it in more
detail). By default it's **read-only** — it prints the plan and you author the
items yourself (`alix trace --build` a trace, `alix deck` a facts deck).

With **`--into <dir>`** it materializes the plan into a ready-made **workspace**:

```
alix explore . --goal "how review scheduling works" \
  --into ~/decks/scheduling/ --title "Scheduling internals"
```

That writes an `alix.toml` and one stub file per item — a `% trace:` deck for
each trace (run `alix trace --build` on it) and a `% title:` facts deck for each
deck (author it or `alix deck`) — wired together with `% requires:` so they
unlock in dependency order, with each `% source:` pointing back at the real
source. The `--goal` becomes the workspace's `description`; **`--title`** names
it (omitted, the folder name is used); **`--unlock-stage <1–5>`** writes a shared
`[defaults]` `unlock-stage` so a member unlocks once its cards reach that stage
(without retiring early). (Refuses a non-empty folder unless `--force`.)

Add **`--build`** to go all the way: `alix explore … --into <dir> --build`
explores the source **once** and then reuses that same session to fill every
item — predict-verify checkpoints for the traces and fact cards for the decks —
so the workspace comes out review-ready in one command. Writing the whole set
from one understanding keeps the items coherent (each builds on its prerequisites
instead of repeating them), and it fills facts decks too. As a final step it
**freezes the cited excerpts** of every cited deck — traces *and* fact decks with
[`% at:` citations](#source-citations--at-on-a-fact-card) — into the workspace's
`assets/` (see [Snapshotting the source](#traces-alix-trace)), so the workspace
is self-contained and its locators never drift. (A snapshotted fact deck's
`% source:` then points at `assets/`, so its exam grades against the frozen
excerpts.)

**Explore walk.** Before you even know what to trace, `alix explore --walk
<source>` builds a short **tour of the source's shape** and walks it like a trace:
you predict what kind of program it is (from the manifest), its domain nouns (from
the module list), how it's driven (the entry point), its spine (the central file),
and finally the first paths worth tracing — each hop revealing the real lines.
It's written to a file (`-o`, default `explore.txt`), so `alix trace explore.txt`
re-walks it.

## Configuration

Key bindings can be changed in `~/.config/alix/config.toml`. Create the
file with `alix config --init`, inspect the active bindings with
`alix config`. Every action takes a list of keys; the first one is
shown in the footer. For example, to grade flip-mode cards with j/k/l:

```toml
[keys]
again = ["j"]
good = ["k"]
easy = ["l"]
```

Keys are written as a single character (`"j"`), a special key name
(`"space"`, `"enter"`, `"tab"`, `"esc"`, `"backspace"`), or either with a
`ctrl-` prefix (`"ctrl-s"`). Rebindable actions: `again`, `good`, `easy`,
`reveal`, `hint`, `submit`, `skip`, `remove` (mark the card for deletion from
its deck file, default `ctrl-x`), `continue`, `restart` (start a new session
from the summary screen, default `r`), `quit`. While you are typing
an answer (typing and fuzzy mode), plain character bindings are ignored so
they cannot shadow text input — use `ctrl-`/special keys for `hint`, `skip`
and `quit`. A different config file can be passed with `--config <path>`.

The browser (`alix browse`) has its own bindings in a `[browse]` section:

```toml
[browse]
next = ["l", "n", "space"]    # default vim-style l, plus n and space
prev = ["h", "p"]
remove = ["x"]                # mark the current card for removal
quit = ["q", "esc", "ctrl-c"]
```

Letter bindings are case-insensitive, so jump-to-first/last stays fixed at
`g`/`G` (and Home/End); the arrow keys always move next/previous too.

The web frontend (`--serve`) reads its default port from a `[serve]`
section; `--port` overrides it:

```toml
[serve]
port = 7777
```

## Card identity and storage

- Cards are identified by an XxHash64 over the deck **file name** plus the
  back lines. Your progress survives editing a card's front and adding notes,
  but renaming a deck file or editing its back lines resets the affected
  cards.
- Progress is stored at `~/.local/share/alix/progress.json` and config at
  `~/.config/alix/config.toml` (created on first use).
- `alix reset <deck>...` clears stored progress so cards become "new" again —
  for whole decks, a single card (`--card <id-or-front-text>`), or the entire
  store (`--all`). Run it with no decks to pick from the deck list, or add
  `--cards` to pick individual cards from a checkbox list. It confirms first
  unless you pass `-y`/`--yes`.

## Desktop integration

To launch `alix` from the desktop menu (Cinnamon, GNOME, KDE, ...), run:

```sh
assets/install-desktop.sh
```

It installs the icon (`assets/alix.svg`, rendered to the standard
PNG sizes), a launcher that reviews everything due in `~/decks` in a
terminal, and a `.desktop` entry under `~/.local/share`. Re-run it after
editing the SVG. The launcher prefers an installed `alix` (`cargo install
--path .`) and falls back to the project build.

## Development

```sh
cargo test       # unit tests
cargo clippy     # lints
```

## License

Licensed under either of

- Apache License, Version 2.0 ([LICENSE-APACHE]LICENSE-APACHE or
  <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT]LICENSE-MIT or
  <http://opensource.org/licenses/MIT>)

at your option.

### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.