isthmus-cli 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,13 +22,13 @@ isthmus는 조사한 도구들이 언어별로 나눠 보던 이 교차 경계
22
22
 
23
23
  ## 상태
24
24
 
25
- **0.1.3.** bridge-facts 버전 1 파서와 `check`, `query`, `graph`,
25
+ **0.1.4.** bridge-facts 버전 1 파서와 `check`, `query`, `graph`, `diff`,
26
26
  cartograph용 외부 보존 근거 왕복을 구현했다. 외부 입력·혼합 target·그래프 크기와
27
27
  Dart/Swift Phase 0 추출 경계를 fail-closed로 강화했다. 다음 단계는 실제 Flutter 앱
28
28
  도그푸딩과 React Native 지원이다.
29
29
 
30
- 현재 cartographSwift fact를 생산하지만 Dart producer는 아직 정식 릴리스 전이다.
31
- 저장소의 Phase 0 Dart 추출기는 형식 검증용 임시 구현이며 운영 도구로 배포되지 않는다.
30
+ 정식 producercartograph 0.5.3 이상과 dartograph 0.1.1 이상이다. 도구의 실제 출력과
31
+ 공개 battery 플러그인의 Swift USR·Dart 호출 근거 왕복을 검증했다.
32
32
 
33
33
  | 문서 | 내용 |
34
34
  |---|---|
@@ -101,6 +101,26 @@ cartograph dead --external-retentions external-retentions.json
101
101
 
102
102
  `retentions`는 핸들러의 USR을 우선 사용하고 없으면 `qualifiedName`을 남긴다. `mixed-targets` 문서는 v1에서 사실별 target을 복원할 수 없어 모든 소비 명령이 종료 코드 2로 조인을 보류한다. 먼저 생산 단계에서 target별 문서로 분리해야 한다.
103
103
 
104
+ 실제 공개 Flutter 플러그인에서 생산부터 소비까지 확인하려면 저장소 루트에서 다음 검증을
105
+ 실행한다. 스크립트는 `plus_plugins`의 고정 커밋을 sparse checkout하고 배터리 플러그인의
106
+ 원본 Dart·Swift 소스에서 세 메서드의 보존 근거를 확인한 뒤 임시 checkout을 지운다.
107
+ 네트워크, Git 2.26 이상, Swift 6, cartograph 0.5.3 이상, dartograph 0.1.1 이상이
108
+ 필요하다. isthmus는 현재 소스에서 자동으로 다시 빌드하며, 세 번째 인자로 별도 isthmus
109
+ JavaScript 산출물을 넘길 수도 있다.
110
+
111
+ ```bash
112
+ npm run build
113
+ node scripts/verify-public-flutter-plugin.mjs \
114
+ /path/to/cartograph \
115
+ /path/to/dartograph
116
+ ```
117
+
118
+ 공개 플러그인 검증은 원본 `addMethodCallDelegate` 구현에서 나온 Swift USR과 세 원본 Dart
119
+ 호출 위치를 확인하고, cartograph `--explain`이 해당 심볼의 대표 근거를 읽는지 검증한다. 이미
120
+ public인 플러그인 handler의 dead 상태 전환을 억지로 만들지는 않는다. 그 전환과
121
+ `setMethodCallHandler` 경로는 `verify-cartograph-roundtrip.mjs`의 합성 코퍼스가 별도로
122
+ 검증한다.
123
+
104
124
  채널이나 메서드가 경계 반대편의 어느 위치와 연결되는지 조회하려면:
105
125
 
106
126
  ```bash
@@ -146,16 +166,45 @@ handler 본문, USR 누락, 입력 생성 시각 차이, 혼합 target은 `limit
146
166
  npm run verify
147
167
  ```
148
168
 
149
- 로컬 cartograph 코퍼스와 외부 보존 근거 왕복을 검증하려면 빌드된 cartograph 바이너리와 fixture 루트를 넘긴다.
150
- 검증은 외부 바이너리와 코퍼스가 필요하므로 `npm run verify`와 공개 CI에는 포함되지
151
- 않으며 릴리스 전에 수동으로 실행한다.
169
+ 실제 producer와 외부 보존 근거 왕복을 검증하려면 cartograph 0.5.3 이상,
170
+ dartograph 바이너리와 도구가 함께 분석할 fixture 루트를 넘긴다. 검증은 producer
171
+ 바이너리와 컴파일러 인덱스가 필요하므로 `npm run verify`와 공개 CI에는 포함되지 않으며
172
+ 릴리스 전에 수동으로 실행한다.
152
173
 
153
174
  ```bash
154
175
  node scripts/verify-cartograph-roundtrip.mjs \
155
176
  /path/to/cartograph \
177
+ /path/to/dartograph \
156
178
  /path/to/FalsePositiveCorpus
157
179
  ```
158
180
 
181
+ ## 변경 전후 비교 (0.1.4 이상)
182
+
183
+ 같은 프로젝트의 변경 전후 Dart·Swift 교환 파일을 비교하려면:
184
+
185
+ ```bash
186
+ isthmus diff \
187
+ --before before-dart.json before-swift.json \
188
+ --after after-dart.json after-swift.json --strict
189
+ ```
190
+
191
+ `isthmus-diff` v1은 추가·제거된 논리 메서드 연결, 새로 관찰된 불일치와 사라진 불일치,
192
+ 양 시점의 분석 한계와 그 차이, producer 버전·생성 시각을 JSON으로 출력한다. 연결에는 호출자와
193
+ 핸들러 위치가 포함된다. 줄 이동은 연결 변경으로 세지 않으며 rename은 추측하지 않는다.
194
+ 같은 논리 키의 호출자·핸들러 교체나 개별 호출 위치 증감은 이번 비교 범위에 포함하지 않는다.
195
+
196
+ `--strict`는 새로 관찰된 error가 있을 때만 1이다. 기존 오류·경고·분석 한계만 있으면 0이므로
197
+ 성공 코드가 삭제 안전성이나 완전한 분석을 뜻하지 않는다. `resolvedIssues`도 이전 불일치가
198
+ 더 이상 관찰되지 않는다는 뜻이며, 동적 전환·추출기 변경 때문인지 한계를 함께 확인해야 한다.
199
+
200
+ 현재 diff는 Flutter의 Dart·Swift 문서만 받는다. 각 시점에 두 플랫폼이 모두 필요하며,
201
+ 양 시점의 `project`와 플랫폼·도구별 문서 개수가
202
+ 같아야 한다. 한 checkout의 같은 경로에서 각 revision을 빌드해 JSON을 보관한다. 일부 파일만
203
+ 추출한 결과와 전체 결과를 비교하지 말고 같은 분석 설정을 사용한다. 입력 파일은 합계 256개,
204
+ 텍스트 길이 제한은 기존 CLI와 동일하다. 혼합 target이나 비교 불가능한 입력은 코드 2로 거부한다.
205
+ `generatedAt`은 fact 추출 시각이며 revision 순서가 아니다. 비교 방향은 `--before`와
206
+ `--after` 인자로 결정되므로 사용자가 올바른 revision의 파일을 지정해야 한다.
207
+
159
208
  ## 코딩 에이전트 skill
160
209
 
161
210
  네이티브 브리지 핸들러를 지우거나 이름을 바꾸기 전에 `query`로 다른 언어의 호출자를
@@ -15,8 +15,9 @@ another platform may call.
15
15
 
16
16
  Obtain current `bridge-facts` JSON from both sides, then run:
17
17
 
18
- As of isthmus 0.1.1, cartograph produces the Swift facts. The Dart producer is not yet
19
- released; use the repository's Phase 0 extractor only as an interim source.
18
+ Use cartograph 0.5.3 or later for Swift and dartograph 0.1.1 or later for Dart.
19
+ Build the Swift project first to create its compiler index. Run both producers against
20
+ the same project root and select `cartograph bridges --target flutter --format json`.
20
21
  Do not fabricate a missing facts file or treat an older file as current evidence.
21
22
 
22
23
  ```bash
@@ -0,0 +1,5 @@
1
+ import { type CommandResult, type ReadTextFile } from './check-command.ts';
2
+ /** 전후 교환 파일을 하나의 입력 예산으로 읽어 새 경계 오류만 CI 실패로 표시한다. */
3
+ export declare function runDiffCommand(arguments_: readonly string[], readTextFile: ReadTextFile): Promise<CommandResult>;
4
+ /** 각 snapshot은 caller와 receiver 문서를 함께 제공해야 한다. */
5
+ export declare const diffUsage: string;
@@ -0,0 +1,39 @@
1
+ import { BridgeJoinValidationError, MAX_DOCUMENTS_PER_JOIN } from "../join/join.js";
2
+ import { createBridgeDiff } from "../report/diff.js";
3
+ import { encodeSortedJson } from "../report/sorted-json.js";
4
+ import { internalError, isExpectedInputError, readBridgeDocuments } from "./check-command.js";
5
+ /** 전후 교환 파일을 하나의 입력 예산으로 읽어 새 경계 오류만 CI 실패로 표시한다. */
6
+ export async function runDiffCommand(arguments_, readTextFile) {
7
+ const strict = arguments_.at(-1) === '--strict';
8
+ const args = strict ? arguments_.slice(0, -1) : arguments_;
9
+ const separator = args.indexOf('--after');
10
+ const beforePaths = args.slice(2, separator);
11
+ const afterPaths = args.slice(separator + 1);
12
+ const paths = [...beforePaths, ...afterPaths];
13
+ if (args[0] !== 'diff' || args[1] !== '--before' || separator < 4 ||
14
+ beforePaths.length < 2 || afterPaths.length < 2 || paths.length > MAX_DOCUMENTS_PER_JOIN ||
15
+ paths.some((path) => path.trim().length === 0 || path.startsWith('-'))) {
16
+ return { standardOutput: '', standardError: `${diffUsage}\n`, exitCode: 64 };
17
+ }
18
+ try {
19
+ const documents = await readBridgeDocuments(paths, readTextFile);
20
+ const report = createBridgeDiff(documents.slice(0, beforePaths.length), documents.slice(beforePaths.length));
21
+ return {
22
+ standardOutput: encodeSortedJson(report),
23
+ standardError: '',
24
+ exitCode: strict && report.summary.introducedErrors > 0 ? 1 : 0,
25
+ };
26
+ }
27
+ catch (error) {
28
+ if (error instanceof BridgeJoinValidationError) {
29
+ return { standardOutput: '', standardError: 'Unable to compare bridge facts; use the same project and Flutter Dart/Swift producer inventory without mixed targets in both snapshots.\n', exitCode: 2 };
30
+ }
31
+ return isExpectedInputError(error)
32
+ ? { standardOutput: '', standardError: 'Unable to read or validate bridge facts; check the input files.\n', exitCode: 2 }
33
+ : internalError();
34
+ }
35
+ }
36
+ /** 각 snapshot은 caller와 receiver 문서를 함께 제공해야 한다. */
37
+ export const diffUsage = 'Usage: isthmus diff --before <dart.json> <swift.json> [more...] '
38
+ + '--after <dart.json> <swift.json> [more...] [--strict]';
39
+ //# sourceMappingURL=diff-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-command.js","sourceRoot":"","sources":["../../src/cli/diff-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACpF,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,mBAAmB,EAAyC,MAAM,oBAAoB,CAAC;AAErI,qDAAqD;AACrD,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,UAA6B,EAAE,YAA0B;IAC5F,MAAM,MAAM,GAAG,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC;IAChD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;IAC3D,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,CAAC,GAAG,WAAW,EAAE,GAAG,UAAU,CAAC,CAAC;IAC9C,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,UAAU,IAAI,SAAS,GAAG,CAAC;QAC/D,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,sBAAsB;QACxF,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QACzE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,aAAa,EAAE,GAAG,SAAS,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC/E,CAAC;IACD,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,mBAAmB,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;QACjE,MAAM,MAAM,GAAG,gBAAgB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;QAC7G,OAAO;YACL,cAAc,EAAE,gBAAgB,CAAC,MAAM,CAAC;YACxC,aAAa,EAAE,EAAE;YACjB,QAAQ,EAAE,MAAM,IAAI,MAAM,CAAC,OAAO,CAAC,gBAAgB,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SAChE,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,yBAAyB,EAAE,CAAC;YAC/C,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,aAAa,EAAE,2IAA2I,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;QACzM,CAAC;QACD,OAAO,oBAAoB,CAAC,KAAK,CAAC;YAChC,CAAC,CAAC,EAAE,cAAc,EAAE,EAAE,EAAE,aAAa,EAAE,mEAAmE,EAAE,QAAQ,EAAE,CAAC,EAAE;YACzH,CAAC,CAAC,aAAa,EAAE,CAAC;IACtB,CAAC;AACH,CAAC;AAED,mDAAmD;AACnD,MAAM,CAAC,MAAM,SAAS,GAAG,kEAAkE;MACvF,uDAAuD,CAAC","sourcesContent":["import { BridgeJoinValidationError, MAX_DOCUMENTS_PER_JOIN } from '../join/join.ts';\nimport { createBridgeDiff } from '../report/diff.ts';\nimport { encodeSortedJson } from '../report/sorted-json.ts';\nimport { internalError, isExpectedInputError, readBridgeDocuments, type CommandResult, type ReadTextFile } from './check-command.ts';\n\n/** 전후 교환 파일을 하나의 입력 예산으로 읽어 새 경계 오류만 CI 실패로 표시한다. */\nexport async function runDiffCommand(arguments_: readonly string[], readTextFile: ReadTextFile): Promise<CommandResult> {\n const strict = arguments_.at(-1) === '--strict';\n const args = strict ? arguments_.slice(0, -1) : arguments_;\n const separator = args.indexOf('--after');\n const beforePaths = args.slice(2, separator);\n const afterPaths = args.slice(separator + 1);\n const paths = [...beforePaths, ...afterPaths];\n if (args[0] !== 'diff' || args[1] !== '--before' || separator < 4 ||\n beforePaths.length < 2 || afterPaths.length < 2 || paths.length > MAX_DOCUMENTS_PER_JOIN ||\n paths.some((path) => path.trim().length === 0 || path.startsWith('-'))) {\n return { standardOutput: '', standardError: `${diffUsage}\\n`, exitCode: 64 };\n }\n try {\n const documents = await readBridgeDocuments(paths, readTextFile);\n const report = createBridgeDiff(documents.slice(0, beforePaths.length), documents.slice(beforePaths.length));\n return {\n standardOutput: encodeSortedJson(report),\n standardError: '',\n exitCode: strict && report.summary.introducedErrors > 0 ? 1 : 0,\n };\n } catch (error) {\n if (error instanceof BridgeJoinValidationError) {\n return { standardOutput: '', standardError: 'Unable to compare bridge facts; use the same project and Flutter Dart/Swift producer inventory without mixed targets in both snapshots.\\n', exitCode: 2 };\n }\n return isExpectedInputError(error)\n ? { standardOutput: '', standardError: 'Unable to read or validate bridge facts; check the input files.\\n', exitCode: 2 }\n : internalError();\n }\n}\n\n/** 각 snapshot은 caller와 receiver 문서를 함께 제공해야 한다. */\nexport const diffUsage = 'Usage: isthmus diff --before <dart.json> <swift.json> [more...] '\n + '--after <dart.json> <swift.json> [more...] [--strict]';\n"]}
package/dist/cli/main.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { checkUsage, runCheckCommand, } from "./check-command.js";
4
4
  import { graphUsage, runGraphCommand } from "./graph-command.js";
5
+ import { diffUsage, runDiffCommand } from "./diff-command.js";
5
6
  import { queryUsage, runQueryCommand } from "./query-command.js";
6
7
  import { retentionUsage, runRetentionsCommand, } from "./retentions-command.js";
7
8
  process.stdout.on('error', handleStreamError);
@@ -9,6 +10,7 @@ process.stderr.on('error', handleStreamError);
9
10
  const commandUsages = new Map([
10
11
  ['check', checkUsage],
11
12
  ['graph', graphUsage],
13
+ ['diff', diffUsage],
12
14
  ['query', queryUsage],
13
15
  ['retentions', retentionUsage],
14
16
  ]);
@@ -18,6 +20,7 @@ Commands:
18
20
  check Report unmatched bridge calls and handlers
19
21
  query Find both sides of a channel or method
20
22
  graph Render matched boundary edges
23
+ diff Compare bridge observations before and after a change
21
24
  retentions Produce external retention evidence
22
25
 
23
26
  Options:
@@ -64,6 +67,8 @@ async function dispatchCommand(commandArguments) {
64
67
  return runCheckCommand(commandArguments, readTextFile);
65
68
  case 'graph':
66
69
  return runGraphCommand(commandArguments, readTextFile);
70
+ case 'diff':
71
+ return runDiffCommand(commandArguments, readTextFile);
67
72
  case 'query':
68
73
  return runQueryCommand(commandArguments, readTextFile);
69
74
  case 'retentions': {
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport { readFile } from 'node:fs/promises';\n\nimport {\n checkUsage,\n runCheckCommand,\n type CommandResult,\n} from './check-command.ts';\nimport { graphUsage, runGraphCommand } from './graph-command.ts';\nimport { queryUsage, runQueryCommand } from './query-command.ts';\nimport {\n retentionUsage,\n runRetentionsCommand,\n} from './retentions-command.ts';\n\nprocess.stdout.on('error', handleStreamError);\nprocess.stderr.on('error', handleStreamError);\n\nconst commandUsages = new Map([\n ['check', checkUsage],\n ['graph', graphUsage],\n ['query', queryUsage],\n ['retentions', retentionUsage],\n]);\n\nconst rootHelp = `Usage: isthmus <command> [options]\n\nCommands:\n check Report unmatched bridge calls and handlers\n query Find both sides of a channel or method\n graph Render matched boundary edges\n retentions Produce external retention evidence\n\nOptions:\n -h, --help Show help\n --version Show version\n`;\n\nconst arguments_ = process.argv.slice(2);\nconst readTextFile = (path: string) => readFile(path, 'utf8');\nconst informationalResult = await runInformationalCommand(arguments_);\nconst result = informationalResult ?? await dispatchCommand(arguments_);\n\nprocess.stdout.write(result.standardOutput);\nprocess.stderr.write(result.standardError);\nprocess.exitCode = result.exitCode;\n\n/** 파이프 소비자가 먼저 닫힌 정상 상황만 조용히 종료한다. */\nfunction handleStreamError(error: NodeJS.ErrnoException): void {\n if (error.code === 'EPIPE') {\n process.exitCode = 0;\n return;\n }\n throw error;\n}\n\n/** 도움말과 버전처럼 입력 문서가 필요 없는 명령을 실행한다. */\nasync function runInformationalCommand(\n commandArguments: readonly string[],\n): Promise<CommandResult | undefined> {\n const first = commandArguments[0];\n if (\n commandArguments.length === 1 &&\n (first === '--help' || first === '-h')\n ) {\n return successfulText(rootHelp);\n }\n if (commandArguments.length === 1 && first === '--version') {\n return readVersion();\n }\n if (\n commandArguments.length === 2 &&\n (commandArguments[1] === '--help' || commandArguments[1] === '-h')\n ) {\n const usage = first === undefined ? undefined : commandUsages.get(first);\n if (usage !== undefined) return successfulText(`${usage}\\n`);\n }\n return undefined;\n}\n\n/** 알려진 분석 하위 명령으로만 라우팅한다. */\nasync function dispatchCommand(\n commandArguments: readonly string[],\n): Promise<CommandResult> {\n switch (commandArguments[0]) {\n case 'check':\n return runCheckCommand(commandArguments, readTextFile);\n case 'graph':\n return runGraphCommand(commandArguments, readTextFile);\n case 'query':\n return runQueryCommand(commandArguments, readTextFile);\n case 'retentions': {\n const version = await readPackageVersion();\n return version === undefined\n ? packageMetadataError()\n : runRetentionsCommand(\n commandArguments,\n readTextFile,\n () => new Date(),\n version,\n );\n }\n default:\n return {\n standardOutput: '',\n standardError: rootHelp,\n exitCode: 64,\n };\n }\n}\n\n/** 설치된 package.json을 단일 출처로 사용해 버전을 출력한다. */\nasync function readVersion(): Promise<CommandResult> {\n const version = await readPackageVersion();\n return version === undefined\n ? packageMetadataError()\n : successfulText(`${version}\\n`);\n}\n\n/** 설치된 package.json에서 검증된 버전 문자열을 읽는다. */\nasync function readPackageVersion(): Promise<string | undefined> {\n try {\n const text = await readFile(new URL('../../package.json', import.meta.url), 'utf8');\n const document: unknown = JSON.parse(text);\n return isVersionDocument(document) ? document.version : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** package metadata가 비어 있지 않은 버전을 제공하는지 확인한다. */\nfunction isVersionDocument(value: unknown): value is { readonly version: string } {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'version' in value &&\n typeof value.version === 'string' &&\n value.version.length > 0\n );\n}\n\n/** stdout 텍스트와 성공 종료 코드를 만든다. */\nfunction successfulText(standardOutput: string): CommandResult {\n return { standardOutput, standardError: '', exitCode: 0 };\n}\n\n/** package metadata 실패를 민감정보 없는 코드 2로 바꾼다. */\nfunction packageMetadataError(): CommandResult {\n return {\n standardOutput: '',\n standardError: 'Unable to read package metadata.\\n',\n exitCode: 2,\n };\n}\n"]}
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport { readFile } from 'node:fs/promises';\n\nimport {\n checkUsage,\n runCheckCommand,\n type CommandResult,\n} from './check-command.ts';\nimport { graphUsage, runGraphCommand } from './graph-command.ts';\nimport { diffUsage, runDiffCommand } from './diff-command.ts';\nimport { queryUsage, runQueryCommand } from './query-command.ts';\nimport {\n retentionUsage,\n runRetentionsCommand,\n} from './retentions-command.ts';\n\nprocess.stdout.on('error', handleStreamError);\nprocess.stderr.on('error', handleStreamError);\n\nconst commandUsages = new Map([\n ['check', checkUsage],\n ['graph', graphUsage],\n ['diff', diffUsage],\n ['query', queryUsage],\n ['retentions', retentionUsage],\n]);\n\nconst rootHelp = `Usage: isthmus <command> [options]\n\nCommands:\n check Report unmatched bridge calls and handlers\n query Find both sides of a channel or method\n graph Render matched boundary edges\n diff Compare bridge observations before and after a change\n retentions Produce external retention evidence\n\nOptions:\n -h, --help Show help\n --version Show version\n`;\n\nconst arguments_ = process.argv.slice(2);\nconst readTextFile = (path: string) => readFile(path, 'utf8');\nconst informationalResult = await runInformationalCommand(arguments_);\nconst result = informationalResult ?? await dispatchCommand(arguments_);\n\nprocess.stdout.write(result.standardOutput);\nprocess.stderr.write(result.standardError);\nprocess.exitCode = result.exitCode;\n\n/** 파이프 소비자가 먼저 닫힌 정상 상황만 조용히 종료한다. */\nfunction handleStreamError(error: NodeJS.ErrnoException): void {\n if (error.code === 'EPIPE') {\n process.exitCode = 0;\n return;\n }\n throw error;\n}\n\n/** 도움말과 버전처럼 입력 문서가 필요 없는 명령을 실행한다. */\nasync function runInformationalCommand(\n commandArguments: readonly string[],\n): Promise<CommandResult | undefined> {\n const first = commandArguments[0];\n if (\n commandArguments.length === 1 &&\n (first === '--help' || first === '-h')\n ) {\n return successfulText(rootHelp);\n }\n if (commandArguments.length === 1 && first === '--version') {\n return readVersion();\n }\n if (\n commandArguments.length === 2 &&\n (commandArguments[1] === '--help' || commandArguments[1] === '-h')\n ) {\n const usage = first === undefined ? undefined : commandUsages.get(first);\n if (usage !== undefined) return successfulText(`${usage}\\n`);\n }\n return undefined;\n}\n\n/** 알려진 분석 하위 명령으로만 라우팅한다. */\nasync function dispatchCommand(\n commandArguments: readonly string[],\n): Promise<CommandResult> {\n switch (commandArguments[0]) {\n case 'check':\n return runCheckCommand(commandArguments, readTextFile);\n case 'graph':\n return runGraphCommand(commandArguments, readTextFile);\n case 'diff':\n return runDiffCommand(commandArguments, readTextFile);\n case 'query':\n return runQueryCommand(commandArguments, readTextFile);\n case 'retentions': {\n const version = await readPackageVersion();\n return version === undefined\n ? packageMetadataError()\n : runRetentionsCommand(\n commandArguments,\n readTextFile,\n () => new Date(),\n version,\n );\n }\n default:\n return {\n standardOutput: '',\n standardError: rootHelp,\n exitCode: 64,\n };\n }\n}\n\n/** 설치된 package.json을 단일 출처로 사용해 버전을 출력한다. */\nasync function readVersion(): Promise<CommandResult> {\n const version = await readPackageVersion();\n return version === undefined\n ? packageMetadataError()\n : successfulText(`${version}\\n`);\n}\n\n/** 설치된 package.json에서 검증된 버전 문자열을 읽는다. */\nasync function readPackageVersion(): Promise<string | undefined> {\n try {\n const text = await readFile(new URL('../../package.json', import.meta.url), 'utf8');\n const document: unknown = JSON.parse(text);\n return isVersionDocument(document) ? document.version : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** package metadata가 비어 있지 않은 버전을 제공하는지 확인한다. */\nfunction isVersionDocument(value: unknown): value is { readonly version: string } {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'version' in value &&\n typeof value.version === 'string' &&\n value.version.length > 0\n );\n}\n\n/** stdout 텍스트와 성공 종료 코드를 만든다. */\nfunction successfulText(standardOutput: string): CommandResult {\n return { standardOutput, standardError: '', exitCode: 0 };\n}\n\n/** package metadata 실패를 민감정보 없는 코드 2로 바꾼다. */\nfunction packageMetadataError(): CommandResult {\n return {\n standardOutput: '',\n standardError: 'Unable to read package metadata.\\n',\n exitCode: 2,\n };\n}\n"]}
@@ -0,0 +1,37 @@
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;
12
+ };
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[];
22
+ };
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
+ }[];
36
+ };
37
+ };
@@ -0,0 +1,78 @@
1
+ import { compareStrings } from "../compare.js";
2
+ import { BridgeJoinValidationError, isBridgeJoinDeferred, joinBridgeDocuments } from "../join/join.js";
3
+ import { createCheckReport } from "./check-report.js";
4
+ /** 동일 프로젝트의 관찰 결과를 비교하며 삭제 안전성이나 rename을 추측하지 않는다. */
5
+ export function createBridgeDiff(before, after) {
6
+ validateSnapshots(before, after);
7
+ const oldJoin = joinBridgeDocuments(before);
8
+ const newJoin = joinBridgeDocuments(after);
9
+ if (isBridgeJoinDeferred(oldJoin) || isBridgeJoinDeferred(newJoin)) {
10
+ throw new BridgeJoinValidationError('Cannot compare deferred bridge joins.');
11
+ }
12
+ const oldReport = createCheckReport(oldJoin);
13
+ const newReport = createCheckReport(newJoin);
14
+ const addedMethods = difference(newJoin.matchedMethods, oldJoin.matchedMethods, logicalKey);
15
+ const removedMethods = difference(oldJoin.matchedMethods, newJoin.matchedMethods, logicalKey);
16
+ const introducedIssues = difference(newReport.issues, oldReport.issues, issueKey);
17
+ const resolvedIssues = difference(oldReport.issues, newReport.issues, issueKey);
18
+ return {
19
+ format: 'isthmus-diff',
20
+ version: 1,
21
+ summary: {
22
+ addedMethods: addedMethods.length,
23
+ removedMethods: removedMethods.length,
24
+ introducedErrors: introducedIssues.filter((issue) => issue.severity === 'error').length,
25
+ introducedWarnings: introducedIssues.filter((issue) => issue.severity === 'warning').length,
26
+ resolvedIssues: resolvedIssues.length,
27
+ },
28
+ addedMethods,
29
+ removedMethods,
30
+ introducedIssues,
31
+ resolvedIssues,
32
+ limitations: {
33
+ before: oldReport.limitations,
34
+ after: newReport.limitations,
35
+ added: difference(newReport.limitations, oldReport.limitations, limitationKey),
36
+ removed: difference(oldReport.limitations, newReport.limitations, limitationKey),
37
+ },
38
+ producers: { before: producerVersions(before), after: producerVersions(after) },
39
+ };
40
+ }
41
+ /** 플랫폼 누락이나 다른 프로젝트를 코드 삭제로 오해하지 않도록 입력 구성을 고정한다. */
42
+ function validateSnapshots(before, after) {
43
+ const all = [...before, ...after];
44
+ if (new Set(all.map((doc) => doc.project)).size !== 1 ||
45
+ ![before, after].every((docs) => docs.some((doc) => doc.platform === 'dart') &&
46
+ docs.some((doc) => doc.platform === 'swift')) ||
47
+ JSON.stringify(producerInventory(before)) !== JSON.stringify(producerInventory(after)) ||
48
+ all.some((doc) => (doc.platform !== 'dart' && doc.platform !== 'swift') ||
49
+ (doc.target !== null && doc.target !== 'flutter'))) {
50
+ throw new BridgeJoinValidationError('Diff requires matching Flutter producer inventories and project roots.');
51
+ }
52
+ }
53
+ /** 버전 변화는 출력하되 플랫폼·도구별 문서 개수 변화는 허용하지 않는다. */
54
+ function producerInventory(docs) {
55
+ return docs.map((doc) => JSON.stringify([doc.platform, doc.tool.name])).sort(compareStrings);
56
+ }
57
+ /** 추출기 업그레이드가 관찰 차이의 원인인지 검토할 버전 근거다. */
58
+ function producerVersions(docs) {
59
+ return docs.map((doc) => ({ platform: doc.platform, ...doc.tool, generatedAt: doc.generatedAt }))
60
+ .sort((a, b) => compareStrings(JSON.stringify(a), JSON.stringify(b)));
61
+ }
62
+ /** 충돌 없는 논리 키로 비교해 소스 줄 이동을 추가·삭제로 보고하지 않는다. */
63
+ function logicalKey(item) {
64
+ return JSON.stringify([item.target, item.channel, item.method ?? null]);
65
+ }
66
+ function issueKey(item) {
67
+ return JSON.stringify([item.code, logicalKey(item)]);
68
+ }
69
+ function limitationKey(item) {
70
+ return JSON.stringify([item.platform, item.tool, item.message]);
71
+ }
72
+ /** 키 집합 차이를 안정적으로 정렬하고 원래 증거를 보존한다. */
73
+ function difference(left, right, key) {
74
+ const existing = new Set(right.map(key));
75
+ return left.filter((item) => !existing.has(key(item)))
76
+ .sort((a, b) => compareStrings(key(a), key(b)));
77
+ }
78
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
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 { BridgeJoinValidationError, isBridgeJoinDeferred, joinBridgeDocuments } from '../join/join.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('Cannot compare deferred bridge joins.');\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, issueKey);\n const resolvedIssues = difference(oldReport.issues, newReport.issues, issueKey);\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('Diff requires matching Flutter producer inventories and project roots.');\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 issueKey(item: Parameters<typeof logicalKey>[0] & { readonly code: string }): string {\n return JSON.stringify([item.code, logicalKey(item)]);\n}\n\nfunction limitationKey(item: { readonly platform: string; readonly tool: string; readonly message: string }): string {\n return JSON.stringify([item.platform, item.tool, item.message]);\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isthmus-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Join bridge facts across cross-platform application boundaries.",
5
5
  "keywords": [
6
6
  "flutter",