feat(coding-agent): added notebook-aware .ipynb edit/read path helpers
- Added `.ipynb` detection and editable-cell conversion utilities, including merge/serialize helpers in `edit/notebook`. - Rerouted hashline, patch, and replace edit flows, plus read/write paths, through notebook-aware helpers before persistence. - Removed the dedicated `notebook` tool, its schema flags, renderer, and built-in registration/settings checks. - Updated notebook read behavior, docs, and tests so `.ipynb` reads return editable `# %%` cells and edits reserialize to JSON.
This commit is contained in:
@@ -52,6 +52,7 @@ import {
|
||||
HL_HASH_CAPTURE_RE_RAW,
|
||||
} from "../line-hash";
|
||||
import { detectLineEnding, normalizeToLF, restoreLineEndings, stripBom } from "../normalize";
|
||||
import { readEditFileText, serializeEditFileText } from "../read-file";
|
||||
import type { EditToolDetails, LspBatchRequest } from "../renderer";
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
@@ -1656,11 +1657,14 @@ export function splitHashlineInputs(input: string, options: SplitHashlineOptions
|
||||
// 13. Diff computation (for streaming preview)
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
|
||||
async function readHashlineFileText(file: { text(): Promise<string> }, pathText: string): Promise<string> {
|
||||
async function readHashlineFileText(
|
||||
_file: { text(): Promise<string> },
|
||||
absolutePath: string,
|
||||
pathText: string,
|
||||
): Promise<string> {
|
||||
try {
|
||||
return await file.text();
|
||||
return await readEditFileText(absolutePath, pathText);
|
||||
} catch (error) {
|
||||
if (isEnoent(error)) throw new Error(`File not found: ${pathText}`);
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(message || `Unable to read ${pathText}`);
|
||||
}
|
||||
@@ -1679,7 +1683,7 @@ export async function computeHashlineDiff(
|
||||
const [section] = sections;
|
||||
|
||||
const absolutePath = resolveToCwd(section.path, cwd);
|
||||
const rawContent = await readHashlineFileText(Bun.file(absolutePath), section.path);
|
||||
const rawContent = await readHashlineFileText(Bun.file(absolutePath), absolutePath, section.path);
|
||||
const { text: content } = stripBom(rawContent);
|
||||
const normalized = normalizeToLF(content);
|
||||
const result = applyHashlineEdits(normalized, parseHashline(section.diff), options);
|
||||
@@ -1699,11 +1703,13 @@ interface ReadHashlineFileResult {
|
||||
rawContent: string;
|
||||
}
|
||||
|
||||
async function readHashlineFile(absolutePath: string): Promise<ReadHashlineFileResult> {
|
||||
async function readHashlineFile(absolutePath: string, pathText: string): Promise<ReadHashlineFileResult> {
|
||||
try {
|
||||
return { exists: true, rawContent: await Bun.file(absolutePath).text() };
|
||||
return { exists: true, rawContent: await readEditFileText(absolutePath, pathText) };
|
||||
} catch (error) {
|
||||
if (isEnoent(error)) return { exists: false, rawContent: "" };
|
||||
if (error instanceof Error && error.message === `File not found: ${pathText}`)
|
||||
return { exists: false, rawContent: "" };
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -1746,7 +1752,7 @@ async function preflightHashlineSection(options: ExecuteHashlineSingleOptions &
|
||||
const { edits } = parseHashlineWithWarnings(diff);
|
||||
enforcePlanModeWrite(session, sectionPath, { op: "update" });
|
||||
|
||||
const source = await readHashlineFile(absolutePath);
|
||||
const source = await readHashlineFile(absolutePath, sectionPath);
|
||||
if (!source.exists && hasAnchorScopedEdit(edits)) throw new Error(`File not found: ${sectionPath}`);
|
||||
if (source.exists) assertEditableFileContent(source.rawContent, sectionPath);
|
||||
|
||||
@@ -1773,7 +1779,7 @@ async function executeHashlineSection(
|
||||
const { edits, warnings: parseWarnings } = parseHashlineWithWarnings(diff);
|
||||
enforcePlanModeWrite(session, sourcePath, { op: "update" });
|
||||
|
||||
const source = await readHashlineFile(absolutePath);
|
||||
const source = await readHashlineFile(absolutePath, sourcePath);
|
||||
if (!source.exists && hasAnchorScopedEdit(edits)) throw new Error(`File not found: ${sourcePath}`);
|
||||
if (source.exists) assertEditableFileContent(source.rawContent, sourcePath);
|
||||
|
||||
@@ -1789,7 +1795,11 @@ async function executeHashlineSection(
|
||||
};
|
||||
}
|
||||
|
||||
const finalContent = bom + restoreLineEndings(result.lines, originalEnding);
|
||||
const finalContent = await serializeEditFileText(
|
||||
absolutePath,
|
||||
sourcePath,
|
||||
bom + restoreLineEndings(result.lines, originalEnding),
|
||||
);
|
||||
const diagnostics = await writethrough(
|
||||
absolutePath,
|
||||
finalContent,
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
restoreLineEndings,
|
||||
stripBom,
|
||||
} from "../normalize";
|
||||
import { readEditFileText, serializeEditFileText } from "../read-file";
|
||||
import type { EditToolDetails, LspBatchRequest } from "../renderer";
|
||||
import {
|
||||
type ContextLineResult,
|
||||
@@ -104,13 +105,13 @@ export const defaultFileSystem: FileSystem = {
|
||||
return Bun.file(path).exists();
|
||||
},
|
||||
async read(path: string): Promise<string> {
|
||||
return Bun.file(path).text();
|
||||
return readEditFileText(path, path);
|
||||
},
|
||||
async readBinary(path: string): Promise<Uint8Array> {
|
||||
return fs.promises.readFile(path);
|
||||
},
|
||||
async write(path: string, content: string): Promise<void> {
|
||||
await Bun.write(path, content);
|
||||
await Bun.write(path, await serializeEditFileText(path, path, content));
|
||||
},
|
||||
async delete(path: string): Promise<void> {
|
||||
await fs.promises.unlink(path);
|
||||
@@ -1637,7 +1638,7 @@ class LspFileSystem implements FileSystem {
|
||||
}
|
||||
|
||||
async read(path: string): Promise<string> {
|
||||
return this.#getFile(path).text();
|
||||
return readEditFileText(path, path);
|
||||
}
|
||||
|
||||
async readBinary(path: string): Promise<Uint8Array> {
|
||||
@@ -1647,10 +1648,11 @@ class LspFileSystem implements FileSystem {
|
||||
|
||||
async write(path: string, content: string): Promise<void> {
|
||||
const file = this.#getFile(path);
|
||||
const finalContent = await serializeEditFileText(path, path, content);
|
||||
const deferredForPath = this.deferredForPath;
|
||||
const result = await this.writethrough(
|
||||
path,
|
||||
content,
|
||||
finalContent,
|
||||
this.signal,
|
||||
file,
|
||||
this.batchRequest,
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
restoreLineEndings,
|
||||
stripBom,
|
||||
} from "../normalize";
|
||||
import { readEditFileText } from "../read-file";
|
||||
import { readEditFileText, serializeEditFileText } from "../read-file";
|
||||
import type { EditToolDetails, LspBatchRequest } from "../renderer";
|
||||
|
||||
export interface FuzzyMatch {
|
||||
@@ -1065,7 +1065,11 @@ export async function executeReplaceSingle(
|
||||
throw new Error(`Edits to ${path} resulted in no changes being made.`);
|
||||
}
|
||||
|
||||
const finalContent = bom + restoreLineEndings(result.content, originalEnding);
|
||||
const finalContent = await serializeEditFileText(
|
||||
absolutePath,
|
||||
path,
|
||||
bom + restoreLineEndings(result.content, originalEnding),
|
||||
);
|
||||
const diagnostics = await writethrough(
|
||||
absolutePath,
|
||||
finalContent,
|
||||
|
||||
Reference in New Issue
Block a user