gx-code-agent 0.1.1 → 0.1.2

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
@@ -12,8 +12,8 @@ GX 高代码平台的伴生 CLI。M0 阶段提供 `gx init`:一次性完成 MC
12
12
 
13
13
  ```bash
14
14
  # 主 CLI
15
- npm i -g gx-code-agent@0.1.1
16
- npx --yes gx-code-agent@0.1.1 --help
15
+ npm i -g gx-code-agent@0.1.2
16
+ npx --yes gx-code-agent@0.1.2 --help
17
17
 
18
18
  # 独立本地 MCP 启动器
19
19
  npx --yes gx-mcp-local
@@ -86,6 +86,7 @@ gx verify [--dir <path>] [--manifest <path>] [--strict]
86
86
  gx scan [--dir <path>] [--compile] [--report|--no-report] [--source-dir a,b]
87
87
  gx audit [--dir <path>] [--compile] [--report|--no-report] [--strict]
88
88
  gx chunks [--dir <path>] [--source-dir a,b] [--report|--no-report] [--no-content] [--strict]
89
+ gx doctor [--dir <path>] [--url <url>] [--token <token>] [--skip-mcp] [--skip-local] [--json] [--strict]
89
90
  gx version
90
91
  gx help
91
92
  ```
@@ -99,6 +100,31 @@ gx help
99
100
  - 非交互模式写入必须带 `--yes`,否则退出码 2。
100
101
  - 不支持的客户端会直接报错,不会猜测格式。
101
102
 
103
+ ## 诊断:gx doctor
104
+
105
+ ```bash
106
+ # 人类可读
107
+ gx doctor
108
+
109
+ # 只输出 JSON(供 CI / 脚本)
110
+ gx doctor --json
111
+
112
+ # 离线环境跳过远端 MCP / 本地 MCP
113
+ gx doctor --skip-mcp --skip-local
114
+ ```
115
+
116
+ 检查项:
117
+
118
+ - Node.js 版本(>= 18);
119
+ - JDK / javac 版本(AST 与 `--compile` 需要 JDK 9+,推荐 17);
120
+ - `GX_API_TOKEN` 是否存在、格式是否为 `gxmcp_` + 43 位;
121
+ - `.gx/config.json` 的 planId/templateId;
122
+ - `.gx/manifest.json` 是否存在及文件数;
123
+ - 远端 MCP:`ping` / `tools/list` / `get_current_user`;
124
+ - `gx-local` 本地 stdio MCP 是否能启动并返回工具列表。
125
+
126
+ 失败项会给出修复提示;`--strict` 时 warn 也视为失败。退出码:存在 fail(或 strict+warn)为 1。
127
+
102
128
  ## M3:拉取 / 应用 / 校验
103
129
 
104
130
  ```bash
@@ -235,7 +261,7 @@ CI 变量:
235
261
 
236
262
  | 变量 | 说明 |
237
263
  |---|---|
238
- | `GX_AGENT_VERSION` | 固定 CLI 版本,默认 `0.1.1` |
264
+ | `GX_AGENT_VERSION` | 固定 CLI 版本,默认 `0.1.2` |
239
265
  | `NPM_REGISTRY` | npm registry;默认 `https://registry.npmjs.org/` |
240
266
  | `GX_API_TOKEN` / `GX_PLAN_ID` | MCP 鉴权与方案范围 |
241
267
 
@@ -251,7 +277,7 @@ CI 变量:
251
277
  node --test
252
278
  ```
253
279
 
254
- 当前:CLI `39/39`;平台侧 Java MCP `80/80`。
280
+ 当前:CLI `44/44`;平台侧 Java MCP `80/80`。
255
281
 
256
282
  ## 当前进度与后续
257
283
 
@@ -265,12 +291,13 @@ node --test
265
291
  - P7:审计 compilation facet 合并、向量重建、审计保留、pgVector 健康检查
266
292
  - P8:Java AST chunker、混合检索评测集、本地 stdio MCP(`gx-mcp-local`)
267
293
  - CLI-02:`gx init` 支持 `--with-local`、timeout、项目绑定、连接自检、`.gx/.gitignore`、`--check`
294
+ - CLI-03:`gx doctor` 诊断 Node/JDK/Token/配置/manifest/MCP/`gx-local`,支持 `--json`
268
295
 
269
296
  待办:
270
297
 
271
298
  - `gx context` / `gx push` / `gx adopt`
272
299
  - Agent 外壳(本地 stdio MCP 已提供基础版)
273
- - 公共 npm 发布(发布准备已完成,待 2FA OTP 或 Bypass 2FA granular token)
300
+ - 公共 npm 已发布 `gx-code-agent@0.1.2` 与 `gx-mcp-local@0.1.2`
274
301
 
275
302
  ## 与主平台的关系
276
303
 
package/bin/gx.js CHANGED
@@ -8,6 +8,7 @@ import { parseArgs, usage } from "../src/adapters/cli/args.js";
8
8
  import { runApply } from "../src/application/applyWorkflow.js";
9
9
  import { runAudit } from "../src/application/auditCommand.js";
10
10
  import { runChunks } from "../src/application/chunksCommand.js";
11
+ import { runDoctor } from "../src/application/doctorCommand.js";
11
12
  import { runEvalSearch } from "../src/application/evalSearchCommand.js";
12
13
  import { runInit } from "../src/application/initWorkflow.js";
13
14
  import { runPull } from "../src/application/pullWorkflow.js";
@@ -85,6 +86,13 @@ async function main() {
85
86
  }
86
87
  return;
87
88
  }
89
+ if (command === "doctor") {
90
+ const result = await runDoctor(args);
91
+ if (!result.ok) {
92
+ process.exitCode = 1;
93
+ }
94
+ return;
95
+ }
88
96
 
89
97
  usage();
90
98
  process.exitCode = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gx-code-agent",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "GX 高代码平台伴生 CLI:注册 MCP、注入平台规范、校验生成物、本地 stdio MCP",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  import { UsageError } from "../../application/errors.js";
2
2
 
3
3
  export function usage() {
4
- console.log(`gx-code-agent 0.1.1
4
+ console.log(`gx-code-agent 0.1.2
5
5
 
6
6
  用法:
7
7
  gx init [选项]
@@ -35,6 +35,7 @@ export function usage() {
35
35
  gx audit 上报扩展点并调用 audit_alignment,合并本地/平台 Unknown
36
36
  gx chunks 生成 Java/Vue 代码切片并上报 record_code_chunks
37
37
  gx eval-search 用评测集计算 search_code 的 HitRate/MRR
38
+ gx doctor 诊断 Node/JDK/Token/配置/manifest/MCP/gx-local
38
39
 
39
40
  gx pull 选项:
40
41
  --plan <planId> --template <templateId> [--model-ids a,b] [--url <url>] [--token <token>] [--dry-run]
@@ -56,6 +57,16 @@ gx chunks 选项:
56
57
  --no-report 只生成本地 .gx/chunks.json
57
58
  --no-content 上报时不上传切片内容(仅元数据)
58
59
 
60
+ gx doctor 选项:
61
+ --dir <path> 项目目录,默认当前目录
62
+ --url <url> MCP 地址,默认读取 .gx/config.json 或 http://127.0.0.1:4013/mcp
63
+ --token <token> 临时指定 Token;默认读取 GX_API_TOKEN
64
+ --token-env <name> Token 环境变量名,默认 GX_API_TOKEN
65
+ --skip-mcp 跳过远端 MCP 检查
66
+ --skip-local 跳过 gx-local 检查
67
+ --json 输出 JSON
68
+ --strict warn 也视为失败
69
+
59
70
  示例:
60
71
  GX_API_TOKEN=gxmcp_xxx gx init --client opencode --yes
61
72
  gx init --client claude --token gxmcp_xxx --embed-token --yes --url https://example.com/code/mcp
@@ -79,7 +90,7 @@ export function parseArgs(argv) {
79
90
  continue;
80
91
  }
81
92
  const key = item.slice(2);
82
- if (["help", "dry-run", "force", "embed-token", "yes", "strict", "compile", "report", "no-report", "json", "no-content", "check", "with-local", "skip-verify"].includes(key)) {
93
+ if (["help", "dry-run", "force", "embed-token", "yes", "strict", "compile", "report", "no-report", "json", "no-content", "check", "with-local", "skip-verify", "skip-mcp", "skip-local"].includes(key)) {
83
94
  args[key] = true;
84
95
  continue;
85
96
  }
@@ -17,7 +17,7 @@ export class McpHttpClient {
17
17
  this.url = options.url;
18
18
  this.token = options.token;
19
19
  this.clientName = options.clientName ?? "gx-code-agent";
20
- this.clientVersion = options.clientVersion ?? "0.1.1";
20
+ this.clientVersion = options.clientVersion ?? "0.1.2";
21
21
  this.protocolVersion = options.protocolVersion ?? "2025-06-18";
22
22
  this.timeoutMs = Number.isFinite(options.timeoutMs) && options.timeoutMs > 0 ? options.timeoutMs : 30000;
23
23
  this.sessionId = null;
@@ -0,0 +1,381 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ import { McpHttpClient } from "../adapters/platform/mcpClient.js";
7
+ import { readJson } from "../store/localStore.js";
8
+
9
+ const STDIO_BIN = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../bin/gx-stdio-mcp.js");
10
+ const TOKEN_PATTERN = /^gxmcp_[A-Za-z0-9_-]{43}$/;
11
+
12
+ /**
13
+ * 校验 GX MCP API Token 格式。
14
+ *
15
+ * @param {string | undefined} token
16
+ * @returns {{ status: "ok"|"warn"|"fail", message: string, fix?: string }}
17
+ */
18
+ export function validateToken(token) {
19
+ if (!token) {
20
+ return {
21
+ status: "warn",
22
+ message: "未设置 GX_API_TOKEN",
23
+ fix: "在终端 export GX_API_TOKEN='gxmcp_...' 或写入 ~/.zshrc;见 README『快速开始』"
24
+ };
25
+ }
26
+ if (TOKEN_PATTERN.test(token)) {
27
+ return {
28
+ status: "ok",
29
+ message: `格式正确(gxmcp_ + 43 位,共 ${token.length} 字符)`
30
+ };
31
+ }
32
+ return {
33
+ status: "fail",
34
+ message: `格式不正确(长度 ${token.length},应以 gxmcp_ 开头并共 49 字符)`,
35
+ fix: "重新创建 Token,见 README『快速开始』与 plan/MCPServer/opencode-integration.md 2.4"
36
+ };
37
+ }
38
+
39
+ /**
40
+ * 解析 Node 主版本。
41
+ *
42
+ * @param {string} version
43
+ * @returns {number}
44
+ */
45
+ export function parseNodeMajor(version) {
46
+ return Number(String(version).replace(/^v/u, "").split(".")[0]) || 0;
47
+ }
48
+
49
+ /**
50
+ * 从 java -version / javac -version 输出中解析主版本。
51
+ *
52
+ * @param {string} output
53
+ * @returns {number}
54
+ */
55
+ export function parseJavaMajor(output) {
56
+ const value = String(output ?? "");
57
+ const quoted = /version\s+"(\d+)(?:\.(\d+))?/u.exec(value);
58
+ if (quoted) {
59
+ const major = Number(quoted[1]);
60
+ return major === 1 ? Number(quoted[2]) : major;
61
+ }
62
+ const plain = /(?:javac|openjdk|java)\s+(\d+)(?:\.(\d+))?/iu.exec(value);
63
+ if (plain) {
64
+ const major = Number(plain[1]);
65
+ return major === 1 ? Number(plain[2]) : major;
66
+ }
67
+ return 0;
68
+ }
69
+
70
+ /**
71
+ * 执行命令并合并 stdout/stderr。
72
+ *
73
+ * @param {string} command
74
+ * @param {string[]} args
75
+ * @returns {{ ok: boolean, output: string, error?: string }}
76
+ */
77
+ function commandVersion(command, args) {
78
+ const result = spawnSync(command, args, {
79
+ encoding: "utf8",
80
+ timeout: 5000,
81
+ stdio: ["ignore", "pipe", "pipe"]
82
+ });
83
+ const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
84
+ if (result.error) {
85
+ return { ok: false, output, error: result.error.message };
86
+ }
87
+ return { ok: result.status === 0, output, error: result.status === 0 ? undefined : `exit ${result.status}` };
88
+ }
89
+
90
+ /**
91
+ * 查找 java/javac 命令。
92
+ *
93
+ * @returns {{ java: string, javac: string, home?: string }}
94
+ */
95
+ function javaCommands() {
96
+ const home = process.env.JAVA_HOME;
97
+ if (home) {
98
+ return {
99
+ home,
100
+ java: path.join(home, "bin", "java"),
101
+ javac: path.join(home, "bin", "javac")
102
+ };
103
+ }
104
+ return { java: "java", javac: "javac" };
105
+ }
106
+
107
+ /**
108
+ * 检查本地 stdio MCP。
109
+ *
110
+ * @returns {{ status: "ok"|"warn"|"fail", message: string, tools?: string[] }}
111
+ */
112
+ function checkLocalMcp() {
113
+ if (!fs.existsSync(STDIO_BIN)) {
114
+ return { status: "fail", message: `找不到 ${STDIO_BIN}` };
115
+ }
116
+ const input = [
117
+ '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"gx-doctor","version":"1"}}}',
118
+ '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}',
119
+ ""
120
+ ].join("\n");
121
+ const result = spawnSync(process.execPath, [STDIO_BIN], {
122
+ input,
123
+ encoding: "utf8",
124
+ timeout: 5000
125
+ });
126
+ if (result.error) {
127
+ return { status: "warn", message: `gx-local 启动失败:${result.error.message}` };
128
+ }
129
+ if (result.status !== 0) {
130
+ return { status: "warn", message: `gx-local 退出码 ${result.status}` };
131
+ }
132
+ const messages = `${result.stdout ?? ""}`
133
+ .split(/\r?\n/u)
134
+ .map((line) => line.trim())
135
+ .filter(Boolean)
136
+ .map((line) => {
137
+ try {
138
+ return JSON.parse(line);
139
+ } catch {
140
+ return null;
141
+ }
142
+ })
143
+ .filter(Boolean);
144
+ const tools = messages.find((message) => message.id === 2)?.result?.tools ?? [];
145
+ if (!Array.isArray(tools) || tools.length === 0) {
146
+ return { status: "warn", message: "gx-local 未返回 tools/list" };
147
+ }
148
+ return {
149
+ status: "ok",
150
+ message: `gx-local 可用(${tools.length} 个工具)`,
151
+ tools: tools.map((item) => item.name).filter(Boolean)
152
+ };
153
+ }
154
+
155
+ /**
156
+ * 检查远端 MCP 连接。
157
+ *
158
+ * @param {string} url
159
+ * @param {string | undefined} token
160
+ * @returns {Promise<{ status: "ok"|"warn"|"fail"|"skip", message: string, details?: Record<string, unknown> }>}
161
+ */
162
+ async function checkRemoteMcp(url, token) {
163
+ if (!token) {
164
+ return { status: "skip", message: "未提供 Token,跳过远端 MCP 检查" };
165
+ }
166
+ const client = new McpHttpClient({
167
+ url,
168
+ token,
169
+ clientName: "gx-doctor",
170
+ timeoutMs: 5000
171
+ });
172
+ try {
173
+ await client.initialize();
174
+ const tools = await client.listTools();
175
+ const ping = await client.callTool("ping", { client: "gx doctor" });
176
+ let user = null;
177
+ try {
178
+ user = await client.callTool("get_current_user", {});
179
+ }
180
+ catch {
181
+ user = null;
182
+ }
183
+ return {
184
+ status: "ok",
185
+ message: `MCP 可用(协议 ${client.protocolVersion},工具 ${tools.length} 个)`,
186
+ details: { protocolVersion: client.protocolVersion, toolCount: tools.length, ping, user }
187
+ };
188
+ }
189
+ catch (error) {
190
+ const message = error instanceof Error ? error.message : String(error);
191
+ const httpStatus = Number(/MCP HTTP (\d+)/u.exec(message)?.[1] ?? 0);
192
+ return {
193
+ status: "fail",
194
+ message: httpStatus === 401 ? "MCP 返回 401,Token 无效/过期/已吊销" : `MCP 连接失败:${message}`,
195
+ details: { httpStatus }
196
+ };
197
+ }
198
+ finally {
199
+ await client.close().catch(() => {});
200
+ }
201
+ }
202
+
203
+ /**
204
+ * 执行 gx doctor。
205
+ *
206
+ * @param {Record<string, unknown>} args
207
+ * @param {{ stdout: NodeJS.WriteStream, stderr: NodeJS.WriteStream }} [io]
208
+ * @returns {Promise<Record<string, unknown>>}
209
+ */
210
+ export async function runDoctor(args, io = { stdout: process.stdout, stderr: process.stderr }) {
211
+ const dir = path.resolve(String(args.dir || process.cwd()));
212
+ const config = readJson(path.join(dir, ".gx", "config.json"), {});
213
+ const url = String(args.url || config.mcpUrl || "http://127.0.0.1:4013/mcp");
214
+ const tokenEnv = String(args["token-env"] || "GX_API_TOKEN");
215
+ const token = typeof args.token === "string" ? args.token : process.env[tokenEnv];
216
+ const skipMcp = Boolean(args["skip-mcp"]);
217
+ const skipLocal = Boolean(args["skip-local"]);
218
+ const strict = Boolean(args.strict);
219
+ const checks = [];
220
+
221
+ const nodeMajor = parseNodeMajor(process.version);
222
+ checks.push({
223
+ id: "node",
224
+ label: "Node.js",
225
+ status: nodeMajor >= 18 ? "ok" : "fail",
226
+ message: `${process.version}${nodeMajor >= 18 ? "" : "(需要 >= 18)"}`,
227
+ ...(nodeMajor >= 18 ? {} : { fix: "升级 Node.js 到 18 或更高版本" })
228
+ });
229
+
230
+ const java = javaCommands();
231
+ const javaResult = commandVersion(java.java, ["-version"]);
232
+ const javacResult = commandVersion(java.javac, ["-version"]);
233
+ const javaMajor = parseJavaMajor(javaResult.output);
234
+ const javacMajor = parseJavaMajor(javacResult.output);
235
+ const jdkStatus = javaResult.ok && javacResult.ok && javaMajor >= 9 ? "ok" : "warn";
236
+ checks.push({
237
+ id: "jdk",
238
+ label: "JDK",
239
+ status: jdkStatus,
240
+ message: javaResult.ok ? (javaResult.output.split(/\r?\n/u)[0] || `Java ${javaMajor}`) : `java 不可用:${javaResult.error}`,
241
+ ...(jdkStatus === "ok" ? {} : { fix: "安装 JDK 17,并设置 JAVA_HOME;见 README 环境要求" })
242
+ });
243
+ checks.push({
244
+ id: "javac",
245
+ label: "javac",
246
+ status: javacResult.ok && javacMajor >= 9 ? "ok" : "warn",
247
+ message: javacResult.ok ? javacResult.output.split(/\r?\n/u)[0] : `javac 不可用:${javacResult.error}`,
248
+ ...(javacResult.ok && javacMajor >= 9 ? {} : { fix: "安装 JDK 17;gx scan --compile 需要 javac" })
249
+ });
250
+
251
+ const tokenCheck = validateToken(token);
252
+ checks.push({
253
+ id: "token",
254
+ label: "GX_API_TOKEN",
255
+ status: tokenCheck.status,
256
+ message: tokenCheck.message,
257
+ ...(tokenCheck.fix ? { fix: tokenCheck.fix } : {})
258
+ });
259
+
260
+ const configPath = path.join(dir, ".gx", "config.json");
261
+ if (!fs.existsSync(configPath)) {
262
+ checks.push({
263
+ id: "config",
264
+ label: ".gx/config.json",
265
+ status: "warn",
266
+ message: "未找到;请先执行 gx init",
267
+ fix: "gx init --client opencode --plan <planId> --yes"
268
+ });
269
+ }
270
+ else {
271
+ checks.push({
272
+ id: "config",
273
+ label: ".gx/config.json",
274
+ status: config.planId || config.templateId ? "ok" : "warn",
275
+ message: `planId=${config.planId ?? "-"}, templateId=${config.templateId ?? "-"}`,
276
+ ...(config.planId || config.templateId ? {} : { fix: "gx init --plan <planId> --template <templateId> --yes" })
277
+ });
278
+ }
279
+
280
+ const manifestPath = path.join(dir, ".gx", "manifest.json");
281
+ if (!fs.existsSync(manifestPath)) {
282
+ checks.push({
283
+ id: "manifest",
284
+ label: ".gx/manifest.json",
285
+ status: "warn",
286
+ message: "未找到;请先执行 gx pull",
287
+ fix: "gx pull --plan <planId> --template <templateId>"
288
+ });
289
+ }
290
+ else {
291
+ try {
292
+ const manifest = readJson(manifestPath, {});
293
+ const fileCount = Array.isArray(manifest.files) ? manifest.files.length : 0;
294
+ checks.push({
295
+ id: "manifest",
296
+ label: ".gx/manifest.json",
297
+ status: "ok",
298
+ message: `${fileCount} 个文件,metadataVersion=${manifest.metadataVersion ?? "-"}`
299
+ });
300
+ }
301
+ catch (error) {
302
+ checks.push({
303
+ id: "manifest",
304
+ label: ".gx/manifest.json",
305
+ status: "fail",
306
+ message: error instanceof Error ? error.message : String(error),
307
+ fix: "重新执行 gx pull,不要手工修改 manifest"
308
+ });
309
+ }
310
+ }
311
+
312
+ if (skipMcp) {
313
+ checks.push({ id: "mcp", label: "MCP", status: "skip", message: "使用 --skip-mcp 跳过" });
314
+ }
315
+ else {
316
+ const mcp = await checkRemoteMcp(url, token);
317
+ checks.push({
318
+ id: "mcp",
319
+ label: "MCP",
320
+ status: mcp.status,
321
+ message: mcp.message,
322
+ ...(mcp.details ? { details: mcp.details } : {}),
323
+ ...(mcp.status === "fail" ? { fix: "检查 MCP 地址、Token、网络;见 plan/MCPServer/opencode-integration.md 第 6 节" } : {})
324
+ });
325
+ }
326
+
327
+ if (skipLocal) {
328
+ checks.push({ id: "local", label: "gx-local", status: "skip", message: "使用 --skip-local 跳过" });
329
+ }
330
+ else {
331
+ const local = checkLocalMcp();
332
+ checks.push({
333
+ id: "local",
334
+ label: "gx-local",
335
+ status: local.status,
336
+ message: local.message,
337
+ ...(local.tools ? { details: { tools: local.tools } } : {}),
338
+ ...(local.status === "fail" ? { fix: "重新安装 gx-code-agent,或检查包内 bin/gx-stdio-mcp.js" } : {})
339
+ });
340
+ }
341
+
342
+ const summary = {
343
+ ok: checks.filter((item) => item.status === "ok").length,
344
+ warn: checks.filter((item) => item.status === "warn").length,
345
+ fail: checks.filter((item) => item.status === "fail").length,
346
+ skip: checks.filter((item) => item.status === "skip").length
347
+ };
348
+ const ok = strict ? summary.fail === 0 && summary.warn === 0 : summary.fail === 0;
349
+ const report = {
350
+ ok,
351
+ generatedAt: new Date().toISOString(),
352
+ dir,
353
+ url,
354
+ checks,
355
+ summary
356
+ };
357
+
358
+ if (args.json) {
359
+ io.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
360
+ return report;
361
+ }
362
+
363
+ io.stdout.write(`GX Doctor\n`);
364
+ io.stdout.write(`- 目录: ${dir}\n`);
365
+ io.stdout.write(`- MCP: ${url}\n\n`);
366
+ for (const item of checks) {
367
+ const marker = item.status === "ok" ? "OK" : item.status.toUpperCase();
368
+ io.stdout.write(`[${marker}] ${item.label}: ${item.message}\n`);
369
+ if (item.fix) {
370
+ io.stdout.write(` 修复: ${item.fix}\n`);
371
+ }
372
+ }
373
+ io.stdout.write(`\n汇总: ok=${summary.ok}, warn=${summary.warn}, fail=${summary.fail}, skip=${summary.skip}\n`);
374
+ if (!ok) {
375
+ io.stdout.write("结果: 未通过\n");
376
+ }
377
+ else {
378
+ io.stdout.write("结果: 通过\n");
379
+ }
380
+ return report;
381
+ }