@mcpeak/cli 0.10.0 → 0.12.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/LICENSE +1 -1
- package/README.md +5 -0
- package/dist/cli.cjs +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/commands.cjs +2 -3
- package/dist/commands.d.cts +28 -6
- package/dist/commands.d.mts +28 -6
- package/dist/commands.mjs +2 -2
- package/dist/index.cjs +219 -5
- package/dist/index.d.cts +3 -14
- package/dist/index.d.mts +3 -14
- package/dist/index.mjs +217 -2
- package/dist/{replay-command-BAtSzf8R.d.mts → repair-command-8GUlJJ47.d.mts} +91 -49
- package/dist/{replay-command-V0ZSJPiD.d.cts → repair-command-CVFqxU6V.d.cts} +91 -49
- package/dist/{test-command-BbjJIX7j.cjs → test-command-1m3myzTS.cjs} +2516 -1151
- package/dist/{test-command-CtQbeyhy.mjs → test-command-P8_uaAIy.mjs} +2509 -1138
- package/package.json +7 -6
- package/dist/src-BzVS8KXQ.mjs +0 -459
- package/dist/src-CwhDFSWk.cjs +0 -482
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,217 @@
|
|
|
1
|
-
import { i as
|
|
2
|
-
|
|
1
|
+
import { c as runGenerateCommand, d as commandHelp, f as escapeTerminalText, i as runRepairCommand, l as GLOBAL_HELP, n as runCli, o as nodeGenerateDependencies, p as version, s as nodeReviewIO, t as parseTestCommand, u as commandDiscovery } from "./test-command-P8_uaAIy.mjs";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
//#region src/index.ts
|
|
4
|
+
const COMMANDS = [
|
|
5
|
+
"test",
|
|
6
|
+
"generate",
|
|
7
|
+
"repair",
|
|
8
|
+
"record",
|
|
9
|
+
"mock"
|
|
10
|
+
];
|
|
11
|
+
const unavailableDependencies = {
|
|
12
|
+
readFile: async () => {
|
|
13
|
+
throw new Error("runtime dependencies unavailable");
|
|
14
|
+
},
|
|
15
|
+
validateSuite: () => {
|
|
16
|
+
throw new Error("runtime dependencies unavailable");
|
|
17
|
+
},
|
|
18
|
+
connect: async () => {
|
|
19
|
+
throw new Error("runtime dependencies unavailable");
|
|
20
|
+
},
|
|
21
|
+
startRunner: () => {
|
|
22
|
+
throw new Error("runtime dependencies unavailable");
|
|
23
|
+
},
|
|
24
|
+
finalize: async () => {
|
|
25
|
+
throw new Error("runtime dependencies unavailable");
|
|
26
|
+
},
|
|
27
|
+
renderReport: () => {
|
|
28
|
+
throw new Error("runtime dependencies unavailable");
|
|
29
|
+
},
|
|
30
|
+
renderJUnit: () => {
|
|
31
|
+
throw new Error("runtime dependencies unavailable");
|
|
32
|
+
},
|
|
33
|
+
writeFile: async () => {
|
|
34
|
+
throw new Error("runtime dependencies unavailable");
|
|
35
|
+
},
|
|
36
|
+
colorEnabled: false,
|
|
37
|
+
writeStdout: (text) => process.stdout.write(text),
|
|
38
|
+
writeStderr: (text) => process.stderr.write(text)
|
|
39
|
+
};
|
|
40
|
+
const unavailableRuntimeDependencies = {
|
|
41
|
+
...unavailableDependencies,
|
|
42
|
+
readFile
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* `repair` 실행 의존성. 함수로 빼 둔 이유는 **주입 자체를 테스트가 단언할 수 있게** 하기
|
|
46
|
+
* 위해서다. 분기 안에 리터럴로 두면 `reviewIO` 를 빠뜨려도 아무 테스트도 안 깨진다.
|
|
47
|
+
* 실제로 그렇게 한 번 빠뜨렸다(T10 보고서).
|
|
48
|
+
*
|
|
49
|
+
* 대화형 판정은 `generate` 와 같은 기준이다. 같은 `nodeReviewIO()` 를 쓰고, 그 구현이
|
|
50
|
+
* stdin·stdout 이 둘 다 TTY 일 때만 `interactive` 를 참으로 만든다.
|
|
51
|
+
*/
|
|
52
|
+
function nodeRepairDependencies(generate) {
|
|
53
|
+
return {
|
|
54
|
+
readFile: (path) => readFile(path, "utf8"),
|
|
55
|
+
writeStdout: (text) => void process.stdout.write(text),
|
|
56
|
+
writeStderr: (text) => void process.stderr.write(text),
|
|
57
|
+
reviewIO: nodeReviewIO(),
|
|
58
|
+
diagnosis: {
|
|
59
|
+
prepare: generate.prepareDiagnosisRequest,
|
|
60
|
+
dispatch: generate.dispatchDiagnosisRequest,
|
|
61
|
+
providers: {
|
|
62
|
+
codex: (model) => generate.createCodexProvider({ model }),
|
|
63
|
+
claude: (model) => generate.createClaudeProvider({ model })
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** `mcpeak help <이름>` 으로 볼 수 있는 명령. 위 두 갈래가 같은 목록을 봐야 한다. */
|
|
69
|
+
const HELP_TOPICS = [
|
|
70
|
+
"test",
|
|
71
|
+
"generate",
|
|
72
|
+
"repair"
|
|
73
|
+
];
|
|
74
|
+
const isHelpTopic = (value) => HELP_TOPICS.includes(value);
|
|
75
|
+
async function run(argv) {
|
|
76
|
+
if (argv.length === 0 || argv.length === 1 && [
|
|
77
|
+
"--help",
|
|
78
|
+
"-h",
|
|
79
|
+
"help"
|
|
80
|
+
].includes(argv[0] ?? "")) {
|
|
81
|
+
process.stdout.write(GLOBAL_HELP);
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
if (argv.length === 2) {
|
|
85
|
+
const command = argv[0] === "help" ? argv[1] : argv[1] === "--help" ? argv[0] : void 0;
|
|
86
|
+
if (command === "replay" || command === "verify") return runCli([command], unavailableDependencies);
|
|
87
|
+
if (isHelpTopic(command)) {
|
|
88
|
+
process.stdout.write(commandHelp(command));
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* `help` 로 시작했는데 위에서 안 걸렸다면 **뒤에 붙은 인자가 틀린 것**이다.
|
|
94
|
+
*
|
|
95
|
+
* 여기서 안 막고 아래로 흘려보내면 `runCli` 가 `argv[0]` 인 `help` 를 명령으로 읽어
|
|
96
|
+
* "알 수 없는 CLI 명령 'help'입니다" 라고 한다. 사용자가 정확히 친 토큰을 틀렸다고
|
|
97
|
+
* 지목하는 셈이라, 무엇을 고쳐야 하는지가 화면에서 사라진다.
|
|
98
|
+
*/
|
|
99
|
+
if (argv[0] === "help") {
|
|
100
|
+
const target = argv[1] ?? "";
|
|
101
|
+
const extra = argv[2];
|
|
102
|
+
const message = isHelpTopic(target) && extra !== void 0 ? `\`help ${escapeTerminalText(target)}\` 뒤의 '${escapeTerminalText(extra)}' 는 받지 않습니다.` : `도움말이 없는 명령 '${escapeTerminalText(target)}'입니다.`;
|
|
103
|
+
process.stderr.write(`오류 [CLI_USAGE]: ${message}\n해결: ${commandDiscovery}\n`);
|
|
104
|
+
return 1;
|
|
105
|
+
}
|
|
106
|
+
if (argv.length === 1 && argv[0] === "--version") {
|
|
107
|
+
process.stdout.write(`mcpeak ${version}\n`);
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
if (argv[0] === "generate") {
|
|
111
|
+
let core;
|
|
112
|
+
let runner;
|
|
113
|
+
let generate;
|
|
114
|
+
try {
|
|
115
|
+
[core, runner, generate] = await Promise.all([
|
|
116
|
+
import("@mcpeak/core"),
|
|
117
|
+
import("@mcpeak/runner"),
|
|
118
|
+
import("@mcpeak/generate")
|
|
119
|
+
]);
|
|
120
|
+
} catch {
|
|
121
|
+
return runGenerateCommand(argv, {
|
|
122
|
+
...nodeGenerateDependencies(),
|
|
123
|
+
connect: unavailableDependencies.connect,
|
|
124
|
+
createBaselineSuite: unavailableDependencies.validateSuite,
|
|
125
|
+
createAuthoringSession: unavailableDependencies.validateSuite,
|
|
126
|
+
finalizeAuthoringDraft: unavailableDependencies.validateSuite,
|
|
127
|
+
getAuthoringExecutionSuite: unavailableDependencies.validateSuite,
|
|
128
|
+
validateSuite: unavailableDependencies.validateSuite
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return runGenerateCommand(argv, {
|
|
132
|
+
...nodeGenerateDependencies(),
|
|
133
|
+
connect: core.connectStdio,
|
|
134
|
+
connectHttp: core.connectHttp,
|
|
135
|
+
readEnv: (name) => process.env[name],
|
|
136
|
+
createBaselineSuite: generate.createBaselineSuite,
|
|
137
|
+
createAuthoringSession: generate.createAuthoringSession,
|
|
138
|
+
finalizeAuthoringDraft: generate.finalizeAuthoringDraft,
|
|
139
|
+
getAuthoringExecutionSuite: generate.getAuthoringExecutionSuite,
|
|
140
|
+
validateSuite: runner.validateMcpSuite,
|
|
141
|
+
reviewIO: nodeReviewIO(),
|
|
142
|
+
providers: {
|
|
143
|
+
codex: (model) => generate.createCodexAuthoringProvider({ model }),
|
|
144
|
+
claude: (model) => generate.createClaudeAuthoringProvider({ model })
|
|
145
|
+
},
|
|
146
|
+
prepareAuthoringRequest: generate.prepareAuthoringRequest,
|
|
147
|
+
dispatchAuthoringRequest: generate.dispatchAuthoringRequest,
|
|
148
|
+
createAuthoringDiff: generate.createAuthoringDiff,
|
|
149
|
+
applyAuthoringChanges: generate.applyAuthoringChanges,
|
|
150
|
+
reviewLocalAuthoringCandidate: generate.reviewLocalAuthoringCandidate,
|
|
151
|
+
computeCoverage: generate.computeCoverage,
|
|
152
|
+
preparePreFillRequest: generate.preparePreFillRequest,
|
|
153
|
+
previewPreFillRequest: generate.previewPreFillRequest,
|
|
154
|
+
dispatchPreFillRequest: generate.dispatchPreFillRequest,
|
|
155
|
+
preFillProviders: {
|
|
156
|
+
codex: (model) => generate.createCodexProvider({ model }),
|
|
157
|
+
claude: (model) => generate.createClaudeProvider({ model })
|
|
158
|
+
},
|
|
159
|
+
prepareRejectionDiagnosisRequests: generate.prepareRejectionDiagnosisRequests,
|
|
160
|
+
dispatchRejectionDiagnosis: generate.dispatchRejectionDiagnosis,
|
|
161
|
+
rejectionProviders: {
|
|
162
|
+
codex: (model) => generate.createCodexProvider({ model }),
|
|
163
|
+
claude: (model) => generate.createClaudeProvider({ model })
|
|
164
|
+
},
|
|
165
|
+
GenerateTestsError: generate.GenerateTestsError
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
if (argv[0] === "repair") {
|
|
169
|
+
/**
|
|
170
|
+
* `generate` 분기와 같은 모양으로 동적 import 한다. `test` 경로는 이 분기를 지나지 않으므로
|
|
171
|
+
* 여전히 `core` 와 `runner` 만 로드한다. 계획서 §8 위험표 첫 줄.
|
|
172
|
+
*/
|
|
173
|
+
let generate;
|
|
174
|
+
try {
|
|
175
|
+
generate = await import("@mcpeak/generate");
|
|
176
|
+
} catch {
|
|
177
|
+
process.stderr.write("오류 [REPAIR_RUNTIME_UNAVAILABLE]: 진단에 필요한 @mcpeak/generate 를 로드하지 못했습니다.\n해결: 의존성을 설치한 뒤 다시 실행하세요.\n");
|
|
178
|
+
return 1;
|
|
179
|
+
}
|
|
180
|
+
const dependencies = nodeRepairDependencies(generate);
|
|
181
|
+
try {
|
|
182
|
+
return await runRepairCommand(argv, dependencies);
|
|
183
|
+
} finally {
|
|
184
|
+
dependencies.reviewIO?.close?.();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (argv[0] !== "test") return runCli(argv, unavailableDependencies);
|
|
188
|
+
try {
|
|
189
|
+
if (!parseTestCommand(argv.slice(1)).suitePath.toLowerCase().endsWith(".json")) return runCli(argv, unavailableDependencies);
|
|
190
|
+
} catch {
|
|
191
|
+
return runCli(argv, unavailableDependencies);
|
|
192
|
+
}
|
|
193
|
+
let core;
|
|
194
|
+
let runner;
|
|
195
|
+
try {
|
|
196
|
+
[core, runner] = await Promise.all([import("@mcpeak/core"), import("@mcpeak/runner")]);
|
|
197
|
+
} catch {
|
|
198
|
+
return runCli(argv, unavailableRuntimeDependencies);
|
|
199
|
+
}
|
|
200
|
+
return runCli(argv, {
|
|
201
|
+
readFile,
|
|
202
|
+
validateSuite: runner.validateMcpSuite,
|
|
203
|
+
connect: core.connectStdio,
|
|
204
|
+
connectHttp: core.connectHttp,
|
|
205
|
+
readEnv: (name) => process.env[name],
|
|
206
|
+
startRunner: runner.runSuite,
|
|
207
|
+
finalize: runner.finalizeRunnerExecution,
|
|
208
|
+
renderReport: runner.renderReport,
|
|
209
|
+
renderJUnit: runner.renderJUnit,
|
|
210
|
+
writeFile: (path, text) => writeFile(path, text, "utf8"),
|
|
211
|
+
colorEnabled: process.stdout.isTTY === true && process.env.NO_COLOR === void 0,
|
|
212
|
+
writeStdout: (text) => process.stdout.write(text),
|
|
213
|
+
writeStderr: (text) => process.stderr.write(text)
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
//#endregion
|
|
217
|
+
export { COMMANDS, nodeRepairDependencies, run };
|
|
@@ -1,38 +1,116 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { McpStdioConnection, ToolDef } from "@mcpeak/core";
|
|
4
|
-
|
|
1
|
+
import { AuthoringExecutionSnapshot, AuthoringSessionView, BaselineGenerationResult, FixtureFile, PreFillProvider } from "@mcpeak/generate";
|
|
2
|
+
import { SuiteValidationResult, TestSuiteSpec } from "@mcpeak/runner";
|
|
3
|
+
import { McpHttpConnection, McpStdioConnection, ToolDef } from "@mcpeak/core";
|
|
4
|
+
//#region src/connect-target.d.ts
|
|
5
|
+
/** 파서가 확정한 연결 대상. */
|
|
6
|
+
type ConnectTarget = {
|
|
7
|
+
readonly transport: "stdio";
|
|
8
|
+
readonly command: string;
|
|
9
|
+
readonly args: readonly string[];
|
|
10
|
+
/**
|
|
11
|
+
* 자식에게 넘길 부모 환경변수의 **이름**. 값은 여기에도 argv 에도 없다(`--header-env` 와
|
|
12
|
+
* 같은 이유). `openConnection` 이 `readEnv` 로 읽어 `env` 에 얹는다.
|
|
13
|
+
*/
|
|
14
|
+
readonly envNames: readonly string[];
|
|
15
|
+
} | {
|
|
16
|
+
readonly transport: "http";
|
|
17
|
+
readonly url: string;
|
|
18
|
+
/**
|
|
19
|
+
* 헤더 이름 → 그 값을 담은 **환경변수 이름**. 값 자체는 여기에도 argv 에도 없다.
|
|
20
|
+
*
|
|
21
|
+
* 평문 `--header 'Authorization: Bearer …'` 를 두지 않은 이유가 이 타입이다. 토큰을
|
|
22
|
+
* argv 에 실으면 `ps` 목록과 셸 히스토리에 그대로 남고, 그건 우리가 만든 노출이다.
|
|
23
|
+
*/
|
|
24
|
+
readonly headerEnv: Readonly<Record<string, string>>;
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/reset-hook.d.ts
|
|
28
|
+
/**
|
|
29
|
+
* 초기화가 실제로 무엇을 했는가. `--reset-cmd` 가 0 으로 끝난 것을 "초기 상태" 라고
|
|
30
|
+
* 부르지 않기 위해 있다. 명령이 무엇을 복원했는지 우리는 모른다(이슈 #399).
|
|
31
|
+
*/
|
|
32
|
+
type ResetGrade =
|
|
33
|
+
/**
|
|
34
|
+
* 새 프로세스·새 연결 + 초기화 명령 성공. 우리가 제공할 수 있는 가장 강한 것.
|
|
35
|
+
*
|
|
36
|
+
* **이번 회차는 이 값을 내지 않는다.** 프로세스를 다시 띄우는 것은 core 의 수명주기를
|
|
37
|
+
* 건드리고, generate 흐름 한가운데서 연결을 바꾸면 그 뒤 단계가 전부 새 client 를 봐야
|
|
38
|
+
* 한다. 값을 미리 정의하는 이유는, commandOnly 를 "초기 상태" 라고 부르지 않으려면
|
|
39
|
+
* 그보다 강한 것이 있다는 사실이 타입에 있어야 하기 때문이다. 죽은 값이 아니라
|
|
40
|
+
* 의도된 자리다. 지우지 마라(설계 §3).
|
|
41
|
+
*/
|
|
42
|
+
"freshProcess" |
|
|
43
|
+
/** 같은 연결에 초기화 명령만 성공. 서버 프로세스의 메모리 상태는 그대로다. */
|
|
44
|
+
"commandOnly" |
|
|
45
|
+
/** 초기화 수단이 없다. 앞 실행이 바꾼 상태가 남아 있다. */
|
|
46
|
+
"none" |
|
|
47
|
+
/** 초기화를 시도했으나 실패했다. 상태를 모른다. */
|
|
48
|
+
"failed";
|
|
49
|
+
//#endregion
|
|
5
50
|
//#region src/generate-command.d.ts
|
|
6
51
|
interface GenerateCommandInput {
|
|
7
52
|
readonly suiteId: string;
|
|
8
53
|
readonly name: string;
|
|
9
54
|
readonly outPath: string;
|
|
10
|
-
|
|
11
|
-
|
|
55
|
+
/**
|
|
56
|
+
* 명세를 뽑을 대상. `--command`/`--arg` 면 stdio, `--url` 이면 Streamable HTTP 다(#137).
|
|
57
|
+
* `command` · `args` 두 필드를 이 하나로 바꾼 근거는 `TestCommandInput.target` 과 같다.
|
|
58
|
+
*/
|
|
59
|
+
readonly target: ConnectTarget;
|
|
12
60
|
readonly baselineOnly: boolean;
|
|
13
61
|
readonly provider?: "codex" | "claude";
|
|
14
62
|
readonly model?: string;
|
|
15
63
|
/** 승인 전 시험 실행 여부. 기본은 실행이고 `--no-dry-run` 이 끈다. 설계 문서 §4.3. */
|
|
16
64
|
readonly dryRun: boolean;
|
|
17
|
-
/** `--cassette` 경로. 없으면 서버를 직접 부른다. */
|
|
18
|
-
readonly cassettePath?: string;
|
|
19
|
-
/** `--record`. 카세트 파일이 있어도 새로 녹화한다. */
|
|
20
|
-
readonly forceRecord: boolean;
|
|
21
65
|
/** `--force`. `--out` 에 파일이 있으면 지우고 새로 쓴다. 설계 문서 §5. */
|
|
22
66
|
readonly force: boolean;
|
|
23
67
|
/** `--reset-cmd`. 시험 실행 직전 1회 실행한다. */
|
|
24
68
|
readonly resetCmd?: string;
|
|
25
69
|
/** 입력값 교정 단계를 돌릴지 여부. 기본은 실행이고 `--no-repair` 가 끈다. 설계 문서 §7. */
|
|
26
70
|
readonly repair: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* 통과한 거절 케이스의 근거를 확인할지 여부. 기본은 꺼짐이고 `--diagnose-rejections` 가 켠다.
|
|
73
|
+
* 끄면 미확인 목록도 AI 진단도 나가지 않고 한 줄 고지만 남는다(ADR-0098 · 설계 §4.2).
|
|
74
|
+
*/
|
|
75
|
+
readonly diagnoseRejections: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* `--fixtures` 로 명시한 픽스처 파일 경로. 없으면 기본 경로(`mcpeak.fixtures.json`)를 본다.
|
|
78
|
+
*
|
|
79
|
+
* "명시했는가" 를 여기서 구분해 두는 이유는 없는 파일의 처리가 다르기 때문이다. 기본
|
|
80
|
+
* 경로는 없는 것이 정상이라 조용히 지나가고, 사용자가 적은 경로가 없는 것은 오타다(#390).
|
|
81
|
+
*/
|
|
82
|
+
readonly fixturesPath?: string;
|
|
27
83
|
}
|
|
28
84
|
interface GenerateCommandDependencies {
|
|
29
85
|
connect(options: {
|
|
30
86
|
command: string;
|
|
31
87
|
args: readonly string[];
|
|
32
88
|
}): Promise<McpStdioConnection>;
|
|
89
|
+
/**
|
|
90
|
+
* 원격(Streamable HTTP) 대상용 연결. `core.connectHttp` 다. 선택 사항으로 두는 근거는
|
|
91
|
+
* `TestCommandDependencies.connectHttp` 와 같다(#137).
|
|
92
|
+
*/
|
|
93
|
+
connectHttp?(options: {
|
|
94
|
+
url: string;
|
|
95
|
+
headers?: Readonly<Record<string, string>>;
|
|
96
|
+
}): Promise<McpHttpConnection>;
|
|
97
|
+
/** `--header-env` 가 가리키는 환경변수를 읽는다. CLI 가 `process` 를 직접 읽지 않기 위한 주입점. */
|
|
98
|
+
readEnv?(name: string): string | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* 초기화 주입점. 기본은 `attemptReset` 이다.
|
|
101
|
+
*
|
|
102
|
+
* 사전보완 후보 비교가 같은 초기 상태에서 도는지를 유닛테스트가 보려면 초기화를 세워야 하는데,
|
|
103
|
+
* 실제 `attemptReset` 은 프로세스를 띄운다. 이 저장소는 유닛테스트를 인메모리로만 돌린다
|
|
104
|
+
* (터미널 병렬 안전). `dryRun` · `preparePreFillRequest` 를 주입으로 받는 것과 같은 이유다.
|
|
105
|
+
* 실제 명령을 돌리는 테스트는 `reset-hook-e2e.test.ts` 하나에만 둔다(#399).
|
|
106
|
+
*/
|
|
107
|
+
attemptReset?(resetCmd: string | undefined): Promise<{
|
|
108
|
+
readonly grade: ResetGrade;
|
|
109
|
+
}>;
|
|
33
110
|
createBaselineSuite(tools: readonly ToolDef[], options: {
|
|
34
111
|
suiteId: string;
|
|
35
112
|
suiteName: string;
|
|
113
|
+
fixtures?: FixtureFile;
|
|
36
114
|
}): BaselineGenerationResult;
|
|
37
115
|
createAuthoringSession(baseline: BaselineGenerationResult, options?: {
|
|
38
116
|
readonly preFilledCaseIds?: readonly string[];
|
|
@@ -100,15 +178,6 @@ interface GenerateCommandDependencies {
|
|
|
100
178
|
* 동일성도 보장된다. 위 6개 필드가 같은 방식이다.
|
|
101
179
|
*/
|
|
102
180
|
GenerateTestsError?: typeof import("@mcpeak/generate").GenerateTestsError;
|
|
103
|
-
/**
|
|
104
|
-
* 카세트 파일 입출력. 주입점을 여기 하나만 두는 이유는 시험 실행 경로에서 파일시스템을
|
|
105
|
-
* 만지는 곳이 이것뿐이기 때문이다. 나머지(`runDryRun`·`reviewDryRun`)는 주입한 client 와
|
|
106
|
-
* io 만 쓰므로 테스트가 실제 구현을 그대로 돌린다.
|
|
107
|
-
*/
|
|
108
|
-
cassetteIo?: {
|
|
109
|
-
load(path: string): Promise<Cassette | null>;
|
|
110
|
-
save(path: string, cassette: Cassette): Promise<void>;
|
|
111
|
-
};
|
|
112
181
|
}
|
|
113
182
|
interface ReviewIO {
|
|
114
183
|
input(message: string): Promise<string>;
|
|
@@ -119,6 +188,8 @@ interface ReviewIO {
|
|
|
119
188
|
close?(): void;
|
|
120
189
|
}
|
|
121
190
|
type TestAuthoringProvider = import("@mcpeak/generate").TestAuthoringProvider;
|
|
191
|
+
/** 대시보드의 검토 하위 입력에서 상위 메뉴로 돌아갈 때 쓰는 내부 제어값. */
|
|
192
|
+
declare const REVIEW_BACK_VALUE = "__mcpeak_review_back__";
|
|
122
193
|
declare function runGenerateCommand(argv: readonly string[], deps: GenerateCommandDependencies): Promise<number>;
|
|
123
194
|
declare const nodeGenerateDependencies: () => Omit<GenerateCommandDependencies, "connect" | "createBaselineSuite" | "createAuthoringSession" | "finalizeAuthoringDraft" | "getAuthoringExecutionSuite" | "validateSuite">;
|
|
124
195
|
declare function nodeReviewIO(input?: NodeJS.ReadableStream, output?: NodeJS.WritableStream): ReviewIO;
|
|
@@ -152,35 +223,6 @@ interface RepairCommandDependencies {
|
|
|
152
223
|
};
|
|
153
224
|
}
|
|
154
225
|
declare function parseRepairCommand(argv: readonly string[]): RepairCommandInput;
|
|
155
|
-
/**
|
|
156
|
-
* `repair` 명령. 번들을 읽고, 전송 내용을 확인받고, provider 에게 진단을 물어 화면에 찍는다.
|
|
157
|
-
* 파일도 명세도 고치지 않는다. 종료 코드는 운영 실패에만 1 이다(설계서 §7.1).
|
|
158
|
-
*/
|
|
159
226
|
declare function runRepairCommand(argv: readonly string[], deps: RepairCommandDependencies): Promise<number>;
|
|
160
227
|
//#endregion
|
|
161
|
-
|
|
162
|
-
interface ReplayCommandInput {
|
|
163
|
-
readonly suitePath: string;
|
|
164
|
-
readonly cassettePath: string;
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* `connect` 가 없다. replay 가 서버를 띄우지 않는다는 것이 주석이 아니라 타입에 드러난다.
|
|
168
|
-
* ADR-0028.
|
|
169
|
-
*/
|
|
170
|
-
interface ReplayCommandDependencies {
|
|
171
|
-
readFile(path: string): Promise<Uint8Array>;
|
|
172
|
-
validateSuite(input: unknown): SuiteValidationResult;
|
|
173
|
-
loadCassette(path: string): Promise<Cassette | null>;
|
|
174
|
-
startRunner(options: RunSuiteOptions): RunnerExecution;
|
|
175
|
-
finalize(options: FinalizeRunnerExecutionOptions): Promise<RunnerReport>;
|
|
176
|
-
renderReport(report: RunnerReport, options?: {
|
|
177
|
-
color?: boolean;
|
|
178
|
-
}): string;
|
|
179
|
-
colorEnabled: boolean;
|
|
180
|
-
writeStdout(text: string): void;
|
|
181
|
-
writeStderr(text: string): void;
|
|
182
|
-
}
|
|
183
|
-
declare function parseReplayCommand(argv: readonly string[]): ReplayCommandInput;
|
|
184
|
-
declare function runReplayCommand(argv: readonly string[], dependencies: ReplayCommandDependencies): Promise<number>;
|
|
185
|
-
//#endregion
|
|
186
|
-
export { RepairCommandDependencies as a, runRepairCommand as c, ReviewIO as d, nodeGenerateDependencies as f, runReplayCommand as i, GenerateCommandDependencies as l, runGenerateCommand as m, ReplayCommandInput as n, RepairCommandInput as o, nodeReviewIO as p, parseReplayCommand as r, parseRepairCommand as s, ReplayCommandDependencies as t, GenerateCommandInput as u };
|
|
228
|
+
export { GenerateCommandDependencies as a, ReviewIO as c, runGenerateCommand as d, ConnectTarget as f, runRepairCommand as i, nodeGenerateDependencies as l, RepairCommandInput as n, GenerateCommandInput as o, parseRepairCommand as r, REVIEW_BACK_VALUE as s, RepairCommandDependencies as t, nodeReviewIO as u };
|
|
@@ -1,38 +1,116 @@
|
|
|
1
|
-
import { McpStdioConnection, ToolDef } from "@mcpeak/core";
|
|
2
|
-
import { AuthoringExecutionSnapshot, AuthoringSessionView, BaselineGenerationResult, PreFillProvider } from "@mcpeak/generate";
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
import { McpHttpConnection, McpStdioConnection, ToolDef } from "@mcpeak/core";
|
|
2
|
+
import { AuthoringExecutionSnapshot, AuthoringSessionView, BaselineGenerationResult, FixtureFile, PreFillProvider } from "@mcpeak/generate";
|
|
3
|
+
import { SuiteValidationResult, TestSuiteSpec } from "@mcpeak/runner";
|
|
4
|
+
//#region src/connect-target.d.ts
|
|
5
|
+
/** 파서가 확정한 연결 대상. */
|
|
6
|
+
type ConnectTarget = {
|
|
7
|
+
readonly transport: "stdio";
|
|
8
|
+
readonly command: string;
|
|
9
|
+
readonly args: readonly string[];
|
|
10
|
+
/**
|
|
11
|
+
* 자식에게 넘길 부모 환경변수의 **이름**. 값은 여기에도 argv 에도 없다(`--header-env` 와
|
|
12
|
+
* 같은 이유). `openConnection` 이 `readEnv` 로 읽어 `env` 에 얹는다.
|
|
13
|
+
*/
|
|
14
|
+
readonly envNames: readonly string[];
|
|
15
|
+
} | {
|
|
16
|
+
readonly transport: "http";
|
|
17
|
+
readonly url: string;
|
|
18
|
+
/**
|
|
19
|
+
* 헤더 이름 → 그 값을 담은 **환경변수 이름**. 값 자체는 여기에도 argv 에도 없다.
|
|
20
|
+
*
|
|
21
|
+
* 평문 `--header 'Authorization: Bearer …'` 를 두지 않은 이유가 이 타입이다. 토큰을
|
|
22
|
+
* argv 에 실으면 `ps` 목록과 셸 히스토리에 그대로 남고, 그건 우리가 만든 노출이다.
|
|
23
|
+
*/
|
|
24
|
+
readonly headerEnv: Readonly<Record<string, string>>;
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/reset-hook.d.ts
|
|
28
|
+
/**
|
|
29
|
+
* 초기화가 실제로 무엇을 했는가. `--reset-cmd` 가 0 으로 끝난 것을 "초기 상태" 라고
|
|
30
|
+
* 부르지 않기 위해 있다. 명령이 무엇을 복원했는지 우리는 모른다(이슈 #399).
|
|
31
|
+
*/
|
|
32
|
+
type ResetGrade =
|
|
33
|
+
/**
|
|
34
|
+
* 새 프로세스·새 연결 + 초기화 명령 성공. 우리가 제공할 수 있는 가장 강한 것.
|
|
35
|
+
*
|
|
36
|
+
* **이번 회차는 이 값을 내지 않는다.** 프로세스를 다시 띄우는 것은 core 의 수명주기를
|
|
37
|
+
* 건드리고, generate 흐름 한가운데서 연결을 바꾸면 그 뒤 단계가 전부 새 client 를 봐야
|
|
38
|
+
* 한다. 값을 미리 정의하는 이유는, commandOnly 를 "초기 상태" 라고 부르지 않으려면
|
|
39
|
+
* 그보다 강한 것이 있다는 사실이 타입에 있어야 하기 때문이다. 죽은 값이 아니라
|
|
40
|
+
* 의도된 자리다. 지우지 마라(설계 §3).
|
|
41
|
+
*/
|
|
42
|
+
"freshProcess" |
|
|
43
|
+
/** 같은 연결에 초기화 명령만 성공. 서버 프로세스의 메모리 상태는 그대로다. */
|
|
44
|
+
"commandOnly" |
|
|
45
|
+
/** 초기화 수단이 없다. 앞 실행이 바꾼 상태가 남아 있다. */
|
|
46
|
+
"none" |
|
|
47
|
+
/** 초기화를 시도했으나 실패했다. 상태를 모른다. */
|
|
48
|
+
"failed";
|
|
49
|
+
//#endregion
|
|
5
50
|
//#region src/generate-command.d.ts
|
|
6
51
|
interface GenerateCommandInput {
|
|
7
52
|
readonly suiteId: string;
|
|
8
53
|
readonly name: string;
|
|
9
54
|
readonly outPath: string;
|
|
10
|
-
|
|
11
|
-
|
|
55
|
+
/**
|
|
56
|
+
* 명세를 뽑을 대상. `--command`/`--arg` 면 stdio, `--url` 이면 Streamable HTTP 다(#137).
|
|
57
|
+
* `command` · `args` 두 필드를 이 하나로 바꾼 근거는 `TestCommandInput.target` 과 같다.
|
|
58
|
+
*/
|
|
59
|
+
readonly target: ConnectTarget;
|
|
12
60
|
readonly baselineOnly: boolean;
|
|
13
61
|
readonly provider?: "codex" | "claude";
|
|
14
62
|
readonly model?: string;
|
|
15
63
|
/** 승인 전 시험 실행 여부. 기본은 실행이고 `--no-dry-run` 이 끈다. 설계 문서 §4.3. */
|
|
16
64
|
readonly dryRun: boolean;
|
|
17
|
-
/** `--cassette` 경로. 없으면 서버를 직접 부른다. */
|
|
18
|
-
readonly cassettePath?: string;
|
|
19
|
-
/** `--record`. 카세트 파일이 있어도 새로 녹화한다. */
|
|
20
|
-
readonly forceRecord: boolean;
|
|
21
65
|
/** `--force`. `--out` 에 파일이 있으면 지우고 새로 쓴다. 설계 문서 §5. */
|
|
22
66
|
readonly force: boolean;
|
|
23
67
|
/** `--reset-cmd`. 시험 실행 직전 1회 실행한다. */
|
|
24
68
|
readonly resetCmd?: string;
|
|
25
69
|
/** 입력값 교정 단계를 돌릴지 여부. 기본은 실행이고 `--no-repair` 가 끈다. 설계 문서 §7. */
|
|
26
70
|
readonly repair: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* 통과한 거절 케이스의 근거를 확인할지 여부. 기본은 꺼짐이고 `--diagnose-rejections` 가 켠다.
|
|
73
|
+
* 끄면 미확인 목록도 AI 진단도 나가지 않고 한 줄 고지만 남는다(ADR-0098 · 설계 §4.2).
|
|
74
|
+
*/
|
|
75
|
+
readonly diagnoseRejections: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* `--fixtures` 로 명시한 픽스처 파일 경로. 없으면 기본 경로(`mcpeak.fixtures.json`)를 본다.
|
|
78
|
+
*
|
|
79
|
+
* "명시했는가" 를 여기서 구분해 두는 이유는 없는 파일의 처리가 다르기 때문이다. 기본
|
|
80
|
+
* 경로는 없는 것이 정상이라 조용히 지나가고, 사용자가 적은 경로가 없는 것은 오타다(#390).
|
|
81
|
+
*/
|
|
82
|
+
readonly fixturesPath?: string;
|
|
27
83
|
}
|
|
28
84
|
interface GenerateCommandDependencies {
|
|
29
85
|
connect(options: {
|
|
30
86
|
command: string;
|
|
31
87
|
args: readonly string[];
|
|
32
88
|
}): Promise<McpStdioConnection>;
|
|
89
|
+
/**
|
|
90
|
+
* 원격(Streamable HTTP) 대상용 연결. `core.connectHttp` 다. 선택 사항으로 두는 근거는
|
|
91
|
+
* `TestCommandDependencies.connectHttp` 와 같다(#137).
|
|
92
|
+
*/
|
|
93
|
+
connectHttp?(options: {
|
|
94
|
+
url: string;
|
|
95
|
+
headers?: Readonly<Record<string, string>>;
|
|
96
|
+
}): Promise<McpHttpConnection>;
|
|
97
|
+
/** `--header-env` 가 가리키는 환경변수를 읽는다. CLI 가 `process` 를 직접 읽지 않기 위한 주입점. */
|
|
98
|
+
readEnv?(name: string): string | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* 초기화 주입점. 기본은 `attemptReset` 이다.
|
|
101
|
+
*
|
|
102
|
+
* 사전보완 후보 비교가 같은 초기 상태에서 도는지를 유닛테스트가 보려면 초기화를 세워야 하는데,
|
|
103
|
+
* 실제 `attemptReset` 은 프로세스를 띄운다. 이 저장소는 유닛테스트를 인메모리로만 돌린다
|
|
104
|
+
* (터미널 병렬 안전). `dryRun` · `preparePreFillRequest` 를 주입으로 받는 것과 같은 이유다.
|
|
105
|
+
* 실제 명령을 돌리는 테스트는 `reset-hook-e2e.test.ts` 하나에만 둔다(#399).
|
|
106
|
+
*/
|
|
107
|
+
attemptReset?(resetCmd: string | undefined): Promise<{
|
|
108
|
+
readonly grade: ResetGrade;
|
|
109
|
+
}>;
|
|
33
110
|
createBaselineSuite(tools: readonly ToolDef[], options: {
|
|
34
111
|
suiteId: string;
|
|
35
112
|
suiteName: string;
|
|
113
|
+
fixtures?: FixtureFile;
|
|
36
114
|
}): BaselineGenerationResult;
|
|
37
115
|
createAuthoringSession(baseline: BaselineGenerationResult, options?: {
|
|
38
116
|
readonly preFilledCaseIds?: readonly string[];
|
|
@@ -100,15 +178,6 @@ interface GenerateCommandDependencies {
|
|
|
100
178
|
* 동일성도 보장된다. 위 6개 필드가 같은 방식이다.
|
|
101
179
|
*/
|
|
102
180
|
GenerateTestsError?: typeof import("@mcpeak/generate").GenerateTestsError;
|
|
103
|
-
/**
|
|
104
|
-
* 카세트 파일 입출력. 주입점을 여기 하나만 두는 이유는 시험 실행 경로에서 파일시스템을
|
|
105
|
-
* 만지는 곳이 이것뿐이기 때문이다. 나머지(`runDryRun`·`reviewDryRun`)는 주입한 client 와
|
|
106
|
-
* io 만 쓰므로 테스트가 실제 구현을 그대로 돌린다.
|
|
107
|
-
*/
|
|
108
|
-
cassetteIo?: {
|
|
109
|
-
load(path: string): Promise<Cassette | null>;
|
|
110
|
-
save(path: string, cassette: Cassette): Promise<void>;
|
|
111
|
-
};
|
|
112
181
|
}
|
|
113
182
|
interface ReviewIO {
|
|
114
183
|
input(message: string): Promise<string>;
|
|
@@ -119,6 +188,8 @@ interface ReviewIO {
|
|
|
119
188
|
close?(): void;
|
|
120
189
|
}
|
|
121
190
|
type TestAuthoringProvider = import("@mcpeak/generate").TestAuthoringProvider;
|
|
191
|
+
/** 대시보드의 검토 하위 입력에서 상위 메뉴로 돌아갈 때 쓰는 내부 제어값. */
|
|
192
|
+
declare const REVIEW_BACK_VALUE = "__mcpeak_review_back__";
|
|
122
193
|
declare function runGenerateCommand(argv: readonly string[], deps: GenerateCommandDependencies): Promise<number>;
|
|
123
194
|
declare const nodeGenerateDependencies: () => Omit<GenerateCommandDependencies, "connect" | "createBaselineSuite" | "createAuthoringSession" | "finalizeAuthoringDraft" | "getAuthoringExecutionSuite" | "validateSuite">;
|
|
124
195
|
declare function nodeReviewIO(input?: NodeJS.ReadableStream, output?: NodeJS.WritableStream): ReviewIO;
|
|
@@ -152,35 +223,6 @@ interface RepairCommandDependencies {
|
|
|
152
223
|
};
|
|
153
224
|
}
|
|
154
225
|
declare function parseRepairCommand(argv: readonly string[]): RepairCommandInput;
|
|
155
|
-
/**
|
|
156
|
-
* `repair` 명령. 번들을 읽고, 전송 내용을 확인받고, provider 에게 진단을 물어 화면에 찍는다.
|
|
157
|
-
* 파일도 명세도 고치지 않는다. 종료 코드는 운영 실패에만 1 이다(설계서 §7.1).
|
|
158
|
-
*/
|
|
159
226
|
declare function runRepairCommand(argv: readonly string[], deps: RepairCommandDependencies): Promise<number>;
|
|
160
227
|
//#endregion
|
|
161
|
-
|
|
162
|
-
interface ReplayCommandInput {
|
|
163
|
-
readonly suitePath: string;
|
|
164
|
-
readonly cassettePath: string;
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* `connect` 가 없다. replay 가 서버를 띄우지 않는다는 것이 주석이 아니라 타입에 드러난다.
|
|
168
|
-
* ADR-0028.
|
|
169
|
-
*/
|
|
170
|
-
interface ReplayCommandDependencies {
|
|
171
|
-
readFile(path: string): Promise<Uint8Array>;
|
|
172
|
-
validateSuite(input: unknown): SuiteValidationResult;
|
|
173
|
-
loadCassette(path: string): Promise<Cassette | null>;
|
|
174
|
-
startRunner(options: RunSuiteOptions): RunnerExecution;
|
|
175
|
-
finalize(options: FinalizeRunnerExecutionOptions): Promise<RunnerReport>;
|
|
176
|
-
renderReport(report: RunnerReport, options?: {
|
|
177
|
-
color?: boolean;
|
|
178
|
-
}): string;
|
|
179
|
-
colorEnabled: boolean;
|
|
180
|
-
writeStdout(text: string): void;
|
|
181
|
-
writeStderr(text: string): void;
|
|
182
|
-
}
|
|
183
|
-
declare function parseReplayCommand(argv: readonly string[]): ReplayCommandInput;
|
|
184
|
-
declare function runReplayCommand(argv: readonly string[], dependencies: ReplayCommandDependencies): Promise<number>;
|
|
185
|
-
//#endregion
|
|
186
|
-
export { RepairCommandDependencies as a, runRepairCommand as c, ReviewIO as d, nodeGenerateDependencies as f, runReplayCommand as i, GenerateCommandDependencies as l, runGenerateCommand as m, ReplayCommandInput as n, RepairCommandInput as o, nodeReviewIO as p, parseReplayCommand as r, parseRepairCommand as s, ReplayCommandDependencies as t, GenerateCommandInput as u };
|
|
228
|
+
export { GenerateCommandDependencies as a, ReviewIO as c, runGenerateCommand as d, ConnectTarget as f, runRepairCommand as i, nodeGenerateDependencies as l, RepairCommandInput as n, GenerateCommandInput as o, parseRepairCommand as r, REVIEW_BACK_VALUE as s, RepairCommandDependencies as t, nodeReviewIO as u };
|