@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/src/index.ts
CHANGED
|
@@ -5,19 +5,24 @@
|
|
|
5
5
|
* This plugin ships TWO things that used to need two installations:
|
|
6
6
|
*
|
|
7
7
|
* 1. ten KiCad tools, registered with `ctx.tools.register(defineTool(...))`
|
|
8
|
-
* 2.
|
|
8
|
+
* 2. every bundled skill under `skills/`, registered with
|
|
9
|
+
* `ctx.skills.register(...)` — currently `kicad-ipc` (the reasoning around
|
|
10
|
+
* the tools) and `hardware-design-brief` (requirements → design inputs)
|
|
9
11
|
*
|
|
10
12
|
* Installing the Huaqiu DSH PCB/EDA bundle therefore makes KiCad agent
|
|
11
|
-
* capabilities AND the
|
|
13
|
+
* capabilities AND the skills that teach the agent to use them available out of
|
|
12
14
|
* the box. There is no separate skill installation step, and this plugin adds no
|
|
13
15
|
* second skill-registration mechanism — `ctx.skills.register()` is the DSH
|
|
14
16
|
* runtime's own plugin-bundled skill channel.
|
|
15
17
|
*
|
|
18
|
+
* The skill set is data, not code: `./skills.ts` owns the registry, so adding a
|
|
19
|
+
* skill from `kicad-agent` is a copy plus a registry entry.
|
|
20
|
+
*
|
|
16
21
|
* ── Boundaries ──────────────────────────────────────────────────────────────
|
|
17
22
|
* KiCad IPC is reached through the bundled Python scripts under
|
|
18
|
-
* `skills
|
|
19
|
-
* dependency of any kind — no runtime, executable, service, port,
|
|
20
|
-
* artifact dependency (§3, §21) — and no `@hqedge/*` import.
|
|
23
|
+
* `skills/<KICAD_SCRIPT_SKILL_ID>/scripts/` (see `./ipc.ts`). This package holds
|
|
24
|
+
* no HQ Edge dependency of any kind — no runtime, executable, service, port,
|
|
25
|
+
* config or artifact dependency (§3, §21) — and no `@hqedge/*` import.
|
|
21
26
|
*
|
|
22
27
|
* @module @huaqiu/dsh-kicad
|
|
23
28
|
*/
|
|
@@ -32,8 +37,8 @@ import {
|
|
|
32
37
|
type KicadConfig,
|
|
33
38
|
type KicadConfigInput,
|
|
34
39
|
} from './config.js'
|
|
35
|
-
import { requireSkillDir, resolveSkillDir, scriptsDir } from './paths.js'
|
|
36
|
-
import {
|
|
40
|
+
import { requireSkillDir, resolveSkillDir, scriptsDir, skillsRoot } from './paths.js'
|
|
41
|
+
import { KICAD_SCRIPT_SKILL_ID, KICAD_SKILL_IDS, kicadSkill } from './skills.js'
|
|
37
42
|
import { createKicadTools, kicadToolNames } from './tools.js'
|
|
38
43
|
|
|
39
44
|
/** Plugin id — matches package.json. */
|
|
@@ -43,7 +48,7 @@ export const name = '@huaqiu/dsh-kicad'
|
|
|
43
48
|
* Cordis services this half depends on.
|
|
44
49
|
*
|
|
45
50
|
* `skills` is REQUIRED: it is the DSH runtime's skill registry, and registering
|
|
46
|
-
* the bundled
|
|
51
|
+
* the bundled skills is this plugin's core job. Without the inject,
|
|
47
52
|
* `apply()`'s `ctx.skills` access would throw
|
|
48
53
|
* `cannot get property "skills" without inject`.
|
|
49
54
|
*
|
|
@@ -57,9 +62,16 @@ export const inject = ['skills', 'tools'] as const
|
|
|
57
62
|
export type { KicadConfig, KicadConfigInput } from './config.js'
|
|
58
63
|
export type { KicadError, KicadErrorKind, ScriptRun } from './ipc.js'
|
|
59
64
|
export type { KicadScript, ScriptEffect } from './scripts.js'
|
|
60
|
-
export {
|
|
65
|
+
export type { KicadBundledSkill } from './skills.js'
|
|
66
|
+
export { KICAD_SCRIPTS, KICAD_SCRIPT_IDS, kicadScript } from './scripts.js'
|
|
67
|
+
export {
|
|
68
|
+
KICAD_SCRIPT_SKILL_ID,
|
|
69
|
+
KICAD_SKILL_IDS,
|
|
70
|
+
KICAD_SKILLS,
|
|
71
|
+
kicadSkill,
|
|
72
|
+
} from './skills.js'
|
|
61
73
|
export { kicadToolNames } from './tools.js'
|
|
62
|
-
export { resolveSkillDir, requireSkillDir, scriptsDir } from './paths.js'
|
|
74
|
+
export { resolveSkillDir, requireSkillDir, scriptsDir, skillsRoot } from './paths.js'
|
|
63
75
|
export { runKicadScript, classifyRun, invokeKicadScript } from './ipc.js'
|
|
64
76
|
export { createKicadTools } from './tools.js'
|
|
65
77
|
|
|
@@ -110,21 +122,11 @@ const log = getLogger(COMPONENT)
|
|
|
110
122
|
// `@huaqiu/dsh-eda-host`.
|
|
111
123
|
log.info('dsh-kicad: module loaded (waiting for the skills + tools services)')
|
|
112
124
|
|
|
113
|
-
/**
|
|
114
|
-
* Used only when the bundled SKILL.md has no parseable `description`
|
|
115
|
-
* frontmatter. The real value always comes from the shipped file, so the skill
|
|
116
|
-
* catalog cannot drift away from the skill body.
|
|
117
|
-
*/
|
|
118
|
-
const FALLBACK_SKILL_DESCRIPTION =
|
|
119
|
-
'Operate a KiCad PCB through the official KiCad IPC API: inspect the live ' +
|
|
120
|
-
'board and create, modify or delete objects. Use for PCB automation and ' +
|
|
121
|
-
'autorouter-result import — not for editing .kicad_pcb files directly.'
|
|
122
|
-
|
|
123
125
|
/**
|
|
124
126
|
* Extract the `description` field from SKILL.md YAML frontmatter.
|
|
125
127
|
*
|
|
126
128
|
* Only the single-line form is supported (quoted or bare), which is what the
|
|
127
|
-
* migrated
|
|
129
|
+
* migrated skills use. Returns `undefined` when absent so the caller can fall
|
|
128
130
|
* back rather than registering a skill with an empty description — DSH ignores
|
|
129
131
|
* frontmatter-less skills entirely, so an empty description would silently
|
|
130
132
|
* break discovery.
|
|
@@ -141,31 +143,48 @@ export function skillDescription(markdown: string): string | undefined {
|
|
|
141
143
|
return unquoted.length > 0 ? unquoted : undefined
|
|
142
144
|
}
|
|
143
145
|
|
|
144
|
-
/**
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
* Exposed for tests and for callers that want the skill body without loading
|
|
148
|
-
* the plugin (e.g. a packaging check).
|
|
149
|
-
*/
|
|
150
|
-
export function readBundledSkill(moduleUrl: string, override?: string): {
|
|
151
|
-
dir: string
|
|
146
|
+
/** One bundled skill, as read from disk and ready for `ctx.skills.register()`. */
|
|
147
|
+
export interface BundledSkill {
|
|
148
|
+
/** Skill id — equals the directory name and the DSH-catalogued id. */
|
|
152
149
|
name: string
|
|
150
|
+
/** Directory holding SKILL.md, `references/` and `scripts/`. */
|
|
151
|
+
dir: string
|
|
152
|
+
/** Catalogue description (from SKILL.md frontmatter, else the registry). */
|
|
153
153
|
description: string
|
|
154
|
+
/** Full SKILL.md body. */
|
|
154
155
|
content: string
|
|
155
|
-
}
|
|
156
|
-
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Read one bundled SKILL.md.
|
|
160
|
+
*
|
|
161
|
+
* Exposed for tests and for callers that want a skill body without loading the
|
|
162
|
+
* plugin (e.g. a packaging check).
|
|
163
|
+
*
|
|
164
|
+
* @param moduleUrl - `import.meta.url` of the calling module.
|
|
165
|
+
* @param skillId - bundled skill id (see `./skills.ts`).
|
|
166
|
+
* @param override - explicit skills root (plugin config or env var).
|
|
167
|
+
*/
|
|
168
|
+
export function readBundledSkill(
|
|
169
|
+
moduleUrl: string,
|
|
170
|
+
skillId: string,
|
|
171
|
+
override?: string,
|
|
172
|
+
): BundledSkill {
|
|
173
|
+
const dir = requireSkillDir(moduleUrl, skillId, override)
|
|
157
174
|
const content = readFileSync(join(dir, 'SKILL.md'), 'utf8')
|
|
158
175
|
return {
|
|
159
176
|
dir,
|
|
160
|
-
name:
|
|
161
|
-
|
|
177
|
+
name: skillId,
|
|
178
|
+
// The shipped frontmatter is authoritative; the registry summary is only a
|
|
179
|
+
// safety net so the catalog can never drift away from the skill body.
|
|
180
|
+
description: skillDescription(content) ?? kicadSkill(skillId).summary,
|
|
162
181
|
content,
|
|
163
182
|
}
|
|
164
183
|
}
|
|
165
184
|
|
|
166
185
|
/**
|
|
167
|
-
* Read
|
|
168
|
-
*
|
|
186
|
+
* Read one bundled skill, or `null` when the installed package does not carry
|
|
187
|
+
* it.
|
|
169
188
|
*
|
|
170
189
|
* ── Why this degrades instead of throwing ──────────────────────────────────
|
|
171
190
|
* A missing `skills/` tree is a PACKAGING failure, and the honest signal for a
|
|
@@ -177,25 +196,29 @@ export function readBundledSkill(moduleUrl: string, override?: string): {
|
|
|
177
196
|
* That actually happened: HQ Edge's builtin-plugin staging copied only
|
|
178
197
|
* `package.json` + `lib/`, so `skills/` never reached the bundle and the
|
|
179
198
|
* server could not start at all. The correct blast radius for "this plugin's
|
|
180
|
-
* skill is missing" is "this
|
|
181
|
-
* with the exact path and remedy
|
|
182
|
-
*
|
|
199
|
+
* skill is missing" is "this skill is unavailable", so we log at error level
|
|
200
|
+
* with the exact path and remedy and skip that skill — per skill, so a broken
|
|
201
|
+
* `kicad-ipc` never hides a perfectly good `hardware-design-brief`. The tools
|
|
202
|
+
* are always kept (they fail per-call with a typed `FAILED_PRECONDITION`
|
|
203
|
+
* rather than at load).
|
|
183
204
|
*
|
|
184
205
|
* @param moduleUrl - `import.meta.url` of the calling module.
|
|
185
|
-
* @param
|
|
206
|
+
* @param skillId - bundled skill id (see `./skills.ts`).
|
|
207
|
+
* @param override - explicit skills root (plugin config or env var).
|
|
186
208
|
*/
|
|
187
209
|
export function tryReadBundledSkill(
|
|
188
210
|
moduleUrl: string,
|
|
211
|
+
skillId: string,
|
|
189
212
|
override?: string,
|
|
190
|
-
):
|
|
213
|
+
): BundledSkill | null {
|
|
191
214
|
try {
|
|
192
|
-
return readBundledSkill(moduleUrl, override)
|
|
215
|
+
return readBundledSkill(moduleUrl, skillId, override)
|
|
193
216
|
} catch (err) {
|
|
194
217
|
log.error(
|
|
195
|
-
|
|
196
|
-
'tools are registered but will fail until the package is reinstalled',
|
|
218
|
+
`dsh-kicad: bundled skill "${skillId}" unavailable — continuing degraded, ` +
|
|
219
|
+
'the KiCad tools are registered but will fail until the package is reinstalled',
|
|
197
220
|
{
|
|
198
|
-
expectedDir: resolveSkillDir(moduleUrl, override),
|
|
221
|
+
expectedDir: resolveSkillDir(moduleUrl, skillId, override),
|
|
199
222
|
error: String((err as Error)?.message ?? err),
|
|
200
223
|
},
|
|
201
224
|
)
|
|
@@ -204,16 +227,16 @@ export function tryReadBundledSkill(
|
|
|
204
227
|
}
|
|
205
228
|
|
|
206
229
|
/**
|
|
207
|
-
* Host plugin body — register
|
|
230
|
+
* Host plugin body — register every bundled skill and the KiCad tools.
|
|
208
231
|
*
|
|
209
|
-
* Both halves are registered here so that one installation delivers both.
|
|
210
|
-
* skill's `resourceBase` points at
|
|
211
|
-
*
|
|
212
|
-
*
|
|
232
|
+
* Both halves are registered here so that one installation delivers both. Each
|
|
233
|
+
* skill's `resourceBase` points at its own bundled directory, which is how the
|
|
234
|
+
* agent reaches `references/` and `scripts/` as progressive-disclosure
|
|
235
|
+
* resources.
|
|
213
236
|
*
|
|
214
237
|
* @param ctx - real cordis context (node side).
|
|
215
238
|
* @param config - plugin overlay config (python interpreter, timeouts).
|
|
216
|
-
* @returns disposer — unregisters
|
|
239
|
+
* @returns disposer — unregisters every skill and tool on plugin dispose.
|
|
217
240
|
*/
|
|
218
241
|
export function apply(ctx: Context, config: KicadConfigInput = {}): () => void {
|
|
219
242
|
if (!ctx.tools || typeof ctx.tools.register !== 'function') {
|
|
@@ -225,26 +248,35 @@ export function apply(ctx: Context, config: KicadConfigInput = {}): () => void {
|
|
|
225
248
|
|
|
226
249
|
const resolved = resolveKicadConfig(config)
|
|
227
250
|
|
|
228
|
-
// Degraded rather than fatal: see tryReadBundledSkill.
|
|
229
|
-
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
251
|
+
// Degraded rather than fatal, per skill: see tryReadBundledSkill.
|
|
252
|
+
const skills = new Map<string, BundledSkill>()
|
|
253
|
+
for (const skillId of KICAD_SKILL_IDS) {
|
|
254
|
+
const skill = tryReadBundledSkill(import.meta.url, skillId, config.skillsDir)
|
|
255
|
+
if (skill) skills.set(skillId, skill)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// The tools resolve their Python scripts under the script-owning skill's
|
|
259
|
+
// directory, so they use the same path even when that skill itself failed to
|
|
260
|
+
// load — a call then fails with a typed FAILED_PRECONDITION naming the exact
|
|
261
|
+
// script it needed.
|
|
262
|
+
const scriptSkillDir =
|
|
263
|
+
skills.get(KICAD_SCRIPT_SKILL_ID)?.dir ??
|
|
264
|
+
resolveSkillDir(import.meta.url, KICAD_SCRIPT_SKILL_ID, config.skillsDir)
|
|
235
265
|
|
|
236
266
|
log.info('applying dsh-kicad node half', {
|
|
237
267
|
hasConfigHost: hasHostConfig(config),
|
|
238
268
|
pythonPath: resolved.pythonPath,
|
|
239
|
-
|
|
240
|
-
|
|
269
|
+
skillsRoot: skillsRoot(import.meta.url, config.skillsDir),
|
|
270
|
+
skillsRegistered: [...skills.keys()],
|
|
271
|
+
skillsMissing: KICAD_SKILL_IDS.filter((id) => !skills.has(id)),
|
|
272
|
+
scriptSkillDir,
|
|
241
273
|
timeoutMs: resolved.timeoutMs,
|
|
242
274
|
})
|
|
243
275
|
|
|
244
276
|
const disposers: Array<() => void> = []
|
|
245
277
|
|
|
246
|
-
// ──
|
|
247
|
-
|
|
278
|
+
// ── Skills (bundled, no separate installation) ───────────────────────────
|
|
279
|
+
for (const skill of skills.values()) {
|
|
248
280
|
disposers.push(
|
|
249
281
|
ctx.skills.register({
|
|
250
282
|
name: skill.name,
|
|
@@ -258,7 +290,7 @@ export function apply(ctx: Context, config: KicadConfigInput = {}): () => void {
|
|
|
258
290
|
|
|
259
291
|
// ── Tools ────────────────────────────────────────────────────────────────
|
|
260
292
|
const tools = createKicadTools({
|
|
261
|
-
scriptsDir: scriptsDir(
|
|
293
|
+
scriptsDir: scriptsDir(scriptSkillDir),
|
|
262
294
|
pythonPath: resolved.pythonPath,
|
|
263
295
|
config: resolved,
|
|
264
296
|
})
|
|
@@ -267,8 +299,8 @@ export function apply(ctx: Context, config: KicadConfigInput = {}): () => void {
|
|
|
267
299
|
}
|
|
268
300
|
|
|
269
301
|
log.info('dsh-kicad node half ready', {
|
|
270
|
-
|
|
271
|
-
degraded:
|
|
302
|
+
skills: [...skills.keys()],
|
|
303
|
+
degraded: skills.size < KICAD_SKILL_IDS.length,
|
|
272
304
|
tools: tools.length,
|
|
273
305
|
expectedTools: kicadToolNames().length,
|
|
274
306
|
})
|
package/src/paths.ts
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Resolution of the bundled
|
|
2
|
+
* Resolution of the bundled skill directories at runtime.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* beside the source tree, so it is located
|
|
6
|
-
* makes one resolver correct for every
|
|
4
|
+
* Every bundled skill ships inside the installed package
|
|
5
|
+
* (`<pkg>/skills/<skill-id>/`), not beside the source tree, so it is located
|
|
6
|
+
* relative to the loaded module. That makes one resolver correct for every
|
|
7
|
+
* install shape:
|
|
7
8
|
*
|
|
8
|
-
* - built artifact: `<pkg>/lib/index.mjs` -> `<pkg>/skills
|
|
9
|
-
* - source (vitest): `<pkg>/src/index.ts` -> `<pkg>/skills
|
|
9
|
+
* - built artifact: `<pkg>/lib/index.mjs` -> `<pkg>/skills/<skill-id>`
|
|
10
|
+
* - source (vitest): `<pkg>/src/index.ts` -> `<pkg>/skills/<skill-id>`
|
|
10
11
|
* - npm / git install: identical to the built artifact case
|
|
11
12
|
*
|
|
12
13
|
* `skills` must stay in `package.json` `files[]` or npm strips it and this
|
|
13
14
|
* resolver fails loudly — which is the intended signal, because a `dsh-kicad`
|
|
14
|
-
* without its
|
|
15
|
+
* without its skills is a broken delivery boundary.
|
|
16
|
+
*
|
|
17
|
+
* The override (`skillsDir` config / `DSH_KICAD_SKILLS_DIR`) is the **skills
|
|
18
|
+
* root**, i.e. the directory that *contains* the skill directories — not one
|
|
19
|
+
* skill's directory. One override therefore relocates the whole set, and the
|
|
20
|
+
* per-skill layout below it is unchanged.
|
|
15
21
|
*
|
|
16
22
|
* @module
|
|
17
23
|
*/
|
|
@@ -19,19 +25,17 @@ import { existsSync } from 'node:fs'
|
|
|
19
25
|
import { dirname, join, resolve } from 'node:path'
|
|
20
26
|
import { fileURLToPath } from 'node:url'
|
|
21
27
|
|
|
22
|
-
import { KICAD_SKILL_NAME } from './scripts.js'
|
|
23
|
-
|
|
24
28
|
/**
|
|
25
|
-
* Absolute path of the bundled
|
|
29
|
+
* Absolute path of the directory holding every bundled skill.
|
|
26
30
|
*
|
|
27
31
|
* Resolution order: an explicit override (config / `DSH_KICAD_SKILLS_DIR`),
|
|
28
32
|
* then the package-relative location.
|
|
29
33
|
*
|
|
30
34
|
* @param moduleUrl - `import.meta.url` of the calling module.
|
|
31
|
-
* @param override - explicit
|
|
35
|
+
* @param override - explicit skills root (plugin config or env var).
|
|
32
36
|
* @returns the resolved directory, whether or not it exists yet.
|
|
33
37
|
*/
|
|
34
|
-
export function
|
|
38
|
+
export function skillsRoot(moduleUrl: string, override?: string): string {
|
|
35
39
|
if (override && override.trim().length > 0) return resolve(override)
|
|
36
40
|
const envOverride = process.env['DSH_KICAD_SKILLS_DIR']
|
|
37
41
|
if (envOverride && envOverride.trim().length > 0) return resolve(envOverride)
|
|
@@ -44,17 +48,29 @@ export function resolveSkillDir(moduleUrl: string, override?: string): string {
|
|
|
44
48
|
const here = dirname(fileURLToPath(moduleUrl))
|
|
45
49
|
// One level up is right for both `lib/` (built) and `src/` (vitest) because
|
|
46
50
|
// both sit directly under the package root next to `skills/`.
|
|
47
|
-
return resolve(here, '..', 'skills'
|
|
51
|
+
return resolve(here, '..', 'skills')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Absolute path of one bundled skill directory.
|
|
56
|
+
*
|
|
57
|
+
* @param moduleUrl - `import.meta.url` of the calling module.
|
|
58
|
+
* @param skillId - bundled skill id; MUST equal the directory name.
|
|
59
|
+
* @param override - explicit skills root (plugin config or env var).
|
|
60
|
+
* @returns the resolved directory, whether or not it exists yet.
|
|
61
|
+
*/
|
|
62
|
+
export function resolveSkillDir(moduleUrl: string, skillId: string, override?: string): string {
|
|
63
|
+
return join(skillsRoot(moduleUrl, override), skillId)
|
|
48
64
|
}
|
|
49
65
|
|
|
50
66
|
/**
|
|
51
|
-
* Resolve
|
|
67
|
+
* Resolve one skill directory and assert the skill is actually present.
|
|
52
68
|
*
|
|
53
69
|
* @throws when `SKILL.md` is missing — this is a packaging failure, not a
|
|
54
70
|
* runtime condition, so it must be loud rather than silently degraded.
|
|
55
71
|
*/
|
|
56
|
-
export function requireSkillDir(moduleUrl: string, override?: string): string {
|
|
57
|
-
const dir = resolveSkillDir(moduleUrl, override)
|
|
72
|
+
export function requireSkillDir(moduleUrl: string, skillId: string, override?: string): string {
|
|
73
|
+
const dir = resolveSkillDir(moduleUrl, skillId, override)
|
|
58
74
|
const skillFile = join(dir, 'SKILL.md')
|
|
59
75
|
if (!existsSync(skillFile)) {
|
|
60
76
|
throw new Error(
|
|
@@ -66,7 +82,7 @@ export function requireSkillDir(moduleUrl: string, override?: string): string {
|
|
|
66
82
|
return dir
|
|
67
83
|
}
|
|
68
84
|
|
|
69
|
-
/** Absolute path of the bundled Python script directory. */
|
|
85
|
+
/** Absolute path of the bundled Python script directory inside a skill. */
|
|
70
86
|
export function scriptsDir(skillDir: string): string {
|
|
71
87
|
return join(skillDir, 'scripts')
|
|
72
88
|
}
|
package/src/scripts.ts
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* Registry of the KiCad IPC script templates bundled with this package.
|
|
3
3
|
*
|
|
4
4
|
* The scripts are the migrated `kicad-agent` executable surface. They live
|
|
5
|
-
* under `skills
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* under `skills/<KICAD_SCRIPT_SKILL_ID>/scripts/` — the `kicad-ipc` skill (see
|
|
6
|
+
* `./skills.ts`, which owns the skill ids) — and are shipped verbatim: this
|
|
7
|
+
* module only describes them so the DSH tools and the skill stay in sync.
|
|
8
|
+
* Nothing here reimplements KiCad IPC: the scripts own it (see `./ipc.ts`).
|
|
8
9
|
*
|
|
9
10
|
* @module
|
|
10
11
|
*/
|
|
@@ -126,6 +127,3 @@ export function kicadScript(id: string): KicadScript {
|
|
|
126
127
|
}
|
|
127
128
|
return script
|
|
128
129
|
}
|
|
129
|
-
|
|
130
|
-
/** The canonical skill directory name shipped by this package. */
|
|
131
|
-
export const KICAD_SKILL_NAME = 'kicad-ipc'
|
package/src/skills.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry of the skills bundled with this package.
|
|
3
|
+
*
|
|
4
|
+
* The skills under `skills/` are the migrated `kicad-agent` knowledge, shipped
|
|
5
|
+
* verbatim (see `README.md` and `docs/dsh-kicad-migration.md`) — this module
|
|
6
|
+
* only *describes* them, so `apply()` can register each one with
|
|
7
|
+
* `ctx.skills.register()` without hardcoding paths or names.
|
|
8
|
+
*
|
|
9
|
+
* The registry is the single source of truth for three things that must not
|
|
10
|
+
* drift apart:
|
|
11
|
+
*
|
|
12
|
+
* - the DSH skill id, which MUST equal the directory name under `skills/`
|
|
13
|
+
* (the bundling tests assert this, because a mismatch silently produces a
|
|
14
|
+
* skill whose `resourceBase` points somewhere else);
|
|
15
|
+
* - the fallback catalogue description, used only when SKILL.md frontmatter
|
|
16
|
+
* has no parseable `description` (the shipped file always wins);
|
|
17
|
+
* - which skill owns the Python script templates the KiCad tools run.
|
|
18
|
+
*
|
|
19
|
+
* Adding a skill from `kicad-agent` means: copy the directory, add an entry
|
|
20
|
+
* here, and register it — nothing else in `src/` is skill-specific.
|
|
21
|
+
*
|
|
22
|
+
* @module
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** A skill this package bundles and registers at load time. */
|
|
26
|
+
export interface KicadBundledSkill {
|
|
27
|
+
/** DSH skill id — must match the directory name under `skills/`. */
|
|
28
|
+
readonly id: string
|
|
29
|
+
/**
|
|
30
|
+
* Agent-facing catalogue description, used ONLY when the bundled SKILL.md has
|
|
31
|
+
* no parseable `description` frontmatter. The shipped file is authoritative,
|
|
32
|
+
* so this cannot be the normal path — DSH ignores a skill without a
|
|
33
|
+
* description, so an empty value here would silently break discovery.
|
|
34
|
+
*/
|
|
35
|
+
readonly summary: string
|
|
36
|
+
/**
|
|
37
|
+
* Whether this skill owns `skills/<id>/scripts/` — the runnable Python
|
|
38
|
+
* templates that back the DSH tools. Exactly one bundled skill does; the
|
|
39
|
+
* tools resolve their interpreter scripts through it (`./paths.ts`).
|
|
40
|
+
*/
|
|
41
|
+
readonly ownsScripts: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The skill that owns the script templates behind the KiCad tools.
|
|
46
|
+
*
|
|
47
|
+
* The tools are the *executable* half of this skill: `kicad_pcb_*` shells out
|
|
48
|
+
* to `skills/kicad-ipc/scripts/*.py`. Kept as a named constant because the
|
|
49
|
+
* tools must keep resolving correctly even when that particular skill fails to
|
|
50
|
+
* load (they then fail per-call with a typed `FAILED_PRECONDITION` instead of
|
|
51
|
+
* silently pointing at another skill's directory).
|
|
52
|
+
*/
|
|
53
|
+
export const KICAD_SCRIPT_SKILL_ID = 'kicad-ipc'
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Every skill bundled with `@huaqiu/dsh-kicad`, keyed by id.
|
|
57
|
+
*
|
|
58
|
+
* Mirrors `skills/` 1:1 — the bundling test asserts that every entry exists on
|
|
59
|
+
* disk with a valid SKILL.md, and that no directory is left unregistered.
|
|
60
|
+
*/
|
|
61
|
+
export const KICAD_SKILLS: Readonly<Record<string, KicadBundledSkill>> = {
|
|
62
|
+
[KICAD_SCRIPT_SKILL_ID]: {
|
|
63
|
+
id: KICAD_SCRIPT_SKILL_ID,
|
|
64
|
+
summary:
|
|
65
|
+
'Operate a KiCad PCB through the official KiCad IPC API: inspect the live ' +
|
|
66
|
+
'board and create, modify or delete objects. Use for PCB automation and ' +
|
|
67
|
+
'autorouter-result import — not for editing .kicad_pcb files directly.',
|
|
68
|
+
ownsScripts: true,
|
|
69
|
+
},
|
|
70
|
+
'hardware-design-brief': {
|
|
71
|
+
id: 'hardware-design-brief',
|
|
72
|
+
summary:
|
|
73
|
+
'Turn a market need into verifiable hardware requirements: clarify them ' +
|
|
74
|
+
'through staged questioning and write the market MRD, the hardware PRD ' +
|
|
75
|
+
'and the schematic/PCB design inputs. Use when defining requirements for ' +
|
|
76
|
+
'a new board project.',
|
|
77
|
+
ownsScripts: false,
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The bundled skill ids, in registration order. */
|
|
82
|
+
export const KICAD_SKILL_IDS: readonly string[] = Object.keys(KICAD_SKILLS)
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Look up one bundled skill by id.
|
|
86
|
+
* @throws when the id is not part of the bundled set.
|
|
87
|
+
*/
|
|
88
|
+
export function kicadSkill(id: string): KicadBundledSkill {
|
|
89
|
+
const skill = KICAD_SKILLS[id]
|
|
90
|
+
if (!skill) {
|
|
91
|
+
throw new Error(`@huaqiu/dsh-kicad: unknown bundled skill "${id}"`)
|
|
92
|
+
}
|
|
93
|
+
return skill
|
|
94
|
+
}
|