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
@@ -1 +1 @@
1
- {"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/report/query.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BridgeTarget } from '../exchange/parse.ts';\nimport { compareStrings } from '../compare.ts';\nimport type {\n BridgeEndpoint,\n BridgeJoinResult,\n JoinLimitation,\n} from '../join/join.ts';\nimport { isBridgeJoinDeferred } from '../join/join.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\n\n/** query가 식별한 채널 또는 메서드 키다. */\nexport interface BridgeQuerySubject {\n readonly name: string;\n readonly qualifiedName: string;\n readonly kind: 'channel' | 'method';\n}\n\n/** 한 브리지 키에서 본 호출 측과 수신 측 증거다. */\nexport interface BridgeQueryResult {\n readonly subject: BridgeQuerySubject;\n readonly usedBy: readonly BridgeEndpoint[];\n readonly dependsOn: readonly BridgeEndpoint[];\n}\n\n/** cartograph query와 같은 상태 외피를 쓰는 브리지 질의 문서다. */\nexport interface BridgeQueryDocument {\n readonly status: 'found' | 'ambiguous' | 'notFound';\n readonly requested: string;\n readonly level: 'bridge';\n readonly limitations: readonly JoinLimitation[];\n readonly result?: BridgeQueryResult;\n readonly candidates?: ReadonlyArray<{\n readonly qualifiedName: string;\n }>;\n}\n\n/** query 문서를 결정적인 JSON 문자열로 인코딩한다. */\nexport function encodeBridgeQuery(document: BridgeQueryDocument): string {\n return encodeSortedJson(document);\n}\n\n/** 조인된 브리지에서 채널 또는 메서드 문자열을 질의한다. */\nexport function createBridgeQuery(\n joined: BridgeJoinResult,\n requested: string,\n): BridgeQueryDocument {\n if (isBridgeJoinDeferred(joined)) {\n throw new Error('Cannot query a deferred bridge join.');\n }\n const results = [...channelResults(joined), ...methodResults(joined)];\n const exact = results.filter(\n ({ subject }) => subject.qualifiedName === requested,\n );\n if (exact.length > 1) return ambiguousQuery(joined, requested, exact);\n if (exact[0] !== undefined) return foundQuery(joined, requested, exact[0]);\n\n const named = results.filter(({ subject }) => subject.name === requested);\n if (named.length > 1) return ambiguousQuery(joined, requested, named);\n if (named[0] !== undefined) return foundQuery(joined, requested, named[0]);\n return {\n status: 'notFound',\n requested,\n level: 'bridge',\n limitations: joined.limitations,\n };\n}\n\n/** 유일하게 식별된 논리 키와 증거를 found 문서로 감싼다. */\nfunction foundQuery(\n joined: BridgeJoinResult,\n requested: string,\n result: BridgeQueryResult,\n): BridgeQueryDocument {\n return {\n status: 'found',\n requested,\n level: 'bridge',\n limitations: joined.limitations,\n result,\n };\n}\n\n/** 여러 논리 키가 같은 요청과 맞을 때 선택 가능한 정규화 이름을 돌려준다. */\nfunction ambiguousQuery(\n joined: BridgeJoinResult,\n requested: string,\n results: readonly BridgeQueryResult[],\n): BridgeQueryDocument {\n return {\n status: 'ambiguous',\n requested,\n level: 'bridge',\n limitations: joined.limitations,\n candidates: results\n .map(({ subject }) => ({ qualifiedName: subject.qualifiedName }))\n .sort((left, right) => compareStrings(left.qualifiedName, right.qualifiedName)),\n };\n}\n\n/** 요청 문자열과 정확히 같은 논리 메서드 결과를 만든다. */\nfunction methodResults(\n joined: BridgeJoinResult,\n): BridgeQueryResult[] {\n const matched = joined.matchedMethods\n .map(({ target, channel, method, invocations, handlers }) =>\n makeMethodResult(target, channel, method, invocations, handlers),\n );\n const unhandled = joined.unhandledInvocations\n .map(({ target, channel, method, invocations }) =>\n makeMethodResult(target, channel, method, invocations, []),\n );\n const handlers = joined.handlersWithoutInvocations\n .map(({ target, channel, method, handlers }) =>\n makeMethodResult(target, channel, method, [], handlers),\n );\n return [...matched, ...unhandled, ...handlers];\n}\n\n/** 메서드 키와 양쪽 증거를 query result로 바꾼다. */\nfunction makeMethodResult(\n target: BridgeTarget,\n channel: string,\n method: string,\n usedBy: readonly BridgeEndpoint[],\n dependsOn: readonly BridgeEndpoint[],\n): BridgeQueryResult {\n return {\n subject: {\n name: method,\n qualifiedName:\n `${target}:${encodeSubjectComponent(channel)}#${encodeSubjectComponent(method)}`,\n kind: 'method',\n },\n usedBy,\n dependsOn,\n };\n}\n\n/** 요청 문자열과 정확히 같은 논리 채널 결과를 만든다. */\nfunction channelResults(\n joined: BridgeJoinResult,\n): BridgeQueryResult[] {\n const matched = joined.matchedChannels\n .map(({ target, channel, creations, registrations }) =>\n makeQueryResult(target, channel, 'channel', creations, registrations),\n );\n const unregistered = joined.unregisteredChannelCreations\n .map(({ target, channel, creations }) =>\n makeQueryResult(target, channel, 'channel', creations, []),\n );\n const registrations = joined.registrationsWithoutCreations\n .map(({ target, channel, registrations }) =>\n makeQueryResult(target, channel, 'channel', [], registrations),\n );\n return [...matched, ...unregistered, ...registrations];\n}\n\n/** 조인 키와 양쪽 증거를 query result 골격으로 바꾼다. */\nfunction makeQueryResult(\n target: BridgeTarget,\n name: string,\n kind: 'channel' | 'method',\n usedBy: readonly BridgeEndpoint[],\n dependsOn: readonly BridgeEndpoint[],\n): BridgeQueryResult {\n return {\n subject: {\n name,\n qualifiedName: `${target}:${encodeSubjectComponent(name)}`,\n kind,\n },\n usedBy,\n dependsOn,\n };\n}\n\n/**\n * qualifiedName 구분자(:와 #)와 이스케이프 문자(%)를 가역적인 퍼센트 표기로\n * 바꾼다. 세 문자를 모두 이스케이프하므로 첫 `:`와 `#` 기준으로 나눠 되돌릴 수\n * 있다 — 채널·메서드 이름에 `:`가 들어도 소비자의 분해가 모호해지지 않는다.\n */\nfunction encodeSubjectComponent(value: string): string {\n return value\n .replaceAll('%', '%25')\n .replaceAll('#', '%23')\n .replaceAll(':', '%3A');\n}\n"]}
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/report/query.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BridgeTarget } from '../exchange/parse.ts';\nimport { compareStrings } from '../compare.ts';\nimport type {\n BridgeEndpoint,\n BridgeJoinResult,\n JoinLimitation,\n MatchedBoundaryName,\n UnexportedBoundaryName,\n UnrequiredBoundaryName,\n} from '../join/join.ts';\nimport { compareLimitations, isBridgeJoinDeferred } from '../join/join.ts';\nimport type { MessageBridgeJoin } from '../join/messages.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\n\n/** query가 식별한 채널·메서드·모듈·컴포넌트·메시지·스트림 키다. */\nexport interface BridgeQuerySubject {\n readonly name: string;\n readonly qualifiedName: string;\n readonly kind: 'channel' | 'method' | 'module' | 'component' | 'message' | 'stream';\n}\n\n/** 한 브리지 키에서 본 호출 측과 수신 측 증거다. */\nexport interface BridgeQueryResult {\n readonly subject: BridgeQuerySubject;\n readonly usedBy: readonly BridgeEndpoint[];\n readonly dependsOn: readonly BridgeEndpoint[];\n}\n\n/** cartograph query와 같은 상태 외피를 쓰는 브리지 질의 문서다. */\nexport interface BridgeQueryDocument {\n readonly status: 'found' | 'ambiguous' | 'notFound';\n readonly requested: string;\n readonly level: 'bridge';\n readonly limitations: readonly JoinLimitation[];\n readonly result?: BridgeQueryResult;\n readonly candidates?: ReadonlyArray<{\n readonly qualifiedName: string;\n }>;\n}\n\n/** query 문서를 결정적인 JSON 문자열로 인코딩한다. */\nexport function encodeBridgeQuery(document: BridgeQueryDocument): string {\n return encodeSortedJson(document);\n}\n\n/**\n * 조인된 브리지에서 채널 또는 메서드 문자열을 질의한다.\n *\n * `messages`를 주면 BasicMessageChannel·EventChannel 경계도 `message`·`stream`\n * 주체로 질의할 수 있다. v1 채널과 이름이 같아도 qualifiedName의 kind 세그먼트로\n * 구분된다.\n */\nexport function createBridgeQuery(\n joined: BridgeJoinResult,\n requested: string,\n messages?: MessageBridgeJoin,\n): BridgeQueryDocument {\n if (isBridgeJoinDeferred(joined)) {\n throw new Error('Cannot query a deferred bridge join.');\n }\n const limitations = messages === undefined\n ? joined.limitations\n : [...joined.limitations, ...messages.limitations].sort(compareLimitations);\n const results = [\n ...channelResults(joined),\n ...methodResults(joined),\n ...nameResults(joined.matchedModules, joined.moduleImportsWithoutExports,\n joined.moduleExportsWithoutImports, 'module'),\n ...nameResults(joined.matchedComponents, joined.componentRequiresWithoutExports,\n joined.componentExportsWithoutRequires, 'component'),\n ...(messages === undefined ? [] : messageResults(messages)),\n ];\n const exact = results.filter(\n ({ subject }) => subject.qualifiedName === requested,\n );\n if (exact.length > 1) return ambiguousQuery(limitations, requested, exact);\n if (exact[0] !== undefined) return foundQuery(limitations, requested, exact[0]);\n\n const named = results.filter(({ subject }) => subject.name === requested);\n if (named.length > 1) return ambiguousQuery(limitations, requested, named);\n if (named[0] !== undefined) return foundQuery(limitations, requested, named[0]);\n return {\n status: 'notFound',\n requested,\n level: 'bridge',\n limitations,\n };\n}\n\n/** 유일하게 식별된 논리 키와 증거를 found 문서로 감싼다. */\nfunction foundQuery(\n limitations: readonly JoinLimitation[],\n requested: string,\n result: BridgeQueryResult,\n): BridgeQueryDocument {\n return {\n status: 'found',\n requested,\n level: 'bridge',\n limitations,\n result,\n };\n}\n\n/** 여러 논리 키가 같은 요청과 맞을 때 선택 가능한 정규화 이름을 돌려준다. */\nfunction ambiguousQuery(\n limitations: readonly JoinLimitation[],\n requested: string,\n results: readonly BridgeQueryResult[],\n): BridgeQueryDocument {\n return {\n status: 'ambiguous',\n requested,\n level: 'bridge',\n limitations,\n candidates: [\n ...new Map(\n results.map(({ subject }) => [\n subject.qualifiedName,\n { qualifiedName: subject.qualifiedName },\n ]),\n ).values(),\n ].sort((left, right) => compareStrings(left.qualifiedName, right.qualifiedName)),\n };\n}\n\n/** v2 메시지·스트림 경계를 query result로 만든다. 양쪽 증거를 그대로 보존한다. */\nfunction messageResults(messages: MessageBridgeJoin): BridgeQueryResult[] {\n return messages.routes.map((route) => {\n const kind = route.transport === 'event-channel' ? 'stream' : 'message';\n return {\n subject: {\n name: route.channel,\n qualifiedName: `flutter:${kind}:${encodeSubjectComponent(route.channel)}`,\n kind,\n },\n usedBy: route.senders,\n dependsOn: route.handlers,\n } satisfies BridgeQueryResult;\n });\n}\n\n/** 요청 문자열과 정확히 같은 논리 메서드 결과를 만든다. */\nfunction methodResults(\n joined: BridgeJoinResult,\n): BridgeQueryResult[] {\n const matched = joined.matchedMethods\n .map(({ target, channel, method, invocations, handlers }) =>\n makeMethodResult(target, channel, method, invocations, handlers),\n );\n const unhandled = joined.unhandledInvocations\n .map(({ target, channel, method, invocations }) =>\n makeMethodResult(target, channel, method, invocations, []),\n );\n const handlers = joined.handlersWithoutInvocations\n .map(({ target, channel, method, handlers }) =>\n makeMethodResult(target, channel, method, [], handlers),\n );\n return [...matched, ...unhandled, ...handlers];\n}\n\n/** 메서드 키와 양쪽 증거를 query result로 바꾼다. */\nfunction makeMethodResult(\n target: BridgeTarget,\n channel: string,\n method: string,\n usedBy: readonly BridgeEndpoint[],\n dependsOn: readonly BridgeEndpoint[],\n): BridgeQueryResult {\n return {\n subject: {\n name: method,\n qualifiedName:\n `${target}:${encodeSubjectComponent(channel)}#${encodeSubjectComponent(method)}`,\n kind: 'method',\n },\n usedBy,\n dependsOn,\n };\n}\n\n/** 요청 문자열과 정확히 같은 논리 채널 결과를 만든다. */\nfunction channelResults(\n joined: BridgeJoinResult,\n): BridgeQueryResult[] {\n const matched = joined.matchedChannels\n .map(({ target, channel, creations, registrations }) =>\n makeQueryResult(target, channel, 'channel', creations, registrations),\n );\n const unregistered = joined.unregisteredChannelCreations\n .map(({ target, channel, creations }) =>\n makeQueryResult(target, channel, 'channel', creations, []),\n );\n const registrations = joined.registrationsWithoutCreations\n .map(({ target, channel, registrations }) =>\n makeQueryResult(target, channel, 'channel', [], registrations),\n );\n return [...matched, ...unregistered, ...registrations];\n}\n\n/**\n * 요청 문자열과 정확히 같은 논리 모듈·컴포넌트 이름 결과를 만든다.\n *\n * mechanism이 섞인 이름은 매치·미수출·미호출 컬렉션에 동시에 나타날 수\n * 있으므로 (target, 이름)별로 합친다 — 같은 qualifiedName의 결과가 여럿이면\n * 재질의로도 풀리지 않는 영구 모호 상태가 된다.\n */\nfunction nameResults(\n matched: readonly MatchedBoundaryName[],\n unexported: readonly UnexportedBoundaryName[],\n unrequired: readonly UnrequiredBoundaryName[],\n kind: 'module' | 'component',\n): BridgeQueryResult[] {\n const merged = new Map<string, {\n target: BridgeTarget;\n name: string;\n usedBy: BridgeEndpoint[];\n dependsOn: BridgeEndpoint[];\n }>();\n const merge = (\n target: BridgeTarget,\n name: string,\n usedBy: readonly BridgeEndpoint[],\n dependsOn: readonly BridgeEndpoint[],\n ): void => {\n // 합침 키의 NUL 구분자는 파서가 이름의 제어문자를 거부한다는 데 의존한다 —\n // parse를 우회해 조립하는 경로가 생기면 이 구분자는 안전하지 않다.\n const key = `${target}\\u0000${name}`;\n const entry = merged.get(key) ??\n { target, name, usedBy: [], dependsOn: [] };\n entry.usedBy.push(...usedBy);\n entry.dependsOn.push(...dependsOn);\n merged.set(key, entry);\n };\n for (const { target, channel, callers, receivers } of matched) {\n merge(target, channel, callers, receivers);\n }\n for (const { target, channel, callers } of unexported) {\n merge(target, channel, callers, []);\n }\n for (const { target, channel, receivers } of unrequired) {\n merge(target, channel, [], receivers);\n }\n return [...merged.values()].map(({ target, name, usedBy, dependsOn }) =>\n makeQueryResult(target, name, kind, usedBy, dependsOn));\n}\n\n/** 조인 키와 양쪽 증거를 query result 골격으로 바꾼다. */\nfunction makeQueryResult(\n target: BridgeTarget,\n name: string,\n kind: 'channel' | 'method' | 'module' | 'component',\n usedBy: readonly BridgeEndpoint[],\n dependsOn: readonly BridgeEndpoint[],\n): BridgeQueryResult {\n return {\n subject: {\n name,\n // 모듈·컴포넌트는 kind 세그먼트를 넣어야 같은 이름의 채널·서로 다른 종류와\n // qualifiedName이 충돌하지 않아 모호성을 재질의로 풀 수 있다.\n qualifiedName: kind === 'channel'\n ? `${target}:${encodeSubjectComponent(name)}`\n : `${target}:${kind}:${encodeSubjectComponent(name)}`,\n kind,\n },\n usedBy,\n dependsOn,\n };\n}\n\n/**\n * qualifiedName 구분자(:와 #)와 이스케이프 문자(%)를 가역적인 퍼센트 표기로\n * 바꾼다. 세 문자를 모두 이스케이프하므로 첫 `:`와 `#` 기준으로 나눠 되돌릴 수\n * 있다 — 채널·메서드 이름에 `:`가 들어도 소비자의 분해가 모호해지지 않는다.\n */\nfunction encodeSubjectComponent(value: string): string {\n return value\n .replaceAll('%', '%25')\n .replaceAll('#', '%23')\n .replaceAll(':', '%3A');\n}\n"]}
@@ -1,5 +1,7 @@
1
1
  import type { BridgeFactsDocument, BridgePlatform, BridgeSymbol } from '../exchange/parse.ts';
2
+ import type { BridgeMessageDocument } from '../exchange/messages.ts';
2
3
  import { type BridgeJoinResult } from '../join/join.ts';
4
+ import type { MessageBridgeJoin } from '../join/messages.ts';
3
5
  /** cartograph가 보존할 Swift 선언 식별자다. */
4
6
  export interface RetentionSymbol extends BridgeSymbol {
5
7
  readonly usr?: string;
@@ -10,10 +12,15 @@ export interface RetentionCaller {
10
12
  readonly path: string;
11
13
  readonly line: number;
12
14
  }
13
- /** 보존 판단을 설명할 채널·메서드·호출자 근거다. */
15
+ /**
16
+ * 보존 판단을 설명할 채널·메서드·호출자 근거다.
17
+ *
18
+ * `method`는 MethodChannel 근거에만 있다. Basic·Event v2 경계는 메서드가 없으므로
19
+ * 생략하며, cartograph의 `ExternalRetention.Evidence.method`도 선택 필드다.
20
+ */
14
21
  export interface RetentionEvidence {
15
22
  readonly channel: string;
16
- readonly method: string;
23
+ readonly method?: string;
17
24
  /** 대표 호출 위치다. 결정적 순서의 첫 호출이며 옛 소비자가 읽는 필드다. */
18
25
  readonly caller: RetentionCaller;
19
26
  /**
@@ -62,8 +69,13 @@ export declare class RetentionValidationError extends Error {
62
69
  * 성공하므로, 검증하지 않으면 보존할 근거가 없다는 사실이 빈 목록과 코드 0으로
63
70
  * 사라진다. 사실이 없는 Swift 문서도 그 플랫폼을 분석했다는 근거로 인정한다.
64
71
  */
65
- export declare function validateCartographRetentionInputs(documents: readonly BridgeFactsDocument[]): void;
72
+ export declare function validateCartographRetentionInputs(documents: readonly BridgeFactsDocument[], messageDocuments?: readonly BridgeMessageDocument[]): void;
66
73
  /** cartograph 보존 문서를 결정적인 JSON으로 인코딩한다. */
67
74
  export declare function encodeCartographRetentionsDocument(document: CartographRetentionsDocument): string;
68
- /** 매치된 브리지 메서드를 cartograph 보존 근거로 바꾼다. */
69
- export declare function createCartographRetentionsDocument(joined: BridgeJoinResult, generatedAt: string, producerVersion: string): CartographRetentionsDocument;
75
+ /**
76
+ * 매치된 브리지 메서드와 v2 Basic·Event 경계를 cartograph 보존 근거로 바꾼다.
77
+ *
78
+ * `messages`를 주면 literal로 확정된 v2 경계의 Swift 핸들러도 보존 근거로 싣는다.
79
+ * v2 근거에는 메서드가 없으므로 `method`를 생략하며, cartograph는 선택 필드로 읽는다.
80
+ */
81
+ export declare function createCartographRetentionsDocument(joined: BridgeJoinResult, generatedAt: string, producerVersion: string, messages?: MessageBridgeJoin): CartographRetentionsDocument;
@@ -23,9 +23,11 @@ export class RetentionValidationError extends Error {
23
23
  * 성공하므로, 검증하지 않으면 보존할 근거가 없다는 사실이 빈 목록과 코드 0으로
24
24
  * 사라진다. 사실이 없는 Swift 문서도 그 플랫폼을 분석했다는 근거로 인정한다.
25
25
  */
26
- export function validateCartographRetentionInputs(documents) {
26
+ export function validateCartographRetentionInputs(documents, messageDocuments) {
27
27
  if (documents.some(({ platform }) => platform === 'swift'))
28
28
  return;
29
+ if (messageDocuments?.some(({ platform }) => platform === 'swift'))
30
+ return;
29
31
  throw new RetentionValidationError('Retentions for cartograph require at least one swift bridge facts document; '
30
32
  + 'run a swift producer for the receiver side.');
31
33
  }
@@ -33,24 +35,32 @@ export function validateCartographRetentionInputs(documents) {
33
35
  export function encodeCartographRetentionsDocument(document) {
34
36
  return encodeSortedJson(document);
35
37
  }
36
- /** 매치된 브리지 메서드를 cartograph 보존 근거로 바꾼다. */
37
- export function createCartographRetentionsDocument(joined, generatedAt, producerVersion) {
38
+ /**
39
+ * 매치된 브리지 메서드와 v2 Basic·Event 경계를 cartograph 보존 근거로 바꾼다.
40
+ *
41
+ * `messages`를 주면 literal로 확정된 v2 경계의 Swift 핸들러도 보존 근거로 싣는다.
42
+ * v2 근거에는 메서드가 없으므로 `method`를 생략하며, cartograph는 선택 필드로 읽는다.
43
+ */
44
+ export function createCartographRetentionsDocument(joined, generatedAt, producerVersion, messages) {
38
45
  if (isBridgeJoinDeferred(joined)) {
39
46
  throw new RetentionValidationError('Cannot create retentions from a deferred bridge join.');
40
47
  }
41
- rejectUnresolvedSwiftHandlers(joined);
42
- const omittedObjectiveCHandlers = countObjectiveCHandlers(joined);
48
+ rejectUnresolvedSwiftHandlers(joined, messages);
49
+ const omittedObjectiveCHandlers = countObjectiveCHandlers(joined, messages);
43
50
  return {
44
51
  format: 'external-retentions',
45
52
  version: 0,
46
53
  producedBy: { name: 'isthmus', version: producerVersion },
47
54
  generatedAt,
48
- retentions: collectCartographRetentions(joined),
55
+ retentions: [
56
+ ...collectCartographRetentions(joined),
57
+ ...(messages === undefined ? [] : collectMessageRetentions(messages)),
58
+ ],
49
59
  ...(omittedObjectiveCHandlers === 0 ? {} : { omittedObjectiveCHandlers }),
50
60
  };
51
61
  }
52
62
  /** 그래프 밖의 매치도 사라지지 않게 채널·메서드·위치별로 센다. */
53
- function countObjectiveCHandlers(joined) {
63
+ function countObjectiveCHandlers(joined, messages) {
54
64
  const keys = new Set();
55
65
  for (const method of joined.matchedMethods) {
56
66
  for (const handler of method.handlers) {
@@ -60,6 +70,16 @@ function countObjectiveCHandlers(joined) {
60
70
  handler.location.path, handler.location.line, handler.location.column]));
61
71
  }
62
72
  }
73
+ if (messages !== undefined) {
74
+ for (const route of retainedMessageRoutes(messages)) {
75
+ for (const handler of route.handlers) {
76
+ if (handler.sourceLanguage !== 'objective-c')
77
+ continue;
78
+ keys.add(JSON.stringify(['v2', route.transport, route.channel,
79
+ handler.location.path, handler.location.line, handler.location.column]));
80
+ }
81
+ }
82
+ }
63
83
  return keys.size;
64
84
  }
65
85
  /**
@@ -70,7 +90,7 @@ function countObjectiveCHandlers(joined) {
70
90
  * 핸들러를 조용히 빼면 cartograph는 그 핸들러를 계속 미사용으로 보고하고,
71
91
  * 소비자는 살아 있는 코드를 지운다. 부분 보존 문서 대신 실패를 돌려준다.
72
92
  */
73
- function rejectUnresolvedSwiftHandlers(joined) {
93
+ function rejectUnresolvedSwiftHandlers(joined, messages) {
74
94
  const unresolved = new Set();
75
95
  for (const method of joined.matchedMethods) {
76
96
  for (const handler of method.handlers) {
@@ -80,6 +100,16 @@ function rejectUnresolvedSwiftHandlers(joined) {
80
100
  unresolved.add(`${path}\u0000${line}\u0000${column}`);
81
101
  }
82
102
  }
103
+ if (messages !== undefined) {
104
+ for (const route of retainedMessageRoutes(messages)) {
105
+ for (const handler of route.handlers) {
106
+ if (handler.platform !== 'swift' || handler.sourceLanguage === 'objective-c' || handler.symbol !== undefined)
107
+ continue;
108
+ const { path, line, column } = handler.location;
109
+ unresolved.add(`${path}\u0000${line}\u0000${column}`);
110
+ }
111
+ }
112
+ }
83
113
  if (unresolved.size === 0)
84
114
  return;
85
115
  throw new RetentionValidationError(`Cannot produce retention evidence for ${unresolved.size} matched swift `
@@ -133,6 +163,53 @@ function collectCartographRetentions(joined) {
133
163
  }
134
164
  return retentions;
135
165
  }
166
+ /** literal로 확정되고 호출자가 있는 v2 경계만 보존 후보로 돌려준다. */
167
+ function retainedMessageRoutes(messages) {
168
+ return messages.routes.filter((route) => route.matching === 'literal' && route.senders.length > 0);
169
+ }
170
+ /** 매치된 v2 경계의 송신자를 Swift 심볼 보존 근거로 결합한다. */
171
+ function collectMessageRetentions(messages) {
172
+ const retentions = [];
173
+ const seen = new Set();
174
+ let callerEntries = 0;
175
+ for (const route of retainedMessageRoutes(messages)) {
176
+ const callers = route.senders
177
+ .slice(0, MAX_RETENTION_CALLERS)
178
+ .map(toRetentionCaller);
179
+ const callersOmitted = route.senders.length - callers.length;
180
+ const representative = callers[0];
181
+ if (representative === undefined)
182
+ continue;
183
+ for (const handler of route.handlers) {
184
+ if (handler.platform !== 'swift' || handler.sourceLanguage === 'objective-c' || handler.symbol === undefined)
185
+ continue;
186
+ const symbolKey = handler.symbol.usr === undefined
187
+ ? `name:${handler.symbol.qualifiedName}`
188
+ : `usr:${handler.symbol.usr}`;
189
+ // transport까지 키에 넣어 같은 이름의 MethodChannel 보존과 섞이지 않게 한다.
190
+ const retentionKey = `${symbolKey}\u0000${route.transport}\u0000${route.channel}`;
191
+ if (seen.has(retentionKey))
192
+ continue;
193
+ seen.add(retentionKey);
194
+ callerEntries += callers.length;
195
+ if (callerEntries > MAX_RETENTION_CALLER_ENTRIES) {
196
+ throw new RetentionValidationError(`Cannot produce retention evidence with more than `
197
+ + `${MAX_RETENTION_CALLER_ENTRIES} caller entries; narrow the join inputs.`);
198
+ }
199
+ retentions.push({
200
+ symbol: handler.symbol,
201
+ reason: 'bridge',
202
+ evidence: {
203
+ channel: route.channel,
204
+ caller: representative,
205
+ ...(callers.length > 1 ? { callers } : {}),
206
+ ...(callersOmitted > 0 ? { callersOmitted } : {}),
207
+ },
208
+ });
209
+ }
210
+ }
211
+ return retentions;
212
+ }
136
213
  /** 증거 끝점을 external-retentions의 호출자 형태로 바꾼다. */
137
214
  function toRetentionCaller(endpoint) {
138
215
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"retentions.js","sourceRoot":"","sources":["../../src/report/retentions.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n BridgeFactsDocument,\n BridgePlatform,\n BridgeSymbol,\n} from '../exchange/parse.ts';\nimport {\n isBridgeJoinDeferred,\n type BridgeJoinResult,\n} from '../join/join.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\n\n/** cartograph가 보존할 Swift 선언 식별자다. */\nexport interface RetentionSymbol extends BridgeSymbol {\n readonly usr?: string;\n}\n\n/** 언어 경계 너머 호출자의 증거 위치다. */\nexport interface RetentionCaller {\n readonly platform: BridgePlatform;\n readonly path: string;\n readonly line: number;\n}\n\n/** 보존 판단을 설명할 채널·메서드·호출자 근거다. */\nexport interface RetentionEvidence {\n readonly channel: string;\n readonly method: string;\n /** 대표 호출 위치다. 결정적 순서의 첫 호출이며 옛 소비자가 읽는 필드다. */\n readonly caller: RetentionCaller;\n /**\n * 이 보존의 전체 호출 위치(대표 포함)다. 결정적 순서를 유지하고 호출이 둘 이상일\n * 때만 실는다. 옛 소비자는 이 필드를 모르고 대표 `caller`만 읽는다.\n */\n readonly callers?: readonly RetentionCaller[];\n /** 상한 때문에 `callers`에 실지 못한 호출 수다. 0이면 실지 않는다. */\n readonly callersOmitted?: number;\n}\n\n/** cartograph 외부 보존 근거 하나다. */\nexport interface ExternalRetention {\n readonly symbol: RetentionSymbol;\n readonly reason: 'bridge';\n readonly evidence: RetentionEvidence;\n}\n\n/** cartograph가 읽는 external-retentions 버전 0 문서다. */\nexport interface CartographRetentionsDocument {\n readonly format: 'external-retentions';\n readonly version: 0;\n readonly producedBy: Readonly<{ name: 'isthmus'; version: string }>;\n readonly generatedAt: string;\n readonly retentions: readonly ExternalRetention[];\n readonly omittedObjectiveCHandlers?: number;\n}\n\n/** 근거 하나가 실을 수 있는 호출 위치 상한이다. */\nexport const MAX_RETENTION_CALLERS = 100;\n\n/**\n * 문서 전체가 실을 수 있는 호출 위치 총상한이다.\n *\n * 호출자 증거는 (심볼 × 채널 × 메서드)마다 다시 실리므로 건당 상한만으로는 출력이\n * 입력보다 커질 수 있다. 베이스라인의 항목 상한과 같은 자원 거버넌스다.\n */\nexport const MAX_RETENTION_CALLER_ENTRIES = 1_000_000;\n\n/** 불완전한 조인으로 보존 결정을 만들 수 없음을 나타낸다. */\nexport class RetentionValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'RetentionValidationError';\n }\n}\n\n/**\n * cartograph가 소비할 수 있는 수신 측 문서가 입력에 있는지 검증한다.\n *\n * cartograph는 Swift 심볼만 보존한다. Swift 문서가 없는 입력은 조인 자체는\n * 성공하므로, 검증하지 않으면 보존할 근거가 없다는 사실이 빈 목록과 코드 0으로\n * 사라진다. 사실이 없는 Swift 문서도 그 플랫폼을 분석했다는 근거로 인정한다.\n */\nexport function validateCartographRetentionInputs(\n documents: readonly BridgeFactsDocument[],\n): void {\n if (documents.some(({ platform }) => platform === 'swift')) return;\n throw new RetentionValidationError(\n 'Retentions for cartograph require at least one swift bridge facts document; '\n + 'run a swift producer for the receiver side.',\n );\n}\n\n/** cartograph 보존 문서를 결정적인 JSON으로 인코딩한다. */\nexport function encodeCartographRetentionsDocument(\n document: CartographRetentionsDocument,\n): string {\n return encodeSortedJson(document);\n}\n\n/** 매치된 브리지 메서드를 cartograph 보존 근거로 바꾼다. */\nexport function createCartographRetentionsDocument(\n joined: BridgeJoinResult,\n generatedAt: string,\n producerVersion: string,\n): CartographRetentionsDocument {\n if (isBridgeJoinDeferred(joined)) {\n throw new RetentionValidationError(\n 'Cannot create retentions from a deferred bridge join.',\n );\n }\n rejectUnresolvedSwiftHandlers(joined);\n const omittedObjectiveCHandlers = countObjectiveCHandlers(joined);\n return {\n format: 'external-retentions',\n version: 0,\n producedBy: { name: 'isthmus', version: producerVersion },\n generatedAt,\n retentions: collectCartographRetentions(joined),\n ...(omittedObjectiveCHandlers === 0 ? {} : { omittedObjectiveCHandlers }),\n };\n}\n\n/** 그래프 밖의 매치도 사라지지 않게 채널·메서드·위치별로 센다. */\nfunction countObjectiveCHandlers(joined: BridgeJoinResult): number {\n const keys = new Set<string>();\n for (const method of joined.matchedMethods) {\n for (const handler of method.handlers) {\n if (handler.sourceLanguage !== 'objective-c') continue;\n keys.add(JSON.stringify([method.target, method.channel, method.method,\n handler.location.path, handler.location.line, handler.location.column]));\n }\n }\n return keys.size;\n}\n\n/**\n * 심볼이 없어 보존 근거로 바꿀 수 없는 매치 Swift 핸들러를 거부한다.\n * sourceLanguage로 확인된 Objective-C 구현은 Swift 그래프의 보존 대상이 아니다.\n *\n * 교환 계약에서 `symbol`은 선택 필드다. 호출자가 있는데도 근거를 만들지 못한\n * 핸들러를 조용히 빼면 cartograph는 그 핸들러를 계속 미사용으로 보고하고,\n * 소비자는 살아 있는 코드를 지운다. 부분 보존 문서 대신 실패를 돌려준다.\n */\nfunction rejectUnresolvedSwiftHandlers(joined: BridgeJoinResult): void {\n const unresolved = new Set<string>();\n for (const method of joined.matchedMethods) {\n for (const handler of method.handlers) {\n if (handler.platform !== 'swift' || handler.sourceLanguage === 'objective-c' || handler.symbol !== undefined) continue;\n const { path, line, column } = handler.location;\n unresolved.add(`${path}\\u0000${line}\\u0000${column}`);\n }\n }\n if (unresolved.size === 0) return;\n throw new RetentionValidationError(\n `Cannot produce retention evidence for ${unresolved.size} matched swift `\n + 'handlers without a symbol; regenerate the swift document with a producer '\n + 'that attaches handler symbols.',\n );\n}\n\n/** 매치별 Dart 호출자와 Swift 심볼을 cartograph 근거로 결합한다. */\nfunction collectCartographRetentions(\n joined: BridgeJoinResult,\n): ExternalRetention[] {\n const retentions: ExternalRetention[] = [];\n const seen = new Set<string>();\n let callerEntries = 0;\n for (const method of joined.matchedMethods) {\n if (method.invocations.length === 0) continue;\n // 결정적 순서(플랫폼·경로·줄·열)의 첫 호출이 대표 증거다. 상한 밖 호출까지\n // 객체로 만들지 않도록 먼저 자른다.\n const callers = method.invocations\n .slice(0, MAX_RETENTION_CALLERS)\n .map(toRetentionCaller);\n const callersOmitted = method.invocations.length - callers.length;\n const representative = callers[0];\n if (representative === undefined) continue;\n for (const handler of method.handlers) {\n if (handler.platform !== 'swift' || handler.sourceLanguage === 'objective-c' || handler.symbol === undefined) continue;\n const symbolKey = handler.symbol.usr === undefined\n ? `name:${handler.symbol.qualifiedName}`\n : `usr:${handler.symbol.usr}`;\n const retentionKey = `${symbolKey}\\u0000${method.channel}\\u0000${method.method}`;\n if (seen.has(retentionKey)) continue;\n seen.add(retentionKey);\n callerEntries += callers.length;\n if (callerEntries > MAX_RETENTION_CALLER_ENTRIES) {\n throw new RetentionValidationError(\n `Cannot produce retention evidence with more than `\n + `${MAX_RETENTION_CALLER_ENTRIES} caller entries; narrow the join inputs.`,\n );\n }\n retentions.push({\n symbol: handler.symbol,\n reason: 'bridge',\n evidence: {\n channel: method.channel,\n method: method.method,\n caller: representative,\n ...(callers.length > 1 ? { callers } : {}),\n ...(callersOmitted > 0 ? { callersOmitted } : {}),\n },\n });\n }\n }\n return retentions;\n}\n\n/** 증거 끝점을 external-retentions의 호출자 형태로 바꾼다. */\nfunction toRetentionCaller(endpoint: {\n readonly platform: BridgePlatform;\n readonly location: { readonly path: string; readonly line: number };\n}): RetentionCaller {\n return {\n platform: endpoint.platform,\n path: endpoint.location.path,\n line: endpoint.location.line,\n };\n}\n"]}
1
+ {"version":3,"file":"retentions.js","sourceRoot":"","sources":["../../src/report/retentions.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n BridgeFactsDocument,\n BridgePlatform,\n BridgeSymbol,\n} from '../exchange/parse.ts';\nimport type { BridgeMessageDocument } from '../exchange/messages.ts';\nimport {\n isBridgeJoinDeferred,\n type BridgeJoinResult,\n} from '../join/join.ts';\nimport type { MessageBridgeJoin, MessageEndpoint } from '../join/messages.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\n\n/** cartograph가 보존할 Swift 선언 식별자다. */\nexport interface RetentionSymbol extends BridgeSymbol {\n readonly usr?: string;\n}\n\n/** 언어 경계 너머 호출자의 증거 위치다. */\nexport interface RetentionCaller {\n readonly platform: BridgePlatform;\n readonly path: string;\n readonly line: number;\n}\n\n/**\n * 보존 판단을 설명할 채널·메서드·호출자 근거다.\n *\n * `method`는 MethodChannel 근거에만 있다. Basic·Event v2 경계는 메서드가 없으므로\n * 생략하며, cartograph의 `ExternalRetention.Evidence.method`도 선택 필드다.\n */\nexport interface RetentionEvidence {\n readonly channel: string;\n readonly method?: string;\n /** 대표 호출 위치다. 결정적 순서의 첫 호출이며 옛 소비자가 읽는 필드다. */\n readonly caller: RetentionCaller;\n /**\n * 이 보존의 전체 호출 위치(대표 포함)다. 결정적 순서를 유지하고 호출이 둘 이상일\n * 때만 실는다. 옛 소비자는 이 필드를 모르고 대표 `caller`만 읽는다.\n */\n readonly callers?: readonly RetentionCaller[];\n /** 상한 때문에 `callers`에 실지 못한 호출 수다. 0이면 실지 않는다. */\n readonly callersOmitted?: number;\n}\n\n/** cartograph 외부 보존 근거 하나다. */\nexport interface ExternalRetention {\n readonly symbol: RetentionSymbol;\n readonly reason: 'bridge';\n readonly evidence: RetentionEvidence;\n}\n\n/** cartograph가 읽는 external-retentions 버전 0 문서다. */\nexport interface CartographRetentionsDocument {\n readonly format: 'external-retentions';\n readonly version: 0;\n readonly producedBy: Readonly<{ name: 'isthmus'; version: string }>;\n readonly generatedAt: string;\n readonly retentions: readonly ExternalRetention[];\n readonly omittedObjectiveCHandlers?: number;\n}\n\n/** 근거 하나가 실을 수 있는 호출 위치 상한이다. */\nexport const MAX_RETENTION_CALLERS = 100;\n\n/**\n * 문서 전체가 실을 수 있는 호출 위치 총상한이다.\n *\n * 호출자 증거는 (심볼 × 채널 × 메서드)마다 다시 실리므로 건당 상한만으로는 출력이\n * 입력보다 커질 수 있다. 베이스라인의 항목 상한과 같은 자원 거버넌스다.\n */\nexport const MAX_RETENTION_CALLER_ENTRIES = 1_000_000;\n\n/** 불완전한 조인으로 보존 결정을 만들 수 없음을 나타낸다. */\nexport class RetentionValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'RetentionValidationError';\n }\n}\n\n/**\n * cartograph가 소비할 수 있는 수신 측 문서가 입력에 있는지 검증한다.\n *\n * cartograph는 Swift 심볼만 보존한다. Swift 문서가 없는 입력은 조인 자체는\n * 성공하므로, 검증하지 않으면 보존할 근거가 없다는 사실이 빈 목록과 코드 0으로\n * 사라진다. 사실이 없는 Swift 문서도 그 플랫폼을 분석했다는 근거로 인정한다.\n */\nexport function validateCartographRetentionInputs(\n documents: readonly BridgeFactsDocument[],\n messageDocuments?: readonly BridgeMessageDocument[],\n): void {\n if (documents.some(({ platform }) => platform === 'swift')) return;\n if (messageDocuments?.some(({ platform }) => platform === 'swift')) return;\n throw new RetentionValidationError(\n 'Retentions for cartograph require at least one swift bridge facts document; '\n + 'run a swift producer for the receiver side.',\n );\n}\n\n/** cartograph 보존 문서를 결정적인 JSON으로 인코딩한다. */\nexport function encodeCartographRetentionsDocument(\n document: CartographRetentionsDocument,\n): string {\n return encodeSortedJson(document);\n}\n\n/**\n * 매치된 브리지 메서드와 v2 Basic·Event 경계를 cartograph 보존 근거로 바꾼다.\n *\n * `messages`를 주면 literal로 확정된 v2 경계의 Swift 핸들러도 보존 근거로 싣는다.\n * v2 근거에는 메서드가 없으므로 `method`를 생략하며, cartograph는 선택 필드로 읽는다.\n */\nexport function createCartographRetentionsDocument(\n joined: BridgeJoinResult,\n generatedAt: string,\n producerVersion: string,\n messages?: MessageBridgeJoin,\n): CartographRetentionsDocument {\n if (isBridgeJoinDeferred(joined)) {\n throw new RetentionValidationError(\n 'Cannot create retentions from a deferred bridge join.',\n );\n }\n rejectUnresolvedSwiftHandlers(joined, messages);\n const omittedObjectiveCHandlers = countObjectiveCHandlers(joined, messages);\n return {\n format: 'external-retentions',\n version: 0,\n producedBy: { name: 'isthmus', version: producerVersion },\n generatedAt,\n retentions: [\n ...collectCartographRetentions(joined),\n ...(messages === undefined ? [] : collectMessageRetentions(messages)),\n ],\n ...(omittedObjectiveCHandlers === 0 ? {} : { omittedObjectiveCHandlers }),\n };\n}\n\n/** 그래프 밖의 매치도 사라지지 않게 채널·메서드·위치별로 센다. */\nfunction countObjectiveCHandlers(\n joined: BridgeJoinResult,\n messages?: MessageBridgeJoin,\n): number {\n const keys = new Set<string>();\n for (const method of joined.matchedMethods) {\n for (const handler of method.handlers) {\n if (handler.sourceLanguage !== 'objective-c') continue;\n keys.add(JSON.stringify([method.target, method.channel, method.method,\n handler.location.path, handler.location.line, handler.location.column]));\n }\n }\n if (messages !== undefined) {\n for (const route of retainedMessageRoutes(messages)) {\n for (const handler of route.handlers) {\n if (handler.sourceLanguage !== 'objective-c') continue;\n keys.add(JSON.stringify(['v2', route.transport, route.channel,\n handler.location.path, handler.location.line, handler.location.column]));\n }\n }\n }\n return keys.size;\n}\n\n/**\n * 심볼이 없어 보존 근거로 바꿀 수 없는 매치 Swift 핸들러를 거부한다.\n * sourceLanguage로 확인된 Objective-C 구현은 Swift 그래프의 보존 대상이 아니다.\n *\n * 교환 계약에서 `symbol`은 선택 필드다. 호출자가 있는데도 근거를 만들지 못한\n * 핸들러를 조용히 빼면 cartograph는 그 핸들러를 계속 미사용으로 보고하고,\n * 소비자는 살아 있는 코드를 지운다. 부분 보존 문서 대신 실패를 돌려준다.\n */\nfunction rejectUnresolvedSwiftHandlers(\n joined: BridgeJoinResult,\n messages?: MessageBridgeJoin,\n): void {\n const unresolved = new Set<string>();\n for (const method of joined.matchedMethods) {\n for (const handler of method.handlers) {\n if (handler.platform !== 'swift' || handler.sourceLanguage === 'objective-c' || handler.symbol !== undefined) continue;\n const { path, line, column } = handler.location;\n unresolved.add(`${path}\\u0000${line}\\u0000${column}`);\n }\n }\n if (messages !== undefined) {\n for (const route of retainedMessageRoutes(messages)) {\n for (const handler of route.handlers) {\n if (handler.platform !== 'swift' || handler.sourceLanguage === 'objective-c' || handler.symbol !== undefined) continue;\n const { path, line, column } = handler.location;\n unresolved.add(`${path}\\u0000${line}\\u0000${column}`);\n }\n }\n }\n if (unresolved.size === 0) return;\n throw new RetentionValidationError(\n `Cannot produce retention evidence for ${unresolved.size} matched swift `\n + 'handlers without a symbol; regenerate the swift document with a producer '\n + 'that attaches handler symbols.',\n );\n}\n\n/** 매치별 Dart 호출자와 Swift 심볼을 cartograph 근거로 결합한다. */\nfunction collectCartographRetentions(\n joined: BridgeJoinResult,\n): ExternalRetention[] {\n const retentions: ExternalRetention[] = [];\n const seen = new Set<string>();\n let callerEntries = 0;\n for (const method of joined.matchedMethods) {\n if (method.invocations.length === 0) continue;\n // 결정적 순서(플랫폼·경로·줄·열)의 첫 호출이 대표 증거다. 상한 밖 호출까지\n // 객체로 만들지 않도록 먼저 자른다.\n const callers = method.invocations\n .slice(0, MAX_RETENTION_CALLERS)\n .map(toRetentionCaller);\n const callersOmitted = method.invocations.length - callers.length;\n const representative = callers[0];\n if (representative === undefined) continue;\n for (const handler of method.handlers) {\n if (handler.platform !== 'swift' || handler.sourceLanguage === 'objective-c' || handler.symbol === undefined) continue;\n const symbolKey = handler.symbol.usr === undefined\n ? `name:${handler.symbol.qualifiedName}`\n : `usr:${handler.symbol.usr}`;\n const retentionKey = `${symbolKey}\\u0000${method.channel}\\u0000${method.method}`;\n if (seen.has(retentionKey)) continue;\n seen.add(retentionKey);\n callerEntries += callers.length;\n if (callerEntries > MAX_RETENTION_CALLER_ENTRIES) {\n throw new RetentionValidationError(\n `Cannot produce retention evidence with more than `\n + `${MAX_RETENTION_CALLER_ENTRIES} caller entries; narrow the join inputs.`,\n );\n }\n retentions.push({\n symbol: handler.symbol,\n reason: 'bridge',\n evidence: {\n channel: method.channel,\n method: method.method,\n caller: representative,\n ...(callers.length > 1 ? { callers } : {}),\n ...(callersOmitted > 0 ? { callersOmitted } : {}),\n },\n });\n }\n }\n return retentions;\n}\n\n/** literal로 확정되고 호출자가 있는 v2 경계만 보존 후보로 돌려준다. */\nfunction retainedMessageRoutes(messages: MessageBridgeJoin) {\n return messages.routes.filter((route) =>\n route.matching === 'literal' && route.senders.length > 0);\n}\n\n/** 매치된 v2 경계의 송신자를 Swift 심볼 보존 근거로 결합한다. */\nfunction collectMessageRetentions(\n messages: MessageBridgeJoin,\n): ExternalRetention[] {\n const retentions: ExternalRetention[] = [];\n const seen = new Set<string>();\n let callerEntries = 0;\n for (const route of retainedMessageRoutes(messages)) {\n const callers = route.senders\n .slice(0, MAX_RETENTION_CALLERS)\n .map(toRetentionCaller);\n const callersOmitted = route.senders.length - callers.length;\n const representative = callers[0];\n if (representative === undefined) continue;\n for (const handler of route.handlers) {\n if (handler.platform !== 'swift' || handler.sourceLanguage === 'objective-c' || handler.symbol === undefined) continue;\n const symbolKey = handler.symbol.usr === undefined\n ? `name:${handler.symbol.qualifiedName}`\n : `usr:${handler.symbol.usr}`;\n // transport까지 키에 넣어 같은 이름의 MethodChannel 보존과 섞이지 않게 한다.\n const retentionKey = `${symbolKey}\\u0000${route.transport}\\u0000${route.channel}`;\n if (seen.has(retentionKey)) continue;\n seen.add(retentionKey);\n callerEntries += callers.length;\n if (callerEntries > MAX_RETENTION_CALLER_ENTRIES) {\n throw new RetentionValidationError(\n `Cannot produce retention evidence with more than `\n + `${MAX_RETENTION_CALLER_ENTRIES} caller entries; narrow the join inputs.`,\n );\n }\n retentions.push({\n symbol: handler.symbol,\n reason: 'bridge',\n evidence: {\n channel: route.channel,\n caller: representative,\n ...(callers.length > 1 ? { callers } : {}),\n ...(callersOmitted > 0 ? { callersOmitted } : {}),\n },\n });\n }\n }\n return retentions;\n}\n\n/** 증거 끝점을 external-retentions의 호출자 형태로 바꾼다. */\nfunction toRetentionCaller(endpoint: {\n readonly platform: BridgePlatform;\n readonly location: { readonly path: string; readonly line: number };\n}): RetentionCaller {\n return {\n platform: endpoint.platform,\n path: endpoint.location.path,\n line: endpoint.location.line,\n };\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import type { CheckIssueCode } from './check-report.ts';
2
+ /**
3
+ * 논리 이슈 키를 안정 지문으로 바꾸는 주입 경계다. 해싱은 cli 계층이 담당한다.
4
+ * SARIF와 Code Quality가 같은 지문을 써야 두 아티팩트의 병합 기준이 일치한다.
5
+ */
6
+ export type IssueFingerprint = (issue: {
7
+ readonly code: string;
8
+ readonly target: string;
9
+ readonly channel: string;
10
+ readonly method?: string;
11
+ }) => string;
12
+ /**
13
+ * check가 보고하는 진단 종류의 산문 설명이다. SARIF 규칙 문구와 Code Quality
14
+ * 발견 설명이 같은 표를 써야 두 형식이 같은 이슈를 다르게 말하지 않는다.
15
+ */
16
+ export declare const checkIssueRuleDescriptions: Record<CheckIssueCode, string>;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * check가 보고하는 진단 종류의 산문 설명이다. SARIF 규칙 문구와 Code Quality
3
+ * 발견 설명이 같은 표를 써야 두 형식이 같은 이슈를 다르게 말하지 않는다.
4
+ */
5
+ export const checkIssueRuleDescriptions = {
6
+ 'unhandled-invocation': 'A caller-side bridge method invocation has no matching handler on any receiver-side document.',
7
+ 'unhandled-invocation-unverified': 'A caller-side bridge method invocation has no matching handler, and a receiver-side analysis gap may be hiding it.',
8
+ 'unregistered-channel-creation': 'A caller-side bridge channel creation has no matching registration on any receiver-side document.',
9
+ 'unregistered-channel-creation-unverified': 'A caller-side bridge channel creation has no matching registration, and a receiver-side analysis gap may be hiding it.',
10
+ 'registration-without-creation': 'A receiver-side channel registration has no matching caller-side channel creation.',
11
+ 'handler-without-invocation': 'A receiver-side bridge method handler has no matching caller-side invocation.',
12
+ 'module-import-without-export': 'A caller-side native module import has no matching export on any receiver-side document.',
13
+ 'module-import-without-export-unverified': 'A caller-side native module import has no matching export, and a receiver-side analysis gap may be hiding it.',
14
+ 'module-import-without-export-optional': 'A caller-side native module import has no matching export, but every observed caller tolerates absence by receiving null.',
15
+ 'module-import-mechanism-mismatch': 'A caller-side native module import matches an export name, but the observed exports resolve through a different bridge mechanism.',
16
+ 'module-export-without-import': 'A receiver-side native module export has no matching caller-side import.',
17
+ 'module-export-mechanism-mismatch': 'A receiver-side native module export matches an import name, but the observed imports resolve through a different bridge mechanism.',
18
+ 'component-require-without-export': 'A caller-side native component require has no matching export on any receiver-side document.',
19
+ 'component-require-without-export-unverified': 'A caller-side native component require has no matching export, and a receiver-side analysis gap may be hiding it.',
20
+ 'component-require-mechanism-mismatch': 'A caller-side native component require matches an export name, but the observed exports resolve through a different bridge mechanism.',
21
+ 'component-export-without-require': 'A receiver-side native component export has no matching caller-side require.',
22
+ 'component-export-mechanism-mismatch': 'A receiver-side native component export matches a require name, but the observed requires resolve through a different bridge mechanism.',
23
+ 'unhandled-message-send': 'A Dart BasicMessageChannel send has no matching native message handler on any receiver-side document.',
24
+ 'unhandled-message-send-unverified': 'A Dart BasicMessageChannel send has no matching native message handler, and a receiver-side analysis gap may be hiding it.',
25
+ 'message-handler-without-send': 'A native BasicMessageChannel handler has no matching Dart send.',
26
+ 'unhandled-stream-listen': 'A Dart EventChannel stream listener has no matching native stream handler on any receiver-side document.',
27
+ 'unhandled-stream-listen-unverified': 'A Dart EventChannel stream listener has no matching native stream handler, and a receiver-side analysis gap may be hiding it.',
28
+ 'stream-handler-without-listen': 'A native EventChannel stream handler has no matching Dart stream listener.',
29
+ };
30
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../../src/report/rules.ts"],"names":[],"mappings":"AAaA;;;GAGG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAmC;IACxE,sBAAsB,EACpB,+FAA+F;IACjG,iCAAiC,EAC/B,oHAAoH;IACtH,+BAA+B,EAC7B,mGAAmG;IACrG,0CAA0C,EACxC,wHAAwH;IAC1H,+BAA+B,EAC7B,oFAAoF;IACtF,4BAA4B,EAC1B,+EAA+E;IACjF,8BAA8B,EAC5B,0FAA0F;IAC5F,yCAAyC,EACvC,+GAA+G;IACjH,uCAAuC,EACrC,2HAA2H;IAC7H,kCAAkC,EAChC,mIAAmI;IACrI,8BAA8B,EAC5B,0EAA0E;IAC5E,kCAAkC,EAChC,qIAAqI;IACvI,kCAAkC,EAChC,8FAA8F;IAChG,6CAA6C,EAC3C,mHAAmH;IACrH,sCAAsC,EACpC,uIAAuI;IACzI,kCAAkC,EAChC,8EAA8E;IAChF,qCAAqC,EACnC,yIAAyI;IAC3I,wBAAwB,EACtB,uGAAuG;IACzG,mCAAmC,EACjC,4HAA4H;IAC9H,8BAA8B,EAC5B,iEAAiE;IACnE,yBAAyB,EACvB,0GAA0G;IAC5G,oCAAoC,EAClC,+HAA+H;IACjI,+BAA+B,EAC7B,4EAA4E;CAC/E,CAAC","sourcesContent":["import type { CheckIssueCode } from './check-report.ts';\n\n/**\n * 논리 이슈 키를 안정 지문으로 바꾸는 주입 경계다. 해싱은 cli 계층이 담당한다.\n * SARIF와 Code Quality가 같은 지문을 써야 두 아티팩트의 병합 기준이 일치한다.\n */\nexport type IssueFingerprint = (issue: {\n readonly code: string;\n readonly target: string;\n readonly channel: string;\n readonly method?: string;\n}) => string;\n\n/**\n * check가 보고하는 진단 종류의 산문 설명이다. SARIF 규칙 문구와 Code Quality\n * 발견 설명이 같은 표를 써야 두 형식이 같은 이슈를 다르게 말하지 않는다.\n */\nexport const checkIssueRuleDescriptions: Record<CheckIssueCode, string> = {\n 'unhandled-invocation':\n 'A caller-side bridge method invocation has no matching handler on any receiver-side document.',\n 'unhandled-invocation-unverified':\n 'A caller-side bridge method invocation has no matching handler, and a receiver-side analysis gap may be hiding it.',\n 'unregistered-channel-creation':\n 'A caller-side bridge channel creation has no matching registration on any receiver-side document.',\n 'unregistered-channel-creation-unverified':\n 'A caller-side bridge channel creation has no matching registration, and a receiver-side analysis gap may be hiding it.',\n 'registration-without-creation':\n 'A receiver-side channel registration has no matching caller-side channel creation.',\n 'handler-without-invocation':\n 'A receiver-side bridge method handler has no matching caller-side invocation.',\n 'module-import-without-export':\n 'A caller-side native module import has no matching export on any receiver-side document.',\n 'module-import-without-export-unverified':\n 'A caller-side native module import has no matching export, and a receiver-side analysis gap may be hiding it.',\n 'module-import-without-export-optional':\n 'A caller-side native module import has no matching export, but every observed caller tolerates absence by receiving null.',\n 'module-import-mechanism-mismatch':\n 'A caller-side native module import matches an export name, but the observed exports resolve through a different bridge mechanism.',\n 'module-export-without-import':\n 'A receiver-side native module export has no matching caller-side import.',\n 'module-export-mechanism-mismatch':\n 'A receiver-side native module export matches an import name, but the observed imports resolve through a different bridge mechanism.',\n 'component-require-without-export':\n 'A caller-side native component require has no matching export on any receiver-side document.',\n 'component-require-without-export-unverified':\n 'A caller-side native component require has no matching export, and a receiver-side analysis gap may be hiding it.',\n 'component-require-mechanism-mismatch':\n 'A caller-side native component require matches an export name, but the observed exports resolve through a different bridge mechanism.',\n 'component-export-without-require':\n 'A receiver-side native component export has no matching caller-side require.',\n 'component-export-mechanism-mismatch':\n 'A receiver-side native component export matches a require name, but the observed requires resolve through a different bridge mechanism.',\n 'unhandled-message-send':\n 'A Dart BasicMessageChannel send has no matching native message handler on any receiver-side document.',\n 'unhandled-message-send-unverified':\n 'A Dart BasicMessageChannel send has no matching native message handler, and a receiver-side analysis gap may be hiding it.',\n 'message-handler-without-send':\n 'A native BasicMessageChannel handler has no matching Dart send.',\n 'unhandled-stream-listen':\n 'A Dart EventChannel stream listener has no matching native stream handler on any receiver-side document.',\n 'unhandled-stream-listen-unverified':\n 'A Dart EventChannel stream listener has no matching native stream handler, and a receiver-side analysis gap may be hiding it.',\n 'stream-handler-without-listen':\n 'A native EventChannel stream handler has no matching Dart stream listener.',\n};\n"]}
@@ -0,0 +1,38 @@
1
+ import type { ImpactSelection } from '../exchange/impact-selection.ts';
2
+ import type { BridgeLocation } from '../exchange/parse.ts';
3
+ import type { BridgeRuntimeDocument, RuntimeRoute } from '../exchange/runtime.ts';
4
+ import type { BridgeJoinResult } from '../join/join.ts';
5
+ import type { SelectedBridgeFact } from './impact.ts';
6
+ /** CLI/CI 호출자가 선언한 현재 분석 revision과 실제 실행 기록이다. */
7
+ export interface ImpactRuntimeInput {
8
+ readonly document: BridgeRuntimeDocument;
9
+ readonly revision: string;
10
+ }
11
+ /** 인스턴스별 실제 주소. 정적 핸들러는 실행 대상 확정이 아닌 검토 후보다. */
12
+ export interface RuntimeImpactRoute extends RuntimeRoute {
13
+ readonly instance: string;
14
+ readonly observedCalls: number;
15
+ readonly failedCalls: number;
16
+ readonly pendingCalls: number;
17
+ readonly callers: readonly BridgeLocation[];
18
+ readonly callersOmitted: number;
19
+ readonly staticStatus: 'candidates' | 'unobserved' | 'unsupported';
20
+ }
21
+ /** 정적 사실과 구분해서 보존하는 변경 관련 런타임 증거다. */
22
+ export interface RuntimeImpactEvidence {
23
+ readonly run: BridgeRuntimeDocument['run'];
24
+ readonly tool: BridgeRuntimeDocument['tool'];
25
+ readonly revision: string;
26
+ readonly stale: boolean;
27
+ readonly incomplete: boolean;
28
+ readonly droppedEvents: number;
29
+ readonly selectedEvents: number;
30
+ readonly failedCalls: number;
31
+ readonly pendingCalls: number;
32
+ readonly routes: readonly RuntimeImpactRoute[];
33
+ readonly reviewFiles: readonly string[];
34
+ }
35
+ /** 정적 선택에서 놓친 동적 호출을 실제 실행 주소로 찾되 다른 플랫폼을 추측하지 않는다. */
36
+ export declare function collectRuntimeImpact(joined: BridgeJoinResult, project: string | undefined, selection: ImpactSelection, selected: readonly SelectedBridgeFact[], input: ImpactRuntimeInput, nativePlatforms: ReadonlySet<string>): RuntimeImpactEvidence;
37
+ /** 기록의 부재·실패·불완전성 또는 정적 후보를 찾지 못한 주소는 검토가 필요하다. */
38
+ export declare function hasRuntimeImpactGaps(runtime: RuntimeImpactEvidence): boolean;
@@ -0,0 +1,94 @@
1
+ import { compareStrings } from "../compare.js";
2
+ import { isSafeNonEmptyString } from "../exchange/parse.js";
3
+ import { RuntimeValidationError } from "../exchange/runtime.js";
4
+ /** 정적 선택에서 놓친 동적 호출을 실제 실행 주소로 찾되 다른 플랫폼을 추측하지 않는다. */
5
+ export function collectRuntimeImpact(joined, project, selection, selected, input, nativePlatforms) {
6
+ const { document, revision } = input;
7
+ if (document.project !== project)
8
+ throw new RuntimeValidationError('Runtime and static inputs must describe the same project.');
9
+ if (!isSafeNonEmptyString(revision))
10
+ throw new RuntimeValidationError('Expected a current analysis revision.');
11
+ const stale = document.revision !== revision;
12
+ const native = document.run.platform === 'android' ? 'kotlin'
13
+ : document.run.platform === 'ios' || document.run.platform === 'macos' ? 'swift' : undefined;
14
+ const files = new Set(selection.files);
15
+ const locations = new Set(selected.map(({ location }) => locationKey(location)));
16
+ const selectedChannels = new Set();
17
+ const selectedMethods = new Set();
18
+ for (const fact of selected) {
19
+ if (fact.dynamic || fact.channel === null || fact.target !== 'flutter')
20
+ continue;
21
+ if (fact.method === undefined)
22
+ selectedChannels.add(fact.channel);
23
+ else
24
+ selectedMethods.add(methodKey(fact.channel, fact.method));
25
+ }
26
+ const candidates = new Set([...joined.matchedMethods, ...joined.handlersWithoutInvocations]
27
+ .filter(({ target, handlers }) => target === 'flutter' && handlers.some(({ platform }) => platform === native))
28
+ .map(({ channel, method }) => methodKey(channel, method)));
29
+ const groups = new Map();
30
+ const reviewFiles = new Set();
31
+ if (!stale)
32
+ for (const event of document.events) {
33
+ const selectedCaller = event.caller !== undefined &&
34
+ (files.has(event.caller.path) || locations.has(locationKey(event.caller)));
35
+ const selectedRoute = event.transport === 'method-channel' &&
36
+ (selectedChannels.has(event.channel) || selectedMethods.has(methodKey(event.channel, event.method)));
37
+ if (!selectedCaller && !selectedRoute)
38
+ continue;
39
+ const key = JSON.stringify([event.transport, event.channel, event.method ?? null, event.instance]);
40
+ let group = groups.get(key);
41
+ if (group === undefined) {
42
+ group = { event, observedCalls: 0, failedCalls: 0, pendingCalls: 0, callers: new Map() };
43
+ groups.set(key, group);
44
+ }
45
+ group.observedCalls++;
46
+ if (event.outcome === 'error' || event.outcome === 'missing-handler' || event.outcome === 'timeout')
47
+ group.failedCalls++;
48
+ if (event.outcome === 'pending')
49
+ group.pendingCalls++;
50
+ if (event.caller !== undefined) {
51
+ group.callers.set(locationKey(event.caller), event.caller);
52
+ reviewFiles.add(event.caller.path);
53
+ }
54
+ }
55
+ const routes = [...groups.entries()].sort(([a], [b]) => compareStrings(a, b))
56
+ .map(([, group]) => {
57
+ const { event, observedCalls, failedCalls, pendingCalls } = group;
58
+ const supported = native !== undefined && nativePlatforms.has(native) &&
59
+ event.transport === 'method-channel';
60
+ return {
61
+ transport: event.transport, channel: event.channel,
62
+ ...(event.method === undefined ? {} : { method: event.method }),
63
+ instance: event.instance, observedCalls, failedCalls, pendingCalls,
64
+ callers: [...group.callers.entries()].sort(([a], [b]) => compareStrings(a, b))
65
+ .slice(0, 20).map(([, caller]) => caller),
66
+ callersOmitted: Math.max(0, group.callers.size - 20),
67
+ staticStatus: !supported ? 'unsupported' : candidates.has(methodKey(event.channel, event.method))
68
+ ? 'candidates' : 'unobserved',
69
+ };
70
+ });
71
+ return {
72
+ run: document.run, tool: document.tool, revision: document.revision, stale,
73
+ incomplete: document.run.status !== 'completed', droppedEvents: document.droppedEvents,
74
+ selectedEvents: routes.reduce((count, route) => count + route.observedCalls, 0),
75
+ failedCalls: routes.reduce((count, route) => count + route.failedCalls, 0),
76
+ pendingCalls: routes.reduce((count, route) => count + route.pendingCalls, 0), routes,
77
+ reviewFiles: [...reviewFiles].sort(compareStrings),
78
+ };
79
+ }
80
+ /** 기록의 부재·실패·불완전성 또는 정적 후보를 찾지 못한 주소는 검토가 필요하다. */
81
+ export function hasRuntimeImpactGaps(runtime) {
82
+ return runtime.stale || runtime.incomplete || runtime.droppedEvents > 0 || runtime.selectedEvents === 0 ||
83
+ runtime.failedCalls > 0 || runtime.pendingCalls > 0 ||
84
+ runtime.routes.some(({ staticStatus }) => staticStatus !== 'candidates');
85
+ }
86
+ /** 메서드 이름의 구분자와 undefined를 실제 문자열과 혼동하지 않는다. */
87
+ function methodKey(channel, method) {
88
+ return JSON.stringify([channel, method ?? null]);
89
+ }
90
+ /** 명시된 호출 지점만 연결하고 주변 선언·인접 줄을 추측하지 않는다. */
91
+ function locationKey(location) {
92
+ return JSON.stringify([location.path, location.line, location.column]);
93
+ }
94
+ //# sourceMappingURL=runtime-impact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-impact.js","sourceRoot":"","sources":["../../src/report/runtime-impact.ts"],"names":[],"mappings":"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","sourcesContent":["import { compareStrings } from '../compare.ts';\nimport type { ImpactSelection } from '../exchange/impact-selection.ts';\nimport { isSafeNonEmptyString } from '../exchange/parse.ts';\nimport type { BridgeLocation } from '../exchange/parse.ts';\nimport { RuntimeValidationError } from '../exchange/runtime.ts';\nimport type { BridgeRuntimeDocument, RuntimeEvent, RuntimeRoute } from '../exchange/runtime.ts';\nimport type { BridgeJoinResult } from '../join/join.ts';\nimport type { SelectedBridgeFact } from './impact.ts';\n\n/** CLI/CI 호출자가 선언한 현재 분석 revision과 실제 실행 기록이다. */\nexport interface ImpactRuntimeInput {\n readonly document: BridgeRuntimeDocument;\n readonly revision: string;\n}\n\n/** 인스턴스별 실제 주소. 정적 핸들러는 실행 대상 확정이 아닌 검토 후보다. */\nexport interface RuntimeImpactRoute extends RuntimeRoute {\n readonly instance: string;\n readonly observedCalls: number;\n readonly failedCalls: number;\n readonly pendingCalls: number;\n readonly callers: readonly BridgeLocation[];\n readonly callersOmitted: number;\n readonly staticStatus: 'candidates' | 'unobserved' | 'unsupported';\n}\n\n/** 정적 사실과 구분해서 보존하는 변경 관련 런타임 증거다. */\nexport interface RuntimeImpactEvidence {\n readonly run: BridgeRuntimeDocument['run'];\n readonly tool: BridgeRuntimeDocument['tool'];\n readonly revision: string;\n readonly stale: boolean;\n readonly incomplete: boolean;\n readonly droppedEvents: number;\n readonly selectedEvents: number;\n readonly failedCalls: number;\n readonly pendingCalls: number;\n readonly routes: readonly RuntimeImpactRoute[];\n readonly reviewFiles: readonly string[];\n}\n\n/** 정적 선택에서 놓친 동적 호출을 실제 실행 주소로 찾되 다른 플랫폼을 추측하지 않는다. */\nexport function collectRuntimeImpact(\n joined: BridgeJoinResult, project: string | undefined, selection: ImpactSelection,\n selected: readonly SelectedBridgeFact[], input: ImpactRuntimeInput,\n nativePlatforms: ReadonlySet<string>,\n): RuntimeImpactEvidence {\n const { document, revision } = input;\n if (document.project !== project) throw new RuntimeValidationError('Runtime and static inputs must describe the same project.');\n if (!isSafeNonEmptyString(revision)) throw new RuntimeValidationError('Expected a current analysis revision.');\n const stale = document.revision !== revision;\n const native = document.run.platform === 'android' ? 'kotlin'\n : document.run.platform === 'ios' || document.run.platform === 'macos' ? 'swift' : undefined;\n const files = new Set(selection.files);\n const locations = new Set(selected.map(({ location }) => locationKey(location)));\n const selectedChannels = new Set<string>();\n const selectedMethods = new Set<string>();\n for (const fact of selected) {\n if (fact.dynamic || fact.channel === null || fact.target !== 'flutter') continue;\n if (fact.method === undefined) selectedChannels.add(fact.channel);\n else selectedMethods.add(methodKey(fact.channel, fact.method));\n }\n const candidates = new Set([...joined.matchedMethods, ...joined.handlersWithoutInvocations]\n .filter(({ target, handlers }) => target === 'flutter' && handlers.some(({ platform }) => platform === native))\n .map(({ channel, method }) => methodKey(channel, method)));\n const groups = new Map<string, {\n event: RuntimeEvent; observedCalls: number; failedCalls: number; pendingCalls: number;\n callers: Map<string, BridgeLocation>;\n }>();\n const reviewFiles = new Set<string>();\n if (!stale) for (const event of document.events) {\n const selectedCaller = event.caller !== undefined &&\n (files.has(event.caller.path) || locations.has(locationKey(event.caller)));\n const selectedRoute = event.transport === 'method-channel' &&\n (selectedChannels.has(event.channel) || selectedMethods.has(methodKey(event.channel, event.method)));\n if (!selectedCaller && !selectedRoute) continue;\n const key = JSON.stringify([event.transport, event.channel, event.method ?? null, event.instance]);\n let group = groups.get(key);\n if (group === undefined) {\n group = { event, observedCalls: 0, failedCalls: 0, pendingCalls: 0, callers: new Map() };\n groups.set(key, group);\n }\n group.observedCalls++;\n if (event.outcome === 'error' || event.outcome === 'missing-handler' || event.outcome === 'timeout') group.failedCalls++;\n if (event.outcome === 'pending') group.pendingCalls++;\n if (event.caller !== undefined) {\n group.callers.set(locationKey(event.caller), event.caller);\n reviewFiles.add(event.caller.path);\n }\n }\n const routes = [...groups.entries()].sort(([a], [b]) => compareStrings(a, b))\n .map(([, group]): RuntimeImpactRoute => {\n const { event, observedCalls, failedCalls, pendingCalls } = group;\n const supported = native !== undefined && nativePlatforms.has(native) &&\n event.transport === 'method-channel';\n return {\n transport: event.transport, channel: event.channel,\n ...(event.method === undefined ? {} : { method: event.method }),\n instance: event.instance, observedCalls, failedCalls, pendingCalls,\n callers: [...group.callers.entries()].sort(([a], [b]) => compareStrings(a, b))\n .slice(0, 20).map(([, caller]) => caller),\n callersOmitted: Math.max(0, group.callers.size - 20),\n staticStatus: !supported ? 'unsupported' : candidates.has(methodKey(event.channel, event.method))\n ? 'candidates' : 'unobserved',\n };\n });\n return {\n run: document.run, tool: document.tool, revision: document.revision, stale,\n incomplete: document.run.status !== 'completed', droppedEvents: document.droppedEvents,\n selectedEvents: routes.reduce((count, route) => count + route.observedCalls, 0),\n failedCalls: routes.reduce((count, route) => count + route.failedCalls, 0),\n pendingCalls: routes.reduce((count, route) => count + route.pendingCalls, 0), routes,\n reviewFiles: [...reviewFiles].sort(compareStrings),\n };\n}\n\n/** 기록의 부재·실패·불완전성 또는 정적 후보를 찾지 못한 주소는 검토가 필요하다. */\nexport function hasRuntimeImpactGaps(runtime: RuntimeImpactEvidence): boolean {\n return runtime.stale || runtime.incomplete || runtime.droppedEvents > 0 || runtime.selectedEvents === 0 ||\n runtime.failedCalls > 0 || runtime.pendingCalls > 0 ||\n runtime.routes.some(({ staticStatus }) => staticStatus !== 'candidates');\n}\n\n/** 메서드 이름의 구분자와 undefined를 실제 문자열과 혼동하지 않는다. */\nfunction methodKey(channel: string, method: string | undefined): string {\n return JSON.stringify([channel, method ?? null]);\n}\n\n/** 명시된 호출 지점만 연결하고 주변 선언·인접 줄을 추측하지 않는다. */\nfunction locationKey(location: BridgeLocation): string {\n return JSON.stringify([location.path, location.line, location.column]);\n}\n"]}
@@ -0,0 +1,54 @@
1
+ import type { BridgeRuntimeDocument, RuntimeEvent, RuntimeExpectation, RuntimeExpectations } from '../exchange/runtime.ts';
2
+ /** 전체 평가와 무관한 증거 표시 상한. 생략 수를 명시한다. */
3
+ export declare const MAX_RUNTIME_EVIDENCE_PER_CHECK = 20;
4
+ /** 원본 run과 sequence로 재현 가능한 통신 결과 참조다. */
5
+ export interface RuntimeEvidence {
6
+ readonly runId: string;
7
+ readonly sequence: number;
8
+ readonly instance: string;
9
+ readonly outcome: RuntimeEvent['outcome'];
10
+ readonly caller?: RuntimeEvent['caller'];
11
+ }
12
+ /** 기대 항목 하나의 결과와 관찰 근거다. */
13
+ export interface RuntimeCheckResult {
14
+ readonly expected: RuntimeExpectation;
15
+ readonly status: 'passed' | 'failed' | 'unobserved' | 'incomplete';
16
+ readonly observedCalls: number;
17
+ readonly evidence: readonly RuntimeEvidence[];
18
+ readonly evidenceOmitted: number;
19
+ }
20
+ /** 선언한 시나리오의 통신 검증 결과. 모든 실행 경로를 검증했다는 뜻은 아니다. */
21
+ export interface RuntimeVerificationReport {
22
+ readonly format: 'isthmus-runtime-check';
23
+ readonly version: 1;
24
+ readonly project: string;
25
+ readonly revision: string;
26
+ readonly scope: 'declared-scenarios';
27
+ readonly complete: false;
28
+ readonly status: 'passed' | 'failed' | 'incomplete';
29
+ readonly summary: {
30
+ readonly runs: number;
31
+ readonly staleRuns: number;
32
+ readonly incompleteRuns: number;
33
+ readonly droppedEvents: number;
34
+ readonly failedCalls: number;
35
+ readonly expectedFailedCalls: number;
36
+ readonly unexpectedFailedCalls: number;
37
+ readonly pendingCalls: number;
38
+ readonly passedChecks: number;
39
+ readonly failedChecks: number;
40
+ readonly unobservedChecks: number;
41
+ readonly incompleteChecks: number;
42
+ };
43
+ readonly checks: readonly RuntimeCheckResult[];
44
+ readonly failures: ReadonlyArray<{
45
+ readonly runId: string;
46
+ readonly event: RuntimeEvent;
47
+ }>;
48
+ readonly runs: ReadonlyArray<Omit<BridgeRuntimeDocument, 'events' | 'project' | 'format' | 'version'> & {
49
+ readonly stale: boolean;
50
+ readonly observedEvents: number;
51
+ }>;
52
+ }
53
+ /** 검증된 관찰을 라우팅 키로 색인해 기대 항목마다 전체 이벤트를 다시 훑지 않는다. */
54
+ export declare function verifyRuntimeEvidence(expectations: RuntimeExpectations, documents: readonly BridgeRuntimeDocument[]): RuntimeVerificationReport;