copperhead 0.10.0 → 0.11.0

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.
Files changed (99) hide show
  1. package/README.md +41 -2
  2. package/dist/agent/context.js +2 -0
  3. package/dist/agent/context.js.map +1 -0
  4. package/dist/agent/dock-renderer.js +2 -2
  5. package/dist/agent/dock-renderer.js.map +1 -1
  6. package/dist/agent/envelope.js +105 -0
  7. package/dist/agent/envelope.js.map +1 -0
  8. package/dist/agent/loop.js +34 -14
  9. package/dist/agent/loop.js.map +1 -1
  10. package/dist/agent/providers/claude-code.js +17 -1
  11. package/dist/agent/providers/claude-code.js.map +1 -1
  12. package/dist/agent/providers/codex.js +84 -39
  13. package/dist/agent/providers/codex.js.map +1 -1
  14. package/dist/agent/recovery.js +91 -14
  15. package/dist/agent/recovery.js.map +1 -1
  16. package/dist/agent/registry.js +49 -0
  17. package/dist/agent/registry.js.map +1 -0
  18. package/dist/agent/render.js +2 -2
  19. package/dist/agent/render.js.map +1 -1
  20. package/dist/agent/theme.js +10 -5
  21. package/dist/agent/theme.js.map +1 -1
  22. package/dist/agent/tools.js +99 -769
  23. package/dist/agent/tools.js.map +1 -1
  24. package/dist/capabilities/define.js +35 -0
  25. package/dist/capabilities/define.js.map +1 -0
  26. package/dist/capabilities/handlers.js +744 -0
  27. package/dist/capabilities/handlers.js.map +1 -0
  28. package/dist/capabilities/helpers.js +39 -0
  29. package/dist/capabilities/helpers.js.map +1 -0
  30. package/dist/capabilities/index.js +50 -0
  31. package/dist/capabilities/index.js.map +1 -0
  32. package/dist/capabilities/skills/generate-report.js +23 -0
  33. package/dist/capabilities/skills/generate-report.js.map +1 -0
  34. package/dist/cli.js +84 -1
  35. package/dist/cli.js.map +1 -1
  36. package/dist/commands/create.js +5 -2
  37. package/dist/commands/create.js.map +1 -1
  38. package/dist/commands/doctor.js +33 -3
  39. package/dist/commands/doctor.js.map +1 -1
  40. package/dist/commands/skill.js +109 -0
  41. package/dist/commands/skill.js.map +1 -0
  42. package/dist/commands/sync.js +3 -1
  43. package/dist/commands/sync.js.map +1 -1
  44. package/dist/config.js +18 -6
  45. package/dist/config.js.map +1 -1
  46. package/dist/kicad/cli.js +106 -18
  47. package/dist/kicad/cli.js.map +1 -1
  48. package/dist/kicad/draft/draft.js +3 -0
  49. package/dist/kicad/draft/draft.js.map +1 -1
  50. package/dist/kicad/draft/engine.js +3139 -218
  51. package/dist/kicad/draft/engine.js.map +1 -1
  52. package/dist/kicad/draft/symsource.js +24 -10
  53. package/dist/kicad/draft/symsource.js.map +1 -1
  54. package/dist/kicad/emit.js +45 -6
  55. package/dist/kicad/emit.js.map +1 -1
  56. package/dist/kicad/legibility.js +51 -4
  57. package/dist/kicad/legibility.js.map +1 -1
  58. package/dist/kicad/score.js +173 -3
  59. package/dist/kicad/score.js.map +1 -1
  60. package/dist/kicad/sexp.js +32 -6
  61. package/dist/kicad/sexp.js.map +1 -1
  62. package/dist/mcp/server.js +485 -0
  63. package/dist/mcp/server.js.map +1 -0
  64. package/dist/memory/scaffold.js +8 -1
  65. package/dist/memory/scaffold.js.map +1 -1
  66. package/package.json +5 -2
  67. package/src/agent/context.ts +35 -0
  68. package/src/agent/dock-renderer.ts +3 -2
  69. package/src/agent/envelope.ts +124 -0
  70. package/src/agent/loop.ts +45 -17
  71. package/src/agent/providers/claude-code.ts +22 -1
  72. package/src/agent/providers/codex.ts +91 -42
  73. package/src/agent/recovery.ts +89 -12
  74. package/src/agent/registry.ts +58 -0
  75. package/src/agent/render.ts +4 -3
  76. package/src/agent/theme.ts +15 -5
  77. package/src/agent/tools.ts +124 -816
  78. package/src/agent/types.ts +10 -5
  79. package/src/capabilities/define.ts +88 -0
  80. package/src/capabilities/handlers.ts +769 -0
  81. package/src/capabilities/helpers.ts +37 -0
  82. package/src/capabilities/index.ts +53 -0
  83. package/src/capabilities/skills/generate-report.ts +25 -0
  84. package/src/cli.ts +84 -1
  85. package/src/commands/create.ts +5 -2
  86. package/src/commands/doctor.ts +34 -3
  87. package/src/commands/skill.ts +127 -0
  88. package/src/commands/sync.ts +5 -3
  89. package/src/config.ts +32 -8
  90. package/src/kicad/cli.ts +129 -18
  91. package/src/kicad/draft/draft.ts +2 -0
  92. package/src/kicad/draft/engine.ts +3034 -226
  93. package/src/kicad/draft/symsource.ts +24 -10
  94. package/src/kicad/emit.ts +71 -7
  95. package/src/kicad/legibility.ts +55 -6
  96. package/src/kicad/score.ts +187 -8
  97. package/src/kicad/sexp.ts +37 -6
  98. package/src/mcp/server.ts +560 -0
  99. package/src/memory/scaffold.ts +8 -1
package/src/kicad/cli.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execa, ExecaError } from 'execa';
2
- import { existsSync } from 'node:fs';
2
+ import { existsSync, readdirSync } from 'node:fs';
3
3
  import { mkdtemp, readFile, rm } from 'node:fs/promises';
4
4
  import { tmpdir } from 'node:os';
5
5
  import path from 'node:path';
@@ -7,14 +7,29 @@ import { normalizeReport, type CheckReport } from './report.js';
7
7
  import { PreflightError, isNotFoundError } from '../util/preflight.js';
8
8
 
9
9
  export class KicadCliMissingError extends PreflightError {
10
- constructor() {
10
+ constructor(platform = process.platform) {
11
+ const isWin = platform === 'win32';
12
+ const isMac = platform === 'darwin';
13
+
14
+ const locationHint = isWin
15
+ ? 'ensure kicad-cli.exe is on PATH (typically in C:\\Program Files\\KiCad\\<version>\\bin)'
16
+ : isMac
17
+ ? 'ensure the kicad-cli binary is on PATH (on macOS it ships inside KiCad.app/Contents/MacOS)'
18
+ : 'ensure the kicad-cli binary is on PATH (typically /usr/bin/kicad-cli)';
19
+
20
+ const envHint = isWin
21
+ ? 'or set COPPERHEAD_KICAD_CLI="C:\\Program Files\\KiCad\\10.0\\bin\\kicad-cli.exe"'
22
+ : isMac
23
+ ? 'or set COPPERHEAD_KICAD_CLI=/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli'
24
+ : 'or set COPPERHEAD_KICAD_CLI=/usr/bin/kicad-cli';
25
+
11
26
  super(
12
27
  'kicad-cli not found on PATH',
13
28
  'copperhead verifies every mutation with kicad-cli ERC/DRC; without it no edit can be checked, so no run can start',
14
29
  [
15
30
  'install KiCad ≥ 8: https://www.kicad.org/download/',
16
- 'ensure the kicad-cli binary is on PATH (on macOS it ships inside KiCad.app/Contents/MacOS)',
17
- 'or set COPPERHEAD_KICAD_CLI=/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli',
31
+ locationHint,
32
+ envHint,
18
33
  'confirm with "kicad-cli version", then rerun',
19
34
  ],
20
35
  );
@@ -28,15 +43,32 @@ export class KicadCliMissingError extends PreflightError {
28
43
  * seen and rejected, so telling the user to set it would be nonsense.
29
44
  */
30
45
  export class KicadCliBadOverrideError extends PreflightError {
31
- constructor(configured: string) {
46
+ constructor(configured: string, platform = process.platform) {
47
+ const isWin = platform === 'win32';
48
+ const isMac = platform === 'darwin';
49
+
50
+ const checkHint = isWin
51
+ ? `check the path in PowerShell: Test-Path "${configured}"`
52
+ : `check the path: ls -l "${configured}"`;
53
+
54
+ const locationHint = isWin
55
+ ? 'on Windows the binary typically lives at C:\\Program Files\\KiCad\\<version>\\bin\\kicad-cli.exe'
56
+ : isMac
57
+ ? 'on macOS the binary lives at /Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli'
58
+ : 'on Linux the binary typically lives at /usr/bin/kicad-cli';
59
+
60
+ const confirmHint = isWin
61
+ ? 'confirm with "& $env:COPPERHEAD_KICAD_CLI version", then rerun'
62
+ : 'confirm with "$COPPERHEAD_KICAD_CLI" version, then rerun';
63
+
32
64
  super(
33
65
  `COPPERHEAD_KICAD_CLI points to a path that does not exist: ${configured}`,
34
66
  'the override wins over PATH, so falling back silently would run a different binary than the one you named and make the failure impossible to diagnose',
35
67
  [
36
- `check the path: ls -l "${configured}"`,
37
- 'on macOS the binary lives at /Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli',
68
+ checkHint,
69
+ locationHint,
38
70
  'or unset COPPERHEAD_KICAD_CLI to fall back to PATH',
39
- 'confirm with "$COPPERHEAD_KICAD_CLI version", then rerun',
71
+ confirmHint,
40
72
  ],
41
73
  );
42
74
  this.name = 'KicadCliBadOverrideError';
@@ -44,20 +76,89 @@ export class KicadCliBadOverrideError extends PreflightError {
44
76
  }
45
77
 
46
78
  /** Well-known install locations when `kicad-cli` is not on PATH (macOS app bundle). */
47
- const FALLBACK_BINARIES = [
79
+ const MACOS_FALLBACK_BINARIES = [
48
80
  '/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli',
49
81
  '/Applications/KiCad-10.0/KiCad.app/Contents/MacOS/kicad-cli',
50
82
  '/Applications/KiCad-9.0/KiCad.app/Contents/MacOS/kicad-cli',
51
83
  '/Applications/KiCad-8.0/KiCad.app/Contents/MacOS/kicad-cli',
52
84
  ];
53
85
 
54
- let fallbackBinaries: readonly string[] = FALLBACK_BINARIES;
86
+ /**
87
+ * Minimum KiCad major version accepted for ERC/DRC and candidate discovery.
88
+ * Re-used by doctor.ts to ensure CLI discovery and environment preflight
89
+ * enforce the same version floor.
90
+ */
91
+ export const MIN_KICAD_MAJOR = 8;
92
+
93
+ const DEFAULT_WIN_ROOTS = [
94
+ process.env.ProgramFiles ? path.join(process.env.ProgramFiles, 'KiCad') : 'C:/Program Files/KiCad',
95
+ 'C:/Program Files/KiCad',
96
+ 'C:/Program Files (x86)/KiCad',
97
+ ];
98
+
99
+ /**
100
+ * Return default fallback candidate paths for macOS and Windows standard KiCad installations.
101
+ * On Windows, version-numbered directories (e.g. 10.0, 9.0, 8.0) under the KiCad root
102
+ * are probed in descending numerical order, mirroring symlib.ts.
103
+ */
104
+ export function defaultFallbackBinaries(winRoots: readonly string[] = DEFAULT_WIN_ROOTS): string[] {
105
+ const candidates: string[] = [...MACOS_FALLBACK_BINARIES];
106
+ const seenRoots = new Set<string>();
107
+
108
+ for (const winRoot of winRoots) {
109
+ const normalized = path.normalize(winRoot).toLowerCase();
110
+ if (seenRoots.has(normalized)) continue;
111
+ seenRoots.add(normalized);
112
+
113
+ if (!existsSync(winRoot)) continue;
114
+ try {
115
+ const entries = readdirSync(winRoot, { withFileTypes: true });
116
+ const versions = entries
117
+ .filter((e) => e.isDirectory())
118
+ .map((e) => e.name)
119
+ // Only include versions that meet the minimum supported KiCad major.
120
+ // This prevents a stale KiCad 7.x install from silently becoming the
121
+ // ERC/DRC binary while doctor correctly reports FAIL for it.
122
+ .filter((name) => {
123
+ const major = Number(name.split('.')[0]);
124
+ return Number.isFinite(major) && major >= MIN_KICAD_MAJOR;
125
+ })
126
+ .sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
127
+
128
+ for (const version of versions) {
129
+ candidates.push(
130
+ path.join(winRoot, version, 'bin', 'kicad-cli.exe'),
131
+ path.join(winRoot, version, 'bin', 'kicad-cli.cmd'),
132
+ path.join(winRoot, version, 'bin', 'kicad-cli.bat'),
133
+ path.join(winRoot, version, 'bin', 'kicad-cli'),
134
+ );
135
+ }
136
+ } catch {
137
+ // not readable / not a directory; versioned candidates unavailable, but
138
+ // the unversioned bin/ fallback below is still worth probing.
139
+ }
140
+ // Unversioned fallback: pushed unconditionally so a readdirSync failure
141
+ // (e.g. ENOTDIR) on the root itself doesn't also suppress this probe.
142
+ candidates.push(
143
+ path.join(winRoot, 'bin', 'kicad-cli.exe'),
144
+ path.join(winRoot, 'bin', 'kicad-cli.cmd'),
145
+ path.join(winRoot, 'bin', 'kicad-cli.bat'),
146
+ path.join(winRoot, 'bin', 'kicad-cli'),
147
+ );
148
+ }
149
+
150
+ return candidates;
151
+ }
152
+
153
+ let fallbackBinariesOverride: readonly string[] | null = null;
154
+ let fallbackWinRootsOverride: readonly string[] | null = null;
55
155
 
56
156
  let cachedBinary: string | null | undefined;
57
157
 
58
158
  /**
59
159
  * Resolve the kicad-cli executable: `COPPERHEAD_KICAD_CLI` > PATH name
60
- * (`kicad-cli`). On PATH miss, `runKicad` falls back to macOS KiCad.app paths.
160
+ * (`kicad-cli`). On PATH miss, `runKicad` falls back to macOS KiCad.app paths
161
+ * and Windows standard installation paths.
61
162
  */
62
163
  export function resolveKicadCli(): string {
63
164
  if (cachedBinary !== undefined) {
@@ -88,7 +189,7 @@ function envOverride(): string | null {
88
189
  }
89
190
 
90
191
  /**
91
- * After ENOENT on PATH, retry with a known macOS install path.
192
+ * After ENOENT on PATH, retry with known install paths (macOS app bundles, Windows standard paths).
92
193
  *
93
194
  * Deliberately does not re-read `COPPERHEAD_KICAD_CLI`: this is reached only
94
195
  * when resolveKicadCli() cached the bare PATH name, which in turn happens only
@@ -96,7 +197,9 @@ function envOverride(): string | null {
96
197
  * override re-check here could never fire.
97
198
  */
98
199
  function fallbackAfterMissing(): string {
99
- for (const candidate of fallbackBinaries) {
200
+ const candidates =
201
+ fallbackBinariesOverride ?? defaultFallbackBinaries(fallbackWinRootsOverride ?? undefined);
202
+ for (const candidate of candidates) {
100
203
  if (existsSync(candidate)) {
101
204
  cachedBinary = candidate;
102
205
  return candidate;
@@ -133,13 +236,21 @@ export function resetKicadCliCache(): void {
133
236
  }
134
237
 
135
238
  /**
136
- * Test helper: point the app-bundle probe at fixture paths, or call with no
137
- * argument to restore the real list. Without this the fallback branch is only
138
- * exercisable on a macOS host that happens to have KiCad installed, which
139
- * makes the outcome depend on the developer's machine.
239
+ * Test helper: point the app-bundle / binary probe at fixture paths, or call with no
240
+ * argument to restore the default dynamic list.
140
241
  */
141
242
  export function setKicadFallbackBinaries(paths?: readonly string[]): void {
142
- fallbackBinaries = paths ?? FALLBACK_BINARIES;
243
+ fallbackBinariesOverride = paths ?? null;
244
+ fallbackWinRootsOverride = null;
245
+ }
246
+
247
+ /**
248
+ * Test helper: point the Windows installation discovery probe at fixture roots, or call
249
+ * with no argument to restore the default search roots.
250
+ */
251
+ export function setKicadFallbackWinRoots(roots?: readonly string[]): void {
252
+ fallbackWinRootsOverride = roots ?? null;
253
+ fallbackBinariesOverride = null;
143
254
  }
144
255
 
145
256
  export async function kicadCliVersion(): Promise<string> {
@@ -166,6 +166,8 @@ export function formatSchematicDraftReport(report: SchematicDraftReport): string
166
166
  ` net classes: ${report.netClasses.map((n) => `${n.name}=${n.class}${mark(n)}`).join(', ')}${legend.length ? ` (${legend.join(', ')})` : ''}`,
167
167
  );
168
168
  if (report.pwrFlags.length) lines.push(` PWR_FLAG synthesized on: ${report.pwrFlags.join(', ')}`);
169
+ const sf = report.sheetFit as SchematicDraftReport['sheetFit'] | undefined;
170
+ if (sf) lines.push(` sheet fit: ${sf.paper}, ink ${Math.round(sf.inkUtilization * 100)}% of the usable frame, compaction ${sf.compaction}`);
169
171
  for (const n of report.notes) lines.push(` note: ${n}`);
170
172
  return lines.join('\n');
171
173
  }