isthmus-cli 0.5.0 → 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.
- package/README.ko.md +49 -28
- package/README.md +55 -31
- package/Skills/isthmus/SKILL.md +93 -4
- package/dist/cli/command-support.d.ts +1 -1
- package/dist/cli/command-support.js +2 -2
- package/dist/cli/command-support.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/main.js +15 -0
- package/dist/cli/main.js.map +1 -1
- 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/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/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 +51 -0
- package/dist/exchange/messages.js +126 -0
- package/dist/exchange/messages.js.map +1 -0
- package/dist/exchange/parse.d.ts +8 -0
- package/dist/exchange/parse.js +12 -8
- 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/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 +23 -0
- package/dist/join/messages.js +80 -0
- package/dist/join/messages.js.map +1 -0
- package/dist/report/diff.js +4 -3
- package/dist/report/diff.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 +189 -0
- package/dist/report/preflight-view.js.map +1 -0
- package/dist/report/preflight.d.ts +104 -0
- package/dist/report/preflight.js +296 -0
- package/dist/report/preflight.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/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-MESSAGES.md +102 -0
- package/docs/GRAPH-EXCHANGE.md +255 -0
- package/docs/IMPACT.md +96 -0
- package/docs/PREFLIGHT.md +295 -0
- package/docs/RUNTIME.md +172 -0
- package/docs/TOOLCHAIN.md +101 -0
- package/package.json +11 -2
- package/scripts/build-preflight-toolchain.mjs +186 -0
- package/scripts/capture-preflight.mjs +392 -0
- 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"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ImpactSelection } from '../exchange/impact-selection.ts';
|
|
2
|
+
import type { BridgeLocation } from '../exchange/parse.ts';
|
|
3
|
+
import type { BridgeRuntimeDocument, RuntimeRoute } from '../exchange/runtime.ts';
|
|
4
|
+
import type { BridgeJoinResult } from '../join/join.ts';
|
|
5
|
+
import type { SelectedBridgeFact } from './impact.ts';
|
|
6
|
+
/** CLI/CI 호출자가 선언한 현재 분석 revision과 실제 실행 기록이다. */
|
|
7
|
+
export interface ImpactRuntimeInput {
|
|
8
|
+
readonly document: BridgeRuntimeDocument;
|
|
9
|
+
readonly revision: string;
|
|
10
|
+
}
|
|
11
|
+
/** 인스턴스별 실제 주소. 정적 핸들러는 실행 대상 확정이 아닌 검토 후보다. */
|
|
12
|
+
export interface RuntimeImpactRoute extends RuntimeRoute {
|
|
13
|
+
readonly instance: string;
|
|
14
|
+
readonly observedCalls: number;
|
|
15
|
+
readonly failedCalls: number;
|
|
16
|
+
readonly pendingCalls: number;
|
|
17
|
+
readonly callers: readonly BridgeLocation[];
|
|
18
|
+
readonly callersOmitted: number;
|
|
19
|
+
readonly staticStatus: 'candidates' | 'unobserved' | 'unsupported';
|
|
20
|
+
}
|
|
21
|
+
/** 정적 사실과 구분해서 보존하는 변경 관련 런타임 증거다. */
|
|
22
|
+
export interface RuntimeImpactEvidence {
|
|
23
|
+
readonly run: BridgeRuntimeDocument['run'];
|
|
24
|
+
readonly tool: BridgeRuntimeDocument['tool'];
|
|
25
|
+
readonly revision: string;
|
|
26
|
+
readonly stale: boolean;
|
|
27
|
+
readonly incomplete: boolean;
|
|
28
|
+
readonly droppedEvents: number;
|
|
29
|
+
readonly selectedEvents: number;
|
|
30
|
+
readonly failedCalls: number;
|
|
31
|
+
readonly pendingCalls: number;
|
|
32
|
+
readonly routes: readonly RuntimeImpactRoute[];
|
|
33
|
+
readonly reviewFiles: readonly string[];
|
|
34
|
+
}
|
|
35
|
+
/** 정적 선택에서 놓친 동적 호출을 실제 실행 주소로 찾되 다른 플랫폼을 추측하지 않는다. */
|
|
36
|
+
export declare function collectRuntimeImpact(joined: BridgeJoinResult, project: string | undefined, selection: ImpactSelection, selected: readonly SelectedBridgeFact[], input: ImpactRuntimeInput, nativePlatforms: ReadonlySet<string>): RuntimeImpactEvidence;
|
|
37
|
+
/** 기록의 부재·실패·불완전성 또는 정적 후보를 찾지 못한 주소는 검토가 필요하다. */
|
|
38
|
+
export declare function hasRuntimeImpactGaps(runtime: RuntimeImpactEvidence): boolean;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { compareStrings } from "../compare.js";
|
|
2
|
+
import { isSafeNonEmptyString } from "../exchange/parse.js";
|
|
3
|
+
import { RuntimeValidationError } from "../exchange/runtime.js";
|
|
4
|
+
/** 정적 선택에서 놓친 동적 호출을 실제 실행 주소로 찾되 다른 플랫폼을 추측하지 않는다. */
|
|
5
|
+
export function collectRuntimeImpact(joined, project, selection, selected, input, nativePlatforms) {
|
|
6
|
+
const { document, revision } = input;
|
|
7
|
+
if (document.project !== project)
|
|
8
|
+
throw new RuntimeValidationError('Runtime and static inputs must describe the same project.');
|
|
9
|
+
if (!isSafeNonEmptyString(revision))
|
|
10
|
+
throw new RuntimeValidationError('Expected a current analysis revision.');
|
|
11
|
+
const stale = document.revision !== revision;
|
|
12
|
+
const native = document.run.platform === 'android' ? 'kotlin'
|
|
13
|
+
: document.run.platform === 'ios' || document.run.platform === 'macos' ? 'swift' : undefined;
|
|
14
|
+
const files = new Set(selection.files);
|
|
15
|
+
const locations = new Set(selected.map(({ location }) => locationKey(location)));
|
|
16
|
+
const selectedChannels = new Set();
|
|
17
|
+
const selectedMethods = new Set();
|
|
18
|
+
for (const fact of selected) {
|
|
19
|
+
if (fact.dynamic || fact.channel === null || fact.target !== 'flutter')
|
|
20
|
+
continue;
|
|
21
|
+
if (fact.method === undefined)
|
|
22
|
+
selectedChannels.add(fact.channel);
|
|
23
|
+
else
|
|
24
|
+
selectedMethods.add(methodKey(fact.channel, fact.method));
|
|
25
|
+
}
|
|
26
|
+
const candidates = new Set([...joined.matchedMethods, ...joined.handlersWithoutInvocations]
|
|
27
|
+
.filter(({ target, handlers }) => target === 'flutter' && handlers.some(({ platform }) => platform === native))
|
|
28
|
+
.map(({ channel, method }) => methodKey(channel, method)));
|
|
29
|
+
const groups = new Map();
|
|
30
|
+
const reviewFiles = new Set();
|
|
31
|
+
if (!stale)
|
|
32
|
+
for (const event of document.events) {
|
|
33
|
+
const selectedCaller = event.caller !== undefined &&
|
|
34
|
+
(files.has(event.caller.path) || locations.has(locationKey(event.caller)));
|
|
35
|
+
const selectedRoute = event.transport === 'method-channel' &&
|
|
36
|
+
(selectedChannels.has(event.channel) || selectedMethods.has(methodKey(event.channel, event.method)));
|
|
37
|
+
if (!selectedCaller && !selectedRoute)
|
|
38
|
+
continue;
|
|
39
|
+
const key = JSON.stringify([event.transport, event.channel, event.method ?? null, event.instance]);
|
|
40
|
+
let group = groups.get(key);
|
|
41
|
+
if (group === undefined) {
|
|
42
|
+
group = { event, observedCalls: 0, failedCalls: 0, pendingCalls: 0, callers: new Map() };
|
|
43
|
+
groups.set(key, group);
|
|
44
|
+
}
|
|
45
|
+
group.observedCalls++;
|
|
46
|
+
if (event.outcome === 'error' || event.outcome === 'missing-handler' || event.outcome === 'timeout')
|
|
47
|
+
group.failedCalls++;
|
|
48
|
+
if (event.outcome === 'pending')
|
|
49
|
+
group.pendingCalls++;
|
|
50
|
+
if (event.caller !== undefined) {
|
|
51
|
+
group.callers.set(locationKey(event.caller), event.caller);
|
|
52
|
+
reviewFiles.add(event.caller.path);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const routes = [...groups.entries()].sort(([a], [b]) => compareStrings(a, b))
|
|
56
|
+
.map(([, group]) => {
|
|
57
|
+
const { event, observedCalls, failedCalls, pendingCalls } = group;
|
|
58
|
+
const supported = native !== undefined && nativePlatforms.has(native) &&
|
|
59
|
+
event.transport === 'method-channel';
|
|
60
|
+
return {
|
|
61
|
+
transport: event.transport, channel: event.channel,
|
|
62
|
+
...(event.method === undefined ? {} : { method: event.method }),
|
|
63
|
+
instance: event.instance, observedCalls, failedCalls, pendingCalls,
|
|
64
|
+
callers: [...group.callers.entries()].sort(([a], [b]) => compareStrings(a, b))
|
|
65
|
+
.slice(0, 20).map(([, caller]) => caller),
|
|
66
|
+
callersOmitted: Math.max(0, group.callers.size - 20),
|
|
67
|
+
staticStatus: !supported ? 'unsupported' : candidates.has(methodKey(event.channel, event.method))
|
|
68
|
+
? 'candidates' : 'unobserved',
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
run: document.run, tool: document.tool, revision: document.revision, stale,
|
|
73
|
+
incomplete: document.run.status !== 'completed', droppedEvents: document.droppedEvents,
|
|
74
|
+
selectedEvents: routes.reduce((count, route) => count + route.observedCalls, 0),
|
|
75
|
+
failedCalls: routes.reduce((count, route) => count + route.failedCalls, 0),
|
|
76
|
+
pendingCalls: routes.reduce((count, route) => count + route.pendingCalls, 0), routes,
|
|
77
|
+
reviewFiles: [...reviewFiles].sort(compareStrings),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** 기록의 부재·실패·불완전성 또는 정적 후보를 찾지 못한 주소는 검토가 필요하다. */
|
|
81
|
+
export function hasRuntimeImpactGaps(runtime) {
|
|
82
|
+
return runtime.stale || runtime.incomplete || runtime.droppedEvents > 0 || runtime.selectedEvents === 0 ||
|
|
83
|
+
runtime.failedCalls > 0 || runtime.pendingCalls > 0 ||
|
|
84
|
+
runtime.routes.some(({ staticStatus }) => staticStatus !== 'candidates');
|
|
85
|
+
}
|
|
86
|
+
/** 메서드 이름의 구분자와 undefined를 실제 문자열과 혼동하지 않는다. */
|
|
87
|
+
function methodKey(channel, method) {
|
|
88
|
+
return JSON.stringify([channel, method ?? null]);
|
|
89
|
+
}
|
|
90
|
+
/** 명시된 호출 지점만 연결하고 주변 선언·인접 줄을 추측하지 않는다. */
|
|
91
|
+
function locationKey(location) {
|
|
92
|
+
return JSON.stringify([location.path, location.line, location.column]);
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=runtime-impact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-impact.js","sourceRoot":"","sources":["../../src/report/runtime-impact.ts"],"names":[],"mappings":"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","sourcesContent":["import { compareStrings } from '../compare.ts';\nimport type { ImpactSelection } from '../exchange/impact-selection.ts';\nimport { isSafeNonEmptyString } from '../exchange/parse.ts';\nimport type { BridgeLocation } from '../exchange/parse.ts';\nimport { RuntimeValidationError } from '../exchange/runtime.ts';\nimport type { BridgeRuntimeDocument, RuntimeEvent, RuntimeRoute } from '../exchange/runtime.ts';\nimport type { BridgeJoinResult } from '../join/join.ts';\nimport type { SelectedBridgeFact } from './impact.ts';\n\n/** CLI/CI 호출자가 선언한 현재 분석 revision과 실제 실행 기록이다. */\nexport interface ImpactRuntimeInput {\n readonly document: BridgeRuntimeDocument;\n readonly revision: string;\n}\n\n/** 인스턴스별 실제 주소. 정적 핸들러는 실행 대상 확정이 아닌 검토 후보다. */\nexport interface RuntimeImpactRoute extends RuntimeRoute {\n readonly instance: string;\n readonly observedCalls: number;\n readonly failedCalls: number;\n readonly pendingCalls: number;\n readonly callers: readonly BridgeLocation[];\n readonly callersOmitted: number;\n readonly staticStatus: 'candidates' | 'unobserved' | 'unsupported';\n}\n\n/** 정적 사실과 구분해서 보존하는 변경 관련 런타임 증거다. */\nexport interface RuntimeImpactEvidence {\n readonly run: BridgeRuntimeDocument['run'];\n readonly tool: BridgeRuntimeDocument['tool'];\n readonly revision: string;\n readonly stale: boolean;\n readonly incomplete: boolean;\n readonly droppedEvents: number;\n readonly selectedEvents: number;\n readonly failedCalls: number;\n readonly pendingCalls: number;\n readonly routes: readonly RuntimeImpactRoute[];\n readonly reviewFiles: readonly string[];\n}\n\n/** 정적 선택에서 놓친 동적 호출을 실제 실행 주소로 찾되 다른 플랫폼을 추측하지 않는다. */\nexport function collectRuntimeImpact(\n joined: BridgeJoinResult, project: string | undefined, selection: ImpactSelection,\n selected: readonly SelectedBridgeFact[], input: ImpactRuntimeInput,\n nativePlatforms: ReadonlySet<string>,\n): RuntimeImpactEvidence {\n const { document, revision } = input;\n if (document.project !== project) throw new RuntimeValidationError('Runtime and static inputs must describe the same project.');\n if (!isSafeNonEmptyString(revision)) throw new RuntimeValidationError('Expected a current analysis revision.');\n const stale = document.revision !== revision;\n const native = document.run.platform === 'android' ? 'kotlin'\n : document.run.platform === 'ios' || document.run.platform === 'macos' ? 'swift' : undefined;\n const files = new Set(selection.files);\n const locations = new Set(selected.map(({ location }) => locationKey(location)));\n const selectedChannels = new Set<string>();\n const selectedMethods = new Set<string>();\n for (const fact of selected) {\n if (fact.dynamic || fact.channel === null || fact.target !== 'flutter') continue;\n if (fact.method === undefined) selectedChannels.add(fact.channel);\n else selectedMethods.add(methodKey(fact.channel, fact.method));\n }\n const candidates = new Set([...joined.matchedMethods, ...joined.handlersWithoutInvocations]\n .filter(({ target, handlers }) => target === 'flutter' && handlers.some(({ platform }) => platform === native))\n .map(({ channel, method }) => methodKey(channel, method)));\n const groups = new Map<string, {\n event: RuntimeEvent; observedCalls: number; failedCalls: number; pendingCalls: number;\n callers: Map<string, BridgeLocation>;\n }>();\n const reviewFiles = new Set<string>();\n if (!stale) for (const event of document.events) {\n const selectedCaller = event.caller !== undefined &&\n (files.has(event.caller.path) || locations.has(locationKey(event.caller)));\n const selectedRoute = event.transport === 'method-channel' &&\n (selectedChannels.has(event.channel) || selectedMethods.has(methodKey(event.channel, event.method)));\n if (!selectedCaller && !selectedRoute) continue;\n const key = JSON.stringify([event.transport, event.channel, event.method ?? null, event.instance]);\n let group = groups.get(key);\n if (group === undefined) {\n group = { event, observedCalls: 0, failedCalls: 0, pendingCalls: 0, callers: new Map() };\n groups.set(key, group);\n }\n group.observedCalls++;\n if (event.outcome === 'error' || event.outcome === 'missing-handler' || event.outcome === 'timeout') group.failedCalls++;\n if (event.outcome === 'pending') group.pendingCalls++;\n if (event.caller !== undefined) {\n group.callers.set(locationKey(event.caller), event.caller);\n reviewFiles.add(event.caller.path);\n }\n }\n const routes = [...groups.entries()].sort(([a], [b]) => compareStrings(a, b))\n .map(([, group]): RuntimeImpactRoute => {\n const { event, observedCalls, failedCalls, pendingCalls } = group;\n const supported = native !== undefined && nativePlatforms.has(native) &&\n event.transport === 'method-channel';\n return {\n transport: event.transport, channel: event.channel,\n ...(event.method === undefined ? {} : { method: event.method }),\n instance: event.instance, observedCalls, failedCalls, pendingCalls,\n callers: [...group.callers.entries()].sort(([a], [b]) => compareStrings(a, b))\n .slice(0, 20).map(([, caller]) => caller),\n callersOmitted: Math.max(0, group.callers.size - 20),\n staticStatus: !supported ? 'unsupported' : candidates.has(methodKey(event.channel, event.method))\n ? 'candidates' : 'unobserved',\n };\n });\n return {\n run: document.run, tool: document.tool, revision: document.revision, stale,\n incomplete: document.run.status !== 'completed', droppedEvents: document.droppedEvents,\n selectedEvents: routes.reduce((count, route) => count + route.observedCalls, 0),\n failedCalls: routes.reduce((count, route) => count + route.failedCalls, 0),\n pendingCalls: routes.reduce((count, route) => count + route.pendingCalls, 0), routes,\n reviewFiles: [...reviewFiles].sort(compareStrings),\n };\n}\n\n/** 기록의 부재·실패·불완전성 또는 정적 후보를 찾지 못한 주소는 검토가 필요하다. */\nexport function hasRuntimeImpactGaps(runtime: RuntimeImpactEvidence): boolean {\n return runtime.stale || runtime.incomplete || runtime.droppedEvents > 0 || runtime.selectedEvents === 0 ||\n runtime.failedCalls > 0 || runtime.pendingCalls > 0 ||\n runtime.routes.some(({ staticStatus }) => staticStatus !== 'candidates');\n}\n\n/** 메서드 이름의 구분자와 undefined를 실제 문자열과 혼동하지 않는다. */\nfunction methodKey(channel: string, method: string | undefined): string {\n return JSON.stringify([channel, method ?? null]);\n}\n\n/** 명시된 호출 지점만 연결하고 주변 선언·인접 줄을 추측하지 않는다. */\nfunction locationKey(location: BridgeLocation): string {\n return JSON.stringify([location.path, location.line, location.column]);\n}\n"]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { BridgeRuntimeDocument, RuntimeEvent, RuntimeExpectation, RuntimeExpectations } from '../exchange/runtime.ts';
|
|
2
|
+
/** 전체 평가와 무관한 증거 표시 상한. 생략 수를 명시한다. */
|
|
3
|
+
export declare const MAX_RUNTIME_EVIDENCE_PER_CHECK = 20;
|
|
4
|
+
/** 원본 run과 sequence로 재현 가능한 통신 결과 참조다. */
|
|
5
|
+
export interface RuntimeEvidence {
|
|
6
|
+
readonly runId: string;
|
|
7
|
+
readonly sequence: number;
|
|
8
|
+
readonly instance: string;
|
|
9
|
+
readonly outcome: RuntimeEvent['outcome'];
|
|
10
|
+
readonly caller?: RuntimeEvent['caller'];
|
|
11
|
+
}
|
|
12
|
+
/** 기대 항목 하나의 결과와 관찰 근거다. */
|
|
13
|
+
export interface RuntimeCheckResult {
|
|
14
|
+
readonly expected: RuntimeExpectation;
|
|
15
|
+
readonly status: 'passed' | 'failed' | 'unobserved' | 'incomplete';
|
|
16
|
+
readonly observedCalls: number;
|
|
17
|
+
readonly evidence: readonly RuntimeEvidence[];
|
|
18
|
+
readonly evidenceOmitted: number;
|
|
19
|
+
}
|
|
20
|
+
/** 선언한 시나리오의 통신 검증 결과. 모든 실행 경로를 검증했다는 뜻은 아니다. */
|
|
21
|
+
export interface RuntimeVerificationReport {
|
|
22
|
+
readonly format: 'isthmus-runtime-check';
|
|
23
|
+
readonly version: 1;
|
|
24
|
+
readonly project: string;
|
|
25
|
+
readonly revision: string;
|
|
26
|
+
readonly scope: 'declared-scenarios';
|
|
27
|
+
readonly complete: false;
|
|
28
|
+
readonly status: 'passed' | 'failed' | 'incomplete';
|
|
29
|
+
readonly summary: {
|
|
30
|
+
readonly runs: number;
|
|
31
|
+
readonly staleRuns: number;
|
|
32
|
+
readonly incompleteRuns: number;
|
|
33
|
+
readonly droppedEvents: number;
|
|
34
|
+
readonly failedCalls: number;
|
|
35
|
+
readonly expectedFailedCalls: number;
|
|
36
|
+
readonly unexpectedFailedCalls: number;
|
|
37
|
+
readonly pendingCalls: number;
|
|
38
|
+
readonly passedChecks: number;
|
|
39
|
+
readonly failedChecks: number;
|
|
40
|
+
readonly unobservedChecks: number;
|
|
41
|
+
readonly incompleteChecks: number;
|
|
42
|
+
};
|
|
43
|
+
readonly checks: readonly RuntimeCheckResult[];
|
|
44
|
+
readonly failures: ReadonlyArray<{
|
|
45
|
+
readonly runId: string;
|
|
46
|
+
readonly event: RuntimeEvent;
|
|
47
|
+
}>;
|
|
48
|
+
readonly runs: ReadonlyArray<Omit<BridgeRuntimeDocument, 'events' | 'project' | 'format' | 'version'> & {
|
|
49
|
+
readonly stale: boolean;
|
|
50
|
+
readonly observedEvents: number;
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
53
|
+
/** 검증된 관찰을 라우팅 키로 색인해 기대 항목마다 전체 이벤트를 다시 훑지 않는다. */
|
|
54
|
+
export declare function verifyRuntimeEvidence(expectations: RuntimeExpectations, documents: readonly BridgeRuntimeDocument[]): RuntimeVerificationReport;
|