firmwareloop 0.8.0__tar.gz → 0.9.1__tar.gz

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 (40) hide show
  1. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/PKG-INFO +6 -4
  2. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/README.md +3 -3
  3. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/firmwareloop.egg-info/PKG-INFO +6 -4
  4. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/firmwareloop.egg-info/SOURCES.txt +4 -1
  5. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/firmwareloop.egg-info/requires.txt +3 -0
  6. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/pyproject.toml +12 -1
  7. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/__init__.py +1 -1
  8. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/fw_mcp_server.py +149 -80
  9. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/lib/ndtdbg.py +63 -29
  10. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/ndtdbg_cli.py +33 -8
  11. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/setup-agent-mcp.ps1 +2 -2
  12. firmwareloop-0.9.1/tools/templates/lab.example.yaml +195 -0
  13. firmwareloop-0.9.1/tools/templates/skill-firmwareloop.md +37 -0
  14. firmwareloop-0.9.1/tools/templates/skill-fwloop-adapter.md +190 -0
  15. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/LICENSE +0 -0
  16. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/firmwareloop.egg-info/dependency_links.txt +0 -0
  17. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/firmwareloop.egg-info/entry_points.txt +0 -0
  18. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/firmwareloop.egg-info/top_level.txt +0 -0
  19. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/setup.cfg +0 -0
  20. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/acceptance-scenario.ps1 +0 -0
  21. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/build.ps1 +0 -0
  22. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/can.ps1 +0 -0
  23. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/check-qoder-mcp.ps1 +0 -0
  24. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/common/build-backends.psm1 +0 -0
  25. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/common/fw.psm1 +0 -0
  26. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/common/uart_probe.py +0 -0
  27. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/doctor.ps1 +0 -0
  28. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/flash.ps1 +0 -0
  29. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/instrument_cli.py +0 -0
  30. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/lib/__init__.py +0 -0
  31. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/lib/instruments.py +0 -0
  32. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/lib/serial_assistant.py +0 -0
  33. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/logic_capture.ps1 +0 -0
  34. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/logic_decode.ps1 +0 -0
  35. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/ndtdbg_bridge.ps1 +0 -0
  36. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/reset.ps1 +0 -0
  37. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/serial_cli.py +0 -0
  38. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/sigrok_csv_to_fwloop.py +0 -0
  39. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/test-backends.ps1 +0 -0
  40. {firmwareloop-0.8.0 → firmwareloop-0.9.1}/tools/test.ps1 +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: firmwareloop
3
- Version: 0.8.0
3
+ Version: 0.9.1
4
4
  Summary: AI Agent Firmware Development and Lab Automation Platform (Dual-Tier MCP Server)
5
5
  Author-email: WeilinZhang <19523330249@163.com>
6
6
  License: MIT
@@ -24,6 +24,8 @@ Requires-Dist: pytest>=8.0.0
24
24
  Requires-Dist: pyocd>=0.36.0
25
25
  Requires-Dist: pyyaml>=6.0
26
26
  Requires-Dist: agentic-hil>=0.14.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest-xdist>=3.6.0; extra == "dev"
27
29
  Dynamic: license-file
28
30
 
29
31
  # FirmwareLoop (`fwloop`)
@@ -385,7 +387,7 @@ claude mcp remove --scope user agentic-hil
385
387
  ├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
386
388
  ├── demo-make/ 示例固件(Make;构建测试载体)
387
389
  ├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
388
- ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.8.0)
390
+ ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.9.1)
389
391
  ├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
390
392
  ├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
391
393
  ├── CLAUDE.md Claude Code CLI 指南
@@ -395,7 +397,7 @@ claude mcp remove --scope user agentic-hil
395
397
 
396
398
  ---
397
399
 
398
- ## 能力验证状态(v0.8.0
400
+ ## 能力验证状态(v0.9.1
399
401
 
400
402
  > 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
401
403
  > `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
@@ -419,7 +421,7 @@ claude mcp remove --scope user agentic-hil
419
421
  | 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
420
422
  | NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
421
423
  | NdtBox 目标断电复位(fw_ndtdbg_power_cycle, 仅 io10 断电复位, 无写闸门) | Implemented(单测覆盖时序/探针缺失指引)-> Real Hardware Validated 待 NdtBox |
422
- | NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated(84.87.1.1 / 84.87.1.2 真机整片擦写 verify pass; 2026-09-11 同批文件桥内单体 flash 6/6 pass;双闸门 Fail-Closed)。**v0.7.2 起默认引擎 = 桥内单体 flash**(逐笔 serve 路径现场实测"擦除生效但页写不落地",保留 `flash_engine: serve` 供 A/B);差分 boot 判据 + `boot_judge: off` 逃生开关见 0.7.1;**待验收声明:默认引擎路径(Python 组 plan → Popen 桥 → 解析信封)尚待真机验收** |
424
+ | NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated(两批真实固件镜像真机整片擦写 verify pass; 2026-09-11 同批文件桥内单体 flash 6/6 pass;双闸门 Fail-Closed)。**v0.7.2 起默认引擎 = 桥内单体 flash**(逐笔 serve 路径现场实测"擦除生效但页写不落地",保留 `flash_engine: serve` 供 A/B);差分 boot 判据 + `boot_judge: off` 逃生开关见 0.7.1;**待验收声明:默认引擎路径(Python 组 plan → Popen 桥 → 解析信封)尚待真机验收** |
423
425
  | 烧后冷启动冒烟(fw_ndtdbg_flash 默认开, `cold_boot_check`) | Implemented(单测覆盖通过/全 FF/期望不符/关闭;`COLD_BOOT_VERIFY_FAILED` 错误类)-> Real Hardware Validated 待真机 |
424
426
  | 串口占用取证(`port_probe` 试开硬结论 + `holder_candidates` 候选名单) | Implemented(试开结论可复现;候选按 cmdline/name 标注置信度) |
425
427
  | 抓开机日志(fw_serial_capture + power_cycle_off_ms, 窗口内断电复位) | Implemented(simulator 验证窗口与复位串联)-> Real Hardware Validated 待 NdtBox + DUT |
@@ -357,7 +357,7 @@ claude mcp remove --scope user agentic-hil
357
357
  ├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
358
358
  ├── demo-make/ 示例固件(Make;构建测试载体)
359
359
  ├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
360
- ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.8.0)
360
+ ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.9.1)
361
361
  ├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
362
362
  ├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
363
363
  ├── CLAUDE.md Claude Code CLI 指南
@@ -367,7 +367,7 @@ claude mcp remove --scope user agentic-hil
367
367
 
368
368
  ---
369
369
 
370
- ## 能力验证状态(v0.8.0
370
+ ## 能力验证状态(v0.9.1
371
371
 
372
372
  > 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
373
373
  > `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
@@ -391,7 +391,7 @@ claude mcp remove --scope user agentic-hil
391
391
  | 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
392
392
  | NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
393
393
  | NdtBox 目标断电复位(fw_ndtdbg_power_cycle, 仅 io10 断电复位, 无写闸门) | Implemented(单测覆盖时序/探针缺失指引)-> Real Hardware Validated 待 NdtBox |
394
- | NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated(84.87.1.1 / 84.87.1.2 真机整片擦写 verify pass; 2026-09-11 同批文件桥内单体 flash 6/6 pass;双闸门 Fail-Closed)。**v0.7.2 起默认引擎 = 桥内单体 flash**(逐笔 serve 路径现场实测"擦除生效但页写不落地",保留 `flash_engine: serve` 供 A/B);差分 boot 判据 + `boot_judge: off` 逃生开关见 0.7.1;**待验收声明:默认引擎路径(Python 组 plan → Popen 桥 → 解析信封)尚待真机验收** |
394
+ | NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated(两批真实固件镜像真机整片擦写 verify pass; 2026-09-11 同批文件桥内单体 flash 6/6 pass;双闸门 Fail-Closed)。**v0.7.2 起默认引擎 = 桥内单体 flash**(逐笔 serve 路径现场实测"擦除生效但页写不落地",保留 `flash_engine: serve` 供 A/B);差分 boot 判据 + `boot_judge: off` 逃生开关见 0.7.1;**待验收声明:默认引擎路径(Python 组 plan → Popen 桥 → 解析信封)尚待真机验收** |
395
395
  | 烧后冷启动冒烟(fw_ndtdbg_flash 默认开, `cold_boot_check`) | Implemented(单测覆盖通过/全 FF/期望不符/关闭;`COLD_BOOT_VERIFY_FAILED` 错误类)-> Real Hardware Validated 待真机 |
396
396
  | 串口占用取证(`port_probe` 试开硬结论 + `holder_candidates` 候选名单) | Implemented(试开结论可复现;候选按 cmdline/name 标注置信度) |
397
397
  | 抓开机日志(fw_serial_capture + power_cycle_off_ms, 窗口内断电复位) | Implemented(simulator 验证窗口与复位串联)-> Real Hardware Validated 待 NdtBox + DUT |
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: firmwareloop
3
- Version: 0.8.0
3
+ Version: 0.9.1
4
4
  Summary: AI Agent Firmware Development and Lab Automation Platform (Dual-Tier MCP Server)
5
5
  Author-email: WeilinZhang <19523330249@163.com>
6
6
  License: MIT
@@ -24,6 +24,8 @@ Requires-Dist: pytest>=8.0.0
24
24
  Requires-Dist: pyocd>=0.36.0
25
25
  Requires-Dist: pyyaml>=6.0
26
26
  Requires-Dist: agentic-hil>=0.14.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest-xdist>=3.6.0; extra == "dev"
27
29
  Dynamic: license-file
28
30
 
29
31
  # FirmwareLoop (`fwloop`)
@@ -385,7 +387,7 @@ claude mcp remove --scope user agentic-hil
385
387
  ├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
386
388
  ├── demo-make/ 示例固件(Make;构建测试载体)
387
389
  ├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
388
- ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.8.0)
390
+ ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.9.1)
389
391
  ├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
390
392
  ├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
391
393
  ├── CLAUDE.md Claude Code CLI 指南
@@ -395,7 +397,7 @@ claude mcp remove --scope user agentic-hil
395
397
 
396
398
  ---
397
399
 
398
- ## 能力验证状态(v0.8.0
400
+ ## 能力验证状态(v0.9.1
399
401
 
400
402
  > 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
401
403
  > `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
@@ -419,7 +421,7 @@ claude mcp remove --scope user agentic-hil
419
421
  | 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
420
422
  | NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
421
423
  | NdtBox 目标断电复位(fw_ndtdbg_power_cycle, 仅 io10 断电复位, 无写闸门) | Implemented(单测覆盖时序/探针缺失指引)-> Real Hardware Validated 待 NdtBox |
422
- | NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated(84.87.1.1 / 84.87.1.2 真机整片擦写 verify pass; 2026-09-11 同批文件桥内单体 flash 6/6 pass;双闸门 Fail-Closed)。**v0.7.2 起默认引擎 = 桥内单体 flash**(逐笔 serve 路径现场实测"擦除生效但页写不落地",保留 `flash_engine: serve` 供 A/B);差分 boot 判据 + `boot_judge: off` 逃生开关见 0.7.1;**待验收声明:默认引擎路径(Python 组 plan → Popen 桥 → 解析信封)尚待真机验收** |
424
+ | NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated(两批真实固件镜像真机整片擦写 verify pass; 2026-09-11 同批文件桥内单体 flash 6/6 pass;双闸门 Fail-Closed)。**v0.7.2 起默认引擎 = 桥内单体 flash**(逐笔 serve 路径现场实测"擦除生效但页写不落地",保留 `flash_engine: serve` 供 A/B);差分 boot 判据 + `boot_judge: off` 逃生开关见 0.7.1;**待验收声明:默认引擎路径(Python 组 plan → Popen 桥 → 解析信封)尚待真机验收** |
423
425
  | 烧后冷启动冒烟(fw_ndtdbg_flash 默认开, `cold_boot_check`) | Implemented(单测覆盖通过/全 FF/期望不符/关闭;`COLD_BOOT_VERIFY_FAILED` 错误类)-> Real Hardware Validated 待真机 |
424
426
  | 串口占用取证(`port_probe` 试开硬结论 + `holder_candidates` 候选名单) | Implemented(试开结论可复现;候选按 cmdline/name 标注置信度) |
425
427
  | 抓开机日志(fw_serial_capture + power_cycle_off_ms, 窗口内断电复位) | Implemented(simulator 验证窗口与复位串联)-> Real Hardware Validated 待 NdtBox + DUT |
@@ -32,4 +32,7 @@ tools/common/uart_probe.py
32
32
  tools/lib/__init__.py
33
33
  tools/lib/instruments.py
34
34
  tools/lib/ndtdbg.py
35
- tools/lib/serial_assistant.py
35
+ tools/lib/serial_assistant.py
36
+ tools/templates/lab.example.yaml
37
+ tools/templates/skill-firmwareloop.md
38
+ tools/templates/skill-fwloop-adapter.md
@@ -5,3 +5,6 @@ pytest>=8.0.0
5
5
  pyocd>=0.36.0
6
6
  pyyaml>=6.0
7
7
  agentic-hil>=0.14.0
8
+
9
+ [dev]
10
+ pytest-xdist>=3.6.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "firmwareloop"
7
- version = "0.8.0"
7
+ version = "0.9.1"
8
8
  description = "AI Agent Firmware Development and Lab Automation Platform (Dual-Tier MCP Server)"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -42,9 +42,20 @@ fwloop = "tools.fw_mcp_server:main"
42
42
  fw-mcp = "tools.fw_mcp_server:main"
43
43
  fw = "tools.fw_mcp_server:main"
44
44
 
45
+ # 开发/验收工具链 (不随 wheel 分发给使用者): 并行跑单测用
46
+ # uv pip install -e ".[dev]"
47
+ # python -m pytest tests/unit -n auto --dist=loadfile
48
+ [project.optional-dependencies]
49
+ dev = [
50
+ "pytest-xdist>=3.6.0",
51
+ ]
52
+
45
53
  [tool.setuptools.packages.find]
46
54
  where = ["."]
47
55
  include = ["tools*"]
48
56
 
49
57
  [tool.setuptools.package-data]
50
58
  "*" = ["*.ps1", "*.psm1", "*.yaml", "*.md", "*.json"]
59
+ # 随包分发的模板/资源 (v0.9.1): tools/templates/ 下的 lab.yaml 模板与 skill 副本。
60
+ # 仓库根的 lab/ 与 skills/ 不在 wheel 内, 安装态必须靠这些副本 (防漂移测试锁定一致)。
61
+ "tools" = ["templates/*.yaml", "templates/*.md"]
@@ -2,4 +2,4 @@
2
2
  FirmwareLoop - AI Agent Firmware Development and Lab Automation Platform.
3
3
  """
4
4
 
5
- __version__ = "0.8.0"
5
+ __version__ = "0.9.1"
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- fw_mcp_server.py - FirmwareLoop High-Level Workflow & Device MCP Server (v0.8.0).
3
+ fw_mcp_server.py - FirmwareLoop High-Level Workflow & Device MCP Server (v0.9.1).
4
4
 
5
5
  Exposes high-level firmware engineering and hardware management tools to AI Coding
6
6
  Agents (Antigravity CLI, Claude Code CLI, Qoder IDE, CodeBuddy IDE, DeepSeek Harness,
@@ -384,13 +384,16 @@ def handle_fw_ndtdbg_power_cycle(args: Dict[str, Any]) -> Dict[str, Any]:
384
384
  def _stream_cli_cmd(args: Dict[str, Any], out_path: str, duration_cap: float) -> Optional[list]:
385
385
  """构造 stream CLI 命令 (同步/job 共用); 返回 None 表示参数非法。"""
386
386
  cli_script = os.path.join(REPO_ROOT, "tools", "ndtdbg_cli.py")
387
- regs = args.get("regs") or ["0xEE:8"]
388
387
  duration_s = min(max(float(args.get("duration_s", 10)), 0.0), duration_cap)
389
388
  max_frames = int(args.get("max_frames", 0))
390
389
  if duration_s <= 0 and max_frames <= 0:
391
390
  return None
392
- cmd = [sys.executable, cli_script, "stream", "--regs", *[str(r) for r in regs],
393
- "--out", out_path]
391
+ cmd = [sys.executable, cli_script, "stream", "--out", out_path]
392
+ regs = args.get("regs")
393
+ if regs:
394
+ if isinstance(regs, str):
395
+ regs = [regs]
396
+ cmd.extend(["--regs", *[str(r) for r in regs]])
394
397
  if args.get("interval_ms") is not None:
395
398
  cmd.extend(["--interval-ms", str(int(args["interval_ms"]))])
396
399
  if duration_s > 0:
@@ -567,10 +570,13 @@ def handle_fw_ndtdbg_capture_seq(args: Dict[str, Any]) -> Dict[str, Any]:
567
570
  cli_script = os.path.join(REPO_ROOT, "tools", "ndtdbg_cli.py")
568
571
  cap_script = os.path.join(REPO_ROOT, "tools", "logic_capture.ps1")
569
572
  duration_ms = int(args.get("duration_ms", 8000))
570
- writes = args.get("writes", [{"reg": "0xBE", "data": "01"}, {"reg": "0x01", "data": "BE 42"}])
571
- poll_reg = args.get("poll_reg", "0x01")
573
+ writes = args.get("writes")
574
+ if not writes:
575
+ return {"ok": False, "error_class": "CONFIG_ERROR",
576
+ "error": "fw_ndtdbg_capture_seq 必须传入非空 writes 序列 (如 [{'reg': '0xBE', 'data': '01'}]), 无默认序列; 参见目标固件协议文档"}
577
+ poll_reg = args.get("poll_reg")
572
578
  poll_attempts = int(args.get("poll_attempts", 5))
573
- readback_reg = args.get("readback_reg", "0xBE")
579
+ readback_reg = args.get("readback_reg")
574
580
  poll_expect = " ".join(str(args.get("poll_expect", "00 00")).split()).upper()
575
581
  confirm = bool(args.get("confirm", True))
576
582
 
@@ -811,13 +817,47 @@ def _detect_hardware() -> Dict[str, Any]:
811
817
  return detected
812
818
 
813
819
 
820
+ _PACKAGE_DIR = os.path.dirname(os.path.abspath(__file__))
821
+
822
+
823
+ def _lab_example_template_path() -> str:
824
+ """lab.yaml 唯一模板定位 (v0.9.1 修复安装态 FileNotFoundError):
825
+ 源码仓库态优先 lab/lab.example.yaml (开发真身); wheel 安装态
826
+ (REPO_ROOT=site-packages, 无 lab/ 目录) 回退随包分发的
827
+ tools/templates/lab.example.yaml——两份由防漂移测试逐行锁定一致。"""
828
+ repo = os.path.join(REPO_ROOT, "lab", "lab.example.yaml")
829
+ if os.path.isfile(repo):
830
+ return repo
831
+ return os.path.join(_PACKAGE_DIR, "templates", "lab.example.yaml")
832
+
833
+
834
+ def _skill_template_path(name: str) -> Optional[str]:
835
+ """skill 源文件定位: 源码仓库 skills/<name>/SKILL.md 优先 (skills/ 不随 wheel
836
+ 分发), 安装态回退包内 tools/templates/skill-<name>.md; 均缺失返回 None。"""
837
+ repo = os.path.join(REPO_ROOT, "skills", name, "SKILL.md")
838
+ if os.path.isfile(repo):
839
+ return repo
840
+ pkg = os.path.join(_PACKAGE_DIR, "templates", f"skill-{name}.md")
841
+ return pkg if os.path.isfile(pkg) else None
842
+
843
+
844
+ def _project_lab_yaml_path() -> str:
845
+ """配置写入位置: 源码仓库态 (REPO_ROOT/lab 存在) 写仓库 lab/lab.yaml (工程习惯
846
+ 与既有测试锚点不变); 安装态写调用方工程 CWD/lab/lab.yaml (IDE 以工程根为 CWD
847
+ 启动 MCP), 避免把配置写进 site-packages 自身。"""
848
+ repo_lab = os.path.join(REPO_ROOT, "lab")
849
+ if os.path.isdir(repo_lab):
850
+ return os.path.join(repo_lab, "lab.yaml")
851
+ return os.path.join(os.getcwd(), "lab", "lab.yaml")
852
+
853
+
814
854
  def _build_lab_yaml_from_example(target_dir: str, target_chip: str, build_backend: str,
815
855
  detected_dll_dir: Optional[str]) -> str:
816
856
  """P1-5: lab/lab.example.yaml 为唯一模板 (example 加段 init 自动跟上);
817
857
  仅替换 project.name/target_chip/build_backend/source_dir 与 dut.uart.port
818
858
  (留空 + 注释 "用 fw_scan_hardware 探测", 去掉硬编码 COM3);
819
859
  ndtdbg.dll_dir 按探测结果预填, 探测不到保持注释态 + 人工指引。"""
820
- example_path = os.path.join(REPO_ROOT, "lab", "lab.example.yaml")
860
+ example_path = _lab_example_template_path()
821
861
  with open(example_path, encoding="utf-8") as fh:
822
862
  lines = fh.read().splitlines()
823
863
  out: List[str] = []
@@ -865,7 +905,7 @@ def _upgrade_lab_yaml(existing_path: str) -> List[str]:
865
905
  import yaml as _yaml
866
906
  with open(existing_path, encoding="utf-8") as fh:
867
907
  existing = _yaml.safe_load(fh) or {}
868
- example_path = os.path.join(REPO_ROOT, "lab", "lab.example.yaml")
908
+ example_path = _lab_example_template_path()
869
909
  with open(example_path, encoding="utf-8") as fh:
870
910
  example = _yaml.safe_load(fh) or {}
871
911
  added: List[str] = []
@@ -902,7 +942,7 @@ def handle_fw_init_project(args: Dict[str, Any]) -> Dict[str, Any]:
902
942
  return {
903
943
  "ok": False,
904
944
  "error_class": "CONFIG_ERROR",
905
- "error": "target_chip 必填 (如 STM32F103C8 / CSA37F71)",
945
+ "error": "target_chip 必填 (如 STM32F103C8 / STM32F407ZG)",
906
946
  "detail": "fwloop init <target_chip> 或 MCP fw_init_project(target_chip=...)",
907
947
  }
908
948
 
@@ -1225,8 +1265,8 @@ fw_serial_capture(power_cycle_off_ms=500) # 抓开机日志(窗口内自动
1225
1265
 
1226
1266
 
1227
1267
  def handle_fw_configure_lab(args: Dict[str, Any]) -> Dict[str, Any]:
1228
- lab_yaml_path = os.path.join(REPO_ROOT, "lab", "lab.yaml")
1229
- example_yaml_path = os.path.join(REPO_ROOT, "lab", "lab.example.yaml")
1268
+ lab_yaml_path = _project_lab_yaml_path()
1269
+ example_yaml_path = _lab_example_template_path()
1230
1270
 
1231
1271
  config: Dict[str, Any] = {}
1232
1272
  if os.path.exists(lab_yaml_path):
@@ -1936,8 +1976,7 @@ TOOLS_REGISTRY = {
1936
1976
  "regs": {
1937
1977
  "type": "array",
1938
1978
  "items": {"type": "string"},
1939
- "description": "Register specs 'addr:len[:name]', e.g. ['0xEE:8:ch', '0x20:138:event']. Default ['0xEE:8'].",
1940
- "default": ["0xEE:8"],
1979
+ "description": "Register specs 'addr:len[:name]', e.g. ['0xEE:8:ch'] (EXAMPLE). Required unless configured in lab.yaml ndtdbg.stream.regs or ndtdbg.target_profile.stream_regs.",
1941
1980
  },
1942
1981
  "interval_ms": {
1943
1982
  "type": "integer",
@@ -1961,7 +2000,7 @@ TOOLS_REGISTRY = {
1961
2000
  "decode": {
1962
2001
  "type": "string",
1963
2002
  "enum": ["hex", "int16le", "uint16le", "int8", "uint8"],
1964
- "description": "Optional decoded columns besides raw hex (int16le for PT488 0xEE channels).",
2003
+ "description": "Optional decoded columns besides raw hex (e.g. int16le for 4-channel little-endian sensor frames).",
1965
2004
  "default": "hex",
1966
2005
  },
1967
2006
  "retry": {
@@ -1987,7 +2026,7 @@ TOOLS_REGISTRY = {
1987
2026
  "interleave_writes": {
1988
2027
  "type": "array",
1989
2028
  "items": {"type": "string"},
1990
- "description": "Interleave-write stress profile (P1-1): 'addr:cycle:v1,v2,...:every=N' (rotate values every N frames) or 'addr:once:v:frame=M' (single write at frame M). Written in-frame gaps over the SAME USB session (no dual-open). Requires write_allowed: true AND confirm: true. e.g. ['0x56:cycle:00,01,02,03:every=8'] reproduces the PT488 schedule-race stress profile.",
2029
+ "description": "Interleave-write stress profile (P1-1): 'addr:cycle:v1,v2,...:every=N' (rotate values every N frames) or 'addr:once:v:frame=M' (single write at frame M). Written in-frame gaps over the SAME USB session (no dual-open). Requires write_allowed: true AND confirm: true. e.g. ['0x56:cycle:00,01,02,03:every=8'] reproduces schedule-race style concurrent-write stress profiles (example from a real product debugging session).",
1991
2030
  },
1992
2031
  "confirm": {
1993
2032
  "type": "boolean",
@@ -1997,7 +2036,6 @@ TOOLS_REGISTRY = {
1997
2036
  "dll_dir": {"type": "string", "description": "NdtDebugTool lib dir; default from lab.yaml ndtdbg.dll_dir"},
1998
2037
  "slave_addr": {"type": "string", "description": "IIC slave address, e.g. 0xA0; default from lab.yaml"},
1999
2038
  },
2000
- "required": ["regs"],
2001
2039
  "additionalProperties": False,
2002
2040
  },
2003
2041
  "handler": handle_fw_ndtdbg_stream,
@@ -2010,8 +2048,7 @@ TOOLS_REGISTRY = {
2010
2048
  "regs": {
2011
2049
  "type": "array",
2012
2050
  "items": {"type": "string"},
2013
- "description": "Register specs 'addr:len[:name]'. Default ['0xEE:8'].",
2014
- "default": ["0xEE:8"],
2051
+ "description": "Register specs 'addr:len[:name]', e.g. ['0xEE:8:ch'] (EXAMPLE). Required unless configured in lab.yaml ndtdbg.stream.regs or ndtdbg.target_profile.stream_regs.",
2015
2052
  },
2016
2053
  "interval_ms": {"type": "integer", "description": "Target sampling interval in ms.", "default": 100},
2017
2054
  "duration_s": {"type": "number", "description": "Total capture duration in seconds (cap 3600).", "default": 10},
@@ -2056,7 +2093,7 @@ TOOLS_REGISTRY = {
2056
2093
  "handler": handle_fw_ndtdbg_stream_stop,
2057
2094
  },
2058
2095
  "fw_ndtdbg_capture_seq": {
2059
- "description": "One-call protocol evidence: concurrently runs a NdtBox register write sequence and a sigrok I2C capture, returning the capture path for fw_logic_decode. NOTE: the default sequence (0xBE=01, 0x01=BE 42, poll 0x01, readback 0xBE) is an EXAMPLE only - always pass your target firmware's real register protocol via writes/poll_reg/poll_expect. Writes are double-gated (confirm: true + lab.yaml ndtdbg.write_allowed); degrades to sequence-only execution (capture_skipped) when sigrok-cli is missing.",
2096
+ "description": "One-call protocol evidence: concurrently runs a target-specific register write sequence and a sigrok I2C capture, returning the capture path for fw_logic_decode. REQUIRED: pass your target firmware's real register protocol via writes (e.g. [{'reg': '0xBE', 'data': '01'}]). Optional poll_reg/poll_expect/readback_reg for handshake & verification. Writes are double-gated (confirm: true + lab.yaml ndtdbg.write_allowed); degrades to sequence-only execution (capture_skipped) when sigrok-cli is missing.",
2060
2097
  "inputSchema": {
2061
2098
  "type": "object",
2062
2099
  "properties": {
@@ -2074,20 +2111,21 @@ TOOLS_REGISTRY = {
2074
2111
  "data": {"type": "string"},
2075
2112
  },
2076
2113
  },
2077
- "description": "Register write sequence per your firmware protocol (default 0xBE=01, 0x01=BE 42 is an EXAMPLE, may not match real firmware)",
2114
+ "description": "Target-specific register write sequence (e.g. [{'reg': '0xBE', 'data': '01'}]). Required, no hardcoded default.",
2078
2115
  },
2079
2116
  "confirm": {
2080
2117
  "type": "boolean",
2081
2118
  "description": "Acknowledge the register write intent; handler auto-passes --confirm to the CLI (double-gated with lab.yaml ndtdbg.write_allowed)",
2082
2119
  "default": True,
2083
2120
  },
2084
- "poll_reg": {"type": "string", "description": "Poll register until it matches poll_expect (default 0x01)"},
2121
+ "poll_reg": {"type": "string", "description": "Optional poll register until it matches poll_expect (no default; skipped if omitted)"},
2085
2122
  "poll_expect": {"type": "string", "description": "Expected poll register value (hex) that ends the poll loop; default '00 00' - set to match your firmware protocol", "default": "00 00"},
2086
2123
  "poll_attempts": {"type": "integer", "description": "Max poll attempts", "default": 5},
2087
- "readback_reg": {"type": "string", "description": "Read-back verification register (default 0xBE)"},
2124
+ "readback_reg": {"type": "string", "description": "Optional read-back verification register (no default; skipped if omitted)"},
2088
2125
  "dll_dir": {"type": "string", "description": "NdtDebugTool lib directory; default from lab.yaml"},
2089
2126
  "slave_addr": {"type": "string", "description": "IIC slave address (e.g. 0xA0)"},
2090
2127
  },
2128
+ "required": ["writes"],
2091
2129
  "additionalProperties": False,
2092
2130
  },
2093
2131
  "handler": handle_fw_ndtdbg_capture_seq,
@@ -2174,7 +2212,7 @@ TOOLS_REGISTRY = {
2174
2212
  },
2175
2213
  "target_chip": {
2176
2214
  "type": "string",
2177
- "description": "Target MCU chip model (e.g. STM32F103C8, CSA37F71) - REQUIRED, no hardcoded default",
2215
+ "description": "Target MCU chip model (e.g. STM32F103C8, STM32F407ZG) - REQUIRED, no hardcoded default",
2178
2216
  },
2179
2217
  "build_backend": {
2180
2218
  "type": "string",
@@ -2387,7 +2425,7 @@ def process_request(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
2387
2425
  },
2388
2426
  "serverInfo": {
2389
2427
  "name": "firmwareloop",
2390
- "version": "0.8.0"
2428
+ "version": "0.9.1"
2391
2429
  }
2392
2430
  }
2393
2431
  }
@@ -2522,7 +2560,7 @@ def _settle_stashed_entrypoints(stashed) -> None:
2522
2560
  def handle_cli_update() -> int:
2523
2561
  """Handle `firmwareloop update` command."""
2524
2562
  print("============================================================")
2525
- print(" FirmwareLoop Auto-Updater (v0.8.0)")
2563
+ print(" FirmwareLoop Auto-Updater (v0.9.1)")
2526
2564
  print("============================================================")
2527
2565
 
2528
2566
  is_git_repo = os.path.exists(os.path.join(REPO_ROOT, ".git"))
@@ -2659,6 +2697,18 @@ def handle_cli_selftest(target: str = "ndtdbg") -> int:
2659
2697
  def report(name, status, detail=""):
2660
2698
  results.append((name, status, detail))
2661
2699
 
2700
+ # P2-2: 从生效配置加载产品协议 profile (避免硬编码 0xEE 与 0xBE)
2701
+ try:
2702
+ from tools import ndtdbg_cli
2703
+ _cfg = ndtdbg_cli.load_config(None)
2704
+ except Exception:
2705
+ _cfg = {}
2706
+ ndt_cfg = (_cfg.get("ndtdbg") or {}) if isinstance(_cfg, dict) else {}
2707
+ target_profile = ndt_cfg.get("target_profile") or {}
2708
+ stream_cfg = ndt_cfg.get("stream") or {}
2709
+ stream_regs = target_profile.get("stream_regs") or stream_cfg.get("regs")
2710
+ loop_cfg = target_profile.get("write_loop") or target_profile.get("selftest_sequence")
2711
+
2662
2712
  # 1. status 分层探测
2663
2713
  print("\n[1/4] status 探测 (Box / IIC bus / target chip) ...")
2664
2714
  rc, body = run_cli("status", timeout=30)
@@ -2675,40 +2725,58 @@ def handle_cli_selftest(target: str = "ndtdbg") -> int:
2675
2725
  report("protocol.desync", _st, _detail)
2676
2726
 
2677
2727
  # 2. stream 冒烟 (100ms x 10s)
2678
- print("[2/4] stream 冒烟 (100ms x 10s, int16 解码) ...")
2679
- rc, body = run_cli("stream", "--regs", "0xEE:8:ch", "--interval-ms", "100",
2680
- "--duration-s", "10", "--decode", "int16le", timeout=60)
2681
- stream_data = (body or {}).get("data") if isinstance(body, dict) else None
2682
- if rc == 0 and stream_data:
2683
- total = stream_data.get("frames_total", 0)
2684
- conserved = (stream_data.get("frames_ok", 0) + stream_data.get("frames_ff_gap", 0)
2685
- + stream_data.get("frames_io_fail", 0)) == total
2686
- avg = stream_data.get("interval_actual_avg_ms")
2687
- report("stream.frames_conserved", "PASS" if conserved else "FAIL",
2688
- f"total={total} (ok={stream_data.get('frames_ok')}, "
2689
- f"ff_gap={stream_data.get('frames_ff_gap')}, io_fail={stream_data.get('frames_io_fail')})")
2690
- report("stream.interval_perf", "PASS" if (avg is not None and avg <= 110) else "FAIL",
2691
- f"avg={avg}ms (target 100ms, 验收线 <=110ms)")
2692
- report("stream.io_overhead", "PASS" if stream_data.get("io_overhead_avg_ms") is not None else "FAIL",
2693
- f"io_overhead_avg_ms={stream_data.get('io_overhead_avg_ms')}")
2694
- report("stream.gaps", "PASS", f"gaps_n={stream_data.get('gaps_n', 0)} (仅 >2x target 计数)")
2695
- else:
2696
- report("stream", "FAIL", (body or {}).get("error_class", f"exit {rc}"))
2697
-
2698
- # 3. 写读回环 (write_allowed 双门内; 未授权 SKIP, 不算 FAIL)
2699
- print("[3/4] 写读回环 (0xBE debug_mode, lab.yaml write_allowed) ...")
2700
- rc, body = run_cli("write-reg", "--addr", "0xBE", "--data", "01", "--confirm")
2701
- if rc == 0 and body and body.get("ok"):
2702
- auth = (body.get("data") or {}).get("authorization") or {}
2703
- report("write.readback", "PASS", f"0xBE=01 写入成功 (授权来源: {auth.get('granted_by', '?')})")
2704
- run_cli("write-reg", "--addr", "0xBE", "--data", "00", "--confirm")
2705
- report("write.restore", "PASS", "0xBE=00 已恢复")
2728
+ if stream_regs:
2729
+ if isinstance(stream_regs, str):
2730
+ stream_regs = [stream_regs]
2731
+ stream_cmd = ["stream", "--interval-ms", "100", "--duration-s", "10",
2732
+ "--regs", *[str(r) for r in stream_regs]]
2733
+ frame_cfg = target_profile.get("measure_frame") or {}
2734
+ dec = frame_cfg.get("decode")
2735
+ if dec and dec != "raw":
2736
+ stream_cmd.extend(["--decode", dec])
2737
+ print(f"[2/4] stream 冒烟 (100ms x 10s, regs={stream_regs}) ...")
2738
+ rc, body = run_cli(*stream_cmd, timeout=60)
2739
+ stream_data = (body or {}).get("data") if isinstance(body, dict) else None
2740
+ if rc == 0 and stream_data:
2741
+ total = stream_data.get("frames_total", 0)
2742
+ conserved = (stream_data.get("frames_ok", 0) + stream_data.get("frames_ff_gap", 0)
2743
+ + stream_data.get("frames_io_fail", 0)) == total
2744
+ avg = stream_data.get("interval_actual_avg_ms")
2745
+ report("stream.frames_conserved", "PASS" if conserved else "FAIL",
2746
+ f"total={total} (ok={stream_data.get('frames_ok')}, "
2747
+ f"ff_gap={stream_data.get('frames_ff_gap')}, io_fail={stream_data.get('frames_io_fail')})")
2748
+ report("stream.interval_perf", "PASS" if (avg is not None and avg <= 110) else "FAIL",
2749
+ f"avg={avg}ms (target 100ms, 验收线 <=110ms)")
2750
+ report("stream.io_overhead", "PASS" if stream_data.get("io_overhead_avg_ms") is not None else "FAIL",
2751
+ f"io_overhead_avg_ms={stream_data.get('io_overhead_avg_ms')}")
2752
+ report("stream.gaps", "PASS", f"gaps_n={stream_data.get('gaps_n', 0)} (仅 >2x target 计数)")
2753
+ else:
2754
+ report("stream", "FAIL", (body or {}).get("error_class", f"exit {rc}"))
2706
2755
  else:
2707
- err_class = (body or {}).get("error_class", f"exit {rc}")
2708
- if err_class == "PERMISSION_DENIED":
2709
- report("write.readback", "SKIP", "write_allowed 未开启 (双门保护), 跳过写回环")
2756
+ print("[2/4] stream 冒烟 (未配置 target_profile.stream_regs, 跳过) ...")
2757
+ report("stream", "SKIP", "未配置产品协议 profile (target_profile.stream_regs),寄存器级自验跳过")
2758
+
2759
+ # 3. 写读回环 (write_allowed 双门内; 未授权或未配 profile 时 SKIP, 不算 FAIL)
2760
+ if loop_cfg:
2761
+ loop_addr = str(loop_cfg.get("addr", "0xBE"))
2762
+ w_data = str(loop_cfg.get("write_data", "01"))
2763
+ r_data = str(loop_cfg.get("restore_data", "00"))
2764
+ print(f"[3/4] 写读回环 ({loop_addr}={w_data}->{r_data}, 需 lab.yaml write_allowed) ...")
2765
+ rc, body = run_cli("write-reg", "--addr", loop_addr, "--data", w_data, "--confirm")
2766
+ if rc == 0 and body and body.get("ok"):
2767
+ auth = (body.get("data") or {}).get("authorization") or {}
2768
+ report("write.readback", "PASS", f"{loop_addr}={w_data} 写入成功 (授权来源: {auth.get('granted_by', '?')})")
2769
+ run_cli("write-reg", "--addr", loop_addr, "--data", r_data, "--confirm")
2770
+ report("write.restore", "PASS", f"{loop_addr}={r_data} 已恢复")
2710
2771
  else:
2711
- report("write.readback", "FAIL", err_class)
2772
+ err_class = (body or {}).get("error_class", f"exit {rc}")
2773
+ if err_class == "PERMISSION_DENIED":
2774
+ report("write.readback", "SKIP", "write_allowed 未开启 (双门保护), 跳过写回环")
2775
+ else:
2776
+ report("write.readback", "FAIL", err_class)
2777
+ else:
2778
+ print("[3/4] 写读回环 (未配置 target_profile.write_loop, 跳过) ...")
2779
+ report("write.readback", "SKIP", "未配置产品协议 profile (target_profile.write_loop),写回环自验跳过")
2712
2780
 
2713
2781
  # 3.5 write_allowed 卫生检查 (P1-2): 任何层级为 true 即 WARNING (会话级临时态)
2714
2782
  print("[3.5/4] write_allowed 卫生检查 ...")
@@ -3139,7 +3207,7 @@ def set_ndtdbg_dlldir(dll_path: str) -> str:
3139
3207
  def handle_cli_setup() -> int:
3140
3208
  """Handle `firmwareloop setup` command: simultaneously configure Antigravity CLI, Claude Code CLI, Qoder IDE, CodeBuddy IDE, DeepSeek Harness (DSH), and ZCode IDE."""
3141
3209
  print("============================================================")
3142
- print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.8.0)")
3210
+ print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.9.1)")
3143
3211
  print("============================================================")
3144
3212
  print("")
3145
3213
 
@@ -3254,27 +3322,28 @@ def handle_cli_setup() -> int:
3254
3322
  print(f" - User MCP: [Warning] Could not write {zc_result['path']}: {zc_result.get('reason')}")
3255
3323
 
3256
3324
  # 7. Global Skills Sync for Antigravity and Claude Code
3325
+ # 源解析 (v0.9.1): 源码仓库 skills/<name>/SKILL.md 优先, 安装态回退包内
3326
+ # tools/templates/skill-<name>.md (skills/ 不随 wheel 分发, 此前安装态静默 0 同步)。
3327
+ print(f"\n[7] Global Skills:")
3257
3328
  ag_skills_dir = os.path.join(user_home, ".gemini", "antigravity-cli", "skills")
3258
3329
  claude_skills_dir = os.path.join(user_home, ".claude", "skills")
3259
- source_skills_dir = os.path.join(REPO_ROOT, "skills")
3260
3330
  skills_synced = 0
3261
- if os.path.exists(source_skills_dir):
3262
- for sk in ["firmwareloop", "fwloop-adapter"]:
3263
- sk_src = os.path.join(source_skills_dir, sk, "SKILL.md")
3264
- if os.path.exists(sk_src):
3265
- try:
3266
- ag_target = os.path.join(ag_skills_dir, sk)
3267
- os.makedirs(ag_target, exist_ok=True)
3268
- shutil.copyfile(sk_src, os.path.join(ag_target, "SKILL.md"))
3269
-
3270
- cl_target = os.path.join(claude_skills_dir, sk)
3271
- os.makedirs(cl_target, exist_ok=True)
3272
- shutil.copyfile(sk_src, os.path.join(cl_target, "SKILL.md"))
3273
- skills_synced += 1
3274
- except Exception:
3275
- pass
3276
-
3277
- print(f"\n[7] Global Skills:")
3331
+ for sk in ["firmwareloop", "fwloop-adapter"]:
3332
+ sk_src = _skill_template_path(sk)
3333
+ if not sk_src:
3334
+ print(f" - {sk}: [Warning] skill 源缺失 (仓库 skills/ 与包内 templates/ 均未找到)")
3335
+ continue
3336
+ try:
3337
+ ag_target = os.path.join(ag_skills_dir, sk)
3338
+ os.makedirs(ag_target, exist_ok=True)
3339
+ shutil.copyfile(sk_src, os.path.join(ag_target, "SKILL.md"))
3340
+
3341
+ cl_target = os.path.join(claude_skills_dir, sk)
3342
+ os.makedirs(cl_target, exist_ok=True)
3343
+ shutil.copyfile(sk_src, os.path.join(cl_target, "SKILL.md"))
3344
+ skills_synced += 1
3345
+ except Exception as exc: # noqa: BLE001
3346
+ print(f" - {sk}: [Warning] {exc}")
3278
3347
  print(f" - Synchronized: [OK] {skills_synced} skills (firmwareloop, fwloop-adapter) to Antigravity & Claude Code")
3279
3348
 
3280
3349
  # 8. Machine-level global lab.yaml (ndtdbg config for the globally installed tool)
@@ -3294,7 +3363,7 @@ def handle_cli_setup() -> int:
3294
3363
  def handle_cli_init(chip: Optional[str] = None, upgrade: bool = False) -> int:
3295
3364
  """Handle `firmwareloop init [target_chip]` command."""
3296
3365
  print("============================================================")
3297
- print(" FirmwareLoop Multi-Agent Project Initializer (v0.8.0)")
3366
+ print(" FirmwareLoop Multi-Agent Project Initializer (v0.9.1)")
3298
3367
  print("============================================================")
3299
3368
  cwd = os.getcwd()
3300
3369
  print(f"[*] Initializing multi-agent guidelines & bench config in:\n {cwd}\n")
@@ -3327,7 +3396,7 @@ def handle_cli_init(chip: Optional[str] = None, upgrade: bool = False) -> int:
3327
3396
 
3328
3397
 
3329
3398
  def print_cli_help() -> None:
3330
- print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.8.0)
3399
+ print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.9.1)
3331
3400
 
3332
3401
  Usage:
3333
3402
  fwloop [command] (or: firmwareloop [command])
@@ -3373,7 +3442,7 @@ def main() -> None:
3373
3442
  sys.exit(2)
3374
3443
  sys.exit(handle_cli_ndtdebugtool(args[1]))
3375
3444
  elif cmd in ["version", "-v", "--version"]:
3376
- print("FirmwareLoop v0.8.0")
3445
+ print("FirmwareLoop v0.9.1")
3377
3446
  sys.exit(0)
3378
3447
  elif cmd in ["help", "-h", "--help"]:
3379
3448
  print_cli_help()