@nghyane/arcane 0.1.27 → 0.1.28

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.1.28] - 2026-03-08
6
+
7
+ ### Fixed
8
+
9
+ - Fix edit tool: hashline delete missing `saveForUndo` causing unrecoverable file deletion
10
+ - Fix `any` types on `EditTool` class — replaced with `EditToolDetails`
11
+ - Fix `applyHashlineEdits` mutating caller's input array via splice
12
+
5
13
  ## [0.1.27] - 2026-03-08
6
14
 
7
15
  ### Fixed
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@nghyane/arcane",
4
- "version": "0.1.27",
4
+ "version": "0.1.28",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/nghyane/arcane",
7
7
  "author": "Can Bölük",
@@ -134,13 +134,21 @@ function mergeDiagnosticsWithWarnings(
134
134
  };
135
135
  }
136
136
 
137
- export class EditTool implements AgentTool<TInput, any, Theme> {
137
+ export class EditTool implements AgentTool<TInput, EditToolDetails, Theme> {
138
138
  readonly name = "edit";
139
139
  readonly label = "Edit";
140
140
  readonly nonAbortable = true;
141
141
  readonly concurrency = "exclusive";
142
- readonly renderCall = editToolRenderer.renderCall as unknown as AgentTool<TInput, any, Theme>["renderCall"];
143
- readonly renderResult = editToolRenderer.renderResult as unknown as AgentTool<TInput, any, Theme>["renderResult"];
142
+ readonly renderCall = editToolRenderer.renderCall as unknown as AgentTool<
143
+ TInput,
144
+ EditToolDetails,
145
+ Theme
146
+ >["renderCall"];
147
+ readonly renderResult = editToolRenderer.renderResult as unknown as AgentTool<
148
+ TInput,
149
+ EditToolDetails,
150
+ Theme
151
+ >["renderResult"];
144
152
 
145
153
  readonly #allowFuzzy: boolean;
146
154
  readonly #fuzzyThreshold: number;
@@ -252,6 +260,7 @@ export class EditTool implements AgentTool<TInput, any, Theme> {
252
260
 
253
261
  if (deleteFile) {
254
262
  if (await file.exists()) {
263
+ saveForUndo(absolutePath, await file.text());
255
264
  await file.unlink();
256
265
  }
257
266
  invalidateFsScanAfterDelete(absolutePath);
@@ -592,7 +592,7 @@ function autocorrectEscapedTabs(lines: string[]): string[] {
592
592
  */
593
593
  export function applyHashlineEdits(
594
594
  content: string,
595
- edits: HashlineEdit[],
595
+ edits: readonly HashlineEdit[],
596
596
  ): {
597
597
  content: string;
598
598
  firstChangedLine: number | undefined;
@@ -603,6 +603,8 @@ export function applyHashlineEdits(
603
603
  return { content, firstChangedLine: undefined };
604
604
  }
605
605
 
606
+ const mutableEdits: HashlineEdit[] = edits.map(e => ({ ...e, content: [...e.content] }));
607
+
606
608
  const fileLines = content.split("\n");
607
609
  const hadFinalNewline = content.endsWith("\n");
608
610
  const originalFileLines = [...fileLines];
@@ -612,7 +614,7 @@ export function applyHashlineEdits(
612
614
  const autocorrect = Bun.env.ARCANE_HL_AUTOCORRECT === "1";
613
615
 
614
616
  const warnings: string[] = [];
615
- for (const edit of edits) {
617
+ for (const edit of mutableEdits) {
616
618
  const unicodeWarning = detectUnicodeEscapePlaceholders(edit.content);
617
619
  if (unicodeWarning && !warnings.includes(unicodeWarning)) {
618
620
  warnings.push(unicodeWarning);
@@ -622,7 +624,7 @@ export function applyHashlineEdits(
622
624
 
623
625
  function collectExplicitlyTouchedLines(): Set<number> {
624
626
  const touched = new Set<number>();
625
- for (const edit of edits) {
627
+ for (const edit of mutableEdits) {
626
628
  switch (edit.op) {
627
629
  case "replace":
628
630
  touched.add(edit.target.line);
@@ -652,7 +654,7 @@ export function applyHashlineEdits(
652
654
  mismatches.push({ line: ref.line, expected: ref.hash, actual: actualHash });
653
655
  return false;
654
656
  }
655
- for (const edit of edits) {
657
+ for (const edit of mutableEdits) {
656
658
  switch (edit.op) {
657
659
  case "replace": {
658
660
  if (!validateRef(edit.target)) continue;
@@ -678,8 +680,8 @@ export function applyHashlineEdits(
678
680
  }
679
681
  const seenEditKeys = new Map<string, number>();
680
682
  const dedupIndices = new Set<number>();
681
- for (let i = 0; i < edits.length; i++) {
682
- const edit = edits[i];
683
+ for (let i = 0; i < mutableEdits.length; i++) {
684
+ const edit = mutableEdits[i];
683
685
  let lineKey: string;
684
686
  switch (edit.op) {
685
687
  case "replace":
@@ -697,13 +699,13 @@ export function applyHashlineEdits(
697
699
  }
698
700
  }
699
701
  if (dedupIndices.size > 0) {
700
- for (let i = edits.length - 1; i >= 0; i--) {
701
- if (dedupIndices.has(i)) edits.splice(i, 1);
702
+ for (let i = mutableEdits.length - 1; i >= 0; i--) {
703
+ if (dedupIndices.has(i)) mutableEdits.splice(i, 1);
702
704
  }
703
705
  }
704
706
 
705
707
  // Compute sort key (descending) — bottom-up application
706
- const annotated = edits.map((edit, idx) => {
708
+ const annotated = mutableEdits.map((edit, idx) => {
707
709
  let sortLine: number;
708
710
  let precedence: number;
709
711
  switch (edit.op) {