isthmus-cli 0.5.0 → 0.7.0

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 (146) hide show
  1. package/README.ko.md +170 -45
  2. package/README.md +178 -36
  3. package/Skills/isthmus/SKILL.md +104 -5
  4. package/compatibility.json +10 -0
  5. package/dist/cli/check-command.d.ts +2 -2
  6. package/dist/cli/check-command.js +22 -9
  7. package/dist/cli/check-command.js.map +1 -1
  8. package/dist/cli/command-support.d.ts +20 -2
  9. package/dist/cli/command-support.js +38 -9
  10. package/dist/cli/command-support.js.map +1 -1
  11. package/dist/cli/diff-command.js +4 -3
  12. package/dist/cli/diff-command.js.map +1 -1
  13. package/dist/cli/doctor-command.d.ts +11 -0
  14. package/dist/cli/doctor-command.js +128 -0
  15. package/dist/cli/doctor-command.js.map +1 -0
  16. package/dist/cli/extract-js-command.d.ts +33 -0
  17. package/dist/cli/extract-js-command.js +233 -0
  18. package/dist/cli/extract-js-command.js.map +1 -0
  19. package/dist/cli/graph-command.js +13 -6
  20. package/dist/cli/graph-command.js.map +1 -1
  21. package/dist/cli/impact-command.d.ts +5 -0
  22. package/dist/cli/impact-command.js +111 -0
  23. package/dist/cli/impact-command.js.map +1 -0
  24. package/dist/cli/init-command.d.ts +11 -0
  25. package/dist/cli/init-command.js +115 -0
  26. package/dist/cli/init-command.js.map +1 -0
  27. package/dist/cli/main.js +67 -1
  28. package/dist/cli/main.js.map +1 -1
  29. package/dist/cli/mcp-server.d.ts +16 -0
  30. package/dist/cli/mcp-server.js +534 -0
  31. package/dist/cli/mcp-server.js.map +1 -0
  32. package/dist/cli/preflight-command.d.ts +5 -0
  33. package/dist/cli/preflight-command.js +83 -0
  34. package/dist/cli/preflight-command.js.map +1 -0
  35. package/dist/cli/query-command.js +13 -6
  36. package/dist/cli/query-command.js.map +1 -1
  37. package/dist/cli/retentions-command.js +14 -7
  38. package/dist/cli/retentions-command.js.map +1 -1
  39. package/dist/cli/runtime-command.d.ts +5 -0
  40. package/dist/cli/runtime-command.js +42 -0
  41. package/dist/cli/runtime-command.js.map +1 -0
  42. package/dist/cli/runtime-json-reader.d.ts +12 -0
  43. package/dist/cli/runtime-json-reader.js +37 -0
  44. package/dist/cli/runtime-json-reader.js.map +1 -0
  45. package/dist/cli/serve-command.d.ts +13 -0
  46. package/dist/cli/serve-command.js +43 -0
  47. package/dist/cli/serve-command.js.map +1 -0
  48. package/dist/exchange/capture-config.d.ts +18 -0
  49. package/dist/exchange/capture-config.js +100 -0
  50. package/dist/exchange/capture-config.js.map +1 -0
  51. package/dist/exchange/impact-selection.d.ts +13 -0
  52. package/dist/exchange/impact-selection.js +35 -0
  53. package/dist/exchange/impact-selection.js.map +1 -0
  54. package/dist/exchange/kartograph-impact.d.ts +4 -0
  55. package/dist/exchange/kartograph-impact.js +169 -0
  56. package/dist/exchange/kartograph-impact.js.map +1 -0
  57. package/dist/exchange/messages.d.ts +36 -0
  58. package/dist/exchange/messages.js +96 -0
  59. package/dist/exchange/messages.js.map +1 -0
  60. package/dist/exchange/parse.d.ts +69 -0
  61. package/dist/exchange/parse.js +127 -20
  62. package/dist/exchange/parse.js.map +1 -1
  63. package/dist/exchange/preflight-context.d.ts +71 -0
  64. package/dist/exchange/preflight-context.js +317 -0
  65. package/dist/exchange/preflight-context.js.map +1 -0
  66. package/dist/exchange/producer-impact.d.ts +17 -0
  67. package/dist/exchange/producer-impact.js +226 -0
  68. package/dist/exchange/producer-impact.js.map +1 -0
  69. package/dist/exchange/runtime.d.ts +69 -0
  70. package/dist/exchange/runtime.js +160 -0
  71. package/dist/exchange/runtime.js.map +1 -0
  72. package/dist/extract/js-document.d.ts +22 -0
  73. package/dist/extract/js-document.js +230 -0
  74. package/dist/extract/js-document.js.map +1 -0
  75. package/dist/extract/js-scan.d.ts +93 -0
  76. package/dist/extract/js-scan.js +1205 -0
  77. package/dist/extract/js-scan.js.map +1 -0
  78. package/dist/extract/lexer.d.ts +26 -0
  79. package/dist/extract/lexer.js +316 -0
  80. package/dist/extract/lexer.js.map +1 -0
  81. package/dist/join/join.d.ts +66 -1
  82. package/dist/join/join.js +171 -1
  83. package/dist/join/join.js.map +1 -1
  84. package/dist/join/message-address.d.ts +7 -0
  85. package/dist/join/message-address.js +37 -0
  86. package/dist/join/message-address.js.map +1 -0
  87. package/dist/join/messages.d.ts +26 -0
  88. package/dist/join/messages.js +87 -0
  89. package/dist/join/messages.js.map +1 -0
  90. package/dist/report/check-report.d.ts +20 -3
  91. package/dist/report/check-report.js +240 -8
  92. package/dist/report/check-report.js.map +1 -1
  93. package/dist/report/codequality.d.ts +45 -0
  94. package/dist/report/codequality.js +74 -0
  95. package/dist/report/codequality.js.map +1 -0
  96. package/dist/report/diff.d.ts +25 -3
  97. package/dist/report/diff.js +91 -10
  98. package/dist/report/diff.js.map +1 -1
  99. package/dist/report/graph.d.ts +10 -4
  100. package/dist/report/graph.js +60 -6
  101. package/dist/report/graph.js.map +1 -1
  102. package/dist/report/impact.d.ts +62 -0
  103. package/dist/report/impact.js +168 -0
  104. package/dist/report/impact.js.map +1 -0
  105. package/dist/report/preflight-runtime.d.ts +42 -0
  106. package/dist/report/preflight-runtime.js +193 -0
  107. package/dist/report/preflight-runtime.js.map +1 -0
  108. package/dist/report/preflight-view.d.ts +146 -0
  109. package/dist/report/preflight-view.js +216 -0
  110. package/dist/report/preflight-view.js.map +1 -0
  111. package/dist/report/preflight.d.ts +104 -0
  112. package/dist/report/preflight.js +331 -0
  113. package/dist/report/preflight.js.map +1 -0
  114. package/dist/report/query.d.ts +11 -4
  115. package/dist/report/query.js +84 -17
  116. package/dist/report/query.js.map +1 -1
  117. package/dist/report/retentions.d.ts +17 -5
  118. package/dist/report/retentions.js +85 -8
  119. package/dist/report/retentions.js.map +1 -1
  120. package/dist/report/rules.d.ts +16 -0
  121. package/dist/report/rules.js +30 -0
  122. package/dist/report/rules.js.map +1 -0
  123. package/dist/report/runtime-impact.d.ts +38 -0
  124. package/dist/report/runtime-impact.js +94 -0
  125. package/dist/report/runtime-impact.js.map +1 -0
  126. package/dist/report/runtime.d.ts +54 -0
  127. package/dist/report/runtime.js +137 -0
  128. package/dist/report/runtime.js.map +1 -0
  129. package/dist/report/sarif.d.ts +2 -8
  130. package/dist/report/sarif.js +2 -10
  131. package/dist/report/sarif.js.map +1 -1
  132. package/dist/report/sorted-json.d.ts +1 -1
  133. package/dist/report/sorted-json.js +2 -2
  134. package/dist/report/sorted-json.js.map +1 -1
  135. package/docs/BRIDGE-EVENTS.md +87 -0
  136. package/docs/BRIDGE-MESSAGES.md +117 -0
  137. package/docs/GRAPH-EXCHANGE.md +382 -0
  138. package/docs/IMPACT.md +96 -0
  139. package/docs/MCP.md +62 -0
  140. package/docs/PREFLIGHT.md +311 -0
  141. package/docs/RUNTIME.md +172 -0
  142. package/docs/TOOLCHAIN.md +101 -0
  143. package/package.json +14 -2
  144. package/scripts/build-preflight-toolchain.mjs +186 -0
  145. package/scripts/capture-preflight.mjs +380 -0
  146. package/scripts/run-child.mjs +17 -0
@@ -0,0 +1,311 @@
1
+ # 언어 간 변경 사전 점검
2
+
3
+ `isthmus preflight`는 npm 0.7.0 발행본에 포함되며 JSON만 읽는다. 공개 호환 버전은
4
+ [COMPATIBILITY.md](COMPATIBILITY.md), 고정 source commit에서 새로 구축하는 방법은
5
+ [TOOLCHAIN.md](TOOLCHAIN.md)에 있다.
6
+ 언어 내부 해석과 compiler index 생성은 producer 및 별도 workflow가 맡는다.
7
+ Flutter Dart↔Swift/Kotlin의 MethodChannel과 producer가 제공한 사용 관계를 연결한다.
8
+ 선택적 [BasicMessageChannel v2 입력](BRIDGE-MESSAGES.md)을 함께 수집하면 literal 주소와
9
+ Pigeon의 증명된 prefix 후보도 연결한다. 플랫폼별 실제 실행·모든 생성 형태·앱 전체 정확도는
10
+ 별도 검증 범위다. Kotlin 및 Basic producer 확장은 공개 버전(cartograph 0.18.0·kartograph
11
+ 0.10.2·dartograph 0.14.0)에 포함됐다.
12
+
13
+ ```bash
14
+ isthmus preflight context.json --strict --compact
15
+ isthmus preflight context.json --summary --limit 20 --strict --compact
16
+ isthmus preflight context.json --explain <exact-producer-symbol-id> --strict --compact
17
+ isthmus preflight context.json --revision <expected-capture-revision> --strict --compact
18
+ isthmus preflight context.json success.json failure.json --expectations checks.json --strict --compact
19
+ ```
20
+
21
+ `impact`는 bridge-facts에서 직접 관련된 경계를 찾는다. `preflight`는 언어별 전이 분석도
22
+ 입력받아 Swift helper→handler→채널→Dart caller→Dart 소비자를 연결한다. 두 명령 모두
23
+ `complete: false`이며 삭제 허가나 영향 범위의 완전성을 보증하지 않는다.
24
+
25
+ ## 입력 계약
26
+
27
+ `isthmus-preflight-context` v1은 다음을 담는다. 예제는 저장소의
28
+ `fixtures/preflight/context.json`이며 실제 앱 입력으로 사용할 수 없는 합성 자료다.
29
+
30
+ - `project`: 두 bridge-facts 문서와 동일한 정규화된 프로젝트 경로.
31
+ - `revision`: 같은 수집의 신원을 나타내는 비어 있지 않은 문자열. 필드만으로 로컬 코드의
32
+ 신선도를 확인할 수 없다. 아래 수집기는 명시된 입력 내용의 SHA-256을 사용한다.
33
+ - `selection`: `dart`·`swift`·`kotlin`별 `{files, symbols}`. 파일은 project 상대 경로이며 이름·USR은
34
+ producer가 해석한다. `{}`는 `noChanges`이고 검증되지 않은 선택과 구분된다.
35
+ - `bridges`: 같은 project의 Dart와 Swift 또는 Kotlin bridge-facts v1. 호출/수신 문서가 모두 필요하며 mixed-targets는 거부한다.
36
+ - `messages`: 선택적 Basic 전용 bridge-facts v2 목록. 주면 같은 project의 Dart와 native 문서가
37
+ 모두 필요하다. v1과 별도 transport로 조인하며 method를 합성하지 않는다.
38
+ - `analyses`: 언어별 `{id, platform, tool, requested, roots, affected, limitations, truncated}`.
39
+ 심볼은 `{id, qualifiedName, kind?, location?}`이고 위치는 producer가 관찰한 소스 위치다. affected 항목은
40
+ `{symbol, via, depth, relationships}`이다. `via`가 가리키는 선행 심볼을 사용하므로 영향을
41
+ 받는 소비자라는 뜻이다. 원래 producer ID를 유지하며 누락된 위치를 만들지 않는다.
42
+ Kotlin/JVM 심볼 위치는 관찰된 `{path, line?, column?}`를 보존한다. bytecode에 없는
43
+ 줄·열 번호를 1로 채우지 않는다. Dart binding과 bridge fact의 위치는 기존처럼 완전한
44
+ line/column을 요구하며, 부분 위치는 Kotlin 분석 심볼에만 허용한다.
45
+ JVM line table의 줄은 함수 선언보다 첫 실행 구문을 가리킬 수 있으므로 선언 시작으로
46
+ 다시 해석하지 않는다.
47
+ - `trigger`: 후속 Dart 분석에만 쓰는 선택적 필드. 브리지에서 도달한 호출자 ID이며, 해당
48
+ 분석의 유일한 symbol 요청과 root에 포함되어야 한다. 초기 선택으로 다시 세지 않는다.
49
+ - `bindings`: Dart fact 위치와 실제 query로 얻은 심볼의 연결. `{platform:"dart", location,
50
+ requested, symbol}`. requested는 fact의 짧은 이름과 같아야 하고 선언과 호출 파일이 같아야
51
+ 한다. query qualifiedName은 전체 graph ID일 수 있어 requested와 같다고 강제하지 않는다.
52
+ - `limitations`: 수집 단계에서 남은 공백. producer 공백·미귀속·truncation도 보고서에서 보존한다.
53
+
54
+ 분석 ID·분석 안의 심볼 ID는 유일해야 한다. 부모 누락·순환을 만드는 depth 불일치·다른
55
+ 프로젝트·모르는 플랫폼을 거부한다. 초기 requested의 합집합은 selection과 정확히 같아야 한다.
56
+ 상한은 context 텍스트 UTF-16 길이 64 Mi, 분석 256개, roots+affected 총 50,000개,
57
+ binding 100,000개, 각 producer depth 128, 관계 문자열 32개, 조합된 근거 1,000,000개다.
58
+
59
+ ## 출력과 해석
60
+
61
+ 보고서는 `isthmus-preflight` v1, `scope: cross-language-impact`다.
62
+
63
+ - `roots`, `affected[].via/depth/relations`로 가장 가까운 변경 root까지 경로를 복원한다.
64
+ `language` 관계와 실제 fact를 가리키는 bridge 관계를 구분한다.
65
+ - `boundaries`는 영향받는 소비자 경계와 검토할 의존 경계를 구분한다. Dart 호출자를 바꾼다는
66
+ 이유로 변경되지 않은 native handler의 다른 호출자에게까지 영향을 전파하지 않는다.
67
+ - Basic의 완전한 handler 범위 귀속 근거가 있으면 실제 dependency/dispatch 후보로 해당
68
+ 경계에만 전파한다. `bridge-message-dependency`는 원래 참조 위치·대상과 선택된 dispatch
69
+ 후보를 보존한다. 등록 함수 직접 변경과 closure 밖 공유 의존 변경은 전체 관련 배선을
70
+ 포함한다. 근거가 없으면 넓은 후보와 `unresolved-message-handler-scope` 공백을 유지한다.
71
+ - 전송별 의존 관계 종류는 구분된다. 일반 MethodChannel 경로는 `bridge-method-dependency`,
72
+ BasicMessageChannel은 `bridge-message-dependency`, EventChannel은
73
+ `bridge-stream-dependency`다 — 소비자가 전송을 관계 종류로도 구분할 수 있다.
74
+ - `reviewFiles`, `issues`, `limitations`, `bridgeLimitations`와 선택적 `messageLimitations`를 함께 읽는다. 바인딩 부재,
75
+ 후속 Dart 분석 부재, 미관찰 선택, producer truncation은 검토가 필요한 공백이다.
76
+ - compact는 공백만 제거한다. `--strict`는 관련 error·공백·미관찰 선택 또는 revision 불일치에
77
+ 코드 1을 반환하고 JSON을 유지한다. 입력 계약/읽기 실패는 2, 사용 오류는 64다.
78
+ - `noChanges`는 모델링한 소스 선택이 없다는 뜻이다. 설정·리소스 변경 공백이 있으면 strict는
79
+ 여전히 실패한다. 알 수 없는 선택을 영향 없는 변경으로 처리하지 않는다.
80
+
81
+ ### 작은 요약과 개별 경로
82
+
83
+ `--summary`는 `isthmus-preflight-summary` v1이다. 전체 분석의 `summary`·`requiresReview`를
84
+ 유지하고 목록을 `{total, items, omitted}`로 제한한다. 기본 20개, `--limit`은 1~100이며
85
+ 요약에만 쓴다. 생략한 항목도 분석·strict 판정에 포함된다. 원문 producer 근거는 context의
86
+ sidecar와 전체 보고서에서 확인한다. `--compact`는 각 출력의 JSON 공백만 줄인다.
87
+
88
+ `--explain <selector>`는 `isthmus-preflight-explanation` v1이다. 정확한 subject key,
89
+ producer ID, qualifiedName 순서로 찾는다. `result.path`는 root에서 대상까지 **전체 배열**이고
90
+ 각 단계의 relations만 표시 한도가 있다. `found`이면 전체 보고서의 strict 종료 코드를
91
+ 유지하며 `notFound`·`ambiguous`는 JSON과 종료 코드 64를 반환한다. summary와 explain은
92
+ 동시에 쓰지 않는다. 처음에 summary로 찾은 ID를 explain에 전달하면 대형 JSON을 반복해서
93
+ 읽지 않고 필요한 경로를 얻을 수 있다.
94
+
95
+ 두 뷰의 `runtime.verification.declaredScenarioPlatforms`는 선언된 scenario/platform 쌍 수다.
96
+ 통과한 시나리오 수가 아니며 `passedChecks`와도 구분한다. 일부 표시 목록이 비어 있거나
97
+ 잘려도 전체 검토 필요 상태와 근거 공백은 사라지지 않는다.
98
+ runtime route의 candidateKey로 후보 목록을 찾을 수 있으며 `handlers`도 `{total, items, omitted}`로
99
+ native 위치·심볼을 제공한다. 이 omitted는 뷰의 표시 생략까지 포함하며 기존 `handlersOmitted`는
100
+ 전체 runtime 보고서가 먼저 적용한 후보 표시 상한의 생략 수다. 둘을 합산하지 않는다.
101
+
102
+ ## 전이 분석과 runtime 대조
103
+
104
+ `--expectations <checks.json>`를 주면 context 뒤의 위치 인자는 bridge-runtime v1 기록이다.
105
+ 기대만 주고 기록을 생략할 수도 있으며 이는 미관찰 검증으로 남는다. 기록만 주고 기대를
106
+ 생략하면 사용 오류다. 각 기대/기록은 UTF-16 16 Mi, context를 포함한 전체 입력은 64 Mi,
107
+ runtime 문서는 최대 256개다. [런타임 계약](RUNTIME.md)의 독립 기대·실패·미완료 규칙을 재사용한다.
108
+
109
+ - `runtime.verification`: 선언된 시나리오 자체의 검증. 기대 목록과 기록이 과거 revision에서
110
+ 서로 일치하면 이 부분만 passed일 수 있으므로 `runtime.aligned`도 확인해야 한다.
111
+ - `runtime.aligned`: 기대 목록의 revision과 정적 context revision의 일치 여부. 다르면
112
+ `stale-runtime-expectations` 공백을 추가해 strict가 실패한다. 원본 revision을 고쳐 쓰지 않는다.
113
+ - `runtime.routes`: 현재 context revision의 관련 실행 주소. run·scenario·OS·instance·outcome
114
+ 집계를 보존하며 정적 후보는 `candidates`라고 표시한다. 특정 native 심볼이 실제 실행됐다는
115
+ 증명이 아니다. 기존 `affected` 경로나 정적 동적/미귀속 공백은 덮어쓰지 않는다.
116
+ - `runtime.candidates`: 동적 호출로 새로 찾은 native 후보의 실제 fact 위치·심볼. route의
117
+ `candidateKey`로 찾으며 여러 runtime 인스턴스에서도 후보 목록은 한 번만 저장한다.
118
+ 후보는 주소당 20개까지 표시하고 `handlersOmitted`로 생략 수를 알린다. 검토 파일에는
119
+ 표시 상한 밖의 후보도 포함한다. 후보 존재를 실제 native 실행 심볼의 확정으로 바꾸지 않는다.
120
+ Swift producer가 실은 Objective-C fact는 `sourceLanguage: objective-c`를 유지하며,
121
+ Swift 언어 그래프의 신원으로 변환하지 않는다.
122
+ - `selectionReasons`는 주소 일치(`route`)와 검토 파일에서 선언한 caller(`caller-file`)를
123
+ 구분한다. 기록의 caller는 수집기가 명시한 위치이며 주변 선언이나 stack에서 추측하지 않는다.
124
+ caller 표시는 route당 20개까지이며 생략 수를 알리고 reviewFiles에는 모든 관련 파일을 남긴다.
125
+ - `unobservedBoundaries`: 현재 실행 기록이 없는 관련 정적 경계. `uncoveredBoundaries`는
126
+ 현재 기대 시나리오에 들어 있지 않은 관련 경계다. 다른 주소의 통신만 성공하면 이 공백은 남는다.
127
+ - Basic/Pigeon 기록은 v2 messages가 있을 때 Basic 주소에만 연결한다. 같은 이름의
128
+ MethodChannel이 Basic을 덮지 않는다. `matching: prefix` 후보의 실행 관찰도 suffix/instance
129
+ 배선의 정적 공백을 지우지 않는다. v2가 없거나 해당 native 언어 문서가 없으면 정적 연결은 unsupported다.
130
+ runtime 검증과 정적 후보 연결의 지원 범위는 다르며 다른 플랫폼을 추측해 연결하지 않는다.
131
+ - Android 실행은 Kotlin 후보, iOS/macOS 실행은 Swift 후보에만 연결한다. 양쪽 native 문서가
132
+ 함께 있는 경계는 한쪽 실행만으로 모두 관찰됐다고 표시하지 않는다. 같은 주소라도 candidateKey가
133
+ 다를 수 있으므로 원래 키로 조회한다. 조건부 플랫폼 분기가 필요한 앱은 native별 capture를 사용한다.
134
+
135
+ 기대 실패는 `allowedOutcomes`로 명시한다. `runtime.verification.status`가 passed이고
136
+ aligned가 true여도 정적 공백·미관찰/미포함 경계가 남으면 전체 strict는 1이다.
137
+
138
+ 경계의 관찰/기대 포함 여부는 **주소 수준**이다. 같은 주소를 공유하는 기능·인자 분기·호출
139
+ 경로가 모두 실행됐다는 뜻이 아니다. 원하는 기능은 독립 기대 목록에 해당 scenario로 지정해야
140
+ 한다. 알려진 관련 메서드는 각각 대조하며, 메서드가 알려지지 않은 channel-only 경계는 해당
141
+ 채널의 메서드 통신을 배선 관찰로만 인정한다. 그것으로 모든 메서드나 소스 경로의 실행을 보증하지 않는다.
142
+
143
+ ## Android 수집
144
+
145
+ `cartograph` 대신 `kartograph`와 `kartographSnapshot`을 지정하면 Android만 수집할 수 있다.
146
+ 양쪽 native producer를 함께 지정할 수도 있다. Kotlin snapshot은 prepare 단계가 만들며
147
+ 내용을 캐시 지문에 자동 포함한다. `toolInputs`에는 Kartograph 실행 스크립트와 배포의 `lib/`
148
+ 디렉터리를 모두 넣는다. 실행 스크립트가 같아도 실제 JAR이 바뀔 수 있기 때문이다.
149
+
150
+ ```json
151
+ {
152
+ "project": "/path/to/flutter-app",
153
+ "inputs": ["lib", "android/app/src", "android/app/build.gradle.kts", "pubspec.yaml", "pubspec.lock"],
154
+ "toolInputs": ["/path/to/kartograph/bin/kartograph", "/path/to/kartograph/lib", "/path/to/dartograph"],
155
+ "dartograph": ["/path/to/dartograph"],
156
+ "kartograph": ["/path/to/kartograph/bin/kartograph"],
157
+ "kartographSnapshot": "android/app/build/reports/kartograph/debug-snapshot.json",
158
+ "prepare": [["android/gradlew", "-p", "android", ":app:kartographSnapshotDebug"]],
159
+ "messages": true,
160
+ "selection": {"kotlin": {"files": ["android/app/src/main/kotlin/example/CameraPlugin.kt"], "symbols": []}},
161
+ "output": ".isthmus/context.json",
162
+ "cache": ".isthmus/cache.json"
163
+ }
164
+ ```
165
+
166
+ 예제 Gradle task는 앱에 Kartograph plugin을 적용한 경우다. snapshot 출력 경로는 실제
167
+ 프로젝트의 build directory에 맞춘다(Flutter가 build directory를 옮길 수 있다). 별도 snapshot
168
+ 생성 명령을 prepare에 연결해도 된다. 생성 설정·SDK·variant 등 실제 분석에 영향을 주는
169
+ 입력도 선언한다. Git `since`는 `.kt`·`.java` 변경과 rename 양쪽 경로를 선택하며 해당
170
+ producer를 구성하지 않은 플랫폼의 변경은 공백으로 남긴다.
171
+
172
+ Kotlin adapter는 `kartograph-impact` v1의 current 경로와 bytecode/runtime-model 출처를 보존한다.
173
+ base/current를 섞은 입력은 거부한다. 경로 중간 심볼·위치·출력 페이지가 빠졌으면 그 사실을
174
+ 알리고 현재 의존 경로를 지어내지 않는다. 전체 원본은 `.sources.json`에 남긴다.
175
+ adapter는 경로의 node/edge 항목을 합해 1,000,000개까지 읽고 상한 초과는 입력 오류로
176
+ 거부한다. 수집 명령은 producer의 depth 128·출력 10,000개 한도를 사용한다.
177
+
178
+ 앱 내부에 복사한 공개 Pigeon 패키지의 Dart 구현까지 연결하려면 Dartograph의
179
+ `dartograph.yaml`에서 분석할 로컬 패키지를 명시한다(`source_packages`, dartograph 0.9.0부터).
180
+
181
+ ```yaml
182
+ source_packages:
183
+ - vendor/shared_preferences_android
184
+ ```
185
+
186
+ 이는 지정한 패키지의 `lib/`만 추가한다. 앱의 path dependency로 연결하고 `flutter pub get`을
187
+ 실행한 뒤, 설정 파일·패키지 소스·pubspec·package_config를 capture 입력에도 포함한다.
188
+ pub 캐시 전체나 모든 의존 패키지를 분석했다고 간주하지 않는다. 같은 이름의 Dart 후보는
189
+ producer의 실제 ID와 파일 위치로 대조하며 같은 파일 안에서도 모호하면 연결하지 않는다.
190
+
191
+ ## 자동 수집과 CI
192
+
193
+ `scripts/capture-preflight.mjs`는 별도 workflow 진입점이다. 사용자가 작성한 설정의 `prepare`
194
+ 명령을 실행하고 producer JSON을 수집한다. 신뢰하지 않는 저장소가 제공한 명령 설정을
195
+ 그대로 실행하지 않는다. 패키지에는 이 스크립트·run-child·컴파일된 소비자 모듈이 포함된다.
196
+
197
+ ```bash
198
+ # 저장소에서 사용: npm ci와 npm run build 후
199
+ node scripts/capture-preflight.mjs capture.json
200
+
201
+ # 앱에 설치한 패키지에서 사용: 새 기능이 포함된 버전을 설치한 뒤
202
+ node node_modules/isthmus-cli/scripts/capture-preflight.mjs capture.json
203
+ ```
204
+
205
+ 아래는 iOS 앱용 설정 형태다. project·scheme·도구 경로와 입력 목록은 실제 앱에 맞춰야 한다.
206
+ 이 iOS 설정 자체를 실행 검증했다고 주장하지 않는다. 실검증한 SwiftPM fixture 절차는 아래에 있다.
207
+
208
+ ```json
209
+ {
210
+ "project": "/absolute/path/to/flutter-app",
211
+ "inputs": [
212
+ "lib", "ios/Runner", "ios/Runner.xcodeproj/project.pbxproj",
213
+ "ios/Podfile", "ios/Podfile.lock", "pubspec.yaml", "pubspec.lock",
214
+ ".dart_tool/package_config.json"
215
+ ],
216
+ "toolInputs": ["/absolute/path/to/cartograph", "/absolute/path/to/dartograph"],
217
+ "dartograph": ["/absolute/path/to/dartograph"],
218
+ "cartograph": ["/absolute/path/to/cartograph"],
219
+ "prepare": [[
220
+ "xcodebuild", "-workspace", "ios/Runner.xcworkspace", "-scheme", "Runner",
221
+ "-configuration", "Debug", "-sdk", "iphonesimulator",
222
+ "-derivedDataPath", ".isthmus/DerivedData",
223
+ "COMPILER_INDEX_STORE_ENABLE=YES", "CODE_SIGNING_ALLOWED=NO", "build"
224
+ ]],
225
+ "indexStore": ".isthmus/DerivedData/Index.noindex/DataStore",
226
+ "since": "main",
227
+ "output": ".isthmus/context.json",
228
+ "cache": ".isthmus/cache.json"
229
+ }
230
+ ```
231
+
232
+ `isthmus init [capture.json]`은 이 설정의 scaffold를 쓰고, `--toolchain <toolchain.json>`을
233
+ 주면 `scripts/build-preflight-toolchain.mjs`가 만든 `isthmus-built-toolchain`의 실제
234
+ producer 명령을 채운다. 앱별로 다른 `prepare`는 자리표시자로 남고, 기존 파일은
235
+ `--force` 없이는 덮어쓰지 않는다. `isthmus doctor <capture.json>`은 설정을 수집기와 같은
236
+ 정본 규칙(`src/exchange/capture-config.ts`)으로 검증하고 producer·prepare 첫 토큰이
237
+ PATH나 지정 경로에 실제로 있는지만 확인한다 — 제품은 producer를 실행하지 않는다. 종료
238
+ 코드는 정상 0, 미완성 1, 입력·계약 오류 2, 사용 오류 64다.
239
+
240
+ `since` 대신 `selection: {"swift":{"files":["ios/Runner/CameraPlugin.swift"],"symbols":[]}}`
241
+ 처럼 변경 전 대상을 명시할 수 있다. 둘을 함께 쓰지 않는다. CI에서는 checkout한 저장소의
242
+ base commit SHA를 since에 넣고 해당 commit을 fetch해 둔다. Git의 rename 양쪽·삭제·변경
243
+ 파일과 미추적 Dart/Swift 소스를 선택한다. 추적된 설정·리소스 변경은 별도 검토 공백으로 남긴다.
244
+
245
+ Flutter 의존성을 준비한 후 수집기를 실행하고 생성된 JSON을 `isthmus preflight`에 전달한다.
246
+ 수집기 코드 1은 보고서가 생성됐지만 검토가 필요하다는 뜻이고, 2는 수집 실패다. CI 스크립트는
247
+ 1일 때도 context 보고서를 보존해야 한다. 외부로 올릴 artifact에 개인 경로가 있는지 검토한다.
248
+ `.isthmus/`와 빌드 출력은 Git에서 제외하고 입력 해시 범위에도 넣지 않는다.
249
+
250
+ 캐시는 **명시한 입력 범위**에서만 유효하다. 사용하는 xcconfig·Pigeon 입력·로컬 패키지·
251
+ SDK 버전 파일·producer가 로드하는 라이브러리와 설정도 inputs/toolInputs에 포함해야 한다.
252
+ 실행 파일이 wrapper이면 wrapper 하나의 해시만으로 실제 구현 전체를 보증하지 못한다.
253
+ 입력 디렉터리 내부 symlink는 거부하므로 필요한 실제 소스 경로를 별도로 선언한다.
254
+
255
+ 소스·설정·producer 파일 내용, 명령·선택 설정, isthmus 구현, Node/OS/아키텍처 및 주요
256
+ toolchain 환경의 해시가 키에 포함된다. 파일 추가·수정·삭제로 키가 바뀌면 prepare부터 다시
257
+ 실행한다. 수집 전후 해시가 다르면 결과를 발행하지 않는다. prepare가 lock 파일 등을 정상적으로
258
+ 갱신한 경우에는 준비가 끝난 상태부터 수집 전후를 비교한다. 빌드 명령이 실제 index를 갱신하는지는
259
+ 호출자가 책임지는 명시적 전제다. 코드 0을 임의의 빌드 설정까지 정확하다는 보증으로 삼지 않는다.
260
+
261
+ context 옆의 `.sources.json`에는 원래 producer 출력, 선언한 입력의 해시, 단계별 시간이 있다.
262
+ 캐시 복원 시에도 이 근거를 복원한다. 일반 호출 그래프로 투영하지 못한 Cartograph runtime
263
+ review·runtime dependency 항목은 공백으로 표시하고 원문 producer 보고서에서 확인한다.
264
+ 런타임 통신은 [별도 검증](RUNTIME.md)을 사용하며 이 정적 경로에 실제 실행 신원을 추측해 붙이지 않는다.
265
+
266
+ Basic 수집은 설정에 `"messages": true`를 추가한다. 같은 producer에 `bridges --messages`를
267
+ 호출하며 공개 호환 버전(cartograph 0.18.0·kartograph 0.10.2·dartograph 0.14.0)에 포함된
268
+ 기능이다. 별도의 message producer를 쓸 때는
269
+ `"messages": {"cartograph": ["/path/to/message-cartograph"], "dartograph": ["/path/to/message-dartograph"]}`처럼
270
+ 명령을 지정한다. 생략한 쪽은 기본 producer를 사용한다. override 실행 파일과 관련 구현도
271
+ `toolInputs`에 넣어야 하며 feature 설정·명령·override 버전은 캐시 키에 포함된다.
272
+
273
+ ## 실행 근거
274
+
275
+ 당시 개발 producer가 있던 환경에서 다음 검증을 실행했다. 필요한 producer 명령은
276
+ 공개 호환 버전에 포함됐지만, 이 스크립트를 공개 실행 파일 조합으로 다시 실행하지는
277
+ 않았다(인자 형태 확인 필요).
278
+
279
+ ```bash
280
+ node scripts/verify-preflight-producers.mjs /path/to/cartograph /path/to/dartograph/bin/dartograph.dart /path/to/flutter
281
+ ```
282
+
283
+ 임시 SwiftPM 소스의 실제 compiler index와 실제 Dartograph 분석을 사용해 helper→handler→
284
+ channel→Dart caller→service→screen 경로, source 위치, 캐시 재사용, 소스 변경 후 재수집을 확인한다.
285
+ Swift의 Flutter 타입은 컴파일용 stub이므로 native IPC 검증이 아니다. Flutter 의존성 준비는
286
+ offline 캐시를 사용한다. SDK·producer 저장소를 변경하지 않고 임시 fixture에서 빌드한다.
287
+
288
+ 2026-09-14 최신 실행은 첫 수집 23.811초, 같은 입력의 캐시 사용 3.735초, 소스 변경 후 18.458초였다.
289
+ Dartograph 소스를 직접 실행하므로 버전 조회에도 약 4초가 들었다. producer 설치·Flutter 의존성
290
+ 준비는 이 시간 밖이며, 대형 앱이나 배포된 producer 실행 파일의 성능으로 일반화하지 않는다.
291
+ 수집기의 코드·도구 변경/삭제·중간 변경·캐시 변조·다른 파일의 query·Git rename 회귀는
292
+ `node --test scripts/capture-preflight.test.mjs`로 검사하며 `npm run verify`에도 포함된다.
293
+
294
+ 소비자 성능 회귀는 `node scripts/benchmark-preflight.mjs`로 검사하며 저장소 CI에서도 실행한다.
295
+ CLI 시작·JSON 읽기·분석·직렬화를 포함한 5회 실행을 측정한다. 전이/runtime 대형 입력과
296
+ 같은 setup을 공유하는 Basic handler 10,000개의 단일 구현 선택을 포함한다. 각 입력의
297
+ 최대 실행 5초를 게이트로 쓰며 producer·인덱스·앱 빌드 시간은 이 게이트 밖이다.
298
+
299
+ 공개 source의 메서드별 전파 검증은 아래 명령으로 재현한다. 준비된 Flutter pub 캐시의
300
+ `url_launcher_macos 3.2.2` 원본과 실제 FlutterMacOS framework를 사용한다. 생성 파일 원본을
301
+ 수정하지 않고 검증용 SwiftPM manifest와 Dart dev dependency 제외를 적용한다.
302
+
303
+ ```bash
304
+ node scripts/verify-public-pigeon.mjs <flutter> <cartograph-impact> <cartograph-messages> <dartograph-entry-or-exe> <url_launcher_macos-3.2.2>
305
+ ```
306
+
307
+ Swift launch/canLaunch 구현을 각각 선택하면 해당 Dart wrapper만, 공통 setup을 선택하면
308
+ 양쪽 wrapper 모두를 포함해야 한다. 이 검증은 native IPC 실행과 별도다. 실제 macOS 통합은
309
+ `verify-flutter-runtime.mjs`의 기존 producer 인자 뒤에 `--message-cartograph <path>`를 추가한다.
310
+ 하네스는 공개 plugin을 임시 앱의 vendor로 복사하고 원본 bytes·명시된 입력 해시·동일 revision의
311
+ 정적 후보/runtime 관찰을 검증한다. 기본 Flutter recorder는 payload나 원문 오류를 기록하지 않는다.
@@ -0,0 +1,172 @@
1
+ # 런타임 통신 검증 계약
2
+
3
+ `verify-runtime`은 npm 0.7.0 발행본에 포함됐으며 명시한 시나리오에서 실제 관찰한
4
+ 통신 결과를 검증한다. [Flutter 수집기](../packages/isthmus_runtime/README.md)는 앱의
5
+ BinaryMessenger에 주입해 실제 outgoing 호출을 기록한다. 정적 후보 연결은
6
+ [impact의 런타임 입력](IMPACT.md#런타임-관찰-연결)과
7
+ [preflight의 실행 대조](PREFLIGHT.md#전이-분석과-runtime-대조)를 사용한다.
8
+
9
+ 아래 fixtures/runtime JSON은 합성 소비자 검증이다. 실제 native 검증은 별도 절차로 구분한다.
10
+
11
+ ```bash
12
+ npm run build
13
+ node dist/cli/main.js verify-runtime \
14
+ --expectations fixtures/runtime/expectations.json fixtures/runtime/success.json --strict
15
+ ```
16
+
17
+ ## 실제 Flutter 앱 검증
18
+
19
+ ```bash
20
+ node scripts/verify-flutter-runtime.mjs /path/to/flutter/bin/flutter
21
+ # 실제 producer와 전이 분석까지 연결할 때(최신 isthmus build 필요)
22
+ node scripts/verify-flutter-runtime.mjs /path/to/flutter/bin/flutter dist/cli/main.js /path/to/cartograph /path/to/dartograph/bin/dartograph.dart
23
+ ```
24
+
25
+ macOS·Xcode·CocoaPods·Flutter SDK가 필요하며 최초 준비에는 네트워크를 사용한다. 스크립트는 임시 앱을
26
+ 만들고 `url_launcher_macos 3.2.2`를 고정해 실제 Pigeon 생성 API를 호출한다. URL을 열지 않고
27
+ `canLaunchUrl`만 실행한다. 자체 Swift MethodChannel/BasicMessageChannel도 함께 검사한다.
28
+ 공개 의존성은 pub.dev에서 해결한다. 앱·Pods 배포 대상은 macOS 12이며 시스템/사용자 앱 설정은 수정하지 않는다.
29
+
30
+ 성공 4개 기대(동적 호출·전이 소비자 호출·자체 Basic·공개 Pigeon), 네이티브 error·missing-handler·timeout의 구분, 앱 Future의 원래 지연 응답,
31
+ pending 실행의 미완료와 payload 미기록을 확인한다. 소스·의존성 잠금·SDK revision으로
32
+ 검증 revision을 만들며, 임시 앱은 정리하고 기록 JSON과 검증 요약 디렉터리를 출력한다.
33
+ 이 검사는 실제 macOS native 통신이며 iOS/Android 앱 검증으로 확대 해석하지 않는다.
34
+
35
+ producer 인자를 주면 실제 Xcode compiler index와 양쪽 producer로 별도 Swift helper에서
36
+ Dart runtimeBridge→runtimeService→runtimeScreen까지의 경로를 확인한다. context의 입력
37
+ 해시를 앱의 recorder revision으로 전달하고 같은 revision의 runtime과 preflight를 대조한다.
38
+ 동적 echo와 전이 소비자의 echo는 별도 시나리오·recorder로 기록해 다른 호출 위치를 공유하지 않는다.
39
+ Basic 정적 미지원·실행하지 않은 경계 때문에 preflight strict가 1인 경우도 근거로 보존한다.
40
+ 선택적 마지막 인자로 준비된 dartograph 실행 파일을 주면 소스 실행의 반복 JIT 비용을 줄일 수 있다.
41
+
42
+ 검증 SDK는 Flutter 3.32.2/Dart 3.8.1이다. 최초 SDK/공개 패키지 준비 시간과 소비자 CLI
43
+ 시간은 분리한다. 같은 앱의 첫 빌드와 변경 없는 재빌드를 둘 다 측정해 `steps`에 남긴다.
44
+
45
+ 수집기 자체의 검사와 고정 payload 오버헤드 측정:
46
+
47
+ ```bash
48
+ cd packages/isthmus_runtime
49
+ flutter pub get
50
+ flutter analyze --no-pub
51
+ flutter test --no-pub
52
+ flutter test tool/emit_runtime_fixture.dart --no-pub
53
+ flutter test tool/benchmark_recorder.dart --no-pub
54
+ ```
55
+
56
+ 벤치마크는 fake messenger를 쓰는 디버그 Flutter 테스트다. 실제 디바이스 IPC 지연의 측정이
57
+ 아니다. 임시/진단 빌드에서 필요한 채널과 실제 codec을 명시하며, Basic/Pigeon reply의 성공·
58
+ 실패 의미는 명시 classifier가 정한다. 대응하는 codec이 없으면 이름만으로 추측하지 않는다.
59
+
60
+ ## 실제 Android 앱 검증
61
+
62
+ 개발 소스의 Android 하네스는 별도 debug 앱에서 Kotlin MethodChannel/BasicMessageChannel
63
+ 핸들러를 실행하고 수집기 JSON을 검증한다. Android SDK·JDK·Flutter와 실행 가능한 Android
64
+ 장치 또는 에뮬레이터 이미지가 필요하다.
65
+
66
+ ```bash
67
+ node scripts/verify-flutter-android-runtime.mjs <flutter> <adb> <isthmus-js>
68
+ # 같은 capture의 Kotlin 정적 영향과 연결
69
+ node scripts/verify-flutter-android-runtime.mjs <flutter> <adb> <isthmus-js> \
70
+ --kartograph <kartograph-bin> --dartograph <dartograph-aot>
71
+ ```
72
+
73
+ API 36 arm64 실행에서는 자체 Method/Basic과 공개 `shared_preferences_android 2.4.1`의
74
+ Pigeon `getBool` 성공 3개, 기대한 오류·핸들러 누락·timeout 3개, pending 미완료와 자체
75
+ Kotlin 본문의 실행 marker를 확인했다. 공개 패키지의 원본 소스·LICENSE·실제 생성 codec을
76
+ 사용한다. 하네스가 만든 앱과 에뮬레이터만 정리하고 기록과 실행 인자는 근거 폴더에 남긴다.
77
+ 기본 공개 패키지 경로는 pub 캐시이며 `ISTHMUS_SHARED_PREFERENCES_ANDROID`로 지정할 수 있다.
78
+ 장치가 없으면 API 36 Google Play 이미지를 사용하며 `ISTHMUS_ANDROID_SYSTEM_IMAGE`로
79
+ 설치된 이미지 ID를 지정할 수 있다. 최신 조합의 결과는 [진행 기록](COMPETITIVENESS.md)에 남긴다.
80
+ 이는 명시한 통신 시나리오의 검증이며 임의의 런타임 의존성을 자동 발견하는 기능은 아니다.
81
+ 정적 연결에는 실제 앱 project와 capture revision을 사용하며 실행 후 식별자를 고쳐 맞추지 않는다.
82
+ 공개 Kotlin `SharedPreferencesPlugin.getBool`의 실제 snapshot ID만 변경 대상으로 선택하는
83
+ 별도 capture도 검사한다. 생성 Dart API와 앱 호출자까지 도달해야 통과하며, 이 선택의
84
+ revision은 원래 실행 기록과 구분한다. Kotlin의 공통 Pigeon 등록 함수는 여러 채널을 영향
85
+ 후보로 넓힐 수 있으므로 메서드별 완전한 정밀도를 주장하지 않는다.
86
+
87
+ ## 독립적인 기대 목록
88
+
89
+ `bridge-expectations` v1:
90
+
91
+ ```json
92
+ {
93
+ "format": "bridge-expectations",
94
+ "version": 1,
95
+ "project": "/app",
96
+ "revision": "tested-source-revision",
97
+ "checks": [{
98
+ "id": "photo-main-ios",
99
+ "scenario": "take-photo",
100
+ "platform": "ios",
101
+ "instance": "main",
102
+ "transport": "method-channel",
103
+ "channel": "example/camera",
104
+ "method": "takePhoto",
105
+ "allowedOutcomes": ["success"]
106
+ }]
107
+ }
108
+ ```
109
+
110
+ 실행 로그를 보고 기대 목록을 역생성하면 누락된 실행을 발견할 수 없다. 검증할 기능에서
111
+ 목록을 먼저 정한다. checks 1~10,000개, 고유한 id가 필요하다. 인스턴스 생략은 어떤
112
+ 인스턴스든 허용한다는 뜻이다. 엔진별 검증이 필요하면 인스턴스를 명시한다.
113
+ `allowedOutcomes`는 선택 사항이며 `success`, `error`, `missing-handler`, `timeout` 중
114
+ 1~4개의 중복 없는 terminal outcome만 허용한다. 생략하면 `success`만 허용하고,
115
+ `pending`은 응답 대기 상태라 기대 결과로 지정할 수 없다.
116
+
117
+ Flutter recorder에서 timeout은 관찰 제한시간을 넘겼다는 뜻이다. 늦은 응답이나 예외는 앱에
118
+ 그대로 전달하고 기록의 timeout은 유지한다. 실제 Future가 모두 끝난 뒤 finish하면 completed가
119
+ 될 수 있지만, 응답이 아직 남은 상태로 finish하면 incomplete다. 따라서 명시한 timeout 기대가
120
+ 아직 진행 중인 통신까지 허용하지 않는다. finish 후 기록은 나중 응답으로 변경되지 않는다.
121
+
122
+ ## 실행 기록
123
+
124
+ `bridge-runtime` v1은 같은 project·revision과 producer의 `tool: {name, version}`,
125
+ `run: {id, scenario, platform, status, startedAt, finishedAt?}`, `droppedEvents`, `events`를 가진다.
126
+
127
+ - platform은 `ios/macOS`를 추측하지 않고 `ios|macos|android|linux|windows` 중 하나로 선언한다.
128
+ - run status는 `completed|incomplete`. 완료 실행은 종료 시각이 필요하며 시작보다 앞설 수 없다.
129
+ - 시각은 bridge-facts와 같은 유효한 timezone 포함 ISO 형식이다.
130
+ - event는 `sequence`, `instance`, `transport`, `channel`, `method?`, `outcome`, `caller?`다.
131
+ sequence는 1부터 시작하는 고유한 정수, caller는 상대 `path`와 1부터 시작하는 `line/column`이다.
132
+ - transport `method-channel`은 method가 필요하다. `basic-message-channel`은 채널이 주소이며
133
+ method를 붙일 수 없다. Pigeon 메서드 이름이나 접미사를 채널 문자열에서 임의로 파싱하지 않는다.
134
+ - outcome은 `success|missing-handler|error|timeout|pending`. 인자·반환값·원문 오류·스택은
135
+ 출력 계약에 없으며 입력에 있더라도 파싱 단계에서 버린다. 수집기 자체도 저장하지 않아야 한다.
136
+ - 이벤트 100,000개 상한. 수집을 제한하면 `droppedEvents`를 증가시킨다. sequence 빈 구간은
137
+ 적어도 그만큼의 유실 계수가 있어야 한다. 응답 대기는 pending으로 남긴다.
138
+ - 입력 파일당 16×1024², 전체 64×1024² UTF-16 코드 유닛 예산. 실행 파일은 최대 256개다.
139
+ - run id는 전체 입력에서 유일해야 한다. 다른 project·중복 run은 계약 오류다.
140
+
141
+ project와 revision은 생산자의 선언이다. 소비자는 서로 일치하는지 검사하며 실제 checkout·
142
+ 빌드 내용까지 증명하지 않는다. 수집·CI 단계에서 변경된 소스의 정확한 식별자를 넣어야 한다.
143
+
144
+ ## 판정과 증거
145
+
146
+ 현재 revision의 run만 기대와 대조한다. 오래된 run은 `staleRuns`로 세고 전체 결과를 incomplete로
147
+ 만든다. 시나리오·플랫폼·transport·channel·method·선택한 instance가 모두 맞아야 관찰 근거다.
148
+
149
+ - `passed`: 해당 라우팅의 관찰이 하나 이상 있고 모든 terminal outcome이 기대의
150
+ `allowedOutcomes`에 포함되며 실행 중단·유실·대기가 없다.
151
+ - `failed`: 같은 호출에 기대하지 않은 terminal outcome이 하나라도 있다. 기대한
152
+ `error`·`missing-handler`·`timeout`도 원본 `failures`와 `failedCalls`에는 남기며,
153
+ `expectedFailedCalls`와 `unexpectedFailedCalls`로 구분한다. 기대 목록 밖의 실패는
154
+ `unexpectedFailedCalls`로 전체 상태를 실패로 만든다.
155
+ - `unobserved`: 해당 기대와 맞는 호출을 관찰하지 못했다.
156
+ - `incomplete`: 맞는 관찰이 있지만 실행 중단·유실·대기가 남았다.
157
+
158
+ 라우팅과 인스턴스가 겹치는 기대가 있으면 이벤트는 적용 가능한 모든 기대의
159
+ `allowedOutcomes`를 만족해야 한다. 넓은 인스턴스 기대가 특정 인스턴스 기대의 실패를
160
+ 가리지 않는다. 기대하지 않은 성공도 해당 check를 `failed`로 만들며 terminal 실패가
161
+ 없어도 전체 상태에 반영한다.
162
+
163
+ 기대 밖 통신 실패도 `failures`와 전체 status에 반영한다. 전체 결과는 실행 공백이나 미충족
164
+ 기대가 있으면 incomplete다. `scope: declared-scenarios`, `complete: false`를 항상 보존하므로
165
+ passed는 선언한 기대의 통과이며 모든 의존성·기능의 완전성 보증이 아니다.
166
+
167
+ 항목별 evidence는 runId·sequence·instance·outcome·caller로 원본 기록을 가리킨다. 최대 20개를
168
+ 표시하고 `observedCalls`·`evidenceOmitted`로 전체 수와 생략 수를 구분한다. 평가는 모든 호출에
169
+ 적용한다. 증거 표시 생략은 원본 수집 유실(`droppedEvents`)과 다르다.
170
+
171
+ 기본 모드는 보고서 생성 성공 0, strict는 failed/incomplete 1, 입력 실패 2, 사용법 오류 64다.
172
+ `--compact`는 JSON 공백만 줄인다. 로그에 비밀을 넣지 않는 책임은 수집기와 실행 환경에도 있다.
@@ -0,0 +1,101 @@
1
+ # 고정 소스에서 사전 점검 도구 구축
2
+
3
+ 이 절차는 로컬 Git 저장소의 **지정한 commit만** 새 디렉터리에 풀어 선택한 도구를 구축한다.
4
+ 공개 버전만으로 충분하면 [COMPATIBILITY.md](COMPATIBILITY.md)의 설치 경로가 더 빠르다.
5
+ 원본의 미커밋 수정, dist, .build, .dart_tool을 복사하지 않는다. SDK와 전역 의존성 캐시는
6
+ 공유할 수 있으므로 빈 머신 전체 설치 시간과 구분한다. 소스 다운로드·발행은 수행하지 않는다.
7
+
8
+ ## 준비
9
+
10
+ - Node.js 22.18.0 이상, Dart 3.11 이상.
11
+ - 로컬 Git checkout: isthmus·dartograph와 native producer 하나 이상.
12
+ - Swift 분석에는 cartograph와 Swift 6 toolchain, Android 분석에는 kartograph와 JDK 17+가 필요하다.
13
+ Android만 구축할 때는 Swift toolchain을 호출하지 않는다. JDK 경로는 `JAVA_HOME`으로 지정한다.
14
+ - 선택한 Cartograph commit은 `impact`와 `bridges --messages`를 함께 제공해야 한다.
15
+ Dartograph는 `bridges --messages`, isthmus는 `preflight --summary/--explain`이 필요하다.
16
+ - 선택한 Kartograph commit은 `impact --graph-file`과 `bridges --messages --graph-file`을 제공해야 한다.
17
+ - 앱의 Swift 인덱스와 실제 Flutter 검증에는 해당 앱의 Flutter/Xcode 환경이 추가로 필요하다.
18
+ 도구 자체를 구축하는 과정은 Flutter SDK를 필수로 요구하지 않는다.
19
+
20
+ 공개 호환 버전 세트(isthmus 0.7.0 · cartograph 0.18.0 · kartograph 0.10.2 · dartograph
21
+ 0.14.0)는 이미 발행됐다 — [COMPATIBILITY.md](COMPATIBILITY.md)를 본다. 이 절차는
22
+ 발행본이 아니라 검증된 개발 commit을 그대로 재현할 때 쓴다. 소스 commit이 로컬 Git에
23
+ 있어야 하며 빌드 과정의 npm/pub/SwiftPM/Gradle 의존성 해석은 네트워크를 사용할 수 있다.
24
+
25
+ ## 실행
26
+
27
+ `build.json`에 실제 로컬 경로와 `git rev-parse <ref>`로 확인한 전체 40자리 commit을 적는다.
28
+ 아래 `<...>`는 대체해야 할 값이다. 상대 경로는 JSON 파일의 디렉터리를 기준으로 해석한다.
29
+ destination은 존재하지 않아야 하고 그 부모 디렉터리는 미리 준비한다.
30
+
31
+ ```json
32
+ {
33
+ "format": "isthmus-toolchain-build",
34
+ "version": 1,
35
+ "destination": "../built-isthmus-tools",
36
+ "repositories": {
37
+ "isthmus": {"path": "../isthmus", "revision": "<40-character-commit>"},
38
+ "cartograph": {"path": "../cartograph", "revision": "<40-character-commit>"},
39
+ "dartograph": {"path": "../dartograph", "revision": "<40-character-commit>"}
40
+ }
41
+ }
42
+ ```
43
+
44
+ ```bash
45
+ node scripts/build-preflight-toolchain.mjs build.json
46
+ ```
47
+
48
+ Android만 필요하면 위 repositories의 `cartograph`를 `kartograph`로 바꾸고 실제 Kotlin
49
+ producer checkout과 commit을 지정한다. 두 native producer를 함께 넣으면 네 도구를 구축한다.
50
+ Kartograph는 Gradle `:cli:installDist`로 만든 배포 스크립트와 runtime JAR을 사용한다.
51
+
52
+ 스크립트는 commit 확인 → source archive → 선택한 native producer 빌드 → Dart AOT 빌드 → npm tarball 생성·
53
+ 격리 설치 순서로 실행한다. 기존 destination을 덮어쓰지 않고, 실패 시 자신이 새로 만든
54
+ destination만 정리한다. 각 단계의 로그는 별도의 임시 근거 디렉터리에 남기며 경로를 알린다.
55
+ 성공한 디렉터리는 옮기지 않고 그대로 사용한다.
56
+
57
+ `<destination>/toolchain.json`에는 source commit, SDK 버전, 실행 파일/패키지 hash, 단계별
58
+ 시간과 아래 명령 배열이 있다.
59
+
60
+ - `commands.isthmus`: Node와 설치된 CLI의 경로.
61
+ - `commands.cartograph`: impact와 Basic을 함께 제공하는 실행 파일 경로.
62
+ - `commands.kartograph`: Android 분석용 배포 스크립트 경로(선택한 경우).
63
+ - `commands.dartograph`: AOT 실행 파일 경로.
64
+
65
+ `artifacts.kartographInputs`는 실행 스크립트와 `lib/` 디렉터리다. 둘 다 capture의 toolInputs에
66
+ 사용하며 launcher hash 외에 `hashes.kartographLibraries`로 runtime JAR 묶음도 기록한다.
67
+
68
+ 이 성공은 구축과 CLI 기능 확인이다. 앱 분석의 정확성·모든 런타임 의존성 검증을 뜻하지 않는다.
69
+
70
+ ## 앱 수집 및 CI 연결
71
+
72
+ [PREFLIGHT.md](PREFLIGHT.md)의 capture 설정에서 위 두 producer 명령을 사용하고 Basic이
73
+ 필요하면 `"messages": true`를 지정한다. 서로 다른 Cartograph 실행 파일을 나눠 쓸 필요가 없다.
74
+ 실행 파일과 앱 소스·생성 설정·SDK 입력을 `toolInputs`/`inputs`에 선언한다.
75
+
76
+ ```bash
77
+ node <isthmus-source>/scripts/capture-preflight.mjs capture.json
78
+ node <installed-isthmus-main.js> preflight context.json --summary --strict --compact
79
+ ```
80
+
81
+ 첫 명령은 기존 설정의 prepare 명령으로 앱 인덱스를 갱신하고 내용 해시가 같은 수집 결과만
82
+ 재사용한다. 두 번째 명령의 exit 1은 검토할 결과이며 JSON을 버리지 않는다. 공개 Pigeon의
83
+ prefix와 실행하지 않은 시나리오 등은 의도적으로 공백으로 남을 수 있다.
84
+
85
+ CI에서는 도구 구축과 앱 수집을 구분한다. 도구는 검증한 commit의 빌드 산출물을 사용하고,
86
+ 앱 입력이 바뀔 때 capture를 갱신한다. 저장소의 소비자 성능 검사는 5초 예산을 검사하지만
87
+ producer 및 앱 빌드 시간은 포함하지 않는다. 원격 CI 실행과 최초 외부 사용자의 구축은
88
+ 로컬 source archive 검증과 별도로 확인해야 한다.
89
+
90
+ ## 실제 Dart 바인딩 상태 대조
91
+
92
+ 저장소 검증 스크립트는 Flutter의 실제 BasicMessageChannel API를 mock messenger와 함께
93
+ 실행해 mutable 필드, 고정 필드, 순차 대입, then/else에서 관찰한 주소를 producer와 대조한다.
94
+
95
+ ```bash
96
+ node scripts/verify-dart-binding-runtime.mjs <flutter> <dartograph-aot>
97
+ ```
98
+
99
+ 같은 mutable 필드 호출이 호출 이력에 따라 다른 주소를 쓰면 정적 단일 주소로 확정하지
100
+ 않아야 한다. 반대로 고정 필드와 명확한 직선 흐름은 실제 주소를 유지해야 한다.
101
+ 이 검증은 Dart/Flutter 바인딩 상태의 실행이며 native IPC나 앱 전체 동작 검증은 아니다.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isthmus-cli",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Join bridge facts across cross-platform application boundaries.",
5
5
  "keywords": [
6
6
  "flutter",
@@ -28,6 +28,18 @@
28
28
  "files": [
29
29
  "dist",
30
30
  "Skills",
31
+ "scripts/capture-preflight.mjs",
32
+ "scripts/run-child.mjs",
33
+ "scripts/build-preflight-toolchain.mjs",
34
+ "docs/TOOLCHAIN.md",
35
+ "docs/PREFLIGHT.md",
36
+ "docs/RUNTIME.md",
37
+ "docs/IMPACT.md",
38
+ "docs/BRIDGE-MESSAGES.md",
39
+ "docs/BRIDGE-EVENTS.md",
40
+ "docs/MCP.md",
41
+ "docs/GRAPH-EXCHANGE.md",
42
+ "compatibility.json",
31
43
  "LICENSE",
32
44
  "README.md",
33
45
  "README.ko.md"
@@ -43,7 +55,7 @@
43
55
  "verify:build": "node scripts/verify-build-contract.mjs",
44
56
  "verify:cli": "npm run verify:build && node scripts/verify-cli-contract.mjs",
45
57
  "verify:package": "npm run verify:build && node scripts/verify-package-contract.mjs",
46
- "verify": "npm run typecheck && npm test && npm run test:phase0:join && npm run verify:build && node scripts/verify-cli-contract.mjs && node scripts/verify-package-contract.mjs",
58
+ "verify": "npm run typecheck && node scripts/verify-compatibility.mjs && npm test && npm run test:phase0:join && npm run verify:build && node --test scripts/capture-preflight.test.mjs scripts/build-preflight-toolchain.test.mjs scripts/stop-detached-process.test.mjs scripts/verify-installed-compatibility.test.mjs && node scripts/verify-cli-contract.mjs && node scripts/verify-package-contract.mjs",
47
59
  "prepublishOnly": "npm run verify"
48
60
  },
49
61
  "devDependencies": {