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.
- hypernote-0.3.0/.github/workflows/release.yml +236 -0
- hypernote-0.3.0/AGENTS.md +186 -0
- hypernote-0.3.0/CHANGELOG.md +119 -0
- hypernote-0.3.0/CONTEXT.md +38 -0
- {hypernote-0.1.3 → hypernote-0.3.0}/PKG-INFO +24 -40
- {hypernote-0.1.3 → hypernote-0.3.0}/README.md +20 -35
- {hypernote-0.1.3 → hypernote-0.3.0}/SKILL.md +31 -20
- hypernote-0.3.0/dev/README.md +27 -0
- hypernote-0.3.0/dev/cli-agent-ergonomics-rollout.md +69 -0
- hypernote-0.3.0/dev/current-architecture.md +87 -0
- hypernote-0.3.0/dev/module-map.md +29 -0
- hypernote-0.3.0/dev/release.md +135 -0
- {hypernote-0.1.3 → hypernote-0.3.0}/dev/testing-and-verification.md +2 -4
- {hypernote-0.1.3 → hypernote-0.3.0}/docs/README.md +3 -1
- {hypernote-0.1.3 → hypernote-0.3.0}/docs/browser-regression-spec.md +32 -6
- hypernote-0.3.0/docs/cli.md +120 -0
- {hypernote-0.1.3 → hypernote-0.3.0}/docs/getting-started.md +11 -9
- hypernote-0.3.0/docs/runtime-model.md +84 -0
- {hypernote-0.1.3 → hypernote-0.3.0}/docs/sdk.md +37 -0
- {hypernote-0.1.3 → hypernote-0.3.0}/pyproject.toml +8 -6
- {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/cli/main.py +769 -61
- {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/execution_orchestrator.py +62 -79
- {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/runtime_manager.py +5 -0
- {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/sdk.py +353 -24
- hypernote-0.3.0/src/hypernote/server/extension.py +288 -0
- {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/server/handlers.py +102 -0
- hypernote-0.3.0/src/hypernote/server/subshell.py +395 -0
- hypernote-0.3.0/tests/test_browser_regression.py +386 -0
- {hypernote-0.1.3 → hypernote-0.3.0}/tests/test_cli.py +590 -3
- hypernote-0.3.0/tests/test_package_metadata.py +27 -0
- {hypernote-0.1.3 → hypernote-0.3.0}/tests/test_sdk.py +32 -0
- hypernote-0.3.0/tests/test_server_extension.py +83 -0
- hypernote-0.3.0/tests/test_shared_notebook_accessor.py +47 -0
- hypernote-0.3.0/tests/test_subshell.py +360 -0
- {hypernote-0.1.3 → hypernote-0.3.0}/uv.lock +8 -10
- hypernote-0.1.3/.github/workflows/release.yml +0 -87
- hypernote-0.1.3/AGENTS.md +0 -161
- hypernote-0.1.3/CHANGELOG.md +0 -83
- hypernote-0.1.3/dev/README.md +0 -24
- hypernote-0.1.3/dev/current-architecture.md +0 -34
- hypernote-0.1.3/dev/module-map.md +0 -32
- hypernote-0.1.3/docs/cli.md +0 -85
- hypernote-0.1.3/docs/runtime-model.md +0 -50
- hypernote-0.1.3/hypernote/server/extension.py +0 -156
- hypernote-0.1.3/tests/test_browser_regression.py +0 -207
- {hypernote-0.1.3 → hypernote-0.3.0}/.gitignore +0 -0
- {hypernote-0.1.3 → hypernote-0.3.0}/CLAUDE.md +0 -0
- {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/__init__.py +0 -0
- {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/actor_ledger.py +0 -0
- {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/cli/__init__.py +0 -0
- {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/errors.py +0 -0
- {hypernote-0.1.3 → hypernote-0.3.0/src}/hypernote/server/__init__.py +0 -0
- {hypernote-0.1.3 → hypernote-0.3.0}/tests/__init__.py +0 -0
- {hypernote-0.1.3 → hypernote-0.3.0}/tests/conftest.py +0 -0
- {hypernote-0.1.3 → hypernote-0.3.0}/tests/helpers.py +0 -0
- {hypernote-0.1.3 → hypernote-0.3.0}/tests/test_actor_ledger.py +0 -0
- {hypernote-0.1.3 → hypernote-0.3.0}/tests/test_live_server.py +0 -0
- {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**.
|