320 Commits

Author SHA1 Message Date
darthrubik 4393a5dba2 Remove old test_edit_history_flag.sh (issue 125) 2026-06-02 16:17:20 -05:00
darthrubik aa04ad0902 Split test_edit_history_flag.sh into individual test scripts (issue 125) 2026-06-02 16:16:42 -05:00
darthrubik 0366d3db57 Issue 124: Break test_edit_history.sh into multiple test scripts
Split the monolithic test_edit_history.sh file into 21 individual
test scripts, each containing a single test. This follows the
project's testing guideline of having no more than one test per script.

Each test file contains its own helper functions (run_drharness,
get_output, reset_editor_state) and runs independently.

Updated CMakeLists.txt to register all 21 new test targets and
removed the old test_edit_history target.
2026-06-02 16:12:23 -05:00
darthrubik ac755f5083 Slop cleanup 2026-06-02 15:55:30 -05:00
darthrubik 69bd732ce7 Fix: Drop messages when lines are deleted from rebase editor (issue 120)
- Modified parse_rebase_file to skip header comments before '-- >8 --'
- Modified apply_rebase to drop messages whose indices are not mentioned
  in the parsed commands (implicit drop)
- Special case: if no commands are parsed (all lines deleted), keep all messages
- Updated test_implicit_pick to reflect new behavior
- Added test_deleting_lines_drops_messages and test_all_lines_deleted_keeps_all
- Updated test_delete_line_equals_drop to reflect new behavior
2026-06-01 19:33:46 -05:00
darthrubik eee99a53c9 Replace std::stoul with custom string_toul wrapper
Add string_toul() function in utility.cpp that wraps std::stoul and returns
std::expected<unsigned long, std::string> instead of throwing exceptions.

Replace all uses of std::stoul with string_toul():
- src/main.cpp: 2 occurrences in test flags
- src/rebase_engine.cpp: 1 occurrence in parse_rebase_file()

The wrapper handles null input, empty input, and trailing characters,
converting any exceptions from std::stoul into error strings.
2026-06-01 19:11:32 -05:00
darthrubik 938f85a017 Refactor rebase_engine.cpp to use helper function for line number error formatting
- Added format_error() helper function to centralize line number error formatting
- Replaced all 11 occurrences of inline line number formatting with calls to format_error()
- No functional changes - all 33 tests pass
2026-06-01 18:47:05 -05:00
darthrubik 211f42a87e Implement issue 119: Exit on first parse error instead of aggregating all errors
- Removed the errors vector from parse_rebase_file()
- Changed all error handling to return std::unexpected() immediately
- Removed the error aggregation code at the end of the function
- Updated test to verify first error is returned (not all errors)
2026-06-01 18:22:13 -05:00
darthrubik 5351f4f72a Refactor: Extract read_harness_state_or_exit() helper function
This refactors the duplicated code for deserializing HarnessState from a file
into a reusable helper function. The same pattern was used in two places:
1. In the edit_history block
2. In the main flow when loading existing chat files

The helper function handles:
- Opening the file
- Reading file content
- Parsing JSON
- Converting to HarnessState
- Error reporting

This reduces code duplication and makes future modifications easier.
2026-06-01 18:14:41 -05:00
darthrubik 8441775410 Move History Editing section to docs/history-editing.md
As per issue #115, moved the History Editing documentation from README.md
to a separate docs/history-editing.md file. Added a short note with a link
to the new documentation in README.md.
2026-06-01 09:32:30 -05:00
darthrubik e9e19c199f Add documentation for history editing feature
Document the -b/--edit-history flag and rebase editor format.
Covers commands (pick/edit/drop), role editing, and common workflows.
2026-06-01 08:22:26 -05:00
darthrubik 7fe1656c7d Add edge case tests for history editing feature (issue 114)
- Add test for editing a message and removing all content (empty content)
- Add test for multiple consecutive edits to the same message (changes stack)
- Add test for editing messages with special characters
- Add test for very long message content
- Add test for editing assistant messages with tool_calls
- Add test for dropping all messages (empty history result)
- Add test for unicode characters in message content
- Add test for whitespace variations in rebase file
- Add test for comments and empty lines mixed with commands
- Add test for special characters and unicode in rebase content display
- Fix apply_rebase to preserve original content when only role is changed
- Add content_edited flag to RebaseLine to track explicit edits
2026-05-30 19:49:05 -05:00
darthrubik 51c911df8d Fix issue 112: Make test flags minimal hooks
- Changed parse_json_safely to return std::expected<nlohmann::json, std::string>
- Updated all usages to handle the expected return type properly
- Simplified test flags in main.cpp to be minimal hooks without extra validation
- Removed try/catch blocks and explicit error handling from test flags
- All tests pass
2026-05-30 18:14:15 -05:00
darthrubik db3e7b7d29 Fix: Make -J flag a minimal hook
The -J flag was outputting 'valid' or 'discarded' instead of the raw
JSON result. This was test logic that should be in the test scripts.

Changed the -J flag to output the raw JSON result (pretty-printed) or
an error message. Updated the test script to use jq for JSON validation.

All other debug flags (-E, -e, -R, -P, -B, -A, -T) were already minimal
hooks and didn't need to be changed.
2026-05-30 17:42:09 -05:00
darthrubik e129fcc99a Issue #110: Remove thick debug flags from main.cpp
Per CONTRIBUTING.md, test flags should be minimal hooks that:
- Accept input, call the internal function, and output the result
- Do NOT contain test logic (assertions, validation, etc.)

Changes:
1. Added json_to<RebaseCommand> and json_to<RebaseLine> serialization
   so the -A flag can use json_to<std::vector<RebaseLine>> instead of
   60+ lines of manual JSON parsing.

2. Simplified -P flag to output JSON directly instead of formatted
   text with switch statements.

3. Added helper functions to run_test.sh for parsing rebase JSON output
   (rebase_line_count, rebase_command_at, rebase_index_at, rebase_role_at).

4. Updated test_rebase_parser.sh to use JSON output and helper functions.

The -E, -e, -R, -J, -B, and -T flags are already minimal hooks and
remain unchanged.
2026-05-30 14:38:20 -05:00
darthrubik 11073a12c8 Update CONTRIBUTING.md to clarify test flag policy
Clarify that test flags should be minimal hooks into internal
functionality, not contain test logic. All test logic (assertions,
validation) belongs in Bash test scripts in tests/.

This addresses the ambiguity mentioned in issue 109 regarding
the distinction between test hooks in main.cpp and test scripts.
2026-05-30 14:20:05 -05:00
darthrubik 643388a24d Fix issue 108: Replace manual JSON history creation with helper function
Add create_chat_file() helper to run_test.sh and update test_edit_history.sh
and test_edit_history_flag.sh to use it instead of manually creating JSON.

This follows the CONTRIBUTING.md guideline that tests should use helper
functions instead of inline JSON handling.
2026-05-30 13:55:23 -05:00
darthrubik 1cb6553d85 Fix issue #107: Move JSON parsing from test file to helper functions
- Add dr_reasoning() helper to run_test.sh
- Add dr_reasoning_content() helper to run_test.sh
- Update process_reasoning.sh to use helper functions instead of direct jq calls

This follows CONTRIBUTING.md guidelines that prohibit direct JSON
parsing in test files.
2026-05-30 13:42:39 -05:00
darthrubik 9d8ec6c308 Fix issue #106: Place explanatory text before scissors in rebase editor
The edit prompt now shows the explanatory text 'Edit message X (role: Y) below:'
BEFORE the scissors (-- >8 --) separator, not after. This ensures the
explanatory text is not part of the editable content.

Also added test_edit_prompt_noop to verify that doing nothing at the edit
prompt stage keeps history unchanged.
2026-05-30 13:36:38 -05:00
darthrubik 56873828ad Add test for no edits scenario (issue #105)
- Added new test test_no_edits_history_unchanged that verifies when the
  user does nothing with the rebase editor, the history remains unchanged
- Fixed staged input format in all tests to match actual build_rebase_content
  output (pick commands only after -- >8 -- scissors line, not before)
2026-05-30 13:24:30 -05:00
darthrubik 62dabd0f93 Add scissors line to rebase editor output
The rebase editor now includes a scissors line (-- >8 --) that separates
the comment header from the actual pick commands. This helps editors like
Vim automatically position the cursor at the relevant content and signals
to users where the editable commands begin.

Issue: #104
2026-05-30 13:17:09 -05:00
darthrubik 271ac96100 Refactor: Extract shared utilities from rebase_engine and editor
Create new utility module (include/utility.hpp, src/utility.cpp) containing
common code that was duplicated between rebase_engine.cpp and editor.cpp:

- trim() function
- random_string() function
- random_file_name() function
- Defer RAII helper class
- spawn_editor() function

Both rebase_engine.cpp and editor.cpp now include utility.hpp instead of
duplicating these functions.

All 33 unit tests pass.
2026-05-29 16:30:54 -05:00
darthrubik 8f43e668b6 Fix #102: Update README to say C++23 not C++20
- Updated project description to mention C++23
- Updated build requirements to mention C++23
2026-05-29 16:27:00 -05:00
darthrubik c46ebb665b Issue 101: Update file operation tool descriptions to indicate paths can be relative or absolute 2026-05-29 16:25:22 -05:00
darthrubik 4c3d24f2c2 Fix CONTRIBUTING.md violations from auto review
- Remove namespace alias in src/rebase_engine.cpp (use full std::filesystem:: path)
- Add dr_role() helper function to tests/run_test.sh
- Replace inline JSON parsing in tests/test_edit_history.sh with dr_role() helper
2026-05-29 16:21:38 -05:00
darthrubik b507436801 Implement issue #100: Full Interactive Edit Flow and Integration Tests
- Add edit_history_interactive() method to HistoryEditor class
  - Implements the complete history editing flow:
    1. Build rebase content from history
    2. Spawn editor for rebase commands
    3. Parse and validate rebase commands
    4. For each edit command, build edit prompt and spawn editor
    5. Apply all changes
  - Returns expected<vector<Message>, string>

- Add content field to RebaseLine struct for storing edited content
- Update apply_rebase() to use content field when present
- Wire up -b/--edit-history flag to call edit_history_interactive()
- Program exits after successful edit and saves modified history

- Add 12 integration tests for the edit history feature:
  1. Pick all messages (history unchanged)
  2. Drop a single message
  3. Edit message content
  4. Change message role
  5. Delete line equals drop (implicit pick)
  6. Edit first message (no context)
  7. Empty history rejected
  8. Invalid command error
  9. Invalid index error
  10. Invalid role error
  11. Multiple edits in one session
  12. Edit then continue conversation normally

- Register test_edit_history in CMakeLists.txt
2026-05-29 16:18:00 -05:00
darthrubik 1d799cc05d Implement build_edit_prompt for history editing (issue #99)
- Add HistoryEditor::build_edit_prompt() to format edit prompts
  with previous message context and >8 separator
- Message 0 shows '(no previous message)' instead of context
- Add -T debug flag to test build_edit_prompt
- Extend mock_editor.sh to support sequential invocations via
  counter file and staged input files
- Add unit tests for build_edit_prompt functionality
- Register new test in CMakeLists.txt
2026-05-29 15:43:47 -05:00
darthrubik e8ebb08106 Fix: Use json_to_field() for JSON field access instead of .at().get<T>()
- Replace .at("field").get<T>() with json_to_field<T>() calls
- Use .contains() and .value() for safe field access
- Follows CONTRIBUTING.md rule to always use json_to<T>() for JSON deserialization
2026-05-29 15:37:20 -05:00
darthrubik a1aa63b260 Implement RebaseEngine::apply_rebase() for issue #98
- Added apply_rebase() method to RebaseEngine class
- Pick commands preserve messages unchanged
- Drop commands remove messages from result
- Edit commands update role while preserving all other fields
- Messages not referenced by any command are preserved (implicit pick)
- Lenient mode: orphaned tool results are allowed
- Added -A test flag for testing apply_rebase
- Added unit tests in test_rebase_apply.sh
2026-05-29 15:31:49 -05:00
darthrubik 9e45bebc3d Implement issue 97: Add -b/--edit-history CLI flag with validation
Add the -b/--edit-history CLI flag to main.cpp with the following behavior:
- Flag is recognized in both short (-b) and long (--edit-history) forms
- Requires -F <chat_file> to be specified, otherwise prints error and exits
- Rejects empty chat files with error message
- Rejects chat files with no history (empty history array) with error message
- Updates help message to document the new flag

This is a thin slice that validates the CLI integration point before the
full history editing feature is built.

Includes integration tests covering all error cases.
2026-05-29 15:24:59 -05:00
darthrubik a1e0a299cd Implement HistoryEditor::build_rebase_content() for issue 96
- Added HistoryEditor class with build_rebase_content() method
- Formats messages for rebase editor display:
  - Normal messages show first line of content
  - Assistant messages with tool_calls show [tool_call: function_name]
  - Tool messages show first line of result content
  - Empty messages show (empty)
- Added header comment block explaining pick/edit/drop commands
- Added -B CLI flag for testing
- Added unit tests covering all display format scenarios
2026-05-29 15:13:25 -05:00
darthrubik adc4be09ce Implement RebaseEngine::parse_rebase_file() for issue #95
Add rebase parser that validates rebase editor format with:
- RebaseCommand enum (Pick, Edit, Drop)
- RebaseLine struct (command, index, role)
- parse_rebase_file() with strict validation and error aggregation

Validations:
- Invalid commands (not pick/edit/drop)
- Index out of range
- Duplicate indices
- Missing/malformed role brackets
- Invalid role values (must be user/assistant/tool)
- Comment lines (#) and empty lines are skipped

Tests cover all validation scenarios via -P debug flag.
2026-05-29 15:05:57 -05:00
darthrubik 2b2829dba7 Merge pull request 'refactor: remove std::any from codebase (issue #90)' (#29) from feature/remove-any into master
Reviewed-on: https://192.168.8.63/git/darthrubik/drharness/pulls/29
2026-05-29 14:14:58 -05:00
darthrubik b327df20a2 refactor: remove std::any from codebase (issue #90)
Replace std::any-based parameter passing with compile-time type-safe adapter pattern.

- Add create_tool_execute<T>() template function in tool.hpp
- Remove convert_params field from Tool struct
- Change execute field type from std::function<std::string(const std::any&)>
  to std::function<std::string(const nlohmann::json&)>
- Change ToolRegistry::invoke() return type from std::expected to std::string
- Update all tool implementations to use create_tool_execute<ParamsType>()
- Remove #include <any> from tool.hpp and json_types.hpp
- Simplify harness.cpp invoke caller

All 27 tests pass.
2026-05-29 14:11:09 -05:00
darthrubik 9fd6782ecd Merge pull request 'feature/remove-exceptions' (#28) from feature/remove-exceptions into master
Reviewed-on: https://192.168.8.63/git/darthrubik/drharness/pulls/28
2026-05-29 14:10:39 -05:00
darthrubik 092dd92216 feat: add tool_call_raw helper and use it in test_tool_invocation_failure
Per CONTRIBUTING.md, inline JSON generation in tests should use helper
functions from run_test.sh. Added tool_call_raw() helper that generates
tool call responses without sanitizing the arguments (unlike tool_call()
which escapes JSON). Updated test_tool_invocation_failure to use the new
helper instead of inline JSON.

Closes issue 87
2026-05-29 07:39:20 -05:00
darthrubik a12891199b Fix test_client_retry_exhaustion to actually test retry limit
The original test provided 20 invalid JSON responses and checked for
'max retries exceeded'. However, the retry loop only attempts 19 calls
before hitting the limit, and providing fewer responses (e.g., 10) would
still pass because stdin exhaustion also eventually triggers the same
error message.

This fix:
1. Changes the response count from 20 to 19 (actual retry attempts)
2. Adds a check to ensure no 'failed to read from stdin' messages appear,
   proving the test validates retry exhaustion rather than stdin exhaustion
3. Now if the count is reduced to 10, the test will correctly fail

Fixes issue 88
2026-05-28 17:12:45 -05:00
darthrubik 2429637d09 Fix issue 89: Improve expect_or_exit error test to verify stderr output
The previous test only checked exit code was non-zero but discarded all
output. Now the test captures stderr and verifies both the exit code and
that the correct error message was printed, making it a meaningful test of
expect_or_exit behavior.
2026-05-28 17:06:36 -05:00
darthrubik fddecde9bd Removing more unneeded tests 2026-05-28 16:53:13 -05:00
darthrubik 62acfd1acc Removing unneeded tests 2026-05-28 16:46:50 -05:00
darthrubik 8f12972492 Fix: Exception safety in json_to helpers and add deeply nested type test
Issue 86

1. Added try-catch in json_to_field for primitive types to catch
   nlohmann::json::exception and convert to std::unexpected.

2. Added try-catch in json_to_impl::apply for primitive types to catch
   nlohmann::json::exception and convert to std::unexpected.

3. Added test for deeply nested type error propagation:
   Message → vector<ToolCall> → FunctionCall error propagation.

This ensures that type mismatches (e.g., string where int expected)
return descriptive error messages instead of crashing with exceptions.
2026-05-28 15:57:17 -05:00
darthrubik e28089aa25 Removing random junk agent added 2026-05-28 15:45:28 -05:00
darthrubik 73cc7668e2 Remove duplicated logic in JSON macros (issue 85)
Extract the duplicated has_json_to<Type> registration and json_dispatch_map
registration code from DRH_DEFINE_TYPE_REQUIRED, DRH_DEFINE_TYPE_OPTIONAL,
and DRH_DEFINE_TYPE_MIXED into two helper macros:
- DRH_JSON_REGISTER_HAS_JSON_TO(Type)
- DRH_JSON_REGISTER_DISPATCH(Type)

This eliminates code duplication while preserving identical functionality.
2026-05-28 15:43:02 -05:00
darthrubik fbc8b4e54d Replace all direct calls to nlohmann::json::parse with parse_json_safely (#84)
As per CONTRIBUTING.md, nlohmann::json::parse should not be called directly.
Replace all direct calls with the safe wrapper function parse_json_safely().

- debug_client.cpp: Use parse_json_safely for stdin input
- ollama_client.cpp: Use parse_json_safely for HTTP response parsing
- main.cpp: Read file content to string, then use parse_json_safely
2026-05-28 15:35:06 -05:00
darthrubik c0d14de8d8 Implement json_dispatch_map and macro auto-registration (issue #80)
- Add #include <unordered_map> and #include <functional> to json_types.hpp
- Declare inline dispatch map for -R debug flag
- Add auto-registration IIFE to DRH_DEFINE_TYPE_REQUIRED macro
- Add auto-registration IIFE to DRH_DEFINE_TYPE_OPTIONAL macro
- Add auto-registration IIFE to DRH_DEFINE_TYPE_MIXED macro
- Add manual registration for vector<FunctionCall> (cannot use macros)
- Replace ~140-line if-else chain in main.cpp with simple map lookup

All 30 tests pass.
2026-05-28 15:23:42 -05:00
darthrubik 2fbccadab8 issue 76: Convert HarnessState to use DRH_DEFINE_TYPE_MIXED macro
- Replace hand-written json_to<HarnessState>() specialization with DRH_DEFINE_TYPE_MIXED macro
- Remove hand-written to_json() free function for HarnessState
- Remove explicit has_json_to<HarnessState> trait registration (now handled by macro)
- Update test to expect new error format that includes field path (more informative)

HarnessState now uses:
  DRH_DEFINE_TYPE_MIXED(HarnessState, (history, tools), (config))

All 30 tests pass.
2026-05-28 14:20:53 -05:00
darthrubik 3461a92ce2 Replace hand-written json_to<Choice>() with DRH_DEFINE_TYPE_MIXED macro
- Remove hand-written json_to<Choice>() specialization and to_json() function
- Add DRH_DEFINE_TYPE_MIXED(Choice, (message), (finish_reason)) macro call
- Update test expectations for nested error propagation to include field path prefix
  (e.g., 'field message: missing required field: role' instead of 'missing required field: role')

This change improves consistency with the PRD goal of having all json_to<T>()
implementations use macros, and provides better error messages with field path
information for debugging.
2026-05-28 14:15:51 -05:00
darthrubik a741891048 Issue #74: Convert Message to use DRH_DEFINE_TYPE_MIXED macro
- Remove hand-written json_to<Message>() template specialization
- Remove custom to_json() member method from Message struct
- Add DRH_DEFINE_TYPE_MIXED(Message, (role), (content, reasoning, reasoning_content, tool_call_id, tool_calls))
- Preserve dump() method as out-of-line member function that delegates to free to_json()
- All 30 tests pass
2026-05-28 14:08:46 -05:00
darthrubik 3b4ecb0cbc Issue #73: Convert MixedFieldsStruct to use DRH_DEFINE_TYPE_MIXED macro
- Replace hand-written json_to<MixedFieldsStruct>() specialization with DRH_DEFINE_TYPE_MIXED macro
- Add tests for valid MixedFieldsStruct deserialization
- Add tests for missing required field errors (name, count, func)
- Add test for nested type error propagation (FunctionCall inside MixedFieldsStruct)
2026-05-28 13:58:04 -05:00
darthrubik de78daa6f7 Implement DRH_DEFINE_TYPE_MIXED macro with tests (issue 72)
Add DRH_DEFINE_TYPE_MIXED(Type, (required_fields...), (optional_fields...))
macro that generates to_json() and json_to<Type>() for structs with mixed
required and optional JSON fields.

Features:
- Required fields: checks presence, reads via json_to_field<T>() for proper
  nested type error propagation
- Optional fields: checks contains() and !is_null() before reading; uses
  struct member defaults otherwise
- Generates free to_json() that sets ALL fields (required and optional)
- Automatically registers has_json_to<Type>
- Supports empty field groups: (), () for all-optional/all-required types
- Uses __VA_OPT__ (C++20+) for empty group handling

Added test structs:
- MixedTypeSmokeTest: one required + one optional field
- MixedTypeAllRequiredTest: empty optional group
- MixedTypeAllOptionalTest: empty required group

Updated tests/test_json_type_mixed.sh with smoke tests and edge case tests.
2026-05-28 13:47:47 -05:00