4e3d367637
ci / build (push) Successful in 28s
Инструменты были нарезаны по способу вызова, а не по смыслу: четыре отверстия, двойники *_index, одиннадцать sketch_add_*. Агент платил за это дважды — 40 КБ описаний в каждой сессии и лишние round-trip'ы, а каждый вызов это ещё и шанс сбиться. Теперь инструмент называет ОПЕРАЦИЮ, вариант задаётся параметром, объект выбирается индексом или точкой одним и тем же инструментом. Проверка построения приходит сама. Каждая мутирующая операция дописывает к ответу итог validate_part (AutoValidation): Create()/Update()==true не значит успех, а правило навыка «проверяй после каждого шага» удваивало число вызовов. Выключается через set_auto_validate или KOMPAS_MCP_AUTOVALIDATE=0 — сбой самой проверки уходит в примечание и никогда не превращает удачную операцию в ошибку. Эскиз: 16 инструментов → 3. sketch_create(plane|faceIndex|x,y,z, entities[], autoClose) строит контур целиком; пакет выполняется за ОДИН заход на STA-поток (PartModeler.AddEntitiesAsync), ошибка называет позицию примитива в списке. Слияния: hole(type=simple|counterbore|countersink|conic), extrude/revolve(mode), pattern(kind), mirror (без featureIds — всё тело), document_save(path?), set_variable как upsert (разведочный вызов «есть ли такая» больше не нужен), list_faces/list_edges(index?) вместо отдельных describe_*, get_part_info и get_bounding_box — в describe_model(sections), где незапрошенные разделы вообще не читаются из модели. Селектор index|point: fillet_edge/chamfer_edge принимают edgeIndices списком — одна операция дерева на все рёбра; для операций API7, умеющих только точку, индекс переводится в точку через ModelInspectionService.FaceCenterPointAsync (середина параметрической области грани). Схема слитого инструмента не запрещает неверную комбинацию полей — это делает валидация, и её сообщение называет type и недостающий параметр. Тесты: 291 unit (+41) и 136 integration (+5), интеграционные — на живом КОМПАС. Новые проверяют ровно рискованные места: точка-из-индекса лежит на грани и по ней создаётся эскиз, мульти-ребёрное скругление даёт один узел дерева и убыль объёма, upsert создаёт и затем меняет переменную с формулой, пакет сообщает позицию сбоя. PluginSkillsTests теперь падает, если в публикуемом навыке всплывёт слитое имя.
75 lines
3.0 KiB
C#
75 lines
3.0 KiB
C#
namespace Kompas.Mcp.Tests;
|
|
|
|
/// <summary>Публикуемые навыки должны быть самодостаточны: без ссылок на внутренности репозитория.</summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class PluginSkillsTests
|
|
{
|
|
private static string SkillsRoot => Path.Combine(TestPaths.RepoRoot(), "plugin", "skills");
|
|
|
|
public static TheoryData<string> SkillFiles()
|
|
{
|
|
var data = new TheoryData<string>();
|
|
foreach (var file in Directory.GetFiles(SkillsRoot, "*.md", SearchOption.AllDirectories))
|
|
data.Add(file);
|
|
return data;
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(SkillFiles))]
|
|
public void Skill_has_no_repository_only_references(string file)
|
|
{
|
|
var text = File.ReadAllText(file);
|
|
string[] forbidden = ["usecases/", "src/Kompas", "kompas-sdk-research", "docs-maintainer", "docs/Kompas3D_SDK"];
|
|
|
|
foreach (var token in forbidden)
|
|
Assert.DoesNotContain(token, text, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(SkillFiles))]
|
|
public void Skill_has_no_stale_tool_count(string file)
|
|
{
|
|
var text = File.ReadAllText(file);
|
|
string[] staleCounts = ["46 инструментов", "83 инструмента", "84 инструмента"];
|
|
|
|
foreach (var stale in staleCounts)
|
|
Assert.DoesNotContain(stale, text, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(SkillFiles))]
|
|
public void Skill_does_not_reference_merged_away_tools(string file)
|
|
{
|
|
var text = File.ReadAllText(file);
|
|
// Инструменты, слитые в общие: навык, зовущий их по имени, стоит агенту потерянного хода.
|
|
string[] gone =
|
|
[
|
|
"sketch_add_", "sketch_create_on_face", "sketch_create_on_offset_plane",
|
|
"extrude_boss", "extrude_cut", "revolve_boss", "revolve_cut",
|
|
"fillet_edge_index", "chamfer_edge_index",
|
|
"hole_counterbore", "hole_countersink", "hole_conic",
|
|
"linear_pattern", "circular_pattern", "mirror_operation", "mirror_body",
|
|
"document_save_as", "create_variable", "set_variable_note",
|
|
"get_part_info", "get_bounding_box", "describe_face", "describe_edge",
|
|
];
|
|
|
|
foreach (var name in gone)
|
|
Assert.DoesNotContain(name, text, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void Both_skills_are_present_with_frontmatter()
|
|
{
|
|
foreach (var name in new[] { "kompas-3d", "kompas-fdm-design" })
|
|
{
|
|
var path = Path.Combine(SkillsRoot, name, "SKILL.md");
|
|
Assert.True(File.Exists(path), $"нет навыка {path}");
|
|
|
|
var text = File.ReadAllText(path);
|
|
Assert.StartsWith("---", text, StringComparison.Ordinal);
|
|
Assert.Contains($"name: {name}", text, StringComparison.Ordinal);
|
|
Assert.Contains("description:", text, StringComparison.Ordinal);
|
|
}
|
|
}
|
|
}
|