@michaelbel/cuckcoder-mcp 1.6.12 → 1.6.14

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 (40) hide show
  1. package/assets/agents/architect-auditor.md +182 -0
  2. package/assets/agents/bug-hunter.md +185 -0
  3. package/assets/agents/build-engineer.md +148 -0
  4. package/assets/agents/business-analyst.md +178 -0
  5. package/assets/agents/code-refine.md +150 -0
  6. package/assets/agents/code-reviewer.md +180 -0
  7. package/assets/agents/compose-builder.md +192 -0
  8. package/assets/agents/device-ui-tester.md +133 -0
  9. package/assets/agents/devops-expert.md +177 -0
  10. package/assets/agents/explorer.md +132 -0
  11. package/assets/agents/github-project-manager.md +203 -0
  12. package/assets/agents/guide-android-builder.md +81 -0
  13. package/assets/agents/guide-writer.md +63 -0
  14. package/assets/agents/kotlin-engineer.md +215 -0
  15. package/assets/agents/mechanical-operator.md +166 -0
  16. package/assets/agents/notion-project-manager.md +185 -0
  17. package/assets/agents/performance-reviewer.md +188 -0
  18. package/assets/agents/security-auditor.md +203 -0
  19. package/assets/agents/swift-engineer.md +223 -0
  20. package/assets/agents/swiftui-builder.md +236 -0
  21. package/assets/agents/tech-writer.md +214 -0
  22. package/assets/agents/ux-reviewer.md +235 -0
  23. package/assets/rules/kotlin-datetime.md +10 -0
  24. package/assets/rules/resource.md +7 -1
  25. package/assets/workflows/architecture-sweep.js +98 -0
  26. package/assets/workflows/business-feature-sweep.js +218 -0
  27. package/assets/workflows/full-review.js +190 -0
  28. package/assets/workflows/mvi-compliance-sweep.js +74 -0
  29. package/assets/workflows/redesign-sweep.js +152 -0
  30. package/assets/workflows/refactoring-sweep.js +199 -0
  31. package/assets/workflows/security-sweep.js +98 -0
  32. package/assets/workflows/task-batch-create.js +104 -0
  33. package/dist/search.js +38 -0
  34. package/dist/server.js +161 -2
  35. package/dist/source/bundled.js +78 -0
  36. package/dist/source/github-source.js +76 -0
  37. package/dist/validation.js +16 -0
  38. package/dist/workflow-meta.js +29 -0
  39. package/package.json +1 -1
  40. package/assets/rules/app-badging.md +0 -170
@@ -0,0 +1,199 @@
1
+ export const meta = {
2
+ name: 'refactoring-sweep',
3
+ description:
4
+ 'Аудит → triage → (опционально) применение рефакторинга по проекту: параллельный ' +
5
+ 'аудит линзами архитектуры/упрощения/безопасности/производительности, затем ' +
6
+ 'применение одобренных фиксов в изолированных worktree по платформам и ' +
7
+ 'валидация сборки.',
8
+ whenToUse: 'Крупный рефакторинг или чистка — рефакторинг, почисти код, убери дублирование.',
9
+ }
10
+
11
+ // ── args ────────────────────────────────────────────────────────────────
12
+ // request : что рефакторить / область фокуса (может быть пустым —
13
+ // "весь проект")
14
+ // mode : 'audit-only' (по умолчанию) | 'apply'
15
+ // severityFilter : 'all' | 'critical+high' (по умолчанию) | 'critical-only' —
16
+ // какие находки чинит mode='apply'
17
+ const A = typeof args === 'string' ? JSON.parse(args) || {} : args || {}
18
+ const REQUEST = A.request || 'общая чистка кода'
19
+ const MODE = A.mode || 'audit-only'
20
+ const SEVERITY_FILTER = A.severityFilter || 'critical+high'
21
+
22
+ const FINDING_SCHEMA = {
23
+ type: 'object',
24
+ required: ['file', 'issue', 'severity', 'fix'],
25
+ properties: {
26
+ file: { type: 'string' },
27
+ line: { type: 'string' },
28
+ issue: { type: 'string' },
29
+ severity: { type: 'string', enum: ['critical', 'high', 'medium', 'low'] },
30
+ fix: { type: 'string' },
31
+ },
32
+ }
33
+
34
+ phase('Audit')
35
+ const lenses = [
36
+ {
37
+ role: 'architecture',
38
+ agentType: 'cuckcoder:architect-auditor',
39
+ focus:
40
+ 'границы слоёв, направление зависимостей, связность, единственная ' +
41
+ 'ответственность, публичные контракты',
42
+ },
43
+ {
44
+ role: 'simplification',
45
+ agentType: 'cuckcoder:code-refine',
46
+ focus:
47
+ 'мёртвый код, дублирование, лишняя вложенность или абстракция, ' +
48
+ 'переусложнённый control flow — только отчёт, не применяй пока',
49
+ },
50
+ {
51
+ role: 'security',
52
+ agentType: 'cuckcoder:security-auditor',
53
+ focus:
54
+ 'захардкоженные секреты, отсутствующая авторизация, небезопасная ' +
55
+ 'обработка ввода, небезопасные значения по умолчанию',
56
+ },
57
+ {
58
+ role: 'performance',
59
+ agentType: 'cuckcoder:performance-reviewer',
60
+ focus:
61
+ 'лишняя рекомпозиция, блокирующая main thread работа, N+1-запросы, ' +
62
+ 'неограниченный рост памяти',
63
+ },
64
+ ]
65
+
66
+ const perLens = await parallel(
67
+ lenses.map((l) => () =>
68
+ agent(
69
+ `Аудит рефакторинга. Область фокуса: "${REQUEST}". Твоя линза — ${l.role}:
70
+ ${l.focus}. Читай реальный код. Указывай только конкретно проверяемые
71
+ находки, каждая с file, line если известна, issue, severity и
72
+ минимальным fix. Если проблем нет, верни пустой массив.`,
73
+ {
74
+ label: `audit:${l.role}`,
75
+ phase: 'Audit',
76
+ agentType: l.agentType,
77
+ schema: {
78
+ type: 'object',
79
+ required: ['findings'],
80
+ properties: { findings: { type: 'array', items: FINDING_SCHEMA } },
81
+ },
82
+ },
83
+ ).then((result) => (result ? result.findings.map((f) => ({ role: l.role, ...f })) : [])),
84
+ ),
85
+ )
86
+
87
+ const allFindings = perLens.flat()
88
+
89
+ phase('Triage')
90
+ const triage = await agent(
91
+ `Сгруппируй эти находки рефакторинга по severity и дедуплицируй
92
+ пересекающиеся (один и тот же file+issue от нескольких линз — объедини,
93
+ оставь максимальный severity, перечисли все линзы-источники):
94
+
95
+ ${JSON.stringify(allFindings, null, 2)}
96
+
97
+ Severity-фильтр для одобренного плана: "${SEVERITY_FILTER}" ("all" — всё,
98
+ "critical+high" — только critical и high, "critical-only" — только
99
+ critical). Верни counts по severity и approvedPlan — находки, прошедшие
100
+ фильтр.`,
101
+ {
102
+ label: 'triage',
103
+ phase: 'Triage',
104
+ schema: {
105
+ type: 'object',
106
+ required: ['counts', 'approvedPlan'],
107
+ properties: {
108
+ counts: {
109
+ type: 'object',
110
+ properties: {
111
+ critical: { type: 'number' },
112
+ high: { type: 'number' },
113
+ medium: { type: 'number' },
114
+ low: { type: 'number' },
115
+ },
116
+ },
117
+ approvedPlan: { type: 'array', items: FINDING_SCHEMA },
118
+ },
119
+ },
120
+ },
121
+ )
122
+
123
+ if (MODE === 'audit-only' || !triage.approvedPlan || triage.approvedPlan.length === 0) {
124
+ return { status: 'audited', counts: triage.counts, approvedPlan: triage.approvedPlan || [] }
125
+ }
126
+
127
+ phase('Execute')
128
+ const isCompose = (f) => /\.kt$/.test(f) && /(screen|composable|compose|ui\/)/i.test(f)
129
+ const isSwiftUI = (f) => /\.swift$/.test(f) && /(view|screen)/i.test(f)
130
+ const isSwift = (f) => /\.swift$/.test(f) && !isSwiftUI(f)
131
+ const isKotlin = (f) => /\.kt(s)?$/.test(f) && !isCompose(f)
132
+
133
+ const layers = [
134
+ { name: 'kotlin', agentType: 'cuckcoder:kotlin-engineer', match: isKotlin },
135
+ { name: 'compose', agentType: 'cuckcoder:compose-builder', match: isCompose },
136
+ { name: 'swift', agentType: 'cuckcoder:swift-engineer', match: isSwift },
137
+ { name: 'swiftui', agentType: 'cuckcoder:swiftui-builder', match: isSwiftUI },
138
+ ]
139
+ .map((layer) => ({
140
+ ...layer,
141
+ plan: triage.approvedPlan.filter((f) => layer.match(f.file || '')),
142
+ }))
143
+ .filter((layer) => layer.plan.length > 0)
144
+
145
+ const execResults = await parallel(
146
+ layers.map((layer) => () =>
147
+ agent(
148
+ `Примени ровно этот одобренный план рефакторинга и ничего сверх него —
149
+ никаких попутных улучшений, не трогай файлы вне этого списка:
150
+
151
+ ${JSON.stringify(layer.plan, null, 2)}
152
+
153
+ Сохраняй публичный API, если план явно не говорит иное. Не ломай
154
+ существующие тесты. Следуй конвенциям и правилам этого проекта.
155
+ Верни список изменённых файлов и краткое summary изменений и причин.`,
156
+ {
157
+ label: `execute:${layer.name}`,
158
+ phase: 'Execute',
159
+ agentType: layer.agentType,
160
+ isolation: 'worktree',
161
+ schema: {
162
+ type: 'object',
163
+ required: ['changedFiles', 'summary'],
164
+ properties: {
165
+ changedFiles: { type: 'array', items: { type: 'string' } },
166
+ summary: { type: 'string' },
167
+ },
168
+ },
169
+ },
170
+ ),
171
+ ),
172
+ )
173
+
174
+ phase('Validate')
175
+ const changedFiles = execResults.filter(Boolean).flatMap((r) => r.changedFiles || [])
176
+ const validation = await agent(
177
+ `Провалидируй этот рефакторинг. Изменённые файлы: ${JSON.stringify(changedFiles)}.
178
+ Собери проект и запусти его тесты (найди build-инструмент: gradlew,
179
+ swift build и т.п.). Верни passed (boolean) и details (что запускал
180
+ и результат).`,
181
+ {
182
+ label: 'validate',
183
+ phase: 'Validate',
184
+ agentType: 'cuckcoder:build-engineer',
185
+ schema: {
186
+ type: 'object',
187
+ required: ['passed', 'details'],
188
+ properties: { passed: { type: 'boolean' }, details: { type: 'string' } },
189
+ },
190
+ },
191
+ )
192
+
193
+ return {
194
+ status: validation && validation.passed ? 'refactored' : 'refactored-with-issues',
195
+ counts: triage.counts,
196
+ execResults: execResults.filter(Boolean),
197
+ changedFiles,
198
+ validation,
199
+ }
@@ -0,0 +1,98 @@
1
+ export const meta = {
2
+ name: 'security-sweep',
3
+ description:
4
+ 'Параллельный security-аудит по каждому модулю проекта с независимой проверкой ' +
5
+ 'каждой находки перед итоговой выдачей.',
6
+ }
7
+
8
+ const discovery = await agent(
9
+ `Перечисли каждый модуль верхнего уровня или feature-пакет в этом проекте,
10
+ содержащий прикладной код, который стоит проверить отдельно (Gradle-модули,
11
+ KMP source set-ы или feature-пакеты вида shared/data/feature/<name> и
12
+ androidApp/feature/<name>). Верни пути относительно корня репозитория —
13
+ без build-вывода, сгенерированного кода и сторонних зависимостей.`,
14
+ {
15
+ schema: {
16
+ type: 'object',
17
+ required: ['modules'],
18
+ properties: { modules: { type: 'array', items: { type: 'string' } } },
19
+ },
20
+ },
21
+ )
22
+
23
+ const perModule = await pipeline(discovery.modules, (module) =>
24
+ agent(
25
+ `Ты старший application security engineer, проводящий read-only аудит
26
+ только модуля "${module}". Сначала построй краткую threat model (активы,
27
+ актёры, границы доверия), затем проверь аутентификацию, авторизацию,
28
+ секреты, криптографию, границы ввода/вывода, конфигурацию платформы
29
+ (манифест, exported-компоненты, permissions), transport security и
30
+ риски supply chain. Указывай только находки с конкретным эксплуатируемым
31
+ attack path и предусловиями — без общих советов по hardening. Если
32
+ находок нет, верни пустой массив.`,
33
+ {
34
+ label: module,
35
+ schema: {
36
+ type: 'object',
37
+ required: ['findings'],
38
+ properties: {
39
+ findings: {
40
+ type: 'array',
41
+ items: {
42
+ type: 'object',
43
+ required: ['severity', 'location', 'summary', 'attackPath', 'fix'],
44
+ properties: {
45
+ severity: { type: 'string' },
46
+ location: { type: 'string' },
47
+ summary: { type: 'string' },
48
+ attackPath: { type: 'string' },
49
+ impact: { type: 'string' },
50
+ fix: { type: 'string' },
51
+ validation: { type: 'string' },
52
+ },
53
+ },
54
+ },
55
+ },
56
+ },
57
+ },
58
+ ).then((result) => (result ? result.findings : [])),
59
+ )
60
+
61
+ const candidates = perModule.flat()
62
+
63
+ const verified = await pipeline(candidates, (finding) =>
64
+ agent(
65
+ `Независимо проверь эту заявленную security-находку — перечитай код сам,
66
+ не доверяя исходному описанию. Подтверди, реален и достижим ли
67
+ attack path с учётом фактических проверок авторизации, валидации
68
+ ввода и конфигурации платформы.
69
+
70
+ Заявленная находка:
71
+ ${JSON.stringify(finding, null, 2)}
72
+
73
+ Верни те же поля плюс "confirmed" (boolean) и "confidence"
74
+ (50, 75 или 100). Если находка не подтвердилась, установи
75
+ confirmed: false и объясни причину в поле "reason".`,
76
+ {
77
+ label: finding.location,
78
+ schema: {
79
+ type: 'object',
80
+ required: ['confirmed', 'confidence'],
81
+ properties: {
82
+ confirmed: { type: 'boolean' },
83
+ confidence: { type: 'number' },
84
+ reason: { type: 'string' },
85
+ severity: { type: 'string' },
86
+ location: { type: 'string' },
87
+ summary: { type: 'string' },
88
+ attackPath: { type: 'string' },
89
+ impact: { type: 'string' },
90
+ fix: { type: 'string' },
91
+ validation: { type: 'string' },
92
+ },
93
+ },
94
+ },
95
+ ),
96
+ )
97
+
98
+ return verified.filter((finding) => finding?.confirmed)
@@ -0,0 +1,104 @@
1
+ export const meta = {
2
+ name: 'task-batch-create',
3
+ description:
4
+ 'Декомпозирует фичу или эпик на отдельные задачи и создаёт их все параллельно ' +
5
+ 'в GitHub Issues или Notion — для одиночной задачи проще обратиться к агенту ' +
6
+ 'трекера напрямую.',
7
+ whenToUse:
8
+ 'Разбивка фичи/эпика на много задач сразу — заведи задачи, разбей на таски ' +
9
+ 'и создай.',
10
+ }
11
+
12
+ // ── args ────────────────────────────────────────────────────────────────
13
+ // request : описание фичи/эпика для декомпозиции (обязателен)
14
+ // platform : 'github' (по умолчанию) | 'notion'
15
+ // target : "owner/repo" для GitHub, либо имя/id базы Notion — обязателен
16
+ const A = typeof args === 'string' ? JSON.parse(args) || {} : args || {}
17
+ const REQUEST = A.request || ''
18
+ const PLATFORM = A.platform || 'github'
19
+ const TARGET = A.target || ''
20
+
21
+ if (!REQUEST || !TARGET) {
22
+ return {
23
+ status: 'skipped',
24
+ reason: 'Нужны и "request", и "target" (репозиторий или база Notion).',
25
+ }
26
+ }
27
+
28
+ phase('Decompose')
29
+ const decomposition = await agent(
30
+ `Разбей эту фичу/эпик на отдельные, независимо выполнимые задачи:
31
+
32
+ "${REQUEST}"
33
+
34
+ Для каждой задачи нужно: title (в повелительном наклонении, например
35
+ "Добавить X", "Исправить Y"), type (bug/feature/improvement/task),
36
+ description (2-4 предложения), acceptanceCriteria (минимум 2 конкретных
37
+ критерия) и labels. Дели по естественным границам (слой, экран,
38
+ endpoint) — не создавай задачи меньше чем на полдня работы и не
39
+ объединяй несвязанные вещи в одну задачу.`,
40
+ {
41
+ label: 'decompose',
42
+ phase: 'Decompose',
43
+ schema: {
44
+ type: 'object',
45
+ required: ['tasks'],
46
+ properties: {
47
+ tasks: {
48
+ type: 'array',
49
+ items: {
50
+ type: 'object',
51
+ required: ['title', 'type', 'description', 'acceptanceCriteria', 'labels'],
52
+ properties: {
53
+ title: { type: 'string' },
54
+ type: { type: 'string', enum: ['bug', 'feature', 'improvement', 'task'] },
55
+ description: { type: 'string' },
56
+ acceptanceCriteria: { type: 'array', items: { type: 'string' } },
57
+ labels: { type: 'array', items: { type: 'string' } },
58
+ },
59
+ },
60
+ },
61
+ },
62
+ },
63
+ },
64
+ )
65
+
66
+ const tasks = (decomposition && decomposition.tasks) || []
67
+ if (tasks.length === 0) {
68
+ return { status: 'no-tasks', request: REQUEST }
69
+ }
70
+
71
+ phase('Create')
72
+ const agentType = PLATFORM === 'notion'
73
+ ? 'cuckcoder:notion-project-manager'
74
+ : 'cuckcoder:github-project-manager'
75
+
76
+ const created = await pipeline(tasks, (task) =>
77
+ agent(
78
+ `Создай ровно один ${PLATFORM === 'notion' ? 'таск в Notion' : 'GitHub Issue'}
79
+ в "${TARGET}" для этой задачи, части более крупного эпика "${REQUEST}":
80
+
81
+ ${JSON.stringify(task, null, 2)}
82
+
83
+ Тело/описание должно включать acceptance criteria как чек-лист.
84
+ Не создавай никаких других issue и не изменяй существующие. Верни
85
+ url и id созданного элемента.`,
86
+ {
87
+ label: task.title,
88
+ phase: 'Create',
89
+ agentType,
90
+ schema: {
91
+ type: 'object',
92
+ required: ['url', 'id'],
93
+ properties: { url: { type: 'string' }, id: { type: 'string' } },
94
+ },
95
+ },
96
+ ).then((result) => ({ title: task.title, ...result })),
97
+ )
98
+
99
+ return {
100
+ status: 'created',
101
+ platform: PLATFORM,
102
+ target: TARGET,
103
+ tasks: created,
104
+ }
package/dist/search.js ADDED
@@ -0,0 +1,38 @@
1
+ const SNIPPET_RADIUS = 80;
2
+ function buildSnippet(content, query) {
3
+ const index = content.toLowerCase().indexOf(query.toLowerCase());
4
+ if (index === -1) {
5
+ return content.slice(0, SNIPPET_RADIUS * 2).trim();
6
+ }
7
+ const start = Math.max(0, index - SNIPPET_RADIUS);
8
+ const end = Math.min(content.length, index + query.length + SNIPPET_RADIUS);
9
+ const prefix = start > 0 ? "…" : "";
10
+ const suffix = end < content.length ? "…" : "";
11
+ return `${prefix}${content.slice(start, end).trim()}${suffix}`;
12
+ }
13
+ /**
14
+ * Rough case-insensitive keyword search across every rule and skill's full content (and, for
15
+ * skills, their description). Fetches every item from `source` — cheap for the bundled source
16
+ * (local reads), and no worse than a caller looping `get_rule`/`get_skill` themselves for the
17
+ * GitHub source, which caches/dedupes per-file fetches already.
18
+ */
19
+ export async function searchRulesAndSkills(source, query) {
20
+ const needle = query.toLowerCase();
21
+ const results = [];
22
+ const [ruleNames, skills] = await Promise.all([source.listRules(), source.listSkills()]);
23
+ await Promise.all(ruleNames.map(async (name) => {
24
+ const content = await source.getRule(name);
25
+ if (content.toLowerCase().includes(needle) || name.toLowerCase().includes(needle)) {
26
+ results.push({ type: "rule", name, snippet: buildSnippet(content, query) });
27
+ }
28
+ }));
29
+ await Promise.all(skills.map(async (skill) => {
30
+ const { description, content } = await source.getSkill(skill.name);
31
+ const haystack = `${skill.name}\n${description}\n${content}`;
32
+ if (haystack.toLowerCase().includes(needle)) {
33
+ results.push({ type: "skill", name: skill.name, snippet: buildSnippet(`${description}\n\n${content}`, query) });
34
+ }
35
+ }));
36
+ results.sort((a, b) => (a.type === b.type ? a.name.localeCompare(b.name) : a.type.localeCompare(b.type)));
37
+ return results;
38
+ }
package/dist/server.js CHANGED
@@ -1,13 +1,15 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { z } from "zod";
3
3
  import { toToolErrorResult } from "./errors.js";
4
+ import { searchRulesAndSkills } from "./search.js";
4
5
  import { createSource } from "./source/index.js";
5
- import { validateRuleName, validateSkillName } from "./validation.js";
6
+ import { validateAgentName, validateRuleName, validateSkillName, validateWorkflowName } from "./validation.js";
6
7
  import { getServerName, getServerVersion } from "./version.js";
7
8
  const SERVER_INSTRUCTIONS = [
8
- "Use this server as the source of truth for Cuckcoder rules and skills.",
9
+ "Use this server as the source of truth for Cuckcoder rules, skills, agents, and workflows.",
9
10
  "Before any git commit, call get_rule with name 'git' and apply the returned rules.",
10
11
  "Before deleting files, call get_rule with name 'filesystem' and apply the returned rules.",
12
+ "Use search to find relevant rules/skills by keyword instead of guessing names.",
11
13
  ].join("\n");
12
14
  const sourceOutputShape = {
13
15
  kind: z.enum(["bundled", "github"]),
@@ -127,5 +129,162 @@ export function createServer(options = {}) {
127
129
  return toToolErrorResult(error);
128
130
  }
129
131
  });
132
+ // ─── list_agents ───────────────────────────────────────────────────────────
133
+ server.registerTool("list_agents", {
134
+ title: "List agents",
135
+ description: "List all available sub-agent names and descriptions in the Cuckcoder repository.",
136
+ inputSchema: {},
137
+ outputSchema: {
138
+ agents: z.array(z.object({ name: z.string(), description: z.string() })),
139
+ source: z.object(sourceOutputShape),
140
+ },
141
+ annotations: {
142
+ title: "List agents",
143
+ readOnlyHint,
144
+ openWorldHint,
145
+ },
146
+ }, async () => {
147
+ try {
148
+ const agents = await source.listAgents();
149
+ const structuredContent = { agents, source: sourceInfo };
150
+ const lines = agents.length
151
+ ? agents.map((agent) => `- ${agent.name} — ${agent.description}`).join("\n")
152
+ : "_none_";
153
+ return {
154
+ content: [{ type: "text", text: lines }],
155
+ structuredContent,
156
+ };
157
+ }
158
+ catch (error) {
159
+ return toToolErrorResult(error);
160
+ }
161
+ });
162
+ // ─── get_agent ─────────────────────────────────────────────────────────────
163
+ server.registerTool("get_agent", {
164
+ title: "Get agent definition",
165
+ description: "Get the role, tools, and full definition of a sub-agent. Use a name from `list_agents`, e.g. 'kotlin-engineer'.",
166
+ inputSchema: {
167
+ name: z.string().describe("Lowercase kebab-case agent name, e.g. 'kotlin-engineer'"),
168
+ },
169
+ outputSchema: {
170
+ name: z.string(),
171
+ description: z.string(),
172
+ tools: z.string(),
173
+ disallowedTools: z.string(),
174
+ content: z.string(),
175
+ source: z.object(sourceOutputShape),
176
+ },
177
+ annotations: {
178
+ title: "Get agent definition",
179
+ readOnlyHint,
180
+ openWorldHint,
181
+ },
182
+ }, async ({ name }) => {
183
+ try {
184
+ const validName = validateAgentName(name);
185
+ const agent = await source.getAgent(validName);
186
+ return {
187
+ content: [{ type: "text", text: agent.content }],
188
+ structuredContent: { name: validName, ...agent, source: sourceInfo },
189
+ };
190
+ }
191
+ catch (error) {
192
+ return toToolErrorResult(error);
193
+ }
194
+ });
195
+ // ─── list_workflows ────────────────────────────────────────────────────────
196
+ server.registerTool("list_workflows", {
197
+ title: "List workflows",
198
+ description: "List all available workflow (sweep pipeline) names and descriptions in the Cuckcoder repository.",
199
+ inputSchema: {},
200
+ outputSchema: {
201
+ workflows: z.array(z.object({ name: z.string(), description: z.string() })),
202
+ source: z.object(sourceOutputShape),
203
+ },
204
+ annotations: {
205
+ title: "List workflows",
206
+ readOnlyHint,
207
+ openWorldHint,
208
+ },
209
+ }, async () => {
210
+ try {
211
+ const workflows = await source.listWorkflows();
212
+ const structuredContent = { workflows, source: sourceInfo };
213
+ const lines = workflows.length
214
+ ? workflows.map((workflow) => `- ${workflow.name} — ${workflow.description}`).join("\n")
215
+ : "_none_";
216
+ return {
217
+ content: [{ type: "text", text: lines }],
218
+ structuredContent,
219
+ };
220
+ }
221
+ catch (error) {
222
+ return toToolErrorResult(error);
223
+ }
224
+ });
225
+ // ─── get_workflow ──────────────────────────────────────────────────────────
226
+ server.registerTool("get_workflow", {
227
+ title: "Get workflow source",
228
+ description: "Get the description, when-to-use, and full source of a workflow. Use a name from `list_workflows`, e.g. 'full-review'.",
229
+ inputSchema: {
230
+ name: z.string().describe("Lowercase kebab-case workflow name, e.g. 'full-review'"),
231
+ },
232
+ outputSchema: {
233
+ name: z.string(),
234
+ description: z.string(),
235
+ whenToUse: z.string(),
236
+ content: z.string(),
237
+ source: z.object(sourceOutputShape),
238
+ },
239
+ annotations: {
240
+ title: "Get workflow source",
241
+ readOnlyHint,
242
+ openWorldHint,
243
+ },
244
+ }, async ({ name }) => {
245
+ try {
246
+ const validName = validateWorkflowName(name);
247
+ const workflow = await source.getWorkflow(validName);
248
+ return {
249
+ content: [{ type: "text", text: workflow.content }],
250
+ structuredContent: { name: validName, ...workflow, source: sourceInfo },
251
+ };
252
+ }
253
+ catch (error) {
254
+ return toToolErrorResult(error);
255
+ }
256
+ });
257
+ // ─── search ────────────────────────────────────────────────────────────────
258
+ server.registerTool("search", {
259
+ title: "Search rules and skills",
260
+ description: "Rough case-insensitive keyword search across every rule's and skill's full content. Use instead of guessing a rule/skill name.",
261
+ inputSchema: {
262
+ query: z.string().min(1).describe("Keyword or phrase to search for, e.g. 'coroutine scope'"),
263
+ },
264
+ outputSchema: {
265
+ results: z.array(z.object({ type: z.enum(["rule", "skill"]), name: z.string(), snippet: z.string() })),
266
+ source: z.object(sourceOutputShape),
267
+ },
268
+ annotations: {
269
+ title: "Search rules and skills",
270
+ readOnlyHint,
271
+ openWorldHint,
272
+ },
273
+ }, async ({ query }) => {
274
+ try {
275
+ const results = await searchRulesAndSkills(source, query);
276
+ const structuredContent = { results, source: sourceInfo };
277
+ const lines = results.length
278
+ ? results.map((result) => `- [${result.type}] ${result.name}: ${result.snippet}`).join("\n")
279
+ : "_no matches_";
280
+ return {
281
+ content: [{ type: "text", text: lines }],
282
+ structuredContent,
283
+ };
284
+ }
285
+ catch (error) {
286
+ return toToolErrorResult(error);
287
+ }
288
+ });
130
289
  return server;
131
290
  }