refactor: restructured chunk tree initialization and extraction logic for clarity

- Refactored chunk tree initialization to use explicit prologue/epilogue byte boundaries instead of None values.
- Extracted chunk resolution logic to use split_selector_crc_and_region() helper for improved code reuse.
- Reorganized package.json exports across multiple modules (autoresearch, cli, dap, edit, modes) for better API surface clarity.
- Updated test expectations for Go receiver method rendering and chunk selector formats to match refactored output.
- Consolidated notebook conversion logic and removed unused _createErrorToolResult helper function.
- Improved leading trivia preservation in chunk edits by detecting and maintaining line prefix whitespace.
This commit is contained in:
can1357
2026-04-08 06:30:50 +02:00
parent d1d0187859
commit 58c0d52679
13 changed files with 174 additions and 82 deletions
+2 -2
View File
@@ -406,8 +406,8 @@ pub fn build_notebook_tree_from_virtual(
start_byte: 0,
end_byte: virtual_source.len() as u32,
checksum_start_byte: 0,
prologue_end_byte: None,
epilogue_start_byte: None,
prologue_end_byte: Some(0),
epilogue_start_byte: Some(virtual_source.len() as u32),
checksum: root_checksum.clone(),
error: false,
indent: 0,
+33 -9
View File
@@ -7,7 +7,7 @@ use crate::chunk::{
},
resolve::{
chunk_region_range, chunk_supports_region, resolve_chunk_selector, resolve_chunk_with_crc,
sanitize_chunk_selector, sanitize_crc,
sanitize_chunk_selector, sanitize_crc, split_selector_crc_and_region,
},
state::{ChunkState, ChunkStateInner},
types::{
@@ -284,10 +284,18 @@ fn resolve_edit_target(
None
}
});
let batch_auto_accepted = ensure_batch_operation_target_current(scheduled, crc, touched_paths);
let resolve_crc = if batch_auto_accepted { None } else { crc };
let resolved = resolve_chunk_with_crc(state, selector, resolve_crc, warnings)?;
let region = operation.region.unwrap_or(resolved.region);
let (cleaned_selector, cleaned_crc, parsed_region) =
split_selector_crc_and_region(selector, crc, operation.region)?;
let batch_auto_accepted =
ensure_batch_operation_target_current(scheduled, cleaned_crc.as_deref(), touched_paths);
let resolve_crc = if batch_auto_accepted {
None
} else {
cleaned_crc.as_deref()
};
let resolved =
resolve_chunk_with_crc(state, cleaned_selector.as_deref(), resolve_crc, warnings)?;
let region = operation.region.unwrap_or(parsed_region);
if !batch_auto_accepted {
validate_batch_crc(resolved.chunk, resolved.crc.as_deref(), requires_checksum)?;
}
@@ -696,16 +704,32 @@ fn preserve_attached_leading_trivia(
return replacement.to_owned();
}
let leading_trivia = &state.source[trivia_start..trivia_end];
let line_start = state.source[..trivia_start]
.rfind('\n')
.map_or(0, |pos| pos + 1);
let line_prefix = &state.source[line_start..trivia_start];
let mut leading_trivia =
if !line_prefix.is_empty() && line_prefix.chars().all(|ch| matches!(ch, ' ' | '\t')) {
format!("{line_prefix}{}", &state.source[trivia_start..trivia_end])
} else {
state.source[trivia_start..trivia_end].to_owned()
};
if let Some(last_newline) = leading_trivia.rfind('\n')
&& leading_trivia[last_newline + 1..]
.chars()
.all(|ch| matches!(ch, ' ' | '\t' | '\r'))
{
leading_trivia.truncate(last_newline + 1);
}
if leading_trivia.trim().is_empty()
|| replacement.starts_with(leading_trivia)
|| replacement_supplies_leading_trivia(leading_trivia, replacement)
|| replacement.starts_with(&leading_trivia)
|| replacement_supplies_leading_trivia(&leading_trivia, replacement)
{
return replacement.to_owned();
}
let mut combined = String::with_capacity(leading_trivia.len() + replacement.len());
combined.push_str(leading_trivia);
combined.push_str(&leading_trivia);
combined.push_str(replacement);
combined
}
+4 -4
View File
@@ -136,8 +136,8 @@ pub(crate) fn build_chunk_tree(source: &str, language: &str) -> Result<ChunkTree
start_byte: 0,
end_byte: source.len() as u32,
checksum_start_byte: 0,
prologue_end_byte: None,
epilogue_start_byte: None,
prologue_end_byte: Some(0),
epilogue_start_byte: Some(source.len() as u32),
checksum: root_checksum.clone(),
error: false,
indent: 0,
@@ -213,8 +213,8 @@ fn build_blank_line_tree(
start_byte: 0,
end_byte: source.len() as u32,
checksum_start_byte: 0,
prologue_end_byte: None,
epilogue_start_byte: None,
prologue_end_byte: Some(0),
epilogue_start_byte: Some(source.len() as u32),
checksum: checksum.clone(),
error: false,
indent: 0,
+17 -1
View File
@@ -1,7 +1,6 @@
# Changelog
## [Unreleased]
### Breaking Changes
- Simplified chunk edit operations: removed `append_child`, `prepend_child`, `append_sibling`, `prepend_sibling`, and `replace_body` ops in favor of unified `replace`, `before`, `after`, `prepend`, and `append` with region targeting (`@container`, `@prologue`, `@body`, `@epilogue`)
@@ -10,6 +9,16 @@
### Added
- Exported autoresearch module and submodules via `./autoresearch` and `./autoresearch/*` package paths
- Exported autoresearch tools via `./autoresearch/tools/*` package path
- Exported CLI commands via `./cli/commands/*` package path
- Exported DAP module and submodules via `./dap` and `./dap/*` package paths
- Exported edit module and submodules via `./edit`, `./edit/*`, and `./edit/modes/*` package paths
- Exported bundled ci-green custom command via `./extensibility/custom-commands/bundled/ci-green` package path
- Exported extensibility plugins marketplace via `./extensibility/plugins/marketplace` and `./extensibility/plugins/marketplace/*` package paths
- Exported ACP mode via `./modes/acp` and `./modes/acp/*` package paths
- Exported web utilities via `./web/*` package path
- Exported line-hash utilities from edit module via `./edit/line-hash`
- Host-owned custom tools support: RPC clients can now register custom tools via `setCustomTools()` and the RPC server will invoke them over the transport with `host_tool_call` requests
- RPC host tool framework: `RpcHostToolBridge` for managing host tool execution, `RpcHostToolDefinition` for tool metadata, and bidirectional `host_tool_call`, `host_tool_cancel`, `host_tool_update`, and `host_tool_result` frames
- RPC client tool API: `defineRpcClientTool()` helper, `RpcClientCustomTool` interface, and `RpcClientToolContext` for implementing host-side tool execution with update streaming and abort support
@@ -46,6 +55,9 @@
### Changed
- Reorganized package.json exports: moved `./edit` exports before `./plan-mode` for better logical grouping
- Notebook conversion logic now checks for raw read mode or non-chunk mode before converting via markit, allowing chunk-mode reads of `.ipynb` files to use chunk parsing instead of conversion
- Go receiver methods now render as top-level siblings instead of nested under their receiver type in chunk read output
- Moved prompt formatting and rendering utilities from `coding-agent` to `pi-utils` package; `renderPromptTemplate()` and `formatPromptContent()` now accessed via `prompt.render()` and `prompt.format()` from `@oh-my-pi/pi-utils`
- Moved `parseFrontmatter()` utility from `coding-agent` to `pi-utils` package; now imported from `@oh-my-pi/pi-utils` instead of local utils
- Consolidated prompt template handling: `TemplateContext` type now available as `prompt.TemplateContext` from `@oh-my-pi/pi-utils`
@@ -97,6 +109,10 @@
### Fixed
- Removed unused `_createErrorToolResult` helper function from RPC host-tools module
- Fixed Go receiver method indentation in append operations to preserve relative indentation from the anchor chunk
- Fixed Go type chunk line counts to report only the type body lines instead of including grouped receiver methods
- Fixed enum variant insertion to avoid adding extra blank lines between variants
- Chunk read output now correctly preserves embedded CRC in selectors (e.g., `class_Foo.fn_bar#ZZPM`) instead of stripping them during path parsing
- Chunk edit error messages now consistently report checksum mismatches with format `Checksum mismatch` instead of variable phrasing
- Chunk-mode read output now correctly displays scoped response trees showing only touched chunks and adjacent siblings, preventing unrelated distant chunks from appearing in responses
+60 -12
View File
@@ -91,6 +91,18 @@
"types": "./src/async/*.ts",
"import": "./src/async/*.ts"
},
"./autoresearch": {
"types": "./src/autoresearch/index.ts",
"import": "./src/autoresearch/index.ts"
},
"./autoresearch/*": {
"types": "./src/autoresearch/*.ts",
"import": "./src/autoresearch/*.ts"
},
"./autoresearch/tools/*": {
"types": "./src/autoresearch/tools/*.ts",
"import": "./src/autoresearch/tools/*.ts"
},
"./capability": {
"types": "./src/capability/index.ts",
"import": "./src/capability/index.ts"
@@ -103,6 +115,10 @@
"types": "./src/cli/*.ts",
"import": "./src/cli/*.ts"
},
"./cli/commands/*": {
"types": "./src/cli/commands/*.ts",
"import": "./src/cli/commands/*.ts"
},
"./commands/*": {
"types": "./src/commands/*.ts",
"import": "./src/commands/*.ts"
@@ -167,6 +183,14 @@
"types": "./src/config/*.ts",
"import": "./src/config/*.ts"
},
"./dap": {
"types": "./src/dap/index.ts",
"import": "./src/dap/index.ts"
},
"./dap/*": {
"types": "./src/dap/*.ts",
"import": "./src/dap/*.ts"
},
"./debug": {
"types": "./src/debug/index.ts",
"import": "./src/debug/index.ts"
@@ -183,6 +207,18 @@
"types": "./src/discovery/*.ts",
"import": "./src/discovery/*.ts"
},
"./edit": {
"types": "./src/edit/index.ts",
"import": "./src/edit/index.ts"
},
"./edit/*": {
"types": "./src/edit/*.ts",
"import": "./src/edit/*.ts"
},
"./edit/modes/*": {
"types": "./src/edit/modes/*.ts",
"import": "./src/edit/modes/*.ts"
},
"./exa": {
"types": "./src/exa/index.ts",
"import": "./src/exa/index.ts"
@@ -219,6 +255,10 @@
"types": "./src/extensibility/custom-commands/*.ts",
"import": "./src/extensibility/custom-commands/*.ts"
},
"./extensibility/custom-commands/bundled/ci-green": {
"types": "./src/extensibility/custom-commands/bundled/ci-green/index.ts",
"import": "./src/extensibility/custom-commands/bundled/ci-green/index.ts"
},
"./extensibility/custom-commands/bundled/review": {
"types": "./src/extensibility/custom-commands/bundled/review/index.ts",
"import": "./src/extensibility/custom-commands/bundled/review/index.ts"
@@ -255,6 +295,14 @@
"types": "./src/extensibility/plugins/*.ts",
"import": "./src/extensibility/plugins/*.ts"
},
"./extensibility/plugins/marketplace": {
"types": "./src/extensibility/plugins/marketplace/index.ts",
"import": "./src/extensibility/plugins/marketplace/index.ts"
},
"./extensibility/plugins/marketplace/*": {
"types": "./src/extensibility/plugins/marketplace/*.ts",
"import": "./src/extensibility/plugins/marketplace/*.ts"
},
"./internal-urls": {
"types": "./src/internal-urls/index.ts",
"import": "./src/internal-urls/index.ts"
@@ -315,6 +363,14 @@
"types": "./src/modes/*.ts",
"import": "./src/modes/*.ts"
},
"./modes/acp": {
"types": "./src/modes/acp/index.ts",
"import": "./src/modes/acp/index.ts"
},
"./modes/acp/*": {
"types": "./src/modes/acp/*.ts",
"import": "./src/modes/acp/*.ts"
},
"./modes/components": {
"types": "./src/modes/components/index.ts",
"import": "./src/modes/components/index.ts"
@@ -359,18 +415,6 @@
"types": "./src/modes/utils/*.ts",
"import": "./src/modes/utils/*.ts"
},
"./edit": {
"types": "./src/edit/index.ts",
"import": "./src/edit/index.ts"
},
"./edit/*": {
"types": "./src/edit/*.ts",
"import": "./src/edit/*.ts"
},
"./edit/modes/*": {
"types": "./src/edit/modes/*.ts",
"import": "./src/edit/modes/*.ts"
},
"./plan-mode/*": {
"types": "./src/plan-mode/*.ts",
"import": "./src/plan-mode/*.ts"
@@ -440,6 +484,10 @@
"types": "./src/utils/*.ts",
"import": "./src/utils/*.ts"
},
"./web/*": {
"types": "./src/web/*.ts",
"import": "./src/web/*.ts"
},
"./web/scrapers": {
"types": "./src/web/scrapers/index.ts",
"import": "./src/web/scrapers/index.ts"
+1
View File
@@ -27,6 +27,7 @@ import { type EditToolDetails, getLspBatchRequest, type LspBatchRequest } from "
export { DEFAULT_EDIT_MODE, type EditMode, normalizeEditMode } from "../utils/edit-mode";
export * from "./diff";
export * from "./line-hash";
export * from "./modes/chunk";
export * from "./modes/hashline";
export * from "./modes/patch";
@@ -19,13 +19,6 @@ type PendingHostToolCall = {
onUpdate?: AgentToolUpdateCallback<unknown>;
};
function _createErrorToolResult(message: string): AgentToolResult<unknown> {
return {
content: [{ type: "text", text: message }],
details: {},
};
}
function isAgentToolResult(value: unknown): value is AgentToolResult<unknown> {
if (!value || typeof value !== "object") return false;
const content = (value as { content?: unknown }).content;
+3 -1
View File
@@ -818,6 +818,8 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
const language = getLanguageFromPath(absolutePath);
const skipChunksForExplore = !hasEditTool && !this.session.settings.get("read.explorechunks");
const skipChunksForProse = isProseLanguage(language) && !this.session.settings.get("read.prosechunks");
const shouldConvertWithMarkit =
CONVERTIBLE_EXTENSIONS.has(ext) || (ext === ".ipynb" && (parsed.kind === "raw" || !chunkMode));
if (chunkMode && parsed.kind !== "raw" && !skipChunksForExplore && !skipChunksForProse) {
const absoluteLineRange =
@@ -928,7 +930,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
throw error;
}
}
} else if (CONVERTIBLE_EXTENSIONS.has(ext)) {
} else if (shouldConvertWithMarkit) {
// Convert document or notebook via markit.
const result = await convertFileWithMarkit(absolutePath, signal);
if (result.ok) {
@@ -397,7 +397,7 @@ type Server struct {
expect(result.diffSourceAfter).not.toMatch(/Addr string\n[ \t]+func \(s \*Server\) Ping/);
});
test("append on Go type_Server container keeps receiver method body indentation relative to column 0", () => {
test("append on Go type_Server container keeps receiver method body indentation relative to the anchor chunk", () => {
const source = `package main
type Server struct {
@@ -419,9 +419,9 @@ type Server struct {
expect(result.parseValid).toBe(true);
expect(result.diffSourceAfter).toContain(
"\nfunc (s *Server) LogCount() int {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\treturn 0\n}\n",
"\n\tfunc (s *Server) LogCount() int {\n\t\ts.mu.Lock()\n\t\tdefer s.mu.Unlock()\n\t\treturn 0\n\t}\n",
);
expect(result.diffSourceAfter).not.toContain("\n\tfunc (s *Server) LogCount() int {");
expect(result.diffSourceAfter).not.toContain("\nfunc (s *Server) LogCount() int {");
});
test("keeps append separated from the closing delimiter when adding the last child", () => {
const result = edit([
@@ -728,7 +728,7 @@ describe("formatChunkedRead", () => {
expect(result.text).toMatch(new RegExp(`with-tail\\.ts·${totalLines}L`));
});
test("leaf read shows absolute file lines and raw source indentation", async () => {
test("leaf read shows absolute file lines and canonical tab indentation", async () => {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "chunk-tree-core-"));
const filePath = path.join(tmpDir, "worker.ts");
await Bun.write(filePath, testSource);
@@ -740,12 +740,10 @@ describe("formatChunkedRead", () => {
language: "typescript",
});
expect(result.text).toContain("worker.ts:class_Worker.fn_run·");
expect(result.text).toContain("worker.ts:class_Worker.fn_run@container·");
expect(result.text).toContain("[class_Worker.fn_run#");
expect(result.text).toContain("run(): void {");
expect(result.text).toContain("6| ");
expect(result.text).toContain("run(): void {");
expect(result.text).toContain("7| ");
expect(result.text).toContain("6| \trun(): void {");
expect(result.text).toContain("7| \t\tconsole.log(this.name);");
expect(result.text).toContain("console.log(this.name);");
});
@@ -763,8 +761,9 @@ describe("formatChunkedRead", () => {
});
expect(result.text).not.toContain("to expand ⋮");
expect(result.text).toContain("3| step(0);");
expect(result.text).toContain("27| step(24);");
expect(result.text).toContain("service.ts:class_Service.fn_handle@container·");
expect(result.text).toContain("3| \t\tstep(0);");
expect(result.text).toContain("27| \t\tstep(24);");
expect(result.text).toContain("done();");
});
});
@@ -873,10 +872,10 @@ describe("addressable member rendering", () => {
expect(result.text).toContain("[type_Handler#");
expect(result.text).toContain("3| type Handler interface {");
expect(result.text).toContain("4| Handle(method, path string) Result");
expect(result.text).toContain("4| \tHandle(method, path string) Result");
});
test("renders Go receiver methods beneath their receiver type", async () => {
test("renders Go receiver methods as top-level siblings", async () => {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "chunk-tree-core-"));
const filePath = path.join(tmpDir, "server.go");
await Bun.write(
@@ -893,11 +892,13 @@ describe("addressable member rendering", () => {
expect(result.text).toContain("[type_Server#");
expect(result.text).toContain("[type_Server.field_Addr#");
expect(result.text).toContain("[type_Server.fn_Start#");
expect(result.text).toContain("[type_Server.fn_Stop#");
expect(result.text).toContain("[fn_Start#");
expect(result.text).toContain("[fn_Stop#");
expect(result.text).not.toContain("[type_Server.fn_Start#");
expect(result.text).not.toContain("[type_Server.fn_Stop#");
});
test("line range filter shows receiver methods under a type even when the range skips the type header", async () => {
test("line range filter shows top-level receiver methods even when the range skips the type header", async () => {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "chunk-tree-core-"));
const filePath = path.join(tmpDir, "server.go");
await Bun.write(
@@ -913,8 +914,9 @@ describe("addressable member rendering", () => {
});
expect(result.text).toContain("L7-L8");
expect(result.text).toContain("[type_Server.fn_Start#");
expect(result.text).toContain("[type_Server.fn_Stop#");
expect(result.text).toContain("[fn_Start#");
expect(result.text).toContain("[fn_Stop#");
expect(result.text).not.toContain("[type_Server.fn_Start#");
});
test("renders trivial TypeScript enum variants as addressable children", async () => {
@@ -950,8 +952,8 @@ describe("addressable member rendering", () => {
});
});
describe("grouped Go receiver chunk headers", () => {
test("reports grouped receiver chunk line counts from rendered lines", async () => {
describe("Go type chunk headers", () => {
test("reports Go type chunk line counts from the type body only", async () => {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "chunk-tree-core-"));
const filePath = path.join(tmpDir, "server.go");
await Bun.write(
@@ -966,9 +968,9 @@ describe("grouped Go receiver chunk headers", () => {
language: "go",
});
expect(result.text).toContain("server.go:type_Server·6L");
expect(result.text).toContain("[type_Server.fn_Start#");
expect(result.text).toContain("[type_Server.fn_Stop#");
expect(result.text).toContain("server.go:type_Server@container·3L");
expect(result.text).not.toContain("[fn_Start#");
expect(result.text).not.toContain("[fn_Stop#");
});
});
@@ -992,7 +994,7 @@ describe("addressable member editing", () => {
expect(result.diffSourceAfter).not.toContain('Busy = "busy"');
});
test("after inserts beside an individually addressable enum variant", () => {
test("after inserts beside an individually addressable enum variant without extra blank lines", () => {
const result = edit(
[
{
@@ -1004,7 +1006,7 @@ describe("addressable member editing", () => {
enumSource,
);
expect(result.diffSourceAfter).toContain(' Idle = "idle",\n\n Paused = "paused",\n\n Busy = "busy",');
expect(result.diffSourceAfter).toContain(' Idle = "idle",\n Paused = "paused",\n Busy = "busy",');
});
test("replace with empty content removes an individually addressable enum variant", () => {
@@ -1017,7 +1019,7 @@ describe("addressable member editing", () => {
});
describe("Go receiver render ownership", () => {
test("omits unrelated top-level siblings from grouped receiver output", () => {
test("before inserts beside a top-level Go receiver method", () => {
const source = `package main\n\ntype Server struct {\n Addr string\n}\n\nfunc (s *Server) Start() {}\nfunc (s Server) Stop() {}\n`;
const result = applyEdit({
source,
@@ -1026,7 +1028,7 @@ describe("Go receiver render ownership", () => {
operations: [
{
op: "before",
sel: "type_Server.fn_Start",
sel: "fn_Start",
content: "func DefaultServer() *Server {\n return &Server{}\n}",
},
],
@@ -1034,7 +1036,8 @@ describe("Go receiver render ownership", () => {
expect(result.responseText).toContain("func DefaultServer() *Server");
expect(result.responseText).toContain("[fn_DefaultServer#");
expect(result.responseText).toContain("[type_Server.fn_Start#");
expect(result.responseText).toContain("[fn_Start#");
expect(result.responseText).not.toContain("[type_Server.fn_Start#");
});
});
@@ -1096,9 +1099,7 @@ describe("chunk selector auto-resolution", () => {
content: "run(): void {\n\tconsole.log(this.name);\n}",
},
]);
expect(
result.warnings.some(w => w.includes('Auto-resolved chunk selector "fn_run" to "class_Worker.fn_run"')),
).toBe(true);
expect(result.warnings.join("\n")).toMatch(/Auto-resolved chunk selector "fn_run" to "class_Worker\.fn_run#/);
});
test("warns on prefix auto-resolution", () => {
@@ -1109,9 +1110,7 @@ describe("chunk selector auto-resolution", () => {
content: "run(): void {\n\tconsole.log(this.name);\n}",
},
]);
expect(result.warnings.some(w => w.includes('Auto-resolved chunk selector "run" to "class_Worker.fn_run"'))).toBe(
true,
);
expect(result.warnings.join("\n")).toMatch(/Auto-resolved chunk selector "run" to "class_Worker\.fn_run#/);
});
test("errors on ambiguous suffix matches", () => {
@@ -12,10 +12,13 @@ import {
stripNewLinePrefixes,
validateLineRef,
} from "@oh-my-pi/pi-coding-agent/edit";
import { type Anchor, formatLineTag, type HashlineEdit } from "@oh-my-pi/pi-coding-agent/edit/modes/hashline";
import type { Anchor, HashlineEdit } from "@oh-my-pi/pi-coding-agent/edit/modes/hashline";
function makeTag(line: number, content: string): Anchor {
return parseTag(formatLineTag(line, content));
return {
line,
hash: computeLineHash(line, content),
};
}
// ═══════════════════════════════════════════════════════════════════════════
+2 -2
View File
@@ -266,7 +266,7 @@ describe("Coding Agent Tools", () => {
expect(result.details?.truncation).toBeUndefined();
});
it("should convert ipynb files through markit before rendering", async () => {
it("should convert ipynb files through markit for raw reads", async () => {
const notebookPath = path.join(testDir, "notebook.ipynb");
const notebook = {
cells: [
@@ -287,7 +287,7 @@ describe("Coding Agent Tools", () => {
content: "# Notebook Title\n\nNotebook body\n",
});
const result = await readTool.execute("test-call-ipynb", { path: notebookPath });
const result = await readTool.execute("test-call-ipynb", { path: notebookPath, sel: "raw" });
const output = getTextOutput(result);
expect(convertSpy).toHaveBeenCalledTimes(1);
@@ -4,7 +4,7 @@ import * as os from "node:os";
import * as path from "node:path";
import { _resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
import { EditTool } from "@oh-my-pi/pi-coding-agent/edit";
import { HASHLINE_NIBBLE_ALPHABET } from "@oh-my-pi/pi-coding-agent/edit/modes/hashline";
import { HASHLINE_NIBBLE_ALPHABET } from "@oh-my-pi/pi-coding-agent/edit/line-hash";
import { getLanguageFromPath } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
import { GrepTool } from "@oh-my-pi/pi-coding-agent/tools/grep";
@@ -106,8 +106,9 @@ describe("chunk mode tools", () => {
const text = getText(result);
expect(text).not.toContain("to expand ⋮");
expect(text).toContain("server.ts:class_Server.fn_handleError@container·");
expect(text).toContain("let total = 0;");
expect(text).toContain("29| total += 25;");
expect(text).toContain("29| \t\t\ttotal += 25;");
expect(text).toContain("return err.message + total;");
});
@@ -157,7 +158,7 @@ describe("chunk mode tools", () => {
});
const text = getText(result);
expect(text).toContain("server.ts:class_Server.fn_handleError·");
expect(text).toContain("server.ts:class_Server.fn_handleError@container·");
expect(text).not.toContain("[Warning: checksum #");
});
@@ -308,7 +309,13 @@ describe("chunk mode tools", () => {
await editTool.execute("chunk-edit-string-content", {
path: filePath,
edits: [{ target: "class_Server", op: "append", content: 'status(): string {\n return "ok";\n}\n' }],
edits: [
{
target: "class_Server@body",
op: "append",
content: 'status(): string {\n return "ok";\n}\n',
},
],
} as never);
const updatedSource = await Bun.file(filePath).text();
+3 -4
View File
@@ -36,18 +36,17 @@
"test": "bun run build:native && bun test",
"bench": "bun bench/grep.ts"
},
"devDependencies": {
"@napi-rs/cli": "3.6.0",
"@types/bun": "^1.3"
},
"engines": {
"bun": ">=1.3.7"
},
"napi": {
"binaryName": "pi_natives",
"triples": {}
},
"engines": {
"bun": ">=1.3.7"
},
"files": [
"src",
"native",