@g1cloud/bpmn-modeler-next 5.0.0-alpha.8 → 5.0.0-alpha.80

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.
@@ -6,7 +6,11 @@ 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 { default as SpaceTool } from 'diagram-js/lib/features/space-tool/SpaceTool';
10
+ import { Shape } from 'bpmn-js/lib/model/Types';
9
11
  import { SymbolicOp } from '../symbolicOp';
12
+ import { BwlSide } from '../../core/hints';
13
+ import { GeometryFinding, GeometryScene } from '../geometryAudit';
10
14
  /** resolver 가 대면하는 bpmn-js 서비스 표면 — 하강은 이 묶음만 사용한다. */
11
15
  export interface ResolverServices {
12
16
  modeling: Modeling;
@@ -16,7 +20,89 @@ export interface ResolverServices {
16
20
  canvas: Canvas;
17
21
  autoPlace: AutoPlace;
18
22
  bpmnReplace: BpmnReplace;
23
+ /** 공간 확보(`createSpace`)의 이동·리사이즈 집합 산출 — 레인 증설이 쓴다(B-97 ⓘ). */
24
+ spaceTool: SpaceTool;
19
25
  }
20
26
  export declare function resolverServicesOf(modeler: BpmnModelerLike): ResolverServices;
27
+ /**
28
+ * ── B-40. 주석 자리 산출 — **add·update·기존 주석 보정 공유** ──
29
+ *
30
+ * 선언 방위 → 실제 좌표(풀 안 클램프 B-20 ⓑ · 앵커 회피 · 형제·간선 B-21 · 외부 라벨 B-24 ⓑ 회피 ·
31
+ * 대안 방위 좌 우선 B-26 ⓐ). 힌트가 없으면 휴리스틱 자리를 같은 규칙으로 클램프·비낀다.
32
+ *
33
+ * 종전에는 이 전부가 `annotation.add` 루프 안에 인라인이었고 `annotation.update` 는 **원시 계산**만 했다.
34
+ * 표본 34 실측: 에이전트가 `annotation-over-label` 을 playbook §6 대로 `annotation.update` 로 고치려 하자
35
+ * 주석이 풀 위로 65px 나갔고(`shape-outside-container` 신규), **배치 1 과 완전히 같은 placement 를 다시
36
+ * 보내도 되돌아가지 않았다**(add 810 = 클램프 / update 775 = 원시값). 같은 입력이 경로에 따라 다른 좌표를
37
+ * 내는 것 자체가 결함이고, 규약대로 행동한 에이전트가 문서를 나쁘게 만들었다.
38
+ * B-33 → B-34 ⓐ 가 그룹 경계 보정에서 닫은 것과 같은 클래스다.
39
+ *
40
+ * `selfId` = 이미 존재하는 주석(update)의 자기 도형·자기 연관을 장애물에서 뺀다 — 안 빼면 늘 "막힘"이다.
41
+ */
42
+ export declare function annotationSpot(elementRegistry: ElementRegistry, anchor: {
43
+ x: number;
44
+ y: number;
45
+ width: number;
46
+ height: number;
47
+ }, dims: {
48
+ width: number;
49
+ height: number;
50
+ }, box: Shape, opts: {
51
+ placement?: {
52
+ side: BwlSide;
53
+ offset?: number | null;
54
+ } | null;
55
+ from?: {
56
+ x: number;
57
+ y: number;
58
+ };
59
+ selfId?: string;
60
+ }): {
61
+ x: number;
62
+ y: number;
63
+ };
21
64
  /** 검증 통과가 전제 — 참조 해소 실패는 여기서 프로그래밍 오류로 throw 되고 applyBpmnOps 가 롤백한다. */
22
65
  export declare function lowerOps(ops: readonly SymbolicOp[], services: ResolverServices): void;
66
+ /** 주석 도형이 소유자로 등장할 수 있는 finding 의 소유 도형 id. */
67
+ export declare function annotationOwnersOf(finding: GeometryFinding): readonly string[];
68
+ export declare function repairIntroducedAnnotationOverlaps(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeKeys: ReadonlySet<string>, beforeShapeIds: ReadonlySet<string>): void;
69
+ /**
70
+ * **B-91 예방** — 이 배치가 만든 **풀 × 풀 겹침**을, 오른쪽 이웃을 밀어 해소한다.
71
+ *
72
+ * B-91 은 탐지만 했다(`pool-overlap` finding). 겹침 자체는 계속 생겼고 사람이 매번 손으로 뺐다 —
73
+ * 🔴 **표본 68 에서 재발이 3점 검정으로 확정돼**(base 0 / 산출 2 / 사람 최종 0) 예방 축 트리거가 발동했다.
74
+ *
75
+ * ⚠ **사람과 방향이 늘 같지는 않다** — 표본 67 은 겹친 풀을 **위로** 뺐고 표본 68 은 **옆 카드를 오른쪽으로**
76
+ * 옮겼다. 이 패스는 후자만 한다. 겹침은 막되 **사람과 다른 그림이 될 수 있다**는 것이 등록된 대가다.
77
+ *
78
+ * 🔴 **동반 범위는 「배치 전」 기하 포함으로 정한다 — 「배치 후」로 정하면 오염된다.** 표본 68 실측:
79
+ * 왼쪽 카드에서 **밀려 나간 노드 3개**(B-83 ⓑ 의 그 이탈)가 배치 후에는 **오른쪽 카드 상자 안**에 들어가
80
+ * 있어, 후 기준으로 카드를 모으면 밀 대상이 14 → **17** 로 부풀고 **남의 노드를 함께 밀어 버린다**.
81
+ * (루트 요소만 옮기므로 실제 이동에서는 걸러지지만, **폐포를 잇는 근거**가 오염되면 카드 판정 자체가 틀린다.)
82
+ *
83
+ * 🔁 **「앞 단계의 판단이 뒤 단계가 바꾼 전제 위에 남는다」의 거울상** — 여기서는 *뒤* 단계가 앞 상태를
84
+ * 근거로 삼아야 옳다(B-51·B-58·B-71·B-88 클래스와 반대 방향).
85
+ *
86
+ * **밀 거리 = 배치 전 틈의 복원**이다(고정 상수가 아니다 — 문서마다 카드 간격이 다르다). 표본 68 실측 =
87
+ * 틈 **60px** · 겹침 308·180 ⇒ dx **368**(사람은 +340 으로 틈 32 를 수용했다 — 같은 방향 28px 차).
88
+ */
89
+ export declare function repairIntroducedPoolOverlaps(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeKeys: ReadonlySet<string>, sceneBefore: GeometryScene): void;
90
+ /**
91
+ * **B-83 ⓐ** — 기존 그룹 상자가 **크기 변화**를 따라가지 못하는 것을 고친다.
92
+ *
93
+ * 표본 67 이 진단을 정밀화했다 — *"안 따라간다"* 가 아니라 **「크기 변화를 못 따라간다」**다(위치 이동은
94
+ * 이미 따라간다). 표본 68 에서 **증언 1순위**로 올라왔고 사람이 상자를 직접 키웠다(Δw+348 Δh+110).
95
+ *
96
+ * 🔴 **수단은 「재산출」이 아니라 「여백 보존」이다.** 5.7 패스(B-51)는 `groupBoundsOf`(멤버 bbox + PAD 40)로
97
+ * 다시 그리는데, 그 수단을 사람 그룹에 걸면 **36/36 이 전부 움직인다**(10px 이내 **0** · 중앙 **30px** ·
98
+ * 최대 **150px**) — 사람이 그린 여백이 상수 40 으로 교체되기 때문이다. 그래서 여기서는 **배치 전 여백을
99
+ * 네 변마다 그대로 유지**한다. 절대식이라 **멱등**이고, 상자가 이미 이동한 경우에도 이중 적용되지 않는다.
100
+ *
101
+ * 🔴 **멤버십은 「배치 전 기하 포함」으로 대신한다** — 사람 문서의 그룹은 **100% 가 `bwl:members` 미선언**
102
+ * 이라(B-83 N20 · 36/36) 5.7 의 `if (!members.length) continue` 게이트에서 통째로 빠진다. ⚠ **`members` 를
103
+ * 선언해 주는 것은 수단이 아니다** — 선언하는 순간 사람이 그린 상자가 멤버 bbox 로 **교체**된다.
104
+ *
105
+ * ⚠ **`repairIntroducedPoolOverlaps` 뒤여야 한다** — 키울 자리를 그 패스가 만든다(표본 68 실측: 자리 없이
106
+ * +368 키우면 이웃 상자 좌단을 **348px 침범**한다). 사람도 **둘 다** 했다(상자 +348 · 이웃 카드 +340).
107
+ */
108
+ export declare function repairIntroducedGroupGrowth(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeKeys: ReadonlySet<string>, sceneBefore: GeometryScene): void;
@@ -11,6 +11,21 @@ import { ResolverServices } from './lower';
11
11
  * - 장애물이 구간 끝에 걸쳐 있음 — 끝점 도킹과 얽혀 잘못 건드리면 연결이 떨어져 보인다
12
12
  */
13
13
  export declare function planSegmentDetours(a: GeometryPoint, b: GeometryPoint, rect: GeometryShape, margin?: number): [GeometryPoint[], GeometryPoint[]] | null;
14
+ /**
15
+ * B-76. 한 구간이 장애물을 **여럿** 가로지를 때 그 **전부를 한 번에** 넘는 4점(순수).
16
+ *
17
+ * `planSegmentDetours` 를 장애물마다 반복하면 우회가 누적돼 **톱니**가 된다 — 실측으로
18
+ * 장애물 5개가 **22점 · 우회 구간 5개**를 만들었고 사용자가 그 선을 지목해 포기했다
19
+ * (표본 60 · 같은 간선이 표본 59 에서도 14점이었다).
20
+ *
21
+ * 🔴 **사람은 예외 없이 한 번에 넘는다** — 실무 코퍼스 26종에서 이 형상이 21건 발화하는데
22
+ * **전량이 우회 구간 1개**다(장애물이 6개인 경우도 4점). 그래서 판별자는 개수가 아니라
23
+ * **위상**(톱니인가 단일인가)이고, 고칠 것은 판정이 아니라 **형상**이다.
24
+ *
25
+ * 반환은 `planSegmentDetours` 와 같은 계약(구간 사이에 끼울 점들 · 두 방향). `null` =
26
+ * 장애물이 하나거나(그때는 구간 우회와 같다) 형상이 자명하지 않다.
27
+ */
28
+ export declare function planMergedSegmentDetour(a: GeometryPoint, b: GeometryPoint, rects: readonly GeometryShape[], margin?: number): [GeometryPoint[], GeometryPoint[]] | null;
14
29
  /**
15
30
  * 꺾임점이 장애물 **안**에 있는 형상 — 구간 우회로는 못 푼다(B-13 ②, 두 번째 표본 잔존 1건).
16
31
  *
@@ -23,6 +38,24 @@ export declare function planSegmentDetours(a: GeometryPoint, b: GeometryPoint, r
23
38
  * `null` = 4점 직각 경로가 아니다.
24
39
  */
25
40
  export declare function planMiddleSegmentShifts(waypoints: readonly GeometryPoint[], rect: GeometryShape, margin?: number): [GeometryPoint[], GeometryPoint[]] | null;
41
+ /**
42
+ * 정방향 간선(목표가 출발의 오른쪽, 다른 행)의 3점 후보 둘(순수) — B-17 ⓒ.
43
+ * h-v: 출발 옆면(오른쪽) → 출발 행에서 가로 → 목표 중심 열에서 목표 윗/아랫면으로
44
+ * v-h: 출발 윗/아랫면 → 목표 행까지 세로 → 목표 왼쪽 옆면으로
45
+ * 같은 행이거나 되돌아가는 간선이면 빈 배열(그쪽은 `backEdgeCandidates` 몫).
46
+ */
47
+ export declare function forwardThreePointCandidates(edge: GeometryEdge, shapes: readonly GeometryShape[]): GeometryPoint[][];
48
+ /**
49
+ * B-19 ⓓ. 다른 풀(위/아래)의 노드로 가는 메시지플로우가 자기 풀 안에서 막힐 때 — **목표 반대편으로 나가**
50
+ * 자기 행 바깥의 채널(출발 도형 가장자리 + 30)을 타고 목표 열까지 간 뒤 직진하는 4점.
51
+ * 열 번째 표본에서 사용자가 `반려 상품 재발송 → 처리 결과 수신` 을 정확히 이렇게 놓았다(재발송 아랫면 →
52
+ * y+30 채널 → 수신 열 → 위로 직진). 종전에는 위로 나간 기본 경로가 게이트웨이를 만나 ㄷ 우회 2회(8점)가
53
+ * 됐다 — 저작 에이전트도 그 지그재그를 보고에 지목했다.
54
+ * 목표 열은 목표 중심에 도킹 편이 0/±25/±40 — 그 열에 다른 메시지가 이미 중심 도킹해 있으면(같은 방향·같은
55
+ * 종류라 finding 은 아니지만 두 화살표가 한 선으로 합쳐 보인다 — 사용자도 -19 비껴 놓았다) 편이 후보가 피한다.
56
+ * 채택은 호출자 몫: 관통 0 을 먼저, 그다음 다른 간선과의 공선 공유가 없는 후보를 고른다.
57
+ */
58
+ export declare function farSideChannelCandidates(edge: GeometryEdge, shapes: readonly GeometryShape[]): GeometryPoint[][];
26
59
  /**
27
60
  * 한 간선의 관통을 없애는 웨이포인트를 찾는다(순수). 못 찾으면 `null`.
28
61
  *
@@ -35,7 +68,7 @@ export declare function planEdgeRepair(edge: GeometryEdge, targets: readonly Geo
35
68
  y: number;
36
69
  width: number;
37
70
  height: number;
38
- }): GeometryPoint[] | null;
71
+ }, others?: readonly GeometryEdge[]): GeometryPoint[] | null;
39
72
  /**
40
73
  * 이 배치가 **새로 만든** 관통을 고친다(하강 직후, undo 에피소드 안에서 호출된다).
41
74
  *
@@ -43,7 +76,29 @@ export declare function planEdgeRepair(edge: GeometryEdge, targets: readonly Geo
43
76
  * 완전히 해소되는 우회만 채택하고, 아니면 손대지 않는다(안전 성질 1) — 부분 개선을 받으면
44
77
  * "고쳤는데 여전히 깨져 있다"가 되어 보고와 실물이 어긋난다.
45
78
  */
46
- export declare function repairIntroducedCrossings(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeKeys: ReadonlySet<string>): void;
79
+ export declare function repairIntroducedCrossings(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeKeys: ReadonlySet<string>,
80
+ /** 2차 호출 전용 — 이 간선들만 본다(B-90. 1차가 돌려준 «주석 때문에 포기» 집합). */
81
+ only?: ReadonlySet<string>): Set<string>;
82
+ /**
83
+ * 🔴 **이 배치가 새로 만든 간선 × 간선 교차 중 「대가 없이 없앨 수 있는 것」만 없앤다**(B-90 ⓑ).
84
+ *
85
+ * 🔴 **판별자를 찾는 축이 아니다 — 다섯 번 무너진 뒤의 결론이다.** 「어떤 교차가 결함인가」를 정적
86
+ * 술어로 가르려 했으나 구성(solid/dashed) · 직선강제 · 풀횡단 · 다중교차 · 회피가능이 전부 기각됐다.
87
+ * 실측이 말한 것은 **회차가 판단을 지배한다**는 것이다(같은 성질의 교차를 g29 는 전부 남기고 g30 은
88
+ * 전부 없앴다). ⇒ 묻지 않는다. **계산이 곧 판별이다** — 라우팅만으로 대가 없이 없앨 수 있으면 없애고,
89
+ * 아니면 그대로 둔다(B-9 "확신 없는 자동 수정보다 정직한 미해결"과 같은 자세).
90
+ *
91
+ * **범위**(N158 — 발동과 함께 정한다):
92
+ * - **이 배치가 만든 교차만**(`beforePairs` 에 없는 쌍). 기존 문서의 교차는 사람이 수용한 것일 수
93
+ * 있고 건드리면 교란이다(확정 7 ③).
94
+ * - **이 배치가 만든 간선만 움직인다**(`beforeEdgeIds` 밖). 남의 경로를 바꾸면 같은 이유로 교란이다.
95
+ * ⚠ 이 제한이 사정권을 줄인다 — 실측(6회차 introduced 30건) = 양쪽 허용이면 9, **신규만이면 6**.
96
+ * 그러나 그 **6 은 전부 사람이 실제로 없앤 것**이고 **사람이 남긴 것은 0** 이다(오탐 0).
97
+ *
98
+ * **대가 판정** — 이 쌍이 사라지고 · 도형 관통이 늘지 않고 · **다른 간선과 새 교차가 0** 이고 ·
99
+ * 읽을 수 없는 중첩이 늘지 않을 때만 채택(🔁 「채택 조건에 대가가 없다」 클래스를 미리 막는다).
100
+ */
101
+ export declare function repairIntroducedEdgeCrossings(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforePairs: ReadonlySet<string>, beforeEdgeIds: ReadonlySet<string>): void;
47
102
  /**
48
103
  * 풀 횡단 메시지플로우의 6점 경로 계획(순수). `null` = 이 간선은 대상이 아니거나(중간 풀 없음 ·
49
104
  * 끝점 해소 불가) 더 나은 열이 없다.
@@ -54,12 +109,79 @@ export declare function planCrossPoolRoute(edge: GeometryEdge, scene: GeometrySc
54
109
  * 안). `beforeEdgeIds` = 하강 전 간선 id — 그 밖의 것만 대상(안전 성질 2: 선재 경로 불가침).
55
110
  */
56
111
  export declare function rerouteIntroducedCrossPoolEdges(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeEdgeIds: ReadonlySet<string>): void;
57
- /** 한 간선의 반대 방향 중첩을 없애거나 줄이는 경로(순수). 못 찾으면 `null`. */
112
+ /** 안쪽 구간(첫·끝 도킹 구간 제외)이 컨테이너 경계선과 나란히 이 거리 안에서 달리면 "경계에 붙었다"고 본다. */
113
+ export declare const BOUNDARY_CLEARANCE = 30;
114
+ /** 웨이포인트의 **안쪽 구간**(도킹 구간 제외)이 어느 컨테이너의 경계선과 나란히 `BOUNDARY_CLEARANCE` 안에서 겹쳐 달리는가. */
115
+ export declare function hugsContainerBoundary(pts: readonly GeometryPoint[], containers: readonly GeometryShape[]): boolean;
116
+ /** 안쪽 구간이 이 거리 안에서 다른 도형 옆을 지나면 "스친다"고 본다(관통은 아니지만 도형 사이 통로를 달리는 선). */
117
+ export declare const NEAR_PASS_MARGIN = 40;
118
+ /** 안쪽 구간(도킹 구간 제외)이 끝점 아닌 도형을 `NEAR_PASS_MARGIN` 안에서 스치는 횟수 — 빈 여백을 달리는 경로가 0. */
119
+ export declare function nearPassCount(pts: readonly GeometryPoint[], shapes: readonly GeometryShape[], endpoints: readonly string[]): number;
120
+ /**
121
+ * 경계에 붙어 달리는 되돌아가는 간선의 재경로(순수, B-26 ⓑ). 스무 번째 표본 `재결재`(담당자 레인 위 행 → 팀장 레인
122
+ * 결재)의 보정 경로 A2 는 채널이 레인 경계선 15px 아래를 달리고 목표 열 세로선이 위 행 노드들 옆을 지나갔다 —
123
+ * 사용자는 출발 아랫면 → 오른쪽 빈 열 → 결재 아래 채널 → 결재 아랫면으로 **빈 여백만 달리는** 경로를 그렸다(그 채널도
124
+ * 경계에서 20px — 경계 근접만으로는 사용자 답까지 버려진다). 그래서 지금 경로가 경계에 붙어 있을 때만 발동하고, 후보는
125
+ * 관통 0 · 읽을 수 없는 중첩 비증가 · 교차 비용 비증가를 지킨 뒤 **(스침 수 + 경계 근접) 최소 → 편이 → 길이** 로 고른다.
126
+ * 같은 레인 안에서 되돌아가는 간선(r2 `Flow_o14`)은 A2 채널이 경계에서 멀어 발동하지 않는다. 없으면 `null`.
127
+ */
128
+ export declare function planBackEdgeBoundaryClearance(edge: GeometryEdge, scene: GeometryScene): GeometryPoint[] | null;
129
+ /** 이 배치가 **새로 만든** 시퀀스 플로우 중 경계에 붙어 달리는 되돌아가는 간선을 재경로한다(B-26 ⓑ). */
130
+ /**
131
+ * 배치가 만든 정방향 간선의 **불필요한 꺾임을 줄인다**(B-34 ⓑ) — bpmn-js 기본 맨해튼 라우팅은 출발 직후
132
+ * 꺾어 목표 행으로 올라간 뒤 가로로 가는 4점을 낸다. 사람은 두 회차 연속(표본 30·31, 같은 간선 같은 변환)
133
+ * **출발 행으로 끝까지 간 뒤 목표 열에서 꺾는 3점**으로 고쳤다.
134
+ *
135
+ * `planEdgeRepair`(B-9 ②)는 **관통이 있을 때만** 돌아 이 형상을 보지 못한다 — 관통이 없는 우회다.
136
+ * 안전 성질은 그대로: 배치가 만든 간선만 · 점 수가 줄 때만 · 관통 0 · 읽을 수 없는 중첩 비증가 ·
137
+ * 교차 비용 비증가. 하나라도 어긋나면 손대지 않는다.
138
+ */
139
+ export declare function repairIntroducedDetours(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeEdgeIds: ReadonlySet<string>): void;
140
+ export declare function repairIntroducedBoundaryHugging(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeEdgeIds: ReadonlySet<string>): void;
141
+ /**
142
+ * 한 간선의 **읽을 수 없는 중첩**(반대 방향 · 종류 다름)을 없애거나 줄이는 경로(순수). 못 찾으면 `null`.
143
+ * 종류 다른 중첩(B-17 ⓐ)의 보정 = 도킹 편이 — "가급적 중심에 도킹하되 어쩔 수 없을 땐 약간 비껴서"
144
+ * (사용자 프레이밍). 기본은 중심이고, finding 이 났을 때만, 최소 편이(±25)부터 본다.
145
+ */
58
146
  export declare function planOppositeOverlapRepair(edge: GeometryEdge, scene: GeometryScene): GeometryPoint[] | null;
59
147
  /**
60
- * 이 배치가 **새로 만든** 반대 방향 중첩을 고친다(하강 직후, undo 에피소드 안). 짝 중 새 간선만
61
- * 손댄다(안전 성질 2) — 둘 다 새 것이면 되돌아가는 간선(A 후보가 있는 쪽)부터.
148
+ * 이 간선의 **가짜 합류**(B-77)를 없애는 경로(순수). 못 찾으면 `null`.
149
+ *
150
+ * 🔴 **기존 중첩 보정(`planOppositeOverlapRepair`)과 예산이 다르고, 그래서 패스를 갈랐다.** 거기는
151
+ * 중첩 1 감소에 예산 1 인데 `crossingCost` 는 같은 종류 교차를 **2** 로 세므로(B-18 ⓑ) *"중첩 하나를
152
+ * 없애려고 같은 종류 교차 하나를 받는" 교환이 구조적으로 불가능*하다. 여기서는 그 교환을 허용한다 —
153
+ * 확정 6 이 *"간선 교차는 결함 축이 아니다"*(교차 5·10 반복 수용)인 반면 가짜 합류는 사용자가
154
+ * *"이해가 안되는 상황"* 으로 지목했다(재하강 대조 2026-09-18).
155
+ *
156
+ * ⚠ **이 예산을 기존 보정에 섞는 수단은 실측으로 기각됐다** — `--check` **46건**이 흔들리고
157
+ * g15-edit1 에서는 **없던 반대 중첩이 하나 생겼다**(🔁 「채택 조건에 대가가 없다」). 중간 단계마다
158
+ * 도는 보정에 예산을 주면 파급이 문서 전체로 간다. 🔴 **축이 다르면 조건도 다르다.**
159
+ */
160
+ export declare function planFakeMergeRepair(edge: GeometryEdge, scene: GeometryScene): GeometryPoint[] | null;
161
+ /**
162
+ * 이 배치가 **새로 만든** 가짜 합류(B-77)를 고친다 — 기존 중첩 보정과 **별도 패스**다(예산이 다르다).
163
+ * 짝 중 **새 간선만** 손댄다(안전 성질 2).
62
164
  */
165
+ export declare function repairIntroducedFakeMerges(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeKeys: ReadonlySet<string>, beforeEdgeIds: ReadonlySet<string>): void;
166
+ /**
167
+ * 이 간선의 **연쇄 왕복 중첩**(B-78)을 없애는 경로(순수). 못 찾으면 `null`.
168
+ *
169
+ * 🔴 **기존 중첩 보정과 예산이 달라 패스를 갈랐다** — B-77 과 같은 이유다. `planOppositeOverlapRepair`
170
+ * 는 중첩 1 감소에 예산 1 인데 `crossingCost` 는 같은 종류 교차를 **2** 로 세므로(B-18 ⓑ) *"중첩 하나를
171
+ * 없애려고 같은 종류 교차 하나를 받는"* 교환이 **구조적으로 불가능**하다. 실측(표본 61) = 도킹 편이
172
+ * 후보 넷 중 **왼쪽 둘은 중첩 0 · 신규 관통 0 인데 교차 +1 로 예산에서 탈락**했고 오른쪽 둘은 기존
173
+ * 노드를 관통해 탈락했다 — **네 후보가 두 게이트에 반씩 걸려 전멸**했다.
174
+ *
175
+ * ⚠ **기존 보정의 예산을 올리는 안은 이미 실측 기각**이다(`--check` 46·47건 · 없던 중첩 1 — B-77 ⓐ).
176
+ * ⚠ **후보 생성기는 기존 3종 그대로 둔다** — 대각 후보(B-77)를 더하면 이 패스가 고르는 경로가 넓어진다
177
+ * (🔁 「넓히려는 충동」). 표본 61 형상은 도킹 편이만으로 풀린다.
178
+ */
179
+ export declare function planChainOverlapRepair(edge: GeometryEdge, scene: GeometryScene): GeometryPoint[] | null;
180
+ /**
181
+ * 이 배치가 **새로 만든** 연쇄 왕복 중첩(B-78)을 고친다 — 기존 중첩 보정과 **별도 패스**다(예산이 다르다).
182
+ * 짝 중 **새 간선만** 손댄다(안전 성질 2).
183
+ */
184
+ export declare function repairIntroducedChainOverlaps(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeKeys: ReadonlySet<string>, beforeEdgeIds: ReadonlySet<string>): void;
63
185
  export declare function repairIntroducedOppositeOverlaps(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeKeys: ReadonlySet<string>, beforeEdgeIds: ReadonlySet<string>): void;
64
186
  /**
65
187
  * 라벨의 새 bounds(순수). 라벨이 공유 구간 위에 없으면 `null`(옮길 이유가 없다). 고유 구간을 못
@@ -69,8 +191,96 @@ export declare function repairIntroducedOppositeOverlaps(services: Pick<Resolver
69
191
  * 곳을 고른다 — 부분 공유 구간(갈림점 아래로 이어지는 세로 구간)도 그 아래쪽이 자유 부분이다.
70
192
  */
71
193
  export declare function planLabelPlacement(edge: GeometryEdge, scene: GeometryScene): GeometryLabel | null;
194
+ /** 라벨 상자와 선 사이 최소 여백(px). 가로 구간 라벨의 bpmn-js 기본(중심 15, 높이 14 → 아랫변 8 위)과 같은 값. */
195
+ export declare const LABEL_LINE_GAP = 8;
196
+ /**
197
+ * 라벨의 새 bounds(순수) — 어떤 선(자기 구간 · 다른 간선 구간 · 컨테이너 경계선)이든 상자를 가로지르면, 라벨이 붙은
198
+ * 구간(중심에 가장 가까운 자기 구간)의 옆자리 후보를 순서대로 시험해 **아무 선도 지나지 않는 첫 자리**로 옮긴다.
199
+ * 순서 = 세로 구간이면 오른쪽 → 왼쪽, 가로 구간이면 위 → 아래; 각각 구간 방향으로 0 · 위/왼쪽 · 아래/오른쪽 · 2단 편이
200
+ * (사용자는 레인 경계에 걸친 라벨을 **위로** 올렸다 — 위/왼쪽 우선). 구간 밖으로는 안 나간다. 전부 막히면 `null`
201
+ * (판정기가 보고한다). 바꿀 것이 없으면 `null`.
202
+ */
203
+ export declare function planLabelLineClearance(edge: GeometryEdge, scene: GeometryScene): GeometryLabel | null;
204
+ /**
205
+ * ── B-99. **배치가 옮긴 선을 선재 라벨이 따라간다** ──
206
+ *
207
+ * 🔴 **등록 진단이 뒤집혔다**(🔁 B-38·B-47·B-48·B-62·B-71·B-86). 원장은 *"라벨이 어느 경로에도 안
208
+ * 실린다"* 였는데 **세로 경로에는 실려 있다** — g39 전수에서 라벨 보유 간선 10 건의 **y 는 10/10
209
+ * 추종**이고 **x 만 3/10** 이다. 프로브가 자리를 `modeling.createSpace` 한 곳으로 특정했다(그 한 번의
210
+ * 전후가 `wp0(4555,869) → (4728,869)` 인데 라벨은 `(4572,851)` 로 그대로). 근인은 그 `moving` 이
211
+ * `pool.children` 인데 **간선 라벨의 부모는 루트**라는 것이다.
212
+ *
213
+ * 🔴 **원장의 기각 수단이 왜 안 들었는지도 이것이 설명한다** — `growLaneMakingSpace` 의 **세로** 델타를
214
+ * 라벨에 더한 실험은 **이미 추종 중인 축에 중복 가산**이라 되레 멀어졌다(146.4 → 151.6). 원장 해석
215
+ * (*"세로 52 가 부호도 못 정한다"*)은 증상이고 근인은 중복 가산이다.
216
+ *
217
+ * 🔴 **그런데 `createSpace` 직후에 고치면 안 된다 — 실측이 기각했다.** 거기서 델타를 재어 옮겼더니
218
+ * g37 의 라벨 다섯이 **+615 로 끌려가 되레 545~573px 로 벌어졌다**(그 간선들은 뒤 단계에서 제자리로
219
+ * 돌아왔는데 라벨만 남았다). 🔁 **「앞 단계의 판단이 뒤 단계가 바꾼 전제 위에 남는다」**(B-51·B-58·
220
+ * B-71·B-88 클래스) ⇒ **기준선은 배치 시작, 적용은 경로가 전부 확정된 뒤**다.
221
+ *
222
+ * ⚠ **델타 추종이지 재계산이 아니다** — 사람이 놓은 상대 위치를 **보존**한 채 선만 따라간다(🔁 B-74 와
223
+ * 같은 철학). **균일하게 평행이동한 간선만** 대상이라 델타가 정의된다 — 모양이 바뀐 간선(우회·꺾임
224
+ * 변경)은 「따라간다」가 뜻을 잃으므로 조건에서 저절로 빠진다(마지막 관통 보정이 그래서 안전하다).
225
+ *
226
+ * ⚠ **대가가 있다 — 따라간 자리가 남의 무엇 위일 수 있다**(🔁 「채택 조건에 대가가 없다」). **개별
227
+ * 축을 나열하면 두더지 잡기가 된다** — 처음에 「선 얹힘」만 막았더니 회귀선이 `label-over-line` 을
228
+ * 잡았고(B-94 ⓐ fixture), 그것을 막자 이번엔 **`annotation-over-label`** 이 났다(g14 · `--check`).
229
+ * 🔴 **B-90 ⓑ·B-92 ⓐ 가 이미 일반화해 둔 자리다: 채택 조건은 `auditGeometry` **키 집합**이다.**
230
+ * ⚠ **총량이 아니라 키 집합**이어야 한다 — 사라진 것과 생긴 것이 상쇄되면 총량은 침묵한다(🔁 B-70).
231
+ *
232
+ * ⚠ **판정은 순수 scene 으로 하고 실제 이동은 통과한 것만** 한다 — 옮겼다 되돌리는 방식은 **되돌리기
233
+ * 자체가 1px 어긋난다**(B-74 실측 · 그 이동이 스냅을 탄다). 채택한 라벨은 scene 에 반영해 다음 라벨의
234
+ * 판정 기준으로 쓴다(누적).
235
+ *
236
+ * 라벨 하나씩 재고 새 키가 생기면 **그 라벨만** 두고 간다(부분 해소 허용 — 🔁 B-61).
237
+ */
238
+ export declare function followMovedEdgeLabels(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, before: ReadonlyMap<string, {
239
+ wps: ReadonlyArray<{
240
+ x: number;
241
+ y: number;
242
+ }>;
243
+ label: {
244
+ x: number;
245
+ y: number;
246
+ };
247
+ }>): void;
248
+ /** 이 배치가 **새로 만든** 간선의 라벨을 선·경계선에서 뗀다(맨 마지막 — 라벨 귀속 보정 뒤). 선재 라벨은 불가침. */
249
+ export declare function repairIntroducedLabelLines(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeEdgeIds: ReadonlySet<string>): void;
72
250
  /**
73
251
  * 이 배치가 **새로 만든** 간선의 라벨 귀속 문제를 고친다(하강 직후, undo 에피소드 안, 경로 보정들 뒤).
74
252
  * 선재 간선의 라벨은 사람이 놓았을 수 있어 손대지 않는다(안전 성질 2).
75
253
  */
76
254
  export declare function repairIntroducedSharedSegmentLabels(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeKeys: ReadonlySet<string>, beforeEdgeIds: ReadonlySet<string>): void;
255
+ /** 라벨 상자를 가로지르는 선을 피한 새 자리(순수). 같은 쪽에서 선 밖으로 비끼기 → 도형 상·하·좌·우 기본 자리. 없으면 `null`. */
256
+ export declare function planShapeLabelClearance(shape: GeometryShape, scene: GeometryScene): GeometryLabel | null;
257
+ /** 이 배치가 **새로 만든** 도형의 이름 라벨을 선에서 뗀다(맨 마지막 — 경로·간선 라벨이 전부 확정된 뒤). */
258
+ export declare function repairIntroducedShapeLabelLines(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeShapeIds: ReadonlySet<string>): void;
259
+ /**
260
+ * ── B-98. **이 배치가 도형을 옮겨 놓고 간 끝점을 다시 도킹한다** ──
261
+ *
262
+ * 🔴 **등록 진단이 뒤집혔다**(🔁 B-38·B-47·B-48·B-62·B-71·B-86·B-99). 원장은 *"주석 보정이 주석만
263
+ * 옮기고 연관선을 두고 간다"* 였는데 **주석 보정(`repairIntroducedAnnotationOverlaps`)은 이 요소에
264
+ * 손대지 않았다** — 단계 경계 프로브가 보정 패스 **17개 전부 no-op** 이고 전부 `lowerOps` 안에서
265
+ * 벌어졌음을 보였다(N22). 실제 경위는 **두 단계**다:
266
+ * ① `pushRootBelow` **직전** 이미 붕괴해 있었다 — 풀이 아래로 커지자(h340 → 450) 주석이 **풀 안**에
267
+ * 들어갔고 bpmn-js 가 연관선을 **길이 0**(285,1296)→(285,1296) 으로 크롭했다.
268
+ * ② `pushRootBelow` 가 주석을 +110 내리자 **끝점 하나만** 따라가 (285,1296)→(285,1406) 이 됐다.
269
+ * ⇒ 「선을 두고 간다」가 아니라 **붕괴를 상속한다**. 어느 쪽이든 최종 기하에 대고 다시 도킹해야 한다.
270
+ *
271
+ * 🔴 **N20 이 처방 강도를 줬다**(실무 26종 · 간선 1,456/1,606): 끝점 이탈 **`>40px` 0.00%**(최대가
272
+ * 40 이하 · `>20px` 도 3건뿐이고 전량 association) · **길이 `<1px` 0/1606**. 이 결함은 54px + 길이 0
273
+ * 이라 **두 축 모두 사람 기저 밖**이다 ⇒ 처방이 오탐을 살 여지가 없다.
274
+ *
275
+ * ⚠ **재경로가 아니라 재도킹이다** — `layoutConnection` 은 선재 간선의 사람 웨이포인트를 **버린다**
276
+ * (확정 7 ③ 교란). 그래서 **어긋난 끝점만** 자기 도형 경계의 **가장 가까운 점**으로 옮기고 중간
277
+ * 웨이포인트는 그대로 둔다(🔁 B-74·B-99 의 「사람이 놓은 것은 보존」 철학).
278
+ *
279
+ * ⚠ **컨테이너 끝도 함께 본다** — 판정(`edge-endpoint-detached`)은 컨테이너를 제외하지만(풀은 정상적으로
280
+ * 경계에 도킹한다) 그것은 **신고 대상**의 이야기다. 신고된 간선을 고칠 때 반대쪽 풀 끝이 풀 **안**에
281
+ * 처박혀 있으면 같은 붕괴의 나머지 반쪽이므로 함께 뺀다(실측 = 그 끝이 풀 하단보다 14px 안).
282
+ *
283
+ * ⚠ **채택 조건은 개별 축이 아니라 `auditGeometry` 키 집합**(🔁 B-90 ⓑ·B-92 ⓐ·B-99). 판정은 순수
284
+ * `scene` 으로 하고 통과한 간선만 실제로 옮긴다 · 새 키가 생기면 **그 간선만** 두고 간다(🔁 B-61).
285
+ */
286
+ export declare function redockIntroducedDetachedEndpoints(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeKeys: ReadonlySet<string>): void;
@@ -1,5 +1,5 @@
1
- import { BwlBand, BwlPlacement, BwlPoolOrder } from '../core/hints';
2
- import { IrAssociation, IrDataAssociation, IrDataStore, IrFlowCondition, IrFlowNodeType, IrGroup, IrLane, IrMessageFlow, IrNode, IrParticipant, IrSequenceFlow, IrTextAnnotation } from '../core/ir';
1
+ import { BwlAfterColumn, BwlBand, BwlPlacement, BwlPoolOrder } from '../core/hints';
2
+ import { IrAssociation, IrDataAssociation, IrDataStore, AddableFlowNodeType, IrFlowCondition, IrGroup, IrLane, IrMessageFlow, IrNode, IrParticipant, IrSequenceFlow, IrTextAnnotation } from '../core/ir';
3
3
  /** LLM 이 산출하는 단일 심볼릭 op. `kind` 로 판별한다. */
4
4
  export type SymbolicOp =
5
5
  /** 풀 추가. `order` = 풀 세로 순서 힌트(생략 시 op 순서가 곧 배치 순서). */
@@ -8,11 +8,15 @@ export type SymbolicOp =
8
8
  participant: IrParticipant;
9
9
  order?: BwlPoolOrder;
10
10
  }
11
- /** 플로우 노드 추가. `node.lane` 은 좌표 결정에만 쓰인다(A-3 — flowNodeRef 는 bpmn-js 지오메트리 자동 유지). `band` = 프로세스 내부 가로 밴드 힌트. */
11
+ /**
12
+ * 플로우 노드 추가. `node.lane` 은 좌표 결정에만 쓰인다(A-3 — flowNodeRef 는 bpmn-js 지오메트리 자동 유지).
13
+ * `band` = 프로세스 내부 가로 밴드(행) 힌트 · `afterColumn` = **열** 지목 힌트(그 노드보다 뒤 열 · 표본 73).
14
+ */
12
15
  | {
13
16
  kind: 'node.add';
14
17
  node: IrNode;
15
18
  band?: BwlBand;
19
+ afterColumn?: BwlAfterColumn;
16
20
  } | {
17
21
  kind: 'sequenceFlow.add';
18
22
  flow: IrSequenceFlow;
@@ -56,12 +60,15 @@ export type SymbolicOp =
56
60
  lanes?: IrLane[];
57
61
  }
58
62
  /** `lane` = 레인 재배정(이동). `type` = 태스크/이벤트 타입 전환(bpmn-js replace 하강 — 외래 확장 속성 유실 가능성은 불변식 2 가 완충). */
63
+ /** 노드 갱신. `band` = 기존 노드의 행 재지정(B-72 ⓐ 승격) — 기존 컨테이너에서 band 는 **행 identity** 다. */
64
+ /** ⚠ `type` 은 **만들 수 있는 집합**으로 좁다 — read 집합(11)이 아니다(B-87 ⓐ). 타입이 0차 방어다. */
59
65
  | {
60
66
  kind: 'node.update';
61
67
  id: string;
62
68
  name?: string | null;
63
69
  lane?: string;
64
- type?: IrFlowNodeType;
70
+ type?: AddableFlowNodeType;
71
+ band?: BwlBand;
65
72
  }
66
73
  /**
67
74
  * `source`/`target` = 끝점 재배선(B-6 승격). 한쪽만 줘도 되고(나머지 보존) 새 끝점은