agentf 0.3.0 → 0.4.0

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.
@@ -6,25 +6,25 @@ require "yaml"
6
6
  module Agentf
7
7
  class Installer
8
8
  READ_ACTIONS = {
9
- "get_recent_memories" => { cli: "agentf memory recent -n 10", tool: "agentf_memory_recent" },
10
- "get_pitfalls" => { cli: "agentf memory pitfalls -n 10", tool: "agentf_memory_recent" },
11
- "get_lessons" => { cli: "agentf memory lessons -n 10", tool: "agentf_memory_recent" },
12
- "get_successes" => { cli: "agentf memory successes -n 10", tool: "agentf_memory_recent" },
13
- "get_intents" => { cli: "agentf memory intents", tool: "agentf_memory_recent" },
14
- "get_all_tags" => { cli: "agentf memory tags", tool: "agentf_memory_recent" },
15
- "get_by_tag" => { cli: "agentf memory by-tag <tag> -n 10", tool: "agentf_memory_search" },
16
- "get_by_type" => { cli: "agentf memory by-type <type> -n 10", tool: "agentf_memory_search" },
17
- "get_by_agent" => { cli: "agentf memory by-agent <agent> -n 10", tool: "agentf_memory_search" },
18
- "search" => { cli: "agentf memory search \"<query>\" -n 10", tool: "agentf_memory_search" },
19
- "get_summary" => { cli: "agentf memory summary", tool: "agentf_memory_recent" }
9
+ "get_recent_memories" => { cli: "agentf memory recent -n 10", tool: "agentf-memory-recent" },
10
+ "get_pitfalls" => { cli: "agentf memory pitfalls -n 10", tool: "agentf-memory-recent" },
11
+ "get_lessons" => { cli: "agentf memory lessons -n 10", tool: "agentf-memory-recent" },
12
+ "get_successes" => { cli: "agentf memory successes -n 10", tool: "agentf-memory-recent" },
13
+ "get_intents" => { cli: "agentf memory intents", tool: "agentf-memory-recent" },
14
+ "get_all_tags" => { cli: "agentf memory tags", tool: "agentf-memory-recent" },
15
+ "get_by_tag" => { cli: "agentf memory by-tag <tag> -n 10", tool: "agentf-memory-search" },
16
+ "get_by_type" => { cli: "agentf memory by-type <type> -n 10", tool: "agentf-memory-search" },
17
+ "get_by_agent" => { cli: "agentf memory by-agent <agent> -n 10", tool: "agentf-memory-search" },
18
+ "search" => { cli: "agentf memory search \"<query>\" -n 10", tool: "agentf-memory-search" },
19
+ "get_summary" => { cli: "agentf memory summary", tool: "agentf-memory-recent" }
20
20
  }.freeze
21
21
 
22
22
  WRITE_ACTIONS = {
23
- "store_lesson" => { cli: "agentf memory add-lesson \"<title>\" \"<description>\" --agent=<AGENT> --tags=learning", tool: "agentf_memory_add_lesson" },
24
- "store_success" => { cli: "agentf memory add-success \"<title>\" \"<description>\" --agent=<AGENT> --tags=success", tool: "agentf_memory_add_success" },
25
- "store_pitfall" => { cli: "agentf memory add-pitfall \"<title>\" \"<description>\" --agent=<AGENT> --tags=pitfall", tool: "agentf_memory_add_pitfall" },
26
- "store_business_intent" => { cli: "agentf memory add-business-intent \"<title>\" \"<description>\" --tags=strategy", tool: "agentf_memory_add_lesson" },
27
- "store_feature_intent" => { cli: "agentf memory add-feature-intent \"<title>\" \"<description>\" --acceptance=\"<criteria>\"", tool: "agentf_memory_add_lesson" }
23
+ "store_lesson" => { cli: "agentf memory add-lesson \"<title>\" \"<description>\" --agent=<AGENT> --tags=learning", tool: "agentf-memory-add-lesson" },
24
+ "store_success" => { cli: "agentf memory add-success \"<title>\" \"<description>\" --agent=<AGENT> --tags=success", tool: "agentf-memory-add-success" },
25
+ "store_pitfall" => { cli: "agentf memory add-pitfall \"<title>\" \"<description>\" --agent=<AGENT> --tags=pitfall", tool: "agentf-memory-add-pitfall" },
26
+ "store_business_intent" => { cli: "agentf memory add-business-intent \"<title>\" \"<description>\" --tags=strategy", tool: "agentf-memory-add-lesson" },
27
+ "store_feature_intent" => { cli: "agentf memory add-feature-intent \"<title>\" \"<description>\" --acceptance=\"<criteria>\"", tool: "agentf-memory-add-lesson" }
28
28
  }.freeze
29
29
 
30
30
  PROVIDER_LAYOUTS = {
@@ -116,7 +116,7 @@ module Agentf
116
116
  def write_opencode_helpers(root:)
117
117
  writes = []
118
118
  writes << write_manifest(
119
- File.join(root, ".opencode/agents/agentf-workflow-engine.md"),
119
+ File.join(root, ".opencode/agents/agentf-orchestrator.md"),
120
120
  render_workflow_engine_manifest
121
121
  )
122
122
  writes << write_manifest(
@@ -160,7 +160,7 @@ module Agentf
160
160
 
161
161
  def render_agent_manifest(klass, provider:)
162
162
  meta = {
163
- "name" => klass.typed_name,
163
+ "name" => agent_identifier(klass),
164
164
  "description" => klass.description,
165
165
  "commands" => klass.commands,
166
166
  "memory" => klass.memory_concepts,
@@ -171,6 +171,18 @@ module Agentf
171
171
  #{meta.to_yaml}---
172
172
  #{klass.prompt}
173
173
 
174
+ ## Core Mission
175
+ #{klass.description}
176
+
177
+ ## When To Use
178
+ #{klass.when_to_use}
179
+
180
+ ## Deliverables
181
+ #{Array(klass.deliverables).map { |item| "- #{item}" }.join("\n")}
182
+
183
+ ## Working Style
184
+ #{klass.working_style}
185
+
174
186
  ## Memory Integration
175
187
  - Reads: #{Array(klass.memory_concepts["reads"]).join(", ")}
176
188
  - Writes: #{Array(klass.memory_concepts["writes"]).join(", ")}
@@ -211,10 +223,10 @@ module Agentf
211
223
  end
212
224
 
213
225
  if actions.none? { |a| a.start_with?("- Read:") }
214
- actions << "- Read: Use `agentf_memory_recent` tool"
226
+ actions << "- Read: Use `agentf-memory-recent` tool"
215
227
  end
216
228
  if actions.none? { |a| a.start_with?("- Write:") }
217
- actions << "- Write: Use `agentf_memory_add_lesson` tool"
229
+ actions << "- Write: Use `agentf-memory-add-lesson` tool"
218
230
  end
219
231
 
220
232
  actions
@@ -234,10 +246,18 @@ module Agentf
234
246
  end
235
247
  end
236
248
 
249
+ def agent_identifier(klass)
250
+ "agentf-#{klass.typed_name.downcase}"
251
+ end
252
+
253
+ def command_identifier(name)
254
+ "agentf-#{name.to_s.downcase}"
255
+ end
256
+
237
257
  def render_command_manifest(manifest, provider:)
238
258
  commands = Array(manifest.fetch("commands"))
239
259
  frontmatter = {
240
- "name" => manifest.fetch("name"),
260
+ "name" => command_identifier(manifest.fetch("name")),
241
261
  "description" => manifest.fetch("description"),
242
262
  "commands" => commands
243
263
  }
@@ -260,9 +280,9 @@ module Agentf
260
280
 
261
281
  Copilot should call the local `agentf` MCP server tools for runtime actions.
262
282
 
263
- - Code discovery tools: `code_glob`, `code_grep`, `code_tree`, `code_related_files`
264
- - Memory read tools: `memory_recent`, `memory_search`
265
- - Memory write tools (if enabled): `memory_add_lesson`, `memory_add_success`, `memory_add_pitfall`
283
+ - Code discovery tools: `agentf-code-glob`, `agentf-code-grep`, `agentf-code-tree`, `agentf-code-related-files`
284
+ - Memory read tools: `agentf-memory-recent`, `agentf-memory-search`
285
+ - Memory write tools (if enabled): `agentf-memory-add-lesson`, `agentf-memory-add-success`, `agentf-memory-add-pitfall`
266
286
 
267
287
  MCP server is started via `agentf mcp-server` and runs locally over stdio.
268
288
  MARKDOWN
@@ -273,12 +293,12 @@ module Agentf
273
293
 
274
294
  command_name = manifest.fetch("name")
275
295
  recommended_tools = case command_name
276
- when "explorer"
277
- "`code_glob`, `code_grep`, `code_tree`, `code_related_files`"
278
- when "memory"
279
- "`memory_recent`, `memory_search`, `memory_add_lesson`, `memory_add_success`, `memory_add_pitfall`"
280
- else
281
- "`code_glob`, `code_grep`, `memory_recent`, `memory_search`"
296
+ when "explorer"
297
+ "`agentf-code-glob`, `agentf-code-grep`, `agentf-code-tree`, `agentf-code-related-files`"
298
+ when "memory"
299
+ "`agentf-memory-recent`, `agentf-memory-search`, `agentf-memory-add-lesson`, `agentf-memory-add-success`, `agentf-memory-add-pitfall`"
300
+ else
301
+ "`agentf-code-glob`, `agentf-code-grep`, `agentf-memory-recent`, `agentf-memory-search`"
282
302
  end
283
303
 
284
304
  <<~MARKDOWN
@@ -294,9 +314,15 @@ module Agentf
294
314
  <<~MARKDOWN
295
315
  # AGENTF-WORKFLOW-ENGINE Agent
296
316
 
317
+ ## Identity
318
+
319
+ - Role: ORCHESTRATOR
320
+ - Division: strategy
321
+ - Specialty: orchestration
322
+
297
323
  ## Role
298
324
 
299
- The WORKFLOW_ENGINE coordinates end-to-end workflows by selecting a provider adapter (`opencode` or `copilot`), creating an execution plan, and running agents in sequence.
325
+ The ORCHESTRATOR coordinates end-to-end workflows by selecting a provider adapter (`opencode` or `copilot`), creating an execution plan, and running agents in sequence.
300
326
 
301
327
  Implemented in `lib/agentf/workflow_engine.rb`.
302
328
 
@@ -311,11 +337,11 @@ module Agentf
311
337
 
312
338
  ## Execution Flow
313
339
 
314
- 1. WORKFLOW_ENGINE → Requests provider plan
315
- 2. WORKFLOW_ENGINE → Captures feature intent in memory
316
- 3. WORKFLOW_ENGINE → Executes planned agents sequentially
340
+ 1. ORCHESTRATOR → Requests provider plan
341
+ 2. ORCHESTRATOR → Captures feature intent in memory
342
+ 3. ORCHESTRATOR → Executes planned agents sequentially
317
343
  4. Each agent → Reads relevant context + writes lessons
318
- 5. WORKFLOW_ENGINE → Summarizes status and returns results
344
+ 5. ORCHESTRATOR → Summarizes status and returns results
319
345
 
320
346
  ## Notes
321
347
 
@@ -384,8 +410,8 @@ module Agentf
384
410
 
385
411
  export const agentfPlugin: Plugin = async () => {
386
412
  return {
387
- tool: {
388
- agentf_code_glob: tool({
413
+ tools: {
414
+ "agentf-code-glob": tool({
389
415
  description: "Find files using project glob patterns via Agentf code CLI.",
390
416
  args: {
391
417
  pattern: tool.schema.string().describe("Glob pattern, example: lib/**/*.rb"),
@@ -400,7 +426,7 @@ module Agentf
400
426
  return runAgentfCli(context.directory, "code", "glob", [args.pattern, ...commandArgs]);
401
427
  },
402
428
  }),
403
- agentf_code_grep: tool({
429
+ "agentf-code-grep": tool({
404
430
  description: "Search file contents via Agentf code CLI.",
405
431
  args: {
406
432
  pattern: tool.schema.string().describe("Regex/text to search"),
@@ -415,7 +441,7 @@ module Agentf
415
441
  return runAgentfCli(context.directory, "code", "grep", [args.pattern, ...commandArgs]);
416
442
  },
417
443
  }),
418
- agentf_code_tree: tool({
444
+ "agentf-code-tree": tool({
419
445
  description: "Get directory tree data via Agentf code CLI.",
420
446
  args: {
421
447
  depth: tool.schema.number().int().min(1).max(10).optional().describe("Max traversal depth"),
@@ -425,7 +451,7 @@ module Agentf
425
451
  return runAgentfCli(context.directory, "code", "tree", [`--depth=${depth}`]);
426
452
  },
427
453
  }),
428
- agentf_code_related_files: tool({
454
+ "agentf-code-related-files": tool({
429
455
  description: "Find import and related file hints for a target file.",
430
456
  args: {
431
457
  targetFile: tool.schema.string().describe("Workspace-relative file path"),
@@ -434,7 +460,7 @@ module Agentf
434
460
  return runAgentfCli(context.directory, "code", "related", [args.targetFile]);
435
461
  },
436
462
  }),
437
- agentf_memory_recent: tool({
463
+ "agentf-memory-recent": tool({
438
464
  description: "Get recent Agentf memories from Redis.",
439
465
  args: {
440
466
  limit: tool.schema.number().int().min(1).max(100).optional().describe("How many memories to return"),
@@ -444,7 +470,7 @@ module Agentf
444
470
  return runAgentfCli(context.directory, "memory", "recent", ["-n", String(limit)]);
445
471
  },
446
472
  }),
447
- agentf_memory_search: tool({
473
+ "agentf-memory-search": tool({
448
474
  description: "Search Agentf memories by keyword.",
449
475
  args: {
450
476
  query: tool.schema.string().describe("Search query"),
@@ -455,7 +481,7 @@ module Agentf
455
481
  return runAgentfCli(context.directory, "memory", "search", [args.query, "-n", String(limit)]);
456
482
  },
457
483
  }),
458
- agentf_memory_add_lesson: tool({
484
+ "agentf-memory-add-lesson": tool({
459
485
  description: "Store a lesson memory in Redis.",
460
486
  args: {
461
487
  title: tool.schema.string(),
@@ -473,7 +499,7 @@ module Agentf
473
499
  return runAgentfCli(context.directory, "memory", "add-lesson", commandArgs);
474
500
  },
475
501
  }),
476
- agentf_memory_add_success: tool({
502
+ "agentf-memory-add-success": tool({
477
503
  description: "Store a success memory in Redis.",
478
504
  args: {
479
505
  title: tool.schema.string(),
@@ -491,7 +517,7 @@ module Agentf
491
517
  return runAgentfCli(context.directory, "memory", "add-success", commandArgs);
492
518
  },
493
519
  }),
494
- agentf_memory_add_pitfall: tool({
520
+ "agentf-memory-add-pitfall": tool({
495
521
  description: "Store a pitfall memory in Redis.",
496
522
  args: {
497
523
  title: tool.schema.string(),
@@ -512,6 +538,8 @@ module Agentf
512
538
  },
513
539
  };
514
540
  };
541
+
542
+ export default agentfPlugin;
515
543
  TYPESCRIPT
516
544
  end
517
545
 
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "mcp"
3
+ begin
4
+ require "mcp"
5
+ rescue LoadError
6
+ require_relative "stub"
7
+ end
4
8
  require "json"
5
9
 
6
10
  module Agentf
@@ -17,22 +21,24 @@ module Agentf
17
21
  # AGENTF_MCP_MAX_ARG_LENGTH - max length per string argument
18
22
  class Server
19
23
  KNOWN_TOOLS = %w[
20
- code_glob
21
- code_grep
22
- code_tree
23
- code_related_files
24
- architecture_analyze_layers
25
- memory_recent
26
- memory_search
27
- memory_add_lesson
28
- memory_add_success
29
- memory_add_pitfall
24
+ agentf-code-glob
25
+ agentf-code-grep
26
+ agentf-code-tree
27
+ agentf-code-related-files
28
+ agentf-architecture-analyze-layers
29
+ agentf-memory-recent
30
+ agentf-memory-search
31
+ agentf-memory-neighbors
32
+ agentf-memory-subgraph
33
+ agentf-memory-add-lesson
34
+ agentf-memory-add-success
35
+ agentf-memory-add-pitfall
30
36
  ].freeze
31
37
 
32
38
  WRITE_TOOLS = Set.new(%w[
33
- memory_add_lesson
34
- memory_add_success
35
- memory_add_pitfall
39
+ agentf-memory-add-lesson
40
+ agentf-memory-add-success
41
+ agentf-memory-add-pitfall
36
42
  ]).freeze
37
43
 
38
44
  attr_reader :server, :guardrails
@@ -132,25 +138,25 @@ module Agentf
132
138
  explorer = @explorer
133
139
  mcp_server = self
134
140
 
135
- s.tool("code_glob") do
141
+ s.tool("agentf-code-glob") do
136
142
  description "Find files using project glob patterns."
137
143
  argument :pattern, String, required: true, description: "Glob pattern, e.g. lib/**/*.rb"
138
144
  argument :types, Array, required: false, items: String, description: "File extensions to filter, e.g. [\"rb\",\"py\"]"
139
145
  call do |args|
140
- mcp_server.send(:guard!, "code_glob", **args)
146
+ mcp_server.send(:guard!, "agentf-code-glob", **args)
141
147
  file_types = args[:types]&.empty? ? nil : args[:types]
142
148
  results = explorer.glob(args[:pattern], file_types: file_types)
143
149
  JSON.generate(pattern: args[:pattern], matches: results, count: results.length)
144
150
  end
145
151
  end
146
152
 
147
- s.tool("code_grep") do
153
+ s.tool("agentf-code-grep") do
148
154
  description "Search file contents with regex."
149
155
  argument :pattern, String, required: true, description: "Regex or text to search"
150
156
  argument :file_pattern, String, required: false, description: "Include pattern, e.g. *.rb"
151
157
  argument :context_lines, Integer, required: false, description: "Context lines (0-20)"
152
158
  call do |args|
153
- mcp_server.send(:guard!, "code_grep", **args)
159
+ mcp_server.send(:guard!, "agentf-code-grep", **args)
154
160
  ctx = args[:context_lines] || 2
155
161
  matches = explorer.grep(args[:pattern], file_pattern: args[:file_pattern], context_lines: ctx)
156
162
  serialized = matches.map { |m| m.respond_to?(:to_h) ? m.to_h : m }
@@ -158,22 +164,22 @@ module Agentf
158
164
  end
159
165
  end
160
166
 
161
- s.tool("code_tree") do
167
+ s.tool("agentf-code-tree") do
162
168
  description "Get directory tree structure."
163
169
  argument :depth, Integer, required: false, description: "Max traversal depth (1-10)"
164
170
  call do |args|
165
- mcp_server.send(:guard!, "code_tree", **args)
171
+ mcp_server.send(:guard!, "agentf-code-tree", **args)
166
172
  max_depth = args[:depth] || 3
167
173
  tree = explorer.get_file_tree(max_depth: max_depth)
168
174
  JSON.generate(max_depth: max_depth, tree: tree)
169
175
  end
170
176
  end
171
177
 
172
- s.tool("code_related_files") do
178
+ s.tool("agentf-code-related-files") do
173
179
  description "Find imports and related files for a target file."
174
180
  argument :target_file, String, required: true, description: "Workspace-relative file path"
175
181
  call do |args|
176
- mcp_server.send(:guard!, "code_related_files", **args)
182
+ mcp_server.send(:guard!, "agentf-code-related-files", **args)
177
183
  related = explorer.find_related_files(args[:target_file])
178
184
  JSON.generate(target_file: args[:target_file], related: related)
179
185
  end
@@ -187,28 +193,64 @@ module Agentf
187
193
  memory = @memory
188
194
  mcp_server = self
189
195
 
190
- s.tool("memory_recent") do
196
+ s.tool("agentf-memory-recent") do
191
197
  description "Get recent memories from Redis."
192
198
  argument :limit, Integer, required: false, description: "How many memories to return (1-100)"
193
199
  call do |args|
194
- mcp_server.send(:guard!, "memory_recent", **args)
200
+ mcp_server.send(:guard!, "agentf-memory-recent", **args)
195
201
  result = reviewer.get_recent_memories(limit: args[:limit] || 10)
196
202
  JSON.generate(result)
197
203
  end
198
204
  end
199
205
 
200
- s.tool("memory_search") do
206
+ s.tool("agentf-memory-search") do
201
207
  description "Search memories by keyword."
202
208
  argument :query, String, required: true, description: "Search query"
203
209
  argument :limit, Integer, required: false, description: "How many results to return (1-100)"
204
210
  call do |args|
205
- mcp_server.send(:guard!, "memory_search", **args)
211
+ mcp_server.send(:guard!, "agentf-memory-search", **args)
206
212
  result = reviewer.search(args[:query], limit: args[:limit] || 10)
207
213
  JSON.generate(result)
208
214
  end
209
215
  end
210
216
 
211
- s.tool("memory_add_lesson") do
217
+ s.tool("agentf-memory-neighbors") do
218
+ description "Get neighboring memory nodes by edge traversal."
219
+ argument :node_id, String, required: true, description: "Starting node id"
220
+ argument :relation, String, required: false, description: "Optional relation filter"
221
+ argument :depth, Integer, required: false, description: "Traversal depth"
222
+ argument :limit, Integer, required: false, description: "Maximum edges"
223
+ call do |args|
224
+ mcp_server.send(:guard!, "agentf-memory-neighbors", **args)
225
+ result = reviewer.neighbors(
226
+ args[:node_id],
227
+ relation: args[:relation],
228
+ depth: args[:depth] || 1,
229
+ limit: args[:limit] || 50
230
+ )
231
+ JSON.generate(result)
232
+ end
233
+ end
234
+
235
+ s.tool("agentf-memory-subgraph") do
236
+ description "Build a subgraph from seed ids."
237
+ argument :seed_ids, Array, required: true, items: String, description: "Seed node ids"
238
+ argument :relation_filters, Array, required: false, items: String, description: "Optional relations"
239
+ argument :depth, Integer, required: false, description: "Traversal depth"
240
+ argument :limit, Integer, required: false, description: "Maximum edges"
241
+ call do |args|
242
+ mcp_server.send(:guard!, "agentf-memory-subgraph", **args)
243
+ result = reviewer.subgraph(
244
+ seed_ids: args[:seed_ids] || [],
245
+ relation_filters: args[:relation_filters],
246
+ depth: args[:depth] || 2,
247
+ limit: args[:limit] || 200
248
+ )
249
+ JSON.generate(result)
250
+ end
251
+ end
252
+
253
+ s.tool("agentf-memory-add-lesson") do
212
254
  description "Store a lesson memory in Redis."
213
255
  argument :title, String, required: true, description: "Lesson title"
214
256
  argument :description, String, required: true, description: "Lesson description"
@@ -216,12 +258,12 @@ module Agentf
216
258
  argument :tags, Array, required: false, items: String, description: "Tags"
217
259
  argument :context, String, required: false, description: "Context"
218
260
  call do |args|
219
- mcp_server.send(:guard!, "memory_add_lesson", **args)
261
+ mcp_server.send(:guard!, "agentf-memory-add-lesson", **args)
220
262
  id = memory.store_episode(
221
263
  type: "lesson",
222
264
  title: args[:title],
223
265
  description: args[:description],
224
- agent: args[:agent] || "SPECIALIST",
266
+ agent: args[:agent] || Agentf::AgentRoles::ENGINEER,
225
267
  tags: args[:tags] || [],
226
268
  context: args[:context].to_s,
227
269
  code_snippet: ""
@@ -230,7 +272,7 @@ module Agentf
230
272
  end
231
273
  end
232
274
 
233
- s.tool("memory_add_success") do
275
+ s.tool("agentf-memory-add-success") do
234
276
  description "Store a success memory in Redis."
235
277
  argument :title, String, required: true, description: "Success title"
236
278
  argument :description, String, required: true, description: "Success description"
@@ -238,12 +280,12 @@ module Agentf
238
280
  argument :tags, Array, required: false, items: String, description: "Tags"
239
281
  argument :context, String, required: false, description: "Context"
240
282
  call do |args|
241
- mcp_server.send(:guard!, "memory_add_success", **args)
283
+ mcp_server.send(:guard!, "agentf-memory-add-success", **args)
242
284
  id = memory.store_episode(
243
285
  type: "success",
244
286
  title: args[:title],
245
287
  description: args[:description],
246
- agent: args[:agent] || "SPECIALIST",
288
+ agent: args[:agent] || Agentf::AgentRoles::ENGINEER,
247
289
  tags: args[:tags] || [],
248
290
  context: args[:context].to_s,
249
291
  code_snippet: ""
@@ -252,7 +294,7 @@ module Agentf
252
294
  end
253
295
  end
254
296
 
255
- s.tool("memory_add_pitfall") do
297
+ s.tool("agentf-memory-add-pitfall") do
256
298
  description "Store a pitfall memory in Redis."
257
299
  argument :title, String, required: true, description: "Pitfall title"
258
300
  argument :description, String, required: true, description: "Pitfall description"
@@ -260,12 +302,12 @@ module Agentf
260
302
  argument :tags, Array, required: false, items: String, description: "Tags"
261
303
  argument :context, String, required: false, description: "Context"
262
304
  call do |args|
263
- mcp_server.send(:guard!, "memory_add_pitfall", **args)
305
+ mcp_server.send(:guard!, "agentf-memory-add-pitfall", **args)
264
306
  id = memory.store_episode(
265
307
  type: "pitfall",
266
308
  title: args[:title],
267
309
  description: args[:description],
268
- agent: args[:agent] || "SPECIALIST",
310
+ agent: args[:agent] || Agentf::AgentRoles::ENGINEER,
269
311
  tags: args[:tags] || [],
270
312
  context: args[:context].to_s,
271
313
  code_snippet: ""
@@ -279,13 +321,13 @@ module Agentf
279
321
  architecture = @architecture
280
322
  mcp_server = self
281
323
 
282
- s.tool("architecture_analyze_layers") do
324
+ s.tool("agentf-architecture-analyze-layers") do
283
325
  description "Analyze architecture layers, review violations, or create gradual adoption plans."
284
326
  argument :mode, String, required: false, description: "analyze|review|gradual"
285
327
  argument :limit, Integer, required: false, description: "Maximum violations to return for review mode"
286
328
  argument :goal, String, required: false, description: "Adoption goal for gradual mode"
287
329
  call do |args|
288
- mcp_server.send(:guard!, "architecture_analyze_layers", **args)
330
+ mcp_server.send(:guard!, "agentf-architecture-analyze-layers", **args)
289
331
  case (args[:mode] || "analyze").to_s
290
332
  when "review"
291
333
  JSON.generate(architecture.review_layer_violations(limit: args[:limit] || 20))
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MCP
4
+ # Minimal stub of the MCP::Server DSL so specs can run without the
5
+ # official MCP gem. Provides just enough behavior for tool
6
+ # registration, listing, and invocation.
7
+ class Server
8
+ Tool = Struct.new(:name, :description, :arguments, :handler, keyword_init: true)
9
+
10
+ def initialize(name:, version:)
11
+ @name = name
12
+ @version = version
13
+ @tools = {}
14
+ end
15
+
16
+ def tool(name, &block)
17
+ builder = ToolBuilder.new(name)
18
+ builder.instance_eval(&block)
19
+ @tools[name] = Tool.new(
20
+ name: name,
21
+ description: builder.description,
22
+ arguments: builder.arguments,
23
+ handler: builder.handler
24
+ )
25
+ end
26
+
27
+ def list_tools
28
+ @tools.values.map do |tool|
29
+ {
30
+ name: tool.name,
31
+ description: tool.description.to_s,
32
+ inputSchema: {
33
+ type: "object",
34
+ properties: tool.arguments.transform_values { |arg| arg[:schema] },
35
+ required: tool.arguments.select { |_k, arg| arg[:required] }.keys.map(&:to_s)
36
+ }
37
+ }
38
+ end
39
+ end
40
+
41
+ def call_tool(name, **args)
42
+ tool = @tools[name]
43
+ raise "Unknown tool: #{name}" unless tool
44
+
45
+ tool.handler.call(args)
46
+ rescue StandardError => e
47
+ e.message
48
+ end
49
+
50
+ def run
51
+ raise "Stub MCP::Server cannot run"
52
+ end
53
+
54
+ class ToolBuilder
55
+ attr_reader :description, :arguments, :handler
56
+
57
+ def initialize(name)
58
+ @name = name
59
+ @arguments = {}
60
+ end
61
+
62
+ def description(value = nil)
63
+ @description = value unless value.nil?
64
+ @description
65
+ end
66
+
67
+ def argument(name, _type, required: false, description:, **_opts)
68
+ @arguments[name] = {
69
+ required: required,
70
+ schema: {
71
+ description: description
72
+ }
73
+ }
74
+ end
75
+
76
+ def call(&block)
77
+ @handler = block
78
+ end
79
+ end
80
+ end
81
+ end