isthmus-cli 0.1.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/LICENSE +21 -0
- package/README.md +151 -0
- package/Skills/isthmus/SKILL.md +47 -0
- package/dist/cli/check-command.d.ts +12 -0
- package/dist/cli/check-command.js +48 -0
- package/dist/cli/check-command.js.map +1 -0
- package/dist/cli/graph-command.d.ts +5 -0
- package/dist/cli/graph-command.js +62 -0
- package/dist/cli/graph-command.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +111 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/query-command.d.ts +5 -0
- package/dist/cli/query-command.js +48 -0
- package/dist/cli/query-command.js.map +1 -0
- package/dist/cli/retentions-command.d.ts +7 -0
- package/dist/cli/retentions-command.js +55 -0
- package/dist/cli/retentions-command.js.map +1 -0
- package/dist/compare.d.ts +2 -0
- package/dist/compare.js +9 -0
- package/dist/compare.js.map +1 -0
- package/dist/exchange/parse.d.ts +48 -0
- package/dist/exchange/parse.js +148 -0
- package/dist/exchange/parse.js.map +1 -0
- package/dist/join/join.d.ts +59 -0
- package/dist/join/join.js +218 -0
- package/dist/join/join.js.map +1 -0
- package/dist/report/check-report.d.ts +32 -0
- package/dist/report/check-report.js +46 -0
- package/dist/report/check-report.js.map +1 -0
- package/dist/report/graph.d.ts +30 -0
- package/dist/report/graph.js +143 -0
- package/dist/report/graph.js.map +1 -0
- package/dist/report/query.d.ts +28 -0
- package/dist/report/query.js +112 -0
- package/dist/report/query.js.map +1 -0
- package/dist/report/retentions.d.ts +39 -0
- package/dist/report/retentions.js +49 -0
- package/dist/report/retentions.js.map +1 -0
- package/dist/report/sorted-json.d.ts +2 -0
- package/dist/report/sorted-json.js +16 -0
- package/dist/report/sorted-json.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** bridge-facts 생산 플랫폼이다. */
|
|
2
|
+
export type BridgePlatform = 'dart' | 'swift' | 'kotlin' | 'js';
|
|
3
|
+
/** 언어 경계를 잇는 메커니즘이다. */
|
|
4
|
+
export type BridgeTarget = 'flutter' | 'react-native' | 'capacitor';
|
|
5
|
+
/** 교환 형식의 사실 종류다. */
|
|
6
|
+
export type BridgeFactKind = 'channel-create' | 'channel-register' | 'method-invoke' | 'method-handle' | 'module-export' | 'module-import' | 'component-export' | 'component-require';
|
|
7
|
+
/** 사실의 프로젝트 상대 소스 위치다. */
|
|
8
|
+
export interface BridgeLocation {
|
|
9
|
+
readonly path: string;
|
|
10
|
+
readonly line: number;
|
|
11
|
+
readonly column: number;
|
|
12
|
+
}
|
|
13
|
+
/** 사실을 감싸는 언어별 선언 식별자다. */
|
|
14
|
+
export interface BridgeSymbol {
|
|
15
|
+
readonly qualifiedName: string;
|
|
16
|
+
readonly usr?: string;
|
|
17
|
+
}
|
|
18
|
+
/** 생산 도구 하나가 관찰한 언어 경계 사실이다. */
|
|
19
|
+
export interface BridgeFact {
|
|
20
|
+
readonly kind: BridgeFactKind;
|
|
21
|
+
readonly channel: string | null;
|
|
22
|
+
readonly method?: string;
|
|
23
|
+
readonly dynamic: boolean;
|
|
24
|
+
readonly location: BridgeLocation;
|
|
25
|
+
readonly symbol?: BridgeSymbol;
|
|
26
|
+
}
|
|
27
|
+
/** bridge-facts 버전 1 문서다. */
|
|
28
|
+
export interface BridgeFactsDocument {
|
|
29
|
+
readonly format: 'bridge-facts';
|
|
30
|
+
readonly version: 1;
|
|
31
|
+
readonly tool: Readonly<{
|
|
32
|
+
name: string;
|
|
33
|
+
version: string;
|
|
34
|
+
}>;
|
|
35
|
+
readonly generatedAt: string;
|
|
36
|
+
readonly platform: BridgePlatform;
|
|
37
|
+
readonly target: BridgeTarget | null;
|
|
38
|
+
readonly project: string;
|
|
39
|
+
readonly facts: readonly BridgeFact[];
|
|
40
|
+
readonly limitations: readonly string[];
|
|
41
|
+
}
|
|
42
|
+
/** 외부 교환 문서가 v1 계약을 어겼음을 나타낸다. */
|
|
43
|
+
export declare class BridgeFactsValidationError extends Error {
|
|
44
|
+
/** 입력 내용을 노출하지 않는 안전한 메시지를 보존한다. */
|
|
45
|
+
constructor(message: string);
|
|
46
|
+
}
|
|
47
|
+
/** 신뢰하지 않는 JSON 값을 검증된 bridge-facts v1 문서로 바꾼다. */
|
|
48
|
+
export declare function parseBridgeFactsDocument(input: unknown): BridgeFactsDocument;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/** 외부 교환 문서가 v1 계약을 어겼음을 나타낸다. */
|
|
2
|
+
export class BridgeFactsValidationError extends Error {
|
|
3
|
+
/** 입력 내용을 노출하지 않는 안전한 메시지를 보존한다. */
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'BridgeFactsValidationError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/** 신뢰하지 않는 JSON 값을 검증된 bridge-facts v1 문서로 바꾼다. */
|
|
10
|
+
export function parseBridgeFactsDocument(input) {
|
|
11
|
+
if (!isJsonObject(input)) {
|
|
12
|
+
throw new BridgeFactsValidationError('Bridge facts must be a JSON object.');
|
|
13
|
+
}
|
|
14
|
+
if (input.format !== 'bridge-facts') {
|
|
15
|
+
throw new BridgeFactsValidationError('Expected format "bridge-facts".');
|
|
16
|
+
}
|
|
17
|
+
if (input.version !== 1) {
|
|
18
|
+
throw new BridgeFactsValidationError('Unsupported bridge-facts version; expected version 1.');
|
|
19
|
+
}
|
|
20
|
+
validateDocumentMetadata(input);
|
|
21
|
+
return input;
|
|
22
|
+
}
|
|
23
|
+
/** 문서 수준 필드가 v1 타입과 허용값을 따르는지 검증한다. */
|
|
24
|
+
function validateDocumentMetadata(document) {
|
|
25
|
+
validateTool(document.tool);
|
|
26
|
+
if (!isTimestamp(document.generatedAt))
|
|
27
|
+
fail('Invalid generatedAt timestamp.');
|
|
28
|
+
if (!bridgePlatforms.has(document.platform))
|
|
29
|
+
fail('Unsupported bridge platform.');
|
|
30
|
+
if (document.target !== null && !bridgeTargets.has(document.target)) {
|
|
31
|
+
fail('Unsupported bridge target.');
|
|
32
|
+
}
|
|
33
|
+
if (!isNonEmptyString(document.project))
|
|
34
|
+
fail('Invalid project path.');
|
|
35
|
+
if (!Array.isArray(document.facts))
|
|
36
|
+
fail('Facts must be an array.');
|
|
37
|
+
document.facts.forEach(validateFact);
|
|
38
|
+
if (!isStringArray(document.limitations))
|
|
39
|
+
fail('Limitations must be strings.');
|
|
40
|
+
}
|
|
41
|
+
/** 사실 하나의 조인 키와 증거 필드를 검증한다. */
|
|
42
|
+
function validateFact(value, index) {
|
|
43
|
+
if (!isJsonObject(value))
|
|
44
|
+
fail(`Fact at index ${index} must be a JSON object.`);
|
|
45
|
+
if (!bridgeFactKinds.has(value.kind))
|
|
46
|
+
fail(`Invalid fact kind at index ${index}.`);
|
|
47
|
+
if (value.channel !== null && !isSafeNonEmptyString(value.channel)) {
|
|
48
|
+
fail(`Invalid fact channel at index ${index}.`);
|
|
49
|
+
}
|
|
50
|
+
if (methodFactKinds.has(value.kind) && !isSafeNonEmptyString(value.method)) {
|
|
51
|
+
fail(`Method fact at index ${index} requires a method name.`);
|
|
52
|
+
}
|
|
53
|
+
if (typeof value.dynamic !== 'boolean')
|
|
54
|
+
fail(`Invalid dynamic flag at index ${index}.`);
|
|
55
|
+
validateLocation(value.location, index);
|
|
56
|
+
validateSymbol(value.symbol, index);
|
|
57
|
+
}
|
|
58
|
+
/** 사실 위치가 상대 경로와 1부터 시작하는 줄·열을 갖는지 검증한다. */
|
|
59
|
+
function validateLocation(value, index) {
|
|
60
|
+
if (!isJsonObject(value) ||
|
|
61
|
+
!isProjectRelativePath(value.path) ||
|
|
62
|
+
!isPositiveInteger(value.line) ||
|
|
63
|
+
!isPositiveInteger(value.column)) {
|
|
64
|
+
fail(`Invalid fact location at index ${index}.`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** 절대·상위 경로와 제어 문자를 제외한 프로젝트 상대 경로인지 확인한다. */
|
|
68
|
+
function isProjectRelativePath(value) {
|
|
69
|
+
if (!isNonEmptyString(value))
|
|
70
|
+
return false;
|
|
71
|
+
if (/^(?:[/\\]|[A-Za-z]:)/u.test(value))
|
|
72
|
+
return false;
|
|
73
|
+
if (/[\u0000-\u001f\u007f]/u.test(value))
|
|
74
|
+
return false;
|
|
75
|
+
return !value.split(/[/\\]/u).includes('..');
|
|
76
|
+
}
|
|
77
|
+
/** 선택 symbol의 이름과 USR이 비어 있지 않은지 검증한다. */
|
|
78
|
+
function validateSymbol(value, index) {
|
|
79
|
+
if (value === undefined)
|
|
80
|
+
return;
|
|
81
|
+
if (!isJsonObject(value) ||
|
|
82
|
+
!isSafeNonEmptyString(value.qualifiedName) ||
|
|
83
|
+
(value.usr !== undefined && !isSafeNonEmptyString(value.usr))) {
|
|
84
|
+
fail(`Invalid fact symbol at index ${index}.`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** 생산 도구 이름과 버전이 비어 있지 않은지 검증한다. */
|
|
88
|
+
function validateTool(value) {
|
|
89
|
+
if (!isJsonObject(value) ||
|
|
90
|
+
!isNonEmptyString(value.name) ||
|
|
91
|
+
!isNonEmptyString(value.version)) {
|
|
92
|
+
fail('Invalid tool metadata.');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** ISO 계열 생성 시각으로 해석할 수 있는지 확인한다. */
|
|
96
|
+
function isTimestamp(value) {
|
|
97
|
+
return (typeof value === 'string' &&
|
|
98
|
+
timestampPattern.test(value) &&
|
|
99
|
+
!Number.isNaN(Date.parse(value)));
|
|
100
|
+
}
|
|
101
|
+
/** 공백만 있지 않은 문자열인지 확인한다. */
|
|
102
|
+
function isNonEmptyString(value) {
|
|
103
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
104
|
+
}
|
|
105
|
+
/** 조인 키를 깨뜨리는 제어 문자가 없는 비어 있지 않은 문자열인지 확인한다. */
|
|
106
|
+
function isSafeNonEmptyString(value) {
|
|
107
|
+
return isNonEmptyString(value) && !controlCharacterPattern.test(value);
|
|
108
|
+
}
|
|
109
|
+
/** 모든 원소가 문자열인 배열인지 확인한다. */
|
|
110
|
+
function isStringArray(value) {
|
|
111
|
+
return Array.isArray(value) && value.every((item) => typeof item === 'string');
|
|
112
|
+
}
|
|
113
|
+
/** 1부터 시작하는 정수인지 확인한다. */
|
|
114
|
+
function isPositiveInteger(value) {
|
|
115
|
+
return Number.isInteger(value) && Number(value) >= 1;
|
|
116
|
+
}
|
|
117
|
+
/** 입력 값을 포함하지 않는 검증 오류를 던진다. */
|
|
118
|
+
function fail(message) {
|
|
119
|
+
throw new BridgeFactsValidationError(message);
|
|
120
|
+
}
|
|
121
|
+
/** 지원하는 생산 플랫폼 집합이다. */
|
|
122
|
+
const bridgePlatforms = new Set(['dart', 'swift', 'kotlin', 'js']);
|
|
123
|
+
/** 지원하는 브리지 메커니즘 집합이다. */
|
|
124
|
+
const bridgeTargets = new Set([
|
|
125
|
+
'flutter',
|
|
126
|
+
'react-native',
|
|
127
|
+
'capacitor',
|
|
128
|
+
]);
|
|
129
|
+
/** 버전 1이 정의한 사실 종류 집합이다. */
|
|
130
|
+
const bridgeFactKinds = new Set([
|
|
131
|
+
'channel-create',
|
|
132
|
+
'channel-register',
|
|
133
|
+
'method-invoke',
|
|
134
|
+
'method-handle',
|
|
135
|
+
'module-export',
|
|
136
|
+
'module-import',
|
|
137
|
+
'component-export',
|
|
138
|
+
'component-require',
|
|
139
|
+
]);
|
|
140
|
+
/** method 필드가 필수인 사실 종류다. */
|
|
141
|
+
const methodFactKinds = new Set(['method-invoke', 'method-handle']);
|
|
142
|
+
const timestampPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/u;
|
|
143
|
+
const controlCharacterPattern = /[\u0000-\u001f\u007f]/u;
|
|
144
|
+
/** 배열과 null을 제외한 JSON 객체인지 확인한다. */
|
|
145
|
+
function isJsonObject(value) {
|
|
146
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/exchange/parse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { BridgeFactsDocument, BridgeLocation, BridgePlatform, BridgeSymbol, BridgeTarget } from '../exchange/parse.ts';
|
|
2
|
+
/** 한 언어 문서가 제공한 브리지 증거 위치다. */
|
|
3
|
+
export interface BridgeEndpoint {
|
|
4
|
+
readonly platform: BridgePlatform;
|
|
5
|
+
readonly location: BridgeLocation;
|
|
6
|
+
readonly symbol?: BridgeSymbol;
|
|
7
|
+
}
|
|
8
|
+
/** 논리 채널 하나에 모인 양쪽 생성·등록 증거다. */
|
|
9
|
+
export interface MatchedChannel {
|
|
10
|
+
readonly target: BridgeTarget;
|
|
11
|
+
readonly channel: string;
|
|
12
|
+
readonly creations: readonly BridgeEndpoint[];
|
|
13
|
+
readonly registrations: readonly BridgeEndpoint[];
|
|
14
|
+
}
|
|
15
|
+
/** 등록을 찾지 못한 논리 채널과 모든 생성 증거다. */
|
|
16
|
+
export interface UnregisteredChannelCreation {
|
|
17
|
+
readonly target: BridgeTarget;
|
|
18
|
+
readonly channel: string;
|
|
19
|
+
readonly creations: readonly BridgeEndpoint[];
|
|
20
|
+
}
|
|
21
|
+
/** 논리 메서드 하나에 모인 양쪽 호출·핸들러 증거다. */
|
|
22
|
+
export interface MatchedMethod {
|
|
23
|
+
readonly target: BridgeTarget;
|
|
24
|
+
readonly channel: string;
|
|
25
|
+
readonly method: string;
|
|
26
|
+
readonly invocations: readonly BridgeEndpoint[];
|
|
27
|
+
readonly handlers: readonly BridgeEndpoint[];
|
|
28
|
+
}
|
|
29
|
+
/** 핸들러를 찾지 못한 논리 호출과 모든 호출 증거다. */
|
|
30
|
+
export interface UnhandledInvocation {
|
|
31
|
+
readonly target: BridgeTarget;
|
|
32
|
+
readonly channel: string;
|
|
33
|
+
readonly method: string;
|
|
34
|
+
readonly invocations: readonly BridgeEndpoint[];
|
|
35
|
+
}
|
|
36
|
+
/** 호출자를 찾지 못한 논리 핸들러와 모든 네이티브 증거다. */
|
|
37
|
+
export interface HandlerWithoutInvocation {
|
|
38
|
+
readonly target: BridgeTarget;
|
|
39
|
+
readonly channel: string;
|
|
40
|
+
readonly method: string;
|
|
41
|
+
readonly handlers: readonly BridgeEndpoint[];
|
|
42
|
+
}
|
|
43
|
+
/** 생산 문서가 밝힌 분석 한계와 출처다. */
|
|
44
|
+
export interface JoinLimitation {
|
|
45
|
+
readonly platform: BridgePlatform | 'cross-platform';
|
|
46
|
+
readonly tool: string;
|
|
47
|
+
readonly message: string;
|
|
48
|
+
}
|
|
49
|
+
/** 검증된 교환 문서들의 논리 조인 결과다. */
|
|
50
|
+
export interface BridgeJoinResult {
|
|
51
|
+
readonly matchedChannels: readonly MatchedChannel[];
|
|
52
|
+
readonly unregisteredChannelCreations: readonly UnregisteredChannelCreation[];
|
|
53
|
+
readonly matchedMethods: readonly MatchedMethod[];
|
|
54
|
+
readonly unhandledInvocations: readonly UnhandledInvocation[];
|
|
55
|
+
readonly handlersWithoutInvocations: readonly HandlerWithoutInvocation[];
|
|
56
|
+
readonly limitations: readonly JoinLimitation[];
|
|
57
|
+
}
|
|
58
|
+
/** 위치가 아니라 문자열 키로 검증된 교환 문서를 조인한다. */
|
|
59
|
+
export declare function joinBridgeDocuments(documents: readonly BridgeFactsDocument[]): BridgeJoinResult;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { compareStrings } from "../compare.js";
|
|
2
|
+
/** 위치가 아니라 문자열 키로 검증된 교환 문서를 조인한다. */
|
|
3
|
+
export function joinBridgeDocuments(documents) {
|
|
4
|
+
const limitations = collectLimitations(documents);
|
|
5
|
+
if (documents.some(hasMixedTargets))
|
|
6
|
+
return emptyJoinResult(limitations);
|
|
7
|
+
const groups = collectChannelGroups(documents);
|
|
8
|
+
const matchedChannels = [...groups.values()]
|
|
9
|
+
.filter((group) => group.creations.length > 0 && group.registrations.length > 0)
|
|
10
|
+
.sort(compareChannels);
|
|
11
|
+
const unregisteredChannelCreations = [...groups.values()]
|
|
12
|
+
.filter((group) => group.creations.length > 0 && group.registrations.length === 0)
|
|
13
|
+
.map(({ target, channel, creations }) => ({ target, channel, creations }))
|
|
14
|
+
.sort(compareChannels);
|
|
15
|
+
const methodGroups = collectMethodGroups(documents);
|
|
16
|
+
const matchedMethods = [...methodGroups.values()]
|
|
17
|
+
.filter((group) => group.invocations.length > 0 && group.handlers.length > 0)
|
|
18
|
+
.sort(compareMethodKeys);
|
|
19
|
+
const unhandledInvocations = [...methodGroups.values()]
|
|
20
|
+
.filter((group) => group.invocations.length > 0 && group.handlers.length === 0)
|
|
21
|
+
.map(({ target, channel, method, invocations }) => ({
|
|
22
|
+
target,
|
|
23
|
+
channel,
|
|
24
|
+
method,
|
|
25
|
+
invocations,
|
|
26
|
+
}))
|
|
27
|
+
.sort(compareMethodKeys);
|
|
28
|
+
const handlersWithoutInvocations = [...methodGroups.values()]
|
|
29
|
+
.filter((group) => group.handlers.length > 0 && group.invocations.length === 0)
|
|
30
|
+
.map(({ target, channel, method, handlers }) => ({
|
|
31
|
+
target,
|
|
32
|
+
channel,
|
|
33
|
+
method,
|
|
34
|
+
handlers,
|
|
35
|
+
}))
|
|
36
|
+
.sort(compareMethodKeys);
|
|
37
|
+
return {
|
|
38
|
+
matchedChannels,
|
|
39
|
+
unregisteredChannelCreations,
|
|
40
|
+
matchedMethods,
|
|
41
|
+
unhandledInvocations,
|
|
42
|
+
handlersWithoutInvocations,
|
|
43
|
+
limitations,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** 사실별 target이 없는 혼합 문서인지 확인한다. */
|
|
47
|
+
function hasMixedTargets(document) {
|
|
48
|
+
return document.limitations.some((message) => message.startsWith('mixed-targets:'));
|
|
49
|
+
}
|
|
50
|
+
/** 안전하게 조인을 보류하면서 입력 한계만 전달한다. */
|
|
51
|
+
function emptyJoinResult(limitations) {
|
|
52
|
+
return {
|
|
53
|
+
matchedChannels: [],
|
|
54
|
+
unregisteredChannelCreations: [],
|
|
55
|
+
matchedMethods: [],
|
|
56
|
+
unhandledInvocations: [],
|
|
57
|
+
handlersWithoutInvocations: [],
|
|
58
|
+
limitations,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** 입력 limitation에 생산 플랫폼과 도구 이름을 붙여 정렬한다. */
|
|
62
|
+
function collectLimitations(documents) {
|
|
63
|
+
const limitations = documents.flatMap((document) => document.limitations.map((message) => ({
|
|
64
|
+
platform: document.platform,
|
|
65
|
+
tool: document.tool.name,
|
|
66
|
+
message,
|
|
67
|
+
})));
|
|
68
|
+
const freshness = freshnessLimitation(documents);
|
|
69
|
+
if (freshness !== undefined)
|
|
70
|
+
limitations.push(freshness);
|
|
71
|
+
return limitations.sort(compareLimitations);
|
|
72
|
+
}
|
|
73
|
+
/** 생성 시각 차이가 하루를 넘을 때 교차 입력 한계를 만든다. */
|
|
74
|
+
function freshnessLimitation(documents) {
|
|
75
|
+
if (documents.length < 2)
|
|
76
|
+
return undefined;
|
|
77
|
+
const timestamps = documents.map((document) => Date.parse(document.generatedAt));
|
|
78
|
+
const difference = Math.max(...timestamps) - Math.min(...timestamps);
|
|
79
|
+
if (difference <= millisecondsPerDay)
|
|
80
|
+
return undefined;
|
|
81
|
+
const hours = Math.round(difference / millisecondsPerHour);
|
|
82
|
+
return {
|
|
83
|
+
platform: 'cross-platform',
|
|
84
|
+
tool: 'isthmus',
|
|
85
|
+
message: `input-freshness: bridge documents differ by ${hours} hours`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** limitation을 플랫폼·도구·문장 순으로 고정한다. */
|
|
89
|
+
function compareLimitations(left, right) {
|
|
90
|
+
return (compareStrings(left.platform, right.platform) ||
|
|
91
|
+
compareStrings(left.tool, right.tool) ||
|
|
92
|
+
compareStrings(left.message, right.message));
|
|
93
|
+
}
|
|
94
|
+
/** 메서드 키별로 호출과 핸들러 증거를 모은다. */
|
|
95
|
+
function collectMethodGroups(documents) {
|
|
96
|
+
const groups = new Map();
|
|
97
|
+
for (const document of documents)
|
|
98
|
+
collectDocumentMethods(document, groups);
|
|
99
|
+
for (const group of groups.values()) {
|
|
100
|
+
sortUniqueEndpoints(group.invocations);
|
|
101
|
+
sortUniqueEndpoints(group.handlers);
|
|
102
|
+
}
|
|
103
|
+
return groups;
|
|
104
|
+
}
|
|
105
|
+
/** 증거 위치와 심볼을 정렬하고 같은 증거를 한 번만 남긴다. */
|
|
106
|
+
function sortUniqueEndpoints(endpoints) {
|
|
107
|
+
endpoints.sort(compareEndpoints);
|
|
108
|
+
let previous;
|
|
109
|
+
const unique = endpoints.filter((endpoint) => {
|
|
110
|
+
const isDuplicate = previous !== undefined && compareEndpoints(previous, endpoint) === 0;
|
|
111
|
+
previous = endpoint;
|
|
112
|
+
return !isDuplicate;
|
|
113
|
+
});
|
|
114
|
+
endpoints.splice(0, endpoints.length, ...unique);
|
|
115
|
+
}
|
|
116
|
+
/** 증거 위치와 선택 심볼을 완전한 결정 순서로 비교한다. */
|
|
117
|
+
function compareEndpoints(left, right) {
|
|
118
|
+
const locationOrder = compareStrings(left.platform, right.platform) ||
|
|
119
|
+
compareStrings(left.location.path, right.location.path) ||
|
|
120
|
+
left.location.line - right.location.line ||
|
|
121
|
+
left.location.column - right.location.column;
|
|
122
|
+
if (locationOrder !== 0)
|
|
123
|
+
return locationOrder;
|
|
124
|
+
if (left.symbol === undefined)
|
|
125
|
+
return right.symbol === undefined ? 0 : 1;
|
|
126
|
+
if (right.symbol === undefined)
|
|
127
|
+
return -1;
|
|
128
|
+
return (compareStrings(left.symbol.qualifiedName, right.symbol.qualifiedName) ||
|
|
129
|
+
compareOptionalStrings(left.symbol.usr, right.symbol.usr));
|
|
130
|
+
}
|
|
131
|
+
/** 존재하는 선택 문자열을 없는 값보다 먼저 두고 비교한다. */
|
|
132
|
+
function compareOptionalStrings(left, right) {
|
|
133
|
+
if (left === undefined)
|
|
134
|
+
return right === undefined ? 0 : 1;
|
|
135
|
+
if (right === undefined)
|
|
136
|
+
return -1;
|
|
137
|
+
return compareStrings(left, right);
|
|
138
|
+
}
|
|
139
|
+
/** 문서 하나의 정적 메서드 사실을 그룹에 추가한다. */
|
|
140
|
+
function collectDocumentMethods(document, groups) {
|
|
141
|
+
if (document.target === null)
|
|
142
|
+
return;
|
|
143
|
+
for (const fact of document.facts) {
|
|
144
|
+
if (!isStaticMethodFact(fact))
|
|
145
|
+
continue;
|
|
146
|
+
const key = `${document.target}\u0000${fact.channel}\u0000${fact.method}`;
|
|
147
|
+
const group = groups.get(key) ?? createMethodGroup(document.target, fact.channel, fact.method);
|
|
148
|
+
const endpoints = fact.kind === 'method-invoke' ? group.invocations : group.handlers;
|
|
149
|
+
endpoints.push(toEndpoint(document.platform, fact));
|
|
150
|
+
groups.set(key, group);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** 조인 가능한 정적 메서드 호출·핸들러 사실인지 확인한다. */
|
|
154
|
+
function isStaticMethodFact(fact) {
|
|
155
|
+
return ((fact.kind === 'method-invoke' || fact.kind === 'method-handle') &&
|
|
156
|
+
!fact.dynamic &&
|
|
157
|
+
fact.channel !== null &&
|
|
158
|
+
fact.method !== undefined);
|
|
159
|
+
}
|
|
160
|
+
/** 빈 메서드 그룹을 만든다. */
|
|
161
|
+
function createMethodGroup(target, channel, method) {
|
|
162
|
+
return { target, channel, method, invocations: [], handlers: [] };
|
|
163
|
+
}
|
|
164
|
+
/** 채널 키별로 생성과 등록 증거를 모은다. */
|
|
165
|
+
function collectChannelGroups(documents) {
|
|
166
|
+
const groups = new Map();
|
|
167
|
+
for (const document of documents)
|
|
168
|
+
collectDocumentChannels(document, groups);
|
|
169
|
+
for (const group of groups.values()) {
|
|
170
|
+
sortUniqueEndpoints(group.creations);
|
|
171
|
+
sortUniqueEndpoints(group.registrations);
|
|
172
|
+
}
|
|
173
|
+
return groups;
|
|
174
|
+
}
|
|
175
|
+
/** 문서 하나의 정적 채널 사실을 그룹에 추가한다. */
|
|
176
|
+
function collectDocumentChannels(document, groups) {
|
|
177
|
+
if (document.target === null)
|
|
178
|
+
return;
|
|
179
|
+
for (const fact of document.facts) {
|
|
180
|
+
if (!isStaticChannelFact(fact))
|
|
181
|
+
continue;
|
|
182
|
+
const key = `${document.target}\u0000${fact.channel}`;
|
|
183
|
+
const group = groups.get(key) ?? createChannelGroup(document.target, fact.channel);
|
|
184
|
+
const endpoints = fact.kind === 'channel-create' ? group.creations : group.registrations;
|
|
185
|
+
endpoints.push(toEndpoint(document.platform, fact));
|
|
186
|
+
groups.set(key, group);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/** 조인 가능한 정적 채널 생성·등록 사실인지 확인한다. */
|
|
190
|
+
function isStaticChannelFact(fact) {
|
|
191
|
+
return ((fact.kind === 'channel-create' || fact.kind === 'channel-register') &&
|
|
192
|
+
!fact.dynamic &&
|
|
193
|
+
fact.channel !== null);
|
|
194
|
+
}
|
|
195
|
+
/** 빈 채널 그룹을 만든다. */
|
|
196
|
+
function createChannelGroup(target, channel) {
|
|
197
|
+
return { target, channel, creations: [], registrations: [] };
|
|
198
|
+
}
|
|
199
|
+
/** 사실을 플랫폼이 포함된 증거 위치로 바꾼다. */
|
|
200
|
+
function toEndpoint(platform, fact) {
|
|
201
|
+
return fact.symbol === undefined
|
|
202
|
+
? { platform, location: fact.location }
|
|
203
|
+
: { platform, location: fact.location, symbol: fact.symbol };
|
|
204
|
+
}
|
|
205
|
+
/** 채널 결과를 target과 이름 순으로 고정한다. */
|
|
206
|
+
function compareChannels(left, right) {
|
|
207
|
+
return (compareStrings(left.target, right.target) ||
|
|
208
|
+
compareStrings(left.channel, right.channel));
|
|
209
|
+
}
|
|
210
|
+
/** 메서드 결과를 target·채널·메서드 순으로 고정한다. */
|
|
211
|
+
function compareMethodKeys(left, right) {
|
|
212
|
+
return (compareStrings(left.target, right.target) ||
|
|
213
|
+
compareStrings(left.channel, right.channel) ||
|
|
214
|
+
compareStrings(left.method, right.method));
|
|
215
|
+
}
|
|
216
|
+
const millisecondsPerHour = 60 * 60 * 1_000;
|
|
217
|
+
const millisecondsPerDay = 24 * millisecondsPerHour;
|
|
218
|
+
//# sourceMappingURL=join.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"join.js","sourceRoot":"","sources":["../../src/join/join.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { BridgeTarget } from '../exchange/parse.ts';
|
|
2
|
+
import type { BridgeEndpoint, BridgeJoinResult, JoinLimitation } from '../join/join.ts';
|
|
3
|
+
/** check 결과 개수를 빠르게 판단할 요약이다. */
|
|
4
|
+
export interface CheckSummary {
|
|
5
|
+
readonly errors: number;
|
|
6
|
+
readonly warnings: number;
|
|
7
|
+
readonly matchedChannels: number;
|
|
8
|
+
readonly matchedMethods: number;
|
|
9
|
+
}
|
|
10
|
+
/** check가 보고하는 안정적인 진단 종류다. */
|
|
11
|
+
export type CheckIssueCode = 'unhandled-invocation' | 'unregistered-channel-creation' | 'handler-without-invocation';
|
|
12
|
+
/** 삭제 판정 없이 경계 불일치 사실과 증거만 전달한다. */
|
|
13
|
+
export interface CheckIssue {
|
|
14
|
+
readonly severity: 'error' | 'warning';
|
|
15
|
+
readonly code: CheckIssueCode;
|
|
16
|
+
readonly target: BridgeTarget;
|
|
17
|
+
readonly channel: string;
|
|
18
|
+
readonly method?: string;
|
|
19
|
+
readonly evidence: readonly BridgeEndpoint[];
|
|
20
|
+
}
|
|
21
|
+
/** 에이전트와 CI가 소비할 check 문서다. */
|
|
22
|
+
export interface CheckReport {
|
|
23
|
+
readonly format: 'isthmus-check';
|
|
24
|
+
readonly version: 1;
|
|
25
|
+
readonly summary: CheckSummary;
|
|
26
|
+
readonly issues: readonly CheckIssue[];
|
|
27
|
+
readonly limitations: readonly JoinLimitation[];
|
|
28
|
+
}
|
|
29
|
+
/** check 문서를 결정적인 JSON 문자열로 인코딩한다. */
|
|
30
|
+
export declare function encodeCheckReport(report: CheckReport): string;
|
|
31
|
+
/** 조인 결과를 정책 심각도가 포함된 check 문서로 바꾼다. */
|
|
32
|
+
export declare function createCheckReport(joined: BridgeJoinResult): CheckReport;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { encodeSortedJson } from "./sorted-json.js";
|
|
2
|
+
/** check 문서를 결정적인 JSON 문자열로 인코딩한다. */
|
|
3
|
+
export function encodeCheckReport(report) {
|
|
4
|
+
return encodeSortedJson(report);
|
|
5
|
+
}
|
|
6
|
+
/** 조인 결과를 정책 심각도가 포함된 check 문서로 바꾼다. */
|
|
7
|
+
export function createCheckReport(joined) {
|
|
8
|
+
return {
|
|
9
|
+
format: 'isthmus-check',
|
|
10
|
+
version: 1,
|
|
11
|
+
summary: {
|
|
12
|
+
errors: joined.unhandledInvocations.length +
|
|
13
|
+
joined.unregisteredChannelCreations.length,
|
|
14
|
+
warnings: joined.handlersWithoutInvocations.length,
|
|
15
|
+
matchedChannels: joined.matchedChannels.length,
|
|
16
|
+
matchedMethods: joined.matchedMethods.length,
|
|
17
|
+
},
|
|
18
|
+
issues: [
|
|
19
|
+
...joined.unhandledInvocations.map((item) => ({
|
|
20
|
+
severity: 'error',
|
|
21
|
+
code: 'unhandled-invocation',
|
|
22
|
+
target: item.target,
|
|
23
|
+
channel: item.channel,
|
|
24
|
+
method: item.method,
|
|
25
|
+
evidence: item.invocations,
|
|
26
|
+
})),
|
|
27
|
+
...joined.unregisteredChannelCreations.map((item) => ({
|
|
28
|
+
severity: 'error',
|
|
29
|
+
code: 'unregistered-channel-creation',
|
|
30
|
+
target: item.target,
|
|
31
|
+
channel: item.channel,
|
|
32
|
+
evidence: item.creations,
|
|
33
|
+
})),
|
|
34
|
+
...joined.handlersWithoutInvocations.map((item) => ({
|
|
35
|
+
severity: 'warning',
|
|
36
|
+
code: 'handler-without-invocation',
|
|
37
|
+
target: item.target,
|
|
38
|
+
channel: item.channel,
|
|
39
|
+
method: item.method,
|
|
40
|
+
evidence: item.handlers,
|
|
41
|
+
})),
|
|
42
|
+
],
|
|
43
|
+
limitations: joined.limitations,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=check-report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-report.js","sourceRoot":"","sources":["../../src/report/check-report.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAmCpD,sCAAsC;AACtC,MAAM,UAAU,iBAAiB,CAAC,MAAmB;IACnD,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAClC,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,iBAAiB,CAAC,MAAwB;IACxD,OAAO;QACL,MAAM,EAAE,eAAe;QACvB,OAAO,EAAE,CAAC;QACV,OAAO,EAAE;YACP,MAAM,EACJ,MAAM,CAAC,oBAAoB,CAAC,MAAM;gBAClC,MAAM,CAAC,4BAA4B,CAAC,MAAM;YAC5C,QAAQ,EAAE,MAAM,CAAC,0BAA0B,CAAC,MAAM;YAClD,eAAe,EAAE,MAAM,CAAC,eAAe,CAAC,MAAM;YAC9C,cAAc,EAAE,MAAM,CAAC,cAAc,CAAC,MAAM;SAC7C;QACD,MAAM,EAAE;YACN,GAAG,MAAM,CAAC,oBAAoB,CAAC,GAAG,CAAa,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBACxD,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,sBAAsB;gBAC5B,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,QAAQ,EAAE,IAAI,CAAC,WAAW;aAC3B,CAAC,CAAC;YACH,GAAG,MAAM,CAAC,4BAA4B,CAAC,GAAG,CAAa,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAChE,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,+BAA+B;gBACrC,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,QAAQ,EAAE,IAAI,CAAC,SAAS;aACzB,CAAC,CAAC;YACH,GAAG,MAAM,CAAC,0BAA0B,CAAC,GAAG,CAAa,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAC9D,QAAQ,EAAE,SAAS;gBACnB,IAAI,EAAE,4BAA4B;gBAClC,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;aACxB,CAAC,CAAC;SACJ;QACD,WAAW,EAAE,MAAM,CAAC,WAAW;KAChC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { BridgeLocation, BridgePlatform, BridgeSymbol, BridgeTarget } from '../exchange/parse.ts';
|
|
2
|
+
import type { BridgeJoinResult, JoinLimitation } from '../join/join.ts';
|
|
3
|
+
/** 소스 위치 하나를 나타내는 경계 그래프 노드다. */
|
|
4
|
+
export interface BridgeGraphNode {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly platform: BridgePlatform;
|
|
7
|
+
readonly location: BridgeLocation;
|
|
8
|
+
readonly symbol?: BridgeSymbol;
|
|
9
|
+
}
|
|
10
|
+
/** 호출 측 위치에서 수신 측 위치로 향하는 경계 간선이다. */
|
|
11
|
+
export interface BridgeGraphEdge {
|
|
12
|
+
readonly from: string;
|
|
13
|
+
readonly to: string;
|
|
14
|
+
readonly kind: 'channel' | 'method';
|
|
15
|
+
readonly target: BridgeTarget;
|
|
16
|
+
readonly channel: string;
|
|
17
|
+
readonly method?: string;
|
|
18
|
+
}
|
|
19
|
+
/** 매치된 경계 간선만 담는 그래프 문서다. */
|
|
20
|
+
export interface BridgeGraphDocument {
|
|
21
|
+
readonly format: 'isthmus-graph';
|
|
22
|
+
readonly version: 1;
|
|
23
|
+
readonly nodes: readonly BridgeGraphNode[];
|
|
24
|
+
readonly edges: readonly BridgeGraphEdge[];
|
|
25
|
+
readonly limitations: readonly JoinLimitation[];
|
|
26
|
+
}
|
|
27
|
+
/** 경계 그래프를 요청한 출력 형식으로 렌더링한다. */
|
|
28
|
+
export declare function renderBridgeGraph(graph: BridgeGraphDocument, format: 'json' | 'dot' | 'mermaid'): string;
|
|
29
|
+
/** 조인 결과의 매치만 경계 그래프로 바꾼다. */
|
|
30
|
+
export declare function createBridgeGraph(joined: BridgeJoinResult): BridgeGraphDocument;
|