Skip to content

feat(deepnote-file): Migration to single notebook deepnote file - #364

Merged
tkislan merged 52 commits into
mainfrom
tk/single-notebook-deepnote-file
Jun 26, 2026
Merged

tkislan merged 52 commits into
mainfrom
tk/single-notebook-deepnote-file

Conversation

@tkislan

@tkislan tkislan commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added deepnote split <path> to generate one-notebook-per-file outputs (with --output and --force) and included shell completion.
    • Added --outputFormat to the convert CLI.
  • Bug Fixes
    • Improved snapshot discovery/loading with optional notebook-scoped behavior to prevent cross-notebook mixing.
    • Enhanced sibling init composition and execution snapshot persistence across run/analyze/lint/stats.
    • Added a multi-notebook lint warning when multiple non-init notebooks are present.
  • Documentation
    • Updated .deepnote organization guidance and snapshot/split naming documentation.
  • Tests
    • Expanded coverage for split, snapshot lookup/splitting, and sibling init resolution/execution.

@coderabbitai

coderabbitai Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0956a2c3-23ca-4f1b-a407-f2eaf3169f6f

📥 Commits

Reviewing files that changed from the base of the PR and between 1ad6e6f and 6748998.

📒 Files selected for processing (9)
  • packages/cli/src/cli.ts
  • packages/cli/src/commands/analyze.ts
  • packages/cli/src/commands/dag.ts
  • packages/cli/src/commands/lint.test.ts
  • packages/cli/src/commands/lint.ts
  • packages/cli/src/commands/run.test.ts
  • packages/cli/src/commands/run.ts
  • packages/cli/src/utils/analysis.ts
  • skills/deepnote/SKILL.md
✅ Files skipped from review due to trivial changes (1)
  • skills/deepnote/SKILL.md
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/cli/src/cli.ts
  • packages/cli/src/commands/analyze.ts
  • packages/cli/src/utils/analysis.ts
  • packages/cli/src/commands/dag.ts
  • packages/cli/src/commands/lint.ts
  • packages/cli/src/commands/run.ts
  • packages/cli/src/commands/lint.test.ts

📝 Walkthrough

Walkthrough

Adds deepnote split for multi-notebook .deepnote files, updates snapshot naming and lookup to carry notebook IDs, and adds sibling init composition for native .deepnote inputs. The CLI run, analyze, lint, stats, DAG, and MCP execution paths now load resolved files and surface init-resolution warnings. Documentation, completions, tests, and shared convert exports were expanded around the new notebook-per-file and snapshot behavior.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • deepnote/deepnote#408: Shares the snapshot persistence and notebook-id scoping changes around saveExecutionSnapshot, getSnapshotPath, and related shared convert APIs.

Suggested reviewers

  • mfranczel
  • dinohamzic
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: migrating Deepnote files to a single-notebook format.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Updates Docs ✅ Passed OSS docs now cover single-notebook files and deepnote split in FILES.md, READMEs, and skill docs; I can’t verify the private roadmap repo.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 markdownlint-cli2 (0.22.1)
skills/deepnote/SKILL.md

markdownlint-cli2 v0.22.1 (markdownlint v0.40.0)
Error: Unable to use configuration file '/coderabbit-0.markdownlint-cli2.jsonc'; ENOENT: no such file or directory, open '/coderabbit-0.markdownlint-cli2.jsonc'
at throwForConfigurationFile (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:48:9)
at readOptionsOrConfig (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:169:5)
at async main (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:927:21)
at async file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2-bin.mjs:14:22 {
[cause]: Error: ENOENT: no such file or directory, open '/coderabbit-0.markdownlint-cli2.jsonc'
at async open (node:internal/fs/promises:640:25)
at async Object.readFile (node:internal/fs/promises:1287:14)
at async readOptionsOrConfig (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:141:17)
at async main (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:927:21)
at async file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2-bin.mjs:14:22 {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/coderabbit-0.markdownlint-cli2.jsonc'
}
}


Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Apr 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.39130% with 49 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.50%. Comparing base (e25912b) to head (6748998).

Files with missing lines Patch % Lines
packages/convert/src/cli.ts 82.85% 6 Missing ⚠️
packages/convert/src/source-notebook-formats.ts 84.21% 6 Missing ⚠️
packages/cli/src/commands/convert.ts 86.11% 5 Missing ⚠️
packages/cli/src/utils/load-and-resolve-init.ts 68.75% 5 Missing ⚠️
packages/convert/src/marimo-to-deepnote.ts 81.81% 4 Missing ⚠️
packages/convert/src/snapshot/split.ts 95.12% 4 Missing ⚠️
packages/mcp/src/tools/execution.ts 71.42% 4 Missing ⚠️
packages/convert/src/snapshot/resolve-init.ts 97.02% 3 Missing ⚠️
packages/mcp/src/tools/conversion.ts 89.47% 2 Missing ⚠️
packages/mcp/src/tools/snapshots.ts 80.00% 2 Missing ⚠️
... and 8 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #364      +/-   ##
==========================================
+ Coverage   84.51%   86.50%   +1.98%     
==========================================
  Files         153      160       +7     
  Lines        8093     8380     +287     
  Branches     2189     2330     +141     
==========================================
+ Hits         6840     7249     +409     
+ Misses       1252     1130     -122     
  Partials        1        1              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cli/src/commands/lint.test.ts (1)

309-318: ⚠️ Potential issue | 🟠 Major

Add direct behavioral tests for multi-notebook, not only allowlisting.

Line 317 updates accepted codes, but this does not verify emission logic. Add assertions that the warning appears for multi-notebook projects and is suppressed when --notebook is set.

As per coding guidelines, "/*.test.{ts,tsx}: Create comprehensive tests for all new features using Vitest."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/lint.test.ts` around lines 309 - 318, Add
behavioral tests that assert the "multi-notebook" warning is actually emitted
and suppressed: in packages/cli/src/commands/lint.test.ts (near the validCodes
array and existing lint command tests) add two Vitest cases — one that runs the
lint command against a synthetic multi-notebook project fixture and expects a
warning with code "multi-notebook" to appear in the output/diagnostics, and a
second that runs the same fixture but passes the CLI flag "--notebook" (or uses
the helper that simulates that flag) and asserts that no "multi-notebook"
warning is emitted; ensure you reference the existing validCodes array and the
lint invocation helper (e.g., runLintCommand / invokeCli or whatever test helper
is used) so the new tests integrate with the current suite.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/src/commands/split.test.ts`:
- Around line 37-157: Add a test to split.test.ts that uses
createMultiNotebookFile/serializeDeepnoteFile to write a multi-notebook project
and also writes an accompanying snapshot file next to the source (e.g.,
`${inputPath}.snapshot`), then run the split via createSplitAction(program) and
assert that per-notebook snapshot files were created in the output directory
(one snapshot per generated .deepnote file) and that their contents deserialize
via deserializeDeepnoteFile and correspond to each notebook; use the existing
tempDir, fs helpers, and cleanup pattern consistent with the other tests and
mirror the checks used for generated .deepnote files.

In `@packages/cli/src/completions.ts`:
- Around line 155-162: The completion logic for the output option doesn't treat
`split` as a command, so when completing the `-o/--output` value Bash uses
generic file completion instead of directory completion; update the case that
handles `-o --output` (the branch that inspects `prev` for output flags) to
include `split)` alongside other command labels so the output-option handler
triggers directory-only completion for the `split` subcommand as well (look for
the `split)` case and the `COMPREPLY=( $(compgen -W "-o --output --force" --
"${cur}") )` logic to make the change).

In `@packages/cli/src/utils/analysis.ts`:
- Around line 212-218: The warning message uses nonInitNotebooks.length but
calls it "notebooks" which is misleading when file.project.initNotebookId
exists; update the issues.push message in the block that creates the
'multi-notebook' warning (the place using nonInitNotebooks and
file.project.initNotebookId) to either (a) report the actual total number of
notebooks using file.project.notebooks.length if you mean total notebooks, or
(b) explicitly say "non-init notebooks" and keep nonInitNotebooks.length if you
mean to count only non-init ones—make the message text consistent with the count
you choose.

In `@packages/cli/src/utils/output-persistence.test.ts`:
- Around line 133-137: Add a test case in
packages/cli/src/utils/output-persistence.test.ts that covers the multi-notebook
branch by constructing a fixture where file.project.notebooks.length > 1 and
asserting the produced path matches the legacy format
"{slug}_{projectId}_latest.snapshot.deepnote" (e.g., resolve(..., 'snapshots',
'test-project_test-project-id-1234-5678-90ab_latest.snapshot.deepnote'););
locate the existing single-notebook assertions around the resolve(...) lines and
duplicate the pattern for the multi-notebook scenario, ensuring the code path
exercised calls the same helper (the function under test used by those
assertions) so the legacy filename format remains covered during migration;
apply the same addition for the other mentioned blocks (around lines 149-153 and
165-169) to fully cover all branches.

In `@packages/convert/src/snapshot/split.ts`:
- Around line 149-152: The single-notebook branch returns the original file
object which breaks immutability consistency with the multi-notebook path that
returns new objects; update the single-notebook return to return a shallow copy
of file (e.g., spread properties) so callers that mutate results won't alter the
input — change the branch that uses nb = file.project.notebooks[0] and return [{
notebook: { id: nb.id, name: nb.name }, file }] to return an object where file
is a copied object instead of the original reference.

In `@packages/mcp/src/tools/snapshots.ts`:
- Around line 227-229: handleSnapshotSplit currently writes legacy filenames
like {slug}_{projectId}_... while reads (findSnapshotsForProject) are scoped by
notebookId; update handleSnapshotSplit (and the other snapshot-write sites
referenced around lines ~318-320 and ~490-491) to include the notebookId in the
snapshot filenames (or use a single filename-construction helper) so that split
single-notebook snapshots are saved as notebook-scoped names (e.g., include
notebookId in the "latest" and timestamped filename patterns), and ensure any
helper/function used by findSnapshotsForProject matches the new naming
convention.

---

Outside diff comments:
In `@packages/cli/src/commands/lint.test.ts`:
- Around line 309-318: Add behavioral tests that assert the "multi-notebook"
warning is actually emitted and suppressed: in
packages/cli/src/commands/lint.test.ts (near the validCodes array and existing
lint command tests) add two Vitest cases — one that runs the lint command
against a synthetic multi-notebook project fixture and expects a warning with
code "multi-notebook" to appear in the output/diagnostics, and a second that
runs the same fixture but passes the CLI flag "--notebook" (or uses the helper
that simulates that flag) and asserts that no "multi-notebook" warning is
emitted; ensure you reference the existing validCodes array and the lint
invocation helper (e.g., runLintCommand / invokeCli or whatever test helper is
used) so the new tests integrate with the current suite.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 911ea08d-0dc1-463a-bc0b-536ed8b957b0

📥 Commits

Reviewing files that changed from the base of the PR and between 76f8895 and ff77deb.

📒 Files selected for processing (22)
  • FILES.md
  • docs/deepnote-format.md
  • packages/cli/src/cli.ts
  • packages/cli/src/commands/lint.test.ts
  • packages/cli/src/commands/split.test.ts
  • packages/cli/src/commands/split.ts
  • packages/cli/src/completions.ts
  • packages/cli/src/utils/analysis.ts
  • packages/cli/src/utils/output-persistence.test.ts
  • packages/cli/src/utils/output-persistence.ts
  • packages/convert/src/cli.ts
  • packages/convert/src/index.ts
  • packages/convert/src/snapshot/index.ts
  • packages/convert/src/snapshot/lookup.test.ts
  • packages/convert/src/snapshot/lookup.ts
  • packages/convert/src/snapshot/split.test.ts
  • packages/convert/src/snapshot/split.ts
  • packages/convert/src/snapshot/types.ts
  • packages/convert/src/write-deepnote-file.ts
  • packages/mcp/src/tools/execution.ts
  • packages/mcp/src/tools/snapshots.ts
  • skills/deepnote/SKILL.md

Comment thread packages/cli/src/commands/split.test.ts
Comment thread packages/cli/src/completions.ts
Comment thread packages/cli/src/utils/analysis.ts
Comment thread packages/convert/src/snapshot/save-execution-snapshot.test.ts Outdated
Comment thread packages/convert/src/snapshot/split.ts Outdated
Comment thread packages/mcp/src/tools/snapshots.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
packages/cli/src/utils/output-persistence.test.ts (1)

125-168: ⚠️ Potential issue | 🟡 Minor

Add a multi-notebook fallback test case.

These assertions only cover the single-notebook filename branch. Please add one case with file.project.notebooks.length > 1 and assert legacy format ({slug}_{projectId}_latest.snapshot.deepnote) is preserved.

As per coding guidelines, "Create comprehensive tests for all new features using Vitest" and "Test edge cases, error handling, and special characters in test files".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/utils/output-persistence.test.ts` around lines 125 - 168,
Add a test for the multi-notebook fallback branch of getSnapshotPath: use
loadTestFile() to get a file, mutate file.project.notebooks to have length > 1
(e.g., push another notebook or set an array with two notebook entries), choose
a sourcePath like '/path/to/project.deepnote', call getSnapshotPath(sourcePath,
file) and assert the returned path uses the legacy multi-notebook filename
format "{slug}_{projectId}_latest.snapshot.deepnote" (i.e., no notebook id in
the filename) while still being resolved under resolve('/path/to', 'snapshots',
...); reference getSnapshotPath and file.project.notebooks to locate the code
path to exercise.
packages/cli/src/commands/split.test.ts (1)

203-227: 🧹 Nitpick | 🔵 Trivial

Missing test for successful snapshot splitting.

Tests the warning path when snapshots fail, but no test verifies successful per-notebook snapshot generation. Consider adding a test that writes a valid snapshot alongside the source, then asserts per-notebook snapshots are created.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/split.test.ts` around lines 203 - 227, Add a new
test in packages/cli/src/commands/split.test.ts that mirrors the existing
"should print a warning..." case but for the success path: use
createMultiNotebookFile and serializeDeepnoteFile to write the input deepnote
file, create a valid snapshot file (named like
my-project_<projectId>_latest.snapshot.deepnote) containing a properly
serialized per-notebook snapshot payload, call createSplitAction(program) and
await action(inputPath, {}), then assert that per-notebook snapshot files (e.g.,
project-dashboard.snapshot.deepnote and project-data.snapshot.deepnote) exist in
tempDir (use fs.access or similar) and that consoleSpy output does not contain
the failure warning; reference createMultiNotebookFile, createSplitAction,
serializeDeepnoteFile and consoleSpy when locating where to add the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/src/commands/split.ts`:
- Around line 69-112: The final success line always reports "Split N snapshot(s)
into ..." using existingSnapshots.length even if some failed, which can mislead
users; update the code in the snapshot handling block (around
findSnapshotsForProject, loadSnapshotFile, splitSnapshotByNotebooks,
serializeDeepnoteSnapshot and the snapshotFailures array) to compute and report
the actual number of successfully split snapshots (e.g., successCount =
existingSnapshots.length - snapshotFailures.length) or include both counts
(e.g., "Split X of N snapshot(s); Y failed") and use that value in the output
call instead of existingSnapshots.length so the summary accurately reflects
failures.

In `@packages/cli/src/utils/output-persistence.ts`:
- Around line 107-113: The generated snapshot filenames pass notebookId verbatim
to generateSnapshotFilename, but parseSnapshotFilename only recognizes strict
UUID/hex notebook IDs; to fix, apply the same validation guard used by
parseSnapshotFilename to the notebookId before calling generateSnapshotFilename
(i.e., if notebookId does not match the UUID/hex regex, set it to undefined) —
update both occurrences where notebookId is computed (the notebookId assignment
used with generateSnapshotFilename around the current block and the similar
block at the later occurrence referenced in the comment) so filenames and lookup
parsing remain consistent.

---

Duplicate comments:
In `@packages/cli/src/commands/split.test.ts`:
- Around line 203-227: Add a new test in packages/cli/src/commands/split.test.ts
that mirrors the existing "should print a warning..." case but for the success
path: use createMultiNotebookFile and serializeDeepnoteFile to write the input
deepnote file, create a valid snapshot file (named like
my-project_<projectId>_latest.snapshot.deepnote) containing a properly
serialized per-notebook snapshot payload, call createSplitAction(program) and
await action(inputPath, {}), then assert that per-notebook snapshot files (e.g.,
project-dashboard.snapshot.deepnote and project-data.snapshot.deepnote) exist in
tempDir (use fs.access or similar) and that consoleSpy output does not contain
the failure warning; reference createMultiNotebookFile, createSplitAction,
serializeDeepnoteFile and consoleSpy when locating where to add the test.

In `@packages/cli/src/utils/output-persistence.test.ts`:
- Around line 125-168: Add a test for the multi-notebook fallback branch of
getSnapshotPath: use loadTestFile() to get a file, mutate file.project.notebooks
to have length > 1 (e.g., push another notebook or set an array with two
notebook entries), choose a sourcePath like '/path/to/project.deepnote', call
getSnapshotPath(sourcePath, file) and assert the returned path uses the legacy
multi-notebook filename format "{slug}_{projectId}_latest.snapshot.deepnote"
(i.e., no notebook id in the filename) while still being resolved under
resolve('/path/to', 'snapshots', ...); reference getSnapshotPath and
file.project.notebooks to locate the code path to exercise.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3e2da83c-c06c-4332-b72d-c303f686b852

📥 Commits

Reviewing files that changed from the base of the PR and between ff77deb and 0a32dbb.

📒 Files selected for processing (15)
  • packages/cli/src/cli.ts
  • packages/cli/src/commands/split.test.ts
  • packages/cli/src/commands/split.ts
  • packages/cli/src/utils/output-persistence.test.ts
  • packages/cli/src/utils/output-persistence.ts
  • packages/convert/src/index.ts
  • packages/convert/src/snapshot/index.ts
  • packages/convert/src/snapshot/lookup.test.ts
  • packages/convert/src/snapshot/lookup.ts
  • packages/convert/src/snapshot/snapshot-integration.test.ts
  • packages/convert/src/snapshot/split.test.ts
  • packages/convert/src/snapshot/split.ts
  • packages/convert/src/snapshot/types.ts
  • packages/convert/src/write-deepnote-file.ts
  • packages/mcp/src/tools/execution.ts

Comment thread packages/cli/src/commands/split.ts Outdated
Comment thread packages/cli/src/utils/output-persistence.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@skills/deepnote/SKILL.md`:
- Around line 116-124: The "Location" example in SKILL.md must show the
single-notebook filename pattern so readers don't think {notebookId} is
optional; update the example block to include both patterns by adding an example
using `{slug}_{projectId}_{notebookId}_{timestamp}.snapshot.deepnote` alongside
the existing legacy `{slug}_{projectId}_{timestamp}.snapshot.deepnote`, and
ensure the explanatory text references the `{notebookId}` token explicitly so
users see when to use each format.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5e4f06f0-8125-4da5-ae52-133558180336

📥 Commits

Reviewing files that changed from the base of the PR and between 0a32dbb and d776292.

📒 Files selected for processing (2)
  • docs/deepnote-format.md
  • skills/deepnote/SKILL.md

Comment thread skills/deepnote/SKILL.md
…s and enhance split functionality for non-init notebooks

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/src/commands/split.test.ts`:
- Around line 236-261: Add a new test that mirrors the existing failure case but
uses a valid snapshot so we can assert successful per-notebook snapshot files
are created: create a test (e.g., "should split a valid snapshot into
per-notebook snapshots") that uses createMultiNotebookFile and writes the
project.deepnote via serializeDeepnoteFile to inputPath, create snapshotsDir and
write a valid snapshot file named using the same pattern
(`my-project_${projectId}_latest.snapshot.deepnote`) containing a properly
serialized snapshot (use the test helper you have for snapshots, analogous to
serializeDeepnoteFile), invoke the split action via createSplitAction(program)
and then assert that per-notebook snapshot files (e.g., names derived from the
notebook slugs like project-dashboard_<projectId>_latest.snapshot.deepnote and
project-data_...snapshot.deepnote) exist in snapshotsDir and contain the
expected snapshot content; reference createSplitAction, createMultiNotebookFile,
serializeDeepnoteFile, tempDir, snapshotsDir and projectId to locate where to
change/add the test.

In `@packages/cli/src/commands/split.ts`:
- Around line 93-101: The code directly reads initSnapshot.project.notebooks[0]
and mainSnapshot.project.notebooks[0] which can be undefined if either notebooks
array is empty; update the logic in the split handler to defensively check that
initSnapshot.project.notebooks and mainSnapshot.project.notebooks are non-empty
before assigning initNb and mainNb (e.g., validate array length or presence),
and if either is empty, handle it explicitly (throw a clear error or fallback)
before constructing nbSnapshot so nbSnapshot.project.notebooks never contains
undefined entries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3b99d1e1-e32c-486f-be01-e937699a9483

📥 Commits

Reviewing files that changed from the base of the PR and between d776292 and 2d61d4c.

📒 Files selected for processing (6)
  • packages/cli/src/commands/split.test.ts
  • packages/cli/src/commands/split.ts
  • packages/convert/src/index.ts
  • packages/convert/src/snapshot/index.ts
  • packages/convert/src/snapshot/split.test.ts
  • packages/convert/src/snapshot/split.ts

Comment thread packages/cli/src/commands/split.test.ts Outdated
Comment thread packages/cli/src/commands/split.ts Outdated
- Add resolveSnapshotNotebookId for single-notebook and init+main split files so snapshot paths stay distinct per main notebook when project.id is shared.

- Route snapshot save/load/merge and MCP split through the helper and generateSnapshotFilename.

- Extend parseSnapshotFilename notebook segment to match generateSnapshotFilename output (e.g. notebook-1, nb-1) while keeping legacy two-segment names.

- Add regression tests for init+main saves, sibling isolation, MCP split/load, and non-UUID filename parsing.

Made-with: Cursor

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/cli/src/utils/output-persistence.test.ts (1)

270-333: ⚠️ Potential issue | 🟡 Minor

Add a true multi-notebook legacy-path case.

This new coverage pins down the init+main branch, but the backward-compatible branch where resolveSnapshotNotebookId(...) returns undefined is still untested. Please add one fixture with either 3+ notebooks or 2 notebooks without initNotebookId and assert it keeps the legacy {slug}_{projectId}_latest.snapshot.deepnote shape.

As per coding guidelines, "Create comprehensive tests for all new features using Vitest" and "Test edge cases, error handling, and special characters in test files".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/utils/output-persistence.test.ts` around lines 270 - 333,
The test suite is missing a case for the legacy multi-notebook path when
resolveSnapshotNotebookId(...) returns undefined; add a new test (in
packages/cli/src/utils/output-persistence.test.ts) that constructs a
DeepnoteFile whose project either has 3+ notebooks or has 2 notebooks but no
initNotebookId, call saveExecutionSnapshot(sourcePath, file, outputs, timing)
and assert the returned result.snapshotPath and result.timestampedSnapshotPath
follow the legacy pattern (contain `_{projectId}_latest.snapshot.deepnote` or
the `{slug}_{projectId}_latest.snapshot.deepnote` shape). Use the same
sourcePath/outputs/timing variables pattern as the existing test so the new test
verifies the fallback branch in saveExecutionSnapshot and the behavior of
resolveSnapshotNotebookId.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/mcp/src/tools/execution.ts`:
- Around line 268-275: Existing code duplicates notebook-aware snapshot filename
logic; extract a shared helper that centralizes slug/notebook/timestamp filename
generation and path resolution so other modules can reuse it. Create a function
(e.g., buildSnapshotPaths or makeSnapshotFilenames) that takes the inputs used
here (file, slug, snapshotDir, timing/finishedAt) and internally calls
resolveSnapshotNotebookId and generateSnapshotFilename to produce both the
timestamped and latest filenames and their resolved paths
(timestampedSnapshotPath, snapshotPath), keeping the timestamp formatting logic
identical (new Date(...).toISOString().replace(/[:.]/g, '-').slice(0, 19)); then
replace the duplicated block in this file to call the new helper and update
other places (where the same logic exists) to use it too so future
snapshot-format changes are made in one spot.

---

Duplicate comments:
In `@packages/cli/src/utils/output-persistence.test.ts`:
- Around line 270-333: The test suite is missing a case for the legacy
multi-notebook path when resolveSnapshotNotebookId(...) returns undefined; add a
new test (in packages/cli/src/utils/output-persistence.test.ts) that constructs
a DeepnoteFile whose project either has 3+ notebooks or has 2 notebooks but no
initNotebookId, call saveExecutionSnapshot(sourcePath, file, outputs, timing)
and assert the returned result.snapshotPath and result.timestampedSnapshotPath
follow the legacy pattern (contain `_{projectId}_latest.snapshot.deepnote` or
the `{slug}_{projectId}_latest.snapshot.deepnote` shape). Use the same
sourcePath/outputs/timing variables pattern as the existing test so the new test
verifies the fallback branch in saveExecutionSnapshot and the behavior of
resolveSnapshotNotebookId.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8acef060-70c7-4e68-b4c5-5b136fb5a355

📥 Commits

Reviewing files that changed from the base of the PR and between 2d61d4c and 6e621f5.

📒 Files selected for processing (13)
  • packages/cli/src/utils/output-persistence.test.ts
  • packages/cli/src/utils/output-persistence.ts
  • packages/convert/src/cli.ts
  • packages/convert/src/index.ts
  • packages/convert/src/snapshot/index.ts
  • packages/convert/src/snapshot/lookup.test.ts
  • packages/convert/src/snapshot/lookup.ts
  • packages/convert/src/snapshot/snapshot-integration.test.ts
  • packages/convert/src/snapshot/snapshot-notebook-id.ts
  • packages/convert/src/write-deepnote-file.ts
  • packages/mcp/src/tools/execution.ts
  • packages/mcp/src/tools/snapshots.test.ts
  • packages/mcp/src/tools/snapshots.ts

Comment thread packages/mcp/src/tools/execution.ts Outdated
tkislan added 2 commits April 14, 2026 18:55
- Add logic to resolve and compose init notebooks alongside main notebooks during execution.
- Enhance project setup to include init block IDs and notebook IDs for proper execution flow.
- Update validation to ensure required integrations are detected for both init and main notebooks.
- Introduce tests to verify the correct loading and execution of sibling init notebooks, ensuring they are prioritized correctly in the execution order.
- Refactor related functions to support the new init resolution logic, improving overall clarity and maintainability.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/cli/src/commands/run.ts`:
- Around line 205-213: The preflight plumbing is using initNotebookName to key
the init notebook scope which can collide when names aren't unique; update all
places that reference or store initNotebookName to use initNotebookId instead so
the init scope is keyed by the unique identifier. Search for uses of
initNotebookName (including where scope/inputs/dry-run/requirement checks are
built) and replace them with initNotebookId, ensuring variables, function
parameters and any maps/lookup keys (e.g., where the init notebook is added to
scope, listed in --list-inputs, included in dry-run plans, and in requirement
checks) are switched to use initNotebookId and that initNotebookName is only
retained for diagnostics where uniqueness is not required. Verify
functions/methods that accept the notebook identifier (references to
initNotebookId) are passed the id and update any validation or filter logic that
previously compared names to compare ids instead.

In `@packages/cli/src/commands/split.ts`:
- Around line 125-126: The snapshot writes (creating snapshotPath via join and
calling fs.writeFile with serializeDeepnoteSnapshot(nbSnapshot)) always use the
default write mode and ignore the CLI --force option; update the fs.writeFile
calls in split.ts (the ones that write snapshotPath and the other write at lines
~141-142) to respect the force flag by passing write options: use
exclusive-write mode when force is false (e.g., flag 'wx') and normal overwrite
when force is true (flag 'w'), so the writes will error if a snapshot exists
unless --force was provided.

In `@packages/convert/src/load-runnable-file.ts`:
- Around line 78-85: The .deepnote branch in loadRunnableFile currently catches
fs.readFile errors and throws a new LoadRunnableFileError, which breaks the
contract that filesystem errors are propagated; modify the catch in the ext ===
'.deepnote' block (around rawBytes/absolutePath) to rethrow the original
readError (or remove the try/catch) instead of wrapping it in
LoadRunnableFileError so callers receive the original errno/code details.

In `@packages/convert/src/snapshot/resolve-init.ts`:
- Around line 345-350: The normalization in areSettingsEqual currently treats
undefined/null and {} differently; update the norm function so that undefined,
null, and an empty plain object ({}) all normalize to the same sentinel (e.g.,
'null') before JSON.stringify with sortedKeysReplacer. Concretely, inside
areSettingsEqual's norm, check if value is undefined or null or (typeof value
=== 'object' && value !== null && Object.keys(value).length === 0) and return
'null' in those cases, otherwise return JSON.stringify(value,
sortedKeysReplacer).

In `@packages/mcp/src/tools/execution.ts`:
- Around line 322-333: The code currently maps a notebookFilter match back to
engineNotebookName (string) which loses the exact-ID selection; instead, when
notebookFilter matched by id (i.e., found.id === notebookFilter or original
filter looked like an id), do not collapse to name—leave notebookName undefined
and include the exact id in preludeNotebookIds (use targetNotebookId / found.id)
so engine.runProject receives the precise notebook id via preludeNotebookIds;
alternatively, if you must use notebookName, detect duplicate notebook names in
file.project.notebooks and throw/return an error when duplicates exist to avoid
ambiguous selection. Ensure changes touch the variables engineNotebookName,
targetNotebookId, and the call to engine.runProject (preludeNotebookIds /
notebookName) so selection is unambiguous.

In `@skills/deepnote/SKILL.md`:
- Around line 274-287: The Markdown table has a long unwrapped cell (the
"deepnote split <path>" row) causing Prettier/formatting failures; fix by
reflowing that long description into shorter lines or run Prettier to auto-wrap
the table so the row fits within line-length limits, ensuring the table syntax
remains valid and all pipes/columns (e.g., the "deepnote split <path>" row)
align with the other rows.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b11ed399-6f20-48de-bd6a-77f2f4ed506f

📥 Commits

Reviewing files that changed from the base of the PR and between 9823ab7 and 49eeee6.

📒 Files selected for processing (21)
  • packages/cli/src/commands/run.test.ts
  • packages/cli/src/commands/run.ts
  • packages/cli/src/commands/split.test.ts
  • packages/cli/src/commands/split.ts
  • packages/cli/src/integrations/collect-integrations.ts
  • packages/cli/src/utils/format-converter.ts
  • packages/cli/src/utils/output-persistence.ts
  • packages/convert/src/index.ts
  • packages/convert/src/load-runnable-file.ts
  • packages/convert/src/snapshot/index.ts
  • packages/convert/src/snapshot/resolve-init.test.ts
  • packages/convert/src/snapshot/resolve-init.ts
  • packages/convert/src/snapshot/save-execution-snapshot.ts
  • packages/convert/src/snapshot/snapshot-notebook-id.ts
  • packages/convert/src/snapshot/split.test.ts
  • packages/convert/src/snapshot/split.ts
  • packages/convert/src/snapshot/types.ts
  • packages/mcp/src/tools/execution.test.ts
  • packages/mcp/src/tools/execution.ts
  • packages/runtime-core/src/execution-engine.ts
  • skills/deepnote/SKILL.md

Comment thread packages/cli/src/commands/run.ts Outdated
Comment thread packages/cli/src/commands/split.ts Outdated
Comment thread packages/convert/src/load-runnable-file.ts Outdated
Comment thread packages/convert/src/snapshot/resolve-init.ts
Comment thread packages/mcp/src/tools/execution.ts Outdated
Comment thread skills/deepnote/SKILL.md Outdated
tkislan and others added 3 commits June 9, 2026 10:08
Notebook id, project id, and slug are parsed from untrusted .deepnote
files as arbitrary strings and interpolated into snapshot filenames that
callers join to the snapshots/ directory and write to disk. A value
containing path separators or `..` could escape that directory and
overwrite arbitrary files. Run each attacker-derived component through
sanitizeFilenameComponent ([^A-Za-z0-9_-] -> _) at the single chokepoint
feeding all write sinks; this is a no-op for the ids snapshot readers
expect (UUIDs, 32-char hex, notebook-1). timestamp is internally
generated and left intact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a multi-notebook .deepnote file sets initNotebookId to an id that
matches no notebook (e.g. a deleted/renamed notebook), splitByNotebooks
emitted no init entry but still spread the dangling initNotebookId into
every per-notebook output. `deepnote run` then attempted sibling-init
resolution, found no matching init file, and failed with exit 2 — so a
"successful" split produced non-runnable files.

Detect the dangling case (initNotebookId set but no matching notebook)
and delete initNotebookId from each emitted entry so the splits are plain
no-init files. The valid-init path still preserves the id for the
sibling-init resolver. Tighten the missing-notebook test to assert the id
is dropped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On a split/init file with init prelude blocks, a misspelled or
non-executable --block was folded into the engine filter alongside the
executable init blocks. That kept the filter non-empty, so the engine's
missing/non-executable-target validation was skipped: only init ran and
the command exited 0 without ever running the requested block (the
dry-run plan had the same blind spot).

Validate options.block via assertExecutableBlockExists in the shared
buildEngineExecutionScope before init prelude ids are folded in, guarded
on initBlockIds.size > 0 (the bug condition). Plain files are still
validated downstream (engine for run, collectExecutableBlocks for
dry-run), so their behavior is unchanged. Split/init files now fail
loudly with the same error as plain files. Covers both run and dry-run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/cli/src/commands/run.ts (1)

535-550: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Keep this preflight scope keyed by init notebook ID.

This new guard still resolves its prelude scope through additionalNotebookNames, so a same-named non-init notebook can satisfy assertExecutableBlockExists() even though execution stays keyed by preludeNotebookIds. That keeps --block preflight/dry-run out of sync with the engine on split projects with duplicate notebook names.

Thread initNotebookId through these scope helpers and keep initNotebookName diagnostic-only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/commands/run.ts` around lines 535 - 550, The preflight block
validation uses getNotebooksForExecutionScope with additionalNotebookNames and
can pick a same-named non-init notebook; instead thread the init notebook
identity (initNotebookId) into the scope resolution so the preflight scope
matches the engine's execution scope instead of relying on initNotebookName.
Update the call-site around options.block && initBlockIds to pass initNotebookId
to getNotebooksForExecutionScope (and any downstream helpers it calls) and keep
initNotebookName only for logging/diagnostics; ensure
assertExecutableBlockExists is invoked with the notebook list resolved using
initNotebookId so the dry-run/preflight and engine use the same notebook-keyed
scope.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/convert/src/snapshot/split.ts`:
- Around line 54-61: The filename builder generateSnapshotFilename currently
sanitizes slug, projectId and notebookId but interpolates timestamp raw; update
generateSnapshotFilename to pass timestamp through sanitizeFilenameComponent
(e.g. const safeTimestamp = sanitizeFilenameComponent(timestamp)) and use
safeTimestamp in both the notebookId and non-notebookId return branches so the
produced basename cannot contain traversal or unsafe characters while preserving
existing sanitization of slug/projectId/notebookId.

---

Duplicate comments:
In `@packages/cli/src/commands/run.ts`:
- Around line 535-550: The preflight block validation uses
getNotebooksForExecutionScope with additionalNotebookNames and can pick a
same-named non-init notebook; instead thread the init notebook identity
(initNotebookId) into the scope resolution so the preflight scope matches the
engine's execution scope instead of relying on initNotebookName. Update the
call-site around options.block && initBlockIds to pass initNotebookId to
getNotebooksForExecutionScope (and any downstream helpers it calls) and keep
initNotebookName only for logging/diagnostics; ensure
assertExecutableBlockExists is invoked with the notebook list resolved using
initNotebookId so the dry-run/preflight and engine use the same notebook-keyed
scope.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 09542121-acf1-41e3-a64d-c8adf6601248

📥 Commits

Reviewing files that changed from the base of the PR and between 49eeee6 and 82b3ea9.

📒 Files selected for processing (4)
  • packages/cli/src/commands/run.test.ts
  • packages/cli/src/commands/run.ts
  • packages/convert/src/snapshot/split.test.ts
  • packages/convert/src/snapshot/split.ts

Comment thread packages/convert/src/snapshot/split.ts Outdated
tkislan and others added 2 commits June 10, 2026 20:04
Add Bash directory completion for `deepnote split -o/--output`, document
single-notebook snapshot naming in the skill file, and reformat the CLI
reference table so Prettier passes.

Co-authored-by: Cursor <cursoragent@cursor.com>
Trim comments introduced in this branch to at most a single line:
- multi-line // blocks and JSDoc condensed to a one-line summary
- comments on newly-added properties dropped where sibling fields
  are uncommented
- self-evident narration removed, incl. Arrange/Act/Assert test markers
- pre-existing comments and functional directives (biome-ignore, etc.)
  left untouched

Comment-only change; no code modified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/cli/src/commands/run.test.ts (1)

1910-2383: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add one sibling-init regression for duplicate notebook names.

Add a case where a non-init notebook shares initNotebookName, then assert preflight scope still tracks the true init notebook only. This will lock in the ID-based fix and prevent regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/commands/run.test.ts` around lines 1910 - 2383, Add a new
test case to cover the regression where a non-init notebook shares the same name
as the init notebook: create files via makeInitFile()/makeMainFile() plus an
extra sibling notebook file whose notebook.name equals the init notebook's name,
call action(mainPath, { notebook: 'Main' }) (or the appropriate invocation used
in similar tests), then assert mockRunProject was called once and that the
runOptions.preludeNotebookIds is a Set containing only INIT_NB_ID (and that the
project's notebooks order remains [INIT_NB_ID, MAIN_NB_ID]); place this
alongside the other "sibling init resolution" tests so it exercises the resolver
and preflight logic (referencing action, mockRunProject, and
runOptions.preludeNotebookIds).
packages/cli/src/commands/split.test.ts (1)

323-467: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add explicit snapshot-collision tests for --force behavior.

Current tests cover snapshot splitting and corrupt-snapshot warnings, but not “existing snapshot output already exists” behavior. Add one case for default mode (must not overwrite) and one for force: true (must overwrite).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/commands/split.test.ts` around lines 323 - 467, Add two
tests around createSplitAction: one asserting that when a snapshot file already
exists the split action does not overwrite it by default
(createSplitAction(program); call action(inputPath, {}) and then verify the
original snapshot file content remains unchanged and no new snapshot replace
occurs), and a second asserting that when invoked with force: true (call
action(inputPath, { force: true }) or the equivalent option) the existing
snapshot is overwritten (write an identifiable original snapshot to snapshotsDir
before running, run action, then assert the snapshot file content has changed to
the new split output). Reference createSplitAction, action(inputPath, {}),
action(inputPath, { force: true }), snapshotsDir, and the snapshot filenames to
locate where to add these assertions.
packages/mcp/src/tools/execution.ts (1)

524-533: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Init block runs currently widen to the whole init notebook.

effectiveBlockIds is built from initBlockIds whenever composition is active. If the requested blockId is itself inside the init notebook, this schedules every executable init block instead of just the requested block. Gate the prelude on targetNotebook.id !== initNotebookId; the dry-run branch should mirror the same check.

Minimal fix
-    const effectiveBlockIds = initBlockIds.size > 0 ? [...initBlockIds, targetBlock.id] : undefined
+    const includeInitPrelude =
+      initNotebookId !== undefined && targetNotebook.id !== initNotebookId && initBlockIds.size > 0
+    const effectiveBlockIds = includeInitPrelude ? [...initBlockIds, targetBlock.id] : undefined

     const summary = await engine.runProject(file, {
       notebookName: targetNotebook.name,
       blockId: effectiveBlockIds === undefined ? targetBlock.id : undefined,
       blockIds: effectiveBlockIds,
-      preludeNotebookIds:
-        initNotebookId !== undefined && initNotebookId !== targetNotebook.id ? new Set([initNotebookId]) : undefined,
+      preludeNotebookIds: includeInitPrelude ? new Set([initNotebookId]) : undefined,
       inputs,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/mcp/src/tools/execution.ts` around lines 524 - 533, The current
composition always expands initBlockIds into effectiveBlockIds even when the
requested targetBlock sits in the same init notebook, which causes all
executable init blocks to run; update the logic around effectiveBlockIds (and
the engine.runProject preludeNotebookIds and the dry-run branch) to only compose
and include initBlockIds when initNotebookId !== targetNotebook.id (i.e., gate
the prelude on targetNotebook.id !== initNotebookId) so that if the target block
is inside the init notebook we pass only that blockId, not the whole init
notebook's executable blocks; apply the same conditional in the dry-run branch
to mirror the behavior.
♻️ Duplicate comments (6)
packages/cli/src/commands/split.ts (2)

149-149: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Snapshot success summary is inaccurate on partial failure.

Line 149 always reports total discovered snapshots, even when some snapshot splits failed and were logged as warnings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/commands/split.ts` at line 149, The summary message uses
existingSnapshots.length but does not account for failures; change the split
logic to count only successful splits (e.g., introduce a successfulSplits
counter updated inside the block that performs each split) and then replace
existingSnapshots.length with that counter in the output call (the line calling
output(`\n  ${c.dim(`Split ${existingSnapshots.length} snapshot(s) into
${snapshotDir}`)}`)). Ensure the counter is initialized before processing,
incremented only on confirmed success (not on warnings/errors), and used in the
final output so the message reflects partial failures accurately.

117-117: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Snapshot writes still bypass --force and can overwrite existing files.

Line 117 and Line 132 use default write mode ('w'), so reruns clobber existing *.snapshot.deepnote outputs even when --force is not set.

Suggested patch
-              await fs.writeFile(snapshotPath, serializeDeepnoteSnapshot(nbSnapshot), 'utf-8')
+              try {
+                await fs.writeFile(snapshotPath, serializeDeepnoteSnapshot(nbSnapshot), {
+                  encoding: 'utf-8',
+                  flag: force ? 'w' : 'wx',
+                })
+              } catch (err) {
+                if (!force && isErrnoException(err, 'EEXIST')) {
+                  throw new Error(`Output file already exists: ${snapshotPath}. Use --force to overwrite.`)
+                }
+                throw err
+              }
@@
-                await fs.writeFile(snapshotPath, serializeDeepnoteSnapshot(initSnapshot), 'utf-8')
+                try {
+                  await fs.writeFile(snapshotPath, serializeDeepnoteSnapshot(initSnapshot), {
+                    encoding: 'utf-8',
+                    flag: force ? 'w' : 'wx',
+                  })
+                } catch (err) {
+                  if (!force && isErrnoException(err, 'EEXIST')) {
+                    throw new Error(`Output file already exists: ${snapshotPath}. Use --force to overwrite.`)
+                  }
+                  throw err
+                }

Also applies to: 132-132

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/commands/split.ts` at line 117, The snapshot writes
currently call fs.writeFile(snapshotPath, serializeDeepnoteSnapshot(...),
'utf-8') and always clobber files; update both occurrences (the writes using
snapshotPath and serializeDeepnoteSnapshot at lines noted) to honor the CLI
--force flag (likely exposed as a boolean like force or flags.force). Use write
options: if force is true use 'w' (or default) else use { encoding: 'utf-8',
flag: 'wx' } so the write will fail if the file exists, and catch EEXIST to
surface a friendly error/exit; ensure both write sites are changed consistently
and any error handling uses the same message.
packages/cli/src/commands/run.ts (1)

346-349: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Prelude scope is keyed by notebook name; this is ambiguous and can target the wrong notebook.

These paths include init/prelude notebooks via names, not IDs. If two notebooks share a name, preflight scope can diverge from runtime scope (which uses preludeNotebookIds by ID).

Use additionalNotebookIds end-to-end (scope selection, input listing, validation, and integration collection), and only keep names for display.

Also applies to: 437-444, 747-748, 928-929

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/commands/run.ts` around lines 346 - 349, The code calls
collectRequiredIntegrationIds(file, options.notebook, { additionalNotebookNames:
initNotebookName ? [initNotebookName] : [] }) which passes the init/prelude
notebook by name (initNotebookName) causing ambiguous scope when multiple
notebooks share a name; replace name-based usage with ID-based propagation:
compute the init/prelude notebook ID (use preludeNotebookIds or resolve
initNotebookName to its ID earlier) and pass it via additionalNotebookIds to
collectRequiredIntegrationIds and all related flows (scope selection, input
listing, validation, integration collection), keep notebook names only for
display; update the other occurrences that pass additionalNotebookNames (the
blocks referenced around the other ranges) to use additionalNotebookIds
consistently.
packages/convert/src/load-runnable-file.ts (1)

43-43: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Align .deepnote read-error behavior with the stated contract.

Line 43 says fs errors are propagated unwrapped, but Lines 63-68 wrap .deepnote read failures in LoadRunnableFileError. This makes .deepnote behave differently from .ipynb/.py/.qmd reads and changes upstream error handling semantics.

Suggested fix
   if (ext === '.deepnote') {
-    let rawBytes: Buffer
-    try {
-      rawBytes = await fs.readFile(absolutePath)
-    } catch (readError) {
-      const message = readError instanceof Error ? readError.message : String(readError)
-      throw new LoadRunnableFileError(`Failed to read .deepnote file: ${absolutePath}\n\nRead error: ${message}`)
-    }
+    const rawBytes = await fs.readFile(absolutePath)

Also applies to: 61-68

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/convert/src/load-runnable-file.ts` at line 43, The .deepnote read
currently wraps read failures in LoadRunnableFileError, breaking the stated
contract that filesystem errors should be propagated unwrapped; in the
loadRunnableFile implementation, update the .deepnote file-read path (the block
that currently catches errors at lines handling ".deepnote") to stop catching
and rewrapping raw fs errors—only convert non-fs/content-validation errors to
LoadRunnableFileError (or rethrow after inspecting error type), and let native
fs errors bubble up unchanged so .deepnote behaves consistently with
.ipynb/.py/.qmd; reference LoadRunnableFileError and the .deepnote read block
when making this change.
packages/convert/src/snapshot/split.ts (1)

33-50: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Sanitize timestamp too.

slug, projectId, and notebookId are sanitized, but timestamp still flows into the basename verbatim. As written, generateSnapshotFilename({ ..., timestamp: '../x' }) can escape the target snapshot directory once callers join() and write the result.

Suggested fix
 export function generateSnapshotFilename(params: GenerateSnapshotFilenameParams): string {
   const { slug, projectId, notebookId, timestamp = 'latest' } = params
   const safeSlug = sanitizeFilenameComponent(slug)
   const safeProjectId = sanitizeFilenameComponent(projectId)
+  const safeTimestamp = sanitizeFilenameComponent(timestamp)
   if (notebookId) {
-    return `${safeSlug}_${safeProjectId}_${sanitizeFilenameComponent(notebookId)}_${timestamp}.snapshot.deepnote`
+    const safeNotebookId = sanitizeFilenameComponent(notebookId)
+    return `${safeSlug}_${safeProjectId}_${safeNotebookId}_${safeTimestamp}.snapshot.deepnote`
   }
-  return `${safeSlug}_${safeProjectId}_${timestamp}.snapshot.deepnote`
+  return `${safeSlug}_${safeProjectId}_${safeTimestamp}.snapshot.deepnote`
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/convert/src/snapshot/split.ts` around lines 33 - 50, The timestamp
parameter is not sanitized causing path-traversal when interpolated into the
filename; update generateSnapshotFilename to pass timestamp through
sanitizeFilenameComponent (use sanitizeFilenameComponent(timestamp || 'latest'))
before building the basename so the returned string always uses safeSlug,
safeProjectId, sanitized notebookId and sanitized timestamp (preserving the
.snapshot.deepnote suffix) to prevent directory escape; reference
generateSnapshotFilename and sanitizeFilenameComponent when making the change.
packages/mcp/src/tools/execution.ts (1)

237-250: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Notebook selection is still ambiguous when names collide.

Dry-run picks a single notebook via find(...), but actual execution collapses that choice back to found.name before calling engine.runProject. The engine filters by name, so passing an ID for one duplicate-name notebook can still execute every notebook sharing that name, while the preview shows only one. Fail fast on non-unique names here until the engine can target notebook IDs directly.

Suggested guard
   let notebooks = file.project.notebooks
+  let selectedNotebook: DeepnoteFile['project']['notebooks'][number] | undefined
   if (notebookFilter) {
-    const found = file.project.notebooks.find(n => n.name === notebookFilter || n.id === notebookFilter)
-    if (!found) {
+    const matches = file.project.notebooks.filter(n => n.name === notebookFilter || n.id === notebookFilter)
+    selectedNotebook = matches.find(n => n.id === notebookFilter) ?? matches[0]
+    if (!selectedNotebook) {
       return {
         content: [{ type: 'text', text: `Notebook not found: ${notebookFilter}` }],
         isError: true,
       }
     }
+    const nameCollisions = file.project.notebooks.filter(n => n.name === selectedNotebook.name)
+    if (nameCollisions.length > 1) {
+      return {
+        content: [
+          {
+            type: 'text',
+            text: `Notebook filter "${notebookFilter}" is ambiguous because notebook name "${selectedNotebook.name}" is duplicated. Use a unique notebook name.`,
+          },
+        ],
+        isError: true,
+      }
+    }
     const initNotebook =
       initNotebookId !== undefined ? file.project.notebooks.find(n => n.id === initNotebookId) : undefined
-    notebooks = initNotebook !== undefined && initNotebook.id !== found.id ? [initNotebook, found] : [found]
+    notebooks =
+      initNotebook !== undefined && initNotebook.id !== selectedNotebook.id
+        ? [initNotebook, selectedNotebook]
+        : [selectedNotebook]
   }
@@
-    if (notebookFilter) {
-      const found = file.project.notebooks.find(n => n.name === notebookFilter || n.id === notebookFilter)
-      engineNotebookName = found?.name
-      targetNotebookId = found?.id
-    }
+    if (selectedNotebook) {
+      engineNotebookName = selectedNotebook.name
+      targetNotebookId = selectedNotebook.id
+    }

Also applies to: 310-322

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/mcp/src/tools/execution.ts` around lines 237 - 250, The selection
logic for notebookFilter uses find(...) and then later relies on
engine.runProject filtering by name, which causes ambiguity when multiple
notebooks share the same name; update the block that computes notebooks
(variables: notebookFilter, found, initNotebook, file.project.notebooks) to
detect when notebookFilter matched by name but more than one notebook has that
name and fail fast with an error asking for the notebook ID (or require using an
ID match), instead of proceeding; implement the same uniqueness guard in the
analogous selection code later (the other notebooks filtering block) so we never
pass an ambiguous name to engine.runProject.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/cli/src/commands/run.test.ts`:
- Around line 1910-2383: Add a new test case to cover the regression where a
non-init notebook shares the same name as the init notebook: create files via
makeInitFile()/makeMainFile() plus an extra sibling notebook file whose
notebook.name equals the init notebook's name, call action(mainPath, { notebook:
'Main' }) (or the appropriate invocation used in similar tests), then assert
mockRunProject was called once and that the runOptions.preludeNotebookIds is a
Set containing only INIT_NB_ID (and that the project's notebooks order remains
[INIT_NB_ID, MAIN_NB_ID]); place this alongside the other "sibling init
resolution" tests so it exercises the resolver and preflight logic (referencing
action, mockRunProject, and runOptions.preludeNotebookIds).

In `@packages/cli/src/commands/split.test.ts`:
- Around line 323-467: Add two tests around createSplitAction: one asserting
that when a snapshot file already exists the split action does not overwrite it
by default (createSplitAction(program); call action(inputPath, {}) and then
verify the original snapshot file content remains unchanged and no new snapshot
replace occurs), and a second asserting that when invoked with force: true (call
action(inputPath, { force: true }) or the equivalent option) the existing
snapshot is overwritten (write an identifiable original snapshot to snapshotsDir
before running, run action, then assert the snapshot file content has changed to
the new split output). Reference createSplitAction, action(inputPath, {}),
action(inputPath, { force: true }), snapshotsDir, and the snapshot filenames to
locate where to add these assertions.

In `@packages/mcp/src/tools/execution.ts`:
- Around line 524-533: The current composition always expands initBlockIds into
effectiveBlockIds even when the requested targetBlock sits in the same init
notebook, which causes all executable init blocks to run; update the logic
around effectiveBlockIds (and the engine.runProject preludeNotebookIds and the
dry-run branch) to only compose and include initBlockIds when initNotebookId !==
targetNotebook.id (i.e., gate the prelude on targetNotebook.id !==
initNotebookId) so that if the target block is inside the init notebook we pass
only that blockId, not the whole init notebook's executable blocks; apply the
same conditional in the dry-run branch to mirror the behavior.

---

Duplicate comments:
In `@packages/cli/src/commands/run.ts`:
- Around line 346-349: The code calls collectRequiredIntegrationIds(file,
options.notebook, { additionalNotebookNames: initNotebookName ?
[initNotebookName] : [] }) which passes the init/prelude notebook by name
(initNotebookName) causing ambiguous scope when multiple notebooks share a name;
replace name-based usage with ID-based propagation: compute the init/prelude
notebook ID (use preludeNotebookIds or resolve initNotebookName to its ID
earlier) and pass it via additionalNotebookIds to collectRequiredIntegrationIds
and all related flows (scope selection, input listing, validation, integration
collection), keep notebook names only for display; update the other occurrences
that pass additionalNotebookNames (the blocks referenced around the other
ranges) to use additionalNotebookIds consistently.

In `@packages/cli/src/commands/split.ts`:
- Line 149: The summary message uses existingSnapshots.length but does not
account for failures; change the split logic to count only successful splits
(e.g., introduce a successfulSplits counter updated inside the block that
performs each split) and then replace existingSnapshots.length with that counter
in the output call (the line calling output(`\n  ${c.dim(`Split
${existingSnapshots.length} snapshot(s) into ${snapshotDir}`)}`)). Ensure the
counter is initialized before processing, incremented only on confirmed success
(not on warnings/errors), and used in the final output so the message reflects
partial failures accurately.
- Line 117: The snapshot writes currently call fs.writeFile(snapshotPath,
serializeDeepnoteSnapshot(...), 'utf-8') and always clobber files; update both
occurrences (the writes using snapshotPath and serializeDeepnoteSnapshot at
lines noted) to honor the CLI --force flag (likely exposed as a boolean like
force or flags.force). Use write options: if force is true use 'w' (or default)
else use { encoding: 'utf-8', flag: 'wx' } so the write will fail if the file
exists, and catch EEXIST to surface a friendly error/exit; ensure both write
sites are changed consistently and any error handling uses the same message.

In `@packages/convert/src/load-runnable-file.ts`:
- Line 43: The .deepnote read currently wraps read failures in
LoadRunnableFileError, breaking the stated contract that filesystem errors
should be propagated unwrapped; in the loadRunnableFile implementation, update
the .deepnote file-read path (the block that currently catches errors at lines
handling ".deepnote") to stop catching and rewrapping raw fs errors—only convert
non-fs/content-validation errors to LoadRunnableFileError (or rethrow after
inspecting error type), and let native fs errors bubble up unchanged so
.deepnote behaves consistently with .ipynb/.py/.qmd; reference
LoadRunnableFileError and the .deepnote read block when making this change.

In `@packages/convert/src/snapshot/split.ts`:
- Around line 33-50: The timestamp parameter is not sanitized causing
path-traversal when interpolated into the filename; update
generateSnapshotFilename to pass timestamp through sanitizeFilenameComponent
(use sanitizeFilenameComponent(timestamp || 'latest')) before building the
basename so the returned string always uses safeSlug, safeProjectId, sanitized
notebookId and sanitized timestamp (preserving the .snapshot.deepnote suffix) to
prevent directory escape; reference generateSnapshotFilename and
sanitizeFilenameComponent when making the change.

In `@packages/mcp/src/tools/execution.ts`:
- Around line 237-250: The selection logic for notebookFilter uses find(...) and
then later relies on engine.runProject filtering by name, which causes ambiguity
when multiple notebooks share the same name; update the block that computes
notebooks (variables: notebookFilter, found, initNotebook,
file.project.notebooks) to detect when notebookFilter matched by name but more
than one notebook has that name and fail fast with an error asking for the
notebook ID (or require using an ID match), instead of proceeding; implement the
same uniqueness guard in the analogous selection code later (the other notebooks
filtering block) so we never pass an ambiguous name to engine.runProject.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6ed902b2-b607-4a4b-b3dc-71d43525f782

📥 Commits

Reviewing files that changed from the base of the PR and between 757066b and 4d288b3.

📒 Files selected for processing (25)
  • packages/cli/src/commands/run.test.ts
  • packages/cli/src/commands/run.ts
  • packages/cli/src/commands/split.test.ts
  • packages/cli/src/commands/split.ts
  • packages/cli/src/integrations/collect-integrations.ts
  • packages/cli/src/utils/format-converter.ts
  • packages/cli/src/utils/output-persistence.test.ts
  • packages/cli/src/utils/output-persistence.ts
  • packages/convert/src/index.ts
  • packages/convert/src/load-runnable-file.ts
  • packages/convert/src/snapshot/index.ts
  • packages/convert/src/snapshot/lookup.test.ts
  • packages/convert/src/snapshot/lookup.ts
  • packages/convert/src/snapshot/resolve-init.test.ts
  • packages/convert/src/snapshot/resolve-init.ts
  • packages/convert/src/snapshot/save-execution-snapshot.ts
  • packages/convert/src/snapshot/snapshot-integration.test.ts
  • packages/convert/src/snapshot/snapshot-notebook-id.ts
  • packages/convert/src/snapshot/split.test.ts
  • packages/convert/src/snapshot/split.ts
  • packages/convert/src/snapshot/types.ts
  • packages/mcp/src/tools/execution.test.ts
  • packages/mcp/src/tools/execution.ts
  • packages/mcp/src/tools/snapshots.test.ts
  • packages/runtime-core/src/execution-engine.ts
💤 Files with no reviewable changes (2)
  • packages/convert/src/snapshot/index.ts
  • packages/convert/src/index.ts

tkislan and others added 4 commits June 16, 2026 11:56
Flow positively: single-notebook, then composed [init, main], then a
single trailing undefined fallback for multi-notebook (legacy) or an
unresolvable init. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Integrate PR #408 (shared runnable-file loading & snapshot persistence
extracted into @deepnote/convert), which was split out of this branch and
merged to main. Resolution principle: adopt main's clean extraction as the
base, then re-apply this branch's single-notebook feature work on top.

Conflict resolutions (12 files):
- convert/load-runnable-file.ts: took main's version (preserves fs ENOENT/EISDIR
  codes on .deepnote reads; this branch had wrapped them into a code-less error).
- convert/snapshot/save-execution-snapshot.ts: kept this branch's feature version
  (notebookId-scoped filenames, init-notebook composed runs, init result paths).
  Kept the branch's mergeOutputsIntoFile strip semantics (drops stale per-block
  executionCount + execution timing so a re-run never leaks a prior run's
  metadata) and rewrote main's pinning test to assert the strip.
- convert/snapshot/split.ts + write-deepnote-file.ts: object-param
  generateSnapshotFilename (from #408) plus this branch's notebookId +
  sanitizeFilenameComponent additions.
- convert barrels (index.ts, snapshot/index.ts): union of #408 exports and the
  feature exports (resolve-init, snapshot-notebook-id, SaveExecutionSnapshotOptions).
- convert test files: object-form generateSnapshotFilename calls.
- cli/format-converter.ts: took main's clean thin wrapper (returns
  LoadedRunnableFile; dropped re-export shims no consumer needs).
- cli/output-persistence.ts: main's thin wrapper + this branch's init options and
  init result paths; dropped the unused mergeOutputsIntoFile/getSnapshotPath shims.
- cli/run.ts + mcp/execution.ts: union imports; kept the init composition
  (resolveAndComposeInit / resolveRunnableWithInit) while preserving main's
  fs error-code handling contract.

Ran pnpm install (main added a `yaml` dep to @deepnote/database-integrations).
Verified green: pnpm typecheck, biome:check, build, test (2330 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 24, 2026

@dinohamzic dinohamzic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some semi-minor AI findings + an additional comment

- [P2] Snapshot filenames do not round-trip: packages/convert/src/snapshot/lookup.ts:27 ambiguously parses notebook IDs containing an underscore-delimited UUID. ISO timestamps sanitized by generateSnapshotFilename also return
    null when parsed. Such snapshots become undiscoverable.
 
- [P2] Split can leave partial output: packages/cli/src/commands/split.ts:41 writes sequentially before discovering a later collision. Preflight all targets or stage writes before committing them.
 
- [P2] Directory conversion can create duplicate snapshot identities: packages/cli/src/utils/to-deepnote-conversion.ts:47 shares one project ID while packages/convert/src/jupyter-to-deepnote.ts:100 preserves notebook IDs. Copied
    notebooks with the same metadata therefore overwrite each other’s snapshots.

- [P2] Init-prelude documentation is inaccurate: skills/deepnote/references/cli-utility.md:25 says the sibling init runs as a prelude, but --notebook and --block execution filter it out. Qualify the docs or implement prelude
    behavior for filtered runs.

@@ -1 +1 @@
# @deepnote/convert

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this README is still referencing deprecated/removed APIs, can you please double check?

@dinohamzic dinohamzic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found while testing manually. 👌

- snapshot: normalize the timestamp segment to a '_'-free, parser-readable
  form in generateSnapshotFilename so raw ISO timestamps no longer misparse
  the notebook id or produce undiscoverable snapshot filenames.
- cli/split: roll back already-written files when a later split collides
  (without --force), making a failed split all-or-nothing instead of
  leaving partial output behind.
- docs: clarify that filtered runs (--notebook/--block) do not execute the
  sibling init prelude.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 25, 2026
…ferences

- Wire the existing convert() outputFormat param into the deepnote-convert
  CLI as --output-format (also accepts --outputFormat), validated against the
  shared SourceNotebookFormat set, so a .deepnote can be exported to
  percent/quarto/marimo from the CLI, not just Jupyter.
- README: correct the removed programmatic API — the to-Deepnote helpers are
  convert{Ipynb,Quarto,Percent,Marimo}FileToDeepnoteFile and take a single
  file path (not the old plural *FilesToDeepnoteFile with an array) — and
  document the --singleFile flag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 25, 2026
@tkislan
tkislan requested a review from dinohamzic June 25, 2026 12:45

@dinohamzic dinohamzic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • [P1] Split files lose init context in analysis. split.ts keeps only the main notebook, while lint/analyze/DAG load it without resolving the sibling init. Dependencies defined in init disappear from the DAG. Make analysis loaders init-aware, as run already is.

  • [P1] Copied Jupyter notebooks can overwrite each other's snapshots. Directory conversion shares one project ID (utility) while preserving deepnote_notebook_id (converter). Duplicate IDs therefore produce identical snapshot paths. Detect and regenerate/reject duplicates.

  • [P2] Ordinary .py helpers abort directory conversion. to-deepnote-conversion.ts calls throwing detectFormat() inside filter(). Mixed notebook/helper directories fail instead of ignoring unsupported Python files.

  • [P2] MCP drops sibling-init divergence warnings. execution.ts discards resolved.warnings, hiding settings/integration mismatches that CLI reports.

  • [P2] Documentation still uses removed APIs and legacy snapshot names. converting-notebooks.md imports the removed plural converter; FILES.md omits the new notebook-ID filename segment.

… docs

- P1: resolve sibling init notebooks in lint/analyze/dag/stats like `run`
  does, via a shared loadAndResolveDeepnoteFile helper, so init-defined
  symbols no longer disappear from analysis; `run` reuses the shared
  emitInitResolverWarnings
- P2: add non-throwing tryDetectFormat so ordinary .py helpers no longer
  abort directory conversion (also covers the convert auto-detect loop)
- P2: log MCP sibling-init divergence warnings to stderr instead of
  dropping them
- P2: fix docs — singular convertIpynbFileToDeepnoteFile API and the new
  notebook-ID snapshot filename segment
- Extract MCP test fixtures into tools/test-helpers.ts

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/cli/src/commands/analyze.ts`:
- Around line 367-370: The exit-code mapping in analyze.ts should treat
ParseError the same as FileResolutionError and InitNotebookResolutionError,
since loadAndResolveDeepnoteFile can fail with ParseError on malformed .deepnote
files. Update the error instanceof check around the exitCode assignment in
analyze.ts to include ParseError so these failures return ExitCode.InvalidUsage,
and make the same adjustment in dag.ts to keep both command handlers consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: abec5278-721f-4878-8260-41a9e2e1d957

📥 Commits

Reviewing files that changed from the base of the PR and between 1321cc7 and 1ad6e6f.

📒 Files selected for processing (22)
  • FILES.md
  • docs/converting-notebooks.md
  • packages/cli/src/commands/analyze.test.ts
  • packages/cli/src/commands/analyze.ts
  • packages/cli/src/commands/convert.test.ts
  • packages/cli/src/commands/convert.ts
  • packages/cli/src/commands/dag.test.ts
  • packages/cli/src/commands/dag.ts
  • packages/cli/src/commands/lint.test.ts
  • packages/cli/src/commands/lint.ts
  • packages/cli/src/commands/run.ts
  • packages/cli/src/commands/stats.test.ts
  • packages/cli/src/commands/stats.ts
  • packages/cli/src/commands/test-helpers.ts
  • packages/cli/src/utils/load-and-resolve-init.ts
  • packages/cli/src/utils/to-deepnote-conversion.ts
  • packages/convert/src/format-detection.test.ts
  • packages/convert/src/format-detection.ts
  • packages/convert/src/index.ts
  • packages/mcp/src/tools/execution.test.ts
  • packages/mcp/src/tools/execution.ts
  • packages/mcp/src/tools/test-helpers.ts

Comment thread packages/cli/src/commands/analyze.ts
tkislan and others added 2 commits June 25, 2026 15:49
Malformed .deepnote files can throw ParseError during load; map it to
exit code 2 consistently with other file-resolution failures.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tkislan
tkislan requested a review from dinohamzic June 26, 2026 06:15
@tkislan
tkislan merged commit cb24219 into main Jun 26, 2026
21 checks passed
@tkislan
tkislan deleted the tk/single-notebook-deepnote-file branch June 26, 2026 07:07
tkislan added a commit that referenced this pull request Jun 26, 2026
Account for PRs that landed on main after the initial bump:

- @deepnote/convert  3.2.3 -> 4.0.0 (patch -> MAJOR): #364 and #408
  rename/remove many public exports (convertIpynbFilesToDeepnoteFile ->
  convertIpynbFileToDeepnoteFile, etc.) and switch the .deepnote format to
  single-notebook-per-file — breaking changes for a published 3.x package.
- @deepnote/mcp      0.3.3 -> 0.4.0 (patch -> minor): #364/#408 change tool
  behavior (single-notebook conversion output, notebook-scoped snapshots),
  so it is no longer a dependency-bump-only release.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tkislan added a commit that referenced this pull request Jun 29, 2026
* chore: bump package versions

Release the changes that have accumulated on main since the last tag for
each package:

- @deepnote/blocks               4.5.1 -> 4.6.0
- @deepnote/cli                  0.6.1 -> 0.7.0
- @deepnote/convert              3.2.2 -> 3.2.3
- @deepnote/database-integrations 1.4.3 -> 1.5.0
- @deepnote/mcp                  0.3.2 -> 0.3.3
- @deepnote/reactivity           1.2.0 -> 1.2.1
- @deepnote/runtime-core         0.3.0 -> 0.4.0

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: correct convert (major) and mcp (minor) bumps

Account for PRs that landed on main after the initial bump:

- @deepnote/convert  3.2.3 -> 4.0.0 (patch -> MAJOR): #364 and #408
  rename/remove many public exports (convertIpynbFilesToDeepnoteFile ->
  convertIpynbFileToDeepnoteFile, etc.) and switch the .deepnote format to
  single-notebook-per-file — breaking changes for a published 3.x package.
- @deepnote/mcp      0.3.3 -> 0.4.0 (patch -> minor): #364/#408 change tool
  behavior (single-notebook conversion output, notebook-scoped snapshots),
  so it is no longer a dependency-bump-only release.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants