hypernote 0.1.3__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. hypernote-0.3.0/.github/workflows/release.yml +236 -0
  2. hypernote-0.3.0/AGENTS.md +186 -0
  3. hypernote-0.3.0/CHANGELOG.md +119 -0
  4. hypernote-0.3.0/CONTEXT.md +38 -0
  5. {hypernote-0.1.3 → hypernote-0.3.0}/PKG-INFO +24 -40
  6. {hypernote-0.1.3 → hypernote-0.3.0}/README.md +20 -35
  7. {hypernote-0.1.3 → hypernote-0.3.0}/SKILL.md +31 -20
  8. hypernote-0.3.0/dev/README.md +27 -0
  9. hypernote-0.3.0/dev/cli-agent-ergonomics-rollout.md +69 -0
  10. hypernote-0.3.0/dev/current-architecture.md +87 -0
  11. hypernote-0.3.0/dev/module-map.md +29 -0
  12. hypernote-0.3.0/dev/release.md +135 -0
  13. {hypernote-0.1.3 → hypernote-0.3.0}/dev/testing-and-verification.md +2 -4
  14. {hypernote-0.1.3 → hypernote-0.3.0}/docs/README.md +3 -1
  15. {hypernote-0.1.3 → hypernote-0.3.0}/docs/browser-regression-spec.md +32 -6
  16. hypernote-0.3.0/docs/cli.md +120 -0
  17. {hypernote-0.1.3 → hypernote-0.3.0}/docs/getting-started.md +11 -9
  18. hypernote-0.3.0/docs/runtime-model.md +84 -0
  19. {hypernote-0.1.3 → hypernote-0.3.0}/docs/sdk.md +37 -0
  20. {hypernote-0.1.3 → hypernote-0.3.0}/pyproject.toml +8 -6
  21. {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/cli/main.py +769 -61
  22. {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/execution_orchestrator.py +62 -79
  23. {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/runtime_manager.py +5 -0
  24. {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/sdk.py +353 -24
  25. hypernote-0.3.0/src/hypernote/server/extension.py +288 -0
  26. {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/server/handlers.py +102 -0
  27. hypernote-0.3.0/src/hypernote/server/subshell.py +395 -0
  28. hypernote-0.3.0/tests/test_browser_regression.py +386 -0
  29. {hypernote-0.1.3 → hypernote-0.3.0}/tests/test_cli.py +590 -3
  30. hypernote-0.3.0/tests/test_package_metadata.py +27 -0
  31. {hypernote-0.1.3 → hypernote-0.3.0}/tests/test_sdk.py +32 -0
  32. hypernote-0.3.0/tests/test_server_extension.py +83 -0
  33. hypernote-0.3.0/tests/test_shared_notebook_accessor.py +47 -0
  34. hypernote-0.3.0/tests/test_subshell.py +360 -0
  35. {hypernote-0.1.3 → hypernote-0.3.0}/uv.lock +8 -10
  36. hypernote-0.1.3/.github/workflows/release.yml +0 -87
  37. hypernote-0.1.3/AGENTS.md +0 -161
  38. hypernote-0.1.3/CHANGELOG.md +0 -83
  39. hypernote-0.1.3/dev/README.md +0 -24
  40. hypernote-0.1.3/dev/current-architecture.md +0 -34
  41. hypernote-0.1.3/dev/module-map.md +0 -32
  42. hypernote-0.1.3/docs/cli.md +0 -85
  43. hypernote-0.1.3/docs/runtime-model.md +0 -50
  44. hypernote-0.1.3/hypernote/server/extension.py +0 -156
  45. hypernote-0.1.3/tests/test_browser_regression.py +0 -207
  46. {hypernote-0.1.3 → hypernote-0.3.0}/.gitignore +0 -0
  47. {hypernote-0.1.3 → hypernote-0.3.0}/CLAUDE.md +0 -0
  48. {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/__init__.py +0 -0
  49. {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/actor_ledger.py +0 -0
  50. {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/cli/__init__.py +0 -0
  51. {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/errors.py +0 -0
  52. {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/server/__init__.py +0 -0
  53. {hypernote-0.1.3 → hypernote-0.3.0}/tests/__init__.py +0 -0
  54. {hypernote-0.1.3 → hypernote-0.3.0}/tests/conftest.py +0 -0
  55. {hypernote-0.1.3 → hypernote-0.3.0}/tests/helpers.py +0 -0
  56. {hypernote-0.1.3 → hypernote-0.3.0}/tests/test_actor_ledger.py +0 -0
  57. {hypernote-0.1.3 → hypernote-0.3.0}/tests/test_live_server.py +0 -0
  58. {hypernote-0.1.3 → hypernote-0.3.0}/tests/test_runtime_manager.py +0 -0
@@ -0,0 +1,236 @@
1
+ name: Release
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ version:
7
+ description: "Version to release (e.g., 0.3.0)"
8
+ required: true
9
+ type: string
10
+ publish_to_pypi:
11
+ description: "Publish to PyPI"
12
+ required: true
13
+ default: true
14
+ type: boolean
15
+ create_draft:
16
+ description: "Create as draft release"
17
+ required: false
18
+ default: false
19
+ type: boolean
20
+ push:
21
+ branches:
22
+ - master
23
+ paths:
24
+ - "CHANGELOG.md"
25
+ - "pyproject.toml"
26
+ - "uv.lock"
27
+ - ".github/workflows/release.yml"
28
+
29
+ permissions:
30
+ contents: write
31
+
32
+ jobs:
33
+ resolve-version:
34
+ if: ${{ github.event_name != 'push' || github.actor != 'github-actions[bot]' }}
35
+ runs-on: ubuntu-latest
36
+ outputs:
37
+ version: ${{ steps.resolve.outputs.version }}
38
+ publish_to_pypi: ${{ steps.resolve.outputs.publish_to_pypi }}
39
+ create_draft: ${{ steps.resolve.outputs.create_draft }}
40
+ should_release: ${{ steps.resolve.outputs.should_release }}
41
+ steps:
42
+ - uses: actions/checkout@v6
43
+ with:
44
+ fetch-depth: 0
45
+
46
+ - name: Resolve release version
47
+ id: resolve
48
+ env:
49
+ INPUT_VERSION: ${{ inputs.version }}
50
+ INPUT_PUBLISH_TO_PYPI: ${{ inputs.publish_to_pypi }}
51
+ INPUT_CREATE_DRAFT: ${{ inputs.create_draft }}
52
+ run: |
53
+ VERSION="$INPUT_VERSION"
54
+ if [ -z "$VERSION" ]; then
55
+ VERSION="$(python - <<'PY'
56
+ import tomllib
57
+ from pathlib import Path
58
+
59
+ print(tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"])
60
+ PY
61
+ )"
62
+ fi
63
+
64
+ if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)?$ ]]; then
65
+ echo "❌ Invalid version format: $VERSION"
66
+ echo "Expected semantic version (e.g., 0.3.0, 0.3.0-alpha.1)"
67
+ exit 1
68
+ fi
69
+
70
+ git fetch --tags --force
71
+ TAG="v$VERSION"
72
+ if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
73
+ if [ "$GITHUB_EVENT_NAME" = "push" ] && [ "${GITHUB_RUN_ATTEMPT:-1}" = "1" ]; then
74
+ echo "ℹ️ $TAG already exists; nothing to release."
75
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
76
+ echo "publish_to_pypi=false" >> "$GITHUB_OUTPUT"
77
+ echo "create_draft=false" >> "$GITHUB_OUTPUT"
78
+ echo "should_release=false" >> "$GITHUB_OUTPUT"
79
+ exit 0
80
+ fi
81
+ echo "ℹ️ $TAG already exists; continuing because this is a rerun/recovery attempt."
82
+ fi
83
+
84
+ echo "✅ Version format is valid: $VERSION"
85
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
86
+ echo "publish_to_pypi=${INPUT_PUBLISH_TO_PYPI:-true}" >> "$GITHUB_OUTPUT"
87
+ echo "create_draft=${INPUT_CREATE_DRAFT:-false}" >> "$GITHUB_OUTPUT"
88
+ echo "should_release=true" >> "$GITHUB_OUTPUT"
89
+
90
+ release:
91
+ needs: resolve-version
92
+ if: ${{ needs.resolve-version.outputs.should_release == 'true' }}
93
+ runs-on: ubuntu-latest
94
+ outputs:
95
+ version: ${{ needs.resolve-version.outputs.version }}
96
+ steps:
97
+ - uses: actions/checkout@v6
98
+ with:
99
+ fetch-depth: 0
100
+ token: ${{ secrets.GITHUB_TOKEN }}
101
+
102
+ - uses: astral-sh/setup-uv@v7
103
+ with:
104
+ enable-cache: true
105
+
106
+ - name: Ensure release version is checked in
107
+ env:
108
+ VERSION: ${{ needs.resolve-version.outputs.version }}
109
+ run: |
110
+ CURRENT_VERSION="$(python - <<'PY'
111
+ import tomllib
112
+ from pathlib import Path
113
+
114
+ print(tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"])
115
+ PY
116
+ )"
117
+
118
+ if [ "$CURRENT_VERSION" = "$VERSION" ]; then
119
+ echo "✅ pyproject.toml already declares $VERSION"
120
+ exit 0
121
+ fi
122
+
123
+ if [ "$GITHUB_EVENT_NAME" != "workflow_dispatch" ]; then
124
+ echo "❌ pyproject.toml declares $CURRENT_VERSION, expected $VERSION"
125
+ exit 1
126
+ fi
127
+
128
+ sed -i -E "0,/^version = \".*\"/s//version = \"$VERSION\"/" pyproject.toml
129
+
130
+ if ! grep -q "version = \"$VERSION\"" pyproject.toml; then
131
+ echo "❌ Failed to update version in pyproject.toml"
132
+ exit 1
133
+ fi
134
+
135
+ git config user.name "github-actions[bot]"
136
+ git config user.email "github-actions[bot]@users.noreply.github.com"
137
+ uv lock
138
+ git add pyproject.toml uv.lock
139
+ git commit -m "chore: bump version to v$VERSION"
140
+ CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
141
+ git push origin "$CURRENT_BRANCH"
142
+ echo "✅ Version commit pushed"
143
+
144
+ - name: Build package
145
+ run: |
146
+ rm -rf dist/ build/ *.egg-info/
147
+ uv build
148
+ if [ ! -f dist/*.whl ] || [ ! -f dist/*.tar.gz ]; then
149
+ echo "❌ Build artifacts not found"
150
+ exit 1
151
+ fi
152
+ echo "✅ Package built"
153
+ ls -la dist/
154
+
155
+ - name: Verify package
156
+ run: |
157
+ uv run --isolated --no-project --with dist/*.whl python -c "import hypernote; print('ok')"
158
+ echo "✅ Package verified"
159
+
160
+ - uses: actions/upload-artifact@v6
161
+ with:
162
+ name: dist-${{ needs.resolve-version.outputs.version }}
163
+ path: dist/*
164
+ if-no-files-found: error
165
+
166
+ - name: Run tests
167
+ run: |
168
+ uv run --extra dev playwright install --with-deps chromium
169
+ uv run --extra dev python -m pytest -q
170
+
171
+ - name: Create and push tag
172
+ env:
173
+ VERSION: ${{ needs.resolve-version.outputs.version }}
174
+ run: |
175
+ TAG="v$VERSION"
176
+ git config user.name "github-actions[bot]"
177
+ git config user.email "github-actions[bot]@users.noreply.github.com"
178
+ git fetch --tags --force
179
+ if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
180
+ TAG_TARGET="$(git rev-list -n 1 "$TAG")"
181
+ HEAD_SHA="$(git rev-parse HEAD)"
182
+ if [ "$TAG_TARGET" = "$HEAD_SHA" ]; then
183
+ echo "✅ $TAG already points at $HEAD_SHA; continuing."
184
+ exit 0
185
+ fi
186
+ echo "❌ $TAG points at $TAG_TARGET, not current HEAD $HEAD_SHA. Refusing to retag."
187
+ exit 1
188
+ fi
189
+ git tag -a "$TAG" -m "Release $TAG"
190
+ git push origin "$TAG"
191
+ echo "✅ Created and pushed tag $TAG"
192
+
193
+ - name: Create GitHub Release
194
+ env:
195
+ GH_TOKEN: ${{ github.token }}
196
+ CREATE_DRAFT: ${{ needs.resolve-version.outputs.create_draft }}
197
+ run: |
198
+ TAG="v${{ needs.resolve-version.outputs.version }}"
199
+ DRAFT_FLAG=""
200
+ if [ "$CREATE_DRAFT" = "true" ]; then
201
+ DRAFT_FLAG="--draft"
202
+ fi
203
+ if gh release view "$TAG" >/dev/null 2>&1; then
204
+ gh release upload "$TAG" dist/* --clobber
205
+ else
206
+ gh release create "$TAG" dist/* \
207
+ --generate-notes \
208
+ --verify-tag \
209
+ $DRAFT_FLAG
210
+ fi
211
+
212
+ - name: Summary
213
+ run: |
214
+ echo "## Release Summary" >> "$GITHUB_STEP_SUMMARY"
215
+ echo "- **Version:** v${{ needs.resolve-version.outputs.version }}" >> "$GITHUB_STEP_SUMMARY"
216
+ echo "- **PyPI:** ${{ needs.resolve-version.outputs.publish_to_pypi }}" >> "$GITHUB_STEP_SUMMARY"
217
+ echo "- **Draft:** ${{ needs.resolve-version.outputs.create_draft }}" >> "$GITHUB_STEP_SUMMARY"
218
+
219
+ publish-pypi:
220
+ needs:
221
+ - resolve-version
222
+ - release
223
+ if: ${{ needs.resolve-version.outputs.publish_to_pypi == 'true' }}
224
+ runs-on: ubuntu-latest
225
+ steps:
226
+ - uses: actions/download-artifact@v5
227
+ with:
228
+ name: dist-${{ needs.resolve-version.outputs.version }}
229
+ path: dist/
230
+
231
+ - uses: astral-sh/setup-uv@v7
232
+
233
+ - name: Publish to PyPI
234
+ env:
235
+ UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
236
+ run: uv publish
@@ -0,0 +1,186 @@
1
+ # Hypernote
2
+
3
+ Notebook-first execution system built on top of Jupyter shared documents
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ uv sync --extra dev
9
+ uv run hypernote
10
+ uv run hypernote --help
11
+ uv run hypernote setup serve
12
+ uv run hypernote setup doctor
13
+ uv run hypernote create tmp/demo.ipynb
14
+ uv run hypernote ix tmp/demo.ipynb -s 'value = 20 + 22\nprint(value)'
15
+ uv run hypernote status tmp/demo.ipynb --full
16
+ uv run python -m pytest -q
17
+ uv run ruff check src/hypernote tests
18
+ ```
19
+
20
+ ## Architecture
21
+
22
+ Jupyter owns notebook truth:
23
+
24
+ - `.ipynb` persistence
25
+ - shared YDoc document state
26
+ - kernel and session primitives
27
+ - notebook rendering in JupyterLab
28
+
29
+ Hypernote owns a thin control plane:
30
+
31
+ - public SDK in [src/hypernote/sdk.py](src/hypernote/sdk.py)
32
+ - public errors in [src/hypernote/errors.py](src/hypernote/errors.py)
33
+ - agent-first CLI in [src/hypernote/cli/main.py](src/hypernote/cli/main.py)
34
+ - execution orchestration and shared-document mutation in [src/hypernote/execution_orchestrator.py](src/hypernote/execution_orchestrator.py)
35
+ - runtime ownership in [src/hypernote/runtime_manager.py](src/hypernote/runtime_manager.py)
36
+ - HTTP handlers in [src/hypernote/server/handlers.py](src/hypernote/server/handlers.py)
37
+ - Jupyter extension wiring in [src/hypernote/server/extension.py](src/hypernote/server/extension.py)
38
+ - ephemeral job and attribution ledger in [src/hypernote/actor_ledger.py](src/hypernote/actor_ledger.py)
39
+ - subshell-aware execute/interrupt/restart in [src/hypernote/server/subshell.py](src/hypernote/server/subshell.py)
40
+
41
+ Core rule: notebook edits and execution must operate on one logical document truth whether JupyterLab is closed, already open, or opened mid-run.
42
+
43
+ Lifecycle rule: notebook contents and outputs persist through Jupyter's `.ipynb` model, but Hypernote's runtime state, job records, and cell attribution are intentionally in-memory and notebook-scoped.
44
+
45
+ Concurrent-actor rule: JupyterLab and Hypernote share one notebook session and one kernel. Hypernote-driven cells run in an ipykernel subshell so the kernel's main shell stays responsive to native Lab actions. Hypernote's extension overrides `/api/kernels/{id}/interrupt` and `/api/kernels/{id}/restart` so Lab's Stop and Restart toolbar buttons reach the subshell-routed cell or perform the right cleanup. See [dev/current-architecture.md](dev/current-architecture.md) for the full mechanism.
46
+
47
+ ## Shipped Surface
48
+
49
+ ### SDK
50
+
51
+ - `hypernote.connect(path, create=False)`
52
+ - `Notebook`, `CellCollection`, `CellHandle`, `Runtime`, `Job`
53
+ - `nb.run(...)`, `nb.run_all()`, `nb.restart()`, `nb.interrupt()`
54
+ - `nb.snapshot()`, `nb.status()`, `nb.diff(...)`
55
+
56
+ ### CLI
57
+
58
+ - `hypernote` — live workspace dashboard with hints
59
+ - `create`
60
+ - `ix`
61
+ - `exec`
62
+ - `edit`
63
+ - `run-all`
64
+ - `restart`
65
+ - `restart-run-all`
66
+ - `interrupt`
67
+ - `status`
68
+ - `diff`
69
+ - `cat`
70
+ - `job ...`
71
+ - `runtime ...`
72
+ - `setup doctor`
73
+ - `setup serve`
74
+
75
+ Default CLI contract:
76
+
77
+ - bare `hypernote` shows live workspace state and next-step hints
78
+ - TTY: concise human-readable progress
79
+ - non-TTY: one compact final JSON result
80
+ - explicit streaming only through `--watch` or `--stream-json`
81
+ - summary-first read payloads should come from SDK-backed observation helpers, not CLI-only formatting rules
82
+ - `setup serve` is the default local bootstrap path for a Hypernote-enabled JupyterLab server
83
+ - `setup doctor --path PATH` is the preferred first diagnostic when server reachability,
84
+ kernelspec selection, or runtime mismatch is unclear
85
+
86
+ ## Rules
87
+
88
+ - Terminology: when the user says "our system", treat that as the maintained project-operating surface, including `AGENTS.md`, `SKILL.md`, `docs/`, `dev/`, tests, and other shared guidance or verification artifacts around the code.
89
+ - SDK first. CLI behavior should come from the SDK, not duplicate raw HTTP semantics.
90
+ - When CLI and SDK both need compact observation behavior, define the summary/truncation/focused-read logic in the SDK and let the CLI adapt it.
91
+ - Shared logic needs one owner. When a helper or shaping rule moves into the SDK or another shared layer, delete the old copies instead of letting multiple versions drift.
92
+ - One truth. Do not reintroduce a contents-vs-YDoc split for notebook reads or writes.
93
+ - Runtime creation must honor the requested kernel first, otherwise notebook metadata
94
+ `kernelspec.name`, otherwise `python3`.
95
+ - Keep control-plane state ephemeral. Do not add durable job history unless the product explicitly needs it.
96
+ - Our system is part of done. After every meaningful change, update the relevant parts of `AGENTS.md`, `SKILL.md`, `docs/`, `dev/`, tests, and adjacent shared project guidance so they stay in sync with implementation.
97
+ - Keep adapters thin. CLI, JupyterLab, and tests should reuse shared contracts instead of re-encoding notebook behavior.
98
+ - Treat output and API shapes as contracts. Feature variants should preserve the same top-level envelope and field semantics unless there is a deliberate, documented exception.
99
+ - Normalize boundary inputs early. If upstream payloads can arrive in more than one valid shape, accept and normalize them at the adapter boundary rather than assuming a single representation.
100
+ - Prefer unique notebook paths in tests and demos. Browser tests must also use unique JupyterLab workspace URLs.
101
+ - Keep `tmp/` disposable. Durable notes belong in `docs/` or `dev/`, not `tmp/`.
102
+ - Release every version through a PR (CHANGELOG move + version bump + lockfile refresh on a `release/vX.Y.Z` branch), never via direct push to master. The full process is in [dev/release.md](dev/release.md).
103
+
104
+ ## Read These First
105
+
106
+ - [SKILL.md](SKILL.md)
107
+ - [docs/README.md](docs/README.md)
108
+ - [dev/README.md](dev/README.md)
109
+ - [dev/release.md](dev/release.md)
110
+
111
+ ## If You Are Editing...
112
+
113
+ ### `src/hypernote/sdk.py` or `src/hypernote/errors.py`
114
+
115
+ - preserve the notebook-first public object model
116
+ - keep public enums and errors stable
117
+ - prefer adding reusable observation helpers on `NotebookStatus` / `CellStatus` over adding CLI-only shaping logic
118
+ - update [docs/sdk.md](docs/sdk.md)
119
+
120
+ ### `src/hypernote/cli/main.py`
121
+
122
+ - keep non-TTY output compact by default
123
+ - keep bare `hypernote` as the live dashboard view
124
+ - preserve `ix` as the happy-path command
125
+ - preserve summary-first `status` and compact `cat` with contextual hints
126
+ - prefer rendering SDK observation helpers over introducing new CLI-only data shaping
127
+ - keep streaming explicit in non-TTY mode
128
+ - update [docs/cli.md](docs/cli.md)
129
+
130
+ ### `src/hypernote/execution_orchestrator.py`, `src/hypernote/runtime_manager.py`, or `src/hypernote/server/*`
131
+
132
+ - preserve the single-truth shared-document path
133
+ - verify open-tab and late-open behavior still hold
134
+ - update [dev/current-architecture.md](dev/current-architecture.md)
135
+ - if browser-visible behavior changes, update [docs/browser-regression-spec.md](docs/browser-regression-spec.md)
136
+
137
+ ### `tests/*`
138
+
139
+ - prefer the narrowest test that proves the behavior
140
+ - preserve coverage for:
141
+ - SDK shape
142
+ - CLI output contract
143
+ - live server behavior
144
+ - browser regression for streaming and late-open correctness
145
+ - for contract-heavy changes, add invariant coverage for:
146
+ - default and focused variants
147
+ - empty and failure states
148
+ - alternate valid input shapes from upstream payloads
149
+ - parity between real helpers and any fake/test-double implementations
150
+
151
+ ### `pyproject.toml` version, `CHANGELOG.md`, or `.github/workflows/release.yml`
152
+
153
+ - always use the PR-based release process in [dev/release.md](dev/release.md)
154
+ - do not push release-prep commits directly to master, even for a one-line version bump
155
+ - before opening the release PR, confirm `git ls-tree origin/master` shows every file mentioned in the new CHANGELOG section — local-only work must not be claimed in the changelog
156
+ - if you change the release workflow shape (steps, secrets, version source), update [dev/release.md](dev/release.md) in the same PR so the doc stays accurate
157
+
158
+ ## Verification
159
+
160
+ Install guidance:
161
+
162
+ - `uv sync`
163
+ - Hypernote's default JupyterLab integration stack
164
+ - `uv sync --extra dev`
165
+ - adds local development, lint, test, and browser-validation tooling
166
+
167
+ Minimum checks for most changes:
168
+
169
+ ```bash
170
+ uv run ruff check src/hypernote tests
171
+ uv run python -m pytest -q
172
+ ```
173
+
174
+ When browser or live-server behavior changes, also use:
175
+
176
+ ```bash
177
+ HYPERNOTE_INTEGRATION=1 uv run python -m pytest -q tests/test_live_server.py
178
+ uv run python -m pytest -q tests/test_browser_regression.py
179
+ ```
180
+
181
+ Before opening or updating a PR for a cross-surface change, do a short contract pass:
182
+
183
+ - check that sibling command variants still share one consistent envelope
184
+ - check that aggregate field names still match their exact semantics
185
+ - check that docs and hints only mention shipped commands and flags
186
+ - check that moved logic no longer has stale copies in adapters or tests
@@ -0,0 +1,119 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## Unreleased
6
+
7
+ ## 0.3.0 - 2026-05-10
8
+
9
+ Hypernote is now a JupyterLab-first integration: the default install carries the
10
+ collaboration/docprovider stack, `setup serve` opens Lab by default, and
11
+ `setup doctor` can distinguish API reachability from shared-document and Lab
12
+ frontend health.
13
+
14
+ ### Changed
15
+
16
+ - Hypernote is now packaged and documented as a JupyterLab-first integration:
17
+ the default install includes JupyterLab collaboration support, `setup serve`
18
+ opens Lab by default, `setup doctor` reports the shared-document stack and
19
+ duplicate local servers, and cell-state operations require the shared
20
+ document path instead of falling back to contents-manager edits.
21
+
22
+ ### Notes
23
+
24
+ - correction: the 0.2.0 headline mentions "an experimental VS Code extension" but the
25
+ `vscode-extension/` work was never committed and did not ship in the 0.2.0 artifact.
26
+ Documentation referring to the VS Code extension has been removed from `README.md`,
27
+ `AGENTS.md`, `SKILL.md`, `docs/README.md`, `dev/README.md`, and `dev/module-map.md`.
28
+
29
+ ## 0.2.0 - 2026-05-07
30
+
31
+ Native JupyterLab as a first-class concurrent actor: open a notebook
32
+ mid-run and see streaming output, click Stop and the cell terminates,
33
+ click Restart and the kernel comes back ready. Plus the package now ships
34
+ in PyPA src layout and an experimental VS Code extension.
35
+
36
+ ### Added
37
+
38
+ - subshell-routed kernel execution (JEP 91 / ipykernel 7+): Hypernote sends `execute_request` with a `subshell_id` so the kernel's main shell stays free to answer concurrent clients (e.g. JupyterLab) — opening a notebook mid-run now renders cells immediately and continues to stream output without waiting for the running cell
39
+ - subshell-aware `POST /api/kernels/{id}/interrupt` override: JupyterLab's Stop button (and `nb.interrupt()`) now terminates a Hypernote-driven cell, raising `KeyboardInterrupt` in the subshell thread via a main-shell `PyThreadState_SetAsyncExc` injection. Falls back to default SIGINT for non-Hypernote-driven kernels.
40
+ - subshell- and nbmodel-aware `POST /api/kernels/{id}/restart` override: JupyterLab's Restart button now leaves the kernel ready to run new cells. Hypernote evicts nbmodel's stale per-kernel client and worker, and clears its cached subshell id, so the next execute rebuilds against the fresh kernel.
41
+ - new browser regression tests for Lab Stop and Lab Restart against subshell-routed cells; new real-kernel unit tests for subshell creation, routing, interrupt latency, and restart cleanup.
42
+
43
+ ### Changed
44
+
45
+ - package layout migrated to `src/hypernote/` (PyPA src layout). `pyproject.toml` now configures `[tool.hatch.build.targets.wheel] packages = ["src/hypernote"]` and `[tool.ruff] src = ["src", "tests"]`. The `hypernote` import name is unchanged — `import hypernote` continues to work.
46
+ - release workflow switched from tag-triggered to `workflow_dispatch` — run `gh workflow run release.yml -f version=X.Y.Z` to release
47
+
48
+ ### Notes
49
+
50
+ - subshell routing requires ipykernel 7+ (IPython kernels). Other kernels fall back to the main shell — late-open during a long-running cell will block the JupyterLab UI for those kernels and the subshell-targeted interrupt becomes a no-op (override falls through to SIGINT, which works on the main shell).
51
+ - `PyThreadState_SetAsyncExc`-based interrupt cannot terminate a thread inside a long blocking C call without GIL release (e.g. `requests.get` with no timeout). The `KeyboardInterrupt` only fires once control returns to Python. The interrupt snippet falls back to `os.kill(pid, SIGINT)` internally if the subshell thread cannot be found, so user-visible "Stop did nothing" cases degrade to default SIGINT behavior rather than silent failure.
52
+
53
+ ## 0.1.3 - 2026-04-03
54
+
55
+ Cross-repo runtime hardening and agent ergonomics.
56
+
57
+ ### Added
58
+
59
+ - `hypernote setup serve` — bootstraps a Hypernote-enabled Jupyter server with all required extensions
60
+ - `hypernote setup doctor --path PATH` — reports notebook kernelspec, live runtime kernel, resolved launcher, and warns on mismatches
61
+ - `hypernote create --empty` — removes any default cells Jupyter auto-inserts so notebooks start clean
62
+ - batch `ix` output now includes `cells_inserted`, `cells_total`, `cells_remaining`, `halt_reason`, and `last_processed_cell_id` on early halt
63
+ - timeout errors now surface job id, last known status, and recovery hints pointing to `job get` and `cat`
64
+ - runtime kernel mismatch detection — clear error when a live runtime's kernel doesn't match notebook metadata, with guidance to restart
65
+
66
+ ### Changed
67
+
68
+ - execution now resolves kernels as: explicit override > notebook metadata `kernelspec.name` > `python3`
69
+ - `RuntimeManager.ensure_room()` rejects silent reuse when a live room exists with a different kernel name
70
+ - removed all hardcoded local paths from project markdown files — links are now repo-relative
71
+ - SKILL.md rewritten: iterative `ix → observe → ix` is the primary workflow, heredoc/stdin documented for multi-line cells, server lifecycle section added, cross-repo setup simplified to `uv add hypernote --dev`
72
+
73
+ ### Notes
74
+
75
+ - `--cells-file` batch mode is now documented as a convenience for known-good sequences, not the default workflow
76
+ - the recommended cross-repo pattern is now: install hypernote in the target repo, not `uv run --with`
77
+
78
+ ## 0.1.1 - 2026-04-02
79
+
80
+ Patch release focused on release automation hardening.
81
+
82
+ ### Changed
83
+
84
+ - updated the GitHub release workflow to newer official action majors where available
85
+ - replaced the third-party GitHub release action with the `gh` CLI in release automation
86
+ - fixed the release workflow to install Playwright browser binaries before running browser tests
87
+ - fixed the release workflow to run tests with the `dev` extra installed
88
+ - fixed the PyPI publish step to use token-only `uv publish` authentication
89
+
90
+ ### Notes
91
+
92
+ - package code is unchanged from `0.1.0`
93
+ - this release exists to verify and stabilize the automated release path
94
+
95
+ ## 0.1.0 - 2026-04-02
96
+
97
+ Initial public release.
98
+
99
+ ### Added
100
+
101
+ - notebook-first Python SDK built around `Notebook`, `CellCollection`, `CellHandle`, `Runtime`, and `Job`
102
+ - agent-first CLI with `create`, `ix`, `exec`, `edit`, `run-all`, `restart`, `interrupt`, `status`, `diff`, `cat`, `job`, and `runtime` flows
103
+ - Jupyter server extension with narrow Hypernote REST handlers
104
+ - notebook-scoped runtime lifecycle with attach, detach, recovery, stop, and GC
105
+ - headless execution flow over Jupyter shared documents and `jupyter-server-nbmodel`
106
+ - job polling and `input()` round-trip support for long-running or interactive execution
107
+ - live-server and browser regression tests for late-open, persistence, and shared-document correctness
108
+ - project and developer docs for the runtime model, SDK, CLI, and release workflow
109
+
110
+ ### Changed
111
+
112
+ - aligned the control plane around an explicitly ephemeral lifecycle
113
+ - moved job tracking and cell attribution to an in-memory notebook-scoped ledger
114
+ - removed the SQLite dependency and any implied durable job history
115
+
116
+ ### Notes
117
+
118
+ - Jupyter owns durable notebook contents and outputs in `.ipynb`
119
+ - Hypernote owns ephemeral runtime state, jobs, and attribution
@@ -0,0 +1,38 @@
1
+ # Hypernote
2
+
3
+ Hypernote coordinates notebook execution through a Hypernote-enabled JupyterLab
4
+ server so agents and humans can work against one notebook truth.
5
+
6
+ ## Language
7
+
8
+ **Shared Document**:
9
+ The server-side notebook document that all Hypernote operations use as the live notebook truth.
10
+ _Avoid_: file-only notebook truth
11
+
12
+ **Hypernote JupyterLab Server**:
13
+ A JupyterLab server launched or verified with Hypernote's required server and collaboration extensions.
14
+ _Avoid_: plain Jupyter server, separate agent server
15
+
16
+ **Open Lab Tab**:
17
+ A browser tab viewing a notebook through the Hypernote JupyterLab Server.
18
+ _Avoid_: separate Lab server, plain Lab tab
19
+
20
+ **Agent Automation**:
21
+ CLI or SDK notebook work performed through the Hypernote JupyterLab Server, whether an Open Lab Tab currently exists.
22
+ _Avoid_: separate runtime mode
23
+
24
+ ## Relationships
25
+
26
+ - A **Hypernote JupyterLab Server** owns the **Shared Document**.
27
+ - An **Open Lab Tab** and **Agent Automation** must attach to the same **Hypernote JupyterLab Server**.
28
+ - **Agent Automation** does not require an **Open Lab Tab**, but it still requires the **Hypernote JupyterLab Server**.
29
+
30
+ ## Example Dialogue
31
+
32
+ > **Dev:** "Can agents run notebooks without JupyterLab?"
33
+ > **Domain expert:** "No separate mode: agents use the **Hypernote JupyterLab Server** even if nobody has an **Open Lab Tab**."
34
+
35
+ ## Flagged Ambiguities
36
+
37
+ - Agent work without an **Open Lab Tab** was previously described as a separate product mode; resolved: Hypernote only distinguishes whether an **Open Lab Tab** exists.
38
+ - "Jupyter server" was used broadly; resolved: the supported product server is a **Hypernote JupyterLab Server**.