@huaqiu/dsh-kicad 0.4.12 → 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/README.md +62 -28
- package/cordis.patch.yml +4 -3
- package/lib/index.d.mts +130 -42
- package/lib/index.mjs +157 -94
- package/package.json +2 -2
- package/skills/hardware-design-brief/SKILL.md +55 -0
- package/skills/hardware-design-brief/agents/openai.yaml +4 -0
- package/skills/hardware-design-brief/references/design-brief-guide.md +16 -0
- package/skills/hardware-design-brief/references/hardware-prd-guide.md +14 -0
- package/skills/hardware-design-brief/references/mrd-guide.md +14 -0
- package/src/config.ts +6 -1
- package/src/index.ts +96 -64
- package/src/paths.ts +33 -17
- package/src/scripts.ts +4 -6
- package/src/skills.ts +94 -0
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
|
|
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
|
|
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
|
|
940
|
+
* Read one bundled SKILL.md.
|
|
891
941
|
*
|
|
892
|
-
* Exposed for tests and for callers that want
|
|
893
|
-
*
|
|
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:
|
|
901
|
-
description: skillDescription(content) ??
|
|
954
|
+
name: skillId,
|
|
955
|
+
description: skillDescription(content) ?? kicadSkill(skillId).summary,
|
|
902
956
|
content
|
|
903
957
|
};
|
|
904
958
|
}
|
|
905
959
|
/**
|
|
906
|
-
* Read
|
|
907
|
-
*
|
|
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
|
|
920
|
-
* with the exact path and remedy
|
|
921
|
-
*
|
|
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
|
|
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(
|
|
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
|
|
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.
|
|
941
|
-
* skill's `resourceBase` points at
|
|
942
|
-
*
|
|
943
|
-
*
|
|
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
|
|
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
|
|
954
|
-
const
|
|
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
|
-
|
|
959
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
981
|
-
degraded:
|
|
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,
|
|
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.
|
|
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.
|
|
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,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
|
-
/**
|
|
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
|