@g1cloud/bpmn-modeler-next 5.0.0-alpha.4 → 5.0.0-alpha.40

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.
@@ -1,6 +1,6 @@
1
1
  import { BpmnDocumentModelerLike } from '../core/modelerLike';
2
2
  import { SymbolicOp } from './symbolicOp';
3
- import { ResolveIssue } from './resolver';
3
+ import { ApplyBpmnOpsResult, ResolveIssue } from './resolver';
4
4
  import { GeometryFinding } from './geometryAudit';
5
5
  /** op 출처 — 감사·정책 분기용. `gui` = 호스트 채팅 패널, `rest` = 외부 브리지. */
6
6
  export type BpmnOpOrigin = 'agent' | 'gui' | 'rest';
@@ -41,6 +41,8 @@ export type ApplyBatchOutcome =
41
41
  version: number;
42
42
  xml: string;
43
43
  findings: GeometryFinding[];
44
+ /** 간선 교차 계측(B-14 ⓐ) — 문서 전체, 하강 전/후. finding 이 아니다. */
45
+ stats: ApplyBpmnOpsResult['stats'];
44
46
  } | {
45
47
  status: 'conflict';
46
48
  baseVersion: number;
@@ -16,6 +16,37 @@ export interface GeometryShape {
16
16
  container: boolean;
17
17
  /** 경계 이벤트처럼 **호스트에 붙는 것이 정상**인 도형 — 겹침 판정에서 제외된다. */
18
18
  attached: boolean;
19
+ /**
20
+ * 이 도형을 **의미론적으로** 담는 컨테이너 id(참여자 또는 확장 서브프로세스) — 없으면 루트.
21
+ * 좌표가 아니라 모델에서 온다(DI 는 프로세스 소속을 말하지 않는다). `shape-outside-container`
22
+ * 판정의 근거: 노드가 자기 풀 밖에 그려져 있으면 좌표만 보는 사람은 **남의 풀 노드로 읽는다.**
23
+ */
24
+ parent?: string;
25
+ /**
26
+ * 도형의 **외부 이름 라벨** bounds(이벤트·게이트웨이의 이름은 도형 밖에 그려진다 — 있을 때만).
27
+ * `annotation-over-label` 의 입력(B-24 ⓑ): 주석 상자가 이 위에 놓이면 텍스트가 겹쳐 둘 다 읽을 수 없다.
28
+ *
29
+ * ⚠ **블랙박스 풀은 예외적으로 "움직일 수 없는" 라벨을 갖는다**(B-38) — `processRef` 없는 참여자는
30
+ * bpmn-js 가 이름을 풀 **본체 중앙에 가로로** 그리고(`isExpanded` = `!!processRef` → 렌더러 else 분기,
31
+ * `align: 'center-middle'`), 그 좌표는 DI 에 `BPMNLabel` 로 남지 않는 **렌더 산물**이다. 위치가 폭의
32
+ * 함수라 라벨을 옮겨서는 못 고치고 **풀 기하를 바꿔야** 한다. 라벨을 이동시키는 보정
33
+ * (`repairIntroducedShapeLabelLines`)은 `container` 로 이미 걸러진다.
34
+ */
35
+ label?: GeometryLabel;
36
+ /**
37
+ * 그룹의 멤버 id 목록(`bwl:members` 힌트, A-4 — 그룹일 때만). `group-overlap-unintended` 의 입력:
38
+ * 두 그룹의 바운즈가 겹칠 때 **멤버를 공유하면 의도된 겹침**(한 노드가 두 단계에 걸친다)이고,
39
+ * 공유하지 않으면 **그리다 보니 겹친 것**이다. 힌트가 없는 그룹(사람이 GUI 로 그린 것)은 판단할
40
+ * 근거가 없으므로 판정하지 않는다.
41
+ */
42
+ members?: readonly string[];
43
+ }
44
+ /** 간선 외부 라벨의 bounds — 라벨은 판정 대상 도형이 아니지만 **어느 간선의 것으로 읽히는가**는 판정한다(B-15). */
45
+ export interface GeometryLabel {
46
+ x: number;
47
+ y: number;
48
+ width: number;
49
+ height: number;
19
50
  }
20
51
  export interface GeometryEdge {
21
52
  id: string;
@@ -23,6 +54,8 @@ export interface GeometryEdge {
23
54
  waypoints: GeometryPoint[];
24
55
  /** 이 간선이 자기 끝점으로 삼는 도형 id 들 — 그 도형과의 교차는 도킹이라 관통이 아니다. */
25
56
  endpoints: readonly string[];
57
+ /** 이름이 있는 간선의 외부 라벨 bounds(있을 때만). `label-on-shared-segment` 의 입력. */
58
+ label?: GeometryLabel;
26
59
  }
27
60
  /** 판정기가 대면하는 최소 입력 — 좌표만 담는다(의미론 없음). */
28
61
  export interface GeometryScene {
@@ -47,9 +80,237 @@ export type GeometryFinding =
47
80
  kind: 'shape-overlap';
48
81
  shapes: [string, string];
49
82
  identical: boolean;
83
+ }
84
+ /**
85
+ * 도형이 자기 컨테이너(풀·확장 서브프로세스) 경계 밖으로 나가 있다. 원인은 컨테이너 크기가
86
+ * 내용물을 따라 늘지 않은 것(B-12 실측: 풀 높이가 band 수를 몰라 band 2 노드가 아래 풀 안에
87
+ * 놓였다). 작은 도형(이벤트)은 아무것과도 겹치지 않아 **관통·겹침 축은 침묵**하는데, 사람은
88
+ * 그 노드를 다른 풀의 것으로 읽는다 — 의미론이 조용히 뒤바뀌는 클래스라 따로 잡는다.
89
+ */
90
+ | {
91
+ kind: 'shape-outside-container';
92
+ shape: string;
93
+ container: string;
94
+ }
95
+ /**
96
+ * 두 간선의 축 정렬 구간이 **같은 선 위에 겹치는데 진행 방향이 반대**다 — 한 선처럼 보이는데
97
+ * 화살표가 양쪽 끝에 달려 어디서 어디로 가는지 읽을 수 없다(확정 6 네 번째 표본에서 사용자가
98
+ * 지목한 "문제가 되는 겹침" — 같은 방향으로 합류·팬아웃하는 겹침은 문제가 아니라 제외한다).
99
+ * 전형 = 되돌아가는 간선이 주 줄의 정방향 간선 위를 거꾸로 달리는 것, 같은 열을 반대로 오르내리는
100
+ * 메시지플로우 둘. `length` = 겹친 길이(px).
101
+ */
102
+ | {
103
+ kind: 'edge-overlap-opposite';
104
+ edges: [string, string];
105
+ axis: 'h' | 'v';
106
+ length: number;
107
+ }
108
+ /**
109
+ * 두 간선의 축 정렬 구간이 같은 선 위에 **같은 방향으로** 겹치는데 **종류가 다르다**(실선 시퀀스 ×
110
+ * 점선 메시지 · 점선 연관) — 일곱 번째 표본에서 사용자가 지목: "같은 화살표 방향이면 겹쳐도 좋지만
111
+ * 다른 종류(실선/점선)가 겹치면 가독성을 해친다". 같은 종류·같은 방향(합류·팬아웃)은 여전히 제외.
112
+ * 전형 = 되돌아가는 시퀀스가 목표 아랫면 중심으로 들어오는 열에 메시지플로우도 중심 도킹하는 것.
113
+ */
114
+ | {
115
+ kind: 'edge-overlap-mixed';
116
+ edges: [string, string];
117
+ axis: 'h' | 'v';
118
+ length: number;
119
+ }
120
+ /**
121
+ * 간선의 **라벨이 다른 간선과 공유하는 구간 위**에 놓여 어느 간선의 라벨인지 읽을 수 없다(확정 6
122
+ * 다섯 번째 표본에서 사용자가 지목: "겹치는 선상에 있는 분기 문구가 어느 선에 해당하는지 애매").
123
+ * 전형 = 게이트웨이 팬아웃의 3점 경로(세로 → 가로)는 첫 세로 구간을 갈래끼리 공선 공유하는데,
124
+ * bpmn-js 기본 라벨 위치(중간 구간 중점 = 3점이면 첫 구간)가 정확히 그 공유 구간이라 '승인'·
125
+ * '보완 요청' 이 한 선 위에 나란히 선다. 같은 방향 공선 중첩은 선으로서는 문제가 아니지만
126
+ * (`edge-overlap-opposite` 가 제외한 이유) **라벨 귀속** 축에서는 문제다. `other` = 그 구간을
127
+ * 함께 쓰는 간선(여럿이면 id 최소).
128
+ */
129
+ | {
130
+ kind: 'label-on-shared-segment';
131
+ edge: string;
132
+ other: string;
133
+ axis: 'h' | 'v';
134
+ }
135
+ /**
136
+ * 간선 라벨의 상자를 **선이 가로지른다** — 자기 간선(또는 다른 간선)의 축 정렬 구간이 상자 안을 지나거나,
137
+ * 컨테이너(레인·풀) 경계선이 상자를 가른다(열세 번째 표본에서 사용자가 지목: "선에 달린 글자가 선과 겹치거나
138
+ * 레인선상에 걸린다"). 전형 = 세로 2점 메시지플로우의 라벨 — bpmn-js 는 라벨 **중심**을 선 + 15 에 두므로 폭 42
139
+ * 라벨은 6px, 폭 64 는 17px 선 위에 걸친다. 가로 구간의 라벨(중심이 선 위 15, 높이 14)은 걸치지 않는다.
140
+ * `line` = 그 간선 id 또는 컨테이너 id. **`edge` = 라벨의 소유자 id** — 간선 라벨이면 간선, **도형의 외부 이름 라벨**
141
+ * (게이트웨이·이벤트 이름, B-29)이면 그 도형 id(스물네 번째 표본: 되돌아가는 간선 가로선이 게이트웨이 이름 라벨을
142
+ * 가로질러 사용자가 세 회차 연속 라벨을 올렸다 — bpmn-js 적응 배치는 도킹 면만 보고 지나가는 선은 안 본다).
143
+ */
144
+ | {
145
+ kind: 'label-over-line';
146
+ edge: string;
147
+ line: string;
148
+ }
149
+ /**
150
+ * 텍스트 주석 상자가 다른 요소의 **외부 라벨**(게이트웨이·이벤트 이름, 간선 라벨) 위에 놓였다 — 두 텍스트가
151
+ * 겹쳐 어느 쪽도 읽을 수 없다(열여덟 번째 표본: `top` 주석이 `처리 결과?` 게이트웨이의 이름 라벨을 덮었고
152
+ * 사용자는 "라벨과 코멘트가 비슷한 위치" 라며 주석을 옆으로 옮겼다). 주석 배치가 도형·간선 구간만 피하고
153
+ * 라벨은 보지 않던 공백. `label` = 그 라벨을 소유한 요소 id(도형 또는 간선).
154
+ */
155
+ | {
156
+ kind: 'annotation-over-label';
157
+ annotation: string;
158
+ label: string;
159
+ }
160
+ /**
161
+ * 두 그룹의 바운즈가 겹치는데 **멤버를 하나도 공유하지 않는다** — 사람은 그 겹침이 업무적 판단인지
162
+ * (한 활동이 두 단계에 걸친다) 그리다 보니 생긴 것인지 구분할 수 없다(G12 실측에서 사용자가 지목:
163
+ * "그룹이 겹쳐서 판단에 의한 건지 그리다 보니 겹친 건지 판단이 잘 안 되는 상태"). 멤버를 공유하는
164
+ * 겹침은 **의도로 읽히므로 제외**한다 — 겹침 자체가 결함이 아니라 *의도가 표현되지 않은 겹침*이 결함이다.
165
+ * 전형 = 단계 그룹은 x 구간인데 멤버가 여러 풀에 흩어져 x 범위가 서로 물리는 경우(A-4 멤버 bbox 의
166
+ * 구조적 한계). `groups` = 겹친 두 그룹(id 오름차순).
167
+ */
168
+ | {
169
+ kind: 'group-overlap-unintended';
170
+ groups: [string, string];
171
+ }
172
+ /**
173
+ * 그룹의 테두리가 컨테이너(풀·확장 서브프로세스) 경계선과 **한 선으로 그려진다** — 두 선이 겹쳐
174
+ * 그룹 영역이 풀 테두리와 구분되지 않는다(표본 30 에서 사용자가 지목: "그룹이 레인선에 겹쳐서
175
+ * 출력되는 부분"). 배치 경로에서는 **결정적으로 발생**했다: 풀 상단에서 band 0 노드 중심까지 80,
176
+ * 노드 높이 80 이라 노드 상단 = 풀 상단 + 40 인데 그룹 패딩도 40 이라 정확히 얹힌다(그룹 5/5).
177
+ * 실무 문서에서는 그룹 24개 중 2개(8%)만 이 상태라 사람도 대개 떼어 놓는다. `container` = 그 컨테이너,
178
+ * `side` = 겹친 변.
179
+ */
180
+ | {
181
+ kind: 'group-on-container-boundary';
182
+ group: string;
183
+ container: string;
184
+ side: 'top' | 'bottom' | 'left' | 'right';
185
+ }
186
+ /**
187
+ * **두 그룹의 테두리가 한 선으로 그려진다**(B-45) — 단계 그룹이 옆으로 늘어설 때 결정적으로 난다:
188
+ * 상자 = 멤버 bbox ± 패딩(40) 이라 두 단계의 멤버가 정확히 **80**(= 2 × 패딩) 떨어지면 두 테두리가
189
+ * 정확히 겹친다. 표본 38 에서 사용자가 지목("그룹간에 선들이 겹쳐있는 부분")하고 두 쌍 모두 손으로 뗐다.
190
+ * 겹침이 아니라 **맞닿음**이라 `group-overlap-unintended`(양 축 5px 초과 겹침)는 이 형상을 못 본다.
191
+ *
192
+ * 근거(오탐): 실무 18문서의 그룹쌍 65 중 이 상태는 **0건** — 사람은 테두리를 겹쳐 두지 않는다.
193
+ * 우리 산출물에서는 fixture 10종(그룹 2개 이상) 중 **7종**에서 났다(관측 11건 전부 간격 0).
194
+ */
195
+ | {
196
+ kind: 'group-on-group-boundary';
197
+ groups: [string, string];
198
+ axis: 'x' | 'y';
199
+ }
200
+ /**
201
+ * **블랙박스 풀의 이름 텍스트**가 다른 요소의 외부 라벨과 겹쳤다(B-38) — 두 텍스트가 포개져 어느 쪽도
202
+ * 읽을 수 없는데, 종전에는 **아무 판정 채널도 이 텍스트를 보지 않았다**(DI 에 `BPMNLabel` 이 없고
203
+ * `auditTargetsOf` 는 컨테이너를 뺀다). G13 에서 사용자가 지목: 풀을 넓히자 이름이 중앙으로 따라
204
+ * 이동해 메시지 라벨을 덮었고 *"해당 문구는 선택 가능한 문구가 아니라서 에이전트가 판단이 될지 의문"*.
205
+ * 고치는 수단이 다르다 — 이름은 **옮길 수 없으므로**(위치 = 풀 중심 = 폭의 함수) 상대 라벨을 옮기거나
206
+ * 풀 기하를 바꾼다. `pool` = 그 참여자, `label` = 겹친 라벨의 소유자 id.
207
+ *
208
+ * 근거(오탐): 실무 13문서의 외부 라벨 302개 중 라벨끼리 겹친 쌍은 **0** 이다 — 사람은 텍스트를 겹쳐
209
+ * 두지 않는다. 우리 산출물에서도 이 겹침은 G10~G13 표본 전량 0건이다.
210
+ */
211
+ | {
212
+ kind: 'pool-name-over-label';
213
+ pool: string;
214
+ label: string;
50
215
  };
51
216
  /** 발견을 전후 비교(배치가 새로 만든 것 가려내기)할 수 있게 하는 안정 키. */
52
217
  export declare function geometryFindingKey(finding: GeometryFinding): string;
218
+ /**
219
+ * `processRef` 없는 참여자(= bpmn-js `isExpanded` 거짓)의 **이름 텍스트 사각형**. DI 에 없는 렌더 산물이라
220
+ * 여기서 산출한다 — 폭 근사는 주석 크기 산출과 같은 출처(`approximateTextWidth`)를 쓴다.
221
+ *
222
+ * 줄바꿈은 계산하지 않는다: 상자 폭이 풀 폭(1000px 이상)이라 실무·표본 전량에서 접히지 않았고, 명시 개행
223
+ * (`\n`)만 줄을 가른다. 접히는 이름이 관찰되면 그때 폭 기준 줄바꿈을 넣는다(조기 일반화 회피).
224
+ */
225
+ export declare function blackBoxNameRect(pool: GeometryShape, name: string): GeometryLabel;
226
+ export interface AxisSegment {
227
+ axis: 'h' | 'v';
228
+ /** 고정 좌표(h 면 y, v 면 x). */
229
+ at: number;
230
+ lo: number;
231
+ hi: number;
232
+ /** 진행 방향 부호(+ = 오른쪽/아래). */
233
+ dir: 1 | -1;
234
+ }
235
+ /** 간선의 축 정렬 구간들(웨이포인트 순서). 사선 구간은 뺀다. */
236
+ export declare function axisSegmentsOf(edge: GeometryEdge): AxisSegment[];
237
+ /**
238
+ * 두 간선의 **읽을 수 없는** 공선 중첩 — 방향이 반대(`opposite`)이거나, 같은 방향인데 종류가 다른
239
+ * (`mixed`: 실선 × 점선) 겹침. 가장 긴 것의 (축, 길이, 종류). 없으면 `null`. 같은 종류·같은 방향
240
+ * (합류·팬아웃)은 세지 않는다 — 사용자가 그대로 두는 형상이다(표본 4·7).
241
+ */
242
+ export declare function unreadableOverlapOf(a: GeometryEdge, b: GeometryEdge): {
243
+ axis: 'h' | 'v';
244
+ length: number;
245
+ kind: 'opposite' | 'mixed';
246
+ } | null;
247
+ /** 두 간선의 **반대 방향** 공선 중첩(종류 무관). `unreadableOverlapOf` 의 부분집합 — 기존 호출자 호환. */
248
+ export declare function oppositeOverlapOf(a: GeometryEdge, b: GeometryEdge): {
249
+ axis: 'h' | 'v';
250
+ length: number;
251
+ } | null;
252
+ /** 한 간선(후보 경로)이 다른 간선들과 만드는 반대 방향 중첩 수. */
253
+ export declare function oppositeOverlapCount(edge: GeometryEdge, others: readonly GeometryEdge[]): number;
254
+ /** 한 간선(후보 경로)이 다른 간선들과 만드는 읽을 수 없는 중첩 수(반대 방향 + 종류 다름) — 보정의 목적 함수. */
255
+ export declare function unreadableOverlapCount(edge: GeometryEdge, others: readonly GeometryEdge[]): number;
256
+ /** bpmn-js `LabelUtil.FLOW_LABEL_INDENT` — 라벨 중심이 선에서 떨어지는 거리(가로 구간은 위, 세로 구간은 오른쪽). */
257
+ export declare const FLOW_LABEL_INDENT = 15;
258
+ /**
259
+ * 갈림점 바로 옆에 붙은 라벨도 모호하다(다섯 번째 표본: 갈림 16px 아래의 '보완 요청' 을 사용자가 더
260
+ * 내렸다) — 공유 구간을 양쪽으로 이만큼 넓혀 본다. 배치(`planLabelPlacement`)도 같은 폭을 뺀다.
261
+ */
262
+ export declare const LABEL_FORK_MARGIN = 20;
263
+ /**
264
+ * `seg` 가 다른 간선의 구간과 공선 공유하는 부분들(≥ `OVERLAP_MIN_LENGTH`, 방향 무관) — 갈림 여백을
265
+ * 더해 돌려준다. 판정과 배치가 **같은 목록**을 써야 "옮겼는데 여전히 잡힌다"가 나지 않는다.
266
+ */
267
+ export declare function sharedSpansOf(seg: AxisSegment, otherSegments: readonly AxisSegment[]): Array<[number, number]>;
268
+ /**
269
+ * 이 간선의 라벨이 다른 간선과 **공선 공유하는 구간**(방향 무관, ≥ `OVERLAP_MIN_LENGTH`, 갈림 여백 포함)
270
+ * 위에 놓여 있으면 그 상대(id 최소)와 축을 돌려준다. 라벨이 없거나 고유 구간에 있으면 `null`.
271
+ */
272
+ export declare function sharedSegmentUnderLabel(edge: GeometryEdge, others: readonly GeometryEdge[]): {
273
+ other: string;
274
+ axis: 'h' | 'v';
275
+ } | null;
276
+ export declare function lineCrossesLabel(label: GeometryLabel, seg: AxisSegment): boolean;
277
+ /** 컨테이너 도형의 네 변을 축 정렬 구간으로 — 레인·풀 경계선. */
278
+ export declare function containerBoundarySegments(shape: GeometryShape): AxisSegment[];
279
+ /**
280
+ * 이 간선의 라벨을 가로지르는 선 — 자기 간선 구간 → 다른 간선 구간(id 순) → 컨테이너 경계선(id 순). 첫 것의 id.
281
+ * 없으면 `null`. 판정과 보정(routeRepair `planLabelLineClearance`)이 같은 술어를 쓴다.
282
+ */
283
+ export declare function lineThroughLabel(edge: GeometryEdge, scene: GeometryScene): string | null;
284
+ /**
285
+ * 이 간선이 관통하는 도형들(자기 끝점 제외). 판정과 **우회 보정**(routeRepair)이 같은
286
+ * 술어를 써야 "고쳤다는데 판정은 그대로"가 나지 않는다.
287
+ *
288
+ * `targets` 는 컨테이너를 제외한 도형 목록 — 호출자가 한 번 걸러 넘긴다(반복 호출 비용).
289
+ */
290
+ export declare function crossedShapesOf(edge: GeometryEdge, targets: readonly GeometryShape[]): GeometryShape[];
291
+ /**
292
+ * 한 간선(또는 그 후보 경로)이 다른 간선들과 교차하는 수. **끝점을 공유하는 간선은 제외**한다 —
293
+ * 같은 노드에서 나가는 팬아웃·같은 노드로 드는 합류는 꺾이는 자리가 겹쳐 보여도 교차가 아니다.
294
+ */
295
+ export declare function edgeCrossCount(edge: GeometryEdge, others: readonly GeometryEdge[]): number;
296
+ /**
297
+ * 교차 **비용**(B-18 ⓑ) — 같은 종류(실선×실선)의 교차는 2, 종류가 다른(실선×점선) 교차는 1. 여덟 번째 표본의
298
+ * 사용자 우선순위: "어쩔 수 없을 땐 크로스될 수밖에 없지만 가급적 **다른 종류의 선**과 크로스되는 게 구분이
299
+ * 좋다". 후보 선택의 교차 축은 이 비용으로 재고, 계측(`edgeCrossCount`·`stats`)은 개수 그대로 둔다.
300
+ */
301
+ export declare function crossingCost(edge: GeometryEdge, others: readonly GeometryEdge[]): number;
302
+ /** 장면 전체의 교차 비용 합(쌍마다 같은 종류 2 · 다른 종류 1). */
303
+ export declare function sceneCrossingCost(scene: GeometryScene): number;
304
+ /** 장면 전체의 교차 쌍(id 오름차순 정렬, 결정론적). 길이가 곧 교차 수다. */
305
+ export declare function edgeCrossingPairsOf(scene: GeometryScene): Array<[string, string]>;
306
+ export declare const auditTargetsOf: (scene: GeometryScene) => GeometryShape[];
307
+ /** 도형의 외부 이름 라벨을 가로지르는 선 — 간선 구간(id 순) → 컨테이너 경계선(id 순). 첫 것의 id, 없으면 `null`. */
308
+ export declare function lineThroughShapeLabel(shape: GeometryShape, scene: GeometryScene): string | null;
309
+ /** 장면의 외부 라벨 전부(도형 이름 라벨 + 간선 라벨)와 그 소유자 — 판정과 주석 배치(6단계 장애물)가 같은 목록을 쓴다. */
310
+ export declare function labelRectsOf(scene: GeometryScene): Array<{
311
+ owner: string;
312
+ rect: GeometryLabel;
313
+ }>;
53
314
  /**
54
315
  * 기하 판정 (순수). 결과는 **결정론적 순서**로 정렬해 반환한다 — 전후 diff·골든 비교·회귀
55
316
  * 고정이 전부 순서에 의존한다.
@@ -58,10 +319,6 @@ export declare function geometryFindingKey(finding: GeometryFinding): string;
58
319
  * 에서 무시할 수준이고, 색인은 실제로 큰 문서가 관찰될 때 넣는다(조기 최적화 회피).
59
320
  */
60
321
  export declare function auditGeometry(scene: GeometryScene): GeometryFinding[];
61
- /**
62
- * moddle `definitions` → 장면. 입력은 `describeProcess` 와 같은 트리라 캔버스·헤드리스 양쪽
63
- * 에서 같은 것을 준다(A-6 입력 계약 동형).
64
- */
65
322
  export declare function sceneFromDefinitions(definitions: ModdleElement): GeometryScene;
66
323
  /** 저장 진실 기준 전수 판정 — 그 문서에 있는 **전부**(선재 결함 포함). */
67
324
  export declare function auditBpmnGeometry(definitions: ModdleElement): GeometryFinding[];
@@ -8,6 +8,7 @@ export type { DocumentIndex, ElementCategory } from './documentIndex';
8
8
  export { buildRegistryIndex } from './documentIndex';
9
9
  export { validateOps } from './validate';
10
10
  export { lowerOps, resolverServicesOf, type ResolverServices } from './lower';
11
+ export { forwardThreePointCandidates, planCrossPoolRoute, planEdgeRepair, planLabelLineClearance, planLabelPlacement, planMiddleSegmentShifts, planOppositeOverlapRepair, planSegmentDetours, planBackEdgeBoundaryClearance, repairIntroducedBoundaryHugging, repairIntroducedCrossings, repairIntroducedOppositeOverlaps, repairIntroducedSharedSegmentLabels, repairIntroducedLabelLines, repairIntroducedShapeLabelLines, planShapeLabelClearance, rerouteIntroducedCrossPoolEdges, } from './routeRepair';
11
12
  export interface ApplyBpmnOpsResult {
12
13
  ok: boolean;
13
14
  issues: ResolveIssue[];
@@ -20,5 +21,22 @@ export interface ApplyBpmnOpsResult {
20
21
  * 자기 배치 탓으로 읽고 무한 교정에 빠진다. 문서 전체 감사는 `auditBpmnGeometry`.
21
22
  */
22
23
  findings: GeometryFinding[];
24
+ /**
25
+ * 계측(B-14 ⓐ) — finding 이 아니다. 문서 **전체**의 간선 교차 수를 하강 전/후로 준다. 사람
26
+ * 마무리의 실체가 이 축이었다(확정 6 세 번째 표본: 교차 7 → 1)는 것이 드러난 뒤 도구가 그것을
27
+ * 볼 수 있게 한 채널. 배치가 교차를 얼마나 만들었는지 = `after - before`.
28
+ */
29
+ stats: {
30
+ edgeCrossings: {
31
+ before: number;
32
+ after: number;
33
+ /**
34
+ * 하강 뒤 남은 진성 교차 쌍(간선 id, 오름차순) — 개수만으로는 "이 정도가 정상인가"를 에이전트가
35
+ * 판단할 수 없다(G12 마찰: 교차 5 를 받고 어느 선끼리인지 몰라 배치 재검토를 못 했다).
36
+ * 쌍을 보면 한 메시지가 여러 시퀀스를 건너는 것인지(풀 횡단 = 정상) 배치가 엉킨 것인지 갈린다.
37
+ */
38
+ pairs: Array<[string, string]>;
39
+ };
40
+ };
23
41
  }
24
42
  export declare function applyBpmnOps(modeler: BpmnModelerLike, ops: readonly SymbolicOp[], lower?: typeof lowerOps): ApplyBpmnOpsResult;
@@ -6,7 +6,10 @@ import { default as ElementRegistry } from 'diagram-js/lib/core/ElementRegistry'
6
6
  import { default as Canvas } from 'diagram-js/lib/core/Canvas';
7
7
  import { default as AutoPlace } from 'diagram-js/lib/features/auto-place/AutoPlace';
8
8
  import { default as BpmnReplace } from 'bpmn-js/lib/features/replace/BpmnReplace';
9
+ import { Shape } from 'bpmn-js/lib/model/Types';
9
10
  import { SymbolicOp } from '../symbolicOp';
11
+ import { BwlSide } from '../../core/hints';
12
+ import { GeometryFinding } from '../geometryAudit';
10
13
  /** resolver 가 대면하는 bpmn-js 서비스 표면 — 하강은 이 묶음만 사용한다. */
11
14
  export interface ResolverServices {
12
15
  modeling: Modeling;
@@ -18,5 +21,45 @@ export interface ResolverServices {
18
21
  bpmnReplace: BpmnReplace;
19
22
  }
20
23
  export declare function resolverServicesOf(modeler: BpmnModelerLike): ResolverServices;
24
+ /**
25
+ * ── B-40. 주석 자리 산출 — **add·update·기존 주석 보정 공유** ──
26
+ *
27
+ * 선언 방위 → 실제 좌표(풀 안 클램프 B-20 ⓑ · 앵커 회피 · 형제·간선 B-21 · 외부 라벨 B-24 ⓑ 회피 ·
28
+ * 대안 방위 좌 우선 B-26 ⓐ). 힌트가 없으면 휴리스틱 자리를 같은 규칙으로 클램프·비낀다.
29
+ *
30
+ * 종전에는 이 전부가 `annotation.add` 루프 안에 인라인이었고 `annotation.update` 는 **원시 계산**만 했다.
31
+ * 표본 34 실측: 에이전트가 `annotation-over-label` 을 playbook §6 대로 `annotation.update` 로 고치려 하자
32
+ * 주석이 풀 위로 65px 나갔고(`shape-outside-container` 신규), **배치 1 과 완전히 같은 placement 를 다시
33
+ * 보내도 되돌아가지 않았다**(add 810 = 클램프 / update 775 = 원시값). 같은 입력이 경로에 따라 다른 좌표를
34
+ * 내는 것 자체가 결함이고, 규약대로 행동한 에이전트가 문서를 나쁘게 만들었다.
35
+ * B-33 → B-34 ⓐ 가 그룹 경계 보정에서 닫은 것과 같은 클래스다.
36
+ *
37
+ * `selfId` = 이미 존재하는 주석(update)의 자기 도형·자기 연관을 장애물에서 뺀다 — 안 빼면 늘 "막힘"이다.
38
+ */
39
+ export declare function annotationSpot(elementRegistry: ElementRegistry, anchor: {
40
+ x: number;
41
+ y: number;
42
+ width: number;
43
+ height: number;
44
+ }, dims: {
45
+ width: number;
46
+ height: number;
47
+ }, box: Shape, opts: {
48
+ placement?: {
49
+ side: BwlSide;
50
+ offset?: number | null;
51
+ } | null;
52
+ from?: {
53
+ x: number;
54
+ y: number;
55
+ };
56
+ selfId?: string;
57
+ }): {
58
+ x: number;
59
+ y: number;
60
+ };
21
61
  /** 검증 통과가 전제 — 참조 해소 실패는 여기서 프로그래밍 오류로 throw 되고 applyBpmnOps 가 롤백한다. */
22
62
  export declare function lowerOps(ops: readonly SymbolicOp[], services: ResolverServices): void;
63
+ /** 주석 도형이 소유자로 등장할 수 있는 finding 의 소유 도형 id. */
64
+ export declare function annotationOwnersOf(finding: GeometryFinding): readonly string[];
65
+ export declare function repairIntroducedAnnotationOverlaps(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeKeys: ReadonlySet<string>, beforeShapeIds: ReadonlySet<string>): void;
@@ -0,0 +1,144 @@
1
+ import { GeometryEdge, GeometryLabel, GeometryPoint, GeometryScene, GeometryShape } from '../geometryAudit';
2
+ import { ResolverServices } from './lower';
3
+ /**
4
+ * 축 정렬 구간이 장애물을 **완전히 가로지를 때** 그것을 우회하는 4점을 만든다(순수).
5
+ *
6
+ * 반환은 `a`·`b` **사이에 끼워 넣을** 점들이다(양 끝점은 포함하지 않는다). 두 방향(위/아래
7
+ * 또는 좌/우)을 모두 돌려주고 어느 쪽이 나은지는 호출자가 판정기로 고른다.
8
+ *
9
+ * `null` = 이 구간은 다루지 않는다:
10
+ * - 사선 구간(축 정렬이 아님) — 우회 형상이 자명하지 않다
11
+ * - 장애물이 구간 끝에 걸쳐 있음 — 끝점 도킹과 얽혀 잘못 건드리면 연결이 떨어져 보인다
12
+ */
13
+ export declare function planSegmentDetours(a: GeometryPoint, b: GeometryPoint, rect: GeometryShape, margin?: number): [GeometryPoint[], GeometryPoint[]] | null;
14
+ /**
15
+ * 꺾임점이 장애물 **안**에 있는 형상 — 구간 우회로는 못 푼다(B-13 ②, 두 번째 표본 잔존 1건).
16
+ *
17
+ * bpmn-js 의 직각 경로는 대개 h-v-h(또는 v-h-v) 3구간이고, **중간 구간의 좌표**는 양 끝점의
18
+ * 중점이다. 그 중점이 어떤 도형의 폭 안에 떨어지면 세로 구간이 그 도형을 관통하고 꺾임점도
19
+ * 그 안에 있어 `planSegmentDetours` 가 거부한다(실측: 되돌아가는 간선의 중간 x 가 게이트웨이
20
+ * 폭 안). 해법은 우회가 아니라 **중간 구간을 장애물 옆으로 옮기는 것** — 점 수는 그대로 4 다.
21
+ * 두 후보(장애물 왼쪽/오른쪽 또는 위/아래)를 돌려주고 채택은 호출자가 판정기로 한다.
22
+ *
23
+ * `null` = 4점 직각 경로가 아니다.
24
+ */
25
+ export declare function planMiddleSegmentShifts(waypoints: readonly GeometryPoint[], rect: GeometryShape, margin?: number): [GeometryPoint[], GeometryPoint[]] | null;
26
+ /**
27
+ * 정방향 간선(목표가 출발의 오른쪽, 다른 행)의 3점 후보 둘(순수) — B-17 ⓒ.
28
+ * h-v: 출발 옆면(오른쪽) → 출발 행에서 가로 → 목표 중심 열에서 목표 윗/아랫면으로
29
+ * v-h: 출발 윗/아랫면 → 목표 행까지 세로 → 목표 왼쪽 옆면으로
30
+ * 같은 행이거나 되돌아가는 간선이면 빈 배열(그쪽은 `backEdgeCandidates` 몫).
31
+ */
32
+ export declare function forwardThreePointCandidates(edge: GeometryEdge, shapes: readonly GeometryShape[]): GeometryPoint[][];
33
+ /**
34
+ * B-19 ⓓ. 다른 풀(위/아래)의 노드로 가는 메시지플로우가 자기 풀 안에서 막힐 때 — **목표 반대편으로 나가**
35
+ * 자기 행 바깥의 채널(출발 도형 가장자리 + 30)을 타고 목표 열까지 간 뒤 직진하는 4점.
36
+ * 열 번째 표본에서 사용자가 `반려 상품 재발송 → 처리 결과 수신` 을 정확히 이렇게 놓았다(재발송 아랫면 →
37
+ * y+30 채널 → 수신 열 → 위로 직진). 종전에는 위로 나간 기본 경로가 게이트웨이를 만나 ㄷ 우회 2회(8점)가
38
+ * 됐다 — 저작 에이전트도 그 지그재그를 보고에 지목했다.
39
+ * 목표 열은 목표 중심에 도킹 편이 0/±25/±40 — 그 열에 다른 메시지가 이미 중심 도킹해 있으면(같은 방향·같은
40
+ * 종류라 finding 은 아니지만 두 화살표가 한 선으로 합쳐 보인다 — 사용자도 -19 비껴 놓았다) 편이 후보가 피한다.
41
+ * 채택은 호출자 몫: 관통 0 을 먼저, 그다음 다른 간선과의 공선 공유가 없는 후보를 고른다.
42
+ */
43
+ export declare function farSideChannelCandidates(edge: GeometryEdge, shapes: readonly GeometryShape[]): GeometryPoint[][];
44
+ /**
45
+ * 한 간선의 관통을 없애는 웨이포인트를 찾는다(순수). 못 찾으면 `null`.
46
+ *
47
+ * 장애물을 하나씩 처리하며 **매번 두 방향을 다 재보고 관통이 더 적은 쪽**을 고른다.
48
+ * 부모 컨테이너 바운즈를 알면 그 밖으로 나가는 우회를 후순위로 민다 — 풀 밖으로 튀어나간
49
+ * 선은 관통이 없어도 사람 눈에 결함이다(판정기가 못 보는 축이라 여기서 챙긴다).
50
+ */
51
+ export declare function planEdgeRepair(edge: GeometryEdge, targets: readonly GeometryShape[], container?: {
52
+ x: number;
53
+ y: number;
54
+ width: number;
55
+ height: number;
56
+ }, others?: readonly GeometryEdge[]): GeometryPoint[] | null;
57
+ /**
58
+ * 이 배치가 **새로 만든** 관통을 고친다(하강 직후, undo 에피소드 안에서 호출된다).
59
+ *
60
+ * `beforeKeys` = 하강 전 판정 키 집합. 거기 없던 관통만 대상이다(안전 성질 2).
61
+ * 완전히 해소되는 우회만 채택하고, 아니면 손대지 않는다(안전 성질 1) — 부분 개선을 받으면
62
+ * "고쳤는데 여전히 깨져 있다"가 되어 보고와 실물이 어긋난다.
63
+ */
64
+ export declare function repairIntroducedCrossings(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeKeys: ReadonlySet<string>): void;
65
+ /**
66
+ * 풀 횡단 메시지플로우의 6점 경로 계획(순수). `null` = 이 간선은 대상이 아니거나(중간 풀 없음 ·
67
+ * 끝점 해소 불가) 더 나은 열이 없다.
68
+ */
69
+ export declare function planCrossPoolRoute(edge: GeometryEdge, scene: GeometryScene): GeometryPoint[] | null;
70
+ /**
71
+ * 이 배치가 **새로 만든** 메시지플로우 중 풀을 횡단하는 것을 재경로한다(하강 직후, undo 에피소드
72
+ * 안). `beforeEdgeIds` = 하강 전 간선 id — 그 밖의 것만 대상(안전 성질 2: 선재 경로 불가침).
73
+ */
74
+ export declare function rerouteIntroducedCrossPoolEdges(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeEdgeIds: ReadonlySet<string>): void;
75
+ /** 안쪽 구간(첫·끝 도킹 구간 제외)이 컨테이너 경계선과 나란히 이 거리 안에서 달리면 "경계에 붙었다"고 본다. */
76
+ export declare const BOUNDARY_CLEARANCE = 30;
77
+ /** 웨이포인트의 **안쪽 구간**(도킹 구간 제외)이 어느 컨테이너의 경계선과 나란히 `BOUNDARY_CLEARANCE` 안에서 겹쳐 달리는가. */
78
+ export declare function hugsContainerBoundary(pts: readonly GeometryPoint[], containers: readonly GeometryShape[]): boolean;
79
+ /** 안쪽 구간이 이 거리 안에서 다른 도형 옆을 지나면 "스친다"고 본다(관통은 아니지만 도형 사이 통로를 달리는 선). */
80
+ export declare const NEAR_PASS_MARGIN = 40;
81
+ /** 안쪽 구간(도킹 구간 제외)이 끝점 아닌 도형을 `NEAR_PASS_MARGIN` 안에서 스치는 횟수 — 빈 여백을 달리는 경로가 0. */
82
+ export declare function nearPassCount(pts: readonly GeometryPoint[], shapes: readonly GeometryShape[], endpoints: readonly string[]): number;
83
+ /**
84
+ * 경계에 붙어 달리는 되돌아가는 간선의 재경로(순수, B-26 ⓑ). 스무 번째 표본 `재결재`(담당자 레인 위 행 → 팀장 레인
85
+ * 결재)의 보정 경로 A2 는 채널이 레인 경계선 15px 아래를 달리고 목표 열 세로선이 위 행 노드들 옆을 지나갔다 —
86
+ * 사용자는 출발 아랫면 → 오른쪽 빈 열 → 결재 아래 채널 → 결재 아랫면으로 **빈 여백만 달리는** 경로를 그렸다(그 채널도
87
+ * 경계에서 20px — 경계 근접만으로는 사용자 답까지 버려진다). 그래서 지금 경로가 경계에 붙어 있을 때만 발동하고, 후보는
88
+ * 관통 0 · 읽을 수 없는 중첩 비증가 · 교차 비용 비증가를 지킨 뒤 **(스침 수 + 경계 근접) 최소 → 편이 → 길이** 로 고른다.
89
+ * 같은 레인 안에서 되돌아가는 간선(r2 `Flow_o14`)은 A2 채널이 경계에서 멀어 발동하지 않는다. 없으면 `null`.
90
+ */
91
+ export declare function planBackEdgeBoundaryClearance(edge: GeometryEdge, scene: GeometryScene): GeometryPoint[] | null;
92
+ /** 이 배치가 **새로 만든** 시퀀스 플로우 중 경계에 붙어 달리는 되돌아가는 간선을 재경로한다(B-26 ⓑ). */
93
+ /**
94
+ * 배치가 만든 정방향 간선의 **불필요한 꺾임을 줄인다**(B-34 ⓑ) — bpmn-js 기본 맨해튼 라우팅은 출발 직후
95
+ * 꺾어 목표 행으로 올라간 뒤 가로로 가는 4점을 낸다. 사람은 두 회차 연속(표본 30·31, 같은 간선 같은 변환)
96
+ * **출발 행으로 끝까지 간 뒤 목표 열에서 꺾는 3점**으로 고쳤다.
97
+ *
98
+ * `planEdgeRepair`(B-9 ②)는 **관통이 있을 때만** 돌아 이 형상을 보지 못한다 — 관통이 없는 우회다.
99
+ * 안전 성질은 그대로: 배치가 만든 간선만 · 점 수가 줄 때만 · 관통 0 · 읽을 수 없는 중첩 비증가 ·
100
+ * 교차 비용 비증가. 하나라도 어긋나면 손대지 않는다.
101
+ */
102
+ export declare function repairIntroducedDetours(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeEdgeIds: ReadonlySet<string>): void;
103
+ export declare function repairIntroducedBoundaryHugging(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeEdgeIds: ReadonlySet<string>): void;
104
+ /**
105
+ * 한 간선의 **읽을 수 없는 중첩**(반대 방향 · 종류 다름)을 없애거나 줄이는 경로(순수). 못 찾으면 `null`.
106
+ * 종류 다른 중첩(B-17 ⓐ)의 보정 = 도킹 편이 — "가급적 중심에 도킹하되 어쩔 수 없을 땐 약간 비껴서"
107
+ * (사용자 프레이밍). 기본은 중심이고, finding 이 났을 때만, 최소 편이(±25)부터 본다.
108
+ */
109
+ export declare function planOppositeOverlapRepair(edge: GeometryEdge, scene: GeometryScene): GeometryPoint[] | null;
110
+ /**
111
+ * 이 배치가 **새로 만든** 읽을 수 없는 중첩(반대 방향 · 종류 다름)을 고친다(하강 직후, undo 에피소드 안).
112
+ * 짝 중 새 간선만 손댄다(안전 성질 2). 둘 다 새 것이면 **시퀀스 플로우부터** — 종류 다른 중첩에서 사용자는
113
+ * 메시지플로우(중심 도킹)를 두고 시퀀스 쪽을 비켰다(표본 7).
114
+ */
115
+ export declare function repairIntroducedOppositeOverlaps(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeKeys: ReadonlySet<string>, beforeEdgeIds: ReadonlySet<string>): void;
116
+ /**
117
+ * 라벨의 새 bounds(순수). 라벨이 공유 구간 위에 없으면 `null`(옮길 이유가 없다). 고유 구간을 못
118
+ * 찾거나 옮긴 자리가 도형과 겹치면 `null`(손대지 않고 보고에 맡긴다).
119
+ *
120
+ * 구간마다 다른 간선과 공선 공유하는 부분(≥ 20px)을 빼고 남은 **첫 자유 부분** 중 라벨이 들어가는
121
+ * 곳을 고른다 — 부분 공유 구간(갈림점 아래로 이어지는 세로 구간)도 그 아래쪽이 자유 부분이다.
122
+ */
123
+ export declare function planLabelPlacement(edge: GeometryEdge, scene: GeometryScene): GeometryLabel | null;
124
+ /** 라벨 상자와 선 사이 최소 여백(px). 가로 구간 라벨의 bpmn-js 기본(중심 15, 높이 14 → 아랫변 8 위)과 같은 값. */
125
+ export declare const LABEL_LINE_GAP = 8;
126
+ /**
127
+ * 라벨의 새 bounds(순수) — 어떤 선(자기 구간 · 다른 간선 구간 · 컨테이너 경계선)이든 상자를 가로지르면, 라벨이 붙은
128
+ * 구간(중심에 가장 가까운 자기 구간)의 옆자리 후보를 순서대로 시험해 **아무 선도 지나지 않는 첫 자리**로 옮긴다.
129
+ * 순서 = 세로 구간이면 오른쪽 → 왼쪽, 가로 구간이면 위 → 아래; 각각 구간 방향으로 0 · 위/왼쪽 · 아래/오른쪽 · 2단 편이
130
+ * (사용자는 레인 경계에 걸친 라벨을 **위로** 올렸다 — 위/왼쪽 우선). 구간 밖으로는 안 나간다. 전부 막히면 `null`
131
+ * (판정기가 보고한다). 바꿀 것이 없으면 `null`.
132
+ */
133
+ export declare function planLabelLineClearance(edge: GeometryEdge, scene: GeometryScene): GeometryLabel | null;
134
+ /** 이 배치가 **새로 만든** 간선의 라벨을 선·경계선에서 뗀다(맨 마지막 — 라벨 귀속 보정 뒤). 선재 라벨은 불가침. */
135
+ export declare function repairIntroducedLabelLines(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeEdgeIds: ReadonlySet<string>): void;
136
+ /**
137
+ * 이 배치가 **새로 만든** 간선의 라벨 귀속 문제를 고친다(하강 직후, undo 에피소드 안, 경로 보정들 뒤).
138
+ * 선재 간선의 라벨은 사람이 놓았을 수 있어 손대지 않는다(안전 성질 2).
139
+ */
140
+ export declare function repairIntroducedSharedSegmentLabels(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeKeys: ReadonlySet<string>, beforeEdgeIds: ReadonlySet<string>): void;
141
+ /** 라벨 상자를 가로지르는 선을 피한 새 자리(순수). 같은 쪽에서 선 밖으로 비끼기 → 도형 상·하·좌·우 기본 자리. 없으면 `null`. */
142
+ export declare function planShapeLabelClearance(shape: GeometryShape, scene: GeometryScene): GeometryLabel | null;
143
+ /** 이 배치가 **새로 만든** 도형의 이름 라벨을 선에서 뗀다(맨 마지막 — 경로·간선 라벨이 전부 확정된 뒤). */
144
+ export declare function repairIntroducedShapeLabelLines(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeShapeIds: ReadonlySet<string>): void;