@kodocagent/cli 0.4.0 → 0.4.2

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/dist/index.js CHANGED
@@ -9739,9 +9739,9 @@ var require_load = __commonJS({
9739
9739
  var require_lib3 = __commonJS({
9740
9740
  "../../node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/lib/index.js"(exports, module) {
9741
9741
  "use strict";
9742
- function JSZip3() {
9743
- if (!(this instanceof JSZip3)) {
9744
- return new JSZip3();
9742
+ function JSZip5() {
9743
+ if (!(this instanceof JSZip5)) {
9744
+ return new JSZip5();
9745
9745
  }
9746
9746
  if (arguments.length) {
9747
9747
  throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide.");
@@ -9750,7 +9750,7 @@ var require_lib3 = __commonJS({
9750
9750
  this.comment = null;
9751
9751
  this.root = "";
9752
9752
  this.clone = function() {
9753
- var newObj = new JSZip3();
9753
+ var newObj = new JSZip5();
9754
9754
  for (var i in this) {
9755
9755
  if (typeof this[i] !== "function") {
9756
9756
  newObj[i] = this[i];
@@ -9759,16 +9759,16 @@ var require_lib3 = __commonJS({
9759
9759
  return newObj;
9760
9760
  };
9761
9761
  }
9762
- JSZip3.prototype = require_object();
9763
- JSZip3.prototype.loadAsync = require_load();
9764
- JSZip3.support = require_support();
9765
- JSZip3.defaults = require_defaults();
9766
- JSZip3.version = "3.10.1";
9767
- JSZip3.loadAsync = function(content, options) {
9768
- return new JSZip3().loadAsync(content, options);
9762
+ JSZip5.prototype = require_object();
9763
+ JSZip5.prototype.loadAsync = require_load();
9764
+ JSZip5.support = require_support();
9765
+ JSZip5.defaults = require_defaults();
9766
+ JSZip5.version = "3.10.1";
9767
+ JSZip5.loadAsync = function(content, options) {
9768
+ return new JSZip5().loadAsync(content, options);
9769
9769
  };
9770
- JSZip3.external = require_external();
9771
- module.exports = JSZip3;
9770
+ JSZip5.external = require_external();
9771
+ module.exports = JSZip5;
9772
9772
  }
9773
9773
  });
9774
9774
 
@@ -10775,7 +10775,12 @@ var ToolRegistry = class {
10775
10775
  return `\uC0AC\uC6A9\uC790\uAC00 \uBCC0\uACBD\uC744 \uAC70\uC808\uD558\uC5EC \uC800\uC7A5\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4${reasonPart}. \uAC19\uC740 \uC218\uC815\uC548\uC744 \uC790\uB3D9\uC73C\uB85C \uB2E4\uC2DC \uC81C\uC548\uD558\uC9C0 \uB9D0\uACE0, \uC0AC\uC6A9\uC790\uC758 \uB2E4\uC74C \uC9C0\uC2DC\uB97C \uAE30\uB2E4\uB9AC\uC138\uC694.`;
10776
10776
  }
10777
10777
  try {
10778
- return await commit();
10778
+ const commitMsg = await commit();
10779
+ if (proposal.warnings && proposal.warnings.length > 0) {
10780
+ return `${commitMsg}
10781
+ [\uACBD\uACE0] ${proposal.warnings.join("\n[\uACBD\uACE0] ")}`;
10782
+ }
10783
+ return commitMsg;
10779
10784
  } catch (err) {
10780
10785
  const msg = err instanceof Error ? err.message : String(err);
10781
10786
  return `\uC800\uC7A5 \uC624\uB958: ${msg}`;
@@ -10803,6 +10808,7 @@ import { copyFile, mkdir as mkdir3, readdir as readdir2, readFile as readFile3,
10803
10808
  import { basename as basename2, dirname as dirname22, extname, join as join22 } from "path";
10804
10809
  var import_jszip = __toESM(require_lib3(), 1);
10805
10810
  var import_jszip2 = __toESM(require_lib3(), 1);
10811
+ var import_jszip3 = __toESM(require_lib3(), 1);
10806
10812
  import { createTwoFilesPatch } from "diff";
10807
10813
  import { readFile as readFile22 } from "fs/promises";
10808
10814
  import { blocksToMarkdown, compare } from "@clazic/kordoc";
@@ -10833,26 +10839,35 @@ import {
10833
10839
  } from "docx";
10834
10840
  import { readFile as readFile6 } from "fs/promises";
10835
10841
  import { extname as extname6 } from "path";
10836
- import { extractFormFields, markdownToHwpx as markdownToHwpx2, parse as parse2 } from "@clazic/kordoc";
10842
+ import { parse as parse2 } from "@clazic/kordoc";
10837
10843
  import { z as z6 } from "zod";
10838
10844
  import { readFile as readFile7 } from "fs/promises";
10839
10845
  import { extname as extname7 } from "path";
10840
- import ExcelJS from "exceljs";
10846
+ import { extractFormFields, markdownToHwpx as markdownToHwpx2, parse as parse3 } from "@clazic/kordoc";
10847
+ var import_jszip4 = __toESM(require_lib3(), 1);
10841
10848
  import { z as z7 } from "zod";
10842
- import { readFile as readFile8, stat as stat3 } from "fs/promises";
10849
+ import { readFile as readFile8 } from "fs/promises";
10843
10850
  import { extname as extname8 } from "path";
10844
- import { parse as parse3 } from "@clazic/kordoc";
10851
+ import ExcelJS from "exceljs";
10845
10852
  import { z as z8 } from "zod";
10846
- import { readFile as fsReadFile, stat as stat4 } from "fs/promises";
10853
+ import { readFile as readFile9 } from "fs/promises";
10854
+ import { extname as extname9 } from "path";
10855
+ import { parse as parse4 } from "@clazic/kordoc";
10847
10856
  import { z as z9 } from "zod";
10857
+ import { readFile as readFile10, stat as stat3 } from "fs/promises";
10858
+ import { extname as extname10 } from "path";
10859
+ import { parse as parse5 } from "@clazic/kordoc";
10860
+ import { z as z10 } from "zod";
10861
+ import { readFile as fsReadFile, stat as stat4 } from "fs/promises";
10862
+ import { z as z11 } from "zod";
10848
10863
  import { stat as stat5 } from "fs/promises";
10849
- import { extname as extname9 } from "path";
10864
+ import { extname as extname11 } from "path";
10850
10865
  import { markdownToHwpx as markdownToHwpx3 } from "@clazic/kordoc";
10851
- import { z as z10 } from "zod";
10866
+ import { z as z12 } from "zod";
10852
10867
  import { stat as stat6 } from "fs/promises";
10853
- import { extname as extname10 } from "path";
10868
+ import { extname as extname12 } from "path";
10854
10869
  import ExcelJS2 from "exceljs";
10855
- import { z as z11 } from "zod";
10870
+ import { z as z13 } from "zod";
10856
10871
  async function resolveSafePath(cwd, p) {
10857
10872
  const normalizedCwd = normalize(cwd).normalize("NFC");
10858
10873
  const normalizedP = p.normalize("NFC");
@@ -11236,7 +11251,7 @@ function applyFormObjectEdits(xml, edits) {
11236
11251
  };
11237
11252
  continue;
11238
11253
  }
11239
- const openTag = xml.slice(pos, openTagEnd + 1);
11254
+ const _openTag = xml.slice(pos, openTagEnd + 1);
11240
11255
  const closeTag = `</${xmlTag}>`;
11241
11256
  const closeIdx = xml.indexOf(closeTag, openTagEnd);
11242
11257
  if (closeIdx < 0) {
@@ -11433,7 +11448,7 @@ function replaceAttrInOpenTag(xml, tagStart, tagEnd, attr, newValue) {
11433
11448
  text: newValue
11434
11449
  };
11435
11450
  }
11436
- function replaceEditText(xml, pos, elementContent, newText) {
11451
+ function replaceEditText(_xml, pos, elementContent, newText) {
11437
11452
  const escaped = escapeXml(newText);
11438
11453
  const selfCloseRe = /<hp:text\s*\/>/;
11439
11454
  const scm = selfCloseRe.exec(elementContent);
@@ -11837,39 +11852,22 @@ var listFilesTool = {
11837
11852
  return lines.join("\n") + truncateNotice;
11838
11853
  }
11839
11854
  };
11840
- var coordinateCellEditItemSchema = z4.object({
11841
- tableIndex: z4.number().int().nonnegative().describe(
11842
- "0-based \uD45C \uC778\uB371\uC2A4. read_document\uB85C \uC77D\uC740 kordoc \uBE14\uB85D \uC21C\uC11C\uC640 \uB3D9\uC77C (\uC911\uCCA9 \uD45C\uB294 \uBCC4\uB3C4 \uCE74\uC6B4\uD305 \uC548 \uD568)"
11843
- ),
11844
- row: z4.number().int().nonnegative().describe("\uC140\uC758 rowAddr (0-based, <hp:cellAddr rowAddr=..> \uAC12)"),
11845
- col: z4.number().int().nonnegative().describe("\uC140\uC758 colAddr (0-based, <hp:cellAddr colAddr=..> \uAC12)"),
11846
- newText: z4.string().describe("\uC140\uC5D0 \uC4F8 \uC0C8 \uD14D\uC2A4\uD2B8"),
11847
- expectedText: z4.string().optional().describe(
11848
- "\uD604\uC7AC \uC140 \uD14D\uC2A4\uD2B8 (\uC548\uC804 \uAC80\uC99D\uC6A9). \uC81C\uACF5 \uC2DC \uD604\uC7AC \uC140 \uD14D\uC2A4\uD2B8\uC640 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC73C\uBA74 \uC218\uC815\uD558\uC9C0 \uC54A\uC74C. \uC798\uBABB\uB41C \uC140 \uC218\uC815\uC744 \uBC29\uC9C0\uD558\uB824\uBA74 \uBC18\uB4DC\uC2DC \uC0AC\uC6A9\uD558\uC138\uC694."
11849
- ),
11850
- // label 필드 없음 — 좌표 모드 전용
11851
- label: z4.undefined().optional()
11852
- });
11853
- var labelCellEditItemSchema = z4.object({
11854
- label: z4.string().describe(
11855
- "\uB808\uC774\uBE14 \uC140\uC758 \uD14D\uC2A4\uD2B8 (\uD2B8\uB9BC \uBE44\uAD50). \uC774 \uC140\uC758 \uC778\uC811 \uBC29\uD5A5(direction)\uC5D0 \uC788\uB294 \uC140\uC5D0 newText\uB97C \uAE30\uB85D\uD568."
11856
- ),
11857
- direction: z4.enum(["right", "below"]).optional().default("right").describe(
11858
- "\uB808\uC774\uBE14 \uAE30\uC900 \uB300\uC0C1 \uC140 \uBC29\uD5A5. right(\uAE30\uBCF8): \uC624\uB978\uCABD \uC140, below: \uC544\uB798 \uC140. \uBCD1\uD569 \uC140\uC740 span\uC744 \uACE0\uB824\uD558\uC5EC \uB300\uC0C1 \uC140 \uC8FC\uC18C\uB97C \uACC4\uC0B0\uD569\uB2C8\uB2E4."
11859
- ),
11855
+ var cellEditItemSchema = z4.object({
11860
11856
  tableIndex: z4.number().int().nonnegative().optional().describe(
11861
- "\uD0D0\uC0C9\uC744 \uC81C\uD55C\uD560 \uD45C \uC778\uB371\uC2A4 (0-based). \uC0DD\uB7B5\uD558\uBA74 \uBB38\uC11C \uB0B4 \uBAA8\uB4E0 \uCD5C\uC0C1\uC704 \uD45C\uC5D0\uC11C \uD0D0\uC0C9. \uB808\uC774\uBE14\uC774 \uC911\uBCF5\uB420 \uACBD\uC6B0 tableIndex\uB85C \uBC94\uC704\uB97C \uC881\uD788\uC138\uC694."
11857
+ "\uC88C\uD45C \uBAA8\uB4DC: 0-based \uD45C \uC778\uB371\uC2A4(read_document\uC758 kordoc \uBE14\uB85D \uC21C\uC11C, \uC911\uCCA9\uD45C \uC81C\uC678). \uB808\uC774\uBE14 \uBAA8\uB4DC\uC5D0\uC120 \uD0D0\uC0C9 \uBC94\uC704 \uC81C\uD55C\uC6A9(\uC120\uD0DD)."
11858
+ ),
11859
+ row: z4.number().int().nonnegative().optional().describe("\uC88C\uD45C \uBAA8\uB4DC: \uC140\uC758 rowAddr (0-based)"),
11860
+ col: z4.number().int().nonnegative().optional().describe("\uC88C\uD45C \uBAA8\uB4DC: \uC140\uC758 colAddr (0-based)"),
11861
+ label: z4.string().optional().describe(
11862
+ "\uB808\uC774\uBE14 \uBAA8\uB4DC: \uAE30\uC900 \uC140 \uD14D\uC2A4\uD2B8(\uD2B8\uB9BC \uBE44\uAD50). \uC774 \uC140\uC758 direction \uBC29\uD5A5 \uC778\uC811 \uC140\uC5D0 newText\uB97C \uAE30\uB85D. \uC88C\uD45C \uBAA8\uB4DC\uBA74 \uC0DD\uB7B5."
11862
11863
  ),
11864
+ direction: z4.enum(["right", "below"]).optional().describe("\uB808\uC774\uBE14 \uBAA8\uB4DC \uBC29\uD5A5. \uAE30\uBCF8 right(\uC624\uB978\uCABD \uC140), below(\uC544\uB798 \uC140). \uBCD1\uD569 span \uACE0\uB824."),
11863
11865
  newText: z4.string().describe("\uC140\uC5D0 \uC4F8 \uC0C8 \uD14D\uC2A4\uD2B8"),
11864
11866
  expectedText: z4.string().optional().describe(
11865
- "\uD604\uC7AC \uC140 \uD14D\uC2A4\uD2B8 (\uC548\uC804 \uAC80\uC99D\uC6A9). \uC81C\uACF5 \uC2DC \uD604\uC7AC \uC140 \uD14D\uC2A4\uD2B8\uC640 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC73C\uBA74 \uC218\uC815\uD558\uC9C0 \uC54A\uC74C. \uC798\uBABB\uB41C \uC140 \uC218\uC815\uC744 \uBC29\uC9C0\uD558\uB824\uBA74 \uBC18\uB4DC\uC2DC \uC0AC\uC6A9\uD558\uC138\uC694."
11866
- ),
11867
- // row/col 필드 없음 — 레이블 모드 전용
11868
- row: z4.undefined().optional(),
11869
- col: z4.undefined().optional()
11870
- });
11871
- var cellEditItemSchema = z4.union([coordinateCellEditItemSchema, labelCellEditItemSchema]).describe(
11872
- "\uD3B8\uC9D1 \uD56D\uBAA9: \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label+direction) \uC911 \uD558\uB098 \uC0AC\uC6A9. \uB450 \uBAA8\uB4DC\uB97C \uB3D9\uC2DC\uC5D0 \uC9C0\uC815\uD558\uAC70\uB098 \uB458 \uB2E4 \uC0DD\uB7B5\uD558\uBA74 \uC720\uD6A8\uC131 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD569\uB2C8\uB2E4."
11867
+ "\uD604\uC7AC \uC140 \uD14D\uC2A4\uD2B8(\uC548\uC804 \uAC80\uC99D\uC6A9). \uBD88\uC77C\uCE58 \uC2DC \uC218\uC815\uD558\uC9C0 \uC54A\uC74C. \uC798\uBABB\uB41C \uC140 \uC218\uC815 \uBC29\uC9C0\uB97C \uC704\uD574 \uAD8C\uC7A5."
11868
+ )
11869
+ }).describe(
11870
+ "\uD3B8\uC9D1 \uD56D\uBAA9. \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label[+direction]) \uC911 \uD558\uB098\uB97C \uC0AC\uC6A9\uD558\uC138\uC694. \uB458 \uB2E4 \uC9C0\uC815\uD558\uAC70\uB098 \uB458 \uB2E4 \uC0DD\uB7B5\uD558\uBA74 \uC624\uB958\uC785\uB2C8\uB2E4."
11873
11871
  );
11874
11872
  var proposeCellEditSchema = z4.object({
11875
11873
  path: z4.string().describe("\uC218\uC815\uD560 .hwpx \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
@@ -12337,7 +12335,11 @@ var proposeCellEditTool = {
12337
12335
  for (let i = 0; i < input.edits.length; i++) {
12338
12336
  const e = input.edits[i];
12339
12337
  if (!e) continue;
12340
- if ("label" in e && e.label !== void 0) {
12338
+ if (e.label !== void 0 && (e.row !== void 0 || e.col !== void 0)) {
12339
+ resolveErrors.push(
12340
+ `\uD3B8\uC9D1 #${i + 1}: label\uACFC row/col\uC744 \uB3D9\uC2DC\uC5D0 \uC9C0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label) \uC911 \uD558\uB098\uB9CC \uC0AC\uC6A9\uD558\uC138\uC694.`
12341
+ );
12342
+ } else if (e.label !== void 0) {
12341
12343
  const direction = e.direction ?? "right";
12342
12344
  const resolved = resolveLabelAcrossSections(e.label, direction, e.tableIndex);
12343
12345
  if ("error" in resolved) {
@@ -12352,15 +12354,18 @@ var proposeCellEditTool = {
12352
12354
  label: e.label
12353
12355
  });
12354
12356
  }
12355
- } else {
12356
- const coord = e;
12357
+ } else if (e.tableIndex !== void 0 && e.row !== void 0 && e.col !== void 0) {
12357
12358
  resolvedEdits.push({
12358
- tableIndex: coord.tableIndex,
12359
- row: coord.row,
12360
- col: coord.col,
12361
- newText: coord.newText,
12362
- expectedText: coord.expectedText
12359
+ tableIndex: e.tableIndex,
12360
+ row: e.row,
12361
+ col: e.col,
12362
+ newText: e.newText,
12363
+ expectedText: e.expectedText
12363
12364
  });
12365
+ } else {
12366
+ resolveErrors.push(
12367
+ `\uD3B8\uC9D1 #${i + 1}: \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label) \uC911 \uD558\uB098\uB97C \uC644\uC804\uD788 \uC9C0\uC815\uD558\uC138\uC694. read_document\uB85C \uD45C \uB0B4\uC6A9\uC744 \uBA3C\uC800 \uD655\uC778\uD558\uC138\uC694.`
12368
+ );
12364
12369
  }
12365
12370
  }
12366
12371
  if (resolveErrors.length > 0) {
@@ -12631,11 +12636,245 @@ ${diff}`;
12631
12636
  };
12632
12637
  }
12633
12638
  };
12634
- var proposeFormFillSchema = z6.object({
12635
- path: z6.string().describe("\uC591\uC2DD \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
12636
- fields: z6.record(z6.string(), z6.string()).describe("\uCC44\uC6B8 \uD544\uB4DC \uB9E4\uD551: { \uB77C\uBCA8: \uAC12 }. read_document\uB85C \uBA3C\uC800 \uD544\uB4DC \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uC138\uC694"),
12639
+ var proposeFindReplaceSchema = z6.object({
12640
+ path: z6.string().describe("\uC218\uC815\uD560 .hwpx \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
12641
+ find: z6.string().min(1).describe("\uCC3E\uC744 \uD14D\uC2A4\uD2B8"),
12642
+ replace: z6.string().describe("\uBC14\uAFC0 \uD14D\uC2A4\uD2B8"),
12643
+ caseSensitive: z6.boolean().optional().default(false).describe("\uB300\uC18C\uBB38\uC790 \uAD6C\uBD84 (\uAE30\uBCF8\uAC12: false)"),
12644
+ all: z6.boolean().optional().default(true).describe("\uBAA8\uB4E0 \uD56D\uBAA9\uC744 \uAD50\uCCB4\uD560\uC9C0 \uC5EC\uBD80 (\uAE30\uBCF8\uAC12: true). false\uC774\uBA74 \uCCAB \uBC88\uC9F8 \uB9E4\uCE58\uB9CC \uAD50\uCCB4"),
12637
12645
  summary: z6.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
12638
12646
  });
12647
+ function escapeXml3(text3) {
12648
+ return text3.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
12649
+ }
12650
+ function replaceInSectionXml(xml, find, replace, caseSensitive, replaceAll, alreadyReplaced) {
12651
+ if (!replaceAll && alreadyReplaced > 0) {
12652
+ return { xml, count: 0 };
12653
+ }
12654
+ const escapedFind = escapeXml3(find);
12655
+ const escapedReplace = escapeXml3(replace);
12656
+ if (escapedFind.length === 0) {
12657
+ return { xml, count: 0 };
12658
+ }
12659
+ const normFind = caseSensitive ? escapedFind : escapedFind.toLowerCase();
12660
+ const normReplace = escapedReplace;
12661
+ let totalCount = 0;
12662
+ let result = xml;
12663
+ const tNodeRe = /<hp:t>([\s\S]*?)<\/hp:t>/g;
12664
+ let offset = 0;
12665
+ let m = tNodeRe.exec(xml);
12666
+ while (m !== null) {
12667
+ const content = m[1];
12668
+ const nodeResult = replaceInContent(
12669
+ content,
12670
+ normFind,
12671
+ normReplace,
12672
+ caseSensitive,
12673
+ replaceAll,
12674
+ alreadyReplaced + totalCount
12675
+ );
12676
+ if (nodeResult.count > 0) {
12677
+ const openTagLen = "<hp:t>".length;
12678
+ const contentStart = m.index + offset + openTagLen;
12679
+ const contentEnd = contentStart + content.length;
12680
+ result = result.substring(0, contentStart) + nodeResult.text + result.substring(contentEnd);
12681
+ offset += nodeResult.text.length - content.length;
12682
+ totalCount += nodeResult.count;
12683
+ if (!replaceAll && alreadyReplaced + totalCount >= 1) {
12684
+ break;
12685
+ }
12686
+ }
12687
+ m = tNodeRe.exec(xml);
12688
+ }
12689
+ return { xml: result, count: totalCount };
12690
+ }
12691
+ function replaceInContent(content, normFind, escapedReplace, caseSensitive, replaceAll, alreadyReplaced) {
12692
+ const searchIn = caseSensitive ? content : content.toLowerCase();
12693
+ const findLen = normFind.length;
12694
+ if (findLen === 0) return { text: content, count: 0 };
12695
+ let result = "";
12696
+ let pos = 0;
12697
+ let count = 0;
12698
+ while (pos <= content.length) {
12699
+ if (!replaceAll && alreadyReplaced + count >= 1) {
12700
+ result += content.substring(pos);
12701
+ break;
12702
+ }
12703
+ const idx = searchIn.indexOf(normFind, pos);
12704
+ if (idx === -1) {
12705
+ result += content.substring(pos);
12706
+ break;
12707
+ }
12708
+ result += content.substring(pos, idx) + escapedReplace;
12709
+ pos = idx + findLen;
12710
+ count++;
12711
+ }
12712
+ return { text: result, count };
12713
+ }
12714
+ function countOccurrences(str, sub) {
12715
+ if (sub.length === 0) return 0;
12716
+ let count = 0;
12717
+ let pos = 0;
12718
+ while (true) {
12719
+ const idx = str.indexOf(sub, pos);
12720
+ if (idx === -1) break;
12721
+ count++;
12722
+ pos = idx + sub.length;
12723
+ }
12724
+ return count;
12725
+ }
12726
+ async function applyFindReplaceToHwpx(hwpxBuffer, find, replace, caseSensitive, replaceAll) {
12727
+ const zip = await import_jszip3.default.loadAsync(hwpxBuffer);
12728
+ const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
12729
+ const sectionXmls = [];
12730
+ for (const sf of sectionFiles) {
12731
+ const entry = zip.file(sf);
12732
+ const xml = entry ? await entry.async("string") : "";
12733
+ sectionXmls.push(xml);
12734
+ }
12735
+ let totalCount = 0;
12736
+ const newSectionXmls = [];
12737
+ for (let si = 0; si < sectionFiles.length; si++) {
12738
+ const srcXml = sectionXmls[si] ?? "";
12739
+ const { xml: newXml, count } = replaceInSectionXml(
12740
+ srcXml,
12741
+ find,
12742
+ replace,
12743
+ caseSensitive,
12744
+ replaceAll,
12745
+ totalCount
12746
+ );
12747
+ newSectionXmls.push(newXml);
12748
+ totalCount += count;
12749
+ if (!replaceAll && totalCount >= 1) {
12750
+ for (let j = si + 1; j < sectionFiles.length; j++) {
12751
+ newSectionXmls.push(sectionXmls[j] ?? "");
12752
+ }
12753
+ break;
12754
+ }
12755
+ }
12756
+ if (totalCount === 0) {
12757
+ return { buffer: hwpxBuffer, count: 0 };
12758
+ }
12759
+ const out = new import_jszip3.default();
12760
+ const mimetypeEntry = zip.file("mimetype");
12761
+ if (mimetypeEntry) {
12762
+ out.file("mimetype", await mimetypeEntry.async("uint8array"), { compression: "STORE" });
12763
+ }
12764
+ for (const [name, entry] of Object.entries(zip.files)) {
12765
+ if (name === "mimetype" || entry.dir) continue;
12766
+ const sectionIdx = sectionFiles.indexOf(name);
12767
+ if (sectionIdx >= 0) {
12768
+ out.file(name, newSectionXmls[sectionIdx] ?? "");
12769
+ } else {
12770
+ out.file(name, await entry.async("uint8array"));
12771
+ }
12772
+ }
12773
+ const buf = await out.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
12774
+ return {
12775
+ buffer: new Uint8Array(buf),
12776
+ count: totalCount
12777
+ };
12778
+ }
12779
+ var proposeFindReplaceTool = {
12780
+ name: "propose_find_replace",
12781
+ description: "HWPX \uBB38\uC11C \uC804\uCCB4\uC5D0\uC11C \uD14D\uC2A4\uD2B8\uB97C \uCC3E\uC544 \uBC14\uAFC9\uB2C8\uB2E4. \uBCF8\uBB38\xB7\uD45C\xB7\uBA38\uB9AC\uB9D0\xB7\uAF2C\uB9AC\uB9D0 \uB4F1 \uBAA8\uB4E0 \uC139\uC158\uC744 \uB300\uC0C1\uC73C\uB85C \uBB38\uC11C XML\uC744 \uC9C1\uC811 \uD328\uCE58\uD569\uB2C8\uB2E4(rhwp \uC5D4\uC9C4 \uBBF8\uC0AC\uC6A9). \uC774\uBBF8\uC9C0\xB7\uD45C\xB7\uB808\uC774\uC544\uC6C3 \uB4F1 \uBB38\uC11C \uAD6C\uC870\uB97C \uC644\uC804\uD788 \uBCF4\uC874\uD569\uB2C8\uB2E4. .hwpx \uC804\uC6A9\uC785\uB2C8\uB2E4. .hwp(\uAD6C\uD615 OLE \uBC14\uC774\uB108\uB9AC)\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC73C\uBA70, \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C '\uB2E4\uB978 \uC774\uB984\uC73C\uB85C \uC800\uC7A5 \u2192 .hwpx'\uB85C \uC800\uC7A5\uD55C \uD6C4 \uC0AC\uC6A9\uD558\uC138\uC694. all:true(\uAE30\uBCF8\uAC12)\uC774\uBA74 \uBAA8\uB4E0 \uD56D\uBAA9\uC744 \uAD50\uCCB4\uD558\uACE0, all:false\uC774\uBA74 \uCCAB \uBC88\uC9F8 \uB9E4\uCE58\uB9CC \uAD50\uCCB4\uD569\uB2C8\uB2E4. \uD14D\uC2A4\uD2B8\uAC00 \uC5EC\uB7EC \uC11C\uC2DD \uB7F0\uC5D0 \uB098\uB258\uC5B4 \uC788\uC73C\uBA74 \uACBD\uACC4\uB97C \uAC00\uB85C\uC9C0\uB974\uB294 \uD328\uD134\uC740 \uAD50\uCCB4\uB418\uC9C0 \uC54A\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4(\uC11C\uC2DD\uC774 \uB098\uB25C \uD14D\uC2A4\uD2B8). \uCC3E\uC744 \uD14D\uC2A4\uD2B8\uAC00 \uC5C6\uC73C\uBA74 \uD30C\uC77C\uC744 \uC218\uC815\uD558\uC9C0 \uC54A\uACE0 \uC624\uB958\uB97C \uBC18\uD658\uD569\uB2C8\uB2E4. \uBCC0\uACBD \uC0AC\uD56D\uC740 diff \uBBF8\uB9AC\uBCF4\uAE30\uC640 \uD568\uAED8 \uC0AC\uC6A9\uC790 \uC2B9\uC778\uC744 \uBC1B\uC740 \uD6C4\uC5D0\uB9CC \uC800\uC7A5\uB429\uB2C8\uB2E4.",
12782
+ inputSchema: proposeFindReplaceSchema,
12783
+ requiresApproval: true,
12784
+ propose: async ({
12785
+ input,
12786
+ ctx
12787
+ }) => {
12788
+ const safePath = await resolveSafePath(ctx.cwd, input.path);
12789
+ const ext = extname6(safePath).toLowerCase();
12790
+ if (ext === ".hwp") {
12791
+ return "\uC624\uB958: propose_find_replace\uB294 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. .hwp(\uAD6C\uD615 OLE \uBC14\uC774\uB108\uB9AC)\uB294 XML \uC9C1\uC811 \uD3B8\uC9D1\uC774 \uBD88\uAC00\uD569\uB2C8\uB2E4. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C '\uB2E4\uB978 \uC774\uB984\uC73C\uB85C \uC800\uC7A5 \u2192 .hwpx'\uB85C \uC800\uC7A5\uD55C \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
12792
+ }
12793
+ if (ext !== ".hwpx") {
12794
+ return `\uC624\uB958: propose_find_replace\uB294 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C \uD655\uC7A5\uC790: ${ext}. .hwpx \uD30C\uC77C\uC744 \uC9C0\uC815\uD558\uC138\uC694.`;
12795
+ }
12796
+ let originalBuf;
12797
+ try {
12798
+ originalBuf = await readFile6(safePath);
12799
+ } catch {
12800
+ return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.`;
12801
+ }
12802
+ if (originalBuf[0] !== 80 || originalBuf[1] !== 75) {
12803
+ return "\uC624\uB958: \uD30C\uC77C\uC774 \uC720\uD6A8\uD55C .hwpx(ZIP) \uD3EC\uB9F7\uC774 \uC544\uB2D9\uB2C8\uB2E4. \uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uAC70\uB098 \uAD6C\uD615 .hwp(OLE \uBC14\uC774\uB108\uB9AC) \uD3EC\uB9F7\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C .hwpx\uB85C \uC800\uC7A5 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
12804
+ }
12805
+ const originalBytes = new Uint8Array(
12806
+ originalBuf.buffer,
12807
+ originalBuf.byteOffset,
12808
+ originalBuf.byteLength
12809
+ );
12810
+ let newBytes;
12811
+ let replacedCount;
12812
+ try {
12813
+ const result = await applyFindReplaceToHwpx(
12814
+ originalBytes,
12815
+ input.find,
12816
+ input.replace,
12817
+ input.caseSensitive ?? false,
12818
+ input.all ?? true
12819
+ );
12820
+ newBytes = result.buffer;
12821
+ replacedCount = result.count;
12822
+ } catch (e) {
12823
+ return `\uC624\uB958: \uCE58\uD658 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4. ${String(e)}`;
12824
+ }
12825
+ if (replacedCount === 0) {
12826
+ const caseNote = input.caseSensitive ? " (\uB300\uC18C\uBB38\uC790 \uAD6C\uBD84)" : "";
12827
+ return `\uC624\uB958: \uCC3E\uC744 \uD14D\uC2A4\uD2B8\uB97C \uBB38\uC11C\uC5D0\uC11C \uBC1C\uACAC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: "${input.find}"${caseNote}. read_document\uB85C \uBB38\uC11C \uB0B4\uC6A9\uC744 \uD655\uC778\uD558\uACE0 \uC815\uD655\uD55C \uD14D\uC2A4\uD2B8\uB97C \uC9C0\uC815\uD558\uC138\uC694.`;
12828
+ }
12829
+ const warnings = [];
12830
+ try {
12831
+ const exportedResult = await parse2(newBytes.buffer);
12832
+ if (exportedResult.success) {
12833
+ const exportedMd = exportedResult.markdown;
12834
+ const normAfter = input.caseSensitive ?? false ? exportedMd : exportedMd.toLowerCase();
12835
+ const normFind = input.caseSensitive ?? false ? input.find : input.find.toLowerCase();
12836
+ const normReplace = input.caseSensitive ?? false ? input.replace : input.replace.toLowerCase();
12837
+ if (!normReplace.includes(normFind)) {
12838
+ const remaining = countOccurrences(normAfter, normFind);
12839
+ if (remaining > 0) {
12840
+ warnings.push(
12841
+ `\uC77C\uBD80 "${input.find}"(${remaining}\uACF3)\uC774 \uAD50\uCCB4\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uD14D\uC2A4\uD2B8\uAC00 \uC5EC\uB7EC \uC11C\uC2DD \uB7F0\uC5D0 \uB098\uB258\uC5B4 \uC788\uC5B4 \uACBD\uACC4\uB97C \uAC00\uB85C\uC9C0\uB974\uB294 \uD328\uD134\uC740 \uAD50\uCCB4\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4(\uC11C\uC2DD\uC774 \uB098\uB25C \uD14D\uC2A4\uD2B8). \uC774\uBBF8 \uAD50\uCCB4\uB41C ${replacedCount}\uACF3\uC740 \uC815\uC0C1 \uBC18\uC601\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`
12842
+ );
12843
+ }
12844
+ }
12845
+ }
12846
+ } catch {
12847
+ }
12848
+ const { outputPath, willConvertFormat } = resolveOutputPath(safePath);
12849
+ const allLabel = input.all ?? true ? `${replacedCount}\uACF3` : "\uCCAB \uBC88\uC9F8 1\uACF3";
12850
+ const diff = `\uCC3E\uAE30: "${input.find}" \u2192 \uBC14\uAFB8\uAE30: "${input.replace}" (${allLabel} \uAD50\uCCB4\uB428)`;
12851
+ const stagedPath = await stageFile(ctx.sessionId, outputPath, newBytes);
12852
+ const proposalId = crypto.randomUUID();
12853
+ return {
12854
+ proposal: {
12855
+ id: proposalId,
12856
+ kind: "find-replace",
12857
+ targetPath: outputPath,
12858
+ stagedPath,
12859
+ summary: input.summary,
12860
+ diff,
12861
+ warnings,
12862
+ willConvertFormat
12863
+ },
12864
+ commit: async () => {
12865
+ const backupPath = await backupFile(safePath);
12866
+ await commitStaged(stagedPath, outputPath);
12867
+ const backupInfo = backupPath ? ` (\uBC31\uC5C5: ${backupPath})` : "";
12868
+ return `\uC800\uC7A5 \uC644\uB8CC: ${outputPath}${backupInfo}`;
12869
+ }
12870
+ };
12871
+ }
12872
+ };
12873
+ var proposeFormFillSchema = z7.object({
12874
+ path: z7.string().describe("\uC591\uC2DD \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
12875
+ fields: z7.record(z7.string(), z7.string()).describe("\uCC44\uC6B8 \uD544\uB4DC \uB9E4\uD551: { \uB77C\uBCA8: \uAC12 }. read_document\uB85C \uBA3C\uC800 \uD544\uB4DC \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uC138\uC694"),
12876
+ summary: z7.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
12877
+ });
12639
12878
  var proposeFormFillTool = {
12640
12879
  name: "propose_form_fill",
12641
12880
  description: "HWPX/HWP \uC591\uC2DD \uBB38\uC11C\uC758 \uD544\uB4DC\uB97C \uCC44\uC6C1\uB2C8\uB2E4. \uBC18\uB4DC\uC2DC read_document\uB85C \uD604\uC7AC \uD544\uB4DC \uBAA9\uB85D\uC744 \uBA3C\uC800 \uD655\uC778\uD55C \uD6C4 \uC0AC\uC6A9\uD558\uC138\uC694. \uBCC0\uACBD \uC0AC\uD56D\uC740 diff \uBBF8\uB9AC\uBCF4\uAE30\uC640 \uD568\uAED8 \uC0AC\uC6A9\uC790 \uC2B9\uC778\uC744 \uBC1B\uC740 \uD6C4\uC5D0\uB9CC \uC800\uC7A5\uB429\uB2C8\uB2E4.",
@@ -12646,17 +12885,17 @@ var proposeFormFillTool = {
12646
12885
  ctx
12647
12886
  }) => {
12648
12887
  const safePath = await resolveSafePath(ctx.cwd, input.path);
12649
- const ext = extname6(safePath).toLowerCase();
12888
+ const ext = extname7(safePath).toLowerCase();
12650
12889
  if (ext !== ".hwpx" && ext !== ".hwp") {
12651
12890
  return `\uC624\uB958: propose_form_fill\uC740 .hwp/.hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C: ${ext}. .docx \uD30C\uC77C\uC740 propose_edit\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.`;
12652
12891
  }
12653
12892
  let originalBuffer;
12654
12893
  try {
12655
- originalBuffer = await readFile6(safePath);
12894
+ originalBuffer = await readFile7(safePath);
12656
12895
  } catch {
12657
12896
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.`;
12658
12897
  }
12659
- const parseResult = await parse2(originalBuffer.buffer);
12898
+ const parseResult = await parse3(originalBuffer.buffer);
12660
12899
  if (!parseResult.success) {
12661
12900
  const msg = kordocErrorMessage(
12662
12901
  parseResult.code,
@@ -12720,16 +12959,16 @@ var proposeFormFillTool = {
12720
12959
  };
12721
12960
  }
12722
12961
  };
12723
- var proposeSheetEditSchema = z7.object({
12724
- path: z7.string().describe("\uC218\uC815\uD560 XLSX \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
12725
- updates: z7.array(
12726
- z7.object({
12727
- sheet: z7.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
12728
- cell: z7.string().describe("\uC140 \uC8FC\uC18C (\uC608: A1, B3)"),
12729
- value: z7.union([z7.string(), z7.number()]).describe("\uC0C8 \uAC12")
12962
+ var proposeSheetEditSchema = z8.object({
12963
+ path: z8.string().describe("\uC218\uC815\uD560 XLSX \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
12964
+ updates: z8.array(
12965
+ z8.object({
12966
+ sheet: z8.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
12967
+ cell: z8.string().describe("\uC140 \uC8FC\uC18C (\uC608: A1, B3)"),
12968
+ value: z8.union([z8.string(), z8.number()]).describe("\uC0C8 \uAC12")
12730
12969
  })
12731
12970
  ).min(1).describe("\uC218\uC815\uD560 \uC140 \uBAA9\uB85D. read_document\uB85C \uC6D0\uBCF8 \uC2DC\uD2B8 \uAD6C\uC870\uB97C \uBA3C\uC800 \uD655\uC778\uD558\uC138\uC694"),
12732
- summary: z7.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
12971
+ summary: z8.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
12733
12972
  });
12734
12973
  var proposeSheetEditTool = {
12735
12974
  name: "propose_sheet_edit",
@@ -12741,13 +12980,13 @@ var proposeSheetEditTool = {
12741
12980
  ctx
12742
12981
  }) => {
12743
12982
  const safePath = await resolveSafePath(ctx.cwd, input.path);
12744
- const ext = extname7(safePath).toLowerCase();
12983
+ const ext = extname8(safePath).toLowerCase();
12745
12984
  if (ext !== ".xlsx" && ext !== ".xls") {
12746
12985
  return `\uC624\uB958: propose_sheet_edit\uC740 .xlsx/.xls \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C: ${ext}.`;
12747
12986
  }
12748
12987
  let originalBuffer;
12749
12988
  try {
12750
- originalBuffer = await readFile7(safePath);
12989
+ originalBuffer = await readFile8(safePath);
12751
12990
  } catch {
12752
12991
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.`;
12753
12992
  }
@@ -12807,6 +13046,881 @@ var proposeSheetEditTool = {
12807
13046
  };
12808
13047
  }
12809
13048
  };
13049
+ function detectStructuralLoss(beforeBlocks, afterBlocks) {
13050
+ const LABELS = {
13051
+ paragraph: "\uB2E8\uB77D",
13052
+ table: "\uD45C",
13053
+ heading: "\uC81C\uBAA9",
13054
+ list: "\uBAA9\uB85D",
13055
+ image: "\uC774\uBBF8\uC9C0",
13056
+ separator: "\uAD6C\uBD84\uC120"
13057
+ };
13058
+ function histogram(blocks) {
13059
+ const h = {};
13060
+ for (const b of blocks) {
13061
+ h[b.type] = (h[b.type] ?? 0) + 1;
13062
+ }
13063
+ return h;
13064
+ }
13065
+ const beforeH = histogram(beforeBlocks);
13066
+ const afterH = histogram(afterBlocks);
13067
+ const dropParts = [];
13068
+ for (const [type, beforeCount] of Object.entries(beforeH)) {
13069
+ const afterCount = afterH[type] ?? 0;
13070
+ if (afterCount < beforeCount) {
13071
+ const label = LABELS[type] ?? type;
13072
+ dropParts.push(`${label} ${beforeCount}\u2192${afterCount}`);
13073
+ }
13074
+ }
13075
+ if (dropParts.length > 0) {
13076
+ return { lost: true, detail: dropParts.join(", ") };
13077
+ }
13078
+ return { lost: false, detail: "" };
13079
+ }
13080
+ var insertRowOpSchema = z9.object({
13081
+ type: z9.literal("insertRow"),
13082
+ row: z9.number().int().nonnegative().describe("\uAE30\uC900 \uD589 \uC778\uB371\uC2A4 (0-based)"),
13083
+ position: z9.enum(["above", "below"]).describe("\uC0BD\uC785 \uC704\uCE58: above=row \uC704\uC5D0, below=row \uC544\uB798\uC5D0")
13084
+ });
13085
+ var deleteRowOpSchema = z9.object({
13086
+ type: z9.literal("deleteRow"),
13087
+ row: z9.number().int().nonnegative().describe("\uC0AD\uC81C\uD560 \uD589 \uC778\uB371\uC2A4 (0-based)")
13088
+ });
13089
+ var insertColumnOpSchema = z9.object({
13090
+ type: z9.literal("insertColumn"),
13091
+ col: z9.number().int().nonnegative().describe("\uAE30\uC900 \uC5F4 \uC778\uB371\uC2A4 (0-based)"),
13092
+ position: z9.enum(["left", "right"]).describe("\uC0BD\uC785 \uC704\uCE58: left=col \uC67C\uCABD\uC5D0, right=col \uC624\uB978\uCABD\uC5D0")
13093
+ });
13094
+ var deleteColumnOpSchema = z9.object({
13095
+ type: z9.literal("deleteColumn"),
13096
+ col: z9.number().int().nonnegative().describe("\uC0AD\uC81C\uD560 \uC5F4 \uC778\uB371\uC2A4 (0-based)")
13097
+ });
13098
+ var mergeCellsOpSchema = z9.object({
13099
+ type: z9.literal("mergeCells"),
13100
+ startRow: z9.number().int().nonnegative().describe("\uBCD1\uD569 \uC2DC\uC791 \uD589 (0-based)"),
13101
+ startCol: z9.number().int().nonnegative().describe("\uBCD1\uD569 \uC2DC\uC791 \uC5F4 (0-based)"),
13102
+ endRow: z9.number().int().nonnegative().describe("\uBCD1\uD569 \uB05D \uD589 (0-based, \uD3EC\uD568)"),
13103
+ endCol: z9.number().int().nonnegative().describe("\uBCD1\uD569 \uB05D \uC5F4 (0-based, \uD3EC\uD568)")
13104
+ });
13105
+ var operationSchema = z9.discriminatedUnion("type", [
13106
+ insertRowOpSchema,
13107
+ deleteRowOpSchema,
13108
+ insertColumnOpSchema,
13109
+ deleteColumnOpSchema,
13110
+ mergeCellsOpSchema
13111
+ ]).describe("\uD45C \uAD6C\uC870 \uC5F0\uC0B0. \uB098\uC911 \uC5F0\uC0B0\uC758 row/col \uC778\uB371\uC2A4\uB294 \uC55E \uC5F0\uC0B0 \uC801\uC6A9 \uD6C4 \uC0C1\uD0DC\uB97C \uAE30\uC900\uC73C\uB85C \uD55C\uB2E4.");
13112
+ var proposeTableStructureSchema = z9.object({
13113
+ path: z9.string().describe("\uC218\uC815\uD560 .hwpx \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13114
+ anchor: z9.string().min(1).describe(
13115
+ "\uB300\uC0C1 \uD45C\uB97C \uC2DD\uBCC4\uD558\uB294 \uC575\uCEE4 \uD14D\uC2A4\uD2B8. \uD45C \uC548\uC5D0\uB9CC \uC788\uB294 \uB3C5\uD2B9\uD55C \uC140 \uD14D\uC2A4\uD2B8\uB97C \uC9C0\uC815\uD558\uC138\uC694. (\uBD80\uBD84 \uC77C\uCE58, \uACF5\uBC31 \uD2B8\uB9BC) \u2014 read_document\uB85C \uD655\uC778 \uD6C4 \uC0AC\uC6A9 \uAD8C\uC7A5."
13116
+ ),
13117
+ operations: z9.array(operationSchema).min(1).describe(
13118
+ "\uC801\uC6A9\uD560 \uD45C \uAD6C\uC870 \uC5F0\uC0B0 \uBAA9\uB85D (\uC21C\uC11C\uB300\uB85C \uC2E4\uD589). \uAC01 \uC5F0\uC0B0\uC740 \uC774\uC804 \uC5F0\uC0B0\uC774 \uC801\uC6A9\uB41C \uD6C4\uC758 \uD45C \uC0C1\uD0DC \uAE30\uC900\uC73C\uB85C row/col\uC744 \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4."
13119
+ ),
13120
+ summary: z9.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13121
+ });
13122
+ function tokenizeHwpxXml2(xml) {
13123
+ const tokens = [];
13124
+ const re = /<hp:tbl[\s>]|<\/hp:tbl>|<hp:tr[\s>]|<\/hp:tr>|<hp:tc[\s>]|<\/hp:tc>|<hp:t\/>|<hp:t>|<\/hp:t>|<hp:cellAddr[^/>]*|<hp:cellSpan[^/>]*|<hp:cellSz[^/>]*/g;
13125
+ let m = re.exec(xml);
13126
+ while (m !== null) {
13127
+ const raw = m[0];
13128
+ const pos = m.index;
13129
+ if (raw.startsWith("<hp:tbl")) {
13130
+ tokens.push({ kind: "tbl_open", pos, end: pos + raw.length });
13131
+ } else if (raw === "</hp:tbl>") {
13132
+ tokens.push({ kind: "tbl_close", pos, end: pos + raw.length });
13133
+ } else if (raw.startsWith("<hp:tr")) {
13134
+ tokens.push({ kind: "tr_open", pos, end: pos + raw.length });
13135
+ } else if (raw === "</hp:tr>") {
13136
+ tokens.push({ kind: "tr_close", pos, end: pos + raw.length });
13137
+ } else if (raw.startsWith("<hp:tc")) {
13138
+ tokens.push({ kind: "tc_open", pos, end: pos + raw.length });
13139
+ } else if (raw === "</hp:tc>") {
13140
+ tokens.push({ kind: "tc_close", pos, end: pos + raw.length });
13141
+ } else if (raw === "<hp:t/>") {
13142
+ tokens.push({ kind: "t_empty", pos, end: pos + raw.length });
13143
+ } else if (raw === "<hp:t>") {
13144
+ tokens.push({ kind: "t_open", pos, end: pos + raw.length });
13145
+ } else if (raw === "</hp:t>") {
13146
+ tokens.push({ kind: "t_close", pos, end: pos + raw.length });
13147
+ } else if (raw.startsWith("<hp:cellAddr")) {
13148
+ const colM = raw.match(/colAddr="(\d+)"/);
13149
+ const rowM = raw.match(/rowAddr="(\d+)"/);
13150
+ if (colM && rowM) {
13151
+ const selfClose = xml.indexOf("/>", pos);
13152
+ const end = selfClose >= 0 ? selfClose + 2 : pos + raw.length;
13153
+ tokens.push({
13154
+ kind: "cell_addr",
13155
+ pos,
13156
+ end,
13157
+ colAddr: Number(colM[1]),
13158
+ rowAddr: Number(rowM[1])
13159
+ });
13160
+ }
13161
+ } else if (raw.startsWith("<hp:cellSpan")) {
13162
+ const colM = raw.match(/colSpan="(\d+)"/);
13163
+ const rowM = raw.match(/rowSpan="(\d+)"/);
13164
+ const selfClose = xml.indexOf("/>", pos);
13165
+ const end = selfClose >= 0 ? selfClose + 2 : pos + raw.length;
13166
+ tokens.push({
13167
+ kind: "cell_span",
13168
+ pos,
13169
+ end,
13170
+ colSpan: colM ? Number(colM[1]) : 1,
13171
+ rowSpan: rowM ? Number(rowM[1]) : 1
13172
+ });
13173
+ } else if (raw.startsWith("<hp:cellSz")) {
13174
+ const selfClose = xml.indexOf("/>", pos);
13175
+ const end = selfClose >= 0 ? selfClose + 2 : pos + raw.length;
13176
+ tokens.push({ kind: "cell_sz", pos, end });
13177
+ }
13178
+ m = re.exec(xml);
13179
+ }
13180
+ return tokens;
13181
+ }
13182
+ function findAllTopLevelTableRanges(tokens) {
13183
+ const ranges = [];
13184
+ let depth = 0;
13185
+ let topCount = 0;
13186
+ let startPos = -1;
13187
+ for (const tok of tokens) {
13188
+ if (tok.kind === "tbl_open") {
13189
+ if (depth === 0) {
13190
+ startPos = tok.pos;
13191
+ }
13192
+ depth++;
13193
+ } else if (tok.kind === "tbl_close") {
13194
+ depth--;
13195
+ if (depth === 0 && startPos >= 0) {
13196
+ ranges.push({ start: startPos, end: tok.end, index: topCount });
13197
+ topCount++;
13198
+ startPos = -1;
13199
+ }
13200
+ }
13201
+ }
13202
+ return ranges;
13203
+ }
13204
+ function collectTableText(xml, tokens, tblStart, tblEnd) {
13205
+ const parts = [];
13206
+ const tblTokens = tokens.filter((t) => t.pos >= tblStart && t.pos < tblEnd);
13207
+ for (const tok of tblTokens) {
13208
+ if (tok.kind === "t_open") {
13209
+ const closePos = xml.indexOf("</hp:t>", tok.end);
13210
+ if (closePos >= 0 && closePos < tblEnd) {
13211
+ const raw = xml.substring(tok.end, closePos);
13212
+ parts.push(raw.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">"));
13213
+ }
13214
+ }
13215
+ }
13216
+ return parts.join("");
13217
+ }
13218
+ function parseTableRows(_xml, tokens, tblStart, tblEnd) {
13219
+ const tblTokens = tokens.filter((t) => t.pos > tblStart && t.pos < tblEnd);
13220
+ const rows = [];
13221
+ const trStack = [];
13222
+ const tcStack = [];
13223
+ let innerTblDepth = 0;
13224
+ let currentRowCells = [];
13225
+ for (const tok of tblTokens) {
13226
+ if (tok.kind === "tbl_open") {
13227
+ innerTblDepth++;
13228
+ } else if (tok.kind === "tbl_close") {
13229
+ innerTblDepth--;
13230
+ } else if (innerTblDepth > 0) {
13231
+ } else if (tok.kind === "tr_open") {
13232
+ trStack.push({ pos: tok.pos });
13233
+ currentRowCells = [];
13234
+ } else if (tok.kind === "tr_close") {
13235
+ const trEntry = trStack.pop();
13236
+ if (trEntry !== void 0) {
13237
+ rows.push({ trStart: trEntry.pos, trEnd: tok.end, cells: currentRowCells });
13238
+ currentRowCells = [];
13239
+ }
13240
+ } else if (tok.kind === "tc_open") {
13241
+ tcStack.push({ pos: tok.pos });
13242
+ } else if (tok.kind === "tc_close") {
13243
+ const tcEntry = tcStack.pop();
13244
+ if (tcEntry !== void 0) {
13245
+ const tcTokens = tblTokens.filter(
13246
+ (t) => t.pos >= tcEntry.pos && t.pos < tok.end && innerTblDepth === 0
13247
+ );
13248
+ let colAddr = -1;
13249
+ let rowAddr = -1;
13250
+ let colSpan = 1;
13251
+ let rowSpan = 1;
13252
+ for (const t of tcTokens) {
13253
+ if (t.kind === "cell_addr" && t.colAddr !== void 0 && t.rowAddr !== void 0) {
13254
+ colAddr = t.colAddr;
13255
+ rowAddr = t.rowAddr;
13256
+ } else if (t.kind === "cell_span" && t.colSpan !== void 0 && t.rowSpan !== void 0) {
13257
+ colSpan = t.colSpan;
13258
+ rowSpan = t.rowSpan;
13259
+ }
13260
+ }
13261
+ if (colAddr >= 0 && rowAddr >= 0) {
13262
+ currentRowCells.push({
13263
+ colAddr,
13264
+ rowAddr,
13265
+ colSpan,
13266
+ rowSpan,
13267
+ tcStart: tcEntry.pos,
13268
+ tcEnd: tok.end
13269
+ });
13270
+ }
13271
+ }
13272
+ }
13273
+ }
13274
+ return rows;
13275
+ }
13276
+ function parseTblDimensions(xml, tblStart) {
13277
+ const tagEnd = xml.indexOf(">", tblStart);
13278
+ const tagStr = xml.substring(tblStart, tagEnd + 1);
13279
+ const rowM = tagStr.match(/rowCnt="(\d+)"/);
13280
+ const colM = tagStr.match(/colCnt="(\d+)"/);
13281
+ return {
13282
+ rowCnt: rowM ? Number(rowM[1]) : 0,
13283
+ colCnt: colM ? Number(colM[1]) : 0
13284
+ };
13285
+ }
13286
+ function updateTblAttr(tblBlock, attr, newVal) {
13287
+ return tblBlock.replace(new RegExp(`(${attr}=")\\d+(")`), `$1${newVal}$2`);
13288
+ }
13289
+ function clearCellText(tcXml) {
13290
+ let first = true;
13291
+ return tcXml.replace(/<hp:t>([\s\S]*?)<\/hp:t>/g, (_match, _content) => {
13292
+ if (first) {
13293
+ first = false;
13294
+ return "<hp:t/>";
13295
+ }
13296
+ return "<hp:t/>";
13297
+ });
13298
+ }
13299
+ function setCellRowAddr(tcXml, newRow) {
13300
+ return tcXml.replace(/(rowAddr=")(\d+)(")/, `$1${newRow}$3`);
13301
+ }
13302
+ function setCellColAddr(tcXml, newCol) {
13303
+ return tcXml.replace(/(colAddr=")(\d+)(")/, `$1${newCol}$3`);
13304
+ }
13305
+ function resetCellSpan(tcXml) {
13306
+ return tcXml.replace(/(colSpan=")(\d+)(")/, `$11$3`).replace(/(rowSpan=")(\d+)(")/, `$11$3`);
13307
+ }
13308
+ function setCellSpan(tcXml, colSpan, rowSpan) {
13309
+ return tcXml.replace(/(colSpan=")(\d+)(")/, `$1${colSpan}$3`).replace(/(rowSpan=")(\d+)(")/, `$1${rowSpan}$3`);
13310
+ }
13311
+ function insertRowInTbl(tblXml, row, below) {
13312
+ const tokens = tokenizeHwpxXml2(tblXml);
13313
+ const tblStart = 0;
13314
+ const tblEnd = tblXml.length;
13315
+ const rows = parseTableRows(tblXml, tokens, tblStart, tblEnd);
13316
+ const dims = parseTblDimensions(tblXml, tblStart);
13317
+ if (rows.length === 0) {
13318
+ return { ok: false, error: "\uD45C\uC5D0 \uD589\uC774 \uC5C6\uC5B4 \uC0BD\uC785\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." };
13319
+ }
13320
+ const targetRow = rows.find((r) => r.cells.some((c) => c.rowAddr === row));
13321
+ if (!targetRow) {
13322
+ return {
13323
+ ok: false,
13324
+ error: `\uD589 ${row}\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uD45C\uC5D0 rowAddr ${row}\uC778 \uC140\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`
13325
+ };
13326
+ }
13327
+ const newRowAddr = below ? row + 1 : row;
13328
+ for (const r of rows) {
13329
+ for (const c of r.cells) {
13330
+ if (c.rowSpan > 1) {
13331
+ const spanEnd = c.rowAddr + c.rowSpan - 1;
13332
+ if (c.rowAddr < newRowAddr && newRowAddr <= spanEnd) {
13333
+ return {
13334
+ ok: false,
13335
+ error: `\uC774 \uD45C\uC758 \uAE30\uC874 \uBCD1\uD569 \uC140\uACFC \uACB9\uCCD0 \uC548\uC804\uD558\uAC8C \uCC98\uB9AC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC140 (\uD589 ${c.rowAddr}, \uC5F4 ${c.colAddr})\uC758 rowSpan=${c.rowSpan}\uC774 \uC0BD\uC785 \uC704\uCE58 \uD589 ${newRowAddr}\uC744 \uAC00\uB85C\uC9C0\uB985\uB2C8\uB2E4). \uBCD1\uD569\uC744 \uD574\uC81C\uD55C \uD6C4 \uC2DC\uB3C4\uD558\uC138\uC694.`
13336
+ };
13337
+ }
13338
+ }
13339
+ }
13340
+ }
13341
+ const trOffset = targetRow.trStart;
13342
+ let modifiedTr = tblXml.substring(targetRow.trStart, targetRow.trEnd);
13343
+ const sortedCells = [...targetRow.cells].sort((a, b) => b.tcStart - a.tcStart);
13344
+ for (const cell of sortedCells) {
13345
+ const tcLocalStart = cell.tcStart - trOffset;
13346
+ const tcLocalEnd = cell.tcEnd - trOffset;
13347
+ let tcXml = modifiedTr.substring(tcLocalStart, tcLocalEnd);
13348
+ tcXml = setCellRowAddr(tcXml, newRowAddr);
13349
+ tcXml = resetCellSpan(tcXml);
13350
+ tcXml = clearCellText(tcXml);
13351
+ modifiedTr = modifiedTr.substring(0, tcLocalStart) + tcXml + modifiedTr.substring(tcLocalEnd);
13352
+ }
13353
+ let result = tblXml;
13354
+ const allTokens = tokenizeHwpxXml2(result);
13355
+ const addrPatches = [];
13356
+ for (const tok of allTokens) {
13357
+ if (tok.kind === "cell_addr" && tok.rowAddr !== void 0 && tok.rowAddr >= newRowAddr) {
13358
+ addrPatches.push({ pos: tok.pos, end: tok.end, newAddr: tok.rowAddr + 1 });
13359
+ }
13360
+ }
13361
+ addrPatches.sort((a, b) => b.pos - a.pos);
13362
+ for (const p of addrPatches) {
13363
+ const oldTag = result.substring(p.pos, p.end);
13364
+ const newTag = oldTag.replace(/(rowAddr=")(\d+)(")/, `$1${p.newAddr}$3`);
13365
+ result = result.substring(0, p.pos) + newTag + result.substring(p.end);
13366
+ }
13367
+ const resultTokens = tokenizeHwpxXml2(result);
13368
+ const resultRows = parseTableRows(result, resultTokens, 0, result.length);
13369
+ let insertAfterPos;
13370
+ let insertBeforePos;
13371
+ if (below) {
13372
+ const refRow = resultRows.find((r) => r.cells.some((c) => c.rowAddr === row));
13373
+ if (!refRow) {
13374
+ return { ok: false, error: "\uC0BD\uC785 \uAE30\uC900 \uD589\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uB0B4\uBD80 \uC624\uB958)." };
13375
+ }
13376
+ insertAfterPos = refRow.trEnd;
13377
+ } else {
13378
+ const refRow = resultRows.find((r) => r.cells.some((c) => c.rowAddr === row + 1));
13379
+ if (!refRow) {
13380
+ const firstRow = resultRows[0];
13381
+ if (!firstRow) {
13382
+ return { ok: false, error: "\uC0BD\uC785 \uC704\uCE58\uB97C \uACB0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uB0B4\uBD80 \uC624\uB958)." };
13383
+ }
13384
+ insertBeforePos = firstRow.trStart;
13385
+ } else {
13386
+ insertBeforePos = refRow.trStart;
13387
+ }
13388
+ insertAfterPos = -1;
13389
+ }
13390
+ if (below) {
13391
+ result = result.substring(0, insertAfterPos) + modifiedTr + result.substring(insertAfterPos);
13392
+ } else {
13393
+ result = result.substring(0, insertBeforePos) + modifiedTr + result.substring(insertBeforePos);
13394
+ }
13395
+ result = updateTblAttr(result, "rowCnt", dims.rowCnt + 1);
13396
+ return { ok: true, xml: result };
13397
+ }
13398
+ function deleteRowInTbl(tblXml, row) {
13399
+ const tokens = tokenizeHwpxXml2(tblXml);
13400
+ const rows = parseTableRows(tblXml, tokens, 0, tblXml.length);
13401
+ const dims = parseTblDimensions(tblXml, 0);
13402
+ if (rows.length === 0) {
13403
+ return { ok: false, error: "\uD45C\uC5D0 \uD589\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." };
13404
+ }
13405
+ const targetRow = rows.find((r) => r.cells.some((c) => c.rowAddr === row));
13406
+ if (!targetRow) {
13407
+ return {
13408
+ ok: false,
13409
+ error: `\uD589 ${row}\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uD45C\uC5D0 rowAddr ${row}\uC778 \uC140\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`
13410
+ };
13411
+ }
13412
+ for (const cell of targetRow.cells) {
13413
+ if (cell.rowSpan > 1) {
13414
+ return {
13415
+ ok: false,
13416
+ error: `\uC774 \uD45C\uC758 \uAE30\uC874 \uBCD1\uD569 \uC140\uACFC \uACB9\uCCD0 \uC548\uC804\uD558\uAC8C \uCC98\uB9AC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uD589 ${row}\uC758 \uC140 (\uC5F4 ${cell.colAddr})\uC5D0 rowSpan=${cell.rowSpan} \uBCD1\uD569\uC774 \uC788\uC2B5\uB2C8\uB2E4). \uBCD1\uD569\uC744 \uD574\uC81C\uD55C \uD6C4 \uC2DC\uB3C4\uD558\uC138\uC694.`
13417
+ };
13418
+ }
13419
+ }
13420
+ for (const r of rows) {
13421
+ if (r.trStart === targetRow.trStart) continue;
13422
+ for (const c of r.cells) {
13423
+ if (c.rowSpan > 1) {
13424
+ const spanEnd = c.rowAddr + c.rowSpan - 1;
13425
+ if (c.rowAddr < row && row <= spanEnd) {
13426
+ return {
13427
+ ok: false,
13428
+ error: `\uC774 \uD45C\uC758 \uAE30\uC874 \uBCD1\uD569 \uC140\uACFC \uACB9\uCCD0 \uC548\uC804\uD558\uAC8C \uCC98\uB9AC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uB2E4\uB978 \uD589 \uC140 (\uD589 ${c.rowAddr}, \uC5F4 ${c.colAddr})\uC758 rowSpan=${c.rowSpan}\uC774 \uC0AD\uC81C\uD560 \uD589 ${row}\uC744 \uD3EC\uD568\uD569\uB2C8\uB2E4). \uBCD1\uD569\uC744 \uD574\uC81C\uD55C \uD6C4 \uC2DC\uB3C4\uD558\uC138\uC694.`
13429
+ };
13430
+ }
13431
+ }
13432
+ }
13433
+ }
13434
+ let result = tblXml;
13435
+ result = result.substring(0, targetRow.trStart) + result.substring(targetRow.trEnd);
13436
+ const afterTokens = tokenizeHwpxXml2(result);
13437
+ const addrPatches = [];
13438
+ for (const tok of afterTokens) {
13439
+ if (tok.kind === "cell_addr" && tok.rowAddr !== void 0 && tok.rowAddr > row) {
13440
+ addrPatches.push({ pos: tok.pos, end: tok.end, newAddr: tok.rowAddr - 1 });
13441
+ }
13442
+ }
13443
+ addrPatches.sort((a, b) => b.pos - a.pos);
13444
+ for (const p of addrPatches) {
13445
+ const oldTag = result.substring(p.pos, p.end);
13446
+ const newTag = oldTag.replace(/(rowAddr=")(\d+)(")/, `$1${p.newAddr}$3`);
13447
+ result = result.substring(0, p.pos) + newTag + result.substring(p.end);
13448
+ }
13449
+ result = updateTblAttr(result, "rowCnt", dims.rowCnt - 1);
13450
+ return { ok: true, xml: result };
13451
+ }
13452
+ function insertColumnInTbl(tblXml, col, right) {
13453
+ const tokens = tokenizeHwpxXml2(tblXml);
13454
+ const rows = parseTableRows(tblXml, tokens, 0, tblXml.length);
13455
+ const dims = parseTblDimensions(tblXml, 0);
13456
+ if (rows.length === 0) {
13457
+ return { ok: false, error: "\uD45C\uC5D0 \uD589\uC774 \uC5C6\uC5B4 \uC5F4\uC744 \uC0BD\uC785\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." };
13458
+ }
13459
+ const newColAddr = right ? col + 1 : col;
13460
+ for (const r of rows) {
13461
+ for (const c of r.cells) {
13462
+ if (c.colSpan > 1) {
13463
+ const spanEnd = c.colAddr + c.colSpan - 1;
13464
+ if (c.colAddr < newColAddr && newColAddr <= spanEnd) {
13465
+ return {
13466
+ ok: false,
13467
+ error: `\uC774 \uD45C\uC758 \uAE30\uC874 \uBCD1\uD569 \uC140\uACFC \uACB9\uCCD0 \uC548\uC804\uD558\uAC8C \uCC98\uB9AC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC140 (\uD589 ${c.rowAddr}, \uC5F4 ${c.colAddr})\uC758 colSpan=${c.colSpan}\uC774 \uC0BD\uC785 \uC704\uCE58 \uC5F4 ${newColAddr}\uC744 \uAC00\uB85C\uC9C0\uB985\uB2C8\uB2E4). \uBCD1\uD569\uC744 \uD574\uC81C\uD55C \uD6C4 \uC2DC\uB3C4\uD558\uC138\uC694.`
13468
+ };
13469
+ }
13470
+ }
13471
+ }
13472
+ }
13473
+ let result = tblXml;
13474
+ const allTokens = tokenizeHwpxXml2(result);
13475
+ const addrPatches = [];
13476
+ for (const tok of allTokens) {
13477
+ if (tok.kind === "cell_addr" && tok.colAddr !== void 0 && tok.colAddr >= newColAddr) {
13478
+ addrPatches.push({ pos: tok.pos, end: tok.end, newAddr: tok.colAddr + 1 });
13479
+ }
13480
+ }
13481
+ addrPatches.sort((a, b) => b.pos - a.pos);
13482
+ for (const p of addrPatches) {
13483
+ const oldTag = result.substring(p.pos, p.end);
13484
+ const newTag = oldTag.replace(/(colAddr=")(\d+)(")/, `$1${p.newAddr}$3`);
13485
+ result = result.substring(0, p.pos) + newTag + result.substring(p.end);
13486
+ }
13487
+ const resultTokens2 = tokenizeHwpxXml2(result);
13488
+ const resultRows = parseTableRows(result, resultTokens2, 0, result.length);
13489
+ const sortedRows = [...resultRows].sort((a, b) => b.trStart - a.trStart);
13490
+ for (const r of sortedRows) {
13491
+ const refRowAddr = r.cells[0]?.rowAddr ?? 0;
13492
+ const cloneSourceCell = r.cells.find((c) => c.colAddr === col + 1);
13493
+ const anyCell = cloneSourceCell ?? r.cells[0];
13494
+ if (!anyCell) continue;
13495
+ let newTcXml = result.substring(anyCell.tcStart, anyCell.tcEnd);
13496
+ newTcXml = setCellColAddr(newTcXml, newColAddr);
13497
+ newTcXml = setCellRowAddr(newTcXml, refRowAddr);
13498
+ newTcXml = resetCellSpan(newTcXml);
13499
+ newTcXml = clearCellText(newTcXml);
13500
+ const refCell = r.cells.find((c) => c.colAddr === col + 1);
13501
+ let insertPos;
13502
+ if (refCell) {
13503
+ insertPos = refCell.tcStart;
13504
+ } else {
13505
+ const lastCell = r.cells[r.cells.length - 1];
13506
+ insertPos = lastCell ? lastCell.tcEnd : r.trEnd - "</hp:tr>".length;
13507
+ }
13508
+ result = result.substring(0, insertPos) + newTcXml + result.substring(insertPos);
13509
+ }
13510
+ result = updateTblAttr(result, "colCnt", dims.colCnt + 1);
13511
+ return { ok: true, xml: result };
13512
+ }
13513
+ function deleteColumnInTbl(tblXml, col) {
13514
+ const tokens = tokenizeHwpxXml2(tblXml);
13515
+ const rows = parseTableRows(tblXml, tokens, 0, tblXml.length);
13516
+ const dims = parseTblDimensions(tblXml, 0);
13517
+ if (rows.length === 0) {
13518
+ return { ok: false, error: "\uD45C\uC5D0 \uD589\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." };
13519
+ }
13520
+ for (const r of rows) {
13521
+ const cell = r.cells.find((c) => c.colAddr === col);
13522
+ if (cell && cell.colSpan > 1) {
13523
+ return {
13524
+ ok: false,
13525
+ error: `\uC774 \uD45C\uC758 \uAE30\uC874 \uBCD1\uD569 \uC140\uACFC \uACB9\uCCD0 \uC548\uC804\uD558\uAC8C \uCC98\uB9AC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC5F4 ${col}\uC758 \uC140 (\uD589 ${cell.rowAddr})\uC5D0 colSpan=${cell.colSpan} \uBCD1\uD569\uC774 \uC788\uC2B5\uB2C8\uB2E4). \uBCD1\uD569\uC744 \uD574\uC81C\uD55C \uD6C4 \uC2DC\uB3C4\uD558\uC138\uC694.`
13526
+ };
13527
+ }
13528
+ for (const c of r.cells) {
13529
+ if (c.colAddr !== col && c.colSpan > 1) {
13530
+ const spanEnd = c.colAddr + c.colSpan - 1;
13531
+ if (c.colAddr < col && col <= spanEnd) {
13532
+ return {
13533
+ ok: false,
13534
+ error: `\uC774 \uD45C\uC758 \uAE30\uC874 \uBCD1\uD569 \uC140\uACFC \uACB9\uCCD0 \uC548\uC804\uD558\uAC8C \uCC98\uB9AC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC140 (\uD589 ${c.rowAddr}, \uC5F4 ${c.colAddr})\uC758 colSpan=${c.colSpan}\uC774 \uC0AD\uC81C\uD560 \uC5F4 ${col}\uC744 \uD3EC\uD568\uD569\uB2C8\uB2E4). \uBCD1\uD569\uC744 \uD574\uC81C\uD55C \uD6C4 \uC2DC\uB3C4\uD558\uC138\uC694.`
13535
+ };
13536
+ }
13537
+ }
13538
+ }
13539
+ }
13540
+ let result = tblXml;
13541
+ const sortedRows = [...rows].sort((a, b) => b.trStart - a.trStart);
13542
+ for (const r of sortedRows) {
13543
+ const cell = r.cells.find((c) => c.colAddr === col);
13544
+ if (cell) {
13545
+ result = result.substring(0, cell.tcStart) + result.substring(cell.tcEnd);
13546
+ }
13547
+ }
13548
+ const afterTokens = tokenizeHwpxXml2(result);
13549
+ const addrPatches = [];
13550
+ for (const tok of afterTokens) {
13551
+ if (tok.kind === "cell_addr" && tok.colAddr !== void 0 && tok.colAddr > col) {
13552
+ addrPatches.push({ pos: tok.pos, end: tok.end, newAddr: tok.colAddr - 1 });
13553
+ }
13554
+ }
13555
+ addrPatches.sort((a, b) => b.pos - a.pos);
13556
+ for (const p of addrPatches) {
13557
+ const oldTag = result.substring(p.pos, p.end);
13558
+ const newTag = oldTag.replace(/(colAddr=")(\d+)(")/, `$1${p.newAddr}$3`);
13559
+ result = result.substring(0, p.pos) + newTag + result.substring(p.end);
13560
+ }
13561
+ result = updateTblAttr(result, "colCnt", dims.colCnt - 1);
13562
+ return { ok: true, xml: result };
13563
+ }
13564
+ function mergeCellsInTbl(tblXml, startRow, startCol, endRow, endCol) {
13565
+ if (startRow === endRow && startCol === endCol) {
13566
+ return { ok: false, error: "\uBCD1\uD569 \uBC94\uC704\uAC00 \uB2E8\uC77C \uC140\uC785\uB2C8\uB2E4. \uCD5C\uC18C 2\uAC1C \uC774\uC0C1\uC758 \uC140\uC744 \uC9C0\uC815\uD558\uC138\uC694." };
13567
+ }
13568
+ if (startRow > endRow || startCol > endCol) {
13569
+ return { ok: false, error: "\uBCD1\uD569 \uBC94\uC704\uAC00 \uC798\uBABB\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (start > end)." };
13570
+ }
13571
+ const tokens = tokenizeHwpxXml2(tblXml);
13572
+ const rows = parseTableRows(tblXml, tokens, 0, tblXml.length);
13573
+ const rangeCells = [];
13574
+ for (const r of rows) {
13575
+ for (const c of r.cells) {
13576
+ if (c.rowAddr >= startRow && c.rowAddr <= endRow && c.colAddr >= startCol && c.colAddr <= endCol) {
13577
+ rangeCells.push(c);
13578
+ }
13579
+ }
13580
+ }
13581
+ if (rangeCells.length === 0) {
13582
+ return {
13583
+ ok: false,
13584
+ error: `\uBCD1\uD569 \uBC94\uC704 (${startRow},${startCol})~(${endRow},${endCol})\uC5D0 \uC140\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`
13585
+ };
13586
+ }
13587
+ for (const c of rangeCells) {
13588
+ if (c.colSpan > 1 || c.rowSpan > 1) {
13589
+ return {
13590
+ ok: false,
13591
+ error: `\uC774 \uD45C\uC758 \uAE30\uC874 \uBCD1\uD569 \uC140\uACFC \uACB9\uCCD0 \uC548\uC804\uD558\uAC8C \uCC98\uB9AC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC140 (\uD589 ${c.rowAddr}, \uC5F4 ${c.colAddr})\uC774 \uC774\uBBF8 colSpan=${c.colSpan}, rowSpan=${c.rowSpan}\uC73C\uB85C \uBCD1\uD569\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4). \uAE30\uC874 \uBCD1\uD569\uC744 \uD574\uC81C\uD55C \uD6C4 \uC2DC\uB3C4\uD558\uC138\uC694.`
13592
+ };
13593
+ }
13594
+ }
13595
+ for (const r of rows) {
13596
+ for (const c of r.cells) {
13597
+ if (c.colSpan <= 1 && c.rowSpan <= 1) continue;
13598
+ const inRange = c.rowAddr >= startRow && c.rowAddr <= endRow && c.colAddr >= startCol && c.colAddr <= endCol;
13599
+ if (inRange) continue;
13600
+ const colEnd = c.colAddr + c.colSpan - 1;
13601
+ const rowEnd = c.rowAddr + c.rowSpan - 1;
13602
+ if (c.colAddr <= endCol && colEnd >= startCol && c.rowAddr <= endRow && rowEnd >= startRow) {
13603
+ return {
13604
+ ok: false,
13605
+ error: `\uC774 \uD45C\uC758 \uAE30\uC874 \uBCD1\uD569 \uC140\uACFC \uACB9\uCCD0 \uC548\uC804\uD558\uAC8C \uCC98\uB9AC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC140 (\uD589 ${c.rowAddr}, \uC5F4 ${c.colAddr})\uC758 span\uC774 \uBCD1\uD569 \uBC94\uC704\uC640 \uACB9\uCE69\uB2C8\uB2E4). \uAE30\uC874 \uBCD1\uD569\uC744 \uD574\uC81C\uD55C \uD6C4 \uC2DC\uB3C4\uD558\uC138\uC694.`
13606
+ };
13607
+ }
13608
+ }
13609
+ }
13610
+ const colSpan = endCol - startCol + 1;
13611
+ const rowSpan = endRow - startRow + 1;
13612
+ const topLeftCell = rangeCells.find((c) => c.rowAddr === startRow && c.colAddr === startCol);
13613
+ if (!topLeftCell) {
13614
+ return {
13615
+ ok: false,
13616
+ error: `\uBCD1\uD569 \uBC94\uC704\uC758 \uC88C\uC0C1\uB2E8 \uC140 (\uD589 ${startRow}, \uC5F4 ${startCol})\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`
13617
+ };
13618
+ }
13619
+ const cellsToRemove = rangeCells.filter(
13620
+ (c) => !(c.rowAddr === startRow && c.colAddr === startCol)
13621
+ );
13622
+ let result = tblXml;
13623
+ const sortedRemove = [...cellsToRemove].sort((a, b) => b.tcStart - a.tcStart);
13624
+ for (const c of sortedRemove) {
13625
+ result = result.substring(0, c.tcStart) + result.substring(c.tcEnd);
13626
+ }
13627
+ const afterTokens = tokenizeHwpxXml2(result);
13628
+ const afterRows = parseTableRows(result, afterTokens, 0, result.length);
13629
+ const afterTopLeft = afterRows.flatMap((r) => r.cells).find((c) => c.rowAddr === startRow && c.colAddr === startCol);
13630
+ if (!afterTopLeft) {
13631
+ return { ok: false, error: "\uBCD1\uD569 \uD6C4 \uC88C\uC0C1\uB2E8 \uC140\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uB0B4\uBD80 \uC624\uB958)." };
13632
+ }
13633
+ let tcXml = result.substring(afterTopLeft.tcStart, afterTopLeft.tcEnd);
13634
+ tcXml = setCellSpan(tcXml, colSpan, rowSpan);
13635
+ result = result.substring(0, afterTopLeft.tcStart) + tcXml + result.substring(afterTopLeft.tcEnd);
13636
+ return { ok: true, xml: result };
13637
+ }
13638
+ function findTableByAnchorInXml(xml, anchor) {
13639
+ const tokens = tokenizeHwpxXml2(xml);
13640
+ const allRanges = findAllTopLevelTableRanges(tokens);
13641
+ const trimmedAnchor = anchor.trim();
13642
+ const matched = [];
13643
+ for (const r of allRanges) {
13644
+ const text3 = collectTableText(xml, tokens, r.start, r.end);
13645
+ if (text3.includes(trimmedAnchor)) {
13646
+ matched.push(r);
13647
+ }
13648
+ }
13649
+ if (matched.length === 0) {
13650
+ return {
13651
+ error: `anchor\uB97C \uD3EC\uD568\uD55C \uD45C\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: "${anchor}". read_document\uB85C \uD45C \uB0B4\uC6A9\uC744 \uD655\uC778\uD558\uACE0 \uD45C \uC548\uC5D0 \uC788\uB294 \uB3C5\uD2B9\uD55C \uD14D\uC2A4\uD2B8\uB97C anchor\uB85C \uC9C0\uC815\uD558\uC138\uC694.`
13652
+ };
13653
+ }
13654
+ if (matched.length > 1) {
13655
+ return {
13656
+ error: `anchor "${anchor}"\uC774(\uAC00) ${matched.length}\uAC1C\uC758 \uD45C\uC5D0\uC11C \uBC1C\uACAC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uB354 \uAD6C\uCCB4\uC801\uC778 anchor \uD14D\uC2A4\uD2B8\uB97C \uC0AC\uC6A9\uD558\uC5EC \uD45C\uB97C \uD55C \uAC1C\uB9CC \uC120\uD0DD\uD560 \uC218 \uC788\uB3C4\uB85D \uD558\uC138\uC694.`
13657
+ };
13658
+ }
13659
+ return { range: matched[0], sectionXml: xml };
13660
+ }
13661
+ function computeExpectedDelta(ops) {
13662
+ let rowDelta = 0;
13663
+ let colDelta = 0;
13664
+ for (const op of ops) {
13665
+ if (op.type === "mergeCells") return null;
13666
+ if (op.type === "insertRow") rowDelta++;
13667
+ else if (op.type === "deleteRow") rowDelta--;
13668
+ else if (op.type === "insertColumn") colDelta++;
13669
+ else if (op.type === "deleteColumn") colDelta--;
13670
+ }
13671
+ return { rowDelta, colDelta };
13672
+ }
13673
+ function getTblDims(tblXml) {
13674
+ return parseTblDimensions(tblXml, 0);
13675
+ }
13676
+ async function applyOpsToHwpx(hwpxBuffer, anchor, operations) {
13677
+ const zip = await import_jszip4.default.loadAsync(hwpxBuffer);
13678
+ const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
13679
+ let targetSectionIdx = -1;
13680
+ let targetRange = null;
13681
+ const sectionXmls = [];
13682
+ for (let si = 0; si < sectionFiles.length; si++) {
13683
+ const entry = zip.file(sectionFiles[si] ?? "");
13684
+ const xml = entry ? await entry.async("string") : "";
13685
+ sectionXmls.push(xml);
13686
+ if (targetSectionIdx === -1) {
13687
+ const found = findTableByAnchorInXml(xml, anchor);
13688
+ if ("range" in found) {
13689
+ targetSectionIdx = si;
13690
+ targetRange = found.range;
13691
+ } else if (found.error.includes("\uC774(\uAC00)") && found.error.includes("\uAC1C\uC758 \uD45C\uC5D0\uC11C")) {
13692
+ return { ok: false, error: `\uC624\uB958: ${found.error}` };
13693
+ }
13694
+ }
13695
+ }
13696
+ for (let si = sectionXmls.length; si < sectionFiles.length; si++) {
13697
+ const entry = zip.file(sectionFiles[si] ?? "");
13698
+ const xml = entry ? await entry.async("string") : "";
13699
+ sectionXmls.push(xml);
13700
+ }
13701
+ if (targetSectionIdx === -1 || targetRange === null) {
13702
+ return {
13703
+ ok: false,
13704
+ error: `\uC624\uB958: anchor\uB97C \uD3EC\uD568\uD55C \uD45C\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: "${anchor}". read_document\uB85C \uD45C \uB0B4\uC6A9\uC744 \uD655\uC778\uD558\uACE0 \uD45C \uC548\uC5D0 \uC788\uB294 \uB3C5\uD2B9\uD55C \uD14D\uC2A4\uD2B8\uB97C anchor\uB85C \uC9C0\uC815\uD558\uC138\uC694.`
13705
+ };
13706
+ }
13707
+ const sectionXml = sectionXmls[targetSectionIdx] ?? "";
13708
+ let tblBlock = sectionXml.substring(targetRange.start, targetRange.end);
13709
+ const beforeDims = getTblDims(tblBlock);
13710
+ for (let i = 0; i < operations.length; i++) {
13711
+ const op = operations[i];
13712
+ if (!op) continue;
13713
+ let opResult;
13714
+ if (op.type === "insertRow") {
13715
+ opResult = insertRowInTbl(tblBlock, op.row, op.position === "below");
13716
+ } else if (op.type === "deleteRow") {
13717
+ opResult = deleteRowInTbl(tblBlock, op.row);
13718
+ } else if (op.type === "insertColumn") {
13719
+ opResult = insertColumnInTbl(tblBlock, op.col, op.position === "right");
13720
+ } else if (op.type === "deleteColumn") {
13721
+ opResult = deleteColumnInTbl(tblBlock, op.col);
13722
+ } else {
13723
+ opResult = mergeCellsInTbl(tblBlock, op.startRow, op.startCol, op.endRow, op.endCol);
13724
+ }
13725
+ if (!opResult.ok) {
13726
+ return {
13727
+ ok: false,
13728
+ error: `\uC624\uB958: \uC5F0\uC0B0 #${i + 1} (${op.type}) \uC2E4\uD328. \uD30C\uC77C\uC744 \uBCC0\uACBD\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uC6D0\uC778: ${opResult.error}`
13729
+ };
13730
+ }
13731
+ tblBlock = opResult.xml;
13732
+ }
13733
+ const afterDims = getTblDims(tblBlock);
13734
+ const newSectionXml = sectionXml.substring(0, targetRange.start) + tblBlock + sectionXml.substring(targetRange.end);
13735
+ sectionXmls[targetSectionIdx] = newSectionXml;
13736
+ const out = new import_jszip4.default();
13737
+ const mimetypeEntry = zip.file("mimetype");
13738
+ if (mimetypeEntry) {
13739
+ out.file("mimetype", await mimetypeEntry.async("uint8array"), { compression: "STORE" });
13740
+ }
13741
+ for (const [name, entry] of Object.entries(zip.files)) {
13742
+ if (name === "mimetype" || entry.dir) continue;
13743
+ const sectionIdx = sectionFiles.indexOf(name);
13744
+ if (sectionIdx >= 0) {
13745
+ out.file(name, sectionXmls[sectionIdx] ?? "");
13746
+ } else {
13747
+ out.file(name, await entry.async("uint8array"));
13748
+ }
13749
+ }
13750
+ const buf = await out.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
13751
+ return {
13752
+ ok: true,
13753
+ buffer: new Uint8Array(buf),
13754
+ beforeDims,
13755
+ afterDims,
13756
+ anchorTableIndex: targetRange.index
13757
+ };
13758
+ }
13759
+ async function verifyOutputDims(newBytes, anchor, expectedRowCnt, expectedColCnt) {
13760
+ const zip = await import_jszip4.default.loadAsync(newBytes);
13761
+ const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
13762
+ for (const sf of sectionFiles) {
13763
+ const entry = zip.file(sf);
13764
+ const xml = entry ? await entry.async("string") : "";
13765
+ const tokens = tokenizeHwpxXml2(xml);
13766
+ const allRanges = findAllTopLevelTableRanges(tokens);
13767
+ const trimmedAnchor = anchor.trim();
13768
+ for (const r of allRanges) {
13769
+ const text3 = collectTableText(xml, tokens, r.start, r.end);
13770
+ if (text3.includes(trimmedAnchor)) {
13771
+ const tblBlock = xml.substring(r.start, r.end);
13772
+ const dims = getTblDims(tblBlock);
13773
+ if (dims.rowCnt !== expectedRowCnt || dims.colCnt !== expectedColCnt) {
13774
+ return {
13775
+ ok: false,
13776
+ error: `\uC790\uAE30\uAC80\uC99D \uC2E4\uD328 \u2014 \uC608\uC0C1 \uCE58\uC218 (${expectedRowCnt}\uD589 \xD7 ${expectedColCnt}\uC5F4)\uC640 \uC2E4\uC81C \uCE58\uC218 (${dims.rowCnt}\uD589 \xD7 ${dims.colCnt}\uC5F4)\uAC00 \uB2E4\uB985\uB2C8\uB2E4.`
13777
+ };
13778
+ }
13779
+ return { ok: true };
13780
+ }
13781
+ }
13782
+ }
13783
+ return { ok: true };
13784
+ }
13785
+ var proposeTableStructureTool = {
13786
+ name: "propose_table_structure",
13787
+ description: "HWPX \uBB38\uC11C\uC5D0\uC11C anchor \uD14D\uC2A4\uD2B8\uB85C \uC2DD\uBCC4\uB41C \uD45C\uC758 \uAD6C\uC870(\uD589/\uC5F4/\uC140 \uBCD1\uD569)\uB97C \uC218\uC815\uD569\uB2C8\uB2E4. ZIP XML \uC9C1\uC811 \uD328\uCE58 \uBC29\uC2DD\uC73C\uB85C \uB3D9\uC791\uD569\uB2C8\uB2E4(rhwp \uBBF8\uC0AC\uC6A9). \uC774\uBBF8\uC9C0\xB7\uC911\uCCA9\uD45C \uB4F1 \uBCF5\uC7A1\uD55C \uBB38\uC11C\uB3C4 \uAD6C\uC870 \uC190\uC2E4 \uC5C6\uC774 \uC548\uC804\uD558\uAC8C \uD3B8\uC9D1 \uAC00\uB2A5\uD569\uB2C8\uB2E4. anchor\uB294 \uB300\uC0C1 \uD45C \uC548\uC5D0\uB9CC \uC788\uB294 \uB3C5\uD2B9\uD55C \uC140 \uD14D\uC2A4\uD2B8\uB97C \uC9C0\uC815\uD558\uC138\uC694 \u2014 \uBA3C\uC800 read_document\uB85C \uBB38\uC11C\uB97C \uC77D\uC5B4 \uD45C \uB0B4\uC6A9\uC744 \uD655\uC778\uD558\uC138\uC694. \uC5F0\uC0B0\uC740 \uC9C0\uC815\uB41C \uC21C\uC11C\uB300\uB85C \uC801\uC6A9\uB418\uBA70, \uB098\uC911 \uC5F0\uC0B0\uC758 row/col \uC778\uB371\uC2A4\uB294 \uC55E \uC5F0\uC0B0 \uC801\uC6A9 \uD6C4\uC758 \uD45C \uC0C1\uD0DC\uB97C \uAE30\uC900\uC73C\uB85C \uD569\uB2C8\uB2E4. .hwpx \uC804\uC6A9\uC785\uB2C8\uB2E4 (.hwp\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC73C\uBA70, Hancom\uC5D0\uC11C .hwpx\uB85C \uC800\uC7A5 \uD6C4 \uC0AC\uC6A9\uD558\uC138\uC694). \uAE30\uC874 \uBCD1\uD569 \uC140\uACFC \uAD50\uCC28\uD558\uB294 \uC5F0\uC0B0\uC740 \uC548\uC804\uC744 \uC704\uD574 \uAC70\uBD80\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uBCC0\uACBD \uC0AC\uD56D\uC740 \uC790\uAE30\uAC80\uC99D \uAC8C\uC774\uD2B8\uB97C \uD1B5\uACFC\uD55C \uD6C4 \uC0AC\uC6A9\uC790 \uC2B9\uC778\uC744 \uBC1B\uC544\uC57C\uB9CC \uC800\uC7A5\uB429\uB2C8\uB2E4.",
13788
+ inputSchema: proposeTableStructureSchema,
13789
+ requiresApproval: true,
13790
+ propose: async ({
13791
+ input,
13792
+ ctx
13793
+ }) => {
13794
+ const safePath = await resolveSafePath(ctx.cwd, input.path);
13795
+ const ext = extname9(safePath).toLowerCase();
13796
+ if (ext === ".hwp") {
13797
+ return "\uC624\uB958: propose_table_structure\uB294 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. .hwp(\uAD6C\uD615 OLE \uBC14\uC774\uB108\uB9AC)\uB294 XML \uC9C1\uC811 \uD3B8\uC9D1\uC774 \uBD88\uAC00\uD569\uB2C8\uB2E4. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C '\uB2E4\uB978 \uC774\uB984\uC73C\uB85C \uC800\uC7A5 \u2192 .hwpx'\uB85C \uC800\uC7A5\uD55C \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
13798
+ }
13799
+ if (ext !== ".hwpx") {
13800
+ return `\uC624\uB958: propose_table_structure\uB294 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C \uD655\uC7A5\uC790: ${ext}. .hwpx \uD30C\uC77C\uC744 \uC9C0\uC815\uD558\uC138\uC694.`;
13801
+ }
13802
+ let originalBuf;
13803
+ try {
13804
+ originalBuf = await readFile9(safePath);
13805
+ } catch {
13806
+ return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.`;
13807
+ }
13808
+ if (originalBuf[0] !== 80 || originalBuf[1] !== 75) {
13809
+ return "\uC624\uB958: \uD30C\uC77C\uC774 \uC720\uD6A8\uD55C .hwpx(ZIP) \uD3EC\uB9F7\uC774 \uC544\uB2D9\uB2C8\uB2E4. \uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uAC70\uB098 \uAD6C\uD615 .hwp(OLE \uBC14\uC774\uB108\uB9AC) \uD3EC\uB9F7\uC785\uB2C8\uB2E4. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C .hwpx\uB85C \uC800\uC7A5 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
13810
+ }
13811
+ const originalBytes = new Uint8Array(
13812
+ originalBuf.buffer,
13813
+ originalBuf.byteOffset,
13814
+ originalBuf.byteLength
13815
+ );
13816
+ let originalBlocks = null;
13817
+ try {
13818
+ const origResult = await parse4(originalBuf.buffer);
13819
+ if (origResult.success) {
13820
+ originalBlocks = origResult.blocks;
13821
+ }
13822
+ } catch {
13823
+ }
13824
+ let applyResult;
13825
+ try {
13826
+ applyResult = await applyOpsToHwpx(originalBytes, input.anchor, input.operations);
13827
+ } catch (e) {
13828
+ return `\uC624\uB958: \uD45C \uAD6C\uC870 \uD3B8\uC9D1 \uC911 \uC608\uC678\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4. ${String(e)}`;
13829
+ }
13830
+ if (!applyResult.ok) {
13831
+ return applyResult.error;
13832
+ }
13833
+ const { buffer: newBytes, beforeDims, afterDims } = applyResult;
13834
+ const warnings = [];
13835
+ let exportedMd = "";
13836
+ let kordocOk = false;
13837
+ let exportedBlocks = null;
13838
+ try {
13839
+ const exportedResult = await parse4(newBytes.buffer);
13840
+ if (exportedResult.success) {
13841
+ exportedMd = exportedResult.markdown;
13842
+ exportedBlocks = exportedResult.blocks;
13843
+ kordocOk = true;
13844
+ }
13845
+ } catch {
13846
+ }
13847
+ if (!kordocOk) {
13848
+ return `\uC624\uB958: \uB0B4\uBCF4\uB0B8 \uBB38\uC11C\uB97C kordoc\uC73C\uB85C \uC7AC\uD30C\uC2F1\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uBB38\uC11C\uAC00 \uC190\uC0C1\uB418\uC5C8\uC744 \uC218 \uC788\uC73C\uBBC0\uB85C \uD30C\uC77C\uC744 \uC800\uC7A5\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`;
13849
+ }
13850
+ if (originalBlocks !== null && exportedBlocks !== null) {
13851
+ const lossResult = detectStructuralLoss(originalBlocks, exportedBlocks);
13852
+ if (lossResult.lost) {
13853
+ return `\uC624\uB958: XML \uD3B8\uC9D1 \uD6C4 \uAD6C\uC870 \uC190\uC2E4\uC774 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4(${lossResult.detail}). XML \uD328\uCE58\uC5D0 \uBC84\uADF8\uAC00 \uC788\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uD30C\uC77C\uC744 \uC800\uC7A5\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`;
13854
+ }
13855
+ }
13856
+ const hasDeleteOp = input.operations.some(
13857
+ (op) => op.type === "deleteRow" || op.type === "deleteColumn"
13858
+ );
13859
+ const anchorInExported = exportedMd.includes(input.anchor.trim());
13860
+ if (!anchorInExported) {
13861
+ if (!hasDeleteOp) {
13862
+ return `\uC624\uB958: \uC790\uAE30\uAC80\uC99D \uC2E4\uD328 \u2014 \uB0B4\uBCF4\uB0B8 \uBB38\uC11C\uC5D0\uC11C anchor "${input.anchor}"\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uD45C \uAD6C\uC870\uAC00 \uC190\uC0C1\uB418\uC5C8\uC744 \uC218 \uC788\uC73C\uBBC0\uB85C \uD30C\uC77C\uC744 \uC800\uC7A5\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`;
13863
+ }
13864
+ warnings.push(
13865
+ `\uC790\uAE30\uAC80\uC99D: \uB0B4\uBCF4\uB0B8 \uBB38\uC11C\uC5D0\uC11C anchor "${input.anchor}"\uAC00 \uBCF4\uC774\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC0AD\uC81C \uC5F0\uC0B0\uC73C\uB85C anchor\uAC00 \uD3EC\uD568\uB41C \uD589/\uC5F4\uC774 \uC81C\uAC70\uB418\uC5C8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uACB0\uACFC\uB97C \uC9C1\uC811 \uD655\uC778\uD558\uC138\uC694.`
13866
+ );
13867
+ }
13868
+ const delta = computeExpectedDelta(input.operations);
13869
+ if (delta !== null) {
13870
+ const expectedRowCnt = beforeDims.rowCnt + delta.rowDelta;
13871
+ const expectedColCnt = beforeDims.colCnt + delta.colDelta;
13872
+ if (afterDims.rowCnt !== expectedRowCnt || afterDims.colCnt !== expectedColCnt) {
13873
+ return `\uC624\uB958: \uC790\uAE30\uAC80\uC99D \uC2E4\uD328 \u2014 \uC608\uC0C1 \uCE58\uC218 (${expectedRowCnt}\uD589 \xD7 ${expectedColCnt}\uC5F4)\uC640 \uC2E4\uC81C \uCE58\uC218 (${afterDims.rowCnt}\uD589 \xD7 ${afterDims.colCnt}\uC5F4)\uAC00 \uB2E4\uB985\uB2C8\uB2E4. \uD30C\uC77C\uC744 \uC800\uC7A5\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`;
13874
+ }
13875
+ if (anchorInExported) {
13876
+ const dimVerify = await verifyOutputDims(
13877
+ newBytes,
13878
+ input.anchor,
13879
+ expectedRowCnt,
13880
+ expectedColCnt
13881
+ );
13882
+ if (!dimVerify.ok) {
13883
+ return `\uC624\uB958: ${dimVerify.error} \uD30C\uC77C\uC744 \uC800\uC7A5\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`;
13884
+ }
13885
+ }
13886
+ }
13887
+ const { outputPath, willConvertFormat } = resolveOutputPath(safePath);
13888
+ const opDescriptions = input.operations.map((op, i) => {
13889
+ if (op.type === "insertRow")
13890
+ return ` ${i + 1}. \uD589 ${op.row} ${op.position === "below" ? "\uC544\uB798" : "\uC704"}\uC5D0 \uD589 \uC0BD\uC785`;
13891
+ if (op.type === "deleteRow") return ` ${i + 1}. \uD589 ${op.row} \uC0AD\uC81C`;
13892
+ if (op.type === "insertColumn")
13893
+ return ` ${i + 1}. \uC5F4 ${op.col} ${op.position === "right" ? "\uC624\uB978\uCABD" : "\uC67C\uCABD"}\uC5D0 \uC5F4 \uC0BD\uC785`;
13894
+ if (op.type === "deleteColumn") return ` ${i + 1}. \uC5F4 ${op.col} \uC0AD\uC81C`;
13895
+ return ` ${i + 1}. (${op.startRow},${op.startCol})~(${op.endRow},${op.endCol}) \uC140 \uBCD1\uD569`;
13896
+ });
13897
+ const diff = `anchor: "${input.anchor}" (\uD45C \uC778\uB371\uC2A4 ${applyResult.anchorTableIndex})
13898
+ \uC774\uC804: ${beforeDims.rowCnt}\uD589 \xD7 ${beforeDims.colCnt}\uC5F4
13899
+ \uC774\uD6C4: ${afterDims.rowCnt}\uD589 \xD7 ${afterDims.colCnt}\uC5F4
13900
+ \uC5F0\uC0B0 (${input.operations.length}\uAC1C):
13901
+ ${opDescriptions.join("\n")}`;
13902
+ const stagedPath = await stageFile(ctx.sessionId, outputPath, newBytes);
13903
+ const proposalId = crypto.randomUUID();
13904
+ return {
13905
+ proposal: {
13906
+ id: proposalId,
13907
+ kind: "table-structure",
13908
+ targetPath: outputPath,
13909
+ stagedPath,
13910
+ summary: input.summary,
13911
+ diff,
13912
+ warnings,
13913
+ willConvertFormat
13914
+ },
13915
+ commit: async () => {
13916
+ const backupPath = await backupFile(safePath);
13917
+ await commitStaged(stagedPath, outputPath);
13918
+ const backupInfo = backupPath ? ` (\uBC31\uC5C5: ${backupPath})` : "";
13919
+ return `\uC800\uC7A5 \uC644\uB8CC: ${outputPath}${backupInfo}`;
13920
+ }
13921
+ };
13922
+ }
13923
+ };
12810
13924
  var MAX_MARKDOWN_LENGTH2 = 8e4;
12811
13925
  var MAX_FILE_SIZE_BYTES = 100 * 1024 * 1024;
12812
13926
  var PLAIN_TEXT_EXTS = /* @__PURE__ */ new Set([".md", ".markdown", ".txt", ".text"]);
@@ -12883,11 +13997,11 @@ function searchExcerpts(markdown, query) {
12883
13997
  }
12884
13998
  return result;
12885
13999
  }
12886
- var readDocumentSchema = z8.object({
12887
- path: z8.string().describe("\uC77D\uC744 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
12888
- pages: z8.string().optional().describe('\uC77D\uC744 \uD398\uC774\uC9C0 \uBC94\uC704 (\uC608: "1-3", "1,3,5") \u2014 \uBBF8\uC9C0\uC815 \uC2DC \uC804\uCCB4'),
12889
- outline: z8.boolean().optional().describe("\uD5E4\uB529(\uC81C\uBAA9) \uAD6C\uC870\uB9CC \uBC18\uD658\uD574 \uBB38\uC11C \uAC1C\uC694 \uD30C\uC545 (\uB300\uD615 \uBB38\uC11C \uD0D0\uC0C9\uC6A9)"),
12890
- search: z8.string().optional().describe("\uD0A4\uC6CC\uB4DC\uAC00 \uD3EC\uD568\uB41C \uBD80\uBD84\uACFC \uC8FC\uBCC0 \uB9E5\uB77D\uB9CC \uBC18\uD658 (\uB300\uD615 \uBB38\uC11C\uC5D0\uC11C \uD544\uC694\uD55C \uBD80\uBD84\uB9CC \uC77D\uAE30)")
14000
+ var readDocumentSchema = z10.object({
14001
+ path: z10.string().describe("\uC77D\uC744 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
14002
+ pages: z10.string().optional().describe('\uC77D\uC744 \uD398\uC774\uC9C0 \uBC94\uC704 (\uC608: "1-3", "1,3,5") \u2014 \uBBF8\uC9C0\uC815 \uC2DC \uC804\uCCB4'),
14003
+ outline: z10.boolean().optional().describe("\uD5E4\uB529(\uC81C\uBAA9) \uAD6C\uC870\uB9CC \uBC18\uD658\uD574 \uBB38\uC11C \uAC1C\uC694 \uD30C\uC545 (\uB300\uD615 \uBB38\uC11C \uD0D0\uC0C9\uC6A9)"),
14004
+ search: z10.string().optional().describe("\uD0A4\uC6CC\uB4DC\uAC00 \uD3EC\uD568\uB41C \uBD80\uBD84\uACFC \uC8FC\uBCC0 \uB9E5\uB77D\uB9CC \uBC18\uD658 (\uB300\uD615 \uBB38\uC11C\uC5D0\uC11C \uD544\uC694\uD55C \uBD80\uBD84\uB9CC \uC77D\uAE30)")
12891
14005
  });
12892
14006
  function applyReadMode(body, outline, search) {
12893
14007
  if (outline === true) {
@@ -12916,11 +14030,11 @@ var readDocumentTool = {
12916
14030
  const msg = e instanceof Error ? e.message : String(e);
12917
14031
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
12918
14032
  }
12919
- const ext = extname8(safePath).toLowerCase();
14033
+ const ext = extname10(safePath).toLowerCase();
12920
14034
  if (PLAIN_TEXT_EXTS.has(ext)) {
12921
14035
  let raw;
12922
14036
  try {
12923
- raw = await readFile8(safePath, "utf-8");
14037
+ raw = await readFile10(safePath, "utf-8");
12924
14038
  } catch (e) {
12925
14039
  const msg = e instanceof Error ? e.message : String(e);
12926
14040
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
@@ -12944,7 +14058,7 @@ var readDocumentTool = {
12944
14058
  \u26A0\uFE0F \uB0B4\uC6A9\uC774 \uB108\uBB34 \uAE38\uC5B4 \uC57D 80,000\uC790\uC5D0\uC11C \uC798\uB838\uC2B5\uB2C8\uB2E4. (\uD3C9\uBB38 \uD14D\uC2A4\uD2B8 \uD30C\uC77C\uC774\uB77C \uD398\uC774\uC9C0 \uB2E8\uC704 \uBD84\uD560 \uC77D\uAE30\uB294 \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4)` : "";
12945
14059
  return `${meta2}${body2}${truncationNotice2}`;
12946
14060
  }
12947
- const result = await parse3(safePath, input.pages ? { pages: input.pages } : void 0);
14061
+ const result = await parse5(safePath, input.pages ? { pages: input.pages } : void 0);
12948
14062
  if (!result.success) {
12949
14063
  const msg = kordocErrorMessage(result.code, `\uBB38\uC11C\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${result.error}`);
12950
14064
  return `\uC624\uB958: ${msg}`;
@@ -13020,8 +14134,8 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
13020
14134
  ".csv",
13021
14135
  ".log"
13022
14136
  ]);
13023
- var readFileSchema = z9.object({
13024
- path: z9.string().describe("\uC77D\uC744 \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)")
14137
+ var readFileSchema = z11.object({
14138
+ path: z11.string().describe("\uC77D\uC744 \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)")
13025
14139
  });
13026
14140
  var readFileTool = {
13027
14141
  name: "read_file",
@@ -13045,8 +14159,8 @@ var readFileTool = {
13045
14159
  if (info.size > MAX_SIZE) {
13046
14160
  return `\uC624\uB958: \uD30C\uC77C\uC774 \uB108\uBB34 \uD07D\uB2C8\uB2E4 (${Math.round(info.size / 1024)}KB). \uCD5C\uB300 256KB\uAE4C\uC9C0 \uC77D\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`;
13047
14161
  }
13048
- const { extname: extname11 } = await import("path");
13049
- const ext = extname11(safePath).toLowerCase();
14162
+ const { extname: extname13 } = await import("path");
14163
+ const ext = extname13(safePath).toLowerCase();
13050
14164
  if (!TEXT_EXTENSIONS.has(ext) && ext !== "") {
13051
14165
  return `\uC624\uB958: '${ext}' \uD615\uC2DD\uC740 \uD14D\uC2A4\uD2B8 \uD30C\uC77C\uB85C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uC11C \uD30C\uC77C\uC774\uB77C\uBA74 read_document \uD234\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.`;
13052
14166
  }
@@ -13061,9 +14175,9 @@ var readFileTool = {
13061
14175
  }
13062
14176
  };
13063
14177
  var MAX_PREVIEW_CHARS = 1e4;
13064
- var writeNewDocumentSchema = z10.object({
13065
- path: z10.string().describe("\uC0DD\uC131\uD560 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13066
- markdown: z10.string().describe("\uC0C8 \uBB38\uC11C \uB0B4\uC6A9 (\uB9C8\uD06C\uB2E4\uC6B4 \uD615\uC2DD)")
14178
+ var writeNewDocumentSchema = z12.object({
14179
+ path: z12.string().describe("\uC0DD\uC131\uD560 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
14180
+ markdown: z12.string().describe("\uC0C8 \uBB38\uC11C \uB0B4\uC6A9 (\uB9C8\uD06C\uB2E4\uC6B4 \uD615\uC2DD)")
13067
14181
  });
13068
14182
  var writeNewDocumentTool = {
13069
14183
  name: "write_new_document",
@@ -13075,7 +14189,7 @@ var writeNewDocumentTool = {
13075
14189
  ctx
13076
14190
  }) => {
13077
14191
  const safePath = await resolveSafePath(ctx.cwd, input.path);
13078
- const ext = extname9(safePath).toLowerCase();
14192
+ const ext = extname11(safePath).toLowerCase();
13079
14193
  try {
13080
14194
  await stat5(safePath);
13081
14195
  return `\uC624\uB958: \uD30C\uC77C\uC774 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4: ${input.path}. \uAE30\uC874 \uD30C\uC77C\uC744 \uC218\uC815\uD558\uB824\uBA74 propose_edit\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.`;
@@ -13125,12 +14239,12 @@ ${preview}`,
13125
14239
  };
13126
14240
  }
13127
14241
  };
13128
- var writeNewSpreadsheetSchema = z11.object({
13129
- path: z11.string().describe("\uC0DD\uC131\uD560 XLSX \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13130
- sheets: z11.array(
13131
- z11.object({
13132
- name: z11.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
13133
- rows: z11.array(z11.array(z11.string())).describe("\uD589 \uB370\uC774\uD130 \uBC30\uC5F4 (\uAC01 \uD589\uC740 \uC140 \uAC12 \uBC30\uC5F4)")
14242
+ var writeNewSpreadsheetSchema = z13.object({
14243
+ path: z13.string().describe("\uC0DD\uC131\uD560 XLSX \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
14244
+ sheets: z13.array(
14245
+ z13.object({
14246
+ name: z13.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
14247
+ rows: z13.array(z13.array(z13.string())).describe("\uD589 \uB370\uC774\uD130 \uBC30\uC5F4 (\uAC01 \uD589\uC740 \uC140 \uAC12 \uBC30\uC5F4)")
13134
14248
  })
13135
14249
  ).min(1).describe("\uC0DD\uC131\uD560 \uC2DC\uD2B8 \uBAA9\uB85D")
13136
14250
  });
@@ -13144,7 +14258,7 @@ var writeNewSpreadsheetTool = {
13144
14258
  ctx
13145
14259
  }) => {
13146
14260
  const safePath = await resolveSafePath(ctx.cwd, input.path);
13147
- const ext = extname10(safePath).toLowerCase();
14261
+ const ext = extname12(safePath).toLowerCase();
13148
14262
  if (ext !== ".xlsx") {
13149
14263
  return `\uC624\uB958: write_new_spreadsheet\uC740 .xlsx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD655\uC7A5\uC790: ${ext}.`;
13150
14264
  }
@@ -13206,7 +14320,9 @@ function createDocTools(_ctx) {
13206
14320
  proposeEditTool,
13207
14321
  proposeFormFillTool,
13208
14322
  proposeCellEditTool,
14323
+ proposeFindReplaceTool,
13209
14324
  proposeSheetEditTool,
14325
+ proposeTableStructureTool,
13210
14326
  listFormObjectsTool,
13211
14327
  proposeFormObjectTool,
13212
14328
  writeNewDocumentTool,
@@ -14014,10 +15130,10 @@ async function runOnboarding() {
14014
15130
 
14015
15131
  // src/update.ts
14016
15132
  import { spawn } from "child_process";
14017
- import { mkdir as mkdir4, readFile as readFile9, writeFile as writeFile3 } from "fs/promises";
15133
+ import { mkdir as mkdir4, readFile as readFile11, writeFile as writeFile3 } from "fs/promises";
14018
15134
  import { dirname as dirname3 } from "path";
14019
15135
  function compareSemver(a, b) {
14020
- const parse4 = (v) => {
15136
+ const parse6 = (v) => {
14021
15137
  const clean = v.replace(/^v/, "").split("-")[0] ?? "";
14022
15138
  const parts = clean.split(".").map((p) => {
14023
15139
  const n = parseInt(p, 10);
@@ -14025,8 +15141,8 @@ function compareSemver(a, b) {
14025
15141
  });
14026
15142
  return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
14027
15143
  };
14028
- const [aMaj, aMin, aPat] = parse4(a);
14029
- const [bMaj, bMin, bPat] = parse4(b);
15144
+ const [aMaj, aMin, aPat] = parse6(a);
15145
+ const [bMaj, bMin, bPat] = parse6(b);
14030
15146
  if (aMaj !== bMaj) return aMaj < bMaj ? -1 : 1;
14031
15147
  if (aMin !== bMin) return aMin < bMin ? -1 : 1;
14032
15148
  if (aPat !== bPat) return aPat < bPat ? -1 : 1;
@@ -14035,7 +15151,7 @@ function compareSemver(a, b) {
14035
15151
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
14036
15152
  async function readCache(cachePath) {
14037
15153
  try {
14038
- const raw = await readFile9(cachePath, "utf-8");
15154
+ const raw = await readFile11(cachePath, "utf-8");
14039
15155
  const parsed = JSON.parse(raw);
14040
15156
  if (parsed !== null && typeof parsed === "object" && "checkedAt" in parsed && "latest" in parsed && typeof parsed.checkedAt === "string" && typeof parsed.latest === "string") {
14041
15157
  return parsed;