@aipanel/core 1.3.4 → 1.3.6

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.
@@ -196,14 +196,15 @@ export declare const MUTATING_TOOLS: ReadonlySet<string>;
196
196
  /** 挂件主题可选值(对应 AIPanelWidgetTheme) */
197
197
  export declare const WIDGET_THEME_MODES: readonly ["auto", "light", "dark"];
198
198
  /** ==================== OpenCode 环境变量名 ==================== */
199
- /** OpenCode 相关环境变量名(opencode provider 写、es/plugins 与 dsh-plugin 读,统一引用防止字面量漂移) */
199
+ /** OpenCode 相关环境变量名(opencode provider 写、es/plugins 读,统一引用防止字面量漂移) */
200
200
  export declare const OPENCODE_ENV: {
201
201
  readonly CONFIG_DIR: "OPENCODE_CONFIG_DIR";
202
202
  readonly CONTEXT_API_URL: "OPENCODE_CONTEXT_API_URL";
203
203
  readonly VITE_LOGS_API_URL: "OPENCODE_VITE_LOGS_API_URL";
204
204
  readonly LOG_FILES_JSON: "OPENCODE_LOG_FILES_JSON";
205
205
  readonly VERBOSE: "OPENCODE_VERBOSE";
206
- readonly ENABLE_LINT: "OPENCODE_ENABLE_LINT";
206
+ /** 诊断策略 JSON(DiagnosticsPolicy):provider 写,opencode 插件读 */
207
+ readonly DIAGNOSTICS: "OPENCODE_DIAGNOSTICS";
207
208
  readonly VUE_DEVTOOLS_API_URL: "OPENCODE_VUE_DEVTOOLS_API_URL";
208
209
  readonly WORKSPACE: "OPENCODE_WORKSPACE";
209
210
  };
@@ -130,7 +130,8 @@ const OPENCODE_ENV = {
130
130
  VITE_LOGS_API_URL: "OPENCODE_VITE_LOGS_API_URL",
131
131
  LOG_FILES_JSON: "OPENCODE_LOG_FILES_JSON",
132
132
  VERBOSE: "OPENCODE_VERBOSE",
133
- ENABLE_LINT: "OPENCODE_ENABLE_LINT",
133
+ /** 诊断策略 JSON(DiagnosticsPolicy):provider 写,opencode 插件读 */
134
+ DIAGNOSTICS: "OPENCODE_DIAGNOSTICS",
134
135
  VUE_DEVTOOLS_API_URL: "OPENCODE_VUE_DEVTOOLS_API_URL",
135
136
  WORKSPACE: "OPENCODE_WORKSPACE"
136
137
  };
@@ -0,0 +1,199 @@
1
+ /**
2
+ * 诊断的用户配置契约(provider 无关)。
3
+ *
4
+ * 用户配置面只有一处:vite 插件的 `providerOptions.diagnostics`。核心层在这里定义策略形状、
5
+ * 检查来源(内置引擎 / 项目命令)、输出适配器协议与默认值;两个 provider(deepseek / opencode)
6
+ * 与两侧宿主插件共用同一份语义,因此"用户配置了什么"在每个宿主上含义一致。
7
+ *
8
+ * 不在这里定义的东西:排除规则(ignore)—— 那是底层工具的事(ESLint 自身 ignore、tsconfig
9
+ * `exclude`、命令自己的 glob),我们不复制一套 glob 语义。
10
+ */
11
+ import type { AIPanelDiagnosticEntry } from "./types";
12
+ /** 诊断阶段:编辑后自动诊断 / agent 手动调用工具 */
13
+ export type DiagnosticsPhase = "edit" | "manual";
14
+ /** 检查在哪些阶段执行(缺省 both) */
15
+ export type DiagnosticsRun = DiagnosticsPhase | "both";
16
+ /**
17
+ * 类型检查检查:唯一的"引擎级"内置(要 tsconfig 感知的批量归并、项目本地 tsc/vue-tsc 探测
18
+ * 与自带 vue-tsc 兜底,不是一条命令能表达的)。其余一切检查都走同一条命令执行路径。
19
+ */
20
+ export interface TypecheckCheck {
21
+ builtin: "typecheck";
22
+ run?: DiagnosticsRun;
23
+ /** 只吃这些扩展名(如 [".ts", ".vue"]);缺省 = 可诊断源码扩展名(与历史行为一致) */
24
+ extensions?: string[];
25
+ }
26
+ /** 命令输出的内置适配器名(内置适配器由我们开发维护) */
27
+ export type DiagnosticsFormat = "text" | "tsc" | "eslint-json" | "oxlint-json" | "stylelint-json" | "aipanel-json";
28
+ /**
29
+ * 命令检查:跑用户项目里的一条命令(或项目本地某个包的 bin),输出经适配器归一成分区。
30
+ *
31
+ * 命令按 argv 直传、不经 shell(见 ADR-0002);需要 shell 语义时由用户显式选择解释器。
32
+ * `command` 与 `bin` 二选一:`bin` 走"项目本地包解析"(与内置预设同一策略),
33
+ * 因此 stylelint / biome / 自研工具包都是同一条路,不需要为每个工具内置特判。
34
+ */
35
+ export interface CommandCheck {
36
+ /** 分区标题(模型与诊断卡片都看到它) */
37
+ name: string;
38
+ /** argv[0]:命令名(PATH 解析)或路径;与 `bin` 二选一 */
39
+ command?: string;
40
+ /** 项目本地包的 bin 名(同时也是包名):解析不到时给明确文案;与 `command` 二选一 */
41
+ bin?: string;
42
+ /** argv[1..];可含 `{file}`(逐文件)与 `{files}`(一次传全部)占位符 */
43
+ args?: string[];
44
+ /**
45
+ * 无目标文件(全量诊断)时的 argv。
46
+ * 含占位符的检查没写它 → 全量诊断跳过该检查;不带占位符的检查三种目标都跑同一个 argv。
47
+ */
48
+ projectArgs?: string[];
49
+ /** 只吃这些扩展名(如 [".css", ".scss"]);缺省 = 不限 */
50
+ extensions?: string[];
51
+ /** 相对项目根,默认项目根 */
52
+ cwd?: string;
53
+ run?: DiagnosticsRun;
54
+ /** 内置适配器名;与 `adapter` 同时给出时 `adapter` 优先 */
55
+ format?: DiagnosticsFormat;
56
+ /** 用户适配器模块路径(相对项目根);default export 一个 {@link DiagnosticsAdapter} */
57
+ adapter?: string;
58
+ /** 命令超时(ms),默认 {@link COMMAND_CHECK_TIMEOUT_MS} */
59
+ timeoutMs?: number;
60
+ }
61
+ /**
62
+ * 内置 linter 预设:只写 `builtin: "eslint"` 就能用——包名 / bin / 默认 argv / 输出格式 /
63
+ * 扩展名都取 {@link BUILTIN_LINTERS} 的目录项,展开后与用户自定义的 {@link CommandCheck}
64
+ * **完全同形**,走同一条执行与适配逻辑。可局部覆盖(args / extensions / format / run …)。
65
+ */
66
+ export type LinterPresetCheck = {
67
+ builtin: BuiltinLinterId;
68
+ args?: string[];
69
+ projectArgs?: string[];
70
+ extensions?: string[];
71
+ format?: DiagnosticsFormat;
72
+ cwd?: string;
73
+ run?: DiagnosticsRun;
74
+ timeoutMs?: number;
75
+ };
76
+ export type DiagnosticsCheck = TypecheckCheck | LinterPresetCheck | CommandCheck;
77
+ /** 诊断策略:用户可自定义的全部行为 */
78
+ export interface DiagnosticsPolicy {
79
+ /** 检查来源;缺省(或归一化后为空)= 内置 lint + typecheck */
80
+ checks: DiagnosticsCheck[];
81
+ /** 编辑后自动诊断(tools/post-execute 登记 → agent/pre-step 收尾注入) */
82
+ auto: boolean;
83
+ /** 是否把 run_diagnostics 暴露给模型 */
84
+ exposeTool: boolean;
85
+ /** 投递门槛:`error` 只报错误,`warning` 错误与警告都报 */
86
+ severity: "error" | "warning";
87
+ /** 自动诊断每个分区最多列出的发现条数 */
88
+ maxFindingsPerSection: number;
89
+ /** 自动诊断注入消息的字符上限 */
90
+ maxMessageChars: number;
91
+ }
92
+ /** 用户适配器入参:命令的原始产物 + 本次目标 */
93
+ export interface DiagnosticsAdapterInput {
94
+ /** 分区名(check.name) */
95
+ name: string;
96
+ stdout: string;
97
+ stderr: string;
98
+ /** 超时 / 被信号杀死时为 undefined */
99
+ exitCode: number | undefined;
100
+ /** 本次目标文件(绝对路径);项目级检查为空数组 */
101
+ files: string[];
102
+ /** 项目根 */
103
+ cwd: string;
104
+ }
105
+ /**
106
+ * 用户适配器出参。
107
+ * 提供 `diagnostics` 时文本由 AIPanel 统一渲染(保证 severity 门槛与诊断卡片一致);
108
+ * 只有需要自定义叙述时才只给 `text`(此时无结构化条目、也不参与 severity 过滤)。
109
+ */
110
+ export interface DiagnosticsAdapterOutput {
111
+ text?: string;
112
+ diagnostics?: AIPanelDiagnosticEntry[];
113
+ }
114
+ export type DiagnosticsAdapter = (input: DiagnosticsAdapterInput) => DiagnosticsAdapterOutput | Promise<DiagnosticsAdapterOutput>;
115
+ /** 命令检查默认超时 */
116
+ export declare const COMMAND_CHECK_TIMEOUT_MS = 60000;
117
+ /**
118
+ * 可诊断源码扩展名(内置 lint / typecheck 的默认匹配集)。
119
+ * node 侧的 `JS_EXTENSIONS` 引它,保持单一来源。
120
+ */
121
+ export declare const SOURCE_EXTENSIONS: readonly string[];
122
+ /**
123
+ * 目标文件是否命中检查声明的扩展名。
124
+ * 未声明(undefined / 空)表示不限;实现保持 client-safe(不引 node:path)。
125
+ */
126
+ export declare function matchesExtensions(filePath: string, extensions: readonly string[] | undefined): boolean;
127
+ /** 内置 linter 目录项:一个预设 = 一条命令检查的全部默认值(纯数据) */
128
+ export interface BuiltinLinterSpec {
129
+ /** 分区标题 */
130
+ label: string;
131
+ /** 项目本地包名(bin 从项目 package.json 解析,解析不到即视为未安装) */
132
+ package: string;
133
+ /** bin 名;缺省与包名相同 */
134
+ bin?: string;
135
+ /** 默认 argv(可含 `{file}` / `{files}` 占位符) */
136
+ args: string[];
137
+ /** 全量诊断时的 argv(含占位符的预设必须给,否则全量时跳过) */
138
+ projectArgs?: string[];
139
+ /** 默认匹配的扩展名 */
140
+ extensions: string[];
141
+ /** 默认输出适配器(内置工具一律能出结构化诊断,因此卡片/severity 都生效) */
142
+ format: DiagnosticsFormat;
143
+ }
144
+ /**
145
+ * 内置 linter 目录(数据表)。加一个常见 lint = 加一行;预设展开后与用户自定义检查同形,
146
+ * 走同一条执行 + 适配逻辑,所以"内置"与"自定义"没有第二套底层实现。
147
+ */
148
+ export declare const BUILTIN_LINTERS: {
149
+ readonly eslint: {
150
+ readonly label: "ESLint";
151
+ readonly package: "eslint";
152
+ readonly args: ["--format", "json", "{files}"];
153
+ readonly projectArgs: ["--format", "json", "."];
154
+ readonly extensions: string[];
155
+ readonly format: "eslint-json";
156
+ };
157
+ readonly oxlint: {
158
+ readonly label: "oxlint";
159
+ readonly package: "oxlint";
160
+ readonly args: ["--format=json", "--ignore-pattern", "node_modules", "{files}"];
161
+ readonly projectArgs: ["--format=json", "--ignore-pattern", "node_modules", "."];
162
+ readonly extensions: string[];
163
+ readonly format: "oxlint-json";
164
+ };
165
+ readonly stylelint: {
166
+ readonly label: "Stylelint";
167
+ readonly package: "stylelint";
168
+ readonly args: ["--formatter", "json", "{files}"];
169
+ readonly projectArgs: ["--formatter", "json", "**/*.{css,scss,less}"];
170
+ readonly extensions: [".css", ".scss", ".less"];
171
+ readonly format: "stylelint-json";
172
+ };
173
+ };
174
+ /** 内置 linter 预设 id */
175
+ export type BuiltinLinterId = keyof typeof BUILTIN_LINTERS;
176
+ /** 自动诊断默认分区条数上限(与历史行为一致) */
177
+ export declare const DEFAULT_MAX_FINDINGS_PER_SECTION = 3;
178
+ /** 自动诊断默认注入字符上限(与历史行为一致) */
179
+ export declare const DEFAULT_MAX_MESSAGE_CHARS = 4000;
180
+ /**
181
+ * 缺省检查来源(= 未配置 diagnostics 时的行为):
182
+ * 项目装了哪个内置 linter 就跑哪个(没装的自动跳过),类型检查始终在。
183
+ */
184
+ export declare const DEFAULT_BUILTIN_CHECKS: readonly DiagnosticsCheck[];
185
+ /** 策略默认值(单一来源:provider 的默认值、插件的兜底都引它) */
186
+ export declare const DEFAULT_DIAGNOSTICS_POLICY: DiagnosticsPolicy;
187
+ /** 是否为命令检查(用户自定义:既无内置 linter 预设也无内置引擎) */
188
+ export declare function isCommandCheck(check: DiagnosticsCheck): check is CommandCheck;
189
+ /** 是否为内置 linter 预设 */
190
+ export declare function isLinterPreset(check: DiagnosticsCheck): check is LinterPresetCheck;
191
+ /** 是否为内置类型检查(引擎级) */
192
+ export declare function isTypecheckCheck(check: DiagnosticsCheck): check is TypecheckCheck;
193
+ /** 该检查是否在指定阶段执行 */
194
+ export declare function checkRunsInPhase(check: DiagnosticsCheck, phase: DiagnosticsPhase): boolean;
195
+ /**
196
+ * 逐层覆盖 + 逐字段归一化:后面的层覆盖前面的层,非法值回退默认并通过 `onInvalid` 告警。
197
+ * 调用方(provider)负责把告警写进日志,核心层保持无副作用、可单测。
198
+ */
199
+ export declare function resolveDiagnosticsPolicy(layers: readonly (Partial<DiagnosticsPolicy> | undefined)[], onInvalid?: (message: string) => void): DiagnosticsPolicy;
@@ -0,0 +1,226 @@
1
+ const COMMAND_CHECK_TIMEOUT_MS = 6e4;
2
+ const SOURCE_EXTENSIONS = [
3
+ ".js",
4
+ ".jsx",
5
+ ".ts",
6
+ ".tsx",
7
+ ".mjs",
8
+ ".cjs",
9
+ ".mts",
10
+ ".cts",
11
+ ".vue"
12
+ ];
13
+ function matchesExtensions(filePath, extensions) {
14
+ if (!extensions?.length) return true;
15
+ const base = filePath.replace(/\\/g, "/").split("/").pop() ?? "";
16
+ const dot = base.lastIndexOf(".");
17
+ if (dot <= 0) return false;
18
+ return extensions.includes(base.slice(dot).toLowerCase());
19
+ }
20
+ const BUILTIN_LINTERS = {
21
+ eslint: {
22
+ label: "ESLint",
23
+ package: "eslint",
24
+ args: ["--format", "json", "{files}"],
25
+ projectArgs: ["--format", "json", "."],
26
+ extensions: [...SOURCE_EXTENSIONS],
27
+ format: "eslint-json"
28
+ },
29
+ oxlint: {
30
+ label: "oxlint",
31
+ package: "oxlint",
32
+ args: ["--format=json", "--ignore-pattern", "node_modules", "{files}"],
33
+ projectArgs: ["--format=json", "--ignore-pattern", "node_modules", "."],
34
+ extensions: [...SOURCE_EXTENSIONS],
35
+ format: "oxlint-json"
36
+ },
37
+ stylelint: {
38
+ label: "Stylelint",
39
+ package: "stylelint",
40
+ args: ["--formatter", "json", "{files}"],
41
+ projectArgs: ["--formatter", "json", "**/*.{css,scss,less}"],
42
+ extensions: [".css", ".scss", ".less"],
43
+ format: "stylelint-json"
44
+ }
45
+ };
46
+ const DEFAULT_MAX_FINDINGS_PER_SECTION = 3;
47
+ const DEFAULT_MAX_MESSAGE_CHARS = 4e3;
48
+ const DEFAULT_BUILTIN_CHECKS = Object.freeze([
49
+ Object.freeze({ builtin: "eslint" }),
50
+ Object.freeze({ builtin: "oxlint" }),
51
+ Object.freeze({ builtin: "typecheck" })
52
+ ]);
53
+ const DEFAULT_DIAGNOSTICS_POLICY = Object.freeze({
54
+ checks: DEFAULT_BUILTIN_CHECKS.map((check) => ({ ...check })),
55
+ auto: true,
56
+ exposeTool: true,
57
+ severity: "warning",
58
+ maxFindingsPerSection: DEFAULT_MAX_FINDINGS_PER_SECTION,
59
+ maxMessageChars: DEFAULT_MAX_MESSAGE_CHARS
60
+ });
61
+ function isCommandCheck(check) {
62
+ return !("builtin" in check);
63
+ }
64
+ function isLinterPreset(check) {
65
+ return "builtin" in check && check.builtin !== "typecheck";
66
+ }
67
+ function isTypecheckCheck(check) {
68
+ return "builtin" in check && check.builtin === "typecheck";
69
+ }
70
+ function checkRunsInPhase(check, phase) {
71
+ const run = check.run ?? "both";
72
+ return run === "both" || run === phase;
73
+ }
74
+ const FORMATS = [
75
+ "text",
76
+ "tsc",
77
+ "eslint-json",
78
+ "oxlint-json",
79
+ "stylelint-json",
80
+ "aipanel-json"
81
+ ];
82
+ const RUNS = ["edit", "manual", "both"];
83
+ function normalizeExtensions(raw) {
84
+ if (!Array.isArray(raw)) return void 0;
85
+ const extensions = raw.filter((item) => typeof item === "string" && item.trim() !== "").map((item) => {
86
+ const trimmed = item.trim().toLowerCase();
87
+ return trimmed.startsWith(".") ? trimmed : `.${trimmed}`;
88
+ });
89
+ return extensions.length > 0 ? [...new Set(extensions)] : void 0;
90
+ }
91
+ function normalizeArgs(raw) {
92
+ if (!Array.isArray(raw)) return void 0;
93
+ const args = raw.filter((item) => typeof item === "string");
94
+ return args.length > 0 ? args : void 0;
95
+ }
96
+ function normalizeCheck(raw) {
97
+ if (!raw || typeof raw !== "object") return void 0;
98
+ const value = raw;
99
+ const run = RUNS.includes(value.run) ? value.run : void 0;
100
+ const extensions = normalizeExtensions(value.extensions);
101
+ const cwd = typeof value.cwd === "string" && value.cwd ? { cwd: value.cwd } : {};
102
+ const timeout = typeof value.timeoutMs === "number" && Number.isFinite(value.timeoutMs) && value.timeoutMs > 0 ? { timeoutMs: Math.floor(value.timeoutMs) } : {};
103
+ const format = FORMATS.includes(value.format) ? { format: value.format } : {};
104
+ if (value.builtin !== void 0) {
105
+ if (value.builtin === "typecheck") {
106
+ return {
107
+ builtin: "typecheck",
108
+ ...run ? { run } : {},
109
+ ...extensions ? { extensions } : {}
110
+ };
111
+ }
112
+ if (typeof value.builtin === "string" && value.builtin in BUILTIN_LINTERS) {
113
+ const presetArgs = normalizeArgs(value.args);
114
+ const presetProjectArgs = normalizeArgs(value.projectArgs);
115
+ return {
116
+ builtin: value.builtin,
117
+ ...presetArgs ? { args: presetArgs } : {},
118
+ ...presetProjectArgs ? { projectArgs: presetProjectArgs } : {},
119
+ ...extensions ? { extensions } : {},
120
+ ...run ? { run } : {},
121
+ ...format,
122
+ ...cwd,
123
+ ...timeout
124
+ };
125
+ }
126
+ return void 0;
127
+ }
128
+ if (typeof value.name !== "string" || !value.name) return void 0;
129
+ const command = typeof value.command === "string" && value.command ? value.command : void 0;
130
+ const bin = typeof value.bin === "string" && value.bin ? value.bin : void 0;
131
+ if (!command && !bin) return void 0;
132
+ const args = normalizeArgs(value.args);
133
+ const projectArgs = normalizeArgs(value.projectArgs);
134
+ return {
135
+ name: value.name,
136
+ ...command ? { command } : {},
137
+ ...bin ? { bin } : {},
138
+ ...args ? { args } : {},
139
+ ...projectArgs ? { projectArgs } : {},
140
+ ...extensions ? { extensions } : {},
141
+ ...cwd,
142
+ ...run ? { run } : {},
143
+ ...format,
144
+ ...typeof value.adapter === "string" && value.adapter ? { adapter: value.adapter } : {},
145
+ ...timeout
146
+ };
147
+ }
148
+ function normalizeChecks(raw, onInvalid) {
149
+ if (raw === void 0) return void 0;
150
+ if (!Array.isArray(raw)) {
151
+ onInvalid("diagnostics.checks \u5FC5\u987B\u662F\u6570\u7EC4\uFF0C\u5DF2\u56DE\u9000\u4E3A\u5185\u7F6E\u68C0\u67E5");
152
+ return void 0;
153
+ }
154
+ const checks = [];
155
+ for (const item of raw) {
156
+ const check = normalizeCheck(item);
157
+ if (check) checks.push(check);
158
+ else onInvalid(`diagnostics.checks \u542B\u975E\u6CD5\u68C0\u67E5\u9879\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${JSON.stringify(item)}`);
159
+ }
160
+ if (checks.length === 0) {
161
+ onInvalid("diagnostics.checks \u5F52\u4E00\u5316\u540E\u4E3A\u7A7A\uFF0C\u5DF2\u56DE\u9000\u4E3A\u5185\u7F6E\u68C0\u67E5");
162
+ return void 0;
163
+ }
164
+ return checks;
165
+ }
166
+ function normalizeBoolean(raw, fallback) {
167
+ return typeof raw === "boolean" ? raw : fallback;
168
+ }
169
+ function isValidPositiveInt(raw) {
170
+ return typeof raw === "number" && Number.isFinite(raw) && raw >= 1;
171
+ }
172
+ function normalizePositiveInt(raw, fallback) {
173
+ return isValidPositiveInt(raw) ? Math.floor(raw) : fallback;
174
+ }
175
+ function resolveDiagnosticsPolicy(layers, onInvalid = () => {
176
+ }) {
177
+ let policy = {
178
+ ...DEFAULT_DIAGNOSTICS_POLICY,
179
+ checks: DEFAULT_BUILTIN_CHECKS.map((check) => ({ ...check }))
180
+ };
181
+ for (const layer of layers) {
182
+ if (!layer || typeof layer !== "object") continue;
183
+ const checks = normalizeChecks(layer.checks, onInvalid);
184
+ if (layer.severity !== void 0 && layer.severity !== "error" && layer.severity !== "warning") {
185
+ onInvalid(`diagnostics.severity \u53D6\u503C\u975E\u6CD5\uFF08${String(layer.severity)}\uFF09\uFF0C\u5DF2\u56DE\u9000\u4E0A\u4E00\u5C42`);
186
+ }
187
+ if (layer.maxFindingsPerSection !== void 0 && !isValidPositiveInt(layer.maxFindingsPerSection)) {
188
+ onInvalid("diagnostics.maxFindingsPerSection \u5FC5\u987B\u662F \u2265 1 \u7684\u6574\u6570\uFF0C\u5DF2\u56DE\u9000\u4E0A\u4E00\u5C42");
189
+ }
190
+ if (layer.maxMessageChars !== void 0 && !isValidPositiveInt(layer.maxMessageChars)) {
191
+ onInvalid("diagnostics.maxMessageChars \u5FC5\u987B\u662F \u2265 1 \u7684\u6574\u6570\uFF0C\u5DF2\u56DE\u9000\u4E0A\u4E00\u5C42");
192
+ }
193
+ for (const key of ["auto", "exposeTool"]) {
194
+ if (layer[key] !== void 0 && typeof layer[key] !== "boolean") {
195
+ onInvalid(`diagnostics.${key} \u5FC5\u987B\u662F\u5E03\u5C14\u503C\uFF0C\u5DF2\u56DE\u9000\u4E0A\u4E00\u5C42`);
196
+ }
197
+ }
198
+ policy = {
199
+ checks: checks ? checks.map((check) => ({ ...check })) : policy.checks,
200
+ auto: normalizeBoolean(layer.auto, policy.auto),
201
+ exposeTool: normalizeBoolean(layer.exposeTool, policy.exposeTool),
202
+ severity: layer.severity === "error" || layer.severity === "warning" ? layer.severity : policy.severity,
203
+ maxFindingsPerSection: normalizePositiveInt(
204
+ layer.maxFindingsPerSection,
205
+ policy.maxFindingsPerSection
206
+ ),
207
+ maxMessageChars: normalizePositiveInt(layer.maxMessageChars, policy.maxMessageChars)
208
+ };
209
+ }
210
+ return policy;
211
+ }
212
+ export {
213
+ BUILTIN_LINTERS,
214
+ COMMAND_CHECK_TIMEOUT_MS,
215
+ DEFAULT_BUILTIN_CHECKS,
216
+ DEFAULT_DIAGNOSTICS_POLICY,
217
+ DEFAULT_MAX_FINDINGS_PER_SECTION,
218
+ DEFAULT_MAX_MESSAGE_CHARS,
219
+ SOURCE_EXTENSIONS,
220
+ checkRunsInPhase,
221
+ isCommandCheck,
222
+ isLinterPreset,
223
+ isTypecheckCheck,
224
+ matchesExtensions,
225
+ resolveDiagnosticsPolicy
226
+ };
@@ -86,8 +86,6 @@ export interface PluginOptions<P extends Record<string, unknown> = Record<string
86
86
  language?: string;
87
87
  /** @deprecated 使用 providerOptions.settings */
88
88
  settings?: unknown;
89
- /** @deprecated 使用 providerOptions.enableLsp */
90
- enableLsp?: boolean;
91
89
  /** @deprecated 使用 providerOptions.enablePrettier */
92
90
  enablePrettier?: boolean;
93
91
  }
package/es/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./common/constants";
2
+ export * from "./common/diagnostics";
2
3
  export * from "./common/inspector";
3
4
  export * from "./common/logger-core";
4
5
  export * from "./common/options";
package/es/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./common/constants.mjs";
2
+ export * from "./common/diagnostics.mjs";
2
3
  export * from "./common/inspector/index.mjs";
3
4
  export * from "./common/logger-core.mjs";
4
5
  export * from "./common/options.mjs";
@@ -1,8 +1,9 @@
1
+ import { type DiagnosticsPhase, type DiagnosticsPolicy } from "../common/diagnostics";
1
2
  /** 是否为可诊断的源码文件(供宿主钩子过滤 edit/write 目标) */
2
3
  export declare function isJsFile(filePath: string): boolean;
3
4
  /**
4
5
  * run_diagnostics 工具描述(单一来源,供 opencode / dsh 两侧插件引用):
5
- * 只声明能力与支持的文件类型,不涉及内部使用的检查工具。
6
+ * 只声明能力与支持的文件类型,不涉及内部使用的检查工具(检查来源由用户配置,可能是项目命令)。
6
7
  */
7
8
  export declare const DIAGNOSTICS_TOOL_DESCRIPTION: string;
8
9
  /** LSP 风格诊断项(供宿主写入 metadata.diagnostics 等结构化输出) */
@@ -33,18 +34,7 @@ export interface TscResult {
33
34
  export interface EslintOutput {
34
35
  text?: string;
35
36
  diagnostics?: DiagnosticItem[];
36
- /** 实际运行的 lint 引擎("ESLint" / "oxlint");缺省视为 ESLint(兼容旧调用方) */
37
- engines?: string[];
38
37
  }
39
- export interface DiagnosticsResult {
40
- eslintOutput: EslintOutput;
41
- tscOutput: TscResult;
42
- }
43
- /**
44
- * ESLint 检查,接受文件路径或 glob 模式。
45
- * 结果按 error / warning 分级格式化;warnings 数量超限时截断并注明。
46
- */
47
- export declare function lintFiles(pattern: string, cwd: string, warnLimit?: number): Promise<EslintOutput>;
48
38
  /** 从文件路径向上查找最近的 tsconfig.json 所在目录 */
49
39
  export declare function findTsconfigDir(filePath: string): string | null;
50
40
  /** 在工作区子目录中查找 tsconfig.json(不包括根目录;调用方已处理根目录场景) */
@@ -58,34 +48,62 @@ export declare function runTypeCheck(filePath: string | undefined, cwd: string):
58
48
  * 一次编辑批次里的 N 个文件因此从 N 次项目构建降到 1 次。
59
49
  */
60
50
  export declare function runTypeChecksForFiles(files: string[], cwd: string): Promise<Map<string, TscResult>>;
61
- /** 并行运行 ESLint + 类型检查(单文件或 glob) */
62
- export declare function runAllChecks(pattern: string, cwd: string): Promise<DiagnosticsResult>;
63
- /**
64
- * 批量运行 ESLint + 类型检查:Lint 逐文件(ESLint 进程内、oxlint 单次 CLI),
65
- * 类型检查按 tsconfig 项目合并为一次(见 runTypeChecksForFiles)。
66
- * 返回"文件绝对路径 → 诊断结果",供 step 边界的一次性收尾诊断使用。
67
- */
68
- export declare function runAllChecksForFiles(files: string[], cwd: string): Promise<Map<string, DiagnosticsResult>>;
69
- /**
70
- * 全量项目诊断:优先从根 tsconfig 运行一次类型检查 --build,
71
- * 根无 tsconfig 时回退到逐个子目录 build;ESLint 以 "." 全量扫描。
72
- */
73
- export declare function runProjectDiagnostics(workspace: string): Promise<DiagnosticsResult>;
51
+ /** 诊断分区:一个检查在一段目标上的产出 */
52
+ export interface DiagnosticsSection {
53
+ /** 分区标题:check 的 name,或内置引擎标题(ESLint + oxlint / vue-tsc) */
54
+ name: string;
55
+ /** 所属目标文件(相对项目根);仅编辑后自动诊断按文件拆分时存在 */
56
+ target?: string;
57
+ /** 模型可见正文;无内容时缺省(自动诊断据此跳过该分区) */
58
+ text?: string;
59
+ /** LSP 坐标结构化条目(0-based),供 canonical 输出与诊断卡片使用 */
60
+ diagnostics: DiagnosticItem[];
61
+ }
62
+ /** 诊断结果:有序分区列表(历史上是写死的 { eslintOutput, tscOutput },现已泛化) */
63
+ export interface DiagnosticsResult {
64
+ sections: DiagnosticsSection[];
65
+ }
66
+ /** 诊断目标:agent 调工具(单文件 / 全量)或编辑后自动诊断(本步编辑过的文件) */
67
+ export type DiagnosticsTarget = {
68
+ kind: "file";
69
+ file: string;
70
+ cwd: string;
71
+ } | {
72
+ kind: "project";
73
+ cwd: string;
74
+ } | {
75
+ kind: "edited";
76
+ files: string[];
77
+ cwd: string;
78
+ };
79
+ /** 渲染选项 */
80
+ export interface RenderDiagnosticsOptions {
81
+ /** 只输出有发现的分区(编辑后自动诊断;全空返回空串) */
82
+ onlyFindings?: boolean;
83
+ /** 每个分区的发现条数上限(超出折叠成一行提示) */
84
+ maxFindingsPerSection?: number;
85
+ }
86
+ /** 类型检查分区标题(单一来源:跟随实际引擎 tsc / vue-tsc) */
74
87
  /** 类型检查分区标题(单一来源:跟随实际引擎 tsc / vue-tsc) */
75
88
  export declare function tscSectionTitle(tscOutput: TscResult): string;
76
- /** Lint 分区标题(单一来源:跟随实际运行的引擎组合,如 "ESLint + oxlint") */
77
- export declare function lintSectionTitle(lintOutput: EslintOutput): string;
78
89
  /** 分区发现被条数上限折叠时的提示(自动诊断摘要与 run_diagnostics 完整输出的分界) */
79
90
  export declare function omittedFindingsHint(omitted: number): string;
80
91
  /**
81
- * 组装统一的分区诊断文本(Lint / 类型检查)。
82
- * 默认保留空分区的占位文案(run_diagnostics 要正面回答"有没有问题");
92
+ * 诊断深入口:按策略与阶段执行配置的检查,返回有序分区。
93
+ *
94
+ * 三类检查:`typecheck`(引擎级内置)、内置 linter 预设(展开成 CommandCheck)、
95
+ * 用户自定义检查——后两类走**完全同一条**执行与适配逻辑。
96
+ * 检查按声明顺序串行执行,输出顺序确定、进程数可控;
97
+ * `severity` 门槛在分区归一化处生效,文本与结构化条目一起收紧。
98
+ */
99
+ export declare function runDiagnostics(target: DiagnosticsTarget, policy: DiagnosticsPolicy, phase: DiagnosticsPhase): Promise<DiagnosticsResult>;
100
+ /** 汇总结构化条目(canonical 输出 / 诊断卡片用) */
101
+ export declare function collectDiagnostics(result: DiagnosticsResult): DiagnosticItem[];
102
+ /**
103
+ * 把分区结果渲染成模型可见文本。
83
104
  * `onlyFindings: true` 只输出有发现的分区、全空返回空串(编辑后自动诊断不刷占位噪音);
84
105
  * `maxFindingsPerSection` 把每个分区的发现折叠成有界摘要——编辑后自动诊断每个 step 都会投递,
85
106
  * 因此用条数上限控制重复成本,完整结果仍由不带上限的 run_diagnostics 给出。
86
- * `title` 允许为空串,此时直接返回分区正文(追加式投递场景无需标题)。
107
+ * 编辑后自动诊断(分区带 target)保留 `### 文件` 结构。
87
108
  */
88
- export declare function formatDiagnosticsSections(title: string, eslintOutput: EslintOutput, tscOutput: TscResult, options?: {
89
- onlyFindings?: boolean;
90
- maxFindingsPerSection?: number;
91
- }): string;
109
+ export declare function renderDiagnostics(result: DiagnosticsResult, options?: RenderDiagnosticsOptions): string;