320 Commits

Author SHA1 Message Date
darthrubik ba92c6fabd Issue #70: Replace hand-written json_to<ChatResponse> with DRH_DEFINE_TYPE_REQUIRED
- Remove hand-written json_to<ChatResponse>, json_to_ChatResponse, and to_json for ChatResponse
- Replace with DRH_DEFINE_TYPE_REQUIRED(ChatResponse, choices, usage)
- Add has_json_to partial specialization for std::vector<T> when T has json_to
- Remove from_json generation from macros (uses .get_to() which doesn't work with nested custom types)
- Add tests for ChatResponse:
  - Valid choices array and usage succeeds
  - Missing required field returns error
  - Invalid nested Choice in vector returns element-index error
  - Invalid Usage returns field-contextualized error
  - Empty choices array succeeds
2026-05-28 10:19:42 -05:00
darthrubik 6a0dec9854 Implement issue 69: Replace hand-written json_to<ToolCall> with DRH_DEFINE_TYPE_REQUIRED
- Remove hand-written to_json, from_json for ToolCall
- Remove hand-written json_to<ToolCall> specialization
- Remove hand-written has_json_to<ToolCall> specialization
- Add DRH_DEFINE_TYPE_REQUIRED(ToolCall, id, type, function) macro
- Update test to expect field-contextualized error for nested FunctionCall

This validates that the updated macros correctly handle structs with
nested custom types (FunctionCall) with proper error propagation.
2026-05-28 09:35:33 -05:00
darthrubik d3b4b08a43 Issue 68: Update DRH_JSON_FROM_REQUIRED and DRH_JSON_CHECK_OPTIONAL macros to use json_to_field()
- Update DRH_JSON_FROM_REQUIRED macro to use json_to_field() for proper nested type error handling
- Update DRH_JSON_CHECK_OPTIONAL macro to use json_to_field() for proper nested type error handling
- Add DRH_JSON_FROM_OPTIONAL macro for from_json (void return type) since DRH_JSON_CHECK_OPTIONAL now returns std::expected
- Update DRH_DEFINE_TYPE_OPTIONAL to use DRH_JSON_FROM_OPTIONAL for from_json

Note: json_to_field() is used without namespace prefix to comply with CONTRIBUTING.md no-namespaces policy.
2026-05-28 09:24:41 -05:00
darthrubik 4b8636d174 fix: remove namespace detail from json_to_field to comply with no-namespace policy
The CONTRIBUTING.md policy states: 'This project does not use namespaces
in C++ code.' Move json_to_field to global scope and update test struct
to call it directly without namespace qualification.
2026-05-28 09:00:19 -05:00
darthrubik 4cc328a6fc feat: add json_to_field<T>() helper with if constexpr dispatch (issue #67)
- Add json_to_field<T>() helper in detail namespace for compile-time dispatch
- Uses if constexpr(has_json_to_v<T>) to choose between recursive json_to<T>()
  for custom types and direct .get_to() for primitives
- Custom types: calls json_to<T>() and prepends field name to error messages
- Primitive types: uses .get_to() directly (no recursion)
- Add MixedFieldsStruct test type with both primitive and custom type fields
- Add test_json_to_field.sh test suite to verify correct dispatch and error messages
2026-05-28 08:56:28 -05:00
darthrubik a245ab571f feat: Add json_to<std::vector<T>> specialization for recursive nested types
- Add helper class template detail::json_to_impl with partial specialization
  for std::vector<T> when T has json_to<T>() specialization
- Primary json_to<T> template delegates to helper class
- Explicit specializations (from macros/manual) take precedence
- Add json_to_vector<T>() convenience function for direct use
- Add -R vector<FunctionCall> test support in main.cpp
- Add test_json_type_vector.sh with comprehensive tests

Closes issue 66
2026-05-28 08:48:02 -05:00
darthrubik 9906f699a2 Refactor has_json_to_v trait to remove namespace usage
The project CONTRIBUTING.md policy prohibits namespaces. Removed
namespace detail wrapper from has_json_to trait and all its
specializations, placing them at global scope instead.
2026-05-28 08:32:46 -05:00
darthrubik 1015d7f8c7 Add has_json_to_v type trait to detect json_to<T> specializations
- Add compile-time type trait  in  namespace
- Expose public alias  at namespace scope
- Update DRH_DEFINE_TYPE_REQUIRED and DRH_DEFINE_TYPE_OPTIONAL macros
  to specialize the trait for generated types
- Add explicit trait specializations for manually-defined types:
  ToolCall, Message, Choice, ChatResponse, HarnessState
- Add static_assert tests verifying correct behavior for primitive
  types (std::string, int) return false and custom types
  (FunctionCall, Usage) return true
2026-05-28 08:29:38 -05:00
darthrubik ffb00117df Removing unused function 2026-05-28 07:42:34 -05:00
darthrubik 6a37f312c8 Issue 64: Convert tool parameter types to json_to<T>() template syntax
Convert EditToolParams, ReadFileToolParams, SkillToolParams, and
AgentTaskParams from using json_to_##Type() function calls to the
json_to<Type>() template specialization syntax.

Updated files:
- tools/edit_tool.cpp
- tools/file_read_tool.cpp
- src/skill.cpp
- tools/agent_tool.cpp
- src/main.cpp (-R flag handler)
2026-05-28 07:35:21 -05:00
darthrubik 611c5578ac issue 63: Convert complex types to json_to<T>() template specializations
Convert hand-written json_to functions to template specializations for
complex types with mixed required/optional fields and nested types:
- ToolCall uses json_to<ToolCall>() and internally calls json_to<FunctionCall>()
- Message uses json_to<Message>() and internally calls json_to<ToolCall>()
- Choice uses json_to<Choice>() and internally calls json_to<Message>()
- ChatResponse json_to_ChatResponse() now calls json_to<Choice>() for nested types
- HarnessState uses json_to<HarnessState>() with nested Message and HarnessConfig

Update all call sites in src/harness.cpp (ollama_client.cpp), src/debug_client.cpp,
src/main.cpp to use json_to<T>() instead of json_to_T().

All 28 tests pass.
2026-05-28 07:30:14 -05:00
darthrubik acb5dd21cc Implement issue 61: Required Types template specializations
- Update DRH_DEFINE_TYPE_REQUIRED macro to generate template specialization
  json_to<Type>() in addition to the existing json_to_##Type() function
- Add template specialization for ChatResponse
- Update main.cpp -R flag handler for FunctionCall, Usage, ModelInfo, ModelListResponse
- Update ollama_client.cpp to use json_to<ChatResponse>() and json_to<ModelListResponse>()
- Update json_to_ChatResponse to use json_to<Usage>() for nested Usage field

All 28 tests pass.
2026-05-28 07:24:50 -05:00
darthrubik 25b815cebe Issue #60: Convert remaining optional types to json_to<T>() template syntax
Update ListFilesToolParams, GrepToolParams, and ShellToolParams
call sites to use the template json_to<T>() function instead of
the old json_to_##Type() style.

- tools/file_list_tool.cpp: Use json_to<ListFilesToolParams>()
- tools/grep_tool.cpp: Use json_to<GrepToolParams>()
- tools/shell_tool.cpp: Use json_to<ShellToolParams>()
- src/main.cpp: Update -R flag handler for these three types
2026-05-28 07:18:47 -05:00
darthrubik 2252cd5e30 Implement template json_to<T>() infrastructure with HarnessConfig specialization
- Add template forward declaration json_to<T>() in json_types.hpp
- Update DRH_DEFINE_TYPE_OPTIONAL macro to generate template specialization
  json_to<Type>() while keeping the old json_to_##Type() function
- Update main.cpp -R flag handler for HarnessConfig to use json_to<HarnessConfig>()

Closes issue #59
2026-05-28 07:12:25 -05:00
darthrubik 7514557b94 Remove try/catch from grep_tool.cpp (issue #58)
- Add make_regex_safely() helper to json_types.hpp that wraps std::regex
  construction and returns std::expected<std::regex, std::string>
- Replace try/catch for regex compilation with make_regex_safely()
- Replace try/catch for filesystem operations with std::error_code overloads
  (following the pattern in skill.cpp)

All acceptance criteria met:
- No try/catch blocks in tools/grep_tool.cpp
- Invalid regex patterns produce clear error messages
- Filesystem errors during directory iteration produce clear error messages
- test_tool_grep.sh passes
- No throw statements in modified file
2026-05-27 18:26:51 -05:00
darthrubik 2a927dc503 issue 57: Convert remaining tool parameters to json_to<T>()
Apply the parameter conversion pattern from issue 55 to all remaining
tools. Each tool's convert_params lambda now uses json_to_<Type>()
instead of .get<T>() for exception-free parameter parsing.

Tools converted:
- shell_tool -> json_to_ShellToolParams()
- file_read_tool -> json_to_ReadFileToolParams()
- file_list_tool -> json_to_ListFilesToolParams()
- grep_tool -> json_to_GrepToolParams()
- skill_tool -> json_to_SkillToolParams()

All 28 tests pass.
2026-05-27 18:18:27 -05:00
darthrubik 62739d7fcb Issue 56: Remove try/catch from skill module directory scanning
Replace the try/catch block in collect_paths() lambda with explicit
std::error_code-based error handling using recursive_directory_iterator
constructor and increment() methods that don't throw exceptions.

Error messages include the path that failed and the underlying error
message from the filesystem operation.

Added test_skill_unreadable_dir.sh to verify proper error handling when
a directory is unreadable.
2026-05-27 18:14:25 -05:00
darthrubik d2b94485c4 Issue 55: Tool Parameter Error Handling Infrastructure
Change the Tool interface to support error handling during parameter conversion:
- ToolParamsConverter now returns std::expected<std::any, std::string>
- ToolRegistry::invoke() handles conversion errors and returns them as tool result strings
- Replace NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE with DRH_DEFINE_TYPE_REQUIRED for SkillToolParams and AgentTaskParams
- edit_tool uses json_to_EditToolParams() instead of args.get<EditToolParams>()
- agent_tool uses json_to_AgentTaskParams() instead of args.get<AgentTaskParams>()
- Add test for missing required field error handling
2026-05-27 18:09:20 -05:00
darthrubik 43a7db9d96 docs: add error handling rules to CONTRIBUTING.md
Add a new Error Handling section documenting the project's error handling
conventions using std::expected:

- JSON parsing must use parse_json_safely()
- JSON deserialization must use json_to<T>()
- No try/catch blocks — handle std::expected explicitly
- No new exceptions allowed
- Error handling uses std::expected<T, std::string> exclusively
- Library code returns std::expected; expect_or_exit() for top-level code only

Also update C++ version requirement from C++20 to C++23 in Prerequisites.

Fixes: #53
2026-05-27 17:48:07 -05:00
darthrubik 111deec596 issue 52: Remove try/catch from main.cpp print_file section
Replace try/catch block in -p (print_file) mode with explicit
std::expected-style error checking using nlohmann::json::parse()
with the allow_discarded parameter and is_discarded() check.

This follows the pattern already used elsewhere in main.cpp
(line 541) for loading chat files.
2026-05-27 17:44:53 -05:00
darthrubik 4bce22b66f Implement issue 51: Harness::chat() returns std::expected with complete error handling
- Updated Harness::chat() to return std::expected<std::string, std::string>
- Client errors are retried up to 20 times, then returns 'max retries exceeded'
- Tool invocation errors are returned as tool result string to LLM
- Max tool iterations returns 'max tool iterations exceeded' error
- Removed try/catch block for JSON parsing, using parse_json_safely() instead
- Updated main.cpp to handle expected return type from chat()
- Updated agent_tool.cpp to handle expected return type from chat()
- Added tests for client retry exhaustion, max tool iterations, and tool invocation errors
2026-05-27 17:40:04 -05:00
darthrubik 8679052334 Implement issue 50: Use std::expected return types in skill module
- parse_skill_frontmatter() now returns std::expected<SkillMetadata, std::string>
- scan_skills() now returns std::expected<std::vector<SkillMetadata>, std::string>
- All throw statements replaced with return std::unexpected()
- Filesystem scan errors are now fatal instead of warnings
- Updated main.cpp to handle the new return types
2026-05-27 17:19:29 -05:00
darthrubik f13007602b Issue 49: Editor Module - Use std::expected return type
Update run_editor() to return std::expected<std::string, std::string>
for all editor-related failures instead of throwing exceptions.

Changes:
- include/editor.hpp: Change return type to std::expected
- src/editor.cpp: Replace all throw std::runtime_error() with
  return std::unexpected() for error cases:
  * Temp file open failure
  * No nvim/ set
  * Editor exit error
  * Scissors separator not found
  * Empty prompt after separator
- src/main.cpp: Use expect_or_exit() instead of try/catch
2026-05-27 17:14:36 -05:00
darthrubik f85abd85b6 Issue #48: Update ToolRegistry methods to return std::expected
- register_tool() returns std::expected<void, std::string> for duplicate detection
- invoke() returns std::expected<std::string, std::string> for unknown tool errors
- create_subregistry() returns std::expected<ToolRegistry, std::string> for missing tools
- All throw statements removed from tool_registry.cpp
- Updated main.cpp to use expect_or_exit() for startup tool registration
- Updated harness.cpp and agent_tool.cpp to handle new return types

All 26 existing tests pass.
2026-05-27 17:11:26 -05:00
darthrubik b699f20eed Issue 47: Client Module: std::expected Return Types
Update the Client abstract interface and its implementations to return
std::expected instead of throwing exceptions.

Changes:
- Client interface: chat() and list_models() now return std::expected
- OllamaClient: replaced all throw statements with return std::unexpected()
- DebugClient: replaced all throw statements with return std::unexpected()
- Removed OllamaClientError custom exception class
- Harness: updated to handle std::expected returns from Client
- main.cpp: updated list_models() call to handle std::expected

All 26 existing tests pass.
2026-05-27 17:05:28 -05:00
darthrubik 65334b3480 Implement json_to<T>() for Message, Choice, and HarnessState (#46)
- Add json_to<Message>() with role required, other fields optional
- Add json_to<Choice>() with message required, finish_reason optional
- Add json_to<HarnessState>() with history/tools required, config optional
- Update json_to_ChatResponse() to use json_to_Choice() for nested types
- Remove from_json() functions for Message, Choice, HarnessState, ChatResponse
- Update ollama_client.cpp and debug_client.cpp to use json_to_* functions
- Update main.cpp to use json_to_HarnessState() for loading chat files
- Add test cases for Message, Choice, and HarnessState types
- Add test_json_type_mixed.sh test file
2026-05-27 16:58:40 -05:00
darthrubik 0654d49e2f feat: implement DRH_DEFINE_TYPE_OPTIONAL macro
- Add DRH_DEFINE_TYPE_OPTIONAL macro for structs with all optional fields
- Missing JSON fields use struct member defaults
- Convert HarnessConfig, ListFilesToolParams, GrepToolParams, ShellToolParams
  from NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE[_WITH_DEFAULT] to new macro
- Add test handling for new types in main.cpp -R flag
- Add comprehensive tests for optional type handling

Closes issue 45
2026-05-27 16:47:53 -05:00
darthrubik b79a767eba feat: implement DRH_DEFINE_TYPE_REQUIRED macro for JSON type safety
- Added DRH_DEFINE_TYPE_REQUIRED macro that generates to_json(), from_json(),
  and json_to<Type>() functions for structs with all required fields
- Macro uses NLOHMANN_JSON_PASTE and NLOHMANN_JSON_EXPAND for variadic expansion
- Missing required fields produce clear error messages via std::expected
- Converted the following structs from NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE:
  - FunctionCall
  - ToolCall (with custom json_to for nested FunctionCall handling)
  - Usage
  - ChatResponse (with custom json_to for nested Usage handling)
  - ModelInfo
  - ModelListResponse
  - EditToolParams
  - ReadFileToolParams
- Added -R CLI flag for testing json_to<Type>() functions
- Added comprehensive bash tests in test_json_type_required.sh

Closes issue #44
2026-05-27 16:39:41 -05:00
darthrubik 6de57de2f5 Issue #43: Foundation - C++23 upgrade and basic error infrastructure
- Upgrade CMakeLists.txt from C++20 to C++23 to enable std::expected
- Add parse_json_safely() to json_types.hpp: wraps json::parse with
  allow_exceptions=false, returns discarded JSON on parse failure
- Add expect_or_exit() to json_types.hpp: helper for std::expected that
  prints error to stderr and exits on failure
- Add test flags (-J, -E, -e) to CLI for testing the new functions
- Add bash tests for parse_json_safely() and expect_or_exit()
2026-05-27 16:27:41 -05:00
darthrubik 673d39de2c Merge pull request 'New contribution guidelines' (#26) from feature/new-guidelines into master
Reviewed-on: https://192.168.8.63/git/darthrubik/drharness/pulls/26
2026-05-27 15:12:30 -05:00
darthrubik f813b156c4 New contribution guidelines 2026-05-27 15:08:15 -05:00
darthrubik fab9e4930c Merge pull request 'feature/auto-file-creation' (#25) from feature/auto-file-creation into master
Reviewed-on: https://192.168.8.63/git/darthrubik/drharness/pulls/25
2026-05-21 17:02:29 -05:00
darthrubik 16921492bb Resolve dangling pointer issue by reversing assignment order
The bug was in src/main.cpp where temp_file_path.string().c_str() was called
on a temporary std::string object. The temporary was destroyed at the end of
the statement, leaving file_to_open pointing to deallocated memory.

Fixed by assigning to auto_temp_file first (which extends the lifetime), then
taking its c_str() for file_to_open.
2026-05-21 17:00:26 -05:00
darthrubik cb41065347 update README to reflect auto-generated temp file for -n flag 2026-05-21 16:49:24 -05:00
darthrubik 44db144bfb Auto-generate temp file with -n flag
- Added UUID v4 generation using <random> and <filesystem> headers
- When -n is specified without -F, create a temp file in system temp dir
- Temp file is named harness-<uuid>.json
- Print temp file path to stderr when session exits
- Updated help message to reflect new behavior
- Added test_auto_temp_file.sh to verify:
  - Temp file creation with -n alone
  - Path printed to stderr
  - Existing -F <file> -n behavior preserved
  - UUID uniqueness
2026-05-21 16:49:19 -05:00
darthrubik 20dd58e6d4 Updating contributing.md 2026-05-21 16:20:54 -05:00
darthrubik ca3a3b0d3e Merge pull request 'feature/skills' (#24) from feature/skills into master
Reviewed-on: https://192.168.8.63/git/darthrubik/drharness/pulls/24
2026-05-21 15:28:28 -05:00
darthrubik 6a248918cf Remove smart pointer/pointer semantics from skill tool logic
- Changed skill_map to store SkillMetadata by value instead of using shared_ptr to pointers
- Removed unnecessary #include <memory>
- Updated lambda capture to move the map by value
- All unit tests pass
2026-05-21 15:10:02 -05:00
darthrubik f0160618da Align Skill Tool contract with PRD
- Register skill tool as 'skill' instead of 'skill_tool'
- Change parameter name from 'skill_name' to 'name'
- Update XML output to include name attribute: <skill_content name="...">
- Update test_tool_skill.sh and test_skill_filter.sh to reflect new names
2026-05-21 15:05:09 -05:00
darthrubik e62d63aa6f Correct stderr capture in test_skill_validation.sh
- Fixed Bash redirection bug () that prevented capturing stderr output.
- Replaced with  which correctly captures stderr (since stdout is empty on failure).
- Ensures validation checks for clean error messages and non-zero exit codes work as intended.
2026-05-21 15:05:09 -05:00
darthrubik d8e5027bc0 Graceful error handling for malformed SKILL.md files
- Wrap skill discovery and validation in a try-catch block to intercept std::runtime_error.
- Print a clean diagnostic message to stderr (e.g., 'Fatal: Malformed SKILL.md at <path>').
- Exit immediately with code 1 instead of crashing with a C++ stack trace.
- Update tests/test_skill_validation.sh to verify the clean error message and exit code.
2026-05-21 15:05:06 -05:00
darthrubik 5fd0ec56a1 Refactor skill module architecture: consolidate into single skill.hpp/skill.cpp
Consolidate skill_parsing, skill_scanner, and skill_tool modules into
a single skill.hpp / skill.cpp pair as specified in issue #16.

Changes:
- Created include/skill.hpp with all declarations
- Created src/skill.cpp with all implementations
- Updated CMakeLists.txt to use the new consolidated file
- Updated src/main.cpp to include the new consolidated header
- Removed old files: skill_parsing.hpp/cpp, skill_scanner.hpp/cpp,
  skill_tool.hpp/cpp

All 21 tests pass without regression.
2026-05-21 15:04:40 -05:00
darthrubik 012a3e1588 Isolate global skill paths in temp directory per CONTRIBUTING.md 2026-05-21 15:04:37 -05:00
darthrubik c6b5a7c9c5 Implement local skill override for global skills and add unit test 2026-05-21 15:04:32 -05:00
darthrubik 5d23854882 Refactor SkillScanner from class to free functions
- Replace SkillScanner class with free functions (scan_skills, print_discovered_paths, print_skills)
- Extend SkillMetadata with raw_content, files, and directory fields
- Update parse_skill_frontmatter to extract raw markdown content after YAML frontmatter
- Refactor make_skill_tool to take std::vector<SkillMetadata> and perform minimal logic (lookup and format pre-computed data)
- Update main.cpp to use new free functions
- All 20 tests pass
2026-05-20 17:14:47 -05:00
darthrubik 7a7a8abd16 Include skill descriptions in skill_tool description
- Updated make_skill_tool to accept a descriptions map
- Modified skill_tool.cpp to build a comprehensive description listing all available skills and their descriptions
- Updated main.cpp to populate and pass the descriptions map
- Updated test_tool_skill.sh to verify tool execution with loaded skills
2026-05-20 16:59:35 -05:00
darthrubik 9146cb67b6 Removing unused files 2026-05-20 16:28:17 -05:00
darthrubik 72b5f644c4 Validation, Error Handling & Full Integration
- Remove fallback behavior for malformed SKILL.md files in skill_scanner.cpp
- Now harness crashes immediately on invalid YAML frontmatter
- Updated tests to use proper YAML frontmatter
- Added new test_skill_validation.sh to verify crash behavior
- Removed test that expected fallback for invalid frontmatter
2026-05-20 16:22:08 -05:00
darthrubik 35e30a4ddf More tests 2026-05-20 16:22:08 -05:00
darthrubik 2831b06d0d CLI Flag -s (Enable Skills) & Registry Filtering
- Add  flag to filter the list of enabled skills.
- Updated argument parsing to handle  flag multiple times.
- Modified  construction to filter skills based on provided names.
- Added test  to verify non-loaded tools error when called.
- Updated README.md to document the new  flag.
- Registered new test in tests/CMakeLists.txt.
2026-05-20 16:22:05 -05:00