ht32-code-mcp 0.5.2__tar.gz → 0.5.4__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 (36) hide show
  1. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/PKG-INFO +1 -1
  2. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/pyproject.toml +1 -1
  3. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/src/ht32_code_mcp/server.py +50 -7
  4. ht32_code_mcp-0.5.4/tests/test_tool_output_budget.py +78 -0
  5. ht32_code_mcp-0.5.4/tests/test_version_file.py +148 -0
  6. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/uv.lock +1 -1
  7. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/.gitignore +0 -0
  8. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/.gitkeep +0 -0
  9. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/DEPLOY.md +0 -0
  10. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/README.md +0 -0
  11. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/components.example.json +0 -0
  12. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/e2e_scenario_a.py +0 -0
  13. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/e2e_scenario_b.py +0 -0
  14. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/smoke_http.py +0 -0
  15. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/smoke_stdio.py +0 -0
  16. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/src/ht32_code_mcp/__init__.py +0 -0
  17. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/src/ht32_code_mcp/__main__.py +0 -0
  18. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/src/ht32_code_mcp/component_exporter.py +0 -0
  19. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/src/ht32_code_mcp/component_source.py +0 -0
  20. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/src/ht32_code_mcp/components.py +0 -0
  21. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/src/ht32_code_mcp/exporter.py +0 -0
  22. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/src/ht32_code_mcp/index.py +0 -0
  23. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/src/ht32_code_mcp/knowledge.py +0 -0
  24. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/src/ht32_code_mcp/materializer.py +0 -0
  25. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/src/ht32_code_mcp/release.py +0 -0
  26. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/src/ht32_code_mcp/usage.py +0 -0
  27. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/tests/conftest.py +0 -0
  28. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/tests/test_component_export.py +0 -0
  29. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/tests/test_component_redact.py +0 -0
  30. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/tests/test_component_source.py +0 -0
  31. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/tests/test_component_tools.py +0 -0
  32. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/tests/test_components.py +0 -0
  33. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/tests/test_export_cloud_mode.py +0 -0
  34. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/tests/test_knowledge_grounding.py +0 -0
  35. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/tests/test_recipe_claims.py +0 -0
  36. {ht32_code_mcp-0.5.2 → ht32_code_mcp-0.5.4}/tests/test_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ht32-code-mcp
3
- Version: 0.5.2
3
+ Version: 0.5.4
4
4
  Summary: HT32 Code MCP Server - 为 AI Agent 提供 HT32 函数库/例程/模板检索与工程导出
5
5
  License-Expression: MIT
6
6
  Keywords: firmware,holtek,ht32,mcp,mcu
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ht32-code-mcp"
7
- version = "0.5.2"
7
+ version = "0.5.4"
8
8
  description = "HT32 Code MCP Server - 为 AI Agent 提供 HT32 函数库/例程/模板检索与工程导出"
9
9
  requires-python = ">=3.11"
10
10
  license = "MIT"
@@ -132,13 +132,20 @@ try:
132
132
  except PackageNotFoundError:
133
133
  _MCP_VERSION = "unknown (开发模式)"
134
134
 
135
- _INSTRUCTIONS = """HT32 代码知识库(函数库/例程/工程模板),当前覆盖 7 个官方固件库包:
135
+ _INSTRUCTIONS = """## 入口前置(最高优先)
136
+ 本服务是 ht32 编排 Skill 的知识后端。凡涉及 HT32 开发任务(写码/建工程/编译/烧录/调试/排障),
137
+ 先调用宿主 Skill `ht32`,由它决定调用顺序与本地执行;不要用本服务工具自行拼流程。
138
+ 仅纯知识查询(查 API/例程/规格)可直接使用本服务。
139
+ 若宿主未安装 ht32 Skill,提示用户按 ht32-skills README 安装后再继续。
140
+
141
+ HT32 代码知识库(函数库/例程/工程模板),当前覆盖 7 个官方固件库包:
136
142
  M0+(HT32F5 / L5)、M3(HT32F1 / F175x-275x)、M4(F490x1 / F491x3 / F493x5)。
137
143
  写 HT32 代码必须基于本库真实函数库知识,不要编造 API/宏/路径。
138
144
 
139
145
  ## 按需求选工具
140
146
  - 确认版本/覆盖范围 → get_code_version
141
- - 问"某外设库怎么用/有哪些模块" → list_libraries → search_code_knowledge(模块名)
147
+ - 问"某外设库怎么用/有哪些模块" → list_libraries(compact=True) 或 list_libraries(module="gpio")
148
+ → search_code_knowledge(模块名);需要 raw 路径时用 list_libraries(module="<模块名>") 取,不要猜路径
142
149
  - 找官方例程(按外设/系列/芯片) → find_example → get_file(例程路径) 看原码
143
150
  - 取 raw/ 真实源文件(驱动 .c/.h、例程、devices.json 等) → get_file
144
151
  - 检索知识页(初始化套路/易错点/子系列差异) → search_code_knowledge
@@ -304,32 +311,59 @@ def get_code_version() -> str:
304
311
 
305
312
 
306
313
  @mcp.tool()
307
- def list_libraries() -> str:
314
+ def list_libraries(series: str = "", module: str = "", compact: bool = False) -> str:
308
315
  """枚举函数库模块与覆盖系列。
309
316
 
310
317
  返回每个库模块的驱动源码/头文件路径(raw/)、变体数、
311
318
  对应官方例程数与知识页路径(entities/libraries/)。
319
+
320
+ Args:
321
+ series: 系列或内核家族过滤(f5 / l5 / F493x5 / M0+ / M3 / M4),默认不过滤
322
+ module: 模块名子串过滤(如 gpio / usart),只列命中模块并保留完整路径
323
+ compact: True 时每包一行只给模块名清单(省 token);需要路径时用
324
+ module= 或 compact=False
312
325
  """
313
326
  fwlibs = index.data.get("fwlibs", [])
314
327
  families: dict[str, list[str]] = {}
315
328
  for p in fwlibs:
316
329
  families.setdefault(p.get("family") or "?", []).append(
317
330
  "/".join(p.get("series") or ["?"]))
331
+ # 表头始终按全量固件库计算:migrate 靠「包 → 系列」判定同系列,过滤后不能丢
318
332
  lines = ["库模块清单(模块名按官方驱动 .c 文件名归一):",
319
333
  f"覆盖 {len(fwlibs)} 个固件库包 / {len(families)} 个内核家族:"
320
334
  + ";".join(f"{k}({'、'.join(v)})" for k, v in sorted(families.items()))]
335
+ pool = fwlibs
336
+ if series:
337
+ pool = _filter_by_series(pool, series)
338
+ if not pool:
339
+ return (f"系列 '{series}' 不在覆盖范围。当前覆盖:"
340
+ + "、".join(
341
+ f"{p['name']}({'/'.join(p.get('series') or ['?'])},"
342
+ f"{p.get('family') or '?'})" for p in fwlibs))
343
+ lines.append(f"(已按系列 '{series}' 过滤,命中 {len(pool)} 个包)")
321
344
  page_paths = {p["path"] for p in index.data.get("knowledge_pages", [])}
322
- for pkg in fwlibs:
323
- series = "/".join(pkg.get("series") or ["?"]) + (f"({pkg['family']})" if pkg.get("family") else "")
345
+ want = module.strip().lower()
346
+ matched = False
347
+ for pkg in pool:
348
+ series_label = "/".join(pkg.get("series") or ["?"]) + (
349
+ f"({pkg['family']})" if pkg.get("family") else "")
324
350
  ex_counts: dict[str, int] = {}
325
351
  for ex in pkg.get("examples", []):
326
352
  ex_counts[ex["peripheral"].lower()] = ex_counts.get(ex["peripheral"].lower(), 0) + 1
327
353
  modules: dict[str, list[dict]] = {}
328
354
  for m in pkg.get("modules", []):
329
355
  modules.setdefault(m["module"], []).append(m)
356
+ if want:
357
+ modules = {k: v for k, v in modules.items() if want in k.lower()}
358
+ if not modules:
359
+ continue
360
+ matched = True
330
361
  lines.append("")
331
- lines.append(f"== 固件库 {pkg['name']}(系列 {series})== "
362
+ lines.append(f"== 固件库 {pkg['name']}(系列 {series_label})== "
332
363
  f"共 {len(modules)} 个模块")
364
+ if compact:
365
+ lines.append("、".join(sorted(modules)))
366
+ continue
333
367
  for mod in sorted(modules):
334
368
  variants = modules[mod]
335
369
  srcs = "、".join(v["src"] for v in variants)
@@ -339,8 +373,17 @@ def list_libraries() -> str:
339
373
  f"- {mod}:{len(variants)} 个驱动变体 | {srcs}"
340
374
  f" | 例程 {ex_counts.get(mod, 0)} 个 | 知识页 {page_note}"
341
375
  )
376
+ if want and not matched:
377
+ # 模块名按驱动文件名归一(usart 而非 uart),查不到时回退列全部模块名
378
+ all_mods = sorted({m["module"] for p in fwlibs for m in p.get("modules", [])})
379
+ return (f"未找到模块 '{module}'。可用模块({len(all_mods)} 个):"
380
+ + "、".join(all_mods))
342
381
  lines.append("")
343
- lines.append("用法:模块详情查 search_code_knowledge(模块名);原码用 get_file(路径)。")
382
+ if compact:
383
+ lines.append("需要路径/例程数/知识页:list_libraries(module=\"<模块名>\") "
384
+ "或 list_libraries(compact=False)。")
385
+ else:
386
+ lines.append("用法:模块详情查 search_code_knowledge(模块名);原码用 get_file(路径)。")
344
387
  lines.append("注:模块名按驱动文件名归一,跨包同名模块(如 gpio)在各包下独立列出,"
345
388
  "选型时以目标芯片所属系列为准。")
346
389
  return "\n".join(lines)
@@ -0,0 +1,78 @@
1
+ """工具输出体积预算 + list_libraries 过滤行为门禁。
2
+
3
+ 目的:`list_libraries` 全量输出曾达 28.5k 字符(~12–14k token),是单次调用最大开销。
4
+ 本文件把 compact / module 过滤的收益钉住,同时守住三条准确度约束:
5
+ 1. 表头「包 → 系列」始终按全量固件库计算(migrate 靠它判定同系列)
6
+ 2. module 无命中时回退列出全部模块名(模块名按驱动文件名归一,uart≠usart)
7
+ 3. compact 输出带「怎么取路径」的指引
8
+
9
+ 运行(从 .work/mcp-server/):pytest tests/test_tool_output_budget.py
10
+ """
11
+ from __future__ import annotations
12
+
13
+ from ht32_code_mcp import server as srv
14
+
15
+
16
+ # ---------------------------------------------------------------- 体积预算
17
+ def test_list_libraries_compact_budget():
18
+ out = srv.list_libraries(compact=True)
19
+ assert len(out) <= 4000, f"compact 全量 {len(out)} 字符,超出 4000 预算"
20
+
21
+
22
+ def test_list_libraries_module_budget():
23
+ out = srv.list_libraries(module="gpio")
24
+ assert len(out) <= 2000, f"module=gpio {len(out)} 字符,超出 2000 预算"
25
+
26
+
27
+ def test_list_libraries_full_budget():
28
+ out = srv.list_libraries()
29
+ assert len(out) <= 30000, f"全量 {len(out)} 字符,超出 30000 预算(防回涨)"
30
+
31
+
32
+ def test_get_code_version_budget():
33
+ out = srv.get_code_version()
34
+ assert len(out) <= 1500, f"get_code_version {len(out)} 字符,超出 1500 预算"
35
+
36
+
37
+ # ---------------------------------------------------------------- 准确度约束
38
+ def test_compact_keeps_package_series_header():
39
+ """约束 1:过滤后表头仍列全部固件库包与内核家族。"""
40
+ full = srv.list_libraries(compact=True)
41
+ filtered = srv.list_libraries(series="f5", compact=True)
42
+ header = full.splitlines()[1]
43
+ assert header in filtered, "按系列过滤后表头丢失了全量包/家族信息"
44
+ assert "覆盖" in header and "内核家族" in header
45
+
46
+
47
+ def test_series_filter_narrows_packages():
48
+ out = srv.list_libraries(series="f5", compact=True)
49
+ assert "已按系列 'f5' 过滤" in out
50
+ assert "HT32_STD_5xxxx_FWLib" in out
51
+ assert "HT32F493x5_FWLib" not in out
52
+
53
+
54
+ def test_unknown_series_lists_coverage():
55
+ out = srv.list_libraries(series="f9")
56
+ assert "不在覆盖范围" in out
57
+ assert "HT32_STD_5xxxx_FWLib" in out
58
+
59
+
60
+ def test_module_filter_keeps_paths():
61
+ out = srv.list_libraries(module="gpio")
62
+ assert "gpio" in out
63
+ assert "raw/" in out, "module 过滤必须保留 raw 路径,否则 agent 只能猜路径"
64
+ assert "entities/libraries/gpio.md" in out
65
+
66
+
67
+ def test_unknown_module_falls_back_to_all_names():
68
+ """约束 2:查不到时回退列全部模块名,避免 agent 误判模块不存在。"""
69
+ out = srv.list_libraries(module="uart")
70
+ assert "未找到模块 'uart'" in out
71
+ assert "可用模块" in out
72
+ assert "usart" in out
73
+
74
+
75
+ def test_compact_has_path_hint():
76
+ """约束 3:compact 输出必须告诉 agent 怎么取路径。"""
77
+ out = srv.list_libraries(compact=True)
78
+ assert 'list_libraries(module="<模块名>")' in out
@@ -0,0 +1,148 @@
1
+ """VERSION 文件格式门禁:固定 6 行,禁止回涨成变更日志。
2
+
3
+ 为什么需要这道门禁
4
+ ------------------
5
+ `get_code_version` 会把 VERSION **全文**回显给 agent,VERSION 每多写一段变更说明,
6
+ 每次版本查询就多烧一份 token。历史上这份文件长成了「多版本并列 + run-on 变更史」
7
+ (1.3 KB),靠人自觉守不住,用测试钉死格式。
8
+
9
+ 格式(见 AGENTS.md 核心规则 10):
10
+ vX.Y.Z
11
+ Date: YYYY-MM-DD
12
+ Pages: N(可选括号细分)
13
+ Sources: <官方固件库包目录名> / ... ← 与 raw/ 目录名一致
14
+ Components: <组件 id> <默认 tag> / ... ← 与 .work/components/components.json 一致
15
+ MCP Server: X.Y.Z
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import re
21
+ import tomllib
22
+
23
+ import pytest
24
+
25
+ from ht32_code_mcp import server as srv
26
+
27
+ MAX_LINES = 6
28
+ MAX_BYTES = 400
29
+ MAX_LINE_CHARS = 120
30
+ MAX_SOURCES_CHARS = 240 # 官方包全名较长,单独放宽
31
+
32
+ _FIRST_LINE = re.compile(r"^v\d+\.\d+\.\d+$")
33
+ _DATE_LINE = re.compile(r"^Date:\s*\d{4}-\d{2}-\d{2}$")
34
+ _PAGES_LINE = re.compile(r"^Pages:\s*\d+")
35
+ _SOURCES_LINE = re.compile(r"^Sources:\s*\S")
36
+ _COMPONENTS_LINE = re.compile(r"^Components:\s*\S")
37
+ _MCP_LINE = re.compile(r"^MCP Server:\s*(\d+\.\d+\.\d+)$")
38
+
39
+
40
+ def _lines() -> list[str]:
41
+ text = (srv.CODE_ROOT / "VERSION").read_text(encoding="utf-8")
42
+ return [ln for ln in text.splitlines() if ln.strip()]
43
+
44
+
45
+ def _segments(line: str) -> list[str]:
46
+ return [seg.strip() for seg in line.split(":", 1)[1].split(" / ") if seg.strip()]
47
+
48
+
49
+ def test_version_file_line_count_and_size():
50
+ text = (srv.CODE_ROOT / "VERSION").read_text(encoding="utf-8")
51
+ lines = _lines()
52
+ assert len(lines) <= MAX_LINES, (
53
+ f"VERSION 最多 {MAX_LINES} 行,实际 {len(lines)} 行:{lines}\n"
54
+ "变更说明请写进 log.md,不要写进 VERSION")
55
+ assert len(text.encode("utf-8")) <= MAX_BYTES, (
56
+ f"VERSION 体积 {len(text.encode('utf-8'))} 字节,超过 {MAX_BYTES} 字节上限")
57
+ for line in lines:
58
+ limit = MAX_SOURCES_CHARS if line.startswith("Sources:") else MAX_LINE_CHARS
59
+ assert len(line) <= limit, f"VERSION 单行超过 {limit} 字符:{line}"
60
+
61
+
62
+ def test_version_file_fields():
63
+ lines = _lines()
64
+ assert len(lines) == MAX_LINES, f"VERSION 应为 {MAX_LINES} 行,实际 {len(lines)} 行:{lines}"
65
+ assert _FIRST_LINE.match(lines[0]), f"首行必须是纯版本号(vX.Y.Z):{lines[0]}"
66
+ assert _DATE_LINE.match(lines[1]), f"第 2 行必须是 'Date: YYYY-MM-DD':{lines[1]}"
67
+ assert _PAGES_LINE.match(lines[2]), f"第 3 行必须是 'Pages: N':{lines[2]}"
68
+ assert _SOURCES_LINE.match(lines[3]), (
69
+ f"第 4 行必须是 'Sources: <官方包目录名> / ...':{lines[3]}")
70
+ assert _COMPONENTS_LINE.match(lines[4]), (
71
+ f"第 5 行必须是 'Components: <id> <tag> / ...':{lines[4]}")
72
+ assert _MCP_LINE.match(lines[5]), (
73
+ f"末行必须是 'MCP Server: X.Y.Z'(只写版本号,不写 deployed/日期/变更说明):{lines[5]}")
74
+
75
+
76
+ def test_version_sources_are_real_raw_packages():
77
+ """Sources 的每一项必须是 raw/ 下真实存在的官方包目录,且包数与索引一致。"""
78
+ segments = _segments(_lines()[3])
79
+ raw = srv.CODE_ROOT / "raw"
80
+ for seg in segments:
81
+ assert (raw / seg).is_dir(), f"Sources 里的 '{seg}' 不是 raw/ 下的真实目录"
82
+ packages = srv.index.data.get("fwlibs", [])
83
+ assert len(segments) == len(packages), (
84
+ f"Sources 登记 {len(segments)} 个官方包,索引里有 {len(packages)} 个:{segments}")
85
+
86
+
87
+ def test_version_components_match_catalog():
88
+ """Components 的每一项必须与私有组件登记表的 id + 默认 tag 一致。"""
89
+ catalog_path = srv.CODE_ROOT / ".work" / "components" / "components.json"
90
+ if not catalog_path.is_file():
91
+ pytest.skip("未配置私有组件登记表")
92
+ catalog = json.loads(catalog_path.read_text(encoding="utf-8"))
93
+ expected = {c["id"]: c.get("default_tag", "") for c in catalog.get("components", [])}
94
+ actual = {}
95
+ for seg in _segments(_lines()[4]):
96
+ parts = seg.split()
97
+ assert len(parts) == 2, f"Components 项格式应为 '<id> <tag>':{seg}"
98
+ actual[parts[0]] = parts[1]
99
+ assert actual == expected, f"Components {actual} 与登记表 {expected} 不一致"
100
+
101
+
102
+ def test_version_mcp_line_matches_pyproject():
103
+ """VERSION 末行与 pyproject.toml 的 version 必须一致(发布流程的硬约束)。"""
104
+ pyproject = srv.CODE_ROOT / ".work" / "mcp-server" / "pyproject.toml"
105
+ with pyproject.open("rb") as fh:
106
+ pkg_version = tomllib.load(fh)["project"]["version"]
107
+ match = _MCP_LINE.match(_lines()[-1])
108
+ assert match, "VERSION 末行格式不对,无法比对 pyproject 版本"
109
+ assert match.group(1) == pkg_version, (
110
+ f"VERSION 末行 {match.group(1)} != pyproject.toml {pkg_version}")
111
+
112
+
113
+ def test_release_version_pins_match():
114
+ """发布版本四处必须一致:pyproject / VERSION 末行 / Dockerfile ARG / compose 镜像 tag。
115
+
116
+ 历史问题:0.5.3 发布时只改了 pyproject 与 VERSION,Dockerfile 与 compose 仍钉在
117
+ 0.5.2,线上容器装的是旧包,`get_code_version` 头部与 VERSION 回显不一致。
118
+ """
119
+ pyproject = srv.CODE_ROOT / ".work" / "mcp-server" / "pyproject.toml"
120
+ with pyproject.open("rb") as fh:
121
+ pyproject_version = tomllib.load(fh)["project"]["version"]
122
+
123
+ match = _MCP_LINE.match(_lines()[-1])
124
+ assert match, "VERSION 末行格式不对,无法比对版本"
125
+ version_line = match.group(1)
126
+
127
+ dockerfile = srv.CODE_ROOT / ".work" / "docker" / "Dockerfile"
128
+ docker_match = re.search(
129
+ r"^ARG\s+HT32_CODE_MCP_VERSION=(\d+\.\d+\.\d+)\s*$",
130
+ dockerfile.read_text(encoding="utf-8"), re.MULTILINE)
131
+ assert docker_match, "Dockerfile 缺少 'ARG HT32_CODE_MCP_VERSION=X.Y.Z'"
132
+ docker_version = docker_match.group(1)
133
+
134
+ compose = srv.CODE_ROOT / ".work" / "docker" / "docker-compose.yml"
135
+ compose_match = re.search(
136
+ r"image:\s*ht32-code-mcp:(\d+\.\d+\.\d+)",
137
+ compose.read_text(encoding="utf-8"))
138
+ assert compose_match, "docker-compose.yml 缺少 'image: ht32-code-mcp:X.Y.Z'"
139
+ compose_version = compose_match.group(1)
140
+
141
+ versions = {
142
+ "pyproject.toml": pyproject_version,
143
+ "VERSION": version_line,
144
+ "Dockerfile": docker_version,
145
+ "docker-compose.yml": compose_version,
146
+ }
147
+ assert len(set(versions.values())) == 1, (
148
+ "发布版本四处必须一致,当前:" + ", ".join(f"{k}={v}" for k, v in versions.items()))
@@ -440,7 +440,7 @@ wheels = [
440
440
 
441
441
  [[package]]
442
442
  name = "ht32-code-mcp"
443
- version = "0.5.2"
443
+ version = "0.5.4"
444
444
  source = { editable = "." }
445
445
  dependencies = [
446
446
  { name = "fastmcp" },
File without changes
File without changes
File without changes
File without changes