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.
- package/README.ko.md +170 -45
- package/README.md +178 -36
- package/Skills/isthmus/SKILL.md +104 -5
- package/compatibility.json +10 -0
- package/dist/cli/check-command.d.ts +2 -2
- package/dist/cli/check-command.js +22 -9
- package/dist/cli/check-command.js.map +1 -1
- package/dist/cli/command-support.d.ts +20 -2
- package/dist/cli/command-support.js +38 -9
- package/dist/cli/command-support.js.map +1 -1
- package/dist/cli/diff-command.js +4 -3
- package/dist/cli/diff-command.js.map +1 -1
- package/dist/cli/doctor-command.d.ts +11 -0
- package/dist/cli/doctor-command.js +128 -0
- package/dist/cli/doctor-command.js.map +1 -0
- package/dist/cli/extract-js-command.d.ts +33 -0
- package/dist/cli/extract-js-command.js +233 -0
- package/dist/cli/extract-js-command.js.map +1 -0
- package/dist/cli/graph-command.js +13 -6
- package/dist/cli/graph-command.js.map +1 -1
- package/dist/cli/impact-command.d.ts +5 -0
- package/dist/cli/impact-command.js +111 -0
- package/dist/cli/impact-command.js.map +1 -0
- package/dist/cli/init-command.d.ts +11 -0
- package/dist/cli/init-command.js +115 -0
- package/dist/cli/init-command.js.map +1 -0
- package/dist/cli/main.js +67 -1
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/mcp-server.d.ts +16 -0
- package/dist/cli/mcp-server.js +534 -0
- package/dist/cli/mcp-server.js.map +1 -0
- package/dist/cli/preflight-command.d.ts +5 -0
- package/dist/cli/preflight-command.js +83 -0
- package/dist/cli/preflight-command.js.map +1 -0
- package/dist/cli/query-command.js +13 -6
- package/dist/cli/query-command.js.map +1 -1
- package/dist/cli/retentions-command.js +14 -7
- package/dist/cli/retentions-command.js.map +1 -1
- package/dist/cli/runtime-command.d.ts +5 -0
- package/dist/cli/runtime-command.js +42 -0
- package/dist/cli/runtime-command.js.map +1 -0
- package/dist/cli/runtime-json-reader.d.ts +12 -0
- package/dist/cli/runtime-json-reader.js +37 -0
- package/dist/cli/runtime-json-reader.js.map +1 -0
- package/dist/cli/serve-command.d.ts +13 -0
- package/dist/cli/serve-command.js +43 -0
- package/dist/cli/serve-command.js.map +1 -0
- package/dist/exchange/capture-config.d.ts +18 -0
- package/dist/exchange/capture-config.js +100 -0
- package/dist/exchange/capture-config.js.map +1 -0
- package/dist/exchange/impact-selection.d.ts +13 -0
- package/dist/exchange/impact-selection.js +35 -0
- package/dist/exchange/impact-selection.js.map +1 -0
- package/dist/exchange/kartograph-impact.d.ts +4 -0
- package/dist/exchange/kartograph-impact.js +169 -0
- package/dist/exchange/kartograph-impact.js.map +1 -0
- package/dist/exchange/messages.d.ts +36 -0
- package/dist/exchange/messages.js +96 -0
- package/dist/exchange/messages.js.map +1 -0
- package/dist/exchange/parse.d.ts +69 -0
- package/dist/exchange/parse.js +127 -20
- package/dist/exchange/parse.js.map +1 -1
- package/dist/exchange/preflight-context.d.ts +71 -0
- package/dist/exchange/preflight-context.js +317 -0
- package/dist/exchange/preflight-context.js.map +1 -0
- package/dist/exchange/producer-impact.d.ts +17 -0
- package/dist/exchange/producer-impact.js +226 -0
- package/dist/exchange/producer-impact.js.map +1 -0
- package/dist/exchange/runtime.d.ts +69 -0
- package/dist/exchange/runtime.js +160 -0
- package/dist/exchange/runtime.js.map +1 -0
- package/dist/extract/js-document.d.ts +22 -0
- package/dist/extract/js-document.js +230 -0
- package/dist/extract/js-document.js.map +1 -0
- package/dist/extract/js-scan.d.ts +93 -0
- package/dist/extract/js-scan.js +1205 -0
- package/dist/extract/js-scan.js.map +1 -0
- package/dist/extract/lexer.d.ts +26 -0
- package/dist/extract/lexer.js +316 -0
- package/dist/extract/lexer.js.map +1 -0
- package/dist/join/join.d.ts +66 -1
- package/dist/join/join.js +171 -1
- package/dist/join/join.js.map +1 -1
- package/dist/join/message-address.d.ts +7 -0
- package/dist/join/message-address.js +37 -0
- package/dist/join/message-address.js.map +1 -0
- package/dist/join/messages.d.ts +26 -0
- package/dist/join/messages.js +87 -0
- package/dist/join/messages.js.map +1 -0
- package/dist/report/check-report.d.ts +20 -3
- package/dist/report/check-report.js +240 -8
- package/dist/report/check-report.js.map +1 -1
- package/dist/report/codequality.d.ts +45 -0
- package/dist/report/codequality.js +74 -0
- package/dist/report/codequality.js.map +1 -0
- package/dist/report/diff.d.ts +25 -3
- package/dist/report/diff.js +91 -10
- package/dist/report/diff.js.map +1 -1
- package/dist/report/graph.d.ts +10 -4
- package/dist/report/graph.js +60 -6
- package/dist/report/graph.js.map +1 -1
- package/dist/report/impact.d.ts +62 -0
- package/dist/report/impact.js +168 -0
- package/dist/report/impact.js.map +1 -0
- package/dist/report/preflight-runtime.d.ts +42 -0
- package/dist/report/preflight-runtime.js +193 -0
- package/dist/report/preflight-runtime.js.map +1 -0
- package/dist/report/preflight-view.d.ts +146 -0
- package/dist/report/preflight-view.js +216 -0
- package/dist/report/preflight-view.js.map +1 -0
- package/dist/report/preflight.d.ts +104 -0
- package/dist/report/preflight.js +331 -0
- package/dist/report/preflight.js.map +1 -0
- package/dist/report/query.d.ts +11 -4
- package/dist/report/query.js +84 -17
- package/dist/report/query.js.map +1 -1
- package/dist/report/retentions.d.ts +17 -5
- package/dist/report/retentions.js +85 -8
- package/dist/report/retentions.js.map +1 -1
- package/dist/report/rules.d.ts +16 -0
- package/dist/report/rules.js +30 -0
- package/dist/report/rules.js.map +1 -0
- package/dist/report/runtime-impact.d.ts +38 -0
- package/dist/report/runtime-impact.js +94 -0
- package/dist/report/runtime-impact.js.map +1 -0
- package/dist/report/runtime.d.ts +54 -0
- package/dist/report/runtime.js +137 -0
- package/dist/report/runtime.js.map +1 -0
- package/dist/report/sarif.d.ts +2 -8
- package/dist/report/sarif.js +2 -10
- package/dist/report/sarif.js.map +1 -1
- package/dist/report/sorted-json.d.ts +1 -1
- package/dist/report/sorted-json.js +2 -2
- package/dist/report/sorted-json.js.map +1 -1
- package/docs/BRIDGE-EVENTS.md +87 -0
- package/docs/BRIDGE-MESSAGES.md +117 -0
- package/docs/GRAPH-EXCHANGE.md +382 -0
- package/docs/IMPACT.md +96 -0
- package/docs/MCP.md +62 -0
- package/docs/PREFLIGHT.md +311 -0
- package/docs/RUNTIME.md +172 -0
- package/docs/TOOLCHAIN.md +101 -0
- package/package.json +14 -2
- package/scripts/build-preflight-toolchain.mjs +186 -0
- package/scripts/capture-preflight.mjs +380 -0
- package/scripts/run-child.mjs +17 -0
package/dist/report/graph.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/report/graph.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n BridgeLocation,\n BridgePlatform,\n BridgeSymbol,\n BridgeSourceLanguage,\n BridgeTarget,\n} from '../exchange/parse.ts';\nimport type {\n BridgeEndpoint,\n BridgeJoinResult,\n JoinLimitation,\n} from '../join/join.ts';\nimport { isBridgeJoinDeferred } from '../join/join.ts';\nimport { compareStrings } from '../compare.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\n\n/** 소스 위치 하나를 나타내는 경계 그래프 노드다. */\nexport interface BridgeGraphNode {\n readonly id: string;\n readonly platform: BridgePlatform;\n readonly location: BridgeLocation;\n readonly symbol?: BridgeSymbol;\n readonly sourceLanguage?: BridgeSourceLanguage;\n}\n\n/** 호출 측 위치에서 수신 측 위치로 향하는 경계 간선이다. */\nexport interface BridgeGraphEdge {\n readonly from: string;\n readonly to: string;\n readonly kind: 'channel' | 'method';\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly method?: string;\n}\n\n/** 매치된 경계 간선만 담는 그래프 문서다. */\nexport interface BridgeGraphDocument {\n readonly format: 'isthmus-graph';\n readonly version: 1;\n readonly nodes: readonly BridgeGraphNode[];\n readonly edges: readonly BridgeGraphEdge[];\n readonly limitations: readonly JoinLimitation[];\n}\n\n/** 한 번에 생성할 수 있는 경계 그래프 간선 수다. */\nexport const MAX_GRAPH_EDGES = 100_000;\n\n/** 입력 증거의 곱이 안전한 그래프 크기를 넘었음을 나타낸다. */\nexport class BridgeGraphLimitError extends Error {\n constructor() {\n super(`Bridge graph exceeds the ${MAX_GRAPH_EDGES} edge limit.`);\n this.name = 'BridgeGraphLimitError';\n }\n}\n\n/** 같은 위치 노드가 서로 모순된 심볼을 가졌음을 나타낸다. */\nexport class BridgeGraphValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BridgeGraphValidationError';\n }\n}\n\n/** 경계 그래프를 요청한 출력 형식으로 렌더링한다. */\nexport function renderBridgeGraph(\n graph: BridgeGraphDocument,\n format: 'json' | 'dot' | 'mermaid',\n): string {\n if (format === 'json') return encodeSortedJson(graph);\n if (format === 'dot') return renderDot(graph);\n return renderMermaid(graph);\n}\n\n/** Mermaid flowchart 문법으로 경계 그래프를 렌더링한다. */\nfunction renderMermaid(graph: BridgeGraphDocument): string {\n const identifiers = new Map(\n graph.nodes.map((node, index) => [node.id, `n${index}`]),\n );\n const lines = ['flowchart LR'];\n for (const limitation of graph.limitations) {\n lines.push(` %% limitation: ${limitationComment(limitation)}`);\n }\n for (const node of graph.nodes) {\n const label = `${mermaidText(node.sourceLanguage ?? node.platform)}<br/>${mermaidText(locationLabel(node.location))}`;\n lines.push(` ${identifiers.get(node.id)}[\"${label}\"]`);\n }\n for (const edge of graph.edges) {\n const from = identifiers.get(edge.from);\n const to = identifiers.get(edge.to);\n lines.push(` ${from} -->|\"${mermaidText(edgeLabel(edge))}\"| ${to}`);\n }\n return `${lines.join('\\n')}\\n`;\n}\n\n/** Mermaid 인용 라벨에서 제어 문자를 HTML 엔터티로 바꾼다. */\nfunction mermaidText(value: string): string {\n return value\n .replaceAll('&', '&')\n .replaceAll('\"', '"')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('|', '|')\n .replaceAll('\\r', ' ')\n .replaceAll('\\n', ' ');\n}\n\n/** Graphviz DOT 문법으로 경계 그래프를 렌더링한다. */\nfunction renderDot(graph: BridgeGraphDocument): string {\n const lines = ['digraph isthmus {', ' rankdir=LR;'];\n for (const limitation of graph.limitations) {\n lines.push(` // limitation: ${limitationComment(limitation)}`);\n }\n for (const node of graph.nodes) {\n const label = `${node.sourceLanguage ?? node.platform}\\n${locationLabel(node.location)}`;\n lines.push(` ${dotString(node.id)} [label=${dotString(label)}];`);\n }\n for (const edge of graph.edges) {\n lines.push(\n ` ${dotString(edge.from)} -> ${dotString(edge.to)} `\n + `[label=${dotString(edgeLabel(edge))}];`,\n );\n }\n lines.push('}');\n return `${lines.join('\\n')}\\n`;\n}\n\n/** 분석 한계를 텍스트 그래프의 한 줄 주석으로 안전하게 만든다. */\nfunction limitationComment(limitation: JoinLimitation): string {\n const text =\n `${limitation.platform}/${limitation.target}/${limitation.tool}: `\n + limitation.message\n + (limitation.origin === undefined ? '' : ' [origin: consumer]')\n + (limitation.channels === undefined ? '' : ` [channels: ${JSON.stringify(limitation.channels)}]`);\n return text.replace(/[\\u0000-\\u001f\\u007f-\\u009f\\u2028\\u2029]+/gu, ' ');\n}\n\n/** DOT 문자열 리터럴로 안전하게 이스케이프한다. */\nfunction dotString(value: string): string {\n return JSON.stringify(value);\n}\n\n/** 위치를 사람이 읽을 수 있는 경로:줄:열로 만든다. */\nfunction locationLabel(location: BridgeLocation): string {\n return `${location.path}:${location.line}:${location.column}`;\n}\n\n/** 간선 종류와 문자열 키를 한 줄 라벨로 만든다. */\nfunction edgeLabel(edge: BridgeGraphEdge): string {\n const key = edge.method === undefined\n ? edge.channel\n : `${edge.channel}#${edge.method}`;\n return `${edge.kind} ${key}`;\n}\n\n/** 조인 결과의 매치만 경계 그래프로 바꾼다. */\nexport function createBridgeGraph(joined: BridgeJoinResult): BridgeGraphDocument {\n if (isBridgeJoinDeferred(joined)) {\n throw new Error('Cannot create a graph from a deferred bridge join.');\n }\n assertBridgeGraphSize(joined);\n const nodes = new Map<string, BridgeGraphNode>();\n const edges: BridgeGraphEdge[] = [];\n addChannelEdges(joined, nodes, edges);\n addMethodEdges(joined, nodes, edges);\n return {\n format: 'isthmus-graph',\n version: 1,\n nodes: [...nodes.values()].sort((left, right) => compareStrings(left.id, right.id)),\n edges: edges.sort(compareEdges),\n limitations: joined.limitations,\n };\n}\n\n/** 모든 Cartesian 간선 수를 할당 전에 계산해 메모리 폭증을 막는다. */\nexport function assertBridgeGraphSize(joined: BridgeJoinResult): void {\n let edgeCount = 0;\n const endpointCounts = [\n ...joined.matchedChannels.map(({ creations, registrations }) =>\n [creations.length, registrations.length] as const,\n ),\n ...joined.matchedMethods.map(({ invocations, handlers }) =>\n [invocations.length, handlers.length] as const,\n ),\n ];\n for (const [fromCount, toCount] of endpointCounts) {\n if (\n toCount > 0 &&\n fromCount > Math.floor((MAX_GRAPH_EDGES - edgeCount) / toCount)\n ) {\n throw new BridgeGraphLimitError();\n }\n edgeCount += fromCount * toCount;\n }\n}\n\n/** 매치된 채널의 생성→등록 간선을 추가한다. */\nfunction addChannelEdges(\n joined: BridgeJoinResult,\n nodes: Map<string, BridgeGraphNode>,\n edges: BridgeGraphEdge[],\n): void {\n for (const match of joined.matchedChannels) {\n for (const creation of match.creations) {\n for (const registration of match.registrations) {\n const from = addNode(nodes, creation);\n const to = addNode(nodes, registration);\n edges.push({\n from,\n to,\n kind: 'channel',\n target: match.target,\n channel: match.channel,\n });\n }\n }\n }\n}\n\n/** 매치된 메서드의 호출→핸들러 간선을 추가한다. */\nfunction addMethodEdges(\n joined: BridgeJoinResult,\n nodes: Map<string, BridgeGraphNode>,\n edges: BridgeGraphEdge[],\n): void {\n for (const match of joined.matchedMethods) {\n for (const invocation of match.invocations) {\n for (const handler of match.handlers) {\n const from = addNode(nodes, invocation);\n const to = addNode(nodes, handler);\n edges.push({\n from,\n to,\n kind: 'method',\n target: match.target,\n channel: match.channel,\n method: match.method,\n });\n }\n }\n }\n}\n\n/** 증거 위치를 그래프 노드로 추가하고 안정 ID를 돌려준다. */\nfunction addNode(\n nodes: Map<string, BridgeGraphNode>,\n endpoint: BridgeEndpoint,\n): string {\n const id = endpointId(endpoint);\n const existing = nodes.get(id);\n if (existing === undefined) {\n nodes.set(id, { id, ...endpoint });\n } else if (endpoint.symbol !== undefined) {\n const symbol = mergeSymbols(existing.symbol, endpoint.symbol);\n if (symbol === undefined) {\n throw new BridgeGraphValidationError(\n 'Bridge graph node has conflicting symbols.',\n );\n }\n nodes.set(id, { ...existing, symbol });\n }\n return id;\n}\n\n/** 호환되는 두 심볼을 더 구체적인 선언 식별자로 병합한다. */\nfunction mergeSymbols(\n left: BridgeSymbol | undefined,\n right: BridgeSymbol,\n): BridgeSymbol | undefined {\n if (left === undefined) return right;\n if (left.qualifiedName !== right.qualifiedName) return undefined;\n if (left.usr !== undefined && right.usr !== undefined && left.usr !== right.usr) {\n return undefined;\n }\n const usr = left.usr ?? right.usr;\n return usr === undefined\n ? { qualifiedName: left.qualifiedName }\n : { qualifiedName: left.qualifiedName, usr };\n}\n\n/** 플랫폼과 소스 위치로 실행 간 안정적인 노드 ID를 만든다. */\nfunction endpointId(endpoint: BridgeEndpoint): string {\n const { path, line, column } = endpoint.location;\n const suffix = endpoint.sourceLanguage === undefined ? '' : `:${endpoint.sourceLanguage}`;\n return `${endpoint.platform}:${path}:${line}:${column}${suffix}`;\n}\n\n/** 간선을 종류·from·to 순으로 고정한다. */\nfunction compareEdges(left: BridgeGraphEdge, right: BridgeGraphEdge): number {\n return (\n compareStrings(left.kind, right.kind) ||\n compareStrings(left.from, right.from) ||\n compareStrings(left.to, right.to)\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/report/graph.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n BridgeLocation,\n BridgePlatform,\n BridgeSymbol,\n BridgeSourceLanguage,\n BridgeTarget,\n} from '../exchange/parse.ts';\nimport type {\n BridgeEndpoint,\n BridgeJoinResult,\n JoinLimitation,\n MatchedBoundaryName,\n} from '../join/join.ts';\nimport { compareLimitations, isBridgeJoinDeferred } from '../join/join.ts';\nimport type { MessageBridgeJoin } from '../join/messages.ts';\nimport { compareStrings } from '../compare.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\n\n/** 소스 위치 하나를 나타내는 경계 그래프 노드다. */\nexport interface BridgeGraphNode {\n readonly id: string;\n readonly platform: BridgePlatform;\n readonly location: BridgeLocation;\n readonly symbol?: BridgeSymbol;\n readonly sourceLanguage?: BridgeSourceLanguage;\n}\n\n/** 호출 측 위치에서 수신 측 위치로 향하는 경계 간선이다. */\nexport interface BridgeGraphEdge {\n readonly from: string;\n readonly to: string;\n readonly kind: 'channel' | 'method' | 'module' | 'component' | 'message' | 'stream';\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly method?: string;\n}\n\n/** 매치된 경계 간선만 담는 그래프 문서다. */\nexport interface BridgeGraphDocument {\n readonly format: 'isthmus-graph';\n readonly version: 1;\n readonly nodes: readonly BridgeGraphNode[];\n readonly edges: readonly BridgeGraphEdge[];\n readonly limitations: readonly JoinLimitation[];\n}\n\n/** 한 번에 생성할 수 있는 경계 그래프 간선 수다. */\nexport const MAX_GRAPH_EDGES = 100_000;\n\n/** 입력 증거의 곱이 안전한 그래프 크기를 넘었음을 나타낸다. */\nexport class BridgeGraphLimitError extends Error {\n constructor() {\n super(`Bridge graph exceeds the ${MAX_GRAPH_EDGES} edge limit.`);\n this.name = 'BridgeGraphLimitError';\n }\n}\n\n/** 같은 위치 노드가 서로 모순된 심볼을 가졌음을 나타낸다. */\nexport class BridgeGraphValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BridgeGraphValidationError';\n }\n}\n\n/** 경계 그래프를 요청한 출력 형식으로 렌더링한다. */\nexport function renderBridgeGraph(\n graph: BridgeGraphDocument,\n format: 'json' | 'dot' | 'mermaid',\n): string {\n if (format === 'json') return encodeSortedJson(graph);\n if (format === 'dot') return renderDot(graph);\n return renderMermaid(graph);\n}\n\n/** Mermaid flowchart 문법으로 경계 그래프를 렌더링한다. */\nfunction renderMermaid(graph: BridgeGraphDocument): string {\n const identifiers = new Map(\n graph.nodes.map((node, index) => [node.id, `n${index}`]),\n );\n const lines = ['flowchart LR'];\n for (const limitation of graph.limitations) {\n lines.push(` %% limitation: ${limitationComment(limitation)}`);\n }\n for (const node of graph.nodes) {\n const label = `${mermaidText(node.sourceLanguage ?? node.platform)}<br/>${mermaidText(locationLabel(node.location))}`;\n lines.push(` ${identifiers.get(node.id)}[\"${label}\"]`);\n }\n for (const edge of graph.edges) {\n const from = identifiers.get(edge.from);\n const to = identifiers.get(edge.to);\n lines.push(` ${from} -->|\"${mermaidText(edgeLabel(edge))}\"| ${to}`);\n }\n return `${lines.join('\\n')}\\n`;\n}\n\n/** Mermaid 인용 라벨에서 제어 문자를 HTML 엔터티로 바꾼다. */\nfunction mermaidText(value: string): string {\n return value\n .replaceAll('&', '&')\n .replaceAll('\"', '"')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('|', '|')\n .replaceAll('\\r', ' ')\n .replaceAll('\\n', ' ');\n}\n\n/** Graphviz DOT 문법으로 경계 그래프를 렌더링한다. */\nfunction renderDot(graph: BridgeGraphDocument): string {\n const lines = ['digraph isthmus {', ' rankdir=LR;'];\n for (const limitation of graph.limitations) {\n lines.push(` // limitation: ${limitationComment(limitation)}`);\n }\n for (const node of graph.nodes) {\n const label = `${node.sourceLanguage ?? node.platform}\\n${locationLabel(node.location)}`;\n lines.push(` ${dotString(node.id)} [label=${dotString(label)}];`);\n }\n for (const edge of graph.edges) {\n lines.push(\n ` ${dotString(edge.from)} -> ${dotString(edge.to)} `\n + `[label=${dotString(edgeLabel(edge))}];`,\n );\n }\n lines.push('}');\n return `${lines.join('\\n')}\\n`;\n}\n\n/** 분석 한계를 텍스트 그래프의 한 줄 주석으로 안전하게 만든다. */\nfunction limitationComment(limitation: JoinLimitation): string {\n const text =\n `${limitation.platform}/${limitation.target}/${limitation.tool}: `\n + limitation.message\n + (limitation.origin === undefined ? '' : ' [origin: consumer]')\n + (limitation.channels === undefined ? '' : ` [channels: ${JSON.stringify(limitation.channels)}]`);\n return text.replace(/[\\u0000-\\u001f\\u007f-\\u009f\\u2028\\u2029]+/gu, ' ');\n}\n\n/** DOT 문자열 리터럴로 안전하게 이스케이프한다. */\nfunction dotString(value: string): string {\n return JSON.stringify(value);\n}\n\n/** 위치를 사람이 읽을 수 있는 경로:줄:열로 만든다. */\nfunction locationLabel(location: BridgeLocation): string {\n return `${location.path}:${location.line}:${location.column}`;\n}\n\n/** 간선 종류와 문자열 키를 한 줄 라벨로 만든다. */\nfunction edgeLabel(edge: BridgeGraphEdge): string {\n const key = edge.method === undefined\n ? edge.channel\n : `${edge.channel}#${edge.method}`;\n return `${edge.kind} ${key}`;\n}\n\n/**\n * 조인 결과의 매치만 경계 그래프로 만든다.\n *\n * `messages`를 주면 literal로 확정된 Basic·Event 경계가 `message`·`stream`\n * 간선으로 더해진다. dynamic prefix 후보는 확정 매치가 아니므로 담지 않는다.\n */\nexport function createBridgeGraph(\n joined: BridgeJoinResult,\n messages?: MessageBridgeJoin,\n): BridgeGraphDocument {\n if (isBridgeJoinDeferred(joined)) {\n throw new Error('Cannot create a graph from a deferred bridge join.');\n }\n assertBridgeGraphSize(joined, messages);\n const nodes = new Map<string, BridgeGraphNode>();\n const edges: BridgeGraphEdge[] = [];\n addChannelEdges(joined, nodes, edges);\n addMethodEdges(joined, nodes, edges);\n addNameEdges(joined.matchedModules, 'module', nodes, edges);\n addNameEdges(joined.matchedComponents, 'component', nodes, edges);\n if (messages !== undefined) addMessageEdges(messages, nodes, edges);\n return {\n format: 'isthmus-graph',\n version: 1,\n nodes: [...nodes.values()].sort((left, right) => compareStrings(left.id, right.id)),\n edges: edges.sort(compareEdges),\n limitations: messages === undefined\n ? joined.limitations\n : [...joined.limitations, ...messages.limitations].sort(compareLimitations),\n };\n}\n\n/** literal로 확정된 Basic·Event 경계의 발신→수신 간선을 추가한다. */\nfunction addMessageEdges(\n messages: MessageBridgeJoin,\n nodes: Map<string, BridgeGraphNode>,\n edges: BridgeGraphEdge[],\n): void {\n for (const route of messages.routes) {\n if (route.matching !== 'literal') continue;\n for (const sender of route.senders) {\n for (const handler of route.handlers) {\n const from = addNode(nodes, sender);\n const to = addNode(nodes, handler);\n edges.push({\n from,\n to,\n kind: route.transport === 'event-channel' ? 'stream' : 'message',\n target: 'flutter',\n channel: route.channel,\n });\n }\n }\n }\n}\n\n/** 모든 Cartesian 간선 수를 할당 전에 계산해 메모리 폭증을 막는다. */\nexport function assertBridgeGraphSize(\n joined: BridgeJoinResult,\n messages?: MessageBridgeJoin,\n): void {\n let edgeCount = 0;\n const endpointCounts = [\n ...joined.matchedChannels.map(({ creations, registrations }) =>\n [creations.length, registrations.length] as const,\n ),\n ...joined.matchedMethods.map(({ invocations, handlers }) =>\n [invocations.length, handlers.length] as const,\n ),\n ...joined.matchedModules.map(({ callers, receivers }) =>\n [callers.length, receivers.length] as const,\n ),\n ...joined.matchedComponents.map(({ callers, receivers }) =>\n [callers.length, receivers.length] as const,\n ),\n ...(messages?.routes ?? [])\n .filter((route) => route.matching === 'literal')\n .map(({ senders, handlers }) =>\n [senders.length, handlers.length] as const,\n ),\n ];\n for (const [fromCount, toCount] of endpointCounts) {\n if (\n toCount > 0 &&\n fromCount > Math.floor((MAX_GRAPH_EDGES - edgeCount) / toCount)\n ) {\n throw new BridgeGraphLimitError();\n }\n edgeCount += fromCount * toCount;\n }\n}\n\n/** 매치된 채널의 생성→등록 간선을 추가한다. */\nfunction addChannelEdges(\n joined: BridgeJoinResult,\n nodes: Map<string, BridgeGraphNode>,\n edges: BridgeGraphEdge[],\n): void {\n for (const match of joined.matchedChannels) {\n for (const creation of match.creations) {\n for (const registration of match.registrations) {\n const from = addNode(nodes, creation);\n const to = addNode(nodes, registration);\n edges.push({\n from,\n to,\n kind: 'channel',\n target: match.target,\n channel: match.channel,\n });\n }\n }\n }\n}\n\n/** 매치된 메서드의 호출→핸들러 간선을 추가한다. */\nfunction addMethodEdges(\n joined: BridgeJoinResult,\n nodes: Map<string, BridgeGraphNode>,\n edges: BridgeGraphEdge[],\n): void {\n for (const match of joined.matchedMethods) {\n for (const invocation of match.invocations) {\n for (const handler of match.handlers) {\n const from = addNode(nodes, invocation);\n const to = addNode(nodes, handler);\n edges.push({\n from,\n to,\n kind: 'method',\n target: match.target,\n channel: match.channel,\n method: match.method,\n });\n }\n }\n }\n}\n\n/** 매치된 모듈·컴포넌트 이름의 호출→export 간선을 추가한다. */\nfunction addNameEdges(\n matches: readonly MatchedBoundaryName[],\n kind: 'module' | 'component',\n nodes: Map<string, BridgeGraphNode>,\n edges: BridgeGraphEdge[],\n): void {\n for (const match of matches) {\n for (const caller of match.callers) {\n for (const receiver of match.receivers) {\n const from = addNode(nodes, caller);\n const to = addNode(nodes, receiver);\n edges.push({\n from,\n to,\n kind,\n target: match.target,\n channel: match.channel,\n });\n }\n }\n }\n}\n\n/** 증거 위치를 그래프 노드로 추가하고 안정 ID를 돌려준다. */\nfunction addNode(\n nodes: Map<string, BridgeGraphNode>,\n endpoint: BridgeEndpoint,\n): string {\n const id = endpointId(endpoint);\n const existing = nodes.get(id);\n if (existing === undefined) {\n nodes.set(id, { id, ...endpoint });\n } else if (endpoint.symbol !== undefined) {\n const symbol = mergeSymbols(existing.symbol, endpoint.symbol);\n if (symbol === undefined) {\n throw new BridgeGraphValidationError(\n 'Bridge graph node has conflicting symbols.',\n );\n }\n nodes.set(id, { ...existing, symbol });\n }\n return id;\n}\n\n/** 호환되는 두 심볼을 더 구체적인 선언 식별자로 병합한다. */\nfunction mergeSymbols(\n left: BridgeSymbol | undefined,\n right: BridgeSymbol,\n): BridgeSymbol | undefined {\n if (left === undefined) return right;\n if (left.qualifiedName !== right.qualifiedName) return undefined;\n if (left.usr !== undefined && right.usr !== undefined && left.usr !== right.usr) {\n return undefined;\n }\n const usr = left.usr ?? right.usr;\n return usr === undefined\n ? { qualifiedName: left.qualifiedName }\n : { qualifiedName: left.qualifiedName, usr };\n}\n\n/** 플랫폼과 소스 위치로 실행 간 안정적인 노드 ID를 만든다. */\nfunction endpointId(endpoint: BridgeEndpoint): string {\n const { path, line, column } = endpoint.location;\n const suffix = endpoint.sourceLanguage === undefined ? '' : `:${endpoint.sourceLanguage}`;\n return `${endpoint.platform}:${path}:${line}:${column}${suffix}`;\n}\n\n/** 간선을 종류·from·to 순으로 고정한다. */\nfunction compareEdges(left: BridgeGraphEdge, right: BridgeGraphEdge): number {\n return (\n compareStrings(left.kind, right.kind) ||\n compareStrings(left.from, right.from) ||\n compareStrings(left.to, right.to)\n );\n}\n"]}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { BridgeFact, BridgeFactsDocument, BridgeTarget } from '../exchange/parse.ts';
|
|
2
|
+
import type { ImpactSelection } from '../exchange/impact-selection.ts';
|
|
3
|
+
import type { BridgeEndpoint, JoinLimitation, MatchedChannel, MatchedMethod } from '../join/join.ts';
|
|
4
|
+
import type { CheckIssue } from './check-report.ts';
|
|
5
|
+
import type { ImpactRuntimeInput, RuntimeImpactEvidence } from './runtime-impact.ts';
|
|
6
|
+
/** 선택된 원본 사실. 조인 불가 사실도 위치·식별자와 함께 보존한다. */
|
|
7
|
+
export interface SelectedBridgeFact extends BridgeEndpoint {
|
|
8
|
+
readonly target: BridgeTarget | null;
|
|
9
|
+
readonly kind: BridgeFact['kind'];
|
|
10
|
+
readonly channel: string | null;
|
|
11
|
+
readonly method?: string;
|
|
12
|
+
readonly dynamic: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** 채널 배선 변경은 같은 채널의 모든 메서드에 영향을 준다. */
|
|
15
|
+
export type ImpactReason = 'channel-wiring' | 'method' | 'runtime-observation';
|
|
16
|
+
/** 변경과 관련된 채널 생성·등록 위치다. */
|
|
17
|
+
export interface ImpactChannel extends MatchedChannel {
|
|
18
|
+
readonly reason: ImpactReason;
|
|
19
|
+
}
|
|
20
|
+
/** 변경과 관련된 메서드의 호출자·핸들러다. 실행 가능성을 판정하지 않는다. */
|
|
21
|
+
export interface ImpactMethod extends MatchedMethod {
|
|
22
|
+
readonly reason: ImpactReason;
|
|
23
|
+
}
|
|
24
|
+
/** 변경 전 확인할 브리지 관계와 공백을 기계적으로 소비하는 문서다. */
|
|
25
|
+
export interface BridgeImpactReport {
|
|
26
|
+
readonly format: 'isthmus-impact';
|
|
27
|
+
readonly version: 1;
|
|
28
|
+
/** 증거 상대 경로의 기준이다. CLI를 실행한 디렉터리와 같다고 추측하지 않는다. */
|
|
29
|
+
readonly project: string;
|
|
30
|
+
readonly status: 'observed' | 'unobserved';
|
|
31
|
+
readonly scope: 'bridge';
|
|
32
|
+
/** 전체 프로그램·런타임 도달성의 완전성을 주장하지 않는다. */
|
|
33
|
+
readonly complete: false;
|
|
34
|
+
readonly selection: ImpactSelection;
|
|
35
|
+
readonly unmatchedSelectors: ImpactSelection;
|
|
36
|
+
readonly summary: {
|
|
37
|
+
readonly observedFacts: number;
|
|
38
|
+
readonly selectedFacts: number;
|
|
39
|
+
readonly unresolvedSelectedFacts: number;
|
|
40
|
+
readonly unmatchedSelectors: number;
|
|
41
|
+
readonly affectedChannels: number;
|
|
42
|
+
readonly affectedMethods: number;
|
|
43
|
+
readonly reviewFiles: number;
|
|
44
|
+
readonly errors: number;
|
|
45
|
+
readonly warnings: number;
|
|
46
|
+
};
|
|
47
|
+
readonly selectedFacts: readonly SelectedBridgeFact[];
|
|
48
|
+
readonly channels: readonly ImpactChannel[];
|
|
49
|
+
readonly methods: readonly ImpactMethod[];
|
|
50
|
+
readonly reviewFiles: readonly string[];
|
|
51
|
+
readonly issues: readonly CheckIssue[];
|
|
52
|
+
readonly limitations: readonly JoinLimitation[];
|
|
53
|
+
readonly relevantLimitations: readonly JoinLimitation[];
|
|
54
|
+
readonly inputs: ReadonlyArray<Pick<BridgeFactsDocument, 'tool' | 'platform' | 'target' | 'generatedAt'>>;
|
|
55
|
+
readonly runtime?: RuntimeImpactEvidence;
|
|
56
|
+
}
|
|
57
|
+
/** 파일·심볼에서 관련 브리지 키를 역탐색하고 채널 배선 변경의 범위를 확장한다. */
|
|
58
|
+
export declare function createBridgeImpact(documents: readonly BridgeFactsDocument[], requested: ImpactSelection, runtimeInput?: ImpactRuntimeInput): BridgeImpactReport;
|
|
59
|
+
/** 0을 전체 안전 판정으로 해석하지 않고, 관련 오류·공백을 CI 실패 조건으로 계산한다. */
|
|
60
|
+
export declare function hasImpactBlockers(report: BridgeImpactReport): boolean;
|
|
61
|
+
/** 같은 문서를 사람이 읽는 JSON 또는 정보 손실 없는 컴팩트 JSON으로 인코딩한다. */
|
|
62
|
+
export declare function encodeBridgeImpact(report: BridgeImpactReport, compact?: boolean): string;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { compareStrings } from "../compare.js";
|
|
2
|
+
import { parseImpactSelection } from "../exchange/impact-selection.js";
|
|
3
|
+
import { BridgeJoinValidationError, joinBridgeDocuments } from "../join/join.js";
|
|
4
|
+
import { createCheckReport } from "./check-report.js";
|
|
5
|
+
import { encodeSortedJson } from "./sorted-json.js";
|
|
6
|
+
import { collectRuntimeImpact, hasRuntimeImpactGaps } from "./runtime-impact.js";
|
|
7
|
+
/** 파일·심볼에서 관련 브리지 키를 역탐색하고 채널 배선 변경의 범위를 확장한다. */
|
|
8
|
+
export function createBridgeImpact(documents, requested, runtimeInput) {
|
|
9
|
+
const selection = parseImpactSelection({ format: 'isthmus-changes', version: 1, ...requested });
|
|
10
|
+
const joined = joinBridgeDocuments(documents);
|
|
11
|
+
if (joined.deferred) {
|
|
12
|
+
throw new BridgeJoinValidationError('Bridge impact could not be computed; split mixed bridge targets and retry. '
|
|
13
|
+
+ `The inputs observed ${joined.observedFacts} facts across ${documents.length} documents.`);
|
|
14
|
+
}
|
|
15
|
+
const { selectedFacts, unmatchedSelectors } = selectFacts(documents, selection);
|
|
16
|
+
const channelKeys = new Set();
|
|
17
|
+
const wiringKeys = new Set();
|
|
18
|
+
const methodKeys = new Set();
|
|
19
|
+
let unresolvedSelectedFacts = 0;
|
|
20
|
+
for (const fact of selectedFacts) {
|
|
21
|
+
if (fact.dynamic || fact.channel === null || fact.target === null) {
|
|
22
|
+
unresolvedSelectedFacts++;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const key = channelKey(fact.target, fact.channel);
|
|
26
|
+
channelKeys.add(key);
|
|
27
|
+
if (fact.kind === 'channel-create' || fact.kind === 'channel-register') {
|
|
28
|
+
wiringKeys.add(key);
|
|
29
|
+
}
|
|
30
|
+
else if (fact.method !== undefined) {
|
|
31
|
+
methodKeys.add(methodKey(fact.target, fact.channel, fact.method));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const staticChannelKeys = new Set(channelKeys);
|
|
35
|
+
const runtime = runtimeInput === undefined ? undefined
|
|
36
|
+
: collectRuntimeImpact(joined, documents[0]?.project, selection, selectedFacts, runtimeInput, new Set(documents.map(({ platform }) => platform)));
|
|
37
|
+
const runtimeNative = runtimeInput?.document.run.platform === 'android' ? 'kotlin' : 'swift';
|
|
38
|
+
const runtimeMethodKeys = new Set();
|
|
39
|
+
for (const route of runtime?.routes ?? []) {
|
|
40
|
+
if (route.staticStatus !== 'candidates' || route.method === undefined)
|
|
41
|
+
continue;
|
|
42
|
+
channelKeys.add(channelKey('flutter', route.channel));
|
|
43
|
+
runtimeMethodKeys.add(methodKey('flutter', route.channel, route.method));
|
|
44
|
+
}
|
|
45
|
+
const methods = [
|
|
46
|
+
...joined.matchedMethods,
|
|
47
|
+
...joined.unhandledInvocations.map((item) => ({ ...item, handlers: [] })),
|
|
48
|
+
...joined.handlersWithoutInvocations.map((item) => ({ ...item, invocations: [] })),
|
|
49
|
+
].filter(({ target, channel, method }) => wiringKeys.has(channelKey(target, channel)) ||
|
|
50
|
+
methodKeys.has(methodKey(target, channel, method)) || runtimeMethodKeys.has(methodKey(target, channel, method)))
|
|
51
|
+
.map((item) => {
|
|
52
|
+
const reason = impactReason(wiringKeys, item, !methodKeys.has(methodKey(item.target, item.channel, item.method)));
|
|
53
|
+
return { ...item, reason, handlers: reason === 'runtime-observation'
|
|
54
|
+
? item.handlers.filter(({ platform }) => platform === runtimeNative) : item.handlers };
|
|
55
|
+
})
|
|
56
|
+
.sort(compareLogicalKeys);
|
|
57
|
+
const channelMap = new Map([
|
|
58
|
+
...joined.matchedChannels,
|
|
59
|
+
...joined.unregisteredChannelCreations.map((item) => ({ ...item, registrations: [] })),
|
|
60
|
+
...joined.registrationsWithoutCreations.map((item) => ({ ...item, creations: [] })),
|
|
61
|
+
].map((item) => [channelKey(item.target, item.channel), item]));
|
|
62
|
+
// 생성·등록을 놓친 채널에서도 메서드의 존재 근거가 사라지지 않아야 한다.
|
|
63
|
+
for (const { target, channel } of methods) {
|
|
64
|
+
const key = channelKey(target, channel);
|
|
65
|
+
if (!channelMap.has(key))
|
|
66
|
+
channelMap.set(key, { target, channel, creations: [], registrations: [] });
|
|
67
|
+
}
|
|
68
|
+
const channels = [...channelMap.values()]
|
|
69
|
+
.filter(({ target, channel }) => channelKeys.has(channelKey(target, channel)))
|
|
70
|
+
.map((item) => {
|
|
71
|
+
const reason = impactReason(wiringKeys, item, !staticChannelKeys.has(channelKey(item.target, item.channel)));
|
|
72
|
+
return { ...item, reason, registrations: reason === 'runtime-observation'
|
|
73
|
+
? item.registrations.filter(({ platform }) => platform === runtimeNative) : item.registrations };
|
|
74
|
+
})
|
|
75
|
+
.sort(compareLogicalKeys);
|
|
76
|
+
const issues = createCheckReport(joined).issues.filter(({ target, channel, method }) => channelKeys.has(channelKey(target, channel)) &&
|
|
77
|
+
(method === undefined || wiringKeys.has(channelKey(target, channel)) ||
|
|
78
|
+
methodKeys.has(methodKey(target, channel, method)) || runtimeMethodKeys.has(methodKey(target, channel, method))));
|
|
79
|
+
const reviewFiles = [...new Set([
|
|
80
|
+
...selectedFacts,
|
|
81
|
+
...channels.flatMap(({ creations, registrations }) => [...creations, ...registrations]),
|
|
82
|
+
...methods.flatMap(({ invocations, handlers }) => [...invocations, ...handlers]),
|
|
83
|
+
...(runtime?.reviewFiles ?? []).map((path) => ({ location: { path } })),
|
|
84
|
+
].map(({ location }) => location.path))].sort(compareStrings);
|
|
85
|
+
const unmatchedCount = unmatchedSelectors.files.length + unmatchedSelectors.symbols.length;
|
|
86
|
+
const relevantLimitations = joined.limitations.filter((limitation) => unresolvedSelectedFacts > 0 || unmatchedCount > 0 || channels.some(({ target, channel }) => (limitation.target === null || limitation.target === target) &&
|
|
87
|
+
(limitation.channels === undefined || limitation.channels.includes(channel))));
|
|
88
|
+
return {
|
|
89
|
+
// 조인이 호출·수신 문서의 존재와 동일 project를 이미 검증했다.
|
|
90
|
+
format: 'isthmus-impact', version: 1, project: documents[0].project,
|
|
91
|
+
status: selectedFacts.length > 0 ? 'observed' : 'unobserved', scope: 'bridge', complete: false,
|
|
92
|
+
selection, unmatchedSelectors,
|
|
93
|
+
summary: {
|
|
94
|
+
observedFacts: joined.observedFacts, selectedFacts: selectedFacts.length,
|
|
95
|
+
unresolvedSelectedFacts, unmatchedSelectors: unmatchedCount,
|
|
96
|
+
affectedChannels: channels.length, affectedMethods: methods.length,
|
|
97
|
+
reviewFiles: reviewFiles.length,
|
|
98
|
+
errors: issues.filter(({ severity }) => severity === 'error').length,
|
|
99
|
+
warnings: issues.filter(({ severity }) => severity === 'warning').length,
|
|
100
|
+
},
|
|
101
|
+
selectedFacts, channels, methods, reviewFiles, issues,
|
|
102
|
+
...(runtime === undefined ? {} : { runtime }),
|
|
103
|
+
limitations: joined.limitations, relevantLimitations,
|
|
104
|
+
inputs: documents.map(({ tool, platform, target, generatedAt }) => ({
|
|
105
|
+
tool, platform, target, generatedAt,
|
|
106
|
+
})).sort((a, b) => compareStrings(encodeSortedJson(a), encodeSortedJson(b))),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/** 0을 전체 안전 판정으로 해석하지 않고, 관련 오류·공백을 CI 실패 조건으로 계산한다. */
|
|
110
|
+
export function hasImpactBlockers(report) {
|
|
111
|
+
return report.summary.errors > 0 || report.summary.unresolvedSelectedFacts > 0 ||
|
|
112
|
+
report.summary.unmatchedSelectors > 0 || report.relevantLimitations.length > 0 ||
|
|
113
|
+
report.issues.some(({ code }) => code.endsWith('-unverified')) ||
|
|
114
|
+
(report.runtime !== undefined && hasRuntimeImpactGaps(report.runtime));
|
|
115
|
+
}
|
|
116
|
+
/** 같은 문서를 사람이 읽는 JSON 또는 정보 손실 없는 컴팩트 JSON으로 인코딩한다. */
|
|
117
|
+
export function encodeBridgeImpact(report, compact = false) {
|
|
118
|
+
return encodeSortedJson(report, compact);
|
|
119
|
+
}
|
|
120
|
+
/** 선택 집합과 사실을 한 번씩 읽어 선택 수×사실 수의 곱을 피한다. */
|
|
121
|
+
function selectFacts(documents, selection) {
|
|
122
|
+
const files = new Set(selection.files);
|
|
123
|
+
const symbols = new Set(selection.symbols);
|
|
124
|
+
const foundFiles = new Set();
|
|
125
|
+
const foundSymbols = new Set();
|
|
126
|
+
const selected = new Map();
|
|
127
|
+
for (const { platform, target, facts } of documents) {
|
|
128
|
+
for (const fact of facts) {
|
|
129
|
+
const fileMatches = files.has(fact.location.path);
|
|
130
|
+
const names = fact.symbol === undefined ? [] : [fact.symbol.qualifiedName, fact.symbol.usr];
|
|
131
|
+
const matchedNames = names.filter((name) => name !== undefined && symbols.has(name));
|
|
132
|
+
if (!fileMatches && matchedNames.length === 0)
|
|
133
|
+
continue;
|
|
134
|
+
if (fileMatches)
|
|
135
|
+
foundFiles.add(fact.location.path);
|
|
136
|
+
for (const name of matchedNames)
|
|
137
|
+
foundSymbols.add(name);
|
|
138
|
+
const item = { ...fact, platform, target };
|
|
139
|
+
selected.set(encodeSortedJson(item), item);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
selectedFacts: [...selected.entries()].sort(([a], [b]) => compareStrings(a, b)).map(([, item]) => item),
|
|
144
|
+
unmatchedSelectors: {
|
|
145
|
+
files: selection.files.filter((path) => !foundFiles.has(path)),
|
|
146
|
+
symbols: selection.symbols.filter((name) => !foundSymbols.has(name)),
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/** 구분 문자 포함 이름도 서로 충돌하지 않는 채널 키다. */
|
|
151
|
+
function channelKey(target, channel) {
|
|
152
|
+
return JSON.stringify([target, channel]);
|
|
153
|
+
}
|
|
154
|
+
/** 채널 내부에서만 유일한 메서드 키다. */
|
|
155
|
+
function methodKey(target, channel, method) {
|
|
156
|
+
return JSON.stringify([target, channel, method]);
|
|
157
|
+
}
|
|
158
|
+
/** 생성·등록이 선택됐는지에 따라 검토 범위가 넓어진 이유를 설명한다. */
|
|
159
|
+
function impactReason(wiringKeys, item, runtimeOnly = false) {
|
|
160
|
+
return wiringKeys.has(channelKey(item.target, item.channel)) ? 'channel-wiring'
|
|
161
|
+
: runtimeOnly ? 'runtime-observation' : 'method';
|
|
162
|
+
}
|
|
163
|
+
/** 입력 파일 순서와 locale에 의존하지 않는 논리 키 정렬이다. */
|
|
164
|
+
function compareLogicalKeys(a, b) {
|
|
165
|
+
return compareStrings(a.target, b.target) || compareStrings(a.channel, b.channel) ||
|
|
166
|
+
compareStrings(a.method ?? '', b.method ?? '');
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=impact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"impact.js","sourceRoot":"","sources":["../../src/report/impact.ts"],"names":[],"mappings":"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","sourcesContent":["import { compareStrings } from '../compare.ts';\nimport type { BridgeFact, BridgeFactsDocument, BridgeTarget } from '../exchange/parse.ts';\nimport type { ImpactSelection } from '../exchange/impact-selection.ts';\nimport { parseImpactSelection } from '../exchange/impact-selection.ts';\nimport { BridgeJoinValidationError, joinBridgeDocuments } from '../join/join.ts';\nimport type { BridgeEndpoint, JoinLimitation, MatchedChannel, MatchedMethod } from '../join/join.ts';\nimport { createCheckReport } from './check-report.ts';\nimport type { CheckIssue } from './check-report.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\nimport { collectRuntimeImpact, hasRuntimeImpactGaps } from './runtime-impact.ts';\nimport type { ImpactRuntimeInput, RuntimeImpactEvidence } from './runtime-impact.ts';\n\n/** 선택된 원본 사실. 조인 불가 사실도 위치·식별자와 함께 보존한다. */\nexport interface SelectedBridgeFact extends BridgeEndpoint {\n readonly target: BridgeTarget | null;\n readonly kind: BridgeFact['kind'];\n readonly channel: string | null;\n readonly method?: string;\n readonly dynamic: boolean;\n}\n\n/** 채널 배선 변경은 같은 채널의 모든 메서드에 영향을 준다. */\nexport type ImpactReason = 'channel-wiring' | 'method' | 'runtime-observation';\n\n/** 변경과 관련된 채널 생성·등록 위치다. */\nexport interface ImpactChannel extends MatchedChannel {\n readonly reason: ImpactReason;\n}\n\n/** 변경과 관련된 메서드의 호출자·핸들러다. 실행 가능성을 판정하지 않는다. */\nexport interface ImpactMethod extends MatchedMethod {\n readonly reason: ImpactReason;\n}\n\n/** 변경 전 확인할 브리지 관계와 공백을 기계적으로 소비하는 문서다. */\nexport interface BridgeImpactReport {\n readonly format: 'isthmus-impact';\n readonly version: 1;\n /** 증거 상대 경로의 기준이다. CLI를 실행한 디렉터리와 같다고 추측하지 않는다. */\n readonly project: string;\n readonly status: 'observed' | 'unobserved';\n readonly scope: 'bridge';\n /** 전체 프로그램·런타임 도달성의 완전성을 주장하지 않는다. */\n readonly complete: false;\n readonly selection: ImpactSelection;\n readonly unmatchedSelectors: ImpactSelection;\n readonly summary: {\n readonly observedFacts: number;\n readonly selectedFacts: number;\n readonly unresolvedSelectedFacts: number;\n readonly unmatchedSelectors: number;\n readonly affectedChannels: number;\n readonly affectedMethods: number;\n readonly reviewFiles: number;\n readonly errors: number;\n readonly warnings: number;\n };\n readonly selectedFacts: readonly SelectedBridgeFact[];\n readonly channels: readonly ImpactChannel[];\n readonly methods: readonly ImpactMethod[];\n readonly reviewFiles: readonly string[];\n readonly issues: readonly CheckIssue[];\n readonly limitations: readonly JoinLimitation[];\n readonly relevantLimitations: readonly JoinLimitation[];\n readonly inputs: ReadonlyArray<Pick<BridgeFactsDocument, 'tool' | 'platform' | 'target' | 'generatedAt'>>;\n readonly runtime?: RuntimeImpactEvidence;\n}\n\n/** 파일·심볼에서 관련 브리지 키를 역탐색하고 채널 배선 변경의 범위를 확장한다. */\nexport function createBridgeImpact(\n documents: readonly BridgeFactsDocument[],\n requested: ImpactSelection,\n runtimeInput?: ImpactRuntimeInput,\n): BridgeImpactReport {\n const selection = parseImpactSelection({ format: 'isthmus-changes', version: 1, ...requested });\n const joined = joinBridgeDocuments(documents);\n if (joined.deferred) {\n throw new BridgeJoinValidationError(\n 'Bridge impact could not be computed; split mixed bridge targets and retry. '\n + `The inputs observed ${joined.observedFacts} facts across ${documents.length} documents.`,\n );\n }\n const { selectedFacts, unmatchedSelectors } = selectFacts(documents, selection);\n const channelKeys = new Set<string>();\n const wiringKeys = new Set<string>();\n const methodKeys = new Set<string>();\n let unresolvedSelectedFacts = 0;\n for (const fact of selectedFacts) {\n if (fact.dynamic || fact.channel === null || fact.target === null) {\n unresolvedSelectedFacts++;\n continue;\n }\n const key = channelKey(fact.target, fact.channel);\n channelKeys.add(key);\n if (fact.kind === 'channel-create' || fact.kind === 'channel-register') {\n wiringKeys.add(key);\n } else if (fact.method !== undefined) {\n methodKeys.add(methodKey(fact.target, fact.channel, fact.method));\n }\n }\n const staticChannelKeys = new Set(channelKeys);\n const runtime = runtimeInput === undefined ? undefined\n : collectRuntimeImpact(joined, documents[0]?.project, selection, selectedFacts, runtimeInput,\n new Set(documents.map(({ platform }) => platform)));\n const runtimeNative = runtimeInput?.document.run.platform === 'android' ? 'kotlin' : 'swift';\n const runtimeMethodKeys = new Set<string>();\n for (const route of runtime?.routes ?? []) {\n if (route.staticStatus !== 'candidates' || route.method === undefined) continue;\n channelKeys.add(channelKey('flutter', route.channel));\n runtimeMethodKeys.add(methodKey('flutter', route.channel, route.method));\n }\n const methods: ImpactMethod[] = [\n ...joined.matchedMethods,\n ...joined.unhandledInvocations.map((item) => ({ ...item, handlers: [] })),\n ...joined.handlersWithoutInvocations.map((item) => ({ ...item, invocations: [] })),\n ].filter(({ target, channel, method }) => wiringKeys.has(channelKey(target, channel)) ||\n methodKeys.has(methodKey(target, channel, method)) || runtimeMethodKeys.has(methodKey(target, channel, method)))\n .map((item) => {\n const reason = impactReason(wiringKeys, item, !methodKeys.has(methodKey(item.target, item.channel, item.method)));\n return { ...item, reason, handlers: reason === 'runtime-observation'\n ? item.handlers.filter(({ platform }) => platform === runtimeNative) : item.handlers };\n })\n .sort(compareLogicalKeys);\n const channelMap = new Map<string, MatchedChannel>([\n ...joined.matchedChannels,\n ...joined.unregisteredChannelCreations.map((item) => ({ ...item, registrations: [] })),\n ...joined.registrationsWithoutCreations.map((item) => ({ ...item, creations: [] })),\n ].map((item) => [channelKey(item.target, item.channel), item]));\n // 생성·등록을 놓친 채널에서도 메서드의 존재 근거가 사라지지 않아야 한다.\n for (const { target, channel } of methods) {\n const key = channelKey(target, channel);\n if (!channelMap.has(key)) channelMap.set(key, { target, channel, creations: [], registrations: [] });\n }\n const channels = [...channelMap.values()]\n .filter(({ target, channel }) => channelKeys.has(channelKey(target, channel)))\n .map((item) => {\n const reason = impactReason(wiringKeys, item, !staticChannelKeys.has(channelKey(item.target, item.channel)));\n return { ...item, reason, registrations: reason === 'runtime-observation'\n ? item.registrations.filter(({ platform }) => platform === runtimeNative) : item.registrations };\n })\n .sort(compareLogicalKeys);\n const issues = createCheckReport(joined).issues.filter(({ target, channel, method }) =>\n channelKeys.has(channelKey(target, channel)) &&\n (method === undefined || wiringKeys.has(channelKey(target, channel)) ||\n methodKeys.has(methodKey(target, channel, method)) || runtimeMethodKeys.has(methodKey(target, channel, method))));\n const reviewFiles = [...new Set([\n ...selectedFacts,\n ...channels.flatMap(({ creations, registrations }) => [...creations, ...registrations]),\n ...methods.flatMap(({ invocations, handlers }) => [...invocations, ...handlers]),\n ...(runtime?.reviewFiles ?? []).map((path) => ({ location: { path } })),\n ].map(({ location }) => location.path))].sort(compareStrings);\n const unmatchedCount = unmatchedSelectors.files.length + unmatchedSelectors.symbols.length;\n const relevantLimitations = joined.limitations.filter((limitation) =>\n unresolvedSelectedFacts > 0 || unmatchedCount > 0 || channels.some(({ target, channel }) =>\n (limitation.target === null || limitation.target === target) &&\n (limitation.channels === undefined || limitation.channels.includes(channel))));\n return {\n // 조인이 호출·수신 문서의 존재와 동일 project를 이미 검증했다.\n format: 'isthmus-impact', version: 1, project: documents[0]!.project,\n status: selectedFacts.length > 0 ? 'observed' : 'unobserved', scope: 'bridge', complete: false,\n selection, unmatchedSelectors,\n summary: {\n observedFacts: joined.observedFacts, selectedFacts: selectedFacts.length,\n unresolvedSelectedFacts, unmatchedSelectors: unmatchedCount,\n affectedChannels: channels.length, affectedMethods: methods.length,\n reviewFiles: reviewFiles.length,\n errors: issues.filter(({ severity }) => severity === 'error').length,\n warnings: issues.filter(({ severity }) => severity === 'warning').length,\n },\n selectedFacts, channels, methods, reviewFiles, issues,\n ...(runtime === undefined ? {} : { runtime }),\n limitations: joined.limitations, relevantLimitations,\n inputs: documents.map(({ tool, platform, target, generatedAt }) => ({\n tool, platform, target, generatedAt,\n })).sort((a, b) => compareStrings(encodeSortedJson(a), encodeSortedJson(b))),\n };\n}\n\n/** 0을 전체 안전 판정으로 해석하지 않고, 관련 오류·공백을 CI 실패 조건으로 계산한다. */\nexport function hasImpactBlockers(report: BridgeImpactReport): boolean {\n return report.summary.errors > 0 || report.summary.unresolvedSelectedFacts > 0 ||\n report.summary.unmatchedSelectors > 0 || report.relevantLimitations.length > 0 ||\n report.issues.some(({ code }) => code.endsWith('-unverified')) ||\n (report.runtime !== undefined && hasRuntimeImpactGaps(report.runtime));\n}\n\n/** 같은 문서를 사람이 읽는 JSON 또는 정보 손실 없는 컴팩트 JSON으로 인코딩한다. */\nexport function encodeBridgeImpact(report: BridgeImpactReport, compact = false): string {\n return encodeSortedJson(report, compact);\n}\n\n/** 선택 집합과 사실을 한 번씩 읽어 선택 수×사실 수의 곱을 피한다. */\nfunction selectFacts(documents: readonly BridgeFactsDocument[], selection: ImpactSelection): {\n selectedFacts: SelectedBridgeFact[]; unmatchedSelectors: ImpactSelection;\n} {\n const files = new Set(selection.files);\n const symbols = new Set(selection.symbols);\n const foundFiles = new Set<string>();\n const foundSymbols = new Set<string>();\n const selected = new Map<string, SelectedBridgeFact>();\n for (const { platform, target, facts } of documents) {\n for (const fact of facts) {\n const fileMatches = files.has(fact.location.path);\n const names = fact.symbol === undefined ? [] : [fact.symbol.qualifiedName, fact.symbol.usr];\n const matchedNames = names.filter((name): name is string => name !== undefined && symbols.has(name));\n if (!fileMatches && matchedNames.length === 0) continue;\n if (fileMatches) foundFiles.add(fact.location.path);\n for (const name of matchedNames) foundSymbols.add(name);\n const item = { ...fact, platform, target };\n selected.set(encodeSortedJson(item), item);\n }\n }\n return {\n selectedFacts: [...selected.entries()].sort(([a], [b]) => compareStrings(a, b)).map(([, item]) => item),\n unmatchedSelectors: {\n files: selection.files.filter((path) => !foundFiles.has(path)),\n symbols: selection.symbols.filter((name) => !foundSymbols.has(name)),\n },\n };\n}\n\n/** 구분 문자 포함 이름도 서로 충돌하지 않는 채널 키다. */\nfunction channelKey(target: BridgeTarget, channel: string): string {\n return JSON.stringify([target, channel]);\n}\n\n/** 채널 내부에서만 유일한 메서드 키다. */\nfunction methodKey(target: BridgeTarget, channel: string, method: string): string {\n return JSON.stringify([target, channel, method]);\n}\n\n/** 생성·등록이 선택됐는지에 따라 검토 범위가 넓어진 이유를 설명한다. */\nfunction impactReason(\n wiringKeys: ReadonlySet<string>, item: MatchedChannel | MatchedMethod, runtimeOnly = false,\n): ImpactReason {\n return wiringKeys.has(channelKey(item.target, item.channel)) ? 'channel-wiring'\n : runtimeOnly ? 'runtime-observation' : 'method';\n}\n\n/** 입력 파일 순서와 locale에 의존하지 않는 논리 키 정렬이다. */\nfunction compareLogicalKeys(\n a: { target: BridgeTarget; channel: string; method?: string },\n b: { target: BridgeTarget; channel: string; method?: string },\n): number {\n return compareStrings(a.target, b.target) || compareStrings(a.channel, b.channel) ||\n compareStrings(a.method ?? '', b.method ?? '');\n}\n"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { BridgeLocation } from '../exchange/parse.ts';
|
|
2
|
+
import type { PreflightContext } from '../exchange/preflight-context.ts';
|
|
3
|
+
import type { BridgeRuntimeDocument, RuntimeExpectations, RuntimeOutcome, RuntimePlatform, RuntimeRoute } from '../exchange/runtime.ts';
|
|
4
|
+
import type { BridgeEndpoint } from '../join/join.ts';
|
|
5
|
+
import type { PreflightReport } from './preflight.ts';
|
|
6
|
+
import type { RuntimeVerificationReport } from './runtime.ts';
|
|
7
|
+
/** 실제 주소 관찰과 정적 후보를 구분하며 기록의 run·instance를 보존한다. */
|
|
8
|
+
export interface PreflightRuntimeRoute extends RuntimeRoute {
|
|
9
|
+
readonly runId: string;
|
|
10
|
+
readonly scenario: string;
|
|
11
|
+
readonly platform: RuntimePlatform;
|
|
12
|
+
readonly instance: string;
|
|
13
|
+
readonly observedCalls: number;
|
|
14
|
+
readonly outcomes: Readonly<Record<RuntimeOutcome, number>>;
|
|
15
|
+
readonly callers: readonly BridgeLocation[];
|
|
16
|
+
readonly callersOmitted: number;
|
|
17
|
+
readonly selectionReasons: readonly ('route' | 'caller-file')[];
|
|
18
|
+
readonly staticStatus: 'candidates' | 'unobserved' | 'unsupported';
|
|
19
|
+
readonly boundaryKeys: readonly string[];
|
|
20
|
+
readonly candidateKey?: string;
|
|
21
|
+
}
|
|
22
|
+
/** 같은 주소의 후보는 runtime 인스턴스 수와 무관하게 한 번만 싣는다. */
|
|
23
|
+
export interface PreflightRuntimeCandidates {
|
|
24
|
+
readonly key: string;
|
|
25
|
+
readonly channel: string;
|
|
26
|
+
readonly method?: string;
|
|
27
|
+
readonly transport?: 'basic-message-channel';
|
|
28
|
+
readonly matching?: 'literal' | 'prefix';
|
|
29
|
+
readonly handlers: readonly BridgeEndpoint[];
|
|
30
|
+
readonly handlersOmitted: number;
|
|
31
|
+
}
|
|
32
|
+
/** 선언된 시나리오의 검증과 선택한 정적 경계의 실행 공백을 별도로 제공한다. */
|
|
33
|
+
export interface PreflightRuntimeReport {
|
|
34
|
+
readonly aligned: boolean;
|
|
35
|
+
readonly verification: RuntimeVerificationReport;
|
|
36
|
+
readonly routes: readonly PreflightRuntimeRoute[];
|
|
37
|
+
readonly candidates: readonly PreflightRuntimeCandidates[];
|
|
38
|
+
readonly unobservedBoundaries: readonly string[];
|
|
39
|
+
readonly uncoveredBoundaries: readonly string[];
|
|
40
|
+
}
|
|
41
|
+
/** runtime은 원래 정적 간선을 바꾸지 않고 독립 검증과 후보 근거만 추가한다. */
|
|
42
|
+
export declare function attachPreflightRuntime(context: PreflightContext, report: PreflightReport, expectations: RuntimeExpectations, documents: readonly BridgeRuntimeDocument[]): PreflightReport;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { compareStrings } from "../compare.js";
|
|
2
|
+
import { RuntimeValidationError } from "../exchange/runtime.js";
|
|
3
|
+
import { MessageAddressIndex } from "../join/message-address.js";
|
|
4
|
+
import { verifyRuntimeEvidence } from "./runtime.js";
|
|
5
|
+
/** runtime은 원래 정적 간선을 바꾸지 않고 독립 검증과 후보 근거만 추가한다. */
|
|
6
|
+
export function attachPreflightRuntime(context, report, expectations, documents) {
|
|
7
|
+
if (expectations.project !== context.project || report.project !== context.project || report.revision !== context.revision) {
|
|
8
|
+
throw new RuntimeValidationError('Preflight and runtime expectations must describe the same project and capture.');
|
|
9
|
+
}
|
|
10
|
+
const verification = verifyRuntimeEvidence(expectations, documents);
|
|
11
|
+
const aligned = expectations.revision === context.revision;
|
|
12
|
+
const files = new Set(report.reviewFiles);
|
|
13
|
+
const addedFiles = new Set();
|
|
14
|
+
const byRoute = new Map();
|
|
15
|
+
const nativePlatforms = new Set([...context.bridges, ...(context.messages ?? [])]
|
|
16
|
+
.flatMap(({ platform }) => platform === 'swift' || platform === 'kotlin' ? [platform] : []));
|
|
17
|
+
const messagePlatforms = new Set((context.messages ?? []).map(({ platform }) => platform));
|
|
18
|
+
const boundaryPlatforms = new Map(report.boundaries.map(({ subject, receivers }) => {
|
|
19
|
+
const platforms = new Set(receivers.flatMap(({ platform }) => platform === 'swift' || platform === 'kotlin' ? [platform] : []));
|
|
20
|
+
return [subject.key, platforms.size > 0 ? platforms : nativePlatforms];
|
|
21
|
+
}));
|
|
22
|
+
const messageBoundaries = new MessageAddressIndex();
|
|
23
|
+
const methodChannels = new Set();
|
|
24
|
+
for (const { subject } of report.boundaries) {
|
|
25
|
+
if (subject.target !== 'flutter')
|
|
26
|
+
continue;
|
|
27
|
+
if (subject.transport === 'basic-message-channel') {
|
|
28
|
+
messageBoundaries.add(subject.channel, subject.matching ?? 'literal', subject.key);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const key = routeKey(subject.channel, subject.method);
|
|
32
|
+
const keys = byRoute.get(key) ?? [];
|
|
33
|
+
keys.push(subject.key);
|
|
34
|
+
byRoute.set(key, keys);
|
|
35
|
+
if (subject.method !== undefined)
|
|
36
|
+
methodChannels.add(subject.channel);
|
|
37
|
+
}
|
|
38
|
+
const required = report.boundaries.filter(({ subject }) => subject.target === 'flutter' &&
|
|
39
|
+
(subject.transport === 'basic-message-channel' || subject.method !== undefined || !methodChannels.has(subject.channel)));
|
|
40
|
+
const boundaryKeys = (channel, method, transport, platform) => (transport === 'basic-message-channel'
|
|
41
|
+
? messageBoundaries.matching(channel)
|
|
42
|
+
: [...(byRoute.get(routeKey(channel, method)) ?? []), ...(byRoute.get(routeKey(channel, undefined)) ?? [])])
|
|
43
|
+
.filter((key) => platform !== undefined && boundaryPlatforms.get(key)?.has(platform));
|
|
44
|
+
const pair = (key, platform) => JSON.stringify([key, platform]);
|
|
45
|
+
const declared = new Set(expectations.checks.flatMap((check) => {
|
|
46
|
+
const platform = nativeLanguage(check.platform);
|
|
47
|
+
return boundaryKeys(check.channel, check.method, check.transport, platform).map((key) => pair(key, platform));
|
|
48
|
+
}));
|
|
49
|
+
const observed = new Set();
|
|
50
|
+
const handlers = new Map();
|
|
51
|
+
for (const document of context.bridges) {
|
|
52
|
+
if ((document.platform !== 'swift' && document.platform !== 'kotlin') || document.target !== 'flutter')
|
|
53
|
+
continue;
|
|
54
|
+
for (const fact of document.facts) {
|
|
55
|
+
if (fact.kind !== 'method-handle' || fact.dynamic || fact.channel === null || fact.method === undefined)
|
|
56
|
+
continue;
|
|
57
|
+
const key = candidateKey(document.platform, fact.channel, fact.method);
|
|
58
|
+
let group = handlers.get(key);
|
|
59
|
+
if (group === undefined) {
|
|
60
|
+
group = { channel: fact.channel, method: fact.method, endpoints: new Map() };
|
|
61
|
+
handlers.set(key, group);
|
|
62
|
+
}
|
|
63
|
+
group.endpoints.set(JSON.stringify([fact.location.path, fact.location.line, fact.location.column,
|
|
64
|
+
fact.symbol?.usr ?? null, fact.symbol?.qualifiedName ?? null, fact.sourceLanguage ?? null]), {
|
|
65
|
+
platform: document.platform, location: fact.location,
|
|
66
|
+
...(fact.symbol === undefined ? {} : { symbol: fact.symbol }),
|
|
67
|
+
...(fact.sourceLanguage === undefined ? {} : { sourceLanguage: fact.sourceLanguage }),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const candidateGroups = new Map();
|
|
72
|
+
const nativeMessages = new MessageAddressIndex();
|
|
73
|
+
for (const document of context.messages ?? []) {
|
|
74
|
+
if (document.platform !== 'swift' && document.platform !== 'kotlin')
|
|
75
|
+
continue;
|
|
76
|
+
for (const fact of document.facts) {
|
|
77
|
+
const address = fact.dynamic ? fact.channelPrefix : fact.channel;
|
|
78
|
+
if (!address)
|
|
79
|
+
continue;
|
|
80
|
+
nativeMessages.add(address, fact.dynamic ? 'prefix' : 'literal', { matching: fact.dynamic ? 'prefix' : 'literal',
|
|
81
|
+
endpoint: { platform: document.platform, location: fact.location, ...(fact.symbol === undefined ? {} : { symbol: fact.symbol }),
|
|
82
|
+
...(fact.sourceLanguage === undefined ? {} : { sourceLanguage: fact.sourceLanguage }),
|
|
83
|
+
...(fact.dynamic ? { channelExpression: fact.channel, channelPrefix: fact.channelPrefix } : {}) } });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
let messageCandidateWork = 0;
|
|
87
|
+
const groups = new Map();
|
|
88
|
+
for (const document of [...documents].sort((a, b) => compareStrings(a.run.id, b.run.id))) {
|
|
89
|
+
// 기대 문서가 이전 revision이어도 현재 정적 분석과 같은 실행만 경계 근거에 연결한다.
|
|
90
|
+
if (document.revision !== context.revision)
|
|
91
|
+
continue;
|
|
92
|
+
for (const event of document.events) {
|
|
93
|
+
const platform = nativeLanguage(document.run.platform);
|
|
94
|
+
const supported = platform !== undefined && nativePlatforms.has(platform)
|
|
95
|
+
&& (event.transport === 'method-channel' || messagePlatforms.has(platform));
|
|
96
|
+
const route = candidateKey(platform, event.channel, event.method, event.transport);
|
|
97
|
+
const keys = supported ? boundaryKeys(event.channel, event.method, event.transport, platform) : [];
|
|
98
|
+
const fromFile = event.caller !== undefined && files.has(event.caller.path);
|
|
99
|
+
if (keys.length === 0 && !fromFile)
|
|
100
|
+
continue;
|
|
101
|
+
for (const key of keys)
|
|
102
|
+
observed.add(pair(key, platform));
|
|
103
|
+
const candidates = supported && event.transport === 'method-channel' ? handlers.get(route) : undefined;
|
|
104
|
+
if (candidates !== undefined && !candidateGroups.has(route)) {
|
|
105
|
+
const endpoints = [...candidates.endpoints.entries()].sort(([a], [b]) => compareStrings(a, b)).map(([, value]) => value);
|
|
106
|
+
for (const endpoint of endpoints)
|
|
107
|
+
addedFiles.add(endpoint.location.path);
|
|
108
|
+
candidateGroups.set(route, { key: route, channel: candidates.channel, method: candidates.method,
|
|
109
|
+
handlers: endpoints.slice(0, 20), handlersOmitted: Math.max(0, endpoints.length - 20) });
|
|
110
|
+
}
|
|
111
|
+
if (supported && event.transport === 'basic-message-channel' && !candidateGroups.has(route)) {
|
|
112
|
+
const addressMatches = nativeMessages.matching(event.channel);
|
|
113
|
+
messageCandidateWork += addressMatches.length;
|
|
114
|
+
if (messageCandidateWork > 1_000_000)
|
|
115
|
+
throw new RuntimeValidationError('Message runtime candidate budget exceeded.');
|
|
116
|
+
const matches = addressMatches.filter(({ endpoint }) => endpoint.platform === platform);
|
|
117
|
+
if (matches.length > 0) {
|
|
118
|
+
const unique = [...new Map(matches.map(({ endpoint }) => [JSON.stringify(endpoint), endpoint])).entries()]
|
|
119
|
+
.sort(([a], [b]) => compareStrings(a, b)).map(([, endpoint]) => endpoint);
|
|
120
|
+
for (const endpoint of unique)
|
|
121
|
+
addedFiles.add(endpoint.location.path);
|
|
122
|
+
candidateGroups.set(route, { key: route, channel: event.channel, transport: 'basic-message-channel',
|
|
123
|
+
matching: matches.some(({ matching }) => matching === 'prefix') ? 'prefix' : 'literal',
|
|
124
|
+
handlers: unique.slice(0, 20), handlersOmitted: Math.max(0, unique.length - 20) });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const hasCandidates = supported && candidateGroups.has(route);
|
|
128
|
+
const key = JSON.stringify([document.run.id, event.transport, event.channel, event.method ?? null, event.instance]);
|
|
129
|
+
let group = groups.get(key);
|
|
130
|
+
if (group === undefined) {
|
|
131
|
+
group = { route: {
|
|
132
|
+
runId: document.run.id, scenario: document.run.scenario, platform: document.run.platform,
|
|
133
|
+
transport: event.transport, channel: event.channel, ...(event.method === undefined ? {} : { method: event.method }),
|
|
134
|
+
instance: event.instance, staticStatus: !supported ? 'unsupported' : hasCandidates ? 'candidates' : 'unobserved',
|
|
135
|
+
boundaryKeys: [...new Set(keys)].sort(compareStrings),
|
|
136
|
+
...(!hasCandidates ? {} : { candidateKey: route }),
|
|
137
|
+
}, count: 0, outcomes: { success: 0, error: 0, timeout: 0, pending: 0, 'missing-handler': 0 },
|
|
138
|
+
callers: new Map(), reasons: new Set() };
|
|
139
|
+
groups.set(key, group);
|
|
140
|
+
}
|
|
141
|
+
group.count++;
|
|
142
|
+
group.outcomes[event.outcome]++;
|
|
143
|
+
if (keys.length > 0)
|
|
144
|
+
group.reasons.add('route');
|
|
145
|
+
if (fromFile)
|
|
146
|
+
group.reasons.add('caller-file');
|
|
147
|
+
if (event.caller !== undefined) {
|
|
148
|
+
group.callers.set(JSON.stringify([event.caller.path, event.caller.line, event.caller.column]), event.caller);
|
|
149
|
+
addedFiles.add(event.caller.path);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const routes = [...groups.entries()].sort(([a], [b]) => compareStrings(a, b)).map(([, group]) => ({
|
|
154
|
+
...group.route, observedCalls: group.count, outcomes: group.outcomes,
|
|
155
|
+
callers: [...group.callers.entries()].sort(([a], [b]) => compareStrings(a, b)).slice(0, 20).map(([, caller]) => caller),
|
|
156
|
+
callersOmitted: Math.max(0, group.callers.size - 20), selectionReasons: [...group.reasons].sort(compareStrings),
|
|
157
|
+
}));
|
|
158
|
+
const missingPlatform = (set, key) => [...(boundaryPlatforms.get(key) ?? [])].some((platform) => !set.has(pair(key, platform)));
|
|
159
|
+
const unobservedBoundaries = required.filter(({ subject }) => missingPlatform(observed, subject.key))
|
|
160
|
+
.map(({ subject }) => subject.key).sort(compareStrings);
|
|
161
|
+
const uncoveredBoundaries = required.filter(({ subject }) => !aligned || missingPlatform(declared, subject.key))
|
|
162
|
+
.map(({ subject }) => subject.key).sort(compareStrings);
|
|
163
|
+
const gaps = [];
|
|
164
|
+
if (!aligned)
|
|
165
|
+
gaps.push({ code: 'stale-runtime-expectations', message: 'Runtime expectations target a different capture revision.' });
|
|
166
|
+
if (verification.status !== 'passed')
|
|
167
|
+
gaps.push({ code: 'runtime-verification-gap',
|
|
168
|
+
message: 'Declared runtime scenarios contain failures, missing observations, stale runs, or incomplete evidence.' });
|
|
169
|
+
if (unobservedBoundaries.length > 0)
|
|
170
|
+
gaps.push({ code: 'unobserved-runtime-boundaries',
|
|
171
|
+
message: `${unobservedBoundaries.length} related static boundary(s) have no current runtime observations.` });
|
|
172
|
+
if (uncoveredBoundaries.length > 0)
|
|
173
|
+
gaps.push({ code: 'uncovered-runtime-boundaries',
|
|
174
|
+
message: `${uncoveredBoundaries.length} related static boundary(s) have no current scenario expectation.` });
|
|
175
|
+
if (routes.some(({ staticStatus }) => staticStatus !== 'candidates'))
|
|
176
|
+
gaps.push({ code: 'runtime-static-binding-gap',
|
|
177
|
+
message: 'Some related runtime routes have no supported static handler candidate; observations do not identify native symbols.' });
|
|
178
|
+
const reviewFiles = [...new Set([...report.reviewFiles, ...addedFiles])].sort(compareStrings);
|
|
179
|
+
const candidates = [...candidateGroups.entries()].sort(([a], [b]) => compareStrings(a, b)).map(([, value]) => value);
|
|
180
|
+
return { ...report, runtime: { aligned, verification, routes, candidates, unobservedBoundaries, uncoveredBoundaries },
|
|
181
|
+
reviewFiles, limitations: [...report.limitations, ...gaps],
|
|
182
|
+
summary: { ...report.summary, reviewFiles: reviewFiles.length, evidenceGaps: report.summary.evidenceGaps + gaps.length } };
|
|
183
|
+
}
|
|
184
|
+
function routeKey(channel, method, transport = 'method-channel') {
|
|
185
|
+
return JSON.stringify(transport === 'basic-message-channel' ? [transport, channel, null] : [channel, method ?? null]);
|
|
186
|
+
}
|
|
187
|
+
function nativeLanguage(platform) {
|
|
188
|
+
return platform === 'android' ? 'kotlin' : platform === 'ios' || platform === 'macos' ? 'swift' : undefined;
|
|
189
|
+
}
|
|
190
|
+
function candidateKey(platform, channel, method, transport = 'method-channel') {
|
|
191
|
+
return platform === 'kotlin' ? JSON.stringify(['kotlin', transport, channel, method ?? null]) : routeKey(channel, method, transport);
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=preflight-runtime.js.map
|