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.
data/lib/agentf/memory.rb CHANGED
@@ -10,6 +10,8 @@ module Agentf
10
10
  module Memory
11
11
  # Redis-backed memory system for agent learning
12
12
  class RedisMemory
13
+ EDGE_INDEX = "edge:links"
14
+
13
15
  attr_reader :project
14
16
 
15
17
  def initialize(redis_url: nil, project: nil)
@@ -21,7 +23,7 @@ module Agentf
21
23
  ensure_indexes if @search_supported
22
24
  end
23
25
 
24
- def store_task(content:, embedding: [], language: nil, task_type: nil, success: true, agent: "ARCHITECT")
26
+ def store_task(content:, embedding: [], language: nil, task_type: nil, success: true, agent: Agentf::AgentRoles::PLANNER)
25
27
  task_id = "task_#{SecureRandom.hex(4)}"
26
28
 
27
29
  data = {
@@ -42,9 +44,19 @@ module Agentf
42
44
  task_id
43
45
  end
44
46
 
45
- def store_episode(type:, title:, description:, context: "", code_snippet: "", tags: [], agent: "SPECIALIST", related_task_id: nil,
46
- metadata: {})
47
+ def store_episode(type:, title:, description:, context: "", code_snippet: "", tags: [], agent: Agentf::AgentRoles::ENGINEER,
48
+ related_task_id: nil, metadata: {}, entity_ids: [], relationships: [], parent_episode_id: nil, causal_from: nil)
47
49
  episode_id = "episode_#{SecureRandom.hex(4)}"
50
+ normalized_metadata = enrich_metadata(
51
+ metadata: metadata,
52
+ agent: agent,
53
+ type: type,
54
+ tags: tags,
55
+ entity_ids: entity_ids,
56
+ relationships: relationships,
57
+ parent_episode_id: parent_episode_id,
58
+ causal_from: causal_from
59
+ )
48
60
 
49
61
  data = {
50
62
  "id" => episode_id,
@@ -58,7 +70,11 @@ module Agentf
58
70
  "created_at" => Time.now.to_i,
59
71
  "agent" => agent,
60
72
  "related_task_id" => related_task_id || "",
61
- "metadata" => metadata
73
+ "entity_ids" => entity_ids,
74
+ "relationships" => relationships,
75
+ "parent_episode_id" => parent_episode_id.to_s,
76
+ "causal_from" => causal_from.to_s,
77
+ "metadata" => normalized_metadata
62
78
  }
63
79
 
64
80
  key = "episodic:#{episode_id}"
@@ -79,10 +95,19 @@ module Agentf
79
95
  @client.set(key, payload)
80
96
  end
81
97
 
98
+ persist_relationship_edges(
99
+ episode_id: episode_id,
100
+ related_task_id: related_task_id,
101
+ relationships: relationships,
102
+ metadata: normalized_metadata,
103
+ tags: tags,
104
+ agent: agent
105
+ )
106
+
82
107
  episode_id
83
108
  end
84
109
 
85
- def store_success(title:, description:, context: "", code_snippet: "", tags: [], agent: "SPECIALIST")
110
+ def store_success(title:, description:, context: "", code_snippet: "", tags: [], agent: Agentf::AgentRoles::ENGINEER)
86
111
  store_episode(
87
112
  type: "success",
88
113
  title: title,
@@ -94,7 +119,7 @@ module Agentf
94
119
  )
95
120
  end
96
121
 
97
- def store_pitfall(title:, description:, context: "", code_snippet: "", tags: [], agent: "SPECIALIST")
122
+ def store_pitfall(title:, description:, context: "", code_snippet: "", tags: [], agent: Agentf::AgentRoles::ENGINEER)
98
123
  store_episode(
99
124
  type: "pitfall",
100
125
  title: title,
@@ -106,7 +131,7 @@ module Agentf
106
131
  )
107
132
  end
108
133
 
109
- def store_lesson(title:, description:, context: "", code_snippet: "", tags: [], agent: "SPECIALIST")
134
+ def store_lesson(title:, description:, context: "", code_snippet: "", tags: [], agent: Agentf::AgentRoles::ENGINEER)
110
135
  store_episode(
111
136
  type: "lesson",
112
137
  title: title,
@@ -118,7 +143,7 @@ module Agentf
118
143
  )
119
144
  end
120
145
 
121
- def store_business_intent(title:, description:, constraints: [], tags: [], agent: "WORKFLOW_ENGINE", priority: 1)
146
+ def store_business_intent(title:, description:, constraints: [], tags: [], agent: Agentf::AgentRoles::ORCHESTRATOR, priority: 1)
122
147
  context = constraints.any? ? "Constraints: #{constraints.join('; ')}" : ""
123
148
 
124
149
  store_episode(
@@ -136,7 +161,8 @@ module Agentf
136
161
  )
137
162
  end
138
163
 
139
- def store_feature_intent(title:, description:, acceptance_criteria: [], non_goals: [], tags: [], agent: "ARCHITECT", related_task_id: nil)
164
+ def store_feature_intent(title:, description:, acceptance_criteria: [], non_goals: [], tags: [], agent: Agentf::AgentRoles::PLANNER,
165
+ related_task_id: nil)
140
166
  context_parts = []
141
167
  context_parts << "Acceptance: #{acceptance_criteria.join('; ')}" if acceptance_criteria.any?
142
168
  context_parts << "Non-goals: #{non_goals.join('; ')}" if non_goals.any?
@@ -157,7 +183,8 @@ module Agentf
157
183
  )
158
184
  end
159
185
 
160
- def store_incident(title:, description:, root_cause: "", resolution: "", tags: [], agent: "DEBUGGER", business_capability: nil)
186
+ def store_incident(title:, description:, root_cause: "", resolution: "", tags: [], agent: Agentf::AgentRoles::INCIDENT_RESPONDER,
187
+ business_capability: nil)
161
188
  store_episode(
162
189
  type: "incident",
163
190
  title: title,
@@ -174,7 +201,7 @@ module Agentf
174
201
  )
175
202
  end
176
203
 
177
- def store_playbook(title:, description:, steps: [], tags: [], agent: "ARCHITECT", feature_area: nil)
204
+ def store_playbook(title:, description:, steps: [], tags: [], agent: Agentf::AgentRoles::PLANNER, feature_area: nil)
178
205
  store_episode(
179
206
  type: "playbook",
180
207
  title: title,
@@ -226,7 +253,7 @@ module Agentf
226
253
  query = "@type:#{type} @project:{#{@project}}"
227
254
  search_episodic(query: query, limit: limit)
228
255
  else
229
- fetch_memories_without_search(limit: [limit * 4, 100].min).select { |mem| mem["type"] == type }.first(limit)
256
+ fetch_memories_without_search(limit: 100).select { |mem| mem["type"] == type }.first(limit)
230
257
  end
231
258
  end
232
259
 
@@ -285,6 +312,50 @@ module Agentf
285
312
  all_tags.to_a
286
313
  end
287
314
 
315
+ def store_edge(source_id:, target_id:, relation:, weight: 1.0, tags: [], agent: Agentf::AgentRoles::ORCHESTRATOR, metadata: {})
316
+ edge_id = "edge_#{SecureRandom.hex(5)}"
317
+ data = {
318
+ "id" => edge_id,
319
+ "source_id" => source_id,
320
+ "target_id" => target_id,
321
+ "relation" => relation,
322
+ "weight" => weight.to_f,
323
+ "tags" => tags,
324
+ "project" => @project,
325
+ "agent" => agent,
326
+ "metadata" => metadata,
327
+ "created_at" => Time.now.to_i
328
+ }
329
+
330
+ key = "edge:#{edge_id}"
331
+ payload = JSON.generate(data)
332
+
333
+ if @json_supported
334
+ begin
335
+ @client.call("JSON.SET", key, ".", payload)
336
+ rescue Redis::CommandError => e
337
+ if missing_json_module?(e)
338
+ @json_supported = false
339
+ @client.set(key, payload)
340
+ else
341
+ raise
342
+ end
343
+ end
344
+ else
345
+ @client.set(key, payload)
346
+ end
347
+
348
+ edge_id
349
+ end
350
+
351
+ def neighbors(node_id:, relation: nil, depth: 1, limit: 50)
352
+ traverse_edges(seed_ids: [node_id], relation_filters: relation ? [relation] : nil, depth: depth, limit: limit)
353
+ end
354
+
355
+ def subgraph(seed_ids:, depth: 2, relation_filters: nil, limit: 200)
356
+ traverse_edges(seed_ids: seed_ids, relation_filters: relation_filters, depth: depth, limit: limit)
357
+ end
358
+
288
359
  def close
289
360
  @client.close
290
361
  end
@@ -295,8 +366,9 @@ module Agentf
295
366
  return unless @search_supported
296
367
 
297
368
  create_episodic_index
369
+ create_edge_index
298
370
  rescue Redis::CommandError => e
299
- raise Redis::CommandError, "Failed to create episodic index: #{e.message}. Ensure Redis Stack with RediSearch is available." unless index_already_exists?(e)
371
+ raise Redis::CommandError, "Failed to create indexes: #{e.message}. Ensure Redis Stack with RediSearch is available." unless index_already_exists?(e)
300
372
  end
301
373
 
302
374
  def create_episodic_index
@@ -320,7 +392,31 @@ module Agentf
320
392
  "$.metadata.priority", "AS", "priority", "NUMERIC",
321
393
  "$.metadata.confidence", "AS", "confidence", "NUMERIC",
322
394
  "$.metadata.business_capability", "AS", "business_capability", "TAG",
323
- "$.metadata.feature_area", "AS", "feature_area", "TAG"
395
+ "$.metadata.feature_area", "AS", "feature_area", "TAG",
396
+ "$.metadata.agent_role", "AS", "agent_role", "TAG",
397
+ "$.metadata.division", "AS", "division", "TAG",
398
+ "$.metadata.specialty", "AS", "specialty", "TAG",
399
+ "$.entity_ids[*]", "AS", "entity_ids", "TAG",
400
+ "$.parent_episode_id", "AS", "parent_episode_id", "TEXT",
401
+ "$.causal_from", "AS", "causal_from", "TEXT"
402
+ )
403
+ end
404
+
405
+ def create_edge_index
406
+ @client.call(
407
+ "FT.CREATE", EDGE_INDEX,
408
+ "ON", "JSON",
409
+ "PREFIX", "1", "edge:",
410
+ "SCHEMA",
411
+ "$.id", "AS", "id", "TEXT",
412
+ "$.source_id", "AS", "source_id", "TAG",
413
+ "$.target_id", "AS", "target_id", "TAG",
414
+ "$.relation", "AS", "relation", "TAG",
415
+ "$.project", "AS", "project", "TAG",
416
+ "$.agent", "AS", "agent", "TAG",
417
+ "$.weight", "AS", "weight", "NUMERIC",
418
+ "$.created_at", "AS", "created_at", "NUMERIC",
419
+ "$.tags", "AS", "tags", "TAG"
324
420
  )
325
421
  end
326
422
 
@@ -432,15 +528,15 @@ module Agentf
432
528
 
433
529
  def context_profile(agent)
434
530
  case agent.to_s.upcase
435
- when "ARCHITECT"
531
+ when Agentf::AgentRoles::PLANNER
436
532
  { "preferred_types" => %w[business_intent feature_intent lesson playbook pitfall], "pitfall_penalty" => 0.1 }
437
- when "SPECIALIST"
533
+ when Agentf::AgentRoles::ENGINEER
438
534
  { "preferred_types" => %w[playbook success lesson pitfall], "pitfall_penalty" => 0.05 }
439
- when "TESTER"
535
+ when Agentf::AgentRoles::QA_TESTER
440
536
  { "preferred_types" => %w[lesson pitfall incident success], "pitfall_penalty" => 0.0 }
441
- when "DEBUGGER"
537
+ when Agentf::AgentRoles::INCIDENT_RESPONDER
442
538
  { "preferred_types" => %w[incident pitfall lesson], "pitfall_penalty" => 0.0 }
443
- when "SECURITY"
539
+ when Agentf::AgentRoles::SECURITY_REVIEWER
444
540
  { "preferred_types" => %w[pitfall lesson incident], "pitfall_penalty" => 0.0 }
445
541
  else
446
542
  { "preferred_types" => %w[lesson pitfall success business_intent feature_intent], "pitfall_penalty" => 0.05 }
@@ -461,7 +557,7 @@ module Agentf
461
557
  confidence = 1.0 if confidence > 1.0
462
558
 
463
559
  type_score = preferred_types.include?(type) ? 1.0 : 0.25
464
- agent_score = (memory["agent"] == agent || memory["agent"] == "WORKFLOW_ENGINE") ? 1.0 : 0.2
560
+ agent_score = (memory["agent"] == agent || memory["agent"] == Agentf::AgentRoles::ORCHESTRATOR) ? 1.0 : 0.2
465
561
  age_seconds = [now - memory.fetch("created_at", now).to_i, 0].max
466
562
  recency_score = 1.0 / (1.0 + (age_seconds / 86_400.0))
467
563
 
@@ -520,6 +616,235 @@ module Agentf
520
616
  def client_options
521
617
  { url: @redis_url }
522
618
  end
619
+
620
+ def persist_relationship_edges(episode_id:, related_task_id:, relationships:, metadata:, tags:, agent:)
621
+ if related_task_id && !related_task_id.to_s.strip.empty?
622
+ store_edge(source_id: episode_id, target_id: related_task_id, relation: "relates_to", tags: tags, agent: agent)
623
+ end
624
+
625
+ Array(relationships).each do |relation|
626
+ next unless relation.is_a?(Hash)
627
+
628
+ target = relation["to"] || relation[:to]
629
+ relation_type = relation["type"] || relation[:type] || "related"
630
+ next if target.to_s.strip.empty?
631
+
632
+ store_edge(
633
+ source_id: episode_id,
634
+ target_id: target,
635
+ relation: relation_type,
636
+ weight: (relation["weight"] || relation[:weight] || 1.0).to_f,
637
+ tags: tags,
638
+ agent: agent,
639
+ metadata: { "source_metadata" => extract_metadata_slice(metadata, %w[intent_kind agent_role division]) }
640
+ )
641
+ end
642
+
643
+ parent = metadata["parent_episode_id"].to_s
644
+ unless parent.empty?
645
+ store_edge(source_id: episode_id, target_id: parent, relation: "child_of", tags: tags, agent: agent)
646
+ end
647
+
648
+ causal_from = metadata["causal_from"].to_s
649
+ unless causal_from.empty?
650
+ store_edge(source_id: episode_id, target_id: causal_from, relation: "caused_by", tags: tags, agent: agent)
651
+ end
652
+ rescue StandardError
653
+ nil
654
+ end
655
+
656
+ def enrich_metadata(metadata:, agent:, type:, tags:, entity_ids:, relationships:, parent_episode_id:, causal_from:)
657
+ base = metadata.is_a?(Hash) ? metadata.dup : {}
658
+ base["agent_role"] = agent
659
+ base["division"] = infer_division(agent)
660
+ base["specialty"] = infer_specialty(agent)
661
+ base["capabilities"] = infer_capabilities(agent)
662
+ base["episode_type"] = type
663
+ base["tag_count"] = Array(tags).length
664
+ base["relationship_count"] = Array(relationships).length
665
+ base["entity_ids"] = Array(entity_ids)
666
+ base["parent_episode_id"] = parent_episode_id.to_s unless parent_episode_id.to_s.empty?
667
+ base["causal_from"] = causal_from.to_s unless causal_from.to_s.empty?
668
+ base
669
+ end
670
+
671
+ def infer_division(agent)
672
+ case agent
673
+ when Agentf::AgentRoles::PLANNER, Agentf::AgentRoles::ORCHESTRATOR, Agentf::AgentRoles::KNOWLEDGE_MANAGER
674
+ "strategy"
675
+ when Agentf::AgentRoles::ENGINEER, Agentf::AgentRoles::RESEARCHER, Agentf::AgentRoles::UI_ENGINEER
676
+ "engineering"
677
+ when Agentf::AgentRoles::QA_TESTER, Agentf::AgentRoles::REVIEWER, Agentf::AgentRoles::SECURITY_REVIEWER
678
+ "quality"
679
+ when Agentf::AgentRoles::INCIDENT_RESPONDER
680
+ "operations"
681
+ else
682
+ "general"
683
+ end
684
+ end
685
+
686
+ def infer_specialty(agent)
687
+ case agent
688
+ when Agentf::AgentRoles::PLANNER
689
+ "planning"
690
+ when Agentf::AgentRoles::ENGINEER
691
+ "implementation"
692
+ when Agentf::AgentRoles::RESEARCHER
693
+ "discovery"
694
+ when Agentf::AgentRoles::QA_TESTER
695
+ "testing"
696
+ when Agentf::AgentRoles::INCIDENT_RESPONDER
697
+ "debugging"
698
+ when Agentf::AgentRoles::UI_ENGINEER
699
+ "design-implementation"
700
+ when Agentf::AgentRoles::SECURITY_REVIEWER
701
+ "security"
702
+ when Agentf::AgentRoles::KNOWLEDGE_MANAGER
703
+ "documentation"
704
+ when Agentf::AgentRoles::REVIEWER
705
+ "review"
706
+ when Agentf::AgentRoles::ORCHESTRATOR
707
+ "orchestration"
708
+ else
709
+ "general"
710
+ end
711
+ end
712
+
713
+ def infer_capabilities(agent)
714
+ case agent
715
+ when Agentf::AgentRoles::PLANNER
716
+ %w[decompose prioritize plan]
717
+ when Agentf::AgentRoles::ENGINEER
718
+ %w[implement execute modify]
719
+ when Agentf::AgentRoles::RESEARCHER
720
+ %w[search map discover]
721
+ when Agentf::AgentRoles::QA_TESTER
722
+ %w[test validate report]
723
+ when Agentf::AgentRoles::INCIDENT_RESPONDER
724
+ %w[triage diagnose remediate]
725
+ when Agentf::AgentRoles::UI_ENGINEER
726
+ %w[design implement-ui validate-ui]
727
+ when Agentf::AgentRoles::SECURITY_REVIEWER
728
+ %w[scan assess harden]
729
+ when Agentf::AgentRoles::KNOWLEDGE_MANAGER
730
+ %w[summarize document synthesize]
731
+ when Agentf::AgentRoles::REVIEWER
732
+ %w[review approve reject]
733
+ else
734
+ %w[coordinate]
735
+ end
736
+ end
737
+
738
+ def traverse_edges(seed_ids:, relation_filters:, depth:, limit:)
739
+ current = Array(seed_ids).compact.map(&:to_s).reject(&:empty?).uniq
740
+ visited_nodes = Set.new(current)
741
+ visited_edges = Set.new
742
+ layers = []
743
+ edges = []
744
+
745
+ depth.to_i.times do |hop|
746
+ break if current.empty?
747
+
748
+ next_nodes = []
749
+ layer_edges = []
750
+ current.each do |node_id|
751
+ fetch_edges_for(node_id: node_id, relation_filters: relation_filters, limit: limit).each do |edge|
752
+ edge_id = edge["id"].to_s
753
+ next if edge_id.empty? || visited_edges.include?(edge_id)
754
+
755
+ visited_edges << edge_id
756
+ layer_edges << edge
757
+ target = edge["target_id"].to_s
758
+ next if target.empty? || visited_nodes.include?(target)
759
+
760
+ visited_nodes << target
761
+ next_nodes << target
762
+ end
763
+ end
764
+ layers << { "depth" => hop + 1, "count" => layer_edges.length }
765
+ edges.concat(layer_edges)
766
+ current = next_nodes.uniq
767
+ break if edges.length >= limit
768
+ end
769
+
770
+ {
771
+ "seed_ids" => seed_ids,
772
+ "nodes" => visited_nodes.to_a,
773
+ "edges" => edges.first(limit),
774
+ "layers" => layers,
775
+ "count" => [edges.length, limit].min
776
+ }
777
+ end
778
+
779
+ def fetch_edges_for(node_id:, relation_filters:, limit:)
780
+ if @search_supported
781
+ query = ["@source_id:{#{escape_tag(node_id)}}", "@project:{#{escape_tag(@project)}}"]
782
+ if relation_filters && relation_filters.any?
783
+ relations = relation_filters.map { |item| escape_tag(item.to_s) }.join("|")
784
+ query << "@relation:{#{relations}}"
785
+ end
786
+ search_json_index(index: EDGE_INDEX, query: query.join(" "), limit: limit)
787
+ else
788
+ fetch_edges_without_search(node_id: node_id, relation_filters: relation_filters, limit: limit)
789
+ end
790
+ end
791
+
792
+ def search_json_index(index:, query:, limit:)
793
+ results = @client.call(
794
+ "FT.SEARCH", index,
795
+ query,
796
+ "SORTBY", "created_at", "DESC",
797
+ "LIMIT", "0", limit.to_s
798
+ )
799
+ return [] unless results && results[0] > 0
800
+
801
+ records = []
802
+ (2...results.length).step(2) do |i|
803
+ item = results[i]
804
+ next unless item.is_a?(Array)
805
+
806
+ item.each_with_index do |part, j|
807
+ next unless part == "$" && j + 1 < item.length
808
+
809
+ begin
810
+ records << JSON.parse(item[j + 1])
811
+ rescue JSON::ParserError
812
+ nil
813
+ end
814
+ end
815
+ end
816
+ records
817
+ end
818
+
819
+ def fetch_edges_without_search(node_id:, relation_filters:, limit:)
820
+ edges = []
821
+ cursor = "0"
822
+ loop do
823
+ cursor, batch = @client.scan(cursor, match: "edge:*", count: 100)
824
+ batch.each do |key|
825
+ edge = load_episode(key)
826
+ next unless edge.is_a?(Hash)
827
+ next unless edge["source_id"].to_s == node_id.to_s
828
+ next unless edge["project"].to_s == @project.to_s
829
+ next if relation_filters && relation_filters.any? && !relation_filters.include?(edge["relation"])
830
+
831
+ edges << edge
832
+ return edges.first(limit) if edges.length >= limit
833
+ end
834
+ break if cursor == "0"
835
+ end
836
+ edges
837
+ end
838
+
839
+ def escape_tag(value)
840
+ value.to_s.gsub(/[\-{}\[\]|\\]/) { |m| "\\#{m}" }
841
+ end
842
+
843
+ def extract_metadata_slice(metadata, keys)
844
+ keys.each_with_object({}) do |key, acc|
845
+ acc[key] = metadata[key] if metadata.key?(key)
846
+ end
847
+ end
523
848
  end
524
849
 
525
850
  # Convenience method
data/lib/agentf/packs.rb CHANGED
@@ -14,11 +14,11 @@ module Agentf
14
14
  "description" => "Thin models/controllers with services, queries, presenters, and policy reviews.",
15
15
  "keywords" => %w[rails activerecord rspec pundit viewcomponent hotwire turbo stimulus],
16
16
  "workflow_templates" => {
17
- "feature" => %w[ARCHITECT EXPLORER SPECIALIST TESTER SECURITY REVIEWER DOCUMENTER],
18
- "bugfix" => %w[ARCHITECT DEBUGGER SPECIALIST TESTER SECURITY REVIEWER],
19
- "refactor" => %w[ARCHITECT EXPLORER SPECIALIST TESTER REVIEWER],
20
- "quick_fix" => %w[SPECIALIST TESTER REVIEWER],
21
- "exploration" => %w[EXPLORER]
17
+ "feature" => %w[PLANNER RESEARCHER ENGINEER QA_TESTER SECURITY_REVIEWER REVIEWER KNOWLEDGE_MANAGER],
18
+ "bugfix" => %w[PLANNER INCIDENT_RESPONDER ENGINEER QA_TESTER SECURITY_REVIEWER REVIEWER],
19
+ "refactor" => %w[PLANNER RESEARCHER ENGINEER QA_TESTER REVIEWER],
20
+ "quick_fix" => %w[ENGINEER QA_TESTER REVIEWER],
21
+ "exploration" => %w[RESEARCHER]
22
22
  }
23
23
  },
24
24
  "rails_37signals" => {
@@ -26,11 +26,11 @@ module Agentf
26
26
  "description" => "Resource-centric workflows favoring concerns, CRUD and model-rich patterns.",
27
27
  "keywords" => %w[rails concern crud closure model minitest hotwire],
28
28
  "workflow_templates" => {
29
- "feature" => %w[ARCHITECT EXPLORER SPECIALIST TESTER REVIEWER DOCUMENTER],
30
- "bugfix" => %w[ARCHITECT DEBUGGER SPECIALIST TESTER REVIEWER],
31
- "refactor" => %w[ARCHITECT SPECIALIST TESTER REVIEWER],
32
- "quick_fix" => %w[SPECIALIST REVIEWER],
33
- "exploration" => %w[EXPLORER]
29
+ "feature" => %w[PLANNER RESEARCHER ENGINEER QA_TESTER REVIEWER KNOWLEDGE_MANAGER],
30
+ "bugfix" => %w[PLANNER INCIDENT_RESPONDER ENGINEER QA_TESTER REVIEWER],
31
+ "refactor" => %w[PLANNER ENGINEER QA_TESTER REVIEWER],
32
+ "quick_fix" => %w[ENGINEER REVIEWER],
33
+ "exploration" => %w[RESEARCHER]
34
34
  }
35
35
  },
36
36
  "rails_feature_spec" => {
@@ -38,11 +38,11 @@ module Agentf
38
38
  "description" => "Feature-spec-first orchestration with planning and review emphasis.",
39
39
  "keywords" => %w[rails feature specification acceptance criteria],
40
40
  "workflow_templates" => {
41
- "feature" => %w[ARCHITECT EXPLORER DESIGNER SPECIALIST TESTER REVIEWER DOCUMENTER],
42
- "bugfix" => %w[ARCHITECT DEBUGGER SPECIALIST TESTER REVIEWER],
43
- "refactor" => %w[ARCHITECT EXPLORER SPECIALIST TESTER REVIEWER],
44
- "quick_fix" => %w[SPECIALIST REVIEWER],
45
- "exploration" => %w[EXPLORER]
41
+ "feature" => %w[PLANNER RESEARCHER UI_ENGINEER ENGINEER QA_TESTER REVIEWER KNOWLEDGE_MANAGER],
42
+ "bugfix" => %w[PLANNER INCIDENT_RESPONDER ENGINEER QA_TESTER REVIEWER],
43
+ "refactor" => %w[PLANNER RESEARCHER ENGINEER QA_TESTER REVIEWER],
44
+ "quick_fix" => %w[ENGINEER REVIEWER],
45
+ "exploration" => %w[RESEARCHER]
46
46
  }
47
47
  }
48
48
  }.freeze
@@ -61,18 +61,18 @@ module Agentf
61
61
  return { "error" => "Agent #{agent_name} not found" } unless agent
62
62
 
63
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"
64
+ when Agentf::AgentRoles::PLANNER
65
+ agent.plan_task(task)
66
+ when Agentf::AgentRoles::RESEARCHER
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 Agentf::AgentRoles::QA_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
76
 
77
77
  if tdd_phase == "red"
78
78
  failure_signature = "expected-failure:#{File.basename(source_file)}:#{Time.now.to_i}"
@@ -92,32 +92,32 @@ module Agentf
92
92
  response["failure_signature"] = context["tdd_failure_signature"]
93
93
  response
94
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"])
95
+ when Agentf::AgentRoles::INCIDENT_RESPONDER
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
99
  response["analysis"] = {
100
100
  "error_type" => analysis.error_type,
101
101
  "root_cause" => analysis.possible_causes,
102
102
  "suggested_fix" => analysis.suggested_fix
103
103
  }
104
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")
105
+ when Agentf::AgentRoles::UI_ENGINEER
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 Agentf::AgentRoles::ENGINEER
112
+ subtask = context["current_subtask"] || { "description" => task }
113
+ agent.execute(subtask)
114
+ when Agentf::AgentRoles::SECURITY_REVIEWER
115
+ agent.assess(task: task, context: context)
116
+ when Agentf::AgentRoles::REVIEWER
117
+ last_result = context["execution"] || {}
118
+ agent.review(last_result)
119
+ when Agentf::AgentRoles::KNOWLEDGE_MANAGER
120
+ agent.sync_docs("project")
121
121
  else
122
122
  { "status" => "not_implemented" }
123
123
  end
@@ -133,11 +133,11 @@ module Agentf
133
133
  class OpenCode < Base
134
134
  def workflow_templates
135
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]
136
+ "feature" => %w[PLANNER RESEARCHER UI_ENGINEER ENGINEER QA_TESTER SECURITY_REVIEWER REVIEWER KNOWLEDGE_MANAGER],
137
+ "bugfix" => %w[PLANNER INCIDENT_RESPONDER ENGINEER QA_TESTER SECURITY_REVIEWER REVIEWER],
138
+ "quick_fix" => %w[ENGINEER SECURITY_REVIEWER REVIEWER],
139
+ "exploration" => %w[RESEARCHER],
140
+ "refactor" => %w[PLANNER RESEARCHER ENGINEER QA_TESTER SECURITY_REVIEWER REVIEWER]
141
141
  }
142
142
  end
143
143
  end
@@ -145,11 +145,11 @@ module Agentf
145
145
  class Copilot < Base
146
146
  def workflow_templates
147
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]
148
+ "feature" => %w[PLANNER ENGINEER QA_TESTER SECURITY_REVIEWER REVIEWER KNOWLEDGE_MANAGER],
149
+ "bugfix" => %w[INCIDENT_RESPONDER ENGINEER QA_TESTER SECURITY_REVIEWER REVIEWER],
150
+ "quick_fix" => %w[ENGINEER REVIEWER],
151
+ "exploration" => %w[RESEARCHER],
152
+ "refactor" => %w[PLANNER ENGINEER QA_TESTER REVIEWER]
153
153
  }
154
154
  end
155
155
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Agentf
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end