devmate 2.5.1__tar.gz → 2.5.2__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 (66) hide show
  1. {devmate-2.5.1 → devmate-2.5.2}/PKG-INFO +1 -1
  2. {devmate-2.5.1 → devmate-2.5.2}/pyproject.toml +1 -1
  3. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/__init__.py +1 -1
  4. devmate-2.5.2/src/devmate/sub_agent/__init__.py +237 -0
  5. {devmate-2.5.1 → devmate-2.5.2}/src/devmate.egg-info/PKG-INFO +1 -1
  6. devmate-2.5.1/src/devmate/sub_agent/__init__.py +0 -130
  7. {devmate-2.5.1 → devmate-2.5.2}/README.md +0 -0
  8. {devmate-2.5.1 → devmate-2.5.2}/setup.cfg +0 -0
  9. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/__main__.py +0 -0
  10. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/agent/validators.py +0 -0
  11. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/agent_eval/__init__.py +0 -0
  12. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/ai_config/__init__.py +0 -0
  13. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/ai_scenarios/__init__.py +0 -0
  14. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/app/__init__.py +0 -0
  15. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/app/cli.py +0 -0
  16. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/app/tui.py +0 -0
  17. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/app/web/__init__.py +0 -0
  18. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/app/web/cli_docs.py +0 -0
  19. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/bus/__init__.py +0 -0
  20. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/bus/bus.py +0 -0
  21. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/bus/command.py +0 -0
  22. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/core/__init__.py +0 -0
  23. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/core/config.py +0 -0
  24. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/core/logging.py +0 -0
  25. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/core/security.py +0 -0
  26. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/events/__init__.py +0 -0
  27. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/events/bus.py +0 -0
  28. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/events/domain_event.py +0 -0
  29. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/mcp/__init__.py +0 -0
  30. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/memory_store/__init__.py +0 -0
  31. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/module/__init__.py +0 -0
  32. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/module/registry.py +0 -0
  33. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/plugin/__init__.py +0 -0
  34. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/plugin_market/__init__.py +0 -0
  35. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/plugin_market/ratings.py +0 -0
  36. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/plugin_market/remote.py +0 -0
  37. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/rag/__init__.py +0 -0
  38. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/security/audit.py +0 -0
  39. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/security/vault.py +0 -0
  40. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/share/__init__.py +0 -0
  41. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/skill/__init__.py +0 -0
  42. {devmate-2.5.1 → devmate-2.5.2}/src/devmate/workflow/__init__.py +0 -0
  43. {devmate-2.5.1 → devmate-2.5.2}/src/devmate.egg-info/SOURCES.txt +0 -0
  44. {devmate-2.5.1 → devmate-2.5.2}/src/devmate.egg-info/dependency_links.txt +0 -0
  45. {devmate-2.5.1 → devmate-2.5.2}/src/devmate.egg-info/entry_points.txt +0 -0
  46. {devmate-2.5.1 → devmate-2.5.2}/src/devmate.egg-info/requires.txt +0 -0
  47. {devmate-2.5.1 → devmate-2.5.2}/src/devmate.egg-info/top_level.txt +0 -0
  48. {devmate-2.5.1 → devmate-2.5.2}/src/devmate_agent/__init__.py +0 -0
  49. {devmate-2.5.1 → devmate-2.5.2}/src/devmate_apidev/__init__.py +0 -0
  50. {devmate-2.5.1 → devmate-2.5.2}/src/devmate_apihub/__init__.py +0 -0
  51. {devmate-2.5.1 → devmate-2.5.2}/src/devmate_dbadmin/__init__.py +0 -0
  52. {devmate-2.5.1 → devmate-2.5.2}/src/devmate_gitflow/__init__.py +0 -0
  53. {devmate-2.5.1 → devmate-2.5.2}/src/devmate_monitor/__init__.py +0 -0
  54. {devmate-2.5.1 → devmate-2.5.2}/src/devmate_notekeeper/__init__.py +0 -0
  55. {devmate-2.5.1 → devmate-2.5.2}/src/devmate_plugins/__init__.py +0 -0
  56. {devmate-2.5.1 → devmate-2.5.2}/src/devmate_plugins/code_stats.py +0 -0
  57. {devmate-2.5.1 → devmate-2.5.2}/src/devmate_plugins/env_switch.py +0 -0
  58. {devmate-2.5.1 → devmate-2.5.2}/src/devmate_plugins/file_watch.py +0 -0
  59. {devmate-2.5.1 → devmate-2.5.2}/src/devmate_plugins/pomodoro.py +0 -0
  60. {devmate-2.5.1 → devmate-2.5.2}/src/devmate_plugins/port_scan.py +0 -0
  61. {devmate-2.5.1 → devmate-2.5.2}/src/devmate_publisher/__init__.py +0 -0
  62. {devmate-2.5.1 → devmate-2.5.2}/src/devmate_regexlab/__init__.py +0 -0
  63. {devmate-2.5.1 → devmate-2.5.2}/src/devmate_reporter/__init__.py +0 -0
  64. {devmate-2.5.1 → devmate-2.5.2}/src/devmate_scaffold/__init__.py +0 -0
  65. {devmate-2.5.1 → devmate-2.5.2}/src/devmate_sshman/__init__.py +0 -0
  66. {devmate-2.5.1 → devmate-2.5.2}/src/devmate_toolkit/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devmate
3
- Version: 2.5.1
3
+ Version: 2.5.2
4
4
  Summary: 程序员终端瑞士军刀 + AI 原生平台——替代 5 个独立工具,一个 CLI 搞定
5
5
  Author-email: DevMate Team <devmate@example.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devmate"
7
- version = "2.5.1"
7
+ version = "2.5.2"
8
8
  description = "程序员终端瑞士军刀 + AI 原生平台——替代 5 个独立工具,一个 CLI 搞定"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -1,6 +1,6 @@
1
1
  """
2
2
  DevMate — 程序员终端瑞士军刀
3
3
  """
4
- __version__ = "2.5.1"
4
+ __version__ = "2.5.2"
5
5
  __app_name__ = "DevMate"
6
6
  __description__ = "程序员终端瑞士军刀——替代 5 个独立工具,一个 CLI 搞定"
@@ -0,0 +1,237 @@
1
+ """devmate.sub_agent — Sub-agent 隔离(P0-⑥ · Context Isolate)
2
+
3
+ 方法论:Context Engineering 五动作之一「隔离(Isolate)」
4
+ - 旁支任务委派给独立上下文的 Sub-agent
5
+ - 主 Agent 不承载子任务细节,只拿回结构化结果摘要
6
+ - 长任务 → 拆子任务 → 各子任务独立上下文 → 汇总
7
+ - 输出契约(多 Agent 工程三原则):每个子任务声明期望输出格式
8
+ 缺失字段 → 标记 schema_failed(关键节点校验)
9
+
10
+ 设计:
11
+ - Sub-agent 复用 AgentRuntime(独立 SessionMemory,互不污染)
12
+ - 结果自动截断/摘要,只回传关键信息给主上下文
13
+ - 支持并行执行多个子任务(ThreadPoolExecutor)
14
+ - 输出契约:expected_schema 声明字段名→类型,结果校验缺字段/类型错
15
+ """
16
+
17
+ import json
18
+ import time
19
+ from dataclasses import dataclass, field
20
+ from pathlib import Path # noqa: F401
21
+
22
+ from devmate.bus.command import CommandResult
23
+
24
+ # 允许的 schema 类型
25
+ _SCHEMA_TYPES = {"string": str, "int": int, "float": (int, float),
26
+ "bool": bool, "list": list, "dict": dict, "any": object}
27
+
28
+
29
+ @dataclass
30
+ class SubTask:
31
+ """子任务定义"""
32
+ name: str # 子任务名
33
+ prompt: str # 子任务指令(独立上下文)
34
+ max_steps: int = 5 # 最大步数
35
+ tool_whitelist: list[str] = field(default_factory=list) # 允许的工具白名单
36
+ expected_schema: dict | None = None # 输出契约 {字段: 类型}(如 {"host": "string"})
37
+ schema_required: bool = True # 契约校验失败是否算任务失败
38
+
39
+ def to_dict(self) -> dict:
40
+ return {"name": self.name, "prompt": self.prompt,
41
+ "max_steps": self.max_steps,
42
+ "tool_whitelist": self.tool_whitelist,
43
+ "expected_schema": self.expected_schema}
44
+
45
+
46
+ def validate_schema(output: str, schema: dict) -> tuple[bool, list[str]]:
47
+ """校验输出是否符合契约
48
+
49
+ 策略(宽容解析):
50
+ 1. 尝试 JSON 解析(输出可能是 JSON)
51
+ 2. 否则尝试 key: value 逐行解析
52
+ 3. 从文本中按字段名查找
53
+
54
+ Returns:
55
+ (是否通过, 缺失/错误字段列表)
56
+ """
57
+ if not schema:
58
+ return True, []
59
+
60
+ # 尝试 JSON 解析
61
+ obj = None
62
+ try:
63
+ obj = json.loads(output)
64
+ except Exception:
65
+ pass
66
+
67
+ issues = []
68
+ for field_name, field_type in schema.items():
69
+ # 定位值
70
+ value = None
71
+ if isinstance(obj, dict):
72
+ value = obj.get(field_name)
73
+ elif obj is not None:
74
+ continue # JSON 数组等,无法按字段校验
75
+ else:
76
+ # 行解析:key: value
77
+ for line in output.splitlines():
78
+ line = line.strip().lstrip("-• ")
79
+ if ":" in line:
80
+ k, _, v = line.partition(":")
81
+ if k.strip().strip('"').strip("'") == field_name:
82
+ value = v.strip()
83
+ break
84
+ if value is None:
85
+ # 全文包含字段名
86
+ if field_name in output:
87
+ continue # 出现但无法解析值,视为通过
88
+
89
+ if value is None or value == "":
90
+ issues.append(f"{field_name} 缺失")
91
+ continue
92
+
93
+ # 类型校验(宽容:去掉逗号/百分号/中文单位后缀)
94
+ expected_type = _SCHEMA_TYPES.get(field_type, object)
95
+ try:
96
+ clean = str(value).replace(",", "").replace("%", "")
97
+ # 去常见中英文单位后缀(核/GB/MB/秒/ms/元/%)
98
+ for unit in ("核", "个", "台", "GB", "MB", "KB", "秒", "ms", "元",
99
+ "字节", "张", "条"):
100
+ clean = clean.removesuffix(unit)
101
+ clean = clean.strip()
102
+ if field_type == "int":
103
+ int(clean)
104
+ elif field_type == "float":
105
+ float(clean)
106
+ elif field_type == "string":
107
+ str(value)
108
+ elif field_type in ("list", "dict"):
109
+ if not isinstance(value, expected_type):
110
+ issues.append(f"{field_name} 类型错误(期望 {field_type})")
111
+ elif field_type == "bool":
112
+ if str(value).lower() not in ("true", "false", "1", "0", "是", "否"):
113
+ issues.append(f"{field_name} 类型错误(期望 bool)")
114
+ elif field_type == "any":
115
+ pass
116
+ elif not isinstance(value, expected_type):
117
+ issues.append(f"{field_name} 类型错误(期望 {field_type})")
118
+ except (ValueError, TypeError):
119
+ issues.append(f"{field_name} 类型错误(期望 {field_type})")
120
+
121
+ return not issues, issues
122
+
123
+
124
+ @dataclass
125
+ class SubTaskResult:
126
+ """子任务结果"""
127
+ name: str
128
+ success: bool
129
+ summary: str = ""
130
+ duration_ms: float = 0.0
131
+ steps: int = 0
132
+ error: str = ""
133
+ schema_ok: bool = True # 输出契约校验是否通过
134
+ schema_missing: list[str] = field(default_factory=list) # 缺失/错误字段
135
+
136
+ def to_dict(self) -> dict:
137
+ return {"name": self.name, "success": self.success,
138
+ "summary": self.summary[:300],
139
+ "duration_ms": round(self.duration_ms, 1),
140
+ "steps": self.steps, "error": self.error[:200],
141
+ "schema_ok": self.schema_ok,
142
+ "schema_missing": self.schema_missing}
143
+
144
+
145
+ def run_subtask(task: SubTask, workdir: str = ".") -> SubTaskResult:
146
+ """执行单个子任务(独立上下文)
147
+
148
+ 用 AgentRuntime 跑,但:
149
+ - 独立 SessionMemory(不污染主 Agent)
150
+ - 独立工具环境(tool_whitelist 过滤)
151
+ - 结果只回传摘要 + 输出契约校验(expected_schema)
152
+ """
153
+ start = time.time()
154
+ try:
155
+ from devmate_agent import AgentRuntime
156
+
157
+ runtime = AgentRuntime()
158
+
159
+ # 工具白名单过滤(隔离:子任务只看到允许的工具)
160
+ if task.tool_whitelist:
161
+ allowed = set(task.tool_whitelist)
162
+ for tname in list(runtime.tools.list_names()):
163
+ if tname not in allowed:
164
+ runtime.tools.unregister(tname)
165
+
166
+ # 运行子任务(dry_run=False 真实执行)
167
+ result = runtime.run(task.prompt, max_steps=task.max_steps)
168
+
169
+ if result.success:
170
+ data = result.data
171
+ summary = str(data.get("final_output", data.get("output", "")))[:300]
172
+ # 输出契约校验(关键节点校验:缺失字段标记失败)
173
+ schema_ok, missing = True, []
174
+ if task.expected_schema:
175
+ schema_ok, missing = validate_schema(summary, task.expected_schema)
176
+ if not schema_ok and task.schema_required:
177
+ return SubTaskResult(
178
+ name=task.name, success=False,
179
+ error=f"输出契约校验失败: 缺 {', '.join(missing)}",
180
+ summary=summary, schema_ok=False,
181
+ schema_missing=missing,
182
+ duration_ms=(time.time() - start) * 1000,
183
+ steps=data.get("step_count", data.get("total_steps", 0)),
184
+ )
185
+ return SubTaskResult(
186
+ name=task.name, success=True, summary=summary,
187
+ duration_ms=(time.time() - start) * 1000,
188
+ steps=data.get("step_count", data.get("total_steps", 0)),
189
+ schema_ok=schema_ok, schema_missing=missing,
190
+ )
191
+ return SubTaskResult(
192
+ name=task.name, success=False,
193
+ error=str(result.error or "任务失败"),
194
+ duration_ms=(time.time() - start) * 1000,
195
+ )
196
+ except Exception as e:
197
+ return SubTaskResult(
198
+ name=task.name, success=False, error=str(e),
199
+ duration_ms=(time.time() - start) * 1000,
200
+ )
201
+
202
+
203
+ def run_subtasks(tasks: list[SubTask], workdir: str = ".",
204
+ parallel: bool = True) -> CommandResult:
205
+ """并行/串行执行多个子任务
206
+
207
+ Args:
208
+ tasks: 子任务列表
209
+ workdir: 工作目录
210
+ parallel: 是否并行(默认 True)
211
+ """
212
+ results = []
213
+ if parallel and len(tasks) > 1:
214
+ from concurrent.futures import ThreadPoolExecutor
215
+ with ThreadPoolExecutor(max_workers=min(len(tasks), 4)) as pool:
216
+ futures = {pool.submit(run_subtask, t, workdir): t.name
217
+ for t in tasks}
218
+ # 保持任务顺序
219
+ ordered = [futures[f] for f in futures]
220
+ for f, name in zip(futures, ordered):
221
+ results.append(f.result())
222
+ else:
223
+ for t in tasks:
224
+ results.append(run_subtask(t, workdir))
225
+
226
+ success = sum(1 for r in results if r.success)
227
+ total = len(results)
228
+ return CommandResult(success=success == total, data={
229
+ "results": [r.to_dict() for r in results],
230
+ "success_count": success,
231
+ "total": total,
232
+ "message": f"Sub-agents: {success}/{total} 成功",
233
+ "summary": "\n".join(
234
+ f"[{'✅' if r.success else '❌'}] {r.name}: "
235
+ f"{r.summary or r.error}" for r in results
236
+ )[:800],
237
+ })
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devmate
3
- Version: 2.5.1
3
+ Version: 2.5.2
4
4
  Summary: 程序员终端瑞士军刀 + AI 原生平台——替代 5 个独立工具,一个 CLI 搞定
5
5
  Author-email: DevMate Team <devmate@example.com>
6
6
  License: MIT
@@ -1,130 +0,0 @@
1
- """devmate.sub_agent — Sub-agent 隔离(P0-⑥ · Context Isolate)
2
-
3
- 方法论:Context Engineering 五动作之一「隔离(Isolate)」
4
- - 旁支任务委派给独立上下文的 Sub-agent
5
- - 主 Agent 不承载子任务细节,只拿回结构化结果摘要
6
- - 长任务 → 拆子任务 → 各子任务独立上下文 → 汇总
7
-
8
- 设计:
9
- - Sub-agent 复用 AgentRuntime(独立 SessionMemory,互不污染)
10
- - 结果自动截断/摘要,只回传关键信息给主上下文
11
- - 支持并行执行多个子任务(ThreadPoolExecutor)
12
- """
13
-
14
- import time
15
- from dataclasses import dataclass, field
16
- from pathlib import Path # noqa: F401
17
-
18
- from devmate.bus.command import CommandResult
19
-
20
-
21
- @dataclass
22
- class SubTask:
23
- """子任务定义"""
24
- name: str # 子任务名
25
- prompt: str # 子任务指令(独立上下文)
26
- max_steps: int = 5 # 最大步数
27
- tool_whitelist: list[str] = field(default_factory=list) # 允许的工具白名单
28
-
29
- def to_dict(self) -> dict:
30
- return {"name": self.name, "prompt": self.prompt,
31
- "max_steps": self.max_steps,
32
- "tool_whitelist": self.tool_whitelist}
33
-
34
-
35
- @dataclass
36
- class SubTaskResult:
37
- """子任务结果"""
38
- name: str
39
- success: bool
40
- summary: str = ""
41
- duration_ms: float = 0.0
42
- steps: int = 0
43
- error: str = ""
44
-
45
- def to_dict(self) -> dict:
46
- return {"name": self.name, "success": self.success,
47
- "summary": self.summary[:300],
48
- "duration_ms": round(self.duration_ms, 1),
49
- "steps": self.steps, "error": self.error[:200]}
50
-
51
-
52
- def run_subtask(task: SubTask, workdir: str = ".") -> SubTaskResult:
53
- """执行单个子任务(独立上下文)
54
-
55
- 用 AgentRuntime 跑,但:
56
- - 独立 SessionMemory(不污染主 Agent)
57
- - 独立工具环境(tool_whitelist 过滤)
58
- - 结果只回传摘要
59
- """
60
- start = time.time()
61
- try:
62
- from devmate_agent import AgentRuntime
63
-
64
- runtime = AgentRuntime()
65
-
66
- # 工具白名单过滤(隔离:子任务只看到允许的工具)
67
- if task.tool_whitelist:
68
- allowed = set(task.tool_whitelist)
69
- for tname in list(runtime.tools.list_names()):
70
- if tname not in allowed:
71
- runtime.tools.unregister(tname)
72
-
73
- # 运行子任务(dry_run=False 真实执行)
74
- result = runtime.run(task.prompt, max_steps=task.max_steps)
75
-
76
- if result.success:
77
- data = result.data
78
- summary = str(data.get("final_output", data.get("output", "")))[:300]
79
- return SubTaskResult(
80
- name=task.name, success=True, summary=summary,
81
- duration_ms=(time.time() - start) * 1000,
82
- steps=data.get("step_count", data.get("total_steps", 0)),
83
- )
84
- return SubTaskResult(
85
- name=task.name, success=False,
86
- error=str(result.error or "任务失败"),
87
- duration_ms=(time.time() - start) * 1000,
88
- )
89
- except Exception as e:
90
- return SubTaskResult(
91
- name=task.name, success=False, error=str(e),
92
- duration_ms=(time.time() - start) * 1000,
93
- )
94
-
95
-
96
- def run_subtasks(tasks: list[SubTask], workdir: str = ".",
97
- parallel: bool = True) -> CommandResult:
98
- """并行/串行执行多个子任务
99
-
100
- Args:
101
- tasks: 子任务列表
102
- workdir: 工作目录
103
- parallel: 是否并行(默认 True)
104
- """
105
- results = []
106
- if parallel and len(tasks) > 1:
107
- from concurrent.futures import ThreadPoolExecutor
108
- with ThreadPoolExecutor(max_workers=min(len(tasks), 4)) as pool:
109
- futures = {pool.submit(run_subtask, t, workdir): t.name
110
- for t in tasks}
111
- # 保持任务顺序
112
- ordered = [futures[f] for f in futures]
113
- for f, name in zip(futures, ordered):
114
- results.append(f.result())
115
- else:
116
- for t in tasks:
117
- results.append(run_subtask(t, workdir))
118
-
119
- success = sum(1 for r in results if r.success)
120
- total = len(results)
121
- return CommandResult(success=success == total, data={
122
- "results": [r.to_dict() for r in results],
123
- "success_count": success,
124
- "total": total,
125
- "message": f"Sub-agents: {success}/{total} 成功",
126
- "summary": "\n".join(
127
- f"[{'✅' if r.success else '❌'}] {r.name}: "
128
- f"{r.summary or r.error}" for r in results
129
- )[:800],
130
- })
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes