isthmus-cli 0.4.1 → 0.6.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 (104) hide show
  1. package/README.ko.md +64 -22
  2. package/README.md +70 -26
  3. package/Skills/isthmus/SKILL.md +93 -4
  4. package/dist/cli/check-command.d.ts +2 -25
  5. package/dist/cli/check-command.js +27 -181
  6. package/dist/cli/check-command.js.map +1 -1
  7. package/dist/cli/command-support.d.ts +48 -0
  8. package/dist/cli/command-support.js +144 -0
  9. package/dist/cli/command-support.js.map +1 -0
  10. package/dist/cli/diff-command.d.ts +1 -1
  11. package/dist/cli/diff-command.js +1 -1
  12. package/dist/cli/diff-command.js.map +1 -1
  13. package/dist/cli/graph-command.d.ts +1 -1
  14. package/dist/cli/graph-command.js +15 -14
  15. package/dist/cli/graph-command.js.map +1 -1
  16. package/dist/cli/impact-command.d.ts +5 -0
  17. package/dist/cli/impact-command.js +111 -0
  18. package/dist/cli/impact-command.js.map +1 -0
  19. package/dist/cli/main.js +35 -13
  20. package/dist/cli/main.js.map +1 -1
  21. package/dist/cli/parse-arguments.d.ts +18 -0
  22. package/dist/cli/parse-arguments.js +48 -0
  23. package/dist/cli/parse-arguments.js.map +1 -0
  24. package/dist/cli/preflight-command.d.ts +5 -0
  25. package/dist/cli/preflight-command.js +83 -0
  26. package/dist/cli/preflight-command.js.map +1 -0
  27. package/dist/cli/query-command.d.ts +1 -1
  28. package/dist/cli/query-command.js +30 -10
  29. package/dist/cli/query-command.js.map +1 -1
  30. package/dist/cli/retentions-command.d.ts +1 -1
  31. package/dist/cli/retentions-command.js +11 -10
  32. package/dist/cli/retentions-command.js.map +1 -1
  33. package/dist/cli/runtime-command.d.ts +5 -0
  34. package/dist/cli/runtime-command.js +42 -0
  35. package/dist/cli/runtime-command.js.map +1 -0
  36. package/dist/cli/runtime-json-reader.d.ts +12 -0
  37. package/dist/cli/runtime-json-reader.js +37 -0
  38. package/dist/cli/runtime-json-reader.js.map +1 -0
  39. package/dist/exchange/impact-selection.d.ts +13 -0
  40. package/dist/exchange/impact-selection.js +35 -0
  41. package/dist/exchange/impact-selection.js.map +1 -0
  42. package/dist/exchange/kartograph-impact.d.ts +4 -0
  43. package/dist/exchange/kartograph-impact.js +169 -0
  44. package/dist/exchange/kartograph-impact.js.map +1 -0
  45. package/dist/exchange/messages.d.ts +51 -0
  46. package/dist/exchange/messages.js +126 -0
  47. package/dist/exchange/messages.js.map +1 -0
  48. package/dist/exchange/parse.d.ts +8 -0
  49. package/dist/exchange/parse.js +12 -8
  50. package/dist/exchange/parse.js.map +1 -1
  51. package/dist/exchange/preflight-context.d.ts +71 -0
  52. package/dist/exchange/preflight-context.js +317 -0
  53. package/dist/exchange/preflight-context.js.map +1 -0
  54. package/dist/exchange/producer-impact.d.ts +17 -0
  55. package/dist/exchange/producer-impact.js +226 -0
  56. package/dist/exchange/producer-impact.js.map +1 -0
  57. package/dist/exchange/runtime.d.ts +69 -0
  58. package/dist/exchange/runtime.js +160 -0
  59. package/dist/exchange/runtime.js.map +1 -0
  60. package/dist/join/message-address.d.ts +7 -0
  61. package/dist/join/message-address.js +37 -0
  62. package/dist/join/message-address.js.map +1 -0
  63. package/dist/join/messages.d.ts +23 -0
  64. package/dist/join/messages.js +80 -0
  65. package/dist/join/messages.js.map +1 -0
  66. package/dist/report/diff.d.ts +40 -33
  67. package/dist/report/diff.js +8 -4
  68. package/dist/report/diff.js.map +1 -1
  69. package/dist/report/impact.d.ts +62 -0
  70. package/dist/report/impact.js +168 -0
  71. package/dist/report/impact.js.map +1 -0
  72. package/dist/report/preflight-runtime.d.ts +42 -0
  73. package/dist/report/preflight-runtime.js +193 -0
  74. package/dist/report/preflight-runtime.js.map +1 -0
  75. package/dist/report/preflight-view.d.ts +146 -0
  76. package/dist/report/preflight-view.js +189 -0
  77. package/dist/report/preflight-view.js.map +1 -0
  78. package/dist/report/preflight.d.ts +104 -0
  79. package/dist/report/preflight.js +296 -0
  80. package/dist/report/preflight.js.map +1 -0
  81. package/dist/report/query.js +9 -2
  82. package/dist/report/query.js.map +1 -1
  83. package/dist/report/runtime-impact.d.ts +38 -0
  84. package/dist/report/runtime-impact.js +94 -0
  85. package/dist/report/runtime-impact.js.map +1 -0
  86. package/dist/report/runtime.d.ts +54 -0
  87. package/dist/report/runtime.js +137 -0
  88. package/dist/report/runtime.js.map +1 -0
  89. package/dist/report/sarif.d.ts +9 -2
  90. package/dist/report/sarif.js +4 -10
  91. package/dist/report/sarif.js.map +1 -1
  92. package/dist/report/sorted-json.d.ts +1 -1
  93. package/dist/report/sorted-json.js +2 -2
  94. package/dist/report/sorted-json.js.map +1 -1
  95. package/docs/BRIDGE-MESSAGES.md +102 -0
  96. package/docs/GRAPH-EXCHANGE.md +255 -0
  97. package/docs/IMPACT.md +96 -0
  98. package/docs/PREFLIGHT.md +295 -0
  99. package/docs/RUNTIME.md +172 -0
  100. package/docs/TOOLCHAIN.md +101 -0
  101. package/package.json +11 -2
  102. package/scripts/build-preflight-toolchain.mjs +186 -0
  103. package/scripts/capture-preflight.mjs +392 -0
  104. package/scripts/run-child.mjs +16 -0
@@ -0,0 +1,296 @@
1
+ import { compareStrings } from "../compare.js";
2
+ import { joinBridgeDocuments } from "../join/join.js";
3
+ import { createCheckReport } from "./check-report.js";
4
+ import { encodeSortedJson } from "./sorted-json.js";
5
+ import { joinMessageBridges } from "../join/messages.js";
6
+ /** 잘못된 그래프 투영과 자원 상한 초과를 입력 값 없이 구분한다. */
7
+ export class PreflightGraphError extends Error {
8
+ }
9
+ const MAX_RELATIONS = 1_000_000;
10
+ /** 전체 언어 그래프가 아닌 producer의 영향 숲을 경계에서 연결한다. */
11
+ export function createPreflightReport(context) {
12
+ const joined = joinBridgeDocuments(context.bridges);
13
+ const messages = joinMessageBridges(context.messages ?? [], context.project);
14
+ if (joined.deferred)
15
+ throw new PreflightGraphError('Preflight cannot use mixed-target bridge documents.');
16
+ const nodes = new Map();
17
+ const roots = new Set();
18
+ const consumers = new Map();
19
+ let relationCount = 0;
20
+ const limits = context.limitations.map((message) => ({ code: 'capture-limitation', message }));
21
+ const bindings = new Map(context.bindings.map((binding) => [locationKey('dart', binding.location), binding.symbol]));
22
+ const analyses = [...context.analyses].sort((a, b) => compareStrings(a.id, b.id));
23
+ const dartRoots = new Set(analyses.filter(({ platform }) => platform === 'dart')
24
+ .flatMap(({ roots }) => roots.map(({ id }) => symbolKey('dart', id))));
25
+ function addSymbol(platform, symbol) {
26
+ const key = symbolKey(platform, symbol.id);
27
+ const existing = nodes.get(key);
28
+ if (existing !== undefined && existing.kind === 'symbol') {
29
+ const before = existing.symbol.location;
30
+ const after = symbol.location;
31
+ if (existing.symbol.qualifiedName !== symbol.qualifiedName ||
32
+ (before !== undefined && after !== undefined && (before.path !== after.path ||
33
+ (before.line !== undefined && after.line !== undefined && before.line !== after.line) ||
34
+ (before.column !== undefined && after.column !== undefined && before.column !== after.column)))) {
35
+ throw new PreflightGraphError('Conflicting symbol identities in producer impact inputs.');
36
+ }
37
+ if (before !== undefined && after !== undefined) {
38
+ nodes.set(key, { ...existing, symbol: { ...existing.symbol, location: {
39
+ path: before.path,
40
+ ...(before.line === undefined && after.line === undefined ? {} : { line: before.line ?? after.line }),
41
+ ...(before.column === undefined && after.column === undefined ? {} : { column: before.column ?? after.column }),
42
+ } } });
43
+ return key;
44
+ }
45
+ if (before !== undefined || after === undefined)
46
+ return key;
47
+ nodes.set(key, { ...existing, symbol: { ...existing.symbol, location: after } });
48
+ return key;
49
+ }
50
+ nodes.set(key, { key, kind: 'symbol', platform, symbol });
51
+ return key;
52
+ }
53
+ function link(dependency, consumer, relation) {
54
+ let outgoing = consumers.get(dependency);
55
+ if (outgoing === undefined) {
56
+ outgoing = new Map();
57
+ consumers.set(dependency, outgoing);
58
+ }
59
+ let reasons = outgoing.get(consumer);
60
+ if (reasons === undefined) {
61
+ reasons = new Map();
62
+ outgoing.set(consumer, reasons);
63
+ }
64
+ const key = encodeSortedJson(relation, true);
65
+ if (!reasons.has(key) && ++relationCount > MAX_RELATIONS)
66
+ throw new PreflightGraphError('Preflight relation budget exceeded.');
67
+ reasons.set(key, relation);
68
+ }
69
+ for (const analysis of analyses) {
70
+ for (const symbol of analysis.roots) {
71
+ const key = addSymbol(analysis.platform, symbol);
72
+ if (analysis.trigger === undefined)
73
+ roots.add(key);
74
+ }
75
+ for (const item of analysis.affected)
76
+ addSymbol(analysis.platform, item.symbol);
77
+ for (const item of analysis.affected)
78
+ link(symbolKey(analysis.platform, item.via), symbolKey(analysis.platform, item.symbol.id), { kind: 'language', analysis: analysis.id, relationships: item.relationships });
79
+ }
80
+ function endpointKey(endpoint) {
81
+ if (endpoint.platform !== 'dart' && endpoint.platform !== 'swift' && endpoint.platform !== 'kotlin')
82
+ return undefined;
83
+ if (endpoint.sourceLanguage === 'objective-c')
84
+ return undefined;
85
+ const binding = endpoint.platform === 'dart' ? bindings.get(locationKey('dart', endpoint.location)) : undefined;
86
+ const id = endpoint.symbol?.usr ?? binding?.id;
87
+ if (id === undefined)
88
+ return undefined;
89
+ const key = symbolKey(endpoint.platform, id);
90
+ if (binding !== undefined) {
91
+ if (binding.id !== id)
92
+ throw new PreflightGraphError('Conflicting symbol identities in Dart bridge bindings.');
93
+ // 영향 root는 위치를 생략할 수 있다. query가 관찰한 선언 위치도 병합·대조한다.
94
+ addSymbol(endpoint.platform, binding);
95
+ }
96
+ else if (!nodes.has(key) && endpoint.symbol !== undefined) {
97
+ addSymbol(endpoint.platform, { id, qualifiedName: endpoint.symbol.qualifiedName });
98
+ }
99
+ return nodes.has(key) ? key : undefined;
100
+ }
101
+ const channelRecords = [
102
+ ...joined.matchedChannels,
103
+ ...joined.unregisteredChannelCreations.map((item) => ({ ...item, registrations: [] })),
104
+ ...joined.registrationsWithoutCreations.map((item) => ({ ...item, creations: [] })),
105
+ ];
106
+ const registrations = new Map(channelRecords.map((item) => [JSON.stringify([item.target, item.channel]), item.registrations]));
107
+ const routes = [];
108
+ function boundary(target, channel, method, callers, handlers, wire, matching) {
109
+ const key = matching === undefined ? JSON.stringify(['bridge', target, channel, method ?? null])
110
+ : JSON.stringify(['bridge', target, 'basic-message-channel', matching, channel]);
111
+ const subject = { key, kind: 'bridge', target, channel, ...(method === undefined ? {} : { method }),
112
+ ...(matching === undefined ? {} : { transport: 'basic-message-channel', matching }) };
113
+ nodes.set(key, subject);
114
+ const route = { subject, callers, receivers: [...handlers, ...wire], callerKeys: [], receiverKeys: [],
115
+ missing: [], imprecise: [] };
116
+ for (const [endpoints, kind] of [[handlers, 'bridge-handler'], [wire, 'bridge-registration']]) {
117
+ for (const endpoint of endpoints) {
118
+ const receiver = endpointKey(endpoint);
119
+ if (receiver === undefined) {
120
+ route.missing.push(endpoint);
121
+ continue;
122
+ }
123
+ let linkEvidence = endpoint;
124
+ if (matching !== undefined) {
125
+ const { handlerScope, dependencies, ...evidence } = endpoint;
126
+ linkEvidence = evidence;
127
+ if (handlerScope?.complete === true && dependencies !== undefined) {
128
+ // 공통 등록 선언은 직접 선택했을 때 전체 배선을 검토한다. 다른 handler의 호출로
129
+ // 선언에 도달한 경우에는 발생 위치로 구분한 사용 관계만 해당 boundary로 전파한다.
130
+ if (roots.has(receiver)) {
131
+ route.receiverKeys.push(receiver);
132
+ link(receiver, key, { kind: 'bridge-message-handler', evidence });
133
+ }
134
+ for (const { dispatchTargets, ...dependency } of dependencies) {
135
+ for (const target of [dependency.symbol, ...(dispatchTargets ?? [])]) {
136
+ if (endpoint.platform !== 'swift' && endpoint.platform !== 'kotlin')
137
+ continue;
138
+ const dependencyKey = symbolKey(endpoint.platform, target.usr);
139
+ if (!nodes.has(dependencyKey))
140
+ continue;
141
+ route.receiverKeys.push(dependencyKey);
142
+ link(dependencyKey, key, { kind: 'bridge-message-dependency', evidence, dependency,
143
+ ...(target === dependency.symbol ? {} : { dispatchTarget: target }) });
144
+ }
145
+ }
146
+ continue;
147
+ }
148
+ route.imprecise.push(evidence);
149
+ }
150
+ route.receiverKeys.push(receiver);
151
+ link(receiver, key, { kind: matching === undefined ? kind : 'bridge-message-handler', evidence: linkEvidence });
152
+ }
153
+ }
154
+ for (const endpoint of callers) {
155
+ const caller = endpointKey(endpoint);
156
+ if (caller === undefined) {
157
+ route.missing.push(endpoint);
158
+ continue;
159
+ }
160
+ route.callerKeys.push(caller);
161
+ link(key, caller, { kind: matching !== undefined ? 'bridge-message-send' : method === undefined ? 'bridge-creation' : 'bridge-invocation', evidence: endpoint });
162
+ }
163
+ routes.push(route);
164
+ }
165
+ for (const item of channelRecords)
166
+ boundary(item.target, item.channel, undefined, item.creations, [], item.registrations);
167
+ for (const item of [
168
+ ...joined.matchedMethods,
169
+ ...joined.unhandledInvocations.map((value) => ({ ...value, handlers: [] })),
170
+ ...joined.handlersWithoutInvocations.map((value) => ({ ...value, invocations: [] })),
171
+ ])
172
+ boundary(item.target, item.channel, item.method, item.invocations, item.handlers, registrations.get(JSON.stringify([item.target, item.channel])) ?? []);
173
+ for (const route of messages.routes)
174
+ boundary('flutter', route.channel, undefined, route.senders, route.handlers, [], route.matching);
175
+ const depths = new Map([...roots].map((key) => [key, 0]));
176
+ const visits = new Map();
177
+ const queue = [...roots].sort(compareStrings);
178
+ for (let head = 0; head < queue.length; head++) {
179
+ const dependency = queue[head];
180
+ for (const [key, reasons] of [...(consumers.get(dependency) ?? [])].sort(([a], [b]) => compareStrings(a, b))) {
181
+ if (depths.has(key))
182
+ continue;
183
+ const subject = nodes.get(key);
184
+ if (subject === undefined)
185
+ throw new PreflightGraphError('Producer impact refers to an unknown symbol.');
186
+ const depth = depths.get(dependency) + 1;
187
+ depths.set(key, depth);
188
+ visits.set(key, { subject, depth, via: dependency,
189
+ relations: [...reasons.entries()].sort(([a], [b]) => compareStrings(a, b)).map(([, reason]) => reason) });
190
+ queue.push(key);
191
+ }
192
+ }
193
+ const rootSubjects = [...roots].sort(compareStrings).map((key) => nodes.get(key));
194
+ const affected = [...visits.values()].sort((a, b) => a.depth - b.depth || compareStrings(a.subject.key, b.subject.key));
195
+ const reachedFiles = new Set([...rootSubjects, ...affected.map(({ subject }) => subject)]
196
+ .flatMap((item) => item.kind === 'symbol' && item.symbol.location !== undefined ? [item.symbol.location.path] : []));
197
+ for (const selection of Object.values(context.selection))
198
+ for (const path of selection.files)
199
+ reachedFiles.add(path);
200
+ const relevant = routes.filter((route) => depths.has(route.subject.key) || route.callerKeys.some((key) => depths.has(key)) ||
201
+ route.receiverKeys.some((key) => depths.has(key)) || route.missing.some(({ location }) => reachedFiles.has(location.path)));
202
+ for (const route of relevant) {
203
+ if (route.subject.transport === 'basic-message-channel') {
204
+ for (const evidence of route.imprecise)
205
+ limits.push({ code: 'unresolved-message-handler-scope',
206
+ message: 'Handler dependencies could not be fully attributed in the observed index; enclosing declaration impact remains conservative.', evidence });
207
+ if (route.subject.matching === 'prefix')
208
+ limits.push({ code: 'dynamic-message-address',
209
+ message: 'Message channel prefixes describe possible routes; suffix and instance wiring are not resolved.' });
210
+ if (route.callers.length === 0 || route.receivers.length === 0)
211
+ limits.push({ code: 'unmatched-message-boundary',
212
+ message: 'A related message boundary has no observed counterpart in the Basic extraction scope.' });
213
+ }
214
+ for (const evidence of route.missing)
215
+ limits.push({ code: 'unresolved-bridge-binding',
216
+ message: 'A bridge endpoint has no verified language symbol binding.', evidence });
217
+ if (depths.has(route.subject.key))
218
+ for (const key of route.callerKeys) {
219
+ if (!dartRoots.has(key)) {
220
+ const subject = nodes.get(key);
221
+ const location = subject?.kind === 'symbol' ? subject.symbol.location : undefined;
222
+ limits.push({ code: 'missing-language-continuation', message: 'Dart consumer impact was not supplied for a reached bridge caller.',
223
+ ...(location?.line !== undefined && location.column !== undefined
224
+ ? { evidence: { platform: 'dart', location: { path: location.path, line: location.line, column: location.column } } } : {}) });
225
+ }
226
+ }
227
+ }
228
+ for (const analysis of analyses) {
229
+ if (analysis.trigger !== undefined && !depths.has(symbolKey(analysis.platform, analysis.trigger)))
230
+ continue;
231
+ for (const message of analysis.limitations)
232
+ limits.push({ code: 'producer-limitation', analysis: analysis.id, message });
233
+ if (analysis.truncated)
234
+ limits.push({ code: 'producer-truncated', analysis: analysis.id, message: 'The producer impact traversal or output was truncated.' });
235
+ if (analysis.trigger === undefined && analysis.roots.length === 0)
236
+ limits.push({ code: 'unobserved-selection', analysis: analysis.id,
237
+ message: 'The producer did not resolve a requested change selection.' });
238
+ }
239
+ for (const document of context.bridges)
240
+ for (const fact of document.facts) {
241
+ if ((fact.dynamic || fact.channel === null) && reachedFiles.has(fact.location.path))
242
+ limits.push({
243
+ code: 'unresolved-dynamic-boundary', message: 'A related dynamic or unattributed bridge fact could not be connected.',
244
+ evidence: { platform: document.platform, location: fact.location },
245
+ });
246
+ }
247
+ for (const evidence of messages.unresolved)
248
+ if (reachedFiles.has(evidence.location.path))
249
+ limits.push({
250
+ code: 'unresolved-message-boundary', message: 'A related message address could not be resolved to a literal or proven prefix.', evidence,
251
+ });
252
+ if (relevant.some(({ subject }) => subject.transport === undefined)) {
253
+ for (const limit of joined.limitations)
254
+ limits.push({ code: 'bridge-limitation', message: limit.message });
255
+ }
256
+ if (relevant.some(({ subject }) => subject.transport === 'basic-message-channel')) {
257
+ for (const limit of messages.limitations)
258
+ limits.push({ code: 'message-producer-limitation', message: limit.message });
259
+ }
260
+ const uniqueLimits = [...new Map(limits.map((item) => [encodeSortedJson(item, true), item])).entries()]
261
+ .sort(([a], [b]) => compareStrings(a, b)).map(([, item]) => item);
262
+ const routeKeys = new Set(relevant.filter(({ subject }) => subject.transport === undefined)
263
+ .map(({ subject }) => JSON.stringify([subject.target, subject.channel, subject.method ?? null])));
264
+ const issues = createCheckReport(joined).issues.filter((issue) => routeKeys.has(JSON.stringify([issue.target, issue.channel, issue.method ?? null])));
265
+ for (const route of relevant)
266
+ for (const endpoint of [...route.callers, ...route.receivers])
267
+ reachedFiles.add(endpoint.location.path);
268
+ const reviewFiles = [...reachedFiles].sort(compareStrings);
269
+ const hasSelection = Object.keys(context.selection).length > 0;
270
+ return {
271
+ format: 'isthmus-preflight', version: 1, project: context.project, revision: context.revision,
272
+ scope: 'cross-language-impact', complete: false,
273
+ status: !hasSelection ? 'noChanges' : roots.size === 0 ? 'unobserved' : 'observed',
274
+ selection: context.selection, roots: rootSubjects, affected,
275
+ boundaries: relevant.sort((a, b) => compareStrings(a.subject.key, b.subject.key)).map((route) => ({
276
+ subject: route.subject, relationship: depths.has(route.subject.key) ? 'consumer' : 'dependency', callers: route.callers, receivers: route.receivers,
277
+ })), reviewFiles, issues, limitations: uniqueLimits, bridgeLimitations: joined.limitations,
278
+ ...(context.messages === undefined ? {} : { messageLimitations: messages.limitations }),
279
+ producers: analyses.map(({ id, platform, tool, truncated }) => ({ analysis: id, platform, tool, truncated })),
280
+ summary: { selectedSymbols: roots.size, affectedSymbols: affected.filter(({ subject }) => subject.kind === 'symbol').length,
281
+ bridgeBoundaries: affected.filter(({ subject }) => subject.kind === 'bridge').length, reviewFiles: reviewFiles.length,
282
+ errors: issues.filter(({ severity }) => severity === 'error').length,
283
+ warnings: issues.filter(({ severity }) => severity === 'warning').length, evidenceGaps: uniqueLimits.length },
284
+ };
285
+ }
286
+ /** 관찰된 영향과 "영향 없음"의 보증을 구분하며 관련 공백은 CI 검토를 요구한다. */
287
+ export function hasPreflightBlockers(report) {
288
+ return report.status === 'unobserved' || report.summary.errors > 0 || report.summary.evidenceGaps > 0;
289
+ }
290
+ /** 언어 ID를 다른 언어의 같은 문자열과 혼동하지 않는 키다. */
291
+ function symbolKey(platform, id) { return JSON.stringify([platform, id]); }
292
+ /** 호출 위치와 선언 위치의 의미를 바꾸지 않고 정확한 fact binding만 찾는다. */
293
+ function locationKey(platform, location) {
294
+ return JSON.stringify([platform, location.path, location.line, location.column]);
295
+ }
296
+ //# sourceMappingURL=preflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../src/report/preflight.ts"],"names":[],"mappings":"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","sourcesContent":["import { compareStrings } from '../compare.ts';\nimport type { ImpactSymbol, PreflightContext } from '../exchange/preflight-context.ts';\nimport type { BridgeEndpoint, JoinLimitation } from '../join/join.ts';\nimport { joinBridgeDocuments } from '../join/join.ts';\nimport type { BridgeTarget } from '../exchange/parse.ts';\nimport { createCheckReport } from './check-report.ts';\nimport type { CheckIssue } from './check-report.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\nimport type { PreflightRuntimeReport } from './preflight-runtime.ts';\nimport { joinMessageBridges } from '../join/messages.ts';\nimport type { MessageEndpoint } from '../join/messages.ts';\nimport type { BridgeHandlerDependency } from '../exchange/messages.ts';\nimport type { BridgeSymbol } from '../exchange/parse.ts';\n\ntype Language = 'dart' | 'swift' | 'kotlin';\n\n/** 언어 심볼과 경계의 키 공간을 분리한다. 원래 producer ID는 symbol.id에 보존한다. */\nexport type PreflightSubject = {\n readonly key: string; readonly kind: 'symbol'; readonly platform: Language; readonly symbol: ImpactSymbol;\n} | {\n readonly key: string; readonly kind: 'bridge'; readonly target: BridgeTarget;\n readonly channel: string; readonly method?: string;\n readonly transport?: 'basic-message-channel';\n readonly matching?: 'literal' | 'prefix';\n};\n\n/** 실제 producer 사용 관계와 문자열 브리지 관계를 서로 다른 근거로 표시한다. */\nexport type PreflightRelation = {\n readonly kind: 'language'; readonly analysis: string; readonly relationships: readonly string[];\n} | {\n readonly kind: 'bridge-message-dependency'; readonly evidence: BridgeEndpoint;\n readonly dependency: Omit<BridgeHandlerDependency, 'dispatchTargets'>;\n readonly dispatchTarget?: BridgeSymbol;\n} | {\n readonly kind: 'bridge-handler' | 'bridge-registration' | 'bridge-invocation' | 'bridge-creation'\n | 'bridge-message-send' | 'bridge-message-handler';\n readonly evidence: BridgeEndpoint;\n};\n\n/** 가장 가까운 변경 씨앗까지의 선행 정점을 공유해 긴 경로 복사를 피한다. */\nexport interface PreflightAffected {\n readonly subject: PreflightSubject;\n readonly depth: number;\n readonly via: string;\n readonly relations: readonly PreflightRelation[];\n}\n\n/** 근거를 연결할 수 없는 부분과 발생 위치다. */\nexport interface PreflightLimitation {\n readonly code: string;\n readonly message: string;\n readonly analysis?: string;\n readonly evidence?: BridgeEndpoint;\n}\n\n/** 언어별 영향 투영과 브리지 관계를 연결한 사전 검토 문서다. */\nexport interface PreflightReport {\n readonly format: 'isthmus-preflight'; readonly version: 1;\n readonly project: string; readonly revision: string;\n readonly scope: 'cross-language-impact'; readonly complete: false;\n readonly status: 'observed' | 'unobserved' | 'noChanges';\n readonly selection: PreflightContext['selection'];\n readonly roots: readonly PreflightSubject[];\n readonly affected: readonly PreflightAffected[];\n readonly boundaries: readonly {\n subject: Extract<PreflightSubject, { kind: 'bridge' }>;\n relationship: 'consumer' | 'dependency';\n callers: readonly BridgeEndpoint[];\n receivers: readonly BridgeEndpoint[];\n }[];\n readonly reviewFiles: readonly string[];\n readonly issues: readonly CheckIssue[];\n readonly limitations: readonly PreflightLimitation[];\n readonly bridgeLimitations: readonly JoinLimitation[];\n readonly messageLimitations?: readonly JoinLimitation[];\n readonly runtime?: PreflightRuntimeReport;\n readonly producers: ReadonlyArray<{ analysis: string; platform: Language; tool: { name: string; version: string }; truncated: boolean }>;\n readonly summary: {\n selectedSymbols: number; affectedSymbols: number; bridgeBoundaries: number;\n reviewFiles: number; errors: number; warnings: number; evidenceGaps: number;\n };\n}\n\n/** 잘못된 그래프 투영과 자원 상한 초과를 입력 값 없이 구분한다. */\nexport class PreflightGraphError extends Error {}\n\nconst MAX_RELATIONS = 1_000_000;\n\n/** 전체 언어 그래프가 아닌 producer의 영향 숲을 경계에서 연결한다. */\nexport function createPreflightReport(context: PreflightContext): PreflightReport {\n const joined = joinBridgeDocuments(context.bridges);\n const messages = joinMessageBridges(context.messages ?? [], context.project);\n if (joined.deferred) throw new PreflightGraphError('Preflight cannot use mixed-target bridge documents.');\n const nodes = new Map<string, PreflightSubject>();\n const roots = new Set<string>();\n const consumers = new Map<string, Map<string, Map<string, PreflightRelation>>>();\n let relationCount = 0;\n const limits: PreflightLimitation[] = context.limitations.map((message) => ({ code: 'capture-limitation', message }));\n const bindings = new Map(context.bindings.map((binding) => [locationKey('dart', binding.location), binding.symbol]));\n const analyses = [...context.analyses].sort((a, b) => compareStrings(a.id, b.id));\n const dartRoots = new Set(analyses.filter(({ platform }) => platform === 'dart')\n .flatMap(({ roots }) => roots.map(({ id }) => symbolKey('dart', id))));\n\n function addSymbol(platform: Language, symbol: ImpactSymbol): string {\n const key = symbolKey(platform, symbol.id);\n const existing = nodes.get(key);\n if (existing !== undefined && existing.kind === 'symbol') {\n const before = existing.symbol.location;\n const after = symbol.location;\n if (existing.symbol.qualifiedName !== symbol.qualifiedName ||\n (before !== undefined && after !== undefined && (before.path !== after.path ||\n (before.line !== undefined && after.line !== undefined && before.line !== after.line) ||\n (before.column !== undefined && after.column !== undefined && before.column !== after.column)))) {\n throw new PreflightGraphError('Conflicting symbol identities in producer impact inputs.');\n }\n if (before !== undefined && after !== undefined) {\n nodes.set(key, { ...existing, symbol: { ...existing.symbol, location: {\n path: before.path,\n ...(before.line === undefined && after.line === undefined ? {} : { line: before.line ?? after.line }),\n ...(before.column === undefined && after.column === undefined ? {} : { column: before.column ?? after.column }),\n } } });\n return key;\n }\n if (before !== undefined || after === undefined) return key;\n nodes.set(key, { ...existing, symbol: { ...existing.symbol, location: after } });\n return key;\n }\n nodes.set(key, { key, kind: 'symbol', platform, symbol });\n return key;\n }\n\n function link(dependency: string, consumer: string, relation: PreflightRelation): void {\n let outgoing = consumers.get(dependency);\n if (outgoing === undefined) { outgoing = new Map(); consumers.set(dependency, outgoing); }\n let reasons = outgoing.get(consumer);\n if (reasons === undefined) { reasons = new Map(); outgoing.set(consumer, reasons); }\n const key = encodeSortedJson(relation, true);\n if (!reasons.has(key) && ++relationCount > MAX_RELATIONS) throw new PreflightGraphError('Preflight relation budget exceeded.');\n reasons.set(key, relation);\n }\n\n for (const analysis of analyses) {\n for (const symbol of analysis.roots) {\n const key = addSymbol(analysis.platform, symbol);\n if (analysis.trigger === undefined) roots.add(key);\n }\n for (const item of analysis.affected) addSymbol(analysis.platform, item.symbol);\n for (const item of analysis.affected) link(symbolKey(analysis.platform, item.via), symbolKey(analysis.platform, item.symbol.id),\n { kind: 'language', analysis: analysis.id, relationships: item.relationships });\n }\n\n function endpointKey(endpoint: BridgeEndpoint): string | undefined {\n if (endpoint.platform !== 'dart' && endpoint.platform !== 'swift' && endpoint.platform !== 'kotlin') return undefined;\n if (endpoint.sourceLanguage === 'objective-c') return undefined;\n const binding = endpoint.platform === 'dart' ? bindings.get(locationKey('dart', endpoint.location)) : undefined;\n const id = endpoint.symbol?.usr ?? binding?.id;\n if (id === undefined) return undefined;\n const key = symbolKey(endpoint.platform, id);\n if (binding !== undefined) {\n if (binding.id !== id) throw new PreflightGraphError('Conflicting symbol identities in Dart bridge bindings.');\n // 영향 root는 위치를 생략할 수 있다. query가 관찰한 선언 위치도 병합·대조한다.\n addSymbol(endpoint.platform, binding);\n } else if (!nodes.has(key) && endpoint.symbol !== undefined) {\n addSymbol(endpoint.platform, { id, qualifiedName: endpoint.symbol.qualifiedName });\n }\n return nodes.has(key) ? key : undefined;\n }\n\n const channelRecords = [\n ...joined.matchedChannels,\n ...joined.unregisteredChannelCreations.map((item) => ({ ...item, registrations: [] })),\n ...joined.registrationsWithoutCreations.map((item) => ({ ...item, creations: [] })),\n ];\n const registrations = new Map(channelRecords.map((item) => [JSON.stringify([item.target, item.channel]), item.registrations]));\n const routes: Array<{\n subject: Extract<PreflightSubject, { kind: 'bridge' }>;\n callers: readonly BridgeEndpoint[]; receivers: readonly BridgeEndpoint[];\n callerKeys: string[]; receiverKeys: string[]; missing: BridgeEndpoint[]; imprecise: BridgeEndpoint[];\n }> = [];\n\n function boundary(target: BridgeTarget, channel: string, method: string | undefined,\n callers: readonly BridgeEndpoint[], handlers: readonly MessageEndpoint[], wire: readonly BridgeEndpoint[],\n matching?: 'literal' | 'prefix'): void {\n const key = matching === undefined ? JSON.stringify(['bridge', target, channel, method ?? null])\n : JSON.stringify(['bridge', target, 'basic-message-channel', matching, channel]);\n const subject = { key, kind: 'bridge' as const, target, channel, ...(method === undefined ? {} : { method }),\n ...(matching === undefined ? {} : { transport: 'basic-message-channel' as const, matching }) };\n nodes.set(key, subject);\n const route = { subject, callers, receivers: [...handlers, ...wire], callerKeys: [] as string[], receiverKeys: [] as string[],\n missing: [] as BridgeEndpoint[], imprecise: [] as BridgeEndpoint[] };\n for (const [endpoints, kind] of [[handlers, 'bridge-handler'], [wire, 'bridge-registration']] as const) {\n for (const endpoint of endpoints) {\n const receiver = endpointKey(endpoint);\n if (receiver === undefined) { route.missing.push(endpoint); continue; }\n let linkEvidence: BridgeEndpoint = endpoint;\n if (matching !== undefined) {\n const { handlerScope, dependencies, ...evidence } = endpoint as MessageEndpoint;\n linkEvidence = evidence;\n if (handlerScope?.complete === true && dependencies !== undefined) {\n // 공통 등록 선언은 직접 선택했을 때 전체 배선을 검토한다. 다른 handler의 호출로\n // 선언에 도달한 경우에는 발생 위치로 구분한 사용 관계만 해당 boundary로 전파한다.\n if (roots.has(receiver)) {\n route.receiverKeys.push(receiver);\n link(receiver, key, { kind: 'bridge-message-handler', evidence });\n }\n for (const { dispatchTargets, ...dependency } of dependencies) {\n for (const target of [dependency.symbol, ...(dispatchTargets ?? [])]) {\n if (endpoint.platform !== 'swift' && endpoint.platform !== 'kotlin') continue;\n const dependencyKey = symbolKey(endpoint.platform, target.usr!);\n if (!nodes.has(dependencyKey)) continue;\n route.receiverKeys.push(dependencyKey);\n link(dependencyKey, key, { kind: 'bridge-message-dependency', evidence, dependency,\n ...(target === dependency.symbol ? {} : { dispatchTarget: target }) });\n }\n }\n continue;\n }\n route.imprecise.push(evidence);\n }\n route.receiverKeys.push(receiver);\n link(receiver, key, { kind: matching === undefined ? kind : 'bridge-message-handler', evidence: linkEvidence });\n }\n }\n for (const endpoint of callers) {\n const caller = endpointKey(endpoint);\n if (caller === undefined) { route.missing.push(endpoint); continue; }\n route.callerKeys.push(caller);\n link(key, caller, { kind: matching !== undefined ? 'bridge-message-send' : method === undefined ? 'bridge-creation' : 'bridge-invocation', evidence: endpoint });\n }\n routes.push(route);\n }\n\n for (const item of channelRecords) boundary(item.target, item.channel, undefined, item.creations, [], item.registrations);\n for (const item of [\n ...joined.matchedMethods,\n ...joined.unhandledInvocations.map((value) => ({ ...value, handlers: [] })),\n ...joined.handlersWithoutInvocations.map((value) => ({ ...value, invocations: [] })),\n ]) boundary(item.target, item.channel, item.method, item.invocations, item.handlers,\n registrations.get(JSON.stringify([item.target, item.channel])) ?? []);\n for (const route of messages.routes) boundary('flutter', route.channel, undefined, route.senders, route.handlers, [], route.matching);\n\n const depths = new Map([...roots].map((key) => [key, 0]));\n const visits = new Map<string, PreflightAffected>();\n const queue = [...roots].sort(compareStrings);\n for (let head = 0; head < queue.length; head++) {\n const dependency = queue[head]!;\n for (const [key, reasons] of [...(consumers.get(dependency) ?? [])].sort(([a], [b]) => compareStrings(a, b))) {\n if (depths.has(key)) continue;\n const subject = nodes.get(key);\n if (subject === undefined) throw new PreflightGraphError('Producer impact refers to an unknown symbol.');\n const depth = depths.get(dependency)! + 1;\n depths.set(key, depth);\n visits.set(key, { subject, depth, via: dependency,\n relations: [...reasons.entries()].sort(([a], [b]) => compareStrings(a, b)).map(([, reason]) => reason) });\n queue.push(key);\n }\n }\n const rootSubjects = [...roots].sort(compareStrings).map((key) => nodes.get(key)!);\n const affected = [...visits.values()].sort((a, b) => a.depth - b.depth || compareStrings(a.subject.key, b.subject.key));\n const reachedFiles = new Set([...rootSubjects, ...affected.map(({ subject }) => subject)]\n .flatMap((item) => item.kind === 'symbol' && item.symbol.location !== undefined ? [item.symbol.location.path] : []));\n for (const selection of Object.values(context.selection)) for (const path of selection.files) reachedFiles.add(path);\n\n const relevant = routes.filter((route) => depths.has(route.subject.key) || route.callerKeys.some((key) => depths.has(key)) ||\n route.receiverKeys.some((key) => depths.has(key)) || route.missing.some(({ location }) => reachedFiles.has(location.path)));\n for (const route of relevant) {\n if (route.subject.transport === 'basic-message-channel') {\n for (const evidence of route.imprecise) limits.push({ code: 'unresolved-message-handler-scope',\n message: 'Handler dependencies could not be fully attributed in the observed index; enclosing declaration impact remains conservative.', evidence });\n if (route.subject.matching === 'prefix') limits.push({ code: 'dynamic-message-address',\n message: 'Message channel prefixes describe possible routes; suffix and instance wiring are not resolved.' });\n if (route.callers.length === 0 || route.receivers.length === 0) limits.push({ code: 'unmatched-message-boundary',\n message: 'A related message boundary has no observed counterpart in the Basic extraction scope.' });\n }\n for (const evidence of route.missing) limits.push({ code: 'unresolved-bridge-binding',\n message: 'A bridge endpoint has no verified language symbol binding.', evidence });\n if (depths.has(route.subject.key)) for (const key of route.callerKeys) {\n if (!dartRoots.has(key)) {\n const subject = nodes.get(key);\n const location = subject?.kind === 'symbol' ? subject.symbol.location : undefined;\n limits.push({ code: 'missing-language-continuation', message: 'Dart consumer impact was not supplied for a reached bridge caller.',\n ...(location?.line !== undefined && location.column !== undefined\n ? { evidence: { platform: 'dart' as const, location: { path: location.path, line: location.line, column: location.column } } } : {}) });\n }\n }\n }\n for (const analysis of analyses) {\n if (analysis.trigger !== undefined && !depths.has(symbolKey(analysis.platform, analysis.trigger))) continue;\n for (const message of analysis.limitations) limits.push({ code: 'producer-limitation', analysis: analysis.id, message });\n if (analysis.truncated) limits.push({ code: 'producer-truncated', analysis: analysis.id, message: 'The producer impact traversal or output was truncated.' });\n if (analysis.trigger === undefined && analysis.roots.length === 0) limits.push({ code: 'unobserved-selection', analysis: analysis.id,\n message: 'The producer did not resolve a requested change selection.' });\n }\n for (const document of context.bridges) for (const fact of document.facts) {\n if ((fact.dynamic || fact.channel === null) && reachedFiles.has(fact.location.path)) limits.push({\n code: 'unresolved-dynamic-boundary', message: 'A related dynamic or unattributed bridge fact could not be connected.',\n evidence: { platform: document.platform, location: fact.location },\n });\n }\n for (const evidence of messages.unresolved) if (reachedFiles.has(evidence.location.path)) limits.push({\n code: 'unresolved-message-boundary', message: 'A related message address could not be resolved to a literal or proven prefix.', evidence,\n });\n if (relevant.some(({ subject }) => subject.transport === undefined)) {\n for (const limit of joined.limitations) limits.push({ code: 'bridge-limitation', message: limit.message });\n }\n if (relevant.some(({ subject }) => subject.transport === 'basic-message-channel')) {\n for (const limit of messages.limitations) limits.push({ code: 'message-producer-limitation', message: limit.message });\n }\n const uniqueLimits = [...new Map(limits.map((item) => [encodeSortedJson(item, true), item])).entries()]\n .sort(([a], [b]) => compareStrings(a, b)).map(([, item]) => item);\n const routeKeys = new Set(relevant.filter(({ subject }) => subject.transport === undefined)\n .map(({ subject }) => JSON.stringify([subject.target, subject.channel, subject.method ?? null])));\n const issues = createCheckReport(joined).issues.filter((issue) => routeKeys.has(JSON.stringify([issue.target, issue.channel, issue.method ?? null])));\n for (const route of relevant) for (const endpoint of [...route.callers, ...route.receivers]) reachedFiles.add(endpoint.location.path);\n const reviewFiles = [...reachedFiles].sort(compareStrings);\n const hasSelection = Object.keys(context.selection).length > 0;\n return {\n format: 'isthmus-preflight', version: 1, project: context.project, revision: context.revision,\n scope: 'cross-language-impact', complete: false,\n status: !hasSelection ? 'noChanges' : roots.size === 0 ? 'unobserved' : 'observed',\n selection: context.selection, roots: rootSubjects, affected,\n boundaries: relevant.sort((a, b) => compareStrings(a.subject.key, b.subject.key)).map((route) => ({\n subject: route.subject, relationship: depths.has(route.subject.key) ? 'consumer' : 'dependency', callers: route.callers, receivers: route.receivers,\n })), reviewFiles, issues, limitations: uniqueLimits, bridgeLimitations: joined.limitations,\n ...(context.messages === undefined ? {} : { messageLimitations: messages.limitations }),\n producers: analyses.map(({ id, platform, tool, truncated }) => ({ analysis: id, platform, tool, truncated })),\n summary: { selectedSymbols: roots.size, affectedSymbols: affected.filter(({ subject }) => subject.kind === 'symbol').length,\n bridgeBoundaries: affected.filter(({ subject }) => subject.kind === 'bridge').length, reviewFiles: reviewFiles.length,\n errors: issues.filter(({ severity }) => severity === 'error').length,\n warnings: issues.filter(({ severity }) => severity === 'warning').length, evidenceGaps: uniqueLimits.length },\n };\n}\n\n/** 관찰된 영향과 \"영향 없음\"의 보증을 구분하며 관련 공백은 CI 검토를 요구한다. */\nexport function hasPreflightBlockers(report: PreflightReport): boolean {\n return report.status === 'unobserved' || report.summary.errors > 0 || report.summary.evidenceGaps > 0;\n}\n\n/** 언어 ID를 다른 언어의 같은 문자열과 혼동하지 않는 키다. */\nfunction symbolKey(platform: Language, id: string): string { return JSON.stringify([platform, id]); }\n\n/** 호출 위치와 선언 위치의 의미를 바꾸지 않고 정확한 fact binding만 찾는다. */\nfunction locationKey(platform: string, location: { path: string; line?: number; column?: number }): string {\n return JSON.stringify([platform, location.path, location.line, location.column]);\n}\n"]}
@@ -94,8 +94,15 @@ function makeQueryResult(target, name, kind, usedBy, dependsOn) {
94
94
  dependsOn,
95
95
  };
96
96
  }
97
- /** qualifiedName 구분자와 이스케이프 문자를 가역적인 퍼센트 표기로 바꾼다. */
97
+ /**
98
+ * qualifiedName 구분자(:와 #)와 이스케이프 문자(%)를 가역적인 퍼센트 표기로
99
+ * 바꾼다. 세 문자를 모두 이스케이프하므로 첫 `:`와 `#` 기준으로 나눠 되돌릴 수
100
+ * 있다 — 채널·메서드 이름에 `:`가 들어도 소비자의 분해가 모호해지지 않는다.
101
+ */
98
102
  function encodeSubjectComponent(value) {
99
- return value.replaceAll('%', '%25').replaceAll('#', '%23');
103
+ return value
104
+ .replaceAll('%', '%25')
105
+ .replaceAll('#', '%23')
106
+ .replaceAll(':', '%3A');
100
107
  }
101
108
  //# sourceMappingURL=query.js.map
@@ -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/** qualifiedName 구분자와 이스케이프 문자를 가역적인 퍼센트 표기로 바꾼다. */\nfunction encodeSubjectComponent(value: string): string {\n return value.replaceAll('%', '%25').replaceAll('#', '%23');\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} 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"]}
@@ -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"]}