Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,29 @@

## [Unreleased]

## [3.1.0] - 2026-07-16

### 추가
- **타입드 혼합 양식 계획기**: `hwpx.mixed-form-plan/v1`이 네이티브 필드, 고유 라벨 셀,
revision-bound canonical path, 본문 직접 단일-run anchor를 하나의 엄격한 계획으로 받습니다.
모든 대상을 먼저 canonical path로 해석한 뒤 기존 `hwpx.agent-batch/v1` 한 건으로 컴파일하며,
`apply_document_commands`만 실행기로 사용해 dry-run·rollback·idempotency·단일 저장 게이트를
그대로 보장합니다.
- 병합 셀의 논리 좌표를 실제 `cellAddr` 앵커로 정규화하는 비변형 `resolve_cell_target` 계획
프리미티브와 공개 plan/compiled-plan JSON Schema를 추가했습니다.

### 변경
- 에이전트 batch JSON Schema를 정본 카탈로그에 추가하고 quality 객체의 enum·boolean 필드를
엄격하게 검증해 알 수 없는 값이 실행 단계까지 전달되지 않도록 했습니다.
- 혼합 양식 출력이 입력과 동일한 resolved path·symlink·hardlink인 경우 계획과 적용 직전에
거부하고, 셀 라벨은 정규화된 정확 일치만 허용합니다. 컴파일 계획은 locator별 node/path/
section/좌표 불변식을 검증하며, 멱등성 식별자에 공개 locator 요청 hash를 포함합니다.

### 검증
- 합성 1쪽 한국어 혼합 양식으로 네 locator 동시 적용, 드라이런 무출력, 주입 실패 rollback,
idempotent replay/conflict, 미변경 OPC member 바이트 보존, 재열기와 `openSafety.ok`를 고정했습니다.
`evalplan` 회귀는 유지하며 `exam` 모듈은 혼합 양식 경로에 가져오지 않습니다.

## [3.0.0] - 2026-07-16

### 변경
Expand Down
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<p align="center">
<h1 align="center">python-hwpx</h1>
<p align="center">
<strong>한글 없이 HWPX 문서를 Python으로 읽고, 편집하고, 생성하고, 검증합니다.</strong>
<strong>한글 없이 HWPX 문서를 Python으로 읽고, 편집하고, 생성하고, 구조를 검증합니다.</strong>
</p>
<p align="center">
<a href="https://pypi.org/project/python-hwpx/"><img src="https://img.shields.io/pypi/v/python-hwpx?color=blue&label=PyPI" alt="PyPI"></a>
Expand All @@ -19,15 +19,26 @@
|---|---|---|
| 📦 라이브러리 | **[`python-hwpx`](https://github.com/airmang/python-hwpx)** | 순수 파이썬 HWPX 파싱·편집·생성 코어 |
| 🔌 MCP 서버 | [`hwpx-mcp-server`](https://github.com/airmang/hwpx-mcp-server) | MCP 클라이언트(Claude Desktop, VS Code 등)에서 HWPX 조작 |
| 🎯 에이전트 스킬 | [`hwpx-skill`](https://github.com/airmang/hwpx-plugins) | 에이전트가 HWPX를 바로 쓰게 해주는 공식 온보딩 스킬 |
| 🎯 에이전트 스킬 | [`hwpx-plugin`](https://github.com/airmang/hwpx-plugins) | 에이전트가 HWPX를 바로 쓰게 해주는 first-party 플러그인·스킬 번들 |

`python-hwpx`는 HWPX 파싱·편집·생성을 제공하는 코어 라이브러리이며,
`hwpx-mcp-server`와 `hwpx-plugin`은 같은 프로젝트가 직접 유지보수하는 first-party 연동 구성요소입니다.
“first-party”는 프로젝트 유지보수 관계를 뜻하며, 한컴 또는 제3자의 공식 인증을 뜻하지 않습니다.

현재 공개 릴리스와 최소 호환 코어 버전은 모두 `python-hwpx 3.1.0`입니다.
현재 패키지 분류는 `Development Status :: 3 - Alpha`입니다. 이 분류는 API와 제품의
성숙도를 나타내며, 공개 버전이나 플러그인의 최소 호환 버전을 대신하지 않습니다.

---

## 왜 python-hwpx인가

- **한컴오피스 설치 불필요** — HWPX는 ZIP+XML(OWPML/OPC) 구조라, 순수 파이썬으로 Windows·macOS·Linux·CI 어디서나 읽고 씁니다.
- **코어 편집에 한컴오피스 설치 불필요** — HWPX는 ZIP+XML(OWPML/OPC) 구조라, 순수 파이썬으로 Windows·macOS·Linux·CI 어디서나 읽고 씁니다.
- **읽기부터 생성까지 한 코어** — 텍스트/서식 추출, 문단·표·양식 편집, 새 문서 생성, XSD 스키마 검증을 하나의 API로 처리합니다.
- **에이전트·자동화 친화** — 같은 스택 위에서 `hwpx-mcp-server`와 공식 스킬이 직결됩니다.
- **에이전트·자동화 친화** — 같은 프로젝트가 유지보수하는 `hwpx-mcp-server`와 `hwpx-plugin`이 코어에 연결됩니다.

문서 파싱·편집·생성은 순수 Python으로 수행할 수 있습니다. 다만 페이지 나눔, 표 넘침,
글꼴 대체 등 최종 시각 품질을 확언하려면 필요에 따라 실제 한컴 렌더 오라클을 별도로 사용합니다.

## 빠른 시작

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "python-hwpx"
version = "3.0.0"
version = "3.1.0"
description = "한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 자동화 라이브러리"
readme = { file = "README.md", content-type = "text/markdown" }
license = "Apache-2.0"
Expand Down
26 changes: 26 additions & 0 deletions src/hwpx/agent/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@
from .catalog import agent_catalog, agent_json_schemas, catalog_hash, human_help, node_help
from .commands import apply_document_commands
from .document import HwpxAgentDocument, NodeRecord
from .form_plan import (
MIXED_FORM_COMPILED_PLAN_SCHEMA,
MIXED_FORM_LOCATOR_KINDS,
MIXED_FORM_PLAN_SCHEMA,
MIXED_FORM_REQUEST_SCHEMA,
MixedFormPlan,
MixedFormResolution,
apply_mixed_form_fill,
apply_mixed_form_plan,
mixed_form_json_schemas,
plan_mixed_form_fill,
validate_mixed_form_plan,
validate_mixed_form_request,
)
from .path import PathSegment, SemanticPath, canonicalize_path, parse_path
from .query import QueryResult, SemanticSelector, parse_selector
from .blueprint import (
Expand Down Expand Up @@ -52,6 +66,10 @@
"AGENT_COMMAND_SCHEMA",
"AGENT_ERROR_SCHEMA",
"AGENT_NODE_SCHEMA",
"MIXED_FORM_COMPILED_PLAN_SCHEMA",
"MIXED_FORM_LOCATOR_KINDS",
"MIXED_FORM_PLAN_SCHEMA",
"MIXED_FORM_REQUEST_SCHEMA",
"BLUEPRINT_CATALOG_SCHEMA",
"BLUEPRINT_REPLAY_RESULT_SCHEMA",
"BLUEPRINT_REPLAY_SCHEMA",
Expand All @@ -65,6 +83,8 @@
"BlueprintBundle",
"BlueprintDumpResult",
"HwpxAgentDocument",
"MixedFormPlan",
"MixedFormResolution",
"NodeRecord",
"PathSegment",
"QueryResult",
Expand All @@ -74,6 +94,8 @@
"agent_json_schemas",
"agent_contract_manifest",
"apply_document_commands",
"apply_mixed_form_fill",
"apply_mixed_form_plan",
"blueprint_catalog",
"blueprint_catalog_hash",
"blueprint_hash",
Expand All @@ -84,15 +106,19 @@
"canonicalize_path",
"catalog_hash",
"human_help",
"mixed_form_json_schemas",
"node_help",
"parse_path",
"parse_selector",
"plan_mixed_form_fill",
"dump_document_blueprint",
"read_blueprint_bundle",
"repack_blueprint_bundle",
"replay_document_blueprint",
"validate_agent_batch",
"validate_agent_command",
"validate_mixed_form_plan",
"validate_mixed_form_request",
"validate_blueprint_manifest",
"validate_replay_request",
"write_blueprint_bundle",
Expand Down
85 changes: 85 additions & 0 deletions src/hwpx/agent/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@
from typing import Any

from .model import (
AGENT_BATCH_SCHEMA,
AGENT_CATALOG_SCHEMA,
MAX_COMMANDS,
AgentContractError,
NODE_KINDS,
NODE_PROPERTY_CATALOG_V1,
QUALITY_KEYS,
QUALITY_MODES,
REVISION_PATTERN,
VERIFICATION_REQUIREMENTS,
agent_contract_manifest,
)

Expand Down Expand Up @@ -78,6 +84,22 @@ def agent_json_schemas() -> dict[str, Any]:
"properties": {key: value for key, value in properties.items() if key in allowed},
}
)
quality_object = {
"type": "object",
"additionalProperties": False,
"properties": {
"mode": {"enum": list(QUALITY_MODES)},
"renderCheck": {"enum": ["off", "auto", "required"]},
"xsdMode": {"enum": ["off", "lint"]},
"overflowPolicy": {"enum": ["fail", "warn", "truncate"]},
"layoutLint": {"enum": ["off", "warn", "strict"]},
"preserveUnmodifiedParts": {"type": "boolean"},
"requireReferenceIntegrity": {"type": "boolean"},
},
}
if set(quality_object["properties"]) != set(QUALITY_KEYS): # pragma: no cover
raise AssertionError("quality JSON Schema drifted from QUALITY_KEYS")

return {
"node": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
Expand Down Expand Up @@ -113,6 +135,69 @@ def agent_json_schemas() -> dict[str, Any]:
"title": "HwpxAgentCommand v1",
"oneOf": command_variants,
},
"batch": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "HwpxAgentBatch v1",
"type": "object",
"required": [
"schemaVersion",
"input",
"output",
"commands",
"expectedRevision",
"idempotencyKey",
"dryRun",
"quality",
"verificationRequirements",
],
"additionalProperties": False,
"properties": {
"schemaVersion": {"const": AGENT_BATCH_SCHEMA},
"input": {
"type": "object",
"required": ["filename"],
"additionalProperties": False,
"properties": {"filename": {"type": "string", "minLength": 1}},
},
"output": {
"type": "object",
"required": ["filename", "overwrite"],
"additionalProperties": False,
"properties": {
"filename": {"type": "string", "minLength": 1},
"overwrite": {"type": "boolean"},
},
},
"commands": {
"type": "array",
"minItems": 1,
"maxItems": MAX_COMMANDS,
"items": {"oneOf": deepcopy(command_variants)},
},
"expectedRevision": {
"type": ["string", "null"],
"pattern": REVISION_PATTERN.pattern,
},
"idempotencyKey": {
"type": ["string", "null"],
"minLength": 1,
"maxLength": 128,
},
"dryRun": {"type": "boolean"},
"quality": {
"oneOf": [
{"enum": list(QUALITY_MODES)},
deepcopy(quality_object),
{"type": "null"},
]
},
"verificationRequirements": {
"type": "array",
"items": {"enum": list(VERIFICATION_REQUIREMENTS)},
"uniqueItems": True,
},
},
},
"queryInput": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
Expand Down
Loading
Loading