Class: Ask::CodingHarness::SystemPrompt
- Inherits:
-
Object
- Object
- Ask::CodingHarness::SystemPrompt
- Defined in:
- lib/ask/coding_harness/system_prompt.rb
Overview
Builds the agent system prompt, in the style of the pi coding agent: a composable base (custom or default), tool-aware guidelines, project context files injected as <project_context> (AGENTS.md / CLAUDE.md walked from the workspace up to the filesystem root), an append section, and the working directory as a closing footer.
SystemPrompt.build(workspace: "/path/to/project")
SystemPrompt.build(workspace: "/p", custom: "...", append: "...",
guidelines: ["Always run tests"])
Constant Summary collapse
- CONTEXT_FILE_CANDIDATES =
Files picked up per directory when building project context.
%w[AGENTS.md AGENTS.MD CLAUDE.md CLAUDE.MD].freeze
- MAX_CONTEXT_FILE_SIZE =
Context files larger than this are skipped (token hygiene).
64 * 1024
- DEFAULT_GUIDELINES =
Guidelines always present, regardless of configuration.
[ "Be concise in your responses", "Show file paths clearly when working with files", "Use absolute paths rooted at your working directory for all file and command operations", "Investigate before you act: read the relevant files before editing them", "Verify your work: run tests or checks after changing code", "Never claim an action you did not take" ].freeze
- DEFAULT_PROMPT =
<<~PROMPT.chomp You are an expert coding assistant operating inside ask-coding-harness, a coding agent harness for the ask-rb ecosystem. You help users by reading files, executing commands, editing code, and writing new files. Work autonomously: investigate the project, make changes, and verify your work. PROMPT
Class Method Summary collapse
-
.build(workspace:, custom: nil, append: nil, guidelines: [], context_files: :auto) ⇒ String
Build the full system prompt for a workspace.
-
.context_file_in(dir) ⇒ Object
The first existing context file in a directory, or nil.
-
.load_project_context(workspace) ⇒ Array<Hash>
Walk from the workspace up to the filesystem root, collecting the nearest AGENTS.md / CLAUDE.md in each directory (nearest first, deduplicated by realpath).
- .render_guidelines(extra) ⇒ Object
- .render_project_context(files) ⇒ Object
Class Method Details
.build(workspace:, custom: nil, append: nil, guidelines: [], context_files: :auto) ⇒ String
Build the full system prompt for a workspace.
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/ask/coding_harness/system_prompt.rb', line 51 def self.build(workspace:, custom: nil, append: nil, guidelines: [], context_files: :auto) files = case context_files when :auto then load_project_context(workspace) when nil then [] else context_files end parts = [] parts << (custom.nil? || custom.empty? ? DEFAULT_PROMPT : custom) parts << render_guidelines(guidelines) parts << render_project_context(files) unless files.empty? parts << append unless append.nil? || append.empty? parts << "Current working directory: #{workspace.to_s.tr("\\", "/")}" parts.reject { |p| p.nil? || p.empty? }.join("\n\n") end |
.context_file_in(dir) ⇒ Object
The first existing context file in a directory, or nil.
98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/ask/coding_harness/system_prompt.rb', line 98 def self.context_file_in(dir) CONTEXT_FILE_CANDIDATES.each do |name| path = File.join(dir, name) next unless File.file?(path) size = File.size(path) next if size > MAX_CONTEXT_FILE_SIZE content = File.read(path, encoding: "UTF-8") return { file: { path: path, content: content }, realpath: File.realpath(path) } rescue SystemCallError, EncodingError next end nil end |
.load_project_context(workspace) ⇒ Array<Hash>
Walk from the workspace up to the filesystem root, collecting the nearest AGENTS.md / CLAUDE.md in each directory (nearest first, deduplicated by realpath). Mirrors the pi coding agent's project context discovery.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/ask/coding_harness/system_prompt.rb', line 76 def self.load_project_context(workspace) files = [] seen = {} dir = File.(workspace) loop do candidate = context_file_in(dir) if candidate && !seen.key?(candidate[:realpath]) seen[candidate[:realpath]] = true files << candidate[:file] end parent = File.dirname(dir) break if parent == dir dir = parent end # Nearest first: the walk starts at the workspace. files end |
.render_guidelines(extra) ⇒ Object
112 113 114 115 116 117 118 119 |
# File 'lib/ask/coding_harness/system_prompt.rb', line 112 def self.render_guidelines(extra) list = DEFAULT_GUIDELINES.dup Array(extra).each do |g| normalized = g.to_s.strip list << normalized unless normalized.empty? || list.include?(normalized) end "Guidelines:\n" + list.map { |g| "- #{g}" }.join("\n") end |
.render_project_context(files) ⇒ Object
121 122 123 124 125 126 |
# File 'lib/ask/coding_harness/system_prompt.rb', line 121 def self.render_project_context(files) body = files.map do |f| "<project_instructions path=\"#{f[:path]}\">\n#{f[:content]}\n</project_instructions>" end.join("\n\n") "<project_context>\n\nProject-specific instructions and guidelines:\n\n#{body}\n</project_context>" end |