release: предупреждение по пути, имя модуля по умолчанию в mcp, миграция раскладки в agent publish (M-1, M-2, M-3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
90d1af57f1
commit
82bbf0f5cd
@ -39,6 +39,7 @@ def main(argv=None) -> int:
|
|||||||
return 2
|
return 2
|
||||||
try:
|
try:
|
||||||
secure_dir(paths.release_root())
|
secure_dir(paths.release_root())
|
||||||
|
release.migrate_layout(paths.release_root()) # прежняя раскладка release/ до проверки «только вверх»
|
||||||
m = release.publish(release.module_dir(paths.release_root(), args.module), args.path)
|
m = release.publish(release.module_dir(paths.release_root(), args.module), args.path)
|
||||||
except release.PublishError as e:
|
except release.PublishError as e:
|
||||||
print(e, file=sys.stderr)
|
print(e, file=sys.stderr)
|
||||||
|
|||||||
@ -25,11 +25,11 @@ def unknown_project(label) -> str:
|
|||||||
return f"проект {label} не заведён в хабе: project_create или исправьте --project в .mcp.json"
|
return f"проект {label} не заведён в хабе: project_create или исправьте --project в .mcp.json"
|
||||||
|
|
||||||
|
|
||||||
def mcp_json(label: str, module: str = "jira") -> dict:
|
def mcp_json(label: str, module: str = registry.DEFAULT_AGENT) -> dict:
|
||||||
"""Фрагмент .mcp.json для репозитория проекта: у jira прежний (сервер jira-hub), у прочих модулей — сервер
|
"""Фрагмент .mcp.json для репозитория проекта: у модуля по умолчанию прежний (сервер jira-hub), у прочих модулей —
|
||||||
jira-hub-<модуль>."""
|
сервер jira-hub-<модуль>."""
|
||||||
run = ["run", "--project", str(HUB_ROOT), "jira-hub", "mcp"]
|
run = ["run", "--project", str(HUB_ROOT), "jira-hub", "mcp"]
|
||||||
if module == "jira":
|
if module == registry.DEFAULT_AGENT:
|
||||||
return {"mcpServers": {"jira-hub": {"command": "uv", "args": [*run, "--project", label]}}}
|
return {"mcpServers": {"jira-hub": {"command": "uv", "args": [*run, "--project", label]}}}
|
||||||
return {"mcpServers": {f"jira-hub-{module}": {"command": "uv", "args": [*run, module, "--project", label]}}}
|
return {"mcpServers": {f"jira-hub-{module}": {"command": "uv", "args": [*run, module, "--project", label]}}}
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@ VERSION_RE = re.compile(r"^AGENT_VERSION = (\d+)[ \t]*\r?$", re.M)
|
|||||||
|
|
||||||
SHA256_RE = re.compile(r"[0-9a-f]{64}")
|
SHA256_RE = re.compile(r"[0-9a-f]{64}")
|
||||||
|
|
||||||
_warned: str | None = None # текст последнего предупреждения о манифесте: одно на каждую новую ошибку (решение 5)
|
_warned: dict[str, str] = {} # путь манифеста → текст последнего предупреждения: одно на каждый новый текст на путь (решение 5)
|
||||||
_warned_lock = threading.Lock()
|
_warned_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
@ -152,12 +152,14 @@ def _manifest_problem(m) -> str | None:
|
|||||||
def _note_manifest(problem: str | None, path: Path) -> None:
|
def _note_manifest(problem: str | None, path: Path) -> None:
|
||||||
"""Предупреждение о негодном манифесте — один раз на каждый новый текст; годный или отсутствующий манифест
|
"""Предупреждение о негодном манифесте — один раз на каждый новый текст; годный или отсутствующий манифест
|
||||||
сбрасывает память (решение 5). Замок: claim и GET /agent читают манифест из разных потоков."""
|
сбрасывает память (решение 5). Замок: claim и GET /agent читают манифест из разных потоков."""
|
||||||
global _warned
|
|
||||||
text = None if problem is None else f"release: манифест {path}: {problem} — публикации нет"
|
text = None if problem is None else f"release: манифест {path}: {problem} — публикации нет"
|
||||||
|
key = str(path)
|
||||||
with _warned_lock:
|
with _warned_lock:
|
||||||
if text is not None and text != _warned:
|
if text is None:
|
||||||
|
_warned.pop(key, None)
|
||||||
|
elif _warned.get(key) != text:
|
||||||
log.warning("%s", text)
|
log.warning("%s", text)
|
||||||
_warned = text
|
_warned[key] = text
|
||||||
|
|
||||||
|
|
||||||
def current(release_dir) -> dict | None:
|
def current(release_dir) -> dict | None:
|
||||||
|
|||||||
@ -498,7 +498,7 @@ def test_claim_survives_unreadable_release(hub):
|
|||||||
|
|
||||||
def test_invalid_manifest_one_warning_for_many_claims(hub, monkeypatch, caplog):
|
def test_invalid_manifest_one_warning_for_many_claims(hub, monkeypatch, caplog):
|
||||||
"""M-3/M-7 #25, спека «Негодный манифест»: version = 6.9 — публикации нет; 10 claim и GET /agent — одно предупреждение."""
|
"""M-3/M-7 #25, спека «Негодный манифест»: version = 6.9 — публикации нет; 10 claim и GET /agent — одно предупреждение."""
|
||||||
monkeypatch.setattr(release, "_warned", None)
|
monkeypatch.setattr(release, "_warned", {})
|
||||||
rd = release.module_dir(hub["hub"].release_root, "jira")
|
rd = release.module_dir(hub["hub"].release_root, "jira")
|
||||||
(rd / "manifest.json").write_text('{"version": 6.9, "sha256": "' + "a" * 64 + '"}', encoding="utf-8")
|
(rd / "manifest.json").write_text('{"version": 6.9, "sha256": "' + "a" * 64 + '"}', encoding="utf-8")
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
|
|||||||
@ -89,3 +89,18 @@ def test_mcp_unknown_module(monkeypatch, capsys):
|
|||||||
err = capsys.readouterr().err
|
err = capsys.readouterr().err
|
||||||
assert "неизвестный модуль nope" in err and "jira" in err
|
assert "неизвестный модуль nope" in err and "jira" in err
|
||||||
assert seen == []
|
assert seen == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_agent_publish_migrates_old_layout(tmp_path, monkeypatch, capsys):
|
||||||
|
"""M-3: корневые agent.py/manifest.json прежней раскладки переезжают в release/jira/ до проверки «только вверх»."""
|
||||||
|
monkeypatch.setenv("JIRA_HUB_HOME", str(tmp_path))
|
||||||
|
rel = tmp_path / "release"
|
||||||
|
rel.mkdir()
|
||||||
|
old = tmp_path / "old.py"
|
||||||
|
old.write_text("AGENT_VERSION = 14\n", encoding="utf-8")
|
||||||
|
release.publish(rel, old) # прежняя раскладка: release/agent.py + manifest.json
|
||||||
|
new = tmp_path / "new.py"
|
||||||
|
new.write_text("AGENT_VERSION = 13\n", encoding="utf-8")
|
||||||
|
assert cli.main(["agent", "publish", str(new)]) == 1 # v13 < v14: «только вверх» видит прежнюю публикацию
|
||||||
|
assert not (rel / "agent.py").exists() and not (rel / "manifest.json").exists()
|
||||||
|
assert json.loads((rel / "jira" / "manifest.json").read_text(encoding="utf-8"))["version"] == 14
|
||||||
|
|||||||
@ -223,7 +223,7 @@ def test_invalid_manifest_means_no_release(tmp_path, manifest):
|
|||||||
|
|
||||||
def test_invalid_manifest_warns_once_per_new_error(tmp_path, monkeypatch, caplog):
|
def test_invalid_manifest_warns_once_per_new_error(tmp_path, monkeypatch, caplog):
|
||||||
"""M-3 #25, решение 5: одно предупреждение на каждый новый текст ошибки; годный манифест сбрасывает память."""
|
"""M-3 #25, решение 5: одно предупреждение на каждый новый текст ошибки; годный манифест сбрасывает память."""
|
||||||
monkeypatch.setattr(release, "_warned", None)
|
monkeypatch.setattr(release, "_warned", {})
|
||||||
rd = tmp_path / "release"
|
rd = tmp_path / "release"
|
||||||
rd.mkdir()
|
rd.mkdir()
|
||||||
manifest = rd / "manifest.json"
|
manifest = rd / "manifest.json"
|
||||||
@ -285,3 +285,16 @@ def test_hint_unknown_launcher_until_reported():
|
|||||||
assert release.hint(rel, online9, None, None, reported=True) == "агент запущен без run.py — обновится только вручную"
|
assert release.hint(rel, online9, None, None, reported=True) == "агент запущен без run.py — обновится только вручную"
|
||||||
rb = {"state": "rolled_back", "from_version": 9, "to_version": 10, "error": "код 1"}
|
rb = {"state": "rolled_back", "from_version": 9, "to_version": 10, "error": "код 1"}
|
||||||
assert release.hint(rel, online9, rb, None, reported=False) == "откат с v10 на v9: код 1"
|
assert release.hint(rel, online9, rb, None, reported=False) == "откат с v10 на v9: код 1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_manifest_warning_not_repeated_across_alternating_dirs(tmp_path, monkeypatch, caplog):
|
||||||
|
"""M-1: два агента — негодный манифест одного и отсутствующий у другого не сбрасывают память друг друга."""
|
||||||
|
monkeypatch.setattr(release, "_warned", {})
|
||||||
|
bad, none = tmp_path / "jira", tmp_path / "wiki"
|
||||||
|
bad.mkdir()
|
||||||
|
none.mkdir()
|
||||||
|
(bad / "manifest.json").write_text('{"version": 0, "sha256": "x"}', encoding="utf-8")
|
||||||
|
for _ in range(10):
|
||||||
|
assert release.current(bad) is None
|
||||||
|
assert release.current(none) is None
|
||||||
|
assert len([r for r in caplog.records if "манифест" in r.getMessage()]) == 1
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user