aia 1.1.0 → 2.0.0.0.pre.alpha

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 (170) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +5 -1
  3. data/.loki +231 -0
  4. data/.quality/flay_baseline.txt +1 -0
  5. data/.quality/flog_baseline.txt +29 -0
  6. data/.quality/reek_baseline.txt +80 -0
  7. data/.rubocop.yml +116 -0
  8. data/.version +1 -1
  9. data/CHANGELOG.md +266 -42
  10. data/IMPLEMENTATION_PLAN.md +506 -0
  11. data/README.md +266 -238
  12. data/Rakefile +118 -5
  13. data/architecture_review.md +314 -0
  14. data/bin/aia +16 -0
  15. data/docs/AGENTS.md +40 -0
  16. data/docs/advanced-prompting.md +67 -3
  17. data/docs/cli-reference.md +312 -56
  18. data/docs/configuration.md +130 -19
  19. data/docs/contributing.md +56 -2
  20. data/docs/directives-reference.md +593 -78
  21. data/docs/faq.md +85 -3
  22. data/docs/guides/available-models.md +1 -1
  23. data/docs/guides/basic-usage.md +6 -6
  24. data/docs/guides/chat.md +40 -16
  25. data/docs/guides/crew.md +239 -0
  26. data/docs/guides/executable-prompts.md +1 -1
  27. data/docs/guides/index.md +1 -0
  28. data/docs/guides/models.md +15 -0
  29. data/docs/index.md +29 -2
  30. data/docs/installation.md +44 -17
  31. data/docs/mcp-integration.md +40 -0
  32. data/docs/prompt_management.md +85 -86
  33. data/docs/security.md +47 -0
  34. data/docs/special_projects_guide.md +386 -0
  35. data/docs/tools-and-mcp-examples.md +23 -0
  36. data/docs/workflows-and-pipelines.md +84 -7
  37. data/examples/.gitignore +1 -0
  38. data/examples/00_setup_aia.sh +27 -44
  39. data/examples/11_multi_model.sh +4 -14
  40. data/examples/12_token_usage.sh +3 -12
  41. data/examples/18_tools.sh +10 -2
  42. data/examples/22_chat_mode.sh +0 -10
  43. data/examples/23_verify.sh +139 -0
  44. data/examples/24_decompose.sh +139 -0
  45. data/examples/25_spawn.sh +139 -0
  46. data/examples/26_debate.sh +97 -0
  47. data/examples/27_mention_routing.sh +157 -0
  48. data/examples/28_model_switching.sh +106 -0
  49. data/examples/29_agent_harness.sh +177 -0
  50. data/examples/README.md +65 -0
  51. data/examples/advanced_multi_robot_capabilities_without_examples.md +106 -0
  52. data/examples/aia_config.yml +1 -1
  53. data/examples/aia_config_orchestrator.yml +45 -0
  54. data/examples/common.sh +19 -0
  55. data/examples/context/tech_stack.md +2 -2
  56. data/examples/prompts_dir/project_summary +2 -2
  57. data/examples/prompts_dir/roles/orchestrator.md +21 -0
  58. data/examples/requirements/sinatra_taskflow_app.md +139 -0
  59. data/examples/rules/01_classify_ruby.rb +16 -0
  60. data/examples/rules/02_prefer_claude_for_code.rb +19 -0
  61. data/examples/rules/03_gate_prompt_length.rb +19 -0
  62. data/examples/rules/04_tool_selection.rb +41 -0
  63. data/examples/rules/README.md +30 -0
  64. data/examples/run_all.sh +48 -15
  65. data/examples/tools/word_count_tool.rb +1 -1
  66. data/lib/AGENTS.md +57 -0
  67. data/lib/aia/chat_loop.rb +306 -159
  68. data/lib/aia/config/cli_parser.rb +174 -111
  69. data/lib/aia/config/defaults.yml +62 -33
  70. data/lib/aia/config/mcp_parser.rb +39 -46
  71. data/lib/aia/config/model_spec.rb +34 -2
  72. data/lib/aia/config/validator.rb +121 -138
  73. data/lib/aia/config.rb +110 -145
  74. data/lib/aia/content_extractor.rb +153 -0
  75. data/lib/aia/cost_calculator.rb +38 -0
  76. data/lib/aia/crew.rb +164 -0
  77. data/lib/aia/debate_handler.rb +166 -0
  78. data/lib/aia/delegate_handler.rb +112 -0
  79. data/lib/aia/directive.rb +33 -18
  80. data/lib/aia/directive_processor.rb +16 -7
  81. data/lib/aia/directives/configuration_directives.rb +160 -20
  82. data/lib/aia/directives/context_directives.rb +38 -26
  83. data/lib/aia/directives/execution_directives.rb +136 -4
  84. data/lib/aia/directives/model_directives.rb +76 -34
  85. data/lib/aia/directives/trakflow_directives.rb +44 -0
  86. data/lib/aia/directives/utility_directives.rb +203 -6
  87. data/lib/aia/directives/web_and_file_directives.rb +96 -60
  88. data/lib/aia/errors.rb +15 -0
  89. data/lib/aia/fact_asserter.rb +27 -0
  90. data/lib/aia/fzf.rb +9 -31
  91. data/lib/aia/handler_context.rb +17 -0
  92. data/lib/aia/handler_protocol.rb +19 -0
  93. data/lib/aia/history_transfer.rb +55 -0
  94. data/lib/aia/input_collector.rb +3 -3
  95. data/lib/aia/layered_orchestrator.rb +448 -0
  96. data/lib/aia/logger.rb +24 -4
  97. data/lib/aia/mcp_config_normalizer.rb +35 -0
  98. data/lib/aia/mcp_connection_manager.rb +305 -0
  99. data/lib/aia/mcp_discovery.rb +44 -0
  100. data/lib/aia/mcp_grouper.rb +33 -0
  101. data/lib/aia/mcp_utility.rb +57 -0
  102. data/lib/aia/mention_router.rb +260 -0
  103. data/lib/aia/model_alias_registry.rb +97 -0
  104. data/lib/aia/model_switch_handler.rb +100 -0
  105. data/lib/aia/network_builder.rb +155 -0
  106. data/lib/aia/network_memory_manager.rb +55 -0
  107. data/lib/aia/patches/ruby_llm_streaming_error.rb +43 -0
  108. data/lib/aia/patches/ruby_llm_tool_error.rb +96 -0
  109. data/lib/aia/pipeline_orchestrator.rb +262 -0
  110. data/lib/aia/plugin_loader.rb +170 -0
  111. data/lib/aia/plugin_monitor.rb +208 -0
  112. data/lib/aia/prompt_decomposer.rb +157 -0
  113. data/lib/aia/prompt_handler.rb +19 -39
  114. data/lib/aia/robot_builder.rb +51 -0
  115. data/lib/aia/robot_factory.rb +334 -0
  116. data/lib/aia/robot_namer.rb +116 -0
  117. data/lib/aia/session.rb +83 -17
  118. data/lib/aia/session_tracker.rb +209 -0
  119. data/lib/aia/similarity_scorer.rb +39 -0
  120. data/lib/aia/skill_utils.rb +105 -1
  121. data/lib/aia/spawn_handler.rb +129 -0
  122. data/lib/aia/spawn_spec_parser.rb +65 -0
  123. data/lib/aia/special_mode_handler.rb +302 -0
  124. data/lib/aia/startup_coordinator.rb +150 -0
  125. data/lib/aia/streaming_runner.rb +169 -0
  126. data/lib/aia/system_prompt_assembler.rb +88 -0
  127. data/lib/aia/task_coordinator.rb +202 -0
  128. data/lib/aia/task_decomposer.rb +57 -0
  129. data/lib/aia/task_executor.rb +51 -0
  130. data/lib/aia/tfidf_math.rb +27 -0
  131. data/lib/aia/tool_filter/tfidf.rb +116 -0
  132. data/lib/aia/tool_filter/wordnet_expander.rb +127 -0
  133. data/lib/aia/tool_filter.rb +82 -0
  134. data/lib/aia/tool_filter_registry.rb +30 -0
  135. data/lib/aia/tool_filter_strategy.rb +143 -0
  136. data/lib/aia/tool_loader.rb +210 -0
  137. data/lib/aia/tool_utility.rb +30 -0
  138. data/lib/aia/tools/delegate_to_foreman_tool.rb +70 -0
  139. data/lib/aia/tools/recruit_robot_tool.rb +60 -0
  140. data/lib/aia/tools/reskill_robot_tool.rb +44 -0
  141. data/lib/aia/tools/task_board_tool.rb +114 -0
  142. data/lib/aia/trakflow_bridge.rb +173 -0
  143. data/lib/aia/turn_state.rb +94 -0
  144. data/lib/aia/ui_presenter.rb +166 -198
  145. data/lib/aia/utility.rb +134 -87
  146. data/lib/aia/{history_manager.rb → variable_input_collector.rb} +8 -9
  147. data/lib/aia/verification_network.rb +58 -0
  148. data/lib/aia.rb +108 -63
  149. data/mkdocs.yml +1 -0
  150. metadata +179 -56
  151. data/justfile +0 -215
  152. data/lib/aia/adapter/chat_execution.rb +0 -242
  153. data/lib/aia/adapter/error_handler.rb +0 -68
  154. data/lib/aia/adapter/gem_activator.rb +0 -57
  155. data/lib/aia/adapter/mcp_connector.rb +0 -274
  156. data/lib/aia/adapter/modality_handlers.rb +0 -167
  157. data/lib/aia/adapter/model_registry.rb +0 -81
  158. data/lib/aia/adapter/multi_model_chat.rb +0 -218
  159. data/lib/aia/adapter/provider_configurator.rb +0 -59
  160. data/lib/aia/adapter/tool_filter.rb +0 -85
  161. data/lib/aia/adapter/tool_loader.rb +0 -90
  162. data/lib/aia/chat_processor_service.rb +0 -164
  163. data/lib/aia/prompt_pipeline.rb +0 -183
  164. data/lib/aia/ruby_llm_adapter.rb +0 -95
  165. data/lib/extensions/openstruct_merge.rb +0 -48
  166. data/lib/extensions/ruby_llm/.irbrc +0 -56
  167. data/lib/extensions/ruby_llm/modalities.rb +0 -36
  168. data/lib/extensions/ruby_llm/provider_fix.rb +0 -79
  169. data/lib/refinements/string.rb +0 -16
  170. data/main.just +0 -76
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/aia/tools/task_board_tool.rb
4
+ #
5
+ # RubyLLM::Tool that lets robots manage a shared TrakFlow task board.
6
+ # Robots can create tasks for each other, check what's ready, claim
7
+ # work, and report completion — all through standard tool-use protocol.
8
+
9
+ require "json"
10
+
11
+ module AIA
12
+ class TaskBoardTool < RubyLLM::Tool
13
+ description "Manage a shared task board. Create tasks for other robots, " \
14
+ "check what's ready, claim tasks, and report completion."
15
+
16
+ param :action, type: :string, required: true,
17
+ desc: "One of: create, plan, ready, claim, complete, block, status"
18
+ param :title, type: :string,
19
+ desc: "Task title (for create/plan actions)"
20
+ param :assignee, type: :string,
21
+ desc: "Robot name to assign to (for create/claim/ready)"
22
+ param :labels, type: :string,
23
+ desc: "Comma-separated labels (for create)"
24
+ param :blocked_by, type: :string,
25
+ desc: "Comma-separated blocker task IDs (for create)"
26
+ param :steps, type: :string,
27
+ desc: "JSON array of step objects [{title:, assignee:}] (for plan)"
28
+ param :task_id, type: :string,
29
+ desc: "Task ID (for claim/complete/block)"
30
+ param :result, type: :string,
31
+ desc: "Result summary (for complete) or reason (for block)"
32
+
33
+ # Never auto-discovered onto primary robots. Only Foreman
34
+ # (built by DelegateToForemanTool) receives this tool explicitly.
35
+ def available?
36
+ false
37
+ end
38
+
39
+ def execute(action:, **params)
40
+ coordinator = AIA.task_coordinator
41
+ return "Task board unavailable (TrakFlow not initialized)" unless coordinator&.available?
42
+
43
+ robot_name = params[:_robot_name] || "unknown"
44
+
45
+ case action
46
+ when "create" then execute_create(coordinator, robot_name, params)
47
+ when "plan" then execute_plan(coordinator, robot_name, params)
48
+ when "ready" then execute_ready(coordinator, params)
49
+ when "claim" then execute_claim(coordinator, robot_name, params)
50
+ when "complete" then execute_complete(coordinator, robot_name, params)
51
+ when "block" then execute_block(coordinator, robot_name, params)
52
+ when "status" then coordinator.status_summary || "No tasks"
53
+ else
54
+ "Unknown action: #{action}. Use: create, plan, ready, claim, complete, block, status"
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def execute_create(coordinator, robot_name, params)
61
+ task = coordinator.create_task(
62
+ params[:title],
63
+ assignee: params[:assignee],
64
+ labels: parse_csv(params[:labels]),
65
+ blocked_by: parse_csv(params[:blocked_by]),
66
+ creator: robot_name
67
+ )
68
+ task ? "Created [#{task.id}] '#{task.title}'" : "Failed to create task"
69
+ end
70
+
71
+ def execute_plan(coordinator, robot_name, params)
72
+ steps = JSON.parse(params[:steps] || "[]", symbolize_names: true)
73
+ result = coordinator.create_plan(
74
+ params[:title], steps: steps, creator: robot_name
75
+ )
76
+ result ? "Plan created with #{result[:steps].size} steps" : "Failed to create plan"
77
+ rescue JSON::ParserError => e
78
+ "Invalid steps JSON: #{e.message}"
79
+ end
80
+
81
+ def execute_ready(coordinator, params)
82
+ tasks = coordinator.ready_tasks(robot_name: params[:assignee])
83
+ return "No ready tasks" if tasks.empty?
84
+ tasks.map { |t| "[#{t.id}] #{t.title} (#{t.assignee || 'unassigned'})" }.join("\n")
85
+ end
86
+
87
+ def execute_claim(coordinator, robot_name, params)
88
+ coordinator.claim_task(params[:task_id], robot_name)
89
+ "Claimed task #{params[:task_id]}"
90
+ end
91
+
92
+ def execute_complete(coordinator, robot_name, params)
93
+ coordinator.complete_task(
94
+ params[:task_id],
95
+ result: params[:result] || "Done",
96
+ robot_name: robot_name
97
+ )
98
+ "Completed task #{params[:task_id]}"
99
+ end
100
+
101
+ def execute_block(coordinator, robot_name, params)
102
+ coordinator.block_task(
103
+ params[:task_id],
104
+ reason: params[:result] || "Blocked",
105
+ robot_name: robot_name
106
+ )
107
+ "Blocked task #{params[:task_id]}"
108
+ end
109
+
110
+ def parse_csv(value)
111
+ (value || "").split(",").map(&:strip).reject(&:empty?)
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/aia/trakflow_bridge.rb
4
+ #
5
+ # Bridge between AIA and TrakFlow task tracking.
6
+ # Uses the trak_flow gem directly as a Ruby library —
7
+ # no MCP server or LLM intermediation needed.
8
+
9
+ require "trak_flow"
10
+
11
+ module AIA
12
+ class TrakFlowBridge
13
+ def initialize
14
+ @db = connect_database
15
+ end
16
+
17
+ # Whether a TrakFlow project is initialized and the database is accessible.
18
+ #
19
+ # @return [Boolean]
20
+ def available?
21
+ !@db.nil?
22
+ end
23
+
24
+ # Create a TrakFlow plan from a pipeline of prompt IDs.
25
+ # Each step blocks the next, enforcing sequential execution.
26
+ #
27
+ # @param pipeline [Array<String>] prompt IDs in pipeline order
28
+ # @return [String, nil] summary of created plan
29
+ def create_plan_from_pipeline(pipeline)
30
+ return nil unless available?
31
+
32
+ plan_title = "Pipeline: #{pipeline.join(' → ')}"
33
+ plan = TrakFlow::Models::Task.new(title: plan_title, plan: true, type: "task")
34
+ plan = @db.create_task(plan)
35
+
36
+ prev_step = nil
37
+ pipeline.each_with_index do |prompt_id, i|
38
+ step = @db.create_child_task(plan.id, {
39
+ title: "Step #{i + 1}: #{prompt_id}",
40
+ type: "task"
41
+ })
42
+
43
+ if prev_step
44
+ @db.add_dependency(TrakFlow::Models::Dependency.new(
45
+ source_id: prev_step.id,
46
+ target_id: step.id,
47
+ type: "blocks"
48
+ ))
49
+ end
50
+
51
+ prev_step = step
52
+ end
53
+
54
+ "Plan '#{plan_title}' created with #{pipeline.size} steps."
55
+ rescue StandardError => e
56
+ $stderr.puts "Warning: TrakFlow plan creation failed: #{e.message}"
57
+ nil
58
+ end
59
+
60
+ # Update a task's status in TrakFlow.
61
+ #
62
+ # @param step_name [String] task title to find
63
+ # @param status [Symbol] :started, :completed, or :failed
64
+ # @param reason [String, nil] reason for failure
65
+ def update_step_status(step_name, status, reason: nil)
66
+ return unless available?
67
+
68
+ task = find_task_by_title(step_name)
69
+ return unless task
70
+
71
+ case status
72
+ when :started
73
+ task.status = "in_progress"
74
+ @db.update_task(task)
75
+ when :completed
76
+ task.close!(reason: reason || "Completed")
77
+ @db.update_task(task)
78
+ when :failed
79
+ task.status = "blocked"
80
+ task.append_trace("blocked", reason) if reason
81
+ @db.update_task(task)
82
+ end
83
+ rescue StandardError => e
84
+ $stderr.puts "Warning: TrakFlow status update failed: #{e.message}"
85
+ end
86
+
87
+ # List ready tasks (tasks with no open blockers).
88
+ #
89
+ # @return [String, nil] formatted list of ready tasks
90
+ def check_ready_tasks
91
+ return nil unless available?
92
+
93
+ tasks = @db.ready_tasks
94
+ return nil if tasks.empty?
95
+
96
+ lines = tasks.map { |t| " - [#{t.id}] #{t.title} (#{t.status})" }
97
+ "Ready tasks (#{tasks.size}):\n#{lines.join("\n")}"
98
+ rescue StandardError => e
99
+ $stderr.puts "Warning: TrakFlow ready tasks query failed: #{e.message}"
100
+ nil
101
+ end
102
+
103
+ # Get a summary of the current TrakFlow project state.
104
+ #
105
+ # @return [String, nil] project summary
106
+ def project_summary
107
+ return nil unless available?
108
+
109
+ all = @db.list_tasks({})
110
+ by_status = all.group_by(&:status)
111
+
112
+ lines = ["TrakFlow Project Summary (#{all.size} tasks):"]
113
+ by_status.sort.each do |status, tasks|
114
+ lines << " #{status}: #{tasks.size}"
115
+ end
116
+
117
+ ready = @db.ready_tasks
118
+ lines << " ready (unblocked): #{ready.size}" unless ready.empty?
119
+
120
+ lines.join("\n")
121
+ rescue StandardError => e
122
+ $stderr.puts "Warning: TrakFlow summary failed: #{e.message}"
123
+ nil
124
+ end
125
+
126
+ # Create a task in TrakFlow.
127
+ #
128
+ # @param title [String] task title
129
+ # @param description [String, nil] task description
130
+ # @param labels [Array<String>] labels to attach
131
+ # @return [String, nil] confirmation message
132
+ def create_task(title, description: nil, labels: [])
133
+ return nil unless available?
134
+
135
+ task = TrakFlow::Models::Task.new(
136
+ title: title,
137
+ description: description,
138
+ type: "task"
139
+ )
140
+ task = @db.create_task(task)
141
+
142
+ labels.each do |label_name|
143
+ @db.add_label(TrakFlow::Models::Label.new(task_id: task.id, name: label_name))
144
+ end
145
+
146
+ "Task created: [#{task.id}] #{title}"
147
+ rescue StandardError => e
148
+ $stderr.puts "Warning: TrakFlow task creation failed: #{e.message}"
149
+ nil
150
+ end
151
+
152
+ attr_reader :db
153
+
154
+ private
155
+
156
+ # Connect to the TrakFlow database if a project is initialized.
157
+ def connect_database
158
+ return nil unless TrakFlow.initialized?
159
+
160
+ db = TrakFlow::Storage::Database.new(TrakFlow.database_path)
161
+ db.connect
162
+ db
163
+ rescue StandardError
164
+ nil
165
+ end
166
+
167
+ # Find a task by its title (partial match).
168
+ def find_task_by_title(title)
169
+ tasks = @db.list_tasks(title: title)
170
+ tasks.first
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/aia/turn_state.rb
4
+ #
5
+ # Request-scoped state object for inter-component signaling.
6
+ # Replaces ad-hoc instance variable flags on AIA.config.
7
+ #
8
+ # Only one special mode may be requested per turn (mutual exclusion).
9
+ # Use TurnState#request(mode, **opts) to enqueue a mode — this clears
10
+ # all other force_* flags before setting the new one. Assigning the
11
+ # attr_accessors directly bypasses mutual exclusion (for backward compat
12
+ # and direct test assertions).
13
+
14
+ module AIA
15
+ class TurnState
16
+ # Exclusive mode flags — only one may be active at a time via #request.
17
+ EXCLUSIVE_MODES = %i[
18
+ verify decompose concurrent_mcp debate delegate spawn orchestrate
19
+ ].freeze
20
+
21
+ attr_accessor :force_verify, :force_decompose, :force_concurrent_mcp,
22
+ :force_debate, :force_delegate, :force_spawn, :spawn_type,
23
+ :spawn_spec, :force_orchestrate, :active_mcp_servers
24
+
25
+ def initialize
26
+ clear!
27
+ end
28
+
29
+ # Request a special mode for this turn.
30
+ # Clears all other exclusive force_* flags before activating the new mode,
31
+ # enforcing mutual exclusion at the point of enqueue.
32
+ #
33
+ # @param mode [Symbol] one of EXCLUSIVE_MODES
34
+ # @param type [String, nil] specialist type (only relevant for :spawn)
35
+ # @raise [ArgumentError] if mode is not one of EXCLUSIVE_MODES
36
+ def request(mode, type: nil)
37
+ unless EXCLUSIVE_MODES.include?(mode)
38
+ raise ArgumentError, "Unknown TurnState mode: #{mode.inspect}. Valid: #{EXCLUSIVE_MODES.join(', ')}"
39
+ end
40
+
41
+ clear_exclusive_flags!
42
+
43
+ case mode
44
+ when :verify then @force_verify = true
45
+ when :decompose then @force_decompose = true
46
+ when :concurrent_mcp then @force_concurrent_mcp = true
47
+ when :debate then @force_debate = true
48
+ when :delegate then @force_delegate = true
49
+ when :spawn then @force_spawn = true
50
+ @spawn_type = type
51
+ when :orchestrate then @force_orchestrate = true
52
+ end
53
+ end
54
+
55
+ def clear!
56
+ @force_verify = false
57
+ @force_decompose = false
58
+ @force_concurrent_mcp = false
59
+ @force_debate = false
60
+ @force_delegate = false
61
+ @force_spawn = false
62
+ @spawn_type = nil
63
+ @spawn_spec = nil
64
+ @force_orchestrate = false
65
+ @active_mcp_servers = nil
66
+ end
67
+
68
+ # Returns which exclusive mode is currently active, or nil if none.
69
+ def active_mode
70
+ return :verify if @force_verify
71
+ return :decompose if @force_decompose
72
+ return :concurrent_mcp if @force_concurrent_mcp
73
+ return :debate if @force_debate
74
+ return :delegate if @force_delegate
75
+ return :spawn if @force_spawn
76
+ return :orchestrate if @force_orchestrate
77
+ nil
78
+ end
79
+
80
+ private
81
+
82
+ def clear_exclusive_flags!
83
+ @force_verify = false
84
+ @force_decompose = false
85
+ @force_concurrent_mcp = false
86
+ @force_debate = false
87
+ @force_delegate = false
88
+ @force_spawn = false
89
+ @spawn_type = nil
90
+ @spawn_spec = nil
91
+ @force_orchestrate = false
92
+ end
93
+ end
94
+ end