@yottameta/yotta-dev-mcp-plugin 0.2.0 → 0.2.2

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.
@@ -12,7 +12,7 @@
12
12
  "authentication": "ON_INSTALL"
13
13
  },
14
14
  "category": "Developer Tools",
15
- "version": "0.2.0",
15
+ "version": "0.2.2",
16
16
  "description": "YuanKai (yotta-dev-mcp) - deterministic local development tools exposed over stdio MCP: repo_map, system_model, architecture_review, impact_analysis, verify_change, self_test, run_adapter, find_code, compress_output, review_code, review_diff, mcp_doctor, scan_secrets, scan_dependencies, check_publish_readiness, run_checks, scaffold_skill and workflow_state. Reads an optional .yotta/architecture.json contract plus .yotta/verification.json policy for architecture reviews, change impact cones and the L0-L5 evidence ledger. Optional explicit adapters integrate import-linter, dependency-cruiser and Repomix without auto-install or network access. Python 3.8+ standard library, offline by default; writes and command execution require explicit flags.",
17
17
  "interface": {
18
18
  "displayName": "元开 yotta-dev-mcp",
@@ -12,7 +12,7 @@
12
12
  "authentication": "ON_INSTALL"
13
13
  },
14
14
  "category": "Developer Tools",
15
- "version": "0.2.0",
15
+ "version": "0.2.2",
16
16
  "description": "YuanKai (yotta-dev-mcp) - deterministic local development tools exposed over stdio MCP: repo_map, system_model, architecture_review, impact_analysis, verify_change, self_test, run_adapter, find_code, compress_output, review_code, review_diff, mcp_doctor, scan_secrets, scan_dependencies, check_publish_readiness, run_checks, scaffold_skill and workflow_state. Reads an optional .yotta/architecture.json contract plus .yotta/verification.json policy for architecture reviews, change impact cones and the L0-L5 evidence ledger. Optional explicit adapters integrate import-linter, dependency-cruiser and Repomix without auto-install or network access. Python 3.8+ standard library, offline by default; writes and command execution require explicit flags.",
17
17
  "interface": {
18
18
  "displayName": "元开 yotta-dev-mcp",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yottameta/yotta-dev-mcp-plugin",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "YuanKai (yotta-dev-mcp) Agent Plugin: deterministic local development tools over stdio MCP.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "yotta-dev-mcp",
4
- "version": "0.2.0",
4
+ "version": "0.2.2",
5
5
  "description": "YuanKai (yotta-dev-mcp) - deterministic local development tools exposed over stdio MCP: repo_map, system_model, architecture_review, impact_analysis, verify_change, self_test, run_adapter, find_code, compress_output, review_code, review_diff, mcp_doctor, scan_secrets, scan_dependencies, check_publish_readiness, run_checks, scaffold_skill and workflow_state. Reads an optional .yotta/architecture.json contract plus .yotta/verification.json policy for architecture reviews, change impact cones and the L0-L5 evidence ledger. Optional explicit adapters integrate import-linter, dependency-cruiser and Repomix without auto-install or network access. Python 3.8+ standard library, offline by default; writes and command execution require explicit flags.",
6
6
  "author": {
7
7
  "name": "YottaMeta",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: yotta-dev-mcp
3
3
  description: 元开(yotta-dev-mcp)—— 本地、确定性的开发工具 MCP,把只读默认的开发能力暴露为 stdio MCP server:repo_map / system_model / architecture_review / impact_analysis / verify_change / self_test / run_adapter / find_code / compress_output / review_code / review_diff / mcp_doctor / scan_secrets / scan_dependencies / check_publish_readiness / run_checks / scaffold_skill / workflow_state。触发:让 AI 在陌生项目里先做结构盘点、构建系统模型或架构契约(.yotta/architecture.json)、按契约评审架构、分析改动影响锥与回归面、按验证阶梯产出证据账本、对元开自身做完整性 / 反证自测、探测或显式运行 import-linter / dependency-cruiser / Repomix 可选适配器、定位代码、评审改动、扫描密钥/依赖、检查发布就绪、运行白名单检查、生成脚手架或读取 .workflow 状态时;或用户说 元开 / 开发能力 MCP / yotta-dev-mcp / 代码库地图 / 系统模型 / 架构契约 / 架构评审 / 影响分析 / 验证账本 / 反证自测 / 代码评审 MCP / 适配器 等。边界:Python 3.8+ 标准库、离线默认;除 run_checks(显式 allow_execute)、verify_change 的 L2-L4 策略检查(显式 allow_execute)与 self_test 的测试子集(显式 allow_execute)、run_adapter 的 action=run(显式 allow_execute)、scaffold_skill / workflow_state 的显式 apply 外均为只读;不上传源码、不自动修改、不提交、不联网查询包是否存在、不自动安装或下载适配器。
4
- version: 0.2.0
4
+ version: 0.2.2
5
5
  license: MIT
6
6
  ---
7
7
 
@@ -37,6 +37,18 @@ npx -y @yottameta/yotta-dev-mcp
37
37
  python scripts/yotta_dev_mcp.py
38
38
  ```
39
39
 
40
+ 工具分组(`--tools`,与元忆同口径):
41
+
42
+ ```bash
43
+ python scripts/yotta_dev_mcp.py --tools core # 5 工具:repo_map / find_code / review_code / review_diff / verify_change
44
+ python scripts/yotta_dev_mcp.py --tools full # 18 工具(默认)
45
+ ```
46
+
47
+ `core` 给「需要把工具表常驻在上下文里」的宿主用:schema 体积约为 `full` 的 29%
48
+ (5 工具 3,883 字符 vs 18 工具 13,230 字符,紧凑 JSON 口径),适合默认启用;
49
+ `full` 暴露全部工具,适合按需调用、不常驻的场景。`core` 模式下调用 full 专属工具会返回
50
+ 明确提示(提示用 `--tools full` 重启)。
51
+
40
52
  自动接入说明:AI 首次协助配置时,必须先展示目标配置文件、完整配置片段和影响,
41
53
  并获得用户明确同意后再写入 `mcpServers`。用户拒绝时不要写文件,直接提供上面的启动命令
42
54
  让用户手动接入。
@@ -45,7 +57,7 @@ python scripts/yotta_dev_mcp.py
45
57
 
46
58
  | 工具 | 用途 | 写入 |
47
59
  |---|---|---|
48
- | `repo_map` | 模块、导入、入口点地图 | 否 |
60
+ | `repo_map` | 模块、导入、入口点地图;支持 `from . import X` / 别名 / 包,忽略临时与探针目录 | 否 |
49
61
  | `system_model` | 系统模型:模块、依赖、入口、测试映射、配置与数据归属;附带契约分层,输出 PASS / FAIL / UNKNOWN | 否 |
50
62
  | `architecture_review` | 按契约评审依赖规则、边界可见性与数据归属;critical / high 判 FAIL,medium / low 只告警 | 否 |
51
63
  | `impact_analysis` | 变更影响锥:直接消费者、受影响层 / 边界 / 存储 / 不变量、映射测试、风险分级与回滚探针 | 否 |
@@ -54,10 +66,10 @@ python scripts/yotta_dev_mcp.py
54
66
  | `run_adapter` | 探测或显式运行 import-linter / dependency-cruiser / Repomix;固定 argv、项目内 cwd、有界输出与哈希 | 仅 action=run + 显式 allow_execute |
55
67
  | `find_code` | 符号 / 文本定位,结果有上限 | 否 |
56
68
  | `compress_output` | 保留错误与首尾的长输出压缩 | 否 |
57
- | `review_code` | 规则化代码评审,带行号与建议 | 否 |
69
+ | `review_code` | 规则化代码评审,带行号与建议;忽略 `.workflow` / `scratch` / `_probe` 等临时目录 | 否 |
58
70
  | `review_diff` | 只评审 diff 的新增行 | 否 |
59
- | `mcp_doctor` | 技能版本与 MCP JSON 配置体检 | 否 |
60
- | `scan_secrets` | 密钥 / 凭据 / 高熵令牌扫描(强制脱敏) | 否 |
71
+ | `mcp_doctor` | 技能版本与多宿主 MCP 配置体检;返回 coverage,只列 server 名,不含 command / env | 否 |
72
+ | `scan_secrets` | 密钥 / 凭据 / 高熵令牌扫描(强制脱敏;路径 / 哈希 / 文件名噪声过滤) | 否 |
61
73
  | `scan_dependencies` | 依赖清单、lockfile、来源与 typosquat 启发式检查 | 否 |
62
74
  | `check_publish_readiness` | 版本四件、发布文件、仓库与 publishConfig 检查 | 否 |
63
75
  | `run_checks` | 白名单测试 / lint / compile 并返回结构化摘要 | 仅显式 allow_execute |
@@ -77,5 +89,6 @@ python scripts/yotta_dev_mcp.py
77
89
 
78
90
  ## 当前版本
79
91
 
92
+ - v0.2.1(2026-09-25):缺陷修复批次——`mcp_doctor` 改为多宿主 / 多根 / JSON+JSONC+TOML 子集发现并返回 coverage;`self_test(mode="installed")` 按精简分发副本 / npm / plugin / r0 形态判断 banner;`repo_map` 修复 `from . import X` 解析;`scan_secrets` 过滤路径 / 哈希 / 文件名噪声;`review_code` / `repo_map` 忽略 `.workflow` / `scratch` / `_probe` 等临时目录。
80
93
  - v0.2.0(2026-09-25):新增 `system_model`、`architecture_review`、`impact_analysis`、`verify_change`、`self_test`、`run_adapter`,以及 `.yotta/architecture.json` 与可选 `.yotta/verification.json` 契约(版本 1);工具总数 18,原 12 个工具行为不变。
81
94
  - v0.1.1:品牌显示名统一为「元开」;功能与 12 个工具不变。
@@ -14,6 +14,12 @@ Input:
14
14
 
15
15
  Output: `root`, `modules`, `imports`, `entrypoints`, `truncated`.
16
16
 
17
+ Python imports include absolute, `from .X import Y`, `from . import X`,
18
+ `as` aliases, packages and multi-name forms. Temporary / agent-state
19
+ directories (`.workflow`, `.codex`, `.cursor`, `.claude`, `.agents`,
20
+ `scratch`, `_probe`, `probe`, `sandbox`, `debug`, `.tmp`) and probe/temp
21
+ file names are ignored by default.
22
+
17
23
  ## system_model
18
24
 
19
25
  Input:
@@ -201,14 +207,36 @@ Input:
201
207
 
202
208
  Only added lines are reviewed. Output: `files`, `findings`, `truncated`.
203
209
 
210
+ `review_code` uses the same default ignore set as `repo_map`: agent state
211
+ directories, scratch / probe / sandbox / debug directories and probe/temp
212
+ file names do not flood the result. `review_diff` only reviews added lines,
213
+ so it is unaffected by directory traversal.
214
+
204
215
  ## mcp_doctor
205
216
 
206
217
  Input:
207
218
 
208
219
  - `skills_dirs` (optional array).
209
220
  - `config_paths` (optional array).
210
-
211
- Output: `skills`, `mcp_configs`, `issues`, `checked_skills`, `checked_configs`.
221
+ - `include_defaults` (optional boolean): when `config_paths` is supplied, also
222
+ scan the built-in host registry instead of explicit-only scope.
223
+
224
+ Output: `skills`, `mcp_configs`, `coverage`, `skills_coverage`, `issues`,
225
+ `coverage_gaps`, `unknown_hosts`, `summary`, `scope`, `checked_skills`,
226
+ `checked_configs`, `checked_hosts`.
227
+
228
+ Discovery is tiered and environment-aware: verified hosts include Codex
229
+ (`$CODEX_HOME/config.toml`, JSON fallbacks), Cursor, WorkBuddy
230
+ (`~/.workbuddy/mcp.json` plus `connectors/*/mcp.json`), OpenCode
231
+ (`$XDG_CONFIG_HOME/opencode/opencode.jsonc|json`), Claude Code, Windsurf,
232
+ Continue, Gemini, Qwen, Trae, Comate, CodeBuddy, Kimi, Kiro, VS Code and Zed;
233
+ additional hosts are best-effort candidates and are reported as
234
+ `unverified` when absent. JSON, JSONC and a narrow TOML `[mcp_servers.*]`
235
+ subset are parsed; YAML is reported as `unsupported`, never silently skipped.
236
+ Only server names are returned; commands, args and env values are never
237
+ included. `summary.all_clear` is true only for a fully covered, issue-free
238
+ default scan; always read `coverage_confidence` before treating it as
239
+ all-clear.
212
240
 
213
241
  ## scan_secrets
214
242
 
@@ -218,6 +246,11 @@ Input: `path` or `text`, optional `max_findings`, optional `include_git_history`
218
246
  Output: `findings` with `path`, `line`, `rule`, `severity`, redacted `evidence`,
219
247
  `suggestion`; `truncated`.
220
248
 
249
+ High-entropy findings apply a narrow noise filter for absolute paths,
250
+ URL / `file://` percent-encoded paths, common binary/source/document
251
+ suffixes and hash-context hex values (SHA-1/256/512, MD5, checksum, digest,
252
+ integrity). Credential-name, AWS-key and private-key rules are not relaxed.
253
+
221
254
  ## scan_dependencies
222
255
 
223
256
  Input: `path`.
@@ -10,7 +10,14 @@ from pathlib import Path
10
10
  IGNORE_DIRS = {
11
11
  ".git", ".hg", ".svn", "node_modules", "__pycache__", ".venv", "venv",
12
12
  "dist", "build", ".next", ".nuxt", ".cache", ".tmp", ".tmp2",
13
+ ".workflow", ".codex", ".cursor", ".claude", ".agents",
14
+ "scratch", "_scratch", "_probe", "probe", "probes",
15
+ "sandbox", ".sandbox", "debug", "_debug",
13
16
  }
17
+ IGNORE_TEMP_FILE_RE = re.compile(
18
+ r"(?i)^(?:probe[_-].*|_probe[_-].*|scratch[_-].*|_scratch[_-].*|tmp_.*|temp_.*|.*_tmp)"
19
+ r"\.(?:py|js|jsx|mjs|cjs|ts|tsx|sh|ps1|go|rs|java|kt|kts|rb|php)$"
20
+ )
14
21
  SOURCE_EXTS = {
15
22
  ".py", ".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".sh", ".ps1",
16
23
  ".go", ".rs", ".java", ".kt", ".kts", ".rb", ".php",
@@ -54,7 +61,9 @@ VERIFY_REQUIRED_SOURCE_FILES = (
54
61
  "references/tools.md", "references/adapters.md",
55
62
  "references/architecture-contract.md", "assets/banner.png",
56
63
  )
57
- VERIFY_REQUIRED_INSTALLED_FILES = ("SKILL.md", "assets/banner.png")
64
+ VERIFY_REQUIRED_INSTALLED_FILES = ("SKILL.md",)
65
+ VERIFY_REQUIRED_INSTALLED_ASSETS = ("assets/banner.png",)
66
+ LEAN_INSTALL_MARKERS = ("_icon.png",)
58
67
  VERIFY_WRITE_GATES = (
59
68
  ("run_checks", "allow_execute"),
60
69
  ("scaffold_skill", "apply"),
@@ -94,13 +103,15 @@ def _read_text(path):
94
103
  return data.decode("utf-8", errors="replace")
95
104
 
96
105
 
97
- def _iter_files(root, extensions=None, max_files=5000, all_files=False):
106
+ def _iter_files(root, extensions=None, max_files=5000, all_files=False, ignore_temp=True):
98
107
  root = Path(root)
99
108
  extensions = set(extensions or TEXT_EXTS)
100
109
  count = 0
101
110
  for current, dirs, files in os.walk(str(root)):
102
111
  dirs[:] = sorted(d for d in dirs if d not in IGNORE_DIRS)
103
112
  for name in sorted(files):
113
+ if ignore_temp and IGNORE_TEMP_FILE_RE.match(name):
114
+ continue
104
115
  path = Path(current) / name
105
116
  if path.is_symlink():
106
117
  continue
@@ -26,12 +26,22 @@ from dev_common import (
26
26
  _frontmatter_name, _frontmatter_version, source_exts,
27
27
  )
28
28
  from dev_impact import impact_analysis as _impact_analysis_impl
29
+ from dev_mcp_doctor import (
30
+ default_config_paths as _default_config_paths_impl,
31
+ default_skill_dirs as _default_skill_dirs_impl,
32
+ mcp_doctor as _mcp_doctor_impl,
33
+ )
29
34
  from dev_verify import verify_change as _verify_change_impl
30
35
  from dev_selftest import self_test
31
- from dev_model import _repo_map_js, _repo_map_python, system_model as _system_model_impl
36
+ from dev_model import (
37
+ _classify_python_import,
38
+ _repo_map_js,
39
+ _repo_map_python,
40
+ system_model as _system_model_impl,
41
+ )
32
42
  from dev_rules import REVIEW_RULES
33
43
 
34
- VERSION = "0.2.0"
44
+ VERSION = "0.2.2"
35
45
 
36
46
 
37
47
 
@@ -54,6 +64,7 @@ def repo_map(path, max_files=2000):
54
64
  if len(files) > max_files:
55
65
  truncated = True
56
66
  files = files[:max_files]
67
+ module_set = {_rel(root, file_path) for file_path in files}
57
68
  for file_path in files:
58
69
  rel = _rel(root, file_path)
59
70
  try:
@@ -63,7 +74,11 @@ def repo_map(path, max_files=2000):
63
74
  language = _language(file_path)
64
75
  modules.append({"path": rel, "language": language, "lines": len(text.splitlines())})
65
76
  if file_path.suffix.lower() == ".py":
66
- imports.extend(_repo_map_python(file_path, rel))
77
+ for raw in _repo_map_python(file_path, rel):
78
+ _, target = _classify_python_import(
79
+ raw["target"], rel, module_set, relative=raw.get("relative", False)
80
+ )
81
+ imports.append({"source": rel, "target": target, "line": raw["line"]})
67
82
  elif file_path.suffix.lower() in (".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"):
68
83
  imports.extend(_repo_map_js(file_path, rel))
69
84
  if (
@@ -274,81 +289,19 @@ def review_diff(diff_text=None, path=None, base=None, max_findings=200):
274
289
 
275
290
 
276
291
  def _default_skill_dirs():
277
- home = Path.home()
278
- candidates = [
279
- home / ".codex" / "skills",
280
- home / ".claude" / "skills",
281
- home / ".cursor" / "skills",
282
- home / ".config" / "opencode" / "skills",
283
- ]
284
- codex_home = os.environ.get("CODEX_HOME")
285
- if codex_home:
286
- candidates.insert(0, Path(codex_home) / "skills")
287
- claude_home = os.environ.get("CLAUDE_CONFIG_DIR")
288
- if claude_home:
289
- candidates.insert(0, Path(claude_home) / "skills")
290
- xdg_home = os.environ.get("XDG_CONFIG_HOME")
291
- if xdg_home:
292
- candidates.insert(0, Path(xdg_home) / "opencode" / "skills")
293
- return candidates
292
+ return _default_skill_dirs_impl()
294
293
 
295
294
 
296
295
  def _default_config_paths():
297
- home = Path.home()
298
- return [
299
- home / ".codex" / "config.json",
300
- home / ".codex" / "mcp.json",
301
- home / ".config" / "opencode" / "opencode.json",
302
- home / ".claude" / "settings.json",
303
- home / ".cursor" / "mcp.json",
304
- ]
305
-
306
-
307
- def mcp_doctor(skills_dirs=None, config_paths=None):
308
- skills = []
309
- issues = []
310
- for directory in (skills_dirs or _default_skill_dirs()):
311
- root = Path(directory)
312
- if not root.is_dir():
313
- continue
314
- for child in sorted(root.iterdir(), key=lambda item: item.name):
315
- skill_file = child / "SKILL.md"
316
- if not child.is_dir() or not skill_file.is_file():
317
- continue
318
- try:
319
- text = _read_text(skill_file)
320
- except (OSError, ValueError) as exc:
321
- issues.append("%s: %s" % (skill_file, exc))
322
- continue
323
- skills.append({
324
- "name": _frontmatter_name(text) or child.name,
325
- "version": _frontmatter_version(text),
326
- "path": str(skill_file),
327
- })
328
- mcp_configs = []
329
- for config_path in (config_paths or _default_config_paths()):
330
- path = Path(config_path)
331
- if not path.is_file():
332
- continue
333
- try:
334
- payload = json.loads(_read_text(path))
335
- except Exception as exc: # noqa: BLE001
336
- issues.append("%s: %s" % (path, exc))
337
- continue
338
- servers = payload.get("mcpServers") if isinstance(payload, dict) else None
339
- mcp_configs.append({
340
- "path": str(path),
341
- "servers": sorted(servers.keys()) if isinstance(servers, dict) else [],
342
- })
343
- skills.sort(key=lambda item: (item["name"], item["path"]))
344
- mcp_configs.sort(key=lambda item: item["path"])
345
- return {
346
- "skills": skills,
347
- "mcp_configs": mcp_configs,
348
- "issues": issues,
349
- "checked_skills": len(skills),
350
- "checked_configs": len(mcp_configs),
351
- }
296
+ return _default_config_paths_impl()
297
+
298
+
299
+ def mcp_doctor(skills_dirs=None, config_paths=None, include_defaults=None):
300
+ return _mcp_doctor_impl(
301
+ skills_dirs=skills_dirs,
302
+ config_paths=config_paths,
303
+ include_defaults=include_defaults,
304
+ )
352
305
 
353
306
 
354
307
  SECRET_KEY_RE = re.compile(
@@ -357,6 +310,35 @@ SECRET_KEY_RE = re.compile(
357
310
  AWS_KEY_RE = re.compile(r"\bAKIA[0-9A-Z]{16}\b")
358
311
  PRIVATE_KEY_RE = re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----")
359
312
  HIGH_ENTROPY_RE = re.compile(r"[A-Za-z0-9_+/=\-]{32,}")
313
+ HASH_CONTEXT_RE = re.compile(r"(?i)(sha1|sha256|sha512|hash|checksum|digest|integrity)")
314
+ HEX_TOKEN_RE = re.compile(r"^[0-9a-fA-F]+$")
315
+ PATH_HINT_RE = re.compile(r"(?i)(?:[a-z]:[\\/]|\\\\|https?://|file://)")
316
+ URL_HINT_RE = re.compile(r"(?i)(?:file|https?)://")
317
+ PERCENT_ESCAPE_RE = re.compile(r"%[0-9A-Fa-f]{2}")
318
+ HASH_PREFIX_RE = re.compile(r"(?i)^(?:sha1|sha256|sha512|md5)[=:]([0-9a-fA-F]{32,128})$")
319
+ FILE_SUFFIX_RE = re.compile(
320
+ r"(?i)\.(exe|dll|sys|py|js|ts|tsx|json|md|txt|log|whl|tar|gz|zip|png|jpg|jpeg|svg)"
321
+ )
322
+
323
+
324
+ def _high_entropy_noise(line, token, start, end):
325
+ """Return True for common non-secret high-entropy noise."""
326
+ if HASH_PREFIX_RE.match(token):
327
+ return True
328
+ if PERCENT_ESCAPE_RE.search(token) and URL_HINT_RE.search(line):
329
+ return True
330
+ if start > 0 and line[start - 1] == "%":
331
+ return True
332
+ if HEX_TOKEN_RE.match(token) and len(token) in (32, 40, 64, 128) and HASH_CONTEXT_RE.search(line):
333
+ return True
334
+ window = line[max(0, start - 16):min(len(line), end + 16)]
335
+ if PATH_HINT_RE.search(window):
336
+ return True
337
+ if URL_HINT_RE.search(line) and PERCENT_ESCAPE_RE.search(window):
338
+ return True
339
+ if FILE_SUFFIX_RE.match(line[end:end + 8]):
340
+ return True
341
+ return False
360
342
 
361
343
 
362
344
  def _entropy(value):
@@ -385,7 +367,9 @@ def scan_secrets(path=None, text=None, max_findings=200, include_git_history=Fal
385
367
  root = Path(path)
386
368
  if not root.exists():
387
369
  raise ValueError("路径不存在: %s" % path)
388
- files = [root] if root.is_file() else list(_iter_files(root, all_files=True))
370
+ files = [root] if root.is_file() else list(
371
+ _iter_files(root, all_files=True, ignore_temp=False)
372
+ )
389
373
  base = root.parent if root.is_file() else root
390
374
  for file_path in files:
391
375
  try:
@@ -421,7 +405,11 @@ def scan_secrets(path=None, text=None, max_findings=200, include_git_history=Fal
421
405
  })
422
406
  for match in HIGH_ENTROPY_RE.finditer(line):
423
407
  token = match.group(0)
424
- if _entropy(token) >= 4.0 and not PRIVATE_KEY_RE.search(line):
408
+ if (
409
+ _entropy(token) >= 4.0
410
+ and not PRIVATE_KEY_RE.search(line)
411
+ and not _high_entropy_noise(line, token, match.start(), match.end())
412
+ ):
425
413
  findings.append({
426
414
  "path": rel, "line": line_no, "rule": "high-entropy-token",
427
415
  "severity": "medium", "evidence": _redact(token),
@@ -1083,6 +1071,8 @@ def main():
1083
1071
  doctor = sub.add_parser("mcp-doctor")
1084
1072
  doctor.add_argument("--skills-dir", action="append")
1085
1073
  doctor.add_argument("--config", action="append")
1074
+ doctor.add_argument("--include-defaults", action="store_true",
1075
+ help="scan built-in host registry in addition to --config paths")
1086
1076
  args = parser.parse_args()
1087
1077
  if args.command == "repo-map":
1088
1078
  result = repo_map(args.path)
@@ -1126,7 +1116,11 @@ def main():
1126
1116
  elif args.command == "review-code":
1127
1117
  result = review_code(args.path)
1128
1118
  elif args.command == "mcp-doctor":
1129
- result = mcp_doctor(skills_dirs=args.skills_dir, config_paths=args.config)
1119
+ result = mcp_doctor(
1120
+ skills_dirs=args.skills_dir,
1121
+ config_paths=args.config,
1122
+ include_defaults=args.include_defaults,
1123
+ )
1130
1124
  else:
1131
1125
  parser.print_help()
1132
1126
  return 2