sync-wb-nb
Keep a .wb and its paired .nb mirror in sync. We edit/test in the .wb (Wolfbook MCP);
the .nb is the mirror we cannot run but the user opens in desktop Mathematica. Direction is
always .wb → .nb (the .wb is authoritative; /nb-to-wolfbook handles the initial
reverse conversion). Set the default pair via defaultWb/defaultNb at the top of
sync-wb-nb.wls, or pass absolute paths as trailing args.
Two operations, one helper script (sync-wb-nb.wls, in this skill's directory):
- Snippet sync (
replace/insert/verify/stability) — propagate ONE edited cell,
preserving the rest of a hand-crafted .nb (its outputs, manual tweaks). This is the
standing-rule after editing the main notebook.
regenerate — rebuild the WHOLE .nb from the .wb so it opens in Mathematica
looking normal: syntax-coloured code, styled headings. Use it to create a fresh .nb
(e.g. a new generated/*.wb) or after a wholesale rewrite. It overwrites the .nb,
so never use it to patch one cell of a hand-crafted notebook.
When to invoke
- After editing/adding cells in the MAIN
.wb: snippet sync, immediately. Add to your
CLAUDE.md:
Always run /sync-wb-nb immediately after editing or adding cells in any .wb.
- For a NEW or fully-rewritten
.nb: regenerate.
regenerate — full colourised rebuild
WLS=".claude/skills/sync-wb-nb/sync-wb-nb.wls" # path from project root
wolframscript -file "$WLS" regenerate "<abs wbPath>" "<abs nbPath>"
What it produces, cell by cell from the .wb:
- code cells →
Cell[BoxData[…], "Input"] parsed by the front-end parser
(FrontEndUndocumentedTestFEParserPacket), which is what makes Mathematica colour them (blue user symbols, green pattern vars, black built-ins). A plain Cell["string","Input"]`
shows up all black — that was the bug this mode fixes.
- markdown cells →
Title/Section/Subsection/Subsubsection (by #/##/###/####)
and Text styles, with **bold**/`code`/[t](l) markup stripped.
- in-code
(* … *) comments → lifted into an adjacent Text cell (the box parser drops
comments, same as the snippet engine).
Hard-won rules baked into the mode (don't undo them):
- Absolute paths only. A relative
Export["generated/x.nb"] lands in wolframscript's own
cwd, not the project — the file silently doesn't change and you debug a ghost. The mode
re-reads the exact path and asserts the file is a Notebook, contains BoxData, and has
one coloured cell per code cell.
- FE parser, not
MakeBoxes. The FE parser preserves the source line breaks and the
literal expression; MakeBoxes reassociates Times in product-numerator fractions (the
stability hazard below) and would risk changing the maths on a whole-file rewrite.
- Tell the user to reopen. If the
.nb is open in Mathematica, the stale in-memory copy
must be closed WITHOUT saving (or File ▸ Revert) — otherwise a save overwrites the rebuild.
regenerate is not /sync-wb-nb-incremental: it is the same skill's bulk path. Re-run it
whenever the .wb changes (there is no cheaper incremental update for a generated/ notebook).
Snippet sync — propagate one edited cell
For the main hand-crafted .nb: backup, box conversion, stability gate, unique-target
assertions, post-write verification with automatic restore — all in sync-wb-nb.wls. One
Bash call per operation:
WLS=".claude/skills/sync-wb-nb/sync-wb-nb.wls" # path from project root
wolframscript -file "$WLS" stability "<snippet>" # is the .wb cell box-stable?
wolframscript -file "$WLS" replace "<wbSnippet>" ["<nbOldSnippet>"] # sync an edited cell
wolframscript -file "$WLS" insert "<wbSnippet>" "<nbAnchorSnippet>" # sync a new cell
wolframscript -file "$WLS" verify "<snippet>" # check a cell is in sync
<snippet> selects the cell and must be a single token (a symbol name defined in the
cell, e.g. "myFunction"): in box structures myFunc[x_ never matches — myFunc, [,
x_ are separate strings.
replace takes a second snippet when the OLD .nb cell doesn't contain the new token
(e.g. new code introduces newSymbol; locate the old cell by one of its own existing tokens).
insert needs a snippet of the .nb cell the new cell goes AFTER.
- Non-default notebook pair: append
<wbPath> <nbPath> (absolute paths) as trailing args.
- Exit 0 +
PASS: … = success. Exit 1 + FAIL: … = nothing (or a restored backup) on disk.
Workflow per synced cell: stability → fix source if it fails → replace/insert →
done (verification is built in). Report the PASS lines.
Hard rules (the script enforces #2–#5; you own #1)
- Keep synced code cells pure code. The code→boxes conversion discards
(* … *)
comments, so a commented cell would differ between the two files. Notes go in a separate
text cell or the workbook.
- Box-round-trip stability. A fraction with a product numerator (
a*b/c)
reassociates Times through FractionBox and breaks ===; atomic-numerator fractions
are stable. Fix by assigning the numerator to a local (c2 = a*b; … c2/c), re-validate
the maths numerically, then sync. The script refuses to write unstable code.
- Backup before write, restore on failed verification. Automatic (
/tmp/sync-wb-nb-backup-*.nb).
- Unique-target assertion. Ambiguous or missing snippet matches abort before writing.
- Verify, don't trust: after writing, the
.nb cell must parse === the .wb code.
Why wolframscript (not Python)
Only the Wolfram kernel can convert code text to Input boxes (MakeBoxes or the front-end
parser) and parse .nb box structures back; a Python splice would also leave the .nb's
internal byte-offset cache stale. Import→modify→Export regenerates it correctly and
preserves outputs.
Implementation notes (for maintaining the script)
Snippet modes:
- Snippet matching uses
ToString[boxes, InputForm, PageWidth -> Infinity] — default
ToString line-wraps and can split a token across a \ continuation, silently breaking
StringContainsQ.
- Cell replacement uses
Position+Select(SameQ)+ReplacePart, never nb /. oldCell -> new: a literal Cell as a /. LHS can silently fail to fire. UUID-anchored /. rules
are fine for insertion (the anchor cell is matched by its UUID option, not its body).
- Markdown
.wb cells (kind 1) are NOT touched by the snippet modes; mirror them manually as
Cell[text, "Subsection"/"Text"] matching neighbouring styles. (regenerate DOES handle
them — it rebuilds the whole file.)
regenerate mode:
- The FE parser returns
{BoxData[…], …}; take [[1]]. It yields the multi-line list-form
BoxData[{box, "\n", box, …}] (the FE's native typed-input shape — it colours correctly);
Import later normalises that to a single RowBox. So the faithfulness check must NOT
compare held expressions of the two shapes — it just asserts every stored cell re-parses
without $Failed.
- The FE parser drops
(* *) comments (with the True flag too — tested), so comments are
lifted to a Text cell.
General:
- Quote the project path (spaces + parentheses); wolframscript needs absolute paths. For
regenerate the absolute output path is load-bearing, not just convenient.
1---2name: sync-wb-nb3description: Propagate a change made in a Wolfbook .wb notebook into the paired .nb notebook so the two stay identical. Use immediately after every .wb edit.4---56# sync-wb-nb78Keep a `.wb` and its paired `.nb` mirror in sync. We edit/test in the `.wb` (Wolfbook MCP);9the `.nb` is the mirror we cannot run but the user opens in desktop Mathematica. Direction is10always `.wb` → `.nb` (the `.wb` is authoritative; `/nb-to-wolfbook` handles the initial11reverse conversion). Set the default pair via `defaultWb`/`defaultNb` at the top of12`sync-wb-nb.wls`, or pass absolute paths as trailing args.1314Two operations, one helper script (`sync-wb-nb.wls`, in this skill's directory):1516- **Snippet sync** (`replace`/`insert`/`verify`/`stability`) — propagate ONE edited cell,17 preserving the rest of a hand-crafted `.nb` (its outputs, manual tweaks). This is the18 standing-rule after editing the main notebook.19- **`regenerate`** — rebuild the WHOLE `.nb` from the `.wb` so it opens in Mathematica20 looking normal: syntax-coloured code, styled headings. Use it to create a fresh `.nb`21 (e.g. a new `generated/*.wb`) or after a wholesale rewrite. It **overwrites** the `.nb`,22 so never use it to patch one cell of a hand-crafted notebook.2324## When to invoke25- After editing/adding cells in the MAIN `.wb`: snippet sync, **immediately**. Add to your26 CLAUDE.md:27 > Always run `/sync-wb-nb` immediately after editing or adding cells in any `.wb`.28- For a NEW or fully-rewritten `.nb`: `regenerate`.2930## regenerate — full colourised rebuild3132```bash33WLS=".claude/skills/sync-wb-nb/sync-wb-nb.wls" # path from project root34wolframscript -file "$WLS" regenerate "<abs wbPath>" "<abs nbPath>"35```3637What it produces, cell by cell from the `.wb`:38- code cells → `Cell[BoxData[…], "Input"]` parsed by the **front-end parser**39 (`FrontEnd`UndocumentedTestFEParserPacket`), which is what makes Mathematica colour them40 (blue user symbols, green pattern vars, black built-ins). A plain `Cell["string","Input"]`41 shows up all black — that was the bug this mode fixes.42- markdown cells → `Title`/`Section`/`Subsection`/`Subsubsection` (by `#`/`##`/`###`/`####`)43 and `Text` styles, with `**bold**`/`` `code` ``/`[t](l)` markup stripped.44- in-code `(* … *)` comments → lifted into an adjacent `Text` cell (the box parser drops45 comments, same as the snippet engine).4647Hard-won rules baked into the mode (don't undo them):48- **Absolute paths only.** A relative `Export["generated/x.nb"]` lands in wolframscript's own49 cwd, not the project — the file silently doesn't change and you debug a ghost. The mode50 re-reads the exact path and asserts the file is a `Notebook`, contains `BoxData`, and has51 one coloured cell per code cell.52- **FE parser, not `MakeBoxes`.** The FE parser preserves the source line breaks and the53 literal expression; `MakeBoxes` reassociates `Times` in product-numerator fractions (the54 stability hazard below) and would risk changing the maths on a whole-file rewrite.55- **Tell the user to reopen.** If the `.nb` is open in Mathematica, the stale in-memory copy56 must be closed WITHOUT saving (or `File ▸ Revert`) — otherwise a save overwrites the rebuild.5758`regenerate` is not `/sync-wb-nb`-incremental: it is the same skill's bulk path. Re-run it59whenever the `.wb` changes (there is no cheaper incremental update for a `generated/` notebook).6061## Snippet sync — propagate one edited cell6263For the main hand-crafted `.nb`: backup, box conversion, stability gate, unique-target64assertions, post-write verification with automatic restore — all in `sync-wb-nb.wls`. One65Bash call per operation:6667```bash68WLS=".claude/skills/sync-wb-nb/sync-wb-nb.wls" # path from project root69wolframscript -file "$WLS" stability "<snippet>" # is the .wb cell box-stable?70wolframscript -file "$WLS" replace "<wbSnippet>" ["<nbOldSnippet>"] # sync an edited cell71wolframscript -file "$WLS" insert "<wbSnippet>" "<nbAnchorSnippet>" # sync a new cell72wolframscript -file "$WLS" verify "<snippet>" # check a cell is in sync73```7475- `<snippet>` selects the cell and **must be a single token** (a symbol name defined in the76 cell, e.g. `"myFunction"`): in box structures `myFunc[x_` never matches — `myFunc`, `[`,77 `x_` are separate strings.78- `replace` takes a second snippet when the OLD `.nb` cell doesn't contain the new token79 (e.g. new code introduces `newSymbol`; locate the old cell by one of its own existing tokens).80- `insert` needs a snippet of the `.nb` cell the new cell goes AFTER.81- Non-default notebook pair: append `<wbPath> <nbPath>` (absolute paths) as trailing args.82- Exit 0 + `PASS: …` = success. Exit 1 + `FAIL: …` = nothing (or a restored backup) on disk.8384Workflow per synced cell: `stability` → fix source if it fails → `replace`/`insert` →85done (verification is built in). Report the PASS lines.8687## Hard rules (the script enforces #2–#5; you own #1)88891. **Keep synced code cells pure code.** The code→boxes conversion discards `(* … *)`90 comments, so a commented cell would differ between the two files. Notes go in a separate91 text cell or the workbook.922. **Box-round-trip stability.** A fraction with a *product* numerator (`a*b/c`)93 reassociates `Times` through `FractionBox` and breaks `===`; atomic-numerator fractions94 are stable. Fix by assigning the numerator to a local (`c2 = a*b; … c2/c`), re-validate95 the maths numerically, then sync. The script refuses to write unstable code.963. **Backup before write, restore on failed verification.** Automatic (`/tmp/sync-wb-nb-backup-*.nb`).974. **Unique-target assertion.** Ambiguous or missing snippet matches abort before writing.985. **Verify, don't trust**: after writing, the `.nb` cell must parse `===` the `.wb` code.99100## Why wolframscript (not Python)101102Only the Wolfram kernel can convert code text to Input boxes (`MakeBoxes` or the front-end103parser) and parse `.nb` box structures back; a Python splice would also leave the `.nb`'s104internal byte-offset cache stale. Import→modify→Export regenerates it correctly and105preserves outputs.106107## Implementation notes (for maintaining the script)108109Snippet modes:110- Snippet matching uses `ToString[boxes, InputForm, PageWidth -> Infinity]` — default111 `ToString` line-wraps and can split a token across a `\` continuation, silently breaking112 `StringContainsQ`.113- Cell replacement uses `Position`+`Select`(SameQ)+`ReplacePart`, never `nb /. oldCell ->114 new`: a literal Cell as a `/.` LHS can silently fail to fire. UUID-anchored `/.` rules115 are fine for *insertion* (the anchor cell is matched by its UUID option, not its body).116- Markdown `.wb` cells (kind 1) are NOT touched by the snippet modes; mirror them manually as117 `Cell[text, "Subsection"/"Text"]` matching neighbouring styles. (`regenerate` DOES handle118 them — it rebuilds the whole file.)119120regenerate mode:121- The FE parser returns `{BoxData[…], …}`; take `[[1]]`. It yields the multi-line *list-form*122 `BoxData[{box, "\n", box, …}]` (the FE's native typed-input shape — it colours correctly);123 `Import` later normalises that to a single `RowBox`. So the faithfulness check must NOT124 compare held expressions of the two shapes — it just asserts every stored cell re-parses125 without `$Failed`.126- The FE parser drops `(* *)` comments (with the `True` flag too — tested), so comments are127 lifted to a Text cell.128129General:130- Quote the project path (spaces + parentheses); wolframscript needs absolute paths. For131 `regenerate` the absolute output path is load-bearing, not just convenient.