python-hwpx-automation 6.4.2__tar.gz → 6.5.1__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 (225) hide show
  1. {python_hwpx_automation-6.4.2/src/python_hwpx_automation.egg-info → python_hwpx_automation-6.5.1}/PKG-INFO +8 -7
  2. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/README.md +6 -5
  3. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/pyproject.toml +2 -2
  4. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/handlers/specialized.py +90 -1
  5. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/identity.json +9 -9
  6. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/agent/blueprint/dump.py +7 -0
  7. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/agent/blueprint/native.py +19 -4
  8. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/agent/commands.py +52 -17
  9. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/agent/document.py +57 -15
  10. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/__init__.py +18 -2
  11. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/builder/core.py +56 -0
  12. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/house_style/composition.py +9 -11
  13. python_hwpx_automation-6.5.1/src/hwpx_automation/office/house_style/org_chart.py +273 -0
  14. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/public-modules.json +5 -4
  15. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/server.py +3 -0
  16. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/tool_bindings.py +3 -0
  17. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/tool_contract.py +12 -10
  18. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1/src/python_hwpx_automation.egg-info}/PKG-INFO +8 -7
  19. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/python_hwpx_automation.egg-info/SOURCES.txt +1 -0
  20. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/python_hwpx_automation.egg-info/requires.txt +1 -1
  21. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/LICENSE +0 -0
  22. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/MANIFEST.in +0 -0
  23. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/NOTICE +0 -0
  24. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/setup.cfg +0 -0
  25. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/__init__.py +0 -0
  26. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/__init__.pyi +0 -0
  27. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/__main__.py +0 -0
  28. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/agent_document.py +0 -0
  29. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/api.py +0 -0
  30. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/blind_eval.py +0 -0
  31. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/capabilities.py +0 -0
  32. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/compat.py +0 -0
  33. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/configuration.py +0 -0
  34. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/core/__init__.py +0 -0
  35. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/core/content.py +0 -0
  36. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/core/context.py +0 -0
  37. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/core/diff.py +0 -0
  38. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/core/document.py +0 -0
  39. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/core/formatting.py +0 -0
  40. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/core/handles.py +0 -0
  41. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/core/locations.py +0 -0
  42. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/core/locator.py +0 -0
  43. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/core/plan.py +0 -0
  44. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/core/resources.py +0 -0
  45. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/core/search.py +0 -0
  46. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/core/transactions.py +0 -0
  47. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/core/txn.py +0 -0
  48. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/document_state.py +0 -0
  49. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/errors.py +0 -0
  50. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/execution_lock.py +0 -0
  51. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/fastmcp_adapter.py +0 -0
  52. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/form_fill.py +0 -0
  53. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/form_output_models.py +0 -0
  54. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/handlers/__init__.py +0 -0
  55. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/handlers/_shared.py +0 -0
  56. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/handlers/agent_document.py +0 -0
  57. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/handlers/authoring.py +0 -0
  58. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/handlers/content_edit.py +0 -0
  59. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/handlers/form_fill.py +0 -0
  60. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/handlers/layout_style.py +0 -0
  61. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/handlers/quality_render.py +0 -0
  62. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/handlers/read_export.py +0 -0
  63. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/handlers/tracked_changes.py +0 -0
  64. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/handlers/workflow.py +0 -0
  65. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/hwp_converter.py +0 -0
  66. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/hwp_support.py +0 -0
  67. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/hwpx_ops.py +0 -0
  68. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/identity.py +0 -0
  69. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/ingest_adapters.py +0 -0
  70. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/markdown_plan.py +0 -0
  71. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/mcp_cli.py +0 -0
  72. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/metadata/tools_meta.py +0 -0
  73. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/mixed_form.py +0 -0
  74. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/mutation_models.py +0 -0
  75. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/network_policy.py +0 -0
  76. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/__init__.py +0 -0
  77. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/agent/__init__.py +0 -0
  78. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/agent/_batch_verification.py +0 -0
  79. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/agent/blueprint/__init__.py +0 -0
  80. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/agent/blueprint/bundle.py +0 -0
  81. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/agent/blueprint/catalog.py +0 -0
  82. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/agent/blueprint/mapping.py +0 -0
  83. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/agent/blueprint/model.py +0 -0
  84. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/agent/blueprint/replay.py +0 -0
  85. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/agent/catalog.py +0 -0
  86. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/agent/cli.py +0 -0
  87. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/agent/form_plan.py +0 -0
  88. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/agent/model.py +0 -0
  89. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/agent/path.py +0 -0
  90. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/agent/query.py +0 -0
  91. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/agent/story.py +0 -0
  92. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/advanced_generators.py +0 -0
  93. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/builder/__init__.py +0 -0
  94. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/builder/report.py +0 -0
  95. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/__init__.py +0 -0
  96. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/_support.py +0 -0
  97. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/composer.py +0 -0
  98. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/harvest.py +0 -0
  99. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/plan.py +0 -0
  100. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profile.py +0 -0
  101. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/application_form/fragments/body.xml +0 -0
  102. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/application_form/fragments/heading.xml +0 -0
  103. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/application_form/fragments/info_table.xml +0 -0
  104. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/application_form/fragments/title.xml +0 -0
  105. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/application_form/profile.json +0 -0
  106. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/application_form/template.hwpx +0 -0
  107. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/home_notice/fragments/body.xml +0 -0
  108. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/home_notice/fragments/heading.xml +0 -0
  109. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/home_notice/fragments/title.xml +0 -0
  110. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/home_notice/profile.json +0 -0
  111. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/home_notice/template.hwpx +0 -0
  112. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/official_notice/fragments/body.xml +0 -0
  113. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/official_notice/fragments/heading.xml +0 -0
  114. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/official_notice/fragments/info_table.xml +0 -0
  115. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/official_notice/fragments/title.xml +0 -0
  116. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/official_notice/profile.json +0 -0
  117. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/official_notice/template.hwpx +0 -0
  118. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/report/fragments/body.xml +0 -0
  119. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/report/fragments/heading.xml +0 -0
  120. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/report/fragments/info_table.xml +0 -0
  121. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/report/fragments/title.xml +0 -0
  122. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/report/profile.json +0 -0
  123. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/profiles/report/template.hwpx +0 -0
  124. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/design/validator.py +0 -0
  125. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/presets/__init__.py +0 -0
  126. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/presets/proposal.py +0 -0
  127. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/report_parser.py +0 -0
  128. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/style_profile.py +0 -0
  129. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/authoring/template_analyzer.py +0 -0
  130. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/charting/__init__.py +0 -0
  131. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/compliance/__init__.py +0 -0
  132. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/compliance/official_lint.py +0 -0
  133. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/compliance/pii.py +0 -0
  134. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/document_ops/__init__.py +0 -0
  135. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/document_ops/comparison.py +0 -0
  136. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/document_ops/mail_merge.py +0 -0
  137. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/document_ops/redline.py +0 -0
  138. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/evalplan/__init__.py +0 -0
  139. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/evalplan/runtime.py +0 -0
  140. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/exam/__init__.py +0 -0
  141. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/exam/compose.py +0 -0
  142. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/exam/ir.py +0 -0
  143. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/exam/measure.py +0 -0
  144. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/exam/parser.py +0 -0
  145. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/exam/profile.py +0 -0
  146. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/form_fill/__init__.py +0 -0
  147. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/form_fill/classification.py +0 -0
  148. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/form_fill/fill_residue.py +0 -0
  149. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/form_fill/fit/__init__.py +0 -0
  150. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/form_fill/fit/apply.py +0 -0
  151. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/form_fill/fit/engine.py +0 -0
  152. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/form_fill/fit/measure.py +0 -0
  153. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/form_fill/fit/policy.py +0 -0
  154. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/form_fill/fit/report.py +0 -0
  155. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/form_fill/fit/seal.py +0 -0
  156. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/form_fill/fit/wordbox.py +0 -0
  157. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/form_fill/guidance.py +0 -0
  158. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/form_fill/quality.py +0 -0
  159. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/form_fill/split_run.py +0 -0
  160. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/form_fill/template_formfit.py +0 -0
  161. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/house_style/__init__.py +0 -0
  162. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/house_style/data/bank.json +0 -0
  163. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/house_style/data/genres.json +0 -0
  164. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/quality/__init__.py +0 -0
  165. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/quality/page_guard.py +0 -0
  166. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/rendering/__init__.py +0 -0
  167. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/rendering/_hancom_open_rate.ps1 +0 -0
  168. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/rendering/_refresh_hwpx_mac.applescript +0 -0
  169. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/rendering/_render_hwpx.ps1 +0 -0
  170. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/rendering/_render_hwpx_mac.applescript +0 -0
  171. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/rendering/block_splits.py +0 -0
  172. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/rendering/detectors.py +0 -0
  173. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/rendering/diff.py +0 -0
  174. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/rendering/fixture_corpus.py +0 -0
  175. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/rendering/oracle.py +0 -0
  176. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/rendering/page_qa.py +0 -0
  177. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/rendering/qa_contracts.py +0 -0
  178. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/rendering/qa_metrics.py +0 -0
  179. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/rendering/worker.py +0 -0
  180. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/utilities/__init__.py +0 -0
  181. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/office/utilities/table_compute.py +0 -0
  182. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/ops_services/__init__.py +0 -0
  183. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/ops_services/_border_fill.py +0 -0
  184. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/ops_services/composition.py +0 -0
  185. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/ops_services/content_layout.py +0 -0
  186. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/ops_services/context.py +0 -0
  187. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/ops_services/form_fields.py +0 -0
  188. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/ops_services/media.py +0 -0
  189. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/ops_services/memo_style.py +0 -0
  190. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/ops_services/package_validation.py +0 -0
  191. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/ops_services/planning.py +0 -0
  192. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/ops_services/preview_export.py +0 -0
  193. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/ops_services/read_query.py +0 -0
  194. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/ops_services/save_policy.py +0 -0
  195. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/ops_services/tables.py +0 -0
  196. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/ops_services/transactions.py +0 -0
  197. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/preview_output_models.py +0 -0
  198. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/py.typed +0 -0
  199. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/quality.py +0 -0
  200. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/quality_generation.py +0 -0
  201. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/runtime.py +0 -0
  202. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/runtime_services.py +0 -0
  203. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/storage.py +0 -0
  204. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/upstream.py +0 -0
  205. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/utils/__init__.py +0 -0
  206. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/utils/helpers.py +0 -0
  207. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/visual_qa.py +0 -0
  208. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/workflow/__init__.py +0 -0
  209. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/workflow/adapters.py +0 -0
  210. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/workflow/dispatcher.py +0 -0
  211. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/workflow/models.py +0 -0
  212. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/workflow/policy.py +0 -0
  213. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/workflow/render_contracts.py +0 -0
  214. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/workflow/render_metrics.py +0 -0
  215. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/workflow/render_queue.py +0 -0
  216. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/workflow/render_security.py +0 -0
  217. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/workflow/render_transport.py +0 -0
  218. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/workflow/rendering.py +0 -0
  219. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/workflow/service.py +0 -0
  220. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/workflow/state_machine.py +0 -0
  221. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/workflow/store.py +0 -0
  222. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/hwpx_automation/workspace.py +0 -0
  223. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/python_hwpx_automation.egg-info/dependency_links.txt +0 -0
  224. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/python_hwpx_automation.egg-info/entry_points.txt +0 -0
  225. {python_hwpx_automation-6.4.2 → python_hwpx_automation-6.5.1}/src/python_hwpx_automation.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hwpx-automation
3
- Version: 6.4.2
3
+ Version: 6.5.1
4
4
  Summary: Task-oriented HWPX document automation for Python, with an optional MCP adapter.
5
5
  Author: Kohkyuhyun
6
6
  License-Expression: Apache-2.0
@@ -20,7 +20,7 @@ Requires-Python: >=3.10
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
22
  License-File: NOTICE
23
- Requires-Dist: python-hwpx<6,>=5.4.0
23
+ Requires-Dist: python-hwpx<6,>=5.5.0
24
24
  Requires-Dist: pydantic<3,>=2.11
25
25
  Requires-Dist: cryptography>=42.0
26
26
  Requires-Dist: anyio>=4.0
@@ -74,11 +74,12 @@ Dynamic: license-file
74
74
 
75
75
  <!-- release-state: release-approved -->
76
76
  > [!NOTE]
77
- > 이 저장소의 main은 아직 공개되지 않은 6.4.2 후보 — release-approved 상태( 테두리·음영
78
- > `format_table` 확장, 수리된 core 5.4.0 기본값, 계약 `dbdbdfaac26148b7`,
79
- > 130 도구)입니다. 현재 공개 트레인은
80
- > `python-hwpx 5.3.0 → python-hwpx-automation 6.3.1 → hwpx-plugin 1.3.0`
81
- > (계약 `236f8ea855c875fe`, 2026-07-31 released)입니다. 공개 트레인은
77
+ > 이 저장소의 main은 아직 공개되지 않은 6.5.1 후보 — release-approved 상태(운영계획 장르 저작 —
78
+ > 표-기반 박스 조직도 `add_boxed_org_chart`·섹션칩 `compose_section_chip`·
79
+ > 장르 문법 `get_genre_grammar`, 계약 `f61d2c60c0aa0413`, 133 도구)입니다.
80
+ > 현재 공개 트레인은
81
+ > `python-hwpx 5.5.0 → python-hwpx-automation 6.4.2 hwpx-plugin 1.4.1`
82
+ > (계약 `dbdbdfaac26148b7`, 2026-08-02 released)입니다. 공개 트레인은
82
83
  > core·automation PyPI와 plugin GitHub
83
84
  > Release·marketplace·실제 marketplace 설치까지 확인한 뒤 승격했으며, 기존
84
85
  > 5.x 설치는 별도 발행된 `hwpx-mcp-server 5.1.1`이 보호합니다. 절차와 상태
@@ -14,11 +14,12 @@
14
14
 
15
15
  <!-- release-state: release-approved -->
16
16
  > [!NOTE]
17
- > 이 저장소의 main은 아직 공개되지 않은 6.4.2 후보 — release-approved 상태( 테두리·음영
18
- > `format_table` 확장, 수리된 core 5.4.0 기본값, 계약 `dbdbdfaac26148b7`,
19
- > 130 도구)입니다. 현재 공개 트레인은
20
- > `python-hwpx 5.3.0 → python-hwpx-automation 6.3.1 → hwpx-plugin 1.3.0`
21
- > (계약 `236f8ea855c875fe`, 2026-07-31 released)입니다. 공개 트레인은
17
+ > 이 저장소의 main은 아직 공개되지 않은 6.5.1 후보 — release-approved 상태(운영계획 장르 저작 —
18
+ > 표-기반 박스 조직도 `add_boxed_org_chart`·섹션칩 `compose_section_chip`·
19
+ > 장르 문법 `get_genre_grammar`, 계약 `f61d2c60c0aa0413`, 133 도구)입니다.
20
+ > 현재 공개 트레인은
21
+ > `python-hwpx 5.5.0 → python-hwpx-automation 6.4.2 hwpx-plugin 1.4.1`
22
+ > (계약 `dbdbdfaac26148b7`, 2026-08-02 released)입니다. 공개 트레인은
22
23
  > core·automation PyPI와 plugin GitHub
23
24
  > Release·marketplace·실제 marketplace 설치까지 확인한 뒤 승격했으며, 기존
24
25
  > 5.x 설치는 별도 발행된 `hwpx-mcp-server 5.1.1`이 보호합니다. 절차와 상태
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-hwpx-automation"
7
- version = "6.4.2"
7
+ version = "6.5.1"
8
8
  description = "Task-oriented HWPX document automation for Python, with an optional MCP adapter."
9
9
  readme = "README.md"
10
10
  authors = [{ name = "Kohkyuhyun" }]
@@ -29,7 +29,7 @@ dependencies = [
29
29
  # the bounded major here is what makes "no valid install has two declarers"
30
30
  # true rather than hopeful, and prevents the next core major from silently
31
31
  # breaking an already-published automation line.
32
- "python-hwpx>=5.4.0,<6", # HWPX object model, OXML/OPC, format-native primitives
32
+ "python-hwpx>=5.5.0,<6", # HWPX object model, OXML/OPC, format-native primitives
33
33
 
34
34
  "pydantic>=2.11,<3",
35
35
  "cryptography>=42.0",
@@ -7,7 +7,7 @@ import os
7
7
  import re
8
8
  import tempfile
9
9
  from pathlib import Path
10
- from typing import Any
10
+ from typing import Any, Literal, cast
11
11
 
12
12
  from ..core.content import (
13
13
  collect_full_text,
@@ -23,6 +23,11 @@ from ..office.authoring.advanced_generators import (
23
23
  build_organization_chart as build_hwpx_organization_chart,
24
24
  )
25
25
  from ..office.compliance import DEFAULT_POLICY, detect_pii, mask_value
26
+ from ..office.house_style import load_bank, load_genres
27
+ from ..office.house_style.composition import (
28
+ compose_section_chip as compose_house_section_chip,
29
+ )
30
+ from ..office.house_style.org_chart import lower_boxed_org_chart
26
31
  from ..office.document_ops import (
27
32
  build_mail_merge as build_hwpx_mail_merge,
28
33
  )
@@ -217,6 +222,90 @@ def build_organization_chart(
217
222
  }
218
223
 
219
224
 
225
+ def add_boxed_org_chart(
226
+ filename: str,
227
+ hierarchy: dict,
228
+ accent_color: str = None,
229
+ dry_run: bool = False,
230
+ expected_revision: str = None,
231
+ ) -> dict:
232
+ """계층 트리를 실물 관례의 박스 조직도(표 캔버스+병합 박스+셀 테두리
233
+ 커넥터)로 문서 끝에 그립니다. 각 노드는 {"label", "sublabel"?,
234
+ "children"?[]} 매핑이며 깊이 4·박스 40 초과는 typed 거부.
235
+ accent_color는 루트 박스 배경색. dry_run=True이면 저장하지 않습니다."""
236
+ path = resolve_path(filename)
237
+ guard = _revision_guard(path, expected_revision)
238
+ if guard is not None:
239
+ return guard
240
+ doc = open_doc(path)
241
+ summary = lower_boxed_org_chart(doc, hierarchy or {}, accent_color=accent_color)
242
+ result = {"orgChart": summary}
243
+ if dry_run:
244
+ return _with_dry_run_verification(result, doc, path)
245
+ verification = _save_doc_verification(doc, path)
246
+ return _with_save_verification(result, verification)
247
+
248
+
249
+ def compose_section_chip(
250
+ number: str,
251
+ title: str,
252
+ style: str = "box",
253
+ accent_color: str = None,
254
+ ) -> dict:
255
+ """운영계획 장르의 섹션 구분자(섹션칩)를 범용 document_plan block으로
256
+ 합성합니다. style="box"(1×3 accent 칩) 또는 "inline"(번호 제목 헤딩).
257
+ 파일은 쓰지 않습니다 — 반환 block을 document_plan에 끼워 넣으십시오."""
258
+ if style not in ("box", "inline"):
259
+ raise ValueError('style must be "box" or "inline"')
260
+ chip_style = cast('Literal["box", "inline"]', style)
261
+ block = compose_house_section_chip(
262
+ number, title, style=chip_style, accent_color=accent_color
263
+ )
264
+ result: dict = {"block": block, "next_tool": "validate_document_plan"}
265
+ if style == "box" and accent_color:
266
+ result["styling"] = {
267
+ "accentColor": accent_color,
268
+ "how": (
269
+ "문서 생성 후 이 칩 표의 번호 셀에 "
270
+ "format_table(fill_color=accentColor, row=0, col=0)을 적용"
271
+ ),
272
+ }
273
+ return result
274
+
275
+
276
+ def get_genre_grammar(genre: str) -> dict:
277
+ """장르 구조 문법·타이포 역할(하우스 스타일 뱅크)을 반환하는 read-only
278
+ 조회입니다. 스킬이 per-문서 인스턴스화(번호 체계·칩 렌더·블록 선택·색)
279
+ 판단에 사용합니다. 미보유 장르는 사용 가능 목록과 함께 typed 거부."""
280
+ catalog = load_genres()
281
+ genres = catalog.genres if hasattr(catalog, "genres") else {}
282
+ if genre not in genres:
283
+ raise ValueError(
284
+ f"unknown genre {genre!r}; available: {sorted(genres)}"
285
+ )
286
+ bank = load_bank()
287
+ entry = genres[genre]
288
+ payload = entry if isinstance(entry, dict) else entry.model_dump()
289
+ inherits = None
290
+ typography = payload.get("typography") if isinstance(payload, dict) else None
291
+ if isinstance(typography, dict):
292
+ inherits = typography.get("inherits")
293
+ profiles = bank.profiles if hasattr(bank, "profiles") else {}
294
+ inherited_profile = None
295
+ if inherits and inherits in profiles:
296
+ base = profiles[inherits]
297
+ inherited_profile = base if isinstance(base, dict) else base.model_dump()
298
+ return {
299
+ "genre": genre,
300
+ "grammar": payload,
301
+ "inheritedProfile": {"name": inherits, "profile": inherited_profile}
302
+ if inherited_profile
303
+ else None,
304
+ "sectionChipTool": "compose_section_chip",
305
+ "orgChartTool": "add_boxed_org_chart",
306
+ }
307
+
308
+
220
309
  def scan_personal_info(filename: str | None = None, text: str | None = None) -> dict:
221
310
  """문서/텍스트의 개인정보(PII)를 탐지하는 read-only 감사 (원본값 미노출).
222
311
 
@@ -5,20 +5,20 @@
5
5
  "releaseState": {
6
6
  "status": "release-approved",
7
7
  "candidate": {
8
- "pythonHwpx": "5.4.0",
8
+ "pythonHwpx": "5.5.0",
9
9
  "canonicalDistribution": "python-hwpx-automation",
10
- "canonicalAutomation": "6.4.2",
10
+ "canonicalAutomation": "6.5.1",
11
11
  "compatibilityDistribution": "hwpx-mcp-server",
12
- "compatibility": "6.4.2",
13
- "plugin": "1.4.0",
14
- "contractHash": "dbdbdfaac26148b7"
12
+ "compatibility": "6.5.1",
13
+ "plugin": "1.5.0",
14
+ "contractHash": "f61d2c60c0aa0413"
15
15
  },
16
16
  "currentPublic": {
17
- "pythonHwpx": "5.3.0",
17
+ "pythonHwpx": "5.5.0",
18
18
  "primaryDistribution": "python-hwpx-automation",
19
- "primaryApplication": "6.3.1",
20
- "plugin": "1.3.0",
21
- "contractHash": "236f8ea855c875fe"
19
+ "primaryApplication": "6.4.2",
20
+ "plugin": "1.4.1",
21
+ "contractHash": "dbdbdfaac26148b7"
22
22
  },
23
23
  "promotionGate": "Three states are mandatory: unreleased-candidate while auditing; release-approved only after separate owner approval and while currentPublic still names the previously observed coherent stack; released only in a follow-up commit after remote truth is observed for core, canonical automation, the compatibility distribution, the plugin GitHub release, the marketplace entry, and a real marketplace install. The automation tag workflow publishes only release-approved, leaves currentPublic unchanged, and hands an attached receipt to plugin publication."
24
24
  },
@@ -276,6 +276,13 @@ def _host_run_path(record: NodeRecord, selected: Mapping[str, NodeRecord]) -> st
276
276
  run_element = candidate.native.element
277
277
  if parent is run_element or (element is not None and any(child is element for child in run_element)):
278
278
  return candidate.path
279
+ # core 5.5.0+: notes are ctrl-wrapped inside the run (the real-Hancom
280
+ # contract) — the run hosts the wrapper, one hop above the note.
281
+ if element is not None and any(
282
+ child.tag.endswith("}ctrl") and any(grand is element for grand in child)
283
+ for child in run_element
284
+ ):
285
+ return candidate.path
279
286
  return None
280
287
 
281
288
 
@@ -115,15 +115,30 @@ def create_numbering(document: HwpxDocument, properties: dict[str, Any]) -> str:
115
115
  def _move_to_host_run(paragraph: Any, native: Any, host_run: Any) -> None:
116
116
  element = getattr(native, "element", native)
117
117
  temporary_run = None
118
+ movable = element
118
119
  for run in list(paragraph.element):
119
- if _local_name(run) == "run" and any(child is element for child in run):
120
- temporary_run = run
120
+ if _local_name(run) != "run":
121
+ continue
122
+ for child in run:
123
+ if child is element:
124
+ temporary_run = run
125
+ movable = element
126
+ break
127
+ # core 5.5.0+: notes are ctrl-wrapped inside the run (the
128
+ # real-Hancom contract) — relocate the wrapper, not the note.
129
+ if _local_name(child) == "ctrl" and any(
130
+ grandchild is element for grandchild in child
131
+ ):
132
+ temporary_run = run
133
+ movable = child
134
+ break
135
+ if temporary_run is not None:
121
136
  break
122
137
  if temporary_run is not None and temporary_run is not host_run.element:
123
- temporary_run.remove(element)
138
+ temporary_run.remove(movable)
124
139
  if len(temporary_run) == 0:
125
140
  paragraph.element.remove(temporary_run)
126
- host_run.element.append(element)
141
+ host_run.element.append(movable)
127
142
  paragraph.section.mark_dirty()
128
143
  _remove_empty_text_placeholders(host_run)
129
144
 
@@ -1020,6 +1020,57 @@ def _identity_kind(local: str) -> str:
1020
1020
  }.get(local, "shape" if local in _SHAPE_KINDS else local)
1021
1021
 
1022
1022
 
1023
+ def _identity_attribute_names(local: str, node: Any) -> tuple[str, ...]:
1024
+ if local in {"p", "tbl", "pic", "memo", "fieldBegin", *_SHAPE_KINDS}:
1025
+ return tuple(name for name in ("id", "instid", "instId") if node.get(name) is not None)
1026
+ if local in {"footNote", "endNote"}:
1027
+ return tuple(name for name in ("instId", "id") if node.get(name) is not None)
1028
+ return ()
1029
+
1030
+
1031
+ def _refresh_one_identity(
1032
+ node: Any,
1033
+ local: str,
1034
+ allocate: Any,
1035
+ identity_map: list[dict[str, str]],
1036
+ field_ids: dict[str, str],
1037
+ ) -> None:
1038
+ names = _identity_attribute_names(local, node)
1039
+ paired_identity = allocate() if len(names) > 1 and local in {"pic", *_SHAPE_KINDS} else None
1040
+ for name in names:
1041
+ old = str(node.get(name))
1042
+ new = paired_identity or allocate()
1043
+ node.set(name, new)
1044
+ identity_map.append(
1045
+ {"kind": _identity_kind(local), "attribute": name, "old": old, "new": new}
1046
+ )
1047
+ if local == "fieldBegin" and name == "id":
1048
+ field_ids[old] = new
1049
+
1050
+
1051
+ def _walk_refresh_identities(
1052
+ node: Any,
1053
+ inside_note: bool,
1054
+ allocate: Any,
1055
+ identity_map: list[dict[str, str]],
1056
+ field_ids: dict[str, str],
1057
+ ) -> None:
1058
+ """Refresh copyable identities in document order.
1059
+
1060
+ Real-Hancom note bodies keep paragraph id="0" inside their subList (the
1061
+ gold contract) — those are not document-order identities, so nothing
1062
+ inside a note is refreshed. Single-pass recursion keeps this correct on
1063
+ both element backends (lxml proxies break id()-sets).
1064
+ """
1065
+
1066
+ local = _local_name(node)
1067
+ if not inside_note:
1068
+ _refresh_one_identity(node, local, allocate, identity_map, field_ids)
1069
+ child_inside = inside_note or local in {"footNote", "endNote"}
1070
+ for child in node:
1071
+ _walk_refresh_identities(child, child_inside, allocate, identity_map, field_ids)
1072
+
1073
+
1023
1074
  def _refresh_copy_identities(document: HwpxDocument, clone: Any) -> list[dict[str, str]]:
1024
1075
  used: set[str] = set()
1025
1076
  max_numeric = 0
@@ -1043,23 +1094,7 @@ def _refresh_copy_identities(document: HwpxDocument, clone: Any) -> list[dict[st
1043
1094
 
1044
1095
  identity_map: list[dict[str, str]] = []
1045
1096
  field_ids: dict[str, str] = {}
1046
- for node in clone.iter():
1047
- local = _local_name(node)
1048
- names: tuple[str, ...] = ()
1049
- if local in {"p", "tbl", "pic", "memo", "fieldBegin", *_SHAPE_KINDS}:
1050
- names = tuple(name for name in ("id", "instid", "instId") if node.get(name) is not None)
1051
- elif local in {"footNote", "endNote"}:
1052
- names = tuple(name for name in ("instId", "id") if node.get(name) is not None)
1053
- paired_identity = allocate() if len(names) > 1 and local in {"pic", *_SHAPE_KINDS} else None
1054
- for name in names:
1055
- old = str(node.get(name))
1056
- new = paired_identity or allocate()
1057
- node.set(name, new)
1058
- identity_map.append(
1059
- {"kind": _identity_kind(local), "attribute": name, "old": old, "new": new}
1060
- )
1061
- if local == "fieldBegin" and name == "id":
1062
- field_ids[old] = new
1097
+ _walk_refresh_identities(clone, False, allocate, identity_map, field_ids)
1063
1098
  if field_ids:
1064
1099
  for node in clone.iter():
1065
1100
  if _local_name(node) != "fieldEnd":
@@ -419,24 +419,66 @@ class HwpxAgentDocument:
419
419
  note = next(candidate for candidate in notes if candidate.element is child)
420
420
  self._project_note(note, path, kind, object_indexes[kind])
421
421
  elif local == "ctrl":
422
- matches = fields_by_position.get((run_index - 1, child_index), [])
423
- if matches:
424
- for match in matches:
425
- object_indexes["form-field"] += 1
426
- self._project_form_field(match, path, object_indexes["form-field"])
427
- elif len(child) > 0 and all(
428
- _local_name(control_child) in {"fieldEnd", "colPr", "secPr"}
429
- for control_child in child
430
- ):
431
- # A matched FORM field is projected from its fieldBegin. Its paired
432
- # fieldEnd and section/column controls carry no additional subtree
433
- # semantics and must not make an otherwise complete block unsupported.
434
- continue
435
- else:
436
- record.mark_unsupported("ctrl")
422
+ self._project_ctrl_child(
423
+ child, paragraph, path, object_indexes,
424
+ fields_by_position, run_index, child_index, record,
425
+ )
437
426
  elif local not in _IGNORED_INLINE_KINDS:
438
427
  record.mark_unsupported(local)
439
428
 
429
+ def _project_ctrl_child(
430
+ self,
431
+ child: Any,
432
+ paragraph: Any,
433
+ path: SemanticPath,
434
+ object_indexes: Any,
435
+ fields_by_position: Any,
436
+ run_index: int,
437
+ child_index: int,
438
+ record: Any,
439
+ ) -> None:
440
+ if self._project_wrapped_notes(child, paragraph, path, object_indexes):
441
+ return
442
+ matches = fields_by_position.get((run_index - 1, child_index), [])
443
+ if matches:
444
+ for match in matches:
445
+ object_indexes["form-field"] += 1
446
+ self._project_form_field(match, path, object_indexes["form-field"])
447
+ elif len(child) > 0 and all(
448
+ _local_name(control_child) in {"fieldEnd", "colPr", "secPr"}
449
+ for control_child in child
450
+ ):
451
+ # A matched FORM field is projected from its fieldBegin. Its paired
452
+ # fieldEnd and section/column controls carry no additional subtree
453
+ # semantics and must not make an otherwise complete block unsupported.
454
+ return
455
+ else:
456
+ record.mark_unsupported("ctrl")
457
+
458
+ def _project_wrapped_notes(
459
+ self, ctrl: Any, paragraph: Any, path: SemanticPath, object_indexes: Any
460
+ ) -> bool:
461
+ """Project ctrl-wrapped footnotes/endnotes (the real-Hancom contract).
462
+
463
+ Returns True when the ctrl hosted at least one note — core 5.5.0+
464
+ wraps notes in ``<hp:ctrl>`` inside the run.
465
+ """
466
+
467
+ projected = False
468
+ for control_child in ctrl:
469
+ note_local = _local_name(control_child)
470
+ if note_local not in {"footNote", "endNote"}:
471
+ continue
472
+ kind = "footnote" if note_local == "footNote" else "endnote"
473
+ object_indexes[kind] += 1
474
+ notes = paragraph.footnotes if kind == "footnote" else paragraph.endnotes
475
+ note = next(
476
+ candidate for candidate in notes if candidate.element is control_child
477
+ )
478
+ self._project_note(note, path, kind, object_indexes[kind])
479
+ projected = True
480
+ return projected
481
+
440
482
  def _project_table(self, paragraph: Any, element: Any, parent: SemanticPath, index: int) -> None:
441
483
  table = next(candidate for candidate in paragraph.tables if candidate.element is element)
442
484
  identity = _identity(element, "id")
@@ -1894,6 +1894,7 @@ def _normalize_block(raw_block: Any, *, index: int) -> DocumentBlock:
1894
1894
  "caption": caption,
1895
1895
  "columns": columns,
1896
1896
  "rows": rows,
1897
+ **_plan_table_header_extras(raw_block),
1897
1898
  }
1898
1899
  if unit:
1899
1900
  table_data["unit"] = unit
@@ -2268,9 +2269,14 @@ def _block_to_builder_nodes(block: DocumentBlock) -> tuple[Any, ...]:
2268
2269
  caption = str(block.data.get("caption") or "").strip()
2269
2270
  if caption:
2270
2271
  nodes.append(BuilderParagraph(text=caption, style="heading"))
2272
+ show_header = block.data.get("showHeader") is not False
2271
2273
  nodes.append(
2272
2274
  BuilderTable(
2273
- header=tuple(str(column["label"]) for column in columns),
2275
+ header=(
2276
+ tuple(str(column["label"]) for column in columns)
2277
+ if show_header
2278
+ else ()
2279
+ ),
2274
2280
  rows=tuple(
2275
2281
  tuple(str(row.get(column["key"], "")) for column in columns)
2276
2282
  for row in rows
@@ -2310,6 +2316,14 @@ def _plan_table_column_widths(columns: list[dict[str, Any]]) -> list[int]:
2310
2316
  return widths
2311
2317
 
2312
2318
 
2319
+ def _plan_table_header_extras(raw_block: Mapping[str, Any]) -> dict[str, Any]:
2320
+ """Carry the additive header-suppression flag (section chips, title bands)."""
2321
+
2322
+ if raw_block.get("showHeader") is False:
2323
+ return {"showHeader": False}
2324
+ return {}
2325
+
2326
+
2313
2327
  def _normalize_columns(value: Any, *, index: int) -> list[dict[str, Any]]:
2314
2328
  if not isinstance(value, list) or not value:
2315
2329
  raise ValueError(f"blocks[{index}].columns must be a non-empty list")
@@ -2578,7 +2592,9 @@ def _add_builder_table(
2578
2592
  table_node: BuilderTable,
2579
2593
  tokens: Mapping[str, str],
2580
2594
  ) -> None:
2581
- rows = [list(table_node.header), *(list(row) for row in table_node.rows)]
2595
+ rows = (
2596
+ [list(table_node.header)] if table_node.header else []
2597
+ ) + [list(row) for row in table_node.rows]
2582
2598
  if not rows:
2583
2599
  raise ValueError("table must contain a header or at least one row")
2584
2600
  column_count = max(len(row) for row in rows)
@@ -126,8 +126,64 @@ class _BuilderPreset:
126
126
  return default_chars[level % len(default_chars)]
127
127
 
128
128
 
129
+ def _genre_preset(genre: str) -> "_BuilderPreset":
130
+ """Preset driven by the house-style bank — no literal typography here."""
131
+
132
+ from ...house_style import load_bank, load_genres
133
+
134
+ catalog = load_genres()
135
+ genres = catalog.genres if hasattr(catalog, "genres") else {}
136
+ if genre not in genres:
137
+ raise ValueError(f"unknown genre preset: {genre!r}")
138
+ entry = genres[genre]
139
+ payload = entry if isinstance(entry, dict) else entry.model_dump()
140
+ typography = payload.get("typography") or {}
141
+ inherits = str(typography.get("inherits") or "")
142
+ bank = load_bank()
143
+ profiles = bank.profiles if hasattr(bank, "profiles") else {}
144
+ base = profiles.get(inherits) if inherits else None
145
+ base_payload = base if isinstance(base, dict) or base is None else base.model_dump()
146
+ return _GenreBankPreset(
147
+ name=f"genre:{genre}",
148
+ roles={**((base_payload or {}).get("roles") or {}), **(typography.get("roles") or {})},
149
+ )
150
+
151
+
152
+ @dataclass(frozen=True)
153
+ class _GenreBankPreset(_BuilderPreset):
154
+ """Typography sourced from the packaged bank/genre data."""
155
+
156
+ roles: Mapping[str, Any] = field(default_factory=dict)
157
+
158
+ def _role(self, *names: str) -> Mapping[str, Any]:
159
+ for name in names:
160
+ value = self.roles.get(name)
161
+ if isinstance(value, Mapping):
162
+ return value
163
+ return {}
164
+
165
+ def heading_style(self, level: int) -> dict[str, Any]:
166
+ role = self._role("section_header", "section_chip_title", "title")
167
+ return {
168
+ "bold": True,
169
+ "size": int(role.get("sizePt") or (16 - (level - 1) * 2)),
170
+ "font": str(role.get("font") or "함초롬바탕"),
171
+ }
172
+
173
+ def run_style(self, run: "Run") -> dict[str, Any]:
174
+ styles = super().run_style(run)
175
+ body = self._role("body")
176
+ if styles.get("font") is None and body.get("font"):
177
+ styles["font"] = str(body["font"])
178
+ if styles.get("size") is None and body.get("sizePt"):
179
+ styles["size"] = int(body["sizePt"])
180
+ return styles
181
+
182
+
129
183
  def _builder_preset(value: str | None) -> _BuilderPreset:
130
184
  normalized = (value or "default").strip().lower().replace("-", "_")
185
+ if normalized.startswith("genre:"):
186
+ return _genre_preset(normalized.split(":", 1)[1])
131
187
  if normalized in {"", "default", "standard", "standard_korean_business"}:
132
188
  return _BuilderPreset()
133
189
  if normalized in {"government_report", "gov_report", "공문보고서"}:
@@ -26,25 +26,23 @@ class SectionChipPlan(BaseModel):
26
26
  return normalized
27
27
 
28
28
  def to_document_plan_block(self) -> dict[str, Any]:
29
- metadata: dict[str, Any] = {
30
- "generator": "section_chip",
31
- "style": self.style,
32
- "number": self.number,
33
- }
34
- if self.accent_color:
35
- metadata["accentColor"] = self.accent_color
36
29
  if self.style == "inline":
37
30
  return {
38
31
  "type": "heading",
39
32
  "level": 1,
40
33
  "text": f"{self.number} {self.title}",
41
- "metadata": metadata,
42
34
  }
43
35
  return {
44
36
  "type": "table",
45
- "rows": [[self.number, "", self.title]],
46
- "columnWidths": [1, 0.4, 11],
47
- "metadata": metadata,
37
+ "showHeader": False,
38
+ "columns": [
39
+ {"key": "number", "label": "번호", "widthWeight": 2},
40
+ {"key": "gap", "label": "여백", "widthWeight": 1},
41
+ {"key": "title", "label": "제목", "widthWeight": 15},
42
+ ],
43
+ "rows": [
44
+ {"number": self.number, "gap": "", "title": self.title}
45
+ ],
48
46
  }
49
47
 
50
48