ollama_agent 0.1.0 → 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 +4 -4
- data/.cursor/skills/ruby-code-review-levels/SKILL.md +115 -0
- data/.cursor/skills/self-improvement-sandbox-safety/SKILL.md +65 -0
- data/.env.example +25 -0
- data/CHANGELOG.md +40 -0
- data/README.md +135 -4
- data/docs/ARCHITECTURE.md +42 -0
- data/docs/PERFORMANCE.md +22 -0
- data/docs/SESSIONS.md +48 -0
- data/docs/TOOLS.md +53 -0
- data/docs/TOOL_RUNTIME.md +154 -0
- data/docs/superpowers/plans/2026-03-26-production-ready-ollama-agent.md +2454 -0
- data/docs/superpowers/specs/2026-03-26-production-ready-ollama-agent-design.md +400 -0
- data/lib/ollama_agent/agent/agent_config.rb +53 -0
- data/lib/ollama_agent/agent/client_wiring.rb +76 -0
- data/lib/ollama_agent/agent/prompt_wiring.rb +55 -0
- data/lib/ollama_agent/agent/session_wiring.rb +53 -0
- data/lib/ollama_agent/agent.rb +148 -73
- data/lib/ollama_agent/agent_prompt.rb +31 -1
- data/lib/ollama_agent/chat_stream_carry.rb +88 -0
- data/lib/ollama_agent/chat_stream_thinking_format.rb +29 -0
- data/lib/ollama_agent/cli.rb +394 -4
- data/lib/ollama_agent/console.rb +177 -5
- data/lib/ollama_agent/context/manager.rb +100 -0
- data/lib/ollama_agent/context/token_counter.rb +33 -0
- data/lib/ollama_agent/diff_path_validator.rb +32 -10
- data/lib/ollama_agent/env_config.rb +44 -0
- data/lib/ollama_agent/external_agents/TODO-plan.md +1948 -0
- data/lib/ollama_agent/external_agents/argv_interp.rb +21 -0
- data/lib/ollama_agent/external_agents/default_agents.yml +60 -0
- data/lib/ollama_agent/external_agents/delegate_logger.rb +31 -0
- data/lib/ollama_agent/external_agents/delegate_timeout_status.rb +12 -0
- data/lib/ollama_agent/external_agents/env_helpers.rb +38 -0
- data/lib/ollama_agent/external_agents/path_validator.rb +32 -0
- data/lib/ollama_agent/external_agents/probe.rb +122 -0
- data/lib/ollama_agent/external_agents/registry.rb +50 -0
- data/lib/ollama_agent/external_agents/runner.rb +118 -0
- data/lib/ollama_agent/external_agents.rb +9 -0
- data/lib/ollama_agent/global_dotenv.rb +39 -0
- data/lib/ollama_agent/model_env.rb +26 -0
- data/lib/ollama_agent/ollama_chat_thinking_stream.rb +41 -0
- data/lib/ollama_agent/ollama_connection.rb +6 -1
- data/lib/ollama_agent/patch_risk.rb +81 -0
- data/lib/ollama_agent/patch_support.rb +27 -1
- data/lib/ollama_agent/path_sandbox.rb +62 -0
- data/lib/ollama_agent/prompt_skills/clean_ruby.md +131 -0
- data/lib/ollama_agent/prompt_skills/code_review.md +112 -0
- data/lib/ollama_agent/prompt_skills/design_patterns.md +56 -0
- data/lib/ollama_agent/prompt_skills/manifest.yml +25 -0
- data/lib/ollama_agent/prompt_skills/ollama_agent_patterns.md +132 -0
- data/lib/ollama_agent/prompt_skills/rails_best_practices.md +41 -0
- data/lib/ollama_agent/prompt_skills/rails_style.md +138 -0
- data/lib/ollama_agent/prompt_skills/rspec.md +280 -0
- data/lib/ollama_agent/prompt_skills/rubocop.md +7 -0
- data/lib/ollama_agent/prompt_skills/ruby_style.md +121 -0
- data/lib/ollama_agent/prompt_skills/solid.md +270 -0
- data/lib/ollama_agent/prompt_skills/solid_ruby.md +223 -0
- data/lib/ollama_agent/prompt_skills.rb +169 -0
- data/lib/ollama_agent/repo_list.rb +4 -1
- data/lib/ollama_agent/resilience/audit_logger.rb +79 -0
- data/lib/ollama_agent/resilience/retry_middleware.rb +45 -0
- data/lib/ollama_agent/resilience/retry_policy.rb +51 -0
- data/lib/ollama_agent/ruby_index_tool_support.rb +17 -6
- data/lib/ollama_agent/runner.rb +123 -0
- data/lib/ollama_agent/sandboxed_tools/delegate_external.rb +62 -0
- data/lib/ollama_agent/sandboxed_tools/file_read_write.rb +100 -0
- data/lib/ollama_agent/sandboxed_tools/search_text.rb +60 -0
- data/lib/ollama_agent/sandboxed_tools.rb +55 -116
- data/lib/ollama_agent/search_backend.rb +93 -0
- data/lib/ollama_agent/self_improvement/analyzer.rb +34 -0
- data/lib/ollama_agent/self_improvement/improver.rb +340 -0
- data/lib/ollama_agent/self_improvement/modes.rb +25 -0
- data/lib/ollama_agent/self_improvement/ruby_mastery_context.rb +66 -0
- data/lib/ollama_agent/self_improvement.rb +5 -0
- data/lib/ollama_agent/session/session.rb +8 -0
- data/lib/ollama_agent/session/store.rb +68 -0
- data/lib/ollama_agent/streaming/console_streamer.rb +29 -0
- data/lib/ollama_agent/streaming/hooks.rb +39 -0
- data/lib/ollama_agent/tool_arguments.rb +13 -1
- data/lib/ollama_agent/tool_content_parser.rb +1 -1
- data/lib/ollama_agent/tool_runtime/executor.rb +34 -0
- data/lib/ollama_agent/tool_runtime/json_extractor.rb +62 -0
- data/lib/ollama_agent/tool_runtime/loop.rb +72 -0
- data/lib/ollama_agent/tool_runtime/memory.rb +32 -0
- data/lib/ollama_agent/tool_runtime/ollama_json_planner.rb +98 -0
- data/lib/ollama_agent/tool_runtime/plan_extractor.rb +12 -0
- data/lib/ollama_agent/tool_runtime/registry.rb +60 -0
- data/lib/ollama_agent/tool_runtime/tool.rb +24 -0
- data/lib/ollama_agent/tool_runtime.rb +24 -0
- data/lib/ollama_agent/tools/registry.rb +55 -0
- data/lib/ollama_agent/tools_schema.rb +74 -1
- data/lib/ollama_agent/user_prompt.rb +35 -0
- data/lib/ollama_agent/version.rb +1 -1
- data/lib/ollama_agent.rb +25 -0
- data/reproduce_429.rb +40 -0
- data/sig/ollama_agent.rbs +111 -1
- metadata +78 -2
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OllamaAgent
|
|
4
|
+
# Classifies a proposed unified diff for semi-automatic patch approval (obvious vs risky).
|
|
5
|
+
module PatchRisk
|
|
6
|
+
FORBIDDEN_PATTERNS = [
|
|
7
|
+
/\beval\s*\(/,
|
|
8
|
+
/`rm\s+-rf/,
|
|
9
|
+
/system\s*\(\s*["']sudo/,
|
|
10
|
+
/\bFile\.delete\b/,
|
|
11
|
+
/\bKernel\.exec\b/
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
LARGE_DIFF_LINES = 80
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
def large_diff_line_limit
|
|
19
|
+
v = ENV.fetch("OLLAMA_AGENT_PATCH_RISK_MAX_DIFF_LINES", nil)
|
|
20
|
+
return LARGE_DIFF_LINES if v.nil? || v.to_s.strip.empty?
|
|
21
|
+
|
|
22
|
+
Integer(v)
|
|
23
|
+
rescue ArgumentError, TypeError
|
|
24
|
+
LARGE_DIFF_LINES
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def forbidden?(diff)
|
|
28
|
+
FORBIDDEN_PATTERNS.any? { |pattern| diff.match?(pattern) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns :auto_approve (no prompt) or :require_confirmation (prompt when confirm_patches is on).
|
|
32
|
+
def assess(path, diff)
|
|
33
|
+
relative = path.to_s.tr("\\", "/")
|
|
34
|
+
|
|
35
|
+
return :require_confirmation if risky?(relative, diff)
|
|
36
|
+
|
|
37
|
+
return :auto_approve if obvious_path?(relative)
|
|
38
|
+
return :auto_approve if safe_spec_change?(relative, diff)
|
|
39
|
+
|
|
40
|
+
:require_confirmation
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def risky?(relative, diff)
|
|
44
|
+
forbidden?(diff) ||
|
|
45
|
+
critical_path?(relative) ||
|
|
46
|
+
large_diff?(diff) ||
|
|
47
|
+
critical_lib_file?(relative)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def large_diff?(diff, limit: nil)
|
|
51
|
+
max = limit.nil? ? large_diff_line_limit : limit
|
|
52
|
+
diff.scan(/^[-+][^-+]/).size > max
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def obvious_path?(relative)
|
|
56
|
+
relative.end_with?(".md") || relative.start_with?("docs/")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def safe_spec_change?(relative, diff)
|
|
60
|
+
relative.start_with?("spec/") && !large_diff?(diff, limit: 40)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def critical_lib_file?(relative)
|
|
64
|
+
return false unless relative.start_with?("lib/")
|
|
65
|
+
|
|
66
|
+
relative.include?("sandboxed_tools") ||
|
|
67
|
+
relative.include?("patch_support") ||
|
|
68
|
+
relative.end_with?("ollama_agent/agent.rb") ||
|
|
69
|
+
relative.end_with?("ollama_agent/tools_schema.rb")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def critical_path?(relative)
|
|
73
|
+
return true if relative.match?(/\A(Gemfile|Gemfile\.lock)\z/)
|
|
74
|
+
return true if relative.end_with?(".gemspec")
|
|
75
|
+
return true if relative == "lib/ollama_agent/version.rb"
|
|
76
|
+
return true if relative.start_with?("exe/")
|
|
77
|
+
|
|
78
|
+
false
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -8,6 +8,8 @@ module OllamaAgent
|
|
|
8
8
|
private
|
|
9
9
|
|
|
10
10
|
def patch_dry_run(diff)
|
|
11
|
+
return patch_missing_tool_message unless patch_available?
|
|
12
|
+
|
|
11
13
|
output, status = Open3.capture2e(
|
|
12
14
|
"patch", "-p1", "-f", "-d", @root, "--dry-run",
|
|
13
15
|
stdin_data: diff
|
|
@@ -35,7 +37,8 @@ module OllamaAgent
|
|
|
35
37
|
Re-read the file with read_file, then rebuild the diff using exact lines from that file (not placeholders).
|
|
36
38
|
The @@ hunk line counts must match the hunk body the way git diff would emit them.
|
|
37
39
|
MSG
|
|
38
|
-
hint.empty? ? msg : "#{msg}\n#{hint}"
|
|
40
|
+
body = hint.empty? ? msg : "#{msg}\n#{hint}"
|
|
41
|
+
body.end_with?("\n") ? body : "#{body}\n"
|
|
39
42
|
end
|
|
40
43
|
|
|
41
44
|
def patch_stderr_hint(detail)
|
|
@@ -65,6 +68,8 @@ module OllamaAgent
|
|
|
65
68
|
end
|
|
66
69
|
|
|
67
70
|
def apply_patch(diff)
|
|
71
|
+
return patch_missing_tool_message unless patch_available?
|
|
72
|
+
|
|
68
73
|
output, status = Open3.capture2e(
|
|
69
74
|
"patch", "-p1", "-f", "-d", @root,
|
|
70
75
|
stdin_data: diff
|
|
@@ -74,5 +79,26 @@ module OllamaAgent
|
|
|
74
79
|
|
|
75
80
|
patch_failure_message(output, dry_run: false)
|
|
76
81
|
end
|
|
82
|
+
|
|
83
|
+
def patch_available?
|
|
84
|
+
return @patch_available if defined?(@patch_available)
|
|
85
|
+
|
|
86
|
+
@patch_available = patch_binary_usable?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def patch_binary_usable?
|
|
90
|
+
_, status = Open3.capture2e("patch", "--version")
|
|
91
|
+
return true if status.success?
|
|
92
|
+
|
|
93
|
+
_, which_status = Open3.capture2e("which", "patch")
|
|
94
|
+
which_status.success?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def patch_missing_tool_message
|
|
98
|
+
<<~MSG.strip
|
|
99
|
+
Error: ollama_agent: the `patch` program was not found or is not usable on PATH. edit_file requires GNU patch.
|
|
100
|
+
Install it (e.g. apt install patch, brew install gpatch), or on Windows use Git Bash, WSL, or GnuWin32.
|
|
101
|
+
MSG
|
|
102
|
+
end
|
|
77
103
|
end
|
|
78
104
|
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
module OllamaAgent
|
|
6
|
+
# Resolves paths under a project root using File.realpath so symlinks cannot escape the sandbox.
|
|
7
|
+
module PathSandbox
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# @param root_abs [String] File.expand_path of the project root (may be a symlink path)
|
|
11
|
+
# @param root_real [String] File.realpath(root_abs) when the root exists
|
|
12
|
+
# @param user_path [String] relative or absolute path from tool args
|
|
13
|
+
def allowed?(root_abs, root_real, user_path)
|
|
14
|
+
return false if user_path.nil? || user_path.to_s.strip.empty?
|
|
15
|
+
|
|
16
|
+
expanded = Pathname(user_path.to_s).expand_path(root_abs).cleanpath.to_s
|
|
17
|
+
return false unless lexically_under_root_abs?(expanded, root_abs)
|
|
18
|
+
|
|
19
|
+
candidate_under_root?(expanded, root_real, root_abs)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def lexically_under_root_abs?(expanded_abs, root_abs)
|
|
23
|
+
expanded_abs == root_abs || expanded_abs.start_with?(root_abs + File::SEPARATOR)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def candidate_under_root?(expanded_abs, root_real, root_abs)
|
|
27
|
+
path_real = File.realpath(expanded_abs)
|
|
28
|
+
under_root_real?(path_real, root_real)
|
|
29
|
+
rescue Errno::ENOENT
|
|
30
|
+
nonexistent_path_allowed_under_root?(expanded_abs, root_real, root_abs)
|
|
31
|
+
rescue Errno::ELOOP, Errno::EACCES
|
|
32
|
+
false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def under_root_real?(path_real, root_real)
|
|
36
|
+
path_real == root_real || path_real.start_with?(root_real + File::SEPARATOR)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# rubocop:disable Metrics/MethodLength -- parent walk for missing path segments
|
|
40
|
+
def nonexistent_path_allowed_under_root?(expanded_abs, root_real, root_abs)
|
|
41
|
+
parent = expanded_abs
|
|
42
|
+
loop do
|
|
43
|
+
next_parent = File.dirname(parent)
|
|
44
|
+
break if next_parent == parent
|
|
45
|
+
|
|
46
|
+
parent = next_parent
|
|
47
|
+
begin
|
|
48
|
+
pr = File.realpath(parent)
|
|
49
|
+
return false unless under_root_real?(pr, root_real)
|
|
50
|
+
|
|
51
|
+
return lexically_under_root_abs?(expanded_abs, root_abs)
|
|
52
|
+
rescue Errno::ENOENT
|
|
53
|
+
next
|
|
54
|
+
rescue Errno::ELOOP, Errno::EACCES
|
|
55
|
+
return false
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
false
|
|
59
|
+
end
|
|
60
|
+
# rubocop:enable Metrics/MethodLength
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clean-ruby-code
|
|
3
|
+
description: >
|
|
4
|
+
Enforces clean Ruby code principles during all Ruby/Rails code generation, review, and refactoring tasks.
|
|
5
|
+
Applies naming conventions, method design, boolean logic, class architecture, refactoring patterns,
|
|
6
|
+
and TDD practices derived from Clean Ruby methodology. Use this skill whenever writing Ruby code,
|
|
7
|
+
reviewing Ruby pull requests, refactoring Rails models/controllers/services, creating new Ruby classes
|
|
8
|
+
or modules, writing RSpec tests, or when the user asks for code review, code improvement, naming help,
|
|
9
|
+
or architecture guidance in Ruby. Also trigger when the user mentions "clean code", "refactor",
|
|
10
|
+
"code smell", "naming", "SRP", "TDD", or "code quality" in a Ruby context. This skill should activate
|
|
11
|
+
for ANY Ruby code generation task — even if the user doesn't explicitly ask for "clean" code — because
|
|
12
|
+
all generated Ruby should be clean by default.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Clean Ruby Code Agent
|
|
16
|
+
|
|
17
|
+
This skill enforces clean Ruby code principles across all code generation, review, and refactoring.
|
|
18
|
+
Every piece of Ruby code produced must satisfy three core qualities: **readable**, **extensible**, **simple**.
|
|
19
|
+
|
|
20
|
+
## Core Philosophy
|
|
21
|
+
|
|
22
|
+
Code is read far more often than it is written. Every naming choice, method signature, and class boundary
|
|
23
|
+
should minimize the cognitive load on the next reader. When in doubt, choose the simpler solution —
|
|
24
|
+
complexity is the enemy of maintainability.
|
|
25
|
+
|
|
26
|
+
## Decision Flow
|
|
27
|
+
|
|
28
|
+
When generating or reviewing Ruby code, apply these checks in order:
|
|
29
|
+
|
|
30
|
+
1. **Naming** → Are all names descriptive, snake_case, verb-prefixed (methods), purpose-named (classes)?
|
|
31
|
+
2. **Methods** → Fewer params? Guard clauses? Under 10 lines? No deep nesting?
|
|
32
|
+
3. **Boolean Logic** → Extracted to named methods/variables? No double negatives? No raw `unless` with compound conditions?
|
|
33
|
+
4. **Classes** → Simple `initialize`? SRP? Max 3 levels of inheritance? Composition where appropriate?
|
|
34
|
+
5. **Refactoring** → Can any piece be simplified without losing functionality? Are there comments that should be methods?
|
|
35
|
+
6. **Tests** → Does the code have clear, descriptive RSpec tests with context blocks and explicit expectations?
|
|
36
|
+
|
|
37
|
+
For detailed rules on each area, read the corresponding reference file:
|
|
38
|
+
|
|
39
|
+
- **Naming rules**: `references/naming.md`
|
|
40
|
+
- **Method design**: `references/methods.md`
|
|
41
|
+
- **Boolean logic**: `references/boolean-logic.md`
|
|
42
|
+
- **Class architecture**: `references/classes.md`
|
|
43
|
+
- **Refactoring patterns**: `references/refactoring.md`
|
|
44
|
+
- **TDD practices**: `references/tdd.md`
|
|
45
|
+
|
|
46
|
+
Read the relevant reference file(s) before generating or reviewing code in that area.
|
|
47
|
+
|
|
48
|
+
## Enforcement Rules
|
|
49
|
+
|
|
50
|
+
These rules apply to ALL Ruby code this agent produces or reviews:
|
|
51
|
+
|
|
52
|
+
### Naming (always enforced)
|
|
53
|
+
|
|
54
|
+
- Variables: snake_case, descriptive of the data, no Hungarian notation, no conjunctions ("and"/"or"), no numeric suffixes unless versioning, no crutch words (Manager, Data, Info, List)
|
|
55
|
+
- Methods: verb-prefixed, `?` suffix for boolean returns, `!` suffix for destructive mutations
|
|
56
|
+
- Classes: named for purpose (e.g., `UserSetup`) or role (e.g., `InActiveUserQuery`), never generic (`UserManager`)
|
|
57
|
+
- Modules: named for the concept they group (e.g., `Calculable`, `Loggable`)
|
|
58
|
+
|
|
59
|
+
### Methods (always enforced)
|
|
60
|
+
|
|
61
|
+
- Maximum 3 parameters; use a config/params object beyond that
|
|
62
|
+
- Guard clauses instead of deep `if/else` nesting
|
|
63
|
+
- Target length: 5–10 lines; extract sub-operations into named private methods
|
|
64
|
+
- No unnecessary intermediate variables; leverage Ruby's implicit return
|
|
65
|
+
- Comments only when they explain *why*, never *what* — if a comment describes what code does, extract a method instead
|
|
66
|
+
|
|
67
|
+
### Boolean Logic (always enforced)
|
|
68
|
+
|
|
69
|
+
- Complex conditions → extract to a named predicate method (`def ready_to_spawn?`)
|
|
70
|
+
- Never use `unless` with compound conditions (`unless a && b` is banned)
|
|
71
|
+
- Avoid double negatives (`!not_found` → `found?`)
|
|
72
|
+
- Use `&&` (short-circuit) not `&` (eager) unless eager evaluation is explicitly needed
|
|
73
|
+
- Leverage Ruby truthy/falsy — don't write `if x == true`, write `if x`
|
|
74
|
+
- Ternary only for single simple conditions; use `if/else` for compound logic
|
|
75
|
+
|
|
76
|
+
### Classes (always enforced)
|
|
77
|
+
|
|
78
|
+
- `initialize` does assignment only — no external calls, no side effects
|
|
79
|
+
- Error-prone operations go in explicit setup methods called after instantiation
|
|
80
|
+
- Limit inheritance to 3 levels; prefer composition (`has-a`) over inheritance (`is-a`)
|
|
81
|
+
- Instance variables: use `attr_reader`/`attr_accessor` — avoid raw `@var` access in public methods
|
|
82
|
+
- Private methods below `private` keyword, ordered by call sequence
|
|
83
|
+
- If private methods are generic utilities (math, formatting), extract to a module
|
|
84
|
+
|
|
85
|
+
### Refactoring (applied during review and generation)
|
|
86
|
+
|
|
87
|
+
- No change is too small — rename a variable, extract a method, remove a comment
|
|
88
|
+
- Replace comments with self-documenting method names
|
|
89
|
+
- Apply SRP: if a class/method does two things, split it
|
|
90
|
+
- Watch for shotgun surgery (one change requires edits in many places) — consolidate
|
|
91
|
+
- Replace conditional chains (`if/elsif/elsif`) with polymorphism or lookup tables
|
|
92
|
+
|
|
93
|
+
### TDD (applied when generating tests)
|
|
94
|
+
|
|
95
|
+
- Write the test first, then the minimal implementation
|
|
96
|
+
- Test descriptions: `context '#method_name'` → `it 'returns X when Y'`
|
|
97
|
+
- Use `subject`, `let`, and `before` blocks — no repeated setup
|
|
98
|
+
- Separate expected and actual values for clarity
|
|
99
|
+
- Cover: happy path, nil inputs, edge cases, error conditions
|
|
100
|
+
- Tests are production code — apply the same naming and structure rules
|
|
101
|
+
|
|
102
|
+
## Anti-Patterns to Flag
|
|
103
|
+
|
|
104
|
+
When reviewing code, flag these immediately:
|
|
105
|
+
|
|
106
|
+
| Anti-Pattern | Fix |
|
|
107
|
+
|---|---|
|
|
108
|
+
| Method > 15 lines | Extract private methods |
|
|
109
|
+
| > 3 params | Introduce parameter object |
|
|
110
|
+
| Nested `if` > 2 levels | Guard clauses or extract predicate |
|
|
111
|
+
| `unless` with `&&`/`||` | Convert to `if` with inverted logic |
|
|
112
|
+
| Comment describing *what* | Extract named method |
|
|
113
|
+
| God class (> 200 lines) | Split by responsibility |
|
|
114
|
+
| `initialize` with side effects | Move to explicit setup method |
|
|
115
|
+
| Generic name (Manager, Helper, Data) | Rename to purpose/role |
|
|
116
|
+
| Double negative (`!not_x`) | Invert the method name |
|
|
117
|
+
| Bare `rescue` | Rescue specific exceptions |
|
|
118
|
+
|
|
119
|
+
## Output Format
|
|
120
|
+
|
|
121
|
+
When generating Ruby code, structure output as:
|
|
122
|
+
|
|
123
|
+
1. The code itself — complete, runnable, following all rules above
|
|
124
|
+
2. If refactoring existing code: brief annotation of what changed and why (1-2 lines per change)
|
|
125
|
+
3. If the user asked for review: list violations as `[RULE] description → fix` format
|
|
126
|
+
|
|
127
|
+
When the code is part of a Rails app, also apply:
|
|
128
|
+
- Models: thin, no business logic beyond validations and scopes
|
|
129
|
+
- Controllers: thin, delegate to domain objects
|
|
130
|
+
- Services: only for orchestration across multiple domain objects or external systems
|
|
131
|
+
- Queries: extract complex `where` chains to query objects (e.g., `InActiveUserQuery`)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-review
|
|
3
|
+
description: Structured code review for Ruby/Rails projects. Detects file type, scans the local codebase for project-specific patterns first, then applies global skills in priority order. Use when asked to review, audit, or check code quality.
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
argument-hint: [file-or-directory]
|
|
6
|
+
allowed-tools: Read, Glob, Grep
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Code Review — Orchestrator
|
|
10
|
+
|
|
11
|
+
Perform a structured code review of `$ARGUMENTS`. Follow this exact workflow.
|
|
12
|
+
|
|
13
|
+
## Step 1 — Identify File Type
|
|
14
|
+
|
|
15
|
+
| File Pattern | Type | Primary Skills |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| `app/controllers/**` | Controller | rails-best-practices, solid-ruby |
|
|
18
|
+
| `app/models/**` | Model | rails-best-practices, solid-ruby, ruby-design-patterns |
|
|
19
|
+
| `app/services/**` | Service Object | solid-ruby, rails-best-practices |
|
|
20
|
+
| `app/workers/**`, `app/jobs/**` | Background Job | rails-best-practices, solid-ruby |
|
|
21
|
+
| `spec/**/*_spec.rb` | RSpec spec | rspec, solid-ruby |
|
|
22
|
+
| `db/migrate/**` | Migration | rails-best-practices |
|
|
23
|
+
| `config/routes.rb` | Routes | rails-best-practices |
|
|
24
|
+
| `app/views/**` | View/Partial | rails-best-practices, ruby-style |
|
|
25
|
+
| `lib/**` | Library | solid-ruby, ruby-style, ruby-design-patterns |
|
|
26
|
+
|
|
27
|
+
## Step 2 — Scan Project for Existing Patterns (MANDATORY)
|
|
28
|
+
|
|
29
|
+
Before applying any rule, read 2–3 similar files in the same directory to extract project conventions.
|
|
30
|
+
|
|
31
|
+
**Find and extract:**
|
|
32
|
+
- Controllers: response format, base controller, auth pattern, error format, param naming
|
|
33
|
+
- Models: shared concerns/modules, validation style, enum convention, scope naming
|
|
34
|
+
- Services: calling convention (`.call` vs `.run`), return type, class naming, base class
|
|
35
|
+
- Specs: factory style, shared examples, helper includes, context naming
|
|
36
|
+
|
|
37
|
+
See [project-pattern-detection.md](project-pattern-detection.md) for full detection checklist.
|
|
38
|
+
|
|
39
|
+
## Step 3 — Review in Priority Order
|
|
40
|
+
|
|
41
|
+
### 🔴 CRITICAL — Always flag, regardless of project style
|
|
42
|
+
- SQL injection via string interpolation
|
|
43
|
+
- Missing Strong Parameters / mass assignment vulnerability
|
|
44
|
+
- `rescue Exception` (swallows signals)
|
|
45
|
+
- User input in file paths, shell commands, or redirects
|
|
46
|
+
- Hardcoded credentials or secrets
|
|
47
|
+
- `save` return value silently ignored
|
|
48
|
+
- Auth check missing or bypassable
|
|
49
|
+
- Sensitive data logged
|
|
50
|
+
|
|
51
|
+
### 🟠 PERFORMANCE — Always flag
|
|
52
|
+
- N+1 queries (association in loop without preload)
|
|
53
|
+
- `.where` / query methods in AR instance methods (breaks preloading)
|
|
54
|
+
- `.count` where `.size` should be used
|
|
55
|
+
- `any?`/`empty?` then `.each` on same relation (two queries)
|
|
56
|
+
- `exists?` called multiple times (never memoized)
|
|
57
|
+
- `find_each` missing on large dataset iteration
|
|
58
|
+
- No timeout on external HTTP/Redis calls
|
|
59
|
+
- `after_save` for side effects instead of `after_commit`
|
|
60
|
+
|
|
61
|
+
### 🟡 PROJECT CONSISTENCY — Most important correctness layer
|
|
62
|
+
Compare against patterns found in Step 2. Phrase as: "The rest of the codebase does X — this does Y."
|
|
63
|
+
- Different service calling convention
|
|
64
|
+
- Different response/error format in controller
|
|
65
|
+
- Different factory/spec helper pattern
|
|
66
|
+
- Missing shared concern/module that all similar models include
|
|
67
|
+
- Different enum/scope/validation naming
|
|
68
|
+
|
|
69
|
+
### 🔵 BEST PRACTICES — Apply where no project pattern covers
|
|
70
|
+
Reference: rails-best-practices, solid-ruby, rspec, ruby-design-patterns
|
|
71
|
+
|
|
72
|
+
### ⚪ STYLE — Only if clearly inconsistent
|
|
73
|
+
Reference: ruby-style, rails-style
|
|
74
|
+
|
|
75
|
+
## Step 4 — Output Format
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
## Code Review: [filename]
|
|
79
|
+
**Type:** [file type]
|
|
80
|
+
**Project patterns detected:** [1-line summary]
|
|
81
|
+
|
|
82
|
+
### 🔴 CRITICAL | [title]
|
|
83
|
+
**File:** path/to/file.rb:42
|
|
84
|
+
**Issue:** [what and why]
|
|
85
|
+
**Fix:**
|
|
86
|
+
# Before / After code
|
|
87
|
+
**Rule:** [source]
|
|
88
|
+
|
|
89
|
+
[repeat per finding, skip empty severity sections]
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
## Verdict: SHIP ✅ | NEEDS FIXES 🔧 | CRITICAL ISSUES 🚨
|
|
93
|
+
|
|
94
|
+
- 🔴 Critical: N
|
|
95
|
+
- 🟠 Performance: N
|
|
96
|
+
- 🟡 Consistency: N
|
|
97
|
+
- 🔵 Best Practice: N
|
|
98
|
+
- ⚪ Style: N
|
|
99
|
+
|
|
100
|
+
Must fix before merge: [list]
|
|
101
|
+
Can fix in follow-up: [list]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
After verdict, ask: "Want me to apply the fixes?"
|
|
105
|
+
|
|
106
|
+
## Behavior Rules
|
|
107
|
+
- Cannot find similar files → say so, proceed with global skills only
|
|
108
|
+
- Do not flag style as critical
|
|
109
|
+
- Do not invent violations — only flag what is visible in the code
|
|
110
|
+
- Project patterns beat global skills when they conflict
|
|
111
|
+
- One finding per issue — no duplicates across severity levels
|
|
112
|
+
- For specs: also check that the spec tests what production code actually does
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ruby-design-patterns
|
|
3
|
+
description: All 23 GoF design patterns with Ruby implementations from refactoring.guru. Use when designing or refactoring Ruby/Rails code and you need to apply or recognize a design pattern. Covers Creational (5), Structural (7), and Behavioral (11) patterns with Ruby code examples and Rails-specific applications.
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Ruby Design Patterns — Complete Reference
|
|
8
|
+
|
|
9
|
+
Source: https://refactoring.guru/design-patterns/ruby
|
|
10
|
+
|
|
11
|
+
> Don't force patterns. Let them emerge from refactoring. Apply when you recognize the problem, not speculatively.
|
|
12
|
+
|
|
13
|
+
## By Problem — Quick Lookup
|
|
14
|
+
|
|
15
|
+
| Problem | Pattern | Category |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Creating families of compatible objects | Abstract Factory | Creational |
|
|
18
|
+
| Building complex objects step by step | Builder | Creational |
|
|
19
|
+
| Subclass decides which object to create | Factory Method | Creational |
|
|
20
|
+
| Clone objects without coupling to class | Prototype | Creational |
|
|
21
|
+
| One instance globally | Singleton | Creational |
|
|
22
|
+
| Incompatible interfaces must work together | Adapter | Structural |
|
|
23
|
+
| Split abstraction from implementation | Bridge | Structural |
|
|
24
|
+
| Tree structures, treat parts and wholes uniformly | Composite | Structural |
|
|
25
|
+
| Add behavior without subclassing | Decorator | Structural |
|
|
26
|
+
| Simplify a complex subsystem | Facade | Structural |
|
|
27
|
+
| Share common state across many objects | Flyweight | Structural |
|
|
28
|
+
| Control access to an object | Proxy | Structural |
|
|
29
|
+
| Pass request through a handler chain | Chain of Responsibility | Behavioral |
|
|
30
|
+
| Encapsulate a request as an object | Command | Behavioral |
|
|
31
|
+
| Traverse a collection without exposing it | Iterator | Behavioral |
|
|
32
|
+
| Decouple communicating objects | Mediator | Behavioral |
|
|
33
|
+
| Save and restore object state | Memento | Behavioral |
|
|
34
|
+
| Notify many objects about events | Observer | Behavioral |
|
|
35
|
+
| Alter behavior when state changes | State | Behavioral |
|
|
36
|
+
| Swap algorithms at runtime | Strategy | Behavioral |
|
|
37
|
+
| Define algorithm skeleton, defer steps | Template Method | Behavioral |
|
|
38
|
+
| Add operations without modifying classes | Visitor | Behavioral |
|
|
39
|
+
|
|
40
|
+
## Ruby-Native Pattern Support
|
|
41
|
+
|
|
42
|
+
| Pattern | Ruby built-in |
|
|
43
|
+
|---|---|
|
|
44
|
+
| Iterator | `Enumerable`, `Enumerator` |
|
|
45
|
+
| Observer | `Observable` module (stdlib) |
|
|
46
|
+
| Singleton | `Singleton` module (stdlib) |
|
|
47
|
+
| Decorator | Modules + `prepend` / `extend` |
|
|
48
|
+
| Strategy | Blocks / Procs / lambdas |
|
|
49
|
+
| Template Method | Inheritance + hook methods |
|
|
50
|
+
| Command | Proc / Method objects |
|
|
51
|
+
|
|
52
|
+
## Pattern Detail Files
|
|
53
|
+
|
|
54
|
+
- [Creational Patterns (1–5)](creational-patterns.md) — Abstract Factory, Builder, Factory Method, Prototype, Singleton
|
|
55
|
+
- [Structural Patterns (6–12)](structural-patterns.md) — Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy
|
|
56
|
+
- [Behavioral Patterns (13–22)](behavioral-patterns.md) — Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Ordered bundled prompt skills (ids used by OLLAMA_AGENT_SKILLS_INCLUDE / EXCLUDE).
|
|
2
|
+
version: 1
|
|
3
|
+
skills:
|
|
4
|
+
- id: clean_ruby
|
|
5
|
+
file: clean_ruby.md
|
|
6
|
+
- id: ruby_style
|
|
7
|
+
file: ruby_style.md
|
|
8
|
+
- id: rubocop
|
|
9
|
+
file: rubocop.md
|
|
10
|
+
- id: solid
|
|
11
|
+
file: solid.md
|
|
12
|
+
- id: solid_ruby
|
|
13
|
+
file: solid_ruby.md
|
|
14
|
+
- id: design_patterns
|
|
15
|
+
file: design_patterns.md
|
|
16
|
+
- id: rspec
|
|
17
|
+
file: rspec.md
|
|
18
|
+
- id: rails_style
|
|
19
|
+
file: rails_style.md
|
|
20
|
+
- id: rails_best_practices
|
|
21
|
+
file: rails_best_practices.md
|
|
22
|
+
- id: code_review
|
|
23
|
+
file: code_review.md
|
|
24
|
+
- id: ollama_agent_patterns
|
|
25
|
+
file: ollama_agent_patterns.md
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ollama-agent-patterns
|
|
3
|
+
description: >-
|
|
4
|
+
Blueprint for building a CLI Ollama-based coding agent (Ruby gem) with design
|
|
5
|
+
patterns and judicious metaprogramming—Facade, Template Method, Factory/Registry,
|
|
6
|
+
Builder, Adapter, Proxy, Command, Observer, Strategy, State, and tool DSLs. Use
|
|
7
|
+
when working on ollama_agent, ollama-client integration, tools, LLM adapters,
|
|
8
|
+
prompts, streaming, patch application, or when the user asks for agent
|
|
9
|
+
architecture, extensibility, registries, or Ruby metaprogramming for agents.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Skill: Ollama Agent — Design Patterns & Metaprogramming
|
|
13
|
+
|
|
14
|
+
## 1. Overview
|
|
15
|
+
|
|
16
|
+
Blueprint for a **CLI coding agent** (e.g. `ollama_agent`) that uses **ollama-client** to chat with tools: read/search files, apply small unified diffs from natural language. Patterns keep the design **extensible and maintainable** without front-loading complexity.
|
|
17
|
+
|
|
18
|
+
## 2. Core components & patterns
|
|
19
|
+
|
|
20
|
+
| Component | Pattern(s) | Purpose |
|
|
21
|
+
|-----------|------------|---------|
|
|
22
|
+
| **Agent** | Facade, Template Method | Simple API (`run`); skeleton loop with overridable steps. |
|
|
23
|
+
| **Tools** | Factory Method, Command, Registry | Load/execute tools; optional logging/replay/undo. |
|
|
24
|
+
| **LLM client** | Adapter, (optional) Proxy | Swap backends; logging/metrics without forking core. |
|
|
25
|
+
| **Prompts** | Builder | Fluent construction of messages + tools + options. |
|
|
26
|
+
| **Streaming** | Observer | Multiple subscribers for tokens (UI, logs). |
|
|
27
|
+
| **Patch application** | Strategy | Swap `patch(1)` vs Ruby-native apply, etc. |
|
|
28
|
+
| **Conversation** | State | Phases (idle, tools, confirmation) without giant `if/else`. |
|
|
29
|
+
|
|
30
|
+
**Client lifetime:** **Singleton** is optional. Prefer **injectable** `Ollama::Client` (or adapter) for tests and per-run config (`timeout`, `base_url`, `api_key`).
|
|
31
|
+
|
|
32
|
+
## 3. Principles
|
|
33
|
+
|
|
34
|
+
1. **Start simple** — add Factory, State, Strategy when duplication or branching hurts; do not adopt every pattern up front.
|
|
35
|
+
2. **Core loop stays explicit** — readable `run` / tool loop; collaborators (`Adapter`, `PromptBuilder`, `ToolCommand`) hide detail, not `send` spaghetti.
|
|
36
|
+
3. **Metaprogramming at boundaries** — registration / DSLs at tool edges; avoid dynamic dispatch in error paths and hot loops unless measured.
|
|
37
|
+
4. **Workspace rules** — validated tool schemas; loggable/replayable actions; **no hardcoded model names** (runtime config).
|
|
38
|
+
|
|
39
|
+
## 4. Pattern map (where code lives)
|
|
40
|
+
|
|
41
|
+
| Area | Patterns | Role |
|
|
42
|
+
|------|-----------|------|
|
|
43
|
+
| **Agent** | Facade, Template Method | Single entry; fixed loop with overridable hooks. |
|
|
44
|
+
| **Tools** | Factory, Command, Registry | Instantiate tools; optional command objects for audit. |
|
|
45
|
+
| **LLM** | Adapter, Proxy | Backends; cross-cutting logging/rate limits. |
|
|
46
|
+
| **Prompts** | Builder | Messages + tools + options. |
|
|
47
|
+
| **Streaming** | Observer | Fan-out from client `hooks`. |
|
|
48
|
+
| **Patches** | Strategy | Pluggable apply path. |
|
|
49
|
+
| **Conversation** | State | Explicit phases when control flow grows. |
|
|
50
|
+
|
|
51
|
+
### Target layout (illustrative)
|
|
52
|
+
|
|
53
|
+
A fuller **pattern-oriented** tree (with `bin/console`, `commands/`, `strategies/`, `states/`, etc.) and a **“this repo today”** note live in **reference.md** under *Recommended gem structure*. Migrate only when complexity justifies new directories.
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
ollama_agent/
|
|
57
|
+
├── exe/ollama_agent
|
|
58
|
+
├── lib/ollama_agent.rb
|
|
59
|
+
└── lib/ollama_agent/
|
|
60
|
+
├── agent.rb
|
|
61
|
+
├── cli.rb
|
|
62
|
+
├── prompt_builder.rb # Builder (optional)
|
|
63
|
+
├── tool_registry.rb
|
|
64
|
+
├── tools/base.rb
|
|
65
|
+
├── tools/
|
|
66
|
+
├── llm/base_adapter.rb
|
|
67
|
+
├── llm/ollama_adapter.rb
|
|
68
|
+
├── llm/logging_proxy.rb
|
|
69
|
+
├── commands/tool_command.rb
|
|
70
|
+
├── observers/
|
|
71
|
+
├── strategies/
|
|
72
|
+
└── states/
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## 5. Creational patterns (summary)
|
|
76
|
+
|
|
77
|
+
- **Factory + registry** — Replace a growing `case` with `ToolRegistry.get(name)`; auto-register via `inherited` **or** explicit `tool_name` + hash (clearer than magic naming).
|
|
78
|
+
- **Builder** — `PromptBuilder` when message construction branches; skip until you have real optional composition.
|
|
79
|
+
- **Singleton** — Avoid as default for HTTP clients; use when you truly need one process-wide resource and tests can still stub.
|
|
80
|
+
|
|
81
|
+
## 6. Structural patterns (summary)
|
|
82
|
+
|
|
83
|
+
- **Adapter** — Common `chat(messages:, tools:, **options)` surface for Ollama vs future providers.
|
|
84
|
+
- **Proxy** — Wrap adapter for logging/metrics; keep thin.
|
|
85
|
+
- **Facade** — `Agent#run` — already the right shape.
|
|
86
|
+
|
|
87
|
+
## 7. Behavioral patterns (summary)
|
|
88
|
+
|
|
89
|
+
- **Command** — Wrap tool invocations when you need queues, structured logs, or replay; **undo** only with a real story (VCS/snapshots).
|
|
90
|
+
- **Observer** — Fan-out streaming tokens; compose with ollama-client `hooks`.
|
|
91
|
+
- **Strategy** — `PatchStrategy` if you need non-`patch` apply paths.
|
|
92
|
+
- **State** — When confirmation + tool rounds + idle become tangled.
|
|
93
|
+
- **Template Method** — Base agent class only if you have **multiple** agent variants sharing one loop.
|
|
94
|
+
|
|
95
|
+
## 8. Metaprogramming (judicious)
|
|
96
|
+
|
|
97
|
+
| Technique | Use when | Caution |
|
|
98
|
+
|-----------|----------|--------|
|
|
99
|
+
| **`inherited` + registry** | Many tools, stable naming | Keep in sync with **tool JSON schema**; consider explicit `tool_name`. |
|
|
100
|
+
| **Tool DSL** (`tool :name do …`) | Repetition dominates | Stack traces and IDE nav suffer; keep DSL thin. |
|
|
101
|
+
| **`send` for hooks** | Fixed event names | Prefer explicit methods for public API. |
|
|
102
|
+
| **`method_missing`** | Rare delegation | Not for core tool dispatch — use a Hash/registry. |
|
|
103
|
+
| **Plugin `extend`** | Third-party tools | Document load order and sandbox rules. |
|
|
104
|
+
|
|
105
|
+
## 9. When to avoid heavy metaprogramming
|
|
106
|
+
|
|
107
|
+
- Main agent loop and **error handling** — explicit flow wins.
|
|
108
|
+
- **Performance-sensitive** inner loops — measure before dynamic dispatch.
|
|
109
|
+
- **Public APIs** — stable, documented entry points over hidden DSL magic.
|
|
110
|
+
|
|
111
|
+
## 10. Summary
|
|
112
|
+
|
|
113
|
+
| Pattern | Benefit |
|
|
114
|
+
|---------|---------|
|
|
115
|
+
| Factory Method | Decouples tool creation from call sites. |
|
|
116
|
+
| Builder | Composes prompts/options without positional arg soup. |
|
|
117
|
+
| Singleton | Single shared resource (use sparingly for HTTP clients). |
|
|
118
|
+
| Adapter | Multiple LLM backends behind one shape. |
|
|
119
|
+
| Proxy | Cross-cutting concerns on the client. |
|
|
120
|
+
| Facade | Hides orchestration from CLI users. |
|
|
121
|
+
| Command | Tool calls as objects (log/replay/queue). |
|
|
122
|
+
| Observer | Decoupled streaming consumers. |
|
|
123
|
+
| Strategy | Swappable patch application. |
|
|
124
|
+
| State | Explicit conversation phases. |
|
|
125
|
+
| Template Method | Shared loop, varied steps. |
|
|
126
|
+
| Metaprogramming | Less boilerplate at **boundaries** only. |
|
|
127
|
+
|
|
128
|
+
**Start with straightforward code; refactor into patterns when pain appears.**
|
|
129
|
+
|
|
130
|
+
## 11. Full code examples
|
|
131
|
+
|
|
132
|
+
Runnable snippets (registry, `PromptBuilder`, adapters, proxy, command, observer, strategy, state, template-method skeleton, DSL sketch) live in **[reference.md](reference.md)**. Prefer copying from there and adapting to the real `ollama-client` API and this repo’s `SandboxedTools` / `tools_schema` constraints.
|