agentf 0.3.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.
- checksums.yaml +7 -0
- data/bin/agentf +8 -0
- data/lib/agentf/agent_policy.rb +54 -0
- data/lib/agentf/agents/architect.rb +67 -0
- data/lib/agentf/agents/base.rb +53 -0
- data/lib/agentf/agents/debugger.rb +75 -0
- data/lib/agentf/agents/designer.rb +69 -0
- data/lib/agentf/agents/documenter.rb +58 -0
- data/lib/agentf/agents/explorer.rb +65 -0
- data/lib/agentf/agents/reviewer.rb +64 -0
- data/lib/agentf/agents/security.rb +84 -0
- data/lib/agentf/agents/specialist.rb +68 -0
- data/lib/agentf/agents/tester.rb +79 -0
- data/lib/agentf/agents.rb +19 -0
- data/lib/agentf/cli/architecture.rb +83 -0
- data/lib/agentf/cli/arg_parser.rb +50 -0
- data/lib/agentf/cli/code.rb +165 -0
- data/lib/agentf/cli/install.rb +112 -0
- data/lib/agentf/cli/memory.rb +393 -0
- data/lib/agentf/cli/metrics.rb +103 -0
- data/lib/agentf/cli/router.rb +111 -0
- data/lib/agentf/cli/update.rb +204 -0
- data/lib/agentf/commands/architecture.rb +183 -0
- data/lib/agentf/commands/debugger.rb +238 -0
- data/lib/agentf/commands/designer.rb +179 -0
- data/lib/agentf/commands/explorer.rb +208 -0
- data/lib/agentf/commands/memory_reviewer.rb +186 -0
- data/lib/agentf/commands/metrics.rb +272 -0
- data/lib/agentf/commands/security_scanner.rb +98 -0
- data/lib/agentf/commands/tester.rb +232 -0
- data/lib/agentf/commands.rb +17 -0
- data/lib/agentf/context_builder.rb +35 -0
- data/lib/agentf/installer.rb +580 -0
- data/lib/agentf/mcp/server.rb +310 -0
- data/lib/agentf/memory.rb +530 -0
- data/lib/agentf/packs.rb +74 -0
- data/lib/agentf/service/providers.rb +158 -0
- data/lib/agentf/tools/component_spec.rb +28 -0
- data/lib/agentf/tools/error_analysis.rb +19 -0
- data/lib/agentf/tools/file_match.rb +21 -0
- data/lib/agentf/tools/test_template.rb +17 -0
- data/lib/agentf/tools.rb +12 -0
- data/lib/agentf/version.rb +5 -0
- data/lib/agentf/workflow_contract.rb +158 -0
- data/lib/agentf/workflow_engine.rb +424 -0
- data/lib/agentf.rb +87 -0
- metadata +164 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agentf
|
|
4
|
+
module CLI
|
|
5
|
+
# CLI subcommand for memory operations.
|
|
6
|
+
# Refactored from bin/agentf-memory with bug fixes:
|
|
7
|
+
# - show_help now prints output (finding #6)
|
|
8
|
+
# - extract_limit removes consumed args (finding #8)
|
|
9
|
+
# - search_memories extracts limit before joining query (finding #7)
|
|
10
|
+
# - parse_single_option removes consumed args (finding #9)
|
|
11
|
+
# - @json_output is a proper boolean (finding #10)
|
|
12
|
+
# - by_type accepts business_intent and feature_intent (finding #11)
|
|
13
|
+
class Memory
|
|
14
|
+
include ArgParser
|
|
15
|
+
|
|
16
|
+
VALID_EPISODE_TYPES = %w[pitfall lesson success business_intent feature_intent].freeze
|
|
17
|
+
|
|
18
|
+
def initialize(reviewer: nil, memory: nil)
|
|
19
|
+
@reviewer = reviewer || Commands::MemoryReviewer.new
|
|
20
|
+
@memory = memory || Agentf::Memory::RedisMemory.new
|
|
21
|
+
@json_output = false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def run(args)
|
|
25
|
+
@json_output = !args.delete("--json").nil?
|
|
26
|
+
command = args.shift || "help"
|
|
27
|
+
|
|
28
|
+
case command
|
|
29
|
+
when "recent", "list"
|
|
30
|
+
list_memories(args)
|
|
31
|
+
when "pitfalls"
|
|
32
|
+
list_pitfalls(args)
|
|
33
|
+
when "lessons"
|
|
34
|
+
list_lessons(args)
|
|
35
|
+
when "successes"
|
|
36
|
+
list_successes(args)
|
|
37
|
+
when "intents"
|
|
38
|
+
list_intents(args)
|
|
39
|
+
when "business-intents"
|
|
40
|
+
list_business_intents(args)
|
|
41
|
+
when "feature-intents"
|
|
42
|
+
list_feature_intents(args)
|
|
43
|
+
when "add-business-intent"
|
|
44
|
+
add_business_intent(args)
|
|
45
|
+
when "add-feature-intent"
|
|
46
|
+
add_feature_intent(args)
|
|
47
|
+
when "add-lesson"
|
|
48
|
+
add_episode("lesson", args)
|
|
49
|
+
when "add-success"
|
|
50
|
+
add_episode("success", args)
|
|
51
|
+
when "add-pitfall"
|
|
52
|
+
add_episode("pitfall", args)
|
|
53
|
+
when "tags"
|
|
54
|
+
list_tags
|
|
55
|
+
when "search"
|
|
56
|
+
search_memories(args)
|
|
57
|
+
when "summary", "stats"
|
|
58
|
+
show_summary
|
|
59
|
+
when "by-tag"
|
|
60
|
+
by_tag(args)
|
|
61
|
+
when "by-agent"
|
|
62
|
+
by_agent(args)
|
|
63
|
+
when "by-type"
|
|
64
|
+
by_type(args)
|
|
65
|
+
when "help", "--help", "-h"
|
|
66
|
+
show_help
|
|
67
|
+
else
|
|
68
|
+
$stderr.puts "Unknown memory command: #{command}"
|
|
69
|
+
$stderr.puts
|
|
70
|
+
show_help
|
|
71
|
+
exit 1
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def list_memories(args)
|
|
78
|
+
limit = extract_limit(args)
|
|
79
|
+
result = @reviewer.get_recent_memories(limit: limit)
|
|
80
|
+
output(result)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def list_pitfalls(args)
|
|
84
|
+
limit = extract_limit(args)
|
|
85
|
+
result = @reviewer.get_pitfalls(limit: limit)
|
|
86
|
+
output(result)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def list_lessons(args)
|
|
90
|
+
limit = extract_limit(args)
|
|
91
|
+
result = @reviewer.get_lessons(limit: limit)
|
|
92
|
+
output(result)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def list_successes(args)
|
|
96
|
+
limit = extract_limit(args)
|
|
97
|
+
result = @reviewer.get_successes(limit: limit)
|
|
98
|
+
output(result)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def list_intents(args)
|
|
102
|
+
limit = extract_limit(args)
|
|
103
|
+
kind = args.shift
|
|
104
|
+
|
|
105
|
+
result = case kind
|
|
106
|
+
when "business"
|
|
107
|
+
@reviewer.get_business_intents(limit: limit)
|
|
108
|
+
when "feature"
|
|
109
|
+
@reviewer.get_feature_intents(limit: limit)
|
|
110
|
+
else
|
|
111
|
+
business = @reviewer.get_business_intents(limit: limit)
|
|
112
|
+
feature = @reviewer.get_feature_intents(limit: limit)
|
|
113
|
+
merge_memory_results(business, feature, limit: limit)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
output(result)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def list_business_intents(args)
|
|
120
|
+
limit = extract_limit(args)
|
|
121
|
+
output(@reviewer.get_business_intents(limit: limit))
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def list_feature_intents(args)
|
|
125
|
+
limit = extract_limit(args)
|
|
126
|
+
output(@reviewer.get_feature_intents(limit: limit))
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def add_business_intent(args)
|
|
130
|
+
title = args.shift
|
|
131
|
+
description = args.shift
|
|
132
|
+
|
|
133
|
+
if title.to_s.empty? || description.to_s.empty?
|
|
134
|
+
$stderr.puts "Error: add-business-intent requires <title> <description>"
|
|
135
|
+
exit 1
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
tags = parse_list_option(args, "--tags=")
|
|
139
|
+
constraints = parse_list_option(args, "--constraints=")
|
|
140
|
+
priority = parse_integer_option(args, "--priority=", default: 1)
|
|
141
|
+
|
|
142
|
+
intent_id = @memory.store_business_intent(
|
|
143
|
+
title: title,
|
|
144
|
+
description: description,
|
|
145
|
+
tags: tags,
|
|
146
|
+
constraints: constraints,
|
|
147
|
+
priority: priority
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if @json_output
|
|
151
|
+
puts JSON.generate({ "id" => intent_id, "type" => "business_intent", "status" => "stored" })
|
|
152
|
+
else
|
|
153
|
+
puts "Stored business intent: #{intent_id}"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def add_feature_intent(args)
|
|
158
|
+
title = args.shift
|
|
159
|
+
description = args.shift
|
|
160
|
+
|
|
161
|
+
if title.to_s.empty? || description.to_s.empty?
|
|
162
|
+
$stderr.puts "Error: add-feature-intent requires <title> <description>"
|
|
163
|
+
exit 1
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
tags = parse_list_option(args, "--tags=")
|
|
167
|
+
acceptance_criteria = parse_list_option(args, "--acceptance=")
|
|
168
|
+
non_goals = parse_list_option(args, "--non-goals=")
|
|
169
|
+
related_task_id = parse_single_option(args, "--task=")
|
|
170
|
+
|
|
171
|
+
intent_id = @memory.store_feature_intent(
|
|
172
|
+
title: title,
|
|
173
|
+
description: description,
|
|
174
|
+
tags: tags,
|
|
175
|
+
acceptance_criteria: acceptance_criteria,
|
|
176
|
+
non_goals: non_goals,
|
|
177
|
+
related_task_id: related_task_id
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if @json_output
|
|
181
|
+
puts JSON.generate({ "id" => intent_id, "type" => "feature_intent", "status" => "stored" })
|
|
182
|
+
else
|
|
183
|
+
puts "Stored feature intent: #{intent_id}"
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def add_episode(type, args)
|
|
188
|
+
title = args.shift
|
|
189
|
+
description = args.shift
|
|
190
|
+
|
|
191
|
+
if title.to_s.empty? || description.to_s.empty?
|
|
192
|
+
$stderr.puts "Error: add-#{type} requires <title> <description>"
|
|
193
|
+
exit 1
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
tags = parse_list_option(args, "--tags=")
|
|
197
|
+
context = parse_single_option(args, "--context=").to_s
|
|
198
|
+
agent = parse_single_option(args, "--agent=") || "SPECIALIST"
|
|
199
|
+
code_snippet = parse_single_option(args, "--code=").to_s
|
|
200
|
+
|
|
201
|
+
intent_id = @memory.store_episode(
|
|
202
|
+
type: type,
|
|
203
|
+
title: title,
|
|
204
|
+
description: description,
|
|
205
|
+
context: context,
|
|
206
|
+
tags: tags,
|
|
207
|
+
agent: agent,
|
|
208
|
+
code_snippet: code_snippet
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if @json_output
|
|
212
|
+
puts JSON.generate({ "id" => intent_id, "type" => type, "status" => "stored" })
|
|
213
|
+
else
|
|
214
|
+
puts "Stored #{type}: #{intent_id}"
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def list_tags
|
|
219
|
+
result = @reviewer.get_all_tags
|
|
220
|
+
if @json_output
|
|
221
|
+
puts JSON.generate(result)
|
|
222
|
+
return
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
if result["tags"].empty?
|
|
226
|
+
puts "No tags found."
|
|
227
|
+
else
|
|
228
|
+
puts "Tags (#{result["count"]}):"
|
|
229
|
+
result["tags"].each { |tag| puts " - #{tag}" }
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def search_memories(args)
|
|
234
|
+
# Extract limit BEFORE joining remaining args as query (fixes finding #7)
|
|
235
|
+
limit = extract_limit(args)
|
|
236
|
+
query = args.join(" ")
|
|
237
|
+
|
|
238
|
+
if query.empty?
|
|
239
|
+
$stderr.puts "Error: search requires a query string"
|
|
240
|
+
exit 1
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
result = @reviewer.search(query, limit: limit)
|
|
244
|
+
output(result)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def show_summary
|
|
248
|
+
result = @reviewer.get_summary
|
|
249
|
+
if @json_output
|
|
250
|
+
puts JSON.generate(result)
|
|
251
|
+
return
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
puts "Memory Summary for project: #{result["project"]}"
|
|
255
|
+
puts "-" * 40
|
|
256
|
+
puts "Total memories: #{result["total_memories"]}"
|
|
257
|
+
puts ""
|
|
258
|
+
puts "By type:"
|
|
259
|
+
result["by_type"].each { |type, count| puts " #{type}: #{count}" }
|
|
260
|
+
puts ""
|
|
261
|
+
puts "By agent:"
|
|
262
|
+
result["by_agent"].each { |agent, count| puts " #{agent}: #{count}" }
|
|
263
|
+
puts ""
|
|
264
|
+
puts "Unique tags: #{result["unique_tags"]}"
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def by_tag(args)
|
|
268
|
+
tag = args.shift
|
|
269
|
+
if tag.nil? || tag.empty?
|
|
270
|
+
$stderr.puts "Error: by-tag requires a tag name"
|
|
271
|
+
exit 1
|
|
272
|
+
end
|
|
273
|
+
limit = extract_limit(args)
|
|
274
|
+
result = @reviewer.get_by_tag(tag, limit: limit)
|
|
275
|
+
output(result)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def by_agent(args)
|
|
279
|
+
agent = args.shift
|
|
280
|
+
if agent.nil? || agent.empty?
|
|
281
|
+
$stderr.puts "Error: by-agent requires an agent name"
|
|
282
|
+
exit 1
|
|
283
|
+
end
|
|
284
|
+
limit = extract_limit(args)
|
|
285
|
+
result = @reviewer.get_by_agent(agent, limit: limit)
|
|
286
|
+
output(result)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def by_type(args)
|
|
290
|
+
type = args.shift
|
|
291
|
+
unless VALID_EPISODE_TYPES.include?(type)
|
|
292
|
+
$stderr.puts "Error: type must be one of: #{VALID_EPISODE_TYPES.join(", ")}"
|
|
293
|
+
exit 1
|
|
294
|
+
end
|
|
295
|
+
limit = extract_limit(args)
|
|
296
|
+
result = @reviewer.get_by_type(type, limit: limit)
|
|
297
|
+
output(result)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def merge_memory_results(*results, limit:)
|
|
301
|
+
entries = results.flat_map { |result| result["memories"] || [] }
|
|
302
|
+
sorted = entries.sort_by { |entry| -(entry["created_at_unix"] || 0) }
|
|
303
|
+
{ "memories" => sorted.first(limit), "count" => [sorted.length, limit].min }
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def output(result)
|
|
307
|
+
if result["error"]
|
|
308
|
+
if @json_output
|
|
309
|
+
puts JSON.generate({ "error" => result["error"] })
|
|
310
|
+
else
|
|
311
|
+
$stderr.puts "Error: #{result["error"]}"
|
|
312
|
+
end
|
|
313
|
+
exit 1
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
if @json_output
|
|
317
|
+
puts JSON.generate(result)
|
|
318
|
+
return
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
if result["count"] == 0
|
|
322
|
+
puts "No memories found."
|
|
323
|
+
return
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
memories = result["memories"] || result.values.first
|
|
327
|
+
|
|
328
|
+
memories.each do |mem|
|
|
329
|
+
puts format_memory(mem)
|
|
330
|
+
puts "-" * 40
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def format_memory(mem)
|
|
335
|
+
<<~OUTPUT
|
|
336
|
+
[#{mem["type"]&.upcase}] #{mem["title"]}
|
|
337
|
+
#{mem["created_at"]} by #{mem["agent"]}
|
|
338
|
+
#{mem["description"]}
|
|
339
|
+
#{format_code(mem["code_snippet"]) unless mem["code_snippet"].to_s.empty?}
|
|
340
|
+
Tags: #{mem["tags"]&.join(", ") || "none"}
|
|
341
|
+
OUTPUT
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def format_code(snippet)
|
|
345
|
+
return "" if snippet.to_s.empty?
|
|
346
|
+
|
|
347
|
+
"\n```\n#{snippet.strip}\n```"
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def show_help
|
|
351
|
+
puts <<~HELP
|
|
352
|
+
Usage: agentf memory <command> [options]
|
|
353
|
+
|
|
354
|
+
Commands:
|
|
355
|
+
recent, list List recent memories (default: 10)
|
|
356
|
+
pitfalls List pitfalls (things that went wrong)
|
|
357
|
+
lessons List lessons learned
|
|
358
|
+
successes List successes
|
|
359
|
+
intents [kind] List intents (kind: business|feature)
|
|
360
|
+
business-intents List business intents
|
|
361
|
+
feature-intents List feature intents
|
|
362
|
+
add-business-intent Store business intent
|
|
363
|
+
add-feature-intent Store feature intent
|
|
364
|
+
add-lesson Store lesson memory
|
|
365
|
+
add-success Store success memory
|
|
366
|
+
add-pitfall Store pitfall memory
|
|
367
|
+
tags List all unique tags
|
|
368
|
+
search <query> Search memories by keyword
|
|
369
|
+
summary, stats Show summary statistics
|
|
370
|
+
by-tag <tag> Get memories with specific tag
|
|
371
|
+
by-agent <agent> Get memories from specific agent
|
|
372
|
+
by-type <type> Get memories by type (#{VALID_EPISODE_TYPES.join("|")})
|
|
373
|
+
|
|
374
|
+
Options:
|
|
375
|
+
-n <count> Limit number of results (default: 10)
|
|
376
|
+
--json Output in JSON format
|
|
377
|
+
|
|
378
|
+
Examples:
|
|
379
|
+
agentf memory recent -n 5
|
|
380
|
+
agentf memory pitfalls
|
|
381
|
+
agentf memory intents business -n 5
|
|
382
|
+
agentf memory add-business-intent "Reliability" "Prioritize uptime" --tags=ops,platform --constraints="No downtime;No vendor lock-in"
|
|
383
|
+
agentf memory add-feature-intent "Agent handoff" "Improve orchestrator continuity" --acceptance="Keeps context;Preserves task state"
|
|
384
|
+
agentf memory add-lesson "Refactor strategy" "Extracted adapter seam" --agent=ARCHITECT --tags=architecture
|
|
385
|
+
agentf memory add-success "Provider install works" "Installed copilot + opencode manifests" --agent=SPECIALIST
|
|
386
|
+
agentf memory search "react"
|
|
387
|
+
agentf memory by-tag "performance"
|
|
388
|
+
agentf memory summary
|
|
389
|
+
HELP
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agentf
|
|
4
|
+
module CLI
|
|
5
|
+
class Metrics
|
|
6
|
+
include ArgParser
|
|
7
|
+
|
|
8
|
+
def initialize(metrics: nil)
|
|
9
|
+
@metrics = metrics || Commands::Metrics.new
|
|
10
|
+
@json_output = false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def run(args)
|
|
14
|
+
@json_output = !args.delete("--json").nil?
|
|
15
|
+
command = args.shift || "summary"
|
|
16
|
+
|
|
17
|
+
case command
|
|
18
|
+
when "summary"
|
|
19
|
+
run_summary(args)
|
|
20
|
+
when "parity"
|
|
21
|
+
run_parity(args)
|
|
22
|
+
when "help", "--help", "-h"
|
|
23
|
+
show_help
|
|
24
|
+
else
|
|
25
|
+
emit_error("Unknown metrics command: #{command}")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def run_summary(args)
|
|
32
|
+
limit = extract_limit(args)
|
|
33
|
+
emit(@metrics.summary(limit: limit))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def run_parity(args)
|
|
37
|
+
limit = extract_limit(args)
|
|
38
|
+
emit(@metrics.provider_parity(limit: limit))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def emit(payload)
|
|
42
|
+
if payload["error"]
|
|
43
|
+
emit_error(payload["error"])
|
|
44
|
+
return
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
if @json_output
|
|
48
|
+
puts JSON.generate(payload)
|
|
49
|
+
return
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if payload.key?("completion_rate_gap")
|
|
53
|
+
puts "Provider Parity (#{payload['project']})"
|
|
54
|
+
puts "- opencode runs: #{payload['opencode_runs']}"
|
|
55
|
+
puts "- copilot runs: #{payload['copilot_runs']}"
|
|
56
|
+
puts "- completion rate gap: #{payload['completion_rate_gap']}"
|
|
57
|
+
puts "- approval rate gap: #{payload['approval_rate_gap']}"
|
|
58
|
+
puts "- security issue rate gap: #{payload['security_issue_rate_gap']}"
|
|
59
|
+
puts "- avg agents gap: #{payload['avg_agents_gap']}"
|
|
60
|
+
return
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
puts "Workflow Metrics Summary (#{payload['project']})"
|
|
64
|
+
puts "- total runs: #{payload['total_runs']}"
|
|
65
|
+
puts "- completion rate: #{payload['completion_rate']}"
|
|
66
|
+
puts "- approval rate: #{payload['approval_rate']}"
|
|
67
|
+
puts "- failure rate: #{payload['failure_rate']}"
|
|
68
|
+
puts "- security issue rate: #{payload['security_issue_rate']}"
|
|
69
|
+
puts "- avg agents executed: #{payload['avg_agents_executed']}"
|
|
70
|
+
puts "- contract adherence rate: #{payload['contract_adherence_rate']}"
|
|
71
|
+
puts "- contract blocked runs: #{payload['contract_blocked_runs']}"
|
|
72
|
+
puts "- policy violation rate: #{payload['policy_violation_rate']}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def emit_error(message)
|
|
76
|
+
if @json_output
|
|
77
|
+
puts JSON.generate({ "error" => message })
|
|
78
|
+
else
|
|
79
|
+
$stderr.puts "Error: #{message}"
|
|
80
|
+
end
|
|
81
|
+
exit 1
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def show_help
|
|
85
|
+
puts <<~HELP
|
|
86
|
+
Usage: agentf metrics <command> [options]
|
|
87
|
+
|
|
88
|
+
Commands:
|
|
89
|
+
summary Show workflow success metrics summary
|
|
90
|
+
parity Compare OpenCode vs Copilot metric gaps
|
|
91
|
+
|
|
92
|
+
Options:
|
|
93
|
+
-n <count> Number of recent metric records to evaluate (default: 10)
|
|
94
|
+
--json Output in JSON format
|
|
95
|
+
|
|
96
|
+
Examples:
|
|
97
|
+
agentf metrics summary -n 100
|
|
98
|
+
agentf metrics parity --json
|
|
99
|
+
HELP
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "arg_parser"
|
|
4
|
+
require_relative "memory"
|
|
5
|
+
require_relative "code"
|
|
6
|
+
require_relative "install"
|
|
7
|
+
require_relative "update"
|
|
8
|
+
require_relative "metrics"
|
|
9
|
+
require_relative "architecture"
|
|
10
|
+
|
|
11
|
+
module Agentf
|
|
12
|
+
module CLI
|
|
13
|
+
# Top-level subcommand router for the unified `agentf` CLI.
|
|
14
|
+
#
|
|
15
|
+
# Usage:
|
|
16
|
+
# agentf memory recent -n 5
|
|
17
|
+
# agentf code glob "**/*.rb"
|
|
18
|
+
# agentf install --provider opencode,copilot
|
|
19
|
+
# agentf version
|
|
20
|
+
# agentf help
|
|
21
|
+
class Router
|
|
22
|
+
SUBCOMMANDS = %w[memory code metrics architecture install update mcp-server version help].freeze
|
|
23
|
+
|
|
24
|
+
def run(args)
|
|
25
|
+
subcommand = args.shift || "help"
|
|
26
|
+
|
|
27
|
+
case subcommand
|
|
28
|
+
when "memory"
|
|
29
|
+
Memory.new.run(args)
|
|
30
|
+
when "code"
|
|
31
|
+
Code.new.run(args)
|
|
32
|
+
when "install"
|
|
33
|
+
Install.new.run(args)
|
|
34
|
+
when "metrics"
|
|
35
|
+
if Agentf.config.metrics_enabled
|
|
36
|
+
Metrics.new.run(args)
|
|
37
|
+
else
|
|
38
|
+
$stderr.puts "Metrics are disabled. Set AGENTF_METRICS_ENABLED=true to enable."
|
|
39
|
+
exit 1
|
|
40
|
+
end
|
|
41
|
+
when "architecture"
|
|
42
|
+
Architecture.new.run(args)
|
|
43
|
+
when "update"
|
|
44
|
+
Update.new.run(args)
|
|
45
|
+
when "mcp-server"
|
|
46
|
+
start_mcp_server
|
|
47
|
+
when "version", "--version", "-v"
|
|
48
|
+
puts "agentf #{Agentf::VERSION}"
|
|
49
|
+
when "help", "--help", "-h"
|
|
50
|
+
show_help
|
|
51
|
+
else
|
|
52
|
+
$stderr.puts "Unknown command: #{subcommand}"
|
|
53
|
+
$stderr.puts
|
|
54
|
+
show_help
|
|
55
|
+
exit 1
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def start_mcp_server
|
|
62
|
+
require_relative "../mcp/server"
|
|
63
|
+
Agentf::MCP::Server.new.run
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def show_help
|
|
67
|
+
puts <<~HELP
|
|
68
|
+
Usage: agentf <command> [subcommand] [options]
|
|
69
|
+
|
|
70
|
+
Commands:
|
|
71
|
+
memory Manage agent memory (lessons, pitfalls, successes, intents)
|
|
72
|
+
code Explore codebase (glob, grep, tree, related files)
|
|
73
|
+
metrics Show workflow success and provider parity metrics
|
|
74
|
+
architecture Analyze architecture layers and violations
|
|
75
|
+
install Generate provider manifests (agents, commands, tools)
|
|
76
|
+
update Regenerate manifests when gem version changes
|
|
77
|
+
mcp-server Start MCP server over stdio (for Copilot integration)
|
|
78
|
+
version Show version
|
|
79
|
+
|
|
80
|
+
Global Options:
|
|
81
|
+
--json Output in JSON format (supported by memory and code)
|
|
82
|
+
--help Show help for any command
|
|
83
|
+
|
|
84
|
+
Env:
|
|
85
|
+
AGENTF_METRICS_ENABLED=true|false Enable/disable workflow metrics capture and CLI
|
|
86
|
+
AGENTF_WORKFLOW_CONTRACT_ENABLED=true|false Enable/disable workflow contract checks
|
|
87
|
+
AGENTF_WORKFLOW_CONTRACT_MODE=advisory|enforcing|off Contract behavior mode
|
|
88
|
+
AGENTF_DEFAULT_PACK=generic|rails_standard|rails_37signals|rails_feature_spec
|
|
89
|
+
AGENTF_GEM_PATH=/path/to/gem Path to agentf gem (for OpenCode plugin binary resolution)
|
|
90
|
+
|
|
91
|
+
Examples:
|
|
92
|
+
agentf memory recent -n 5
|
|
93
|
+
agentf memory add-lesson "Title" "Description" --agent=ARCHITECT
|
|
94
|
+
agentf code glob "lib/**/*.rb"
|
|
95
|
+
agentf code grep "def execute" --file-pattern=*.rb
|
|
96
|
+
agentf install --provider opencode,copilot --scope local
|
|
97
|
+
agentf metrics summary -n 100
|
|
98
|
+
agentf metrics parity --json
|
|
99
|
+
agentf architecture analyze
|
|
100
|
+
agentf architecture review --json
|
|
101
|
+
agentf update
|
|
102
|
+
agentf update --force --provider=opencode,copilot
|
|
103
|
+
agentf mcp-server
|
|
104
|
+
agentf version
|
|
105
|
+
|
|
106
|
+
Run 'agentf <command> help' for detailed help on a command.
|
|
107
|
+
HELP
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|