Skip to content

에이전트 문서 조작 커널 1층 — 문서 선택자 언어(DSEL) 신설 (#4875) - #4878

Closed
kevin9327 wants to merge 1 commit into
edwardkim:develfrom
kevin9327:agent_op_kernel
Closed

에이전트 문서 조작 커널 1층 — 문서 선택자 언어(DSEL) 신설 (#4875)#4878
kevin9327 wants to merge 1 commit into
edwardkim:develfrom
kevin9327:agent_op_kernel

Conversation

@kevin9327

Copy link
Copy Markdown
Contributor

무엇

에이전트 문서 조작 커널의 1층 — 문서 선택자 언어(DSEL) 를 라이브러리
표면(rhwp::agent::dsel)에 세운다. CSS 선택자를 rhwp IR 에 맞춰 좁힌 문법으로
"문서의 어디"를 값으로 만든다.

let sel = dsel::parse("section:nth(2) > table:last cell[row=-1]")?;
for node in dsel::select(&sel, &document)? {
    println!("{} = {:?}", node.id, node.node.text());
}
// /section[2]/para[7]/control[0]/cell[11] = Some("합계")

이슈: #4875 · 처리결과 문서: mydocs/report/task_dsel_selector_language.md

왜 — 지금은 지목 문법이 명령마다 다르다

에이전트가 문서를 고치는 길은 edit 하위 명령 6개가 전부이고, 여섯이 각자 다른
방식으로 대상을 지목한다.

하위 명령 대상 지목
fill-fields --data 이름=값, 같은 이름은 [k]
replace-text --find + --occurrence
set-cell --table / --row / --col
insert-image --page / --x / --y
redact --kind
sanitize (대상 없음)

즉 "문서의 어디"를 가리키는 공통 개념이 없다. "3절 두 번째 표의 마지막 행 전부"를
표현할 문법 자체가 없고, 여섯 명령이 미리 뚫어 둔 구멍 밖은 손댈 수 없다.

전 / 후

— 명령마다 다른 플래그, 미리 뚫린 구멍 안에서만:

$ rhwp edit set-cell doc.hwp --table 1 --row 3 --col 2 --text "합계"
# "3절 두 번째 표의 마지막 행" → 표현할 방법이 없다
# "합계가 든 표의 3열 전부"    → 표현할 방법이 없다
# "빈 문단이 아닌 개요 2 문단"  → 표현할 방법이 없다

— 하나의 지목 문법, 명령과 분리된 값:

dsel::parse("section:nth(2) > table:last cell[row=-1]")?;
dsel::parse(r#"table:has(cell:contains("합계")) cell[col=3]"#)?;
dsel::parse("para[styleId=2]:not(:empty)")?;
dsel::parse(r#"field[name~="수급자*"]"#)?;

진단도 값이다 — 위치·기대 목록·수복 힌트가 함께 나온다:

축 `para` 에 없는 속성 `rows` — 기대: controls | empty | index | len | shapeId | styleId | text
para[rows>1]
     ^

무엇이 들어 있나

  • 축 16종section para run control table cell picture
    equation field footnote endnote header footer bookmark
    hyperlink shape (+ *). 전부 IR 의 실제 노드 종류이며 지어낸 층은 없다.
  • 결합자 4종 — 자손(공백) · 직계(>) · 다음 형제(+) · 이후 형제(~)
  • 비교자 10종= != > < >= <= ^= $= *= ~=
  • 의사 선택자 9종first last nth range contains matches
    empty not has
  • 노드 주소/section[0]/para[3]/control[0]/cell[5]. 참조가 아니라 값이라
    편집을 사이에 두고 살아남는다(앵커 층의 전제). 사전식 순서가 곧 문서 순서다.

설계 판단 (근거는 각 모듈 문서에)

  1. 사전은 하나뿐 — 축·속성·의사 선택자 목록이 ast.rs 상수 한 벌이고
    파서·평가기·진단이 그것만 읽는다. 축 계층(tablecontrol)도 데이터로
    적어 ontologyrdfs:subClassOf 를 유도할 때 손으로 다시 적지 않게 했다.
    celltable 의 특수화가 아닌 것(포함이지 특수화가 아님)을 테스트가
    고정한다.
  2. 파싱 중에 의미까지 검사 — 축·속성·연산자 적합성을 파싱 단계에서 확인한다.
    평가에서 거절하면 오류 위치가 "이 선택자 어딘가"로 뭉개진다.
  3. 위치 의사 선택자는 결과 집합 기준, [index] 는 형제 기준table:last
    "문서에서 마지막 표"다. 두 기준을 다른 문법에 두어 어느 쪽인지 항상 보이게 했다.
    값 술어를 위치 술어보다 먼저 적용한다(table:last[rows>2] 의 뜻이 갈리는 자리).
  4. 없는 값은 어떤 조건도 만족하지 않는다!= 도 포함. cell[name!="합계"]
    가 이름 없는 셀을 전부 고르면 편집이 새어 나간다.
  5. 결합자는 부모 색인 비교로 환원 — 문서를 문서 순서로 한 번 펼친 뒤 접는다.
    형제 판정은 종류까지 본다(컨트롤 다음의 구간이 "다음 형제"가 되면, 글자
    모양이 하나 바뀔 때마다 같은 선택자가 다른 것을 고른다).
  6. 정규식 없음 — 의존성이 늘고, 역추적 정규식은 지수 시간으로 터진다.
    선택자가 맞대는 값은 문서에서 오고 문서는 신뢰 경계 밖이다. 대신 역추적
    지점이 * 하나뿐인 글롭 — 최악 O(패턴 × 입력) 유계, 지수 경로 없음.

정지성·손상 입력

선택자는 모델이 만들고 문서에서 온 값과 맞대어진다. 양쪽 다 신뢰 경계 밖이므로
상한 8종을 전부 테스트로 고정했다.

상한
파싱 원문 길이 / 중첩 / 합집합 / 스텝 / 술어 4096자 / 8 / 64 / 32 / 16
평가 노드 / 깊이 / 결과 200,000 / 64 / 50,000

중첩 깊이는 파서와 평가기 양쪽에서 막는다 — 파서를 거치지 않은 AST(계획서에서
역직렬화한 선택자)로도 평가가 불릴 수 있다.

문단을 구간으로 자르는 경로가 실물 손상 문서를 만난다. 자를 수 없으면 잘못
자르는 대신 구간을 만들지 않는다.

손상 처리
char_offsets 길이 불일치 범위를 조인다
start_pos 비단조 뒤집힌 구간을 건너뛴다(역방향 슬라이스 = 패닉)
start_pos ≠ 0 0 으로 끌어내린다(앞부분 유실 방지)
짝 없는 서로게이트 렉싱에서 힌트와 함께 거절
정수 오버플로 한국어 진단으로 거절

전 구간이 char 단위다 — 바이트 인덱스로 돌면 한글 선택자에서 캐럿이 글자
중간을 가리키고 슬라이싱이 byte index is not a char boundary 로 패닉한다.

신뢰 경계

문서에서 읽은 문자열이 선택자 문법으로 재해석되는 경로는 없다. 문서에
para:has(...) 라고 적혀 있어도 그건 그냥 글자다. 테스트가 고정한다
(document_derived_text_is_never_reinterpreted_as_syntax).

범위

  • 이번 층은 라이브러리 표면까지다. CLI·MCP 명령 표면은 커널이 더 서야
    붙일 수 있으므로 후속 이슈로 쌓는다.
  • 기존 코드 변경은 src/lib.rspub mod agent; 한 줄뿐이다. 기존 동작을
    건드리는 곳이 없다.

검증

cargo test --lib agent::           116 passed; 0 failed
cargo test --lib                   3818 passed; 0 failed; 13 ignored  (회귀 0)
cargo clippy --lib --all-targets   무경고
rustfmt --check                    통과
git merge-tree origin/devel HEAD   무충돌

릴리스 프로파일 전수는 로컬에서 별도 실행 중이며, 결과는 이 PR 코멘트로 잇는다.

신규 4,611 줄 / 단위 테스트 116 건.

파일
agent/dsel/eval.rs 1,006
agent/dsel/ast.rs 946
agent/dsel/parse.rs 804
agent/dsel/node.rs 561
agent/dsel/lex.rs 364
agent/dsel/glob.rs 346
agent/dsel/error.rs 216
agent/dsel/token.rs 171
agent/dsel/mod.rs 112
agent/mod.rs 85

시각 산출물이 없는 라이브러리 층이라 전/후는 위의 코드·진단 비교로 갈음했다.

다음

연산 대수(2층) → 앵커(3층) → 검증·트랜잭션(4·5층) → 정책·계보(6·7층) →
매니페스트(8층). 각각 별도 이슈로 쌓는다. 에이전트 축 모듈 10개
(main.rsmod)를 pub mod 로 승격해 bindings/Native·tools/*·wasm 이
링크할 수 있게 하는 것도 후속이다.

에이전트가 문서를 고치는 길은 `edit` 하위 명령 6개가 전부이고, 여섯이 각자 다른
방식으로 대상을 지목한다(`--table/--row/--col`, `--data 이름=값[k]`,
`--find/--occurrence`). "문서의 어디"를 가리키는 공통 개념이 없어서 "3절 두 번째
표의 마지막 행"을 표현할 문법 자체가 없다.

CSS 선택자를 rhwp IR 에 맞춰 좁힌 지목 언어를 라이브러리 표면에 세운다.

- 축 16종 — 전부 IR 의 실제 노드 종류. 지어낸 층 없음
- 결합자 4종, 비교자 10종, 의사 선택자 9종
- 노드 주소 `/section[0]/para[3]/cell[5]` — 참조가 아니라 값이라 편집을
  사이에 두고 살아남는다(앵커 층의 전제). 사전식 순서가 곧 문서 순서
- 축·속성 사전은 한 벌뿐이고 파서·평가기·진단이 그것만 읽는다. 축 계층도
  데이터로 적어 ontology 가 subClassOf 를 유도할 수 있게 했다
- 파싱 단계에서 축·속성·연산자 적합성까지 검사하고, 진단에 위치·기대 목록·
  수복 힌트를 값으로 싣는다
- 정규식 대신 역추적 지점이 `*` 하나뿐인 글롭 — 최악 O(패턴×입력) 유계.
  선택자가 맞대는 값은 문서에서 오고 문서는 신뢰 경계 밖이다
- 상한 8종(파싱 5·평가 3)을 전부 테스트로 고정. 중첩 깊이는 파서와 평가기
  양쪽에서 막는다 — 파서를 거치지 않은 AST 로도 평가가 불릴 수 있다
- 손상 입력(어긋난 char_offsets, 비단조 start_pos, 짝 없는 서로게이트)에서
  패닉 없이 진단으로 끝난다

기존 코드 변경은 `src/lib.rs` 에 `pub mod agent;` 한 줄뿐이다.

신규 4,611줄 / 단위 테스트 116건 / clippy 무경고 / rustfmt 통과.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@jangster77

Copy link
Copy Markdown
Collaborator

통합 PR #4883(4412546)로 병합 완료했습니다.

원 head와 CI를 다시 확인해 누적 반영했고, 상세 검토·메인터너 보정·검증 근거는 archive 검토 기록에 남겼습니다.

중복 병합을 막기 위해 이 원 PR을 닫습니다. 감사합니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants