@huaqiu/dsh-kicad 0.4.12 → 0.5.1

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,20 +1,29 @@
1
1
  # @huaqiu/dsh-kicad
2
2
 
3
3
  KiCad agent capabilities for DeepSeek Harness, shipped as **one** DSH plugin: the
4
- executable KiCad tools *and* the `kicad-ipc` skill that teaches the agent how to use
5
- them. Installing the Huaqiu DSH PCB/EDA bundle makes both available out of the box —
4
+ executable KiCad tools *and* the bundled skills that teach the agent how to use them.
5
+ Installing the Huaqiu DSH PCB/EDA bundle makes both available out of the box —
6
6
  there is no separate skill installation step.
7
7
 
8
- Migrated from the standalone `kicad-agent` repository.
8
+ Migrated from the standalone `kicad-agent` repository, which now tracks this package: its
9
+ `skills/` tree is kept **byte-identical** to the one below, so picking up an upstream
10
+ change is a plain `cp -r` and never a merge. A local test asserts that lockstep.
9
11
 
10
12
  ## What you get
11
13
 
12
14
  ```
13
15
  @huaqiu/dsh-kicad
14
16
  ├── Tools 10 KiCad tools, registered with ctx.tools.register(defineTool(...))
15
- └── Skills kicad-ipc — SKILL.md + references/ + 11 python script templates
17
+ └── Skills every skill under skills/, registered with ctx.skills.register(...)
18
+ ├── kicad-ipc SKILL.md + references/ + 11 python script templates
19
+ └── hardware-design-brief SKILL.md + 3 reference guides
16
20
  ```
17
21
 
22
+ The skill set is data, not code: `src/skills.ts` is the registry, so adding a skill
23
+ from `kicad-agent` is a directory copy plus one registry entry. There is no rename and no
24
+ translation step — upstream agrees with this package on directory names, skill ids and
25
+ language. Exactly one skill (`kicad-ipc`) owns the Python templates the tools execute.
26
+
18
27
  ### Tools
19
28
 
20
29
  | Tool | Script | Effect |
@@ -43,17 +52,32 @@ retry), `UNAVAILABLE` (KiCad unreachable — retry once after checking the envir
43
52
  `DEADLINE_EXCEEDED` (timed out), `INVALID_ARGUMENT` (board untouched), `INTERNAL`
44
53
  (script failed; the commit was dropped).
45
54
 
46
- ### Skill
55
+ ### Skills
56
+
57
+ Every directory under `skills/` is registered as a DSH skill, so one installation
58
+ delivers the operating guidance along with the tools. Each is progressive-disclosure:
59
+ the agent reads `SKILL.md` first and opens `references/` only when the task needs it.
60
+
61
+ **`skills/kicad-ipc/`** — how to operate KiCad: when to use it, how to inspect current
62
+ state, how to verify mutations, and when to stop and ask. It is *not* an IPC API
63
+ specification — the tool schemas are the executable interface, the skill is the
64
+ reasoning around them. Its `references/ipc-pcb-workflows.md` holds version matrices,
65
+ the public CRUD table and failure handling; `scripts/` holds the runnable templates
66
+ the tools execute.
67
+
68
+ **`skills/hardware-design-brief/`** — how to turn a market need into verifiable
69
+ requirement documents: staged questioning, the `docs/01-MRD.md` →
70
+ `docs/02-hardware-PRD.md` → `docs/03-design-brief.md` chain, and the discipline of
71
+ separating confirmed facts from suggestions, assumptions and open questions. It is
72
+ knowledge-only: no tools, no scripts. Its three guides (`mrd-guide.md`,
73
+ `hardware-prd-guide.md`, `design-brief-guide.md`) are loaded on demand.
47
74
 
48
- `skills/kicad-ipc/SKILL.md` is the agent's operating guidance: when to use KiCad, how to
49
- inspect current state, how to verify mutations, and when to stop and ask. It is *not* an
50
- IPC API specification — the tool schemas are the executable interface, the skill is the
51
- reasoning around them.
75
+ The two skills compose: `hardware-design-brief` produces the design inputs,
76
+ `kicad-ipc` (and the `kicad_pcb_*` tools) implements them on a real board. Each
77
+ skill documents its side of that handoff.
52
78
 
53
- Progressive disclosure: `references/ipc-pcb-workflows.md` holds version matrices, the
54
- public CRUD table and failure handling; `scripts/` holds runnable templates. Both are
55
- reachable by the agent because the skill is registered with a `resourceBase` pointing at
56
- its directory.
79
+ Both are reachable by the agent because each is registered with a `resourceBase`
80
+ pointing at its own directory.
57
81
 
58
82
  ## How the plugin is wired
59
83
 
@@ -67,17 +91,21 @@ its directory.
67
91
 
68
92
  `inject` must match `export const inject` in `src/index.ts`. `skills` is required —
69
93
  that is the DSH runtime's own plugin-bundled skill channel (`ctx.skills.register`).
70
- `apply()` registers the skill and the ten tools and returns a single disposer.
94
+ `apply()` registers every bundled skill and the ten tools, and returns a single
95
+ disposer. Loading is degraded per skill: a skill that fails to resolve is logged at
96
+ error level and skipped, and the rest of the plugin still loads — a broken
97
+ `hardware-design-brief` must never take the KiCad tools down with it, and vice versa.
71
98
 
72
- The skill is located relative to the **loaded module**, not the source tree, so one
99
+ The skills are located relative to the **loaded module**, not the source tree, so one
73
100
  resolver works for `src/` (tests), `lib/` (built) and an npm install:
74
101
 
75
102
  ```
76
- <package>/lib/index.mjs -> <package>/skills/kicad-ipc
103
+ <package>/lib/index.mjs -> <package>/skills -> <package>/skills/<skill-id>
77
104
  ```
78
105
 
79
- That is why `files[]` must contain `skills` — otherwise npm strips it and `apply()`
80
- throws loudly instead of silently degrading.
106
+ `skillsDir` / `$DSH_KICAD_SKILLS_DIR` overrides the **root** (`skills/`), not one
107
+ skill's directory, so it relocates the whole set at once. `skills` must be in
108
+ `files[]` — otherwise npm strips it and every skill resolution fails.
81
109
 
82
110
  ## Runtime requirements
83
111
 
@@ -105,7 +133,7 @@ package never guesses, hardcodes or scans for them.
105
133
  | Setting | Env | Default |
106
134
  |---|---|---|
107
135
  | `pythonPath` | `DSH_KICAD_PYTHON` | `python3` |
108
- | `skillsDir` | `DSH_KICAD_SKILLS_DIR` | bundled `skills/kicad-ipc` |
136
+ | `skillsDir` | `DSH_KICAD_SKILLS_DIR` | bundled `skills/` (the root holding every skill) |
109
137
  | `timeoutMs` | — | `30000` |
110
138
  | `diagnosticTimeoutMs` | — | `15000` |
111
139
  | `refillTimeoutMs` | — | `150000` |
@@ -124,17 +152,23 @@ package never guesses, hardcodes or scans for them.
124
152
 
125
153
  ```
126
154
  packages/dsh-kicad/
127
- src/index.ts # plugin entry: apply() registers skill + tools
155
+ src/index.ts # plugin entry: apply() registers every bundled skill + the tools
128
156
  src/tools.ts # 10 defineTool definitions
129
157
  src/ipc.ts # KiCad IPC adapter (script runner + error classification)
130
- src/scripts.ts # bundled script registry
131
- src/paths.ts # runtime skill-directory resolution
158
+ src/scripts.ts # bundled script registry (the kicad-ipc templates)
159
+ src/skills.ts # bundled skill registry (ids, summaries, which owns the scripts)
160
+ src/paths.ts # runtime skills-root + per-skill directory resolution
132
161
  src/config.ts # python interpreter / timeouts
133
- skills/kicad-ipc/
134
- SKILL.md
135
- agents/openai.yaml
136
- references/ipc-pcb-workflows.md
137
- scripts/*.py
162
+ skills/
163
+ kicad-ipc/
164
+ SKILL.md
165
+ agents/openai.yaml
166
+ references/ipc-pcb-workflows.md
167
+ scripts/*.py
168
+ hardware-design-brief/
169
+ SKILL.md
170
+ agents/openai.yaml
171
+ references/{mrd,hardware-prd,design-brief}-guide.md
138
172
  test/ # plugin loading, tool registration, skill discovery, bundling
139
173
  ```
140
174
 
@@ -144,6 +178,6 @@ packages/dsh-kicad/
144
178
  pnpm --filter @huaqiu/dsh-kicad test
145
179
  ```
146
180
 
147
- 46 tests covering plugin loading, the disposer, skill discovery, tool registration, argv
181
+ 58 tests covering plugin loading, the disposer, skill discovery, tool registration, argv
148
182
  construction, pre-flight validation, semantic error mapping and packaging. Execution is
149
183
  exercised against a fake interpreter, so no KiCad or `kipy` is needed.
package/cordis.patch.yml CHANGED
@@ -1,9 +1,10 @@
1
1
  # DSH bundle patch: inserts the Huaqiu KiCad plugin into a profile's layer stack.
2
2
  #
3
3
  # This one row delivers BOTH halves of `dsh-kicad` (task: dsh-kicad-skill-plugin):
4
- # the KiCad tools and the bundled `kicad-ipc` skill. The plugin registers the
5
- # skill itself at load time via `ctx.skills.register()`, so there is no separate
6
- # skill installation step and no second skill registration mechanism.
4
+ # the KiCad tools and every skill bundled under `skills/` (currently `kicad-ipc`
5
+ # and `hardware-design-brief`). The plugin registers the skills itself at load
6
+ # time via `ctx.skills.register()`, so there is no separate skill installation
7
+ # step and no second skill registration mechanism.
7
8
  #
8
9
  # The entry `inject` MUST match the node-half module contract (`src/index.ts`
9
10
  # `export const inject`): `skills` + `tools`. `skills` is REQUIRED — without it
package/lib/index.d.mts CHANGED
@@ -20,7 +20,12 @@ interface KicadConfig {
20
20
  * official `kicad-python` package (`kipy`) installed.
21
21
  */
22
22
  pythonPath: string;
23
- /** Scripts directory override (defaults to the bundled skill's `scripts/`). */
23
+ /**
24
+ * Skills root override — the directory that CONTAINS the bundled skill
25
+ * directories (`<root>/kicad-ipc`, `<root>/hardware-design-brief`). Defaults
26
+ * to the package's own `skills/`. Relocates the whole skill set at once; the
27
+ * per-skill layout below it never changes.
28
+ */
24
29
  skillsDir?: string;
25
30
  /** Per-script timeout in milliseconds. */
26
31
  timeoutMs: number;
@@ -36,9 +41,10 @@ type KicadConfigInput = Partial<KicadConfig>;
36
41
  * Registry of the KiCad IPC script templates bundled with this package.
37
42
  *
38
43
  * The scripts are the migrated `kicad-agent` executable surface. They live
39
- * under `skills/kicad-ipc/scripts/` and are shipped verbatim — this module only
40
- * describes them so the DSH tools and the skill stay in sync. Nothing here
41
- * reimplements KiCad IPC: the scripts own it (see `./ipc.ts`).
44
+ * under `skills/<KICAD_SCRIPT_SKILL_ID>/scripts/` — the `kicad-ipc` skill (see
45
+ * `./skills.ts`, which owns the skill ids) — and are shipped verbatim: this
46
+ * module only describes them so the DSH tools and the skill stay in sync.
47
+ * Nothing here reimplements KiCad IPC: the scripts own it (see `./ipc.ts`).
42
48
  *
43
49
  * @module
44
50
  */
@@ -77,8 +83,6 @@ declare const KICAD_SCRIPT_IDS: readonly string[];
77
83
  * @throws when the id is not part of the bundled set.
78
84
  */
79
85
  declare function kicadScript(id: string): KicadScript;
80
- /** The canonical skill directory name shipped by this package. */
81
- declare const KICAD_SKILL_NAME = "kicad-ipc";
82
86
  //#endregion
83
87
  //#region src/ipc.d.ts
84
88
  /**
@@ -156,6 +160,73 @@ declare function invokeKicadScript(options: RunScriptOptions): Promise<{
156
160
  error?: KicadError;
157
161
  }>;
158
162
  //#endregion
163
+ //#region src/skills.d.ts
164
+ /**
165
+ * Registry of the skills bundled with this package.
166
+ *
167
+ * The skills under `skills/` are the migrated `kicad-agent` knowledge, shipped
168
+ * verbatim (see `README.md` and `docs/dsh-kicad-migration.md`) — this module
169
+ * only *describes* them, so `apply()` can register each one with
170
+ * `ctx.skills.register()` without hardcoding paths or names.
171
+ *
172
+ * The registry is the single source of truth for three things that must not
173
+ * drift apart:
174
+ *
175
+ * - the DSH skill id, which MUST equal the directory name under `skills/`
176
+ * (the bundling tests assert this, because a mismatch silently produces a
177
+ * skill whose `resourceBase` points somewhere else);
178
+ * - the fallback catalogue description, used only when SKILL.md frontmatter
179
+ * has no parseable `description` (the shipped file always wins);
180
+ * - which skill owns the Python script templates the KiCad tools run.
181
+ *
182
+ * Adding a skill from `kicad-agent` means: copy the directory, add an entry
183
+ * here, and register it — nothing else in `src/` is skill-specific.
184
+ *
185
+ * @module
186
+ */
187
+ /** A skill this package bundles and registers at load time. */
188
+ interface KicadBundledSkill {
189
+ /** DSH skill id — must match the directory name under `skills/`. */
190
+ readonly id: string;
191
+ /**
192
+ * Agent-facing catalogue description, used ONLY when the bundled SKILL.md has
193
+ * no parseable `description` frontmatter. The shipped file is authoritative,
194
+ * so this cannot be the normal path — DSH ignores a skill without a
195
+ * description, so an empty value here would silently break discovery.
196
+ */
197
+ readonly summary: string;
198
+ /**
199
+ * Whether this skill owns `skills/<id>/scripts/` — the runnable Python
200
+ * templates that back the DSH tools. Exactly one bundled skill does; the
201
+ * tools resolve their interpreter scripts through it (`./paths.ts`).
202
+ */
203
+ readonly ownsScripts: boolean;
204
+ }
205
+ /**
206
+ * The skill that owns the script templates behind the KiCad tools.
207
+ *
208
+ * The tools are the *executable* half of this skill: `kicad_pcb_*` shells out
209
+ * to `skills/kicad-ipc/scripts/*.py`. Kept as a named constant because the
210
+ * tools must keep resolving correctly even when that particular skill fails to
211
+ * load (they then fail per-call with a typed `FAILED_PRECONDITION` instead of
212
+ * silently pointing at another skill's directory).
213
+ */
214
+ declare const KICAD_SCRIPT_SKILL_ID = "kicad-ipc";
215
+ /**
216
+ * Every skill bundled with `@huaqiu/dsh-kicad`, keyed by id.
217
+ *
218
+ * Mirrors `skills/` 1:1 — the bundling test asserts that every entry exists on
219
+ * disk with a valid SKILL.md, and that no directory is left unregistered.
220
+ */
221
+ declare const KICAD_SKILLS: Readonly<Record<string, KicadBundledSkill>>;
222
+ /** The bundled skill ids, in registration order. */
223
+ declare const KICAD_SKILL_IDS: readonly string[];
224
+ /**
225
+ * Look up one bundled skill by id.
226
+ * @throws when the id is not part of the bundled set.
227
+ */
228
+ declare function kicadSkill(id: string): KicadBundledSkill;
229
+ //#endregion
159
230
  //#region src/tools.d.ts
160
231
  /** Everything a tool needs to reach the bundled scripts. */
161
232
  interface KicadToolEnv {
@@ -177,24 +248,33 @@ declare function kicadToolNames(): string[];
177
248
  //#endregion
178
249
  //#region src/paths.d.ts
179
250
  /**
180
- * Absolute path of the bundled `kicad-ipc` skill directory.
251
+ * Absolute path of the directory holding every bundled skill.
181
252
  *
182
253
  * Resolution order: an explicit override (config / `DSH_KICAD_SKILLS_DIR`),
183
254
  * then the package-relative location.
184
255
  *
185
256
  * @param moduleUrl - `import.meta.url` of the calling module.
186
- * @param override - explicit directory (plugin config or env var).
257
+ * @param override - explicit skills root (plugin config or env var).
258
+ * @returns the resolved directory, whether or not it exists yet.
259
+ */
260
+ declare function skillsRoot(moduleUrl: string, override?: string): string;
261
+ /**
262
+ * Absolute path of one bundled skill directory.
263
+ *
264
+ * @param moduleUrl - `import.meta.url` of the calling module.
265
+ * @param skillId - bundled skill id; MUST equal the directory name.
266
+ * @param override - explicit skills root (plugin config or env var).
187
267
  * @returns the resolved directory, whether or not it exists yet.
188
268
  */
189
- declare function resolveSkillDir(moduleUrl: string, override?: string): string;
269
+ declare function resolveSkillDir(moduleUrl: string, skillId: string, override?: string): string;
190
270
  /**
191
- * Resolve the skill directory and assert the skill is actually present.
271
+ * Resolve one skill directory and assert the skill is actually present.
192
272
  *
193
273
  * @throws when `SKILL.md` is missing — this is a packaging failure, not a
194
274
  * runtime condition, so it must be loud rather than silently degraded.
195
275
  */
196
- declare function requireSkillDir(moduleUrl: string, override?: string): string;
197
- /** Absolute path of the bundled Python script directory. */
276
+ declare function requireSkillDir(moduleUrl: string, skillId: string, override?: string): string;
277
+ /** Absolute path of the bundled Python script directory inside a skill. */
198
278
  declare function scriptsDir(skillDir: string): string;
199
279
  //#endregion
200
280
  //#region src/index.d.ts
@@ -204,7 +284,7 @@ declare const name = "@huaqiu/dsh-kicad";
204
284
  * Cordis services this half depends on.
205
285
  *
206
286
  * `skills` is REQUIRED: it is the DSH runtime's skill registry, and registering
207
- * the bundled `kicad-ipc` skill is this plugin's core job. Without the inject,
287
+ * the bundled skills is this plugin's core job. Without the inject,
208
288
  * `apply()`'s `ctx.skills` access would throw
209
289
  * `cannot get property "skills" without inject`.
210
290
  *
@@ -255,27 +335,37 @@ declare module '@deepseek-ai/cordis' {
255
335
  * Extract the `description` field from SKILL.md YAML frontmatter.
256
336
  *
257
337
  * Only the single-line form is supported (quoted or bare), which is what the
258
- * migrated skill uses. Returns `undefined` when absent so the caller can fall
338
+ * migrated skills use. Returns `undefined` when absent so the caller can fall
259
339
  * back rather than registering a skill with an empty description — DSH ignores
260
340
  * frontmatter-less skills entirely, so an empty description would silently
261
341
  * break discovery.
262
342
  */
263
343
  declare function skillDescription(markdown: string): string | undefined;
264
- /**
265
- * Read the bundled `kicad-ipc` SKILL.md.
266
- *
267
- * Exposed for tests and for callers that want the skill body without loading
268
- * the plugin (e.g. a packaging check).
269
- */
270
- declare function readBundledSkill(moduleUrl: string, override?: string): {
271
- dir: string;
344
+ /** One bundled skill, as read from disk and ready for `ctx.skills.register()`. */
345
+ interface BundledSkill {
346
+ /** Skill id — equals the directory name and the DSH-catalogued id. */
272
347
  name: string;
348
+ /** Directory holding SKILL.md, `references/` and `scripts/`. */
349
+ dir: string;
350
+ /** Catalogue description (from SKILL.md frontmatter, else the registry). */
273
351
  description: string;
352
+ /** Full SKILL.md body. */
274
353
  content: string;
275
- };
354
+ }
355
+ /**
356
+ * Read one bundled SKILL.md.
357
+ *
358
+ * Exposed for tests and for callers that want a skill body without loading the
359
+ * plugin (e.g. a packaging check).
360
+ *
361
+ * @param moduleUrl - `import.meta.url` of the calling module.
362
+ * @param skillId - bundled skill id (see `./skills.ts`).
363
+ * @param override - explicit skills root (plugin config or env var).
364
+ */
365
+ declare function readBundledSkill(moduleUrl: string, skillId: string, override?: string): BundledSkill;
276
366
  /**
277
- * Read the bundled skill, or `null` when the installed package does not carry
278
- * one.
367
+ * Read one bundled skill, or `null` when the installed package does not carry
368
+ * it.
279
369
  *
280
370
  * ── Why this degrades instead of throwing ──────────────────────────────────
281
371
  * A missing `skills/` tree is a PACKAGING failure, and the honest signal for a
@@ -287,31 +377,29 @@ declare function readBundledSkill(moduleUrl: string, override?: string): {
287
377
  * That actually happened: HQ Edge's builtin-plugin staging copied only
288
378
  * `package.json` + `lib/`, so `skills/` never reached the bundle and the
289
379
  * server could not start at all. The correct blast radius for "this plugin's
290
- * skill is missing" is "this plugin is degraded", so we log at error level
291
- * with the exact path and remedy, skip skill registration, and keep the tools
292
- * (which fail per-call with a typed `FAILED_PRECONDITION` rather than at load).
380
+ * skill is missing" is "this skill is unavailable", so we log at error level
381
+ * with the exact path and remedy and skip that skill — per skill, so a broken
382
+ * `kicad-ipc` never hides a perfectly good `hardware-design-brief`. The tools
383
+ * are always kept (they fail per-call with a typed `FAILED_PRECONDITION`
384
+ * rather than at load).
293
385
  *
294
386
  * @param moduleUrl - `import.meta.url` of the calling module.
295
- * @param override - explicit skill directory (plugin config or env var).
387
+ * @param skillId - bundled skill id (see `./skills.ts`).
388
+ * @param override - explicit skills root (plugin config or env var).
296
389
  */
297
- declare function tryReadBundledSkill(moduleUrl: string, override?: string): {
298
- dir: string;
299
- name: string;
300
- description: string;
301
- content: string;
302
- } | null;
390
+ declare function tryReadBundledSkill(moduleUrl: string, skillId: string, override?: string): BundledSkill | null;
303
391
  /**
304
- * Host plugin body — register the `kicad-ipc` skill and the KiCad tools.
392
+ * Host plugin body — register every bundled skill and the KiCad tools.
305
393
  *
306
- * Both halves are registered here so that one installation delivers both. The
307
- * skill's `resourceBase` points at the bundled skill directory, which is how
308
- * the agent reaches `references/ipc-pcb-workflows.md` and `scripts/` as
309
- * progressive-disclosure resources.
394
+ * Both halves are registered here so that one installation delivers both. Each
395
+ * skill's `resourceBase` points at its own bundled directory, which is how the
396
+ * agent reaches `references/` and `scripts/` as progressive-disclosure
397
+ * resources.
310
398
  *
311
399
  * @param ctx - real cordis context (node side).
312
400
  * @param config - plugin overlay config (python interpreter, timeouts).
313
- * @returns disposer — unregisters the skill and the tools on plugin dispose.
401
+ * @returns disposer — unregisters every skill and tool on plugin dispose.
314
402
  */
315
403
  declare function apply(ctx: Context, config?: KicadConfigInput): () => void;
316
404
  //#endregion
317
- export { KICAD_SCRIPTS, KICAD_SCRIPT_IDS, KICAD_SKILL_NAME, type KicadConfig, type KicadConfigInput, type KicadError, type KicadErrorKind, type KicadScript, type ScriptEffect, type ScriptRun, SkillRegistration, apply, classifyRun, createKicadTools, inject, invokeKicadScript, kicadScript, kicadToolNames, name, readBundledSkill, requireSkillDir, resolveSkillDir, runKicadScript, scriptsDir, skillDescription, tryReadBundledSkill };
405
+ export { BundledSkill, KICAD_SCRIPTS, KICAD_SCRIPT_IDS, KICAD_SCRIPT_SKILL_ID, KICAD_SKILLS, KICAD_SKILL_IDS, type KicadBundledSkill, type KicadConfig, type KicadConfigInput, type KicadError, type KicadErrorKind, type KicadScript, type ScriptEffect, type ScriptRun, SkillRegistration, apply, classifyRun, createKicadTools, inject, invokeKicadScript, kicadScript, kicadSkill, kicadToolNames, name, readBundledSkill, requireSkillDir, resolveSkillDir, runKicadScript, scriptsDir, skillDescription, skillsRoot, tryReadBundledSkill };