hwpx-kit-cli 0.4.1__tar.gz → 0.5.0__tar.gz

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 (93) hide show
  1. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/.claude-plugin/plugin.json +1 -1
  2. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/PKG-INFO +10 -3
  3. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/README.md +9 -2
  4. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/pyproject.toml +1 -1
  5. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/skills/hwpx-form/SKILL.md +16 -0
  6. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/cli.py +12 -3
  7. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/fill.py +25 -0
  8. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_fill_batch.py +49 -0
  9. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/.claude-plugin/marketplace.json +0 -0
  10. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  11. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  12. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/.github/workflows/tests.yml +0 -0
  13. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/.gitignore +0 -0
  14. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/CLAUDE.md +0 -0
  15. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/LICENSE +0 -0
  16. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/commands/hwpx.md +0 -0
  17. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/docs/images/After.png +0 -0
  18. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/docs/images/Before.png +0 -0
  19. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/docs/images/Tasking.png +0 -0
  20. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/install.ps1 +0 -0
  21. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/install.sh +0 -0
  22. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/sample-values.json +0 -0
  23. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/scripts/build_skill_package.py +0 -0
  24. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/scripts/check_stdout_purity.py +0 -0
  25. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/scripts/make_sample.py +0 -0
  26. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/skills/doc-create/SKILL.md +0 -0
  27. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/skills/format-convert/SKILL.md +0 -0
  28. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/skills/gongmun-format/SKILL.md +0 -0
  29. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/skills/office-export/SKILL.md +0 -0
  30. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/skills/table-calc/SKILL.md +0 -0
  31. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/__init__.py +0 -0
  32. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/adapter/__init__.py +0 -0
  33. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/adapter/base.py +0 -0
  34. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/adapter/hwpx_engine.py +0 -0
  35. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/adapter/kordoc_engine.py +0 -0
  36. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/adapter/office_readers.py +0 -0
  37. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/__init__.py +0 -0
  38. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/analyze.py +0 -0
  39. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/convert.py +0 -0
  40. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/export.py +0 -0
  41. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/fill_batch.py +0 -0
  42. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/fmt.py +0 -0
  43. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/generate.py +0 -0
  44. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/header_footer.py +0 -0
  45. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/image_add.py +0 -0
  46. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/inspect_doc.py +0 -0
  47. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/outline.py +0 -0
  48. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/page_break.py +0 -0
  49. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/read.py +0 -0
  50. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/render.py +0 -0
  51. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/row_height.py +0 -0
  52. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/table_clear.py +0 -0
  53. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/table_copy.py +0 -0
  54. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/table_map_cmd.py +0 -0
  55. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/table_new.py +0 -0
  56. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/table_rows.py +0 -0
  57. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/table_set.py +0 -0
  58. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/commands/validate.py +0 -0
  59. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/format.py +0 -0
  60. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/inspect_rules.py +0 -0
  61. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/src/hwpx_kit/output.py +0 -0
  62. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/templates/Default_Template.hwpx +0 -0
  63. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/__init__.py +0 -0
  64. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/conftest.py +0 -0
  65. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/fixtures/real/legacy-sample.hwp +0 -0
  66. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/fixtures/real/seoul-attachment.hwpx +0 -0
  67. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/fixtures/real/seoul-body.hwpx +0 -0
  68. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/fixtures/real/seoul-report-brief.hwpx +0 -0
  69. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/fixtures/real/seoul-report-cover.hwpx +0 -0
  70. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_adapter.py +0 -0
  71. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_analyze.py +0 -0
  72. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_cli.py +0 -0
  73. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_convert.py +0 -0
  74. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_export.py +0 -0
  75. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_fill.py +0 -0
  76. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_format.py +0 -0
  77. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_image_header.py +0 -0
  78. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_inspect.py +0 -0
  79. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_integration.py +0 -0
  80. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_kordoc.py +0 -0
  81. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_office_readers.py +0 -0
  82. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_output.py +0 -0
  83. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_page_break.py +0 -0
  84. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_read_validate.py +0 -0
  85. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_real_forms.py +0 -0
  86. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_row_height.py +0 -0
  87. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_skill_package.py +0 -0
  88. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_stdout_purity.py +0 -0
  89. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_table_clear.py +0 -0
  90. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_table_copy.py +0 -0
  91. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_table_map_new.py +0 -0
  92. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_table_rows.py +0 -0
  93. {hwpx_kit_cli-0.4.1 → hwpx_kit_cli-0.5.0}/tests/test_table_set.py +0 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hwpx-kit",
3
3
  "description": "한글(HWPX/HWP) 문서 자동화 스킬 모음 — 양식 분석·채우기·백지 생성·표기 변환·공문 규약·표 계산·오피스 출력. CLI는 첫 사용 시 스킬이 설치를 안내 (Python 3.10+ 필요).",
4
- "version": "0.4.1",
4
+ "version": "0.5.0",
5
5
  "author": {
6
6
  "name": "6aneffy",
7
7
  "email": "tkdgus990809@gmail.com"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hwpx-kit-cli
3
- Version: 0.4.1
3
+ Version: 0.5.0
4
4
  Summary: 한글(HWPX) 문서 자동화 CLI — Korean HWPX document automation for AI agents: analyze, fill, read, validate, table ops
5
5
  Project-URL: Homepage, https://github.com/6aneffy/hwpx-kit
6
6
  Project-URL: Repository, https://github.com/6aneffy/hwpx-kit
@@ -33,7 +33,7 @@ Description-Content-Type: text/markdown
33
33
  [![PyPI](https://img.shields.io/pypi/v/hwpx-kit-cli.svg)](https://pypi.org/project/hwpx-kit-cli/)
34
34
  [![Python](https://img.shields.io/badge/python-3.10%2B-3776AB.svg)](https://www.python.org/)
35
35
  [![Claude Code](https://img.shields.io/badge/Claude%20Code-plugin-d97757.svg)](https://claude.com/claude-code)
36
- [![Tests](https://img.shields.io/badge/tests-190%20passing-brightgreen.svg)](tests/)
36
+ [![Tests](https://img.shields.io/badge/tests-193%20passing-brightgreen.svg)](tests/)
37
37
 
38
38
  </div>
39
39
 
@@ -204,11 +204,18 @@ templates/ 기본 계획서 템플릿 (표지·장 헤더·표 골격, 마커
204
204
  설계 불변 원칙: **① 원본 절대 수정 금지 ② `--json` stdout은 봉투 한 줄만
205
205
  ③ 서식은 항상 보존** (셀을 비워도 글자모양이 유지되어 재기입 시 원래 서식을 물려받음).
206
206
 
207
+ ## 민감정보 처리 (secure 모드)
208
+
209
+ 주민번호·계좌 같은 값은 **AI 모델을 거치지 않고** 채울 수 있습니다:
210
+ 사용자가 값 파일을 직접 작성 → 스킬은 경로만 전달 → `fill --secure`가 CLI에서만 읽어 채움.
211
+ `--secure`는 하나라도 못 채우면 산출물 없이 실패하고(부분 채움 방지), 출력에 값을 노출하지 않습니다.
212
+ 제출 전 `inspect`로 개인정보 잔존(주민번호·전화·이메일 패턴)도 기계 검사할 수 있습니다.
213
+
207
214
  > 🚧 같은 코어를 MCP 서버로 노출하는 버전도 제작 중입니다 (Claude Desktop 등 MCP 클라이언트용).
208
215
 
209
216
  ## 검증
210
217
 
211
- - 자동 테스트 190개 (pytest)
218
+ - 자동 테스트 193개 (pytest)
212
219
  - 실서식 수렴 테스트: 보도자료·신청서·계획서·회의록·공고문 5계열,
213
220
  실제 공공기관 문서 30여 종으로 analyze→fill→validate 왕복
214
221
  - 실사용 시나리오 테스트 3회 (백지 생성 12분 완주, 장·표 동적 추가, 보도자료 변환)
@@ -10,7 +10,7 @@
10
10
  [![PyPI](https://img.shields.io/pypi/v/hwpx-kit-cli.svg)](https://pypi.org/project/hwpx-kit-cli/)
11
11
  [![Python](https://img.shields.io/badge/python-3.10%2B-3776AB.svg)](https://www.python.org/)
12
12
  [![Claude Code](https://img.shields.io/badge/Claude%20Code-plugin-d97757.svg)](https://claude.com/claude-code)
13
- [![Tests](https://img.shields.io/badge/tests-190%20passing-brightgreen.svg)](tests/)
13
+ [![Tests](https://img.shields.io/badge/tests-193%20passing-brightgreen.svg)](tests/)
14
14
 
15
15
  </div>
16
16
 
@@ -181,11 +181,18 @@ templates/ 기본 계획서 템플릿 (표지·장 헤더·표 골격, 마커
181
181
  설계 불변 원칙: **① 원본 절대 수정 금지 ② `--json` stdout은 봉투 한 줄만
182
182
  ③ 서식은 항상 보존** (셀을 비워도 글자모양이 유지되어 재기입 시 원래 서식을 물려받음).
183
183
 
184
+ ## 민감정보 처리 (secure 모드)
185
+
186
+ 주민번호·계좌 같은 값은 **AI 모델을 거치지 않고** 채울 수 있습니다:
187
+ 사용자가 값 파일을 직접 작성 → 스킬은 경로만 전달 → `fill --secure`가 CLI에서만 읽어 채움.
188
+ `--secure`는 하나라도 못 채우면 산출물 없이 실패하고(부분 채움 방지), 출력에 값을 노출하지 않습니다.
189
+ 제출 전 `inspect`로 개인정보 잔존(주민번호·전화·이메일 패턴)도 기계 검사할 수 있습니다.
190
+
184
191
  > 🚧 같은 코어를 MCP 서버로 노출하는 버전도 제작 중입니다 (Claude Desktop 등 MCP 클라이언트용).
185
192
 
186
193
  ## 검증
187
194
 
188
- - 자동 테스트 190개 (pytest)
195
+ - 자동 테스트 193개 (pytest)
189
196
  - 실서식 수렴 테스트: 보도자료·신청서·계획서·회의록·공고문 5계열,
190
197
  실제 공공기관 문서 30여 종으로 analyze→fill→validate 왕복
191
198
  - 실사용 시나리오 테스트 3회 (백지 생성 12분 완주, 장·표 동적 추가, 보도자료 변환)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hwpx-kit-cli"
3
- version = "0.4.1"
3
+ version = "0.5.0"
4
4
  description = "한글(HWPX) 문서 자동화 CLI — Korean HWPX document automation for AI agents: analyze, fill, read, validate, table ops"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -157,6 +157,22 @@ CLI `hwpx-kit`으로 hwpx 문서를 다룬다. 항상 `--json`을 붙여 실행
157
157
  금지문자는 `_` 치환. 원본 불변은 fill과 동일
158
158
  5. 개인정보 든 명단 파일은 사용자 문서와 동일한 기밀 취급 — 커밋·업로드 금지
159
159
 
160
+ ## 민감정보(주민번호·계좌 등) 채우기 — secure 프로토콜
161
+
162
+ 주민번호·계좌·급여처럼 민감한 값이 들어가는 서식은 **값이 너(Claude)를 거치지
163
+ 않게** 처리한다:
164
+
165
+ 1. 사용자에게 값 JSON 파일을 **직접** 만들어 달라고 안내 (메모장 등):
166
+ `{"table:주민등록번호": "여기에 직접 입력", ...}` — 키 목록은 네가
167
+ analyze 결과로 만들어주되 **값은 비워서** 건넨다
168
+ 2. 파일을 받으면 **절대 Read하지 말고 경로만** 사용:
169
+ `hwpx-kit fill <양식> --data <값파일> --out <결과> --secure --json`
170
+ - --secure = 하나라도 못 채우면 산출물 없이 실패 (반쯤 채워진 개인정보
171
+ 문서가 남지 않음), 출력 JSON에 값 비노출
172
+ 3. 채움 확인은 값을 보지 않고 간접으로: `inspect <결과> --checks residue`
173
+ (빈칸·마커 잔존 없음 = 전부 채워짐)
174
+ 4. 끝나면 값 파일 삭제를 사용자에게 권한다. 값 파일·결과물 모두 커밋·업로드 금지
175
+
160
176
  ## 사진·직인 넣기 — "여기 도장 찍어줘", "사진 붙여줘"
161
177
 
162
178
  `hwpx-kit image-add <문서.hwpx> --image <파일.png> ... --out <결과.hwpx>`
@@ -50,6 +50,8 @@ def _build_parser() -> argparse.ArgumentParser:
50
50
  pf.add_argument("file")
51
51
  pf.add_argument("--data", required=True, help="fill_key: 값 매핑 JSON 파일")
52
52
  pf.add_argument("--out", required=True, help="출력 hwpx 경로")
53
+ pf.add_argument("--secure", action="store_true",
54
+ help="엄격 모드(민감정보용) — 하나라도 못 채우면 산출물 없이 실패, 출력에 값 비노출")
53
55
  pf.add_argument("--json", action="store_true")
54
56
 
55
57
  pr = sub.add_parser("read", help="본문을 Markdown/텍스트로 추출")
@@ -229,9 +231,16 @@ def main(argv: list[str] | None = None) -> int:
229
231
  elif args.command == "fill":
230
232
  with open(args.data, encoding="utf-8") as fh:
231
233
  mapping = json.load(fh)
232
- data = run_fill(args.file, mapping, args.out)
233
- if data["unmatched"]:
234
- exit_code = 2
234
+ if args.secure:
235
+ from hwpx_kit.commands.fill import run_fill_secure
236
+
237
+ data = run_fill_secure(args.file, mapping, args.out)
238
+ if not data["ok"]:
239
+ exit_code = 1
240
+ else:
241
+ data = run_fill(args.file, mapping, args.out)
242
+ if data["unmatched"]:
243
+ exit_code = 2
235
244
  elif args.command == "read":
236
245
  data = run_read(args.file, fmt=args.format)
237
246
  elif args.command == "convert":
@@ -77,6 +77,31 @@ def _apply_one(ad: HwpxEngineAdapter, fill_key: str, value: str) -> str | None:
77
77
  return "알 수 없는 fill_key 형식 (clickhere:/marker:/table:/text:/delete:/bold:/underline: 중 하나여야 함)"
78
78
 
79
79
 
80
+ def run_fill_secure(path: str, data: dict[str, str], out_path: str) -> dict:
81
+ """엄격 모드 — 민감정보(PII) 채우기용.
82
+
83
+ ① 하나라도 못 채우면 산출물을 남기지 않는다 (반쯤 채워진 개인정보 문서 방지)
84
+ ② 결과에 값을 절대 노출하지 않는다 — 키 목록·개수만.
85
+ 값 파일은 CLI만 읽는 것이 전제 (스킬 규약: Claude는 경로만 전달).
86
+ """
87
+ import os as _os
88
+
89
+ result = run_fill(path, data, out_path)
90
+ if result["unmatched"]:
91
+ try:
92
+ _os.remove(result["out"])
93
+ except OSError:
94
+ pass
95
+ return {
96
+ "ok": False,
97
+ "out": None,
98
+ "applied_count": len(result["applied"]),
99
+ "unmatched_keys": [u["key"] for u in result["unmatched"]],
100
+ "reason": "일부 키를 채우지 못해 산출물을 만들지 않았습니다 (--secure 엄격 모드)",
101
+ }
102
+ return {"ok": True, "out": result["out"], "applied_count": len(result["applied"])}
103
+
104
+
80
105
  def run_fill(path: str, data: dict[str, str], out_path: str) -> dict:
81
106
  ad = HwpxEngineAdapter.open(path)
82
107
  applied: list[str] = []
@@ -151,3 +151,52 @@ def test_cli_fill_batch(cert_form, roster_json, template_json, tmp_path, capsys)
151
151
  assert code == 0
152
152
  assert env["ok"] is True
153
153
  assert env["data"]["succeeded"] == 3
154
+
155
+
156
+ # ── fill --secure (엄격 모드) ─────────────────────────────────
157
+
158
+ def test_fill_secure_strict_no_partial(cert_form, tmp_path):
159
+ """--secure: 하나라도 못 채우면 산출물을 남기지 않는다 (반쯤 채워진 PII 문서 방지)."""
160
+ import os
161
+
162
+ from hwpx_kit.commands.fill import run_fill_secure
163
+
164
+ out = str(tmp_path / "o.hwpx")
165
+ result = run_fill_secure(cert_form, {"marker:성명": "김철수", "marker:없는키": "x"}, out)
166
+ assert result["ok"] is False
167
+ assert not os.path.exists(out) # 부분 산출물 없음
168
+ # 실패 사유에 값이 노출되지 않는다
169
+ import json as _json
170
+
171
+ assert "김철수" not in _json.dumps(result, ensure_ascii=False)
172
+
173
+
174
+ def test_fill_secure_success(cert_form, tmp_path):
175
+ import os
176
+
177
+ from hwpx_kit.commands.fill import run_fill_secure
178
+
179
+ out = str(tmp_path / "o.hwpx")
180
+ result = run_fill_secure(cert_form, {"marker:성명": "김철수", "marker:과정명": "AI"}, out)
181
+ assert result["ok"] is True and os.path.exists(out)
182
+ assert result["applied_count"] == 2
183
+ # 값 자체는 결과 JSON에 없음
184
+ import json as _json
185
+
186
+ assert "김철수" not in _json.dumps(result, ensure_ascii=False)
187
+
188
+
189
+ def test_cli_fill_secure(cert_form, tmp_path, capsys):
190
+ import json as _json
191
+
192
+ from hwpx_kit.cli import main
193
+
194
+ values = tmp_path / "v.json"
195
+ values.write_text(_json.dumps({"marker:성명": "김철수", "marker:과정명": "AI"},
196
+ ensure_ascii=False), encoding="utf-8")
197
+ out = str(tmp_path / "o.hwpx")
198
+ code = main(["fill", cert_form, "--data", str(values), "--out", out,
199
+ "--secure", "--json"])
200
+ captured = capsys.readouterr().out
201
+ assert code == 0
202
+ assert "김철수" not in captured # stdout에 값 비노출
File without changes
File without changes
File without changes
File without changes
File without changes