@huaqiu/dsh-kicad 0.4.10 → 0.4.13

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/lib/index.mjs CHANGED
@@ -35,6 +35,116 @@ function hasHostConfig(input = {}) {
35
35
  return Boolean(input.pythonPath ?? process.env["DSH_KICAD_PYTHON"] ?? input.skillsDir ?? process.env["DSH_KICAD_SKILLS_DIR"]);
36
36
  }
37
37
  //#endregion
38
+ //#region src/paths.ts
39
+ /**
40
+ * Resolution of the bundled skill directories at runtime.
41
+ *
42
+ * Every bundled skill ships inside the installed package
43
+ * (`<pkg>/skills/<skill-id>/`), not beside the source tree, so it is located
44
+ * relative to the loaded module. That makes one resolver correct for every
45
+ * install shape:
46
+ *
47
+ * - built artifact: `<pkg>/lib/index.mjs` -> `<pkg>/skills/<skill-id>`
48
+ * - source (vitest): `<pkg>/src/index.ts` -> `<pkg>/skills/<skill-id>`
49
+ * - npm / git install: identical to the built artifact case
50
+ *
51
+ * `skills` must stay in `package.json` `files[]` or npm strips it and this
52
+ * resolver fails loudly — which is the intended signal, because a `dsh-kicad`
53
+ * without its skills is a broken delivery boundary.
54
+ *
55
+ * The override (`skillsDir` config / `DSH_KICAD_SKILLS_DIR`) is the **skills
56
+ * root**, i.e. the directory that *contains* the skill directories — not one
57
+ * skill's directory. One override therefore relocates the whole set, and the
58
+ * per-skill layout below it is unchanged.
59
+ *
60
+ * @module
61
+ */
62
+ /**
63
+ * Absolute path of the directory holding every bundled skill.
64
+ *
65
+ * Resolution order: an explicit override (config / `DSH_KICAD_SKILLS_DIR`),
66
+ * then the package-relative location.
67
+ *
68
+ * @param moduleUrl - `import.meta.url` of the calling module.
69
+ * @param override - explicit skills root (plugin config or env var).
70
+ * @returns the resolved directory, whether or not it exists yet.
71
+ */
72
+ function skillsRoot(moduleUrl, override) {
73
+ if (override && override.trim().length > 0) return resolve(override);
74
+ const envOverride = process.env["DSH_KICAD_SKILLS_DIR"];
75
+ if (envOverride && envOverride.trim().length > 0) return resolve(envOverride);
76
+ const here = dirname(fileURLToPath(moduleUrl));
77
+ return resolve(here, "..", "skills");
78
+ }
79
+ /**
80
+ * Absolute path of one bundled skill directory.
81
+ *
82
+ * @param moduleUrl - `import.meta.url` of the calling module.
83
+ * @param skillId - bundled skill id; MUST equal the directory name.
84
+ * @param override - explicit skills root (plugin config or env var).
85
+ * @returns the resolved directory, whether or not it exists yet.
86
+ */
87
+ function resolveSkillDir(moduleUrl, skillId, override) {
88
+ return join(skillsRoot(moduleUrl, override), skillId);
89
+ }
90
+ /**
91
+ * Resolve one skill directory and assert the skill is actually present.
92
+ *
93
+ * @throws when `SKILL.md` is missing — this is a packaging failure, not a
94
+ * runtime condition, so it must be loud rather than silently degraded.
95
+ */
96
+ function requireSkillDir(moduleUrl, skillId, override) {
97
+ const dir = resolveSkillDir(moduleUrl, skillId, override);
98
+ const skillFile = join(dir, "SKILL.md");
99
+ if (!existsSync(skillFile)) throw new Error(`@huaqiu/dsh-kicad: bundled skill missing at ${skillFile}. The installed package is incomplete — reinstall @huaqiu/dsh-kicad so that its skills/ directory is present.`);
100
+ return dir;
101
+ }
102
+ /** Absolute path of the bundled Python script directory inside a skill. */
103
+ function scriptsDir(skillDir) {
104
+ return join(skillDir, "scripts");
105
+ }
106
+ //#endregion
107
+ //#region src/skills.ts
108
+ /**
109
+ * The skill that owns the script templates behind the KiCad tools.
110
+ *
111
+ * The tools are the *executable* half of this skill: `kicad_pcb_*` shells out
112
+ * to `skills/kicad-ipc/scripts/*.py`. Kept as a named constant because the
113
+ * tools must keep resolving correctly even when that particular skill fails to
114
+ * load (they then fail per-call with a typed `FAILED_PRECONDITION` instead of
115
+ * silently pointing at another skill's directory).
116
+ */
117
+ const KICAD_SCRIPT_SKILL_ID = "kicad-ipc";
118
+ /**
119
+ * Every skill bundled with `@huaqiu/dsh-kicad`, keyed by id.
120
+ *
121
+ * Mirrors `skills/` 1:1 — the bundling test asserts that every entry exists on
122
+ * disk with a valid SKILL.md, and that no directory is left unregistered.
123
+ */
124
+ const KICAD_SKILLS = {
125
+ [KICAD_SCRIPT_SKILL_ID]: {
126
+ id: KICAD_SCRIPT_SKILL_ID,
127
+ summary: "Operate a KiCad PCB through the official KiCad IPC API: inspect the live board and create, modify or delete objects. Use for PCB automation and autorouter-result import — not for editing .kicad_pcb files directly.",
128
+ ownsScripts: true
129
+ },
130
+ "hardware-design-brief": {
131
+ id: "hardware-design-brief",
132
+ summary: "Turn a market need into verifiable hardware requirements: clarify them through staged questioning and write the market MRD, the hardware PRD and the schematic/PCB design inputs. Use when defining requirements for a new board project.",
133
+ ownsScripts: false
134
+ }
135
+ };
136
+ /** The bundled skill ids, in registration order. */
137
+ const KICAD_SKILL_IDS = Object.keys(KICAD_SKILLS);
138
+ /**
139
+ * Look up one bundled skill by id.
140
+ * @throws when the id is not part of the bundled set.
141
+ */
142
+ function kicadSkill(id) {
143
+ const skill = KICAD_SKILLS[id];
144
+ if (!skill) throw new Error(`@huaqiu/dsh-kicad: unknown bundled skill "${id}"`);
145
+ return skill;
146
+ }
147
+ //#endregion
38
148
  //#region src/scripts.ts
39
149
  /**
40
150
  * Every bundled script, keyed by id.
@@ -126,60 +236,6 @@ function kicadScript(id) {
126
236
  if (!script) throw new Error(`@huaqiu/dsh-kicad: unknown bundled KiCad script "${id}"`);
127
237
  return script;
128
238
  }
129
- /** The canonical skill directory name shipped by this package. */
130
- const KICAD_SKILL_NAME = "kicad-ipc";
131
- //#endregion
132
- //#region src/paths.ts
133
- /**
134
- * Resolution of the bundled KiCad skill directory at runtime.
135
- *
136
- * The skill ships inside the installed package (`<pkg>/skills/kicad-ipc/`), not
137
- * beside the source tree, so it is located relative to the loaded module. That
138
- * makes one resolver correct for every install shape:
139
- *
140
- * - built artifact: `<pkg>/lib/index.mjs` -> `<pkg>/skills/kicad-ipc`
141
- * - source (vitest): `<pkg>/src/index.ts` -> `<pkg>/skills/kicad-ipc`
142
- * - npm / git install: identical to the built artifact case
143
- *
144
- * `skills` must stay in `package.json` `files[]` or npm strips it and this
145
- * resolver fails loudly — which is the intended signal, because a `dsh-kicad`
146
- * without its skill is a broken delivery boundary.
147
- *
148
- * @module
149
- */
150
- /**
151
- * Absolute path of the bundled `kicad-ipc` skill directory.
152
- *
153
- * Resolution order: an explicit override (config / `DSH_KICAD_SKILLS_DIR`),
154
- * then the package-relative location.
155
- *
156
- * @param moduleUrl - `import.meta.url` of the calling module.
157
- * @param override - explicit directory (plugin config or env var).
158
- * @returns the resolved directory, whether or not it exists yet.
159
- */
160
- function resolveSkillDir(moduleUrl, override) {
161
- if (override && override.trim().length > 0) return resolve(override);
162
- const envOverride = process.env["DSH_KICAD_SKILLS_DIR"];
163
- if (envOverride && envOverride.trim().length > 0) return resolve(envOverride);
164
- const here = dirname(fileURLToPath(moduleUrl));
165
- return resolve(here, "..", "skills", KICAD_SKILL_NAME);
166
- }
167
- /**
168
- * Resolve the skill directory and assert the skill is actually present.
169
- *
170
- * @throws when `SKILL.md` is missing — this is a packaging failure, not a
171
- * runtime condition, so it must be loud rather than silently degraded.
172
- */
173
- function requireSkillDir(moduleUrl, override) {
174
- const dir = resolveSkillDir(moduleUrl, override);
175
- const skillFile = join(dir, "SKILL.md");
176
- if (!existsSync(skillFile)) throw new Error(`@huaqiu/dsh-kicad: bundled skill missing at ${skillFile}. The installed package is incomplete — reinstall @huaqiu/dsh-kicad so that its skills/ directory is present.`);
177
- return dir;
178
- }
179
- /** Absolute path of the bundled Python script directory. */
180
- function scriptsDir(skillDir) {
181
- return join(skillDir, "scripts");
182
- }
183
239
  //#endregion
184
240
  //#region src/ipc.ts
185
241
  /**
@@ -851,7 +907,7 @@ const name = "@huaqiu/dsh-kicad";
851
907
  * Cordis services this half depends on.
852
908
  *
853
909
  * `skills` is REQUIRED: it is the DSH runtime's skill registry, and registering
854
- * the bundled `kicad-ipc` skill is this plugin's core job. Without the inject,
910
+ * the bundled skills is this plugin's core job. Without the inject,
855
911
  * `apply()`'s `ctx.skills` access would throw
856
912
  * `cannot get property "skills" without inject`.
857
913
  *
@@ -864,16 +920,10 @@ const inject = ["skills", "tools"];
864
920
  const log = getLogger("dsh-kicad");
865
921
  log.info("dsh-kicad: module loaded (waiting for the skills + tools services)");
866
922
  /**
867
- * Used only when the bundled SKILL.md has no parseable `description`
868
- * frontmatter. The real value always comes from the shipped file, so the skill
869
- * catalog cannot drift away from the skill body.
870
- */
871
- const FALLBACK_SKILL_DESCRIPTION = "Operate a KiCad PCB through the official KiCad IPC API: inspect the live board and create, modify or delete objects. Use for PCB automation and autorouter-result import — not for editing .kicad_pcb files directly.";
872
- /**
873
923
  * Extract the `description` field from SKILL.md YAML frontmatter.
874
924
  *
875
925
  * Only the single-line form is supported (quoted or bare), which is what the
876
- * migrated skill uses. Returns `undefined` when absent so the caller can fall
926
+ * migrated skills use. Returns `undefined` when absent so the caller can fall
877
927
  * back rather than registering a skill with an empty description — DSH ignores
878
928
  * frontmatter-less skills entirely, so an empty description would silently
879
929
  * break discovery.
@@ -887,24 +937,28 @@ function skillDescription(markdown) {
887
937
  return unquoted.length > 0 ? unquoted : void 0;
888
938
  }
889
939
  /**
890
- * Read the bundled `kicad-ipc` SKILL.md.
940
+ * Read one bundled SKILL.md.
891
941
  *
892
- * Exposed for tests and for callers that want the skill body without loading
893
- * the plugin (e.g. a packaging check).
942
+ * Exposed for tests and for callers that want a skill body without loading the
943
+ * plugin (e.g. a packaging check).
944
+ *
945
+ * @param moduleUrl - `import.meta.url` of the calling module.
946
+ * @param skillId - bundled skill id (see `./skills.ts`).
947
+ * @param override - explicit skills root (plugin config or env var).
894
948
  */
895
- function readBundledSkill(moduleUrl, override) {
896
- const dir = requireSkillDir(moduleUrl, override);
949
+ function readBundledSkill(moduleUrl, skillId, override) {
950
+ const dir = requireSkillDir(moduleUrl, skillId, override);
897
951
  const content = readFileSync(join(dir, "SKILL.md"), "utf8");
898
952
  return {
899
953
  dir,
900
- name: KICAD_SKILL_NAME,
901
- description: skillDescription(content) ?? FALLBACK_SKILL_DESCRIPTION,
954
+ name: skillId,
955
+ description: skillDescription(content) ?? kicadSkill(skillId).summary,
902
956
  content
903
957
  };
904
958
  }
905
959
  /**
906
- * Read the bundled skill, or `null` when the installed package does not carry
907
- * one.
960
+ * Read one bundled skill, or `null` when the installed package does not carry
961
+ * it.
908
962
  *
909
963
  * ── Why this degrades instead of throwing ──────────────────────────────────
910
964
  * A missing `skills/` tree is a PACKAGING failure, and the honest signal for a
@@ -916,51 +970,60 @@ function readBundledSkill(moduleUrl, override) {
916
970
  * That actually happened: HQ Edge's builtin-plugin staging copied only
917
971
  * `package.json` + `lib/`, so `skills/` never reached the bundle and the
918
972
  * server could not start at all. The correct blast radius for "this plugin's
919
- * skill is missing" is "this plugin is degraded", so we log at error level
920
- * with the exact path and remedy, skip skill registration, and keep the tools
921
- * (which fail per-call with a typed `FAILED_PRECONDITION` rather than at load).
973
+ * skill is missing" is "this skill is unavailable", so we log at error level
974
+ * with the exact path and remedy and skip that skill — per skill, so a broken
975
+ * `kicad-ipc` never hides a perfectly good `hardware-design-brief`. The tools
976
+ * are always kept (they fail per-call with a typed `FAILED_PRECONDITION`
977
+ * rather than at load).
922
978
  *
923
979
  * @param moduleUrl - `import.meta.url` of the calling module.
924
- * @param override - explicit skill directory (plugin config or env var).
980
+ * @param skillId - bundled skill id (see `./skills.ts`).
981
+ * @param override - explicit skills root (plugin config or env var).
925
982
  */
926
- function tryReadBundledSkill(moduleUrl, override) {
983
+ function tryReadBundledSkill(moduleUrl, skillId, override) {
927
984
  try {
928
- return readBundledSkill(moduleUrl, override);
985
+ return readBundledSkill(moduleUrl, skillId, override);
929
986
  } catch (err) {
930
- log.error("dsh-kicad: bundled skill unavailable — continuing degraded, the KiCad tools are registered but will fail until the package is reinstalled", {
931
- expectedDir: resolveSkillDir(moduleUrl, override),
987
+ log.error(`dsh-kicad: bundled skill "${skillId}" unavailable — continuing degraded, the KiCad tools are registered but will fail until the package is reinstalled`, {
988
+ expectedDir: resolveSkillDir(moduleUrl, skillId, override),
932
989
  error: String(err?.message ?? err)
933
990
  });
934
991
  return null;
935
992
  }
936
993
  }
937
994
  /**
938
- * Host plugin body — register the `kicad-ipc` skill and the KiCad tools.
995
+ * Host plugin body — register every bundled skill and the KiCad tools.
939
996
  *
940
- * Both halves are registered here so that one installation delivers both. The
941
- * skill's `resourceBase` points at the bundled skill directory, which is how
942
- * the agent reaches `references/ipc-pcb-workflows.md` and `scripts/` as
943
- * progressive-disclosure resources.
997
+ * Both halves are registered here so that one installation delivers both. Each
998
+ * skill's `resourceBase` points at its own bundled directory, which is how the
999
+ * agent reaches `references/` and `scripts/` as progressive-disclosure
1000
+ * resources.
944
1001
  *
945
1002
  * @param ctx - real cordis context (node side).
946
1003
  * @param config - plugin overlay config (python interpreter, timeouts).
947
- * @returns disposer — unregisters the skill and the tools on plugin dispose.
1004
+ * @returns disposer — unregisters every skill and tool on plugin dispose.
948
1005
  */
949
1006
  function apply(ctx, config = {}) {
950
1007
  if (!ctx.tools || typeof ctx.tools.register !== "function") throw new Error("@huaqiu/dsh-kicad requires the DSH `tools` service (ctx.tools.register).");
951
1008
  if (!ctx.skills || typeof ctx.skills.register !== "function") throw new Error("@huaqiu/dsh-kicad requires the DSH `skills` service (ctx.skills.register).");
952
1009
  const resolved = resolveKicadConfig(config);
953
- const skill = tryReadBundledSkill(import.meta.url, config.skillsDir);
954
- const skillDir = skill?.dir ?? resolveSkillDir(import.meta.url, config.skillsDir);
1010
+ const skills = /* @__PURE__ */ new Map();
1011
+ for (const skillId of KICAD_SKILL_IDS) {
1012
+ const skill = tryReadBundledSkill(import.meta.url, skillId, config.skillsDir);
1013
+ if (skill) skills.set(skillId, skill);
1014
+ }
1015
+ const scriptSkillDir = skills.get("kicad-ipc")?.dir ?? resolveSkillDir(import.meta.url, "kicad-ipc", config.skillsDir);
955
1016
  log.info("applying dsh-kicad node half", {
956
1017
  hasConfigHost: hasHostConfig(config),
957
1018
  pythonPath: resolved.pythonPath,
958
- skillDir,
959
- skillPresent: skill !== null,
1019
+ skillsRoot: skillsRoot(import.meta.url, config.skillsDir),
1020
+ skillsRegistered: [...skills.keys()],
1021
+ skillsMissing: KICAD_SKILL_IDS.filter((id) => !skills.has(id)),
1022
+ scriptSkillDir,
960
1023
  timeoutMs: resolved.timeoutMs
961
1024
  });
962
1025
  const disposers = [];
963
- if (skill) disposers.push(ctx.skills.register({
1026
+ for (const skill of skills.values()) disposers.push(ctx.skills.register({
964
1027
  name: skill.name,
965
1028
  description: skill.description,
966
1029
  content: skill.content,
@@ -971,14 +1034,14 @@ function apply(ctx, config = {}) {
971
1034
  }
972
1035
  }));
973
1036
  const tools = createKicadTools({
974
- scriptsDir: scriptsDir(skillDir),
1037
+ scriptsDir: scriptsDir(scriptSkillDir),
975
1038
  pythonPath: resolved.pythonPath,
976
1039
  config: resolved
977
1040
  });
978
1041
  for (const tool of tools) disposers.push(ctx.tools.register(tool));
979
1042
  log.info("dsh-kicad node half ready", {
980
- skill: skill?.name ?? null,
981
- degraded: skill === null,
1043
+ skills: [...skills.keys()],
1044
+ degraded: skills.size < KICAD_SKILL_IDS.length,
982
1045
  tools: tools.length,
983
1046
  expectedTools: kicadToolNames().length
984
1047
  });
@@ -991,4 +1054,4 @@ function apply(ctx, config = {}) {
991
1054
  };
992
1055
  }
993
1056
  //#endregion
994
- export { KICAD_SCRIPTS, KICAD_SCRIPT_IDS, KICAD_SKILL_NAME, apply, classifyRun, createKicadTools, inject, invokeKicadScript, kicadScript, kicadToolNames, name, readBundledSkill, requireSkillDir, resolveSkillDir, runKicadScript, scriptsDir, skillDescription, tryReadBundledSkill };
1057
+ export { KICAD_SCRIPTS, KICAD_SCRIPT_IDS, KICAD_SCRIPT_SKILL_ID, KICAD_SKILLS, KICAD_SKILL_IDS, apply, classifyRun, createKicadTools, inject, invokeKicadScript, kicadScript, kicadSkill, kicadToolNames, name, readBundledSkill, requireSkillDir, resolveSkillDir, runKicadScript, scriptsDir, skillDescription, skillsRoot, tryReadBundledSkill };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huaqiu/dsh-kicad",
3
- "version": "0.4.10",
3
+ "version": "0.4.13",
4
4
  "type": "module",
5
5
  "main": "./lib/index.mjs",
6
6
  "types": "./lib/index.d.mts",
@@ -22,7 +22,7 @@
22
22
  "@deepseek-ai/dsh-tools": "^0.1.0-rc.0"
23
23
  },
24
24
  "dependencies": {
25
- "@huaqiu/dsh-plugin-log": "0.4.10"
25
+ "@huaqiu/dsh-plugin-log": "0.4.13"
26
26
  },
27
27
  "files": [
28
28
  "lib",
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: hardware-design-brief
3
+ description: "通过分阶段提问澄清硬件产品需求,生成市场 MRD、硬件 PRD,以及原理图和 PCB 设计输入;适用于新电路板项目的需求定义。"
4
+ ---
5
+
6
+ # 硬件产品需求与设计输入
7
+
8
+ 帮助工程师把市场需求逐步转成可验收的产品要求,再转成原理图和 PCB 设计输入。按用户实际请求生成其中一份或整套文档,不强制补齐未请求的阶段。
9
+
10
+ ## 本技能与同包能力的配合
11
+
12
+ 安装 `@huaqiu/dsh-kicad` 后,本技能随该插件一起可用,无需单独安装技能。
13
+
14
+ 本技能是**知识型技能**:它不注册任何工具,只提供提问流程、文档结构和“已确认 / 有来源 / 建议 / 假设 / 待确认”的区分口径。同包内的 `kicad-ipc` 技能负责把本技能产出的设计输入落到实际 PCB 上,并提供一组 `kicad_pcb_*` 工具(新建走线、过孔、铜区、移动/旋转封装、删除选中对象等)。
15
+
16
+ 两者的分工是:
17
+
18
+ | 阶段 | 归属 | 产出 |
19
+ | --- | --- | --- |
20
+ | 市场需求、产品要求、设计输入 | 本技能 | `docs/01-MRD.md`、`docs/02-hardware-PRD.md`、`docs/03-design-brief.md`、`docs/decisions.md` |
21
+ | 把设计输入落到 PCB(读取、修改、验证、保存) | `kicad-ipc` 技能 + `kicad_pcb_*` 工具 | 实际板卡改动 |
22
+
23
+ 当讨论进入“具体要在板上做什么”时,参照 `kicad-ipc` 技能:任何改动前先用 `kicad_ipc_diagnose` 确认 KiCad 可连接,改动后重新读取受影响的状态确认,保存由 `save` 参数显式控制。
24
+
25
+ `references/` 下的三份指南按需读取(见下文),它们是本技能的一部分,不会自动全部加载。
26
+
27
+ ## 文档位置与关系
28
+
29
+ 把生成的项目文档放在**项目根目录的 `docs/`**,与本 skill 的 `references/` 分开。整套文档使用以下文件名;用户已有命名约定时沿用其约定,不另建重复文件。
30
+
31
+ ```text
32
+ docs/
33
+ ├─ 01-MRD.md 市场需求:目标客户、场景、机会与商业边界
34
+ ├─ 02-hardware-PRD.md 硬件产品需求:产品必须达到什么、如何验收
35
+ ├─ 03-design-brief.md 原理图和 PCB 设计输入:如何落实已确认要求
36
+ └─ decisions.md 假设、未决问题、决策及变更依据
37
+ ```
38
+
39
+ MRD 说明“谁需要、为何需要”;PRD 定义“产品必须达到什么”;design brief 将已确认的 PRD 要求转成可供原理图和 PCB 开工的约束与选择。使用稳定编号串联:MRD 为 `M-001`,PRD 为 `H-001`,PRD 标注来源的 MRD 编号,design brief 引用相关 PRD 编号。只给可追溯的需求编号,避免给普通叙述逐句编号。
40
+
41
+ ## 对话与生成
42
+
43
+ 1. 先阅读用户提供的背景和已有文档,确认项目根目录、目标交付物及已知约束。若项目位置明确,直接使用;不要重复询问已给出的信息。
44
+ 2. 按影响决策的程度分批提问,每轮只问少量相关问题,允许部分回答或填写“暂不确定”。先解决会改变目标市场、产品范围、核心功能、供电、接口、尺寸、层数或成本的问题。
45
+ 3. 区分**已确认**、**有来源的事实**、**建议**、**假设**和**待确认**。市场数据、竞品参数和器件能力若未核实,不写成事实;关键未知不能用臆测填满。遇到互相冲突的要求,指出冲突并请求取舍。
46
+ 4. 生成用户要求的文档;若用户要求整套文档,按 MRD → PRD → design brief 的依赖关系编写,并在 `docs/decisions.md` 记录会影响后续设计的假设和未决事项。缺少上游文档时,也可先做下游草案,但应标注其市场或产品前提。
47
+ 5. 检查各文档中数值、单位、状态和编号是否一致。下游文档不能把上游的假设或待确认事项升级为已批准要求。对更新过的要求,指出受影响的后续条目。
48
+
49
+ 按当前任务读取对应指南,不必一次加载全部:
50
+
51
+ - 创建或更新 MRD:阅读 [references/mrd-guide.md](references/mrd-guide.md)。
52
+ - 创建或更新硬件 PRD:阅读 [references/hardware-prd-guide.md](references/hardware-prd-guide.md)。
53
+ - 创建或更新原理图与 PCB 设计输入:阅读 [references/design-brief-guide.md](references/design-brief-guide.md)。
54
+
55
+ 用户只想讨论方向时先讨论;用户只要求一份文件时仅创建或修改该文件,必要的未决问题可直接列在该文件末尾。
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "硬件需求与设计输入"
3
+ short_description: "通过提问生成市场 MRD、硬件 PRD 与电路板设计输入"
4
+ default_prompt: "使用 $hardware-design-brief 帮我梳理市场需求、硬件产品需求和电路板设计输入。"
@@ -0,0 +1,16 @@
1
+ # 原理图与 PCB 设计输入
2
+
3
+ 本文件将已确认的产品需求转成开工约束,保持简练。先从 PRD 和已有设计资料提取信息,再追问会影响电路架构和 PCB 实现的事项:
4
+
5
+ - 电源输入、各电源轨、电流/功率预算、负载、时序和保护。
6
+ - 核心器件及所需计算、存储、外设、封装、供货和软件/工具链条件。
7
+ - 有线/无线接口、连接器、电平、速率、线缆长度、隔离、调试与测试接口。
8
+ - 板外形、板厚、安装孔、禁布区、器件高度、连接器位置和方向。
9
+ - 层数、叠层、铜厚、阻抗、高速/模拟/大电流/天线区域、散热和 EMI 约束。
10
+ - 制造、装配、检测、维修、环境及适用标准的设计输入。
11
+
12
+ 建议将 `docs/03-design-brief.md` 组织为:项目目标与适用 PRD 编号、原理图设计要求、PCB 设计要求、关键器件表、待确认与验证。原理图部分说明功能分区、核心器件、电源、接口、保护和测试点;PCB 部分说明尺寸、层数、机械、布局与工艺约束。每项关键设计要求引用相关 `H-` 编号;若是工程建议而非 PRD 硬性约束,标明“建议”。
13
+
14
+ 区分**指定器件**、**候选器件**和**待选**。具体候选需有足够的功能、电气、封装和供应信息支撑,并说明选择依据与待核实项;未经核对的候选不可写成批准选型。PCB 尺寸、层数、板厚、铜厚和阻抗优先记录明确边界;若用户需要建议,可基于密度、速率、阻抗、电流、散热、EMI、成本及制造能力提出建议值。未知参数写“待确认”,不虚构精度。
15
+
16
+ 对安规、EMC、热、信号完整性及可制造性,列明需要计算、仿真、打样或测试的验证事项。design brief 是设计输入,不代表这些项目已经满足要求。
@@ -0,0 +1,14 @@
1
+ # 硬件产品需求文档(PRD)
2
+
3
+ PRD 定义产品可交付和可验收的结果。先从 MRD 场景或用户直接给出的产品目标出发,追问决定产品架构和可测试性的缺口:
4
+
5
+ - 功能边界、典型/异常使用流程、模式、通道数、响应时间、精度、速率和容量。
6
+ - 输入电源范围、功耗与续航目标、输出负载、上电/掉电行为及故障状态。
7
+ - 外部接口、协议、连接器、线缆、兼容对象,以及固件/软件对硬件的要求。
8
+ - 外形和安装边界、环境条件、寿命、可靠性、维护与升级方式。
9
+ - 安全、EMC/ESD、无线和目标市场的适用要求;认证目标与验证状态分开。
10
+ - 成本目标、生产批量、制造/测试/维修要求和上市时间。
11
+
12
+ 建议将 `docs/02-hardware-PRD.md` 组织为:产品概述与范围、用户场景、系统边界、需求表、接口定义、机械与环境边界、成本/制造约束、验收与验证、待确认事项。需求表的每一项尽量包含:编号 `H-001`、来源 MRD 编号(若存在)、要求、优先级、状态、验收条件/方法。要求应可测量,并写明单位、工作条件和必要的容差;例如电源范围应区分正常工作、极限和故障条件。
13
+
14
+ PRD 可记录用户指定的核心器件或不可更改的实现约束,并说明来源;未指定时优先写功能、电气和接口要求,让具体器件选择留到 design brief。目标尚不明确时标记待确认,不用任意数值凑齐表格。涉及测试或认证的条目写为目标和验收方法,不宣称已经通过。
@@ -0,0 +1,14 @@
1
+ # 市场需求文档(MRD)
2
+
3
+ MRD 为产品取舍提供市场依据,不预先指定电路方案。采访式提问按项目选择,不要照表逐项盘问:
4
+
5
+ - 目标客户和购买者是谁?谁实际使用、安装或维护?目标地区与行业是什么?
6
+ - 客户在什么场景遇到什么问题?现有替代方案是什么,痛点有多频繁或昂贵?
7
+ - 必须解决的场景、差异化价值与明确不覆盖的场景是什么?
8
+ - 竞品或替代方案的价格、能力、限制与渠道是什么?哪些结论有证据,哪些只是判断?
9
+ - 预期售价、目标销量/批量、上市窗口、销售渠道和商业约束是什么?
10
+ - 哪些法规、采购门槛、使用环境或服务要求会影响产品定义?
11
+
12
+ 建议将 `docs/01-MRD.md` 组织为:概要、目标客户与使用场景、问题和现有方案、市场与竞品证据、价值主张、商业目标及边界、市场需求条目、假设与待验证事项。市场需求条目用 `M-001` 等编号,写清对象、场景、需求及优先级;尽量给出支撑证据或验证方式。
13
+
14
+ 涉及现时市场规模、竞品规格、价格、法规或供应状况时,核对可靠来源并记录来源链接和查询日期。用户提供的估计注明“用户估计”,缺证据的判断注明“假设”;不要编造市场规模、销量或竞品数据。MRD 中可以给出目标区间,但不把尚未论证的商业目标写成已验证结果。
package/src/config.ts CHANGED
@@ -18,7 +18,12 @@ export interface KicadConfig {
18
18
  * official `kicad-python` package (`kipy`) installed.
19
19
  */
20
20
  pythonPath: string
21
- /** Scripts directory override (defaults to the bundled skill's `scripts/`). */
21
+ /**
22
+ * Skills root override — the directory that CONTAINS the bundled skill
23
+ * directories (`<root>/kicad-ipc`, `<root>/hardware-design-brief`). Defaults
24
+ * to the package's own `skills/`. Relocates the whole skill set at once; the
25
+ * per-skill layout below it never changes.
26
+ */
22
27
  skillsDir?: string
23
28
  /** Per-script timeout in milliseconds. */
24
29
  timeoutMs: number