@aipanel/dsh-plugin 1.3.3 → 1.3.5

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.
Files changed (2) hide show
  1. package/dist/index.js +735 -236
  2. package/package.json +12 -12
package/dist/index.js CHANGED
@@ -38,16 +38,219 @@ var VUE_DEVTOOLS_TIMELINE_INCLUDES = [
38
38
  ];
39
39
  var DEFAULT_TIMELINE_INCLUDE = VUE_DEVTOOLS_TIMELINE_INCLUDE.SLOW;
40
40
  var MUTATING_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "apply_patch"]);
41
- var OPENCODE_ENV = {
42
- CONFIG_DIR: "OPENCODE_CONFIG_DIR",
43
- CONTEXT_API_URL: "OPENCODE_CONTEXT_API_URL",
44
- VITE_LOGS_API_URL: "OPENCODE_VITE_LOGS_API_URL",
45
- LOG_FILES_JSON: "OPENCODE_LOG_FILES_JSON",
46
- VERBOSE: "OPENCODE_VERBOSE",
47
- ENABLE_LINT: "OPENCODE_ENABLE_LINT",
48
- VUE_DEVTOOLS_API_URL: "OPENCODE_VUE_DEVTOOLS_API_URL",
49
- WORKSPACE: "OPENCODE_WORKSPACE"
41
+
42
+ // ../../core/es/common/diagnostics.mjs
43
+ var COMMAND_CHECK_TIMEOUT_MS = 6e4;
44
+ var SOURCE_EXTENSIONS = [
45
+ ".js",
46
+ ".jsx",
47
+ ".ts",
48
+ ".tsx",
49
+ ".mjs",
50
+ ".cjs",
51
+ ".mts",
52
+ ".cts",
53
+ ".vue"
54
+ ];
55
+ function matchesExtensions(filePath, extensions) {
56
+ if (!extensions?.length) return true;
57
+ const base = filePath.replace(/\\/g, "/").split("/").pop() ?? "";
58
+ const dot = base.lastIndexOf(".");
59
+ if (dot <= 0) return false;
60
+ return extensions.includes(base.slice(dot).toLowerCase());
61
+ }
62
+ var BUILTIN_LINTERS = {
63
+ eslint: {
64
+ label: "ESLint",
65
+ package: "eslint",
66
+ args: ["--format", "json", "{files}"],
67
+ projectArgs: ["--format", "json", "."],
68
+ extensions: [...SOURCE_EXTENSIONS],
69
+ format: "eslint-json"
70
+ },
71
+ oxlint: {
72
+ label: "oxlint",
73
+ package: "oxlint",
74
+ args: ["--format=json", "--ignore-pattern", "node_modules", "{files}"],
75
+ projectArgs: ["--format=json", "--ignore-pattern", "node_modules", "."],
76
+ extensions: [...SOURCE_EXTENSIONS],
77
+ format: "oxlint-json"
78
+ },
79
+ stylelint: {
80
+ label: "Stylelint",
81
+ package: "stylelint",
82
+ args: ["--formatter", "json", "{files}"],
83
+ projectArgs: ["--formatter", "json", "**/*.{css,scss,less}"],
84
+ extensions: [".css", ".scss", ".less"],
85
+ format: "stylelint-json"
86
+ }
50
87
  };
88
+ var DEFAULT_MAX_FINDINGS_PER_SECTION = 3;
89
+ var DEFAULT_MAX_MESSAGE_CHARS = 4e3;
90
+ var DEFAULT_BUILTIN_CHECKS = Object.freeze([
91
+ Object.freeze({ builtin: "eslint" }),
92
+ Object.freeze({ builtin: "oxlint" }),
93
+ Object.freeze({ builtin: "typecheck" })
94
+ ]);
95
+ var DEFAULT_DIAGNOSTICS_POLICY = Object.freeze({
96
+ checks: DEFAULT_BUILTIN_CHECKS.map((check) => ({ ...check })),
97
+ auto: true,
98
+ exposeTool: true,
99
+ severity: "warning",
100
+ maxFindingsPerSection: DEFAULT_MAX_FINDINGS_PER_SECTION,
101
+ maxMessageChars: DEFAULT_MAX_MESSAGE_CHARS
102
+ });
103
+ function isCommandCheck(check) {
104
+ return !("builtin" in check);
105
+ }
106
+ function isLinterPreset(check) {
107
+ return "builtin" in check && check.builtin !== "typecheck";
108
+ }
109
+ function isTypecheckCheck(check) {
110
+ return "builtin" in check && check.builtin === "typecheck";
111
+ }
112
+ function checkRunsInPhase(check, phase) {
113
+ const run = check.run ?? "both";
114
+ return run === "both" || run === phase;
115
+ }
116
+ var FORMATS = [
117
+ "text",
118
+ "tsc",
119
+ "eslint-json",
120
+ "oxlint-json",
121
+ "stylelint-json",
122
+ "aipanel-json"
123
+ ];
124
+ var RUNS = ["edit", "manual", "both"];
125
+ function normalizeExtensions(raw) {
126
+ if (!Array.isArray(raw)) return void 0;
127
+ const extensions = raw.filter((item) => typeof item === "string" && item.trim() !== "").map((item) => {
128
+ const trimmed = item.trim().toLowerCase();
129
+ return trimmed.startsWith(".") ? trimmed : `.${trimmed}`;
130
+ });
131
+ return extensions.length > 0 ? [...new Set(extensions)] : void 0;
132
+ }
133
+ function normalizeArgs(raw) {
134
+ if (!Array.isArray(raw)) return void 0;
135
+ const args = raw.filter((item) => typeof item === "string");
136
+ return args.length > 0 ? args : void 0;
137
+ }
138
+ function normalizeCheck(raw) {
139
+ if (!raw || typeof raw !== "object") return void 0;
140
+ const value = raw;
141
+ const run = RUNS.includes(value.run) ? value.run : void 0;
142
+ const extensions = normalizeExtensions(value.extensions);
143
+ const cwd = typeof value.cwd === "string" && value.cwd ? { cwd: value.cwd } : {};
144
+ const timeout = typeof value.timeoutMs === "number" && Number.isFinite(value.timeoutMs) && value.timeoutMs > 0 ? { timeoutMs: Math.floor(value.timeoutMs) } : {};
145
+ const format = FORMATS.includes(value.format) ? { format: value.format } : {};
146
+ if (value.builtin !== void 0) {
147
+ if (value.builtin === "typecheck") {
148
+ return {
149
+ builtin: "typecheck",
150
+ ...run ? { run } : {},
151
+ ...extensions ? { extensions } : {}
152
+ };
153
+ }
154
+ if (typeof value.builtin === "string" && value.builtin in BUILTIN_LINTERS) {
155
+ const presetArgs = normalizeArgs(value.args);
156
+ const presetProjectArgs = normalizeArgs(value.projectArgs);
157
+ return {
158
+ builtin: value.builtin,
159
+ ...presetArgs ? { args: presetArgs } : {},
160
+ ...presetProjectArgs ? { projectArgs: presetProjectArgs } : {},
161
+ ...extensions ? { extensions } : {},
162
+ ...run ? { run } : {},
163
+ ...format,
164
+ ...cwd,
165
+ ...timeout
166
+ };
167
+ }
168
+ return void 0;
169
+ }
170
+ if (typeof value.name !== "string" || !value.name) return void 0;
171
+ const command = typeof value.command === "string" && value.command ? value.command : void 0;
172
+ const bin = typeof value.bin === "string" && value.bin ? value.bin : void 0;
173
+ if (!command && !bin) return void 0;
174
+ const args = normalizeArgs(value.args);
175
+ const projectArgs = normalizeArgs(value.projectArgs);
176
+ return {
177
+ name: value.name,
178
+ ...command ? { command } : {},
179
+ ...bin ? { bin } : {},
180
+ ...args ? { args } : {},
181
+ ...projectArgs ? { projectArgs } : {},
182
+ ...extensions ? { extensions } : {},
183
+ ...cwd,
184
+ ...run ? { run } : {},
185
+ ...format,
186
+ ...typeof value.adapter === "string" && value.adapter ? { adapter: value.adapter } : {},
187
+ ...timeout
188
+ };
189
+ }
190
+ function normalizeChecks(raw, onInvalid) {
191
+ if (raw === void 0) return void 0;
192
+ if (!Array.isArray(raw)) {
193
+ onInvalid("diagnostics.checks \u5FC5\u987B\u662F\u6570\u7EC4\uFF0C\u5DF2\u56DE\u9000\u4E3A\u5185\u7F6E\u68C0\u67E5");
194
+ return void 0;
195
+ }
196
+ const checks = [];
197
+ for (const item of raw) {
198
+ const check = normalizeCheck(item);
199
+ if (check) checks.push(check);
200
+ else onInvalid(`diagnostics.checks \u542B\u975E\u6CD5\u68C0\u67E5\u9879\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${JSON.stringify(item)}`);
201
+ }
202
+ if (checks.length === 0) {
203
+ onInvalid("diagnostics.checks \u5F52\u4E00\u5316\u540E\u4E3A\u7A7A\uFF0C\u5DF2\u56DE\u9000\u4E3A\u5185\u7F6E\u68C0\u67E5");
204
+ return void 0;
205
+ }
206
+ return checks;
207
+ }
208
+ function normalizeBoolean(raw, fallback) {
209
+ return typeof raw === "boolean" ? raw : fallback;
210
+ }
211
+ function isValidPositiveInt(raw) {
212
+ return typeof raw === "number" && Number.isFinite(raw) && raw >= 1;
213
+ }
214
+ function normalizePositiveInt(raw, fallback) {
215
+ return isValidPositiveInt(raw) ? Math.floor(raw) : fallback;
216
+ }
217
+ function resolveDiagnosticsPolicy(layers, onInvalid = () => {
218
+ }) {
219
+ let policy = {
220
+ ...DEFAULT_DIAGNOSTICS_POLICY,
221
+ checks: DEFAULT_BUILTIN_CHECKS.map((check) => ({ ...check }))
222
+ };
223
+ for (const layer of layers) {
224
+ if (!layer || typeof layer !== "object") continue;
225
+ const checks = normalizeChecks(layer.checks, onInvalid);
226
+ if (layer.severity !== void 0 && layer.severity !== "error" && layer.severity !== "warning") {
227
+ onInvalid(`diagnostics.severity \u53D6\u503C\u975E\u6CD5\uFF08${String(layer.severity)}\uFF09\uFF0C\u5DF2\u56DE\u9000\u4E0A\u4E00\u5C42`);
228
+ }
229
+ if (layer.maxFindingsPerSection !== void 0 && !isValidPositiveInt(layer.maxFindingsPerSection)) {
230
+ onInvalid("diagnostics.maxFindingsPerSection \u5FC5\u987B\u662F \u2265 1 \u7684\u6574\u6570\uFF0C\u5DF2\u56DE\u9000\u4E0A\u4E00\u5C42");
231
+ }
232
+ if (layer.maxMessageChars !== void 0 && !isValidPositiveInt(layer.maxMessageChars)) {
233
+ onInvalid("diagnostics.maxMessageChars \u5FC5\u987B\u662F \u2265 1 \u7684\u6574\u6570\uFF0C\u5DF2\u56DE\u9000\u4E0A\u4E00\u5C42");
234
+ }
235
+ for (const key of ["auto", "exposeTool"]) {
236
+ if (layer[key] !== void 0 && typeof layer[key] !== "boolean") {
237
+ onInvalid(`diagnostics.${key} \u5FC5\u987B\u662F\u5E03\u5C14\u503C\uFF0C\u5DF2\u56DE\u9000\u4E0A\u4E00\u5C42`);
238
+ }
239
+ }
240
+ policy = {
241
+ checks: checks ? checks.map((check) => ({ ...check })) : policy.checks,
242
+ auto: normalizeBoolean(layer.auto, policy.auto),
243
+ exposeTool: normalizeBoolean(layer.exposeTool, policy.exposeTool),
244
+ severity: layer.severity === "error" || layer.severity === "warning" ? layer.severity : policy.severity,
245
+ maxFindingsPerSection: normalizePositiveInt(
246
+ layer.maxFindingsPerSection,
247
+ policy.maxFindingsPerSection
248
+ ),
249
+ maxMessageChars: normalizePositiveInt(layer.maxMessageChars, policy.maxMessageChars)
250
+ };
251
+ }
252
+ return policy;
253
+ }
51
254
 
52
255
  // ../../core/es/common/logger-core.mjs
53
256
  var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
@@ -299,28 +502,16 @@ var PerformanceTimer = class {
299
502
  // ../../core/es/node/diagnostics.mjs
300
503
  import fs from "node:fs";
301
504
  import path from "node:path";
505
+ import { pathToFileURL } from "node:url";
302
506
  import { execa } from "execa";
303
507
  import { createRequire } from "node:module";
304
508
  var log2 = createNodeLogger("Diagnostics");
305
- var JS_EXTENSIONS = /* @__PURE__ */ new Set([
306
- ".js",
307
- ".jsx",
308
- ".ts",
309
- ".tsx",
310
- ".mjs",
311
- ".cjs",
312
- ".mts",
313
- ".cts",
314
- ".vue"
315
- ]);
316
- function isJsFile(filePath) {
317
- return JS_EXTENSIONS.has(path.extname(filePath));
318
- }
509
+ var JS_EXTENSIONS = new Set(SOURCE_EXTENSIONS);
319
510
  var DIAGNOSTICS_TOOL_DESCRIPTION = [
320
- "\u8FD0\u884C Lint\uFF08ESLint / oxlint\uFF09\u4E0E TypeScript \u7C7B\u578B\u8BCA\u65AD\uFF0C\u8FD4\u56DE\u8BCA\u65AD\u7ED3\u679C\u3002",
511
+ "\u8FD0\u884C\u9879\u76EE\u914D\u7F6E\u7684\u4EE3\u7801\u68C0\u67E5\uFF08\u9ED8\u8BA4 Lint \u4E0E TypeScript \u7C7B\u578B\u8BCA\u65AD\uFF09\uFF0C\u8FD4\u56DE\u8BCA\u65AD\u7ED3\u679C\u3002",
321
512
  "",
322
513
  "**\u652F\u6301\u7684\u6587\u4EF6\u7C7B\u578B**\uFF1A",
323
- `- Lint\uFF1AJavaScript / TypeScript / Vue \u6E90\u7801\uFF08${[...JS_EXTENSIONS].map((e) => `*${e}`).join(" ")}\uFF09\uFF1B\u4E24\u5F15\u64CE\u5747\u5B89\u88C5\u65F6\u5E76\u884C\u4E92\u8865\u8FD0\u884C\uFF08\u8BCA\u65AD\u6309\u6765\u6E90\u6807\u6CE8\uFF09`,
514
+ `- Lint\uFF1AJavaScript / TypeScript / Vue \u6E90\u7801\uFF08${[...JS_EXTENSIONS].map((e) => `*${e}`).join(" ")}\uFF09\uFF1B\u9879\u76EE\u540C\u65F6\u88C5\u6709 ESLint \u4E0E oxlint \u65F6\u4E92\u8865\u8FD0\u884C\uFF08\u8BCA\u65AD\u6309\u6765\u6E90\u6807\u6CE8\uFF09`,
324
515
  "- TypeScript \u7C7B\u578B\u68C0\u67E5\uFF1A*.ts *.tsx *.vue",
325
516
  "",
326
517
  "**\u4F55\u65F6\u4F7F\u7528\u6B64\u5DE5\u5177**\uFF1A",
@@ -350,53 +541,14 @@ function resolvePackageBin(basePkgJson, pkgName, binName) {
350
541
  }
351
542
  });
352
543
  }
353
- var _eslintClassByBase = /* @__PURE__ */ new Map();
354
- function loadESLint(workspace) {
355
- const basePkgJson = path.join(workspace, "package.json");
356
- return cachedResolve(_eslintClassByBase, basePkgJson, () => {
357
- log2.debug("Loading eslint", { workspace });
358
- try {
359
- const req = createRequire(basePkgJson);
360
- const eslintModule = req("eslint");
361
- const eslintClass = eslintModule.ESLint ?? eslintModule.FlatESLint ?? null;
362
- log2.debug("eslint loaded", { hasClass: !!eslintClass });
363
- return eslintClass;
364
- } catch (e) {
365
- log2.warn("eslint not found", { error: e.message });
366
- return null;
367
- }
368
- });
369
- }
370
- async function lintFiles(pattern, cwd, warnLimit = 5) {
371
- const eslintClass = loadESLint(cwd);
372
- const [eslintOutput, oxlintOutput] = await Promise.all([
373
- eslintClass ? runEslintFiles(eslintClass, pattern, cwd, warnLimit) : Promise.resolve(null),
374
- runOxlintFiles(pattern, cwd, warnLimit)
375
- ]);
376
- const engines = [];
377
- const texts = [];
378
- const diagnostics = [];
379
- for (const output of [eslintOutput, oxlintOutput]) {
380
- if (!output) continue;
381
- engines.push(...output.engines ?? []);
382
- if (output.text) texts.push(output.text);
383
- diagnostics.push(...output.diagnostics ?? []);
384
- }
385
- if (engines.length === 0) {
386
- return {
387
- text: `[Lint] \u672A\u8FD0\u884C\uFF1A\u65E0\u6CD5\u5728 workspace "${cwd}" \u89E3\u6790\u5230 eslint \u6216 oxlint`,
388
- diagnostics: []
389
- };
390
- }
391
- return { text: texts.join("\n\n") || void 0, diagnostics, engines };
392
- }
393
- function formatLintMessages(messages, engine, warnLimit) {
394
- if (messages.length === 0) return { engines: [engine.label] };
544
+ function formatLintMessages(messages, engine, warnLimit, minSeverity = "warning") {
395
545
  const ESLINT_ERROR = 2;
396
546
  const ESLINT_WARN = 1;
547
+ const kept = minSeverity === "error" ? messages.filter((m) => m.severity === ESLINT_ERROR) : messages;
548
+ if (kept.length === 0) return {};
397
549
  const lines = [];
398
- const errors = messages.filter((m) => m.severity === ESLINT_ERROR);
399
- const warnings = messages.filter((m) => m.severity === ESLINT_WARN);
550
+ const errors = kept.filter((m) => m.severity === ESLINT_ERROR);
551
+ const warnings = kept.filter((m) => m.severity === ESLINT_WARN);
400
552
  if (errors.length > 0) {
401
553
  lines.push(
402
554
  ...errors.map(
@@ -426,35 +578,9 @@ function formatLintMessages(messages, engine, warnLimit) {
426
578
  }));
427
579
  return {
428
580
  text: lines.length > 0 ? lines.join("\n") : void 0,
429
- diagnostics,
430
- engines: [engine.label]
581
+ diagnostics
431
582
  };
432
583
  }
433
- async function runEslintFiles(eslintClass, pattern, cwd, warnLimit) {
434
- try {
435
- const eslint = new eslintClass({ cwd });
436
- const results = await eslint.lintFiles(pattern);
437
- const messages = results.flatMap(
438
- (r) => (r.messages ?? []).map((m) => ({ ...m, filePath: r.filePath }))
439
- );
440
- log2.debug("ESLint lint", {
441
- pattern,
442
- fileCount: results.length,
443
- messageCount: messages.length
444
- });
445
- return formatLintMessages(messages, { label: "ESLint", source: "eslint" }, warnLimit);
446
- } catch (err) {
447
- log2.warn("ESLint failed", { pattern, error: err.message });
448
- return {
449
- text: `[ESLint] \u8FD0\u884C\u5931\u8D25\uFF1A${err.message}\uFF08\u4EC5\u663E\u793A TypeScript \u8BCA\u65AD\uFF09`,
450
- diagnostics: [],
451
- engines: ["ESLint"]
452
- };
453
- }
454
- }
455
- function resolveOxlintBin(workspace) {
456
- return resolvePackageBin(path.join(workspace, "package.json"), "oxlint", "oxlint");
457
- }
458
584
  function oxlintRuleName(code) {
459
585
  if (!code) return null;
460
586
  const match = /\(([^)]*)\)$/.exec(code);
@@ -497,39 +623,6 @@ function parseOxlintOutput(stdout, cwd) {
497
623
  ];
498
624
  });
499
625
  }
500
- async function runOxlintFiles(pattern, cwd, warnLimit) {
501
- const bin = resolveOxlintBin(cwd);
502
- if (!bin) return {};
503
- const result = await execa(
504
- "node",
505
- [bin, "--format=json", "--ignore-pattern", "node_modules", pattern],
506
- { cwd, timeout: 6e4, maxBuffer: 50 * 1024 * 1024, reject: false }
507
- );
508
- if (result.timedOut || result.isTerminated || result.isMaxBuffer) {
509
- log2.warn("oxlint timed out", { pattern });
510
- return {
511
- text: "[oxlint] \u8FD0\u884C\u5931\u8D25\uFF1A\u68C0\u67E5\u8D85\u65F6\uFF0C\u8BF7\u5C1D\u8BD5\u7F29\u5C0F\u68C0\u67E5\u8303\u56F4\u3002",
512
- diagnostics: [],
513
- engines: ["oxlint"]
514
- };
515
- }
516
- try {
517
- const messages = parseOxlintOutput(result.stdout, cwd);
518
- log2.debug("oxlint lint", {
519
- pattern,
520
- messageCount: messages.length,
521
- stderr: result.stderr || void 0
522
- });
523
- return formatLintMessages(messages, { label: "oxlint", source: "oxlint" }, warnLimit);
524
- } catch (e) {
525
- log2.warn("oxlint failed", { pattern, error: e.message });
526
- return {
527
- text: `[oxlint] \u8FD0\u884C\u5931\u8D25\uFF1A${e.message}`,
528
- diagnostics: [],
529
- engines: ["oxlint"]
530
- };
531
- }
532
- }
533
626
  var BUNDLED_ENGINE_KEY = "<bundled>";
534
627
  var _engineCache = /* @__PURE__ */ new Map();
535
628
  var _tsVersionCache = /* @__PURE__ */ new Map();
@@ -820,47 +913,9 @@ async function runTypeChecksForFiles(files, cwd) {
820
913
  }
821
914
  return results;
822
915
  }
823
- async function runAllChecks(pattern, cwd) {
824
- log2.debug("runAllChecks", { pattern, cwd });
825
- const [eslintOutput, tscOutput] = await Promise.all([
826
- lintFiles(pattern, cwd),
827
- runTypeCheck(pattern, cwd)
828
- ]);
829
- return { eslintOutput, tscOutput };
830
- }
831
- async function runAllChecksForFiles(files, cwd) {
832
- const targets = [...new Set(files.map((file) => path.resolve(file)))];
833
- const tscByFile = await runTypeChecksForFiles(targets, cwd);
834
- const results = /* @__PURE__ */ new Map();
835
- for (const file of targets) {
836
- results.set(file, {
837
- eslintOutput: await lintFiles(file, cwd),
838
- tscOutput: tscByFile.get(file) ?? { rawOutput: "", exitCode: 0 }
839
- });
840
- }
841
- return results;
842
- }
843
- async function runProjectDiagnostics(workspace) {
844
- const tscDirs = fs.existsSync(path.join(workspace, "tsconfig.json")) ? [workspace] : findAllTsconfigDirs(workspace);
845
- log2.debug("Tsc dirs to check", { count: tscDirs.length, dirs: tscDirs });
846
- const [eslintOutput, ...tscOutputs] = await Promise.all([
847
- lintFiles(".", workspace, 10),
848
- ...tscDirs.map((dir) => runTypeCheck(void 0, dir))
849
- ]);
850
- const mergedTsc = {
851
- rawOutput: tscOutputs.flatMap((o) => o.rawOutput).filter(Boolean).join("\n"),
852
- exitCode: tscOutputs.reduce((max, o) => Math.max(max, o.exitCode), 0),
853
- diagnostics: tscOutputs.flatMap((o) => o.diagnostics ?? []),
854
- source: tscOutputs.find((o) => o.source)?.source
855
- };
856
- return { eslintOutput, tscOutput: mergedTsc };
857
- }
858
916
  function tscSectionTitle(tscOutput) {
859
917
  return tscOutput.source ?? "tsc";
860
918
  }
861
- function lintSectionTitle(lintOutput) {
862
- return lintOutput.engines?.length ? lintOutput.engines.join(" + ") : "ESLint";
863
- }
864
919
  function omittedFindingsHint(omitted) {
865
920
  return `\u2026\u8FD8\u6709 ${omitted} \u6761\uFF0C\u5B8C\u6574\u7ED3\u679C\u8BF7\u8C03\u7528 run_diagnostics`;
866
921
  }
@@ -872,25 +927,482 @@ function boundFindings(text, maxFindings) {
872
927
  "\n"
873
928
  );
874
929
  }
875
- function formatDiagnosticsSections(title, eslintOutput, tscOutput, options = {}) {
876
- const lintLines = boundFindings(eslintOutput.text, options.maxFindingsPerSection);
877
- const tscLines = boundFindings(tscOutput.rawOutput.trim(), options.maxFindingsPerSection);
878
- const parts = [];
879
- if (!options.onlyFindings || lintLines) {
880
- parts.push(`## ${lintSectionTitle(eslintOutput)}
881
-
882
- ` + (lintLines || "\u6CA1\u6709\u53D1\u73B0\u95EE\u9898"));
930
+ function displayPath(cwd, file) {
931
+ const rel = path.relative(cwd, file);
932
+ return rel.startsWith("..") || path.isAbsolute(rel) ? file : rel;
933
+ }
934
+ function targetFiles(target) {
935
+ if (target.kind === "file") return [path.resolve(target.file)];
936
+ if (target.kind === "edited") return [...new Set(target.files.map((file) => path.resolve(file)))];
937
+ return [];
938
+ }
939
+ function splitsByFile(target) {
940
+ return target.kind === "edited";
941
+ }
942
+ function filterBySeverity(items, severity) {
943
+ return severity === "error" ? items.filter((item) => item.severity === SEVERITY_ERROR) : [...items];
944
+ }
945
+ function renderDiagnosticLines(items) {
946
+ return items.map((item) => {
947
+ const level = item.severity === SEVERITY_ERROR ? "ERROR" : "WARN";
948
+ const file = item.file ?? "";
949
+ const { line, character } = item.range.start;
950
+ return `${level} [${file}:${line + 1}:${character + 1}] ${item.message}`;
951
+ }).join("\n");
952
+ }
953
+ function entryToDiagnosticItem(entry, source) {
954
+ return {
955
+ file: entry.file,
956
+ severity: entry.severity === "error" ? SEVERITY_ERROR : SEVERITY_WARN,
957
+ range: {
958
+ start: { line: entry.line - 1, character: entry.column - 1 },
959
+ end: { line: entry.line - 1, character: entry.column - 1 }
960
+ },
961
+ message: entry.message,
962
+ source
963
+ };
964
+ }
965
+ function normalizeEntries(value, source) {
966
+ if (value === void 0) return [];
967
+ if (!Array.isArray(value)) throw new Error("diagnostics \u5FC5\u987B\u662F\u6570\u7EC4");
968
+ return value.map((raw, index) => {
969
+ const at = `diagnostics[${index}]`;
970
+ if (!raw || typeof raw !== "object") throw new Error(`${at} \u5FC5\u987B\u662F\u5BF9\u8C61`);
971
+ const entry = raw;
972
+ if (typeof entry.file !== "string" || !entry.file)
973
+ throw new Error(`${at}.file \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
974
+ if (typeof entry.line !== "number" || entry.line < 1)
975
+ throw new Error(`${at}.line \u5FC5\u987B\u662F \u2265 1 \u7684\u6570\u5B57`);
976
+ if (typeof entry.column !== "number" || entry.column < 1)
977
+ throw new Error(`${at}.column \u5FC5\u987B\u662F \u2265 1 \u7684\u6570\u5B57`);
978
+ if (entry.severity !== "error" && entry.severity !== "warning")
979
+ throw new Error(`${at}.severity \u5FC5\u987B\u662F "error" \u6216 "warning"`);
980
+ if (typeof entry.message !== "string") throw new Error(`${at}.message \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`);
981
+ return entryToDiagnosticItem(entry, source);
982
+ });
983
+ }
984
+ function extractJsonValue(raw) {
985
+ const trimmed = raw.trim();
986
+ try {
987
+ return JSON.parse(trimmed);
988
+ } catch {
989
+ const starts = [raw.indexOf("{"), raw.indexOf("[")].filter((index) => index >= 0);
990
+ if (starts.length === 0) throw new Error("\u672A\u627E\u5230 JSON \u8F93\u51FA");
991
+ const start = Math.min(...starts);
992
+ const close = raw[start] === "{" ? "}" : "]";
993
+ const end = raw.lastIndexOf(close);
994
+ if (end <= start) throw new Error("\u672A\u627E\u5230 JSON \u8F93\u51FA");
995
+ return JSON.parse(raw.slice(start, end + 1));
883
996
  }
884
- if (!options.onlyFindings || tscLines) {
885
- parts.push(`## ${tscSectionTitle(tscOutput)}
886
-
887
- ` + (tscLines || "\u6CA1\u6709\u53D1\u73B0\u7C7B\u578B\u9519\u8BEF"));
997
+ }
998
+ function parseEslintJson(raw, cwd) {
999
+ const parsed = extractJsonValue(raw);
1000
+ if (!Array.isArray(parsed)) throw new Error("\u9876\u5C42\u5FC5\u987B\u662F\u6570\u7EC4");
1001
+ const all = parsed.flatMap(
1002
+ (file) => (file.messages ?? []).map((message) => ({
1003
+ ...message,
1004
+ filePath: file.filePath ? path.resolve(cwd, file.filePath) : ""
1005
+ }))
1006
+ );
1007
+ return {
1008
+ messages: all.filter((message) => message.filePath && !isEslintIgnoreNotice(message)),
1009
+ ignored: all.filter((message) => message.filePath && isEslintIgnoreNotice(message)).map((m) => m.filePath)
1010
+ };
1011
+ }
1012
+ function isEslintIgnoreNotice(message) {
1013
+ return (message.ruleId ?? null) === null && typeof message.message === "string" && message.message.startsWith("File ignored because");
1014
+ }
1015
+ function filterTscOutputBySeverity(rawOutput, minSeverity) {
1016
+ if (minSeverity === "warning" || !rawOutput) return rawOutput;
1017
+ const errorLinePat = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+TS\d+:/;
1018
+ const kept = [];
1019
+ let keep = false;
1020
+ for (const line of rawOutput.split("\n")) {
1021
+ const match = errorLinePat.exec(line);
1022
+ if (match) keep = match[4] === "error";
1023
+ else if (!/^\s/.test(line)) keep = false;
1024
+ if (keep) kept.push(line);
1025
+ }
1026
+ return kept.join("\n");
1027
+ }
1028
+ function applyTscSeverity(result, minSeverity) {
1029
+ if (minSeverity === "warning") return result;
1030
+ return {
1031
+ ...result,
1032
+ rawOutput: filterTscOutputBySeverity(result.rawOutput, minSeverity),
1033
+ diagnostics: (result.diagnostics ?? []).filter((item) => item.severity === SEVERITY_ERROR)
1034
+ };
1035
+ }
1036
+ function emptySectionText(kind, phase, severity) {
1037
+ if (phase !== "manual") return void 0;
1038
+ if (kind === "typecheck") return "\u6CA1\u6709\u53D1\u73B0\u7C7B\u578B\u9519\u8BEF";
1039
+ return severity === "error" ? "\u6CA1\u6709\u53D1\u73B0\u9519\u8BEF" : "\u6CA1\u6709\u53D1\u73B0\u95EE\u9898";
1040
+ }
1041
+ function buildSection(name2, target, outcome, phase, severity, kind) {
1042
+ const diagnostics = filterBySeverity(outcome.diagnostics ?? [], severity);
1043
+ const explicit = (outcome.text ?? "").trim();
1044
+ const body = explicit || (diagnostics.length > 0 ? renderDiagnosticLines(diagnostics) : emptySectionText(kind, phase, severity) ?? "");
1045
+ const notes = phase === "manual" ? (outcome.notes ?? []).filter(Boolean) : [];
1046
+ const text = [body, ...notes].filter(Boolean).join("\n\n");
1047
+ return {
1048
+ name: name2,
1049
+ ...target ? { target } : {},
1050
+ ...text ? { text } : {},
1051
+ diagnostics
1052
+ };
1053
+ }
1054
+ function resolveCommandSource(check, cwd) {
1055
+ if (check.command) {
1056
+ return { command: check.command, prefix: [], label: check.command };
1057
+ }
1058
+ const binName = check.bin;
1059
+ const bin = resolvePackageBin(path.join(cwd, "package.json"), binName, binName);
1060
+ if (!bin) {
1061
+ return {
1062
+ command: "node",
1063
+ prefix: [],
1064
+ label: binName,
1065
+ error: `\u9879\u76EE\u91CC\u89E3\u6790\u4E0D\u5230 ${binName}\uFF08\u8BF7\u5148\u5B89\u88C5\u8BE5\u4F9D\u8D56\uFF0C\u6216\u6539\u7528 command \u6307\u5B9A\u53EF\u6267\u884C\u6587\u4EF6\uFF09`
1066
+ };
1067
+ }
1068
+ return { command: "node", prefix: [bin], label: binName };
1069
+ }
1070
+ async function runCommand(check, source, argv, cwd) {
1071
+ const result = await execa(source.command, [...source.prefix, ...argv], {
1072
+ cwd,
1073
+ timeout: check.timeoutMs ?? COMMAND_CHECK_TIMEOUT_MS,
1074
+ maxBuffer: 10 * 1024 * 1024,
1075
+ reject: false,
1076
+ stdin: "ignore"
1077
+ });
1078
+ return {
1079
+ stdout: String(result.stdout ?? ""),
1080
+ stderr: String(result.stderr ?? ""),
1081
+ exitCode: typeof result.exitCode === "number" ? result.exitCode : void 0,
1082
+ failed: result.failed,
1083
+ timedOut: Boolean(result.timedOut || result.isTerminated),
1084
+ overflowed: Boolean(result.isMaxBuffer),
1085
+ message: result.originalMessage
1086
+ };
1087
+ }
1088
+ function commandFailureText(check, source, outcome) {
1089
+ if (outcome.timedOut) {
1090
+ return `[${check.name}] \u8FD0\u884C\u5931\u8D25\uFF1A\u68C0\u67E5\u8D85\u65F6\uFF08${check.timeoutMs ?? COMMAND_CHECK_TIMEOUT_MS}ms\uFF09\uFF0C\u53EF\u63D0\u9AD8 diagnostics.timeoutMs \u6216\u7F29\u5C0F\u68C0\u67E5\u8303\u56F4\u3002`;
1091
+ }
1092
+ if (outcome.overflowed) return `[${check.name}] \u8FD0\u884C\u5931\u8D25\uFF1A\u8F93\u51FA\u8D85\u8FC7\u4E0A\u9650\uFF0C\u8BF7\u7F29\u5C0F\u68C0\u67E5\u8303\u56F4\u3002`;
1093
+ if (outcome.failed && outcome.exitCode === void 0) {
1094
+ return `[${check.name}] \u8FD0\u884C\u5931\u8D25\uFF1A\u65E0\u6CD5\u6267\u884C ${source.label}\uFF08${outcome.message ?? "spawn failed"}\uFF09\u3002`;
1095
+ }
1096
+ return void 0;
1097
+ }
1098
+ function rawCommandOutput(outcome) {
1099
+ return [outcome.stdout, outcome.stderr].map((text) => text.trim()).filter(Boolean).join("\n");
1100
+ }
1101
+ function expandArgs(args, files) {
1102
+ return args.flatMap((arg) => {
1103
+ if (arg === "{file}") return files[0] ? [files[0]] : [];
1104
+ if (arg === "{files}") return [...files];
1105
+ return [
1106
+ arg.replace(
1107
+ /\{file\}|\{files\}/g,
1108
+ (match) => match === "{file}" ? files[0] ?? "" : files.join(" ")
1109
+ )
1110
+ ];
1111
+ });
1112
+ }
1113
+ async function runAdapterModule(check, outcome, cwd, files) {
1114
+ const adapterPath = path.resolve(cwd, check.adapter);
1115
+ const fail = (reason) => ({
1116
+ text: `[${check.name}] \u9002\u914D\u5668\u4E0D\u53EF\u7528\uFF1A${check.adapter}\uFF08${reason}\uFF09`,
1117
+ diagnostics: []
1118
+ });
1119
+ let moduleExports;
1120
+ try {
1121
+ moduleExports = await import(pathToFileURL(adapterPath).href);
1122
+ } catch (error) {
1123
+ return fail(error instanceof Error ? error.message : String(error));
1124
+ }
1125
+ if (typeof moduleExports.default !== "function") {
1126
+ return fail("\u6A21\u5757\u5FC5\u987B default export \u4E00\u4E2A\u9002\u914D\u5668\u51FD\u6570");
1127
+ }
1128
+ const input = {
1129
+ name: check.name,
1130
+ stdout: outcome.stdout,
1131
+ stderr: outcome.stderr,
1132
+ exitCode: outcome.exitCode,
1133
+ files: [...files],
1134
+ cwd
1135
+ };
1136
+ let output;
1137
+ try {
1138
+ output = await moduleExports.default(
1139
+ input
1140
+ );
1141
+ } catch (error) {
1142
+ return fail(error instanceof Error ? error.message : String(error));
1143
+ }
1144
+ if (!output || typeof output !== "object") return fail("\u9002\u914D\u5668\u5FC5\u987B\u8FD4\u56DE\u5BF9\u8C61");
1145
+ try {
1146
+ const diagnostics = normalizeEntries(output.diagnostics, check.name);
1147
+ return { text: diagnostics.length > 0 ? void 0 : output.text, diagnostics };
1148
+ } catch (error) {
1149
+ return fail(error instanceof Error ? error.message : String(error));
1150
+ }
1151
+ }
1152
+ function parseStylelintJson(raw, cwd) {
1153
+ const parsed = extractJsonValue(raw);
1154
+ if (!Array.isArray(parsed)) throw new Error("\u9876\u5C42\u5FC5\u987B\u662F\u6570\u7EC4");
1155
+ return parsed.flatMap((file) => {
1156
+ const filePath = file.source ? path.resolve(cwd, file.source) : "";
1157
+ if (!filePath) return [];
1158
+ return (file.warnings ?? []).map((warning) => ({
1159
+ severity: warning.severity === "error" ? 2 : 1,
1160
+ line: warning.line ?? 1,
1161
+ column: warning.column ?? 1,
1162
+ endLine: warning.endLine,
1163
+ endColumn: warning.endColumn,
1164
+ message: warning.text ?? "\u672A\u77E5\u8BCA\u65AD",
1165
+ ruleId: warning.rule ?? null,
1166
+ filePath
1167
+ }));
1168
+ });
1169
+ }
1170
+ function adaptBuiltinFormat(format, check, raw, cwd, severity) {
1171
+ const lintFromMessages = (messages) => {
1172
+ const output = formatLintMessages(
1173
+ messages,
1174
+ { label: check.name, source: check.name },
1175
+ 10,
1176
+ severity
1177
+ );
1178
+ return { text: output.text, diagnostics: output.diagnostics ?? [] };
1179
+ };
1180
+ switch (format) {
1181
+ case "tsc":
1182
+ return {
1183
+ text: filterTscOutputBySeverity(raw, severity),
1184
+ diagnostics: parseTscDiags(raw, void 0, cwd, check.name)
1185
+ };
1186
+ case "eslint-json": {
1187
+ const { messages, ignored } = parseEslintJson(raw, cwd);
1188
+ return {
1189
+ ...lintFromMessages(messages),
1190
+ notes: ignored.map((file) => `${file} \u4E0D\u5728 ESLint \u914D\u7F6E\u8303\u56F4\u5185\uFF0C\u672A\u68C0\u67E5`)
1191
+ };
1192
+ }
1193
+ case "oxlint-json":
1194
+ return lintFromMessages(parseOxlintOutput(raw, cwd));
1195
+ case "stylelint-json":
1196
+ return lintFromMessages(parseStylelintJson(raw, cwd));
1197
+ case "aipanel-json": {
1198
+ const value = extractJsonValue(raw);
1199
+ const payload = Array.isArray(value) ? { diagnostics: value } : value;
1200
+ const diagnostics = normalizeEntries(payload?.diagnostics, check.name);
1201
+ return { text: diagnostics.length > 0 ? void 0 : payload?.text, diagnostics };
1202
+ }
1203
+ case "text":
1204
+ default:
1205
+ return { text: raw, diagnostics: [] };
1206
+ }
1207
+ }
1208
+ function expandLinterPreset(check) {
1209
+ const spec = BUILTIN_LINTERS[check.builtin];
1210
+ return {
1211
+ name: spec.label,
1212
+ bin: spec.bin ?? spec.package,
1213
+ args: check.args ?? [...spec.args],
1214
+ ...check.projectArgs ?? spec.projectArgs ? { projectArgs: [...check.projectArgs ?? spec.projectArgs ?? []] } : {},
1215
+ extensions: check.extensions ?? [...spec.extensions],
1216
+ format: check.format ?? spec.format,
1217
+ ...check.run ? { run: check.run } : {},
1218
+ ...check.cwd ? { cwd: check.cwd } : {},
1219
+ ...check.timeoutMs ? { timeoutMs: check.timeoutMs } : {}
1220
+ };
1221
+ }
1222
+ async function runCommandCheck(check, target, phase, severity, optional = false) {
1223
+ const cwd = check.cwd ? path.resolve(target.cwd, check.cwd) : target.cwd;
1224
+ const args = check.args ?? [];
1225
+ const hasPlaceholder = args.some((arg) => arg.includes("{file}") || arg.includes("{files}"));
1226
+ const perFile = args.some((arg) => arg.includes("{file}"));
1227
+ const split = splitsByFile(target);
1228
+ const targeted = target.kind !== "project";
1229
+ const files = targeted ? targetFiles(target).filter((file) => matchesExtensions(file, check.extensions)) : [];
1230
+ if (targeted && hasPlaceholder && files.length === 0) {
1231
+ log2.debug("skip command check: no matching file target", { name: check.name, phase });
1232
+ return [];
1233
+ }
1234
+ const source = resolveCommandSource(check, cwd);
1235
+ if (source.error) {
1236
+ if (optional && phase === "edit") {
1237
+ log2.debug("skip uninstalled builtin linter", { name: check.name });
1238
+ return [];
1239
+ }
1240
+ const text = optional ? `[${check.name}] \u672A\u8FD0\u884C\uFF1A${source.error}` : `[${check.name}] ${source.error}`;
1241
+ return [buildSection(check.name, void 0, { text }, phase, severity, "command")];
1242
+ }
1243
+ const execute = async (argvTemplate, fileSet) => {
1244
+ const outcome = await runCommand(check, source, expandArgs(argvTemplate, fileSet), cwd);
1245
+ const failure = commandFailureText(check, source, outcome);
1246
+ if (failure) return { text: failure, diagnostics: [] };
1247
+ const raw = rawCommandOutput(outcome);
1248
+ try {
1249
+ if (check.adapter) return await runAdapterModule(check, outcome, cwd, fileSet);
1250
+ return adaptBuiltinFormat(check.format ?? "text", check, raw, cwd, severity);
1251
+ } catch (error) {
1252
+ const reason = error instanceof Error ? error.message : String(error);
1253
+ const excerpt = raw.replace(/\s+/g, " ").trim().slice(0, 200);
1254
+ return {
1255
+ text: `[${check.name}] \u8FD0\u884C\u5931\u8D25\uFF1A\u8F93\u51FA\u89E3\u6790\u5931\u8D25\uFF08${reason}\uFF09${excerpt ? `\uFF1A${excerpt}` : ""}`,
1256
+ diagnostics: []
1257
+ };
1258
+ }
1259
+ };
1260
+ const targetLabel = (file) => split ? displayPath(target.cwd, file) : void 0;
1261
+ if (!hasPlaceholder) {
1262
+ return [
1263
+ buildSection(check.name, void 0, await execute(args, []), phase, severity, "command")
1264
+ ];
1265
+ }
1266
+ if (target.kind === "project") {
1267
+ if (!check.projectArgs) {
1268
+ log2.debug("skip command check: no project args", { name: check.name });
1269
+ return [];
1270
+ }
1271
+ return [
1272
+ buildSection(
1273
+ check.name,
1274
+ void 0,
1275
+ await execute(check.projectArgs, []),
1276
+ phase,
1277
+ severity,
1278
+ "command"
1279
+ )
1280
+ ];
1281
+ }
1282
+ if (perFile) {
1283
+ const sections = [];
1284
+ for (const file of files) {
1285
+ const produced2 = await execute(args, [file]);
1286
+ sections.push(
1287
+ buildSection(check.name, targetLabel(file), produced2, phase, severity, "command")
1288
+ );
1289
+ }
1290
+ return sections;
1291
+ }
1292
+ const produced = await execute(args, files);
1293
+ const label = files.length === 1 ? targetLabel(files[0]) : void 0;
1294
+ return [buildSection(check.name, label, produced, phase, severity, "command")];
1295
+ }
1296
+ async function runProjectTypeCheck(workspace, severity) {
1297
+ const dirs = fs.existsSync(path.join(workspace, "tsconfig.json")) ? [workspace] : findAllTsconfigDirs(workspace);
1298
+ log2.debug("Tsc dirs to check", { count: dirs.length, dirs });
1299
+ const results = await Promise.all(dirs.map((dir) => runTypeCheck(void 0, dir)));
1300
+ return applyTscSeverity(
1301
+ {
1302
+ rawOutput: results.flatMap((output) => output.rawOutput).filter(Boolean).join("\n"),
1303
+ exitCode: results.reduce((max, output) => Math.max(max, output.exitCode), 0),
1304
+ diagnostics: results.flatMap((output) => output.diagnostics ?? []),
1305
+ source: results.find((output) => output.source)?.source
1306
+ },
1307
+ severity
1308
+ );
1309
+ }
1310
+ async function runTypecheckCheck(check, target, phase, severity) {
1311
+ const cwd = target.cwd;
1312
+ const split = splitsByFile(target);
1313
+ const files = target.kind === "project" ? [] : targetFiles(target).filter(
1314
+ (file) => matchesExtensions(file, check.extensions ?? SOURCE_EXTENSIONS)
1315
+ );
1316
+ if (target.kind !== "project" && files.length === 0) {
1317
+ log2.debug("skip builtin check: no matching file target", { builtin: check.builtin, phase });
1318
+ return [];
888
1319
  }
889
- if (parts.length === 0) return "";
890
- const body = parts.join("\n\n");
891
- return title ? `${title}
1320
+ if (split) {
1321
+ const results = await runTypeChecksForFiles(files, cwd);
1322
+ return files.map((file) => {
1323
+ const result2 = applyTscSeverity(
1324
+ results.get(path.resolve(file)) ?? { rawOutput: "", exitCode: 0 },
1325
+ severity
1326
+ );
1327
+ return buildSection(
1328
+ tscSectionTitle(result2),
1329
+ displayPath(cwd, file),
1330
+ { text: result2.rawOutput.trim(), diagnostics: result2.diagnostics },
1331
+ phase,
1332
+ severity,
1333
+ "typecheck"
1334
+ );
1335
+ });
1336
+ }
1337
+ const result = target.kind === "file" ? applyTscSeverity(await runTypeCheck(files[0], cwd), severity) : await runProjectTypeCheck(cwd, severity);
1338
+ return [
1339
+ buildSection(
1340
+ tscSectionTitle(result),
1341
+ void 0,
1342
+ { text: result.rawOutput.trim(), diagnostics: result.diagnostics },
1343
+ phase,
1344
+ severity,
1345
+ "typecheck"
1346
+ )
1347
+ ];
1348
+ }
1349
+ async function runDiagnostics(target, policy, phase) {
1350
+ const checks = policy.checks.filter((check) => checkRunsInPhase(check, phase));
1351
+ log2.debug("runDiagnostics", { phase, target: target.kind, checks: checks.length });
1352
+ const results = await Promise.all(
1353
+ checks.map((check) => {
1354
+ if (isTypecheckCheck(check)) {
1355
+ return runTypecheckCheck(check, target, phase, policy.severity);
1356
+ }
1357
+ if (isLinterPreset(check)) {
1358
+ return runCommandCheck(expandLinterPreset(check), target, phase, policy.severity, true);
1359
+ }
1360
+ if (isCommandCheck(check)) {
1361
+ return runCommandCheck(check, target, phase, policy.severity);
1362
+ }
1363
+ return Promise.resolve([]);
1364
+ })
1365
+ );
1366
+ const sections = results.flat().filter((section) => section.text !== void 0 || section.diagnostics.length > 0);
1367
+ if (sections.length === 0 && phase === "manual") {
1368
+ return {
1369
+ sections: [
1370
+ {
1371
+ name: "\u68C0\u67E5",
1372
+ text: "\u6CA1\u6709\u53EF\u8FD0\u884C\u7684\u68C0\u67E5\uFF1A\u5DF2\u914D\u7F6E\u7684\u68C0\u67E5\u90FD\u4E0D\u5339\u914D\u672C\u8F6E\u76EE\u6807\uFF08\u68C0\u67E5\u7684 extensions / run \u9636\u6BB5 / \u76EE\u6807\u7C7B\u578B\uFF09\u3002",
1373
+ diagnostics: []
1374
+ }
1375
+ ]
1376
+ };
1377
+ }
1378
+ return { sections };
1379
+ }
1380
+ function collectDiagnostics(result) {
1381
+ return result.sections.flatMap((section) => section.diagnostics);
1382
+ }
1383
+ function renderDiagnostics(result, options = {}) {
1384
+ const sections = options.onlyFindings ? result.sections.filter((section) => (section.text ?? "").trim()) : result.sections;
1385
+ if (sections.length === 0) return "";
1386
+ const groups = /* @__PURE__ */ new Map();
1387
+ for (const section of sections) {
1388
+ const key = section.target ?? "";
1389
+ const group = groups.get(key);
1390
+ if (group) group.push(section);
1391
+ else groups.set(key, [section]);
1392
+ }
1393
+ const blocks = [];
1394
+ for (const [key, group] of groups) {
1395
+ const body = group.map((section) => {
1396
+ const text = boundFindings(section.text, options.maxFindingsPerSection);
1397
+ return `## ${section.name}
1398
+
1399
+ ${text || "\u6CA1\u6709\u53D1\u73B0\u95EE\u9898"}`;
1400
+ }).join("\n\n");
1401
+ blocks.push(key ? `### ${key}
892
1402
 
893
- ${body}` : body;
1403
+ ${body}` : body);
1404
+ }
1405
+ return blocks.join("\n\n");
894
1406
  }
895
1407
 
896
1408
  // ../../core/es/common/utils.mjs
@@ -1124,7 +1636,8 @@ function setupEventRelay(ctx, config) {
1124
1636
  }
1125
1637
 
1126
1638
  // dsh-plugin/src/index.ts
1127
- var name = "aipanel";
1639
+ var SOURCE_KIND = "aipanel";
1640
+ var name = SOURCE_KIND;
1128
1641
  var inject = ["tools"];
1129
1642
  var log4 = createNodeLogger("DshPlugin");
1130
1643
  function buildNodeContext(e) {
@@ -1140,15 +1653,14 @@ function buildNodeContext(e) {
1140
1653
  return lines.join("\n");
1141
1654
  }
1142
1655
  function buildPluginMessage(text, notice) {
1656
+ const source = notice ? { kind: SOURCE_KIND, form: "notice", summary: notice.summary } : { kind: SOURCE_KIND };
1143
1657
  return {
1144
1658
  role: "user",
1145
1659
  id: randomUUID(),
1146
1660
  content: [{ type: "text", text }],
1147
- source: { kind: "plugin", plugin: name, ...notice }
1661
+ source
1148
1662
  };
1149
1663
  }
1150
- var DIAGNOSTICS_MESSAGE_MAX_CHARS = 4e3;
1151
- var MAX_FINDINGS_PER_SECTION = 3;
1152
1664
  function editTarget(exec) {
1153
1665
  const rawArgs = exec.arguments;
1154
1666
  const filePath = typeof rawArgs?.file_path === "string" ? rawArgs.file_path : rawArgs?.filePath;
@@ -1159,7 +1671,7 @@ function registerPendingEdit(pending, exec, result, decision, cwd) {
1159
1671
  if (result.isError) return;
1160
1672
  if (decision.kind !== "accept") return;
1161
1673
  const filePath = editTarget(exec);
1162
- if (!filePath || !isJsFile(filePath)) return;
1674
+ if (!filePath) return;
1163
1675
  const { agent } = exec;
1164
1676
  if (!agent) return;
1165
1677
  let files = pending.get(agent);
@@ -1169,36 +1681,24 @@ function registerPendingEdit(pending, exec, result, decision, cwd) {
1169
1681
  }
1170
1682
  files.add(path2.resolve(cwd, filePath));
1171
1683
  }
1172
- function truncateDiagnostics(text) {
1173
- if (text.length <= DIAGNOSTICS_MESSAGE_MAX_CHARS) return text;
1174
- return `${text.slice(0, DIAGNOSTICS_MESSAGE_MAX_CHARS)}
1684
+ function truncateDiagnostics(text, maxChars) {
1685
+ if (text.length <= maxChars) return text;
1686
+ return `${text.slice(0, maxChars)}
1175
1687
 
1176
1688
  \u2026\uFF08\u8BCA\u65AD\u8F93\u51FA\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD\uFF1B\u53EF\u8C03\u7528 run_diagnostics \u67E5\u770B\u5B8C\u6574\u7ED3\u679C\uFF09`;
1177
1689
  }
1178
- async function collectPendingDiagnostics(pending, cwd) {
1690
+ async function collectPendingDiagnostics(pending, cwd, policy) {
1179
1691
  const files = [...pending];
1180
- const checks = await runAllChecksForFiles(files, cwd).catch(
1181
- () => /* @__PURE__ */ new Map()
1692
+ const result = await runDiagnostics({ kind: "edited", files, cwd }, policy, "edit").catch(
1693
+ () => ({ sections: [] })
1182
1694
  );
1183
- const empty = {
1184
- eslintOutput: {},
1185
- tscOutput: { rawOutput: "", exitCode: 0 }
1186
- };
1187
- const blocks = [];
1188
- for (const file of files) {
1189
- const { eslintOutput, tscOutput } = checks.get(file) ?? empty;
1190
- const section = formatDiagnosticsSections(
1191
- `### ${path2.relative(cwd, file)}`,
1192
- eslintOutput,
1193
- tscOutput,
1194
- { onlyFindings: true, maxFindingsPerSection: MAX_FINDINGS_PER_SECTION }
1195
- );
1196
- if (!section) continue;
1197
- blocks.push(section);
1198
- }
1199
- return blocks.length > 0 ? truncateDiagnostics(`\u81EA\u52A8\u8BCA\u65AD\uFF08\u7F16\u8F91\u540E\uFF09\uFF1A
1695
+ const text = renderDiagnostics(result, {
1696
+ onlyFindings: true,
1697
+ maxFindingsPerSection: policy.maxFindingsPerSection
1698
+ });
1699
+ return text ? truncateDiagnostics(`\u81EA\u52A8\u8BCA\u65AD\uFF08\u7F16\u8F91\u540E\uFF09\uFF1A
1200
1700
 
1201
- ${blocks.join("\n\n")}`) : "";
1701
+ ${text}`, policy.maxMessageChars) : "";
1202
1702
  }
1203
1703
  function toDiagnosticEntries(items) {
1204
1704
  return items.map((d) => ({
@@ -1209,17 +1709,14 @@ function toDiagnosticEntries(items) {
1209
1709
  message: d.message
1210
1710
  }));
1211
1711
  }
1212
- function buildDiagnosticsCanonical(title, eslintOutput, tscOutput) {
1712
+ function buildDiagnosticsCanonical(title, result) {
1213
1713
  return {
1214
1714
  title,
1215
- sections: [
1216
- { title: lintSectionTitle(eslintOutput), text: eslintOutput.text || "\u6CA1\u6709\u53D1\u73B0\u95EE\u9898" },
1217
- { title: tscSectionTitle(tscOutput), text: tscOutput.rawOutput.trim() || "\u6CA1\u6709\u53D1\u73B0\u7C7B\u578B\u9519\u8BEF" }
1218
- ],
1219
- diagnostics: [
1220
- ...toDiagnosticEntries(eslintOutput.diagnostics ?? []),
1221
- ...toDiagnosticEntries(tscOutput.diagnostics ?? [])
1222
- ]
1715
+ sections: result.sections.map((section) => ({
1716
+ title: section.target ? `${section.target} \xB7 ${section.name}` : section.name,
1717
+ text: section.text ?? "\u6CA1\u6709\u53D1\u73B0\u95EE\u9898"
1718
+ })),
1719
+ diagnostics: toDiagnosticEntries(collectDiagnostics(result))
1223
1720
  };
1224
1721
  }
1225
1722
  function renderDiagnosticsText(value) {
@@ -1294,13 +1791,15 @@ function applyProviderSettings(ctx, config) {
1294
1791
  }
1295
1792
  function apply(ctx, config = {}) {
1296
1793
  const cwd = config.cwd ?? process.cwd();
1297
- const enableDiagnostics = config.enableDiagnostics ?? false;
1298
- const autoDiagnose = config.autoDiagnose ?? process.env[OPENCODE_ENV.ENABLE_LINT] === "1";
1794
+ const policy = resolveDiagnosticsPolicy(
1795
+ [DEFAULT_DIAGNOSTICS_POLICY, config.diagnostics],
1796
+ (message) => log4.warn(message)
1797
+ );
1299
1798
  const vitePort = config.vitePort ?? 0;
1300
1799
  const viteHost = config.viteHost;
1301
1800
  const contextApiPath = config.contextApiPath ?? CONTEXT_API_PATH;
1302
1801
  const tools = ctx.tools;
1303
- if (enableDiagnostics) {
1802
+ if (policy.exposeTool) {
1304
1803
  const diagnosticsTool = {
1305
1804
  name: "run_diagnostics",
1306
1805
  description: DIAGNOSTICS_TOOL_DESCRIPTION,
@@ -1351,7 +1850,7 @@ function apply(ctx, config = {}) {
1351
1850
  },
1352
1851
  required: ["title", "sections", "diagnostics"]
1353
1852
  },
1354
- // canonical → 模型可见文本(## ESLint / ## vue-tsc 分区,与 formatDiagnosticsSections 一致)
1853
+ // canonical → 模型可见文本(分区标题由配置的检查给出,与 renderDiagnostics 一致)
1355
1854
  render: (_args, value) => [
1356
1855
  { type: "text", text: renderDiagnosticsText(value) }
1357
1856
  ],
@@ -1362,27 +1861,28 @@ function apply(ctx, config = {}) {
1362
1861
  },
1363
1862
  async execute(args) {
1364
1863
  const filePath = args?.filePath;
1864
+ let target;
1865
+ let title;
1365
1866
  if (typeof filePath === "string" && filePath) {
1366
1867
  const resolved = path2.resolve(cwd, filePath);
1367
1868
  if (!fs2.existsSync(resolved)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${resolved}`);
1368
- const { eslintOutput: eslintOutput2, tscOutput: tscOutput2 } = await runAllChecks(resolved, cwd);
1369
- return buildDiagnosticsCanonical(
1370
- `\u8BCA\u65AD\u7ED3\u679C: ${path2.relative(cwd, resolved)}`,
1371
- eslintOutput2,
1372
- tscOutput2
1373
- );
1869
+ target = { kind: "file", file: resolved, cwd };
1870
+ title = `\u8BCA\u65AD\u7ED3\u679C: ${path2.relative(cwd, resolved)}`;
1871
+ } else {
1872
+ target = { kind: "project", cwd };
1873
+ title = "\u5168\u91CF\u8BCA\u65AD\u7ED3\u679C";
1374
1874
  }
1375
- const { eslintOutput, tscOutput } = await runProjectDiagnostics(cwd);
1376
- return buildDiagnosticsCanonical("\u5168\u91CF\u8BCA\u65AD\u7ED3\u679C", eslintOutput, tscOutput);
1875
+ return buildDiagnosticsCanonical(title, await runDiagnostics(target, policy, "manual"));
1377
1876
  }
1378
1877
  };
1379
1878
  tools.register(diagnosticsTool);
1879
+ }
1880
+ if (policy.auto) {
1380
1881
  const pendingEdits = /* @__PURE__ */ new WeakMap();
1381
1882
  ctx.on(
1382
1883
  "tools/post-execute",
1383
1884
  async (exec, result, next) => {
1384
1885
  const decision = await next();
1385
- if (!autoDiagnose) return decision;
1386
1886
  registerPendingEdit(pendingEdits, exec, result, decision, cwd);
1387
1887
  return decision;
1388
1888
  }
@@ -1391,12 +1891,11 @@ function apply(ctx, config = {}) {
1391
1891
  "agent/pre-step",
1392
1892
  async ({ agent }, next) => {
1393
1893
  const decision = await next();
1394
- if (!autoDiagnose) return decision;
1395
1894
  const files = pendingEdits.get(agent);
1396
1895
  if (!files || files.size === 0) return decision;
1397
1896
  pendingEdits.delete(agent);
1398
1897
  if (decision.kind === "reject") return decision;
1399
- const text = await collectPendingDiagnostics(files, cwd);
1898
+ const text = await collectPendingDiagnostics(files, cwd, policy);
1400
1899
  if (!text) return decision;
1401
1900
  return {
1402
1901
  ...decision,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aipanel/dsh-plugin",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "type": "module",
5
5
  "description": "AIPanel for DeepSeek Harness (dsh):注入审查工具 run_diagnostics、编辑后自动诊断。",
6
6
  "main": "./dist/index.js",
@@ -16,17 +16,17 @@
16
16
  "vue-tsc": "^3.3.9"
17
17
  },
18
18
  "devDependencies": {
19
- "@aipanel/core": "1.3.3",
20
- "@deepseek-ai/cordis": "^4.0.2",
21
- "@deepseek-ai/dsh-agent": "^0.1.6-alpha.2",
22
- "@deepseek-ai/dsh-llm": "^0.1.6-alpha.2",
23
- "@deepseek-ai/dsh-session": "^0.1.6-alpha.2",
24
- "@deepseek-ai/dsh-session-title": "^0.1.6-alpha.2",
25
- "@deepseek-ai/dsh-settings": "^0.1.6-alpha.2",
26
- "@deepseek-ai/dsh-tools": "^0.1.6-alpha.2",
27
- "@deepseek-ai/dsh-user-approval": "^0.1.6-alpha.2",
28
- "@deepseek-ai/dsh-user-questions": "^0.1.6-alpha.2",
29
- "@deepseek-ai/dsh-util-values": "^0.1.6-alpha.2",
19
+ "@aipanel/core": "1.3.5",
20
+ "@deepseek-ai/cordis": "~4.0.4",
21
+ "@deepseek-ai/dsh-agent": "^0.1.7-alpha.2",
22
+ "@deepseek-ai/dsh-llm": "^0.1.7-alpha.2",
23
+ "@deepseek-ai/dsh-session": "^0.1.7-alpha.2",
24
+ "@deepseek-ai/dsh-session-title": "^0.1.7-alpha.2",
25
+ "@deepseek-ai/dsh-settings": "^0.1.7-alpha.2",
26
+ "@deepseek-ai/dsh-tools": "^0.1.7-alpha.2",
27
+ "@deepseek-ai/dsh-user-approval": "^0.1.7-alpha.2",
28
+ "@deepseek-ai/dsh-user-questions": "^0.1.7-alpha.2",
29
+ "@deepseek-ai/dsh-util-values": "^0.1.7-alpha.2",
30
30
  "esbuild": "^0.25.0"
31
31
  },
32
32
  "scripts": {