isthmus-cli 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.ko.md +64 -22
  2. package/README.md +70 -26
  3. package/Skills/isthmus/SKILL.md +93 -4
  4. package/dist/cli/check-command.d.ts +2 -25
  5. package/dist/cli/check-command.js +27 -181
  6. package/dist/cli/check-command.js.map +1 -1
  7. package/dist/cli/command-support.d.ts +48 -0
  8. package/dist/cli/command-support.js +144 -0
  9. package/dist/cli/command-support.js.map +1 -0
  10. package/dist/cli/diff-command.d.ts +1 -1
  11. package/dist/cli/diff-command.js +1 -1
  12. package/dist/cli/diff-command.js.map +1 -1
  13. package/dist/cli/graph-command.d.ts +1 -1
  14. package/dist/cli/graph-command.js +15 -14
  15. package/dist/cli/graph-command.js.map +1 -1
  16. package/dist/cli/impact-command.d.ts +5 -0
  17. package/dist/cli/impact-command.js +111 -0
  18. package/dist/cli/impact-command.js.map +1 -0
  19. package/dist/cli/main.js +35 -13
  20. package/dist/cli/main.js.map +1 -1
  21. package/dist/cli/parse-arguments.d.ts +18 -0
  22. package/dist/cli/parse-arguments.js +48 -0
  23. package/dist/cli/parse-arguments.js.map +1 -0
  24. package/dist/cli/preflight-command.d.ts +5 -0
  25. package/dist/cli/preflight-command.js +83 -0
  26. package/dist/cli/preflight-command.js.map +1 -0
  27. package/dist/cli/query-command.d.ts +1 -1
  28. package/dist/cli/query-command.js +30 -10
  29. package/dist/cli/query-command.js.map +1 -1
  30. package/dist/cli/retentions-command.d.ts +1 -1
  31. package/dist/cli/retentions-command.js +11 -10
  32. package/dist/cli/retentions-command.js.map +1 -1
  33. package/dist/cli/runtime-command.d.ts +5 -0
  34. package/dist/cli/runtime-command.js +42 -0
  35. package/dist/cli/runtime-command.js.map +1 -0
  36. package/dist/cli/runtime-json-reader.d.ts +12 -0
  37. package/dist/cli/runtime-json-reader.js +37 -0
  38. package/dist/cli/runtime-json-reader.js.map +1 -0
  39. package/dist/exchange/impact-selection.d.ts +13 -0
  40. package/dist/exchange/impact-selection.js +35 -0
  41. package/dist/exchange/impact-selection.js.map +1 -0
  42. package/dist/exchange/kartograph-impact.d.ts +4 -0
  43. package/dist/exchange/kartograph-impact.js +169 -0
  44. package/dist/exchange/kartograph-impact.js.map +1 -0
  45. package/dist/exchange/messages.d.ts +51 -0
  46. package/dist/exchange/messages.js +126 -0
  47. package/dist/exchange/messages.js.map +1 -0
  48. package/dist/exchange/parse.d.ts +8 -0
  49. package/dist/exchange/parse.js +12 -8
  50. package/dist/exchange/parse.js.map +1 -1
  51. package/dist/exchange/preflight-context.d.ts +71 -0
  52. package/dist/exchange/preflight-context.js +317 -0
  53. package/dist/exchange/preflight-context.js.map +1 -0
  54. package/dist/exchange/producer-impact.d.ts +17 -0
  55. package/dist/exchange/producer-impact.js +226 -0
  56. package/dist/exchange/producer-impact.js.map +1 -0
  57. package/dist/exchange/runtime.d.ts +69 -0
  58. package/dist/exchange/runtime.js +160 -0
  59. package/dist/exchange/runtime.js.map +1 -0
  60. package/dist/join/message-address.d.ts +7 -0
  61. package/dist/join/message-address.js +37 -0
  62. package/dist/join/message-address.js.map +1 -0
  63. package/dist/join/messages.d.ts +23 -0
  64. package/dist/join/messages.js +80 -0
  65. package/dist/join/messages.js.map +1 -0
  66. package/dist/report/diff.d.ts +40 -33
  67. package/dist/report/diff.js +8 -4
  68. package/dist/report/diff.js.map +1 -1
  69. package/dist/report/impact.d.ts +62 -0
  70. package/dist/report/impact.js +168 -0
  71. package/dist/report/impact.js.map +1 -0
  72. package/dist/report/preflight-runtime.d.ts +42 -0
  73. package/dist/report/preflight-runtime.js +193 -0
  74. package/dist/report/preflight-runtime.js.map +1 -0
  75. package/dist/report/preflight-view.d.ts +146 -0
  76. package/dist/report/preflight-view.js +189 -0
  77. package/dist/report/preflight-view.js.map +1 -0
  78. package/dist/report/preflight.d.ts +104 -0
  79. package/dist/report/preflight.js +296 -0
  80. package/dist/report/preflight.js.map +1 -0
  81. package/dist/report/query.js +9 -2
  82. package/dist/report/query.js.map +1 -1
  83. package/dist/report/runtime-impact.d.ts +38 -0
  84. package/dist/report/runtime-impact.js +94 -0
  85. package/dist/report/runtime-impact.js.map +1 -0
  86. package/dist/report/runtime.d.ts +54 -0
  87. package/dist/report/runtime.js +137 -0
  88. package/dist/report/runtime.js.map +1 -0
  89. package/dist/report/sarif.d.ts +9 -2
  90. package/dist/report/sarif.js +4 -10
  91. package/dist/report/sarif.js.map +1 -1
  92. package/dist/report/sorted-json.d.ts +1 -1
  93. package/dist/report/sorted-json.js +2 -2
  94. package/dist/report/sorted-json.js.map +1 -1
  95. package/docs/BRIDGE-MESSAGES.md +102 -0
  96. package/docs/GRAPH-EXCHANGE.md +255 -0
  97. package/docs/IMPACT.md +96 -0
  98. package/docs/PREFLIGHT.md +295 -0
  99. package/docs/RUNTIME.md +172 -0
  100. package/docs/TOOLCHAIN.md +101 -0
  101. package/package.json +11 -2
  102. package/scripts/build-preflight-toolchain.mjs +186 -0
  103. package/scripts/capture-preflight.mjs +392 -0
  104. package/scripts/run-child.mjs +16 -0
@@ -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 new Set(all.filter((doc) => doc.platform === 'swift' || doc.platform === 'kotlin').map((doc) => doc.platform)).size !== 1 ||\n ![before, after].every((docs) => docs.some((doc) => doc.platform === 'dart') &&\n docs.some((doc) => doc.platform === 'swift' || doc.platform === 'kotlin')) ||\n JSON.stringify(producerInventory(before)) !== JSON.stringify(producerInventory(after)) ||\n all.some((doc) => (doc.platform !== 'dart' && doc.platform !== 'swift' && doc.platform !== 'kotlin') ||\n (doc.target !== null && doc.target !== 'flutter'))) {\n throw new BridgeJoinValidationError(\n 'Diff requires the same project and matching Flutter Dart/native 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"]}
@@ -0,0 +1,62 @@
1
+ import type { BridgeFact, BridgeFactsDocument, BridgeTarget } from '../exchange/parse.ts';
2
+ import type { ImpactSelection } from '../exchange/impact-selection.ts';
3
+ import type { BridgeEndpoint, JoinLimitation, MatchedChannel, MatchedMethod } from '../join/join.ts';
4
+ import type { CheckIssue } from './check-report.ts';
5
+ import type { ImpactRuntimeInput, RuntimeImpactEvidence } from './runtime-impact.ts';
6
+ /** 선택된 원본 사실. 조인 불가 사실도 위치·식별자와 함께 보존한다. */
7
+ export interface SelectedBridgeFact extends BridgeEndpoint {
8
+ readonly target: BridgeTarget | null;
9
+ readonly kind: BridgeFact['kind'];
10
+ readonly channel: string | null;
11
+ readonly method?: string;
12
+ readonly dynamic: boolean;
13
+ }
14
+ /** 채널 배선 변경은 같은 채널의 모든 메서드에 영향을 준다. */
15
+ export type ImpactReason = 'channel-wiring' | 'method' | 'runtime-observation';
16
+ /** 변경과 관련된 채널 생성·등록 위치다. */
17
+ export interface ImpactChannel extends MatchedChannel {
18
+ readonly reason: ImpactReason;
19
+ }
20
+ /** 변경과 관련된 메서드의 호출자·핸들러다. 실행 가능성을 판정하지 않는다. */
21
+ export interface ImpactMethod extends MatchedMethod {
22
+ readonly reason: ImpactReason;
23
+ }
24
+ /** 변경 전 확인할 브리지 관계와 공백을 기계적으로 소비하는 문서다. */
25
+ export interface BridgeImpactReport {
26
+ readonly format: 'isthmus-impact';
27
+ readonly version: 1;
28
+ /** 증거 상대 경로의 기준이다. CLI를 실행한 디렉터리와 같다고 추측하지 않는다. */
29
+ readonly project: string;
30
+ readonly status: 'observed' | 'unobserved';
31
+ readonly scope: 'bridge';
32
+ /** 전체 프로그램·런타임 도달성의 완전성을 주장하지 않는다. */
33
+ readonly complete: false;
34
+ readonly selection: ImpactSelection;
35
+ readonly unmatchedSelectors: ImpactSelection;
36
+ readonly summary: {
37
+ readonly observedFacts: number;
38
+ readonly selectedFacts: number;
39
+ readonly unresolvedSelectedFacts: number;
40
+ readonly unmatchedSelectors: number;
41
+ readonly affectedChannels: number;
42
+ readonly affectedMethods: number;
43
+ readonly reviewFiles: number;
44
+ readonly errors: number;
45
+ readonly warnings: number;
46
+ };
47
+ readonly selectedFacts: readonly SelectedBridgeFact[];
48
+ readonly channels: readonly ImpactChannel[];
49
+ readonly methods: readonly ImpactMethod[];
50
+ readonly reviewFiles: readonly string[];
51
+ readonly issues: readonly CheckIssue[];
52
+ readonly limitations: readonly JoinLimitation[];
53
+ readonly relevantLimitations: readonly JoinLimitation[];
54
+ readonly inputs: ReadonlyArray<Pick<BridgeFactsDocument, 'tool' | 'platform' | 'target' | 'generatedAt'>>;
55
+ readonly runtime?: RuntimeImpactEvidence;
56
+ }
57
+ /** 파일·심볼에서 관련 브리지 키를 역탐색하고 채널 배선 변경의 범위를 확장한다. */
58
+ export declare function createBridgeImpact(documents: readonly BridgeFactsDocument[], requested: ImpactSelection, runtimeInput?: ImpactRuntimeInput): BridgeImpactReport;
59
+ /** 0을 전체 안전 판정으로 해석하지 않고, 관련 오류·공백을 CI 실패 조건으로 계산한다. */
60
+ export declare function hasImpactBlockers(report: BridgeImpactReport): boolean;
61
+ /** 같은 문서를 사람이 읽는 JSON 또는 정보 손실 없는 컴팩트 JSON으로 인코딩한다. */
62
+ export declare function encodeBridgeImpact(report: BridgeImpactReport, compact?: boolean): string;
@@ -0,0 +1,168 @@
1
+ import { compareStrings } from "../compare.js";
2
+ import { parseImpactSelection } from "../exchange/impact-selection.js";
3
+ import { BridgeJoinValidationError, joinBridgeDocuments } from "../join/join.js";
4
+ import { createCheckReport } from "./check-report.js";
5
+ import { encodeSortedJson } from "./sorted-json.js";
6
+ import { collectRuntimeImpact, hasRuntimeImpactGaps } from "./runtime-impact.js";
7
+ /** 파일·심볼에서 관련 브리지 키를 역탐색하고 채널 배선 변경의 범위를 확장한다. */
8
+ export function createBridgeImpact(documents, requested, runtimeInput) {
9
+ const selection = parseImpactSelection({ format: 'isthmus-changes', version: 1, ...requested });
10
+ const joined = joinBridgeDocuments(documents);
11
+ if (joined.deferred) {
12
+ throw new BridgeJoinValidationError('Bridge impact could not be computed; split mixed bridge targets and retry. '
13
+ + `The inputs observed ${joined.observedFacts} facts across ${documents.length} documents.`);
14
+ }
15
+ const { selectedFacts, unmatchedSelectors } = selectFacts(documents, selection);
16
+ const channelKeys = new Set();
17
+ const wiringKeys = new Set();
18
+ const methodKeys = new Set();
19
+ let unresolvedSelectedFacts = 0;
20
+ for (const fact of selectedFacts) {
21
+ if (fact.dynamic || fact.channel === null || fact.target === null) {
22
+ unresolvedSelectedFacts++;
23
+ continue;
24
+ }
25
+ const key = channelKey(fact.target, fact.channel);
26
+ channelKeys.add(key);
27
+ if (fact.kind === 'channel-create' || fact.kind === 'channel-register') {
28
+ wiringKeys.add(key);
29
+ }
30
+ else if (fact.method !== undefined) {
31
+ methodKeys.add(methodKey(fact.target, fact.channel, fact.method));
32
+ }
33
+ }
34
+ const staticChannelKeys = new Set(channelKeys);
35
+ const runtime = runtimeInput === undefined ? undefined
36
+ : collectRuntimeImpact(joined, documents[0]?.project, selection, selectedFacts, runtimeInput, new Set(documents.map(({ platform }) => platform)));
37
+ const runtimeNative = runtimeInput?.document.run.platform === 'android' ? 'kotlin' : 'swift';
38
+ const runtimeMethodKeys = new Set();
39
+ for (const route of runtime?.routes ?? []) {
40
+ if (route.staticStatus !== 'candidates' || route.method === undefined)
41
+ continue;
42
+ channelKeys.add(channelKey('flutter', route.channel));
43
+ runtimeMethodKeys.add(methodKey('flutter', route.channel, route.method));
44
+ }
45
+ const methods = [
46
+ ...joined.matchedMethods,
47
+ ...joined.unhandledInvocations.map((item) => ({ ...item, handlers: [] })),
48
+ ...joined.handlersWithoutInvocations.map((item) => ({ ...item, invocations: [] })),
49
+ ].filter(({ target, channel, method }) => wiringKeys.has(channelKey(target, channel)) ||
50
+ methodKeys.has(methodKey(target, channel, method)) || runtimeMethodKeys.has(methodKey(target, channel, method)))
51
+ .map((item) => {
52
+ const reason = impactReason(wiringKeys, item, !methodKeys.has(methodKey(item.target, item.channel, item.method)));
53
+ return { ...item, reason, handlers: reason === 'runtime-observation'
54
+ ? item.handlers.filter(({ platform }) => platform === runtimeNative) : item.handlers };
55
+ })
56
+ .sort(compareLogicalKeys);
57
+ const channelMap = new Map([
58
+ ...joined.matchedChannels,
59
+ ...joined.unregisteredChannelCreations.map((item) => ({ ...item, registrations: [] })),
60
+ ...joined.registrationsWithoutCreations.map((item) => ({ ...item, creations: [] })),
61
+ ].map((item) => [channelKey(item.target, item.channel), item]));
62
+ // 생성·등록을 놓친 채널에서도 메서드의 존재 근거가 사라지지 않아야 한다.
63
+ for (const { target, channel } of methods) {
64
+ const key = channelKey(target, channel);
65
+ if (!channelMap.has(key))
66
+ channelMap.set(key, { target, channel, creations: [], registrations: [] });
67
+ }
68
+ const channels = [...channelMap.values()]
69
+ .filter(({ target, channel }) => channelKeys.has(channelKey(target, channel)))
70
+ .map((item) => {
71
+ const reason = impactReason(wiringKeys, item, !staticChannelKeys.has(channelKey(item.target, item.channel)));
72
+ return { ...item, reason, registrations: reason === 'runtime-observation'
73
+ ? item.registrations.filter(({ platform }) => platform === runtimeNative) : item.registrations };
74
+ })
75
+ .sort(compareLogicalKeys);
76
+ const issues = createCheckReport(joined).issues.filter(({ target, channel, method }) => channelKeys.has(channelKey(target, channel)) &&
77
+ (method === undefined || wiringKeys.has(channelKey(target, channel)) ||
78
+ methodKeys.has(methodKey(target, channel, method)) || runtimeMethodKeys.has(methodKey(target, channel, method))));
79
+ const reviewFiles = [...new Set([
80
+ ...selectedFacts,
81
+ ...channels.flatMap(({ creations, registrations }) => [...creations, ...registrations]),
82
+ ...methods.flatMap(({ invocations, handlers }) => [...invocations, ...handlers]),
83
+ ...(runtime?.reviewFiles ?? []).map((path) => ({ location: { path } })),
84
+ ].map(({ location }) => location.path))].sort(compareStrings);
85
+ const unmatchedCount = unmatchedSelectors.files.length + unmatchedSelectors.symbols.length;
86
+ const relevantLimitations = joined.limitations.filter((limitation) => unresolvedSelectedFacts > 0 || unmatchedCount > 0 || channels.some(({ target, channel }) => (limitation.target === null || limitation.target === target) &&
87
+ (limitation.channels === undefined || limitation.channels.includes(channel))));
88
+ return {
89
+ // 조인이 호출·수신 문서의 존재와 동일 project를 이미 검증했다.
90
+ format: 'isthmus-impact', version: 1, project: documents[0].project,
91
+ status: selectedFacts.length > 0 ? 'observed' : 'unobserved', scope: 'bridge', complete: false,
92
+ selection, unmatchedSelectors,
93
+ summary: {
94
+ observedFacts: joined.observedFacts, selectedFacts: selectedFacts.length,
95
+ unresolvedSelectedFacts, unmatchedSelectors: unmatchedCount,
96
+ affectedChannels: channels.length, affectedMethods: methods.length,
97
+ reviewFiles: reviewFiles.length,
98
+ errors: issues.filter(({ severity }) => severity === 'error').length,
99
+ warnings: issues.filter(({ severity }) => severity === 'warning').length,
100
+ },
101
+ selectedFacts, channels, methods, reviewFiles, issues,
102
+ ...(runtime === undefined ? {} : { runtime }),
103
+ limitations: joined.limitations, relevantLimitations,
104
+ inputs: documents.map(({ tool, platform, target, generatedAt }) => ({
105
+ tool, platform, target, generatedAt,
106
+ })).sort((a, b) => compareStrings(encodeSortedJson(a), encodeSortedJson(b))),
107
+ };
108
+ }
109
+ /** 0을 전체 안전 판정으로 해석하지 않고, 관련 오류·공백을 CI 실패 조건으로 계산한다. */
110
+ export function hasImpactBlockers(report) {
111
+ return report.summary.errors > 0 || report.summary.unresolvedSelectedFacts > 0 ||
112
+ report.summary.unmatchedSelectors > 0 || report.relevantLimitations.length > 0 ||
113
+ report.issues.some(({ code }) => code.endsWith('-unverified')) ||
114
+ (report.runtime !== undefined && hasRuntimeImpactGaps(report.runtime));
115
+ }
116
+ /** 같은 문서를 사람이 읽는 JSON 또는 정보 손실 없는 컴팩트 JSON으로 인코딩한다. */
117
+ export function encodeBridgeImpact(report, compact = false) {
118
+ return encodeSortedJson(report, compact);
119
+ }
120
+ /** 선택 집합과 사실을 한 번씩 읽어 선택 수×사실 수의 곱을 피한다. */
121
+ function selectFacts(documents, selection) {
122
+ const files = new Set(selection.files);
123
+ const symbols = new Set(selection.symbols);
124
+ const foundFiles = new Set();
125
+ const foundSymbols = new Set();
126
+ const selected = new Map();
127
+ for (const { platform, target, facts } of documents) {
128
+ for (const fact of facts) {
129
+ const fileMatches = files.has(fact.location.path);
130
+ const names = fact.symbol === undefined ? [] : [fact.symbol.qualifiedName, fact.symbol.usr];
131
+ const matchedNames = names.filter((name) => name !== undefined && symbols.has(name));
132
+ if (!fileMatches && matchedNames.length === 0)
133
+ continue;
134
+ if (fileMatches)
135
+ foundFiles.add(fact.location.path);
136
+ for (const name of matchedNames)
137
+ foundSymbols.add(name);
138
+ const item = { ...fact, platform, target };
139
+ selected.set(encodeSortedJson(item), item);
140
+ }
141
+ }
142
+ return {
143
+ selectedFacts: [...selected.entries()].sort(([a], [b]) => compareStrings(a, b)).map(([, item]) => item),
144
+ unmatchedSelectors: {
145
+ files: selection.files.filter((path) => !foundFiles.has(path)),
146
+ symbols: selection.symbols.filter((name) => !foundSymbols.has(name)),
147
+ },
148
+ };
149
+ }
150
+ /** 구분 문자 포함 이름도 서로 충돌하지 않는 채널 키다. */
151
+ function channelKey(target, channel) {
152
+ return JSON.stringify([target, channel]);
153
+ }
154
+ /** 채널 내부에서만 유일한 메서드 키다. */
155
+ function methodKey(target, channel, method) {
156
+ return JSON.stringify([target, channel, method]);
157
+ }
158
+ /** 생성·등록이 선택됐는지에 따라 검토 범위가 넓어진 이유를 설명한다. */
159
+ function impactReason(wiringKeys, item, runtimeOnly = false) {
160
+ return wiringKeys.has(channelKey(item.target, item.channel)) ? 'channel-wiring'
161
+ : runtimeOnly ? 'runtime-observation' : 'method';
162
+ }
163
+ /** 입력 파일 순서와 locale에 의존하지 않는 논리 키 정렬이다. */
164
+ function compareLogicalKeys(a, b) {
165
+ return compareStrings(a.target, b.target) || compareStrings(a.channel, b.channel) ||
166
+ compareStrings(a.method ?? '', b.method ?? '');
167
+ }
168
+ //# sourceMappingURL=impact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"impact.js","sourceRoot":"","sources":["../../src/report/impact.ts"],"names":[],"mappings":"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","sourcesContent":["import { compareStrings } from '../compare.ts';\nimport type { BridgeFact, BridgeFactsDocument, BridgeTarget } from '../exchange/parse.ts';\nimport type { ImpactSelection } from '../exchange/impact-selection.ts';\nimport { parseImpactSelection } from '../exchange/impact-selection.ts';\nimport { BridgeJoinValidationError, joinBridgeDocuments } from '../join/join.ts';\nimport type { BridgeEndpoint, JoinLimitation, MatchedChannel, MatchedMethod } from '../join/join.ts';\nimport { createCheckReport } from './check-report.ts';\nimport type { CheckIssue } from './check-report.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\nimport { collectRuntimeImpact, hasRuntimeImpactGaps } from './runtime-impact.ts';\nimport type { ImpactRuntimeInput, RuntimeImpactEvidence } from './runtime-impact.ts';\n\n/** 선택된 원본 사실. 조인 불가 사실도 위치·식별자와 함께 보존한다. */\nexport interface SelectedBridgeFact extends BridgeEndpoint {\n readonly target: BridgeTarget | null;\n readonly kind: BridgeFact['kind'];\n readonly channel: string | null;\n readonly method?: string;\n readonly dynamic: boolean;\n}\n\n/** 채널 배선 변경은 같은 채널의 모든 메서드에 영향을 준다. */\nexport type ImpactReason = 'channel-wiring' | 'method' | 'runtime-observation';\n\n/** 변경과 관련된 채널 생성·등록 위치다. */\nexport interface ImpactChannel extends MatchedChannel {\n readonly reason: ImpactReason;\n}\n\n/** 변경과 관련된 메서드의 호출자·핸들러다. 실행 가능성을 판정하지 않는다. */\nexport interface ImpactMethod extends MatchedMethod {\n readonly reason: ImpactReason;\n}\n\n/** 변경 전 확인할 브리지 관계와 공백을 기계적으로 소비하는 문서다. */\nexport interface BridgeImpactReport {\n readonly format: 'isthmus-impact';\n readonly version: 1;\n /** 증거 상대 경로의 기준이다. CLI를 실행한 디렉터리와 같다고 추측하지 않는다. */\n readonly project: string;\n readonly status: 'observed' | 'unobserved';\n readonly scope: 'bridge';\n /** 전체 프로그램·런타임 도달성의 완전성을 주장하지 않는다. */\n readonly complete: false;\n readonly selection: ImpactSelection;\n readonly unmatchedSelectors: ImpactSelection;\n readonly summary: {\n readonly observedFacts: number;\n readonly selectedFacts: number;\n readonly unresolvedSelectedFacts: number;\n readonly unmatchedSelectors: number;\n readonly affectedChannels: number;\n readonly affectedMethods: number;\n readonly reviewFiles: number;\n readonly errors: number;\n readonly warnings: number;\n };\n readonly selectedFacts: readonly SelectedBridgeFact[];\n readonly channels: readonly ImpactChannel[];\n readonly methods: readonly ImpactMethod[];\n readonly reviewFiles: readonly string[];\n readonly issues: readonly CheckIssue[];\n readonly limitations: readonly JoinLimitation[];\n readonly relevantLimitations: readonly JoinLimitation[];\n readonly inputs: ReadonlyArray<Pick<BridgeFactsDocument, 'tool' | 'platform' | 'target' | 'generatedAt'>>;\n readonly runtime?: RuntimeImpactEvidence;\n}\n\n/** 파일·심볼에서 관련 브리지 키를 역탐색하고 채널 배선 변경의 범위를 확장한다. */\nexport function createBridgeImpact(\n documents: readonly BridgeFactsDocument[],\n requested: ImpactSelection,\n runtimeInput?: ImpactRuntimeInput,\n): BridgeImpactReport {\n const selection = parseImpactSelection({ format: 'isthmus-changes', version: 1, ...requested });\n const joined = joinBridgeDocuments(documents);\n if (joined.deferred) {\n throw new BridgeJoinValidationError(\n 'Bridge impact could not be computed; split mixed bridge targets and retry. '\n + `The inputs observed ${joined.observedFacts} facts across ${documents.length} documents.`,\n );\n }\n const { selectedFacts, unmatchedSelectors } = selectFacts(documents, selection);\n const channelKeys = new Set<string>();\n const wiringKeys = new Set<string>();\n const methodKeys = new Set<string>();\n let unresolvedSelectedFacts = 0;\n for (const fact of selectedFacts) {\n if (fact.dynamic || fact.channel === null || fact.target === null) {\n unresolvedSelectedFacts++;\n continue;\n }\n const key = channelKey(fact.target, fact.channel);\n channelKeys.add(key);\n if (fact.kind === 'channel-create' || fact.kind === 'channel-register') {\n wiringKeys.add(key);\n } else if (fact.method !== undefined) {\n methodKeys.add(methodKey(fact.target, fact.channel, fact.method));\n }\n }\n const staticChannelKeys = new Set(channelKeys);\n const runtime = runtimeInput === undefined ? undefined\n : collectRuntimeImpact(joined, documents[0]?.project, selection, selectedFacts, runtimeInput,\n new Set(documents.map(({ platform }) => platform)));\n const runtimeNative = runtimeInput?.document.run.platform === 'android' ? 'kotlin' : 'swift';\n const runtimeMethodKeys = new Set<string>();\n for (const route of runtime?.routes ?? []) {\n if (route.staticStatus !== 'candidates' || route.method === undefined) continue;\n channelKeys.add(channelKey('flutter', route.channel));\n runtimeMethodKeys.add(methodKey('flutter', route.channel, route.method));\n }\n const methods: ImpactMethod[] = [\n ...joined.matchedMethods,\n ...joined.unhandledInvocations.map((item) => ({ ...item, handlers: [] })),\n ...joined.handlersWithoutInvocations.map((item) => ({ ...item, invocations: [] })),\n ].filter(({ target, channel, method }) => wiringKeys.has(channelKey(target, channel)) ||\n methodKeys.has(methodKey(target, channel, method)) || runtimeMethodKeys.has(methodKey(target, channel, method)))\n .map((item) => {\n const reason = impactReason(wiringKeys, item, !methodKeys.has(methodKey(item.target, item.channel, item.method)));\n return { ...item, reason, handlers: reason === 'runtime-observation'\n ? item.handlers.filter(({ platform }) => platform === runtimeNative) : item.handlers };\n })\n .sort(compareLogicalKeys);\n const channelMap = new Map<string, MatchedChannel>([\n ...joined.matchedChannels,\n ...joined.unregisteredChannelCreations.map((item) => ({ ...item, registrations: [] })),\n ...joined.registrationsWithoutCreations.map((item) => ({ ...item, creations: [] })),\n ].map((item) => [channelKey(item.target, item.channel), item]));\n // 생성·등록을 놓친 채널에서도 메서드의 존재 근거가 사라지지 않아야 한다.\n for (const { target, channel } of methods) {\n const key = channelKey(target, channel);\n if (!channelMap.has(key)) channelMap.set(key, { target, channel, creations: [], registrations: [] });\n }\n const channels = [...channelMap.values()]\n .filter(({ target, channel }) => channelKeys.has(channelKey(target, channel)))\n .map((item) => {\n const reason = impactReason(wiringKeys, item, !staticChannelKeys.has(channelKey(item.target, item.channel)));\n return { ...item, reason, registrations: reason === 'runtime-observation'\n ? item.registrations.filter(({ platform }) => platform === runtimeNative) : item.registrations };\n })\n .sort(compareLogicalKeys);\n const issues = createCheckReport(joined).issues.filter(({ target, channel, method }) =>\n channelKeys.has(channelKey(target, channel)) &&\n (method === undefined || wiringKeys.has(channelKey(target, channel)) ||\n methodKeys.has(methodKey(target, channel, method)) || runtimeMethodKeys.has(methodKey(target, channel, method))));\n const reviewFiles = [...new Set([\n ...selectedFacts,\n ...channels.flatMap(({ creations, registrations }) => [...creations, ...registrations]),\n ...methods.flatMap(({ invocations, handlers }) => [...invocations, ...handlers]),\n ...(runtime?.reviewFiles ?? []).map((path) => ({ location: { path } })),\n ].map(({ location }) => location.path))].sort(compareStrings);\n const unmatchedCount = unmatchedSelectors.files.length + unmatchedSelectors.symbols.length;\n const relevantLimitations = joined.limitations.filter((limitation) =>\n unresolvedSelectedFacts > 0 || unmatchedCount > 0 || channels.some(({ target, channel }) =>\n (limitation.target === null || limitation.target === target) &&\n (limitation.channels === undefined || limitation.channels.includes(channel))));\n return {\n // 조인이 호출·수신 문서의 존재와 동일 project를 이미 검증했다.\n format: 'isthmus-impact', version: 1, project: documents[0]!.project,\n status: selectedFacts.length > 0 ? 'observed' : 'unobserved', scope: 'bridge', complete: false,\n selection, unmatchedSelectors,\n summary: {\n observedFacts: joined.observedFacts, selectedFacts: selectedFacts.length,\n unresolvedSelectedFacts, unmatchedSelectors: unmatchedCount,\n affectedChannels: channels.length, affectedMethods: methods.length,\n reviewFiles: reviewFiles.length,\n errors: issues.filter(({ severity }) => severity === 'error').length,\n warnings: issues.filter(({ severity }) => severity === 'warning').length,\n },\n selectedFacts, channels, methods, reviewFiles, issues,\n ...(runtime === undefined ? {} : { runtime }),\n limitations: joined.limitations, relevantLimitations,\n inputs: documents.map(({ tool, platform, target, generatedAt }) => ({\n tool, platform, target, generatedAt,\n })).sort((a, b) => compareStrings(encodeSortedJson(a), encodeSortedJson(b))),\n };\n}\n\n/** 0을 전체 안전 판정으로 해석하지 않고, 관련 오류·공백을 CI 실패 조건으로 계산한다. */\nexport function hasImpactBlockers(report: BridgeImpactReport): boolean {\n return report.summary.errors > 0 || report.summary.unresolvedSelectedFacts > 0 ||\n report.summary.unmatchedSelectors > 0 || report.relevantLimitations.length > 0 ||\n report.issues.some(({ code }) => code.endsWith('-unverified')) ||\n (report.runtime !== undefined && hasRuntimeImpactGaps(report.runtime));\n}\n\n/** 같은 문서를 사람이 읽는 JSON 또는 정보 손실 없는 컴팩트 JSON으로 인코딩한다. */\nexport function encodeBridgeImpact(report: BridgeImpactReport, compact = false): string {\n return encodeSortedJson(report, compact);\n}\n\n/** 선택 집합과 사실을 한 번씩 읽어 선택 수×사실 수의 곱을 피한다. */\nfunction selectFacts(documents: readonly BridgeFactsDocument[], selection: ImpactSelection): {\n selectedFacts: SelectedBridgeFact[]; unmatchedSelectors: ImpactSelection;\n} {\n const files = new Set(selection.files);\n const symbols = new Set(selection.symbols);\n const foundFiles = new Set<string>();\n const foundSymbols = new Set<string>();\n const selected = new Map<string, SelectedBridgeFact>();\n for (const { platform, target, facts } of documents) {\n for (const fact of facts) {\n const fileMatches = files.has(fact.location.path);\n const names = fact.symbol === undefined ? [] : [fact.symbol.qualifiedName, fact.symbol.usr];\n const matchedNames = names.filter((name): name is string => name !== undefined && symbols.has(name));\n if (!fileMatches && matchedNames.length === 0) continue;\n if (fileMatches) foundFiles.add(fact.location.path);\n for (const name of matchedNames) foundSymbols.add(name);\n const item = { ...fact, platform, target };\n selected.set(encodeSortedJson(item), item);\n }\n }\n return {\n selectedFacts: [...selected.entries()].sort(([a], [b]) => compareStrings(a, b)).map(([, item]) => item),\n unmatchedSelectors: {\n files: selection.files.filter((path) => !foundFiles.has(path)),\n symbols: selection.symbols.filter((name) => !foundSymbols.has(name)),\n },\n };\n}\n\n/** 구분 문자 포함 이름도 서로 충돌하지 않는 채널 키다. */\nfunction channelKey(target: BridgeTarget, channel: string): string {\n return JSON.stringify([target, channel]);\n}\n\n/** 채널 내부에서만 유일한 메서드 키다. */\nfunction methodKey(target: BridgeTarget, channel: string, method: string): string {\n return JSON.stringify([target, channel, method]);\n}\n\n/** 생성·등록이 선택됐는지에 따라 검토 범위가 넓어진 이유를 설명한다. */\nfunction impactReason(\n wiringKeys: ReadonlySet<string>, item: MatchedChannel | MatchedMethod, runtimeOnly = false,\n): ImpactReason {\n return wiringKeys.has(channelKey(item.target, item.channel)) ? 'channel-wiring'\n : runtimeOnly ? 'runtime-observation' : 'method';\n}\n\n/** 입력 파일 순서와 locale에 의존하지 않는 논리 키 정렬이다. */\nfunction compareLogicalKeys(\n a: { target: BridgeTarget; channel: string; method?: string },\n b: { target: BridgeTarget; channel: string; method?: string },\n): number {\n return compareStrings(a.target, b.target) || compareStrings(a.channel, b.channel) ||\n compareStrings(a.method ?? '', b.method ?? '');\n}\n"]}
@@ -0,0 +1,42 @@
1
+ import type { BridgeLocation } from '../exchange/parse.ts';
2
+ import type { PreflightContext } from '../exchange/preflight-context.ts';
3
+ import type { BridgeRuntimeDocument, RuntimeExpectations, RuntimeOutcome, RuntimePlatform, RuntimeRoute } from '../exchange/runtime.ts';
4
+ import type { BridgeEndpoint } from '../join/join.ts';
5
+ import type { PreflightReport } from './preflight.ts';
6
+ import type { RuntimeVerificationReport } from './runtime.ts';
7
+ /** 실제 주소 관찰과 정적 후보를 구분하며 기록의 run·instance를 보존한다. */
8
+ export interface PreflightRuntimeRoute extends RuntimeRoute {
9
+ readonly runId: string;
10
+ readonly scenario: string;
11
+ readonly platform: RuntimePlatform;
12
+ readonly instance: string;
13
+ readonly observedCalls: number;
14
+ readonly outcomes: Readonly<Record<RuntimeOutcome, number>>;
15
+ readonly callers: readonly BridgeLocation[];
16
+ readonly callersOmitted: number;
17
+ readonly selectionReasons: readonly ('route' | 'caller-file')[];
18
+ readonly staticStatus: 'candidates' | 'unobserved' | 'unsupported';
19
+ readonly boundaryKeys: readonly string[];
20
+ readonly candidateKey?: string;
21
+ }
22
+ /** 같은 주소의 후보는 runtime 인스턴스 수와 무관하게 한 번만 싣는다. */
23
+ export interface PreflightRuntimeCandidates {
24
+ readonly key: string;
25
+ readonly channel: string;
26
+ readonly method?: string;
27
+ readonly transport?: 'basic-message-channel';
28
+ readonly matching?: 'literal' | 'prefix';
29
+ readonly handlers: readonly BridgeEndpoint[];
30
+ readonly handlersOmitted: number;
31
+ }
32
+ /** 선언된 시나리오의 검증과 선택한 정적 경계의 실행 공백을 별도로 제공한다. */
33
+ export interface PreflightRuntimeReport {
34
+ readonly aligned: boolean;
35
+ readonly verification: RuntimeVerificationReport;
36
+ readonly routes: readonly PreflightRuntimeRoute[];
37
+ readonly candidates: readonly PreflightRuntimeCandidates[];
38
+ readonly unobservedBoundaries: readonly string[];
39
+ readonly uncoveredBoundaries: readonly string[];
40
+ }
41
+ /** runtime은 원래 정적 간선을 바꾸지 않고 독립 검증과 후보 근거만 추가한다. */
42
+ export declare function attachPreflightRuntime(context: PreflightContext, report: PreflightReport, expectations: RuntimeExpectations, documents: readonly BridgeRuntimeDocument[]): PreflightReport;
@@ -0,0 +1,193 @@
1
+ import { compareStrings } from "../compare.js";
2
+ import { RuntimeValidationError } from "../exchange/runtime.js";
3
+ import { MessageAddressIndex } from "../join/message-address.js";
4
+ import { verifyRuntimeEvidence } from "./runtime.js";
5
+ /** runtime은 원래 정적 간선을 바꾸지 않고 독립 검증과 후보 근거만 추가한다. */
6
+ export function attachPreflightRuntime(context, report, expectations, documents) {
7
+ if (expectations.project !== context.project || report.project !== context.project || report.revision !== context.revision) {
8
+ throw new RuntimeValidationError('Preflight and runtime expectations must describe the same project and capture.');
9
+ }
10
+ const verification = verifyRuntimeEvidence(expectations, documents);
11
+ const aligned = expectations.revision === context.revision;
12
+ const files = new Set(report.reviewFiles);
13
+ const addedFiles = new Set();
14
+ const byRoute = new Map();
15
+ const nativePlatforms = new Set([...context.bridges, ...(context.messages ?? [])]
16
+ .flatMap(({ platform }) => platform === 'swift' || platform === 'kotlin' ? [platform] : []));
17
+ const messagePlatforms = new Set((context.messages ?? []).map(({ platform }) => platform));
18
+ const boundaryPlatforms = new Map(report.boundaries.map(({ subject, receivers }) => {
19
+ const platforms = new Set(receivers.flatMap(({ platform }) => platform === 'swift' || platform === 'kotlin' ? [platform] : []));
20
+ return [subject.key, platforms.size > 0 ? platforms : nativePlatforms];
21
+ }));
22
+ const messageBoundaries = new MessageAddressIndex();
23
+ const methodChannels = new Set();
24
+ for (const { subject } of report.boundaries) {
25
+ if (subject.target !== 'flutter')
26
+ continue;
27
+ if (subject.transport === 'basic-message-channel') {
28
+ messageBoundaries.add(subject.channel, subject.matching ?? 'literal', subject.key);
29
+ continue;
30
+ }
31
+ const key = routeKey(subject.channel, subject.method);
32
+ const keys = byRoute.get(key) ?? [];
33
+ keys.push(subject.key);
34
+ byRoute.set(key, keys);
35
+ if (subject.method !== undefined)
36
+ methodChannels.add(subject.channel);
37
+ }
38
+ const required = report.boundaries.filter(({ subject }) => subject.target === 'flutter' &&
39
+ (subject.transport === 'basic-message-channel' || subject.method !== undefined || !methodChannels.has(subject.channel)));
40
+ const boundaryKeys = (channel, method, transport, platform) => (transport === 'basic-message-channel'
41
+ ? messageBoundaries.matching(channel)
42
+ : [...(byRoute.get(routeKey(channel, method)) ?? []), ...(byRoute.get(routeKey(channel, undefined)) ?? [])])
43
+ .filter((key) => platform !== undefined && boundaryPlatforms.get(key)?.has(platform));
44
+ const pair = (key, platform) => JSON.stringify([key, platform]);
45
+ const declared = new Set(expectations.checks.flatMap((check) => {
46
+ const platform = nativeLanguage(check.platform);
47
+ return boundaryKeys(check.channel, check.method, check.transport, platform).map((key) => pair(key, platform));
48
+ }));
49
+ const observed = new Set();
50
+ const handlers = new Map();
51
+ for (const document of context.bridges) {
52
+ if ((document.platform !== 'swift' && document.platform !== 'kotlin') || document.target !== 'flutter')
53
+ continue;
54
+ for (const fact of document.facts) {
55
+ if (fact.kind !== 'method-handle' || fact.dynamic || fact.channel === null || fact.method === undefined)
56
+ continue;
57
+ const key = candidateKey(document.platform, fact.channel, fact.method);
58
+ let group = handlers.get(key);
59
+ if (group === undefined) {
60
+ group = { channel: fact.channel, method: fact.method, endpoints: new Map() };
61
+ handlers.set(key, group);
62
+ }
63
+ group.endpoints.set(JSON.stringify([fact.location.path, fact.location.line, fact.location.column,
64
+ fact.symbol?.usr ?? null, fact.symbol?.qualifiedName ?? null, fact.sourceLanguage ?? null]), {
65
+ platform: document.platform, location: fact.location,
66
+ ...(fact.symbol === undefined ? {} : { symbol: fact.symbol }),
67
+ ...(fact.sourceLanguage === undefined ? {} : { sourceLanguage: fact.sourceLanguage }),
68
+ });
69
+ }
70
+ }
71
+ const candidateGroups = new Map();
72
+ const nativeMessages = new MessageAddressIndex();
73
+ for (const document of context.messages ?? []) {
74
+ if (document.platform !== 'swift' && document.platform !== 'kotlin')
75
+ continue;
76
+ for (const fact of document.facts) {
77
+ const address = fact.dynamic ? fact.channelPrefix : fact.channel;
78
+ if (!address)
79
+ continue;
80
+ nativeMessages.add(address, fact.dynamic ? 'prefix' : 'literal', { matching: fact.dynamic ? 'prefix' : 'literal',
81
+ endpoint: { platform: document.platform, location: fact.location, ...(fact.symbol === undefined ? {} : { symbol: fact.symbol }),
82
+ ...(fact.sourceLanguage === undefined ? {} : { sourceLanguage: fact.sourceLanguage }),
83
+ ...(fact.dynamic ? { channelExpression: fact.channel, channelPrefix: fact.channelPrefix } : {}) } });
84
+ }
85
+ }
86
+ let messageCandidateWork = 0;
87
+ const groups = new Map();
88
+ for (const document of [...documents].sort((a, b) => compareStrings(a.run.id, b.run.id))) {
89
+ // 기대 문서가 이전 revision이어도 현재 정적 분석과 같은 실행만 경계 근거에 연결한다.
90
+ if (document.revision !== context.revision)
91
+ continue;
92
+ for (const event of document.events) {
93
+ const platform = nativeLanguage(document.run.platform);
94
+ const supported = platform !== undefined && nativePlatforms.has(platform)
95
+ && (event.transport === 'method-channel' || messagePlatforms.has(platform));
96
+ const route = candidateKey(platform, event.channel, event.method, event.transport);
97
+ const keys = supported ? boundaryKeys(event.channel, event.method, event.transport, platform) : [];
98
+ const fromFile = event.caller !== undefined && files.has(event.caller.path);
99
+ if (keys.length === 0 && !fromFile)
100
+ continue;
101
+ for (const key of keys)
102
+ observed.add(pair(key, platform));
103
+ const candidates = supported && event.transport === 'method-channel' ? handlers.get(route) : undefined;
104
+ if (candidates !== undefined && !candidateGroups.has(route)) {
105
+ const endpoints = [...candidates.endpoints.entries()].sort(([a], [b]) => compareStrings(a, b)).map(([, value]) => value);
106
+ for (const endpoint of endpoints)
107
+ addedFiles.add(endpoint.location.path);
108
+ candidateGroups.set(route, { key: route, channel: candidates.channel, method: candidates.method,
109
+ handlers: endpoints.slice(0, 20), handlersOmitted: Math.max(0, endpoints.length - 20) });
110
+ }
111
+ if (supported && event.transport === 'basic-message-channel' && !candidateGroups.has(route)) {
112
+ const addressMatches = nativeMessages.matching(event.channel);
113
+ messageCandidateWork += addressMatches.length;
114
+ if (messageCandidateWork > 1_000_000)
115
+ throw new RuntimeValidationError('Message runtime candidate budget exceeded.');
116
+ const matches = addressMatches.filter(({ endpoint }) => endpoint.platform === platform);
117
+ if (matches.length > 0) {
118
+ const unique = [...new Map(matches.map(({ endpoint }) => [JSON.stringify(endpoint), endpoint])).entries()]
119
+ .sort(([a], [b]) => compareStrings(a, b)).map(([, endpoint]) => endpoint);
120
+ for (const endpoint of unique)
121
+ addedFiles.add(endpoint.location.path);
122
+ candidateGroups.set(route, { key: route, channel: event.channel, transport: 'basic-message-channel',
123
+ matching: matches.some(({ matching }) => matching === 'prefix') ? 'prefix' : 'literal',
124
+ handlers: unique.slice(0, 20), handlersOmitted: Math.max(0, unique.length - 20) });
125
+ }
126
+ }
127
+ const hasCandidates = supported && candidateGroups.has(route);
128
+ const key = JSON.stringify([document.run.id, event.transport, event.channel, event.method ?? null, event.instance]);
129
+ let group = groups.get(key);
130
+ if (group === undefined) {
131
+ group = { route: {
132
+ runId: document.run.id, scenario: document.run.scenario, platform: document.run.platform,
133
+ transport: event.transport, channel: event.channel, ...(event.method === undefined ? {} : { method: event.method }),
134
+ instance: event.instance, staticStatus: !supported ? 'unsupported' : hasCandidates ? 'candidates' : 'unobserved',
135
+ boundaryKeys: [...new Set(keys)].sort(compareStrings),
136
+ ...(!hasCandidates ? {} : { candidateKey: route }),
137
+ }, count: 0, outcomes: { success: 0, error: 0, timeout: 0, pending: 0, 'missing-handler': 0 },
138
+ callers: new Map(), reasons: new Set() };
139
+ groups.set(key, group);
140
+ }
141
+ group.count++;
142
+ group.outcomes[event.outcome]++;
143
+ if (keys.length > 0)
144
+ group.reasons.add('route');
145
+ if (fromFile)
146
+ group.reasons.add('caller-file');
147
+ if (event.caller !== undefined) {
148
+ group.callers.set(JSON.stringify([event.caller.path, event.caller.line, event.caller.column]), event.caller);
149
+ addedFiles.add(event.caller.path);
150
+ }
151
+ }
152
+ }
153
+ const routes = [...groups.entries()].sort(([a], [b]) => compareStrings(a, b)).map(([, group]) => ({
154
+ ...group.route, observedCalls: group.count, outcomes: group.outcomes,
155
+ callers: [...group.callers.entries()].sort(([a], [b]) => compareStrings(a, b)).slice(0, 20).map(([, caller]) => caller),
156
+ callersOmitted: Math.max(0, group.callers.size - 20), selectionReasons: [...group.reasons].sort(compareStrings),
157
+ }));
158
+ const missingPlatform = (set, key) => [...(boundaryPlatforms.get(key) ?? [])].some((platform) => !set.has(pair(key, platform)));
159
+ const unobservedBoundaries = required.filter(({ subject }) => missingPlatform(observed, subject.key))
160
+ .map(({ subject }) => subject.key).sort(compareStrings);
161
+ const uncoveredBoundaries = required.filter(({ subject }) => !aligned || missingPlatform(declared, subject.key))
162
+ .map(({ subject }) => subject.key).sort(compareStrings);
163
+ const gaps = [];
164
+ if (!aligned)
165
+ gaps.push({ code: 'stale-runtime-expectations', message: 'Runtime expectations target a different capture revision.' });
166
+ if (verification.status !== 'passed')
167
+ gaps.push({ code: 'runtime-verification-gap',
168
+ message: 'Declared runtime scenarios contain failures, missing observations, stale runs, or incomplete evidence.' });
169
+ if (unobservedBoundaries.length > 0)
170
+ gaps.push({ code: 'unobserved-runtime-boundaries',
171
+ message: `${unobservedBoundaries.length} related static boundary(s) have no current runtime observations.` });
172
+ if (uncoveredBoundaries.length > 0)
173
+ gaps.push({ code: 'uncovered-runtime-boundaries',
174
+ message: `${uncoveredBoundaries.length} related static boundary(s) have no current scenario expectation.` });
175
+ if (routes.some(({ staticStatus }) => staticStatus !== 'candidates'))
176
+ gaps.push({ code: 'runtime-static-binding-gap',
177
+ message: 'Some related runtime routes have no supported static handler candidate; observations do not identify native symbols.' });
178
+ const reviewFiles = [...new Set([...report.reviewFiles, ...addedFiles])].sort(compareStrings);
179
+ const candidates = [...candidateGroups.entries()].sort(([a], [b]) => compareStrings(a, b)).map(([, value]) => value);
180
+ return { ...report, runtime: { aligned, verification, routes, candidates, unobservedBoundaries, uncoveredBoundaries },
181
+ reviewFiles, limitations: [...report.limitations, ...gaps],
182
+ summary: { ...report.summary, reviewFiles: reviewFiles.length, evidenceGaps: report.summary.evidenceGaps + gaps.length } };
183
+ }
184
+ function routeKey(channel, method, transport = 'method-channel') {
185
+ return JSON.stringify(transport === 'basic-message-channel' ? [transport, channel, null] : [channel, method ?? null]);
186
+ }
187
+ function nativeLanguage(platform) {
188
+ return platform === 'android' ? 'kotlin' : platform === 'ios' || platform === 'macos' ? 'swift' : undefined;
189
+ }
190
+ function candidateKey(platform, channel, method, transport = 'method-channel') {
191
+ return platform === 'kotlin' ? JSON.stringify(['kotlin', transport, channel, method ?? null]) : routeKey(channel, method, transport);
192
+ }
193
+ //# sourceMappingURL=preflight-runtime.js.map