isthmus-cli 0.2.0 → 0.4.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.
package/README.md CHANGED
@@ -1,98 +1,167 @@
1
1
  # isthmus
2
2
 
3
- 크로스플랫폼 앱에서 **언어 경계를 넘는 호출**을 그래프로 잇는 도구.
4
- [cartograph](https://github.com/ictechgy/cartograph)(Swift) · kartograph(Kotlin, 예정) ·
5
- [dartograph](https://github.com/ictechgy/dartograph)(Dart) 각자 그린 지도를 하나로 붙인다.
6
-
7
- 이름은 지협(isthmus) — 두 땅덩어리를 잇는 좁은 육교. 지도에서 다리를 뜻하는 말이다.
8
-
9
- ## 무엇을 하려는가
10
-
11
- React Native Flutter 앱의 네이티브 코드는 JS/Dart 가 **문자열 이름으로** 부른다. `MethodChannel('com.example/camera')`, `NativeModules.CameraModule`. 컴파일러 인덱스는 이 문자열을 못 본다. 그래서:
12
-
13
- - cartograph Flutter 부르는 Swift 핸들러를 **미사용** 이라고 한다 — 오탐
14
- - 기존 언어별 분석만으로는 "Dart가 `invokeMethod('takePhoto')`를 부르는데 Swift 쪽에
15
- 핸들러가 없다"를 **빌드 전에** 잡기 어렵다 런타임 크래시
16
- - 같은 이유로 "이 채널은 Swift에는 있는데 Dart 어디서도 안 부른다"는 교차 경계
17
- 사실을 언어별 도구 하나만으로는 판단하기 어렵다
18
-
19
- isthmus 언어 도구가 내보낸 **브리지 사실**(채널 이름 · 메서드 이름 · 등록 지점 · 호출 지점)을 문자열 키로 조인해서, 경계를 넘는 간선을 만들고 위 세 가지를 답한다. 그리고 그 결과를 cartograph/kartograph 에 **보존 근거로 돌려준다** "Swift `CameraHandler.takePhoto` 는 `lib/camera.dart:42` 가 채널 `com.example/camera` 로 부르므로 보존".
20
-
21
- isthmus는 조사한 도구들이 언어별로 나눠 보던 교차 경계를 조인한다.
22
-
23
- ## 상태
24
-
25
- **0.2.0.** bridge-facts 버전 1 파서와 `check`, `query`, `graph`, `diff`,
26
- cartograph용 외부 보존 근거 왕복을 구현했다. 외부 입력·혼합 target·그래프 크기와
27
- Dart/Swift Phase 0 추출 경계를 fail-closed로 강화했고, 조인하지 못한 사실과 근거를
28
- 만들지 못한 보존 대상이 조용히 사라지지 않도록 소비자 쪽에서 다시 세고, 수신 측이 신고한
29
- 분석 공백은 불일치가 아니라 판정 불가로 보고한다. 한계는 신고 문서의 target으로 귀속되어
30
- 공백 완화가 다른 target의 진단으로 번지지 않는다. 다음 단계는
31
- 실제 Flutter 앱 도그푸딩과 React Native 지원이다.
32
-
33
- 정식 producer는 cartograph 0.5.3 이상과 dartograph 0.1.1 이상이다. 도구의 실제 출력과
34
- 공개 battery 플러그인의 Swift USR·Dart 호출 근거 왕복을 검증했다.
35
-
36
- | 문서 | 내용 |
3
+ **Cross-language bridge calls in cross-platform apps, joined into one graph.**
4
+ [cartograph](https://github.com/ictechgy/cartograph) (Swift) · kartograph (Kotlin, planned) ·
5
+ [dartograph](https://github.com/ictechgy/dartograph) (Dart) each draw their own map; isthmus
6
+ joins them into one.
7
+
8
+ [한국어 문서](README.ko.md)
9
+
10
+ The name refers to an isthmus — the narrow strip of land that connects two landmasses. On a
11
+ map, it is the bridge.
12
+
13
+ ## What it does, and why
14
+
15
+ Native code in a React Native or Flutter app is called from JS/Dart **by string name**:
16
+ `MethodChannel('com.example/camera')`, `NativeModules.CameraModule`. Compiler indexes cannot
17
+ see these strings. As a result:
18
+
19
+ - cartograph reports a Swift handler that Flutter calls as **unused**a false positive
20
+ - per-language analysis alone struggles to catch "Dart calls `invokeMethod('takePhoto')` but
21
+ no Swift handler exists" **before the build** a runtime crash
22
+ - for the same reason, "this channel exists in Swift but nothing in Dart calls it" is a
23
+ cross-boundary fact that is hard to judge from any single per-language tool
24
+
25
+ isthmus joins the **bridge facts** each language tool exports (channel names, method names,
26
+ registration sites, invocation sites) by string key, builds the edges that cross the boundary,
27
+ and answers those three questions. It then hands the result **back to cartograph/kartograph as
28
+ retention evidence** "keep Swift `CameraHandler.takePhoto`, because `lib/camera.dart:42`
29
+ calls it over channel `com.example/camera`".
30
+
31
+ ## Status
32
+
33
+ **0.4.0.** The 0.3.0 contract and commands the bridge-facts version 1 parser, `check`,
34
+ `query`, `graph`, `diff`, the external retention evidence round trip for cartograph, and
35
+ check baselines that suppress accepted findings by logical issue identity while preserving
36
+ their evidence plus this cycle's additions: a SARIF 2.1.0 rendering of check results for
37
+ GitHub code scanning (`check --format sarif`, additive, with logic-key fingerprints that
38
+ survive source line moves), observation volume in the check summary (`observedFacts`,
39
+ `observedLimitations`) so a project without bridges and a run that observed nothing are no
40
+ longer indistinguishable, multi-caller retention evidence (`evidence.callers` with a
41
+ per-retention cap and an explicit `callersOmitted` count, byte-identical for single
42
+ callers), and usr-less `qualifiedName` identity for Objective-C handlers built without an
43
+ index. isthmus keeps fail-closed behavior for external input, mixed targets, graph size,
44
+ and the Dart/Swift Phase 0 extraction boundary. Facts that could not be joined are
45
+ re-counted on the consumer side, and retention subjects whose evidence cannot be built are
46
+ refused loudly, so neither disappears silently. Coverage gaps a receiver reports about
47
+ itself come back as undecidable, not as mismatches, and gap mitigation never leaks across
48
+ targets. Next: dogfooding it on a real Flutter app, and React Native support.
49
+
50
+ The supported producers are cartograph 0.5.3+ and dartograph 0.1.1+. Both were verified on
51
+ their real output, and on a Swift USR ↔ Dart invocation evidence round trip over the public
52
+ battery plugin.
53
+
54
+ | Document | Contents |
37
55
  |---|---|
38
- | [`docs/PRD.md`](docs/PRD.md) | 무엇을 · 누구를 위해 · 어디까지 |
39
- | [`docs/PLAN.md`](docs/PLAN.md) | 단계별 계획. **cartograph dartograph 선행 작업이 있다** |
40
- | [`docs/GRAPH-EXCHANGE.md`](docs/GRAPH-EXCHANGE.md) | 자매 도구가 내보내는 브리지 사실의 형식. 저장소가 공유하는 계약 |
41
- | [`docs/RESEARCH.md`](docs/RESEARCH.md) | 확인된 사실 · 확인되지 않은 주장 |
42
- | [`experiments/phase-0/`](experiments/phase-0/) | Dart·Swift 임시 추출기, 고정 JSON, 조인 검증 |
56
+ | [`docs/PRD.md`](docs/PRD.md) | What, for whom, how far |
57
+ | [`docs/PLAN.md`](docs/PLAN.md) | Step-by-step plan. **cartograph and dartograph have prerequisite work** |
58
+ | [`docs/GRAPH-EXCHANGE.md`](docs/GRAPH-EXCHANGE.md) | The bridge-facts format the sister tools export the contract shared across the sister repositories |
59
+ | [`docs/RESEARCH.md`](docs/RESEARCH.md) | Confirmed facts vs. unconfirmed claims |
60
+ | [`experiments/phase-0/`](experiments/phase-0/) | Temporary Dart/Swift extractors, pinned JSON, hand-join verification |
43
61
 
44
- ## 의존 관계
62
+ Internal documents are maintained in Korean, the maintainer's working language.
63
+
64
+ ## Dependency picture
45
65
 
46
66
  ```
47
- cartograph ──bridges──┐
48
- kartograph ──bridges──┼──▶ isthmus ──▶ 경계 간선 · 불일치 보고 · 보존 근거
49
- dartograph ──bridges──┤
50
- JS/TS 추출기 ─bridges──┘
67
+ cartograph ──bridges──┐
68
+ kartograph ──bridges──┼──▶ isthmus ──▶ boundary edges · mismatch reports · retention evidence
69
+ dartograph ──bridges──┤
70
+ JS/TS extractor ─bridges─┘
51
71
  ```
52
72
 
53
- isthmus 자체는 작다. 무거운 일(각 언어의 해석)은 자매 도구가 한다.
73
+ isthmus itself is small. The heavy lifting interpreting each language — falls to the
74
+ sister tools.
54
75
 
55
- ## 설치
76
+ ## Install
56
77
 
57
- Node.js 22.18.0 이상이 필요하다.
78
+ Requires Node.js 22.18.0 or later.
58
79
 
59
- 전역 설치 CLI 이름 `isthmus`로 실행한다.
80
+ Install globally and run it under the CLI name `isthmus`:
60
81
 
61
82
  ```bash
62
83
  npm install --global isthmus-cli
63
84
  isthmus --help
64
85
  ```
65
86
 
66
- 설치 없이 실행할 때는 package 이름을 명시한다.
87
+ For a one-off run without installing, name the package explicitly:
67
88
 
68
89
  ```bash
69
90
  npx isthmus-cli --help
70
91
  ```
71
92
 
72
- `npx isthmus`는 이름이 같은 다른 package 설치하므로 사용하지 않는다.
93
+ Do not use `npx isthmus` that installs a different package with the same name.
73
94
 
74
- ## 사용
95
+ ## Usage
75
96
 
76
- isthmus 자매 도구를 직접 실행하지 않는다. 도구가 만든 JSON 파일을 전달한다.
97
+ isthmus never runs the sister tools itself. You hand it the JSON files they produced:
77
98
 
78
99
  ```bash
79
100
  isthmus check dart-bridges.json swift-bridges.json
80
101
  ```
81
102
 
82
- 전체 명령과 현재 package 버전은 다음처럼 확인한다.
103
+ List all commands and the installed package version:
83
104
 
84
105
  ```bash
85
106
  isthmus --help
86
107
  isthmus --version
87
108
  ```
88
109
 
89
- CI에서 브리지 오류가 있으면 실패시키려면 `--strict`를 붙인다.
110
+ To make CI fail when bridge errors exist, add `--strict`:
90
111
 
91
112
  ```bash
92
113
  isthmus check dart-bridges.json swift-bridges.json --strict
93
114
  ```
94
115
 
95
- 매치된 Swift 핸들러를 cartograph 보존 근거로 돌려주려면:
116
+ ### SARIF output
117
+
118
+ To upload check results to GitHub code scanning (or any SARIF 2.1.0 consumer), ask for SARIF
119
+ instead of the isthmus-check JSON:
120
+
121
+ ```bash
122
+ isthmus check dart-bridges.json swift-bridges.json --format sarif > isthmus.sarif
123
+ ```
124
+
125
+ The default is `--format json`, which keeps the versioned isthmus-check document. SARIF is an
126
+ additive, isthmus-owned rendering of the same join: every issue becomes a result with its
127
+ `check` issue code as the rule id, the first evidence endpoint as the primary location
128
+ (project-relative paths become percent-encoded, repository-relative URIs), remaining
129
+ endpoints as related locations, and baseline-suppressed issues carry an `external`
130
+ suppression. Results include a
131
+ `partialFingerprints` hash of the logical issue identity (code, target, channel, method), so
132
+ deduplication survives source line moves exactly like baseline suppression. `--strict`,
133
+ `--baseline`, and `--update-baseline` combine with either format and keep their documented
134
+ exit-code behavior.
135
+
136
+ ### Baselines
137
+
138
+ To accept the current findings as a baseline, write them to a file once and apply that file
139
+ from the next run onward:
140
+
141
+ ```bash
142
+ isthmus check dart-bridges.json swift-bridges.json --update-baseline isthmus-baseline.json
143
+ isthmus check dart-bridges.json swift-bridges.json --strict --baseline isthmus-baseline.json
144
+ ```
145
+
146
+ `--update-baseline` does not suppress the run that writes it; it rewrites the whole file — an
147
+ isthmus-owned `isthmus-baseline` version 1 document — from the current issues, so resolved
148
+ items drop out automatically. `--baseline` suppresses only the issues whose logical identity
149
+ (code, target, channel, method) matches an entry, so moving source lines never breaks
150
+ suppression and a new channel or method mismatch is never suppressed. Suppressed issues are
151
+ not deleted: they keep their facts, evidence, and severity, gain a `suppressed` marker, and
152
+ are excluded only from the summary error/warning counts and the `--strict` decision. Entries
153
+ that match no issue are counted in `staleBaselineEntries`, so a baseline hiding a future
154
+ regression stays visible in the report. An unreadable, non-JSON, or contract-violating
155
+ baseline file fails with exit code 2. The two flags cannot be combined in one run, and a value
156
+ starting with `-` is rejected (including legitimate file names that start with `-`). `--update-baseline` combined
157
+ with `--strict` still writes the file, and the exit code follows that run's unsuppressed
158
+ errors. If more than 10,000 entries would be recorded, the command fails with exit code 2
159
+ instead of leaving an artifact it cannot consume. Writes go through a temporary file in the
160
+ same directory (atomic rename), so an interrupted run cannot corrupt an existing baseline.
161
+
162
+ ### Retention evidence
163
+
164
+ To return matched Swift handlers to cartograph as retention evidence:
96
165
 
97
166
  ```bash
98
167
  isthmus retentions \
@@ -102,18 +171,34 @@ isthmus retentions \
102
171
  cartograph dead --external-retentions external-retentions.json
103
172
  ```
104
173
 
105
- `retentions`는 핸들러의 USR을 우선 사용하고 없으면 `qualifiedName`을 남긴다. `mixed-targets` 문서는 v1에서 사실별 target을 복원할 없어 모든 소비 명령이 종료 코드 2로 조인을 보류한다. 먼저 생산 단계에서 target별 문서로 분리해야 한다.
106
-
107
- cartograph는 Swift 심볼만 보존하므로 `--for cartograph`는 수신 Swift 문서를 최소 하나 요구하고, 없으면 빈 보존 문서 대신 종료 코드 2로 거부한다. 호출자가 있는데도 `symbol`이 없어 보존 근거로 바꿀 수 없는 Swift 핸들러가 있으면 부분 문서를 만들지 않고 같은 코드로 실패한다. 근거가 빠진 보존 파일은 소비자에게 살아 있는 핸들러를 미사용으로 보이게 하기 때문이다.
108
-
109
- 모든 소비 명령은 호출 측(dart)과 수신 측(swift) 플랫폼 문서를 최소 하나씩 요구한다. 한쪽만 있으면 한쪽 관찰을 경계 불일치로 오독하지 않고 종료 코드 2로 거부한다. 입력 실패 메시지는 원인(읽기 실패, JSON 오류, 교환 계약 위반, project 불일치, 플랫폼 구성 누락, 크기 상한)과 입력 순서, 해결 방향을 구분해 전달하며 입력 본문과 경로는 노출하지 않는다.
110
-
111
- 실제 공개 Flutter 플러그인에서 생산부터 소비까지 확인하려면 저장소 루트에서 다음 검증을
112
- 실행한다. 스크립트는 `plus_plugins`의 고정 커밋을 sparse checkout하고 배터리 플러그인의
113
- 원본 Dart·Swift 소스에서 메서드의 보존 근거를 확인한 임시 checkout을 지운다.
114
- 네트워크, Git 2.26 이상, Swift 6, cartograph 0.5.3 이상, dartograph 0.1.1 이상이
115
- 필요하다. isthmus는 현재 소스에서 자동으로 다시 빌드하며, 번째 인자로 별도 isthmus
116
- JavaScript 산출물을 넘길 수도 있다.
174
+ `retentions` prefers each handler's USR and falls back to its `qualifiedName`. When a method is
175
+ invoked from several caller locations, the evidence carries all of them in `callers` (the
176
+ representative first `caller` stays for older consumers) and counts any entries beyond the
177
+ 100-per-retention cap in `callersOmitted` instead of dropping them silently. A
178
+ `mixed-targets` document cannot have per-fact targets restored in v1, so every consuming
179
+ command defers the join with exit code 2; split such a document per target at production time
180
+ first.
181
+
182
+ cartograph retains Swift symbols only, so `--for cartograph` requires at least one
183
+ receiver-side Swift document and refuses with exit code 2 instead of emitting an empty
184
+ retention document. If a matched Swift handler has callers but no `symbol`, and therefore
185
+ cannot become evidence, the command fails with the same code rather than producing a partial
186
+ document: a retention file with missing evidence makes live handlers look unused to the
187
+ consumer.
188
+
189
+ Every consuming command requires at least one caller-side (dart) and one receiver-side (swift)
190
+ platform document. Given only one side, it refuses with exit code 2 rather than misreading a
191
+ one-sided observation as a boundary mismatch. Input failure messages state the cause (read
192
+ failure, JSON error, exchange contract violation, project mismatch, missing platform
193
+ composition, size limit), the input position, and how to resolve it, and never expose input
194
+ bodies or paths.
195
+
196
+ To verify the whole path from production to consumption on a real public Flutter plugin, run
197
+ this from the repository root. The script does a sparse checkout of a pinned `plus_plugins`
198
+ commit, confirms the retention evidence for three methods in the battery plugin's original
199
+ Dart and Swift sources, and removes the temporary checkout. It needs network access, Git
200
+ 2.26+, Swift 6, cartograph 0.5.3+, and dartograph 0.1.1+. isthmus is rebuilt from the current
201
+ sources automatically; a third argument can point at a separate isthmus JavaScript artifact.
117
202
 
118
203
  ```bash
119
204
  npm run build
@@ -122,19 +207,22 @@ node scripts/verify-public-flutter-plugin.mjs \
122
207
  /path/to/dartograph
123
208
  ```
124
209
 
125
- 공개 플러그인 검증은 원본 `addMethodCallDelegate` 구현에서 나온 Swift USR과 원본 Dart
126
- 호출 위치를 확인하고, cartograph `--explain`이 해당 심볼의 대표 근거를 읽는지 검증한다. 이미
127
- public인 플러그인 handler의 dead 상태 전환을 억지로 만들지는 않는다. 전환과
128
- `setMethodCallHandler` 경로는 `verify-cartograph-roundtrip.mjs`의 합성 코퍼스가 별도로
129
- 검증한다.
210
+ The public plugin verification checks the Swift USRs from the original `addMethodCallDelegate`
211
+ implementation and the three original Dart call sites, and verifies that cartograph
212
+ `--explain` reads the representative evidence for those symbols. It does not force a
213
+ dead-state transition on an already-public plugin handler; that transition and the
214
+ `setMethodCallHandler` path are covered separately by the synthetic corpus in
215
+ `verify-cartograph-roundtrip.mjs`.
130
216
 
131
- 채널이나 메서드가 경계 반대편의 어느 위치와 연결되는지 조회하려면:
217
+ ### Query and graph
218
+
219
+ To see which locations on the other side of the boundary a channel or method connects to:
132
220
 
133
221
  ```bash
134
222
  isthmus query takePhoto dart-bridges.json swift-bridges.json
135
223
  ```
136
224
 
137
- 경계 간선만 JSON, Graphviz DOT, Mermaid로 출력하려면:
225
+ To emit only the boundary edges as JSON, Graphviz DOT, or Mermaid:
138
226
 
139
227
  ```bash
140
228
  isthmus graph dart-bridges.json swift-bridges.json
@@ -142,82 +230,103 @@ isthmus graph dart-bridges.json swift-bridges.json --format dot
142
230
  isthmus graph dart-bridges.json swift-bridges.json --format mermaid
143
231
  ```
144
232
 
145
- `query`는 같은 메서드가 여러 채널에 있으면 후보를 반환하고 임의로 고르지 않는다.
146
- 반환된 `qualifiedName`을 같은 subject 자리에 넣어 정확한 후보를 다시 조회할 수 있다.
147
- `graph`는 매치된 간선만 내보내며, 입력의 `limitations`를 JSON 필드 또는 DOT/Mermaid
148
- 주석으로 보존한다. 증거의 Cartesian 곱이 100,000 간선을 넘으면 메모리 폭증을 막기
149
- 위해 입력 오류로 종료한다.
150
-
151
- 출력은 `isthmus-check` 버전 1 JSON이며 다음 네 사실을 보고한다.
152
-
153
- - `unhandled-invocation` (error): 호출은 있지만 네이티브 핸들러가 없음
154
- - `unregistered-channel-creation` (error): 호출 측 채널 생성은 있지만 네이티브 등록이 없음
155
- - `registration-without-creation` (warning): 네이티브 채널 등록은 있지만 호출 생성이 없음
156
- - `handler-without-invocation` (warning): 네이티브 핸들러는 있지만 호출 사용이 없음
157
- - `unhandled-invocation-unverified` (warning): 위 첫 항목과 같은 사실이지만, 수신 측이
158
- 핸들러를 놓쳤을 있다고 스스로 신고해 없는 것인지 본 것인지 판정할 수 없음
159
- - `unregistered-channel-creation-unverified` (warning): 같은 이유로 등록 여부를 판정할 수 없음
160
-
161
- `-unverified` 종류는 수신 문서의 한계에서 나온다. 예를 들어 Flutter 핸들러가
162
- Objective-C로 쓰인 플러그인에서 cartograph는 `objective-c-sources:`를 신고하고 핸들러 사실을
163
- 완전히 열거하지 못할 수 있다. 이때 "핸들러 없는 호출"을 error로 단정하면 이 도구가 없애려던 오탐을 이 도구가
164
- 만든다. 사실과 증거는 그대로 보고하되 `--strict`를 실패시키지 않는다. 공백의 종류는 구분해서,
165
- 이름이 리터럴이 아닌 채널 등록은 채널 진단만 낮추고 메서드 진단은 낮추지 않는다. 완화 단위는
166
- 진단의 target이다. 사실은 target별로만 조인되므로 다른 target 수신 문서가 신고한 공백은 현재
167
- target의 진단을 낮추지 않고, 사실이 없는 수신 문서의 공백은 무엇이 가려졌는지 귀속시킬
168
- 없어 모든 target에 적용한다. 호출 한계는 네이티브 코드를 가리지 않으므로 심각도에 영향을
169
- 주지 않으며, 알려지지 않은 한계 문구는 공백으로 해석하지 않는다.
170
-
171
-
172
- 0.2.0은 v1의 선택적 `limitationScopes`를 읽습니다. `{ limitationIndex, channels }`는
173
- 해당 한계 전체의 보수적 채널 상한이며, 단순히 발견한 리터럴 목록이면 됩니다. 스코프가
174
- 없거나 다른 범위 불명 공백이 공존하면 기존 target 전체 완화를 유지합니다. 빈 채널 집합과
175
- 잘못된 인덱스는 입력 오류입니다. 스코프는 check/query/graph/diff에서 `channels`로 보존됩니다.
176
- 생산자의 tool 이름만으로 자체 계수를 신뢰하지 않으며, `unjoined-*`는 소비자가 직접 붙인
177
- `origin: "consumer"`가 있어야 완화 근거가 됩니다.
178
-
179
- 선택적 fact `sourceLanguage: "objective-c"`는 `.m`/`.mm`의 ObjC 구현을 Swift 그래프와
180
- 구분합니다. 사실에는 symbol을 붙이지 않습니다. 매치는 check/query/graph에 남고 Swift
181
- 보존 목록에서는 제외되며, `omittedObjectiveCHandlers`가 제외 수를 알립니다. 표식 없는
182
- Swift 핸들러의 symbol 누락은 여전히 종료 코드 2입니다. 확장을 지원하는 소비자를 먼저
183
- 배포해야 합니다. 소비자는 스코프를 버리고 넓게 완화하며 ObjC 보존 생성은 실패합니다.
184
-
185
- 모든 이슈는 관찰된 위치를 `evidence`로 제공한다. 동적 이름, 해석하지 못한 receiver나
186
- handler 본문, USR 누락, 입력 생성 시각 차이, 혼합 target은 `limitations`에 출처와 함께
187
- 남긴다. 도구는 삭제 가능 여부를 판정하지 않는다.
188
-
189
- isthmus 출력 문서는 버전 1 안에서 필드 추가나 이슈 code를 호환 변경으로 다룬다.
190
- 기존 필드의 의미를 바꾸거나 제거할 문서 버전을 올린다.
191
-
192
- `limitations`에는 생산자가 신고한 한계와 isthmus가 직접 한계가 함께 들어간다.
193
- 각 항목은 `platform`·`target`·`tool`로 출처와 귀속을 밝히고, `origin: "consumer"`인 항목은
194
- 조인 단계에서 관찰한 것이다. 조인하지 못한 사실은 생산자의 신고나 그 개수와 무관하게
195
- 플랫폼·target별로 다시 센다.
196
-
197
- - `unjoined-dynamic-channels`: 이름이 리터럴이 아닌 채널 생성·등록 사실
198
- - `unjoined-dynamic-methods`: 이름이 리터럴이 아닌 호출·핸들러 사실
199
- - `unjoined-unattributed-handlers`: 어느 채널에 속하는지 모르는 핸들러 사실
200
-
201
- 같은 위치의 중복 사실은 한 번만 센다. dynamic이면서 미귀속인 핸들러는 dynamic으로만 센다.
202
-
203
- | 종료 코드 | 의미 |
233
+ When the same method exists on several channels, `query` returns candidates instead of picking
234
+ one; feed a returned `qualifiedName` back into the same subject position to disambiguate.
235
+ `graph` emits matched edges only and preserves the input `limitations` as a JSON field or as
236
+ DOT/Mermaid comments. If the Cartesian product of evidence would exceed 100,000 edges, the
237
+ command fails with exit code 2 to prevent a memory blowup.
238
+
239
+ ### What check reports
240
+
241
+ The output is `isthmus-check` version 1 JSON, reporting these facts:
242
+
243
+ - `unhandled-invocation` (error): an invocation exists but no native handler does
244
+ - `unregistered-channel-creation` (error): a caller-side channel creation exists but no
245
+ native registration does
246
+ - `registration-without-creation` (warning): a native channel registration exists but no
247
+ caller-side creation does
248
+ - `handler-without-invocation` (warning): a native handler exists but no caller-side use does
249
+ - `unhandled-invocation-unverified` (warning): the same fact as the first item, but the
250
+ receiver reported that it may have missed handlers, so "absent" and "not seen" cannot be
251
+ distinguished
252
+ - `unregistered-channel-creation-unverified` (warning): registration undecidable for the same
253
+ reason
254
+
255
+ The `summary` carries the issue counts plus observation volume: `observedFacts` is the total
256
+ number of facts across all input documents and `observedLimitations` counts the reported
257
+ analysis limitations. This keeps a project with no bridges and a run that observed nothing
258
+ from producing indistinguishable reports — an `observedFacts` of 0 means the producers saw
259
+ nothing to describe.
260
+
261
+ The `-unverified` kinds come from limitations in receiver-side documents. For example, in a
262
+ plugin whose Flutter handler is written in Objective-C, cartograph reports
263
+ `objective-c-sources:` and may fail to enumerate the handler facts completely. Asserting
264
+ "unhandled invocation" as an error there would recreate the very false positive this tool
265
+ exists to remove. The facts and evidence are still reported, but `--strict` does not fail.
266
+ Gap kinds are distinguished: a channel registration with a non-literal name downgrades channel
267
+ diagnostics only, never method diagnostics. The mitigation unit is the diagnostic's target —
268
+ facts join per target only, so a gap reported by another target's receiver document never
269
+ downgrades the current target's diagnostics, and a gap from a receiver document with no facts
270
+ applies to all targets because nothing can be attributed. Caller-side limitations never hide
271
+ native code, so they do not affect severity, and unknown limitation wording is never
272
+ interpreted as a gap.
273
+
274
+ Since 0.2.0, isthmus reads the optional v1 `limitationScopes`. `{ limitationIndex, channels }`
275
+ is a conservative channel upper bound for that limitation as a whole — never merely a list of
276
+ literals that happened to be found. Without scopes, or when a scoped entry coexists with
277
+ another unscoped gap, the existing whole-target mitigation stands. An empty channel set or an
278
+ invalid index is an input error. Scopes are preserved as `channels` through
279
+ check/query/graph/diff. A producer's tool name alone never drives mitigation: `unjoined-*`
280
+ counts mitigate only when they carry the consumer-attached `origin: "consumer"`.
281
+
282
+ The optional fact field `sourceLanguage: "objective-c"` distinguishes ObjC implementations in
283
+ `.m`/`.mm` files from the Swift graph. These facts carry no symbol. Their matches stay in
284
+ check/query/graph but are excluded from the Swift retention list, and
285
+ `omittedObjectiveCHandlers` reports how many were excluded. A matched Swift handler that has
286
+ callers but no `symbol` and no `sourceLanguage` marker still fails with exit code 2.
287
+ Consumers supporting this extension must ship before producers: old consumers drop scopes
288
+ (mitigating broadly) and fail to produce ObjC retentions.
289
+
290
+ Every issue carries its observed locations as `evidence`. Dynamic names, unresolved receivers
291
+ or handler bodies, missing USRs, input generation-time differences, and mixed targets stay in
292
+ `limitations` with their provenance. This tool never decides whether code is safe to delete.
293
+
294
+ isthmus output documents treat added fields and new issue codes within version 1 as compatible
295
+ changes; the document version is raised only when an existing field's meaning changes or the
296
+ field is removed.
297
+
298
+ `limitations` holds both producer-reported and isthmus-counted limitations. Each entry states
299
+ its provenance and attribution with `platform`, `target`, and `tool`; entries with
300
+ `origin: "consumer"` were observed at the join stage. Facts that could not be joined are
301
+ re-counted per platform and target, regardless of what producers reported or how many they
302
+ reported:
303
+
304
+ - `unjoined-dynamic-channels`: channel create/register facts with a non-literal name
305
+ - `unjoined-dynamic-methods`: invoke/handle facts with a non-literal name
306
+ - `unjoined-unattributed-handlers`: handler facts that belong to no known channel
307
+
308
+ Duplicate facts at the same location are counted once. A handler that is both dynamic and
309
+ unattributed is counted as dynamic only.
310
+
311
+ | Exit code | Meaning |
204
312
  |---|---|
205
- | `0` | 실행 성공. 기본 모드에서는 이슈가 있어도 보고만 |
206
- | `1` | `--strict`에서 error 이슈를 발견함. `-unverified` 경고는 실패시키지 않음 |
207
- | `2` | 파일 읽기, JSON, 교환 계약, project 불일치, 플랫폼 구성 누락, 보류된 조인 도구 실패. stderr 원인을 구분 |
208
- | `64` | 잘못된 명령·옵션·입력 개수 또는 `query`의 `notFound`·`ambiguous` |
313
+ | `0` | Success. In default mode, issues are reported but do not fail the run |
314
+ | `1` | `--strict` found error issues (for `diff`: newly observed errors only). `-unverified` warnings and baseline-suppressed errors do not fail |
315
+ | `2` | Tool failure: file read, JSON, exchange contract, project mismatch, missing platform composition, deferred join, size limits (input text, graph edges, baseline entries), baseline file or write errors, retention evidence that cannot be built. stderr distinguishes the cause |
316
+ | `64` | Bad command, option, or input count; or `query` `notFound`/`ambiguous` |
209
317
 
210
- 저장소 checkout에서 개발할 때는 먼저 `npm ci`를 실행한다. 개발 검증은 타입 체크,
211
- 제품 코드 90% 커버리지, clean build, 실제 CLI·package 계약을 함께 실행한다.
318
+ For development from a checkout, run `npm ci` first. Development verification runs the type
319
+ check and a clean build, enforces 90% product-code coverage, and exercises the real CLI and
320
+ package contract checks together:
212
321
 
213
322
  ```bash
214
323
  npm run verify
215
324
  ```
216
325
 
217
- 실제 producer와 외부 보존 근거 왕복을 검증하려면 cartograph 0.5.3 이상,
218
- dartograph 바이너리와 도구가 함께 분석할 fixture 루트를 넘긴다. 검증은 producer
219
- 바이너리와 컴파일러 인덱스가 필요하므로 `npm run verify`와 공개 CI에는 포함되지 않으며
220
- 릴리스 전에 수동으로 실행한다.
326
+ To verify the external retention round trip with the two real producers, pass cartograph
327
+ 0.5.3+, the dartograph binary, and a fixture root both tools can analyze. This needs producer
328
+ binaries and a compiler index, so it is not part of `npm run verify` or public CI; run it
329
+ manually before a release.
221
330
 
222
331
  ```bash
223
332
  node scripts/verify-cartograph-roundtrip.mjs \
@@ -226,9 +335,24 @@ node scripts/verify-cartograph-roundtrip.mjs \
226
335
  /path/to/FalsePositiveCorpus
227
336
  ```
228
337
 
229
- ## 변경 전후 비교 (0.1.4 이상)
338
+ To verify that producer-emitted `limitationScopes` reach the consumer and relax diagnostics at
339
+ channel granularity, run the self-contained scope dogfood. It synthesizes a minimal Swift
340
+ package with a delegated handler registration — a literal channel whose handler body the
341
+ scanner cannot inspect, the one shape whose channel upper bound is provable — plus a Dart
342
+ caller, and checks that only the scoped channel's unhandled invocation becomes an unverified
343
+ warning while an adjacent unhandled invocation and an unregistered channel creation stay
344
+ errors. It needs cartograph 0.9.0+, dartograph 0.1.1+, and Swift 6, and performs no network
345
+ access.
230
346
 
231
- 같은 프로젝트의 변경 전후 Dart·Swift 교환 파일을 비교하려면:
347
+ ```bash
348
+ node scripts/verify-limitation-scopes.mjs \
349
+ /path/to/cartograph \
350
+ /path/to/dartograph
351
+ ```
352
+
353
+ ## Comparing before and after a change (0.1.4+)
354
+
355
+ To compare the Dart and Swift exchange files of one project before and after a change:
232
356
 
233
357
  ```bash
234
358
  isthmus diff \
@@ -236,34 +360,38 @@ isthmus diff \
236
360
  --after after-dart.json after-swift.json --strict
237
361
  ```
238
362
 
239
- `isthmus-diff` v1 추가·제거된 논리 메서드 연결, 새로 관찰된 불일치와 사라진 불일치,
240
- 시점의 분석 한계와 차이, producer 버전·생성 시각을 JSON으로 출력한다. 연결에는 호출자와
241
- 핸들러 위치가 포함된다. 이동은 연결 변경으로 세지 않으며 rename은 추측하지 않는다.
242
- 같은 논리 키의 호출자·핸들러 교체나 개별 호출 위치 증감은 이번 비교 범위에 포함하지 않는다.
243
-
244
- `--strict`는 새로 관찰된 error가 있을 때만 1이다. 기존 오류·경고·분석 한계만 있으면 0이므로
245
- 성공 코드가 삭제 안전성이나 완전한 분석을 뜻하지 않는다. `resolvedIssues`도 이전 불일치가
246
- 이상 관찰되지 않는다는 뜻이며, 동적 전환·추출기 변경 때문인지 한계를 함께 확인해야 한다.
247
- `--strict`은 인자 위치와 무관하게 인식하며 이상 없다.
248
-
249
- 현재 diff는 Flutter의 Dart·Swift 문서만 받는다. 시점에 플랫폼이 모두 필요하며,
250
- 시점의 `project`와 플랫폼·도구별 문서 개수가
251
- 같아야 한다. 한 checkout의 같은 경로에서 각 revision을 빌드해 JSON을 보관한다. 일부 파일만
252
- 추출한 결과와 전체 결과를 비교하지 말고 같은 분석 설정을 사용한다. 입력 파일은 합계 256개,
253
- 텍스트 길이 제한은 기존 CLI와 동일하다. 혼합 target이나 비교 불가능한 입력은 코드 2로 거부한다.
254
- `generatedAt`은 fact 추출 시각이며 revision 순서가 아니다. 비교 방향은 `--before`와
255
- `--after` 인자로 결정되므로 사용자가 올바른 revision의 파일을 지정해야 한다.
256
-
257
- ## 코딩 에이전트 skill
258
-
259
- 네이티브 브리지 핸들러를 지우거나 이름을 바꾸기 전에 `query`로 다른 언어의 호출자를
260
- 확인하도록 가르치는 skill 원문을 [`Skills/isthmus/SKILL.md`](Skills/isthmus/SKILL.md)에 제공한다.
261
- 사용하는 에이전트의 프로젝트 skill 디렉터리에 이 파일을 복사해 사용할 수 있다.
262
-
263
- Codex는 이 checkout의 `.agents/skills/isthmus` 링크로 같은 원문을 발견한다.
264
- `Skills/isthmus/SKILL.md` 곳만 편집하며, npm 패키지에는 원문이 포함된다.
265
- 스킬 내용 검증과 모델별 지침 조정 근거는 [에이전트 감사 기록](docs/AGENT-AUDIT.md)에 있다.
266
-
267
- ## 라이선스
268
-
269
- [MIT](LICENSE). 상업적 사용을 포함해 영구 무료다.
363
+ `isthmus-diff` v1 outputs, as JSON: added and removed logical method connections, newly
364
+ observed and no-longer-observed issues, the analysis limitations at both snapshots and their
365
+ difference, and producer versions and generation times. Connections include caller and handler
366
+ locations. Line moves do not count as connection changes, and renames are never inferred.
367
+ Caller/handler replacement under the same logical key, and per-call-site additions or
368
+ removals, are outside this comparison.
369
+
370
+ `--strict` exits 1 only when a newly observed error exists. With only pre-existing errors,
371
+ warnings, or limitations, it exits 0, so a success code never means "safe to delete" or "fully
372
+ analyzed". `resolvedIssues` likewise means a previous mismatch is no longer observed — check
373
+ the limitations to see whether a dynamic transition or an extractor change caused it.
374
+ `--strict` is recognized at any argument position and cannot be given more than once.
375
+
376
+ `diff` currently accepts only Flutter Dart/Swift documents. Both platforms are required at
377
+ each point in time, and the two snapshots must agree on `project` and on the per-platform,
378
+ per-tool document counts. Build each revision from the same checkout path and keep the JSON.
379
+ Do not compare a partial extraction against a full one; use the same analysis settings. Input
380
+ files are capped at 256 total, and the text size limits match the rest of the CLI. Mixed
381
+ targets or incomparable inputs are refused with exit code 2. `generatedAt` is the fact
382
+ extraction time, not an indicator of revision order — the comparison direction comes from the
383
+ `--before` and `--after` arguments, so you must point them at the right revisions.
384
+
385
+ ## Coding-agent skill
386
+
387
+ [`Skills/isthmus/SKILL.md`](Skills/isthmus/SKILL.md) provides a skill that teaches agents to
388
+ check other languages' callers with `query` before deleting or renaming a native bridge
389
+ handler. Copy it into your agent's project skill directory.
390
+
391
+ Codex discovers the same text through the `.agents/skills/isthmus` link in this checkout.
392
+ Edit only `Skills/isthmus/SKILL.md`; the npm package includes it. Skill content verification
393
+ and per-model tuning rationale are in the [agent audit record](docs/AGENT-AUDIT.md).
394
+
395
+ ## License
396
+
397
+ [MIT](LICENSE). Free forever, including commercial use.
@@ -21,6 +21,13 @@ If inputs are missing, identify the required files and proceed with independent
21
21
 
22
22
  ## Choose the requested operation
23
23
 
24
+ - Audit the boundary: `isthmus check <dart.json> <swift.json> [--strict]`.
25
+ Read `summary`, each `issues[].code/severity/evidence`, and `limitations`.
26
+ `-unverified` codes are undecidable findings, not clean results.
27
+ To accept current findings, run once with `--update-baseline <file>`; apply it
28
+ later with `--baseline <file>`. Suppressed issues keep their evidence and only
29
+ leave the summary counts and `--strict` failures; report `staleBaselineEntries`
30
+ as resolved items to prune on the next update.
24
31
  - Trace callers: `isthmus query <channel-or-method> <dart.json> <swift.json>`.
25
32
  Read `usedBy`, `dependsOn`, symbol evidence, and `limitations`.
26
33
  `ambiguous` returns qualified-name candidates; disambiguate from context or ask when necessary.