@aipanel/core 1.2.27 → 1.3.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.
@@ -155,8 +155,40 @@ export declare const VUE_DEVTOOLS_ACTIONS: {
155
155
  readonly GET_APPS: "getApps";
156
156
  readonly TOGGLE_APP: "toggleApp";
157
157
  readonly GET_ROUTER_INFO: "getRouterInfo";
158
+ readonly GET_TIMELINE: "getTimeline";
159
+ readonly MARK_TIMELINE: "markTimeline";
160
+ readonly CLEAR_TIMELINE: "clearTimeline";
158
161
  };
159
162
  export type VueDevtoolsAction = (typeof VUE_DEVTOOLS_ACTIONS)[keyof typeof VUE_DEVTOOLS_ACTIONS];
163
+ /**
164
+ * 时间线层名(工具 schema 的 layer 枚举与页面侧采集器共用)。
165
+ * lifecycle 只累计计数、不产生明细记录;其余层都是明细记录。
166
+ */
167
+ export declare const VUE_DEVTOOLS_TIMELINE_LAYERS: readonly ["perf", "lifecycle", "emit", "navigate", "agent"];
168
+ export type VueDevtoolsTimelineLayer = (typeof VUE_DEVTOOLS_TIMELINE_LAYERS)[number];
169
+ /** 时间线明细档位(唯一来源):数组、类型与页面侧的分支比较都引用这里,避免散落字面量 */
170
+ export declare const VUE_DEVTOOLS_TIMELINE_INCLUDE: {
171
+ readonly SUMMARY: "summary";
172
+ readonly SLOW: "slow";
173
+ readonly ALL: "all";
174
+ };
175
+ export declare const VUE_DEVTOOLS_TIMELINE_INCLUDES: readonly ["summary", "slow", "all"];
176
+ export type VueDevtoolsTimelineInclude = (typeof VUE_DEVTOOLS_TIMELINE_INCLUDES)[number];
177
+ /** 时间线查询默认值(MCP 工具 schema 与页面侧采集器共用,避免两处漂移) */
178
+ export declare const VUE_DEVTOOLS_TIMELINE_DEFAULTS: {
179
+ /** 默认回溯窗口(毫秒) */
180
+ readonly windowMs: 5000;
181
+ /** 默认只返回耗时 ≥ 该值的组件渲染记录(毫秒) */
182
+ readonly minDurationMs: 4;
183
+ /** 默认明细档位 */
184
+ readonly include: "slow";
185
+ /** 默认明细条数上限 */
186
+ readonly limit: 30;
187
+ /** 明细条数上限的硬上限 */
188
+ readonly maxLimit: 200;
189
+ /** 页面侧环形缓冲容量(条) */
190
+ readonly capacity: 1000;
191
+ };
160
192
  /** ==================== 写类工具名单 ==================== */
161
193
  /** 会修改文件的工具名(host 插件与编辑后自动诊断共用的单一来源) */
162
194
  export declare const MUTATING_TOOLS: ReadonlySet<string>;
@@ -85,7 +85,42 @@ const VUE_DEVTOOLS_ACTIONS = {
85
85
  GET_COMPONENT_RENDER_CODE: "getComponentRenderCode",
86
86
  GET_APPS: "getApps",
87
87
  TOGGLE_APP: "toggleApp",
88
- GET_ROUTER_INFO: "getRouterInfo"
88
+ GET_ROUTER_INFO: "getRouterInfo",
89
+ GET_TIMELINE: "getTimeline",
90
+ MARK_TIMELINE: "markTimeline",
91
+ CLEAR_TIMELINE: "clearTimeline"
92
+ };
93
+ const VUE_DEVTOOLS_TIMELINE_LAYERS = [
94
+ "perf",
95
+ "lifecycle",
96
+ "emit",
97
+ "navigate",
98
+ "agent"
99
+ ];
100
+ const VUE_DEVTOOLS_TIMELINE_INCLUDE = {
101
+ SUMMARY: "summary",
102
+ SLOW: "slow",
103
+ ALL: "all"
104
+ };
105
+ const VUE_DEVTOOLS_TIMELINE_INCLUDES = [
106
+ VUE_DEVTOOLS_TIMELINE_INCLUDE.SUMMARY,
107
+ VUE_DEVTOOLS_TIMELINE_INCLUDE.SLOW,
108
+ VUE_DEVTOOLS_TIMELINE_INCLUDE.ALL
109
+ ];
110
+ const DEFAULT_TIMELINE_INCLUDE = VUE_DEVTOOLS_TIMELINE_INCLUDE.SLOW;
111
+ const VUE_DEVTOOLS_TIMELINE_DEFAULTS = {
112
+ /** 默认回溯窗口(毫秒) */
113
+ windowMs: 5e3,
114
+ /** 默认只返回耗时 ≥ 该值的组件渲染记录(毫秒) */
115
+ minDurationMs: 4,
116
+ /** 默认明细档位 */
117
+ include: DEFAULT_TIMELINE_INCLUDE,
118
+ /** 默认明细条数上限 */
119
+ limit: 30,
120
+ /** 明细条数上限的硬上限 */
121
+ maxLimit: 200,
122
+ /** 页面侧环形缓冲容量(条) */
123
+ capacity: 1e3
89
124
  };
90
125
  const MUTATING_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "apply_patch"]);
91
126
  const WIDGET_THEME_MODES = ["auto", "light", "dark"];
@@ -154,6 +189,10 @@ export {
154
189
  START_API_PATH,
155
190
  VUE_DEVTOOLS_ACTIONS,
156
191
  VUE_DEVTOOLS_API_PATH,
192
+ VUE_DEVTOOLS_TIMELINE_DEFAULTS,
193
+ VUE_DEVTOOLS_TIMELINE_INCLUDE,
194
+ VUE_DEVTOOLS_TIMELINE_INCLUDES,
195
+ VUE_DEVTOOLS_TIMELINE_LAYERS,
157
196
  WARMUP_API_PATH,
158
197
  WIDGET_MSG,
159
198
  WIDGET_SCRIPT_PATH,
@@ -51,8 +51,20 @@ export declare function findTsconfigDir(filePath: string): string | null;
51
51
  export declare function findAllTsconfigDirs(workspace: string): string[];
52
52
  /** 运行 TypeScript 类型检查(tsc / vue-tsc 按项目自动选择)--build --noEmit,返回原始输出 */
53
53
  export declare function runTypeCheck(filePath: string | undefined, cwd: string): Promise<TscResult>;
54
+ /**
55
+ * 多文件类型检查:按最近的 tsconfig 项目分组,每个项目只跑一次 `--build --noEmit`,
56
+ * 再把项目输出切分回各文件(与单文件路径共用 filterTscOutputForFile)。
57
+ * 一次编辑批次里的 N 个文件因此从 N 次项目构建降到 1 次。
58
+ */
59
+ export declare function runTypeChecksForFiles(files: string[], cwd: string): Promise<Map<string, TscResult>>;
54
60
  /** 并行运行 ESLint + 类型检查(单文件或 glob) */
55
61
  export declare function runAllChecks(pattern: string, cwd: string): Promise<DiagnosticsResult>;
62
+ /**
63
+ * 批量运行 ESLint + 类型检查:Lint 逐文件(ESLint 进程内、oxlint 单次 CLI),
64
+ * 类型检查按 tsconfig 项目合并为一次(见 runTypeChecksForFiles)。
65
+ * 返回"文件绝对路径 → 诊断结果",供 step 边界的一次性收尾诊断使用。
66
+ */
67
+ export declare function runAllChecksForFiles(files: string[], cwd: string): Promise<Map<string, DiagnosticsResult>>;
56
68
  /**
57
69
  * 全量项目诊断:优先从根 tsconfig 运行一次类型检查 --build,
58
70
  * 根无 tsconfig 时回退到逐个子目录 build;ESLint 以 "." 全量扫描。
@@ -62,5 +74,17 @@ export declare function runProjectDiagnostics(workspace: string): Promise<Diagno
62
74
  export declare function tscSectionTitle(tscOutput: TscResult): string;
63
75
  /** Lint 分区标题(单一来源:跟随实际运行的引擎组合,如 "ESLint + oxlint") */
64
76
  export declare function lintSectionTitle(lintOutput: EslintOutput): string;
65
- /** 组装统一的分区诊断文本(Lint / 类型检查,空结果显示占位文案) */
66
- export declare function formatDiagnosticsSections(title: string, eslintOutput: EslintOutput, tscOutput: TscResult): string;
77
+ /** 分区发现被条数上限折叠时的提示(自动诊断摘要与 run_diagnostics 完整输出的分界) */
78
+ export declare function omittedFindingsHint(omitted: number): string;
79
+ /**
80
+ * 组装统一的分区诊断文本(Lint / 类型检查)。
81
+ * 默认保留空分区的占位文案(run_diagnostics 要正面回答"有没有问题");
82
+ * `onlyFindings: true` 只输出有发现的分区、全空返回空串(编辑后自动诊断不刷占位噪音);
83
+ * `maxFindingsPerSection` 把每个分区的发现折叠成有界摘要——编辑后自动诊断每个 step 都会投递,
84
+ * 因此用条数上限控制重复成本,完整结果仍由不带上限的 run_diagnostics 给出。
85
+ * `title` 允许为空串,此时直接返回分区正文(追加式投递场景无需标题)。
86
+ */
87
+ export declare function formatDiagnosticsSections(title: string, eslintOutput: EslintOutput, tscOutput: TscResult, options?: {
88
+ onlyFindings?: boolean;
89
+ maxFindingsPerSection?: number;
90
+ }): string;
@@ -342,6 +342,22 @@ function parseTscDiags(rawOutput, filePath, projectDir, source = "tsc") {
342
342
  }
343
343
  return diags;
344
344
  }
345
+ function filterTscOutputForFile(rawOutput, filePath, projectDir) {
346
+ const resolved = path.resolve(filePath);
347
+ const errorLinePat = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+TS\d+:/;
348
+ const filtered = [];
349
+ let keep = false;
350
+ for (const line of rawOutput.split("\n")) {
351
+ const m = errorLinePat.exec(line);
352
+ if (m) {
353
+ keep = path.resolve(projectDir, m[1]) === resolved;
354
+ } else if (!/^\s/.test(line)) {
355
+ keep = false;
356
+ }
357
+ if (keep) filtered.push(line);
358
+ }
359
+ return filtered.join("\n");
360
+ }
345
361
  async function runTypeCheck(filePath, cwd) {
346
362
  const dir = cwd;
347
363
  const projectDir = filePath ? findTsconfigDir(filePath) ?? dir : dir;
@@ -372,21 +388,7 @@ async function runTypeCheck(filePath, cwd) {
372
388
  }
373
389
  const diagnostics = parseTscDiags(rawOutput, filePath, projectDir, engine.source);
374
390
  if (filePath) {
375
- const resolved = path.resolve(filePath);
376
- const errorLinePat = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+TS\d+:/;
377
- const lines = rawOutput.split("\n");
378
- const filtered = [];
379
- let keep = false;
380
- for (const line of lines) {
381
- const m = errorLinePat.exec(line);
382
- if (m) {
383
- keep = path.resolve(projectDir, m[1]) === resolved;
384
- } else if (!/^\s/.test(line)) {
385
- keep = false;
386
- }
387
- if (keep) filtered.push(line);
388
- }
389
- rawOutput = filtered.join("\n");
391
+ rawOutput = filterTscOutputForFile(rawOutput, filePath, projectDir);
390
392
  }
391
393
  log.debug("type-check finished", {
392
394
  engine: engine.source,
@@ -396,6 +398,32 @@ async function runTypeCheck(filePath, cwd) {
396
398
  });
397
399
  return { rawOutput, exitCode, diagnostics, source: engine.source };
398
400
  }
401
+ async function runTypeChecksForFiles(files, cwd) {
402
+ const groups = /* @__PURE__ */ new Map();
403
+ for (const file of files) {
404
+ const resolved = path.resolve(file);
405
+ const projectDir = findTsconfigDir(resolved) ?? cwd;
406
+ const group = groups.get(projectDir);
407
+ if (group) group.push(resolved);
408
+ else groups.set(projectDir, [resolved]);
409
+ }
410
+ const results = /* @__PURE__ */ new Map();
411
+ for (const [projectDir, group] of groups) {
412
+ const whole = await runTypeCheck(void 0, projectDir).catch(() => ({
413
+ rawOutput: "",
414
+ exitCode: 0
415
+ }));
416
+ for (const file of group) {
417
+ results.set(file, {
418
+ rawOutput: filterTscOutputForFile(whole.rawOutput, file, projectDir),
419
+ exitCode: whole.exitCode,
420
+ diagnostics: (whole.diagnostics ?? []).filter((d) => d.file === file),
421
+ source: whole.source
422
+ });
423
+ }
424
+ }
425
+ return results;
426
+ }
399
427
  async function runAllChecks(pattern, cwd) {
400
428
  log.debug("runAllChecks", { pattern, cwd });
401
429
  const [eslintOutput, tscOutput] = await Promise.all([
@@ -404,6 +432,18 @@ async function runAllChecks(pattern, cwd) {
404
432
  ]);
405
433
  return { eslintOutput, tscOutput };
406
434
  }
435
+ async function runAllChecksForFiles(files, cwd) {
436
+ const targets = [...new Set(files.map((file) => path.resolve(file)))];
437
+ const tscByFile = await runTypeChecksForFiles(targets, cwd);
438
+ const results = /* @__PURE__ */ new Map();
439
+ for (const file of targets) {
440
+ results.set(file, {
441
+ eslintOutput: await lintFiles(file, cwd),
442
+ tscOutput: tscByFile.get(file) ?? { rawOutput: "", exitCode: 0 }
443
+ });
444
+ }
445
+ return results;
446
+ }
407
447
  async function runProjectDiagnostics(workspace) {
408
448
  const tscDirs = fs.existsSync(path.join(workspace, "tsconfig.json")) ? [workspace] : findAllTsconfigDirs(workspace);
409
449
  log.debug("Tsc dirs to check", { count: tscDirs.length, dirs: tscDirs });
@@ -425,18 +465,36 @@ function tscSectionTitle(tscOutput) {
425
465
  function lintSectionTitle(lintOutput) {
426
466
  return lintOutput.engines?.length ? lintOutput.engines.join(" + ") : "ESLint";
427
467
  }
428
- function formatDiagnosticsSections(title, eslintOutput, tscOutput) {
468
+ function omittedFindingsHint(omitted) {
469
+ return `\u2026\u8FD8\u6709 ${omitted} \u6761\uFF0C\u5B8C\u6574\u7ED3\u679C\u8BF7\u8C03\u7528 run_diagnostics`;
470
+ }
471
+ function boundFindings(text, maxFindings) {
472
+ if (!maxFindings || !text) return text;
473
+ const lines = text.split("\n").filter((line) => line.trim() !== "");
474
+ if (lines.length <= maxFindings) return text;
475
+ return [...lines.slice(0, maxFindings), omittedFindingsHint(lines.length - maxFindings)].join(
476
+ "\n"
477
+ );
478
+ }
479
+ function formatDiagnosticsSections(title, eslintOutput, tscOutput, options = {}) {
480
+ const lintLines = boundFindings(eslintOutput.text, options.maxFindingsPerSection);
481
+ const tscLines = boundFindings(tscOutput.rawOutput.trim(), options.maxFindingsPerSection);
429
482
  const parts = [];
430
- parts.push(`## ${lintSectionTitle(eslintOutput)}
483
+ if (!options.onlyFindings || lintLines) {
484
+ parts.push(`## ${lintSectionTitle(eslintOutput)}
431
485
 
432
- ` + (eslintOutput.text || "\u6CA1\u6709\u53D1\u73B0\u95EE\u9898"));
433
- const tscLines = tscOutput.rawOutput.trim();
434
- parts.push(`## ${tscSectionTitle(tscOutput)}
486
+ ` + (lintLines || "\u6CA1\u6709\u53D1\u73B0\u95EE\u9898"));
487
+ }
488
+ if (!options.onlyFindings || tscLines) {
489
+ parts.push(`## ${tscSectionTitle(tscOutput)}
435
490
 
436
491
  ` + (tscLines || "\u6CA1\u6709\u53D1\u73B0\u7C7B\u578B\u9519\u8BEF"));
437
- return `${title}
492
+ }
493
+ if (parts.length === 0) return "";
494
+ const body = parts.join("\n\n");
495
+ return title ? `${title}
438
496
 
439
- ` + parts.join("\n\n");
497
+ ${body}` : body;
440
498
  }
441
499
  export {
442
500
  DIAGNOSTICS_TOOL_DESCRIPTION,
@@ -446,8 +504,11 @@ export {
446
504
  isJsFile,
447
505
  lintFiles,
448
506
  lintSectionTitle,
507
+ omittedFindingsHint,
449
508
  runAllChecks,
509
+ runAllChecksForFiles,
450
510
  runProjectDiagnostics,
451
511
  runTypeCheck,
512
+ runTypeChecksForFiles,
452
513
  tscSectionTitle
453
514
  };
@@ -60,6 +60,10 @@ __export(constants_exports, {
60
60
  START_API_PATH: () => START_API_PATH,
61
61
  VUE_DEVTOOLS_ACTIONS: () => VUE_DEVTOOLS_ACTIONS,
62
62
  VUE_DEVTOOLS_API_PATH: () => VUE_DEVTOOLS_API_PATH,
63
+ VUE_DEVTOOLS_TIMELINE_DEFAULTS: () => VUE_DEVTOOLS_TIMELINE_DEFAULTS,
64
+ VUE_DEVTOOLS_TIMELINE_INCLUDE: () => VUE_DEVTOOLS_TIMELINE_INCLUDE,
65
+ VUE_DEVTOOLS_TIMELINE_INCLUDES: () => VUE_DEVTOOLS_TIMELINE_INCLUDES,
66
+ VUE_DEVTOOLS_TIMELINE_LAYERS: () => VUE_DEVTOOLS_TIMELINE_LAYERS,
63
67
  WARMUP_API_PATH: () => WARMUP_API_PATH,
64
68
  WIDGET_MSG: () => WIDGET_MSG,
65
69
  WIDGET_SCRIPT_PATH: () => WIDGET_SCRIPT_PATH,
@@ -154,7 +158,42 @@ const VUE_DEVTOOLS_ACTIONS = {
154
158
  GET_COMPONENT_RENDER_CODE: "getComponentRenderCode",
155
159
  GET_APPS: "getApps",
156
160
  TOGGLE_APP: "toggleApp",
157
- GET_ROUTER_INFO: "getRouterInfo"
161
+ GET_ROUTER_INFO: "getRouterInfo",
162
+ GET_TIMELINE: "getTimeline",
163
+ MARK_TIMELINE: "markTimeline",
164
+ CLEAR_TIMELINE: "clearTimeline"
165
+ };
166
+ const VUE_DEVTOOLS_TIMELINE_LAYERS = [
167
+ "perf",
168
+ "lifecycle",
169
+ "emit",
170
+ "navigate",
171
+ "agent"
172
+ ];
173
+ const VUE_DEVTOOLS_TIMELINE_INCLUDE = {
174
+ SUMMARY: "summary",
175
+ SLOW: "slow",
176
+ ALL: "all"
177
+ };
178
+ const VUE_DEVTOOLS_TIMELINE_INCLUDES = [
179
+ VUE_DEVTOOLS_TIMELINE_INCLUDE.SUMMARY,
180
+ VUE_DEVTOOLS_TIMELINE_INCLUDE.SLOW,
181
+ VUE_DEVTOOLS_TIMELINE_INCLUDE.ALL
182
+ ];
183
+ const DEFAULT_TIMELINE_INCLUDE = VUE_DEVTOOLS_TIMELINE_INCLUDE.SLOW;
184
+ const VUE_DEVTOOLS_TIMELINE_DEFAULTS = {
185
+ /** 默认回溯窗口(毫秒) */
186
+ windowMs: 5e3,
187
+ /** 默认只返回耗时 ≥ 该值的组件渲染记录(毫秒) */
188
+ minDurationMs: 4,
189
+ /** 默认明细档位 */
190
+ include: DEFAULT_TIMELINE_INCLUDE,
191
+ /** 默认明细条数上限 */
192
+ limit: 30,
193
+ /** 明细条数上限的硬上限 */
194
+ maxLimit: 200,
195
+ /** 页面侧环形缓冲容量(条) */
196
+ capacity: 1e3
158
197
  };
159
198
  const MUTATING_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "apply_patch"]);
160
199
  const WIDGET_THEME_MODES = ["auto", "light", "dark"];
@@ -224,6 +263,10 @@ const INSPECTOR_ADAPTER_IDS = {
224
263
  START_API_PATH,
225
264
  VUE_DEVTOOLS_ACTIONS,
226
265
  VUE_DEVTOOLS_API_PATH,
266
+ VUE_DEVTOOLS_TIMELINE_DEFAULTS,
267
+ VUE_DEVTOOLS_TIMELINE_INCLUDE,
268
+ VUE_DEVTOOLS_TIMELINE_INCLUDES,
269
+ VUE_DEVTOOLS_TIMELINE_LAYERS,
227
270
  WARMUP_API_PATH,
228
271
  WIDGET_MSG,
229
272
  WIDGET_SCRIPT_PATH,
@@ -155,8 +155,40 @@ export declare const VUE_DEVTOOLS_ACTIONS: {
155
155
  readonly GET_APPS: "getApps";
156
156
  readonly TOGGLE_APP: "toggleApp";
157
157
  readonly GET_ROUTER_INFO: "getRouterInfo";
158
+ readonly GET_TIMELINE: "getTimeline";
159
+ readonly MARK_TIMELINE: "markTimeline";
160
+ readonly CLEAR_TIMELINE: "clearTimeline";
158
161
  };
159
162
  export type VueDevtoolsAction = (typeof VUE_DEVTOOLS_ACTIONS)[keyof typeof VUE_DEVTOOLS_ACTIONS];
163
+ /**
164
+ * 时间线层名(工具 schema 的 layer 枚举与页面侧采集器共用)。
165
+ * lifecycle 只累计计数、不产生明细记录;其余层都是明细记录。
166
+ */
167
+ export declare const VUE_DEVTOOLS_TIMELINE_LAYERS: readonly ["perf", "lifecycle", "emit", "navigate", "agent"];
168
+ export type VueDevtoolsTimelineLayer = (typeof VUE_DEVTOOLS_TIMELINE_LAYERS)[number];
169
+ /** 时间线明细档位(唯一来源):数组、类型与页面侧的分支比较都引用这里,避免散落字面量 */
170
+ export declare const VUE_DEVTOOLS_TIMELINE_INCLUDE: {
171
+ readonly SUMMARY: "summary";
172
+ readonly SLOW: "slow";
173
+ readonly ALL: "all";
174
+ };
175
+ export declare const VUE_DEVTOOLS_TIMELINE_INCLUDES: readonly ["summary", "slow", "all"];
176
+ export type VueDevtoolsTimelineInclude = (typeof VUE_DEVTOOLS_TIMELINE_INCLUDES)[number];
177
+ /** 时间线查询默认值(MCP 工具 schema 与页面侧采集器共用,避免两处漂移) */
178
+ export declare const VUE_DEVTOOLS_TIMELINE_DEFAULTS: {
179
+ /** 默认回溯窗口(毫秒) */
180
+ readonly windowMs: 5000;
181
+ /** 默认只返回耗时 ≥ 该值的组件渲染记录(毫秒) */
182
+ readonly minDurationMs: 4;
183
+ /** 默认明细档位 */
184
+ readonly include: "slow";
185
+ /** 默认明细条数上限 */
186
+ readonly limit: 30;
187
+ /** 明细条数上限的硬上限 */
188
+ readonly maxLimit: 200;
189
+ /** 页面侧环形缓冲容量(条) */
190
+ readonly capacity: 1000;
191
+ };
160
192
  /** ==================== 写类工具名单 ==================== */
161
193
  /** 会修改文件的工具名(host 插件与编辑后自动诊断共用的单一来源) */
162
194
  export declare const MUTATING_TOOLS: ReadonlySet<string>;
@@ -34,9 +34,12 @@ __export(diagnostics_exports, {
34
34
  isJsFile: () => isJsFile,
35
35
  lintFiles: () => lintFiles,
36
36
  lintSectionTitle: () => lintSectionTitle,
37
+ omittedFindingsHint: () => omittedFindingsHint,
37
38
  runAllChecks: () => runAllChecks,
39
+ runAllChecksForFiles: () => runAllChecksForFiles,
38
40
  runProjectDiagnostics: () => runProjectDiagnostics,
39
41
  runTypeCheck: () => runTypeCheck,
42
+ runTypeChecksForFiles: () => runTypeChecksForFiles,
40
43
  tscSectionTitle: () => tscSectionTitle
41
44
  });
42
45
  module.exports = __toCommonJS(diagnostics_exports);
@@ -385,6 +388,22 @@ function parseTscDiags(rawOutput, filePath, projectDir, source = "tsc") {
385
388
  }
386
389
  return diags;
387
390
  }
391
+ function filterTscOutputForFile(rawOutput, filePath, projectDir) {
392
+ const resolved = import_node_path.default.resolve(filePath);
393
+ const errorLinePat = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+TS\d+:/;
394
+ const filtered = [];
395
+ let keep = false;
396
+ for (const line of rawOutput.split("\n")) {
397
+ const m = errorLinePat.exec(line);
398
+ if (m) {
399
+ keep = import_node_path.default.resolve(projectDir, m[1]) === resolved;
400
+ } else if (!/^\s/.test(line)) {
401
+ keep = false;
402
+ }
403
+ if (keep) filtered.push(line);
404
+ }
405
+ return filtered.join("\n");
406
+ }
388
407
  async function runTypeCheck(filePath, cwd) {
389
408
  const dir = cwd;
390
409
  const projectDir = filePath ? findTsconfigDir(filePath) ?? dir : dir;
@@ -415,21 +434,7 @@ async function runTypeCheck(filePath, cwd) {
415
434
  }
416
435
  const diagnostics = parseTscDiags(rawOutput, filePath, projectDir, engine.source);
417
436
  if (filePath) {
418
- const resolved = import_node_path.default.resolve(filePath);
419
- const errorLinePat = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+TS\d+:/;
420
- const lines = rawOutput.split("\n");
421
- const filtered = [];
422
- let keep = false;
423
- for (const line of lines) {
424
- const m = errorLinePat.exec(line);
425
- if (m) {
426
- keep = import_node_path.default.resolve(projectDir, m[1]) === resolved;
427
- } else if (!/^\s/.test(line)) {
428
- keep = false;
429
- }
430
- if (keep) filtered.push(line);
431
- }
432
- rawOutput = filtered.join("\n");
437
+ rawOutput = filterTscOutputForFile(rawOutput, filePath, projectDir);
433
438
  }
434
439
  log.debug("type-check finished", {
435
440
  engine: engine.source,
@@ -439,6 +444,32 @@ async function runTypeCheck(filePath, cwd) {
439
444
  });
440
445
  return { rawOutput, exitCode, diagnostics, source: engine.source };
441
446
  }
447
+ async function runTypeChecksForFiles(files, cwd) {
448
+ const groups = /* @__PURE__ */ new Map();
449
+ for (const file of files) {
450
+ const resolved = import_node_path.default.resolve(file);
451
+ const projectDir = findTsconfigDir(resolved) ?? cwd;
452
+ const group = groups.get(projectDir);
453
+ if (group) group.push(resolved);
454
+ else groups.set(projectDir, [resolved]);
455
+ }
456
+ const results = /* @__PURE__ */ new Map();
457
+ for (const [projectDir, group] of groups) {
458
+ const whole = await runTypeCheck(void 0, projectDir).catch(() => ({
459
+ rawOutput: "",
460
+ exitCode: 0
461
+ }));
462
+ for (const file of group) {
463
+ results.set(file, {
464
+ rawOutput: filterTscOutputForFile(whole.rawOutput, file, projectDir),
465
+ exitCode: whole.exitCode,
466
+ diagnostics: (whole.diagnostics ?? []).filter((d) => d.file === file),
467
+ source: whole.source
468
+ });
469
+ }
470
+ }
471
+ return results;
472
+ }
442
473
  async function runAllChecks(pattern, cwd) {
443
474
  log.debug("runAllChecks", { pattern, cwd });
444
475
  const [eslintOutput, tscOutput] = await Promise.all([
@@ -447,6 +478,18 @@ async function runAllChecks(pattern, cwd) {
447
478
  ]);
448
479
  return { eslintOutput, tscOutput };
449
480
  }
481
+ async function runAllChecksForFiles(files, cwd) {
482
+ const targets = [...new Set(files.map((file) => import_node_path.default.resolve(file)))];
483
+ const tscByFile = await runTypeChecksForFiles(targets, cwd);
484
+ const results = /* @__PURE__ */ new Map();
485
+ for (const file of targets) {
486
+ results.set(file, {
487
+ eslintOutput: await lintFiles(file, cwd),
488
+ tscOutput: tscByFile.get(file) ?? { rawOutput: "", exitCode: 0 }
489
+ });
490
+ }
491
+ return results;
492
+ }
450
493
  async function runProjectDiagnostics(workspace) {
451
494
  const tscDirs = import_node_fs.default.existsSync(import_node_path.default.join(workspace, "tsconfig.json")) ? [workspace] : findAllTsconfigDirs(workspace);
452
495
  log.debug("Tsc dirs to check", { count: tscDirs.length, dirs: tscDirs });
@@ -468,18 +511,36 @@ function tscSectionTitle(tscOutput) {
468
511
  function lintSectionTitle(lintOutput) {
469
512
  return lintOutput.engines?.length ? lintOutput.engines.join(" + ") : "ESLint";
470
513
  }
471
- function formatDiagnosticsSections(title, eslintOutput, tscOutput) {
514
+ function omittedFindingsHint(omitted) {
515
+ return `\u2026\u8FD8\u6709 ${omitted} \u6761\uFF0C\u5B8C\u6574\u7ED3\u679C\u8BF7\u8C03\u7528 run_diagnostics`;
516
+ }
517
+ function boundFindings(text, maxFindings) {
518
+ if (!maxFindings || !text) return text;
519
+ const lines = text.split("\n").filter((line) => line.trim() !== "");
520
+ if (lines.length <= maxFindings) return text;
521
+ return [...lines.slice(0, maxFindings), omittedFindingsHint(lines.length - maxFindings)].join(
522
+ "\n"
523
+ );
524
+ }
525
+ function formatDiagnosticsSections(title, eslintOutput, tscOutput, options = {}) {
526
+ const lintLines = boundFindings(eslintOutput.text, options.maxFindingsPerSection);
527
+ const tscLines = boundFindings(tscOutput.rawOutput.trim(), options.maxFindingsPerSection);
472
528
  const parts = [];
473
- parts.push(`## ${lintSectionTitle(eslintOutput)}
529
+ if (!options.onlyFindings || lintLines) {
530
+ parts.push(`## ${lintSectionTitle(eslintOutput)}
474
531
 
475
- ` + (eslintOutput.text || "\u6CA1\u6709\u53D1\u73B0\u95EE\u9898"));
476
- const tscLines = tscOutput.rawOutput.trim();
477
- parts.push(`## ${tscSectionTitle(tscOutput)}
532
+ ` + (lintLines || "\u6CA1\u6709\u53D1\u73B0\u95EE\u9898"));
533
+ }
534
+ if (!options.onlyFindings || tscLines) {
535
+ parts.push(`## ${tscSectionTitle(tscOutput)}
478
536
 
479
537
  ` + (tscLines || "\u6CA1\u6709\u53D1\u73B0\u7C7B\u578B\u9519\u8BEF"));
480
- return `${title}
538
+ }
539
+ if (parts.length === 0) return "";
540
+ const body = parts.join("\n\n");
541
+ return title ? `${title}
481
542
 
482
- ` + parts.join("\n\n");
543
+ ${body}` : body;
483
544
  }
484
545
  // Annotate the CommonJS export names for ESM import in node:
485
546
  0 && (module.exports = {
@@ -490,8 +551,11 @@ function formatDiagnosticsSections(title, eslintOutput, tscOutput) {
490
551
  isJsFile,
491
552
  lintFiles,
492
553
  lintSectionTitle,
554
+ omittedFindingsHint,
493
555
  runAllChecks,
556
+ runAllChecksForFiles,
494
557
  runProjectDiagnostics,
495
558
  runTypeCheck,
559
+ runTypeChecksForFiles,
496
560
  tscSectionTitle
497
561
  });
@@ -51,8 +51,20 @@ export declare function findTsconfigDir(filePath: string): string | null;
51
51
  export declare function findAllTsconfigDirs(workspace: string): string[];
52
52
  /** 运行 TypeScript 类型检查(tsc / vue-tsc 按项目自动选择)--build --noEmit,返回原始输出 */
53
53
  export declare function runTypeCheck(filePath: string | undefined, cwd: string): Promise<TscResult>;
54
+ /**
55
+ * 多文件类型检查:按最近的 tsconfig 项目分组,每个项目只跑一次 `--build --noEmit`,
56
+ * 再把项目输出切分回各文件(与单文件路径共用 filterTscOutputForFile)。
57
+ * 一次编辑批次里的 N 个文件因此从 N 次项目构建降到 1 次。
58
+ */
59
+ export declare function runTypeChecksForFiles(files: string[], cwd: string): Promise<Map<string, TscResult>>;
54
60
  /** 并行运行 ESLint + 类型检查(单文件或 glob) */
55
61
  export declare function runAllChecks(pattern: string, cwd: string): Promise<DiagnosticsResult>;
62
+ /**
63
+ * 批量运行 ESLint + 类型检查:Lint 逐文件(ESLint 进程内、oxlint 单次 CLI),
64
+ * 类型检查按 tsconfig 项目合并为一次(见 runTypeChecksForFiles)。
65
+ * 返回"文件绝对路径 → 诊断结果",供 step 边界的一次性收尾诊断使用。
66
+ */
67
+ export declare function runAllChecksForFiles(files: string[], cwd: string): Promise<Map<string, DiagnosticsResult>>;
56
68
  /**
57
69
  * 全量项目诊断:优先从根 tsconfig 运行一次类型检查 --build,
58
70
  * 根无 tsconfig 时回退到逐个子目录 build;ESLint 以 "." 全量扫描。
@@ -62,5 +74,17 @@ export declare function runProjectDiagnostics(workspace: string): Promise<Diagno
62
74
  export declare function tscSectionTitle(tscOutput: TscResult): string;
63
75
  /** Lint 分区标题(单一来源:跟随实际运行的引擎组合,如 "ESLint + oxlint") */
64
76
  export declare function lintSectionTitle(lintOutput: EslintOutput): string;
65
- /** 组装统一的分区诊断文本(Lint / 类型检查,空结果显示占位文案) */
66
- export declare function formatDiagnosticsSections(title: string, eslintOutput: EslintOutput, tscOutput: TscResult): string;
77
+ /** 分区发现被条数上限折叠时的提示(自动诊断摘要与 run_diagnostics 完整输出的分界) */
78
+ export declare function omittedFindingsHint(omitted: number): string;
79
+ /**
80
+ * 组装统一的分区诊断文本(Lint / 类型检查)。
81
+ * 默认保留空分区的占位文案(run_diagnostics 要正面回答"有没有问题");
82
+ * `onlyFindings: true` 只输出有发现的分区、全空返回空串(编辑后自动诊断不刷占位噪音);
83
+ * `maxFindingsPerSection` 把每个分区的发现折叠成有界摘要——编辑后自动诊断每个 step 都会投递,
84
+ * 因此用条数上限控制重复成本,完整结果仍由不带上限的 run_diagnostics 给出。
85
+ * `title` 允许为空串,此时直接返回分区正文(追加式投递场景无需标题)。
86
+ */
87
+ export declare function formatDiagnosticsSections(title: string, eslintOutput: EslintOutput, tscOutput: TscResult, options?: {
88
+ onlyFindings?: boolean;
89
+ maxFindingsPerSection?: number;
90
+ }): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aipanel/core",
3
- "version": "1.2.27",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "main": "lib/index.cjs",