traceforge-pcb 1.0.0__py3-none-any.whl

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 (219) hide show
  1. traceforge/__init__.py +3 -0
  2. traceforge/__main__.py +3 -0
  3. traceforge/acp_server.py +570 -0
  4. traceforge/agent.py +680 -0
  5. traceforge/altium/__init__.py +6 -0
  6. traceforge/altium/cfb.py +70 -0
  7. traceforge/altium/cli.py +60 -0
  8. traceforge/altium/model.py +163 -0
  9. traceforge/altium/outputs.py +24 -0
  10. traceforge/altium/pcb.py +301 -0
  11. traceforge/altium/project.py +328 -0
  12. traceforge/altium/schematic.py +400 -0
  13. traceforge/altium/tools.py +166 -0
  14. traceforge/approval.py +219 -0
  15. traceforge/attachments.py +346 -0
  16. traceforge/background.py +253 -0
  17. traceforge/bench/__init__.py +5 -0
  18. traceforge/bench/adapters.py +337 -0
  19. traceforge/bench/checks.py +410 -0
  20. traceforge/bench/cli.py +121 -0
  21. traceforge/bench/design.py +439 -0
  22. traceforge/bench/kicad.py +99 -0
  23. traceforge/bench/partscheck.py +159 -0
  24. traceforge/bench/review.py +100 -0
  25. traceforge/bench/runner.py +226 -0
  26. traceforge/bench/score.py +204 -0
  27. traceforge/bench/tasks.py +102 -0
  28. traceforge/chat.py +1185 -0
  29. traceforge/checks.py +328 -0
  30. traceforge/checks_sides.py +118 -0
  31. traceforge/cli.py +2224 -0
  32. traceforge/clipboard.py +107 -0
  33. traceforge/config.py +299 -0
  34. traceforge/context.py +276 -0
  35. traceforge/design/__init__.py +1 -0
  36. traceforge/design/constraints.py +397 -0
  37. traceforge/design/generate.py +1789 -0
  38. traceforge/design/outline.py +905 -0
  39. traceforge/design/schematic_layout.py +932 -0
  40. traceforge/design/schematic_lint.py +329 -0
  41. traceforge/design/schematic_route.py +207 -0
  42. traceforge/design/sheets.py +854 -0
  43. traceforge/design/sides.py +328 -0
  44. traceforge/design/spec.py +462 -0
  45. traceforge/design/specsheet.py +2351 -0
  46. traceforge/design/specsheet_render.py +183 -0
  47. traceforge/desktop/__init__.py +109 -0
  48. traceforge/desktop/keys.py +90 -0
  49. traceforge/desktop/project.py +443 -0
  50. traceforge/desktop/review.py +20 -0
  51. traceforge/desktop/server.py +1277 -0
  52. traceforge/desktop/static/app.css +223 -0
  53. traceforge/desktop/static/app.js +683 -0
  54. traceforge/desktop/static/highlight.js +77 -0
  55. traceforge/desktop/static/index.html +89 -0
  56. traceforge/desktop/static/md.js +90 -0
  57. traceforge/desktop/static/viewer.js +213 -0
  58. traceforge/desktop/symbols.py +212 -0
  59. traceforge/desktop/update.py +65 -0
  60. traceforge/diagnostics.py +67 -0
  61. traceforge/errata.py +219 -0
  62. traceforge/fab.py +108 -0
  63. traceforge/fab_layers.py +44 -0
  64. traceforge/features/__init__.py +6 -0
  65. traceforge/features/candidates.py +117 -0
  66. traceforge/features/cli.py +200 -0
  67. traceforge/features/package.py +433 -0
  68. traceforge/features/revdiff.py +224 -0
  69. traceforge/features/sarif.py +145 -0
  70. traceforge/features/viewer.py +265 -0
  71. traceforge/firmware/__init__.py +3 -0
  72. traceforge/firmware/cli.py +174 -0
  73. traceforge/firmware/drivers.py +372 -0
  74. traceforge/firmware/drivers_data.py +256 -0
  75. traceforge/firmware/mcus.py +177 -0
  76. traceforge/firmware/netlist.py +55 -0
  77. traceforge/firmware/pinmap.py +296 -0
  78. traceforge/firmware/scaffold.py +466 -0
  79. traceforge/firmware/toolchain.py +262 -0
  80. traceforge/hooks.py +237 -0
  81. traceforge/kicad/__init__.py +0 -0
  82. traceforge/kicad/cli.py +519 -0
  83. traceforge/kicad/diffpair.py +174 -0
  84. traceforge/kicad/edgegeom.py +191 -0
  85. traceforge/kicad/edit.py +405 -0
  86. traceforge/kicad/escape.py +1410 -0
  87. traceforge/kicad/formats.py +1073 -0
  88. traceforge/kicad/impedance.py +320 -0
  89. traceforge/kicad/install.py +75 -0
  90. traceforge/kicad/libraries.py +490 -0
  91. traceforge/kicad/models3d.py +942 -0
  92. traceforge/kicad/outputs.py +81 -0
  93. traceforge/kicad/pcb.py +787 -0
  94. traceforge/kicad/planes.py +1242 -0
  95. traceforge/kicad/project.py +136 -0
  96. traceforge/kicad/route.py +2698 -0
  97. traceforge/kicad/routegeom.py +164 -0
  98. traceforge/kicad/schematic.py +268 -0
  99. traceforge/kicad/sides.py +34 -0
  100. traceforge/kicad/stackup.py +424 -0
  101. traceforge/kicad_plugin/__init__.py +12 -0
  102. traceforge/kicad_plugin/actions.py +92 -0
  103. traceforge/kicad_plugin/cli.py +66 -0
  104. traceforge/kicad_plugin/client.py +86 -0
  105. traceforge/kicad_plugin/context.py +257 -0
  106. traceforge/kicad_plugin/discovery.py +81 -0
  107. traceforge/kicad_plugin/install.py +203 -0
  108. traceforge/kicad_plugin/overlay.py +60 -0
  109. traceforge/kicad_plugin/payload/action_plugin.py +65 -0
  110. traceforge/kicad_plugin/payload/ipc_main.py +42 -0
  111. traceforge/kicad_plugin/payload/plugin.json +90 -0
  112. traceforge/kicad_plugin/payload/requirements.txt +1 -0
  113. traceforge/kicad_plugin/views.py +202 -0
  114. traceforge/langchain.py +57 -0
  115. traceforge/local.py +580 -0
  116. traceforge/mcp_server.py +304 -0
  117. traceforge/models.py +310 -0
  118. traceforge/next_steps.py +110 -0
  119. traceforge/parts/__init__.py +52 -0
  120. traceforge/parts/alternates.py +488 -0
  121. traceforge/parts/bom.py +423 -0
  122. traceforge/parts/brief.py +705 -0
  123. traceforge/parts/catalog.py +297 -0
  124. traceforge/parts/cli_sourcing.py +156 -0
  125. traceforge/parts/data/duty_rates.yaml +37 -0
  126. traceforge/parts/data/hs_codes.yaml +103 -0
  127. traceforge/parts/datasheet.py +278 -0
  128. traceforge/parts/fx.py +100 -0
  129. traceforge/parts/model.py +185 -0
  130. traceforge/parts/net.py +187 -0
  131. traceforge/parts/offers.py +46 -0
  132. traceforge/parts/packages.py +397 -0
  133. traceforge/parts/pinout.py +714 -0
  134. traceforge/parts/providers/__init__.py +17 -0
  135. traceforge/parts/providers/base.py +100 -0
  136. traceforge/parts/providers/jlc.py +364 -0
  137. traceforge/parts/providers/keyed.py +661 -0
  138. traceforge/parts/report.py +508 -0
  139. traceforge/parts/supply.py +330 -0
  140. traceforge/parts/verify.py +455 -0
  141. traceforge/parts/web.py +575 -0
  142. traceforge/plans.py +70 -0
  143. traceforge/preferences.py +127 -0
  144. traceforge/prices.py +130 -0
  145. traceforge/prices.yaml +28 -0
  146. traceforge/prompts/__init__.py +54 -0
  147. traceforge/prompts/system.md +103 -0
  148. traceforge/providers.py +764 -0
  149. traceforge/remote/__init__.py +33 -0
  150. traceforge/remote/client.py +422 -0
  151. traceforge/remote/oidc.py +240 -0
  152. traceforge/remote/orgs.py +510 -0
  153. traceforge/remote/server.py +690 -0
  154. traceforge/remote/store.py +100 -0
  155. traceforge/review_changes.py +187 -0
  156. traceforge/routing.py +99 -0
  157. traceforge/runtime.py +435 -0
  158. traceforge/sessions.py +324 -0
  159. traceforge/sexpr.py +279 -0
  160. traceforge/sim/__init__.py +17 -0
  161. traceforge/sim/acmath.py +234 -0
  162. traceforge/sim/circuit.py +499 -0
  163. traceforge/sim/cli.py +162 -0
  164. traceforge/sim/engine.py +172 -0
  165. traceforge/sim/ltspice.py +175 -0
  166. traceforge/sim/measure.py +123 -0
  167. traceforge/sim/models.py +76 -0
  168. traceforge/sim/project.py +218 -0
  169. traceforge/sim/rawfile.py +123 -0
  170. traceforge/sim/results.py +298 -0
  171. traceforge/sim/tune.py +172 -0
  172. traceforge/skill_commands.py +165 -0
  173. traceforge/skill_library/circuit-simulation/SKILL.md +30 -0
  174. traceforge/skill_library/compliance-review/SKILL.md +51 -0
  175. traceforge/skill_library/design-review/SKILL.md +47 -0
  176. traceforge/skill_library/errata-hunt/SKILL.md +30 -0
  177. traceforge/skill_library/firmware/SKILL.md +26 -0
  178. traceforge/skill_library/power-budget/SKILL.md +27 -0
  179. traceforge/skill_library/release-package/SKILL.md +29 -0
  180. traceforge/skill_library/sourcing-brief/SKILL.md +24 -0
  181. traceforge/skill_library/thermal-review/SKILL.md +15 -0
  182. traceforge/skills.py +131 -0
  183. traceforge/thermal.py +557 -0
  184. traceforge/tools/__init__.py +48 -0
  185. traceforge/tools/agent_tools.py +213 -0
  186. traceforge/tools/base.py +303 -0
  187. traceforge/tools/batch.py +155 -0
  188. traceforge/tools/connections.py +245 -0
  189. traceforge/tools/files.py +777 -0
  190. traceforge/tools/firmware_tools.py +218 -0
  191. traceforge/tools/kicad_tools.py +1072 -0
  192. traceforge/tools/models3d_tools.py +141 -0
  193. traceforge/tools/parts_tools.py +473 -0
  194. traceforge/tools/processes.py +246 -0
  195. traceforge/tools/review_tools.py +178 -0
  196. traceforge/tools/sim_tools.py +230 -0
  197. traceforge/tui.py +620 -0
  198. traceforge/updates.py +124 -0
  199. traceforge/validate/__init__.py +77 -0
  200. traceforge/validate/builtins.py +1091 -0
  201. traceforge/validate/circuits.py +1240 -0
  202. traceforge/validate/config.py +410 -0
  203. traceforge/validate/default.yaml +113 -0
  204. traceforge/validate/design.py +191 -0
  205. traceforge/validate/explain.py +112 -0
  206. traceforge/validate/grounding.py +169 -0
  207. traceforge/validate/ipc2221.py +350 -0
  208. traceforge/validate/multilayer.py +805 -0
  209. traceforge/validate/netmodel.py +261 -0
  210. traceforge/validate/partcheck.py +472 -0
  211. traceforge/validate/report.py +280 -0
  212. traceforge/validate/review.py +322 -0
  213. traceforge/validate/runner.py +662 -0
  214. traceforge/workspace.py +171 -0
  215. traceforge_pcb-1.0.0.dist-info/METADATA +463 -0
  216. traceforge_pcb-1.0.0.dist-info/RECORD +219 -0
  217. traceforge_pcb-1.0.0.dist-info/WHEEL +4 -0
  218. traceforge_pcb-1.0.0.dist-info/entry_points.txt +2 -0
  219. traceforge_pcb-1.0.0.dist-info/licenses/LICENSE +21 -0
traceforge/sim/tune.py ADDED
@@ -0,0 +1,172 @@
1
+ """Closed-loop tuning: change a component, rerun the simulator, repeat until a spec is met."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from collections.abc import Callable
7
+ from dataclasses import dataclass, field
8
+ from pathlib import Path
9
+
10
+ from traceforge.sim.circuit import Circuit, SimError, format_value
11
+ from traceforge.sim.engine import SimRun, run_ngspice
12
+ from traceforge.sim.measure import measure
13
+ from traceforge.sim.results import Result
14
+
15
+
16
+ @dataclass
17
+ class TuneStep:
18
+ iteration: int
19
+ value: float
20
+ measured: float
21
+ error_pct: float
22
+
23
+
24
+ @dataclass
25
+ class TuneResult:
26
+ converged: bool
27
+ parameter: str
28
+ quantity: str
29
+ target: float
30
+ tolerance: float
31
+ value: float
32
+ measured: float
33
+ steps: list[TuneStep] = field(default_factory=list)
34
+ circuit: Circuit | None = None
35
+ reason: str = ""
36
+
37
+ def to_dict(self) -> dict:
38
+ return {
39
+ "converged": self.converged,
40
+ "parameter": self.parameter,
41
+ "quantity": self.quantity,
42
+ "target": self.target,
43
+ "tolerance_pct": self.tolerance * 100,
44
+ "final_value": self.value,
45
+ "measured": self.measured,
46
+ "iterations": [
47
+ {
48
+ "n": s.iteration,
49
+ "value": s.value,
50
+ "measured": s.measured,
51
+ "error_pct": round(s.error_pct, 4),
52
+ }
53
+ for s in self.steps
54
+ ],
55
+ "reason": self.reason,
56
+ }
57
+
58
+
59
+ def parse_target(text: str) -> tuple[str, float]:
60
+ """``f3db(v(out))=500`` -> (quantity, 500.0); the value accepts SPICE suffixes."""
61
+ from traceforge.sim.circuit import parse_value
62
+
63
+ quantity, sep, goal = text.rpartition("=")
64
+ if not sep or not quantity.strip():
65
+ raise SimError("target must look like 'f3db(v(out))=500'")
66
+ return quantity.strip(), parse_value(goal)
67
+
68
+
69
+ def tune(
70
+ circuit: Circuit,
71
+ parameter: str,
72
+ target: float | str,
73
+ tolerance: float = 0.01,
74
+ *,
75
+ quantity: str | None = None,
76
+ analysis: object = None,
77
+ bounds: tuple[float, float] | None = None,
78
+ max_iter: int = 25,
79
+ workdir: str | Path = ".",
80
+ meas: list[str] | None = None,
81
+ runner: Callable[..., SimRun] = run_ngspice,
82
+ ) -> TuneResult:
83
+ """Adjust ``parameter`` (``R1``, a tied group ``R1,R2`` or ``param:NAME``) until
84
+ ``quantity`` (see ``measure``) equals ``target`` within ``tolerance`` (relative).
85
+
86
+ Secant steps in the log domain, falling back to bisection once the target is bracketed.
87
+ The passed circuit is not changed; the tuned copy is in ``TuneResult.circuit``.
88
+ """
89
+ if isinstance(target, str):
90
+ quantity, target = parse_target(target)
91
+ if not quantity:
92
+ raise SimError("tune needs a quantity to measure, e.g. 'f3db(v(out))'")
93
+ if analysis is None:
94
+ raise SimError("tune needs an analysis, e.g. '.ac dec 40 10 100k'")
95
+ work = Path(workdir)
96
+ first = parameter.split(",")[0].strip()
97
+ x0 = circuit.number(first)
98
+ if x0 <= 0:
99
+ raise SimError(f"{first} must start above zero to be tuned")
100
+ lo, hi = bounds or (x0 / 1000, x0 * 1000)
101
+ steps: list[TuneStep] = []
102
+ best: tuple[float, float, Circuit] | None = None
103
+
104
+ def evaluate(x: float) -> tuple[float, Circuit]:
105
+ trial = circuit.copy()
106
+ trial.set_value(parameter, x)
107
+ run = runner(trial, analysis, work / f"iter{len(steps) + 1:02d}", meas=meas)
108
+ value = measure(Result(run), quantity)
109
+ err = (value - target) / abs(target) if target else value
110
+ steps.append(TuneStep(len(steps) + 1, x, value, 100 * err))
111
+ return value, trial
112
+
113
+ def g(v: float) -> float:
114
+ return math.log(v) if v > 0 and target > 0 else v
115
+
116
+ def goal() -> float:
117
+ return g(target)
118
+
119
+ pts: list[tuple[float, float]] = [] # (x, measured)
120
+ below: tuple[float, float] | None = None
121
+ above: tuple[float, float] | None = None
122
+ x = min(max(x0, lo), hi)
123
+ reason = "iteration limit reached"
124
+ for _ in range(max_iter):
125
+ value, trial = evaluate(x)
126
+ err = abs(value - target) / abs(target) if target else abs(value)
127
+ if best is None or err < best[0]:
128
+ best = (err, x, trial)
129
+ if err <= tolerance:
130
+ reason = "target met"
131
+ break
132
+ pts.append((x, value))
133
+ if value < target:
134
+ if below is None or value > below[1]:
135
+ below = (x, value)
136
+ elif above is None or value < above[1]:
137
+ above = (x, value)
138
+ nxt = None
139
+ if len(pts) >= 2:
140
+ (xa, ya), (xb, yb) = pts[-2], pts[-1]
141
+ if xa != xb and g(ya) != g(yb):
142
+ slope = (g(yb) - g(ya)) / (math.log(xb) - math.log(xa))
143
+ nxt = math.exp(math.log(xb) + (goal() - g(yb)) / slope)
144
+ if below and above: # bracketed: stay inside it, bisect when the secant leaves it
145
+ a, b = sorted([below[0], above[0]])
146
+ if nxt is None or not (a < nxt < b):
147
+ nxt = math.sqrt(a * b)
148
+ elif nxt is None or nxt <= 0:
149
+ nxt = x * 1.25 # first move, or a flat response
150
+ nxt = min(max(nxt, lo), hi)
151
+ if len(pts) >= 2 and math.isclose(nxt, x, rel_tol=1e-12):
152
+ reason = "stopped at the parameter bound"
153
+ break
154
+ x = nxt
155
+ assert best is not None
156
+ last = steps[-1]
157
+ converged = reason == "target met"
158
+ winner = best[2] if not converged else trial
159
+ return TuneResult(
160
+ converged,
161
+ parameter,
162
+ quantity,
163
+ target,
164
+ tolerance,
165
+ last.value if converged else best[1],
166
+ last.measured if converged else next(s.measured for s in steps if s.value == best[1]),
167
+ steps,
168
+ winner,
169
+ reason
170
+ if converged
171
+ else f"{reason}; best {format_value(best[1])} ({best[0] * 100:.2f} % off)",
172
+ )
@@ -0,0 +1,165 @@
1
+ """Skills: reusable task instructions the user invokes as ``/<name>`` in a session.
2
+
3
+ A skill is a folder holding a ``SKILL.md``: YAML front matter, then Markdown
4
+ instructions. Project skills live in ``.traceforge/skills/<name>/SKILL.md`` and
5
+ user skills in ``~/.config/traceforge/skills/<name>/SKILL.md`` (a project skill
6
+ wins over a user skill of the same name)::
7
+
8
+ ---
9
+ description: Prepare a JLCPCB order (BOM, CPL, gerbers) and check part stock
10
+ mode: agent # optional: agent | plan | ask
11
+ arguments: "[board variant]" # optional hint shown in the command list
12
+ ---
13
+ # JLCPCB release
14
+
15
+ 1. Run kicad_drc and stop if it reports errors.
16
+ 2. ...
17
+
18
+ ``/jlcpcb-release rev B`` sends the instructions with "rev B" as the task. Helper
19
+ scripts can sit next to ``SKILL.md``; the agent runs them with its normal tools.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import re
25
+ from dataclasses import dataclass
26
+ from pathlib import Path
27
+
28
+ import yaml
29
+
30
+ from traceforge.config import config_home
31
+
32
+ MAX_SKILLS = 64
33
+ MAX_SKILL_BYTES = 64 * 1024
34
+ NAME = re.compile(r"^[a-z0-9][a-z0-9_-]{0,63}$")
35
+ FRONT_MATTER = re.compile(r"^---\r?\n([\s\S]*?)\r?\n---\r?\n?")
36
+ SKILL_TEMPLATE = """---
37
+ description: {description}
38
+ mode: agent
39
+ arguments: "[what to apply it to]"
40
+ ---
41
+ # {title}
42
+
43
+ Describe the procedure step by step. Name the tools and checks to use, the files to
44
+ produce, and what "done" means, for example:
45
+
46
+ 1. Call kicad_project and read the parts of the design this skill is about.
47
+ 2. ...
48
+ 3. Run kicad_erc and kicad_drc and fix what they report.
49
+ 4. Summarize what changed and what was verified.
50
+ """
51
+
52
+
53
+ @dataclass(frozen=True)
54
+ class Skill:
55
+ name: str
56
+ description: str
57
+ instructions: str
58
+ path: Path
59
+ source: str # project | user
60
+ mode: str | None = None
61
+ arguments: str = ""
62
+ title: str = ""
63
+
64
+ def prompt(self, task: str) -> str:
65
+ folder = self.path.parent
66
+ return (
67
+ f'<skill name="{self.name}" folder="{folder}">\n{self.instructions.strip()}\n</skill>\n\n'
68
+ "Follow the skill above. Helper files it mentions are in its folder.\n\n"
69
+ f"Task: {task.strip() or 'Apply this skill to the current project.'}"
70
+ )
71
+
72
+
73
+ def skill_dirs(workspace: Path) -> list[tuple[str, Path]]:
74
+ return [
75
+ ("project", Path(workspace) / ".traceforge" / "skills"),
76
+ ("user", config_home() / "skills"),
77
+ ]
78
+
79
+
80
+ def _load_one(folder: Path, source: str) -> tuple[Skill | None, str | None]:
81
+ name = folder.name.lower()
82
+ file = folder / "SKILL.md"
83
+ if not NAME.fullmatch(name) or not file.is_file():
84
+ return (
85
+ None,
86
+ f"ignored {source} skill folder {folder.name!r} (needs a valid name and SKILL.md)",
87
+ )
88
+ if file.stat().st_size > MAX_SKILL_BYTES:
89
+ return None, f"ignored {source} skill {name!r}: SKILL.md is over 64 KB"
90
+ text = file.read_text(encoding="utf-8", errors="replace")
91
+ meta: dict = {}
92
+ match = FRONT_MATTER.match(text)
93
+ if match:
94
+ try:
95
+ loaded = yaml.safe_load(match.group(1)) or {}
96
+ except yaml.YAMLError:
97
+ return None, f"ignored {source} skill {name!r}: invalid YAML front matter"
98
+ meta = loaded if isinstance(loaded, dict) else {}
99
+ body = text[match.end() :].strip()
100
+ else:
101
+ body = text.strip()
102
+ if not body:
103
+ return None, f"ignored {source} skill {name!r}: no instructions"
104
+ heading = re.search(r"^#\s+(.+)$", body, re.MULTILINE)
105
+ description = str(meta.get("description") or "").strip()
106
+ if not description:
107
+ paragraph = next(
108
+ (p.strip() for p in re.split(r"\n\s*\n", body) if p.strip() and not p.startswith("#")),
109
+ "",
110
+ )
111
+ description = re.sub(r"\s+", " ", paragraph)
112
+ mode = str(meta.get("mode") or "").strip().lower() or None
113
+ return (
114
+ Skill(
115
+ name=name,
116
+ description=description[:300] or "project skill",
117
+ instructions=body,
118
+ path=file,
119
+ source=source,
120
+ mode=mode if mode in {"agent", "plan", "ask"} else None,
121
+ arguments=str(meta.get("arguments") or "")[:80],
122
+ title=heading.group(1).strip() if heading else name.replace("-", " ").title(),
123
+ ),
124
+ None,
125
+ )
126
+
127
+
128
+ def load_skills(workspace: Path, reserved: set[str] | None = None) -> tuple[list[Skill], list[str]]:
129
+ """All skills for ``workspace`` plus diagnostics about folders that were skipped."""
130
+ found: dict[str, Skill] = {}
131
+ problems: list[str] = []
132
+ for source, root in skill_dirs(workspace):
133
+ if not root.is_dir():
134
+ continue
135
+ for folder in sorted(root.iterdir())[:MAX_SKILLS]:
136
+ if not folder.is_dir():
137
+ continue
138
+ skill, problem = _load_one(folder, source)
139
+ if problem:
140
+ problems.append(problem)
141
+ if skill is None or skill.name in found:
142
+ continue
143
+ if reserved and skill.name in reserved:
144
+ problems.append(f"skill {skill.name!r} has the name of a built-in command; renamed")
145
+ continue
146
+ found[skill.name] = skill
147
+ return sorted(found.values(), key=lambda s: s.name), problems
148
+
149
+
150
+ def create_skill(workspace: Path, name: str, description: str, *, user: bool = False) -> Path:
151
+ name = name.strip().lower()
152
+ if not NAME.fullmatch(name):
153
+ raise ValueError("skill names use lowercase letters, digits, - and _")
154
+ root = (config_home() / "skills") if user else (Path(workspace) / ".traceforge" / "skills")
155
+ folder = root / name
156
+ file = folder / "SKILL.md"
157
+ if file.exists():
158
+ raise FileExistsError(f"{file} already exists")
159
+ folder.mkdir(parents=True, exist_ok=True)
160
+ title = name.replace("-", " ").replace("_", " ").title()
161
+ file.write_text(
162
+ SKILL_TEMPLATE.format(description=description or f"{title} procedure", title=title),
163
+ encoding="utf-8",
164
+ )
165
+ return file
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: circuit-simulation
3
+ description: Simulate a KiCad circuit or a SPICE deck with ngspice (DC, AC, transient, noise, operating point), read -3 dB, poles and margins, tune a component to a spec, and export CSV, VCD, SVG or LTspice files.
4
+ ---
5
+
6
+ # Circuit simulation
7
+
8
+ Use this when the user wants to check or tune analogue behaviour: a filter cutoff, an LED
9
+ current, a step response, a rectifier, a regulator's output, noise.
10
+
11
+ 1. Build and run with `kicad_simulate`. For a schematic it takes connectivity from KiCad
12
+ and values from the symbols. Parts with `Sim.*` fields use them; LEDs, diodes,
13
+ op-amps, LDOs, MOSFETs and BJTs without a model get a generic stand-in that is listed in
14
+ `generic_models`. Parts with no model (connectors, MCUs) are listed in `skipped_parts`:
15
+ say so, because anything they drive is floating. A schematic has no power source, so
16
+ pass `sources` (for example `{"pos": "VBUS", "dc": 5}`). Ground nets (GND, GNDA, ...) map to 0.
17
+ 2. Pick the analysis: `.op` (currents and voltages; ask for `i(R5)` in `probes`), `.ac dec 40 10 100k`
18
+ (Bode, -3 dB, poles, second-order f0/Q), `.tran 10u 20m`, `.noise`, `.dc`. Add `.meas` lines
19
+ for numbers you need. For phase and gain margin give `loop_gain` (an AC signal that is the
20
+ loop gain); the poles-based phase margin is only an equivalent for a second-order system.
21
+ 3. To meet a spec use `sim_tune`: `parameter` "R1,R2" (tied) or "param:NAME", `target`
22
+ "f3db(v(out))=500", an AC analysis. It reports every iteration. Apply the result to the
23
+ schematic with `kicad_set_field`, then simulate again. Round to the nearest standard value
24
+ and re-simulate: the E-series value changes the answer.
25
+ 4. `export` gives csv, vcd (transient or DC) and svg; `sim_plot` re-plots a rawfile.
26
+ 5. `engine: "ltspice"` only writes a netlist and `.asc` for LTspice. LTspice does not run
27
+ here, and the `.asc` is untested in LTspice. ngspice cannot run LTspice-only models.
28
+
29
+ Report what is generic and what was skipped. Never present a result from a generic model as
30
+ the behaviour of the real part.
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: compliance-review
3
+ description: Compliance-oriented review aids - IPC-2221 conductor width and spacing, creepage and clearance by voltage class, and a DO-160 test category checklist. Guides and checklists, not certification.
4
+ ---
5
+
6
+ # Compliance review
7
+
8
+ These are review aids. They do not certify anything; the applicable standard and a test lab decide.
9
+
10
+ ## IPC-2221 (deterministic)
11
+ Declare currents and voltages in `.traceforge/electrical.yaml`:
12
+
13
+ delta_t: 10
14
+ copper_oz: {outer: 1, inner: 0.5}
15
+ coating: uncoated # uncoated | coated | conformal
16
+ nets:
17
+ VBUS: {current_a: 10, voltage_v: 5}
18
+ "MAINS_*": {voltage_v: 230}
19
+
20
+ or name net classes `Power_10A`, `HV_230V`. Run `kicad_ipc2221` (or the `pcb.ipc2221` validate check) to
21
+ find tracks narrower than IPC-2221 requires (I = k dT^0.44 A^0.725; 10 A, 10 K, 1 oz external needs about
22
+ 7.2 mm) and conductors closer than Table 6-1 allows for the voltage between them.
23
+ `kicad_ipc2221 width_for_a=3` and `voltage_v=230` answer lookups.
24
+
25
+ ## Creepage and clearance (IEC 60664-1 style guide, pollution degree 2)
26
+ Creepage by working voltage (rms), FR-4 (material group III, CTI 175-400): 50 V 1.2 mm, 100 V 1.4, 250 V 2.5,
27
+ 400 V 4.0, 630 V 6.3, 1000 V 10.0. Basic-insulation clearance, overvoltage category II: 150 V 0.5 mm, 300 V
28
+ 1.5, 600 V 3.0, 1000 V 5.5. Reinforced insulation doubles clearance; pollution degree 3 or altitude over
29
+ 2000 m needs the standard's multipliers. Use `kicad_ipc2221 voltage_v=...` for the numbers, and add slots
30
+ under mains-to-SELV barriers. Measure on the board (DRC clearance and a creepage path by hand); the tool
31
+ checks copper-to-copper distance only.
32
+
33
+ ## DO-160 test category checklist (airborne equipment part class)
34
+ For each section decide the category with the customer, record it, and note the design response. This is a
35
+ checklist, not certification: only an accredited test per the current DO-160 revision shows compliance.
36
+ - 4 Temperature and altitude: operating range, ground survival, short-time operating; part ratings, derating, thermal review.
37
+ - 5 Temperature variation: solder joints, large BGAs, conformal coat cracking.
38
+ - 6 Humidity: coating, creepage after moisture, connector sealing.
39
+ - 7 Operational shocks and crash safety: heavy parts retained, connector retention.
40
+ - 8 Vibration: mass and mounting, tall capacitors and magnetics bonded, crystal and relay placement.
41
+ - 9 Explosive atmosphere, 10 Waterproofness, 11 Fluids: enclosure and finish decisions.
42
+ - 12 Sand and dust, 13 Fungus, 14 Salt fog: coating and finish.
43
+ - 15 Magnetic effect: compass-safe distance.
44
+ - 16 Power input: voltage ranges, transients, brownout and interrupt behaviour; input protection, hold-up.
45
+ - 17 Voltage spike: TVS and clamp ratings on every power input.
46
+ - 18 Audio frequency conducted susceptibility: filter and regulator PSRR.
47
+ - 19 Induced signal susceptibility, 20 and 21 RF susceptibility and emissions: shielding, filtering, ground strategy, clock and switcher layout.
48
+ - 22 Lightning induced transients: protection on external lines, isolation.
49
+ - 23 Lightning direct effects, 24 Icing, 25 ESD (connectors and user-touch points, TVS at connectors).
50
+ - 26 Fire and flammability: PCB laminate and plastics ratings.
51
+ Record each category as "level required / design response / evidence (file, test)" in the review notes.
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: design-review
3
+ description: Structured pre-fabrication review of a KiCad project, with one read-only subagent per subsystem or part group and a findings list rated P0-P3 with evidence.
4
+ ---
5
+
6
+ # Design review
7
+
8
+ Use this when the user asks for a review, an audit or "is this ready to order?".
9
+
10
+ ## 1. Establish the facts first
11
+
12
+ 1. `kicad_project`, then `kicad_components` and `kicad_board`.
13
+ 2. Run the deterministic checks yourself: `kicad_erc`, `kicad_drc` (with parity) and,
14
+ where useful, `kicad_netlist`. Record what could not run and why.
15
+ 3. Read `TRACEFORGE.md` and `.traceforge/notes/` for the stated requirements.
16
+ 4. Write a todo list with `write_todos`: one item per stage below.
17
+
18
+ ## 2. Fan out the part-level checks
19
+
20
+ Group the parts by subsystem (power input and protection, regulators, MCU and its
21
+ support parts, each interface connector, indicators). Start one `task` call per group
22
+ with `subagent_type: explore`, all in the same response so they run in parallel. Give
23
+ each subagent:
24
+
25
+ - the references in its group, the schematic and board paths,
26
+ - what to verify: symbol pin mapping against the footprint pads (`kicad_symbol_pins`,
27
+ `kicad_footprint_pads`), footprint against the part number's package, values against
28
+ datasheet equations (show the arithmetic), ratings (voltage, current, power,
29
+ temperature), decoupling and pull-ups, and placement of the group on the board
30
+ (`kicad_board`, `kicad_netlist` for pin-level nets),
31
+ - the report format: one line per finding, `P0..P3 | reference | problem | evidence`,
32
+ then "no issues" lines for what was checked and found correct.
33
+
34
+ ## 3. Cross-cutting checks (yourself)
35
+
36
+ Power tree and sequencing, connector protection (ESD, reverse polarity, fuses),
37
+ schematic/board parity, routing of power and return paths, fab limits for the chosen
38
+ fab, silkscreen (reference designators visible, polarity marks), fiducials and
39
+ assembly data (part numbers on every placed part), and the release package.
40
+
41
+ ## 4. Report
42
+
43
+ Merge the subagent reports with your own findings, remove duplicates, and sort by
44
+ severity. Every finding needs evidence: file and location, a check result, a datasheet
45
+ section or a calculation. End with a verdict (ready / ready after fixes / not ready)
46
+ and the list of what was not verified. Save a short version with `save_note`
47
+ (name `review_<date>`), and update `design_summary` if you learned something durable.
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: errata-hunt
3
+ description: Find silicon errata, datasheet warnings and known design gotchas for the parts in a KiCad project before bring-up, and check the design against each one.
4
+ ---
5
+
6
+ # Errata hunt
7
+
8
+ Use this when the user asks about errata, known issues, "gotchas", or wants a part-level
9
+ risk check before ordering boards.
10
+
11
+ 1. `kicad_components` to list the parts. Skip passives and connectors unless the user
12
+ asks; focus on ICs, regulators, crystals/oscillators, modules and protection parts.
13
+ 2. Start with `errata_hunt` (part=MPN): it fetches the datasheet and errata documents, extracts
14
+ candidate items with page/section citations and severity, merges duplicates and ranks them.
15
+ Items with `verified: false` had no retrievable source; report them as unverified, never as fact.
16
+ Then, for each part (fan out with read-only subagents when there are more than five):
17
+ - `part_lookup` for the exact MPN and its datasheet URL; `datasheet_fetch` the
18
+ datasheet and look for "Errata", "Application information", "Layout guidelines",
19
+ absolute maximum ratings and footnotes.
20
+ - `web_search` for "<MPN> errata", "<MPN> silicon revision", "<MPN> known issues"
21
+ and the manufacturer's errata sheet; `web_fetch` the ones that match the part's
22
+ revision/date code where stated.
23
+ 3. Check each finding against the design: `kicad_symbol_pins`, `kicad_netlist`,
24
+ `kicad_board` (e.g. a required pull-up present, a forbidden pin state avoided, the
25
+ decoupling and layout rule followed, the affected silicon revision actually used).
26
+ 4. Report a table: part, issue, source (URL + section), affects this design (yes/no/
27
+ unknown with the evidence), fix. Rate each P0-P3. Save it with `save_note`.
28
+
29
+ Never claim an erratum without a source; say "no errata found" only after checking the
30
+ manufacturer's site.
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: firmware
3
+ description: Firmware that matches the hardware - pin map from the schematic nets, pin headers, project scaffolds (Arduino, PlatformIO, ESP-IDF, STM32, AVR), I2C/SPI driver snippets, and compiling with the real toolchain.
4
+ ---
5
+
6
+ # Firmware from the schematic
7
+
8
+ Use this when the user wants code for the board: pin definitions, a starter project, a
9
+ driver for a sensor, or a compile check.
10
+
11
+ 1. `firmware_pins` on the design (`.yaml`, `.kicad_sch` or `.net`). Read the warnings first:
12
+ strapping pins, flash/PSRAM pins, USB pins, RESET used as I/O, a net named like I2C on a
13
+ pin that has no I2C. Report them before writing code; fix the schematic if a pin is wrong.
14
+ 2. Check the pin table is for the same package/module as the design (the data cites its
15
+ datasheet table). If the MCU is not in the table, say so and ask for the pin names.
16
+ 3. `firmware_scaffold` with the target the user names (arduino, platformio, esp-idf, stm32,
17
+ avr). Pass `drivers` for the sensors on the board; use `firmware_driver` with no `part` to
18
+ list them. Drivers include the part's address; confirm address straps (SDO, ADDR, A0-A2)
19
+ against the schematic nets and pass `address` when they differ.
20
+ 4. `firmware_toolchain` shows what is installed. `firmware_build` compiles for real and
21
+ returns parsed errors and flash/RAM use. Never claim a build passed unless the tool did;
22
+ if the toolchain is missing, give the install line it returns and say what was not run.
23
+ 5. `firmware_flash` always asks the user. Use `dry_run` to show the command first.
24
+
25
+ The pin header is generated: regenerate it after a schematic change, do not edit it.
26
+ Raw sensor reads (BME280) need the datasheet's compensation to become units; say so.
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: power-budget
3
+ description: Power budget for a KiCad design - current per rail and operating mode, regulator headroom and dissipation, and battery sizing and runtime.
4
+ ---
5
+
6
+ # Power budget
7
+
8
+ Use this when the user asks for a power budget, battery life, regulator sizing or
9
+ thermal headroom.
10
+
11
+ 1. Map the power tree: `kicad_netlist` and `kicad_components` give the rails, the
12
+ regulators feeding them and the loads on each rail. Confirm unclear rails with the user.
13
+ 2. Ask for (or propose) the operating modes: e.g. active, radio TX, idle, sleep, and the
14
+ duty cycle of each. Write them down before computing.
15
+ 3. For each load and mode take the current from the datasheet (`part_lookup`,
16
+ `datasheet_fetch`): typical and maximum, with the table/figure cited.
17
+ 4. Per rail and mode: sum the loads, add regulator quiescent current, and compute input
18
+ current (LDO: same current; switcher: P_out / (efficiency x V_in), efficiency from the
19
+ datasheet curve at that load). Check each regulator's rated current with 20-30 %
20
+ margin and its dissipation (LDO: (V_in - V_out) x I) against package thermal
21
+ resistance and the maximum ambient temperature.
22
+ 5. Battery: average current from the duty cycles, capacity derated for temperature,
23
+ cut-off voltage and ageing (assume 80 % usable unless the user says otherwise);
24
+ runtime = usable capacity / average current. Propose cells if none is chosen.
25
+ 6. Present one table per mode (rail, loads, current, regulator, margin, dissipation),
26
+ the battery result, and the risks (P0-P3). Save the tables with `save_note`. Use
27
+ `execute` with Python for the arithmetic, not mental math.
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: release-package
3
+ description: Produce and verify a complete fabrication and assembly release (Gerbers, drill, BOM, CPL, schematic PDF, README) for JLCPCB or another fab.
4
+ ---
5
+
6
+ # Release package
7
+
8
+ Use this when the user asks for fabrication files, "outputs", or to get a board ready
9
+ to order.
10
+
11
+ 1. Gate on the checks. Run `kicad_drc` (zones refilled, parity on) and `kicad_erc`.
12
+ Do not release while DRC has errors or unconnected items; fix them first or stop
13
+ and tell the user exactly what blocks the release.
14
+ 2. Check assembly data: every part that will be assembled has an LCSC (or MPN) field,
15
+ values match the schematic, and DNP parts are marked. List any gaps.
16
+ 3. Write the outputs with `kicad_export` (kinds: gerbers, bom, cpl, netlist, svg) into
17
+ `outputs/` (or the folder the user names).
18
+ 4. Verify what was written:
19
+ - Gerbers include every copper layer, both masks, both silkscreens, paste and the
20
+ edge cuts, plus the drill file; zip them for upload with `execute`.
21
+ - The CPL has one row per placed SMD part, with rotations; spot-check polarised
22
+ parts (diodes, LEDs, ICs) against their footprint orientation.
23
+ - The BOM groups identical parts and its quantities add up to the placed parts.
24
+ 5. Render the board (`kicad_export` svg, then `read_file` with `as_image: true`) and
25
+ look at it: silkscreen legible, reference designators visible, connectors on edges.
26
+ 6. Write `outputs/README.md`: board size, layer count, thickness, finish, fab settings,
27
+ programming or bring-up notes, and the check results with the date.
28
+ 7. Report the file list with sizes and anything the user must still decide (finish,
29
+ colour, assembly side, quantity).
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: sourcing-brief
3
+ description: Sourcing brief for a design's BOM - availability and price breaks by distributor, alternates, lifecycle risk, HS codes and landed-cost notes for the target volume.
4
+ ---
5
+
6
+ # Sourcing brief
7
+
8
+ Use this when the user asks to source parts, compare suppliers, check stock/lifecycle,
9
+ or prepare a BOM for purchasing.
10
+
11
+ 1. Ask for the build volume(s) and the destination country if not given.
12
+ 2. `kicad_components` (or the BOM from `kicad_export`) for the line items. Group
13
+ identical parts.
14
+ 3. For each line: `part_search` / `part_lookup` for stock, price breaks at the target
15
+ volume, MOQ, lifecycle status and the LCSC/MPN; `web_search` for other distributors
16
+ when the user wants more than one source. Mark single-source and NRND/EOL parts.
17
+ 4. Propose drop-in alternates for risky lines (same package and pinout, ratings checked
18
+ with `parts_check`) and say what must be verified before swapping.
19
+ 5. Add HS codes for the main part classes and note duties or landed-cost factors only
20
+ when the destination is known; say clearly that duty rates must be confirmed with
21
+ the forwarder.
22
+ 6. Deliver a table (line, refs, MPN, qty, best source, unit and extended price at volume,
23
+ stock, lifecycle, risk, alternate) with the date of the lookup, plus totals and the
24
+ top risks. Write it to a CSV in the workspace when the user wants a file.
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: thermal-review
3
+ description: Estimate dissipation, copper area and junction temperature per part, list hotspots and suggest mitigation (thermal vias, copper).
4
+ ---
5
+
6
+ # Thermal review
7
+
8
+ 1. `kicad_components` and `kicad_netlist` to find regulators, power resistors, LEDs and MOSFETs.
9
+ 2. Establish the load: ask the user or read the datasheets of the ICs on each rail, then call
10
+ `kicad_thermal` with `loads_ma` (ref -> mA) or `rail_current_ma` (net -> mA) and the real `ambient_c`
11
+ (enclosure air, not room temperature). Give `theta_ja` from the datasheet when you have it, and
12
+ `iq_ma` for linear regulators with significant quiescent current.
13
+ 3. Read the hotspots: parts with a Tj margin under 25 K get P2, over the limit P1. Apply the advice
14
+ (copper on the tab/pad net, thermal vias, lower input-output drop or a switching stage).
15
+ 4. Always repeat the tool's `limits` statement in the report: it is a screening estimate, not a simulation.