

<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

## Writing and changing

Cell creation, targeted updates, documentation insertion, and examples.

Writing notebooks safely is harder than appending text to a file. A
notebook edit needs to preserve cell ids, clear stale outputs, validate
Python when possible, export through nbdev automatically, and avoid
overwriting the wrong cell.

[`write_nb`](https://MorsCerta-crypto.github.io/nbskill/write.html#write_nb)
inserts cells; `replace_str` replaces literal text across notebook cell
sources.

There are two writing modes because notebook edits have two different
shapes. Use cell-block writes when adding or replacing structured cells,
and use literal replacement only for exact renames across existing
cells. Both paths stamp nbskill metadata and export affected notebooks
automatically when they have an nbdev export target.
[`write_literal_replacements`](https://MorsCerta-crypto.github.io/nbskill/write.html#write_literal_replacements)
is the public literal-replacement API.

### Production contract

The writing tools are production core. They must preserve stable cell
ids where possible, clear stale outputs for changed cells, validate new
Python, export nbdev modules after source changes, support dry-run
review for broad replacements, and verify structured batch edits by
reading the notebook back.

## Writing cells

The write layer turns structured cell plans into valid notebook cells
and keeps the on-disk notebook in sync with its exported module.

#### Adding cells

[`write_nb`](https://MorsCerta-crypto.github.io/nbskill/write.html#write_nb)
is the broad insertion tool. It can append, insert before or after a
stable cell id, replace a whole notebook, or write into a named chapter
while preserving notebook structure.

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L97"
target="_blank" style="float:right; font-size:smaller">source</a>

### write_literal_replacements

``` python
def write_literal_replacements(
    path:str, # path of the notebook
    old_str:str, # old string to replace
    new_str:str='', # new string to insert
    run_test:bool=False, # run the tests for this code
    validate_code:bool=True, # run the code to make sure it works
    dry_run:bool=False, # don't make actual changes
    show_cells:bool=False, # show the cells that were edited
):
```

*Replace literal text across notebook cell sources.*

``` python
write_literal_replacements("nbs/example.ipynb", "old_name", "new_name", dry_run=True, show_cells=True)
```

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L146"
target="_blank" style="float:right; font-size:smaller">source</a>

### write_nb

``` python
def write_nb(
    path:str, cells:str='', before_id:str | None=None, after_id:str | None=None, chapter:str | None=None,
    replace:bool=False, cell_type:str='code', # cell type code|markdown
    run_test:bool=False, # execute the tests
    run_style:bool=False, # return style problems
    style_strict:bool=False, validate_code:bool=True
):
```

*Write cells to a notebook.*

## Updating and splitting

Use one-cell updates for surgical changes; use explosion or chapter
splitting when the notebook structure itself is the thing being changed.

#### Updating one cell

[`update_cell`](https://MorsCerta-crypto.github.io/nbskill/write.html#update_cell)
is the surgical tool. It keeps the original cell id, can replace a whole
cell or only a line range, and clears stale outputs.

For whole-cell replacement, pass exactly one notebook cell block:
optional `%%code`, `%%markdown`, or `%%raw` marker followed by the cell
source. Do not include standalone `---` separators; those mean multiple
cells and belong with
[`write_nb`](https://MorsCerta-crypto.github.io/nbskill/write.html#write_nb)
or
[`batch_edit_nb`](https://MorsCerta-crypto.github.io/nbskill/write.html#batch_edit_nb).
Use `line_range` or `old_str` when replacing only part of a cell.

[`update_cell`](https://MorsCerta-crypto.github.io/nbskill/write.html#update_cell)
accepts exact Python text and structured source lines, so callers need
no newline decoding or temporary patch files.

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L263"
target="_blank" style="float:right; font-size:smaller">source</a>

### update_cell

``` python
def update_cell(
    path:str, # Notebook path
    new:str='', new_file:str | None=None, # Read replacement text from a UTF-8 file
    cell_id:str | None=None, # Stable notebook cell id to update
    old_str:str | None=None, # Literal text to replace in the target cell, or used to locate the cell when cell_id is absent
    line_range:str | None=None, # 1-based inclusive lines to replace, e.g. 3 or 3:5
    split:bool=False, # Split a multi-cell replacement into separate cells
    split_before:str | None=None, # Split existing cell before the first matching line
    cell_type:str='code', # Default type for whole-cell replacements without %% marker
    run_test:bool=False, # Execute the notebook with execnb after writing
    validate_code:bool=True, # Validate changed Python code before writing
    dry_run:bool=False, # Show the update plan without writing
):
```

*Update one notebook cell: replace whole cell, a text substring, or a
line range.*

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L336"
target="_blank" style="float:right; font-size:smaller">source</a>

### split_cell

``` python
def split_cell(
    path:str, # Notebook path
    cell_id:str, # Stable cell id to split
    new:str='', new_file:str | None=None, # Read replacement text from a UTF-8 file
    split_before:str | None=None, # Split existing cell before the first line containing or matching this text
    cell_type:str='code', # Default type for replacement cells without %% marker
    run_test:bool=False, # Execute the notebook with execnb after writing
    validate_code:bool=True, # Validate changed Python code before writing
    dry_run:bool=False, # Show the split plan without writing
):
```

*Split one notebook cell into multiple cells, either at a matching line
or by supplying replacement content.*

#### Exploding function cells

[`explode_cell`](https://MorsCerta-crypto.github.io/nbskill/write.html#explode_cell)
splits a code cell containing multiple top-level functions into one code
cell per function. The generated cells keep the original leading
directives, and the normal notebook commit path stamps ids and nbskill
metadata.

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L383"
target="_blank" style="float:right; font-size:smaller">source</a>

### explode_cell

``` python
def explode_cell(
    path:str, # Notebook path
    cell_id:str | None=None, # Stable cell id to explode
    target_all:bool=False, # Explode every splittable code cell in the notebook
    run_test:bool=False, # Execute the notebook with execnb after writing
    validate_code:bool=True, # Validate changed Python code before writing
    dry_run:bool=False, # Show the explode plan without writing
):
```

*Split top-level function definitions in one or all code cells into
separate cells.*

#### Previewing an explosion

A dry run shows the planned split without changing the scratch notebook.

``` python
with write_demo_notebook("02_write_explode_preview.ipynb", cells=[mk_cell("#| export\ndef first():\n    return 1\n\ndef second():\n    return 2")]) as path:
    cell_id = read_nb(path).cells[0].id
    explode_cell(str(path), cell_id=cell_id, dry_run=True)
```

``` python
with write_demo_notebook("02_write_update.ipynb") as path:
    _exercise_direct_update_cell(path)
    print("direct update_cell preserved escaped newlines")
```

#### Structured cell edits

Python callers can pass lists of source lines instead of shell-escaped
text. These helpers preserve that representation in the write layer,
including optional `split_lines` markers that split only on empty lines.

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L404"
target="_blank" style="float:right; font-size:smaller">source</a>

### join_source_lines

``` python
def join_source_lines(
    lines, field:str='source_lines'
):
```

*Return newline text from a structured list of source lines.*

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L411"
target="_blank" style="float:right; font-size:smaller">source</a>

### valid_split_line_numbers

``` python
def valid_split_line_numbers(
    source_lines, split_lines:NoneType=None
):
```

*Return requested 1-based split lines that point at empty source lines.*

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L423"
target="_blank" style="float:right; font-size:smaller">source</a>

### split_source_lines

``` python
def split_source_lines(
    source_lines, split_lines:NoneType=None
):
```

*Split source lines at requested empty 1-based line numbers.*

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L437"
target="_blank" style="float:right; font-size:smaller">source</a>

### source_lines_cell

``` python
def source_lines_cell(
    source_lines, cell_type:str='code'
):
```

*Build one clean notebook cell from structured source lines.*

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L444"
target="_blank" style="float:right; font-size:smaller">source</a>

### source_lines_cells

``` python
def source_lines_cells(
    cell, default_cell_type:str='code'
):
```

*Build one or more cells from a structured cell object.*

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L473"
target="_blank" style="float:right; font-size:smaller">source</a>

### should_run_cell_feedback

``` python
def should_run_cell_feedback(
    cell
):
```

*Return whether an edited cell should be executed for immediate
feedback.*

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L482"
target="_blank" style="float:right; font-size:smaller">source</a>

### notebook_edit_feedback

``` python
def notebook_edit_feedback(
    path, cell_ids, auto_feedback:bool=True, timeout:int=10, safe:bool=True
):
```

*Execute through the last feedback-worthy edited cell and return visible
output.*

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L498"
target="_blank" style="float:right; font-size:smaller">source</a>

### append_notebook_edit_feedback

``` python
def append_notebook_edit_feedback(
    message, path, cell_ids, auto_feedback:bool=True, feedback_timeout:int=10, feedback_safe:bool=True
):
```

*Append automatic notebook execution feedback when an edited cell calls
for it.*

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L504"
target="_blank" style="float:right; font-size:smaller">source</a>

### save_notebook_edit

``` python
def save_notebook_edit(
    nb, path
):
```

*Stamp, write, export, and report whether nbdev produced Python.*

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L509"
target="_blank" style="float:right; font-size:smaller">source</a>

### replace_notebook_cell

``` python
def replace_notebook_cell(
    path, cell_id, source_lines, cell_type:str='code', split_lines:NoneType=None, validate_code:bool=True,
    auto_feedback:bool=True, feedback_timeout:int=10, feedback_safe:bool=True
):
```

*Replace one notebook cell from structured source lines, optionally
splitting on empty lines.*

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L530"
target="_blank" style="float:right; font-size:smaller">source</a>

### replace_notebook_range

``` python
def replace_notebook_range(
    path, cell_id, start_line, end_line, replacement_lines, validate_code:bool=True, auto_feedback:bool=True,
    feedback_timeout:int=10, feedback_safe:bool=True
):
```

*Replace one inclusive 1-based line range from structured replacement
lines.*

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L549"
target="_blank" style="float:right; font-size:smaller">source</a>

### insert_notebook_cells

``` python
def insert_notebook_cells(
    path, anchor_id, where:str='after', cells:NoneType=None, validate_code:bool=True, default_cell_type:str='code',
    auto_feedback:bool=True, feedback_timeout:int=10, feedback_safe:bool=True
):
```

*Insert structured cells before or after an anchor cell id.*

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L571"
target="_blank" style="float:right; font-size:smaller">source</a>

### delete_notebook_cell

``` python
def delete_notebook_cell(
    path, cell_id
):
```

*Delete one notebook cell by stable id.*

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L632"
target="_blank" style="float:right; font-size:smaller">source</a>

### apply_notebook_edit

``` python
def apply_notebook_edit(
    edit, default_path:NoneType=None, validate_code:bool=True, default_cell_type:str='code', auto_feedback:bool=True,
    feedback_timeout:int=10, feedback_safe:bool=True, dry_run:bool=False
):
```

*Apply one structured notebook edit operation through edit_notebook.*

``` python
lines = ["def first():", "    return 1", "", "def second():", "    return 2"]
assert join_source_lines(["a", "b"]) == "a\nb"
assert valid_split_line_numbers(lines, [2, 3]) == [3]
assert split_source_lines(lines, [2, 3]) == [lines[:2], lines[3:]]
assert source_lines_cell(["scratch = True"]).source == "scratch = True"
assert len(source_lines_cells({"source_lines": lines, "split_lines": [3]})) == 2
print("structured line helpers:", len(split_source_lines(lines, [3])))
```

    structured line helpers: 2

``` python
with write_demo_notebook("02_write_structured_edit.ipynb") as path:
    write_nb(str(path), "%%code\nvalue = 1", replace=True)
    cell_id = read_nb(path).cells[0].id
    source = ["def first():", "    return 1", "", "def second():", "    return 2"]
    msg = replace_notebook_cell(str(path), cell_id, source, split_lines=[2, 3])
    nb = read_nb(path)
    assert len(nb.cells) == 2
    range_msg = replace_notebook_range(str(path), nb.cells[0].id, 1, 1, ["def uno():"])
    insert_msg = insert_notebook_cells(str(path), nb.cells[1].id, cells=[{"source_lines": ["tail = True"]}])
    assert read_nb(path).cells[2].source == "tail = True"
    print(msg)
    print(range_msg)
    print(insert_msg)
```

    Wrote 1 cells to nbs/data/02_write_structured_edit.ipynb using replace
    Updated cell into 2 cells id=aa02cf6a
    Updated lines 1:1 id=aa02cf6a
    Inserted 1 cell after id=e05b6833

``` python
with write_demo_notebook("02_write_structured_apply.ipynb") as path:
    write_nb(str(path), "%%code\ntail = True", replace=True)
    tail_id = read_nb(path).cells[0].id
    op = dict(op="replace_cell", cell_id=tail_id, source_lines=["tail = 'applied'"])
    apply_msg = apply_notebook_edit(op, str(path))
    assert read_nb(path).cells[0].source == "tail = 'applied'"
    delete_msg = delete_notebook_cell(str(path), tail_id)
    assert len(read_nb(path).cells) == 0
    assert save_notebook_edit(read_nb(path), path) is False
    print(apply_msg)
    print(delete_msg)
    print("structured helpers left cells:", len(read_nb(path).cells))
```

    Wrote 1 cells to nbs/data/02_write_structured_apply.ipynb using replace
    Updated cell id=892ad2a6
    Deleted cell id=892ad2a6
    structured helpers left cells: 0

``` python
with write_demo_notebook("02_write_feedback.ipynb") as path:
    write_nb(str(path), "%%code\nseed = 1", replace=True)
    cell_id = read_nb(path).cells[0].id
    msg = replace_notebook_cell(str(path), cell_id, ["print('feedback', 3)"])
    assert "Auto feedback" in msg
    assert "feedback 3" in msg
    assert read_nb(path).cells[0].outputs == []
    feedback_cell = read_nb(path).cells[0]
    assert should_run_cell_feedback(feedback_cell)
    assert "feedback 3" in notebook_edit_feedback(str(path), [cell_id])
    assert "feedback 3" in append_notebook_edit_feedback("prefix", str(path), [cell_id])
    quiet = replace_notebook_cell(str(path), cell_id, ["seed = 4"])
    assert "Auto feedback" not in quiet
    print("automatic edit feedback returned output")
```

    Wrote 1 cells to nbs/data/02_write_feedback.ipynb using replace
    automatic edit feedback returned output

## Batch operations

Batch plans are useful when several related edits should be validated
and committed together.

#### Batch editing

Agents often need to change several notebook cells together.
[`batch_edit_nb`](https://MorsCerta-crypto.github.io/nbskill/write.html#batch_edit_nb)
accepts a JSON edit plan, runs the same notebook-aware validation and
locking as the single-cell tools, and prints a compact diff before
writing. Use it when repeated shell calls would make multiline code or
dry-run diffs fragile.

#### Verified batch edits

Batch plans are useful when an agent already knows the exact notebook
edits it wants to make. They are also risky if the tool only says
“applied” without checking disk.
[`batch_edit_nb`](https://MorsCerta-crypto.github.io/nbskill/write.html#batch_edit_nb)
now writes the notebooks, reads them back, and reports a verification
status plus before/after hashes for each operation.

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L697"
target="_blank" style="float:right; font-size:smaller">source</a>

### batch_edit_nb

``` python
def batch_edit_nb(
    plan:str='', plan_file:str | None=None, # Read the JSON plan from a UTF-8 file
    path:str | None=None, # Default notebook path for operations that omit path
    dry_run:bool=True, # Show the plan and diffs without writing
    validate_code:bool=True, # Validate changed Python code before writing
    default_cell_type:str='code', # Default cell type for inserted cells without %% markers
):
```

*Apply a JSON batch edit plan as thin edit_notebook operations.*

#### Splitting one chapter

[`split_nb_chapter`](https://MorsCerta-crypto.github.io/nbskill/write.html#split_nb_chapter)
moves one `##` chapter into a new nbdev notebook. It copies imports used
by the moved code, imports source-notebook definitions that the moved
chapter still references, and promotes referenced private source helpers
by dropping the leading underscore. Splitting modules is a broad,
deliberate refactor, so it remains an explicit Python API.

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L974"
target="_blank" style="float:right; font-size:smaller">source</a>

### split_nb_chapter

``` python
def split_nb_chapter(
    path:str, # Source notebook path
    chapter:str, # Chapter title string or regex to split out
    dest:str, # Destination notebook path
    default_exp:str | None=None, # Destination nbdev default_exp; defaults from dest path
    dry_run:bool=True, # Show the split plan without writing notebooks
    force:bool=False, # Overwrite dest if it already exists
    promote_private:bool=True, # Promote referenced private source helpers by dropping the leading underscore
):
```

*Split one \## chapter into a new nbdev notebook.*

## Moving cells between notebooks

[`move_cells`](https://MorsCerta-crypto.github.io/nbskill/write.html#move_cells)
is the transfer boundary: it keeps full cells intact, derives imports
from their AST names, and rolls both files back when a normal commit
fails. A name turns the destination into a new chapter; without one,
moves stay inside one notebook.

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L1060"
target="_blank" style="float:right; font-size:smaller">source</a>

### move_cells

``` python
def move_cells(
    source_path, destination_path:NoneType=None, cell_ids:NoneType=None, query:NoneType=None, chapter:NoneType=None,
    name:NoneType=None, mode:str='move', destination_anchor:NoneType=None, destination_where:str='after',
    default_exp:NoneType=None, promote_private:bool=True, dry_run:bool=False
):
```

*Move or copy selected cells, preserving full cell payloads and inferred
imports.*

------------------------------------------------------------------------

<a
href="https://github.com/MorsCerta-crypto/nbskill/blob/main/nbskill/write.py#L1014"
target="_blank" style="float:right; font-size:smaller">source</a>

### create_notebook

``` python
def create_notebook(
    path, name:NoneType=None, template:str='nbdev', default_exp:NoneType=None, dry_run:bool=False
):
```

*Create a minimal flat numbered nbdev notebook without overwriting one.*

``` python
plan_path = demo_path("batch_edit_plan.json")
try:
    with write_demo_notebook("batch_edit_target.ipynb") as nb_path:
        write_nb(str(nb_path), "%%code\nvalue = 1", replace=True)
        nb = read_nb(nb_path)
        cell = nb.cells[0]
        plan = {
            "operations": [
                {"op": "set_cell_source", "path": str(nb_path), "cell_id": cell.id, "source": "value = 2"},
                {"op": "insert_after_id", "path": str(nb_path), "cell_id": cell.id, "cells": "%%code\nassert value == 2"},
            ]
        }
        plan_path.write_text(json.dumps(plan), encoding="utf-8")
        batch_edit_nb(plan_file=str(plan_path), dry_run=True)
        out = StringIO()
        with redirect_stdout(out):
            batch_edit_nb(plan_file=str(plan_path), dry_run=False)
        applied = out.getvalue()
        print(applied, end="")
        nb = read_nb(nb_path)
        assert cell_source(nb.cells[0]) == "value = 2"
        assert "assert value == 2" in cell_source(nb.cells[1])
        assert "[verified]" in applied
        assert "->" in applied and "inserted id=" in applied
        print(f"verified {applied.count('[verified]')} batch edit records")
finally:
    remove_demo_path(plan_path)
```

    Wrote 1 cells to nbs/data/batch_edit_target.ipynb using replace
    Dry run: would apply 2 batch operations across 1 notebook(s)
    - #1 set_cell_source: nbs/data/batch_edit_target.ipynb id=13f4ef6d [planned] e143e5f6659b->9cfe9326a093
        --- before
        +++ after
        @@ -1 +1 @@
        -value = 1
        +value = 2
    - #2 insert_after_id: nbs/data/batch_edit_target.ipynb id=13f4ef6d [planned]
        inserted id=f631de99 hash=7e8872481e92
        inserted 1 cell(s) after id=13f4ef6d
    Applied 2 batch operations across 1 notebook(s)
    - #1 set_cell_source: nbs/data/batch_edit_target.ipynb id=13f4ef6d [verified] e143e5f6659b->9cfe9326a093
        --- before
        +++ after
        @@ -1 +1 @@
        -value = 1
        +value = 2
    - #2 insert_after_id: nbs/data/batch_edit_target.ipynb id=13f4ef6d [verified]
        inserted id=74af8946 hash=7e8872481e92
        inserted 1 cell(s) after id=13f4ef6d
    verified 2 batch edit records

The write APIs accept ordinary Python values and keep editing operations
explicit for agent workflows.
