isthmus-cli 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +64 -22
- package/README.md +70 -26
- package/Skills/isthmus/SKILL.md +93 -4
- package/dist/cli/check-command.d.ts +2 -25
- package/dist/cli/check-command.js +27 -181
- package/dist/cli/check-command.js.map +1 -1
- package/dist/cli/command-support.d.ts +48 -0
- package/dist/cli/command-support.js +144 -0
- package/dist/cli/command-support.js.map +1 -0
- package/dist/cli/diff-command.d.ts +1 -1
- package/dist/cli/diff-command.js +1 -1
- package/dist/cli/diff-command.js.map +1 -1
- package/dist/cli/graph-command.d.ts +1 -1
- package/dist/cli/graph-command.js +15 -14
- package/dist/cli/graph-command.js.map +1 -1
- package/dist/cli/impact-command.d.ts +5 -0
- package/dist/cli/impact-command.js +111 -0
- package/dist/cli/impact-command.js.map +1 -0
- package/dist/cli/main.js +35 -13
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/parse-arguments.d.ts +18 -0
- package/dist/cli/parse-arguments.js +48 -0
- package/dist/cli/parse-arguments.js.map +1 -0
- package/dist/cli/preflight-command.d.ts +5 -0
- package/dist/cli/preflight-command.js +83 -0
- package/dist/cli/preflight-command.js.map +1 -0
- package/dist/cli/query-command.d.ts +1 -1
- package/dist/cli/query-command.js +30 -10
- package/dist/cli/query-command.js.map +1 -1
- package/dist/cli/retentions-command.d.ts +1 -1
- package/dist/cli/retentions-command.js +11 -10
- package/dist/cli/retentions-command.js.map +1 -1
- package/dist/cli/runtime-command.d.ts +5 -0
- package/dist/cli/runtime-command.js +42 -0
- package/dist/cli/runtime-command.js.map +1 -0
- package/dist/cli/runtime-json-reader.d.ts +12 -0
- package/dist/cli/runtime-json-reader.js +37 -0
- package/dist/cli/runtime-json-reader.js.map +1 -0
- package/dist/exchange/impact-selection.d.ts +13 -0
- package/dist/exchange/impact-selection.js +35 -0
- package/dist/exchange/impact-selection.js.map +1 -0
- package/dist/exchange/kartograph-impact.d.ts +4 -0
- package/dist/exchange/kartograph-impact.js +169 -0
- package/dist/exchange/kartograph-impact.js.map +1 -0
- package/dist/exchange/messages.d.ts +51 -0
- package/dist/exchange/messages.js +126 -0
- package/dist/exchange/messages.js.map +1 -0
- package/dist/exchange/parse.d.ts +8 -0
- package/dist/exchange/parse.js +12 -8
- package/dist/exchange/parse.js.map +1 -1
- package/dist/exchange/preflight-context.d.ts +71 -0
- package/dist/exchange/preflight-context.js +317 -0
- package/dist/exchange/preflight-context.js.map +1 -0
- package/dist/exchange/producer-impact.d.ts +17 -0
- package/dist/exchange/producer-impact.js +226 -0
- package/dist/exchange/producer-impact.js.map +1 -0
- package/dist/exchange/runtime.d.ts +69 -0
- package/dist/exchange/runtime.js +160 -0
- package/dist/exchange/runtime.js.map +1 -0
- package/dist/join/message-address.d.ts +7 -0
- package/dist/join/message-address.js +37 -0
- package/dist/join/message-address.js.map +1 -0
- package/dist/join/messages.d.ts +23 -0
- package/dist/join/messages.js +80 -0
- package/dist/join/messages.js.map +1 -0
- package/dist/report/diff.d.ts +40 -33
- package/dist/report/diff.js +8 -4
- package/dist/report/diff.js.map +1 -1
- package/dist/report/impact.d.ts +62 -0
- package/dist/report/impact.js +168 -0
- package/dist/report/impact.js.map +1 -0
- package/dist/report/preflight-runtime.d.ts +42 -0
- package/dist/report/preflight-runtime.js +193 -0
- package/dist/report/preflight-runtime.js.map +1 -0
- package/dist/report/preflight-view.d.ts +146 -0
- package/dist/report/preflight-view.js +189 -0
- package/dist/report/preflight-view.js.map +1 -0
- package/dist/report/preflight.d.ts +104 -0
- package/dist/report/preflight.js +296 -0
- package/dist/report/preflight.js.map +1 -0
- package/dist/report/query.js +9 -2
- package/dist/report/query.js.map +1 -1
- package/dist/report/runtime-impact.d.ts +38 -0
- package/dist/report/runtime-impact.js +94 -0
- package/dist/report/runtime-impact.js.map +1 -0
- package/dist/report/runtime.d.ts +54 -0
- package/dist/report/runtime.js +137 -0
- package/dist/report/runtime.js.map +1 -0
- package/dist/report/sarif.d.ts +9 -2
- package/dist/report/sarif.js +4 -10
- package/dist/report/sarif.js.map +1 -1
- package/dist/report/sorted-json.d.ts +1 -1
- package/dist/report/sorted-json.js +2 -2
- package/dist/report/sorted-json.js.map +1 -1
- package/docs/BRIDGE-MESSAGES.md +102 -0
- package/docs/GRAPH-EXCHANGE.md +255 -0
- package/docs/IMPACT.md +96 -0
- package/docs/PREFLIGHT.md +295 -0
- package/docs/RUNTIME.md +172 -0
- package/docs/TOOLCHAIN.md +101 -0
- package/package.json +11 -2
- package/scripts/build-preflight-toolchain.mjs +186 -0
- package/scripts/capture-preflight.mjs +392 -0
- package/scripts/run-child.mjs +16 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isBridgeJoinDeferred, joinBridgeDocuments, MAX_DOCUMENTS_PER_JOIN, } from "../join/join.js";
|
|
2
2
|
import { BridgeGraphLimitError, BridgeGraphValidationError, createBridgeGraph, renderBridgeGraph, } from "../report/graph.js";
|
|
3
|
-
import { bridgeJoinDeferredError,
|
|
3
|
+
import { bridgeJoinDeferredError, inputFailureResult, internalError, readBridgeDocuments, } from "./command-support.js";
|
|
4
|
+
import { parseCommandArguments } from "./parse-arguments.js";
|
|
4
5
|
/** graph 인자를 실행해 경계 그래프와 종료 코드를 반환한다. */
|
|
5
6
|
export async function runGraphCommand(arguments_, readTextFile) {
|
|
6
7
|
const options = parseGraphArguments(arguments_);
|
|
@@ -9,8 +10,9 @@ export async function runGraphCommand(arguments_, readTextFile) {
|
|
|
9
10
|
try {
|
|
10
11
|
const documents = await readBridgeDocuments(options.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
|
const graph = createBridgeGraph(joined);
|
|
15
17
|
return {
|
|
16
18
|
standardOutput: renderBridgeGraph(graph, options.format),
|
|
@@ -30,23 +32,22 @@ export async function runGraphCommand(arguments_, readTextFile) {
|
|
|
30
32
|
return inputFailureResult(error) ?? internalError();
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
|
-
/** graph의 입력 파일과 선택 출력 형식을 검증한다. */
|
|
35
|
+
/** graph의 입력 파일과 선택 출력 형식을 검증한다. 플래그는 어디에 와도 된다. */
|
|
34
36
|
function parseGraphArguments(arguments_) {
|
|
35
37
|
if (arguments_[0] !== 'graph')
|
|
36
38
|
return undefined;
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
+
const parsed = parseCommandArguments(arguments_.slice(1), ['--format'], []);
|
|
40
|
+
if (parsed === undefined)
|
|
41
|
+
return undefined;
|
|
42
|
+
const format = parsed.valueFlags.get('--format');
|
|
43
|
+
if (format !== undefined && !isGraphFormat(format))
|
|
44
|
+
return undefined;
|
|
45
|
+
const inputPaths = [...parsed.positionals];
|
|
39
46
|
if (inputPaths.length < 2 ||
|
|
40
|
-
inputPaths.length > MAX_DOCUMENTS_PER_JOIN
|
|
41
|
-
inputPaths.some((path) => path.startsWith('-'))) {
|
|
47
|
+
inputPaths.length > MAX_DOCUMENTS_PER_JOIN) {
|
|
42
48
|
return undefined;
|
|
43
49
|
}
|
|
44
|
-
|
|
45
|
-
return { inputPaths, format: 'json' };
|
|
46
|
-
if (formatIndex !== arguments_.length - 2)
|
|
47
|
-
return undefined;
|
|
48
|
-
const format = arguments_[formatIndex + 1];
|
|
49
|
-
return isGraphFormat(format) ? { inputPaths, format } : undefined;
|
|
50
|
+
return { inputPaths, format: format ?? 'json' };
|
|
50
51
|
}
|
|
51
52
|
/** 지원하는 그래프 형식인지 확인한다. */
|
|
52
53
|
function isGraphFormat(value) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-command.js","sourceRoot":"","sources":["../../src/cli/graph-command.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,qBAAqB,EACrB,0BAA0B,EAC1B,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,uBAAuB,EACvB,aAAa,EACb,
|
|
1
|
+
{"version":3,"file":"graph-command.js","sourceRoot":"","sources":["../../src/cli/graph-command.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n isBridgeJoinDeferred,\n joinBridgeDocuments,\n MAX_DOCUMENTS_PER_JOIN,\n} from '../join/join.ts';\nimport {\n BridgeGraphLimitError,\n BridgeGraphValidationError,\n createBridgeGraph,\n renderBridgeGraph,\n} from '../report/graph.ts';\nimport {\n bridgeJoinDeferredError,\n inputFailureResult,\n internalError,\n readBridgeDocuments,\n type CommandResult,\n type ReadTextFile,\n} from './command-support.ts';\nimport { parseCommandArguments } from './parse-arguments.ts';\n\n/** graph 인자를 실행해 경계 그래프와 종료 코드를 반환한다. */\nexport async function runGraphCommand(\n arguments_: readonly string[],\n readTextFile: ReadTextFile,\n): Promise<CommandResult> {\n const options = parseGraphArguments(arguments_);\n if (options === undefined) return graphUsageError();\n try {\n const documents = await readBridgeDocuments(options.inputPaths, readTextFile);\n const joined = joinBridgeDocuments(documents);\n if (isBridgeJoinDeferred(joined)) {\n return bridgeJoinDeferredError(joined.observedFacts, documents.length);\n }\n const graph = createBridgeGraph(joined);\n return {\n standardOutput: renderBridgeGraph(graph, options.format),\n standardError: '',\n exitCode: 0,\n };\n } catch (error) {\n if (\n error instanceof BridgeGraphLimitError ||\n error instanceof BridgeGraphValidationError\n ) {\n return {\n standardOutput: '',\n standardError: `${error.message}\\n`,\n exitCode: 2,\n };\n }\n return inputFailureResult(error) ?? internalError();\n }\n}\n\n/** graph의 입력 파일과 선택 출력 형식을 검증한다. 플래그는 어디에 와도 된다. */\nfunction parseGraphArguments(arguments_: readonly string[]): GraphOptions | undefined {\n if (arguments_[0] !== 'graph') return undefined;\n const parsed = parseCommandArguments(arguments_.slice(1), ['--format'], []);\n if (parsed === undefined) return undefined;\n const format = parsed.valueFlags.get('--format');\n if (format !== undefined && !isGraphFormat(format)) return undefined;\n const inputPaths = [...parsed.positionals];\n if (\n inputPaths.length < 2 ||\n inputPaths.length > MAX_DOCUMENTS_PER_JOIN\n ) {\n return undefined;\n }\n return { inputPaths, format: format ?? 'json' };\n}\n\n/** 지원하는 그래프 형식인지 확인한다. */\nfunction isGraphFormat(value: string): value is GraphFormat {\n return value === 'json' || value === 'dot' || value === 'mermaid';\n}\n\n/** graph 사용 오류를 경로 없는 코드 64로 바꾼다. */\nfunction graphUsageError(): CommandResult {\n return {\n standardOutput: '',\n standardError: `${graphUsage}\\n`,\n exitCode: 64,\n };\n}\n\n/** 검증된 graph 명령 옵션이다. */\ninterface GraphOptions {\n readonly inputPaths: readonly string[];\n readonly format: GraphFormat;\n}\n\ntype GraphFormat = 'json' | 'dot' | 'mermaid';\n\n/** graph 명령의 한 줄 사용법이다. */\nexport const graphUsage =\n 'Usage: isthmus graph <bridge-facts.json> <bridge-facts.json> '\n + '[more...] [--format json|dot|mermaid]';\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CommandResult, ReadTextFile } from './command-support.ts';
|
|
2
|
+
/** 파일·심볼·CI 변경 목록을 브리지 영향 보고서로 바꾸는 CLI 경계다. */
|
|
3
|
+
export declare function runImpactCommand(arguments_: readonly string[], readTextFile: ReadTextFile): Promise<CommandResult>;
|
|
4
|
+
/** impact 명령의 독립적인 선택 방식과 CI 실패 옵션이다. */
|
|
5
|
+
export declare const impactUsage: string;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { ImpactSelectionValidationError, parseImpactSelection } from "../exchange/impact-selection.js";
|
|
2
|
+
import { isSafeNonEmptyString } from "../exchange/parse.js";
|
|
3
|
+
import { parseBridgeRuntime, RuntimeValidationError } from "../exchange/runtime.js";
|
|
4
|
+
import { MAX_DOCUMENTS_PER_JOIN } from "../join/join.js";
|
|
5
|
+
import { createBridgeImpact, encodeBridgeImpact, hasImpactBlockers } from "../report/impact.js";
|
|
6
|
+
import { inputFailure, inputFailureResult, internalError, isJsonParseFailure, MAX_INPUT_TEXT_LENGTH, readBridgeDocuments, } from "./command-support.js";
|
|
7
|
+
import { parseCommandArguments } from "./parse-arguments.js";
|
|
8
|
+
/** 파일·심볼·CI 변경 목록을 브리지 영향 보고서로 바꾸는 CLI 경계다. */
|
|
9
|
+
export async function runImpactCommand(arguments_, readTextFile) {
|
|
10
|
+
const parsed = parseCommandArguments(arguments_.slice(1), ['--file', '--symbol', '--changes', '--runtime', '--revision'], ['--strict', '--compact']);
|
|
11
|
+
if (parsed === undefined || ['--file', '--symbol', '--changes'].filter((flag) => parsed.valueFlags.has(flag)).length !== 1 ||
|
|
12
|
+
parsed.positionals.length < 2 || parsed.positionals.length > MAX_DOCUMENTS_PER_JOIN) {
|
|
13
|
+
return usageError();
|
|
14
|
+
}
|
|
15
|
+
const runtimePath = parsed.valueFlags.get('--runtime');
|
|
16
|
+
const revision = parsed.valueFlags.get('--revision');
|
|
17
|
+
if ((runtimePath === undefined) !== (revision === undefined) ||
|
|
18
|
+
(revision !== undefined && !isSafeNonEmptyString(revision)))
|
|
19
|
+
return usageError();
|
|
20
|
+
try {
|
|
21
|
+
let selection;
|
|
22
|
+
let supplementalTextLength = 0;
|
|
23
|
+
const changesPath = parsed.valueFlags.get('--changes');
|
|
24
|
+
if (changesPath !== undefined) {
|
|
25
|
+
const loaded = await readSupplemental(changesPath, readTextFile, 'changes', parseImpactSelection);
|
|
26
|
+
if ('exitCode' in loaded)
|
|
27
|
+
return loaded;
|
|
28
|
+
selection = loaded.document;
|
|
29
|
+
supplementalTextLength += loaded.textLength;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
const file = parsed.valueFlags.get('--file');
|
|
33
|
+
const symbol = parsed.valueFlags.get('--symbol');
|
|
34
|
+
try {
|
|
35
|
+
selection = parseImpactSelection({ format: 'isthmus-changes', version: 1,
|
|
36
|
+
files: file === undefined ? [] : [file], symbols: symbol === undefined ? [] : [symbol] });
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error instanceof ImpactSelectionValidationError)
|
|
40
|
+
return usageError();
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
let runtime;
|
|
45
|
+
if (runtimePath !== undefined && revision !== undefined) {
|
|
46
|
+
const loaded = await readSupplemental(runtimePath, readTextFile, 'runtime', parseBridgeRuntime);
|
|
47
|
+
if ('exitCode' in loaded)
|
|
48
|
+
return loaded;
|
|
49
|
+
supplementalTextLength += loaded.textLength;
|
|
50
|
+
runtime = { document: loaded.document, revision };
|
|
51
|
+
}
|
|
52
|
+
const documents = await readBridgeDocuments(parsed.positionals, readTextFile, supplementalTextLength);
|
|
53
|
+
const report = createBridgeImpact(documents, selection, runtime);
|
|
54
|
+
const blocked = parsed.booleanFlags.has('--strict') && hasImpactBlockers(report);
|
|
55
|
+
return {
|
|
56
|
+
standardOutput: encodeBridgeImpact(report, parsed.booleanFlags.has('--compact')),
|
|
57
|
+
standardError: blocked
|
|
58
|
+
? 'Change preflight requires review: related errors, analysis gaps, or unobserved selectors remain.\n'
|
|
59
|
+
: report.status === 'unobserved'
|
|
60
|
+
? 'No selected bridge facts were observed; inspect source coverage and use a pre-change snapshot for deleted code.\n'
|
|
61
|
+
: '',
|
|
62
|
+
exitCode: blocked ? 1 : 0,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error instanceof RuntimeValidationError)
|
|
67
|
+
return inputFailure(`${error.message}\n`);
|
|
68
|
+
return inputFailureResult(error) ?? internalError();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** 변경 파일도 브리지 입력과 같은 읽기·구문·계약 실패 경계를 지킨다. */
|
|
72
|
+
async function readSupplemental(path, readTextFile, kind, parse) {
|
|
73
|
+
const label = kind === 'changes' ? 'Changes' : 'Runtime';
|
|
74
|
+
const format = kind === 'changes' ? 'isthmus-changes' : 'bridge-runtime';
|
|
75
|
+
let text;
|
|
76
|
+
try {
|
|
77
|
+
text = await readTextFile(path);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return inputFailure(`Unable to read ${kind} input; check that the file exists and is readable.\n`);
|
|
81
|
+
}
|
|
82
|
+
if (text.length > MAX_INPUT_TEXT_LENGTH)
|
|
83
|
+
return inputFailure(`${label} input exceeds the input size limit.\n`);
|
|
84
|
+
let value;
|
|
85
|
+
try {
|
|
86
|
+
value = JSON.parse(text);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (isJsonParseFailure(error))
|
|
90
|
+
return inputFailure(`${label} input is not valid JSON.\n`);
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
return { document: parse(value), textLength: text.length };
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (error instanceof ImpactSelectionValidationError || error instanceof RuntimeValidationError) {
|
|
98
|
+
return inputFailure(`${label} input violates the ${format} contract: ${error.message}\n`);
|
|
99
|
+
}
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** 잘못된 인수는 입력을 읽기 전에 코드 64로 거부한다. */
|
|
104
|
+
function usageError() {
|
|
105
|
+
return { standardOutput: '', standardError: `${impactUsage}\n`, exitCode: 64 };
|
|
106
|
+
}
|
|
107
|
+
/** impact 명령의 독립적인 선택 방식과 CI 실패 옵션이다. */
|
|
108
|
+
export const impactUsage = 'Usage: isthmus impact (--file <relative-path> | --symbol <name-or-usr> '
|
|
109
|
+
+ '| --changes <changes.json>) <bridge-facts.json> <bridge-facts.json> [more...] '
|
|
110
|
+
+ '[--runtime <runtime.json> --revision <revision>] [--strict] [--compact]';
|
|
111
|
+
//# sourceMappingURL=impact-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"impact-command.js","sourceRoot":"","sources":["../../src/cli/impact-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { ImpactSelectionValidationError, parseImpactSelection } from '../exchange/impact-selection.ts';\nimport type { ImpactSelection } from '../exchange/impact-selection.ts';\nimport { isSafeNonEmptyString } from '../exchange/parse.ts';\nimport { parseBridgeRuntime, RuntimeValidationError } from '../exchange/runtime.ts';\nimport { MAX_DOCUMENTS_PER_JOIN } from '../join/join.ts';\nimport { createBridgeImpact, encodeBridgeImpact, hasImpactBlockers } from '../report/impact.ts';\nimport type { ImpactRuntimeInput } from '../report/runtime-impact.ts';\nimport {\n inputFailure, inputFailureResult, internalError, isJsonParseFailure,\n MAX_INPUT_TEXT_LENGTH, readBridgeDocuments,\n} from './command-support.ts';\nimport type { CommandResult, ReadTextFile } from './command-support.ts';\nimport { parseCommandArguments } from './parse-arguments.ts';\n\n/** 파일·심볼·CI 변경 목록을 브리지 영향 보고서로 바꾸는 CLI 경계다. */\nexport async function runImpactCommand(\n arguments_: readonly string[], readTextFile: ReadTextFile,\n): Promise<CommandResult> {\n const parsed = parseCommandArguments(arguments_.slice(1),\n ['--file', '--symbol', '--changes', '--runtime', '--revision'], ['--strict', '--compact']);\n if (parsed === undefined || ['--file', '--symbol', '--changes'].filter((flag) => parsed.valueFlags.has(flag)).length !== 1 ||\n parsed.positionals.length < 2 || parsed.positionals.length > MAX_DOCUMENTS_PER_JOIN) {\n return usageError();\n }\n const runtimePath = parsed.valueFlags.get('--runtime');\n const revision = parsed.valueFlags.get('--revision');\n if ((runtimePath === undefined) !== (revision === undefined) ||\n (revision !== undefined && !isSafeNonEmptyString(revision))) return usageError();\n try {\n let selection: ImpactSelection;\n let supplementalTextLength = 0;\n const changesPath = parsed.valueFlags.get('--changes');\n if (changesPath !== undefined) {\n const loaded = await readSupplemental(changesPath, readTextFile, 'changes', parseImpactSelection);\n if ('exitCode' in loaded) return loaded;\n selection = loaded.document;\n supplementalTextLength += loaded.textLength;\n } else {\n const file = parsed.valueFlags.get('--file');\n const symbol = parsed.valueFlags.get('--symbol');\n try {\n selection = parseImpactSelection({ format: 'isthmus-changes', version: 1,\n files: file === undefined ? [] : [file], symbols: symbol === undefined ? [] : [symbol] });\n } catch (error) {\n if (error instanceof ImpactSelectionValidationError) return usageError();\n throw error;\n }\n }\n let runtime: ImpactRuntimeInput | undefined;\n if (runtimePath !== undefined && revision !== undefined) {\n const loaded = await readSupplemental(runtimePath, readTextFile, 'runtime', parseBridgeRuntime);\n if ('exitCode' in loaded) return loaded;\n supplementalTextLength += loaded.textLength;\n runtime = { document: loaded.document, revision };\n }\n const documents = await readBridgeDocuments(parsed.positionals, readTextFile, supplementalTextLength);\n const report = createBridgeImpact(documents, selection, runtime);\n const blocked = parsed.booleanFlags.has('--strict') && hasImpactBlockers(report);\n return {\n standardOutput: encodeBridgeImpact(report, parsed.booleanFlags.has('--compact')),\n standardError: blocked\n ? 'Change preflight requires review: related errors, analysis gaps, or unobserved selectors remain.\\n'\n : report.status === 'unobserved'\n ? 'No selected bridge facts were observed; inspect source coverage and use a pre-change snapshot for deleted code.\\n'\n : '',\n exitCode: blocked ? 1 : 0,\n };\n } catch (error) {\n if (error instanceof RuntimeValidationError) return inputFailure(`${error.message}\\n`);\n return inputFailureResult(error) ?? internalError();\n }\n}\n\n/** 변경 파일도 브리지 입력과 같은 읽기·구문·계약 실패 경계를 지킨다. */\nasync function readSupplemental<T>(\n path: string, readTextFile: ReadTextFile, kind: 'changes' | 'runtime', parse: (value: unknown) => T,\n): Promise<\n { document: T; textLength: number } | CommandResult\n> {\n const label = kind === 'changes' ? 'Changes' : 'Runtime';\n const format = kind === 'changes' ? 'isthmus-changes' : 'bridge-runtime';\n let text: string;\n try { text = await readTextFile(path); }\n catch { return inputFailure(`Unable to read ${kind} input; check that the file exists and is readable.\\n`); }\n if (text.length > MAX_INPUT_TEXT_LENGTH) return inputFailure(`${label} input exceeds the input size limit.\\n`);\n let value: unknown;\n try { value = JSON.parse(text); }\n catch (error) {\n if (isJsonParseFailure(error)) return inputFailure(`${label} input is not valid JSON.\\n`);\n throw error;\n }\n try { return { document: parse(value), textLength: text.length }; }\n catch (error) {\n if (error instanceof ImpactSelectionValidationError || error instanceof RuntimeValidationError) {\n return inputFailure(`${label} input violates the ${format} contract: ${error.message}\\n`);\n }\n throw error;\n }\n}\n\n/** 잘못된 인수는 입력을 읽기 전에 코드 64로 거부한다. */\nfunction usageError(): CommandResult {\n return { standardOutput: '', standardError: `${impactUsage}\\n`, exitCode: 64 };\n}\n\n/** impact 명령의 독립적인 선택 방식과 CI 실패 옵션이다. */\nexport const impactUsage = 'Usage: isthmus impact (--file <relative-path> | --symbol <name-or-usr> '\n + '| --changes <changes.json>) <bridge-facts.json> <bridge-facts.json> [more...] '\n + '[--runtime <runtime.json> --revision <revision>] [--strict] [--compact]';\n"]}
|
package/dist/cli/main.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
|
-
import { checkUsage, runCheckCommand, } from "./check-command.js";
|
|
4
3
|
import { writeTextAtomically } from "./atomic-write.js";
|
|
4
|
+
import { checkUsage, runCheckCommand } from "./check-command.js";
|
|
5
5
|
import { graphUsage, runGraphCommand } from "./graph-command.js";
|
|
6
6
|
import { diffUsage, runDiffCommand } from "./diff-command.js";
|
|
7
7
|
import { queryUsage, runQueryCommand } from "./query-command.js";
|
|
8
|
+
import { impactUsage, runImpactCommand } from "./impact-command.js";
|
|
9
|
+
import { runtimeUsage, runRuntimeCommand } from "./runtime-command.js";
|
|
10
|
+
import { preflightUsage, runPreflightCommand } from "./preflight-command.js";
|
|
8
11
|
import { retentionUsage, runRetentionsCommand, } from "./retentions-command.js";
|
|
9
12
|
process.stdout.on('error', handleStreamError);
|
|
10
13
|
process.stderr.on('error', handleStreamError);
|
|
@@ -13,6 +16,9 @@ const commandUsages = new Map([
|
|
|
13
16
|
['graph', graphUsage],
|
|
14
17
|
['diff', diffUsage],
|
|
15
18
|
['query', queryUsage],
|
|
19
|
+
['impact', impactUsage],
|
|
20
|
+
['preflight', preflightUsage],
|
|
21
|
+
['verify-runtime', runtimeUsage],
|
|
16
22
|
['retentions', retentionUsage],
|
|
17
23
|
]);
|
|
18
24
|
const rootHelp = `Usage: isthmus <command> [options]
|
|
@@ -20,12 +26,16 @@ const rootHelp = `Usage: isthmus <command> [options]
|
|
|
20
26
|
Commands:
|
|
21
27
|
check Report unmatched bridge calls and handlers
|
|
22
28
|
query Find both sides of a channel or method
|
|
29
|
+
impact Inspect bridge dependencies before changing files or symbols
|
|
30
|
+
preflight Trace cross-language impact from producer analysis context
|
|
31
|
+
verify-runtime Verify recorded calls against scenario expectations
|
|
23
32
|
graph Render matched boundary edges
|
|
24
33
|
diff Compare bridge observations before and after a change
|
|
25
34
|
retentions Produce external retention evidence
|
|
35
|
+
help Show command help
|
|
26
36
|
|
|
27
37
|
Options:
|
|
28
|
-
-h, --help Show help
|
|
38
|
+
-h, --help Show help; after a command it shows that command's usage
|
|
29
39
|
--version Show version
|
|
30
40
|
`;
|
|
31
41
|
const arguments_ = process.argv.slice(2);
|
|
@@ -46,22 +56,28 @@ function handleStreamError(error) {
|
|
|
46
56
|
}
|
|
47
57
|
/** 도움말과 버전처럼 입력 문서가 필요 없는 명령을 실행한다. */
|
|
48
58
|
async function runInformationalCommand(commandArguments) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return
|
|
59
|
+
// 도움말 플래그는 어느 위치에 있든 이긴다. 긴 호출 끝에 붙여도 사용법을 본다.
|
|
60
|
+
const helpFlagIndex = commandArguments.findIndex((argument) => argument === '--help' || argument === '-h');
|
|
61
|
+
if (helpFlagIndex >= 0)
|
|
62
|
+
return commandHelp(commandArguments[0]);
|
|
63
|
+
if (commandArguments[0] === 'help') {
|
|
64
|
+
return commandHelp(commandArguments[1]);
|
|
53
65
|
}
|
|
54
|
-
if (commandArguments.length === 1 &&
|
|
66
|
+
if (commandArguments.length === 1 &&
|
|
67
|
+
commandArguments[0] === '--version') {
|
|
55
68
|
return readVersion();
|
|
56
69
|
}
|
|
57
|
-
if (commandArguments.length === 2 &&
|
|
58
|
-
(commandArguments[1] === '--help' || commandArguments[1] === '-h')) {
|
|
59
|
-
const usage = first === undefined ? undefined : commandUsages.get(first);
|
|
60
|
-
if (usage !== undefined)
|
|
61
|
-
return successfulText(`${usage}\n`);
|
|
62
|
-
}
|
|
63
70
|
return undefined;
|
|
64
71
|
}
|
|
72
|
+
/** 명령 이름에 맞는 사용법을, 모르면 루트 도움말을 돌려준다. */
|
|
73
|
+
function commandHelp(command) {
|
|
74
|
+
if (command === undefined)
|
|
75
|
+
return successfulText(rootHelp);
|
|
76
|
+
const usage = commandUsages.get(command);
|
|
77
|
+
return usage === undefined
|
|
78
|
+
? successfulText(rootHelp)
|
|
79
|
+
: successfulText(`${usage}\n`);
|
|
80
|
+
}
|
|
65
81
|
/** 알려진 분석 하위 명령으로만 라우팅한다. */
|
|
66
82
|
async function dispatchCommand(commandArguments) {
|
|
67
83
|
switch (commandArguments[0]) {
|
|
@@ -74,6 +90,12 @@ async function dispatchCommand(commandArguments) {
|
|
|
74
90
|
return runDiffCommand(commandArguments, readTextFile);
|
|
75
91
|
case 'query':
|
|
76
92
|
return runQueryCommand(commandArguments, readTextFile);
|
|
93
|
+
case 'impact':
|
|
94
|
+
return runImpactCommand(commandArguments, readTextFile);
|
|
95
|
+
case 'preflight':
|
|
96
|
+
return runPreflightCommand(commandArguments, readTextFile);
|
|
97
|
+
case 'verify-runtime':
|
|
98
|
+
return runRuntimeCommand(commandArguments, readTextFile);
|
|
77
99
|
case 'retentions': {
|
|
78
100
|
const version = await readPackageVersion();
|
|
79
101
|
return version === undefined
|
package/dist/cli/main.js.map
CHANGED
|
@@ -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 { writeTextAtomically } from './atomic-write.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 writeTextFile = (path: string, text: string) =>\n writeTextAtomically(path, text);\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 // SARIF driver에만 필요하므로 메타데이터 부재는 json 보고서를 막지 않는다.\n return runCheckCommand(\n commandArguments,\n readTextFile,\n writeTextFile,\n undefined,\n await readPackageVersion(),\n );\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"]}
|
|
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 { writeTextAtomically } from './atomic-write.ts';\nimport { checkUsage, runCheckCommand } from './check-command.ts';\nimport type { CommandResult } from './command-support.ts';\nimport { graphUsage, runGraphCommand } from './graph-command.ts';\nimport { diffUsage, runDiffCommand } from './diff-command.ts';\nimport { queryUsage, runQueryCommand } from './query-command.ts';\nimport { impactUsage, runImpactCommand } from './impact-command.ts';\nimport { runtimeUsage, runRuntimeCommand } from './runtime-command.ts';\nimport { preflightUsage, runPreflightCommand } from './preflight-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 ['impact', impactUsage],\n ['preflight', preflightUsage],\n ['verify-runtime', runtimeUsage],\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 impact Inspect bridge dependencies before changing files or symbols\n preflight Trace cross-language impact from producer analysis context\n verify-runtime Verify recorded calls against scenario expectations\n graph Render matched boundary edges\n diff Compare bridge observations before and after a change\n retentions Produce external retention evidence\n help Show command help\n\nOptions:\n -h, --help Show help; after a command it shows that command's usage\n --version Show version\n`;\n\nconst arguments_ = process.argv.slice(2);\nconst readTextFile = (path: string) => readFile(path, 'utf8');\nconst writeTextFile = (path: string, text: string) =>\n writeTextAtomically(path, text);\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 // 도움말 플래그는 어느 위치에 있든 이긴다. 긴 호출 끝에 붙여도 사용법을 본다.\n const helpFlagIndex = commandArguments.findIndex(\n (argument) => argument === '--help' || argument === '-h',\n );\n if (helpFlagIndex >= 0) return commandHelp(commandArguments[0]);\n if (commandArguments[0] === 'help') {\n return commandHelp(commandArguments[1]);\n }\n if (\n commandArguments.length === 1 &&\n commandArguments[0] === '--version'\n ) {\n return readVersion();\n }\n return undefined;\n}\n\n/** 명령 이름에 맞는 사용법을, 모르면 루트 도움말을 돌려준다. */\nfunction commandHelp(command: string | undefined): CommandResult {\n if (command === undefined) return successfulText(rootHelp);\n const usage = commandUsages.get(command);\n return usage === undefined\n ? successfulText(rootHelp)\n : successfulText(`${usage}\\n`);\n}\n\n/** 알려진 분석 하위 명령으로만 라우팅한다. */\nasync function dispatchCommand(\n commandArguments: readonly string[],\n): Promise<CommandResult> {\n switch (commandArguments[0]) {\n case 'check':\n // SARIF driver에만 필요하므로 메타데이터 부재는 json 보고서를 막지 않는다.\n return runCheckCommand(\n commandArguments,\n readTextFile,\n writeTextFile,\n undefined,\n await readPackageVersion(),\n );\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 'impact':\n return runImpactCommand(commandArguments, readTextFile);\n case 'preflight':\n return runPreflightCommand(commandArguments, readTextFile);\n case 'verify-runtime':\n return runRuntimeCommand(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,18 @@
|
|
|
1
|
+
/** 플래그와 위치 인수로 나뉜 명령 인수다. */
|
|
2
|
+
export interface ParsedCommandArguments {
|
|
3
|
+
/** 값 플래그 이름과 그 값이다. 같은 플래그를 두 번 주면 사용법 오류다. */
|
|
4
|
+
readonly valueFlags: ReadonlyMap<string, string>;
|
|
5
|
+
/** 값 없는 플래그 이름들이다. 반복은 한 번과 같다. */
|
|
6
|
+
readonly booleanFlags: ReadonlySet<string>;
|
|
7
|
+
/** 플래그가 아닌 인수다. `--` 뒤는 `-`로 시작해도 모두 여기에 담긴다. */
|
|
8
|
+
readonly positionals: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 모든 분석 하위 명령이 같은 규칙으로 인수를 읽게 하는 공유 파서다.
|
|
12
|
+
*
|
|
13
|
+
* 플래그는 위치 인수 앞뒤 어디에 와도 된다. 값 플래그의 값은 비어 있지 않고
|
|
14
|
+
* `-`로 시작하지 않아야 한다. `--` 이후는 모두 위치 인수로 읽어 `-`로
|
|
15
|
+
* 시작하는 경로·이름도 전달할 수 있다. 모르는 플래그, 중복 값 플래그, 빈
|
|
16
|
+
* 플래그 값은 사용법 오류다(undefined).
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseCommandArguments(arguments_: readonly string[], valueFlagNames: readonly string[], booleanFlagNames: readonly string[]): ParsedCommandArguments | undefined;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 모든 분석 하위 명령이 같은 규칙으로 인수를 읽게 하는 공유 파서다.
|
|
3
|
+
*
|
|
4
|
+
* 플래그는 위치 인수 앞뒤 어디에 와도 된다. 값 플래그의 값은 비어 있지 않고
|
|
5
|
+
* `-`로 시작하지 않아야 한다. `--` 이후는 모두 위치 인수로 읽어 `-`로
|
|
6
|
+
* 시작하는 경로·이름도 전달할 수 있다. 모르는 플래그, 중복 값 플래그, 빈
|
|
7
|
+
* 플래그 값은 사용법 오류다(undefined).
|
|
8
|
+
*/
|
|
9
|
+
export function parseCommandArguments(arguments_, valueFlagNames, booleanFlagNames) {
|
|
10
|
+
const valueFlags = new Set(valueFlagNames);
|
|
11
|
+
const booleanFlags = new Set(booleanFlagNames);
|
|
12
|
+
const values = new Map();
|
|
13
|
+
const booleans = new Set();
|
|
14
|
+
const positionals = [];
|
|
15
|
+
let onlyPositionals = false;
|
|
16
|
+
for (let index = 0; index < arguments_.length; index++) {
|
|
17
|
+
const argument = arguments_[index];
|
|
18
|
+
if (argument === undefined)
|
|
19
|
+
return undefined;
|
|
20
|
+
if (onlyPositionals || !argument.startsWith('-')) {
|
|
21
|
+
positionals.push(argument);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (argument === '--') {
|
|
25
|
+
onlyPositionals = true;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (valueFlags.has(argument)) {
|
|
29
|
+
if (values.has(argument))
|
|
30
|
+
return undefined;
|
|
31
|
+
const value = arguments_[index + 1];
|
|
32
|
+
// 빈 값은 경로가 아니라 호출 오류다. 읽기 실패(코드 2)보다 사용법(64)이 맞다.
|
|
33
|
+
if (value === undefined || value.length === 0 || value.startsWith('-')) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
values.set(argument, value);
|
|
37
|
+
index++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (booleanFlags.has(argument)) {
|
|
41
|
+
booleans.add(argument);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
return { valueFlags: values, booleanFlags: booleans, positionals };
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=parse-arguments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-arguments.js","sourceRoot":"","sources":["../../src/cli/parse-arguments.ts"],"names":[],"mappings":"AAUA;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAA6B,EAC7B,cAAiC,EACjC,gBAAmC;IAEnC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,IAAI,eAAe,GAAG,KAAK,CAAC;IAC5B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACvD,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAC7C,IAAI,eAAe,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACjD,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC3B,SAAS;QACX,CAAC;QACD,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtB,eAAe,GAAG,IAAI,CAAC;YACvB,SAAS;QACX,CAAC;QACD,IAAI,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7B,IAAI,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;gBAAE,OAAO,SAAS,CAAC;YAC3C,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;YACpC,kDAAkD;YAClD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACvE,OAAO,SAAS,CAAC;YACnB,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YAC5B,KAAK,EAAE,CAAC;YACR,SAAS;QACX,CAAC;QACD,IAAI,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/B,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACvB,SAAS;QACX,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AACrE,CAAC","sourcesContent":["/** 플래그와 위치 인수로 나뉜 명령 인수다. */\nexport interface ParsedCommandArguments {\n /** 값 플래그 이름과 그 값이다. 같은 플래그를 두 번 주면 사용법 오류다. */\n readonly valueFlags: ReadonlyMap<string, string>;\n /** 값 없는 플래그 이름들이다. 반복은 한 번과 같다. */\n readonly booleanFlags: ReadonlySet<string>;\n /** 플래그가 아닌 인수다. `--` 뒤는 `-`로 시작해도 모두 여기에 담긴다. */\n readonly positionals: readonly string[];\n}\n\n/**\n * 모든 분석 하위 명령이 같은 규칙으로 인수를 읽게 하는 공유 파서다.\n *\n * 플래그는 위치 인수 앞뒤 어디에 와도 된다. 값 플래그의 값은 비어 있지 않고\n * `-`로 시작하지 않아야 한다. `--` 이후는 모두 위치 인수로 읽어 `-`로\n * 시작하는 경로·이름도 전달할 수 있다. 모르는 플래그, 중복 값 플래그, 빈\n * 플래그 값은 사용법 오류다(undefined).\n */\nexport function parseCommandArguments(\n arguments_: readonly string[],\n valueFlagNames: readonly string[],\n booleanFlagNames: readonly string[],\n): ParsedCommandArguments | undefined {\n const valueFlags = new Set(valueFlagNames);\n const booleanFlags = new Set(booleanFlagNames);\n const values = new Map<string, string>();\n const booleans = new Set<string>();\n const positionals: string[] = [];\n let onlyPositionals = false;\n for (let index = 0; index < arguments_.length; index++) {\n const argument = arguments_[index];\n if (argument === undefined) return undefined;\n if (onlyPositionals || !argument.startsWith('-')) {\n positionals.push(argument);\n continue;\n }\n if (argument === '--') {\n onlyPositionals = true;\n continue;\n }\n if (valueFlags.has(argument)) {\n if (values.has(argument)) return undefined;\n const value = arguments_[index + 1];\n // 빈 값은 경로가 아니라 호출 오류다. 읽기 실패(코드 2)보다 사용법(64)이 맞다.\n if (value === undefined || value.length === 0 || value.startsWith('-')) {\n return undefined;\n }\n values.set(argument, value);\n index++;\n continue;\n }\n if (booleanFlags.has(argument)) {\n booleans.add(argument);\n continue;\n }\n return undefined;\n }\n return { valueFlags: values, booleanFlags: booleans, positionals };\n}\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CommandResult, ReadTextFile } from './command-support.ts';
|
|
2
|
+
/** 같은 실행 문맥의 언어별 영향 결과를 연결해 사람이거나 AI인 소비자에게 전달한다. */
|
|
3
|
+
export declare function runPreflightCommand(arguments_: readonly string[], readTextFile: ReadTextFile): Promise<CommandResult>;
|
|
4
|
+
/** 입력 생성은 producer workflow가 맡으며 CLI는 JSON만 읽는다. */
|
|
5
|
+
export declare const preflightUsage: string;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { isSafeNonEmptyString } from "../exchange/parse.js";
|
|
2
|
+
import { parsePreflightContext, PreflightValidationError } from "../exchange/preflight-context.js";
|
|
3
|
+
import { parseBridgeRuntime, parseRuntimeExpectations, RuntimeValidationError } from "../exchange/runtime.js";
|
|
4
|
+
import { MAX_DOCUMENTS_PER_JOIN } from "../join/join.js";
|
|
5
|
+
import { createPreflightReport, hasPreflightBlockers, PreflightGraphError } from "../report/preflight.js";
|
|
6
|
+
import { attachPreflightRuntime } from "../report/preflight-runtime.js";
|
|
7
|
+
import { createPreflightExplanation, createPreflightSummary } from "../report/preflight-view.js";
|
|
8
|
+
import { encodeSortedJson } from "../report/sorted-json.js";
|
|
9
|
+
import { inputFailure, inputFailureResult, internalError, isJsonParseFailure, MAX_TOTAL_INPUT_TEXT_LENGTH } from "./command-support.js";
|
|
10
|
+
import { parseCommandArguments } from "./parse-arguments.js";
|
|
11
|
+
import { RuntimeInputError, RuntimeJsonReader } from "./runtime-json-reader.js";
|
|
12
|
+
/** 같은 실행 문맥의 언어별 영향 결과를 연결해 사람이거나 AI인 소비자에게 전달한다. */
|
|
13
|
+
export async function runPreflightCommand(arguments_, readTextFile) {
|
|
14
|
+
const parsed = parseCommandArguments(arguments_.slice(1), ['--revision', '--expectations', '--limit', '--explain'], ['--strict', '--compact', '--summary']);
|
|
15
|
+
const revision = parsed?.valueFlags.get('--revision');
|
|
16
|
+
const expectedPath = parsed?.valueFlags.get('--expectations');
|
|
17
|
+
const summaryRequested = parsed?.booleanFlags.has('--summary') ?? false;
|
|
18
|
+
const explainSelector = parsed?.valueFlags.get('--explain');
|
|
19
|
+
const limitText = parsed?.valueFlags.get('--limit');
|
|
20
|
+
const limit = limitText === undefined ? undefined : Number(limitText);
|
|
21
|
+
if (parsed === undefined || parsed.positionals.length < 1 || parsed.positionals.length > MAX_DOCUMENTS_PER_JOIN + 1 ||
|
|
22
|
+
(expectedPath === undefined && parsed.positionals.length !== 1) ||
|
|
23
|
+
(revision !== undefined && !isSafeNonEmptyString(revision)) ||
|
|
24
|
+
(summaryRequested && explainSelector !== undefined) || (!summaryRequested && limitText !== undefined) ||
|
|
25
|
+
(limitText !== undefined && (!Number.isSafeInteger(limit) || limit < 1 || limit > 100))) {
|
|
26
|
+
return { standardOutput: '', standardError: `${preflightUsage}\n`, exitCode: 64 };
|
|
27
|
+
}
|
|
28
|
+
let text;
|
|
29
|
+
try {
|
|
30
|
+
text = await readTextFile(parsed.positionals[0]);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return inputFailure('Unable to read preflight context; check that the file exists and is readable.\n');
|
|
34
|
+
}
|
|
35
|
+
if (text.length > MAX_TOTAL_INPUT_TEXT_LENGTH)
|
|
36
|
+
return inputFailure('Preflight context exceeds the input size limit.\n');
|
|
37
|
+
let value;
|
|
38
|
+
try {
|
|
39
|
+
value = JSON.parse(text);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
return isJsonParseFailure(error) ? inputFailure('Preflight context is not valid JSON.\n') : internalError();
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const context = parsePreflightContext(value);
|
|
46
|
+
let report = createPreflightReport(revision === undefined || revision === context.revision ? context : {
|
|
47
|
+
...context, limitations: [...context.limitations, 'stale-preflight-context: capture revision differs from the requested revision'],
|
|
48
|
+
});
|
|
49
|
+
if (expectedPath !== undefined) {
|
|
50
|
+
const reader = new RuntimeJsonReader(readTextFile, text.length);
|
|
51
|
+
const expectations = parseRuntimeExpectations(await reader.read(expectedPath));
|
|
52
|
+
const documents = [];
|
|
53
|
+
for (const path of parsed.positionals.slice(1))
|
|
54
|
+
documents.push(parseBridgeRuntime(await reader.read(path)));
|
|
55
|
+
report = attachPreflightRuntime(context, report, expectations, documents);
|
|
56
|
+
}
|
|
57
|
+
const blocked = parsed.booleanFlags.has('--strict') && hasPreflightBlockers(report);
|
|
58
|
+
const view = summaryRequested
|
|
59
|
+
? createPreflightSummary(report, limit ?? 20)
|
|
60
|
+
: explainSelector === undefined ? report : createPreflightExplanation(report, explainSelector);
|
|
61
|
+
const viewStatus = explainSelector === undefined ? 'found' : view.status;
|
|
62
|
+
const statusHint = explainSelector === undefined || viewStatus === 'found' ? ''
|
|
63
|
+
: viewStatus === 'ambiguous'
|
|
64
|
+
? 'The requested preflight selector matches multiple subjects; repeat with an exact subject key or producer id.\n'
|
|
65
|
+
: 'No preflight subject matches the requested selector; query an exact subject key, producer id, or qualifiedName.\n';
|
|
66
|
+
const exitCode = viewStatus === 'found' ? blocked ? 1 : 0 : 64;
|
|
67
|
+
return { standardOutput: encodeSortedJson(view, parsed.booleanFlags.has('--compact')),
|
|
68
|
+
standardError: viewStatus === 'found' && blocked
|
|
69
|
+
? 'Cross-language preflight requires review: errors, evidence gaps, or unobserved selections remain.\n'
|
|
70
|
+
: statusHint, exitCode };
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (error instanceof PreflightValidationError || error instanceof PreflightGraphError ||
|
|
74
|
+
error instanceof RuntimeValidationError || error instanceof RuntimeInputError) {
|
|
75
|
+
return inputFailure(`Preflight context violates its contract: ${error.message}\n`);
|
|
76
|
+
}
|
|
77
|
+
return inputFailureResult(error) ?? internalError();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** 입력 생성은 producer workflow가 맡으며 CLI는 JSON만 읽는다. */
|
|
81
|
+
export const preflightUsage = 'Usage: isthmus preflight <context.json> [<runtime.json> ...] '
|
|
82
|
+
+ '[--expectations <checks.json>] [--revision <revision>] [--summary [--limit <1..100>] | --explain <selector>] [--strict] [--compact]';
|
|
83
|
+
//# sourceMappingURL=preflight-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preflight-command.js","sourceRoot":"","sources":["../../src/cli/preflight-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { isSafeNonEmptyString } from '../exchange/parse.ts';\nimport { parsePreflightContext, PreflightValidationError } from '../exchange/preflight-context.ts';\nimport { parseBridgeRuntime, parseRuntimeExpectations, RuntimeValidationError } from '../exchange/runtime.ts';\nimport { MAX_DOCUMENTS_PER_JOIN } from '../join/join.ts';\nimport { createPreflightReport, hasPreflightBlockers, PreflightGraphError } from '../report/preflight.ts';\nimport { attachPreflightRuntime } from '../report/preflight-runtime.ts';\nimport { createPreflightExplanation, createPreflightSummary } from '../report/preflight-view.ts';\nimport { encodeSortedJson } from '../report/sorted-json.ts';\nimport { inputFailure, inputFailureResult, internalError, isJsonParseFailure, MAX_TOTAL_INPUT_TEXT_LENGTH } from './command-support.ts';\nimport type { CommandResult, ReadTextFile } from './command-support.ts';\nimport { parseCommandArguments } from './parse-arguments.ts';\nimport { RuntimeInputError, RuntimeJsonReader } from './runtime-json-reader.ts';\n\n/** 같은 실행 문맥의 언어별 영향 결과를 연결해 사람이거나 AI인 소비자에게 전달한다. */\nexport async function runPreflightCommand(arguments_: readonly string[], readTextFile: ReadTextFile): Promise<CommandResult> {\n const parsed = parseCommandArguments(arguments_.slice(1), ['--revision', '--expectations', '--limit', '--explain'], ['--strict', '--compact', '--summary']);\n const revision = parsed?.valueFlags.get('--revision');\n const expectedPath = parsed?.valueFlags.get('--expectations');\n const summaryRequested = parsed?.booleanFlags.has('--summary') ?? false;\n const explainSelector = parsed?.valueFlags.get('--explain');\n const limitText = parsed?.valueFlags.get('--limit');\n const limit = limitText === undefined ? undefined : Number(limitText);\n if (parsed === undefined || parsed.positionals.length < 1 || parsed.positionals.length > MAX_DOCUMENTS_PER_JOIN + 1 ||\n (expectedPath === undefined && parsed.positionals.length !== 1) ||\n (revision !== undefined && !isSafeNonEmptyString(revision)) ||\n (summaryRequested && explainSelector !== undefined) || (!summaryRequested && limitText !== undefined) ||\n (limitText !== undefined && (!Number.isSafeInteger(limit) || limit! < 1 || limit! > 100))) {\n return { standardOutput: '', standardError: `${preflightUsage}\\n`, exitCode: 64 };\n }\n let text: string;\n try { text = await readTextFile(parsed.positionals[0]!); }\n catch { return inputFailure('Unable to read preflight context; check that the file exists and is readable.\\n'); }\n if (text.length > MAX_TOTAL_INPUT_TEXT_LENGTH) return inputFailure('Preflight context exceeds the input size limit.\\n');\n let value: unknown;\n try { value = JSON.parse(text); }\n catch (error) {\n return isJsonParseFailure(error) ? inputFailure('Preflight context is not valid JSON.\\n') : internalError();\n }\n try {\n const context = parsePreflightContext(value);\n let report = createPreflightReport(revision === undefined || revision === context.revision ? context : {\n ...context, limitations: [...context.limitations, 'stale-preflight-context: capture revision differs from the requested revision'],\n });\n if (expectedPath !== undefined) {\n const reader = new RuntimeJsonReader(readTextFile, text.length);\n const expectations = parseRuntimeExpectations(await reader.read(expectedPath));\n const documents = [];\n for (const path of parsed.positionals.slice(1)) documents.push(parseBridgeRuntime(await reader.read(path)));\n report = attachPreflightRuntime(context, report, expectations, documents);\n }\n const blocked = parsed.booleanFlags.has('--strict') && hasPreflightBlockers(report);\n const view = summaryRequested\n ? createPreflightSummary(report, limit ?? 20)\n : explainSelector === undefined ? report : createPreflightExplanation(report, explainSelector);\n const viewStatus = explainSelector === undefined ? 'found' : view.status;\n const statusHint = explainSelector === undefined || viewStatus === 'found' ? ''\n : viewStatus === 'ambiguous'\n ? 'The requested preflight selector matches multiple subjects; repeat with an exact subject key or producer id.\\n'\n : 'No preflight subject matches the requested selector; query an exact subject key, producer id, or qualifiedName.\\n';\n const exitCode = viewStatus === 'found' ? blocked ? 1 : 0 : 64;\n return { standardOutput: encodeSortedJson(view, parsed.booleanFlags.has('--compact')),\n standardError: viewStatus === 'found' && blocked\n ? 'Cross-language preflight requires review: errors, evidence gaps, or unobserved selections remain.\\n'\n : statusHint, exitCode };\n } catch (error) {\n if (error instanceof PreflightValidationError || error instanceof PreflightGraphError ||\n error instanceof RuntimeValidationError || error instanceof RuntimeInputError) {\n return inputFailure(`Preflight context violates its contract: ${error.message}\\n`);\n }\n return inputFailureResult(error) ?? internalError();\n }\n}\n\n/** 입력 생성은 producer workflow가 맡으며 CLI는 JSON만 읽는다. */\nexport const preflightUsage = 'Usage: isthmus preflight <context.json> [<runtime.json> ...] '\n + '[--expectations <checks.json>] [--revision <revision>] [--summary [--limit <1..100>] | --explain <selector>] [--strict] [--compact]';\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CommandResult, type ReadTextFile } from './
|
|
1
|
+
import { type CommandResult, type ReadTextFile } from './command-support.ts';
|
|
2
2
|
/** query 인자를 실행해 bridge 질의 JSON과 종료 코드를 반환한다. */
|
|
3
3
|
export declare function runQueryCommand(arguments_: readonly string[], readTextFile: ReadTextFile): Promise<CommandResult>;
|
|
4
4
|
/** query 명령의 한 줄 사용법이다. */
|