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.
- package/README.ko.md +64 -22
- package/README.md +70 -26
- package/Skills/isthmus/SKILL.md +93 -4
- package/dist/cli/check-command.d.ts +2 -25
- package/dist/cli/check-command.js +27 -181
- package/dist/cli/check-command.js.map +1 -1
- package/dist/cli/command-support.d.ts +48 -0
- package/dist/cli/command-support.js +144 -0
- package/dist/cli/command-support.js.map +1 -0
- package/dist/cli/diff-command.d.ts +1 -1
- package/dist/cli/diff-command.js +1 -1
- package/dist/cli/diff-command.js.map +1 -1
- package/dist/cli/graph-command.d.ts +1 -1
- package/dist/cli/graph-command.js +15 -14
- package/dist/cli/graph-command.js.map +1 -1
- package/dist/cli/impact-command.d.ts +5 -0
- package/dist/cli/impact-command.js +111 -0
- package/dist/cli/impact-command.js.map +1 -0
- package/dist/cli/main.js +35 -13
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/parse-arguments.d.ts +18 -0
- package/dist/cli/parse-arguments.js +48 -0
- package/dist/cli/parse-arguments.js.map +1 -0
- package/dist/cli/preflight-command.d.ts +5 -0
- package/dist/cli/preflight-command.js +83 -0
- package/dist/cli/preflight-command.js.map +1 -0
- package/dist/cli/query-command.d.ts +1 -1
- package/dist/cli/query-command.js +30 -10
- package/dist/cli/query-command.js.map +1 -1
- package/dist/cli/retentions-command.d.ts +1 -1
- package/dist/cli/retentions-command.js +11 -10
- package/dist/cli/retentions-command.js.map +1 -1
- package/dist/cli/runtime-command.d.ts +5 -0
- package/dist/cli/runtime-command.js +42 -0
- package/dist/cli/runtime-command.js.map +1 -0
- package/dist/cli/runtime-json-reader.d.ts +12 -0
- package/dist/cli/runtime-json-reader.js +37 -0
- package/dist/cli/runtime-json-reader.js.map +1 -0
- package/dist/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.d.ts +40 -33
- package/dist/report/diff.js +8 -4
- 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/query.js +9 -2
- package/dist/report/query.js.map +1 -1
- package/dist/report/runtime-impact.d.ts +38 -0
- package/dist/report/runtime-impact.js +94 -0
- package/dist/report/runtime-impact.js.map +1 -0
- package/dist/report/runtime.d.ts +54 -0
- package/dist/report/runtime.js +137 -0
- package/dist/report/runtime.js.map +1 -0
- package/dist/report/sarif.d.ts +9 -2
- package/dist/report/sarif.js +4 -10
- package/dist/report/sarif.js.map +1 -1
- package/dist/report/sorted-json.d.ts +1 -1
- package/dist/report/sorted-json.js +2 -2
- package/dist/report/sorted-json.js.map +1 -1
- package/docs/BRIDGE-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
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
import { isBridgeJoinDeferred, joinBridgeDocuments, MAX_DOCUMENTS_PER_JOIN, } from "../join/join.js";
|
|
2
2
|
import { createBridgeQuery, encodeBridgeQuery } from "../report/query.js";
|
|
3
|
-
import { bridgeJoinDeferredError,
|
|
3
|
+
import { bridgeJoinDeferredError, inputFailureResult, internalError, readBridgeDocuments, } from "./command-support.js";
|
|
4
|
+
import { parseCommandArguments } from "./parse-arguments.js";
|
|
4
5
|
/** query 인자를 실행해 bridge 질의 JSON과 종료 코드를 반환한다. */
|
|
5
6
|
export async function runQueryCommand(arguments_, readTextFile) {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
const parsed = parseCommandArguments(arguments_.slice(1), [], []);
|
|
8
|
+
if (parsed === undefined)
|
|
9
|
+
return queryUsageError();
|
|
10
|
+
const requested = parsed.positionals[0];
|
|
11
|
+
const inputPaths = parsed.positionals.slice(1);
|
|
12
|
+
if (requested === undefined ||
|
|
10
13
|
requested.trim().length === 0 ||
|
|
11
14
|
inputPaths.length < 2 ||
|
|
12
|
-
inputPaths.length > MAX_DOCUMENTS_PER_JOIN
|
|
13
|
-
inputPaths.some((path) => path.startsWith('-'))) {
|
|
15
|
+
inputPaths.length > MAX_DOCUMENTS_PER_JOIN) {
|
|
14
16
|
return queryUsageError();
|
|
15
17
|
}
|
|
16
18
|
try {
|
|
17
19
|
const documents = await readBridgeDocuments(inputPaths, readTextFile);
|
|
18
20
|
const joined = joinBridgeDocuments(documents);
|
|
19
|
-
if (isBridgeJoinDeferred(joined))
|
|
20
|
-
return bridgeJoinDeferredError();
|
|
21
|
+
if (isBridgeJoinDeferred(joined)) {
|
|
22
|
+
return bridgeJoinDeferredError(joined.observedFacts, documents.length);
|
|
23
|
+
}
|
|
21
24
|
const query = createBridgeQuery(joined, requested);
|
|
22
25
|
return {
|
|
23
26
|
standardOutput: encodeBridgeQuery(query),
|
|
24
|
-
standardError:
|
|
27
|
+
standardError: queryStatusHint(query),
|
|
25
28
|
exitCode: query.status === 'found' ? 0 : 64,
|
|
26
29
|
};
|
|
27
30
|
}
|
|
@@ -29,6 +32,23 @@ export async function runQueryCommand(arguments_, readTextFile) {
|
|
|
29
32
|
return inputFailureResult(error) ?? internalError();
|
|
30
33
|
}
|
|
31
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* 찾지 못한 질의에 원인을 한 줄로 알린다.
|
|
37
|
+
*
|
|
38
|
+
* 문서 자체는 stdout JSON이 이미 다 담고 있으므로, 사람이 종료 코드만 봐도
|
|
39
|
+
* "인수가 잘못됐는지"와 "이름이 없는지"를 구분할 수 있게 하는 보조 문구다.
|
|
40
|
+
* 정적 문자열과 숫자만 보간한다.
|
|
41
|
+
*/
|
|
42
|
+
function queryStatusHint(query) {
|
|
43
|
+
if (query.status === 'found')
|
|
44
|
+
return '';
|
|
45
|
+
if (query.status === 'ambiguous') {
|
|
46
|
+
return `The requested name matches ${query.candidates?.length ?? 0} bridge keys; `
|
|
47
|
+
+ 'repeat the query with a qualifiedName from candidates.\n';
|
|
48
|
+
}
|
|
49
|
+
return 'No bridge channel or method matches the requested name; '
|
|
50
|
+
+ 'query a channel or method name that the inputs observed.\n';
|
|
51
|
+
}
|
|
32
52
|
/** 잘못된 query 호출을 사용법과 코드 64로 바꾼다. */
|
|
33
53
|
function queryUsageError() {
|
|
34
54
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-command.js","sourceRoot":"","sources":["../../src/cli/query-command.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"query-command.js","sourceRoot":"","sources":["../../src/cli/query-command.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE1E,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,aAAa,EACb,mBAAmB,GAGpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,iDAAiD;AACjD,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,UAA6B,EAC7B,YAA0B;IAE1B,MAAM,MAAM,GAAG,qBAAqB,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;IAClE,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,eAAe,EAAE,CAAC;IACnD,MAAM,SAAS,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IACxC,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC/C,IACE,SAAS,KAAK,SAAS;QACvB,SAAS,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAC7B,UAAU,CAAC,MAAM,GAAG,CAAC;QACrB,UAAU,CAAC,MAAM,GAAG,sBAAsB,EAC1C,CAAC;QACD,OAAO,eAAe,EAAE,CAAC;IAC3B,CAAC;IACD,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,mBAAmB,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;QACtE,MAAM,MAAM,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;QAC9C,IAAI,oBAAoB,CAAC,MAAM,CAAC,EAAE,CAAC;YACjC,OAAO,uBAAuB,CAAC,MAAM,CAAC,aAAa,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;QACzE,CAAC;QACD,MAAM,KAAK,GAAG,iBAAiB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QACnD,OAAO;YACL,cAAc,EAAE,iBAAiB,CAAC,KAAK,CAAC;YACxC,aAAa,EAAE,eAAe,CAAC,KAAK,CAAC;YACrC,QAAQ,EAAE,KAAK,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;SAC5C,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,kBAAkB,CAAC,KAAK,CAAC,IAAI,aAAa,EAAE,CAAC;IACtD,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,eAAe,CAAC,KAA0B;IACjD,IAAI,KAAK,CAAC,MAAM,KAAK,OAAO;QAAE,OAAO,EAAE,CAAC;IACxC,IAAI,KAAK,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;QACjC,OAAO,8BAA8B,KAAK,CAAC,UAAU,EAAE,MAAM,IAAI,CAAC,gBAAgB;cAC9E,0DAA0D,CAAC;IACjE,CAAC;IACD,OAAO,0DAA0D;UAC7D,4DAA4D,CAAC;AACnE,CAAC;AAED,qCAAqC;AACrC,SAAS,eAAe;IACtB,OAAO;QACL,cAAc,EAAE,EAAE;QAClB,aAAa,EAAE,GAAG,UAAU,IAAI;QAChC,QAAQ,EAAE,EAAE;KACb,CAAC;AACJ,CAAC;AAED,2BAA2B;AAC3B,MAAM,CAAC,MAAM,UAAU,GACrB,+DAA+D;MAC7D,+BAA+B,CAAC","sourcesContent":["import {\n isBridgeJoinDeferred,\n joinBridgeDocuments,\n MAX_DOCUMENTS_PER_JOIN,\n} from '../join/join.ts';\nimport { createBridgeQuery, encodeBridgeQuery } from '../report/query.ts';\nimport type { BridgeQueryDocument } from '../report/query.ts';\nimport {\n bridgeJoinDeferredError,\n inputFailureResult,\n internalError,\n readBridgeDocuments,\n type CommandResult,\n type ReadTextFile,\n} from './command-support.ts';\nimport { parseCommandArguments } from './parse-arguments.ts';\n\n/** query 인자를 실행해 bridge 질의 JSON과 종료 코드를 반환한다. */\nexport async function runQueryCommand(\n arguments_: readonly string[],\n readTextFile: ReadTextFile,\n): Promise<CommandResult> {\n const parsed = parseCommandArguments(arguments_.slice(1), [], []);\n if (parsed === undefined) return queryUsageError();\n const requested = parsed.positionals[0];\n const inputPaths = parsed.positionals.slice(1);\n if (\n requested === undefined ||\n requested.trim().length === 0 ||\n inputPaths.length < 2 ||\n inputPaths.length > MAX_DOCUMENTS_PER_JOIN\n ) {\n return queryUsageError();\n }\n try {\n const documents = await readBridgeDocuments(inputPaths, readTextFile);\n const joined = joinBridgeDocuments(documents);\n if (isBridgeJoinDeferred(joined)) {\n return bridgeJoinDeferredError(joined.observedFacts, documents.length);\n }\n const query = createBridgeQuery(joined, requested);\n return {\n standardOutput: encodeBridgeQuery(query),\n standardError: queryStatusHint(query),\n exitCode: query.status === 'found' ? 0 : 64,\n };\n } catch (error) {\n return inputFailureResult(error) ?? internalError();\n }\n}\n\n/**\n * 찾지 못한 질의에 원인을 한 줄로 알린다.\n *\n * 문서 자체는 stdout JSON이 이미 다 담고 있으므로, 사람이 종료 코드만 봐도\n * \"인수가 잘못됐는지\"와 \"이름이 없는지\"를 구분할 수 있게 하는 보조 문구다.\n * 정적 문자열과 숫자만 보간한다.\n */\nfunction queryStatusHint(query: BridgeQueryDocument): string {\n if (query.status === 'found') return '';\n if (query.status === 'ambiguous') {\n return `The requested name matches ${query.candidates?.length ?? 0} bridge keys; `\n + 'repeat the query with a qualifiedName from candidates.\\n';\n }\n return 'No bridge channel or method matches the requested name; '\n + 'query a channel or method name that the inputs observed.\\n';\n}\n\n/** 잘못된 query 호출을 사용법과 코드 64로 바꾼다. */\nfunction queryUsageError(): CommandResult {\n return {\n standardOutput: '',\n standardError: `${queryUsage}\\n`,\n exitCode: 64,\n };\n}\n\n/** query 명령의 한 줄 사용법이다. */\nexport const queryUsage =\n 'Usage: isthmus query <channel-or-method> <bridge-facts.json> '\n + '<bridge-facts.json> [more...]';\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Clock, type CommandResult, type ReadTextFile } from './
|
|
1
|
+
import { type Clock, type CommandResult, type ReadTextFile } from './command-support.ts';
|
|
2
2
|
export type { Clock };
|
|
3
3
|
/** retentions 인자를 실행해 cartograph용 보존 문서를 반환한다. */
|
|
4
4
|
export declare function runRetentionsCommand(arguments_: readonly string[], readTextFile: ReadTextFile, now: Clock, producerVersion: string): Promise<CommandResult>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isBridgeJoinDeferred, joinBridgeDocuments, MAX_DOCUMENTS_PER_JOIN, } from "../join/join.js";
|
|
2
2
|
import { createCartographRetentionsDocument, encodeCartographRetentionsDocument, RetentionValidationError, validateCartographRetentionInputs, } from "../report/retentions.js";
|
|
3
|
-
import { bridgeJoinDeferredError,
|
|
3
|
+
import { bridgeJoinDeferredError, inputFailureResult, internalError, readBridgeDocuments, } from "./command-support.js";
|
|
4
|
+
import { parseCommandArguments } from "./parse-arguments.js";
|
|
4
5
|
/** retentions 인자를 실행해 cartograph용 보존 문서를 반환한다. */
|
|
5
6
|
export async function runRetentionsCommand(arguments_, readTextFile, now, producerVersion) {
|
|
6
7
|
const inputPaths = retentionInputPaths(arguments_);
|
|
@@ -9,8 +10,9 @@ export async function runRetentionsCommand(arguments_, readTextFile, now, produc
|
|
|
9
10
|
try {
|
|
10
11
|
const documents = await readBridgeDocuments(inputPaths, readTextFile);
|
|
11
12
|
const joined = joinBridgeDocuments(documents);
|
|
12
|
-
if (isBridgeJoinDeferred(joined))
|
|
13
|
-
return bridgeJoinDeferredError();
|
|
13
|
+
if (isBridgeJoinDeferred(joined)) {
|
|
14
|
+
return bridgeJoinDeferredError(joined.observedFacts, documents.length);
|
|
15
|
+
}
|
|
14
16
|
validateCartographRetentionInputs(documents);
|
|
15
17
|
const retentions = createCartographRetentionsDocument(joined, now().toISOString(), producerVersion);
|
|
16
18
|
return {
|
|
@@ -32,16 +34,15 @@ export async function runRetentionsCommand(arguments_, readTextFile, now, produc
|
|
|
32
34
|
}
|
|
33
35
|
/** 지원 대상과 최소 입력 수를 검증해 파일 경로만 돌려준다. */
|
|
34
36
|
function retentionInputPaths(arguments_) {
|
|
35
|
-
|
|
36
|
-
if (arguments_[0] !== 'retentions' || forIndex < 3)
|
|
37
|
+
if (arguments_[0] !== 'retentions')
|
|
37
38
|
return undefined;
|
|
38
|
-
|
|
39
|
+
const parsed = parseCommandArguments(arguments_.slice(1), ['--for'], []);
|
|
40
|
+
if (parsed === undefined)
|
|
39
41
|
return undefined;
|
|
40
|
-
if (
|
|
42
|
+
if (parsed.valueFlags.get('--for') !== 'cartograph')
|
|
41
43
|
return undefined;
|
|
42
|
-
const paths =
|
|
43
|
-
return paths.length > MAX_DOCUMENTS_PER_JOIN
|
|
44
|
-
paths.some((path) => path.startsWith('-'))
|
|
44
|
+
const paths = [...parsed.positionals];
|
|
45
|
+
return paths.length < 2 || paths.length > MAX_DOCUMENTS_PER_JOIN
|
|
45
46
|
? undefined
|
|
46
47
|
: paths;
|
|
47
48
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retentions-command.js","sourceRoot":"","sources":["../../src/cli/retentions-command.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,kCAAkC,EAClC,kCAAkC,EAClC,wBAAwB,EACxB,iCAAiC,GAClC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uBAAuB,EACvB,aAAa,EACb,
|
|
1
|
+
{"version":3,"file":"retentions-command.js","sourceRoot":"","sources":["../../src/cli/retentions-command.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,kCAAkC,EAClC,kCAAkC,EAClC,wBAAwB,EACxB,iCAAiC,GAClC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,aAAa,EACb,mBAAmB,GAIpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAI7D,kDAAkD;AAClD,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,UAA6B,EAC7B,YAA0B,EAC1B,GAAU,EACV,eAAuB;IAEvB,MAAM,UAAU,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;IACnD,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,mBAAmB,EAAE,CAAC;IAC3D,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,mBAAmB,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;QACtE,MAAM,MAAM,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;QAC9C,IAAI,oBAAoB,CAAC,MAAM,CAAC,EAAE,CAAC;YACjC,OAAO,uBAAuB,CAAC,MAAM,CAAC,aAAa,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;QACzE,CAAC;QACD,iCAAiC,CAAC,SAAS,CAAC,CAAC;QAC7C,MAAM,UAAU,GAAG,kCAAkC,CACnD,MAAM,EACN,GAAG,EAAE,CAAC,WAAW,EAAE,EACnB,eAAe,CAChB,CAAC;QACF,OAAO;YACL,cAAc,EAAE,kCAAkC,CAAC,UAAU,CAAC;YAC9D,aAAa,EAAE,EAAE;YACjB,QAAQ,EAAE,CAAC;SACZ,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,wBAAwB,EAAE,CAAC;YAC9C,OAAO;gBACL,cAAc,EAAE,EAAE;gBAClB,aAAa,EAAE,GAAG,KAAK,CAAC,OAAO,IAAI;gBACnC,QAAQ,EAAE,CAAC;aACZ,CAAC;QACJ,CAAC;QACD,OAAO,kBAAkB,CAAC,KAAK,CAAC,IAAI,aAAa,EAAE,CAAC;IACtD,CAAC;AACH,CAAC;AAED,uCAAuC;AACvC,SAAS,mBAAmB,CAAC,UAA6B;IACxD,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,YAAY;QAAE,OAAO,SAAS,CAAC;IACrD,MAAM,MAAM,GAAG,qBAAqB,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;IACzE,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC3C,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,YAAY;QAAE,OAAO,SAAS,CAAC;IACtE,MAAM,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;IACtC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,sBAAsB;QAC9D,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,KAAK,CAAC;AACZ,CAAC;AAED,0CAA0C;AAC1C,SAAS,mBAAmB;IAC1B,OAAO;QACL,cAAc,EAAE,EAAE;QAClB,aAAa,EAAE,GAAG,cAAc,IAAI;QACpC,QAAQ,EAAE,EAAE;KACb,CAAC;AACJ,CAAC;AAED,gCAAgC;AAChC,MAAM,CAAC,MAAM,cAAc,GACzB,oEAAoE;MAClE,4BAA4B,CAAC","sourcesContent":["import {\n isBridgeJoinDeferred,\n joinBridgeDocuments,\n MAX_DOCUMENTS_PER_JOIN,\n} from '../join/join.ts';\nimport {\n createCartographRetentionsDocument,\n encodeCartographRetentionsDocument,\n RetentionValidationError,\n validateCartographRetentionInputs,\n} from '../report/retentions.ts';\nimport {\n bridgeJoinDeferredError,\n inputFailureResult,\n internalError,\n readBridgeDocuments,\n type Clock,\n type CommandResult,\n type ReadTextFile,\n} from './command-support.ts';\nimport { parseCommandArguments } from './parse-arguments.ts';\n\nexport type { Clock };\n\n/** retentions 인자를 실행해 cartograph용 보존 문서를 반환한다. */\nexport async function runRetentionsCommand(\n arguments_: readonly string[],\n readTextFile: ReadTextFile,\n now: Clock,\n producerVersion: string,\n): Promise<CommandResult> {\n const inputPaths = retentionInputPaths(arguments_);\n if (inputPaths === undefined) return retentionUsageError();\n try {\n const documents = await readBridgeDocuments(inputPaths, readTextFile);\n const joined = joinBridgeDocuments(documents);\n if (isBridgeJoinDeferred(joined)) {\n return bridgeJoinDeferredError(joined.observedFacts, documents.length);\n }\n validateCartographRetentionInputs(documents);\n const retentions = createCartographRetentionsDocument(\n joined,\n now().toISOString(),\n producerVersion,\n );\n return {\n standardOutput: encodeCartographRetentionsDocument(retentions),\n standardError: '',\n exitCode: 0,\n };\n } catch (error) {\n if (error instanceof RetentionValidationError) {\n return {\n standardOutput: '',\n standardError: `${error.message}\\n`,\n exitCode: 2,\n };\n }\n return inputFailureResult(error) ?? internalError();\n }\n}\n\n/** 지원 대상과 최소 입력 수를 검증해 파일 경로만 돌려준다. */\nfunction retentionInputPaths(arguments_: readonly string[]): string[] | undefined {\n if (arguments_[0] !== 'retentions') return undefined;\n const parsed = parseCommandArguments(arguments_.slice(1), ['--for'], []);\n if (parsed === undefined) return undefined;\n if (parsed.valueFlags.get('--for') !== 'cartograph') return undefined;\n const paths = [...parsed.positionals];\n return paths.length < 2 || paths.length > MAX_DOCUMENTS_PER_JOIN\n ? undefined\n : paths;\n}\n\n/** 잘못된 retentions 호출을 사용법과 코드 64로 바꾼다. */\nfunction retentionUsageError(): CommandResult {\n return {\n standardOutput: '',\n standardError: `${retentionUsage}\\n`,\n exitCode: 64,\n };\n}\n\n/** retentions 명령의 한 줄 사용법이다. */\nexport const retentionUsage =\n 'Usage: isthmus retentions <bridge-facts.json> <bridge-facts.json> '\n + '[more...] --for cartograph';\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CommandResult, ReadTextFile } from './command-support.ts';
|
|
2
|
+
/** 시나리오별 실행 기대를 런타임 관찰 JSON과 대조하는 CLI다. */
|
|
3
|
+
export declare function runRuntimeCommand(arguments_: readonly string[], readTextFile: ReadTextFile): Promise<CommandResult>;
|
|
4
|
+
/** 런타임 기대 파일과 하나 이상의 실행 로그가 필요하다. */
|
|
5
|
+
export declare const runtimeUsage: string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { parseBridgeRuntime, parseRuntimeExpectations, RuntimeValidationError } from "../exchange/runtime.js";
|
|
2
|
+
import { MAX_DOCUMENTS_PER_JOIN } from "../join/join.js";
|
|
3
|
+
import { verifyRuntimeEvidence } from "../report/runtime.js";
|
|
4
|
+
import { encodeSortedJson } from "../report/sorted-json.js";
|
|
5
|
+
import { inputFailure, internalError } from "./command-support.js";
|
|
6
|
+
import { parseCommandArguments } from "./parse-arguments.js";
|
|
7
|
+
import { RuntimeInputError, RuntimeJsonReader } from "./runtime-json-reader.js";
|
|
8
|
+
/** 시나리오별 실행 기대를 런타임 관찰 JSON과 대조하는 CLI다. */
|
|
9
|
+
export async function runRuntimeCommand(arguments_, readTextFile) {
|
|
10
|
+
const parsed = parseCommandArguments(arguments_.slice(1), ['--expectations'], ['--strict', '--compact']);
|
|
11
|
+
const expectedPath = parsed?.valueFlags.get('--expectations');
|
|
12
|
+
if (parsed === undefined || expectedPath === undefined || parsed.positionals.length === 0 ||
|
|
13
|
+
parsed.positionals.length > MAX_DOCUMENTS_PER_JOIN) {
|
|
14
|
+
return { standardOutput: '', standardError: `${runtimeUsage}\n`, exitCode: 64 };
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const reader = new RuntimeJsonReader(readTextFile);
|
|
18
|
+
const expectations = parseRuntimeExpectations(await reader.read(expectedPath));
|
|
19
|
+
const documents = [];
|
|
20
|
+
for (const path of parsed.positionals)
|
|
21
|
+
documents.push(parseBridgeRuntime(await reader.read(path)));
|
|
22
|
+
const report = verifyRuntimeEvidence(expectations, documents);
|
|
23
|
+
const failed = parsed.booleanFlags.has('--strict') && report.status !== 'passed';
|
|
24
|
+
return {
|
|
25
|
+
standardOutput: encodeSortedJson(report, parsed.booleanFlags.has('--compact')),
|
|
26
|
+
standardError: failed
|
|
27
|
+
? 'Runtime verification requires review: failed calls, missing scenarios, stale runs, or incomplete evidence remain.\n'
|
|
28
|
+
: '',
|
|
29
|
+
exitCode: failed ? 1 : 0,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error instanceof RuntimeValidationError || error instanceof RuntimeInputError) {
|
|
34
|
+
return inputFailure(`${error.message}\n`);
|
|
35
|
+
}
|
|
36
|
+
return internalError();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** 런타임 기대 파일과 하나 이상의 실행 로그가 필요하다. */
|
|
40
|
+
export const runtimeUsage = 'Usage: isthmus verify-runtime --expectations <expectations.json> '
|
|
41
|
+
+ '<runtime.json> [more...] [--strict] [--compact]';
|
|
42
|
+
//# sourceMappingURL=runtime-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-command.js","sourceRoot":"","sources":["../../src/cli/runtime-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAC9G,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAEnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAEhF,2CAA2C;AAC3C,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,UAA6B,EAAE,YAA0B;IAEzD,MAAM,MAAM,GAAG,qBAAqB,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,gBAAgB,CAAC,EAAE,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC;IACzG,MAAM,YAAY,GAAG,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC9D,IAAI,MAAM,KAAK,SAAS,IAAI,YAAY,KAAK,SAAS,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC;QACvF,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,sBAAsB,EAAE,CAAC;QACrD,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,aAAa,EAAE,GAAG,YAAY,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAClF,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,iBAAiB,CAAC,YAAY,CAAC,CAAC;QACnD,MAAM,YAAY,GAAG,wBAAwB,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;QAC/E,MAAM,SAAS,GAAG,EAAE,CAAC;QACrB,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,WAAW;YAAE,SAAS,CAAC,IAAI,CAAC,kBAAkB,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnG,MAAM,MAAM,GAAG,qBAAqB,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;QAC9D,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC;QACjF,OAAO;YACL,cAAc,EAAE,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YAC9E,aAAa,EAAE,MAAM;gBACnB,CAAC,CAAC,qHAAqH;gBACvH,CAAC,CAAC,EAAE;YACN,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SACzB,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,sBAAsB,IAAI,KAAK,YAAY,iBAAiB,EAAE,CAAC;YAClF,OAAO,YAAY,CAAC,GAAG,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,aAAa,EAAE,CAAC;IACzB,CAAC;AACH,CAAC;AAED,qCAAqC;AACrC,MAAM,CAAC,MAAM,YAAY,GAAG,mEAAmE;MAC3F,iDAAiD,CAAC","sourcesContent":["import { parseBridgeRuntime, parseRuntimeExpectations, RuntimeValidationError } from '../exchange/runtime.ts';\nimport { MAX_DOCUMENTS_PER_JOIN } from '../join/join.ts';\nimport { verifyRuntimeEvidence } from '../report/runtime.ts';\nimport { encodeSortedJson } from '../report/sorted-json.ts';\nimport { inputFailure, internalError } from './command-support.ts';\nimport type { CommandResult, ReadTextFile } from './command-support.ts';\nimport { parseCommandArguments } from './parse-arguments.ts';\nimport { RuntimeInputError, RuntimeJsonReader } from './runtime-json-reader.ts';\n\n/** 시나리오별 실행 기대를 런타임 관찰 JSON과 대조하는 CLI다. */\nexport async function runRuntimeCommand(\n arguments_: readonly string[], readTextFile: ReadTextFile,\n): Promise<CommandResult> {\n const parsed = parseCommandArguments(arguments_.slice(1), ['--expectations'], ['--strict', '--compact']);\n const expectedPath = parsed?.valueFlags.get('--expectations');\n if (parsed === undefined || expectedPath === undefined || parsed.positionals.length === 0 ||\n parsed.positionals.length > MAX_DOCUMENTS_PER_JOIN) {\n return { standardOutput: '', standardError: `${runtimeUsage}\\n`, exitCode: 64 };\n }\n try {\n const reader = new RuntimeJsonReader(readTextFile);\n const expectations = parseRuntimeExpectations(await reader.read(expectedPath));\n const documents = [];\n for (const path of parsed.positionals) documents.push(parseBridgeRuntime(await reader.read(path)));\n const report = verifyRuntimeEvidence(expectations, documents);\n const failed = parsed.booleanFlags.has('--strict') && report.status !== 'passed';\n return {\n standardOutput: encodeSortedJson(report, parsed.booleanFlags.has('--compact')),\n standardError: failed\n ? 'Runtime verification requires review: failed calls, missing scenarios, stale runs, or incomplete evidence remain.\\n'\n : '',\n exitCode: failed ? 1 : 0,\n };\n } catch (error) {\n if (error instanceof RuntimeValidationError || error instanceof RuntimeInputError) {\n return inputFailure(`${error.message}\\n`);\n }\n return internalError();\n }\n}\n\n/** 런타임 기대 파일과 하나 이상의 실행 로그가 필요하다. */\nexport const runtimeUsage = 'Usage: isthmus verify-runtime --expectations <expectations.json> '\n + '<runtime.json> [more...] [--strict] [--compact]';\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReadTextFile } from './command-support.ts';
|
|
2
|
+
/** 기대·실행 JSON의 예산을 공유하며 이미 읽은 정적 context 크기도 포함한다. */
|
|
3
|
+
export declare class RuntimeJsonReader {
|
|
4
|
+
private readonly readTextFile;
|
|
5
|
+
private totalLength;
|
|
6
|
+
private position;
|
|
7
|
+
constructor(readTextFile: ReadTextFile, initialLength?: number);
|
|
8
|
+
read(path: string): Promise<unknown>;
|
|
9
|
+
}
|
|
10
|
+
/** runtime 입력 I/O·JSON 오류를 내부 결함과 구분한다. */
|
|
11
|
+
export declare class RuntimeInputError extends Error {
|
|
12
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { isJsonParseFailure, MAX_INPUT_TEXT_LENGTH, MAX_TOTAL_INPUT_TEXT_LENGTH } from "./command-support.js";
|
|
2
|
+
/** 기대·실행 JSON의 예산을 공유하며 이미 읽은 정적 context 크기도 포함한다. */
|
|
3
|
+
export class RuntimeJsonReader {
|
|
4
|
+
readTextFile;
|
|
5
|
+
totalLength;
|
|
6
|
+
position = 0;
|
|
7
|
+
constructor(readTextFile, initialLength = 0) {
|
|
8
|
+
this.readTextFile = readTextFile;
|
|
9
|
+
this.totalLength = initialLength;
|
|
10
|
+
}
|
|
11
|
+
async read(path) {
|
|
12
|
+
this.position++;
|
|
13
|
+
let text;
|
|
14
|
+
try {
|
|
15
|
+
text = await this.readTextFile(path);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
throw new RuntimeInputError(`Unable to read runtime input ${this.position}; check the file exists and is readable.`);
|
|
19
|
+
}
|
|
20
|
+
this.totalLength += text.length;
|
|
21
|
+
if (text.length > MAX_INPUT_TEXT_LENGTH || this.totalLength > MAX_TOTAL_INPUT_TEXT_LENGTH) {
|
|
22
|
+
throw new RuntimeInputError(`Runtime input ${this.position} exceeds the input size limits.`);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(text);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (isJsonParseFailure(error))
|
|
29
|
+
throw new RuntimeInputError(`Runtime input ${this.position} is not valid JSON.`);
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** runtime 입력 I/O·JSON 오류를 내부 결함과 구분한다. */
|
|
35
|
+
export class RuntimeInputError extends Error {
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=runtime-json-reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-json-reader.js","sourceRoot":"","sources":["../../src/cli/runtime-json-reader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;AAG9G,sDAAsD;AACtD,MAAM,OAAO,iBAAiB;IACX,YAAY,CAAe;IACpC,WAAW,CAAS;IACpB,QAAQ,GAAG,CAAC,CAAC;IAErB,YAAY,YAA0B,EAAE,aAAa,GAAG,CAAC;QACvD,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,WAAW,GAAG,aAAa,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAY;QACrB,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChB,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YAAC,IAAI,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QAAC,CAAC;QAC7C,MAAM,CAAC;YAAC,MAAM,IAAI,iBAAiB,CAAC,gCAAgC,IAAI,CAAC,QAAQ,0CAA0C,CAAC,CAAC;QAAC,CAAC;QAC/H,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,MAAM,CAAC;QAChC,IAAI,IAAI,CAAC,MAAM,GAAG,qBAAqB,IAAI,IAAI,CAAC,WAAW,GAAG,2BAA2B,EAAE,CAAC;YAC1F,MAAM,IAAI,iBAAiB,CAAC,iBAAiB,IAAI,CAAC,QAAQ,iCAAiC,CAAC,CAAC;QAC/F,CAAC;QACD,IAAI,CAAC;YAAC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAAC,CAAC;QAChC,OAAO,KAAK,EAAE,CAAC;YACb,IAAI,kBAAkB,CAAC,KAAK,CAAC;gBAAE,MAAM,IAAI,iBAAiB,CAAC,iBAAiB,IAAI,CAAC,QAAQ,qBAAqB,CAAC,CAAC;YAChH,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;CACF;AAED,2CAA2C;AAC3C,MAAM,OAAO,iBAAkB,SAAQ,KAAK;CAAG","sourcesContent":["import { isJsonParseFailure, MAX_INPUT_TEXT_LENGTH, MAX_TOTAL_INPUT_TEXT_LENGTH } from './command-support.ts';\nimport type { ReadTextFile } from './command-support.ts';\n\n/** 기대·실행 JSON의 예산을 공유하며 이미 읽은 정적 context 크기도 포함한다. */\nexport class RuntimeJsonReader {\n private readonly readTextFile: ReadTextFile;\n private totalLength: number;\n private position = 0;\n\n constructor(readTextFile: ReadTextFile, initialLength = 0) {\n this.readTextFile = readTextFile;\n this.totalLength = initialLength;\n }\n\n async read(path: string): Promise<unknown> {\n this.position++;\n let text: string;\n try { text = await this.readTextFile(path); }\n catch { throw new RuntimeInputError(`Unable to read runtime input ${this.position}; check the file exists and is readable.`); }\n this.totalLength += text.length;\n if (text.length > MAX_INPUT_TEXT_LENGTH || this.totalLength > MAX_TOTAL_INPUT_TEXT_LENGTH) {\n throw new RuntimeInputError(`Runtime input ${this.position} exceeds the input size limits.`);\n }\n try { return JSON.parse(text); }\n catch (error) {\n if (isJsonParseFailure(error)) throw new RuntimeInputError(`Runtime input ${this.position} is not valid JSON.`);\n throw error;\n }\n }\n}\n\n/** runtime 입력 I/O·JSON 오류를 내부 결함과 구분한다. */\nexport class RuntimeInputError extends Error {}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** 변경 대상의 프로젝트 상대 파일과 정확한 심볼 이름/USR이다. 두 목록은 합집합이다. */
|
|
2
|
+
export interface ImpactSelection {
|
|
3
|
+
readonly files: readonly string[];
|
|
4
|
+
readonly symbols: readonly string[];
|
|
5
|
+
}
|
|
6
|
+
/** 변경 목록이 큰 입력을 통한 선택 집합 증폭을 일으키지 않도록 제한한다. */
|
|
7
|
+
export declare const MAX_IMPACT_SELECTORS = 10000;
|
|
8
|
+
/** 변경 선택 계약 실패를 소스 값이 없는 메시지로 구분한다. */
|
|
9
|
+
export declare class ImpactSelectionValidationError extends Error {
|
|
10
|
+
constructor(message: string);
|
|
11
|
+
}
|
|
12
|
+
/** 외부 변경 목록을 검증하고 위치·심볼 문자열을 손대지 않은 채 중복 제거한다. */
|
|
13
|
+
export declare function parseImpactSelection(input: unknown): ImpactSelection;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { isProjectRelativePath, isSafeNonEmptyString } from "./parse.js";
|
|
2
|
+
import { compareStrings } from "../compare.js";
|
|
3
|
+
/** 변경 목록이 큰 입력을 통한 선택 집합 증폭을 일으키지 않도록 제한한다. */
|
|
4
|
+
export const MAX_IMPACT_SELECTORS = 10_000;
|
|
5
|
+
/** 변경 선택 계약 실패를 소스 값이 없는 메시지로 구분한다. */
|
|
6
|
+
export class ImpactSelectionValidationError extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'ImpactSelectionValidationError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** 외부 변경 목록을 검증하고 위치·심볼 문자열을 손대지 않은 채 중복 제거한다. */
|
|
13
|
+
export function parseImpactSelection(input) {
|
|
14
|
+
if (typeof input !== 'object' || input === null || Array.isArray(input)) {
|
|
15
|
+
throw new ImpactSelectionValidationError('Changes must be a JSON object.');
|
|
16
|
+
}
|
|
17
|
+
const value = input;
|
|
18
|
+
if (value.format !== 'isthmus-changes' || value.version !== 1) {
|
|
19
|
+
throw new ImpactSelectionValidationError('Expected isthmus-changes version 1.');
|
|
20
|
+
}
|
|
21
|
+
const files = value.files === undefined ? [] : value.files;
|
|
22
|
+
const symbols = value.symbols === undefined ? [] : value.symbols;
|
|
23
|
+
if (!Array.isArray(files) || !Array.isArray(symbols) ||
|
|
24
|
+
files.length + symbols.length === 0 || files.length + symbols.length > MAX_IMPACT_SELECTORS) {
|
|
25
|
+
throw new ImpactSelectionValidationError('Changes require 1 to 10000 file or symbol selectors.');
|
|
26
|
+
}
|
|
27
|
+
if (!files.every(isProjectRelativePath) || !symbols.every(isSafeNonEmptyString)) {
|
|
28
|
+
throw new ImpactSelectionValidationError('Changes require relative file paths and non-empty safe symbol names.');
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
files: [...new Set(files)].sort(compareStrings),
|
|
32
|
+
symbols: [...new Set(symbols)].sort(compareStrings),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=impact-selection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"impact-selection.js","sourceRoot":"","sources":["../../src/exchange/impact-selection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAQ/C,+CAA+C;AAC/C,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAE3C,uCAAuC;AACvC,MAAM,OAAO,8BAA+B,SAAQ,KAAK;IACvD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gCAAgC,CAAC;IAC/C,CAAC;CACF;AAED,kDAAkD;AAClD,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,8BAA8B,CAAC,gCAAgC,CAAC,CAAC;IAC7E,CAAC;IACD,MAAM,KAAK,GAAG,KAAgC,CAAC;IAC/C,IAAI,KAAK,CAAC,MAAM,KAAK,iBAAiB,IAAI,KAAK,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,8BAA8B,CAAC,qCAAqC,CAAC,CAAC;IAClF,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;IAC3D,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;IACjE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAClD,KAAK,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,oBAAoB,EAAE,CAAC;QAC9F,MAAM,IAAI,8BAA8B,CAAC,sDAAsD,CAAC,CAAC;IACnG,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,qBAAqB,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,oBAAoB,CAAC,EAAE,CAAC;QAChF,MAAM,IAAI,8BAA8B,CAAC,sEAAsE,CAAC,CAAC;IACnH,CAAC;IACD,OAAO;QACL,KAAK,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,KAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC;QAC3D,OAAO,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,OAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC;KAChE,CAAC;AACJ,CAAC","sourcesContent":["import { isProjectRelativePath, isSafeNonEmptyString } from './parse.ts';\nimport { compareStrings } from '../compare.ts';\n\n/** 변경 대상의 프로젝트 상대 파일과 정확한 심볼 이름/USR이다. 두 목록은 합집합이다. */\nexport interface ImpactSelection {\n readonly files: readonly string[];\n readonly symbols: readonly string[];\n}\n\n/** 변경 목록이 큰 입력을 통한 선택 집합 증폭을 일으키지 않도록 제한한다. */\nexport const MAX_IMPACT_SELECTORS = 10_000;\n\n/** 변경 선택 계약 실패를 소스 값이 없는 메시지로 구분한다. */\nexport class ImpactSelectionValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ImpactSelectionValidationError';\n }\n}\n\n/** 외부 변경 목록을 검증하고 위치·심볼 문자열을 손대지 않은 채 중복 제거한다. */\nexport function parseImpactSelection(input: unknown): ImpactSelection {\n if (typeof input !== 'object' || input === null || Array.isArray(input)) {\n throw new ImpactSelectionValidationError('Changes must be a JSON object.');\n }\n const value = input as Record<string, unknown>;\n if (value.format !== 'isthmus-changes' || value.version !== 1) {\n throw new ImpactSelectionValidationError('Expected isthmus-changes version 1.');\n }\n const files = value.files === undefined ? [] : value.files;\n const symbols = value.symbols === undefined ? [] : value.symbols;\n if (!Array.isArray(files) || !Array.isArray(symbols) ||\n files.length + symbols.length === 0 || files.length + symbols.length > MAX_IMPACT_SELECTORS) {\n throw new ImpactSelectionValidationError('Changes require 1 to 10000 file or symbol selectors.');\n }\n if (!files.every(isProjectRelativePath) || !symbols.every(isSafeNonEmptyString)) {\n throw new ImpactSelectionValidationError('Changes require relative file paths and non-empty safe symbol names.');\n }\n return {\n files: [...new Set(files as string[])].sort(compareStrings),\n symbols: [...new Set(symbols as string[])].sort(compareStrings),\n };\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type LanguageImpact } from './preflight-context.ts';
|
|
2
|
+
import type { ProducerImpactMetadata } from './producer-impact.ts';
|
|
3
|
+
/** Kartograph의 현재 시점 영향 근거를 공통 사전 점검 형식으로 연결한다. */
|
|
4
|
+
export declare function adaptKartographImpact(raw: unknown, metadata: ProducerImpactMetadata): LanguageImpact;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { PreflightValidationError, validateLanguageImpact } from "./preflight-context.js";
|
|
2
|
+
import { isProjectRelativePath, isSafeNonEmptyString } from "./parse.js";
|
|
3
|
+
import { compareStrings } from "../compare.js";
|
|
4
|
+
/** Kartograph의 현재 시점 영향 근거를 공통 사전 점검 형식으로 연결한다. */
|
|
5
|
+
export function adaptKartographImpact(raw, metadata) {
|
|
6
|
+
const value = object(raw);
|
|
7
|
+
if (value.format !== 'kartograph-impact' || value.version !== 1)
|
|
8
|
+
fail('Expected kartograph-impact version 1.');
|
|
9
|
+
if (!['found', 'partial', 'notFound', 'noChanges'].includes(string(value.status)))
|
|
10
|
+
fail('Invalid Kartograph impact status.');
|
|
11
|
+
if (!isSafeNonEmptyString(metadata.project))
|
|
12
|
+
fail('Invalid Kartograph project metadata.');
|
|
13
|
+
const inputs = object(value.inputs);
|
|
14
|
+
if (inputs.base !== undefined)
|
|
15
|
+
fail('Kartograph preflight requires a current-only impact report.');
|
|
16
|
+
const limitations = array(value.limitations).map((item) => string(item));
|
|
17
|
+
const flags = object(value.truncated);
|
|
18
|
+
for (const key of ['results', 'depth', 'budget'])
|
|
19
|
+
if (typeof flags[key] !== 'boolean')
|
|
20
|
+
fail('Invalid Kartograph truncation flag.');
|
|
21
|
+
const navigation = value.navigation === undefined ? undefined : object(value.navigation);
|
|
22
|
+
if (navigation !== undefined && (!Number.isSafeInteger(navigation.offset) || navigation.offset < 0 ||
|
|
23
|
+
typeof navigation.hasNext !== 'boolean' || typeof navigation.hasPrevious !== 'boolean'))
|
|
24
|
+
fail('Invalid Kartograph navigation.');
|
|
25
|
+
let truncated = Object.values(flags).some((flag) => flag === true) || value.status === 'partial' ||
|
|
26
|
+
(navigation !== undefined && (navigation.offset !== 0 || navigation.hasNext === true || navigation.hasPrevious === true));
|
|
27
|
+
let missingLocations = 0;
|
|
28
|
+
let partialLocations = 0;
|
|
29
|
+
let omittedPaths = 0;
|
|
30
|
+
let pathItems = 0;
|
|
31
|
+
const symbols = new Map();
|
|
32
|
+
const changed = array(value.changed).map(object);
|
|
33
|
+
const affected = array(value.affected).map(object);
|
|
34
|
+
for (const row of [...changed, ...affected]) {
|
|
35
|
+
const id = string(row.usr);
|
|
36
|
+
if (symbols.has(id))
|
|
37
|
+
fail('Kartograph impact symbol ids must be unique.');
|
|
38
|
+
const current = array(row.presentIn).map(string).includes('current');
|
|
39
|
+
if (!current) {
|
|
40
|
+
omittedPaths++;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
let location;
|
|
44
|
+
if (row.location !== null && row.location !== undefined) {
|
|
45
|
+
const source = object(row.location);
|
|
46
|
+
const path = typeof source.path === 'string' && source.path.startsWith(`${metadata.project}/`)
|
|
47
|
+
? source.path.slice(metadata.project.length + 1) : source.path;
|
|
48
|
+
if (isProjectRelativePath(path)) {
|
|
49
|
+
const line = Number.isSafeInteger(source.line) && source.line > 0 ? source.line : undefined;
|
|
50
|
+
const column = line !== undefined && Number.isSafeInteger(source.column) && source.column > 0
|
|
51
|
+
? source.column : undefined;
|
|
52
|
+
location = { path, ...(line === undefined ? {} : { line }), ...(column === undefined ? {} : { column }) };
|
|
53
|
+
if (column === undefined)
|
|
54
|
+
partialLocations++;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (location === undefined)
|
|
58
|
+
missingLocations++;
|
|
59
|
+
symbols.set(id, { id, qualifiedName: string(row.qualifiedName),
|
|
60
|
+
...(row.kind === undefined ? {} : { kind: string(row.kind) }), ...(location === undefined ? {} : { location }) });
|
|
61
|
+
}
|
|
62
|
+
const roots = changed.flatMap((row) => symbols.has(string(row.usr)) ? [symbols.get(string(row.usr))] : []);
|
|
63
|
+
const rootIds = new Set(roots.map(({ id }) => id));
|
|
64
|
+
const adjacency = new Map();
|
|
65
|
+
for (const row of affected) {
|
|
66
|
+
omittedPaths += row.pathOmissions === undefined ? 0 : array(row.pathOmissions).length;
|
|
67
|
+
if (row.pathStatus !== undefined && row.pathStatus !== 'complete')
|
|
68
|
+
truncated = true;
|
|
69
|
+
for (const input of array(row.paths)) {
|
|
70
|
+
const path = object(input);
|
|
71
|
+
const revision = string(path.revision);
|
|
72
|
+
if (revision === 'base') {
|
|
73
|
+
omittedPaths++;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (revision !== 'current')
|
|
77
|
+
fail('Invalid Kartograph path revision.');
|
|
78
|
+
const nodes = array(path.nodes).map(string);
|
|
79
|
+
const edges = array(path.edges).map(object);
|
|
80
|
+
pathItems += nodes.length + edges.length;
|
|
81
|
+
if (pathItems > 1_000_000)
|
|
82
|
+
fail('Kartograph impact paths exceed their budget.');
|
|
83
|
+
if (nodes.length < 2 || nodes.length > 129 || edges.length !== nodes.length - 1 || nodes[0] !== row.usr ||
|
|
84
|
+
nodes.at(-1) !== path.changed || new Set(nodes).size !== nodes.length)
|
|
85
|
+
fail('Invalid Kartograph impact path.');
|
|
86
|
+
const relationships = edges.map((edge, index) => {
|
|
87
|
+
const source = string(edge.source);
|
|
88
|
+
const target = string(edge.target);
|
|
89
|
+
const forward = source === nodes[index] && target === nodes[index + 1];
|
|
90
|
+
const reverse = target === nodes[index] && source === nodes[index + 1];
|
|
91
|
+
if ((!forward && !reverse) || (reverse && edge.kind !== 'override') ||
|
|
92
|
+
edge.traversal !== (forward ? 'dependency' : 'overrideContract')) {
|
|
93
|
+
fail('Kartograph path edges do not match their traversal.');
|
|
94
|
+
}
|
|
95
|
+
return [string(edge.kind), `origin:${string(edge.origin)}`, `traversal:${string(edge.traversal)}`];
|
|
96
|
+
});
|
|
97
|
+
if (!rootIds.has(string(path.changed)) || nodes.some((id) => !symbols.has(id))) {
|
|
98
|
+
omittedPaths++;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
for (let index = 0; index < edges.length; index++) {
|
|
102
|
+
const parent = nodes[index + 1];
|
|
103
|
+
const child = nodes[index];
|
|
104
|
+
let children = adjacency.get(parent);
|
|
105
|
+
if (children === undefined) {
|
|
106
|
+
children = new Map();
|
|
107
|
+
adjacency.set(parent, children);
|
|
108
|
+
}
|
|
109
|
+
let reasons = children.get(child);
|
|
110
|
+
if (reasons === undefined) {
|
|
111
|
+
reasons = new Set();
|
|
112
|
+
children.set(child, reasons);
|
|
113
|
+
}
|
|
114
|
+
for (const reason of relationships[index])
|
|
115
|
+
reasons.add(reason);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// 같은 current 그래프의 관찰 간선만 사용한다. 한 정점의 대표 경로는 가장 짧은 경로다.
|
|
120
|
+
const queue = [...rootIds].sort(compareStrings);
|
|
121
|
+
const depth = new Map(queue.map((id) => [id, 0]));
|
|
122
|
+
const rows = [];
|
|
123
|
+
for (let index = 0; index < queue.length; index++) {
|
|
124
|
+
const parent = queue[index];
|
|
125
|
+
for (const [id, reasons] of [...(adjacency.get(parent) ?? [])].sort(([a], [b]) => compareStrings(a, b))) {
|
|
126
|
+
if (depth.has(id))
|
|
127
|
+
continue;
|
|
128
|
+
const distance = depth.get(parent) + 1;
|
|
129
|
+
if (distance > 128) {
|
|
130
|
+
omittedPaths++;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
depth.set(id, distance);
|
|
134
|
+
queue.push(id);
|
|
135
|
+
rows.push({ symbol: symbols.get(id), via: parent, depth: distance, relationships: [...reasons].sort(compareStrings) });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
omittedPaths += affected.filter((row) => symbols.has(string(row.usr)) && !depth.has(string(row.usr))).length;
|
|
139
|
+
const unresolved = array(value.unresolved);
|
|
140
|
+
if (unresolved.length > 0)
|
|
141
|
+
limitations.push(`kartograph-unresolved: ${unresolved.length} requested item(s) require review`);
|
|
142
|
+
if (missingLocations > 0)
|
|
143
|
+
limitations.push(`kartograph-unresolved-locations: ${missingLocations} symbol location(s) omitted`);
|
|
144
|
+
if (partialLocations > 0)
|
|
145
|
+
limitations.push(`kartograph-partial-source-locations: ${partialLocations} JVM symbol location(s) lack exact line or column coordinates`);
|
|
146
|
+
if (omittedPaths > 0)
|
|
147
|
+
limitations.push(`kartograph-unrepresented-paths: ${omittedPaths} path or symbol observation(s) require the original producer report`);
|
|
148
|
+
truncated ||= omittedPaths > 0;
|
|
149
|
+
return validateLanguageImpact({ id: metadata.id, platform: 'kotlin', requested: metadata.requested,
|
|
150
|
+
tool: metadata.tool, ...(metadata.trigger === undefined ? {} : { trigger: metadata.trigger }),
|
|
151
|
+
roots, affected: rows, limitations, truncated });
|
|
152
|
+
}
|
|
153
|
+
function object(value) {
|
|
154
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
155
|
+
fail('Invalid Kartograph impact object.');
|
|
156
|
+
return value;
|
|
157
|
+
}
|
|
158
|
+
function array(value) {
|
|
159
|
+
if (!Array.isArray(value) || value.length > 50_000)
|
|
160
|
+
fail('Invalid Kartograph impact array.');
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
function string(value) {
|
|
164
|
+
if (!isSafeNonEmptyString(value))
|
|
165
|
+
fail('Invalid Kartograph impact string.');
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
168
|
+
function fail(message) { throw new PreflightValidationError(message); }
|
|
169
|
+
//# sourceMappingURL=kartograph-impact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kartograph-impact.js","sourceRoot":"","sources":["../../src/exchange/kartograph-impact.ts"],"names":[],"mappings":"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","sourcesContent":["import { PreflightValidationError, validateLanguageImpact, type LanguageImpact, type ImpactSymbol } from './preflight-context.ts';\nimport type { ProducerImpactMetadata } from './producer-impact.ts';\nimport { isProjectRelativePath, isSafeNonEmptyString } from './parse.ts';\nimport { compareStrings } from '../compare.ts';\n\n/** Kartograph의 현재 시점 영향 근거를 공통 사전 점검 형식으로 연결한다. */\nexport function adaptKartographImpact(raw: unknown, metadata: ProducerImpactMetadata): LanguageImpact {\n const value = object(raw);\n if (value.format !== 'kartograph-impact' || value.version !== 1) fail('Expected kartograph-impact version 1.');\n if (!['found', 'partial', 'notFound', 'noChanges'].includes(string(value.status))) fail('Invalid Kartograph impact status.');\n if (!isSafeNonEmptyString(metadata.project)) fail('Invalid Kartograph project metadata.');\n const inputs = object(value.inputs);\n if (inputs.base !== undefined) fail('Kartograph preflight requires a current-only impact report.');\n const limitations = array(value.limitations).map((item) => string(item));\n const flags = object(value.truncated);\n for (const key of ['results', 'depth', 'budget']) if (typeof flags[key] !== 'boolean') fail('Invalid Kartograph truncation flag.');\n const navigation = value.navigation === undefined ? undefined : object(value.navigation);\n if (navigation !== undefined && (!Number.isSafeInteger(navigation.offset) || (navigation.offset as number) < 0 ||\n typeof navigation.hasNext !== 'boolean' || typeof navigation.hasPrevious !== 'boolean')) fail('Invalid Kartograph navigation.');\n let truncated = Object.values(flags).some((flag) => flag === true) || value.status === 'partial' ||\n (navigation !== undefined && (navigation.offset !== 0 || navigation.hasNext === true || navigation.hasPrevious === true));\n let missingLocations = 0;\n let partialLocations = 0;\n let omittedPaths = 0;\n let pathItems = 0;\n const symbols = new Map<string, ImpactSymbol>();\n const changed = array(value.changed).map(object);\n const affected = array(value.affected).map(object);\n for (const row of [...changed, ...affected]) {\n const id = string(row.usr);\n if (symbols.has(id)) fail('Kartograph impact symbol ids must be unique.');\n const current = array(row.presentIn).map(string).includes('current');\n if (!current) { omittedPaths++; continue; }\n let location;\n if (row.location !== null && row.location !== undefined) {\n const source = object(row.location);\n const path = typeof source.path === 'string' && source.path.startsWith(`${metadata.project}/`)\n ? source.path.slice(metadata.project.length + 1) : source.path;\n if (isProjectRelativePath(path)) {\n const line = Number.isSafeInteger(source.line) && (source.line as number) > 0 ? source.line as number : undefined;\n const column = line !== undefined && Number.isSafeInteger(source.column) && (source.column as number) > 0\n ? source.column as number : undefined;\n location = { path, ...(line === undefined ? {} : { line }), ...(column === undefined ? {} : { column }) };\n if (column === undefined) partialLocations++;\n }\n }\n if (location === undefined) missingLocations++;\n symbols.set(id, { id, qualifiedName: string(row.qualifiedName),\n ...(row.kind === undefined ? {} : { kind: string(row.kind) }), ...(location === undefined ? {} : { location }) });\n }\n const roots = changed.flatMap((row) => symbols.has(string(row.usr)) ? [symbols.get(string(row.usr))!] : []);\n const rootIds = new Set(roots.map(({ id }) => id));\n const adjacency = new Map<string, Map<string, Set<string>>>();\n for (const row of affected) {\n omittedPaths += row.pathOmissions === undefined ? 0 : array(row.pathOmissions).length;\n if (row.pathStatus !== undefined && row.pathStatus !== 'complete') truncated = true;\n for (const input of array(row.paths)) {\n const path = object(input);\n const revision = string(path.revision);\n if (revision === 'base') { omittedPaths++; continue; }\n if (revision !== 'current') fail('Invalid Kartograph path revision.');\n const nodes = array(path.nodes).map(string);\n const edges = array(path.edges).map(object);\n pathItems += nodes.length + edges.length;\n if (pathItems > 1_000_000) fail('Kartograph impact paths exceed their budget.');\n if (nodes.length < 2 || nodes.length > 129 || edges.length !== nodes.length - 1 || nodes[0] !== row.usr ||\n nodes.at(-1) !== path.changed || new Set(nodes).size !== nodes.length) fail('Invalid Kartograph impact path.');\n const relationships = edges.map((edge, index) => {\n const source = string(edge.source); const target = string(edge.target);\n const forward = source === nodes[index] && target === nodes[index + 1];\n const reverse = target === nodes[index] && source === nodes[index + 1];\n if ((!forward && !reverse) || (reverse && edge.kind !== 'override') ||\n edge.traversal !== (forward ? 'dependency' : 'overrideContract')) {\n fail('Kartograph path edges do not match their traversal.');\n }\n return [string(edge.kind), `origin:${string(edge.origin)}`, `traversal:${string(edge.traversal)}`];\n });\n if (!rootIds.has(string(path.changed)) || nodes.some((id) => !symbols.has(id))) { omittedPaths++; continue; }\n for (let index = 0; index < edges.length; index++) {\n const parent = nodes[index + 1]!; const child = nodes[index]!;\n let children = adjacency.get(parent);\n if (children === undefined) { children = new Map(); adjacency.set(parent, children); }\n let reasons = children.get(child);\n if (reasons === undefined) { reasons = new Set(); children.set(child, reasons); }\n for (const reason of relationships[index]!) reasons.add(reason);\n }\n }\n }\n // 같은 current 그래프의 관찰 간선만 사용한다. 한 정점의 대표 경로는 가장 짧은 경로다.\n const queue = [...rootIds].sort(compareStrings);\n const depth = new Map(queue.map((id) => [id, 0]));\n const rows: Array<LanguageImpact['affected'][number]> = [];\n for (let index = 0; index < queue.length; index++) {\n const parent = queue[index]!;\n for (const [id, reasons] of [...(adjacency.get(parent) ?? [])].sort(([a], [b]) => compareStrings(a, b))) {\n if (depth.has(id)) continue;\n const distance = depth.get(parent)! + 1;\n if (distance > 128) { omittedPaths++; continue; }\n depth.set(id, distance); queue.push(id);\n rows.push({ symbol: symbols.get(id)!, via: parent, depth: distance, relationships: [...reasons].sort(compareStrings) });\n }\n }\n omittedPaths += affected.filter((row) => symbols.has(string(row.usr)) && !depth.has(string(row.usr))).length;\n const unresolved = array(value.unresolved);\n if (unresolved.length > 0) limitations.push(`kartograph-unresolved: ${unresolved.length} requested item(s) require review`);\n if (missingLocations > 0) limitations.push(`kartograph-unresolved-locations: ${missingLocations} symbol location(s) omitted`);\n if (partialLocations > 0) limitations.push(`kartograph-partial-source-locations: ${partialLocations} JVM symbol location(s) lack exact line or column coordinates`);\n if (omittedPaths > 0) limitations.push(`kartograph-unrepresented-paths: ${omittedPaths} path or symbol observation(s) require the original producer report`);\n truncated ||= omittedPaths > 0;\n return validateLanguageImpact({ id: metadata.id, platform: 'kotlin', requested: metadata.requested,\n tool: metadata.tool, ...(metadata.trigger === undefined ? {} : { trigger: metadata.trigger }),\n roots, affected: rows, limitations, truncated });\n}\n\nfunction object(value: unknown): Record<string, unknown> {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) fail('Invalid Kartograph impact object.');\n return value as Record<string, unknown>;\n}\nfunction array(value: unknown): unknown[] {\n if (!Array.isArray(value) || value.length > 50_000) fail('Invalid Kartograph impact array.');\n return value;\n}\nfunction string(value: unknown): string {\n if (!isSafeNonEmptyString(value)) fail('Invalid Kartograph impact string.');\n return value;\n}\nfunction fail(message: string): never { throw new PreflightValidationError(message); }\n"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { BridgeLocation, BridgeSourceLanguage, BridgeSymbol } from './parse.ts';
|
|
2
|
+
/** producer가 관찰한 사용 관계를 귀속한 실제 native handler 범위다. */
|
|
3
|
+
export interface BridgeHandlerScope {
|
|
4
|
+
readonly start: BridgeLocation;
|
|
5
|
+
readonly end: BridgeLocation;
|
|
6
|
+
readonly complete: boolean;
|
|
7
|
+
}
|
|
8
|
+
/** 실제 참조 위치와 대상, index의 overrides로 확인한 dispatch 후보를 보존한다. */
|
|
9
|
+
export interface BridgeHandlerDependency {
|
|
10
|
+
readonly kind: 'call' | 'reference';
|
|
11
|
+
readonly scope: 'handler' | 'registration';
|
|
12
|
+
readonly location: BridgeLocation;
|
|
13
|
+
readonly symbol: BridgeSymbol & {
|
|
14
|
+
readonly usr: string;
|
|
15
|
+
};
|
|
16
|
+
readonly dispatchTargets?: readonly (BridgeSymbol & {
|
|
17
|
+
readonly usr: string;
|
|
18
|
+
})[];
|
|
19
|
+
}
|
|
20
|
+
/** Basic 채널의 send·handler 사실이며 MethodChannel 메서드를 합성하지 않는다. */
|
|
21
|
+
export interface BridgeMessageFact {
|
|
22
|
+
readonly kind: 'message-send' | 'message-handle';
|
|
23
|
+
readonly channel: string | null;
|
|
24
|
+
readonly dynamic: boolean;
|
|
25
|
+
readonly channelPrefix?: string;
|
|
26
|
+
readonly location: BridgeLocation;
|
|
27
|
+
readonly symbol?: BridgeSymbol;
|
|
28
|
+
readonly sourceLanguage?: BridgeSourceLanguage;
|
|
29
|
+
readonly handlerScope?: BridgeHandlerScope;
|
|
30
|
+
readonly dependencies?: readonly BridgeHandlerDependency[];
|
|
31
|
+
}
|
|
32
|
+
/** v1 전용 소비자가 조용히 무시하지 못하도록 별도 버전으로 전달하는 Basic 문서다. */
|
|
33
|
+
export interface BridgeMessageDocument {
|
|
34
|
+
readonly format: 'bridge-facts';
|
|
35
|
+
readonly version: 2;
|
|
36
|
+
readonly transport: 'basic-message-channel';
|
|
37
|
+
readonly platform: 'dart' | 'swift' | 'kotlin';
|
|
38
|
+
readonly target: 'flutter' | null;
|
|
39
|
+
readonly project: string;
|
|
40
|
+
readonly generatedAt: string;
|
|
41
|
+
readonly tool: {
|
|
42
|
+
readonly name: string;
|
|
43
|
+
readonly version: string;
|
|
44
|
+
};
|
|
45
|
+
readonly facts: readonly BridgeMessageFact[];
|
|
46
|
+
readonly limitations: readonly string[];
|
|
47
|
+
}
|
|
48
|
+
/** 입력 묶음의 역할·프로젝트 범위를 값 조인 없이 검증한다. */
|
|
49
|
+
export declare function validateMessageDocuments(documents: readonly BridgeMessageDocument[], project: string): void;
|
|
50
|
+
/** Basic 전용 v2를 검증하고 알려진 필드만 복사한다. v1 입력 파서는 그대로 유지한다. */
|
|
51
|
+
export declare function parseMessageBridgeDocument(input: unknown): BridgeMessageDocument;
|