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,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agentf
|
|
4
|
+
module Service
|
|
5
|
+
module Providers
|
|
6
|
+
class Base
|
|
7
|
+
attr_reader :pack
|
|
8
|
+
|
|
9
|
+
def initialize(pack: "generic")
|
|
10
|
+
@pack = pack.to_s.downcase
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def name
|
|
14
|
+
self.class.name.split("::").last.upcase
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def build_plan(task:, context: {}, logger: nil)
|
|
18
|
+
logger&.call("[#{name}] Analyzing task: #{task}")
|
|
19
|
+
|
|
20
|
+
workflow_type = classify_task(task)
|
|
21
|
+
agents_needed = pack_workflow_templates.fetch(workflow_type) { workflow_templates.fetch(workflow_type) }
|
|
22
|
+
|
|
23
|
+
logger&.call("[#{name}] Workflow type: #{workflow_type}")
|
|
24
|
+
logger&.call("[#{name}] Agents needed: #{agents_needed.join(', ')}")
|
|
25
|
+
|
|
26
|
+
{
|
|
27
|
+
"provider" => name,
|
|
28
|
+
"pack" => @pack,
|
|
29
|
+
"task" => task,
|
|
30
|
+
"context" => context,
|
|
31
|
+
"workflow_type" => workflow_type,
|
|
32
|
+
"agents_needed" => agents_needed
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def classify_task(task)
|
|
37
|
+
task_lower = task.downcase
|
|
38
|
+
|
|
39
|
+
case
|
|
40
|
+
when task_lower =~ /quick|small|simple/ then "quick_fix"
|
|
41
|
+
when task_lower =~ /fix|bug|error|issue|broken/ then "bugfix"
|
|
42
|
+
when task_lower =~ /explore|find|search|where/ then "exploration"
|
|
43
|
+
when task_lower =~ /refactor|improve|cleanup/ then "refactor"
|
|
44
|
+
else
|
|
45
|
+
"feature"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def workflow_templates
|
|
50
|
+
raise NotImplementedError, "#{self.class} must implement #workflow_templates"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def pack_workflow_templates
|
|
54
|
+
Agentf::Packs.fetch(@pack).fetch("workflow_templates")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def execute_agent(agent_name:, task:, context:, agents:, commands:, logger: nil)
|
|
58
|
+
logger&.call("→ Calling #{agent_name}")
|
|
59
|
+
|
|
60
|
+
agent = agents[agent_name]
|
|
61
|
+
return { "error" => "Agent #{agent_name} not found" } unless agent
|
|
62
|
+
|
|
63
|
+
result = case agent_name
|
|
64
|
+
when "ARCHITECT"
|
|
65
|
+
agent.plan_task(task)
|
|
66
|
+
when "EXPLORER"
|
|
67
|
+
query = context["explore_query"] || "*.rb"
|
|
68
|
+
files = commands.fetch("explorer").glob(query)
|
|
69
|
+
response = agent.explore(query)
|
|
70
|
+
response["files"] = files
|
|
71
|
+
response
|
|
72
|
+
when "TESTER"
|
|
73
|
+
source_file = context["source_file"] || "app/models/application_record.rb"
|
|
74
|
+
tester_commands = commands.fetch("tester")
|
|
75
|
+
tdd_phase = context["tdd_phase"] || "normal"
|
|
76
|
+
|
|
77
|
+
if tdd_phase == "red"
|
|
78
|
+
failure_signature = "expected-failure:#{File.basename(source_file)}:#{Time.now.to_i}"
|
|
79
|
+
{
|
|
80
|
+
"source_file" => source_file,
|
|
81
|
+
"test_file" => source_file.sub(/\.rb$/, "_spec.rb"),
|
|
82
|
+
"tdd_phase" => "red",
|
|
83
|
+
"passed" => false,
|
|
84
|
+
"failure_signature" => failure_signature,
|
|
85
|
+
"stdout" => "Intentional TDD red failure captured"
|
|
86
|
+
}
|
|
87
|
+
else
|
|
88
|
+
template = tester_commands.generate_unit_tests(source_file)
|
|
89
|
+
response = agent.generate_tests(source_file)
|
|
90
|
+
response["generated_code"] = template.test_code
|
|
91
|
+
response["tdd_phase"] = tdd_phase
|
|
92
|
+
response["failure_signature"] = context["tdd_failure_signature"]
|
|
93
|
+
response
|
|
94
|
+
end
|
|
95
|
+
when "DEBUGGER"
|
|
96
|
+
error = context["error"] || "No error provided"
|
|
97
|
+
analysis = commands.fetch("debugger").parse_error(error)
|
|
98
|
+
response = agent.diagnose(error, context: context["error_context"])
|
|
99
|
+
response["analysis"] = {
|
|
100
|
+
"error_type" => analysis.error_type,
|
|
101
|
+
"root_cause" => analysis.possible_causes,
|
|
102
|
+
"suggested_fix" => analysis.suggested_fix
|
|
103
|
+
}
|
|
104
|
+
response
|
|
105
|
+
when "DESIGNER"
|
|
106
|
+
design_spec = context["design_spec"] || "Create a card component"
|
|
107
|
+
spec = commands.fetch("designer").generate_component("GeneratedComponent", design_spec)
|
|
108
|
+
response = agent.implement_design(design_spec)
|
|
109
|
+
response["generated_code"] = spec.code
|
|
110
|
+
response
|
|
111
|
+
when "SPECIALIST"
|
|
112
|
+
subtask = context["current_subtask"] || { "description" => task }
|
|
113
|
+
agent.execute(subtask)
|
|
114
|
+
when "SECURITY"
|
|
115
|
+
agent.assess(task: task, context: context)
|
|
116
|
+
when "REVIEWER"
|
|
117
|
+
last_result = context["execution"] || {}
|
|
118
|
+
agent.review(last_result)
|
|
119
|
+
when "DOCUMENTER"
|
|
120
|
+
agent.sync_docs("project")
|
|
121
|
+
else
|
|
122
|
+
{ "status" => "not_implemented" }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
logger&.call("→ #{agent_name} Complete")
|
|
126
|
+
result
|
|
127
|
+
rescue StandardError => e
|
|
128
|
+
logger&.call("→ #{agent_name} Error: #{e.message}")
|
|
129
|
+
{ "error" => e.message, "agent" => agent_name }
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
class OpenCode < Base
|
|
134
|
+
def workflow_templates
|
|
135
|
+
{
|
|
136
|
+
"feature" => %w[ARCHITECT EXPLORER DESIGNER SPECIALIST TESTER SECURITY REVIEWER DOCUMENTER],
|
|
137
|
+
"bugfix" => %w[ARCHITECT DEBUGGER SPECIALIST TESTER SECURITY REVIEWER],
|
|
138
|
+
"quick_fix" => %w[SPECIALIST SECURITY REVIEWER],
|
|
139
|
+
"exploration" => %w[EXPLORER],
|
|
140
|
+
"refactor" => %w[ARCHITECT EXPLORER SPECIALIST TESTER SECURITY REVIEWER]
|
|
141
|
+
}
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
class Copilot < Base
|
|
146
|
+
def workflow_templates
|
|
147
|
+
{
|
|
148
|
+
"feature" => %w[ARCHITECT SPECIALIST TESTER SECURITY REVIEWER DOCUMENTER],
|
|
149
|
+
"bugfix" => %w[DEBUGGER SPECIALIST TESTER SECURITY REVIEWER],
|
|
150
|
+
"quick_fix" => %w[SPECIALIST REVIEWER],
|
|
151
|
+
"exploration" => %w[EXPLORER],
|
|
152
|
+
"refactor" => %w[ARCHITECT SPECIALIST TESTER REVIEWER]
|
|
153
|
+
}
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agentf
|
|
4
|
+
module Tools
|
|
5
|
+
# Simple data object describing a generated component
|
|
6
|
+
class ComponentSpec
|
|
7
|
+
attr_reader :name, :code, :framework, :style, :props
|
|
8
|
+
|
|
9
|
+
def initialize(name:, code:, framework:, style:, props: [])
|
|
10
|
+
@name = name
|
|
11
|
+
@code = code
|
|
12
|
+
@framework = framework
|
|
13
|
+
@style = style
|
|
14
|
+
@props = props
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def to_h
|
|
18
|
+
{
|
|
19
|
+
"name" => name,
|
|
20
|
+
"code" => code,
|
|
21
|
+
"framework" => framework,
|
|
22
|
+
"style" => style,
|
|
23
|
+
"props" => props
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agentf
|
|
4
|
+
module Tools
|
|
5
|
+
# Data class for error analysis
|
|
6
|
+
class ErrorAnalysis
|
|
7
|
+
attr_reader :error_type, :message, :location, :possible_causes, :suggested_fix, :stack_trace
|
|
8
|
+
|
|
9
|
+
def initialize(error_type:, message:, location:, possible_causes:, suggested_fix:, stack_trace: [])
|
|
10
|
+
@error_type = error_type
|
|
11
|
+
@message = message
|
|
12
|
+
@location = location
|
|
13
|
+
@possible_causes = possible_causes
|
|
14
|
+
@suggested_fix = suggested_fix
|
|
15
|
+
@stack_trace = stack_trace
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agentf
|
|
4
|
+
module Tools
|
|
5
|
+
# Data class for file matches
|
|
6
|
+
class FileMatch
|
|
7
|
+
attr_reader :path, :line_number, :content, :match_type
|
|
8
|
+
|
|
9
|
+
def initialize(path:, line_number:, content:, match_type:)
|
|
10
|
+
@path = path
|
|
11
|
+
@line_number = line_number
|
|
12
|
+
@content = content
|
|
13
|
+
@match_type = match_type
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_h
|
|
17
|
+
{ path: @path, line_number: @line_number, content: @content, match_type: @match_type }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agentf
|
|
4
|
+
module Tools
|
|
5
|
+
# Data class for test templates
|
|
6
|
+
class TestTemplate
|
|
7
|
+
attr_reader :test_file, :test_code, :framework, :dependencies
|
|
8
|
+
|
|
9
|
+
def initialize(test_file:, test_code:, framework:, dependencies: [])
|
|
10
|
+
@test_file = test_file
|
|
11
|
+
@test_code = test_code
|
|
12
|
+
@framework = framework
|
|
13
|
+
@dependencies = dependencies
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/agentf/tools.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "tools/file_match"
|
|
4
|
+
require_relative "tools/test_template"
|
|
5
|
+
require_relative "tools/error_analysis"
|
|
6
|
+
require_relative "tools/component_spec"
|
|
7
|
+
|
|
8
|
+
module Agentf
|
|
9
|
+
module Tools
|
|
10
|
+
# All tool primitives are loaded above.
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agentf
|
|
4
|
+
class WorkflowContract
|
|
5
|
+
STAGES = %w[spec plan execute review finalize].freeze
|
|
6
|
+
|
|
7
|
+
def initialize(enabled:, mode:)
|
|
8
|
+
@enabled = enabled
|
|
9
|
+
@mode = normalize_mode(mode)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def enabled?
|
|
13
|
+
@enabled && @mode != "off"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def mode
|
|
17
|
+
@mode
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def enforcing?
|
|
21
|
+
enabled? && @mode == "enforcing"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def check(stage:, workflow_state:, plan: nil)
|
|
25
|
+
return pass(stage) unless enabled?
|
|
26
|
+
|
|
27
|
+
violations = case stage
|
|
28
|
+
when "spec" then check_spec(workflow_state)
|
|
29
|
+
when "plan" then check_plan(plan)
|
|
30
|
+
when "execute" then check_execute(workflow_state)
|
|
31
|
+
when "review" then check_review(workflow_state)
|
|
32
|
+
when "finalize" then check_finalize(workflow_state)
|
|
33
|
+
else []
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
{
|
|
37
|
+
"stage" => stage,
|
|
38
|
+
"mode" => @mode,
|
|
39
|
+
"ok" => violations.empty?,
|
|
40
|
+
"blocked" => enforcing? && violations.any?,
|
|
41
|
+
"violations" => violations
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def normalize_mode(value)
|
|
48
|
+
mode = value.to_s.strip.downcase
|
|
49
|
+
return mode if %w[advisory enforcing off].include?(mode)
|
|
50
|
+
|
|
51
|
+
"advisory"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def pass(stage)
|
|
55
|
+
{
|
|
56
|
+
"stage" => stage,
|
|
57
|
+
"mode" => @mode,
|
|
58
|
+
"ok" => true,
|
|
59
|
+
"blocked" => false,
|
|
60
|
+
"violations" => []
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def check_spec(workflow_state)
|
|
65
|
+
context = workflow_state["context"] || {}
|
|
66
|
+
violations = []
|
|
67
|
+
|
|
68
|
+
if context["design_spec"].to_s.strip.empty? && context["error"].to_s.strip.empty?
|
|
69
|
+
violations << violation(
|
|
70
|
+
stage: "spec",
|
|
71
|
+
code: "missing_spec_context",
|
|
72
|
+
severity: "warn",
|
|
73
|
+
message: "Expected design_spec or error context before planning"
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
violations
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def check_plan(plan)
|
|
81
|
+
violations = []
|
|
82
|
+
agents = Array(plan && plan["agents_needed"])
|
|
83
|
+
if agents.empty?
|
|
84
|
+
violations << violation(
|
|
85
|
+
stage: "plan",
|
|
86
|
+
code: "missing_agent_plan",
|
|
87
|
+
severity: "error",
|
|
88
|
+
message: "Provider plan did not include any agents"
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
violations
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def check_execute(workflow_state)
|
|
95
|
+
violations = []
|
|
96
|
+
tdd = workflow_state["tdd"] || {}
|
|
97
|
+
|
|
98
|
+
if tdd["enabled"] && !tdd["red_executed"]
|
|
99
|
+
violations << violation(
|
|
100
|
+
stage: "execute",
|
|
101
|
+
code: "tdd_red_not_executed",
|
|
102
|
+
severity: "error",
|
|
103
|
+
message: "TDD red phase must execute before implementation"
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
if tdd["enabled"] && tdd["phase"] == "green" && !tdd["green_executed"]
|
|
108
|
+
violations << violation(
|
|
109
|
+
stage: "execute",
|
|
110
|
+
code: "tdd_green_not_verified",
|
|
111
|
+
severity: "warn",
|
|
112
|
+
message: "TDD green verification has not completed"
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
violations
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def check_review(workflow_state)
|
|
120
|
+
reviewer_result = Array(workflow_state["results"]).find { |item| item["agent"] == "REVIEWER" }
|
|
121
|
+
return [violation(stage: "review", code: "missing_reviewer", severity: "warn", message: "Reviewer step missing")] unless reviewer_result
|
|
122
|
+
|
|
123
|
+
return [] if [true, false].include?(reviewer_result.dig("result", "approved"))
|
|
124
|
+
|
|
125
|
+
[
|
|
126
|
+
violation(
|
|
127
|
+
stage: "review",
|
|
128
|
+
code: "invalid_reviewer_result",
|
|
129
|
+
severity: "warn",
|
|
130
|
+
message: "Reviewer result did not contain approved boolean"
|
|
131
|
+
)
|
|
132
|
+
]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def check_finalize(workflow_state)
|
|
136
|
+
errors = Array(workflow_state["results"]).count { |item| item.dig("result", "error") }
|
|
137
|
+
return [] if errors.zero?
|
|
138
|
+
|
|
139
|
+
[
|
|
140
|
+
violation(
|
|
141
|
+
stage: "finalize",
|
|
142
|
+
code: "workflow_errors_present",
|
|
143
|
+
severity: "error",
|
|
144
|
+
message: "Workflow has #{errors} error result(s)"
|
|
145
|
+
)
|
|
146
|
+
]
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def violation(stage:, code:, severity:, message:)
|
|
150
|
+
{
|
|
151
|
+
"stage" => stage,
|
|
152
|
+
"code" => code,
|
|
153
|
+
"severity" => severity,
|
|
154
|
+
"message" => message
|
|
155
|
+
}
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|