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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/bin/agentf +8 -0
  3. data/lib/agentf/agent_policy.rb +54 -0
  4. data/lib/agentf/agents/architect.rb +67 -0
  5. data/lib/agentf/agents/base.rb +53 -0
  6. data/lib/agentf/agents/debugger.rb +75 -0
  7. data/lib/agentf/agents/designer.rb +69 -0
  8. data/lib/agentf/agents/documenter.rb +58 -0
  9. data/lib/agentf/agents/explorer.rb +65 -0
  10. data/lib/agentf/agents/reviewer.rb +64 -0
  11. data/lib/agentf/agents/security.rb +84 -0
  12. data/lib/agentf/agents/specialist.rb +68 -0
  13. data/lib/agentf/agents/tester.rb +79 -0
  14. data/lib/agentf/agents.rb +19 -0
  15. data/lib/agentf/cli/architecture.rb +83 -0
  16. data/lib/agentf/cli/arg_parser.rb +50 -0
  17. data/lib/agentf/cli/code.rb +165 -0
  18. data/lib/agentf/cli/install.rb +112 -0
  19. data/lib/agentf/cli/memory.rb +393 -0
  20. data/lib/agentf/cli/metrics.rb +103 -0
  21. data/lib/agentf/cli/router.rb +111 -0
  22. data/lib/agentf/cli/update.rb +204 -0
  23. data/lib/agentf/commands/architecture.rb +183 -0
  24. data/lib/agentf/commands/debugger.rb +238 -0
  25. data/lib/agentf/commands/designer.rb +179 -0
  26. data/lib/agentf/commands/explorer.rb +208 -0
  27. data/lib/agentf/commands/memory_reviewer.rb +186 -0
  28. data/lib/agentf/commands/metrics.rb +272 -0
  29. data/lib/agentf/commands/security_scanner.rb +98 -0
  30. data/lib/agentf/commands/tester.rb +232 -0
  31. data/lib/agentf/commands.rb +17 -0
  32. data/lib/agentf/context_builder.rb +35 -0
  33. data/lib/agentf/installer.rb +580 -0
  34. data/lib/agentf/mcp/server.rb +310 -0
  35. data/lib/agentf/memory.rb +530 -0
  36. data/lib/agentf/packs.rb +74 -0
  37. data/lib/agentf/service/providers.rb +158 -0
  38. data/lib/agentf/tools/component_spec.rb +28 -0
  39. data/lib/agentf/tools/error_analysis.rb +19 -0
  40. data/lib/agentf/tools/file_match.rb +21 -0
  41. data/lib/agentf/tools/test_template.rb +17 -0
  42. data/lib/agentf/tools.rb +12 -0
  43. data/lib/agentf/version.rb +5 -0
  44. data/lib/agentf/workflow_contract.rb +158 -0
  45. data/lib/agentf/workflow_engine.rb +424 -0
  46. data/lib/agentf.rb +87 -0
  47. 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
@@ -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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Agentf
4
+ VERSION = "0.3.0"
5
+ 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