@yottameta/yotta-dev-mcp 0.2.0 → 0.2.1
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.
- package/CHANGELOG.md +30 -0
- package/README.md +6 -0
- package/README.zh-CN.md +5 -0
- package/SKILL.md +6 -5
- package/package.json +1 -1
- package/references/tools.md +35 -2
- package/scripts/dev_common.py +13 -2
- package/scripts/dev_engine.py +72 -78
- package/scripts/dev_mcp_doctor.py +768 -0
- package/scripts/dev_model.py +20 -8
- package/scripts/dev_selftest.py +13 -3
- package/scripts/yotta_dev_mcp.py +12 -2
- package/server.json +2 -2
package/scripts/dev_model.py
CHANGED
|
@@ -30,9 +30,14 @@ def _resolve_python_import(source_rel, node):
|
|
|
30
30
|
if node.level > 1:
|
|
31
31
|
parts = parts[:-(node.level - 1)] if len(parts) >= node.level - 1 else []
|
|
32
32
|
prefix = "/".join(parts)
|
|
33
|
+
if not node.module:
|
|
34
|
+
return [
|
|
35
|
+
(prefix + "/" + alias.name.replace(".", "/")).strip("/")
|
|
36
|
+
for alias in node.names
|
|
37
|
+
]
|
|
33
38
|
module = node.module or ""
|
|
34
39
|
target = (prefix + "/" + module.replace(".", "/")).strip("/")
|
|
35
|
-
return [target
|
|
40
|
+
return [target] if target else [source_rel]
|
|
36
41
|
if node.module:
|
|
37
42
|
return [node.module]
|
|
38
43
|
return []
|
|
@@ -47,7 +52,12 @@ def _repo_map_python(path, rel):
|
|
|
47
52
|
for node in ast.walk(tree):
|
|
48
53
|
if isinstance(node, (ast.Import, ast.ImportFrom)):
|
|
49
54
|
for target in _resolve_python_import(rel, node):
|
|
50
|
-
imports.append({
|
|
55
|
+
imports.append({
|
|
56
|
+
"source": rel,
|
|
57
|
+
"target": target,
|
|
58
|
+
"line": getattr(node, "lineno", 1),
|
|
59
|
+
"relative": isinstance(node, ast.ImportFrom) and bool(node.level),
|
|
60
|
+
})
|
|
51
61
|
return imports
|
|
52
62
|
|
|
53
63
|
def _repo_map_js(path, rel):
|
|
@@ -77,7 +87,7 @@ def _resolve_relative_module(raw_target, source_rel, module_set, root=None):
|
|
|
77
87
|
return base, "file"
|
|
78
88
|
return None, None
|
|
79
89
|
|
|
80
|
-
def _classify_python_import(raw_target, source_rel, module_set):
|
|
90
|
+
def _classify_python_import(raw_target, source_rel, module_set, relative=False):
|
|
81
91
|
if raw_target.endswith(".py"):
|
|
82
92
|
if raw_target in module_set:
|
|
83
93
|
return "internal", raw_target
|
|
@@ -91,7 +101,7 @@ def _classify_python_import(raw_target, source_rel, module_set):
|
|
|
91
101
|
for candidate in (base + ".py", base + "/__init__.py"):
|
|
92
102
|
if candidate in module_set:
|
|
93
103
|
return "internal", candidate
|
|
94
|
-
return "external", raw_target
|
|
104
|
+
return ("unresolved" if relative else "external"), raw_target
|
|
95
105
|
|
|
96
106
|
def _classify_js_import(raw_target, source_rel, module_set, root=None):
|
|
97
107
|
if raw_target.startswith("."):
|
|
@@ -265,19 +275,21 @@ def system_model(path, max_files=2000, contract_file=None):
|
|
|
265
275
|
suffix = file_path.suffix.lower()
|
|
266
276
|
if suffix == ".py":
|
|
267
277
|
raw_imports = _repo_map_python(file_path, rel)
|
|
268
|
-
classifier = lambda raw: _classify_python_import(raw, rel, module_set) # noqa: E731
|
|
269
278
|
elif suffix in JS_EXTS:
|
|
270
279
|
raw_imports = _repo_map_js(file_path, rel)
|
|
271
|
-
classifier = lambda raw: _classify_js_import(raw, rel, module_set, root) # noqa: E731
|
|
272
280
|
else:
|
|
273
281
|
raw_imports = []
|
|
274
|
-
classifier = None
|
|
275
282
|
module_imports = []
|
|
276
283
|
for raw in raw_imports:
|
|
277
284
|
target_raw = raw["target"]
|
|
278
285
|
if target_raw == rel:
|
|
279
286
|
continue
|
|
280
|
-
|
|
287
|
+
if suffix == ".py":
|
|
288
|
+
kind, target = _classify_python_import(
|
|
289
|
+
target_raw, rel, module_set, relative=raw.get("relative", False)
|
|
290
|
+
)
|
|
291
|
+
else:
|
|
292
|
+
kind, target = _classify_js_import(target_raw, rel, module_set, root)
|
|
281
293
|
entry = {
|
|
282
294
|
"source": rel,
|
|
283
295
|
"target": target,
|
package/scripts/dev_selftest.py
CHANGED
|
@@ -15,7 +15,8 @@ from pathlib import Path
|
|
|
15
15
|
|
|
16
16
|
from dev_architecture import architecture_review
|
|
17
17
|
from dev_common import (
|
|
18
|
-
EVIDENCE_LIMIT,
|
|
18
|
+
EVIDENCE_LIMIT, LEAN_INSTALL_MARKERS, VERIFY_REQUIRED_INSTALLED_ASSETS,
|
|
19
|
+
VERIFY_REQUIRED_INSTALLED_FILES,
|
|
19
20
|
VERIFY_REQUIRED_SOURCE_FILES, VERIFY_WRITE_GATES, _frontmatter_version,
|
|
20
21
|
_read_text,
|
|
21
22
|
)
|
|
@@ -44,6 +45,9 @@ def _function_default(source_text, function_name, parameter_name):
|
|
|
44
45
|
def _string_constant(node):
|
|
45
46
|
return isinstance(node, ast.Constant) and isinstance(node.value, str)
|
|
46
47
|
|
|
48
|
+
def _is_lean_install(root):
|
|
49
|
+
return any((root / marker).is_file() for marker in LEAN_INSTALL_MARKERS)
|
|
50
|
+
|
|
47
51
|
def _protocol_tool_contracts(source_text):
|
|
48
52
|
tree = ast.parse(source_text)
|
|
49
53
|
function = None
|
|
@@ -380,8 +384,14 @@ def self_test(path, mode="auto", allow_execute=False, timeout=120):
|
|
|
380
384
|
else:
|
|
381
385
|
mode = "source"
|
|
382
386
|
|
|
383
|
-
|
|
384
|
-
|
|
387
|
+
if mode == "source":
|
|
388
|
+
required = list(VERIFY_REQUIRED_SOURCE_FILES)
|
|
389
|
+
else:
|
|
390
|
+
required = list(VERIFY_REQUIRED_INSTALLED_FILES)
|
|
391
|
+
# Lean distribution strips assets/ by platform policy; only require
|
|
392
|
+
# the banner when the copy is not lean or it already ships assets/.
|
|
393
|
+
if not _is_lean_install(root) or (root / "assets").is_dir():
|
|
394
|
+
required.extend(VERIFY_REQUIRED_INSTALLED_ASSETS)
|
|
385
395
|
missing = [rel for rel in required if not (root / rel).is_file()]
|
|
386
396
|
files_check = {
|
|
387
397
|
"id": "files",
|
package/scripts/yotta_dev_mcp.py
CHANGED
|
@@ -386,14 +386,24 @@ def mcp_tools():
|
|
|
386
386
|
{
|
|
387
387
|
"name": "mcp_doctor",
|
|
388
388
|
"description": (
|
|
389
|
-
"
|
|
390
|
-
"
|
|
389
|
+
"Broad, read-only health check for installed skills and MCP client "
|
|
390
|
+
"configuration. Discovers Codex, Cursor, WorkBuddy, OpenCode, Claude "
|
|
391
|
+
"Code and other known hosts (plus project-level configs), parses JSON, "
|
|
392
|
+
"JSONC and a narrow TOML subset, and always returns a coverage report "
|
|
393
|
+
"with checked / missing / unsupported / unverified hosts. Only server "
|
|
394
|
+
"names are returned; commands, args and env values are never included. "
|
|
395
|
+
"A clean result applies only to the covered scope: read coverage and "
|
|
396
|
+
"coverage_confidence before treating it as all-clear."
|
|
391
397
|
),
|
|
392
398
|
"inputSchema": {
|
|
393
399
|
"type": "object",
|
|
394
400
|
"properties": {
|
|
395
401
|
"skills_dirs": {"type": "array", "items": {"type": "string"}},
|
|
396
402
|
"config_paths": {"type": "array", "items": {"type": "string"}},
|
|
403
|
+
"include_defaults": {
|
|
404
|
+
"type": "boolean",
|
|
405
|
+
"description": "Also scan the built-in host registry when config_paths is supplied",
|
|
406
|
+
},
|
|
397
407
|
},
|
|
398
408
|
"additionalProperties": False,
|
|
399
409
|
},
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/YottaMeta/yotta-dev-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.2.
|
|
9
|
+
"version": "0.2.1",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@yottameta/yotta-dev-mcp",
|
|
14
|
-
"version": "0.2.
|
|
14
|
+
"version": "0.2.1",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|