@huaqiu/dsh-kicad-drc 0.5.6 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # dsh-kicad-drc
1
+ # @huaqiu/dsh-kicad-drc
2
2
 
3
3
  DeepSeek Harness (DSH) 插件:为 AI agent 注册 `kicad_drc_run` 工具,对 `.kicad_pcb` 文件执行 KiCad DRC(设计规则检查)并返回结构化结果;Web GUI 中还会在对话轮次尾部渲染**分页报告卡片**。
4
4
 
package/cordis.patch.yml CHANGED
@@ -1,4 +1,4 @@
1
- # dsh-kicad-drc bundle patch layer — 把本插件挂载进 profile 树。
1
+ # @huaqiu/dsh-kicad-drc bundle patch layer — 把本插件挂载进 profile 树。
2
2
  - insert:
3
- - id: kicad-drc
4
- name: dsh-kicad-drc
3
+ - id: kicad-dsh-drc
4
+ name: '@huaqiu/dsh-kicad-drc'
package/lib/client.js CHANGED
@@ -1,5 +1,5 @@
1
1
  window.__ModuleLoader__.load({
2
- id: "dsh-kicad-drc",
2
+ id: "@huaqiu/dsh-kicad-drc",
3
3
  factory: (require) => {
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
@@ -160,7 +160,7 @@ function normalizeDrcReport(raw) {
160
160
  //#endregion
161
161
  //#region src/client/index.tsx
162
162
  /**
163
- * dsh-kicad-drc 浏览器半边:在对话轮次尾部渲染 DRC 报告分页卡片。
163
+ * @huaqiu/dsh-kicad-drc 浏览器半边:在对话轮次尾部渲染 DRC 报告分页卡片。
164
164
  *
165
165
  * 机制(与 @huaqiu/dsh-tool-pcb-viewer 相同):
166
166
  * - host 端工具结果里带 {ok, viewUrl} JSON 块;
@@ -687,7 +687,7 @@ function PreviewWatcher(props) {
687
687
  function apply(ctx) {
688
688
  ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
689
689
  name: "conversation.session.header.actions",
690
- id: "dsh-kicad-drc-watcher",
690
+ id: "@huaqiu/dsh-kicad-drc-watcher",
691
691
  order: 999,
692
692
  inject: () => ({})
693
693
  }, PreviewWatcher));
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../src/client/parse.ts","../src/client/index.tsx"],"sourcesContent":["/**\n * 浏览器半边纯函数:从会话节点提取 DRC 预览载荷、把 drc.json 归一化成扁平违规列表。\n *\n * 本模块不依赖 React / DOM,node:test 可直接单测(test/client-parse.test.ts)。\n * 渲染组件在 index.tsx。\n */\n\n/** 工具名(与 host 端 src/tools.ts 的 TOOL_NAME 保持一致;client bundle 不能 import host 代码)。 */\nexport const DRC_TOOL_NAME = 'kicad_drc_run'\n\n/** 客户端预览载荷:host render() 输出的第二块 JSON 文本。 */\nexport interface DrcPreview {\n ok: true\n viewUrl: string\n key?: string\n name?: string\n}\n\n/** 归一化后的单条违规(新旧两种 KiCad 报告格式统一)。 */\nexport interface DrcViolation {\n severity: string\n type: string\n description: string\n x?: number\n y?: number\n}\n\n/** 归一化后的整份报告。 */\nexport interface DrcReportData {\n violations: DrcViolation[]\n kicadVersion: string\n sourceFile: string\n totalErrors: number\n totalWarnings: number\n totalExclusions: number\n totalIssues: number\n}\n\n/** 会话树遍历深度上限(防御病态嵌套)。 */\nconst MAX_WALK_DEPTH = 100\n\n/**\n * 从一个 tool-result 节点里取出预览载荷。\n * 先逐块 JSON.parse(host 的 render 会额外回一块 {ok, viewUrl} JSON);失败则正则兜底。\n */\nexport function pickPreview(node: unknown): DrcPreview | null {\n const n = node as { content?: Array<{ type?: string; text?: string }> } | null\n const blocks = (Array.isArray(n?.content) ? n.content : []).filter((b) => b?.type === 'text')\n for (const b of blocks) {\n try {\n const v = JSON.parse(String(b.text)) as Record<string, unknown>\n if (v && v.ok === true && typeof v.viewUrl === 'string') {\n return {\n ok: true,\n viewUrl: v.viewUrl,\n key: typeof v.key === 'string' ? v.key : undefined,\n name: typeof v.name === 'string' ? v.name : undefined,\n }\n }\n } catch { /* 非 JSON 块,继续 */ }\n }\n const joined = blocks.map((b) => String(b.text ?? '')).join('\\n')\n const m = joined.match(/\\/drc-report\\/api\\/report\\?key=([\\w.-]+)/)\n if (m) return { ok: true, viewUrl: m[0], key: m[1] }\n return null\n}\n\n/**\n * 遍历会话树,索引所有成功的 kicad_drc_run tool-result:\n * - byTurn: assistant turn 号 → 该轮预览载荷(turnTail 插槽按此渲染)\n * - latest: 最近一次预览载荷\n */\nexport function indexPreviews(nodes: unknown[]): { byTurn: Map<number, DrcPreview>, latest: DrcPreview | null } {\n const visited = new Set<unknown>()\n const byCallId = new Map<string, DrcPreview>()\n let latest: DrcPreview | null = null\n\n // 第一遍:收集 tool-result → callId 索引\n const walk = (node: unknown, depth: number): void => {\n if (!node || typeof node !== 'object' || depth > MAX_WALK_DEPTH || visited.has(node)) return\n const n = node as Record<string, unknown>\n visited.add(n)\n const call = n.call as { name?: string } | undefined\n if (n.kind === 'tool-result' && call?.name === DRC_TOOL_NAME && !n.isError) {\n const p = pickPreview(n)\n if (p) {\n const callId = n.callId\n if (callId != null) byCallId.set(String(callId), p)\n latest = p\n }\n }\n const kids = (Array.isArray(n.children) ? n.children : Array.isArray(n.nodes) ? n.nodes : []) as unknown[]\n for (const k of kids) walk(k, depth + 1)\n if (Array.isArray(n.subCalls)) for (const k of n.subCalls as unknown[]) walk(k, depth + 1)\n }\n for (const root of nodes) walk(root, 0)\n\n // 第二遍:assistant 轮次 → tool-call 的 callId → 预览载荷\n const byTurn = new Map<number, DrcPreview>()\n const visited2 = new Set<unknown>()\n const walkTurns = (node: unknown, depth: number): void => {\n if (!node || typeof node !== 'object' || depth > MAX_WALK_DEPTH || visited2.has(node)) return\n const n = node as Record<string, unknown>\n visited2.add(n)\n if (n.kind === 'assistant') {\n for (const b of (Array.isArray(n.blocks) ? n.blocks : []) as Array<Record<string, unknown>>) {\n if (b?.kind !== 'tool-call' || b.name !== DRC_TOOL_NAME) continue\n const callId = b.callId\n const p = callId != null ? byCallId.get(String(callId)) ?? null : null\n if (p && typeof n.turn === 'number') byTurn.set(n.turn as number, p)\n }\n }\n const kids = (Array.isArray(n.children) ? n.children : Array.isArray(n.nodes) ? n.nodes : []) as unknown[]\n for (const k of kids) walkTurns(k, depth + 1)\n if (Array.isArray(n.subCalls)) for (const k of n.subCalls as unknown[]) walkTurns(k, depth + 1)\n }\n for (const root of nodes) walkTurns(root, 0)\n\n return { byTurn, latest }\n}\n\n/** KiCad 报告里的问题数组之外、需要跳过的顶层字段。 */\nconst NON_ISSUE_SECTIONS = new Set(['ignored_checks'])\n\n/** 从 violation 条目取坐标:旧格式在顶层 pos,KiCad 10 在 items[0].pos。 */\nfunction posOf(it: Record<string, unknown>): { x?: number, y?: number } {\n const p = it.pos as Record<string, unknown> | undefined\n if (p && typeof p.x === 'number' && typeof p.y === 'number') return { x: p.x, y: p.y }\n const items = it.items\n if (Array.isArray(items) && items.length > 0) {\n const first = items[0] as Record<string, unknown> | undefined\n const sp = first?.pos as Record<string, unknown> | undefined\n if (sp && typeof sp.x === 'number' && typeof sp.y === 'number') return { x: sp.x, y: sp.y }\n }\n return {}\n}\n\n/**\n * 把 drc.json 原始文本归一化成扁平违规列表。\n * 兼容两种格式:旧版按检查类型分顶层数组(clearance/track_width/…),\n * KiCad 10 统一 violations 数组(条目自带 type 字段)。\n */\nexport function normalizeDrcReport(raw: string): DrcReportData {\n const data = JSON.parse(raw) as Record<string, unknown>\n const violations: DrcViolation[] = []\n for (const [sectionName, value] of Object.entries(data)) {\n if (!Array.isArray(value) || NON_ISSUE_SECTIONS.has(sectionName)) continue\n for (const item of value) {\n if (typeof item !== 'object' || item === null) continue\n const it = item as Record<string, unknown>\n violations.push({\n severity: typeof it.severity === 'string' ? it.severity : '',\n type: typeof it.type === 'string' ? it.type : sectionName,\n description: typeof it.description === 'string' ? it.description : '',\n ...posOf(it),\n })\n }\n }\n const totalErrors = violations.filter((v) => v.severity === 'error').length\n const totalWarnings = violations.filter((v) => v.severity === 'warning').length\n const totalExclusions = violations.filter((v) => v.severity === 'exclusion').length\n return {\n violations,\n kicadVersion: typeof data.kicad_version === 'string' ? data.kicad_version : '',\n sourceFile: typeof data.source === 'string' ? data.source : '',\n totalErrors,\n totalWarnings,\n totalExclusions,\n totalIssues: violations.length,\n }\n}\n","/**\n * dsh-kicad-drc 浏览器半边:在对话轮次尾部渲染 DRC 报告分页卡片。\n *\n * 机制(与 @huaqiu/dsh-tool-pcb-viewer 相同):\n * - host 端工具结果里带 {ok, viewUrl} JSON 块;\n * - PreviewWatcher 遍历会话树,把每轮 kicad_drc_run 的预览载荷索引到 turn 上;\n * - DrcTailCard 注册在 conversation.chat.turnTail 插槽,select() 按轮次取载荷,\n * 组件经 props.matched 收到载荷后 fetch(viewUrl) 拉取报告 JSON 并分页渲染。\n *\n * 两种视图:\n * - 紧凑卡片(轮次尾部):加宽版小表格,每页 10 行;\n * - 大屏面板(点\"放大查看\"或按 Esc 关闭):右侧全高面板,大字号表格,每页 25 行。\n *\n * 本文件由 tsdown 打包成 CJS factory(window.__ModuleLoader__.load 包装),\n * react / react/jsx-runtime 从 DSH 模块表 require(shell 提供单例)。\n */\nimport react from 'react'\nimport { pickPreview, indexPreviews, normalizeDrcReport, type DrcPreview, type DrcReportData } from './parse.ts'\n\n/** 紧凑卡片每页行数。 */\nconst PAGE_SMALL = 10\n/** 大屏面板每页行数。 */\nconst PAGE_LARGE = 25\n\ntype SeverityFilter = 'all' | 'error' | 'warning'\n\n/* ---------- 报告缓存(同一 viewUrl 不重复拉取) ---------- */\nconst reportCache = new Map<string, Promise<DrcReportData>>()\nconst CACHE_CAP = 8\nfunction loadReport(preview: DrcPreview): Promise<DrcReportData> {\n const cached = reportCache.get(preview.viewUrl)\n if (cached) return cached\n const p = fetch(preview.viewUrl)\n .then((r) => {\n if (!r.ok) throw new Error(`HTTP ${r.status}`)\n return r.text()\n })\n .then(normalizeDrcReport)\n reportCache.set(preview.viewUrl, p)\n if (reportCache.size > CACHE_CAP) {\n const oldest = reportCache.keys().next().value\n if (oldest !== undefined) reportCache.delete(oldest)\n }\n return p\n}\n\n/* ---------- 样式 ---------- */\nconst MONO = 'ui-monospace, Menlo, Consolas, monospace'\nconst S: Record<string, react.CSSProperties> = {\n /* 紧凑卡片 */\n card: {\n display: 'flex', flexDirection: 'column', gap: 8, marginTop: 6, marginBottom: 6,\n padding: 12, borderRadius: 10, maxWidth: 640, width: 'fit-content',\n background: 'var(--dsw-alias-bg-layer-3, rgba(255,255,255,.03))',\n border: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12))',\n },\n head: { display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 },\n title: {\n font: `600 13px ${MONO}`, color: 'var(--dsw-alias-label-primary, #e6e9ef)',\n overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: 280,\n },\n meta: { font: `11px ${MONO}`, color: 'var(--dsw-alias-label-tertiary, #93a1b0)', letterSpacing: '0.04em' },\n chip: {\n display: 'inline-flex', alignItems: 'center', gap: 4,\n font: `600 11px ${MONO}`, color: 'var(--dsw-alias-label-primary, #e6e9ef)',\n background: 'rgba(95,224,205,.08)', border: '1px solid rgba(95,224,205,.35)',\n borderRadius: 5, padding: '3px 10px', cursor: 'pointer', whiteSpace: 'nowrap',\n },\n chipOn: { borderColor: '#5fe0cd', color: '#5fe0cd', boxShadow: '0 0 8px rgba(95,224,205,.2)' },\n btn: {\n font: `600 11px ${MONO}`, color: '#e6e9ef',\n background: 'rgba(95,224,205,.08)', border: '1px solid rgba(95,224,205,.35)',\n borderRadius: 5, padding: '3px 10px', cursor: 'pointer', whiteSpace: 'nowrap',\n },\n btnDisabled: { opacity: 0.35, cursor: 'default' },\n state: { font: `12px ${MONO}`, color: '#7d8b9a', padding: '6px 2px' },\n /* 紧凑表格 */\n table: { width: '100%', borderCollapse: 'collapse', font: `11px ${MONO}` },\n th: {\n textAlign: 'left', padding: '4px 8px', color: 'var(--dsw-alias-label-tertiary, #93a1b0)',\n borderBottom: '1px solid rgba(255,255,255,.1)', whiteSpace: 'nowrap', fontWeight: 600,\n },\n td: {\n padding: '5px 8px', color: 'var(--dsw-alias-label-primary, #e6e9ef)',\n borderBottom: '1px solid rgba(255,255,255,.05)', verticalAlign: 'top', lineHeight: 1.45,\n },\n pager: { display: 'flex', alignItems: 'center', gap: 8, justifyContent: 'flex-end' },\n /* 大屏面板 */\n panel: {\n position: 'fixed', top: 0, right: 0, height: '100vh', zIndex: 250,\n width: 'min(980px, 94vw)',\n background: '#0b0e13', borderLeft: '1px solid rgba(95,224,205,.25)',\n boxShadow: '-18px 0 60px rgba(0,0,0,.55)',\n display: 'flex', flexDirection: 'column', transition: 'width .25s ease',\n },\n panelHead: {\n display: 'flex', alignItems: 'center', gap: 12, padding: '12px 18px',\n borderBottom: '1px solid rgba(95,224,205,.14)', flex: '0 0 auto',\n font: `13px ${MONO}`, color: '#cdd8e4', userSelect: 'none',\n },\n panelTitle: { font: `600 14px ${MONO}`, color: '#5fe0cd', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },\n closeBtn: {\n marginLeft: 'auto', font: `600 13px ${MONO}`, color: '#93a1b0',\n background: 'transparent', border: '1px solid rgba(255,255,255,.15)',\n borderRadius: 6, padding: '4px 10px', cursor: 'pointer',\n },\n panelBody: { flex: 1, overflow: 'auto', padding: '14px 18px' },\n /* 大屏表格 */\n bigTable: { width: '100%', borderCollapse: 'collapse', font: `13px ${MONO}` },\n bigTh: {\n textAlign: 'left', padding: '8px 12px', color: '#93a1b0',\n borderBottom: '1px solid rgba(255,255,255,.12)', whiteSpace: 'nowrap', fontWeight: 600, position: 'sticky', top: 0, background: '#0b0e13',\n },\n bigTd: {\n padding: '9px 12px', color: '#e6e9ef',\n borderBottom: '1px solid rgba(255,255,255,.06)', verticalAlign: 'top', lineHeight: 1.5,\n },\n}\n\nconst SEV_COLOR: Record<string, string> = { error: '#ff6b6b', warning: '#ffc94d', exclusion: '#7d8b9a' }\n\nfunction sevBadge(sev: string): react.CSSProperties {\n return {\n display: 'inline-block', padding: '1px 7px', borderRadius: 4, whiteSpace: 'nowrap',\n font: `600 10px ${MONO}`, color: SEV_COLOR[sev] ?? '#7d8b9a',\n border: `1px solid ${SEV_COLOR[sev] ?? 'rgba(255,255,255,.2)'}`,\n }\n}\n\n/* ---------- 共享子组件 ---------- */\nfunction Chips(props: { data: DrcReportData, filter: SeverityFilter, setFilter: (f: SeverityFilter) => void }) {\n const items: Array<[SeverityFilter, string]> = [\n ['all', `全部 ${props.data.totalIssues}`],\n ['error', `错误 ${props.data.totalErrors}`],\n ['warning', `警告 ${props.data.totalWarnings}`],\n ]\n return (\n <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>\n {items.map(([f, label]) => (\n <button\n key={f}\n type=\"button\"\n style={{ ...S.chip, ...(props.filter === f ? S.chipOn : null) }}\n onClick={() => props.setFilter(f)}\n >\n {label}\n </button>\n ))}\n </div>\n )\n}\n\nfunction Pager(props: { rows: number, page: number, pageCount: number, size: number, setPage: (p: number) => void }) {\n const { rows, page, pageCount, size, setPage } = props\n return (\n <div style={S.pager}>\n <span style={S.meta}>{page * size + 1}–{Math.min(rows, (page + 1) * size)} / {rows}</span>\n <button type=\"button\" style={{ ...S.btn, ...(page === 0 ? S.btnDisabled : null) }} onClick={() => page > 0 && setPage(page - 1)}>‹ 上一页</button>\n <span style={S.meta}>{page + 1} / {pageCount}</span>\n <button type=\"button\" style={{ ...S.btn, ...(page >= pageCount - 1 ? S.btnDisabled : null) }} onClick={() => page < pageCount - 1 && setPage(page + 1)}>下一页 ›</button>\n </div>\n )\n}\n\nfunction ViolationTable(props: { rows: Array<{ severity: string, type: string, description: string, x?: number, y?: number }>, startIdx: number, big?: boolean }) {\n const th = props.big ? S.bigTh : S.th\n const td = props.big ? S.bigTd : S.td\n return (\n <table style={props.big ? S.bigTable : S.table}>\n <thead>\n <tr>\n <th style={th}>#</th>\n <th style={th}>级别</th>\n <th style={th}>类型</th>\n <th style={th}>描述</th>\n <th style={th}>位置</th>\n </tr>\n </thead>\n <tbody>\n {props.rows.map((v, i) => (\n <tr key={props.startIdx + i}>\n <td style={{ ...td, whiteSpace: 'nowrap' }}>{props.startIdx + i + 1}</td>\n <td style={td}><span style={sevBadge(v.severity)}>{v.severity || '?'}</span></td>\n <td style={{ ...td, whiteSpace: 'nowrap' }}>{v.type}</td>\n <td style={td} title={v.description}>\n {props.big ? v.description : (v.description.length > 80 ? v.description.slice(0, 80) + '…' : v.description)}\n </td>\n <td style={{ ...td, whiteSpace: 'nowrap' }}>{v.x != null && v.y != null ? `(${v.x}, ${v.y})` : ''}</td>\n </tr>\n ))}\n </tbody>\n </table>\n )\n}\n\nfunction Counts(props: { data: DrcReportData }) {\n const d = props.data\n return (\n <span style={S.meta}>\n {d.totalErrors > 0 ? `⛔ ${d.totalErrors} error` : ''}\n {d.totalErrors > 0 && d.totalWarnings > 0 ? ' · ' : ''}\n {d.totalWarnings > 0 ? `⚠ ${d.totalWarnings} warning` : ''}\n {d.totalIssues === 0 ? '无违规' : ''}\n </span>\n )\n}\n\n/* ---------- 轮次尾部报告卡片(紧凑)+ 大屏面板 ---------- */\nfunction DrcTailCard(props: { matched?: DrcPreview | null }) {\n const preview = props.matched ?? null\n const [data, setData] = react.useState<DrcReportData | null>(null)\n const [error, setError] = react.useState<string | null>(null)\n const [filter, setFilter] = react.useState<SeverityFilter>('all')\n const [page, setPage] = react.useState(0)\n const [expanded, setExpanded] = react.useState(false)\n\n react.useEffect(() => {\n if (!preview) return\n let cancelled = false\n setData(null)\n setError(null)\n setPage(0)\n loadReport(preview)\n .then((d) => { if (!cancelled) setData(d) })\n .catch((e) => { if (!cancelled) setError(e instanceof Error ? e.message : String(e)) })\n return () => { cancelled = true }\n }, [preview])\n\n // Esc 关闭大屏\n react.useEffect(() => {\n if (!expanded) return\n const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') setExpanded(false) }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [expanded])\n\n if (!preview) return null\n\n // 紧凑 / 大屏共用同一份筛选与页码状态(页大小不同,切换时钳制)\n const size = expanded ? PAGE_LARGE : PAGE_SMALL\n const rows = data\n ? data.violations.filter((v) => filter === 'all' || v.severity === filter)\n : []\n const pageCount = Math.max(1, Math.ceil(rows.length / size))\n const clampedPage = Math.min(page, pageCount - 1)\n const pageRows = rows.slice(clampedPage * size, (clampedPage + 1) * size)\n\n return (\n <>\n <div style={S.card}>\n <div style={S.head}>\n <span style={S.title}>{preview.name || 'DRC 报告'}</span>\n {data && <Counts data={data} />}\n <button type=\"button\" style={{ ...S.btn, marginLeft: 'auto' }} onClick={() => setExpanded(true)}>\n 放大查看\n </button>\n </div>\n\n {!data && !error && <div style={S.state}>加载报告中…</div>}\n {error && <div style={S.state}>加载失败:{error}</div>}\n\n {data && (\n <>\n <Chips data={data} filter={filter} setFilter={(f) => { setFilter(f); setPage(0) }} />\n {rows.length === 0 ? (\n <div style={S.state}>该筛选条件下无违规项</div>\n ) : (\n <>\n <ViolationTable rows={pageRows} startIdx={clampedPage * size} />\n <Pager rows={rows.length} page={clampedPage} pageCount={pageCount} size={size} setPage={setPage} />\n </>\n )}\n </>\n )}\n </div>\n\n {expanded && (\n <div style={S.panel}>\n <div style={S.panelHead}>\n <span style={S.panelTitle}>{preview.name || 'DRC 报告'}</span>\n {data && <Counts data={data} />}\n <button type=\"button\" style={S.closeBtn} onClick={() => setExpanded(false)}>关闭 Esc</button>\n </div>\n <div style={S.panelBody}>\n {!data && !error && <div style={S.state}>加载报告中…</div>}\n {error && <div style={S.state}>加载失败:{error}</div>}\n {data && (\n <>\n <Chips data={data} filter={filter} setFilter={(f) => { setFilter(f); setPage(0) }} />\n {rows.length === 0 ? (\n <div style={{ ...S.state, marginTop: 12 }}>该筛选条件下无违规项</div>\n ) : (\n <>\n <div style={{ height: 14 }} />\n <ViolationTable rows={pageRows} startIdx={clampedPage * size} big />\n <Pager rows={rows.length} page={clampedPage} pageCount={pageCount} size={size} setPage={setPage} />\n </>\n )}\n </>\n )}\n </div>\n </div>\n )}\n </>\n )\n}\n\n/* ---------- Watcher:会话树变化时重建 turn → 预览索引 ---------- */\nconst EMPTY_NODES: unknown[] = []\nconst previewIndex: { byTurn: Map<number, DrcPreview>, latest: DrcPreview | null } = { byTurn: new Map(), latest: null }\n\nfunction PreviewWatcher(props: {\n useChat?: (sel: (s: unknown) => unknown) => unknown\n useSession?: (sel: (s: unknown) => unknown) => unknown\n}) {\n const chatNodes: unknown[] = props.useChat\n ? (props.useChat((s) => ((s as { legacy?: { nodes?: unknown[] } })?.legacy?.nodes) ?? EMPTY_NODES) as unknown[])\n : EMPTY_NODES\n const sessionNodes: unknown[] = props.useSession\n ? (props.useSession((s) => ((s as { nodes?: unknown[] })?.nodes) ?? EMPTY_NODES) as unknown[])\n : EMPTY_NODES\n const nodes = chatNodes.length > 0 ? chatNodes : sessionNodes\n\n react.useEffect(() => {\n const idx = indexPreviews(nodes)\n previewIndex.byTurn = idx.byTurn\n previewIndex.latest = idx.latest\n }, [nodes])\n\n return null\n}\n\n/* ---------- Cordis client 入口 ---------- */\ninterface SlotRegistration {\n name: string\n id?: string\n order?: number\n priority?: number\n select?: (owner: unknown) => unknown\n inject?: () => Record<string, unknown>\n}\ninterface SlotsService {\n inject(slotName: string, register: () => void): void\n register(reg: SlotRegistration, component: unknown): unknown\n}\ninterface ClientCtx {\n slots: SlotsService\n}\n\nexport function apply(ctx: ClientCtx): void {\n // Watcher:挂在会话头部动作区(渲染 null,只负责维护索引)\n ctx.slots.inject('conversation.session.header.actions', () => ctx.slots.register({\n name: 'conversation.session.header.actions',\n id: 'dsh-kicad-drc-watcher',\n order: 999,\n inject: () => ({}),\n }, PreviewWatcher))\n\n // 轮次尾部卡片:select() 按轮次返回该轮的 DRC 预览载荷(无则 null,不渲染)\n ctx.slots.inject('conversation.chat.turnTail', () => ctx.slots.register({\n name: 'conversation.chat.turnTail',\n priority: -1,\n select: (owner) => {\n const turn = (owner as { turn?: { turn?: number } } | null)?.turn?.turn\n return typeof turn === 'number' ? previewIndex.byTurn.get(turn) ?? null : null\n },\n inject: () => ({}),\n }, DrcTailCard))\n}\n\n/** Cordis 服务注入声明(client 半边)。 */\nexport const inject = ['slots']\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../src/client/parse.ts","../src/client/index.tsx"],"sourcesContent":["/**\n * 浏览器半边纯函数:从会话节点提取 DRC 预览载荷、把 drc.json 归一化成扁平违规列表。\n *\n * 本模块不依赖 React / DOM,node:test 可直接单测(test/client-parse.test.ts)。\n * 渲染组件在 index.tsx。\n */\n\n/** 工具名(与 host 端 src/tools.ts 的 TOOL_NAME 保持一致;client bundle 不能 import host 代码)。 */\nexport const DRC_TOOL_NAME = 'kicad_drc_run'\n\n/** 客户端预览载荷:host render() 输出的第二块 JSON 文本。 */\nexport interface DrcPreview {\n ok: true\n viewUrl: string\n key?: string\n name?: string\n}\n\n/** 归一化后的单条违规(新旧两种 KiCad 报告格式统一)。 */\nexport interface DrcViolation {\n severity: string\n type: string\n description: string\n x?: number\n y?: number\n}\n\n/** 归一化后的整份报告。 */\nexport interface DrcReportData {\n violations: DrcViolation[]\n kicadVersion: string\n sourceFile: string\n totalErrors: number\n totalWarnings: number\n totalExclusions: number\n totalIssues: number\n}\n\n/** 会话树遍历深度上限(防御病态嵌套)。 */\nconst MAX_WALK_DEPTH = 100\n\n/**\n * 从一个 tool-result 节点里取出预览载荷。\n * 先逐块 JSON.parse(host 的 render 会额外回一块 {ok, viewUrl} JSON);失败则正则兜底。\n */\nexport function pickPreview(node: unknown): DrcPreview | null {\n const n = node as { content?: Array<{ type?: string; text?: string }> } | null\n const blocks = (Array.isArray(n?.content) ? n.content : []).filter((b) => b?.type === 'text')\n for (const b of blocks) {\n try {\n const v = JSON.parse(String(b.text)) as Record<string, unknown>\n if (v && v.ok === true && typeof v.viewUrl === 'string') {\n return {\n ok: true,\n viewUrl: v.viewUrl,\n key: typeof v.key === 'string' ? v.key : undefined,\n name: typeof v.name === 'string' ? v.name : undefined,\n }\n }\n } catch { /* 非 JSON 块,继续 */ }\n }\n const joined = blocks.map((b) => String(b.text ?? '')).join('\\n')\n const m = joined.match(/\\/drc-report\\/api\\/report\\?key=([\\w.-]+)/)\n if (m) return { ok: true, viewUrl: m[0], key: m[1] }\n return null\n}\n\n/**\n * 遍历会话树,索引所有成功的 kicad_drc_run tool-result:\n * - byTurn: assistant turn 号 → 该轮预览载荷(turnTail 插槽按此渲染)\n * - latest: 最近一次预览载荷\n */\nexport function indexPreviews(nodes: unknown[]): { byTurn: Map<number, DrcPreview>, latest: DrcPreview | null } {\n const visited = new Set<unknown>()\n const byCallId = new Map<string, DrcPreview>()\n let latest: DrcPreview | null = null\n\n // 第一遍:收集 tool-result → callId 索引\n const walk = (node: unknown, depth: number): void => {\n if (!node || typeof node !== 'object' || depth > MAX_WALK_DEPTH || visited.has(node)) return\n const n = node as Record<string, unknown>\n visited.add(n)\n const call = n.call as { name?: string } | undefined\n if (n.kind === 'tool-result' && call?.name === DRC_TOOL_NAME && !n.isError) {\n const p = pickPreview(n)\n if (p) {\n const callId = n.callId\n if (callId != null) byCallId.set(String(callId), p)\n latest = p\n }\n }\n const kids = (Array.isArray(n.children) ? n.children : Array.isArray(n.nodes) ? n.nodes : []) as unknown[]\n for (const k of kids) walk(k, depth + 1)\n if (Array.isArray(n.subCalls)) for (const k of n.subCalls as unknown[]) walk(k, depth + 1)\n }\n for (const root of nodes) walk(root, 0)\n\n // 第二遍:assistant 轮次 → tool-call 的 callId → 预览载荷\n const byTurn = new Map<number, DrcPreview>()\n const visited2 = new Set<unknown>()\n const walkTurns = (node: unknown, depth: number): void => {\n if (!node || typeof node !== 'object' || depth > MAX_WALK_DEPTH || visited2.has(node)) return\n const n = node as Record<string, unknown>\n visited2.add(n)\n if (n.kind === 'assistant') {\n for (const b of (Array.isArray(n.blocks) ? n.blocks : []) as Array<Record<string, unknown>>) {\n if (b?.kind !== 'tool-call' || b.name !== DRC_TOOL_NAME) continue\n const callId = b.callId\n const p = callId != null ? byCallId.get(String(callId)) ?? null : null\n if (p && typeof n.turn === 'number') byTurn.set(n.turn as number, p)\n }\n }\n const kids = (Array.isArray(n.children) ? n.children : Array.isArray(n.nodes) ? n.nodes : []) as unknown[]\n for (const k of kids) walkTurns(k, depth + 1)\n if (Array.isArray(n.subCalls)) for (const k of n.subCalls as unknown[]) walkTurns(k, depth + 1)\n }\n for (const root of nodes) walkTurns(root, 0)\n\n return { byTurn, latest }\n}\n\n/** KiCad 报告里的问题数组之外、需要跳过的顶层字段。 */\nconst NON_ISSUE_SECTIONS = new Set(['ignored_checks'])\n\n/** 从 violation 条目取坐标:旧格式在顶层 pos,KiCad 10 在 items[0].pos。 */\nfunction posOf(it: Record<string, unknown>): { x?: number, y?: number } {\n const p = it.pos as Record<string, unknown> | undefined\n if (p && typeof p.x === 'number' && typeof p.y === 'number') return { x: p.x, y: p.y }\n const items = it.items\n if (Array.isArray(items) && items.length > 0) {\n const first = items[0] as Record<string, unknown> | undefined\n const sp = first?.pos as Record<string, unknown> | undefined\n if (sp && typeof sp.x === 'number' && typeof sp.y === 'number') return { x: sp.x, y: sp.y }\n }\n return {}\n}\n\n/**\n * 把 drc.json 原始文本归一化成扁平违规列表。\n * 兼容两种格式:旧版按检查类型分顶层数组(clearance/track_width/…),\n * KiCad 10 统一 violations 数组(条目自带 type 字段)。\n */\nexport function normalizeDrcReport(raw: string): DrcReportData {\n const data = JSON.parse(raw) as Record<string, unknown>\n const violations: DrcViolation[] = []\n for (const [sectionName, value] of Object.entries(data)) {\n if (!Array.isArray(value) || NON_ISSUE_SECTIONS.has(sectionName)) continue\n for (const item of value) {\n if (typeof item !== 'object' || item === null) continue\n const it = item as Record<string, unknown>\n violations.push({\n severity: typeof it.severity === 'string' ? it.severity : '',\n type: typeof it.type === 'string' ? it.type : sectionName,\n description: typeof it.description === 'string' ? it.description : '',\n ...posOf(it),\n })\n }\n }\n const totalErrors = violations.filter((v) => v.severity === 'error').length\n const totalWarnings = violations.filter((v) => v.severity === 'warning').length\n const totalExclusions = violations.filter((v) => v.severity === 'exclusion').length\n return {\n violations,\n kicadVersion: typeof data.kicad_version === 'string' ? data.kicad_version : '',\n sourceFile: typeof data.source === 'string' ? data.source : '',\n totalErrors,\n totalWarnings,\n totalExclusions,\n totalIssues: violations.length,\n }\n}\n","/**\n * @huaqiu/dsh-kicad-drc 浏览器半边:在对话轮次尾部渲染 DRC 报告分页卡片。\n *\n * 机制(与 @huaqiu/dsh-tool-pcb-viewer 相同):\n * - host 端工具结果里带 {ok, viewUrl} JSON 块;\n * - PreviewWatcher 遍历会话树,把每轮 kicad_drc_run 的预览载荷索引到 turn 上;\n * - DrcTailCard 注册在 conversation.chat.turnTail 插槽,select() 按轮次取载荷,\n * 组件经 props.matched 收到载荷后 fetch(viewUrl) 拉取报告 JSON 并分页渲染。\n *\n * 两种视图:\n * - 紧凑卡片(轮次尾部):加宽版小表格,每页 10 行;\n * - 大屏面板(点\"放大查看\"或按 Esc 关闭):右侧全高面板,大字号表格,每页 25 行。\n *\n * 本文件由 tsdown 打包成 CJS factory(window.__ModuleLoader__.load 包装),\n * react / react/jsx-runtime 从 DSH 模块表 require(shell 提供单例)。\n */\nimport react from 'react'\nimport { pickPreview, indexPreviews, normalizeDrcReport, type DrcPreview, type DrcReportData } from './parse.ts'\n\n/** 紧凑卡片每页行数。 */\nconst PAGE_SMALL = 10\n/** 大屏面板每页行数。 */\nconst PAGE_LARGE = 25\n\ntype SeverityFilter = 'all' | 'error' | 'warning'\n\n/* ---------- 报告缓存(同一 viewUrl 不重复拉取) ---------- */\nconst reportCache = new Map<string, Promise<DrcReportData>>()\nconst CACHE_CAP = 8\nfunction loadReport(preview: DrcPreview): Promise<DrcReportData> {\n const cached = reportCache.get(preview.viewUrl)\n if (cached) return cached\n const p = fetch(preview.viewUrl)\n .then((r) => {\n if (!r.ok) throw new Error(`HTTP ${r.status}`)\n return r.text()\n })\n .then(normalizeDrcReport)\n reportCache.set(preview.viewUrl, p)\n if (reportCache.size > CACHE_CAP) {\n const oldest = reportCache.keys().next().value\n if (oldest !== undefined) reportCache.delete(oldest)\n }\n return p\n}\n\n/* ---------- 样式 ---------- */\nconst MONO = 'ui-monospace, Menlo, Consolas, monospace'\nconst S: Record<string, react.CSSProperties> = {\n /* 紧凑卡片 */\n card: {\n display: 'flex', flexDirection: 'column', gap: 8, marginTop: 6, marginBottom: 6,\n padding: 12, borderRadius: 10, maxWidth: 640, width: 'fit-content',\n background: 'var(--dsw-alias-bg-layer-3, rgba(255,255,255,.03))',\n border: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12))',\n },\n head: { display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 },\n title: {\n font: `600 13px ${MONO}`, color: 'var(--dsw-alias-label-primary, #e6e9ef)',\n overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: 280,\n },\n meta: { font: `11px ${MONO}`, color: 'var(--dsw-alias-label-tertiary, #93a1b0)', letterSpacing: '0.04em' },\n chip: {\n display: 'inline-flex', alignItems: 'center', gap: 4,\n font: `600 11px ${MONO}`, color: 'var(--dsw-alias-label-primary, #e6e9ef)',\n background: 'rgba(95,224,205,.08)', border: '1px solid rgba(95,224,205,.35)',\n borderRadius: 5, padding: '3px 10px', cursor: 'pointer', whiteSpace: 'nowrap',\n },\n chipOn: { borderColor: '#5fe0cd', color: '#5fe0cd', boxShadow: '0 0 8px rgba(95,224,205,.2)' },\n btn: {\n font: `600 11px ${MONO}`, color: '#e6e9ef',\n background: 'rgba(95,224,205,.08)', border: '1px solid rgba(95,224,205,.35)',\n borderRadius: 5, padding: '3px 10px', cursor: 'pointer', whiteSpace: 'nowrap',\n },\n btnDisabled: { opacity: 0.35, cursor: 'default' },\n state: { font: `12px ${MONO}`, color: '#7d8b9a', padding: '6px 2px' },\n /* 紧凑表格 */\n table: { width: '100%', borderCollapse: 'collapse', font: `11px ${MONO}` },\n th: {\n textAlign: 'left', padding: '4px 8px', color: 'var(--dsw-alias-label-tertiary, #93a1b0)',\n borderBottom: '1px solid rgba(255,255,255,.1)', whiteSpace: 'nowrap', fontWeight: 600,\n },\n td: {\n padding: '5px 8px', color: 'var(--dsw-alias-label-primary, #e6e9ef)',\n borderBottom: '1px solid rgba(255,255,255,.05)', verticalAlign: 'top', lineHeight: 1.45,\n },\n pager: { display: 'flex', alignItems: 'center', gap: 8, justifyContent: 'flex-end' },\n /* 大屏面板 */\n panel: {\n position: 'fixed', top: 0, right: 0, height: '100vh', zIndex: 250,\n width: 'min(980px, 94vw)',\n background: '#0b0e13', borderLeft: '1px solid rgba(95,224,205,.25)',\n boxShadow: '-18px 0 60px rgba(0,0,0,.55)',\n display: 'flex', flexDirection: 'column', transition: 'width .25s ease',\n },\n panelHead: {\n display: 'flex', alignItems: 'center', gap: 12, padding: '12px 18px',\n borderBottom: '1px solid rgba(95,224,205,.14)', flex: '0 0 auto',\n font: `13px ${MONO}`, color: '#cdd8e4', userSelect: 'none',\n },\n panelTitle: { font: `600 14px ${MONO}`, color: '#5fe0cd', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },\n closeBtn: {\n marginLeft: 'auto', font: `600 13px ${MONO}`, color: '#93a1b0',\n background: 'transparent', border: '1px solid rgba(255,255,255,.15)',\n borderRadius: 6, padding: '4px 10px', cursor: 'pointer',\n },\n panelBody: { flex: 1, overflow: 'auto', padding: '14px 18px' },\n /* 大屏表格 */\n bigTable: { width: '100%', borderCollapse: 'collapse', font: `13px ${MONO}` },\n bigTh: {\n textAlign: 'left', padding: '8px 12px', color: '#93a1b0',\n borderBottom: '1px solid rgba(255,255,255,.12)', whiteSpace: 'nowrap', fontWeight: 600, position: 'sticky', top: 0, background: '#0b0e13',\n },\n bigTd: {\n padding: '9px 12px', color: '#e6e9ef',\n borderBottom: '1px solid rgba(255,255,255,.06)', verticalAlign: 'top', lineHeight: 1.5,\n },\n}\n\nconst SEV_COLOR: Record<string, string> = { error: '#ff6b6b', warning: '#ffc94d', exclusion: '#7d8b9a' }\n\nfunction sevBadge(sev: string): react.CSSProperties {\n return {\n display: 'inline-block', padding: '1px 7px', borderRadius: 4, whiteSpace: 'nowrap',\n font: `600 10px ${MONO}`, color: SEV_COLOR[sev] ?? '#7d8b9a',\n border: `1px solid ${SEV_COLOR[sev] ?? 'rgba(255,255,255,.2)'}`,\n }\n}\n\n/* ---------- 共享子组件 ---------- */\nfunction Chips(props: { data: DrcReportData, filter: SeverityFilter, setFilter: (f: SeverityFilter) => void }) {\n const items: Array<[SeverityFilter, string]> = [\n ['all', `全部 ${props.data.totalIssues}`],\n ['error', `错误 ${props.data.totalErrors}`],\n ['warning', `警告 ${props.data.totalWarnings}`],\n ]\n return (\n <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>\n {items.map(([f, label]) => (\n <button\n key={f}\n type=\"button\"\n style={{ ...S.chip, ...(props.filter === f ? S.chipOn : null) }}\n onClick={() => props.setFilter(f)}\n >\n {label}\n </button>\n ))}\n </div>\n )\n}\n\nfunction Pager(props: { rows: number, page: number, pageCount: number, size: number, setPage: (p: number) => void }) {\n const { rows, page, pageCount, size, setPage } = props\n return (\n <div style={S.pager}>\n <span style={S.meta}>{page * size + 1}–{Math.min(rows, (page + 1) * size)} / {rows}</span>\n <button type=\"button\" style={{ ...S.btn, ...(page === 0 ? S.btnDisabled : null) }} onClick={() => page > 0 && setPage(page - 1)}>‹ 上一页</button>\n <span style={S.meta}>{page + 1} / {pageCount}</span>\n <button type=\"button\" style={{ ...S.btn, ...(page >= pageCount - 1 ? S.btnDisabled : null) }} onClick={() => page < pageCount - 1 && setPage(page + 1)}>下一页 ›</button>\n </div>\n )\n}\n\nfunction ViolationTable(props: { rows: Array<{ severity: string, type: string, description: string, x?: number, y?: number }>, startIdx: number, big?: boolean }) {\n const th = props.big ? S.bigTh : S.th\n const td = props.big ? S.bigTd : S.td\n return (\n <table style={props.big ? S.bigTable : S.table}>\n <thead>\n <tr>\n <th style={th}>#</th>\n <th style={th}>级别</th>\n <th style={th}>类型</th>\n <th style={th}>描述</th>\n <th style={th}>位置</th>\n </tr>\n </thead>\n <tbody>\n {props.rows.map((v, i) => (\n <tr key={props.startIdx + i}>\n <td style={{ ...td, whiteSpace: 'nowrap' }}>{props.startIdx + i + 1}</td>\n <td style={td}><span style={sevBadge(v.severity)}>{v.severity || '?'}</span></td>\n <td style={{ ...td, whiteSpace: 'nowrap' }}>{v.type}</td>\n <td style={td} title={v.description}>\n {props.big ? v.description : (v.description.length > 80 ? v.description.slice(0, 80) + '…' : v.description)}\n </td>\n <td style={{ ...td, whiteSpace: 'nowrap' }}>{v.x != null && v.y != null ? `(${v.x}, ${v.y})` : ''}</td>\n </tr>\n ))}\n </tbody>\n </table>\n )\n}\n\nfunction Counts(props: { data: DrcReportData }) {\n const d = props.data\n return (\n <span style={S.meta}>\n {d.totalErrors > 0 ? `⛔ ${d.totalErrors} error` : ''}\n {d.totalErrors > 0 && d.totalWarnings > 0 ? ' · ' : ''}\n {d.totalWarnings > 0 ? `⚠ ${d.totalWarnings} warning` : ''}\n {d.totalIssues === 0 ? '无违规' : ''}\n </span>\n )\n}\n\n/* ---------- 轮次尾部报告卡片(紧凑)+ 大屏面板 ---------- */\nfunction DrcTailCard(props: { matched?: DrcPreview | null }) {\n const preview = props.matched ?? null\n const [data, setData] = react.useState<DrcReportData | null>(null)\n const [error, setError] = react.useState<string | null>(null)\n const [filter, setFilter] = react.useState<SeverityFilter>('all')\n const [page, setPage] = react.useState(0)\n const [expanded, setExpanded] = react.useState(false)\n\n react.useEffect(() => {\n if (!preview) return\n let cancelled = false\n setData(null)\n setError(null)\n setPage(0)\n loadReport(preview)\n .then((d) => { if (!cancelled) setData(d) })\n .catch((e) => { if (!cancelled) setError(e instanceof Error ? e.message : String(e)) })\n return () => { cancelled = true }\n }, [preview])\n\n // Esc 关闭大屏\n react.useEffect(() => {\n if (!expanded) return\n const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') setExpanded(false) }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [expanded])\n\n if (!preview) return null\n\n // 紧凑 / 大屏共用同一份筛选与页码状态(页大小不同,切换时钳制)\n const size = expanded ? PAGE_LARGE : PAGE_SMALL\n const rows = data\n ? data.violations.filter((v) => filter === 'all' || v.severity === filter)\n : []\n const pageCount = Math.max(1, Math.ceil(rows.length / size))\n const clampedPage = Math.min(page, pageCount - 1)\n const pageRows = rows.slice(clampedPage * size, (clampedPage + 1) * size)\n\n return (\n <>\n <div style={S.card}>\n <div style={S.head}>\n <span style={S.title}>{preview.name || 'DRC 报告'}</span>\n {data && <Counts data={data} />}\n <button type=\"button\" style={{ ...S.btn, marginLeft: 'auto' }} onClick={() => setExpanded(true)}>\n 放大查看\n </button>\n </div>\n\n {!data && !error && <div style={S.state}>加载报告中…</div>}\n {error && <div style={S.state}>加载失败:{error}</div>}\n\n {data && (\n <>\n <Chips data={data} filter={filter} setFilter={(f) => { setFilter(f); setPage(0) }} />\n {rows.length === 0 ? (\n <div style={S.state}>该筛选条件下无违规项</div>\n ) : (\n <>\n <ViolationTable rows={pageRows} startIdx={clampedPage * size} />\n <Pager rows={rows.length} page={clampedPage} pageCount={pageCount} size={size} setPage={setPage} />\n </>\n )}\n </>\n )}\n </div>\n\n {expanded && (\n <div style={S.panel}>\n <div style={S.panelHead}>\n <span style={S.panelTitle}>{preview.name || 'DRC 报告'}</span>\n {data && <Counts data={data} />}\n <button type=\"button\" style={S.closeBtn} onClick={() => setExpanded(false)}>关闭 Esc</button>\n </div>\n <div style={S.panelBody}>\n {!data && !error && <div style={S.state}>加载报告中…</div>}\n {error && <div style={S.state}>加载失败:{error}</div>}\n {data && (\n <>\n <Chips data={data} filter={filter} setFilter={(f) => { setFilter(f); setPage(0) }} />\n {rows.length === 0 ? (\n <div style={{ ...S.state, marginTop: 12 }}>该筛选条件下无违规项</div>\n ) : (\n <>\n <div style={{ height: 14 }} />\n <ViolationTable rows={pageRows} startIdx={clampedPage * size} big />\n <Pager rows={rows.length} page={clampedPage} pageCount={pageCount} size={size} setPage={setPage} />\n </>\n )}\n </>\n )}\n </div>\n </div>\n )}\n </>\n )\n}\n\n/* ---------- Watcher:会话树变化时重建 turn → 预览索引 ---------- */\nconst EMPTY_NODES: unknown[] = []\nconst previewIndex: { byTurn: Map<number, DrcPreview>, latest: DrcPreview | null } = { byTurn: new Map(), latest: null }\n\nfunction PreviewWatcher(props: {\n useChat?: (sel: (s: unknown) => unknown) => unknown\n useSession?: (sel: (s: unknown) => unknown) => unknown\n}) {\n const chatNodes: unknown[] = props.useChat\n ? (props.useChat((s) => ((s as { legacy?: { nodes?: unknown[] } })?.legacy?.nodes) ?? EMPTY_NODES) as unknown[])\n : EMPTY_NODES\n const sessionNodes: unknown[] = props.useSession\n ? (props.useSession((s) => ((s as { nodes?: unknown[] })?.nodes) ?? EMPTY_NODES) as unknown[])\n : EMPTY_NODES\n const nodes = chatNodes.length > 0 ? chatNodes : sessionNodes\n\n react.useEffect(() => {\n const idx = indexPreviews(nodes)\n previewIndex.byTurn = idx.byTurn\n previewIndex.latest = idx.latest\n }, [nodes])\n\n return null\n}\n\n/* ---------- Cordis client 入口 ---------- */\ninterface SlotRegistration {\n name: string\n id?: string\n order?: number\n priority?: number\n select?: (owner: unknown) => unknown\n inject?: () => Record<string, unknown>\n}\ninterface SlotsService {\n inject(slotName: string, register: () => void): void\n register(reg: SlotRegistration, component: unknown): unknown\n}\ninterface ClientCtx {\n slots: SlotsService\n}\n\nexport function apply(ctx: ClientCtx): void {\n // Watcher:挂在会话头部动作区(渲染 null,只负责维护索引)\n ctx.slots.inject('conversation.session.header.actions', () => ctx.slots.register({\n name: 'conversation.session.header.actions',\n id: '@huaqiu/dsh-kicad-drc-watcher',\n order: 999,\n inject: () => ({}),\n }, PreviewWatcher))\n\n // 轮次尾部卡片:select() 按轮次返回该轮的 DRC 预览载荷(无则 null,不渲染)\n ctx.slots.inject('conversation.chat.turnTail', () => ctx.slots.register({\n name: 'conversation.chat.turnTail',\n priority: -1,\n select: (owner) => {\n const turn = (owner as { turn?: { turn?: number } } | null)?.turn?.turn\n return typeof turn === 'number' ? previewIndex.byTurn.get(turn) ?? null : null\n },\n inject: () => ({}),\n }, DrcTailCard))\n}\n\n/** Cordis 服务注入声明(client 半边)。 */\nexport const inject = ['slots']\n"],"mappings":"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"}
package/lib/index.d.ts CHANGED
@@ -133,7 +133,7 @@ interface Config {
133
133
  /** 运行时配置校验和默认值。 */
134
134
  declare const Config: Schema<Config>;
135
135
  /** 插件名,Cordis 会显示。 */
136
- declare const name = "dsh-kicad-drc";
136
+ declare const name = "@huaqiu/dsh-kicad-drc";
137
137
  /** 依赖的 DSH 服务(webServer 用于 /drc-report 报告查看路由;headless 环境缺失时自动降级)。 */
138
138
  declare const inject: string[];
139
139
  /**
package/lib/index.js CHANGED
@@ -6,7 +6,7 @@ import { basename, dirname, join, resolve } from "node:path";
6
6
  import { randomUUID } from "node:crypto";
7
7
  import { defineTool } from "@deepseek-ai/dsh-tools";
8
8
  //#region package.json
9
- var version = "0.5.6";
9
+ var version = "0.5.7";
10
10
  //#endregion
11
11
  //#region src/executor.ts
12
12
  var ExecutorError = class extends Error {
@@ -419,14 +419,14 @@ async function fetchWithRetry(url, init, label) {
419
419
  const resp = await fetch(url, init);
420
420
  if (resp.status >= 500 && attempt < 2) {
421
421
  await resp.body?.cancel().catch(() => {});
422
- console.warn(`[dsh-kicad-drc] ${label} HTTP ${resp.status},1s 后重试`);
422
+ console.warn(`[@huaqiu/dsh-kicad-drc] ${label} HTTP ${resp.status},1s 后重试`);
423
423
  await sleep(1e3);
424
424
  continue;
425
425
  }
426
426
  return resp;
427
427
  } catch (e) {
428
428
  if (isAbortError(e) || attempt >= 2) throw e;
429
- console.warn(`[dsh-kicad-drc] ${label} 网络错误: ${e.message},1s 后重试`);
429
+ console.warn(`[@huaqiu/dsh-kicad-drc] ${label} 网络错误: ${e.message},1s 后重试`);
430
430
  await sleep(1e3);
431
431
  }
432
432
  throw new Error("unreachable");
@@ -508,20 +508,20 @@ var RemoteExecutor = class {
508
508
  } catch {
509
509
  drcJson = null;
510
510
  }
511
- else console.warn(`[dsh-kicad-drc] outputFilePath 不可读: ${remotePath}`);
511
+ else console.warn(`[@huaqiu/dsh-kicad-drc] outputFilePath 不可读: ${remotePath}`);
512
512
  }
513
513
  let drcJsonPath = null;
514
514
  if (drcJson) try {
515
515
  mkdirSync(pcbDir, { recursive: true });
516
516
  writeFileSync(localPersistPath, drcJson, "utf-8");
517
517
  drcJsonPath = localPersistPath;
518
- console.log(`[dsh-kicad-drc] drc.json 已持久化: ${localPersistPath}`);
518
+ console.log(`[@huaqiu/dsh-kicad-drc] drc.json 已持久化: ${localPersistPath}`);
519
519
  } catch (e) {
520
- console.warn(`[dsh-kicad-drc] 写入本地文件失败: ${e.message}`);
520
+ console.warn(`[@huaqiu/dsh-kicad-drc] 写入本地文件失败: ${e.message}`);
521
521
  drcJsonPath = localPersistPath;
522
522
  }
523
523
  let stderr = data.stderr ?? "";
524
- if (drcJson === null && data.exitCode === 0) stderr += "\n[dsh-kicad-drc] 警告: kicad-cli 执行成功(exit 0),但插件未能取回 drc.json(HTTP 下载或共享磁盘读取失败)";
524
+ if (drcJson === null && data.exitCode === 0) stderr += "\n[@huaqiu/dsh-kicad-drc] 警告: kicad-cli 执行成功(exit 0),但插件未能取回 drc.json(HTTP 下载或共享磁盘读取失败)";
525
525
  const elapsedMs = Math.round(performance.now() - start);
526
526
  return {
527
527
  exitCode: data.exitCode,
@@ -543,14 +543,14 @@ var RemoteExecutor = class {
543
543
  const resp = await fetchWithRetry(fileUrl, { signal: ctrl.signal }, "下载 drc.json");
544
544
  clearTimeout(t);
545
545
  if (!resp.ok) {
546
- console.warn(`[dsh-kicad-drc] 下载 drc.json 失败: HTTP ${resp.status} ${fileUrl}`);
546
+ console.warn(`[@huaqiu/dsh-kicad-drc] 下载 drc.json 失败: HTTP ${resp.status} ${fileUrl}`);
547
547
  return null;
548
548
  }
549
549
  const raw = await resp.text();
550
550
  JSON.parse(raw);
551
551
  return raw;
552
552
  } catch (e) {
553
- console.warn(`[dsh-kicad-drc] 下载 drc.json 异常: ${e.message} ${fileUrl}`);
553
+ console.warn(`[@huaqiu/dsh-kicad-drc] 下载 drc.json 异常: ${e.message} ${fileUrl}`);
554
554
  return null;
555
555
  }
556
556
  }
@@ -931,14 +931,14 @@ const Config = Schema.object({
931
931
  defaultTimeoutSeconds: Schema.number().default(600)
932
932
  });
933
933
  /** 插件名,Cordis 会显示。 */
934
- const name = "dsh-kicad-drc";
934
+ const name = "@huaqiu/dsh-kicad-drc";
935
935
  /** 依赖的 DSH 服务(webServer 用于 /drc-report 报告查看路由;headless 环境缺失时自动降级)。 */
936
936
  const inject = ["tools", "webServer"];
937
937
  /**
938
938
  * 插件入口:根据配置构建执行器并注册工具。
939
939
  */
940
940
  function apply(ctx, config) {
941
- console.log(`[dsh-kicad-drc] 插件版本: ${PLUGIN_VERSION}`);
941
+ console.log(`[@huaqiu/dsh-kicad-drc] 插件版本: ${PLUGIN_VERSION}`);
942
942
  let executor;
943
943
  if (config.executorMode === "remote") {
944
944
  executor = new RemoteExecutor({
@@ -946,14 +946,14 @@ function apply(ctx, config) {
946
946
  workDirRoot: config.cliRunnerWorkDirRoot,
947
947
  fileBaseUrl: config.fileBaseUrl
948
948
  });
949
- console.log(`[dsh-kicad-drc] 使用远程 pcblint-java: ${config.pcblintBaseUrl}` + (config.fileBaseUrl ? `, fileBaseUrl: ${config.fileBaseUrl}` : "(fileBaseUrl 为空,共享磁盘读取模式)"));
949
+ console.log(`[@huaqiu/dsh-kicad-drc] 使用远程 pcblint-java: ${config.pcblintBaseUrl}` + (config.fileBaseUrl ? `, fileBaseUrl: ${config.fileBaseUrl}` : "(fileBaseUrl 为空,共享磁盘读取模式)"));
950
950
  } else {
951
951
  let cliPath;
952
952
  try {
953
953
  cliPath = resolveKicadCli(config.kicadCliPath || void 0);
954
- console.log(`[dsh-kicad-drc] 使用本地 kicad-cli: ${cliPath}`);
954
+ console.log(`[@huaqiu/dsh-kicad-drc] 使用本地 kicad-cli: ${cliPath}`);
955
955
  } catch {
956
- console.warn("[dsh-kicad-drc] 本地模式:启动时未找到 kicad-cli,将在首次调用工具时重新探测");
956
+ console.warn("[@huaqiu/dsh-kicad-drc] 本地模式:启动时未找到 kicad-cli,将在首次调用工具时重新探测");
957
957
  }
958
958
  executor = new LocalExecutor(cliPath ?? (config.kicadCliPath || void 0));
959
959
  }
@@ -970,7 +970,7 @@ function apply(ctx, config) {
970
970
  return () => {
971
971
  if (typeof reg === "function") reg();
972
972
  };
973
- }, "dsh-kicad-drc: /drc-report routes");
973
+ }, "@huaqiu/dsh-kicad-drc: /drc-report routes");
974
974
  registerReport = (key, reportPath) => registry.register(key, reportPath);
975
975
  }
976
976
  const tool = createDrcTool(executor, config.defaultTimeoutSeconds, registerReport);
@@ -978,15 +978,15 @@ function apply(ctx, config) {
978
978
  if (registry) {
979
979
  registry.register(tool);
980
980
  ctx.effect(() => {
981
- console.log(`[dsh-kicad-drc] 已注册工具: ${TOOL_NAME}`);
981
+ console.log(`[@huaqiu/dsh-kicad-drc] 已注册工具: ${TOOL_NAME}`);
982
982
  return () => {
983
983
  try {
984
984
  registry.dispose(TOOL_NAME);
985
985
  } catch {}
986
- console.log(`[dsh-kicad-drc] 工具已卸载: ${TOOL_NAME}`);
986
+ console.log(`[@huaqiu/dsh-kicad-drc] 工具已卸载: ${TOOL_NAME}`);
987
987
  };
988
- }, "dsh-kicad-drc.lifecycle");
989
- } else console.warn("[dsh-kicad-drc] 未找到 tools 服务,工具未注册");
988
+ }, "@huaqiu/dsh-kicad-drc.lifecycle");
989
+ } else console.warn("[@huaqiu/dsh-kicad-drc] 未找到 tools 服务,工具未注册");
990
990
  }
991
991
  //#endregion
992
992
  export { Config, LocalExecutor, RemoteExecutor, apply, createDrcTool, formatSummaryText, inject, name, parseDrcSummary, resolveKicadCli };
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["pkg.version"],"sources":["../package.json","../src/executor.ts","../src/local-executor.ts","../src/remote-executor.ts","../src/drc-summary.ts","../src/tools.ts","../src/web-routes.ts","../src/index.ts"],"sourcesContent":["","/**\n * 执行器接口 + 结果类型。\n * 两种实现:本地 subprocess(LocalExecutor)和远程 cli-runner(RemoteExecutor)。\n */\n\n/** DRC 执行结果。 */\nexport interface DrcRunResult {\n exitCode: number\n stdout: string\n stderr: string\n /** drc.json 原始 JSON 字符串;null 表示未生成。 */\n drcJson: string | null\n elapsedMs: number\n timedOut: boolean\n /** 实际使用的 kicad-cli 可执行文件绝对路径。 */\n kicadCliPath: string\n /** 实际执行的完整命令行(含参数)。 */\n command: string\n /** drc.json 文件持久化保存的绝对路径(供用户直接打开/下载)。 */\n drcJsonPath: string | null\n}\n\n/** 执行器选项。 */\nexport interface DrcRunOptions {\n /** .kicad_pcb 文件绝对路径。 */\n pcbPath: string\n /** 启用所有严重级别。 */\n severityAll?: boolean\n /** DRC 前重绘填充区。 */\n refillZones?: boolean\n /** 报告所有走线错误。 */\n allTrackErrors?: boolean\n /** 超时秒数。 */\n timeoutSeconds?: number\n}\n\n/** 执行器抽象接口。 */\nexport interface DrcExecutor {\n /** 执行 kicad-cli pcb drc 并返回结果。 */\n run(options: DrcRunOptions): Promise<DrcRunResult>\n}\n\nexport class ExecutorError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ExecutorError'\n }\n}\n","/**\n * 本地 subprocess 执行器:在当前机器上直接调用 kicad-cli。\n */\nimport { spawn, execSync } from 'node:child_process'\nimport { existsSync, readFileSync, mkdtempSync, rmSync, readdirSync, statSync, copyFileSync } from 'node:fs'\nimport { tmpdir, platform, homedir } from 'node:os'\nimport { join, dirname, basename, resolve } from 'node:path'\nimport { ExecutorError, type DrcExecutor, type DrcRunOptions, type DrcRunResult } from './executor.ts'\n\nconst IS_WIN = platform() === 'win32'\n\n/** 探测 kicad-cli 路径(多级降级策略)。 */\nexport function resolveKicadCli(explicit?: string): string {\n // 1. 用户显式指定 → 直接用\n if (explicit) {\n if (existsSync(explicit)) return explicit\n throw new ExecutorError(`指定的 kicad-cli 不存在: ${explicit}`)\n }\n\n // 2. 环境变量 KICAD_CLI_PATH / KICAD_PATH\n const envPath = process.env.KICAD_CLI_PATH || process.env.KICAD_PATH\n if (envPath) {\n const candidate = envPath.endsWith('kicad-cli' + (IS_WIN ? '.exe' : ''))\n ? envPath\n : join(envPath, IS_WIN ? 'kicad-cli.exe' : 'kicad-cli')\n if (existsSync(candidate)) return candidate\n }\n\n // 3. 从 PATH 搜索(Windows 用 where,Unix 用 which)\n const inPath = IS_WIN ? searchPathWindows() : searchPathUnix()\n if (inPath) return inPath\n\n // 4. Windows 注册表查询 KiCad 安装路径\n if (IS_WIN) {\n const regPath = searchRegistryKicad()\n if (regPath) return regPath\n }\n\n // 5. 扫描常见安装目录的所有版本子目录\n const scanned = scanCommonInstallDirs()\n if (scanned) return scanned\n\n // 6. 兜底:用 PowerShell 在所有盘符下浅层搜索 kicad-cli.exe(最大深度 4 层)\n if (IS_WIN) {\n const psFound = searchKicadCliByPowerShell()\n if (psFound) return psFound\n }\n\n // 7. 兜底:抛出带详细排查步骤的错误\n throw new ExecutorError(\n '找不到 kicad-cli。请尝试以下任一方法:\\n' +\n ' 1) 在插件配置里设置 kicadCliPath 为 kicad-cli 的绝对路径\\n' +\n ' 2) 设置环境变量 KICAD_CLI_PATH 指向 kicad-cli.exe\\n' +\n ' 3) 安装 KiCad 后重启(安装程序会把 kicad-cli 加入 PATH)\\n' +\n ' 4) 确认 KiCad 安装目录下的 bin/ 子目录里有 kicad-cli.exe',\n )\n}\n\n/** Windows: 用 PowerShell 在所有盘符下浅层搜索 kicad-cli.exe。\n * 最大深度 4 层,跳过系统目录(Windows、$Recycle.Bin、System Volume Information 等)。\n * 对中文路径友好(PowerShell 原生支持 UTF-8)。 */\nfunction searchKicadCliByPowerShell(): string | null {\n const psScript = `\n [Console]::OutputEncoding = [System.Text.Encoding]::UTF8\n $ErrorActionPreference = 'SilentlyContinue'\n $exclude = @('Windows', '$Recycle.Bin', 'System Volume Information', 'WinSxS', 'Installer', 'Microsoft', 'ProgramData', 'Boot', 'Recovery', 'PerfLogs')\n $found = $null\n $drives = Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Used -gt 0 } | Select-Object -ExpandProperty Root\n foreach ($drive in $drives) {\n if ($found) { break }\n # 先直接看根目录下有没有 kicad-cli.exe\n $direct = Join-Path $drive 'kicad-cli.exe'\n if (Test-Path $direct) { $found = $direct; break }\n # 再看根目录\\bin\\kicad-cli.exe\n $directBin = Join-Path $drive 'bin\\kicad-cli.exe'\n if (Test-Path $directBin) { $found = $directBin; break }\n # 递归搜索最大深度 4\n Get-ChildItem -Path $drive -Directory -Depth 3 -ErrorAction SilentlyContinue | ForEach-Object {\n if ($found) { return }\n $name = $_.Name\n if ($exclude -contains $name) { return }\n $candidate = Join-Path $_.FullName 'kicad-cli.exe'\n if (Test-Path $candidate) { $found = $candidate; return }\n $candidateBin = Join-Path $_.FullName 'bin\\kicad-cli.exe'\n if (Test-Path $candidateBin) { $found = $candidateBin; return }\n }\n }\n if ($found) { Write-Output $found }\n `\n\n try {\n const out = execSync(`powershell -NoProfile -Command \"${psScript.replace(/\"/g, '\\\\\"')}\"`, {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'pipe'],\n timeout: 30000, // 30 秒超时\n }).trim()\n if (out && existsSync(out)) return out\n } catch { /* ignore */ }\n return null\n}\n\n/** Windows: 用 where 命令搜 PATH。 */\nfunction searchPathWindows(): string | null {\n try {\n const out = execSync('where kicad-cli', { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] })\n // where 可能输出多行(.exe / .cmd 等),取第一个\n const line = out.split(/\\r?\\n/)[0]?.trim()\n if (line && existsSync(line)) return line\n } catch { /* not found in PATH */ }\n return null\n}\n\n/** Unix: 用 which 命令搜 PATH。 */\nfunction searchPathUnix(): string | null {\n try {\n const out = execSync('which kicad-cli 2>/dev/null', { encoding: 'utf-8', shell: '/bin/bash' }).trim()\n if (out && existsSync(out)) return out\n } catch { /* not found */ }\n return null\n}\n\n/** Windows: 从注册表读 KiCad 安装目录。\n * 注意:reg query 输出是系统默认编码(中文 Windows 为 GBK),用 utf-8 解码会乱码。\n * 改用 PowerShell Get-ItemProperty 读取,输出编码强制 UTF-8。 */\nfunction searchRegistryKicad(): string | null {\n // 注册表键列表定义在下面的 PowerShell 脚本里($keys / $uninstPaths),这里不再重复维护\n const psScript = `\n [Console]::OutputEncoding = [System.Text.Encoding]::UTF8\n $ErrorActionPreference = 'SilentlyContinue'\n $keys = @(\n 'HKLM:\\\\SOFTWARE\\\\KiCad',\n 'HKLM:\\\\SOFTWARE\\\\WOW6432Node\\\\KiCad',\n 'HKCU:\\\\SOFTWARE\\\\KiCad'\n )\n foreach ($k in $keys) {\n $p = Get-ItemProperty -Path $k -ErrorAction SilentlyContinue\n if ($p) {\n if ($p.InstallDir) { Write-Output $p.InstallDir; exit }\n if ($p.'Install Directory') { Write-Output $p.'Install Directory'; exit }\n if ($p.'(default)') { Write-Output $p.'(default)'; exit }\n }\n }\n # 检查 Uninstall 项\n $uninstPaths = @(\n 'HKLM:\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall',\n 'HKLM:\\\\SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall',\n 'HKCU:\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall'\n )\n foreach ($base in $uninstPaths) {\n Get-ChildItem -Path $base -ErrorAction SilentlyContinue | ForEach-Object {\n $p = Get-ItemProperty -Path $_.PSPath -ErrorAction SilentlyContinue\n if ($p.DisplayName -match 'KiCad' -and $p.InstallLocation) {\n Write-Output $p.InstallLocation\n exit\n }\n }\n }\n `\n\n try {\n const out = execSync(`powershell -NoProfile -Command \"${psScript.replace(/\"/g, '\\\\\"')}\"`, {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'pipe'],\n }).trim()\n if (out) {\n // PowerShell 输出可能有多行,取第一行非空\n const dir = out.split(/\\r?\\n/).find(l => l.trim())?.trim()\n if (dir) {\n const candidate = join(dir, 'bin', 'kicad-cli.exe')\n if (existsSync(candidate)) return candidate\n // 有些 InstallLocation 直接指向 bin 目录\n if (existsSync(join(dir, 'kicad-cli.exe'))) return join(dir, 'kicad-cli.exe')\n }\n }\n } catch { /* ignore */ }\n return null\n}\n\n/**\n * 扫描常见安装目录下的所有版本子目录。\n * 策略:\n * 1. 枚举所有可用盘符(C: ~ Z:)\n * 2. 在每个盘符下尝试常见安装根目录名(Program Files, software, tools, apps, 等)\n * 3. 每个根目录下尝试直接 bin/kicad-cli.exe,或遍历子目录找版本号目录\n */\nfunction scanCommonInstallDirs(): string | null {\n const EXE = IS_WIN ? 'kicad-cli.exe' : 'kicad-cli'\n\n const tryPath = (p: string): string | null => {\n try {\n if (existsSync(p)) {\n const st = statSync(p)\n if (st.isFile()) return p\n // 如果是目录,检查其下 bin/EXE\n const bin = join(p, 'bin', EXE)\n if (existsSync(bin)) return bin\n }\n } catch { /* ignore */ }\n return null\n }\n\n if (IS_WIN) {\n // 枚举可用盘符\n const drives: string[] = []\n for (let i = 2; i <= 25; i++) { // C(2) ~ Z(25)\n const drive = String.fromCharCode(65 + i) + ':\\\\'\n try { if (existsSync(drive)) drives.push(drive) } catch { /* ignore */ }\n }\n\n // 每个盘符下的常见安装根目录(含中英文目录名)\n const rootNames = [\n 'Program Files\\\\KiCad',\n 'Program Files (x86)\\\\KiCad',\n 'Program Files\\\\KiCad EDA',\n 'software',\n 'software\\\\KiCad',\n 'tools',\n 'tools\\\\KiCad',\n 'apps',\n 'apps\\\\KiCad',\n 'dev',\n 'dev\\\\KiCad',\n 'KiCad',\n // 中文目录名\n '软件',\n '软件\\\\KiCad',\n '工具',\n '工具\\\\KiCad',\n '应用',\n '应用\\\\KiCad',\n '程序',\n '程序\\\\KiCad',\n '开发',\n '开发\\\\KiCad',\n ]\n\n for (const drive of drives) {\n for (const name of rootNames) {\n const root = join(drive, name)\n const found = tryPath(root)\n if (found) return found\n\n // 遍历根目录下的子目录(版本号目录,如 10.0 / 9.0 / kicad10 / kicad-10.0 等)\n if (existsSync(root)) {\n try {\n const entries = readdirSync(root, { withFileTypes: true })\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n // 只查一层子目录\n const sub = join(root, entry.name)\n const found2 = tryPath(sub)\n if (found2) return found2\n }\n } catch { /* ignore read errors */ }\n }\n }\n }\n\n // 用户目录下的 portable 安装\n const userRoots = [\n join(homedir(), 'KiCad'),\n join(homedir(), 'AppData', 'Local', 'Programs', 'KiCad'),\n join(homedir(), 'AppData', 'Local', 'KiCad'),\n join(homedir(), 'scoop', 'apps', 'kicad'),\n ]\n for (const root of userRoots) {\n const found = tryPath(root)\n if (found) return found\n if (existsSync(root)) {\n try {\n const entries = readdirSync(root, { withFileTypes: true })\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n const found2 = tryPath(join(root, entry.name))\n if (found2) return found2\n }\n } catch { /* ignore */ }\n }\n }\n } else {\n // Unix 常见路径\n const candidates = [\n '/usr/bin/kicad-cli',\n '/usr/local/bin/kicad-cli',\n '/opt/kicad/bin/kicad-cli',\n '/opt/KiCad/bin/kicad-cli',\n '/snap/bin/kicad-cli',\n '/Applications/KiCad.app/Contents/MacOS/kicad-cli',\n join(homedir(), '.local', 'bin', 'kicad-cli'),\n join(homedir(), 'Applications', 'KiCad.app', 'Contents', 'MacOS', 'kicad-cli'),\n ]\n for (const c of candidates) {\n if (existsSync(c)) return c\n }\n }\n return null\n}\n\nexport class LocalExecutor implements DrcExecutor {\n private readonly explicitPath?: string\n /** 解析成功的 kicad-cli 路径缓存(懒解析;失败不缓存,下次重新探测)。 */\n private resolvedPath: string | null = null\n\n constructor(kicadCliPath?: string) {\n this.explicitPath = kicadCliPath\n }\n\n /** 解析 kicad-cli 路径。首次 run() 时才调用,避免本机没装 KiCad 就拖垮插件启动。 */\n private resolveCli(): string {\n if (this.resolvedPath) return this.resolvedPath\n const p = resolveKicadCli(this.explicitPath)\n this.resolvedPath = p\n return p\n }\n\n async run(options: DrcRunOptions): Promise<DrcRunResult> {\n const start = performance.now()\n const pcbPath = resolve(options.pcbPath)\n if (!existsSync(pcbPath)) {\n throw new ExecutorError(`PCB 文件不存在: ${pcbPath}`)\n }\n\n // 懒解析:真正调用工具时才探测 kicad-cli\n const kicadCliPath = this.resolveCli()\n\n // 创建临时工作目录(kicad-cli 的 --output 用相对路径时会写到 cwd)\n const workDir = mkdtempSync(join(tmpdir(), 'kicad-drc-'))\n const outputPath = join(workDir, 'drc.json')\n\n // 组装命令参数\n const args = [\n 'pcb', 'drc',\n '--format', 'json',\n '--output', outputPath,\n ]\n if (options.severityAll !== false) args.push('--severity-all')\n if (options.refillZones !== false) args.push('--refill-zones')\n if (options.allTrackErrors) args.push('--all-track-errors')\n args.push(pcbPath)\n\n const timeoutMs = (options.timeoutSeconds ?? 600) * 1000\n\n try {\n const { exitCode, stdout, stderr, timedOut } = await execProcess(\n kicadCliPath, args, workDir, timeoutMs,\n )\n\n // 读取 drc.json\n let drcJson: string | null = null\n let drcJsonPath: string | null = null\n if (existsSync(outputPath)) {\n try {\n const raw = readFileSync(outputPath, 'utf-8')\n JSON.parse(raw) // 验证 JSON 合法\n drcJson = raw\n\n // 把 drc.json 持久化保存到 PCB 文件同目录(命名为 <pcb名>_drc.json)\n const pcbDir = dirname(pcbPath)\n const pcbStem = basename(pcbPath).replace(/\\.kicad_pcb$/i, '')\n const persistentPath = join(pcbDir, `${pcbStem}_drc.json`)\n copyFileSync(outputPath, persistentPath)\n drcJsonPath = persistentPath\n } catch { /* ignore parse/copy errors */ }\n }\n\n const elapsedMs = Math.round(performance.now() - start)\n\n // 构造实际执行的命令行字符串(用于日志/输出展示)\n const command = [kicadCliPath, ...args].map(quoteIfNeeded).join(' ')\n\n return { exitCode, stdout, stderr, drcJson, elapsedMs, timedOut, kicadCliPath: kicadCliPath, command, drcJsonPath }\n } finally {\n // 清理临时目录\n try { rmSync(workDir, { recursive: true, force: true }) } catch { /* ignore */ }\n }\n }\n}\n\n/** 参数含空格或特殊字符时加引号(仅用于命令行展示)。 */\nfunction quoteIfNeeded(arg: string): string {\n if (/[\\s\"]/.test(arg)) {\n return `\"${arg.replace(/\"/g, '\\\\\"')}\"`\n }\n return arg\n}\n\n/** 启动子进程并等待结束,超时强杀。 */\nfunction execProcess(\n cmd: string, args: string[], cwd: string, timeoutMs: number,\n): Promise<{ exitCode: number, stdout: string, stderr: string, timedOut: boolean }> {\n return new Promise((resolvePromise) => {\n const child = spawn(cmd, args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] })\n let stdout = ''\n let stderr = ''\n let timedOut = false\n let timer: ReturnType<typeof setTimeout> | undefined\n\n child.stdout?.on('data', (d: Buffer) => { stdout += d.toString('utf-8') })\n child.stderr?.on('data', (d: Buffer) => { stderr += d.toString('utf-8') })\n\n timer = setTimeout(() => {\n timedOut = true\n child.kill('SIGKILL')\n }, timeoutMs)\n\n child.on('close', (code) => {\n if (timer) clearTimeout(timer)\n resolvePromise({\n exitCode: code ?? -1,\n stdout,\n stderr,\n timedOut,\n })\n })\n\n child.on('error', (err) => {\n if (timer) clearTimeout(timer)\n resolvePromise({\n exitCode: -1,\n stdout,\n stderr: stderr + '\\n' + err.message,\n timedOut,\n })\n })\n })\n}\n","/**\n * 远程 pcblint-java 执行器:HTTP 上传 PCB 文件到 pcblint-java 的 CliProxyController,\n * 由后者转发到 cli-runner 执行 kicad-cli。\n *\n * 调用完成后,drc.json 会自动持久化到用户本地磁盘(与 .kicad_pcb 同目录),\n * 返回的 drcJsonPath 指向该本地持久化文件。\n *\n * 两种获取 drc.json 内容的方式:\n * 1. 配了 fileBaseUrl → 把 cli-runner 返回的绝对路径拼到 fileBaseUrl 上,HTTP 下载\n * (适合插件与 cli-runner 不在同一文件系统,但有静态文件服务器暴露产物目录)。\n * 2. 没配 fileBaseUrl → 直接从共享磁盘 readFileSync 读取(适合同机/同 PV 部署)。\n */\nimport { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs'\nimport { basename, dirname, resolve, join } from 'node:path'\nimport { ExecutorError, type DrcExecutor, type DrcRunOptions, type DrcRunResult } from './executor.ts'\n\n/** pcblint-java CliProxyController 的响应结构。 */\ninterface CliProxyResponse {\n exitCode: number\n stdout: string\n stderr: string\n truncated: boolean\n timedOut: boolean\n workDir: string\n /** key=输出文件相对路径,value=在共享磁盘上的绝对路径。 */\n outputFilePaths?: Record<string, string>\n elapsedMillis: number\n}\n\n/** 延迟(重试间隔用)。 */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms))\n}\n\n/** 是否中止类错误。超时/主动中止不重试,避免长任务总时长翻倍。 */\nfunction isAbortError(e: unknown): boolean {\n return e instanceof Error && (e.name === 'AbortError' || e.name === 'TimeoutError')\n}\n\n/** 带重试的 fetch:最多 2 次尝试;网络错误或 HTTP 5xx 时重试一次(间隔 1s),中止/超时立即抛出。 */\nasync function fetchWithRetry(url: string, init: RequestInit, label: string): Promise<Response> {\n for (let attempt = 1; attempt <= 2; attempt++) {\n try {\n const resp = await fetch(url, init)\n if (resp.status >= 500 && attempt < 2) {\n await resp.body?.cancel().catch(() => {})\n console.warn(`[dsh-kicad-drc] ${label} HTTP ${resp.status},1s 后重试`)\n await sleep(1000)\n continue\n }\n return resp\n } catch (e) {\n if (isAbortError(e) || attempt >= 2) throw e\n console.warn(`[dsh-kicad-drc] ${label} 网络错误: ${(e as Error).message},1s 后重试`)\n await sleep(1000)\n }\n }\n throw new Error('unreachable')\n}\n\nexport class RemoteExecutor implements DrcExecutor {\n private readonly baseUrl: string\n private readonly workDirRoot: string\n /** null = 共享磁盘读取模式(fileBaseUrl 显式传空字符串时)。 */\n private readonly fileBaseUrl: string | null\n\n constructor(opts: {\n baseUrl?: string\n workDirRoot?: string\n /** 静态文件服务器域名。undefined = 用内置默认值;'' = 禁用 HTTP 下载,走共享磁盘。 */\n fileBaseUrl?: string\n } = {}) {\n this.baseUrl = (opts.baseUrl ?? 'http://47.100.4.100:8080').replace(/\\/$/, '')\n this.workDirRoot = (opts.workDirRoot ?? '/data/kicad/drc').replace(/\\/$/, '')\n const fbu = opts.fileBaseUrl?.replace(/\\/$/, '') ?? 'http://www.fdatasheets.com'\n this.fileBaseUrl = fbu ? fbu : null\n }\n\n async run(options: DrcRunOptions): Promise<DrcRunResult> {\n const start = performance.now()\n const pcbPath = resolve(options.pcbPath)\n if (!existsSync(pcbPath)) {\n throw new ExecutorError(`PCB 文件不存在: ${pcbPath}`)\n }\n\n const filename = basename(pcbPath)\n const fileBytes = readFileSync(pcbPath)\n const jobId = crypto.randomUUID()\n const workDir = `${this.workDirRoot}/${jobId}`\n const outputName = 'drc.json'\n\n // 持久化目标:与 .kicad_pcb 同目录,命名为 <pcb_stem>_drc.json\n const pcbStem = filename.replace(/\\.kicad_pcb$/i, '')\n const pcbDir = dirname(pcbPath)\n const localPersistPath = join(pcbDir, `${pcbStem}_drc.json`)\n\n // 组装 command\n const cmdParts = ['kicad-cli', 'pcb', 'drc', '--format', 'json', '--output', outputName]\n if (options.severityAll !== false) cmdParts.push('--severity-all')\n if (options.refillZones !== false) cmdParts.push('--refill-zones')\n if (options.allTrackErrors) cmdParts.push('--all-track-errors')\n cmdParts.push(filename)\n const command = cmdParts.join(' ')\n\n const timeoutSeconds = options.timeoutSeconds ?? 600\n\n // multipart/form-data 调用 pcblint-java 的 /api/cli/run-with-file\n const url = `${this.baseUrl}/api/cli/run-with-file`\n const formData = new FormData()\n formData.append('file', new Blob([fileBytes]), filename)\n formData.append('workDir', workDir)\n formData.append('command', command)\n // outputFiles 传给 pcblint-java,用于拼 outputFilePaths(不会转发给 cli-runner)\n formData.append('outputFiles', outputName)\n formData.append('timeoutSeconds', String(timeoutSeconds))\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), (timeoutSeconds + 30) * 1000)\n\n let resp: Response\n try {\n resp = await fetchWithRetry(url, {\n method: 'POST',\n body: formData,\n signal: controller.signal,\n }, 'pcblint-java 调用')\n } catch (e) {\n clearTimeout(timeoutId)\n throw new ExecutorError(`pcblint-java HTTP 请求失败: ${(e as Error).message}`)\n }\n clearTimeout(timeoutId)\n\n if (!resp.ok) {\n const text = await resp.text().catch(() => '')\n throw new ExecutorError(`pcblint-java 返回 HTTP ${resp.status}: ${text.slice(0, 500)}`)\n }\n\n const data = (await resp.json()) as CliProxyResponse\n\n // 获取 drc.json 内容:优先 HTTP 下载(fileBaseUrl 配了),回退共享磁盘\n let drcJson: string | null = null\n if (data.outputFilePaths && outputName in data.outputFilePaths) {\n const remotePath = data.outputFilePaths[outputName]\n\n if (this.fileBaseUrl) {\n // 方式 1:HTTP 下载\n const fileUrl = `${this.fileBaseUrl}${remotePath}`\n drcJson = await this.downloadViaHttp(fileUrl)\n } else if (remotePath && existsSync(remotePath)) {\n // 方式 2:共享磁盘直接读\n try {\n const raw = readFileSync(remotePath, 'utf-8')\n JSON.parse(raw) // 验证 JSON 合法\n drcJson = raw\n } catch {\n drcJson = null\n }\n } else {\n console.warn(`[dsh-kicad-drc] outputFilePath 不可读: ${remotePath}`)\n }\n }\n\n // 持久化到用户本地磁盘(与 .kicad_pcb 同目录)\n let drcJsonPath: string | null = null\n if (drcJson) {\n try {\n mkdirSync(pcbDir, { recursive: true })\n writeFileSync(localPersistPath, drcJson, 'utf-8')\n drcJsonPath = localPersistPath\n console.log(`[dsh-kicad-drc] drc.json 已持久化: ${localPersistPath}`)\n } catch (e) {\n console.warn(`[dsh-kicad-drc] 写入本地文件失败: ${(e as Error).message}`)\n drcJsonPath = localPersistPath // 即使写入失败也返回目标路径,方便排查\n }\n }\n\n // kicad-cli 执行成功但报告取回失败时,在 stderr 里显式标注,避免被误读为\"DRC 未生成报告\"\n let stderr = data.stderr ?? ''\n if (drcJson === null && data.exitCode === 0) {\n stderr += '\\n[dsh-kicad-drc] 警告: kicad-cli 执行成功(exit 0),但插件未能取回 drc.json(HTTP 下载或共享磁盘读取失败)'\n }\n\n const elapsedMs = Math.round(performance.now() - start)\n\n return {\n exitCode: data.exitCode,\n stdout: data.stdout ?? '',\n stderr,\n drcJson,\n elapsedMs,\n timedOut: data.timedOut,\n kicadCliPath: 'remote:' + this.baseUrl,\n command,\n drcJsonPath,\n }\n }\n\n /** 通过 HTTP 下载产物文件(带重试),验证 JSON 合法后返回内容。 */\n private async downloadViaHttp(fileUrl: string): Promise<string | null> {\n try {\n const ctrl = new AbortController()\n const t = setTimeout(() => ctrl.abort(), 30_000)\n const resp = await fetchWithRetry(fileUrl, { signal: ctrl.signal }, '下载 drc.json')\n clearTimeout(t)\n if (!resp.ok) {\n console.warn(`[dsh-kicad-drc] 下载 drc.json 失败: HTTP ${resp.status} ${fileUrl}`)\n return null\n }\n const raw = await resp.text()\n JSON.parse(raw) // 验证 JSON 合法\n return raw\n } catch (e) {\n console.warn(`[dsh-kicad-drc] 下载 drc.json 异常: ${(e as Error).message} ${fileUrl}`)\n return null\n }\n }\n}\n","/**\n * 从 drc.json 提取统计摘要,避免把整个 JSON 塞给 AI 导致输出过长。\n */\n\n/** 最小 JSON 值类型(与 dsh-llm 的 JsonValue 结构一致),用于给样例明细定型。 */\nexport type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }\n\nexport type DrcSummary = {\n totalErrors: number\n totalWarnings: number\n totalExclusions: number\n totalIssues: number\n /** issue type → count */\n categories: Record<string, number>\n kicadVersion: string\n sourceFile: string\n date: string\n /** 前 5 条 error 级别明细(原始 drc.json 条目,天然是 JSON 值)。 */\n sampleErrors: JsonValue[]\n /** 已忽略的检查项描述列表 */\n ignoredChecks: string[]\n}\n\n/** drc.json 顶层中不是问题列表的字段(不参与统计)。 */\nconst NON_ISSUE_SECTIONS = new Set(['ignored_checks'])\n\n/**\n * 从 drc.json 字符串生成 DrcSummary。\n *\n * 泛化扫描所有顶层数组字段(除 NON_ISSUE_SECTIONS 外),\n * 这样未来 KiCad 版本新增的检查 section 也不会被漏计。\n */\nexport function parseDrcSummary(jsonStr: string): DrcSummary {\n const data = JSON.parse(jsonStr) as Record<string, unknown>\n\n let totalErrors = 0\n let totalWarnings = 0\n let totalExclusions = 0\n const categories: Record<string, number> = {}\n const sampleErrors: JsonValue[] = []\n\n for (const [section, value] of Object.entries(data)) {\n if (NON_ISSUE_SECTIONS.has(section)) continue\n if (!Array.isArray(value)) continue\n for (const item of value) {\n if (typeof item !== 'object' || item === null) continue\n const rec = item as Record<string, unknown>\n const sev = typeof rec.severity === 'string' ? rec.severity : 'unknown'\n if (sev === 'error') totalErrors++\n else if (sev === 'warning') totalWarnings++\n else if (sev === 'exclusion') totalExclusions++\n const cat = typeof rec.type === 'string' ? rec.type : section\n categories[cat] = (categories[cat] ?? 0) + 1\n if (sev === 'error' && sampleErrors.length < 5) {\n // item 来自 JSON.parse,结构上必为 JsonValue\n sampleErrors.push(item as JsonValue)\n }\n }\n }\n\n const ignoredChecks: string[] = []\n const ignoredRaw = data.ignored_checks\n if (Array.isArray(ignoredRaw)) {\n for (const ic of ignoredRaw) {\n const desc = (ic as Record<string, unknown>)?.description as string\n ?? (ic as Record<string, unknown>)?.key as string\n ?? '?'\n ignoredChecks.push(desc)\n }\n }\n\n return {\n totalErrors,\n totalWarnings,\n totalExclusions,\n totalIssues: totalErrors + totalWarnings + totalExclusions,\n categories,\n kicadVersion: (data.kicad_version as string) ?? 'unknown',\n sourceFile: (data.source as string) ?? '?',\n date: (data.date as string) ?? '?',\n sampleErrors,\n ignoredChecks,\n }\n}\n\n/** 生成纯文本摘要,适合给 AI 模型当 tool 输出。 */\nexport function formatSummaryText(s: DrcSummary): string {\n const lines: string[] = [\n 'KiCad DRC 报告摘要',\n ` KiCad 版本: ${s.kicadVersion}`,\n ` 源文件: ${s.sourceFile}`,\n ` 生成时间: ${s.date}`,\n '',\n ` ⚠️ 总计问题: ${s.totalIssues}`,\n ` - 错误 (error): ${s.totalErrors}`,\n ` - 警告 (warning): ${s.totalWarnings}`,\n ` - 排除 (exclusion): ${s.totalExclusions}`,\n '',\n ' 按类型分类:',\n ]\n const sorted = Object.entries(s.categories).sort((a, b) => b[1] - a[1])\n for (const [cat, count] of sorted) {\n lines.push(` - ${cat}: ${count}`)\n }\n if (s.sampleErrors.length > 0) {\n lines.push('')\n lines.push(` 示例错误 (前 ${s.sampleErrors.length} 条):`)\n s.sampleErrors.forEach((e, i) => {\n const item = e as Record<string, unknown>\n // KiCad 10 的 violation 条目顶层没有 pos,坐标在子项 items[].pos 里;旧格式则直接在顶层\n let pos = item.pos as Record<string, unknown> | undefined\n if (!pos && Array.isArray(item.items) && item.items.length > 0) {\n const first = item.items[0] as Record<string, unknown> | undefined\n pos = first?.pos as Record<string, unknown> | undefined\n }\n lines.push(\n ` ${i + 1}. [${item.severity ?? '?'}] ${item.description ?? '?'}`\n + ` pos=(${pos?.x ?? '?'}, ${pos?.y ?? '?'})`,\n )\n })\n }\n if (s.ignoredChecks.length > 0) {\n lines.push('')\n lines.push(` 已忽略的检查项 (${s.ignoredChecks.length}): ${s.ignoredChecks.join(', ')}`)\n }\n return lines.join('\\n')\n}\n","/**\n * DSH 工具定义:kicad_drc_run。\n *\n * AI agent 调用此工具,传入 .kicad_pcb 文件路径,\n * 工具在 Host 端执行 kicad-cli pcb drc(本地 subprocess 或远程 pcblint-java)\n * 并返回结构化结果。\n */\nimport { randomUUID } from 'node:crypto'\nimport { basename } from 'node:path'\nimport { defineTool } from '@deepseek-ai/dsh-tools'\nimport { parseDrcSummary, formatSummaryText, type DrcSummary } from './drc-summary.ts'\nimport type { DrcExecutor } from './executor.ts'\n\n/** 工具名(唯一来源;注册/卸载日志与 dispose 均使用它)。 */\nexport const TOOL_NAME = 'kicad_drc_run'\n\n/** 工具输出结构。未生成报告的字段直接省略(drcJsonPath/drcJson/drcSummary)。 */\ninterface KicadDrcOutput {\n success: boolean\n exitCode: number\n timedOut: boolean\n elapsedMs: number\n stdout: string\n stderr: string\n /** 本次执行使用的 kicad-cli(local 模式为可执行文件绝对路径,remote 模式为 remote:<baseUrl> 标识)。 */\n kicadCliPath: string\n /** 实际执行的完整命令行(含参数)。 */\n command: string\n /** drc.json 持久化保存的绝对路径(PCB 文件同目录,可直接打开)。 */\n drcJsonPath?: string\n /** drc.json 原始 JSON 字符串(可能很大;仅 includeFullJson=true 时返回)。 */\n drcJson?: string\n /** 解析后的紧凑摘要。 */\n drcSummary?: DrcSummary\n /** Web GUI 报告查看器地址(浏览器半边经此拉取报告 JSON 并分页渲染;无 webServer 时省略)。 */\n viewUrl?: string\n}\n\n/**\n * 创建 kicad_drc_run 工具。\n *\n * @param registerReport 可选的报告登记回调:DRC 成功生成报告后,把 drc.json\n * 路径登记到 webServer 路由的 key 上,供浏览器半边拉取渲染(无 webServer 时传 undefined)。\n */\nexport function createDrcTool(\n executor: DrcExecutor,\n defaultTimeoutSeconds = 600,\n registerReport?: (key: string, reportPath: string) => void,\n) {\n return defineTool({\n name: TOOL_NAME,\n description:\n '对 KiCad PCB 文件执行 DRC(设计规则检查)。\\n\\n'\n + '【工作原理】插件在 Host 端执行如下格式的命令:\\n\\n'\n + ' kicad-cli pcb drc <pcb文件> --output drc.json --format json --severity-all --refill-zones\\n\\n'\n + '有两种执行模式(由插件配置决定,调用方无需选择):\\n'\n + ' - local: 本地 subprocess 直接调 kicad-cli(需本机安装 KiCad;'\n + '可执行文件按 插件配置 → 环境变量 KICAD_CLI_PATH → PATH → Windows 注册表 → 常见安装目录 自动探测)\\n'\n + ' - remote: PCB 文件上传到 pcblint-java 服务,由其转发 cli-runner 执行(本机无需 KiCad)\\n\\n'\n + '传入 .kicad_pcb 文件的绝对路径(文件必须存在于本机文件系统),'\n + '工具返回 stdout、drcSummary(错误/警告数量、按类型分类、示例错误等)'\n + '和 drcJsonPath(完整报告持久化在 PCB 文件同目录,可直接读取该文件查看明细)。'\n + '如果只关心概况,优先读 drcSummary;需要完整明细时传 includeFullJson=true 或直接读 drcJsonPath 指向的文件。\\n\\n'\n + '【注意】默认输出不含 drc.json 原始 JSON(可能数百 KB),避免撑爆上下文。'\n + 'success 表示 DRC 运行是否正常完成,不代表板子没有违规——有无违规请判断 drcSummary.totalIssues。',\n\n parameters: {\n pcbPath: {\n type: 'string',\n required: true,\n description: 'KiCad PCB 文件的绝对路径(.kicad_pcb)。',\n },\n severityAll: {\n type: 'boolean',\n description: '启用所有严重级别(error/warning/exclusion)。默认 true。',\n },\n refillZones: {\n type: 'boolean',\n description: 'DRC 前重绘填充区。默认 true。',\n },\n allTrackErrors: {\n type: 'boolean',\n description: '报告所有走线错误(不仅是第一个)。默认 false。',\n },\n includeFullJson: {\n type: 'boolean',\n description: '在输出中包含 drc.json 原始 JSON 字符串(可能很大)。默认 false;需要完整明细时传 true,或直接读取 drcJsonPath 指向的文件。',\n },\n timeoutSeconds: {\n type: 'number',\n description: `超时秒数。默认 ${defaultTimeoutSeconds}(插件配置 defaultTimeoutSeconds)。`,\n },\n },\n\n output: {\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n success: { type: 'boolean', required: true },\n exitCode: { type: 'number', required: true },\n timedOut: { type: 'boolean', required: true },\n elapsedMs: { type: 'number', required: true },\n stdout: { type: 'string', required: true },\n stderr: { type: 'string', required: true },\n kicadCliPath: {\n type: 'string',\n required: true,\n description: '本次执行使用的 kicad-cli(local 模式为可执行文件绝对路径,remote 模式为 remote:<baseUrl> 标识)。',\n },\n command: {\n type: 'string',\n required: true,\n description: '实际执行的完整命令行。',\n },\n drcJsonPath: {\n type: 'string',\n description:\n 'drc.json 报告文件在本机持久化保存的绝对路径,与 PCB 文件同目录,'\n + '可直接读取查看完整明细。若 DRC 未生成报告,该字段不存在。',\n },\n drcSummary: {\n type: 'object',\n additionalProperties: true,\n description:\n 'DRC 摘要:错误/警告数量、按类型分类、示例错误等。若未生成报告,该字段不存在。',\n },\n drcJson: {\n type: 'string',\n description:\n 'drc.json 原始 JSON 字符串(可能很大,数百 KB)。仅当调用时传 includeFullJson=true 才返回;'\n + '通常只需读 drcSummary 或 drcJsonPath 指向的文件。若未生成报告,该字段不存在。',\n },\n viewUrl: {\n type: 'string',\n description:\n 'Web GUI 报告查看器地址(/drc-report/api/report?key=…)。'\n + '浏览器半边会自动拉取该地址并在对话尾部渲染分页报告卡片,调用方无需处理。'\n + '无 webServer 环境时该字段不存在。',\n },\n },\n },\n render: (args, value) => {\n const summary = value.drcSummary ? formatSummaryText(value.drcSummary as DrcSummary) : 'DRC 未生成报告'\n const pathLine = value.drcJsonPath\n ? `\\n\\n📄 完整 DRC 报告已保存到: ${value.drcJsonPath}`\n : ''\n const blocks: Array<{ type: 'text'; text: string }> = [{ type: 'text', text: summary + pathLine }]\n // 第二块:客户端预览载荷。浏览器半边(src/client)逐块 JSON.parse 工具结果,\n // 找到 {ok:true, viewUrl} 即在该轮尾部渲染分页报告卡片。\n if (value.viewUrl) {\n blocks.push({\n type: 'text',\n text: JSON.stringify({\n ok: true,\n viewUrl: value.viewUrl,\n key: /key=([\\w.-]+)$/.exec(value.viewUrl)?.[1] ?? '',\n name: typeof args.pcbPath === 'string' && args.pcbPath ? basename(args.pcbPath) : '',\n }),\n })\n }\n return blocks\n },\n },\n\n async execute(args, exec) {\n exec.signal.throwIfAborted()\n\n let result\n try {\n result = await executor.run({\n pcbPath: args.pcbPath,\n severityAll: args.severityAll,\n refillZones: args.refillZones,\n allTrackErrors: args.allTrackErrors,\n // 调用方未指定时回退到插件配置的默认超时\n timeoutSeconds: args.timeoutSeconds ?? defaultTimeoutSeconds,\n })\n } catch (e) {\n // 把执行器错误(PCB 不存在、kicad-cli 未找到、HTTP 失败等)转成结构化输出,\n // 让 AI 能统一处理,而不是收到裸异常。\n const message = e instanceof Error ? e.message : String(e)\n return {\n success: false,\n exitCode: -1,\n timedOut: false,\n elapsedMs: 0,\n stdout: '',\n stderr: message,\n kicadCliPath: 'unknown',\n command: '',\n }\n }\n\n // 解析摘要\n let drcSummary: DrcSummary | null = null\n if (result.drcJson) {\n try {\n drcSummary = parseDrcSummary(result.drcJson)\n } catch { /* ignore parse errors */ }\n }\n\n // 注意:drcJsonPath / drcJson / drcSummary 缺失时不放入返回对象,\n // 因为 DSH 的 schema 不支持 type: ['string', 'null'],而这些字段没有 required,\n // 省略即可(AI 侧用 in 检查或可选链访问)。\n const output: KicadDrcOutput = {\n success: result.exitCode === 0 && !result.timedOut,\n exitCode: result.exitCode,\n timedOut: result.timedOut,\n elapsedMs: result.elapsedMs,\n stdout: result.stdout,\n stderr: result.stderr,\n kicadCliPath: result.kicadCliPath,\n command: result.command,\n }\n if (result.drcJsonPath) output.drcJsonPath = result.drcJsonPath\n // 默认不回传原始 JSON,避免大报告撑爆 AI 上下文;仅在显式请求时包含\n if (result.drcJson && args.includeFullJson) output.drcJson = result.drcJson\n if (drcSummary) output.drcSummary = drcSummary\n\n // 登记报告到 webServer 路由,供浏览器半边拉取渲染(无 webServer 时跳过)\n if (result.drcJsonPath && registerReport) {\n const key = `drc-${randomUUID()}`\n registerReport(key, result.drcJsonPath)\n output.viewUrl = `/drc-report/api/report?key=${key}`\n }\n\n return output\n },\n\n presentCall: (args) => ({\n card: 'generic',\n title: `KiCad DRC: ${args.pcbPath ?? '?'}`,\n kind: 'other',\n rawInput: args.pcbPath,\n }),\n })\n}\n","/**\n * /drc-report Web 路由:把 DRC 报告 JSON 提供给浏览器半边(client 插件)。\n *\n * 模式与 @huaqiu/dsh-tool-pcb-viewer 一致:\n * - host 端在工具执行成功后,把 drc.json 文件路径登记到一个 key 上;\n * - 浏览器经 /drc-report/api/report?key=… 拉取报告全文(不走工具结果本体,\n * 避免大 JSON 进会话上下文);\n * - 仅信任 localhost 来源的请求(DSH web server 只监听本机)。\n */\nimport { createReadStream, existsSync } from 'node:fs'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n\n/** 注册表容量上限(超出淘汰最旧条目,防止长会话内存增长)。 */\nconst REGISTRY_CAP = 16\n\n/** DRC 报告登记表:key → drc.json 文件绝对路径。 */\nexport interface ReportRegistry {\n register(key: string, reportPath: string): void\n get(key: string): string | undefined\n}\n\nexport function createReportRegistry(): ReportRegistry {\n const map = new Map<string, string>()\n return {\n register(key, reportPath) {\n if (map.size >= REGISTRY_CAP) {\n const oldest = map.keys().next().value\n if (oldest !== undefined) map.delete(oldest)\n }\n map.set(key, reportPath)\n },\n get: (key) => map.get(key),\n }\n}\n\n/** 仅接受本机(localhost/127.0.0.1/[::1])来源的请求。 */\nexport function isTrustedRequest(req: IncomingMessage): boolean {\n const host = String(req.headers.host ?? '')\n if (!/^(localhost|127\\.0\\.0\\.1|\\[::1\\])(:\\d+)?$/.test(host)) return false\n const origin = req.headers.origin\n if (origin && !/^https?:\\/\\/(localhost|127\\.0\\.0\\.1|\\[::1\\])(:\\d+)?$/.test(String(origin))) return false\n return true\n}\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n const text = JSON.stringify(body)\n res.writeHead(status, {\n 'content-type': 'application/json; charset=utf-8',\n 'content-length': Buffer.byteLength(text),\n })\n res.end(text)\n}\n\n/**\n * 创建 /drc-report 前缀路由 handler。\n *\n * GET /drc-report/api/report?key=<key> → 流式返回登记的 drc.json(application/json)。\n * key 未知或文件已删除 → 404 JSON;非本机来源 → 403 JSON。\n */\nexport function createDrcReportHandler(registry: ReportRegistry) {\n return (req: IncomingMessage, res: ServerResponse): void => {\n if (!isTrustedRequest(req)) {\n sendJson(res, 403, { ok: false, error: { code: 'forbidden', message: 'forbidden' } })\n return\n }\n try {\n const url = new URL(req.url ?? '/', 'http://dsh.internal')\n if (url.pathname === '/drc-report/api/report') {\n const key = url.searchParams.get('key') ?? ''\n const reportPath = registry.get(key)\n if (!reportPath || !existsSync(reportPath)) {\n sendJson(res, 404, { ok: false, error: { code: 'not-found', message: 'unknown report key or file missing' } })\n return\n }\n res.writeHead(200, {\n 'content-type': 'application/json; charset=utf-8',\n 'cache-control': 'no-store',\n })\n createReadStream(reportPath).pipe(res)\n return\n }\n sendJson(res, 404, { ok: false, error: { code: 'not-found', message: 'unknown drc-report route' } })\n } catch (e) {\n sendJson(res, 500, { ok: false, error: { code: 'internal', message: e instanceof Error ? e.message : String(e) } })\n }\n }\n}\n","/**\n * DSH 插件入口:KiCad DRC 工具。\n *\n * 在 Host 端注册 kicad_drc_run 工具,AI agent 可调用它对 .kicad_pcb 文件\n * 执行 DRC 检查并获取结构化结果。\n *\n * 支持两种执行模式:\n * - local: 当前机器 subprocess 调 kicad-cli(需本地安装 KiCad)\n * - remote: HTTP 上传到 pcblint-java 服务(由其转发到 cli-runner 执行 kicad-cli,\n * 不做 base64 回读,返回 outputFilePaths 由本插件从共享磁盘读取)\n *\n * @module dsh-kicad-drc\n */\nimport type { Context } from '@deepseek-ai/cordis'\nimport Schema from '@deepseek-ai/schemastery'\nimport pkg from '../package.json'\nimport { LocalExecutor, resolveKicadCli } from './local-executor.ts'\nimport { RemoteExecutor } from './remote-executor.ts'\nimport { createDrcTool, TOOL_NAME } from './tools.ts'\nimport { createReportRegistry, createDrcReportHandler } from './web-routes.ts'\n\n/** 插件版本号(唯一来源:package.json),启动时打印用于确认生效版本。 */\nconst PLUGIN_VERSION = pkg.version\n\nexport { createDrcTool } from './tools.ts'\nexport { LocalExecutor, resolveKicadCli } from './local-executor.ts'\nexport { RemoteExecutor } from './remote-executor.ts'\nexport { parseDrcSummary, formatSummaryText } from './drc-summary.ts'\nexport type { DrcExecutor, DrcRunOptions, DrcRunResult } from './executor.ts'\nexport type { DrcSummary, JsonValue } from './drc-summary.ts'\n\n/** 插件配置。 */\nexport interface Config {\n /** 执行模式:local(本地 subprocess)或 remote(HTTP 调 pcblint-java)。 */\n executorMode: 'local' | 'remote'\n /** 本地模式:kicad-cli 路径,留空自动探测。 */\n kicadCliPath: string\n /** 远程模式:pcblint-java 服务地址(由其转发到 cli-runner 执行 kicad-cli)。 */\n pcblintBaseUrl: string\n /** 远程模式:cli-runner 工作目录根(共享磁盘上的路径,pcblint-java 和本插件均可访问)。 */\n cliRunnerWorkDirRoot: string\n /** 远程模式:静态文件服务器域名,用于把 cli-runner 返回的绝对路径(如 /data/kicad/drc/xxx/drc.json)\n * 拼接成完整下载 URL(如 http://www.fdatasheets.com/data/kicad/drc/xxx/drc.json)。\n * 配了之后插件通过 HTTP 下载 drc.json;显式设为空字符串则回退到共享磁盘 readFileSync。 */\n fileBaseUrl: string\n /** DRC 默认超时秒数。 */\n defaultTimeoutSeconds: number\n}\n\n/** 运行时配置校验和默认值。 */\nexport const Config: Schema<Config> = Schema.object({\n executorMode: Schema.union(['local', 'remote'] as const).default('remote'),\n kicadCliPath: Schema.string().default(''),\n pcblintBaseUrl: Schema.string().default('http://47.100.4.100:8080'),\n cliRunnerWorkDirRoot: Schema.string().default('/data/kicad/drc'),\n fileBaseUrl: Schema.string().default('http://www.fdatasheets.com'),\n defaultTimeoutSeconds: Schema.number().default(600),\n})\n\n/** 插件名,Cordis 会显示。 */\nexport const name = 'dsh-kicad-drc'\n\n/** 依赖的 DSH 服务(webServer 用于 /drc-report 报告查看路由;headless 环境缺失时自动降级)。 */\nexport const inject = ['tools', 'webServer']\n\n/**\n * 插件入口:根据配置构建执行器并注册工具。\n */\nexport function apply(ctx: Context, config: Config): void {\n console.log(`[dsh-kicad-drc] 插件版本: ${PLUGIN_VERSION}`)\n // 构建执行器\n let executor\n if (config.executorMode === 'remote') {\n // 注意:必须原样传递 config.fileBaseUrl(空字符串 = 显式禁用 HTTP 下载,走共享磁盘);\n // 若在这里转成 undefined,构造函数会兜底回硬编码默认值,导致\"共享磁盘模式\"永远无法启用。\n executor = new RemoteExecutor({\n baseUrl: config.pcblintBaseUrl,\n workDirRoot: config.cliRunnerWorkDirRoot,\n fileBaseUrl: config.fileBaseUrl,\n })\n console.log(\n `[dsh-kicad-drc] 使用远程 pcblint-java: ${config.pcblintBaseUrl}`\n + (config.fileBaseUrl ? `, fileBaseUrl: ${config.fileBaseUrl}` : '(fileBaseUrl 为空,共享磁盘读取模式)'),\n )\n } else {\n // 启动时先探测一次(供日志展示);成功则把结果传给执行器缓存,\n // 避免首次 run() 时重复走完整探测链(慢路径如全盘搜索可达 30s)。\n let cliPath: string | undefined\n try {\n cliPath = resolveKicadCli(config.kicadCliPath || undefined)\n console.log(`[dsh-kicad-drc] 使用本地 kicad-cli: ${cliPath}`)\n } catch {\n // 启动时探测失败不致命:kicad-cli 在首次调用工具时才真正需要\n console.warn('[dsh-kicad-drc] 本地模式:启动时未找到 kicad-cli,将在首次调用工具时重新探测')\n }\n executor = new LocalExecutor(cliPath ?? (config.kicadCliPath || undefined))\n }\n\n // /drc-report 路由:浏览器半边(src/client)经此拉取 drc.json 并渲染分页报告卡片。\n // webServer 服务缺失时(headless 环境)静默跳过,工具功能不受影响。\n let registerReport: ((key: string, reportPath: string) => void) | undefined\n const webServer = (ctx as Context & {\n webServer?: { register: (route: unknown) => void }\n }).webServer\n if (webServer && typeof webServer.register === 'function') {\n const registry = createReportRegistry()\n ctx.effect(() => {\n const reg = webServer.register({\n kind: 'prefix',\n path: '/drc-report',\n handler: createDrcReportHandler(registry),\n })\n return () => { if (typeof reg === 'function') (reg as () => void)() }\n }, 'dsh-kicad-drc: /drc-report routes')\n registerReport = (key, reportPath) => registry.register(key, reportPath)\n }\n\n // 注册工具 — inject=['tools'] 保证 tools 服务已就绪\n const tool = createDrcTool(executor, config.defaultTimeoutSeconds, registerReport)\n const registry = (ctx as Context & { tools: { register: (t: unknown) => void; dispose: (name: string) => void } }).tools\n if (registry) {\n registry.register(tool)\n ctx.effect(() => {\n console.log(`[dsh-kicad-drc] 已注册工具: ${TOOL_NAME}`)\n return () => {\n try { registry.dispose(TOOL_NAME) } catch { /* ignore */ }\n console.log(`[dsh-kicad-drc] 工具已卸载: ${TOOL_NAME}`)\n }\n }, 'dsh-kicad-drc.lifecycle')\n } else {\n console.warn('[dsh-kicad-drc] 未找到 tools 服务,工具未注册')\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["pkg.version"],"sources":["../package.json","../src/executor.ts","../src/local-executor.ts","../src/remote-executor.ts","../src/drc-summary.ts","../src/tools.ts","../src/web-routes.ts","../src/index.ts"],"sourcesContent":["","/**\n * 执行器接口 + 结果类型。\n * 两种实现:本地 subprocess(LocalExecutor)和远程 cli-runner(RemoteExecutor)。\n */\n\n/** DRC 执行结果。 */\nexport interface DrcRunResult {\n exitCode: number\n stdout: string\n stderr: string\n /** drc.json 原始 JSON 字符串;null 表示未生成。 */\n drcJson: string | null\n elapsedMs: number\n timedOut: boolean\n /** 实际使用的 kicad-cli 可执行文件绝对路径。 */\n kicadCliPath: string\n /** 实际执行的完整命令行(含参数)。 */\n command: string\n /** drc.json 文件持久化保存的绝对路径(供用户直接打开/下载)。 */\n drcJsonPath: string | null\n}\n\n/** 执行器选项。 */\nexport interface DrcRunOptions {\n /** .kicad_pcb 文件绝对路径。 */\n pcbPath: string\n /** 启用所有严重级别。 */\n severityAll?: boolean\n /** DRC 前重绘填充区。 */\n refillZones?: boolean\n /** 报告所有走线错误。 */\n allTrackErrors?: boolean\n /** 超时秒数。 */\n timeoutSeconds?: number\n}\n\n/** 执行器抽象接口。 */\nexport interface DrcExecutor {\n /** 执行 kicad-cli pcb drc 并返回结果。 */\n run(options: DrcRunOptions): Promise<DrcRunResult>\n}\n\nexport class ExecutorError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ExecutorError'\n }\n}\n","/**\n * 本地 subprocess 执行器:在当前机器上直接调用 kicad-cli。\n */\nimport { spawn, execSync } from 'node:child_process'\nimport { existsSync, readFileSync, mkdtempSync, rmSync, readdirSync, statSync, copyFileSync } from 'node:fs'\nimport { tmpdir, platform, homedir } from 'node:os'\nimport { join, dirname, basename, resolve } from 'node:path'\nimport { ExecutorError, type DrcExecutor, type DrcRunOptions, type DrcRunResult } from './executor.ts'\n\nconst IS_WIN = platform() === 'win32'\n\n/** 探测 kicad-cli 路径(多级降级策略)。 */\nexport function resolveKicadCli(explicit?: string): string {\n // 1. 用户显式指定 → 直接用\n if (explicit) {\n if (existsSync(explicit)) return explicit\n throw new ExecutorError(`指定的 kicad-cli 不存在: ${explicit}`)\n }\n\n // 2. 环境变量 KICAD_CLI_PATH / KICAD_PATH\n const envPath = process.env.KICAD_CLI_PATH || process.env.KICAD_PATH\n if (envPath) {\n const candidate = envPath.endsWith('kicad-cli' + (IS_WIN ? '.exe' : ''))\n ? envPath\n : join(envPath, IS_WIN ? 'kicad-cli.exe' : 'kicad-cli')\n if (existsSync(candidate)) return candidate\n }\n\n // 3. 从 PATH 搜索(Windows 用 where,Unix 用 which)\n const inPath = IS_WIN ? searchPathWindows() : searchPathUnix()\n if (inPath) return inPath\n\n // 4. Windows 注册表查询 KiCad 安装路径\n if (IS_WIN) {\n const regPath = searchRegistryKicad()\n if (regPath) return regPath\n }\n\n // 5. 扫描常见安装目录的所有版本子目录\n const scanned = scanCommonInstallDirs()\n if (scanned) return scanned\n\n // 6. 兜底:用 PowerShell 在所有盘符下浅层搜索 kicad-cli.exe(最大深度 4 层)\n if (IS_WIN) {\n const psFound = searchKicadCliByPowerShell()\n if (psFound) return psFound\n }\n\n // 7. 兜底:抛出带详细排查步骤的错误\n throw new ExecutorError(\n '找不到 kicad-cli。请尝试以下任一方法:\\n' +\n ' 1) 在插件配置里设置 kicadCliPath 为 kicad-cli 的绝对路径\\n' +\n ' 2) 设置环境变量 KICAD_CLI_PATH 指向 kicad-cli.exe\\n' +\n ' 3) 安装 KiCad 后重启(安装程序会把 kicad-cli 加入 PATH)\\n' +\n ' 4) 确认 KiCad 安装目录下的 bin/ 子目录里有 kicad-cli.exe',\n )\n}\n\n/** Windows: 用 PowerShell 在所有盘符下浅层搜索 kicad-cli.exe。\n * 最大深度 4 层,跳过系统目录(Windows、$Recycle.Bin、System Volume Information 等)。\n * 对中文路径友好(PowerShell 原生支持 UTF-8)。 */\nfunction searchKicadCliByPowerShell(): string | null {\n const psScript = `\n [Console]::OutputEncoding = [System.Text.Encoding]::UTF8\n $ErrorActionPreference = 'SilentlyContinue'\n $exclude = @('Windows', '$Recycle.Bin', 'System Volume Information', 'WinSxS', 'Installer', 'Microsoft', 'ProgramData', 'Boot', 'Recovery', 'PerfLogs')\n $found = $null\n $drives = Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Used -gt 0 } | Select-Object -ExpandProperty Root\n foreach ($drive in $drives) {\n if ($found) { break }\n # 先直接看根目录下有没有 kicad-cli.exe\n $direct = Join-Path $drive 'kicad-cli.exe'\n if (Test-Path $direct) { $found = $direct; break }\n # 再看根目录\\bin\\kicad-cli.exe\n $directBin = Join-Path $drive 'bin\\kicad-cli.exe'\n if (Test-Path $directBin) { $found = $directBin; break }\n # 递归搜索最大深度 4\n Get-ChildItem -Path $drive -Directory -Depth 3 -ErrorAction SilentlyContinue | ForEach-Object {\n if ($found) { return }\n $name = $_.Name\n if ($exclude -contains $name) { return }\n $candidate = Join-Path $_.FullName 'kicad-cli.exe'\n if (Test-Path $candidate) { $found = $candidate; return }\n $candidateBin = Join-Path $_.FullName 'bin\\kicad-cli.exe'\n if (Test-Path $candidateBin) { $found = $candidateBin; return }\n }\n }\n if ($found) { Write-Output $found }\n `\n\n try {\n const out = execSync(`powershell -NoProfile -Command \"${psScript.replace(/\"/g, '\\\\\"')}\"`, {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'pipe'],\n timeout: 30000, // 30 秒超时\n }).trim()\n if (out && existsSync(out)) return out\n } catch { /* ignore */ }\n return null\n}\n\n/** Windows: 用 where 命令搜 PATH。 */\nfunction searchPathWindows(): string | null {\n try {\n const out = execSync('where kicad-cli', { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] })\n // where 可能输出多行(.exe / .cmd 等),取第一个\n const line = out.split(/\\r?\\n/)[0]?.trim()\n if (line && existsSync(line)) return line\n } catch { /* not found in PATH */ }\n return null\n}\n\n/** Unix: 用 which 命令搜 PATH。 */\nfunction searchPathUnix(): string | null {\n try {\n const out = execSync('which kicad-cli 2>/dev/null', { encoding: 'utf-8', shell: '/bin/bash' }).trim()\n if (out && existsSync(out)) return out\n } catch { /* not found */ }\n return null\n}\n\n/** Windows: 从注册表读 KiCad 安装目录。\n * 注意:reg query 输出是系统默认编码(中文 Windows 为 GBK),用 utf-8 解码会乱码。\n * 改用 PowerShell Get-ItemProperty 读取,输出编码强制 UTF-8。 */\nfunction searchRegistryKicad(): string | null {\n // 注册表键列表定义在下面的 PowerShell 脚本里($keys / $uninstPaths),这里不再重复维护\n const psScript = `\n [Console]::OutputEncoding = [System.Text.Encoding]::UTF8\n $ErrorActionPreference = 'SilentlyContinue'\n $keys = @(\n 'HKLM:\\\\SOFTWARE\\\\KiCad',\n 'HKLM:\\\\SOFTWARE\\\\WOW6432Node\\\\KiCad',\n 'HKCU:\\\\SOFTWARE\\\\KiCad'\n )\n foreach ($k in $keys) {\n $p = Get-ItemProperty -Path $k -ErrorAction SilentlyContinue\n if ($p) {\n if ($p.InstallDir) { Write-Output $p.InstallDir; exit }\n if ($p.'Install Directory') { Write-Output $p.'Install Directory'; exit }\n if ($p.'(default)') { Write-Output $p.'(default)'; exit }\n }\n }\n # 检查 Uninstall 项\n $uninstPaths = @(\n 'HKLM:\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall',\n 'HKLM:\\\\SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall',\n 'HKCU:\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall'\n )\n foreach ($base in $uninstPaths) {\n Get-ChildItem -Path $base -ErrorAction SilentlyContinue | ForEach-Object {\n $p = Get-ItemProperty -Path $_.PSPath -ErrorAction SilentlyContinue\n if ($p.DisplayName -match 'KiCad' -and $p.InstallLocation) {\n Write-Output $p.InstallLocation\n exit\n }\n }\n }\n `\n\n try {\n const out = execSync(`powershell -NoProfile -Command \"${psScript.replace(/\"/g, '\\\\\"')}\"`, {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'pipe'],\n }).trim()\n if (out) {\n // PowerShell 输出可能有多行,取第一行非空\n const dir = out.split(/\\r?\\n/).find(l => l.trim())?.trim()\n if (dir) {\n const candidate = join(dir, 'bin', 'kicad-cli.exe')\n if (existsSync(candidate)) return candidate\n // 有些 InstallLocation 直接指向 bin 目录\n if (existsSync(join(dir, 'kicad-cli.exe'))) return join(dir, 'kicad-cli.exe')\n }\n }\n } catch { /* ignore */ }\n return null\n}\n\n/**\n * 扫描常见安装目录下的所有版本子目录。\n * 策略:\n * 1. 枚举所有可用盘符(C: ~ Z:)\n * 2. 在每个盘符下尝试常见安装根目录名(Program Files, software, tools, apps, 等)\n * 3. 每个根目录下尝试直接 bin/kicad-cli.exe,或遍历子目录找版本号目录\n */\nfunction scanCommonInstallDirs(): string | null {\n const EXE = IS_WIN ? 'kicad-cli.exe' : 'kicad-cli'\n\n const tryPath = (p: string): string | null => {\n try {\n if (existsSync(p)) {\n const st = statSync(p)\n if (st.isFile()) return p\n // 如果是目录,检查其下 bin/EXE\n const bin = join(p, 'bin', EXE)\n if (existsSync(bin)) return bin\n }\n } catch { /* ignore */ }\n return null\n }\n\n if (IS_WIN) {\n // 枚举可用盘符\n const drives: string[] = []\n for (let i = 2; i <= 25; i++) { // C(2) ~ Z(25)\n const drive = String.fromCharCode(65 + i) + ':\\\\'\n try { if (existsSync(drive)) drives.push(drive) } catch { /* ignore */ }\n }\n\n // 每个盘符下的常见安装根目录(含中英文目录名)\n const rootNames = [\n 'Program Files\\\\KiCad',\n 'Program Files (x86)\\\\KiCad',\n 'Program Files\\\\KiCad EDA',\n 'software',\n 'software\\\\KiCad',\n 'tools',\n 'tools\\\\KiCad',\n 'apps',\n 'apps\\\\KiCad',\n 'dev',\n 'dev\\\\KiCad',\n 'KiCad',\n // 中文目录名\n '软件',\n '软件\\\\KiCad',\n '工具',\n '工具\\\\KiCad',\n '应用',\n '应用\\\\KiCad',\n '程序',\n '程序\\\\KiCad',\n '开发',\n '开发\\\\KiCad',\n ]\n\n for (const drive of drives) {\n for (const name of rootNames) {\n const root = join(drive, name)\n const found = tryPath(root)\n if (found) return found\n\n // 遍历根目录下的子目录(版本号目录,如 10.0 / 9.0 / kicad10 / kicad-10.0 等)\n if (existsSync(root)) {\n try {\n const entries = readdirSync(root, { withFileTypes: true })\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n // 只查一层子目录\n const sub = join(root, entry.name)\n const found2 = tryPath(sub)\n if (found2) return found2\n }\n } catch { /* ignore read errors */ }\n }\n }\n }\n\n // 用户目录下的 portable 安装\n const userRoots = [\n join(homedir(), 'KiCad'),\n join(homedir(), 'AppData', 'Local', 'Programs', 'KiCad'),\n join(homedir(), 'AppData', 'Local', 'KiCad'),\n join(homedir(), 'scoop', 'apps', 'kicad'),\n ]\n for (const root of userRoots) {\n const found = tryPath(root)\n if (found) return found\n if (existsSync(root)) {\n try {\n const entries = readdirSync(root, { withFileTypes: true })\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n const found2 = tryPath(join(root, entry.name))\n if (found2) return found2\n }\n } catch { /* ignore */ }\n }\n }\n } else {\n // Unix 常见路径\n const candidates = [\n '/usr/bin/kicad-cli',\n '/usr/local/bin/kicad-cli',\n '/opt/kicad/bin/kicad-cli',\n '/opt/KiCad/bin/kicad-cli',\n '/snap/bin/kicad-cli',\n '/Applications/KiCad.app/Contents/MacOS/kicad-cli',\n join(homedir(), '.local', 'bin', 'kicad-cli'),\n join(homedir(), 'Applications', 'KiCad.app', 'Contents', 'MacOS', 'kicad-cli'),\n ]\n for (const c of candidates) {\n if (existsSync(c)) return c\n }\n }\n return null\n}\n\nexport class LocalExecutor implements DrcExecutor {\n private readonly explicitPath?: string\n /** 解析成功的 kicad-cli 路径缓存(懒解析;失败不缓存,下次重新探测)。 */\n private resolvedPath: string | null = null\n\n constructor(kicadCliPath?: string) {\n this.explicitPath = kicadCliPath\n }\n\n /** 解析 kicad-cli 路径。首次 run() 时才调用,避免本机没装 KiCad 就拖垮插件启动。 */\n private resolveCli(): string {\n if (this.resolvedPath) return this.resolvedPath\n const p = resolveKicadCli(this.explicitPath)\n this.resolvedPath = p\n return p\n }\n\n async run(options: DrcRunOptions): Promise<DrcRunResult> {\n const start = performance.now()\n const pcbPath = resolve(options.pcbPath)\n if (!existsSync(pcbPath)) {\n throw new ExecutorError(`PCB 文件不存在: ${pcbPath}`)\n }\n\n // 懒解析:真正调用工具时才探测 kicad-cli\n const kicadCliPath = this.resolveCli()\n\n // 创建临时工作目录(kicad-cli 的 --output 用相对路径时会写到 cwd)\n const workDir = mkdtempSync(join(tmpdir(), 'kicad-drc-'))\n const outputPath = join(workDir, 'drc.json')\n\n // 组装命令参数\n const args = [\n 'pcb', 'drc',\n '--format', 'json',\n '--output', outputPath,\n ]\n if (options.severityAll !== false) args.push('--severity-all')\n if (options.refillZones !== false) args.push('--refill-zones')\n if (options.allTrackErrors) args.push('--all-track-errors')\n args.push(pcbPath)\n\n const timeoutMs = (options.timeoutSeconds ?? 600) * 1000\n\n try {\n const { exitCode, stdout, stderr, timedOut } = await execProcess(\n kicadCliPath, args, workDir, timeoutMs,\n )\n\n // 读取 drc.json\n let drcJson: string | null = null\n let drcJsonPath: string | null = null\n if (existsSync(outputPath)) {\n try {\n const raw = readFileSync(outputPath, 'utf-8')\n JSON.parse(raw) // 验证 JSON 合法\n drcJson = raw\n\n // 把 drc.json 持久化保存到 PCB 文件同目录(命名为 <pcb名>_drc.json)\n const pcbDir = dirname(pcbPath)\n const pcbStem = basename(pcbPath).replace(/\\.kicad_pcb$/i, '')\n const persistentPath = join(pcbDir, `${pcbStem}_drc.json`)\n copyFileSync(outputPath, persistentPath)\n drcJsonPath = persistentPath\n } catch { /* ignore parse/copy errors */ }\n }\n\n const elapsedMs = Math.round(performance.now() - start)\n\n // 构造实际执行的命令行字符串(用于日志/输出展示)\n const command = [kicadCliPath, ...args].map(quoteIfNeeded).join(' ')\n\n return { exitCode, stdout, stderr, drcJson, elapsedMs, timedOut, kicadCliPath: kicadCliPath, command, drcJsonPath }\n } finally {\n // 清理临时目录\n try { rmSync(workDir, { recursive: true, force: true }) } catch { /* ignore */ }\n }\n }\n}\n\n/** 参数含空格或特殊字符时加引号(仅用于命令行展示)。 */\nfunction quoteIfNeeded(arg: string): string {\n if (/[\\s\"]/.test(arg)) {\n return `\"${arg.replace(/\"/g, '\\\\\"')}\"`\n }\n return arg\n}\n\n/** 启动子进程并等待结束,超时强杀。 */\nfunction execProcess(\n cmd: string, args: string[], cwd: string, timeoutMs: number,\n): Promise<{ exitCode: number, stdout: string, stderr: string, timedOut: boolean }> {\n return new Promise((resolvePromise) => {\n const child = spawn(cmd, args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] })\n let stdout = ''\n let stderr = ''\n let timedOut = false\n let timer: ReturnType<typeof setTimeout> | undefined\n\n child.stdout?.on('data', (d: Buffer) => { stdout += d.toString('utf-8') })\n child.stderr?.on('data', (d: Buffer) => { stderr += d.toString('utf-8') })\n\n timer = setTimeout(() => {\n timedOut = true\n child.kill('SIGKILL')\n }, timeoutMs)\n\n child.on('close', (code) => {\n if (timer) clearTimeout(timer)\n resolvePromise({\n exitCode: code ?? -1,\n stdout,\n stderr,\n timedOut,\n })\n })\n\n child.on('error', (err) => {\n if (timer) clearTimeout(timer)\n resolvePromise({\n exitCode: -1,\n stdout,\n stderr: stderr + '\\n' + err.message,\n timedOut,\n })\n })\n })\n}\n","/**\n * 远程 pcblint-java 执行器:HTTP 上传 PCB 文件到 pcblint-java 的 CliProxyController,\n * 由后者转发到 cli-runner 执行 kicad-cli。\n *\n * 调用完成后,drc.json 会自动持久化到用户本地磁盘(与 .kicad_pcb 同目录),\n * 返回的 drcJsonPath 指向该本地持久化文件。\n *\n * 两种获取 drc.json 内容的方式:\n * 1. 配了 fileBaseUrl → 把 cli-runner 返回的绝对路径拼到 fileBaseUrl 上,HTTP 下载\n * (适合插件与 cli-runner 不在同一文件系统,但有静态文件服务器暴露产物目录)。\n * 2. 没配 fileBaseUrl → 直接从共享磁盘 readFileSync 读取(适合同机/同 PV 部署)。\n */\nimport { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs'\nimport { basename, dirname, resolve, join } from 'node:path'\nimport { ExecutorError, type DrcExecutor, type DrcRunOptions, type DrcRunResult } from './executor.ts'\n\n/** pcblint-java CliProxyController 的响应结构。 */\ninterface CliProxyResponse {\n exitCode: number\n stdout: string\n stderr: string\n truncated: boolean\n timedOut: boolean\n workDir: string\n /** key=输出文件相对路径,value=在共享磁盘上的绝对路径。 */\n outputFilePaths?: Record<string, string>\n elapsedMillis: number\n}\n\n/** 延迟(重试间隔用)。 */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms))\n}\n\n/** 是否中止类错误。超时/主动中止不重试,避免长任务总时长翻倍。 */\nfunction isAbortError(e: unknown): boolean {\n return e instanceof Error && (e.name === 'AbortError' || e.name === 'TimeoutError')\n}\n\n/** 带重试的 fetch:最多 2 次尝试;网络错误或 HTTP 5xx 时重试一次(间隔 1s),中止/超时立即抛出。 */\nasync function fetchWithRetry(url: string, init: RequestInit, label: string): Promise<Response> {\n for (let attempt = 1; attempt <= 2; attempt++) {\n try {\n const resp = await fetch(url, init)\n if (resp.status >= 500 && attempt < 2) {\n await resp.body?.cancel().catch(() => {})\n console.warn(`[@huaqiu/dsh-kicad-drc] ${label} HTTP ${resp.status},1s 后重试`)\n await sleep(1000)\n continue\n }\n return resp\n } catch (e) {\n if (isAbortError(e) || attempt >= 2) throw e\n console.warn(`[@huaqiu/dsh-kicad-drc] ${label} 网络错误: ${(e as Error).message},1s 后重试`)\n await sleep(1000)\n }\n }\n throw new Error('unreachable')\n}\n\nexport class RemoteExecutor implements DrcExecutor {\n private readonly baseUrl: string\n private readonly workDirRoot: string\n /** null = 共享磁盘读取模式(fileBaseUrl 显式传空字符串时)。 */\n private readonly fileBaseUrl: string | null\n\n constructor(opts: {\n baseUrl?: string\n workDirRoot?: string\n /** 静态文件服务器域名。undefined = 用内置默认值;'' = 禁用 HTTP 下载,走共享磁盘。 */\n fileBaseUrl?: string\n } = {}) {\n this.baseUrl = (opts.baseUrl ?? 'http://47.100.4.100:8080').replace(/\\/$/, '')\n this.workDirRoot = (opts.workDirRoot ?? '/data/kicad/drc').replace(/\\/$/, '')\n const fbu = opts.fileBaseUrl?.replace(/\\/$/, '') ?? 'http://www.fdatasheets.com'\n this.fileBaseUrl = fbu ? fbu : null\n }\n\n async run(options: DrcRunOptions): Promise<DrcRunResult> {\n const start = performance.now()\n const pcbPath = resolve(options.pcbPath)\n if (!existsSync(pcbPath)) {\n throw new ExecutorError(`PCB 文件不存在: ${pcbPath}`)\n }\n\n const filename = basename(pcbPath)\n const fileBytes = readFileSync(pcbPath)\n const jobId = crypto.randomUUID()\n const workDir = `${this.workDirRoot}/${jobId}`\n const outputName = 'drc.json'\n\n // 持久化目标:与 .kicad_pcb 同目录,命名为 <pcb_stem>_drc.json\n const pcbStem = filename.replace(/\\.kicad_pcb$/i, '')\n const pcbDir = dirname(pcbPath)\n const localPersistPath = join(pcbDir, `${pcbStem}_drc.json`)\n\n // 组装 command\n const cmdParts = ['kicad-cli', 'pcb', 'drc', '--format', 'json', '--output', outputName]\n if (options.severityAll !== false) cmdParts.push('--severity-all')\n if (options.refillZones !== false) cmdParts.push('--refill-zones')\n if (options.allTrackErrors) cmdParts.push('--all-track-errors')\n cmdParts.push(filename)\n const command = cmdParts.join(' ')\n\n const timeoutSeconds = options.timeoutSeconds ?? 600\n\n // multipart/form-data 调用 pcblint-java 的 /api/cli/run-with-file\n const url = `${this.baseUrl}/api/cli/run-with-file`\n const formData = new FormData()\n formData.append('file', new Blob([fileBytes]), filename)\n formData.append('workDir', workDir)\n formData.append('command', command)\n // outputFiles 传给 pcblint-java,用于拼 outputFilePaths(不会转发给 cli-runner)\n formData.append('outputFiles', outputName)\n formData.append('timeoutSeconds', String(timeoutSeconds))\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), (timeoutSeconds + 30) * 1000)\n\n let resp: Response\n try {\n resp = await fetchWithRetry(url, {\n method: 'POST',\n body: formData,\n signal: controller.signal,\n }, 'pcblint-java 调用')\n } catch (e) {\n clearTimeout(timeoutId)\n throw new ExecutorError(`pcblint-java HTTP 请求失败: ${(e as Error).message}`)\n }\n clearTimeout(timeoutId)\n\n if (!resp.ok) {\n const text = await resp.text().catch(() => '')\n throw new ExecutorError(`pcblint-java 返回 HTTP ${resp.status}: ${text.slice(0, 500)}`)\n }\n\n const data = (await resp.json()) as CliProxyResponse\n\n // 获取 drc.json 内容:优先 HTTP 下载(fileBaseUrl 配了),回退共享磁盘\n let drcJson: string | null = null\n if (data.outputFilePaths && outputName in data.outputFilePaths) {\n const remotePath = data.outputFilePaths[outputName]\n\n if (this.fileBaseUrl) {\n // 方式 1:HTTP 下载\n const fileUrl = `${this.fileBaseUrl}${remotePath}`\n drcJson = await this.downloadViaHttp(fileUrl)\n } else if (remotePath && existsSync(remotePath)) {\n // 方式 2:共享磁盘直接读\n try {\n const raw = readFileSync(remotePath, 'utf-8')\n JSON.parse(raw) // 验证 JSON 合法\n drcJson = raw\n } catch {\n drcJson = null\n }\n } else {\n console.warn(`[@huaqiu/dsh-kicad-drc] outputFilePath 不可读: ${remotePath}`)\n }\n }\n\n // 持久化到用户本地磁盘(与 .kicad_pcb 同目录)\n let drcJsonPath: string | null = null\n if (drcJson) {\n try {\n mkdirSync(pcbDir, { recursive: true })\n writeFileSync(localPersistPath, drcJson, 'utf-8')\n drcJsonPath = localPersistPath\n console.log(`[@huaqiu/dsh-kicad-drc] drc.json 已持久化: ${localPersistPath}`)\n } catch (e) {\n console.warn(`[@huaqiu/dsh-kicad-drc] 写入本地文件失败: ${(e as Error).message}`)\n drcJsonPath = localPersistPath // 即使写入失败也返回目标路径,方便排查\n }\n }\n\n // kicad-cli 执行成功但报告取回失败时,在 stderr 里显式标注,避免被误读为\"DRC 未生成报告\"\n let stderr = data.stderr ?? ''\n if (drcJson === null && data.exitCode === 0) {\n stderr += '\\n[@huaqiu/dsh-kicad-drc] 警告: kicad-cli 执行成功(exit 0),但插件未能取回 drc.json(HTTP 下载或共享磁盘读取失败)'\n }\n\n const elapsedMs = Math.round(performance.now() - start)\n\n return {\n exitCode: data.exitCode,\n stdout: data.stdout ?? '',\n stderr,\n drcJson,\n elapsedMs,\n timedOut: data.timedOut,\n kicadCliPath: 'remote:' + this.baseUrl,\n command,\n drcJsonPath,\n }\n }\n\n /** 通过 HTTP 下载产物文件(带重试),验证 JSON 合法后返回内容。 */\n private async downloadViaHttp(fileUrl: string): Promise<string | null> {\n try {\n const ctrl = new AbortController()\n const t = setTimeout(() => ctrl.abort(), 30_000)\n const resp = await fetchWithRetry(fileUrl, { signal: ctrl.signal }, '下载 drc.json')\n clearTimeout(t)\n if (!resp.ok) {\n console.warn(`[@huaqiu/dsh-kicad-drc] 下载 drc.json 失败: HTTP ${resp.status} ${fileUrl}`)\n return null\n }\n const raw = await resp.text()\n JSON.parse(raw) // 验证 JSON 合法\n return raw\n } catch (e) {\n console.warn(`[@huaqiu/dsh-kicad-drc] 下载 drc.json 异常: ${(e as Error).message} ${fileUrl}`)\n return null\n }\n }\n}\n","/**\n * 从 drc.json 提取统计摘要,避免把整个 JSON 塞给 AI 导致输出过长。\n */\n\n/** 最小 JSON 值类型(与 dsh-llm 的 JsonValue 结构一致),用于给样例明细定型。 */\nexport type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }\n\nexport type DrcSummary = {\n totalErrors: number\n totalWarnings: number\n totalExclusions: number\n totalIssues: number\n /** issue type → count */\n categories: Record<string, number>\n kicadVersion: string\n sourceFile: string\n date: string\n /** 前 5 条 error 级别明细(原始 drc.json 条目,天然是 JSON 值)。 */\n sampleErrors: JsonValue[]\n /** 已忽略的检查项描述列表 */\n ignoredChecks: string[]\n}\n\n/** drc.json 顶层中不是问题列表的字段(不参与统计)。 */\nconst NON_ISSUE_SECTIONS = new Set(['ignored_checks'])\n\n/**\n * 从 drc.json 字符串生成 DrcSummary。\n *\n * 泛化扫描所有顶层数组字段(除 NON_ISSUE_SECTIONS 外),\n * 这样未来 KiCad 版本新增的检查 section 也不会被漏计。\n */\nexport function parseDrcSummary(jsonStr: string): DrcSummary {\n const data = JSON.parse(jsonStr) as Record<string, unknown>\n\n let totalErrors = 0\n let totalWarnings = 0\n let totalExclusions = 0\n const categories: Record<string, number> = {}\n const sampleErrors: JsonValue[] = []\n\n for (const [section, value] of Object.entries(data)) {\n if (NON_ISSUE_SECTIONS.has(section)) continue\n if (!Array.isArray(value)) continue\n for (const item of value) {\n if (typeof item !== 'object' || item === null) continue\n const rec = item as Record<string, unknown>\n const sev = typeof rec.severity === 'string' ? rec.severity : 'unknown'\n if (sev === 'error') totalErrors++\n else if (sev === 'warning') totalWarnings++\n else if (sev === 'exclusion') totalExclusions++\n const cat = typeof rec.type === 'string' ? rec.type : section\n categories[cat] = (categories[cat] ?? 0) + 1\n if (sev === 'error' && sampleErrors.length < 5) {\n // item 来自 JSON.parse,结构上必为 JsonValue\n sampleErrors.push(item as JsonValue)\n }\n }\n }\n\n const ignoredChecks: string[] = []\n const ignoredRaw = data.ignored_checks\n if (Array.isArray(ignoredRaw)) {\n for (const ic of ignoredRaw) {\n const desc = (ic as Record<string, unknown>)?.description as string\n ?? (ic as Record<string, unknown>)?.key as string\n ?? '?'\n ignoredChecks.push(desc)\n }\n }\n\n return {\n totalErrors,\n totalWarnings,\n totalExclusions,\n totalIssues: totalErrors + totalWarnings + totalExclusions,\n categories,\n kicadVersion: (data.kicad_version as string) ?? 'unknown',\n sourceFile: (data.source as string) ?? '?',\n date: (data.date as string) ?? '?',\n sampleErrors,\n ignoredChecks,\n }\n}\n\n/** 生成纯文本摘要,适合给 AI 模型当 tool 输出。 */\nexport function formatSummaryText(s: DrcSummary): string {\n const lines: string[] = [\n 'KiCad DRC 报告摘要',\n ` KiCad 版本: ${s.kicadVersion}`,\n ` 源文件: ${s.sourceFile}`,\n ` 生成时间: ${s.date}`,\n '',\n ` ⚠️ 总计问题: ${s.totalIssues}`,\n ` - 错误 (error): ${s.totalErrors}`,\n ` - 警告 (warning): ${s.totalWarnings}`,\n ` - 排除 (exclusion): ${s.totalExclusions}`,\n '',\n ' 按类型分类:',\n ]\n const sorted = Object.entries(s.categories).sort((a, b) => b[1] - a[1])\n for (const [cat, count] of sorted) {\n lines.push(` - ${cat}: ${count}`)\n }\n if (s.sampleErrors.length > 0) {\n lines.push('')\n lines.push(` 示例错误 (前 ${s.sampleErrors.length} 条):`)\n s.sampleErrors.forEach((e, i) => {\n const item = e as Record<string, unknown>\n // KiCad 10 的 violation 条目顶层没有 pos,坐标在子项 items[].pos 里;旧格式则直接在顶层\n let pos = item.pos as Record<string, unknown> | undefined\n if (!pos && Array.isArray(item.items) && item.items.length > 0) {\n const first = item.items[0] as Record<string, unknown> | undefined\n pos = first?.pos as Record<string, unknown> | undefined\n }\n lines.push(\n ` ${i + 1}. [${item.severity ?? '?'}] ${item.description ?? '?'}`\n + ` pos=(${pos?.x ?? '?'}, ${pos?.y ?? '?'})`,\n )\n })\n }\n if (s.ignoredChecks.length > 0) {\n lines.push('')\n lines.push(` 已忽略的检查项 (${s.ignoredChecks.length}): ${s.ignoredChecks.join(', ')}`)\n }\n return lines.join('\\n')\n}\n","/**\n * DSH 工具定义:kicad_drc_run。\n *\n * AI agent 调用此工具,传入 .kicad_pcb 文件路径,\n * 工具在 Host 端执行 kicad-cli pcb drc(本地 subprocess 或远程 pcblint-java)\n * 并返回结构化结果。\n */\nimport { randomUUID } from 'node:crypto'\nimport { basename } from 'node:path'\nimport { defineTool } from '@deepseek-ai/dsh-tools'\nimport { parseDrcSummary, formatSummaryText, type DrcSummary } from './drc-summary.ts'\nimport type { DrcExecutor } from './executor.ts'\n\n/** 工具名(唯一来源;注册/卸载日志与 dispose 均使用它)。 */\nexport const TOOL_NAME = 'kicad_drc_run'\n\n/** 工具输出结构。未生成报告的字段直接省略(drcJsonPath/drcJson/drcSummary)。 */\ninterface KicadDrcOutput {\n success: boolean\n exitCode: number\n timedOut: boolean\n elapsedMs: number\n stdout: string\n stderr: string\n /** 本次执行使用的 kicad-cli(local 模式为可执行文件绝对路径,remote 模式为 remote:<baseUrl> 标识)。 */\n kicadCliPath: string\n /** 实际执行的完整命令行(含参数)。 */\n command: string\n /** drc.json 持久化保存的绝对路径(PCB 文件同目录,可直接打开)。 */\n drcJsonPath?: string\n /** drc.json 原始 JSON 字符串(可能很大;仅 includeFullJson=true 时返回)。 */\n drcJson?: string\n /** 解析后的紧凑摘要。 */\n drcSummary?: DrcSummary\n /** Web GUI 报告查看器地址(浏览器半边经此拉取报告 JSON 并分页渲染;无 webServer 时省略)。 */\n viewUrl?: string\n}\n\n/**\n * 创建 kicad_drc_run 工具。\n *\n * @param registerReport 可选的报告登记回调:DRC 成功生成报告后,把 drc.json\n * 路径登记到 webServer 路由的 key 上,供浏览器半边拉取渲染(无 webServer 时传 undefined)。\n */\nexport function createDrcTool(\n executor: DrcExecutor,\n defaultTimeoutSeconds = 600,\n registerReport?: (key: string, reportPath: string) => void,\n) {\n return defineTool({\n name: TOOL_NAME,\n description:\n '对 KiCad PCB 文件执行 DRC(设计规则检查)。\\n\\n'\n + '【工作原理】插件在 Host 端执行如下格式的命令:\\n\\n'\n + ' kicad-cli pcb drc <pcb文件> --output drc.json --format json --severity-all --refill-zones\\n\\n'\n + '有两种执行模式(由插件配置决定,调用方无需选择):\\n'\n + ' - local: 本地 subprocess 直接调 kicad-cli(需本机安装 KiCad;'\n + '可执行文件按 插件配置 → 环境变量 KICAD_CLI_PATH → PATH → Windows 注册表 → 常见安装目录 自动探测)\\n'\n + ' - remote: PCB 文件上传到 pcblint-java 服务,由其转发 cli-runner 执行(本机无需 KiCad)\\n\\n'\n + '传入 .kicad_pcb 文件的绝对路径(文件必须存在于本机文件系统),'\n + '工具返回 stdout、drcSummary(错误/警告数量、按类型分类、示例错误等)'\n + '和 drcJsonPath(完整报告持久化在 PCB 文件同目录,可直接读取该文件查看明细)。'\n + '如果只关心概况,优先读 drcSummary;需要完整明细时传 includeFullJson=true 或直接读 drcJsonPath 指向的文件。\\n\\n'\n + '【注意】默认输出不含 drc.json 原始 JSON(可能数百 KB),避免撑爆上下文。'\n + 'success 表示 DRC 运行是否正常完成,不代表板子没有违规——有无违规请判断 drcSummary.totalIssues。',\n\n parameters: {\n pcbPath: {\n type: 'string',\n required: true,\n description: 'KiCad PCB 文件的绝对路径(.kicad_pcb)。',\n },\n severityAll: {\n type: 'boolean',\n description: '启用所有严重级别(error/warning/exclusion)。默认 true。',\n },\n refillZones: {\n type: 'boolean',\n description: 'DRC 前重绘填充区。默认 true。',\n },\n allTrackErrors: {\n type: 'boolean',\n description: '报告所有走线错误(不仅是第一个)。默认 false。',\n },\n includeFullJson: {\n type: 'boolean',\n description: '在输出中包含 drc.json 原始 JSON 字符串(可能很大)。默认 false;需要完整明细时传 true,或直接读取 drcJsonPath 指向的文件。',\n },\n timeoutSeconds: {\n type: 'number',\n description: `超时秒数。默认 ${defaultTimeoutSeconds}(插件配置 defaultTimeoutSeconds)。`,\n },\n },\n\n output: {\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n success: { type: 'boolean', required: true },\n exitCode: { type: 'number', required: true },\n timedOut: { type: 'boolean', required: true },\n elapsedMs: { type: 'number', required: true },\n stdout: { type: 'string', required: true },\n stderr: { type: 'string', required: true },\n kicadCliPath: {\n type: 'string',\n required: true,\n description: '本次执行使用的 kicad-cli(local 模式为可执行文件绝对路径,remote 模式为 remote:<baseUrl> 标识)。',\n },\n command: {\n type: 'string',\n required: true,\n description: '实际执行的完整命令行。',\n },\n drcJsonPath: {\n type: 'string',\n description:\n 'drc.json 报告文件在本机持久化保存的绝对路径,与 PCB 文件同目录,'\n + '可直接读取查看完整明细。若 DRC 未生成报告,该字段不存在。',\n },\n drcSummary: {\n type: 'object',\n additionalProperties: true,\n description:\n 'DRC 摘要:错误/警告数量、按类型分类、示例错误等。若未生成报告,该字段不存在。',\n },\n drcJson: {\n type: 'string',\n description:\n 'drc.json 原始 JSON 字符串(可能很大,数百 KB)。仅当调用时传 includeFullJson=true 才返回;'\n + '通常只需读 drcSummary 或 drcJsonPath 指向的文件。若未生成报告,该字段不存在。',\n },\n viewUrl: {\n type: 'string',\n description:\n 'Web GUI 报告查看器地址(/drc-report/api/report?key=…)。'\n + '浏览器半边会自动拉取该地址并在对话尾部渲染分页报告卡片,调用方无需处理。'\n + '无 webServer 环境时该字段不存在。',\n },\n },\n },\n render: (args, value) => {\n const summary = value.drcSummary ? formatSummaryText(value.drcSummary as DrcSummary) : 'DRC 未生成报告'\n const pathLine = value.drcJsonPath\n ? `\\n\\n📄 完整 DRC 报告已保存到: ${value.drcJsonPath}`\n : ''\n const blocks: Array<{ type: 'text'; text: string }> = [{ type: 'text', text: summary + pathLine }]\n // 第二块:客户端预览载荷。浏览器半边(src/client)逐块 JSON.parse 工具结果,\n // 找到 {ok:true, viewUrl} 即在该轮尾部渲染分页报告卡片。\n if (value.viewUrl) {\n blocks.push({\n type: 'text',\n text: JSON.stringify({\n ok: true,\n viewUrl: value.viewUrl,\n key: /key=([\\w.-]+)$/.exec(value.viewUrl)?.[1] ?? '',\n name: typeof args.pcbPath === 'string' && args.pcbPath ? basename(args.pcbPath) : '',\n }),\n })\n }\n return blocks\n },\n },\n\n async execute(args, exec) {\n exec.signal.throwIfAborted()\n\n let result\n try {\n result = await executor.run({\n pcbPath: args.pcbPath,\n severityAll: args.severityAll,\n refillZones: args.refillZones,\n allTrackErrors: args.allTrackErrors,\n // 调用方未指定时回退到插件配置的默认超时\n timeoutSeconds: args.timeoutSeconds ?? defaultTimeoutSeconds,\n })\n } catch (e) {\n // 把执行器错误(PCB 不存在、kicad-cli 未找到、HTTP 失败等)转成结构化输出,\n // 让 AI 能统一处理,而不是收到裸异常。\n const message = e instanceof Error ? e.message : String(e)\n return {\n success: false,\n exitCode: -1,\n timedOut: false,\n elapsedMs: 0,\n stdout: '',\n stderr: message,\n kicadCliPath: 'unknown',\n command: '',\n }\n }\n\n // 解析摘要\n let drcSummary: DrcSummary | null = null\n if (result.drcJson) {\n try {\n drcSummary = parseDrcSummary(result.drcJson)\n } catch { /* ignore parse errors */ }\n }\n\n // 注意:drcJsonPath / drcJson / drcSummary 缺失时不放入返回对象,\n // 因为 DSH 的 schema 不支持 type: ['string', 'null'],而这些字段没有 required,\n // 省略即可(AI 侧用 in 检查或可选链访问)。\n const output: KicadDrcOutput = {\n success: result.exitCode === 0 && !result.timedOut,\n exitCode: result.exitCode,\n timedOut: result.timedOut,\n elapsedMs: result.elapsedMs,\n stdout: result.stdout,\n stderr: result.stderr,\n kicadCliPath: result.kicadCliPath,\n command: result.command,\n }\n if (result.drcJsonPath) output.drcJsonPath = result.drcJsonPath\n // 默认不回传原始 JSON,避免大报告撑爆 AI 上下文;仅在显式请求时包含\n if (result.drcJson && args.includeFullJson) output.drcJson = result.drcJson\n if (drcSummary) output.drcSummary = drcSummary\n\n // 登记报告到 webServer 路由,供浏览器半边拉取渲染(无 webServer 时跳过)\n if (result.drcJsonPath && registerReport) {\n const key = `drc-${randomUUID()}`\n registerReport(key, result.drcJsonPath)\n output.viewUrl = `/drc-report/api/report?key=${key}`\n }\n\n return output\n },\n\n presentCall: (args) => ({\n card: 'generic',\n title: `KiCad DRC: ${args.pcbPath ?? '?'}`,\n kind: 'other',\n rawInput: args.pcbPath,\n }),\n })\n}\n","/**\n * /drc-report Web 路由:把 DRC 报告 JSON 提供给浏览器半边(client 插件)。\n *\n * 模式与 @huaqiu/dsh-tool-pcb-viewer 一致:\n * - host 端在工具执行成功后,把 drc.json 文件路径登记到一个 key 上;\n * - 浏览器经 /drc-report/api/report?key=… 拉取报告全文(不走工具结果本体,\n * 避免大 JSON 进会话上下文);\n * - 仅信任 localhost 来源的请求(DSH web server 只监听本机)。\n */\nimport { createReadStream, existsSync } from 'node:fs'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n\n/** 注册表容量上限(超出淘汰最旧条目,防止长会话内存增长)。 */\nconst REGISTRY_CAP = 16\n\n/** DRC 报告登记表:key → drc.json 文件绝对路径。 */\nexport interface ReportRegistry {\n register(key: string, reportPath: string): void\n get(key: string): string | undefined\n}\n\nexport function createReportRegistry(): ReportRegistry {\n const map = new Map<string, string>()\n return {\n register(key, reportPath) {\n if (map.size >= REGISTRY_CAP) {\n const oldest = map.keys().next().value\n if (oldest !== undefined) map.delete(oldest)\n }\n map.set(key, reportPath)\n },\n get: (key) => map.get(key),\n }\n}\n\n/** 仅接受本机(localhost/127.0.0.1/[::1])来源的请求。 */\nexport function isTrustedRequest(req: IncomingMessage): boolean {\n const host = String(req.headers.host ?? '')\n if (!/^(localhost|127\\.0\\.0\\.1|\\[::1\\])(:\\d+)?$/.test(host)) return false\n const origin = req.headers.origin\n if (origin && !/^https?:\\/\\/(localhost|127\\.0\\.0\\.1|\\[::1\\])(:\\d+)?$/.test(String(origin))) return false\n return true\n}\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n const text = JSON.stringify(body)\n res.writeHead(status, {\n 'content-type': 'application/json; charset=utf-8',\n 'content-length': Buffer.byteLength(text),\n })\n res.end(text)\n}\n\n/**\n * 创建 /drc-report 前缀路由 handler。\n *\n * GET /drc-report/api/report?key=<key> → 流式返回登记的 drc.json(application/json)。\n * key 未知或文件已删除 → 404 JSON;非本机来源 → 403 JSON。\n */\nexport function createDrcReportHandler(registry: ReportRegistry) {\n return (req: IncomingMessage, res: ServerResponse): void => {\n if (!isTrustedRequest(req)) {\n sendJson(res, 403, { ok: false, error: { code: 'forbidden', message: 'forbidden' } })\n return\n }\n try {\n const url = new URL(req.url ?? '/', 'http://dsh.internal')\n if (url.pathname === '/drc-report/api/report') {\n const key = url.searchParams.get('key') ?? ''\n const reportPath = registry.get(key)\n if (!reportPath || !existsSync(reportPath)) {\n sendJson(res, 404, { ok: false, error: { code: 'not-found', message: 'unknown report key or file missing' } })\n return\n }\n res.writeHead(200, {\n 'content-type': 'application/json; charset=utf-8',\n 'cache-control': 'no-store',\n })\n createReadStream(reportPath).pipe(res)\n return\n }\n sendJson(res, 404, { ok: false, error: { code: 'not-found', message: 'unknown drc-report route' } })\n } catch (e) {\n sendJson(res, 500, { ok: false, error: { code: 'internal', message: e instanceof Error ? e.message : String(e) } })\n }\n }\n}\n","/**\n * DSH 插件入口:KiCad DRC 工具。\n *\n * 在 Host 端注册 kicad_drc_run 工具,AI agent 可调用它对 .kicad_pcb 文件\n * 执行 DRC 检查并获取结构化结果。\n *\n * 支持两种执行模式:\n * - local: 当前机器 subprocess 调 kicad-cli(需本地安装 KiCad)\n * - remote: HTTP 上传到 pcblint-java 服务(由其转发到 cli-runner 执行 kicad-cli,\n * 不做 base64 回读,返回 outputFilePaths 由本插件从共享磁盘读取)\n *\n * @module @huaqiu/dsh-kicad-drc\n */\nimport type { Context } from '@deepseek-ai/cordis'\nimport Schema from '@deepseek-ai/schemastery'\nimport pkg from '../package.json'\nimport { LocalExecutor, resolveKicadCli } from './local-executor.ts'\nimport { RemoteExecutor } from './remote-executor.ts'\nimport { createDrcTool, TOOL_NAME } from './tools.ts'\nimport { createReportRegistry, createDrcReportHandler } from './web-routes.ts'\n\n/** 插件版本号(唯一来源:package.json),启动时打印用于确认生效版本。 */\nconst PLUGIN_VERSION = pkg.version\n\nexport { createDrcTool } from './tools.ts'\nexport { LocalExecutor, resolveKicadCli } from './local-executor.ts'\nexport { RemoteExecutor } from './remote-executor.ts'\nexport { parseDrcSummary, formatSummaryText } from './drc-summary.ts'\nexport type { DrcExecutor, DrcRunOptions, DrcRunResult } from './executor.ts'\nexport type { DrcSummary, JsonValue } from './drc-summary.ts'\n\n/** 插件配置。 */\nexport interface Config {\n /** 执行模式:local(本地 subprocess)或 remote(HTTP 调 pcblint-java)。 */\n executorMode: 'local' | 'remote'\n /** 本地模式:kicad-cli 路径,留空自动探测。 */\n kicadCliPath: string\n /** 远程模式:pcblint-java 服务地址(由其转发到 cli-runner 执行 kicad-cli)。 */\n pcblintBaseUrl: string\n /** 远程模式:cli-runner 工作目录根(共享磁盘上的路径,pcblint-java 和本插件均可访问)。 */\n cliRunnerWorkDirRoot: string\n /** 远程模式:静态文件服务器域名,用于把 cli-runner 返回的绝对路径(如 /data/kicad/drc/xxx/drc.json)\n * 拼接成完整下载 URL(如 http://www.fdatasheets.com/data/kicad/drc/xxx/drc.json)。\n * 配了之后插件通过 HTTP 下载 drc.json;显式设为空字符串则回退到共享磁盘 readFileSync。 */\n fileBaseUrl: string\n /** DRC 默认超时秒数。 */\n defaultTimeoutSeconds: number\n}\n\n/** 运行时配置校验和默认值。 */\nexport const Config: Schema<Config> = Schema.object({\n executorMode: Schema.union(['local', 'remote'] as const).default('remote'),\n kicadCliPath: Schema.string().default(''),\n pcblintBaseUrl: Schema.string().default('http://47.100.4.100:8080'),\n cliRunnerWorkDirRoot: Schema.string().default('/data/kicad/drc'),\n fileBaseUrl: Schema.string().default('http://www.fdatasheets.com'),\n defaultTimeoutSeconds: Schema.number().default(600),\n})\n\n/** 插件名,Cordis 会显示。 */\nexport const name = '@huaqiu/dsh-kicad-drc'\n\n/** 依赖的 DSH 服务(webServer 用于 /drc-report 报告查看路由;headless 环境缺失时自动降级)。 */\nexport const inject = ['tools', 'webServer']\n\n/**\n * 插件入口:根据配置构建执行器并注册工具。\n */\nexport function apply(ctx: Context, config: Config): void {\n console.log(`[@huaqiu/dsh-kicad-drc] 插件版本: ${PLUGIN_VERSION}`)\n // 构建执行器\n let executor\n if (config.executorMode === 'remote') {\n // 注意:必须原样传递 config.fileBaseUrl(空字符串 = 显式禁用 HTTP 下载,走共享磁盘);\n // 若在这里转成 undefined,构造函数会兜底回硬编码默认值,导致\"共享磁盘模式\"永远无法启用。\n executor = new RemoteExecutor({\n baseUrl: config.pcblintBaseUrl,\n workDirRoot: config.cliRunnerWorkDirRoot,\n fileBaseUrl: config.fileBaseUrl,\n })\n console.log(\n `[@huaqiu/dsh-kicad-drc] 使用远程 pcblint-java: ${config.pcblintBaseUrl}`\n + (config.fileBaseUrl ? `, fileBaseUrl: ${config.fileBaseUrl}` : '(fileBaseUrl 为空,共享磁盘读取模式)'),\n )\n } else {\n // 启动时先探测一次(供日志展示);成功则把结果传给执行器缓存,\n // 避免首次 run() 时重复走完整探测链(慢路径如全盘搜索可达 30s)。\n let cliPath: string | undefined\n try {\n cliPath = resolveKicadCli(config.kicadCliPath || undefined)\n console.log(`[@huaqiu/dsh-kicad-drc] 使用本地 kicad-cli: ${cliPath}`)\n } catch {\n // 启动时探测失败不致命:kicad-cli 在首次调用工具时才真正需要\n console.warn('[@huaqiu/dsh-kicad-drc] 本地模式:启动时未找到 kicad-cli,将在首次调用工具时重新探测')\n }\n executor = new LocalExecutor(cliPath ?? (config.kicadCliPath || undefined))\n }\n\n // /drc-report 路由:浏览器半边(src/client)经此拉取 drc.json 并渲染分页报告卡片。\n // webServer 服务缺失时(headless 环境)静默跳过,工具功能不受影响。\n let registerReport: ((key: string, reportPath: string) => void) | undefined\n const webServer = (ctx as Context & {\n webServer?: { register: (route: unknown) => void }\n }).webServer\n if (webServer && typeof webServer.register === 'function') {\n const registry = createReportRegistry()\n ctx.effect(() => {\n const reg = webServer.register({\n kind: 'prefix',\n path: '/drc-report',\n handler: createDrcReportHandler(registry),\n })\n return () => { if (typeof reg === 'function') (reg as () => void)() }\n }, '@huaqiu/dsh-kicad-drc: /drc-report routes')\n registerReport = (key, reportPath) => registry.register(key, reportPath)\n }\n\n // 注册工具 — inject=['tools'] 保证 tools 服务已就绪\n const tool = createDrcTool(executor, config.defaultTimeoutSeconds, registerReport)\n const registry = (ctx as Context & { tools: { register: (t: unknown) => void; dispose: (name: string) => void } }).tools\n if (registry) {\n registry.register(tool)\n ctx.effect(() => {\n console.log(`[@huaqiu/dsh-kicad-drc] 已注册工具: ${TOOL_NAME}`)\n return () => {\n try { registry.dispose(TOOL_NAME) } catch { /* ignore */ }\n console.log(`[@huaqiu/dsh-kicad-drc] 工具已卸载: ${TOOL_NAME}`)\n }\n }, '@huaqiu/dsh-kicad-drc.lifecycle')\n } else {\n console.warn('[@huaqiu/dsh-kicad-drc] 未找到 tools 服务,工具未注册')\n }\n}\n"],"mappings":";;;;;;;;;;;AC0CA,IAAa,gBAAb,cAAmC,MAAM;CACvC,YAAY,SAAiB;EAC3B,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;;;;;;ACtCA,MAAM,SAAS,SAAS,MAAM;;AAG9B,SAAgB,gBAAgB,UAA2B;CAEzD,IAAI,UAAU;EACZ,IAAI,WAAW,QAAQ,GAAG,OAAO;EACjC,MAAM,IAAI,cAAc,sBAAsB,UAAU;CAC1D;CAGA,MAAM,UAAU,QAAQ,IAAI,kBAAkB,QAAQ,IAAI;CAC1D,IAAI,SAAS;EACX,MAAM,YAAY,QAAQ,SAAS,eAAe,SAAS,SAAS,GAAG,IACnE,UACA,KAAK,SAAS,SAAS,kBAAkB,WAAW;EACxD,IAAI,WAAW,SAAS,GAAG,OAAO;CACpC;CAGA,MAAM,SAAS,SAAS,kBAAkB,IAAI,eAAe;CAC7D,IAAI,QAAQ,OAAO;CAGnB,IAAI,QAAQ;EACV,MAAM,UAAU,oBAAoB;EACpC,IAAI,SAAS,OAAO;CACtB;CAGA,MAAM,UAAU,sBAAsB;CACtC,IAAI,SAAS,OAAO;CAGpB,IAAI,QAAQ;EACV,MAAM,UAAU,2BAA2B;EAC3C,IAAI,SAAS,OAAO;CACtB;CAGA,MAAM,IAAI,cACR,iNAKF;AACF;;;;AAKA,SAAS,6BAA4C;CACnD,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BjB,IAAI;EACF,MAAM,MAAM,SAAS,mCAAmC,SAAS,QAAQ,MAAM,MAAK,EAAE,IAAI;GACxF,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;GAAM;GAChC,SAAS;EACX,CAAC,CAAC,CAAC,KAAK;EACR,IAAI,OAAO,WAAW,GAAG,GAAG,OAAO;CACrC,QAAQ,CAAe;CACvB,OAAO;AACT;;AAGA,SAAS,oBAAmC;CAC1C,IAAI;EAGF,MAAM,OAFM,SAAS,mBAAmB;GAAE,UAAU;GAAS,OAAO;IAAC;IAAU;IAAQ;GAAM;EAAE,CAEhF,CAAC,CAAC,MAAM,OAAO,CAAC,CAAC,EAAE,EAAE,KAAK;EACzC,IAAI,QAAQ,WAAW,IAAI,GAAG,OAAO;CACvC,QAAQ,CAA0B;CAClC,OAAO;AACT;;AAGA,SAAS,iBAAgC;CACvC,IAAI;EACF,MAAM,MAAM,SAAS,+BAA+B;GAAE,UAAU;GAAS,OAAO;EAAY,CAAC,CAAC,CAAC,KAAK;EACpG,IAAI,OAAO,WAAW,GAAG,GAAG,OAAO;CACrC,QAAQ,CAAkB;CAC1B,OAAO;AACT;;;;AAKA,SAAS,sBAAqC;CAE5C,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCjB,IAAI;EACF,MAAM,MAAM,SAAS,mCAAmC,SAAS,QAAQ,MAAM,MAAK,EAAE,IAAI;GACxF,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;GAAM;EAClC,CAAC,CAAC,CAAC,KAAK;EACR,IAAI,KAAK;GAEP,MAAM,MAAM,IAAI,MAAM,OAAO,CAAC,CAAC,MAAK,MAAK,EAAE,KAAK,CAAC,CAAC,EAAE,KAAK;GACzD,IAAI,KAAK;IACP,MAAM,YAAY,KAAK,KAAK,OAAO,eAAe;IAClD,IAAI,WAAW,SAAS,GAAG,OAAO;IAElC,IAAI,WAAW,KAAK,KAAK,eAAe,CAAC,GAAG,OAAO,KAAK,KAAK,eAAe;GAC9E;EACF;CACF,QAAQ,CAAe;CACvB,OAAO;AACT;;;;;;;;AASA,SAAS,wBAAuC;CAC9C,MAAM,MAAM,SAAS,kBAAkB;CAEvC,MAAM,WAAW,MAA6B;EAC5C,IAAI;GACF,IAAI,WAAW,CAAC,GAAG;IAEjB,IADW,SAAS,CACf,CAAC,CAAC,OAAO,GAAG,OAAO;IAExB,MAAM,MAAM,KAAK,GAAG,OAAO,GAAG;IAC9B,IAAI,WAAW,GAAG,GAAG,OAAO;GAC9B;EACF,QAAQ,CAAe;EACvB,OAAO;CACT;CAEA,IAAI,QAAQ;EAEV,MAAM,SAAmB,CAAC;EAC1B,KAAK,IAAI,IAAI,GAAG,KAAK,IAAI,KAAK;GAC5B,MAAM,QAAQ,OAAO,aAAa,KAAK,CAAC,IAAI;GAC5C,IAAI;IAAE,IAAI,WAAW,KAAK,GAAG,OAAO,KAAK,KAAK;GAAE,QAAQ,CAAe;EACzE;EAGA,MAAM,YAAY;GAChB;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GAEA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;EAEA,KAAK,MAAM,SAAS,QAClB,KAAK,MAAM,QAAQ,WAAW;GAC5B,MAAM,OAAO,KAAK,OAAO,IAAI;GAC7B,MAAM,QAAQ,QAAQ,IAAI;GAC1B,IAAI,OAAO,OAAO;GAGlB,IAAI,WAAW,IAAI,GACjB,IAAI;IACF,MAAM,UAAU,YAAY,MAAM,EAAE,eAAe,KAAK,CAAC;IACzD,KAAK,MAAM,SAAS,SAAS;KAC3B,IAAI,CAAC,MAAM,YAAY,GAAG;KAG1B,MAAM,SAAS,QADH,KAAK,MAAM,MAAM,IACJ,CAAC;KAC1B,IAAI,QAAQ,OAAO;IACrB;GACF,QAAQ,CAA2B;EAEvC;EAIF,MAAM,YAAY;GAChB,KAAK,QAAQ,GAAG,OAAO;GACvB,KAAK,QAAQ,GAAG,WAAW,SAAS,YAAY,OAAO;GACvD,KAAK,QAAQ,GAAG,WAAW,SAAS,OAAO;GAC3C,KAAK,QAAQ,GAAG,SAAS,QAAQ,OAAO;EAC1C;EACA,KAAK,MAAM,QAAQ,WAAW;GAC5B,MAAM,QAAQ,QAAQ,IAAI;GAC1B,IAAI,OAAO,OAAO;GAClB,IAAI,WAAW,IAAI,GACjB,IAAI;IACF,MAAM,UAAU,YAAY,MAAM,EAAE,eAAe,KAAK,CAAC;IACzD,KAAK,MAAM,SAAS,SAAS;KAC3B,IAAI,CAAC,MAAM,YAAY,GAAG;KAC1B,MAAM,SAAS,QAAQ,KAAK,MAAM,MAAM,IAAI,CAAC;KAC7C,IAAI,QAAQ,OAAO;IACrB;GACF,QAAQ,CAAe;EAE3B;CACF,OAAO;EAEL,MAAM,aAAa;GACjB;GACA;GACA;GACA;GACA;GACA;GACA,KAAK,QAAQ,GAAG,UAAU,OAAO,WAAW;GAC5C,KAAK,QAAQ,GAAG,gBAAgB,aAAa,YAAY,SAAS,WAAW;EAC/E;EACA,KAAK,MAAM,KAAK,YACd,IAAI,WAAW,CAAC,GAAG,OAAO;CAE9B;CACA,OAAO;AACT;AAEA,IAAa,gBAAb,MAAkD;CAChD;;CAEA,eAAsC;CAEtC,YAAY,cAAuB;EACjC,KAAK,eAAe;CACtB;;CAGA,aAA6B;EAC3B,IAAI,KAAK,cAAc,OAAO,KAAK;EACnC,MAAM,IAAI,gBAAgB,KAAK,YAAY;EAC3C,KAAK,eAAe;EACpB,OAAO;CACT;CAEA,MAAM,IAAI,SAA+C;EACvD,MAAM,QAAQ,YAAY,IAAI;EAC9B,MAAM,UAAU,QAAQ,QAAQ,OAAO;EACvC,IAAI,CAAC,WAAW,OAAO,GACrB,MAAM,IAAI,cAAc,cAAc,SAAS;EAIjD,MAAM,eAAe,KAAK,WAAW;EAGrC,MAAM,UAAU,YAAY,KAAK,OAAO,GAAG,YAAY,CAAC;EACxD,MAAM,aAAa,KAAK,SAAS,UAAU;EAG3C,MAAM,OAAO;GACX;GAAO;GACP;GAAY;GACZ;GAAY;EACd;EACA,IAAI,QAAQ,gBAAgB,OAAO,KAAK,KAAK,gBAAgB;EAC7D,IAAI,QAAQ,gBAAgB,OAAO,KAAK,KAAK,gBAAgB;EAC7D,IAAI,QAAQ,gBAAgB,KAAK,KAAK,oBAAoB;EAC1D,KAAK,KAAK,OAAO;EAEjB,MAAM,aAAa,QAAQ,kBAAkB,OAAO;EAEpD,IAAI;GACF,MAAM,EAAE,UAAU,QAAQ,QAAQ,aAAa,MAAM,YACnD,cAAc,MAAM,SAAS,SAC/B;GAGA,IAAI,UAAyB;GAC7B,IAAI,cAA6B;GACjC,IAAI,WAAW,UAAU,GACvB,IAAI;IACF,MAAM,MAAM,aAAa,YAAY,OAAO;IAC5C,KAAK,MAAM,GAAG;IACd,UAAU;IAGV,MAAM,SAAS,QAAQ,OAAO;IAC9B,MAAM,UAAU,SAAS,OAAO,CAAC,CAAC,QAAQ,iBAAiB,EAAE;IAC7D,MAAM,iBAAiB,KAAK,QAAQ,GAAG,QAAQ,UAAU;IACzD,aAAa,YAAY,cAAc;IACvC,cAAc;GAChB,QAAQ,CAAiC;GAG3C,MAAM,YAAY,KAAK,MAAM,YAAY,IAAI,IAAI,KAAK;GAGtD,MAAM,UAAU,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,KAAK,GAAG;GAEnE,OAAO;IAAE;IAAU;IAAQ;IAAQ;IAAS;IAAW;IAAwB;IAAc;IAAS;GAAY;EACpH,UAAU;GAER,IAAI;IAAE,OAAO,SAAS;KAAE,WAAW;KAAM,OAAO;IAAK,CAAC;GAAE,QAAQ,CAAe;EACjF;CACF;AACF;;AAGA,SAAS,cAAc,KAAqB;CAC1C,IAAI,QAAQ,KAAK,GAAG,GAClB,OAAO,IAAI,IAAI,QAAQ,MAAM,MAAK,EAAE;CAEtC,OAAO;AACT;;AAGA,SAAS,YACP,KAAa,MAAgB,KAAa,WACwC;CAClF,OAAO,IAAI,SAAS,mBAAmB;EACrC,MAAM,QAAQ,MAAM,KAAK,MAAM;GAAE;GAAK,OAAO;IAAC;IAAU;IAAQ;GAAM;EAAE,CAAC;EACzE,IAAI,SAAS;EACb,IAAI,SAAS;EACb,IAAI,WAAW;EACf,IAAI;EAEJ,MAAM,QAAQ,GAAG,SAAS,MAAc;GAAE,UAAU,EAAE,SAAS,OAAO;EAAE,CAAC;EACzE,MAAM,QAAQ,GAAG,SAAS,MAAc;GAAE,UAAU,EAAE,SAAS,OAAO;EAAE,CAAC;EAEzE,QAAQ,iBAAiB;GACvB,WAAW;GACX,MAAM,KAAK,SAAS;EACtB,GAAG,SAAS;EAEZ,MAAM,GAAG,UAAU,SAAS;GAC1B,IAAI,OAAO,aAAa,KAAK;GAC7B,eAAe;IACb,UAAU,QAAQ;IAClB;IACA;IACA;GACF,CAAC;EACH,CAAC;EAED,MAAM,GAAG,UAAU,QAAQ;GACzB,IAAI,OAAO,aAAa,KAAK;GAC7B,eAAe;IACb,UAAU;IACV;IACA,QAAQ,SAAS,OAAO,IAAI;IAC5B;GACF,CAAC;EACH,CAAC;CACH,CAAC;AACH;;;;;;;;;;;;;;;;AC3YA,SAAS,MAAM,IAA2B;CACxC,OAAO,IAAI,SAAS,MAAM,WAAW,GAAG,EAAE,CAAC;AAC7C;;AAGA,SAAS,aAAa,GAAqB;CACzC,OAAO,aAAa,UAAU,EAAE,SAAS,gBAAgB,EAAE,SAAS;AACtE;;AAGA,eAAe,eAAe,KAAa,MAAmB,OAAkC;CAC9F,KAAK,IAAI,UAAU,GAAG,WAAW,GAAG,WAClC,IAAI;EACF,MAAM,OAAO,MAAM,MAAM,KAAK,IAAI;EAClC,IAAI,KAAK,UAAU,OAAO,UAAU,GAAG;GACrC,MAAM,KAAK,MAAM,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC;GACxC,QAAQ,KAAK,2BAA2B,MAAM,QAAQ,KAAK,OAAO,QAAQ;GAC1E,MAAM,MAAM,GAAI;GAChB;EACF;EACA,OAAO;CACT,SAAS,GAAG;EACV,IAAI,aAAa,CAAC,KAAK,WAAW,GAAG,MAAM;EAC3C,QAAQ,KAAK,2BAA2B,MAAM,SAAU,EAAY,QAAQ,QAAQ;EACpF,MAAM,MAAM,GAAI;CAClB;CAEF,MAAM,IAAI,MAAM,aAAa;AAC/B;AAEA,IAAa,iBAAb,MAAmD;CACjD;CACA;;CAEA;CAEA,YAAY,OAKR,CAAC,GAAG;EACN,KAAK,WAAW,KAAK,WAAW,2BAAA,CAA4B,QAAQ,OAAO,EAAE;EAC7E,KAAK,eAAe,KAAK,eAAe,kBAAA,CAAmB,QAAQ,OAAO,EAAE;EAC5E,MAAM,MAAM,KAAK,aAAa,QAAQ,OAAO,EAAE,KAAK;EACpD,KAAK,cAAc,MAAM,MAAM;CACjC;CAEA,MAAM,IAAI,SAA+C;EACvD,MAAM,QAAQ,YAAY,IAAI;EAC9B,MAAM,UAAU,QAAQ,QAAQ,OAAO;EACvC,IAAI,CAAC,WAAW,OAAO,GACrB,MAAM,IAAI,cAAc,cAAc,SAAS;EAGjD,MAAM,WAAW,SAAS,OAAO;EACjC,MAAM,YAAY,aAAa,OAAO;EACtC,MAAM,QAAQ,OAAO,WAAW;EAChC,MAAM,UAAU,GAAG,KAAK,YAAY,GAAG;EACvC,MAAM,aAAa;EAGnB,MAAM,UAAU,SAAS,QAAQ,iBAAiB,EAAE;EACpD,MAAM,SAAS,QAAQ,OAAO;EAC9B,MAAM,mBAAmB,KAAK,QAAQ,GAAG,QAAQ,UAAU;EAG3D,MAAM,WAAW;GAAC;GAAa;GAAO;GAAO;GAAY;GAAQ;GAAY;EAAU;EACvF,IAAI,QAAQ,gBAAgB,OAAO,SAAS,KAAK,gBAAgB;EACjE,IAAI,QAAQ,gBAAgB,OAAO,SAAS,KAAK,gBAAgB;EACjE,IAAI,QAAQ,gBAAgB,SAAS,KAAK,oBAAoB;EAC9D,SAAS,KAAK,QAAQ;EACtB,MAAM,UAAU,SAAS,KAAK,GAAG;EAEjC,MAAM,iBAAiB,QAAQ,kBAAkB;EAGjD,MAAM,MAAM,GAAG,KAAK,QAAQ;EAC5B,MAAM,WAAW,IAAI,SAAS;EAC9B,SAAS,OAAO,QAAQ,IAAI,KAAK,CAAC,SAAS,CAAC,GAAG,QAAQ;EACvD,SAAS,OAAO,WAAW,OAAO;EAClC,SAAS,OAAO,WAAW,OAAO;EAElC,SAAS,OAAO,eAAe,UAAU;EACzC,SAAS,OAAO,kBAAkB,OAAO,cAAc,CAAC;EAExD,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,YAAY,iBAAiB,WAAW,MAAM,IAAI,iBAAiB,MAAM,GAAI;EAEnF,IAAI;EACJ,IAAI;GACF,OAAO,MAAM,eAAe,KAAK;IAC/B,QAAQ;IACR,MAAM;IACN,QAAQ,WAAW;GACrB,GAAG,iBAAiB;EACtB,SAAS,GAAG;GACV,aAAa,SAAS;GACtB,MAAM,IAAI,cAAc,2BAA4B,EAAY,SAAS;EAC3E;EACA,aAAa,SAAS;EAEtB,IAAI,CAAC,KAAK,IAAI;GACZ,MAAM,OAAO,MAAM,KAAK,KAAK,CAAC,CAAC,YAAY,EAAE;GAC7C,MAAM,IAAI,cAAc,wBAAwB,KAAK,OAAO,IAAI,KAAK,MAAM,GAAG,GAAG,GAAG;EACtF;EAEA,MAAM,OAAQ,MAAM,KAAK,KAAK;EAG9B,IAAI,UAAyB;EAC7B,IAAI,KAAK,mBAAmB,cAAc,KAAK,iBAAiB;GAC9D,MAAM,aAAa,KAAK,gBAAgB;GAExC,IAAI,KAAK,aAAa;IAEpB,MAAM,UAAU,GAAG,KAAK,cAAc;IACtC,UAAU,MAAM,KAAK,gBAAgB,OAAO;GAC9C,OAAO,IAAI,cAAc,WAAW,UAAU,GAE5C,IAAI;IACF,MAAM,MAAM,aAAa,YAAY,OAAO;IAC5C,KAAK,MAAM,GAAG;IACd,UAAU;GACZ,QAAQ;IACN,UAAU;GACZ;QAEA,QAAQ,KAAK,+CAA+C,YAAY;EAE5E;EAGA,IAAI,cAA6B;EACjC,IAAI,SACF,IAAI;GACF,UAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;GACrC,cAAc,kBAAkB,SAAS,OAAO;GAChD,cAAc;GACd,QAAQ,IAAI,0CAA0C,kBAAkB;EAC1E,SAAS,GAAG;GACV,QAAQ,KAAK,qCAAsC,EAAY,SAAS;GACxE,cAAc;EAChB;EAIF,IAAI,SAAS,KAAK,UAAU;EAC5B,IAAI,YAAY,QAAQ,KAAK,aAAa,GACxC,UAAU;EAGZ,MAAM,YAAY,KAAK,MAAM,YAAY,IAAI,IAAI,KAAK;EAEtD,OAAO;GACL,UAAU,KAAK;GACf,QAAQ,KAAK,UAAU;GACvB;GACA;GACA;GACA,UAAU,KAAK;GACf,cAAc,YAAY,KAAK;GAC/B;GACA;EACF;CACF;;CAGA,MAAc,gBAAgB,SAAyC;EACrE,IAAI;GACF,MAAM,OAAO,IAAI,gBAAgB;GACjC,MAAM,IAAI,iBAAiB,KAAK,MAAM,GAAG,GAAM;GAC/C,MAAM,OAAO,MAAM,eAAe,SAAS,EAAE,QAAQ,KAAK,OAAO,GAAG,aAAa;GACjF,aAAa,CAAC;GACd,IAAI,CAAC,KAAK,IAAI;IACZ,QAAQ,KAAK,gDAAgD,KAAK,OAAO,GAAG,SAAS;IACrF,OAAO;GACT;GACA,MAAM,MAAM,MAAM,KAAK,KAAK;GAC5B,KAAK,MAAM,GAAG;GACd,OAAO;EACT,SAAS,GAAG;GACV,QAAQ,KAAK,2CAA4C,EAAY,QAAQ,GAAG,SAAS;GACzF,OAAO;EACT;CACF;AACF;;;;AChMA,MAAM,qCAAqB,IAAI,IAAI,CAAC,gBAAgB,CAAC;;;;;;;AAQrD,SAAgB,gBAAgB,SAA6B;CAC3D,MAAM,OAAO,KAAK,MAAM,OAAO;CAE/B,IAAI,cAAc;CAClB,IAAI,gBAAgB;CACpB,IAAI,kBAAkB;CACtB,MAAM,aAAqC,CAAC;CAC5C,MAAM,eAA4B,CAAC;CAEnC,KAAK,MAAM,CAAC,SAAS,UAAU,OAAO,QAAQ,IAAI,GAAG;EACnD,IAAI,mBAAmB,IAAI,OAAO,GAAG;EACrC,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG;EAC3B,KAAK,MAAM,QAAQ,OAAO;GACxB,IAAI,OAAO,SAAS,YAAY,SAAS,MAAM;GAC/C,MAAM,MAAM;GACZ,MAAM,MAAM,OAAO,IAAI,aAAa,WAAW,IAAI,WAAW;GAC9D,IAAI,QAAQ,SAAS;QAChB,IAAI,QAAQ,WAAW;QACvB,IAAI,QAAQ,aAAa;GAC9B,MAAM,MAAM,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO;GACtD,WAAW,QAAQ,WAAW,QAAQ,KAAK;GAC3C,IAAI,QAAQ,WAAW,aAAa,SAAS,GAE3C,aAAa,KAAK,IAAiB;EAEvC;CACF;CAEA,MAAM,gBAA0B,CAAC;CACjC,MAAM,aAAa,KAAK;CACxB,IAAI,MAAM,QAAQ,UAAU,GAC1B,KAAK,MAAM,MAAM,YAAY;EAC3B,MAAM,OAAQ,IAAgC,eACxC,IAAgC,OACjC;EACL,cAAc,KAAK,IAAI;CACzB;CAGF,OAAO;EACL;EACA;EACA;EACA,aAAa,cAAc,gBAAgB;EAC3C;EACA,cAAe,KAAK,iBAA4B;EAChD,YAAa,KAAK,UAAqB;EACvC,MAAO,KAAK,QAAmB;EAC/B;EACA;CACF;AACF;;AAGA,SAAgB,kBAAkB,GAAuB;CACvD,MAAM,QAAkB;EACtB;EACA,iBAAiB,EAAE;EACnB,gBAAgB,EAAE;EAClB,eAAe,EAAE;EACjB;EACA,eAAe,EAAE;EACjB,2BAA2B,EAAE;EAC7B,2BAA2B,EAAE;EAC7B,2BAA2B,EAAE;EAC7B;EACA;CACF;CACA,MAAM,SAAS,OAAO,QAAQ,EAAE,UAAU,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE;CACtE,KAAK,MAAM,CAAC,KAAK,UAAU,QACzB,MAAM,KAAK,SAAS,IAAI,IAAI,OAAO;CAErC,IAAI,EAAE,aAAa,SAAS,GAAG;EAC7B,MAAM,KAAK,EAAE;EACb,MAAM,KAAK,aAAa,EAAE,aAAa,OAAO,KAAK;EACnD,EAAE,aAAa,SAAS,GAAG,MAAM;GAC/B,MAAM,OAAO;GAEb,IAAI,MAAM,KAAK;GACf,IAAI,CAAC,OAAO,MAAM,QAAQ,KAAK,KAAK,KAAK,KAAK,MAAM,SAAS,GAE3D,MADc,KAAK,MAAM,EACd,EAAE;GAEf,MAAM,KACJ,OAAO,IAAI,EAAE,KAAK,KAAK,YAAY,IAAI,IAAI,KAAK,eAAe,IAAA,SACnD,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,EAC9C;EACF,CAAC;CACH;CACA,IAAI,EAAE,cAAc,SAAS,GAAG;EAC9B,MAAM,KAAK,EAAE;EACb,MAAM,KAAK,cAAc,EAAE,cAAc,OAAO,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG;CACnF;CACA,OAAO,MAAM,KAAK,IAAI;AACxB;;;;;;;;;;;AChHA,MAAa,YAAY;;;;;;;AA8BzB,SAAgB,cACd,UACA,wBAAwB,KACxB,gBACA;CACA,OAAO,WAAW;EAChB,MAAM;EACN,aACE;EAcF,YAAY;GACV,SAAS;IACP,MAAM;IACN,UAAU;IACV,aAAa;GACf;GACA,aAAa;IACX,MAAM;IACN,aAAa;GACf;GACA,aAAa;IACX,MAAM;IACN,aAAa;GACf;GACA,gBAAgB;IACd,MAAM;IACN,aAAa;GACf;GACA,iBAAiB;IACf,MAAM;IACN,aAAa;GACf;GACA,gBAAgB;IACd,MAAM;IACN,aAAa,WAAW,sBAAsB;GAChD;EACF;EAEA,QAAQ;GACN,QAAQ;IACN,MAAM;IACN,sBAAsB;IACtB,YAAY;KACV,SAAS;MAAE,MAAM;MAAW,UAAU;KAAK;KAC3C,UAAU;MAAE,MAAM;MAAU,UAAU;KAAK;KAC3C,UAAU;MAAE,MAAM;MAAW,UAAU;KAAK;KAC5C,WAAW;MAAE,MAAM;MAAU,UAAU;KAAK;KAC5C,QAAQ;MAAE,MAAM;MAAU,UAAU;KAAK;KACzC,QAAQ;MAAE,MAAM;MAAU,UAAU;KAAK;KACzC,cAAc;MACZ,MAAM;MACN,UAAU;MACV,aAAa;KACf;KACA,SAAS;MACP,MAAM;MACN,UAAU;MACV,aAAa;KACf;KACA,aAAa;MACX,MAAM;MACN,aACE;KAEJ;KACA,YAAY;MACV,MAAM;MACN,sBAAsB;MACtB,aACE;KACJ;KACA,SAAS;MACP,MAAM;MACN,aACE;KAEJ;KACA,SAAS;MACP,MAAM;MACN,aACE;KAGJ;IACF;GACF;GACA,SAAS,MAAM,UAAU;IAKvB,MAAM,SAAgD,CAAC;KAAE,MAAM;KAAQ,OAJvD,MAAM,aAAa,kBAAkB,MAAM,UAAwB,IAAI,gBACtE,MAAM,cACnB,yBAAyB,MAAM,gBAC/B;IAC4F,CAAC;IAGjG,IAAI,MAAM,SACR,OAAO,KAAK;KACV,MAAM;KACN,MAAM,KAAK,UAAU;MACnB,IAAI;MACJ,SAAS,MAAM;MACf,KAAK,iBAAiB,KAAK,MAAM,OAAO,CAAC,GAAG,MAAM;MAClD,MAAM,OAAO,KAAK,YAAY,YAAY,KAAK,UAAU,SAAS,KAAK,OAAO,IAAI;KACpF,CAAC;IACH,CAAC;IAEH,OAAO;GACT;EACF;EAEA,MAAM,QAAQ,MAAM,MAAM;GACxB,KAAK,OAAO,eAAe;GAE3B,IAAI;GACJ,IAAI;IACF,SAAS,MAAM,SAAS,IAAI;KAC1B,SAAS,KAAK;KACd,aAAa,KAAK;KAClB,aAAa,KAAK;KAClB,gBAAgB,KAAK;KAErB,gBAAgB,KAAK,kBAAkB;IACzC,CAAC;GACH,SAAS,GAAG;IAIV,OAAO;KACL,SAAS;KACT,UAAU;KACV,UAAU;KACV,WAAW;KACX,QAAQ;KACR,QAPc,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;KAQvD,cAAc;KACd,SAAS;IACX;GACF;GAGA,IAAI,aAAgC;GACpC,IAAI,OAAO,SACT,IAAI;IACF,aAAa,gBAAgB,OAAO,OAAO;GAC7C,QAAQ,CAA4B;GAMtC,MAAM,SAAyB;IAC7B,SAAS,OAAO,aAAa,KAAK,CAAC,OAAO;IAC1C,UAAU,OAAO;IACjB,UAAU,OAAO;IACjB,WAAW,OAAO;IAClB,QAAQ,OAAO;IACf,QAAQ,OAAO;IACf,cAAc,OAAO;IACrB,SAAS,OAAO;GAClB;GACA,IAAI,OAAO,aAAa,OAAO,cAAc,OAAO;GAEpD,IAAI,OAAO,WAAW,KAAK,iBAAiB,OAAO,UAAU,OAAO;GACpE,IAAI,YAAY,OAAO,aAAa;GAGpC,IAAI,OAAO,eAAe,gBAAgB;IACxC,MAAM,MAAM,OAAO,WAAW;IAC9B,eAAe,KAAK,OAAO,WAAW;IACtC,OAAO,UAAU,8BAA8B;GACjD;GAEA,OAAO;EACT;EAEA,cAAc,UAAU;GACtB,MAAM;GACN,OAAO,cAAc,KAAK,WAAW;GACrC,MAAM;GACN,UAAU,KAAK;EACjB;CACF,CAAC;AACH;;;;;;;;;;;;;AChOA,MAAM,eAAe;AAQrB,SAAgB,uBAAuC;CACrD,MAAM,sBAAM,IAAI,IAAoB;CACpC,OAAO;EACL,SAAS,KAAK,YAAY;GACxB,IAAI,IAAI,QAAQ,cAAc;IAC5B,MAAM,SAAS,IAAI,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;IACjC,IAAI,WAAW,KAAA,GAAW,IAAI,OAAO,MAAM;GAC7C;GACA,IAAI,IAAI,KAAK,UAAU;EACzB;EACA,MAAM,QAAQ,IAAI,IAAI,GAAG;CAC3B;AACF;;AAGA,SAAgB,iBAAiB,KAA+B;CAC9D,MAAM,OAAO,OAAO,IAAI,QAAQ,QAAQ,EAAE;CAC1C,IAAI,CAAC,4CAA4C,KAAK,IAAI,GAAG,OAAO;CACpE,MAAM,SAAS,IAAI,QAAQ;CAC3B,IAAI,UAAU,CAAC,uDAAuD,KAAK,OAAO,MAAM,CAAC,GAAG,OAAO;CACnG,OAAO;AACT;AAEA,SAAS,SAAS,KAAqB,QAAgB,MAAqB;CAC1E,MAAM,OAAO,KAAK,UAAU,IAAI;CAChC,IAAI,UAAU,QAAQ;EACpB,gBAAgB;EAChB,kBAAkB,OAAO,WAAW,IAAI;CAC1C,CAAC;CACD,IAAI,IAAI,IAAI;AACd;;;;;;;AAQA,SAAgB,uBAAuB,UAA0B;CAC/D,QAAQ,KAAsB,QAA8B;EAC1D,IAAI,CAAC,iBAAiB,GAAG,GAAG;GAC1B,SAAS,KAAK,KAAK;IAAE,IAAI;IAAO,OAAO;KAAE,MAAM;KAAa,SAAS;IAAY;GAAE,CAAC;GACpF;EACF;EACA,IAAI;GACF,MAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,qBAAqB;GACzD,IAAI,IAAI,aAAa,0BAA0B;IAC7C,MAAM,MAAM,IAAI,aAAa,IAAI,KAAK,KAAK;IAC3C,MAAM,aAAa,SAAS,IAAI,GAAG;IACnC,IAAI,CAAC,cAAc,CAAC,WAAW,UAAU,GAAG;KAC1C,SAAS,KAAK,KAAK;MAAE,IAAI;MAAO,OAAO;OAAE,MAAM;OAAa,SAAS;MAAqC;KAAE,CAAC;KAC7G;IACF;IACA,IAAI,UAAU,KAAK;KACjB,gBAAgB;KAChB,iBAAiB;IACnB,CAAC;IACD,iBAAiB,UAAU,CAAC,CAAC,KAAK,GAAG;IACrC;GACF;GACA,SAAS,KAAK,KAAK;IAAE,IAAI;IAAO,OAAO;KAAE,MAAM;KAAa,SAAS;IAA2B;GAAE,CAAC;EACrG,SAAS,GAAG;GACV,SAAS,KAAK,KAAK;IAAE,IAAI;IAAO,OAAO;KAAE,MAAM;KAAY,SAAS,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;IAAE;GAAE,CAAC;EACpH;CACF;AACF;;;;AChEA,MAAM,iBAAiBA;;AA4BvB,MAAa,SAAyB,OAAO,OAAO;CAClD,cAAc,OAAO,MAAM,CAAC,SAAS,QAAQ,CAAU,CAAC,CAAC,QAAQ,QAAQ;CACzE,cAAc,OAAO,OAAO,CAAC,CAAC,QAAQ,EAAE;CACxC,gBAAgB,OAAO,OAAO,CAAC,CAAC,QAAQ,0BAA0B;CAClE,sBAAsB,OAAO,OAAO,CAAC,CAAC,QAAQ,iBAAiB;CAC/D,aAAa,OAAO,OAAO,CAAC,CAAC,QAAQ,4BAA4B;CACjE,uBAAuB,OAAO,OAAO,CAAC,CAAC,QAAQ,GAAG;AACpD,CAAC;;AAGD,MAAa,OAAO;;AAGpB,MAAa,SAAS,CAAC,SAAS,WAAW;;;;AAK3C,SAAgB,MAAM,KAAc,QAAsB;CACxD,QAAQ,IAAI,iCAAiC,gBAAgB;CAE7D,IAAI;CACJ,IAAI,OAAO,iBAAiB,UAAU;EAGpC,WAAW,IAAI,eAAe;GAC5B,SAAS,OAAO;GAChB,aAAa,OAAO;GACpB,aAAa,OAAO;EACtB,CAAC;EACD,QAAQ,IACN,8CAA8C,OAAO,oBAClD,OAAO,cAAc,kBAAkB,OAAO,gBAAgB,4BACnE;CACF,OAAO;EAGL,IAAI;EACJ,IAAI;GACF,UAAU,gBAAgB,OAAO,gBAAgB,KAAA,CAAS;GAC1D,QAAQ,IAAI,2CAA2C,SAAS;EAClE,QAAQ;GAEN,QAAQ,KAAK,6DAA6D;EAC5E;EACA,WAAW,IAAI,cAAc,YAAY,OAAO,gBAAgB,KAAA,EAAU;CAC5E;CAIA,IAAI;CACJ,MAAM,YAAa,IAEhB;CACH,IAAI,aAAa,OAAO,UAAU,aAAa,YAAY;EACzD,MAAM,WAAW,qBAAqB;EACtC,IAAI,aAAa;GACf,MAAM,MAAM,UAAU,SAAS;IAC7B,MAAM;IACN,MAAM;IACN,SAAS,uBAAuB,QAAQ;GAC1C,CAAC;GACD,aAAa;IAAE,IAAI,OAAO,QAAQ,YAAY,IAAoB;GAAE;EACtE,GAAG,2CAA2C;EAC9C,kBAAkB,KAAK,eAAe,SAAS,SAAS,KAAK,UAAU;CACzE;CAGA,MAAM,OAAO,cAAc,UAAU,OAAO,uBAAuB,cAAc;CACjF,MAAM,WAAY,IAAiG;CACnH,IAAI,UAAU;EACZ,SAAS,SAAS,IAAI;EACtB,IAAI,aAAa;GACf,QAAQ,IAAI,kCAAkC,WAAW;GACzD,aAAa;IACX,IAAI;KAAE,SAAS,QAAQ,SAAS;IAAE,QAAQ,CAAe;IACzD,QAAQ,IAAI,kCAAkC,WAAW;GAC3D;EACF,GAAG,iCAAiC;CACtC,OACE,QAAQ,KAAK,4CAA4C;AAE7D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huaqiu/dsh-kicad-drc",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "description": "KiCad DRC tool for DeepSeek Harness — run kicad-cli pcb drc, return structured results, and render the report as a paginated card in the Web GUI.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -1,5 +1,5 @@
1
1
  /**
2
- * dsh-kicad-drc 浏览器半边:在对话轮次尾部渲染 DRC 报告分页卡片。
2
+ * @huaqiu/dsh-kicad-drc 浏览器半边:在对话轮次尾部渲染 DRC 报告分页卡片。
3
3
  *
4
4
  * 机制(与 @huaqiu/dsh-tool-pcb-viewer 相同):
5
5
  * - host 端工具结果里带 {ok, viewUrl} JSON 块;
@@ -351,7 +351,7 @@ export function apply(ctx: ClientCtx): void {
351
351
  // Watcher:挂在会话头部动作区(渲染 null,只负责维护索引)
352
352
  ctx.slots.inject('conversation.session.header.actions', () => ctx.slots.register({
353
353
  name: 'conversation.session.header.actions',
354
- id: 'dsh-kicad-drc-watcher',
354
+ id: '@huaqiu/dsh-kicad-drc-watcher',
355
355
  order: 999,
356
356
  inject: () => ({}),
357
357
  }, PreviewWatcher))
package/src/index.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  * - remote: HTTP 上传到 pcblint-java 服务(由其转发到 cli-runner 执行 kicad-cli,
10
10
  * 不做 base64 回读,返回 outputFilePaths 由本插件从共享磁盘读取)
11
11
  *
12
- * @module dsh-kicad-drc
12
+ * @module @huaqiu/dsh-kicad-drc
13
13
  */
14
14
  import type { Context } from '@deepseek-ai/cordis'
15
15
  import Schema from '@deepseek-ai/schemastery'
@@ -58,7 +58,7 @@ export const Config: Schema<Config> = Schema.object({
58
58
  })
59
59
 
60
60
  /** 插件名,Cordis 会显示。 */
61
- export const name = 'dsh-kicad-drc'
61
+ export const name = '@huaqiu/dsh-kicad-drc'
62
62
 
63
63
  /** 依赖的 DSH 服务(webServer 用于 /drc-report 报告查看路由;headless 环境缺失时自动降级)。 */
64
64
  export const inject = ['tools', 'webServer']
@@ -67,7 +67,7 @@ export const inject = ['tools', 'webServer']
67
67
  * 插件入口:根据配置构建执行器并注册工具。
68
68
  */
69
69
  export function apply(ctx: Context, config: Config): void {
70
- console.log(`[dsh-kicad-drc] 插件版本: ${PLUGIN_VERSION}`)
70
+ console.log(`[@huaqiu/dsh-kicad-drc] 插件版本: ${PLUGIN_VERSION}`)
71
71
  // 构建执行器
72
72
  let executor
73
73
  if (config.executorMode === 'remote') {
@@ -79,7 +79,7 @@ export function apply(ctx: Context, config: Config): void {
79
79
  fileBaseUrl: config.fileBaseUrl,
80
80
  })
81
81
  console.log(
82
- `[dsh-kicad-drc] 使用远程 pcblint-java: ${config.pcblintBaseUrl}`
82
+ `[@huaqiu/dsh-kicad-drc] 使用远程 pcblint-java: ${config.pcblintBaseUrl}`
83
83
  + (config.fileBaseUrl ? `, fileBaseUrl: ${config.fileBaseUrl}` : '(fileBaseUrl 为空,共享磁盘读取模式)'),
84
84
  )
85
85
  } else {
@@ -88,10 +88,10 @@ export function apply(ctx: Context, config: Config): void {
88
88
  let cliPath: string | undefined
89
89
  try {
90
90
  cliPath = resolveKicadCli(config.kicadCliPath || undefined)
91
- console.log(`[dsh-kicad-drc] 使用本地 kicad-cli: ${cliPath}`)
91
+ console.log(`[@huaqiu/dsh-kicad-drc] 使用本地 kicad-cli: ${cliPath}`)
92
92
  } catch {
93
93
  // 启动时探测失败不致命:kicad-cli 在首次调用工具时才真正需要
94
- console.warn('[dsh-kicad-drc] 本地模式:启动时未找到 kicad-cli,将在首次调用工具时重新探测')
94
+ console.warn('[@huaqiu/dsh-kicad-drc] 本地模式:启动时未找到 kicad-cli,将在首次调用工具时重新探测')
95
95
  }
96
96
  executor = new LocalExecutor(cliPath ?? (config.kicadCliPath || undefined))
97
97
  }
@@ -111,7 +111,7 @@ export function apply(ctx: Context, config: Config): void {
111
111
  handler: createDrcReportHandler(registry),
112
112
  })
113
113
  return () => { if (typeof reg === 'function') (reg as () => void)() }
114
- }, 'dsh-kicad-drc: /drc-report routes')
114
+ }, '@huaqiu/dsh-kicad-drc: /drc-report routes')
115
115
  registerReport = (key, reportPath) => registry.register(key, reportPath)
116
116
  }
117
117
 
@@ -121,13 +121,13 @@ export function apply(ctx: Context, config: Config): void {
121
121
  if (registry) {
122
122
  registry.register(tool)
123
123
  ctx.effect(() => {
124
- console.log(`[dsh-kicad-drc] 已注册工具: ${TOOL_NAME}`)
124
+ console.log(`[@huaqiu/dsh-kicad-drc] 已注册工具: ${TOOL_NAME}`)
125
125
  return () => {
126
126
  try { registry.dispose(TOOL_NAME) } catch { /* ignore */ }
127
- console.log(`[dsh-kicad-drc] 工具已卸载: ${TOOL_NAME}`)
127
+ console.log(`[@huaqiu/dsh-kicad-drc] 工具已卸载: ${TOOL_NAME}`)
128
128
  }
129
- }, 'dsh-kicad-drc.lifecycle')
129
+ }, '@huaqiu/dsh-kicad-drc.lifecycle')
130
130
  } else {
131
- console.warn('[dsh-kicad-drc] 未找到 tools 服务,工具未注册')
131
+ console.warn('[@huaqiu/dsh-kicad-drc] 未找到 tools 服务,工具未注册')
132
132
  }
133
133
  }
@@ -44,14 +44,14 @@ async function fetchWithRetry(url: string, init: RequestInit, label: string): Pr
44
44
  const resp = await fetch(url, init)
45
45
  if (resp.status >= 500 && attempt < 2) {
46
46
  await resp.body?.cancel().catch(() => {})
47
- console.warn(`[dsh-kicad-drc] ${label} HTTP ${resp.status},1s 后重试`)
47
+ console.warn(`[@huaqiu/dsh-kicad-drc] ${label} HTTP ${resp.status},1s 后重试`)
48
48
  await sleep(1000)
49
49
  continue
50
50
  }
51
51
  return resp
52
52
  } catch (e) {
53
53
  if (isAbortError(e) || attempt >= 2) throw e
54
- console.warn(`[dsh-kicad-drc] ${label} 网络错误: ${(e as Error).message},1s 后重试`)
54
+ console.warn(`[@huaqiu/dsh-kicad-drc] ${label} 网络错误: ${(e as Error).message},1s 后重试`)
55
55
  await sleep(1000)
56
56
  }
57
57
  }
@@ -156,7 +156,7 @@ export class RemoteExecutor implements DrcExecutor {
156
156
  drcJson = null
157
157
  }
158
158
  } else {
159
- console.warn(`[dsh-kicad-drc] outputFilePath 不可读: ${remotePath}`)
159
+ console.warn(`[@huaqiu/dsh-kicad-drc] outputFilePath 不可读: ${remotePath}`)
160
160
  }
161
161
  }
162
162
 
@@ -167,9 +167,9 @@ export class RemoteExecutor implements DrcExecutor {
167
167
  mkdirSync(pcbDir, { recursive: true })
168
168
  writeFileSync(localPersistPath, drcJson, 'utf-8')
169
169
  drcJsonPath = localPersistPath
170
- console.log(`[dsh-kicad-drc] drc.json 已持久化: ${localPersistPath}`)
170
+ console.log(`[@huaqiu/dsh-kicad-drc] drc.json 已持久化: ${localPersistPath}`)
171
171
  } catch (e) {
172
- console.warn(`[dsh-kicad-drc] 写入本地文件失败: ${(e as Error).message}`)
172
+ console.warn(`[@huaqiu/dsh-kicad-drc] 写入本地文件失败: ${(e as Error).message}`)
173
173
  drcJsonPath = localPersistPath // 即使写入失败也返回目标路径,方便排查
174
174
  }
175
175
  }
@@ -177,7 +177,7 @@ export class RemoteExecutor implements DrcExecutor {
177
177
  // kicad-cli 执行成功但报告取回失败时,在 stderr 里显式标注,避免被误读为"DRC 未生成报告"
178
178
  let stderr = data.stderr ?? ''
179
179
  if (drcJson === null && data.exitCode === 0) {
180
- stderr += '\n[dsh-kicad-drc] 警告: kicad-cli 执行成功(exit 0),但插件未能取回 drc.json(HTTP 下载或共享磁盘读取失败)'
180
+ stderr += '\n[@huaqiu/dsh-kicad-drc] 警告: kicad-cli 执行成功(exit 0),但插件未能取回 drc.json(HTTP 下载或共享磁盘读取失败)'
181
181
  }
182
182
 
183
183
  const elapsedMs = Math.round(performance.now() - start)
@@ -203,14 +203,14 @@ export class RemoteExecutor implements DrcExecutor {
203
203
  const resp = await fetchWithRetry(fileUrl, { signal: ctrl.signal }, '下载 drc.json')
204
204
  clearTimeout(t)
205
205
  if (!resp.ok) {
206
- console.warn(`[dsh-kicad-drc] 下载 drc.json 失败: HTTP ${resp.status} ${fileUrl}`)
206
+ console.warn(`[@huaqiu/dsh-kicad-drc] 下载 drc.json 失败: HTTP ${resp.status} ${fileUrl}`)
207
207
  return null
208
208
  }
209
209
  const raw = await resp.text()
210
210
  JSON.parse(raw) // 验证 JSON 合法
211
211
  return raw
212
212
  } catch (e) {
213
- console.warn(`[dsh-kicad-drc] 下载 drc.json 异常: ${(e as Error).message} ${fileUrl}`)
213
+ console.warn(`[@huaqiu/dsh-kicad-drc] 下载 drc.json 异常: ${(e as Error).message} ${fileUrl}`)
214
214
  return null
215
215
  }
216
216
  }