agentf 0.4.6 → 0.5.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 +4 -4
- data/lib/agentf/agents/architect.rb +4 -0
- data/lib/agentf/agents/base.rb +29 -1
- data/lib/agentf/agents/debugger.rb +33 -10
- data/lib/agentf/agents/designer.rb +19 -8
- data/lib/agentf/agents/documenter.rb +6 -0
- data/lib/agentf/agents/explorer.rb +31 -12
- data/lib/agentf/agents/reviewer.rb +5 -0
- data/lib/agentf/agents/security.rb +26 -16
- data/lib/agentf/agents/specialist.rb +32 -18
- data/lib/agentf/agents/tester.rb +47 -8
- data/lib/agentf/cli/agent.rb +95 -0
- data/lib/agentf/cli/eval.rb +203 -0
- data/lib/agentf/cli/install.rb +7 -0
- data/lib/agentf/cli/memory.rb +82 -30
- data/lib/agentf/cli/router.rb +15 -3
- data/lib/agentf/cli/update.rb +9 -2
- data/lib/agentf/commands/memory_reviewer.rb +10 -2
- data/lib/agentf/commands/metrics.rb +16 -14
- data/lib/agentf/commands/registry.rb +28 -0
- data/lib/agentf/evals/report.rb +134 -0
- data/lib/agentf/evals/runner.rb +771 -0
- data/lib/agentf/evals/scenario.rb +211 -0
- data/lib/agentf/installer.rb +486 -348
- data/lib/agentf/mcp/server.rb +291 -49
- data/lib/agentf/memory.rb +97 -19
- data/lib/agentf/service/providers.rb +10 -62
- data/lib/agentf/version.rb +1 -1
- data/lib/agentf/workflow_engine.rb +204 -73
- data/lib/agentf.rb +9 -3
- metadata +8 -3
- data/lib/agentf/packs.rb +0 -74
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6072a9a176fba8e2376b10cc125d78d450057c90c4aa5d95158797429504d1f5
|
|
4
|
+
data.tar.gz: f66dc58d0030e1ba289d3f7046811190b9ae818aca61021729c9da12d19e710c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0397d2603cc060239303773b370f14cc9585b94b4ba5109207ac7f705b18aec244f83443478c15c1ed40be4650c20f9f13d669f224dfc916374e924da4ab6cb1'
|
|
7
|
+
data.tar.gz: a8f7562d3bc1f8bd43897b4692c18a4096509d2c4ce413eaa1c992c61c2a7fada7c0c52228c543d62387c9a56c37672819f1becfd39240bc83066491947678f8
|
data/lib/agentf/agents/base.rb
CHANGED
|
@@ -61,7 +61,15 @@ module Agentf
|
|
|
61
61
|
)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
# Unified execution entrypoint for all agents. Concrete agents must
|
|
65
|
+
# implement `execute(task:, context:, agents:, commands:, logger:)`.
|
|
66
|
+
def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
|
|
67
|
+
raise NotImplementedError, "#{self.class} must implement #execute"
|
|
68
|
+
end
|
|
69
|
+
|
|
64
70
|
def log(message)
|
|
71
|
+
return if ENV["AGENTF_SUPPRESS_AGENT_LOGS"] == "true"
|
|
72
|
+
|
|
65
73
|
puts "\n[#{@name}] #{message}"
|
|
66
74
|
end
|
|
67
75
|
|
|
@@ -83,8 +91,28 @@ module Agentf
|
|
|
83
91
|
result: result
|
|
84
92
|
)
|
|
85
93
|
|
|
86
|
-
|
|
94
|
+
result
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Helper to centralize memory write confirmation handling.
|
|
98
|
+
# Yields a block that performs the memory write. If the memory layer
|
|
99
|
+
# requires confirmation (ask_first policy) a structured hash is
|
|
100
|
+
# returned with confirmation details so agents can merge that into
|
|
101
|
+
# their own return payloads or let the orchestrator handle prompting.
|
|
102
|
+
def safe_memory_write(attempted: {})
|
|
103
|
+
begin
|
|
104
|
+
yield
|
|
105
|
+
rescue Agentf::Memory::RedisMemory::ConfirmationRequired => e
|
|
106
|
+
log "[MEMORY] Confirmation required: #{e.message} -- details=#{e.details.inspect}"
|
|
107
|
+
{
|
|
108
|
+
"confirmation_required" => true,
|
|
109
|
+
"confirmation_details" => e.details,
|
|
110
|
+
"attempted" => attempted,
|
|
111
|
+
"confirmed_write_token" => "confirmed",
|
|
112
|
+
"confirmation_prompt" => "Ask the user whether to save this memory. If they approve, rerun the same tool with confirmedWrite=confirmed. If they decline, do not retry."
|
|
113
|
+
}
|
|
87
114
|
end
|
|
88
115
|
end
|
|
116
|
+
end
|
|
89
117
|
end
|
|
90
118
|
end
|
|
@@ -45,8 +45,8 @@ module Agentf
|
|
|
45
45
|
|
|
46
46
|
def self.policy_boundaries
|
|
47
47
|
{
|
|
48
|
-
"always" => ["Return analysis with root causes and suggested fix"
|
|
49
|
-
"ask_first" => ["Applying speculative fixes without reproducible error"],
|
|
48
|
+
"always" => ["Return analysis with root causes and suggested fix"],
|
|
49
|
+
"ask_first" => ["Applying speculative fixes without reproducible error", "Persisting debugging lessons to memory"],
|
|
50
50
|
"never" => ["Discard stack trace context when available"],
|
|
51
51
|
"required_inputs" => ["error_text"],
|
|
52
52
|
"required_outputs" => ["analysis", "success"]
|
|
@@ -66,14 +66,32 @@ module Agentf
|
|
|
66
66
|
|
|
67
67
|
analysis = @commands.parse_error(error)
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
res = safe_memory_write(attempted: { action: "store_lesson", title: "Debugged: #{error[0..50]}...", tags: ["debugging", "error", "fix"], agent: name }) do
|
|
70
|
+
memory.store_episode(
|
|
71
|
+
type: "lesson",
|
|
72
|
+
title: "Debugged: #{error[0..50]}...",
|
|
73
|
+
description: "Root cause: #{analysis.possible_causes.first}. Fix: #{analysis.suggested_fix}",
|
|
74
|
+
context: context.to_s,
|
|
75
|
+
tags: ["debugging", "error", "fix"],
|
|
76
|
+
agent: name
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if res.is_a?(Hash) && res["confirmation_required"]
|
|
81
|
+
log "Root cause: #{analysis.possible_causes.first}"
|
|
82
|
+
log "Suggested fix: #{analysis.suggested_fix}"
|
|
83
|
+
return {
|
|
84
|
+
"success" => false,
|
|
85
|
+
"confirmation_required" => true,
|
|
86
|
+
"confirmation_details" => res["confirmation_details"],
|
|
87
|
+
"analysis" => {
|
|
88
|
+
"error_type" => analysis.error_type,
|
|
89
|
+
"possible_causes" => analysis.possible_causes,
|
|
90
|
+
"suggested_fix" => analysis.suggested_fix,
|
|
91
|
+
"stack_trace" => analysis.stack_trace
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
end
|
|
77
95
|
|
|
78
96
|
log "Root cause: #{analysis.possible_causes.first}"
|
|
79
97
|
log "Suggested fix: #{analysis.suggested_fix}"
|
|
@@ -90,6 +108,11 @@ module Agentf
|
|
|
90
108
|
}
|
|
91
109
|
end
|
|
92
110
|
end
|
|
111
|
+
|
|
112
|
+
def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
|
|
113
|
+
error_text = task.is_a?(String) ? task : context["error"]
|
|
114
|
+
diagnose(error_text, context: context)
|
|
115
|
+
end
|
|
93
116
|
end
|
|
94
117
|
end
|
|
95
118
|
end
|
|
@@ -46,7 +46,7 @@ module Agentf
|
|
|
46
46
|
def self.policy_boundaries
|
|
47
47
|
{
|
|
48
48
|
"always" => ["Return generated component details", "Persist successful implementation pattern"],
|
|
49
|
-
"ask_first" => ["Changing primary UI framework"],
|
|
49
|
+
"ask_first" => ["Changing primary UI framework", "Persisting successful implementation patterns to memory"],
|
|
50
50
|
"never" => ["Return empty generated code for successful design task"],
|
|
51
51
|
"required_inputs" => ["design_spec"],
|
|
52
52
|
"required_outputs" => ["component", "generated_code", "success"]
|
|
@@ -64,13 +64,19 @@ module Agentf
|
|
|
64
64
|
|
|
65
65
|
spec = @commands.generate_component("GeneratedComponent", design_spec)
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
res = safe_memory_write(attempted: { action: "store_success", title: "Implemented design: #{design_spec}", tags: ["design", "ui", framework], agent: name }) do
|
|
68
|
+
memory.store_success(
|
|
69
|
+
title: "Implemented design: #{design_spec}",
|
|
70
|
+
description: "Created #{spec.name} in #{spec.framework}",
|
|
71
|
+
context: "Framework: #{framework}",
|
|
72
|
+
tags: ["design", "ui", framework],
|
|
73
|
+
agent: name
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if res.is_a?(Hash) && res["confirmation_required"]
|
|
78
|
+
return { "design_spec" => design_spec, "component" => spec.name, "framework" => framework, "generated_code" => spec.code, "success" => true }.merge(res)
|
|
79
|
+
end
|
|
74
80
|
|
|
75
81
|
log "Created component: #{spec.name}"
|
|
76
82
|
|
|
@@ -83,6 +89,11 @@ module Agentf
|
|
|
83
89
|
}
|
|
84
90
|
end
|
|
85
91
|
end
|
|
92
|
+
|
|
93
|
+
def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
|
|
94
|
+
spec = task.is_a?(String) ? task : context["design_spec"]
|
|
95
|
+
implement_design(spec, framework: context["framework"] || "react")
|
|
96
|
+
end
|
|
86
97
|
end
|
|
87
98
|
end
|
|
88
99
|
end
|
|
@@ -69,6 +69,12 @@ module Agentf
|
|
|
69
69
|
"total_memories" => memories.size
|
|
70
70
|
}
|
|
71
71
|
end
|
|
72
|
+
|
|
73
|
+
def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
|
|
74
|
+
project = task.is_a?(String) ? task : (context["project_name"] || "project")
|
|
75
|
+
sync_docs(project)
|
|
76
|
+
end
|
|
77
|
+
|
|
72
78
|
end
|
|
73
79
|
end
|
|
74
80
|
end
|
|
@@ -11,8 +11,8 @@ module Agentf
|
|
|
11
11
|
COMMANDS = %w[glob grep read_file].freeze
|
|
12
12
|
MEMORY_CONCEPTS = {
|
|
13
13
|
"reads" => [],
|
|
14
|
-
"writes" => ["
|
|
15
|
-
"policy" => "Store
|
|
14
|
+
"writes" => ["store_lesson"],
|
|
15
|
+
"policy" => "Store research findings as lessons after user confirmation."
|
|
16
16
|
}.freeze
|
|
17
17
|
|
|
18
18
|
def self.description
|
|
@@ -45,8 +45,8 @@ module Agentf
|
|
|
45
45
|
|
|
46
46
|
def self.policy_boundaries
|
|
47
47
|
{
|
|
48
|
-
"always" => ["Return concrete file evidence"
|
|
49
|
-
"ask_first" => ["Scanning outside configured base path"],
|
|
48
|
+
"always" => ["Return concrete file evidence"],
|
|
49
|
+
"ask_first" => ["Scanning outside configured base path", "Persisting research lessons to memory"],
|
|
50
50
|
"never" => ["Mutate project files during exploration"],
|
|
51
51
|
"required_inputs" => [],
|
|
52
52
|
"required_outputs" => ["files", "context_gathered"]
|
|
@@ -63,19 +63,38 @@ module Agentf
|
|
|
63
63
|
|
|
64
64
|
files = @commands.glob(query, file_types: nil)
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
66
|
+
res = safe_memory_write(attempted: { action: "store_lesson", title: "Research finding: #{query}", tags: ["research", "exploration"], agent: name }) do
|
|
67
|
+
memory.store_lesson(
|
|
68
|
+
title: "Research finding: #{query}",
|
|
69
|
+
description: "Found #{files.size} relevant files during exploration",
|
|
70
|
+
context: "Search pattern: #{file_pattern || 'all files'}",
|
|
71
|
+
tags: ["research", "exploration"],
|
|
72
|
+
agent: name
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
if res.is_a?(Hash) && res["confirmation_required"]
|
|
77
|
+
log "Memory confirmation required during exploration: #{res['confirmation_details'].inspect}"
|
|
78
|
+
return {
|
|
79
|
+
"files" => files,
|
|
80
|
+
"context_gathered" => true,
|
|
81
|
+
"confirmation_required" => true,
|
|
82
|
+
"confirmation_details" => res["confirmation_details"],
|
|
83
|
+
"attempted" => res["attempted"],
|
|
84
|
+
"confirmed_write_token" => res["confirmed_write_token"],
|
|
85
|
+
"confirmation_prompt" => res["confirmation_prompt"]
|
|
86
|
+
}
|
|
87
|
+
end
|
|
74
88
|
|
|
75
89
|
log "Found #{files.size} files"
|
|
76
90
|
|
|
77
91
|
{ "query" => query, "files" => files, "context_gathered" => true }
|
|
78
92
|
end
|
|
93
|
+
|
|
94
|
+
def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
|
|
95
|
+
query = context["explore_query"] || task || "*.rb"
|
|
96
|
+
explore(query, file_pattern: context["file_pattern"])
|
|
97
|
+
end
|
|
79
98
|
end
|
|
80
99
|
end
|
|
81
100
|
end
|
|
@@ -77,6 +77,11 @@ module Agentf
|
|
|
77
77
|
{ "approved" => approved, "issues" => issues }
|
|
78
78
|
end
|
|
79
79
|
end
|
|
80
|
+
|
|
81
|
+
def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
|
|
82
|
+
subtask = task.is_a?(Hash) ? task : context["execution"] || {}
|
|
83
|
+
review(subtask)
|
|
84
|
+
end
|
|
80
85
|
end
|
|
81
86
|
end
|
|
82
87
|
end
|
|
@@ -45,8 +45,8 @@ module Agentf
|
|
|
45
45
|
|
|
46
46
|
def self.policy_boundaries
|
|
47
47
|
{
|
|
48
|
-
"always" => ["Return issue list and best practices"
|
|
49
|
-
"ask_first" => ["Allowing known secret patterns in context"],
|
|
48
|
+
"always" => ["Return issue list and best practices"],
|
|
49
|
+
"ask_first" => ["Allowing known secret patterns in context", "Persisting security scan findings to memory"],
|
|
50
50
|
"never" => ["Echo raw secrets in output"],
|
|
51
51
|
"required_inputs" => ["task"],
|
|
52
52
|
"required_outputs" => ["issues", "best_practices"]
|
|
@@ -66,27 +66,37 @@ module Agentf
|
|
|
66
66
|
summary = summarize_findings(findings)
|
|
67
67
|
|
|
68
68
|
if findings["issues"].empty?
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
69
|
+
res = safe_memory_write(attempted: { action: "store_success", title: "Security review passed", tags: ["security", "pass"], agent: name }) do
|
|
70
|
+
memory.store_success(
|
|
71
|
+
title: "Security review passed",
|
|
72
|
+
description: summary,
|
|
73
|
+
context: task,
|
|
74
|
+
tags: ["security", "pass"],
|
|
75
|
+
agent: name
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
return findings.merge(res) if res.is_a?(Hash) && res["confirmation_required"]
|
|
76
79
|
else
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
res = safe_memory_write(attempted: { action: "store_pitfall", title: "Security findings detected", tags: ["security", "warning"], agent: name }) do
|
|
81
|
+
memory.store_pitfall(
|
|
82
|
+
title: "Security findings detected",
|
|
83
|
+
description: summary,
|
|
84
|
+
context: task,
|
|
85
|
+
tags: ["security", "warning"],
|
|
86
|
+
agent: name
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
return findings.merge(res) if res.is_a?(Hash) && res["confirmation_required"]
|
|
84
90
|
end
|
|
85
91
|
|
|
86
92
|
findings.merge("best_practices" => @commands.best_practices)
|
|
87
93
|
end
|
|
88
94
|
end
|
|
89
95
|
|
|
96
|
+
def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
|
|
97
|
+
assess(task: task, context: context)
|
|
98
|
+
end
|
|
99
|
+
|
|
90
100
|
private
|
|
91
101
|
|
|
92
102
|
def summarize_findings(findings)
|
|
@@ -45,14 +45,16 @@ module Agentf
|
|
|
45
45
|
def self.policy_boundaries
|
|
46
46
|
{
|
|
47
47
|
"always" => ["Persist execution outcome", "Return deterministic success boolean"],
|
|
48
|
-
"ask_first" => ["Applying architecture style changes across unrelated modules"],
|
|
48
|
+
"ask_first" => ["Applying architecture style changes across unrelated modules", "Persisting execution outcomes to memory (success/pitfall)"] ,
|
|
49
49
|
"never" => ["Claim implementation complete without execution result"],
|
|
50
50
|
"required_inputs" => ["description"],
|
|
51
51
|
"required_outputs" => ["subtask_id", "success"]
|
|
52
52
|
}
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
def execute(
|
|
55
|
+
def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
|
|
56
|
+
subtask = task.is_a?(Hash) ? task : (context["current_subtask"] || { "description" => task })
|
|
57
|
+
|
|
56
58
|
normalized_subtask = subtask.merge(
|
|
57
59
|
"id" => subtask["id"] || "ad-hoc",
|
|
58
60
|
"description" => subtask["description"] || "Execute implementation step"
|
|
@@ -64,23 +66,35 @@ module Agentf
|
|
|
64
66
|
success = normalized_subtask.fetch("success", true)
|
|
65
67
|
|
|
66
68
|
if success
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
69
|
+
res = safe_memory_write(attempted: { action: "store_success", title: "Completed: #{normalized_subtask['description']}", tags: ["implementation", normalized_subtask.fetch("language", "general")], agent: name }) do
|
|
70
|
+
memory.store_success(
|
|
71
|
+
title: "Completed: #{normalized_subtask['description']}",
|
|
72
|
+
description: "Successfully executed subtask #{normalized_subtask['id']}",
|
|
73
|
+
context: "Working on #{normalized_subtask.fetch('task', 'unknown task')}",
|
|
74
|
+
tags: ["implementation", normalized_subtask.fetch("language", "general")],
|
|
75
|
+
agent: name
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
if res.is_a?(Hash) && res["confirmation_required"]
|
|
80
|
+
log "Memory confirmation required when storing success: #{res['confirmation_details'].inspect}"
|
|
81
|
+
return { "subtask_id" => normalized_subtask["id"], "success" => success, "result" => "Code executed", "confirmation_required" => true, "confirmation_details" => res["confirmation_details"], "attempted" => res["attempted"] }
|
|
82
|
+
end
|
|
75
83
|
else
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
res = safe_memory_write(attempted: { action: "store_pitfall", title: "Failed: #{normalized_subtask['description']}", tags: ["failure", "implementation"], agent: name }) do
|
|
85
|
+
memory.store_pitfall(
|
|
86
|
+
title: "Failed: #{normalized_subtask['description']}",
|
|
87
|
+
description: "Subtask #{normalized_subtask['id']} failed",
|
|
88
|
+
context: "Working on #{normalized_subtask.fetch('task', 'unknown task')}",
|
|
89
|
+
tags: ["failure", "implementation"],
|
|
90
|
+
agent: name
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if res.is_a?(Hash) && res["confirmation_required"]
|
|
95
|
+
log "Memory confirmation required when storing pitfall: #{res['confirmation_details'].inspect}"
|
|
96
|
+
return { "subtask_id" => normalized_subtask["id"], "success" => success, "result" => "Code executed", "confirmation_required" => true, "confirmation_details" => res["confirmation_details"], "attempted" => res["attempted"] }
|
|
97
|
+
end
|
|
84
98
|
end
|
|
85
99
|
|
|
86
100
|
{ "subtask_id" => normalized_subtask["id"], "success" => success, "result" => "Code executed" }
|
data/lib/agentf/agents/tester.rb
CHANGED
|
@@ -46,7 +46,7 @@ module Agentf
|
|
|
46
46
|
def self.policy_boundaries
|
|
47
47
|
{
|
|
48
48
|
"always" => ["Produce framework-aware tests", "Verify red/green state when TDD enabled"],
|
|
49
|
-
"ask_first" => ["Changing test framework conventions"],
|
|
49
|
+
"ask_first" => ["Changing test framework conventions", "Persisting test-generation outcomes to memory"],
|
|
50
50
|
"never" => ["Mark passing when command output is uncertain"],
|
|
51
51
|
"required_inputs" => [],
|
|
52
52
|
"required_outputs" => ["test_file"]
|
|
@@ -63,13 +63,20 @@ module Agentf
|
|
|
63
63
|
|
|
64
64
|
template = @commands.generate_unit_tests(code_file)
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
res = safe_memory_write(attempted: { action: "store_success", title: "Generated #{test_type} tests for #{code_file}", tags: ["testing", test_type, code_file.split(".").last], agent: name }) do
|
|
67
|
+
memory.store_success(
|
|
68
|
+
title: "Generated #{test_type} tests for #{code_file}",
|
|
69
|
+
description: "Created #{template.test_file} with #{test_type} tests",
|
|
70
|
+
context: "Test framework: #{template.framework}",
|
|
71
|
+
tags: ["testing", test_type, code_file.split(".").last],
|
|
72
|
+
agent: name
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
if res.is_a?(Hash) && res["confirmation_required"]
|
|
77
|
+
log "Memory confirmation required when storing generated tests: #{res['confirmation_details'].inspect}"
|
|
78
|
+
return { "test_file" => template.test_file, "test_type" => test_type, "generated_code" => template.test_code, "confirmation_required" => true, "confirmation_details" => res["confirmation_details"], "attempted" => res["attempted"] }
|
|
79
|
+
end
|
|
73
80
|
|
|
74
81
|
log "Created: #{template.test_file}"
|
|
75
82
|
|
|
@@ -90,6 +97,38 @@ module Agentf
|
|
|
90
97
|
|
|
91
98
|
{ "test_file" => test_file, "passed" => result["passed"] }
|
|
92
99
|
end
|
|
100
|
+
|
|
101
|
+
def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
|
|
102
|
+
# Support provider-driven TDD red-phase: when context signals a red phase,
|
|
103
|
+
# generate tests via the tester commands (if provided) and return a
|
|
104
|
+
# simulated failing test signature so orchestrator flows can short-circuit.
|
|
105
|
+
if context.to_h["tdd_phase"] == "red"
|
|
106
|
+
tester_commands = if commands.respond_to?(:fetch)
|
|
107
|
+
commands.fetch("tester", nil)
|
|
108
|
+
else
|
|
109
|
+
commands["tester"]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
begin
|
|
113
|
+
tester_commands&.generate_unit_tests(context.to_h["source_file"]) if tester_commands&.respond_to?(:generate_unit_tests)
|
|
114
|
+
rescue StandardError
|
|
115
|
+
# ignore command errors for the simulated red phase
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
return { "tdd_phase" => "red", "passed" => false, "failure_signature" => "expected-failure-#{context.to_h["source_file"] || 'unspecified'}" }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
action = context["action"] || (task.is_a?(String) ? "generate_tests" : context["action"])
|
|
122
|
+
case action
|
|
123
|
+
when "generate_tests"
|
|
124
|
+
code_file = task.is_a?(String) ? task : context["code_file"]
|
|
125
|
+
generate_tests(code_file, test_type: context["test_type"] || "unit")
|
|
126
|
+
when "run_tests"
|
|
127
|
+
run_tests(context["test_file"] || task)
|
|
128
|
+
else
|
|
129
|
+
{ "error" => "Unknown action for Tester: #{action}" }
|
|
130
|
+
end
|
|
131
|
+
end
|
|
93
132
|
end
|
|
94
133
|
end
|
|
95
134
|
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "arg_parser"
|
|
4
|
+
require_relative "../commands/registry"
|
|
5
|
+
require_relative "../commands"
|
|
6
|
+
require_relative "../agents"
|
|
7
|
+
require_relative "../memory"
|
|
8
|
+
|
|
9
|
+
module Agentf
|
|
10
|
+
module CLI
|
|
11
|
+
# CLI entry for running a single agent and returning JSON output.
|
|
12
|
+
class Agent
|
|
13
|
+
include ArgParser
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
@memory = Agentf::Memory::RedisMemory.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def run(args)
|
|
20
|
+
if args.empty? || args.include?("--help") || args.include?("help")
|
|
21
|
+
show_help
|
|
22
|
+
return
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Allow callers (like the TypeScript plugin) to append `--json` to
|
|
26
|
+
# request machine-readable output. Strip it here so it's not treated as
|
|
27
|
+
# part of the agent payload.
|
|
28
|
+
args = args.dup
|
|
29
|
+
json_output = !args.delete("--json").nil?
|
|
30
|
+
confirmed_write = parse_single_option(args, "--confirmed-write=")
|
|
31
|
+
|
|
32
|
+
agent_name = args.shift
|
|
33
|
+
payload = args.join(" ")
|
|
34
|
+
|
|
35
|
+
# Build command registry with default implementations
|
|
36
|
+
registry = Agentf::Commands::Registry.new
|
|
37
|
+
# Register known command providers
|
|
38
|
+
registry.register("explorer", Agentf::Commands::Explorer.new)
|
|
39
|
+
registry.register("tester", Agentf::Commands::Tester.new)
|
|
40
|
+
registry.register("debugger", Agentf::Commands::Debugger.new)
|
|
41
|
+
registry.register("designer", Agentf::Commands::Designer.new)
|
|
42
|
+
registry.register("security", Agentf::Commands::SecurityScanner.new)
|
|
43
|
+
registry.register("architecture", Agentf::Commands::Architecture.new)
|
|
44
|
+
|
|
45
|
+
# Load agents (classes already required via lib/agentf)
|
|
46
|
+
agents = {}
|
|
47
|
+
Agentf::Agents.constants.each do |const|
|
|
48
|
+
klass = Agentf::Agents.const_get(const)
|
|
49
|
+
next unless klass.is_a?(Class) && klass < Agentf::Agents::Base
|
|
50
|
+
agents[klass.typed_name] = klass.new(@memory)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
agent = agents[agent_name.upcase]
|
|
54
|
+
unless agent
|
|
55
|
+
$stderr.puts JSON.generate({ ok: false, error: "Agent not found: #{agent_name}" })
|
|
56
|
+
exit 1
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Parse possible JSON payload
|
|
60
|
+
parsed = nil
|
|
61
|
+
begin
|
|
62
|
+
parsed = JSON.parse(payload) unless payload.strip.empty?
|
|
63
|
+
rescue StandardError
|
|
64
|
+
parsed = payload
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
previous = ENV["AGENTF_SUPPRESS_AGENT_LOGS"]
|
|
68
|
+
previous_auto_confirm = ENV["AGENTF_AUTO_CONFIRM_MEMORIES"]
|
|
69
|
+
ENV["AGENTF_SUPPRESS_AGENT_LOGS"] = "true" if json_output
|
|
70
|
+
ENV["AGENTF_AUTO_CONFIRM_MEMORIES"] = "true" unless confirmed_write.to_s.empty?
|
|
71
|
+
|
|
72
|
+
result = agent.execute(
|
|
73
|
+
task: parsed || payload,
|
|
74
|
+
context: { "confirmed_write" => confirmed_write },
|
|
75
|
+
agents: agents,
|
|
76
|
+
commands: registry,
|
|
77
|
+
logger: json_output ? nil : method(:puts)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
puts JSON.generate(result)
|
|
81
|
+
ensure
|
|
82
|
+
ENV["AGENTF_SUPPRESS_AGENT_LOGS"] = previous if json_output
|
|
83
|
+
ENV["AGENTF_AUTO_CONFIRM_MEMORIES"] = previous_auto_confirm unless confirmed_write.to_s.empty?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def show_help
|
|
87
|
+
puts <<~HELP
|
|
88
|
+
Usage: agentf agent <AGENT_NAME> [payload] [--json] [--confirmed-write=<token>]
|
|
89
|
+
|
|
90
|
+
Runs a single agent and prints JSON result.
|
|
91
|
+
HELP
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|