isthmus-cli 0.4.1 → 0.5.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.
@@ -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, internalError, inputFailureResult, readBridgeDocuments, } from "./check-command.js";
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
- const forIndex = arguments_.indexOf('--for');
36
- if (arguments_[0] !== 'retentions' || forIndex < 3)
37
+ if (arguments_[0] !== 'retentions')
37
38
  return undefined;
38
- if (forIndex !== arguments_.length - 2)
39
+ const parsed = parseCommandArguments(arguments_.slice(1), ['--for'], []);
40
+ if (parsed === undefined)
39
41
  return undefined;
40
- if (arguments_[forIndex + 1] !== 'cartograph')
42
+ if (parsed.valueFlags.get('--for') !== 'cartograph')
41
43
  return undefined;
42
- const paths = arguments_.slice(1, forIndex);
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,kBAAkB,EAClB,mBAAmB,GAIpB,MAAM,oBAAoB,CAAC;AAI5B,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;YAAE,OAAO,uBAAuB,EAAE,CAAC;QACnE,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,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,YAAY,IAAI,QAAQ,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IACrE,IAAI,QAAQ,KAAK,UAAU,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IACzD,IAAI,UAAU,CAAC,QAAQ,GAAG,CAAC,CAAC,KAAK,YAAY;QAAE,OAAO,SAAS,CAAC;IAChE,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC5C,OAAO,KAAK,CAAC,MAAM,GAAG,sBAAsB;QAC1C,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QAC1C,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 internalError,\n inputFailureResult,\n readBridgeDocuments,\n type Clock,\n type CommandResult,\n type ReadTextFile,\n} from './check-command.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)) return bridgeJoinDeferredError();\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 const forIndex = arguments_.indexOf('--for');\n if (arguments_[0] !== 'retentions' || forIndex < 3) return undefined;\n if (forIndex !== arguments_.length - 2) return undefined;\n if (arguments_[forIndex + 1] !== 'cartograph') return undefined;\n const paths = arguments_.slice(1, forIndex);\n return paths.length > MAX_DOCUMENTS_PER_JOIN ||\n paths.some((path) => path.startsWith('-'))\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"]}
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"]}
@@ -1,37 +1,44 @@
1
1
  import type { BridgeFactsDocument } from '../exchange/parse.ts';
2
- /** 동일 프로젝트의 관찰 결과를 비교하며 삭제 안전성이나 rename을 추측하지 않는다. */
3
- export declare function createBridgeDiff(before: readonly BridgeFactsDocument[], after: readonly BridgeFactsDocument[]): {
4
- format: "isthmus-diff";
5
- version: 1;
6
- summary: {
7
- addedMethods: number;
8
- removedMethods: number;
9
- introducedErrors: number;
10
- introducedWarnings: number;
11
- resolvedIssues: number;
2
+ import type { JoinLimitation, MatchedMethod } from '../join/join.ts';
3
+ import type { CheckIssue } from './check-report.ts';
4
+ /** 두 스냅샷의 생산 도구 버전 근거다. */
5
+ export interface DiffProducerVersion {
6
+ readonly platform: string;
7
+ readonly name: string;
8
+ readonly version: string;
9
+ readonly generatedAt: string;
10
+ }
11
+ /**
12
+ * 동일 프로젝트의 전후 관찰을 비교한 isthmus-diff 문서다.
13
+ *
14
+ * added/removed는 키 집합 차이라 소스 줄 이동은 차이가 아니다. introduced와
15
+ * resolved는 논리 이슈 키 기준이라 코드가 바뀌면(-unverified → 본 판정)
16
+ * 해결·추가 한 쌍으로 보인다 — 심각도 상승은 새 관찰이기 때문이다.
17
+ */
18
+ export interface BridgeDiffDocument {
19
+ readonly format: 'isthmus-diff';
20
+ readonly version: 1;
21
+ readonly summary: {
22
+ readonly addedMethods: number;
23
+ readonly removedMethods: number;
24
+ readonly introducedErrors: number;
25
+ readonly introducedWarnings: number;
26
+ readonly resolvedIssues: number;
12
27
  };
13
- addedMethods: import("../join/join.ts").MatchedMethod[];
14
- removedMethods: import("../join/join.ts").MatchedMethod[];
15
- introducedIssues: import("./check-report.ts").CheckIssue[];
16
- resolvedIssues: import("./check-report.ts").CheckIssue[];
17
- limitations: {
18
- before: readonly import("../join/join.ts").JoinLimitation[];
19
- after: readonly import("../join/join.ts").JoinLimitation[];
20
- added: import("../join/join.ts").JoinLimitation[];
21
- removed: import("../join/join.ts").JoinLimitation[];
28
+ readonly addedMethods: readonly MatchedMethod[];
29
+ readonly removedMethods: readonly MatchedMethod[];
30
+ readonly introducedIssues: readonly CheckIssue[];
31
+ readonly resolvedIssues: readonly CheckIssue[];
32
+ readonly limitations: {
33
+ readonly before: readonly JoinLimitation[];
34
+ readonly after: readonly JoinLimitation[];
35
+ readonly added: readonly JoinLimitation[];
36
+ readonly removed: readonly JoinLimitation[];
22
37
  };
23
- producers: {
24
- before: {
25
- generatedAt: string;
26
- name: string;
27
- version: string;
28
- platform: import("../exchange/parse.ts").BridgePlatform;
29
- }[];
30
- after: {
31
- generatedAt: string;
32
- name: string;
33
- version: string;
34
- platform: import("../exchange/parse.ts").BridgePlatform;
35
- }[];
38
+ readonly producers: {
39
+ readonly before: readonly DiffProducerVersion[];
40
+ readonly after: readonly DiffProducerVersion[];
36
41
  };
37
- };
42
+ }
43
+ /** 동일 프로젝트의 관찰 결과를 비교하며 삭제 안전성이나 rename을 추측하지 않는다. */
44
+ export declare function createBridgeDiff(before: readonly BridgeFactsDocument[], after: readonly BridgeFactsDocument[]): BridgeDiffDocument;
@@ -8,7 +8,10 @@ export function createBridgeDiff(before, after) {
8
8
  const oldJoin = joinBridgeDocuments(before);
9
9
  const newJoin = joinBridgeDocuments(after);
10
10
  if (isBridgeJoinDeferred(oldJoin) || isBridgeJoinDeferred(newJoin)) {
11
- throw new BridgeJoinValidationError('Cannot compare deferred bridge joins; split mixed bridge targets and retry.');
11
+ throw new BridgeJoinValidationError('Cannot compare deferred bridge joins; split mixed bridge targets and '
12
+ + `retry. The before inputs observed ${oldJoin.observedFacts} facts across `
13
+ + `${before.length} documents, and the after inputs observed `
14
+ + `${newJoin.observedFacts} facts across ${after.length} documents.`);
12
15
  }
13
16
  const oldReport = createCheckReport(oldJoin);
14
17
  const newReport = createCheckReport(newJoin);
@@ -1 +1 @@
1
- {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/report/diff.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACvG,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,sDAAsD;AACtD,MAAM,UAAU,gBAAgB,CAC9B,MAAsC,EACtC,KAAqC;IAErC,iBAAiB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACjC,MAAM,OAAO,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAC3C,IAAI,oBAAoB,CAAC,OAAO,CAAC,IAAI,oBAAoB,CAAC,OAAO,CAAC,EAAE,CAAC;QACnE,MAAM,IAAI,yBAAyB,CACjC,6EAA6E,CAC9E,CAAC;IACJ,CAAC;IACD,MAAM,SAAS,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,CAAC,cAAc,EAAE,OAAO,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;IAC5F,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,CAAC,cAAc,EAAE,OAAO,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;IAC9F,MAAM,gBAAgB,GAAG,UAAU,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC1F,MAAM,cAAc,GAAG,UAAU,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACxF,OAAO;QACL,MAAM,EAAE,cAAuB;QAC/B,OAAO,EAAE,CAAU;QACnB,OAAO,EAAE;YACP,YAAY,EAAE,YAAY,CAAC,MAAM;YACjC,cAAc,EAAE,cAAc,CAAC,MAAM;YACrC,gBAAgB,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,MAAM;YACvF,kBAAkB,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM;YAC3F,cAAc,EAAE,cAAc,CAAC,MAAM;SACtC;QACD,YAAY;QACZ,cAAc;QACd,gBAAgB;QAChB,cAAc;QACd,WAAW,EAAE;YACX,MAAM,EAAE,SAAS,CAAC,WAAW;YAC7B,KAAK,EAAE,SAAS,CAAC,WAAW;YAC5B,KAAK,EAAE,UAAU,CAAC,SAAS,CAAC,WAAW,EAAE,SAAS,CAAC,WAAW,EAAE,aAAa,CAAC;YAC9E,OAAO,EAAE,UAAU,CAAC,SAAS,CAAC,WAAW,EAAE,SAAS,CAAC,WAAW,EAAE,aAAa,CAAC;SACjF;QACD,SAAS,EAAE,EAAE,MAAM,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,gBAAgB,CAAC,KAAK,CAAC,EAAE;KAChF,CAAC;AACJ,CAAC;AAED,qDAAqD;AACrD,SAAS,iBAAiB,CAAC,MAAsC,EAAE,KAAqC;IACtG,MAAM,GAAG,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC;IAClC,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC;QACnD,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,CAAC;YAC1E,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;QAC/C,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;QACtF,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC;YACrE,CAAC,GAAG,CAAC,MAAM,KAAK,IAAI,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,yBAAyB,CACjC,0EAA0E;cACxE,0EAA0E,CAC7E,CAAC;IACJ,CAAC;AACH,CAAC;AAED,8CAA8C;AAC9C,SAAS,iBAAiB,CAAC,IAAoC;IAC7D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;AAC/F,CAAC;AAED,yCAAyC;AACzC,SAAS,gBAAgB,CAAC,IAAoC;IAC5D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,GAAG,GAAG,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;SAC9F,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1E,CAAC;AAED,gDAAgD;AAChD,SAAS,UAAU,CAAC,IAA2F;IAC7G,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC;AAC1E,CAAC;AAED,SAAS,aAAa,CAAC,IAAyL;IAC9M,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,IAAI,IAAI,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC;AAC3H,CAAC;AAED,uCAAuC;AACvC,SAAS,UAAU,CAAI,IAAkB,EAAE,KAAmB,EAAE,GAAwB;IACtF,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACzC,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;SACnD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACpD,CAAC","sourcesContent":["import type { BridgeFactsDocument, BridgeTarget } from '../exchange/parse.ts';\nimport { compareStrings } from '../compare.ts';\nimport { BridgeJoinValidationError, isBridgeJoinDeferred, joinBridgeDocuments } from '../join/join.ts';\nimport { baselineEntryKey } from './baseline.ts';\nimport { createCheckReport } from './check-report.ts';\n\n/** 동일 프로젝트의 관찰 결과를 비교하며 삭제 안전성이나 rename을 추측하지 않는다. */\nexport function createBridgeDiff(\n before: readonly BridgeFactsDocument[],\n after: readonly BridgeFactsDocument[],\n) {\n validateSnapshots(before, after);\n const oldJoin = joinBridgeDocuments(before);\n const newJoin = joinBridgeDocuments(after);\n if (isBridgeJoinDeferred(oldJoin) || isBridgeJoinDeferred(newJoin)) {\n throw new BridgeJoinValidationError(\n 'Cannot compare deferred bridge joins; split mixed bridge targets and retry.',\n );\n }\n const oldReport = createCheckReport(oldJoin);\n const newReport = createCheckReport(newJoin);\n const addedMethods = difference(newJoin.matchedMethods, oldJoin.matchedMethods, logicalKey);\n const removedMethods = difference(oldJoin.matchedMethods, newJoin.matchedMethods, logicalKey);\n const introducedIssues = difference(newReport.issues, oldReport.issues, baselineEntryKey);\n const resolvedIssues = difference(oldReport.issues, newReport.issues, baselineEntryKey);\n return {\n format: 'isthmus-diff' as const,\n version: 1 as const,\n summary: {\n addedMethods: addedMethods.length,\n removedMethods: removedMethods.length,\n introducedErrors: introducedIssues.filter((issue) => issue.severity === 'error').length,\n introducedWarnings: introducedIssues.filter((issue) => issue.severity === 'warning').length,\n resolvedIssues: resolvedIssues.length,\n },\n addedMethods,\n removedMethods,\n introducedIssues,\n resolvedIssues,\n limitations: {\n before: oldReport.limitations,\n after: newReport.limitations,\n added: difference(newReport.limitations, oldReport.limitations, limitationKey),\n removed: difference(oldReport.limitations, newReport.limitations, limitationKey),\n },\n producers: { before: producerVersions(before), after: producerVersions(after) },\n };\n}\n\n/** 플랫폼 누락이나 다른 프로젝트를 코드 삭제로 오해하지 않도록 입력 구성을 고정한다. */\nfunction validateSnapshots(before: readonly BridgeFactsDocument[], after: readonly BridgeFactsDocument[]): void {\n const all = [...before, ...after];\n if (new Set(all.map((doc) => doc.project)).size !== 1 ||\n ![before, after].every((docs) => docs.some((doc) => doc.platform === 'dart') &&\n docs.some((doc) => doc.platform === 'swift')) ||\n JSON.stringify(producerInventory(before)) !== JSON.stringify(producerInventory(after)) ||\n all.some((doc) => (doc.platform !== 'dart' && doc.platform !== 'swift') ||\n (doc.target !== null && doc.target !== 'flutter'))) {\n throw new BridgeJoinValidationError(\n 'Diff requires the same project and matching Flutter dart/swift producer '\n + 'inventories in both snapshots; rebuild both snapshots from one checkout.',\n );\n }\n}\n\n/** 버전 변화는 출력하되 플랫폼·도구별 문서 개수 변화는 허용하지 않는다. */\nfunction producerInventory(docs: readonly BridgeFactsDocument[]): string[] {\n return docs.map((doc) => JSON.stringify([doc.platform, doc.tool.name])).sort(compareStrings);\n}\n\n/** 추출기 업그레이드가 관찰 차이의 원인인지 검토할 버전 근거다. */\nfunction producerVersions(docs: readonly BridgeFactsDocument[]) {\n return docs.map((doc) => ({ platform: doc.platform, ...doc.tool, generatedAt: doc.generatedAt }))\n .sort((a, b) => compareStrings(JSON.stringify(a), JSON.stringify(b)));\n}\n\n/** 충돌 없는 논리 키로 비교해 소스 줄 이동을 추가·삭제로 보고하지 않는다. */\nfunction logicalKey(item: { readonly target: BridgeTarget; readonly channel: string; readonly method?: string }): string {\n return JSON.stringify([item.target, item.channel, item.method ?? null]);\n}\n\nfunction limitationKey(item: { readonly platform: string; readonly target: string | null; readonly tool: string; readonly message: string; readonly channels?: readonly string[]; readonly origin?: 'consumer' }): string {\n return JSON.stringify([item.platform, item.target, item.tool, item.message, item.channels ?? null, item.origin ?? null]);\n}\n\n/** 키 집합 차이를 안정적으로 정렬하고 원래 증거를 보존한다. */\nfunction difference<T>(left: readonly T[], right: readonly T[], key: (item: T) => string): T[] {\n const existing = new Set(right.map(key));\n return left.filter((item) => !existing.has(key(item)))\n .sort((a, b) => compareStrings(key(a), key(b)));\n}\n"]}
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/report/diff.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BridgeFactsDocument, BridgeTarget } from '../exchange/parse.ts';\nimport { compareStrings } from '../compare.ts';\nimport type {\n BridgeJoinResult,\n JoinLimitation,\n MatchedMethod,\n} from '../join/join.ts';\nimport { BridgeJoinValidationError, isBridgeJoinDeferred, joinBridgeDocuments } from '../join/join.ts';\nimport type { CheckIssue } from './check-report.ts';\nimport { baselineEntryKey } from './baseline.ts';\nimport { createCheckReport } from './check-report.ts';\n\n/** 두 스냅샷의 생산 도구 버전 근거다. */\nexport interface DiffProducerVersion {\n readonly platform: string;\n readonly name: string;\n readonly version: string;\n readonly generatedAt: string;\n}\n\n/**\n * 동일 프로젝트의 전후 관찰을 비교한 isthmus-diff 문서다.\n *\n * added/removed는 키 집합 차이라 소스 줄 이동은 차이가 아니다. introduced와\n * resolved는 논리 이슈 키 기준이라 코드가 바뀌면(-unverified → 본 판정)\n * 해결·추가 한 쌍으로 보인다 — 심각도 상승은 새 관찰이기 때문이다.\n */\nexport interface BridgeDiffDocument {\n readonly format: 'isthmus-diff';\n readonly version: 1;\n readonly summary: {\n readonly addedMethods: number;\n readonly removedMethods: number;\n readonly introducedErrors: number;\n readonly introducedWarnings: number;\n readonly resolvedIssues: number;\n };\n readonly addedMethods: readonly MatchedMethod[];\n readonly removedMethods: readonly MatchedMethod[];\n readonly introducedIssues: readonly CheckIssue[];\n readonly resolvedIssues: readonly CheckIssue[];\n readonly limitations: {\n readonly before: readonly JoinLimitation[];\n readonly after: readonly JoinLimitation[];\n readonly added: readonly JoinLimitation[];\n readonly removed: readonly JoinLimitation[];\n };\n readonly producers: {\n readonly before: readonly DiffProducerVersion[];\n readonly after: readonly DiffProducerVersion[];\n };\n}\n\n/** 동일 프로젝트의 관찰 결과를 비교하며 삭제 안전성이나 rename을 추측하지 않는다. */\nexport function createBridgeDiff(\n before: readonly BridgeFactsDocument[],\n after: readonly BridgeFactsDocument[],\n): BridgeDiffDocument {\n validateSnapshots(before, after);\n const oldJoin = joinBridgeDocuments(before);\n const newJoin = joinBridgeDocuments(after);\n if (isBridgeJoinDeferred(oldJoin) || isBridgeJoinDeferred(newJoin)) {\n throw new BridgeJoinValidationError(\n 'Cannot compare deferred bridge joins; split mixed bridge targets and '\n + `retry. The before inputs observed ${oldJoin.observedFacts} facts across `\n + `${before.length} documents, and the after inputs observed `\n + `${newJoin.observedFacts} facts across ${after.length} documents.`,\n );\n }\n const oldReport = createCheckReport(oldJoin);\n const newReport = createCheckReport(newJoin);\n const addedMethods = difference(newJoin.matchedMethods, oldJoin.matchedMethods, logicalKey);\n const removedMethods = difference(oldJoin.matchedMethods, newJoin.matchedMethods, logicalKey);\n const introducedIssues = difference(newReport.issues, oldReport.issues, baselineEntryKey);\n const resolvedIssues = difference(oldReport.issues, newReport.issues, baselineEntryKey);\n return {\n format: 'isthmus-diff' as const,\n version: 1 as const,\n summary: {\n addedMethods: addedMethods.length,\n removedMethods: removedMethods.length,\n introducedErrors: introducedIssues.filter((issue) => issue.severity === 'error').length,\n introducedWarnings: introducedIssues.filter((issue) => issue.severity === 'warning').length,\n resolvedIssues: resolvedIssues.length,\n },\n addedMethods,\n removedMethods,\n introducedIssues,\n resolvedIssues,\n limitations: {\n before: oldReport.limitations,\n after: newReport.limitations,\n added: difference(newReport.limitations, oldReport.limitations, limitationKey),\n removed: difference(oldReport.limitations, newReport.limitations, limitationKey),\n },\n producers: { before: producerVersions(before), after: producerVersions(after) },\n };\n}\n\n/** 플랫폼 누락이나 다른 프로젝트를 코드 삭제로 오해하지 않도록 입력 구성을 고정한다. */\nfunction validateSnapshots(before: readonly BridgeFactsDocument[], after: readonly BridgeFactsDocument[]): void {\n const all = [...before, ...after];\n if (new Set(all.map((doc) => doc.project)).size !== 1 ||\n ![before, after].every((docs) => docs.some((doc) => doc.platform === 'dart') &&\n docs.some((doc) => doc.platform === 'swift')) ||\n JSON.stringify(producerInventory(before)) !== JSON.stringify(producerInventory(after)) ||\n all.some((doc) => (doc.platform !== 'dart' && doc.platform !== 'swift') ||\n (doc.target !== null && doc.target !== 'flutter'))) {\n throw new BridgeJoinValidationError(\n 'Diff requires the same project and matching Flutter dart/swift producer '\n + 'inventories in both snapshots; rebuild both snapshots from one checkout.',\n );\n }\n}\n\n/** 버전 변화는 출력하되 플랫폼·도구별 문서 개수 변화는 허용하지 않는다. */\nfunction producerInventory(docs: readonly BridgeFactsDocument[]): string[] {\n return docs.map((doc) => JSON.stringify([doc.platform, doc.tool.name])).sort(compareStrings);\n}\n\n/** 추출기 업그레이드가 관찰 차이의 원인인지 검토할 버전 근거다. */\nfunction producerVersions(docs: readonly BridgeFactsDocument[]) {\n return docs.map((doc) => ({ platform: doc.platform, ...doc.tool, generatedAt: doc.generatedAt }))\n .sort((a, b) => compareStrings(JSON.stringify(a), JSON.stringify(b)));\n}\n\n/** 충돌 없는 논리 키로 비교해 소스 줄 이동을 추가·삭제로 보고하지 않는다. */\nfunction logicalKey(item: { readonly target: BridgeTarget; readonly channel: string; readonly method?: string }): string {\n return JSON.stringify([item.target, item.channel, item.method ?? null]);\n}\n\nfunction limitationKey(item: { readonly platform: string; readonly target: string | null; readonly tool: string; readonly message: string; readonly channels?: readonly string[]; readonly origin?: 'consumer' }): string {\n return JSON.stringify([item.platform, item.target, item.tool, item.message, item.channels ?? null, item.origin ?? null]);\n}\n\n/** 키 집합 차이를 안정적으로 정렬하고 원래 증거를 보존한다. */\nfunction difference<T>(left: readonly T[], right: readonly T[], key: (item: T) => string): T[] {\n const existing = new Set(right.map(key));\n return left.filter((item) => !existing.has(key(item)))\n .sort((a, b) => compareStrings(key(a), key(b)));\n}\n"]}
@@ -94,8 +94,15 @@ function makeQueryResult(target, name, kind, usedBy, dependsOn) {
94
94
  dependsOn,
95
95
  };
96
96
  }
97
- /** qualifiedName 구분자와 이스케이프 문자를 가역적인 퍼센트 표기로 바꾼다. */
97
+ /**
98
+ * qualifiedName 구분자(:와 #)와 이스케이프 문자(%)를 가역적인 퍼센트 표기로
99
+ * 바꾼다. 세 문자를 모두 이스케이프하므로 첫 `:`와 `#` 기준으로 나눠 되돌릴 수
100
+ * 있다 — 채널·메서드 이름에 `:`가 들어도 소비자의 분해가 모호해지지 않는다.
101
+ */
98
102
  function encodeSubjectComponent(value) {
99
- return value.replaceAll('%', '%25').replaceAll('#', '%23');
103
+ return value
104
+ .replaceAll('%', '%25')
105
+ .replaceAll('#', '%23')
106
+ .replaceAll(':', '%3A');
100
107
  }
101
108
  //# sourceMappingURL=query.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/report/query.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BridgeTarget } from '../exchange/parse.ts';\nimport { compareStrings } from '../compare.ts';\nimport type {\n BridgeEndpoint,\n BridgeJoinResult,\n JoinLimitation,\n} from '../join/join.ts';\nimport { isBridgeJoinDeferred } from '../join/join.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\n\n/** query가 식별한 채널 또는 메서드 키다. */\nexport interface BridgeQuerySubject {\n readonly name: string;\n readonly qualifiedName: string;\n readonly kind: 'channel' | 'method';\n}\n\n/** 한 브리지 키에서 본 호출 측과 수신 측 증거다. */\nexport interface BridgeQueryResult {\n readonly subject: BridgeQuerySubject;\n readonly usedBy: readonly BridgeEndpoint[];\n readonly dependsOn: readonly BridgeEndpoint[];\n}\n\n/** cartograph query와 같은 상태 외피를 쓰는 브리지 질의 문서다. */\nexport interface BridgeQueryDocument {\n readonly status: 'found' | 'ambiguous' | 'notFound';\n readonly requested: string;\n readonly level: 'bridge';\n readonly limitations: readonly JoinLimitation[];\n readonly result?: BridgeQueryResult;\n readonly candidates?: ReadonlyArray<{\n readonly qualifiedName: string;\n }>;\n}\n\n/** query 문서를 결정적인 JSON 문자열로 인코딩한다. */\nexport function encodeBridgeQuery(document: BridgeQueryDocument): string {\n return encodeSortedJson(document);\n}\n\n/** 조인된 브리지에서 채널 또는 메서드 문자열을 질의한다. */\nexport function createBridgeQuery(\n joined: BridgeJoinResult,\n requested: string,\n): BridgeQueryDocument {\n if (isBridgeJoinDeferred(joined)) {\n throw new Error('Cannot query a deferred bridge join.');\n }\n const results = [...channelResults(joined), ...methodResults(joined)];\n const exact = results.filter(\n ({ subject }) => subject.qualifiedName === requested,\n );\n if (exact.length > 1) return ambiguousQuery(joined, requested, exact);\n if (exact[0] !== undefined) return foundQuery(joined, requested, exact[0]);\n\n const named = results.filter(({ subject }) => subject.name === requested);\n if (named.length > 1) return ambiguousQuery(joined, requested, named);\n if (named[0] !== undefined) return foundQuery(joined, requested, named[0]);\n return {\n status: 'notFound',\n requested,\n level: 'bridge',\n limitations: joined.limitations,\n };\n}\n\n/** 유일하게 식별된 논리 키와 증거를 found 문서로 감싼다. */\nfunction foundQuery(\n joined: BridgeJoinResult,\n requested: string,\n result: BridgeQueryResult,\n): BridgeQueryDocument {\n return {\n status: 'found',\n requested,\n level: 'bridge',\n limitations: joined.limitations,\n result,\n };\n}\n\n/** 여러 논리 키가 같은 요청과 맞을 때 선택 가능한 정규화 이름을 돌려준다. */\nfunction ambiguousQuery(\n joined: BridgeJoinResult,\n requested: string,\n results: readonly BridgeQueryResult[],\n): BridgeQueryDocument {\n return {\n status: 'ambiguous',\n requested,\n level: 'bridge',\n limitations: joined.limitations,\n candidates: results\n .map(({ subject }) => ({ qualifiedName: subject.qualifiedName }))\n .sort((left, right) => compareStrings(left.qualifiedName, right.qualifiedName)),\n };\n}\n\n/** 요청 문자열과 정확히 같은 논리 메서드 결과를 만든다. */\nfunction methodResults(\n joined: BridgeJoinResult,\n): BridgeQueryResult[] {\n const matched = joined.matchedMethods\n .map(({ target, channel, method, invocations, handlers }) =>\n makeMethodResult(target, channel, method, invocations, handlers),\n );\n const unhandled = joined.unhandledInvocations\n .map(({ target, channel, method, invocations }) =>\n makeMethodResult(target, channel, method, invocations, []),\n );\n const handlers = joined.handlersWithoutInvocations\n .map(({ target, channel, method, handlers }) =>\n makeMethodResult(target, channel, method, [], handlers),\n );\n return [...matched, ...unhandled, ...handlers];\n}\n\n/** 메서드 키와 양쪽 증거를 query result로 바꾼다. */\nfunction makeMethodResult(\n target: BridgeTarget,\n channel: string,\n method: string,\n usedBy: readonly BridgeEndpoint[],\n dependsOn: readonly BridgeEndpoint[],\n): BridgeQueryResult {\n return {\n subject: {\n name: method,\n qualifiedName:\n `${target}:${encodeSubjectComponent(channel)}#${encodeSubjectComponent(method)}`,\n kind: 'method',\n },\n usedBy,\n dependsOn,\n };\n}\n\n/** 요청 문자열과 정확히 같은 논리 채널 결과를 만든다. */\nfunction channelResults(\n joined: BridgeJoinResult,\n): BridgeQueryResult[] {\n const matched = joined.matchedChannels\n .map(({ target, channel, creations, registrations }) =>\n makeQueryResult(target, channel, 'channel', creations, registrations),\n );\n const unregistered = joined.unregisteredChannelCreations\n .map(({ target, channel, creations }) =>\n makeQueryResult(target, channel, 'channel', creations, []),\n );\n const registrations = joined.registrationsWithoutCreations\n .map(({ target, channel, registrations }) =>\n makeQueryResult(target, channel, 'channel', [], registrations),\n );\n return [...matched, ...unregistered, ...registrations];\n}\n\n/** 조인 키와 양쪽 증거를 query result 골격으로 바꾼다. */\nfunction makeQueryResult(\n target: BridgeTarget,\n name: string,\n kind: 'channel' | 'method',\n usedBy: readonly BridgeEndpoint[],\n dependsOn: readonly BridgeEndpoint[],\n): BridgeQueryResult {\n return {\n subject: {\n name,\n qualifiedName: `${target}:${encodeSubjectComponent(name)}`,\n kind,\n },\n usedBy,\n dependsOn,\n };\n}\n\n/** qualifiedName 구분자와 이스케이프 문자를 가역적인 퍼센트 표기로 바꾼다. */\nfunction encodeSubjectComponent(value: string): string {\n return value.replaceAll('%', '%25').replaceAll('#', '%23');\n}\n"]}
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/report/query.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BridgeTarget } from '../exchange/parse.ts';\nimport { compareStrings } from '../compare.ts';\nimport type {\n BridgeEndpoint,\n BridgeJoinResult,\n JoinLimitation,\n} from '../join/join.ts';\nimport { isBridgeJoinDeferred } from '../join/join.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\n\n/** query가 식별한 채널 또는 메서드 키다. */\nexport interface BridgeQuerySubject {\n readonly name: string;\n readonly qualifiedName: string;\n readonly kind: 'channel' | 'method';\n}\n\n/** 한 브리지 키에서 본 호출 측과 수신 측 증거다. */\nexport interface BridgeQueryResult {\n readonly subject: BridgeQuerySubject;\n readonly usedBy: readonly BridgeEndpoint[];\n readonly dependsOn: readonly BridgeEndpoint[];\n}\n\n/** cartograph query와 같은 상태 외피를 쓰는 브리지 질의 문서다. */\nexport interface BridgeQueryDocument {\n readonly status: 'found' | 'ambiguous' | 'notFound';\n readonly requested: string;\n readonly level: 'bridge';\n readonly limitations: readonly JoinLimitation[];\n readonly result?: BridgeQueryResult;\n readonly candidates?: ReadonlyArray<{\n readonly qualifiedName: string;\n }>;\n}\n\n/** query 문서를 결정적인 JSON 문자열로 인코딩한다. */\nexport function encodeBridgeQuery(document: BridgeQueryDocument): string {\n return encodeSortedJson(document);\n}\n\n/** 조인된 브리지에서 채널 또는 메서드 문자열을 질의한다. */\nexport function createBridgeQuery(\n joined: BridgeJoinResult,\n requested: string,\n): BridgeQueryDocument {\n if (isBridgeJoinDeferred(joined)) {\n throw new Error('Cannot query a deferred bridge join.');\n }\n const results = [...channelResults(joined), ...methodResults(joined)];\n const exact = results.filter(\n ({ subject }) => subject.qualifiedName === requested,\n );\n if (exact.length > 1) return ambiguousQuery(joined, requested, exact);\n if (exact[0] !== undefined) return foundQuery(joined, requested, exact[0]);\n\n const named = results.filter(({ subject }) => subject.name === requested);\n if (named.length > 1) return ambiguousQuery(joined, requested, named);\n if (named[0] !== undefined) return foundQuery(joined, requested, named[0]);\n return {\n status: 'notFound',\n requested,\n level: 'bridge',\n limitations: joined.limitations,\n };\n}\n\n/** 유일하게 식별된 논리 키와 증거를 found 문서로 감싼다. */\nfunction foundQuery(\n joined: BridgeJoinResult,\n requested: string,\n result: BridgeQueryResult,\n): BridgeQueryDocument {\n return {\n status: 'found',\n requested,\n level: 'bridge',\n limitations: joined.limitations,\n result,\n };\n}\n\n/** 여러 논리 키가 같은 요청과 맞을 때 선택 가능한 정규화 이름을 돌려준다. */\nfunction ambiguousQuery(\n joined: BridgeJoinResult,\n requested: string,\n results: readonly BridgeQueryResult[],\n): BridgeQueryDocument {\n return {\n status: 'ambiguous',\n requested,\n level: 'bridge',\n limitations: joined.limitations,\n candidates: results\n .map(({ subject }) => ({ qualifiedName: subject.qualifiedName }))\n .sort((left, right) => compareStrings(left.qualifiedName, right.qualifiedName)),\n };\n}\n\n/** 요청 문자열과 정확히 같은 논리 메서드 결과를 만든다. */\nfunction methodResults(\n joined: BridgeJoinResult,\n): BridgeQueryResult[] {\n const matched = joined.matchedMethods\n .map(({ target, channel, method, invocations, handlers }) =>\n makeMethodResult(target, channel, method, invocations, handlers),\n );\n const unhandled = joined.unhandledInvocations\n .map(({ target, channel, method, invocations }) =>\n makeMethodResult(target, channel, method, invocations, []),\n );\n const handlers = joined.handlersWithoutInvocations\n .map(({ target, channel, method, handlers }) =>\n makeMethodResult(target, channel, method, [], handlers),\n );\n return [...matched, ...unhandled, ...handlers];\n}\n\n/** 메서드 키와 양쪽 증거를 query result로 바꾼다. */\nfunction makeMethodResult(\n target: BridgeTarget,\n channel: string,\n method: string,\n usedBy: readonly BridgeEndpoint[],\n dependsOn: readonly BridgeEndpoint[],\n): BridgeQueryResult {\n return {\n subject: {\n name: method,\n qualifiedName:\n `${target}:${encodeSubjectComponent(channel)}#${encodeSubjectComponent(method)}`,\n kind: 'method',\n },\n usedBy,\n dependsOn,\n };\n}\n\n/** 요청 문자열과 정확히 같은 논리 채널 결과를 만든다. */\nfunction channelResults(\n joined: BridgeJoinResult,\n): BridgeQueryResult[] {\n const matched = joined.matchedChannels\n .map(({ target, channel, creations, registrations }) =>\n makeQueryResult(target, channel, 'channel', creations, registrations),\n );\n const unregistered = joined.unregisteredChannelCreations\n .map(({ target, channel, creations }) =>\n makeQueryResult(target, channel, 'channel', creations, []),\n );\n const registrations = joined.registrationsWithoutCreations\n .map(({ target, channel, registrations }) =>\n makeQueryResult(target, channel, 'channel', [], registrations),\n );\n return [...matched, ...unregistered, ...registrations];\n}\n\n/** 조인 키와 양쪽 증거를 query result 골격으로 바꾼다. */\nfunction makeQueryResult(\n target: BridgeTarget,\n name: string,\n kind: 'channel' | 'method',\n usedBy: readonly BridgeEndpoint[],\n dependsOn: readonly BridgeEndpoint[],\n): BridgeQueryResult {\n return {\n subject: {\n name,\n qualifiedName: `${target}:${encodeSubjectComponent(name)}`,\n kind,\n },\n usedBy,\n dependsOn,\n };\n}\n\n/**\n * qualifiedName 구분자(:와 #)와 이스케이프 문자(%)를 가역적인 퍼센트 표기로\n * 바꾼다. 세 문자를 모두 이스케이프하므로 첫 `:`와 `#` 기준으로 나눠 되돌릴 수\n * 있다 — 채널·메서드 이름에 `:`가 들어도 소비자의 분해가 모호해지지 않는다.\n */\nfunction encodeSubjectComponent(value: string): string {\n return value\n .replaceAll('%', '%25')\n .replaceAll('#', '%23')\n .replaceAll(':', '%3A');\n}\n"]}
@@ -1,4 +1,11 @@
1
1
  import type { CheckIssueCode, CheckReport } from './check-report.ts';
2
+ /** 논리 이슈 키를 안정 지문으로 바꾸는 주입 경계다. 해싱은 cli 계층이 담당한다. */
3
+ export type IssueFingerprint = (issue: {
4
+ readonly code: string;
5
+ readonly target: string;
6
+ readonly channel: string;
7
+ readonly method?: string;
8
+ }) => string;
2
9
  /** GitHub code scanning이 받아들이는 SARIF 2.1.0 스키마 식별자다. */
3
10
  export declare const sarifSchema = "https://json.schemastore.org/sarif-2.1.0.json";
4
11
  /** 도구 소개 문서의 안정적인 위치다. */
@@ -63,12 +70,12 @@ export interface SarifLog {
63
70
  }>;
64
71
  }
65
72
  /** check 보고서를 SARIF 2.1.0 로그로 바꾼다. */
66
- export declare function createSarifLog(report: CheckReport, toolVersion?: string): SarifLog;
73
+ export declare function createSarifLog(report: CheckReport, toolVersion: string | undefined, issueFingerprint: IssueFingerprint): SarifLog;
67
74
  /** SARIF 로그를 결정적인 JSON 문자열로 인코딩한다. */
68
75
  export declare function encodeSarifLog(log: SarifLog): string;
69
76
  /**
70
77
  * 경로를 URI 참조로 만든다. 구분자 `/`는 보존하고 각 세그먼트를 RFC 3986으로
71
- * 인코딩해 공백·`#`·비ASCII 파일명이 GitHub 업로드를 깨지 않게 한다.
78
+ * 인코딩해 공백·`#`·비ASCII 파일명이 GitHub 업로드를 깨지지 않게 한다.
72
79
  */
73
80
  export declare function sarifUri(path: string): string;
74
81
  export {};
@@ -1,5 +1,3 @@
1
- import { createHash } from 'node:crypto';
2
- import { baselineEntryKey } from "./baseline.js";
3
1
  import { checkIssueCodes } from "./check-report.js";
4
2
  import { encodeSortedJson } from "./sorted-json.js";
5
3
  /** GitHub code scanning이 받아들이는 SARIF 2.1.0 스키마 식별자다. */
@@ -16,7 +14,7 @@ const ruleDescriptions = {
16
14
  'handler-without-invocation': 'A receiver-side bridge method handler has no matching caller-side invocation.',
17
15
  };
18
16
  /** check 보고서를 SARIF 2.1.0 로그로 바꾼다. */
19
- export function createSarifLog(report, toolVersion) {
17
+ export function createSarifLog(report, toolVersion, issueFingerprint) {
20
18
  const rules = [...checkIssueCodes].sort().map((id) => ({
21
19
  id,
22
20
  shortDescription: { text: ruleDescriptions[id] },
@@ -34,7 +32,7 @@ export function createSarifLog(report, toolVersion) {
34
32
  rules,
35
33
  },
36
34
  },
37
- results: report.issues.map((issue) => sarifResult(issue, ruleIndex)),
35
+ results: report.issues.map((issue) => sarifResult(issue, ruleIndex, issueFingerprint)),
38
36
  }],
39
37
  };
40
38
  }
@@ -43,7 +41,7 @@ export function encodeSarifLog(log) {
43
41
  return encodeSortedJson(log);
44
42
  }
45
43
  /** 논리 이슈 하나를 규칙 참조와 물리 근거를 갖춘 결과로 바꾼다. */
46
- function sarifResult(issue, ruleIndex) {
44
+ function sarifResult(issue, ruleIndex, issueFingerprint) {
47
45
  const [primary, ...related] = issue.evidence;
48
46
  if (primary === undefined) {
49
47
  throw new Error('Cannot create a SARIF result without evidence.');
@@ -93,13 +91,9 @@ function physicalLocation(endpoint) {
93
91
  }
94
92
  /**
95
93
  * 경로를 URI 참조로 만든다. 구분자 `/`는 보존하고 각 세그먼트를 RFC 3986으로
96
- * 인코딩해 공백·`#`·비ASCII 파일명이 GitHub 업로드를 깨지 않게 한다.
94
+ * 인코딩해 공백·`#`·비ASCII 파일명이 GitHub 업로드를 깨지지 않게 한다.
97
95
  */
98
96
  export function sarifUri(path) {
99
97
  return path.split('/').map(encodeURIComponent).join('/');
100
98
  }
101
- /** 베이스라인과 같은 논리 키의 안정 지문으로 소스 줄 이동에도 중복을 막는다. */
102
- function issueFingerprint(issue) {
103
- return createHash('sha256').update(baselineEntryKey(issue)).digest('hex');
104
- }
105
99
  //# sourceMappingURL=sarif.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sarif.js","sourceRoot":"","sources":["../../src/report/sarif.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\n\nimport { baselineEntryKey } from './baseline.ts';\nimport type {\n CheckIssue,\n CheckIssueCode,\n CheckReport,\n} from './check-report.ts';\nimport { checkIssueCodes } from './check-report.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\n\n/** GitHub code scanning이 받아들이는 SARIF 2.1.0 스키마 식별자다. */\nexport const sarifSchema = 'https://json.schemastore.org/sarif-2.1.0.json';\n\n/** 도구 소개 문서의 안정적인 위치다. */\nexport const sarifInformationUri = 'https://github.com/ictechgy/isthmus';\n\n/** SARIF 물리 위치다. 경로는 프로젝트 루트 기준 상대 경로를 그대로 쓴다. */\ninterface SarifPhysicalLocation {\n readonly artifactLocation: { readonly uri: string };\n readonly region: { readonly startLine: number; readonly startColumn: number };\n}\n\n/** 결과 하나가 위치(규칙·수준·메시지·근거)를 다 담는 형태다. */\ninterface SarifResult {\n readonly ruleId: CheckIssueCode;\n readonly ruleIndex: number;\n readonly level: 'error' | 'warning';\n readonly message: { readonly text: string };\n readonly locations: ReadonlyArray<{\n readonly physicalLocation: SarifPhysicalLocation;\n }>;\n readonly relatedLocations?: ReadonlyArray<{\n readonly id: number;\n readonly physicalLocation: SarifPhysicalLocation;\n }>;\n readonly suppressions?: ReadonlyArray<{\n readonly kind: 'external';\n readonly status: 'accepted';\n }>;\n readonly properties: {\n readonly target: string;\n readonly channel: string;\n readonly method?: string;\n };\n readonly partialFingerprints: { readonly isthmusIssueV1: string };\n}\n\n/** check가 보고하는 진단 종류의 SARIF 규칙 문구다. */\nconst ruleDescriptions: Record<CheckIssueCode, string> = {\n 'unhandled-invocation':\n 'A caller-side bridge method invocation has no matching handler on any receiver-side document.',\n 'unhandled-invocation-unverified':\n 'A caller-side bridge method invocation has no matching handler, and a receiver-side analysis gap may be hiding it.',\n 'unregistered-channel-creation':\n 'A caller-side bridge channel creation has no matching registration on any receiver-side document.',\n 'unregistered-channel-creation-unverified':\n 'A caller-side bridge channel creation has no matching registration, and a receiver-side analysis gap may be hiding it.',\n 'registration-without-creation':\n 'A receiver-side channel registration has no matching caller-side channel creation.',\n 'handler-without-invocation':\n 'A receiver-side bridge method handler has no matching caller-side invocation.',\n};\n\n/** SARIF 2.1.0 로그 문서다. 결정적 정렬로 인코딩한다. */\nexport interface SarifLog {\n readonly $schema: string;\n readonly version: '2.1.0';\n readonly runs: ReadonlyArray<{\n readonly tool: {\n readonly driver: {\n readonly name: 'isthmus';\n readonly informationUri: string;\n readonly version?: string;\n readonly rules: ReadonlyArray<{\n readonly id: CheckIssueCode;\n readonly shortDescription: { readonly text: string };\n }>;\n };\n };\n readonly results: readonly SarifResult[];\n }>;\n}\n\n/** check 보고서를 SARIF 2.1.0 로그로 바꾼다. */\nexport function createSarifLog(\n report: CheckReport,\n toolVersion?: string,\n): SarifLog {\n const rules = [...checkIssueCodes].sort().map((id) => ({\n id,\n shortDescription: { text: ruleDescriptions[id] },\n }));\n const ruleIndex = new Map(rules.map(({ id }, index) => [id, index]));\n return {\n $schema: sarifSchema,\n version: '2.1.0',\n runs: [{\n tool: {\n driver: {\n name: 'isthmus',\n informationUri: sarifInformationUri,\n ...(toolVersion ? { version: toolVersion } : {}),\n rules,\n },\n },\n results: report.issues.map((issue) => sarifResult(issue, ruleIndex)),\n }],\n };\n}\n\n/** SARIF 로그를 결정적인 JSON 문자열로 인코딩한다. */\nexport function encodeSarifLog(log: SarifLog): string {\n return encodeSortedJson(log);\n}\n\n/** 논리 이슈 하나를 규칙 참조와 물리 근거를 갖춘 결과로 바꾼다. */\nfunction sarifResult(\n issue: CheckIssue,\n ruleIndex: Map<string, number>,\n): SarifResult {\n const [primary, ...related] = issue.evidence;\n if (primary === undefined) {\n throw new Error('Cannot create a SARIF result without evidence.');\n }\n const index = ruleIndex.get(issue.code);\n if (index === undefined) {\n throw new Error('Cannot create a SARIF result for an unknown rule.');\n }\n const subject = issue.method === undefined\n ? `${issue.code} on channel '${issue.channel}'`\n : `${issue.code} on channel '${issue.channel}' for method '${issue.method}'`;\n return {\n ruleId: issue.code,\n ruleIndex: index,\n level: issue.severity,\n message: { text: subject },\n locations: [{ physicalLocation: physicalLocation(primary) }],\n ...(related.length === 0 ? {} : {\n relatedLocations: related.map((endpoint, order) => ({\n id: order + 1,\n physicalLocation: physicalLocation(endpoint),\n })),\n }),\n ...(issue.suppressed === true\n ? { suppressions: [{ kind: 'external' as const, status: 'accepted' as const }] }\n : {}),\n properties: {\n target: issue.target,\n channel: issue.channel,\n ...(issue.method === undefined ? {} : { method: issue.method }),\n },\n partialFingerprints: {\n isthmusIssueV1: issueFingerprint(issue),\n },\n };\n}\n\n/** 증거 끝점을 SARIF 물리 위치로 바꾼다. 줄·열은 계약과 같은 1 기반이다. */\nfunction physicalLocation(endpoint: {\n readonly location: { readonly path: string; readonly line: number; readonly column: number };\n}): SarifPhysicalLocation {\n const { line, column } = endpoint.location;\n if (line < 1 || column < 1) {\n throw new Error('Cannot create a SARIF region before the first line or column.');\n }\n return {\n artifactLocation: { uri: sarifUri(endpoint.location.path) },\n region: { startLine: line, startColumn: column },\n };\n}\n\n/**\n * 경로를 URI 참조로 만든다. 구분자 `/`는 보존하고 각 세그먼트를 RFC 3986으로\n * 인코딩해 공백·`#`·비ASCII 파일명이 GitHub 업로드를 깨지 않게 한다.\n */\nexport function sarifUri(path: string): string {\n return path.split('/').map(encodeURIComponent).join('/');\n}\n\n/** 베이스라인과 같은 논리 키의 안정 지문으로 소스 줄 이동에도 중복을 막는다. */\nfunction issueFingerprint(issue: {\n readonly code: string;\n readonly target: string;\n readonly channel: string;\n readonly method?: string;\n}): string {\n return createHash('sha256').update(baselineEntryKey(issue)).digest('hex');\n}\n"]}
1
+ {"version":3,"file":"sarif.js","sourceRoot":"","sources":["../../src/report/sarif.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CheckIssue,\n CheckIssueCode,\n CheckReport,\n} from './check-report.ts';\nimport { checkIssueCodes } from './check-report.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\n\n/** 논리 이슈 키를 안정 지문으로 바꾸는 주입 경계다. 해싱은 cli 계층이 담당한다. */\nexport type IssueFingerprint = (issue: {\n readonly code: string;\n readonly target: string;\n readonly channel: string;\n readonly method?: string;\n}) => string;\n\n/** GitHub code scanning이 받아들이는 SARIF 2.1.0 스키마 식별자다. */\nexport const sarifSchema = 'https://json.schemastore.org/sarif-2.1.0.json';\n\n/** 도구 소개 문서의 안정적인 위치다. */\nexport const sarifInformationUri = 'https://github.com/ictechgy/isthmus';\n\n/** SARIF 물리 위치다. 경로는 프로젝트 루트 기준 상대 경로를 그대로 쓴다. */\ninterface SarifPhysicalLocation {\n readonly artifactLocation: { readonly uri: string };\n readonly region: { readonly startLine: number; readonly startColumn: number };\n}\n\n/** 결과 하나가 위치(규칙·수준·메시지·근거)를 다 담는 형태다. */\ninterface SarifResult {\n readonly ruleId: CheckIssueCode;\n readonly ruleIndex: number;\n readonly level: 'error' | 'warning';\n readonly message: { readonly text: string };\n readonly locations: ReadonlyArray<{\n readonly physicalLocation: SarifPhysicalLocation;\n }>;\n readonly relatedLocations?: ReadonlyArray<{\n readonly id: number;\n readonly physicalLocation: SarifPhysicalLocation;\n }>;\n readonly suppressions?: ReadonlyArray<{\n readonly kind: 'external';\n readonly status: 'accepted';\n }>;\n readonly properties: {\n readonly target: string;\n readonly channel: string;\n readonly method?: string;\n };\n readonly partialFingerprints: { readonly isthmusIssueV1: string };\n}\n\n/** check가 보고하는 진단 종류의 SARIF 규칙 문구다. */\nconst ruleDescriptions: Record<CheckIssueCode, string> = {\n 'unhandled-invocation':\n 'A caller-side bridge method invocation has no matching handler on any receiver-side document.',\n 'unhandled-invocation-unverified':\n 'A caller-side bridge method invocation has no matching handler, and a receiver-side analysis gap may be hiding it.',\n 'unregistered-channel-creation':\n 'A caller-side bridge channel creation has no matching registration on any receiver-side document.',\n 'unregistered-channel-creation-unverified':\n 'A caller-side bridge channel creation has no matching registration, and a receiver-side analysis gap may be hiding it.',\n 'registration-without-creation':\n 'A receiver-side channel registration has no matching caller-side channel creation.',\n 'handler-without-invocation':\n 'A receiver-side bridge method handler has no matching caller-side invocation.',\n};\n\n/** SARIF 2.1.0 로그 문서다. 결정적 정렬로 인코딩한다. */\nexport interface SarifLog {\n readonly $schema: string;\n readonly version: '2.1.0';\n readonly runs: ReadonlyArray<{\n readonly tool: {\n readonly driver: {\n readonly name: 'isthmus';\n readonly informationUri: string;\n readonly version?: string;\n readonly rules: ReadonlyArray<{\n readonly id: CheckIssueCode;\n readonly shortDescription: { readonly text: string };\n }>;\n };\n };\n readonly results: readonly SarifResult[];\n }>;\n}\n\n/** check 보고서를 SARIF 2.1.0 로그로 바꾼다. */\nexport function createSarifLog(\n report: CheckReport,\n toolVersion: string | undefined,\n issueFingerprint: IssueFingerprint,\n): SarifLog {\n const rules = [...checkIssueCodes].sort().map((id) => ({\n id,\n shortDescription: { text: ruleDescriptions[id] },\n }));\n const ruleIndex = new Map(rules.map(({ id }, index) => [id, index]));\n return {\n $schema: sarifSchema,\n version: '2.1.0',\n runs: [{\n tool: {\n driver: {\n name: 'isthmus',\n informationUri: sarifInformationUri,\n ...(toolVersion ? { version: toolVersion } : {}),\n rules,\n },\n },\n results: report.issues.map((issue) =>\n sarifResult(issue, ruleIndex, issueFingerprint)\n ),\n }],\n };\n}\n\n/** SARIF 로그를 결정적인 JSON 문자열로 인코딩한다. */\nexport function encodeSarifLog(log: SarifLog): string {\n return encodeSortedJson(log);\n}\n\n/** 논리 이슈 하나를 규칙 참조와 물리 근거를 갖춘 결과로 바꾼다. */\nfunction sarifResult(\n issue: CheckIssue,\n ruleIndex: Map<string, number>,\n issueFingerprint: IssueFingerprint,\n): SarifResult {\n const [primary, ...related] = issue.evidence;\n if (primary === undefined) {\n throw new Error('Cannot create a SARIF result without evidence.');\n }\n const index = ruleIndex.get(issue.code);\n if (index === undefined) {\n throw new Error('Cannot create a SARIF result for an unknown rule.');\n }\n const subject = issue.method === undefined\n ? `${issue.code} on channel '${issue.channel}'`\n : `${issue.code} on channel '${issue.channel}' for method '${issue.method}'`;\n return {\n ruleId: issue.code,\n ruleIndex: index,\n level: issue.severity,\n message: { text: subject },\n locations: [{ physicalLocation: physicalLocation(primary) }],\n ...(related.length === 0 ? {} : {\n relatedLocations: related.map((endpoint, order) => ({\n id: order + 1,\n physicalLocation: physicalLocation(endpoint),\n })),\n }),\n ...(issue.suppressed === true\n ? { suppressions: [{ kind: 'external' as const, status: 'accepted' as const }] }\n : {}),\n properties: {\n target: issue.target,\n channel: issue.channel,\n ...(issue.method === undefined ? {} : { method: issue.method }),\n },\n partialFingerprints: {\n isthmusIssueV1: issueFingerprint(issue),\n },\n };\n}\n\n/** 증거 끝점을 SARIF 물리 위치로 바꾼다. 줄·열은 계약과 같은 1 기반이다. */\nfunction physicalLocation(endpoint: {\n readonly location: { readonly path: string; readonly line: number; readonly column: number };\n}): SarifPhysicalLocation {\n const { line, column } = endpoint.location;\n if (line < 1 || column < 1) {\n throw new Error('Cannot create a SARIF region before the first line or column.');\n }\n return {\n artifactLocation: { uri: sarifUri(endpoint.location.path) },\n region: { startLine: line, startColumn: column },\n };\n}\n\n/**\n * 경로를 URI 참조로 만든다. 구분자 `/`는 보존하고 각 세그먼트를 RFC 3986으로\n * 인코딩해 공백·`#`·비ASCII 파일명이 GitHub 업로드를 깨지지 않게 한다.\n */\nexport function sarifUri(path: string): string {\n return path.split('/').map(encodeURIComponent).join('/');\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isthmus-cli",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Join bridge facts across cross-platform application boundaries.",
5
5
  "keywords": [
6
6
  "flutter",