kordoc 4.14.2 → 4.14.3

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.
Files changed (151) hide show
  1. package/README.md +38 -5
  2. package/dist/{-AQKXLFIM.js → -7XBPXCXN.js} +13 -11
  3. package/dist/{chunk-FOJZNMMK.js → chunk-2LQGSDZ6.js} +1 -1
  4. package/dist/chunk-2LQGSDZ6.js.map +1 -0
  5. package/dist/chunk-4R67INKT.js +1002 -0
  6. package/dist/chunk-4R67INKT.js.map +1 -0
  7. package/dist/{chunk-ILU76UF3.js → chunk-4RJ5D7MT.js} +2 -2
  8. package/dist/chunk-4RJ5D7MT.js.map +1 -0
  9. package/dist/{chunk-EQZI77PP.cjs → chunk-5VVS2QIJ.cjs} +3 -3
  10. package/dist/{chunk-EQZI77PP.cjs.map → chunk-5VVS2QIJ.cjs.map} +1 -1
  11. package/dist/{chunk-3DUADAY2.js → chunk-A275ZQLX.js} +209 -87
  12. package/dist/chunk-A275ZQLX.js.map +1 -0
  13. package/dist/{chunk-EISO3POT.js → chunk-CVEERPMP.js} +788 -194
  14. package/dist/chunk-CVEERPMP.js.map +1 -0
  15. package/dist/{chunk-ZWD734EY.js → chunk-D6KQKMU3.js} +2 -2
  16. package/dist/{chunk-QOMNEWCQ.cjs → chunk-EBZUZC6F.cjs} +305 -31
  17. package/dist/chunk-EBZUZC6F.cjs.map +1 -0
  18. package/dist/{chunk-HR2XNERB.cjs → chunk-EMGF6EEV.cjs} +2 -2
  19. package/dist/{chunk-HR2XNERB.cjs.map → chunk-EMGF6EEV.cjs.map} +1 -1
  20. package/dist/{chunk-GDW4YP7K.js → chunk-FAZR34CA.js} +2 -2
  21. package/dist/{chunk-TNCKOL2O.js → chunk-GTINVM5K.js} +2 -2
  22. package/dist/{chunk-L7DMSMAG.cjs → chunk-HX4UUF7Y.cjs} +1 -1
  23. package/dist/chunk-HX4UUF7Y.cjs.map +1 -0
  24. package/dist/{chunk-UET462HB.js → chunk-I4NNYWIR.js} +2 -2
  25. package/dist/chunk-IH56ZISC.js +620 -0
  26. package/dist/chunk-IH56ZISC.js.map +1 -0
  27. package/dist/{chunk-EDNJLUSE.js → chunk-IHZEFW4L.js} +2 -2
  28. package/dist/chunk-IW2CB755.cjs +1002 -0
  29. package/dist/chunk-IW2CB755.cjs.map +1 -0
  30. package/dist/{chunk-ZWOFQRIB.js → chunk-JCVIOLBF.js} +3 -3
  31. package/dist/{chunk-JQEFP45L.js → chunk-L5EH2NVO.js} +2 -2
  32. package/dist/{chunk-247ANM72.cjs → chunk-NOAQRCV6.cjs} +219 -97
  33. package/dist/chunk-NOAQRCV6.cjs.map +1 -0
  34. package/dist/{chunk-7IAQ4HOK.js → chunk-NQCUAGLR.js} +1 -1
  35. package/dist/chunk-NQCUAGLR.js.map +1 -0
  36. package/dist/{chunk-NS55SM7A.js → chunk-O2PGQNYE.js} +2 -2
  37. package/dist/{chunk-QC5T6D4J.js → chunk-QGW7NMTX.js} +299 -25
  38. package/dist/chunk-QGW7NMTX.js.map +1 -0
  39. package/dist/{chunk-A7QC2ZVP.js → chunk-UVVEBI22.js} +209 -87
  40. package/dist/chunk-UVVEBI22.js.map +1 -0
  41. package/dist/chunk-VJHAUMCF.js +949 -0
  42. package/dist/chunk-VJHAUMCF.js.map +1 -0
  43. package/dist/{chunk-C7FQBSON.js → chunk-VNFJPTU4.js} +2 -2
  44. package/dist/{chunk-VQ5W4MYO.js → chunk-VYOGXNQ3.js} +298 -24
  45. package/dist/chunk-VYOGXNQ3.js.map +1 -0
  46. package/dist/{chunk-DOGPOY4B.js → chunk-WB5LW52Z.js} +2 -2
  47. package/dist/{chunk-TSFS5VKD.js → chunk-YGBZABI4.js} +332 -141
  48. package/dist/chunk-YGBZABI4.js.map +1 -0
  49. package/dist/chunks-I7TEMJXH.js +10 -0
  50. package/dist/cli.js +228 -97
  51. package/dist/cli.js.map +1 -1
  52. package/dist/{formula-PJJ6S3Z2.cjs → formula-CPO2TZGB.cjs} +9 -9
  53. package/dist/{formula-PJJ6S3Z2.cjs.map → formula-CPO2TZGB.cjs.map} +1 -1
  54. package/dist/{formula-5S7WWXGH.js → formula-NOIDZEBA.js} +4 -4
  55. package/dist/{formula-JGFTXWYT.js → formula-X44GV7TN.js} +3 -3
  56. package/dist/{image-ocr-WCAYXLOA.js → image-ocr-72RAR4WR.js} +34 -16
  57. package/dist/image-ocr-72RAR4WR.js.map +1 -0
  58. package/dist/{image-ocr-FLIL7MGN.js → image-ocr-R6W6A75P.js} +32 -14
  59. package/dist/image-ocr-R6W6A75P.js.map +1 -0
  60. package/dist/{image-ocr-ZTYAGV3W.cjs → image-ocr-SUGT64ZM.cjs} +36 -18
  61. package/dist/image-ocr-SUGT64ZM.cjs.map +1 -0
  62. package/dist/index.cjs +2416 -1184
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +62 -25
  65. package/dist/index.d.ts +62 -25
  66. package/dist/index.js +1853 -621
  67. package/dist/index.js.map +1 -1
  68. package/dist/mcp.js +74 -53
  69. package/dist/mcp.js.map +1 -1
  70. package/dist/{model-bundle-FSPZZ2EM.js → model-bundle-57CLEBCO.js} +5 -5
  71. package/dist/{models-CJPAQK5R.js → models-7ELIFTD7.js} +5 -5
  72. package/dist/{parser-SCUTZAIJ.js → parser-4NKAYWAX.js} +261 -241
  73. package/dist/parser-4NKAYWAX.js.map +1 -0
  74. package/dist/{parser-A4TMPJAM.js → parser-HWFQRSUQ.js} +261 -241
  75. package/dist/parser-HWFQRSUQ.js.map +1 -0
  76. package/dist/{parser-VCXE4I5A.cjs → parser-RANGXGBD.cjs} +289 -269
  77. package/dist/parser-RANGXGBD.cjs.map +1 -0
  78. package/dist/pdf-ocr-BNC34MNU.js +13 -0
  79. package/dist/pdf-ocr-QX2GB33Q.js +16 -0
  80. package/dist/pdf-ocr-VHSFACH7.cjs +14 -0
  81. package/dist/{pdf-ocr-MBZZ57LD.cjs.map → pdf-ocr-VHSFACH7.cjs.map} +1 -1
  82. package/dist/{profile-io-LBM263K7.js → profile-io-V52PL5ZO.js} +3 -3
  83. package/dist/{rasterize-46RG4ZF6.js → rasterize-ZIGLJDOG.js} +3 -3
  84. package/dist/redact-TIQEQI3M.js +18 -0
  85. package/dist/redact-doc-KXZ3TZAG.js +1216 -0
  86. package/dist/redact-doc-KXZ3TZAG.js.map +1 -0
  87. package/dist/{render-NGP5T24S.js → render-U5PE6V2E.js} +7 -7
  88. package/dist/seal-DNXJBILR.js +10 -0
  89. package/dist/{setup-AU4OCHNH.js → setup-YRDM4ODP.js} +3 -3
  90. package/dist/visual-5XWMA4EV.js +19 -0
  91. package/dist/{watch-MKPUCPXM.js → watch-5IOLBATT.js} +12 -12
  92. package/package.json +2 -2
  93. package/dist/chunk-247ANM72.cjs.map +0 -1
  94. package/dist/chunk-3DUADAY2.js.map +0 -1
  95. package/dist/chunk-422427XJ.cjs +0 -571
  96. package/dist/chunk-422427XJ.cjs.map +0 -1
  97. package/dist/chunk-7IAQ4HOK.js.map +0 -1
  98. package/dist/chunk-A7QC2ZVP.js.map +0 -1
  99. package/dist/chunk-DZIXKL7E.js +0 -145
  100. package/dist/chunk-DZIXKL7E.js.map +0 -1
  101. package/dist/chunk-EISO3POT.js.map +0 -1
  102. package/dist/chunk-EP7KOI2V.js +0 -571
  103. package/dist/chunk-EP7KOI2V.js.map +0 -1
  104. package/dist/chunk-FOJZNMMK.js.map +0 -1
  105. package/dist/chunk-ILU76UF3.js.map +0 -1
  106. package/dist/chunk-KVWV4ULP.js +0 -516
  107. package/dist/chunk-KVWV4ULP.js.map +0 -1
  108. package/dist/chunk-L7DMSMAG.cjs.map +0 -1
  109. package/dist/chunk-QC5T6D4J.js.map +0 -1
  110. package/dist/chunk-QOMNEWCQ.cjs.map +0 -1
  111. package/dist/chunk-TSFS5VKD.js.map +0 -1
  112. package/dist/chunk-VQ5W4MYO.js.map +0 -1
  113. package/dist/chunks-SX77P5CV.js +0 -10
  114. package/dist/image-ocr-FLIL7MGN.js.map +0 -1
  115. package/dist/image-ocr-WCAYXLOA.js.map +0 -1
  116. package/dist/image-ocr-ZTYAGV3W.cjs.map +0 -1
  117. package/dist/parser-A4TMPJAM.js.map +0 -1
  118. package/dist/parser-SCUTZAIJ.js.map +0 -1
  119. package/dist/parser-VCXE4I5A.cjs.map +0 -1
  120. package/dist/pdf-ocr-76SAUXMZ.js +0 -13
  121. package/dist/pdf-ocr-CPU55CVF.js +0 -16
  122. package/dist/pdf-ocr-MBZZ57LD.cjs +0 -14
  123. package/dist/redact-7ILEAUTS.js +0 -12
  124. package/dist/seal-RGUPOLXD.js +0 -10
  125. package/dist/visual-NZLYIYEM.js +0 -19
  126. /package/dist/{-AQKXLFIM.js.map → -7XBPXCXN.js.map} +0 -0
  127. /package/dist/{chunk-ZWD734EY.js.map → chunk-D6KQKMU3.js.map} +0 -0
  128. /package/dist/{chunk-GDW4YP7K.js.map → chunk-FAZR34CA.js.map} +0 -0
  129. /package/dist/{chunk-TNCKOL2O.js.map → chunk-GTINVM5K.js.map} +0 -0
  130. /package/dist/{chunk-UET462HB.js.map → chunk-I4NNYWIR.js.map} +0 -0
  131. /package/dist/{chunk-EDNJLUSE.js.map → chunk-IHZEFW4L.js.map} +0 -0
  132. /package/dist/{chunk-ZWOFQRIB.js.map → chunk-JCVIOLBF.js.map} +0 -0
  133. /package/dist/{chunk-JQEFP45L.js.map → chunk-L5EH2NVO.js.map} +0 -0
  134. /package/dist/{chunk-NS55SM7A.js.map → chunk-O2PGQNYE.js.map} +0 -0
  135. /package/dist/{chunk-C7FQBSON.js.map → chunk-VNFJPTU4.js.map} +0 -0
  136. /package/dist/{chunk-DOGPOY4B.js.map → chunk-WB5LW52Z.js.map} +0 -0
  137. /package/dist/{chunks-SX77P5CV.js.map → chunks-I7TEMJXH.js.map} +0 -0
  138. /package/dist/{formula-5S7WWXGH.js.map → formula-NOIDZEBA.js.map} +0 -0
  139. /package/dist/{formula-JGFTXWYT.js.map → formula-X44GV7TN.js.map} +0 -0
  140. /package/dist/{model-bundle-FSPZZ2EM.js.map → model-bundle-57CLEBCO.js.map} +0 -0
  141. /package/dist/{models-CJPAQK5R.js.map → models-7ELIFTD7.js.map} +0 -0
  142. /package/dist/{pdf-ocr-76SAUXMZ.js.map → pdf-ocr-BNC34MNU.js.map} +0 -0
  143. /package/dist/{pdf-ocr-CPU55CVF.js.map → pdf-ocr-QX2GB33Q.js.map} +0 -0
  144. /package/dist/{profile-io-LBM263K7.js.map → profile-io-V52PL5ZO.js.map} +0 -0
  145. /package/dist/{rasterize-46RG4ZF6.js.map → rasterize-ZIGLJDOG.js.map} +0 -0
  146. /package/dist/{redact-7ILEAUTS.js.map → redact-TIQEQI3M.js.map} +0 -0
  147. /package/dist/{render-NGP5T24S.js.map → render-U5PE6V2E.js.map} +0 -0
  148. /package/dist/{seal-RGUPOLXD.js.map → seal-DNXJBILR.js.map} +0 -0
  149. /package/dist/{setup-AU4OCHNH.js.map → setup-YRDM4ODP.js.map} +0 -0
  150. /package/dist/{visual-NZLYIYEM.js.map → visual-5XWMA4EV.js.map} +0 -0
  151. /package/dist/{watch-MKPUCPXM.js.map → watch-5IOLBATT.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/redact-rules.ts","../src/redact.ts"],"sourcesContent":["/**\n * redact 룰 정의 — 룰별 정규식 변형·검증기(생년월일 세기·Luhn·사업자/법인 체크섬·전화 국번)·라벨 사전.\n * 엔진(정규화·겹침 처리·마스킹·마크다운 표 문맥)은 redact.ts. 룰별 근거·오탐 실측은 bench/redact-bench.mjs.\n */\n\nimport type { RedactRule } from \"./redact.js\"\n\n/**\n * 룰 우선순위 (앞이 높음). crn 을 rrn 앞에 — 법인 라벨이 붙은 6-7 번호는 법인번호로.\n * email 을 번호 룰들 앞에(로컬파트 숫자 오탐 방지), card·brn 을 phone·account 앞에,\n * driver 를 account 앞에(면허번호 12자리 4그룹이 계좌 패턴에 포섭됨).\n */\nexport const RULE_PRIORITY: readonly RedactRule[] = [\n \"crn\", \"rrn\", \"email\", \"card\", \"brn\", \"phone\", \"driver\", \"passport\", \"account\", \"ip\",\n]\n\n// ─── 검증기 ───────────────────────────────────────────\n\n/** Luhn 체크섬 (카드번호) */\nfunction luhnValid(digits: string): boolean {\n let sum = 0\n for (let i = 0; i < digits.length; i++) {\n let d = digits.charCodeAt(digits.length - 1 - i) - 48\n if (i % 2 === 1) {\n d *= 2\n if (d > 9) d -= 9\n }\n sum += d\n }\n return sum % 10 === 0\n}\n\nconst DAYS_IN_MONTH = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]\n\n/**\n * 주민·외국인등록번호 앞 6자리 생년월일 검증 — 성별 자리로 세기를 정한다\n * (1·2·5·6 → 1900년대, 3·4·7·8 → 2000년대). 2000년대생은 올해를 넘지 못한다.\n */\nfunction birthdateValid(front6: string, genderDigit: string): boolean {\n const yy = Number(front6.slice(0, 2))\n const mm = Number(front6.slice(2, 4))\n const dd = Number(front6.slice(4, 6))\n if (mm < 1 || mm > 12 || dd < 1) return false\n const g = Number(genderDigit)\n const year = (g === 3 || g === 4 || g === 7 || g === 8 ? 2000 : 1900) + yy\n if (year > new Date().getFullYear()) return false\n const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)\n const max = mm === 2 ? (leap ? 29 : 28) : DAYS_IN_MONTH[mm - 1]\n return dd <= max\n}\n\n/** 사업자등록번호 체크섬 (국세청 공개 가중치 1,3,7,1,3,7,1,3,5) */\nfunction brnChecksumValid(d: string): boolean {\n const w = [1, 3, 7, 1, 3, 7, 1, 3, 5]\n let sum = 0\n for (let i = 0; i < 9; i++) sum += w[i] * (d.charCodeAt(i) - 48)\n sum += Math.floor(((d.charCodeAt(8) - 48) * 5) / 10)\n return (10 - (sum % 10)) % 10 === d.charCodeAt(9) - 48\n}\n\n/** 법인등록번호 체크섬 (가중치 1,2 반복, 모듈러 10) */\nfunction crnChecksumValid(d: string): boolean {\n let sum = 0\n for (let i = 0; i < 12; i++) sum += (i % 2 === 0 ? 1 : 2) * (d.charCodeAt(i) - 48)\n return (10 - (sum % 10)) % 10 === d.charCodeAt(12) - 48\n}\n\nconst digitsOf = (s: string): string => s.replace(/\\D/g, \"\")\n/** 같은 숫자 반복(0000…, 1111…) — 서식 예시·자리표시자 */\nconst allSameDigit = (d: string): boolean => d.length > 0 && /^(\\d)\\1*$/.test(d)\n\n// ─── 라벨 문맥 ────────────────────────────────────────\n\nexport type Ctx = \"rrn\" | \"crn\" | \"brn\" | \"account\" | \"card\" | \"phone\" | \"passport\" | \"driver\" | \"ip\" | \"other\"\n\n/**\n * 라벨 사전. 값 앞 창(현재 셀 + 왼쪽 셀, 태그 제거 후 40자, 앞선 값 뒤부터)에 있는 라벨 유형 **집합**이\n * 문맥이다. \"주민등록번호(법인등록번호)\"처럼 한 칸에 두 라벨이 붙은 서식은 둘 다 문맥이 된다 — 모호하면\n * 가리는 쪽(주민번호)으로 판정한다. \"other\" 는 PII 가 아닌 식별번호 라벨(출원번호·과제번호·ISBN·예산과목…).\n */\nconst LABELS: ReadonlyArray<readonly [Ctx, RegExp]> = [\n [\"crn\", /법\\s*인\\s*(?:등\\s*록\\s*)?번\\s*호/g],\n [\"rrn\", /주\\s*민\\s*(?:등\\s*록\\s*)?번\\s*호|주\\s*민\\s*등\\s*록|외\\s*국\\s*인\\s*(?:등\\s*록\\s*)?번\\s*호|거\\s*소\\s*신\\s*고\\s*번\\s*호|생\\s*년\\s*월\\s*일|resident\\s*registration|\\bRRN\\b/gi],\n [\"brn\", /사\\s*업\\s*자\\s*(?:등\\s*록\\s*)?번\\s*호|고\\s*유\\s*번\\s*호|business\\s*registration/gi],\n [\"account\", /계\\s*좌|예\\s*금\\s*주|입\\s*금|은\\s*행|뱅\\s*크|농\\s*협|신\\s*협|수\\s*협|우\\s*체\\s*국|금\\s*고|bank\\s*account|account\\s*(?:no|number)/gi],\n [\"card\", /카\\s*드|credit\\s*card|card\\s*(?:no|number)/gi],\n [\"phone\", /전\\s*화|연\\s*락\\s*처|휴\\s*대|핸\\s*드\\s*폰|팩\\s*스|☎|☏|콜\\s*센\\s*터|고\\s*객\\s*센\\s*터|대\\s*표\\s*번\\s*호|상\\s*담|문\\s*의|\\b(?:tel|fax|mobile|phone|h\\.?p|ARS)\\b/gi],\n [\"passport\", /여\\s*권|passport/gi],\n [\"driver\", /운\\s*전\\s*면\\s*허|면\\s*허\\s*번\\s*호|driver'?s?\\s*licen[cs]e/gi],\n [\"ip\", /\\bIP\\b|아\\s*이\\s*피/g],\n [\"other\", /출\\s*원\\s*번\\s*호|과\\s*제\\s*번\\s*호|사\\s*업\\s*번\\s*호|접\\s*수\\s*번\\s*호|민\\s*원\\s*번\\s*호|신\\s*청\\s*번\\s*호|문\\s*서\\s*번\\s*호|관\\s*리\\s*번\\s*호|주\\s*문\\s*번\\s*호|일\\s*련\\s*번\\s*호|발\\s*간\\s*등\\s*록|등\\s*록\\s*번\\s*호\\s*\\(\\s*기\\s*록|ISBN|ISSN|예\\s*산|과\\s*목|코\\s*드|모\\s*델|제\\s*품|부\\s*품|시\\s*리\\s*얼|serial|model|\\bS\\/N\\b|바\\s*코\\s*드|기\\s*록\\s*물|사\\s*건\\s*번\\s*호/gi],\n]\n\n/** 셀 경계 — GFM 파이프(이스케이프 제외)와 HTML 셀 닫는 태그 */\nconst CELL_SEP_RE = /(?<!\\\\)\\||<\\/t[dh]>/gi\n/** 앞선 값 — 창 안에서 이 뒤부터만 본다 (앞 값의 라벨이 뒤 값으로 번지지 않게) */\nconst PREV_VALUE_RE = /\\d[\\d\\-.\\s]{4,}\\d/g\n\n/** 텍스트 안 라벨 유형 집합 */\nexport function labelsIn(s: string): Set<Ctx> {\n const out = new Set<Ctx>()\n for (const [ctx, re] of LABELS) {\n re.lastIndex = 0\n if (re.test(s)) out.add(ctx)\n }\n return out\n}\n\n/** 매치 앞 문맥 라벨 집합 — 현재 셀 + 바로 왼쪽 셀, 태그 제거 후 40자, 앞선 값 뒤부터 */\nexport function labelsBefore(norm: string, start: number): Set<Ctx> {\n let win = norm.slice(Math.max(0, start - 120), start)\n const seps = [...win.matchAll(CELL_SEP_RE)]\n if (seps.length >= 2) {\n const s = seps[seps.length - 2]\n win = win.slice((s.index as number) + s[0].length)\n }\n win = win.replace(/<[^>]*>/g, \" \").slice(-40)\n const prev = [...win.matchAll(PREV_VALUE_RE)].pop()\n if (prev) win = win.slice((prev.index as number) + prev[0].length)\n return labelsIn(win)\n}\n\n/** 값 바로 뒤 비개인 식별번호 명사 — \"11-24-123456-01 (접수)\", \"S123A4567 부품\" */\nconst OTHER_AFTER_RE = /^\\s{0,2}[(\\[]?\\s{0,2}(?:접\\s*수|신\\s*청|주\\s*문|사\\s*건|과\\s*제|문\\s*서|관\\s*리|부\\s*품|제\\s*품|모\\s*델|코\\s*드|시\\s*리\\s*얼|serial|model)/i\n\n// ─── 룰 정의 ──────────────────────────────────────────\n\nexport interface Match {\n /** 정규화 텍스트 매치 (d 플래그 — 그룹 인덱스 포함) */\n m: RegExpMatchArray\n /** 라벨 문맥 집합 (인라인, 비었으면 표 머리글 — 지연 계산) */\n ctx: () => ReadonlySet<Ctx>\n}\n\nexport interface Variant {\n re: RegExp\n /** 이 유형 라벨이 문맥에 있을 때만 인정 (모호한 모양) */\n needs?: Ctx\n /** 추가 검증 — false면 스킵 */\n ok?: (x: Match) => boolean\n /** 마스킹할 이름 그룹 — 그 범위의 영숫자를 maskChar 로 (구분자·공백·기존 가림표는 유지) */\n mask: readonly string[]\n /** 그룹 안 글자를 구분자까지 전부 가린다 (이메일 로컬파트 — 이름 마디 길이도 숨김) */\n maskAll?: boolean\n}\n\nconst g = (x: Match, name: string): string => x.m.groups?.[name] ?? \"\"\nconst has = (x: Match, c: Ctx): boolean => x.ctx().has(c)\n/** 법인 라벨만 있고 주민 라벨은 없다 — 이때만 6-7 번호를 주민번호에서 뺀다 (모호하면 가린다) */\nconst crnOnly = (x: Match): boolean => has(x, \"crn\") && !has(x, \"rrn\")\n/** 매치 뒤 원문 (정규화 텍스트) */\nconst after = (x: Match, n = 16): string => {\n const end = (x.m.index as number) + x.m[0].length\n return x.m.input!.slice(end, end + n)\n}\nconst before = (x: Match, n = 2): string => {\n const start = x.m.index as number\n return x.m.input!.slice(Math.max(0, start - n), start)\n}\n/** 비개인 식별번호 라벨이 앞에 있거나(해당 PII 라벨 없이) 뒤에 \"(접수)\"류 명사가 붙은 값 */\nconst otherCtx = (x: Match, own: Ctx): boolean => (has(x, \"other\") && !has(x, own)) || OTHER_AFTER_RE.test(after(x))\n/**\n * 값 바로 앞이 일련번호 라벨(\"S/N 0507-9978-0522\", \"바코드: 1588-…\") — 전화 모양이어도 기기·상품 번호.\n * 전화는 \"예산\"·\"과목\" 같은 넓은 other 라벨로는 막지 않는다(예산담당관 044-… 실번호) — 바로 앞 좁은 라벨만\n */\nconst SERIAL_BEFORE_RE = /(?:\\bS\\/N|\\bserial(?:\\s*(?:no|number))?|시\\s*리\\s*얼(?:\\s*번\\s*호)?|일\\s*련\\s*번\\s*호|바\\s*코\\s*드)\\s*[:.]?\\s*$/i\nconst serialBefore = (x: Match): boolean => SERIAL_BEFORE_RE.test(before(x, 20)) && !has(x, \"phone\")\n\n// 공통 경계 — 숫자·영문·@·하이픈에 붙은 번호(더 긴 코드의 일부)는 제외\nconst PHONE_PREFIX = \"0(?:1[016789]|2|3[1-3]|4[1-4]|5[1-5]|6[1-4]|70|80|60|50[2-8])\"\nconst PHONE_AREA = \"0(?:1[016789]|2|3[1-3]|4[1-4]|5[1-5]|6[1-4]|70|80|50[2-8])\"\n/**\n * 전화번호 구분자 한 자리 — 하이픈(앞뒤 공백 0~2칸: \"02- 3668-1330\", \"031- 405 -8255\"), 공백 1~2칸, 온점.\n * 두 자리는 따로 고르되 온점은 온점끼리만(phoneSepsOk) — \"010.1234-5678\" 혼합은 미탐 계약 유지\n */\nconst PHONE_SEP = \"(?: {0,2}- {0,2}| {1,2}|\\\\.)\"\n/** 전화번호 끝 경계 — 영숫자·@·하이픈이 붙으면 더 긴 코드(예산과목 064-1100-1131-301-210) */\nconst PHONE_END = \"(?![\\\\w@-])\"\n\n/** 두 구분자 조합 — 둘 다 없거나 둘 다 있고, 온점은 온점끼리. 온점 구분이면 뒤 \".숫자\"(더 긴 점 코드) 거부 */\nfunction phoneSepsOk(x: Match, s1: string, s2: string): boolean {\n if ((s1 === \"\") !== (s2 === \"\")) return false\n if ((s1 === \".\") !== (s2 === \".\")) return false\n return s1 !== \".\" || !/^\\.\\d/.test(after(x, 2))\n}\n\n/**\n * 전화번호 숫자 검증 — 가입자 번호 전부 0(010-0000-0000 자리표시자) 제외. 구분자 없는 지역번호는\n * 국번이 0으로 시작하면 코드로 본다(0202030201 NCS 능력단위 류) — 구분자가 있으면 형식을 믿는다\n * (실결재 결문 팩스 02-02xx-xxxx 실측). 02-123-4567·010-1234-5678 같은 관용 예시는 실번호처럼 가린다.\n */\nfunction phoneDigitsOk(pre: string, mid: string, last: string, bare: boolean): boolean {\n if (/^0+$/.test(mid + last)) return false\n return !bare || !/^0(?:2|3[1-3]|4[1-4]|5[1-5]|6[1-4])$/.test(pre) || mid[0] !== \"0\"\n}\n\n/** 이메일 시작 경계 — 로컬파트 한가운데(gd.hong 의 hong)·이미 가린 로컬파트 꼬리(j*********5@…)에서는 시작하지\n * 않는다. 글머리·강조 가림표 뒤(*hong@…, ●hong@…)와 문장 부호 뒤(.hong@…)는 허용 */\nconst EMAIL_START = \"(?<![\\\\w%+])(?<!\\\\w[.\\\\-])(?<![A-Za-z0-9][*●○◯×#]+)\"\nconst EMAIL_DOMAIN = \"(?<domain>[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\\\\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)*\\\\.(?<tld>[A-Za-z]{2,24}))(?![\\\\w-])\"\nconst FILE_EXT_TLD = /^(?:png|jpe?g|gif|bmp|svg|webp|tiff?|hwpx?|hwp|pdf|docx?|xlsx?|pptx?|txt|zip|csv|json|xml|html?|js|css|mp[34]|wav|avi|exe)$/i\n\nexport const RULES: Record<RedactRule, readonly Variant[]> = {\n // 주민·외국인등록번호 — 앞 6자리(생년월일) 유지, 뒤 7자리 마스크\n rrn: [\n { // 하이픈형 (앞뒤 공백 0~2칸, 유니코드 대시는 정규화로 흡수). 뒤에 \"-글자\"는 허용, \"-숫자\"는 더 긴 코드.\n // 법인 라벨만 있으면 제외\n re: /(?<![\\w-])(?<front>\\d{6})(?<sep> {0,2}- {0,2})(?<back>[1-8]\\d{6})(?![\\w])(?!-\\d)/dg,\n ok: (x) => birthdateValid(g(x, \"front\"), g(x, \"back\")[0]) && !crnOnly(x),\n mask: [\"back\"],\n },\n { // 뒷자리 일부만 가린 형태 (900101-1******, 900101-1694***) — 남은 숫자를 가린다\n re: /(?<![\\w-])(?<front>\\d{6})(?<sep> {0,2}- {0,2})(?<back>[1-8][0-9*●○◯×xX#]{6})(?![\\w*●○◯×#])(?!-\\d)/dg,\n ok: (x) => /[*●○◯×xX#]/.test(g(x, \"back\")) && birthdateValid(g(x, \"front\"), g(x, \"back\")[0]) && !crnOnly(x),\n mask: [\"back\"],\n },\n { // 구분자 없음·공백/온점/가운뎃점/빗금/물결 구분 — 주민/외국인등록번호(생년월일) 라벨이 있을 때만\n re: /(?<![\\w@.-])(?<front>\\d{6})(?<sep>[ .·/~]?)(?<back>[1-8]\\d{6})(?![\\w@-])/dg,\n needs: \"rrn\",\n ok: (x) => birthdateValid(g(x, \"front\"), g(x, \"back\")[0]),\n mask: [\"back\"],\n },\n ],\n // 법인등록번호 (opt-in) — 법인 라벨이 있거나, 뒷자리가 0으로 시작(주민번호 불가)하고 체크섬 유효\n crn: [\n {\n re: /(?<![\\w-])(?<front>\\d{6})(?<sep> {0,2}- {0,2})(?<back>\\d{7})(?![\\w-])/dg,\n ok: (x) => !allSameDigit(g(x, \"front\") + g(x, \"back\")) && (crnOnly(x)\n || (g(x, \"back\")[0] === \"0\" && crnChecksumValid(g(x, \"front\") + g(x, \"back\")))),\n mask: [\"back\"],\n },\n {\n re: /(?<![\\w@.-])(?<front>\\d{6})(?<back>\\d{7})(?![\\w@-])/dg,\n needs: \"crn\",\n ok: (x) => !allSameDigit(g(x, \"front\") + g(x, \"back\")) && !has(x, \"rrn\"),\n mask: [\"back\"],\n },\n ],\n // 이메일 — 로컬파트 첫 글자만 남기고 마스크, 도메인 유지. 파일명(image@2x.png)은 제외\n // 이미 가린 로컬파트(j*********5@…) 뒤꼬리에서 시작하지 않는다 — 가림표 바로 뒤 매치 금지\n email: [\n {\n re: new RegExp(`${EMAIL_START}(?<l1>[A-Za-z0-9])(?<lrest>[A-Za-z0-9._%+-]+)@${EMAIL_DOMAIN}`, \"dg\"),\n ok: (x) => !FILE_EXT_TLD.test(g(x, \"tld\")),\n mask: [\"lrest\"],\n maskAll: true,\n },\n { // 한 글자 로컬파트 — 첫 글자를 남기면 가린 게 없으므로 전부 가린다\n re: new RegExp(`${EMAIL_START}(?<l1>[A-Za-z0-9])@${EMAIL_DOMAIN}`, \"dg\"),\n ok: (x) => !FILE_EXT_TLD.test(g(x, \"tld\")),\n mask: [\"l1\"],\n },\n ],\n // 카드번호 — 가운데 마스크 + Luhn. 구분자 없는 15~16자리는 카드 라벨이 있을 때만\n card: [\n { // 4-4-4-4 (같은 구분자 강제)\n re: /(?<![\\w-])(?<g1>\\d{4})(?<sep>[- ])(?<g2>\\d{4})\\k<sep>(?<g3>\\d{4})\\k<sep>(?<g4>\\d{4})(?![\\w-])(?![- ]\\d)/dg,\n ok: (x) => { const d = digitsOf(x.m[0]); return luhnValid(d) && !allSameDigit(d) },\n mask: [\"g2\", \"g3\"],\n },\n { // AMEX 4-6-5\n re: /(?<![\\w-])(?<g1>3[47]\\d{2})(?<sep>[- ])(?<g2>\\d{6})\\k<sep>(?<g3a>\\d)(?<g3b>\\d{4})(?![\\w-])(?![- ]\\d)/dg,\n ok: (x) => luhnValid(digitsOf(x.m[0])),\n mask: [\"g2\", \"g3a\"],\n },\n { // 온점 구분 4.4.4.4 — 카드 라벨 필요 (버전·좌표 모양과 겹침)\n re: /(?<![\\w.-])(?<g1>\\d{4})\\.(?<g2>\\d{4})\\.(?<g3>\\d{4})\\.(?<g4>\\d{4})(?![\\w-])(?!\\.\\d)/dg,\n needs: \"card\",\n ok: (x) => { const d = digitsOf(x.m[0]); return luhnValid(d) && !allSameDigit(d) },\n mask: [\"g2\", \"g3\"],\n },\n { // 무구분 15~16자리 — 카드 라벨 필요\n re: /(?<![\\w@.-])(?<g1>\\d{4})(?<mid>\\d{7,8})(?<g4>\\d{4})(?![\\w@-])/dg,\n needs: \"card\",\n ok: (x) => { const d = digitsOf(x.m[0]); return luhnValid(d) && !allSameDigit(d) },\n mask: [\"mid\"],\n },\n ],\n // 사업자등록번호 3-2-5 — 체크섬 유효 또는 사업자 라벨. 뒤 5자리(일련·검증번호) 마스크\n brn: [\n {\n re: /(?<![\\w-])(?<a>\\d{3})-(?<b>\\d{2})-(?<c>\\d{5})(?![\\w-])/dg,\n ok: (x) => {\n const d = digitsOf(x.m[0])\n if (allSameDigit(d)) return false\n return has(x, \"brn\") || (brnChecksumValid(d) && !has(x, \"account\") && !otherCtx(x, \"brn\"))\n },\n mask: [\"c\"],\n },\n {\n re: /(?<![\\w@.-])(?<a>\\d{3})(?<b>\\d{2})(?<c>\\d{5})(?![\\w@-])/dg,\n needs: \"brn\",\n ok: (x) => !allSameDigit(digitsOf(x.m[0])),\n mask: [\"c\"],\n },\n ],\n // 전화번호 — 가운데 자리만 마스크 (대표번호는 뒤 4자리).\n // 뒤에 \"-\"가 붙으면 더 긴 코드(예산과목 064-1100-1131-301-210)의 일부라 제외.\n phone: [\n { // 국내 일반: 휴대폰·지역·070/080/060·050x 안심번호. 구분자 -·공백·온점(PHONE_SEP) 또는 무구분\n re: new RegExp(`(?<![\\\\w@+-])(?<pre>${PHONE_PREFIX})(?<s1>${PHONE_SEP}?)(?<mid>\\\\d{3,4})(?<s2>${PHONE_SEP}?)(?<last>\\\\d{4})${PHONE_END}`, \"dg\"),\n ok: (x) => {\n const pre = g(x, \"pre\")\n const bare = g(x, \"s1\") === \"\"\n if (!phoneSepsOk(x, g(x, \"s1\"), g(x, \"s2\"))) return false\n if (!phoneDigitsOk(pre, g(x, \"mid\"), g(x, \"last\"), bare)) return false\n if (serialBefore(x)) return false\n if (!bare) return true // 구분자 있는 번호는 쉼표·온점 목록 안에서도 번호 (\"02-…,010-…\")\n // 무구분 — 소수점 뒤(3.0212345678)는 아님, 휴대폰이 아닌 번호는 쉼표 숫자 목록 안의 코드(…,0507502002,…) 제외\n if (/\\d\\.$/.test(before(x))) return false\n if (pre.startsWith(\"01\")) return true // 휴대폰 무구분 목록 \"01023456789,01098765432\"\n return !/\\d,$/.test(before(x)) && !/^,\\d/.test(after(x, 2))\n },\n mask: [\"mid\"],\n },\n { // 괄호 지역번호: (02) 123-4567, (042)481-6300\n re: new RegExp(`(?<![\\\\w@])\\\\((?<pre>${PHONE_AREA})\\\\) ?(?<mid>\\\\d{3,4})(?<s2>${PHONE_SEP}?)(?<last>\\\\d{4})${PHONE_END}`, \"dg\"),\n ok: (x) => phoneDigitsOk(g(x, \"pre\"), g(x, \"mid\"), g(x, \"last\"), false) && (g(x, \"s2\") !== \".\" || !/^\\.\\d/.test(after(x, 2))),\n mask: [\"mid\"],\n },\n { // 지역번호 뒤 닫는 괄호: 02)450-1234\n re: new RegExp(`(?<![\\\\w@(+-])(?<!\\\\d\\\\.)(?<pre>${PHONE_AREA})\\\\) ?(?<mid>\\\\d{3,4})(?<s2>${PHONE_SEP})(?<last>\\\\d{4})${PHONE_END}`, \"dg\"),\n ok: (x) => phoneDigitsOk(g(x, \"pre\"), g(x, \"mid\"), g(x, \"last\"), false) && (g(x, \"s2\") !== \".\" || !/^\\.\\d/.test(after(x, 2))),\n mask: [\"mid\"],\n },\n { // 국제 표기: +82 10-1234-5678, +82-2-123-4567, +82 (0)10 1234 5678, +821012345678\n re: new RegExp(`(?<![\\\\w@+])(?<cc>\\\\+82)(?<s0>[-. ]?)(?:\\\\(0\\\\) ?)?(?<pre>1[016789]|2|3[1-3]|4[1-4]|5[1-5]|6[1-4]|70|80)(?<s1>${PHONE_SEP}?)(?<mid>\\\\d{3,4})(?<s2>${PHONE_SEP}?)(?<last>\\\\d{4})${PHONE_END}`, \"dg\"),\n ok: (x) => phoneSepsOk(x, g(x, \"s1\"), g(x, \"s2\")) && phoneDigitsOk(\"0\" + g(x, \"pre\"), g(x, \"mid\"), g(x, \"last\"), g(x, \"s1\") === \"\"),\n mask: [\"mid\"],\n },\n { // 대표번호 15xx·16xx·18xx — 구분자 필수. 연도 범위 모양(1592-1598)·온점/공백 구분은 전화 라벨이 있을 때만.\n // 뒤 4자리 0000 은 자리표시자\n re: new RegExp(`(?<![\\\\w@+-])(?<!\\\\d\\\\.)(?<pre>1(?:5[2-9]\\\\d|6\\\\d\\\\d|8\\\\d\\\\d))(?<sep> {0,2}- {0,2}| {1,2}|\\\\.)(?<last>\\\\d{4})${PHONE_END}`, \"dg\"),\n ok: (x) => {\n if (g(x, \"last\") === \"0000\" || serialBefore(x)) return false\n if (g(x, \"sep\") === \".\" && /^\\.\\d/.test(after(x, 2))) return false\n const a = Number(g(x, \"pre\"))\n const b = Number(g(x, \"last\"))\n // 뒤 그룹이 앞 그룹 이상이고 올해 이하면 연도 범위(재위·생몰·전쟁·시대) — 폭과 무관\n const yearRange = b >= a && b <= new Date().getFullYear()\n return (g(x, \"sep\").trim() === \"-\" && !yearRange) || has(x, \"phone\")\n },\n mask: [\"last\"],\n },\n ],\n // 운전면허번호 — 지역코드(11~28)-연도-일련6-검증2 / 구형 지역명 + 2-6-2. 뒤 8자리 마스크\n driver: [\n { // 신형 — 무문맥이지만 비개인 식별번호 문맥(\"(접수)\" 등)이면 아님\n re: /(?<![\\w-])(?<r>1[1-9]|2[0-8])-(?<y>\\d{2})-(?<s>\\d{6})-(?<c>\\d{2})(?![\\w-])/dg,\n ok: (x) => has(x, \"driver\") || !otherCtx(x, \"driver\"),\n mask: [\"s\", \"c\"],\n },\n {\n re: /(?<rn>서울|부산|경기|강원|충북|충남|전북|전남|경북|경남|제주|대구|인천|광주|대전|울산) ?(?<y>\\d{2})-(?<s>\\d{6})-(?<c>\\d{2})(?![\\w-])/dg,\n mask: [\"s\", \"c\"],\n },\n {\n re: /(?<![\\w@.-])(?<r>1[1-9]|2[0-8])(?<y>\\d{2})(?<s>\\d{6})(?<c>\\d{2})(?![\\w@-])/dg,\n needs: \"driver\",\n mask: [\"s\", \"c\"],\n },\n ],\n // 여권번호 — 신형 복수여권(M123A4567)은 무문맥, 그 밖의 종류 글자(S·R·O·D·G)와 구형(M12345678)은 여권 라벨\n // 필요 (\"S123A4567 부품\" 같은 제품 코드). 첫 글자만 남김\n passport: [\n {\n re: /(?<![A-Za-z0-9])(?<l>[MSRODG])(?<num>\\d{3}[A-Z]\\d{4})(?![A-Za-z0-9])/dg,\n ok: (x) => has(x, \"passport\") || (g(x, \"l\") === \"M\" && !otherCtx(x, \"passport\")),\n mask: [\"num\"],\n },\n {\n re: /(?<![A-Za-z0-9])(?<l>[MSRODG])(?<num>\\d{8})(?![A-Za-z0-9])/dg,\n needs: \"passport\",\n mask: [\"num\"],\n },\n { // 소문자로 친 여권번호(m123a4567) — 여권 라벨이 있을 때만\n re: /(?<![A-Za-z0-9])(?<l>[msrodg])(?<num>\\d{3}[a-z]\\d{4}|\\d{8})(?![A-Za-z0-9])/dg,\n needs: \"passport\",\n mask: [\"num\"],\n },\n ],\n // 계좌번호 — 하이픈 3~4그룹, 총 11~16자리 (10자리는 계좌 라벨 필요). 마지막 그룹 빼고 마스크.\n // 무구분 10~14자리는 계좌·은행 라벨이 있을 때만 (뒤 4자리 유지)\n account: [\n {\n // 마지막 그룹 1자리 허용 — 새마을금고 9002-1234-5678-9, 3-2-6-1·3-3-6-1 형식\n re: /(?<![\\w-])(?<!\\d\\.)(?<head>\\d{2,6}(?:-\\d{2,6}){1,2})-(?<last>\\d{1,8})(?![\\w-])(?!\\.\\d)/dg,\n ok: (x) => {\n const s = x.m[0]\n const d = digitsOf(s)\n const acct = has(x, \"account\")\n if (allSameDigit(d) || (!acct && otherCtx(x, \"account\"))) return false\n // 쉼표 숫자 목록 안(1,234-…)은 계좌 라벨이 있을 때만 (\"계좌 110-…,110-…\" 나열)\n if (!acct && (/\\d,$/.test(before(x)) || /^,\\d/.test(after(x, 2)))) return false\n if (/^\\d+(?:-0+)+$/.test(s)) return false // 010-0000-0000·000-00-00000 류 자리표시자\n if (d.length < 10 || d.length > 16 || (d.length === 10 && !acct)) return false\n if (/^\\s*원/.test(after(x, 3))) return false // 금액 표기 뒤 \"원\"\n if (acct) return true\n if (/^(?:10|20|30|40)-(?:19|20)\\d\\d-\\d{6,7}$/.test(s)) return false // 특허·상표·디자인 출원번호\n // 연도로 시작하는 날짜 부호 일련번호(2024-01-15-1234, 2024-0115-123456, 2023-10-123456)\n if (/^(?:19|20)\\d\\d-(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\\d|3[01])?-/.test(s)) return false\n if (s.split(\"-\").filter((p) => /^(?:19|20)\\d\\d$/.test(p)).length >= 2) return false // 연도 나열 2019-2020-2021\n if (/^\\d{3}-\\d{2}-\\d{5}$/.test(s) && brnChecksumValid(d)) return false // 사업자번호 몫\n return true\n },\n mask: [\"head\"],\n },\n {\n re: /(?<![\\w@.-])(?<!\\d,)(?<head>\\d{6,10})(?<last>\\d{4})(?![\\w@-])(?!,\\d)/dg,\n needs: \"account\",\n ok: (x) => !allSameDigit(x.m[0]),\n mask: [\"head\"],\n },\n ],\n // IPv4 (opt-in) — 옥텟 0~255, 첫 옥텟 10 이상(1.2.3.4 절 번호·버전 제외) 또는 IP 라벨. 뒤 두 옥텟 마스크\n ip: [\n {\n re: /(?<![\\w.])(?<a>\\d{1,3})\\.(?<b>\\d{1,3})\\.(?<c>\\d{1,3})\\.(?<d>\\d{1,3})(?![\\w])(?!\\.\\d)/dg,\n ok: (x) => {\n const oct = [g(x, \"a\"), g(x, \"b\"), g(x, \"c\"), g(x, \"d\")]\n if (oct.some((o) => Number(o) > 255 || (o.length > 1 && o[0] === \"0\"))) return false\n return Number(oct[0]) >= 10 || has(x, \"ip\")\n },\n mask: [\"c\", \"d\"],\n },\n ],\n}\n","/**\n * 한국 공문서 PII(개인정보) 탐지·마스킹 순수 로직.\n *\n * 텍스트 in → 마스킹된 텍스트 + 히트 리포트 out. 문서 파일 단위 마스킹(머리말·각주·미리보기·메타데이터\n * 포함)은 redact-doc.ts 가 이 엔진을 불러 쓴다.\n *\n * 원칙:\n * - 서식 보존 마스킹 — 자릿수·구분자를 유지해 마스킹 전후 길이(UTF-16 단위)가 동일\n * - 히트 리포트에 원본 PII를 절대 담지 않는다 (`masked` 필드만 존재)\n * - 탐지는 같은 길이로 정규화한 그림자 문자열에서 한다 — 전각 숫자·영문·@, 유니코드 대시(‐‑–—−-·─·ㅡ),\n * NBSP·전각 공백을 ASCII 로 1:1 치환하므로 오프셋이 그대로이고, 마스킹은 원문 글자에 적용한다\n * - 번호 모양만으로 모호한 형태(구분자 없는 13자리·10~14자리, 여권 구형 등)는 바로 앞 라벨\n * (\"주민등록번호\", \"계좌\", \"여권\" …) 이나 표 열 머리글이 해당 유형일 때만 잡는다. 가장 가까운\n * 라벨이 이긴다 — \"법인등록번호\" 옆 6-7 번호는 주민번호로 보지 않는다\n * - 체크섬: 카드 Luhn, 사업자·법인등록번호 가중합. 주민·외국인등록번호는 2020-10 이후 뒷자리가\n * 임의 번호라 체크섬으로 거르지 않고 생년월일(세기 포함)·성별 자리만 검증한다\n * - 룰 우선순위 겹침 처리 — 우선순위순으로 매치를 수집하고, 이미 점유된 구간과 겹치는 하위 룰\n * 매치는 스킵 (RULE_PRIORITY 참조)\n * - 정규식은 모듈 로드 시 1회 컴파일 (matchAll은 내부 클론이라 lastIndex 안전)\n *\n * 룰별 근거·오탐 실측은 bench/redact-bench.mjs (합성 정답 셋 + 실코퍼스) 참조.\n */\n\nimport { RULES, RULE_PRIORITY, labelsBefore, labelsIn, type Ctx, type Match, type Variant } from \"./redact-rules.js\"\n\nexport type RedactRule =\n | \"rrn\" | \"phone\" | \"email\" | \"card\" | \"account\" | \"passport\" | \"driver\" | \"brn\" | \"crn\" | \"ip\"\n\nexport interface RedactHit {\n rule: RedactRule\n /** 마스킹 후 문자열 — 원본 PII는 리포트에 담지 않는다 */\n masked: string\n /** 원문 내 시작 오프셋 (UTF-16 단위) */\n index: number\n /** 매치 길이 (서식 보존이라 마스킹 전후 동일) */\n length: number\n}\n\nexport interface RedactTextResult {\n text: string\n hits: RedactHit[]\n}\n\nexport interface RedactOptions {\n /** 적용할 룰 (기본: DEFAULT_REDACT_RULES — crn·ip는 기본 OFF) */\n rules?: readonly RedactRule[]\n /** 마스크 문자 — 1글자(UTF-16 1단위). 영숫자·공백·제어문자·마크다운/XML 특수문자(|\\<>&\"')는 금지. 기본 \"●\" */\n maskChar?: string\n}\n\n/**\n * 기본 적용 룰. 실코퍼스 2,303문서 오탐 실측 근거(bench/redact-bench.mjs --corpus):\n * - brn(사업자등록번호)은 종전 account 가 잡던 것을 체크섬으로 분리 — 기본 마스킹 범위 그대로\n * - passport 는 신형(M123A4567)만 무문맥, 구형(M12345678)은 \"여권\" 라벨이 있을 때만 → 기본 ON\n * - driver 는 지역코드(11~28)·지역명 검증으로 좁혔고 종전에도 account 로 가려지던 모양 → 기본 ON\n * - crn(법인등록번호)은 개인정보가 아니고, ip 는 \"1.2.3.4\" 절 번호·버전과 겹쳐 opt-in\n */\nexport const DEFAULT_REDACT_RULES: readonly RedactRule[] = [\n \"rrn\", \"phone\", \"email\", \"card\", \"account\", \"brn\", \"passport\", \"driver\",\n]\n\n/** 엔진이 아는 전체 룰 (CLI/MCP 입력 검증·벤치용) */\nexport const ALL_REDACT_RULES: readonly RedactRule[] = [...DEFAULT_REDACT_RULES, \"crn\", \"ip\"]\n\n// ─── 정규화 (같은 길이) ───────────────────────────────\n\n// 대시 모양 — 유니코드 하이픈·대시류 + 한글 문서에서 대시로 쓰는 괘선(─ ━)·한글 모음 ㅡ·장음 ー\nconst DASHES = new Set([0x2010, 0x2011, 0x2012, 0x2013, 0x2014, 0x2015, 0x2212, 0xfe58, 0xfe63, 0xff0d, 0x2500, 0x2501, 0x3161, 0x30fc])\nconst SPACES = new Set([0x00a0, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200a, 0x202f, 0x3000])\n\n/** 탐지용 그림자 문자열 — 모든 치환이 UTF-16 1단위 → 1단위라 오프셋이 원문과 같다 */\nexport function normalizeForDetect(text: string): string {\n let out = \"\"\n let changed = false\n for (let i = 0; i < text.length; i++) {\n const c = text.charCodeAt(i)\n let r = c\n if (c >= 0xff01 && c <= 0xff5e) r = c - 0xfee0 // 전각 ASCII(!~~) → 반각\n if (DASHES.has(c)) r = 0x2d\n else if (SPACES.has(c)) r = 0x20\n if (r !== c) changed = true\n out += r === c ? text[i] : String.fromCharCode(r)\n }\n return changed ? out : text\n}\n\n/**\n * 매치 원문에 이름 그룹 범위를 maskChar 로 치환한 문자열 (길이 동일). 영숫자 판정은 정규화 텍스트로\n * 해서 전각 숫자도 가린다. maskAll 이면 구분자까지 전부.\n */\nfunction maskMatch(orig: string, x: Match, v: Variant, maskChar: string): string {\n const base = x.m.index as number\n const norm = x.m[0]\n const chars = orig.split(\"\")\n const idx = x.m.indices?.groups\n for (const name of v.mask) {\n const r = idx?.[name]\n if (!r) continue\n for (let i = r[0] - base; i < r[1] - base; i++) {\n if (v.maskAll || /[0-9A-Za-z]/.test(norm[i])) chars[i] = maskChar\n }\n }\n return chars.join(\"\")\n}\n\n/**\n * 마스크 문자 검증 — 1글자(UTF-16 1단위). 금지: 영숫자(전각 포함 — 정규화하면 숫자라 재탐지됨), 공백·제어·\n * 서식·결합 문자·비문자(U+FFFE/FFFF 등 \\\\p{Z}\\\\p{C}\\\\p{M}), 표·태그를 깨는 |\\\\<>&\"', 번호·주소 구분자\n * ._-@+:/,; (가린 결과가 다시 번호·이메일 모양이 되어 재검사 잔존으로 잡힌다)\n */\nfunction assertMaskChar(maskChar: string): void {\n const n = normalizeForDetect(maskChar)\n if (\n maskChar.length !== 1 || /[0-9A-Za-z|\\\\<>&\"'._\\-@+:/,;]/.test(n)\n || /[\\p{Z}\\p{C}\\p{M}]/u.test(maskChar) || /[\\uFDD0-\\uFDEF\\uFFFE\\uFFFF]/.test(maskChar)\n ) {\n throw new Error(`maskChar는 영숫자·공백·제어/결합 문자·구분 기호(|\\\\<>&\"'._-@+:/,;)가 아닌 보이는 1글자여야 함: ${JSON.stringify(maskChar)}`)\n }\n}\n\n/** 나열 구분자 — 앞 값과 이 값 사이가 이것뿐이면 같은 라벨 아래 목록이다 (\"계좌 110-…, 110-…\") */\nconst LIST_SEP_RE = /^\\s{0,2}(?:[,;/·、]|및|또는)?\\s{0,2}$/\n\n/**\n * 탐지 코어 — headerAt 은 오프셋 → 그 위치 표 열 머리글 텍스트 (redactMarkdown 이 표에서 제공).\n * 인라인 라벨이 없으면 (1) 바로 앞에 나열 구분자로 이어진 값이 있을 때 그 값의 라벨과 룰을 잇고,\n * (2) 그래도 없으면 머리글 라벨 집합을 쓴다.\n */\nfunction detect(\n text: string, rules: readonly RedactRule[], maskChar: string, headerAt?: (index: number) => string | undefined,\n): RedactHit[] {\n const norm = normalizeForDetect(text)\n const hits: RedactHit[] = []\n const occupied: Array<{ start: number; end: number; ctx: () => ReadonlySet<Ctx>; rule: RedactRule }> = []\n for (const rule of RULE_PRIORITY) {\n if (!rules.includes(rule)) continue\n for (const v of RULES[rule]) {\n for (const m of norm.matchAll(v.re)) {\n const start = m.index as number\n const end = start + m[0].length\n if (occupied.some((o) => start < o.end && end > o.start)) continue\n let cached: ReadonlySet<Ctx> | undefined\n const ctx = (): ReadonlySet<Ctx> => {\n if (!cached) {\n let c: ReadonlySet<Ctx> = labelsBefore(norm, start)\n if (c.size === 0) {\n const prev = occupied.find((o) => o.end < start && start - o.end <= 6 && LIST_SEP_RE.test(norm.slice(o.end, start)))\n // 앞 값의 라벨 + 앞 값이 무엇이었는지 자체가 문맥 (주민번호 다음 무구분 13자리도 주민번호)\n if (prev) c = prev.rule === \"email\" ? prev.ctx() : new Set<Ctx>([...prev.ctx(), prev.rule])\n }\n if (c.size === 0 && headerAt) {\n const h = headerAt(start)\n if (h) c = labelsIn(normalizeForDetect(h))\n }\n cached = c\n }\n return cached\n }\n const x: Match = { m, ctx }\n if (v.needs && !ctx().has(v.needs)) continue\n if (v.ok && !v.ok(x)) continue\n occupied.push({ start, end, ctx, rule })\n hits.push({ rule, masked: maskMatch(text.slice(start, end), x, v, maskChar), index: start, length: end - start })\n }\n }\n }\n return hits.sort((a, b) => a.index - b.index)\n}\n\nfunction applyHits(text: string, hits: readonly RedactHit[]): string {\n let out = \"\"\n let cursor = 0\n for (const h of hits) {\n out += text.slice(cursor, h.index) + h.masked\n cursor = h.index + h.length\n }\n return out + text.slice(cursor)\n}\n\n/**\n * 텍스트에서 PII를 탐지해 서식 보존 마스킹.\n *\n * @param text - 대상 텍스트 (마크다운 포함)\n * @param options - 룰 선택·마스크 문자 (기본: DEFAULT_REDACT_RULES, \"●\")\n * @returns 마스킹된 텍스트 + 히트 리포트 (index 오름차순, 원본 PII 미포함)\n */\nexport function redactText(text: string, options?: RedactOptions): RedactTextResult {\n const maskChar = options?.maskChar ?? \"●\"\n assertMaskChar(maskChar)\n const rules = options?.rules ?? DEFAULT_REDACT_RULES\n if (text === \"\" || rules.length === 0) return { text, hits: [] }\n const hits = detect(text, rules, maskChar)\n return { text: applyHits(text, hits), hits }\n}\n\n// ─── 마크다운 표 머리글 문맥 ──────────────────────────\n\n/** GFM 행의 셀 경계(이스케이프 안 된 파이프) 위치 */\nfunction pipePositions(line: string): number[] {\n const out: number[] = []\n for (let i = 0; i < line.length; i++) {\n if (line[i] === \"|\" && line[i - 1] !== \"\\\\\") out.push(i)\n }\n return out\n}\n\nconst isGfmRow = (l: string): boolean => /^\\s*\\|.*\\|\\s*$/.test(l)\nconst isGfmSep = (l: string): boolean => /^\\s*\\|(?:\\s*:?-{3,}:?\\s*\\|)+\\s*$/.test(l)\n\ninterface HtmlCell { start: number; end: number; col: number; span: number; text: string }\n\n/** HTML 표 행 한 줄의 셀 목록 (colspan 반영 열 번호) */\nfunction htmlCells(line: string): HtmlCell[] {\n const out: HtmlCell[] = []\n let col = 0\n for (const m of line.matchAll(/<t([dh])\\b([^>]*)>([\\s\\S]*?)<\\/t\\1>/gi)) {\n const span = Math.max(1, Number(m[2].match(/colspan=\"?(\\d+)/i)?.[1] ?? 1))\n out.push({ start: m.index as number, end: (m.index as number) + m[0].length, col, span, text: m[3].replace(/<[^>]*>/g, \" \") })\n col += span\n }\n return out\n}\n\n/**\n * 줄 번호 → (줄 내 오프셋 → 열 머리글 텍스트). GFM 표는 첫 행, HTML 표는 첫 <tr> 을 머리글로 본다\n * (병합·중첩 표에서는 근사 — 인라인 라벨이 없을 때만 쓰이는 보조 문맥).\n */\nfunction tableHeaderContext(lines: string[]): Map<number, (offset: number) => string | undefined> {\n const out = new Map<number, (offset: number) => string | undefined>()\n for (let i = 0; i < lines.length; i++) {\n // GFM\n if (isGfmRow(lines[i]) && i + 1 < lines.length && isGfmSep(lines[i + 1])) {\n const head = lines[i]\n const hp = pipePositions(head)\n const headers = hp.slice(0, -1).map((p, k) => head.slice(p + 1, hp[k + 1]))\n let j = i + 2\n for (; j < lines.length && isGfmRow(lines[j]); j++) {\n const rp = pipePositions(lines[j])\n out.set(j, (off) => {\n let k = -1\n for (const p of rp) { if (p < off) k++; else break }\n return k >= 0 ? headers[k] : undefined\n })\n }\n i = j - 1\n continue\n }\n // HTML — 표 시작 줄부터 </table> 까지, 첫 <tr> 이 머리글\n if (/<table\\b/i.test(lines[i])) {\n let headers: HtmlCell[] | null = null\n let depth = 0\n for (let j = i; j < lines.length; j++) {\n depth += (lines[j].match(/<table\\b/gi) ?? []).length\n if (/<tr\\b/i.test(lines[j]) && depth === 1) {\n const cells = htmlCells(lines[j])\n if (!headers) headers = cells\n else if (cells.length > 0) {\n const hdr = headers\n out.set(j, (off) => {\n const c = cells.find((x) => off >= x.start && off < x.end)\n if (!c) return undefined\n const h = hdr.find((x) => c.col >= x.col && c.col < x.col + x.span)\n return h?.text\n })\n }\n }\n depth -= (lines[j].match(/<\\/table>/gi) ?? []).length\n if (depth <= 0) { i = j; break }\n }\n }\n }\n return out\n}\n\n/**\n * 인라인 서식 표지 — 굵게·취소선·밑줄/HTML 강조 태그·백슬래시 이스케이프. 번호 한가운데 끼어도\n * (run 경계에서 굵게가 바뀐 \"010-98**76-5431**\", 이스케이프된 가림표 \"900101-1\\\\*\\\\*…\") 탐지되게\n * 지운 사본에서 찾고, 마스킹은 원문 글자 자리에만 입힌다 (표지는 그대로).\n * 항상 표지: ** __ ~~ (3개 이상 연속은 가림표일 수 있어 제외), 속성 없는 인라인 태그(href 속 값은 탐지\n * 대상으로 남긴다), 백슬래시 이스케이프.\n */\nconst MD_ALWAYS_MARKER_RE = new RegExp([\n \"(?<!\\\\*)\\\\*\\\\*(?!\\\\*)\", \"(?<!_)__(?!_)\", \"(?<!~)~~(?!~)\",\n \"<\\\\/?(?:u|b|i|s|em|strong|sup|sub|del|span|mark|small|big|ins|strike|tt|code|kbd|samp|var|q|cite|abbr|dfn)>\",\n \"\\\\\\\\(?=[\\\\\\\\`*_{}[\\\\]()#+\\\\-.!|~>])\",\n].join(\"|\"), \"g\")\n/**\n * 짝지어야 표지 — 홑 * · 홑 _ · *** 기울임은 단어 경계의 여는 표지와 닫는 표지가 한 줄 안에서 짝을 이룰 때만.\n * 짝 없는 것은 가림표(900101-1694***, 010-****-5678)일 수 있어 둔다.\n */\nconst MD_OPEN_RE = /(?<![\\w*\\\\])(\\*\\*\\*|\\*|_)(?=[0-9A-Za-z0-9])/g\nconst MD_CLOSE_RE = /(?<=[0-9A-Za-z0-9])(\\*\\*\\*|\\*|_)(?![\\w*])/g\n\n/** 마크다운 한 줄의 인라인 서식 표지 범위 [시작, 끝) — 오름차순 */\nexport function mdMarkerRanges(line: string): Array<[number, number]> {\n const out: Array<[number, number]> = []\n for (const m of line.matchAll(MD_ALWAYS_MARKER_RE)) out.push([m.index as number, (m.index as number) + m[0].length])\n const taken = (a: number, b: number): boolean => out.some(([s, e]) => a < e && b > s)\n const closes = [...line.matchAll(MD_CLOSE_RE)].filter((m) => !taken(m.index as number, (m.index as number) + m[0].length))\n let from = 0\n for (const o of line.matchAll(MD_OPEN_RE)) {\n const os = o.index as number\n const oe = os + o[0].length\n if (os < from || taken(os, oe)) continue\n const c = closes.find((m) => (m.index as number) >= oe && m[0] === o[0])\n if (!c) continue\n out.push([os, oe], [c.index as number, (c.index as number) + c[0].length])\n from = (c.index as number) + c[0].length\n }\n return out.sort((a, b) => a[0] - b[0])\n}\n\n/** 마크다운 한 줄 탐지 — 인라인 표지를 지운 사본에서 찾아 원문 오프셋·마스킹으로 되돌린다 */\nfunction detectMarkdownLine(\n line: string, rules: readonly RedactRule[], maskChar: string, headerAt?: (index: number) => string | undefined,\n): RedactHit[] {\n const markers = mdMarkerRanges(line)\n if (markers.length === 0) return detect(line, rules, maskChar, headerAt)\n let clean = \"\"\n const map: number[] = []\n let cursor = 0\n const keep = (to: number): void => {\n for (let i = cursor; i < to; i++) { clean += line[i]; map.push(i) }\n }\n for (const [ms, me] of markers) {\n keep(ms)\n cursor = me\n }\n keep(line.length)\n const hits = detect(clean, rules, maskChar, headerAt ? (i) => headerAt(map[i] ?? i) : undefined)\n return hits.map((h) => {\n const start = map[h.index]\n const end = map[h.index + h.length - 1] + 1\n const chars = line.slice(start, end).split(\"\")\n for (let k = 0; k < h.length; k++) {\n if (h.masked[k] !== clean[h.index + k]) chars[map[h.index + k] - start] = maskChar\n }\n return { rule: h.rule, masked: chars.join(\"\"), index: start, length: end - start }\n })\n}\n\n/**\n * 마크다운 문서 전용 래퍼 — base64 이미지(data URI) 라인은 마스킹에서 제외한다.\n * base64 페이로드의 숫자열이 phone 등에 오탐되면 이미지가 깨지기 때문.\n * 표 셀은 열 머리글(\"주민등록번호\"·\"계좌번호\" …)을 라벨 문맥으로 쓰고, 인라인 서식 표지(**·\\\\ 이스케이프 등)가\n * 번호를 갈라도 잡는다. hits의 index는 문서 전체 기준 절대 오프셋으로 환산된다 (masked 에는 표지가 그대로).\n */\nexport function redactMarkdown(markdown: string, options?: RedactOptions): RedactTextResult {\n const maskChar = options?.maskChar ?? \"●\"\n assertMaskChar(maskChar)\n const rules = options?.rules ?? DEFAULT_REDACT_RULES\n const lines = markdown.split(\"\\n\")\n const headers = tableHeaderContext(lines)\n const hits: RedactHit[] = []\n let offset = 0\n const outLines = lines.map((line, i) => {\n if (line.includes(\"data:image/\") || line === \"\" || rules.length === 0) {\n offset += line.length + 1\n return line\n }\n const lineHits = detectMarkdownLine(line, rules, maskChar, headers.get(i))\n for (const h of lineHits) hits.push({ ...h, index: h.index + offset })\n offset += line.length + 1\n return applyHits(line, lineHits)\n })\n return { text: outLines.join(\"\\n\"), hits }\n}\n"],"mappings":"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","names":["g"]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  blocksToMarkdown
4
- } from "./chunk-QC5T6D4J.js";
4
+ } from "./chunk-QGW7NMTX.js";
5
5
 
6
6
  // src/chunks.ts
7
7
  var LIST_MARKER_RE = /^(?:[□■◇◆○◎●◦ㅇ•▪▸▶※-]|\d{1,3}[.)]|[가나다라마바사아자차카타파하][.)]|\([가나다라마바사아자차카타파하0-9]{1,3}\)|[①-⑳㉮-㉻㈎-㈛])\s/;
@@ -79,4 +79,4 @@ function blocksToChunks(blocks, options) {
79
79
  export {
80
80
  blocksToChunks
81
81
  };
82
- //# sourceMappingURL=chunk-EDNJLUSE.js.map
82
+ //# sourceMappingURL=chunk-IHZEFW4L.js.map