Skip to content

Commit cad3133

Browse files
author
SqlRush
committed
Expose notebook cell ids in reads
1 parent 44616a5 commit cad3133

4 files changed

Lines changed: 24 additions & 10 deletions

File tree

docs/claude-code-go-rewrite-plan.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ test/parity/ # golden tests against TS/official behavior
170170

171171
当前进度:
172172

173-
- Read/Edit/Write 初版已落地,覆盖文本 Read、PDF text/page-selection 初版(含常见 Page/Contents 间接对象、Pages/Kids 页序、FlateDecode 文本流和 UTF-16 BOM 字符串)、PNG/JPEG/GIF/WebP image Read、Jupyter notebook cell 渲染初版、Read 大文本 tool-result budget 截断/落盘、read-before-write、mtime stale guard、唯一匹配、`replace_all`、Write/Edit structured diff hunks、`.claude/settings.json`/`settings.local.json` 写前 JSON/语义校验、team-memory secret guard、Read 去重和跨 tool round read-state。
173+
- Read/Edit/Write 初版已落地,覆盖文本 Read、PDF text/page-selection 初版(含常见 Page/Contents 间接对象、Pages/Kids 页序、FlateDecode 文本流和 UTF-16 BOM 字符串)、PNG/JPEG/GIF/WebP image Read、Jupyter notebook cell 渲染初版(含真实 cell id 和 `cell-N` fallback 的可见/结构化输出)、Read 大文本 tool-result budget 截断/落盘、read-before-write、mtime stale guard、唯一匹配、`replace_all`、Write/Edit structured diff hunks、`.claude/settings.json`/`settings.local.json` 写前 JSON/语义校验、team-memory secret guard、Read 去重和跨 tool round read-state。
174174
- NotebookEdit 初版已落地,按官方 `notebook_path`/`cell_id`/`new_source`/`cell_type`/`edit_mode` schema 支持 replace/insert/delete 主路径、真实 cell id 和 `cell-N` 索引、code cell 修改后清空 outputs/execution_count、read-before-edit/stale guard、read-state 刷新、`notebook_path` 权限路径识别、结构化结果和 cell-level diff/hunks;完整 notebook UI/file-history/golden parity 仍需继续补。
175175
- Bash 初版已落地,覆盖 command/timeout/description 输入校验、`/bin/sh -c` 执行、stdout/stderr/exit code/timeout 结构化结果、动态 read-only/concurrency-safe/destructive 分类、Git diff/log/show/status/ls-files/grep/rev-parse/branch/tag/ls-remote safe-flag 校验,Git remote/push/reflog/stash/worktree/merge-base/describe/cat-file/for-each-ref/rev-list/blame/shortlog/config-get 参数级安全分类、`git remote show/get-url` 参数收紧、`git ls-remote` URL/SSH/server-option guard、branch/tag 裸 positional 创建防护、`git reflog expire/delete`、`git stash drop/pop/clear` 和 `git worktree remove/prune` 破坏性分类、`find -delete/-exec rm` 与 `xargs rm` 破坏性分类、safe wrapper/env 前缀归一化(`time`/`nohup`/`timeout`/`nice`/`stdbuf`/`env`)后的只读/破坏性分类、临时环境赋值前缀后的破坏性命令识别、权限规则接入、后台启动、同会话 `BashOutput` 输出读取和 `KillBash` 取消;完整 shell parser、真实 sandbox、interrupt、后台任务完整生命周期和官方 golden 仍需继续补。
176176
- Glob/Grep 纯 Go 初版已落地,覆盖 `**` 递归 glob、Glob 绝对 pattern base-dir 提取、Glob 官方 pattern/path-only strict schema、Glob/Grep 输出工作目录相对路径、Glob 默认 no-ignore/hidden 搜索及 `CLAUDE_CODE_GLOB_NO_IGNORE`/`CLAUDE_CODE_GLOB_HIDDEN` env 切换、Grep 官方 VCS metadata 目录排除(`.git`/`.svn`/`.hg`/`.bzr`/`.jj`/`.sl`)、Grep 层级 `.gitignore`/`.ignore`/`.rgignore`、Grep `hidden`/`--hidden` 和 `no_hidden`/`--no-hidden` 隐藏文件遍历控制、Glob oldest-first modified/path 排序、Glob 截断 tool-result 提示、Grep regex/fixed string (`fixed_strings`/`-F`)、multiline 跨行 dotall 搜索、glob/iglob/type/type-not 过滤、Grep glob 空白/逗号多 pattern 与 brace alternation、Glob/Grep path 存在性校验和 Glob directory-only path 校验、`output_mode`/`outputMode` 的 `files`/`files_with_matches`/`content`/`count` 输出模式、Grep files/--files 不读内容列出将被搜索文件、Grep files_with_matches file-count summary、Grep count-mode occurrence/file summary、Grep `--max-columns 500` 长匹配/上下文行省略占位、`context`/`before_context`/`after_context` 及 `-C`/`-B`/`-A` 上下文行和官方 precedence(非 content 模式忽略)、`line_numbers`/`lineNumbers`/`-n` line-number 控制、`byte_offset`/`--byte-offset`/`-b`、`path_separator`/`--path-separator`、`null`/`--null`/`-0`、`field_match_separator`/`--field-match-separator`、`field_context_separator`/`--field-context-separator`、`context_separator`/`--context-separator` 和 `no_context_separator`/`--no-context-separator` 输出分隔符控制、`max_count`/`maxCount`/`-m` per-file match limiting、`offset`/`head_limit` 分页和 content-mode pagination tool-result 提示、默认 250 条 Grep head limit、`head_limit=0` unlimited、`ignore_case`/`case_insensitive`/`caseInsensitive`/`-i` 大小写不敏感搜索,以及 Grep 数字/布尔参数的 quoted semantic string 兼容;完整 ripgrep parity 和剩余输出参数仍需继续补。

docs/first-second-parity-audit.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ Covered behavior:
206206
- `Bash` `find` read-only classification now rejects deleting or file-writing actions such as `-delete`, `-fprint`, `-fprint0`, `-fprintf`, and `-fls`, avoiding commands that mutate files being auto-allowed as read-only.
207207
- `Bash` safety classification now excludes `find -exec*` forms from read-only auto-allow and detects destructive commands passed through `find -exec*`/`xargs`, including shell `-c` scripts and safe-wrapper/env/assignment forms such as `env rm`, `timeout rm`, and `xargs -I{} env sh -c ...`.
208208
- `PowerShell` native/external file-reading/search read-only classification now applies relative-path guards to `where.exe /R`, `file`, `tree`, and `findstr` path positionals and path-valued flags, rejecting Windows-drive, UNC, URI/provider-like, parent-directory, and missing path-flag values before read-only auto-allow; `where.exe` no longer uses allow-all flag acceptance.
209-
- `Read` line-number formatting, offset/limit slicing, mtime-based same-range dedup, text/binary/device guards, initial PDF text extraction with page-selection parsing, common Page/Contents indirect-object mapping, Pages/Kids page-order recovery, FlateDecode text streams, UTF-16 BOM PDF string decoding, PNG/JPEG/GIF/WebP image content-block reads, Jupyter notebook cell rendering, large text tool-result truncation/persistence, and read-state recording.
209+
- `Read` line-number formatting, offset/limit slicing, mtime-based same-range dedup, text/binary/device guards, initial PDF text extraction with page-selection parsing, common Page/Contents indirect-object mapping, Pages/Kids page-order recovery, FlateDecode text streams, UTF-16 BOM PDF string decoding, PNG/JPEG/GIF/WebP image content-block reads, Jupyter notebook cell rendering with visible/structured `cell_id` values for real IDs and `cell-N` fallbacks, large text tool-result truncation/persistence, and read-state recording.
210210
- `Write` create/update behavior, read-before-write validation for existing files, mtime stale detection, `.claude/settings.json` and `settings.local.json` JSON/semantic validation before writes, structured diff hunks, and post-write read-state refresh.
211211
- `Edit` exact replacement, nonexistent-file creation with empty `old_string`, unique-match enforcement, `replace_all`, quote-style preservation for curly quotes, CRLF preservation, `.claude/settings.json` and `settings.local.json` final-content validation before writes, structured diff hunks, and post-edit read-state refresh.
212212
- `NotebookEdit` initial notebook mutation behavior, including official input field names, replace/insert/delete modes, real cell IDs and `cell-N` fallback indexes, code-cell output/execution reset on replace, read-before-edit/stale guards, read-state refresh, `notebook_path` permission path extraction, structured result payloads, and cell-level diff/hunks.

internal/tools/file/tools.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ type notebookDocument struct {
397397
}
398398

399399
type notebookCell struct {
400+
ID string `json:"id"`
400401
CellType string `json:"cell_type"`
401402
Source any `json:"source"`
402403
Outputs []notebookOutput `json:"outputs"`
@@ -456,7 +457,13 @@ func renderNotebook(displayPath string, doc notebookDocument) (string, []map[str
456457
}
457458
source := strings.TrimRight(notebookText(cell.Source), "\n")
458459
outputs := notebookOutputTexts(cell.Outputs)
459-
fmt.Fprintf(&b, "\n\nCell %d [%s]", i+1, cellType)
460+
cellID := strings.TrimSpace(cell.ID)
461+
fallbackCellID := fmt.Sprintf("cell-%d", i)
462+
displayCellID := cellID
463+
if displayCellID == "" {
464+
displayCellID = fallbackCellID
465+
}
466+
fmt.Fprintf(&b, "\n\nCell %d [%s] cell_id=%s", i+1, cellType, displayCellID)
460467
if cell.ExecutionCount != nil && cellType == "code" {
461468
fmt.Fprintf(&b, " execution_count=%v", cell.ExecutionCount)
462469
}
@@ -472,11 +479,14 @@ func renderNotebook(displayPath string, doc notebookDocument) (string, []map[str
472479
}
473480
}
474481
cells = append(cells, map[string]any{
475-
"index": i + 1,
476-
"cell_type": cellType,
477-
"source": source,
478-
"outputs": outputs,
479-
"execution_count": cell.ExecutionCount,
482+
"index": i + 1,
483+
"cell_id": displayCellID,
484+
"id": cellID,
485+
"fallback_cell_id": fallbackCellID,
486+
"cell_type": cellType,
487+
"source": source,
488+
"outputs": outputs,
489+
"execution_count": cell.ExecutionCount,
480490
})
481491
}
482492
return b.String(), cells

internal/tools/file/tools_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ func TestReadToolRendersNotebookCells(t *testing.T) {
389389
dir := t.TempDir()
390390
raw := `{
391391
"cells": [
392-
{"cell_type": "markdown", "source": ["# Title\n", "body"]},
392+
{"cell_type": "markdown", "id": "intro", "source": ["# Title\n", "body"]},
393393
{"cell_type": "code", "execution_count": 1, "source": "print('hi')\n", "outputs": [{"output_type": "stream", "name": "stdout", "text": ["hi\n"]}]}
394394
],
395395
"metadata": {},
@@ -410,7 +410,7 @@ func TestReadToolRendersNotebookCells(t *testing.T) {
410410
t.Fatal(err)
411411
}
412412
content := result.Content.(string)
413-
for _, want := range []string{"Notebook: analysis.ipynb", "Cell 1 [markdown]:\n# Title\nbody", "Cell 2 [code] execution_count=1:\nprint('hi')", "Outputs:\nhi"} {
413+
for _, want := range []string{"Notebook: analysis.ipynb", "Cell 1 [markdown] cell_id=intro:\n# Title\nbody", "Cell 2 [code] cell_id=cell-1 execution_count=1:\nprint('hi')", "Outputs:\nhi"} {
414414
if !strings.Contains(content, want) {
415415
t.Fatalf("notebook content missing %q:\n%s", want, content)
416416
}
@@ -420,6 +420,10 @@ func TestReadToolRendersNotebookCells(t *testing.T) {
420420
if len(cells) != 2 || cells[0]["cell_type"] != "markdown" || cells[1]["cell_type"] != "code" {
421421
t.Fatalf("structured notebook cells = %#v", cells)
422422
}
423+
if cells[0]["cell_id"] != "intro" || cells[0]["id"] != "intro" || cells[0]["fallback_cell_id"] != "cell-0" ||
424+
cells[1]["cell_id"] != "cell-1" || cells[1]["id"] != "" || cells[1]["fallback_cell_id"] != "cell-1" {
425+
t.Fatalf("structured notebook cell ids = %#v", cells)
426+
}
423427
record, ok := EnsureReadState(ctx).Get(path)
424428
if !ok || !strings.Contains(record.Content, `"nbformat": 4`) || record.PartialView {
425429
t.Fatalf("notebook read state = %#v ok=%v", record, ok)

0 commit comments

Comments
 (0)